Listen for a shortcut (like WIN+A) even if the Python script does not have the focus
Asked Answered
D

1

8

I'd like a Python script to run constantly in background and do something when a certain keyboard shortcut is pressed, such as WIN+A.

I've read Key Listeners in python? and solutions using pynput, but it seems to work and detect key presses when the window/console has focus.

Question: how to detect a keyboard shortcut in Python, such as WIN+A, and launch a function when it happens, even if the focus is somewhere else (e.g. browser, etc.)?

Note: my OS is Windows. Also I would prefer that the Python script only "registers" listening to WIN+A (does this exist?), and does not listen to all keypresses (otherwise it would be more or less a keylogger, which I don't want!).


Here is what I have tried:

import pyHook, pythoncom

def OnKeyboardEvent(event):
    if event.Ascii == 8:  # backspace 
        print('hello')
    return True
    
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()

I would like to avoid it for 2 reasons: first, I find it very intrusive to listen to all keypresses and secondly, this common example about pyhook I found in many places has a bug: TypeError: KeyboardSwitch() missing 8 required positional arguments: 'msg', 'vk_ code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name'. The new version PyHook3 doesn't seem to work for Py36-64 either: pip install PyHook3 fails on Windows.

Denver answered 5/12, 2019 at 8:40 Comment(3)
You're looking for the Windows API "RegisterHotKey". Here is an example using it with Python: timgolden.me.uk/python/win32_how_do_i/…Rosalindarosalinde
I think you can post this as an answer @fpbhb! (Maybe with sample code).Denver
You did it better than me ;-) +1Rosalindarosalinde
D
7

In fact, I was wrong: pynput is indeed able to detect key presses globally, and not only for the currently active window.

1) Using pynput

This following code launches the application notepad with WIN+W and calc with WIN+C:

from pynput import keyboard
import subprocess

pressed = set()

COMBINATIONS = [
    {
        "keys": [
            {keyboard.Key.cmd, keyboard.KeyCode(char="w")},
            {keyboard.Key.cmd, keyboard.KeyCode(char="W")},
        ],
        "command": "notepad",
    },
    {
        "keys": [
            {keyboard.Key.cmd, keyboard.KeyCode(char="c")},
            {keyboard.Key.cmd, keyboard.KeyCode(char="C")},
        ],
        "command": "calc",
    },
]

def run(s):
    subprocess.Popen(s)

def on_press(key):
    pressed.add(key)
    print(pressed)
    for c in COMBINATIONS:
        for keys in c["keys"]:
            if keys.issubset(pressed):
                run(c["command"])

def on_release(key):
    if key in pressed:
        pressed.remove(key)

with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

2) Using ctypes and win32con

This is inspired by this post cited by @fpbhb in a comment. It listens to WIN+F3 and WIN+F4.

import os, sys
import ctypes
from ctypes import wintypes
import win32con

def handle_win_f3():
    os.startfile(os.environ["TEMP"])

def handle_win_f4():
    ctypes.windll.user32.PostQuitMessage(0)

HOTKEYS = [
    {"keys": (win32con.VK_F3, win32con.MOD_WIN), "command": handle_win_f3},
    {"keys": (win32con.VK_F4, win32con.MOD_WIN), "command": handle_win_f4},
]

for i, h in enumerate(HOTKEYS):
    vk, modifiers = h["keys"]
    print("Registering id", i, "for key", vk)
    if not ctypes.windll.user32.RegisterHotKey(None, i, modifiers, vk):
        print("Unable to register id", i)
try:
    msg = wintypes.MSG()
    while ctypes.windll.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
        if msg.message == win32con.WM_HOTKEY:
            HOTKEYS[msg.wParam]["command"]()
        ctypes.windll.user32.TranslateMessage(ctypes.byref(msg))
        ctypes.windll.user32.DispatchMessageA(ctypes.byref(msg))
finally:
    for i, h in enumerate(HOTKEYS):
        ctypes.windll.user32.UnregisterHotKey(None, i)

3) Using keyboard

See https://github.com/boppreh/keyboard. It is interesting, but for me it had some drawbacks compared to other solutions: some fast keystrokes (while CPU-intensive processing is done) were not caught. They were caught when using solution 1) or 2).


It seems there is no 100% Tkinter solution, see Capture all keypresses of the system with Tkinter.

Denver answered 8/12, 2019 at 17:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.