Commit a6d72d3f authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

wayland: Introduce IdleDetector

The IdleDetector is an idle detection helper. Its purpose is to reduce
code duplication in our private KIdleTime plugin and the idle wayland
protocol, and make user activity simulation less error prone.
parent da7dad15
Pipeline #197088 passed with stage
in 25 minutes and 22 seconds
......@@ -10,8 +10,6 @@
#include "platform.h"
#include "virtualdesktops.h"
#include "wayland/display.h"
#include "wayland/idle_interface.h"
#include "wayland_server.h"
#include "window.h"
#include "workspace.h"
......@@ -20,7 +18,6 @@
using namespace KWin;
using namespace KWayland::Client;
using KWaylandServer::IdleInterface;
static const QString s_socketName = QStringLiteral("wayland_test_kwin_idle_inhbition_test-0");
......@@ -69,11 +66,8 @@ void TestIdleInhibition::cleanup()
void TestIdleInhibition::testInhibit()
{
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged);
QVERIFY(inhibitedSpy.isValid());
// no idle inhibitors at the start
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// now create window
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
......@@ -88,22 +82,23 @@ void TestIdleInhibition::testInhibit()
QVERIFY(window);
// this should inhibit our server object
QVERIFY(idle->isInhibited());
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// deleting the object should uninhibit again
inhibitor.reset();
QVERIFY(inhibitedSpy.wait());
QVERIFY(!idle->isInhibited());
Test::flushWaylandConnection(); // don't use QTRY_COMPARE(), it doesn't spin event loop
QGuiApplication::processEvents();
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// inhibit again and destroy window
QScopedPointer<Test::IdleInhibitorV1> inhibitor2(Test::createIdleInhibitorV1(surface.data()));
QVERIFY(inhibitedSpy.wait());
QVERIFY(idle->isInhibited());
Test::flushWaylandConnection();
QGuiApplication::processEvents();
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(window));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
}
void TestIdleInhibition::testDontInhibitWhenNotOnCurrentDesktop()
......@@ -114,13 +109,6 @@ void TestIdleInhibition::testDontInhibitWhenNotOnCurrentDesktop()
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
// Get reference to the idle interface.
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged);
QVERIFY(inhibitedSpy.isValid());
// Create the test window.
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
......@@ -140,30 +128,26 @@ void TestIdleInhibition::testDontInhibitWhenNotOnCurrentDesktop()
QCOMPARE(window->desktops().first(), VirtualDesktopManager::self()->desktops().first());
// This should inhibit our server object.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 1);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Switch to the second virtual desktop.
VirtualDesktopManager::self()->setCurrent(2);
// The surface is no longer visible, so the compositor don't have to honor the
// idle inhibitor object.
QVERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 2);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// Switch back to the first virtual desktop.
VirtualDesktopManager::self()->setCurrent(1);
// The test window became visible again, so the compositor has to honor the idle
// inhibitor object back again.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 3);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Destroy the test window.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(window));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
}
void TestIdleInhibition::testDontInhibitWhenMinimized()
......@@ -171,13 +155,6 @@ void TestIdleInhibition::testDontInhibitWhenMinimized()
// This test verifies that the idle inhibitor object is not honored when the
// associated surface is minimized.
// Get reference to the idle interface.
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged);
QVERIFY(inhibitedSpy.isValid());
// Create the test window.
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
......@@ -193,24 +170,20 @@ void TestIdleInhibition::testDontInhibitWhenMinimized()
QVERIFY(window);
// This should inhibit our server object.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 1);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Minimize the window, the idle inhibitor object should not be honored.
window->minimize();
QVERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 2);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// Unminimize the window, the idle inhibitor object should be honored back again.
window->unminimize();
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 3);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Destroy the test window.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(window));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
}
void TestIdleInhibition::testDontInhibitWhenUnmapped()
......@@ -218,13 +191,6 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
// This test verifies that the idle inhibitor object is not honored by KWin
// when the associated window is unmapped.
// Get reference to the idle interface.
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged);
QVERIFY(inhibitedSpy.isValid());
// Create the test window.
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
......@@ -253,8 +219,7 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
// This should inhibit our server object.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 1);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Unmap the window.
surface->attachBuffer(Buffer::Ptr());
......@@ -263,8 +228,7 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
// The surface is no longer visible, so the compositor doesn't have to honor the
// idle inhibitor object.
QVERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 2);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// Tell the compositor that we want to map the surface.
surface->commit(KWayland::Client::Surface::CommitFlag::None);
......@@ -283,14 +247,12 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
// The test window became visible again, so the compositor has to honor the idle
// inhibitor object back again.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 3);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Destroy the test window.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(window));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
}
void TestIdleInhibition::testDontInhibitWhenLeftCurrentDesktop()
......@@ -301,13 +263,6 @@ void TestIdleInhibition::testDontInhibitWhenLeftCurrentDesktop()
VirtualDesktopManager::self()->setCount(2);
QCOMPARE(VirtualDesktopManager::self()->count(), 2u);
// Get reference to the idle interface.
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
QSignalSpy inhibitedSpy(idle, &IdleInterface::inhibitedChanged);
QVERIFY(inhibitedSpy.isValid());
// Create the test window.
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
......@@ -327,30 +282,26 @@ void TestIdleInhibition::testDontInhibitWhenLeftCurrentDesktop()
QCOMPARE(window->desktops().first(), VirtualDesktopManager::self()->desktops().first());
// This should inhibit our server object.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 1);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Let the window enter the second virtual desktop.
window->enterDesktop(VirtualDesktopManager::self()->desktops().at(1));
QCOMPARE(inhibitedSpy.count(), 1);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// If the window leaves the first virtual desktop, then the associated idle
// inhibitor object should not be honored.
window->leaveDesktop(VirtualDesktopManager::self()->desktops().at(0));
QVERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 2);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
// If the window enters the first desktop, then the associated idle inhibitor
// object should be honored back again.
window->enterDesktop(VirtualDesktopManager::self()->desktops().at(0));
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 3);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{window});
// Destroy the test window.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(window));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
QCOMPARE(input()->idleInhibitors(), QList<Window *>{});
}
WAYLANDTEST_MAIN(TestIdleInhibition)
......
......@@ -60,6 +60,7 @@ target_sources(kwin PRIVATE
globalshortcuts.cpp
group.cpp
idle_inhibition.cpp
idledetector.cpp
input.cpp
input_event.cpp
input_event_spy.cpp
......
......@@ -9,7 +9,7 @@
*/
#include "idle_inhibition.h"
#include "deleted.h"
#include "wayland/idle_interface.h"
#include "input.h"
#include "wayland/surface_interface.h"
#include "window.h"
#include "workspace.h"
......@@ -22,9 +22,8 @@ using KWaylandServer::SurfaceInterface;
namespace KWin
{
IdleInhibition::IdleInhibition(IdleInterface *idle)
: QObject(idle)
, m_idle(idle)
IdleInhibition::IdleInhibition(QObject *parent)
: QObject(parent)
{
// Workspace is created after the wayland server is initialized.
connect(kwinApp(), &Application::workspaceCreated, this, &IdleInhibition::slotWorkspaceCreated);
......@@ -58,24 +57,13 @@ void IdleInhibition::registerClient(Window *client)
void IdleInhibition::inhibit(Window *client)
{
if (isInhibited(client)) {
// already inhibited
return;
}
m_idleInhibitors << client;
m_idle->inhibit();
input()->addIdleInhibitor(client);
// TODO: notify powerdevil?
}
void IdleInhibition::uninhibit(Window *client)
{
auto it = std::find(m_idleInhibitors.begin(), m_idleInhibitors.end(), client);
if (it == m_idleInhibitors.end()) {
// not inhibited
return;
}
m_idleInhibitors.erase(it);
m_idle->uninhibit();
input()->removeIdleInhibitor(client);
}
void IdleInhibition::update(Window *client)
......
......@@ -13,13 +13,6 @@
#include <QObject>
#include <QVector>
namespace KWaylandServer
{
class IdleInterface;
}
using KWaylandServer::IdleInterface;
namespace KWin
{
class Window;
......@@ -28,20 +21,11 @@ class IdleInhibition : public QObject
{
Q_OBJECT
public:
explicit IdleInhibition(IdleInterface *idle);
explicit IdleInhibition(QObject *parent = nullptr);
~IdleInhibition() override;
void registerClient(Window *client);
bool isInhibited() const
{
return !m_idleInhibitors.isEmpty();
}
bool isInhibited(Window *client) const
{
return m_idleInhibitors.contains(client);
}
private Q_SLOTS:
void slotWorkspaceCreated();
void slotDesktopChanged();
......@@ -51,8 +35,6 @@ private:
void uninhibit(Window *client);
void update(Window *client);
IdleInterface *m_idle;
QVector<Window *> m_idleInhibitors;
QMap<Window *, QMetaObject::Connection> m_connections;
};
}
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "idledetector.h"
#include "input.h"
namespace KWin
{
IdleDetector::IdleDetector(std::chrono::milliseconds timeout, QObject *parent)
: QObject(parent)
, m_timer(new QTimer(this))
{
m_timer->setSingleShot(true);
m_timer->setInterval(timeout);
connect(m_timer, &QTimer::timeout, this, &IdleDetector::idle);
m_timer->start();
input()->addIdleDetector(this);
}
IdleDetector::~IdleDetector()
{
if (input()) {
input()->removeIdleDetector(this);
}
}
bool IdleDetector::isInhibited() const
{
return m_isInhibited;
}
void IdleDetector::setInhibited(bool inhibited)
{
if (m_isInhibited == inhibited) {
return;
}
m_isInhibited = inhibited;
if (inhibited) {
if (!m_timer->isActive()) {
Q_EMIT resumed();
}
m_timer->stop();
} else {
m_timer->start();
}
}
void IdleDetector::activity()
{
if (!m_isInhibited) {
if (!m_timer->isActive()) {
Q_EMIT resumed();
}
m_timer->start();
}
}
} // namespace KWin
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
#include <QTimer>
namespace KWin
{
class KWIN_EXPORT IdleDetector : public QObject
{
Q_OBJECT
public:
explicit IdleDetector(std::chrono::milliseconds timeout, QObject *parent = nullptr);
~IdleDetector() override;
void activity();
bool isInhibited() const;
void setInhibited(bool inhibited);
Q_SIGNALS:
void idle();
void resumed();
private:
QTimer *m_timer;
bool m_isInhibited = false;
};
} // namespace KWin
......@@ -17,6 +17,7 @@
#include "gestures.h"
#include "globalshortcuts.h"
#include "hide_cursor_spy.h"
#include "idledetector.h"
#include "input_event.h"
#include "input_event_spy.h"
#include "inputbackend.h"
......@@ -2846,7 +2847,7 @@ public:
private:
void notifyActivity()
{
waylandServer()->simulateUserActivity();
input()->simulateUserActivity();
}
};
......@@ -3146,6 +3147,49 @@ Qt::MouseButtons InputRedirection::qtButtonStates() const
return m_pointer->buttons();
}
void InputRedirection::simulateUserActivity()
{
for (IdleDetector *idleDetector : std::as_const(m_idleDetectors)) {
idleDetector->activity();
}
}
void InputRedirection::addIdleDetector(IdleDetector *detector)
{
Q_ASSERT(!m_idleDetectors.contains(detector));
detector->setInhibited(!m_idleInhibitors.isEmpty());
m_idleDetectors.append(detector);
}
void InputRedirection::removeIdleDetector(IdleDetector *detector)
{
m_idleDetectors.removeOne(detector);
}
QList<Window *> InputRedirection::idleInhibitors() const
{
return m_idleInhibitors;
}
void InputRedirection::addIdleInhibitor(Window *inhibitor)
{
if (!m_idleInhibitors.contains(inhibitor)) {
m_idleInhibitors.append(inhibitor);
for (IdleDetector *idleDetector : std::as_const(m_idleDetectors)) {
idleDetector->setInhibited(true);
}
}
}
void InputRedirection::removeIdleInhibitor(Window *inhibitor)
{
if (m_idleInhibitors.removeOne(inhibitor) && m_idleInhibitors.isEmpty()) {
for (IdleDetector *idleDetector : std::as_const(m_idleDetectors)) {
idleDetector->setInhibited(false);
}
}
}
Window *InputRedirection::findToplevel(const QPoint &pos)
{
if (!Workspace::self()) {
......
......@@ -32,6 +32,7 @@ class QWheelEvent;
namespace KWin
{
class IdleDetector;
class Window;
class GlobalShortcutsManager;
class InputEventFilter;
......@@ -166,6 +167,15 @@ public:
*/
void uninstallInputEventSpy(InputEventSpy *spy);
void simulateUserActivity();
void addIdleDetector(IdleDetector *detector);
void removeIdleDetector(IdleDetector *detector);
QList<Window *> idleInhibitors() const;
void addIdleInhibitor(Window *inhibitor);
void removeIdleInhibitor(Window *inhibitor);
Window *findToplevel(const QPoint &pos);
Window *findManagedToplevel(const QPoint &pos);
GlobalShortcutsManager *shortcuts() const
......@@ -324,6 +334,8 @@ private:
QList<InputBackend *> m_inputBackends;
QList<InputDevice *> m_inputDevices;
QList<IdleDetector *> m_idleDetectors;
QList<Window *> m_idleInhibitors;
WindowSelectorFilter *m_windowSelector = nullptr;
QVector<InputEventFilter *> m_filters;
......
......@@ -7,12 +7,8 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "poller.h"
#include <KIdleTime>
#include "wayland/idle_interface.h"
#include "wayland/seat_interface.h"
#include "wayland_server.h"
#include "idledetector.h"
#include "input.h"
namespace KWin
{
......@@ -22,8 +18,6 @@ KWinIdleTimePoller::KWinIdleTimePoller(QObject *parent)
{
}
KWinIdleTimePoller::~KWinIdleTimePoller() = default;
bool KWinIdleTimePoller::isAvailable()
{
return true;
......@@ -31,110 +25,55 @@ bool KWinIdleTimePoller::isAvailable()
bool KWinIdleTimePoller::setUpPoller()
{
connect(waylandServer()->idle(), &KWaylandServer::IdleInterface::inhibitedChanged, this, &KWinIdleTimePoller::onInhibitedChanged);
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::timestampChanged, this, &KWinIdleTimePoller::onTimestampChanged);
return true;
}
void KWinIdleTimePoller::unloadPoller()
{
if (waylandServer() && waylandServer()->idle()) {
disconnect(waylandServer()->idle(), &KWaylandServer::IdleInterface::inhibitedChanged, this, &KWinIdleTimePoller::onInhibitedChanged);
disconnect(waylandServer()->seat(), &KWaylandServer::SeatInterface::timestampChanged, this, &KWinIdleTimePoller::onTimestampChanged);
}
qDeleteAll(m_timeouts);
m_timeouts.clear();
m_idling = false;
}
void KWinIdleTimePoller::addTimeout(int newTimeout)
void KWinIdleTimePoller::addTimeout(int nextTimeout)
{
if (m_timeouts.contains(newTimeout)) {
if (m_timeouts.contains(nextTimeout)) {
return;
}
auto timer = new QTimer();
timer->setInterval(newTimeout);
timer->setSingleShot(true);
timer->callOnTimeout(this, [newTimeout, this]() {
m_idling = true;
Q_EMIT timeoutReached(newTimeout);
auto detector = new IdleDetector(std::chrono::milliseconds(nextTimeout), this);
m_timeouts.insert(nextTimeout, detector);