kvex.win_focus_patch
A patch for key presses not consumed on window focus change (e.g. alt + tab).
This may only affect linux systems. See: https://github.com/kivy/kivy/issues/7239
In order for this patch to work, the object should be initialized once as early as possible, and stored in a namespace that won't get garbage collected.
Considerations
It seems that either Kivy or the system ensures that the Window gaining focus always happens first, and that the Window losing focus always happens last. And so the solution has to consider that a key press (e.g. "tab") may be the key press that will remove focus from our Window (e.g. alt + tab) later during this frame.
Solution
Hence we consume the Window's on_key_
events and only dispatch them a frame later if
focus has not changed in the previous frame. This of course breaks the guarantee (if one
exists) that gaining focus is always first and losing focus is always last.
Since key releases may happen many frames in the future, we also remember to consume key releases of keys that we consumed their corresponding presses.
This solution doesn't seem to fix all cases.
1"""A patch for key presses not consumed on window focus change (e.g. alt + tab). 2 3This may only affect linux systems. See: https://github.com/kivy/kivy/issues/7239 4 5In order for this patch to work, the object should be initialized once as early as 6possible, and stored in a namespace that won't get garbage collected. 7 8## Considerations 9It seems that either Kivy or the system ensures that the Window gaining focus always 10happens first, and that the Window losing focus always happens last. And so the solution 11has to consider that a key press (e.g. "tab") may be the key press that will remove 12focus from our Window (e.g. alt + tab) later during this frame. 13 14## Solution 15Hence we consume the Window's `on_key_` events and only dispatch them a frame later if 16focus has not changed in the previous frame. This of course breaks the guarantee (if one 17exists) that gaining focus is always first and losing focus is always last. 18 19Since key releases may happen many frames in the future, we also remember to consume key 20releases of keys that we consumed their corresponding presses. 21 22.. warning:: This solution doesn't seem to fix all cases. 23""" 24 25from kivy.clock import Clock 26from kivy.core.window import Window 27 28 29class XWindowFocusPatch: 30 """See module documentation for details.""" 31 32 def __init__(self): 33 """See module documentation for details.""" 34 Window.bind( 35 focus=self._on_focus, 36 on_key_down=self._on_key_down, 37 on_key_up=self._on_key_up, 38 ) 39 self._delayed_events = [] 40 self._dismiss_key_ups = set() 41 self._ignore_key_events = False 42 self._focus_frame = Clock.frames - 1 43 Clock.schedule_interval(self._redispatch, 0) 44 45 def _redispatch(self, *args): 46 # Redispatch last frame's `on_key_` events, depending on focus 47 events, self._delayed_events = self._delayed_events, [] 48 focus_changed = Clock.frames - 1 == self._focus_frame 49 self._ignore_key_events = True 50 for ev in events: 51 evtype, key, *_ = ev 52 if focus_changed and evtype == "on_key_down": 53 self._dismiss_key_ups.add(key) 54 continue 55 if evtype == "on_key_up" and key in self._dismiss_key_ups: 56 self._dismiss_key_ups.remove(key) 57 continue 58 Window.dispatch(*ev) 59 self._ignore_key_events = False 60 61 def _on_focus(self, w, focus): 62 self._focus_frame = Clock.frames 63 64 def _on_key_down(self, w, key, sc, text, mods): 65 if self._ignore_key_events: 66 return False 67 self._delayed_events.append(("on_key_down", key, sc, text, mods)) 68 return True 69 70 def _on_key_up(self, w, key, sc): 71 if self._ignore_key_events: 72 return False 73 self._delayed_events.append(("on_key_up", key, sc)) 74 return True
30class XWindowFocusPatch: 31 """See module documentation for details.""" 32 33 def __init__(self): 34 """See module documentation for details.""" 35 Window.bind( 36 focus=self._on_focus, 37 on_key_down=self._on_key_down, 38 on_key_up=self._on_key_up, 39 ) 40 self._delayed_events = [] 41 self._dismiss_key_ups = set() 42 self._ignore_key_events = False 43 self._focus_frame = Clock.frames - 1 44 Clock.schedule_interval(self._redispatch, 0) 45 46 def _redispatch(self, *args): 47 # Redispatch last frame's `on_key_` events, depending on focus 48 events, self._delayed_events = self._delayed_events, [] 49 focus_changed = Clock.frames - 1 == self._focus_frame 50 self._ignore_key_events = True 51 for ev in events: 52 evtype, key, *_ = ev 53 if focus_changed and evtype == "on_key_down": 54 self._dismiss_key_ups.add(key) 55 continue 56 if evtype == "on_key_up" and key in self._dismiss_key_ups: 57 self._dismiss_key_ups.remove(key) 58 continue 59 Window.dispatch(*ev) 60 self._ignore_key_events = False 61 62 def _on_focus(self, w, focus): 63 self._focus_frame = Clock.frames 64 65 def _on_key_down(self, w, key, sc, text, mods): 66 if self._ignore_key_events: 67 return False 68 self._delayed_events.append(("on_key_down", key, sc, text, mods)) 69 return True 70 71 def _on_key_up(self, w, key, sc): 72 if self._ignore_key_events: 73 return False 74 self._delayed_events.append(("on_key_up", key, sc)) 75 return True
See module documentation for details.
33 def __init__(self): 34 """See module documentation for details.""" 35 Window.bind( 36 focus=self._on_focus, 37 on_key_down=self._on_key_down, 38 on_key_up=self._on_key_up, 39 ) 40 self._delayed_events = [] 41 self._dismiss_key_ups = set() 42 self._ignore_key_events = False 43 self._focus_frame = Clock.frames - 1 44 Clock.schedule_interval(self._redispatch, 0)
See module documentation for details.