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
23namespace os {
24
25namespace {
26
27#if !defined(NDEBUG)
28const 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
70void 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
87void EventQueueX11::queueEvent(const Event& ev)
88{
89 m_events.push(ev);
90}
91
92void 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
176void EventQueueX11::clearEvents()
177{
178 m_events.clear();
179}
180
181void 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