File indexing completed on 2024-04-28 03:54:38

0001 /* This file is part of the KDE libraries
0002    SPDX-FileCopyrightText: 2009 Dario Freddi <drf at kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 // Exceptionnally, include QCoreApplication before our own header, because that one includes X11 headers (#define None...)
0008 #include <QCoreApplication>
0009 
0010 #include "xsync_logging.h"
0011 
0012 #include "xsyncbasedpoller.h"
0013 
0014 #include <QAbstractNativeEventFilter>
0015 #include <QGuiApplication>
0016 
0017 #include <X11/Xlib-xcb.h> // XGetXCBConnection
0018 #include <xcb/sync.h>
0019 
0020 class XSyncBasedPollerHelper : public QAbstractNativeEventFilter
0021 {
0022 public:
0023     XSyncBasedPollerHelper()
0024         : q(nullptr)
0025         , isActive(false)
0026     {
0027     }
0028     ~XSyncBasedPollerHelper() override
0029     {
0030         delete q;
0031     }
0032     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
0033     {
0034         Q_UNUSED(result);
0035         if (isActive && eventType == "xcb_generic_event_t") {
0036             q->xcbEvent(reinterpret_cast<xcb_generic_event_t *>(message));
0037         }
0038         return false;
0039     }
0040     XSyncBasedPoller *q;
0041     bool isActive;
0042 };
0043 
0044 Q_GLOBAL_STATIC(XSyncBasedPollerHelper, s_globalXSyncBasedPoller)
0045 
0046 XSyncBasedPoller *XSyncBasedPoller::instance()
0047 {
0048     if (!s_globalXSyncBasedPoller()->q) {
0049         new XSyncBasedPoller;
0050     }
0051 
0052     return s_globalXSyncBasedPoller()->q;
0053 }
0054 
0055 XSyncBasedPoller::XSyncBasedPoller(QObject *parent)
0056     : KAbstractIdleTimePoller(parent)
0057     , m_display(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display())
0058     , m_xcb_connection(nullptr)
0059     , m_sync_event(0)
0060     , m_idleCounter(None)
0061     , m_resetAlarm(None)
0062     , m_available(true)
0063 {
0064     Q_ASSERT(!s_globalXSyncBasedPoller()->q);
0065     s_globalXSyncBasedPoller()->q = this;
0066 
0067     if (Q_UNLIKELY(!m_display)) {
0068         m_available = false;
0069         qCWarning(KIDLETIME_XSYNC_PLUGIN) << "xcb sync could not find display";
0070         return;
0071     }
0072     m_xcb_connection = XGetXCBConnection(m_display);
0073 
0074     QCoreApplication::instance()->installNativeEventFilter(s_globalXSyncBasedPoller());
0075 
0076     const xcb_query_extension_reply_t *sync_reply = xcb_get_extension_data(m_xcb_connection, &xcb_sync_id);
0077     if (!sync_reply || !sync_reply->present) {
0078         qCWarning(KIDLETIME_XSYNC_PLUGIN) << "xcb sync extension not found";
0079         m_available = false;
0080         return;
0081     }
0082     m_sync_event = sync_reply->first_event;
0083 
0084 #if 0
0085 
0086     // Workaround for https://bugs.freedesktop.org/show_bug.cgi?id=23403
0087 #define xcb_sync_systemcounter_name(sc) (((char *)&(sc)->name_len) + 2)
0088 
0089     xcb_sync_list_system_counters_cookie_t cookie = xcb_sync_list_system_counters(m_xcb_connection);
0090     xcb_sync_list_system_counters_reply_t *reply = xcb_sync_list_system_counters_reply(m_xcb_connection, cookie, NULL);
0091 
0092     xcb_sync_systemcounter_iterator_t iter;
0093     for (iter = xcb_sync_list_system_counters_counters_iterator(reply);
0094             iter.rem; xcb_sync_systemcounter_next(&iter)) {
0095         printf("%d: %.*s\n", iter.data->counter,
0096                iter.data->name_len, xcb_sync_systemcounter_name(iter.data));
0097         /* Extra info for debugging: */
0098         printf("  Actual name: %.*s\n", iter.data->name_len,
0099                ((char *) &iter.data->name_len) + 2);
0100     }
0101 
0102     int xcbcounters = xcb_sync_list_system_counters_counters_length(reply);
0103     xcb_sync_systemcounter_iterator_t it = xcb_sync_list_system_counters_counters_iterator(reply);
0104     for (int i = 0; i < xcbcounters; ++i) {
0105         qCDebug(KIDLETIME_XSYNC_PLUGIN) << it.data->counter << it.rem << it.index;
0106         qCDebug(KIDLETIME_XSYNC_PLUGIN) << "name length" << xcb_sync_systemcounter_name_length(it.data);
0107         QByteArray name(xcb_sync_systemcounter_name(it.data), xcb_sync_systemcounter_name_length(it.data));
0108         qCDebug(KIDLETIME_XSYNC_PLUGIN) << name;
0109         xcb_sync_systemcounter_next(&it);
0110     }
0111     delete reply;
0112 #endif
0113 
0114     int sync_major;
0115     int sync_minor;
0116     int old_sync_event;
0117     int old_sync_error;
0118     if (!XSyncQueryExtension(m_display, &old_sync_event, &old_sync_error)) {
0119         m_available = false;
0120         return;
0121     }
0122 
0123     if (!XSyncInitialize(m_display, &sync_major, &sync_minor)) {
0124         m_available = false;
0125         return;
0126     }
0127 
0128     int ncounters;
0129     XSyncSystemCounter *counters = XSyncListSystemCounters(m_display, &ncounters);
0130 
0131     bool idleFound = false;
0132 
0133     qCDebug(KIDLETIME_XSYNC_PLUGIN) << ncounters << "counters";
0134     for (int i = 0; i < ncounters; ++i) {
0135         qCDebug(KIDLETIME_XSYNC_PLUGIN) << counters[i].name << counters[i].counter;
0136         if (!strcmp(counters[i].name, "IDLETIME")) {
0137             m_idleCounter = counters[i].counter;
0138             idleFound = true;
0139             break;
0140         }
0141     }
0142 
0143     XSyncFreeSystemCounterList(counters);
0144 
0145     if (!idleFound) {
0146         m_available = false;
0147     }
0148 
0149     if (m_available) {
0150         qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync seems available and ready";
0151     } else {
0152         qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync seems not available";
0153     }
0154 }
0155 
0156 XSyncBasedPoller::~XSyncBasedPoller()
0157 {
0158 }
0159 
0160 bool XSyncBasedPoller::isAvailable()
0161 {
0162     return m_available;
0163 }
0164 
0165 bool XSyncBasedPoller::setUpPoller()
0166 {
0167     if (!isAvailable()) {
0168         return false;
0169     }
0170 
0171     qCDebug(KIDLETIME_XSYNC_PLUGIN) << "XSync Inited";
0172 
0173     s_globalXSyncBasedPoller()->isActive = true;
0174 
0175     qCDebug(KIDLETIME_XSYNC_PLUGIN) << "Supported, init completed";
0176 
0177     return true;
0178 }
0179 
0180 void XSyncBasedPoller::unloadPoller()
0181 {
0182     s_globalXSyncBasedPoller()->isActive = false;
0183 }
0184 
0185 void XSyncBasedPoller::addTimeout(int nextTimeout)
0186 {
0187     /* We need to set the counter to the idle time + the value
0188      * requested for next timeout
0189      */
0190 
0191     // If there's already an alarm for the requested timeout, skip
0192     if (m_timeoutAlarm.contains(nextTimeout)) {
0193         return;
0194     }
0195 
0196     XSyncValue timeout;
0197     XSyncAlarm newalarm = None;
0198 
0199     XSyncIntToValue(&timeout, nextTimeout);
0200 
0201     setAlarm(m_display, &newalarm, m_idleCounter, XSyncPositiveComparison, timeout);
0202 
0203     m_timeoutAlarm.insert(nextTimeout, newalarm);
0204 }
0205 
0206 int XSyncBasedPoller::forcePollRequest()
0207 {
0208     return poll();
0209 }
0210 
0211 int XSyncBasedPoller::poll()
0212 {
0213     XSyncValue idleTime;
0214     XSyncQueryCounter(m_display, m_idleCounter, &idleTime);
0215 
0216     return XSyncValueLow32(idleTime);
0217 }
0218 
0219 void XSyncBasedPoller::removeTimeout(int timeout)
0220 {
0221     if (m_timeoutAlarm.contains(timeout)) {
0222         XSyncAlarm a = m_timeoutAlarm[timeout];
0223         XSyncDestroyAlarm(m_display, a);
0224         m_timeoutAlarm.remove(timeout);
0225     }
0226 }
0227 
0228 QList<int> XSyncBasedPoller::timeouts() const
0229 {
0230     return m_timeoutAlarm.keys();
0231 }
0232 
0233 void XSyncBasedPoller::stopCatchingIdleEvents()
0234 {
0235     if (m_resetAlarm != None) {
0236         XSyncDestroyAlarm(m_display, m_resetAlarm);
0237         m_resetAlarm = None;
0238     }
0239 }
0240 
0241 void XSyncBasedPoller::catchIdleEvent()
0242 {
0243     XSyncValue idleTime;
0244 
0245     XSyncQueryCounter(m_display, m_idleCounter, &idleTime);
0246 
0247     /* Set the reset alarm to fire the next time idleCounter < the
0248      * current counter value. XSyncNegativeComparison means <= so
0249      * we have to subtract 1 from the counter value
0250      */
0251 
0252     // NOTE: this must be a int, else compilation might fail
0253     int overflow;
0254     XSyncValue add;
0255     XSyncValue plusone;
0256     XSyncIntToValue(&add, -1);
0257     XSyncValueAdd(&plusone, idleTime, add, &overflow);
0258     setAlarm(m_display, &m_resetAlarm, m_idleCounter, XSyncNegativeComparison, plusone);
0259 }
0260 
0261 void XSyncBasedPoller::reloadAlarms()
0262 {
0263     XSyncValue timeout;
0264 
0265     for (QHash<int, XSyncAlarm>::iterator i = m_timeoutAlarm.begin(); i != m_timeoutAlarm.end(); ++i) {
0266         XSyncIntToValue(&timeout, i.key());
0267 
0268         setAlarm(m_display, &(i.value()), m_idleCounter, XSyncPositiveComparison, timeout);
0269     }
0270 }
0271 
0272 bool XSyncBasedPoller::xcbEvent(xcb_generic_event_t *event)
0273 {
0274     // qCDebug(KIDLETIME_XSYNC_PLUGIN) << event->response_type << "waiting for" << m_sync_event+XCB_SYNC_ALARM_NOTIFY;
0275     if (event->response_type != m_sync_event + XCB_SYNC_ALARM_NOTIFY) {
0276         return false;
0277     }
0278 
0279     xcb_sync_alarm_notify_event_t *alarmEvent = reinterpret_cast<xcb_sync_alarm_notify_event_t *>(event);
0280 
0281     if (alarmEvent->state == XCB_SYNC_ALARMSTATE_DESTROYED) {
0282         return false;
0283     }
0284 
0285     for (QHash<int, XSyncAlarm>::const_iterator i = m_timeoutAlarm.constBegin(); i != m_timeoutAlarm.constEnd(); ++i) {
0286         if (alarmEvent->alarm == i.value()) {
0287             /* Bling! Caught! */
0288             Q_EMIT timeoutReached(i.key());
0289             // Update the alarm to fire back if the system gets inactive for the same time
0290             catchIdleEvent();
0291             return false;
0292         }
0293     }
0294 
0295     if (alarmEvent->alarm == m_resetAlarm) {
0296         /* Resuming from idle here! */
0297         stopCatchingIdleEvents();
0298         reloadAlarms();
0299         Q_EMIT resumingFromIdle();
0300     }
0301 
0302     return false;
0303 }
0304 
0305 void XSyncBasedPoller::setAlarm(Display *dpy, XSyncAlarm *alarm, XSyncCounter counter, XSyncTestType test, XSyncValue value)
0306 {
0307     XSyncAlarmAttributes attr;
0308     XSyncValue delta;
0309     unsigned int flags;
0310 
0311     XSyncIntToValue(&delta, 0);
0312 
0313     attr.trigger.counter = counter;
0314     attr.trigger.value_type = XSyncAbsolute;
0315     attr.trigger.test_type = test;
0316     attr.trigger.wait_value = value;
0317     attr.delta = delta;
0318 
0319     flags = XSyncCACounter | XSyncCAValueType | XSyncCATestType | XSyncCAValue | XSyncCADelta;
0320 
0321     if (*alarm) {
0322         // xcb_sync_change_alarm_checked(m_xcb_connection, alarmId,  ...
0323         XSyncChangeAlarm(dpy, *alarm, flags, &attr);
0324     } else {
0325         *alarm = XSyncCreateAlarm(dpy, flags, &attr);
0326         qCDebug(KIDLETIME_XSYNC_PLUGIN) << "Created alarm" << *alarm;
0327     }
0328 
0329     XFlush(m_display);
0330 }
0331 
0332 void XSyncBasedPoller::simulateUserActivity()
0333 {
0334     XResetScreenSaver(m_display);
0335     XFlush(m_display);
0336 }
0337 
0338 #include "moc_xsyncbasedpoller.cpp"