1 | // LAF OS Library |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
3 | // Copyright (C) 2016-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "os/x11/event_queue.h" |
13 | |
14 | #include "base/thread.h" |
15 | #include "os/x11/window.h" |
16 | |
17 | #include <X11/Xlib.h> |
18 | |
19 | #include <sys/select.h> |
20 | |
21 | #define EV_TRACE(...) |
22 | |
23 | namespace os { |
24 | |
25 | namespace { |
26 | |
27 | #if !defined(NDEBUG) |
28 | const char* get_event_name(XEvent& event) |
29 | { |
30 | switch (event.type) { |
31 | case KeyPress: return "KeyPress" ; |
32 | case KeyRelease: return "KeyRelease" ; |
33 | case ButtonPress: return "ButtonPress" ; |
34 | case ButtonRelease: return "ButtonRelease" ; |
35 | case MotionNotify: return "MotionNotify" ; |
36 | case EnterNotify: return "EnterNotify" ; |
37 | case LeaveNotify: return "LeaveNotify" ; |
38 | case FocusIn: return "FocusIn" ; |
39 | case FocusOut: return "FocusOut" ; |
40 | case KeymapNotify: return "KeymapNotify" ; |
41 | case Expose: return "Expose" ; |
42 | case GraphicsExpose: return "GraphicsExpose" ; |
43 | case NoExpose: return "NoExpose" ; |
44 | case VisibilityNotify: return "VisibilityNotify" ; |
45 | case CreateNotify: return "CreateNotify" ; |
46 | case DestroyNotify: return "DestroyNotify" ; |
47 | case UnmapNotify: return "UnmapNotify" ; |
48 | case MapNotify: return "MapNotify" ; |
49 | case MapRequest: return "MapRequest" ; |
50 | case ReparentNotify: return "ReparentNotify" ; |
51 | case ConfigureNotify: return "ConfigureNotify" ; |
52 | case ConfigureRequest: return "ConfigureRequest" ; |
53 | case GravityNotify: return "GravityNotify" ; |
54 | case ResizeRequest: return "ResizeRequest" ; |
55 | case CirculateNotify: return "CirculateNotify" ; |
56 | case CirculateRequest: return "CirculateRequest" ; |
57 | case PropertyNotify: return "PropertyNotify" ; |
58 | case SelectionClear: return "SelectionClear" ; |
59 | case SelectionRequest: return "SelectionRequest" ; |
60 | case SelectionNotify: return "SelectionNotify" ; |
61 | case ColormapNotify: return "ColormapNotify" ; |
62 | case ClientMessage: return "ClientMessage" ; |
63 | case MappingNotify: return "MappingNotify" ; |
64 | case GenericEvent: return "GenericEvent" ; |
65 | } |
66 | return "Unknown" ; |
67 | } |
68 | #endif |
69 | |
70 | void wait_file_descriptor_for_reading(int fd, base::tick_t timeoutMilliseconds) |
71 | { |
72 | fd_set fds; |
73 | FD_ZERO(&fds); |
74 | FD_SET(fd, &fds); |
75 | |
76 | timeval timeout; |
77 | timeout.tv_sec = timeoutMilliseconds / 1000; |
78 | timeout.tv_usec = ((timeoutMilliseconds % 1000) * 1000); |
79 | |
80 | // First argument must be set to the highest-numbered file |
81 | // descriptor in any of the three sets, plus 1. |
82 | select(fd+1, &fds, nullptr, nullptr, &timeout); |
83 | } |
84 | |
85 | } // anonymous namespace |
86 | |
87 | void EventQueueX11::queueEvent(const Event& ev) |
88 | { |
89 | m_events.push(ev); |
90 | } |
91 | |
92 | void EventQueueX11::getEvent(Event& ev, double timeout) |
93 | { |
94 | base::tick_t startTime = base::current_tick(); |
95 | |
96 | ev.setWindow(nullptr); |
97 | |
98 | ::Display* display = X11::instance()->display(); |
99 | XSync(display, False); |
100 | |
101 | XEvent event; |
102 | int events = XEventsQueued(display, QueuedAlready); |
103 | if (events == 0) { |
104 | if (timeout == kWithoutTimeout) { |
105 | // Wait for a XEvent only if we have an empty queue of os::Event |
106 | // (so there is no more events to process in our own queue). |
107 | if (m_events.empty()) |
108 | events = 1; |
109 | else |
110 | events = 0; |
111 | } |
112 | else if (timeout > 0.0) { |
113 | // Wait timeout (waiting the X11 connection file description for |
114 | // a read operation). We've to use this method to wait for |
115 | // events with timeout because we don't have a X11 function like |
116 | // XNextEvent() with a timeout. |
117 | base::tick_t timeoutMsecs = base::tick_t(timeout * 1000.0); |
118 | base::tick_t elapsedMsecs = base::current_tick() - startTime; |
119 | if (int(timeoutMsecs - elapsedMsecs) > 0) { |
120 | int connFileDesc = ConnectionNumber(display); |
121 | wait_file_descriptor_for_reading(connFileDesc, |
122 | timeoutMsecs - elapsedMsecs); |
123 | } |
124 | |
125 | events = XEventsQueued(display, QueuedAlready); |
126 | } |
127 | } |
128 | |
129 | // If the user is not converting dead keys it means that we are not |
130 | // in a text-input field, and we are expecting a game-like input |
131 | // (shortcuts) so we can remove the repeats of a key |
132 | // (KeyRelease/KeyPress pair events) that are sent when we keep a |
133 | // key pressed. |
134 | const bool removeRepeats = (!WindowX11::translateDeadKeys()); |
135 | |
136 | for (int i=0; i<events; ++i) { |
137 | XNextEvent(display, &event); |
138 | |
139 | // Here we try to "remove" KeyRelease/KeyPress pairs from |
140 | // autorepeat key events (remove = not delivering/converting |
141 | // XEvent to os::Event) |
142 | if ((event.type == KeyRelease) && |
143 | (removeRepeats) && |
144 | (i+1 < events || XEventsQueued(display, QueuedAfterFlush) > 0)) { |
145 | if (i+1 < events) |
146 | ++i; |
147 | XEvent event2; |
148 | XNextEvent(display, &event2); |
149 | // If a KeyPress is just after a KeyRelease with the same time, |
150 | // this is an autorepeat. |
151 | if (event2.type == KeyPress && |
152 | event.xkey.time == event2.xkey.time) { |
153 | // The KeyRelease/KeyPress are an autorepeat, we can just |
154 | // ignore the KeyRelease XEvent (and process the event2, which |
155 | // is a KeyPress to send a os::Event::KeyDown). |
156 | } |
157 | else { |
158 | // This wasn't an autorepeat event |
159 | processX11Event(event); |
160 | } |
161 | processX11Event(event2); |
162 | } |
163 | else { |
164 | processX11Event(event); |
165 | } |
166 | } |
167 | |
168 | if (!m_events.try_pop(ev)) { |
169 | #pragma push_macro("None") |
170 | #undef None // Undefine the X11 None macro |
171 | ev.setType(Event::None); |
172 | #pragma pop_macro("None") |
173 | } |
174 | } |
175 | |
176 | void EventQueueX11::clearEvents() |
177 | { |
178 | m_events.clear(); |
179 | } |
180 | |
181 | void EventQueueX11::processX11Event(XEvent& event) |
182 | { |
183 | EV_TRACE("XEvent: %s (%d)\n" , get_event_name(event), event.type); |
184 | |
185 | WindowX11* window = WindowX11::getPointerFromHandle(event.xany.window); |
186 | // In MappingNotify the window can be nullptr |
187 | if (window) |
188 | window->processX11Event(event); |
189 | } |
190 | |
191 | } // namespace os |
192 | |