File indexing completed on 2024-04-28 09:26:06

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