diff --git a/README.md b/README.md index b7fc78e..78e6569 100644 --- a/README.md +++ b/README.md @@ -277,11 +277,32 @@ export GSETTINGS_SCHEMA_DIR=_build/data/ gsettings set org.sigxcpu.feedbackd.application:/org/sigxcpu/feedbackd/application/sm-puri-phosh/ profile quiet ``` +## Haptic API + +In order to give applications like browsers and games more control +over the haptic motor `Feebackd` implements an interface that allows +to set vibra patterns. The vibration pattern is given as a sequence of +rumble magnitude and duration pairs like: + +``` +busctl call --user org.sigxcpu.Feedback /org/sigxcpu/Feedback org.sigxcpu.Feedback.Haptic Vibrate 'sa(du)' org.foo.app 3 1.0 200 0.0 50 0.5 300 +``` + +Vibration can be ended by submitting an empty array: + +```sh +busctl call --user org.sigxcpu.Feedback /org/sigxcpu/Feedback org.sigxcpu.Feedback.Haptic Vibrate 'sa(du)' org.foo.app 0 +``` + +The API is exported as a separate interface `org.sigxcpu.Feedback.Haptic` which is only available when +a haptic device is found. + ## Documentation - [Libfeedback API](https://honk.sigxcpu.org/projects/feedbackd/doc/) - [Event naming spec draft](./doc/Event-naming-spec-0.0.0.md) - [Feedback-theme-spec draft](./doc/Feedback-theme-spec-0.0.0.md) +- [W3's vibration API draft](https://www.w3.org/TR/vibration/) [debian/control]: ./debian/control#L5 [1]: https://source.puri.sm/Librem5/feedbackd-device-themes diff --git a/data/meson.build b/data/meson.build index 7ff25cc..728989d 100644 --- a/data/meson.build +++ b/data/meson.build @@ -4,7 +4,10 @@ namespace = 'LfbGdbus' generated_dbus_sources = [] -dbus_interfaces = [ 'org.sigxcpu.Feedback.xml' ] +dbus_interfaces = [ + 'org.sigxcpu.Feedback.xml', + 'org.sigxcpu.Feedback.Haptic.xml', +] generated_dbus_sources += gnome.gdbus_codegen('lfb-gdbus', sources : dbus_interfaces, diff --git a/data/org.sigxcpu.Feedback.Haptic.xml b/data/org.sigxcpu.Feedback.Haptic.xml new file mode 100644 index 0000000..ea6c4ee --- /dev/null +++ b/data/org.sigxcpu.Feedback.Haptic.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/src/fbd-dev-vibra.c b/src/fbd-dev-vibra.c index 1f78136..c99444f 100644 --- a/src/fbd-dev-vibra.c +++ b/src/fbd-dev-vibra.c @@ -219,6 +219,7 @@ fbd_dev_vibra_class_init (FbdDevVibraClass *klass) static void fbd_dev_vibra_init (FbdDevVibra *self) { + self->id = -1; } FbdDevVibra * @@ -232,7 +233,7 @@ fbd_dev_vibra_new (GUdevDevice *device, GError **error) } gboolean -fbd_dev_vibra_rumble (FbdDevVibra *self, guint duration, gboolean upload) +fbd_dev_vibra_rumble (FbdDevVibra *self, double magnitude, guint duration, gboolean upload) { struct input_event event = { 0 }; struct ff_effect effect = { 0 }; @@ -242,7 +243,7 @@ fbd_dev_vibra_rumble (FbdDevVibra *self, guint duration, gboolean upload) memset(&effect, 0, sizeof(effect)); effect.type = FF_RUMBLE; effect.id = -1; - effect.u.rumble.strong_magnitude = 0x8000; + effect.u.rumble.strong_magnitude = (0x8000 * magnitude); effect.u.rumble.weak_magnitude = 0; effect.replay.length = duration; effect.replay.delay = 0; @@ -272,7 +273,7 @@ fbd_dev_vibra_rumble (FbdDevVibra *self, guint duration, gboolean upload) /* TODO: fall back to multiple rumbles when sine not supported */ gboolean fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude, - guint fade_in_level, guint fade_in_time) + guint fade_in_level, guint fade_in_time) { struct input_event event; struct ff_effect effect = { 0 }; @@ -330,22 +331,30 @@ fbd_dev_vibra_remove_effect (FbdDevVibra *self) { g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE); + if (self->id == -1) + return TRUE; + g_debug("Erasing vibra effect (%d)", self->fd); if (ioctl(self->fd, EVIOCRMFF, self->id) == -1) { g_warning ("Failed to erase vibra effect with id %d: %s", self->id, strerror(errno)); + self->id = -1; return FALSE; } + self->id = -1; return TRUE; } gboolean -fbd_dev_vibra_stop(FbdDevVibra *self) +fbd_dev_vibra_stop (FbdDevVibra *self) { struct input_event stop = { 0 }; g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE); + if (self->id == -1) + return TRUE; + stop.type = EV_FF; stop.code = self->id; stop.value = 0; @@ -359,9 +368,28 @@ fbd_dev_vibra_stop(FbdDevVibra *self) } GUdevDevice * -fbd_dev_vibra_get_device(FbdDevVibra *self) +fbd_dev_vibra_get_device (FbdDevVibra *self) { g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE); return self->device; } + +/** + * fbd_dev_vibra_is_busy: + * @self: The vibra device + * + * Check whether the device is currently in used + * + * Returns: `TRUE` when the device is in use, otherwise `FALSE` + */ +gboolean +fbd_dev_vibra_is_busy (FbdDevVibra *self) +{ + if (self == NULL) + return FALSE; + + g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), TRUE); + + return self->id != -1; +} diff --git a/src/fbd-dev-vibra.h b/src/fbd-dev-vibra.h index 55c03c6..ef5891b 100644 --- a/src/fbd-dev-vibra.h +++ b/src/fbd-dev-vibra.h @@ -15,12 +15,15 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (FbdDevVibra, fbd_dev_vibra, FBD, DEV_VIBRA, GObject); FbdDevVibra *fbd_dev_vibra_new (GUdevDevice *device, GError **error); -gboolean fbd_dev_vibra_rumble (FbdDevVibra *device, guint duration, gboolean upload); +gboolean fbd_dev_vibra_rumble (FbdDevVibra *device, + double magnitude, + guint duration, + gboolean upload); gboolean fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude, guint fade_in_level, guint fade_in_time); gboolean fbd_dev_vibra_stop (FbdDevVibra *self); gboolean fbd_dev_vibra_remove_effect (FbdDevVibra *self); GUdevDevice *fbd_dev_vibra_get_device(FbdDevVibra *self); - +gboolean fbd_dev_vibra_is_busy (FbdDevVibra *self); G_END_DECLS diff --git a/src/fbd-feedback-manager.c b/src/fbd-feedback-manager.c index 8a647de..44b934c 100644 --- a/src/fbd-feedback-manager.c +++ b/src/fbd-feedback-manager.c @@ -12,8 +12,10 @@ #include "fbd-dev-leds.h" #include "fbd-event.h" #include "fbd-feedback-vibra.h" +#include "fbd-feedback-vibra-pattern.h" #include "fbd-feedback-manager.h" #include "fbd-feedback-theme.h" +#include "fbd-haptic-manager.h" #include "fbd-theme-expander.h" #include @@ -40,6 +42,9 @@ * based on the incoming events. */ +FbdDebugFlags fbd_debug_flags; + + typedef struct _FbdFeedbackManager { LfbGdbusFeedbackSkeleton parent; @@ -54,6 +59,9 @@ typedef struct _FbdFeedbackManager { /* Key: DBus name, value: watch_id */ GHashTable *clients; + /* org.sigxcpu.Feedbackd.Haptic */ + FbdHapticManager *haptic_manager; + /* Hardware interaction */ GUdevClient *client; FbdDevVibra *vibra; @@ -351,6 +359,7 @@ parse_hints (GVariant *hints, FbdFeedbackProfileLevel *level, gboolean *hint_imp return TRUE; } + static gboolean fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object, GDBusMethodInvocation *invocation, @@ -364,9 +373,9 @@ fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object, GSList *feedbacks, *l; guint event_id; const gchar *sender; - FbdFeedbackProfileLevel app_level, level, hint_level = FBD_FEEDBACK_PROFILE_LEVEL_FULL; + FbdFeedbackProfileLevel level, hint_level = FBD_FEEDBACK_PROFILE_LEVEL_FULL; gboolean found_fb = FALSE; - gboolean hint_important = FALSE, can_important; + gboolean hint_important = FALSE; sender = g_dbus_method_invocation_get_sender (invocation); g_debug ("Event '%s' for '%s' from %s", arg_event, arg_app_id, sender); @@ -405,21 +414,31 @@ fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object, event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout, sender); g_hash_table_insert (self->events, GUINT_TO_POINTER (event_id), event); - app_level = app_get_feedback_level (arg_app_id); - can_important = app_is_important (self, arg_app_id); - - if (hint_important && can_important) - level = hint_level; - else - level = get_max_level (self->level, app_level, hint_level); - + level = fbd_feedback_manager_get_effective_level (self, arg_app_id, hint_level, hint_important); feedbacks = fbd_feedback_theme_lookup_feedback (self->theme, level, event); + if (feedbacks) { + gboolean has_vibra = FALSE; + for (l = feedbacks; l; l = l->next) { FbdFeedbackBase *fb = FBD_FEEDBACK_BASE (l->data); if (fbd_feedback_is_available (FBD_FEEDBACK_BASE (fb))) { + /* Handle one haptic feedback at a time. In practice haptics can handle multiple + * patterns but none of the devices supports this atm */ + /* TODO: should respect priorities */ + if (FBD_IS_FEEDBACK_VIBRA (fb)) { + if (fbd_dev_vibra_is_busy (self->vibra)) + continue; + has_vibra = TRUE; + } + fbd_event_add_feedback (event, fb); + + /* Events take priority over the haptic interface */ + if (has_vibra) + fbd_haptic_manager_end_feedback (self->haptic_manager); + found_fb = TRUE; } } @@ -495,6 +514,9 @@ fbd_feedback_manager_constructed (GObject *object) g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_ALLOW_IMPORTANT, G_CALLBACK (on_feedbackd_allow_important_changed), self); on_feedbackd_allow_important_changed (self, FEEDBACKD_KEY_ALLOW_IMPORTANT, self->settings); + + if (self->vibra || fbd_debug_flags & FBD_DEBUG_FLAG_FORCE_HAPTIC) + self->haptic_manager = fbd_haptic_manager_new (); } @@ -503,6 +525,8 @@ fbd_feedback_manager_dispose (GObject *object) { FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object); + g_clear_object (&self->haptic_manager); + g_clear_object (&self->settings); g_clear_object (&self->theme); g_clear_object (&self->sound); @@ -662,3 +686,44 @@ fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile return TRUE; } + + +FbdHapticManager * +fbd_feedback_manager_get_haptic_manager (FbdFeedbackManager *self) +{ + g_assert (FBD_IS_FEEDBACK_MANAGER (self)); + + return self->haptic_manager; +} + +/** + * fbd_feedback_manager_get_effective_level: + * @self: The feedback manager + * @app_id: The app-id of the app that triggered the feedback + * @want_level: The wanted level + * @important: Whether the important hint is set + * + * Calculates the effective feedback level taking the hints sent for the event + * and the system configuration into account + * + * Returns: The effective feedback level + */ +FbdFeedbackProfileLevel +fbd_feedback_manager_get_effective_level (FbdFeedbackManager *self, + const char *app_id, + FbdFeedbackProfileLevel want_level, + gboolean important) +{ + gboolean can_important; + FbdFeedbackProfileLevel app_level, level; + + app_level = app_get_feedback_level (app_id); + can_important = app_is_important (self, app_id); + + if (important && can_important) + level = want_level; + else + level = get_max_level (self->level, app_level, want_level); + + return level; +} diff --git a/src/fbd-feedback-manager.h b/src/fbd-feedback-manager.h index 76291d9..19308ef 100644 --- a/src/fbd-feedback-manager.h +++ b/src/fbd-feedback-manager.h @@ -8,21 +8,34 @@ #include "fbd-dev-vibra.h" #include "fbd-dev-leds.h" #include "fbd-dev-sound.h" +#include "fbd-feedback-profile.h" +#include "fbd-haptic-manager.h" #include "lfb-gdbus.h" #include G_BEGIN_DECLS +typedef enum { + FBD_DEBUG_FLAG_FORCE_HAPTIC = 1 << 0, +} FbdDebugFlags; + +extern FbdDebugFlags fbd_debug_flags; + #define FBD_TYPE_FEEDBACK_MANAGER (fbd_feedback_manager_get_type()) G_DECLARE_FINAL_TYPE (FbdFeedbackManager, fbd_feedback_manager, FBD, FEEDBACK_MANAGER, LfbGdbusFeedbackSkeleton); FbdFeedbackManager *fbd_feedback_manager_get_default (void); +FbdHapticManager *fbd_feedback_manager_get_haptic_manager (FbdFeedbackManager *self); FbdDevVibra *fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self); FbdDevSound *fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self); FbdDevLeds *fbd_feedback_manager_get_dev_leds (FbdFeedbackManager *self); void fbd_feedback_manager_load_theme (FbdFeedbackManager *self); gboolean fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile); +FbdFeedbackProfileLevel fbd_feedback_manager_get_effective_level (FbdFeedbackManager *self, + const char *app_id, + FbdFeedbackProfileLevel want_level, + gboolean important); G_END_DECLS diff --git a/src/fbd-feedback-vibra-pattern.c b/src/fbd-feedback-vibra-pattern.c new file mode 100644 index 0000000..b74c44c --- /dev/null +++ b/src/fbd-feedback-vibra-pattern.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0+ + + * Author: Guido Günther + */ + +#define G_LOG_DOMAIN "fbd-feedback-vibra-pattern" + +#include "fbd-enums.h" +#include "fbd-feedback-vibra-pattern.h" +#include "fbd-feedback-vibra-priv.h" +#include "fbd-feedback-manager.h" + +/** + * FbdFeedbackVibraPattern: + * + * Describes a pattern feedback via a haptic motor + * + * The #FbdVibraVibraPattern describes the properties of a haptic feedback + * event. It knows nothing about the hardware itself but calls + * #FbdDevVibra for that. + */ + +enum { + PROP_0, + PROP_MAGNITUDES, + PROP_DURATIONS, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + +typedef struct _FbdFeedbackVibraPattern { + FbdFeedbackVibra parent; + + GArray *magnitudes; + GArray *durations; + int pos; + + guint timer_id; +} FbdFeedbackVibraPattern; + +G_DEFINE_TYPE (FbdFeedbackVibraPattern, fbd_feedback_vibra_pattern, FBD_TYPE_FEEDBACK_VIBRA); + + +static void +set_magnitudes (FbdFeedbackVibraPattern *self, GArray *magnitudes) +{ + g_clear_pointer (&self->magnitudes, g_array_unref); + self->magnitudes = g_array_ref (magnitudes); +} + + +static void +set_durations (FbdFeedbackVibraPattern *self, GArray *durations) +{ + guint total_duration = 0; + + g_clear_pointer (&self->durations, g_array_unref); + self->durations = g_array_ref (durations); + + for (int i = 0; i < self->durations->len; i++) { + guint *duration = &g_array_index (self->durations, guint, i); + total_duration += *duration; + } + + fbd_feedback_vibra_set_duration (FBD_FEEDBACK_VIBRA (self), total_duration); +} + + +static void +fbd_feedback_vibra_pattern_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + FbdFeedbackVibraPattern *self = FBD_FEEDBACK_VIBRA_PATTERN (object); + + switch (property_id) { + case PROP_MAGNITUDES: + set_magnitudes (self, g_value_get_boxed (value)); + break; + case PROP_DURATIONS: + set_durations (self, g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +fbd_feedback_vibra_pattern_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + FbdFeedbackVibraPattern *self = FBD_FEEDBACK_VIBRA_PATTERN (object); + + switch (property_id) { + case PROP_MAGNITUDES: + g_value_set_boxed (value, self->magnitudes); + break; + case PROP_DURATIONS: + g_value_set_boxed (value, self->durations); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void on_timer_expired (gpointer data); + + +static void +do_pattern_step (FbdFeedbackVibraPattern *self) +{ + FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager); + double *magnitude; + guint *duration; + + magnitude = &g_array_index (self->magnitudes, double, self->pos); + duration = &g_array_index (self->durations, guint, self->pos); + + g_debug ("step: pos: %d/%d, magn: %f, timeout %u", + self->pos, + self->durations->len, + *magnitude, + *duration); + + /* Removing any pending effect */ + fbd_dev_vibra_remove_effect (dev); + + if (*magnitude != 0.0) + fbd_dev_vibra_rumble (dev, *magnitude, *duration, TRUE); + + self->timer_id = g_timeout_add_once (*duration, + on_timer_expired, + self); + g_source_set_name_by_id (self->timer_id, "feedback-vibra-pattern-timer"); +} + + +static void +on_timer_expired (gpointer data) +{ + FbdFeedbackVibraPattern *self = data; + + g_return_if_fail (FBD_IS_FEEDBACK_VIBRA_PATTERN (self)); + g_return_if_fail (self->pos < self->durations->len); + + self->pos++; + + if (self->pos == self->durations->len) { + self->timer_id = 0; + return; + } + + do_pattern_step (self); +} + + +static void +fbd_feedback_vibra_pattern_end_vibra (FbdFeedbackVibra *vibra) +{ + FbdFeedbackVibraPattern *self = FBD_FEEDBACK_VIBRA_PATTERN (vibra); + FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager); + + if (self->timer_id) { + self->pos = 0; + g_clear_handle_id (&self->timer_id, g_source_remove); + } + + fbd_dev_vibra_stop (dev); +} + + +static void +fbd_feedback_vibra_pattern_start_vibra (FbdFeedbackVibra *vibra) +{ + FbdFeedbackVibraPattern *self = FBD_FEEDBACK_VIBRA_PATTERN (vibra); + FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager); + + g_return_if_fail (FBD_IS_DEV_VIBRA (dev)); + g_return_if_fail (self->magnitudes); + g_return_if_fail (self->durations); + g_return_if_fail (self->durations->len == self->magnitudes->len); + g_return_if_fail (self->pos == 0); + + g_debug ("Pattern Vibra: %u elements", self->durations->len); + + if (self->durations->len == 0) + return; + + do_pattern_step (self); +} + + +static gboolean +fbd_feedback_vibra_pattern_is_available (FbdFeedbackBase *base) +{ + FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager); + + return FBD_IS_DEV_VIBRA (dev); +} + + +static void +fbd_feedback_vibra_pattern_finalize (GObject *object) +{ + FbdFeedbackVibraPattern *self = FBD_FEEDBACK_VIBRA_PATTERN (object); + + fbd_feedback_vibra_pattern_end_vibra (FBD_FEEDBACK_VIBRA (self)); + g_clear_pointer (&self->magnitudes, g_array_unref); + g_clear_pointer (&self->durations, g_array_unref); + + G_OBJECT_CLASS (fbd_feedback_vibra_pattern_parent_class)->finalize (object); +} + + +static void +fbd_feedback_vibra_pattern_class_init (FbdFeedbackVibraPatternClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass); + FbdFeedbackVibraClass *vibra_class = FBD_FEEDBACK_VIBRA_CLASS (klass); + + object_class->finalize = fbd_feedback_vibra_pattern_finalize; + object_class->set_property = fbd_feedback_vibra_pattern_set_property; + object_class->get_property = fbd_feedback_vibra_pattern_get_property; + + base_class->is_available = fbd_feedback_vibra_pattern_is_available; + + vibra_class->start_vibra = fbd_feedback_vibra_pattern_start_vibra; + vibra_class->end_vibra = fbd_feedback_vibra_pattern_end_vibra; + + /** + * FbdFeedbackVibraPattern:magnitudes + * + * The magnitudes as array of doubles. The values specify + * the relative magnitude of a rumble. A value of 0 indicates + * a pause. + */ + props[PROP_MAGNITUDES] = + g_param_spec_boxed ("magnitudes", "", "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + /** + * FbdFeedbackVibraPattern:durations + * + * The durations as array of unsigned integers. The values specify + * an the length of each rumble. + */ + props[PROP_DURATIONS] = + g_param_spec_boxed ("durations", "", "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +fbd_feedback_vibra_pattern_init (FbdFeedbackVibraPattern *self) +{ +} + + +FbdFeedbackVibraPattern * +fbd_feedback_vibra_pattern_new (GArray *magnitudes, GArray *durations) +{ + return g_object_new (FBD_TYPE_FEEDBACK_VIBRA_PATTERN, + "magnitudes", magnitudes, + "durations", durations, + NULL); +} diff --git a/src/fbd-feedback-vibra-pattern.h b/src/fbd-feedback-vibra-pattern.h new file mode 100644 index 0000000..170905b --- /dev/null +++ b/src/fbd-feedback-vibra-pattern.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0+ + */ +#pragma once + +#include "fbd-feedback-vibra.h" + +G_BEGIN_DECLS + +#define FBD_TYPE_FEEDBACK_VIBRA_PATTERN (fbd_feedback_vibra_pattern_get_type()) + +G_DECLARE_FINAL_TYPE (FbdFeedbackVibraPattern, fbd_feedback_vibra_pattern, FBD, + FEEDBACK_VIBRA_PATTERN, + FbdFeedbackVibra); + +FbdFeedbackVibraPattern *fbd_feedback_vibra_pattern_new (GArray *magnitudes, GArray *durations); + +G_END_DECLS diff --git a/src/fbd-feedback-vibra-priv.h b/src/fbd-feedback-vibra-priv.h new file mode 100644 index 0000000..445bf3d --- /dev/null +++ b/src/fbd-feedback-vibra-priv.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0+ + */ +#pragma once + +#include + +G_BEGIN_DECLS + +void fbd_feedback_vibra_set_duration (FbdFeedbackVibra *self, guint duration); + +G_END_DECLS diff --git a/src/fbd-feedback-vibra-rumble.c b/src/fbd-feedback-vibra-rumble.c index b4ec083..e81b097 100644 --- a/src/fbd-feedback-vibra-rumble.c +++ b/src/fbd-feedback-vibra-rumble.c @@ -18,6 +18,8 @@ * The #FbdVibraVibraRumble describes the properties of a haptic feedback * event. It knows nothing about the hardware itself but calls * #FbdDevVibra for that. + * + * TODO: could be dropped in favor of the more flexible `FbdFeedbackVibraPattern`. */ enum { @@ -43,9 +45,9 @@ G_DEFINE_TYPE (FbdFeedbackVibraRumble, fbd_feedback_vibra_rumble, FBD_TYPE_FEEDB static void fbd_feedback_vibra_rumble_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) + guint property_id, + const GValue *value, + GParamSpec *pspec) { FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object); @@ -63,10 +65,10 @@ fbd_feedback_vibra_rumble_set_property (GObject *object, } static void -fbd_feedback_vibra_rumble_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) +fbd_feedback_vibra_rumble_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object); @@ -92,7 +94,7 @@ on_period_ended (FbdFeedbackVibraRumble *self) g_return_val_if_fail (FBD_IS_FEEDBACK_VIBRA_RUMBLE (self), G_SOURCE_REMOVE); if (self->periods) { - fbd_dev_vibra_rumble (dev, self->rumble, FALSE); + fbd_dev_vibra_rumble (dev, 1.0, self->rumble, FALSE); self->periods--; return G_SOURCE_CONTINUE; } @@ -129,8 +131,8 @@ fbd_feedback_vibra_rumble_start_vibra (FbdFeedbackVibra *vibra) self->periods = self->count; g_debug ("Rumble Vibra event: duration %d, rumble: %d, pause: %d, period: %d", - duration, self->rumble, self->pause, period); - fbd_dev_vibra_rumble (dev, self->rumble, TRUE); + duration, self->rumble, self->pause, period); + fbd_dev_vibra_rumble (dev, 1.0, self->rumble, TRUE); self->periods--; if (self->periods) { self->timer_id = g_timeout_add (period, (GSourceFunc) on_period_ended, self); diff --git a/src/fbd-feedback-vibra.c b/src/fbd-feedback-vibra.c index 9c4555c..a04a902 100644 --- a/src/fbd-feedback-vibra.c +++ b/src/fbd-feedback-vibra.c @@ -8,6 +8,7 @@ #include "fbd-enums.h" #include "fbd-feedback-vibra.h" +#include "fbd-feedback-vibra-priv.h" #include "fbd-feedback-manager.h" /** @@ -35,17 +36,19 @@ typedef struct _FbdFeedbackVibraPrivate { G_DEFINE_TYPE_WITH_PRIVATE (FbdFeedbackVibra, fbd_feedback_vibra, FBD_TYPE_FEEDBACK_BASE); -static gboolean +static void on_timeout_expired (FbdFeedbackVibra *self) { FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self); FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager); - fbd_dev_vibra_remove_effect (dev); - fbd_feedback_base_done (FBD_FEEDBACK_BASE(self)); - return G_SOURCE_REMOVE; + fbd_dev_vibra_stop (dev); + priv->timer_id = 0; + fbd_feedback_base_done (FBD_FEEDBACK_BASE (self)); } + static void fbd_feedback_vibra_run (FbdFeedbackBase *base) { @@ -57,9 +60,9 @@ fbd_feedback_vibra_run (FbdFeedbackBase *base) g_return_if_fail (klass->start_vibra); klass->start_vibra (self); - priv->timer_id = g_timeout_add (priv->duration, - (GSourceFunc)on_timeout_expired, - self); + priv->timer_id = g_timeout_add_once (priv->duration, + (GSourceOnceFunc)on_timeout_expired, + self); g_source_set_name_by_id (priv->timer_id, "feedback-vibra-timer"); } @@ -75,8 +78,8 @@ fbd_feedback_vibra_end (FbdFeedbackBase *base) return; g_return_if_fail (klass->end_vibra); - klass->end_vibra(self); - g_clear_handle_id(&priv->timer_id, g_source_remove); + klass->end_vibra (self); + g_clear_handle_id (&priv->timer_id, g_source_remove); fbd_feedback_base_done (FBD_FEEDBACK_BASE(self)); } @@ -102,9 +105,9 @@ fbd_feedback_vibra_set_property (GObject *object, static void fbd_feedback_vibra_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) + guint property_id, + GValue *value, + GParamSpec *pspec) { FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (object); FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self); @@ -131,13 +134,16 @@ fbd_feedback_vibra_class_init (FbdFeedbackVibraClass *klass) base_class->run = fbd_feedback_vibra_run; base_class->end = fbd_feedback_vibra_end; + /** + * FbdFeedbackVibra:duration: + * + * The total duration of the feedback event in milliseconds. + */ props[PROP_DURATION] = g_param_spec_uint ( - "duration", - "Duration", - "Vibra event duration in msecs", + "duration", "", "", 0, G_MAXUINT, FBD_FEEDBACK_VIBRA_DEFAULT_DURATION, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } @@ -156,3 +162,20 @@ fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self) priv = fbd_feedback_vibra_get_instance_private (self); return priv->duration; } + + +void +fbd_feedback_vibra_set_duration (FbdFeedbackVibra *self, guint duration) +{ + FbdFeedbackVibraPrivate *priv; + + g_return_if_fail (FBD_IS_FEEDBACK_VIBRA (self)); + + priv = fbd_feedback_vibra_get_instance_private (self); + + if (duration == priv->duration) + return; + + priv->duration = duration; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DURATION]); +} diff --git a/src/fbd-haptic-manager.c b/src/fbd-haptic-manager.c new file mode 100644 index 0000000..a886f35 --- /dev/null +++ b/src/fbd-haptic-manager.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author: Guido Günther + */ + +#define G_LOG_DOMAIN "fbd-haptic-manager" + +#include "fbd-feedback-manager.h" +#include "fbd-haptic-manager.h" +#include "fbd-feedback-base.h" +#include "fbd-feedback-profile.h" +#include "fbd-feedback-vibra-pattern.h" + +#include + +#define MAX_ITEMS 10 +#define MAX_LEN 10000 + +/** + * FbdHapticManager: + * + * Handles the org.sigxcpu.Feedback.Haptic interface + */ + +struct _FbdHapticManager { + LfbGdbusFeedbackHapticSkeleton parent; + + FbdFeedbackVibraPattern *vibra; +}; + +static void fbd_feedback_manager_feedback_haptic_iface_init (LfbGdbusFeedbackHapticIface *iface); + +G_DEFINE_TYPE_WITH_CODE (FbdHapticManager, + fbd_haptic_manager, + LFB_GDBUS_TYPE_FEEDBACK_HAPTIC_SKELETON, + G_IMPLEMENT_INTERFACE ( + LFB_GDBUS_TYPE_FEEDBACK_HAPTIC, + fbd_feedback_manager_feedback_haptic_iface_init)); + + +/** + * build_pattern: + * @pattern: The pattern as GVariant + * @out_magnitudes:(out): The magnitudes + * @out_durations: The durations + * + * Parses the pattern (applying the limits) into an array of magnitudes + * and durations. + * + * Returns: The `TRUE` if here were elements in the pattern, otherwise `FALSE` + */ +static gboolean +build_pattern (GVariant *pattern, GArray **out_magnitudes, GArray **out_durations) +{ + GVariantIter iter; + GArray *durations, *magnitudes; + double magnitude; + guint duration, n_items; + + g_variant_iter_init (&iter, pattern); + + n_items = MIN (g_variant_iter_n_children (&iter), MAX_ITEMS); + + if (n_items == 0) + return FALSE; + + durations = g_array_sized_new (FALSE, FALSE, sizeof (guint), n_items); + magnitudes = g_array_sized_new (FALSE, FALSE, sizeof (double), n_items); + + for (guint pos = 0; pos < n_items; pos++) { + g_variant_iter_next (&iter, "(du)", &magnitude, &duration); + + duration = MIN (duration, MAX_LEN); + g_array_append_val (durations, duration); + + magnitude = MAX(0.0, MIN (magnitude, 1.0)); + g_array_append_val (magnitudes, magnitude); + } + + *out_durations = durations; + *out_magnitudes = magnitudes; + + return TRUE; +} + + +static gboolean +fbd_feedback_manager_handle_vibrate (LfbGdbusFeedbackHaptic *object, + GDBusMethodInvocation *invocation, + const gchar *app_id, + GVariant *pattern) +{ + FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdHapticManager *self = FBD_HAPTIC_MANAGER (object); + FbdDevVibra *vibra_dev; + FbdFeedbackProfileLevel level; + gboolean non_empty; + g_autoptr (FbdFeedbackVibraPattern) fb = NULL; + g_autoptr (GArray) magnitudes = NULL, durations = NULL; + + g_debug ("Haptic triggered for %s", app_id); + + level = fbd_feedback_manager_get_effective_level (manager, + app_id, + FBD_FEEDBACK_PROFILE_LEVEL_QUIET, + FALSE); + if (level < FBD_FEEDBACK_PROFILE_LEVEL_QUIET) { + g_debug ("Feedback level too low for haptic"); + lfb_gdbus_feedback_haptic_complete_vibrate (object, invocation, FALSE); + return TRUE; + } + + vibra_dev = fbd_feedback_manager_get_dev_vibra (manager); + if (!vibra_dev) { + g_debug ("No haptic device"); + lfb_gdbus_feedback_haptic_complete_vibrate (object, invocation, FALSE); + return TRUE; + } + + non_empty = build_pattern (pattern, &magnitudes, &durations); + if (!non_empty) { + g_debug ("Empty pattern, ending feedback"); + fbd_haptic_manager_end_feedback (self); + g_clear_object (&self->vibra); + lfb_gdbus_feedback_haptic_complete_vibrate (object, invocation, TRUE); + return TRUE; + } + + if (self->vibra) + fbd_feedback_end (FBD_FEEDBACK_BASE (self->vibra)); + if (fbd_dev_vibra_is_busy (vibra_dev)) { + g_debug ("Haptic busy"); + /* If there's an event with haptic deny haptic patter */ + lfb_gdbus_feedback_haptic_complete_vibrate (object, invocation, FALSE); + return TRUE; + } + + fb = fbd_feedback_vibra_pattern_new (magnitudes, durations); + fbd_feedback_run (FBD_FEEDBACK_BASE (fb)); + self->vibra = g_steal_pointer (&fb); + + lfb_gdbus_feedback_haptic_complete_vibrate (object, invocation, TRUE); + return TRUE; +} + + +static void +fbd_feedback_manager_feedback_haptic_iface_init (LfbGdbusFeedbackHapticIface *iface) +{ + iface->handle_vibrate = fbd_feedback_manager_handle_vibrate; +} + + +static void +fbd_haptic_manager_finalize (GObject *object) +{ + FbdHapticManager *self = FBD_HAPTIC_MANAGER (object); + + fbd_haptic_manager_end_feedback (self); + + G_OBJECT_CLASS (fbd_haptic_manager_parent_class)->finalize (object); +} + + +static void +fbd_haptic_manager_class_init (FbdHapticManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = fbd_haptic_manager_finalize; +} + + +static void +fbd_haptic_manager_init (FbdHapticManager *self) +{ +} + + +FbdHapticManager * +fbd_haptic_manager_new (void) +{ + return g_object_new (FBD_TYPE_HAPTIC_MANAGER, NULL); +} + + +void +fbd_haptic_manager_end_feedback (FbdHapticManager *self) +{ + g_return_if_fail (FBD_IS_HAPTIC_MANAGER (self)); + + if (!self->vibra) + return; + + fbd_feedback_end (FBD_FEEDBACK_BASE (self->vibra)); + g_clear_object (&self->vibra); +} diff --git a/src/fbd-haptic-manager.h b/src/fbd-haptic-manager.h new file mode 100644 index 0000000..3579184 --- /dev/null +++ b/src/fbd-haptic-manager.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lfb-gdbus.h" +#include + +G_BEGIN_DECLS + +#define FBD_TYPE_HAPTIC_MANAGER (fbd_haptic_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (FbdHapticManager, fbd_haptic_manager, FBD, HAPTIC_MANAGER, + LfbGdbusFeedbackHapticSkeleton) + +FbdHapticManager *fbd_haptic_manager_new (void); +void fbd_haptic_manager_end_feedback (FbdHapticManager *self); + + +G_END_DECLS diff --git a/src/fbd.c b/src/fbd.c index b1bb320..655d05a 100644 --- a/src/fbd.c +++ b/src/fbd.c @@ -8,15 +8,24 @@ #include "fbd.h" #include "fbd-feedback-manager.h" +#include "fbd-haptic-manager.h" #include "lfb-names.h" #include "lfb-gdbus.h" #include #include - static GMainLoop *loop; + +static GDebugKey debug_keys[] = +{ + { .key = "force-haptic", + .value = FBD_DEBUG_FLAG_FORCE_HAPTIC, + }, +}; + + static gboolean quit_cb (gpointer user_data) { @@ -45,17 +54,29 @@ reload_cb (gpointer user_data) static void bus_acquired_cb (GDBusConnection *connection, - const gchar *name, - gpointer user_data) + const gchar *name, + gpointer user_data) { FbdFeedbackManager *manager = fbd_feedback_manager_get_default (); + FbdHapticManager *haptic_manager; - g_debug ("Bus acquired, creating manager..."); + g_assert (FBD_IS_FEEDBACK_MANAGER (manager)); + + g_debug ("Bus acquired, exporting manager..."); g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager), connection, FB_DBUS_PATH, NULL); + + haptic_manager = fbd_feedback_manager_get_haptic_manager (manager); + if (haptic_manager) { + g_debug ("Exporting haptic manager..."); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (haptic_manager), + connection, + FB_DBUS_PATH, + NULL); + } } @@ -88,11 +109,13 @@ name_lost_cb (GDBusConnection *connection, } -int main(int argc, char *argv[]) +int +main (int argc, char *argv[]) { g_autoptr(GError) err = NULL; g_autoptr(GOptionContext) opt_context = NULL; g_autoptr (FbdFeedbackManager) manager = NULL; + const char *debugenv; opt_context = g_option_context_new ("- A daemon to trigger event feedback"); if (!g_option_context_parse (opt_context, &argc, &argv, &err)) { @@ -101,6 +124,11 @@ int main(int argc, char *argv[]) return 1; } + debugenv = g_getenv ("FEEDBACKD_DEBUG"); + fbd_debug_flags = g_parse_debug_string (debugenv, + debug_keys, + G_N_ELEMENTS (debug_keys)); + manager = fbd_feedback_manager_get_default (); fbd_feedback_manager_load_theme (manager); diff --git a/src/meson.build b/src/meson.build index 1efea59..8f071a7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -30,8 +30,10 @@ sources = [ 'fbd-feedback-sound.c', 'fbd-feedback-theme.c', 'fbd-feedback-vibra.c', + 'fbd-feedback-vibra-pattern.c', 'fbd-feedback-vibra-periodic.c', 'fbd-feedback-vibra-rumble.c', + 'fbd-haptic-manager.c', 'fbd-theme-expander.c', 'fbd-udev.c', ]