diff --git a/src/meson.build b/src/meson.build index a902f7df1..947f7b45a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -595,6 +595,8 @@ if have_wayland 'wayland/meta-wayland-dma-buf.h', 'wayland/meta-wayland-dnd-surface.c', 'wayland/meta-wayland-dnd-surface.h', + 'wayland/meta-wayland-filter-manager.c', + 'wayland/meta-wayland-filter-manager.h', 'wayland/meta-wayland-gtk-shell.c', 'wayland/meta-wayland-gtk-shell.h', 'wayland/meta-wayland.h', diff --git a/src/tests/meson.build b/src/tests/meson.build index ebf22bf5e..79f7a7e91 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -408,6 +408,9 @@ if have_native_tests 'suite': 'wayland', 'sources': [ 'wayland-unit-tests.c', + dummy_client_header, + dummy_server_header, + dummy_protocol_code, wayland_test_utils, ], }, diff --git a/src/tests/protocol/dummy.xml b/src/tests/protocol/dummy.xml new file mode 100644 index 000000000..a8b486b9d --- /dev/null +++ b/src/tests/protocol/dummy.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/tests/protocol/meson.build b/src/tests/protocol/meson.build index 62d3fce1e..b4a9ae2d7 100644 --- a/src/tests/protocol/meson.build +++ b/src/tests/protocol/meson.build @@ -30,3 +30,36 @@ test_driver_protocol_code = custom_target( '@INPUT@', '@OUTPUT@', ] ) + +dummy_server_header = custom_target( + 'dummy server header', + input: 'dummy.xml', + output: 'dummy-server-protocol.h', + command: [ + wayland_scanner, + 'server-header', + '@INPUT@', '@OUTPUT@', + ] +) + +dummy_client_header = custom_target( + 'dummy client header', + input: 'dummy.xml', + output: 'dummy-client-protocol.h', + command: [ + wayland_scanner, + 'client-header', + '@INPUT@', '@OUTPUT@', + ] +) + +dummy_protocol_code = custom_target( + 'dummy protocol code', + input: 'dummy.xml', + output: 'dummy-protocol.c', + command: [ + wayland_scanner, + 'private-code', + '@INPUT@', '@OUTPUT@', + ] +) diff --git a/src/tests/wayland-unit-tests.c b/src/tests/wayland-unit-tests.c index 748f9d9a4..89c39e5e7 100644 --- a/src/tests/wayland-unit-tests.c +++ b/src/tests/wayland-unit-tests.c @@ -29,8 +29,13 @@ #include "tests/meta-test-utils.h" #include "tests/meta-wayland-test-driver.h" #include "tests/meta-wayland-test-utils.h" +#include "wayland/meta-wayland-client-private.h" +#include "wayland/meta-wayland-filter-manager.h" #include "wayland/meta-wayland-surface.h" +#include "dummy-client-protocol.h" +#include "dummy-server-protocol.h" + static MetaContext *test_context; static MetaWaylandTestDriver *test_driver; static MetaVirtualMonitor *virtual_monitor; @@ -677,6 +682,176 @@ xdg_foreign_set_parent_of (void) meta_wayland_test_client_finish (wayland_test_client); } +static MetaWaylandAccess +dummy_global_filter (const struct wl_client *client, + const struct wl_global *global, + gpointer user_data) +{ + MetaWaylandClient *allowed_client = META_WAYLAND_CLIENT (user_data); + + if (g_object_get_data (G_OBJECT (allowed_client), + "test-client-destroyed")) + return META_WAYLAND_ACCESS_DENIED; + else if (meta_wayland_client_matches (allowed_client, client)) + return META_WAYLAND_ACCESS_ALLOWED; + else + return META_WAYLAND_ACCESS_DENIED; +} + +static void +dummy_bind (struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) + +{ + g_assert_not_reached (); +} + +static void +handle_registry_global (void *user_data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + gboolean *global_seen = user_data; + + if (strcmp (interface, dummy_interface.name) == 0) + *global_seen = TRUE; +} + +static void +handle_registry_global_remove (void *user_data, + struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + handle_registry_global, + handle_registry_global_remove +}; + +static gpointer +test_client_thread_func (gpointer user_data) +{ + int fd = GPOINTER_TO_INT (user_data); + struct wl_display *wl_display; + struct wl_registry *wl_registry; + gboolean global_seen = FALSE; + + wl_display = wl_display_connect_to_fd (fd); + g_assert_nonnull (wl_display); + + wl_registry = wl_display_get_registry (wl_display); + wl_registry_add_listener (wl_registry, ®istry_listener, &global_seen); + wl_display_roundtrip (wl_display); + wl_registry_destroy (wl_registry); + + wl_display_disconnect (wl_display); + + return GINT_TO_POINTER (global_seen); +} + +static void +on_client_destroyed (MetaWaylandClient *client, + gboolean *client_destroyed) +{ + *client_destroyed = TRUE; + g_object_set_data (G_OBJECT (client), "test-client-destroyed", + GINT_TO_POINTER (TRUE)); +} + +static void +wayland_registry_filter (void) +{ + g_autoptr (GError) error = NULL; + MetaWaylandCompositor *wayland_compositor = + meta_context_get_wayland_compositor (test_context); + MetaWaylandFilterManager *filter_manager = + meta_wayland_compositor_get_filter_manager (wayland_compositor); + struct wl_display *wayland_display = + meta_wayland_compositor_get_wayland_display (wayland_compositor); + struct wl_global *dummy_global; + int fd; + g_autoptr (MetaWaylandClient) client1 = NULL; + g_autoptr (MetaWaylandClient) client2 = NULL; + g_autoptr (MetaWaylandClient) client3 = NULL; + g_autoptr (GThread) thread1 = NULL; + g_autoptr (GThread) thread2 = NULL; + g_autoptr (GThread) thread3 = NULL; + gboolean client1_destroyed = FALSE; + gboolean client2_destroyed = FALSE; + gboolean client3_destroyed = FALSE; + gboolean client1_saw_global; + gboolean client2_saw_global; + gboolean client3_saw_global; + + client1 = meta_wayland_client_new_indirect (test_context, &error); + g_assert_nonnull (client1); + g_assert_null (error); + client2 = meta_wayland_client_new_indirect (test_context, &error); + g_assert_nonnull (client2); + g_assert_null (error); + client3 = meta_wayland_client_new_indirect (test_context, &error); + g_assert_nonnull (client3); + g_assert_null (error); + + g_signal_connect (client1, "client-destroyed", + G_CALLBACK (on_client_destroyed), &client1_destroyed); + g_signal_connect (client2, "client-destroyed", + G_CALLBACK (on_client_destroyed), &client2_destroyed); + g_signal_connect (client3, "client-destroyed", + G_CALLBACK (on_client_destroyed), &client3_destroyed); + + dummy_global = wl_global_create (wayland_display, + &dummy_interface, + 1, NULL, dummy_bind); + meta_wayland_filter_manager_add_global (filter_manager, + dummy_global, + dummy_global_filter, + client1); + + fd = meta_wayland_client_setup_fd (client1, &error); + g_assert_cmpint (fd, >=, 0); + g_assert_null (error); + thread1 = g_thread_new ("test client thread 1", + test_client_thread_func, + GINT_TO_POINTER (fd)); + + fd = meta_wayland_client_setup_fd (client2, &error); + g_assert_cmpint (fd, >=, 0); + g_assert_null (error); + thread2 = g_thread_new ("test client thread 2", + test_client_thread_func, + GINT_TO_POINTER (fd)); + + while (!client1_destroyed || !client2_destroyed) + g_main_context_iteration (NULL, TRUE); + + client1_saw_global = GPOINTER_TO_INT (g_thread_join (thread1)); + client2_saw_global = GPOINTER_TO_INT (g_thread_join (thread2)); + + g_assert_true (client1_saw_global); + g_assert_false (client2_saw_global); + + meta_wayland_filter_manager_remove_global (filter_manager, dummy_global); + wl_global_destroy (dummy_global); + + fd = meta_wayland_client_setup_fd (client3, &error); + g_assert_cmpint (fd, >=, 0); + g_assert_null (error); + thread3 = g_thread_new ("test client thread 3", + test_client_thread_func, + GINT_TO_POINTER (fd)); + while (!client3_destroyed) + g_main_context_iteration (NULL, TRUE); + + client3_saw_global = GPOINTER_TO_INT (g_thread_join (thread3)); + g_assert_false (client3_saw_global); +} + static void on_before_tests (void) { @@ -722,6 +897,8 @@ init_tests (void) toplevel_bounds_monitors); g_test_add_func ("/wayland/xdg-foreign/set-parent-of", xdg_foreign_set_parent_of); + g_test_add_func ("/wayland/registry/filter", + wayland_registry_filter); } int diff --git a/src/wayland/meta-wayland-filter-manager.c b/src/wayland/meta-wayland-filter-manager.c new file mode 100644 index 000000000..9f1e3efd5 --- /dev/null +++ b/src/wayland/meta-wayland-filter-manager.c @@ -0,0 +1,104 @@ +/* + * Copyright 2023 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. + */ + +#include "config.h" + +#include "meta-wayland-filter-manager.h" + +#include "meta-wayland.h" + +struct _MetaWaylandFilterManager +{ + GHashTable *filters; +}; + +typedef struct _MetaWaylandFilter +{ + MetaWaylandFilterFunc func; + gpointer user_data; +} MetaWaylandFilter; + +static bool +global_filter_func (const struct wl_client *client, + const struct wl_global *global, + void *user_data) +{ + MetaWaylandFilterManager *filter_manager = user_data; + MetaWaylandFilter *filter; + + filter = g_hash_table_lookup (filter_manager->filters, global); + if (!filter) + return true; + + switch (filter->func (client, global, filter->user_data)) + { + case META_WAYLAND_ACCESS_ALLOWED: + return true; + case META_WAYLAND_ACCESS_DENIED: + return false; + } + + g_assert_not_reached (); +} + +MetaWaylandFilterManager * +meta_wayland_filter_manager_new (MetaWaylandCompositor *compositor) +{ + struct wl_display *wayland_display = + meta_wayland_compositor_get_wayland_display (compositor); + MetaWaylandFilterManager *filter_manager; + + filter_manager = g_new0 (MetaWaylandFilterManager, 1); + filter_manager->filters = g_hash_table_new_full (NULL, NULL, NULL, g_free); + wl_display_set_global_filter (wayland_display, + global_filter_func, filter_manager); + + return filter_manager; +} + +void +meta_wayland_filter_manager_free (MetaWaylandFilterManager *filter_manager) +{ + g_hash_table_unref (filter_manager->filters); + g_free (filter_manager); +} + +void +meta_wayland_filter_manager_add_global (MetaWaylandFilterManager *filter_manager, + struct wl_global *global, + MetaWaylandFilterFunc filter_func, + gpointer user_data) +{ + MetaWaylandFilter *filter; + + g_return_if_fail (!g_hash_table_lookup (filter_manager->filters, global)); + + filter = g_new0 (MetaWaylandFilter, 1); + filter->func = filter_func; + filter->user_data = user_data; + + g_hash_table_insert (filter_manager->filters, global, filter); +} + +void +meta_wayland_filter_manager_remove_global (MetaWaylandFilterManager *filter_manager, + struct wl_global *global) +{ + g_hash_table_remove (filter_manager->filters, global); +} diff --git a/src/wayland/meta-wayland-filter-manager.h b/src/wayland/meta-wayland-filter-manager.h new file mode 100644 index 000000000..789dfc959 --- /dev/null +++ b/src/wayland/meta-wayland-filter-manager.h @@ -0,0 +1,56 @@ +/* + * Copyright 2023 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. + */ + +#ifndef META_WAYLAND_FILTER_MANAGER_H +#define META_WAYLAND_FILTER_MANAGER_H + +#include +#include + +#include "core/util-private.h" +#include "wayland/meta-wayland-types.h" + +typedef enum _MetaWaylandAccess +{ + META_WAYLAND_ACCESS_ALLOWED, + META_WAYLAND_ACCESS_DENIED, +} MetaWaylandAccess; + +typedef struct _MetaWaylandFilterManager MetaWaylandFilterManager; + +typedef MetaWaylandAccess (* MetaWaylandFilterFunc) (const struct wl_client *client, + const struct wl_global *global, + gpointer user_data); + +MetaWaylandFilterManager * meta_wayland_filter_manager_new (MetaWaylandCompositor *compositor); + +void meta_wayland_filter_manager_free (MetaWaylandFilterManager *filter_manager); + +META_EXPORT_TEST +void meta_wayland_filter_manager_add_global (MetaWaylandFilterManager *filter_manager, + struct wl_global *global, + MetaWaylandFilterFunc filter_func, + gpointer user_data); + + +META_EXPORT_TEST +void meta_wayland_filter_manager_remove_global (MetaWaylandFilterManager *filter_manager, + struct wl_global *global); + +#endif /* META_WAYLAND_FILTER_MANAGER_H */ diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h index 44e302358..81c38d9e2 100644 --- a/src/wayland/meta-wayland-types.h +++ b/src/wayland/meta-wayland-types.h @@ -73,4 +73,6 @@ typedef struct _MetaXWaylandManager MetaXWaylandManager; typedef struct _MetaWaylandXdgForeign MetaWaylandXdgForeign; +typedef struct _MetaWaylandFilterManager MetaWaylandFilterManager; + #endif diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c index ebb911094..61f6d485b 100644 --- a/src/wayland/meta-wayland.c +++ b/src/wayland/meta-wayland.c @@ -38,6 +38,7 @@ #include "wayland/meta-wayland-data-device.h" #include "wayland/meta-wayland-dma-buf.h" #include "wayland/meta-wayland-egl-stream.h" +#include "wayland/meta-wayland-filter-manager.h" #include "wayland/meta-wayland-inhibit-shortcuts-dialog.h" #include "wayland/meta-wayland-inhibit-shortcuts.h" #include "wayland/meta-wayland-legacy-xdg-foreign.h" @@ -75,6 +76,8 @@ static char *_display_name_override; typedef struct _MetaWaylandCompositorPrivate { gboolean is_wayland_egl_display_bound; + + MetaWaylandFilterManager *filter_manager; } MetaWaylandCompositorPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaWaylandCompositor, meta_wayland_compositor, @@ -451,6 +454,8 @@ static void meta_wayland_compositor_finalize (GObject *object) { MetaWaylandCompositor *compositor = META_WAYLAND_COMPOSITOR (object); + MetaWaylandCompositorPrivate *priv = + meta_wayland_compositor_get_instance_private (compositor); MetaBackend *backend = meta_context_get_backend (compositor->context); ClutterActor *stage = meta_backend_get_stage (backend); @@ -470,6 +475,8 @@ meta_wayland_compositor_finalize (GObject *object) g_clear_pointer (&compositor->seat, meta_wayland_seat_free); + g_clear_pointer (&priv->filter_manager, meta_wayland_filter_manager_free); + g_clear_pointer (&compositor->display_name, g_free); g_clear_pointer (&compositor->wayland_display, wl_display_destroy); g_clear_pointer (&compositor->source, g_source_destroy); @@ -480,6 +487,9 @@ meta_wayland_compositor_finalize (GObject *object) static void meta_wayland_compositor_init (MetaWaylandCompositor *compositor) { + MetaWaylandCompositorPrivate *priv = + meta_wayland_compositor_get_instance_private (compositor); + compositor->scheduled_surface_associations = g_hash_table_new (NULL, NULL); wl_log_set_handler_server (meta_wayland_log_func); @@ -487,6 +497,8 @@ meta_wayland_compositor_init (MetaWaylandCompositor *compositor) compositor->wayland_display = wl_display_create (); if (compositor->wayland_display == NULL) g_error ("Failed to create the global wl_display"); + + priv->filter_manager = meta_wayland_filter_manager_new (compositor); } static void @@ -861,3 +873,12 @@ meta_wayland_compositor_get_wayland_display (MetaWaylandCompositor *compositor) { return compositor->wayland_display; } + +MetaWaylandFilterManager * +meta_wayland_compositor_get_filter_manager (MetaWaylandCompositor *compositor) +{ + MetaWaylandCompositorPrivate *priv = + meta_wayland_compositor_get_instance_private (compositor); + + return priv->filter_manager; +} diff --git a/src/wayland/meta-wayland.h b/src/wayland/meta-wayland.h index cf13fa107..e0e5c9251 100644 --- a/src/wayland/meta-wayland.h +++ b/src/wayland/meta-wayland.h @@ -102,8 +102,12 @@ MetaXWaylandManager * meta_wayland_compositor_get_xwayland_manager (MetaWaylan META_EXPORT_TEST MetaContext * meta_wayland_compositor_get_context (MetaWaylandCompositor *compositor); +META_EXPORT_TEST struct wl_display * meta_wayland_compositor_get_wayland_display (MetaWaylandCompositor *compositor); gboolean meta_wayland_compositor_is_grabbed (MetaWaylandCompositor *compositor); +META_EXPORT_TEST +MetaWaylandFilterManager * meta_wayland_compositor_get_filter_manager (MetaWaylandCompositor *compositor); + #endif diff --git a/src/wayland/meta-xwayland-grab-keyboard.c b/src/wayland/meta-xwayland-grab-keyboard.c index 69213b65a..25322517b 100644 --- a/src/wayland/meta-xwayland-grab-keyboard.c +++ b/src/wayland/meta-xwayland-grab-keyboard.c @@ -26,6 +26,7 @@ #include "meta/meta-backend.h" #include "backends/meta-settings-private.h" +#include "wayland/meta-wayland-filter-manager.h" #include "wayland/meta-wayland-private.h" #include "wayland/meta-wayland-versions.h" #include "wayland/meta-wayland-seat.h" @@ -339,12 +340,37 @@ bind_keyboard_grab (struct wl_client *client, NULL, NULL); } +static MetaWaylandAccess +xwayland_grab_keyboard_filter (const struct wl_client *client, + const struct wl_global *global, + gpointer user_data) +{ + MetaWaylandCompositor *compositor = user_data; + MetaXWaylandManager *xwayland_manager = &compositor->xwayland_manager; + + if (client == xwayland_manager->client) + return META_WAYLAND_ACCESS_ALLOWED; + else + return META_WAYLAND_ACCESS_DENIED; +} + gboolean meta_xwayland_grab_keyboard_init (MetaWaylandCompositor *compositor) { - return (wl_global_create (compositor->wayland_display, - &zwp_xwayland_keyboard_grab_manager_v1_interface, - META_ZWP_XWAYLAND_KEYBOARD_GRAB_V1_VERSION, - NULL, - bind_keyboard_grab) != NULL); + struct wl_global *global; + MetaWaylandFilterManager *filter_manager; + + global = wl_global_create (compositor->wayland_display, + &zwp_xwayland_keyboard_grab_manager_v1_interface, + META_ZWP_XWAYLAND_KEYBOARD_GRAB_V1_VERSION, + NULL, + bind_keyboard_grab); + + filter_manager = meta_wayland_compositor_get_filter_manager (compositor); + meta_wayland_filter_manager_add_global (filter_manager, + global, + xwayland_grab_keyboard_filter, + compositor); + + return TRUE; } diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c index b8c858483..8a50b93f3 100644 --- a/src/wayland/meta-xwayland.c +++ b/src/wayland/meta-xwayland.c @@ -73,11 +73,6 @@ static void meta_xwayland_stop_xserver (MetaXWaylandManager *manager); static void meta_xwayland_set_primary_output (MetaX11Display *x11_display); -static bool -meta_xwayland_global_filter (const struct wl_client *client, - const struct wl_global *global, - void *data); - static MetaMonitorManager * monitor_manager_from_x11_display (MetaX11Display *x11_display) { @@ -1127,9 +1122,7 @@ meta_xwayland_init (MetaXWaylandManager *manager, /* Xwayland specific protocol, needs to be filtered out for all other clients */ meta_xwayland_grab_keyboard_init (compositor); - wl_display_set_global_filter (compositor->wayland_display, - meta_xwayland_global_filter, - compositor); + return TRUE; } @@ -1299,23 +1292,6 @@ meta_xwayland_manager_handle_xevent (MetaXWaylandManager *manager, return FALSE; } -static bool -meta_xwayland_global_filter (const struct wl_client *client, - const struct wl_global *global, - void *data) -{ - MetaWaylandCompositor *compositor = (MetaWaylandCompositor *) data; - MetaXWaylandManager *xwayland_manager = &compositor->xwayland_manager; - - /* Keyboard grabbing protocol is for Xwayland only */ - if (client != xwayland_manager->client) - return (wl_global_get_interface (global) != - &zwp_xwayland_keyboard_grab_manager_v1_interface); - - /* All others are visible to all clients */ - return true; -} - gboolean meta_xwayland_signal (MetaXWaylandManager *manager, int signum,