From 3e4f66993ed33a8f3c4ce6d7d259bfdda48de441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:08 +0100 Subject: [PATCH 01/22] dir-locals: Simlify consistent JS indenting --- .dir-locals.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dir-locals.el b/.dir-locals.el index 390f382..476ef0e 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -11,5 +11,9 @@ (css-mode . ( (css-indent-offset . 2) )) + (js-mode . ( + (indent-tabs-mode . nil) + (js-indent-level . 2) + )) ) From 3c124e8ecdc7a08709429da87cae5fa6fd5d4be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:10 +0100 Subject: [PATCH 02/22] feedback-vibra: Fix indentation --- src/fbd-feedback-vibra.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fbd-feedback-vibra.c b/src/fbd-feedback-vibra.c index 52b6d69..9c4555c 100644 --- a/src/fbd-feedback-vibra.c +++ b/src/fbd-feedback-vibra.c @@ -147,7 +147,8 @@ fbd_feedback_vibra_init (FbdFeedbackVibra *self) { } -guint fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self) +guint +fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self) { FbdFeedbackVibraPrivate *priv; From 96b6af3766e6e0d2f0e743a7047d9e151f9c8bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:10 +0100 Subject: [PATCH 03/22] tests: Test feedback theme name --- tests/test-fbd-feedback-theme.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test-fbd-feedback-theme.c b/tests/test-fbd-feedback-theme.c index 9b333f4..3e04551 100644 --- a/tests/test-fbd-feedback-theme.c +++ b/tests/test-fbd-feedback-theme.c @@ -97,10 +97,15 @@ test_fbd_feedback_theme_parse (void) g_autoptr (GError) err = NULL; g_autoptr (FbdFeedbackTheme) theme = NULL; FbdFeedbackProfile *profile; + const char *name; theme = fbd_feedback_theme_new_from_data (json, &err); g_assert_no_error (err); g_assert_nonnull (theme); + + name = fbd_feedback_theme_get_name (theme); + g_assert_cmpstr ("test", ==, name); + profile = fbd_feedback_theme_get_profile (theme, "full"); g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); g_assert_cmpstr ("full", ==, From c9404d871e8bd02508a00cbfbf0dc731b591c691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:11 +0100 Subject: [PATCH 04/22] feedback-theme: No need for constructed We can do all that in init --- src/fbd-feedback-theme.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index 013615e..ab2a42a 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -186,20 +186,6 @@ json_serializable_iface_init (JsonSerializableIface *iface) iface->deserialize_property = fbd_theme_serializable_deserialize_property; } -static void -fbd_feedback_theme_constructed (GObject *object) -{ - FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object); - - G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->constructed (object); - - self->profiles = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify)g_object_unref); -} - - static void fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass) { @@ -208,7 +194,6 @@ fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass) object_class->set_property = fbd_feedback_theme_set_property; object_class->get_property = fbd_feedback_theme_get_property; - object_class->constructed = fbd_feedback_theme_constructed; object_class->dispose = fbd_feedback_theme_dispose; object_class->finalize = fbd_feedback_theme_finalize; @@ -235,6 +220,10 @@ fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass) static void fbd_feedback_theme_init (FbdFeedbackTheme *self) { + self->profiles = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify)g_object_unref); } FbdFeedbackTheme * From e6f1c351e25becfc052d7dd385735e33d64c94cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:11 +0100 Subject: [PATCH 05/22] feedback-theme: Fix indentation --- src/fbd-feedback-theme.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index ab2a42a..5bd488b 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -115,9 +115,9 @@ fbd_theme_serializable_deserialize_property (JsonSerializable *serializable, static void fbd_feedback_theme_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) + guint property_id, + const GValue *value, + GParamSpec *pspec) { FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object); @@ -140,9 +140,9 @@ fbd_feedback_theme_set_property (GObject *object, static void fbd_feedback_theme_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) + guint property_id, + GValue *value, + GParamSpec *pspec) { FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object); From 1d21bfa0dcd959ee44ae43df407bb121d7a5d080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:12 +0100 Subject: [PATCH 06/22] feedback-theme: Use getter Ensures we stay consistent with future changes --- src/fbd-feedback-theme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index 5bd488b..ef75020 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -148,7 +148,7 @@ fbd_feedback_theme_get_property (GObject *object, switch (property_id) { case PROP_NAME: - g_value_set_string (value, self->name); + g_value_set_string (value, fbd_feedback_theme_get_name (self)); break; case PROP_PROFILES: g_value_set_boxed (value, self->profiles); From 82a8b91096b41158c8d681d8734f1b9a076d83bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:13 +0100 Subject: [PATCH 07/22] feedback-theme: Allow to set name --- src/fbd-feedback-theme.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index ef75020..6fe8109 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -123,8 +123,7 @@ fbd_feedback_theme_set_property (GObject *object, switch (property_id) { case PROP_NAME: - g_free (self->name); - self->name = g_value_dup_string (value); + fbd_feedback_theme_set_name (self, g_value_get_string (value)); break; case PROP_PROFILES: if (self->profiles) @@ -203,7 +202,7 @@ fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass) "Name", "The theme name", NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_PROFILES] = g_param_spec_boxed ( @@ -257,6 +256,20 @@ fbd_feedback_theme_new_from_file (const gchar *filename, GError **error) return fbd_feedback_theme_new_from_data (data, error); } +void +fbd_feedback_theme_set_name (FbdFeedbackTheme *self, const char *name) +{ + g_return_if_fail (FBD_IS_FEEDBACK_THEME (self)); + + if (g_strcmp0 (self->name, name) == 0) + return; + + g_free (self->name); + self->name = g_strdup (name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NAME]); +} + const char * fbd_feedback_theme_get_name (FbdFeedbackTheme *self) { From 53677e29efc4e5caee621a5e7b6356a56f1e1ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:13 +0100 Subject: [PATCH 08/22] feedback-profile: Document hash types --- src/fbd-feedback-profile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fbd-feedback-profile.c b/src/fbd-feedback-profile.c index 1ad4f34..3512256 100644 --- a/src/fbd-feedback-profile.c +++ b/src/fbd-feedback-profile.c @@ -35,7 +35,7 @@ typedef struct _FbdFeedbackProfile { GObject parent; gchar *name; - GHashTable *feedbacks; + GHashTable *feedbacks; /* key: event name, value: feedback */ } FbdFeedbackProfile; static void json_serializable_iface_init (JsonSerializableIface *iface); From 29bbe38fb38312fa6fb5f6bf6dfcc2a012a7a82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:14 +0100 Subject: [PATCH 09/22] feedback-dummy: Add getter for duration Useful in tests --- src/fbd-feedback-dummy.c | 8 ++++++++ src/fbd-feedback-dummy.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/fbd-feedback-dummy.c b/src/fbd-feedback-dummy.c index 7d405e7..f742898 100644 --- a/src/fbd-feedback-dummy.c +++ b/src/fbd-feedback-dummy.c @@ -126,3 +126,11 @@ static void fbd_feedback_dummy_init (FbdFeedbackDummy *self) { } + +guint +fbd_feedback_dummy_get_duration (FbdFeedbackDummy *self) +{ + g_return_val_if_fail (FBD_IS_FEEDBACK_DUMMY (self), 0); + + return self->duration; +} diff --git a/src/fbd-feedback-dummy.h b/src/fbd-feedback-dummy.h index 2df8328..7c39d1d 100644 --- a/src/fbd-feedback-dummy.h +++ b/src/fbd-feedback-dummy.h @@ -13,4 +13,6 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (FbdFeedbackDummy, fbd_feedback_dummy, FBD, FEEDBACK_DUMMY, FbdFeedbackBase); +guint fbd_feedback_dummy_get_duration (FbdFeedbackDummy *self); + G_END_DECLS From d7f97dca51d5458714f66dfe4cd84beb67ee1732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:14 +0100 Subject: [PATCH 10/22] feedback-profile: Allow to update profiles Allow to add feedbacks from a profile to another one --- src/fbd-feedback-profile.c | 30 +++++++++++++++++++++ src/fbd-feedback-profile.h | 2 ++ tests/test-fbd-feedback-profile.c | 44 +++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/src/fbd-feedback-profile.c b/src/fbd-feedback-profile.c index 3512256..28d6f29 100644 --- a/src/fbd-feedback-profile.c +++ b/src/fbd-feedback-profile.c @@ -341,3 +341,33 @@ fbd_feedback_profile_level_to_string (FbdFeedbackProfileLevel level) return "full"; } } + +/** + * fbd_profile_update: + * @self: The profile that should be updated + * @new: The profile theme we want to take new feedbacks from + * + * Updates a profile. Feedbacks are read from the `new` profile. If + * feedback already exists in `self` it is overwritten with the feedback + * from `new`. + * + * It is not allowed to update a profile with a profile of a different name. + */ +void +fbd_feedback_profile_update (FbdFeedbackProfile *self, FbdFeedbackProfile *new) +{ + GHashTableIter iter; + const char *event_name; + FbdFeedbackBase *fb; + + g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (self)); + g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (new)); + + g_return_if_fail (g_str_equal (fbd_feedback_profile_get_name (self), + fbd_feedback_profile_get_name (new))); + + g_hash_table_iter_init (&iter, new->feedbacks); + while (g_hash_table_iter_next (&iter, (gpointer)&event_name, (gpointer)&fb)) { + g_hash_table_insert (self->feedbacks, g_strdup (event_name), g_object_ref (fb)); + } +} diff --git a/src/fbd-feedback-profile.h b/src/fbd-feedback-profile.h index 9844172..b054368 100644 --- a/src/fbd-feedback-profile.h +++ b/src/fbd-feedback-profile.h @@ -24,6 +24,8 @@ typedef enum _FbdFeedbackProfileLevel { G_DECLARE_FINAL_TYPE (FbdFeedbackProfile, fbd_feedback_profile, FBD, FEEDBACK_PROFILE, GObject); FbdFeedbackProfile *fbd_feedback_profile_new (const gchar *name); +void fbd_feedback_profile_update (FbdFeedbackProfile *self, + FbdFeedbackProfile *new); const gchar *fbd_feedback_profile_get_name (FbdFeedbackProfile *self); void fbd_feedback_profile_add_feedback (FbdFeedbackProfile *self, FbdFeedbackBase *feedback); diff --git a/tests/test-fbd-feedback-profile.c b/tests/test-fbd-feedback-profile.c index bb7cc5c..96fd247 100644 --- a/tests/test-fbd-feedback-profile.c +++ b/tests/test-fbd-feedback-profile.c @@ -81,6 +81,49 @@ test_fbd_feedback_profile_parse (void) g_assert_true (FBD_IS_FEEDBACK_VIBRA(fb)); } + +static void +test_fbd_feedback_profile_update (void) +{ + FbdFeedbackBase *fb; + FbdFeedbackProfile *a = fbd_feedback_profile_new (PROFILE_NAME); + FbdFeedbackProfile *b = fbd_feedback_profile_new (PROFILE_NAME); + + g_autoptr (FbdFeedbackDummy) fb_a_1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, + "event-name", "a-1", + NULL); + g_autoptr (FbdFeedbackDummy) fb_a_2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, + "event-name", "a-2", + NULL); + /* New event */ + g_autoptr (FbdFeedbackDummy) fb_b_1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, + "event-name", "b-1", + NULL); + /* overwrite existing event */ + g_autoptr (FbdFeedbackDummy) fb_b_2 = g_object_new (FBD_TYPE_FEEDBACK_VIBRA, + "event-name", "a-2", + NULL); + + fbd_feedback_profile_add_feedback (a, FBD_FEEDBACK_BASE(fb_a_1)); + fbd_feedback_profile_add_feedback (a, FBD_FEEDBACK_BASE(fb_a_2)); + + fbd_feedback_profile_add_feedback (b, FBD_FEEDBACK_BASE(fb_b_1)); + fbd_feedback_profile_add_feedback (b, FBD_FEEDBACK_BASE(fb_b_2)); + + fbd_feedback_profile_update (a,b); + + fb = fbd_feedback_profile_get_feedback (a, "a-1"); + g_assert_true (FBD_IS_FEEDBACK_DUMMY(fb)); + fb = fbd_feedback_profile_get_feedback (a, "a-2"); + g_assert_true (FBD_IS_FEEDBACK_VIBRA(fb)); + fb = fbd_feedback_profile_get_feedback (a, "b-1"); + g_assert_true (FBD_IS_FEEDBACK_DUMMY(fb)); + + g_assert_finalize_object (b); + g_assert_finalize_object (a); +} + + gint main (gint argc, gchar *argv[]) { @@ -89,6 +132,7 @@ main (gint argc, gchar *argv[]) g_test_add_func("/feedbackd/fbd/feedback-profile/name", test_fbd_feedback_profile_name); g_test_add_func("/feedbackd/fbd/feedback-profile/feedbacks", test_fbd_feedback_profile_feedbacks); g_test_add_func("/feedbackd/fbd/feedback-profile/parse", test_fbd_feedback_profile_parse); + g_test_add_func("/feedbackd/fbd/feedback-profile/update", test_fbd_feedback_profile_update); return g_test_run(); } From e10b2cc150bb1afa94993ce1c7806819049b6ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:15 +0100 Subject: [PATCH 11/22] feedback-theme: Allow to update theme New profiles are added, existing profiles updated. This will allow to merge themes. --- src/fbd-feedback-theme.c | 38 +++++++++++++++++++ src/fbd-feedback-theme.h | 2 + tests/data/parent/base.json | 30 +++++++++++++++ tests/data/parent/test.json | 40 ++++++++++++++++++++ tests/meson.build | 3 +- tests/test-fbd-feedback-theme.c | 66 +++++++++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 tests/data/parent/base.json create mode 100644 tests/data/parent/test.json diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index 6fe8109..f6dd8e0 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -319,3 +319,41 @@ fbd_feedback_theme_lookup_feedback (FbdFeedbackTheme *self, g_debug ("No feedback for event %s", fbd_event_get_event (event)); return feedbacks; } + + +/** + * fbd_feedback_theme_update: + * @self: The feedback theme that should be updated + * @new: The feedback theme we want to take profiles and feedbacks new + * + * Merges two feedback themes. Feedbacks and profiles are read new + * the `new` theme. If feedback already exists in the same profile + * of `self` it is overwritten with the feedback in `new`. + */ +void +fbd_feedback_theme_update (FbdFeedbackTheme *self, FbdFeedbackTheme *new) +{ + GHashTableIter iter; + const char *profile_name; + FbdFeedbackProfile *profile; + + g_return_if_fail (FBD_IS_FEEDBACK_THEME (self)); + g_return_if_fail (FBD_IS_FEEDBACK_THEME (new)); + + fbd_feedback_theme_set_name (self, fbd_feedback_theme_get_name (new)); + + g_hash_table_iter_init (&iter, new->profiles); + while (g_hash_table_iter_next (&iter, (gpointer)&profile_name, (gpointer)&profile)) { + FbdFeedbackProfile *current; + + current = g_hash_table_lookup (self->profiles, profile_name); + if (current == NULL) { + current = fbd_feedback_profile_new (profile_name); + g_hash_table_insert (self->profiles, + g_strdup (profile_name), + current); + } + + fbd_feedback_profile_update (current, profile); + } +} diff --git a/src/fbd-feedback-theme.h b/src/fbd-feedback-theme.h index f9ed3ff..ddddc46 100644 --- a/src/fbd-feedback-theme.h +++ b/src/fbd-feedback-theme.h @@ -21,8 +21,10 @@ G_DECLARE_FINAL_TYPE (FbdFeedbackTheme, fbd_feedback_theme, FBD, FEEDBACK_THEME, FbdFeedbackTheme *fbd_feedback_theme_new (const char *name); FbdFeedbackTheme *fbd_feedback_theme_new_from_data (const gchar *data, GError **error); FbdFeedbackTheme *fbd_feedback_theme_new_from_file (const gchar *filename, GError **error); +void fbd_feedback_theme_update (FbdFeedbackTheme *self, FbdFeedbackTheme *from); const char *fbd_feedback_theme_get_name (FbdFeedbackTheme *self); +void fbd_feedback_theme_set_name (FbdFeedbackTheme *self, const char *name); void fbd_feedback_theme_add_profile (FbdFeedbackTheme *self, FbdFeedbackProfile *profile); FbdFeedbackProfile *fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name); diff --git a/tests/data/parent/base.json b/tests/data/parent/base.json new file mode 100644 index 0000000..650d625 --- /dev/null +++ b/tests/data/parent/base.json @@ -0,0 +1,30 @@ +{ + "name" : "base", + "profiles" : [ + { + "name" : "full", + "feedbacks" : [ + { + "type" : "Dummy", + "event-name" : "test-dummy-00", + "duration" : 0x01 + }, + { + "type" : "Dummy", + "event-name" : "test-dummy-01", + "duration" : 0x02 + } + ] + }, + { + "name" : "quiet", + "feedbacks" : [ + { + "type" : "Dummy", + "event-name" : "test-dummy-00", + "duration" : 0x03 + } + ] + } + ] +} diff --git a/tests/data/parent/test.json b/tests/data/parent/test.json new file mode 100644 index 0000000..60f863e --- /dev/null +++ b/tests/data/parent/test.json @@ -0,0 +1,40 @@ +{ + "name" : "test", + "profiles" : [ + { + "name" : "full", + "feedbacks" : [ + { + "type" : "Dummy", + "event-name" : "test-dummy-00", + "duration" : 0x10 + }, + { + "type" : "Dummy", + "event-name" : "test-dummy-10", + "duration" : 0x20 + } + ] + }, + { + "name" : "quiet", + "feedbacks" : [ + { + "type" : "Dummy", + "event-name" : "test-dummy-10", + "duration" : 0x30 + } + ] + }, + { + "name" : "silent", + "feedbacks" : [ + { + "type" : "Dummy", + "event-name" : "test-dummy-10", + "duration" : 0x40 + } + ] + } + ] +} diff --git a/tests/meson.build b/tests/meson.build index d9905f5..85b1d7e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -53,7 +53,8 @@ endforeach if get_option('daemon') test_fbd_cflags = [ - '-DTEST_APP_ID="org.sigxcpu.feedbackd_test"', + '-DTEST_APP_ID="org.sigxcpu.feedbackd_test"', + '-DTEST_DATA_DIR="@0@"'.format(join_paths(meson.current_source_dir(), 'data')), ] test_fbd_link_args = [ diff --git a/tests/test-fbd-feedback-theme.c b/tests/test-fbd-feedback-theme.c index 3e04551..421b4a2 100644 --- a/tests/test-fbd-feedback-theme.c +++ b/tests/test-fbd-feedback-theme.c @@ -116,6 +116,71 @@ test_fbd_feedback_theme_parse (void) fbd_feedback_profile_get_name (profile)); } + +static void +test_fbd_feedback_theme_update (void) +{ + g_autoptr (GError) err = NULL; + FbdFeedbackTheme *theme, *from; + FbdFeedbackProfile *profile; + FbdFeedbackBase *fb; + const char *name; + + /* test-dummy-1x feedbacks */ + from = fbd_feedback_theme_new_from_file (TEST_DATA_DIR "/parent/test.json", &err); + g_assert_no_error (err); + g_assert_true (FBD_IS_FEEDBACK_THEME (from)); + + /* test-dummy-0{0,1} feedbacks */ + theme = fbd_feedback_theme_new_from_file (TEST_DATA_DIR "/parent/base.json", &err); + g_assert_no_error (err); + g_assert_true (FBD_IS_FEEDBACK_THEME (theme)); + + name = fbd_feedback_theme_get_name (from); + g_assert_cmpstr ("test", ==, name); + + name = fbd_feedback_theme_get_name (theme); + g_assert_cmpstr ("base", ==, name); + + /* `theme` is lacking "silent" while `from` has it */ + profile = fbd_feedback_theme_get_profile (theme, "silent"); + g_assert_null (profile); + profile = fbd_feedback_theme_get_profile (from, "silent"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + + fbd_feedback_theme_update (theme, from); + + /* After merging `theme` has a silent profile too */ + profile = fbd_feedback_theme_get_profile (theme, "silent"); + g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); + g_assert_cmpstr ("silent", ==, fbd_feedback_profile_get_name (profile)); + + /* Name updated */ + name = fbd_feedback_theme_get_name (theme); + g_assert_cmpstr ("test", ==, name); + + /* test-dummy-10 added */ + profile = fbd_feedback_theme_get_profile (theme, "full"); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-10"); + g_assert_cmpstr ("test-dummy-10", ==, fbd_feedback_get_event_name (fb)); + profile = fbd_feedback_theme_get_profile (theme, "quiet"); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-10"); + g_assert_cmpstr ("test-dummy-10", ==, fbd_feedback_get_event_name (fb)); + profile = fbd_feedback_theme_get_profile (theme, "silent"); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-10"); + g_assert_cmpstr ("test-dummy-10", ==, fbd_feedback_get_event_name (fb)); + + /* test-dummy-00 updated */ + profile = fbd_feedback_theme_get_profile (theme, "full"); + fb = fbd_feedback_profile_get_feedback (profile, "test-dummy-00"); + g_assert_cmpstr ("test-dummy-00", ==, fbd_feedback_get_event_name (fb)); + g_assert_cmpint (fbd_feedback_dummy_get_duration (FBD_FEEDBACK_DUMMY (fb)), ==, 0x10); + + g_assert_finalize_object (from); + g_assert_finalize_object (theme); +} + + gint main (gint argc, gchar *argv[]) { @@ -124,6 +189,7 @@ main (gint argc, gchar *argv[]) g_test_add_func("/feedbackd/fbd/feedback-theme/name", test_fbd_feedback_theme_name); g_test_add_func("/feedbackd/fbd/feedback-theme/profiles", test_fbd_feedback_theme_profiles); g_test_add_func("/feedbackd/fbd/feedback-theme/parse", test_fbd_feedback_theme_parse); + g_test_add_func("/feedbackd/fbd/feedback-theme/update", test_fbd_feedback_theme_update); return g_test_run(); } From ada82a0ed1fd5f5c7a4b8d63c73222dce2fd964a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:16 +0100 Subject: [PATCH 12/22] feedback-theme: Allow to get and set parent theme name If set the parent theme's feedbackd and profiles should be merged into this theme. --- src/fbd-feedback-theme.c | 42 +++++++++++++++++++++++++++++++++ src/fbd-feedback-theme.h | 3 +++ tests/test-fbd-feedback-theme.c | 4 ++++ 3 files changed, 49 insertions(+) diff --git a/src/fbd-feedback-theme.c b/src/fbd-feedback-theme.c index f6dd8e0..6d1bc22 100644 --- a/src/fbd-feedback-theme.c +++ b/src/fbd-feedback-theme.c @@ -18,6 +18,7 @@ enum { PROP_0, PROP_NAME, + PROP_PARENT_NAME, PROP_PROFILES, PROP_LAST_PROP, }; @@ -27,6 +28,8 @@ typedef struct _FbdFeedbackTheme { GObject parent; char *name; + char *parent_name; + GHashTable *profiles; } FbdFeedbackTheme; @@ -125,6 +128,9 @@ fbd_feedback_theme_set_property (GObject *object, case PROP_NAME: fbd_feedback_theme_set_name (self, g_value_get_string (value)); break; + case PROP_PARENT_NAME: + fbd_feedback_theme_set_parent_name (self, g_value_get_string (value)); + break; case PROP_PROFILES: if (self->profiles) g_hash_table_unref (self->profiles); @@ -149,6 +155,9 @@ fbd_feedback_theme_get_property (GObject *object, case PROP_NAME: g_value_set_string (value, fbd_feedback_theme_get_name (self)); break; + case PROP_PARENT_NAME: + g_value_set_string (value, fbd_feedback_theme_get_parent_name (self)); + break; case PROP_PROFILES: g_value_set_boxed (value, self->profiles); break; @@ -204,6 +213,14 @@ fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass) NULL, G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + props[PROP_PARENT_NAME] = + g_param_spec_string ( + "parent-name", + "Parent theme Name", + "The parent theme name", + NULL, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + props[PROP_PROFILES] = g_param_spec_boxed ( "profiles", @@ -278,6 +295,31 @@ fbd_feedback_theme_get_name (FbdFeedbackTheme *self) return self->name; } + +void +fbd_feedback_theme_set_parent_name (FbdFeedbackTheme *self, const char *parent_name) +{ + g_return_if_fail (FBD_IS_FEEDBACK_THEME (self)); + + if (g_strcmp0 (self->parent_name, parent_name) == 0) + return; + + g_free (self->parent_name); + self->parent_name = g_strdup (parent_name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PARENT_NAME]); +} + + +const char * +fbd_feedback_theme_get_parent_name (FbdFeedbackTheme *self) +{ + g_return_val_if_fail (FBD_IS_FEEDBACK_THEME (self), ""); + + return self->parent_name; +} + + void fbd_feedback_theme_add_profile (FbdFeedbackTheme *self, FbdFeedbackProfile *profile) { diff --git a/src/fbd-feedback-theme.h b/src/fbd-feedback-theme.h index ddddc46..3485eb0 100644 --- a/src/fbd-feedback-theme.h +++ b/src/fbd-feedback-theme.h @@ -25,6 +25,9 @@ void fbd_feedback_theme_update (FbdFeedbackTheme *self, FbdFeedba const char *fbd_feedback_theme_get_name (FbdFeedbackTheme *self); void fbd_feedback_theme_set_name (FbdFeedbackTheme *self, const char *name); +void fbd_feedback_theme_set_parent_name (FbdFeedbackTheme *self, + const char *parent_name); +const char *fbd_feedback_theme_get_parent_name (FbdFeedbackTheme *self); void fbd_feedback_theme_add_profile (FbdFeedbackTheme *self, FbdFeedbackProfile *profile); FbdFeedbackProfile *fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name); diff --git a/tests/test-fbd-feedback-theme.c b/tests/test-fbd-feedback-theme.c index 421b4a2..c5cb636 100644 --- a/tests/test-fbd-feedback-theme.c +++ b/tests/test-fbd-feedback-theme.c @@ -65,6 +65,7 @@ test_fbd_feedback_theme_parse (void) const char *json =" " "{ " " \"name\" : \"test\", " + " \"parent-name\" : \"parent-test\", " " \"profiles\" : [ " " { " " \"name\" : \"full\", " @@ -106,6 +107,9 @@ test_fbd_feedback_theme_parse (void) name = fbd_feedback_theme_get_name (theme); g_assert_cmpstr ("test", ==, name); + name = fbd_feedback_theme_get_parent_name (theme); + g_assert_cmpstr ("parent-test", ==, name); + profile = fbd_feedback_theme_get_profile (theme, "full"); g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile)); g_assert_cmpstr ("full", ==, From 4c75f42c7e373feebb9184720e5144eb38a4d33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 20:01:35 +0100 Subject: [PATCH 13/22] ci: Install git Needed for the submodule --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9d41c06..6a71037 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: - export DEBIAN_FRONTEND=noninteractive - apt-get -y update - apt-get -y build-dep . - - apt-get -y install gcovr + - apt-get -y install gcovr git .image: &image image: From ba91939c8537baa7029033a8e190b651b1a88f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:16 +0100 Subject: [PATCH 14/22] Add gmobile library --- .gitmodules | 3 +++ meson.build | 7 +++++++ src/meson.build | 1 + subprojects/gmobile | 1 + 4 files changed, 12 insertions(+) create mode 100644 .gitmodules create mode 160000 subprojects/gmobile diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fae72b7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subprojects/gmobile"] + path = subprojects/gmobile + url = https://gitlab.gnome.org/guidog/gmobile.git diff --git a/meson.build b/meson.build index 7835999..3b63564 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,13 @@ gio_unix = dependency('gio-unix-2.0', version: '>=2.50.0') glib = dependency('glib-2.0', version: '>=2.50.0') if get_option('daemon') gobject = dependency('gobject-2.0', version: '>=2.50.0') + gmobile_subp = subproject('gmobile', + default_options: [ + 'examples=false', + 'gtk_doc=false', + 'tests=false', + ]) + gmobile = gmobile_subp.get_variable('libgmobile_dep') gsound = dependency('gsound') gudev = dependency('gudev-1.0', version: '>=232') json_glib = dependency('json-glib-1.0') diff --git a/src/meson.build b/src/meson.build index 918c73f..e85cca5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -34,6 +34,7 @@ fbd_deps = [ gio, gio_unix, glib, + gmobile, gobject, gsound, gudev, diff --git a/subprojects/gmobile b/subprojects/gmobile new file mode 160000 index 0000000..1039e78 --- /dev/null +++ b/subprojects/gmobile @@ -0,0 +1 @@ +Subproject commit 1039e7808195d4de367ce2718481641ca8af2427 From 34f8733b2bb4a03444a90332fff074b8d168134a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:17 +0100 Subject: [PATCH 15/22] build: Don't include main in lib For that move the error quark to a separate file. --- src/fbd-error.c | 20 ++++++++++++++++++++ src/fbd.c | 13 ------------- src/meson.build | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 src/fbd-error.c diff --git a/src/fbd-error.c b/src/fbd-error.c new file mode 100644 index 0000000..22993fb --- /dev/null +++ b/src/fbd-error.c @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 Guido Günther + * + * SPDX-License-Identifier: GPL-3.0+ + * + * Author: Guido Günther + */ + +#include "fbd.h" + +GQuark +fbd_error_quark (void) +{ + static GQuark quark = 0; + + if (!quark) + quark = g_quark_from_static_string("fbd"); + + return quark; +} diff --git a/src/fbd.c b/src/fbd.c index 31f20f8..b6cc586 100644 --- a/src/fbd.c +++ b/src/fbd.c @@ -17,19 +17,6 @@ static GMainLoop *loop; - -GQuark -fbd_error_quark (void) -{ - static GQuark quark = 0; - - if (!quark) - quark = g_quark_from_static_string("fbd"); - - return quark; -} - - static gboolean quit_cb (gpointer user_data) { diff --git a/src/meson.build b/src/meson.build index e85cca5..cb507de 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,7 +12,7 @@ fbd_enum_sources = gnome.mkenums_simple('fbd-enums', sources = [ generated_dbus_sources, fbd_enum_sources, - 'fbd.c', + 'fbd-error.c', 'fbd-dev-vibra.c', 'fbd-dev-sound.c', 'fbd-dev-leds.c', From 242519498a6110308f1d2fabf9703c87e8e972b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:17 +0100 Subject: [PATCH 16/22] 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 --- src/fbd-feedback-manager.c | 98 +---- src/fbd-theme-expander.c | 400 ++++++++++++++++++ src/fbd-theme-expander.h | 28 ++ src/fbd.h | 1 + src/meson.build | 1 + .../user-config/feedbackd/themes/custom.json | 21 + .../xdg-data/feedbackd/themes/chainup.json | 4 + .../xdg-data/feedbackd/themes/default.json | 31 ++ .../xdg-data/feedbackd/themes/replace.json | 21 + tests/meson.build | 10 +- tests/test-fbd-theme-expander.c | 95 +++++ 11 files changed, 629 insertions(+), 81 deletions(-) create mode 100644 src/fbd-theme-expander.c create mode 100644 src/fbd-theme-expander.h create mode 100644 tests/data/user-config/feedbackd/themes/custom.json create mode 100644 tests/data/xdg-data/feedbackd/themes/chainup.json create mode 100644 tests/data/xdg-data/feedbackd/themes/default.json create mode 100644 tests/data/xdg-data/feedbackd/themes/replace.json create mode 100644 tests/test-fbd-theme-expander.c 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(); +} From a75cdbbc5930bdc169195cc5233d871f4a3b229e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:18 +0100 Subject: [PATCH 17/22] theme-expander: Allow to set the theme to expand --- src/fbd-feedback-manager.c | 1 + src/fbd-theme-expander.c | 59 +++++++++++++++++++++++++++++---- src/fbd-theme-expander.h | 3 ++ tests/meson.build | 2 +- tests/test-fbd-theme-expander.c | 37 +++++++++++++++++++-- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/fbd-feedback-manager.c b/src/fbd-feedback-manager.c index 7872132..a8f45e4 100644 --- a/src/fbd-feedback-manager.c +++ b/src/fbd-feedback-manager.c @@ -556,6 +556,7 @@ fbd_feedback_manager_load_theme (FbdFeedbackManager *self) } expander = fbd_theme_expander_new ((const char *const *)compatibles, + NULL, g_getenv (FEEDBACKD_THEME_VAR)); theme = fbd_theme_expander_load_theme_files (expander, &err); diff --git a/src/fbd-theme-expander.c b/src/fbd-theme-expander.c index 69a9cea..2979e3b 100644 --- a/src/fbd-theme-expander.c +++ b/src/fbd-theme-expander.c @@ -12,9 +12,6 @@ #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" @@ -31,6 +28,7 @@ enum { PROP_0, + PROP_THEME_NAME, PROP_THEME_FILE, PROP_COMPATIBLES, PROP_LAST_PROP @@ -40,6 +38,7 @@ static GParamSpec *props[PROP_LAST_PROP]; struct _FbdThemeExpander { GObject parent; + char *theme_name; char *theme_file; gboolean device_theme_loaded; GStrv compatibles; @@ -59,6 +58,20 @@ fbd_theme_expander_set_compatibles (FbdThemeExpander *self, const char * const * self->device_theme_loaded = FALSE; } +static void +fbd_theme_expander_set_theme_name (FbdThemeExpander *self, const char *theme_name) +{ + g_return_if_fail (FBD_IS_THEME_EXPANDER (self)); + + g_free (self->theme_name); + if (theme_name) + self->theme_name = g_strdup (theme_name); + else + self->theme_name = g_strdup (DEFAULT_THEME_NAME); + + /* 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) @@ -187,6 +200,9 @@ fbd_theme_expander_set_property (GObject *object, FbdThemeExpander *self = FBD_THEME_EXPANDER (object); switch (property_id) { + case PROP_THEME_NAME: + fbd_theme_expander_set_theme_name (self, g_value_get_string (value)); + break; case PROP_THEME_FILE: fbd_theme_expander_set_theme_file (self, g_value_get_string (value)); break; @@ -209,6 +225,9 @@ fbd_theme_expander_get_property (GObject *object, FbdThemeExpander *self = FBD_THEME_EXPANDER (object); switch (property_id) { + case PROP_THEME_NAME: + g_value_set_string (value, fbd_theme_expander_get_theme_name (self)); + break; case PROP_THEME_FILE: g_value_set_string (value, fbd_theme_expander_get_theme_file (self)); break; @@ -227,6 +246,7 @@ fbd_theme_expander_finalize (GObject *object) { FbdThemeExpander *self = FBD_THEME_EXPANDER(object); + g_clear_pointer (&self->theme_name, g_free); g_clear_pointer (&self->theme_file, g_free); g_clear_pointer (&self->compatibles, g_strfreev); @@ -243,6 +263,18 @@ fbd_theme_expander_class_init (FbdThemeExpanderClass *klass) object_class->set_property = fbd_theme_expander_set_property; object_class->finalize = fbd_theme_expander_finalize; + /** + * FbdThemeExpander:theme-name: + * + * Specifies the theme to expand. Assumes `default` if unset. + * + * The `compatibles` defines which device specific theme bits are loaded. + */ + props[PROP_THEME_NAME] = + g_param_spec_string ("theme-name", "", "", + DEFAULT_THEME_NAME, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_STATIC_STRINGS); /** * FbdThemeExpander:theme-file: * @@ -278,9 +310,12 @@ fbd_theme_expander_init (FbdThemeExpander *self) FbdThemeExpander * -fbd_theme_expander_new (const char * const *compatibles, const char *theme_file) +fbd_theme_expander_new (const char * const *compatibles, + const char *theme_name, + const char *theme_file) { return FBD_THEME_EXPANDER (g_object_new (FBD_TYPE_THEME_EXPANDER, + "theme-name", theme_name, "theme-file", theme_file, "compatibles", compatibles, NULL)); @@ -322,10 +357,10 @@ fbd_theme_expander_load_theme_files (FbdThemeExpander *self, GError **err) 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) + if (self->theme_file) { theme_file = g_strdup (self->theme_file); - else { - theme_file = fbd_theme_expander_find_theme_path (self, DEFAULT_THEME_NAME); + } else { + theme_file = fbd_theme_expander_find_theme_path (self, self->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]); @@ -380,9 +415,19 @@ fbd_theme_expander_load_theme_files (FbdThemeExpander *self, GError **err) /* Merge themes bottom to top */ g_queue_foreach (queue, update_theme, merged); + fbd_feedback_theme_set_name (merged, self->theme_name); return g_steal_pointer (&merged); } +const char * +fbd_theme_expander_get_theme_name (FbdThemeExpander *self) +{ + g_return_val_if_fail (FBD_IS_THEME_EXPANDER (self), NULL); + + return self->theme_name; + +} + const char * fbd_theme_expander_get_theme_file (FbdThemeExpander *self) { diff --git a/src/fbd-theme-expander.h b/src/fbd-theme-expander.h index 8d2db79..834907e 100644 --- a/src/fbd-theme-expander.h +++ b/src/fbd-theme-expander.h @@ -18,10 +18,13 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (FbdThemeExpander, fbd_theme_expander, FBD, THEME_EXPANDER, GObject) + FbdThemeExpander *fbd_theme_expander_new (const char * const *compatibles, + const char *theme_name, const char *theme_file); FbdFeedbackTheme *fbd_theme_expander_load_theme_files (FbdThemeExpander *self, GError **err); +const char *fbd_theme_expander_get_theme_name (FbdThemeExpander *self); const char *fbd_theme_expander_get_theme_file (FbdThemeExpander *self); const char * const *fbd_theme_expander_get_compatibles (FbdThemeExpander *self); diff --git a/tests/meson.build b/tests/meson.build index c776cf0..254f684 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -8,7 +8,7 @@ test_env.set('GSETTINGS_BACKEND', 'memory') test_env.set('GSETTINGS_SCHEMA_DIR', '@0@/data'.format(meson.project_build_root())) test_env.set('PYTHONDONTWRITEBYTECODE', 'yes') test_env.set('MALLOC_CHECK_', '2') -test_env.set('XDG_CONFIG_HOME', meson.current_source_dir()) +test_env.set('XDG_CONFIG_HOME', meson.current_source_dir() / 'data' / 'user-config') test_env.set('XDG_CONFIG_DIRS', meson.current_source_dir()) # Shared library Unit tests diff --git a/tests/test-fbd-theme-expander.c b/tests/test-fbd-theme-expander.c index 1bde3cd..1ffb2af 100644 --- a/tests/test-fbd-theme-expander.c +++ b/tests/test-fbd-theme-expander.c @@ -15,6 +15,7 @@ test_fbd_theme_expander_object (void) { g_autoptr (FbdThemeExpander) expander = g_object_new (FBD_TYPE_THEME_EXPANDER, NULL); + g_assert_cmpstr ("default", ==, fbd_theme_expander_get_theme_name (expander)); g_assert_null (fbd_theme_expander_get_theme_file (expander)); g_assert_null (fbd_theme_expander_get_compatibles (expander)); } @@ -32,7 +33,9 @@ test_fbd_theme_expander_device (void) /* just the default profile */ compatibles[0] = "doesnotexist"; - expander = fbd_theme_expander_new (compatibles, NULL); + expander = fbd_theme_expander_new (compatibles, NULL, NULL); + g_assert_cmpstr ("default", ==, fbd_theme_expander_get_theme_name (expander)); + theme = fbd_theme_expander_load_theme_files (expander, &err); compatibles2 = fbd_theme_expander_get_compatibles (expander); g_assert_cmpstrv (compatibles2, compatibles); @@ -48,7 +51,7 @@ test_fbd_theme_expander_device (void) /* Empty device theme that just chains up */ compatibles[0] = "chainup"; - expander = fbd_theme_expander_new (compatibles, NULL); + expander = fbd_theme_expander_new (compatibles, NULL, NULL); compatibles2 = fbd_theme_expander_get_compatibles (expander); g_assert_cmpstrv (compatibles2, compatibles); theme = fbd_theme_expander_load_theme_files (expander, &err); @@ -64,7 +67,7 @@ test_fbd_theme_expander_device (void) /* Replace an existing event */ compatibles[0] = "replace"; - expander = fbd_theme_expander_new (compatibles, NULL); + expander = fbd_theme_expander_new (compatibles, NULL, NULL); compatibles2 = fbd_theme_expander_get_compatibles (expander); g_assert_cmpstrv (compatibles2, compatibles); theme = fbd_theme_expander_load_theme_files (expander, &err); @@ -82,6 +85,33 @@ test_fbd_theme_expander_device (void) g_assert_finalize_object (expander); } +static void +test_fbd_theme_expander_custom (void) +{ + g_autoptr (GError) err = NULL; + const char *compatibles[] = { NULL, NULL }; + FbdFeedbackProfile *profile; + FbdThemeExpander *expander; + FbdFeedbackTheme *theme; + FbdFeedbackBase *fb; + + /* Load a custom theme that overrides a single event on top of the `replace` from above */ + compatibles[0] = "replace"; + expander = fbd_theme_expander_new (compatibles, "custom", NULL); + 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)), ==, 0x30); + g_assert_finalize_object (theme); + g_assert_finalize_object (expander); +} gint main (int argc, char *argv[]) @@ -90,6 +120,7 @@ main (int argc, char *argv[]) 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); + g_test_add_func("/feedbackd/fbd/theme-expander/custom", test_fbd_theme_expander_custom); return g_test_run(); } From c3beac7d4151e9332c78a980b6cad144112c27f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:19 +0100 Subject: [PATCH 18/22] data: Allow to set feedback theme --- data/org.sigxcpu.feedbackd.gschema.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/org.sigxcpu.feedbackd.gschema.xml b/data/org.sigxcpu.feedbackd.gschema.xml index eba31ba..723f8c1 100644 --- a/data/org.sigxcpu.feedbackd.gschema.xml +++ b/data/org.sigxcpu.feedbackd.gschema.xml @@ -14,6 +14,15 @@ level given on events to the user. + + + 'default' + The feedback theme to use + + The feedback theme determines which feedbacks are triggered when + feedbackd receives an event. + + From f5757045aa025443353d9aaf6f9f13e336205d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 19:38:08 +0100 Subject: [PATCH 19/22] manager: Don't load theme in constructor Loading the theme can trigger calls to fbd_feedback_manager_get_default() which then results in an endless look as the object isn't fully initialized in constructed. Instead do the setup in main() which has the advantage that the manager's life cycle becomes more obvious too. --- src/fbd-feedback-manager.c | 2 -- src/fbd.c | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fbd-feedback-manager.c b/src/fbd-feedback-manager.c index a8f45e4..03cb947 100644 --- a/src/fbd-feedback-manager.c +++ b/src/fbd-feedback-manager.c @@ -439,8 +439,6 @@ fbd_feedback_manager_constructed (GObject *object) G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->constructed (object); - fbd_feedback_manager_load_theme(self); - g_signal_connect (self, "notify::profile", (GCallback)on_profile_changed, NULL); self->settings = g_settings_new (FEEDBACKD_SCHEMA_ID); diff --git a/src/fbd.c b/src/fbd.c index b6cc586..b1bb320 100644 --- a/src/fbd.c +++ b/src/fbd.c @@ -92,6 +92,7 @@ int main(int argc, char *argv[]) { g_autoptr(GError) err = NULL; g_autoptr(GOptionContext) opt_context = NULL; + g_autoptr (FbdFeedbackManager) manager = NULL; opt_context = g_option_context_new ("- A daemon to trigger event feedback"); if (!g_option_context_parse (opt_context, &argc, &argv, &err)) { @@ -100,6 +101,9 @@ int main(int argc, char *argv[]) return 1; } + manager = fbd_feedback_manager_get_default (); + fbd_feedback_manager_load_theme (manager); + g_unix_signal_add (SIGTERM, quit_cb, NULL); g_unix_signal_add (SIGINT, quit_cb, NULL); g_unix_signal_add (SIGHUP, reload_cb, NULL); @@ -117,6 +121,4 @@ int main(int argc, char *argv[]) g_main_loop_run (loop); g_main_loop_unref (loop); - - g_object_unref (fbd_feedback_manager_get_default ()); } From 83091be25dfbb131bd6701d42e1ec5ee0ec5c96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:20 +0100 Subject: [PATCH 20/22] feedback-manager: Handle theme gsetting Update the theme when the gsetting changes --- src/fbd-feedback-manager.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/fbd-feedback-manager.c b/src/fbd-feedback-manager.c index 03cb947..e0788cf 100644 --- a/src/fbd-feedback-manager.c +++ b/src/fbd-feedback-manager.c @@ -25,6 +25,7 @@ #define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd" #define FEEDBACKD_KEY_PROFILE "profile" +#define FEEDBACKD_KEY_THEME "theme" #define APP_SCHEMA FEEDBACKD_SCHEMA_ID ".application" #define APP_PREFIX "/org/sigxcpu/feedbackd/application/" @@ -213,10 +214,15 @@ on_feedbackd_setting_changed (FbdFeedbackManager *self, g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self)); g_return_if_fail (G_IS_SETTINGS (settings)); - g_return_if_fail (!g_strcmp0 (key, FEEDBACKD_KEY_PROFILE)); - profile = g_settings_get_string (settings, key); - fbd_feedback_manager_set_profile (self, profile); + if (g_str_equal (key, FEEDBACKD_KEY_PROFILE)) { + profile = g_settings_get_string (settings, key); + fbd_feedback_manager_set_profile (self, profile); + } else if (g_str_equal (key, FEEDBACKD_KEY_THEME)) { + fbd_feedback_manager_load_theme (self); + } else { + g_critical ("Unknown settings key '%s'", key); + } } static void @@ -442,6 +448,8 @@ fbd_feedback_manager_constructed (GObject *object) g_signal_connect (self, "notify::profile", (GCallback)on_profile_changed, NULL); self->settings = g_settings_new (FEEDBACKD_SCHEMA_ID); + g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_THEME, + G_CALLBACK (on_feedbackd_setting_changed), self); g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_PROFILE, G_CALLBACK (on_feedbackd_setting_changed), self); on_feedbackd_setting_changed (self, FEEDBACKD_KEY_PROFILE, self->settings); @@ -546,6 +554,8 @@ fbd_feedback_manager_load_theme (FbdFeedbackManager *self) g_autoptr (FbdFeedbackTheme) theme = NULL; g_autoptr (GError) err = NULL; g_auto (GStrv) compatibles = NULL; + g_autofree char *theme_name = NULL; + const char *theme_file = g_getenv (FEEDBACKD_THEME_VAR); compatibles = gm_devicetree_get_compatibles (NULL, &err); if (compatibles == NULL && err) { @@ -553,11 +563,12 @@ fbd_feedback_manager_load_theme (FbdFeedbackManager *self) g_clear_error (&err); } - expander = fbd_theme_expander_new ((const char *const *)compatibles, - NULL, - g_getenv (FEEDBACKD_THEME_VAR)); - theme = fbd_theme_expander_load_theme_files (expander, &err); + if (theme_file == NULL) + theme_name = g_settings_get_string (self->settings, FEEDBACKD_KEY_THEME); + expander = fbd_theme_expander_new ((const char *const *)compatibles, + theme_name, theme_file); + theme = fbd_theme_expander_load_theme_files (expander, &err); if (theme) { g_set_object(&self->theme, theme); } else { From 9824d1cfc6f06a8db0e9f49ae7854a3d09e4a98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 Dec 2022 18:44:20 +0100 Subject: [PATCH 21/22] README: Document custom themes --- README.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b36c67f..db28c03 100644 --- a/README.md +++ b/README.md @@ -79,17 +79,45 @@ app that triggered it crashes. ## Feedback theme Events are then mapped to a specific type of feedback (sound, led, vibra) via a -device specific theme (since devices have different capabilities). +device specific theme - since devices have different capabilities and +different users different needs. Feedbackd is shipped with a default theme `default.json`. -You can add your own themes in one of two ways: +You can add your own themes in multiple ways: 1. By exporting an environment variable `FEEDBACK_THEME` with a path to a valid theme file (not recommended, use for testing only), or 2. By creating a theme file under `$XDG_CONFIG_HOME/feedbackd/themes/default.json`. If `XDG_CONFIG_HOME` environment variable is not set or empty, it will default to `$HOME/.config`, or -3. By adding your theme file to one of the folders in the `XDG_DATA_DIRS` +3. By creating a theme file under `$XDG_CONFIG_HOME/feedbackd/themes/custom.json`. + You only specify the values you want to change in that theme and add an entry + ```json + { + "name: "custom" + "parent-theme": "default" + "profiles" : [ + ...(entries you want to change go here)... + ] + } + ``` + next to the `name` entry in. This has the upside that your theme + gets way smaller and that new entries added to the default theme + will automatically be used by your theme too. See + [here](./tests/data/user-config/feedbackd/themes/custom.json) for + an example. Once you have the file in place, tell feedbackd the + them you want to use: + + gsettings set org.sigxcpu.feedbackd theme custom + + When you want to go back to the default theme just do: + + gsettings reset org.sigxcpu.feedbackd theme + + Note that you can name your theme as you wish but avoid theme names + starting with `__` or `$` as this namespace is reserved. + +4. By adding your theme file to one of the folders in the `XDG_DATA_DIRS` environment variable, appended with `feedbackd/themes/`. This folder isn't created automatically, so you have to create it yourself. Here's an example: ```bash From 9e13793bbea0bede9056bdb689acfbc2cadd5024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 6 Dec 2022 15:35:57 +0100 Subject: [PATCH 22/22] tests: Help old glib on PureOS along --- tests/test-fbd-theme-expander.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test-fbd-theme-expander.c b/tests/test-fbd-theme-expander.c index 1ffb2af..accb2ae 100644 --- a/tests/test-fbd-theme-expander.c +++ b/tests/test-fbd-theme-expander.c @@ -38,7 +38,9 @@ test_fbd_theme_expander_device (void) theme = fbd_theme_expander_load_theme_files (expander, &err); compatibles2 = fbd_theme_expander_get_compatibles (expander); +#if GLIB_CHECK_VERSION(2, 68, 0) g_assert_cmpstrv (compatibles2, compatibles); +#endif g_assert_no_error (err); profile = fbd_feedback_theme_get_profile (theme, "silent"); g_assert_null (profile); @@ -53,7 +55,9 @@ test_fbd_theme_expander_device (void) compatibles[0] = "chainup"; expander = fbd_theme_expander_new (compatibles, NULL, NULL); compatibles2 = fbd_theme_expander_get_compatibles (expander); +#if GLIB_CHECK_VERSION(2, 68, 0) g_assert_cmpstrv (compatibles2, compatibles); +#endif theme = fbd_theme_expander_load_theme_files (expander, &err); g_assert_no_error (err); profile = fbd_feedback_theme_get_profile (theme, "silent"); @@ -69,7 +73,9 @@ test_fbd_theme_expander_device (void) compatibles[0] = "replace"; expander = fbd_theme_expander_new (compatibles, NULL, NULL); compatibles2 = fbd_theme_expander_get_compatibles (expander); +#if GLIB_CHECK_VERSION(2, 68, 0) g_assert_cmpstrv (compatibles2, compatibles); +#endif theme = fbd_theme_expander_load_theme_files (expander, &err); g_assert_no_error (err); profile = fbd_feedback_theme_get_profile (theme, "full");