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:
committed by
Sebastian Krzyszkowiak
parent
2f083ea5ae
commit
a5b4f99412
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user