feedbackd: Support visual feedback via LEDs

Currently all LED feedbacks are infinite we can improve that
later on by using the 'repeat' attribute of the LED pattern
trigger.
This commit is contained in:
Guido Günther
2020-07-05 19:25:44 +00:00
committed by Sebastian Krzyszkowiak
parent 2f083ea5ae
commit a5b4f99412
12 changed files with 623 additions and 0 deletions
+1
View File
@@ -80,6 +80,7 @@ available feedback types are:
- Sound (an audible sound from the sound naming spec)
- VibraRumble: haptic motor rumbling
- VibraPeriodic: periodic feedback from the haptic motor
- Led: Feedback via blinking LEDs
You can check the feedback theme and the classes (prefixed with Fbd)
for available properties. Note that the feedback theme API (including
+245
View File
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*
* See Documentation/ABI/testing/sysfs-class-led-trigger-pattern
*/
#define G_LOG_DOMAIN "fbd-dev-leds"
#include "fbd.h"
#include "fbd-enums.h"
#include "fbd-dev-leds.h"
#include "fbd-feedback-led.h"
#include "fbd-udev.h"
#include <gio/gio.h>
/**
* SECTION:fbd-dev-led
* @short_description: LED device interface
* @Title: FbdDevLeds
*
* #FbdDevLeds is used to interface with LEDS via sysfs
* It currently only supports one pattern per led at a time.
*/
#define LED_BRIGHTNESS_ATTR "brightness"
#define LED_PATTERN_ATTR "pattern"
#define LED_SUBSYSTEM "leds"
typedef struct _FbdDevLed {
GUdevDevice *dev;
guint max_brightness;
/*
* We just use the colors from the feedback until we
* do rgb mixing, etc
*/
FbdFeedbackLedColor color;
} FbdDevLed;
typedef struct _FbdDevLeds {
GObject parent;
GUdevClient *client;
GSList *leds;
} FbdDevLeds;
static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE (FbdDevLeds, fbd_dev_leds, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
static void
fbd_dev_led_free (FbdDevLed *led)
{
g_object_unref (led->dev);
}
static gboolean
fbd_dev_led_set_brightness (FbdDevLed *led, guint brightness)
{
g_autoptr (GError) err = NULL;
if (!fbd_udev_set_sysfs_path_attr_as_int (led->dev, LED_BRIGHTNESS_ATTR, 0, &err)) {
g_warning ("Failed to setup brightness: %s", err->message);
return FALSE;
}
return TRUE;
}
static FbdDevLed *
find_led_by_color (FbdDevLeds *self, FbdFeedbackLedColor color)
{
g_return_val_if_fail (self->leds, NULL);
for (GSList *l = self->leds; l != NULL; l = l->next) {
FbdDevLed *led = l->data;
if (led->color == color)
return led;
}
/* If we did not match a color pick the first */
return self->leds->data;
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
const gchar * const subsystems[] = { LED_SUBSYSTEM, NULL };
FbdDevLeds *self = FBD_DEV_LEDS (initable);
g_autoptr (GList) leds;
gboolean found = FALSE;
self->client = g_udev_client_new (subsystems);
leds = g_udev_client_query_by_subsystem (self->client, LED_SUBSYSTEM);
for (GList *l = leds; l != NULL; l = l->next) {
GUdevDevice *dev = G_UDEV_DEVICE (l->data);
const gchar *name, *path;
FbdDevLed *led = NULL;
if (g_strcmp0 (g_udev_device_get_property (dev, FEEDBACKD_UDEV_ATTR),
FEEDBACKD_UDEV_VAL_LED)) {
continue;
}
name = g_udev_device_get_name (dev);
/* We don't know anything about diffusors that can combine different
color LEDSs so go with fixed colors until the kernel gives us
enough information */
for (int i = 0; i <= FBD_FEEDBACK_LED_COLOR_LAST; i++) {
g_autofree gchar *color;
gchar *c;
c = strrchr (g_enum_to_string (FBD_TYPE_FEEDBACK_LED_COLOR, i), '_');
color = g_strdup (g_ascii_strdown (c+1, -1));
if (g_strstr_len (name, -1, color)) {
g_autoptr (GError) err = NULL;
guint brightness = g_udev_device_get_sysfs_attr_as_int (dev, "max_brightness");
if (!brightness)
continue;
led = g_malloc0 (sizeof(FbdDevLed));
led->dev = dev;
led->max_brightness = brightness;
path = g_udev_device_get_sysfs_path (dev);
g_debug ("LED at '%s' usable", path);
self->leds = g_slist_append (self->leds, led);
found = TRUE;
break;
}
}
if (!led)
g_object_unref (dev);
}
/* TODO: listen for new leds via udev events */
if (!found) {
g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR_FAILED,
"No usable LEDs found");
}
return found;
}
static void
initable_iface_init (GInitableIface *iface)
{
iface->init = initable_init;
}
static void
fbd_dev_leds_dispose (GObject *object)
{
FbdDevLeds *self = FBD_DEV_LEDS (object);
g_clear_object (&self->client);
g_slist_free_full (self->leds, (GDestroyNotify)fbd_dev_led_free);
self->leds = NULL;
G_OBJECT_CLASS (fbd_dev_leds_parent_class)->dispose (object);
}
static void
fbd_dev_leds_class_init (FbdDevLedsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = fbd_dev_leds_dispose;
}
static void
fbd_dev_leds_init (FbdDevLeds *self)
{
}
FbdDevLeds *
fbd_dev_leds_new (GError **error)
{
return FBD_DEV_LEDS (g_initable_new (FBD_TYPE_DEV_LEDS,
NULL,
error,
NULL));
}
/**
* fbd_dev_leds_start_periodic:
* @self: The #FbdDevLeds
* @color: The color to use for the LED pattern
* @max_brightness: The max brightness (in percent) to use for the pattern
* @freq: The pattern's frequency in mHz
*
* Start periodic feedback.
*/
gboolean
fbd_dev_leds_start_periodic (FbdDevLeds *self, FbdFeedbackLedColor color,
guint max_brightness, guint freq)
{
FbdDevLed *led;
gdouble max;
gdouble t;
g_autofree gchar *str = NULL;
g_autoptr (GError) err = NULL;
gboolean success;
g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
led = find_led_by_color (self, color);
g_return_val_if_fail (led, FALSE);
max = led->max_brightness * (max_brightness / 100.0);
/* ms mHz T/2 */
t = 1000.0 * 1000.0 / freq / 2.0;
str = g_strdup_printf ("0 %d %d %d\n", (gint)t, (gint)max, (gint)t);
g_debug ("Freq %d mHz, Brightness: %d%%, Blink pattern: %s", freq, max_brightness, str);
success = fbd_udev_set_sysfs_path_attr_as_string (led->dev, LED_PATTERN_ATTR, str, &err);
if (!success)
g_warning ("Failed to set led pattern: %s", err->message);
return success;
}
gboolean
fbd_dev_leds_stop (FbdDevLeds *self, FbdFeedbackLedColor color)
{
FbdDevLed *led;
g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
led = find_led_by_color (self, color);
g_return_val_if_fail (led, FALSE);
return fbd_dev_led_set_brightness (led, 0);
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 Purism SPC
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include "fbd-feedback-led.h"
#include <glib-object.h>
#include <gudev/gudev.h>
G_BEGIN_DECLS
#define FBD_TYPE_DEV_LEDS (fbd_dev_leds_get_type ())
G_DECLARE_FINAL_TYPE (FbdDevLeds, fbd_dev_leds, FBD, DEV_LEDS, GObject);
FbdDevLeds *fbd_dev_leds_new (GError **error);
gboolean fbd_dev_leds_start_periodic (FbdDevLeds *self,
FbdFeedbackLedColor color,
guint max_brighness, guint freq);
gboolean fbd_dev_leds_stop (FbdDevLeds *self,
FbdFeedbackLedColor color);
G_END_DECLS
+190
View File
@@ -0,0 +1,190 @@
/*
* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*/
#define G_LOG_DOMAIN "fbd-feedback-led"
#include "fbd-enums.h"
#include "fbd-dev-leds.h"
#include "fbd-feedback-led.h"
#include "fbd-feedback-manager.h"
/**
* SECTION:fbd-feedback-led
* @short_description: Describes a led feedback
* @Title: FbdFeedbackLed
*
* The #FbdFeedbackLed describes a feedback via an LED. It currently
* only supports periodic patterns.
*/
enum {
PROP_0,
PROP_FREQUENCY,
PROP_COLOR,
PROP_MAX_BRIGHTNESS,
PROP_PRIORITY,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
typedef struct _FbdFeedbackLed {
FbdFeedbackBase parent;
guint frequency;
guint priority;
guint max_brightness;
FbdFeedbackLedColor color;
} FbdFeedbackLed;
G_DEFINE_TYPE (FbdFeedbackLed, fbd_feedback_led, FBD_TYPE_FEEDBACK_BASE)
static void
fbd_feedback_led_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
FbdFeedbackLed *self = FBD_FEEDBACK_LED (object);
switch (property_id) {
case PROP_FREQUENCY:
self->frequency = g_value_get_uint (value);
break;
case PROP_PRIORITY:
self->priority = g_value_get_uint (value);
break;
case PROP_MAX_BRIGHTNESS:
self->max_brightness = g_value_get_uint (value);
break;
case PROP_COLOR:
self->color = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
fbd_feedback_led_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
FbdFeedbackLed *self = FBD_FEEDBACK_LED (object);
switch (property_id) {
case PROP_FREQUENCY:
g_value_set_uint (value, self->frequency);
break;
case PROP_PRIORITY:
g_value_set_uint (value, self->priority);
break;
case PROP_MAX_BRIGHTNESS:
g_value_set_uint (value, self->max_brightness);
break;
case PROP_COLOR:
g_value_set_enum (value, self->color);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
fbd_feedback_led_run (FbdFeedbackBase *base)
{
FbdFeedbackLed *self = FBD_FEEDBACK_LED (base);
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
FbdDevLeds *dev = fbd_feedback_manager_get_dev_leds (manager);
g_debug ("Periodic led feeedback: self->max_brightness, self->frequency");
/* FIXME: handle priority */
fbd_dev_leds_start_periodic (dev,
self->color,
self->max_brightness,
self->frequency);
}
static void
fbd_feedback_led_end (FbdFeedbackBase *base)
{
FbdFeedbackLed *self = FBD_FEEDBACK_LED (base);
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
FbdDevLeds *dev = fbd_feedback_manager_get_dev_leds (manager);
fbd_dev_leds_stop (dev, FBD_FEEDBACK_LED_COLOR_BLUE);
fbd_feedback_base_done (FBD_FEEDBACK_BASE (self));
}
static void
fbd_feedback_led_class_init (FbdFeedbackLedClass *klass)
{
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = fbd_feedback_led_set_property;
object_class->get_property = fbd_feedback_led_get_property;
base_class->run = fbd_feedback_led_run;
base_class->end = fbd_feedback_led_end;
props[PROP_FREQUENCY] =
g_param_spec_uint (
"frequency",
"Frequency",
"Led event frequency in mHz",
0, G_MAXUINT, 0,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
props[PROP_COLOR] =
g_param_spec_enum (
"color",
"Color",
"The LED color",
FBD_TYPE_FEEDBACK_LED_COLOR,
FBD_FEEDBACK_LED_COLOR_WHITE,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* FbdFeedbackLed:priority:
*
* Priority of the led pattern. Led devices can only display a limited
* amount of patterns at time. In this case the pattern with the highest
* priority wins.
*/
props[PROP_PRIORITY] =
g_param_spec_uint (
"priority",
"Priority",
"The LED pattern priority",
0, 255, 0,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* FbdFeedbackLed:max-brightness:
*
* The maximum brightness in the LED pattern in percent of the LEDs
* maximum brightness.
*/
props[PROP_MAX_BRIGHTNESS] =
g_param_spec_uint (
"max-brightness",
"Maximum brightness percentage",
"Maximum brightness in the LED pattern",
1, 100, 100,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
}
static void
fbd_feedback_led_init (FbdFeedbackLed *self)
{
self->max_brightness = 100;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2020 Purism SPC
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include "fbd-feedback-base.h"
G_BEGIN_DECLS
typedef enum _FbdFeedbackLedColor {
FBD_FEEDBACK_LED_COLOR_WHITE = 0,
FBD_FEEDBACK_LED_COLOR_RED = 1,
FBD_FEEDBACK_LED_COLOR_GREEN = 2,
FBD_FEEDBACK_LED_COLOR_BLUE = 3,
FBD_FEEDBACK_LED_COLOR_LAST = FBD_FEEDBACK_LED_COLOR_BLUE,
} FbdFeedbackLedColor;
#define FBD_TYPE_FEEDBACK_LED (fbd_feedback_led_get_type ())
G_DECLARE_FINAL_TYPE (FbdFeedbackLed, fbd_feedback_led, FBD, FEEDBACK_LED, FbdFeedbackBase);
G_END_DECLS
+16
View File
@@ -9,6 +9,7 @@
#include "lfb-names.h"
#include "fbd.h"
#include "fbd-dev-vibra.h"
#include "fbd-dev-leds.h"
#include "fbd-event.h"
#include "fbd-feedback-vibra.h"
#include "fbd-feedback-manager.h"
@@ -48,6 +49,7 @@ typedef struct _FbdFeedbackManager {
GUdevClient *client;
FbdDevVibra *vibra;
FbdDevSound *sound;
FbdDevLeds *leds;
} FbdFeedbackManager;
static void fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface);
@@ -141,6 +143,12 @@ init_devices (FbdFeedbackManager *self)
if (!self->vibra)
g_debug ("No vibra capable device found");
self->leds = fbd_dev_leds_new (&err);
if (!self->leds) {
g_debug ("Failed to init leds device: %s", err->message);
g_clear_error (&err);
}
self->sound = fbd_dev_sound_new (&err);
if (!self->sound) {
g_warning ("Failed to init sound device: %s", err->message);
@@ -409,6 +417,14 @@ fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self)
return self->sound;
}
FbdDevLeds *
fbd_feedback_manager_get_dev_leds (FbdFeedbackManager *self)
{
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
return self->leds;
}
gboolean
fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile)
{
+3
View File
@@ -6,7 +6,9 @@
#pragma once
#include "fbd-dev-vibra.h"
#include "fbd-dev-leds.h"
#include "fbd-dev-sound.h"
#include "lfb-gdbus.h"
#include <glib-object.h>
@@ -19,6 +21,7 @@ G_DECLARE_FINAL_TYPE (FbdFeedbackManager, fbd_feedback_manager, FBD, FEEDBACK_MA
FbdFeedbackManager *fbd_feedback_manager_get_default (void);
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);
gboolean fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile);
G_END_DECLS
+2
View File
@@ -9,6 +9,7 @@
#include "fbd-feedback-dummy.h"
#include "fbd-feedback-profile.h"
#include "fbd-feedback-sound.h"
#include "fbd-feedback-led.h"
#include "fbd-feedback-vibra-periodic.h"
#include "fbd-feedback-vibra-rumble.h"
@@ -83,6 +84,7 @@ feedback_get_type (JsonNode *feedback_node)
/* Ensure all feedback types so the json parsing can use them */
g_type_ensure (FBD_TYPE_FEEDBACK_DUMMY);
g_type_ensure (FBD_TYPE_FEEDBACK_LED);
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_PERIODIC);
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_RUMBLE);
g_type_ensure (FBD_TYPE_FEEDBACK_SOUND);
+86
View File
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*
*/
#define G_LOG_DOMAIN "fbd-udev"
#include "fbd-udev.h"
#include <gio/gio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
gboolean
fbd_udev_set_sysfs_path_attr_as_string (GUdevDevice *dev, const gchar *attr,
const gchar *s, GError **err)
{
gint fd;
int len;
g_autofree gchar *path = g_strjoin ("/", g_udev_device_get_sysfs_path (dev),
attr, NULL);
fd = open (path, O_WRONLY|O_TRUNC, 0666);
if (fd < 0) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to open %s: %s",
path, strerror (errno));
return FALSE;
}
len = strlen (s);
if (write (fd, s, len) < 0) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to write %s to %s: %s",
s, path, strerror (errno));
close (fd);
return FALSE;
}
if (close (fd) < 0) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to close %s: %s",
path, strerror (errno));
return FALSE;
}
return TRUE;
}
gboolean
fbd_udev_set_sysfs_path_attr_as_int (GUdevDevice *dev, const gchar *attr,
gint val, GError **err)
{
gint fd;
int len;
g_autofree gchar *s = NULL;
g_autofree gchar *path = g_strjoin ("/", g_udev_device_get_sysfs_path (dev),
attr, NULL);
fd = open (path, O_WRONLY|O_TRUNC, 0666);
if (fd < 0) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to open %s: %s",
path, strerror (errno));
return FALSE;
}
s = g_strdup_printf ("%d", val);
len = strlen (s);
if (write (fd, s, len) < len) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to write %s: %s",
path, strerror (errno));
close (fd);
return FALSE;
}
if (close (fd) < 0) {
g_set_error (err, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to close %s: %s",
path, strerror (errno));
return FALSE;
}
return TRUE;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 Purism SPC
*
* SPDX-License-Identifier: GPL-3.0+
*/
#pragma once
#include <glib.h>
#include <gudev/gudev.h>
G_BEGIN_DECLS
gboolean fbd_udev_set_sysfs_path_attr_as_string (GUdevDevice *dev, const gchar *attr,
const gchar *s, GError **err);
gboolean fbd_udev_set_sysfs_path_attr_as_int (GUdevDevice *dev, const gchar *attr,
gint val, GError **err);
G_END_DECLS
+7
View File
@@ -4,6 +4,13 @@
G_BEGIN_DECLS
/*
* The udev attribute and values devices must have to be handled by
* fbd feedbackd
*/
#define FEEDBACKD_UDEV_ATTR "FEEDBACKD_TYPE"
#define FEEDBACKD_UDEV_VAL_LED "led"
typedef enum {
FBD_ERROR_FAILED = 0,
} FbdError;
+4
View File
@@ -3,6 +3,7 @@ if get_option('daemon')
fbd_enum_headers = files([
'fbd-event.h',
'fbd-feedback-led.h',
'fbd-feedback-vibra.h',
])
fbd_enum_sources = gnome.mkenums_simple('fbd-enums',
@@ -14,9 +15,11 @@ sources = [
'fbd.c',
'fbd-dev-vibra.c',
'fbd-dev-sound.c',
'fbd-dev-leds.c',
'fbd-event.c',
'fbd-feedback-base.c',
'fbd-feedback-dummy.c',
'fbd-feedback-led.c',
'fbd-feedback-manager.c',
'fbd-feedback-profile.c',
'fbd-feedback-sound.c',
@@ -24,6 +27,7 @@ sources = [
'fbd-feedback-vibra.c',
'fbd-feedback-vibra-periodic.c',
'fbd-feedback-vibra-rumble.c',
'fbd-udev.c',
]
fbd_deps = [