1 | /* This file is part of the KDE libraries |
2 | SPDX-FileCopyrightText: 2009 Dario Freddi <drf at kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.1-or-later |
5 | */ |
6 | |
7 | // Exceptionnally, include QCoreApplication before our own header, because that one includes X11 headers (#define None...) |
8 | #include <QCoreApplication> |
9 | |
10 | #include "xsync_logging.h" |
11 | |
12 | #include "xsyncbasedpoller.h" |
13 | |
14 | #include <QAbstractNativeEventFilter> |
15 | #include <QGuiApplication> |
16 | |
17 | #include <X11/Xlib-xcb.h> // XGetXCBConnection |
18 | #include <xcb/sync.h> |
19 | |
20 | class XSyncBasedPollerHelper : public QAbstractNativeEventFilter |
21 | { |
22 | public: |
23 | XSyncBasedPollerHelper() |
24 | : q(nullptr) |
25 | , isActive(false) |
26 | { |
27 | } |
28 | ~XSyncBasedPollerHelper() override |
29 | { |
30 | delete q; |
31 | } |
32 | bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override |
33 | { |
34 | Q_UNUSED(result); |
35 | if (isActive && eventType == "xcb_generic_event_t" ) { |
36 | q->xcbEvent(event: reinterpret_cast<xcb_generic_event_t *>(message)); |
37 | } |
38 | return false; |
39 | } |
40 | XSyncBasedPoller *q; |
41 | bool isActive; |
42 | }; |
43 | |
44 | Q_GLOBAL_STATIC(XSyncBasedPollerHelper, s_globalXSyncBasedPoller) |
45 | |
46 | XSyncBasedPoller *XSyncBasedPoller::instance() |
47 | { |
48 | if (!s_globalXSyncBasedPoller()->q) { |
49 | new XSyncBasedPoller; |
50 | } |
51 | |
52 | return s_globalXSyncBasedPoller()->q; |
53 | } |
54 | |
55 | XSyncBasedPoller::XSyncBasedPoller(QObject *parent) |
56 | : KAbstractIdleTimePoller(parent) |
57 | , m_display(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) |
58 | , m_xcb_connection(nullptr) |
59 | , m_sync_event(0) |
60 | , m_idleCounter(None) |
61 | , m_resetAlarm(None) |
62 | , m_available(true) |
63 | { |
64 | Q_ASSERT(!s_globalXSyncBasedPoller()->q); |
65 | s_globalXSyncBasedPoller()->q = this; |
66 | |
67 | if (Q_UNLIKELY(!m_display)) { |
68 | m_available = false; |
69 | qCWarning(KIDLETIME_XSYNC_PLUGIN) << "xcb sync could not find display" ; |
70 | return; |
71 | } |
72 | m_xcb_connection = XGetXCBConnection(dpy: m_display); |
73 | |
74 | QCoreApplication::instance()->installNativeEventFilter(filterObj: s_globalXSyncBasedPoller()); |
75 | |
76 | const xcb_query_extension_reply_t *sync_reply = xcb_get_extension_data(c: m_xcb_connection, ext: &xcb_sync_id); |
77 | if (!sync_reply || !sync_reply->present) { |
78 | qCWarning(KIDLETIME_XSYNC_PLUGIN) << "xcb sync extension not found" ; |
79 | m_available = false; |
80 | return; |
81 | } |
82 | m_sync_event = sync_reply->first_event; |
83 | |
84 | #if 0 |
85 | |
86 | // Workaround for https://bugs.freedesktop.org/show_bug.cgi?id=23403 |
87 | #define xcb_sync_systemcounter_name(sc) (((char *)&(sc)->name_len) + 2) |
88 | |
89 | xcb_sync_list_system_counters_cookie_t cookie = xcb_sync_list_system_counters(m_xcb_connection); |
90 | xcb_sync_list_system_counters_reply_t *reply = xcb_sync_list_system_counters_reply(m_xcb_connection, cookie, NULL); |
91 | |
92 | xcb_sync_systemcounter_iterator_t iter; |
93 | for (iter = xcb_sync_list_system_counters_counters_iterator(reply); |
94 | iter.rem; xcb_sync_systemcounter_next(&iter)) { |
95 | printf("%d: %.*s\n" , iter.data->counter, |
96 | iter.data->name_len, xcb_sync_systemcounter_name(iter.data)); |
97 | /* Extra info for debugging: */ |
98 | printf(" Actual name: %.*s\n" , iter.data->name_len, |
99 | ((char *) &iter.data->name_len) + 2); |
100 | } |
101 | |
102 | int xcbcounters = xcb_sync_list_system_counters_counters_length(reply); |
103 | xcb_sync_systemcounter_iterator_t it = xcb_sync_list_system_counters_counters_iterator(reply); |
104 | for (int i = 0; i < xcbcounters; ++i) { |
105 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << it.data->counter << it.rem << it.index; |
106 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "name length" << xcb_sync_systemcounter_name_length(it.data); |
107 | QByteArray name(xcb_sync_systemcounter_name(it.data), xcb_sync_systemcounter_name_length(it.data)); |
108 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << name; |
109 | xcb_sync_systemcounter_next(&it); |
110 | } |
111 | delete reply; |
112 | #endif |
113 | |
114 | int sync_major; |
115 | int sync_minor; |
116 | int old_sync_event; |
117 | int old_sync_error; |
118 | if (!XSyncQueryExtension(m_display, &old_sync_event, &old_sync_error)) { |
119 | m_available = false; |
120 | return; |
121 | } |
122 | |
123 | if (!XSyncInitialize(m_display, &sync_major, &sync_minor)) { |
124 | m_available = false; |
125 | return; |
126 | } |
127 | |
128 | int ncounters; |
129 | XSyncSystemCounter *counters = XSyncListSystemCounters(m_display, &ncounters); |
130 | |
131 | bool idleFound = false; |
132 | |
133 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << ncounters << "counters" ; |
134 | for (int i = 0; i < ncounters; ++i) { |
135 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << counters[i].name << counters[i].counter; |
136 | if (!strcmp(s1: counters[i].name, s2: "IDLETIME" )) { |
137 | m_idleCounter = counters[i].counter; |
138 | idleFound = true; |
139 | break; |
140 | } |
141 | } |
142 | |
143 | XSyncFreeSystemCounterList(counters); |
144 | |
145 | if (!idleFound) { |
146 | m_available = false; |
147 | } |
148 | |
149 | if (m_available) { |
150 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync seems available and ready" ; |
151 | } else { |
152 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync seems not available" ; |
153 | } |
154 | } |
155 | |
156 | XSyncBasedPoller::~XSyncBasedPoller() |
157 | { |
158 | } |
159 | |
160 | bool XSyncBasedPoller::isAvailable() |
161 | { |
162 | return m_available; |
163 | } |
164 | |
165 | bool XSyncBasedPoller::setUpPoller() |
166 | { |
167 | if (!isAvailable()) { |
168 | return false; |
169 | } |
170 | |
171 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync Inited" ; |
172 | |
173 | s_globalXSyncBasedPoller()->isActive = true; |
174 | |
175 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "Supported, init completed" ; |
176 | |
177 | return true; |
178 | } |
179 | |
180 | void XSyncBasedPoller::unloadPoller() |
181 | { |
182 | s_globalXSyncBasedPoller()->isActive = false; |
183 | } |
184 | |
185 | void XSyncBasedPoller::addTimeout(int nextTimeout) |
186 | { |
187 | /* We need to set the counter to the idle time + the value |
188 | * requested for next timeout |
189 | */ |
190 | |
191 | // If there's already an alarm for the requested timeout, skip |
192 | if (m_timeoutAlarm.contains(key: nextTimeout)) { |
193 | return; |
194 | } |
195 | |
196 | XSyncValue timeout; |
197 | XSyncAlarm newalarm = None; |
198 | |
199 | XSyncIntToValue(&timeout, nextTimeout); |
200 | |
201 | setAlarm(dpy: m_display, alarm: &newalarm, counter: m_idleCounter, test: XSyncPositiveComparison, value: timeout); |
202 | |
203 | m_timeoutAlarm.insert(key: nextTimeout, value: newalarm); |
204 | } |
205 | |
206 | int XSyncBasedPoller::forcePollRequest() |
207 | { |
208 | return poll(); |
209 | } |
210 | |
211 | int XSyncBasedPoller::poll() |
212 | { |
213 | XSyncValue idleTime; |
214 | XSyncQueryCounter(m_display, m_idleCounter, &idleTime); |
215 | |
216 | return XSyncValueLow32(idleTime); |
217 | } |
218 | |
219 | void XSyncBasedPoller::removeTimeout(int timeout) |
220 | { |
221 | if (m_timeoutAlarm.contains(key: timeout)) { |
222 | XSyncAlarm a = m_timeoutAlarm[timeout]; |
223 | XSyncDestroyAlarm(m_display, a); |
224 | m_timeoutAlarm.remove(key: timeout); |
225 | } |
226 | } |
227 | |
228 | QList<int> XSyncBasedPoller::timeouts() const |
229 | { |
230 | return m_timeoutAlarm.keys(); |
231 | } |
232 | |
233 | void XSyncBasedPoller::stopCatchingIdleEvents() |
234 | { |
235 | if (m_resetAlarm != None) { |
236 | XSyncDestroyAlarm(m_display, m_resetAlarm); |
237 | m_resetAlarm = None; |
238 | } |
239 | } |
240 | |
241 | void XSyncBasedPoller::catchIdleEvent() |
242 | { |
243 | XSyncValue idleTime; |
244 | |
245 | XSyncQueryCounter(m_display, m_idleCounter, &idleTime); |
246 | |
247 | /* Set the reset alarm to fire the next time idleCounter < the |
248 | * current counter value. XSyncNegativeComparison means <= so |
249 | * we have to subtract 1 from the counter value |
250 | */ |
251 | |
252 | // NOTE: this must be a int, else compilation might fail |
253 | int overflow; |
254 | XSyncValue add; |
255 | XSyncValue plusone; |
256 | XSyncIntToValue(&add, -1); |
257 | XSyncValueAdd(&plusone, idleTime, add, &overflow); |
258 | setAlarm(dpy: m_display, alarm: &m_resetAlarm, counter: m_idleCounter, test: XSyncNegativeComparison, value: plusone); |
259 | } |
260 | |
261 | void XSyncBasedPoller::reloadAlarms() |
262 | { |
263 | XSyncValue timeout; |
264 | |
265 | for (QHash<int, XSyncAlarm>::iterator i = m_timeoutAlarm.begin(); i != m_timeoutAlarm.end(); ++i) { |
266 | XSyncIntToValue(&timeout, i.key()); |
267 | |
268 | setAlarm(dpy: m_display, alarm: &(i.value()), counter: m_idleCounter, test: XSyncPositiveComparison, value: timeout); |
269 | } |
270 | } |
271 | |
272 | bool XSyncBasedPoller::xcbEvent(xcb_generic_event_t *event) |
273 | { |
274 | // qCDebug(KIDLETIME_XSYNC_PLUGIN) << event->response_type << "waiting for" << m_sync_event+XCB_SYNC_ALARM_NOTIFY; |
275 | if (event->response_type != m_sync_event + XCB_SYNC_ALARM_NOTIFY) { |
276 | return false; |
277 | } |
278 | |
279 | xcb_sync_alarm_notify_event_t *alarmEvent = reinterpret_cast<xcb_sync_alarm_notify_event_t *>(event); |
280 | |
281 | if (alarmEvent->state == XCB_SYNC_ALARMSTATE_DESTROYED) { |
282 | return false; |
283 | } |
284 | |
285 | for (QHash<int, XSyncAlarm>::const_iterator i = m_timeoutAlarm.constBegin(); i != m_timeoutAlarm.constEnd(); ++i) { |
286 | if (alarmEvent->alarm == i.value()) { |
287 | /* Bling! Caught! */ |
288 | Q_EMIT timeoutReached(msec: i.key()); |
289 | // Update the alarm to fire back if the system gets inactive for the same time |
290 | catchIdleEvent(); |
291 | return false; |
292 | } |
293 | } |
294 | |
295 | if (alarmEvent->alarm == m_resetAlarm) { |
296 | /* Resuming from idle here! */ |
297 | stopCatchingIdleEvents(); |
298 | reloadAlarms(); |
299 | Q_EMIT resumingFromIdle(); |
300 | } |
301 | |
302 | return false; |
303 | } |
304 | |
305 | void XSyncBasedPoller::setAlarm(Display *dpy, XSyncAlarm *alarm, XSyncCounter counter, XSyncTestType test, XSyncValue value) |
306 | { |
307 | XSyncAlarmAttributes attr; |
308 | XSyncValue delta; |
309 | unsigned int flags; |
310 | |
311 | XSyncIntToValue(&delta, 0); |
312 | |
313 | attr.trigger.counter = counter; |
314 | attr.trigger.value_type = XSyncAbsolute; |
315 | attr.trigger.test_type = test; |
316 | attr.trigger.wait_value = value; |
317 | attr.delta = delta; |
318 | |
319 | flags = XSyncCACounter | XSyncCAValueType | XSyncCATestType | XSyncCAValue | XSyncCADelta; |
320 | |
321 | if (*alarm) { |
322 | // xcb_sync_change_alarm_checked(m_xcb_connection, alarmId, ... |
323 | XSyncChangeAlarm(dpy, *alarm, flags, &attr); |
324 | } else { |
325 | *alarm = XSyncCreateAlarm(dpy, flags, &attr); |
326 | qCDebug(KIDLETIME_XSYNC_PLUGIN) << "Created alarm" << *alarm; |
327 | } |
328 | |
329 | XFlush(m_display); |
330 | } |
331 | |
332 | void XSyncBasedPoller::simulateUserActivity() |
333 | { |
334 | XResetScreenSaver(m_display); |
335 | XFlush(m_display); |
336 | } |
337 | |
338 | #include "moc_xsyncbasedpoller.cpp" |
339 | |