session.c

Go to the documentation of this file.
00001 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
00002 
00003 /* Metacity Session Management */
00004 
00005 /* 
00006  * Copyright (C) 2001 Havoc Pennington (some code in here from
00007  * libgnomeui, (C) Tom Tromey, Carsten Schaar)
00008  * Copyright (C) 2004, 2005 Elijah Newren
00009  * 
00010  * This program is free software; you can redistribute it and/or
00011  * modify it under the terms of the GNU General Public License as
00012  * published by the Free Software Foundation; either version 2 of the
00013  * License, or (at your option) any later version.
00014  *
00015  * This program is distributed in the hope that it will be useful, but
00016  * WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018  * General Public License for more details.
00019  * 
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
00023  * 02111-1307, USA.
00024  */
00025 
00026 #include <config.h>
00027 
00028 #include "session.h"
00029 #include <X11/Xatom.h>
00030 
00031 #include <time.h>
00032 
00033 #ifndef HAVE_SM
00034 void
00035 meta_session_init (const char *client_id,
00036                    const char *save_file)
00037 {
00038   meta_topic (META_DEBUG_SM, "Compiled without session management support\n");
00039 }
00040 
00041 void
00042 meta_session_shutdown (void)
00043 {
00044   /* nothing */
00045 }
00046 
00047 const MetaWindowSessionInfo*
00048 meta_window_lookup_saved_state (MetaWindow *window)
00049 {
00050   return NULL;
00051 }
00052 
00053 void
00054 meta_window_release_saved_state (const MetaWindowSessionInfo *info)
00055 {
00056   ;
00057 }
00058 #else /* HAVE_SM */
00059 
00060 #include <X11/ICE/ICElib.h>
00061 #include <X11/SM/SMlib.h>
00062 #include <unistd.h>
00063 #include <sys/stat.h>
00064 #include <sys/types.h>
00065 #include <fcntl.h>
00066 #include <errno.h>
00067 #include <glib.h>
00068 #include <string.h>
00069 #include <stdlib.h>
00070 #include <stdio.h>
00071 #include "main.h"
00072 #include "util.h"
00073 #include "display-private.h"
00074 #include "workspace.h"
00075 
00076 static void ice_io_error_handler (IceConn connection);
00077 
00078 static void new_ice_connection (IceConn connection, IcePointer client_data, 
00079                                 Bool opening, IcePointer *watch_data);
00080 
00081 static void        save_state         (void);
00082 static char*       load_state         (const char *previous_save_file);
00083 static void        regenerate_save_file (void);
00084 static const char* full_save_file       (void);
00085 static void        warn_about_lame_clients_and_finish_interact (gboolean shutdown);
00086 
00087 /* This is called when data is available on an ICE connection.  */
00088 static gboolean
00089 process_ice_messages (GIOChannel *channel,
00090                       GIOCondition condition,
00091                       gpointer client_data)
00092 {
00093   IceConn connection = (IceConn) client_data;
00094   IceProcessMessagesStatus status;
00095 
00096   /* This blocks infinitely sometimes. I don't know what
00097    * to do about it. Checking "condition" just breaks
00098    * session management.
00099    */
00100   status = IceProcessMessages (connection, NULL, NULL);
00101 
00102   if (status == IceProcessMessagesIOError)
00103     {
00104 #if 0
00105       IcePointer context = IceGetConnectionContext (connection);
00106 #endif
00107       
00108       /* We were disconnected */
00109       IceSetShutdownNegotiation (connection, False);
00110       IceCloseConnection (connection);
00111 
00112       return FALSE;
00113     }
00114   
00115   return TRUE;
00116 }
00117 
00118 /* This is called when a new ICE connection is made.  It arranges for
00119    the ICE connection to be handled via the event loop.  */
00120 static void
00121 new_ice_connection (IceConn connection, IcePointer client_data, Bool opening,
00122                     IcePointer *watch_data)
00123 {
00124   guint input_id;
00125 
00126   if (opening)
00127     {
00128       /* Make sure we don't pass on these file descriptors to any
00129        * exec'ed children
00130        */
00131       GIOChannel *channel;
00132       
00133       fcntl (IceConnectionNumber (connection), F_SETFD,
00134              fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC);
00135 
00136       channel = g_io_channel_unix_new (IceConnectionNumber (connection));
00137       
00138       input_id = g_io_add_watch (channel,
00139                                  G_IO_IN | G_IO_ERR,
00140                                  process_ice_messages,
00141                                  connection);
00142 
00143       g_io_channel_unref (channel);
00144       
00145       *watch_data = (IcePointer) GUINT_TO_POINTER (input_id);
00146     }
00147   else 
00148     {
00149       input_id = GPOINTER_TO_UINT ((gpointer) *watch_data);
00150 
00151       g_source_remove (input_id);
00152     }
00153 }
00154 
00155 static IceIOErrorHandler ice_installed_handler;
00156 
00157 /* We call any handler installed before (or after) gnome_ice_init but 
00158    avoid calling the default libICE handler which does an exit() */
00159 static void
00160 ice_io_error_handler (IceConn connection)
00161 {
00162     if (ice_installed_handler)
00163       (*ice_installed_handler) (connection);
00164 }    
00165 
00166 static void
00167 ice_init (void)
00168 {
00169   static gboolean ice_initted = FALSE;
00170 
00171   if (! ice_initted)
00172     {
00173       IceIOErrorHandler default_handler;
00174 
00175       ice_installed_handler = IceSetIOErrorHandler (NULL);
00176       default_handler = IceSetIOErrorHandler (ice_io_error_handler);
00177 
00178       if (ice_installed_handler == default_handler)
00179         ice_installed_handler = NULL;
00180 
00181       IceAddConnectionWatch (new_ice_connection, NULL);
00182 
00183       ice_initted = TRUE;
00184     }
00185 }
00186 
00187 typedef enum
00188 {
00189   STATE_DISCONNECTED,
00190   STATE_IDLE,
00191   STATE_SAVING_PHASE_1,
00192   STATE_WAITING_FOR_PHASE_2,
00193   STATE_SAVING_PHASE_2,
00194   STATE_WAITING_FOR_INTERACT,
00195   STATE_DONE_WITH_INTERACT,
00196   STATE_SKIPPING_GLOBAL_SAVE,
00197   STATE_FROZEN,
00198   STATE_REGISTERING
00199 } ClientState;
00200 
00201 static void save_phase_2_callback       (SmcConn   smc_conn,
00202                                          SmPointer client_data);
00203 static void interact_callback           (SmcConn   smc_conn,
00204                                          SmPointer client_data);
00205 static void shutdown_cancelled_callback (SmcConn   smc_conn,
00206                                          SmPointer client_data);
00207 static void save_complete_callback      (SmcConn   smc_conn,
00208                                          SmPointer client_data);
00209 static void die_callback                (SmcConn   smc_conn,
00210                                          SmPointer client_data);
00211 static void save_yourself_callback      (SmcConn   smc_conn,
00212                                          SmPointer client_data,
00213                                          int       save_style,
00214                                          Bool      shutdown,
00215                                          int       interact_style,
00216                                          Bool      fast);
00217 static void set_clone_restart_commands  (void);
00218 
00219 static char *client_id = NULL;
00220 static gpointer session_connection = NULL;
00221 static ClientState current_state = STATE_DISCONNECTED;
00222 static gboolean interaction_allowed = FALSE;
00223 
00224 void
00225 meta_session_init (const char *previous_client_id,
00226                    const char *previous_save_file)
00227 {
00228   /* Some code here from twm */
00229   char buf[256];
00230   unsigned long mask;
00231   SmcCallbacks callbacks;
00232   char *saved_client_id;
00233   
00234   meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'\n",
00235               previous_save_file ? previous_save_file : "(none)");
00236 
00237   if (previous_save_file)
00238     {
00239       saved_client_id = load_state (previous_save_file);
00240       previous_client_id = saved_client_id;
00241     }
00242   else if (previous_client_id)
00243     {
00244       char *save_file = g_strconcat (previous_client_id, ".ms", NULL);
00245       saved_client_id = load_state (save_file);
00246       g_free (save_file);
00247     }
00248   else
00249     {
00250       saved_client_id = NULL;
00251     }
00252   
00253   ice_init ();
00254   
00255   mask = SmcSaveYourselfProcMask | SmcDieProcMask |
00256     SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask;
00257   
00258   callbacks.save_yourself.callback = save_yourself_callback;
00259   callbacks.save_yourself.client_data = NULL;
00260   
00261   callbacks.die.callback = die_callback;
00262   callbacks.die.client_data = NULL;
00263   
00264   callbacks.save_complete.callback = save_complete_callback;
00265   callbacks.save_complete.client_data = NULL;
00266   
00267   callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback;
00268   callbacks.shutdown_cancelled.client_data = NULL;
00269   
00270   session_connection =
00271     SmcOpenConnection (NULL, /* use SESSION_MANAGER env */
00272                        NULL, /* means use existing ICE connection */
00273                        SmProtoMajor,
00274                        SmProtoMinor,
00275                        mask,
00276                        &callbacks,
00277                        (char*) previous_client_id,
00278                        &client_id,
00279                        255, buf);
00280   
00281   if (session_connection == NULL)
00282     {
00283       meta_topic (META_DEBUG_SM, 
00284                   "Failed to a open connection to a session manager, so window positions will not be saved: %s\n",
00285                   buf);
00286 
00287       goto out;
00288     }
00289   else
00290     {
00291       if (client_id == NULL)
00292         meta_bug ("Session manager gave us a NULL client ID?");
00293       meta_topic (META_DEBUG_SM, "Obtained session ID '%s'\n", client_id);
00294     }
00295 
00296   if (previous_client_id && strcmp (previous_client_id, client_id) == 0)
00297     current_state = STATE_IDLE;
00298   else
00299     current_state = STATE_REGISTERING;
00300   
00301   {
00302     SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6];
00303     SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val;
00304     char pid[32];
00305     char hint = SmRestartImmediately;
00306     char priority = 20; /* low to run before other apps */
00307     
00308     prop1.name = SmProgram;
00309     prop1.type = SmARRAY8;
00310     prop1.num_vals = 1;
00311     prop1.vals = &prop1val;
00312     prop1val.value = "metacity";
00313     prop1val.length = strlen ("metacity");
00314 
00315     /* twm sets getuid() for this, but the SM spec plainly
00316      * says pw_name, twm is on crack
00317      */
00318     prop2.name = SmUserID;
00319     prop2.type = SmARRAY8;
00320     prop2.num_vals = 1;
00321     prop2.vals = &prop2val;
00322     prop2val.value = (char*) g_get_user_name ();
00323     prop2val.length = strlen (prop2val.value);
00324         
00325     prop3.name = SmRestartStyleHint;
00326     prop3.type = SmCARD8;
00327     prop3.num_vals = 1;
00328     prop3.vals = &prop3val;
00329     prop3val.value = &hint;
00330     prop3val.length = 1;
00331 
00332     sprintf (pid, "%d", getpid ());
00333     prop4.name = SmProcessID;
00334     prop4.type = SmARRAY8;
00335     prop4.num_vals = 1;
00336     prop4.vals = &prop4val;
00337     prop4val.value = pid;
00338     prop4val.length = strlen (prop4val.value);    
00339 
00340     /* Always start in home directory */
00341     prop5.name = SmCurrentDirectory;
00342     prop5.type = SmARRAY8;
00343     prop5.num_vals = 1;
00344     prop5.vals = &prop5val;
00345     prop5val.value = (char*) g_get_home_dir ();
00346     prop5val.length = strlen (prop5val.value);
00347 
00348     prop6.name = "_GSM_Priority";
00349     prop6.type = SmCARD8;
00350     prop6.num_vals = 1;
00351     prop6.vals = &prop6val;
00352     prop6val.value = &priority;
00353     prop6val.length = 1;
00354     
00355     props[0] = &prop1;
00356     props[1] = &prop2;
00357     props[2] = &prop3;
00358     props[3] = &prop4;
00359     props[4] = &prop5;
00360     props[5] = &prop6;
00361     
00362     SmcSetProperties (session_connection, 6, props);
00363   }
00364 
00365  out:
00366   g_free (saved_client_id);
00367 }
00368 
00369 void
00370 meta_session_shutdown (void)
00371 {
00372   /* Change our restart mode to IfRunning */
00373   
00374   SmProp prop1;
00375   SmPropValue prop1val;
00376   SmProp *props[1];
00377   char hint = SmRestartIfRunning;
00378 
00379   if (session_connection == NULL)
00380     return;
00381   
00382   prop1.name = SmRestartStyleHint;
00383   prop1.type = SmCARD8;
00384   prop1.num_vals = 1;
00385   prop1.vals = &prop1val;
00386   prop1val.value = &hint;
00387   prop1val.length = 1;
00388     
00389   props[0] = &prop1;
00390   
00391   SmcSetProperties (session_connection, 1, props);
00392 }
00393 
00394 static void
00395 disconnect (void)
00396 {
00397   SmcCloseConnection (session_connection, 0, NULL);
00398   session_connection = NULL;
00399   current_state = STATE_DISCONNECTED;
00400 }
00401 
00402 static void
00403 save_yourself_possibly_done (gboolean shutdown,
00404                              gboolean successful)
00405 {
00406   meta_topic (META_DEBUG_SM,
00407               "save possibly done shutdown = %d success = %d\n",
00408               shutdown, successful);
00409   
00410   if (current_state == STATE_SAVING_PHASE_1)
00411     {
00412       Status status;
00413       
00414       status = SmcRequestSaveYourselfPhase2 (session_connection,
00415                                              save_phase_2_callback,
00416                                              GINT_TO_POINTER (shutdown));
00417 
00418       if (status)
00419         current_state = STATE_WAITING_FOR_PHASE_2;
00420 
00421       meta_topic (META_DEBUG_SM,
00422                   "Requested phase 2, status = %d\n", status);
00423     }
00424 
00425   if (current_state == STATE_SAVING_PHASE_2 &&
00426       interaction_allowed)
00427     {
00428       Status status;
00429 
00430       status = SmcInteractRequest (session_connection,
00431                                    /* ignore this feature of the protocol by always
00432                                     * claiming normal
00433                                     */
00434                                    SmDialogNormal,
00435                                    interact_callback,
00436                                    GINT_TO_POINTER (shutdown));
00437 
00438       if (status)
00439         current_state = STATE_WAITING_FOR_INTERACT;
00440 
00441       meta_topic (META_DEBUG_SM,
00442                   "Requested interact, status = %d\n", status);
00443     }
00444   
00445   if (current_state == STATE_SAVING_PHASE_1 ||
00446       current_state == STATE_SAVING_PHASE_2 ||
00447       current_state == STATE_DONE_WITH_INTERACT ||
00448       current_state == STATE_SKIPPING_GLOBAL_SAVE)
00449     {
00450       meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone\n");
00451       
00452       SmcSaveYourselfDone (session_connection,
00453                            successful);
00454       
00455       if (shutdown)
00456         current_state = STATE_FROZEN;
00457       else
00458         current_state = STATE_IDLE;
00459     }
00460 }
00461 
00462 static void 
00463 save_phase_2_callback (SmcConn smc_conn, SmPointer client_data)
00464 {
00465   gboolean shutdown;
00466 
00467   meta_topic (META_DEBUG_SM, "Phase 2 save");
00468   
00469   shutdown = GPOINTER_TO_INT (client_data);
00470   
00471   current_state = STATE_SAVING_PHASE_2;
00472 
00473   save_state ();
00474   
00475   save_yourself_possibly_done (shutdown, TRUE);
00476 }
00477 
00478 static void
00479 save_yourself_callback (SmcConn   smc_conn,
00480                         SmPointer client_data,
00481                         int       save_style,
00482                         Bool      shutdown,
00483                         int       interact_style,
00484                         Bool      fast)
00485 {
00486   gboolean successful;
00487 
00488   meta_topic (META_DEBUG_SM, "SaveYourself received");
00489   
00490   successful = TRUE;
00491   
00492   /* The first SaveYourself after registering for the first time
00493    * is a special case (SM specs 7.2).
00494    */
00495 
00496 #if 0 /* I think the GnomeClient rationale for this doesn't apply */
00497   if (current_state == STATE_REGISTERING)
00498     {
00499       current_state = STATE_IDLE;
00500       /* Double check that this is a section 7.2 SaveYourself: */
00501       
00502       if (save_style == SmSaveLocal && 
00503           interact_style == SmInteractStyleNone &&
00504           !shutdown && !fast)
00505         {
00506           /* The protocol requires this even if xsm ignores it. */
00507           SmcSaveYourselfDone (session_connection, successful);
00508           return;
00509         }
00510     }
00511 #endif
00512 
00513   /* ignore Global style saves
00514    * 
00515    * This interpretaion of the Local/Global/Both styles
00516    * was discussed extensively on the xdg-list. See:
00517    *
00518    * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html
00519    */
00520   if (save_style == SmSaveGlobal)
00521     {
00522       current_state = STATE_SKIPPING_GLOBAL_SAVE;
00523       save_yourself_possibly_done (shutdown, successful);
00524       return;
00525     }
00526 
00527   interaction_allowed = interact_style != SmInteractStyleNone;
00528   
00529   current_state = STATE_SAVING_PHASE_1;
00530 
00531   regenerate_save_file ();
00532   
00533   set_clone_restart_commands ();
00534 
00535   save_yourself_possibly_done (shutdown, successful);
00536 }
00537 
00538 
00539 static void
00540 die_callback (SmcConn smc_conn, SmPointer client_data)
00541 {
00542   meta_topic (META_DEBUG_SM, "Exiting at request of session manager\n");
00543   disconnect ();
00544   meta_quit (META_EXIT_SUCCESS);
00545 }
00546 
00547 static void
00548 save_complete_callback (SmcConn smc_conn, SmPointer client_data)
00549 {
00550   /* nothing */
00551   meta_topic (META_DEBUG_SM, "SaveComplete received\n");
00552 }
00553 
00554 static void
00555 shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data)
00556 {
00557   meta_topic (META_DEBUG_SM, "Shutdown cancelled received\n");
00558   
00559   if (session_connection != NULL &&
00560       (current_state != STATE_IDLE && current_state != STATE_FROZEN))
00561     {
00562       SmcSaveYourselfDone (session_connection, True);
00563       current_state = STATE_IDLE;
00564     }
00565 }
00566 
00567 static void 
00568 interact_callback (SmcConn smc_conn, SmPointer client_data)
00569 {
00570   /* nothing */
00571   gboolean shutdown;
00572 
00573   meta_topic (META_DEBUG_SM, "Interaction permission received\n");
00574   
00575   shutdown = GPOINTER_TO_INT (client_data);
00576 
00577   current_state = STATE_DONE_WITH_INTERACT;
00578 
00579   warn_about_lame_clients_and_finish_interact (shutdown);
00580 }
00581 
00582 static void
00583 set_clone_restart_commands (void)
00584 {
00585   char *restartv[10];
00586   char *clonev[10];
00587   char *discardv[10];
00588   int i;
00589   SmProp prop1, prop2, prop3, *props[3];
00590   
00591   /* Restart (use same client ID) */
00592   
00593   prop1.name = SmRestartCommand;
00594   prop1.type = SmLISTofARRAY8;
00595   
00596   g_return_if_fail (client_id);
00597   
00598   i = 0;
00599   restartv[i] = "metacity";
00600   ++i;
00601   restartv[i] = "--sm-client-id";
00602   ++i;
00603   restartv[i] = client_id;
00604   ++i;
00605   restartv[i] = NULL;
00606 
00607   prop1.vals = g_new (SmPropValue, i);
00608   i = 0;
00609   while (restartv[i])
00610     {
00611       prop1.vals[i].value = restartv[i];
00612       prop1.vals[i].length = strlen (restartv[i]);
00613       ++i;
00614     }
00615   prop1.num_vals = i;
00616 
00617   /* Clone (no client ID) */
00618   
00619   i = 0;
00620   clonev[i] = "metacity";
00621   ++i;
00622   clonev[i] = NULL;
00623 
00624   prop2.name = SmCloneCommand;
00625   prop2.type = SmLISTofARRAY8;
00626   
00627   prop2.vals = g_new (SmPropValue, i);
00628   i = 0;
00629   while (clonev[i])
00630     {
00631       prop2.vals[i].value = clonev[i];
00632       prop2.vals[i].length = strlen (clonev[i]);
00633       ++i;
00634     }
00635   prop2.num_vals = i;
00636 
00637   /* Discard */
00638   
00639   i = 0;
00640   discardv[i] = "rm";
00641   ++i;
00642   discardv[i] = "-f";
00643   ++i;
00644   discardv[i] = (char*) full_save_file ();
00645   ++i;
00646   discardv[i] = NULL;
00647   
00648   prop3.name = SmDiscardCommand;
00649   prop3.type = SmLISTofARRAY8;
00650   
00651   prop3.vals = g_new (SmPropValue, i);
00652   i = 0;
00653   while (discardv[i])
00654     {
00655       prop3.vals[i].value = discardv[i];
00656       prop3.vals[i].length = strlen (discardv[i]);
00657       ++i;
00658     }
00659   prop3.num_vals = i;
00660 
00661   
00662   props[0] = &prop1;
00663   props[1] = &prop2;
00664   props[2] = &prop3;
00665   
00666   SmcSetProperties (session_connection, 3, props);
00667 
00668   g_free (prop1.vals);
00669   g_free (prop2.vals);
00670   g_free (prop3.vals);
00671 }
00672 
00673 /* The remaining code in this file actually loads/saves the session,
00674  * while the code above this comment handles chatting with the
00675  * session manager.
00676  */
00677 
00678 static const char*
00679 window_type_to_string (MetaWindowType type)
00680 {
00681   switch (type)
00682     {
00683     case META_WINDOW_NORMAL:
00684       return "normal";
00685     case META_WINDOW_DESKTOP:
00686       return "desktop";
00687     case META_WINDOW_DOCK:
00688       return "dock";
00689     case META_WINDOW_DIALOG:
00690       return "dialog";
00691     case META_WINDOW_MODAL_DIALOG:
00692       return "modal_dialog";
00693     case META_WINDOW_TOOLBAR:
00694       return "toolbar";
00695     case META_WINDOW_MENU:
00696       return "menu";
00697     case META_WINDOW_SPLASHSCREEN:
00698       return "splashscreen";
00699     case META_WINDOW_UTILITY:
00700       return "utility";
00701     }
00702 
00703   return "";
00704 } 
00705 
00706 static MetaWindowType
00707 window_type_from_string (const char *str)
00708 {
00709   if (strcmp (str, "normal") == 0)
00710     return META_WINDOW_NORMAL;
00711   else if (strcmp (str, "desktop") == 0)
00712     return META_WINDOW_DESKTOP;
00713   else if (strcmp (str, "dock") == 0)
00714     return META_WINDOW_DOCK;
00715   else if (strcmp (str, "dialog") == 0)
00716     return META_WINDOW_DIALOG;
00717   else if (strcmp (str, "modal_dialog") == 0)
00718     return META_WINDOW_MODAL_DIALOG;
00719   else if (strcmp (str, "toolbar") == 0)
00720     return META_WINDOW_TOOLBAR;
00721   else if (strcmp (str, "menu") == 0)
00722     return META_WINDOW_MENU;
00723   else if (strcmp (str, "utility") == 0)
00724     return META_WINDOW_UTILITY;
00725   else if (strcmp (str, "splashscreen") == 0)
00726     return META_WINDOW_SPLASHSCREEN;
00727   else
00728     return META_WINDOW_NORMAL;
00729 }
00730 
00731 static int
00732 window_gravity_from_string (const char *str)
00733 {
00734   if (strcmp (str, "NorthWestGravity") == 0)
00735     return NorthWestGravity;
00736   else if (strcmp (str, "NorthGravity") == 0)
00737     return NorthGravity;
00738   else if (strcmp (str, "NorthEastGravity") == 0)
00739     return NorthEastGravity;
00740   else if (strcmp (str, "WestGravity") == 0)
00741     return WestGravity;
00742   else if (strcmp (str, "CenterGravity") == 0)
00743     return CenterGravity;
00744   else if (strcmp (str, "EastGravity") == 0)
00745     return EastGravity;
00746   else if (strcmp (str, "SouthWestGravity") == 0)
00747     return SouthWestGravity;
00748   else if (strcmp (str, "SouthGravity") == 0)
00749     return SouthGravity;
00750   else if (strcmp (str, "SouthEastGravity") == 0)
00751     return SouthEastGravity;
00752   else if (strcmp (str, "StaticGravity") == 0)
00753     return StaticGravity;
00754   else
00755     return NorthWestGravity;
00756 }
00757 
00758 static char*
00759 encode_text_as_utf8_markup (const char *text)
00760 {
00761   /* text can be any encoding, and is nul-terminated.
00762    * we pretend it's Latin-1 and encode as UTF-8
00763    */
00764   GString *str;
00765   const char *p;
00766   char *escaped;
00767   
00768   str = g_string_new ("");
00769 
00770   p = text;
00771   while (*p)
00772     {
00773       g_string_append_unichar (str, *p);
00774       ++p;
00775     }
00776 
00777   escaped = g_markup_escape_text (str->str, str->len);
00778   g_string_free (str, TRUE);
00779   
00780   return escaped;
00781 }
00782 
00783 static char*
00784 decode_text_from_utf8 (const char *text)
00785 {
00786   /* Convert back from the encoded (but not escaped) UTF-8 */
00787   GString *str;
00788   const char *p;
00789 
00790   str = g_string_new ("");
00791   
00792   p = text;
00793   while (*p)
00794     {
00795       /* obviously this barfs if the UTF-8 contains chars > 255 */
00796       g_string_append_c (str, g_utf8_get_char (p));
00797 
00798       p = g_utf8_next_char (p);
00799     }
00800 
00801   return g_string_free (str, FALSE);
00802 }
00803 
00804 static void
00805 save_state (void)
00806 {
00807   char *metacity_dir;
00808   char *session_dir;
00809   FILE *outfile;
00810   GSList *windows;
00811   GSList *tmp;
00812   int stack_position;
00813   
00814   g_assert (client_id);
00815 
00816   outfile = NULL;
00817   
00818   /*
00819    * g_get_user_config_dir() is guaranteed to return an existing directory.
00820    * Eventually, if SM stays with the WM, I'd like to make this
00821    * something like <config>/window_placement in a standard format.
00822    * Future optimisers should note also that by the time we get here
00823    * we probably already have full_save_path figured out and therefore
00824    * can just use the directory name from that.
00825    */
00826   metacity_dir = g_strconcat (g_get_user_config_dir (),
00827                               G_DIR_SEPARATOR_S "metacity",
00828                               NULL);
00829   
00830   session_dir = g_strconcat (metacity_dir,
00831                              G_DIR_SEPARATOR_S "sessions",
00832                              NULL);
00833 
00834   if (mkdir (metacity_dir, 0700) < 0 &&
00835       errno != EEXIST)
00836     {
00837       meta_warning (_("Could not create directory '%s': %s\n"),
00838                     metacity_dir, g_strerror (errno));
00839     }
00840 
00841   if (mkdir (session_dir, 0700) < 0 &&
00842       errno != EEXIST)
00843     {
00844       meta_warning (_("Could not create directory '%s': %s\n"),
00845                     session_dir, g_strerror (errno));
00846     }
00847 
00848   meta_topic (META_DEBUG_SM, "Saving session to '%s'\n", full_save_file ());
00849   
00850   outfile = fopen (full_save_file (), "w");
00851 
00852   if (outfile == NULL)
00853     {
00854       meta_warning (_("Could not open session file '%s' for writing: %s\n"),
00855                     full_save_file (), g_strerror (errno));
00856       goto out;
00857     }
00858 
00859   /* The file format is:
00860    * <metacity_session id="foo">
00861    *   <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5">
00862    *     <workspace index="2"/>
00863    *     <workspace index="4"/>
00864    *     <sticky/> <minimized/> <maximized/>
00865    *     <geometry x="100" y="100" width="200" height="200" gravity="northwest"/>
00866    *   </window>
00867    * </metacity_session>
00868    *
00869    * Note that attributes on <window> are the match info we use to
00870    * see if the saved state applies to a restored window, and
00871    * child elements are the saved state to be applied.
00872    * 
00873    */
00874   
00875   fprintf (outfile, "<metacity_session id=\"%s\">\n",
00876            client_id);
00877 
00878   windows = meta_display_list_windows (meta_get_display ());
00879   stack_position = 0;
00880 
00881   windows = g_slist_sort (windows, meta_display_stack_cmp);
00882   tmp = windows;
00883   stack_position = 0;
00884 
00885   while (tmp != NULL)
00886     {
00887       MetaWindow *window;
00888 
00889       window = tmp->data;
00890 
00891       if (window->sm_client_id)
00892         {
00893           char *sm_client_id;
00894           char *res_class;
00895           char *res_name;
00896           char *role;
00897           char *title;
00898 
00899           /* client id, class, name, role are not expected to be
00900            * in UTF-8 (I think they are in XPCS which is Latin-1?
00901            * in practice they are always ascii though.)
00902            */
00903               
00904           sm_client_id = encode_text_as_utf8_markup (window->sm_client_id);
00905           res_class = window->res_class ?
00906             encode_text_as_utf8_markup (window->res_class) : NULL;
00907           res_name = window->res_name ?
00908             encode_text_as_utf8_markup (window->res_name) : NULL;
00909           role = window->role ?
00910             encode_text_as_utf8_markup (window->role) : NULL;
00911           if (window->title)
00912             title = g_markup_escape_text (window->title, -1);
00913           else
00914             title = NULL;
00915               
00916           meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'\n",
00917                       window->desc, window->sm_client_id);
00918 
00919           fprintf (outfile,
00920                    "  <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n",
00921                    sm_client_id,
00922                    res_class ? res_class : "",
00923                    res_name ? res_name : "",
00924                    title ? title : "",
00925                    role ? role : "",
00926                    window_type_to_string (window->type),
00927                    stack_position);
00928 
00929           g_free (sm_client_id);
00930           g_free (res_class);
00931           g_free (res_name);
00932           g_free (role);
00933           g_free (title);
00934               
00935           /* Sticky */
00936           if (window->on_all_workspaces)
00937             fputs ("    <sticky/>\n", outfile);
00938 
00939           /* Minimized */
00940           if (window->minimized)
00941             fputs ("    <minimized/>\n", outfile);
00942 
00943           /* Maximized */
00944           if (META_WINDOW_MAXIMIZED (window))
00945             {
00946               fprintf (outfile,
00947                        "    <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n", 
00948                        window->saved_rect.x,
00949                        window->saved_rect.y,
00950                        window->saved_rect.width,
00951                        window->saved_rect.height);
00952             }
00953               
00954           /* Workspaces we're on */
00955           {
00956             int n;
00957             n = meta_workspace_index (window->workspace);
00958             fprintf (outfile,
00959                      "    <workspace index=\"%d\"/>\n", n);
00960           }
00961 
00962           /* Gravity */
00963           {
00964             int x, y, w, h;
00965             meta_window_get_geometry (window, &x, &y, &w, &h);
00966             
00967             fprintf (outfile,
00968                      "    <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n",
00969                      x, y, w, h,
00970                      meta_gravity_to_string (window->size_hints.win_gravity));
00971           }
00972               
00973           fputs ("  </window>\n", outfile);
00974         }
00975       else
00976         {
00977           meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed\n",
00978                       window->desc);
00979         }
00980           
00981       tmp = tmp->next;
00982       ++stack_position;
00983     }
00984       
00985   g_slist_free (windows);
00986 
00987   fputs ("</metacity_session>\n", outfile);
00988   
00989  out:
00990   if (outfile)
00991     {
00992       /* FIXME need a dialog for this */
00993       if (ferror (outfile))
00994         {
00995           meta_warning (_("Error writing session file '%s': %s\n"),
00996                         full_save_file (), g_strerror (errno));
00997         }
00998       if (fclose (outfile))
00999         {
01000           meta_warning (_("Error closing session file '%s': %s\n"),
01001                         full_save_file (), g_strerror (errno));
01002         }
01003     }
01004   
01005   g_free (metacity_dir);
01006   g_free (session_dir);
01007 }
01008 
01009 typedef enum
01010 {
01011   WINDOW_TAG_NONE,
01012   WINDOW_TAG_DESKTOP,
01013   WINDOW_TAG_STICKY,
01014   WINDOW_TAG_MINIMIZED,
01015   WINDOW_TAG_MAXIMIZED,
01016   WINDOW_TAG_GEOMETRY
01017 } WindowTag;
01018 
01019 typedef struct
01020 {
01021   MetaWindowSessionInfo *info;
01022   char *previous_id;
01023 } ParseData;
01024 
01025 static void                   session_info_free (MetaWindowSessionInfo *info);
01026 static MetaWindowSessionInfo* session_info_new  (void);
01027 
01028 static void start_element_handler (GMarkupParseContext  *context,
01029                                    const gchar          *element_name,
01030                                    const gchar         **attribute_names,
01031                                    const gchar         **attribute_values,
01032                                    gpointer              user_data,
01033                                    GError              **error);
01034 static void end_element_handler   (GMarkupParseContext  *context,
01035                                    const gchar          *element_name,
01036                                    gpointer              user_data,
01037                                    GError              **error);
01038 static void text_handler          (GMarkupParseContext  *context,
01039                                    const gchar          *text,
01040                                    gsize                 text_len,
01041                                    gpointer              user_data,
01042                                    GError              **error);
01043 
01044 static GMarkupParser metacity_session_parser = {
01045   start_element_handler,
01046   end_element_handler,
01047   text_handler,
01048   NULL,
01049   NULL
01050 };
01051 
01052 static GSList *window_info_list = NULL;
01053 
01054 static char*
01055 load_state (const char *previous_save_file)
01056 {
01057   GMarkupParseContext *context;
01058   GError *error;
01059   ParseData parse_data;
01060   char *text;
01061   gsize length;
01062   char *session_file;
01063 
01064   session_file = g_strconcat (g_get_user_config_dir (),
01065                               G_DIR_SEPARATOR_S "metacity"
01066                               G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
01067                               previous_save_file,
01068                               NULL);
01069 
01070   error = NULL;
01071   if (!g_file_get_contents (session_file,
01072                             &text,
01073                             &length,
01074                             &error))
01075     {
01076       char *canonical_session_file = session_file;
01077 
01078       /* Maybe they were doing it the old way, with ~/.metacity */
01079       session_file = g_strconcat (g_get_home_dir (),
01080                                   G_DIR_SEPARATOR_S ".metacity"
01081                                   G_DIR_SEPARATOR_S "sessions"
01082                                   G_DIR_SEPARATOR_S,
01083                                   previous_save_file,
01084                                   NULL);
01085       
01086       if (!g_file_get_contents (session_file,
01087                                 &text,
01088                                 &length,
01089                                 NULL))
01090         {
01091           /* oh, just give up */
01092 
01093           meta_warning (_("Failed to read saved session file %s: %s\n"),
01094                     canonical_session_file, error->message);
01095           g_error_free (error);
01096           g_free (session_file);
01097           g_free (canonical_session_file);
01098           return NULL;
01099         }
01100 
01101       g_free (canonical_session_file);
01102     }
01103 
01104   meta_topic (META_DEBUG_SM, "Parsing saved session file %s\n", session_file);
01105   g_free (session_file);
01106   session_file = NULL;
01107   
01108   parse_data.info = NULL;
01109   parse_data.previous_id = NULL;
01110   
01111   context = g_markup_parse_context_new (&metacity_session_parser,
01112                                         0, &parse_data, NULL);
01113 
01114   error = NULL;
01115   if (!g_markup_parse_context_parse (context,
01116                                      text,
01117                                      length,
01118                                      &error))
01119     goto error;
01120   
01121   
01122   error = NULL;
01123   if (!g_markup_parse_context_end_parse (context, &error))
01124     goto error;
01125 
01126   g_markup_parse_context_free (context);
01127 
01128   goto out;
01129 
01130  error:
01131   
01132   meta_warning (_("Failed to parse saved session file: %s\n"),
01133                 error->message);
01134   g_error_free (error);
01135 
01136   if (parse_data.info)
01137     session_info_free (parse_data.info);
01138 
01139   g_free (parse_data.previous_id);
01140   parse_data.previous_id = NULL;
01141   
01142  out:
01143   
01144   g_free (text);
01145 
01146   return parse_data.previous_id;
01147 }
01148 
01149 /* FIXME this isn't very robust against bogus session files */
01150 static void
01151 start_element_handler  (GMarkupParseContext *context,
01152                         const gchar         *element_name,
01153                         const gchar        **attribute_names,
01154                         const gchar        **attribute_values,
01155                         gpointer             user_data,
01156                         GError             **error)
01157 {
01158   ParseData *pd;
01159 
01160   pd = user_data;
01161 
01162   if (strcmp (element_name, "metacity_session") == 0)
01163     {
01164       /* Get previous ID */
01165       int i;      
01166 
01167       i = 0;
01168       while (attribute_names[i])
01169         {
01170           const char *name;
01171           const char *val;
01172           
01173           name = attribute_names[i];
01174           val = attribute_values[i];
01175 
01176           if (pd->previous_id)
01177             {
01178               g_set_error (error,
01179                            G_MARKUP_ERROR,
01180                        G_MARKUP_ERROR_PARSE,
01181                            _("<metacity_session> attribute seen but we already have the session ID"));
01182               return;
01183             }
01184           
01185           if (strcmp (name, "id") == 0)
01186             {
01187               pd->previous_id = decode_text_from_utf8 (val);
01188             }
01189           else
01190             {
01191               g_set_error (error,
01192                            G_MARKUP_ERROR,
01193                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
01194                            _("Unknown attribute %s on <metacity_session> element"),
01195                            name);
01196               return;
01197             }
01198           
01199           ++i;
01200         }
01201     }
01202   else if (strcmp (element_name, "window") == 0)
01203     {
01204       int i;
01205       
01206       if (pd->info)
01207         {
01208           g_set_error (error,
01209                        G_MARKUP_ERROR,
01210                        G_MARKUP_ERROR_PARSE,
01211                        _("nested <window> tag"));
01212           return;
01213         }
01214       
01215       pd->info = session_info_new ();
01216 
01217       i = 0;
01218       while (attribute_names[i])
01219         {
01220           const char *name;
01221           const char *val;
01222           
01223           name = attribute_names[i];
01224           val = attribute_values[i];
01225           
01226           if (strcmp (name, "id") == 0)
01227             {
01228               if (*val)
01229                 pd->info->id = decode_text_from_utf8 (val);
01230             }
01231           else if (strcmp (name, "class") == 0)
01232             {
01233               if (*val)
01234                 pd->info->res_class = decode_text_from_utf8 (val);
01235             }
01236           else if (strcmp (name, "name") == 0)
01237             {
01238               if (*val)
01239                 pd->info->res_name = decode_text_from_utf8 (val);
01240             }
01241           else if (strcmp (name, "title") == 0)
01242             {
01243               if (*val)
01244                 pd->info->title = g_strdup (val);
01245             }
01246           else if (strcmp (name, "role") == 0)
01247             {
01248               if (*val)
01249                 pd->info->role = decode_text_from_utf8 (val);
01250             }
01251           else if (strcmp (name, "type") == 0)
01252             {
01253               if (*val)
01254                 pd->info->type = window_type_from_string (val);
01255             }
01256           else if (strcmp (name, "stacking") == 0)
01257             {
01258               if (*val)
01259                 {
01260                   pd->info->stack_position = atoi (val);
01261                   pd->info->stack_position_set = TRUE;
01262                 }
01263             }
01264           else
01265             {
01266               g_set_error (error,
01267                            G_MARKUP_ERROR,
01268                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
01269                            _("Unknown attribute %s on <window> element"),
01270                            name);
01271               session_info_free (pd->info);
01272               pd->info = NULL;
01273               return;
01274             }
01275           
01276           ++i;
01277         }
01278     }
01279   else if (strcmp (element_name, "workspace") == 0)
01280     {
01281       int i;
01282 
01283       i = 0;
01284       while (attribute_names[i])
01285         {
01286           const char *name;
01287 
01288           name = attribute_names[i];
01289           
01290           if (strcmp (name, "index") == 0)
01291             {
01292               pd->info->workspace_indices =
01293                 g_slist_prepend (pd->info->workspace_indices,
01294                                  GINT_TO_POINTER (atoi (attribute_values[i])));
01295             }
01296           else
01297             {
01298               g_set_error (error,
01299                            G_MARKUP_ERROR,
01300                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
01301                            _("Unknown attribute %s on <window> element"),
01302                            name);
01303               session_info_free (pd->info);
01304               pd->info = NULL;
01305               return;
01306             }
01307           
01308           ++i;
01309         }
01310     }
01311   else if (strcmp (element_name, "sticky") == 0)
01312     {
01313       pd->info->on_all_workspaces = TRUE;
01314       pd->info->on_all_workspaces_set = TRUE;
01315     }
01316   else if (strcmp (element_name, "minimized") == 0)
01317     {
01318       pd->info->minimized = TRUE;
01319       pd->info->minimized_set = TRUE;
01320     }
01321   else if (strcmp (element_name, "maximized") == 0)
01322     {
01323       int i;
01324 
01325       i = 0;
01326       pd->info->maximized = TRUE;
01327       pd->info->maximized_set = TRUE;
01328       while (attribute_names[i])
01329         {
01330           const char *name;
01331           const char *val;
01332 
01333           name = attribute_names[i];
01334           val = attribute_values[i];
01335 
01336           if (strcmp (name, "saved_x") == 0)
01337             {
01338               if (*val)
01339                 {
01340                   pd->info->saved_rect.x = atoi (val);
01341                   pd->info->saved_rect_set = TRUE;
01342                 }
01343             }
01344           else if (strcmp (name, "saved_y") == 0)
01345             {
01346               if (*val)
01347                 {
01348                   pd->info->saved_rect.y = atoi (val);
01349                   pd->info->saved_rect_set = TRUE;
01350                 }
01351             }
01352           else if (strcmp (name, "saved_width") == 0)
01353             {
01354               if (*val)
01355                 {
01356                   pd->info->saved_rect.width = atoi (val);
01357                   pd->info->saved_rect_set = TRUE;
01358                 }
01359             }
01360           else if (strcmp (name, "saved_height") == 0)
01361             {
01362               if (*val)
01363                 {
01364                   pd->info->saved_rect.height = atoi (val);
01365                   pd->info->saved_rect_set = TRUE;
01366                 }
01367             }
01368           else
01369             {
01370               g_set_error (error,
01371                            G_MARKUP_ERROR,
01372                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
01373                            _("Unknown attribute %s on <maximized> element"),
01374                            name);
01375               return;
01376             }
01377 
01378           ++i;
01379         }
01380 
01381       if (pd->info->saved_rect_set)
01382         meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d \n",
01383                     pd->info->saved_rect.x,
01384                     pd->info->saved_rect.y,
01385                     pd->info->saved_rect.width,
01386                     pd->info->saved_rect.height);
01387     }  
01388   else if (strcmp (element_name, "geometry") == 0)
01389     {
01390       int i;
01391 
01392       pd->info->geometry_set = TRUE;
01393       
01394       i = 0;
01395       while (attribute_names[i])
01396         {
01397           const char *name;
01398           const char *val;
01399           
01400           name = attribute_names[i];
01401           val = attribute_values[i];
01402           
01403           if (strcmp (name, "x") == 0)
01404             {
01405               if (*val)
01406                 pd->info->rect.x = atoi (val);
01407             }
01408           else if (strcmp (name, "y") == 0)
01409             {
01410               if (*val)
01411                 pd->info->rect.y = atoi (val);
01412             }
01413           else if (strcmp (name, "width") == 0)
01414             {
01415               if (*val)
01416                 pd->info->rect.width = atoi (val);
01417             }
01418           else if (strcmp (name, "height") == 0)
01419             {
01420               if (*val)
01421                 pd->info->rect.height = atoi (val);
01422             }
01423           else if (strcmp (name, "gravity") == 0)
01424             {
01425               if (*val)
01426                 pd->info->gravity = window_gravity_from_string (val);
01427             }
01428           else
01429             {
01430               g_set_error (error,
01431                            G_MARKUP_ERROR,
01432                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
01433                            _("Unknown attribute %s on <geometry> element"),
01434                            name);
01435               return;
01436             }
01437           
01438           ++i;
01439         }
01440 
01441       meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s\n",
01442                   pd->info->rect.x,
01443                   pd->info->rect.y,
01444                   pd->info->rect.width,
01445                   pd->info->rect.height,
01446                   meta_gravity_to_string (pd->info->gravity));
01447     }
01448   else
01449     {
01450       g_set_error (error,
01451                    G_MARKUP_ERROR,
01452                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
01453                    _("Unknown element %s"),
01454                    element_name);
01455       return;
01456     }
01457 }
01458 
01459 static void
01460 end_element_handler    (GMarkupParseContext *context,
01461                         const gchar         *element_name,
01462                         gpointer             user_data,
01463                         GError             **error)
01464 {
01465   ParseData *pd;
01466 
01467   pd = user_data;
01468 
01469   if (strcmp (element_name, "window") == 0)
01470     {
01471       g_assert (pd->info);
01472 
01473       window_info_list = g_slist_prepend (window_info_list,
01474                                           pd->info);
01475       
01476       meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s\n",
01477                   pd->info->res_class ? pd->info->res_class : "(none)",
01478                   pd->info->res_name ? pd->info->res_name : "(none)",
01479                   pd->info->role ? pd->info->role : "(none)");
01480       
01481       pd->info = NULL;
01482     }
01483 }
01484 
01485 static void
01486 text_handler           (GMarkupParseContext *context,
01487                         const gchar         *text,
01488                         gsize                text_len,
01489                         gpointer             user_data,
01490                         GError             **error)
01491 {
01492   /* Right now we don't have any elements where we care about their
01493    * content
01494    */
01495 }
01496 
01497 static gboolean
01498 both_null_or_matching (const char *a,
01499                        const char *b)
01500 {
01501   if (a == NULL && b == NULL)
01502     return TRUE;
01503   else if (a && b && strcmp (a, b) == 0)
01504     return TRUE;
01505   else
01506     return FALSE;
01507 }
01508 
01509 static GSList*
01510 get_possible_matches (MetaWindow *window)
01511 {
01512   /* Get all windows with this client ID */
01513   GSList *retval;
01514   GSList *tmp;
01515   gboolean ignore_client_id;
01516   
01517   retval = NULL;
01518 
01519   ignore_client_id = g_getenv ("METACITY_DEBUG_SM") != NULL;
01520   
01521   tmp = window_info_list;
01522   while (tmp != NULL)
01523     {
01524       MetaWindowSessionInfo *info;
01525 
01526       info = tmp->data;
01527       
01528       if ((ignore_client_id ||
01529            both_null_or_matching (info->id, window->sm_client_id)) && 
01530           both_null_or_matching (info->res_class, window->res_class) &&
01531           both_null_or_matching (info->res_name, window->res_name) &&
01532           both_null_or_matching (info->role, window->role))
01533         {
01534           meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s\n",
01535                       window->desc,
01536                       info->res_class ? info->res_class : "(none)",
01537                       info->res_name ? info->res_name : "(none)",
01538                       info->role ? info->role : "(none)");
01539 
01540           retval = g_slist_prepend (retval, info);
01541         }
01542       else
01543         {
01544           if (meta_is_verbose ())
01545             {
01546               if (!both_null_or_matching (info->id, window->sm_client_id))
01547                 meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match\n",
01548                             window->desc,
01549                             window->sm_client_id ? window->sm_client_id : "(none)",
01550                             info->id ? info->id : "(none)");
01551               else if (!both_null_or_matching (info->res_class, window->res_class))
01552                 meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match\n",
01553                             window->desc,
01554                             window->res_class ? window->res_class : "(none)",
01555                             info->res_class ? info->res_class : "(none)");
01556               
01557               else if (!both_null_or_matching (info->res_name, window->res_name))
01558                 meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match\n",
01559                             window->desc,
01560                             window->res_name ? window->res_name : "(none)",
01561                             info->res_name ? info->res_name : "(none)");
01562               else if (!both_null_or_matching (info->role, window->role))
01563                 meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match\n",
01564                             window->desc,
01565                             window->role ? window->role : "(none)",
01566                             info->role ? info->role : "(none)");
01567               else
01568                 meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason\n",
01569                             window->desc, info->id);
01570             }
01571         }
01572       
01573       tmp = tmp->next;
01574     }
01575 
01576   return retval;
01577 }
01578 
01579 static const MetaWindowSessionInfo*
01580 find_best_match (GSList     *infos,
01581                  MetaWindow *window)
01582 {
01583   GSList *tmp;
01584   const MetaWindowSessionInfo *matching_title;
01585   const MetaWindowSessionInfo *matching_type;
01586   
01587   matching_title = NULL;
01588   matching_type = NULL;
01589   
01590   tmp = infos;
01591   while (tmp != NULL)
01592     {
01593       MetaWindowSessionInfo *info;
01594 
01595       info = tmp->data;
01596 
01597       if (matching_title == NULL &&
01598           both_null_or_matching (info->title, window->title))
01599         matching_title = info;
01600 
01601       if (matching_type == NULL &&
01602           info->type == window->type)
01603         matching_type = info;
01604       
01605       tmp = tmp->next;
01606     }
01607 
01608   /* Prefer same title, then same type of window, then
01609    * just pick something. Eventually we could enhance this
01610    * to e.g. break ties by geometry hint similarity,
01611    * or other window features.
01612    */
01613   
01614   if (matching_title)
01615     return matching_title;
01616   else if (matching_type)
01617     return matching_type;
01618   else
01619     return infos->data;
01620 }
01621 
01622 const MetaWindowSessionInfo*
01623 meta_window_lookup_saved_state (MetaWindow *window)
01624 {
01625   GSList *possibles;
01626   const MetaWindowSessionInfo *info;
01627   
01628   /* Window is not session managed.
01629    * I haven't yet figured out how to deal with these
01630    * in a way that doesn't cause broken side effects in
01631    * situations other than on session restore.
01632    */
01633   if (window->sm_client_id == NULL)
01634     {
01635       meta_topic (META_DEBUG_SM,
01636                   "Window %s is not session managed, not checking for saved state\n",
01637                   window->desc);
01638       return NULL;
01639     }
01640 
01641   possibles = get_possible_matches (window);
01642 
01643   if (possibles == NULL)
01644     {
01645       meta_topic (META_DEBUG_SM, "Window %s has no possible matches in the list of saved window states\n",
01646                   window->desc);
01647       return NULL;
01648     }
01649 
01650   info = find_best_match (possibles, window);
01651   
01652   g_slist_free (possibles);
01653   
01654   return info;
01655 }
01656 
01657 void
01658 meta_window_release_saved_state (const MetaWindowSessionInfo *info)
01659 {
01660   /* We don't want to use the same saved state again for another
01661    * window.
01662    */
01663   window_info_list = g_slist_remove (window_info_list, info);
01664 
01665   session_info_free ((MetaWindowSessionInfo*) info);
01666 }
01667 
01668 static void
01669 session_info_free (MetaWindowSessionInfo *info)
01670 {
01671   g_free (info->id);
01672   g_free (info->res_class);
01673   g_free (info->res_name);
01674   g_free (info->title);
01675   g_free (info->role);
01676 
01677   g_slist_free (info->workspace_indices);
01678   
01679   g_free (info);
01680 }
01681 
01682 static MetaWindowSessionInfo*
01683 session_info_new (void)
01684 {
01685   MetaWindowSessionInfo *info;
01686 
01687   info = g_new0 (MetaWindowSessionInfo, 1);
01688 
01689   info->type = META_WINDOW_NORMAL;
01690   info->gravity = NorthWestGravity;
01691   
01692   return info;
01693 }
01694 
01695 static char* full_save_path = NULL;
01696 
01697 static void
01698 regenerate_save_file (void)
01699 {
01700   g_free (full_save_path);
01701 
01702   if (client_id)
01703     full_save_path = g_strconcat (g_get_user_config_dir (),
01704                                   G_DIR_SEPARATOR_S "metacity"
01705                                   G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
01706                                   client_id,
01707                                   ".ms",
01708                                   NULL);
01709   else
01710     full_save_path = NULL;
01711 }
01712 
01713 static const char*
01714 full_save_file (void)
01715 {
01716   return full_save_path;
01717 }
01718 
01719 static int
01720 windows_cmp_by_title (MetaWindow *a,
01721                       MetaWindow *b)
01722 {
01723   return g_utf8_collate (a->title, b->title);
01724 }
01725 
01726 typedef struct
01727 {
01728   int child_pid;
01729   int child_pipe;
01730   gboolean shutdown;
01731 } LameClientsDialogData;
01732 
01733 static void
01734 finish_interact (gboolean shutdown)
01735 {
01736   if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */
01737     {
01738       SmcInteractDone (session_connection, False /* don't cancel logout */);
01739       
01740       save_yourself_possibly_done (shutdown, TRUE);
01741     }
01742 }
01743 
01744 static gboolean  
01745 io_from_warning_dialog (GIOChannel   *channel,
01746                         GIOCondition  condition,
01747                         gpointer      data)
01748 {
01749   LameClientsDialogData *d;
01750 
01751   d = data;
01752   
01753   meta_topic (META_DEBUG_PING,
01754               "IO handler from lame clients dialog, condition = %x\n",
01755               condition);
01756   
01757   if (condition & (G_IO_HUP | G_IO_NVAL | G_IO_ERR))
01758     {
01759       finish_interact (d->shutdown);
01760 
01761       /* Remove the callback, freeing data */
01762       return FALSE; 
01763     }
01764   else if (condition & G_IO_IN)
01765     {
01766       /* Check for EOF */
01767       
01768       char buf[16];
01769       int ret;
01770  
01771       ret = read (d->child_pipe, buf, sizeof (buf));
01772       if (ret == 0)
01773         {
01774           finish_interact (d->shutdown);
01775           return FALSE;
01776         }
01777     }
01778 
01779   /* Keep callback installed */
01780   return TRUE;
01781 }
01782 
01783 static void
01784 warn_about_lame_clients_and_finish_interact (gboolean shutdown)
01785 {
01786   GSList *lame;
01787   GSList *windows;
01788   char **argv;
01789   int i;
01790   GSList *tmp;
01791   int len;
01792   int child_pid;
01793   int child_pipe;
01794   GError *err;
01795   GIOChannel *channel;
01796   LameClientsDialogData *d;
01797   guint32 timestamp;
01798   char timestampbuf[32];
01799   
01800   lame = NULL;
01801   windows = meta_display_list_windows (meta_get_display ());
01802   tmp = windows;
01803   while (tmp != NULL)
01804     {
01805       MetaWindow *window;
01806           
01807       window = tmp->data;
01808 
01809       /* only complain about normal windows, the others
01810        * are kind of dumb to worry about
01811        */
01812       if (window->sm_client_id == NULL &&
01813           window->type == META_WINDOW_NORMAL)
01814         lame = g_slist_prepend (lame, window);
01815           
01816       tmp = tmp->next;
01817     }
01818       
01819   g_slist_free (windows);
01820   
01821   if (lame == NULL)
01822     {
01823       /* No lame apps. */
01824       finish_interact (shutdown);
01825       return;
01826     }
01827   
01828   lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title);
01829 
01830   timestamp = meta_display_get_current_time_roundtrip (meta_get_display ());
01831   sprintf (timestampbuf, "%u", timestamp);
01832 
01833   len = g_slist_length (lame);
01834   len *= 2; /* titles and also classes */
01835   len += 2; /* --timestamp flag and actual timestamp */
01836   len += 1; /* NULL term */
01837   len += 2; /* metacity-dialog command and option */
01838   
01839   argv = g_new0 (char*, len);
01840   
01841   i = 0;
01842 
01843   argv[i] = METACITY_LIBEXECDIR"/metacity-dialog";
01844   ++i;
01845   argv[i] = "--timestamp";
01846   ++i;
01847   argv[i] = timestampbuf;
01848   ++i;
01849   argv[i] = "--warn-about-no-sm-support";
01850   ++i;
01851   
01852   tmp = lame;
01853   while (tmp != NULL)
01854     {
01855       MetaWindow *w = tmp->data;
01856 
01857       argv[i] = w->title;
01858       ++i;
01859       argv[i] = w->res_class ? w->res_class : "";
01860       ++i;
01861 
01862       tmp = tmp->next;
01863     }
01864 
01865   child_pipe = -1;
01866   child_pid = -1;
01867   err = NULL;
01868   if (!g_spawn_async_with_pipes ("/",
01869                                  argv,
01870                                  NULL,
01871                                  0,
01872                                  NULL, NULL,
01873                                  &child_pid,
01874                                  NULL,
01875                                  &child_pipe,
01876                                  NULL,
01877                                  &err))
01878     {
01879       meta_warning (_("Error launching metacity-dialog to warn about apps that don't support session management: %s\n"),
01880                     err->message);
01881       g_error_free (err);
01882     }
01883 
01884   g_free (argv);
01885   g_slist_free (lame);
01886 
01887   d = g_new0 (LameClientsDialogData, 1);
01888   d->child_pipe = child_pipe;
01889   d->child_pid = child_pid;
01890   d->shutdown = shutdown;
01891   
01892   channel = g_io_channel_unix_new (d->child_pipe);
01893   g_io_add_watch_full (channel, G_PRIORITY_DEFAULT,
01894                        G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
01895                        io_from_warning_dialog,
01896                        d, g_free);
01897   g_io_channel_unref (channel);
01898 }
01899 
01900 #endif /* HAVE_SM */

Generated on Sat Aug 23 22:04:17 2008 for metacity by  doxygen 1.5.5