On windows 10 running Python 3.11, add_hotkey("ctrl") works exactly the same as add_hotkey("right ctrl"). If you bind"ctrl" to funcA and "right ctrl" to funcB, pressing either key calls both functions.
I wondered if the keys mapped to the same key code or something like that. I wrote a tkinter program to spit out key events.
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.bind("<Key>", print)
Window().mainloop()
Output:
<KeyPress event send_event=True state=Mod1 keysym=Control_L keycode=17 x=835 y=292>
<KeyPress event send_event=True state=Mod1|0x40000 keysym=Control_R keycode=17 x=835 y=292>
The keycode is the same, but the state and keysym are different. Unfortunately you cannot map the control key in tkinter, but you could map all keys and make your own hotkey mapper.
class Window(tk.Tk):
keymap = {}
def __init__(self):
super().__init__()
self.keymap["Control_L"] = self.left_ctrl
self.keymap["Control_R"] = self.right_ctrl
self.bind("<Key>", self.hotkeys)
def hotkeys(self, event):
if event.keysym in self.keymap:
self.keymap[event.keysym]()
def left_ctrl(self):
print("left ctrl")
def right_ctrl(self):
print("right ctrl")
Window().mainloop()
I think that looks pretty clean, but it does you no good. You will never get a Control_R event after you witdraw the window.
I tried the same thing using keyboard. First I wrote a generic binder. hotkeys does not pass the key press event as an argument so I used on_press()
import keyboard
def handler(event):
print(event.__dict__)
keyboard.on_press(handler)
keyboard.wait()
Output:
{'event_type': 'down', 'scan_code': 29, 'time': 1682957871.5058897, 'device': None, 'is_keypad': False, 'modifiers': None, 'name': 'ctrl'}
{'event_type': 'down', 'scan_code': 29, 'time': 1682957872.049906, 'device': None, 'is_keypad': False, 'modifiers': None, 'name': 'right ctrl'}
The scan_code for the left and right control keys is the same. Only the name has been changed. You could do something similar to the homebrew tkinter hotkey. Here I use hook_key so my handler is not called for every key event. Hook sends events for "down" and "up". I decided to use the "up" event so the mapper function is only called once per keypress (key repeat generates multiple key down events).
import keyboard
def handler(event):
if event.event_type == "up":
if event.name == "ctrl":
left_ctrl()
else:
right_ctrl()
def left_ctrl():
print("left ctrl")
def right_ctrl():
print("right ctrl")
keyboard.hook_key("ctrl", handler)
keyboard.wait()
Output:
left ctrl
right ctrl
I would choose different hotkeys, but this does work.
import tkinter as tk
import keyboard
class Window(tk.Tk):
keymap = {}
def __init__(self):
super().__init__()
self.keymap["ctrl"] = self.withdraw
self.keymap["right ctrl"] = self.show
keyboard.hook_key("ctrl", self.hotkeys)
def hotkeys(self, event):
if event.event_type == "up" and event.name in self.keymap:
self.keymap[event.name]()
def show(self):
self.deiconify()
Window().mainloop()