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 if (fcntl(IceConnectionNumber(connection
), F_SETFD
, FD_CLOEXEC
) != 0)
90 purple_debug_warning("gtksession", "couldn't set FD_CLOEXEC\n");
92 conninfo
= g_new(struct ice_connection_info
, 1);
93 conninfo
->connection
= connection
;
95 /* watch the connection */
96 conninfo
->input_id
= purple_input_add(IceConnectionNumber(connection
), PURPLE_INPUT_READ
,
97 ice_process_messages
, conninfo
);
98 *watch_data
= conninfo
;
100 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
101 "Handling closed ICE connection... \n");
103 /* get the input ID back and stop watching it */
104 conninfo
= (struct ice_connection_info
*) *watch_data
;
105 purple_input_remove(conninfo
->input_id
);
109 purple_debug(PURPLE_DEBUG_INFO
, NULL
, "done.\n");
112 /* We call any handler installed before (or after) ice_init but
113 * avoid calling the default libICE handler which does an exit().
115 * This means we do nothing by default, which is probably correct,
116 * the connection will get closed by libICE
119 static void ice_io_error_handler(IceConn connection
) {
120 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
121 "Handling ICE IO error... ");
123 if (ice_installed_io_error_handler
)
124 (*ice_installed_io_error_handler
)(connection
);
126 purple_debug(PURPLE_DEBUG_INFO
, NULL
, "done.\n");
129 static void ice_init(void) {
130 IceIOErrorHandler default_handler
;
132 ice_installed_io_error_handler
= IceSetIOErrorHandler(NULL
);
133 default_handler
= IceSetIOErrorHandler(ice_io_error_handler
);
135 if (ice_installed_io_error_handler
== default_handler
)
136 ice_installed_io_error_handler
= NULL
;
138 IceAddConnectionWatch(ice_connection_watch
, NULL
);
140 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
141 "ICE initialized.\n");
144 /* my magic utility function */
146 static gchar
**session_make_command(gchar
*client_id
, gchar
*config_dir
) {
151 if (client_id
) i
+= 2;
152 if (config_dir
) i
+= 2; /* we will specify purple's user dir */
154 ret
= g_new(gchar
*, i
);
155 ret
[j
++] = g_strdup(myself
);
158 ret
[j
++] = g_strdup("--session");
159 ret
[j
++] = g_strdup(client_id
);
163 ret
[j
++] = g_strdup("--config");
164 ret
[j
++] = g_strdup(config_dir
);
167 ret
[j
++] = g_strdup("--display");
168 ret
[j
++] = g_strdup((gchar
*)gdk_display_get_name(gdk_display_get_default()));
175 /* SM callback handlers */
177 static void session_save_yourself(SmcConn conn
, SmPointer data
, int save_type
,
178 Bool shutdown
, int interact_style
, Bool fast
) {
179 if (had_first_save
== FALSE
&& save_type
== SmSaveLocal
&&
180 interact_style
== SmInteractStyleNone
&& !shutdown
&&
182 /* this is just a dry run, spit it back */
183 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
184 "Received first save_yourself\n");
185 SmcSaveYourselfDone(conn
, True
);
186 had_first_save
= TRUE
;
190 /* tum ti tum... don't add anything else here without *
191 * reading SMlib.PS from an X.org ftp server near you */
193 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
194 "Received save_yourself\n");
196 if (save_type
== SmSaveGlobal
|| save_type
== SmSaveBoth
) {
197 /* may as well do something ... */
198 /* or not -- save_prefs(); */
201 SmcSaveYourselfDone(conn
, True
);
204 static void session_die(SmcConn conn
, SmPointer data
) {
205 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
210 static void session_save_complete(SmcConn conn
, SmPointer data
) {
211 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
212 "Received save_complete\n");
215 static void session_shutdown_cancelled(SmcConn conn
, SmPointer data
) {
216 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
217 "Received shutdown_cancelled\n");
220 /* utility functions stolen from Gnome-client */
222 static void session_set_value(SmcConn conn
, gchar
*name
, char *type
,
223 int num_vals
, SmPropValue
*vals
) {
227 g_return_if_fail(conn
);
231 prop
.num_vals
= num_vals
;
235 SmcSetProperties(conn
, 1, proplist
);
238 static void session_set_string(SmcConn conn
, gchar
*name
, gchar
*value
) {
241 g_return_if_fail(name
);
243 val
.length
= strlen (value
)+1;
246 session_set_value(conn
, name
, SmARRAY8
, 1, &val
);
249 static void session_set_gchar(SmcConn conn
, gchar
*name
, gchar value
) {
252 g_return_if_fail(name
);
257 session_set_value(conn
, name
, SmCARD8
, 1, &val
);
260 static void session_set_array(SmcConn conn
, gchar
*name
, gchar
*array
[]) {
267 g_return_if_fail (name
);
269 /* We count the number of elements in our array. */
270 for (ptr
= array
, argc
= 0; *ptr
; ptr
++, argc
++) /* LOOP */;
272 /* Now initialize the 'vals' array. */
273 vals
= g_new (SmPropValue
, argc
);
274 for (ptr
= array
, i
= 0 ; i
< argc
; ptr
++, i
++) {
275 vals
[i
].length
= strlen (*ptr
);
276 vals
[i
].value
= *ptr
;
279 session_set_value(conn
, name
, SmLISTofARRAY8
, argc
, vals
);
286 /* setup functions */
289 pidgin_session_init(gchar
*argv0
, gchar
*previous_id
, gchar
*config_dir
)
292 SmcCallbacks callbacks
;
293 gchar
*client_id
= NULL
;
294 gchar error
[ERROR_LENGTH
] = "";
298 if (session
!= NULL
) {
299 /* session is already established, what the hell is going on? */
300 purple_debug(PURPLE_DEBUG_WARNING
, "Session Management",
301 "Duplicated call to pidgin_session_init!\n");
305 if (g_getenv("SESSION_MANAGER") == NULL
) {
306 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
307 "No SESSION_MANAGER found, aborting.\n");
313 callbacks
.save_yourself
.callback
= session_save_yourself
;
314 callbacks
.die
.callback
= session_die
;
315 callbacks
.save_complete
.callback
= session_save_complete
;
316 callbacks
.shutdown_cancelled
.callback
= session_shutdown_cancelled
;
318 callbacks
.save_yourself
.client_data
= NULL
;
319 callbacks
.die
.client_data
= NULL
;
320 callbacks
.save_complete
.client_data
= NULL
;
321 callbacks
.shutdown_cancelled
.client_data
= NULL
;
324 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
325 "Connecting with previous ID %s\n", previous_id
);
327 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
328 "Connecting with no previous ID\n");
331 session
= SmcOpenConnection(NULL
, "session", SmProtoMajor
, SmProtoMinor
, SmcSaveYourselfProcMask
|
332 SmcDieProcMask
| SmcSaveCompleteProcMask
| SmcShutdownCancelledProcMask
,
333 &callbacks
, previous_id
, &client_id
, ERROR_LENGTH
, error
);
335 if (session
== NULL
) {
336 if (error
[0] != '\0') {
337 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
338 "Connection failed with error: %s\n", error
);
340 purple_debug(PURPLE_DEBUG_ERROR
, "Session Management",
341 "Connetion failed with unknown error.\n");
346 tmp
= SmcVendor(session
);
347 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
348 "Connected to manager (%s) with client ID %s\n",
352 session_managed
= TRUE
;
353 gdk_set_sm_client_id(client_id
);
355 tmp
= g_get_current_dir();
356 session_set_string(session
, SmCurrentDirectory
, tmp
);
359 tmp
= g_strdup_printf("%d", (int) getpid());
360 session_set_string(session
, SmProcessID
, tmp
);
363 tmp
= g_strdup(g_get_user_name());
364 session_set_string(session
, SmUserID
, tmp
);
367 session_set_gchar(session
, SmRestartStyleHint
, (gchar
) SmRestartIfRunning
);
368 session_set_string(session
, SmProgram
, (gchar
*) g_get_prgname());
370 myself
= g_strdup(argv0
);
371 purple_debug(PURPLE_DEBUG_MISC
, "Session Management",
372 "Using %s as command\n", myself
);
374 cmd
= session_make_command(NULL
, config_dir
);
375 session_set_array(session
, SmCloneCommand
, cmd
);
378 /* this is currently useless, but gnome-session warns 'the following applications will not
379 save their current status' bla bla if we don't have it and the user checks 'Save Session'
381 cmd
= g_new(gchar
*, 2);
382 cmd
[0] = g_strdup("/bin/true");
384 session_set_array(session
, SmDiscardCommand
, cmd
);
387 cmd
= session_make_command(client_id
, config_dir
);
388 session_set_array(session
, SmRestartCommand
, cmd
);
399 if (session
== NULL
) /* no session to close */
402 SmcCloseConnection(session
, 0, NULL
);
404 purple_debug(PURPLE_DEBUG_INFO
, "Session Management",
405 "Connection closed.\n");