1 /* session.c - session state handling and R6 style session management
3 * Copyright (c) 1998 Dan Pascu
4 * Copyright (c) 1998 Alfredo Kojima
6 * Window Maker window manager
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
27 #include <X11/Xutil.h>
30 #include <X11/SM/SMlib.h>
40 #include "WindowMaker.h"
46 #include "workspace.h"
48 #include "properties.h"
49 #include "application.h"
61 /* requested for SaveYourselfPhase2 */
62 static Bool sWaitingPhase2
= False
;
64 static SmcConn sSMCConn
= NULL
;
66 static WMHandlerID sSMInputHandler
= NULL
;
68 /* our SM client ID */
69 static char *sClientID
= NULL
;
73 static proplist_t sApplications
= NULL
;
74 static proplist_t sCommand
;
75 static proplist_t sName
;
76 static proplist_t sHost
;
77 static proplist_t sWorkspace
;
78 static proplist_t sShaded
;
79 static proplist_t sMiniaturized
;
80 static proplist_t sHidden
;
81 static proplist_t sGeometry
;
83 static proplist_t sDock
;
85 static proplist_t sYes
, sNo
;
91 if (sApplications
!=NULL
)
94 sApplications
= PLMakeString("Applications");
95 sCommand
= PLMakeString("Command");
96 sName
= PLMakeString("Name");
97 sHost
= PLMakeString("Host");
98 sWorkspace
= PLMakeString("Workspace");
99 sShaded
= PLMakeString("Shaded");
100 sMiniaturized
= PLMakeString("Miniaturized");
101 sHidden
= PLMakeString("Hidden");
102 sGeometry
= PLMakeString("Geometry");
103 sDock
= PLMakeString("Dock");
105 sYes
= PLMakeString("Yes");
106 sNo
= PLMakeString("No");
112 getBool(proplist_t value
)
116 if (!PLIsString(value
)) {
119 if (!(val
= PLGetString(value
))) {
123 if ((val
[1]=='\0' && (val
[0]=='y' || val
[0]=='Y'))
124 || strcasecmp(val
, "YES")==0) {
127 } else if ((val
[1]=='\0' && (val
[0]=='n' || val
[0]=='N'))
128 || strcasecmp(val
, "NO")==0) {
132 if (sscanf(val
, "%i", &i
)==1) {
135 wwarning(_("can't convert \"%s\" to boolean"), val
);
144 makeWindowState(WWindow
*wwin
, WApplication
*wapp
)
146 WScreen
*scr
= wwin
->screen_ptr
;
150 char *class, *instance
, *command
=NULL
, buffer
[256];
151 proplist_t win_state
, cmd
, name
, workspace
;
152 proplist_t shaded
, miniaturized
, hidden
, geometry
;
155 if (wwin
->main_window
!=None
&& wwin
->main_window
!=wwin
->client_win
)
156 win
= wwin
->main_window
;
158 win
= wwin
->client_win
;
160 if (XGetCommand(dpy
, win
, &argv
, &argc
) && argc
>0) {
161 command
= FlattenStringList(argv
, argc
);
162 XFreeStringList(argv
);
167 if (PropGetWMClass(win
, &class, &instance
)) {
168 if (class && instance
)
169 sprintf(buffer
, "%s.%s", instance
, class);
171 sprintf(buffer
, "%s", instance
);
173 sprintf(buffer
, ".%s", class);
175 sprintf(buffer
, ".");
177 name
= PLMakeString(buffer
);
178 cmd
= PLMakeString(command
);
179 /*sprintf(buffer, "%d", wwin->frame->workspace+1);
180 workspace = PLMakeString(buffer);*/
181 workspace
= PLMakeString(scr
->workspaces
[wwin
->frame
->workspace
]->name
);
182 shaded
= wwin
->flags
.shaded
? sYes
: sNo
;
183 miniaturized
= wwin
->flags
.miniaturized
? sYes
: sNo
;
184 hidden
= wwin
->flags
.hidden
? sYes
: sNo
;
185 sprintf(buffer
, "%ix%i+%i+%i", wwin
->client
.width
, wwin
->client
.height
,
186 wwin
->frame_x
, wwin
->frame_y
);
187 geometry
= PLMakeString(buffer
);
189 win_state
= PLMakeDictionaryFromEntries(sName
, name
,
191 sWorkspace
, workspace
,
193 sMiniaturized
, miniaturized
,
200 PLRelease(workspace
);
202 if (wapp
&& wapp
->app_icon
&& wapp
->app_icon
->dock
) {
205 if (wapp
->app_icon
->dock
== scr
->dock
) {
208 for(i
=0; i
<scr
->workspace_count
; i
++)
209 if(scr
->workspaces
[i
]->clip
== wapp
->app_icon
->dock
)
211 assert( i
< scr
->workspace_count
);
213 name
= scr
->workspaces
[i
]->name
;
215 dock
= PLMakeString(name
);
216 PLInsertDictionaryEntry(win_state
, sDock
, dock
);
223 if (instance
) XFree(instance
);
224 if (class) XFree(class);
225 if (command
) free(command
);
232 wSessionSaveState(WScreen
*scr
)
234 WWindow
*wwin
= scr
->focused_window
;
235 proplist_t win_info
, wks
;
236 proplist_t list
=NULL
;
237 LinkedList
*wapp_list
=NULL
;
242 if (!scr
->session_state
) {
243 scr
->session_state
= PLMakeDictionaryFromEntries(NULL
, NULL
, NULL
);
244 if (!scr
->session_state
)
248 list
= PLMakeArrayFromElements(NULL
);
251 WApplication
*wapp
=wApplicationOf(wwin
->main_window
);
253 if (wwin
->transient_for
==None
&& list_find(wapp_list
, wapp
)==NULL
254 && !wwin
->window_flags
.dont_save_session
) {
255 /* A entry for this application was not yet saved. Save one. */
256 if ((win_info
= makeWindowState(wwin
, wapp
))!=NULL
) {
257 list
= PLAppendArrayElement(list
, win_info
);
259 /* If we were succesful in saving the info for this window
260 * add the application the window belongs to, to the
261 * application list, so no multiple entries for the same
262 * application are saved.
264 wapp_list
= list_cons(wapp
, wapp_list
);
269 PLRemoveDictionaryEntry(scr
->session_state
, sApplications
);
270 PLInsertDictionaryEntry(scr
->session_state
, sApplications
, list
);
273 wks
= PLMakeString(scr
->workspaces
[scr
->current_workspace
]->name
);
274 PLInsertDictionaryEntry(scr
->session_state
, sWorkspace
, wks
);
277 list_free(wapp_list
);
282 wSessionClearState(WScreen
*scr
)
286 if (!scr
->session_state
)
289 PLRemoveDictionaryEntry(scr
->session_state
, sApplications
);
290 PLRemoveDictionaryEntry(scr
->session_state
, sWorkspace
);
295 execCommand(WScreen
*scr
, char *command
, char *host
)
301 ParseCommand(command
, &argv
, &argc
);
307 if ((pid
=fork())==0) {
311 SetupEnvironment(scr
);
315 args
= malloc(sizeof(char*)*(argc
+1));
318 for (i
=0; i
<argc
; i
++) {
322 execvp(argv
[0], args
);
333 getWindowState(WScreen
*scr
, proplist_t win_state
)
335 WSavedState
*state
= wmalloc(sizeof(WSavedState
));
340 memset(state
, 0, sizeof(WSavedState
));
341 state
->workspace
= -1;
342 value
= PLGetDictionaryEntry(win_state
, sWorkspace
);
343 if (value
&& PLIsString(value
)) {
344 tmp
= PLGetString(value
);
345 if (sscanf(tmp
, "%i", &state
->workspace
)!=1) {
346 state
->workspace
= -1;
347 for (i
=0; i
< scr
->workspace_count
; i
++) {
348 if (strcmp(scr
->workspaces
[i
]->name
, tmp
)==0) {
349 state
->workspace
= i
;
357 if ((value
= PLGetDictionaryEntry(win_state
, sShaded
))!=NULL
)
358 state
->shaded
= getBool(value
);
359 if ((value
= PLGetDictionaryEntry(win_state
, sMiniaturized
))!=NULL
)
360 state
->miniaturized
= getBool(value
);
361 if ((value
= PLGetDictionaryEntry(win_state
, sHidden
))!=NULL
)
362 state
->hidden
= getBool(value
);
364 value
= PLGetDictionaryEntry(win_state
, sGeometry
);
365 if (value
&& PLIsString(value
)) {
366 if (sscanf(PLGetString(value
), "%ix%i+%i+%i",
367 &state
->w
, &state
->h
, &state
->x
, &state
->y
)==4 &&
368 (state
->w
>0 && state
->h
>0)) {
369 state
->use_geometry
= 1;
370 } else if (sscanf(PLGetString(value
), "%i,%i,%i,%i",
371 &state
->x
, &state
->y
, &state
->w
, &state
->h
)==4 &&
372 (state
->w
>0 && state
->h
>0)) {
373 /* TODO: remove redundant sscanf() in version 0.20.x */
374 state
->use_geometry
= 1;
383 #define SAME(x, y) (((x) && (y) && !strcmp((x), (y))) || (!(x) && !(y)))
387 wSessionRestoreState(WScreen
*scr
)
390 char *instance
, *class, *command
, *host
;
391 proplist_t win_info
, apps
, cmd
, value
;
401 if (!scr
->session_state
)
404 PLSetStringCmpHook(NULL
);
406 apps
= PLGetDictionaryEntry(scr
->session_state
, sApplications
);
410 count
= PLGetNumberOfElements(apps
);
414 for (i
=0; i
<count
; i
++) {
415 win_info
= PLGetArrayElement(apps
, i
);
417 cmd
= PLGetDictionaryEntry(win_info
, sCommand
);
418 if (!cmd
|| !PLIsString(cmd
) || !(command
= PLGetString(cmd
))) {
422 value
= PLGetDictionaryEntry(win_info
, sName
);
426 ParseWindowName(value
, &instance
, &class, "session");
427 if (!instance
&& !class)
430 value
= PLGetDictionaryEntry(win_info
, sHost
);
431 if (value
&& PLIsString(value
))
432 host
= PLGetString(value
);
436 state
= getWindowState(scr
, win_info
);
439 value
= PLGetDictionaryEntry(win_info
, sDock
);
440 if (value
&& PLIsString(value
) && (tmp
= PLGetString(value
))!=NULL
) {
441 if (sscanf(tmp
, "%i", &n
)!=1) {
442 if (!strcasecmp(tmp
, "DOCK")) {
445 for (j
=0; j
< scr
->workspace_count
; j
++) {
446 if (strcmp(scr
->workspaces
[j
]->name
, tmp
)==0) {
447 dock
= scr
->workspaces
[j
]->clip
;
455 } else if (n
>0 && n
<=scr
->workspace_count
) {
456 dock
= scr
->workspaces
[n
-1]->clip
;
463 for (j
=0; j
<dock
->max_icons
; j
++) {
464 btn
= dock
->icon_array
[j
];
465 if (btn
&& SAME(instance
, btn
->wm_instance
) &&
466 SAME(class, btn
->wm_class
) &&
467 SAME(command
, btn
->command
)) {
475 wDockLaunchWithState(dock
, btn
, state
);
476 } else if ((pid
= execCommand(scr
, command
, host
)) > 0) {
477 wWindowAddSavedState(instance
, class, command
, pid
, state
);
482 if (instance
) free(instance
);
483 if (class) free(class);
486 PLSetStringCmpHook(StringCompareHook
);
491 wSessionRestoreLastWorkspace(WScreen
*scr
)
499 if (!scr
->session_state
)
502 PLSetStringCmpHook(NULL
);
504 wks
= PLGetDictionaryEntry(scr
->session_state
, sWorkspace
);
505 if (!wks
|| !PLIsString(wks
))
508 tmp
= PLGetString(wks
);
511 PLSetStringCmpHook(StringCompareHook
);
513 if (sscanf(tmp
, "%i", &w
)!=1) {
515 for (i
=0; i
< scr
->workspace_count
; i
++) {
516 if (strcmp(scr
->workspaces
[i
]->name
, tmp
)==0) {
525 if (w
!=scr
->current_workspace
&& w
<scr
->workspace_count
) {
526 wWorkspaceChange(scr
, w
);
534 * With full session management support, the part of WMState
535 * that store client window state will become obsolete,
536 * but we still need to store state info like the dock and workspaces.
537 * It is better to keep dock/wspace info in WMState because the user
538 * might want to keep the dock configuration while not wanting to
539 * resume a previously saved session.
540 * So, wmaker specific state info can be saved in
541 * ~/GNUstep/.AppInfo/WindowMaker/statename.state
542 * Its better to not put it in the defaults directory because:
543 * - its not a defaults file (having domain names like wmaker0089504baa
544 * in the defaults directory wouldn't be very neat)
545 * - this state file is not meant to be edited by users
547 * The old session code will become obsolete. When wmaker is
548 * compiled with R6 sm support compiled in, itll be better to
549 * use a totally rewritten state saving code, but we can keep
550 * the current code for when R6SM is not compiled in.
552 * This will be confusing to old users (well get lots of "SAVE_SESSION broke!"
553 * messages), but itll be better.
560 * Windows are identified as:
561 * WM_CLASS(instance.class).WM_WINDOW_ROLE
566 saveClientState(WWindow
*wwin
, proplist_t dict
)
575 smSaveYourselfPhase2Proc(SmcConn smc_conn
, SmPointer client_data
)
578 SmPropValue prop1val
, prop2val
, prop3val
, prop4val
;
579 char **argv
= (char**)client_data
;
583 char *statefile
= NULL
;
585 Bool gsPrefix
= False
;
586 char *discardCmd
= NULL
;
591 puts("received SaveYourselfPhase2 SM message");
594 /* save session state */
596 /* the file that will contain the state */
597 prefix
= getenv("SM_SAVE_DIR");
599 prefix
= wusergnusteppath();
604 prefix
= getenv("HOME");
609 statefile
= malloc(strlen(prefix
)+64);
613 wwarning(_("end of memory while saving session state"));
621 sprintf(statefile
, "%s/.AppInfo/WindowMaker/%l%i.state",
624 sprintf(statefile
, "%s/wmaker.%l%i.state", prefix
, t
, i
);
626 } while (access(F_OK
, statefile
)!=-1);
631 /* save the states of all windows we're managing */
633 file
= fopen(statefile
, "w");
635 wsyserror(_("could not create state file %s"), statefile
);
644 /* set the remaining properties that we didn't set at
647 for (argc
=0, i
=0; argv
[i
]!=NULL
; i
++) {
648 if (strcmp(argv
[i
], "-clientid")==0
649 || strcmp(argv
[i
], "-restore")==0) {
656 prop
[0].name
= SmRestartCommand
;
657 prop
[0].type
= SmLISTofARRAY8
;
658 prop
[0].vals
= malloc(sizeof(SmPropValue
)*(argc
+4));
659 prop
[0].num_vals
= argc
+4;
661 prop
[1].name
= SmCloneCommand
;
662 prop
[1].type
= SmLISTofARRAY8
;
663 prop
[1].vals
= malloc(sizeof(SmPropValue
)*(argc
));
664 prop
[1].num_vals
= argc
;
666 if (!prop
[0].vals
|| !prop
[1].vals
) {
667 wwarning(_("end of memory while saving session state"));
671 for (j
=0, i
=0; i
<argc
+4; i
++) {
672 if (strcmp(argv
[i
], "-clientid")==0
673 || strcmp(argv
[i
], "-restore")==0) {
676 prop
[0].vals
[j
].value
= argv
[i
];
677 prop
[0].vals
[j
].length
= strlen(argv
[i
]);
678 prop
[1].vals
[j
].value
= argv
[i
];
679 prop
[1].vals
[j
].length
= strlen(argv
[i
]);
683 prop
[0].vals
[j
].value
= "-clientid";
684 prop
[0].vals
[j
].length
= 9;
686 prop
[0].vals
[j
].value
= sClientID
;
687 prop
[0].vals
[j
].length
= strlen(sClientID
);
689 prop
[0].vals
[j
].value
= "-restore";
690 prop
[0].vals
[j
].length
= 11;
692 prop
[0].vals
[j
].value
= statefile
;
693 prop
[0].vals
[j
].length
= strlen(statefile
);
695 discardCmd
= malloc(strlen(statefile
)+8);
698 sprintf(discardCmd
, "rm %s", statefile
);
699 prop
[2].name
= SmDiscardCommand
;
700 prop
[2].type
= SmARRAY8
;
701 prop
[2].vals
[0] = discardCmd
;
702 prop
[2].num_vals
= 1;
704 SmcSetProperties(sSMCConn
, 3, prop
);
708 SmcSaveYourselfDone(smc_conn
, ok
);
726 smSaveYourselfProc(SmcConn smc_conn
, SmPointer client_data
, int save_type
,
727 Bool shutdown
, int interact_style
, Bool fast
)
730 puts("received SaveYourself SM message");
733 if (!SmcRequestSaveYourselfPhase2(smc_conn
, smSaveYourselfPhase2Proc
,
736 SmcSaveYourselfDone(smc_conn
, False
);
737 sWaitingPhase2
= False
;
740 puts("successfull request of SYS phase 2");
742 sWaitingPhase2
= True
;
748 smDieProc(SmcConn smc_conn
, SmPointer client_data
)
751 puts("received Die SM message");
754 wSessionDisconnectManager();
756 RestoreDesktop(NULL
);
765 smSaveCompleteProc(SmcConn smc_conn
)
767 /* it means that we can resume doing things that can change our state */
769 puts("received SaveComplete SM message");
775 smShutdownCancelledProc(SmcConn smc_conn
, SmPointer client_data
)
777 if (sWaitingPhase2
) {
779 sWaitingPhase2
= False
;
781 SmcSaveYourselfDone(smc_conn
, False
);
787 iceMessageProc(int fd
, int mask
, void *clientData
)
789 IceConn iceConn
= (IceConn
)clientData
;
791 IceProcessMessages(iceConn
, NULL
, NULL
);
796 iceIOErrorHandler(IceConnection ice_conn
)
798 /* This is not fatal but can mean the session manager exited.
799 * If the session manager exited normally we would get a
800 * Die message, so this probably means an abnormal exit.
801 * If the sm was the last client of session, then we'll die
802 * anyway, otherwise we can continue doing our stuff.
804 wwarning(_("connection to the session manager was lost"));
805 wSessionDisconnectManager();
810 wSessionConnectManager(char **argv
, int argc
)
813 char *previous_id
= NULL
;
815 SmcCallbacks callbacks
;
820 SmPropValue prop1val
, prop2val
, prop3val
, prop4val
;
824 mask
= SmcSaveYourselfProcMask
|SmcDieProcMask
|SmcSaveCompleteProcMask
825 |SmcShutdownCancelledProcMask
;
827 callbacks
.save_yourself
.callback
= smSaveYourselfProc
;
828 callbacks
.save_yourself
.client_data
= argv
;
830 callbacks
.die
.callback
= smDieProc
;
831 callbacks
.die
.client_data
= NULL
;
833 callbacks
.save_complete
.callback
= smSaveCompleteProc
;
834 callbacks
.save_complete
.client_data
= NULL
;
836 callbacks
.shutdown_cancelled
.callback
= smShutdownCancelledProc
;
837 callbacks
.shutdown_cancelled
.client_data
= NULL
;
839 for (i
=0; i
<argc
; i
++) {
840 if (strcmp(argv
[i
], "-clientid")==0) {
841 previous_id
= argv
[i
+1];
846 /* connect to the session manager */
847 sSMCConn
= SmcOpenConnection(NULL
, NULL
, SmProtoMajor
, SmProtoMinor
,
848 mask
, &callbacks
, previous_id
,
849 &sClientID
, 255, buffer
);
854 puts("connected to the session manager");
857 /* IceSetIOErrorHandler(iceIOErrorHandler);*/
859 /* check for session manager clients */
860 iceConn
= SmcGetIceConnection(smcConn
);
863 sSMInputHandler
= WMAddInputHandler(IceConnectionNumber(iceConn
),
864 WIReadMask
, iceMessageProc
, iceConn
);
866 /* setup information about ourselves */
869 prop1val
.value
= argv
[0];
870 prop1val
.length
= strlen(argv
[0]);
871 prop
[0].name
= SmProgram
;
872 prop
[0].type
= SmARRAY8
;
873 prop
[0].num_vals
= 1;
874 prop
[0].vals
= &prop1val
;
876 /* The XSMP doc from X11R6.1 says it contains the user name,
877 * but every client implementation I saw places the uid # */
878 sprintf(uid
, "%i", getuid());
879 prop2val
.value
= uid
;
880 prop2val
.length
= strlen(uid
);
881 prop
[1].name
= SmUserID
;
882 prop
[1].type
= SmARRAY8
;
883 prop
[1].num_vals
= 1;
884 prop
[1].vals
= &prop2val
;
886 /* Restart style. We should restart only if we were running when
887 * the previous session finished. */
888 restartStyle
= SmRestartIfRunning
;
889 prop3val
.value
= &restartStyle
;
891 prop
[2].name
= SmRestartStyleHint
;
892 prop
[2].type
= SmCARD8
;
893 prop
[2].num_vals
= 1;
894 prop
[2].vals
= &prop3val
;
896 /* Our PID. Not required but might be usefull */
897 sprintf(pid
, "%i", getpid());
898 prop4val
.value
= pid
;
899 prop4val
.length
= strlen(pid
);
900 prop
[3].name
= SmProcessID
;
901 prop
[3].type
= SmARRAY8
;
902 prop
[3].num_vals
= 1;
903 prop
[3].vals
= &prop4val
;
905 /* we'll set the rest of the hints later */
907 SmcSetProperties(sSMCConn
, 4, props
);
912 _wSessionCloseDescriptors(void)
915 close(IceConnectionNumber(SmcGetIceConnection(smcConn
)));
919 wSessionDisconnectManager(void)
922 WMDeleteInputHandler(sSMInputHandler
);
923 sSMInputHandler
= NULL
;
925 SmcCloseConnection(sSMCConn
, 0, NULL
);
931 wSessionRequestShutdown(void)
933 /* request a shutdown to the session manager */
935 SmcRequestSaveYourself(sSMCConn
, SmSaveBoth
, True
, SmInteractStyleAny
,
940 wSessionIsManaged(void)
942 return sSMCConn
!=NULL
;