Catch system-wide hotkeys


The requirement: to respond to system hotkeys (typically Ctrl-Alt-key or Win-key) even when the application doesn't have the focus.

As a pleasant surprise, this is quite easy to arrange. The example below is a little more complex than it needs to be, but is designed to become the basis of a more general thread for handling hotkeys in any app. The main element is the RegisterHotKey-WM_HOTKEY-UnregisterHotKey combination. This could, for example, be running within a service so that the service's visible component (some control window) would popup in response to Ctrl-Alt-S etc.

In the code below, the system responds to Win-F3 by popping %TEMP% up in an explorer window, and to Win-F4 by finishing its loop.

# After a post to by Richie Hindle:
import os
import sys
import ctypes
from ctypes import wintypes
import win32con

byref = ctypes.byref
user32 = ctypes.windll.user32

  1 : (win32con.VK_F3, win32con.MOD_WIN),
  2 : (win32con.VK_F4, win32con.MOD_WIN)

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

def handle_win_f4 ():
  user32.PostQuitMessage (0)

  1 : handle_win_f3,
  2 : handle_win_f4

# RegisterHotKey takes:
#  Window handle for WM_HOTKEY messages (None = this thread)
#  arbitrary id unique within the thread
#  VK code (either ord ('x') or one of win32con.VK_*)
for id, (vk, modifiers) in HOTKEYS.items ():
  print "Registering id", id, "for key", vk
  if not user32.RegisterHotKey (None, id, modifiers, vk):
    print "Unable to register id", id

# Home-grown Windows message loop: does
#  just enough to handle the WM_HOTKEY
#  messages and pass everything else along.
  msg = wintypes.MSG ()
  while user32.GetMessageA (byref (msg), None, 0, 0) != 0:
    if msg.message == win32con.WM_HOTKEY:
      action_to_take = HOTKEY_ACTIONS.get (msg.wParam)
      if action_to_take:
        action_to_take ()

    user32.TranslateMessage (byref (msg))
    user32.DispatchMessageA (byref (msg))

  for id in HOTKEYS.keys ():
    user32.UnregisterHotKey (None, id)