diff --git a/configure.ac b/configure.ac index bb1f93296..6fe0c188c 100644 --- a/configure.ac +++ b/configure.ac @@ -70,7 +70,7 @@ CLUTTER_PACKAGE=clutter-1.0 MUTTER_PC_MODULES=" gtk+-3.0 >= 3.9.11 - gio-2.0 >= 2.25.10 + gio-unix-2.0 >= 2.25.10 pango >= 1.2.0 cairo >= 1.10.0 gsettings-desktop-schemas >= 3.7.3 diff --git a/src/Makefile.am b/src/Makefile.am index dc1cac73a..cbdb0264f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -132,6 +132,7 @@ libmutter_la_SOURCES = \ core/screen-private.h \ meta/screen.h \ meta/types.h \ + core/restart.c \ core/session.c \ core/session.h \ core/stack.c \ @@ -220,6 +221,10 @@ bin_PROGRAMS=mutter mutter_SOURCES = core/mutter.c mutter_LDADD = $(MUTTER_LIBS) libmutter.la +libexec_PROGRAMS = mutter-restart-helper +mutter_restart_helper_SOURCES = core/restart-helper.c +mutter_restart_helper_LDADD = $(MUTTER_LIBS) + if HAVE_INTROSPECTION include $(INTROSPECTION_MAKEFILE) diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index 51e27f3b6..1d22af666 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -74,7 +74,7 @@ #include "meta-window-actor-private.h" #include "meta-window-group.h" #include "window-private.h" /* to check window->hidden */ -#include "display-private.h" /* for meta_display_lookup_x_window() */ +#include "display-private.h" #include "util-private.h" #include #include @@ -181,6 +181,10 @@ get_output_window (MetaScreen *screen) xroot = meta_screen_get_xroot (screen); output = XCompositeGetOverlayWindow (xdisplay, xroot); + /* Now that we've gotten taken a reference count on the COW, we + * can close the helper that is holding on to it */ + meta_restart_finish (); + meta_core_add_old_event_mask (xdisplay, output, &mask); XISetMask (mask.mask, XI_KeyPress); diff --git a/src/core/display-private.h b/src/core/display-private.h index 5c47d12f5..c7b37c751 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -483,4 +483,11 @@ void meta_display_set_input_focus_xwindow (MetaDisplay *display, Window window, guint32 timestamp); +gboolean meta_display_show_restart_message (MetaDisplay *display, + const char *message); +gboolean meta_display_request_restart (MetaDisplay *display); + +void meta_restart_init (void); +void meta_restart_finish (void); + #endif diff --git a/src/core/display.c b/src/core/display.c index e8a284fe2..dac97f4ee 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -130,6 +130,8 @@ enum WINDOW_MARKED_URGENT, GRAB_OP_BEGIN, GRAB_OP_END, + SHOW_RESTART_MESSAGE, + RESTART, LAST_SIGNAL }; @@ -306,6 +308,59 @@ meta_display_class_init (MetaDisplayClass *klass) META_TYPE_WINDOW, META_TYPE_GRAB_OP); + /** + * MetaDisplay::show-restart-message: + * @display: the #MetaDisplay instance + * @message: (allow-none): The message to display, or %NULL + * to clear a previous restart message. + * + * The ::show-restart-message signal will be emitted to indicate + * that the compositor should show a message during restart. This is + * emitted when meta_restart() is called, either by Mutter + * internally or by the embedding compositor. The message should be + * immediately added to the Clutter stage in its final form - + * ::restart will be emitted to exit the application and leave the + * stage contents frozen as soon as the the stage is painted again. + * + * On case of failure to restart, this signal will be emitted again + * with %NULL for @message. + * + * Returns: %TRUE means the message was added to the stage; %FALSE + * indicates that the compositor did not show the message. + */ + display_signals[SHOW_RESTART_MESSAGE] = + g_signal_new ("show-restart-message", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, + NULL, NULL, + G_TYPE_BOOLEAN, 1, + G_TYPE_STRING); + + /** + * MetaDisplay::restart: + * @display: the #MetaDisplay instance + * + * The ::restart signal is emitted to indicate that compositor + * should reexec the process. This is + * emitted when meta_restart() is called, either by Mutter + * internally or by the embedding compositor. See also + * ::show-restart-message. + * + * Returns: %FALSE to indicate that the compositor could not + * be restarted. When the compositor is restarted, the signal + * should not return. + */ + display_signals[RESTART] = + g_signal_new ("restart", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, + NULL, NULL, + G_TYPE_BOOLEAN, 0); + g_object_class_install_property (object_class, PROP_FOCUS_WINDOW, g_param_spec_object ("focus-window", @@ -5939,3 +5994,28 @@ meta_display_clear_mouse_mode (MetaDisplay *display) { display->mouse_mode = FALSE; } + +gboolean +meta_display_show_restart_message (MetaDisplay *display, + const char *message) +{ + gboolean result = FALSE; + + g_signal_emit (display, + display_signals[SHOW_RESTART_MESSAGE], 0, + message, &result); + + return result; +} + +gboolean +meta_display_request_restart (MetaDisplay *display) +{ + gboolean result = FALSE; + + g_signal_emit (display, + display_signals[RESTART], 0, + &result); + + return result; +} diff --git a/src/core/main.c b/src/core/main.c index 3f5f3c5a0..3d92a32f6 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -51,6 +51,7 @@ #include #include "ui.h" #include "session.h" +#include "stereo.h" #include #include @@ -444,6 +445,8 @@ meta_init (void) meta_ui_init (); + meta_restart_init (); + /* * Clutter can only be initialized after the UI. */ diff --git a/src/core/restart-helper.c b/src/core/restart-helper.c new file mode 100644 index 000000000..57a19fb27 --- /dev/null +++ b/src/core/restart-helper.c @@ -0,0 +1,82 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * SECTION:restart-helper + * @short_description: helper program during a restart + * + * To smoothly restart Mutter, we want to keep the composite + * overlay window enabled during the restart. This is done by + * spawning this program, which keeps a reference to the the composite + * overlay window until Mutter picks it back up. + */ + +/* + * Copyright (C) 2014 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 . + */ + +#include +#include +#include +#include + +int +main (int argc, + char **argv) +{ + Display *display = XOpenDisplay (NULL); + Window selection_window; + XSetWindowAttributes xwa; + unsigned long mask = 0; + + xwa.override_redirect = True; + mask |= CWOverrideRedirect; + + + XCompositeGetOverlayWindow (display, DefaultRootWindow (display)); + + selection_window = XCreateWindow (display, + DefaultRootWindow (display), + -100, -100, 1, 1, 0, + 0, + InputOnly, + DefaultVisual (display, DefaultScreen (display)), + mask, &xwa); + + XSetSelectionOwner (display, + XInternAtom (display, "_MUTTER_RESTART_HELPER", False), + selection_window, + CurrentTime); + + /* Mutter looks for an (arbitrary) line printed to stdout to know that + * we have started and have a reference to the COW. XSync() so that + * everything is set on the X server before Mutter starts restarting. + */ + XSync (display, False); + + printf ("STARTED\n"); + fflush (stdout); + + while (True) + { + XEvent xev; + + XNextEvent (display, &xev); + /* Mutter restarted and unset the selection to indicate that + * it has a reference on the COW again */ + if (xev.xany.type == SelectionClear) + return 0; + } +} diff --git a/src/core/restart.c b/src/core/restart.c new file mode 100644 index 000000000..3b0add390 --- /dev/null +++ b/src/core/restart.c @@ -0,0 +1,208 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * SECTION:restart + * @short_description: Smoothly restart the compositor + * + * There are some cases where we need to restart Mutter in order + * to deal with changes in state - the particular case inspiring + * this is enabling or disabling stereo output. To make this + * fairly smooth for the user, we need to do two things: + * + * - Display a message to the user and make sure that it is + * actually painted before we exit. + * - Use a helper program so that the Composite Overlay Window + * isn't unmapped and mapped. + * + * This handles both of these. + */ + +/* + * Copyright (C) 2014 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 . + */ + +#include + +#include +#include + +#include +#include "ui.h" +#include "util-private.h" +#include "display-private.h" + +static gboolean restart_helper_started = FALSE; +static gboolean restart_message_shown = FALSE; +static gboolean is_restart = FALSE; + +void +meta_restart_init (void) +{ + Display *xdisplay = meta_ui_get_display (); + Atom atom_restart_helper = XInternAtom (xdisplay, "_MUTTER_RESTART_HELPER", False); + Window restart_helper_window = NULL; + + restart_helper_window = XGetSelectionOwner (xdisplay, atom_restart_helper); + if (restart_helper_window) + is_restart = TRUE; +} + +static void +restart_check_ready (void) +{ + if (restart_helper_started && restart_message_shown) + meta_display_request_restart (meta_get_display ()); +} + +static void +restart_helper_read_line_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + gsize length; + char *line = g_data_input_stream_read_line_finish_utf8 (G_DATA_INPUT_STREAM (source_object), + res, + &length, &error); + if (line == NULL) + { + meta_warning ("Failed to read output from restart helper%s%s\n", + error ? ": " : NULL, + error ? error->message : NULL); + } + else + g_free (line); /* We don't actually care what the restart helper outputs */ + + g_object_unref (source_object); + + restart_helper_started = TRUE; + restart_check_ready (); +} + +static gboolean +restart_message_painted (gpointer data) +{ + restart_message_shown = TRUE; + restart_check_ready (); + + return FALSE; +} + +/** + * meta_restart: + * @message: message to display to the user. + * + * Starts the process of restarting the compositor. Note that Mutter's + * involvement here is to make the restart visually smooth for the + * user - it cannot itself safely reexec a program that embeds libmuttter. + * So in order for this to work, the compositor must handle two + * signals - MetaDisplay::show-restart-message, to display the + * message passed here on the Clutter stage, and ::restart to actually + * reexec the compositor. + */ +void +meta_restart (const char *message) +{ + MetaDisplay *display = meta_get_display(); + GError *error = NULL; + int helper_out_fd; + + static const char * const helper_argv[] = { + MUTTER_LIBEXECDIR "/mutter-restart-helper", NULL + }; + + if (meta_display_show_restart_message (display, message)) + { + /* Wait until the stage was painted */ + clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT, + restart_message_painted, + NULL, NULL); + } + else + { + /* Can't show the message, show the message as soon as the + * restart helper starts + */ + restart_message_painted (NULL); + } + + /* We also need to wait for the restart helper to get its + * reference to the Composite Overlay Window. + */ + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (char **)helper_argv, + NULL, /* envp */ + G_SPAWN_DEFAULT, + NULL, NULL, /* child_setup */ + NULL, /* child_pid */ + NULL, /* standard_input */ + &helper_out_fd, + NULL, /* standard_error */ + &error)) + { + meta_warning ("Failed to start restart helper: %s\n", error->message); + goto error; + } + else + { + GInputStream *unix_stream = g_unix_input_stream_new (helper_out_fd, TRUE); + GDataInputStream *data_stream = g_data_input_stream_new (unix_stream); + g_object_unref (unix_stream); + + g_data_input_stream_read_line_async (data_stream, G_PRIORITY_DEFAULT, + NULL, restart_helper_read_line_callback, + &error); + if (error != NULL) + { + meta_warning ("Failed to read from restart helper: %s\n", error->message); + g_object_unref (data_stream); + goto error; + } + } + + return; + + error: + restart_helper_started = TRUE; + restart_check_ready (); + + return; +} + +void +meta_restart_finish (void) +{ + if (is_restart) + { + Display *xdisplay = meta_ui_get_display (); + Atom atom_restart_helper = XInternAtom (xdisplay, "_MUTTER_RESTART_HELPER", False); + XSetSelectionOwner (xdisplay, atom_restart_helper, None, CurrentTime); + } +} + +/** + * meta_is_restart: + * + * Returns %TRUE if this instance of Mutter comes from Mutter + * restarting itself (for example to enable/disable stereo.) + * See meta_restart(). If this is the case, any startup visuals + * or animations should be suppressed. + */ +gboolean +meta_is_restart (void) +{ + return is_restart; +} diff --git a/src/meta/main.h b/src/meta/main.h index 35eb73da7..1f4745b7f 100644 --- a/src/meta/main.h +++ b/src/meta/main.h @@ -33,6 +33,9 @@ gboolean meta_get_replace_current_wm (void); /* Actually defined in util void meta_set_wm_name (const char *wm_name); void meta_set_gnome_wm_keybindings (const char *wm_keybindings); +void meta_restart (const char *message); +gboolean meta_is_restart (void); + /** * MetaExitCode: * @META_EXIT_SUCCESS: Success