/**
* @file led.c
* LED module -- this handles the LED logic for MCE
*
* Copyright (c) 2006 - 2011 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (c) 2012 - 2020 Jolla Ltd.
* Copyright (c) 2020 Open Mobile Platform LLC.
*
* @author David Weinehall
* @author Tapio Rantala
* @author Santtu Lakkala
* @author Jukka Turunen
* @author Simo Piiroinen
* @author Islam Amer
* @author Filip Matijević
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mce. If not, see .
*/
#include "led.h"
#include "../mce.h"
#include "../mce-log.h"
#include "../mce-io.h"
#include "../mce-lib.h"
#include "../mce-hal.h"
#include "../mce-conf.h"
#include "../mce-setting.h"
#include "../mce-dbus.h"
#include "../mce-hbtimer.h"
#ifdef ENABLE_HYBRIS
# include "../mce-hybris.h"
#endif
#include "../libwakelock.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if 0 // DEBUG: make all logging from this module "critical"
# undef mce_log
# define mce_log(LEV, FMT, ARGS...) \
mce_log_file(LL_CRIT, __FILE__, __FUNCTION__, FMT , ## ARGS)
#endif
/** Module name */
#define MODULE_NAME "led"
/** Functionality provided by this module */
static const gchar *const provides[] = { MODULE_NAME, NULL };
/** Module information */
G_MODULE_EXPORT module_info_struct module_info = {
/** Name of the module */
.name = MODULE_NAME,
/** Module provides */
.provides = provides,
/** Module priority */
.priority = 100
};
/** The pattern queue */
static GQueue *pattern_stack = NULL;
/** The pattern combination rule queue */
static GQueue *combination_rule_list = NULL;
/** The pattern combination rule queue */
static GQueue *combination_rule_xref_list = NULL;
/** The D-Bus controlled LED switch */
static gboolean led_enabled = FALSE;
/** Fields in the patterns */
typedef enum {
/** Pattern priority field */
PATTERN_PRIO_FIELD = 0,
/** Pattern screen display policy field */
PATTERN_SCREEN_ON_FIELD = 1,
/** Pattern timeout field */
PATTERN_TIMEOUT_FIELD = 2,
/** On-period field for direct-controlled monochrome patterns */
PATTERN_ON_PERIOD_FIELD = 3,
/** R-channel pattern field for NJoy-controlled RGB patterns */
PATTERN_R_CHANNEL_FIELD = 3,
/** LED-muxing field for Lysti-controlled RGB patterns */
PATTERN_MUXING_FIELD = 3,
/**
* Engine channel field for Lysti-controlled monochrome patterns
* and NJoy-controlled monochrome patterns
*/
PATTERN_E_CHANNEL_FIELD = 3,
/** Number of fields used by Lysti-controlled monochrome patterns */
NUMBER_OF_PATTERN_FIELDS_LYSTI_MONO = 4,
/** Number of fields used by NJoy-controlled monochrome patterns */
NUMBER_OF_PATTERN_FIELDS_NJOY_MONO = 4,
/** Off-period field for direct-controlled monochrome patterns */
PATTERN_OFF_PERIOD_FIELD = 4,
/** G-channel pattern field for NJoy-controlled RGB patterns */
PATTERN_G_CHANNEL_FIELD = 4,
/** Engine channel 1 field for Lysti-controlled RGB patterns */
PATTERN_E1_CHANNEL_FIELD = 4,
/** Pattern brightness field for direct-controlled monochrome patterns */
PATTERN_BRIGHTNESS_FIELD = 5,
/** B-channel pattern field for NJoy-controlled RGB patterns */
PATTERN_B_CHANNEL_FIELD = 5,
/** Engine channel 2 field for Lysti-controlled RGB patterns */
PATTERN_E2_CHANNEL_FIELD = 5,
/**
* Number of fields used by Lysti-controlled RGB patterns,
* NJoy-controlled RGB patterns,
* and monochrome direct-controlled patterns
*/
NUMBER_OF_PATTERN_FIELDS = 6
} pattern_field;
/**
* Size of each LED channel
*
* Multiply the channel size by 2 since we store hexadecimal ASCII
*/
#define CHANNEL_SIZE 32 * 2
/** Structure holding LED patterns */
typedef struct {
gchar *name; /**< Pattern name */
gint priority; /**< Pattern priority */
gint policy; /**< Show pattern when screen is on? */
gint timeout; /**< Auto-deactivate timeout in seconds */
mce_hbtimer_t *timeout_id; /**< Timer for auto-deactivate */
gint on_period; /**< Pattern on-period in ms */
gint off_period; /**< Pattern off-period in ms */
gint brightness; /**< Pattern brightness */
gboolean active; /**< Is the pattern active? */
gboolean enabled; /**< Is the pattern enabled? */
guint engine1_mux; /**< Muxing for engine 1 */
guint engine2_mux; /**< Muxing for engine 2 */
/** Pattern for the R-channel/engine 1 */
gchar channel1[CHANNEL_SIZE + 1];
/** Pattern for the G-channel/engine 2 */
gchar channel2[CHANNEL_SIZE + 1];
/** Pattern for the B-channel */
gchar channel3[CHANNEL_SIZE + 1];
guint setting_id; /**< Callback ID for GConf entry */
guint rgb_color; /**< RGB24 data for libhybris use */
gboolean undecided; /**< Flag for policy=6 lock in */
} pattern_struct;
/** Pattern combination rule struct; this is also used for cross-referencing */
typedef struct {
/** Name of the combined pattern */
gchar *rulename;
/** List of pre-requisite patterns */
GQueue *pre_requisites;
} combination_rule_struct;
/** Pointer to the top pattern */
static pattern_struct *active_pattern = NULL;
/** The active brightness */
static gint active_brightness = -1;
/** Currently driven leds */
static guint current_lysti_led_pattern = 0;
/** Brightness levels for the mono-LED */
static const gchar *const brightness_map[] = {
BRIGHTNESS_LEVEL_0,
BRIGHTNESS_LEVEL_1,
BRIGHTNESS_LEVEL_2,
BRIGHTNESS_LEVEL_3,
BRIGHTNESS_LEVEL_4,
BRIGHTNESS_LEVEL_5,
BRIGHTNESS_LEVEL_6,
BRIGHTNESS_LEVEL_7,
BRIGHTNESS_LEVEL_8,
BRIGHTNESS_LEVEL_9,
BRIGHTNESS_LEVEL_10,
BRIGHTNESS_LEVEL_11,
BRIGHTNESS_LEVEL_12,
BRIGHTNESS_LEVEL_13,
BRIGHTNESS_LEVEL_14,
BRIGHTNESS_LEVEL_15
};
/** LED type */
typedef enum {
/** LED type unset */
LED_TYPE_UNSET = -1,
/** No LED available */
LED_TYPE_NONE = 0,
/** Monochrome LED, direct LED control */
LED_TYPE_DIRECT_MONO = 1,
/** RGB LED, NJoy (LP5521) LED controller */
LED_TYPE_NJOY_RGB = 2,
/** Monochrome LED, NJoy (LP5521) LED controller */
LED_TYPE_NJOY_MONO = 3,
/** RGB LED, Lysti (LP5523) LED controller */
LED_TYPE_LYSTI_RGB = 4,
/** Monochrome LED, Lysti (LP5523) LED controller */
LED_TYPE_LYSTI_MONO = 5,
#ifdef ENABLE_HYBRIS
/** Android adaptation via libhybris */
LED_TYPE_HYBRIS = 6,
#endif
} led_type_t;
/**
* The configuration group containing the LED pattern
*/
static const gchar *led_pattern_group = NULL;
/** Path to monochrome/red channel LED current path */
static output_state_t led_current_rm_output =
{
.context = "led_current_rm",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to green channel LED current path */
static output_state_t led_current_g_output =
{
.context = "led_current_g",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to blue channel LED current path */
static output_state_t led_current_b_output =
{
.context = "led_current_b",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to monochrome/red channel LED brightness path */
static output_state_t led_brightness_rm_output =
{
.context = "led_brightness_rm",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to red channel LED brightness path */
static output_state_t led_brightness_g_output =
{
.context = "led_brightness_g",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to blue channel LED brightness path */
static output_state_t led_brightness_b_output =
{
.context = "led_brightness_b",
.truncate_file = TRUE,
.close_on_exit = FALSE,
};
/** Path to engine 1 mode */
static gchar *engine1_mode_path = NULL;
/** Path to engine 2 mode */
static gchar *engine2_mode_path = NULL;
/** Path to engine 3 mode */
static gchar *engine3_mode_path = NULL;
/** Path to engine 1 load */
static gchar *engine1_load_path = NULL;
/** Path to engine 2 load */
static gchar *engine2_load_path = NULL;
/** Path to engine 3 load */
static gchar *engine3_load_path = NULL;
/** Path to engine 1 leds */
static gchar *engine1_leds_path = NULL;
/** Path to engine 2 leds */
static gchar *engine2_leds_path = NULL;
/** Path to engine 3 leds */
static gchar *engine3_leds_path = NULL;
/** Cached display state */
static display_state_t display_state_curr = MCE_DISPLAY_UNDEF;
/** Cached system state */
static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF;
/** Cached led brightness */
static gint led_brightness = 0;
/** Maximum LED brightness
*
* The led_brightness_pipe is initialized to maximum_led_brightness
* value and never modified. There is an ALS based filter for
* led_brightness_pipe that converts the led brightness profile
* values [%] into 0 ... maximum_led_brightness range. The latter are
* then handled by the led_brightness_trigger() function below. */
static guint maximum_led_brightness = MAXIMUM_LYSTI_MONOCHROME_LED_CURRENT;
/* Function prototypes */
static void disable_reno (void);
static led_type_t get_led_type (void);
static gint queue_find (gconstpointer data, gconstpointer userdata);
static gint queue_prio_compare (gconstpointer entry1, gconstpointer entry2, gpointer userdata);
static void lysti_set_brightness (gint brightness);
static void njoy_set_brightness (gint brightness);
static void mono_set_brightness (gint brightness);
static void hybris_set_brightness (gint brightness);
static void lysti_disable_led (void);
static void njoy_disable_led (void);
static void mono_disable_led (void);
static void hybris_disable_led (void);
static void disable_led (void);
static pattern_struct *led_pattern_create (void);
static void led_pattern_delete (pattern_struct *self);
static void led_pattern_set_active (pattern_struct *self, gboolean active);
static bool led_pattern_should_breathe (const pattern_struct *self);
static bool led_pattern_can_breathe (const pattern_struct *self);
static gboolean led_pattern_timeout_cb (gpointer data);
static void lysti_program_led (const pattern_struct *const pattern);
static void njoy_program_led (const pattern_struct *const pattern);
static void mono_program_led (const pattern_struct *const pattern);
static void hybris_program_led (const pattern_struct *const pattern);
static void program_led (const pattern_struct *const pattern);
static void allow_sw_breathing (bool enable);
static void led_set_active_pattern (pattern_struct *pattern);
static gboolean display_off_p (display_state_t state);
static void led_update_active_pattern (void);
static pattern_struct *find_pattern_struct (const gchar *const name);
static void update_combination_rule (gpointer name, gpointer data);
static void update_combination_rules (const gchar *const name);
static void led_activate_pattern (const gchar *const name);
static void led_deactivate_pattern (const gchar *const name);
static void led_enable (void);
static void led_disable (void);
static void system_state_trigger (gconstpointer data);
static void get_monotime (struct timeval *tv);
static void type6_lock_in_cb (void *data, void *aptr);
static void type6_revert_cb (void *data, void *aptr);
static void type6_deactivate_cb (void *data, void *aptr);
static void led_pattern_op (GFunc cb);
static void user_activity_event_trigger (gconstpointer data);
static void display_state_curr_trigger (gconstpointer data);
static void led_brightness_trigger (gconstpointer data);
static void led_pattern_activate_trigger (gconstpointer data);
static void led_pattern_deactivate_trigger (gconstpointer data);
static gint setting_id_find (gconstpointer data, gconstpointer userdata);
static void led_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data);
static gboolean pattern_get_enabled (const gchar *const patternname, guint *setting_id);
static gboolean led_activate_pattern_dbus_cb (DBusMessage *const msg);
static gboolean led_deactivate_pattern_dbus_cb (DBusMessage *const msg);
static gboolean led_enable_dbus_cb (DBusMessage *const msg);
static gboolean led_disable_dbus_cb (DBusMessage *const msg);
static gboolean init_combination_rules (void);
static gboolean init_lysti_patterns (void);
static gboolean init_njoy_patterns (void);
static gboolean init_mono_patterns (void);
static int list_compare_item (const void *a, const void *b);
static void list_remove_duplicates (gchar **list);
static gboolean list_includes_item (gchar **list, const gchar *elem);
static gboolean init_hybris_patterns (void);
static gboolean init_patterns (void);
static void sw_breathing_rethink (void);
static void sw_breathing_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data);
static void sw_breathing_quit (void);
static void sw_breathing_init (void);
static void charger_state_trigger (gconstpointer data);
static void battery_level_trigger (gconstpointer data);
static void mce_led_init_dbus (void);
static void mce_led_quit_dbus (void);
G_MODULE_EXPORT const gchar *g_module_check_init (GModule *module);
G_MODULE_EXPORT void g_module_unload (GModule *module);
/**
* Disable the Reno LED controller
*/
static void disable_reno(void)
{
int fd;
if (access("/dev/i2c-0", F_OK) == 0) {
mce_log(LL_DEBUG, "Skipping Reno disable - suitable kernel detected");
/* Reset errno,
* to avoid false positives down the line
*/
errno = 0;
return;
}
mce_log(LL_DEBUG, "Disabling Reno");
if ((fd = open("/dev/i2c-1", O_RDWR)) == -1) {
mce_log(LL_CRIT, "Failed to open /dev/i2c-1; %s",
g_strerror(errno));
/* Reset errno,
* to avoid false positives down the line
*/
errno = 0;
goto EXIT;
}
if (ioctl(fd, I2C_SLAVE_FORCE, TWL5031_BCC) == -1) {
mce_log(LL_CRIT,
"ioctl() I2C_SLAVE_FORCE (%d) failed on `%s'; %s",
TWL5031_BCC, "/dev/i2c-1", g_strerror(errno));
/* Reset errno,
* to avoid false positives down the line
*/
errno = 0;
goto EXIT;
}
struct i2c_smbus_ioctl_data args;
union i2c_smbus_data data;
data.byte = LEDC_DISABLE;
args.read_write = I2C_SMBUS_WRITE;
args.command = LED_DRIVER_CTRL;
args.size = I2C_SMBUS_BYTE_DATA;
args.data = &data;
if (ioctl(fd, I2C_SMBUS, &args) == -1) {
mce_log(LL_ERR,
"ioctl() I2C_SMBUS (write LED_DRIVER_CTRL %d) failed on `%s'; %s",
LEDC_DISABLE, "/dev/i2c-1", g_strerror(errno));
errno = 0;
}
EXIT:
if (fd != -1) {
if (close(fd) == -1) {
mce_log(LL_ERR,
"Failed to close `%s': %s",
"/dev/i2c-1", g_strerror(errno));
/* Reset errno,
* to avoid false positives down the line
*/
errno = 0;
}
}
return;
}
/**
* Get the LED type
*
* @return The LED type
*/
static led_type_t get_led_type(void)
{
product_id_t product_id = PRODUCT_UNKNOWN;
static led_type_t led_type = LED_TYPE_UNSET;
/* If we have the LED type already, return it */
if (led_type != LED_TYPE_UNSET)
goto EXIT;
#ifdef ENABLE_HYBRIS
/* Use mce-plugin-libhybris if available */
if( mce_hybris_indicator_init() ) {
led_type = LED_TYPE_HYBRIS;
led_pattern_group = MCE_CONF_LED_PATTERN_HYBRIS_GROUP;
maximum_led_brightness = MAXIMUM_HYBRIS_LED_BRIGHTNESS;
goto DONE;
}
#endif
/* Otherwise use product id for determining led type */
product_id = get_product_id();
// FIXME: The code below is defunct as get_product_id()
// does not work without sysinfod.
/* First build the paths needed to check */
switch ( product_id ) {
case PRODUCT_RM716:
case PRODUCT_RM696:
led_type = LED_TYPE_NJOY_MONO;
led_pattern_group = MCE_CONF_LED_PATTERN_RM696_GROUP;
maximum_led_brightness = MAXIMUM_NJOY_MONOCHROME_LED_CURRENT;
/* Build paths */
led_current_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_CURRENT_SUFFIX, NULL);
led_brightness_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
engine1_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_MODE_SUFFIX, NULL);
engine2_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_MODE_SUFFIX, NULL);
engine3_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_MODE_SUFFIX, NULL);
/* We have 3 engines, but only 1 LED,
* so while we need to be able to set the mode of all
* engines (to disable the unused ones), we don't need
* to program them
*/
engine1_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LOAD_SUFFIX, NULL);
disable_reno();
break;
case PRODUCT_RM690:
case PRODUCT_RM680:
led_type = LED_TYPE_LYSTI_MONO;
led_pattern_group = MCE_CONF_LED_PATTERN_RM680_GROUP;
maximum_led_brightness = MAXIMUM_LYSTI_MONOCHROME_LED_CURRENT;
/* Build paths */
led_current_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL8, MCE_LED_CURRENT_SUFFIX, NULL);
led_brightness_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL8, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
/* Engine 3 is used by keyboard backlight */
engine1_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_MODE_SUFFIX, NULL);
engine2_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_MODE_SUFFIX, NULL);
engine1_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LOAD_SUFFIX, NULL);
engine2_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_LOAD_SUFFIX, NULL);
engine1_leds_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LEDS_SUFFIX, NULL);
engine2_leds_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_LEDS_SUFFIX, NULL);
disable_reno();
break;
case PRODUCT_RX51:
led_type = LED_TYPE_LYSTI_RGB;
led_pattern_group = MCE_CONF_LED_PATTERN_RX51_GROUP;
maximum_led_brightness = MAXIMUM_LYSTI_RGB_LED_CURRENT;
/* Build paths */
led_current_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_CURRENT_SUFFIX, NULL);
led_current_g_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL1, MCE_LED_CURRENT_SUFFIX, NULL);
led_current_b_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL2, MCE_LED_CURRENT_SUFFIX, NULL);
led_brightness_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
led_brightness_g_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL1, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
led_brightness_b_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL2, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
engine1_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_MODE_SUFFIX, NULL);
engine2_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_MODE_SUFFIX, NULL);
engine3_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_MODE_SUFFIX, NULL);
engine1_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LOAD_SUFFIX, NULL);
engine2_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_LOAD_SUFFIX, NULL);
engine3_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_LOAD_SUFFIX, NULL);
engine1_leds_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LEDS_SUFFIX, NULL);
engine2_leds_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_LEDS_SUFFIX, NULL);
engine3_leds_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5523_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_LEDS_SUFFIX, NULL);
break;
case PRODUCT_RX44:
case PRODUCT_RX48:
led_type = LED_TYPE_NJOY_RGB;
maximum_led_brightness = MAXIMUM_NJOY_RGB_LED_CURRENT;
if (product_id == PRODUCT_RX48)
led_pattern_group = MCE_CONF_LED_PATTERN_RX48_GROUP;
else
led_pattern_group = MCE_CONF_LED_PATTERN_RX44_GROUP;
/* Build paths */
led_current_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_CURRENT_SUFFIX, NULL);
led_brightness_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
engine1_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_MODE_SUFFIX, NULL);
engine2_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL1, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_MODE_SUFFIX, NULL);
engine3_mode_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL2, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_MODE_SUFFIX, NULL);
engine1_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL0, MCE_LED_DEVICE, MCE_LED_ENGINE1, MCE_LED_LOAD_SUFFIX, NULL);
engine2_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL1, MCE_LED_DEVICE, MCE_LED_ENGINE2, MCE_LED_LOAD_SUFFIX, NULL);
engine3_load_path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_LP5521_PREFIX, MCE_LED_CHANNEL2, MCE_LED_DEVICE, MCE_LED_ENGINE3, MCE_LED_LOAD_SUFFIX, NULL);
break;
case PRODUCT_RX34:
led_type = LED_TYPE_DIRECT_MONO;
led_pattern_group = MCE_CONF_LED_PATTERN_RX34_GROUP;
/* Build paths */
led_brightness_rm_output.path = g_strconcat(MCE_LED_DIRECT_SYS_PATH, MCE_LED_KEYPAD_PREFIX, MCE_LED_BRIGHTNESS_SUFFIX, NULL);
break;
default:
led_type = LED_TYPE_NONE;
break;
}
#ifdef ENABLE_HYBRIS
DONE:
#endif
mce_log(LL_DEBUG, "LED-type: %d", led_type);
EXIT:
return led_type;
}
/**
* Custom find function to get a particular entry in the pattern stack
*
* @param data The pattern_struct entry
* @param userdata The pattern name
* @return Less than, equal to, or greater than zero depending
* whether the name of the pattern struct pointed to by data
* is less than, equal to, or greater than entry2
*/
static gint queue_find(gconstpointer data, gconstpointer userdata) G_GNUC_PURE;
static gint queue_find(gconstpointer data, gconstpointer userdata)
{
pattern_struct *psp;
if (data == NULL || userdata == NULL)
return -1;
psp = (pattern_struct *)data;
if (psp->name == NULL)
return -1;
return strcmp(psp->name, (gchar *)userdata);
}
/**
* Custom compare function used for priority insertions
*
* @param entry1 Queue entry 1
* @param entry2 Queue entry 2
* @param userdata The pattern name
* @return Less than, equal to, or greater than zero depending
* whether the priority of entry1 is less than, equal to,
* or greater than the priority of entry2
*/
static gint queue_prio_compare(gconstpointer entry1,
gconstpointer entry2,
gpointer userdata) G_GNUC_PURE;
static gint queue_prio_compare(gconstpointer entry1,
gconstpointer entry2,
gpointer userdata)
{
pattern_struct *psp1 = (pattern_struct *)entry1;
pattern_struct *psp2 = (pattern_struct *)entry2;
(void)userdata;
return psp1->priority - psp2->priority;
}
/**
* Set Lysti-LED brightness
*
* @param brightness The brightness of the LED
* (0 - maximum_led_brightness),
* or -1 to adjust colour hues without changing brightness,
* and to reset brightness when the LED has been disabled
*/
static void lysti_set_brightness(gint brightness)
{
guint r_brightness = 0;
guint g_brightness = 0;
guint b_brightness = 0;
if (brightness < -1 || brightness > (gint)maximum_led_brightness) {
mce_log(LL_WARN, "Invalid brightness value %d", brightness);
return;
}
if (brightness != -1) {
if (active_brightness == brightness)
return;
active_brightness = brightness;
}
if ((current_lysti_led_pattern & MCE_LYSTI_RED_MASK_RX51) &&
(get_led_type() == LED_TYPE_LYSTI_RGB)) {
/* Red is on, tweaking is needed */
if ((current_lysti_led_pattern & MCE_LYSTI_GREEN_MASK_RX51) &&
(current_lysti_led_pattern & MCE_LYSTI_BLUE_MASK_RX51)) {
/* White */
r_brightness = (unsigned)active_brightness * 4;
r_brightness = (r_brightness < maximum_led_brightness) ? r_brightness : maximum_led_brightness;
g_brightness = r_brightness / 4;
b_brightness = r_brightness / 4;
} else if (current_lysti_led_pattern & MCE_LYSTI_GREEN_MASK_RX51) {
/* Orange */
r_brightness = (unsigned)active_brightness * 10;
r_brightness = (r_brightness < maximum_led_brightness) ? r_brightness : maximum_led_brightness;
g_brightness = r_brightness / 10;
b_brightness = 0;
} else {
/* Purple */
r_brightness = (unsigned)active_brightness * 4;
r_brightness = (r_brightness < maximum_led_brightness) ? r_brightness : maximum_led_brightness;
b_brightness = r_brightness / 4;
g_brightness = 0;
}
} else {
/* When red is not on, we use brightness as is */
r_brightness = (unsigned)active_brightness;
g_brightness = (unsigned)active_brightness;
b_brightness = (unsigned)active_brightness;
}
if (get_led_type() == LED_TYPE_LYSTI_MONO) {
/* If we have a monochrome LED only set one brightness */
mce_write_number_string_to_file(&led_current_rm_output, r_brightness);
mce_log(LL_DEBUG,
"Brightness set to %d",
active_brightness);
} else if (get_led_type() == LED_TYPE_LYSTI_RGB) {
/* If we have an RGB LED set the brightness for all channels */
mce_write_number_string_to_file(&led_current_rm_output, r_brightness);
mce_write_number_string_to_file(&led_current_g_output, g_brightness);
mce_write_number_string_to_file(&led_current_b_output, b_brightness);
mce_log(LL_DEBUG,
"Brightness set to %d (%d, %d, %d)",
active_brightness, r_brightness,
g_brightness, b_brightness);
}
}
/**
* Set NJoy-LED brightness
*
* @param brightness The brightness of the LED
* (0 - maximum_led_brightness),
* or -1 to reset brightness when the LED has been disabled
*/
static void njoy_set_brightness(gint brightness)
{
if (brightness < -1 || brightness > (gint)maximum_led_brightness) {
mce_log(LL_WARN, "Invalid brightness value %d", brightness);
return;
}
/* This is a bit questionable, but currently 696 does not have any
* use for brightness setting, it only causes unwanted LED
* turn-ons when used with ALS. Let zero brightnesses through to
* be a bit safer.
*/
if ((get_product_id() == PRODUCT_RM696) &&
((brightness > 0) ||
(brightness == -1 && active_brightness != 0))) {
mce_log(LL_DEBUG, "don't set useless brightness value %d", brightness);
return;
}
if (brightness != -1) {
if (active_brightness == brightness)
return;
active_brightness = brightness;
}
mce_write_number_string_to_file(&led_brightness_rm_output,
(unsigned)active_brightness);
mce_log(LL_DEBUG, "Brightness set to %d", active_brightness);
}
/**
* Set mono-LED brightness
*
* @param brightness The brightness of the LED (0-15)
*/
static void mono_set_brightness(gint brightness)
{
if (brightness < 0 || brightness > 15) {
mce_log(LL_WARN, "Invalid brightness value %d", brightness);
return;
}
if (active_brightness == brightness)
return;
active_brightness = brightness;
mce_write_string_to_file(led_brightness_rm_output.path,
brightness_map[brightness]);
mce_log(LL_DEBUG, "Brightness set to %d", brightness);
}
#ifdef ENABLE_HYBRIS
static void hybris_program_led(const pattern_struct *const pattern);
/**
* Set libhybris-LED brightness
*
* @param brightness The brightness of the LED
* (0 - maximum_led_brightness),
* or -1 to reset brightness when the LED has been disabled
*/
static void hybris_set_brightness(gint brightness)
{
if (brightness < -1 || brightness > (gint)maximum_led_brightness) {
mce_log(LL_WARN, "Invalid brightness value %d", brightness);
return;
}
if( active_brightness == brightness )
return;
if( brightness != -1 )
active_brightness = brightness;
mce_log(LL_DEBUG, "Brightness set to %d", active_brightness);
/* Scale from [1...100%] to [1...255] range */
brightness = mce_xlat_int(1,maximum_led_brightness, 1,255, brightness);
mce_hybris_indicator_set_brightness(brightness);
}
#endif /* ENABLE_HYBRIS */
/**
* Disable the Lysti-LED
*/
static void lysti_disable_led(void)
{
/* Disable engine 1 */
mce_write_string_to_file(engine1_mode_path,
MCE_LED_DISABLED_MODE);
if (get_led_type() == LED_TYPE_LYSTI_MONO) {
/* Turn off the led */
mce_write_number_string_to_file(&led_brightness_rm_output, 0);
} else if (get_led_type() == LED_TYPE_LYSTI_RGB) {
/* Disable engine 2 */
mce_write_string_to_file(engine2_mode_path,
MCE_LED_DISABLED_MODE);
/* Turn off all three leds */
mce_write_number_string_to_file(&led_brightness_rm_output, 0);
mce_write_number_string_to_file(&led_brightness_g_output, 0);
mce_write_number_string_to_file(&led_brightness_b_output, 0);
}
}
/**
* Disable the NJoy-LED
*/
static void njoy_disable_led(void)
{
/* Disable engine 1 */
mce_write_string_to_file(engine1_mode_path,
MCE_LED_DISABLED_MODE);
if (get_led_type() == LED_TYPE_NJOY_MONO) {
/* Turn off the led */
mce_write_number_string_to_file(&led_brightness_rm_output, 0);
} else if (get_led_type() == LED_TYPE_NJOY_RGB) {
/* Disable engine 2 */
mce_write_string_to_file(engine2_mode_path,
MCE_LED_DISABLED_MODE);
/* Disable engine 3 */
mce_write_string_to_file(engine3_mode_path,
MCE_LED_DISABLED_MODE);
/* Turn off all three leds */
mce_write_number_string_to_file(&led_brightness_rm_output, 0);
mce_write_number_string_to_file(&led_brightness_g_output, 0);
mce_write_number_string_to_file(&led_brightness_b_output, 0);
}
}
/**
* Disable the mono-LED
*/
static void mono_disable_led(void)
{
mce_write_string_to_file(MCE_LED_TRIGGER_PATH,
MCE_LED_TRIGGER_NONE);
mono_set_brightness(0);
}
#ifdef ENABLE_HYBRIS
/** Disable the libhybris-LED
*/
static void hybris_disable_led(void)
{
mce_hybris_indicator_set_pattern(0,0,0, 0,0);
}
#endif /* ENABLE_HYBRIS */
/**
* Disable the LED
*/
static void disable_led(void)
{
switch (get_led_type()) {
case LED_TYPE_LYSTI_RGB:
case LED_TYPE_LYSTI_MONO:
lysti_disable_led();
break;
case LED_TYPE_NJOY_RGB:
case LED_TYPE_NJOY_MONO:
njoy_disable_led();
break;
case LED_TYPE_DIRECT_MONO:
mono_disable_led();
break;
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
hybris_disable_led();
break;
#endif
default:
break;
}
}
/** Allocate and initialize led pattern object
*
* @return initialzied led pattern object, or NULL
*/
static pattern_struct *led_pattern_create(void)
{
pattern_struct *self = g_slice_new0(pattern_struct);
if( !self )
goto EXIT;
self->name = 0;
self->timeout_id = 0;
self->setting_id = 0;
EXIT:
return self;
}
/** Destroy led pattern object
*
* @param initialzied led pattern object, or NULL
*/
static void led_pattern_delete(pattern_struct *self)
{
if( !self )
goto EXIT;
mce_hbtimer_delete(self->timeout_id);
mce_setting_notifier_remove(self->setting_id);
free(self->name);
g_slice_free(pattern_struct, self);
EXIT:
return;
}
/** Setter for led pattern active property
*
* Apart from initialization to FALSE state, all active
* property changes must go through this function.
*
* If the active property actually changes and the pattern
* is not disabled an appropriate D-Bus signal is broadcast
* over the system bus.
*
* @param self pattern object
* @param active new value for active property
*/
static void led_pattern_set_active(pattern_struct *self, gboolean active)
{
DBusMessage *msg = NULL;
if( !self )
goto EXIT;
if( self->active == active )
goto EXIT;
self->active = active;
if( !self->enabled )
goto EXIT;
if( self->active )
mce_hbtimer_start(self->timeout_id);
else
mce_hbtimer_stop(self->timeout_id);
mce_log(LL_DEVEL, "led pattern %s %sactivated",
self->name, self->active ? "" : "de");
const char *member = (self->active ?
MCE_LED_PATTERN_ACTIVATED_SIG :
MCE_LED_PATTERN_DEACTIVATED_SIG);
msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, member);
if( !dbus_message_append_args(msg,
DBUS_TYPE_STRING, &self->name,
DBUS_TYPE_INVALID) ) {
mce_log(LL_ERR, "failed to construct %s signal", member);
goto EXIT;
}
dbus_send_message(msg), msg = 0;
EXIT:
if( msg )
dbus_message_unref(msg);
return;
}
/** Check if a led pattern should always utilize sw breathing
*
* @param self led pattern object
*
* @return true if the pattern should always breathe, false otherwise
*/
static bool led_pattern_should_breathe(const pattern_struct *self)
{
static const char * const lut[] =
{
/* Battery full breathes by default. If user has tuned
* the pattern config to disable battery full blinking,
* the led_pattern_can_breathe() should catch it. */
MCE_LED_PATTERN_BATTERY_FULL,
/* The CSD test has some led patterns that should utilize
* breathing regardless of the breathing settings and/or
* charging status. */
MCE_LED_PATTERN_CSD_BINARY_BLINK,
MCE_LED_PATTERN_CSD_WHITE_BLINK,
};
bool breathe = false;
if( !self || !self->name )
goto EXIT;
for( size_t i = 0; i < G_N_ELEMENTS(lut); ++i ) {
if( strcmp(self->name, lut[i]) )
continue;
breathe = true;
break;
}
EXIT:
return breathe;
}
/** Check if pattern is breathable
*
* @param self led pattern object
*
* @return true if pattern should be breathed, false otherwise
*/
static bool led_pattern_can_breathe(const pattern_struct *self)
{
/* FIXME: This should be directly available in the pattern
* configuration. But until we know better what is
* needed and how to configure it, heuristics are
* used to determine whether a pattern should be
* turned in to breathing kind or not. */
/* Assume no pattern is breathable */
bool breathe = false;
/* What we want to breathe are the normal blinking indicator
* patterns. By default these have the following characteristics
* - "on_period" = 500 ms
* - "off_period" = 1500 ... 2500 ms
*
* Extend these bounds in case the users have edited the
* defaults, or added new patterns.
*/
int normal_pattern_minimum_on_period = 250; // [ms]
int normal_pattern_maximum_on_period = 1500; // [ms]
int normal_pattern_minimum_off_period = 250; // [ms]
int normal_pattern_maximum_off_period = 5000; // [ms]
/* Then assume anything out of those limits probably
* a) is unbreathable static pattern
* b) is rapid panic pattern
* c) is custom beacon with short on, long off cycle
* d) has too short rise time for timer based adjustments
* e) has so long fall time that breathing is unnoticeable
* and should not be made to breathe.
*/
if( self->on_period < normal_pattern_minimum_on_period ||
self->on_period > normal_pattern_maximum_on_period ||
self->off_period < normal_pattern_minimum_off_period ||
self->off_period > normal_pattern_maximum_off_period )
goto EXIT;
/* There is no reason not to breathe */
breathe = true;
EXIT:
return breathe;
}
/** Timeout callback for LED patterns
*
* @param data led pattern object
*
* @return Always returns FALSE to disable timeout
*/
static gboolean led_pattern_timeout_cb(gpointer data)
{
pattern_struct *psp = data;
led_pattern_set_active(psp, FALSE);
led_update_active_pattern();
return FALSE;
}
/**
* Setup and activate a new Lysti-LED pattern
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void lysti_program_led(const pattern_struct *const pattern)
{
/* Disable old LED patterns */
lysti_disable_led();
/* Load new patterns, one engine at a time */
/* Engine 1 */
mce_write_string_to_file(engine1_mode_path,
MCE_LED_LOAD_MODE);
mce_write_string_to_file(engine1_leds_path,
bin_to_string(pattern->engine1_mux));
mce_write_string_to_file(engine1_load_path,
pattern->channel1);
/* Engine 2; if needed */
if (get_led_type() == LED_TYPE_LYSTI_RGB) {
mce_write_string_to_file(engine2_mode_path,
MCE_LED_LOAD_MODE);
mce_write_string_to_file(engine2_leds_path,
bin_to_string(pattern->engine2_mux));
mce_write_string_to_file(engine2_load_path,
pattern->channel2);
/* Run the new pattern; enable engines in reverse order */
mce_write_string_to_file(engine2_mode_path,
MCE_LED_RUN_MODE);
}
mce_write_string_to_file(engine1_mode_path,
MCE_LED_RUN_MODE);
/* Save what colors we are driving */
current_lysti_led_pattern = pattern->engine1_mux | pattern->engine2_mux;
/* Reset brightness and update color hue
* according what leds are driven
*/
lysti_set_brightness(-1);
}
/**
* Setup and activate a new NJoy-LED pattern
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void njoy_program_led(const pattern_struct *const pattern)
{
/* Disable old LED patterns */
njoy_disable_led();
/* Load new patterns */
/* Engine 1 */
mce_write_string_to_file(engine1_mode_path,
MCE_LED_LOAD_MODE);
mce_write_string_to_file(engine1_load_path,
pattern->channel1);
if (get_led_type() == LED_TYPE_NJOY_RGB) {
/* Engine 2 */
mce_write_string_to_file(engine2_mode_path,
MCE_LED_LOAD_MODE);
mce_write_string_to_file(engine2_load_path,
pattern->channel2);
/* Engine 3 */
mce_write_string_to_file(engine3_mode_path,
MCE_LED_LOAD_MODE);
mce_write_string_to_file(engine3_load_path,
pattern->channel3);
/* Run the new pattern; enable engines in reverse order */
mce_write_string_to_file(engine3_mode_path,
MCE_LED_RUN_MODE);
mce_write_string_to_file(engine2_mode_path,
MCE_LED_RUN_MODE);
}
mce_write_string_to_file(engine1_mode_path,
MCE_LED_RUN_MODE);
/* Reset brightness */
njoy_set_brightness(-1);
}
/**
* Setup and activate a new mono-LED pattern
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void mono_program_led(const pattern_struct *const pattern)
{
static output_state_t led_on_period_output =
{
.context = "led_on_period",
.truncate_file = TRUE,
.close_on_exit = TRUE,
.path = MCE_LED_ON_PERIOD_PATH,
};
static output_state_t led_off_period_output =
{
.context = "led_off_period",
.truncate_file = TRUE,
.close_on_exit = TRUE,
.path = MCE_LED_OFF_PERIOD_PATH,
};
/* This shouldn't happen; disable the LED instead */
if (pattern->on_period == 0) {
mono_disable_led();
goto EXIT;
}
/* If we have a normal, on/off pattern,
* use a timer trigger, otherwise disable the trigger
*/
if (pattern->off_period != 0) {
mce_write_string_to_file(MCE_LED_TRIGGER_PATH,
MCE_LED_TRIGGER_TIMER);
mce_write_number_string_to_file(&led_off_period_output,
(unsigned)pattern->off_period);
mce_write_number_string_to_file(&led_on_period_output,
(unsigned)pattern->on_period);
} else {
mce_write_string_to_file(MCE_LED_TRIGGER_PATH,
MCE_LED_TRIGGER_NONE);
}
mono_set_brightness(pattern->brightness);
EXIT:
return;
}
#ifdef ENABLE_HYBRIS
/**
* Setup and activate a new libhybris-LED pattern
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void hybris_program_led(const pattern_struct *const pattern)
{
int r = (pattern->rgb_color >> 16) & 0xff;
int g = (pattern->rgb_color >> 8) & 0xff;
int b = (pattern->rgb_color >> 0) & 0xff;
mce_hybris_indicator_set_pattern(r, g, b,
pattern->on_period,
pattern->off_period);
}
#endif /* ENABLE_HYBRIS */
/**
* Setup and activate a new LED pattern
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void program_led(const pattern_struct *const pattern)
{
switch (get_led_type()) {
case LED_TYPE_LYSTI_RGB:
case LED_TYPE_LYSTI_MONO:
lysti_program_led(pattern);
break;
case LED_TYPE_NJOY_RGB:
case LED_TYPE_NJOY_MONO:
njoy_program_led(pattern);
break;
case LED_TYPE_DIRECT_MONO:
mono_program_led(pattern);
break;
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
hybris_program_led(pattern);
break;
#endif
default:
break;
}
}
/** Enable/disable led breathing via software
*
* @param pattern A pointer to a pattern_struct with the new pattern
*/
static void allow_sw_breathing(bool enable)
{
static bool current = false;
/* If led backend does not support breathing make sure we do
* not grab a useless wakelock and block suspend unnecessarily */
if( !mce_hybris_indicator_can_breathe() )
enable = false;
if( current == enable )
goto EXIT;
current = enable;
switch (get_led_type()) {
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
if( enable )
wakelock_lock("mce_led_breathing", -1);
mce_hybris_indicator_enable_breathing(enable);
if( !enable )
wakelock_unlock("mce_led_breathing");
break;
#endif
default:
break;
}
EXIT:
return;
}
/** Setter function for active_pattern
*
* @param pattern The led pattern to activate, or NULL to disable
*/
static void led_set_active_pattern(pattern_struct *pattern)
{
if( active_pattern == pattern )
goto EXIT;
active_pattern = pattern;
if( active_pattern ) {
program_led(active_pattern);
}
else {
disable_led();
}
sw_breathing_rethink();
EXIT:
return;
}
/** Display state is close enough to "off" predicate
*
* @param state display state
*
* @return TRUE if display is off, otherwise FALSE
*/
static gboolean display_off_p(display_state_t state)
{
gboolean is_off = TRUE;
switch( state ) {
case MCE_DISPLAY_ON:
case MCE_DISPLAY_DIM:
case MCE_DISPLAY_UNDEF:
is_off = FALSE;
break;
default:
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_LPM_OFF:
case MCE_DISPLAY_LPM_ON:
case MCE_DISPLAY_POWER_UP:
case MCE_DISPLAY_POWER_DOWN:
break;
}
return is_off;
}
/**
* Recalculate active pattern and update the pattern timer
*/
static void led_update_active_pattern(void)
{
pattern_struct *new_active_pattern = 0;
if( !pattern_stack )
goto EXIT;
for( GList *iter = pattern_stack->head; ; iter = iter->next ) {
if( !iter ) {
new_active_pattern = 0;
break;
}
new_active_pattern = iter->data;
#if 0 /* While this can be useful when actively debugging led
* activation logic, it creates so much noise that using
* debug verbosity becomes impossible - do not compile in
* by default. */
mce_log(LL_DEBUG,
"pattern: %s, active: %d, enabled: %d",
new_active_pattern->name,
new_active_pattern->active,
new_active_pattern->enabled);
#endif
/* If the pattern is deactivated, ignore */
if (new_active_pattern->active == FALSE)
continue;
/* If the pattern is disabled through GConf, ignore */
if (new_active_pattern->enabled == FALSE)
continue;
/* If the LED is disabled,
* only patterns with visibility 5 are shown
*/
if ((led_enabled == FALSE) &&
(new_active_pattern->policy != 5))
continue;
/* Always show pattern with visibility 3 or 5 */
if ((new_active_pattern->policy == 3) ||
(new_active_pattern->policy == 5))
break;
/* Show pattern with visibility 7 if display is dimmed */
if( new_active_pattern->policy == 7 ) {
if( display_state_curr == MCE_DISPLAY_DIM )
break;
continue;
}
/* Acting dead behaviour */
if (system_state == MCE_SYSTEM_STATE_ACTDEAD) {
/* If we're in acting dead,
* show patterns with visibility 4
*/
if (new_active_pattern->policy == 4)
break;
/* If we're in acting dead
* and the display is off, show pattern
*/
if (display_off_p(display_state_curr) &&
(new_active_pattern->policy == 2))
break;
/* If the display is on and visibility is 2,
* or if visibility is 1/0, ignore pattern
*/
continue;
}
/* If the display is off or in low power mode,
* we can use any active pattern
*/
if( display_off_p(display_state_curr) )
break;
/* If the pattern should be shown with screen on, use it */
if (new_active_pattern->policy == 1)
break;
}
EXIT:
led_set_active_pattern(new_active_pattern);
return;
}
/**
* Find the pattern struct for a pattern
*
* @param name The name of the pattern
* @return A pointer to the pattern struct, or NULL if no such pattern exists
*/
static pattern_struct *find_pattern_struct(const gchar *const name)
{
pattern_struct *psp = NULL;
GList *glp;
if (name == NULL)
goto EXIT;
if ((glp = g_queue_find_custom(pattern_stack,
name, queue_find)) != NULL) {
psp = (pattern_struct *)glp->data;
}
EXIT:
return psp;
}
/**
* Update combination rule
*
* @param name The rule to process
* @param data Unused
*/
static void update_combination_rule(gpointer name, gpointer data)
{
combination_rule_struct *cr;
gboolean enabled = TRUE;
pattern_struct *psp;
GList *glp;
gchar *tmp;
gint i;
(void)data;
if ((glp = g_queue_find_custom(combination_rule_list,
name, queue_find)) == NULL)
goto EXIT;
cr = glp->data;
/* If all patterns in the pre_requisite list are enabled,
* then enable this pattern, else disable it
*/
for (i = 0; (tmp = g_queue_peek_nth(cr->pre_requisites, i)) != NULL; i++) {
/* We've got a pattern name; check if that pattern is active */
if (((psp = find_pattern_struct(tmp)) == NULL) ||
(psp->active == FALSE)) {
enabled = FALSE;
break;
}
}
if ((psp = find_pattern_struct(name)) == NULL)
goto EXIT;
led_pattern_set_active(psp, enabled);
EXIT:
return;
}
/**
* Update activate patterns based on combination rules
*
* @param name THe name of the pattern that changed state
*/
static void update_combination_rules(const gchar *const name)
{
GList *glp;
if (name == NULL) {
mce_log(LL_CRIT,
"called with name == NULL");
goto EXIT;
}
if ((glp = g_queue_find_custom(combination_rule_xref_list, name,
queue_find)) != NULL) {
combination_rule_struct *xrf = glp->data;
/* Update all combination rules that this pattern influences */
g_queue_foreach(xrf->pre_requisites,
update_combination_rule, NULL);
}
EXIT:
return;
}
/**
* Activate a pattern in the pattern-stack
*
* @param name The name of the pattern to activate
*/
static void led_activate_pattern(const gchar *const name)
{
pattern_struct *psp;
if (name == NULL) {
mce_log(LL_CRIT,
"called with name == NULL");
goto EXIT;
}
if ((psp = find_pattern_struct(name)) != NULL) {
if( !psp->active && psp->policy == 6 )
psp->undecided = TRUE;
led_pattern_set_active(psp, TRUE);
update_combination_rules(name);
led_update_active_pattern();
} else {
mce_log(LL_DEBUG,
"Received request to activate "
"a non-existing LED pattern '%s'", name);
}
EXIT:
return;
}
/**
* Deactivate a pattern in the pattern-stack
*
* @param name The name of the pattern to deactivate
*/
static void led_deactivate_pattern(const gchar *const name)
{
pattern_struct *psp;
if ((psp = find_pattern_struct(name)) != NULL) {
led_pattern_set_active(psp, FALSE);
update_combination_rules(name);
led_update_active_pattern();
} else {
mce_log(LL_DEBUG,
"Received request to deactivate "
"a non-existing LED pattern '%s'", name);
}
}
/**
* Enable the LED
*/
static void led_enable(void)
{
led_enabled = TRUE;
led_update_active_pattern();
}
/**
* Disable the LED
*/
static void led_disable(void)
{
led_enabled = FALSE;
led_update_active_pattern();
}
/**
* Handle system state change
*
* @param data Unused
*/
static void system_state_trigger(gconstpointer data)
{
system_state_t prev = system_state;
system_state = GPOINTER_TO_INT(data);
if( prev == system_state )
goto EXIT;
mce_log(LL_DEBUG, "system_state: %s -> %s",
system_state_repr(prev),
system_state_repr(system_state));
led_update_active_pattern();
EXIT:
return;
}
/** Monotonic time stamp helper
*
* @param tv where to store the time stamp
*/
static void get_monotime(struct timeval *tv)
{
struct timespec ts;
#ifdef CLOCK_BOOTTIME
if( clock_gettime(CLOCK_BOOTTIME, &ts) == 0 )
goto CONVERT;
#endif
#ifdef CLOCK_MONOTIME
if( clock_gettime(CLOCK_MONOTIME, &ts) == 0 )
goto CONVERT;
#endif
if( gettimeofday(tv, 0) != 0 )
timerclear(tv);
goto EXIT;
CONVERT:
TIMESPEC_TO_TIMEVAL(tv, &ts);
EXIT:
return;
}
/** Timestamp for latest user activity */
static struct timeval activity_time = { .tv_sec = 0, .tv_usec = 0 };
/** Timelimit for the activity_time to be considered recent */
static const struct timeval activity_limit = { .tv_sec = 2, .tv_usec = 0 };
/** Lock in undecided policy=6 led patterns
*
* For use with led_pattern_op()
*/
static void type6_lock_in_cb(void *data, void *aptr)
{
(void)aptr;
pattern_struct *psp = data;
if( psp->undecided && psp->active && psp->policy == 6 ) {
mce_log(LL_DEBUG, "LED pattern %s: locked in", psp->name);
}
psp->undecided = FALSE;
}
/** Revert undecided policy=6 led patterns
*
* For use with led_pattern_op()
*/
static void type6_revert_cb(void *data, void *aptr)
{
(void)aptr;
pattern_struct *psp = data;
if( psp->undecided && psp->active && psp->policy == 6 ) {
led_pattern_set_active(psp, FALSE);
update_combination_rules(psp->name);
mce_log(LL_DEBUG, "LED pattern %s: reverted", psp->name);
}
psp->undecided = FALSE;
}
/** De-activate all policy=6 led patterns
*
* For use with led_pattern_op()
*/
static void type6_deactivate_cb(void *data, void *aptr)
{
(void)aptr;
pattern_struct *psp = data;
if( psp->active && psp->policy == 6 ) {
led_pattern_set_active(psp, FALSE);
update_combination_rules(psp->name);
mce_log(LL_DEBUG, "LED pattern %s: deactivated", psp->name);
}
psp->undecided = FALSE;
}
/** Apply callback on all led patterns
*/
static void led_pattern_op(GFunc cb)
{
g_queue_foreach(pattern_stack, cb, 0);
}
/** Handle real user activity
*
* @param data Unused
*/
static void user_activity_event_trigger(gconstpointer data)
{
(void)data; // the data is irrelevant
if( display_state_curr == MCE_DISPLAY_ON )
led_pattern_op(type6_revert_cb);
get_monotime(&activity_time);
}
/**
* Handle display state change
*
* @param data Unused
*/
static void display_state_curr_trigger(gconstpointer data)
{
display_state_t prev = display_state_curr;
display_state_curr = GPOINTER_TO_INT(data);
struct timeval tv;
if (prev == display_state_curr)
goto EXIT;
mce_log(LL_DEBUG, "display_state_curr: %s -> %s",
display_state_repr(prev),
display_state_repr(display_state_curr));
get_monotime(&tv);
timersub(&tv, &activity_time, &tv);
switch( display_state_curr ) {
case MCE_DISPLAY_ON:
if( timercmp(&tv, &activity_limit, <) )
led_pattern_op(type6_deactivate_cb);
timerclear(&activity_time);
break;
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_LPM_OFF:
case MCE_DISPLAY_LPM_ON:
if( timercmp(&tv, &activity_limit, <) )
led_pattern_op(type6_revert_cb);
else
led_pattern_op(type6_lock_in_cb);
timerclear(&activity_time);
break;
default:
case MCE_DISPLAY_DIM:
case MCE_DISPLAY_UNDEF:
case MCE_DISPLAY_POWER_UP:
case MCE_DISPLAY_POWER_DOWN:
break;
}
led_update_active_pattern();
EXIT:
return;
}
/**
* Handle led brightness change
*
* @param data The LED brightness stored in a pointer
*/
static void led_brightness_trigger(gconstpointer data)
{
gint prev = led_brightness;
led_brightness = GPOINTER_TO_INT(data);
if( prev == led_brightness )
goto EXIT;
mce_log(LL_DEBUG, "led_brightness: %d -> %d",
prev, led_brightness);
switch (get_led_type()) {
case LED_TYPE_LYSTI_RGB:
case LED_TYPE_LYSTI_MONO:
lysti_set_brightness(led_brightness);
break;
case LED_TYPE_NJOY_RGB:
case LED_TYPE_NJOY_MONO:
njoy_set_brightness(led_brightness);
break;
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
hybris_set_brightness(led_brightness);
break;
#endif
case LED_TYPE_DIRECT_MONO:
case LED_TYPE_UNSET:
case LED_TYPE_NONE:
default:
break;
}
EXIT:
return;
}
/**
* Handle LED pattern activate requests
*
* @param data The pattern name
*/
static void led_pattern_activate_trigger(gconstpointer data)
{
const char *name = data;
/* The datapipe does not have a state, so we need to
* ignore null data that shows up on initialization */
if( name )
led_activate_pattern(name);
}
/**
* Handle LED pattern deactivate requests
*
* @param data The pattern name
*/
static void led_pattern_deactivate_trigger(gconstpointer data)
{
const char *name = data;
/* The datapipe does not have a state, so we need to
* ignore null data that shows up on initialization */
if( name )
led_deactivate_pattern(data);
}
/**
* Custom find function to get a GConf callback ID in the pattern stack
*
* @param data The pattern_struct entry
* @param userdata The pattern name
* @return 0 if the GConf callback id of data matches that of userdata,
* -1 if they don't match
*/
static gint setting_id_find(gconstpointer data, gconstpointer userdata)
{
pattern_struct *psp;
if ((data == NULL) || (userdata == NULL))
return -1;
psp = (pattern_struct *)data;
return psp->setting_id != *(guint *)userdata;
}
/**
* GConf callback for LED related settings
*
* @param gcc Unused
* @param id Connection ID from gconf_client_notify_add()
* @param entry The modified GConf entry
* @param data Unused
*/
static void led_setting_cb(GConfClient *const gcc, const guint id,
GConfEntry *const entry, gpointer const data)
{
const GConfValue *gcv = gconf_entry_get_value(entry);
pattern_struct *psp = NULL;
GList *glp = NULL;
(void)gcc;
(void)data;
/* Key is unset */
if (gcv == NULL) {
mce_log(LL_DEBUG,
"GConf Key `%s' has been unset",
gconf_entry_get_key(entry));
goto EXIT;
}
if ((glp = g_queue_find_custom(pattern_stack,
&id, setting_id_find)) != NULL) {
psp = (pattern_struct *)glp->data;
psp->enabled = gconf_value_get_bool(gcv);
led_update_active_pattern();
} else {
mce_log(LL_WARN, "Spurious GConf value received; confused!");
}
EXIT:
return;
}
/**
* Get the enabled/disabled value from GConf and set up a notifier
*/
static gboolean pattern_get_enabled(const gchar *const patternname,
guint *setting_id)
{
gboolean retval = MCE_DEFAULT_LED_PATTERN_ENABLED;
gchar *path = gconf_concat_dir_and_key(MCE_SETTING_LED_PATH,
patternname);
/* Since custom led patterns do not have persistent toggles
* in configuration, avoid complaining about missing keys
* on default verbosity level. */
if( !mce_setting_has_key(path) ) {
mce_log(LL_INFO, "missing led config entry: %s", path);
goto EXIT;
}
/* Since we've set a default, error handling is unnecessary */
mce_setting_notifier_add(MCE_SETTING_LED_PATH, path,
led_setting_cb, setting_id);
mce_setting_get_bool(path, &retval);
EXIT:
g_free(path);
return retval;
}
/**
* D-Bus callback for the activate LED pattern method call
*
* @param msg The D-Bus message
* @return TRUE on success, FALSE on failure
*/
static gboolean led_activate_pattern_dbus_cb(DBusMessage *const msg)
{
dbus_bool_t no_reply = dbus_message_get_no_reply(msg);
const gchar *pattern = NULL;
gboolean status = FALSE;
DBusError error = DBUS_ERROR_INIT;
if (dbus_message_get_args(msg, &error,
DBUS_TYPE_STRING, &pattern,
DBUS_TYPE_INVALID) == FALSE) {
// XXX: should we return an error instead?
mce_log(LL_CRIT,
"Failed to get argument from %s.%s: %s",
MCE_REQUEST_IF, MCE_ACTIVATE_LED_PATTERN,
error.message);
goto EXIT;
}
mce_log(LL_DEVEL, "activate LED pattern %s request from %s",
pattern, mce_dbus_get_message_sender_ident(msg));
led_activate_pattern(pattern);
if (no_reply == FALSE) {
DBusMessage *reply = dbus_new_method_reply(msg);
status = dbus_send_message(reply);
} else {
status = TRUE;
}
EXIT:
dbus_error_free(&error);
return status;
}
/**
* D-Bus callback for the deactivate LED pattern method call
*
* @param msg The D-Bus message
* @return TRUE on success, FALSE on failure
*/
static gboolean led_deactivate_pattern_dbus_cb(DBusMessage *const msg)
{
dbus_bool_t no_reply = dbus_message_get_no_reply(msg);
const gchar *pattern = NULL;
gboolean status = FALSE;
DBusError error = DBUS_ERROR_INIT;
if (dbus_message_get_args(msg, &error,
DBUS_TYPE_STRING, &pattern,
DBUS_TYPE_INVALID) == FALSE) {
// XXX: should we return an error instead?
mce_log(LL_CRIT,
"Failed to get argument from %s.%s: %s",
MCE_REQUEST_IF, MCE_DEACTIVATE_LED_PATTERN,
error.message);
goto EXIT;
}
mce_log(LL_DEVEL, "de-activate LED pattern %s request from %s",
pattern, mce_dbus_get_message_sender_ident(msg));
led_deactivate_pattern(pattern);
if (no_reply == FALSE) {
DBusMessage *reply = dbus_new_method_reply(msg);
status = dbus_send_message(reply);
} else {
status = TRUE;
}
EXIT:
dbus_error_free(&error);
return status;
}
/**
* D-Bus callback for the enable LED method call
*
* @param msg The D-Bus message
* @return TRUE on success, FALSE on failure
*/
static gboolean led_enable_dbus_cb(DBusMessage *const msg)
{
dbus_bool_t no_reply = dbus_message_get_no_reply(msg);
gboolean status = FALSE;
mce_log(LL_DEVEL, "Received LED enable request from %s",
mce_dbus_get_message_sender_ident(msg));
led_enable();
if (no_reply == FALSE) {
DBusMessage *reply = dbus_new_method_reply(msg);
status = dbus_send_message(reply);
} else {
status = TRUE;
}
//EXIT:
return status;
}
/**
* D-Bus callback for the disable LED method call
*
* @param msg The D-Bus message
* @return TRUE on success, FALSE on failure
*/
static gboolean led_disable_dbus_cb(DBusMessage *const msg)
{
dbus_bool_t no_reply = dbus_message_get_no_reply(msg);
gboolean status = FALSE;
mce_log(LL_DEVEL, "Received LED disable request from %s",
mce_dbus_get_message_sender_ident(msg));
led_disable();
if (no_reply == FALSE) {
DBusMessage *reply = dbus_new_method_reply(msg);
status = dbus_send_message(reply);
} else {
status = TRUE;
}
//EXIT:
return status;
}
/**
* Init LED pattern combination rules
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_combination_rules(void)
{
gboolean status = FALSE;
gchar **crlist = NULL;
gsize length;
gint i;
/* Get the list of valid LED patttern combination rules */
crlist = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_COMBINATION_RULES,
&length);
/* Treat failed conf-value reads as if they were due to invalid keys
* rather than failed allocations; let future allocation attempts fail
* instead; otherwise we'll miss the real invalid key failures
*/
if (crlist == NULL) {
mce_log(LL_WARN,
"Failed to configure LED pattern combination rules");
status = TRUE;
goto EXIT;
}
/* Used for all combination patterns */
for (i = 0; crlist[i]; i++) {
gchar **tmp;
mce_log(LL_DEBUG,
"Getting LED pattern combination rule for: %s",
crlist[i]);
tmp = mce_conf_get_string_list(led_pattern_group,
crlist[i],
&length);
if (tmp != NULL) {
combination_rule_struct *cr = NULL;
guint j;
if (length < 2) {
mce_log(LL_ERR,
"LED Pattern Combination rule `%s'",
crlist[i]);
g_strfreev(tmp);
goto EXIT2;
}
cr = g_slice_new(combination_rule_struct);
if (cr == NULL) {
g_strfreev(tmp);
goto EXIT2;
}
cr->rulename = strdup(tmp[0]);
cr->pre_requisites = g_queue_new();
for (j = 1; j < length; j++) {
gchar *str = strdup(tmp[j]);
GList *glp;
combination_rule_struct *xrf = NULL;
g_queue_push_head(cr->pre_requisites, str);
glp = g_queue_find_custom(combination_rule_xref_list, str, queue_find);
if ((glp == NULL) || (glp->data == NULL)) {
xrf = g_slice_new(combination_rule_struct);
xrf->rulename = str;
xrf->pre_requisites = g_queue_new();
g_queue_push_head(combination_rule_xref_list, xrf);
} else {
xrf = (combination_rule_struct *)glp->data;
}
/* If the cross reference isn't in the list
* already, add it
*/
if (g_queue_find_custom(xrf->pre_requisites, cr->rulename, queue_find) == NULL) {
g_queue_push_head(xrf->pre_requisites,
cr->rulename);
}
}
g_queue_push_head(combination_rule_list, cr);
}
}
status = TRUE;
EXIT2:
g_strfreev(crlist);
EXIT:
return status;
}
/**
* Init patterns for Lysti controlled RGB or monochrome LED
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_lysti_patterns(void)
{
led_type_t led_type = get_led_type();
gchar **patternlist = NULL;
gboolean status = FALSE;
gsize length;
gint i;
/* Get the list of valid LED patterns */
patternlist = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_PATTERNS_REQUIRED,
&length);
/* Treat failed conf-value reads as if they were due to invalid keys
* rather than failed allocations; let future allocation attempts fail
* instead; otherwise we'll miss the real invalid key failures
*/
if (patternlist == NULL) {
mce_log(LL_WARN,
"Failed to configure LED patterns");
status = TRUE;
goto EXIT;
}
/* Used for Lysti LED patterns */
for (i = 0; patternlist[i]; i++) {
gchar **tmp;
mce_log(LL_DEBUG,
"Getting LED pattern for: %s",
patternlist[i]);
tmp = mce_conf_get_string_list(led_pattern_group,
patternlist[i],
&length);
if (tmp != NULL) {
pattern_struct *psp;
guint engine1_mux;
guint engine2_mux;
if (((led_type == LED_TYPE_LYSTI_MONO) &&
((length != NUMBER_OF_PATTERN_FIELDS_LYSTI_MONO) ||
(strlen(tmp[PATTERN_E_CHANNEL_FIELD]) >
CHANNEL_SIZE))) ||
((led_type == LED_TYPE_LYSTI_RGB) &&
((length != NUMBER_OF_PATTERN_FIELDS) ||
(strlen(tmp[PATTERN_E1_CHANNEL_FIELD]) >
CHANNEL_SIZE) ||
(strlen(tmp[PATTERN_E2_CHANNEL_FIELD]) >
CHANNEL_SIZE)))) {
mce_log(LL_ERR,
"Skipping invalid LED-pattern");
g_strfreev(tmp);
continue;
}
engine1_mux = 0;
engine2_mux = 0;
if (led_type == LED_TYPE_LYSTI_MONO) {
engine1_mux |= MCE_LYSTI_MONOCHROME_MASK_RM680;
} else if (led_type == LED_TYPE_LYSTI_RGB) {
if (strchr(tmp[PATTERN_MUXING_FIELD], 'r'))
engine1_mux |= MCE_LYSTI_RED_MASK_RX51;
if (strchr(tmp[PATTERN_MUXING_FIELD], 'R'))
engine2_mux |= MCE_LYSTI_RED_MASK_RX51;
if (strchr(tmp[PATTERN_MUXING_FIELD], 'g'))
engine1_mux |= MCE_LYSTI_GREEN_MASK_RX51;
if (strchr(tmp[PATTERN_MUXING_FIELD], 'G'))
engine2_mux |= MCE_LYSTI_GREEN_MASK_RX51;
if (strchr(tmp[PATTERN_MUXING_FIELD], 'b'))
engine1_mux |= MCE_LYSTI_BLUE_MASK_RX51;
if (strchr(tmp[PATTERN_MUXING_FIELD], 'B'))
engine2_mux |= MCE_LYSTI_BLUE_MASK_RX51;
}
if ((engine1_mux & engine2_mux) != 0) {
mce_log(LL_ERR,
"Same LED muxed to multiple engines; "
"skipping invalid LED-pattern");
g_strfreev(tmp);
continue;
}
psp = led_pattern_create();
if (!psp) {
g_strfreev(tmp);
goto EXIT2;
}
psp->priority = strtoul(tmp[PATTERN_PRIO_FIELD],
NULL, 10);
psp->policy = strtoul(tmp[PATTERN_SCREEN_ON_FIELD],
NULL, 10);
if ((psp->timeout = strtoul(tmp[PATTERN_TIMEOUT_FIELD],
NULL, 10)) == 0)
psp->timeout = -1;
/* Catch all error checking for all three strtoul */
if ((errno == EINVAL) || (errno == ERANGE)) {
/* Reset errno,
* to avoid false positives further down
*/
g_strfreev(tmp);
led_pattern_delete(psp);
continue;
}
psp->engine1_mux = engine1_mux;
psp->engine2_mux = engine2_mux;
if (led_type == LED_TYPE_LYSTI_MONO) {
strncpy(psp->channel1,
tmp[PATTERN_E_CHANNEL_FIELD],
CHANNEL_SIZE);
} else if (led_type == LED_TYPE_LYSTI_RGB) {
strncpy(psp->channel1,
tmp[PATTERN_E1_CHANNEL_FIELD],
CHANNEL_SIZE);
strncpy(psp->channel2,
tmp[PATTERN_E2_CHANNEL_FIELD],
CHANNEL_SIZE);
}
led_pattern_set_active(psp, FALSE);
psp->enabled = pattern_get_enabled(patternlist[i],
&psp->setting_id);
psp->name = strdup(patternlist[i]);
g_strfreev(tmp);
g_queue_insert_sorted(pattern_stack, psp,
queue_prio_compare,
NULL);
}
}
init_combination_rules();
/* Set the LED brightness */
datapipe_exec_full(&led_brightness_pipe,
GINT_TO_POINTER(maximum_led_brightness));
status = TRUE;
EXIT2:
g_strfreev(patternlist);
EXIT:
return status;
}
/**
* Init patterns for NJoy controlled RGB LED
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_njoy_patterns(void)
{
led_type_t led_type = get_led_type();
gchar **patternlist = NULL;
gboolean status = FALSE;
gsize length;
gint i;
/* Get the list of valid LED patterns */
patternlist = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_PATTERNS_REQUIRED,
&length);
/* Treat failed conf-value reads as if they were due to invalid keys
* rather than failed allocations; let future allocation attempts fail
* instead; otherwise we'll miss the real invalid key failures
*/
if (patternlist == NULL) {
mce_log(LL_WARN,
"Failed to configure LED patterns");
status = TRUE;
goto EXIT;
}
/* Used for RGB NJoy LED patterns */
for (i = 0; patternlist[i]; i++) {
gchar **tmp;
mce_log(LL_DEBUG,
"Getting LED pattern for: %s",
patternlist[i]);
tmp = mce_conf_get_string_list(led_pattern_group,
patternlist[i],
&length);
if (tmp != NULL) {
pattern_struct *psp;
if (((led_type == LED_TYPE_NJOY_MONO) &&
((length != NUMBER_OF_PATTERN_FIELDS_NJOY_MONO) ||
(strlen(tmp[PATTERN_E_CHANNEL_FIELD]) >
CHANNEL_SIZE))) ||
((led_type == LED_TYPE_NJOY_RGB) &&
((length != NUMBER_OF_PATTERN_FIELDS) ||
(strlen(tmp[PATTERN_R_CHANNEL_FIELD]) >
CHANNEL_SIZE) ||
(strlen(tmp[PATTERN_G_CHANNEL_FIELD]) >
CHANNEL_SIZE) ||
(strlen(tmp[PATTERN_B_CHANNEL_FIELD]) >
CHANNEL_SIZE)))) {
mce_log(LL_ERR,
"Skipping invalid LED-pattern");
g_strfreev(tmp);
continue;
}
psp = led_pattern_create();
if (!psp) {
g_strfreev(tmp);
goto EXIT2;
}
psp->priority = strtoul(tmp[PATTERN_PRIO_FIELD],
NULL, 10);
psp->policy = strtoul(tmp[PATTERN_SCREEN_ON_FIELD],
NULL, 10);
if ((psp->timeout = strtoul(tmp[PATTERN_TIMEOUT_FIELD],
NULL, 10)) == 0)
psp->timeout = -1;
/* Catch all error checking for all three strtoul */
if ((errno == EINVAL) || (errno == ERANGE)) {
/* Reset errno,
* to avoid false positives further down
*/
g_strfreev(tmp);
led_pattern_delete(psp);
continue;
}
if (led_type == LED_TYPE_NJOY_MONO) {
strncpy(psp->channel1,
tmp[PATTERN_E_CHANNEL_FIELD],
CHANNEL_SIZE);
} else {
strncpy(psp->channel1,
tmp[PATTERN_R_CHANNEL_FIELD],
CHANNEL_SIZE);
strncpy(psp->channel2,
tmp[PATTERN_G_CHANNEL_FIELD],
CHANNEL_SIZE);
strncpy(psp->channel3,
tmp[PATTERN_B_CHANNEL_FIELD],
CHANNEL_SIZE);
}
led_pattern_set_active(psp, FALSE);
psp->enabled = pattern_get_enabled(patternlist[i],
&psp->setting_id);
psp->name = strdup(patternlist[i]);
g_strfreev(tmp);
g_queue_insert_sorted(pattern_stack, psp,
queue_prio_compare,
NULL);
}
}
/* Set the LED brightness */
datapipe_exec_full(&led_brightness_pipe,
GINT_TO_POINTER(maximum_led_brightness));
status = TRUE;
EXIT2:
g_strfreev(patternlist);
EXIT:
return status;
}
/**
* Init patterns for direct controlled monochrome LED
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_mono_patterns(void)
{
gchar **patternlist = NULL;
gboolean status = FALSE;
gsize length;
gint i;
/* Get the list of valid LED patterns */
patternlist = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_PATTERNS_REQUIRED,
&length);
/* Treat failed conf-value reads as if they were due to invalid keys
* rather than failed allocations; let future allocation attempts fail
* instead; otherwise we'll miss the real invalid key failures
*/
if (patternlist == NULL) {
mce_log(LL_WARN,
"Failed to configure LED patterns");
status = TRUE;
goto EXIT;
}
/* Used for single-colour LED patterns */
for (i = 0; patternlist[i]; i++) {
gint *tmp;
mce_log(LL_DEBUG,
"Getting LED pattern for: %s",
patternlist[i]);
tmp = mce_conf_get_int_list(led_pattern_group,
patternlist[i],
&length);
if (tmp != NULL) {
pattern_struct *psp;
if (length != NUMBER_OF_PATTERN_FIELDS) {
mce_log(LL_ERR,
"Skipping invalid LED-pattern");
g_free(tmp);
continue;
}
psp = led_pattern_create();
if (!psp) {
g_free(tmp);
goto EXIT2;
}
psp->name = strdup(patternlist[i]);
psp->priority = tmp[PATTERN_PRIO_FIELD];
psp->policy = tmp[PATTERN_SCREEN_ON_FIELD];
psp->timeout = tmp[PATTERN_TIMEOUT_FIELD] ? tmp[PATTERN_TIMEOUT_FIELD] : -1;
psp->on_period = tmp[PATTERN_ON_PERIOD_FIELD];
psp->off_period = tmp[PATTERN_OFF_PERIOD_FIELD];
psp->brightness = tmp[PATTERN_BRIGHTNESS_FIELD];
psp->active = FALSE;
psp->enabled = pattern_get_enabled(patternlist[i],
&psp->setting_id);
g_free(tmp);
g_queue_insert_sorted(pattern_stack, psp,
queue_prio_compare,
NULL);
}
}
status = TRUE;
EXIT2:
g_strfreev(patternlist);
EXIT:
return status;
}
#ifdef ENABLE_HYBRIS
/** Compare operator for sorting arrays of led pattern names
*
* @param a pointer to 1st string pointer
* @param b pointer to 2nd string pointer
*
* @return negative, zero or positive if ab
*/
static int list_compare_item(const void *a, const void *b)
{
return strcmp(*(const char **)a, *(const char **)b);
}
/** Sort array of led pattern names and remove duplicates
*
* @param list array of led pattern names
*/
static void list_remove_duplicates(gchar **list)
{
size_t i,k,n;
gchar *s;
if( !list )
goto EXIT;
/* remove empty strings */
for( n = 0, k = 0; (s = list[k]); ++k ) {
if( *s )
list[n++] = s;
else
g_free(s);
}
list[n] = 0;
if( n < 2 )
goto EXIT;
/* sort elements */
qsort(list, n, sizeof *list, list_compare_item);
/* remove duplicate entries */
for( i = 0, k = 1; k < n; ++k ) {
s = list[k];
if( strcmp(list[i], s) )
list[++i] = s;
else
g_free(s);
}
list[i+1] = 0;
EXIT:
return;
}
/** Name exists in array of led pattern names predicate
*
* @param list array of led pattern names
* @param elem led pattern name
*
* @return TRUE if elem is in the list, FALSE otherwise
*/
static gboolean list_includes_item(gchar **list, const gchar *elem)
{
if( !list || !elem )
return FALSE;
for( size_t i = 0; list[i]; ++i ) {
if( !strcmp(list[i], elem) )
return TRUE;
}
return FALSE;
}
/**
* Init patterns for libhybris-LED
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_hybris_patterns(void)
{
enum {
IDX_PRIO, /* Pattern priority field */
IDX_SCREEN_ON, /* Pattern screen display policy field */
IDX_TIMEOUT, /* Pattern timeout field */
IDX_ON_PERIOD, /* On-period field */
IDX_OFF_PERIOD, /* Off-period field */
IDX_COLOR, /* LED color field */
IDX_NUMOF
};
gboolean status = FALSE;
gchar **require = NULL;
gchar **disable = NULL;
gchar **pattern = NULL;
/* Get the list of required LED patterns */
require = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_PATTERNS_REQUIRED, 0);
list_remove_duplicates(require);
/* Get the list of disabled LED patterns */
disable = mce_conf_get_string_list(MCE_CONF_LED_GROUP,
MCE_CONF_LED_PATTERNS_DISABLED, 0);
list_remove_duplicates(disable);
/* Get the list of configured patterns */
pattern = mce_conf_get_keys(led_pattern_group, 0);
list_remove_duplicates(pattern);
if( !pattern || !*pattern ) {
mce_log(LL_WARN, "No LED patterns configured");
goto EXIT;
}
/* Check if we have data for required patterns */
if( require && *require ) {
for( size_t i = 0; require[i]; ++i ) {
if( !list_includes_item(pattern, require[i]) )
mce_log(LL_WARN, "Required LED pattern "
"'%s' not defined", require[i]);
}
}
for( size_t i = 0; pattern[i]; i++ ) {
const char *name = pattern[i];
if( list_includes_item(disable, name) ) {
mce_log(LL_NOTICE,"LED pattern '%s' disabled", name);
continue;
}
gsize length = 0;
gchar **v = mce_conf_get_string_list(led_pattern_group,
name, &length);
if( !v ) {
mce_log(LL_WARN,"LED pattern '%s' not configured",
name);
}
else if( length != IDX_NUMOF ) {
mce_log(LL_ERR,"LED pattern '%s' is invalid",
name);
}
else {
mce_log(LL_DEBUG,"Getting LED pattern for: %s",
name);
pattern_struct *psp = led_pattern_create();
psp->name = strdup(name);
psp->priority = strtol(v[IDX_PRIO], 0, 0);
psp->policy = strtol(v[IDX_SCREEN_ON], 0, 0);
psp->timeout = strtol(v[IDX_TIMEOUT], 0, 0) ?: -1;
psp->on_period = strtol(v[IDX_ON_PERIOD], 0, 0);
psp->off_period = strtol(v[IDX_OFF_PERIOD], 0, 0);
psp->rgb_color = strtol(v[IDX_COLOR], 0, 16);
psp->active = FALSE;
psp->enabled = pattern_get_enabled(name,
&psp->setting_id);
g_queue_insert_sorted(pattern_stack, psp,
queue_prio_compare,
NULL);
}
g_strfreev(v);
}
init_combination_rules();
/* Set the LED brightness */
datapipe_exec_full(&led_brightness_pipe,
GINT_TO_POINTER(maximum_led_brightness));
status = TRUE;
EXIT:
g_strfreev(pattern);
g_strfreev(disable);
g_strfreev(require);
return status;
}
#endif /* ENABLE_HYBRIS */
/**
* Init patterns for the LED
*
* @return TRUE on success, FALSE on failure
*/
static gboolean init_patterns(void)
{
gboolean status = TRUE;
/* Type specific pattern configuration */
switch (get_led_type()) {
case LED_TYPE_LYSTI_MONO:
case LED_TYPE_LYSTI_RGB:
status = init_lysti_patterns();
break;
case LED_TYPE_NJOY_MONO:
case LED_TYPE_NJOY_RGB:
status = init_njoy_patterns();
break;
case LED_TYPE_DIRECT_MONO:
status = init_mono_patterns();
break;
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
status = init_hybris_patterns();
break;
#endif
default:
break;
}
/* Handle common pattern initialization */
for( GList *iter = pattern_stack->head; iter; iter = iter->next ) {
pattern_struct *psp = iter->data;
/* Add hbtimers for patterns that use timeout */
if( psp->timeout > 0 ) {
psp->timeout_id =
mce_hbtimer_create(psp->name,
psp->timeout * 1000,
led_pattern_timeout_cb,
psp);
}
}
return status;
}
/** Flag for: charger connected */
static charger_state_t charger_state = CHARGER_STATE_UNDEF;
/** Current battery percent level: assume unknown */
static int battery_level = MCE_BATTERY_LEVEL_UNKNOWN;
/** Setting: sw breathing allowed */
static gboolean sw_breathing_enabled = MCE_DEFAULT_LED_SW_BREATH_ENABLED;
static guint sw_breathing_enabled_setting_id = 0;
/** Setting: battery level at which sw breathing is disabled */
static gint sw_breathing_battery_limit = MCE_DEFAULT_LED_SW_BREATH_BATTERY_LIMIT;
static guint sw_breathing_battery_limit_setting_id = 0;
/** Re-evaluate sw breathing enable state
*/
static void sw_breathing_rethink(void)
{
bool breathe = false;
/* Check breathing configuration */
if( sw_breathing_enabled ) {
breathe = (charger_state == CHARGER_STATE_ON ||
battery_level >= sw_breathing_battery_limit);
}
/* Check if active pattern can utilize breathing */
if( !active_pattern ) {
/* If there is no active pattern, breathing must
* be disabled so that suspend does not get blocked */
breathe = false;
}
else {
/* Check for always breathing special cases */
if( !breathe )
breathe = led_pattern_should_breathe(active_pattern);
/* If pattern is configured not to breathe, we should
* not breathe even if it were allowed */
if( !led_pattern_can_breathe(active_pattern) )
breathe = false;
}
allow_sw_breathing(breathe);
}
/** Gconf notification callback function
*/
static void sw_breathing_setting_cb(GConfClient *const gcc, const guint id,
GConfEntry *const entry, gpointer const data)
{
(void)gcc;
(void)data;
(void)id;
const GConfValue *gcv = gconf_entry_get_value(entry);
if( !gcv ) {
mce_log(LL_DEBUG, "GConf Key `%s' has been unset",
gconf_entry_get_key(entry));
goto EXIT;
}
if( id == sw_breathing_enabled_setting_id ) {
sw_breathing_enabled = gconf_value_get_bool(gcv) ? 1 : 0;
sw_breathing_rethink();
}
else if( id == sw_breathing_battery_limit_setting_id ) {
sw_breathing_battery_limit = gconf_value_get_int(gcv);
sw_breathing_rethink();
}
else {
mce_log(LL_WARN, "Spurious GConf value received; confused!");
}
EXIT:
return;
}
/** De-initialize sw breathing state data
*/
static void sw_breathing_quit(void)
{
mce_setting_notifier_remove(sw_breathing_battery_limit_setting_id),
sw_breathing_battery_limit_setting_id = 0;
mce_setting_notifier_remove(sw_breathing_enabled_setting_id),
sw_breathing_enabled_setting_id = 0;
allow_sw_breathing(false);
}
/** Initialize sw breathing state data
*/
static void sw_breathing_init(void)
{
/* sw_breath_enabled */
mce_setting_notifier_add(MCE_SETTING_LED_PATH,
MCE_SETTING_LED_SW_BREATH_ENABLED,
sw_breathing_setting_cb,
&sw_breathing_enabled_setting_id);
mce_setting_get_bool(MCE_SETTING_LED_SW_BREATH_ENABLED,
&sw_breathing_enabled);
/* sw_breath_battery_limit */
mce_setting_notifier_add(MCE_SETTING_LED_PATH,
MCE_SETTING_LED_SW_BREATH_BATTERY_LIMIT,
sw_breathing_setting_cb,
&sw_breathing_battery_limit_setting_id);
mce_setting_get_int(MCE_SETTING_LED_SW_BREATH_BATTERY_LIMIT,
&sw_breathing_battery_limit);
}
/** Notification callback function for charger_state_pipe
*/
static void charger_state_trigger(gconstpointer data)
{
charger_state_t prev = charger_state;
charger_state = GPOINTER_TO_INT(data);
if( charger_state == prev )
goto EXIT;
mce_log(LL_DEBUG, "charger_state: %s -> %s",
charger_state_repr(prev),
charger_state_repr(charger_state));
sw_breathing_rethink();
EXIT:
return;
}
/** Notification callback function for battery_level_pipe
*/
static void battery_level_trigger(gconstpointer data)
{
int prev = battery_level;
battery_level = GPOINTER_TO_INT(data);
if( battery_level == prev )
goto EXIT;
mce_log(LL_DEBUG, "battery_level: %d -> %d", prev, battery_level);
sw_breathing_rethink();
EXIT:
return;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t led_dbus_handlers[] =
{
/* signals - outbound (for Introspect purposes only) */
{
.interface = MCE_SIGNAL_IF,
.name = MCE_LED_PATTERN_ACTIVATED_SIG,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.args =
" \n"
},
{
.interface = MCE_SIGNAL_IF,
.name = MCE_LED_PATTERN_DEACTIVATED_SIG,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.args =
" \n"
},
/* method calls */
{
.interface = MCE_REQUEST_IF,
.name = MCE_ACTIVATE_LED_PATTERN,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = led_activate_pattern_dbus_cb,
.args =
" \n"
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_DEACTIVATE_LED_PATTERN,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = led_deactivate_pattern_dbus_cb,
.args =
" \n"
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_ENABLE_LED,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = led_enable_dbus_cb,
.args =
""
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_DISABLE_LED,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = led_disable_dbus_cb,
.args =
""
},
/* sentinel */
{
.interface = 0
}
};
/** Add dbus handlers
*/
static void mce_led_init_dbus(void)
{
mce_dbus_handler_register_array(led_dbus_handlers);
}
/** Remove dbus handlers
*/
static void mce_led_quit_dbus(void)
{
mce_dbus_handler_unregister_array(led_dbus_handlers);
}
/** Array of datapipe handlers */
static datapipe_handler_t mce_led_datapipe_handlers[] =
{
// output triggers
{
.datapipe = &user_activity_event_pipe,
.output_cb = user_activity_event_trigger,
},
{
.datapipe = &system_state_pipe,
.output_cb = system_state_trigger,
},
{
.datapipe = &display_state_curr_pipe,
.output_cb = display_state_curr_trigger,
},
{
.datapipe = &led_brightness_pipe,
.output_cb = led_brightness_trigger,
},
{
.datapipe = &led_pattern_activate_pipe,
.output_cb = led_pattern_activate_trigger,
},
{
.datapipe = &led_pattern_deactivate_pipe,
.output_cb = led_pattern_deactivate_trigger,
},
{
.datapipe = &charger_state_pipe,
.output_cb = charger_state_trigger,
},
{
.datapipe = &battery_level_pipe,
.output_cb = battery_level_trigger,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t mce_led_datapipe_bindings =
{
.module = "led",
.handlers = mce_led_datapipe_handlers,
};
/** Append triggers/filters to datapipes
*/
static void
mce_led_datapipes_init(void)
{
mce_datapipe_init_bindings(&mce_led_datapipe_bindings);
}
/** Remove triggers/filters from datapipes
*/
static void
mce_led_datapipes_quit(void)
{
mce_datapipe_quit_bindings(&mce_led_datapipe_bindings);
}
/**
* Init function for the LED logic module
*
* @todo XXX status needs to be set on error!
*
* @param module Unused
* @return NULL on success, a string with an error message on failure
*/
const gchar *g_module_check_init(GModule *module)
{
gchar *status = NULL;
(void)module;
/* Append triggers/filters to datapipes */
mce_led_datapipes_init();
/* Setup a pattern stack,
* a combination rule stack and a cross-refernce for said stack
* and initialise the patterns
*/
pattern_stack = g_queue_new();
combination_rule_list = g_queue_new();
combination_rule_xref_list = g_queue_new();
if (init_patterns() == FALSE)
goto EXIT;
/* Add dbus handlers */
mce_led_init_dbus();
/* Initialize sw breathing state data */
sw_breathing_init();
charger_state_trigger(datapipe_value(&charger_state_pipe));
battery_level_trigger(datapipe_value(&battery_level_pipe));
/* Evaluate initial active pattern state */
led_enable();
EXIT:
return status;
}
/**
* Exit function for the LED logic module
*
* @todo D-Bus unregistration
*
* @param module Unused
*/
void g_module_unload(GModule *module)
{
(void)module;
/* Remove dbus handlers */
mce_led_quit_dbus();
/* Close files */
mce_close_output(&led_current_rm_output);
mce_close_output(&led_current_g_output);
mce_close_output(&led_current_b_output);
mce_close_output(&led_brightness_rm_output);
mce_close_output(&led_brightness_g_output);
mce_close_output(&led_brightness_b_output);
/* Remove triggers/filters from datapipes */
mce_led_datapipes_quit();
/* Remove breathing timers and wakelocks */
sw_breathing_quit();
/* Don't disable the LED on shutdown/reboot/acting dead */
if ((system_state != MCE_SYSTEM_STATE_ACTDEAD) &&
(system_state != MCE_SYSTEM_STATE_SHUTDOWN) &&
(system_state != MCE_SYSTEM_STATE_REBOOT)) {
led_set_active_pattern(0);
switch (get_led_type()) {
#ifdef ENABLE_HYBRIS
case LED_TYPE_HYBRIS:
/* The hybris plugin reprograms the led asynchronously
* after some delay. In this case we want to block
* until the led is actually turned off. */
mce_hybris_indicator_quit();
break;
#endif
default:
break;
}
}
/* Free path strings; this has to be done after
* led_set_active_pattern(0), since it uses these paths
*/
g_free((void*)led_current_rm_output.path);
g_free((void*)led_current_g_output.path);
g_free((void*)led_current_b_output.path);
g_free((void*)led_brightness_rm_output.path);
g_free((void*)led_brightness_g_output.path);
g_free((void*)led_brightness_b_output.path);
g_free(engine1_mode_path);
g_free(engine2_mode_path);
g_free(engine3_mode_path);
g_free(engine1_load_path);
g_free(engine2_load_path);
g_free(engine3_load_path);
g_free(engine1_leds_path);
g_free(engine2_leds_path);
g_free(engine3_leds_path);
/* Free the pattern stack */
if (pattern_stack != NULL) {
pattern_struct *psp;
while ((psp = g_queue_pop_head(pattern_stack)) != NULL) {
led_pattern_delete(psp);
}
g_queue_free(pattern_stack);
pattern_stack = NULL;
}
/* Free the combination rule list */
if (combination_rule_list != NULL) {
combination_rule_struct *cr;
while ((cr = g_queue_pop_head(combination_rule_list)) != NULL) {
gchar *tmp;
while ((tmp = g_queue_pop_head(cr->pre_requisites)) != NULL) {
g_free(tmp);
tmp = NULL;
}
g_queue_free(cr->pre_requisites);
cr->pre_requisites = NULL;
g_slice_free(combination_rule_struct, cr);
}
g_queue_free(combination_rule_list);
combination_rule_list = NULL;
}
/* Free the combination rule cross reference list */
if (combination_rule_xref_list != NULL) {
combination_rule_struct *xrf;
while ((xrf = g_queue_pop_head(combination_rule_xref_list)) != NULL) {
g_queue_free(xrf->pre_requisites);
xrf->pre_requisites = NULL;
g_slice_free(combination_rule_struct, xrf);
}
g_queue_free(combination_rule_xref_list);
combination_rule_xref_list = NULL;
}
return;
}