Commit a5b4f994 authored by Guido Gunther's avatar Guido Gunther Committed by Sebastian Krzyszkowiak
Browse files

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.
parent 2f083ea5
......@@ -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
......
/*
* 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);
}
/*
* 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
/*
* 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;
}
/*
* 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
......@@ -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)
{
......
......@@ -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
......@@ -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);
......
/*
* 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;