1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Mutter Session Management */
4 
5 /*
6  * Copyright (C) 2001 Havoc Pennington (some code in here from
7  * libgnomeui, (C) Tom Tromey, Carsten Schaar)
8  * Copyright (C) 2004, 2005 Elijah Newren
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "config.h"
25 
26 #include "x11/session.h"
27 
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <X11/Xatom.h>
31 
32 #include "core/util-private.h"
33 #include "meta/meta-context.h"
34 #include "x11/meta-x11-display-private.h"
35 
36 #ifndef HAVE_SM
37 void
meta_session_init(MetaContext * context,const char * client_id,const char * save_file)38 meta_session_init (MetaContext *context,
39                    const char  *client_id,
40                    const char  *save_file)
41 {
42   meta_topic (META_DEBUG_SM, "Compiled without session management support");
43 }
44 
45 const MetaWindowSessionInfo*
meta_window_lookup_saved_state(MetaWindow * window)46 meta_window_lookup_saved_state (MetaWindow *window)
47 {
48   return NULL;
49 }
50 
51 void
meta_window_release_saved_state(const MetaWindowSessionInfo * info)52 meta_window_release_saved_state (const MetaWindowSessionInfo *info)
53 {
54   ;
55 }
56 #else /* HAVE_SM */
57 
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <glib.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <sys/stat.h>
65 #include <sys/types.h>
66 #include <unistd.h>
67 #include <X11/ICE/ICElib.h>
68 #include <X11/SM/SMlib.h>
69 
70 #include "core/display-private.h"
71 #include "meta/main.h"
72 #include "meta/util.h"
73 #include "meta/workspace.h"
74 
75 typedef struct _MetaIceConnection
76 {
77   IceConn ice_connection;
78   MetaContext *context;
79 } MetaIceConnection;
80 
81 static void ice_io_error_handler (IceConn connection);
82 
83 static void new_ice_connection (IceConn connection, IcePointer client_data,
84 				Bool opening, IcePointer *watch_data);
85 
86 static void        save_state         (void);
87 static char*       load_state         (const char *previous_save_file);
88 static void        regenerate_save_file (void);
89 static const char* full_save_file       (void);
90 static void        warn_about_lame_clients_and_finish_interact (gboolean shutdown);
91 static void        disconnect         (void);
92 
93 /* This is called when data is available on an ICE connection.  */
94 static gboolean
process_ice_messages(GIOChannel * channel,GIOCondition condition,gpointer user_data)95 process_ice_messages (GIOChannel   *channel,
96                       GIOCondition  condition,
97                       gpointer      user_data)
98 {
99   MetaIceConnection *ice_connection = user_data;
100   IceConn connection = ice_connection->ice_connection;
101   IceProcessMessagesStatus status;
102 
103   /* This blocks infinitely sometimes. I don't know what
104    * to do about it. Checking "condition" just breaks
105    * session management.
106    */
107   status = IceProcessMessages (connection, NULL, NULL);
108 
109   if (status == IceProcessMessagesIOError)
110     {
111       /* We were disconnected; close our connection to the
112        * session manager, this will result in the ICE connection
113        * being cleaned up, since it is owned by libSM.
114        */
115       disconnect ();
116       meta_context_terminate (ice_connection->context);
117 
118       return FALSE;
119     }
120 
121   return TRUE;
122 }
123 
124 /* This is called when a new ICE connection is made.  It arranges for
125    the ICE connection to be handled via the event loop.  */
126 static void
new_ice_connection(IceConn connection,IcePointer client_data,Bool opening,IcePointer * watch_data)127 new_ice_connection (IceConn connection, IcePointer client_data, Bool opening,
128 		    IcePointer *watch_data)
129 {
130   MetaContext *context = client_data;
131   guint input_id;
132 
133   if (opening)
134     {
135       MetaIceConnection *ice_connection;
136       GIOChannel *channel;
137 
138       fcntl (IceConnectionNumber (connection), F_SETFD,
139              fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC);
140 
141       ice_connection = g_new0 (MetaIceConnection, 1);
142       ice_connection->ice_connection = connection;
143       ice_connection->context = context;
144 
145       channel = g_io_channel_unix_new (IceConnectionNumber (connection));
146 
147       input_id = g_io_add_watch_full (channel,
148                                       G_PRIORITY_DEFAULT,
149                                       G_IO_IN | G_IO_ERR,
150                                       process_ice_messages,
151                                       ice_connection,
152                                       g_free);
153 
154       g_io_channel_unref (channel);
155 
156       *watch_data = (IcePointer) GUINT_TO_POINTER (input_id);
157     }
158   else
159     {
160       input_id = GPOINTER_TO_UINT ((gpointer) *watch_data);
161 
162       g_clear_handle_id (&input_id, g_source_remove);
163     }
164 }
165 
166 static IceIOErrorHandler ice_installed_handler;
167 
168 /* We call any handler installed before (or after) gnome_ice_init but
169    avoid calling the default libICE handler which does an exit() */
170 static void
ice_io_error_handler(IceConn connection)171 ice_io_error_handler (IceConn connection)
172 {
173     if (ice_installed_handler)
174       (*ice_installed_handler) (connection);
175 }
176 
177 static void
ice_init(void)178 ice_init (void)
179 {
180   static gboolean ice_initted = FALSE;
181 
182   if (! ice_initted)
183     {
184       IceIOErrorHandler default_handler;
185 
186       ice_installed_handler = IceSetIOErrorHandler (NULL);
187       default_handler = IceSetIOErrorHandler (ice_io_error_handler);
188 
189       if (ice_installed_handler == default_handler)
190 	ice_installed_handler = NULL;
191 
192       IceAddConnectionWatch (new_ice_connection, NULL);
193 
194       ice_initted = TRUE;
195     }
196 }
197 
198 typedef enum
199 {
200   STATE_DISCONNECTED,
201   STATE_IDLE,
202   STATE_SAVING_PHASE_1,
203   STATE_WAITING_FOR_PHASE_2,
204   STATE_SAVING_PHASE_2,
205   STATE_WAITING_FOR_INTERACT,
206   STATE_DONE_WITH_INTERACT,
207   STATE_SKIPPING_GLOBAL_SAVE,
208   STATE_FROZEN,
209   STATE_REGISTERING
210 } ClientState;
211 
212 static void save_phase_2_callback       (SmcConn   smc_conn,
213                                          SmPointer client_data);
214 static void interact_callback           (SmcConn   smc_conn,
215                                          SmPointer client_data);
216 static void shutdown_cancelled_callback (SmcConn   smc_conn,
217                                          SmPointer client_data);
218 static void save_complete_callback      (SmcConn   smc_conn,
219                                          SmPointer client_data);
220 static void die_callback                (SmcConn   smc_conn,
221                                          SmPointer client_data);
222 static void save_yourself_callback      (SmcConn   smc_conn,
223                                          SmPointer client_data,
224                                          int       save_style,
225                                          Bool      shutdown,
226                                          int       interact_style,
227                                          Bool      fast);
228 static void set_clone_restart_commands  (void);
229 
230 static char *client_id = NULL;
231 static gpointer session_connection = NULL;
232 static ClientState current_state = STATE_DISCONNECTED;
233 static gboolean interaction_allowed = FALSE;
234 
235 void
meta_session_init(MetaContext * context,const char * previous_client_id,const char * previous_save_file)236 meta_session_init (MetaContext *context,
237                    const char  *previous_client_id,
238                    const char  *previous_save_file)
239 {
240   /* Some code here from twm */
241   char buf[256];
242   unsigned long mask;
243   SmcCallbacks callbacks;
244   char *saved_client_id;
245 
246   if (!previous_client_id)
247     {
248       const char *desktop_autostart_id;
249 
250       desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
251       if (desktop_autostart_id)
252         previous_client_id = desktop_autostart_id;
253     }
254   g_unsetenv ("DESKTOP_AUTOSTART_ID");
255 
256   meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'",
257               previous_save_file ? previous_save_file : "(none)");
258 
259   if (previous_save_file)
260     {
261       saved_client_id = load_state (previous_save_file);
262       previous_client_id = saved_client_id;
263     }
264   else if (previous_client_id)
265     {
266       char *save_file = g_strconcat (previous_client_id, ".ms", NULL);
267       saved_client_id = load_state (save_file);
268       g_free (save_file);
269     }
270   else
271     {
272       saved_client_id = NULL;
273     }
274 
275   ice_init ();
276 
277   mask = SmcSaveYourselfProcMask | SmcDieProcMask |
278     SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask;
279 
280   callbacks.save_yourself.callback = save_yourself_callback;
281   callbacks.save_yourself.client_data = context;
282 
283   callbacks.die.callback = die_callback;
284   callbacks.die.client_data = context;
285 
286   callbacks.save_complete.callback = save_complete_callback;
287   callbacks.save_complete.client_data = context;
288 
289   callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback;
290   callbacks.shutdown_cancelled.client_data = context;
291 
292   session_connection =
293     SmcOpenConnection (NULL, /* use SESSION_MANAGER env */
294                        NULL, /* means use existing ICE connection */
295                        SmProtoMajor,
296                        SmProtoMinor,
297                        mask,
298                        &callbacks,
299                        (char*) previous_client_id,
300                        &client_id,
301                        255, buf);
302 
303   if (session_connection == NULL)
304     {
305       meta_topic (META_DEBUG_SM,
306                   "Failed to a open connection to a session manager, so window positions will not be saved: %s",
307                   buf);
308 
309       goto out;
310     }
311   else
312     {
313       if (client_id == NULL)
314         meta_bug ("Session manager gave us a NULL client ID?");
315       meta_topic (META_DEBUG_SM, "Obtained session ID '%s'", client_id);
316     }
317 
318   if (previous_client_id && strcmp (previous_client_id, client_id) == 0)
319     current_state = STATE_IDLE;
320   else
321     current_state = STATE_REGISTERING;
322 
323   {
324     SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6];
325     SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val;
326     char pid[32];
327     /* Historically, this was SmRestartImmediately, which made sense
328      * for a stateless window manager, but we don't really control
329      * what embedders do, and it's all around better if gnome-session
330      * handles this.
331      */
332     char hint = SmRestartIfRunning;
333     char priority = 20; /* low to run before other apps */
334     const char *prgname;
335 
336     prgname = g_get_prgname ();
337 
338     prop1.name = (char *)SmProgram;
339     prop1.type = (char *)SmARRAY8;
340     prop1.num_vals = 1;
341     prop1.vals = &prop1val;
342     prop1val.value = (char *)prgname;
343     prop1val.length = strlen (prgname);
344 
345     /* twm sets getuid() for this, but the SM spec plainly
346      * says pw_name, twm is on crack
347      */
348     prop2.name = (char *)SmUserID;
349     prop2.type = (char *)SmARRAY8;
350     prop2.num_vals = 1;
351     prop2.vals = &prop2val;
352     prop2val.value = (char*) g_get_user_name ();
353     prop2val.length = strlen (prop2val.value);
354 
355     prop3.name = (char *)SmRestartStyleHint;
356     prop3.type = (char *)SmCARD8;
357     prop3.num_vals = 1;
358     prop3.vals = &prop3val;
359     prop3val.value = &hint;
360     prop3val.length = 1;
361 
362     sprintf (pid, "%d", getpid ());
363     prop4.name = (char *)SmProcessID;
364     prop4.type = (char *)SmARRAY8;
365     prop4.num_vals = 1;
366     prop4.vals = &prop4val;
367     prop4val.value = pid;
368     prop4val.length = strlen (prop4val.value);
369 
370     /* Always start in home directory */
371     prop5.name = (char *)SmCurrentDirectory;
372     prop5.type = (char *)SmARRAY8;
373     prop5.num_vals = 1;
374     prop5.vals = &prop5val;
375     prop5val.value = (char*) g_get_home_dir ();
376     prop5val.length = strlen (prop5val.value);
377 
378     prop6.name = (char *)"_GSM_Priority";
379     prop6.type = (char *)SmCARD8;
380     prop6.num_vals = 1;
381     prop6.vals = &prop6val;
382     prop6val.value = &priority;
383     prop6val.length = 1;
384 
385     props[0] = &prop1;
386     props[1] = &prop2;
387     props[2] = &prop3;
388     props[3] = &prop4;
389     props[4] = &prop5;
390     props[5] = &prop6;
391 
392     SmcSetProperties (session_connection, 6, props);
393   }
394 
395  out:
396   g_free (saved_client_id);
397 }
398 
399 static void
disconnect(void)400 disconnect (void)
401 {
402   SmcCloseConnection (session_connection, 0, NULL);
403   session_connection = NULL;
404   current_state = STATE_DISCONNECTED;
405 }
406 
407 static void
save_yourself_possibly_done(gboolean shutdown,gboolean successful)408 save_yourself_possibly_done (gboolean shutdown,
409                              gboolean successful)
410 {
411   meta_topic (META_DEBUG_SM,
412               "save possibly done shutdown = %d success = %d",
413               shutdown, successful);
414 
415   if (current_state == STATE_SAVING_PHASE_1)
416     {
417       Status status;
418 
419       status = SmcRequestSaveYourselfPhase2 (session_connection,
420                                              save_phase_2_callback,
421                                              GINT_TO_POINTER (shutdown));
422 
423       if (status)
424         current_state = STATE_WAITING_FOR_PHASE_2;
425 
426       meta_topic (META_DEBUG_SM,
427                   "Requested phase 2, status = %d", status);
428     }
429 
430   if (current_state == STATE_SAVING_PHASE_2 &&
431       interaction_allowed)
432     {
433       Status status;
434 
435       status = SmcInteractRequest (session_connection,
436                                    /* ignore this feature of the protocol by always
437                                     * claiming normal
438                                     */
439                                    SmDialogNormal,
440                                    interact_callback,
441                                    GINT_TO_POINTER (shutdown));
442 
443       if (status)
444         current_state = STATE_WAITING_FOR_INTERACT;
445 
446       meta_topic (META_DEBUG_SM,
447                   "Requested interact, status = %d", status);
448     }
449 
450   if (current_state == STATE_SAVING_PHASE_1 ||
451       current_state == STATE_SAVING_PHASE_2 ||
452       current_state == STATE_DONE_WITH_INTERACT ||
453       current_state == STATE_SKIPPING_GLOBAL_SAVE)
454     {
455       meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone");
456 
457       SmcSaveYourselfDone (session_connection,
458                            successful);
459 
460       if (shutdown)
461         current_state = STATE_FROZEN;
462       else
463         current_state = STATE_IDLE;
464     }
465 }
466 
467 static void
save_phase_2_callback(SmcConn smc_conn,SmPointer client_data)468 save_phase_2_callback (SmcConn smc_conn, SmPointer client_data)
469 {
470   gboolean shutdown;
471 
472   meta_topic (META_DEBUG_SM, "Phase 2 save");
473 
474   shutdown = GPOINTER_TO_INT (client_data);
475 
476   current_state = STATE_SAVING_PHASE_2;
477 
478   save_state ();
479 
480   save_yourself_possibly_done (shutdown, TRUE);
481 }
482 
483 static void
save_yourself_callback(SmcConn smc_conn,SmPointer client_data,int save_style,Bool shutdown,int interact_style,Bool fast)484 save_yourself_callback (SmcConn   smc_conn,
485                         SmPointer client_data,
486                         int       save_style,
487                         Bool      shutdown,
488                         int       interact_style,
489                         Bool      fast)
490 {
491   gboolean successful;
492 
493   meta_topic (META_DEBUG_SM, "SaveYourself received");
494 
495   successful = TRUE;
496 
497   /* The first SaveYourself after registering for the first time
498    * is a special case (SM specs 7.2).
499    */
500 
501 #if 0 /* I think the GnomeClient rationale for this doesn't apply */
502   if (current_state == STATE_REGISTERING)
503     {
504       current_state = STATE_IDLE;
505       /* Double check that this is a section 7.2 SaveYourself: */
506 
507       if (save_style == SmSaveLocal &&
508 	  interact_style == SmInteractStyleNone &&
509 	  !shutdown && !fast)
510 	{
511 	  /* The protocol requires this even if xsm ignores it. */
512 	  SmcSaveYourselfDone (session_connection, successful);
513 	  return;
514 	}
515     }
516 #endif
517 
518   /* ignore Global style saves
519    *
520    * This interpretaion of the Local/Global/Both styles
521    * was discussed extensively on the xdg-list. See:
522    *
523    * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html
524    */
525   if (save_style == SmSaveGlobal)
526     {
527       current_state = STATE_SKIPPING_GLOBAL_SAVE;
528       save_yourself_possibly_done (shutdown, successful);
529       return;
530     }
531 
532   interaction_allowed = interact_style != SmInteractStyleNone;
533 
534   current_state = STATE_SAVING_PHASE_1;
535 
536   regenerate_save_file ();
537 
538   set_clone_restart_commands ();
539 
540   save_yourself_possibly_done (shutdown, successful);
541 }
542 
543 
544 static void
die_callback(SmcConn smc_conn,SmPointer client_data)545 die_callback (SmcConn smc_conn, SmPointer client_data)
546 {
547   MetaContext *context = client_data;
548 
549   meta_topic (META_DEBUG_SM, "Disconnecting from session manager");
550 
551   disconnect ();
552   /* We don't actually exit here - we will simply go away with the X
553    * server on logout, when we lose the X connection and libx11 kills
554    * us.  It looks like *crap* on logout if the user sees their
555    * windows lose the decorations, etc.
556    *
557    * Anything that wants us to go away outside of session management
558    * can use kill().
559    */
560 
561   /* All of that is true - unless we're a wayland compositor. In which
562    * case the X server won't go down until we do, so we must die first.
563    */
564   if (meta_is_wayland_compositor ())
565     meta_context_terminate (context);
566 }
567 
568 static void
save_complete_callback(SmcConn smc_conn,SmPointer client_data)569 save_complete_callback (SmcConn smc_conn, SmPointer client_data)
570 {
571   /* nothing */
572   meta_topic (META_DEBUG_SM, "SaveComplete received");
573 }
574 
575 static void
shutdown_cancelled_callback(SmcConn smc_conn,SmPointer client_data)576 shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data)
577 {
578   meta_topic (META_DEBUG_SM, "Shutdown cancelled received");
579 
580   if (session_connection != NULL &&
581       (current_state != STATE_IDLE && current_state != STATE_FROZEN))
582     {
583       SmcSaveYourselfDone (session_connection, True);
584       current_state = STATE_IDLE;
585     }
586 }
587 
588 static void
interact_callback(SmcConn smc_conn,SmPointer client_data)589 interact_callback (SmcConn smc_conn, SmPointer client_data)
590 {
591   /* nothing */
592   gboolean shutdown;
593 
594   meta_topic (META_DEBUG_SM, "Interaction permission received");
595 
596   shutdown = GPOINTER_TO_INT (client_data);
597 
598   current_state = STATE_DONE_WITH_INTERACT;
599 
600   warn_about_lame_clients_and_finish_interact (shutdown);
601 }
602 
603 static void
set_clone_restart_commands(void)604 set_clone_restart_commands (void)
605 {
606   char *restartv[10];
607   char *clonev[10];
608   char *discardv[10];
609   int i;
610   SmProp prop1, prop2, prop3, *props[3];
611   const char *prgname;
612 
613   prgname = g_get_prgname ();
614 
615   /* Restart (use same client ID) */
616 
617   prop1.name = (char *)SmRestartCommand;
618   prop1.type = (char *)SmLISTofARRAY8;
619 
620   g_return_if_fail (client_id);
621 
622   i = 0;
623   restartv[i] = (char *)prgname;
624   ++i;
625   restartv[i] = (char *)"--sm-client-id";
626   ++i;
627   restartv[i] = client_id;
628   ++i;
629   restartv[i] = NULL;
630 
631   prop1.vals = g_new (SmPropValue, i);
632   i = 0;
633   while (restartv[i])
634     {
635       prop1.vals[i].value = restartv[i];
636       prop1.vals[i].length = strlen (restartv[i]);
637       ++i;
638     }
639   prop1.num_vals = i;
640 
641   /* Clone (no client ID) */
642 
643   i = 0;
644   clonev[i] = (char *)prgname;
645   ++i;
646   clonev[i] = NULL;
647 
648   prop2.name = (char *)SmCloneCommand;
649   prop2.type = (char *)SmLISTofARRAY8;
650 
651   prop2.vals = g_new (SmPropValue, i);
652   i = 0;
653   while (clonev[i])
654     {
655       prop2.vals[i].value = clonev[i];
656       prop2.vals[i].length = strlen (clonev[i]);
657       ++i;
658     }
659   prop2.num_vals = i;
660 
661   /* Discard */
662 
663   i = 0;
664   discardv[i] = (char *)"rm";
665   ++i;
666   discardv[i] = (char *)"-f";
667   ++i;
668   discardv[i] = (char*) full_save_file ();
669   ++i;
670   discardv[i] = NULL;
671 
672   prop3.name = (char *)SmDiscardCommand;
673   prop3.type = (char *)SmLISTofARRAY8;
674 
675   prop3.vals = g_new (SmPropValue, i);
676   i = 0;
677   while (discardv[i])
678     {
679       prop3.vals[i].value = discardv[i];
680       prop3.vals[i].length = strlen (discardv[i]);
681       ++i;
682     }
683   prop3.num_vals = i;
684 
685 
686   props[0] = &prop1;
687   props[1] = &prop2;
688   props[2] = &prop3;
689 
690   SmcSetProperties (session_connection, 3, props);
691 
692   g_free (prop1.vals);
693   g_free (prop2.vals);
694   g_free (prop3.vals);
695 }
696 
697 /* The remaining code in this file actually loads/saves the session,
698  * while the code above this comment handles chatting with the
699  * session manager.
700  */
701 
702 static const char*
window_type_to_string(MetaWindowType type)703 window_type_to_string (MetaWindowType type)
704 {
705   switch (type)
706     {
707     case META_WINDOW_NORMAL:
708       return "normal";
709     case META_WINDOW_DESKTOP:
710       return "desktop";
711     case META_WINDOW_DOCK:
712       return "dock";
713     case META_WINDOW_DIALOG:
714       return "dialog";
715     case META_WINDOW_MODAL_DIALOG:
716       return "modal_dialog";
717     case META_WINDOW_TOOLBAR:
718       return "toolbar";
719     case META_WINDOW_MENU:
720       return "menu";
721     case META_WINDOW_SPLASHSCREEN:
722       return "splashscreen";
723     case META_WINDOW_UTILITY:
724       return "utility";
725     case META_WINDOW_DROPDOWN_MENU:
726       return "dropdown_menu";
727     case META_WINDOW_POPUP_MENU:
728       return "popup_menu";
729     case META_WINDOW_TOOLTIP:
730       return "tooltip";
731     case META_WINDOW_NOTIFICATION:
732       return "notification";
733     case META_WINDOW_COMBO:
734       return "combo";
735     case META_WINDOW_DND:
736       return "dnd";
737     case META_WINDOW_OVERRIDE_OTHER:
738       return "override_redirect";
739     }
740 
741   return "";
742 }
743 
744 static MetaWindowType
window_type_from_string(const char * str)745 window_type_from_string (const char *str)
746 {
747   if (strcmp (str, "normal") == 0)
748     return META_WINDOW_NORMAL;
749   else if (strcmp (str, "desktop") == 0)
750     return META_WINDOW_DESKTOP;
751   else if (strcmp (str, "dock") == 0)
752     return META_WINDOW_DOCK;
753   else if (strcmp (str, "dialog") == 0)
754     return META_WINDOW_DIALOG;
755   else if (strcmp (str, "modal_dialog") == 0)
756     return META_WINDOW_MODAL_DIALOG;
757   else if (strcmp (str, "toolbar") == 0)
758     return META_WINDOW_TOOLBAR;
759   else if (strcmp (str, "menu") == 0)
760     return META_WINDOW_MENU;
761   else if (strcmp (str, "utility") == 0)
762     return META_WINDOW_UTILITY;
763   else if (strcmp (str, "splashscreen") == 0)
764     return META_WINDOW_SPLASHSCREEN;
765   else
766     return META_WINDOW_NORMAL;
767 }
768 
769 static int
window_gravity_from_string(const char * str)770 window_gravity_from_string (const char *str)
771 {
772   if (strcmp (str, "META_GRAVITY_NORTH_WEST") == 0)
773     return META_GRAVITY_NORTH_WEST;
774   else if (strcmp (str, "META_GRAVITY_NORTH") == 0)
775     return META_GRAVITY_NORTH;
776   else if (strcmp (str, "META_GRAVITY_NORTH_EAST") == 0)
777     return META_GRAVITY_NORTH_EAST;
778   else if (strcmp (str, "META_GRAVITY_WEST") == 0)
779     return META_GRAVITY_WEST;
780   else if (strcmp (str, "META_GRAVITY_CENTER") == 0)
781     return META_GRAVITY_CENTER;
782   else if (strcmp (str, "META_GRAVITY_EAST") == 0)
783     return META_GRAVITY_EAST;
784   else if (strcmp (str, "META_GRAVITY_SOUTH_WEST") == 0)
785     return META_GRAVITY_SOUTH_WEST;
786   else if (strcmp (str, "META_GRAVITY_SOUTH") == 0)
787     return META_GRAVITY_SOUTH;
788   else if (strcmp (str, "META_GRAVITY_SOUTH_EAST") == 0)
789     return META_GRAVITY_SOUTH_EAST;
790   else if (strcmp (str, "META_GRAVITY_STATIC") == 0)
791     return META_GRAVITY_STATIC;
792   else
793     return META_GRAVITY_NORTH_WEST;
794 }
795 
796 static char*
encode_text_as_utf8_markup(const char * text)797 encode_text_as_utf8_markup (const char *text)
798 {
799   /* text can be any encoding, and is nul-terminated.
800    * we pretend it's Latin-1 and encode as UTF-8
801    */
802   GString *str;
803   const char *p;
804   char *escaped;
805 
806   str = g_string_new ("");
807 
808   p = text;
809   while (*p)
810     {
811       g_string_append_unichar (str, *p);
812       ++p;
813     }
814 
815   escaped = g_markup_escape_text (str->str, str->len);
816   g_string_free (str, TRUE);
817 
818   return escaped;
819 }
820 
821 static char*
decode_text_from_utf8(const char * text)822 decode_text_from_utf8 (const char *text)
823 {
824   /* Convert back from the encoded (but not escaped) UTF-8 */
825   GString *str;
826   const char *p;
827 
828   str = g_string_new ("");
829 
830   p = text;
831   while (*p)
832     {
833       /* obviously this barfs if the UTF-8 contains chars > 255 */
834       g_string_append_c (str, g_utf8_get_char (p));
835 
836       p = g_utf8_next_char (p);
837     }
838 
839   return g_string_free (str, FALSE);
840 }
841 
842 static void
save_state(void)843 save_state (void)
844 {
845   char *mutter_dir;
846   char *session_dir;
847   FILE *outfile;
848   GSList *windows;
849   GSList *tmp;
850   int stack_position;
851 
852   g_assert (client_id);
853 
854   outfile = NULL;
855 
856   /*
857    * g_get_user_config_dir() is guaranteed to return an existing directory.
858    * Eventually, if SM stays with the WM, I'd like to make this
859    * something like <config>/window_placement in a standard format.
860    * Future optimisers should note also that by the time we get here
861    * we probably already have full_save_path figured out and therefore
862    * can just use the directory name from that.
863    */
864   mutter_dir = g_strconcat (g_get_user_config_dir (),
865                               G_DIR_SEPARATOR_S "mutter",
866                               NULL);
867 
868   session_dir = g_strconcat (mutter_dir,
869                              G_DIR_SEPARATOR_S "sessions",
870                              NULL);
871 
872   if (mkdir (mutter_dir, 0700) < 0 &&
873       errno != EEXIST)
874     {
875       meta_warning ("Could not create directory '%s': %s",
876                     mutter_dir, g_strerror (errno));
877     }
878 
879   if (mkdir (session_dir, 0700) < 0 &&
880       errno != EEXIST)
881     {
882       meta_warning ("Could not create directory '%s': %s",
883                     session_dir, g_strerror (errno));
884     }
885 
886   meta_topic (META_DEBUG_SM, "Saving session to '%s'", full_save_file ());
887 
888   outfile = fopen (full_save_file (), "w");
889 
890   if (outfile == NULL)
891     {
892       meta_warning ("Could not open session file '%s' for writing: %s",
893                     full_save_file (), g_strerror (errno));
894       goto out;
895     }
896 
897   /* The file format is:
898    * <mutter_session id="foo">
899    *   <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5">
900    *     <workspace index="2"/>
901    *     <workspace index="4"/>
902    *     <sticky/> <minimized/> <maximized/>
903    *     <geometry x="100" y="100" width="200" height="200" gravity="northwest"/>
904    *   </window>
905    * </mutter_session>
906    *
907    * Note that attributes on <window> are the match info we use to
908    * see if the saved state applies to a restored window, and
909    * child elements are the saved state to be applied.
910    *
911    */
912 
913   fprintf (outfile, "<mutter_session id=\"%s\">\n",
914            client_id);
915 
916   windows = meta_display_list_windows (meta_get_display (), META_LIST_DEFAULT);
917   stack_position = 0;
918 
919   windows = g_slist_sort (windows, meta_display_stack_cmp);
920   tmp = windows;
921   stack_position = 0;
922 
923   while (tmp != NULL)
924     {
925       MetaWindow *window;
926 
927       window = tmp->data;
928 
929       if (window->sm_client_id)
930         {
931           char *sm_client_id;
932           char *res_class;
933           char *res_name;
934           char *role;
935           char *title;
936 
937           /* client id, class, name, role are not expected to be
938            * in UTF-8 (I think they are in XPCS which is Latin-1?
939            * in practice they are always ascii though.)
940            */
941 
942           sm_client_id = encode_text_as_utf8_markup (window->sm_client_id);
943           res_class = window->res_class ?
944             encode_text_as_utf8_markup (window->res_class) : NULL;
945           res_name = window->res_name ?
946             encode_text_as_utf8_markup (window->res_name) : NULL;
947           role = window->role ?
948             encode_text_as_utf8_markup (window->role) : NULL;
949           if (window->title)
950             title = g_markup_escape_text (window->title, -1);
951           else
952             title = NULL;
953 
954           meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'",
955                       window->desc, window->sm_client_id);
956 
957           fprintf (outfile,
958                    "  <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n",
959                    sm_client_id,
960                    res_class ? res_class : "",
961                    res_name ? res_name : "",
962                    title ? title : "",
963                    role ? role : "",
964                    window_type_to_string (window->type),
965                    stack_position);
966 
967           g_free (sm_client_id);
968           g_free (res_class);
969           g_free (res_name);
970           g_free (role);
971           g_free (title);
972 
973           /* Sticky */
974           if (window->on_all_workspaces_requested)
975             {
976               fputs ("    <sticky/>\n", outfile);
977             } else {
978               int n;
979               if (window->workspace)
980                 n = meta_workspace_index (window->workspace);
981               else
982                 n = window->initial_workspace;
983               fprintf (outfile,
984                        "    <workspace index=\"%d\"/>\n", n);
985             }
986 
987 
988           /* Minimized */
989           if (window->minimized)
990             fputs ("    <minimized/>\n", outfile);
991 
992           /* Maximized */
993           if (META_WINDOW_MAXIMIZED (window))
994             {
995               fprintf (outfile,
996                        "    <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n",
997                        window->saved_rect.x,
998                        window->saved_rect.y,
999                        window->saved_rect.width,
1000                        window->saved_rect.height);
1001             }
1002 
1003           /* Gravity */
1004           {
1005             int x, y, w, h;
1006             meta_window_get_session_geometry (window, &x, &y, &w, &h);
1007 
1008             fprintf (outfile,
1009                      "    <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n",
1010                      x, y, w, h,
1011                      meta_gravity_to_string (window->size_hints.win_gravity));
1012           }
1013 
1014           fputs ("  </window>\n", outfile);
1015         }
1016       else
1017         {
1018           meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed",
1019                       window->desc);
1020         }
1021 
1022       tmp = tmp->next;
1023       ++stack_position;
1024     }
1025 
1026   g_slist_free (windows);
1027 
1028   fputs ("</mutter_session>\n", outfile);
1029 
1030  out:
1031   if (outfile)
1032     {
1033       /* FIXME need a dialog for this */
1034       if (ferror (outfile))
1035         {
1036           meta_warning ("Error writing session file '%s': %s",
1037                         full_save_file (), g_strerror (errno));
1038         }
1039       if (fclose (outfile))
1040         {
1041           meta_warning ("Error closing session file '%s': %s",
1042                         full_save_file (), g_strerror (errno));
1043         }
1044     }
1045 
1046   g_free (mutter_dir);
1047   g_free (session_dir);
1048 }
1049 
1050 typedef enum
1051 {
1052   WINDOW_TAG_NONE,
1053   WINDOW_TAG_DESKTOP,
1054   WINDOW_TAG_STICKY,
1055   WINDOW_TAG_MINIMIZED,
1056   WINDOW_TAG_MAXIMIZED,
1057   WINDOW_TAG_GEOMETRY
1058 } WindowTag;
1059 
1060 typedef struct
1061 {
1062   MetaWindowSessionInfo *info;
1063   char *previous_id;
1064 } ParseData;
1065 
1066 static void                   session_info_free (MetaWindowSessionInfo *info);
1067 static MetaWindowSessionInfo* session_info_new  (void);
1068 
1069 static void start_element_handler (GMarkupParseContext  *context,
1070                                    const gchar          *element_name,
1071                                    const gchar         **attribute_names,
1072                                    const gchar         **attribute_values,
1073                                    gpointer              user_data,
1074                                    GError              **error);
1075 static void end_element_handler   (GMarkupParseContext  *context,
1076                                    const gchar          *element_name,
1077                                    gpointer              user_data,
1078                                    GError              **error);
1079 static void text_handler          (GMarkupParseContext  *context,
1080                                    const gchar          *text,
1081                                    gsize                 text_len,
1082                                    gpointer              user_data,
1083                                    GError              **error);
1084 
1085 static GMarkupParser mutter_session_parser = {
1086   start_element_handler,
1087   end_element_handler,
1088   text_handler,
1089   NULL,
1090   NULL
1091 };
1092 
1093 static GSList *window_info_list = NULL;
1094 
1095 static char*
load_state(const char * previous_save_file)1096 load_state (const char *previous_save_file)
1097 {
1098   GMarkupParseContext *context;
1099   GError *error;
1100   ParseData parse_data;
1101   char *text;
1102   gsize length;
1103   char *session_file;
1104 
1105   session_file = g_strconcat (g_get_user_config_dir (),
1106                               G_DIR_SEPARATOR_S "mutter"
1107                               G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
1108                               previous_save_file,
1109                               NULL);
1110 
1111   error = NULL;
1112   if (!g_file_get_contents (session_file,
1113                             &text,
1114                             &length,
1115                             &error))
1116     {
1117       char *canonical_session_file = session_file;
1118 
1119       /* Maybe they were doing it the old way, with ~/.mutter */
1120       session_file = g_strconcat (g_get_home_dir (),
1121                                   G_DIR_SEPARATOR_S ".mutter"
1122                                   G_DIR_SEPARATOR_S "sessions"
1123                                   G_DIR_SEPARATOR_S,
1124                                   previous_save_file,
1125                                   NULL);
1126 
1127       if (!g_file_get_contents (session_file,
1128                                 &text,
1129                                 &length,
1130                                 NULL))
1131         {
1132           /* oh, just give up */
1133 
1134           g_error_free (error);
1135           g_free (session_file);
1136           g_free (canonical_session_file);
1137           return NULL;
1138         }
1139 
1140       g_free (canonical_session_file);
1141     }
1142 
1143   meta_topic (META_DEBUG_SM, "Parsing saved session file %s", session_file);
1144   g_free (session_file);
1145   session_file = NULL;
1146 
1147   parse_data.info = NULL;
1148   parse_data.previous_id = NULL;
1149 
1150   context = g_markup_parse_context_new (&mutter_session_parser,
1151                                         0, &parse_data, NULL);
1152 
1153   error = NULL;
1154   if (!g_markup_parse_context_parse (context,
1155                                      text,
1156                                      length,
1157                                      &error))
1158     goto error;
1159 
1160 
1161   error = NULL;
1162   if (!g_markup_parse_context_end_parse (context, &error))
1163     goto error;
1164 
1165   g_markup_parse_context_free (context);
1166 
1167   goto out;
1168 
1169  error:
1170 
1171   meta_warning ("Failed to parse saved session file: %s",
1172                 error->message);
1173   g_error_free (error);
1174 
1175   if (parse_data.info)
1176     session_info_free (parse_data.info);
1177 
1178   g_free (parse_data.previous_id);
1179   parse_data.previous_id = NULL;
1180 
1181  out:
1182 
1183   g_free (text);
1184 
1185   return parse_data.previous_id;
1186 }
1187 
1188 /* FIXME this isn't very robust against bogus session files */
1189 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)1190 start_element_handler  (GMarkupParseContext *context,
1191                         const gchar         *element_name,
1192                         const gchar        **attribute_names,
1193                         const gchar        **attribute_values,
1194                         gpointer             user_data,
1195                         GError             **error)
1196 {
1197   ParseData *pd;
1198 
1199   pd = user_data;
1200 
1201   if (strcmp (element_name, "mutter_session") == 0)
1202     {
1203       /* Get previous ID */
1204       int i;
1205 
1206       i = 0;
1207       while (attribute_names[i])
1208         {
1209           const char *name;
1210           const char *val;
1211 
1212           name = attribute_names[i];
1213           val = attribute_values[i];
1214 
1215           if (pd->previous_id)
1216             {
1217               g_set_error (error,
1218                            G_MARKUP_ERROR,
1219                        G_MARKUP_ERROR_PARSE,
1220                            "<mutter_session> attribute seen but we already have the session ID");
1221               return;
1222             }
1223 
1224           if (strcmp (name, "id") == 0)
1225             {
1226               pd->previous_id = decode_text_from_utf8 (val);
1227             }
1228           else
1229             {
1230               g_set_error (error,
1231                            G_MARKUP_ERROR,
1232                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1233                            "Unknown attribute %s on <%s> element",
1234                            name, "mutter_session");
1235               return;
1236             }
1237 
1238           ++i;
1239         }
1240     }
1241   else if (strcmp (element_name, "window") == 0)
1242     {
1243       int i;
1244 
1245       if (pd->info)
1246         {
1247           g_set_error (error,
1248                        G_MARKUP_ERROR,
1249                        G_MARKUP_ERROR_PARSE,
1250                        "nested <window> tag");
1251           return;
1252         }
1253 
1254       pd->info = session_info_new ();
1255 
1256       i = 0;
1257       while (attribute_names[i])
1258         {
1259           const char *name;
1260           const char *val;
1261 
1262           name = attribute_names[i];
1263           val = attribute_values[i];
1264 
1265           if (strcmp (name, "id") == 0)
1266             {
1267               if (*val)
1268                 pd->info->id = decode_text_from_utf8 (val);
1269             }
1270           else if (strcmp (name, "class") == 0)
1271             {
1272               if (*val)
1273                 pd->info->res_class = decode_text_from_utf8 (val);
1274             }
1275           else if (strcmp (name, "name") == 0)
1276             {
1277               if (*val)
1278                 pd->info->res_name = decode_text_from_utf8 (val);
1279             }
1280           else if (strcmp (name, "title") == 0)
1281             {
1282               if (*val)
1283                 pd->info->title = g_strdup (val);
1284             }
1285           else if (strcmp (name, "role") == 0)
1286             {
1287               if (*val)
1288                 pd->info->role = decode_text_from_utf8 (val);
1289             }
1290           else if (strcmp (name, "type") == 0)
1291             {
1292               if (*val)
1293                 pd->info->type = window_type_from_string (val);
1294             }
1295           else if (strcmp (name, "stacking") == 0)
1296             {
1297               if (*val)
1298                 {
1299                   pd->info->stack_position = atoi (val);
1300                   pd->info->stack_position_set = TRUE;
1301                 }
1302             }
1303           else
1304             {
1305               g_set_error (error,
1306                            G_MARKUP_ERROR,
1307                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1308                            "Unknown attribute %s on <%s> element",
1309                            name, "window");
1310               session_info_free (pd->info);
1311               pd->info = NULL;
1312               return;
1313             }
1314 
1315           ++i;
1316         }
1317     }
1318   else if (strcmp (element_name, "workspace") == 0)
1319     {
1320       int i;
1321 
1322       i = 0;
1323       while (attribute_names[i])
1324         {
1325           const char *name;
1326 
1327           name = attribute_names[i];
1328 
1329           if (strcmp (name, "index") == 0)
1330             {
1331               pd->info->workspace_indices =
1332                 g_slist_prepend (pd->info->workspace_indices,
1333                                  GINT_TO_POINTER (atoi (attribute_values[i])));
1334             }
1335           else
1336             {
1337               g_set_error (error,
1338                            G_MARKUP_ERROR,
1339                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1340                            "Unknown attribute %s on <%s> element",
1341                            name, "window");
1342               session_info_free (pd->info);
1343               pd->info = NULL;
1344               return;
1345             }
1346 
1347           ++i;
1348         }
1349     }
1350   else if (strcmp (element_name, "sticky") == 0)
1351     {
1352       pd->info->on_all_workspaces = TRUE;
1353       pd->info->on_all_workspaces_set = TRUE;
1354     }
1355   else if (strcmp (element_name, "minimized") == 0)
1356     {
1357       pd->info->minimized = TRUE;
1358       pd->info->minimized_set = TRUE;
1359     }
1360   else if (strcmp (element_name, "maximized") == 0)
1361     {
1362       int i;
1363 
1364       i = 0;
1365       pd->info->maximized = TRUE;
1366       pd->info->maximized_set = TRUE;
1367       while (attribute_names[i])
1368         {
1369           const char *name;
1370           const char *val;
1371 
1372           name = attribute_names[i];
1373           val = attribute_values[i];
1374 
1375           if (strcmp (name, "saved_x") == 0)
1376             {
1377               if (*val)
1378                 {
1379                   pd->info->saved_rect.x = atoi (val);
1380                   pd->info->saved_rect_set = TRUE;
1381                 }
1382             }
1383           else if (strcmp (name, "saved_y") == 0)
1384             {
1385               if (*val)
1386                 {
1387                   pd->info->saved_rect.y = atoi (val);
1388                   pd->info->saved_rect_set = TRUE;
1389                 }
1390             }
1391           else if (strcmp (name, "saved_width") == 0)
1392             {
1393               if (*val)
1394                 {
1395                   pd->info->saved_rect.width = atoi (val);
1396                   pd->info->saved_rect_set = TRUE;
1397                 }
1398             }
1399           else if (strcmp (name, "saved_height") == 0)
1400             {
1401               if (*val)
1402                 {
1403                   pd->info->saved_rect.height = atoi (val);
1404                   pd->info->saved_rect_set = TRUE;
1405                 }
1406             }
1407           else
1408             {
1409               g_set_error (error,
1410                            G_MARKUP_ERROR,
1411                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1412                            "Unknown attribute %s on <%s> element",
1413                            name, "maximized");
1414               return;
1415             }
1416 
1417           ++i;
1418         }
1419 
1420       if (pd->info->saved_rect_set)
1421         meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d ",
1422                     pd->info->saved_rect.x,
1423                     pd->info->saved_rect.y,
1424                     pd->info->saved_rect.width,
1425                     pd->info->saved_rect.height);
1426     }
1427   else if (strcmp (element_name, "geometry") == 0)
1428     {
1429       int i;
1430 
1431       pd->info->geometry_set = TRUE;
1432 
1433       i = 0;
1434       while (attribute_names[i])
1435         {
1436           const char *name;
1437           const char *val;
1438 
1439           name = attribute_names[i];
1440           val = attribute_values[i];
1441 
1442           if (strcmp (name, "x") == 0)
1443             {
1444               if (*val)
1445                 pd->info->rect.x = atoi (val);
1446             }
1447           else if (strcmp (name, "y") == 0)
1448             {
1449               if (*val)
1450                 pd->info->rect.y = atoi (val);
1451             }
1452           else if (strcmp (name, "width") == 0)
1453             {
1454               if (*val)
1455                 pd->info->rect.width = atoi (val);
1456             }
1457           else if (strcmp (name, "height") == 0)
1458             {
1459               if (*val)
1460                 pd->info->rect.height = atoi (val);
1461             }
1462           else if (strcmp (name, "gravity") == 0)
1463             {
1464               if (*val)
1465                 pd->info->gravity = window_gravity_from_string (val);
1466             }
1467           else
1468             {
1469               g_set_error (error,
1470                            G_MARKUP_ERROR,
1471                            G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1472                            "Unknown attribute %s on <%s> element",
1473                            name, "geometry");
1474               return;
1475             }
1476 
1477           ++i;
1478         }
1479 
1480       meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s",
1481                   pd->info->rect.x,
1482                   pd->info->rect.y,
1483                   pd->info->rect.width,
1484                   pd->info->rect.height,
1485                   meta_gravity_to_string (pd->info->gravity));
1486     }
1487   else
1488     {
1489       g_set_error (error,
1490                    G_MARKUP_ERROR,
1491                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1492                    "Unknown element %s",
1493                    element_name);
1494       return;
1495     }
1496 }
1497 
1498 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)1499 end_element_handler    (GMarkupParseContext *context,
1500                         const gchar         *element_name,
1501                         gpointer             user_data,
1502                         GError             **error)
1503 {
1504   ParseData *pd;
1505 
1506   pd = user_data;
1507 
1508   if (strcmp (element_name, "window") == 0)
1509     {
1510       g_assert (pd->info);
1511 
1512       window_info_list = g_slist_prepend (window_info_list,
1513                                           pd->info);
1514 
1515       meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s",
1516                   pd->info->res_class ? pd->info->res_class : "(none)",
1517                   pd->info->res_name ? pd->info->res_name : "(none)",
1518                   pd->info->role ? pd->info->role : "(none)");
1519 
1520       pd->info = NULL;
1521     }
1522 }
1523 
1524 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)1525 text_handler           (GMarkupParseContext *context,
1526                         const gchar         *text,
1527                         gsize                text_len,
1528                         gpointer             user_data,
1529                         GError             **error)
1530 {
1531   /* Right now we don't have any elements where we care about their
1532    * content
1533    */
1534 }
1535 
1536 static gboolean
both_null_or_matching(const char * a,const char * b)1537 both_null_or_matching (const char *a,
1538                        const char *b)
1539 {
1540   if (a == NULL && b == NULL)
1541     return TRUE;
1542   else if (a && b && strcmp (a, b) == 0)
1543     return TRUE;
1544   else
1545     return FALSE;
1546 }
1547 
1548 static GSList*
get_possible_matches(MetaWindow * window)1549 get_possible_matches (MetaWindow *window)
1550 {
1551   /* Get all windows with this client ID */
1552   GSList *retval;
1553   GSList *tmp;
1554   gboolean ignore_client_id;
1555 
1556   retval = NULL;
1557 
1558   ignore_client_id = g_getenv ("MUTTER_DEBUG_SM") != NULL;
1559 
1560   tmp = window_info_list;
1561   while (tmp != NULL)
1562     {
1563       MetaWindowSessionInfo *info;
1564 
1565       info = tmp->data;
1566 
1567       if ((ignore_client_id ||
1568            both_null_or_matching (info->id, window->sm_client_id)) &&
1569           both_null_or_matching (info->res_class, window->res_class) &&
1570           both_null_or_matching (info->res_name, window->res_name) &&
1571           both_null_or_matching (info->role, window->role))
1572         {
1573           meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s",
1574                       window->desc,
1575                       info->res_class ? info->res_class : "(none)",
1576                       info->res_name ? info->res_name : "(none)",
1577                       info->role ? info->role : "(none)");
1578 
1579           retval = g_slist_prepend (retval, info);
1580         }
1581       else
1582         {
1583           if (meta_is_verbose ())
1584             {
1585               if (!both_null_or_matching (info->id, window->sm_client_id))
1586                 meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match",
1587                             window->desc,
1588                             window->sm_client_id ? window->sm_client_id : "(none)",
1589                             info->id ? info->id : "(none)");
1590               else if (!both_null_or_matching (info->res_class, window->res_class))
1591                 meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match",
1592                             window->desc,
1593                             window->res_class ? window->res_class : "(none)",
1594                             info->res_class ? info->res_class : "(none)");
1595 
1596               else if (!both_null_or_matching (info->res_name, window->res_name))
1597                 meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match",
1598                             window->desc,
1599                             window->res_name ? window->res_name : "(none)",
1600                             info->res_name ? info->res_name : "(none)");
1601               else if (!both_null_or_matching (info->role, window->role))
1602                 meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match",
1603                             window->desc,
1604                             window->role ? window->role : "(none)",
1605                             info->role ? info->role : "(none)");
1606               else
1607                 meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason",
1608                             window->desc, info->id);
1609             }
1610         }
1611 
1612       tmp = tmp->next;
1613     }
1614 
1615   return retval;
1616 }
1617 
1618 static const MetaWindowSessionInfo*
find_best_match(GSList * infos,MetaWindow * window)1619 find_best_match (GSList     *infos,
1620                  MetaWindow *window)
1621 {
1622   GSList *tmp;
1623   const MetaWindowSessionInfo *matching_title;
1624   const MetaWindowSessionInfo *matching_type;
1625 
1626   matching_title = NULL;
1627   matching_type = NULL;
1628 
1629   tmp = infos;
1630   while (tmp != NULL)
1631     {
1632       MetaWindowSessionInfo *info;
1633 
1634       info = tmp->data;
1635 
1636       if (matching_title == NULL &&
1637           both_null_or_matching (info->title, window->title))
1638         matching_title = info;
1639 
1640       if (matching_type == NULL &&
1641           info->type == window->type)
1642         matching_type = info;
1643 
1644       tmp = tmp->next;
1645     }
1646 
1647   /* Prefer same title, then same type of window, then
1648    * just pick something. Eventually we could enhance this
1649    * to e.g. break ties by geometry hint similarity,
1650    * or other window features.
1651    */
1652 
1653   if (matching_title)
1654     return matching_title;
1655   else if (matching_type)
1656     return matching_type;
1657   else
1658     return infos->data;
1659 }
1660 
1661 const MetaWindowSessionInfo*
meta_window_lookup_saved_state(MetaWindow * window)1662 meta_window_lookup_saved_state (MetaWindow *window)
1663 {
1664   GSList *possibles;
1665   const MetaWindowSessionInfo *info;
1666 
1667   /* Window is not session managed.
1668    * I haven't yet figured out how to deal with these
1669    * in a way that doesn't cause broken side effects in
1670    * situations other than on session restore.
1671    */
1672   if (window->sm_client_id == NULL)
1673     {
1674       meta_topic (META_DEBUG_SM,
1675                   "Window %s is not session managed, not checking for saved state",
1676                   window->desc);
1677       return NULL;
1678     }
1679 
1680   possibles = get_possible_matches (window);
1681 
1682   if (possibles == NULL)
1683     {
1684       meta_topic (META_DEBUG_SM,
1685                   "Window %s has no possible matches in the list of saved window states",
1686                   window->desc);
1687       return NULL;
1688     }
1689 
1690   info = find_best_match (possibles, window);
1691 
1692   g_slist_free (possibles);
1693 
1694   return info;
1695 }
1696 
1697 void
meta_window_release_saved_state(const MetaWindowSessionInfo * info)1698 meta_window_release_saved_state (const MetaWindowSessionInfo *info)
1699 {
1700   /* We don't want to use the same saved state again for another
1701    * window.
1702    */
1703   window_info_list = g_slist_remove (window_info_list, info);
1704 
1705   session_info_free ((MetaWindowSessionInfo*) info);
1706 }
1707 
1708 static void
session_info_free(MetaWindowSessionInfo * info)1709 session_info_free (MetaWindowSessionInfo *info)
1710 {
1711   g_free (info->id);
1712   g_free (info->res_class);
1713   g_free (info->res_name);
1714   g_free (info->title);
1715   g_free (info->role);
1716 
1717   g_slist_free (info->workspace_indices);
1718 
1719   g_free (info);
1720 }
1721 
1722 static MetaWindowSessionInfo*
session_info_new(void)1723 session_info_new (void)
1724 {
1725   MetaWindowSessionInfo *info;
1726 
1727   info = g_new0 (MetaWindowSessionInfo, 1);
1728 
1729   info->type = META_WINDOW_NORMAL;
1730   info->gravity = META_GRAVITY_NORTH_WEST;
1731 
1732   return info;
1733 }
1734 
1735 static char* full_save_path = NULL;
1736 
1737 static void
regenerate_save_file(void)1738 regenerate_save_file (void)
1739 {
1740   g_free (full_save_path);
1741 
1742   if (client_id)
1743     full_save_path = g_strconcat (g_get_user_config_dir (),
1744                                   G_DIR_SEPARATOR_S "mutter"
1745                                   G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
1746                                   client_id,
1747                                   ".ms",
1748                                   NULL);
1749   else
1750     full_save_path = NULL;
1751 }
1752 
1753 static const char*
full_save_file(void)1754 full_save_file (void)
1755 {
1756   return full_save_path;
1757 }
1758 
1759 static int
windows_cmp_by_title(MetaWindow * a,MetaWindow * b)1760 windows_cmp_by_title (MetaWindow *a,
1761                       MetaWindow *b)
1762 {
1763   return g_utf8_collate (a->title, b->title);
1764 }
1765 
1766 static void
finish_interact(gboolean shutdown)1767 finish_interact (gboolean shutdown)
1768 {
1769   if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */
1770     {
1771       SmcInteractDone (session_connection, False /* don't cancel logout */);
1772 
1773       save_yourself_possibly_done (shutdown, TRUE);
1774     }
1775 }
1776 
1777 static void
dialog_closed(GPid pid,int status,gpointer user_data)1778 dialog_closed (GPid pid, int status, gpointer user_data)
1779 {
1780   gboolean shutdown = GPOINTER_TO_INT (user_data);
1781 
1782   if (WIFEXITED (status) && WEXITSTATUS (status) == 0) /* pressed "OK" */
1783     {
1784       finish_interact (shutdown);
1785     }
1786 }
1787 
1788 static void
warn_about_lame_clients_and_finish_interact(gboolean shutdown)1789 warn_about_lame_clients_and_finish_interact (gboolean shutdown)
1790 {
1791   GSList *lame = NULL;
1792   GSList *windows;
1793   GSList *lame_details = NULL;
1794   GSList *tmp;
1795   GSList *columns = NULL;
1796   GPid pid;
1797 
1798   windows = meta_display_list_windows (meta_get_display (), META_LIST_DEFAULT);
1799   tmp = windows;
1800   while (tmp != NULL)
1801     {
1802       MetaWindow *window;
1803 
1804       window = tmp->data;
1805 
1806       /* only complain about normal windows, the others
1807        * are kind of dumb to worry about
1808        */
1809       if (window->sm_client_id == NULL &&
1810           window->type == META_WINDOW_NORMAL)
1811         lame = g_slist_prepend (lame, window);
1812 
1813       tmp = tmp->next;
1814     }
1815 
1816   g_slist_free (windows);
1817 
1818   if (lame == NULL)
1819     {
1820       /* No lame apps. */
1821       finish_interact (shutdown);
1822       return;
1823     }
1824 
1825   columns = g_slist_prepend (columns, (gpointer)"Window");
1826   columns = g_slist_prepend (columns, (gpointer)"Class");
1827 
1828   lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title);
1829 
1830   tmp = lame;
1831   while (tmp != NULL)
1832     {
1833       MetaWindow *w = tmp->data;
1834 
1835       lame_details = g_slist_prepend (lame_details,
1836                                       w->res_class ? w->res_class : (gpointer)"");
1837       lame_details = g_slist_prepend (lame_details,
1838                                       w->title);
1839 
1840       tmp = tmp->next;
1841     }
1842   g_slist_free (lame);
1843 
1844   pid = meta_show_dialog("--list",
1845                          _("These windows do not support “save current setup” "
1846                            "and will have to be restarted manually next time "
1847                            "you log in."),
1848                          "240",
1849                          meta_get_display()->x11_display->screen_name,
1850                          NULL, NULL, NULL,
1851                          None,
1852                          columns,
1853                          lame_details);
1854 
1855   g_slist_free (lame_details);
1856 
1857   g_child_watch_add (pid, dialog_closed, GINT_TO_POINTER (shutdown));
1858 }
1859 
1860 #endif /* HAVE_SM */
1861