Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap the SendInput function to python using ctypes

Tags:

python

ctypes

I am trying to get the SendInput function from user32.dll to work in python using ctypes.I am a noob but from what I read from the docs you have to create the structs the function requires in python and then pass it to the function.

import ctypes
import keyboard
from ctypes import *
lib = windll.user32

KEYEVENTF_SCANCODE = 0x8
KEYEVENTF_KEYUP = 0x2
SPACEBAR = 57 # 0x39
INPUT_KEYBOARD = 1


class KEYBDINPUT(Structure):
    _fields_ = [('wVk' , c_ushort) , ('wScan' , c_ushort)
    , ('dwFlags' , c_ulong) , ('time' , c_ulong) , ('dwExtraInfo' , c_ulong)]
class INPUT(Structure):
    _fields_ = [('type' , c_ulong) ,('ki' , KEYBDINPUT)]


lib.SendInput.restype = c_uint
lib.SendInput.argtypes = [c_uint , INPUT , c_int]

keybdinput_obj = KEYBDINPUT(0 , SPACEBAR , KEYEVENTF_SCANCODE , 0 , 0)
input_obj = INPUT(INPUT_KEYBOARD , keybdinput_obj)
keyboard.wait('u')
lib.SendInput(1 , byref(input_obj) , sizeof(INPUT))
keybdinput_obj = KEYBDINPUT(0 , SPACEBAR , KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP , 0 , 0)
input_obj = INPUT(INPUT_KEYBOARD , keybdinput_obj)
lib.SendInput(1 , byref(input_obj) , sizeof(INPUT))

In the microsoft docs at which I guided myself from the INPUT struct had an union but i figured if I would only need the KEYBDINPUT then it's the same thing as if i had an union.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input

https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput

I pretty much got stuck because i can't see what's going wrong here so I am asking for help.The program is supposed to send a space after i press 'u' on the keyboard (this was for debugging purposes) and i do it this way because i want it to send as a scancode instead of a virtual keypress.

So if there is another way in python with with which you can send scancodes , that'll work and I would appreciate it a lot .

like image 600
Fine Baby Avatar asked Oct 22 '25 07:10

Fine Baby


1 Answers

Define the whole union, or at least MOUSEINPUT, which is the largest member of the union. You can test that your definition is the correct size by print(sizeof(INPUT)) and it should agree with printing the size of an INPUT structure in a C program. I got size 28 for 32-bit and 40 for 64-bit C structure.

Also, your second parameter to SendInput is POINTER(INPUT), not INPUT, and ULONG_PTR is not necessarily c_ulong because it depends on running 32-bit or 64-bit Python.

Here's a tested example:

import ctypes as ct
import ctypes.wintypes as w
import struct
import time
import array

KEYEVENTF_SCANCODE = 0x8
KEYEVENTF_UNICODE = 0x4
KEYEVENTF_KEYUP = 0x2
SPACE = 0x39
INPUT_KEYBOARD = 1

# not defined by wintypes
ULONG_PTR = ct.c_size_t

class KEYBDINPUT(ct.Structure):
    _fields_ = [('wVk' , w.WORD),
                ('wScan', w.WORD),
                ('dwFlags', w.DWORD),
                ('time', w.DWORD),
                ('dwExtraInfo', ULONG_PTR)]

class MOUSEINPUT(ct.Structure):
    _fields_ = [('dx' , w.LONG),
                ('dy', w.LONG),
                ('mouseData', w.DWORD),
                ('dwFlags', w.DWORD),
                ('time', w.DWORD),
                ('dwExtraInfo', ULONG_PTR)]

class HARDWAREINPUT(ct.Structure):
    _fields_ = [('uMsg' , w.DWORD),
                ('wParamL', w.WORD),
                ('wParamH', w.WORD)]

class DUMMYUNIONNAME(ct.Union):
    _fields_ = [('mi', MOUSEINPUT),
                ('ki', KEYBDINPUT),
                ('hi', HARDWAREINPUT)]

class INPUT(ct.Structure):
    _anonymous_ = ['u']
    _fields_ = [('type', w.DWORD),
                ('u', DUMMYUNIONNAME)]

print(ct.sizeof(INPUT))

def zerocheck(result, func, args):
    if result == 0:
        raise ct.WinError(ct.get_last_error())
    return result

user32 = ct.WinDLL('user32', use_last_error=True)
SendInput = user32.SendInput
SendInput.argtypes = w.UINT, ct.POINTER(INPUT), ct.c_int
SendInput.restype = w.UINT
SendInput.errcheck = zerocheck

def send_scancode(code):
    i = INPUT()
    i.type = INPUT_KEYBOARD
    i.ki = KEYBDINPUT(0, code, KEYEVENTF_SCANCODE, 0, 0)
    SendInput(1, ct.byref(i), ct.sizeof(INPUT))
    i.ki.dwFlags |= KEYEVENTF_KEYUP
    SendInput(1, ct.byref(i), ct.sizeof(INPUT))

def send_unicode(s):
    i = INPUT()
    i.type = INPUT_KEYBOARD
    # Handles non-BMP code points by sending UTF-16 code units.
    for c in array.array('H', s.encode('utf-16le')):
        i.ki = KEYBDINPUT(0, c, KEYEVENTF_UNICODE, 0, 0)
        SendInput(1, ct.byref(i), ct.sizeof(INPUT))
        i.ki.dwFlags |= KEYEVENTF_KEYUP
        SendInput(1, ct.byref(i), ct.sizeof(INPUT))

# Allow time to focus on another app
# such as Notepad or Notepad++
time.sleep(3)

send_scancode(SPACE)

# Last character is 7 code points:
# U+1F468 MAN
# U+200D  ZERO WIDTH JOINER
# U+1F469 WOMAN
# U+200D  ZERO WIDTH JOINER
# U+1F467 GIRL
# U+200D  ZERO WIDTH JOINER
# U+1F466 BOY
send_unicode('The quick brown fox jumped over the lazy dog๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ')

Output to Notepad.exe:

enter image description here

like image 90
Mark Tolonen Avatar answered Oct 23 '25 22:10

Mark Tolonen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!