Add virtual display management to the KWin UI
This commit is contained in:
parent
aa3d7d24c1
commit
85f9e9a9d6
|
|
@ -67,5 +67,12 @@
|
|||
<label>Remove virtual displays on disable</label>
|
||||
<description>Whether to remove any virtual displays when the effect is disabled</description>
|
||||
</entry>
|
||||
<entry name="LookAheadOverride" type="Int">
|
||||
<default>-1</default>
|
||||
<min>-1</min>
|
||||
<max>40</max>
|
||||
<label>Movement look-ahead (ms)</label>
|
||||
<description>Override the default look ahead time in milliseconds (-1 to use default)</description>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
#include "core/output.h"
|
||||
#include "core/rendertarget.h"
|
||||
#include "core/renderviewport.h"
|
||||
#include "kcm/shortcuts.h"
|
||||
|
|
@ -39,15 +39,23 @@ public:
|
|||
: QObject(effect), m_effect(effect) {}
|
||||
|
||||
public Q_SLOTS:
|
||||
void AddVirtualDisplay(int width, int height) {
|
||||
QMetaObject::invokeMethod(m_effect, [this, width, height]() {
|
||||
m_effect->addVirtualDisplay(QSize(width, height));
|
||||
}, Qt::QueuedConnection);
|
||||
QVariantList AddVirtualDisplay(int width, int height) {
|
||||
m_effect->addVirtualDisplay(QSize(width, height));
|
||||
return m_effect->listVirtualDisplays();
|
||||
}
|
||||
|
||||
private:
|
||||
KWin::BreezyDesktopEffect *m_effect;
|
||||
};
|
||||
QVariantList ListVirtualDisplays() const {
|
||||
return m_effect->listVirtualDisplays();
|
||||
}
|
||||
|
||||
QVariantList RemoveVirtualDisplay(const QString &id) {
|
||||
m_effect->removeVirtualDisplay(id);
|
||||
return m_effect->listVirtualDisplays();
|
||||
}
|
||||
|
||||
private:
|
||||
KWin::BreezyDesktopEffect *m_effect;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace DataView
|
||||
|
|
@ -263,10 +271,12 @@ void BreezyDesktopEffect::deactivate()
|
|||
showCursor();
|
||||
|
||||
if (m_removeVirtualDisplaysOnDisable) {
|
||||
for (auto output : m_virtualOutputs) {
|
||||
KWin::kwinApp()->outputBackend()->removeVirtualOutput(output);
|
||||
for (auto it = m_virtualDisplays.begin(); it != m_virtualDisplays.end(); ++it) {
|
||||
if (it->output) {
|
||||
KWin::kwinApp()->outputBackend()->removeVirtualOutput(it->output);
|
||||
}
|
||||
}
|
||||
m_virtualOutputs.clear();
|
||||
m_virtualDisplays.clear();
|
||||
}
|
||||
|
||||
setRunning(false);
|
||||
|
|
@ -302,7 +312,7 @@ void BreezyDesktopEffect::addVirtualDisplay(QSize size)
|
|||
{
|
||||
static int virtualDisplayCount = 0;
|
||||
++virtualDisplayCount;
|
||||
QString name = QStringLiteral("BreezyDesktop_VirtualDisplay_%1x%2_%3").arg(size.width()).arg(size.height()).arg(virtualDisplayCount);
|
||||
QString name = QStringLiteral("BreezyDesktop_%1").arg(virtualDisplayCount);
|
||||
#if defined(KWIN_VERSION_ENCODED) && KWIN_VERSION_ENCODED >= 60290
|
||||
QString description = QStringLiteral("Breezy Display %1x%2 (%3)").arg(size.width()).arg(size.height()).arg(virtualDisplayCount);
|
||||
auto output = KWin::kwinApp()->outputBackend()->createVirtualOutput(name, description, size, 1.0);
|
||||
|
|
@ -310,10 +320,42 @@ void BreezyDesktopEffect::addVirtualDisplay(QSize size)
|
|||
auto output = KWin::kwinApp()->outputBackend()->createVirtualOutput(name, size, 1.0);
|
||||
#endif
|
||||
if (output) {
|
||||
m_virtualOutputs.append(output);
|
||||
VirtualOutputInfo info;
|
||||
info.output = output;
|
||||
info.id = name;
|
||||
info.size = size;
|
||||
m_virtualDisplays.insert(info.id, info);
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList BreezyDesktopEffect::listVirtualDisplays() const {
|
||||
QVariantList list;
|
||||
for (auto it = m_virtualDisplays.constBegin(); it != m_virtualDisplays.constEnd(); ++it) {
|
||||
const auto &info = it.value();
|
||||
if (!info.output)
|
||||
continue;
|
||||
QVariantMap entry;
|
||||
entry.insert(QStringLiteral("id"), info.id);
|
||||
entry.insert(QStringLiteral("width"), info.size.width());
|
||||
entry.insert(QStringLiteral("height"), info.size.height());
|
||||
list.push_back(entry);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
bool BreezyDesktopEffect::removeVirtualDisplay(const QString &id) {
|
||||
auto it = m_virtualDisplays.find(id);
|
||||
if (it != m_virtualDisplays.end()) {
|
||||
Output *output = it->output;
|
||||
if (output) {
|
||||
KWin::kwinApp()->outputBackend()->removeVirtualOutput(output);
|
||||
}
|
||||
m_virtualDisplays.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BreezyDesktopEffect::isEnabled() const {
|
||||
return m_enabled;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
#include <QImage>
|
||||
#include <QKeySequence>
|
||||
#include <QQuaternion>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
#include <QHash>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
|
@ -91,6 +94,8 @@ namespace KWin
|
|||
void updateImuRotation();
|
||||
void updateCursorImage();
|
||||
void updateCursorPos();
|
||||
QVariantList listVirtualDisplays() const;
|
||||
bool removeVirtualDisplay(const QString &id);
|
||||
|
||||
Q_SIGNALS:
|
||||
void focusedDisplayDistanceChanged();
|
||||
|
|
@ -146,7 +151,13 @@ namespace KWin
|
|||
int m_antialiasingQuality = 3; // 0=None, 1=Medium, 2=High, 3=VeryHigh
|
||||
bool m_removeVirtualDisplaysOnDisable = true;
|
||||
bool m_mirrorPhysicalDisplays = false;
|
||||
QList<Output *> m_virtualOutputs;
|
||||
|
||||
struct VirtualOutputInfo {
|
||||
Output *output = nullptr;
|
||||
QString id;
|
||||
QSize size;
|
||||
};
|
||||
QHash<QString, VirtualOutputInfo> m_virtualDisplays;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@
|
|||
#include <QComboBox>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusReply>
|
||||
#include <QDBusVariant>
|
||||
#include <QDBusArgument>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QIcon>
|
||||
|
||||
Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr")
|
||||
|
||||
|
|
@ -149,27 +157,28 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
|
|||
}
|
||||
|
||||
// Wire Add Virtual Display buttons via DBus to the effect
|
||||
auto callAddVirtualDisplay = [](int w, int h) {
|
||||
QDBusInterface iface(
|
||||
QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("/com/xronlinux/BreezyDesktop"),
|
||||
QStringLiteral("com.xronlinux.BreezyDesktop"),
|
||||
QDBusConnection::sessionBus());
|
||||
if (iface.isValid()) {
|
||||
iface.call(QDBus::NoBlock, QStringLiteral("AddVirtualDisplay"), w, h);
|
||||
}
|
||||
};
|
||||
if (auto btn1080p = widget()->findChild<QPushButton*>("buttonAdd1080p")) {
|
||||
connect(btn1080p, &QPushButton::clicked, this, [callAddVirtualDisplay]() {
|
||||
callAddVirtualDisplay(1920, 1080);
|
||||
connect(btn1080p, &QPushButton::clicked, this, [this]() {
|
||||
auto list = dbusAddVirtualDisplay(1920, 1080);
|
||||
renderVirtualDisplays(list);
|
||||
});
|
||||
}
|
||||
if (auto btn1440p = widget()->findChild<QPushButton*>("buttonAdd1440p")) {
|
||||
connect(btn1440p, &QPushButton::clicked, this, [callAddVirtualDisplay]() {
|
||||
callAddVirtualDisplay(2560, 1440);
|
||||
connect(btn1440p, &QPushButton::clicked, this, [this]() {
|
||||
auto list = dbusAddVirtualDisplay(2560, 1440);
|
||||
renderVirtualDisplays(list);
|
||||
});
|
||||
}
|
||||
|
||||
renderVirtualDisplays(dbusListVirtualDisplays());
|
||||
|
||||
m_virtualDisplayPollTimer.setInterval(15000);
|
||||
m_virtualDisplayPollTimer.setTimerType(Qt::CoarseTimer);
|
||||
connect(&m_virtualDisplayPollTimer, &QTimer::timeout, this, [this]() {
|
||||
renderVirtualDisplays(dbusListVirtualDisplays());
|
||||
});
|
||||
m_virtualDisplayPollTimer.start();
|
||||
|
||||
// General tab: Open KDE Displays Settings
|
||||
if (auto btnDisplays = widget()->findChild<QPushButton*>(QStringLiteral("buttonOpenDisplaysSettings"))) {
|
||||
connect(btnDisplays, &QPushButton::clicked, this, [this]() {
|
||||
|
|
@ -243,6 +252,131 @@ void BreezyDesktopEffectConfig::updateUnmanagedState()
|
|||
{
|
||||
}
|
||||
|
||||
static QDBusInterface makeVDInterface() {
|
||||
return QDBusInterface(
|
||||
QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("/com/xronlinux/BreezyDesktop"),
|
||||
QStringLiteral("com.xronlinux.BreezyDesktop"),
|
||||
QDBusConnection::sessionBus());
|
||||
}
|
||||
|
||||
QVariantList BreezyDesktopEffectConfig::dbusListVirtualDisplays() const {
|
||||
QDBusInterface iface = makeVDInterface();
|
||||
if (!iface.isValid()) return {};
|
||||
QDBusReply<QVariantList> reply = iface.call(QStringLiteral("ListVirtualDisplays"));
|
||||
return reply.isValid() ? reply.value() : QVariantList{};
|
||||
}
|
||||
|
||||
QVariantList BreezyDesktopEffectConfig::dbusAddVirtualDisplay(int w, int h) const {
|
||||
QDBusInterface iface = makeVDInterface();
|
||||
if (!iface.isValid()) return {};
|
||||
// Fire add, then fetch authoritative list to avoid marshalling quirks
|
||||
iface.call(QStringLiteral("AddVirtualDisplay"), w, h);
|
||||
QDBusReply<QVariantList> list = iface.call(QStringLiteral("ListVirtualDisplays"));
|
||||
return list.isValid() ? list.value() : QVariantList{};
|
||||
}
|
||||
|
||||
QVariantList BreezyDesktopEffectConfig::dbusRemoveVirtualDisplay(const QString &id) const {
|
||||
QDBusInterface iface = makeVDInterface();
|
||||
if (!iface.isValid()) return {};
|
||||
// Fire remove, then fetch authoritative list to avoid marshalling quirks
|
||||
iface.call(QStringLiteral("RemoveVirtualDisplay"), id);
|
||||
QDBusReply<QVariantList> list = iface.call(QStringLiteral("ListVirtualDisplays"));
|
||||
return list.isValid() ? list.value() : QVariantList{};
|
||||
}
|
||||
|
||||
void BreezyDesktopEffectConfig::renderVirtualDisplays(const QVariantList &rows) {
|
||||
auto listContainer = widget()->findChild<QWidget*>(QStringLiteral("widgetVirtualDisplayList"));
|
||||
auto listLayout = listContainer ? qobject_cast<QVBoxLayout*>(listContainer->layout()) : nullptr;
|
||||
if (!listContainer || !listLayout) return;
|
||||
|
||||
while (QLayoutItem *child = listLayout->takeAt(0)) {
|
||||
if (auto w = child->widget()) w->deleteLater();
|
||||
delete child;
|
||||
}
|
||||
|
||||
const bool hasRows = !rows.isEmpty();
|
||||
listContainer->setVisible(hasRows);
|
||||
listContainer->setEnabled(hasRows);
|
||||
|
||||
auto toMapCompat = [](const QVariant &v) -> QVariantMap {
|
||||
if (v.metaType().id() == QMetaType::QVariantMap) {
|
||||
return v.toMap();
|
||||
}
|
||||
if (v.canConvert<QDBusVariant>()) {
|
||||
const QDBusVariant dv = v.value<QDBusVariant>();
|
||||
if (dv.variant().metaType().id() == QMetaType::QVariantMap) {
|
||||
return dv.variant().toMap();
|
||||
}
|
||||
}
|
||||
if (v.metaType().id() == qMetaTypeId<QDBusArgument>()) {
|
||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||
QVariantMap map;
|
||||
arg.beginMap();
|
||||
while (!arg.atEnd()) {
|
||||
arg.beginMapEntry();
|
||||
QString key; QVariant val;
|
||||
QDBusArgument &nonConst = const_cast<QDBusArgument&>(arg);
|
||||
nonConst >> key >> val;
|
||||
arg.endMapEntry();
|
||||
map.insert(key, val);
|
||||
}
|
||||
arg.endMap();
|
||||
return map;
|
||||
}
|
||||
return QVariantMap{};
|
||||
};
|
||||
|
||||
auto unwrapValue = [](QVariant v) -> QVariant {
|
||||
if (v.canConvert<QDBusVariant>()) {
|
||||
const QDBusVariant dv = v.value<QDBusVariant>();
|
||||
return dv.variant();
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
for (const QVariant &rowVar : rows) {
|
||||
const QVariantMap row = toMapCompat(rowVar);
|
||||
const QString id = unwrapValue(row.value(QStringLiteral("id"))).toString();
|
||||
const int w = unwrapValue(row.value(QStringLiteral("width"))).toInt();
|
||||
const int h = unwrapValue(row.value(QStringLiteral("height"))).toInt();
|
||||
|
||||
QWidget *rowWidget = new QWidget(listContainer);
|
||||
auto *hl = new QHBoxLayout(rowWidget);
|
||||
hl->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto *spacer = new QWidget(rowWidget);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
hl->addWidget(spacer, 1);
|
||||
|
||||
auto *icon = new QLabel(rowWidget);
|
||||
icon->setPixmap(QIcon::fromTheme(QStringLiteral("video-display-symbolic")).pixmap(16, 16));
|
||||
icon->setContentsMargins(8, 0, 8, 0);
|
||||
hl->addWidget(icon, 0);
|
||||
|
||||
auto *idLabel = new QLabel(QStringLiteral("%1").arg(id), rowWidget);
|
||||
idLabel->setContentsMargins(8, 0, 8, 0);
|
||||
hl->addWidget(idLabel, 0);
|
||||
|
||||
auto *resLabel = new QLabel(QStringLiteral("%1x%2").arg(w).arg(h), rowWidget);
|
||||
resLabel->setContentsMargins(8, 0, 8, 0);
|
||||
hl->addWidget(resLabel, 0);
|
||||
|
||||
auto *removeBtn = new QPushButton(rowWidget);
|
||||
removeBtn->setIcon(QIcon::fromTheme(QStringLiteral("user-trash-symbolic")));
|
||||
removeBtn->setToolTip(QStringLiteral("Remove virtual display"));
|
||||
removeBtn->setObjectName(QStringLiteral("remove-virtual-display"));
|
||||
hl->addWidget(removeBtn, 0);
|
||||
|
||||
connect(removeBtn, &QPushButton::clicked, this, [this, id]() {
|
||||
auto list = dbusRemoveVirtualDisplay(id);
|
||||
renderVirtualDisplays(list);
|
||||
});
|
||||
|
||||
listLayout->addWidget(rowWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void BreezyDesktopEffectConfig::updateDriverEnabled()
|
||||
{
|
||||
auto configJsonOpt = XRDriverIPC::instance().retrieveConfig();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
#include <QString>
|
||||
|
||||
#include "ui_breezydesktopeffectkcm.h"
|
||||
|
||||
|
|
@ -39,6 +42,12 @@ private:
|
|||
void setRequestInProgress(std::initializer_list<QObject*> widgets, bool inProgress);
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
// Virtual display DBus helpers and UI rendering
|
||||
QVariantList dbusListVirtualDisplays() const;
|
||||
QVariantList dbusAddVirtualDisplay(int w, int h) const;
|
||||
QVariantList dbusRemoveVirtualDisplay(const QString &id) const;
|
||||
void renderVirtualDisplays(const QVariantList &rows);
|
||||
|
||||
::Ui::BreezyDesktopEffectConfig ui;
|
||||
|
||||
KConfigWatcher::Ptr m_configWatcher;
|
||||
|
|
@ -47,5 +56,6 @@ private:
|
|||
QString m_connectedDeviceBrand;
|
||||
QString m_connectedDeviceModel;
|
||||
QTimer m_statePollTimer; // periodic driver state polling
|
||||
QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling
|
||||
bool m_licenseLoading = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QWidget" name="widgetVirtualDisplayButtons">
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
|
|
@ -193,7 +193,20 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QWidget" name="widgetVirtualDisplayList">
|
||||
<property name="visible"><bool>false</bool></property>
|
||||
<property name="enabled"><bool>false</bool></property>
|
||||
<layout class="QVBoxLayout" name="layoutVirtualDisplayList">
|
||||
<property name="spacing"><number>6</number></property>
|
||||
<property name="leftMargin"><number>0</number></property>
|
||||
<property name="topMargin"><number>0</number></property>
|
||||
<property name="rightMargin"><number>0</number></property>
|
||||
<property name="bottomMargin"><number>0</number></property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="KShortcutsEditor" name="shortcutsEditor" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
|
|
|
|||
Loading…
Reference in New Issue