1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2006, 2010 Novell, Inc.
4 * Copyright (C) 2008 Red Hat, Inc.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser 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 - Suite 500, Boston, MA
19 * 02110-1335, USA.
20 */
21
22 #include <config.h>
23
24 #include "csm-session-fill.h"
25
26 #include "csm-system.h"
27 #include "csm-manager.h"
28 #include "csm-process-helper.h"
29 #include "csm-util.h"
30
31 #define CSM_KEYFILE_SESSION_GROUP "Cinnamon Session"
32 #define CSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper"
33 #define CSM_KEYFILE_FALLBACK_KEY "FallbackSession"
34 #define CSM_KEYFILE_DESKTOP_NAME_KEY "DesktopName"
35 #define CSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents"
36 #define CSM_KEYFILE_REQUIRED_PROVIDERS_KEY "RequiredProviders"
37 #define CSM_KEYFILE_DEFAULT_PROVIDER_PREFIX "DefaultProvider"
38
39 /* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */
40 #define CSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */
41
42 typedef void (*CsmFillHandleProvider) (const char *provides,
43 const char *default_provider,
44 const char *app_path,
45 gpointer user_data);
46 typedef void (*CsmFillHandleComponent) (const char *component,
47 const char *app_path,
48 gpointer user_data);
49
50 static void
handle_default_providers(GKeyFile * keyfile,gboolean look_in_saved_session,CsmFillHandleProvider callback,gpointer user_data)51 handle_default_providers (GKeyFile *keyfile,
52 gboolean look_in_saved_session,
53 CsmFillHandleProvider callback,
54 gpointer user_data)
55 {
56 char **default_providers;
57 int i;
58
59 g_assert (keyfile != NULL);
60 g_assert (callback != NULL);
61
62 default_providers = g_key_file_get_string_list (keyfile,
63 CSM_KEYFILE_SESSION_GROUP,
64 CSM_KEYFILE_REQUIRED_PROVIDERS_KEY,
65 NULL, NULL);
66
67 if (!default_providers)
68 return;
69
70 for (i = 0; default_providers[i] != NULL; i++) {
71 char *key;
72 char *value;
73 char *app_path;
74
75 if (IS_STRING_EMPTY (default_providers[i]))
76 continue;
77
78 key = g_strdup_printf ("%s-%s",
79 CSM_KEYFILE_DEFAULT_PROVIDER_PREFIX,
80 default_providers[i]);
81 value = g_key_file_get_string (keyfile,
82 CSM_KEYFILE_SESSION_GROUP, key,
83 NULL);
84 g_free (key);
85
86 if (IS_STRING_EMPTY (value)) {
87 g_free (value);
88 continue;
89 }
90
91 g_debug ("fill: provider '%s' looking for component: '%s'",
92 default_providers[i], value);
93 app_path = csm_util_find_desktop_file_for_app_name (value,
94 look_in_saved_session, TRUE);
95
96 callback (default_providers[i], value, app_path, user_data);
97 g_free (app_path);
98
99 g_free (value);
100 }
101
102 g_strfreev (default_providers);
103 }
104
105 static void
handle_required_components(GKeyFile * keyfile,gboolean look_in_saved_session,CsmFillHandleComponent callback,gpointer user_data)106 handle_required_components (GKeyFile *keyfile,
107 gboolean look_in_saved_session,
108 CsmFillHandleComponent callback,
109 gpointer user_data)
110 {
111 char **required_components;
112 int i;
113
114 g_assert (keyfile != NULL);
115 g_assert (callback != NULL);
116
117 required_components = g_key_file_get_string_list (keyfile,
118 CSM_KEYFILE_SESSION_GROUP,
119 CSM_KEYFILE_REQUIRED_COMPONENTS_KEY,
120 NULL, NULL);
121
122 if (!required_components)
123 return;
124
125 for (i = 0; required_components[i] != NULL; i++) {
126 char *app_path;
127
128 app_path = csm_util_find_desktop_file_for_app_name (required_components[i],
129 look_in_saved_session, TRUE);
130 callback (required_components[i], app_path, user_data);
131 g_free (app_path);
132 }
133
134 g_strfreev (required_components);
135 }
136
137 static void
check_required_providers_helper(const char * provides,const char * default_provider,const char * app_path,gpointer user_data)138 check_required_providers_helper (const char *provides,
139 const char *default_provider,
140 const char *app_path,
141 gpointer user_data)
142 {
143 gboolean *error = user_data;
144
145 if (app_path == NULL) {
146 g_warning ("Unable to find default provider '%s' of required provider '%s'",
147 default_provider, provides);
148 *error = TRUE;
149 }
150 }
151
152 static void
check_required_components_helper(const char * component,const char * app_path,gpointer user_data)153 check_required_components_helper (const char *component,
154 const char *app_path,
155 gpointer user_data)
156 {
157 gboolean *error = user_data;
158
159 if (app_path == NULL) {
160 g_warning ("Unable to find required component '%s'", component);
161 *error = TRUE;
162 }
163 }
164
165 static gboolean
check_required(GKeyFile * keyfile)166 check_required (GKeyFile *keyfile)
167 {
168 gboolean error = FALSE;
169
170 g_debug ("fill: *** Checking required components and providers");
171
172 handle_default_providers (keyfile, FALSE,
173 check_required_providers_helper, &error);
174 handle_required_components (keyfile, FALSE,
175 check_required_components_helper, &error);
176
177 g_debug ("fill: *** Done checking required components and providers");
178
179 return !error;
180 }
181
182 static void
maybe_load_saved_session_apps(CsmManager * manager)183 maybe_load_saved_session_apps (CsmManager *manager)
184 {
185 CsmSystem *system;
186 gboolean is_login;
187
188 system = csm_get_system ();
189 is_login = csm_system_is_login_session (system);
190 g_object_unref (system);
191
192 // if (is_login)
193 // return;
194
195 csm_manager_add_autostart_apps_from_dir (manager, csm_util_get_saved_session_dir ());
196 }
197
198 static void
append_required_providers_helper(const char * provides,const char * default_provider,const char * app_path,gpointer user_data)199 append_required_providers_helper (const char *provides,
200 const char *default_provider,
201 const char *app_path,
202 gpointer user_data)
203 {
204 CsmManager *manager = user_data;
205
206 if (app_path == NULL)
207 g_warning ("Unable to find default provider '%s' of required provider '%s'",
208 default_provider, provides);
209 else
210 csm_manager_add_required_app (manager, app_path, provides);
211 }
212
213 static void
append_required_components_helper(const char * component,const char * app_path,gpointer user_data)214 append_required_components_helper (const char *component,
215 const char *app_path,
216 gpointer user_data)
217 {
218 CsmManager *manager = user_data;
219
220 if (app_path == NULL)
221 g_warning ("Unable to find required component '%s'", component);
222 else
223 csm_manager_add_required_app (manager, app_path, NULL);
224 }
225
226
227 static void
load_standard_apps(CsmManager * manager,GKeyFile * keyfile)228 load_standard_apps (CsmManager *manager,
229 GKeyFile *keyfile)
230 {
231
232 if (g_file_test ("/usr/bin/session-migration", G_FILE_TEST_EXISTS)) {
233 GError *error;
234 g_debug ("fill: *** Executing user migration");
235 error = NULL;
236 if(!g_spawn_command_line_sync ("session-migration", NULL, NULL, NULL, &error)) {
237 g_warning ("Error while executing session-migration: %s", error->message);
238 g_error_free (error);
239 }
240 }
241
242 g_debug ("fill: *** Adding required components");
243 handle_required_components (keyfile, !csm_manager_get_failsafe (manager),
244 append_required_components_helper, manager);
245 g_debug ("fill: *** Done adding required components");
246
247 if (!csm_manager_get_failsafe (manager)) {
248 char **autostart_dirs;
249 int i;
250
251 autostart_dirs = csm_util_get_autostart_dirs ();
252
253 if (csm_manager_get_autosave_enabled (manager))
254 maybe_load_saved_session_apps (manager);
255
256 for (i = 0; autostart_dirs[i]; i++) {
257 csm_manager_add_autostart_apps_from_dir (manager,
258 autostart_dirs[i]);
259 }
260
261 g_strfreev (autostart_dirs);
262 }
263
264 g_debug ("fill: *** Adding default providers");
265 handle_default_providers (keyfile, !csm_manager_get_failsafe (manager),
266 append_required_providers_helper, manager);
267 g_debug ("fill: *** Done adding default providers");
268 }
269
270 static GKeyFile *
get_session_keyfile_if_valid(const char * path)271 get_session_keyfile_if_valid (const char *path)
272 {
273 GKeyFile *keyfile;
274 gsize len;
275 char **list;
276
277 g_debug ("fill: *** Looking if %s is a valid session file", path);
278
279 keyfile = g_key_file_new ();
280
281 if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) {
282 g_debug ("Cannot use session '%s': non-existing or invalid file.", path);
283 goto error;
284 }
285
286 if (!g_key_file_has_group (keyfile, CSM_KEYFILE_SESSION_GROUP)) {
287 g_warning ("Cannot use session '%s': no '%s' group.", path, CSM_KEYFILE_SESSION_GROUP);
288 goto error;
289 }
290
291 /* check that we have default providers defined for required providers */
292 list = g_key_file_get_string_list (keyfile,
293 CSM_KEYFILE_SESSION_GROUP,
294 CSM_KEYFILE_REQUIRED_PROVIDERS_KEY,
295 &len, NULL);
296 if (list != NULL) {
297 int i;
298 char *key;
299 char *value;
300
301 for (i = 0; list[i] != NULL; i++) {
302 key = g_strdup_printf ("%s-%s", CSM_KEYFILE_DEFAULT_PROVIDER_PREFIX, list[i]);
303 value = g_key_file_get_string (keyfile,
304 CSM_KEYFILE_SESSION_GROUP, key,
305 NULL);
306 g_free (key);
307
308 if (IS_STRING_EMPTY (value)) {
309 g_free (value);
310 break;
311 }
312
313 g_free (value);
314 }
315
316 if (list[i] != NULL) {
317 g_warning ("Cannot use session '%s': required provider '%s' is not defined.", path, list[i]);
318 g_strfreev (list);
319 goto error;
320 }
321
322 g_strfreev (list);
323 }
324
325 /* we don't want an empty session, so if there's no required provider, check
326 * that we do have some required components */
327 if (len == 0) {
328 list = g_key_file_get_string_list (keyfile,
329 CSM_KEYFILE_SESSION_GROUP,
330 CSM_KEYFILE_REQUIRED_COMPONENTS_KEY,
331 &len, NULL);
332 if (list)
333 g_strfreev (list);
334 if (len == 0) {
335 g_warning ("Cannot use session '%s': no component in the session.", path);
336 goto error;
337 }
338 }
339
340 return keyfile;
341
342 error:
343 g_key_file_free (keyfile);
344 return NULL;
345 }
346
347 /**
348 * find_valid_session_keyfile:
349 * @session: name of session
350 *
351 * We look for the session file in XDG_CONFIG_HOME, XDG_CONFIG_DIRS and
352 * XDG_DATA_DIRS. This enables users and sysadmins to override a specific
353 * session that is shipped in XDG_DATA_DIRS.
354 */
355 static GKeyFile *
find_valid_session_keyfile(const char * session)356 find_valid_session_keyfile (const char *session)
357 {
358 GPtrArray *dirs;
359 const char * const *system_config_dirs;
360 const char * const *system_data_dirs;
361 int i;
362 GKeyFile *keyfile;
363 char *basename;
364 char *path;
365
366 dirs = g_ptr_array_new ();
367
368 g_ptr_array_add (dirs, (gpointer) g_get_user_config_dir ());
369
370 system_config_dirs = g_get_system_config_dirs ();
371 for (i = 0; system_config_dirs[i]; i++)
372 g_ptr_array_add (dirs, (gpointer) system_config_dirs[i]);
373
374 system_data_dirs = g_get_system_data_dirs ();
375 for (i = 0; system_data_dirs[i]; i++)
376 g_ptr_array_add (dirs, (gpointer) system_data_dirs[i]);
377
378 keyfile = NULL;
379 basename = g_strdup_printf ("%s.session", session);
380 path = NULL;
381
382 for (i = 0; i < dirs->len; i++) {
383 path = g_build_filename (dirs->pdata[i], "cinnamon-session", "sessions", basename, NULL);
384 keyfile = get_session_keyfile_if_valid (path);
385 if (keyfile != NULL)
386 break;
387 }
388
389 if (dirs)
390 g_ptr_array_free (dirs, TRUE);
391 if (basename)
392 g_free (basename);
393 if (path)
394 g_free (path);
395
396 return keyfile;
397 }
398
399 static GKeyFile *
get_session_keyfile(const char * session,char ** actual_session,gboolean * is_fallback)400 get_session_keyfile (const char *session,
401 char **actual_session,
402 gboolean *is_fallback)
403 {
404 GKeyFile *keyfile;
405 gboolean session_runnable;
406 char *value;
407 GError *error = NULL;
408
409 *actual_session = NULL;
410
411 g_debug ("fill: *** Getting session '%s'", session);
412
413 keyfile = find_valid_session_keyfile (session);
414
415 if (!keyfile)
416 return NULL;
417
418 session_runnable = TRUE;
419
420 value = g_key_file_get_string (keyfile,
421 CSM_KEYFILE_SESSION_GROUP, CSM_KEYFILE_RUNNABLE_KEY,
422 NULL);
423 if (!IS_STRING_EMPTY (value)) {
424 g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value);
425 session_runnable = csm_process_helper (value, CSM_RUNNABLE_HELPER_TIMEOUT, &error);
426 if (!session_runnable) {
427 g_warning ("Session '%s' runnable check failed: %s", session,
428 error->message);
429 g_clear_error (&error);
430 }
431 }
432 g_free (value);
433
434 if (session_runnable) {
435 session_runnable = check_required (keyfile);
436 }
437
438 if (session_runnable) {
439 *actual_session = g_strdup (session);
440 if (is_fallback)
441 *is_fallback = FALSE;
442 return keyfile;
443 }
444
445 g_debug ("fill: *** Session is not runnable");
446
447 /* We can't run this session, so try to use the fallback */
448 value = g_key_file_get_string (keyfile,
449 CSM_KEYFILE_SESSION_GROUP, CSM_KEYFILE_FALLBACK_KEY,
450 NULL);
451
452 g_key_file_free (keyfile);
453 keyfile = NULL;
454
455 if (!IS_STRING_EMPTY (value)) {
456 if (is_fallback)
457 *is_fallback = TRUE;
458 keyfile = get_session_keyfile (value, actual_session, NULL);
459 }
460 g_free (value);
461
462 return keyfile;
463 }
464
465 static void
set_xdg_current_desktop(GKeyFile * keyfile)466 set_xdg_current_desktop (GKeyFile *keyfile)
467 {
468 char *value;
469
470 value = g_key_file_get_string (keyfile,
471 CSM_KEYFILE_SESSION_GROUP, CSM_KEYFILE_DESKTOP_NAME_KEY,
472 NULL);
473
474 if (!IS_STRING_EMPTY (value)) {
475 csm_util_setenv ("XDG_CURRENT_DESKTOP", value);
476 }
477 else {
478 csm_util_setenv ("XDG_CURRENT_DESKTOP", "GNOME");
479 }
480 g_free (value);
481 }
482
483 gboolean
csm_session_fill(CsmManager * manager,const char * session)484 csm_session_fill (CsmManager *manager,
485 const char *session)
486 {
487 GKeyFile *keyfile;
488 gboolean is_fallback;
489 char *actual_session;
490
491 keyfile = get_session_keyfile (session, &actual_session, &is_fallback);
492
493 if (!keyfile) {
494 g_free (actual_session);
495 return FALSE;
496 }
497
498 _csm_manager_set_active_session (manager, actual_session, is_fallback);
499
500 g_free (actual_session);
501
502 set_xdg_current_desktop (keyfile);
503
504 load_standard_apps (manager, keyfile);
505
506 g_key_file_free (keyfile);
507
508 return TRUE;
509 }
510