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"