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; @@ -197,6 +198,16 @@ } } + if (uint(kscreenOutput->replicationSource()) != currentOutput->replicationSource()) { + currentOutput->setReplicationSource(kscreenOutput->replicationSource()); + if (int sourceId = kscreenOutput->replicationSource()) { + kscreenOutput->setPos(config->output(sourceId)->pos()); + } + if (!toChange.contains(outputId)) { + toChange.insert(outputId, kscreenOutput); + } + } + XRandRMode *currentMode = currentOutput->modes().value( kscreenOutput->currentModeId().toInt()); // For some reason, in some environments currentMode is null @@ -321,6 +332,14 @@ setPrimaryOutput(primaryOutput); } + for (auto *out : m_outputs) { + if (out->applyReplicationSource()) { + KScreen::OutputPtr kscreenOut = kscreenOutputs[out->id()]; + changeOutput(kscreenOut); + forceScreenSizeUpdate = true; + } + } + if (forceScreenSizeUpdate || intermediateScreenSize != newScreenSize) { QSize newSize = newScreenSize; if (forceScreenSizeUpdate) { @@ -592,6 +611,7 @@ { XRandROutput *xOutput = output(kscreenOutput->id()); Q_ASSERT(xOutput); + if (!xOutput->crtc()) { qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id() << "has no CRTC, falling back to enableOutput()"; @@ -609,27 +629,36 @@ << "\tMode:" << modeId << kscreenOutput->currentMode() << "\n" << "\tRotation:" << kscreenOutput->rotation(); - xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; - - 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); - - XCB::ScopedPointer - reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr)); + auto sendConfig = [kscreenOutput, xOutput, modeId]() { + xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; + 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); + + 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; + return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); + }; - if (!reply) { - qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)"; + if (!sendConfig()) { 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()); + xOutput->update(xOutput->crtc()->crtc(), modeId, + XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); + if (uint(kscreenOutput->replicationSource()) == xOutput->replicationSource()) { + xOutput->updateReplicationSource(); + if (!sendConfig()) { + return false; + } } - return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); + return true; } diff --git a/backends/xrandr/xrandrcrtc.h b/backends/xrandr/xrandrcrtc.h --- a/backends/xrandr/xrandrcrtc.h +++ b/backends/xrandr/xrandrcrtc.h @@ -44,6 +44,7 @@ QRect geometry() const; xcb_randr_rotation_t rotation() const; + xcb_render_transform_t transform() const; QVector possibleOutputs(); QVector outputs() const; @@ -62,6 +63,7 @@ QRect m_geometry; xcb_randr_rotation_t m_rotation; + xcb_render_transform_t m_transform; QVector m_possibleOutputs; QVector m_outputs; 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,31 @@ KScreen::OutputPtr toKScreenOutput() const; + bool updateReplicationSource(); + bool applyReplicationSource(); + + xcb_randr_output_t replicationSource() const; + void setReplicationSource(xcb_randr_output_t source); + + 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 +114,8 @@ QStringList m_preferredModes; QList m_clones; + xcb_randr_output_t m_replicationSource; + xcb_randr_output_t m_pendingReplicationSource; 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,24 @@ #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_pendingReplicationSource(XCB_NONE) , m_crtc(nullptr) { init(); @@ -101,6 +111,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 +130,16 @@ return m_crtc; } +xcb_randr_output_t XRandROutput::replicationSource() const +{ + return m_replicationSource; +} + +void XRandROutput::setReplicationSource(xcb_randr_output_t source) +{ + m_pendingReplicationSource = source; +} + void XRandROutput::update() { init(); @@ -300,6 +326,149 @@ 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(); + } + + const QSize sourceSize = sourceMode->size(); + QSize size = ownMode->size(); + + if (isHorizontal() != 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::updateReplicationSource() +{ + XRandROutput *source = m_config->output(m_pendingReplicationSource); + if (source && (!source->isEnabled() || !source->isConnected())) { + source = nullptr; + } + + 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); + m_replicationSource = m_pendingReplicationSource; + return true; +} + +bool XRandROutput::applyReplicationSource() +{ + if (!m_crtc) { + return false; + } + if (m_replicationSource == m_pendingReplicationSource) { + return false; + } + return updateReplicationSource(); +} + KScreen::OutputPtr XRandROutput::toKScreenOutput() const { KScreen::OutputPtr kscreenOutput(new KScreen::Output); @@ -341,9 +510,9 @@ kscreenOutput->setRotation(rotation()); kscreenOutput->setCurrentModeId(currentModeId()); } + kscreenOutput->setReplicationSource(m_replicationSource); } - kscreenOutput->blockSignals(signalsBlocked); return kscreenOutput; }