/**
* @file filter-brightness-als.c
* Ambient Light Sensor level adjusting filter module
* for display backlight, key backlight, and LED brightness
* This file implements a filter module for MCE
*
* Copyright © 2007-2011 Nokia Corporation and/or its subsidiary(-ies).
* Copyright © 2012-2019 Jolla Ltd.
*
* @author David Weinehall
* @author Tuomo Tanskanen
* @author Tapio Rantala
* @author Santtu Lakkala
* @author Jukka Turunen
* @author Victor Portnov
* @author Kalle Jokiniemi
* @author Simo Piiroinen
*
* 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 "filter-brightness-als.h"
#include "display.h"
#include "../mce-log.h"
#include "../mce-io.h"
#include "../mce-conf.h"
#include "../mce-setting.h"
#include "../mce-dbus.h"
#include "../mce-sensorfw.h"
#include "../mce-wakelock.h"
#include "../tklock.h"
#include
#include
#include
/* ========================================================================= *
* CONSTANTS
* ========================================================================= */
/** Module name */
#define MODULE_NAME "filter-brightness-als"
/** Maximum number of Lux->Brightness curves that can be defined in config
*
* Using odd number means the brightness setting values 1, 50 and 100 will
* be mapped to the 1st, the middle and the last of the profiles available.
*
* Using 21 equals rouhgly: 1 automatic curve / 5 brightness setting steps.
*/
#define FBA_PROFILE_COUNT 21
/** Maximum number of steps Lux->Brightness curves can have
*
* Using 21 allows: Covering the 1-100% brightness range in < 5% steps.
*/
#define FBA_PROFILE_STEPS 21
/** Size of the median filtering window; code expects the value to be odd */
#define FBA_INPUTFLT_MEDIAN_SIZE 9
/** Duration of temporary ALS enable sessions */
#define FBA_SENSORPOLL_DURATION_MS 5000
/* ========================================================================= *
* FUNCTIONALITY
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* MISC_UTILS
* ------------------------------------------------------------------------- */
static int fba_util_imin (int a, int b);
static bool fba_util_streq (const char *s1, const char *s2);
/* ------------------------------------------------------------------------- *
* DYNAMIC_SETTINGS
* ------------------------------------------------------------------------- */
static void fba_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data);
static void fba_setting_init (void);
static void fba_setting_quit (void);
/* ------------------------------------------------------------------------- *
* COLOR_PROFILE
* ------------------------------------------------------------------------- */
static bool fba_color_profile_exists (const char *id);
static bool fba_color_profile_set (const gchar *id);
static void fba_color_profile_init (void);
static void fba_color_profile_quit (void);
/* ------------------------------------------------------------------------- *
* SENSOR_INPUT_FILTERING
* ------------------------------------------------------------------------- */
/** Hooks that an input filter backend needs to implement */
typedef struct
{
const char *fi_name;
void (*fi_reset)(void);
int (*fi_filter)(int);
bool (*fi_stable)(void);
} fba_inputflt_t;
// INPUT_FILTER_BACKEND_DUMMY
static int fba_inputflt_dummy_filter (int add);
static bool fba_inputflt_dummy_stable (void);
static void fba_inputflt_dummy_reset (void);
// INPUT_FILTER_BACKEND_MEDIAN
static int fba_inputflt_median_filter (int add);
static bool fba_inputflt_median_stable (void);
static void fba_inputflt_median_reset (void);
// INPUT_FILTER_FRONTEND
static void fba_inputflt_reset (void);
static int fba_inputflt_filter (int lux);
static bool fba_inputflt_stable (void);
static void fba_inputflt_select (const char *name);
static void fba_inputflt_flush_on_change (void);
static void fba_inputflt_sampling_output (int lux);
static gboolean fba_inputflt_sampling_cb (gpointer aptr);
static void fba_inputflt_sampling_start (void);
static void fba_inputflt_sampling_stop (void);
static int fba_inputflt_sampling_time (void);
static void fba_inputflt_sampling_input (int lux);
static void fba_inputflt_init (void);
static void fba_inputflt_quit (void);
/* ------------------------------------------------------------------------- *
* ALS_FILTER
* ------------------------------------------------------------------------- */
/** A step in ALS ramp */
typedef struct
{
int lux; /**< upper lux limit */
int val; /**< brightness percentage to use */
} fba_als_limit_t;
/** ALS filtering state */
typedef struct
{
/** Filter name; used for locating configuration data */
const char *id;
/* Number of profiles available */
int profiles;
/** Threshold: lower lux limit */
int lux_lo;
/** Threshold: upper lux limit */
int lux_hi;
/** Threshold: active ALS profile */
int prof;
/** Latest brightness percentage result */
int val;
/** Brightness percent from lux value look up table */
fba_als_limit_t lut[FBA_PROFILE_COUNT][FBA_PROFILE_STEPS+1];
} fba_als_filter_t;
static void fba_als_filter_clear_threshold (fba_als_filter_t *self);
static bool fba_als_filter_load_profile (fba_als_filter_t *self, const char *grp, int prof);
static void fba_als_filter_reset_profiles (fba_als_filter_t *self);
static void fba_als_filter_load_profiles (fba_als_filter_t *self);
static int fba_als_filter_get_lux (fba_als_filter_t *self, int prof, int slot);
static int fba_als_filter_run (fba_als_filter_t *self, int prof, int lux);
static void fba_als_filter_init (void);
/* ------------------------------------------------------------------------- *
* DATAPIPE_TRACKING
* ------------------------------------------------------------------------- */
static gpointer fba_datapipe_display_brightness_filter (gpointer data);
static gpointer fba_datapipe_led_brightness_filter (gpointer data);
static gpointer fba_datapipe_lpm_brightness_filter (gpointer data);
static gpointer fba_datapipe_key_backlight_brightness_filter(gpointer data);
static gpointer fba_datapipe_light_sensor_poll_request_filter(gpointer data);
static void fba_datapipe_display_state_curr_trigger (gconstpointer data);
static void fba_datapipe_display_state_next_trigger (gconstpointer data);
static void fba_datapipe_execute_brightness_change (void);
static void fba_datapipe_init (void);
static void fba_datapipe_quit (void);
/* ------------------------------------------------------------------------- *
* DBUS_HANDLERS
* ------------------------------------------------------------------------- */
static void fba_dbus_send_current_color_profile (DBusMessage *method_call);
static gboolean fba_dbus_get_color_profile_cb (DBusMessage *const msg);
static gboolean fba_dbus_get_color_profiles_cb (DBusMessage *const msg);
static gboolean fba_dbus_set_color_profile_cb (DBusMessage *const msg);
static void fba_dbus_init (void);
static void fba_dbus_quit (void);
/* ------------------------------------------------------------------------- *
* SENSOR_STATUS
* ------------------------------------------------------------------------- */
static void fba_status_sensor_value_change_cb (int lux);
static bool fba_status_sensor_is_needed (void);
static void fba_status_rethink (void);
/* ------------------------------------------------------------------------- *
* SENSOR_POLL
* ------------------------------------------------------------------------- */
static gboolean fba_sensorpoll_timer_cb (gpointer aptr);
static void fba_sensorpoll_start (void);
static void fba_sensorpoll_stop (void);
static void fba_sensorpoll_rethink (void);
/* ------------------------------------------------------------------------- *
* LOAD_UNLOAD
* ------------------------------------------------------------------------- */
G_MODULE_EXPORT const gchar *g_module_check_init (GModule *module);
G_MODULE_EXPORT void g_module_unload (GModule *module);
/** Flag for: The plugin is about to be unloaded */
static bool fba_module_unload = false;
/* ========================================================================= *
* MISC_UTILS
* ========================================================================= */
/** Integer minimum helper in style of fminf()
*
* @param a integer value
* @param b integer value
*
* @return minimum of the two values
*/
static int fba_util_imin(int a, int b)
{
return (a < b) ? a : b;
}
/* Null tolerant string equality predicate
*
* @param s1 string
* @param s2 string
*
* @return true if both s1 and s2 are null or same string, false otherwise
*/
static bool fba_util_streq(const char *s1, const char *s2)
{
return (s1 && s2) ? !strcmp(s1, s2) : (s1 == s2);
}
/* ========================================================================= *
* DYNAMIC_SETTINGS
* ========================================================================= */
/** Master ALS enabled setting */
static gboolean fba_setting_als_enabled = MCE_DEFAULT_DISPLAY_ALS_ENABLED;
static guint fba_setting_als_enabled_id = 0;
/** Filter brightness through ALS setting */
static gboolean fba_setting_als_autobrightness = MCE_DEFAULT_DISPLAY_ALS_AUTOBRIGHTNESS;
static guint fba_setting_als_autobrightness_id = 0;
/** ALS is used for LID filtering setting */
static gboolean fba_setting_filter_lid_with_als = MCE_DEFAULT_TK_FILTER_LID_WITH_ALS;
static guint fba_setting_filter_lid_with_als_id = 0;
/** Input filter to use for ALS sensor - config value */
static gchar *fba_setting_als_input_filter = 0;
static guint fba_setting_als_input_filter_id = 0;
/** Sample time for ALS input filtering - config value */
static gint fba_setting_als_sample_time = MCE_DEFAULT_DISPLAY_ALS_SAMPLE_TIME;
static guint fba_setting_als_sample_time_id = 0;
/** Currently active color profile (dummy implementation) */
static gchar *fba_setting_color_profile = 0;
static guint fba_setting_color_profile_id = 0;
/** Default color profile, set in ini-files */
static gchar *fba_default_color_profile = 0;
/** GConf callback for powerkey related settings
*
* @param gcc (not used)
* @param id Connection ID from gconf_client_notify_add()
* @param entry The modified GConf entry
* @param data (not used)
*/
static void
fba_setting_cb(GConfClient *const gcc, const guint id,
GConfEntry *const entry, gpointer const data)
{
(void)gcc;
(void)data;
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 == fba_setting_als_enabled_id ) {
fba_setting_als_enabled = gconf_value_get_bool(gcv);
fba_status_rethink();
}
else if( id == fba_setting_als_autobrightness_id ) {
fba_setting_als_autobrightness = gconf_value_get_bool(gcv);
fba_status_rethink();
}
else if( id == fba_setting_filter_lid_with_als_id ) {
fba_setting_filter_lid_with_als = gconf_value_get_bool(gcv);
fba_status_rethink();
}
else if( id == fba_setting_als_input_filter_id ) {
const char *val = gconf_value_get_string(gcv);
if( !fba_util_streq(fba_setting_als_input_filter, val) ) {
g_free(fba_setting_als_input_filter);
fba_setting_als_input_filter = g_strdup(val);
fba_inputflt_select(fba_setting_als_input_filter);
}
}
else if( id == fba_setting_als_sample_time_id ) {
gint old = fba_setting_als_sample_time;
fba_setting_als_sample_time = gconf_value_get_int(gcv);
if( fba_setting_als_sample_time != old ) {
mce_log(LL_NOTICE, "fba_setting_als_sample_time: %d -> %d",
old, fba_setting_als_sample_time);
// NB: takes effect on the next sample timer restart
}
}
else if (id == fba_setting_color_profile_id) {
const gchar *val = gconf_value_get_string(gcv);
mce_log(LL_NOTICE, "fba_setting_color_profile: '%s' -> '%s'",
fba_setting_color_profile, val);
fba_color_profile_set(val);
}
else {
mce_log(LL_WARN, "Spurious GConf value received; confused!");
}
EXIT:
return;
}
/** Start tracking setting changes
*/
static void
fba_setting_init(void)
{
/* ALS enabled settings */
mce_setting_track_bool(MCE_SETTING_DISPLAY_ALS_ENABLED,
&fba_setting_als_enabled,
MCE_DEFAULT_DISPLAY_ALS_ENABLED,
fba_setting_cb,
&fba_setting_als_enabled_id);
mce_setting_track_bool(MCE_SETTING_DISPLAY_ALS_AUTOBRIGHTNESS,
&fba_setting_als_autobrightness,
MCE_DEFAULT_DISPLAY_ALS_AUTOBRIGHTNESS,
fba_setting_cb,
&fba_setting_als_autobrightness_id);
mce_setting_track_bool(MCE_SETTING_TK_FILTER_LID_WITH_ALS,
&fba_setting_filter_lid_with_als,
MCE_DEFAULT_TK_FILTER_LID_WITH_ALS,
fba_setting_cb,
&fba_setting_filter_lid_with_als_id);
/* ALS input filter setting */
mce_setting_track_string(MCE_SETTING_DISPLAY_ALS_INPUT_FILTER,
&fba_setting_als_input_filter,
MCE_DEFAULT_DISPLAY_ALS_INPUT_FILTER,
fba_setting_cb,
&fba_setting_als_input_filter_id);
/* ALS sample time setting */
mce_setting_track_int(MCE_SETTING_DISPLAY_ALS_SAMPLE_TIME,
&fba_setting_als_sample_time,
MCE_DEFAULT_DISPLAY_ALS_SAMPLE_TIME,
fba_setting_cb,
&fba_setting_als_sample_time_id);
/* Color profile setting */
mce_setting_notifier_add(MCE_SETTING_DISPLAY_PATH,
MCE_SETTING_DISPLAY_COLOR_PROFILE,
fba_setting_cb,
&fba_setting_color_profile_id);
fba_color_profile_init();
}
/** Stop tracking setting changes
*/
static void
fba_setting_quit(void)
{
mce_setting_notifier_remove(fba_setting_als_enabled_id),
fba_setting_als_enabled_id = 0;
mce_setting_notifier_remove(fba_setting_als_autobrightness_id),
fba_setting_als_autobrightness_id = 0;
mce_setting_notifier_remove(fba_setting_filter_lid_with_als_id),
fba_setting_filter_lid_with_als_id = 0;
mce_setting_notifier_remove(fba_setting_als_input_filter_id),
fba_setting_als_input_filter_id = 0;
mce_setting_notifier_remove(fba_setting_als_sample_time_id),
fba_setting_als_sample_time_id = 0;
mce_setting_notifier_remove(fba_setting_color_profile_id),
fba_setting_color_profile_id = 0;
fba_color_profile_quit();
}
/* ========================================================================= *
* COLOR_PROFILE
* ========================================================================= */
/** List of known color profiles (dummy implementation) */
static const char * const fba_color_profile_names[] =
{
COLOR_PROFILE_ID_HARDCODED,
};
/** Check if color profile is supported
*
* @param id color profile name
*
* @return true if profile is supported, false otherwise
*/
static bool
fba_color_profile_exists(const char *id)
{
if( !id || !*id )
return false;
if( !strcmp(id, COLOR_PROFILE_ID_HARDCODED) )
return true;
for( size_t i = 0; i < G_N_ELEMENTS(fba_color_profile_names); ++i ) {
if( !strcmp(fba_color_profile_names[i], id) )
return true;
}
return false;
}
/**
* Set the current color profile according to requested
*
* @param id Name of requested color profile
*
* @return true on success, false on failure
*/
static bool
fba_color_profile_set(const gchar *id)
{
// NOTE: color profile support dropped, this just a stub
/* Transform "default" request into request for whatever is
* the configured default color profile. */
if( !g_strcmp0(id, COLOR_PROFILE_ID_DEFAULT) )
id = fba_default_color_profile ?: COLOR_PROFILE_ID_HARDCODED;
/* Succes is: The requested color profile exists */
bool success = fba_color_profile_exists(id);
/* Fall back to hadrcoded if requested profile is not supported */
if( !success ) {
if( id && *id )
mce_log(LL_WARN, "%s: unsupported color profile", id);
id = COLOR_PROFILE_ID_HARDCODED;
}
/* Check if the color profile does change */
bool changed = g_strcmp0(id, fba_setting_color_profile);
/* Update the cached value */
if( changed ) {
g_free(fba_setting_color_profile),
fba_setting_color_profile = g_strdup(id);
}
/* Send a change indication if the value did change, or
* if the requested value was not accepted as-is */
if( changed || !success ) {
fba_dbus_send_current_color_profile(0);
}
/* If we were to do something about the color profile,
* it would happen here */
if( changed ) {
/* nop */
}
/* Always sync the settings cache */
mce_setting_set_string(MCE_SETTING_DISPLAY_COLOR_PROFILE,
fba_setting_color_profile);
return success;
}
/**
* Initialization of saveed color profile during boot
*/
static void
fba_color_profile_init(void)
{
/* Get the default value specified in static configuration.
* This must be done before fba_color_profile_set() is called.
*/
fba_default_color_profile =
mce_conf_get_string(MCE_CONF_COMMON_GROUP,
MCE_CONF_DEFAULT_PROFILE_ID_KEY,
NULL);
/* Apply the last value saved to dynamic settings / the default.
*/
gchar *saved_color_profile = 0;
mce_setting_get_string(MCE_SETTING_DISPLAY_COLOR_PROFILE,
&saved_color_profile);
fba_color_profile_set(saved_color_profile);
g_free(saved_color_profile);
}
static void
fba_color_profile_quit(void)
{
g_free(fba_default_color_profile),
fba_default_color_profile = 0;
g_free(fba_setting_color_profile),
fba_setting_color_profile = 0;
}
/* ========================================================================= *
* SENSOR_INPUT_FILTERING
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* INPUT_FILTER_BACKEND_DUMMY
* ------------------------------------------------------------------------- */
static int
fba_inputflt_dummy_filter(int add)
{
return add;
}
static bool
fba_inputflt_dummy_stable(void)
{
return true;
}
static void
fba_inputflt_dummy_reset(void)
{
}
/* ------------------------------------------------------------------------- *
* INPUT_FILTER_BACKEND_MEDIAN
* ------------------------------------------------------------------------- */
/** Moving window of ALS measurements */
static int fba_inputflt_median_fifo[FBA_INPUTFLT_MEDIAN_SIZE] = { };
/** Contents of fba_inputflt_median_fifo in ascending order */
static int fba_inputflt_median_stat[FBA_INPUTFLT_MEDIAN_SIZE] = { };;
static int
fba_inputflt_median_filter(int add)
{
/* Adding negative sample values mean the sensor is not
* in use and we should forget any history that exists */
if( add < 0 ) {
for( int i = 0; i < FBA_INPUTFLT_MEDIAN_SIZE; ++i )
fba_inputflt_median_fifo[i] = fba_inputflt_median_stat[i] = -1;
goto EXIT;
}
/* Value to be removed */
int rem = fba_inputflt_median_fifo[0];
/* If negative value gets shifted out, it means we do not
* have history. Initialize with the value we have */
if( rem < 0 ) {
for( int i = 0; i < FBA_INPUTFLT_MEDIAN_SIZE; ++i )
fba_inputflt_median_fifo[i] = fba_inputflt_median_stat[i] = add;
goto EXIT;
}
/* Shift the new value to the sample window fifo */
for( int i = 1; i < FBA_INPUTFLT_MEDIAN_SIZE; ++i )
fba_inputflt_median_fifo[i-1] = fba_inputflt_median_fifo[i];
fba_inputflt_median_fifo[FBA_INPUTFLT_MEDIAN_SIZE-1] = add;
/* If we shift in the same value as what was shifted out,
* the ordered statistics do not change */
if( add == rem )
goto EXIT;
/* Do one pass filtering of ordered statistics:
* Remove the value we shifted out of the fifo
* and insert the new sample to correct place. */
int src = 0, dst = 0, tmp;
int stk = fba_inputflt_median_stat[src++];
while( dst < FBA_INPUTFLT_MEDIAN_SIZE ) {
if( stk < add )
tmp = stk, stk = add, add = tmp;
if( add == rem )
rem = INT_MAX;
else
fba_inputflt_median_stat[dst++] = add;
if( src < FBA_INPUTFLT_MEDIAN_SIZE )
add = fba_inputflt_median_stat[src++];
else
add = INT_MAX;
}
EXIT:
mce_log(LL_DEBUG, "%d - %d - %d",
fba_inputflt_median_stat[0],
fba_inputflt_median_stat[FBA_INPUTFLT_MEDIAN_SIZE/2],
fba_inputflt_median_stat[FBA_INPUTFLT_MEDIAN_SIZE-1]);
/* Return median of the history window */
return fba_inputflt_median_stat[FBA_INPUTFLT_MEDIAN_SIZE/2];
}
static bool
fba_inputflt_median_stable(void)
{
/* The smallest and the largest values in the sorted
* samples buffer are equal */
int small = 0;
int large = FBA_INPUTFLT_MEDIAN_SIZE-1;
return fba_inputflt_median_stat[small] == fba_inputflt_median_stat[large];
}
static void
fba_inputflt_median_reset(void)
{
fba_inputflt_median_filter(-1);
}
/* ------------------------------------------------------------------------- *
* INPUT_FILTER_FRONTEND
* ------------------------------------------------------------------------- */
/** Array of available input filter backendss */
static const fba_inputflt_t fba_inputflt_lut[] =
{
// NOTE: "disabled" must be in the 1st slot
{
.fi_name = "disabled",
.fi_reset = fba_inputflt_dummy_reset,
.fi_filter = fba_inputflt_dummy_filter,
.fi_stable = fba_inputflt_dummy_stable,
},
{
.fi_name = "median",
.fi_reset = fba_inputflt_median_reset,
.fi_filter = fba_inputflt_median_filter,
.fi_stable = fba_inputflt_median_stable,
},
};
/** Currently used input filter backend */
static const fba_inputflt_t *fba_inputflt_cur = fba_inputflt_lut;
/** Latest Lux value fed in to the filter */
static gint fba_inputflt_input_lux = 0;
/** Latest Lux value emitted from the filter */
static gint fba_inputflt_output_lux = -1;
/** Flag for: forget history on the next als change */
static bool fba_inputflt_flush_history = true;
/** Timer ID for: ALS data sampling */
static guint fba_inputflt_sampling_id = 0;
/** Set input filter backend
*
* @param name name of the backend to use
*/
static void
fba_inputflt_select(const char *name)
{
fba_inputflt_reset();
if( !name ) {
fba_inputflt_cur = fba_inputflt_lut;
goto EXIT;
}
for( size_t i = 0; ; ++i ) {
if( i == G_N_ELEMENTS(fba_inputflt_lut) ) {
mce_log(LL_WARN, "filter '%s' is unknown", name);
fba_inputflt_cur = fba_inputflt_lut;
break;
}
if( !strcmp(fba_inputflt_lut[i].fi_name, name) ) {
fba_inputflt_cur = fba_inputflt_lut + i;
break;
}
}
EXIT:
mce_log(LL_NOTICE, "selected '%s' als filter",
fba_inputflt_cur->fi_name);
fba_inputflt_reset();
}
/** Reset history buffer
*
* Is used to clear stale history data when powering up the sensor.
*/
static void
fba_inputflt_reset(void)
{
fba_inputflt_cur->fi_reset();
}
/** Apply filtering backend for als sensor value
*
* @param add sample to shift into the filter history
*
* @return filtered value
*/
static int
fba_inputflt_filter(int lux)
{
return fba_inputflt_cur->fi_filter(lux);
}
/** Check if the whole history buffer is filled with the same value
*
* Is used to determine when the pseudo sampling timer can be stopped.
*/
static bool
fba_inputflt_stable(void)
{
return fba_inputflt_cur->fi_stable();
}
/** Request filter history to be flushed on the next ALS change
*/
static void
fba_inputflt_flush_on_change(void)
{
fba_inputflt_flush_history = true;
}
/** Get filtered ALS state and feed it to datapipes
*
* @param lux sensor reading, or -1 for no-data
*/
static void
fba_inputflt_sampling_output(int lux)
{
lux = fba_inputflt_filter(lux);
if( fba_inputflt_output_lux == lux )
goto EXIT;
mce_log(LL_DEBUG, "output: %d -> %d", fba_inputflt_output_lux, lux);
fba_inputflt_output_lux = lux;
fba_datapipe_execute_brightness_change();
/* Feed filtered sensor data to datapipe */
datapipe_exec_full(&light_sensor_filtered_pipe,
GINT_TO_POINTER(fba_inputflt_output_lux));
EXIT:
return;
}
/** Timer callback for: ALS data sampling
*
* @param aptr (unused user data pointer)
*
* @return TRUE to keep timer repeating, or FALSE to stop it
*/
static gboolean
fba_inputflt_sampling_cb(gpointer aptr)
{
(void)aptr;
if( !fba_inputflt_sampling_id )
return FALSE;
/* Drive the filter */
fba_inputflt_sampling_output(fba_inputflt_input_lux);
/* Keep timer active while changes come in */
if( !fba_inputflt_stable() )
return TRUE;
/* Stop sampling activity */
mce_log(LL_DEBUG, "stable");
fba_inputflt_sampling_id = 0;
return FALSE;
}
static int
fba_inputflt_sampling_time(void)
{
return mce_clip_int(ALS_SAMPLE_TIME_MIN,
ALS_SAMPLE_TIME_MAX,
fba_setting_als_sample_time);
}
/** Start ALS sampling timer
*/
static void
fba_inputflt_sampling_start(void)
{
// check if we need to flush history
if( fba_inputflt_flush_history ) {
fba_inputflt_flush_history = false;
mce_log(LL_DEBUG, "reset");
if( fba_inputflt_sampling_id ) {
g_source_remove(fba_inputflt_sampling_id),
fba_inputflt_sampling_id = 0;
}
fba_inputflt_reset();
}
// start collecting history
if( !fba_inputflt_sampling_id ) {
mce_log(LL_DEBUG, "start");
fba_inputflt_sampling_id = g_timeout_add(fba_inputflt_sampling_time(),
fba_inputflt_sampling_cb, 0);
fba_inputflt_sampling_output(fba_inputflt_input_lux);
}
}
/** Stop ALS sampling timer
*/
static void
fba_inputflt_sampling_stop(void)
{
if( fba_inputflt_sampling_id ) {
mce_log(LL_DEBUG, "stop");
g_source_remove(fba_inputflt_sampling_id),
fba_inputflt_sampling_id = 0;
}
fba_inputflt_sampling_output(fba_inputflt_input_lux);
}
/** Feed sensor input to filter
*
* @param lux sensor reading, or -1 for no-data
*/
static void
fba_inputflt_sampling_input(int lux)
{
if( fba_inputflt_input_lux == lux )
goto EXIT;
mce_log(LL_DEBUG, "input: %d -> %d", fba_inputflt_input_lux, lux);
fba_inputflt_input_lux = lux;
if( fba_inputflt_input_lux < 0 )
fba_inputflt_sampling_stop();
else
fba_inputflt_sampling_start();
EXIT:
return;
}
/** Initialize ALS filtering
*/
static void
fba_inputflt_init(void)
{
fba_inputflt_select(fba_setting_als_input_filter);
}
/** De-initialize ALS filtering
*/
static void
fba_inputflt_quit(void)
{
/* Stop sampling timer by feeding no-data to filter */
fba_inputflt_sampling_input(-1);
}
/* ========================================================================= *
* ALS_FILTER
* ========================================================================= */
/** ALS filtering state for display backlight brightness */
static fba_als_filter_t lut_display =
{
.id = "Display",
};
/** ALS filtering state for keypad backlight brightness */
static fba_als_filter_t lut_key =
{
.id = "Keypad",
};
/** ALS filtering state for indication led brightness */
static fba_als_filter_t lut_led =
{
.id = "Led",
};
/** ALS filtering state for low power mode display simulation */
static fba_als_filter_t lut_lpm =
{
.id = "LPM",
};
/** Remove transition thresholds from filtering state
*
* Set thresholds so that any lux value will be out of bounds.
*
* @param self ALS filtering state data
*/
static void
fba_als_filter_clear_threshold(fba_als_filter_t *self)
{
self->lux_lo = INT_MAX;
self->lux_hi = 0;
}
/** Load ALS ramp into filtering state
*
* @param self ALS filtering state data
* @param grp Configuration group name
* @param prof ALS profile id
*/
static bool
fba_als_filter_load_profile(fba_als_filter_t *self, const char *grp, int prof)
{
bool success = false;
gsize lim_cnt = 0;
gsize lev_cnt = 0;
gint *lim_val = 0;
gint *lev_val = 0;
char lim_key[64];
char lev_key[64];
snprintf(lim_key, sizeof lim_key, "LimitsProfile%d", prof);
snprintf(lev_key, sizeof lev_key, "LevelsProfile%d", prof);
lim_val = mce_conf_get_int_list(grp, lim_key, &lim_cnt);
lev_val = mce_conf_get_int_list(grp, lev_key, &lev_cnt);
if( !lim_val || lim_cnt < 1 ) {
if( prof == 0 )
mce_log(LL_WARN, "[%s] %s: no items", grp, lim_key);
goto EXIT;
}
if( !lev_val || lev_cnt != lim_cnt ) {
mce_log(LL_WARN, "[%s] %s: must have %zd items",
grp, lev_key, lim_cnt);
goto EXIT;
}
if( lim_cnt > FBA_PROFILE_STEPS ) {
lim_cnt = FBA_PROFILE_STEPS;
mce_log(LL_WARN, "[%s] %s: excess items",
grp, lim_key);
}
else if( lim_cnt < FBA_PROFILE_STEPS ) {
mce_log(LL_DEBUG, "[%s] %s: missing items",
grp, lim_key);
}
for( gsize k = 0; k < lim_cnt; ++k ) {
self->lut[prof][k].lux = lim_val[k];
self->lut[prof][k].val = lev_val[k];
}
success = true;
EXIT:
g_free(lim_val);
g_free(lev_val);
return success;
}
/** Initialize ALS filtering state
*
* @param self ALS filtering state data
*/
static void
fba_als_filter_reset_profiles(fba_als_filter_t *self)
{
/* Reset ramps to a state where any lux value will
* yield 100% brightness */
for( int i = 0; i < FBA_PROFILE_COUNT; ++i ) {
for( int k = 0; k <= FBA_PROFILE_STEPS; ++k ) {
self->lut[i][k].lux = INT_MAX;
self->lut[i][k].val = 100;
}
}
/* Default to 100% output */
self->val = 100;
/* Invalidate thresholds */
self->prof = -1;
fba_als_filter_clear_threshold(self);
}
/** Load ALS ramps into filtering state
*
* @param self ALS filtering state data
*/
static void
fba_als_filter_load_profiles(fba_als_filter_t *self)
{
fba_als_filter_reset_profiles(self);
char grp[64];
snprintf(grp, sizeof grp, "Brightness%s", self->id);
if( !mce_conf_has_group(grp) ) {
mce_log(LL_WARN, "[%s]: als config missing", grp);
goto EXIT;
}
for( self->profiles = 0; self->profiles < FBA_PROFILE_COUNT; ++self->profiles ) {
if( !fba_als_filter_load_profile(self, grp, self->profiles) )
break;
}
if( self->profiles < 1 )
mce_log(LL_WARN, "[%s]: als config broken", grp);
EXIT:
return;
}
/** Get lux value for given profile and step in ramp
*
* @param self ALS filtering state data
* @param prof ALS profile id
* @param slot position in ramp
*
* @return lux value
*/
static int
fba_als_filter_get_lux(fba_als_filter_t *self, int prof, int slot)
{
if( slot < 0 )
return 0;
if( slot < FBA_PROFILE_STEPS )
return self->lut[prof][slot].lux;
return INT_MAX;
}
/** Run ALS filter
*
* @param self ALS filtering state data
* @param prof ALS profile id
* @param lux ambient light value
*
* @return 0 ... 100 percentage
*/
static int
fba_als_filter_run(fba_als_filter_t *self, int prof, int lux)
{
mce_log(LL_DEBUG, "FILTERING: %s", self->id);
if( lux < 0 ) {
mce_log(LL_DEBUG, "no lux data yet");
goto EXIT;
}
if( self->prof != prof ) {
mce_log(LL_DEBUG, "profile changed");
}
else if( self->lux_lo <= lux && lux <= self->lux_hi ) {
mce_log(LL_DEBUG, "within thresholds");
goto EXIT;
}
int slot;
for( slot = 0; slot < FBA_PROFILE_STEPS; ++slot ) {
if( lux < self->lut[prof][slot].lux )
break;
}
self->prof = prof;
if( slot < FBA_PROFILE_STEPS )
self->val = self->lut[prof][slot].val;
else
self->val = 100;
self->lux_lo = 0;
self->lux_hi = INT_MAX;
/* Add hysteresis to transitions that make the display dimmer
*
* lux from ALS
* |
* | configuration slot
* | |
* v |
* 0----A------B-----C-----> [lux]
*
* |-------|
* threshold lo hi
*/
int a = fba_als_filter_get_lux(self, prof, slot-2);
int b = fba_als_filter_get_lux(self, prof, slot-1);
int c = fba_als_filter_get_lux(self, prof, slot+0);
self->lux_lo = b - fba_util_imin(b-a, c-b) / 10;
self->lux_hi = c;
mce_log(LL_DEBUG, "prof=%d, slot=%d, range=%d...%d",
prof, slot, self->lux_lo, self->lux_hi);
EXIT:
return self->val;
}
/** Setup ini-file based config items
*/
static void
fba_als_filter_init(void)
{
/* Read lux ramps from configuration */
fba_als_filter_load_profiles(&lut_display);
fba_als_filter_load_profiles(&lut_led);
fba_als_filter_load_profiles(&lut_key);
fba_als_filter_load_profiles(&lut_lpm);
}
/* ========================================================================= *
* DATAPIPE_TRACKING
* ========================================================================= */
/** Cached display state; tracked via fba_datapipe_display_state_curr_trigger() */
static display_state_t fba_display_state_curr = MCE_DISPLAY_UNDEF;
/** Cached target display state; tracked via fba_datapipe_display_state_next_trigger() */
static display_state_t fba_display_state_curr_next = MCE_DISPLAY_UNDEF;
/** Cached als poll state; tracked via fba_datapipe_light_sensor_poll_request_filter() */
static bool fba_light_sensor_polling = false;
/**
* Ambient Light Sensor filter for display brightness
*
* @param data The un-processed brightness setting (1-100) stored in a pointer
* @return The processed brightness value (percentage) stored in a pointer
*/
static gpointer
fba_datapipe_display_brightness_filter(gpointer data)
{
int setting = GPOINTER_TO_INT(data);
int brightness = setting;
if( !fba_setting_als_autobrightness || fba_inputflt_output_lux < 0 )
goto EXIT;
int max_prof = lut_display.profiles - 1;
if( max_prof < 0 )
goto EXIT;
int prof = mce_xlat_int(1,100, 0,max_prof, setting);
brightness = fba_als_filter_run(&lut_display, prof,
fba_inputflt_output_lux);
EXIT:
mce_log(LL_DEBUG, "in=%d -> out=%d", setting, brightness);
return GINT_TO_POINTER(brightness);
}
/**
* Ambient Light Sensor filter for LED brightness
*
* @param data The un-processed brightness setting (1-100) stored in a pointer
* @return The processed brightness value
*/
static gpointer
fba_datapipe_led_brightness_filter(gpointer data)
{
/* Startup default: LED brightness scale is unknown */
static int prev_scale = -1;
/* Default to: Brightness setting * 40 % */
int brightness = GPOINTER_TO_INT(data);
int curr_scale = 40;
/* Check if LED brightness configuration exists */
if( lut_led.profiles < 1 )
goto EXIT;
/* Forget cached output state if als master toggle or
* autobrightness setting gets disabled */
if( !fba_setting_als_enabled ||
!fba_setting_als_autobrightness ) {
prev_scale = -1;
goto EXIT;
}
if( fba_inputflt_output_lux >= 0 ) {
/* Evaluate brightness scale based on available sensor data */
curr_scale = fba_als_filter_run(&lut_led, 0, fba_inputflt_output_lux);
prev_scale = curr_scale;
}
else if( prev_scale >= 0 ) {
/* Use previously evaluated brightness scale */
curr_scale = prev_scale;
}
EXIT:
return GINT_TO_POINTER(brightness * curr_scale / 100);
}
/** Ambient Light Sensor filter for LPM brightness
*
* @param data The un-processed brightness setting (1-100) stored in a pointer
* @return The processed brightness value
*/
static gpointer
fba_datapipe_lpm_brightness_filter(gpointer data)
{
int value = GPOINTER_TO_INT(data);
if( !fba_setting_als_autobrightness || fba_inputflt_output_lux < 0 )
goto EXIT;
if( lut_lpm.profiles < 1 )
goto EXIT;
/* Note: Input value is ignored and output is
* determined only by the als config */
value = fba_als_filter_run(&lut_lpm, 0, fba_inputflt_output_lux);
EXIT:
return GINT_TO_POINTER(value);
}
/**
* Ambient Light Sensor filter for keyboard backlight brightness
*
* @param data The un-processed brightness setting (1-100) stored in a pointer
* @return The processed brightness value
*/
static gpointer
fba_datapipe_key_backlight_brightness_filter(gpointer data)
{
int value = GPOINTER_TO_INT(data);
int scale = 100;
if( !fba_setting_als_autobrightness || fba_inputflt_output_lux < 0 )
goto EXIT;
if( lut_key.profiles < 1 )
goto EXIT;
scale = fba_als_filter_run(&lut_key, 0, fba_inputflt_output_lux);
EXIT:
return GINT_TO_POINTER(value * scale / 100);
}
/** Ambient Light Sensor filter for temporary sensor enable
*
* @param data Requested sensor enable/disable bool (as void pointer)
*
* @return Granted sensor enable/disable bool (as void pointer)
*/
static gpointer
fba_datapipe_light_sensor_poll_request_filter(gpointer data)
{
bool prev = fba_light_sensor_polling;
fba_light_sensor_polling = GPOINTER_TO_INT(data);
if( !fba_setting_als_enabled )
fba_light_sensor_polling = FALSE;
if( fba_light_sensor_polling == prev )
goto EXIT;
mce_log(LL_DEVEL, "light_sensor_polling = %s",
fba_light_sensor_polling ? "true" : "false");
/* Sensor status is affected only if the value changes */
fba_status_rethink();
EXIT:
/* The termination timer must be renewed/stopped even
* if the value does not change. */
fba_sensorpoll_rethink();
return GINT_TO_POINTER(fba_light_sensor_polling);
}
/**
* Handle display state change
*
* @param data The display stated stored in a pointer
*/
static void
fba_datapipe_display_state_curr_trigger(gconstpointer data)
{
display_state_t prev = fba_display_state_curr;
fba_display_state_curr = GPOINTER_TO_INT(data);
if( prev == fba_display_state_curr )
goto EXIT;
mce_log(LL_DEBUG, "display_state_curr: %s -> %s",
display_state_repr(prev),
display_state_repr(fba_display_state_curr));
fba_status_rethink();
EXIT:
return;
}
static void
fba_datapipe_display_state_next_trigger(gconstpointer data)
{
display_state_t prev = fba_display_state_curr_next;
fba_display_state_curr_next = GPOINTER_TO_INT(data);
if( prev == fba_display_state_curr_next )
goto EXIT;
mce_log(LL_DEBUG, "display_state_next: %s -> %s",
display_state_repr(prev),
display_state_repr(fba_display_state_curr_next));
fba_status_rethink();
EXIT:
return;
}
static void
fba_datapipe_execute_brightness_change(void)
{
/* Re-filter the brightness */
datapipe_exec_full(&display_brightness_pipe,
datapipe_value(&display_brightness_pipe));
datapipe_exec_full(&led_brightness_pipe,
datapipe_value(&led_brightness_pipe));
datapipe_exec_full(&lpm_brightness_pipe,
datapipe_value(&lpm_brightness_pipe));
datapipe_exec_full(&key_backlight_brightness_pipe,
datapipe_value(&key_backlight_brightness_pipe));
}
/** Array of datapipe handlers */
static datapipe_handler_t fba_datapipe_handlers[] =
{
// input filters
{
.datapipe = &display_brightness_pipe,
.filter_cb = fba_datapipe_display_brightness_filter,
},
{
.datapipe = &led_brightness_pipe,
.filter_cb = fba_datapipe_led_brightness_filter,
},
{
.datapipe = &lpm_brightness_pipe,
.filter_cb = fba_datapipe_lpm_brightness_filter,
},
{
.datapipe = &key_backlight_brightness_pipe,
.filter_cb = fba_datapipe_key_backlight_brightness_filter,
},
{
.datapipe = &light_sensor_poll_request_pipe,
.filter_cb = fba_datapipe_light_sensor_poll_request_filter,
},
// output triggers
{
.datapipe = &display_state_next_pipe,
.output_cb = fba_datapipe_display_state_next_trigger,
},
{
.datapipe = &display_state_curr_pipe,
.output_cb = fba_datapipe_display_state_curr_trigger,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t fba_datapipe_bindings =
{
.module = MODULE_NAME,
.handlers = fba_datapipe_handlers,
};
/** Install datapipe triggers/filters
*/
static void
fba_datapipe_init(void)
{
mce_datapipe_init_bindings(&fba_datapipe_bindings);
}
/** Remove datapipe triggers/filters
*/
static void
fba_datapipe_quit(void)
{
mce_datapipe_quit_bindings(&fba_datapipe_bindings);
}
/* ========================================================================= *
* DBUS_HANDLERS
* ========================================================================= */
/**
* Send the current profile id
*
* @param method_call A DBusMessage to reply to
*/
static void
fba_dbus_send_current_color_profile(DBusMessage *method_call)
{
DBusMessage *msg = 0;
const char *val = fba_setting_color_profile;
if( !fba_color_profile_exists(val) )
val = COLOR_PROFILE_ID_HARDCODED;
if( method_call )
msg = dbus_new_method_reply(method_call);
else
msg = dbus_new_signal(MCE_SIGNAL_PATH,
MCE_SIGNAL_IF,
MCE_COLOR_PROFILE_SIG);
if( !msg )
goto EXIT;
if( !dbus_message_append_args(msg,
DBUS_TYPE_STRING, &val,
DBUS_TYPE_INVALID) )
goto EXIT;
dbus_send_message(msg), msg = 0;
EXIT:
if( msg ) dbus_message_unref(msg);
}
/**
* D-Bus callback for the get color profile method call
*
* @param msg The D-Bus message
* @return TRUE
*/
static gboolean
fba_dbus_get_color_profile_cb(DBusMessage *const msg)
{
mce_log(LL_DEVEL, "Received get color profile request from %s",
mce_dbus_get_message_sender_ident(msg));
if( dbus_message_get_no_reply(msg) )
goto EXIT;
fba_dbus_send_current_color_profile(msg);
EXIT:
return TRUE;
}
/** D-Bus callback for the get color profile ids method call
*
* @param msg The D-Bus message
* @return TRUE
*/
static gboolean
fba_dbus_get_color_profiles_cb(DBusMessage *const msg)
{
mce_log(LL_DEVEL, "Received list color profiles request from %s",
mce_dbus_get_message_sender_ident(msg));
DBusMessage *rsp = 0;
int cnt = sizeof fba_color_profile_names / sizeof *fba_color_profile_names;
const char * const * vec = fba_color_profile_names;
if( dbus_message_get_no_reply(msg) )
goto EXIT;
if( !(rsp = dbus_message_new_method_return(msg)) )
goto EXIT;
if( !dbus_message_append_args(rsp,
DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING, &vec, cnt,
DBUS_TYPE_INVALID) )
goto EXIT;
dbus_send_message(rsp), rsp = 0;
EXIT:
if( rsp ) dbus_message_unref(rsp);
return TRUE;
}
/** D-Bus callback for the color profile change method call
*
* @param msg The D-Bus message
* @return TRUE
*/
static gboolean
fba_dbus_set_color_profile_cb(DBusMessage *const msg)
{
mce_log(LL_DEVEL, "Received set color profile request from %s",
mce_dbus_get_message_sender_ident(msg));
const char *val = 0;
DBusError err = DBUS_ERROR_INIT;
dbus_bool_t ack = FALSE;
DBusMessage *rsp = 0;
if( !dbus_message_get_args(msg, &err,
DBUS_TYPE_STRING, &val,
DBUS_TYPE_INVALID)) {
// XXX: should we return an error instead?
mce_log(LL_ERR, "Failed to get argument from %s.%s: %s: %s",
MCE_REQUEST_IF, MCE_COLOR_PROFILE_CHANGE_REQ,
err.name, err.message);
}
else {
if( fba_color_profile_set(val) )
ack = TRUE;
}
if( dbus_message_get_no_reply(msg) )
goto EXIT;
if( !(rsp = dbus_message_new_method_return(msg)) )
goto EXIT;
dbus_message_append_args(rsp,
DBUS_TYPE_BOOLEAN, &ack,
DBUS_TYPE_INVALID);
EXIT:
if( rsp ) dbus_send_message(rsp), rsp = 0;
dbus_error_free(&err);
return TRUE;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t filter_brightness_dbus_handlers[] =
{
/* signals - outbound (for Introspect purposes only) */
{
.interface = MCE_SIGNAL_IF,
.name = MCE_COLOR_PROFILE_SIG,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.args =
" \n"
},
/* method calls */
{
.interface = MCE_REQUEST_IF,
.name = MCE_COLOR_PROFILE_GET,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = fba_dbus_get_color_profile_cb,
.args =
" \n"
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_COLOR_PROFILE_IDS_GET,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = fba_dbus_get_color_profiles_cb,
.args =
" \n"
},
{
.interface = MCE_REQUEST_IF,
.name = MCE_COLOR_PROFILE_CHANGE_REQ,
.type = DBUS_MESSAGE_TYPE_METHOD_CALL,
.callback = fba_dbus_set_color_profile_cb,
.args =
" \n"
" \n"
},
/* sentinel */
{
.interface = 0
}
};
/** Add dbus handlers
*/
static void
fba_dbus_init(void)
{
mce_dbus_handler_register_array(filter_brightness_dbus_handlers);
}
/** Remove dbus handlers
*/
static void
fba_dbus_quit(void)
{
mce_dbus_handler_unregister_array(filter_brightness_dbus_handlers);
}
/* ========================================================================= *
* SENSOR_STATUS
* ========================================================================= */
/** Raw lux value from the ALS */
static gint fba_status_sensor_lux = -1;
/** Handle lux value changed event
*
* @param lux ambient light value
*/
static void
fba_status_sensor_value_change_cb(int lux)
{
if( !fba_setting_als_enabled )
fba_status_sensor_lux = -1;
else
fba_status_sensor_lux = lux;
mce_log(LL_DEBUG, "sensor: %d", fba_status_sensor_lux);
/* Filter raw sensor data */
fba_inputflt_sampling_input(fba_status_sensor_lux);
/* Feed raw sensor data to datapipe */
datapipe_exec_full(&light_sensor_actual_pipe,
GINT_TO_POINTER(fba_status_sensor_lux));
}
static bool
fba_status_sensor_is_needed(void)
{
bool need_als = false;
switch( fba_display_state_curr_next ) {
case MCE_DISPLAY_ON:
case MCE_DISPLAY_DIM:
case MCE_DISPLAY_LPM_OFF:
case MCE_DISPLAY_LPM_ON:
if( fba_setting_als_autobrightness || fba_setting_filter_lid_with_als )
need_als = true;
break;
default:
case MCE_DISPLAY_OFF:
case MCE_DISPLAY_UNDEF:
break;
}
return need_als;
}
/** Check if ALS sensor should be enabled or disabled
*/
static void
fba_status_rethink(void)
{
static int old_autobrightness = -1;
static int enable_old = -1;
bool enable_new = false;
if( fba_setting_als_enabled )
enable_new = (fba_light_sensor_polling ||
fba_status_sensor_is_needed());
if( fba_module_unload )
enable_new = false;
if( enable_old == enable_new )
goto EXIT;
mce_log(LL_DEBUG, "enabled=%d; autobright=%d; filter_lid=%d -> enable=%d",
fba_setting_als_enabled, fba_setting_als_autobrightness, fba_setting_filter_lid_with_als, enable_new);
enable_old = enable_new;
if( enable_new ) {
/* Enable sensor before attaching notification callback.
* So that the last seen light sensor reading is made active
* again and reported instead of the fallback/default value. */
mce_sensorfw_als_enable();
mce_sensorfw_als_set_notify(fba_status_sensor_value_change_cb);
/* The sensor has been off for some time, so the
* history needs to be forgotten when we get fresh
* data */
fba_inputflt_flush_on_change();
}
else {
/* Disable change notifications */
mce_sensorfw_als_set_notify(0);
/* Force cached value re-evaluation */
fba_status_sensor_value_change_cb(-1);
mce_sensorfw_als_disable();
/* Clear thresholds so that the next reading from
* als will not be affected by previous state */
fba_als_filter_clear_threshold(&lut_display);
fba_als_filter_clear_threshold(&lut_led);
fba_als_filter_clear_threshold(&lut_key);
fba_als_filter_clear_threshold(&lut_lpm);
}
EXIT:
if( old_autobrightness != fba_setting_als_autobrightness ) {
old_autobrightness = fba_setting_als_autobrightness;
fba_datapipe_execute_brightness_change();
}
/* Block device from suspending while temporary ALS poll is active */
if( enable_new && fba_light_sensor_polling )
mce_wakelock_obtain("als_poll", -1);
else
mce_wakelock_release("als_poll");
return;
}
/* ========================================================================= *
* SENSOR_POLL
* ========================================================================= */
/** Timer ID: Terminate temporary ALS enable */
static guint fba_sensorpoll_timer_id = 0;
/** Timer callback: Terminate temporary ALS enable
*
* @param aptr user data (unused)
*
* @return FALSE to stop timer from repeating
*/
static gboolean
fba_sensorpoll_timer_cb(gpointer aptr)
{
(void)aptr;
if( !fba_sensorpoll_timer_id )
goto EXIT;
mce_log(LL_DEBUG, "als poll: %s", "timeout");
fba_sensorpoll_timer_id = 0;
datapipe_exec_full(&light_sensor_poll_request_pipe,
GINT_TO_POINTER(false));
EXIT:
return FALSE;
}
/** Schedule temporary ALS enable termination
*
* If the timer is already active, the termination time
* will be rescheduled.
*/
static void
fba_sensorpoll_start(void)
{
if( fba_sensorpoll_timer_id )
g_source_remove(fba_sensorpoll_timer_id);
else
mce_log(LL_DEBUG, "als poll: %s", "start");
fba_sensorpoll_timer_id = g_timeout_add(FBA_SENSORPOLL_DURATION_MS,
fba_sensorpoll_timer_cb, 0);
}
/** Cancel temporary ALS enable termination
*/
static void
fba_sensorpoll_stop(void)
{
if( !fba_sensorpoll_timer_id )
goto EXIT;
mce_log(LL_DEBUG, "als poll: %s", "stop");
g_source_remove(fba_sensorpoll_timer_id),
fba_sensorpoll_timer_id = 0;
EXIT:
return;
}
/** Evaluate need for temporary ALS enable termination
*/
static void fba_sensorpoll_rethink(void)
{
if( fba_light_sensor_polling )
fba_sensorpoll_start();
else
fba_sensorpoll_stop();
}
/* ========================================================================= *
* LOAD_UNLOAD
* ========================================================================= */
/** Init function for the ALS filter
*
* @param module (Unused)
*
* @return NULL on success, a string with an error message on failure
*/
const gchar *
g_module_check_init(GModule *module)
{
(void)module;
fba_als_filter_init();
fba_datapipe_init();
fba_dbus_init();
fba_setting_init();
fba_inputflt_init();
fba_status_rethink();
return NULL;
}
/** Exit function for the ALS filter
*
* @param module (Unused)
*/
void
g_module_unload(GModule *module)
{
(void)module;
/* Mark that plugin is about to be unloaded */
fba_module_unload = true;
fba_setting_quit();
fba_dbus_quit();
fba_datapipe_quit();
/* Final rethink to release wakelock & detach from sensorfw */
fba_status_rethink();
/* Make sure no timers with invalid callbacks are left active */
fba_sensorpoll_stop();
fba_inputflt_quit();
g_free(fba_setting_als_input_filter),
fba_setting_als_input_filter = 0;
return;
}