5

This code (inspired from Which is the easiest way to simulate keyboard and mouse on Python?) opens a Notepad and send the keys A, B, C, D, ..., Z every second:

import win32com.client, time
shell = win32com.client.Dispatch("WScript.Shell")
shell.Run('Notepad')
time.sleep(1)
shell.AppActivate("Notepad")
for i in range(65,91):
    shell.SendKeys(chr(i))
    time.sleep(1)

I would like to let this operation continue in background, continue my work on the computer, and have the keystrokes sent to Notepad (in background).

Problem: if I open another application (example: browser) in the meantime, the keystrokes are sent ... to the currently active window, which I dont't want!

Question: how to have Python send the keystrokes to notepad.exe only, even if this application is not in foreground?

Context: I'm automating some long task requiring that my Python script sends keystrokes to app.exe (in background) during maybe 15 minutes, but I'd like to do something else with the computer in the meantime.

Note: More generally, the use case I'm interested in is the case where the process app.exe might open dialogs, close dialogs, open other windows, so the solution should be able to send the keystrokes to the active window of the process. Thus a solution with a fixed hWnd like here doesn't work directly.

Basj
  • 41,386
  • 99
  • 383
  • 673
  • Use UI Automation to automate other programs – David Heffernan Feb 26 '19 at 10:58
  • Thank you @DavidHeffernan. Would you have a simple example (in the Notepad case) in a few lines, to show how to use it from a Python script? – Basj Feb 26 '19 at 11:18
  • No, I would not have, but I'm sure one can be found. That said, it would be easier for you to experiment with this using a different language. At least to convince yourself that it is even possible. Not doubt Notepad isn't the intended target for the end product. – David Heffernan Feb 26 '19 at 11:56
  • There is nothing like that for Notepad. Word has an IDispatch interface for automation, but such stuff is probably more feasible with C++. Actually, it is not worth the trouble, it's better (if it is notepad) to edit the text yourself. – Michael Chourdakis Feb 26 '19 at 13:29
  • @Michael In my real situation, it's not notepad.exe but another software, notepad was just a simple example, that everyone has installed on the computer. If we know how to do it with notepad, we can adapt to any software. – Basj Feb 26 '19 at 13:44
  • There is no such possibility unless the software itself has implemented automation interfaces. If so, then they will have a SDK that would allow a programmer interact with it. – Michael Chourdakis Feb 26 '19 at 13:56
  • @Michael well, there is a solution: use a virtual machine, and let Python+win32com+SendKeys work alone with myapplication.exe inside the VM, and minimize the VM, and continue to work on the computer... But maybe there is even simpler? – Basj Feb 26 '19 at 20:46
  • Even if this works, it will not be reliable for long term plans. As I said, this must be done via automation which is not a feature on most apps. You should contact the app developer and request some automation API. – Michael Chourdakis Feb 26 '19 at 21:15
  • @Michael The latter is not possible ; if it was, I would just use an API, and no UI automation / keystroke simulation at all. Not possible here. – Basj Feb 26 '19 at 21:20
  • 1
    @Michael many apps can be automated without them providing a bespoke automation interface in the manner of office. Your comments are very misleading. – David Heffernan Feb 27 '19 at 07:34
  • @DavidHeffernan and what would be a reliable automation method? – Michael Chourdakis Feb 27 '19 at 07:49
  • @Michael UI Automation – David Heffernan Feb 27 '19 at 08:37
  • Impossible to make something work for any application. – David Heffernan Feb 28 '19 at 13:21
  • @DavidHeffernan if it already works to send keystrokes to notepad.exe and, let's say calc.exe, it will already be a good example that it could be generalized to a few apps probably. Such a solution would already be fine. – Basj Feb 28 '19 at 20:00
  • Nope. One is a Win32 app, the other a UWP app. Huge difference. Or do you know different. – David Heffernan Feb 28 '19 at 20:24
  • @DavidHeffernan Sorry, I'm speaking about Windows7's calc.exe, I think it's a good old Win32 app, right? (https://en.wikipedia.org/wiki/Universal_Windows_Platform_apps seems to apply to Windows 8+, is this correct?). More generally, I'm looking for a solution for standard Win32 applications. – Basj Feb 28 '19 at 20:36
  • OK so you want this to work for all applications apart from the ones that it won't work for – David Heffernan Feb 28 '19 at 21:23
  • @DavidHeffernan Having a solution working for 20% of the applications will still be better than what I have now, i.e. a solution working for 0% of the applications ;) – Basj Feb 28 '19 at 22:23
  • Well your bounty asks for 100% – David Heffernan Feb 28 '19 at 22:51
  • **NOTE:** Possible duplicate of : https://stackoverflow.com/questions/12996985/send-some-keys-to-inactive-window-with-python – xilpex Mar 01 '19 at 00:46
  • @DavidHeffernan Half a loaf is better than no bread... – Basj Mar 01 '19 at 08:00
  • related: https://stackoverflow.com/questions/34745699/how-to-send-sendkeys-to-windows-form-in-python-script – Jean-François Fabre Mar 01 '19 at 08:59
  • related: https://stackoverflow.com/questions/21917965/send-keys-to-a-inactive-window-in-python – Basj Mar 01 '19 at 09:09

1 Answers1

4

In order to send Keystroke to any application window, without activating the application to get input focus. We must get the windows handler first. This requires Windows API FindWindow and FindWindowsEx. First, the handle of Top Level window of application is obtained by FindWindow. Then use FindWindowsEx to get the handle of the child window or control to receive keys. Because the top window of the application is not always the window that accepts Keystroke (such as notepad.exe, the window that actually accepts Keystroke is the Edit control under the main window of Notepad), it can be found by ClassID or Caption.

Assuming that the handle of the target window has been got(hwnd), the key message will be sent to the window with PostMessage.

For normal character keys, it is simplest to use WM_CHAR message directly, as follows:

PostMessage(hwnd, WM_CHAR, 'a', 0);

For un-normal character keys, such as function keys, direction keys, etc., WM_KEYDOWN and WM_KEYUP messages should be used as follows:

VirtualKey = MapVirtualKeyA(VK_RIGHT, 0);
PostMessage(hwnd, WM_KEYDOWN, VK_RIGHT, 0x0001|VirtualKey<<16);
PostMessage(hwnd, WM_KEYUP, VK_RIGHT, 0x0001|VirtualKey<<16|0xC0<<24);

The details for last parameter (lParam) you can reference to msdn.

For the keys "Shift/Ctrl", sample:

keybd_event(VK_SHIFT, 0, 0, 0);
PostMessage(hwnd, WM_KEYDOWN, 0x41, 0x001E0001);
PostMessage(hwnd, WM_KEYUP, 0x41, 0xC01E0001);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);

For the keys "Alt", It belongs to the system button, using WM_SYSKEYDOWN/WM_SYSKEYUP message. sample:

PostMessage(hwnd, WM_SYSKEYDOWN, VK_F4, 0x003E0001 |0x20000000);
PostMessage(hwnd, WM_SYSKEYUP, VK_F4, 0xC03E0001 | 0x20000000);

0x20000000 means context code, the value is 1 if the "Alt" key is down.

Drake Wu
  • 6,927
  • 1
  • 7
  • 30
  • Thank you for your answer, I'll try this. Can you elaborate on why `0x0001|VirtualKey>>16`, `0x0001|VirtualKey>>16|C0>>24`, `0x001E0001`, `0xC01E0001`, `0x003E0001 |0x20000000` or `0xC03E0001 | 0x20000000` in your example? I read the MSDN post you linked, but it is still a bit mysterious. – Basj Mar 01 '19 at 10:36
  • These are the virtual key status and flags. – Drake Wu Mar 04 '19 at 02:32
  • `0x0001`(0-15 bits) represents the number of repeated messages, `VirtualKey`, the `1E`(VirtualKey of 'a') in `0xC01E0001` and the `3E`(VirtualKey of "F4") in `0x003E0001`, represents scanner code (16-23 bits), `C0` is a write mistake of `0xC0`(1100,0000), I have fixed it. It represents the 24-31 bits of the status. So it need to ">>24". 25-28 bits are reserved,set to zero, 29th bit is zero since the `Alt` key is not down. 30th bit is 1 since the previous key state is down. 31th bit is 1 since the key is being released. `0x20000000` means 29th bit is set to 1 since the `Alt` key is held down. – Drake Wu Mar 04 '19 at 02:33
  • Not working for my specific use case, but interesting though! – Basj Mar 26 '19 at 20:47
  • 1
    Were those meant to be right shifts? Unless I'm missing something, 0xc0>>24 would just end up being 0 because all the bits have been cleared, making ORing with it seem pointless. Running that in my interpreter gives me 0x0 where running a left shift (0xC0 << 24) gives me 0xC0000000 which I think may have been the intended result. – The Elemental of Destruction Aug 29 '21 at 05:48