mutter/src/core/meta-context.c
Jonas Ådahl adc5489ba7 Add debug controller
The debug controller can optionally, when passing --debug-control,
enable manipulating debug state, so far enabling/disabling HDR, via
D-Bus.

It's always created, in order to have a place to store debug state and
emit signals etc when it changes, but so far, it doesn't have its own
state it tracks, it just mirrors that of the monitor manager.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3432>
2023-12-01 14:25:08 +00:00

860 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);
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);
}
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_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;
}