1 /*
2 * Copyright (C) 2020 Purism SPC
3 * SPDX-License-Identifier: GPL-3.0+
4 * Author: Guido Günther <agx@sigxcpu.org>
5 */
6
7 #define G_LOG_DOMAIN "fbd-feedback-manager"
8
9 #include "lfb-names.h"
10 #include "fbd.h"
11 #include "fbd-dev-vibra.h"
12 #include "fbd-dev-leds.h"
13 #include "fbd-event.h"
14 #include "fbd-feedback-vibra.h"
15 #include "fbd-feedback-manager.h"
16 #include "fbd-feedback-theme.h"
17
18 #include <gio/gio.h>
19 #include <glib-unix.h>
20 #include <gudev/gudev.h>
21
22 #define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd"
23 #define FEEDBACKD_KEY_PROFILE "profile"
24 #define FEEDBACKD_THEME_VAR "FEEDBACK_THEME"
25
26 #define APP_SCHEMA FEEDBACKD_SCHEMA_ID ".application"
27 #define APP_PREFIX "/org/sigxcpu/feedbackd/application/"
28
29 #define DEVICE_TREE_PATH "/sys/firmware/devicetree/base/compatible"
30 #define DEVICE_NAME_MAX 1024
31
32
33 /**
34 * SECTION:fbd-feedback-manager
35 * @short_description: The manager processing incoming events
36 * @Title: FbdFeedbackManager
37 *
38 * The #FbdFeedbackManager listens for DBus messages and triggers feedbacks
39 * based on the incoming events.
40 */
41
42 typedef struct _FbdFeedbackManager {
43 LfbGdbusFeedbackSkeleton parent;
44
45 GSettings *settings;
46 FbdFeedbackProfileLevel level;
47 FbdFeedbackTheme *theme;
48 guint next_id;
49
50 /* Key: event id, value: event */
51 GHashTable *events;
52 /* Key: DBus name, value: watch_id */
53 GHashTable *clients;
54
55 /* Hardware interaction */
56 GUdevClient *client;
57 FbdDevVibra *vibra;
58 FbdDevSound *sound;
59 FbdDevLeds *leds;
60 } FbdFeedbackManager;
61
62 static void fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface);
63
64 G_DEFINE_TYPE_WITH_CODE (FbdFeedbackManager,
65 fbd_feedback_manager,
66 LFB_GDBUS_TYPE_FEEDBACK_SKELETON,
67 G_IMPLEMENT_INTERFACE (
68 LFB_GDBUS_TYPE_FEEDBACK,
69 fbd_feedback_manager_feedback_iface_init));
70
71 static void
device_changes(FbdFeedbackManager * self,gchar * action,GUdevDevice * device,GUdevClient * client)72 device_changes (FbdFeedbackManager *self, gchar *action, GUdevDevice *device,
73 GUdevClient *client)
74 {
75 g_debug ("Device changes: action = %s, device = %s",
76 action, g_udev_device_get_sysfs_path (device));
77
78 if (g_strcmp0 (action, "remove") == 0 && self->vibra) {
79 GUdevDevice *dev = fbd_dev_vibra_get_device (self->vibra);
80
81 if (g_strcmp0 (g_udev_device_get_sysfs_path (dev),
82 g_udev_device_get_sysfs_path (device)) == 0) {
83 g_debug ("Vibra device %s got removed", g_udev_device_get_sysfs_path (dev));
84 g_clear_object (&self->vibra);
85 }
86 } else if (g_strcmp0 (action, "add") == 0) {
87 if (!g_strcmp0 (g_udev_device_get_property (device, "FEEDBACKD_TYPE"), "vibra")) {
88 g_autoptr (GError) err = NULL;
89
90 g_debug ("Found hotplugged vibra device at %s", g_udev_device_get_sysfs_path (device));
91 g_clear_object (&self->vibra);
92 self->vibra = fbd_dev_vibra_new (device, &err);
93 if (!self->vibra)
94 g_warning ("Failed to init vibra device: %s", err->message);
95 }
96 }
97 }
98
99 static gchar *
munge_app_id(const gchar * app_id)100 munge_app_id (const gchar *app_id)
101 {
102 gchar *id = g_strdup (app_id);
103 gint i;
104
105 g_strcanon (id,
106 "0123456789"
107 "abcdefghijklmnopqrstuvwxyz"
108 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
109 "-",
110 '-');
111 for (i = 0; id[i] != '\0'; i++)
112 id[i] = g_ascii_tolower (id[i]);
113
114 return id;
115 }
116
117 static FbdFeedbackProfileLevel
app_get_feedback_level(const gchar * app_id)118 app_get_feedback_level (const gchar *app_id)
119 {
120 g_autofree gchar *profile = NULL;
121 g_autofree gchar *munged_app_id = munge_app_id (app_id);
122 g_autofree gchar *path = g_strconcat (APP_PREFIX, munged_app_id, "/", NULL);
123 g_autoptr (GSettings) setting = g_settings_new_with_path (APP_SCHEMA, path);
124
125 profile = g_settings_get_string (setting, FEEDBACKD_KEY_PROFILE);
126 g_debug ("%s uses app profile %s", app_id, profile);
127 return fbd_feedback_profile_level (profile);
128 }
129
130 static void
init_devices(FbdFeedbackManager * self)131 init_devices (FbdFeedbackManager *self)
132 {
133 GList *l;
134 g_autolist (GUdevClient) devices = NULL;
135 g_autoptr(GError) err = NULL;
136
137 devices = g_udev_client_query_by_subsystem (self->client, "input");
138
139 for (l = devices; l != NULL; l = l->next) {
140 GUdevDevice *dev = l->data;
141
142 if (!g_strcmp0 (g_udev_device_get_property (dev, "FEEDBACKD_TYPE"), "vibra")) {
143 g_debug ("Found vibra device");
144 self->vibra = fbd_dev_vibra_new (dev, &err);
145 if (!self->vibra) {
146 g_warning ("Failed to init vibra device: %s", err->message);
147 g_clear_error (&err);
148 }
149 }
150 }
151 if (!self->vibra)
152 g_debug ("No vibra capable device found");
153
154 self->leds = fbd_dev_leds_new (&err);
155 if (!self->leds) {
156 g_debug ("Failed to init leds device: %s", err->message);
157 g_clear_error (&err);
158 }
159
160 self->sound = fbd_dev_sound_new (&err);
161 if (!self->sound) {
162 g_warning ("Failed to init sound device: %s", err->message);
163 g_clear_error (&err);
164 }
165 }
166
167 static void
on_event_feedbacks_ended(FbdFeedbackManager * self,FbdEvent * event)168 on_event_feedbacks_ended (FbdFeedbackManager *self, FbdEvent *event)
169 {
170 guint event_id;
171
172 g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
173 g_return_if_fail (FBD_IS_EVENT (event));
174
175 event_id = fbd_event_get_id (event);
176 event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
177 if (!event) {
178 g_warning ("Feedback ended for unknown event %d", event_id);
179 return;
180 }
181
182 g_return_if_fail (fbd_event_get_feedbacks_ended (event));
183
184 lfb_gdbus_feedback_emit_feedback_ended (LFB_GDBUS_FEEDBACK (self), event_id,
185 fbd_event_get_end_reason (event));
186
187 g_debug ("All feedbacks for event %d finished", event_id);
188 g_hash_table_remove (self->events, GUINT_TO_POINTER (event_id));
189 }
190
191 static void
on_profile_changed(FbdFeedbackManager * self,GParamSpec * psepc,gpointer unused)192 on_profile_changed (FbdFeedbackManager *self, GParamSpec *psepc, gpointer unused)
193 {
194 const gchar *pname;
195
196 g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
197
198 pname = lfb_gdbus_feedback_get_profile (LFB_GDBUS_FEEDBACK (self));
199
200 if (!fbd_feedback_manager_set_profile (self, pname))
201 g_warning ("Invalid profile '%s'", pname);
202
203 /* TODO: end running feedbacks that aren't allowed in new profile immediately */
204 }
205
206 static void
on_feedbackd_setting_changed(FbdFeedbackManager * self,const gchar * key,GSettings * settings)207 on_feedbackd_setting_changed (FbdFeedbackManager *self,
208 const gchar *key,
209 GSettings *settings)
210 {
211 g_autofree gchar *profile = NULL;
212
213 g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
214 g_return_if_fail (G_IS_SETTINGS (settings));
215 g_return_if_fail (!g_strcmp0 (key, FEEDBACKD_KEY_PROFILE));
216
217 profile = g_settings_get_string (settings, key);
218 fbd_feedback_manager_set_profile (self, profile);
219 }
220
221 static void
on_client_vanished(GDBusConnection * connection,const gchar * name,gpointer user_data)222 on_client_vanished (GDBusConnection *connection,
223 const gchar *name,
224 gpointer user_data)
225 {
226 FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (user_data);
227 GHashTableIter iter;
228 gpointer key, value;
229 FbdEvent *event;
230 GSList *l;
231 g_autoptr (GSList) events = NULL;
232
233 g_return_if_fail (name);
234
235 g_debug ("Client %s vanished", name);
236
237 /*
238 * Prepare a list of events to end feedback for so we don't modify
239 * the hash table in place when 'feedbacks-ended' fires.
240 */
241 g_hash_table_iter_init (&iter, self->events);
242 while (g_hash_table_iter_next (&iter, &key, &value)) {
243 event = FBD_EVENT (value);
244 if (!g_strcmp0 (fbd_event_get_sender (event), name))
245 events = g_slist_append (events, event);
246 }
247
248 for (l = events; l; l = l->next) {
249 event = l->data;
250 g_debug ("Ending event %s (%d) since %s vanished",
251 fbd_event_get_event (event),
252 fbd_event_get_id (event),
253 name);
254 fbd_event_end_feedbacks (event);
255 }
256
257 g_hash_table_remove (self->clients, name);
258 }
259
260 static void
watch_client(FbdFeedbackManager * self,GDBusMethodInvocation * invocation)261 watch_client (FbdFeedbackManager *self, GDBusMethodInvocation *invocation)
262 {
263 guint watch_id;
264 GDBusConnection *conn = g_dbus_method_invocation_get_connection (invocation);
265 const char *sender = g_dbus_method_invocation_get_sender (invocation);
266
267 watch_id = g_bus_watch_name_on_connection (conn,
268 sender,
269 G_BUS_NAME_WATCHER_FLAGS_NONE,
270 NULL,
271 on_client_vanished,
272 self,
273 NULL);
274 g_hash_table_insert (self->clients, g_strdup (sender), GUINT_TO_POINTER (watch_id));
275 }
276
277 static void
free_client_watch(gpointer data)278 free_client_watch (gpointer data)
279 {
280 guint watch_id = GPOINTER_TO_UINT (data);
281
282 if (watch_id == 0)
283 return;
284 g_bus_unwatch_name (watch_id);
285 }
286
287 static FbdFeedbackProfileLevel
get_max_level(FbdFeedbackProfileLevel global_level,FbdFeedbackProfileLevel app_level,FbdFeedbackProfileLevel event_level)288 get_max_level (FbdFeedbackProfileLevel global_level,
289 FbdFeedbackProfileLevel app_level,
290 FbdFeedbackProfileLevel event_level)
291 {
292 FbdFeedbackProfileLevel level;
293
294 /* Individual events and apps can select lower levels than the global level but not higher ones */
295 level = global_level > app_level ? app_level : global_level;
296 level = level > event_level ? event_level : level;
297 return level;
298 }
299
300 static gboolean
parse_hints(GVariant * hints,FbdFeedbackProfileLevel * level)301 parse_hints (GVariant *hints, FbdFeedbackProfileLevel *level)
302 {
303 const gchar *profile;
304 gboolean found;
305 g_auto (GVariantDict) dict = G_VARIANT_DICT_INIT (NULL);
306
307 g_variant_dict_init (&dict, hints);
308 found = g_variant_dict_lookup (&dict, "profile", "&s", &profile);
309
310 if (level && found)
311 *level = fbd_feedback_profile_level (profile);
312 return TRUE;
313 }
314
315 static gboolean
fbd_feedback_manager_handle_trigger_feedback(LfbGdbusFeedback * object,GDBusMethodInvocation * invocation,const gchar * arg_app_id,const gchar * arg_event,GVariant * arg_hints,gint arg_timeout)316 fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
317 GDBusMethodInvocation *invocation,
318 const gchar *arg_app_id,
319 const gchar *arg_event,
320 GVariant *arg_hints,
321 gint arg_timeout)
322 {
323 FbdFeedbackManager *self;
324 FbdEvent *event;
325 GSList *feedbacks, *l;
326 gint event_id;
327 const gchar *sender;
328 FbdFeedbackProfileLevel app_level, level, hint_level = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
329 gboolean found_fb = FALSE;
330
331 sender = g_dbus_method_invocation_get_sender (invocation);
332 g_debug ("Event '%s' for '%s' from %s", arg_event, arg_app_id, sender);
333
334 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
335 g_return_val_if_fail (arg_app_id, FALSE);
336 g_return_val_if_fail (arg_event, FALSE);
337
338 self = FBD_FEEDBACK_MANAGER (object);
339 if (!strlen (arg_app_id)) {
340 g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
341 G_DBUS_ERROR_INVALID_ARGS,
342 "Invalid app id %s", arg_app_id);
343 return TRUE;
344 }
345
346 if (!strlen (arg_event)) {
347 g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
348 G_DBUS_ERROR_INVALID_ARGS,
349 "Invalid event %s", arg_event);
350 return TRUE;
351 }
352
353 if (!parse_hints (arg_hints, &hint_level)) {
354 g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
355 G_DBUS_ERROR_INVALID_ARGS,
356 "Invalid hints");
357 return TRUE;
358 }
359
360 if (arg_timeout < -1)
361 arg_timeout = -1;
362
363 event_id = self->next_id++;
364
365 event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout, sender);
366 g_hash_table_insert (self->events, GUINT_TO_POINTER (event_id), event);
367
368 app_level = app_get_feedback_level (arg_app_id);
369 level = get_max_level (self->level, app_level, hint_level);
370
371 feedbacks = fbd_feedback_theme_lookup_feedback (self->theme, level, event);
372 if (feedbacks) {
373 for (l = feedbacks; l; l = l->next) {
374 FbdFeedbackBase *fb = l->data;
375
376 if (fbd_feedback_is_available (FBD_FEEDBACK_BASE (fb))) {
377 fbd_event_add_feedback (event, fb);
378 found_fb = TRUE;
379 }
380 }
381 g_slist_free_full (feedbacks, g_object_unref);
382 } else {
383 /* No feedbacks found at all */
384 found_fb = FALSE;
385 }
386
387 if (found_fb) {
388 g_signal_connect_object (event, "feedbacks-ended",
389 (GCallback) on_event_feedbacks_ended,
390 self,
391 G_CONNECT_SWAPPED);
392 fbd_event_run_feedbacks (event);
393 watch_client (self, invocation);
394 } else {
395 g_hash_table_remove (self->events, GUINT_TO_POINTER (event_id));
396 lfb_gdbus_feedback_emit_feedback_ended (LFB_GDBUS_FEEDBACK (self), event_id,
397 FBD_EVENT_END_REASON_NOT_FOUND);
398 }
399
400 lfb_gdbus_feedback_complete_trigger_feedback (object, invocation, event_id);
401 return TRUE;
402 }
403
404 static gboolean
fbd_feedback_manager_handle_end_feedback(LfbGdbusFeedback * object,GDBusMethodInvocation * invocation,guint event_id)405 fbd_feedback_manager_handle_end_feedback (LfbGdbusFeedback *object,
406 GDBusMethodInvocation *invocation,
407 guint event_id)
408 {
409 FbdFeedbackManager *self;
410 FbdEvent *event;
411
412 g_debug ("Ending feedback for event '%d'", event_id);
413
414 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
415 g_return_val_if_fail (event_id, FALSE);
416
417 self = FBD_FEEDBACK_MANAGER (object);
418
419 event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
420 if (event) {
421 /* The last feedback ending will trigger event disposal via
422 `on_fb_ended` */
423 fbd_event_end_feedbacks (event);
424 } else {
425 g_warning ("Tried to end non-existing event %d", event_id);
426 }
427
428 lfb_gdbus_feedback_complete_end_feedback (object, invocation);
429 return TRUE;
430 }
431
432 static const gchar *
find_themefile(void)433 find_themefile (void)
434 {
435 gint i = 0;
436 gsize len;
437 const gchar *comp;
438
439 g_autoptr (GError) err = NULL;
440 g_autofree gchar *user_config_path = NULL;
441 gchar **xdg_data_dirs = (gchar **) g_get_system_data_dirs ();
442 g_autofree gchar *compatibles = NULL;
443
444 // First look for a default file under $XDG_DATA_HOME
445 user_config_path = g_build_filename (g_get_user_config_dir (), "feedbackd",
446 "themes", "default.json", NULL);
447 if (g_file_test (user_config_path, (G_FILE_TEST_EXISTS))) {
448 g_debug ("Found user themefile at: %s", user_config_path);
449 return g_steal_pointer (&user_config_path);
450 }
451
452 // Try to read the device name
453 if (g_file_test (DEVICE_TREE_PATH, (G_FILE_TEST_EXISTS))) {
454 g_debug ("Found device tree device compatible at %s", DEVICE_TREE_PATH);
455
456 // Check if feedbackd has a proper config available this device
457 if (!g_file_get_contents (DEVICE_TREE_PATH, &compatibles, &len, &err))
458 g_warning ("Unable to read: %s", err->message);
459
460 comp = compatibles;
461 while (comp - compatibles < len) {
462
463 // Iterate over $XDG_DATA_DIRS
464 for (i = 0; i < g_strv_length (xdg_data_dirs); i++) {
465 g_autofree gchar *config_path = NULL;
466 g_autofree gchar *theme_file_name = NULL;
467
468 // We leave it to g_build_filename to add/remove erroneous path separators
469 theme_file_name = g_strconcat (comp, ".json", NULL);
470 config_path = g_build_filename (xdg_data_dirs[i], "feedbackd", "themes",
471 theme_file_name, NULL);
472 g_debug ("Searching for device specific themefile in %s", config_path);
473
474 // Check if file exist
475 if (g_file_test (config_path, (G_FILE_TEST_EXISTS))) {
476 g_debug ("Found themefile for this device at: %s", config_path);
477 return g_steal_pointer (&config_path);
478 }
479 }
480
481 // Next compatible
482 comp = strchr (comp, 0);
483 comp++;
484 }
485 }else {
486 g_debug ("Device tree path does not exist: %s", DEVICE_TREE_PATH);
487 }
488
489 return NULL;
490 }
491
492 static void
fbd_feedback_manager_constructed(GObject * object)493 fbd_feedback_manager_constructed (GObject *object)
494 {
495 FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
496
497 G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->constructed (object);
498
499 fbd_feedback_manager_load_theme(self);
500
501 g_signal_connect (self, "notify::profile", (GCallback)on_profile_changed, NULL);
502 lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self),
503 fbd_feedback_profile_level_to_string (self->level));
504
505 self->settings = g_settings_new (FEEDBACKD_SCHEMA_ID);
506 g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_PROFILE,
507 G_CALLBACK (on_feedbackd_setting_changed), self);
508 }
509
510 static void
fbd_feedback_manager_dispose(GObject * object)511 fbd_feedback_manager_dispose (GObject *object)
512 {
513 FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
514
515 g_clear_object (&self->settings);
516 g_clear_object (&self->theme);
517 g_clear_object (&self->sound);
518 g_clear_object (&self->vibra);
519 g_clear_object (&self->leds);
520 g_clear_object (&self->client);
521 g_clear_pointer (&self->events, g_hash_table_destroy);
522 g_clear_pointer (&self->clients, g_hash_table_destroy);
523
524 G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->dispose (object);
525 }
526
527 static void
fbd_feedback_manager_feedback_iface_init(LfbGdbusFeedbackIface * iface)528 fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface)
529 {
530 iface->handle_trigger_feedback = fbd_feedback_manager_handle_trigger_feedback;
531 iface->handle_end_feedback = fbd_feedback_manager_handle_end_feedback;
532 }
533
534 static void
fbd_feedback_manager_class_init(FbdFeedbackManagerClass * klass)535 fbd_feedback_manager_class_init (FbdFeedbackManagerClass *klass)
536 {
537 GObjectClass *object_class = G_OBJECT_CLASS (klass);
538
539 object_class->constructed = fbd_feedback_manager_constructed;
540 object_class->dispose = fbd_feedback_manager_dispose;
541 }
542
543 static void
fbd_feedback_manager_init(FbdFeedbackManager * self)544 fbd_feedback_manager_init (FbdFeedbackManager *self)
545 {
546 const gchar * const subsystems[] = { "input", NULL };
547
548 self->next_id = 1;
549 self->level = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
550
551 self->client = g_udev_client_new (subsystems);
552 g_signal_connect_swapped (G_OBJECT (self->client), "uevent",
553 G_CALLBACK (device_changes), self);
554 init_devices (self);
555
556 self->events = g_hash_table_new_full (g_direct_hash,
557 g_direct_equal,
558 NULL,
559 (GDestroyNotify)g_object_unref);
560 self->clients = g_hash_table_new_full (g_str_hash,
561 g_str_equal,
562 g_free,
563 free_client_watch);
564 }
565
566 FbdFeedbackManager *
fbd_feedback_manager_get_default(void)567 fbd_feedback_manager_get_default (void)
568 {
569 static FbdFeedbackManager *instance;
570
571 if (instance == NULL) {
572 instance = g_object_new (FBD_TYPE_FEEDBACK_MANAGER, NULL);
573 g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
574 }
575 return instance;
576 }
577
578 FbdDevVibra *
fbd_feedback_manager_get_dev_vibra(FbdFeedbackManager * self)579 fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self)
580 {
581 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
582
583 return self->vibra;
584 }
585
586 FbdDevSound *
fbd_feedback_manager_get_dev_sound(FbdFeedbackManager * self)587 fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self)
588 {
589 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
590
591 return self->sound;
592 }
593
594 FbdDevLeds *
fbd_feedback_manager_get_dev_leds(FbdFeedbackManager * self)595 fbd_feedback_manager_get_dev_leds (FbdFeedbackManager *self)
596 {
597 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
598
599 return self->leds;
600 }
601
fbd_feedback_manager_load_theme(FbdFeedbackManager * self)602 void fbd_feedback_manager_load_theme (FbdFeedbackManager *self) {
603 g_autoptr (GError) err = NULL;
604 g_autoptr (FbdFeedbackTheme) theme = NULL;
605 const gchar *themefile;
606
607 // Overide themefile with environment variable if requested
608 themefile = g_getenv (FEEDBACKD_THEME_VAR);
609
610 // Search for device-specific configuration
611 if (!themefile)
612 themefile = find_themefile ();
613
614 // Fallback to default configuration if needed
615 if (!themefile)
616 themefile = FEEDBACKD_THEME_DIR "/default.json";
617 g_info ("Using themefile: %s", themefile);
618
619 theme = fbd_feedback_theme_new_from_file (themefile, &err);
620 if (theme) {
621 g_set_object(&self->theme, theme);
622 } else {
623 if (self->theme)
624 g_warning ("Failed to reload theme: %s", err->message);
625 else
626 g_error ("Failed to load theme: %s", err->message); // No point to carry on
627 }
628 }
629
630 gboolean
fbd_feedback_manager_set_profile(FbdFeedbackManager * self,const gchar * profile)631 fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile)
632 {
633 FbdFeedbackProfileLevel level;
634
635 g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), FALSE);
636
637 level = fbd_feedback_profile_level (profile);
638
639 if (level == FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN)
640 return FALSE;
641
642 if (level == self->level)
643 return TRUE;
644
645 g_debug ("Switching profile to '%s'", profile);
646 self->level = level;
647 lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self), profile);
648 g_settings_set_string (self->settings, FEEDBACKD_KEY_PROFILE, profile);
649 return TRUE;
650 }
651