Reputation: 3305
I am writing a small timing module in Python using tkinter. In that regard, I would like to globally monitor when the escape key is pressed for stopping the timing.
Unfortunately, tkinters ".bind" and ".bind_all" functions only pick up keystrokes when the window is in focus.
I have looked at several other solutions for logging keystrokes including the packages "keyboard" and "pynput", however these packages requires running a while loop which makes the tkinter GUI freeze up and stop working.
I found this thread, but it is not very helpful for specifically showing how it can be done: Detect keypress without drawing canvas or frame on tkinter
Some different options i tried
Option 1: Using the tkinter loop function, but doesn't register when key is pressed
import keyboard
def _check_esc_pressed(self):
if self.run_active and keyboard.press('esc'):
self.Lap()
self.Stop()
self.after(50, self._check_esc_pressed())
Option 2: Freezes the tkinter client
import keyboard
def _check_esc_pressed(self):
while True:
if keyboard.is_pressed('esc'):
self.Lap()
self.Stop()
break
else:
pass
Option 3: Freezes the tkinter client
from pynput.keyboard import Key, Listener
def on_release(self, key):
if key == Key.esc:
self.Lap()
self.Stop()
# Stop listener
return False
def _check_esc_pressed(self):
def on_press(key):
pass
with Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
I expect that pressing escape will terminate the "_check_esc_pressed" function, register a lap and stop the timer. The check for escape should only be processed while a run is active
Upvotes: 0
Views: 2791
Reputation: 276
I encountered some issues using the keyboard module on Linux because it requires sudo privileges to run. This is not the case for the pynput module. Therefore, I am providing this alternative example:
from pynput import keyboard
import tkinter as tk
if __name__ == "__main__":
# Create the GUI
root = tk.Tk()
root.geometry("170x60")
lbl_1 = tk.Label(root, text="Hello World!")
lbl_1.pack()
btn_1 = tk.Button(root, text="Exit")
btn_1.pack()
# Create tk event handler
def lbl_1_handler(event):
lbl_1.config(text = "GoodBye World!")
# Bind event handler to button click
btn_1.bind("<Button-1>",lbl_1_handler)
# Bind event handler to key pressed
def on_press(key):
# Check if esc key has been pressed
if key == keyboard.Key.esc:
lbl_1_handler(None)
# The `keyboard.Listener` creates a thread that listens for
# key presses even when the tk window is not focused.
listener = keyboard.Listener(
on_press=on_press)
listener.start()
# Run the GUI main loop
root.mainloop()
Upvotes: 1
Reputation: 3305
I found a solution to the issue by using the system_hotkey package for python. This package allows you to assign system-wide hotkeys that work without focus on the tkinter program.
from system_hotkey import SystemHotkey
hk = SystemHotkey()
hk.register(['alt', 'q'], callback=lambda event: self.Start())
hk.register(['alt', 'w'], callback=lambda event: self.Stop())
Bear in mind that any hotkey added like this will make the registered hotkey combination inaccessible to other programs.
Upvotes: 1
Reputation: 41
In case you still need an answer, or others find this useful...
The answer may lie in the code we can't see - By putting the app's logic in another thread, it allows tkinter to do its thing without freezing.
The code below follows your (slightly modified) option 2:
import queue
import keyboard
import threading
import time
import tkinter as tk
def app_main_loop(my_label):
# Create another thread that monitors the keyboard
input_queue = queue.Queue()
kb_input_thread = threading.Thread(target=_check_esc_pressed, args=(input_queue,))
kb_input_thread.daemon = True
kb_input_thread.start()
# Main logic loop
run_active = True
while True:
if not input_queue.empty():
if (run_active) and (input_queue.get() == "esc"):
run_active = False
Lap(my_label)
Stop()
time.sleep(0.1) # seconds
def _check_esc_pressed(input_queue):
while True:
if keyboard.is_pressed('esc'):
input_queue.put("esc")
time.sleep(0.1) # seconds
def Lap(my_label):
my_label.configure(text = "Lap")
def Stop():
print("Stopped")
if __name__ == "__main__":
# Create the ui
root = tk.Tk()
root.attributes("-fullscreen", True)
my_label = tk.Label(root, text="Hello World!")
my_label.pack()
# Run the app's main logic loop in a different thread
main_loop_thread = threading.Thread(target=app_main_loop, args=(my_label, ))
main_loop_thread.daemon = True
main_loop_thread.start()
# Run the UI's main loop
root.mainloop()
Upvotes: 3