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 ret
[j
++] = g_strdup("--display");
167 ret
[j
++] = g_strdup((gchar
*)gdk_display_get_name(gdk_display_get_default()));
174 /* SM callback handlers */
176 static void session_save_yourself(SmcConn conn
, SmPointer data
, int save_type
,
177 Bool shutdown
, int interact_style
, Bool fast
) {
178 if (had_first_save
== FALSE
&& save_type
== SmSaveLocal
&&
179 interact_style
== SmInteractStyleNone
&& !shutdown
&&
181 /* this is just a dry run, spit it back */
182 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
183 "Received first save_yourself\n");
184 SmcSaveYourselfDone(conn
, True
);
185 had_first_save
= TRUE
;
189 /* tum ti tum... don't add anything else here without *
190 * reading SMlib.PS from an X.org ftp server near you */
192 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
193 "Received save_yourself\n");
195 if (save_type
== SmSaveGlobal
|| save_type
== SmSaveBoth
) {
196 /* may as well do something ... */
197 /* or not -- save_prefs(); */
200 SmcSaveYourselfDone(conn
, True
);
203 static void session_die(SmcConn conn
, SmPointer data
) {
204 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
209 static void session_save_complete(SmcConn conn
, SmPointer data
) {
210 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
211 "Received save_complete\n");
214 static void session_shutdown_cancelled(SmcConn conn
, SmPointer data
) {
215 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
216 "Received shutdown_cancelled\n");
219 /* utility functions stolen from Gnome-client */
221 static void session_set_value(SmcConn conn
, gchar
*name
, char *type
,
222 int num_vals
, SmPropValue
*vals
) {
226 g_return_if_fail(conn
);
230 prop
.num_vals
= num_vals
;
234 SmcSetProperties(conn
, 1, proplist
);
237 static void session_set_string(SmcConn conn
, gchar
*name
, gchar
*value
) {
240 g_return_if_fail(name
);
242 val
.length
= strlen (value
)+1;
245 session_set_value(conn
, name
, SmARRAY8
, 1, &val
);
248 static void session_set_gchar(SmcConn conn
, gchar
*name
, gchar value
) {
251 g_return_if_fail(name
);
256 session_set_value(conn
, name
, SmCARD8
, 1, &val
);
259 static void session_set_array(SmcConn conn
, gchar
*name
, gchar
*array
[]) {
266 g_return_if_fail (name
);
268 /* We count the number of elements in our array. */
269 for (ptr
= array
, argc
= 0; *ptr
; ptr
++, argc
++) /* LOOP */;
271 /* Now initialize the 'vals' array. */
272 vals
= g_new (SmPropValue
, argc
);
273 for (ptr
= array
, i
= 0 ; i
< argc
; ptr
++, i
++) {
274 vals
[i
].length
= strlen (*ptr
);
275 vals
[i
].value
= *ptr
;
278 session_set_value(conn
, name
, SmLISTofARRAY8
, argc
, vals
);
285 /* setup functions */
288 pidgin_session_init(gchar
*argv0
, gchar
*previous_id
, gchar
*config_dir
)
291 SmcCallbacks callbacks
;
292 gchar
*client_id
= NULL
;
293 gchar error
[ERROR_LENGTH
] = "";
297 if (session
!= NULL
) {
298 /* session is already established, what the hell is going on? */
299 purple_debug(PURPLE_DEBUG_WARNING
, "Session Management",
300 "Duplicated call to pidgin_session_init!\n");
304 if (g_getenv("SESSION_MANAGER") == NULL
) {
305 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
306 "No SESSION_MANAGER found, aborting.\n");
312 callbacks
.save_yourself
.callback
= session_save_yourself
;
313 callbacks
.die
.callback
= session_die
;
314 callbacks
.save_complete
.callback
= session_save_complete
;
315 callbacks
.shutdown_cancelled
.callback
= session_shutdown_cancelled
;
317 callbacks
.save_yourself
.client_data
= NULL
;
318 callbacks
.die
.client_data
= NULL
;
319 callbacks
.save_complete
.client_data
= NULL
;
320 callbacks
.shutdown_cancelled
.client_data
= NULL
;
323 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
324 "Connecting with previous ID %s\n", previous_id
);
326 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
327 "Connecting with no previous ID\n");
330 session
= SmcOpenConnection(NULL
, "session", SmProtoMajor
, SmProtoMinor
, SmcSaveYourselfProcMask
|
331 SmcDieProcMask
| SmcSaveCompleteProcMask
| SmcShutdownCancelledProcMask
,
332 &callbacks
, previous_id
, &client_id
, ERROR_LENGTH
, error
);
334 if (session
== NULL
) {
335 if (error
[0] != '\0') {
336 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
337 "Connection failed with error: %s\n", error
);
339 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
340 "Connetion failed with unknown error.\n");
345 tmp
= SmcVendor(session
);
346 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
347 "Connected to manager (%s) with client ID %s\n",
351 session_managed
= TRUE
;
352 gdk_set_sm_client_id(client_id
);
354 tmp
= g_get_current_dir();
355 session_set_string(session
, SmCurrentDirectory
, tmp
);
358 tmp
= g_strdup_printf("%d", (int) getpid());
359 session_set_string(session
, SmProcessID
, tmp
);
362 tmp
= g_strdup(g_get_user_name());
363 session_set_string(session
, SmUserID
, tmp
);
366 session_set_gchar(session
, SmRestartStyleHint
, (gchar
) SmRestartIfRunning
);
367 session_set_string(session
, SmProgram
, g_get_prgname());
369 myself
= g_strdup(argv0
);
370 purple_debug(PURPLE_DEBUG_MISC
, "Session Management",
371 "Using %s as command\n", myself
);
373 cmd
= session_make_command(NULL
, config_dir
);
374 session_set_array(session
, SmCloneCommand
, cmd
);
377 /* this is currently useless, but gnome-session warns 'the following applications will not
378 save their current status' bla bla if we don't have it and the user checks 'Save Session'
380 cmd
= g_new(gchar
*, 2);
381 cmd
[0] = g_strdup("/bin/true");
383 session_set_array(session
, SmDiscardCommand
, cmd
);
386 cmd
= session_make_command(client_id
, config_dir
);
387 session_set_array(session
, SmRestartCommand
, cmd
);
398 if (session
== NULL
) /* no session to close */
401 SmcCloseConnection(session
, 0, NULL
);
403 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
404 "Connection closed.\n");