From 3a949ea8eb0277f52d10ef0d54760323752321c9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 4 Oct 2022 11:31:23 +0200 Subject: [PATCH] idle-notify-v1: new protocol implementation References: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/29 --- include/wlr/types/wlr_idle_notify_v1.h | 44 +++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_idle_notify_v1.c | 255 +++++++++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 include/wlr/types/wlr_idle_notify_v1.h create mode 100644 types/wlr_idle_notify_v1.c diff --git a/include/wlr/types/wlr_idle_notify_v1.h b/include/wlr/types/wlr_idle_notify_v1.h new file mode 100644 index 000000000..7bc11cb7a --- /dev/null +++ b/include/wlr/types/wlr_idle_notify_v1.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_IDLE_NOTIFY_H +#define WLR_TYPES_WLR_IDLE_NOTIFY_H + +#include + +struct wlr_seat; + +/** + * An idle notifier, implementing the ext-idle-notify-v1 protocol. + */ +struct wlr_idle_notifier_v1; + +/** + * Create the ext_idle_notifier_v1 global. + */ +struct wlr_idle_notifier_v1 *wlr_idle_notifier_v1_create(struct wl_display *display); + +/** + * Inhibit idle. + * + * Compositors should call this function when the idle state is disabled, e.g. + * because a visible client is using the idle-inhibit protocol. + */ +void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, + bool inhibited); + +/** + * Notify for user activity on a seat. + * + * Compositors should call this function whenever an input event is triggered + * on a seat. + */ +void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, + struct wlr_seat *seat); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 35aee3598..4d56bbf4b 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -19,6 +19,7 @@ protocols = { # Staging upstream protocols 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', diff --git a/types/meson.build b/types/meson.build index f7c24e123..853d31c7a 100644 --- a/types/meson.build +++ b/types/meson.build @@ -44,6 +44,7 @@ wlr_files += files( 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', 'wlr_idle.c', + 'wlr_idle_notify_v1.c', 'wlr_input_device.c', 'wlr_input_inhibitor.c', 'wlr_input_method_v2.c', diff --git a/types/wlr_idle_notify_v1.c b/types/wlr_idle_notify_v1.c new file mode 100644 index 000000000..6283dc6a3 --- /dev/null +++ b/types/wlr_idle_notify_v1.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include "ext-idle-notify-v1-protocol.h" + +#define IDLE_NOTIFIER_VERSION 1 + +struct wlr_idle_notifier_v1 { + struct wl_global *global; + + bool inhibited; + struct wl_list notifications; // wlr_idle_notification_v1.link + + struct wl_listener display_destroy; +}; + +struct wlr_idle_notification_v1 { + struct wl_resource *resource; + struct wl_list link; // wlr_idle_notifier_v1.notifications + struct wlr_idle_notifier_v1 *notifier; + struct wlr_seat *seat; + + uint32_t timeout_ms; + struct wl_event_source *timer; + + bool idle; + + struct wl_listener seat_destroy; +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_idle_notifier_v1_interface notifier_impl; + +static const struct ext_idle_notification_v1_interface notification_impl = { + .destroy = resource_handle_destroy, +}; + +// Returns NULL if the resource is inert +static struct wlr_idle_notification_v1 *notification_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_idle_notification_v1_interface, ¬ification_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_idle_notifier_v1 *notifier_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_idle_notifier_v1_interface, ¬ifier_impl)); + return wl_resource_get_user_data(resource); +} + +static void notification_set_idle(struct wlr_idle_notification_v1 *notification, + bool idle) { + if (notification->idle == idle) { + return; + } + + if (idle) { + ext_idle_notification_v1_send_idled(notification->resource); + } else { + ext_idle_notification_v1_send_resumed(notification->resource); + } + + notification->idle = idle; +} + +static int notification_handle_timer(void *data) { + struct wlr_idle_notification_v1 *notification = data; + notification_set_idle(notification, true); + return 0; +} + +static void notification_destroy(struct wlr_idle_notification_v1 *notification) { + if (notification == NULL) { + return; + } + wl_list_remove(¬ification->link); + wl_list_remove(¬ification->seat_destroy.link); + if (notification->timer != NULL) { + wl_event_source_remove(notification->timer); + } + wl_resource_set_user_data(notification->resource, NULL); // make inert + free(notification); +} + +static void notification_reset_timer(struct wlr_idle_notification_v1 *notification) { + if (notification->notifier->inhibited) { + notification_set_idle(notification, false); + if (notification->timer != NULL) { + wl_event_source_timer_update(notification->timer, 0); + } + return; + } + + if (notification->timer != NULL) { + wl_event_source_timer_update(notification->timer, + notification->timeout_ms); + } else { + notification_set_idle(notification, true); + } +} + +static void notification_handle_activity(struct wlr_idle_notification_v1 *notification) { + notification_set_idle(notification, false); + notification_reset_timer(notification); +} + +static void notification_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_idle_notification_v1 *notification = + wl_container_of(listener, notification, seat_destroy); + notification_destroy(notification); +} + +static void notification_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_idle_notification_v1 *notification = + notification_from_resource(resource); + notification_destroy(notification); +} + +static void notifier_handle_get_idle_notification(struct wl_client *client, + struct wl_resource *notifier_resource, uint32_t id, uint32_t timeout, + struct wl_resource *seat_resource) { + struct wlr_idle_notifier_v1 *notifier = + notifier_from_resource(notifier_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + uint32_t version = wl_resource_get_version(notifier_resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_idle_notification_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, ¬ification_impl, NULL, + notification_handle_resource_destroy); + + if (seat_client == NULL) { + return; // leave the resource inert + } + + struct wlr_idle_notification_v1 *notification = + calloc(1, sizeof(*notification)); + if (notification == NULL) { + wl_client_post_no_memory(client); + return; + } + + notification->notifier = notifier; + notification->resource = resource; + notification->timeout_ms = timeout; + notification->seat = seat_client->seat; + + if (timeout > 0) { + struct wl_display *display = wl_client_get_display(client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + notification->timer = wl_event_loop_add_timer(loop, + notification_handle_timer, notification); + if (notification->timer == NULL) { + free(notification); + wl_client_post_no_memory(client); + return; + } + } + + notification->seat_destroy.notify = notification_handle_seat_destroy; + wl_signal_add(&seat_client->seat->events.destroy, ¬ification->seat_destroy); + + wl_resource_set_user_data(resource, notification); + wl_list_insert(¬ifier->notifications, ¬ification->link); + + notification_reset_timer(notification); +} + +static const struct ext_idle_notifier_v1_interface notifier_impl = { + .destroy = resource_handle_destroy, + .get_idle_notification = notifier_handle_get_idle_notification, +}; + +static void notifier_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_idle_notifier_v1 *notifier = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_idle_notifier_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, ¬ifier_impl, notifier, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle_notifier_v1 *notifier = + wl_container_of(listener, notifier, display_destroy); + wl_global_destroy(notifier->global); + free(notifier); +} + +struct wlr_idle_notifier_v1 *wlr_idle_notifier_v1_create(struct wl_display *display) { + struct wlr_idle_notifier_v1 *notifier = calloc(1, sizeof(*notifier)); + if (notifier == NULL) { + return NULL; + } + + notifier->global = wl_global_create(display, + &ext_idle_notifier_v1_interface, IDLE_NOTIFIER_VERSION, notifier, + notifier_bind); + if (notifier->global == NULL) { + free(notifier); + return NULL; + } + + wl_list_init(¬ifier->notifications); + + notifier->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, ¬ifier->display_destroy); + + return notifier; +} + +void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, + bool inhibited) { + if (notifier->inhibited == inhibited) { + return; + } + + notifier->inhibited = inhibited; + + struct wlr_idle_notification_v1 *notification; + wl_list_for_each(notification, ¬ifier->notifications, link) { + notification_reset_timer(notification); + } +} + +void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, + struct wlr_seat *seat) { + if (notifier->inhibited) { + return; + } + + struct wlr_idle_notification_v1 *notification; + wl_list_for_each(notification, ¬ifier->notifications, link) { + if (notification->seat == seat) { + notification_handle_activity(notification); + } + } +}