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