How to create a global hotkey on Windows with 3 arguments?

helloworld1234 picture helloworld1234 · May 17, 2013 · Viewed 7.5k times · Source

Just like Ctl, Alt + delete

I want to write a program, which uses global hotkeys with 3 or more arguments in python. The assigned function should only perform when I press all three keys on my keyboard. For example alt, windows and F3.

win32con.VK_F3, win32con.MOD_WIN, win32con.VK_F5

This is the current program I want to run, however its output is:

Traceback (most recent call last):
 File "C:\Python32\Syntax\hot keys\hotkeys2.py", line 41, in <module>
   for id, (vk, modifiers) in HOTKEYS.items ():
ValueError: too many values to unpack (expected 2)

The Program:

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

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

HOTKEYS = {
  1 : (win32con.VK_F3, win32con.MOD_WIN, win32con.VK_F5),
  2 : (win32con.VK_F4, win32con.MOD_WIN),
  3 : (win32con.VK_F2, win32con.MOD_WIN)
    }

    def handle_win_f3 ():
  #os.startfile (os.environ['TEMP'])
  print ("Hello WOrld! F3")

def handle_win_f4 ():
  #user32.PostQuitMessage (0)
    print ("Hello WOrld! F4")

def handle_win_f1_escape ():
    print("exit")
    sys.exit()

HOTKEY_ACTIONS = {
  1 : handle_win_f3,
  2 : handle_win_f4,
  3 : handle_win_f1_escape
}

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)

try:
  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)
      #print(" msg.message == win32con.WM_HOTKEY:")
      if action_to_take:
        action_to_take ()

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

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

Registering 3 hotkeys? Possible? Explains how one can use assign one key that needs to be pressed and then if two of which either needs to be pressed. However I won’t that the function only performs when all there are pressed simultaneously. I took

Answer

maxxim picture maxxim · Jul 9, 2014

For anyone interested in details and a more elaborate example regarding this topic, I recently wrote a short program demonstrating the hotkey functions provided by win32con. The program allows you to specify and test any hotkeys you want via command line:

Usage: python.exe hotkey.py MOD_ALT VK_UP -> test hotkey ALT + Arrow UP

# Imports
import win32con
import ctypes, ctypes.wintypes
import sys

#
# Functions
#
def dispatch_hotkey(msg):
    mod = msg.lParam & 0b1111111111111111
    key = msg.lParam >> 16
    bit = bin(msg.lParam)[2:]
    print("\n*** Received hotkey message (wParam: %d, lParam: %d)" % (msg.wParam, msg.lParam))
    print("lParam bitmap: %s" % bit)
    print("lParam low-word (modifier): %d, high-word (key): %d" % (mod, key))
    print("-> Hotkey %s with modifier %s detected\n" % (keys[key], mods[mod]))

#
# Main
#

# Build translation maps (virtual key codes / modifiers to string)
# Note: exec() is a hack and should not be used in real programs!!
print("\n*** Building translation maps")
mods = {}
keys = {}
for item in dir(win32con):
    if item.startswith("MOD_"):
        exec("mods[item] = win32con." + item)
        exec("mods[win32con." + item + "] = '" + item + "'")
    if item.startswith("VK_"):
        exec("keys[item] = win32con." + item)
        exec("keys[win32con." + item + "] = '" + item + "'")

# Process command line
print("\n*** Processing command line")

mod = "MOD_WIN"
key = "VK_ESCAPE"
for param in sys.argv:
    if param.startswith("MOD_"):
        if param in mods: mod = param
        else: print("\nInvalid modifier specified (%s). Using default.\n-> Use '--list-mods' for a list of valid modifiers." % param)
    if param.startswith("VK_"):
        if param in keys: key = param
        else: print("\nInvalid key specified (%s). Using default.\n-> Use '--list-keys' for a list of valid keys." % param)

if "--list-mods" in sys.argv:
    print("\nAvailable modifiers:")
    for item in dir(win32con):
        if item.startswith("MOD_"): sys.stdout.write(item + ", ")
    print("\b\b ")

if "--list-keys" in sys.argv:
    print("\nAvailable keys:")
    for item in dir(win32con):
        if item.startswith("VK_"): sys.stdout.write(item + ", ")
    print("\b\b ")

# Register hotkey
print("\n*** Registering global hotkey (modifier: %s, key: %s)" % (mod, key))
ctypes.windll.user32.RegisterHotKey(None, 1, mods[mod], keys[key])

# Wait for hotkey to be triggered
print("\n*** Waiting for hotkey message...")
try:
    msg = ctypes.wintypes.MSG()
    while ctypes.windll.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
        if msg.message == win32con.WM_HOTKEY:
            dispatch_hotkey(msg)
            break
        ctypes.windll.user32.TranslateMessage(ctypes.byref(msg))
        ctypes.windll.user32.DispatchMessageA(ctypes.byref(msg))

# Unregister hotkey
finally:
    ctypes.windll.user32.UnregisterHotKey(None, 1)

Please note that this program is intended for demonstration purposes only as parts of the program (e.g. exec function) should not be used in production environments. Also note that with this approach, you won't be able to override built-in hotkeys like WIN + E etc., they will simply be ignored and still perform the built-in functions (e.g. opening Explorer).

Another Way (courtesy of @martineau)

Here's how to build the translation maps without using exec():

print("\n*** Building translation maps")
mods = {}
keys = {}
for item, value in vars(win32con).items():
    if item.startswith("MOD_"):
        mods[item] = value
        mods[value] = item
    elif item.startswith("VK_"):
        keys[item] = value
        keys[value] = item