From c831a352f2dce8feb4b606d136f06e8d29c03e7e Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 18:11:50 -0500 Subject: [PATCH 1/9] Added signal support and wait-for-activity bool. --- include/modules/idle_inhibitor.hpp | 10 +++ man/waybar-idle-inhibitor.5.scd | 44 +++++++++- src/modules/idle_inhibitor.cpp | 130 +++++++++++++++++++++++++---- 3 files changed, 167 insertions(+), 17 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 22bd808f..4bc9124a 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,21 +10,31 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; + sigc::connection activity_timeout_; + sigc::connection motion_connection_; + sigc::connection key_connection_; public: IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~IdleInhibitor(); auto update() -> void override; + auto refresh(int) -> void override; static std::list modules; static bool status; 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(); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; int pid_; + bool wait_for_activity_; }; } // namespace waybar::modules diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index 405c8fc5..cd6980f2 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -76,6 +76,17 @@ screensaver, also known as "presentation mode". typeof: double ++ The number of minutes the inhibition should last. +*wait-for-activity*: ++ + typeof: bool ++ + default: *false* ++ + When enabled, the idle inhibitor remains active as long as there is keyboard or mouse activity on the bar. If there is no activity for the duration specified in *timeout*, the inhibitor will automatically toggle off. This option requires *timeout* to be set. + +*signal*: ++ + typeof: integer ++ + The signal number used to toggle the idle inhibitor externally. ++ + The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++ + Use `pkill -SIGRTMIN+N waybar` to toggle the idle inhibitor from scripts or keybindings. + *tooltip*: ++ typeof: bool ++ default: true ++ @@ -115,17 +126,46 @@ screensaver, also known as "presentation mode". # EXAMPLES +Basic usage with timeout: + ``` "idle_inhibitor": { "format": "{icon}", "format-icons": { - "activated": "", - "deactivated": "" + "activated": "", + "deactivated": "" }, "timeout": 30.5 } ``` +With external control via signals (can be toggled with `pkill -SIGRTMIN+8 waybar`): + +``` +"idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "signal": 8 +} +``` + +With wait-for-activity feature: + +``` +"idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "timeout": 5.0, + "wait-for-activity": true +} +``` + # STYLE - *#idle_inhibitor* diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index a5fc9ac7..6012bb4c 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -11,11 +11,17 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), - pid_(-1) { + pid_(-1), + wait_for_activity_(false) { if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { throw std::runtime_error("idle-inhibit not available"); } + // Read the wait-for-activity config option + if (config_["wait-for-activity"].isBool()) { + wait_for_activity_ = config_["wait-for-activity"].asBool(); + } + if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() && config_["start-activated"].asBool() != status) { toggleStatus(); @@ -32,6 +38,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& } waybar::modules::IdleInhibitor::~IdleInhibitor() { + teardownActivityMonitoring(); + if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; @@ -77,6 +85,17 @@ auto waybar::modules::IdleInhibitor::update() -> void { ALabel::update(); } +auto waybar::modules::IdleInhibitor::refresh(int sig) -> void { + if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { + toggleStatus(); + + // Make all other idle inhibitor modules update + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + } +} + void waybar::modules::IdleInhibitor::toggleStatus() { status = !status; @@ -89,20 +108,30 @@ void waybar::modules::IdleInhibitor::toggleStatus() { auto timeoutMins = config_["timeout"].asDouble(); int timeoutSecs = timeoutMins * 60; - timeout_ = Glib::signal_timeout().connect_seconds( - []() { - /* intentionally not tied to a module instance lifetime - * as the output with `this` can be disconnected - */ - spdlog::info("deactivating idle_inhibitor by timeout"); - status = false; - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - module->update(); - } - /* disconnect */ - return false; - }, - timeoutSecs); + // If wait-for-activity is enabled, set up activity monitoring + if (wait_for_activity_) { + setupActivityMonitoring(); + resetActivityTimeout(); + } else { + // Original behavior: simple timeout + timeout_ = Glib::signal_timeout().connect_seconds( + []() { + /* intentionally not tied to a module instance lifetime + * as the output with `this` can be disconnected + */ + spdlog::info("deactivating idle_inhibitor by timeout"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + /* disconnect */ + return false; + }, + timeoutSecs); + } + } else { + // When deactivated, tear down activity monitoring + teardownActivityMonitoring(); } } @@ -121,3 +150,74 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { ALabel::handleToggle(e); return true; } + +bool waybar::modules::IdleInhibitor::handleMotion(GdkEventMotion* const& e) { + if (wait_for_activity_ && status) { + resetActivityTimeout(); + } + return false; +} + +bool waybar::modules::IdleInhibitor::handleKey(GdkEventKey* const& e) { + if (wait_for_activity_ && status) { + resetActivityTimeout(); + } + return false; +} + +void waybar::modules::IdleInhibitor::resetActivityTimeout() { + if (!config_["timeout"].isNumeric()) { + 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()) { + return; + } + + // Enable motion and key event monitoring on the bar window + auto window = bar_.window.get_window(); + if (window) { + window->set_events(window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + } + + // Connect to the bar window's event signals + motion_connection_ = bar_.window.signal_motion_notify_event().connect( + sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); + key_connection_ = bar_.window.signal_key_press_event().connect( + sigc::mem_fun(*this, &IdleInhibitor::handleKey)); +} + +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(); + } +} From 262f8d96f31d1ac608beadee680e5f7b63d76353 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 18:26:06 -0500 Subject: [PATCH 2/9] fixed the compilation errors in the `idle_inhibitor.cpp` file --- src/modules/idle_inhibitor.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 6012bb4c..619c52f1 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -195,16 +195,20 @@ void waybar::modules::IdleInhibitor::setupActivityMonitoring() { 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 window = bar_.window.get_window(); - if (window) { - window->set_events(window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + 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); } // Connect to the bar window's event signals - motion_connection_ = bar_.window.signal_motion_notify_event().connect( + motion_connection_ = window.signal_motion_notify_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); - key_connection_ = bar_.window.signal_key_press_event().connect( + key_connection_ = window.signal_key_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleKey)); } From 173e7306d0a968debedd934d24a9d9513df4c721 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:42:53 -0500 Subject: [PATCH 3/9] 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; } } From d68f168e09cfbaf782bd5ef73ac737649a1014e6 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:50:38 -0500 Subject: [PATCH 4/9] fixed the compilation error --- include/modules/idle_inhibitor.hpp | 6 +++--- src/modules/idle_inhibitor.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 523426c6..8ccb0d33 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,7 +10,7 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - struct ext_idle_notification_v1* idle_notification_; + struct ::ext_idle_notification_v1* idle_notification_; uint32_t idle_timeout_ms_; public: @@ -26,8 +26,8 @@ class IdleInhibitor : public ALabel { void toggleStatus(); 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); + 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/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 7d5fdd01..78cc5ab8 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -160,7 +160,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { } void waybar::modules::IdleInhibitor::handleIdled(void* data, - struct ext_idle_notification_v1* /*notification*/) { + 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) { @@ -169,7 +169,7 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, } void waybar::modules::IdleInhibitor::handleResumed(void* data, - struct ext_idle_notification_v1* /*notification*/) { + struct ::ext_idle_notification_v1* /*notification*/) { // User became active again - notification will continue monitoring spdlog::debug("user activity detected, idle_inhibitor still active"); } From 3e2ea1a8705b47e3967b5ae54bf5cd134ce24bfc Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:58:43 -0500 Subject: [PATCH 5/9] fixed the type declarations --- include/modules/idle_inhibitor.hpp | 6 +++--- src/modules/idle_inhibitor.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 8ccb0d33..515d7f5a 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,7 +10,7 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - struct ::ext_idle_notification_v1* idle_notification_; + ext_idle_notification_v1* idle_notification_; uint32_t idle_timeout_ms_; public: @@ -26,8 +26,8 @@ class IdleInhibitor : public ALabel { void toggleStatus(); 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); + static void handleIdled(void* data, ext_idle_notification_v1* notification); + static void handleResumed(void* data, ext_idle_notification_v1* notification); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 78cc5ab8..95ef94bd 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -160,7 +160,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { } void waybar::modules::IdleInhibitor::handleIdled(void* data, - struct ::ext_idle_notification_v1* /*notification*/) { + ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; for (auto const& module : waybar::modules::IdleInhibitor::modules) { @@ -169,7 +169,7 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, } void waybar::modules::IdleInhibitor::handleResumed(void* data, - struct ::ext_idle_notification_v1* /*notification*/) { + ext_idle_notification_v1* /*notification*/) { // User became active again - notification will continue monitoring spdlog::debug("user activity detected, idle_inhibitor still active"); } From 26922c7fbc4296c27ae92384f0cfd5fd8283641c Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 20:07:28 -0500 Subject: [PATCH 6/9] Update idle_inhibitor.hpp --- include/modules/idle_inhibitor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 515d7f5a..40c5227c 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -6,6 +6,8 @@ #include "bar.hpp" #include "client.hpp" +struct ext_idle_notification_v1; + namespace waybar::modules { class IdleInhibitor : public ALabel { From e1e99716802d40814964f3e3a4351cad18a9062f Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 20:49:24 -0500 Subject: [PATCH 7/9] fixed stale notification object issue --- src/modules/idle_inhibitor.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 95ef94bd..7ce192af 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -119,6 +119,9 @@ void waybar::modules::IdleInhibitor::toggleStatus() { // If wait-for-activity is enabled, set up idle notification if (wait_for_activity_) { + spdlog::debug("idle_inhibitor: wait-for-activity enabled, timeout: {} ms", idle_timeout_ms_); + // Tear down any existing notification first to ensure fresh setup + teardownIdleNotification(); setupIdleNotification(); } else { // Original behavior: simple timeout @@ -163,6 +166,13 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; + + // Clean up the notification since we're deactivating + auto* self = static_cast(data); + if (self != nullptr) { + self->teardownIdleNotification(); + } + for (auto const& module : waybar::modules::IdleInhibitor::modules) { module->update(); } @@ -175,9 +185,12 @@ void waybar::modules::IdleInhibitor::handleResumed(void* data, } void waybar::modules::IdleInhibitor::setupIdleNotification() { - // Don't set up if already exists + spdlog::debug("idle_inhibitor: setting up idle notification"); + + // Clean up any existing notification first if (idle_notification_ != nullptr) { - return; + spdlog::debug("idle_inhibitor: cleaning up existing notification before setup"); + teardownIdleNotification(); } auto* client = waybar::Client::inst(); @@ -197,9 +210,15 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { // 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 + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms", idle_timeout_ms_); idle_notification_ = ext_idle_notifier_v1_get_idle_notification( client->idle_notifier, idle_timeout_ms_, wl_seat); + if (idle_notification_ == nullptr) { + spdlog::error("idle_inhibitor: failed to create idle notification"); + return; + } + static const struct ext_idle_notification_v1_listener idle_notification_listener = { .idled = &IdleInhibitor::handleIdled, .resumed = &IdleInhibitor::handleResumed, @@ -207,10 +226,12 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this); wl_display_roundtrip(client->wl_display); + spdlog::debug("idle_inhibitor: idle notification setup complete"); } void waybar::modules::IdleInhibitor::teardownIdleNotification() { if (idle_notification_ != nullptr) { + spdlog::debug("idle_inhibitor: tearing down idle notification"); ext_idle_notification_v1_destroy(idle_notification_); idle_notification_ = nullptr; } From 19afad86751c022cc1a6690aea6be11b7da1ee21 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 21:21:48 -0500 Subject: [PATCH 8/9] updated client to bind version 2 of the ext-idle-notifier protocol --- src/client.cpp | 6 +++++- src/modules/idle_inhibitor.cpp | 26 ++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index a71d1c7a..d4c7aca2 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -28,8 +29,11 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint 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) { + // Bind version 2 if available (for get_input_idle_notification), otherwise version 1 + auto bind_version = std::min(version, 2u); client->idle_notifier = static_cast( - wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1)); + wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, bind_version)); + spdlog::debug("Bound ext-idle-notifier-v1 at version {}", bind_version); } } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 7ce192af..8dfe0b85 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -207,12 +207,26 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { } auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); - // 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 - spdlog::debug("idle_inhibitor: creating notification with timeout {} ms", idle_timeout_ms_); - idle_notification_ = ext_idle_notifier_v1_get_idle_notification( - client->idle_notifier, idle_timeout_ms_, wl_seat); + // Check protocol version to determine which function to use + uint32_t version = wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); + + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", + idle_timeout_ms_, version); + + if (version >= 2) { + // Version 2+: Use get_input_idle_notification which ignores idle inhibitors + // This allows us to detect actual user inactivity even while the inhibitor is active + spdlog::debug("idle_inhibitor: using get_input_idle_notification (ignores inhibitors)"); + idle_notification_ = ext_idle_notifier_v1_get_input_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + } else { + // Version 1: Fall back to get_idle_notification + // WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active + spdlog::warn("idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support get_input_idle_notification, " + "wait-for-activity may not work correctly", version); + idle_notification_ = ext_idle_notifier_v1_get_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + } if (idle_notification_ == nullptr) { spdlog::error("idle_inhibitor: failed to create idle notification"); From 66ecba4e99fcbb3a91acd197c6c552d730a608a7 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:58:46 +0200 Subject: [PATCH 9/9] idle_inhibitor: apply clang-format --- src/modules/idle_inhibitor.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 8dfe0b85..9638d0fc 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -23,7 +23,7 @@ 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"); @@ -162,31 +162,31 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { return true; } -void waybar::modules::IdleInhibitor::handleIdled(void* data, - ext_idle_notification_v1* /*notification*/) { +void waybar::modules::IdleInhibitor::handleIdled(void* data, + ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; - + // Clean up the notification since we're deactivating auto* self = static_cast(data); if (self != nullptr) { self->teardownIdleNotification(); } - + for (auto const& module : waybar::modules::IdleInhibitor::modules) { module->update(); } } void waybar::modules::IdleInhibitor::handleResumed(void* data, - ext_idle_notification_v1* /*notification*/) { + 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::setupIdleNotification() { spdlog::debug("idle_inhibitor: setting up idle notification"); - + // Clean up any existing notification first if (idle_notification_ != nullptr) { spdlog::debug("idle_inhibitor: cleaning up existing notification before setup"); @@ -208,11 +208,12 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); // Check protocol version to determine which function to use - uint32_t version = wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); - - spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", + uint32_t version = + wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); + + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", idle_timeout_ms_, version); - + if (version >= 2) { // Version 2+: Use get_input_idle_notification which ignores idle inhibitors // This allows us to detect actual user inactivity even while the inhibitor is active @@ -222,10 +223,13 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { } else { // Version 1: Fall back to get_idle_notification // WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active - spdlog::warn("idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support get_input_idle_notification, " - "wait-for-activity may not work correctly", version); - idle_notification_ = ext_idle_notifier_v1_get_idle_notification( - client->idle_notifier, idle_timeout_ms_, wl_seat); + spdlog::warn( + "idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support " + "get_input_idle_notification, " + "wait-for-activity may not work correctly", + version); + idle_notification_ = ext_idle_notifier_v1_get_idle_notification(client->idle_notifier, + idle_timeout_ms_, wl_seat); } if (idle_notification_ == nullptr) {