summaryrefslogtreecommitdiff
path: root/qutebrowser/misc/nativeeventfilter.py
blob: 82317683b7497ba1c1acef8bae744faf8244573a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:

# Copyright 2023 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <https://www.gnu.org/licenses/>.

"""Native Qt event filter.

This entire file is a giant WORKAROUND for https://bugreports.qt.io/browse/QTBUG-114334.
"""

from typing import Tuple
import ctypes

from qutebrowser.qt.core import QAbstractNativeEventFilter, qVersion

from qutebrowser.misc import objects
from qutebrowser.utils import log


# Needs to be saved to avoid garbage collection
_instance = None


class xcb_ge_generic_event_t(ctypes.Structure):
    """See https://xcb.freedesktop.org/manual/structxcb__ge__generic__event__t.html.

    Also used for xcb_generic_event_t as the structures overlap:
    https://xcb.freedesktop.org/manual/structxcb__generic__event__t.html
    """

    _fields_ = [
        ("response_type", ctypes.c_uint8),
        ("extension", ctypes.c_uint8),
        ("sequence", ctypes.c_uint16),
        ("length", ctypes.c_uint32),
        ("event_type", ctypes.c_uint16),
        ("pad0", ctypes.c_uint8 * 22),
        ("full_sequence", ctypes.c_uint32),
    ]


_XCB_GE_GENERIC = 35
_PROBLEMATIC_XINPUT_EVENTS = [
    18,  # XCB_INPUT_TOUCH_BEGIN
    19,  # XCB_INPUT_TOUCH_UPDATE
    20,  # XCB_INPUT_TOUCH_END
    27,  # XCB_INPUT_GESTURE_PINCH_BEGIN
    28,  # XCB_INPUT_GESTURE_PINCH_UPDATE
    29,  # XCB_INPUT_GESTURE_PINCH_END
    30,  # XCB_INPUT_GESTURE_SWIPE_BEGIN
    31,  # XCB_INPUT_GESTURE_SWIPE_UPDATE
    32,  # XCB_INPUT_GESTURE_SWIPE_END
]


class xcb_query_extension_reply_t(ctypes.Structure):
    """https://xcb.freedesktop.org/manual/structxcb__query__extension__reply__t.html."""

    _fields_ = [
        ("response_type", ctypes.c_uint8),
        ("pad0", ctypes.c_uint8),
        ("sequence", ctypes.c_uint16),
        ("length", ctypes.c_uint32),
        ("present", ctypes.c_uint8),
        ("major_opcode", ctypes.c_uint8),
        ("first_event", ctypes.c_uint8),
        ("first_error", ctypes.c_uint8),
    ]


class NativeEventFilter(QAbstractNativeEventFilter):
    def __init__(self) -> None:
        super().__init__()
        xcb = ctypes.cdll.LoadLibrary("libxcb.so.1")
        xcb.xcb_connect.restype = ctypes.POINTER(ctypes.c_void_p)
        xcb.xcb_query_extension_reply.restype = ctypes.POINTER(
            xcb_query_extension_reply_t
        )

        conn = xcb.xcb_connect(None, None)
        assert conn
        assert not xcb.xcb_connection_has_error(conn)

        # Get major opcode ID of Xinput extension
        name = b"XInputExtension"
        cookie = xcb.xcb_query_extension(conn, len(name), name)
        reply = xcb.xcb_query_extension_reply(conn, cookie, None)
        assert reply

        if not reply.contents.present:
            self.xinput_opcode = None
        else:
            self.xinput_opcode = reply.contents.major_opcode

        xcb.xcb_disconnect(conn)

    def nativeEventFilter(self, evtype: bytes, message: int) -> Tuple[bool, int]:
        # We're only installed when the platform plugin is xcb
        assert evtype == b"xcb_generic_event_t", evtype

        # We cast to xcb_ge_generic_event_t, which overlaps with xcb_generic_event_t.
        # .extension and .event_type will only make sense if this is an
        # XCB_GE_GENERIC event, but this is the first thing we check in the 'if'
        # below anyways.
        event = ctypes.cast(
            int(message), ctypes.POINTER(xcb_ge_generic_event_t)
        ).contents

        if (
            event.response_type == _XCB_GE_GENERIC
            and event.extension == self.xinput_opcode
            and event.event_type in _PROBLEMATIC_XINPUT_EVENTS
        ):
            log.misc.warning(f"Ignoring problematic XInput event {event.event_type}")
            return (True, 0)

        return (False, 0)


def init() -> None:
    global _instance

    platform = objects.qapp.platformName()
    qt_version = qVersion()
    log.misc.debug(f"Platform {platform}, Qt {qt_version}")

    if platform != "xcb" or qt_version != "6.5.1":
        return

    _instance = NativeEventFilter()
    objects.qapp.installNativeEventFilter(_instance)