From 173e7306d0a968debedd934d24a9d9513df4c721 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:42:53 -0500 Subject: [PATCH] fix using the `ext-idle-notify-v1` protocol --- include/client.hpp | 2 + include/modules/idle_inhibitor.hpp | 14 ++- protocol/ext-idle-notify-v1.xml | 131 +++++++++++++++++++++++++++++ protocol/meson.build | 1 + src/client.cpp | 4 + src/modules/idle_inhibitor.cpp | 116 ++++++++++++------------- 6 files changed, 197 insertions(+), 71 deletions(-) create mode 100644 protocol/ext-idle-notify-v1.xml diff --git a/include/client.hpp b/include/client.hpp index 0e68f002..a4bc5332 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -12,6 +12,7 @@ struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibit_manager_v1; +struct ext_idle_notifier_v1; namespace waybar { @@ -27,6 +28,7 @@ class Client { struct wl_registry *registry = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; + struct ext_idle_notifier_v1 *idle_notifier = nullptr; std::vector> bars; Config config; std::string bar_id; diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 4bc9124a..523426c6 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,9 +10,8 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - sigc::connection activity_timeout_; - sigc::connection motion_connection_; - sigc::connection key_connection_; + struct ext_idle_notification_v1* idle_notification_; + uint32_t idle_timeout_ms_; public: IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); @@ -24,12 +23,11 @@ class IdleInhibitor : public ALabel { private: bool handleToggle(GdkEventButton* const& e) override; - bool handleMotion(GdkEventMotion* const& e); - bool handleKey(GdkEventKey* const& e); void toggleStatus(); - void resetActivityTimeout(); - void setupActivityMonitoring(); - void teardownActivityMonitoring(); + void setupIdleNotification(); + void teardownIdleNotification(); + static void handleIdled(void* data, struct ext_idle_notification_v1* notification); + static void handleResumed(void* data, struct ext_idle_notification_v1* notification); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/protocol/ext-idle-notify-v1.xml b/protocol/ext-idle-notify-v1.xml new file mode 100644 index 00000000..db7d9c16 --- /dev/null +++ b/protocol/ext-idle-notify-v1.xml @@ -0,0 +1,131 @@ + + + + Copyright © 2015 Martin Gräßlin + Copyright © 2022 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface allows clients to monitor user idle status. + + After binding to this global, clients can create ext_idle_notification_v1 + objects to get notified when the user is idle for a given amount of time. + + + + + Destroy the manager object. All objects created via this interface + remain valid. + + + + + + Create a new idle notification object. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + Create a new idle notification object to track input from the + user, such as keyboard and mouse movement. Because this object is + meant to track user input alone, it ignores idle inhibitors. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + This interface is used by the compositor to send idle notification events + to clients. + + Initially the notification object is not idle. The notification object + becomes idle when no user activity has happened for at least the timeout + duration, starting from the creation of the notification object. User + activity may include input events or a presence sensor, but is + compositor-specific. + + How this notification responds to idle inhibitors depends on how + it was constructed. If constructed from the + get_idle_notification request, then if an idle inhibitor is + active (e.g. another client has created a zwp_idle_inhibitor_v1 + on a visible surface), the compositor must not make the + notification object idle. However, if constructed from the + get_input_idle_notification request, then idle inhibitors are + ignored, and only input from the user, e.g. from a keyboard or + mouse, counts as activity. + + When the notification object becomes idle, an idled event is sent. When + user activity starts again, the notification object stops being idle, + a resumed event is sent and the timeout is restarted. + + + + + Destroy the notification object. + + + + + + This event is sent when the notification object becomes idle. + + It's a compositor protocol error to send this event twice without a + resumed event in-between. + + + + + + This event is sent when the notification object stops being idle. + + It's a compositor protocol error to send this event twice without an + idled event in-between. It's a compositor protocol error to send this + event prior to any idled event. + + + + diff --git a/protocol/meson.build b/protocol/meson.build index b16113b2..3da6ee47 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -29,6 +29,7 @@ client_protocols = [ ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ['dwl-ipc-unstable-v2.xml'], + ['ext-idle-notify-v1.xml'], ] if wayland_protos.version().version_compare('>=1.39') diff --git a/src/client.cpp b/src/client.cpp index 946780db..a71d1c7a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -7,6 +7,7 @@ #include #include "gtkmm/icontheme.h" +#include "ext-idle-notify-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/clara.hpp" #include "util/format.hpp" @@ -26,6 +27,9 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); + } else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) { + client->idle_notifier = static_cast( + wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1)); } } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 619c52f1..7d5fdd01 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -1,5 +1,6 @@ #include "modules/idle_inhibitor.hpp" +#include "ext-idle-notify-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/command.hpp" @@ -11,6 +12,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), + idle_notification_(nullptr), + idle_timeout_ms_(0), pid_(-1), wait_for_activity_(false) { if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { @@ -20,6 +23,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& // Read the wait-for-activity config option if (config_["wait-for-activity"].isBool()) { wait_for_activity_ = config_["wait-for-activity"].asBool(); + + // Check if ext-idle-notify protocol is available when wait-for-activity is enabled + if (wait_for_activity_ && waybar::Client::inst()->idle_notifier == nullptr) { + throw std::runtime_error("wait-for-activity requires ext-idle-notify-v1 protocol support"); + } } if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() && @@ -38,7 +46,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& } waybar::modules::IdleInhibitor::~IdleInhibitor() { - teardownActivityMonitoring(); + teardownIdleNotification(); if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); @@ -107,11 +115,11 @@ void waybar::modules::IdleInhibitor::toggleStatus() { if (status && config_["timeout"].isNumeric()) { auto timeoutMins = config_["timeout"].asDouble(); int timeoutSecs = timeoutMins * 60; + idle_timeout_ms_ = timeoutSecs * 1000; - // If wait-for-activity is enabled, set up activity monitoring + // If wait-for-activity is enabled, set up idle notification if (wait_for_activity_) { - setupActivityMonitoring(); - resetActivityTimeout(); + setupIdleNotification(); } else { // Original behavior: simple timeout timeout_ = Glib::signal_timeout().connect_seconds( @@ -130,8 +138,8 @@ void waybar::modules::IdleInhibitor::toggleStatus() { timeoutSecs); } } else { - // When deactivated, tear down activity monitoring - teardownActivityMonitoring(); + // When deactivated, tear down idle notification + teardownIdleNotification(); } } @@ -151,77 +159,59 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { return true; } -bool waybar::modules::IdleInhibitor::handleMotion(GdkEventMotion* const& e) { - if (wait_for_activity_ && status) { - resetActivityTimeout(); +void waybar::modules::IdleInhibitor::handleIdled(void* data, + struct ext_idle_notification_v1* /*notification*/) { + spdlog::info("deactivating idle_inhibitor due to user inactivity"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); } - return false; } -bool waybar::modules::IdleInhibitor::handleKey(GdkEventKey* const& e) { - if (wait_for_activity_ && status) { - resetActivityTimeout(); - } - return false; +void waybar::modules::IdleInhibitor::handleResumed(void* data, + struct ext_idle_notification_v1* /*notification*/) { + // User became active again - notification will continue monitoring + spdlog::debug("user activity detected, idle_inhibitor still active"); } -void waybar::modules::IdleInhibitor::resetActivityTimeout() { - if (!config_["timeout"].isNumeric()) { +void waybar::modules::IdleInhibitor::setupIdleNotification() { + // Don't set up if already exists + if (idle_notification_ != nullptr) { return; } - if (activity_timeout_.connected()) { - activity_timeout_.disconnect(); - } - - auto timeoutMins = config_["timeout"].asDouble(); - int timeoutSecs = timeoutMins * 60; - - activity_timeout_ = Glib::signal_timeout().connect_seconds( - []() { - spdlog::info("deactivating idle_inhibitor due to inactivity"); - status = false; - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - module->update(); - } - return false; - }, - timeoutSecs); -} - -void waybar::modules::IdleInhibitor::setupActivityMonitoring() { - // Don't set up if already connected - if (motion_connection_.connected() || key_connection_.connected()) { + auto* client = waybar::Client::inst(); + if (client->idle_notifier == nullptr) { + spdlog::error("ext-idle-notify protocol not available"); return; } - // Get non-const reference to the window to set up event monitoring - // This is safe because we're only setting up signal handlers, not modifying the Bar itself - auto& window = const_cast(bar_.window); - - // Enable motion and key event monitoring on the bar window - auto gdk_window = window.get_window(); - if (gdk_window) { - gdk_window->set_events(gdk_window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + // Get the wayland seat from the display + auto* gdk_seat = gdk_display_get_default_seat(client->gdk_display->gobj()); + if (gdk_seat == nullptr) { + spdlog::error("failed to get default seat"); + return; } + auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); - // Connect to the bar window's event signals - motion_connection_ = window.signal_motion_notify_event().connect( - sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); - key_connection_ = window.signal_key_press_event().connect( - sigc::mem_fun(*this, &IdleInhibitor::handleKey)); + // Create idle notification that monitors all input (not just when inhibitor is active) + // We use get_idle_notification instead of get_input_idle_notification to respect + // idle inhibitors from other applications + idle_notification_ = ext_idle_notifier_v1_get_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + + static const struct ext_idle_notification_v1_listener idle_notification_listener = { + .idled = &IdleInhibitor::handleIdled, + .resumed = &IdleInhibitor::handleResumed, + }; + + ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this); + wl_display_roundtrip(client->wl_display); } -void waybar::modules::IdleInhibitor::teardownActivityMonitoring() { - if (activity_timeout_.connected()) { - activity_timeout_.disconnect(); - } - - if (motion_connection_.connected()) { - motion_connection_.disconnect(); - } - - if (key_connection_.connected()) { - key_connection_.disconnect(); +void waybar::modules::IdleInhibitor::teardownIdleNotification() { + if (idle_notification_ != nullptr) { + ext_idle_notification_v1_destroy(idle_notification_); + idle_notification_ = nullptr; } }