|
27 | 27 | e.KEY_LEFTMETA, e.KEY_RIGHTMETA, |
28 | 28 | } |
29 | 29 |
|
| 30 | +# Mouse button keys - paired with KEYBOARD_MODIFIER_KEYS to detect modifier+mouse |
| 31 | +# combos that need a forced delay (see AUTO_MOUSE_MODIFIER_DELAY_MS). |
| 32 | +MOUSE_EVENT_KEYS = {e.BTN_LEFT, e.BTN_RIGHT, e.BTN_MIDDLE} |
| 33 | + |
| 34 | +# When a single batch contains both a modifier press and a mouse event (REL_* or |
| 35 | +# BTN_LEFT/RIGHT/MIDDLE), we force at least this much delay between the modifier |
| 36 | +# and the mouse event so the compositor has time to latch the modifier into |
| 37 | +# keystate before the pointer event arrives. Without this, apps like DaVinci |
| 38 | +# Resolve see Alt+scroll as a plain scroll. Pure-keyboard combos are not |
| 39 | +# affected and still honor the user's `modifier_delay` setting verbatim. |
| 40 | +AUTO_MOUSE_MODIFIER_DELAY_MS = 15 |
| 41 | + |
30 | 42 | logger = logging.getLogger(__name__) |
31 | 43 |
|
32 | 44 | # Controls that can be held (buttons) vs momentary (rotary) |
@@ -147,47 +159,64 @@ def is_modifier_button(self, control_name: str) -> bool: |
147 | 159 | def _send_events(self, events: List[Tuple[int, int, int]]): |
148 | 160 | """Send events to the virtual input device with optional modifier delay |
149 | 161 |
|
150 | | - If modifier_delay is configured and the events contain both keyboard |
151 | | - modifier keys (Ctrl, Shift, Alt, Meta) and non-modifier keys, sends |
152 | | - the modifier keys first, syncs, waits for the delay, then sends the |
153 | | - remaining keys. This helps applications recognize key combinations. |
| 162 | + If the batch contains a modifier key press (Ctrl/Shift/Alt/Meta) along |
| 163 | + with other events, the modifier is sent first, syned, then the rest of |
| 164 | + the events are sent after a delay. This helps applications recognize |
| 165 | + key combinations. |
| 166 | +
|
| 167 | + The delay used is `max(self.modifier_delay, AUTO_MOUSE_MODIFIER_DELAY_MS)` |
| 168 | + when the batch mixes a modifier with a mouse event (REL_* or BTN_LEFT/ |
| 169 | + RIGHT/MIDDLE), and `self.modifier_delay` otherwise. The auto-delay |
| 170 | + exists because compositors need a moment to latch a modifier into |
| 171 | + keystate before processing pointer events; without it, things like |
| 172 | + Alt+scroll get delivered as plain scrolls. Pure keyboard combos still |
| 173 | + honor `self.modifier_delay` verbatim (default 0). |
154 | 174 |
|
155 | 175 | Args: |
156 | 176 | events: List of (event_type, event_code, value) tuples |
157 | 177 | """ |
158 | 178 | if not events or not self.controller: |
159 | 179 | return |
160 | 180 |
|
161 | | - # Check if we need to apply modifier delay |
162 | | - if self.modifier_delay > 0: |
163 | | - # Separate modifier key presses from other events |
164 | | - modifier_presses = [] |
165 | | - other_events = [] |
166 | | - |
167 | | - for event in events: |
168 | | - event_type, event_code, value = event |
169 | | - if (event_type == e.EV_KEY and |
170 | | - value == 1 and |
171 | | - event_code in KEYBOARD_MODIFIER_KEYS): |
172 | | - modifier_presses.append(event) |
173 | | - else: |
174 | | - other_events.append(event) |
| 181 | + # Single pass: partition events and detect mouse-mixing. |
| 182 | + modifier_presses = [] |
| 183 | + other_events = [] |
| 184 | + has_mouse_event = False |
175 | 185 |
|
176 | | - # If we have both modifiers and other events, send with delay |
177 | | - if modifier_presses and other_events: |
178 | | - # Send modifier keys first |
179 | | - for event in modifier_presses: |
180 | | - self.controller.write(*event) |
181 | | - self.controller.syn() |
| 186 | + for event in events: |
| 187 | + event_type, event_code, value = event |
| 188 | + if (event_type == e.EV_KEY and |
| 189 | + value == 1 and |
| 190 | + event_code in KEYBOARD_MODIFIER_KEYS): |
| 191 | + modifier_presses.append(event) |
| 192 | + else: |
| 193 | + other_events.append(event) |
| 194 | + if event_type == e.EV_REL: |
| 195 | + has_mouse_event = True |
| 196 | + elif (event_type == e.EV_KEY and |
| 197 | + value == 1 and |
| 198 | + event_code in MOUSE_EVENT_KEYS): |
| 199 | + has_mouse_event = True |
| 200 | + |
| 201 | + # Compute the delay to use for this batch. Mouse+modifier combos get |
| 202 | + # the auto floor; everything else just uses the configured value. |
| 203 | + effective_delay = self.modifier_delay |
| 204 | + if modifier_presses and has_mouse_event: |
| 205 | + effective_delay = max(effective_delay, AUTO_MOUSE_MODIFIER_DELAY_MS) |
| 206 | + |
| 207 | + # If we have both modifiers and other events and a non-zero delay, |
| 208 | + # send the modifier first, wait, then send the rest. |
| 209 | + if effective_delay > 0 and modifier_presses and other_events: |
| 210 | + for event in modifier_presses: |
| 211 | + self.controller.write(*event) |
| 212 | + self.controller.syn() |
182 | 213 |
|
183 | | - # Wait for modifier delay |
184 | | - time.sleep(self.modifier_delay / 1000.0) |
| 214 | + time.sleep(effective_delay / 1000.0) |
185 | 215 |
|
186 | | - # Send remaining events |
187 | | - for event in other_events: |
188 | | - self.controller.write(*event) |
189 | | - self.controller.syn() |
190 | | - return |
| 216 | + for event in other_events: |
| 217 | + self.controller.write(*event) |
| 218 | + self.controller.syn() |
| 219 | + return |
191 | 220 |
|
192 | 221 | # No delay needed - send all events normally |
193 | 222 | for event in events: |
|
0 commit comments