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.
This commit is contained in:
Vlad Zahorodnii 2021-10-18 21:34:40 +03:00
parent da7dad1586
commit a6d72d3f60
17 changed files with 253 additions and 661 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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;
};
}

64
src/idledetector.cpp Normal file
View file

@ -0,0 +1,64 @@
/*
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

38
src/idledetector.h Normal file
View file

@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <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

View file

@ -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()) {

View file

@ -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;

View file

@ -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,100 +25,25 @@ 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);
connect(detector, &IdleDetector::idle, this, [this, nextTimeout] {
Q_EMIT timeoutReached(nextTimeout);
});
m_timeouts.insert(newTimeout, timer);
if (!waylandServer()->idle()->isInhibited()) {
timer->start();
}
}
void KWinIdleTimePoller::processActivity()
{
if (m_idling) {
Q_EMIT resumingFromIdle();
m_idling = false;
}
for (QTimer *timer : qAsConst(m_timeouts)) {
timer->start();
}
}
void KWinIdleTimePoller::onInhibitedChanged()
{
if (waylandServer()->idle()->isInhibited()) {
// must stop the timers
stopCatchingIdleEvents();
} else {
// resume the timers
catchIdleEvent();
// register some activity
Q_EMIT resumingFromIdle();
}
}
void KWinIdleTimePoller::onTimestampChanged()
{
if (!waylandServer()->idle()->isInhibited()) {
processActivity();
}
}
void KWinIdleTimePoller::catchIdleEvent()
{
for (QTimer *timer : qAsConst(m_timeouts)) {
timer->start();
}
}
void KWinIdleTimePoller::stopCatchingIdleEvents()
{
for (QTimer *timer : qAsConst(m_timeouts)) {
timer->stop();
}
}
void KWinIdleTimePoller::simulateUserActivity()
{
if (waylandServer()->idle()->isInhibited()) {
return;
}
processActivity();
waylandServer()->simulateUserActivity();
connect(detector, &IdleDetector::resumed, this, &KWinIdleTimePoller::resumingFromIdle);
}
void KWinIdleTimePoller::removeTimeout(int nextTimeout)
@ -132,16 +51,39 @@ void KWinIdleTimePoller::removeTimeout(int nextTimeout)
delete m_timeouts.take(nextTimeout);
}
QList<int> KWinIdleTimePoller::timeouts() const
QList< int > KWinIdleTimePoller::timeouts() const
{
return m_timeouts.keys();
}
void KWinIdleTimePoller::catchIdleEvent()
{
if (m_catchResumeTimeout) {
// already setup
return;
}
m_catchResumeTimeout = new IdleDetector(std::chrono::milliseconds::zero(), this);
connect(m_catchResumeTimeout, &IdleDetector::resumed, this, [this]() {
m_catchResumeTimeout->deleteLater();
m_catchResumeTimeout = nullptr;
Q_EMIT resumingFromIdle();
});
}
void KWinIdleTimePoller::stopCatchingIdleEvents()
{
delete m_catchResumeTimeout;
m_catchResumeTimeout = nullptr;
}
int KWinIdleTimePoller::forcePollRequest()
{
return 0;
}
void KWinIdleTimePoller::simulateUserActivity()
{
input()->simulateUserActivity();
}
#include "poller.moc"
} // namespace KWin

View file

@ -16,6 +16,8 @@
namespace KWin
{
class IdleDetector;
class KWinIdleTimePoller : public AbstractSystemPoller
{
Q_OBJECT
@ -24,7 +26,6 @@ class KWinIdleTimePoller : public AbstractSystemPoller
public:
KWinIdleTimePoller(QObject *parent = nullptr);
~KWinIdleTimePoller() override;
bool isAvailable() override;
bool setUpPoller() override;
@ -39,14 +40,9 @@ public Q_SLOTS:
void stopCatchingIdleEvents() override;
void simulateUserActivity() override;
private Q_SLOTS:
void onInhibitedChanged();
void onTimestampChanged();
private:
void processActivity();
QHash<int, QTimer *> m_timeouts;
bool m_idling = false;
IdleDetector *m_catchResumeTimeout = nullptr;
QHash<int, IdleDetector *> m_timeouts;
};
}

View file

@ -162,17 +162,6 @@ target_link_libraries( testPlasmaShell Qt::Test Qt::Gui KF5::WaylandClient kwin
add_test(NAME kwayland-testPlasmaShell COMMAND testPlasmaShell)
ecm_mark_as_test(testPlasmaShell)
########################################################
# Test Idle
########################################################
set( testIdle_SRCS
test_idle.cpp
)
add_executable(testIdle ${testIdle_SRCS})
target_link_libraries( testIdle Qt::Test Qt::Gui KF5::WaylandClient kwin)
add_test(NAME kwayland-testIdle COMMAND testIdle)
ecm_mark_as_test(testIdle)
########################################################
# Test Shadow
########################################################

View file

@ -1,271 +0,0 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QtTest>
// client
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/idle.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
// server
#include "wayland/display.h"
#include "wayland/idle_interface.h"
#include "wayland/seat_interface.h"
using namespace KWayland::Client;
using namespace KWaylandServer;
class IdleTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testTimeout();
void testSimulateUserActivity();
void testServerSimulateUserActivity();
void testIdleInhibit();
void testIdleInhibitBlocksTimeout();
private:
KWaylandServer::Display *m_display = nullptr;
SeatInterface *m_seatInterface = nullptr;
IdleInterface *m_idleInterface = nullptr;
ConnectionThread *m_connection = nullptr;
QThread *m_thread = nullptr;
EventQueue *m_queue = nullptr;
Seat *m_seat = nullptr;
Idle *m_idle = nullptr;
};
static const QString s_socketName = QStringLiteral("kwayland-test-idle-0");
void IdleTest::init()
{
delete m_display;
m_display = new KWaylandServer::Display(this);
m_display->addSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
m_display->createShm();
m_seatInterface = new SeatInterface(m_display);
m_seatInterface->setName(QStringLiteral("seat0"));
m_idleInterface = new IdleInterface(m_display);
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
QVERIFY(connectedSpy.isValid());
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
m_connection->initConnection();
QVERIFY(connectedSpy.wait());
m_queue = new EventQueue(this);
m_queue->setup(m_connection);
Registry registry;
QSignalSpy interfacesAnnouncedSpy(&registry, &Registry::interfacesAnnounced);
QVERIFY(interfacesAnnouncedSpy.isValid());
registry.setEventQueue(m_queue);
registry.create(m_connection);
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(interfacesAnnouncedSpy.wait());
m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this);
QVERIFY(m_seat->isValid());
m_idle = registry.createIdle(registry.interface(Registry::Interface::Idle).name, registry.interface(Registry::Interface::Idle).version, this);
QVERIFY(m_idle->isValid());
}
void IdleTest::cleanup()
{
#define CLEANUP(variable) \
if (variable) { \
delete variable; \
variable = nullptr; \
}
CLEANUP(m_idle)
CLEANUP(m_seat)
CLEANUP(m_queue)
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
CLEANUP(m_display)
#undef CLEANUP
// these are the children of the display
m_idleInterface = nullptr;
m_seatInterface = nullptr;
}
void IdleTest::testTimeout()
{
// this test verifies the basic functionality of a timeout, that it gets fired
// and that it resumes from idle, etc.
QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(1, m_seat));
QVERIFY(timeout->isValid());
QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
QVERIFY(idleSpy.isValid());
QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
QVERIFY(resumedFormIdleSpy.isValid());
// we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
QVERIFY(!idleSpy.wait(500));
// the default of 5 sec will now pass
QVERIFY(idleSpy.wait());
// simulate some activity
QVERIFY(resumedFormIdleSpy.isEmpty());
m_idleInterface->simulateUserActivity();
QVERIFY(resumedFormIdleSpy.wait());
timeout.reset();
m_connection->flush();
m_display->dispatchEvents();
}
void IdleTest::testSimulateUserActivity()
{
// this test verifies that simulate user activity doesn't fire the timer
QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(6000, m_seat));
QVERIFY(timeout->isValid());
QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
QVERIFY(idleSpy.isValid());
QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
QVERIFY(resumedFormIdleSpy.isValid());
m_connection->flush();
QTest::qWait(4000);
timeout->simulateUserActivity();
// waiting default five sec should fail
QVERIFY(!idleSpy.wait());
// another 2 sec should fire
QVERIFY(idleSpy.wait(2000));
// now simulating user activity should emit a resumedFromIdle
QVERIFY(resumedFormIdleSpy.isEmpty());
timeout->simulateUserActivity();
QVERIFY(resumedFormIdleSpy.wait());
timeout.reset();
m_connection->flush();
m_display->dispatchEvents();
}
void IdleTest::testServerSimulateUserActivity()
{
// this test verifies that simulate user activity doesn't fire the timer
QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(6000, m_seat));
QVERIFY(timeout->isValid());
QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
QVERIFY(idleSpy.isValid());
QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
QVERIFY(resumedFormIdleSpy.isValid());
m_connection->flush();
QTest::qWait(4000);
m_idleInterface->simulateUserActivity();
// waiting default five sec should fail
QVERIFY(!idleSpy.wait());
// another 2 sec should fire
QVERIFY(idleSpy.wait(2000));
// now simulating user activity should emit a resumedFromIdle
QVERIFY(resumedFormIdleSpy.isEmpty());
m_idleInterface->simulateUserActivity();
QVERIFY(resumedFormIdleSpy.wait());
timeout.reset();
m_connection->flush();
m_display->dispatchEvents();
}
void IdleTest::testIdleInhibit()
{
QCOMPARE(m_idleInterface->isInhibited(), false);
QSignalSpy idleInhibitedSpy(m_idleInterface, &IdleInterface::inhibitedChanged);
QVERIFY(idleInhibitedSpy.isValid());
m_idleInterface->inhibit();
QCOMPARE(m_idleInterface->isInhibited(), true);
QCOMPARE(idleInhibitedSpy.count(), 1);
m_idleInterface->inhibit();
QCOMPARE(m_idleInterface->isInhibited(), true);
QCOMPARE(idleInhibitedSpy.count(), 1);
m_idleInterface->uninhibit();
QCOMPARE(m_idleInterface->isInhibited(), true);
QCOMPARE(idleInhibitedSpy.count(), 1);
m_idleInterface->uninhibit();
QCOMPARE(m_idleInterface->isInhibited(), false);
QCOMPARE(idleInhibitedSpy.count(), 2);
}
void IdleTest::testIdleInhibitBlocksTimeout()
{
// this test verifies that a timeout does not fire when the system is inhibited
// so first inhibit
QCOMPARE(m_idleInterface->isInhibited(), false);
m_idleInterface->inhibit();
QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(1, m_seat));
QVERIFY(timeout->isValid());
QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
QVERIFY(idleSpy.isValid());
QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
QVERIFY(resumedFormIdleSpy.isValid());
// we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
QVERIFY(!idleSpy.wait(500));
// the default of 5 sec won't pass
QVERIFY(!idleSpy.wait());
// simulate some activity
QVERIFY(resumedFormIdleSpy.isEmpty());
m_seatInterface->setTimestamp(1);
// resume from idle should not fire
QVERIFY(!resumedFormIdleSpy.wait());
// let's uninhibit
m_idleInterface->uninhibit();
QCOMPARE(m_idleInterface->isInhibited(), false);
// we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
QVERIFY(!idleSpy.wait(500));
// the default of 5 sec will now pass
QVERIFY(idleSpy.wait());
// if we inhibit now it will trigger a resume from idle
QVERIFY(resumedFormIdleSpy.isEmpty());
m_idleInterface->inhibit();
QVERIFY(resumedFormIdleSpy.wait());
// let's wait again just to verify that also inhibit for already existing IdleTimeout works
QVERIFY(!idleSpy.wait(500));
QVERIFY(!idleSpy.wait());
QCOMPARE(idleSpy.count(), 1);
timeout.reset();
m_connection->flush();
m_display->dispatchEvents();
}
QTEST_GUILESS_MAIN(IdleTest)
#include "test_idle.moc"

View file

@ -7,13 +7,18 @@
#include "idle_interface_p.h"
#include "seat_interface.h"
#include "idledetector.h"
#include "input.h"
using namespace KWin;
namespace KWaylandServer
{
static const quint32 s_version = 1;
IdleInterfacePrivate::IdleInterfacePrivate(IdleInterface *_q, Display *display)
IdleInterfacePrivate::IdleInterfacePrivate(Display *display)
: QtWaylandServer::org_kde_kwin_idle(*display, s_version)
, q(_q)
{
}
@ -28,75 +33,29 @@ void IdleInterfacePrivate::org_kde_kwin_idle_get_idle_timeout(Resource *resource
return;
}
IdleTimeoutInterface *idleTimeout = new IdleTimeoutInterface(s, q, idleTimoutResource);
idleTimeouts << idleTimeout;
QObject::connect(idleTimeout, &IdleTimeoutInterface::destroyed, q, [this, idleTimeout]() {
idleTimeouts.removeOne(idleTimeout);
});
idleTimeout->setup(timeout);
new IdleTimeoutInterface(std::chrono::milliseconds(timeout), idleTimoutResource);
}
IdleInterface::IdleInterface(Display *display, QObject *parent)
: QObject(parent)
, d(new IdleInterfacePrivate(this, display))
, d(new IdleInterfacePrivate(display))
{
}
IdleInterface::~IdleInterface() = default;
void IdleInterface::inhibit()
IdleTimeoutInterface::IdleTimeoutInterface(std::chrono::milliseconds timeout, wl_resource *resource)
: QtWaylandServer::org_kde_kwin_idle_timeout(resource)
{
d->inhibitCount++;
if (d->inhibitCount == 1) {
Q_EMIT inhibitedChanged();
}
}
void IdleInterface::uninhibit()
{
d->inhibitCount--;
if (d->inhibitCount == 0) {
Q_EMIT inhibitedChanged();
}
}
bool IdleInterface::isInhibited() const
{
return d->inhibitCount > 0;
}
void IdleInterface::simulateUserActivity()
{
for (auto i : qAsConst(d->idleTimeouts)) {
i->simulateUserActivity();
}
}
IdleTimeoutInterface::IdleTimeoutInterface(SeatInterface *seat, IdleInterface *manager, wl_resource *resource)
: QObject()
, QtWaylandServer::org_kde_kwin_idle_timeout(resource)
, seat(seat)
, manager(manager)
{
connect(manager, &IdleInterface::inhibitedChanged, this, [this, manager] {
if (!timer) {
// not yet configured
return;
}
if (manager->isInhibited()) {
if (!timer->isActive()) {
send_resumed();
}
timer->stop();
} else {
timer->start();
}
auto detector = new IdleDetector(timeout, this);
connect(detector, &IdleDetector::idle, this, [this]() {
send_idle();
});
connect(detector, &IdleDetector::resumed, this, [this]() {
send_resumed();
});
}
IdleTimeoutInterface::~IdleTimeoutInterface() = default;
void IdleTimeoutInterface::org_kde_kwin_idle_timeout_release(Resource *resource)
{
wl_resource_destroy(resource->handle);
@ -111,40 +70,7 @@ void IdleTimeoutInterface::org_kde_kwin_idle_timeout_destroy_resource(Resource *
void IdleTimeoutInterface::org_kde_kwin_idle_timeout_simulate_user_activity(Resource *resource)
{
Q_UNUSED(resource)
simulateUserActivity();
}
void IdleTimeoutInterface::simulateUserActivity()
{
if (!timer) {
// not yet configured
return;
}
if (manager->isInhibited()) {
// ignored while inhibited
return;
}
if (!timer->isActive()) {
send_resumed();
}
timer->start();
input()->simulateUserActivity();
}
void IdleTimeoutInterface::setup(quint32 timeout)
{
if (timer) {
return;
}
timer = new QTimer(this);
timer->setSingleShot(true);
// less than 500 msec is not idle by definition
timer->setInterval(qMax(timeout, 500u));
QObject::connect(timer, &QTimer::timeout, this, [this] {
send_idle();
});
if (manager->isInhibited()) {
// don't start if inhibited
return;
}
timer->start();
}
}

View file

@ -42,56 +42,6 @@ public:
explicit IdleInterface(Display *display, QObject *parent = nullptr);
~IdleInterface() override;
/**
* Inhibits the IdleInterface. While inhibited no IdleTimeoutInterface interface gets
* notified about an idle timeout.
*
* This can be used to inhibit power management, screen locking, etc. directly from
* Compositor side.
*
* To resume idle timeouts invoke @link{uninhibit}. It is possible to invoke inhibit several
* times, in that case uninhibit needs to called the same amount as inhibit has been called.
* @see uninhibit
* @see isInhibited
* @see inhibitedChanged
*/
void inhibit();
/**
* Inhibits the IdleInterface. The idle timeouts are only restarted if uninhibit has been
* called the same amount as inhibit.
*
* @see inhibit
* @see isInhibited
* @see inhibitedChanged
*/
void uninhibit();
/**
* @returns Whether idle timeouts are currently inhibited
* @see inhibit
* @see uninhibit
* @see inhibitedChanged
*/
bool isInhibited() const;
/**
* Calling this method allows the Compositor to simulate user activity.
* This means the same action is performed as if the user interacted with
* an input device on the SeatInterface.
* Idle timeouts are resumed and the idle time gets restarted.
*/
void simulateUserActivity();
Q_SIGNALS:
/**
* Emitted when the system gets inhibited or uninhibited.
* @see inhibit
* @see uninhibit
* @see isInhibited
*/
void inhibitedChanged();
private:
QScopedPointer<IdleInterfacePrivate> d;
};

View file

@ -21,29 +21,18 @@ class IdleTimeoutInterface;
class IdleInterfacePrivate : public QtWaylandServer::org_kde_kwin_idle
{
public:
IdleInterfacePrivate(IdleInterface *_q, Display *display);
int inhibitCount = 0;
QVector<IdleTimeoutInterface *> idleTimeouts;
IdleInterface *q;
IdleInterfacePrivate(Display *display);
protected:
void org_kde_kwin_idle_get_idle_timeout(Resource *resource, uint32_t id, wl_resource *seat, uint32_t timeout) override;
};
class IdleTimeoutInterface : public QObject, QtWaylandServer::org_kde_kwin_idle_timeout
class IdleTimeoutInterface : public QObject, public QtWaylandServer::org_kde_kwin_idle_timeout
{
Q_OBJECT
public:
explicit IdleTimeoutInterface(SeatInterface *seat, IdleInterface *parent, wl_resource *resource);
~IdleTimeoutInterface() override;
void setup(quint32 timeout);
void simulateUserActivity();
private:
SeatInterface *seat;
IdleInterface *manager;
QTimer *timer = nullptr;
public:
explicit IdleTimeoutInterface(std::chrono::milliseconds timeout, wl_resource *resource);
protected:
void org_kde_kwin_idle_timeout_destroy_resource(Resource *resource) override;

View file

@ -756,13 +756,6 @@ bool WaylandServer::hasGlobalShortcutSupport() const
return !m_initFlags.testFlag(InitializationFlag::NoGlobalShortcuts);
}
void WaylandServer::simulateUserActivity()
{
if (m_idle) {
m_idle->simulateUserActivity();
}
}
bool WaylandServer::isKeyboardShortcutsInhibited() const
{
auto surface = seat()->focusedKeyboardSurface();

View file

@ -209,8 +209,6 @@ public:
*/
SocketPairConnection createConnection();
void simulateUserActivity();
QSet<KWaylandServer::LinuxDmaBufV1ClientBuffer *> linuxDmabufBuffers() const
{
return m_linuxDmabufBuffers;