1816645d513a8bc93aa11555023d61c32fcfa502
[lxde/lxsession.git] / lxsession / lxsession.c
1 /*
2 * lxsession.c
3 *
4 * Copyright 2008 PCMan <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <glib.h>
28
29 #include <unistd.h>
30 #include <signal.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <string.h>
34
35 #include "autostart.h"
36
37 typedef struct _ChildWatch
38 {
39 GPid pid;
40 gboolean exited;
41 int status;
42 char* cmd;
43 }ChildWatch;
44
45 #define child_watch_free( cw ) \
46 { \
47 g_free( cw->cmd ); \
48 g_free( cw ); \
49 }
50
51 static GMainLoop* main_loop = NULL;
52 static const char *display_name = NULL;
53 static char* window_manager = NULL;
54
55 /* name of environment variables */
56 static char sm_env[] = "SESSION_MANAGER";
57 static char display_env[] = "DISPLAY";
58 static char pid_env[] = "_LXSESSION_PID";
59
60 static char prog_name[]="lxsession";
61 static char config_filename[]="config";
62 static char autostart_filename[]="autostart";
63
64 const char *session_name = NULL;
65
66 static GSList* child_watches = NULL;
67 static int wakeup_pipe[ 2 ];
68
69 static void sig_term_handler ( int sig )
70 {
71 /* g_main_loop_quit(main_loop); */
72 close( wakeup_pipe[0] );
73 }
74
75 static void sig_child_handler( int sig )
76 {
77 /* notify the main loop that a child process exits */
78 write( wakeup_pipe[1], "X", 1 );
79 }
80
81 static void register_signals()
82 {
83 /* Ignore SIGPIPE */
84 signal( SIGPIPE, SIG_IGN );
85
86 /* If child process dies, call our handler */
87 signal( SIGCHLD, sig_child_handler );
88
89 #if 0
90 action.sa_handler = g_child_watch_signal_handler;
91 sigemptyset (&action.sa_mask);
92 action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
93 sigaction (SIGCHLD, &action, NULL);
94 #endif
95
96 /* If we get a SIGTERM, do logout */
97 signal( SIGTERM, sig_term_handler );
98 }
99
100 static void load_config( GKeyFile* kf, const char* filename )
101 {
102 if( g_key_file_load_from_file( kf, filename, 0, NULL ) )
103 {
104 char* str;
105 if( str = g_key_file_get_string( kf, "Session", "window_manager", NULL ) )
106 {
107 g_free( window_manager );
108 window_manager = str;
109 }
110 }
111 }
112
113 /* Returns pid if succesful, returns -1 if errors happen. */
114 static GPid run_app( const char* cmd )
115 {
116 char** argv;
117 int argc;
118 GPid pid = -1;
119 if( g_shell_parse_argv( cmd, &argc, &argv, NULL ) )
120 {
121 g_spawn_async( NULL, argv, NULL,
122 G_SPAWN_DO_NOT_REAP_CHILD|
123 G_SPAWN_SEARCH_PATH|
124 G_SPAWN_STDOUT_TO_DEV_NULL|
125 G_SPAWN_STDERR_TO_DEV_NULL,
126 NULL, NULL, &pid, NULL );
127 }
128 g_strfreev( argv );
129 return pid;
130 }
131
132 static void on_child_exit( ChildWatch* cw )
133 {
134 int sig = WTERMSIG( cw->status );
135 /* if the term signal is not SIGTERM or SIGKILL, this might be a crash! */
136 if( sig && sig != SIGTERM && sig != SIGKILL )
137 {
138 GPid pid = run_app( cw->cmd );
139 if( pid < 0 ) /* error, remove the watch */
140 {
141 child_watches = g_slist_remove( child_watches, cw );
142 child_watch_free( cw );
143 }
144 else
145 {
146 cw->pid = pid;
147 cw->exited = FALSE;
148 cw->status = 0;
149 }
150 }
151 }
152
153 static void add_child_watch( GPid pid, const char* cmd )
154 {
155 ChildWatch* cw = g_new0( ChildWatch, 1 );
156 cw->pid = pid;
157 cw->cmd = g_strdup( cmd );
158 child_watches = g_slist_prepend( child_watches, cw );
159 }
160
161 static void run_guarded_app( const char* cmd )
162 {
163 GPid pid = run_app( cmd );
164 if( pid > 0 )
165 {
166 add_child_watch( pid, cmd );
167 /*
168 g_child_watch_add_full( G_PRIORITY_DEFAULT_IDLE, pid,
169 (GChildWatchFunc)on_child_exit,
170 g_strdup( cmd ), (GDestroyNotify)g_free );
171 */
172 }
173 }
174
175 static void load_default_apps( const char* filename )
176 {
177 char buf[1024];
178 int len;
179 FILE* file = fopen( filename, "r" );
180 if( file )
181 {
182 while ( fgets( buf, sizeof(buf) - 2, file ) )
183 {
184 if ( buf[0] == '\0' || buf[0] == '\n' || buf[0] == '#' )
185 continue; /* a comment */
186 len = strlen ( buf );
187 if( buf[ len - 1 ] == '\n' ) /* remove the '\n' at the end of line */
188 {
189 buf[ len ] = '\0';
190 --len;
191 }
192 if( buf[0] == '@' ) /* if the app should be restarted on crash */
193 run_guarded_app( buf + 1 );
194 else
195 g_spawn_command_line_async( buf, NULL );
196 }
197 fclose( file );
198 }
199 }
200
201 /*
202 * system wide default config is /etc/xdg/lxsession/SESSION_NAME/config
203 * system wide default apps are listed in /etc/xdg/lxsession/SESSION_NAME/autostart
204 */
205 static void start_session()
206 {
207 FILE *file = NULL;
208 const gchar* const *dirs = g_get_system_config_dirs();
209 const gchar* const *dir;
210 GKeyFile* kf = g_key_file_new();
211 char* filename;
212
213 /* load system-wide session config files */
214 for( dir = dirs; *dir; ++dir )
215 {
216 filename = g_build_filename( *dir, prog_name, session_name, config_filename, NULL );
217 load_config( kf, filename );
218 g_free( filename );
219 }
220 /* load user-specific session config */
221 filename = g_build_filename( g_get_user_config_dir(), prog_name, session_name, config_filename, NULL );
222 load_config( kf, filename );
223 g_free( filename );
224
225 g_key_file_free( kf );
226
227 /* run window manager first */
228 if( G_LIKELY( window_manager ) )
229 run_guarded_app( window_manager );
230
231 /* load system-wide default apps */
232 for( dir = dirs; *dir; ++dir )
233 {
234 filename = g_build_filename( *dir, prog_name, session_name, autostart_filename, NULL );
235 load_default_apps( filename );
236 g_free( filename );
237 }
238 /* load user-specific default apps */
239 filename = g_build_filename( g_get_user_config_dir(), prog_name, session_name, autostart_filename, NULL );
240 load_default_apps( filename );
241 g_free( filename );
242
243 /* Support autostart spec of freedesktop.org */
244 handle_autostart( session_name );
245 }
246
247 static void dispatch_child_watches()
248 {
249 GSList* l;
250 ChildWatch* cw;
251 int status;
252
253 for( l = child_watches; l; l = l->next )
254 {
255 cw = (ChildWatch*)l->data;
256 if (waitpid (cw->pid, &status, WNOHANG) > 0)
257 {
258 cw->status = status;
259 cw->exited = TRUE;
260 on_child_exit( cw );
261 }
262 }
263 }
264
265 int main(int argc, char** argv)
266 {
267 const char *pid_str;
268 char str[ 16 ];
269 int i;
270
271 pid_str = g_getenv(pid_env);
272
273 display_name = g_getenv( display_env );
274 if( ! display_name )
275 {
276 display_name = ":0";
277 g_setenv( display_env, display_name, TRUE );
278 }
279
280 for ( i = 1; i < argc; ++i )
281 {
282 if ( argv[i][0] == '-' )
283 {
284 switch ( argv[i][1] )
285 {
286 case 'd': /* -display */
287 if ( ++i >= argc ) goto usage;
288 display_name = argv[i];
289 g_setenv( display_env, display_name, TRUE );
290 continue;
291
292 case 's': /* -session */
293 if ( ++i >= argc ) goto usage;
294 session_name = argv[i];
295 continue;
296
297 case 'e':
298 if( 0 == strcmp( argv[i]+1, "exit" ) )
299 {
300 if( pid_str ) /* _LXSESSION_PID has been set */
301 {
302 GPid pid = atoi( pid_str );
303 kill( pid, SIGTERM );
304 return 0;
305 }
306 else
307 {
308 g_print( "Error: LXSession is not running.\n" );
309 return 1;
310 }
311 }
312 }
313 }
314
315 usage:
316 fprintf ( stderr,
317 "Usage: lxsession [-display display] [-session session_name] [-exit]\n" );
318 return 1;
319 }
320
321 if( G_UNLIKELY( pid_str ) ) /* _LXSESSION_PID has been set */
322 {
323 g_error("LXSession is already running\n");
324 return 1;
325 }
326
327 if( pipe( wakeup_pipe ) < 0 )
328 return 1;
329
330 /*
331 * NOTE:
332 * g_main_loop has some weird problems when child watch is the only event source.
333 * It seems that in this situation glib does busy loop. (Not yet fully confirmed.)
334 * To prevent this, I handle this myself with pipe and don't use main loop provided by glib.
335 */
336 /* main_loop = g_main_loop_new( NULL, TRUE ); */
337
338 g_snprintf( str, 16, "%d", getpid() );
339 g_setenv(pid_env, str, TRUE );
340
341 register_signals();
342
343 if ( !session_name )
344 session_name = "LXDE";
345
346 g_setenv( "DESKTOP_SESSION", session_name, TRUE );
347
348 start_session();
349
350 /*
351 g_main_loop_run( main_loop );
352 g_main_loop_unref( main_loop );
353 */
354
355 while( read( wakeup_pipe[0], str, 16 ) > 0 )
356 dispatch_child_watches();
357
358 /* close( wakeup_pipe[0] ); */
359 close( wakeup_pipe[1] );
360
361 return 0;
362 }