File indexing completed on 2024-05-12 13:36:45
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 }