diff --git a/backends/xrandr/xrandr.cpp b/backends/xrandr/xrandr.cpp index 52b4116..97cdff5 100644 --- a/backends/xrandr/xrandr.cpp +++ b/backends/xrandr/xrandr.cpp @@ -1,312 +1,311 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 by Daniel Vrátil * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "xrandr.h" #include "xrandrconfig.h" #include "xrandrscreen.h" #include "../xcbwrapper.h" #include "../xcbeventlistener.h" #include "config.h" #include "output.h" #include "edid.h" #include #include #include #include #include #include #include xcb_screen_t* XRandR::s_screen = nullptr; xcb_window_t XRandR::s_rootWindow = 0; XRandRConfig* XRandR::s_internalConfig = nullptr; int XRandR::s_randrBase = 0; int XRandR::s_randrError = 0; bool XRandR::s_monitorInitialized = false; bool XRandR::s_has_1_3 = false; bool XRandR::s_xorgCacheInitialized = false; using namespace KScreen; Q_LOGGING_CATEGORY(KSCREEN_XRANDR, "kscreen.xrandr") XRandR::XRandR() : KScreen::AbstractBackend() , m_x11Helper(nullptr) , m_isValid(false) , m_configChangeCompressor(nullptr) { qRegisterMetaType("xcb_randr_output_t"); qRegisterMetaType("xcb_randr_crtc_t"); qRegisterMetaType("xcb_randr_mode_t"); qRegisterMetaType("xcb_randr_connection_t"); qRegisterMetaType("xcb_randr_rotation_t"); // Use our own connection to make sure that we won't mess up Qt's connection // if something goes wrong on our side. xcb_generic_error_t *error = nullptr; xcb_randr_query_version_reply_t* version; XCB::connection(); version = xcb_randr_query_version_reply(XCB::connection(), xcb_randr_query_version(XCB::connection(), XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &error); if (!version || error) { XCB::closeConnection(); free(error); return; } if ((version->major_version > 1) || ((version->major_version == 1) && (version->minor_version >= 2))) { m_isValid = true; } else { XCB::closeConnection(); qCWarning(KSCREEN_XRANDR) << "XRandR extension not available or unsupported version"; return; } if (s_screen == nullptr) { s_screen = XCB::screenOfDisplay(XCB::connection(), QX11Info::appScreen()); s_rootWindow = s_screen->root; xcb_prefetch_extension_data(XCB::connection(), &xcb_randr_id); auto reply = xcb_get_extension_data(XCB::connection(), &xcb_randr_id); s_randrBase = reply->first_event; s_randrError = reply->first_error; } XRandR::s_has_1_3 = (version->major_version > 1 || (version->major_version == 1 && version->minor_version >= 3)); if (s_internalConfig == nullptr) { s_internalConfig = new XRandRConfig(); } if (!s_monitorInitialized) { m_x11Helper = new XCBEventListener(); connect(m_x11Helper, &XCBEventListener::outputChanged, this, &XRandR::outputChanged, Qt::QueuedConnection); connect(m_x11Helper, &XCBEventListener::crtcChanged, this, &XRandR::crtcChanged, Qt::QueuedConnection); connect(m_x11Helper, &XCBEventListener::screenChanged, this, &XRandR::screenChanged, Qt::QueuedConnection); m_configChangeCompressor = new QTimer(this); m_configChangeCompressor->setSingleShot(true); m_configChangeCompressor->setInterval(500); connect(m_configChangeCompressor, &QTimer::timeout, [&]() { qCDebug(KSCREEN_XRANDR) << "Emitting configChanged()"; Q_EMIT configChanged(config()); }); s_monitorInitialized = true; } } XRandR::~XRandR() { delete m_x11Helper; } QString XRandR::name() const { return QStringLiteral("XRandR"); } QString XRandR::serviceName() const { return QStringLiteral("org.kde.KScreen.Backend.XRandR"); } void XRandR::outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection) { XRandROutput *xOutput = s_internalConfig->output(output); XCB::PrimaryOutput primary(XRandR::rootWindow()); if (!xOutput) { s_internalConfig->addNewOutput(output); } else { switch (crtc == XCB_NONE && mode == XCB_NONE && connection == XCB_RANDR_CONNECTION_DISCONNECTED) { case true: { XCB::OutputInfo info(output, XCB_TIME_CURRENT_TIME); if (info.isNull()) { s_internalConfig->removeOutput(output); qCDebug(KSCREEN_XRANDR) << "Output" << output << " removed"; break; } // info is valid: fall-through Q_FALLTHROUGH(); } case false: { xOutput->update(crtc, mode, connection, (primary->output == output)); qCDebug(KSCREEN_XRANDR) << "Output" << xOutput->id() << ": connected =" << xOutput->isConnected() << ", enabled =" << xOutput->isEnabled(); break; } } // switch } m_configChangeCompressor->start(); } void XRandR::crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect& geom) { XRandRCrtc *xCrtc = s_internalConfig->crtc(crtc); if (!xCrtc) { s_internalConfig->addNewCrtc(crtc); } else { xCrtc->update(mode, rotation, geom); } m_configChangeCompressor->start(); } void XRandR::screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm) { Q_UNUSED(sizeMm); QSize newSizePx = sizePx; if (rotation == XCB_RANDR_ROTATION_ROTATE_90 || rotation == XCB_RANDR_ROTATION_ROTATE_270) { newSizePx.transpose(); } XRandRScreen *xScreen = s_internalConfig->screen(); Q_ASSERT(xScreen); xScreen->update(newSizePx); m_configChangeCompressor->start(); } ConfigPtr XRandR::config() const { return s_internalConfig->toKScreenConfig(); } void XRandR::setConfig(const ConfigPtr &config) { if (!config) { return; } qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig"; s_internalConfig->applyKScreenConfig(config); qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig done!"; } QByteArray XRandR::edid(int outputId) const { const XRandROutput *output = s_internalConfig->output(outputId); if (!output) { return QByteArray(); } return output->edid(); } bool XRandR::isValid() const { return m_isValid; } quint8* XRandR::getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t &len) { quint8 *result; auto cookie = xcb_randr_get_output_property(XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 100, false, false); auto reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr); if (reply->type == XCB_ATOM_INTEGER && reply->format == 8) { result = new quint8[reply->num_items]; memcpy(result, xcb_randr_get_output_property_data(reply), reply->num_items); len = reply->num_items; } else { result = nullptr; } free(reply); return result; } -quint8 *XRandR::outputEdid(xcb_randr_output_t outputId, size_t &len) +QByteArray XRandR::outputEdid(xcb_randr_output_t outputId) { + size_t len = 0; quint8 *result; auto edid_atom = XCB::InternAtom(false, 4, "EDID")->atom; result = XRandR::getXProperty(outputId, edid_atom, len); if (result == nullptr) { auto edid_atom = XCB::InternAtom(false, 9, "EDID_DATA")->atom; result = XRandR::getXProperty(outputId, edid_atom, len); } if (result == nullptr) { auto edid_atom = XCB::InternAtom(false, 25, "XFree86_DDC_EDID1_RAWDATA")->atom; result = XRandR::getXProperty(outputId, edid_atom, len); } - if (result) { + QByteArray edid; + if (result != nullptr) { if (len % 128 == 0) { - return result; - } else { - len = 0; - delete[] result; + edid = QByteArray((char *) result, len); } + delete[] result; } - - return nullptr; + return edid; } xcb_randr_get_screen_resources_reply_t* XRandR::screenResources() { if (XRandR::s_has_1_3) { if (XRandR::s_xorgCacheInitialized) { // HACK: This abuses the fact that xcb_randr_get_screen_resources_reply_t // and xcb_randr_get_screen_resources_current_reply_t are the same return reinterpret_cast( xcb_randr_get_screen_resources_current_reply(XCB::connection(), xcb_randr_get_screen_resources_current(XCB::connection(), XRandR::rootWindow()), nullptr)); } else { /* XRRGetScreenResourcesCurrent is faster then XRRGetScreenResources * because it returns cached values. However the cached values are not * available until someone calls XRRGetScreenResources first. In case * we happen to be the first ones, we need to fill the cache first. */ XRandR::s_xorgCacheInitialized = true; } } return xcb_randr_get_screen_resources_reply(XCB::connection(), xcb_randr_get_screen_resources(XCB::connection(), XRandR::rootWindow()), nullptr); } xcb_window_t XRandR::rootWindow() { return s_rootWindow; } xcb_screen_t* XRandR::screen() { return s_screen; } diff --git a/backends/xrandr/xrandr.h b/backends/xrandr/xrandr.h index d388246..1102834 100644 --- a/backends/xrandr/xrandr.h +++ b/backends/xrandr/xrandr.h @@ -1,94 +1,94 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 by Daniel Vrátil * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #ifndef XRANDR_BACKEND_H #define XRANDR_BACKEND_H #include "abstractbackend.h" #include #include #include "../xcbwrapper.h" class QRect; class QTimer; class XCBEventListener; class XRandRConfig; namespace KScreen { class Output; } class XRandR : public KScreen::AbstractBackend { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.xrandr") public: explicit XRandR(); ~XRandR() override; QString name() const override; QString serviceName() const override; KScreen::ConfigPtr config() const override; void setConfig(const KScreen::ConfigPtr &config) override; bool isValid() const override; QByteArray edid(int outputId) const override; - static quint8 *outputEdid(xcb_randr_output_t outputId, size_t &len); + static QByteArray outputEdid(xcb_randr_output_t outputId); static xcb_randr_get_screen_resources_reply_t* screenResources(); static xcb_screen_t* screen(); static xcb_window_t rootWindow(); private Q_SLOTS: void outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection); void crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom); void screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm); private: static quint8* getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t &len); static xcb_screen_t *s_screen; static xcb_window_t s_rootWindow; static XRandRConfig *s_internalConfig; static int s_randrBase; static int s_randrError; static bool s_monitorInitialized; static bool s_has_1_3; static bool s_xorgCacheInitialized; XCBEventListener *m_x11Helper; bool m_isValid; QTimer *m_configChangeCompressor; }; Q_DECLARE_LOGGING_CATEGORY(KSCREEN_XRANDR) #endif //XRandR_BACKEND_H diff --git a/backends/xrandr/xrandroutput.cpp b/backends/xrandr/xrandroutput.cpp index 3e1c1e4..0048296 100644 --- a/backends/xrandr/xrandroutput.cpp +++ b/backends/xrandr/xrandroutput.cpp @@ -1,345 +1,337 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 by Daniel Vrátil * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "xrandroutput.h" #include "xrandrmode.h" #include "xrandrconfig.h" #include "xrandr.h" #include "output.h" #include "config.h" #include "../utils.h" #include Q_DECLARE_METATYPE(QList) XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig *config) : QObject(config) , m_config(config) , m_id(id) , m_type(KScreen::Output::Unknown) , m_primary(false) , m_crtc(nullptr) { init(); } XRandROutput::~XRandROutput() { } xcb_randr_output_t XRandROutput::id() const { return m_id; } bool XRandROutput::isConnected() const { return m_connected == XCB_RANDR_CONNECTION_CONNECTED; } bool XRandROutput::isEnabled() const { return m_crtc != nullptr && m_crtc->mode() != XCB_NONE; } bool XRandROutput::isPrimary() const { return m_primary; } QPoint XRandROutput::position() const { return m_crtc ? m_crtc->geometry().topLeft() : QPoint(); } QSize XRandROutput::size() const { return m_crtc ? m_crtc->geometry().size() : QSize(); } XRandRMode::Map XRandROutput::modes() const { return m_modes; } QString XRandROutput::currentModeId() const { return m_crtc ? QString::number(m_crtc->mode()) : QString(); } XRandRMode* XRandROutput::currentMode() const { if (!m_crtc) { return nullptr; } unsigned int modeId = m_crtc->mode(); if (!m_modes.contains(modeId)) { return nullptr; } return m_modes[modeId]; } KScreen::Output::Rotation XRandROutput::rotation() const { return static_cast(m_crtc ? m_crtc->rotation() : XCB_RANDR_ROTATION_ROTATE_0); } QByteArray XRandROutput::edid() const { if (m_edid.isNull()) { - size_t len; - quint8 *data = XRandR::outputEdid(m_id, len); - if (data) { - m_edid = QByteArray((char *) data, len); - delete[] data; - } else { - m_edid = QByteArray(); - } + m_edid = XRandR::outputEdid(m_id); } - return m_edid; } XRandRCrtc* XRandROutput::crtc() const { return m_crtc; } void XRandROutput::update() { init(); } void XRandROutput::update(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t conn, bool primary) { qCDebug(KSCREEN_XRANDR) << "XRandROutput" << m_id << "update"; qCDebug(KSCREEN_XRANDR) << "\tm_connected:" << m_connected; qCDebug(KSCREEN_XRANDR) << "\tm_crtc" << m_crtc; qCDebug(KSCREEN_XRANDR) << "\tCRTC:" << crtc; qCDebug(KSCREEN_XRANDR) << "\tMODE:" << mode; qCDebug(KSCREEN_XRANDR) << "\tConnection:" << conn; qCDebug(KSCREEN_XRANDR) << "\tPrimary:" << primary; // Connected or disconnected if (isConnected() != (conn == XCB_RANDR_CONNECTION_CONNECTED)) { if (conn == XCB_RANDR_CONNECTION_CONNECTED) { // New monitor has been connected, refresh everything init(); } else { // Disconnected m_connected = conn; m_clones.clear(); m_heightMm = 0; m_widthMm = 0; m_type = KScreen::Output::Unknown; qDeleteAll(m_modes); m_modes.clear(); m_preferredModes.clear(); m_edid.clear(); } } else if (conn == XCB_RANDR_CONNECTION_CONNECTED) { // the output changed in some way, let's update the internal // list of modes, as it may have changed XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); if (outputInfo) { updateModes(outputInfo); } } // A monitor has been enabled or disabled // We don't use isEnabled(), because it checks for crtc && crtc->mode(), however // crtc->mode may already be unset due to xcb_randr_crtc_tChangeNotify coming before // xcb_randr_output_tChangeNotify and reseting the CRTC mode if ((m_crtc == nullptr) != (crtc == XCB_NONE)) { if (crtc == XCB_NONE && mode == XCB_NONE) { // Monitor has been disabled m_crtc->disconectOutput(m_id); m_crtc = nullptr; } else { m_crtc = m_config->crtc(crtc); m_crtc->connectOutput(m_id); } } // Primary has changed m_primary = primary; } void XRandROutput::setIsPrimary(bool primary) { m_primary = primary; } void XRandROutput::init() { XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); Q_ASSERT(outputInfo); if (!outputInfo) { return; } XCB::PrimaryOutput primary(XRandR::rootWindow()); m_name = QString::fromUtf8((const char *) xcb_randr_get_output_info_name(outputInfo.data()), outputInfo->name_len); m_type = fetchOutputType(m_id, m_name); m_icon = QString(); m_connected = (xcb_randr_connection_t) outputInfo->connection; m_primary = (primary->output == m_id); xcb_randr_output_t *clones = xcb_randr_get_output_info_clones(outputInfo.data()); for (int i = 0; i < outputInfo->num_clones; ++i) { m_clones.append(clones[i]); } m_widthMm = outputInfo->mm_width; m_heightMm = outputInfo->mm_height; m_crtc = m_config->crtc(outputInfo->crtc); if (m_crtc) { m_crtc->connectOutput(m_id); } updateModes(outputInfo); } void XRandROutput::updateModes(const XCB::OutputInfo &outputInfo) { /* Init modes */ XCB::ScopedPointer screenResources(XRandR::screenResources()); Q_ASSERT(screenResources); if (!screenResources) { return; } xcb_randr_mode_info_t *modes = xcb_randr_get_screen_resources_modes(screenResources.data()); xcb_randr_mode_t *outputModes = xcb_randr_get_output_info_modes(outputInfo.data()); m_preferredModes.clear(); qDeleteAll(m_modes); m_modes.clear(); for (int i = 0; i < outputInfo->num_modes; ++i) { /* Resources->modes contains all possible modes, we are only interested * in those listed in outputInfo->modes. */ for (int j = 0; j < screenResources->num_modes; ++j) { if (modes[j].id != outputModes[i]) { continue; } XRandRMode *mode = new XRandRMode(modes[j], this); m_modes.insert(mode->id(), mode); if (i < outputInfo->num_preferred) { m_preferredModes.append(QString::number(mode->id())); } break; } } } KScreen::Output::Type XRandROutput::fetchOutputType(xcb_randr_output_t outputId, const QString &name) { QByteArray type = typeFromProperty(outputId); if (type.isEmpty()) { type = name.toLocal8Bit(); } return Utils::guessOutputType(type, name); } QByteArray XRandROutput::typeFromProperty(xcb_randr_output_t outputId) { QByteArray type; XCB::InternAtom atomType(true, 13, "ConnectorType"); if (!atomType) { return type; } char *connectorType; auto cookie = xcb_randr_get_output_property(XCB::connection(), outputId, atomType->atom, XCB_ATOM_ANY, 0, 100, false, false); XCB::ScopedPointer reply(xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr)); if (!reply) { return type; } if (!(reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->num_items == 1)) { return type; } const uint8_t *prop = xcb_randr_get_output_property_data(reply.data()); XCB::AtomName atomName(*reinterpret_cast(prop)); if (!atomName) { return type; } connectorType = xcb_get_atom_name_name(atomName); if (!connectorType) { return type; } type = connectorType; return type; } KScreen::OutputPtr XRandROutput::toKScreenOutput() const { KScreen::OutputPtr kscreenOutput(new KScreen::Output); const bool signalsBlocked = kscreenOutput->signalsBlocked(); kscreenOutput->blockSignals(true); kscreenOutput->setId(m_id); kscreenOutput->setType(m_type); kscreenOutput->setSizeMm(QSize(m_widthMm, m_heightMm)); kscreenOutput->setName(m_name); kscreenOutput->setIcon(m_icon); kscreenOutput->setConnected(isConnected()); if (isConnected()) { KScreen::ModeList kscreenModes; for (auto iter = m_modes.constBegin(), end = m_modes.constEnd(); iter != end; ++iter) { XRandRMode *mode = iter.value(); kscreenModes.insert(QString::number(iter.key()), mode->toKScreenMode()); } kscreenOutput->setModes(kscreenModes); kscreenOutput->setPreferredModes(m_preferredModes); kscreenOutput->setPrimary(m_primary); kscreenOutput->setClones([](const QList &clones) { QList kclones; kclones.reserve(clones.size()); for (xcb_randr_output_t o : clones) { kclones.append(static_cast(o)); } return kclones; }(m_clones)); kscreenOutput->setEnabled(isEnabled()); if (isEnabled()) { kscreenOutput->setSize(size()); kscreenOutput->setPos(position()); kscreenOutput->setRotation(rotation()); kscreenOutput->setCurrentModeId(currentModeId()); } } kscreenOutput->blockSignals(signalsBlocked); return kscreenOutput; }