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"