Simplifying Ctype union in Python (to send Keyboard events in Windows)

Question:

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
Asked By: Jean-Francois T.

||

Answers:

"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:

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.

Answered By: CristiFati
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.