File indexing completed on 2024-05-12 05:33:52

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