forked from Tiyenti/kbdisplay
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkbdisplay.py
More file actions
executable file
·133 lines (114 loc) · 5.01 KB
/
kbdisplay.py
File metadata and controls
executable file
·133 lines (114 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env python3
import subprocess, json, sys, os
import tkinter as tk
import threading
try:
layout = json.load(open(sys.argv[1]))
except FileNotFoundError:
print(f"Error: JSON file not found at {sys.argv[1]}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: JSON Decode Error: {e}", file=sys.stderr)
sys.exit(1)
except IndexError: # Specific error for missing sys.argv[1]
print('Error: No layout file supplied.', file=sys.stderr)
print(f'Usage: {sys.argv[0]} <layout.json>', file=sys.stderr)
sys.exit(1)
except Exception as e: # Catch other potential startup errors
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)
buttons = {}
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master,
width=layout['width'],
height=layout['height'])
self['bg'] = layout['rootbg']
self.pack()
self.create_widgets()
self.proc = None # Will store the subprocess instance
# Start the xinput listener in a separate thread
self.start_xinput_listener()
# Add a handler for the window's close button
self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
def create_widgets(self):
"""Creates the labels based on the layout file."""
for button in layout['buttons']:
btn = tk.Label(self, text=button['text'])
btn.place(width=button['width'], height=button['height'],
x=button['x'], y=button['y'])
btn['bg'] = layout['bg1']
btn['fg'] = layout['fg1']
btn['font'] = (layout['fontfamily'], layout['fontsize'], layout['fontweight'])
buttons[button['keycode']] = btn
def start_xinput_listener(self):
"""Creates and starts a daemon thread to run the _xinput_loop."""
# will exit automatically when the main program exits
self.listener_thread = threading.Thread(target=self._xinput_loop, daemon=True)
self.listener_thread.start()
def _xinput_loop(self):
try:
self.proc = subprocess.Popen(['xinput', 'test-xi2', '--root'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=1, # Line buffered
universal_newlines=True)
except FileNotFoundError:
print("Error: 'xinput' command not found. This program requires 'xinput'.", file=sys.stderr)
# Schedule the application to quit from the main thread
self.master.after(0, self.master.quit)
return
except Exception as e:
print(f"Error starting subprocess: {e}", file=sys.stderr)
self.master.after(0, self.master.quit)
return
inkeypressevent = False
inkeyrelevent = False
# Will loop until the process ends
for line in iter(self.proc.stdout.readline, ''):
if 'EVENT type 2 (KeyPress)' in line:
inkeypressevent = True
inkeyrelevent = False
elif 'EVENT type 3 (KeyRelease)' in line:
inkeyrelevent = True
inkeypressevent = False
elif 'detail:' in line.strip():
if inkeypressevent or inkeyrelevent:
try:
code = int(line.split()[1])
if inkeypressevent:
self.master.after(0, self.update_button, code, layout['bg2'])
self.master.after(0, self.update_button, code, layout['fg2'])
elif inkeyrelevent:
self.master.after(0, self.update_button, code, layout['bg1'])
except (ValueError, IndexError):
pass # Ignore malformed lines
# Reset flags
inkeypressevent = False
inkeyrelevent = False
self.proc.stdout.close()
self.proc.stderr.close()
self.proc.wait()
def update_button(self, keycode, color):
try:
buttons[keycode]['bg'] = color
except KeyError:
pass # No button assigned to this keycode, which is fine
def on_closing(self):
print("Closing application...")
if self.proc:
# Terminate the subprocess
self.proc.terminate()
try:
# Give it a moment to die
self.proc.wait(timeout=0.5)
except subprocess.TimeoutExpired:
self.proc.kill() # Force kill if it doesn't terminate
# Destroy the Tkinter window, which stops the mainloop
self.master.destroy()
# --- Main execution ---
if __name__ == "__main__":
root = tk.Tk()
root.title("kbdisplay")
app = Application(master=root)
app.mainloop()