diff --git a/src/fbd-feedback-manager.c b/src/fbd-feedback-manager.c index e37cb8d..7872132 100644 --- a/src/fbd-feedback-manager.c +++ b/src/fbd-feedback-manager.c @@ -14,6 +14,10 @@ #include "fbd-feedback-vibra.h" #include "fbd-feedback-manager.h" #include "fbd-feedback-theme.h" +#include "fbd-theme-expander.h" + +#define GMOBILE_USE_UNSTABLE_API +#include #include #include @@ -21,14 +25,11 @@ #define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd" #define FEEDBACKD_KEY_PROFILE "profile" -#define FEEDBACKD_THEME_VAR "FEEDBACK_THEME" #define APP_SCHEMA FEEDBACKD_SCHEMA_ID ".application" #define APP_PREFIX "/org/sigxcpu/feedbackd/application/" -#define DEVICE_TREE_PATH "/sys/firmware/devicetree/base/compatible" -#define DEVICE_NAME_MAX 1024 - +#define FEEDBACKD_THEME_VAR "FEEDBACK_THEME" /** * SECTION:fbd-feedback-manager @@ -430,65 +431,6 @@ fbd_feedback_manager_handle_end_feedback (LfbGdbusFeedback *object, return TRUE; } -static const gchar * -find_themefile (void) -{ - gint i = 0; - gsize len; - const gchar *comp; - - g_autoptr (GError) err = NULL; - g_autofree gchar *user_config_path = NULL; - gchar **xdg_data_dirs = (gchar **) g_get_system_data_dirs (); - g_autofree gchar *compatibles = NULL; - - // First look for a default file under $XDG_DATA_HOME - user_config_path = g_build_filename (g_get_user_config_dir (), "feedbackd", - "themes", "default.json", NULL); - if (g_file_test (user_config_path, (G_FILE_TEST_EXISTS))) { - g_debug ("Found user themefile at: %s", user_config_path); - return g_steal_pointer (&user_config_path); - } - - // Try to read the device name - if (g_file_test (DEVICE_TREE_PATH, (G_FILE_TEST_EXISTS))) { - g_debug ("Found device tree device compatible at %s", DEVICE_TREE_PATH); - - // Check if feedbackd has a proper config available this device - if (!g_file_get_contents (DEVICE_TREE_PATH, &compatibles, &len, &err)) - g_warning ("Unable to read: %s", err->message); - - comp = compatibles; - while (comp - compatibles < len) { - - // Iterate over $XDG_DATA_DIRS - for (i = 0; i < g_strv_length (xdg_data_dirs); i++) { - g_autofree gchar *config_path = NULL; - g_autofree gchar *theme_file_name = NULL; - - // We leave it to g_build_filename to add/remove erroneous path separators - theme_file_name = g_strconcat (comp, ".json", NULL); - config_path = g_build_filename (xdg_data_dirs[i], "feedbackd", "themes", - theme_file_name, NULL); - g_debug ("Searching for device specific themefile in %s", config_path); - - // Check if file exist - if (g_file_test (config_path, (G_FILE_TEST_EXISTS))) { - g_debug ("Found themefile for this device at: %s", config_path); - return g_steal_pointer (&config_path); - } - } - - // Next compatible - comp = strchr (comp, 0); - comp++; - } - }else { - g_debug ("Device tree path does not exist: %s", DEVICE_TREE_PATH); - } - - return NULL; -} static void fbd_feedback_manager_constructed (GObject *object) @@ -599,31 +541,31 @@ fbd_feedback_manager_get_dev_leds (FbdFeedbackManager *self) return self->leds; } -void fbd_feedback_manager_load_theme (FbdFeedbackManager *self) { - g_autoptr (GError) err = NULL; +void +fbd_feedback_manager_load_theme (FbdFeedbackManager *self) +{ + g_autoptr (FbdThemeExpander) expander = NULL; g_autoptr (FbdFeedbackTheme) theme = NULL; - const gchar *themefile; + g_autoptr (GError) err = NULL; + g_auto (GStrv) compatibles = NULL; - // Overide themefile with environment variable if requested - themefile = g_getenv (FEEDBACKD_THEME_VAR); + compatibles = gm_devicetree_get_compatibles (NULL, &err); + if (compatibles == NULL && err) { + g_debug ("Failed to get compatibles: %s", err->message); + g_clear_error (&err); + } - // Search for device-specific configuration - if (!themefile) - themefile = find_themefile (); + expander = fbd_theme_expander_new ((const char *const *)compatibles, + g_getenv (FEEDBACKD_THEME_VAR)); + theme = fbd_theme_expander_load_theme_files (expander, &err); - // Fallback to default configuration if needed - if (!themefile) - themefile = FEEDBACKD_THEME_DIR "/default.json"; - g_info ("Using themefile: %s", themefile); - - theme = fbd_feedback_theme_new_from_file (themefile, &err); if (theme) { g_set_object(&self->theme, theme); } else { if (self->theme) g_warning ("Failed to reload theme: %s", err->message); else - g_error ("Failed to load theme: %s", err->message); // No point to carry on + g_error ("Failed to load any theme: %s", err->message); // No point to carry on } } diff --git a/src/fbd-theme-expander.c b/src/fbd-theme-expander.c new file mode 100644 index 0000000..69a9cea --- /dev/null +++ b/src/fbd-theme-expander.c @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2022 Guido Günther + * + * SPDX-License-Identifier: GPL-3.0+ + * + * Author: Guido Günther + */ + +#define G_LOG_DOMAIN "fbd-theme-expander" + +#include "fbd.h" +#include "fbd-feedback-theme.h" +#include "fbd-theme-expander.h" + +#define DEVICE_TREE_PATH "/sys/firmware/devicetree/base/compatible" +#define DEVICE_NAME_MAX 1024 + +#define DEFAULT_THEME_NAME "default" +#define DEVICE_THEME_NAME "$device" + +#define MAX_THEME_DEPTH 10 + +/** + * SECTION:theme-expander + * @short_description: Feedback theme expander + * @Title: FbdThemeExpander + * + * The theme expander reads themes from disks and expands references + * to other themes + */ + +enum { + PROP_0, + PROP_THEME_FILE, + PROP_COMPATIBLES, + PROP_LAST_PROP +}; +static GParamSpec *props[PROP_LAST_PROP]; + +struct _FbdThemeExpander { + GObject parent; + + char *theme_file; + gboolean device_theme_loaded; + GStrv compatibles; +}; +G_DEFINE_TYPE (FbdThemeExpander, fbd_theme_expander, G_TYPE_OBJECT) + + +static void +fbd_theme_expander_set_compatibles (FbdThemeExpander *self, const char * const * compatibles) +{ + g_return_if_fail (FBD_IS_THEME_EXPANDER (self)); + + g_strfreev (self->compatibles); + self->compatibles = g_strdupv ((GStrv)compatibles); + + /* Make sure we reload the device theme */ + self->device_theme_loaded = FALSE; +} + + +static void +fbd_theme_expander_set_theme_file (FbdThemeExpander *self, const char *theme_file) +{ + g_return_if_fail (FBD_IS_THEME_EXPANDER (self)); + + g_free (self->theme_file); + self->theme_file = g_strdup (theme_file); + + /* Make sure we reload the device theme */ + self->device_theme_loaded = FALSE; +} + + +static char * +fbd_theme_expander_find_theme_in_xdg_data (const char *theme_name) +{ + const char * const *xdg_data_dirs = g_get_system_data_dirs (); + g_autofree char *theme_file_name = g_strconcat (theme_name, ".json", NULL); + + for (int i = 0; xdg_data_dirs[i] != NULL; i++) { + g_autofree char *theme_path = NULL; + + theme_path = g_build_filename (xdg_data_dirs[i], "feedbackd", "themes", + theme_file_name, NULL); + g_debug ("Looking for theme file at %s", theme_path); + + /* Check if file exist */ + if (g_file_test (theme_path, (G_FILE_TEST_EXISTS))) { + g_info ("Loading theme file at '%s'", theme_path); + return g_steal_pointer (&theme_path); + } + } + return NULL; +} + + +static char * +fbd_theme_expander_find_device_theme_path (FbdThemeExpander *self, const char *theme_name) +{ + /* Device specific lookup only for default theme */ + if (g_str_equal (theme_name, DEVICE_THEME_NAME) == FALSE && + g_str_equal (theme_name, DEFAULT_THEME_NAME) == FALSE) { + return NULL; + } + + if (self->device_theme_loaded) + return NULL; + + self->device_theme_loaded = TRUE; + + /* no compatibles found */ + if (self->compatibles == NULL) + return NULL; + + for (int i = 0; i < g_strv_length (self->compatibles); i++) { + const char *compatible = self->compatibles[i]; + g_autofree char *theme_path = NULL; + + theme_path = fbd_theme_expander_find_theme_in_xdg_data (compatible); + if (theme_path) { + g_info ("Loading themefile for compatible '%s' at: %s", compatible, theme_path); + return g_steal_pointer (&theme_path); + } + } + + g_debug ("No device theme found"); + return NULL; +} + + +static char * +fbd_theme_expander_find_user_theme_path (const char *theme_name) +{ + g_autofree char *filename = g_strdup_printf ("%s.json", theme_name); + g_autofree char *user_config_path = NULL; + + user_config_path = g_build_filename (g_get_user_config_dir (), "feedbackd", + "themes", filename, NULL); + if (g_file_test (user_config_path, (G_FILE_TEST_EXISTS))) { + g_info ("Found theme file at: %s", user_config_path); + return g_steal_pointer (&user_config_path); + } + + g_debug ("No user theme found for '%s'", theme_name); + return NULL; +} + + +static char * +fbd_theme_expander_find_theme_path (FbdThemeExpander *self, const char *theme_name) +{ + g_autofree char *file_name = NULL; + char *theme_path; + + g_assert (theme_name); + + theme_path = fbd_theme_expander_find_user_theme_path (theme_name); + if (theme_path) + return theme_path; + + theme_path = fbd_theme_expander_find_device_theme_path (self, theme_name); + if (theme_path) + return theme_path; + + if (g_str_equal (theme_name, DEFAULT_THEME_NAME) == FALSE) + g_critical ("Theme '%s' not found, falling back to default theme", theme_name); + + theme_path = fbd_theme_expander_find_theme_in_xdg_data (DEFAULT_THEME_NAME); + if (theme_path) + return theme_path; + + /* Last resort is shipped default config */ + file_name = g_strdup_printf ("%s.json", theme_name); + g_info ("Using theme_file: %s", file_name); + return g_build_filename (FEEDBACKD_THEME_DIR, file_name, NULL); +} + + +static void +fbd_theme_expander_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + FbdThemeExpander *self = FBD_THEME_EXPANDER (object); + + switch (property_id) { + case PROP_THEME_FILE: + fbd_theme_expander_set_theme_file (self, g_value_get_string (value)); + break; + case PROP_COMPATIBLES: + fbd_theme_expander_set_compatibles (self, g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +fbd_theme_expander_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + FbdThemeExpander *self = FBD_THEME_EXPANDER (object); + + switch (property_id) { + case PROP_THEME_FILE: + g_value_set_string (value, fbd_theme_expander_get_theme_file (self)); + break; + case PROP_COMPATIBLES: + g_value_set_boxed (value, fbd_theme_expander_get_compatibles (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +fbd_theme_expander_finalize (GObject *object) +{ + FbdThemeExpander *self = FBD_THEME_EXPANDER(object); + + g_clear_pointer (&self->theme_file, g_free); + g_clear_pointer (&self->compatibles, g_strfreev); + + G_OBJECT_CLASS (fbd_theme_expander_parent_class)->finalize (object); +} + + +static void +fbd_theme_expander_class_init (FbdThemeExpanderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = fbd_theme_expander_get_property; + object_class->set_property = fbd_theme_expander_set_property; + object_class->finalize = fbd_theme_expander_finalize; + + /** + * FbdThemeExpander:theme-file: + * + * Specifies the theme-file to load an expand. Takes preference over `theme`. + */ + props[PROP_THEME_FILE] = + g_param_spec_string ("theme-file", "", "", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_STATIC_STRINGS); + /** + * FbdThemeExpander:compatibles: + * + * Specifies the device types this device is compatible with. The + * device compatbiles to look for. When unset the value is retrieved + * from /sysfs. + * + * The `compatibles` defines which device specific theme bits are loaded. + */ + props[PROP_COMPATIBLES] = + g_param_spec_boxed ("compatibles", "", "", + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +fbd_theme_expander_init (FbdThemeExpander *self) +{ +} + + +FbdThemeExpander * +fbd_theme_expander_new (const char * const *compatibles, const char *theme_file) +{ + return FBD_THEME_EXPANDER (g_object_new (FBD_TYPE_THEME_EXPANDER, + "theme-file", theme_file, + "compatibles", compatibles, + NULL)); +} + + +static void +update_theme (gpointer data, gpointer user_data) +{ + FbdFeedbackTheme *theme = FBD_FEEDBACK_THEME (data); + FbdFeedbackTheme *merged = FBD_FEEDBACK_THEME (user_data); + + g_assert (FBD_IS_FEEDBACK_THEME (theme)); + g_assert (FBD_IS_FEEDBACK_THEME (merged)); + + fbd_feedback_theme_update (merged, theme); +} + + +/** + * fbd_theme_expander_load_theme_files: + * @self: The theme expander + * @err: return location for error or %NULL + * + * Parses a theme recursively taking the theme's `parent-name` relations into + * account as well as the expander's list of `compatibles`. + * + * Returns: (transfer full)(allow-none): The parsed theme or %NULL on error + */ +FbdFeedbackTheme * +fbd_theme_expander_load_theme_files (FbdThemeExpander *self, GError **err) +{ + g_autoqueue (FbdFeedbackTheme) queue = g_queue_new (); + g_autoptr (FbdFeedbackTheme) merged = fbd_feedback_theme_new ("merged-theme"); + g_autoptr (FbdFeedbackTheme) theme = NULL; + g_autofree char *theme_file = NULL; + guint len = 0; + + g_return_val_if_fail (FBD_IS_THEME_EXPANDER (self), NULL); + g_return_val_if_fail (err == NULL || *err == NULL, NULL); + + if (self->theme_file) + theme_file = g_strdup (self->theme_file); + else { + theme_file = fbd_theme_expander_find_theme_path (self, DEFAULT_THEME_NAME); + if (g_strcmp0 (self->theme_file, theme_file)) { + self->theme_file = g_steal_pointer (&theme_file); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_THEME_FILE]); + } + } + + theme = fbd_feedback_theme_new_from_file (self->theme_file, err); + if (theme == NULL) + return NULL; + + /* Build a list of themes */ + while (TRUE) { + g_autofree char *parent_path = NULL; + const char *parent_name, *theme_name; + + if (len > MAX_THEME_DEPTH) { + g_set_error (err, fbd_error_quark(), FBD_ERROR_THEME_EXPAND, "Theme depth exceeded"); + return NULL; + } + + theme_name = fbd_feedback_theme_get_name (theme); + if (theme_name == NULL || theme_name[0] == '\0') { + g_set_error (err, fbd_error_quark(), FBD_ERROR_THEME_EXPAND, + "Theme name of %s can't be empty", self->theme_file); + return NULL; + } + + parent_name = fbd_feedback_theme_get_parent_name (theme); + + if (parent_name && + g_str_equal (fbd_feedback_theme_get_name (theme), DEFAULT_THEME_NAME)) { + g_set_error (err, fbd_error_quark(), FBD_ERROR_THEME_EXPAND, + "Default theme can't specify a parent"); + return NULL; + } + + parent_name = fbd_feedback_theme_get_parent_name (theme); + + g_queue_push_head (queue, g_steal_pointer (&theme)); + + if (parent_name == NULL) + break; + + parent_path = fbd_theme_expander_find_theme_path (self, parent_name); + theme = fbd_feedback_theme_new_from_file (parent_path, err); + if (theme == NULL) + return NULL; + + len++; + } + + /* Merge themes bottom to top */ + g_queue_foreach (queue, update_theme, merged); + + return g_steal_pointer (&merged); +} + +const char * +fbd_theme_expander_get_theme_file (FbdThemeExpander *self) +{ + g_return_val_if_fail (FBD_IS_THEME_EXPANDER (self), NULL); + + return self->theme_file; +} + +const char * const * +fbd_theme_expander_get_compatibles (FbdThemeExpander *self) +{ + g_return_val_if_fail (FBD_IS_THEME_EXPANDER (self), NULL); + + return (const char * const*)self->compatibles; +} diff --git a/src/fbd-theme-expander.h b/src/fbd-theme-expander.h new file mode 100644 index 0000000..8d2db79 --- /dev/null +++ b/src/fbd-theme-expander.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Guido Günther + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author: Guido Günther + */ + +#pragma once + +#include "fbd-feedback-theme.h" + +#include + +G_BEGIN_DECLS + +#define FBD_TYPE_THEME_EXPANDER (fbd_theme_expander_get_type ()) + +G_DECLARE_FINAL_TYPE (FbdThemeExpander, fbd_theme_expander, FBD, THEME_EXPANDER, GObject) + +FbdThemeExpander *fbd_theme_expander_new (const char * const *compatibles, + const char *theme_file); +FbdFeedbackTheme *fbd_theme_expander_load_theme_files (FbdThemeExpander *self, + GError **err); +const char *fbd_theme_expander_get_theme_file (FbdThemeExpander *self); +const char * const *fbd_theme_expander_get_compatibles (FbdThemeExpander *self); + +G_END_DECLS diff --git a/src/fbd.h b/src/fbd.h index c5a1e12..82da4e2 100644 --- a/src/fbd.h +++ b/src/fbd.h @@ -13,6 +13,7 @@ G_BEGIN_DECLS typedef enum { FBD_ERROR_FAILED = 0, + FBD_ERROR_THEME_EXPAND = 1, } FbdError; GQuark fbd_error_quark (void); diff --git a/src/meson.build b/src/meson.build index cb507de..4a2db09 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,7 @@ sources = [ 'fbd-feedback-vibra.c', 'fbd-feedback-vibra-periodic.c', 'fbd-feedback-vibra-rumble.c', + 'fbd-theme-expander.c', 'fbd-udev.c', ] diff --git a/tests/data/user-config/feedbackd/themes/custom.json b/tests/data/user-config/feedbackd/themes/custom.json new file mode 100644 index 0000000..d208710 --- /dev/null +++ b/tests/data/user-config/feedbackd/themes/custom.json @@ -0,0 +1,21 @@ +{ + "name" : "custom", + "parent-name": "default", + "profiles" : [ + { + "name" : "full", + "feedbacks" : [ + { + "event-name" : "test-dummy-0", + "type" : "Dummy", + "duration" : 0x10 + }, + { + "event-name" : "test-dummy-2", + "type" : "Dummy", + "duration" : 0x30 + } + ] + } + ] +} diff --git a/tests/data/xdg-data/feedbackd/themes/chainup.json b/tests/data/xdg-data/feedbackd/themes/chainup.json new file mode 100644 index 0000000..e0b3278 --- /dev/null +++ b/tests/data/xdg-data/feedbackd/themes/chainup.json @@ -0,0 +1,4 @@ +{ + "name" : "$device", + "parent-name": "default" +} diff --git a/tests/data/xdg-data/feedbackd/themes/default.json b/tests/data/xdg-data/feedbackd/themes/default.json new file mode 100644 index 0000000..0b5d87d --- /dev/null +++ b/tests/data/xdg-data/feedbackd/themes/default.json @@ -0,0 +1,31 @@ +{ + "name" : "default", + "profiles" : [ + { + "name" : "full", + "feedbacks" : [ + { + "event-name" : "test-dummy-0", + "type" : "Dummy" + }, + { + "event-name" : "test-dummy-1", + "type" : "Dummy" + } + ] + }, + { + "name" : "quiet", + "feedbacks" : [ + { + "event-name" : "test-dummy-0", + "type" : "Dummy" + }, + { + "event-name" : "test-dummy-1", + "type" : "Dummy" + } + ] + } + ] +} diff --git a/tests/data/xdg-data/feedbackd/themes/replace.json b/tests/data/xdg-data/feedbackd/themes/replace.json new file mode 100644 index 0000000..1fc0e26 --- /dev/null +++ b/tests/data/xdg-data/feedbackd/themes/replace.json @@ -0,0 +1,21 @@ +{ + "name" : "$device", + "parent-name": "default", + "profiles" : [ + { + "name" : "full", + "feedbacks" : [ + { + "event-name" : "test-dummy-0", + "type" : "Dummy", + "duration" : 0x10 + }, + { + "event-name" : "test-dummy-2", + "type" : "Dummy", + "duration" : 0x20 + } + ] + } + ] +} diff --git a/tests/meson.build b/tests/meson.build index 85b1d7e..c776cf0 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -11,7 +11,7 @@ test_env.set('MALLOC_CHECK_', '2') test_env.set('XDG_CONFIG_HOME', meson.current_source_dir()) test_env.set('XDG_CONFIG_DIRS', meson.current_source_dir()) -# Shared lfbrary Unit tests +# Shared library Unit tests test_lfb_cflags = [ '-DTEST_APP_ID="org.sigxcpu.feedbackd_test"', @@ -52,10 +52,13 @@ endforeach # Daemon Unit tests if get_option('daemon') +test_env_fbd = test_env +test_env_fbd.set('XDG_DATA_DIRS', meson.current_source_dir() / 'data' / 'xdg-data') + test_fbd_cflags = [ '-DTEST_APP_ID="org.sigxcpu.feedbackd_test"', '-DTEST_DATA_DIR="@0@"'.format(join_paths(meson.current_source_dir(), 'data')), -] + ] test_fbd_link_args = [ '-fPIC', @@ -69,6 +72,7 @@ fbd_tests = [ 'fbd-feedback-profile', 'fbd-feedback-theme', 'fbd-event', + 'fbd-theme-expander', ] foreach test : fbd_tests @@ -79,7 +83,7 @@ foreach test : fbd_tests link_args : test_fbd_link_args, include_directories : fbd_inc, dependencies : test_fbd_deps) - test(test, t, env : test_env) + test(test, t, env : test_env_fbd) endforeach diff --git a/tests/test-fbd-theme-expander.c b/tests/test-fbd-theme-expander.c new file mode 100644 index 0000000..1bde3cd --- /dev/null +++ b/tests/test-fbd-theme-expander.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 Guido Günther + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include "fbd-feedback-dummy.h" +#include "fbd-theme-expander.h" + +#include + + +static void +test_fbd_theme_expander_object (void) +{ + g_autoptr (FbdThemeExpander) expander = g_object_new (FBD_TYPE_THEME_EXPANDER, NULL); + + g_assert_null (fbd_theme_expander_get_theme_file (expander)); + g_assert_null (fbd_theme_expander_get_compatibles (expander)); +} + +static void +test_fbd_theme_expander_device (void) +{ + g_autoptr (GError) err = NULL; + const char *compatibles[] = { NULL, NULL }; + FbdFeedbackProfile *profile; + FbdThemeExpander *expander; + FbdFeedbackTheme *theme; + FbdFeedbackBase *fb; + const char *const *compatibles2; + + /* just the default profile */ + compatibles[0] = "doesnotexist"; + expander = fbd_theme_expander_new (compatibles, NULL); + theme = fbd_theme_expander_load_theme_files (expander, &err); + compatibles2 = fbd_theme_expander_get_compatibles (expander); + g_assert_cmpstrv (compatibles2, compatibles); + g_assert_no_error (err); + profile = fbd_feedback_theme_get_profile (theme, "silent"); + g_assert_null (profile); + profile = fbd_feedback_theme_get_profile (theme, "quiet"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + profile = fbd_feedback_theme_get_profile (theme, "full"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + g_assert_finalize_object (theme); + g_assert_finalize_object (expander); + + /* Empty device theme that just chains up */ + compatibles[0] = "chainup"; + expander = fbd_theme_expander_new (compatibles, NULL); + compatibles2 = fbd_theme_expander_get_compatibles (expander); + g_assert_cmpstrv (compatibles2, compatibles); + theme = fbd_theme_expander_load_theme_files (expander, &err); + g_assert_no_error (err); + profile = fbd_feedback_theme_get_profile (theme, "silent"); + g_assert_null (profile); + profile = fbd_feedback_theme_get_profile (theme, "quiet"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + profile = fbd_feedback_theme_get_profile (theme, "full"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + g_assert_finalize_object (theme); + g_assert_finalize_object (expander); + + /* Replace an existing event */ + compatibles[0] = "replace"; + expander = fbd_theme_expander_new (compatibles, NULL); + compatibles2 = fbd_theme_expander_get_compatibles (expander); + g_assert_cmpstrv (compatibles2, compatibles); + theme = fbd_theme_expander_load_theme_files (expander, &err); + g_assert_no_error (err); + profile = fbd_feedback_theme_get_profile (theme, "full"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-0"); + g_assert_cmpstr ("test-dummy-0", ==, fbd_feedback_get_event_name (fb)); + g_assert_cmpint (fbd_feedback_dummy_get_duration (FBD_FEEDBACK_DUMMY (fb)), ==, 0x10); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-2"); + g_assert_cmpstr ("test-dummy-2", ==, fbd_feedback_get_event_name (fb)); + g_assert_cmpint (fbd_feedback_dummy_get_duration (FBD_FEEDBACK_DUMMY (fb)), ==, 0x20); + g_assert_finalize_object (theme); + g_assert_finalize_object (expander); +} + + +gint +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func("/feedbackd/fbd/theme-expander/object", test_fbd_theme_expander_object); + g_test_add_func("/feedbackd/fbd/theme-expander/device", test_fbd_theme_expander_device); + + return g_test_run(); +}