diff options
Diffstat (limited to 'src/x11/session.c')
-rw-r--r-- | src/x11/session.c | 1860 |
1 files changed, 0 insertions, 1860 deletions
diff --git a/src/x11/session.c b/src/x11/session.c deleted file mode 100644 index 339a7ae33..000000000 --- a/src/x11/session.c +++ /dev/null @@ -1,1860 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Mutter Session Management */ - -/* - * Copyright (C) 2001 Havoc Pennington (some code in here from - * libgnomeui, (C) Tom Tromey, Carsten Schaar) - * Copyright (C) 2004, 2005 Elijah Newren - * - * 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 "x11/session.h" - -#include <sys/wait.h> -#include <time.h> -#include <X11/Xatom.h> - -#include "core/util-private.h" -#include "meta/meta-context.h" -#include "x11/meta-x11-display-private.h" - -#ifndef HAVE_SM -void -meta_session_init (MetaContext *context, - const char *client_id, - const char *save_file) -{ - meta_topic (META_DEBUG_SM, "Compiled without session management support"); -} - -const MetaWindowSessionInfo* -meta_window_lookup_saved_state (MetaWindow *window) -{ - return NULL; -} - -void -meta_window_release_saved_state (const MetaWindowSessionInfo *info) -{ - ; -} -#else /* HAVE_SM */ - -#include <errno.h> -#include <fcntl.h> -#include <glib.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include <X11/ICE/ICElib.h> -#include <X11/SM/SMlib.h> - -#include "core/display-private.h" -#include "meta/main.h" -#include "meta/util.h" -#include "meta/workspace.h" - -typedef struct _MetaIceConnection -{ - IceConn ice_connection; - MetaContext *context; -} MetaIceConnection; - -static void ice_io_error_handler (IceConn connection); - -static void new_ice_connection (IceConn connection, IcePointer client_data, - Bool opening, IcePointer *watch_data); - -static void save_state (void); -static char* load_state (const char *previous_save_file); -static void regenerate_save_file (void); -static const char* full_save_file (void); -static void warn_about_lame_clients_and_finish_interact (gboolean shutdown); -static void disconnect (void); - -/* This is called when data is available on an ICE connection. */ -static gboolean -process_ice_messages (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - MetaIceConnection *ice_connection = user_data; - IceConn connection = ice_connection->ice_connection; - IceProcessMessagesStatus status; - - /* This blocks infinitely sometimes. I don't know what - * to do about it. Checking "condition" just breaks - * session management. - */ - status = IceProcessMessages (connection, NULL, NULL); - - if (status == IceProcessMessagesIOError) - { - /* We were disconnected; close our connection to the - * session manager, this will result in the ICE connection - * being cleaned up, since it is owned by libSM. - */ - disconnect (); - meta_context_terminate (ice_connection->context); - - return FALSE; - } - - return TRUE; -} - -/* This is called when a new ICE connection is made. It arranges for - the ICE connection to be handled via the event loop. */ -static void -new_ice_connection (IceConn connection, IcePointer client_data, Bool opening, - IcePointer *watch_data) -{ - MetaContext *context = client_data; - guint input_id; - - if (opening) - { - MetaIceConnection *ice_connection; - GIOChannel *channel; - - fcntl (IceConnectionNumber (connection), F_SETFD, - fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC); - - ice_connection = g_new0 (MetaIceConnection, 1); - ice_connection->ice_connection = connection; - ice_connection->context = context; - - channel = g_io_channel_unix_new (IceConnectionNumber (connection)); - - input_id = g_io_add_watch_full (channel, - G_PRIORITY_DEFAULT, - G_IO_IN | G_IO_ERR, - process_ice_messages, - ice_connection, - g_free); - - g_io_channel_unref (channel); - - *watch_data = (IcePointer) GUINT_TO_POINTER (input_id); - } - else - { - input_id = GPOINTER_TO_UINT ((gpointer) *watch_data); - - g_clear_handle_id (&input_id, g_source_remove); - } -} - -static IceIOErrorHandler ice_installed_handler; - -/* We call any handler installed before (or after) gnome_ice_init but - avoid calling the default libICE handler which does an exit() */ -static void -ice_io_error_handler (IceConn connection) -{ - if (ice_installed_handler) - (*ice_installed_handler) (connection); -} - -static void -ice_init (void) -{ - static gboolean ice_initted = FALSE; - - if (! ice_initted) - { - IceIOErrorHandler default_handler; - - ice_installed_handler = IceSetIOErrorHandler (NULL); - default_handler = IceSetIOErrorHandler (ice_io_error_handler); - - if (ice_installed_handler == default_handler) - ice_installed_handler = NULL; - - IceAddConnectionWatch (new_ice_connection, NULL); - - ice_initted = TRUE; - } -} - -typedef enum -{ - STATE_DISCONNECTED, - STATE_IDLE, - STATE_SAVING_PHASE_1, - STATE_WAITING_FOR_PHASE_2, - STATE_SAVING_PHASE_2, - STATE_WAITING_FOR_INTERACT, - STATE_DONE_WITH_INTERACT, - STATE_SKIPPING_GLOBAL_SAVE, - STATE_FROZEN, - STATE_REGISTERING -} ClientState; - -static void save_phase_2_callback (SmcConn smc_conn, - SmPointer client_data); -static void interact_callback (SmcConn smc_conn, - SmPointer client_data); -static void shutdown_cancelled_callback (SmcConn smc_conn, - SmPointer client_data); -static void save_complete_callback (SmcConn smc_conn, - SmPointer client_data); -static void die_callback (SmcConn smc_conn, - SmPointer client_data); -static void save_yourself_callback (SmcConn smc_conn, - SmPointer client_data, - int save_style, - Bool shutdown, - int interact_style, - Bool fast); -static void set_clone_restart_commands (void); - -static char *client_id = NULL; -static gpointer session_connection = NULL; -static ClientState current_state = STATE_DISCONNECTED; -static gboolean interaction_allowed = FALSE; - -void -meta_session_init (MetaContext *context, - const char *previous_client_id, - const char *previous_save_file) -{ - /* Some code here from twm */ - char buf[256]; - unsigned long mask; - SmcCallbacks callbacks; - char *saved_client_id; - - if (!previous_client_id) - { - const char *desktop_autostart_id; - - desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); - if (desktop_autostart_id) - previous_client_id = desktop_autostart_id; - } - g_unsetenv ("DESKTOP_AUTOSTART_ID"); - - meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'", - previous_save_file ? previous_save_file : "(none)"); - - if (previous_save_file) - { - saved_client_id = load_state (previous_save_file); - previous_client_id = saved_client_id; - } - else if (previous_client_id) - { - char *save_file = g_strconcat (previous_client_id, ".ms", NULL); - saved_client_id = load_state (save_file); - g_free (save_file); - } - else - { - saved_client_id = NULL; - } - - ice_init (); - - mask = SmcSaveYourselfProcMask | SmcDieProcMask | - SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask; - - callbacks.save_yourself.callback = save_yourself_callback; - callbacks.save_yourself.client_data = context; - - callbacks.die.callback = die_callback; - callbacks.die.client_data = context; - - callbacks.save_complete.callback = save_complete_callback; - callbacks.save_complete.client_data = context; - - callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback; - callbacks.shutdown_cancelled.client_data = context; - - session_connection = - SmcOpenConnection (NULL, /* use SESSION_MANAGER env */ - NULL, /* means use existing ICE connection */ - SmProtoMajor, - SmProtoMinor, - mask, - &callbacks, - (char*) previous_client_id, - &client_id, - 255, buf); - - if (session_connection == NULL) - { - meta_topic (META_DEBUG_SM, - "Failed to a open connection to a session manager, so window positions will not be saved: %s", - buf); - - goto out; - } - else - { - if (client_id == NULL) - meta_bug ("Session manager gave us a NULL client ID?"); - meta_topic (META_DEBUG_SM, "Obtained session ID '%s'", client_id); - } - - if (previous_client_id && strcmp (previous_client_id, client_id) == 0) - current_state = STATE_IDLE; - else - current_state = STATE_REGISTERING; - - { - SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6]; - SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val; - char pid[32]; - /* Historically, this was SmRestartImmediately, which made sense - * for a stateless window manager, but we don't really control - * what embedders do, and it's all around better if gnome-session - * handles this. - */ - char hint = SmRestartIfRunning; - char priority = 20; /* low to run before other apps */ - const char *prgname; - - prgname = g_get_prgname (); - - prop1.name = (char *)SmProgram; - prop1.type = (char *)SmARRAY8; - prop1.num_vals = 1; - prop1.vals = &prop1val; - prop1val.value = (char *)prgname; - prop1val.length = strlen (prgname); - - /* twm sets getuid() for this, but the SM spec plainly - * says pw_name, twm is on crack - */ - prop2.name = (char *)SmUserID; - prop2.type = (char *)SmARRAY8; - prop2.num_vals = 1; - prop2.vals = &prop2val; - prop2val.value = (char*) g_get_user_name (); - prop2val.length = strlen (prop2val.value); - - prop3.name = (char *)SmRestartStyleHint; - prop3.type = (char *)SmCARD8; - prop3.num_vals = 1; - prop3.vals = &prop3val; - prop3val.value = &hint; - prop3val.length = 1; - - sprintf (pid, "%d", getpid ()); - prop4.name = (char *)SmProcessID; - prop4.type = (char *)SmARRAY8; - prop4.num_vals = 1; - prop4.vals = &prop4val; - prop4val.value = pid; - prop4val.length = strlen (prop4val.value); - - /* Always start in home directory */ - prop5.name = (char *)SmCurrentDirectory; - prop5.type = (char *)SmARRAY8; - prop5.num_vals = 1; - prop5.vals = &prop5val; - prop5val.value = (char*) g_get_home_dir (); - prop5val.length = strlen (prop5val.value); - - prop6.name = (char *)"_GSM_Priority"; - prop6.type = (char *)SmCARD8; - prop6.num_vals = 1; - prop6.vals = &prop6val; - prop6val.value = &priority; - prop6val.length = 1; - - props[0] = &prop1; - props[1] = &prop2; - props[2] = &prop3; - props[3] = &prop4; - props[4] = &prop5; - props[5] = &prop6; - - SmcSetProperties (session_connection, 6, props); - } - - out: - g_free (saved_client_id); -} - -static void -disconnect (void) -{ - SmcCloseConnection (session_connection, 0, NULL); - session_connection = NULL; - current_state = STATE_DISCONNECTED; -} - -static void -save_yourself_possibly_done (gboolean shutdown, - gboolean successful) -{ - meta_topic (META_DEBUG_SM, - "save possibly done shutdown = %d success = %d", - shutdown, successful); - - if (current_state == STATE_SAVING_PHASE_1) - { - Status status; - - status = SmcRequestSaveYourselfPhase2 (session_connection, - save_phase_2_callback, - GINT_TO_POINTER (shutdown)); - - if (status) - current_state = STATE_WAITING_FOR_PHASE_2; - - meta_topic (META_DEBUG_SM, - "Requested phase 2, status = %d", status); - } - - if (current_state == STATE_SAVING_PHASE_2 && - interaction_allowed) - { - Status status; - - status = SmcInteractRequest (session_connection, - /* ignore this feature of the protocol by always - * claiming normal - */ - SmDialogNormal, - interact_callback, - GINT_TO_POINTER (shutdown)); - - if (status) - current_state = STATE_WAITING_FOR_INTERACT; - - meta_topic (META_DEBUG_SM, - "Requested interact, status = %d", status); - } - - if (current_state == STATE_SAVING_PHASE_1 || - current_state == STATE_SAVING_PHASE_2 || - current_state == STATE_DONE_WITH_INTERACT || - current_state == STATE_SKIPPING_GLOBAL_SAVE) - { - meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone"); - - SmcSaveYourselfDone (session_connection, - successful); - - if (shutdown) - current_state = STATE_FROZEN; - else - current_state = STATE_IDLE; - } -} - -static void -save_phase_2_callback (SmcConn smc_conn, SmPointer client_data) -{ - gboolean shutdown; - - meta_topic (META_DEBUG_SM, "Phase 2 save"); - - shutdown = GPOINTER_TO_INT (client_data); - - current_state = STATE_SAVING_PHASE_2; - - save_state (); - - save_yourself_possibly_done (shutdown, TRUE); -} - -static void -save_yourself_callback (SmcConn smc_conn, - SmPointer client_data, - int save_style, - Bool shutdown, - int interact_style, - Bool fast) -{ - gboolean successful; - - meta_topic (META_DEBUG_SM, "SaveYourself received"); - - successful = TRUE; - - /* The first SaveYourself after registering for the first time - * is a special case (SM specs 7.2). - */ - -#if 0 /* I think the GnomeClient rationale for this doesn't apply */ - if (current_state == STATE_REGISTERING) - { - current_state = STATE_IDLE; - /* Double check that this is a section 7.2 SaveYourself: */ - - if (save_style == SmSaveLocal && - interact_style == SmInteractStyleNone && - !shutdown && !fast) - { - /* The protocol requires this even if xsm ignores it. */ - SmcSaveYourselfDone (session_connection, successful); - return; - } - } -#endif - - /* ignore Global style saves - * - * This interpretaion of the Local/Global/Both styles - * was discussed extensively on the xdg-list. See: - * - * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html - */ - if (save_style == SmSaveGlobal) - { - current_state = STATE_SKIPPING_GLOBAL_SAVE; - save_yourself_possibly_done (shutdown, successful); - return; - } - - interaction_allowed = interact_style != SmInteractStyleNone; - - current_state = STATE_SAVING_PHASE_1; - - regenerate_save_file (); - - set_clone_restart_commands (); - - save_yourself_possibly_done (shutdown, successful); -} - - -static void -die_callback (SmcConn smc_conn, SmPointer client_data) -{ - MetaContext *context = client_data; - - meta_topic (META_DEBUG_SM, "Disconnecting from session manager"); - - disconnect (); - /* We don't actually exit here - we will simply go away with the X - * server on logout, when we lose the X connection and libx11 kills - * us. It looks like *crap* on logout if the user sees their - * windows lose the decorations, etc. - * - * Anything that wants us to go away outside of session management - * can use kill(). - */ - - /* All of that is true - unless we're a wayland compositor. In which - * case the X server won't go down until we do, so we must die first. - */ - if (meta_is_wayland_compositor ()) - meta_context_terminate (context); -} - -static void -save_complete_callback (SmcConn smc_conn, SmPointer client_data) -{ - /* nothing */ - meta_topic (META_DEBUG_SM, "SaveComplete received"); -} - -static void -shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data) -{ - meta_topic (META_DEBUG_SM, "Shutdown cancelled received"); - - if (session_connection != NULL && - (current_state != STATE_IDLE && current_state != STATE_FROZEN)) - { - SmcSaveYourselfDone (session_connection, True); - current_state = STATE_IDLE; - } -} - -static void -interact_callback (SmcConn smc_conn, SmPointer client_data) -{ - /* nothing */ - gboolean shutdown; - - meta_topic (META_DEBUG_SM, "Interaction permission received"); - - shutdown = GPOINTER_TO_INT (client_data); - - current_state = STATE_DONE_WITH_INTERACT; - - warn_about_lame_clients_and_finish_interact (shutdown); -} - -static void -set_clone_restart_commands (void) -{ - char *restartv[10]; - char *clonev[10]; - char *discardv[10]; - int i; - SmProp prop1, prop2, prop3, *props[3]; - const char *prgname; - - prgname = g_get_prgname (); - - /* Restart (use same client ID) */ - - prop1.name = (char *)SmRestartCommand; - prop1.type = (char *)SmLISTofARRAY8; - - g_return_if_fail (client_id); - - i = 0; - restartv[i] = (char *)prgname; - ++i; - restartv[i] = (char *)"--sm-client-id"; - ++i; - restartv[i] = client_id; - ++i; - restartv[i] = NULL; - - prop1.vals = g_new (SmPropValue, i); - i = 0; - while (restartv[i]) - { - prop1.vals[i].value = restartv[i]; - prop1.vals[i].length = strlen (restartv[i]); - ++i; - } - prop1.num_vals = i; - - /* Clone (no client ID) */ - - i = 0; - clonev[i] = (char *)prgname; - ++i; - clonev[i] = NULL; - - prop2.name = (char *)SmCloneCommand; - prop2.type = (char *)SmLISTofARRAY8; - - prop2.vals = g_new (SmPropValue, i); - i = 0; - while (clonev[i]) - { - prop2.vals[i].value = clonev[i]; - prop2.vals[i].length = strlen (clonev[i]); - ++i; - } - prop2.num_vals = i; - - /* Discard */ - - i = 0; - discardv[i] = (char *)"rm"; - ++i; - discardv[i] = (char *)"-f"; - ++i; - discardv[i] = (char*) full_save_file (); - ++i; - discardv[i] = NULL; - - prop3.name = (char *)SmDiscardCommand; - prop3.type = (char *)SmLISTofARRAY8; - - prop3.vals = g_new (SmPropValue, i); - i = 0; - while (discardv[i]) - { - prop3.vals[i].value = discardv[i]; - prop3.vals[i].length = strlen (discardv[i]); - ++i; - } - prop3.num_vals = i; - - - props[0] = &prop1; - props[1] = &prop2; - props[2] = &prop3; - - SmcSetProperties (session_connection, 3, props); - - g_free (prop1.vals); - g_free (prop2.vals); - g_free (prop3.vals); -} - -/* The remaining code in this file actually loads/saves the session, - * while the code above this comment handles chatting with the - * session manager. - */ - -static const char* -window_type_to_string (MetaWindowType type) -{ - switch (type) - { - case META_WINDOW_NORMAL: - return "normal"; - case META_WINDOW_DESKTOP: - return "desktop"; - case META_WINDOW_DOCK: - return "dock"; - case META_WINDOW_DIALOG: - return "dialog"; - case META_WINDOW_MODAL_DIALOG: - return "modal_dialog"; - case META_WINDOW_TOOLBAR: - return "toolbar"; - case META_WINDOW_MENU: - return "menu"; - case META_WINDOW_SPLASHSCREEN: - return "splashscreen"; - case META_WINDOW_UTILITY: - return "utility"; - case META_WINDOW_DROPDOWN_MENU: - return "dropdown_menu"; - case META_WINDOW_POPUP_MENU: - return "popup_menu"; - case META_WINDOW_TOOLTIP: - return "tooltip"; - case META_WINDOW_NOTIFICATION: - return "notification"; - case META_WINDOW_COMBO: - return "combo"; - case META_WINDOW_DND: - return "dnd"; - case META_WINDOW_OVERRIDE_OTHER: - return "override_redirect"; - } - - return ""; -} - -static MetaWindowType -window_type_from_string (const char *str) -{ - if (strcmp (str, "normal") == 0) - return META_WINDOW_NORMAL; - else if (strcmp (str, "desktop") == 0) - return META_WINDOW_DESKTOP; - else if (strcmp (str, "dock") == 0) - return META_WINDOW_DOCK; - else if (strcmp (str, "dialog") == 0) - return META_WINDOW_DIALOG; - else if (strcmp (str, "modal_dialog") == 0) - return META_WINDOW_MODAL_DIALOG; - else if (strcmp (str, "toolbar") == 0) - return META_WINDOW_TOOLBAR; - else if (strcmp (str, "menu") == 0) - return META_WINDOW_MENU; - else if (strcmp (str, "utility") == 0) - return META_WINDOW_UTILITY; - else if (strcmp (str, "splashscreen") == 0) - return META_WINDOW_SPLASHSCREEN; - else - return META_WINDOW_NORMAL; -} - -static int -window_gravity_from_string (const char *str) -{ - if (strcmp (str, "META_GRAVITY_NORTH_WEST") == 0) - return META_GRAVITY_NORTH_WEST; - else if (strcmp (str, "META_GRAVITY_NORTH") == 0) - return META_GRAVITY_NORTH; - else if (strcmp (str, "META_GRAVITY_NORTH_EAST") == 0) - return META_GRAVITY_NORTH_EAST; - else if (strcmp (str, "META_GRAVITY_WEST") == 0) - return META_GRAVITY_WEST; - else if (strcmp (str, "META_GRAVITY_CENTER") == 0) - return META_GRAVITY_CENTER; - else if (strcmp (str, "META_GRAVITY_EAST") == 0) - return META_GRAVITY_EAST; - else if (strcmp (str, "META_GRAVITY_SOUTH_WEST") == 0) - return META_GRAVITY_SOUTH_WEST; - else if (strcmp (str, "META_GRAVITY_SOUTH") == 0) - return META_GRAVITY_SOUTH; - else if (strcmp (str, "META_GRAVITY_SOUTH_EAST") == 0) - return META_GRAVITY_SOUTH_EAST; - else if (strcmp (str, "META_GRAVITY_STATIC") == 0) - return META_GRAVITY_STATIC; - else - return META_GRAVITY_NORTH_WEST; -} - -static char* -encode_text_as_utf8_markup (const char *text) -{ - /* text can be any encoding, and is nul-terminated. - * we pretend it's Latin-1 and encode as UTF-8 - */ - GString *str; - const char *p; - char *escaped; - - str = g_string_new (""); - - p = text; - while (*p) - { - g_string_append_unichar (str, *p); - ++p; - } - - escaped = g_markup_escape_text (str->str, str->len); - g_string_free (str, TRUE); - - return escaped; -} - -static char* -decode_text_from_utf8 (const char *text) -{ - /* Convert back from the encoded (but not escaped) UTF-8 */ - GString *str; - const char *p; - - str = g_string_new (""); - - p = text; - while (*p) - { - /* obviously this barfs if the UTF-8 contains chars > 255 */ - g_string_append_c (str, g_utf8_get_char (p)); - - p = g_utf8_next_char (p); - } - - return g_string_free (str, FALSE); -} - -static void -save_state (void) -{ - char *mutter_dir; - char *session_dir; - FILE *outfile; - GSList *windows; - GSList *tmp; - int stack_position; - - g_assert (client_id); - - outfile = NULL; - - /* - * g_get_user_config_dir() is guaranteed to return an existing directory. - * Eventually, if SM stays with the WM, I'd like to make this - * something like <config>/window_placement in a standard format. - * Future optimisers should note also that by the time we get here - * we probably already have full_save_path figured out and therefore - * can just use the directory name from that. - */ - mutter_dir = g_strconcat (g_get_user_config_dir (), - G_DIR_SEPARATOR_S "mutter", - NULL); - - session_dir = g_strconcat (mutter_dir, - G_DIR_SEPARATOR_S "sessions", - NULL); - - if (mkdir (mutter_dir, 0700) < 0 && - errno != EEXIST) - { - meta_warning ("Could not create directory '%s': %s", - mutter_dir, g_strerror (errno)); - } - - if (mkdir (session_dir, 0700) < 0 && - errno != EEXIST) - { - meta_warning ("Could not create directory '%s': %s", - session_dir, g_strerror (errno)); - } - - meta_topic (META_DEBUG_SM, "Saving session to '%s'", full_save_file ()); - - outfile = fopen (full_save_file (), "w"); - - if (outfile == NULL) - { - meta_warning ("Could not open session file '%s' for writing: %s", - full_save_file (), g_strerror (errno)); - goto out; - } - - /* The file format is: - * <mutter_session id="foo"> - * <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5"> - * <workspace index="2"/> - * <workspace index="4"/> - * <sticky/> <minimized/> <maximized/> - * <geometry x="100" y="100" width="200" height="200" gravity="northwest"/> - * </window> - * </mutter_session> - * - * Note that attributes on <window> are the match info we use to - * see if the saved state applies to a restored window, and - * child elements are the saved state to be applied. - * - */ - - fprintf (outfile, "<mutter_session id=\"%s\">\n", - client_id); - - windows = meta_display_list_windows (meta_get_display (), META_LIST_DEFAULT); - stack_position = 0; - - windows = g_slist_sort (windows, meta_display_stack_cmp); - tmp = windows; - stack_position = 0; - - while (tmp != NULL) - { - MetaWindow *window; - - window = tmp->data; - - if (window->sm_client_id) - { - char *sm_client_id; - char *res_class; - char *res_name; - char *role; - char *title; - - /* client id, class, name, role are not expected to be - * in UTF-8 (I think they are in XPCS which is Latin-1? - * in practice they are always ascii though.) - */ - - sm_client_id = encode_text_as_utf8_markup (window->sm_client_id); - res_class = window->res_class ? - encode_text_as_utf8_markup (window->res_class) : NULL; - res_name = window->res_name ? - encode_text_as_utf8_markup (window->res_name) : NULL; - role = window->role ? - encode_text_as_utf8_markup (window->role) : NULL; - if (window->title) - title = g_markup_escape_text (window->title, -1); - else - title = NULL; - - meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'", - window->desc, window->sm_client_id); - - fprintf (outfile, - " <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n", - sm_client_id, - res_class ? res_class : "", - res_name ? res_name : "", - title ? title : "", - role ? role : "", - window_type_to_string (window->type), - stack_position); - - g_free (sm_client_id); - g_free (res_class); - g_free (res_name); - g_free (role); - g_free (title); - - /* Sticky */ - if (window->on_all_workspaces_requested) - { - fputs (" <sticky/>\n", outfile); - } else { - int n; - if (window->workspace) - n = meta_workspace_index (window->workspace); - else - n = window->initial_workspace; - fprintf (outfile, - " <workspace index=\"%d\"/>\n", n); - } - - - /* Minimized */ - if (window->minimized) - fputs (" <minimized/>\n", outfile); - - /* Maximized */ - if (META_WINDOW_MAXIMIZED (window)) - { - fprintf (outfile, - " <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n", - window->saved_rect.x, - window->saved_rect.y, - window->saved_rect.width, - window->saved_rect.height); - } - - /* Gravity */ - { - int x, y, w, h; - meta_window_get_session_geometry (window, &x, &y, &w, &h); - - fprintf (outfile, - " <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n", - x, y, w, h, - meta_gravity_to_string (window->size_hints.win_gravity)); - } - - fputs (" </window>\n", outfile); - } - else - { - meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed", - window->desc); - } - - tmp = tmp->next; - ++stack_position; - } - - g_slist_free (windows); - - fputs ("</mutter_session>\n", outfile); - - out: - if (outfile) - { - /* FIXME need a dialog for this */ - if (ferror (outfile)) - { - meta_warning ("Error writing session file '%s': %s", - full_save_file (), g_strerror (errno)); - } - if (fclose (outfile)) - { - meta_warning ("Error closing session file '%s': %s", - full_save_file (), g_strerror (errno)); - } - } - - g_free (mutter_dir); - g_free (session_dir); -} - -typedef enum -{ - WINDOW_TAG_NONE, - WINDOW_TAG_DESKTOP, - WINDOW_TAG_STICKY, - WINDOW_TAG_MINIMIZED, - WINDOW_TAG_MAXIMIZED, - WINDOW_TAG_GEOMETRY -} WindowTag; - -typedef struct -{ - MetaWindowSessionInfo *info; - char *previous_id; -} ParseData; - -static void session_info_free (MetaWindowSessionInfo *info); -static MetaWindowSessionInfo* session_info_new (void); - -static void start_element_handler (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, - GError **error); -static void end_element_handler (GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, - GError **error); -static void text_handler (GMarkupParseContext *context, - const gchar *text, - gsize text_len, - gpointer user_data, - GError **error); - -static GMarkupParser mutter_session_parser = { - start_element_handler, - end_element_handler, - text_handler, - NULL, - NULL -}; - -static GSList *window_info_list = NULL; - -static char* -load_state (const char *previous_save_file) -{ - GMarkupParseContext *context; - GError *error; - ParseData parse_data; - char *text; - gsize length; - char *session_file; - - session_file = g_strconcat (g_get_user_config_dir (), - G_DIR_SEPARATOR_S "mutter" - G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S, - previous_save_file, - NULL); - - error = NULL; - if (!g_file_get_contents (session_file, - &text, - &length, - &error)) - { - char *canonical_session_file = session_file; - - /* Maybe they were doing it the old way, with ~/.mutter */ - session_file = g_strconcat (g_get_home_dir (), - G_DIR_SEPARATOR_S ".mutter" - G_DIR_SEPARATOR_S "sessions" - G_DIR_SEPARATOR_S, - previous_save_file, - NULL); - - if (!g_file_get_contents (session_file, - &text, - &length, - NULL)) - { - /* oh, just give up */ - - g_error_free (error); - g_free (session_file); - g_free (canonical_session_file); - return NULL; - } - - g_free (canonical_session_file); - } - - meta_topic (META_DEBUG_SM, "Parsing saved session file %s", session_file); - g_free (session_file); - session_file = NULL; - - parse_data.info = NULL; - parse_data.previous_id = NULL; - - context = g_markup_parse_context_new (&mutter_session_parser, - 0, &parse_data, NULL); - - error = NULL; - if (!g_markup_parse_context_parse (context, - text, - length, - &error)) - goto error; - - - error = NULL; - if (!g_markup_parse_context_end_parse (context, &error)) - goto error; - - g_markup_parse_context_free (context); - - goto out; - - error: - - meta_warning ("Failed to parse saved session file: %s", - error->message); - g_error_free (error); - - if (parse_data.info) - session_info_free (parse_data.info); - - g_free (parse_data.previous_id); - parse_data.previous_id = NULL; - - out: - - g_free (text); - - return parse_data.previous_id; -} - -/* FIXME this isn't very robust against bogus session files */ -static void -start_element_handler (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, - GError **error) -{ - ParseData *pd; - - pd = user_data; - - if (strcmp (element_name, "mutter_session") == 0) - { - /* Get previous ID */ - int i; - - i = 0; - while (attribute_names[i]) - { - const char *name; - const char *val; - - name = attribute_names[i]; - val = attribute_values[i]; - - if (pd->previous_id) - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_PARSE, - "<mutter_session> attribute seen but we already have the session ID"); - return; - } - - if (strcmp (name, "id") == 0) - { - pd->previous_id = decode_text_from_utf8 (val); - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Unknown attribute %s on <%s> element", - name, "mutter_session"); - return; - } - - ++i; - } - } - else if (strcmp (element_name, "window") == 0) - { - int i; - - if (pd->info) - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_PARSE, - "nested <window> tag"); - return; - } - - pd->info = session_info_new (); - - i = 0; - while (attribute_names[i]) - { - const char *name; - const char *val; - - name = attribute_names[i]; - val = attribute_values[i]; - - if (strcmp (name, "id") == 0) - { - if (*val) - pd->info->id = decode_text_from_utf8 (val); - } - else if (strcmp (name, "class") == 0) - { - if (*val) - pd->info->res_class = decode_text_from_utf8 (val); - } - else if (strcmp (name, "name") == 0) - { - if (*val) - pd->info->res_name = decode_text_from_utf8 (val); - } - else if (strcmp (name, "title") == 0) - { - if (*val) - pd->info->title = g_strdup (val); - } - else if (strcmp (name, "role") == 0) - { - if (*val) - pd->info->role = decode_text_from_utf8 (val); - } - else if (strcmp (name, "type") == 0) - { - if (*val) - pd->info->type = window_type_from_string (val); - } - else if (strcmp (name, "stacking") == 0) - { - if (*val) - { - pd->info->stack_position = atoi (val); - pd->info->stack_position_set = TRUE; - } - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Unknown attribute %s on <%s> element", - name, "window"); - session_info_free (pd->info); - pd->info = NULL; - return; - } - - ++i; - } - } - else if (strcmp (element_name, "workspace") == 0) - { - int i; - - i = 0; - while (attribute_names[i]) - { - const char *name; - - name = attribute_names[i]; - - if (strcmp (name, "index") == 0) - { - pd->info->workspace_indices = - g_slist_prepend (pd->info->workspace_indices, - GINT_TO_POINTER (atoi (attribute_values[i]))); - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Unknown attribute %s on <%s> element", - name, "window"); - session_info_free (pd->info); - pd->info = NULL; - return; - } - - ++i; - } - } - else if (strcmp (element_name, "sticky") == 0) - { - pd->info->on_all_workspaces = TRUE; - pd->info->on_all_workspaces_set = TRUE; - } - else if (strcmp (element_name, "minimized") == 0) - { - pd->info->minimized = TRUE; - pd->info->minimized_set = TRUE; - } - else if (strcmp (element_name, "maximized") == 0) - { - int i; - - i = 0; - pd->info->maximized = TRUE; - pd->info->maximized_set = TRUE; - while (attribute_names[i]) - { - const char *name; - const char *val; - - name = attribute_names[i]; - val = attribute_values[i]; - - if (strcmp (name, "saved_x") == 0) - { - if (*val) - { - pd->info->saved_rect.x = atoi (val); - pd->info->saved_rect_set = TRUE; - } - } - else if (strcmp (name, "saved_y") == 0) - { - if (*val) - { - pd->info->saved_rect.y = atoi (val); - pd->info->saved_rect_set = TRUE; - } - } - else if (strcmp (name, "saved_width") == 0) - { - if (*val) - { - pd->info->saved_rect.width = atoi (val); - pd->info->saved_rect_set = TRUE; - } - } - else if (strcmp (name, "saved_height") == 0) - { - if (*val) - { - pd->info->saved_rect.height = atoi (val); - pd->info->saved_rect_set = TRUE; - } - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Unknown attribute %s on <%s> element", - name, "maximized"); - return; - } - - ++i; - } - - if (pd->info->saved_rect_set) - meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d ", - pd->info->saved_rect.x, - pd->info->saved_rect.y, - pd->info->saved_rect.width, - pd->info->saved_rect.height); - } - else if (strcmp (element_name, "geometry") == 0) - { - int i; - - pd->info->geometry_set = TRUE; - - i = 0; - while (attribute_names[i]) - { - const char *name; - const char *val; - - name = attribute_names[i]; - val = attribute_values[i]; - - if (strcmp (name, "x") == 0) - { - if (*val) - pd->info->rect.x = atoi (val); - } - else if (strcmp (name, "y") == 0) - { - if (*val) - pd->info->rect.y = atoi (val); - } - else if (strcmp (name, "width") == 0) - { - if (*val) - pd->info->rect.width = atoi (val); - } - else if (strcmp (name, "height") == 0) - { - if (*val) - pd->info->rect.height = atoi (val); - } - else if (strcmp (name, "gravity") == 0) - { - if (*val) - pd->info->gravity = window_gravity_from_string (val); - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Unknown attribute %s on <%s> element", - name, "geometry"); - return; - } - - ++i; - } - - meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s", - pd->info->rect.x, - pd->info->rect.y, - pd->info->rect.width, - pd->info->rect.height, - meta_gravity_to_string (pd->info->gravity)); - } - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ELEMENT, - "Unknown element %s", - element_name); - return; - } -} - -static void -end_element_handler (GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, - GError **error) -{ - ParseData *pd; - - pd = user_data; - - if (strcmp (element_name, "window") == 0) - { - g_assert (pd->info); - - window_info_list = g_slist_prepend (window_info_list, - pd->info); - - meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s", - pd->info->res_class ? pd->info->res_class : "(none)", - pd->info->res_name ? pd->info->res_name : "(none)", - pd->info->role ? pd->info->role : "(none)"); - - pd->info = NULL; - } -} - -static void -text_handler (GMarkupParseContext *context, - const gchar *text, - gsize text_len, - gpointer user_data, - GError **error) -{ - /* Right now we don't have any elements where we care about their - * content - */ -} - -static gboolean -both_null_or_matching (const char *a, - const char *b) -{ - if (a == NULL && b == NULL) - return TRUE; - else if (a && b && strcmp (a, b) == 0) - return TRUE; - else - return FALSE; -} - -static GSList* -get_possible_matches (MetaWindow *window) -{ - /* Get all windows with this client ID */ - GSList *retval; - GSList *tmp; - gboolean ignore_client_id; - - retval = NULL; - - ignore_client_id = g_getenv ("MUTTER_DEBUG_SM") != NULL; - - tmp = window_info_list; - while (tmp != NULL) - { - MetaWindowSessionInfo *info; - - info = tmp->data; - - if ((ignore_client_id || - both_null_or_matching (info->id, window->sm_client_id)) && - both_null_or_matching (info->res_class, window->res_class) && - both_null_or_matching (info->res_name, window->res_name) && - both_null_or_matching (info->role, window->role)) - { - meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s", - window->desc, - info->res_class ? info->res_class : "(none)", - info->res_name ? info->res_name : "(none)", - info->role ? info->role : "(none)"); - - retval = g_slist_prepend (retval, info); - } - else - { - if (meta_is_verbose ()) - { - if (!both_null_or_matching (info->id, window->sm_client_id)) - meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match", - window->desc, - window->sm_client_id ? window->sm_client_id : "(none)", - info->id ? info->id : "(none)"); - else if (!both_null_or_matching (info->res_class, window->res_class)) - meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match", - window->desc, - window->res_class ? window->res_class : "(none)", - info->res_class ? info->res_class : "(none)"); - - else if (!both_null_or_matching (info->res_name, window->res_name)) - meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match", - window->desc, - window->res_name ? window->res_name : "(none)", - info->res_name ? info->res_name : "(none)"); - else if (!both_null_or_matching (info->role, window->role)) - meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match", - window->desc, - window->role ? window->role : "(none)", - info->role ? info->role : "(none)"); - else - meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason", - window->desc, info->id); - } - } - - tmp = tmp->next; - } - - return retval; -} - -static const MetaWindowSessionInfo* -find_best_match (GSList *infos, - MetaWindow *window) -{ - GSList *tmp; - const MetaWindowSessionInfo *matching_title; - const MetaWindowSessionInfo *matching_type; - - matching_title = NULL; - matching_type = NULL; - - tmp = infos; - while (tmp != NULL) - { - MetaWindowSessionInfo *info; - - info = tmp->data; - - if (matching_title == NULL && - both_null_or_matching (info->title, window->title)) - matching_title = info; - - if (matching_type == NULL && - info->type == window->type) - matching_type = info; - - tmp = tmp->next; - } - - /* Prefer same title, then same type of window, then - * just pick something. Eventually we could enhance this - * to e.g. break ties by geometry hint similarity, - * or other window features. - */ - - if (matching_title) - return matching_title; - else if (matching_type) - return matching_type; - else - return infos->data; -} - -const MetaWindowSessionInfo* -meta_window_lookup_saved_state (MetaWindow *window) -{ - GSList *possibles; - const MetaWindowSessionInfo *info; - - /* Window is not session managed. - * I haven't yet figured out how to deal with these - * in a way that doesn't cause broken side effects in - * situations other than on session restore. - */ - if (window->sm_client_id == NULL) - { - meta_topic (META_DEBUG_SM, - "Window %s is not session managed, not checking for saved state", - window->desc); - return NULL; - } - - possibles = get_possible_matches (window); - - if (possibles == NULL) - { - meta_topic (META_DEBUG_SM, - "Window %s has no possible matches in the list of saved window states", - window->desc); - return NULL; - } - - info = find_best_match (possibles, window); - - g_slist_free (possibles); - - return info; -} - -void -meta_window_release_saved_state (const MetaWindowSessionInfo *info) -{ - /* We don't want to use the same saved state again for another - * window. - */ - window_info_list = g_slist_remove (window_info_list, info); - - session_info_free ((MetaWindowSessionInfo*) info); -} - -static void -session_info_free (MetaWindowSessionInfo *info) -{ - g_free (info->id); - g_free (info->res_class); - g_free (info->res_name); - g_free (info->title); - g_free (info->role); - - g_slist_free (info->workspace_indices); - - g_free (info); -} - -static MetaWindowSessionInfo* -session_info_new (void) -{ - MetaWindowSessionInfo *info; - - info = g_new0 (MetaWindowSessionInfo, 1); - - info->type = META_WINDOW_NORMAL; - info->gravity = META_GRAVITY_NORTH_WEST; - - return info; -} - -static char* full_save_path = NULL; - -static void -regenerate_save_file (void) -{ - g_free (full_save_path); - - if (client_id) - full_save_path = g_strconcat (g_get_user_config_dir (), - G_DIR_SEPARATOR_S "mutter" - G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S, - client_id, - ".ms", - NULL); - else - full_save_path = NULL; -} - -static const char* -full_save_file (void) -{ - return full_save_path; -} - -static int -windows_cmp_by_title (MetaWindow *a, - MetaWindow *b) -{ - return g_utf8_collate (a->title, b->title); -} - -static void -finish_interact (gboolean shutdown) -{ - if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */ - { - SmcInteractDone (session_connection, False /* don't cancel logout */); - - save_yourself_possibly_done (shutdown, TRUE); - } -} - -static void -dialog_closed (GPid pid, int status, gpointer user_data) -{ - gboolean shutdown = GPOINTER_TO_INT (user_data); - - if (WIFEXITED (status) && WEXITSTATUS (status) == 0) /* pressed "OK" */ - { - finish_interact (shutdown); - } -} - -static void -warn_about_lame_clients_and_finish_interact (gboolean shutdown) -{ - GSList *lame = NULL; - GSList *windows; - GSList *lame_details = NULL; - GSList *tmp; - GSList *columns = NULL; - GPid pid; - - windows = meta_display_list_windows (meta_get_display (), META_LIST_DEFAULT); - tmp = windows; - while (tmp != NULL) - { - MetaWindow *window; - - window = tmp->data; - - /* only complain about normal windows, the others - * are kind of dumb to worry about - */ - if (window->sm_client_id == NULL && - window->type == META_WINDOW_NORMAL) - lame = g_slist_prepend (lame, window); - - tmp = tmp->next; - } - - g_slist_free (windows); - - if (lame == NULL) - { - /* No lame apps. */ - finish_interact (shutdown); - return; - } - - columns = g_slist_prepend (columns, (gpointer)"Window"); - columns = g_slist_prepend (columns, (gpointer)"Class"); - - lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title); - - tmp = lame; - while (tmp != NULL) - { - MetaWindow *w = tmp->data; - - lame_details = g_slist_prepend (lame_details, - w->res_class ? w->res_class : (gpointer)""); - lame_details = g_slist_prepend (lame_details, - w->title); - - tmp = tmp->next; - } - g_slist_free (lame); - - pid = meta_show_dialog("--list", - _("These windows do not support “save current setup” " - "and will have to be restarted manually next time " - "you log in."), - "240", - meta_get_display()->x11_display->screen_name, - NULL, NULL, NULL, - None, - columns, - lame_details); - - g_slist_free (lame_details); - - g_child_watch_add (pid, dialog_closed, GINT_TO_POINTER (shutdown)); -} - -#endif /* HAVE_SM */ |