Files
feedbackd/src/fbd-dev-leds.c
T
Guido Günther f9f248ef23 fbd-dev-led: Sort leds by priority
This makes sure we e.g. prefer multicolor leds over single color ones
and those that can be on insuspend (qcom) over those that can't.

We can extend this to allow to set the priority via udev/hwdb in the
future.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-05-06 14:54:52 +00:00

270 lines
6.4 KiB
C

/*
* 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-led.h"
#include "fbd-dev-led-flash.h"
#include "fbd-dev-led-multicolor.h"
#include "fbd-dev-led-qcom.h"
#include "fbd-dev-led-qcom-multicolor.h"
#include "fbd-dev-leds.h"
#include "fbd-feedback-led.h"
#include "fbd-udev.h"
#include <gio/gio.h>
#define LED_SUBSYSTEM "leds"
/**
* FbdDevLeds:
*
* LED device interface
*
* #FbdDevLeds is used to interface with all LEDs detected in sysfs
* It currently only supports one pattern per led at a time.
*/
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 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 = FBD_DEV_LED (l->data);
if (fbd_dev_led_supports_color (led, color))
return led;
}
/* If we did not match a color pick the first non flash LED */
for (GSList *l = self->leds; l != NULL; l = l->next) {
FbdDevLed *led = FBD_DEV_LED (l->data);
if (!fbd_dev_led_supports_color (led, FBD_FEEDBACK_LED_COLOR_FLASH))
return led;
}
return NULL;
}
static FbdDevLed*
probe_led (GUdevDevice *dev, GError **error) {
FbdDevLed *led = NULL;
led = fbd_dev_led_qcom_multicolor_new (dev, error);
if (led != NULL) {
g_debug ("Discovered QCOM multicolor LED");
return led;
}
g_clear_error (error);
led = fbd_dev_led_qcom_new (dev, error);
if (led != NULL) {
g_debug ("Discovered QCOM single color LED");
return led;
}
g_clear_error (error);
led = fbd_dev_led_multicolor_new (dev, error);
if (led != NULL) {
g_debug ("Discovered multicolor LED");
return led;
}
g_clear_error (error);
led = fbd_dev_led_flash_new (dev, error);
if (led != NULL) {
g_debug ("Discovered flash LED");
return led;
}
g_clear_error (error);
led = fbd_dev_led_new (dev, error);
if (led != NULL) {
g_debug ("Discovered single color LED");
return led;
}
g_debug ("Unable to determine LED driver");
return NULL;
}
static int
priority_cmp (gconstpointer a, gconstpointer b)
{
int prio_a = fbd_dev_led_get_priority ((FbdDevLed *)a);
int prio_b = fbd_dev_led_get_priority ((FbdDevLed *)b);
return prio_b - prio_a;
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
const gchar * const subsystems[] = { LED_SUBSYSTEM, NULL };
FbdDevLeds *self = FBD_DEV_LEDS (initable);
g_autolist (GUdevDevice) leds = NULL;
GSList *usable_leds = NULL;
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) {
g_autoptr (GError) err = NULL;
GUdevDevice *dev = G_UDEV_DEVICE (l->data);
FbdDevLed *led;
if (g_strcmp0 (g_udev_device_get_property (dev, FEEDBACKD_UDEV_ATTR),
FEEDBACKD_UDEV_VAL_LED)) {
continue;
}
led = probe_led (dev, &err);
if (led)
usable_leds = g_slist_append (usable_leds, led);
}
/* TODO: listen for new leds via udev events */
if (usable_leds) {
self->leds = g_slist_sort (usable_leds, priority_cmp);
} else {
g_set_error (error,
G_FILE_ERROR, G_FILE_ERROR_FAILED,
"No usable LEDs found");
}
return !!usable_leds;
}
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)g_object_unref);
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 LED to use for the LED pattern
* @rgb: The rgb value to set (if `color` indicates an RGB led)
* @max_brightness_percentage: 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,
FbdLedRgbColor *rgb,
guint max_brightness_percentage,
guint freq)
{
FbdDevLed *led;
g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
g_return_val_if_fail (max_brightness_percentage <= 100.0, FALSE);
led = find_led_by_color (self, color);
if (!led) {
g_warning_once ("No usable led found");
return FALSE;
}
fbd_dev_led_set_color (led, color, rgb);
return fbd_dev_led_start_periodic (led, max_brightness_percentage, freq);
}
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);
if (!led) {
g_warning_once ("No usable led found");
return FALSE;
}
return fbd_dev_led_set_brightness (led, 0);
}
/**
* fbd_dev_leds_has_led:
* @self: The FbdDevLeds
* @color: The color type to check
*
* Whether there's a usable LED of the given type
*
* Returns: `TRUE` if there's a at least one usable LED, otherwise `FALSE`
*/
gboolean
fbd_dev_leds_has_led (FbdDevLeds *self, FbdFeedbackLedColor color)
{
g_return_val_if_fail (FBD_IS_DEV_LEDS (self), FALSE);
return !!find_led_by_color (self, color);
}