File indexing completed on 2024-04-21 16:17:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "xcbeventlistener.h"
0008 
0009 #include "../xcbwrapper.h"
0010 
0011 #include <QGuiApplication>
0012 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0013 #include <private/qtx11extras_p.h>
0014 #else
0015 #include <QX11Info>
0016 #endif
0017 
0018 Q_LOGGING_CATEGORY(KSCREEN_XCB_HELPER, "kscreen.xcb.helper")
0019 
0020 XCBEventListener::XCBEventListener()
0021     : m_isRandrPresent(false)
0022     , m_randrBase(0)
0023     , m_randrErrorBase(0)
0024     , m_majorOpcode(0)
0025     , m_versionMajor(0)
0026     , m_versionMinor(0)
0027     , m_window(0)
0028 {
0029     xcb_connection_t *c = QX11Info::connection();
0030     xcb_prefetch_extension_data(c, &xcb_randr_id);
0031     auto cookie = xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
0032     const auto *queryExtension = xcb_get_extension_data(c, &xcb_randr_id);
0033     if (!queryExtension) {
0034         qCDebug(KSCREEN_XCB_HELPER) << "Fail to query for xrandr extension";
0035         return;
0036     }
0037     if (!queryExtension->present) {
0038         qCDebug(KSCREEN_XCB_HELPER) << "XRandR extension is not present at all";
0039         return;
0040     }
0041 
0042     m_isRandrPresent = queryExtension->present;
0043     m_randrBase = queryExtension->first_event;
0044     m_randrErrorBase = queryExtension->first_error;
0045     m_majorOpcode = queryExtension->major_opcode;
0046 
0047     xcb_generic_error_t *error = nullptr;
0048     auto *versionReply = xcb_randr_query_version_reply(c, cookie, &error);
0049     Q_ASSERT_X(versionReply, "xrandrxcbhelper", "Query to fetch xrandr version failed");
0050     if (error) {
0051         qFatal("Error while querying for xrandr version: %d", error->error_code);
0052     }
0053     m_versionMajor = versionReply->major_version;
0054     m_versionMinor = versionReply->minor_version;
0055     free(versionReply);
0056 
0057     qCDebug(KSCREEN_XCB_HELPER).nospace() << "Detected XRandR " << m_versionMajor << "." << m_versionMinor;
0058     qCDebug(KSCREEN_XCB_HELPER) << "Event Base: " << m_randrBase;
0059     qCDebug(KSCREEN_XCB_HELPER) << "Event Error: " << m_randrErrorBase;
0060 
0061     uint32_t rWindow = QX11Info::appRootWindow();
0062     m_window = xcb_generate_id(c);
0063     xcb_create_window(c, XCB_COPY_FROM_PARENT, m_window, rWindow, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, nullptr);
0064 
0065     xcb_randr_select_input(c,
0066                            m_window,
0067                            XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE
0068                                | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
0069 
0070     qApp->installNativeEventFilter(this);
0071 }
0072 
0073 XCBEventListener::~XCBEventListener()
0074 {
0075     if (m_window && QX11Info::connection()) {
0076         xcb_destroy_window(QX11Info::connection(), m_window);
0077     }
0078 }
0079 
0080 QString XCBEventListener::rotationToString(xcb_randr_rotation_t rotation)
0081 {
0082     switch (rotation) {
0083     case XCB_RANDR_ROTATION_ROTATE_0:
0084         return QStringLiteral("Rotate_0");
0085     case XCB_RANDR_ROTATION_ROTATE_90:
0086         return QStringLiteral("Rotate_90");
0087     case XCB_RANDR_ROTATION_ROTATE_180:
0088         return QStringLiteral("Rotate_180");
0089     case XCB_RANDR_ROTATION_ROTATE_270:
0090         return QStringLiteral("Rotate_270");
0091     case XCB_RANDR_ROTATION_REFLECT_X:
0092         return QStringLiteral("Reflect_X");
0093     case XCB_RANDR_ROTATION_REFLECT_Y:
0094         return QStringLiteral("Reflect_Y");
0095     }
0096 
0097     return QStringLiteral("invalid value (%1)").arg(rotation);
0098 }
0099 
0100 QString XCBEventListener::connectionToString(xcb_randr_connection_t connection)
0101 {
0102     switch (connection) {
0103     case XCB_RANDR_CONNECTION_CONNECTED:
0104         return QStringLiteral("Connected");
0105     case XCB_RANDR_CONNECTION_DISCONNECTED:
0106         return QStringLiteral("Disconnected");
0107     case XCB_RANDR_CONNECTION_UNKNOWN:
0108         return QStringLiteral("UnknownConnection");
0109     }
0110 
0111     return QStringLiteral("invalid value (%1)").arg(connection);
0112 }
0113 
0114 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0115 bool XCBEventListener::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
0116 #else
0117 bool XCBEventListener::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0118 #endif
0119 {
0120     Q_UNUSED(result);
0121 
0122     if (eventType != "xcb_generic_event_t") {
0123         return false;
0124     }
0125 
0126     auto *e = static_cast<xcb_generic_event_t *>(message);
0127     const uint8_t xEventType = e->response_type & ~0x80;
0128 
0129     // If this event is not xcb_randr_notify, we don't want it
0130     if (xEventType == m_randrBase + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
0131         handleScreenChange(e);
0132     }
0133     if (xEventType == m_randrBase + XCB_RANDR_NOTIFY) {
0134         handleXRandRNotify(e);
0135     }
0136 
0137     return false;
0138 }
0139 
0140 void XCBEventListener::handleScreenChange(xcb_generic_event_t *e)
0141 {
0142     auto *e2 = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(e);
0143 
0144     // Only accept notifications for our window
0145     if (e2->request_window != m_window) {
0146         return;
0147     }
0148 
0149     qCDebug(KSCREEN_XCB_HELPER) << "RRScreenChangeNotify";
0150     qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << e2->timestamp;
0151     qCDebug(KSCREEN_XCB_HELPER) << "\tConfig_timestamp: " << e2->config_timestamp;
0152     qCDebug(KSCREEN_XCB_HELPER) << "\tWindow:" << e2->request_window;
0153     qCDebug(KSCREEN_XCB_HELPER) << "\tRoot:" << e2->root;
0154     qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)e2->rotation);
0155     qCDebug(KSCREEN_XCB_HELPER) << "\tSize ID:" << e2->sizeID;
0156     qCDebug(KSCREEN_XCB_HELPER) << "\tSize: " << e2->width << e2->height;
0157     qCDebug(KSCREEN_XCB_HELPER) << "\tSizeMM: " << e2->mwidth << e2->mheight;
0158 
0159     Q_EMIT screenChanged((xcb_randr_rotation_t)e2->rotation, QSize(e2->width, e2->height), QSize(e2->mwidth, e2->mheight));
0160     Q_EMIT outputsChanged();
0161 }
0162 
0163 void XCBEventListener::handleXRandRNotify(xcb_generic_event_t *e)
0164 {
0165     auto *randrEvent = reinterpret_cast<xcb_randr_notify_event_t *>(e);
0166 
0167     if (randrEvent->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) {
0168         xcb_randr_crtc_change_t crtc = randrEvent->u.cc;
0169         qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_CrtcChange";
0170         qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << crtc.timestamp;
0171         qCDebug(KSCREEN_XCB_HELPER) << "\tCRTC: " << crtc.crtc;
0172         qCDebug(KSCREEN_XCB_HELPER) << "\tMode: " << crtc.mode;
0173         qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)crtc.rotation);
0174         qCDebug(KSCREEN_XCB_HELPER) << "\tGeometry: " << crtc.x << crtc.y << crtc.width << crtc.height;
0175         Q_EMIT crtcChanged(crtc.crtc, crtc.mode, (xcb_randr_rotation_t)crtc.rotation, QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.timestamp);
0176 
0177     } else if (randrEvent->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) {
0178         xcb_randr_output_change_t output = randrEvent->u.oc;
0179         qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_OutputChange";
0180         qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << output.timestamp;
0181         qCDebug(KSCREEN_XCB_HELPER) << "\tOutput: " << output.output;
0182         qCDebug(KSCREEN_XCB_HELPER) << "\tCRTC: " << output.crtc;
0183         qCDebug(KSCREEN_XCB_HELPER) << "\tMode: " << output.mode;
0184         qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)output.rotation);
0185         qCDebug(KSCREEN_XCB_HELPER) << "\tConnection: " << connectionToString((xcb_randr_connection_t)output.connection);
0186         qCDebug(KSCREEN_XCB_HELPER) << "\tSubpixel Order: " << output.subpixel_order;
0187         Q_EMIT outputChanged(output.output, output.crtc, output.mode, (xcb_randr_connection_t)output.connection);
0188 
0189     } else if (randrEvent->subCode == XCB_RANDR_NOTIFY_OUTPUT_PROPERTY) {
0190         xcb_randr_output_property_t property = randrEvent->u.op;
0191 
0192         XCB::ScopedPointer<xcb_get_atom_name_reply_t> reply(
0193             xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name(QX11Info::connection(), property.atom), nullptr));
0194 
0195         qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_OutputProperty (ignored)";
0196         qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << property.timestamp;
0197         qCDebug(KSCREEN_XCB_HELPER) << "\tOutput: " << property.output;
0198         qCDebug(KSCREEN_XCB_HELPER) << "\tProperty: " << xcb_get_atom_name_name(reply.data());
0199         qCDebug(KSCREEN_XCB_HELPER) << "\tState (newValue, Deleted): " << property.status;
0200     }
0201 }