Question: I’m setting up a hotkey system for Windows in Python, using the Win32 API and PySide6. I want to register hotkeys in a HotkeyManager class and listen for them in a separate thread, so the GUI remains responsive. However, when I move the listening logic to a thread, the hotkey events are not detected correctly. Here’s the code that works without using threads, where hotkeys are registered and detected on the main thread: from threading import Thread from typing import Callable, Dict from win32gui import RegisterHotKey, UnregisterHotKey, GetMessage from win32con import VK_NUMPAD0, MOD_NOREPEAT class HotkeyManager: def __init__(self): self.hotkey_id = 1 self._callbacks: Dict[int, Callable] = {} def register_hotkey(self, key_code: int, callback: Callable): self._callbacks[self.hotkey_id] = callback RegisterHotKey(0, self.hotkey_id, MOD_NOREPEAT, key_code) self.hotkey_id += 1 def listen(self): while True: print("Listener started.") msg = GetMessage(None, 0, 0) hotkey_id = msg[1] if hotkey_id in self._callbacks: self._callbacks[hotkey_id]()  In the main code, this setup works as expected: from PySide6 import QtWidgets from win32con import VK_NUMPAD0 def on_press(): print("Numpad 0 pressed!") app = QtWidgets.QApplication([]) manager = HotkeyManager() manager.register_hotkey(VK_NUMPAD0, on_press) manager.listen() # Initialize window widget = QtWidgets.QMainWindow() widget.show() app.exec()  When I try to move the listen() method to a separate thread, however, the hotkey doesn’t respond properly: class HotkeyManager: def listen(self): def run(): while True: print("Listener started.") msg = GetMessage(None, 0, 0) hotkey_id = msg[1] if hotkey_id in self._callbacks: self._callbacks[hotkey_id]() thread = Thread(target=run, daemon=True) thread.start()  How can I correctly listen for hotkeys in a separate thread without losing functionality? It seems that the issue may be due to the hotkeys being registered on the main thread while the listening logic runs in a secondary thread. How could I solve this so everything works as expected?

Answer: Regarding the statement: "Here’s the code that works without using threads", there's nothing about code in the question that actually works. Let me detail:  from win32con import VK_NUMPAD0, MOD_NOREPEAT would yield ImportError. Check [GitHub]: mhammond/pywin32 - feat: add MOD_NOREPEAT (RegisterHotKey) constant (that I submitted a few minutes ago)  [GitHub.MHammond]: win32gui.GetMessage (documentation) seems to be wrong, the return is a tuple of 2 elements:  Underlying GetMessage return code (BOOL)  A [GitHub.MHammond]: PyMSG Object   Submitted [GitHub]: mhammond/pywin32 - win32gui.GetMessage documentation is incorrect for this   This way of having a thread monitoring for WM_HOTKEY, seems a bit clumsy as the window has its own message loop, and if it was for me to chose, I'd try to handle these messages in the window's message processing function, but a shallow Google search didn't reveal anything useful. Posting a solution that works based on your code. The idea is to wrap the hotkey registering and listening in a function to be executed in a thread. code00.py: #!/usr/bin/env python import sys import threading import typing import win32con as wcon import win32gui as wgui from PySide6 import QtWidgets MOD_NOREPEAT = 0x4000 class HotkeyManager: def __init__(self): self.hotkey_id = 1 self._callbacks: typing.Dict[int, typing.Callable] = {} self.threads = [] def __del__(self): self.clear() def register_hotkey(self, key_code: int, callback: typing.Callable): t = threading.Thread(target=self._register_hotkey, args=(key_code, callback), daemon=True) self.threads.append(t) t.start() def _register_hotkey(self, key_code: int, callback: typing.Callable): self._callbacks[self.hotkey_id] = callback wgui.RegisterHotKey(None, self.hotkey_id, MOD_NOREPEAT, key_code) self.hotkey_id += 1 self._listen() def _listen(self): print(f"Listener started ({threading.get_ident()}, {threading.get_native_id()})") while True: res = wgui.GetMessage(None, 0, 0) print(f" GetMessage returned: {res}") # @TODO - cfati: Check what it returns! rc, msg = res hotkey_id = msg[2] if hotkey_id in self._callbacks: self._callbacks[hotkey_id]() def clear(self): for hkid in self._callbacks: wgui.UnregisterHotKey(None, hkid) self._callbacks = {} def on_press_np0(): print(f"NumPad0 pressed!") def main(*argv): print(f"Main thread ({threading.get_ident()}, {threading.get_native_id()})") app = QtWidgets.QApplication([]) widget = QtWidgets.QMainWindow() widget.setWindowTitle("SO q079161068") widget.show() print("HotKeys") hkm = HotkeyManager() hkm.register_hotkey(wcon.VK_NUMPAD0, on_press_np0) print("App mesage loop") app.exec() if __name__ == "__main__": print( "Python {:s} {:03d}bit on {:s}\n".format( " ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform, ) ) rc = main(*sys.argv[1:]) print("\nDone.\n") sys.exit(rc)  Output:  [cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q079161068]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Main thread (23772, 23772) HotKeys App mesage loop Listener started (23672, 23672) GetMessage returned: [1, (0, 786, 1, 6291456, 514232828, (1, 0))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 514234437, (1919, 0))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 514235703, (1919, 1079))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 514237421, (0, 1079))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 514238718, (933, 534))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 514244375, (1650, 301))] NumPad0 pressed! Done.   Also posting a screenshot:  As a note, while thread is running NumPad 0 keystroke doesn't reach to other windows.