Reputation: 12920
I have the following script that sends a keyboard key every xx seconds (F15 by default), inspired by some code found online
I want to remove the types and union related to Mouse events but cannot make it to work. In particular, I don't know how to remove the class MOUSEINPUT
and the union of types _INPUTunion
(removing them will stop sending the keyboard key).
Any suggestion on how to trim the script to a minimum (i.e. only keep code related to Keyboard)?
The following code will send the key "C" to be able to debug.
#!/python
import ctypes
import sys
import time
LONG = ctypes.c_long
DWORD = ctypes.c_ulong
ULONG_PTR = ctypes.POINTER(DWORD)
WORD = ctypes.c_ushort
class MOUSEINPUT(ctypes.Structure):
_fields_ = (
('dx', LONG), ('dy', LONG), ('mouseData', DWORD),
('dwFlags', DWORD), ('time', DWORD),
('dwExtraInfo', ULONG_PTR)
)
class KEYBDINPUT(ctypes.Structure):
_fields_ = (
('wVk', WORD), ('wScan', WORD),
('dwFlags', DWORD), ('time', DWORD),
('dwExtraInfo', ULONG_PTR)
)
class _INPUTunion(ctypes.Union):
_fields_ = (('mi', MOUSEINPUT), ('ki', KEYBDINPUT))
class INPUT(ctypes.Structure):
_fields_ = (('type', DWORD), ('union', _INPUTunion))
def SendInput(*inputs):
print(inputs[0].union.mi)
nInputs = len(inputs)
LPINPUT = INPUT * nInputs
pInputs = LPINPUT(*inputs)
cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize)
INPUT_KEYBOARD = 1
def Input(structure):
if isinstance(structure, KEYBDINPUT):
return INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure))
else:
raise TypeError('Cannot create INPUT structure (keyboard)!')
def Keyboard(code, flags=0):
return Input(KEYBDINPUT(code, code, flags, 0, None))
if __name__ == '__main__':
nb_cycles = 10
while nb_cycles != 0:
time.sleep(2) # 3 seconds
# Key "c" for debug, but ideally use 0x7E for "F15"
SendInput(Keyboard(ord("C")))
sys.stdout.write(".")
nb_cycles -= 1
Upvotes: 3
Views: 3077
Reputation: 41116
"The Bible" for tasks like this: [Python.Docs]: ctypes - A foreign function library for Python.
I modified your code (found a bunch of problems, out of which some were critical).
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
import time
from ctypes import wintypes as wts
wts.BYTE = cts.c_ubyte
class KEYBDINPUT(cts.Structure):
_fields_ = (
("wVk", wts.WORD),
("wScan", wts.WORD),
("dwFlags", wts.DWORD),
("time", wts.DWORD),
("dwExtraInfo", wts.PULONG),
)
class INPUT(cts.Structure):
_fields_ = (
("type", wts.DWORD),
("ki", KEYBDINPUT),
("padding", wts.BYTE * 8)
)
INPUT_KEYBOARD = 1 # Also defined by win32con if you have pywin32 installed
INPUT_LEN = cts.sizeof(INPUT)
LPINPUT = cts.POINTER(INPUT)
user32_dll = cts.windll.user32
SendInput = user32_dll.SendInput
SendInput.argtypes = (wts.UINT, LPINPUT, cts.c_int)
SendInput.restype = wts.UINT
def send_input(_input):
return SendInput(1, cts.byref(_input), INPUT_LEN)
def keyboard(code, flags=0):
return INPUT(INPUT_KEYBOARD, (KEYBDINPUT(code, code, flags, 0, None)))
def main(*argv):
time.sleep(2)
nb_cycles = 3
for _ in range(nb_cycles):
time.sleep(0.5) # 3 seconds
# Key "c" for debug, but ideally use 0x7E for "F15"
ret = send_input(keyboard(ord("C")))
#print(ret)
sys.stdout.write(".")
sys.stdout.flush()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Notes:
Initially, I defined the INPUT structure having only its 1st 2 members, but took a look at [MS.Learn]: INPUT structure (winuser.h) and noted that the union consists of:
MOUSEINPUT
KEYBDINPUT
HARDWAREINPUT
Did some tests and noted that MOUSEINPUT has the largest size out of the 3 structs, and it's 8 bytes greater than KEYBDINPUT (for both 032bit and 064bit), so I added the (dummy) (padding) member. Normally, I wouldn't expect that SendInput to go beyond KEYBDINPUT size when receiving an INPUT structure with type set to INPUT_KEYBOARD, but the INPUT size is required by [MS.Docs]: SendInput function (winuser.h)'s cbSize arg
def SendInput(*inputs):
- in Python, Asterisk (*) before an argument does something totally different than in C. Check [SO]: Expanding tuples into arguments (I didn't find the official .doc). I modified the function so that it only sends one such structure
Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions)
Use ctypes.wintypes for Win specific types, don't reinvent the wheel. Not to mention that sometimes mismatches might occur (most recent one that I saw, was ctypes.c_bool vs. wintypes.BOOL)
I renamed your functions to be [Python]: PEP 8 - Style Guide for Python Code compliant. I also removed some of them, that were no longer needed
Other small changes that don't worth being mentioned individually
As for testing, I used a Notepad window (or any other, "sensitive" to user input):
Launch the script
Alt + Tab to the test window (I added the 1st instruction (time.sleep(2)) from main just to give user time to switch windows), and notice the cs "magically" appearing
... Or just launch the script from console, press (and hold) Ctrl, then notice the KeyboardInterrupt (Ctrl + C) occurrence.
Upvotes: 6