[MouseFox logo]
The MouseFox Project
Join The Community Discord Server
[Discord logo]
Edit on GitHub

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
class XWindowFocusPatch:
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.

XWindowFocusPatch()
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.