[Tutor] [ctypes-users] Press ESC to exit()

eryk sun eryksun at gmail.com
Mon May 1 23:02:05 EDT 2017


On Mon, May 1, 2017 at 6:28 PM, Michael C
<mysecretrobotfactory at gmail.com> wrote:
> Hi all, I found out that one way to press ESC to kill the script was to use
> my previous
> script language, AutoHotKey and this is how it works:
>
> AutoHotKey code
>  ## function that kills the window with title '*Python 3.6.1 Shell*'
>
> kill()
> {
> WinKill, *Python 3.6.1 Shell*
> }
>
> ## When ESC is pressed, runs the function 'kill'
> Esc::kill()
>
> Yes, that's the entire script.
> Is there a way to write it in Python on windows?

Instead of using a hotkey, you can set a global low-level keyboard
hook. Then optionally you can check for a particular foreground window
before handling the key. For example:

    import ctypes
    import win32con
    import win32gui

    from ctypes import wintypes

    user32 = ctypes.WinDLL('user32', use_last_error=True)

    VK_ESCAPE = 0x1B

    LLKHF_EXTENDED          = 0x00000001
    LLKHF_LOWER_IL_INJECTED = 0x00000002
    LLKHF_INJECTED          = 0x00000010
    LLKHF_ALTDOWN           = 0x00000020
    LLKHF_UP                = 0x00000080

    ULONG_PTR = wintypes.WPARAM
    class KBDLLHOOKSTRUCT(ctypes.Structure):
        _fields_ = (('vkCode',      wintypes.DWORD),
                    ('scanCode',    wintypes.DWORD),
                    ('flags',       wintypes.DWORD),
                    ('time',        wintypes.DWORD),
                    ('dwExtraInfo', ULONG_PTR))

    HOOKPROC = ctypes.WINFUNCTYPE(wintypes.LPARAM, ctypes.c_int,
        wintypes.WPARAM, wintypes.LPARAM)

    user32.SetWindowsHookExW.restype = wintypes.HHOOK
    user32.SetWindowsHookExW.argtypes = (
        ctypes.c_int,       # _In_ idHook
        HOOKPROC,           # _In_ lpfn
        wintypes.HINSTANCE, # _In_ hMod
        wintypes.DWORD)     # _In_ dwThreadId

    user32.CallNextHookEx.restype = wintypes.LPARAM
    user32.CallNextHookEx.argtypes = (
        wintypes.HHOOK,  # _In_opt_ hhk
        ctypes.c_int,    # _In_     nCode
        wintypes.WPARAM, # _In_     wParam
        wintypes.LPARAM) # _In_     lParam

    def keyboard_hook(handler, hwnd=None):
        @HOOKPROC
        def hookfunc(nCode, wParam, lParam):
            event = KBDLLHOOKSTRUCT.from_address(lParam)
            if hwnd is not None and win32gui.GetForegroundWindow() == hwnd:
                handler(event)
            return user32.CallNextHookEx(hHook, nCode, wParam, lParam)

        hHook = user32.SetWindowsHookExW(win32con.WH_KEYBOARD_LL, hookfunc,
                    None, 0)
        if not hHook:
            raise ctypes.WinError(ctypes.get_last_error())

        win32gui.PumpMessages()

    if __name__ == '__main__':
        import msvcrt
        import threading
        import win32console

        print('Press escape to quit')

        def escape_handler(event):
            if event.vkCode == VK_ESCAPE:
                # kill the hook thread
                win32gui.PostQuitMessage(0)
            elif not (event.flags & LLKHF_UP):
                print('Virtual Key Code: {:#04x} [{}]'.format(event.vkCode,
                    event.time))

        t = threading.Thread(target=keyboard_hook, args=(escape_handler,
                win32console.GetConsoleWindow()), daemon=True)
        t.start()

        # consume keyboard input
        while t.is_alive():
            if msvcrt.kbhit():
                msvcrt.getwch()

The __main__ demo should be run as a console script. The keyboard hook
filters on the console window handle from GetConsoleWindow().


More information about the Tutor mailing list