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',
]