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.
In fact, I was wrong: pynput
is indeed able to detect key presses globally, and not only for the currently active window.
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()
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)
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.