fix using the ext-idle-notify-v1 protocol

This commit is contained in:
guttermonk
2025-10-17 19:42:53 -05:00
parent 262f8d96f3
commit 173e7306d0
6 changed files with 197 additions and 71 deletions
+2
View File
@@ -12,6 +12,7 @@
struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibitor_v1;
struct zwp_idle_inhibit_manager_v1; struct zwp_idle_inhibit_manager_v1;
struct ext_idle_notifier_v1;
namespace waybar { namespace waybar {
@@ -27,6 +28,7 @@ class Client {
struct wl_registry *registry = nullptr; struct wl_registry *registry = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
struct ext_idle_notifier_v1 *idle_notifier = nullptr;
std::vector<std::unique_ptr<Bar>> bars; std::vector<std::unique_ptr<Bar>> bars;
Config config; Config config;
std::string bar_id; std::string bar_id;
+6 -8
View File
@@ -10,9 +10,8 @@ namespace waybar::modules {
class IdleInhibitor : public ALabel { class IdleInhibitor : public ALabel {
sigc::connection timeout_; sigc::connection timeout_;
sigc::connection activity_timeout_; struct ext_idle_notification_v1* idle_notification_;
sigc::connection motion_connection_; uint32_t idle_timeout_ms_;
sigc::connection key_connection_;
public: public:
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
@@ -24,12 +23,11 @@ class IdleInhibitor : public ALabel {
private: private:
bool handleToggle(GdkEventButton* const& e) override; bool handleToggle(GdkEventButton* const& e) override;
bool handleMotion(GdkEventMotion* const& e);
bool handleKey(GdkEventKey* const& e);
void toggleStatus(); void toggleStatus();
void resetActivityTimeout(); void setupIdleNotification();
void setupActivityMonitoring(); void teardownIdleNotification();
void teardownActivityMonitoring(); 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_; const Bar& bar_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_; struct zwp_idle_inhibitor_v1* idle_inhibitor_;
+131
View File
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_idle_notify_v1">
<copyright>
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.
</copyright>
<interface name="ext_idle_notifier_v1" version="2">
<description summary="idle notification manager">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the manager object. All objects created via this interface
remain valid.
</description>
</request>
<request name="get_idle_notification">
<description summary="create a notification object">
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.
</description>
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<!-- Version 2 additions -->
<request name="get_input_idle_notification" since="2">
<description summary="create a notification object">
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.
</description>
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
</interface>
<interface name="ext_idle_notification_v1" version="2">
<description summary="idle notification">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the notification object">
Destroy the notification object.
</description>
</request>
<event name="idled">
<description summary="notification object is idle">
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.
</description>
</event>
<event name="resumed">
<description summary="notification object is no longer idle">
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.
</description>
</event>
</interface>
</protocol>
+1
View File
@@ -29,6 +29,7 @@ client_protocols = [
['river-status-unstable-v1.xml'], ['river-status-unstable-v1.xml'],
['river-control-unstable-v1.xml'], ['river-control-unstable-v1.xml'],
['dwl-ipc-unstable-v2.xml'], ['dwl-ipc-unstable-v2.xml'],
['ext-idle-notify-v1.xml'],
] ]
if wayland_protos.version().version_compare('>=1.39') if wayland_protos.version().version_compare('>=1.39')
+4
View File
@@ -7,6 +7,7 @@
#include <utility> #include <utility>
#include "gtkmm/icontheme.h" #include "gtkmm/icontheme.h"
#include "ext-idle-notify-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/format.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) { } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>( client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>(
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); 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<struct ext_idle_notifier_v1 *>(
wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1));
} }
} }
+53 -63
View File
@@ -1,5 +1,6 @@
#include "modules/idle_inhibitor.hpp" #include "modules/idle_inhibitor.hpp"
#include "ext-idle-notify-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/command.hpp" #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), : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar), bar_(bar),
idle_inhibitor_(nullptr), idle_inhibitor_(nullptr),
idle_notification_(nullptr),
idle_timeout_ms_(0),
pid_(-1), pid_(-1),
wait_for_activity_(false) { wait_for_activity_(false) {
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { 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 // Read the wait-for-activity config option
if (config_["wait-for-activity"].isBool()) { if (config_["wait-for-activity"].isBool()) {
wait_for_activity_ = config_["wait-for-activity"].asBool(); 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() && 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() { waybar::modules::IdleInhibitor::~IdleInhibitor() {
teardownActivityMonitoring(); teardownIdleNotification();
if (idle_inhibitor_ != nullptr) { if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
@@ -107,11 +115,11 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
if (status && config_["timeout"].isNumeric()) { if (status && config_["timeout"].isNumeric()) {
auto timeoutMins = config_["timeout"].asDouble(); auto timeoutMins = config_["timeout"].asDouble();
int timeoutSecs = timeoutMins * 60; 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_) { if (wait_for_activity_) {
setupActivityMonitoring(); setupIdleNotification();
resetActivityTimeout();
} else { } else {
// Original behavior: simple timeout // Original behavior: simple timeout
timeout_ = Glib::signal_timeout().connect_seconds( timeout_ = Glib::signal_timeout().connect_seconds(
@@ -130,8 +138,8 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
timeoutSecs); timeoutSecs);
} }
} else { } else {
// When deactivated, tear down activity monitoring // When deactivated, tear down idle notification
teardownActivityMonitoring(); teardownIdleNotification();
} }
} }
@@ -151,77 +159,59 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
return true; return true;
} }
bool waybar::modules::IdleInhibitor::handleMotion(GdkEventMotion* const& e) { void waybar::modules::IdleInhibitor::handleIdled(void* data,
if (wait_for_activity_ && status) { struct ext_idle_notification_v1* /*notification*/) {
resetActivityTimeout(); 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) { void waybar::modules::IdleInhibitor::handleResumed(void* data,
if (wait_for_activity_ && status) { struct ext_idle_notification_v1* /*notification*/) {
resetActivityTimeout(); // User became active again - notification will continue monitoring
} spdlog::debug("user activity detected, idle_inhibitor still active");
return false;
} }
void waybar::modules::IdleInhibitor::resetActivityTimeout() { void waybar::modules::IdleInhibitor::setupIdleNotification() {
if (!config_["timeout"].isNumeric()) { // Don't set up if already exists
if (idle_notification_ != nullptr) {
return; return;
} }
if (activity_timeout_.connected()) { auto* client = waybar::Client::inst();
activity_timeout_.disconnect(); if (client->idle_notifier == nullptr) {
} spdlog::error("ext-idle-notify protocol not available");
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; return;
} }
// Get non-const reference to the window to set up event monitoring // Get the wayland seat from the display
// This is safe because we're only setting up signal handlers, not modifying the Bar itself auto* gdk_seat = gdk_display_get_default_seat(client->gdk_display->gobj());
auto& window = const_cast<Gtk::Window&>(bar_.window); if (gdk_seat == nullptr) {
spdlog::error("failed to get default seat");
// Enable motion and key event monitoring on the bar window return;
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);
} }
auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
// Connect to the bar window's event signals // Create idle notification that monitors all input (not just when inhibitor is active)
motion_connection_ = window.signal_motion_notify_event().connect( // We use get_idle_notification instead of get_input_idle_notification to respect
sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); // idle inhibitors from other applications
key_connection_ = window.signal_key_press_event().connect( idle_notification_ = ext_idle_notifier_v1_get_idle_notification(
sigc::mem_fun(*this, &IdleInhibitor::handleKey)); 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() { void waybar::modules::IdleInhibitor::teardownIdleNotification() {
if (activity_timeout_.connected()) { if (idle_notification_ != nullptr) {
activity_timeout_.disconnect(); ext_idle_notification_v1_destroy(idle_notification_);
} idle_notification_ = nullptr;
if (motion_connection_.connected()) {
motion_connection_.disconnect();
}
if (key_connection_.connected()) {
key_connection_.disconnect();
} }
} }