Commit 02a05610 authored by Martin Flöser's avatar Martin Flöser

Add windowsystem plugin for KWin's qpa

Summary:
KWindowSystem provides a plugin interface to have platform specific
implementations. So far KWin relied on the implementation in
KWayland-integration repository.

This is something I find unsuited, for the following reasons:
 * any test in KWin for functionality set through the plugin would fail
 * it's not clear what's going on where
 * in worst case some code could deadlock
 * KWin shouldn't use KWindowSystem and only a small subset is allowed
to be used

The last point needs some further explanation. KWin internally does not
and cannot use KWindowSystem. KWindowSystem (especially KWindowInfo) is
exposing information which KWin sets. It's more than weird if KWin asks
KWindowSystem for the state of a window it set itself. On X11 it's just
slow, on Wayland it can result in roundtrips to KWin itself which is
dangerous.

But due to using Plasma components we have a few areas where we use
KWindowSystem. E.g. a Plasma::Dialog sets a window type, the slide in
direction, blur and background contrast. This we want to support and
need to support. Other API elements we do not want, like for examples
the available windows. KWin internal windows either have direct access
to KWin or a scripting interface exposed providing (limited) access -
there is just no need to have this in KWindowSystem.

To make it more clear what KWin supports as API of KWindowSystem for
internal windows this change implements a stripped down version of the
kwayland-integration plugin. The main difference is that it does not use
KWayland at all, but a QWindow internal side channel.

To support this EffectWindow provides an accessor for internalWindow and
the three already mentioned effects are adjusted to read from the
internal QWindow and it's dynamic properties.

This change is a first step for a further refactoring. I plan to split
the internal window out of ShellClient into a dedicated class. I think
there are nowadays too many special cases. If it moves out there is the
question whether we really want to use Wayland for the internal windows
or whether this is just historic ballast (after all we used to use
qwayland for that in the beginning).

As the change could introduce regressions I'm targetting 5.16.

Test Plan:
new test case for window type, manual testing using Alt+Tab
for the effects integration. Sliding popups, blur and contrast worked fine.

Reviewers: #kwin

Subscribers: kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D18228
parent ef510b4e
......@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kwin_wayland_test.h"
#include "platform.h"
#include "cursor.h"
#include "effects.h"
#include "shell_client.h"
#include "screens.h"
#include "wayland_server.h"
......@@ -32,6 +33,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/surface.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shell.h>
#include <KWindowSystem>
#include <KWayland/Server/surface_interface.h>
......@@ -39,6 +41,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using namespace KWayland::Client;
Q_DECLARE_METATYPE(NET::WindowType);
namespace KWin
{
......@@ -67,6 +71,11 @@ private Q_SLOTS:
void testModifierScroll();
void testPopup();
void testScale();
void testWindowType_data();
void testWindowType();
void testChangeWindowType_data();
void testChangeWindowType();
void testEffectWindow();
};
class HelperWindow : public QRasterWindow
......@@ -712,6 +721,98 @@ void InternalWindowTest::testScale()
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
}
void InternalWindowTest::testWindowType_data()
{
QTest::addColumn<NET::WindowType>("windowType");
QTest::newRow("normal") << NET::Normal;
QTest::newRow("desktop") << NET::Desktop;
QTest::newRow("Dock") << NET::Dock;
QTest::newRow("Toolbar") << NET::Toolbar;
QTest::newRow("Menu") << NET::Menu;
QTest::newRow("Dialog") << NET::Dialog;
QTest::newRow("Utility") << NET::Utility;
QTest::newRow("Splash") << NET::Splash;
QTest::newRow("DropdownMenu") << NET::DropdownMenu;
QTest::newRow("PopupMenu") << NET::PopupMenu;
QTest::newRow("Tooltip") << NET::Tooltip;
QTest::newRow("Notification") << NET::Notification;
QTest::newRow("ComboBox") << NET::ComboBox;
QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay;
}
void InternalWindowTest::testWindowType()
{
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
HelperWindow win;
win.setGeometry(0, 0, 100, 100);
QFETCH(NET::WindowType, windowType);
KWindowSystem::setType(win.winId(), windowType);
win.show();
QVERIFY(clientAddedSpy.wait());
QTRY_COMPARE(clientAddedSpy.count(), 1);
auto internalClient = clientAddedSpy.first().first().value<ShellClient*>();
QVERIFY(internalClient);
QCOMPARE(internalClient->windowType(), windowType);
}
void InternalWindowTest::testChangeWindowType_data()
{
QTest::addColumn<NET::WindowType>("windowType");
QTest::newRow("desktop") << NET::Desktop;
QTest::newRow("Dock") << NET::Dock;
QTest::newRow("Toolbar") << NET::Toolbar;
QTest::newRow("Menu") << NET::Menu;
QTest::newRow("Dialog") << NET::Dialog;
QTest::newRow("Utility") << NET::Utility;
QTest::newRow("Splash") << NET::Splash;
QTest::newRow("DropdownMenu") << NET::DropdownMenu;
QTest::newRow("PopupMenu") << NET::PopupMenu;
QTest::newRow("Tooltip") << NET::Tooltip;
QTest::newRow("Notification") << NET::Notification;
QTest::newRow("ComboBox") << NET::ComboBox;
QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay;
}
void InternalWindowTest::testChangeWindowType()
{
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
HelperWindow win;
win.setGeometry(0, 0, 100, 100);
win.show();
QVERIFY(clientAddedSpy.wait());
QTRY_COMPARE(clientAddedSpy.count(), 1);
auto internalClient = clientAddedSpy.first().first().value<ShellClient*>();
QVERIFY(internalClient);
QCOMPARE(internalClient->windowType(), NET::Normal);
QFETCH(NET::WindowType, windowType);
KWindowSystem::setType(win.winId(), windowType);
QTRY_COMPARE(internalClient->windowType(), windowType);
KWindowSystem::setType(win.winId(), NET::Normal);
QTRY_COMPARE(internalClient->windowType(), NET::Normal);
}
void InternalWindowTest::testEffectWindow()
{
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
HelperWindow win;
win.setGeometry(0, 0, 100, 100);
win.show();
QVERIFY(clientAddedSpy.wait());
auto internalClient = clientAddedSpy.first().first().value<ShellClient*>();
QVERIFY(internalClient);
QVERIFY(internalClient->effectWindow());
QCOMPARE(internalClient->effectWindow()->internalWindow(), &win);
QCOMPARE(effects->findWindow(&win), internalClient->effectWindow());
QCOMPARE(effects->findWindow(&win)->internalWindow(), &win);
}
}
......
......@@ -184,6 +184,9 @@ void TestShellClient::testMapUnmapMap()
QVERIFY(client->property("moveable").toBool());
QVERIFY(client->property("moveableAcrossScreens").toBool());
QVERIFY(client->property("resizeable").toBool());
QCOMPARE(client->isInternal(), false);
QVERIFY(client->effectWindow());
QVERIFY(!client->effectWindow()->internalWindow());
QCOMPARE(client->internalId().isNull(), false);
const auto uuid = client->internalId();
QUuid deletedUuid;
......
......@@ -134,6 +134,9 @@ public:
KWin::EffectWindow *findWindow(KWayland::Server::SurfaceInterface *) const override {
return nullptr;
}
KWin::EffectWindow *findWindow(QWindow *w) const override {
return nullptr;
}
void *getProxy(QString) override {
return nullptr;
}
......
......@@ -59,6 +59,9 @@ public:
void closeWindow() override;
void referencePreviousWindowPixmap() override {}
void unreferencePreviousWindowPixmap() override {}
QWindow *internalWindow() const override {
return nullptr;
}
bool isDeleted() const override {
return false;
}
......
......@@ -1086,6 +1086,19 @@ EffectWindow* EffectsHandlerImpl::findWindow(KWayland::Server::SurfaceInterface
return nullptr;
}
EffectWindow *EffectsHandlerImpl::findWindow(QWindow *w) const
{
if (waylandServer()) {
if (auto c = waylandServer()->findClient(w)) {
return c->effectWindow();
}
}
if (auto u = Workspace::self()->findUnmanaged(w->winId())) {
return u->effectWindow();
}
return nullptr;
}
EffectWindowList EffectsHandlerImpl::stackingOrder() const
{
......@@ -1936,6 +1949,15 @@ EffectWindow* EffectWindowImpl::findModal()
return nullptr;
}
QWindow *EffectWindowImpl::internalWindow() const
{
auto client = qobject_cast<ShellClient*>(toplevel);
if (!client) {
return nullptr;
}
return client->internalWindow();
}
template <typename T>
EffectWindowList getMainWindows(T *c)
{
......
......@@ -134,6 +134,7 @@ public:
void stopMousePolling() override;
EffectWindow* findWindow(WId id) const override;
EffectWindow* findWindow(KWayland::Server::SurfaceInterface *surf) const override;
EffectWindow *findWindow(QWindow *w) const override;
EffectWindowList stackingOrder() const override;
void setElevatedWindow(KWin::EffectWindow* w, bool set) override;
......@@ -474,6 +475,8 @@ public:
void referencePreviousWindowPixmap() override;
void unreferencePreviousWindowPixmap() override;
QWindow *internalWindow() const override;
const Toplevel* window() const;
Toplevel* window();
......
......@@ -25,6 +25,7 @@
#include <QMatrix4x4>
#include <QLinkedList>
#include <QWindow>
#include <KWayland/Server/surface_interface.h>
#include <KWayland/Server/contrast_interface.h>
......@@ -140,6 +141,27 @@ void ContrastEffect::updateContrastRegion(EffectWindow *w)
m_colorMatrices[w] = colorMatrix(surf->contrast()->contrast(), surf->contrast()->intensity(), surf->contrast()->saturation());
}
if (auto internal = w->internalWindow()) {
const auto property = internal->property("kwin_background_region");
if (property.isValid()) {
region = property.value<QRegion>();
bool ok = false;
qreal contrast = internal->property("kwin_background_contrast").toReal(&ok);
if (!ok) {
contrast = 1.0;
}
qreal intensity = internal->property("kwin_background_intensity").toReal(&ok);
if (!ok) {
intensity = 1.0;
}
qreal saturation = internal->property("kwin_background_saturation").toReal(&ok);
if (!ok) {
saturation = 1.0;
}
m_colorMatrices[w] = colorMatrix(contrast, intensity, saturation);
}
}
//!value.isNull() full window in X11 case, surf->contrast()
//valid, full window in wayland case
if (region.isEmpty() && (!value.isNull() || (surf && surf->contrast()))) {
......@@ -163,9 +185,31 @@ void ContrastEffect::slotWindowAdded(EffectWindow *w)
}
});
}
if (auto internal = w->internalWindow()) {
internal->installEventFilter(this);
}
updateContrastRegion(w);
}
bool ContrastEffect::eventFilter(QObject *watched, QEvent *event)
{
auto internal = qobject_cast<QWindow*>(watched);
if (internal && event->type() == QEvent::DynamicPropertyChange) {
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
if (pe->propertyName() == "kwin_background_region" ||
pe->propertyName() == "kwin_background_contrast" ||
pe->propertyName() == "kwin_background_intensity" ||
pe->propertyName() == "kwin_background_saturation") {
if (auto w = effects->findWindow(internal)) {
updateContrastRegion(w);
}
}
}
return false;
}
void ContrastEffect::slotWindowDeleted(EffectWindow *w)
{
if (m_contrastChangedConnections.contains(w)) {
......
......@@ -64,6 +64,8 @@ public:
return 76;
}
bool eventFilter(QObject *watched, QEvent *event) override;
public Q_SLOTS:
void slotWindowAdded(KWin::EffectWindow *w);
void slotWindowDeleted(KWin::EffectWindow *w);
......
......@@ -29,6 +29,7 @@
#include <QLinkedList>
#include <QScreen> // for QGuiApplication
#include <QTime>
#include <QWindow>
#include <cmath> // for ceil()
#include <KWayland/Server/surface_interface.h>
......@@ -272,6 +273,13 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) const
region = surf->blur()->region();
}
if (auto internal = w->internalWindow()) {
const auto property = internal->property("kwin_blur");
if (property.isValid()) {
region = property.value<QRegion>();
}
}
//!value.isNull() full window in X11 case, surf->blur()
//valid, full window in wayland case
if (region.isEmpty() && (!value.isNull() || (surf && surf->blur()))) {
......@@ -294,6 +302,9 @@ void BlurEffect::slotWindowAdded(EffectWindow *w)
}
});
}
if (auto internal = w->internalWindow()) {
internal->installEventFilter(this);
}
updateBlurRegion(w);
}
......@@ -315,6 +326,20 @@ void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
}
}
bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
{
auto internal = qobject_cast<QWindow*>(watched);
if (internal && event->type() == QEvent::DynamicPropertyChange) {
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
if (pe->propertyName() == "kwin_blur") {
if (auto w = effects->findWindow(internal)) {
updateBlurRegion(w);
}
}
}
return false;
}
bool BlurEffect::enabledByDefault()
{
GLPlatform *gl = GLPlatform::instance();
......
......@@ -67,6 +67,8 @@ public:
return 75;
}
bool eventFilter(QObject *watched, QEvent *event) override;
public Q_SLOTS:
void slotWindowAdded(KWin::EffectWindow *w);
void slotWindowDeleted(KWin::EffectWindow *w);
......
......@@ -24,11 +24,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QApplication>
#include <QFontMetrics>
#include <QWindow>
#include <KWayland/Server/surface_interface.h>
#include <KWayland/Server/slide_interface.h>
#include <KWayland/Server/display.h>
#include <KWindowEffects>
Q_DECLARE_METATYPE(KWindowEffects::SlideFromLocation)
namespace KWin
{
......@@ -200,6 +205,11 @@ void SlidingPopupsEffect::slotWindowAdded(EffectWindow *w)
});
}
if (auto internal = w->internalWindow()) {
internal->installEventFilter(this);
setupInternalWindowSlide(w);
}
slideIn(w);
}
......@@ -377,6 +387,62 @@ void SlidingPopupsEffect::slotWaylandSlideOnShowChanged(EffectWindow* w)
}
}
void SlidingPopupsEffect::setupInternalWindowSlide(EffectWindow *w)
{
if (!w) {
return;
}
auto internal = w->internalWindow();
if (!internal) {
return;
}
const QVariant slideProperty = internal->property("kwin_slide");
if (!slideProperty.isValid()) {
return;
}
AnimationData &animData = m_animationsData[w];
switch (slideProperty.value<KWindowEffects::SlideFromLocation>()) {
case KWindowEffects::BottomEdge:
animData.location = Location::Bottom;
break;
case KWindowEffects::TopEdge:
animData.location = Location::Top;
break;
case KWindowEffects::RightEdge:
animData.location = Location::Right;
break;
case KWindowEffects::LeftEdge:
animData.location = Location::Left;
break;
default:
return;
}
bool intOk = false;
animData.offset = internal->property("kwin_slide_offset").toInt(&intOk);
if (!intOk) {
animData.offset = -1;
}
animData.slideLength = 0;
animData.slideInDuration = m_slideInDuration;
animData.slideOutDuration = m_slideOutDuration;
setupAnimData(w);
}
bool SlidingPopupsEffect::eventFilter(QObject *watched, QEvent *event)
{
auto internal = qobject_cast<QWindow*>(watched);
if (internal && event->type() == QEvent::DynamicPropertyChange) {
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
if (pe->propertyName() == "kwin_slide" || pe->propertyName() == "kwin_slide_offset") {
if (auto w = effects->findWindow(internal)) {
setupInternalWindowSlide(w);
}
}
}
return false;
}
void SlidingPopupsEffect::slideIn(EffectWindow *w)
{
if (effects->activeFullScreenEffect()) {
......
......@@ -53,6 +53,8 @@ public:
int slideInDuration() const;
int slideOutDuration() const;
bool eventFilter(QObject *watched, QEvent *event) override;
private Q_SLOTS:
void slotWindowAdded(EffectWindow *w);
void slotWindowDeleted(EffectWindow *w);
......@@ -65,6 +67,7 @@ private Q_SLOTS:
private:
void setupAnimData(EffectWindow *w);
void setupInternalWindowSlide(EffectWindow *w);
long m_atom;
......
......@@ -1078,6 +1078,16 @@ public:
Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(WId id) const = 0;
Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(KWayland::Server::SurfaceInterface *surf) const = 0;
/**
* Finds the EffectWindow for the internal window @p w.
* If there is no such window @c null is returned.
*
* On Wayland this returns the internal window. On X11 it returns an Unamanged with the
* window id matching that of the provided window @p w.
*
* @since 5.16
**/
Q_SCRIPTABLE virtual KWin::EffectWindow *findWindow(QWindow *w) const = 0;
virtual EffectWindowList stackingOrder() const = 0;
// window will be temporarily painted as if being at the top of the stack
Q_SCRIPTABLE virtual void setElevatedWindow(KWin::EffectWindow* w, bool set) = 0;
......@@ -2041,6 +2051,14 @@ class KWINEFFECTS_EXPORT EffectWindow : public QObject
**/
Q_PROPERTY(bool popupWindow READ isPopupWindow CONSTANT)
/**
* KWin internal window. Specific to Wayland platform.
*
* If the EffectWindow does not reference an internal window, this property is @c null.
* @since 5.16
**/
Q_PROPERTY(QWindow *internalWindow READ internalWindow CONSTANT)
public:
/** Flags explaining why painting should be disabled */
enum {
......@@ -2324,6 +2342,11 @@ public:
**/
virtual bool isPopupWindow() const = 0;
/**
* @since 5.16
**/
virtual QWindow *internalWindow() const = 0;
/**
* Can be used to by effects to store arbitrary data in the EffectWindow.
*
......
......@@ -3,6 +3,7 @@ add_subdirectory(qpa)
add_subdirectory(idletime)
add_subdirectory(platforms)
add_subdirectory(scenes)
add_subdirectory(windowsystem)
if(KWIN_BUILD_DECORATIONS)
add_subdirectory(kdecorations)
......
set(kwindowsystem_plugin_SRCS
plugin.cpp
windoweffects.cpp
windowsystem.cpp
)
add_library(KF5WindowSystemKWinPrivatePlugin MODULE ${kwindowsystem_plugin_SRCS})
set_target_properties(KF5WindowSystemKWinPrivatePlugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/org.kde.kwindowsystem.platforms/")
target_link_libraries(KF5WindowSystemKWinPrivatePlugin
kwin
)
install(
TARGETS
KF5WindowSystemKWinPrivatePlugin
DESTINATION
${PLUGIN_INSTALL_DIR}/kf5/org.kde.kwindowsystem.platforms/
)
{
"platforms": ["wayland-org.kde.kwin.qpa"]
}
/*
* Copyright 2019 Martin Flöser <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "plugin.h"
#include "windowsystem.h"
#include "windoweffects.h"
KWindowSystemKWinPlugin::KWindowSystemKWinPlugin(QObject *parent)
: KWindowSystemPluginInterface(parent)
{
}
KWindowSystemKWinPlugin::~KWindowSystemKWinPlugin()
{
}
KWindowEffectsPrivate *KWindowSystemKWinPlugin::createEffects()
{
return new KWin::WindowEffects();
}
KWindowSystemPrivate *KWindowSystemKWinPlugin::createWindowSystem()
{
return new KWin::WindowSystem();
}
/*
* Copyright 2019 Martin Flöser <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <KWindowSystem/private/kwindowsystemplugininterface_p.h>
class KWindowSystemKWinPlugin : public KWindowSystemPluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.kwindowsystem.KWindowSystemPluginInterface" FILE "kwindowsystem.json")
Q_INTERFACES(KWindowSystemPluginInterface)
public:
explicit KWindowSystemKWinPlugin(QObject *parent = nullptr);
~KWindowSystemKWinPlugin() override;
KWindowEffectsPrivate *createEffects() override;
KWindowSystemPrivate *createWindowSystem() override;
};
/*
* Copyright 2019 Martin Flöser <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of