Merge pull request #4565 from guttermonk/idle_inhibitor-activity-detection
Added Idle Inhibitor Features: Signal Inputs & Activity Detection
This commit is contained in:
@@ -3,10 +3,12 @@
|
||||
#include <gtk-layer-shell.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#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"
|
||||
@@ -39,6 +41,12 @@ void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint
|
||||
|
||||
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
||||
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<struct ext_idle_notifier_v1 *>(
|
||||
wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, bind_version));
|
||||
spdlog::debug("Bound ext-idle-notifier-v1 at version {}", bind_version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+148
-15
@@ -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,11 +12,24 @@ 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) {
|
||||
idle_notification_(nullptr),
|
||||
idle_timeout_ms_(0),
|
||||
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();
|
||||
|
||||
// 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() &&
|
||||
config_["start-activated"].asBool() != status) {
|
||||
toggleStatus();
|
||||
@@ -32,6 +46,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
|
||||
}
|
||||
|
||||
waybar::modules::IdleInhibitor::~IdleInhibitor() {
|
||||
teardownIdleNotification();
|
||||
|
||||
if (idle_inhibitor_ != nullptr) {
|
||||
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
|
||||
idle_inhibitor_ = nullptr;
|
||||
@@ -77,6 +93,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;
|
||||
|
||||
@@ -88,21 +115,34 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
|
||||
if (status && config_["timeout"].isNumeric()) {
|
||||
auto timeoutMins = config_["timeout"].asDouble();
|
||||
int timeoutSecs = timeoutMins * 60;
|
||||
idle_timeout_ms_ = timeoutSecs * 1000;
|
||||
|
||||
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 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
|
||||
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 idle notification
|
||||
teardownIdleNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,3 +161,96 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
|
||||
ALabel::handleToggle(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
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<IdleInhibitor*>(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*/) {
|
||||
// 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");
|
||||
teardownIdleNotification();
|
||||
}
|
||||
|
||||
auto* client = waybar::Client::inst();
|
||||
if (client->idle_notifier == nullptr) {
|
||||
spdlog::error("ext-idle-notify protocol not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Check protocol version to determine which function to use
|
||||
uint32_t version =
|
||||
wl_proxy_get_version(reinterpret_cast<struct wl_proxy*>(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");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user