393d7246cc
GSettings now recognizes per-desktop overrides that can be used to change schemas' default values for a particular desktop. This is not entirely unlike our existing custom override mechanism in mutter, except that it is not limited to keys in org.gnome.mutter, and it doesn't require a separate schema - the latter means that we (and gnome-teak-tool) no longer have to figure out the correct schema for the current login session and just use the original one. https://bugzilla.gnome.org/show_bug.cgi?id=786496
509 lines
15 KiB
C
509 lines
15 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_MALLINFO
|
|
#include <malloc.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <cogl-pango/cogl-pango.h>
|
|
#include <clutter/clutter.h>
|
|
#include <clutter/x11/clutter-x11.h>
|
|
#include <gdk/gdk.h>
|
|
#include <gdk/gdkx.h>
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <girepository.h>
|
|
#include <meta/main.h>
|
|
#include <meta/meta-plugin.h>
|
|
#include <meta/prefs.h>
|
|
#include <atk-bridge.h>
|
|
|
|
#include "shell-global.h"
|
|
#include "shell-global-private.h"
|
|
#include "shell-perf-log.h"
|
|
#include "st.h"
|
|
|
|
extern GType gnome_shell_plugin_get_type (void);
|
|
|
|
#define SHELL_DBUS_SERVICE "org.gnome.Shell"
|
|
#define MAGNIFIER_DBUS_SERVICE "org.gnome.Magnifier"
|
|
|
|
#define WM_NAME "GNOME Shell"
|
|
#define GNOME_WM_KEYBINDINGS "Mutter,GNOME Shell"
|
|
|
|
static gboolean is_gdm_mode = FALSE;
|
|
static char *session_mode = NULL;
|
|
static int caught_signal = 0;
|
|
|
|
#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
|
|
#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
|
|
|
|
enum {
|
|
SHELL_DEBUG_BACKTRACE_WARNINGS = 1,
|
|
SHELL_DEBUG_BACKTRACE_SEGFAULTS = 2,
|
|
};
|
|
static int _shell_debug;
|
|
static gboolean _tracked_signals[NSIG] = { 0 };
|
|
|
|
static void
|
|
shell_dbus_acquire_name (GDBusProxy *bus,
|
|
guint32 request_name_flags,
|
|
guint32 *request_name_result,
|
|
const gchar *name,
|
|
gboolean fatal)
|
|
{
|
|
GError *error = NULL;
|
|
GVariant *request_name_variant;
|
|
|
|
if (!(request_name_variant = g_dbus_proxy_call_sync (bus,
|
|
"RequestName",
|
|
g_variant_new ("(su)", name, request_name_flags),
|
|
0, /* call flags */
|
|
-1, /* timeout */
|
|
NULL, /* cancellable */
|
|
&error)))
|
|
{
|
|
g_printerr ("failed to acquire %s: %s\n", name, error->message);
|
|
g_clear_error (&error);
|
|
if (!fatal)
|
|
return;
|
|
exit (1);
|
|
}
|
|
g_variant_get (request_name_variant, "(u)", request_name_result);
|
|
g_variant_unref (request_name_variant);
|
|
}
|
|
|
|
static void
|
|
shell_dbus_acquire_names (GDBusProxy *bus,
|
|
guint32 request_name_flags,
|
|
const gchar *name,
|
|
gboolean fatal, ...) G_GNUC_NULL_TERMINATED;
|
|
|
|
static void
|
|
shell_dbus_acquire_names (GDBusProxy *bus,
|
|
guint32 request_name_flags,
|
|
const gchar *name,
|
|
gboolean fatal, ...)
|
|
{
|
|
va_list al;
|
|
guint32 request_name_result;
|
|
va_start (al, fatal);
|
|
for (;;)
|
|
{
|
|
shell_dbus_acquire_name (bus,
|
|
request_name_flags,
|
|
&request_name_result,
|
|
name, fatal);
|
|
name = va_arg (al, gchar *);
|
|
if (!name)
|
|
break;
|
|
fatal = va_arg (al, gboolean);
|
|
}
|
|
va_end (al);
|
|
}
|
|
|
|
static void
|
|
shell_dbus_init (gboolean replace)
|
|
{
|
|
GDBusConnection *session;
|
|
GDBusProxy *bus;
|
|
GError *error = NULL;
|
|
guint32 request_name_flags;
|
|
guint32 request_name_result;
|
|
|
|
session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
|
|
|
if (error) {
|
|
g_printerr ("Failed to connect to session bus: %s", error->message);
|
|
exit (1);
|
|
}
|
|
|
|
bus = g_dbus_proxy_new_sync (session,
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
NULL, /* interface info */
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
NULL, /* cancellable */
|
|
&error);
|
|
|
|
if (!bus)
|
|
{
|
|
g_printerr ("Failed to get a session bus proxy: %s", error->message);
|
|
exit (1);
|
|
}
|
|
|
|
request_name_flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
|
|
if (replace)
|
|
request_name_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
|
|
|
|
shell_dbus_acquire_name (bus,
|
|
request_name_flags,
|
|
&request_name_result,
|
|
SHELL_DBUS_SERVICE, TRUE);
|
|
if (!(request_name_result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
|
|
|| request_name_result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER))
|
|
{
|
|
g_printerr (SHELL_DBUS_SERVICE " already exists on bus and --replace not specified\n");
|
|
exit (1);
|
|
}
|
|
|
|
/*
|
|
* We always specify REPLACE_EXISTING to ensure we kill off
|
|
* the existing service if it was running.
|
|
*/
|
|
request_name_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
|
|
|
|
shell_dbus_acquire_names (bus,
|
|
request_name_flags,
|
|
/* Also grab org.gnome.Panel to replace any existing panel process */
|
|
"org.gnome.Panel", TRUE,
|
|
/* ...and the org.gnome.Magnifier service. */
|
|
MAGNIFIER_DBUS_SERVICE, FALSE,
|
|
/* ...and the org.freedesktop.Notifications service. */
|
|
"org.freedesktop.Notifications", FALSE,
|
|
NULL);
|
|
g_object_unref (bus);
|
|
g_object_unref (session);
|
|
}
|
|
|
|
static void
|
|
shell_introspection_init (void)
|
|
{
|
|
|
|
g_irepository_prepend_search_path (MUTTER_TYPELIB_DIR);
|
|
g_irepository_prepend_search_path (GNOME_SHELL_PKGLIBDIR);
|
|
|
|
/* We need to explicitly add the directories where the private libraries are
|
|
* installed to the GIR's library path, so that they can be found at runtime
|
|
* when linking using DT_RUNPATH (instead of DT_RPATH), which is the default
|
|
* for some linkers (e.g. gold) and in some distros (e.g. Debian).
|
|
*/
|
|
g_irepository_prepend_library_path (MUTTER_TYPELIB_DIR);
|
|
g_irepository_prepend_library_path (GNOME_SHELL_PKGLIBDIR);
|
|
}
|
|
|
|
static void
|
|
shell_fonts_init (void)
|
|
{
|
|
CoglPangoFontMap *fontmap;
|
|
|
|
/* Disable text mipmapping; it causes problems on pre-GEM Intel
|
|
* drivers and we should just be rendering text at the right
|
|
* size rather than scaling it. If we do effects where we dynamically
|
|
* zoom labels, then we might want to reconsider.
|
|
*/
|
|
fontmap = COGL_PANGO_FONT_MAP (clutter_get_font_map ());
|
|
cogl_pango_font_map_set_use_mipmapping (fontmap, FALSE);
|
|
}
|
|
|
|
static void
|
|
malloc_statistics_callback (ShellPerfLog *perf_log,
|
|
gpointer data)
|
|
{
|
|
#ifdef HAVE_MALLINFO
|
|
struct mallinfo info = mallinfo ();
|
|
|
|
shell_perf_log_update_statistic_i (perf_log,
|
|
"malloc.arenaSize",
|
|
info.arena);
|
|
shell_perf_log_update_statistic_i (perf_log,
|
|
"malloc.mmapSize",
|
|
info.hblkhd);
|
|
shell_perf_log_update_statistic_i (perf_log,
|
|
"malloc.usedSize",
|
|
info.uordblks);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
shell_perf_log_init (void)
|
|
{
|
|
ShellPerfLog *perf_log = shell_perf_log_get_default ();
|
|
|
|
/* For probably historical reasons, mallinfo() defines the returned values,
|
|
* even those in bytes as int, not size_t. We're determined not to use
|
|
* more than 2G of malloc'ed memory, so are OK with that.
|
|
*/
|
|
shell_perf_log_define_statistic (perf_log,
|
|
"malloc.arenaSize",
|
|
"Amount of memory allocated by malloc() with brk(), in bytes",
|
|
"i");
|
|
shell_perf_log_define_statistic (perf_log,
|
|
"malloc.mmapSize",
|
|
"Amount of memory allocated by malloc() with mmap(), in bytes",
|
|
"i");
|
|
shell_perf_log_define_statistic (perf_log,
|
|
"malloc.usedSize",
|
|
"Amount of malloc'ed memory currently in use",
|
|
"i");
|
|
|
|
shell_perf_log_add_statistics_callback (perf_log,
|
|
malloc_statistics_callback,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
shell_a11y_init (void)
|
|
{
|
|
cally_accessibility_init ();
|
|
|
|
if (clutter_get_accessibility_enabled () == FALSE)
|
|
{
|
|
g_warning ("Accessibility: clutter has no accessibility enabled"
|
|
" skipping the atk-bridge load");
|
|
}
|
|
else
|
|
{
|
|
atk_bridge_adaptor_init (NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
shell_init_debug (const char *debug_env)
|
|
{
|
|
static const GDebugKey keys[] = {
|
|
{ "backtrace-warnings", SHELL_DEBUG_BACKTRACE_WARNINGS },
|
|
{ "backtrace-segfaults", SHELL_DEBUG_BACKTRACE_SEGFAULTS },
|
|
};
|
|
|
|
_shell_debug = g_parse_debug_string (debug_env, keys,
|
|
G_N_ELEMENTS (keys));
|
|
}
|
|
|
|
static void
|
|
default_log_handler (const char *log_domain,
|
|
GLogLevelFlags log_level,
|
|
const char *message,
|
|
gpointer data)
|
|
{
|
|
GTimeVal now;
|
|
|
|
g_get_current_time (&now);
|
|
|
|
if (!log_domain || !g_str_has_prefix (log_domain, "tp-glib"))
|
|
g_log_default_handler (log_domain, log_level, message, data);
|
|
|
|
/* Filter out Gjs logs, those already have the stack */
|
|
if (log_domain && strcmp (log_domain, "Gjs") == 0)
|
|
return;
|
|
|
|
if ((_shell_debug & SHELL_DEBUG_BACKTRACE_WARNINGS) &&
|
|
((log_level & G_LOG_LEVEL_CRITICAL) ||
|
|
(log_level & G_LOG_LEVEL_WARNING)))
|
|
gjs_dumpstack ();
|
|
}
|
|
|
|
static void
|
|
shut_up (const char *domain,
|
|
GLogLevelFlags level,
|
|
const char *message,
|
|
gpointer user_data)
|
|
{
|
|
}
|
|
|
|
static void
|
|
dump_gjs_stack_alarm_sigaction (int signo)
|
|
{
|
|
g_log_set_default_handler (g_log_default_handler, NULL);
|
|
g_warning ("Failed to dump Javascript stack, got stuck");
|
|
g_log_set_default_handler (default_log_handler, NULL);
|
|
|
|
raise (caught_signal);
|
|
}
|
|
|
|
static void
|
|
dump_gjs_stack_on_signal_handler (int signo)
|
|
{
|
|
struct sigaction sa = { 0 };
|
|
gsize i;
|
|
|
|
/* Ignore all the signals starting this point, a part the one we'll raise
|
|
* (which is implicitly ignored here through SA_RESETHAND), this is needed
|
|
* not to get this handler being called by other signals that we were
|
|
* tracking and that might be emitted by code called starting from now.
|
|
*/
|
|
for (i = 0; i < G_N_ELEMENTS (_tracked_signals); ++i)
|
|
{
|
|
if (_tracked_signals[i] && i != signo)
|
|
signal (i, SIG_IGN);
|
|
}
|
|
|
|
/* Waiting at least 5 seconds for the dumpstack, if it fails, we raise the error */
|
|
caught_signal = signo;
|
|
sa.sa_handler = dump_gjs_stack_alarm_sigaction;
|
|
sigemptyset (&sa.sa_mask);
|
|
sigaction (SIGALRM, &sa, NULL);
|
|
|
|
alarm (5);
|
|
gjs_dumpstack ();
|
|
alarm (0);
|
|
|
|
raise (signo);
|
|
}
|
|
|
|
static void
|
|
dump_gjs_stack_on_signal (int signo)
|
|
{
|
|
struct sigaction sa = { 0 };
|
|
|
|
sa.sa_flags = SA_RESETHAND | SA_NODEFER;
|
|
sa.sa_handler = dump_gjs_stack_on_signal_handler;
|
|
sigemptyset (&sa.sa_mask);
|
|
|
|
sigaction (signo, &sa, NULL);
|
|
_tracked_signals[signo] = TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
list_modes (const char *option_name,
|
|
const char *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
ShellGlobal *global;
|
|
GjsContext *context;
|
|
const char *script;
|
|
int status;
|
|
|
|
/* Many of our imports require global to be set, so rather than
|
|
* tayloring our imports carefully here to avoid that dependency,
|
|
* we just set it.
|
|
* ShellGlobal has some GTK+ dependencies, so initialize GTK+; we
|
|
* don't really care if it fails though (e.g. when running from a tty),
|
|
* so we mute all warnings */
|
|
g_log_set_default_handler (shut_up, NULL);
|
|
gtk_init_check (NULL, NULL);
|
|
|
|
_shell_global_init (NULL);
|
|
global = shell_global_get ();
|
|
context = _shell_global_get_gjs_context (global);
|
|
|
|
shell_introspection_init ();
|
|
|
|
script = "imports.ui.environment.init();"
|
|
"imports.ui.sessionMode.listModes();";
|
|
if (!gjs_context_eval (context, script, -1, "<main>", &status, NULL))
|
|
g_message ("Retrieving list of available modes failed.");
|
|
|
|
g_object_unref (context);
|
|
exit (status);
|
|
}
|
|
|
|
static gboolean
|
|
print_version (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
g_print ("GNOME Shell %s\n", VERSION);
|
|
exit (0);
|
|
}
|
|
|
|
GOptionEntry gnome_shell_options[] = {
|
|
{
|
|
"version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
|
|
print_version,
|
|
N_("Print version"),
|
|
NULL
|
|
},
|
|
{
|
|
"gdm-mode", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
|
|
&is_gdm_mode,
|
|
N_("Mode used by GDM for login screen"),
|
|
NULL
|
|
},
|
|
{
|
|
"mode", 0, 0, G_OPTION_ARG_STRING,
|
|
&session_mode,
|
|
N_("Use a specific mode, e.g. “gdm” for login screen"),
|
|
"MODE"
|
|
},
|
|
{
|
|
"list-modes", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
|
|
list_modes,
|
|
N_("List possible modes"),
|
|
NULL
|
|
},
|
|
{ NULL }
|
|
};
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GOptionContext *ctx;
|
|
GError *error = NULL;
|
|
int ecode;
|
|
|
|
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
|
|
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
|
textdomain (GETTEXT_PACKAGE);
|
|
|
|
session_mode = (char *) g_getenv ("GNOME_SHELL_SESSION_MODE");
|
|
|
|
ctx = meta_get_option_context ();
|
|
g_option_context_add_main_entries (ctx, gnome_shell_options, GETTEXT_PACKAGE);
|
|
g_option_context_add_group (ctx, g_irepository_get_option_group ());
|
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
|
{
|
|
g_printerr ("%s: %s\n", argv[0], error->message);
|
|
exit (1);
|
|
}
|
|
|
|
g_option_context_free (ctx);
|
|
|
|
meta_plugin_manager_set_plugin_type (gnome_shell_plugin_get_type ());
|
|
|
|
meta_set_wm_name (WM_NAME);
|
|
meta_set_gnome_wm_keybindings (GNOME_WM_KEYBINDINGS);
|
|
|
|
/* Prevent meta_init() from causing gtk to load the atk-bridge*/
|
|
g_setenv ("NO_AT_BRIDGE", "1", TRUE);
|
|
meta_init ();
|
|
g_unsetenv ("NO_AT_BRIDGE");
|
|
|
|
/* FIXME: Add gjs API to set this stuff and don't depend on the
|
|
* environment. These propagate to child processes.
|
|
*/
|
|
g_setenv ("GJS_DEBUG_OUTPUT", "stderr", TRUE);
|
|
g_setenv ("GJS_DEBUG_TOPICS", "JS ERROR;JS LOG", TRUE);
|
|
|
|
shell_init_debug (g_getenv ("SHELL_DEBUG"));
|
|
|
|
shell_dbus_init (meta_get_replace_current_wm ());
|
|
shell_a11y_init ();
|
|
shell_perf_log_init ();
|
|
shell_introspection_init ();
|
|
shell_fonts_init ();
|
|
|
|
g_log_set_default_handler (default_log_handler, NULL);
|
|
|
|
/* Initialize the global object */
|
|
if (session_mode == NULL)
|
|
session_mode = is_gdm_mode ? (char *)"gdm" : (char *)"user";
|
|
|
|
_shell_global_init ("session-mode", session_mode, NULL);
|
|
|
|
dump_gjs_stack_on_signal (SIGABRT);
|
|
dump_gjs_stack_on_signal (SIGFPE);
|
|
dump_gjs_stack_on_signal (SIGIOT);
|
|
dump_gjs_stack_on_signal (SIGTRAP);
|
|
|
|
if ((_shell_debug & SHELL_DEBUG_BACKTRACE_SEGFAULTS))
|
|
{
|
|
dump_gjs_stack_on_signal (SIGBUS);
|
|
dump_gjs_stack_on_signal (SIGSEGV);
|
|
}
|
|
|
|
ecode = meta_run ();
|
|
|
|
g_debug ("Doing final cleanup");
|
|
_shell_global_destroy_gjs_context (shell_global_get ());
|
|
g_object_unref (shell_global_get ());
|
|
|
|
return ecode;
|
|
}
|