From 7e147624bf5246c740f82aced7c5abe6ea7bd76b Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Fri, 16 May 2025 15:06:14 +0400 Subject: [PATCH 1/3] [Wayland] Implement xdg-activation-v1 protocol --- meson.build | 2 +- src/core/startup-notification.c | 14 +- src/meson.build | 10 + src/wayland/meta-wayland-activation.c | 425 ++++++++++++++++++++++++++ src/wayland/meta-wayland-activation.h | 31 ++ src/wayland/meta-wayland-keyboard.c | 12 + src/wayland/meta-wayland-keyboard.h | 4 + src/wayland/meta-wayland-private.h | 1 + src/wayland/meta-wayland-types.h | 2 + src/wayland/meta-wayland-versions.h | 1 + src/wayland/meta-wayland.c | 2 + src/wayland/meta-window-wayland.c | 12 - 12 files changed, 502 insertions(+), 14 deletions(-) create mode 100644 src/wayland/meta-wayland-activation.c create mode 100644 src/wayland/meta-wayland-activation.h diff --git a/meson.build b/meson.build index c799a188c..0deb46c3f 100644 --- a/meson.build +++ b/meson.build @@ -38,7 +38,7 @@ gudev_req = '>= 232' # wayland version requirements wayland_server_req = '>= 1.13.0' -wayland_protocols_req = '>= 1.19' +wayland_protocols_req = '>= 1.21' # native backend version requirements libinput_req = '>= 1.7' diff --git a/src/core/startup-notification.c b/src/core/startup-notification.c index 99c0d9d06..26948b78d 100644 --- a/src/core/startup-notification.c +++ b/src/core/startup-notification.c @@ -62,6 +62,7 @@ enum enum { SEQ_COMPLETE, + SEQ_TIMEOUT, N_SEQ_SIGNALS }; @@ -268,6 +269,13 @@ meta_startup_sequence_class_init (MetaStartupSequenceClass *klass) NULL, NULL, NULL, G_TYPE_NONE, 0); + seq_signals[SEQ_TIMEOUT] = + g_signal_new ("timeout", + META_TYPE_STARTUP_SEQUENCE, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + seq_props[PROP_SEQ_ID] = g_param_spec_string ("id", "ID", @@ -488,7 +496,11 @@ startup_sequence_timeout (void *data) "Timed out sequence %s\n", meta_startup_sequence_get_id (sequence)); - meta_startup_sequence_complete (sequence); + if (!meta_startup_sequence_get_completed (sequence)) + { + g_signal_emit (sequence, seq_signals[SEQ_TIMEOUT], 0, sequence); + meta_startup_sequence_complete (sequence); + } meta_startup_notification_remove_sequence (sn, sequence); } diff --git a/src/meson.build b/src/meson.build index 193472bd2..dd7dc73fd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -489,6 +489,8 @@ if have_wayland 'wayland/meta-pointer-lock-wayland.h', 'wayland/meta-selection-source-wayland.c', 'wayland/meta-selection-source-wayland-private.h', + 'wayland/meta-wayland-activation.c', + 'wayland/meta-wayland-activation.h', 'wayland/meta-wayland-actor-surface.c', 'wayland/meta-wayland-actor-surface.h', 'wayland/meta-wayland-buffer.c', @@ -813,6 +815,7 @@ if have_wayland ['tablet', 'unstable', 'v2', ], ['text-input', 'unstable', 'v3', ], ['viewporter', 'stable', ], + ['xdg-activation', 'staging', 'v1', ], ['xdg-foreign', 'unstable', 'v1', ], ['xdg-output', 'unstable', 'v1', ], ['xdg-shell', 'unstable', 'v6', ], @@ -840,6 +843,13 @@ if have_wayland '@0@/@1@/@2@.xml'.format(protocol_type, protocol_name, output_base)) + elif protocol_type == 'staging' + protocol_version = p.get(2) + output_base = '@0@-@1@'.format(protocol_name, protocol_version) + input = join_paths(protocols_dir, + '@0@/@1@/@2@.xml'.format(protocol_type, + protocol_name, + output_base)) elif protocol_type == 'private' output_base = protocol_name input = 'wayland/protocol/@0@.xml'.format(protocol_name) diff --git a/src/wayland/meta-wayland-activation.c b/src/wayland/meta-wayland-activation.c new file mode 100644 index 000000000..ec2c6185c --- /dev/null +++ b/src/wayland/meta-wayland-activation.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2020 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#include "config.h" + +#include "wayland/meta-wayland-activation.h" + +#include +#include + +#include "wayland/meta-wayland-private.h" +#include "wayland/meta-wayland-versions.h" + +#include "xdg-activation-v1-server-protocol.h" + +typedef struct _MetaXdgActivationToken MetaXdgActivationToken; + +struct _MetaWaylandActivation +{ + MetaWaylandCompositor *compositor; + struct wl_list resource_list; + struct wl_list token_list; + GHashTable *tokens; +}; + +struct _MetaXdgActivationToken +{ + MetaWaylandSurface *surface; + MetaWaylandSeat *seat; + MetaWaylandActivation *activation; + MetaStartupSequence *sequence; + struct wl_listener surface_listener; + char *app_id; + char *token; + uint32_t serial; + gulong sequence_complete_id; + gulong sequence_timeout_id; + gboolean committed; +}; + +static void +unbind_resource (struct wl_resource *resource) +{ + wl_list_remove (wl_resource_get_link (resource)); +} + +static void +token_set_serial (struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + struct wl_resource *seat_resource) +{ + MetaXdgActivationToken *token = wl_resource_get_user_data (resource); + MetaWaylandSeat *seat = wl_resource_get_user_data (seat_resource); + + token->serial = serial; + token->seat = seat; +} + +static void +token_set_app_id (struct wl_client *client, + struct wl_resource *resource, + const char *app_id) +{ + MetaXdgActivationToken *token = wl_resource_get_user_data (resource); + + g_clear_pointer (&token->app_id, g_free); + token->app_id = g_strdup (app_id); +} + +static void +token_set_surface (struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + MetaXdgActivationToken *token = wl_resource_get_user_data (resource); + MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource); + + token->surface = surface; + wl_resource_add_destroy_listener (surface_resource, + &token->surface_listener); +} + +static void +sequence_complete_cb (MetaStartupSequence *sequence, + MetaXdgActivationToken *token) +{ + MetaWaylandActivation *activation = token->activation; + MetaDisplay *display = meta_get_display (); + + if (!g_hash_table_contains (activation->tokens, token->token)) + return; + + meta_startup_notification_remove_sequence (display->startup_notification, + sequence); + g_hash_table_remove (activation->tokens, token->token); +} + +static void +sequence_timeout_cb (MetaStartupSequence *sequence, + MetaXdgActivationToken *token) +{ + MetaWaylandActivation *activation = token->activation; + + g_hash_table_remove (activation->tokens, token->token); +} + +static char * +create_startup_token (MetaWaylandActivation *activation, + MetaDisplay *display) +{ + g_autofree char *uuid = NULL, *token = NULL; + + do + { + g_clear_pointer (&uuid, g_free); + g_clear_pointer (&token, g_free); + uuid = g_uuid_string_random (); + token = g_strdup_printf ("%s_TIME%d", uuid, + meta_display_get_current_time (display)); + } + while (g_hash_table_contains (activation->tokens, token)); + + return g_steal_pointer (&token); +} + +static void +token_commit (struct wl_client *client, + struct wl_resource *resource) +{ + MetaXdgActivationToken *token = wl_resource_get_user_data (resource); + MetaWaylandActivation *activation = token->activation; + MetaDisplay *display = meta_get_display (); + uint64_t timestamp; + + if (token->committed) + { + wl_resource_post_error (resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "Activation token was already used"); + return; + } + + timestamp = meta_display_get_current_time_roundtrip (display); + + token->committed = TRUE; + token->token = create_startup_token (activation, display); + token->sequence = g_object_new (META_TYPE_STARTUP_SEQUENCE, + "id", token->token, + "application-id", token->app_id, + "timestamp", timestamp, + NULL); + + token->sequence_complete_id = + g_signal_connect (token->sequence, + "complete", + G_CALLBACK (sequence_complete_cb), + token); + + token->sequence_timeout_id = + g_signal_connect (token->sequence, + "timeout", + G_CALLBACK (sequence_timeout_cb), + token); + + meta_startup_notification_add_sequence (display->startup_notification, + token->sequence); + + xdg_activation_token_v1_send_done (resource, token->token); + g_hash_table_insert (activation->tokens, token->token, token); +} + +static void +token_destroy (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct xdg_activation_token_v1_interface token_interface = { + token_set_serial, + token_set_app_id, + token_set_surface, + token_commit, + token_destroy, +}; + +static void +meta_xdg_activation_token_free (MetaXdgActivationToken *token) +{ + if (token->sequence) + { + g_clear_signal_handler (&token->sequence_complete_id, + token->sequence); + g_clear_signal_handler (&token->sequence_timeout_id, + token->sequence); + g_clear_object (&token->sequence); + } + + if (token->surface) + wl_list_remove (&token->surface_listener.link); + + g_free (token->app_id); + g_free (token->token); + g_free (token); +} + +static void +token_handle_surface_destroy (struct wl_listener *listener, + void *data) +{ + MetaXdgActivationToken *token = wl_container_of (listener, token, + surface_listener); + + token->surface = NULL; + wl_list_remove (&token->surface_listener.link); +} + +static void +meta_wayland_activation_token_create_new_resource (MetaWaylandActivation *activation, + struct wl_client *client, + struct wl_resource *activation_resource, + uint32_t id) +{ + MetaXdgActivationToken *token; + struct wl_resource *token_resource; + + token = g_new0 (MetaXdgActivationToken, 1); + token->activation = activation; + + token_resource = + wl_resource_create (client, &xdg_activation_token_v1_interface, + wl_resource_get_version (activation_resource), + id); + wl_resource_set_implementation (token_resource, &token_interface, + token, unbind_resource); + wl_resource_set_user_data (token_resource, token); + wl_list_insert (&activation->token_list, + wl_resource_get_link (token_resource)); + token->surface_listener.notify = token_handle_surface_destroy; +} + +static void +activation_destroy (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +activation_get_activation_token (struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + MetaWaylandActivation *activation = wl_resource_get_user_data (resource); + + meta_wayland_activation_token_create_new_resource (activation, + client, + resource, + id); +} + +static gboolean +token_can_activate (MetaXdgActivationToken *token) +{ + MetaWaylandSeat *seat; + + if (!token->seat) + return FALSE; + if (!token->surface) + return FALSE; + + seat = token->seat; + + if (seat->keyboard && + meta_wayland_keyboard_can_grab_surface (seat->keyboard, + token->surface, + token->serial)) + return TRUE; + + return meta_wayland_seat_get_grab_info (seat, + token->surface, + token->serial, + FALSE, NULL, NULL); +} + +static gboolean +startup_sequence_is_recent (MetaDisplay *display, + MetaStartupSequence *sequence) +{ + uint32_t seq_timestamp_ms, last_user_time_ms; + + seq_timestamp_ms = meta_startup_sequence_get_timestamp (sequence); + last_user_time_ms = meta_display_get_last_user_time (display); + + return seq_timestamp_ms >= last_user_time_ms; +} + +static void +activation_activate (struct wl_client *client, + struct wl_resource *resource, + const char *token_str, + struct wl_resource *surface_resource) +{ + MetaWaylandActivation *activation = wl_resource_get_user_data (resource); + MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource); + MetaDisplay *display = meta_get_display (); + MetaXdgActivationToken *token; + MetaStartupSequence *sequence; + MetaWindow *window; + + window = meta_wayland_surface_get_window (surface); + if (!window) + return; + + token = g_hash_table_lookup (activation->tokens, token_str); + if (token) + { + sequence = token->sequence; + } + else + { + sequence = meta_startup_notification_lookup_sequence (display->startup_notification, + token_str); + } + + if (!sequence) + return; + + if ((token && token_can_activate (token)) || + (!token && startup_sequence_is_recent (display, sequence))) + { + uint32_t timestamp; + int32_t workspace_idx; + + workspace_idx = meta_startup_sequence_get_workspace (sequence); + timestamp = meta_startup_sequence_get_timestamp (sequence); + + if (workspace_idx >= 0) + meta_window_change_workspace_by_index (window, workspace_idx, TRUE); + + meta_window_activate_full (window, timestamp, + META_CLIENT_TYPE_APPLICATION, NULL); + } + else + { + meta_window_set_demands_attention (window); + } + + meta_startup_sequence_complete (sequence); +} + +static const struct xdg_activation_v1_interface activation_interface = { + activation_destroy, + activation_get_activation_token, + activation_activate, +}; + +static void +bind_activation (struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + MetaWaylandCompositor *compositor = data; + MetaWaylandActivation *activation = compositor->activation; + struct wl_resource *resource; + + resource = wl_resource_create (client, &xdg_activation_v1_interface, + MIN (version, META_XDG_ACTIVATION_V1_VERSION), + id); + wl_resource_set_implementation (resource, &activation_interface, + activation, unbind_resource); + wl_resource_set_user_data (resource, activation); + wl_list_insert (&activation->resource_list, + wl_resource_get_link (resource)); +} + +void +meta_wayland_activation_finalize (MetaWaylandCompositor *compositor) +{ + g_hash_table_destroy (compositor->activation->tokens); + g_clear_pointer (&compositor->activation, g_free); +} + +void +meta_wayland_activation_init (MetaWaylandCompositor *compositor) +{ + MetaWaylandActivation *activation; + + activation = g_new0 (MetaWaylandActivation, 1); + activation->compositor = compositor; + wl_list_init (&activation->resource_list); + wl_list_init (&activation->token_list); + + activation->tokens = + g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) meta_xdg_activation_token_free); + + wl_global_create (compositor->wayland_display, + &xdg_activation_v1_interface, + META_XDG_ACTIVATION_V1_VERSION, + compositor, bind_activation); + + compositor->activation = activation; +} diff --git a/src/wayland/meta-wayland-activation.h b/src/wayland/meta-wayland-activation.h new file mode 100644 index 000000000..53cef4526 --- /dev/null +++ b/src/wayland/meta-wayland-activation.h @@ -0,0 +1,31 @@ +/* + * Wayland Support + * + * Copyright (C) 2020 Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: Carlos Garnacho + */ + +#pragma once + +#include +#include + +#include "wayland/meta-wayland-types.h" + +void meta_wayland_activation_finalize (MetaWaylandCompositor *compositor); + +void meta_wayland_activation_init (MetaWaylandCompositor *compositor); diff --git a/src/wayland/meta-wayland-keyboard.c b/src/wayland/meta-wayland-keyboard.c index 37731d0ad..017970032 100644 --- a/src/wayland/meta-wayland-keyboard.c +++ b/src/wayland/meta-wayland-keyboard.c @@ -878,6 +878,18 @@ meta_wayland_keyboard_create_new_resource (MetaWaylandKeyboard *keyboard, } } +gboolean +meta_wayland_keyboard_can_grab_surface (MetaWaylandKeyboard *keyboard, + MetaWaylandSurface *surface, + uint32_t serial) +{ + if (keyboard->focus_surface != surface) + return FALSE; + + return (keyboard->focus_serial == serial || + meta_wayland_keyboard_can_popup (keyboard, serial)); +} + gboolean meta_wayland_keyboard_can_popup (MetaWaylandKeyboard *keyboard, uint32_t serial) diff --git a/src/wayland/meta-wayland-keyboard.h b/src/wayland/meta-wayland-keyboard.h index 1dd3b12ba..d708b03f8 100644 --- a/src/wayland/meta-wayland-keyboard.h +++ b/src/wayland/meta-wayland-keyboard.h @@ -130,6 +130,10 @@ void meta_wayland_keyboard_create_new_resource (MetaWaylandKeyboard *keyboard, struct wl_resource *seat_resource, uint32_t id); +gboolean meta_wayland_keyboard_can_grab_surface (MetaWaylandKeyboard *keyboard, + MetaWaylandSurface *surface, + uint32_t serial); + gboolean meta_wayland_keyboard_can_popup (MetaWaylandKeyboard *keyboard, uint32_t serial); diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h index 727009b07..34c864ea5 100644 --- a/src/wayland/meta-wayland-private.h +++ b/src/wayland/meta-wayland-private.h @@ -85,6 +85,7 @@ struct _MetaWaylandCompositor MetaWaylandSeat *seat; MetaWaylandTabletManager *tablet_manager; + MetaWaylandActivation *activation; GHashTable *scheduled_surface_associations; }; diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h index 00712ad1f..aeb6f6178 100644 --- a/src/wayland/meta-wayland-types.h +++ b/src/wayland/meta-wayland-types.h @@ -61,4 +61,6 @@ typedef struct _MetaWaylandWindowConfiguration MetaWaylandWindowConfiguration; typedef struct _MetaWaylandPointerClient MetaWaylandPointerClient; +typedef struct _MetaWaylandActivation MetaWaylandActivation; + #endif diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h index 2d6ce5ace..e50da66b2 100644 --- a/src/wayland/meta-wayland-versions.h +++ b/src/wayland/meta-wayland-versions.h @@ -55,5 +55,6 @@ #define META_GTK_TEXT_INPUT_VERSION 1 #define META_ZWP_TEXT_INPUT_V3_VERSION 1 #define META_WP_VIEWPORTER_VERSION 1 +#define META_XDG_ACTIVATION_V1_VERSION 1 #endif diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c index e4b09cbfc..078d92d0f 100644 --- a/src/wayland/meta-wayland.c +++ b/src/wayland/meta-wayland.c @@ -31,6 +31,7 @@ #include "clutter/clutter.h" #include "clutter/wayland/clutter-wayland-compositor.h" #include "core/main-private.h" +#include "wayland/meta-wayland-activation.h" #include "wayland/meta-wayland-data-device.h" #include "wayland/meta-wayland-dma-buf.h" #include "wayland/meta-wayland-egl-stream.h" @@ -438,6 +439,7 @@ meta_wayland_compositor_setup (MetaWaylandCompositor *wayland_compositor) meta_wayland_surface_inhibit_shortcuts_dialog_init (); meta_wayland_text_input_init (compositor); meta_wayland_gtk_text_input_init (compositor); + meta_wayland_activation_init (compositor); /* Xwayland specific protocol, needs to be filtered out for all other clients */ if (meta_xwayland_grab_keyboard_init (compositor)) diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c index ed030b937..8bb4b63fd 100644 --- a/src/wayland/meta-window-wayland.c +++ b/src/wayland/meta-window-wayland.c @@ -730,16 +730,6 @@ meta_window_wayland_calculate_layer (MetaWindow *window) return meta_window_get_default_layer (window); } -static void -meta_window_wayland_map (MetaWindow *window) -{ -} - -static void -meta_window_wayland_unmap (MetaWindow *window) -{ -} - static void meta_window_wayland_finalize (GObject *object) { @@ -778,8 +768,6 @@ meta_window_wayland_class_init (MetaWindowWaylandClass *klass) window_class->can_ping = meta_window_wayland_can_ping; window_class->are_updates_frozen = meta_window_wayland_are_updates_frozen; window_class->calculate_layer = meta_window_wayland_calculate_layer; - window_class->map = meta_window_wayland_map; - window_class->unmap = meta_window_wayland_unmap; window_class->is_focus_async = meta_window_wayland_is_focus_async; } From 0902b57a8e464fed4de9a6bbe116e4e87f6e064d Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Fri, 16 May 2025 15:08:56 +0400 Subject: [PATCH 2/3] Small cleanup --- src/wayland/meta-window-wayland.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c index 8bb4b63fd..ed030b937 100644 --- a/src/wayland/meta-window-wayland.c +++ b/src/wayland/meta-window-wayland.c @@ -730,6 +730,16 @@ meta_window_wayland_calculate_layer (MetaWindow *window) return meta_window_get_default_layer (window); } +static void +meta_window_wayland_map (MetaWindow *window) +{ +} + +static void +meta_window_wayland_unmap (MetaWindow *window) +{ +} + static void meta_window_wayland_finalize (GObject *object) { @@ -768,6 +778,8 @@ meta_window_wayland_class_init (MetaWindowWaylandClass *klass) window_class->can_ping = meta_window_wayland_can_ping; window_class->are_updates_frozen = meta_window_wayland_are_updates_frozen; window_class->calculate_layer = meta_window_wayland_calculate_layer; + window_class->map = meta_window_wayland_map; + window_class->unmap = meta_window_wayland_unmap; window_class->is_focus_async = meta_window_wayland_is_focus_async; } From f426910533a2877a04b9ab5e8b2254c3126abe56 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Mon, 4 Aug 2025 13:49:11 +0400 Subject: [PATCH 3/3] window: Further allow new window to get activated before mapped --- src/core/window.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/window.c b/src/core/window.c index 4edee7b75..67a4a7c51 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -5573,6 +5573,13 @@ meta_window_raise (MetaWindow *window) g_return_if_fail (!window->override_redirect); + /* Flush pending visible state now. + * It is important that this runs before meta_stack_raise() because + * showing a window may overwrite its stacking order based on the + * stacking rules for newly shown windows. + */ + meta_window_flush_calc_showing (window); + ancestor = meta_window_find_root_ancestor (window); meta_topic (META_DEBUG_WINDOW_OPS,