Skip to content

Commit 6eb1255

Browse files
committed
wayland/idle-notify: add idle notify
1 parent b8fa424 commit 6eb1255

File tree

8 files changed

+329
-0
lines changed

8 files changed

+329
-0
lines changed

src/wayland/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ endif()
117117
add_subdirectory(idle_inhibit)
118118
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor)
119119

120+
add_subdirectory(idle_notify)
121+
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
122+
120123
# widgets for qmenu
121124
target_link_libraries(quickshell-wayland PRIVATE
122125
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
qt_add_library(quickshell-wayland-idle-notify STATIC
2+
proto.cpp
3+
monitor.cpp
4+
)
5+
6+
qt_add_qml_module(quickshell-wayland-idle-notify
7+
URI Quickshell.Wayland._IdleNotify
8+
VERSION 0.1
9+
DEPENDENCIES QtQuick
10+
)
11+
12+
install_qml_module(quickshell-wayland-idle-notify)
13+
14+
qs_add_module_deps_light(quickshell-wayland-idle-notify Quickshell)
15+
16+
wl_proto(wlp-idle-notify ext-idle-notify-v1 "${WAYLAND_PROTOCOLS}/staging/ext-idle-notify")
17+
18+
target_link_libraries(quickshell-wayland-idle-notify PRIVATE
19+
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
20+
wlp-idle-notify
21+
)
22+
23+
qs_module_pch(quickshell-wayland-idle-notify SET large)
24+
25+
target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-notifyplugin)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include "monitor.hpp"
2+
#include <algorithm>
3+
4+
#include <qlogging.h>
5+
#include <qscopeguard.h>
6+
#include <qtypes.h>
7+
8+
#include "proto.hpp"
9+
10+
namespace qs::wayland::idle_notify {
11+
12+
IdleMonitor::~IdleMonitor() { delete this->bNotification.value(); }
13+
14+
void IdleMonitor::onPostReload() {
15+
this->bParams.setBinding([this] {
16+
return Params {
17+
.enabled = this->bEnabled.value(),
18+
.timeout = this->bTimeout.value(),
19+
.respectInhibitors = this->bRespectInhibitors.value()
20+
};
21+
});
22+
23+
this->bIsIdle.setBinding([this] {
24+
auto* notification = this->bNotification.value();
25+
return notification ? notification->bIsIdle.value() : false;
26+
});
27+
}
28+
29+
void IdleMonitor::updateNotification() {
30+
auto* notification = this->bNotification.value();
31+
delete notification;
32+
notification = nullptr;
33+
34+
auto guard = qScopeGuard([&] { this->bNotification = notification; });
35+
36+
auto params = this->bParams.value();
37+
38+
if (params.enabled) {
39+
auto* manager = impl::IdleNotificationManager::instance();
40+
41+
if (!manager) {
42+
qWarning() << "Cannot create idle monitor as ext-idle-notify-v1 is not supported by the "
43+
"current compositor.";
44+
return;
45+
}
46+
47+
auto timeout = static_cast<quint32>(std::max(0, static_cast<int>(params.timeout * 1000)));
48+
notification = manager->createIdleNotification(timeout, params.respectInhibitors);
49+
}
50+
}
51+
52+
} // namespace qs::wayland::idle_notify
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#pragma once
2+
3+
#include <qobject.h>
4+
#include <qproperty.h>
5+
#include <qqmlintegration.h>
6+
#include <qtclasshelpermacros.h>
7+
#include <qtmetamacros.h>
8+
#include <qtypes.h>
9+
10+
#include "../../core/reload.hpp"
11+
#include "proto.hpp"
12+
13+
namespace qs::wayland::idle_notify {
14+
15+
///! Provides a notification when a wayland session is makred idle
16+
/// An idle monitor detects when the user stops providing input for a period of time.
17+
///
18+
/// > [!NOTE] Using an idle monitor requires the compositor support the [ext-idle-notify-v1] protocol.
19+
///
20+
/// [ext-idle-notify-v1]: https://wayland.app/protocols/ext-idle-notify-v1
21+
class IdleMonitor: public PostReloadHook {
22+
Q_OBJECT;
23+
QML_ELEMENT;
24+
// clang-format off
25+
/// If the idle monitor should be enabled. Defaults to true.
26+
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged);
27+
/// The amount of time in seconds the idle monitor should wait before reporting an idle state.
28+
///
29+
/// Defaults to zero, which reports idle status immediately.
30+
Q_PROPERTY(qreal timeout READ default WRITE default NOTIFY timeoutChanged BINDABLE bindableTimeout);
31+
/// When set to true, @@isIdle will depend on both user interaction and active idle inhibitors.
32+
/// When false, the value will depend solely on user interaction. Defaults to true.
33+
Q_PROPERTY(bool respectInhibitors READ default WRITE default NOTIFY respectInhibitorsChanged BINDABLE bindableRespectInhibitors);
34+
/// This property is true if the user has been idle for at least @@timeout.
35+
/// What is considered to be idle is influenced by @@respectInhibitors.
36+
Q_PROPERTY(bool isIdle READ default NOTIFY isIdleChanged BINDABLE bindableIsIdle);
37+
// clang-format on
38+
39+
public:
40+
IdleMonitor() = default;
41+
~IdleMonitor() override;
42+
Q_DISABLE_COPY_MOVE(IdleMonitor);
43+
44+
void onPostReload() override;
45+
46+
[[nodiscard]] bool isEnabled() const { return this->bNotification.value(); }
47+
void setEnabled(bool enabled) { this->bEnabled = enabled; }
48+
49+
[[nodiscard]] QBindable<qreal> bindableTimeout() { return &this->bTimeout; }
50+
[[nodiscard]] QBindable<bool> bindableRespectInhibitors() { return &this->bRespectInhibitors; }
51+
[[nodiscard]] QBindable<bool> bindableIsIdle() const { return &this->bIsIdle; }
52+
53+
signals:
54+
void enabledChanged();
55+
void timeoutChanged();
56+
void respectInhibitorsChanged();
57+
void isIdleChanged();
58+
59+
private:
60+
void updateNotification();
61+
62+
struct Params {
63+
bool enabled;
64+
qreal timeout;
65+
bool respectInhibitors;
66+
};
67+
68+
// clang-format off
69+
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bEnabled, true, &IdleMonitor::enabledChanged);
70+
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, qreal, bTimeout, &IdleMonitor::timeoutChanged);
71+
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bRespectInhibitors, true, &IdleMonitor::respectInhibitorsChanged);
72+
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, Params, bParams, &IdleMonitor::updateNotification);
73+
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, impl::IdleNotification*, bNotification);
74+
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, bool, bIsIdle, &IdleMonitor::isIdleChanged);
75+
// clang-format on
76+
};
77+
78+
} // namespace qs::wayland::idle_notify

src/wayland/idle_notify/proto.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include "proto.hpp"
2+
3+
#include <private/qwaylanddisplay_p.h>
4+
#include <private/qwaylandinputdevice_p.h>
5+
#include <private/qwaylandintegration_p.h>
6+
#include <qlogging.h>
7+
#include <qloggingcategory.h>
8+
#include <qtypes.h>
9+
#include <qwayland-ext-idle-notify-v1.h>
10+
#include <qwaylandclientextension.h>
11+
#include <wayland-ext-idle-notify-v1-client-protocol.h>
12+
13+
#include "../../core/logcat.hpp"
14+
15+
namespace qs::wayland::idle_notify {
16+
QS_LOGGING_CATEGORY(logIdleNotify, "quickshell.wayland.idle_notify", QtWarningMsg);
17+
}
18+
19+
namespace qs::wayland::idle_notify::impl {
20+
21+
IdleNotificationManager::IdleNotificationManager(): QWaylandClientExtensionTemplate(2) {
22+
this->initialize();
23+
}
24+
25+
IdleNotificationManager* IdleNotificationManager::instance() {
26+
static auto* instance = new IdleNotificationManager(); // NOLINT
27+
return instance->isInitialized() ? instance : nullptr;
28+
}
29+
30+
IdleNotification*
31+
IdleNotificationManager::createIdleNotification(quint32 timeout, bool respectInhibitors) {
32+
if (!respectInhibitors
33+
&& this->QtWayland::ext_idle_notifier_v1::version()
34+
< EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION)
35+
{
36+
qCWarning(logIdleNotify) << "Cannot ignore inhibitors for new idle notifier: Compositor does "
37+
"not support protocol version 2.";
38+
39+
respectInhibitors = true;
40+
}
41+
42+
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
43+
auto* inputDevice = display->lastInputDevice();
44+
if (inputDevice == nullptr) inputDevice = display->defaultInputDevice();
45+
if (inputDevice == nullptr) {
46+
qCCritical(logIdleNotify) << "Could not create idle notifier: No seat.";
47+
return nullptr;
48+
}
49+
50+
::ext_idle_notification_v1* notification = nullptr;
51+
if (respectInhibitors) notification = this->get_idle_notification(timeout, inputDevice->object());
52+
else notification = this->get_input_idle_notification(timeout, inputDevice->object());
53+
54+
auto* wrapper = new IdleNotification(notification);
55+
qCDebug(logIdleNotify) << "Created" << wrapper << "with timeout:" << timeout
56+
<< "respects inhibitors:" << respectInhibitors;
57+
return wrapper;
58+
}
59+
60+
IdleNotification::~IdleNotification() {
61+
qCDebug(logIdleNotify) << "Destroyed" << this;
62+
if (this->isInitialized()) this->destroy();
63+
}
64+
65+
void IdleNotification::ext_idle_notification_v1_idled() {
66+
qCDebug(logIdleNotify) << this << "has been marked idle";
67+
this->bIsIdle = true;
68+
}
69+
70+
void IdleNotification::ext_idle_notification_v1_resumed() {
71+
qCDebug(logIdleNotify) << this << "has been marked resumed";
72+
this->bIsIdle = false;
73+
}
74+
75+
} // namespace qs::wayland::idle_notify::impl

src/wayland/idle_notify/proto.hpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <qobject.h>
4+
#include <qproperty.h>
5+
#include <qtclasshelpermacros.h>
6+
#include <qtmetamacros.h>
7+
#include <qtypes.h>
8+
#include <qwayland-ext-idle-notify-v1.h>
9+
#include <qwaylandclientextension.h>
10+
#include <wayland-ext-idle-notify-v1-client-protocol.h>
11+
12+
#include "../../core/logcat.hpp"
13+
14+
namespace qs::wayland::idle_notify {
15+
QS_DECLARE_LOGGING_CATEGORY(logIdleNotify);
16+
}
17+
18+
namespace qs::wayland::idle_notify::impl {
19+
20+
class IdleNotification;
21+
22+
class IdleNotificationManager
23+
: public QWaylandClientExtensionTemplate<IdleNotificationManager>
24+
, public QtWayland::ext_idle_notifier_v1 {
25+
public:
26+
explicit IdleNotificationManager();
27+
IdleNotification* createIdleNotification(quint32 timeout, bool respectInhibitors);
28+
29+
static IdleNotificationManager* instance();
30+
};
31+
32+
class IdleNotification
33+
: public QObject
34+
, public QtWayland::ext_idle_notification_v1 {
35+
Q_OBJECT;
36+
37+
public:
38+
explicit IdleNotification(::ext_idle_notification_v1* notification)
39+
: QtWayland::ext_idle_notification_v1(notification) {}
40+
41+
~IdleNotification() override;
42+
Q_DISABLE_COPY_MOVE(IdleNotification);
43+
44+
Q_OBJECT_BINDABLE_PROPERTY(IdleNotification, bool, bIsIdle);
45+
46+
protected:
47+
void ext_idle_notification_v1_idled() override;
48+
void ext_idle_notification_v1_resumed() override;
49+
};
50+
51+
} // namespace qs::wayland::idle_notify::impl
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
import Quickshell
5+
import Quickshell.Wayland
6+
7+
FloatingWindow {
8+
color: contentItem.palette.window
9+
10+
IdleMonitor {
11+
id: monitor
12+
enabled: enabledCb.checked
13+
timeout: timeoutSb.value
14+
respectInhibitors: respectInhibitorsCb.checked
15+
}
16+
17+
ColumnLayout {
18+
Label { text: `Is idle? ${monitor.isIdle}` }
19+
20+
CheckBox {
21+
id: enabledCb
22+
text: "Enabled"
23+
checked: true
24+
}
25+
26+
CheckBox {
27+
id: respectInhibitorsCb
28+
text: "Respect Inhibitors"
29+
checked: true
30+
}
31+
32+
RowLayout {
33+
Label { text: "Timeout" }
34+
35+
SpinBox {
36+
id: timeoutSb
37+
editable: true
38+
from: 0
39+
to: 1000
40+
value: 5
41+
}
42+
}
43+
}
44+
}

src/wayland/module.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ headers = [
66
"toplevel_management/qml.hpp",
77
"screencopy/view.hpp",
88
"idle_inhibit/inhibitor.hpp",
9+
"idle_notify/monitor.hpp",
910
]
1011
-----

0 commit comments

Comments
 (0)