diff --git a/src/client/outputconfiguration.h b/src/client/outputconfiguration.h --- a/src/client/outputconfiguration.h +++ b/src/client/outputconfiguration.h @@ -225,6 +225,18 @@ */ void setColorCurves(OutputDevice *outputdevice, QVector red, QVector green, QVector blue); + /** + * Set replication source for this output. + * Unsets any replication when a null array is set as second argument. + * The changes done in this call will be recorded in the + * OutputDevice and only applied after apply() has been called. + * + * @param source the replication source the outputdevice is a replica of + * @param outputdevice the OutputDevice this change applies to. + * @since 5.62 + */ + void setReplicationSource(OutputDevice *outputdevice, OutputDevice *source); + /** * Ask the compositor to apply the changes. * This results in the compositor looking at all outputdevices and if they have diff --git a/src/client/outputconfiguration.cpp b/src/client/outputconfiguration.cpp --- a/src/client/outputconfiguration.cpp +++ b/src/client/outputconfiguration.cpp @@ -201,6 +201,13 @@ wl_array_release(&wlBlue); } +void OutputConfiguration::setReplicationSource(OutputDevice *outputdevice, OutputDevice *source) +{ + org_kde_kwin_outputdevice *od = outputdevice->output(); + org_kde_kwin_outputdevice *src = source ? source->output() : nullptr; + org_kde_kwin_outputconfiguration_replicate(d->outputconfiguration, od, src); +} + void OutputConfiguration::apply() { org_kde_kwin_outputconfiguration_apply(d->outputconfiguration); diff --git a/src/client/outputdevice.h b/src/client/outputdevice.h --- a/src/client/outputdevice.h +++ b/src/client/outputdevice.h @@ -95,6 +95,18 @@ Disabled = 0, Enabled = 1 }; + enum class TriggerReason { + None = 0, + Explicit = 1 << 0, + Enablement = 1 << 1, + Hotplug = 1 << 2, + Dpms = 1 << 3 + }; + enum class TriggerDirection { + None = 0, + Up = 1 << 0, + Down = 1 << 1 + }; struct Mode { enum class Flag { None = 0, @@ -209,10 +221,12 @@ * @since 5.50 **/ qreal scaleF() const; + /** * Subpixel orientation of this OutputDevice. **/ SubPixel subPixel() const; + /** * Transform that maps framebuffer to OutputDevice. * @@ -227,14 +241,36 @@ **/ ColorCurves colorCurves() const; + /** + * @brief The clone group id of this output + * + * Returns 0 when the output does not have any clones. + * + * @returns Clone group id + * + * @since 5.62 + **/ + quint32 cloneGroupId() const; + + /** + * @brief The replication source uuid to this output + * + * If this output is a replica this returns the uuid of its source. Otherwise a null byte array + * is returned. + * + * @returns Replication source + * + * @since 5.62 + **/ + QByteArray replicationSource() const; + /** * @returns The Modes of this OutputDevice. **/ QList modes() const; KWayland::Client::OutputDevice::Mode currentMode() const; - /** * Sets the @p queue to use for bound proxies. **/ @@ -305,9 +341,16 @@ /** * Emitted whenever the color curves changed. * - * @since 5.TODO + * @since 5.49 **/ void colorCurvesChanged(); + /** + * Emitted whenever the replication source changes. + * + * @since 5.62 + **/ + void replicationSourceChanged(QByteArray source, TriggerReason trigger, + TriggerDirection direction); /** * The corresponding global for this interface on the Registry got removed. * diff --git a/src/client/outputdevice.cpp b/src/client/outputdevice.cpp --- a/src/client/outputdevice.cpp +++ b/src/client/outputdevice.cpp @@ -63,6 +63,8 @@ QByteArray uuid; ColorCurves colorCurves; + quint32 cloneGroupId = 0; + QByteArray replicationSource; bool done = false; @@ -84,14 +86,18 @@ static void serialNumberCallback(void *data, org_kde_kwin_outputdevice *output, const char *serialNumber); static void eisaIdCallback(void *data, org_kde_kwin_outputdevice *output, const char *eisa); + static void cloneCallback(void *data, org_kde_kwin_outputdevice *output, uint32_t group); + static void replicateCallback(void *data, org_kde_kwin_outputdevice *output, + const char *source_uuid, uint32_t trigger, uint32_t direction); void setPhysicalSize(const QSize &size); void setGlobalPosition(const QPoint &pos); void setManufacturer(const QString &manufacturer); void setModel(const QString &model); void setScale(qreal scale); void setSerialNumber(const QString &serialNumber); void setEisaId(const QString &eisaId); + void setCloneGroupId(uint32_t group); void setSubPixel(SubPixel subPixel); void setTransform(Transform transform); void addMode(uint32_t flags, int32_t width, int32_t height, int32_t refresh, int32_t mode_id); @@ -151,7 +157,9 @@ scaleFCallback, colorcurvesCallback, serialNumberCallback, - eisaIdCallback + eisaIdCallback, + cloneCallback, + replicateCallback }; void OutputDevice::Private::geometryCallback(void *data, org_kde_kwin_outputdevice *output, @@ -367,6 +375,87 @@ o->setEisaId(raw); } +void OutputDevice::Private::cloneCallback(void *data, org_kde_kwin_outputdevice *output, uint32_t group) +{ + auto o = reinterpret_cast(data); + Q_UNUSED(output); + o->setCloneGroupId(group); +} + +OutputDevice::TriggerReason toTrigger(uint32_t trigger) +{ + using Reason = OutputDevice::TriggerReason; + + Reason reason = Reason::None; + switch (trigger) { + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_EXPLICIT: + reason = Reason::Explicit; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_ENABLEMENT: + reason = Reason::Enablement; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_HOTPLUG: + reason = Reason::Hotplug; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_DPMS: + reason = Reason::Dpms; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_NONE: + break; + default: + Q_UNREACHABLE(); + break; + } + return reason; +} + +OutputDevice::TriggerDirection toDirection(uint32_t direction) +{ + using Direction = OutputDevice::TriggerDirection; + + Direction dir = Direction::None; + switch (direction) { + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_DIRECTION_UP: + dir = Direction::Up; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_DIRECTION_DOWN: + dir = Direction::Down; + break; + case ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_NONE: + break; + default: + Q_UNREACHABLE(); + break; + } + return dir; +} + +void OutputDevice::Private::replicateCallback(void *data, + org_kde_kwin_outputdevice *output, + const char *source_uuid, + uint32_t trigger, uint32_t direction) +{ + auto o = reinterpret_cast(data); + Q_UNUSED(output); + if (qstrlen(source_uuid) == 0) { + if (o->replicationSource.isNull()) { + // No change. + return; + } + o->replicationSource.clear(); + } else { + const size_t len = strlen(source_uuid); + if (uint(o->replicationSource.size()) == len && + qstrncmp(o->replicationSource, source_uuid, len) == 0) { + // No change. + return; + } + o->replicationSource = source_uuid; + } + emit o->q->replicationSourceChanged(o->replicationSource, toTrigger(trigger), + toDirection(direction)); +} + void OutputDevice::setup(org_kde_kwin_outputdevice *output) { d->setup(output); @@ -407,6 +496,11 @@ eisaId = e; } +void OutputDevice::Private::setCloneGroupId(uint32_t group) +{ + cloneGroupId = group; +} + void OutputDevice::Private::setPhysicalSize(const QSize &size) { physicalSize = size; @@ -545,6 +639,16 @@ return d->colorCurves; } +quint32 OutputDevice::cloneGroupId() const +{ + return d->cloneGroupId; +} + +QByteArray OutputDevice::replicationSource() const +{ + return d->replicationSource; +} + void OutputDevice::destroy() { d->output.destroy(); diff --git a/src/client/protocols/output-management.xml b/src/client/protocols/output-management.xml --- a/src/client/protocols/output-management.xml +++ b/src/client/protocols/output-management.xml @@ -28,7 +28,7 @@ THIS SOFTWARE. ]]> - + This interface enables clients to set properties of output devices for screen configuration purposes via the server. To this end output devices are referenced @@ -80,7 +80,7 @@ - + outputconfiguration is a client-specific resource that can be used to ask the server to apply changes to available output devices. @@ -190,6 +190,18 @@ + + + For an output set the replication source and by this make the output + a replica if the source specifies a different output and to not be a + replica if the specified source is null or the output itself. + + + + diff --git a/src/client/protocols/outputdevice.xml b/src/client/protocols/outputdevice.xml --- a/src/client/protocols/outputdevice.xml +++ b/src/client/protocols/outputdevice.xml @@ -29,7 +29,7 @@ ]]> - + An outputdevice describes a display device available to the compositor. outputdevice is similar to wl_output, but focuses on output @@ -288,6 +288,93 @@ summary="textual representation of EISA identifier"/> + + + Because of hardware restrictions some outputs may be not + configurable independently. Such outputs are called clones of each other. + + To identify such outputs a unique positive group id is sent with this event. + In case the output does not have any clones anymore 0 must be sent. The group id + might change at any time as the compositor pleases. One reason for + such a change could be an output hot plug event. + + + + + + + These flags describe the reason for a certain property change. + + + + + + + + + + + These flags describe the direction of a certain property change. + + + + + + + + + Indicates the state of this output being a replica of another + output. + + The argument specifies the output this output is replicating. The + former is called the replication source. The later is called a + replica of the former. There can only ever be at most one + replication source for a replicating output but multiple replicas + for a replicated output. + + While being a replica it can be expected that its position and scale stay + the same as before until changed through this interface. To get the current + actual position the replication source position must be looked on. The current + client scale can be received through the wl_output interface, the effective + logical scale on the output is an opaque internal to the server. + + Changes to all output device properties can be issued while the output device is + a replica but if a property change directly affects the picture depends on the + compositor behavior. Likely only changes to mode, transform and color curves do + have a direct effect, other properties will be applied when the output is not + anymore a replica. + + The compositor is expected to use black bars or stretch the image on + replicas to fit differing aspect ratios. + + Replication is active when the source argument is non-null. On the other side + the source argument is set to a null string to indicate that the output is + not anymore a replica. + + The event might also be sent by the compositor when the replication source is + disappearing, for example through DPMS change or an hotplug event on the source. + Compositors are urged to fill the argument trigger with this reason, so clients can + react accordingly. The argument direction gives additional information about the reason, + for example on a source removing DPMS change the down enum should be sent, analogous for + enablement and hotplug changes. When the reason is enum explicit the direction down + signals that a client request was responsible for the change while up means that the + change was carried out on some compositor's internal logic. + + + + + diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -213,14 +213,14 @@ &Registry::fakeInputRemoved }}, {Registry::Interface::OutputManagement, { - 2, + 3, QByteArrayLiteral("org_kde_kwin_outputmanagement"), &org_kde_kwin_outputmanagement_interface, &Registry::outputManagementAnnounced, &Registry::outputManagementRemoved }}, {Registry::Interface::OutputDevice, { - 2, + 3, QByteArrayLiteral("org_kde_kwin_outputdevice"), &org_kde_kwin_outputdevice_interface, &Registry::outputDeviceAnnounced, diff --git a/src/server/outputchangeset.h b/src/server/outputchangeset.h --- a/src/server/outputchangeset.h +++ b/src/server/outputchangeset.h @@ -69,6 +69,11 @@ * @returns @c true if the scale() property of the outputdevice has changed. */ bool scaleChanged() const; + /** Whether the replicationSource() property of the outputdevice changed. + * @returns @c true if the replicationSource() property of the outputdevice has changed. + * @since 5.62 + */ + bool replicationSourceChanged() const; /** Whether the colorCurves() property of the outputdevice changed. * @returns @c true if the colorCurves() property of the outputdevice has changed. */ @@ -94,6 +99,10 @@ * @since 5.XX */ OutputDeviceInterface::ColorCurves colorCurves() const; + /** The new value for the replicationSource. + * @since 5.62 + */ + OutputDeviceInterface* replicationSource() const; private: friend class OutputConfigurationInterface; diff --git a/src/server/outputchangeset.cpp b/src/server/outputchangeset.cpp --- a/src/server/outputchangeset.cpp +++ b/src/server/outputchangeset.cpp @@ -35,6 +35,7 @@ , position(o->globalPosition()) , scale(o->scale()) , colorCurves(o->colorCurves()) + , replicationSource(o->replicationSource()) { } @@ -130,5 +131,17 @@ return d->colorCurves; } +bool OutputChangeSet::replicationSourceChanged() const +{ + Q_D(); + return d->replicationSource != d->o->replicationSource(); +} + +OutputDeviceInterface* OutputChangeSet::replicationSource() const +{ + Q_D(); + return d->replicationSource; +} + } } diff --git a/src/server/outputchangeset_p.h b/src/server/outputchangeset_p.h --- a/src/server/outputchangeset_p.h +++ b/src/server/outputchangeset_p.h @@ -42,6 +42,7 @@ QPoint position; qreal scale; OutputDeviceInterface::ColorCurves colorCurves; + OutputDeviceInterface *replicationSource; }; } } diff --git a/src/server/outputconfiguration_interface.cpp b/src/server/outputconfiguration_interface.cpp --- a/src/server/outputconfiguration_interface.cpp +++ b/src/server/outputconfiguration_interface.cpp @@ -55,7 +55,7 @@ OutputManagementInterface *outputManagement; QHash changes; - static const quint32 s_version = 2; + static const quint32 s_version = 3; private: static void enableCallback(wl_client *client, wl_resource *resource, @@ -74,6 +74,8 @@ static void colorcurvesCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, wl_array *red, wl_array *green, wl_array *blue); + static void replicateCallback(wl_client *client, wl_resource *resource, + wl_resource * outputdevice, wl_resource *source); OutputConfigurationInterface *q_func() { return reinterpret_cast(q); @@ -91,7 +93,8 @@ applyCallback, scaleFCallback, colorcurvesCallback, - resourceDestroyedCallback + resourceDestroyedCallback, + replicateCallback }; OutputConfigurationInterface::OutputConfigurationInterface(OutputManagementInterface* parent, wl_resource* parentResource): Resource(new Private(this, parent, parentResource)) @@ -253,6 +256,24 @@ s->pendingChanges(o)->d_func()->colorCurves = cc; } +void OutputConfigurationInterface::Private::replicateCallback(wl_client *client, wl_resource *resource, + wl_resource *outputdevice, + wl_resource *source) +{ + Q_UNUSED(client); + + OutputDeviceInterface *src = OutputDeviceInterface::get(source); + if (!src && source) { + qCWarning(KWAYLAND_SERVER) << "Requested to clone output device from" << source << ", but does not exist."; + return; + } + + OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); + auto s = cast(resource); + Q_ASSERT(s); + s->pendingChanges(o)->d_func()->replicationSource = src; +} + void OutputConfigurationInterface::Private::emitConfigurationChangeRequested() const { auto configinterface = reinterpret_cast(q); @@ -326,7 +347,8 @@ c->modeChanged() || c->transformChanged() || c->positionChanged() || - c->scaleChanged(); + c->scaleChanged() || + c->replicationSourceChanged(); } void OutputConfigurationInterface::Private::clearPendingChanges() diff --git a/src/server/outputdevice_interface.h b/src/server/outputdevice_interface.h --- a/src/server/outputdevice_interface.h +++ b/src/server/outputdevice_interface.h @@ -83,6 +83,18 @@ Disabled = 0, Enabled = 1 }; + enum class TriggerReason { + None = 0, + Explicit = 1 << 0, + Enablement = 1 << 1, + Hotplug = 1 << 2, + Dpms = 1 << 3 + }; + enum class TriggerDirection { + None = 0, + Up = 1 << 0, + Down = 1 << 1 + }; enum class ModeFlag { Current = 1, Preferred = 2 @@ -102,20 +114,80 @@ virtual ~OutputDeviceInterface(); QSize physicalSize() const; + + /** + * Last explicitly set position in compositor space on this interface. In case the output is + * a replica this position is not the current actual position in compositor space, but if + * a replication goes away the compositor can fall back to this one. + * + * @return last explicitly set position in compositor space + */ QPoint globalPosition() const; + + /** + * This describes the current position in compositor space factoring in the globalPosition() and + * if this output is a replica of another output. + * + * @return current position in compositor space + * + * @since 5.62 + */ + QPoint logicalPosition() const; + + /** + * Current size in compositor space factoring in the globalPosition() and if this output is a + * replica of another output. + * @return + * + * @see globalSize + * @see replicationSource + * + * @since 5.62 + */ + QSize logicalSize() const; + + /** + * Describes the optimal placement of the presented output view in relation to the + * pixel/mode size. Takes into account the aspect ratio and centers the view. + * + * @return view geometry in mode size coordinates + * + * @see pixelSize + * + * @since 5.62 + */ + QRect viewGeometry() const; + QString manufacturer() const; QString model() const; QString serialNumber() const; QString eisaId() const; QSize pixelSize() const; int refreshRate() const; int scale() const; qreal scaleF() const; + + /** + * While scale() and scaleF() only depend on the scale explicity set this provides the + * calculated scale that clients should orientate their rendering at. + * + * It takes into account the value set through setScale(int) and setScale(qreal) as well as the + * replication status of the output such that a sharp image is produced on all replicas and the + * replication source. + * + * @return calculated minimum clients scale on output to produce sharp images. + * + * @since 5.62 + */ + int clientsScale() const; + SubPixel subPixel() const; Transform transform() const; ColorCurves colorCurves() const; QList modes() const; int currentModeId() const; + quint32 cloneGroupId() const; + OutputDeviceInterface* replicationSource() const; QByteArray edid() const; OutputDeviceInterface::Enablement enabled() const; @@ -141,6 +213,8 @@ */ void addMode(Mode &mode); void setCurrentMode(const int modeId); + void setCloneGroupId(const quint32 groupId); + void setReplicationSource(OutputDeviceInterface *source, TriggerReason trigger); void setEdid(const QByteArray &edid); void setEnabled(OutputDeviceInterface::Enablement enabled); @@ -151,16 +225,28 @@ Q_SIGNALS: void physicalSizeChanged(const QSize&); + void viewGeometryChanged(const QRect&); + void globalPositionChanged(const QPoint&); + void logicalPositionChanged(const QPoint&); + void manufacturerChanged(const QString&); void modelChanged(const QString&); void serialNumberChanged(const QString&); void eisaIdChanged(const QString &); void pixelSizeChanged(const QSize&); + + void globalSizeChanged(const QSize&); + void logicalSizeChanged(const QSize&); + void refreshRateChanged(int); + void cloneGroupIdChanged(quint32); + void replicationSourceChanged(OutputDeviceInterface*, TriggerReason trigger, + TriggerDirection direction); //@deprecated see scaleChanged(real) void scaleChanged(int); void scaleFChanged(qreal); + void clientsScaleChanged(int); void subPixelChanged(SubPixel); void transformChanged(Transform); void colorCurvesChanged(ColorCurves); diff --git a/src/server/outputdevice_interface.cpp b/src/server/outputdevice_interface.cpp --- a/src/server/outputdevice_interface.cpp +++ b/src/server/outputdevice_interface.cpp @@ -24,7 +24,6 @@ #include #include "wayland-org_kde_kwin_outputdevice-server-protocol.h" -#include namespace KWayland { @@ -41,12 +40,13 @@ Private(OutputDeviceInterface *q, Display *d); ~Private(); - void updateGeometry(); void updateUuid(); void updateEdid(); void updateEnabled(); void updateScale(); + void updateCloneGroupId(); + void updateReplicationSource(TriggerReason trigger, TriggerDirection direction); void updateColorCurves(); void updateEisaId(); void updateSerialNumber(); @@ -59,21 +59,45 @@ void sendEnabled(const ResourceData &data); void sendScale(const ResourceData &data); void sendColorCurves(const ResourceData &data); + void sendCloneGroupId(const ResourceData &data); + void sendReplicationSource(const ResourceData &data, TriggerReason trigger, + TriggerDirection direction); void sendEisaId(const ResourceData &data); void sendSerialNumber(const ResourceData &data); + void setViewGeometry(const QRect &geo); + void resetViewGeometry(); + + void resetLogicalPosition(); + void resetLogicalSize(); + + void setClientsScale(int scale); + void resetClientsScale(); + void addReplica(OutputDeviceInterface *replica); + void removeReplica(OutputDeviceInterface *replica); + QSize physicalSize; + QRect viewGeometry; + QPoint globalPosition; + QPoint logicalPosition; + QSize globalSize; + QSize logicalSize; + QString manufacturer = QStringLiteral("org.kde.kwin"); QString model = QStringLiteral("none"); qreal scale = 1.0; + int clientsScale = 1; QString serialNumber; QString eisaId; SubPixel subPixel = SubPixel::Unknown; Transform transform = Transform::Normal; ColorCurves colorCurves; QList modes; Mode currentMode; + quint32 cloneGroupId = 0; + OutputDeviceInterface *replicationSource = nullptr; + QVector replicas; QList resources; QByteArray edid; @@ -94,7 +118,7 @@ static QVector s_privates; }; -const quint32 OutputDeviceInterface::Private::s_version = 2; +const quint32 OutputDeviceInterface::Private::s_version = 3; QVector OutputDeviceInterface::Private::s_privates; @@ -151,10 +175,29 @@ connect(this, &OutputDeviceInterface::manufacturerChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::scaleFChanged, this, [this, d] { d->updateScale(); }); connect(this, &OutputDeviceInterface::scaleChanged, this, [this, d] { d->updateScale(); }); + connect(this, &OutputDeviceInterface::cloneGroupIdChanged, this, [this, d] { d->updateCloneGroupId(); }); + connect(this, &OutputDeviceInterface::replicationSourceChanged, + this, [this, d](OutputDeviceInterface *source, TriggerReason trigger, + TriggerDirection direction) { + Q_UNUSED(source) + d->updateReplicationSource(trigger, direction); + }); connect(this, &OutputDeviceInterface::colorCurvesChanged, this, [this, d] { d->updateColorCurves(); }); } -OutputDeviceInterface::~OutputDeviceInterface() = default; +OutputDeviceInterface::~OutputDeviceInterface() +{ + Q_D(); + + if (d->replicationSource) { + d->replicationSource->d_func()->removeReplica(this); + d->replicationSource = nullptr; + } + + for (auto *replica : d->replicas) { + replica->setReplicationSource(nullptr, TriggerReason::Hotplug); + } +} QSize OutputDeviceInterface::pixelSize() const { @@ -225,6 +268,8 @@ emit modesChanged(); if (mode.flags.testFlag(ModeFlag::Current)) { d->currentMode = mode; + d->resetLogicalSize(); + d->resetViewGeometry(); emit refreshRateChanged(mode.refreshRate); emit pixelSizeChanged(mode.size); emit currentModeChanged(); @@ -276,12 +321,46 @@ Q_ASSERT(existingModeIt != d->modes.end()); (*existingModeIt).flags |= ModeFlag::Current; d->currentMode = *existingModeIt; + d->resetLogicalSize(); + d->resetViewGeometry(); emit modesChanged(); emit refreshRateChanged((*existingModeIt).refreshRate); emit pixelSizeChanged((*existingModeIt).size); emit currentModeChanged(); } +void OutputDeviceInterface::setCloneGroupId(const quint32 groupId) +{ + Q_D(); + if (d->cloneGroupId == groupId) { + return; + } + d->cloneGroupId = groupId; + emit cloneGroupIdChanged(groupId); +} + +void OutputDeviceInterface::setReplicationSource(OutputDeviceInterface *source, + TriggerReason trigger) +{ + Q_D(); + if (d->replicationSource == source) { + return; + } + if (source) { + source->d_func()->addReplica(this); + } else { + d->replicationSource->d_func()->removeReplica(this); + } + d->replicationSource = source; + + d->resetLogicalPosition(); + d->resetLogicalSize(); + d->resetViewGeometry(); + + emit replicationSourceChanged(source, trigger, source ? TriggerDirection::Up : + TriggerDirection::Down); +} + int32_t OutputDeviceInterface::Private::toTransform() const { switch (transform) { @@ -342,6 +421,7 @@ sendGeometry(resource); sendScale(r); sendColorCurves(r); + sendReplicationSource(r, TriggerReason::None, TriggerDirection::None); sendEisaId(r); sendSerialNumber(r); @@ -445,6 +525,75 @@ wl_array_release(&wlBlue); } +void OutputDeviceInterface::Private::sendCloneGroupId(const ResourceData &data) +{ + if (wl_resource_get_version(data.resource) < ORG_KDE_KWIN_OUTPUTDEVICE_CLONE_SINCE_VERSION) { + return; + } + org_kde_kwin_outputdevice_send_clone(data.resource, cloneGroupId); +} + +uint32_t toTrigger(OutputDeviceInterface::TriggerReason trigger) +{ + using Reason = OutputDeviceInterface::TriggerReason; + + uint32_t reason = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_NONE; + switch (trigger) { + case Reason::Explicit: + reason = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_EXPLICIT; + break; + case Reason::Enablement: + reason = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_ENABLEMENT; + break; + case Reason::Hotplug: + reason = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_HOTPLUG; + break; + case Reason::Dpms: + reason = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_REASON_DPMS; + break; + case Reason::None: + break; + default: + Q_UNREACHABLE(); + break; + } + return reason; +} + +uint32_t toDirection(OutputDeviceInterface::TriggerDirection direction) +{ + using Direction = OutputDeviceInterface::TriggerDirection; + + uint32_t dir = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_DIRECTION_NONE; + switch (direction) { + case Direction::Up: + dir = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_DIRECTION_UP; + break; + case Direction::Down: + dir = ORG_KDE_KWIN_OUTPUTDEVICE_TRIGGER_DIRECTION_DOWN; + break; + case Direction::None: + break; + default: + Q_UNREACHABLE(); + break; + } + return dir; +} + +void OutputDeviceInterface::Private::sendReplicationSource(const ResourceData &data, + TriggerReason trigger, + TriggerDirection direction) +{ + if (wl_resource_get_version(data.resource) < ORG_KDE_KWIN_OUTPUTDEVICE_REPLICATE_SINCE_VERSION) { + return; + } + + org_kde_kwin_outputdevice_send_replicate(data.resource, + replicationSource ? replicationSource->uuid().constData() : nullptr, + toTrigger(trigger), toDirection(direction)); +} + void KWayland::Server::OutputDeviceInterface::Private::sendSerialNumber(const ResourceData &data) { if (wl_resource_get_version(data.resource) >= ORG_KDE_KWIN_OUTPUTDEVICE_SERIAL_NUMBER_SINCE_VERSION) { @@ -483,14 +632,151 @@ } } +void OutputDeviceInterface::Private::updateCloneGroupId() +{ + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + sendCloneGroupId(*it); + sendDone(*it); + } +} + +void OutputDeviceInterface::Private::updateReplicationSource(TriggerReason trigger, + TriggerDirection direction) +{ + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + sendReplicationSource(*it, trigger, direction); + sendDone(*it); + } +} + void OutputDeviceInterface::Private::updateColorCurves() { for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { sendColorCurves(*it); sendDone(*it); } } +void OutputDeviceInterface::Private::setViewGeometry(const QRect &geo) +{ + if (viewGeometry == geo) { + return; + } + viewGeometry = geo; + resetClientsScale(); + emit q->viewGeometryChanged(geo); +} + +void OutputDeviceInterface::Private::resetViewGeometry() +{ + if (!replicationSource) { + setViewGeometry(QRect(QPoint(0, 0), q->pixelSize())); + return; + } + + // Fit view into output mode keeping the aspect ratio. + const QSize modeSize = q->pixelSize(); + const QSize sourceSize = replicationSource->pixelSize(); + + QSize viewSize; + viewSize.setWidth(modeSize.width()); + viewSize.setHeight(viewSize.width() * sourceSize.height() / (double)sourceSize.width()); + + if (viewSize.height() > modeSize.height()) { + const QSize oldSize = viewSize; + viewSize.setHeight(modeSize.height()); + viewSize.setWidth(oldSize.width() * viewSize.height() / (double)oldSize.height()); + } + + Q_ASSERT(viewSize.height() <= modeSize.height()); + Q_ASSERT(viewSize.width() <= modeSize.width()); + + const QPoint pos((modeSize.width() - viewSize.width()) / 2, + (modeSize.height() - viewSize.height()) / 2); + + setViewGeometry(QRect(pos, viewSize)); +} + +void OutputDeviceInterface::Private::resetLogicalPosition() +{ + const QPoint pos = replicationSource ? replicationSource->d_func()->logicalPosition : + globalPosition; + if (logicalPosition == pos) { + return; + } + logicalPosition = pos; + for (auto *replica : replicas) { + replica->d_func()->resetLogicalPosition(); + } + emit q->logicalPositionChanged(pos); +} + +void OutputDeviceInterface::Private::resetLogicalSize() +{ + const QSize size = replicationSource ? replicationSource->d_func()->logicalSize : + q->pixelSize() / scale; + if (logicalSize == size) { + return; + } + logicalSize = size; + for (auto *replica : replicas) { + replica->d_func()->resetLogicalSize(); + } + emit q->logicalSizeChanged(size); +} + +void OutputDeviceInterface::Private::addReplica(OutputDeviceInterface *replica) +{ + replicas << replica; + resetClientsScale(); +} + +void OutputDeviceInterface::Private::removeReplica(OutputDeviceInterface *replica) +{ + replicas.removeOne(replica); + resetClientsScale(); +} + +void OutputDeviceInterface::Private::setClientsScale(int scale) +{ + if (clientsScale == scale) { + return; + } + clientsScale = scale; + for (auto *replica : replicas) { + replica->d_func()->setClientsScale(scale); + } + resetLogicalSize(); + emit q->clientsScaleChanged(scale); +} + +void OutputDeviceInterface::Private::resetClientsScale() +{ + if (replicationSource) { + // The clients scale is set on the replication source. + setClientsScale(replicationSource->clientsScale()); + return; + } + + const QSize size = q->viewGeometry().size(); + + int maxWidth = size.width(); + int maxHeight = size.height(); + for (auto *replica : replicas) { + const QSize replicaSize = replica->viewGeometry().size(); + maxWidth = qMax(replicaSize.width(), maxWidth); + maxHeight = qMax(replicaSize.height(), maxHeight); + } + + if (size.width() == maxWidth && size.height() == maxHeight) { + setClientsScale(std::ceil(scale)); + return; + } + + qreal factor = qMax(maxWidth / (qreal)size.width(), maxHeight / (qreal)size.height()); + setClientsScale(std::ceil(factor)); +} + bool OutputDeviceInterface::ColorCurves::operator==(const ColorCurves &cc) const { return red == cc.red && green == cc.green && blue == cc.blue; @@ -511,7 +797,6 @@ } SETTER(setPhysicalSize, const QSize&, physicalSize) -SETTER(setGlobalPosition, const QPoint&, globalPosition) SETTER(setManufacturer, const QString&, manufacturer) SETTER(setModel, const QString&, model) SETTER(setSerialNumber, const QString&, serialNumber) @@ -521,13 +806,28 @@ #undef SETTER +void OutputDeviceInterface::setGlobalPosition(const QPoint& pos) \ +{ + Q_D(); + if (d->globalPosition == pos) { + return; + } + d->globalPosition = pos; + for (auto *replica : d->replicas) { + replica->d_func()->resetLogicalPosition(); + } + d->resetLogicalPosition(); + emit globalPositionChanged(pos); +} + void OutputDeviceInterface::setScale(int scale) { Q_D(); if (d->scale == scale) { return; } d->scale = scale; + d->resetClientsScale(); emit scaleChanged(d->scale); emit scaleFChanged(d->scale); } @@ -539,6 +839,7 @@ return; } d->scale = scale; + d->resetClientsScale(); emit scaleChanged(qRound(d->scale)); emit scaleFChanged(d->scale); } @@ -555,6 +856,24 @@ return d->globalPosition; } +QPoint OutputDeviceInterface::logicalPosition() const +{ + Q_D(); + return d->logicalPosition; +} + +QSize OutputDeviceInterface::logicalSize() const +{ + Q_D(); + return d->logicalSize; +} + +QRect OutputDeviceInterface::viewGeometry() const +{ + Q_D(); + return d->viewGeometry; +} + QString OutputDeviceInterface::manufacturer() const { Q_D(); @@ -567,6 +886,18 @@ return d->model; } +quint32 OutputDeviceInterface::cloneGroupId() const +{ + Q_D(); + return d->cloneGroupId; +} + +OutputDeviceInterface* OutputDeviceInterface::replicationSource() const +{ + Q_D(); + return d->replicationSource; +} + QString OutputDeviceInterface::serialNumber() const { Q_D(); @@ -591,6 +922,11 @@ return d->scale; } +int OutputDeviceInterface::clientsScale() const +{ + Q_D(); + return d->clientsScale; +} OutputDeviceInterface::SubPixel OutputDeviceInterface::subPixel() const { diff --git a/src/server/outputmanagement_interface.cpp b/src/server/outputmanagement_interface.cpp --- a/src/server/outputmanagement_interface.cpp +++ b/src/server/outputmanagement_interface.cpp @@ -56,7 +56,7 @@ QHash configurationInterfaces; }; -const quint32 OutputManagementInterface::Private::s_version = 2; +const quint32 OutputManagementInterface::Private::s_version = 3; const struct org_kde_kwin_outputmanagement_interface OutputManagementInterface::Private::s_interface = { createConfigurationCallback