| 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 | |