Implement the layer-shell v1 protocol
The layer-shell protocol allows wayland clients to create surfaces that can be used for building desktop environment components such as panels, notifications, etc. The support for the plasma-shell protocol will be dropped once plasma in all its entirety is ported to the layer-shell protocol.master
parent
7be4ab97e4
commit
d3cca65d39
|
@ -476,6 +476,8 @@ set(kwin_SRCS
|
|||
keyboard_repeat.cpp
|
||||
killwindow.cpp
|
||||
layers.cpp
|
||||
layershellv1client.cpp
|
||||
layershellv1integration.cpp
|
||||
libinput/connection.cpp
|
||||
libinput/context.cpp
|
||||
libinput/device.cpp
|
||||
|
|
|
@ -987,7 +987,7 @@ protected:
|
|||
*/
|
||||
void removeTransientFromList(AbstractClient* cl);
|
||||
|
||||
Layer belongsToLayer() const;
|
||||
virtual Layer belongsToLayer() const;
|
||||
virtual bool belongsToDesktop() const;
|
||||
void invalidateLayer();
|
||||
bool isActiveFullScreen() const;
|
||||
|
|
|
@ -13,6 +13,10 @@ ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
|
|||
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml
|
||||
BASENAME input-method-unstable-v1
|
||||
)
|
||||
ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
|
||||
PROTOCOL protocols/wlr-layer-shell-unstable-v1.xml
|
||||
BASENAME wlr-layer-shell-unstable-v1
|
||||
)
|
||||
add_library(KWinIntegrationTestFramework STATIC ${KWinIntegrationTestFramework_SOURCES})
|
||||
target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test Wayland::Client)
|
||||
|
||||
|
@ -63,6 +67,7 @@ integrationTest(WAYLAND_ONLY NAME testKeymapCreationFailure SRCS keymap_creation
|
|||
integrationTest(WAYLAND_ONLY NAME testShowingDesktop SRCS showing_desktop_test.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testDontCrashUseractionsMenu SRCS dont_crash_useractions_menu.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testLayerShellV1Client SRCS layershellv1client_test.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testXdgShellClientRules SRCS xdgshellclient_rules_test.cpp)
|
||||
integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp)
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
// KWayland
|
||||
#include <KWayland/Client/xdgshell.h>
|
||||
|
||||
#include "qwayland-wlr-layer-shell-unstable-v1.h"
|
||||
|
||||
namespace KWayland
|
||||
{
|
||||
namespace Client
|
||||
|
@ -83,6 +85,28 @@ namespace Test
|
|||
|
||||
class MockInputMethod;
|
||||
|
||||
class LayerShellV1 : public QtWayland::zwlr_layer_shell_v1
|
||||
{
|
||||
public:
|
||||
~LayerShellV1() override;
|
||||
};
|
||||
|
||||
class LayerSurfaceV1 : public QObject, public QtWayland::zwlr_layer_surface_v1
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~LayerSurfaceV1() override;
|
||||
|
||||
protected:
|
||||
void zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) override;
|
||||
void zwlr_layer_surface_v1_closed() override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequested();
|
||||
void configureRequested(quint32 serial, const QSize &size);
|
||||
};
|
||||
|
||||
enum class AdditionalWaylandInterface {
|
||||
Seat = 1 << 0,
|
||||
Decoration = 1 << 1,
|
||||
|
@ -95,7 +119,8 @@ enum class AdditionalWaylandInterface {
|
|||
XdgDecoration = 1 << 8,
|
||||
OutputManagement = 1 << 9,
|
||||
TextInputManagerV2 = 1 << 10,
|
||||
InputMethodV1 = 1 << 11
|
||||
InputMethodV1 = 1 << 11,
|
||||
LayerShellV1 = 1 << 12,
|
||||
};
|
||||
Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface)
|
||||
/**
|
||||
|
@ -129,6 +154,7 @@ KWayland::Client::AppMenuManager *waylandAppMenuManager();
|
|||
KWayland::Client::XdgDecorationManager *xdgDecorationManager();
|
||||
KWayland::Client::OutputManagement *waylandOutputManagement();
|
||||
KWayland::Client::TextInputManager *waylandTextInputManager();
|
||||
QVector<KWayland::Client::Output *> waylandOutputs();
|
||||
|
||||
bool waitForWaylandPointer();
|
||||
bool waitForWaylandTouch();
|
||||
|
@ -139,6 +165,12 @@ void flushWaylandConnection();
|
|||
KWayland::Client::Surface *createSurface(QObject *parent = nullptr);
|
||||
KWayland::Client::SubSurface *createSubSurface(KWayland::Client::Surface *surface,
|
||||
KWayland::Client::Surface *parentSurface, QObject *parent = nullptr);
|
||||
|
||||
LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface,
|
||||
const QString &scope,
|
||||
KWayland::Client::Output *output = nullptr,
|
||||
LayerShellV1::layer layer = LayerShellV1::layer_top);
|
||||
|
||||
enum class XdgShellSurfaceType {
|
||||
XdgShellStable
|
||||
};
|
||||
|
|
|
@ -0,0 +1,584 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kwin_wayland_test.h"
|
||||
#include "abstract_client.h"
|
||||
#include "abstract_output.h"
|
||||
#include "main.h"
|
||||
#include "platform.h"
|
||||
#include "screens.h"
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <KWayland/Client/output.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
Q_DECLARE_METATYPE(QMargins)
|
||||
Q_DECLARE_METATYPE(KWin::Layer)
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
static const QString s_socketName = QStringLiteral("wayland_test_kwin_layershellv1client-0");
|
||||
|
||||
class LayerShellV1ClientTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
void testOutput_data();
|
||||
void testOutput();
|
||||
void testAnchor_data();
|
||||
void testAnchor();
|
||||
void testMargins_data();
|
||||
void testMargins();
|
||||
void testLayer_data();
|
||||
void testLayer();
|
||||
void testPlacementArea_data();
|
||||
void testPlacementArea();
|
||||
void testFill_data();
|
||||
void testFill();
|
||||
void testStack();
|
||||
void testFocus();
|
||||
void testActivate_data();
|
||||
void testActivate();
|
||||
void testUnmap();
|
||||
};
|
||||
|
||||
void LayerShellV1ClientTest::initTestCase()
|
||||
{
|
||||
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
||||
QVERIFY(applicationStartedSpy.isValid());
|
||||
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
|
||||
QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
|
||||
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
|
||||
|
||||
kwinApp()->start();
|
||||
QVERIFY(applicationStartedSpy.wait());
|
||||
QCOMPARE(screens()->count(), 2);
|
||||
QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024));
|
||||
QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024));
|
||||
waylandServer()->initWorkspace();
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::init()
|
||||
{
|
||||
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::LayerShellV1));
|
||||
|
||||
screens()->setCurrent(0);
|
||||
Cursors::self()->mouse()->setPos(QPoint(640, 512));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::cleanup()
|
||||
{
|
||||
Test::destroyWaylandConnection();
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testOutput_data()
|
||||
{
|
||||
QTest::addColumn<int>("screenId");
|
||||
|
||||
QTest::addRow("first output") << 0;
|
||||
QTest::addRow("second output") << 1;
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testOutput()
|
||||
{
|
||||
// Fetch the wl_output object.
|
||||
QFETCH(int, screenId);
|
||||
KWayland::Client::Output *output = Test::waylandOutputs().value(screenId);
|
||||
QVERIFY(output);
|
||||
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test"), output));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the client is on the requested screen.
|
||||
QVERIFY(output->geometry().contains(client->frameGeometry()));
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testAnchor_data()
|
||||
{
|
||||
QTest::addColumn<int>("anchor");
|
||||
QTest::addColumn<QRect>("expectedGeometry");
|
||||
|
||||
QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left)
|
||||
<< QRect(0, 450, 280, 124);
|
||||
|
||||
QTest::addRow("top left") << (Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_left)
|
||||
<< QRect(0, 0, 280, 124);
|
||||
|
||||
QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top)
|
||||
<< QRect(500, 0, 280, 124);
|
||||
|
||||
QTest::addRow("top right") << (Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_right)
|
||||
<< QRect(1000, 0, 280, 124);
|
||||
|
||||
QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right)
|
||||
<< QRect(1000, 450, 280, 124);
|
||||
|
||||
QTest::addRow("bottom right") << (Test::LayerSurfaceV1::anchor_bottom |
|
||||
Test::LayerSurfaceV1::anchor_right)
|
||||
<< QRect(1000, 900, 280, 124);
|
||||
|
||||
QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom)
|
||||
<< QRect(500, 900, 280, 124);
|
||||
|
||||
QTest::addRow("bottom left") << (Test::LayerSurfaceV1::anchor_bottom |
|
||||
Test::LayerSurfaceV1::anchor_left)
|
||||
<< QRect(0, 900, 280, 124);
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testAnchor()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(int, anchor);
|
||||
shellSurface->set_anchor(anchor);
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
QCOMPARE(requestedSize, QSize(280, 124));
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(280, 124), Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the client is placed at expected location.
|
||||
QTEST(client->frameGeometry(), "expectedGeometry");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testMargins_data()
|
||||
{
|
||||
QTest::addColumn<int>("anchor");
|
||||
QTest::addColumn<QMargins>("margins");
|
||||
QTest::addColumn<QRect>("expectedGeometry");
|
||||
|
||||
QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left)
|
||||
<< QMargins(100, 0, 0, 0)
|
||||
<< QRect(100, 450, 280, 124);
|
||||
|
||||
QTest::addRow("top left") << (Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_left)
|
||||
<< QMargins(100, 200, 0, 0)
|
||||
<< QRect(100, 200, 280, 124);
|
||||
|
||||
QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top)
|
||||
<< QMargins(0, 200, 0, 0)
|
||||
<< QRect(500, 200, 280, 124);
|
||||
|
||||
QTest::addRow("top right") << (Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_right)
|
||||
<< QMargins(0, 200, 300, 0)
|
||||
<< QRect(700, 200, 280, 124);
|
||||
|
||||
QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right)
|
||||
<< QMargins(0, 0, 300, 0)
|
||||
<< QRect(700, 450, 280, 124);
|
||||
|
||||
QTest::addRow("bottom right") << (Test::LayerSurfaceV1::anchor_bottom |
|
||||
Test::LayerSurfaceV1::anchor_right)
|
||||
<< QMargins(0, 0, 300, 400)
|
||||
<< QRect(700, 500, 280, 124);
|
||||
|
||||
QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom)
|
||||
<< QMargins(0, 0, 0, 400)
|
||||
<< QRect(500, 500, 280, 124);
|
||||
|
||||
QTest::addRow("bottom left") << (Test::LayerSurfaceV1::anchor_bottom |
|
||||
Test::LayerSurfaceV1::anchor_left)
|
||||
<< QMargins(100, 0, 0, 400)
|
||||
<< QRect(100, 500, 280, 124);
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testMargins()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(QMargins, margins);
|
||||
QFETCH(int, anchor);
|
||||
shellSurface->set_anchor(anchor);
|
||||
shellSurface->set_margin(margins.top(), margins.right(), margins.bottom(), margins.left());
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the client is placed at expected location.
|
||||
QTEST(client->frameGeometry(), "expectedGeometry");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testLayer_data()
|
||||
{
|
||||
QTest::addColumn<int>("protocolLayer");
|
||||
QTest::addColumn<Layer>("compositorLayer");
|
||||
|
||||
QTest::addRow("overlay") << int(Test::LayerShellV1::layer_overlay) << UnmanagedLayer;
|
||||
QTest::addRow("top") << int(Test::LayerShellV1::layer_top) << AboveLayer;
|
||||
QTest::addRow("bottom") << int(Test::LayerShellV1::layer_bottom) << BelowLayer;
|
||||
QTest::addRow("background") << int(Test::LayerShellV1::layer_background) << DesktopLayer;
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testLayer()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(int, protocolLayer);
|
||||
shellSurface->set_layer(protocolLayer);
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the client is placed at expected location.
|
||||
QTEST(client->layer(), "compositorLayer");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testPlacementArea_data()
|
||||
{
|
||||
QTest::addColumn<int>("anchor");
|
||||
QTest::addColumn<int>("exclusiveZone");
|
||||
QTest::addColumn<QRect>("placementArea");
|
||||
|
||||
QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left) << 300 << QRect(300, 0, 980, 1024);
|
||||
QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top) << 300 << QRect(0, 300, 1280, 724);
|
||||
QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right) << 300 << QRect(0, 0, 980, 1024);
|
||||
QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom) << 300 << QRect(0, 0, 1280, 724);
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testPlacementArea()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(int, anchor);
|
||||
QFETCH(int, exclusiveZone);
|
||||
shellSurface->set_anchor(anchor);
|
||||
shellSurface->set_exclusive_zone(exclusiveZone);
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the work area has been adjusted.
|
||||
QTEST(workspace()->clientArea(PlacementArea, client), "placementArea");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testFill_data()
|
||||
{
|
||||
QTest::addColumn<int>("anchor");
|
||||
QTest::addColumn<QSize>("desiredSize");
|
||||
QTest::addColumn<QRect>("expectedGeometry");
|
||||
|
||||
QTest::addRow("horizontal") << (Test::LayerSurfaceV1::anchor_left |
|
||||
Test::LayerSurfaceV1::anchor_right)
|
||||
<< QSize(0, 124)
|
||||
<< QRect(0, 450, 1280, 124);
|
||||
|
||||
QTest::addRow("vertical") << (Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_bottom)
|
||||
<< QSize(280, 0)
|
||||
<< QRect(500, 0, 280, 1024);
|
||||
|
||||
QTest::addRow("all") << (Test::LayerSurfaceV1::anchor_left |
|
||||
Test::LayerSurfaceV1::anchor_top |
|
||||
Test::LayerSurfaceV1::anchor_right |
|
||||
Test::LayerSurfaceV1::anchor_bottom)
|
||||
<< QSize(0, 0)
|
||||
<< QRect(0, 0, 1280, 1024);
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testFill()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(int, anchor);
|
||||
QFETCH(QSize, desiredSize);
|
||||
shellSurface->set_anchor(anchor);
|
||||
shellSurface->set_size(desiredSize.width(), desiredSize.height());
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Verify that the client is placed at expected location.
|
||||
QTEST(client->frameGeometry(), "expectedGeometry");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testStack()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface1(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface1(Test::createLayerSurfaceV1(surface1.data(), QStringLiteral("test")));
|
||||
|
||||
QScopedPointer<KWayland::Client::Surface> surface2(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface2(Test::createLayerSurfaceV1(surface2.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
shellSurface1->set_anchor(Test::LayerSurfaceV1::anchor_left);
|
||||
shellSurface1->set_size(80, 124);
|
||||
shellSurface1->set_exclusive_zone(80);
|
||||
surface1->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
shellSurface2->set_anchor(Test::LayerSurfaceV1::anchor_left);
|
||||
shellSurface2->set_size(200, 124);
|
||||
shellSurface2->set_exclusive_zone(200);
|
||||
surface2->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surfaces.
|
||||
QSignalSpy configureRequestedSpy1(shellSurface1.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QSignalSpy configureRequestedSpy2(shellSurface2.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy2.wait());
|
||||
const QSize requestedSize1 = configureRequestedSpy1.last().at(1).toSize();
|
||||
const QSize requestedSize2 = configureRequestedSpy2.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface1->ack_configure(configureRequestedSpy1.last().at(0).toUInt());
|
||||
AbstractClient *client1 = Test::renderAndWaitForShown(surface1.data(), requestedSize1, Qt::red);
|
||||
QVERIFY(client1);
|
||||
|
||||
shellSurface2->ack_configure(configureRequestedSpy2.last().at(0).toUInt());
|
||||
AbstractClient *client2 = Test::renderAndWaitForShown(surface2.data(), requestedSize2, Qt::red);
|
||||
QVERIFY(client2);
|
||||
|
||||
// Check that the second layer surface is placed next to the first.
|
||||
QCOMPARE(client1->frameGeometry(), QRect(0, 450, 80, 124));
|
||||
QCOMPARE(client2->frameGeometry(), QRect(80, 450, 200, 124));
|
||||
|
||||
// Check that the work area has been adjusted accordingly.
|
||||
QCOMPARE(workspace()->clientArea(PlacementArea, client1), QRect(280, 0, 1000, 1024));
|
||||
QCOMPARE(workspace()->clientArea(PlacementArea, client2), QRect(280, 0, 1000, 1024));
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface1.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client1));
|
||||
shellSurface2.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client2));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testFocus()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
shellSurface->set_keyboard_interactivity(1);
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// The layer surface must be focused when it's mapped.
|
||||
QVERIFY(client->isActive());
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testActivate_data()
|
||||
{
|
||||
QTest::addColumn<int>("layer");
|
||||
QTest::addColumn<bool>("active");
|
||||
|
||||
QTest::addRow("overlay") << int(Test::LayerShellV1::layer_overlay) << true;
|
||||
QTest::addRow("top") << int(Test::LayerShellV1::layer_top) << true;
|
||||
QTest::addRow("bottom") << int(Test::LayerShellV1::layer_bottom) << false;
|
||||
QTest::addRow("background") << int(Test::LayerShellV1::layer_background) << false;
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testActivate()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
QFETCH(int, layer);
|
||||
shellSurface->set_layer(layer);
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), requestedSize, Qt::red);
|
||||
QVERIFY(client);
|
||||
QVERIFY(!client->isActive());
|
||||
|
||||
// Try to activate the layer surface.
|
||||
shellSurface->set_keyboard_interactivity(1);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
QSignalSpy activeChangedSpy(client, &AbstractClient::activeChanged);
|
||||
QVERIFY(activeChangedSpy.isValid());
|
||||
QTEST(activeChangedSpy.wait(1000), "active");
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
void LayerShellV1ClientTest::testUnmap()
|
||||
{
|
||||
// Create a layer shell surface.
|
||||
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
QScopedPointer<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.data(), QStringLiteral("test")));
|
||||
|
||||
// Set the initial state of the layer surface.
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the compositor to position the surface.
|
||||
QSignalSpy configureRequestedSpy(shellSurface.data(), &Test::LayerSurfaceV1::configureRequested);
|
||||
QVERIFY(configureRequestedSpy.isValid());
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
|
||||
// Map the layer surface.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(280, 124), Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Unmap the layer surface.
|
||||
surface->attachBuffer(KWayland::Client::Buffer::Ptr());
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
|
||||
// Notify the compositor that we want to map the layer surface.
|
||||
shellSurface->set_size(280, 124);
|
||||
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
||||
|
||||
// Wait for the configure event.
|
||||
QVERIFY(configureRequestedSpy.wait());
|
||||
|
||||
// Map the layer surface back.
|
||||
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
||||
client = Test::renderAndWaitForShown(surface.data(), QSize(280, 124), Qt::red);
|
||||
QVERIFY(client);
|
||||
|
||||
// Destroy the client.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowDestroyed(client));
|
||||
}
|
||||
|
||||
} // namespace KWin
|
||||
|
||||
WAYLANDTEST_MAIN(KWin::LayerShellV1ClientTest)
|
||||
#include "layershellv1client_test.moc"
|
|
@ -0,0 +1,325 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_layer_shell_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2017 Drew DeVault
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="3">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
rendered with a defined z-depth respective to each other. They may also be
|
||||
anchored to the edges and corners of a screen and specify input handling
|
||||
semantics. This interface should be suitable for the implementation of
|
||||
many desktop shell components, and a broad number of other applications
|
||||
that interact with the desktop.
|
||||
</description>
|
||||
|
||||
<request name="get_layer_surface">
|
||||
<description summary="create a layer_surface from a surface">
|
||||
Create a layer surface for an existing surface. This assigns the role of
|
||||
layer_surface, or raises a protocol error if another role is already
|
||||
assigned.
|
||||
|
||||
Creating a layer surface from a wl_surface which has a buffer attached
|
||||
or committed is a client error, and any attempts by a client to attach
|
||||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
After creating a layer_surface object and setting it up, the client
|
||||
must perform an initial commit without any buffer attached.
|
||||
The compositor will reply with a layer_surface.configure event.
|
||||
The client must acknowledge it and is then allowed to attach a buffer
|
||||
to map the surface.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
||||
Clients can specify a namespace that defines the purpose of the layer
|
||||
surface.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||
<arg name="scope" type="string" summary="namespace for the layer surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="layer">
|
||||
<description summary="available layers for surfaces">
|
||||
These values indicate which layers a surface can be rendered in. They
|
||||
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
will typically be rendered between the bottom and top layers.
|
||||
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
Multiple surfaces can share a single layer, and ordering within a
|
||||
single layer is undefined.
|
||||
</description>
|
||||
|
||||
<entry name="background" value="0"/>
|
||||
<entry name="bottom" value="1"/>
|
||||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="destroy" type="destructor" since="3">
|
||||
<description summary="destroy the layer_shell object">
|
||||
This request indicates that the client will not use the layer_shell
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="3">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
environment.
|
||||
|
||||
Layer surface state (layer, size, anchor, exclusive zone,
|
||||
margin, interactivity) is double-buffered, and will be applied at the
|
||||
time wl_surface.commit of the corresponding wl_surface is called.
|
||||
|
||||
Attaching a null buffer to a layer surface unmaps it.
|
||||
|
||||
Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
compositor until it is explicitly mapped again. The layer_surface
|
||||
returns to the state it had right after layer_shell.get_layer_surface.
|
||||
The client can re-map the surface by performing a commit without any
|
||||
buffer attached, waiting for a configure event and handling it as usual.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
<description summary="sets the size of the surface">
|
||||
Sets the size of the surface in surface-local coordinates. The
|
||||
compositor will display the surface centered with respect to its
|
||||
anchors.
|
||||
|
||||
If you pass 0 for either value, the compositor will assign it and
|
||||
inform you of the assignment in the configure event. You must set your
|
||||
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
protocol error. Both values are 0 by default.
|
||||
|
||||
Size is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="set_anchor">
|
||||
<description summary="configures the anchor point of the surface">
|
||||
Requests that the compositor anchor the surface to the specified edges
|
||||
and corners. If two orthogonal edges are specified (e.g. 'top' and
|
||||
'left'), then the anchor point will be the intersection of the edges
|
||||
(e.g. the top left corner of the output); otherwise the anchor point
|
||||
will be centered on that edge, or in the center if none is specified.
|
||||
|
||||
Anchor is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="anchor" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
|
||||
<request name="set_exclusive_zone">
|
||||
<description summary="configures the exclusive geometry of this surface">
|
||||
Requests that the compositor avoids occluding an area with other
|
||||
surfaces. The compositor's use of this information is
|
||||
implementation-dependent - do not assume that this region will not
|
||||
actually be occluded.
|
||||
|
||||
A positive value is only meaningful if the surface is anchored to one
|
||||
edge or an edge and both perpendicular edges. If the surface is not
|
||||
anchored, anchored to only two perpendicular edges (a corner), anchored
|
||||
to only two parallel edges or anchored to all edges, a positive value
|
||||
will be treated the same as zero.
|
||||
|
||||
A positive zone is the distance from the edge in surface-local
|
||||
coordinates to consider exclusive.
|
||||
|
||||
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
how they should interact with surfaces that do. If set to zero, the
|
||||
surface indicates that it would like to be moved to avoid occluding
|
||||
surfaces with a positive exclusive zone. If set to -1, the surface
|
||||
indicates that it would not like to be moved to accommodate for other
|
||||
surfaces, and the compositor should extend it all the way to the edges
|
||||
it is anchored to.
|
||||
|
||||
For example, a panel might set its exclusive zone to 10, so that
|
||||
maximized shell surfaces are not shown on top of it. A notification
|
||||
might set its exclusive zone to 0, so that it is moved to avoid
|
||||
occluding the panel, but shell surfaces are shown underneath it. A
|
||||
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
they stretch below or over the panel.
|
||||
|
||||
The default value is 0.
|
||||
|
||||
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="zone" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_margin">
|
||||
<description summary="sets a margin from the anchor point">
|
||||
Requests that the surface be placed some distance away from the anchor
|
||||
point on the output, in surface-local coordinates. Setting this value
|
||||
for edges you are not anchored to has no effect.
|
||||
|
||||
The exclusive zone includes the margin.
|
||||
|
||||
Margin is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="top" type="int"/>
|
||||
<arg name="right" type="int"/>
|
||||
<arg name="bottom" type="int"/>
|
||||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set to 1 to request that the seat send keyboard events to this layer
|
||||
surface. For layers below the shell surface layer, the seat will use
|
||||
normal focus semantics. For layers above the shell surface layers, the
|
||||
seat will always give exclusive keyboard focus to the top-most layer
|
||||
which has keyboard interactivity set to true.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Events is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
should have been created via xdg_surface::get_popup with the parent set
|
||||
to NULL, and this request must be invoked before committing the popup's
|
||||
initial state.
|
||||
|
||||
See the documentation of xdg_popup for more details about what an
|
||||
xdg_popup is and how it is used.
|
||||
</description>
|
||||
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||
</request>
|
||||
|
||||
<request name="ack_configure">
|
||||
<description summary="ack a configure event">
|
||||
When a configure event is received, if a client commits the
|
||||
surface in response to the configure event, then the client
|
||||
must make an ack_configure request sometime before the commit
|
||||
request, passing along the serial of the configure event.
|
||||
|
||||
If the client receives multiple configure events before it
|
||||
can respond to one, it only has to ack the last configure event.
|
||||
|
||||
A client is not required to commit immediately after sending
|
||||
an ack_configure request - it may even ack_configure several times
|
||||
before its next surface commit.
|
||||
|
||||
A client may send multiple ack_configure requests before committing, but
|
||||
only the last request sent before a commit indicates which configure
|
||||
event the client really is responding to.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the layer_surface">
|
||||
This request destroys the layer surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="configure">
|
||||
<description summary="suggest a surface change">
|
||||
The configure event asks the client to resize its surface.
|
||||
|
||||
Clients should arrange their surface for the new states, and then send
|
||||
an ack_configure request with the serial sent in this configure event at
|
||||
some point before committing the new surface.
|
||||
|
||||
The client is free to dismiss all but the last configure event it
|
||||
received.
|
||||
|
||||
The width and height arguments specify the size of the window in
|
||||
surface-local coordinates.
|
||||
|
||||
The size is a hint, in the sense that the client is free to ignore it if
|
||||
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
surface will be centered on this axis.
|
||||
|
||||
If the width or height arguments are zero, it means the client should
|
||||
decide its own window dimension.
|
||||
</description>
|
||||
<arg name="serial" type="uint"/>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="surface should be closed">
|
||||
The closed event is sent by the compositor when the surface will no
|
||||
longer be shown. The output may have been destroyed or the user may
|
||||
have asked for it to be removed. Further changes to the surface will be
|
||||
ignored. The client should destroy the resource after receiving this
|
||||
event, and create a new surface if they so choose.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_layer" since="2">
|
||||
<description summary="change the layer of the surface">
|
||||
Change the layer that the surface is rendered on.
|
||||
|
||||
Layer is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -54,6 +54,26 @@ namespace KWin
|
|||
namespace Test
|
||||
{
|
||||
|
||||
LayerShellV1::~LayerShellV1()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
LayerSurfaceV1::~LayerSurfaceV1()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void LayerSurfaceV1::zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height)
|
||||
{
|
||||
emit configureRequested(serial, QSize(width, height));
|
||||
}
|
||||
|
||||
void LayerSurfaceV1::zwlr_layer_surface_v1_closed()
|
||||
{
|
||||
emit closeRequested();
|
||||
}
|
||||
|
||||
static struct {
|
||||
ConnectionThread *connection = nullptr;
|
||||
EventQueue *queue = nullptr;
|
||||
|
@ -78,6 +98,7 @@ static struct {
|
|||
QtWayland::zwp_input_panel_v1 *inputPanelV1 = nullptr;
|
||||
MockInputMethod *inputMethodV1 = nullptr;
|
||||
QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr;
|
||||
LayerShellV1 *layerShellV1 = nullptr;
|
||||
} s_waylandConnection;
|
||||
|
||||
class MockInputMethod : public QtWayland::zwp_input_method_v1
|
||||
|
@ -186,6 +207,12 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
|
|||
s_waylandConnection.inputPanelV1 = new QtWayland::zwp_input_panel_v1(*registry, name, version);
|
||||
}
|
||||
}
|
||||
if (flags & AdditionalWaylandInterface::LayerShellV1) {
|
||||
if (interface == QByteArrayLiteral("zwlr_layer_shell_v1")) {
|
||||
s_waylandConnection.layerShellV1 = new LayerShellV1();
|
||||
s_waylandConnection.layerShellV1->init(*registry, name, version);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QSignalSpy allAnnounced(registry, &Registry::interfacesAnnounced);
|
||||
|
@ -332,6 +359,8 @@ void destroyWaylandConnection()
|
|||
s_waylandConnection.textInputManager = nullptr;
|
||||
delete s_waylandConnection.inputPanelV1;
|
||||
s_waylandConnection.inputPanelV1 = nullptr;
|
||||
delete s_waylandConnection.layerShellV1;
|
||||
s_waylandConnection.layerShellV1 = nullptr;
|
||||
if (s_waylandConnection.thread) {
|
||||
QSignalSpy spy(s_waylandConnection.connection, &QObject::destroyed);
|
||||
s_waylandConnection.connection->deleteLater();
|
||||
|
@ -421,6 +450,10 @@ TextInputManager *waylandTextInputManager()
|
|||
return s_waylandConnection.textInputManager;
|
||||
}
|
||||
|
||||
QVector<KWayland::Client::Output *> waylandOutputs()
|
||||
{
|
||||
return s_waylandConnection.outputs;
|
||||
}
|
||||
|
||||
bool waitForWaylandPointer()
|
||||
{
|
||||
|
@ -531,6 +564,25 @@ SubSurface *createSubSurface(Surface *surface, Surface *parentSurface, QObject *
|
|||
return s;
|
||||
}
|
||||
|
||||
LayerSurfaceV1 *createLayerSurfaceV1(Surface *surface, const QString &scope, Output *output, LayerShellV1::layer layer)
|
||||
{
|
||||
LayerShellV1 *shell = s_waylandConnection.layerShellV1;
|
||||
if (!shell) {
|
||||
qWarning() << "Could not create a layer surface because the layer shell global is not bound";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct ::wl_output *nativeOutput = nullptr;
|
||||
if (output) {
|
||||
nativeOutput = *output;
|
||||
}
|
||||
|
||||
LayerSurfaceV1 *shellSurface = new LayerSurfaceV1();
|
||||
shellSurface->init(shell->get_layer_surface(*surface, nativeOutput, layer, scope));
|
||||
|
||||
return shellSurface;
|
||||
}
|
||||
|
||||
XdgShellSurface *createXdgShellStableSurface(Surface *surface, QObject *parent, CreationSetup creationSetup)
|
||||
{
|
||||
if (!s_waylandConnection.xdgShellStable) {
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "layershellv1client.h"
|
||||
#include "abstract_output.h"
|
||||
#include "layershellv1integration.h"
|
||||
#include "deleted.h"
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <KWaylandServer/layershell_v1_interface.h>
|
||||
#include <KWaylandServer/output_interface.h>
|
||||
#include <KWaylandServer/surface_interface.h>
|
||||
|
||||
using namespace KWaylandServer;
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
static NET::WindowType scopeToType(const QString &scope)
|
||||
{
|
||||
static const QHash<QString, NET::WindowType> scopeToType {
|
||||
{ QStringLiteral("desktop"), NET::Desktop },
|
||||
{ QStringLiteral("dock"), NET::Dock },
|
||||
{ QStringLiteral("crititical-notification"), NET::CriticalNotification },
|
||||
{ QStringLiteral("notification"), NET::Notification },
|
||||
{ QStringLiteral("tooltip"), NET::Tooltip },
|
||||
{ QStringLiteral("on-screen-display"), NET::OnScreenDisplay },
|
||||
{ QStringLiteral("dialog"), NET::Dialog },
|
||||
{ QStringLiteral("splash"), NET::Splash },
|
||||
{ QStringLiteral("utility"), NET::Utility },
|
||||
};
|
||||
return scopeToType.value(scope.toLower(), NET::Normal);
|
||||
}
|
||||
|
||||
LayerShellV1Client::LayerShellV1Client(LayerSurfaceV1Interface *shellSurface,
|
||||
AbstractOutput *output,
|
||||
LayerShellV1Integration *integration)
|
||||
: WaylandClient(shellSurface->surface())
|
||||
, m_output(output)
|
||||
, m_integration(integration)
|
||||
, m_shellSurface(shellSurface)
|
||||
, m_windowType(scopeToType(shellSurface->scope()))
|
||||
{
|
||||
setSkipSwitcher(!isDesktop());
|
||||
setSkipPager(true);
|
||||
setSkipTaskbar(true);
|
||||
setSizeSyncMode(SyncMode::Async);
|
||||
setPositionSyncMode(SyncMode::Sync);
|
||||
setupCompositing();
|
||||
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::aboutToBeDestroyed,
|
||||
this, &LayerShellV1Client::destroyClient);
|
||||
connect(shellSurface->surface(), &SurfaceInterface::aboutToBeDestroyed,
|
||||
this, &LayerShellV1Client::destroyClient);
|
||||
|
||||
connect(output, &AbstractOutput::geometryChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(output, &AbstractOutput::destroyed,
|
||||
this, &LayerShellV1Client::handleOutputDestroyed);
|
||||
|
||||
connect(shellSurface->surface(), &SurfaceInterface::sizeChanged,
|
||||
this, &LayerShellV1Client::handleSizeChanged);
|
||||
connect(shellSurface->surface(), &SurfaceInterface::unmapped,
|
||||
this, &LayerShellV1Client::handleUnmapped);
|
||||
connect(shellSurface->surface(), &SurfaceInterface::committed,
|
||||
this, &LayerShellV1Client::handleCommitted);
|
||||
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::desiredSizeChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::layerChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::marginsChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::anchorChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::exclusiveZoneChanged,
|
||||
this, &LayerShellV1Client::scheduleRearrange);
|
||||
connect(shellSurface, &LayerSurfaceV1Interface::acceptsFocusChanged,
|
||||
this, &LayerShellV1Client::handleAcceptsFocusChanged);
|
||||
}
|
||||
|
||||
LayerSurfaceV1Interface *LayerShellV1Client::shellSurface() const
|
||||
{
|
||||
return m_shellSurface;
|
||||
}
|
||||
|
||||
AbstractOutput *LayerShellV1Client::output() const
|
||||
{
|
||||
return m_output;
|
||||
}
|
||||
|
||||
void LayerShellV1Client::scheduleRearrange()
|
||||
{
|
||||
m_integration->scheduleRearrange();
|
||||
}
|
||||
|
||||
NET::WindowType LayerShellV1Client::windowType(bool, int) const
|
||||
{
|
||||
return m_windowType;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isPlaceable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isCloseable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isMovable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isMovableAcrossScreens() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isResizable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::isInitialPositionSet() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::takeFocus()
|
||||
{
|
||||
setActive(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::wantsInput() const
|
||||
{
|
||||
return acceptsFocus() && readyForPainting();
|
||||
}
|
||||
|
||||
StrutRect LayerShellV1Client::strutRect(StrutArea area) const
|
||||
{
|
||||
switch (area) {
|
||||
case StrutAreaLeft:
|
||||
if (m_shellSurface->exclusiveEdge() == Qt::LeftEdge) {
|
||||
return StrutRect(x(), y(), m_shellSurface->exclusiveZone(), height(), StrutAreaLeft);
|
||||
}
|
||||
return StrutRect();
|
||||
case StrutAreaRight:
|
||||
if (m_shellSurface->exclusiveEdge() == Qt::RightEdge) {
|
||||
return StrutRect(x() + width() - m_shellSurface->exclusiveZone(), y(),
|
||||
m_shellSurface->exclusiveZone(), height(), StrutAreaRight);
|
||||
}
|
||||
return StrutRect();
|
||||
case StrutAreaTop:
|
||||
if (m_shellSurface->exclusiveEdge() == Qt::TopEdge) {
|
||||
return StrutRect(x(), y(), width(), m_shellSurface->exclusiveZone(), StrutAreaTop);
|
||||
}
|
||||
return StrutRect();
|
||||
case StrutAreaBottom:
|
||||
if (m_shellSurface->exclusiveEdge() == Qt::BottomEdge) {
|
||||
return StrutRect(x(), y() + height() - m_shellSurface->exclusiveZone(),
|
||||
width(), m_shellSurface->exclusiveZone(), StrutAreaBottom);
|
||||
}
|
||||
return StrutRect();
|
||||
default:
|
||||
return StrutRect();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::hasStrut() const
|
||||
{
|
||||
return m_shellSurface->exclusiveZone() > 0;
|
||||
}
|
||||
|
||||
void LayerShellV1Client::destroyClient()
|
||||
{
|
||||
markAsZombie();
|
||||
cleanTabBox();
|
||||
Deleted *deleted = Deleted::create(this);
|
||||
emit windowClosed(this, deleted);
|
||||
StackingUpdatesBlocker blocker(workspace());
|
||||
cleanGrouping();
|
||||
waylandServer()->removeClient(this);
|
||||
deleted->unrefWindow();
|
||||
scheduleRearrange();
|
||||
delete this;
|
||||
}
|
||||
|
||||
void LayerShellV1Client::closeWindow()
|
||||
{
|
||||
m_shellSurface->sendClosed();
|
||||
}
|
||||
|
||||
Layer LayerShellV1Client::belongsToLayer() const
|
||||
{
|
||||
if (!isNormalWindow()) {
|
||||
return WaylandClient::belongsToLayer();
|
||||
}
|
||||
switch (m_shellSurface->layer()) {
|
||||
case LayerSurfaceV1Interface::BackgroundLayer:
|
||||
return DesktopLayer;
|
||||
case LayerSurfaceV1Interface::BottomLayer:
|
||||
return BelowLayer;
|
||||
case LayerSurfaceV1Interface::TopLayer:
|
||||
return AboveLayer;
|
||||
case LayerSurfaceV1Interface::OverlayLayer:
|
||||
return UnmanagedLayer;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerShellV1Client::acceptsFocus() const
|
||||
{
|
||||
return m_shellSurface->acceptsFocus();
|
||||
}
|
||||
|
||||
void LayerShellV1Client::addDamage(const QRegion ®ion)
|
||||
{
|
||||
addRepaint(region);
|
||||
WaylandClient::addDamage(region);
|
||||
}
|
||||
|
||||
void LayerShellV1Client::requestGeometry(const QRect &rect)
|
||||
{
|
||||
WaylandClient::requestGeometry(rect);
|
||||
m_shellSurface->sendConfigure(rect.size());
|
||||
}
|
||||
|
||||
void LayerShellV1Client::handleSizeChanged()
|
||||
{
|
||||
updateGeometry(QRect(pos(), clientSizeToFrameSize(surface()->size())));
|
||||
scheduleRearrange();
|
||||
}
|
||||
|
||||
void LayerShellV1Client::handleUnmapped()
|
||||
{
|
||||
m_integration->recreateClient(shellSurface());
|
||||
}
|
||||
|
||||
void LayerShellV1Client::handleCommitted()
|
||||
{
|
||||
if (surface()->buffer()) {
|
||||
updateDepth();
|
||||
setReadyForPainting();
|
||||
}
|
||||
}
|
||||
|
||||
void LayerShellV1Client::handleAcceptsFocusChanged()
|
||||
{
|
||||
switch (m_shellSurface->layer()) {
|
||||
case LayerSurfaceV1Interface::TopLayer:
|
||||
case LayerSurfaceV1Interface::OverlayLayer:
|
||||
if (wantsInput()) {
|
||||
workspace()->activateClient(this);
|
||||
}
|
||||
break;
|
||||
case LayerSurfaceV1Interface::BackgroundLayer:
|
||||
case LayerSurfaceV1Interface::BottomLayer:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LayerShellV1Client::handleOutputDestroyed()
|
||||
{
|
||||
closeWindow();
|
||||
destroyClient();
|
||||
}
|
||||
|
||||
} // namespace KWin
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "waylandclient.h"
|
||||
|
||||
namespace KWaylandServer
|
||||
{
|
||||
class LayerSurfaceV1Interface;
|
||||
}
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class AbstractOutput;
|
||||
class LayerShellV1Integration;
|
||||
|
||||
class LayerShellV1Client : public WaylandClient
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LayerShellV1Client(KWaylandServer::LayerSurfaceV1Interface *shellSurface,
|
||||
AbstractOutput *output,
|
||||
LayerShellV1Integration *integration);
|
||||
|
||||
KWaylandServer::LayerSurfaceV1Interface *shellSurface() const;
|
||||
AbstractOutput *output() const;
|
||||
|
||||
NET::WindowType windowType(bool direct = false, int supported_types = 0) const override;
|
||||
bool isPlaceable() const override;
|
||||
bool isCloseable() const override;
|
||||
bool isMovable() const override;
|
||||
bool isMovableAcrossScreens() const override;
|
||||
bool isResizable() const override;
|
||||
bool isInitialPositionSet() const override;
|
||||
bool takeFocus() override;
|
||||
bool wantsInput() const override;
|
||||
StrutRect strutRect(StrutArea area) const override;
|
||||
bool hasStrut() const override;
|
||||
void destroyClient() override;
|
||||
void closeWindow() override;
|
||||
|
||||
protected:
|
||||
Layer belongsToLayer() const override;
|
||||
bool acceptsFocus() const override;
|
||||
void requestGeometry(const QRect &rect) override;
|
||||
void addDamage(const QRegion ®ion) override;
|
||||
|
||||
private:
|
||||
void handleSizeChanged();
|
||||
void handleUnmapped();
|
||||
void handleCommitted();
|
||||
void handleAcceptsFocusChanged();
|
||||
void handleOutputDestroyed();
|
||||
void scheduleRearrange();
|
||||
|
||||
AbstractOutput *m_output;
|
||||
LayerShellV1Integration *m_integration;
|
||||
KWaylandServer::LayerSurfaceV1Interface *m_shellSurface;
|
||||
NET::WindowType m_windowType;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "layershellv1integration.h"
|
||||
#include "abstract_wayland_output.h"
|
||||
#include "layershellv1client.h"
|
||||
#include "platform.h"
|
||||
#include "screens.h"
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <KWaylandServer/display.h>
|
||||
#include <KWaylandServer/layershell_v1_interface.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
using namespace KWaylandServer;
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
static const Qt::Edges AnchorHorizontal = Qt::LeftEdge | Qt::RightEdge;
|
||||
static const Qt::Edges AnchorVertical = Qt::TopEdge | Qt::BottomEdge;
|
||||
|
||||
LayerShellV1Integration::LayerShellV1Integration(QObject *parent)
|
||||
: WaylandShellIntegration(parent)
|
||||
{
|
||||
LayerShellV1Interface *shell = waylandServer()->display()->createLayerShellV1(this);
|
||||
connect(shell, &KWaylandServer::LayerShellV1Interface::surfaceCreated,
|
||||
this, &LayerShellV1Integration::createClient);
|
||||
|
||||
m_rearrangeTimer = new QTimer(this);
|
||||
m_rearrangeTimer->setSingleShot(true);
|
||||
connect(m_rearrangeTimer, &QTimer::timeout, this, &LayerShellV1Integration::rearrange);
|
||||
}
|
||||
|
||||
void LayerShellV1Integration::createClient(LayerSurfaceV1Interface *shellSurface)
|
||||
{
|
||||
AbstractOutput *output = waylandServer()->findOutput(shellSurface->output());
|
||||
if (!output) {
|
||||
output = kwinApp()->platform()->findOutput(screens()->current());
|
||||
}
|
||||
if (!output) {
|
||||
qCWarning(KWIN_CORE) << "Could not find any suitable output for a layer surface";
|
||||
shellSurface->sendClosed();
|
||||
return;
|
||||
}
|
||||
|
||||
emit clientCreated(new LayerShellV1Client(shellSurface, output, this));
|
||||
}
|
||||
|
||||
void LayerShellV1Integration::recreateClient(LayerSurfaceV1Interface *shellSurface)
|
||||
{
|
||||
destroyClient(shellSurface);
|
||||
createClient(shellSurface);
|
||||
}
|
||||
|
||||
void LayerShellV1Integration::destroyClient(LayerSurfaceV1Interface *shellSurface)
|
||||
{
|
||||
const QList<AbstractClient *> clients = waylandServer()->clients();
|
||||
for (AbstractClient *client : clients) {
|
||||
LayerShellV1Client *layerShellClient = qobject_cast<LayerShellV1Client *>(client);
|
||||
if (layerShellClient && layerShellClient->shellSurface() == shellSurface) {
|
||||
layerShellClient->destroyClient();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void adjustWorkArea(const LayerSurfaceV1Interface *shellSurface, QRect *workArea)
|
||||
{
|
||||
if (shellSurface->exclusiveEdge() == Qt::LeftEdge) {
|
||||
workArea->adjust(shellSurface->leftMargin() + shellSurface->exclusiveZone(), 0, 0, 0);
|
||||
}
|
||||
if (shellSurface->exclusiveEdge() == Qt::RightEdge) {
|
||||
workArea->adjust(0, 0, -shellSurface->rightMargin() - shellSurface->exclusiveZone(), 0);
|
||||
}
|
||||
if (shellSurface->exclusiveEdge() == Qt::TopEdge) {
|
||||
workArea->adjust(0, shellSurface->topMargin() + shellSurface->exclusiveZone(), 0, 0);
|
||||
}
|
||||
if (shellSurface->exclusiveEdge() == Qt::BottomEdge) {
|
||||
workArea->adjust(0, 0, 0, -shellSurface->bottomMargin() - shellSurface->exclusiveZone());
|
||||
}
|
||||
}
|
||||
|
||||
static void rearrangeLayer(const QList<LayerShellV1Client *> &clients, QRect *workArea,
|
||||
LayerSurfaceV1Interface::Layer layer, bool exclusive)
|
||||
{
|
||||
for (LayerShellV1Client *client : clients) {
|
||||
LayerSurfaceV1Interface *shellSurface = client->shellSurface();
|
||||
|
||||
if (shellSurface->layer() != layer) {
|
||||
continue;
|
||||
}
|
||||
if (exclusive != shellSurface->exclusiveZone() > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QRect bounds;
|
||||
if (shellSurface->exclusiveZone() == -1) {
|
||||
bounds = workspace()->clientArea(ScreenArea, client);
|
||||
} else {
|
||||
bounds = *workArea;
|
||||
}
|
||||
|
||||
QRect geometry(QPoint(0, 0), shellSurface->desiredSize());
|
||||
|
||||
if ((shellSurface->anchor() & AnchorHorizontal) && geometry.width() == 0) {
|
||||
geometry.setLeft(bounds.left());
|
||||
geometry.setWidth(bounds.width());
|
||||
} else if (shellSurface->anchor() & Qt::LeftEdge) {
|
||||
geometry.moveLeft(bounds.left());
|
||||
} else if (shellSurface->anchor() & Qt::RightEdge) {
|
||||
geometry.moveRight(bounds.right());
|
||||
} else {
|
||||
geometry.moveLeft(bounds.left() + (bounds.width() - geometry.width()) / 2);
|
||||
}
|
||||
|
||||
if ((shellSurface->anchor() & AnchorVertical) && geometry.height() == 0) {
|
||||
geometry.setTop(bounds.top());
|
||||
geometry.setHeight(bounds.height());
|
||||
} else if (shellSurface->anchor() & Qt::TopEdge) {
|
||||
geometry.moveTop(bounds.top());
|
||||
} else if (shellSurface->anchor() & Qt::BottomEdge) {
|
||||
geometry.moveBottom(bounds.bottom());
|
||||
} else {
|
||||
geometry.moveTop(bounds.top() + (bounds.height() - geometry.height()) / 2);
|
||||
}
|
||||
|
||||
if ((shellSurface->anchor() & AnchorHorizontal) == AnchorHorizontal) {
|
||||
geometry.adjust(shellSurface->leftMargin(), 0, -shellSurface->rightMargin(), 0);
|
||||
} else if (shellSurface->anchor() & Qt::LeftEdge) {
|
||||
geometry.translate(shellSurface->leftMargin(), 0);
|
||||
} else if (shellSurface->anchor() & Qt::RightEdge) {
|
||||
geometry.translate(-shellSurface->rightMargin(), 0);
|
||||
}
|
||||
|
||||
if ((shellSurface->anchor() & AnchorVertical) == AnchorVertical) {
|
||||
geometry.adjust(0, shellSurface->topMargin(), 0, -shellSurface->bottomMargin());
|
||||
} else if (shellSurface->anchor() & Qt::TopEdge) {
|
||||
geometry.translate(0, shellSurface->topMargin());
|
||||
} else if (shellSurface->anchor() & Qt::BottomEdge) {
|
||||
geometry.translate(0, -shellSurface->bottomMargin());
|
||||
}
|
||||
|
||||
if (geometry.isValid()) {
|
||||
client->setFrameGeometry(geometry);
|
||||
} else {
|
||||
qCWarning(KWIN_CORE) << "Closing a layer shell client due to invalid geometry";
|
||||
client->closeWindow();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exclusive && shellSurface->exclusiveZone() > 0) {
|
||||
adjustWorkArea(shellSurface, workArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QList<LayerShellV1Client *> clientsForOutput(AbstractOutput *output)
|
||||
{
|
||||
QList<LayerShellV1Client *> result;
|
||||
const QList<AbstractClient *> clients = waylandServer()->clients();
|
||||
for (AbstractClient *client : clients) {
|
||||
LayerShellV1Client *layerShellClient = qobject_cast<LayerShellV1Client *>(client);
|
||||
if (!layerShellClient || layerShellClient->output() != output) {
|
||||
continue;
|
||||
}
|
||||
if (layerShellClient->shellSurface()->isCommitted()) {
|
||||
result.append(layerShellClient);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void rearrangeOutput(AbstractOutput *output)
|
||||
{
|
||||
const QList<LayerShellV1Client *> clients = clientsForOutput(output);
|
||||
if (!clients.isEmpty()) {
|
||||
QRect workArea = output->geometry();
|
||||
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::OverlayLayer, true);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::TopLayer, true);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::BottomLayer, true);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::BackgroundLayer, true);
|
||||
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::OverlayLayer, false);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::TopLayer, false);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::BottomLayer, false);
|
||||
rearrangeLayer(clients, &workArea, LayerSurfaceV1Interface::BackgroundLayer, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerShellV1Integration::rearrange()
|
||||
{
|
||||
m_rearrangeTimer->stop();
|
||||
|
||||
const QVector<AbstractOutput *> outputs = kwinApp()->platform()->outputs();
|
||||
for (AbstractOutput *output : outputs) {
|
||||
rearrangeOutput(output);
|
||||
}
|
||||
|
||||
workspace()->updateClientArea();
|
||||
}
|
||||
|
||||
void LayerShellV1Integration::scheduleRearrange()
|
||||
{
|
||||
m_rearrangeTimer->start();
|
||||
}
|
||||
|
||||
} // namespace KWin
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "waylandshellintegration.h"
|
||||
|
||||
namespace KWaylandServer
|
||||
{
|
||||
class LayerSurfaceV1Interface;
|
||||
}
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class LayerShellV1Integration : public WaylandShellIntegration
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LayerShellV1Integration(QObject *parent = nullptr);
|
||||
|
||||
void rearrange();
|
||||
void scheduleRearrange();
|
||||
|
||||
void createClient(KWaylandServer::LayerSurfaceV1Interface *shellSurface);
|
||||
void recreateClient(KWaylandServer::LayerSurfaceV1Interface *shellSurface);
|
||||
void destroyClient(KWaylandServer::LayerSurfaceV1Interface *shellSurface);
|
||||
|
||||
private:
|
||||
QTimer *m_rearrangeTimer;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
|
@ -49,6 +49,12 @@ StrutRect::StrutRect(QRect rect, StrutArea area)
|
|||
{
|
||||
}
|
||||
|
||||
StrutRect::StrutRect(int x, int y, int width, int height, StrutArea area)
|
||||
: QRect(x, y, width, height)
|
||||
, m_area(area)
|
||||
{
|
||||
}
|
||||
|
||||
StrutRect::StrutRect(const StrutRect& other)
|
||||
: QRect(other)
|
||||
, m_area(other.area())
|
||||
|
|
1
utils.h
1
utils.h
|
@ -62,6 +62,7 @@ class StrutRect : public QRect
|
|||
{
|
||||
public:
|
||||
explicit StrutRect(QRect rect = QRect(), StrutArea area = StrutAreaInvalid);
|
||||
StrutRect(int x, int y, int width, int height, StrutArea area = StrutAreaInvalid);
|
||||
StrutRect(const StrutRect& other);
|
||||
StrutRect &operator=(const StrutRect& other);
|
||||
inline StrutArea area() const {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "idle_inhibition.h"
|
||||
#include "inputpanelv1client.h"
|
||||
#include "screens.h"
|
||||
#include "layershellv1integration.h"
|
||||
#include "waylandxdgshellintegration.h"
|
||||
#include "workspace.h"
|
||||
#include "xdgshellclient.h"
|
||||
|
@ -366,10 +367,14 @@ bool WaylandServer::init(const QByteArray &socketName, InitializationFlags flags
|
|||
m_tabletManager = m_display->createTabletManagerInterface(m_display);
|
||||
m_keyboardShortcutsInhibitManager = m_display->createKeyboardShortcutsInhibitManagerV1(m_display);
|
||||
|
||||
auto shellIntegration = new WaylandXdgShellIntegration(this);
|
||||
connect(shellIntegration, &WaylandXdgShellIntegration::clientCreated,
|
||||
auto xdgShellIntegration = new WaylandXdgShellIntegration(this);
|
||||
connect(xdgShellIntegration, &WaylandXdgShellIntegration::clientCreated,
|
||||
this, &WaylandServer::registerXdgGenericClient);
|
||||
|
||||
auto layerShellV1Integration = new LayerShellV1Integration(this);
|
||||
connect(layerShellV1Integration, &LayerShellV1Integration::clientCreated,
|
||||
this, &WaylandServer::registerShellClient);
|
||||
|
||||
m_xdgDecorationManagerV1 = m_display->createXdgDecorationManagerV1(m_display);
|
||||
connect(m_xdgDecorationManagerV1, &XdgDecorationManagerV1Interface::decorationCreated, this,
|
||||
[this](XdgToplevelDecorationV1Interface *decoration) {
|
||||
|
|
|
@ -50,8 +50,6 @@ WaylandClient::WaylandClient(SurfaceInterface *surface)
|
|||
|
||||
connect(this, &WaylandClient::frameGeometryChanged,
|
||||
this, &WaylandClient::updateClientOutputs);
|
||||
connect(this, &WaylandClient::frameGeometryChanged,
|
||||
this, &WaylandClient::updateClientArea);
|
||||
connect(this, &WaylandClient::desktopFileNameChanged,
|
||||
this, &WaylandClient::updateIcon);
|
||||
connect(screens(), &Screens::changed, this,
|
||||
|
@ -208,13 +206,6 @@ bool WaylandClient::belongsToDesktop() const
|
|||
);
|
||||
}
|
||||
|
||||
void WaylandClient::updateClientArea()
|
||||
{
|
||||
if (hasStrut()) {
|
||||
workspace()->updateClientArea();
|
||||
}
|
||||
}
|
||||
|
||||
void WaylandClient::updateClientOutputs()
|
||||
{
|
||||
QVector<OutputInterface *> clientOutputs;
|
||||
|
|
|
@ -75,7 +75,6 @@ protected:
|
|||
virtual void updateGeometry(const QRect &rect);
|
||||
|
||||
private:
|
||||
void updateClientArea();
|
||||
void updateClientOutputs();
|
||||
void updateIcon();
|
||||
void updateResourceName();
|
||||
|
|
|
@ -1500,6 +1500,13 @@ void XdgToplevelClient::updateShowOnScreenEdge()
|
|||
}
|
||||
}
|
||||
|
||||
void XdgToplevelClient::updateClientArea()
|
||||
{
|
||||
if (hasStrut()) {
|
||||
workspace()->updateClientArea();
|
||||
}
|
||||
}
|
||||
|
||||
void XdgToplevelClient::setupWindowManagementIntegration()
|
||||
{
|
||||
if (isLockScreen()) {
|
||||
|
@ -1513,6 +1520,8 @@ void XdgToplevelClient::setupPlasmaShellIntegration()
|
|||
{
|
||||
connect(surface(), &SurfaceInterface::mapped,
|
||||
this, &XdgToplevelClient::updateShowOnScreenEdge);
|
||||
connect(this, &XdgToplevelClient::frameGeometryChanged,
|
||||
this, &XdgToplevelClient::updateClientArea);
|
||||
}
|
||||
|
||||
void XdgToplevelClient::setFullScreen(bool set, bool user)
|
||||
|
|
|
@ -186,6 +186,7 @@ private:
|
|||
void updateMaximizeMode(MaximizeMode maximizeMode);
|
||||
void updateFullScreenMode(bool set);
|
||||
void updateShowOnScreenEdge();
|
||||
void updateClientArea();
|
||||
void setupWindowManagementIntegration();
|
||||
void setupPlasmaShellIntegration();
|
||||
void sendPing(PingReason reason);
|
||||
|
|
Loading…
Reference in New Issue