Merge branch 'vibra-pattern' into 'main'

Add org.sigxcup.org.Feedback.Haptic interface

Closes #80

See merge request Librem5/feedbackd!147
This commit is contained in:
Guido Günther
2024-12-18 16:46:56 +00:00
16 changed files with 810 additions and 49 deletions
+21
View File
@@ -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
+4 -1
View File
@@ -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,
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<node>
<!-- org.sigxcpu.Feedback.Haptic
@short_description: Interface to make a device vibrate
This D-Bus interface is used to make a device's haptic motor
vibrate. This is can be useful e.g. for games.
To provider user feedback the event based interface should be
preferred.
-->
<interface name="org.sigxcpu.Feedback.Haptic">
<!--
Vibrate:
@app_id: The application id usually in "reverse DNS" format
@pattern: The vibration pattern.
@success: Whether vibration was triggered
Triggers the given vibration pattern on the haptic device. The
pattern is a sequence of relative amplitude and duration pairs.
The amplitude must be between 0.0 and 1.0.
-->
<method name="Vibrate">
<arg direction="in" name="app_id" type="s"/>
<arg direction="in" name="pattern" type="a(du)"/>
<arg direction="out" name="success" type="b"/>
</method>
</interface>
</node>
+33 -5
View File
@@ -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;
}
+5 -2
View File
@@ -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
+75 -10
View File
@@ -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 <gmobile.h>
@@ -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;
}
+13
View File
@@ -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 <glib-object.h>
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
+284
View File
@@ -0,0 +1,284 @@
/*
* Copyright (C) 2024 The Phosh Developers
*
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*/
#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);
}
+20
View File
@@ -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
+14
View File
@@ -0,0 +1,14 @@
/*
* Copyright (C) 2024 The Phosh Developers
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include <fbd-feedback-vibra.h>
G_BEGIN_DECLS
void fbd_feedback_vibra_set_duration (FbdFeedbackVibra *self, guint duration);
G_END_DECLS
+12 -10
View File
@@ -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);
+39 -16
View File
@@ -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]);
}
+200
View File
@@ -0,0 +1,200 @@
/*
* Copyright (C) 2024 The Phosh Developers
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Author: Guido Günther <agx@sigxcpu.org>
*/
#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 <glib.h>
#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);
}
+23
View File
@@ -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 <glib-object.h>
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
+33 -5
View File
@@ -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 <gio/gio.h>
#include <glib-unix.h>
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);
+2
View File
@@ -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',
]