wayland: Add wl_global filter manager

One can add a wl_global filter to a wl_display instance, which can be
used to decide what clients should see what globals. This has so far
been used to limit a Xwayland specific protocol extension to only
Xwayland. In order to expand the logic about what globals are filtered
to what clients, introduce a filter manager and port the Xwayland
specific protocol filter to this new manager.

Tests are added, using a new dummy protocol, to ensure that filtering is
working as expected.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2810>
This commit is contained in:
Jonas Ådahl 2023-01-27 15:16:14 +01:00 committed by Marge Bot
parent 192401dee5
commit 91c40a47b3
12 changed files with 443 additions and 30 deletions

View File

@ -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',

View File

@ -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,
],
},

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="dummy">
<description summary=""/>
<interface name="dummy" version="1">
<description summary=""/>
<request name="destroy" type="destructor"/>
<event name="event"/>
</interface>
</protocol>

View File

@ -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@',
]
)

View File

@ -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, &registry_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

View File

@ -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);
}

View File

@ -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 <glib.h>
#include <wayland-server-core.h>
#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 */

View File

@ -73,4 +73,6 @@ typedef struct _MetaXWaylandManager MetaXWaylandManager;
typedef struct _MetaWaylandXdgForeign MetaWaylandXdgForeign;
typedef struct _MetaWaylandFilterManager MetaWaylandFilterManager;
#endif

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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,