diff --git a/backends/xrandr/xrandrconfig.h b/backends/xrandr/xrandrconfig.h --- a/backends/xrandr/xrandrconfig.h +++ b/backends/xrandr/xrandrconfig.h @@ -60,6 +60,9 @@ bool disableOutput(const KScreen::OutputPtr &output) const; bool enableOutput(const KScreen::OutputPtr &output) const; bool changeOutput(const KScreen::OutputPtr &output) const; + bool replicateOutput(const KScreen::OutputPtr &output) const; + + bool sendConfig(const KScreen::OutputPtr &kscreenOutput, XRandRCrtc *crtc) const; /** * We need to print stuff to discover the damn bug diff --git a/backends/xrandr/xrandrconfig.cpp b/backends/xrandr/xrandrconfig.cpp --- a/backends/xrandr/xrandrconfig.cpp +++ b/backends/xrandr/xrandrconfig.cpp @@ -108,7 +108,8 @@ { KScreen::ConfigPtr config(new KScreen::Config); - auto features = Config::Feature::Writable | Config::Feature::PrimaryDisplay; + const Config::Features features = Config::Feature::Writable | Config::Feature::PrimaryDisplay | + Config::Feature::OutputReplication; config->setSupportedFeatures(features); KScreen::OutputList kscreenOutputs; @@ -154,7 +155,7 @@ } } - KScreen::OutputList toDisable, toEnable, toChange; + KScreen::OutputList toDisable, toEnable, toChange, toReplicate; for (const KScreen::OutputPtr &kscreenOutput : kscreenOutputs) { xcb_randr_output_t outputId = kscreenOutput->id(); @@ -179,6 +180,16 @@ ++neededCrtcs; + // Update replication when it is supposed to be a replica or changes not to be anymore. + if (kscreenOutput->replicationSource() || currentOutput->replicationSource()) { + if (int sourceId = kscreenOutput->replicationSource()) { + kscreenOutput->setPos(config->output(sourceId)->pos()); + } + if (!toReplicate.contains(outputId)) { + toReplicate.insert(outputId, kscreenOutput); + } + } + if (kscreenOutput->currentModeId() != currentOutput->currentModeId()) { if (!toChange.contains(outputId)) { toChange.insert(outputId, kscreenOutput); @@ -280,7 +291,7 @@ //If there is nothing to do, not even bother if (oldPrimaryOutput == primaryOutput && toDisable.isEmpty() && - toEnable.isEmpty() && toChange.isEmpty()) { + toEnable.isEmpty() && toChange.isEmpty() && toReplicate.isEmpty()) { if (newScreenSize != currentScreenSize) { setScreenSize(newScreenSize); } @@ -302,21 +313,26 @@ /* If we disabled the output before changing it and XRandR failed * to re-enable it, then update screen size too */ if (toDisable.contains(output->id())) { - //output->setEnabled(false); + output->setEnabled(false); qCDebug(KSCREEN_XRANDR) << "Output failed to change: " << output->name(); forceScreenSizeUpdate = true; } } } for (const KScreen::OutputPtr &output : toEnable) { if (!enableOutput(output)) { - //output->setEnabled(false); qCDebug(KSCREEN_XRANDR) << "Output failed to be Enabled: " << output->name(); forceScreenSizeUpdate = true; } } + for (KScreen::OutputPtr &output : toReplicate) { + if (replicateOutput(output)) { + forceScreenSizeUpdate = true; + } + } + if (oldPrimaryOutput != primaryOutput) { setPrimaryOutput(primaryOutput); } @@ -527,8 +543,6 @@ bool XRandRConfig::enableOutput(const OutputPtr &kscreenOutput) const { - xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; - XRandRCrtc *freeCrtc = nullptr; qCDebug(KSCREEN_XRANDR) << m_crtcs; @@ -564,38 +578,55 @@ << "Preferred:" << kscreenOutput->preferredModeId() << "\n" << "\tRotation:" << kscreenOutput->rotation(); - auto cookie = xcb_randr_set_crtc_config(XCB::connection(), freeCrtc->crtc(), - XCB_CURRENT_TIME, XCB_CURRENT_TIME, - kscreenOutput->pos().rx(), kscreenOutput->pos().ry(), - modeId, - kscreenOutput->rotation(), - 1, outputs); + if (!sendConfig(kscreenOutput, freeCrtc)) { + return false; + } - XCB::ScopedPointer - reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr)); + output(kscreenOutput->id())->update(freeCrtc->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, + kscreenOutput->isPrimary()); + return true; +} - if (!reply) { - qCDebug(KSCREEN_XRANDR) << "Result: unknown (error)"; - return false; +bool XRandRConfig::changeOutput(const KScreen::OutputPtr &kscreenOutput) const +{ + XRandROutput *xOutput = output(kscreenOutput->id()); + Q_ASSERT(xOutput); + + if (!xOutput->crtc()) { + qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id() + << "has no CRTC, falling back to enableOutput()"; + return enableOutput(kscreenOutput); } - qCDebug(KSCREEN_XRANDR) << "\tResult:" << reply->status; - if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) { - XRandROutput *xOutput = output(kscreenOutput->id()); - xOutput->update(freeCrtc->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, - kscreenOutput->isPrimary()); + int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : + kscreenOutput->preferredModeId().toInt(); + + qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (change output)" << "\n" + << "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name() + << ")" << "\n" + << "\tCRTC:" << xOutput->crtc()->crtc() << "\n" + << "\tPos:" << kscreenOutput->pos() << "\n" + << "\tMode:" << modeId << kscreenOutput->currentMode() << "\n" + << "\tRotation:" << kscreenOutput->rotation(); + + if (!sendConfig(kscreenOutput, xOutput->crtc())) { + return false; } - return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); + + xOutput->update(xOutput->crtc()->crtc(), modeId, + XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); + return true; } -bool XRandRConfig::changeOutput(const OutputPtr &kscreenOutput) const +bool XRandRConfig::replicateOutput(const KScreen::OutputPtr &kscreenOutput) const { XRandROutput *xOutput = output(kscreenOutput->id()); Q_ASSERT(xOutput); + if (!xOutput->crtc()) { qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id() << "has no CRTC, falling back to enableOutput()"; - return enableOutput(kscreenOutput); + enableOutput(kscreenOutput); } int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : @@ -609,27 +640,37 @@ << "\tMode:" << modeId << kscreenOutput->currentMode() << "\n" << "\tRotation:" << kscreenOutput->rotation(); + if (!xOutput->setReplicationSource(kscreenOutput->replicationSource())) { + return false; + } + if (!sendConfig(kscreenOutput, xOutput->crtc())) { + return false; + } + + xOutput->update(xOutput->crtc()->crtc(), modeId, + XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); + return true; +} + +bool XRandRConfig::sendConfig(const KScreen::OutputPtr &kscreenOutput, XRandRCrtc *crtc) const +{ xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; + const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : + kscreenOutput->preferredModeId().toInt(); - auto cookie = xcb_randr_set_crtc_config(XCB::connection(), xOutput->crtc()->crtc(), - XCB_CURRENT_TIME, XCB_CURRENT_TIME, - kscreenOutput->pos().rx(), kscreenOutput->pos().ry(), - modeId, - kscreenOutput->rotation(), - 1, outputs); + auto cookie = xcb_randr_set_crtc_config(XCB::connection(), crtc->crtc(), + XCB_CURRENT_TIME, XCB_CURRENT_TIME, + kscreenOutput->pos().rx(), kscreenOutput->pos().ry(), + modeId, + kscreenOutput->rotation(), + 1, outputs); XCB::ScopedPointer reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr)); - if (!reply) { qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)"; return false; } qCDebug(KSCREEN_XRANDR) << "\tResult: " << reply->status; - - if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) { - xOutput->update(xOutput->crtc()->crtc(), modeId, - XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); - } return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); } diff --git a/backends/xrandr/xrandroutput.h b/backends/xrandr/xrandroutput.h --- a/backends/xrandr/xrandroutput.h +++ b/backends/xrandr/xrandroutput.h @@ -61,6 +61,7 @@ QPoint position() const; QSize size() const; + QSizeF scaledSize(xcb_render_transform_t transform) const; QString currentModeId() const; XRandRMode::Map modes() const; @@ -74,13 +75,30 @@ KScreen::OutputPtr toKScreenOutput() const; + bool updateReplication(); + bool setReplicationSource(xcb_randr_output_t source); + + xcb_randr_output_t replicationSource() const; + + xcb_render_transform_t currentTransform() const; + private: void init(); void updateModes(const XCB::OutputInfo &outputInfo); static KScreen::Output::Type fetchOutputType(xcb_randr_output_t outputId, const QString &name); static QByteArray typeFromProperty(xcb_randr_output_t outputId); + xcb_render_transform_t getReplicationTransform(XRandROutput *source); + + /** + * This makes an educated guess based on position, size and scale if @param output is a + * replication source for this output. + * + * @return true if this output can be seen as a replica of @param output + */ + bool isReplicaOf(XRandROutput *output, xcb_render_transform_t ownTransform) const; + XRandRConfig *m_config; xcb_randr_output_t m_id; QString m_name; @@ -95,6 +113,7 @@ QStringList m_preferredModes; QList m_clones; + xcb_randr_output_t m_replicationSource; unsigned int m_widthMm; unsigned int m_heightMm; diff --git a/backends/xrandr/xrandroutput.cpp b/backends/xrandr/xrandroutput.cpp --- a/backends/xrandr/xrandroutput.cpp +++ b/backends/xrandr/xrandroutput.cpp @@ -24,14 +24,23 @@ #include "xrandrmode.h" #include "../utils.h" +#include + Q_DECLARE_METATYPE(QList) +#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) +#define FIXED_TO_DOUBLE(f) ((double) ((f) / 65536.0)) + +xcb_render_fixed_t fOne = DOUBLE_TO_FIXED(1); +xcb_render_fixed_t fZero = DOUBLE_TO_FIXED(0); + XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig *config) : QObject(config) , m_config(config) , m_id(id) , m_primary(false) , m_type(KScreen::Output::Unknown) + , m_replicationSource(XCB_NONE) , m_crtc(nullptr) { init(); @@ -101,6 +110,12 @@ XCB_RANDR_ROTATION_ROTATE_0); } +bool XRandROutput::isHorizontal() const +{ + const auto rot = rotation(); + return rot == KScreen::Output::Rotation::None || rot == KScreen::Output::Rotation::Inverted; +} + QByteArray XRandROutput::edid() const { if (m_edid.isNull()) { @@ -114,6 +129,11 @@ return m_crtc; } +xcb_randr_output_t XRandROutput::replicationSource() const +{ + return m_replicationSource; +} + void XRandROutput::update() { init(); @@ -300,6 +320,158 @@ return type; } +bool isScaling(const xcb_render_transform_t &tr) +{ + return tr.matrix11 != fZero && tr.matrix12 == fZero && tr.matrix13 == fZero && + tr.matrix21 == fZero && tr.matrix22 != fZero && tr.matrix23 == fZero && + tr.matrix31 == fZero && tr.matrix32 == fZero && tr.matrix33 == fOne; +} + +xcb_render_transform_t zeroMatrix() +{ + return { DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0) }; +} + +xcb_render_transform_t XRandROutput::currentTransform() const +{ + auto cookie = xcb_randr_get_crtc_transform(XCB::connection(), m_crtc->crtc()); + xcb_generic_error_t *error = nullptr; + auto *reply = xcb_randr_get_crtc_transform_reply(XCB::connection(), cookie, &error); + if (error) { + return zeroMatrix(); + } + + const xcb_render_transform_t transform = reply->pending_transform; + free(reply); + return transform; +} + +QSizeF XRandROutput::scaledSize(xcb_render_transform_t transform) const +{ + const QSize ownSize = size(); + if (!ownSize.isValid()) { + return QSize(); + } + + const qreal width = FIXED_TO_DOUBLE(transform.matrix11) * ownSize.width(); + const qreal height = FIXED_TO_DOUBLE(transform.matrix22) * ownSize.height(); + + return QSizeF(width, height); +} + +bool XRandROutput::isReplicaOf(XRandROutput *output, xcb_render_transform_t ownTransform) const +{ + if (output->id() == m_id) { + return false; + } + if (output->position() != position()) { + return false; + } + if (output->replicationSource() != XCB_NONE) { + return false; + } + + const QSizeF sSize = scaledSize(ownTransform); + if (!sSize.isValid()) { + return false; + } + + const auto outputTransform = output->currentTransform(); + if (!isScaling(outputTransform)) { + return false; + } + + if (sSize != output->scaledSize(outputTransform)) { + return false; + } + + return true; +} + +xcb_render_transform_t unityTransform() +{ + return { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; +} + +xcb_render_transform_t XRandROutput::getReplicationTransform(XRandROutput *source) +{ + if (!source) { + return unityTransform(); + } + + const auto *sourceMode = source->currentMode(); + const auto *ownMode = currentMode(); + if (!sourceMode || !ownMode) { + return unityTransform(); + } + + QSize sourceSize = sourceMode->size(); + QSize size = ownMode->size(); + + if (isHorizontal()) { + if (!source->isHorizontal()) { + sourceSize.transpose(); + } + } else if (source->isHorizontal()) { + size.transpose(); + } + + const qreal widthFactor = sourceSize.width() / (qreal)size.width(); + const qreal heightFactor = sourceSize.height() / (qreal)size.height(); + + xcb_render_transform_t transform = unityTransform(); + transform.matrix11 = DOUBLE_TO_FIXED(widthFactor); + transform.matrix22 = DOUBLE_TO_FIXED(heightFactor); + + return transform; +} + +bool XRandROutput::updateReplication() +{ + XRandROutput *source = m_config->output(m_replicationSource); + if (source && (!source->isEnabled() || !source->isConnected())) { + return false; + } + + xcb_render_transform_t transform = getReplicationTransform(source); + QByteArray filterName(isScaling(transform) ? "bilinear" : "nearest"); + + auto cookie = xcb_randr_set_crtc_transform_checked(XCB::connection(), + m_crtc->crtc(), + transform, + filterName.size(), filterName.data(), + 0, nullptr); + xcb_generic_error_t *error = xcb_request_check(XCB::connection(), cookie); + if (error) { + qCDebug(KSCREEN_XRANDR) << "Error on replication transformation!"; + free(error); + return false; + } + free(error); + return true; +} + +bool XRandROutput::setReplicationSource(xcb_randr_output_t source) +{ + if (!m_crtc) { + return false; + } + if (m_replicationSource == source) { + return true; + } + xcb_randr_output_t oldSource = m_replicationSource; + m_replicationSource = source; + if (!updateReplication()) { + m_replicationSource = oldSource; + return false; + } + return true; +} + KScreen::OutputPtr XRandROutput::toKScreenOutput() const { KScreen::OutputPtr kscreenOutput(new KScreen::Output); @@ -341,9 +513,9 @@ kscreenOutput->setRotation(rotation()); kscreenOutput->setCurrentModeId(currentModeId()); } + kscreenOutput->setReplicationSource(m_replicationSource); } - kscreenOutput->blockSignals(signalsBlocked); return kscreenOutput; }