mutter/src/core/meta-context.c
Jonas Ådahl 74fcdb9a62 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>
2023-02-13 15:34:24 +00:00

817 lines
21 KiB
C

/*
* Copyright (C) 2019 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-context-private.h"
#include <locale.h>
#include <sys/resource.h>
#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"
#ifdef HAVE_PROFILER
#include "core/meta-profiler.h"
#endif
#ifdef HAVE_WAYLAND
#include "wayland/meta-wayland.h"
#endif
enum
{
PROP_0,
PROP_NAME,
PROP_UNSAFE_MODE,
N_PROPS
};
static GParamSpec *obj_props[N_PROPS];
enum
{
STARTED,
PREPARE_SHUTDOWN,
N_SIGNALS
};
static guint signals[N_SIGNALS];
typedef enum _MetaContextState
{
META_CONTEXT_STATE_INIT,
META_CONTEXT_STATE_CONFIGURED,
META_CONTEXT_STATE_SETUP,
META_CONTEXT_STATE_STARTED,
META_CONTEXT_STATE_RUNNING,
META_CONTEXT_STATE_TERMINATED,
} MetaContextState;
typedef struct _MetaContextPrivate
{
char *name;
char *plugin_name;
GType plugin_gtype;
char *gnome_wm_keybindings;
gboolean unsafe_mode;
MetaContextState state;
GOptionContext *option_context;
MetaBackend *backend;
MetaDisplay *display;
#ifdef HAVE_WAYLAND
MetaWaylandCompositor *wayland_compositor;
#endif
GMainLoop *main_loop;
GError *termination_error;
#ifdef RLIMIT_NOFILE
struct rlimit saved_rlimit_nofile;
#endif
#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)
/**
* meta_context_add_option_entries:
* @context: a #MetaContext
* @entries: (array zero-terminated=1): a %NULL-terminated array of #GOptionEntrys
* @translation_domain: (nullable): a translation domain to use, or %NULL
*
* See g_option_context_add_main_entries() for more details.
**/
void
meta_context_add_option_entries (MetaContext *context,
const GOptionEntry *entries,
const char *translation_domain)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_warn_if_fail (priv->state == META_CONTEXT_STATE_INIT);
g_option_context_add_main_entries (priv->option_context,
entries,
translation_domain);
}
/**
* meta_context_add_option_group:
* @context: a #MetaContext
* @group: (transfer full): the group to add
*
* See g_option_context_add_group() for more details.
**/
void
meta_context_add_option_group (MetaContext *context,
GOptionGroup *group)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_warn_if_fail (priv->state == META_CONTEXT_STATE_INIT);
g_return_if_fail (priv->option_context);
g_option_context_add_group (priv->option_context, group);
}
void
meta_context_set_plugin_gtype (MetaContext *context,
GType plugin_gtype)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_return_if_fail (priv->state <= META_CONTEXT_STATE_CONFIGURED);
g_return_if_fail (!priv->plugin_name);
priv->plugin_gtype = plugin_gtype;
}
void
meta_context_set_plugin_name (MetaContext *context,
const char *plugin_name)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_return_if_fail (priv->state <= META_CONTEXT_STATE_CONFIGURED);
g_return_if_fail (priv->plugin_gtype == G_TYPE_NONE);
priv->plugin_name = g_strdup (plugin_name);
}
void
meta_context_set_gnome_wm_keybindings (MetaContext *context,
const char *wm_keybindings)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_return_if_fail (priv->state <= META_CONTEXT_STATE_CONFIGURED);
g_clear_pointer (&priv->gnome_wm_keybindings, g_free);
priv->gnome_wm_keybindings = g_strdup (wm_keybindings);
}
const char *
meta_context_get_gnome_wm_keybindings (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->gnome_wm_keybindings;
}
void
meta_context_notify_ready (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_return_if_fail (priv->state == META_CONTEXT_STATE_STARTED ||
priv->state == META_CONTEXT_STATE_RUNNING);
META_CONTEXT_GET_CLASS (context)->notify_ready (context);
}
const char *
meta_context_get_name (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->name;
}
/**
* meta_context_get_backend:
* @context: The #MetaContext
*
* Returns: (transfer none): the #MetaBackend
*/
MetaBackend *
meta_context_get_backend (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->backend;
}
/**
* meta_context_get_display:
* @context: The #MetaContext
*
* Returns: (transfer none): the #MetaDisplay
*/
MetaDisplay *
meta_context_get_display (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->display;
}
#ifdef HAVE_WAYLAND
MetaWaylandCompositor *
meta_context_get_wayland_compositor (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (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
meta_context_get_compositor_type (MetaContext *context)
{
return META_CONTEXT_GET_CLASS (context)->get_compositor_type (context);
}
gboolean
meta_context_is_replacing (MetaContext *context)
{
return META_CONTEXT_GET_CLASS (context)->is_replacing (context);
}
MetaX11DisplayPolicy
meta_context_get_x11_display_policy (MetaContext *context)
{
return META_CONTEXT_GET_CLASS (context)->get_x11_display_policy (context);
}
#ifdef HAVE_X11
gboolean
meta_context_is_x11_sync (MetaContext *context)
{
return META_CONTEXT_GET_CLASS (context)->is_x11_sync (context);
}
#endif
static gboolean
meta_context_real_configure (MetaContext *context,
int *argc,
char ***argv,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_autoptr (GOptionContext) option_context = NULL;
if (!priv->option_context)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Tried to configure multiple times");
return FALSE;
}
option_context = g_steal_pointer (&priv->option_context);
return g_option_context_parse (option_context, argc, argv, error);
}
/**
* meta_context_configure:
* @context: a #MetaContext
* @argc: (inout): Address of the `argc` parameter of your main() function (or 0
* if @argv is %NULL).
* @argv: (array length=argc) (inout) (nullable): Address of the`argv` parameter
* of main(), or %NULL.
* @error: a return location for errors
*
* Returns: %TRUE if the commandline arguments (if any) were valid and if the
* configuration has been successfull, %FALSE otherwise
*/
gboolean
meta_context_configure (MetaContext *context,
int *argc,
char ***argv,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
MetaCompositorType compositor_type;
g_warn_if_fail (priv->state == META_CONTEXT_STATE_INIT);
if (!META_CONTEXT_GET_CLASS (context)->configure (context, argc, argv, error))
{
priv->state = META_CONTEXT_STATE_TERMINATED;
return FALSE;
}
compositor_type = meta_context_get_compositor_type (context);
switch (compositor_type)
{
case META_COMPOSITOR_TYPE_WAYLAND:
meta_set_is_wayland_compositor (TRUE);
break;
case META_COMPOSITOR_TYPE_X11:
meta_set_is_wayland_compositor (FALSE);
break;
}
priv->state = META_CONTEXT_STATE_CONFIGURED;
return TRUE;
}
static const char *
compositor_type_to_description (MetaCompositorType compositor_type)
{
switch (compositor_type)
{
case META_COMPOSITOR_TYPE_WAYLAND:
return "Wayland display server";
case META_COMPOSITOR_TYPE_X11:
return "X11 window and compositing manager";
}
g_assert_not_reached ();
}
static void
init_introspection (MetaContext *context)
{
#ifdef HAVE_INTROSPECTION
g_irepository_prepend_search_path (MUTTER_PKGLIBDIR);
#endif
}
static gboolean
meta_context_real_setup (MetaContext *context,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
MetaBackend *backend;
backend = META_CONTEXT_GET_CLASS (context)->create_backend (context, error);
if (!backend)
return FALSE;
priv->backend = backend;
return TRUE;
}
gboolean
meta_context_setup (MetaContext *context,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
MetaCompositorType compositor_type;
g_warn_if_fail (priv->state == META_CONTEXT_STATE_CONFIGURED);
if (!priv->plugin_name && priv->plugin_gtype == G_TYPE_NONE)
{
priv->state = META_CONTEXT_STATE_TERMINATED;
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No compositor plugin set");
return FALSE;
}
meta_init_debug_utils ();
compositor_type = meta_context_get_compositor_type (context);
g_message ("Running %s (using mutter %s) as a %s",
priv->name, VERSION,
compositor_type_to_description (compositor_type));
if (priv->plugin_name)
meta_plugin_manager_load (priv->plugin_name);
else
meta_plugin_manager_set_plugin_type (priv->plugin_gtype);
init_introspection (context);
if (!META_CONTEXT_GET_CLASS (context)->setup (context, error))
{
priv->state = META_CONTEXT_STATE_TERMINATED;
return FALSE;
}
priv->state = META_CONTEXT_STATE_SETUP;
return TRUE;
}
gboolean
meta_context_start (MetaContext *context,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_warn_if_fail (priv->state == META_CONTEXT_STATE_SETUP);
meta_prefs_init ();
#ifdef HAVE_WAYLAND
if (meta_context_get_compositor_type (context) ==
META_COMPOSITOR_TYPE_WAYLAND)
priv->wayland_compositor = meta_wayland_compositor_new (context);
#endif
priv->display = meta_display_new (context, error);
if (!priv->display)
{
priv->state = META_CONTEXT_STATE_TERMINATED;
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;
g_signal_emit (context, signals[STARTED], 0);
return TRUE;
}
gboolean
meta_context_run_main_loop (MetaContext *context,
GError **error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_warn_if_fail (priv->state == META_CONTEXT_STATE_STARTED);
if (!priv->main_loop)
{
priv->state = META_CONTEXT_STATE_TERMINATED;
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Tried to run main loop without having started");
return FALSE;
}
priv->state = META_CONTEXT_STATE_RUNNING;
g_main_loop_run (priv->main_loop);
priv->state = META_CONTEXT_STATE_TERMINATED;
g_clear_pointer (&priv->main_loop, g_main_loop_unref);
if (priv->termination_error)
{
g_propagate_error (error, g_steal_pointer (&priv->termination_error));
return FALSE;
}
return TRUE;
}
void
meta_context_terminate (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_warn_if_fail (priv->state == META_CONTEXT_STATE_RUNNING);
g_warn_if_fail (g_main_loop_is_running (priv->main_loop));
g_main_loop_quit (priv->main_loop);
}
void
meta_context_terminate_with_error (MetaContext *context,
GError *error)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
priv->termination_error = g_steal_pointer (&error);
meta_context_terminate (context);
}
void
meta_context_destroy (MetaContext *context)
{
g_object_run_dispose (G_OBJECT (context));
g_object_unref (context);
}
gboolean
meta_context_get_unsafe_mode (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->unsafe_mode;
}
void
meta_context_set_unsafe_mode (MetaContext *context,
gboolean enable)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
if (priv->unsafe_mode == enable)
return;
priv->unsafe_mode = enable;
g_object_notify_by_pspec (G_OBJECT (context), obj_props[PROP_UNSAFE_MODE]);
}
static gboolean
meta_context_save_rlimit_nofile (MetaContext *context,
GError **error)
{
#ifdef RLIMIT_NOFILE
MetaContextPrivate *priv = meta_context_get_instance_private (context);
if (getrlimit (RLIMIT_NOFILE, &priv->saved_rlimit_nofile) != 0)
{
priv->saved_rlimit_nofile.rlim_cur = 0;
priv->saved_rlimit_nofile.rlim_max = 0;
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
"getrlimit failed: %s", g_strerror (errno));
return FALSE;
}
return TRUE;
#else
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOSYS,
"Missing support for RLIMIT_NOFILE");
return FALSE;
#endif
}
/**
* meta_context_raise_rlimit_nofile:
* @context: a #MetaContext
* @error: a return location for errors
*
* Raises the RLIMIT_NOFILE limit value to the hard limit.
*/
gboolean
meta_context_raise_rlimit_nofile (MetaContext *context,
GError **error)
{
#ifdef RLIMIT_NOFILE
struct rlimit new_rlimit;
if (getrlimit (RLIMIT_NOFILE, &new_rlimit) != 0)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
"getrlimit failed: %s", g_strerror (errno));
return FALSE;
}
/* Raise the rlimit_nofile value to the hard limit */
new_rlimit.rlim_cur = new_rlimit.rlim_max;
if (setrlimit (RLIMIT_NOFILE, &new_rlimit) != 0)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
"setrlimit failed: %s", g_strerror (errno));
return FALSE;
}
return TRUE;
#else
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOSYS,
"Missing support for RLIMIT_NOFILE");
return FALSE;
#endif
}
/**
* meta_context_restore_rlimit_nofile:
* @context: a #MetaContext
* @error: a return location for errors
*
* Restores the RLIMIT_NOFILE limits from when the #MetaContext was created.
*/
gboolean
meta_context_restore_rlimit_nofile (MetaContext *context,
GError **error)
{
#ifdef RLIMIT_NOFILE
MetaContextPrivate *priv = meta_context_get_instance_private (context);
if (priv->saved_rlimit_nofile.rlim_cur == 0)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
"RLIMIT_NOFILE not saved");
return FALSE;
}
if (setrlimit (RLIMIT_NOFILE, &priv->saved_rlimit_nofile) != 0)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
"setrlimit failed: %s", g_strerror (errno));
return FALSE;
}
return TRUE;
#else
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOSYS,
"Missing support for RLIMIT_NOFILE");
return FALSE;
#endif
}
static void
meta_context_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MetaContext *context = META_CONTEXT (object);
MetaContextPrivate *priv = meta_context_get_instance_private (context);
switch (prop_id)
{
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_UNSAFE_MODE:
g_value_set_boolean (value, priv->unsafe_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_context_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MetaContext *context = META_CONTEXT (object);
MetaContextPrivate *priv = meta_context_get_instance_private (context);
switch (prop_id)
{
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
case PROP_UNSAFE_MODE:
meta_context_set_unsafe_mode (META_CONTEXT (object),
g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_context_dispose (GObject *object)
{
MetaContext *context = META_CONTEXT (object);
MetaContextPrivate *priv = meta_context_get_instance_private (context);
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
if (priv->display)
meta_display_close (priv->display, META_CURRENT_TIME);
g_clear_object (&priv->display);
#ifdef HAVE_WAYLAND
g_clear_object (&priv->wayland_compositor);
#endif
g_clear_pointer (&priv->backend, meta_backend_destroy);
g_clear_pointer (&priv->option_context, g_option_context_free);
g_clear_pointer (&priv->main_loop, g_main_loop_unref);
G_OBJECT_CLASS (meta_context_parent_class)->dispose (object);
}
static void
meta_context_finalize (GObject *object)
{
MetaContext *context = META_CONTEXT (object);
MetaContextPrivate *priv = meta_context_get_instance_private (context);
#ifdef HAVE_PROFILER
g_clear_object (&priv->profiler);
#endif
g_clear_pointer (&priv->gnome_wm_keybindings, g_free);
g_clear_pointer (&priv->plugin_name, g_free);
g_clear_pointer (&priv->name, g_free);
G_OBJECT_CLASS (meta_context_parent_class)->finalize (object);
}
static void
meta_context_class_init (MetaContextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = meta_context_get_property;
object_class->set_property = meta_context_set_property;
object_class->dispose = meta_context_dispose;
object_class->finalize = meta_context_finalize;
klass->configure = meta_context_real_configure;
klass->setup = meta_context_real_setup;
obj_props[PROP_NAME] =
g_param_spec_string ("name",
"name",
"Human readable name",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_UNSAFE_MODE] =
g_param_spec_boolean ("unsafe-mode",
"unsafe mode",
"Unsafe mode",
FALSE,
G_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, obj_props);
signals[STARTED] =
g_signal_new ("started",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[PREPARE_SHUTDOWN] =
g_signal_new ("prepare-shutdown",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
meta_context_init (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
g_autoptr (GError) error = NULL;
#ifdef HAVE_PROFILER
priv->profiler = meta_profiler_new ();
#endif
priv->plugin_gtype = G_TYPE_NONE;
priv->gnome_wm_keybindings = g_strdup ("Mutter");
if (!setlocale (LC_ALL, ""))
g_warning ("Locale not understood by C library");
bindtextdomain (GETTEXT_PACKAGE, MUTTER_LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
priv->option_context = g_option_context_new (NULL);
g_option_context_set_main_group (priv->option_context,
g_option_group_new (NULL, NULL, NULL,
context, NULL));
if (!meta_context_save_rlimit_nofile (context, &error))
{
if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOSYS))
g_warning ("Failed to save the nofile limit: %s", error->message);
}
}