diff --git a/include/wlr/types/wlr_foreign_toplevel_management_v1.h b/include/wlr/types/wlr_foreign_toplevel_management_v1.h index d21c5fc70..d8a680a4c 100644 --- a/include/wlr/types/wlr_foreign_toplevel_management_v1.h +++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h @@ -36,6 +36,11 @@ enum wlr_foreign_toplevel_handle_v1_state { WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN = (1 << 3), }; +enum wlr_foreign_toplevel_handle_v1_responsiveness { + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_RESPONSIVE = (0 << 0), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_UNRESPONSIVE = (1 << 0), +}; + struct wlr_foreign_toplevel_handle_v1_output { struct wl_list link; // wlr_foreign_toplevel_handle_v1.outputs struct wlr_output *output; @@ -57,6 +62,7 @@ struct wlr_foreign_toplevel_handle_v1 { char *app_id; struct wlr_foreign_toplevel_handle_v1 *parent; struct wl_list outputs; // wlr_foreign_toplevel_v1_output.link + uint32_t responsiveness; // enum wlr_foreign_toplevel_v1_responsiveness uint32_t state; // enum wlr_foreign_toplevel_v1_state struct { @@ -151,5 +157,7 @@ void wlr_foreign_toplevel_handle_v1_set_parent( struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_foreign_toplevel_handle_v1 *parent); +void wlr_foreign_toplevel_handle_v1_set_responsive( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool responsive); #endif diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml index 44505bbb6..6334a7256 100644 --- a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and @@ -68,7 +68,7 @@ - + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. @@ -266,5 +266,27 @@ + + + + + + The different responsiveness states that a toplevel can have. If the client didn't receive + any event from the compositor it should assume the client is responsive. + + + + + + + + + This event is emitted when the compositor detects a change in the toplevels responsiveness. + An unresponsive toplevel usually doesn't respond to ping requests send by the compositor + while a responsive one does but a compositor might use more elaborate means to detect if a + toplevel is still usable by the user. + + + diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index b3d902c7e..084db150e 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,8 @@ struct tinywl_server { struct wlr_output_layout *output_layout; struct wl_list outputs; struct wl_listener new_output; + + struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; }; struct tinywl_output { @@ -83,15 +86,20 @@ struct tinywl_toplevel { struct wl_list link; struct tinywl_server *server; struct wlr_xdg_toplevel *xdg_toplevel; + struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; struct wlr_scene_tree *scene_tree; struct wl_listener map; struct wl_listener unmap; struct wl_listener commit; + struct wl_listener set_app_id; + struct wl_listener set_title; + struct wl_listener set_parent; struct wl_listener destroy; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; + struct wl_listener ping_timeout; }; struct tinywl_popup { @@ -151,6 +159,8 @@ static void focus_toplevel(struct tinywl_toplevel *toplevel) { wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } + wlr_log(WLR_DEBUG, "Pinging toplevel %s", toplevel->xdg_toplevel->title); + wlr_xdg_surface_ping(toplevel->xdg_toplevel->base); } static void keyboard_handle_modifiers( @@ -704,18 +714,54 @@ static void xdg_toplevel_commit(struct wl_listener *listener, void *data) { } } +static void xdg_toplevel_set_app_id(struct wl_listener *listener, void *data) { + /* Called when the toplevel wants to set its app ID. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, set_app_id); + const char *app_id = toplevel->xdg_toplevel->app_id; + wlr_foreign_toplevel_handle_v1_set_app_id(toplevel->foreign_toplevel, app_id); +} + +static void xdg_toplevel_set_title(struct wl_listener *listener, void *data) { + /* Called when the toplevel wants to set its title. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, set_title); + const char *title = toplevel->xdg_toplevel->title; + wlr_foreign_toplevel_handle_v1_set_title(toplevel->foreign_toplevel, title); +} + +static void xdg_toplevel_set_parent(struct wl_listener *listener, void *data) { + /* Called when the toplevel wants to set its parent. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, set_parent); + + /* Iterate through list of toplevels and find the tinywl_toplevel whose xdg_toplevel is the + * current toplevel's parent. */ + struct wlr_foreign_toplevel_handle_v1 *foreign_parent_toplevel = NULL; + struct tinywl_toplevel *parent_toplevel; + wl_list_for_each(parent_toplevel, &toplevel->server->toplevels, link) { + if (parent_toplevel->xdg_toplevel == toplevel->xdg_toplevel->parent) { + foreign_parent_toplevel = parent_toplevel->foreign_toplevel; + } + } + wlr_foreign_toplevel_handle_v1_set_parent(toplevel->foreign_toplevel, foreign_parent_toplevel); +} + static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { /* Called when the xdg_toplevel is destroyed. */ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, destroy); + wlr_foreign_toplevel_handle_v1_destroy(toplevel->foreign_toplevel); + wl_list_remove(&toplevel->map.link); wl_list_remove(&toplevel->unmap.link); wl_list_remove(&toplevel->commit.link); + wl_list_remove(&toplevel->set_app_id.link); + wl_list_remove(&toplevel->set_title.link); + wl_list_remove(&toplevel->set_parent.link); wl_list_remove(&toplevel->destroy.link); wl_list_remove(&toplevel->request_move.link); wl_list_remove(&toplevel->request_resize.link); wl_list_remove(&toplevel->request_maximize.link); wl_list_remove(&toplevel->request_fullscreen.link); + wl_list_remove(&toplevel->ping_timeout.link); free(toplevel); } @@ -800,6 +846,14 @@ static void xdg_toplevel_request_fullscreen( } } +static void xdg_toplevel_ping_timeout( + struct wl_listener *listener, void *data) { + struct tinywl_toplevel *toplevel = + wl_container_of(listener, toplevel, ping_timeout); + wlr_log(WLR_DEBUG, "Ping timeout for toplevel %s", toplevel->xdg_toplevel->title); + wlr_foreign_toplevel_handle_v1_set_responsive(toplevel->foreign_toplevel, false); +} + static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { /* This event is raised when a client creates a new toplevel (application window). */ struct tinywl_server *server = wl_container_of(listener, server, new_xdg_toplevel); @@ -814,6 +868,11 @@ static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { toplevel->scene_tree->node.data = toplevel; xdg_toplevel->base->data = toplevel->scene_tree; + /* Setup foreign_toplevel_handle */ + struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel = + wlr_foreign_toplevel_handle_v1_create(server->foreign_toplevel_manager); + toplevel->foreign_toplevel = foreign_toplevel; + /* Listen to the various events it can emit */ toplevel->map.notify = xdg_toplevel_map; wl_signal_add(&xdg_toplevel->base->surface->events.map, &toplevel->map); @@ -821,6 +880,12 @@ static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &toplevel->unmap); toplevel->commit.notify = xdg_toplevel_commit; wl_signal_add(&xdg_toplevel->base->surface->events.commit, &toplevel->commit); + toplevel->set_app_id.notify = xdg_toplevel_set_app_id; + wl_signal_add(&xdg_toplevel->events.set_app_id, &toplevel->set_app_id); + toplevel->set_title.notify = xdg_toplevel_set_title; + wl_signal_add(&xdg_toplevel->events.set_title, &toplevel->set_title); + toplevel->set_parent.notify = xdg_toplevel_set_parent; + wl_signal_add(&xdg_toplevel->events.set_parent, &toplevel->set_parent); toplevel->destroy.notify = xdg_toplevel_destroy; wl_signal_add(&xdg_toplevel->events.destroy, &toplevel->destroy); @@ -834,6 +899,10 @@ static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize); toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen); + + /* Check for responsiveness */ + toplevel->ping_timeout.notify = xdg_toplevel_ping_timeout; + wl_signal_add(&xdg_toplevel->base->events.ping_timeout, &toplevel->ping_timeout); } static void xdg_popup_commit(struct wl_listener *listener, void *data) { @@ -1038,6 +1107,14 @@ int main(int argc, char *argv[]) { wl_signal_add(&server.seat->events.request_set_selection, &server.request_set_selection); + /* Setup foreign_toplevel_management. This protocol allows creation of taskbars, docks etc. + * which can manage the windows (like maximize, minimize etc.). The current code of tinywl does + * not process these requests. However, this protocol has been added to demonstrate the usage of + * responsiveness event. This event allows tinywl to inform foreign_toplevel_management clients + * of unresponsive apps. + */ + server.foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server.wl_display); + /* Add a Unix socket to the Wayland display. */ const char *socket = wl_display_add_socket_auto(server.wl_display); if (!socket) { diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c index c98457a3d..d31c377f2 100644 --- a/types/wlr_foreign_toplevel_management_v1.c +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -7,7 +7,7 @@ #include #include "wlr-foreign-toplevel-management-unstable-v1-protocol.h" -#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 3 +#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 4 #define FOREIGN_TOPLEVEL_HANDLE_V1_STATE_COUNT 32 @@ -445,6 +445,32 @@ void wlr_foreign_toplevel_handle_v1_set_parent( toplevel_update_idle_source(toplevel); } +void wlr_foreign_toplevel_handle_v1_set_responsive( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool responsive) { + enum wlr_foreign_toplevel_handle_v1_responsiveness responsiveness; + if (responsive) { + responsiveness = WLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_RESPONSIVE; + } else { + responsiveness = WLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_UNRESPONSIVE; + } + + if (toplevel->responsiveness == responsiveness) { + // Only inform clients if the state changed + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + if (wl_resource_get_version(resource) < + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_SINCE_VERSION) { + continue; + } + zwlr_foreign_toplevel_handle_v1_send_responsiveness(resource, responsiveness); + } + toplevel->responsiveness = responsiveness; + toplevel_update_idle_source(toplevel); +} + void wlr_foreign_toplevel_handle_v1_destroy( struct wlr_foreign_toplevel_handle_v1 *toplevel) { if (!toplevel) { @@ -536,6 +562,8 @@ wlr_foreign_toplevel_handle_v1_create( wl_list_init(&toplevel->resources); wl_list_init(&toplevel->outputs); + toplevel->responsiveness = WLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_RESPONSIVE; + wl_signal_init(&toplevel->events.request_maximize); wl_signal_init(&toplevel->events.request_minimize); wl_signal_init(&toplevel->events.request_activate); @@ -598,6 +626,11 @@ static void toplevel_send_details_to_toplevel_resource( toplevel_resource_send_parent(resource, toplevel->parent); + if (wl_resource_get_version(resource) >= + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_RESPONSIVENESS_SINCE_VERSION) { + zwlr_foreign_toplevel_handle_v1_send_responsiveness(resource, toplevel->responsiveness); + } + zwlr_foreign_toplevel_handle_v1_send_done(resource); }