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