@@ -42,10 +42,19 @@ class CombinationHandler(MappingHandler):
42
42
43
43
# map of InputEvent.input_match_hash -> bool , keep track of the combination state
44
44
_pressed_keys : Dict [Hashable , bool ]
45
- _output_state : bool # the last update we sent to a sub-handler
45
+
46
+ # the last update we sent to a sub-handler. If this is true, the output key is
47
+ # still being held down.
48
+ _output_active : bool
46
49
_sub_handler : InputEventHandler
47
50
_handled_input_hashes : list [Hashable ]
48
- _notify_results : Dict [Tuple [int , int ], bool ]
51
+
52
+ # If a key-up event arrives that will inactivate the combination, but
53
+ # for which previously a key-down event was injected (because it was
54
+ # an earlier key in the combination chain), then we need to ensure that its
55
+ # release is injected as well. So we get two release events in that case:
56
+ # one for the key, and one for the output.
57
+ _requires_a_release : Dict [Tuple [int , int ], bool ]
49
58
50
59
def __init__ (
51
60
self ,
@@ -57,9 +66,9 @@ def __init__(
57
66
logger .debug (str (mapping ))
58
67
super ().__init__ (combination , mapping )
59
68
self ._pressed_keys = {}
60
- self ._output_state = False
69
+ self ._output_active = False
61
70
self ._context = context
62
- self ._notify_results = {}
71
+ self ._requires_a_release = {}
63
72
64
73
# prepare a key map for all events with non-zero value
65
74
for input_config in combination :
@@ -95,58 +104,39 @@ def notify(
95
104
event : InputEvent ,
96
105
source : evdev .InputDevice ,
97
106
suppress : bool = False ,
98
- ) -> bool :
99
- result = self ._notify (event , source , suppress )
100
-
101
- if event .type_and_code in self ._notify_results :
102
- # The return value is always the same as for the key-down event.
103
- # If a key-up event arrives that will inactivate the combination, but
104
- # for which previously a key-down event was injected (because it was
105
- # an earlier key in the combination chain), then we need to ensure that its
106
- # release is injected as well.
107
- result = self ._notify_results [event .type_and_code ]
108
- del self ._notify_results [event .type_and_code ]
109
- return result
110
-
111
- self ._notify_results [event .type_and_code ] = result
112
- return result
113
-
114
- def _notify (
115
- self ,
116
- event : InputEvent ,
117
- source : evdev .InputDevice ,
118
- suppress : bool = False ,
119
107
) -> bool :
120
108
if event .input_match_hash not in self ._handled_input_hashes :
121
109
# we are not responsible for the event
122
110
return False
123
111
124
- was_activated = self .is_activated ()
125
-
126
112
# update the state
127
113
# The value of non-key input should have been changed to either 0 or 1 at this
128
114
# point by other handlers.
129
115
is_pressed = event .value == 1
116
+ is_released = event .value == 0
130
117
self ._pressed_keys [event .input_match_hash ] = is_pressed
131
118
# maybe this changes the activation status (triggered/not-triggered)
132
119
is_activated = self .is_activated ()
133
120
134
- if is_activated == was_activated or is_activated == self ._output_state :
121
+ if is_activated == self ._output_active :
135
122
# nothing changed
136
- if self ._output_state :
137
- # combination is active, consume the event
138
- return True
139
- else :
140
- # combination inactive, forward the event
141
- return False
123
+ # combination is active: consume the event
124
+ # combination inactive: forward the event
125
+ if is_pressed :
126
+ self .remember (self ._output_active , event )
127
+ return self ._output_active
128
+
129
+ if is_released :
130
+ return self .should_release_event (event )
142
131
143
132
if is_activated :
144
133
# send key up events to the forwarded uinput
145
134
self .forward_release ()
146
135
event = event .modify (value = 1 )
147
- else :
148
- if self ._output_state or self .mapping .is_axis_mapping ():
149
- # we ignore the suppress argument for release events
136
+
137
+ if not is_activated :
138
+ if self ._output_active or self .mapping .is_axis_mapping ():
139
+ # we ignore the `suppress` argument for release events
150
140
# otherwise we might end up with stuck keys
151
141
# (test_event_pipeline.test_combination)
152
142
@@ -159,14 +149,34 @@ def _notify(
159
149
return False
160
150
161
151
logger .debug ("Sending %s to sub-handler" , self .mapping .input_combination )
162
- self ._output_state = bool (event .value )
163
- return self ._sub_handler .notify (event , source , suppress )
152
+ self ._output_active = bool (event .value )
153
+ sub_handler_result = self ._sub_handler .notify (event , source , suppress )
154
+
155
+ if is_pressed :
156
+ self .remember (sub_handler_result , event )
157
+ return sub_handler_result
158
+
159
+ if is_released :
160
+ return self .should_release_event (event )
161
+
162
+ def should_release_event (self , event ):
163
+ if event .value == 0 and event .type_and_code in self ._requires_a_release :
164
+ forward_release = self ._requires_a_release [event .type_and_code ]
165
+ del self ._requires_a_release [event .type_and_code ]
166
+ # False means "please forward this, event-reader", therefore we negate
167
+ # this.
168
+ return not forward_release
169
+
170
+ return True
171
+
172
+ def remember (self , handled , event ):
173
+ self ._requires_a_release [event .type_and_code ] = not handled
164
174
165
175
def reset (self ) -> None :
166
176
self ._sub_handler .reset ()
167
177
for key in self ._pressed_keys :
168
178
self ._pressed_keys [key ] = False
169
- self ._output_state = False
179
+ self ._output_active = False
170
180
171
181
def is_activated (self ) -> bool :
172
182
"""Return if all keys in the keymap are set to True."""
@@ -188,6 +198,9 @@ def forward_release(self) -> None:
188
198
logger .debug ("Forwarding release for %s" , self .mapping .input_combination )
189
199
190
200
for input_config in keys_to_release :
201
+ if not self ._requires_a_release .get (input_config .type_and_code ):
202
+ continue
203
+
191
204
origin_hash = input_config .origin_hash
192
205
if origin_hash is None :
193
206
logger .error (
@@ -200,6 +213,9 @@ def forward_release(self) -> None:
200
213
forward_to .write (* input_config .type_and_code , 0 )
201
214
forward_to .syn ()
202
215
216
+ # We are done with this key, forget about it
217
+ del self ._requires_a_release [input_config .type_and_code ]
218
+
203
219
def needs_ranking (self ) -> bool :
204
220
return bool (self .input_configs )
205
221
0 commit comments