mirror of
https://github.com/brl/mutter.git
synced 2024-11-21 23:50:41 -05:00
Introduce the 'service channel' D-Bus service
The service channel D-Bus interface aims to be a "back door" for services that needs special casing in Mutter, e.g. have custom private protocols only meant to be used by that particular service. There are currently no special casing implemented; only the basic service channel infrastructure is added. There is a single method on the interface, that is meant to eventually be used by xdg-desktop-portal-gnome to open a Wayland connection with a private protocol needed for the portal backend's rather special window management needs. The service channel Wayland client works by allowing one instance of each "type", where each time needs to be defined to work in parallel. If a new service client connects, the old one will be disconnected. MetaWaylandClient's are used to manage the service clients, and are assigned the service client type. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2810>
This commit is contained in:
parent
a2f93e76a3
commit
74fcdb9a62
20
data/dbus-interfaces/org.gnome.Mutter.ServiceChannel.xml
Normal file
20
data/dbus-interfaces/org.gnome.Mutter.ServiceChannel.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE node PUBLIC
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<node>
|
||||
|
||||
<interface name="org.gnome.Mutter.ServiceChannel">
|
||||
|
||||
<!--
|
||||
ServiceClientTypes:
|
||||
PORTAL_BACKEND: 1
|
||||
-->
|
||||
<method name="OpenWaylandServiceConnection">
|
||||
<arg name="service_client_type" type="u" direction="in" />
|
||||
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
|
||||
<arg name="fd" type="h" direction="out" />
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
|
||||
</node>
|
@ -22,6 +22,7 @@
|
||||
#define META_CONTEXT_PRIVATE_H
|
||||
|
||||
#include "core/meta-private-enums.h"
|
||||
#include "core/meta-service-channel.h"
|
||||
#include "core/util-private.h"
|
||||
#include "meta/meta-backend.h"
|
||||
#include "meta/meta-context.h"
|
||||
@ -63,9 +64,14 @@ gboolean meta_context_get_unsafe_mode (MetaContext *context);
|
||||
void meta_context_set_unsafe_mode (MetaContext *context,
|
||||
gboolean enable);
|
||||
|
||||
#ifdef HAVE_WAYLAND
|
||||
META_EXPORT_TEST
|
||||
MetaWaylandCompositor * meta_context_get_wayland_compositor (MetaContext *context);
|
||||
|
||||
META_EXPORT_TEST
|
||||
MetaServiceChannel * meta_context_get_service_channel (MetaContext *context);
|
||||
#endif
|
||||
|
||||
MetaX11DisplayPolicy meta_context_get_x11_display_policy (MetaContext *context);
|
||||
|
||||
#ifdef HAVE_X11
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "backends/meta-backend-private.h"
|
||||
#include "compositor/meta-plugin-manager.h"
|
||||
#include "core/display-private.h"
|
||||
#include "core/meta-service-channel.h"
|
||||
#include "core/prefs-private.h"
|
||||
#include "core/util-private.h"
|
||||
|
||||
@ -99,6 +100,10 @@ typedef struct _MetaContextPrivate
|
||||
#ifdef HAVE_PROFILER
|
||||
MetaProfiler *profiler;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WAYLAND
|
||||
MetaServiceChannel *service_channel;
|
||||
#endif
|
||||
} MetaContextPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (MetaContext, meta_context, G_TYPE_OBJECT)
|
||||
@ -243,6 +248,14 @@ meta_context_get_wayland_compositor (MetaContext *context)
|
||||
|
||||
return priv->wayland_compositor;
|
||||
}
|
||||
|
||||
MetaServiceChannel *
|
||||
meta_context_get_service_channel (MetaContext *context)
|
||||
{
|
||||
MetaContextPrivate *priv = meta_context_get_instance_private (context);
|
||||
|
||||
return priv->service_channel;
|
||||
}
|
||||
#endif
|
||||
|
||||
MetaCompositorType
|
||||
@ -437,6 +450,10 @@ meta_context_start (MetaContext *context,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_WAYLAND
|
||||
priv->service_channel = meta_service_channel_new (context);
|
||||
#endif
|
||||
|
||||
priv->main_loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
priv->state = META_CONTEXT_STATE_STARTED;
|
||||
@ -682,6 +699,8 @@ meta_context_dispose (GObject *object)
|
||||
g_signal_emit (context, signals[PREPARE_SHUTDOWN], 0);
|
||||
|
||||
#ifdef HAVE_WAYLAND
|
||||
g_clear_object (&priv->service_channel);
|
||||
|
||||
if (priv->wayland_compositor)
|
||||
meta_wayland_compositor_prepare_shutdown (priv->wayland_compositor);
|
||||
#endif
|
||||
|
308
src/core/meta-service-channel.c
Normal file
308
src/core/meta-service-channel.c
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Red Hat Inc.
|
||||
*
|
||||
* 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 "core/meta-service-channel.h"
|
||||
|
||||
#include "wayland/meta-wayland-client-private.h"
|
||||
|
||||
#define META_SERVICE_CHANNEL_DBUS_SERVICE "org.gnome.Mutter.ServiceChannel"
|
||||
#define META_SERVICE_CHANNEL_DBUS_PATH "/org/gnome/Mutter/ServiceChannel"
|
||||
|
||||
struct _MetaServiceChannel
|
||||
{
|
||||
MetaDBusServiceChannelSkeleton parent;
|
||||
|
||||
guint dbus_name_id;
|
||||
|
||||
MetaContext *context;
|
||||
|
||||
GHashTable *service_clients;
|
||||
};
|
||||
|
||||
typedef struct _MetaServiceClient
|
||||
{
|
||||
MetaWaylandClient *wayland_client;
|
||||
gulong destroyed_handler_id;
|
||||
MetaServiceChannel *service_channel;
|
||||
} MetaServiceClient;
|
||||
|
||||
static void meta_service_channel_init_iface (MetaDBusServiceChannelIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (MetaServiceChannel, meta_service_channel,
|
||||
META_DBUS_TYPE_SERVICE_CHANNEL_SKELETON,
|
||||
G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_SERVICE_CHANNEL,
|
||||
meta_service_channel_init_iface))
|
||||
|
||||
static void
|
||||
meta_service_client_free (MetaServiceClient *service_client)
|
||||
{
|
||||
g_signal_handler_disconnect (service_client->wayland_client,
|
||||
service_client->destroyed_handler_id);
|
||||
g_object_unref (service_client->wayland_client);
|
||||
g_free (service_client);
|
||||
}
|
||||
|
||||
static void
|
||||
on_service_client_destroyed (MetaWaylandClient *wayland_client,
|
||||
MetaServiceClient *service_client)
|
||||
{
|
||||
MetaServiceClientType service_client_type;
|
||||
|
||||
service_client_type =
|
||||
meta_wayland_client_get_service_client_type (wayland_client);
|
||||
g_return_if_fail (service_client_type != META_SERVICE_CLIENT_TYPE_NONE);
|
||||
|
||||
g_hash_table_remove (service_client->service_channel->service_clients,
|
||||
GINT_TO_POINTER (service_client_type));
|
||||
}
|
||||
|
||||
static MetaServiceClient *
|
||||
meta_service_client_new (MetaServiceChannel *service_channel,
|
||||
MetaWaylandClient *wayland_client)
|
||||
{
|
||||
MetaServiceClient *service_client;
|
||||
|
||||
service_client = g_new0 (MetaServiceClient, 1);
|
||||
service_client->service_channel = service_channel;
|
||||
service_client->wayland_client = g_object_ref (wayland_client);
|
||||
service_client->destroyed_handler_id =
|
||||
g_signal_connect (wayland_client, "client-destroyed",
|
||||
G_CALLBACK (on_service_client_destroyed),
|
||||
service_client);
|
||||
|
||||
return service_client;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
verify_service_client_type (uint32_t service_client_type)
|
||||
{
|
||||
switch ((MetaServiceClientType) service_client_type)
|
||||
{
|
||||
case META_SERVICE_CLIENT_TYPE_NONE:
|
||||
return FALSE;
|
||||
case META_SERVICE_CLIENT_TYPE_PORTAL_BACKEND:
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_open_wayland_service_connection (MetaDBusServiceChannel *object,
|
||||
GDBusMethodInvocation *invocation,
|
||||
GUnixFDList *in_fd_list,
|
||||
uint32_t service_client_type)
|
||||
{
|
||||
#ifdef HAVE_WAYLAND
|
||||
MetaServiceChannel *service_channel = META_SERVICE_CHANNEL (object);
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (MetaWaylandClient) wayland_client = NULL;
|
||||
g_autoptr (GUnixFDList) out_fd_list = NULL;
|
||||
int fd;
|
||||
int fd_id;
|
||||
|
||||
if (meta_context_get_compositor_type (service_channel->context) !=
|
||||
META_COMPOSITOR_TYPE_WAYLAND)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_NOT_SUPPORTED,
|
||||
"Not a Wayland compositor");
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (!verify_service_client_type (service_client_type))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_INVALID_ARGS,
|
||||
"Invalid service client type");
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
wayland_client = meta_wayland_client_new_indirect (service_channel->context,
|
||||
&error);
|
||||
if (!wayland_client)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_NOT_SUPPORTED,
|
||||
"Failed to create Wayland client: %s",
|
||||
error->message);
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
meta_wayland_client_assign_service_client_type (wayland_client,
|
||||
service_client_type);
|
||||
|
||||
fd = meta_wayland_client_setup_fd (wayland_client, &error);
|
||||
if (fd < 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_NOT_SUPPORTED,
|
||||
"Failed to setup Wayland client socket: %s",
|
||||
error->message);
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
out_fd_list = g_unix_fd_list_new ();
|
||||
fd_id = g_unix_fd_list_append (out_fd_list, fd, &error);
|
||||
close (fd);
|
||||
|
||||
if (fd_id == -1)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_ACCESS_DENIED,
|
||||
"Failed to append fd: %s",
|
||||
error->message);
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
g_hash_table_replace (service_channel->service_clients,
|
||||
GUINT_TO_POINTER (service_client_type),
|
||||
meta_service_client_new (service_channel,
|
||||
wayland_client));
|
||||
|
||||
meta_dbus_service_channel_complete_open_wayland_service_connection (
|
||||
object, invocation, out_fd_list, g_variant_new_handle (fd_id));
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
#else /* HAVE_WAYLAND */
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
G_DBUS_ERROR,
|
||||
G_DBUS_ERROR_NOT_SUPPORTED,
|
||||
"Wayland not supported",
|
||||
error->message);
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
#endif /* HAVE_WAYLAND */
|
||||
}
|
||||
|
||||
static void
|
||||
meta_service_channel_init_iface (MetaDBusServiceChannelIface *iface)
|
||||
{
|
||||
iface->handle_open_wayland_service_connection =
|
||||
handle_open_wayland_service_connection;
|
||||
}
|
||||
|
||||
static void
|
||||
on_bus_acquired (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
MetaServiceChannel *service_channel = user_data;
|
||||
GDBusInterfaceSkeleton *interface_skeleton =
|
||||
G_DBUS_INTERFACE_SKELETON (service_channel);
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!g_dbus_interface_skeleton_export (interface_skeleton,
|
||||
connection,
|
||||
META_SERVICE_CHANNEL_DBUS_PATH,
|
||||
&error))
|
||||
g_warning ("Failed to export service channel object: %s", error->message);
|
||||
}
|
||||
|
||||
static void
|
||||
on_name_acquired (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_info ("Acquired name %s", name);
|
||||
}
|
||||
|
||||
static void
|
||||
on_name_lost (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_warning ("Lost or failed to acquire name %s", name);
|
||||
}
|
||||
|
||||
static void
|
||||
meta_service_channel_constructed (GObject *object)
|
||||
{
|
||||
MetaServiceChannel *service_channel = META_SERVICE_CHANNEL (object);
|
||||
|
||||
service_channel->service_clients =
|
||||
g_hash_table_new_full (NULL, NULL,
|
||||
NULL, (GDestroyNotify) meta_service_client_free);
|
||||
|
||||
service_channel->dbus_name_id =
|
||||
g_bus_own_name (G_BUS_TYPE_SESSION,
|
||||
META_SERVICE_CHANNEL_DBUS_SERVICE,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
on_bus_acquired,
|
||||
on_name_acquired,
|
||||
on_name_lost,
|
||||
service_channel,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
meta_service_channel_finalize (GObject *object)
|
||||
{
|
||||
MetaServiceChannel *service_channel = META_SERVICE_CHANNEL (object);
|
||||
|
||||
g_clear_pointer (&service_channel->service_clients, g_hash_table_unref);
|
||||
g_clear_handle_id (&service_channel->dbus_name_id, g_bus_unown_name);
|
||||
|
||||
G_OBJECT_CLASS (meta_service_channel_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
meta_service_channel_class_init (MetaServiceChannelClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = meta_service_channel_constructed;
|
||||
object_class->finalize = meta_service_channel_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
meta_service_channel_init (MetaServiceChannel *service_channel)
|
||||
{
|
||||
}
|
||||
|
||||
MetaServiceChannel *
|
||||
meta_service_channel_new (MetaContext *context)
|
||||
{
|
||||
MetaServiceChannel *service_channel;
|
||||
|
||||
service_channel = g_object_new (META_TYPE_SERVICE_CHANNEL, NULL);
|
||||
service_channel->context = context;
|
||||
|
||||
return service_channel;
|
||||
}
|
||||
|
||||
MetaWaylandClient *
|
||||
meta_service_channel_get_service_client (MetaServiceChannel *service_channel,
|
||||
MetaServiceClientType service_client_type)
|
||||
{
|
||||
MetaServiceClient *service_client;
|
||||
|
||||
service_client = g_hash_table_lookup (service_channel->service_clients,
|
||||
GINT_TO_POINTER (service_client_type));
|
||||
if (!service_client)
|
||||
return NULL;
|
||||
|
||||
return service_client->wayland_client;
|
||||
}
|
51
src/core/meta-service-channel.h
Normal file
51
src/core/meta-service-channel.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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_SERVICE_CHANNEL_H
|
||||
#define META_SERVICE_CHANNEL_H
|
||||
|
||||
#ifdef HAVE_WAYLAND
|
||||
|
||||
#include "meta/meta-context.h"
|
||||
|
||||
#include "core/util-private.h"
|
||||
#include "wayland/meta-wayland-types.h"
|
||||
|
||||
#include "meta-dbus-service-channel.h"
|
||||
|
||||
typedef enum _MetaServiceClientType
|
||||
{
|
||||
META_SERVICE_CLIENT_TYPE_NONE,
|
||||
META_SERVICE_CLIENT_TYPE_PORTAL_BACKEND,
|
||||
} MetaServiceClientType;
|
||||
|
||||
#define META_TYPE_SERVICE_CHANNEL (meta_service_channel_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (MetaServiceChannel, meta_service_channel,
|
||||
META, SERVICE_CHANNEL,
|
||||
MetaDBusServiceChannelSkeleton)
|
||||
|
||||
MetaServiceChannel * meta_service_channel_new (MetaContext *context);
|
||||
|
||||
META_EXPORT_TEST
|
||||
MetaWaylandClient * meta_service_channel_get_service_client (MetaServiceChannel *service_channel,
|
||||
MetaServiceClientType service_client_type);
|
||||
|
||||
#endif /* HAVE_WAYLAND */
|
||||
|
||||
#endif /* META_SERVICE_CHANNEL_H */
|
@ -560,6 +560,8 @@ if have_wayland
|
||||
'compositor/meta-surface-actor-wayland.h',
|
||||
'compositor/meta-window-actor-wayland.c',
|
||||
'compositor/meta-window-actor-wayland.h',
|
||||
'core/meta-service-channel.c',
|
||||
'core/meta-service-channel.h',
|
||||
'wayland/meta-cursor-sprite-wayland.c',
|
||||
'wayland/meta-cursor-sprite-wayland.h',
|
||||
'wayland/meta-pointer-confinement-wayland.c',
|
||||
@ -879,6 +881,11 @@ dbus_interfaces = [
|
||||
'interface': 'org.gnome.Mutter.InputMapping.xml',
|
||||
'prefix': 'org.gnome.Mutter.',
|
||||
},
|
||||
{
|
||||
'name': 'meta-dbus-service-channel',
|
||||
'interface': 'org.gnome.Mutter.ServiceChannel.xml',
|
||||
'prefix': 'org.gnome.Mutter.',
|
||||
},
|
||||
]
|
||||
|
||||
if have_profiler
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <wayland-server-core.h>
|
||||
|
||||
#include "core/meta-service-channel.h"
|
||||
#include "core/util-private.h"
|
||||
#include "meta/meta-wayland-client.h"
|
||||
|
||||
@ -37,4 +38,9 @@ META_EXPORT_TEST
|
||||
gboolean meta_wayland_client_matches (MetaWaylandClient *client,
|
||||
const struct wl_client *wayland_client);
|
||||
|
||||
void meta_wayland_client_assign_service_client_type (MetaWaylandClient *client,
|
||||
MetaServiceClientType service_client_type);
|
||||
|
||||
MetaServiceClientType meta_wayland_client_get_service_client_type (MetaWaylandClient *client);
|
||||
|
||||
#endif /* META_WAYLAND_CLIENT_PRIVATE_H */
|
||||
|
@ -73,6 +73,7 @@ struct _MetaWaylandClient
|
||||
|
||||
struct wl_client *wayland_client;
|
||||
struct wl_listener client_destroy_listener;
|
||||
MetaServiceClientType service_client_type;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (MetaWaylandClient, meta_wayland_client, G_TYPE_OBJECT)
|
||||
@ -109,6 +110,7 @@ meta_wayland_client_class_init (MetaWaylandClientClass *klass)
|
||||
static void
|
||||
meta_wayland_client_init (MetaWaylandClient *client)
|
||||
{
|
||||
client->service_client_type = META_SERVICE_CLIENT_TYPE_NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -483,3 +485,18 @@ meta_wayland_client_matches (MetaWaylandClient *client,
|
||||
|
||||
return client->wayland_client == wayland_client;
|
||||
}
|
||||
|
||||
void
|
||||
meta_wayland_client_assign_service_client_type (MetaWaylandClient *client,
|
||||
MetaServiceClientType service_client_type)
|
||||
{
|
||||
g_return_if_fail (client->service_client_type ==
|
||||
META_SERVICE_CLIENT_TYPE_NONE);
|
||||
client->service_client_type = service_client_type;
|
||||
}
|
||||
|
||||
MetaServiceClientType
|
||||
meta_wayland_client_get_service_client_type (MetaWaylandClient *client)
|
||||
{
|
||||
return client->service_client_type;
|
||||
}
|
||||
|
@ -75,4 +75,6 @@ typedef struct _MetaWaylandXdgForeign MetaWaylandXdgForeign;
|
||||
|
||||
typedef struct _MetaWaylandFilterManager MetaWaylandFilterManager;
|
||||
|
||||
typedef struct _MetaWaylandClient MetaWaylandClient;
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user