wayland/idle-notify: add idle notify

This commit is contained in:
outfoxxed 2025-09-04 02:44:22 -07:00
parent b8fa424f85
commit 6eb12551ba
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
8 changed files with 329 additions and 0 deletions

View file

@ -117,6 +117,9 @@ endif()
add_subdirectory(idle_inhibit) add_subdirectory(idle_inhibit)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor)
add_subdirectory(idle_notify)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
# widgets for qmenu # widgets for qmenu
target_link_libraries(quickshell-wayland PRIVATE target_link_libraries(quickshell-wayland PRIVATE
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate

View file

@ -0,0 +1,25 @@
qt_add_library(quickshell-wayland-idle-notify STATIC
proto.cpp
monitor.cpp
)
qt_add_qml_module(quickshell-wayland-idle-notify
URI Quickshell.Wayland._IdleNotify
VERSION 0.1
DEPENDENCIES QtQuick
)
install_qml_module(quickshell-wayland-idle-notify)
qs_add_module_deps_light(quickshell-wayland-idle-notify Quickshell)
wl_proto(wlp-idle-notify ext-idle-notify-v1 "${WAYLAND_PROTOCOLS}/staging/ext-idle-notify")
target_link_libraries(quickshell-wayland-idle-notify PRIVATE
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
wlp-idle-notify
)
qs_module_pch(quickshell-wayland-idle-notify SET large)
target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-notifyplugin)

View file

@ -0,0 +1,52 @@
#include "monitor.hpp"
#include <algorithm>
#include <qlogging.h>
#include <qscopeguard.h>
#include <qtypes.h>
#include "proto.hpp"
namespace qs::wayland::idle_notify {
IdleMonitor::~IdleMonitor() { delete this->bNotification.value(); }
void IdleMonitor::onPostReload() {
this->bParams.setBinding([this] {
return Params {
.enabled = this->bEnabled.value(),
.timeout = this->bTimeout.value(),
.respectInhibitors = this->bRespectInhibitors.value()
};
});
this->bIsIdle.setBinding([this] {
auto* notification = this->bNotification.value();
return notification ? notification->bIsIdle.value() : false;
});
}
void IdleMonitor::updateNotification() {
auto* notification = this->bNotification.value();
delete notification;
notification = nullptr;
auto guard = qScopeGuard([&] { this->bNotification = notification; });
auto params = this->bParams.value();
if (params.enabled) {
auto* manager = impl::IdleNotificationManager::instance();
if (!manager) {
qWarning() << "Cannot create idle monitor as ext-idle-notify-v1 is not supported by the "
"current compositor.";
return;
}
auto timeout = static_cast<quint32>(std::max(0, static_cast<int>(params.timeout * 1000)));
notification = manager->createIdleNotification(timeout, params.respectInhibitors);
}
}
} // namespace qs::wayland::idle_notify

View file

@ -0,0 +1,78 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/reload.hpp"
#include "proto.hpp"
namespace qs::wayland::idle_notify {
///! Provides a notification when a wayland session is makred idle
/// An idle monitor detects when the user stops providing input for a period of time.
///
/// > [!NOTE] Using an idle monitor requires the compositor support the [ext-idle-notify-v1] protocol.
///
/// [ext-idle-notify-v1]: https://wayland.app/protocols/ext-idle-notify-v1
class IdleMonitor: public PostReloadHook {
Q_OBJECT;
QML_ELEMENT;
// clang-format off
/// If the idle monitor should be enabled. Defaults to true.
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged);
/// The amount of time in seconds the idle monitor should wait before reporting an idle state.
///
/// Defaults to zero, which reports idle status immediately.
Q_PROPERTY(qreal timeout READ default WRITE default NOTIFY timeoutChanged BINDABLE bindableTimeout);
/// When set to true, @@isIdle will depend on both user interaction and active idle inhibitors.
/// When false, the value will depend solely on user interaction. Defaults to true.
Q_PROPERTY(bool respectInhibitors READ default WRITE default NOTIFY respectInhibitorsChanged BINDABLE bindableRespectInhibitors);
/// This property is true if the user has been idle for at least @@timeout.
/// What is considered to be idle is influenced by @@respectInhibitors.
Q_PROPERTY(bool isIdle READ default NOTIFY isIdleChanged BINDABLE bindableIsIdle);
// clang-format on
public:
IdleMonitor() = default;
~IdleMonitor() override;
Q_DISABLE_COPY_MOVE(IdleMonitor);
void onPostReload() override;
[[nodiscard]] bool isEnabled() const { return this->bNotification.value(); }
void setEnabled(bool enabled) { this->bEnabled = enabled; }
[[nodiscard]] QBindable<qreal> bindableTimeout() { return &this->bTimeout; }
[[nodiscard]] QBindable<bool> bindableRespectInhibitors() { return &this->bRespectInhibitors; }
[[nodiscard]] QBindable<bool> bindableIsIdle() const { return &this->bIsIdle; }
signals:
void enabledChanged();
void timeoutChanged();
void respectInhibitorsChanged();
void isIdleChanged();
private:
void updateNotification();
struct Params {
bool enabled;
qreal timeout;
bool respectInhibitors;
};
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bEnabled, true, &IdleMonitor::enabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, qreal, bTimeout, &IdleMonitor::timeoutChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bRespectInhibitors, true, &IdleMonitor::respectInhibitorsChanged);
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, Params, bParams, &IdleMonitor::updateNotification);
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, impl::IdleNotification*, bNotification);
Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, bool, bIsIdle, &IdleMonitor::isIdleChanged);
// clang-format on
};
} // namespace qs::wayland::idle_notify

View file

@ -0,0 +1,75 @@
#include "proto.hpp"
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandinputdevice_p.h>
#include <private/qwaylandintegration_p.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qtypes.h>
#include <qwayland-ext-idle-notify-v1.h>
#include <qwaylandclientextension.h>
#include <wayland-ext-idle-notify-v1-client-protocol.h>
#include "../../core/logcat.hpp"
namespace qs::wayland::idle_notify {
QS_LOGGING_CATEGORY(logIdleNotify, "quickshell.wayland.idle_notify", QtWarningMsg);
}
namespace qs::wayland::idle_notify::impl {
IdleNotificationManager::IdleNotificationManager(): QWaylandClientExtensionTemplate(2) {
this->initialize();
}
IdleNotificationManager* IdleNotificationManager::instance() {
static auto* instance = new IdleNotificationManager(); // NOLINT
return instance->isInitialized() ? instance : nullptr;
}
IdleNotification*
IdleNotificationManager::createIdleNotification(quint32 timeout, bool respectInhibitors) {
if (!respectInhibitors
&& this->QtWayland::ext_idle_notifier_v1::version()
< EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION)
{
qCWarning(logIdleNotify) << "Cannot ignore inhibitors for new idle notifier: Compositor does "
"not support protocol version 2.";
respectInhibitors = true;
}
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
auto* inputDevice = display->lastInputDevice();
if (inputDevice == nullptr) inputDevice = display->defaultInputDevice();
if (inputDevice == nullptr) {
qCCritical(logIdleNotify) << "Could not create idle notifier: No seat.";
return nullptr;
}
::ext_idle_notification_v1* notification = nullptr;
if (respectInhibitors) notification = this->get_idle_notification(timeout, inputDevice->object());
else notification = this->get_input_idle_notification(timeout, inputDevice->object());
auto* wrapper = new IdleNotification(notification);
qCDebug(logIdleNotify) << "Created" << wrapper << "with timeout:" << timeout
<< "respects inhibitors:" << respectInhibitors;
return wrapper;
}
IdleNotification::~IdleNotification() {
qCDebug(logIdleNotify) << "Destroyed" << this;
if (this->isInitialized()) this->destroy();
}
void IdleNotification::ext_idle_notification_v1_idled() {
qCDebug(logIdleNotify) << this << "has been marked idle";
this->bIsIdle = true;
}
void IdleNotification::ext_idle_notification_v1_resumed() {
qCDebug(logIdleNotify) << this << "has been marked resumed";
this->bIsIdle = false;
}
} // namespace qs::wayland::idle_notify::impl

View file

@ -0,0 +1,51 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qwayland-ext-idle-notify-v1.h>
#include <qwaylandclientextension.h>
#include <wayland-ext-idle-notify-v1-client-protocol.h>
#include "../../core/logcat.hpp"
namespace qs::wayland::idle_notify {
QS_DECLARE_LOGGING_CATEGORY(logIdleNotify);
}
namespace qs::wayland::idle_notify::impl {
class IdleNotification;
class IdleNotificationManager
: public QWaylandClientExtensionTemplate<IdleNotificationManager>
, public QtWayland::ext_idle_notifier_v1 {
public:
explicit IdleNotificationManager();
IdleNotification* createIdleNotification(quint32 timeout, bool respectInhibitors);
static IdleNotificationManager* instance();
};
class IdleNotification
: public QObject
, public QtWayland::ext_idle_notification_v1 {
Q_OBJECT;
public:
explicit IdleNotification(::ext_idle_notification_v1* notification)
: QtWayland::ext_idle_notification_v1(notification) {}
~IdleNotification() override;
Q_DISABLE_COPY_MOVE(IdleNotification);
Q_OBJECT_BINDABLE_PROPERTY(IdleNotification, bool, bIsIdle);
protected:
void ext_idle_notification_v1_idled() override;
void ext_idle_notification_v1_resumed() override;
};
} // namespace qs::wayland::idle_notify::impl

View file

@ -0,0 +1,44 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
FloatingWindow {
color: contentItem.palette.window
IdleMonitor {
id: monitor
enabled: enabledCb.checked
timeout: timeoutSb.value
respectInhibitors: respectInhibitorsCb.checked
}
ColumnLayout {
Label { text: `Is idle? ${monitor.isIdle}` }
CheckBox {
id: enabledCb
text: "Enabled"
checked: true
}
CheckBox {
id: respectInhibitorsCb
text: "Respect Inhibitors"
checked: true
}
RowLayout {
Label { text: "Timeout" }
SpinBox {
id: timeoutSb
editable: true
from: 0
to: 1000
value: 5
}
}
}
}

View file

@ -6,5 +6,6 @@ headers = [
"toplevel_management/qml.hpp", "toplevel_management/qml.hpp",
"screencopy/view.hpp", "screencopy/view.hpp",
"idle_inhibit/inhibitor.hpp", "idle_inhibit/inhibitor.hpp",
"idle_notify/monitor.hpp",
] ]
----- -----