Merge from emacs-23; up to 2010-06-01T01:49:15Z!monnier@iro.umontreal.ca
[bpt/emacs.git] / src / xsmfns.c
CommitLineData
afb4ecad
JD
1/* Session management module for systems which understand the X Session
2 management protocol.
95df8112
GM
3
4Copyright (C) 2002-2011 Free Software Foundation, Inc.
afb4ecad
JD
5
6This file is part of GNU Emacs.
7
9ec0b715 8GNU Emacs is free software: you can redistribute it and/or modify
afb4ecad 9it under the terms of the GNU General Public License as published by
9ec0b715
GM
10the Free Software Foundation, either version 3 of the License, or
11(at your option) any later version.
afb4ecad
JD
12
13GNU Emacs is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
9ec0b715 19along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
afb4ecad
JD
20
21#include <config.h>
22
23#ifdef HAVE_X_SM
24
25#include <X11/SM/SMlib.h>
231d6cfb
JD
26#include <X11/Xlib.h>
27#include <X11/Xutil.h>
28
afb4ecad 29#include <unistd.h>
afb4ecad 30#include <sys/param.h>
656132eb 31#include <stdio.h>
d7306fe6 32#include <setjmp.h>
afb4ecad 33
fa8459a3 34#include "lisp.h"
afb4ecad
JD
35#include "systime.h"
36#include "sysselect.h"
428a555e 37#include "frame.h"
afb4ecad 38#include "termhooks.h"
656132eb 39#include "termopts.h"
231d6cfb 40#include "xterm.h"
4df0af9b
JD
41#include "process.h"
42#include "keyboard.h"
afb4ecad 43
3b8f9651 44/* This is the event used when SAVE_SESSION_EVENT occurs. */
afb4ecad
JD
45
46static struct input_event emacs_event;
47
3b8f9651 48/* The descriptor that we use to check for data from the session manager. */
afb4ecad 49
182659ae 50static int ice_fd;
afb4ecad 51
3b8f9651 52/* A flag that says if we are in shutdown interactions or not. */
afb4ecad 53
182659ae 54static int doing_interact;
afb4ecad 55
4f2f546e 56/* The session manager object for the session manager connection. */
afb4ecad
JD
57
58static SmcConn smc_conn;
59
4f2f546e
JD
60/* The client session id for this session. */
61
afb4ecad
JD
62static char *client_id;
63
4f2f546e
JD
64/* The full path name to the Emacs program. */
65
afb4ecad
JD
66static char *emacs_program;
67
afb4ecad 68/* The option we tell the session manager to start Emacs with when
4f2f546e 69 restarting Emacs. The client_id is appended. */
afb4ecad
JD
70
71#define SMID_OPT "--smid="
72
73
ca2417b9 74/* The option to start Emacs without the splash screen when
4f2f546e 75 restarting Emacs. */
ca2417b9 76
42ca4633 77static char NOSPLASH_OPT[] = "--no-splash";
ca2417b9 78
f63d0028
JD
79/* The option to make Emacs start in the given directory. */
80
81#define CHDIR_OPT "--chdir="
82
182659ae 83static void
971de7fb 84ice_connection_closed (void)
182659ae
JD
85{
86 if (ice_fd >= 0)
4df0af9b 87 delete_read_fd (ice_fd);
182659ae
JD
88 ice_fd = -1;
89}
90
ca2417b9 91
afb4ecad 92/* Handle any messages from the session manager. If no connection is
4df0af9b 93 open to a session manager, just return. */
4f2f546e 94
4df0af9b
JD
95static void
96x_session_check_input (int fd, void *data, int for_read)
afb4ecad 97{
182659ae 98 int ret;
7d0393cf 99
4df0af9b 100 if (ice_fd == -1) return;
7d0393cf 101
afb4ecad
JD
102 /* Reset this so wo can check kind after callbacks have been called by
103 IceProcessMessages. The smc_interact_CB sets the kind to
3b8f9651 104 SAVE_SESSION_EVENT, but we don't know beforehand if that callback
4f2f546e 105 will be called. */
3b8f9651 106 emacs_event.kind = NO_EVENT;
afb4ecad 107
4df0af9b
JD
108 ret = IceProcessMessages (SmcGetIceConnection (smc_conn),
109 (IceReplyWaitInfo *)0, (Bool *)0);
110 if (ret != IceProcessMessagesSuccess)
afb4ecad 111 {
4df0af9b
JD
112 /* Either IO error or Connection closed. */
113 if (ret == IceProcessMessagesIOError)
114 IceCloseConnection (SmcGetIceConnection (smc_conn));
115
182659ae
JD
116 ice_connection_closed ();
117 }
7d0393cf 118
afb4ecad 119 /* Check if smc_interact_CB was called and we shall generate a
4f2f546e 120 SAVE_SESSION_EVENT. */
182659ae 121 if (emacs_event.kind != NO_EVENT)
4df0af9b 122 kbd_buffer_store_event (&emacs_event);
afb4ecad
JD
123}
124
4f2f546e
JD
125/* Return non-zero if we have a connection to a session manager. */
126
afb4ecad 127int
971de7fb 128x_session_have_connection (void)
afb4ecad
JD
129{
130 return ice_fd != -1;
131}
132
133/* This is called when the session manager says it is OK to interact with the
3b8f9651 134 user. Here we set the kind to SAVE_SESSION_EVENT so an event is generated.
4f2f546e
JD
135 Then lisp code can interact with the user. */
136
afb4ecad 137static void
971de7fb 138smc_interact_CB (SmcConn smcConn, SmPointer clientData)
afb4ecad
JD
139{
140 doing_interact = True;
3b8f9651 141 emacs_event.kind = SAVE_SESSION_EVENT;
0b9fc69a 142 emacs_event.arg = Qnil;
afb4ecad
JD
143}
144
f25dcaa0 145/* This is called when the session manager tells us to save ourselves.
afb4ecad
JD
146 We set the required properties so the session manager can restart us,
147 plus the current working directory property (not mandatory) so we
148 are started in the correct directory.
149
150 If this is a shutdown and we can request to interact with the user,
4f2f546e
JD
151 we do so, because we don't know what the lisp code might do. */
152
afb4ecad 153static void
dd4c5104
DN
154smc_save_yourself_CB (SmcConn smcConn,
155 SmPointer clientData,
156 int saveType,
157 Bool shutdown,
158 int interactStyle,
159 Bool fast)
afb4ecad
JD
160{
161#define NR_PROPS 5
7d0393cf 162
afb4ecad
JD
163 SmProp *props[NR_PROPS];
164 SmProp prop_ptr[NR_PROPS];
7d0393cf 165
4df0af9b
JD
166 SmPropValue values[20], *vp;
167 int val_idx = 0, vp_idx = 0;
afb4ecad 168 int props_idx = 0;
42ca4633 169 int i;
4df0af9b 170 char *cwd = get_current_dir_name ();
f63d0028 171 char *smid_opt, *chdir_opt = NULL;
afb4ecad 172
4f2f546e 173 /* How to start a new instance of Emacs. */
afb4ecad 174 props[props_idx] = &prop_ptr[props_idx];
42ca4633
J
175 props[props_idx]->name = xstrdup (SmCloneCommand);
176 props[props_idx]->type = xstrdup (SmLISTofARRAY8);
afb4ecad
JD
177 props[props_idx]->num_vals = 1;
178 props[props_idx]->vals = &values[val_idx++];
179 props[props_idx]->vals[0].length = strlen (emacs_program);
180 props[props_idx]->vals[0].value = emacs_program;
181 ++props_idx;
182
4f2f546e 183 /* The name of the program. */
afb4ecad 184 props[props_idx] = &prop_ptr[props_idx];
42ca4633
J
185 props[props_idx]->name = xstrdup (SmProgram);
186 props[props_idx]->type = xstrdup (SmARRAY8);
afb4ecad
JD
187 props[props_idx]->num_vals = 1;
188 props[props_idx]->vals = &values[val_idx++];
e4c8d29a 189 props[props_idx]->vals[0].length = strlen (SSDATA (Vinvocation_name));
d5db4077 190 props[props_idx]->vals[0].value = SDATA (Vinvocation_name);
afb4ecad 191 ++props_idx;
7d0393cf 192
4f2f546e 193 /* User id. */
afb4ecad 194 props[props_idx] = &prop_ptr[props_idx];
42ca4633
J
195 props[props_idx]->name = xstrdup (SmUserID);
196 props[props_idx]->type = xstrdup (SmARRAY8);
afb4ecad
JD
197 props[props_idx]->num_vals = 1;
198 props[props_idx]->vals = &values[val_idx++];
e4c8d29a 199 props[props_idx]->vals[0].length = strlen (SSDATA (Vuser_login_name));
d5db4077 200 props[props_idx]->vals[0].value = SDATA (Vuser_login_name);
afb4ecad
JD
201 ++props_idx;
202
c187839d
EZ
203
204 if (cwd)
afb4ecad
JD
205 {
206 props[props_idx] = &prop_ptr[props_idx];
42ca4633
J
207 props[props_idx]->name = xstrdup (SmCurrentDirectory);
208 props[props_idx]->type = xstrdup (SmARRAY8);
afb4ecad
JD
209 props[props_idx]->num_vals = 1;
210 props[props_idx]->vals = &values[val_idx++];
211 props[props_idx]->vals[0].length = strlen (cwd);
212 props[props_idx]->vals[0].value = cwd;
213 ++props_idx;
214 }
7d0393cf
JB
215
216
4df0af9b
JD
217 /* How to restart Emacs. */
218 props[props_idx] = &prop_ptr[props_idx];
219 props[props_idx]->name = xstrdup (SmRestartCommand);
220 props[props_idx]->type = xstrdup (SmLISTofARRAY8);
221 /* /path/to/emacs, --smid=xxx --no-splash --chdir=dir ... */
222 i = 3 + initial_argc;
223 props[props_idx]->num_vals = i;
224 vp = (SmPropValue *) xmalloc (i * sizeof(*vp));
225 props[props_idx]->vals = vp;
226 props[props_idx]->vals[vp_idx].length = strlen (emacs_program);
227 props[props_idx]->vals[vp_idx++].value = emacs_program;
228
229 smid_opt = xmalloc (strlen (SMID_OPT) + strlen (client_id) + 1);
230 strcpy (smid_opt, SMID_OPT);
231 strcat (smid_opt, client_id);
232
233 props[props_idx]->vals[vp_idx].length = strlen (smid_opt);
234 props[props_idx]->vals[vp_idx++].value = smid_opt;
235
236 props[props_idx]->vals[vp_idx].length = strlen (NOSPLASH_OPT);
237 props[props_idx]->vals[vp_idx++].value = NOSPLASH_OPT;
238
239 if (cwd)
240 {
241 chdir_opt = xmalloc (strlen (CHDIR_OPT) + strlen (cwd) + 1);
242 strcpy (chdir_opt, CHDIR_OPT);
243 strcat (chdir_opt, cwd);
244
245 props[props_idx]->vals[vp_idx].length = strlen (chdir_opt);
246 props[props_idx]->vals[vp_idx++].value = chdir_opt;
247 }
248
249 for (i = 1; i < initial_argc; ++i)
250 {
251 props[props_idx]->vals[vp_idx].length = strlen (initial_argv[i]);
252 props[props_idx]->vals[vp_idx++].value = initial_argv[i];
253 }
254
255 ++props_idx;
256
afb4ecad
JD
257 SmcSetProperties (smcConn, props_idx, props);
258
259 xfree (smid_opt);
f63d0028 260 xfree (chdir_opt);
4df0af9b
JD
261 xfree (cwd);
262 xfree (vp);
afb4ecad 263
42ca4633
J
264 for (i = 0; i < props_idx; ++i)
265 {
266 xfree (props[i]->type);
267 xfree (props[i]->name);
268 }
c187839d 269
4f2f546e 270 /* See if we maybe shall interact with the user. */
afb4ecad
JD
271 if (interactStyle != SmInteractStyleAny
272 || ! shutdown
273 || saveType == SmSaveLocal
274 || ! SmcInteractRequest (smcConn, SmDialogNormal, smc_interact_CB, 0))
275 {
4f2f546e 276 /* No interaction, we are done saving ourself. */
afb4ecad
JD
277 SmcSaveYourselfDone (smcConn, True);
278 }
279}
280
4f2f546e
JD
281/* According to the SM specification, this shall close the connection. */
282
afb4ecad 283static void
971de7fb 284smc_die_CB (SmcConn smcConn, SmPointer clientData)
afb4ecad 285{
0b9fc69a
JD
286 emacs_event.kind = SAVE_SESSION_EVENT;
287 emacs_event.arg = Qt;
afb4ecad
JD
288}
289
290/* We don't use the next two but they are mandatory, leave them empty.
291 According to the SM specification, we should not interact with the
292 user between smc_save_yourself_CB is called and until smc_save_complete_CB
293 is called. It seems like a lot of job to implement this and it doesn't
4f2f546e
JD
294 even seem necessary. */
295
afb4ecad 296static void
971de7fb 297smc_save_complete_CB (SmcConn smcConn, SmPointer clientData)
afb4ecad
JD
298{
299 /* Empty */
300}
301
302static void
971de7fb 303smc_shutdown_cancelled_CB (SmcConn smcConn, SmPointer clientData)
afb4ecad
JD
304{
305 /* Empty */
306}
307
f25dcaa0 308/* Error handlers for SM and ICE. We don't want to exit Emacs just
4f2f546e
JD
309 because there is some error in the session management. */
310
afb4ecad 311static void
dd4c5104
DN
312smc_error_handler (SmcConn smcConn,
313 Bool swap,
314 int offendingMinorOpcode,
315 unsigned long offendingSequence,
316 int errorClass,
317 int severity,
318 SmPointer values)
afb4ecad 319{
4f2f546e 320 /* Empty */
afb4ecad
JD
321}
322
323static void
dd4c5104
DN
324ice_error_handler (IceConn iceConn,
325 Bool swap,
326 int offendingMinorOpcode,
327 unsigned long offendingSequence,
328 int errorClass,
329 int severity,
330 IcePointer values)
afb4ecad 331{
4f2f546e 332 /* Empty */
afb4ecad
JD
333}
334
335
336static void
971de7fb 337ice_io_error_handler (IceConn iceConn)
afb4ecad 338{
4f2f546e 339 /* Connection probably gone. */
182659ae 340 ice_connection_closed ();
afb4ecad
JD
341}
342
343/* This is called when the ICE connection is created or closed. The SM library
4f2f546e
JD
344 uses ICE as it transport protocol. */
345
afb4ecad 346static void
4df0af9b
JD
347ice_conn_watch_CB (IceConn iceConn, IcePointer clientData,
348 int opening, IcePointer *watchData)
afb4ecad
JD
349{
350 if (! opening)
351 {
182659ae 352 ice_connection_closed ();
afb4ecad
JD
353 return;
354 }
7d0393cf 355
afb4ecad 356 ice_fd = IceConnectionNumber (iceConn);
4df0af9b 357 add_read_fd (ice_fd, x_session_check_input, NULL);
afb4ecad
JD
358}
359
231d6cfb 360/* Create the client leader window. */
4f2f546e 361
e4c8d29a 362#ifndef USE_GTK
231d6cfb 363static void
971de7fb 364create_client_leader_window (struct x_display_info *dpyinfo, char *client_id)
231d6cfb
JD
365{
366 Window w;
367 XClassHint class_hints;
368 Atom sm_id;
369
370 w = XCreateSimpleWindow (dpyinfo->display,
371 dpyinfo->root_window,
372 -1, -1, 1, 1,
373 CopyFromParent, CopyFromParent, CopyFromParent);
374
51b59d79
PE
375 class_hints.res_name = SSDATA (Vx_resource_name);
376 class_hints.res_class = SSDATA (Vx_resource_class);
231d6cfb
JD
377 XSetClassHint (dpyinfo->display, w, &class_hints);
378 XStoreName (dpyinfo->display, w, class_hints.res_name);
379
2d9074ba
JD
380 XChangeProperty (dpyinfo->display, w, dpyinfo->Xatom_SM_CLIENT_ID,
381 XA_STRING, 8, PropModeReplace,
e4c8d29a 382 (unsigned char *)client_id, strlen (client_id));
231d6cfb
JD
383
384 dpyinfo->client_leader_window = w;
385}
e4c8d29a
J
386#endif /* ! USE_GTK */
387
231d6cfb 388
4f2f546e
JD
389/* Try to open a connection to the session manager. */
390
afb4ecad 391void
971de7fb 392x_session_initialize (struct x_display_info *dpyinfo)
afb4ecad
JD
393{
394#define SM_ERRORSTRING_LEN 512
395 char errorstring[SM_ERRORSTRING_LEN];
396 char* previous_id = NULL;
397 SmcCallbacks callbacks;
398 int name_len = 0;
7d0393cf 399
182659ae
JD
400 ice_fd = -1;
401 doing_interact = False;
402
afb4ecad 403 /* Check if we where started by the session manager. If so, we will
4f2f546e 404 have a previous id. */
afb4ecad 405 if (! EQ (Vx_session_previous_id, Qnil) && STRINGP (Vx_session_previous_id))
e4c8d29a 406 previous_id = SSDATA (Vx_session_previous_id);
afb4ecad 407
4f2f546e 408 /* Construct the path to the Emacs program. */
afb4ecad 409 if (! EQ (Vinvocation_directory, Qnil))
e4c8d29a
J
410 name_len += strlen (SSDATA (Vinvocation_directory));
411 name_len += strlen (SSDATA (Vinvocation_name));
afb4ecad
JD
412
413 /* This malloc will not be freed, but it is only done once, and hopefully
4f2f546e 414 not very large */
afb4ecad
JD
415 emacs_program = xmalloc (name_len + 1);
416 emacs_program[0] = '\0';
417
418 if (! EQ (Vinvocation_directory, Qnil))
e4c8d29a
J
419 strcpy (emacs_program, SSDATA (Vinvocation_directory));
420 strcat (emacs_program, SSDATA (Vinvocation_name));
7d0393cf 421
afb4ecad 422 /* The SM protocol says all callbacks are mandatory, so set up all
4f2f546e 423 here and in the mask passed to SmcOpenConnection. */
afb4ecad
JD
424 callbacks.save_yourself.callback = smc_save_yourself_CB;
425 callbacks.save_yourself.client_data = 0;
426 callbacks.die.callback = smc_die_CB;
427 callbacks.die.client_data = 0;
428 callbacks.save_complete.callback = smc_save_complete_CB;
429 callbacks.save_complete.client_data = 0;
430 callbacks.shutdown_cancelled.callback = smc_shutdown_cancelled_CB;
431 callbacks.shutdown_cancelled.client_data = 0;
432
4f2f546e 433 /* Set error handlers. */
afb4ecad
JD
434 SmcSetErrorHandler (smc_error_handler);
435 IceSetErrorHandler (ice_error_handler);
436 IceSetIOErrorHandler (ice_io_error_handler);
437
4f2f546e 438 /* Install callback for when connection status changes. */
afb4ecad
JD
439 IceAddConnectionWatch (ice_conn_watch_CB, 0);
440
441 /* Open the connection to the session manager. A failure is not
f25dcaa0 442 critical, it usually means that no session manager is running.
4f2f546e 443 The errorstring is here for debugging. */
afb4ecad
JD
444 smc_conn = SmcOpenConnection (NULL, NULL, 1, 0,
445 (SmcSaveYourselfProcMask|
446 SmcDieProcMask|
447 SmcSaveCompleteProcMask|
448 SmcShutdownCancelledProcMask),
449 &callbacks,
450 previous_id,
451 &client_id,
452 SM_ERRORSTRING_LEN,
453 errorstring);
454
455 if (smc_conn != 0)
231d6cfb
JD
456 {
457 Vx_session_id = make_string (client_id, strlen (client_id));
458
459#ifdef USE_GTK
460 /* GTK creats a leader window by itself, but we need to tell
461 it about our client_id. */
462 gdk_set_sm_client_id (client_id);
463#else
464 create_client_leader_window (dpyinfo, client_id);
465#endif
466 }
afb4ecad
JD
467}
468
d51abf22
KL
469/* Ensure that the session manager is not contacted again. */
470
471void
971de7fb 472x_session_close (void)
d51abf22 473{
182659ae 474 ice_connection_closed ();
d51abf22
KL
475}
476
afb4ecad
JD
477
478DEFUN ("handle-save-session", Fhandle_save_session,
479 Shandle_save_session, 1, 1, "e",
480 doc: /* Handle the save_yourself event from a session manager.
7d0393cf 481A session manager can tell Emacs that the window system is shutting down
afb4ecad
JD
482by sending Emacs a save_yourself message. Emacs executes this function when
483such an event occurs. This function then executes `emacs-session-save'.
484After that, this function informs the session manager that it can continue
485or abort shutting down the window system depending on the return value
486from `emacs-session-save' If the return value is non-nil the session manager
487is told to abort the window system shutdown.
488
489Do not call this function yourself. */)
5842a27b 490 (Lisp_Object event)
afb4ecad 491{
0b9fc69a
JD
492 int kill_emacs = CONSP (event) && CONSP (XCDR (event))
493 && EQ (Qt, XCAR (XCDR (event)));
494
afb4ecad
JD
495 /* Check doing_interact so that we don't do anything if someone called
496 this at the wrong time. */
0b9fc69a 497 if (doing_interact && ! kill_emacs)
afb4ecad
JD
498 {
499 Bool cancel_shutdown = False;
500
501 cancel_shutdown = ! EQ (call0 (intern ("emacs-session-save")), Qnil);
502
503 SmcInteractDone (smc_conn, cancel_shutdown);
504 SmcSaveYourselfDone (smc_conn, True);
505
506 doing_interact = False;
507 }
0b9fc69a
JD
508 else if (kill_emacs)
509 {
510 /* We should not do user interaction here, but it is not easy to
511 prevent. Fix this in next version. */
512 Fkill_emacs (Qnil);
ad4ace7a 513
0b9fc69a
JD
514 /* This will not be reached, but we want kill-emacs-hook to be run. */
515 SmcCloseConnection (smc_conn, 0, 0);
516 ice_connection_closed ();
517 }
51b59d79 518
ad4ace7a 519 return Qnil;
afb4ecad 520}
51b59d79 521
afb4ecad
JD
522
523\f
524/***********************************************************************
525 Initialization
526 ***********************************************************************/
527void
971de7fb 528syms_of_xsmfns (void)
afb4ecad 529{
29208e82 530 DEFVAR_LISP ("x-session-id", Vx_session_id,
afb4ecad
JD
531 doc: /* The session id Emacs got from the session manager for this session.
532Changing the value does not change the session id used by Emacs.
533The value is nil if no session manager is running.
534See also `x-session-previous-id', `emacs-save-session-functions',
535`emacs-session-save' and `emacs-session-restore'." */);
536 Vx_session_id = Qnil;
537
29208e82 538 DEFVAR_LISP ("x-session-previous-id", Vx_session_previous_id,
afb4ecad 539 doc: /* The previous session id Emacs got from session manager.
7d0393cf 540If Emacs is running on a window system that has a session manager, the
fbcdaefb 541session manager gives Emacs a session id. It is feasible for Emacs Lisp
7d0393cf
JB
542code to use the session id to save configuration in, for example, a file
543with a file name based on the session id. If Emacs is running when the
544window system is shut down, the session manager remembers that Emacs was
afb4ecad
JD
545running and saves the session id Emacs had.
546
7d0393cf
JB
547When the window system is started again, the session manager restarts
548Emacs and hands Emacs the session id it had the last time it was
549running. This is now the previous session id and the value of this
550variable. If configuration was saved in a file as stated above, the
afb4ecad
JD
551previous session id shall be used to reconstruct the file name.
552
7d0393cf 553The session id Emacs has while it is running is in the variable
afb4ecad
JD
554`x-session-id'. The value of this variable and `x-session-id' may be the
555same, depending on how the session manager works.
556
557See also `emacs-save-session-functions', `emacs-session-save' and
558`emacs-session-restore'." */);
559 Vx_session_previous_id = Qnil;
7d0393cf 560
afb4ecad
JD
561 defsubr (&Shandle_save_session);
562}
563
564#endif /* HAVE_X_SM */