Jean-Francois T.
Jean-Francois T.

Reputation: 12920

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

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

Answers (1)

CristiFati
CristiFati

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:

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

Related Questions