11
22
3- from dataclasses import dataclass
3+ from collections import deque
4+ import errno
45import os
56import struct
6- import select
77import threading
88import time
99from typing import OrderedDict
1010
11- from controller .controller_inputs import ControllerInput
1211from controller .controller_interface import ControllerInterface
1312from controller .key_state import KeyState
13+ from controller .key_watcher_controller_dataclasses import KeyEvent
1414from utils .logger import PyUiLogger
1515
1616# Constants for Linux input
2020KEY_RELEASE = 0
2121KEY_REPEAT = 2
2222
23-
24- @dataclass
25- class InputResult :
26- controller_input : ControllerInput
27- key_state : KeyState
28-
29- @dataclass (frozen = True )
30- class KeyEvent :
31- event_type : int
32- code : int
33- value : int
34-
35-
3623class KeyWatcherController (ControllerInterface ):
3724
3825 def __init__ (self , event_path , key_mappings ):
@@ -43,9 +30,10 @@ def __init__(self, event_path, key_mappings):
4330 self .event_path = event_path
4431 self .key_mappings = key_mappings
4532 self .held_controller_inputs = OrderedDict ()
46-
33+ self .input_queue = deque ()
34+
4735 try :
48- self .fd = os .open (self .event_path , os .O_RDONLY | os . O_NONBLOCK )
36+ self .fd = os .open (self .event_path , os .O_RDONLY )
4937 except OSError as e :
5038 PyUiLogger .get_logger ().warning (f"Could not open { self .event_path } : { e } " )
5139 self .fd = None
@@ -84,39 +72,81 @@ def cache_last_event(self):
8472
8573 def restore_cached_event (self ):
8674 self .last_held_input = self .cached_input
75+
76+ def read_event (self , fd ):
77+ """Read exactly one input_event from fd (blocking)."""
78+ buf = b''
79+ while len (buf ) < EVENT_SIZE :
80+ try :
81+ chunk = os .read (fd , EVENT_SIZE - len (buf ))
82+ except BlockingIOError :
83+ continue
84+ if not chunk :
85+ PyUiLogger .get_logger ().debug ("EOF or no data read from fd" )
86+ return None
87+ buf += chunk
88+
89+ event = struct .unpack (EVENT_FORMAT , buf )
90+ return event
91+
8792
8893
8994 def poll_keyboard (self ):
90- while (True ):
95+ logger = PyUiLogger .get_logger ()
96+ if self .fd is None :
97+ logger .error ("File descriptor is None, cannot poll keyboard." )
98+ return
99+
100+ while True :
91101 now = time .time ()
92102 try :
93- rlist , _ , _ = select .select ([self .fd ], [], [], 100 )
94- if rlist :
95- data = os .read (self .fd , EVENT_SIZE )
96103
97- if len (data ) == EVENT_SIZE :
98- _ , _ , event_type , code , value = struct .unpack (EVENT_FORMAT , data )
99- #PyUiLogger.get_logger().error(f"event_type: {event_type}, code: {code}, value: {value}")
100- key_event = KeyEvent (event_type , code , value )
101- if key_event in self .key_mappings :
102- mapped_events = self .key_mappings [key_event ]
103- if (mapped_events is not None and len (mapped_events ) > 0 ):
104- for mapped_event in mapped_events :
105- if mapped_event .key_state == KeyState .PRESS :
106- with self .lock :
107- self .held_controller_inputs [mapped_event .controller_input ] = now
108- self .key_change (mapped_event .controller_input ,"PRESS" )
109- #PyUiLogger.get_logger().debug(f"Adding {mapped_event.controller_input} to held inputs")
110- elif mapped_event .key_state == KeyState .RELEASE :
111- with self .lock :
112- self .held_controller_inputs .pop (mapped_event .controller_input , None )
113- self .key_change (mapped_event .controller_input ,"RELEASE" )
114- #PyUiLogger.get_logger().debug(f"Removing {mapped_event.controller_input} from held inputs")
115- else :
116- PyUiLogger .get_logger ().exception (f"No mapping for event: { key_event } " )
104+ try :
105+ data = os .read (self .fd , EVENT_SIZE )
106+ except OSError as e :
107+ if e .errno == errno .EINTR :
108+ continue
109+
110+ if e .errno in (errno .EBADF , errno .ENODEV , errno .EIO ):
111+ logger .error (
112+ "Keyboard device became unavailable (errno=%d)" ,
113+ e .errno ,
114+ )
115+ return
116+
117+ logger .exception ("Unexpected OSError while reading input" )
118+ return
119+
120+ if len (data ) != EVENT_SIZE :
121+ logger .error ("Short read: got %d bytes, expected %d" , len (data ), EVENT_SIZE )
122+ continue
123+
124+ tv_sec , tv_usec , event_type , code , value = struct .unpack (EVENT_FORMAT , data )
125+
126+ key_event = KeyEvent (event_type , code , value )
127+
128+ if key_event in self .key_mappings :
129+ mapped_events = self .key_mappings [key_event ]
130+ if mapped_events :
131+ for mapped_event in mapped_events :
132+ if mapped_event .key_state == KeyState .PRESS :
133+ with self .lock :
134+ self .held_controller_inputs [mapped_event .controller_input ] = now
135+ if mapped_event .controller_input not in self .input_queue :
136+ self .input_queue .append (mapped_event .controller_input )
137+ self .key_change (mapped_event .controller_input ,"PRESS" )
138+ elif mapped_event .key_state == KeyState .RELEASE :
139+ with self .lock :
140+ self .key_change (mapped_event .controller_input ,"RELEASE" )
141+ self .held_controller_inputs .pop (mapped_event .controller_input , None )
142+ else :
143+ logger .error ("No mapping for event: %s" , key_event )
144+ elif (key_event .event_type != 0 or key_event .code != 0 or key_event .value != 0 ):
145+ #logger.debug("Unmapped key event: %s", key_event)
146+ pass
117147
118148 except Exception as e :
119- PyUiLogger . get_logger (). error ( f "Error reading input: { e } " )
149+ logger . exception ( "Error processing input: %s" , e )
120150
121151 def key_change (self , controller_input , direction ):
122152 if (self .print_key_changes ):
@@ -125,17 +155,27 @@ def key_change(self, controller_input, direction):
125155 def get_input (self , timeoutInMilliseconds ):
126156 start_time = time .time ()
127157 timeout = timeoutInMilliseconds / 1000.0
128-
129- with self .lock :
130- self .last_held_input = next (iter (self .held_controller_inputs ), None )
131158
132- while self .last_held_input is None and (time .time () - start_time ) < timeout :
133- time .sleep (0.05 ) # 1/20 of a second delay
159+ while (time .time () - start_time ) < timeout :
134160 with self .lock :
135- self .last_held_input = next (iter (self .held_controller_inputs ), None )
161+ # First, check the event queue
162+ if self .input_queue :
163+ value = self .input_queue .popleft ()
164+ self .last_held_input = value
165+ return value
166+
167+ # Fallback: return the first currently held key
168+ if self .held_controller_inputs :
169+ value = next (iter (self .held_controller_inputs ))
170+ self .last_held_input = value
171+ return value
172+
173+ time .sleep (0.005 )
136174
137- return self .last_held_input
175+ self .last_held_input = None
176+ return None
138177
178+
139179 def clear_input_queue (self ):
140180 pass
141181
0 commit comments