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 }