1518a778ed
This is not strictly necessary when linking the shell with DT_RPATH as the runtime paths will be extracted from the introspected libraries and used from gobject introspection to find them when loading them based on the typelib files, but when linking with DT_RUNPATH, as it's the case for some linkers (e.g. ld.gold, or ld.bfd in some distros like Debian). By explicitly prepending this directory as a library path for GIR, we're making sure that the wrapped shared objects installed in Mutter's and GNOME Shell's private directories can be found in all cases. https://bugzilla.gnome.org/show_bug.cgi?id=777519
470 lines
14 KiB
C
470 lines
14 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 OVERRIDES_SCHEMA "org.gnome.shell.overrides"
|
|
|
|
#define WM_NAME "GNOME Shell"
|
|
#define GNOME_WM_KEYBINDINGS "Mutter,GNOME Shell"
|
|
|
|
static gboolean is_gdm_mode = FALSE;
|
|
static char *session_mode = NULL;
|
|
|
|
#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
|
|
#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
|
|
|
|
enum {
|
|
SHELL_DEBUG_BACKTRACE_WARNINGS = 1,
|
|
};
|
|
static int _shell_debug;
|
|
|
|
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);
|
|
/* ...and the on-screen keyboard service */
|
|
shell_dbus_acquire_name (bus,
|
|
G_BUS_NAME_OWNER_FLAGS_REPLACE,
|
|
&request_name_result,
|
|
"org.gnome.Caribou.Keyboard", FALSE);
|
|
g_object_unref (bus);
|
|
g_object_unref (session);
|
|
}
|
|
|
|
static void
|
|
shell_prefs_init (void)
|
|
{
|
|
ShellGlobal *global = shell_global_get ();
|
|
GSettings *settings = shell_global_get_overrides_settings (global);
|
|
GSettingsSchema *schema;
|
|
char **keys, **k;
|
|
|
|
if (!settings)
|
|
return;
|
|
|
|
g_object_get (G_OBJECT (settings), "settings-schema", &schema, NULL);
|
|
|
|
for (keys = k = g_settings_schema_list_keys (schema); *k; k++)
|
|
meta_prefs_override_preference_schema (*k, g_settings_schema_get_id (schema));
|
|
|
|
g_strfreev (keys);
|
|
g_settings_schema_unref (schema);
|
|
}
|
|
|
|
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 }
|
|
};
|
|
|
|
_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 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);
|
|
|
|
shell_prefs_init ();
|
|
|
|
ecode = meta_run ();
|
|
|
|
g_debug ("Doing final cleanup");
|
|
_shell_global_destroy_gjs_context (shell_global_get ());
|
|
g_object_unref (shell_global_get ());
|
|
|
|
return ecode;
|
|
}
|