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(); + } +}