File indexing completed on 2024-04-28 16:49:41

0001 /*
0002  *  SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
0003  *  SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 #include "xrandr.h"
0008 
0009 #include "xrandrconfig.h"
0010 #include "xrandrscreen.h"
0011 
0012 #include "../xcbeventlistener.h"
0013 #include "../xcbwrapper.h"
0014 
0015 #include "types.h"
0016 
0017 #include <QRect>
0018 #include <QTime>
0019 #include <QTimer>
0020 
0021 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0022 #include <private/qtx11extras_p.h>
0023 #else
0024 #include <QX11Info>
0025 #endif
0026 
0027 xcb_screen_t *XRandR::s_screen = nullptr;
0028 xcb_window_t XRandR::s_rootWindow = 0;
0029 XRandRConfig *XRandR::s_internalConfig = nullptr;
0030 int XRandR::s_randrBase = 0;
0031 int XRandR::s_randrError = 0;
0032 bool XRandR::s_monitorInitialized = false;
0033 bool XRandR::s_has_1_3 = false;
0034 bool XRandR::s_xorgCacheInitialized = false;
0035 
0036 using namespace KScreen;
0037 
0038 Q_LOGGING_CATEGORY(KSCREEN_XRANDR, "kscreen.xrandr")
0039 
0040 XRandR::XRandR()
0041     : KScreen::AbstractBackend()
0042     , m_x11Helper(nullptr)
0043     , m_isValid(false)
0044     , m_configChangeCompressor(nullptr)
0045 {
0046     qRegisterMetaType<xcb_randr_output_t>("xcb_randr_output_t");
0047     qRegisterMetaType<xcb_randr_crtc_t>("xcb_randr_crtc_t");
0048     qRegisterMetaType<xcb_randr_mode_t>("xcb_randr_mode_t");
0049     qRegisterMetaType<xcb_randr_connection_t>("xcb_randr_connection_t");
0050     qRegisterMetaType<xcb_randr_rotation_t>("xcb_randr_rotation_t");
0051     qRegisterMetaType<xcb_timestamp_t>("xcb_timestamp_t");
0052 
0053     // Use our own connection to make sure that we won't mess up Qt's connection
0054     // if something goes wrong on our side.
0055     xcb_generic_error_t *error = nullptr;
0056     xcb_randr_query_version_reply_t *version;
0057     XCB::connection();
0058     version = xcb_randr_query_version_reply(XCB::connection(), //
0059                                             xcb_randr_query_version(XCB::connection(), XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
0060                                             &error);
0061     if (!version || error) {
0062         XCB::closeConnection();
0063         free(error);
0064         return;
0065     }
0066 
0067     if ((version->major_version > 1) || ((version->major_version == 1) && (version->minor_version >= 2))) {
0068         m_isValid = true;
0069     } else {
0070         XCB::closeConnection();
0071         qCWarning(KSCREEN_XRANDR) << "XRandR extension not available or unsupported version";
0072         return;
0073     }
0074 
0075     if (s_screen == nullptr) {
0076         s_screen = XCB::screenOfDisplay(XCB::connection(), QX11Info::appScreen());
0077         s_rootWindow = s_screen->root;
0078 
0079         xcb_prefetch_extension_data(XCB::connection(), &xcb_randr_id);
0080         auto reply = xcb_get_extension_data(XCB::connection(), &xcb_randr_id);
0081         s_randrBase = reply->first_event;
0082         s_randrError = reply->first_error;
0083     }
0084 
0085     XRandR::s_has_1_3 = (version->major_version > 1 || (version->major_version == 1 && version->minor_version >= 3));
0086 
0087     if (s_internalConfig == nullptr) {
0088         s_internalConfig = new XRandRConfig();
0089     }
0090 
0091     if (!s_monitorInitialized) {
0092         m_x11Helper = new XCBEventListener();
0093         connect(m_x11Helper, &XCBEventListener::outputChanged, this, &XRandR::outputChanged, Qt::QueuedConnection);
0094         connect(m_x11Helper, &XCBEventListener::crtcChanged, this, &XRandR::crtcChanged, Qt::QueuedConnection);
0095         connect(m_x11Helper, &XCBEventListener::screenChanged, this, &XRandR::screenChanged, Qt::QueuedConnection);
0096 
0097         m_configChangeCompressor = new QTimer(this);
0098         m_configChangeCompressor->setSingleShot(true);
0099         m_configChangeCompressor->setInterval(500);
0100         connect(m_configChangeCompressor, &QTimer::timeout, [&]() {
0101             qCDebug(KSCREEN_XRANDR) << "Emitting configChanged()";
0102             Q_EMIT configChanged(config());
0103         });
0104 
0105         s_monitorInitialized = true;
0106     }
0107 }
0108 
0109 XRandR::~XRandR()
0110 {
0111     delete m_x11Helper;
0112 }
0113 
0114 QString XRandR::name() const
0115 {
0116     return QStringLiteral("XRandR");
0117 }
0118 
0119 QString XRandR::serviceName() const
0120 {
0121     return QStringLiteral("org.kde.KScreen.Backend.XRandR");
0122 }
0123 
0124 void XRandR::outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection)
0125 {
0126     m_configChangeCompressor->start();
0127 
0128     XRandROutput *xOutput = s_internalConfig->output(output);
0129     if (!xOutput) {
0130         s_internalConfig->addNewOutput(output);
0131         return;
0132     }
0133 
0134     // check if this output disappeared
0135     if (crtc == XCB_NONE && mode == XCB_NONE && connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
0136         XCB::OutputInfo info(output, XCB_TIME_CURRENT_TIME);
0137         if (info.isNull()) {
0138             s_internalConfig->removeOutput(output);
0139             qCDebug(KSCREEN_XRANDR) << "Output" << output << " removed";
0140             return;
0141         }
0142         // info is valid: the output is still there
0143     }
0144 
0145     xOutput->update(crtc, mode, connection);
0146     qCDebug(KSCREEN_XRANDR) << "Output" << xOutput->id() << ": connected =" << xOutput->isConnected() << ", enabled =" << xOutput->isEnabled();
0147 }
0148 
0149 void XRandR::crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom, xcb_timestamp_t timestamp)
0150 {
0151     XRandRCrtc *xCrtc = s_internalConfig->crtc(crtc);
0152     if (!xCrtc) {
0153         s_internalConfig->addNewCrtc(crtc);
0154         xCrtc = s_internalConfig->crtc(crtc);
0155     }
0156 
0157     xCrtc->update(mode, rotation, geom); // BUG 472280 Still need to call update(...) because the timestamp is newer
0158     xCrtc->updateConfigTimestamp(timestamp);
0159     m_configChangeCompressor->start();
0160 }
0161 
0162 void XRandR::screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm)
0163 {
0164     Q_UNUSED(sizeMm);
0165 
0166     QSize newSizePx = sizePx;
0167     if (rotation == XCB_RANDR_ROTATION_ROTATE_90 || rotation == XCB_RANDR_ROTATION_ROTATE_270) {
0168         newSizePx.transpose();
0169     }
0170 
0171     XRandRScreen *xScreen = s_internalConfig->screen();
0172     Q_ASSERT(xScreen);
0173     xScreen->update(newSizePx);
0174 
0175     m_configChangeCompressor->start();
0176 }
0177 
0178 ConfigPtr XRandR::config() const
0179 {
0180     return s_internalConfig->toKScreenConfig();
0181 }
0182 
0183 void XRandR::setConfig(const ConfigPtr &config)
0184 {
0185     if (!config) {
0186         return;
0187     }
0188 
0189     qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig";
0190     s_internalConfig->applyKScreenConfig(config);
0191     qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig done!";
0192 }
0193 
0194 QByteArray XRandR::edid(int outputId) const
0195 {
0196     const XRandROutput *output = s_internalConfig->output(outputId);
0197     if (!output) {
0198         return QByteArray();
0199     }
0200 
0201     return output->edid();
0202 }
0203 
0204 bool XRandR::isValid() const
0205 {
0206     return m_isValid;
0207 }
0208 
0209 quint8 *XRandR::getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t &len)
0210 {
0211     quint8 *result;
0212 
0213     auto cookie = xcb_randr_get_output_property(XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 100, false, false);
0214     auto reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr);
0215 
0216     if (reply->type == XCB_ATOM_INTEGER && reply->format == 8) {
0217         result = new quint8[reply->num_items];
0218         memcpy(result, xcb_randr_get_output_property_data(reply), reply->num_items);
0219         len = reply->num_items;
0220     } else {
0221         result = nullptr;
0222     }
0223 
0224     free(reply);
0225     return result;
0226 }
0227 
0228 QByteArray XRandR::outputEdid(xcb_randr_output_t outputId)
0229 {
0230     size_t len = 0;
0231     quint8 *result;
0232 
0233     auto edid_atom = XCB::InternAtom(false, 4, "EDID")->atom;
0234     result = XRandR::getXProperty(outputId, edid_atom, len);
0235     if (result == nullptr) {
0236         auto edid_atom = XCB::InternAtom(false, 9, "EDID_DATA")->atom;
0237         result = XRandR::getXProperty(outputId, edid_atom, len);
0238     }
0239     if (result == nullptr) {
0240         auto edid_atom = XCB::InternAtom(false, 25, "XFree86_DDC_EDID1_RAWDATA")->atom;
0241         result = XRandR::getXProperty(outputId, edid_atom, len);
0242     }
0243 
0244     QByteArray edid;
0245     if (result != nullptr) {
0246         if (len % 128 == 0) {
0247             edid = QByteArray(reinterpret_cast<const char *>(result), len);
0248         }
0249         delete[] result;
0250     }
0251     return edid;
0252 }
0253 
0254 bool XRandR::hasProperty(xcb_randr_output_t output, const QByteArray &name)
0255 {
0256     xcb_generic_error_t *error = nullptr;
0257     auto atom = XCB::InternAtom(false, name.length(), name.constData())->atom;
0258 
0259     auto cookie = xcb_randr_get_output_property(XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 1, false, false);
0260     auto prop_reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, &error);
0261 
0262     const bool ret = prop_reply->num_items == 1;
0263     free(prop_reply);
0264     return ret;
0265 }
0266 
0267 xcb_randr_get_screen_resources_reply_t *XRandR::screenResources()
0268 {
0269     if (XRandR::s_has_1_3) {
0270         if (XRandR::s_xorgCacheInitialized) {
0271             // HACK: This abuses the fact that xcb_randr_get_screen_resources_reply_t
0272             // and xcb_randr_get_screen_resources_current_reply_t are the same
0273             return reinterpret_cast<xcb_randr_get_screen_resources_reply_t *>(
0274                 xcb_randr_get_screen_resources_current_reply(XCB::connection(),
0275                                                              xcb_randr_get_screen_resources_current(XCB::connection(), XRandR::rootWindow()),
0276                                                              nullptr));
0277         } else {
0278             /* XRRGetScreenResourcesCurrent is faster then XRRGetScreenResources
0279              * because it returns cached values. However the cached values are not
0280              * available until someone calls XRRGetScreenResources first. In case
0281              * we happen to be the first ones, we need to fill the cache first. */
0282             XRandR::s_xorgCacheInitialized = true;
0283         }
0284     }
0285 
0286     return xcb_randr_get_screen_resources_reply(XCB::connection(), xcb_randr_get_screen_resources(XCB::connection(), XRandR::rootWindow()), nullptr);
0287 }
0288 
0289 xcb_window_t XRandR::rootWindow()
0290 {
0291     return s_rootWindow;
0292 }
0293 
0294 xcb_screen_t *XRandR::screen()
0295 {
0296     return s_screen;
0297 }