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
20class XSyncBasedPollerHelper : public QAbstractNativeEventFilter
21{
22public:
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
44Q_GLOBAL_STATIC(XSyncBasedPollerHelper, s_globalXSyncBasedPoller)
45
46XSyncBasedPoller *XSyncBasedPoller::instance()
47{
48 if (!s_globalXSyncBasedPoller()->q) {
49 new XSyncBasedPoller;
50 }
51
52 return s_globalXSyncBasedPoller()->q;
53}
54
55XSyncBasedPoller::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
156XSyncBasedPoller::~XSyncBasedPoller()
157{
158}
159
160bool XSyncBasedPoller::isAvailable()
161{
162 return m_available;
163}
164
165bool 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
180void XSyncBasedPoller::unloadPoller()
181{
182 s_globalXSyncBasedPoller()->isActive = false;
183}
184
185void 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
206int XSyncBasedPoller::forcePollRequest()
207{
208 return poll();
209}
210
211int XSyncBasedPoller::poll()
212{
213 XSyncValue idleTime;
214 XSyncQueryCounter(m_display, m_idleCounter, &idleTime);
215
216 return XSyncValueLow32(idleTime);
217}
218
219void 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
228QList<int> XSyncBasedPoller::timeouts() const
229{
230 return m_timeoutAlarm.keys();
231}
232
233void XSyncBasedPoller::stopCatchingIdleEvents()
234{
235 if (m_resetAlarm != None) {
236 XSyncDestroyAlarm(m_display, m_resetAlarm);
237 m_resetAlarm = None;
238 }
239}
240
241void 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
261void 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
272bool 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
305void 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
332void XSyncBasedPoller::simulateUserActivity()
333{
334 XResetScreenSaver(m_display);
335 XFlush(m_display);
336}
337
338#include "moc_xsyncbasedpoller.cpp"
339

source code of kidletime/src/plugins/xsync/xsyncbasedpoller.cpp