Warning, file /frameworks/kidletime/src/plugins/xsync/xsyncbasedpoller.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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