effects: Add Window View effect

The Window View effect is a companion effect for the task manager. It
provides the task manager a way to ask the user to pick a window among
windows identified by window ids passed to the activate() method.

The main motivation for adding this effect is to provide a QtQuick based
alternative for the present windows effect, which is needed to kill the
latter.

This change doesn't extend the overview effect to avoid repeating
mistakes of the past, i.e. adding too many (unrelated) features to the
present windows effect.

While the overview effect provides you a way to select windows, it's not
the only thing that it has. For example, it also allows changing virtual
desktops, etc.

On the other hand, the task manager doesn't need all of that. It only
needs to ask the user to pick a window among the specified windows, nothing
more.

As is, the window view effect is simply WindowHeap with a d-bus api slapped
on top of it.
This commit is contained in:
Vlad Zahorodnii 2021-11-22 16:44:30 +02:00 committed by Nate Graham
parent 2ac1f9b7d3
commit a0f456b7dc
8 changed files with 314 additions and 0 deletions

View file

@ -86,6 +86,8 @@ add_subdirectory(slideback)
add_subdirectory(slidingpopups)
add_subdirectory(thumbnailaside)
add_subdirectory(touchpoints)
add_subdirectory(windowgeometry)
add_subdirectory(windowview)
add_subdirectory(zoom)
# OpenGL-specific effects

View file

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
set(windowview_SOURCES
main.cpp
windowvieweffect.cpp
)
qt_add_dbus_adaptor(windowview_SOURCES org.kde.KWin.Effect.WindowView1.xml windowvieweffect.h KWin::WindowViewEffect)
kwin4_add_effect_module(kwin4_effect_windowview ${windowview_SOURCES})
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/windowview)

View file

@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "windowvieweffect.h"
namespace KWin
{
KWIN_EFFECT_FACTORY_SUPPORTED(WindowViewEffect,
"metadata.json.stripped",
return WindowViewEffect::supported();)
} // namespace KWin
#include "main.moc"

View file

@ -0,0 +1,13 @@
{
"KPlugin": {
"Category": "Window Management",
"Description": "Helper effect for the task manager",
"EnabledByDefault": true,
"Id": "windowview",
"License": "GPL",
"Name": "Window View"
},
"org.kde.kwin.effect": {
"internal": true
}
}

View file

@ -0,0 +1,24 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
-->
<node name="/org/kde/KWin/Effect/WindowView1">
<!--
org.kde.KWin.Effect.WindowView1:
@short_description: Application window selector
This interface provides a way to select windows interactively.
-->
<interface name="org.kde.KWin.Effect.WindowView1">
<!--
activate:
@windows: The list with window ids.
-->
<method name="activate">
<arg name="handles" type="as" direction="in" />
</method>
</interface>
</node>

View file

@ -0,0 +1,113 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtGraphicalEffects 1.12
import org.kde.kwin 3.0 as KWinComponents
import org.kde.kwin.private.effects 1.0
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: container
required property QtObject effect
required property QtObject targetScreen
required property var selectedIds
property bool animationEnabled: false
property bool organized: false
readonly property int animationDuration: PlasmaCore.Units.longDuration
function start() {
container.animationEnabled = true;
container.organized = true;
}
function stop() {
container.organized = false;
}
Keys.onEscapePressed: effect.deactivate(animationDuration);
KWinComponents.DesktopBackgroundItem {
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentVirtualDesktop
outputName: targetScreen.name
layer.enabled: true
layer.effect: FastBlur {
radius: container.organized ? 64 : 0
Behavior on radius {
NumberAnimation { duration: effect.animationDuration; easing.type: Easing.OutCubic }
}
}
}
Rectangle {
anchors.fill: parent
color: PlasmaCore.ColorScope.backgroundColor
opacity: container.organized ? 0.75 : 0
TapHandler {
onTapped: effect.deactivate(animationDuration);
}
Behavior on opacity {
OpacityAnimator { duration: animationDuration; easing.type: Easing.OutCubic }
}
}
WindowHeap {
width: targetScreen.geometry.width
height: targetScreen.geometry.height
focus: true
padding: PlasmaCore.Units.largeSpacing
animationDuration: container.animationDuration
animationEnabled: container.animationEnabled
dragEnabled: false
organized: container.organized
showOnly: selectedIds
model: KWinComponents.ClientFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentVirtualDesktop
screenName: targetScreen.name
clientModel: stackModel
windowType: ~KWinComponents.ClientFilterModel.Dock &
~KWinComponents.ClientFilterModel.Desktop &
~KWinComponents.ClientFilterModel.Notification;
}
onActivated: effect.deactivate(animationDuration);
}
Repeater {
model: KWinComponents.ClientFilterModel {
desktop: KWinComponents.Workspace.currentVirtualDesktop
screenName: targetScreen.name
clientModel: stackModel
windowType: KWinComponents.ClientFilterModel.Dock
}
KWinComponents.WindowThumbnailItem {
id: windowThumbnail
wId: model.client.internalId
x: model.client.x - targetScreen.geometry.x
y: model.client.y - targetScreen.geometry.y
visible: opacity > 0
opacity: (model.client.hidden || container.organized) ? 0 : 1
Behavior on opacity {
NumberAnimation { duration: animationDuration; easing.type: Easing.OutCubic }
}
}
}
KWinComponents.ClientModel {
id: stackModel
}
Component.onCompleted: start();
}

View file

@ -0,0 +1,92 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "windowvieweffect.h"
#include "windowview1adaptor.h"
#include <QQuickItem>
#include <QTimer>
namespace KWin
{
static const QString s_dbusServiceName = QStringLiteral("org.kde.KWin.Effect.WindowView1");
static const QString s_dbusObjectPath = QStringLiteral("/org/kde/KWin/Effect/WindowView1");
WindowViewEffect::WindowViewEffect()
: m_shutdownTimer(new QTimer(this))
{
new WindowView1Adaptor(this);
QDBusConnection::sessionBus().registerObject(s_dbusObjectPath, this);
QDBusConnection::sessionBus().registerService(s_dbusServiceName);
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &WindowViewEffect::realDeactivate);
connect(effects, &EffectsHandler::screenAboutToLock, this, &WindowViewEffect::realDeactivate);
setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/windowview/qml/main.qml"))));
}
WindowViewEffect::~WindowViewEffect()
{
QDBusConnection::sessionBus().unregisterService(s_dbusServiceName);
QDBusConnection::sessionBus().unregisterObject(s_dbusObjectPath);
}
QVariantMap WindowViewEffect::initialProperties(EffectScreen *screen)
{
return QVariantMap {
{ QStringLiteral("effect"), QVariant::fromValue(this) },
{ QStringLiteral("targetScreen"), QVariant::fromValue(screen) },
{ QStringLiteral("selectedIds"), QVariant::fromValue(m_windowIds) },
};
}
int WindowViewEffect::requestedEffectChainPosition() const
{
return 70;
}
void WindowViewEffect::activate(const QStringList &windowIds)
{
QList<QUuid> internalIds;
internalIds.reserve(windowIds.count());
for (const QString &windowId : windowIds) {
if (const auto window = effects->findWindow(windowId)) {
internalIds.append(window->internalId());
continue;
}
// On X11, the task manager can pass a list with X11 ids.
bool ok;
if (const long legacyId = windowId.toLong(&ok); ok) {
if (const auto window = effects->findWindow(legacyId)) {
internalIds.append(window->internalId());
}
}
}
if (!internalIds.isEmpty()) {
m_windowIds = internalIds;
setRunning(true);
}
}
void WindowViewEffect::deactivate(int timeout)
{
const auto screenViews = views();
for (QuickSceneView *view : screenViews) {
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
m_shutdownTimer->start(timeout);
}
void WindowViewEffect::realDeactivate()
{
setRunning(false);
}
} // namespace KWin

View file

@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwinquickeffect.h>
namespace KWin
{
class WindowViewEffect : public QuickSceneEffect
{
Q_OBJECT
public:
WindowViewEffect();
~WindowViewEffect() override;
int requestedEffectChainPosition() const override;
public Q_SLOTS:
void activate(const QStringList &windowIds);
void deactivate(int timeout);
protected:
QVariantMap initialProperties(EffectScreen *screen) override;
private:
void realDeactivate();
QTimer *m_shutdownTimer;
QList<QUuid> m_windowIds;
};
} // namespace KWin