File indexing completed on 2024-04-28 16:49:42
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 "xrandroutput.h" 0008 0009 #include "xrandr.h" 0010 #include "xrandrconfig.h" 0011 0012 #include "../utils.h" 0013 0014 #include "mode.h" 0015 0016 #include <array> 0017 #include <cstring> 0018 #include <qglobal.h> 0019 #include <utility> 0020 #include <xcb/randr.h> 0021 #include <xcb/render.h> 0022 0023 Q_DECLARE_METATYPE(QList<int>) 0024 0025 #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t)((d)*65536)) 0026 #define FIXED_TO_DOUBLE(f) ((double)((f) / 65536.0)) 0027 0028 xcb_render_fixed_t fOne = DOUBLE_TO_FIXED(1); 0029 xcb_render_fixed_t fZero = DOUBLE_TO_FIXED(0); 0030 0031 XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig *config) 0032 : QObject(config) 0033 , m_config(config) 0034 , m_id(id) 0035 , m_type(KScreen::Output::Unknown) 0036 , m_crtc(nullptr) 0037 { 0038 init(); 0039 } 0040 0041 XRandROutput::~XRandROutput() 0042 { 0043 } 0044 0045 xcb_randr_output_t XRandROutput::id() const 0046 { 0047 return m_id; 0048 } 0049 0050 bool XRandROutput::isConnected() const 0051 { 0052 return m_connected == XCB_RANDR_CONNECTION_CONNECTED; 0053 } 0054 0055 bool XRandROutput::isEnabled() const 0056 { 0057 return m_crtc != nullptr && m_crtc->mode() != XCB_NONE; 0058 } 0059 0060 bool XRandROutput::isPrimary() const 0061 { 0062 return priority() == 1; 0063 } 0064 0065 uint32_t XRandROutput::priority() const 0066 { 0067 if (isConnected() && isEnabled()) { 0068 return outputPriorityFromProperty(); 0069 } else { 0070 return 0; 0071 } 0072 } 0073 0074 void XRandROutput::setPriority(XRandROutput::Priority newPriority) 0075 { 0076 if (priority() != newPriority) { 0077 setOutputPriorityToProperty(newPriority); 0078 } 0079 0080 // Always update the primary regardless of it having changed. If a primary gets unplugged and plugged back in we'd 0081 // otherwise end up with priorities not being in sync with xrandr primary because the effective priorities in the 0082 // atoms haven't changed; also see priority(). 0083 if (newPriority == 1) { 0084 setAsPrimary(); 0085 } 0086 } 0087 0088 QPoint XRandROutput::position() const 0089 { 0090 return m_crtc ? m_crtc->geometry().topLeft() : QPoint(); 0091 } 0092 0093 QSize XRandROutput::size() const 0094 { 0095 return m_crtc ? m_crtc->geometry().size() : QSize(); 0096 } 0097 0098 XRandRMode::Map XRandROutput::modes() const 0099 { 0100 return m_modes; 0101 } 0102 0103 QString XRandROutput::currentModeId() const 0104 { 0105 return m_crtc ? QString::number(m_crtc->mode()) : QString(); 0106 } 0107 0108 XRandRMode *XRandROutput::currentMode() const 0109 { 0110 if (!m_crtc) { 0111 return nullptr; 0112 } 0113 0114 unsigned int modeId = m_crtc->mode(); 0115 if (!m_modes.contains(modeId)) { 0116 return nullptr; 0117 } 0118 0119 return m_modes[modeId]; 0120 } 0121 0122 KScreen::Output::Rotation XRandROutput::rotation() const 0123 { 0124 return static_cast<KScreen::Output::Rotation>(m_crtc ? m_crtc->rotation() : XCB_RANDR_ROTATION_ROTATE_0); 0125 } 0126 0127 bool XRandROutput::isHorizontal() const 0128 { 0129 const auto rot = rotation(); 0130 return rot == KScreen::Output::Rotation::None || rot == KScreen::Output::Rotation::Inverted; 0131 } 0132 0133 QByteArray XRandROutput::edid() const 0134 { 0135 if (m_edid.isNull()) { 0136 m_edid = XRandR::outputEdid(m_id); 0137 } 0138 return m_edid; 0139 } 0140 0141 XRandRCrtc *XRandROutput::crtc() const 0142 { 0143 return m_crtc; 0144 } 0145 0146 void XRandROutput::update(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t conn) 0147 { 0148 qCDebug(KSCREEN_XRANDR) << "XRandROutput" << m_id << "update" 0149 << "\n" 0150 << "\tm_connected:" << m_connected << "\n" 0151 << "\tm_crtc" << m_crtc << "\n" 0152 << "\tCRTC:" << crtc << "\n" 0153 << "\tMODE:" << mode << "\n" 0154 << "\tConnection:" << conn; 0155 0156 // Connected or disconnected 0157 if (isConnected() != (conn == XCB_RANDR_CONNECTION_CONNECTED)) { 0158 if (conn == XCB_RANDR_CONNECTION_CONNECTED) { 0159 // New monitor has been connected, refresh everything 0160 init(); 0161 } else { 0162 // Disconnected 0163 m_connected = conn; 0164 m_clones.clear(); 0165 m_heightMm = 0; 0166 m_widthMm = 0; 0167 m_type = KScreen::Output::Unknown; 0168 qDeleteAll(m_modes); 0169 m_modes.clear(); 0170 m_preferredModes.clear(); 0171 m_edid.clear(); 0172 } 0173 } else if (conn == XCB_RANDR_CONNECTION_CONNECTED) { 0174 // the output changed in some way, let's update the internal 0175 // list of modes, as it may have changed 0176 XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); 0177 if (outputInfo) { 0178 updateModes(outputInfo); 0179 } 0180 0181 m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update"); 0182 m_edid.clear(); 0183 } 0184 0185 // A monitor has been enabled or disabled 0186 // We don't use isEnabled(), because it checks for crtc && crtc->mode(), however 0187 // crtc->mode may already be unset due to xcb_randr_crtc_tChangeNotify coming before 0188 // xcb_randr_output_tChangeNotify and reseting the CRTC mode 0189 0190 if ((m_crtc == nullptr) != (crtc == XCB_NONE)) { 0191 if (crtc == XCB_NONE && mode == XCB_NONE) { 0192 // Monitor has been disabled 0193 m_crtc->disconectOutput(m_id); 0194 m_crtc = nullptr; 0195 } else { 0196 m_crtc = m_config->crtc(crtc); 0197 m_crtc->connectOutput(m_id); 0198 } 0199 } 0200 } 0201 0202 static constexpr const char *KDE_SCREEN_INDEX = "_KDE_SCREEN_INDEX"; 0203 0204 XRandROutput::Priority XRandROutput::outputPriorityFromProperty() const 0205 { 0206 if (!isConnected()) { 0207 return 0; 0208 } 0209 0210 xcb_atom_t screen_index_atom = XCB::InternAtom(/* only_if_exists */ false, strlen(KDE_SCREEN_INDEX), KDE_SCREEN_INDEX)->atom; 0211 0212 auto cookie = xcb_randr_get_output_property(XCB::connection(), 0213 m_id, 0214 screen_index_atom, 0215 XCB_ATOM_INTEGER, 0216 /*offset*/ 0, 0217 /*length*/ 1, 0218 /*delete*/ false, 0219 /*pending*/ false); 0220 XCB::ScopedPointer<xcb_randr_get_output_property_reply_t> reply(xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr)); 0221 if (!reply) { 0222 return 0; 0223 } 0224 0225 if (!(reply->type == XCB_ATOM_INTEGER && reply->format == PRIORITY_FORMAT && reply->num_items == 1)) { 0226 return 0; 0227 } 0228 0229 const uint8_t *prop = xcb_randr_get_output_property_data(reply.data()); 0230 const Priority priority = *reinterpret_cast<const Priority *>(prop); 0231 return priority; 0232 } 0233 0234 void XRandROutput::setOutputPriorityToProperty(Priority priority) 0235 { 0236 if (!isConnected()) { 0237 return; 0238 } 0239 0240 const std::array<Priority, 1> data = {priority}; 0241 0242 xcb_atom_t screen_index_atom = XCB::InternAtom(/* only_if_exists */ false, strlen(KDE_SCREEN_INDEX), KDE_SCREEN_INDEX)->atom; 0243 0244 xcb_randr_change_output_property(XCB::connection(), // 0245 m_id, 0246 screen_index_atom, 0247 XCB_ATOM_INTEGER, 0248 PRIORITY_FORMAT, 0249 XCB_PROP_MODE_REPLACE, 0250 data.size(), 0251 data.data()); 0252 } 0253 0254 void XRandROutput::setAsPrimary() 0255 { 0256 if (isConnected() && isEnabled()) { 0257 xcb_randr_set_output_primary(XCB::connection(), XRandR::rootWindow(), m_id); 0258 } 0259 } 0260 0261 void XRandROutput::init() 0262 { 0263 XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); 0264 Q_ASSERT(outputInfo); 0265 if (!outputInfo) { 0266 return; 0267 } 0268 0269 m_name = QString::fromUtf8((const char *)xcb_randr_get_output_info_name(outputInfo.data()), outputInfo->name_len); 0270 m_type = fetchOutputType(m_id, m_name); 0271 m_icon = QString(); 0272 m_connected = (xcb_randr_connection_t)outputInfo->connection; 0273 0274 xcb_randr_output_t *clones = xcb_randr_get_output_info_clones(outputInfo.data()); 0275 for (int i = 0; i < outputInfo->num_clones; ++i) { 0276 m_clones.append(clones[i]); 0277 } 0278 0279 m_widthMm = outputInfo->mm_width; 0280 m_heightMm = outputInfo->mm_height; 0281 0282 m_crtc = m_config->crtc(outputInfo->crtc); 0283 if (m_crtc) { 0284 m_crtc->connectOutput(m_id); 0285 } 0286 m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update"); 0287 0288 updateModes(outputInfo); 0289 } 0290 0291 void XRandROutput::updateModes(const XCB::OutputInfo &outputInfo) 0292 { 0293 /* Init modes */ 0294 XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> screenResources(XRandR::screenResources()); 0295 0296 Q_ASSERT(screenResources); 0297 if (!screenResources) { 0298 return; 0299 } 0300 xcb_randr_mode_info_t *modes = xcb_randr_get_screen_resources_modes(screenResources.data()); 0301 xcb_randr_mode_t *outputModes = xcb_randr_get_output_info_modes(outputInfo.data()); 0302 0303 m_preferredModes.clear(); 0304 qDeleteAll(m_modes); 0305 m_modes.clear(); 0306 for (int i = 0; i < outputInfo->num_modes; ++i) { 0307 /* Resources->modes contains all possible modes, we are only interested 0308 * in those listed in outputInfo->modes. */ 0309 for (int j = 0; j < screenResources->num_modes; ++j) { 0310 if (modes[j].id != outputModes[i]) { 0311 continue; 0312 } 0313 0314 XRandRMode *mode = new XRandRMode(modes[j], this); 0315 m_modes.insert(mode->id(), mode); 0316 0317 if (i < outputInfo->num_preferred) { 0318 m_preferredModes.append(QString::number(mode->id())); 0319 } 0320 break; 0321 } 0322 } 0323 } 0324 0325 KScreen::Output::Type XRandROutput::fetchOutputType(xcb_randr_output_t outputId, const QString &name) 0326 { 0327 QString type = QString::fromUtf8(typeFromProperty(outputId)); 0328 if (type.isEmpty()) { 0329 type = name; 0330 } 0331 0332 return Utils::guessOutputType(type, name); 0333 } 0334 0335 QByteArray XRandROutput::typeFromProperty(xcb_randr_output_t outputId) 0336 { 0337 QByteArray type; 0338 0339 XCB::InternAtom atomType(true, 13, "ConnectorType"); 0340 if (!atomType) { 0341 return type; 0342 } 0343 0344 auto cookie = xcb_randr_get_output_property(XCB::connection(), outputId, atomType->atom, XCB_ATOM_ANY, 0, 100, false, false); 0345 XCB::ScopedPointer<xcb_randr_get_output_property_reply_t> reply(xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr)); 0346 if (!reply) { 0347 return type; 0348 } 0349 0350 if (!(reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->num_items == 1)) { 0351 return type; 0352 } 0353 0354 const uint8_t *prop = xcb_randr_get_output_property_data(reply.data()); 0355 XCB::AtomName atomName(*reinterpret_cast<const xcb_atom_t *>(prop)); 0356 if (!atomName) { 0357 return type; 0358 } 0359 0360 return QByteArray(xcb_get_atom_name_name(atomName), xcb_get_atom_name_name_length(atomName)); 0361 } 0362 0363 bool isScaling(const xcb_render_transform_t &tr) 0364 { 0365 return tr.matrix11 != fZero && tr.matrix12 == fZero && tr.matrix13 == fZero && tr.matrix21 == fZero && tr.matrix22 != fZero && tr.matrix23 == fZero 0366 && tr.matrix31 == fZero && tr.matrix32 == fZero && tr.matrix33 == fOne; 0367 } 0368 0369 xcb_render_transform_t zeroTransform() 0370 { 0371 return {DOUBLE_TO_FIXED(0), 0372 DOUBLE_TO_FIXED(0), 0373 DOUBLE_TO_FIXED(0), 0374 DOUBLE_TO_FIXED(0), 0375 DOUBLE_TO_FIXED(0), 0376 DOUBLE_TO_FIXED(0), 0377 DOUBLE_TO_FIXED(0), 0378 DOUBLE_TO_FIXED(0), 0379 DOUBLE_TO_FIXED(0)}; 0380 } 0381 0382 xcb_render_transform_t unityTransform() 0383 { 0384 return {DOUBLE_TO_FIXED(1), 0385 DOUBLE_TO_FIXED(0), 0386 DOUBLE_TO_FIXED(0), 0387 DOUBLE_TO_FIXED(0), 0388 DOUBLE_TO_FIXED(1), 0389 DOUBLE_TO_FIXED(0), 0390 DOUBLE_TO_FIXED(0), 0391 DOUBLE_TO_FIXED(0), 0392 DOUBLE_TO_FIXED(1)}; 0393 } 0394 0395 xcb_render_transform_t XRandROutput::currentTransform() const 0396 { 0397 auto cookie = xcb_randr_get_crtc_transform(XCB::connection(), m_crtc->crtc()); 0398 xcb_generic_error_t *error = nullptr; 0399 auto *reply = xcb_randr_get_crtc_transform_reply(XCB::connection(), cookie, &error); 0400 if (error) { 0401 return zeroTransform(); 0402 } 0403 0404 const xcb_render_transform_t transform = reply->pending_transform; 0405 free(reply); 0406 return transform; 0407 } 0408 0409 QSizeF XRandROutput::logicalSize() const 0410 { 0411 const QSize modeSize = size(); 0412 if (!modeSize.isValid()) { 0413 return QSize(); 0414 } 0415 0416 const xcb_render_transform_t transform = currentTransform(); 0417 const qreal width = FIXED_TO_DOUBLE(transform.matrix11) * modeSize.width(); 0418 const qreal height = FIXED_TO_DOUBLE(transform.matrix22) * modeSize.height(); 0419 0420 return QSizeF(width, height); 0421 } 0422 0423 void XRandROutput::updateLogicalSize(const KScreen::OutputPtr &output, XRandRCrtc *crtc) 0424 { 0425 if (!crtc) { 0426 // TODO: This is a workaround for now when updateLogicalSize is called on enabling the 0427 // output. At this point m_crtc is not yet set. Change the order in the future so 0428 // that the additional argument is not necessary anymore. 0429 crtc = m_crtc; 0430 } 0431 const QSizeF logicalSize = output->explicitLogicalSize(); 0432 xcb_render_transform_t transform = unityTransform(); 0433 0434 KScreen::ModePtr mode = output->currentMode() ? output->currentMode() : output->preferredMode(); 0435 if (mode && logicalSize.isValid()) { 0436 QSize modeSize = mode->size(); 0437 if (!output->isHorizontal()) { 0438 modeSize.transpose(); 0439 } 0440 0441 const qreal widthFactor = logicalSize.width() / (qreal)modeSize.width(); 0442 const qreal heightFactor = logicalSize.height() / (qreal)modeSize.height(); 0443 transform.matrix11 = DOUBLE_TO_FIXED(widthFactor); 0444 transform.matrix22 = DOUBLE_TO_FIXED(heightFactor); 0445 } 0446 0447 QByteArray filterName(isScaling(transform) ? "bilinear" : "nearest"); 0448 0449 auto cookie = xcb_randr_set_crtc_transform_checked(XCB::connection(), crtc->crtc(), transform, filterName.size(), filterName.data(), 0, nullptr); 0450 xcb_generic_error_t *error = xcb_request_check(XCB::connection(), cookie); 0451 if (error) { 0452 qCDebug(KSCREEN_XRANDR) << "Error on logical size transformation!"; 0453 free(error); 0454 } 0455 } 0456 0457 KScreen::OutputPtr XRandROutput::toKScreenOutput() const 0458 { 0459 KScreen::OutputPtr kscreenOutput(new KScreen::Output); 0460 0461 const bool signalsBlocked = kscreenOutput->signalsBlocked(); 0462 kscreenOutput->blockSignals(true); 0463 kscreenOutput->setId(m_id); 0464 kscreenOutput->setType(m_type); 0465 kscreenOutput->setSizeMm(QSize(m_widthMm, m_heightMm)); 0466 kscreenOutput->setName(m_name); 0467 kscreenOutput->setIcon(m_icon); 0468 kscreenOutput->setPriority(priority()); 0469 0470 // See https://bugzilla.redhat.com/show_bug.cgi?id=1290586 0471 // QXL will be creating a new mode we need to jump to every time the display is resized 0472 kscreenOutput->setFollowPreferredMode(m_hotplugModeUpdate && m_crtc && m_crtc->isChangedFromOutside()); 0473 0474 kscreenOutput->setConnected(isConnected()); 0475 if (isConnected()) { 0476 KScreen::ModeList kscreenModes; 0477 for (auto iter = m_modes.constBegin(), end = m_modes.constEnd(); iter != end; ++iter) { 0478 XRandRMode *mode = iter.value(); 0479 kscreenModes.insert(QString::number(iter.key()), mode->toKScreenMode()); 0480 } 0481 kscreenOutput->setModes(kscreenModes); 0482 kscreenOutput->setPreferredModes(m_preferredModes); 0483 kscreenOutput->setClones([](const QList<xcb_randr_output_t> &clones) { 0484 QList<int> kclones; 0485 kclones.reserve(clones.size()); 0486 for (xcb_randr_output_t o : clones) { 0487 kclones.append(static_cast<int>(o)); 0488 } 0489 return kclones; 0490 }(m_clones)); 0491 kscreenOutput->setEnabled(isEnabled()); 0492 if (isEnabled()) { 0493 kscreenOutput->setSize(size()); 0494 kscreenOutput->setPos(position()); 0495 kscreenOutput->setRotation(rotation()); 0496 kscreenOutput->setCurrentModeId(currentModeId()); 0497 } 0498 // TODO: set logical size? 0499 } 0500 0501 kscreenOutput->blockSignals(signalsBlocked); 0502 return kscreenOutput; 0503 }