mutter/src/core/meta-context.c
Bilal Elmoussaoui c388abe1e3 core/context: Make notify_ready vfunc optional
As it is used only in a X11 build for session management
See next commit

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3540>
2024-01-31 12:25:39 +00:00

853 lines
22 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, see <http://www.gnu.org/licenses/>.
*
*/
#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
char *trace_file;
MetaProfiler *profiler;
#endif
#ifdef HAVE_WAYLAND
MetaServiceChannel *service_channel;
#endif
MetaDebugControl *debug_control;
} 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);
if (META_CONTEXT_GET_CLASS (context)->notify_ready)
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
/**
* meta_context_get_wayland_compositor:
* @context: The #MetaContext
*
* Get the #MetaWaylandCompositor associated with the MetaContext. The might be
* none currently associated if the context hasn't been started or if the
* requested compositor type is not %META_COMPOSITOR_TYPE_WAYLAND.
*
* Returns: (transfer none) (nullable): the #MetaWaylandCompositor
*/
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
#ifdef HAVE_PROFILER
MetaProfiler *
meta_context_get_profiler (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->profiler;
}
void
meta_context_set_trace_file (MetaContext *context,
const char *trace_file)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
priv->trace_file = g_strdup (trace_file);
}
#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);
if (!g_option_context_parse (option_context, argc, argv, error))
return FALSE;
priv->debug_control = g_object_new (META_TYPE_DEBUG_CONTROL,
"context", context,
NULL);
return TRUE;
}
/**
* 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 successful, %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;
}
#ifdef HAVE_PROFILER
priv->profiler = meta_profiler_new (priv->trace_file);
#endif
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);
}
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_object (&priv->debug_control);
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);
g_clear_pointer (&priv->trace_file, g_free);
#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", NULL, NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_UNSAFE_MODE] =
g_param_spec_boolean ("unsafe-mode", NULL, NULL,
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;
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);
}
}
MetaDebugControl *
meta_context_get_debug_control (MetaContext *context)
{
MetaContextPrivate *priv = meta_context_get_instance_private (context);
return priv->debug_control;
}