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