Recursively load themes

Themes (other than the default theme) can specify a `parent-name` in the
json file. This theme will be used as a base for the theme. Events and
profiles of the children override those in the parents.

For this we move the lookup code into it's separate class and also add
tests.

Closes: #38
This commit is contained in:
Guido Günther
2022-12-11 17:34:38 +01:00
parent 34f8733b2b
commit 242519498a
11 changed files with 629 additions and 81 deletions
+20 -78
View File
@@ -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 <gmobile.h>
#include <gio/gio.h>
#include <glib-unix.h>
@@ -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
}
}
+400
View File
@@ -0,0 +1,400 @@
/*
* Copyright (C) 2022 Guido Günther
*
* SPDX-License-Identifier: GPL-3.0+
*
* Author: Guido Günther <agx@sigxcpu.org>
*/
#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;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2022 Guido Günther
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Author: Guido Günther <agx@sigxcpu.org>
*/
#pragma once
#include "fbd-feedback-theme.h"
#include <glib-object.h>
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
+1
View File
@@ -13,6 +13,7 @@ G_BEGIN_DECLS
typedef enum {
FBD_ERROR_FAILED = 0,
FBD_ERROR_THEME_EXPAND = 1,
} FbdError;
GQuark fbd_error_quark (void);
+1
View File
@@ -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',
]
@@ -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
}
]
}
]
}
@@ -0,0 +1,4 @@
{
"name" : "$device",
"parent-name": "default"
}
@@ -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"
}
]
}
]
}
@@ -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
}
]
}
]
}
+6 -2
View File
@@ -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,6 +52,9 @@ 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')),
@@ -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
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 Guido Günther <agx@sigxcpu.org>
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include "fbd-feedback-dummy.h"
#include "fbd-theme-expander.h"
#include <json-glib/json-glib.h>
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();
}