Grrruk
Grrruk

Reputation: 438

Using SendMessage in python to set text in foreground window

We have an old, legacy database that needs input from another system. SendInput method of data input into database forms is slow and unreliable, setting clipboard and then ^v is not reliable either (I have no idea why, but database interface is very old, early 2000s). After a lot of fiddling I discovered that using SendMessage to set text and then sending VK_RETURN is fast (much faster than SendInput/keybd_event) and reliable with our database. Now this code in plain C works:

    HWND fghwnd = GetForegroundWindow();
    DWORD threadId = GetWindowThreadProcessId(fghwnd, NULL);
    DWORD myId = GetCurrentThreadId();
    if (AttachThreadInput(myId, threadId, true)) {
        HWND ctrl = GetFocus();
        SendMessage(ctrl, WM_SETTEXT, 0, (LPARAM) sendbuf); // TESTING
        PostMessage(ctrl, WM_KEYDOWN, VK_RETURN, 0);
        PostMessage(ctrl, WM_KEYUP, VK_RETURN, 0);
        AttachThreadInput(myId, threadId, false);
    } else {
        printf("\nError: AttachThreadInput failure!\n");
    }

But this one in python does not:

    foregroundHwnd = win32gui.GetForegroundWindow()
    foregroundThreadID = win32process.GetWindowThreadProcessId(foregroundHwnd)[0]
    ourThreadID = win32api.GetCurrentThreadId()
    if foregroundThreadID != ourThreadID:
        win32process.AttachThreadInput(foregroundThreadID, ourThreadID, True)
        focus_whd = win32gui.GetFocus()
        win32gui.SendMessage(focus_whd, win32con.WM_SETTEXT, None, "test text")
        win32gui.PostMessage(focus_whd, win32con.WM_KEYDOWN, win32con.VK_RETURN, None)
        win32gui.PostMessage(focus_whd, win32con.WM_KEYUP, win32con.VK_RETURN, None)
        win32process.AttachThreadInput(foregroundThreadID, ourThreadID, False)

The trouble is, most of our new logic in python. I turned that C code into a small python module and it works, but as result now I've got dependency on Microsoft's huge compiler and a lot of fiddling with module building. I'd like to have a python-only solution.

Any ideas why this python code does not work? These system calls look the same...

Upvotes: 1

Views: 2668

Answers (1)

Grrruk
Grrruk

Reputation: 438

Yes, AttachThreadInput failed. According to the comment here https://toster.ru/q/79336 win32process.GetWindowThreadProcessId returns wrong value, ctypes must be used. This code works:

"""
Fast "paste" implemented via calls to Windows internals, sends parameter
string and RETURN after that

Usage:

from paste import paste
paste("test")

"""

import time
import random
import string
from ctypes import windll
import ctypes
import win32con

def random_string(string_length=10):
    """Generate a random string of fixed length """
    letters = string.ascii_lowercase
    return ''.join(random.choice(letters) for i in range(string_length))

ERROR_INVALID_PARAMETER = 87

def paste(text_to_paste):
    """Fast "paste" using WM_SETTEXT method + Enter key"""
    current_hwnd = windll.user32.GetForegroundWindow()
    current_thread_id = windll.kernel32.GetCurrentThreadId()
    thread_process_id = windll.user32.GetWindowThreadProcessId(current_hwnd, None)
    if thread_process_id != current_thread_id:
        res = windll.user32.AttachThreadInput(thread_process_id, current_thread_id, True)
        # ERROR_INVALID_PARAMETER means that the two threads are already attached.
        if res == 0 and ctypes.GetLastError() != ERROR_INVALID_PARAMETER:
            print("WARN: could not attach thread input to thread {0} ({1})"
                .format(thread_process_id, ctypes.GetLastError()))
            return
        focus_whd = windll.user32.GetFocus()
        windll.user32.SendMessageW(focus_whd, win32con.WM_SETTEXT, None, text_to_paste)
        windll.user32.PostMessageW(focus_whd, win32con.WM_KEYDOWN, win32con.VK_RETURN, None)
        windll.user32.PostMessageW(focus_whd, win32con.WM_KEYUP, win32con.VK_RETURN, None)
        res = windll.user32.AttachThreadInput(thread_process_id, current_thread_id, True)


if __name__ == '__main__':
    time.sleep(5) # time to switch to the target
    # paste random 150 char string
    paste(random_string(150))

Upvotes: 1

Related Questions