2 * @file gtksession.c X Windows session management API
6 /* Pidgin is the legal property of its developers, whose names are too numerous
7 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
29 #include "eventloop.h"
30 #include "gtksession.h"
34 #include <X11/ICE/ICElib.h>
35 #include <X11/SM/SMlib.h>
42 #define ERROR_LENGTH 512
44 static IceIOErrorHandler ice_installed_io_error_handler
;
45 static SmcConn session
= NULL
;
46 static gchar
*myself
= NULL
;
47 static gboolean had_first_save
= FALSE
;
48 static gboolean session_managed
= FALSE
;
50 /* ICE belt'n'braces stuff */
52 struct ice_connection_info
{
57 static void ice_process_messages(gpointer data
, gint fd
,
58 PurpleInputCondition condition
) {
59 struct ice_connection_info
*conninfo
= (struct ice_connection_info
*) data
;
60 IceProcessMessagesStatus status
;
62 /* please don't block... please! */
63 status
= IceProcessMessages(conninfo
->connection
, NULL
, NULL
);
65 if (status
== IceProcessMessagesIOError
) {
66 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
67 "ICE IO error, closing connection... ");
69 /* IO error, please disconnect */
70 IceSetShutdownNegotiation(conninfo
->connection
, False
);
71 IceCloseConnection(conninfo
->connection
);
73 purple_debug(PURPLE_DEBUG_INFO
, NULL
, "done.\n");
75 /* cancel the handler */
76 purple_input_remove(conninfo
->input_id
);
80 static void ice_connection_watch(IceConn connection
, IcePointer client_data
,
81 Bool opening
, IcePointer
*watch_data
) {
82 struct ice_connection_info
*conninfo
= NULL
;
85 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
86 "Handling new ICE connection... \n");
88 /* ensure ICE connection is not passed to child processes */
89 fcntl(IceConnectionNumber(connection
), F_SETFD
, FD_CLOEXEC
);
91 conninfo
= g_new(struct ice_connection_info
, 1);
92 conninfo
->connection
= connection
;
94 /* watch the connection */
95 conninfo
->input_id
= purple_input_add(IceConnectionNumber(connection
), PURPLE_INPUT_READ
,
96 ice_process_messages
, conninfo
);
97 *watch_data
= conninfo
;
99 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
100 "Handling closed ICE connection... \n");
102 /* get the input ID back and stop watching it */
103 conninfo
= (struct ice_connection_info
*) *watch_data
;
104 purple_input_remove(conninfo
->input_id
);
108 purple_debug(PURPLE_DEBUG_INFO
, NULL
, "done.\n");
111 /* We call any handler installed before (or after) ice_init but
112 * avoid calling the default libICE handler which does an exit().
114 * This means we do nothing by default, which is probably correct,
115 * the connection will get closed by libICE
118 static void ice_io_error_handler(IceConn connection
) {
119 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
120 "Handling ICE IO error... ");
122 if (ice_installed_io_error_handler
)
123 (*ice_installed_io_error_handler
)(connection
);
125 purple_debug(PURPLE_DEBUG_INFO
, NULL
, "done.\n");
128 static void ice_init(void) {
129 IceIOErrorHandler default_handler
;
131 ice_installed_io_error_handler
= IceSetIOErrorHandler(NULL
);
132 default_handler
= IceSetIOErrorHandler(ice_io_error_handler
);
134 if (ice_installed_io_error_handler
== default_handler
)
135 ice_installed_io_error_handler
= NULL
;
137 IceAddConnectionWatch(ice_connection_watch
, NULL
);
139 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
140 "ICE initialized.\n");
143 /* my magic utility function */
145 static gchar
**session_make_command(gchar
*client_id
, gchar
*config_dir
) {
150 if (client_id
) i
+= 2;
151 if (config_dir
) i
+= 2; /* we will specify purple's user dir */
153 ret
= g_new(gchar
*, i
);
154 ret
[j
++] = g_strdup(myself
);
157 ret
[j
++] = g_strdup("--session");
158 ret
[j
++] = g_strdup(client_id
);
162 ret
[j
++] = g_strdup("--config");
163 ret
[j
++] = g_strdup(config_dir
);
166 #if GTK_CHECK_VERSION(2,2,0)
167 ret
[j
++] = g_strdup("--display");
168 ret
[j
++] = g_strdup((gchar
*)gdk_display_get_name(gdk_display_get_default()));
176 /* SM callback handlers */
178 static void session_save_yourself(SmcConn conn
, SmPointer data
, int save_type
,
179 Bool shutdown
, int interact_style
, Bool fast
) {
180 if (had_first_save
== FALSE
&& save_type
== SmSaveLocal
&&
181 interact_style
== SmInteractStyleNone
&& !shutdown
&&
183 /* this is just a dry run, spit it back */
184 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
185 "Received first save_yourself\n");
186 SmcSaveYourselfDone(conn
, True
);
187 had_first_save
= TRUE
;
191 /* tum ti tum... don't add anything else here without *
192 * reading SMlib.PS from an X.org ftp server near you */
194 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
195 "Received save_yourself\n");
197 if (save_type
== SmSaveGlobal
|| save_type
== SmSaveBoth
) {
198 /* may as well do something ... */
199 /* or not -- save_prefs(); */
202 SmcSaveYourselfDone(conn
, True
);
205 static void session_die(SmcConn conn
, SmPointer data
) {
206 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
211 static void session_save_complete(SmcConn conn
, SmPointer data
) {
212 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
213 "Received save_complete\n");
216 static void session_shutdown_cancelled(SmcConn conn
, SmPointer data
) {
217 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
218 "Received shutdown_cancelled\n");
221 /* utility functions stolen from Gnome-client */
223 static void session_set_value(SmcConn conn
, gchar
*name
, char *type
,
224 int num_vals
, SmPropValue
*vals
) {
228 g_return_if_fail(conn
);
232 prop
.num_vals
= num_vals
;
236 SmcSetProperties(conn
, 1, proplist
);
239 static void session_set_string(SmcConn conn
, gchar
*name
, gchar
*value
) {
242 g_return_if_fail(name
);
244 val
.length
= strlen (value
)+1;
247 session_set_value(conn
, name
, SmARRAY8
, 1, &val
);
250 static void session_set_gchar(SmcConn conn
, gchar
*name
, gchar value
) {
253 g_return_if_fail(name
);
258 session_set_value(conn
, name
, SmCARD8
, 1, &val
);
261 static void session_set_array(SmcConn conn
, gchar
*name
, gchar
*array
[]) {
268 g_return_if_fail (name
);
270 /* We count the number of elements in our array. */
271 for (ptr
= array
, argc
= 0; *ptr
; ptr
++, argc
++) /* LOOP */;
273 /* Now initialize the 'vals' array. */
274 vals
= g_new (SmPropValue
, argc
);
275 for (ptr
= array
, i
= 0 ; i
< argc
; ptr
++, i
++) {
276 vals
[i
].length
= strlen (*ptr
);
277 vals
[i
].value
= *ptr
;
280 session_set_value(conn
, name
, SmLISTofARRAY8
, argc
, vals
);
287 /* setup functions */
290 pidgin_session_init(gchar
*argv0
, gchar
*previous_id
, gchar
*config_dir
)
293 SmcCallbacks callbacks
;
294 gchar
*client_id
= NULL
;
295 gchar error
[ERROR_LENGTH
] = "";
299 if (session
!= NULL
) {
300 /* session is already established, what the hell is going on? */
301 purple_debug(PURPLE_DEBUG_WARNING
, "Session Management",
302 "Duplicated call to pidgin_session_init!\n");
306 if (g_getenv("SESSION_MANAGER") == NULL
) {
307 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
308 "No SESSION_MANAGER found, aborting.\n");
314 callbacks
.save_yourself
.callback
= session_save_yourself
;
315 callbacks
.die
.callback
= session_die
;
316 callbacks
.save_complete
.callback
= session_save_complete
;
317 callbacks
.shutdown_cancelled
.callback
= session_shutdown_cancelled
;
319 callbacks
.save_yourself
.client_data
= NULL
;
320 callbacks
.die
.client_data
= NULL
;
321 callbacks
.save_complete
.client_data
= NULL
;
322 callbacks
.shutdown_cancelled
.client_data
= NULL
;
325 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
326 "Connecting with previous ID %s\n", previous_id
);
328 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
329 "Connecting with no previous ID\n");
332 session
= SmcOpenConnection(NULL
, "session", SmProtoMajor
, SmProtoMinor
, SmcSaveYourselfProcMask
|
333 SmcDieProcMask
| SmcSaveCompleteProcMask
| SmcShutdownCancelledProcMask
,
334 &callbacks
, previous_id
, &client_id
, ERROR_LENGTH
, error
);
336 if (session
== NULL
) {
337 if (error
[0] != '\0') {
338 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
339 "Connection failed with error: %s\n", error
);
341 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
342 "Connetion failed with unknown error.\n");
347 tmp
= SmcVendor(session
);
348 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
349 "Connected to manager (%s) with client ID %s\n",
353 session_managed
= TRUE
;
354 gdk_set_sm_client_id(client_id
);
356 tmp
= g_get_current_dir();
357 session_set_string(session
, SmCurrentDirectory
, tmp
);
360 tmp
= g_strdup_printf("%d", (int) getpid());
361 session_set_string(session
, SmProcessID
, tmp
);
364 tmp
= g_strdup(g_get_user_name());
365 session_set_string(session
, SmUserID
, tmp
);
368 session_set_gchar(session
, SmRestartStyleHint
, (gchar
) SmRestartIfRunning
);
369 session_set_string(session
, SmProgram
, g_get_prgname());
371 myself
= g_strdup(argv0
);
372 purple_debug(PURPLE_DEBUG_MISC
, "Session Management",
373 "Using %s as command\n", myself
);
375 cmd
= session_make_command(NULL
, config_dir
);
376 session_set_array(session
, SmCloneCommand
, cmd
);
379 /* this is currently useless, but gnome-session warns 'the following applications will not
380 save their current status' bla bla if we don't have it and the user checks 'Save Session'
382 cmd
= g_new(gchar
*, 2);
383 cmd
[0] = g_strdup("/bin/true");
385 session_set_array(session
, SmDiscardCommand
, cmd
);
388 cmd
= session_make_command(client_id
, config_dir
);
389 session_set_array(session
, SmRestartCommand
, cmd
);
400 if (session
== NULL
) /* no session to close */
403 SmcCloseConnection(session
, 0, NULL
);
405 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
406 "Connection closed.\n");