From 6576a83aeea30eaba1e09ce0f7f751e706611f36 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Wed, 29 Jun 2022 13:52:52 +0200 Subject: [PATCH] Add outputlocator effect An effect that implements the "identify" functionality of the screen configuration kcm. It displays a label on each screen that identifies the screen. Doing this as a kwin effect allows to correctly handle the case when outputs are mirrored (on wayland) compared to absolute positioning of windows which end up on top of each other. --- src/effects/CMakeLists.txt | 1 + src/effects/outputlocator/CMakeLists.txt | 13 +++ src/effects/outputlocator/main.cpp | 14 +++ src/effects/outputlocator/metadata.json | 17 +++ src/effects/outputlocator/outputlocator.cpp | 100 ++++++++++++++++++ src/effects/outputlocator/outputlocator.h | 37 +++++++ src/effects/outputlocator/qml/OutputLabel.qml | 43 ++++++++ 7 files changed, 225 insertions(+) create mode 100644 src/effects/outputlocator/CMakeLists.txt create mode 100644 src/effects/outputlocator/main.cpp create mode 100644 src/effects/outputlocator/metadata.json create mode 100644 src/effects/outputlocator/outputlocator.cpp create mode 100644 src/effects/outputlocator/outputlocator.h create mode 100644 src/effects/outputlocator/qml/OutputLabel.qml diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt index 188ba12ff6..48f99bc1f2 100644 --- a/src/effects/CMakeLists.txt +++ b/src/effects/CMakeLists.txt @@ -76,6 +76,7 @@ add_subdirectory(highlightwindow) add_subdirectory(kscreen) add_subdirectory(screentransform) add_subdirectory(magiclamp) +add_subdirectory(outputlocator) add_subdirectory(overview) add_subdirectory(screenedge) add_subdirectory(showfps) diff --git a/src/effects/outputlocator/CMakeLists.txt b/src/effects/outputlocator/CMakeLists.txt new file mode 100644 index 0000000000..a3c9fb58fe --- /dev/null +++ b/src/effects/outputlocator/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 David Redondo +# +# SPDX-License-Identifier: BSD-3-Clause + + +kwin4_add_effect_module(kwin4_effect_outputlocator main.cpp outputlocator.cpp) +target_link_libraries(kwin4_effect_outputlocator PRIVATE + kwineffects + Qt::DBus + Qt::Quick +) + +install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/outputlocator) diff --git a/src/effects/outputlocator/main.cpp b/src/effects/outputlocator/main.cpp new file mode 100644 index 0000000000..90918a1efd --- /dev/null +++ b/src/effects/outputlocator/main.cpp @@ -0,0 +1,14 @@ +/* + SPDX-FileCopyrightText: 2022 David Redondo + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "outputlocator.h" + +namespace KWin +{ +KWIN_EFFECT_FACTORY(OutputLocatorEffect, "metadata.json.stripped"); +} + +#include "main.moc" diff --git a/src/effects/outputlocator/metadata.json b/src/effects/outputlocator/metadata.json new file mode 100644 index 0000000000..bee9ff99c8 --- /dev/null +++ b/src/effects/outputlocator/metadata.json @@ -0,0 +1,17 @@ +{ + "KPlugin": { + "Id": "outputlocator", + "License": "GPL", + "Category": "Appearance", + "EnabledByDefault": true, + "Authors": [ + { + "Email": "kde@david-redondo.de", + "Name": "David Redondo" + } + ] + }, + "org.kde.kwin.effect": { + "internal": true + } +} diff --git a/src/effects/outputlocator/outputlocator.cpp b/src/effects/outputlocator/outputlocator.cpp new file mode 100644 index 0000000000..e2060ff41f --- /dev/null +++ b/src/effects/outputlocator/outputlocator.cpp @@ -0,0 +1,100 @@ +/* + SPDX-FileCopyrightText: 2022 David Redondo + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "outputlocator.h" + +#include +#include + +#include +#include +#include + +namespace KWin +{ + +static QString outputName(const EffectScreen *screen) +{ + const auto screens = effects->screens(); + const bool shouldShowSerialNumber = std::any_of(screens.cbegin(), screens.cend(), [screen](const EffectScreen *other) { + return other != screen && other->manufacturer() == screen->manufacturer() && other->model() == screen->model(); + }); + QString name = screen->manufacturer() + QLatin1Char(' ') + screen->model(); + if (shouldShowSerialNumber) { + name += QLatin1Char(' ') + screen->serialNumber(); + } + return name; +} + +OutputLocatorEffect::OutputLocatorEffect(QObject *parent) + : Effect(parent) + , m_qmlUrl(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kwin/effects/outputlocator/qml/OutputLabel.qml"))) +{ + QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/Effect/OutputLocator1"), + QStringLiteral("org.kde.KWin.Effect.OutputLocator1"), + this, + QDBusConnection::ExportAllSlots); + connect(&m_showTimer, &QTimer::timeout, this, &OutputLocatorEffect::hide); +} + +bool OutputLocatorEffect::isActive() const +{ + return m_showTimer.isActive(); +} + +void OutputLocatorEffect::show() +{ + if (isActive()) { + m_showTimer.start(std::chrono::milliseconds(2500)); + return; + } + + // Needed until Qt6 https://codereview.qt-project.org/c/qt/qtdeclarative/+/361506 + m_dummyWindow = std::make_unique(); + m_dummyWindow->create(); + + const auto screens = effects->screens(); + for (const auto screen : screens) { + auto scene = new OffscreenQuickScene(this, m_dummyWindow.get()); + scene->setSource(m_qmlUrl, {{QStringLiteral("outputName"), outputName(screen)}, {QStringLiteral("resolution"), screen->geometry().size()}, {QStringLiteral("scale"), screen->devicePixelRatio()}}); + QRectF geometry(0, 0, scene->rootItem()->implicitWidth(), scene->rootItem()->implicitHeight()); + geometry.moveCenter(screen->geometry().center()); + scene->setGeometry(geometry.toRect()); + connect(scene, &OffscreenQuickView::repaintNeeded, this, [scene] { + effects->addRepaint(scene->geometry()); + }); + m_scenesByScreens.insert(screen, scene); + } + + m_showTimer.start(std::chrono::milliseconds(2500)); +} + +void OutputLocatorEffect::hide() +{ + m_showTimer.stop(); + const QRegion repaintRegion = std::accumulate(m_scenesByScreens.cbegin(), m_scenesByScreens.cend(), QRegion(), [](QRegion region, OffscreenQuickScene *scene) { + return region |= scene->geometry(); + }); + qDeleteAll(m_scenesByScreens); + m_scenesByScreens.clear(); + effects->addRepaint(repaintRegion); +} + +void OutputLocatorEffect::paintScreen(int mask, const QRegion ®ion, KWin::ScreenPaintData &data) +{ + effects->paintScreen(mask, region, data); + // On X11 all screens are painted at once + if (effects->waylandDisplay()) { + if (auto scene = m_scenesByScreens.value(data.screen())) { + effects->renderOffscreenQuickView(scene); + } + } else { + for (auto scene : m_scenesByScreens) { + effects->renderOffscreenQuickView(scene); + } + } +} +} diff --git a/src/effects/outputlocator/outputlocator.h b/src/effects/outputlocator/outputlocator.h new file mode 100644 index 0000000000..612b206140 --- /dev/null +++ b/src/effects/outputlocator/outputlocator.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2022 David Redondo + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#pragma once + +#include + +#include +#include + +namespace KWin +{ +class OffscreenQuickScene; + +class OutputLocatorEffect : public KWin::Effect +{ + Q_OBJECT + +public: + explicit OutputLocatorEffect(QObject *parent = nullptr); + void paintScreen(int mask, const QRegion ®ion, KWin::ScreenPaintData &data) override; + bool isActive() const override; + +public Q_SLOTS: + void show(); + void hide(); + +private: + QUrl m_qmlUrl; + QTimer m_showTimer; + std::unique_ptr m_dummyWindow; + QMap m_scenesByScreens; +}; +} diff --git a/src/effects/outputlocator/qml/OutputLabel.qml b/src/effects/outputlocator/qml/OutputLabel.qml new file mode 100644 index 0000000000..0e3be3e474 --- /dev/null +++ b/src/effects/outputlocator/qml/OutputLabel.qml @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2012 Dan Vratil + + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + +import QtQuick 2.1 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +Rectangle { + id: root; + + property string outputName; + property size resolution; + property double scale; + + color: theme.backgroundColor + + implicitWidth: childrenRect.width + 2 * childrenRect.x + implicitHeight: childrenRect.height + 2 * childrenRect.y + + PlasmaComponents3.Label { + id: displayName + x: units.largeSpacing * 2 + y: units.largeSpacing + font.pointSize: theme.defaultFont.pointSize * 3 + text: root.outputName; + wrapMode: Text.WordWrap; + horizontalAlignment: Text.AlignHCenter; + } + + PlasmaComponents3.Label { + id: modeLabel; + anchors { + horizontalCenter: displayName.horizontalCenter + top: displayName.bottom + } + text: resolution.width + "x" + resolution.height + + (root.scale !== 1 ? "@" + Math.round(root.scale * 100.0) + "%": "") + horizontalAlignment: Text.AlignHCenter; + } +}