So I'm trying to figure out how to register a global keyboard hook using Python. From what I have read, it seems to be okay to not have the callback in a DLL. If you use WH_KEYBOARD_LL. I can't confirm that for sure but I find it encouraging that I don't get a 1428 error like I do if I try to hook into say WH_CBT
.
I get a hook handle but nothing shows up when I press buttons on the keyboard as I would expect.
Any idea's on why my callback is not being called? Or is this even possible?
The relevant code :
import time
import string
import ctypes
import functools
import atexit
import pythoncom
from ctypes import windll
hookID = 0
class Keyboard(object):
KEY_EVENT_DOWN = 0
KEY_EVENT_UP = 2
KEY_ENTER = 2
KEY_SHIFT = 16
KEY_SPACE = 32
HOOK_ACTION = 13
HOOK_KEYBOARD = 13
HOOK_KEYDOWN = 0x100
HOOK_KEYUP = 0x101
class Hook:
'''Holds general hook information'''
def __init__(self):
self.hook = 0
self.struct = None
class HookStruct(ctypes.Structure):
'''Structure that windows returns for keyboard events'''
__fields__ = [
('keycode', ctypes.c_long),
('scancode', ctypes.c_long),
('flags', ctypes.c_long),
('time', ctypes.c_long),
('info', ctypes.POINTER(ctypes.c_ulong))
]
def ascii_to_keycode(self, char):
return windll.user32.VkKeyScanA(ord(char))
def inject_key_down(self, keycode):
scancode = windll.user32.MapVirtualKeyA(keycode, 0)
windll.user32.keybd_event(keycode, scancode, Keyboard.KEY_EVENT_DOWN, 0)
def inject_key_up(self, keycode):
scan = windll.user32.MapVirtualKeyA(keycode, 0)
windll.user32.keybd_event(keycode, scan, Keyboard.KEY_EVENT_UP, 0)
def inject_key_press(self, keycode, pause=0.05):
self.inject_key_down(keycode)
time.sleep(pause)
self.inject_key_up(keycode)
def inject_sequence(self, seq, pause=0.05):
for key in seq:
if key == ' ':
self.inject_key_press(Keyboard.KEY_SPACE, pause)
elif key == '
':
self.inject_key_press(Keyboard.KEY_ENTER, pause)
else:
if key in string.ascii_uppercase:
self.inject_key_down(Keyboard.KEY_SHIFT)
self.inject_key_press(self.ascii_to_keycode(key), pause)
self.inject_key_up(Keyboard.KEY_SHIFT)
else:
self.inject_key_press(self.ascii_to_keycode(key), pause)
def _win32_copy_mem(self, dest, src):
src = ctypes.c_void_p(src)
windll.kernel32.RtlMoveMemory(ctypes.addressof(dest), src, ctypes.sizeof(dest))
def _win32_get_last_error(self):
return windll.kernel32.GetLastError()
def _win32_get_module(self, mname):
return windll.kernel32.GetModuleHandleA(mname)
def _win32_call_next_hook(self, id, code, wparam, lparam):
return windll.kernel32.CallNextHookEx(id, code, wparam, lparam)
def _win32_set_hook(self, id, callback, module, thread):
callback_decl = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_long, ctypes.c_long, ctypes.c_long)
return windll.user32.SetWindowsHookExA(id, callback_decl(callback), module, thread)
def _win32_unhook(self, id):
return windll.user32.UnhookWindowsHookEx(id)
def keyboard_event(self, data):
print data.scancode
return False
def capture_input(self):
self.hook = Keyboard.Hook()
self.hook.struct = Keyboard.HookStruct()
def low_level_keyboard_proc(code, event_type, kb_data_ptr):
# win32 spec says return result of CallNextHookEx if code is less than 0
if code < 0:
return self._win32_call_next_hook(self.hook.hook, code, event_type, kb_data_ptr)
if code == Keyboard.HOOK_ACTION:
# copy data from struct into Python structure
self._win32_copy_mem(self.hook.struct, kb_data_ptr)
# only call other handlers if we return false from our handler - allows to stop processing of keys
if self.keyboard_event(self.hook.struct):
return self._win32_call_next_hook(self.hook.hook, code, event_type, kb_data_ptr)
# register hook
try:
hookId = self.hook.hook = self._win32_set_hook(Keyboard.HOOK_KEYBOARD, low_level_keyboard_proc, self._win32_get_module(0), 0)
if self.hook.hook == 0:
print 'Error - ', self._win32_get_last_error()
else:
print 'Hook ID - ', self.hook.hook
except Exception, error:
print error
# unregister hook if python exits
atexit.register(functools.partial(self._win32_unhook, self.hook.hook))
def end_capture(self):
if self.hook.hook:
return self._win32_unhook(self.hook.hook)
kb = Keyboard()#kb.inject_sequence('This is a test
and tHis is line 2')
kb.capture_input()
pythoncom.PumpMessages()
kb.end_capture()
See Question&Answers more detail:
os