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