diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.9.0") -set(KF5_MIN_VERSION "5.34.0") +set(KF5_MIN_VERSION "5.41.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) @@ -467,6 +467,7 @@ window_property_notify_x11_filter.cpp rootinfo_filter.cpp orientation_sensor.cpp + idle_inhibition.cpp ) if(KWIN_BUILD_TABBOX) diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -53,6 +53,7 @@ integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp) integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClientRules SRCS shell_client_rules_test.cpp) +integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) diff --git a/autotests/integration/idle_inhibition_test.cpp b/autotests/integration/idle_inhibition_test.cpp new file mode 100644 --- /dev/null +++ b/autotests/integration/idle_inhibition_test.cpp @@ -0,0 +1,129 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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) any later version. + +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 . +*********************************************************************/ +#include "kwin_wayland_test.h" +#include "shell_client.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +using KWayland::Server::IdleInterface; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_idle_inhbition_test-0"); + +class TestIdleInhibition : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testInhibit_data(); + void testInhibit(); +}; + +void TestIdleInhibition::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + waylandServer()->initWorkspace(); +} + +void TestIdleInhibition::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::IdleInhibition)); + +} + +void TestIdleInhibition::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestIdleInhibition::testInhibit_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; +} + +void TestIdleInhibition::testInhibit() +{ + auto idle = waylandServer()->display()->findChild(); + QVERIFY(idle); + QVERIFY(!idle->isInhibited()); + QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged); + QVERIFY(inhibitedSpy.isValid()); + + // now create window + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + + // not yet inhibited + QVERIFY(!idle->isInhibited()); + + // now create inhibition on window + QScopedPointer inhibitor(Test::waylandIdleInhibitManager()->createInhibitor(surface.data())); + QVERIFY(inhibitor->isValid()); + // this should inhibit our server object + QVERIFY(inhibitedSpy.wait()); + QVERIFY(idle->isInhibited()); + + // deleting the object should uninhibit again + inhibitor.reset(); + QVERIFY(inhibitedSpy.wait()); + QVERIFY(!idle->isInhibited()); + + // inhibit again and destroy window + Test::waylandIdleInhibitManager()->createInhibitor(surface.data(), surface.data()); + QVERIFY(inhibitedSpy.wait()); + QVERIFY(idle->isInhibited()); + + shellSurface.reset(); + if (type == Test::ShellSurfaceType::WlShell) { + surface.reset(); + } + QVERIFY(Test::waitForWindowDestroyed(c)); + QTRY_VERIFY(!idle->isInhibited()); + QCOMPARE(inhibitedSpy.count(), 4); +} + +WAYLANDTEST_MAIN(TestIdleInhibition) +#include "idle_inhibition_test.moc" diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -31,6 +31,7 @@ { class ConnectionThread; class Compositor; +class IdleInhibitManager; class PlasmaShell; class PlasmaWindowManagement; class PointerConstraints; @@ -81,7 +82,8 @@ Decoration = 1 << 1, PlasmaShell = 1 << 2, WindowManagement = 1 << 3, - PointerConstraints = 1 << 4 + PointerConstraints = 1 << 4, + IdleInhibition = 1 << 5 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** @@ -109,6 +111,7 @@ KWayland::Client::PlasmaShell *waylandPlasmaShell(); KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); KWayland::Client::PointerConstraints *waylandPointerConstraints(); +KWayland::Client::IdleInhibitManager *waylandIdleInhibitManager(); bool waitForWaylandPointer(); bool waitForWaylandTouch(); diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,7 @@ Registry *registry = nullptr; QThread *thread = nullptr; QVector outputs; + IdleInhibitManager *idleInhibit = nullptr; } s_waylandConnection; bool setupWaylandConnection(AdditionalWaylandInterfaces flags) @@ -187,6 +189,13 @@ return false; } } + if (flags.testFlag(AdditionalWaylandInterface::IdleInhibition)) { + s_waylandConnection.idleInhibit = registry->createIdleInhibitManager(registry->interface(Registry::Interface::IdleInhibitManagerUnstableV1).name, + registry->interface(Registry::Interface::IdleInhibitManagerUnstableV1).version); + if (!s_waylandConnection.idleInhibit->isValid()) { + return false; + } + } return true; } @@ -213,6 +222,8 @@ s_waylandConnection.xdgShellV6 = nullptr; delete s_waylandConnection.shell; s_waylandConnection.shell = nullptr; + delete s_waylandConnection.idleInhibit; + s_waylandConnection.idleInhibit = nullptr; delete s_waylandConnection.shm; s_waylandConnection.shm = nullptr; delete s_waylandConnection.queue; @@ -278,6 +289,11 @@ return s_waylandConnection.pointerConstraints; } +IdleInhibitManager *waylandIdleInhibitManager() +{ + return s_waylandConnection.idleInhibit; +} + bool waitForWaylandPointer() { if (!s_waylandConnection.seat) { diff --git a/idle_inhibition.h b/idle_inhibition.h new file mode 100644 --- /dev/null +++ b/idle_inhibition.h @@ -0,0 +1,64 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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) any later version. + +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 . +*********************************************************************/ +#pragma once + +#include +#include + +#include + +namespace KWayland +{ +namespace Server +{ +class IdleInterface; +} +} + +using KWayland::Server::IdleInterface; + +namespace KWin +{ +class ShellClient; + +class IdleInhibition : public QObject +{ + Q_OBJECT +public: + explicit IdleInhibition(IdleInterface *idle); + ~IdleInhibition(); + + void registerShellClient(ShellClient *client); + + bool isInhibited() const { + return !m_idleInhibitors.isEmpty(); + } + bool isInhibited(ShellClient *client) const { + return std::any_of(m_idleInhibitors.begin(), m_idleInhibitors.end(), [client] (auto c) { return c == client; }); + } + +private: + void inhibit(ShellClient *client); + void uninhibit(ShellClient *client); + + IdleInterface *m_idle; + QVector m_idleInhibitors; +}; +} diff --git a/idle_inhibition.cpp b/idle_inhibition.cpp new file mode 100644 --- /dev/null +++ b/idle_inhibition.cpp @@ -0,0 +1,80 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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) any later version. + +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 . +*********************************************************************/ +#include "idle_inhibition.h" +#include "deleted.h" +#include "shell_client.h" + +#include +#include + +#include + +using KWayland::Server::SurfaceInterface; + +namespace KWin +{ + +IdleInhibition::IdleInhibition(IdleInterface *idle) + : QObject(idle) + , m_idle(idle) +{ +} + +IdleInhibition::~IdleInhibition() = default; + +void IdleInhibition::registerShellClient(ShellClient *client) +{ + auto surface = client->surface(); + connect(surface, &SurfaceInterface::inhibitsIdleChanged, this, + [this, client] { + // TODO: only inhibit if the ShellClient is visible + if (client->surface()->inhibitsIdle()) { + inhibit(client); + } else { + uninhibit(client); + } + } + ); + connect(client, &ShellClient::windowClosed, this, std::bind(&IdleInhibition::uninhibit, this, client)); +} + +void IdleInhibition::inhibit(ShellClient *client) +{ + if (isInhibited(client)) { + // already inhibited + return; + } + m_idleInhibitors << client; + m_idle->inhibit(); + // TODO: notify powerdevil? +} + +void IdleInhibition::uninhibit(ShellClient *client) +{ + auto it = std::find_if(m_idleInhibitors.begin(), m_idleInhibitors.end(), [client] (auto c) { return c == client; }); + if (it == m_idleInhibitors.end()) { + // not inhibited + return; + } + m_idleInhibitors.erase(it); + m_idle->uninhibit(); +} + +} diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -21,6 +21,7 @@ #include "client.h" #include "platform.h" #include "composite.h" +#include "idle_inhibition.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -238,7 +240,11 @@ } } ); - m_display->createIdle(m_display)->create(); + auto idle = m_display->createIdle(m_display); + idle->create(); + auto idleInhibition = new IdleInhibition(idle); + connect(this, &WaylandServer::shellClientAdded, idleInhibition, &IdleInhibition::registerShellClient); + m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated,