From 5bd33075416dbb0bf22ffb7a0629faff685468de Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Wed, 6 Mar 2024 04:38:01 -0600 Subject: [PATCH] idle: support ext-idle-notify-v1 protocol wlroots no longer supports the old KDE one, so this is necessary. Keep the old one around for now. --- include/wayland.h | 5 +- meson.build | 1 + protocol/ext-idle-notify-v1.xml | 102 ++++++++++++++++++++++++++++++++ protocol/meson.build | 1 + src/wayland.c | 7 ++- src/wl_idle_ext.c | 100 +++++++++++++++++++++++++++++++ 6 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 protocol/ext-idle-notify-v1.xml create mode 100644 src/wl_idle_ext.c diff --git a/include/wayland.h b/include/wayland.h index 7f100f3..2e47d2f 100644 --- a/include/wayland.h +++ b/include/wayland.h @@ -16,6 +16,7 @@ #include "virtual-keyboard-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "idle-client-protocol.h" +#include "ext-idle-notify-v1-client-protocol.h" #include "uSynergy.h" @@ -49,6 +50,7 @@ struct wlIdle void (*inhibit_stop)(struct wlIdle *); }; +extern bool wlIdleInitExt(struct wlContext *ctx); extern bool wlIdleInitKde(struct wlContext *ctx); extern bool wlIdleInitGnome(struct wlContext *ctx); @@ -113,7 +115,8 @@ struct wlContext { struct zxdg_output_manager_v1 *output_manager; struct wlOutput *outputs; /* idle stuff */ - struct org_kde_kwin_idle *idle_manager; + struct org_kde_kwin_idle *idle_manager; /* old KDE */ + struct ext_idle_notifier_v1 *idle_notifier; /* new standard */ struct wlIdle idle; //state int width; diff --git a/meson.build b/meson.build index 9c2f8c2..5b7722b 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ src_c = files( 'src/wl_idle.c', 'src/wl_idle_gnome.c', 'src/wl_idle_kde.c', + 'src/wl_idle_ext.c', 'src/wl_input.c', 'src/wl_input_wlr.c', 'src/wl_input_kde.c', diff --git a/protocol/ext-idle-notify-v1.xml b/protocol/ext-idle-notify-v1.xml new file mode 100644 index 0000000..6fe97d7 --- /dev/null +++ b/protocol/ext-idle-notify-v1.xml @@ -0,0 +1,102 @@ + + + + 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. + + + + + + + + + + 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. 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. + + 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 b0d2242..1d554a3 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -17,6 +17,7 @@ wayland_scanner_client = generator( client_protocols = [ ['idle.xml'], + ['ext-idle-notify-v1.xml'], ['fake-input.xml'], ['keyboard-shortcuts-inhibit-unstable-v1.xml'], ['virtual-keyboard-unstable-v1.xml'], diff --git a/src/wayland.c b/src/wayland.c index 73f8f2f..7d91bcd 100644 --- a/src/wayland.c +++ b/src/wayland.c @@ -465,6 +465,9 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam } else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) { logDbg("Got idle manager"); ctx->idle_manager = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, version); + } else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) { + logDbg("Got idle notifier"); + ctx->idle_notifier = wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, version); } } @@ -566,7 +569,9 @@ bool wlSetup(struct wlContext *ctx, int width, int height, char *backend) /* initiailize idle inhibition */ if (configTryBool("idle-inhibit/enable", true)) { - if (wlIdleInitKde(ctx)) { + if (wlIdleInitExt(ctx)) { + logInfo("Using ext-idle-notify-v1 idle inhibition protocol"); + } else if (wlIdleInitKde(ctx)) { logInfo("Using KDE idle inhibition protocol"); } else if (wlIdleInitGnome(ctx)) { logInfo("Using GNOME idle inhibition through gnome-session-inhibit"); diff --git a/src/wl_idle_ext.c b/src/wl_idle_ext.c new file mode 100644 index 0000000..68c5d24 --- /dev/null +++ b/src/wl_idle_ext.c @@ -0,0 +1,100 @@ +#include "wayland.h" + + +struct ext_state { + struct ext_idle_notification_v1_listener listener; + struct ext_idle_notification_v1 *notification; + xkb_keycode_t key; + int key_raw; + long idle_time; +}; + +static void on_idle_mouse(void *data, struct ext_idle_notification_v1 *notification) +{ + struct wlIdle *idle = data; + logDbg("Got idle event, responding with zero mouse move"); + wlMouseRelativeMotion(idle->wl_ctx, 0, 0); +} +static void on_idle_key(void *data, struct ext_idle_notification_v1 *notification) +{ + struct wlIdle *idle = data; + struct ext_state *ext = idle->state; + //Second try at this -- press a key we do not care about + logDbg("Got idle event, responding with keypress"); + if (ext->key_raw != -1) { + wlKeyRaw(idle->wl_ctx, ext->key_raw, true); + wlKeyRaw(idle->wl_ctx, ext->key_raw, false); + } else { + wlKey(idle->wl_ctx, ext->key, 0, true); + wlKey(idle->wl_ctx, ext->key, 0, false); + } +} +static void on_resumed(void *data, struct ext_idle_notification_v1 *notification) +{ + logDbg("Got resume event"); +} + +static void inhibit_start(struct wlIdle *idle) +{ + struct ext_state *ext = idle->state; + + ext->notification = ext_idle_notifier_v1_get_idle_notification(idle->wl_ctx->idle_notifier, ext->idle_time * 1000, idle->wl_ctx->seat); + if (!ext->notification) { + logErr("Could not get idle notification"); + return; + } + ext_idle_notification_v1_add_listener(ext->notification, &ext->listener, idle); + wlDisplayFlush(idle->wl_ctx); +} + +static void inhibit_stop(struct wlIdle *idle) +{ + struct ext_state *ext = idle->state; + + if (!ext->notification) { + logDbg("Idle already not inhibited"); + return; + } + ext_idle_notification_v1_destroy(ext->notification); + wlDisplayFlush(idle->wl_ctx); + ext->notification = NULL; +} + +bool wlIdleInitExt(struct wlContext *ctx) +{ + char *idle_method; + char *idle_keyname; + + if (!ctx->idle_notifier) { + logWarn("ext-idle-notify-v1 idle inhibit selected, but no idle notifier support"); + return false; + } + struct ext_state *ext = xcalloc(1, sizeof(*ext)); + ext->listener.resumed = on_resumed; + + ext->idle_time = configTryLong("idle-inhibit/interval", 30); + idle_method = configTryString("idle-inhibit/method", "mouse"); + + if (!strcmp(idle_method, "mouse")) { + ext->listener.idled = on_idle_mouse; + } else if (!strcmp(idle_method, "key")) { + ext->listener.idled = on_idle_key; + /* first try a raw keycode for idle, because in case + * of uinput xkb map might be rather useless */ + ext->key_raw = configTryLong("idle-inhibit/keycode", -1); + idle_keyname = configTryString("idle-inhibit/keyname", "HYPR"); + ext->key = xkb_keymap_key_by_name(ctx->input.xkb_map, idle_keyname); + free(idle_keyname); + } else { + logErr("Unknown idle inhibition method %s, initialization failed", idle_method); + free(idle_method); + free(ext); + return false; + } + free(idle_method); + ctx->idle.wl_ctx = ctx; + ctx->idle.state = ext; + ctx->idle.inhibit_start = inhibit_start; + ctx->idle.inhibit_stop = inhibit_stop; + return true; +}