/** * @file powerkey.c * Power key logic for the Mode Control Entity *

* Copyright © 2004-2011 Nokia Corporation and/or its subsidiary(-ies). * Copyright © 2013-2019 Jolla Ltd. *

* @author David Weinehall * @author Tapio Rantala * @author Santtu Lakkala * @author Irina Bezruk * @author Simo Piiroinen * @author Kimmo Lindholm * @author Andrew den Exter * * 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 "powerkey.h" #include "tklock.h" #include "evdev.h" #include "mce-log.h" #include "mce-lib.h" #include "mce-setting.h" #include "mce-common.h" #include "mce-dbus.h" #include "mce-dsme.h" #include "modules/doubletap.h" #include "systemui/dbus-names.h" #ifdef ENABLE_WAKELOCKS # include "libwakelock.h" #endif #include #include #include #include #include #include #include #include /* ========================================================================= * * OVERVIEW * * There is a predefined set of actions. Of these two are dbus actions * that by default make mce broadcast dbus signals, but can be configured * to make any dbus method call with optional string argument. * * Any combination of these actions can be bound to: * - single power key press * - double power key press * - long power key press * * The selected actions are executed in a fixed order and actions that * are common to both single and double press are executed immediately * after powerkey is released. This allows double press configuration * to extend what would be done with single press without causing * delays for single press handling. * * Separate combinations are used depending on whether the * display is on or off during the 1st power key press. * * The build-in defaults are as follows * * From display off: * - single press - turns display on * - double press - turns display on and hides lockscreen (but not device lock) * - long press - does nothing * * From display on: * - single press - turns display off and activates locksreen * - double press - turns display off, activates locksreen and locks device * - long press - initiates shutdown (if lockscreen is not active) * * Effectively this is just as before, except for the double press * actions to apply device lock / wake up to home screen. * ========================================================================= */ /* ========================================================================= * * CONSTANTS * ========================================================================= */ #define MODULE_NAME "powerkey" /* ========================================================================= * * PROTOTYPES * ========================================================================= */ /* ------------------------------------------------------------------------- * * MISC_UTIL * ------------------------------------------------------------------------- */ /* 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 inline bool eq(const char *s1, const char *s2) { return (s1 && s2) ? !strcmp(s1, s2) : (s1 == s2); } /** String is NULL or empty predicate */ static inline bool empty(const char *s) { return s == 0 || *s == 0; } static char *pwrkey_get_token(char **ppos); static bool pwrkey_create_flagfile(const char *path); static bool pwrkey_delete_flagfile(const char *path); static int64_t pwrkey_event_tick(const struct input_event *ev); /* ------------------------------------------------------------------------- * * PS_OVERRIDE * * Provides escape from stuck proximity sensor. * ------------------------------------------------------------------------- */ /** [setting] Power key press count for proximity sensor override */ static gint pwrkey_ps_override_count = MCE_DEFAULT_POWERKEY_PS_OVERRIDE_COUNT; static guint pwrkey_ps_override_count_setting_id = 0; /** [setting] Maximum time between power key presses for proximity sensor override */ static gint pwrkey_ps_override_timeout = MCE_DEFAULT_POWERKEY_PS_OVERRIDE_TIMEOUT; static guint pwrkey_ps_override_timeout_setting_id = 0; static void pwrkey_ps_override_evaluate(void); /* ------------------------------------------------------------------------- * * ACTION_EXEC * * Individual actions that can be taken. * ------------------------------------------------------------------------- */ static gint pwrkey_action_blank_mode = MCE_DEFAULT_POWERKEY_BLANKING_MODE; static guint pwrkey_action_blank_mode_setting_id = 0; static void pwrkey_action_vibrate (void); static void pwrkey_action_shutdown (void); static void pwrkey_action_tklock (void); static void pwrkey_action_blank (void); static void pwrkey_action_unblank (void); static void pwrkey_action_unblanked(void); static void pwrkey_action_tkunlock (void); static void pwrkey_action_tkunlock2(void); static void pwrkey_action_devlock (void); static void pwrkey_action_dbus1 (void); static void pwrkey_action_dbus2 (void); static void pwrkey_action_dbus3 (void); static void pwrkey_action_dbus4 (void); static void pwrkey_action_dbus5 (void); static void pwrkey_action_dbus6 (void); static void pwrkey_action_dbus7 (void); static void pwrkey_action_dbus8 (void); static void pwrkey_action_dbus9 (void); static void pwrkey_action_dbus10 (void); static void pwrkey_action_nop (void); /* ------------------------------------------------------------------------- * * ACTION_SETS * * Handle sets of individual actions. * ------------------------------------------------------------------------- */ typedef struct { const char *name; void (*func)(void); } pwrkey_bitconf_t; static uint32_t pwrkey_mask_from_name (const char *name); static uint32_t pwrkey_mask_from_names (const char *names); static gchar *pwrkey_mask_to_names (uint32_t mask); /* ------------------------------------------------------------------------- * * GESTURE_FILTERING * ------------------------------------------------------------------------- */ /** Touchscreen gesture (doubletap etc) enable mode */ static gint pwrkey_gestures_enable_mode = MCE_DEFAULT_DOUBLETAP_MODE; static guint pwrkey_gestures_enable_mode_cb_id = 0; /* ------------------------------------------------------------------------- * * PWRKEY_PREDICATE * ------------------------------------------------------------------------- */ /** Enumeratio of possible unblank allowed predicates */ typedef enum { /** Apply powerkey press rules */ PWRKEY_UNBLANK_PREDICATE_POWERKEY, /** Apply fingerprint wakeup rules */ PWRKEY_UNBLANK_PREDICATE_FPWAKEUP, /** Apply rules for real gesture events */ PWRKEY_UNBLANK_PREDICATE_GESTURE_REAL, /** Apply rules for synthetized gesture events */ PWRKEY_UNBLANK_PREDICATE_GESTURE_SYNTH, /** Rules for action sets that do not include unblank */ PWRKEY_UNBLANK_PREDICATE_DONTCARE, PWRKEY_UNBLANK_PREDICATE_COUNT } pwrkey_unblank_predicate_t; /** Lookup table of unblank predicate names (for diagnostic logging) */ static const char * const pwrkey_unblank_predicate_name[PWRKEY_UNBLANK_PREDICATE_COUNT] = { [PWRKEY_UNBLANK_PREDICATE_POWERKEY] = "powerkey", [PWRKEY_UNBLANK_PREDICATE_FPWAKEUP] = "fpwakeup", [PWRKEY_UNBLANK_PREDICATE_GESTURE_REAL] = "gesture_real", [PWRKEY_UNBLANK_PREDICATE_GESTURE_SYNTH] = "gesture_synth", [PWRKEY_UNBLANK_PREDICATE_DONTCARE] = "dontcare", }; static bool pwrkey_predicate_gesture (bool synthesized, bool *need_psensor); static bool pwrkey_predicate_fpwakeup(bool *need_psensor); static bool pwrkey_predicate_powerkey(bool *need_psensor); static bool pwrkey_predicate (pwrkey_unblank_predicate_t predicate, bool *need_psensor); /* ------------------------------------------------------------------------- * * ACTION_TRIGGERING * ------------------------------------------------------------------------- */ typedef struct { /** Actions common to single and double press */ uint32_t mask_common; /** Actions for single press */ uint32_t mask_single; /** Actions for double press */ uint32_t mask_double; /** Actions for long press */ uint32_t mask_long; } pwrkey_actions_t; /** Actions when power key is pressed while display is on */ static pwrkey_actions_t pwrkey_actions_from_display_on = { 0, 0, 0, 0 }; /** Actions when power key is pressed while display is off */ static pwrkey_actions_t pwrkey_actions_from_display_off = { 0, 0, 0, 0 }; /** Actions on touch screen gestures */ static pwrkey_actions_t pwrkey_actions_from_gesture[POWERKEY_ACTIONS_GESTURE_COUNT] = {}; /** Currently selected power key actions; default to turning display on */ static pwrkey_actions_t *pwrkey_actions_now = &pwrkey_actions_from_display_off; static gchar *pwrkey_actions_single_on = 0; static guint pwrkey_actions_single_on_setting_id = 0; static gchar *pwrkey_actions_double_on = 0; static guint pwrkey_actions_double_on_setting_id = 0; static gchar *pwrkey_actions_long_on = 0; static guint pwrkey_actions_long_on_setting_id = 0; static gchar *pwrkey_actions_single_off = 0; static guint pwrkey_actions_single_off_setting_id = 0; static gchar *pwrkey_actions_double_off = 0; static guint pwrkey_actions_double_off_setting_id = 0; static gchar *pwrkey_actions_long_off = 0; static guint pwrkey_actions_long_off_setting_id = 0; /** Array of setting keys for configurable touchscreen gestures */ static const char * const pwrkey_actions_gesture_key[POWERKEY_ACTIONS_GESTURE_COUNT] = { MCE_SETTING_POWERKEY_ACTIONS_GESTURE0, MCE_SETTING_POWERKEY_ACTIONS_GESTURE1, MCE_SETTING_POWERKEY_ACTIONS_GESTURE2, MCE_SETTING_POWERKEY_ACTIONS_GESTURE3, MCE_SETTING_POWERKEY_ACTIONS_GESTURE4, MCE_SETTING_POWERKEY_ACTIONS_GESTURE5, MCE_SETTING_POWERKEY_ACTIONS_GESTURE6, MCE_SETTING_POWERKEY_ACTIONS_GESTURE7, MCE_SETTING_POWERKEY_ACTIONS_GESTURE8, MCE_SETTING_POWERKEY_ACTIONS_GESTURE9, MCE_SETTING_POWERKEY_ACTIONS_GESTURE10, MCE_SETTING_POWERKEY_ACTIONS_GESTURE11, MCE_SETTING_POWERKEY_ACTIONS_GESTURE12, MCE_SETTING_POWERKEY_ACTIONS_GESTURE13, MCE_SETTING_POWERKEY_ACTIONS_GESTURE14, MCE_SETTING_POWERKEY_ACTIONS_GESTURE15, MCE_SETTING_POWERKEY_ACTIONS_GESTURE16, MCE_SETTING_POWERKEY_ACTIONS_GESTURE17, MCE_SETTING_POWERKEY_ACTIONS_GESTURE18, MCE_SETTING_POWERKEY_ACTIONS_GESTURE19, }; /** Array of default values for configurable touchscreen gestures */ static const char * const pwrkey_actions_gesture_val[POWERKEY_ACTIONS_GESTURE_COUNT] = { MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE0, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE1, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE2, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE3, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE4, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE5, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE6, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE7, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE8, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE9, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE10, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE11, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE12, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE13, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE14, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE15, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE16, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE17, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE18, MCE_DEFAULT_POWERKEY_ACTIONS_GESTURE19, }; /** Array of current values for configurable touchscreen gestures */ static gchar *pwrkey_actions_gesture [POWERKEY_ACTIONS_GESTURE_COUNT] = {}; static guint pwrkey_actions_gesture_setting_id[POWERKEY_ACTIONS_GESTURE_COUNT] = {}; static void pwrkey_actions_parse (pwrkey_actions_t *self, const char *names_single, const char *names_double, const char *names_long); static void pwrkey_actions_do_gesture (size_t gesture); static bool pwrkey_actions_do_special (void); static void pwrkey_actions_do_common (void); static void pwrkey_actions_do_single_press (void); static void pwrkey_actions_do_double_press (void); static void pwrkey_actions_do_long_press (void); static bool pwrkey_actions_update (const pwrkey_actions_t *self, gchar **names_single, gchar **names_double, gchar **names_long); static bool pwrkey_actions_use_double_press(void); static void pwrkey_actions_select (bool display_is_on); /* ------------------------------------------------------------------------- * * LONG_PRESS_TIMEOUT * * timer for telling apart short and long power key presses * ------------------------------------------------------------------------- */ static gint pwrkey_long_press_delay = MCE_DEFAULT_POWERKEY_LONG_PRESS_DELAY; static guint pwrkey_long_press_delay_setting_id = 0; static guint pwrkey_long_press_timer_id = 0; static gboolean pwrkey_long_press_timer_cb (gpointer aptr); static void pwrkey_long_press_timer_start (void); static bool pwrkey_long_press_timer_pending (void); static bool pwrkey_long_press_timer_cancel (void); /* ------------------------------------------------------------------------- * * DOUBLE_PRESS_TIMEOUT * * timer for telling apart single and double power key presses * ------------------------------------------------------------------------- */ static gint pwrkey_double_press_delay = MCE_DEFAULT_POWERKEY_DOUBLE_PRESS_DELAY; static guint pwrkey_double_press_delay_setting_id = 0; static guint pwrkey_double_press_timer_id = 0; static gboolean pwrkey_double_press_timer_cb(gpointer aptr); static bool pwrkey_double_press_timer_pending(void); static bool pwrkey_double_press_timer_cancel(void); static void pwrkey_double_press_timer_start(void); /* ------------------------------------------------------------------------- * * NGFD_GLUE * ------------------------------------------------------------------------- */ static const char *xngf_state_repr (NgfEventState state); static void xngf_status_cb (NgfClient *client, uint32_t event_id, NgfEventState state, void *userdata); static bool xngf_create_client (void); static void xngf_delete_client (void); static void xngf_play_event (const char *event_name); static void xngf_init (void); static void xngf_quit (void); /* ------------------------------------------------------------------------- * * DBUS_ACTIONS * * emitting dbus signal from mce / making dbus method call to some service * ------------------------------------------------------------------------- */ /** Flag file for: Possibly dangerous dbus action in progress * * Used for resetting dbus action config if it causes mce to crash. * * Using tmpfs is problematic from permissions point of view, but we do * not want to cause flash wear by this either. */ static const char pwrkey_dbus_action_flag[] = "/tmp/mce-powerkey-dbus-action.flag"; typedef struct { const char *setting_key; const char *setting_def; gchar *setting_val; guint setting_id; char *destination; char *object; char *interface; char *member; char *argument; } pwrkey_dbus_action_t; static pwrkey_dbus_action_t pwrkey_dbus_action[POWEKEY_DBUS_ACTION_COUNT] = { { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION1, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION1, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION2, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION2, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION3, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION3, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION4, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION4, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION5, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION5, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION6, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION6, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION7, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION7, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION8, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION8, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION9, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION9, .setting_val = 0, .setting_id = 0, }, { .setting_key = MCE_SETTING_POWERKEY_DBUS_ACTION10, .setting_def = MCE_DEFAULT_POWERKEY_DBUS_ACTION10, .setting_val = 0, .setting_id = 0, }, }; static void pwrkey_dbus_action_clear(pwrkey_dbus_action_t *self); static void pwrkey_dbus_action_reset(pwrkey_dbus_action_t *self); static bool pwrkey_dbus_action_is_methodcall(const pwrkey_dbus_action_t *self); static bool pwrkey_dbus_action_is_signal(const pwrkey_dbus_action_t *self); static void pwrkey_dbus_action_parse(pwrkey_dbus_action_t *self); static gchar *pwrkey_dbus_action_to_string(const pwrkey_dbus_action_t *self); static void pwrkey_dbus_action_sanitize(pwrkey_dbus_action_t *self); static void pwrkey_dbus_action_configure(size_t action_id, bool force_reset); static void pwrkey_dbus_action_execute(size_t index); /* ------------------------------------------------------------------------- * * POWER_KEY_STATE_MACHINE * * main logic for tracking power key presses and associated timers * * state transition graph can be generated from "powerkey.dot" file * ------------------------------------------------------------------------- */ /** Diplay state when power key was pressed */ static display_state_t pwrkey_stm_display_state = MCE_DISPLAY_UNDEF; static alarm_ui_state_t pwrkey_stm_alarm_ui_state = MCE_ALARM_UI_OFF_INT32; static call_state_t pwrkey_stm_call_state = CALL_STATE_NONE; static bool pwrkey_stm_call_silenced = false; static bool pwrkey_stm_alarm_silenced = false; /** [setting] Power key press enable mode */ static gint pwrkey_stm_enable_mode = MCE_DEFAULT_POWERKEY_MODE; static guint pwrkey_stm_enable_mode_setting_id = 0; static void pwrkey_stm_long_press_timeout (void); static void pwrkey_stm_double_press_timeout (void); static void pwrkey_stm_powerkey_pressed (void); static void pwrkey_stm_powerkey_released (void); static bool pwrkey_stm_pending_timers (void); static void pwrkey_stm_rethink_wakelock (void); static void pwrkey_stm_store_initial_state (void); static void pwrkey_stm_terminate (void); /* ------------------------------------------------------------------------- * * HOME_KEY_STATE_MACHINE * ------------------------------------------------------------------------- */ typedef enum { HOMEKEY_STM_WAIT_PRESS = 0, HOMEKEY_STM_WAIT_UNBLANK = 1, HOMEKEY_STM_SEND_SIGNAL = 2, HOMEKEY_STM_WAIT_RELEASE = 3, } homekey_stm_t; static const char *homekey_stm_repr (homekey_stm_t state); static void homekey_stm_set_state (homekey_stm_t state); static bool homekey_stm_exec_step (void); static void homekey_stm_eval_state (void); static void homekey_stm_set_pressed (bool pressed); /* ------------------------------------------------------------------------- * * DBUS_IPC * * handling incoming and outgoing dbus messages * ------------------------------------------------------------------------- */ static void pwrkey_dbus_send_signal(const char *sig, const char *arg); static gboolean pwrkey_dbus_trigger_event_cb(DBusMessage *const req); static gboolean pwrkey_dbus_ignore_incoming_call_cb(DBusMessage *const req); static void pwrkey_dbus_init(void); static void pwrkey_dbus_quit(void); /* ------------------------------------------------------------------------- * * DYNAMIC_SETTINGS * * tracking powerkey related runtime changeable settings * ------------------------------------------------------------------------- */ static gint pwrkey_setting_sanitize_id = 0; static void pwrkey_setting_sanitize_action_masks(void); static void pwrkey_setting_sanitize_dbus_actions(void); static gboolean pwrkey_setting_sanitize_cb (gpointer aptr); static void pwrkey_setting_sanitize_now (void); static void pwrkey_setting_sanitize_later (void); static void pwrkey_setting_sanitize_cancel (void); static bool pwrkey_setting_handle_gesture (const GConfValue *gcv, guint id); static void pwrkey_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); static void pwrkey_setting_init (void); static void pwrkey_setting_quit (void); /* ------------------------------------------------------------------------- * * DATAPIPE_HANDLING * * reacting to state changes / input from other mce modules * ------------------------------------------------------------------------- */ static void pwrkey_datapipe_keypress_event_cb(gconstpointer const data); static void pwrkey_datapipe_ngfd_service_state_cb(gconstpointer data); static void pwrkey_datapipe_system_state_cb(gconstpointer data); static void pwrkey_datapipe_devicelock_state_cb(gconstpointer data); static void pwrkey_datapipe_display_state_curr_cb(gconstpointer data); static void pwrkey_datapipe_display_state_next_cb(gconstpointer data); static void pwrkey_datapipe_lid_sensor_filtered_cb(gconstpointer data); static void pwrkey_datapipe_proximity_sensor_actual_cb(gconstpointer data); static void pwrkey_datapipe_call_state_cb(gconstpointer data); static void pwrkey_datapipe_alarm_ui_state_cb(gconstpointer data); static void pwrkey_datapipe_devicelock_service_state_cb(gconstpointer data); static void pwrkey_datapipe_enroll_in_progress_cb(gconstpointer data); static void pwrkey_datapipe_ngfd_event_request_cb(gconstpointer data); static void pwrkey_datapipe_init(void); static void pwrkey_datapipe_quit(void); /* ------------------------------------------------------------------------- * * PWRKEY_QUEUED * ------------------------------------------------------------------------- */ typedef struct pwrkey_queued_t { pwrkey_unblank_predicate_t predicate; uint32_t actions; } pwrkey_queued_t; static pwrkey_queued_t *pwrkey_queued_create (pwrkey_unblank_predicate_t predicate, uint32_t actions); static void pwrkey_queued_delete (pwrkey_queued_t *self); static void pwrkey_queued_delete_cb (gpointer self); /* ------------------------------------------------------------------------- * * PWRKEY_QUEUE * ------------------------------------------------------------------------- */ static void pwrkey_queue_init (void); static void pwrkey_queue_quit (void); static void pwrkey_queue_add_actions (pwrkey_unblank_predicate_t predicate, uint32_t mask); static void pwrkey_queue_execute (void); static pwrkey_queued_t *pwrkey_queue_current_action (void); static void pwrkey_queue_finish_action (void); static bool pwrkey_queue_blank_delay_needed (void); static void pwrkey_queue_start_blank_delay (guint delay); static void pwrkey_queue_stop_blank_delay (void); static gboolean pwrkey_queue_blank_delay_cb (gpointer aptr); static void pwrkey_queue_start_action_delay (guint delay); static void pwrkey_queue_stop_action_delay (void); static gboolean pwrkey_queue_action_delay_cb (gpointer aptr); static bool pwrkey_queue_unblank_delay_needed(void); static void pwrkey_queue_start_unblank_delay (guint delay); static void pwrkey_queue_stop_unblank_delay (void); static gboolean pwrkey_queue_unblank_delay_cb (gpointer aptr); static void pwrkey_queue_start_delay (uint32_t actions); static void pwrkey_queue_stop_all_delays (void); static bool pwrkey_queue_is_delayed (void); static bool pwrkey_queue_is_busy (void); static void pwrkey_queue_rethink_delays (void); static void pwrkey_queue_rethink_psensor (void); /* ------------------------------------------------------------------------- * * MODULE_INTEFACE * ------------------------------------------------------------------------- */ gboolean mce_powerkey_init(void); void mce_powerkey_exit(void); /* ========================================================================= * * MISC_UTIL * ========================================================================= */ /** Parse element from comma separated string list */ static char * pwrkey_get_token(char **ppos) { char *pos = *ppos; char *beg = pos; if( !pos ) goto cleanup; for( ; *pos; ++pos ) { if( *pos != ',' ) continue; *pos++ = 0; break; } cleanup: return *ppos = pos, beg; } /** Create an empty flag file * * @param path Path to the file to create * * @return true if file was created, false otherwise */ static bool pwrkey_create_flagfile(const char *path) { bool created = false; int fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0666); if( fd != -1 ) { close(fd); created = true; } return created; } /** Delete a flag file * * @param path Path to the file to create * * @return true if file was removed, false otherwise */ static bool pwrkey_delete_flagfile(const char *path) { bool deleted = false; if( unlink(path) == 0 ) { deleted = true; } return deleted; } /** Get event time in millisecond accuracy * * @param ev input event pointer * * @return event time in millisecond accuracy */ static int64_t pwrkey_event_tick(const struct input_event *ev) { int64_t ms = 0; if( ev ) { ms += ev->input_event_sec; ms *= 1000; ms += ev->input_event_usec / 1000; } return ms; } /* ========================================================================= * * DATAPIPE_HANDLING * ========================================================================= */ /** System state; is undefined at bootup, can't assume anything */ static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF; /** Cached devicelock_state ; assume unknown */ static devicelock_state_t devicelock_state = DEVICELOCK_STATE_UNDEFINED; /** Current display state; undefined initially, can't assume anything */ static display_state_t display_state_curr = MCE_DISPLAY_UNDEF; /** Next Display state; undefined initially, can't assume anything */ static display_state_t display_state_next = MCE_DISPLAY_UNDEF; /** Lid cover policy state; assume unknown */ static cover_state_t lid_sensor_filtered = COVER_UNDEF; /** Actual proximity state; assume not covered */ static cover_state_t proximity_sensor_actual = COVER_UNDEF; /** NGFD availability */ static service_state_t ngfd_service_state = SERVICE_STATE_UNDEF; /** Cached alarm ui state */ static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_OFF_INT32; /** Cached call state */ static call_state_t call_state = CALL_STATE_NONE; /** devicelock dbus name is reserved; assume unknown */ static service_state_t devicelock_service_state = SERVICE_STATE_UNDEF; /** Cached ongoing fingerprint enroll; assume not */ static bool enroll_in_progress = false; /** Use powerkey for blanking during incoming calls */ static bool pwrkey_ignore_incoming_call = false; /* ========================================================================= * * PS_OVERRIDE * ========================================================================= */ /** Provide an emergency way out from stuck proximity sensor * * If the proximity sensor is dirty/faulty and stuck to "covered" * state, it can leave the phone in a state where it is impossible * to do anything about incoming call, ringing alarm. * * To offer somekind of remedy for the situation, this function * allows user to force proximity sensor to "uncovered" state * by rapidly pressing power button several times. */ static void pwrkey_ps_override_evaluate(void) { static int64_t t_last = 0; static gint count = 0; /* If the feature is disabled, just reset the counter */ if( pwrkey_ps_override_count <= 0 || pwrkey_ps_override_timeout <= 0 ) { t_last = 0, count = 0; goto EXIT; } /* If neither sensor is not covered, just reset the counter */ if( proximity_sensor_actual == COVER_OPEN && lid_sensor_filtered != COVER_CLOSED ) { t_last = 0, count = 0; goto EXIT; } int64_t t_now = mce_lib_get_boot_tick(); /* If the previous power key press was too far in * the past, start counting from zero again */ if( t_now > t_last + pwrkey_ps_override_timeout ) { mce_log(LL_DEBUG, "ps override count restarted"); count = 0; } t_last = t_now; /* If configured number of power key presses within the time * limits has been reached, force proximity sensor state to * "uncovered". * * This should allow touch input ungrabbing and turning * display on during incoming call / alarm. * * If sensor gets unstuck and new proximity readings are * received, this override will be automatically undone. */ if( ++count != pwrkey_ps_override_count ) { mce_log(LL_DEBUG, "ps override count = %d", count); goto EXIT; } if( proximity_sensor_actual == COVER_CLOSED ) { mce_log(LL_CRIT, "assuming stuck proximity sensor;" " faking uncover event"); /* Force cached proximity state to "open" */ datapipe_exec_full(&proximity_sensor_actual_pipe, GINT_TO_POINTER(COVER_OPEN)); } if( lid_sensor_filtered == COVER_CLOSED ) { mce_log(LL_CRIT, "assuming stuck lid sensor;" " resetting validation data"); /* Reset lid sensor validation data */ datapipe_exec_full(&lid_sensor_is_working_pipe, GINT_TO_POINTER(false)); } t_last = 0, count = 0; EXIT: return; } /* ========================================================================= * * ACTION_EXEC * ========================================================================= */ static void pwrkey_action_vibrate(void) { mce_log(LL_DEBUG, "Requesting vibrate"); xngf_play_event("pwrkey"); } static void pwrkey_action_shutdown(void) { submode_t submode = mce_get_submode_int32(); /* Do not shutdown if the tklock is active */ if( submode & MCE_SUBMODE_TKLOCK ) goto EXIT; mce_log(LL_DEVEL, "Requesting shutdown"); mce_dsme_request_normal_shutdown(); EXIT: return; } static void pwrkey_action_tklock(void) { tklock_request_t request = TKLOCK_REQUEST_ON; mce_datapipe_request_tklock(request); } static void pwrkey_action_tkunlock(void) { /* Only unlock if we are in/entering fully powered on display state */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: break; default: goto EXIT; } tklock_request_t request = TKLOCK_REQUEST_OFF; /* Even if powerkey actions are allowed to work while proximity * sensor is covered, we must not deactivatie the lockscreen */ if( proximity_sensor_actual != COVER_OPEN ) { mce_log(LL_DEBUG, "Proximity sensor %s; rejecting tklock=%s", proximity_state_repr(proximity_sensor_actual), tklock_request_repr(request)); goto EXIT; } mce_datapipe_request_tklock(request); EXIT: return; } static void pwrkey_action_tkunlock2(void) { if( devicelock_state != DEVICELOCK_STATE_UNLOCKED ) { mce_log(LL_DEBUG, "devicelock_state=%s; rejecting 'tkunlock2' action", devicelock_state_repr(devicelock_state)); } else { pwrkey_action_tkunlock(); } } static void pwrkey_action_blank(void) { display_state_t request = MCE_DISPLAY_OFF; switch( pwrkey_action_blank_mode ) { case PWRKEY_BLANK_TO_LPM: request = MCE_DISPLAY_LPM_ON; break; case PWRKEY_BLANK_TO_OFF: default: break; } /* Note: logged from datapipe functions */ mce_datapipe_request_display_state(request); } static void pwrkey_action_unblank(void) { display_state_t request = MCE_DISPLAY_ON; mce_log(LL_DEBUG, "Requesting display=%s", display_state_repr(request)); mce_tklock_unblank(request); } static void pwrkey_action_unblanked(void) { /* Placeholder dummy action. * * Used for preserving unblank predicate when * action set including unblank is executed * in multiple parts. */ } static void pwrkey_action_devlock(void) { static const char service[] = DEVICELOCK_SERVICE; static const char object[] = DEVICELOCK_REQUEST_PATH; static const char interface[] = DEVICELOCK_REQUEST_IF; static const char method[] = "setState"; dbus_int32_t request = DEVICELOCK_STATE_LOCKED; if( devicelock_service_state != SERVICE_STATE_RUNNING ) { mce_log(LL_WARN, "devicelock service state is %s; skip %s request", service_state_repr(devicelock_service_state), devicelock_state_repr(request)); goto EXIT; } mce_log(LL_DEBUG, "Requesting devicelock=%s", devicelock_state_repr(request)); dbus_send(service, object, interface, method, 0, DBUS_TYPE_INT32, &request, DBUS_TYPE_INVALID); EXIT: return; } static void pwrkey_action_dbus1(void) { pwrkey_dbus_action_execute(0); } static void pwrkey_action_dbus2(void) { pwrkey_dbus_action_execute(1); } static void pwrkey_action_dbus3(void) { pwrkey_dbus_action_execute(2); } static void pwrkey_action_dbus4(void) { pwrkey_dbus_action_execute(3); } static void pwrkey_action_dbus5(void) { pwrkey_dbus_action_execute(4); } static void pwrkey_action_dbus6(void) { pwrkey_dbus_action_execute(5); } static void pwrkey_action_dbus7(void) { pwrkey_dbus_action_execute(6); } static void pwrkey_action_dbus8(void) { pwrkey_dbus_action_execute(7); } static void pwrkey_action_dbus9(void) { pwrkey_dbus_action_execute(8); } static void pwrkey_action_dbus10(void) { pwrkey_dbus_action_execute(9); } static void pwrkey_action_nop(void) { /* Do nothing */ } /* ========================================================================= * * ACTION_SETS * ========================================================================= */ /** Config string to callback function mapping * * The configured actions are executed in order defined by this array. * * This is needed for determining actions that common to both single and * double press handling. */ static const pwrkey_bitconf_t pwrkey_action_lut[] = { // Direction: ON->OFF { .name = "blank", .func = pwrkey_action_blank, }, { .name = "tklock", .func = pwrkey_action_tklock, }, { .name = "devlock", .func = pwrkey_action_devlock, }, { .name = "shutdown", .func = pwrkey_action_shutdown, }, { .name = "vibrate", .func = pwrkey_action_vibrate, }, // Direction: OFF->ON { .name = "unblank", .func = pwrkey_action_unblank, }, { .name = "tkunlock2", .func = pwrkey_action_tkunlock2, }, { .name = "tkunlock", .func = pwrkey_action_tkunlock, }, { .name = "unblanked", .func = pwrkey_action_unblanked, }, // D-Bus actions { .name = "dbus1", .func = pwrkey_action_dbus1, }, { .name = "dbus2", .func = pwrkey_action_dbus2, }, { .name = "dbus3", .func = pwrkey_action_dbus3, }, { .name = "dbus4", .func = pwrkey_action_dbus4, }, { .name = "dbus5", .func = pwrkey_action_dbus5, }, { .name = "dbus6", .func = pwrkey_action_dbus6, }, { .name = "dbus7", .func = pwrkey_action_dbus7, }, { .name = "dbus8", .func = pwrkey_action_dbus8, }, { .name = "dbus9", .func = pwrkey_action_dbus9, }, { .name = "dbus10", .func = pwrkey_action_dbus10, }, // Low priority placeholder/dummy action { .name = "nop", .func = pwrkey_action_nop, }, }; static uint32_t pwrkey_mask_from_name(const char *name) { uint32_t mask = 0; for( size_t i = 0; i < G_N_ELEMENTS(pwrkey_action_lut); ++i ) { if( strcmp(pwrkey_action_lut[i].name, name) ) continue; mask |= 1u << i; break; } return mask; } static uint32_t pwrkey_mask_from_names(const char *names) { uint32_t mask = 0; char *work = 0; char *pos; char *end; if( !names ) goto EXIT; if( !(work = strdup(names)) ) goto EXIT; for( pos = work; pos; pos = end ) { if( (end = strchr(pos, ',')) ) *end++ = 0; mask |= pwrkey_mask_from_name(pos); } EXIT: free(work); return mask; } static gchar * pwrkey_mask_to_names(uint32_t mask) { char tmp[256]; char *pos = tmp; char *end = tmp + sizeof tmp - 1; auto void add(const char *str) { while( pos < end && *str ) *pos++ = *str++; }; for( size_t i = 0; i < G_N_ELEMENTS(pwrkey_action_lut); ++i ) { if( mask & (1u << i) ) { if( pos > tmp ) add(","); add(pwrkey_action_lut[i].name); } } *pos = 0; return g_strdup(tmp); } /* ------------------------------------------------------------------------- * * PWRKEY_PREDICATE * ------------------------------------------------------------------------- */ /** Predicate for: touchscreen gesture actions are allowed */ static bool pwrkey_predicate_gesture(bool synthesized, bool *need_psensor) { bool allowed = false; bool psensor = false; /* Only in USER or ACTDEAD */ switch( system_state ) { case MCE_SYSTEM_STATE_USER: case MCE_SYSTEM_STATE_ACTDEAD: break; default: mce_log(LL_DEVEL, "[gesture] ignored due to system state"); goto EXIT; } /* Note: In case we happen to be in middle of display state transition * the double tap blocking must use the next stable display state * rather than the current - potentially transitional - state. */ switch( display_state_next ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_LPM_ON: break; default: case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_UNDEF: mce_log(LL_DEVEL, "[gesture] ignored due to display state"); goto EXIT; } /* Check enable setting */ switch( pwrkey_gestures_enable_mode ) { case DBLTAP_ENABLE_ALWAYS: break; case DBLTAP_ENABLE_NEVER: if( !synthesized ) { mce_log(LL_DEVEL, "[gesture] ignored due to setting=never"); goto EXIT; } /* Synthesized events (e.g. double tap from lpm) are implicitly * subjected to proximity rules. * * Fall through */ default: case DBLTAP_ENABLE_NO_PROXIMITY: if( lid_sensor_filtered == COVER_CLOSED ) { mce_log(LL_DEVEL, "[gesture] ignored due to lid=closed"); goto EXIT; } psensor = true; if( proximity_sensor_actual == COVER_CLOSED ) { mce_log(LL_DEVEL, "[gesture] ignored due to proximity"); goto EXIT; } break; } allowed = true; EXIT: if( need_psensor ) *need_psensor = psensor; return allowed; } /** Predicate for: fpwakeup actions are allowed */ static bool pwrkey_predicate_fpwakeup(bool *need_psensor) { bool allowed = false; bool psensor = false; /* Only in USER state */ if( system_state != MCE_SYSTEM_STATE_USER ) { mce_log(LL_DEVEL, "[fpwakeup] ignored due to system_state=%s", system_state_repr(system_state)); goto EXIT; } /* To have something sensible to do with fpwakeup * - display must be off, or * - display is on and lockscreen active */ submode_t submode = mce_get_submode_int32(); switch( display_state_next ) { case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_OFF: break; case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: if( !(submode & MCE_SUBMODE_TKLOCK) ) { mce_log(LL_DEVEL, "[fpwakeup] ignored due to tklock=false"); goto EXIT; } break; default: mce_log(LL_DEVEL, "[fpwakeup] ignored due to display_state=%s", display_state_repr(display_state_next)); goto EXIT; } /* Not while lid is closed or proximity sensor covered */ if( lid_sensor_filtered == COVER_CLOSED ) { mce_log(LL_DEVEL, "[gesture] ignored due to lid=%s", cover_state_repr(lid_sensor_filtered)); goto EXIT; } psensor = true; if( proximity_sensor_actual == COVER_CLOSED ) { mce_log(LL_DEVEL, "[gesture] ignored due to proximity=%s", proximity_state_repr(proximity_sensor_actual)); goto EXIT; } allowed = true; EXIT: if( need_psensor ) *need_psensor = psensor; return allowed; } /** Predicate for: powerkey actions are allowed */ static bool pwrkey_predicate_powerkey(bool *need_psensor) { /* Assume that power key action should not be ignored */ bool allowed = false; bool psensor = false; /* If user is enrolling a fingerprint, do not blank with powerkey */ if( enroll_in_progress ) { /* We only want to block actions that would blank / lock the * device i.e. what would happen from display on/dimmed state. */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: mce_log(LL_DEVEL, "[powerkey] ignored due to fingerprint enroll"); goto EXIT; default: // dontcare break; } } /* Special case: Even if incoming call is beeing ignored, do not * allow unblanking via power key while proximity sensor is covered * (regardless of user settings). * * Silencing ringing via pressing the power key through fabric * of a pocket easily leads to several power key presses getting * emitted and we do not want the display to get activated by such * activity. */ if( pwrkey_stm_call_state == CALL_STATE_RINGING ) { psensor = true; if( !pwrkey_ignore_incoming_call ) { mce_log(LL_DEVEL, "[powerkey] ignored due incoming call"); goto EXIT; } if( proximity_sensor_actual != COVER_OPEN ) { mce_log(LL_DEVEL, "[powerkey] ignored due to incall proximity"); goto EXIT; } } /* Proximity sensor state vs power key press handling mode */ switch( pwrkey_stm_enable_mode ) { case PWRKEY_ENABLE_NEVER: mce_log(LL_DEVEL, "[powerkey] ignored due to setting=never"); goto EXIT; case PWRKEY_ENABLE_ALWAYS: break; case PWRKEY_ENABLE_NO_PROXIMITY2: /* do not ignore if display is on */ if( pwrkey_stm_display_state == MCE_DISPLAY_ON || pwrkey_stm_display_state == MCE_DISPLAY_DIM || pwrkey_stm_display_state == MCE_DISPLAY_LPM_ON ) { break; } /* fall through */ default: case PWRKEY_ENABLE_NO_PROXIMITY: if( lid_sensor_filtered == COVER_CLOSED ) { mce_log(LL_DEVEL, "[powerkey] ignored due to lid"); goto EXIT; } psensor = true; if( proximity_sensor_actual == COVER_CLOSED ) { mce_log(LL_DEVEL, "[powerkey] ignored due to proximity"); goto EXIT; } break; } allowed = true; EXIT: if( need_psensor ) *need_psensor = psensor; return allowed; } static bool pwrkey_predicate(pwrkey_unblank_predicate_t predicate, bool *need_psensor) { bool allowed = false; bool psensor = false; switch( predicate ) { case PWRKEY_UNBLANK_PREDICATE_POWERKEY: allowed = pwrkey_predicate_powerkey(&psensor); break; case PWRKEY_UNBLANK_PREDICATE_FPWAKEUP: allowed = pwrkey_predicate_fpwakeup(&psensor); break; case PWRKEY_UNBLANK_PREDICATE_GESTURE_REAL: allowed = pwrkey_predicate_gesture(false, &psensor); break; case PWRKEY_UNBLANK_PREDICATE_GESTURE_SYNTH: allowed = pwrkey_predicate_gesture(true, &psensor); break; case PWRKEY_UNBLANK_PREDICATE_DONTCARE: allowed = true; break; default: break; } if( need_psensor ) *need_psensor = psensor; return allowed; } /* ========================================================================= * * ACTION_TRIGGERING * ========================================================================= */ static void pwrkey_actions_do_gesture(size_t gesture) { /* Extract modifier bits */ bool synthetized = (gesture & GESTURE_SYNTHESIZED) != 0; gesture &= ~GESTURE_SYNTHESIZED; /* Treat unconfigurable gestures as doubletaps */ if( gesture >= POWERKEY_ACTIONS_GESTURE_COUNT ) gesture = GESTURE_DOUBLETAP; /* Check settings, proximity sensor state, etc */ pwrkey_unblank_predicate_t predicate = synthetized ? PWRKEY_UNBLANK_PREDICATE_GESTURE_SYNTH : PWRKEY_UNBLANK_PREDICATE_GESTURE_REAL; if( gesture == GESTURE_FPWAKEUP ) predicate = PWRKEY_UNBLANK_PREDICATE_FPWAKEUP; pwrkey_queue_add_actions(predicate, pwrkey_actions_from_gesture[gesture].mask_single); } static bool pwrkey_actions_do_special(void) { bool was_special = true; /* If alarm dialog is up, power key is used for snoozing */ switch( pwrkey_stm_alarm_ui_state ) { case MCE_ALARM_UI_VISIBLE_INT32: case MCE_ALARM_UI_RINGING_INT32: if( !pwrkey_stm_alarm_silenced ) { pwrkey_stm_alarm_silenced = true; mce_log(LL_DEVEL, "[powerkey] silencing alarm"); pwrkey_dbus_send_signal(MCE_ALARM_UI_FEEDBACK_SIG, MCE_FEEDBACK_EVENT_POWERKEY); } goto EXIT; default: // dontcare break; } /* During incoming call power key is used to silence ringing */ switch( pwrkey_stm_call_state ) { case CALL_STATE_RINGING: if( pwrkey_ignore_incoming_call ) { /* Call ui has signaled mce that the incoming call has * been ignored -> powerkey can be used for display * control even if there is incoming call. */ break; } if( !pwrkey_stm_call_silenced ) { pwrkey_stm_call_silenced = true; mce_log(LL_DEVEL, "[powerkey] silencing incoming call"); pwrkey_dbus_send_signal(MCE_CALL_UI_FEEDBACK_SIG, MCE_FEEDBACK_EVENT_POWERKEY); } goto EXIT; default: // dontcare break; } was_special = false; EXIT: return was_special; } static void pwrkey_actions_do_common(void) { if( !pwrkey_actions_do_special() ) pwrkey_queue_add_actions(PWRKEY_UNBLANK_PREDICATE_POWERKEY, pwrkey_actions_now->mask_common); } static void pwrkey_actions_do_single_press(void) { if( !pwrkey_actions_do_special() ) pwrkey_queue_add_actions(PWRKEY_UNBLANK_PREDICATE_POWERKEY, pwrkey_actions_now->mask_single); } static bool pwrkey_actions_use_double_press(void) { return pwrkey_actions_now->mask_double != 0; } static void pwrkey_actions_do_double_press(void) { if( !pwrkey_actions_do_special() ) pwrkey_queue_add_actions(PWRKEY_UNBLANK_PREDICATE_POWERKEY, pwrkey_actions_now->mask_double); } static void pwrkey_actions_do_long_press(void) { /* The action configuration applies only in the USER mode */ switch( system_state ) { case MCE_SYSTEM_STATE_SHUTDOWN: case MCE_SYSTEM_STATE_REBOOT: /* Ignore if we're already shutting down/rebooting */ break; case MCE_SYSTEM_STATE_ACTDEAD: /* Activate power on led pattern and power up to user mode*/ mce_log(LL_DEBUG, "activate MCE_LED_PATTERN_POWER_ON"); datapipe_exec_full(&led_pattern_activate_pipe, MCE_LED_PATTERN_POWER_ON); mce_dsme_request_powerup(); break; case MCE_SYSTEM_STATE_USER: /* Apply configured actions */ if( !pwrkey_actions_do_special() ) pwrkey_queue_add_actions(PWRKEY_UNBLANK_PREDICATE_POWERKEY, pwrkey_actions_now->mask_long); break; default: /* Default to powering off */ mce_log(LL_WARN, "Requesting shutdown from state: %s", system_state_repr(system_state)); mce_dsme_request_normal_shutdown(); break; } } static bool pwrkey_actions_update(const pwrkey_actions_t *self, gchar **names_single, gchar **names_double, gchar **names_long) { bool changed = false; uint32_t unblanked = pwrkey_mask_from_name("unblanked"); auto void update(gchar **prev, uint32_t mask) { /* "unblanked" is internal thing, sanitize settings */ gchar *curr = pwrkey_mask_to_names(mask & ~unblanked); if( prev && !eq(*prev, curr) ) changed = true, g_free(*prev), *prev = curr, curr = 0; g_free(curr); } update(names_single, self->mask_single | self->mask_common); update(names_double, self->mask_double | self->mask_common); update(names_long, self->mask_long); return changed; } static void pwrkey_actions_parse(pwrkey_actions_t *self, const char *names_single, const char *names_double, const char *names_long) { uint32_t unblanked = pwrkey_mask_from_name("unblanked"); /* Parse from configuration strings */ self->mask_common = 0; self->mask_single = pwrkey_mask_from_names(names_single); self->mask_double = pwrkey_mask_from_names(names_double); self->mask_long = pwrkey_mask_from_names(names_long); /* "unblanked" is internal thing, sanitize settings */ self->mask_single &= ~unblanked; self->mask_double &= ~unblanked; self->mask_long &= ~unblanked; /* Separate leading actions that are common to both * single and double press */ uint32_t diff = self->mask_single ^ self->mask_double; uint32_t mask = (diff - 1) & ~diff; uint32_t comm = self->mask_single & self->mask_double & mask; self->mask_common |= comm; self->mask_single &= ~comm; self->mask_double &= ~comm; /* Preserve "unblank" predicate via dummy "unblanked" */ if( self->mask_common & pwrkey_mask_from_name("unblank") ) { if( self->mask_single ) self->mask_single |= unblanked; if( self->mask_double ) self->mask_double |= unblanked; } } static void pwrkey_actions_select(bool display_is_on) { if( display_is_on ) pwrkey_actions_now = &pwrkey_actions_from_display_on; else pwrkey_actions_now = &pwrkey_actions_from_display_off; } /* ========================================================================= * * LONG_PRESS_TIMEOUT * ========================================================================= */ static gboolean pwrkey_long_press_timer_cb(gpointer aptr) { (void) aptr; if( !pwrkey_long_press_timer_id ) goto EXIT; pwrkey_long_press_timer_id = 0; pwrkey_stm_long_press_timeout(); pwrkey_stm_rethink_wakelock(); EXIT: return FALSE; } static bool pwrkey_long_press_timer_pending(void) { return pwrkey_long_press_timer_id != 0; } static bool pwrkey_long_press_timer_cancel(void) { bool canceled = false; if( pwrkey_long_press_timer_id ) { g_source_remove(pwrkey_long_press_timer_id), pwrkey_long_press_timer_id = 0; canceled = true; } return canceled; } static void pwrkey_long_press_timer_start(void) { pwrkey_long_press_timer_cancel(); pwrkey_long_press_timer_id = g_timeout_add(pwrkey_long_press_delay, pwrkey_long_press_timer_cb, 0); } /* ========================================================================= * * DOUBLE_PRESS_TIMEOUT * ========================================================================= */ static gboolean pwrkey_double_press_timer_cb(gpointer aptr) { (void) aptr; if( !pwrkey_double_press_timer_id ) goto EXIT; pwrkey_double_press_timer_id = 0; pwrkey_stm_double_press_timeout(); pwrkey_stm_rethink_wakelock(); EXIT: return FALSE; } static bool pwrkey_double_press_timer_pending(void) { return pwrkey_double_press_timer_id != 0; } static bool pwrkey_double_press_timer_cancel(void) { bool canceled = false; if( pwrkey_double_press_timer_id ) { g_source_remove(pwrkey_double_press_timer_id), pwrkey_double_press_timer_id = 0; canceled = true; } return canceled; } static void pwrkey_double_press_timer_start(void) { pwrkey_double_press_timer_cancel(); pwrkey_double_press_timer_id = g_timeout_add(pwrkey_double_press_delay, pwrkey_double_press_timer_cb, 0); } /* ========================================================================= * * DBUS_ACTIONS * ========================================================================= */ static void pwrkey_dbus_action_clear(pwrkey_dbus_action_t *self) { free(self->destination), self->destination = 0; free(self->object), self->object = 0; free(self->interface), self->interface = 0; free(self->member), self->member = 0; free(self->argument), self->argument = 0; } static void pwrkey_dbus_action_reset(pwrkey_dbus_action_t *self) { pwrkey_dbus_action_clear(self); /* Builtin default is always just a signal arg, no parsing required */ self->argument = strdup(self->setting_def); } static bool pwrkey_dbus_action_is_methodcall(const pwrkey_dbus_action_t *self) { bool valid = false; if( empty(self->destination) || empty(self->object) || empty(self->interface) || empty(self->member) ) { goto cleanup; } if( !dbus_validate_bus_name(self->destination, 0) || !dbus_validate_path(self->object, 0) || !dbus_validate_interface(self->interface, 0) || !dbus_validate_member(self->member, 0) ) { goto cleanup; } if( !empty(self->argument) && !dbus_validate_utf8(self->argument, 0) ) goto cleanup; valid = true; cleanup: return valid; } static bool pwrkey_dbus_action_is_signal(const pwrkey_dbus_action_t *self) { bool valid = false; // must have an argument if( empty(self->argument) ) goto cleanup; // ... and only the argument if( !empty(self->destination) || !empty(self->object) || !empty(self->interface) || !empty(self->member) ) { goto cleanup; } // which needs to be valid utf8 string if( !dbus_validate_utf8(self->argument, 0) ) goto cleanup; valid = true; cleanup: return valid; } static gchar * pwrkey_dbus_action_to_string(const pwrkey_dbus_action_t *self) { gchar *res = 0; if( pwrkey_dbus_action_is_signal(self) ) { res = g_strdup(self->argument); } else if( pwrkey_dbus_action_is_methodcall(self) ) { res = g_strdup_printf("%s,%s,%s,%s,%s", self->destination ?: "", self->object ?: "", self->interface ?: "", self->member ?: "", self->argument ?: ""); } return res; } static void pwrkey_dbus_action_parse(pwrkey_dbus_action_t *self) { char *tmp = 0; char *pos = 0; char *arg = 0; pwrkey_dbus_action_clear(self); if( empty(self->setting_val) ) goto cleanup; pos = tmp = strdup(self->setting_val); arg = pwrkey_get_token(&pos); if( *arg && !*pos ) { self->argument = strdup(arg); } else { self->destination = strdup(arg); self->object = strdup(pwrkey_get_token(&pos)); self->interface = strdup(pwrkey_get_token(&pos)); self->member = strdup(pwrkey_get_token(&pos)); self->argument = strdup(pwrkey_get_token(&pos)); } cleanup: free(tmp); } static void pwrkey_dbus_action_sanitize(pwrkey_dbus_action_t *self) { if( !pwrkey_dbus_action_is_methodcall(self) && !pwrkey_dbus_action_is_signal(self) ) { pwrkey_dbus_action_reset(self); } } static void pwrkey_dbus_action_configure(size_t action_id, bool force_reset) { gchar *use = 0; if( action_id >= POWEKEY_DBUS_ACTION_COUNT ) goto cleanup; pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; if( force_reset ) { pwrkey_dbus_action_reset(action); } else { pwrkey_dbus_action_parse(action); pwrkey_dbus_action_sanitize(action); } use = pwrkey_dbus_action_to_string(action); if( !eq(action->setting_val, use) ) { /* Change locally cached value */ g_free(action->setting_val), action->setting_val = use, use = 0; /* Flush change to settings */ mce_setting_set_string(action->setting_key, action->setting_val); } cleanup: g_free(use); } static void pwrkey_dbus_action_execute(size_t action_id) { bool flag_created = false; if( action_id >= POWEKEY_DBUS_ACTION_COUNT ) goto cleanup; mce_log(LL_DEBUG, "Executing dbus action %zd", action_id); const pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; /* We're potentially creating dbus messages using user specified * parameters. Since libdbus will abort the process rather than * returning some error code -> have a flag file around while * doing the hazardous ipc operations -> if abort occurs, the * flag file is left behind -> mce will reset dbus action config * back to default on restart */ if( !(flag_created = pwrkey_create_flagfile(pwrkey_dbus_action_flag)) ) { mce_log(LL_CRIT, "%s: could not create flagfile: %m", pwrkey_dbus_action_flag); goto cleanup; } if( pwrkey_dbus_action_is_signal(action) ) { pwrkey_dbus_send_signal(MCE_POWER_BUTTON_TRIGGER, action->argument); goto cleanup; } if( !pwrkey_dbus_action_is_methodcall(action) ) { mce_log(LL_WARN, "dbus%zd action does not have valid configuration", action_id + 1); goto cleanup; } if( empty(action->argument) ) { dbus_send(action->destination, action->object, action->interface, action->member, 0, DBUS_TYPE_INVALID); } else { dbus_send(action->destination, action->object, action->interface, action->member, 0, DBUS_TYPE_STRING, &action->argument, DBUS_TYPE_INVALID); } cleanup: if( flag_created && !pwrkey_delete_flagfile(pwrkey_dbus_action_flag) ) { mce_log(LL_CRIT, "%s: could not delete flagfile: %m", pwrkey_dbus_action_flag); } return; } /* ========================================================================= * * POWER_KEY_STATE_MACHINE * ========================================================================= */ /** Check if we need to hold a wakelock for power key handling * * Wakelock is held if there are pending timers. */ static void pwrkey_stm_rethink_wakelock(void) { #ifdef ENABLE_WAKELOCKS static bool have_lock = false; bool want_lock = (pwrkey_stm_pending_timers() | pwrkey_queue_is_busy()); if( have_lock == want_lock ) goto EXIT; if( (have_lock = want_lock) ) { wakelock_lock("mce_pwrkey_stm", -1); mce_log(LL_DEBUG, "acquire wakelock"); } else { mce_log(LL_DEBUG, "release wakelock"); wakelock_unlock("mce_pwrkey_stm"); } EXIT: return; #endif } static bool pwrkey_stm_pending_timers(void) { return (pwrkey_long_press_timer_pending() || pwrkey_double_press_timer_pending()); } static void pwrkey_stm_terminate(void) { /* Cancel timers */ pwrkey_double_press_timer_cancel(); pwrkey_long_press_timer_cancel(); /* Release wakelock */ pwrkey_stm_rethink_wakelock(); } static void pwrkey_stm_long_press_timeout(void) { // execute long press pwrkey_actions_do_long_press(); } static void pwrkey_stm_double_press_timeout(void) { // execute single press pwrkey_actions_do_single_press(); } static void pwrkey_stm_powerkey_pressed(void) { if( pwrkey_double_press_timer_cancel() ) { /* Pressed while we were waiting for double press */ pwrkey_actions_do_double_press(); } else if( !pwrkey_long_press_timer_pending() ) { /* Pressed while there are no timers active */ /* Store display state we started from */ pwrkey_stm_store_initial_state(); /* Start short vs long press detection timer */ pwrkey_long_press_timer_start(); } } static void pwrkey_stm_powerkey_released(void) { if( pwrkey_long_press_timer_cancel() ) { /* Released while we were waiting for long press */ /* Always do actions that are common to both short and * double press */ pwrkey_actions_do_common(); if( pwrkey_actions_use_double_press() ) { /* There is config for double press -> wait a while * to see if it is double press */ pwrkey_double_press_timer_start(); } else { /* There is no config for double press -> just do * actions for single press without further delays */ pwrkey_actions_do_single_press(); } } } static void pwrkey_stm_store_initial_state(void) { /* Cache display, call and alarm ui states */ pwrkey_stm_display_state = display_state_curr; pwrkey_stm_alarm_ui_state = alarm_ui_state; pwrkey_stm_call_state = call_state; pwrkey_stm_call_silenced = false; pwrkey_stm_alarm_silenced = false; mce_log(LL_DEBUG, "display=%s alarm=%s call=%s", display_state_repr(pwrkey_stm_display_state), alarm_state_repr(pwrkey_stm_alarm_ui_state), call_state_repr(pwrkey_stm_call_state)); /* MCE_DISPLAY_OFF requests must be queued only * from fully powered up display states. * Otherwise we create a situation where multiple * power key presses done while the display is off * or powering up will bounce back to display off * once initial the off->on transition finishes */ bool display_is_on = false; switch( pwrkey_stm_display_state ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: display_is_on = true; break; default: break; } pwrkey_actions_select(display_is_on); } /* ========================================================================= * * HOME_KEY_STATE_MACHINE * ========================================================================= */ /** Convert homekey_stm_t enum to human readable string * * @param state homekey_stm_t enumeration value * * @return human readable representation of state */ static const char * homekey_stm_repr(homekey_stm_t state) { const char *repr = "HOMEKEY_STM_UNKNOWN"; switch( state ) { case HOMEKEY_STM_WAIT_PRESS: repr = "HOMEKEY_STM_WAIT_PRESS"; break; case HOMEKEY_STM_WAIT_UNBLANK: repr = "HOMEKEY_STM_WAIT_UNBLANK"; break; case HOMEKEY_STM_SEND_SIGNAL: repr = "HOMEKEY_STM_SEND_SIGNAL"; break; case HOMEKEY_STM_WAIT_RELEASE: repr = "HOMEKEY_STM_WAIT_RELEASE"; break; default: break; } return repr; } /** Current state of home key handling state machine */ static homekey_stm_t homekey_stm_state = HOMEKEY_STM_WAIT_PRESS; /** Cached home key is pressed down state */ static bool homekey_stm_pressed = false; /** Set current state of home key handling state machine * * Perform any actions that are related to leaving current and/or * entering the new state. * * @param state homekey_stm_t enumeration value */ static void homekey_stm_set_state(homekey_stm_t state) { if( homekey_stm_state == state ) goto EXIT; mce_log(LL_DEBUG, "state: %s -> %s", homekey_stm_repr(homekey_stm_state), homekey_stm_repr(state)); /* Handle entering new state */ switch( (homekey_stm_state = state) ) { case HOMEKEY_STM_WAIT_PRESS: break; case HOMEKEY_STM_WAIT_UNBLANK: /* Check if policy allows display unblanking */ if( proximity_sensor_actual != COVER_OPEN ) { mce_log(LL_DEBUG, "Proximity sensor %s; skip unblank", proximity_state_repr(proximity_sensor_actual)); break; } /* Initiate display power up */ mce_log(LL_DEBUG, "request %s", display_state_repr(MCE_DISPLAY_ON)); mce_datapipe_request_display_state(MCE_DISPLAY_ON); break; case HOMEKEY_STM_SEND_SIGNAL: /* Inform compositor that it should perform home key actions */ pwrkey_dbus_send_signal(MCE_POWER_BUTTON_TRIGGER, "home-key"); break; case HOMEKEY_STM_WAIT_RELEASE: break; default: break; } EXIT: return; } /** Perform one home key handling state machine transition * * @return true if state transition took place, false otherwise */ static bool homekey_stm_exec_step(void) { homekey_stm_t prev = homekey_stm_state; switch( homekey_stm_state ) { default: case HOMEKEY_STM_WAIT_PRESS: if( homekey_stm_pressed ) homekey_stm_set_state(HOMEKEY_STM_WAIT_UNBLANK); break; case HOMEKEY_STM_WAIT_UNBLANK: if( display_state_next != MCE_DISPLAY_ON ) homekey_stm_set_state(HOMEKEY_STM_WAIT_RELEASE); else if( display_state_curr == MCE_DISPLAY_ON ) homekey_stm_set_state(HOMEKEY_STM_SEND_SIGNAL); break; case HOMEKEY_STM_SEND_SIGNAL: homekey_stm_set_state(HOMEKEY_STM_WAIT_RELEASE); break; case HOMEKEY_STM_WAIT_RELEASE: if( !homekey_stm_pressed ) homekey_stm_set_state(HOMEKEY_STM_WAIT_PRESS); break; } return homekey_stm_state != prev; } /** Update current state of home key handling state machine * * Repeatedly executes transitions until stable state is reached. */ static void homekey_stm_eval_state(void) { while( homekey_stm_exec_step() ) ; } /** Set home key pressed down state and update state machine */ static void homekey_stm_set_pressed(bool pressed) { if( homekey_stm_pressed == pressed ) goto EXIT; homekey_stm_pressed = pressed; homekey_stm_eval_state(); EXIT: return; } /* ========================================================================= * * DBUS_IPC * ========================================================================= */ /** Helper for sending powerkey feedback dbus signal * * @param sig name of the signal to send */ static void pwrkey_dbus_send_signal(const char *sig, const char *arg) { mce_log(LL_DEVEL, "sending dbus signal: %s %s", sig, arg); dbus_send(0, MCE_SIGNAL_PATH, MCE_SIGNAL_IF, sig, 0, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID); } /** * D-Bus callback for powerkey event triggering * * @param msg D-Bus message * * @return TRUE */ static gboolean pwrkey_dbus_trigger_event_cb(DBusMessage *const req) { DBusMessage *rsp = 0; dbus_uint32_t act = 0; DBusMessageIter iter; mce_log(LL_DEVEL, "[power] button trigger request from %s", mce_dbus_get_message_sender_ident(req)); if( !dbus_message_iter_init(req, &iter) ) goto EXIT; switch( dbus_message_iter_get_arg_type(&iter) ) { case DBUS_TYPE_BOOLEAN: { dbus_bool_t tmp = 0; dbus_message_iter_get_basic(&iter, &tmp); act = tmp ? 1 : 0; } break; case DBUS_TYPE_UINT32: dbus_message_iter_get_basic(&iter, &act); break; default: mce_log(LL_ERR, "Argument passed to %s.%s has incorrect type", MCE_REQUEST_IF, MCE_TRIGGER_POWERKEY_EVENT_REQ); goto EXIT; } if( act > 2 ) { mce_log(LL_ERR, "Incorrect powerkey event passed to %s.%s; " "ignoring request", MCE_REQUEST_IF, MCE_TRIGGER_POWERKEY_EVENT_REQ); goto EXIT; } mce_log(LL_DEBUG, "[power] button event trigger value: %d", act); /* Terminate state machine actions for real power key */ pwrkey_stm_terminate(); /* Choose actions based on display state */ pwrkey_stm_store_initial_state(); switch (act) { default: case MCE_POWERKEY_EVENT_SHORT_PRESS: /* short press */ pwrkey_actions_do_common(); pwrkey_actions_do_single_press(); break; case MCE_POWERKEY_EVENT_LONG_PRESS: /* long press */ pwrkey_actions_do_long_press(); break; case MCE_POWERKEY_EVENT_DOUBLE_PRESS: /* double press */ pwrkey_actions_do_common(); pwrkey_actions_do_double_press(); break; } EXIT: if( !dbus_message_get_no_reply(req) ) { /* Need to send reply, create a dummy one if we do * not have already existing error reply */ if( !rsp ) rsp = dbus_new_method_reply(req); dbus_send_message(rsp), rsp = 0; } if( rsp ) dbus_message_unref(rsp); pwrkey_stm_rethink_wakelock(); return TRUE; } /** D-Bus callback for ignoring incoming call * * @param req D-Bus method call message * * @return TRUE */ static gboolean pwrkey_dbus_ignore_incoming_call_cb(DBusMessage *const req) { mce_log(LL_DEVEL, "ignore incoming call from %s", mce_dbus_get_message_sender_ident(req)); if( call_state == CALL_STATE_RINGING ) { mce_log(LL_DEBUG, "start ignoring incoming calls"); /* Update powerkey module specific toggle */ pwrkey_ignore_incoming_call = true; /* Make also callstate plugin ignore incoming calls. This * should lead to call_state changing from RINGING to ACTIVE * or NONE depending on whether there are other calls or not. */ datapipe_exec_full(&ignore_incoming_call_event_pipe, GINT_TO_POINTER(true)); } if( !dbus_message_get_no_reply(req) ) { DBusMessage *rsp = dbus_new_method_reply(req); dbus_send_message(rsp), rsp = 0; } return TRUE; } /** Array of dbus message handlers */ static mce_dbus_handler_t pwrkey_dbus_handlers[] = { /* signals - outbound (for Introspect purposes only) */ { .interface = MCE_SIGNAL_IF, .name = MCE_ALARM_UI_FEEDBACK_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_CALL_UI_FEEDBACK_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_POWER_BUTTON_TRIGGER, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, /* method calls */ { .interface = MCE_REQUEST_IF, .name = MCE_TRIGGER_POWERKEY_EVENT_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = pwrkey_dbus_trigger_event_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_IGNORE_INCOMING_CALL_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = pwrkey_dbus_ignore_incoming_call_cb, .args = 0 }, /* sentinel */ { .interface = 0 } }; /** Add dbus handlers */ static void pwrkey_dbus_init(void) { mce_dbus_handler_register_array(pwrkey_dbus_handlers); } /** Remove dbus handlers */ static void pwrkey_dbus_quit(void) { mce_dbus_handler_unregister_array(pwrkey_dbus_handlers); } /* ========================================================================= * * DYNAMIC_SETTINGS * ========================================================================= */ static void pwrkey_setting_sanitize_action_masks(void) { /* parse settings -> bitmasks */ pwrkey_actions_parse(&pwrkey_actions_from_display_on, pwrkey_actions_single_on, pwrkey_actions_double_on, pwrkey_actions_long_on); pwrkey_actions_parse(&pwrkey_actions_from_display_off, pwrkey_actions_single_off, pwrkey_actions_double_off, pwrkey_actions_long_off); /* bitmasks -> setting strings */ bool on_changed = pwrkey_actions_update(&pwrkey_actions_from_display_on, &pwrkey_actions_single_on, &pwrkey_actions_double_on, &pwrkey_actions_long_on); bool off_changed = pwrkey_actions_update(&pwrkey_actions_from_display_off, &pwrkey_actions_single_off, &pwrkey_actions_double_off, &pwrkey_actions_long_off); /* send notifications if something changed */ if( on_changed ) { mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_SINGLE_ON, pwrkey_actions_single_on); mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_DOUBLE_ON, pwrkey_actions_double_on); mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_LONG_ON, pwrkey_actions_long_on); } if( off_changed ) { mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_SINGLE_OFF, pwrkey_actions_single_off); mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_DOUBLE_OFF, pwrkey_actions_double_off); mce_setting_set_string(MCE_SETTING_POWERKEY_ACTIONS_LONG_OFF, pwrkey_actions_long_off); } for( size_t i = 0; i < POWERKEY_ACTIONS_GESTURE_COUNT; ++i ) { pwrkey_actions_parse(&pwrkey_actions_from_gesture[i], pwrkey_actions_gesture[i], 0, 0); bool gesture_changed = pwrkey_actions_update(&pwrkey_actions_from_gesture[i], &pwrkey_actions_gesture[i], 0, 0); if( gesture_changed ) { mce_setting_set_string(pwrkey_actions_gesture_key[i], pwrkey_actions_gesture[i]); } } } static void pwrkey_setting_sanitize_dbus_actions(void) { /* The custom dbus action settings can cause mce to * get aborted by dbus_message_new_xxx(). * * Assume having the flag file present means that * mce is restarting after abort and reset the dbus * action config back to defaults to avoid repeating * the abort. */ bool force_reset = pwrkey_delete_flagfile(pwrkey_dbus_action_flag); if( force_reset ) { mce_log(LL_CRIT, "%s: flagfile was present; resetting" "dbus action config", pwrkey_dbus_action_flag); } for( size_t action_id = 0; action_id < POWEKEY_DBUS_ACTION_COUNT; ++action_id ) pwrkey_dbus_action_configure(action_id, force_reset); } static void pwrkey_setting_sanitize_now(void) { pwrkey_setting_sanitize_action_masks(); pwrkey_setting_sanitize_dbus_actions(); } static gboolean pwrkey_setting_sanitize_cb(gpointer aptr) { (void)aptr; if( !pwrkey_setting_sanitize_id ) goto EXIT; pwrkey_setting_sanitize_id = 0; pwrkey_setting_sanitize_now(); EXIT: return FALSE; } static void pwrkey_setting_sanitize_later(void) { if( !pwrkey_setting_sanitize_id ) pwrkey_setting_sanitize_id = g_idle_add(pwrkey_setting_sanitize_cb, 0); } static void pwrkey_setting_sanitize_cancel(void) { if( pwrkey_setting_sanitize_id ) { g_source_remove(pwrkey_setting_sanitize_id), pwrkey_setting_sanitize_id = 0; } } static bool pwrkey_setting_handle_gesture(const GConfValue *gcv, guint id) { bool handled = false; for( size_t i = 0; i < POWERKEY_ACTIONS_GESTURE_COUNT; ++i ) { if( pwrkey_actions_gesture_setting_id[i] != id ) continue; const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_gesture[i], val) ) { mce_log(LL_NOTICE, "pwrkey_actions_gesture[%zu]: '%s' -> '%s'", i, pwrkey_actions_gesture[i], val); g_free(pwrkey_actions_gesture[i]); pwrkey_actions_gesture[i] = g_strdup(val); pwrkey_setting_sanitize_later(); } handled = true; break; } return handled; } /** 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 pwrkey_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 == pwrkey_stm_enable_mode_setting_id ) { gint old = pwrkey_stm_enable_mode; pwrkey_stm_enable_mode = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_stm_enable_mode: %d -> %d", old, pwrkey_stm_enable_mode); } else if( id == pwrkey_action_blank_mode_setting_id ) { gint old = pwrkey_action_blank_mode; pwrkey_action_blank_mode = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_action_blank_mode: %d -> %d", old, pwrkey_action_blank_mode); } else if( id == pwrkey_ps_override_count_setting_id ) { gint old = pwrkey_ps_override_count; pwrkey_ps_override_count = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_ps_override_count: %d -> %d", old, pwrkey_ps_override_count); } else if( id == pwrkey_ps_override_timeout_setting_id ) { gint old = pwrkey_ps_override_timeout; pwrkey_ps_override_timeout = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_ps_override_timeout: %d -> %d", old, pwrkey_ps_override_timeout); } else if( id == pwrkey_long_press_delay_setting_id ) { gint old = pwrkey_long_press_delay; pwrkey_long_press_delay = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_long_press_delay: %d -> %d", old, pwrkey_long_press_delay); } else if( id == pwrkey_double_press_delay_setting_id ) { gint old = pwrkey_double_press_delay; pwrkey_double_press_delay = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_double_press_delay: %d -> %d", old, pwrkey_double_press_delay); } else if( id == pwrkey_actions_single_on_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_single_on, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_single_on: '%s' -> '%s'", pwrkey_actions_single_on, val); g_free(pwrkey_actions_single_on); pwrkey_actions_single_on = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_actions_double_on_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_double_on, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_double_on: '%s' -> '%s'", pwrkey_actions_double_on, val); g_free(pwrkey_actions_double_on); pwrkey_actions_double_on = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_actions_long_on_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_long_on, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_long_on: '%s' -> '%s'", pwrkey_actions_long_on, val); g_free(pwrkey_actions_long_on); pwrkey_actions_long_on = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_actions_single_off_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_single_off, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_single_off: '%s' -> '%s'", pwrkey_actions_single_off, val); g_free(pwrkey_actions_single_off); pwrkey_actions_single_off = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_actions_double_off_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_double_off, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_double_off: '%s' -> '%s'", pwrkey_actions_double_off, val); g_free(pwrkey_actions_double_off); pwrkey_actions_double_off = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_actions_long_off_setting_id ) { const char *val = gconf_value_get_string(gcv); if( !eq(pwrkey_actions_long_off, val) ) { mce_log(LL_NOTICE, "pwrkey_actions_long_off: '%s' -> '%s'", pwrkey_actions_long_off, val); g_free(pwrkey_actions_long_off); pwrkey_actions_long_off = g_strdup(val); pwrkey_setting_sanitize_later(); } } else if( id == pwrkey_gestures_enable_mode_cb_id ) { gint old = pwrkey_gestures_enable_mode; pwrkey_gestures_enable_mode = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "pwrkey_gestures_enable_mode: %d -> %d", old, pwrkey_gestures_enable_mode); } else if( pwrkey_setting_handle_gesture(gcv, id) ) { // nop } else { for( size_t action_id = 0; ; ++action_id ) { if( action_id >= POWEKEY_DBUS_ACTION_COUNT ) { mce_log(LL_WARN, "Spurious GConf value received; confused!"); goto EXIT; } pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; if( id != action->setting_id ) continue; const char *val = gconf_value_get_string(gcv); if( eq(action->setting_val, val) ) break; mce_log(LL_NOTICE, "pwrkey_dbus_action%zd_val: '%s' -> '%s'", action_id, action->setting_val, val); g_free(action->setting_val), action->setting_val = g_strdup(val); pwrkey_setting_sanitize_later(); break; } } EXIT: return; } /** Get setting values and start tracking changes */ static void pwrkey_setting_init(void) { /* Power key press handling mode */ mce_setting_track_int(MCE_SETTING_POWERKEY_MODE, &pwrkey_stm_enable_mode, MCE_DEFAULT_POWERKEY_MODE, pwrkey_setting_cb, &pwrkey_stm_enable_mode_setting_id); /* Power key display blanking mode */ mce_setting_track_int(MCE_SETTING_POWERKEY_BLANKING_MODE, &pwrkey_action_blank_mode, MCE_DEFAULT_POWERKEY_BLANKING_MODE, pwrkey_setting_cb, &pwrkey_action_blank_mode_setting_id); /* Power key press count for proximity sensor override */ mce_setting_track_int(MCE_SETTING_POWERKEY_PS_OVERRIDE_COUNT, &pwrkey_ps_override_count, MCE_DEFAULT_POWERKEY_PS_OVERRIDE_COUNT, pwrkey_setting_cb, &pwrkey_ps_override_count_setting_id); /* Maximum time between power key presses for ps override */ mce_setting_track_int(MCE_SETTING_POWERKEY_PS_OVERRIDE_TIMEOUT, &pwrkey_ps_override_timeout, MCE_DEFAULT_POWERKEY_PS_OVERRIDE_TIMEOUT, pwrkey_setting_cb, &pwrkey_ps_override_timeout_setting_id); /* Delay for waiting long press */ mce_setting_track_int(MCE_SETTING_POWERKEY_LONG_PRESS_DELAY, &pwrkey_long_press_delay, MCE_DEFAULT_POWERKEY_LONG_PRESS_DELAY, pwrkey_setting_cb, &pwrkey_long_press_delay_setting_id); /* Delay for waiting double press */ mce_setting_track_int(MCE_SETTING_POWERKEY_DOUBLE_PRESS_DELAY, &pwrkey_double_press_delay, MCE_DEFAULT_POWERKEY_DOUBLE_PRESS_DELAY, pwrkey_setting_cb, &pwrkey_double_press_delay_setting_id); /* Action sets */ mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_SINGLE_ON, &pwrkey_actions_single_on, MCE_DEFAULT_POWERKEY_ACTIONS_SINGLE_ON, pwrkey_setting_cb, &pwrkey_actions_single_on_setting_id); mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_DOUBLE_ON, &pwrkey_actions_double_on, MCE_DEFAULT_POWERKEY_ACTIONS_DOUBLE_ON, pwrkey_setting_cb, &pwrkey_actions_double_on_setting_id); mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_LONG_ON, &pwrkey_actions_long_on, MCE_DEFAULT_POWERKEY_ACTIONS_LONG_ON, pwrkey_setting_cb, &pwrkey_actions_long_on_setting_id); mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_SINGLE_OFF, &pwrkey_actions_single_off, MCE_DEFAULT_POWERKEY_ACTIONS_SINGLE_OFF, pwrkey_setting_cb, &pwrkey_actions_single_off_setting_id); mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_DOUBLE_OFF, &pwrkey_actions_double_off, MCE_DEFAULT_POWERKEY_ACTIONS_DOUBLE_OFF, pwrkey_setting_cb, &pwrkey_actions_double_off_setting_id); mce_setting_track_string(MCE_SETTING_POWERKEY_ACTIONS_LONG_OFF, &pwrkey_actions_long_off, MCE_DEFAULT_POWERKEY_ACTIONS_LONG_OFF, pwrkey_setting_cb, &pwrkey_actions_long_off_setting_id); mce_setting_track_int(MCE_SETTING_DOUBLETAP_MODE, &pwrkey_gestures_enable_mode, MCE_DEFAULT_DOUBLETAP_MODE, pwrkey_setting_cb, &pwrkey_gestures_enable_mode_cb_id); for( size_t i = 0; i < POWERKEY_ACTIONS_GESTURE_COUNT; ++i ) { mce_setting_track_string(pwrkey_actions_gesture_key[i], &pwrkey_actions_gesture[i], pwrkey_actions_gesture_val[i], pwrkey_setting_cb, &pwrkey_actions_gesture_setting_id[i]); } /* D-Bus actions */ for( size_t action_id = 0; action_id < POWEKEY_DBUS_ACTION_COUNT; ++action_id ) { pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; mce_setting_track_string(action->setting_key, &action->setting_val, action->setting_def, pwrkey_setting_cb, &action->setting_id); } /* Apply sanity checks */ pwrkey_setting_sanitize_now(); } /** Stop tracking setting changes */ static void pwrkey_setting_quit(void) { /* Power key press handling mode */ mce_setting_notifier_remove(pwrkey_stm_enable_mode_setting_id), pwrkey_stm_enable_mode_setting_id = 0; /* Power key press blanking mode */ mce_setting_notifier_remove(pwrkey_action_blank_mode_setting_id), pwrkey_action_blank_mode_setting_id = 0; /* Power key press blanking mode */ mce_setting_notifier_remove(pwrkey_ps_override_count_setting_id), pwrkey_ps_override_count_setting_id = 0; /* Power key press blanking mode */ mce_setting_notifier_remove(pwrkey_ps_override_timeout_setting_id), pwrkey_ps_override_timeout_setting_id = 0; /* Action sets */ mce_setting_notifier_remove(pwrkey_actions_single_on_setting_id), pwrkey_actions_single_on_setting_id = 0; mce_setting_notifier_remove(pwrkey_actions_double_on_setting_id), pwrkey_actions_double_on_setting_id = 0; mce_setting_notifier_remove(pwrkey_actions_long_on_setting_id), pwrkey_actions_long_on_setting_id = 0; mce_setting_notifier_remove(pwrkey_actions_single_off_setting_id), pwrkey_actions_single_off_setting_id = 0; mce_setting_notifier_remove(pwrkey_actions_double_off_setting_id), pwrkey_actions_double_off_setting_id = 0; mce_setting_notifier_remove(pwrkey_actions_long_off_setting_id), pwrkey_actions_long_off_setting_id = 0; mce_setting_notifier_remove(pwrkey_gestures_enable_mode_cb_id), pwrkey_gestures_enable_mode_cb_id = 0; for( size_t i = 0; i < POWERKEY_ACTIONS_GESTURE_COUNT; ++i ) { mce_setting_notifier_remove(pwrkey_actions_gesture_setting_id[i]), pwrkey_actions_gesture_setting_id[i] = 0; } g_free(pwrkey_actions_single_on), pwrkey_actions_single_on = 0; g_free(pwrkey_actions_double_on), pwrkey_actions_double_on = 0; g_free(pwrkey_actions_long_on), pwrkey_actions_long_on = 0; g_free(pwrkey_actions_single_off), pwrkey_actions_single_off = 0; g_free(pwrkey_actions_double_off), pwrkey_actions_double_off = 0; g_free(pwrkey_actions_long_off), pwrkey_actions_long_off = 0;; for( size_t i = 0; i < POWERKEY_ACTIONS_GESTURE_COUNT; ++i ) { g_free(pwrkey_actions_gesture[i]), pwrkey_actions_gesture[i] = 0;; } /* Cancel pending delayed setting sanitizing */ pwrkey_setting_sanitize_cancel(); /* D-Bus actions */ for( size_t action_id = 0; action_id < POWEKEY_DBUS_ACTION_COUNT; ++action_id ) { pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; mce_setting_notifier_remove(action->setting_id), action->setting_id = 0; g_free(action->setting_val), action->setting_val = 0;; } } /* ========================================================================= * * DATAPIPE_HANDLING * ========================================================================= */ /** Change notifications for system_state */ static void pwrkey_datapipe_system_state_cb(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)); EXIT: return; } /** Notification callback for devicelock_state_pipe * * @param data devicelock_state_t value as void pointer */ static void pwrkey_datapipe_devicelock_state_cb(gconstpointer data) { devicelock_state_t prev = devicelock_state; devicelock_state = GPOINTER_TO_INT(data); if( devicelock_state == prev ) goto EXIT; mce_log(LL_DEBUG, "devicelock_state = %s -> %s", devicelock_state_repr(prev), devicelock_state_repr(devicelock_state)); EXIT: return; } /** Handle display state change notifications * * @param data display state (as void pointer) */ static void pwrkey_datapipe_display_state_curr_cb(gconstpointer data) { display_state_t prev = display_state_curr; display_state_curr = GPOINTER_TO_INT(data); if( display_state_curr == prev ) goto EXIT; mce_log(LL_DEBUG, "display_state_curr = %s -> %s", display_state_repr(prev), display_state_repr(display_state_curr)); homekey_stm_eval_state(); pwrkey_queue_rethink_delays(); EXIT: return; } /** Pre-change notifications for display_state_curr */ static void pwrkey_datapipe_display_state_next_cb(gconstpointer data) { display_state_t prev = display_state_next; display_state_next = GPOINTER_TO_INT(data); if( prev == display_state_next ) goto EXIT; mce_log(LL_DEBUG, "display_state_next = %s -> %s", display_state_repr(prev), display_state_repr(display_state_next)); homekey_stm_eval_state(); EXIT: return; } /** Change notifications from lid_sensor_filtered_pipe */ static void pwrkey_datapipe_lid_sensor_filtered_cb(gconstpointer data) { cover_state_t prev = lid_sensor_filtered; lid_sensor_filtered = GPOINTER_TO_INT(data); if( lid_sensor_filtered == prev ) goto EXIT; mce_log(LL_DEBUG, "lid_sensor_filtered = %s -> %s", cover_state_repr(prev), cover_state_repr(lid_sensor_filtered)); EXIT: return; } /** Change notifications for proximity_sensor_actual */ static void pwrkey_datapipe_proximity_sensor_actual_cb(gconstpointer data) { cover_state_t prev = proximity_sensor_actual; proximity_sensor_actual = GPOINTER_TO_INT(data); if( proximity_sensor_actual == prev ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_actual = %s -> %s", proximity_state_repr(prev), proximity_state_repr(proximity_sensor_actual)); if( proximity_sensor_actual != COVER_UNDEF ) pwrkey_queue_execute(); EXIT: return; } /** Handle ngfd_service_state notifications * * @param data service availability (as void pointer) */ static void pwrkey_datapipe_ngfd_service_state_cb(gconstpointer data) { service_state_t prev = ngfd_service_state; ngfd_service_state = GPOINTER_TO_INT(data); if( ngfd_service_state == prev ) goto EXIT; mce_log(LL_NOTICE, "ngfd_service_state = %s -> %s", service_state_repr(prev), service_state_repr(ngfd_service_state)); if( ngfd_service_state != SERVICE_STATE_RUNNING ) xngf_delete_client(); EXIT: return; } /** * Datapipe trigger for the [power] key * * @param data A pointer to the input_event struct */ static void pwrkey_datapipe_keypress_event_cb(gconstpointer const data) { /* Faulty/aged physical power key buttons can generate * bursts of press and release events that are then * interpreted as double presses. To avoid this we * ignore power key presses that occur so soon after * previous release that they are unlikely to be * caused by human activity. */ /* Minimum delay between power key release and press. */ static const int64_t press_delay = 50; /* Time limit for accepting the next power key press */ static int64_t press_limit = 0; const struct input_event * const *evp; const struct input_event *ev; if( !(evp = data) ) goto EXIT; if( !(ev = *evp) ) goto EXIT; switch( ev->type ) { case EV_KEY: switch( ev->code ) { case KEY_POWER: if( ev->value == 1 ) { /* Use event time infomation provided by kernel. * Assumed: monotonically rising values, we use * only difference between values and do not need * to care about sign etc of individual values. */ int64_t press_time = pwrkey_event_tick(ev); if( press_limit && press_time < press_limit ) { /* Too soon after the previous powerkey * release -> assume faulty hw sending * bursts of presses */ mce_log(LL_CRUCIAL, "powerkey press ignored"); } else { mce_log(LL_CRUCIAL, "powerkey pressed"); /* Detect repeated power key pressing while * proximity sensor is covered; assume it means * the sensor is stuck and user wants to be able * to turn on the display regardless of the sensor * state */ pwrkey_ps_override_evaluate(); /* Power key pressed */ pwrkey_stm_powerkey_pressed(); /* Some devices report both power key press and release * already when the physical button is pressed down. * Other devices wait for physical release before * reporting key release. And in some devices it depends * on whether the device is suspended or not. * * To normalize behavior in default configuration (i.e. * begin display power up already on power key press * without waiting for user to lift finger off the button): * Synthetize key release, if no actions are bound to long * power key press from display off state. */ if( pwrkey_stm_display_state == MCE_DISPLAY_OFF ) { if( !pwrkey_actions_from_display_off.mask_long ) { mce_log(LL_DEBUG, "powerkey release simulated"); pwrkey_stm_powerkey_released(); } } } /* Adjust time limit for accepting the next power * key press (and make sure it stays non-zero) */ press_limit = press_time + press_delay ?: 1; } else if( ev->value == 0 ) { mce_log(LL_CRUCIAL, "powerkey released"); /* Power key released */ pwrkey_stm_powerkey_released(); } pwrkey_stm_rethink_wakelock(); break; case KEY_HOME: if( ev->value == 1 ) { mce_log(LL_CRUCIAL, "homekey pressed"); homekey_stm_set_pressed(true); } else if( ev->value == 0 ) { mce_log(LL_CRUCIAL, "homekey released"); homekey_stm_set_pressed(false); } break; default: break; } break; case EV_MSC: if( ev->code == MSC_GESTURE ) { mce_log(LL_CRUCIAL, "gesture(%d)", ev->value); pwrkey_actions_do_gesture(ev->value); } break; default: break; } EXIT: return; } /** Handle call state change notifications * * @param data call state (as void pointer) */ static void pwrkey_datapipe_call_state_cb(gconstpointer data) { call_state_t prev = call_state; call_state = GPOINTER_TO_INT(data); if( call_state == prev ) goto EXIT; mce_log(LL_DEBUG, "call_state = %s -> %s", call_state_repr(prev), call_state_repr(call_state)); if( pwrkey_ignore_incoming_call ) { mce_log(LL_DEBUG, "stop ignoring incoming calls"); pwrkey_ignore_incoming_call = false; } EXIT: return; } /** Handle alarm ui state change notifications * * @param data alarm ui state (as void pointer) */ static void pwrkey_datapipe_alarm_ui_state_cb(gconstpointer data) { alarm_ui_state_t prev = alarm_ui_state; alarm_ui_state = GPOINTER_TO_INT(data); /* No need to separate between "invalid" and "off" */ if( alarm_ui_state == MCE_ALARM_UI_INVALID_INT32 ) alarm_ui_state = MCE_ALARM_UI_OFF_INT32; if( alarm_ui_state == prev ) goto EXIT; mce_log(LL_DEBUG, "alarm_ui_state = %s -> %s", alarm_state_repr(prev), alarm_state_repr(alarm_ui_state)); EXIT: return; } /** Change notifications for devicelock_service_state */ static void pwrkey_datapipe_devicelock_service_state_cb(gconstpointer data) { service_state_t prev = devicelock_service_state; devicelock_service_state = GPOINTER_TO_INT(data); if( devicelock_service_state == prev ) goto EXIT; mce_log(LL_DEBUG, "devicelock_service_state = %s -> %s", service_state_repr(prev), service_state_repr(devicelock_service_state)); /* no immediate action, but see pwrkey_action_devlock() */ EXIT: return; } /** Change notifications for enroll_in_progress */ static void pwrkey_datapipe_enroll_in_progress_cb(gconstpointer data) { bool prev = enroll_in_progress; enroll_in_progress = GPOINTER_TO_INT(data); if( enroll_in_progress == prev ) goto EXIT; mce_log(LL_DEBUG, "enroll_in_progress = %s -> %s", prev ? "true" : "false", enroll_in_progress ? "true" : "false"); /* no immediate action, but see pwrkey_predicate_powerkey() */ EXIT: return; } /** Handle NGFD play event requests * * @param data Requested event name (as void pointer) */ static void pwrkey_datapipe_ngfd_event_request_cb(gconstpointer data) { const char *event = data; mce_log(LL_DEBUG, "ngfd event request = %s", event); xngf_play_event(event); } /** Array of datapipe handlers */ static datapipe_handler_t pwrkey_datapipe_handlers[] = { // input triggers { .datapipe = &keypress_event_pipe, .input_cb = pwrkey_datapipe_keypress_event_cb, }, { .datapipe = &ngfd_event_request_pipe, .input_cb = pwrkey_datapipe_ngfd_event_request_cb, }, // output triggers { .datapipe = &ngfd_service_state_pipe, .output_cb = pwrkey_datapipe_ngfd_service_state_cb, }, { .datapipe = &system_state_pipe, .output_cb = pwrkey_datapipe_system_state_cb, }, { .datapipe = &devicelock_state_pipe, .output_cb = pwrkey_datapipe_devicelock_state_cb, }, { .datapipe = &display_state_curr_pipe, .output_cb = pwrkey_datapipe_display_state_curr_cb, }, { .datapipe = &display_state_next_pipe, .output_cb = pwrkey_datapipe_display_state_next_cb, }, { .datapipe = &lid_sensor_filtered_pipe, .output_cb = pwrkey_datapipe_lid_sensor_filtered_cb, }, { .datapipe = &proximity_sensor_actual_pipe, .output_cb = pwrkey_datapipe_proximity_sensor_actual_cb, }, { .datapipe = &alarm_ui_state_pipe, .output_cb = pwrkey_datapipe_alarm_ui_state_cb, }, { .datapipe = &call_state_pipe, .output_cb = pwrkey_datapipe_call_state_cb, }, { .datapipe = &devicelock_service_state_pipe, .output_cb = pwrkey_datapipe_devicelock_service_state_cb, }, { .datapipe = &enroll_in_progress_pipe, .output_cb = pwrkey_datapipe_enroll_in_progress_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t pwrkey_datapipe_bindings = { .module = MODULE_NAME, .handlers = pwrkey_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void pwrkey_datapipe_init(void) { mce_datapipe_init_bindings(&pwrkey_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void pwrkey_datapipe_quit(void) { mce_datapipe_quit_bindings(&pwrkey_datapipe_bindings); } /* ========================================================================= * * NGFD_GLUE * ========================================================================= */ static NgfClient *ngf_client_hnd = 0; static DBusConnection *ngf_dbus_con = 0; static uint32_t ngf_event_id = 0; static const char * xngf_state_repr(NgfEventState state) { const char *repr = "unknown"; switch( state ) { case NGF_EVENT_FAILED: repr = "failed"; break; case NGF_EVENT_COMPLETED: repr = "completed"; break; case NGF_EVENT_PLAYING: repr = "playing"; break; case NGF_EVENT_PAUSED: repr = "paused"; break; default: break; } return repr; } static void xngf_status_cb(NgfClient *client, uint32_t event_id, NgfEventState state, void *userdata) { (void) client; (void) userdata; mce_log(LL_DEBUG, "%s(%d)", xngf_state_repr(state), event_id); switch( state ) { default: case NGF_EVENT_PLAYING: case NGF_EVENT_PAUSED: break; case NGF_EVENT_COMPLETED: ngf_event_id = 0; break; case NGF_EVENT_FAILED: mce_log(LL_ERR, "Failed to play id %d", event_id); ngf_event_id = 0; break; } } static bool xngf_create_client(void) { if( !ngf_dbus_con ) { mce_log(LL_WARN, "can't use ngfd - no dbus connection"); goto EXIT; } if( ngfd_service_state != SERVICE_STATE_RUNNING ) { mce_log(LL_WARN, "can't use ngfd - service not running"); goto EXIT; } if( ngf_client_hnd ) goto EXIT; ngf_client_hnd = ngf_client_create(NGF_TRANSPORT_DBUS, ngf_dbus_con); if( !ngf_client_hnd ) { mce_log(LL_WARN, "can't use ngfd - failed to create client"); goto EXIT; } ngf_client_set_callback(ngf_client_hnd, xngf_status_cb, NULL); mce_log(LL_DEBUG, "ngfd client created"); EXIT: return ngf_client_hnd != 0; } static void xngf_delete_client(void) { if( ngf_client_hnd ) { ngf_client_destroy(ngf_client_hnd), ngf_client_hnd = 0; mce_log(LL_DEBUG, "ngfd client deleted"); } ngf_event_id = 0; } static void xngf_play_event(const char *event_name) { if( ngf_event_id ) { mce_log(LL_WARN, "previous event not finished yet"); goto EXIT; } if( !xngf_create_client() ) goto EXIT; ngf_event_id = ngf_client_play_event (ngf_client_hnd, event_name, NULL); mce_log(LL_DEBUG, "event=%s, id=%d", event_name, ngf_event_id); EXIT: return; } static void xngf_init(void) { ngf_dbus_con = dbus_connection_get(); } static void xngf_quit(void) { xngf_delete_client(); if( ngf_dbus_con ) dbus_connection_unref(ngf_dbus_con), ngf_dbus_con = 0; } /* ------------------------------------------------------------------------- * * PWRKEY_QUEUED * ------------------------------------------------------------------------- */ /** Create queued actions object * * @param predicate Unblank predicate * @param actions Bitmap of actions * * @return Queued actions object */ static pwrkey_queued_t * pwrkey_queued_create(pwrkey_unblank_predicate_t predicate, uint32_t actions) { if( mce_log_p(LL_DEBUG) ) { gchar *names = pwrkey_mask_to_names(actions); mce_log(LL_DEBUG, "QUEUE actions: %s; predicate: %s", names, pwrkey_unblank_predicate_name[predicate]); g_free(names); } pwrkey_queued_t *self = g_slice_alloc0(sizeof *self); self->predicate = predicate; self->actions = actions; return self; } /** Delete queued actions object * * @param self Queued actions object, or NULL */ static void pwrkey_queued_delete(pwrkey_queued_t *self) { g_slice_free1(sizeof *self, self); } /** Type agnostic callback for deleting queued actions objects * * @param self Queued actions object, or NULL */ static void pwrkey_queued_delete_cb(gpointer self) { pwrkey_queued_delete(self); } /* ------------------------------------------------------------------------- * * PWRKEY_QUEUE * ------------------------------------------------------------------------- */ /** Queued actions objects */ static GQueue *pwrkey_queue_items = NULL; /** Timer id for unblank delay */ static guint pwrkey_queue_unblank_delay_id = 0; /** Timer id for action delay */ static guint pwrkey_queue_action_delay_id = 0; /** Timer id for blank delay */ static guint pwrkey_queue_blank_delay_id = 0; /** Initialize powerkey action queue */ static void pwrkey_queue_init(void) { if( !pwrkey_queue_items ) pwrkey_queue_items = g_queue_new(); } /** Cleanup powerkey action queue */ static void pwrkey_queue_quit(void) { if( pwrkey_queue_items ) { g_queue_clear_full(pwrkey_queue_items, pwrkey_queued_delete_cb); g_queue_free(pwrkey_queue_items), pwrkey_queue_items = NULL; } pwrkey_queue_stop_all_delays(); pwrkey_queue_rethink_psensor(); } /** Queue powerkey actions for execution * * @param predicate Unblank predicate * @param actions Bitmap of actions */ static void pwrkey_queue_add_actions(pwrkey_unblank_predicate_t predicate, uint32_t actions) { if( pwrkey_queue_items ) { /* Note that the given predicate is meaningful only when * executing "unblank" or "unblanked" action sets and * needs to be substituted when not dealing with such. */ pwrkey_unblank_predicate_t dontcare = PWRKEY_UNBLANK_PREDICATE_DONTCARE; /* Note that fixed execution order must be preserved even * if we end up performing the actions in multiple chunks. * * Step 1: Blanking actions * * When blanking, wait for display=off before continuing * to avoid triggering changes during display fade out. */ uint32_t blank = actions & pwrkey_mask_from_name("blank"); if( blank ) { blank = actions & ((blank << 1) - 1); g_queue_push_tail(pwrkey_queue_items, pwrkey_queued_create(dontcare, blank)); actions &= ~blank; } /* Step 2: Unblanking actions * * When unblanking, wait for display power up before * continuing with actions past "tkunlock". */ uint32_t unblank = actions & pwrkey_mask_from_name("unblank"); uint32_t tkunlock = pwrkey_mask_from_name("tkunlock"); if( (tkunlock = actions & ((tkunlock << 1) - 1)) ) { g_queue_push_tail(pwrkey_queue_items, pwrkey_queued_create(predicate, tkunlock)); actions &= ~tkunlock; } /* Step 3: Remaining actions * * No delay, except going through idle callback before * continuing with further actions objects. */ if( actions ) { uint32_t unblanked = actions & pwrkey_mask_from_name("unblanked"); if( !unblank && !unblanked ) predicate = dontcare; g_queue_push_tail(pwrkey_queue_items, pwrkey_queued_create(predicate, actions)); } /* Make an attempt to execute something immediately */ pwrkey_queue_execute(); } } /** Execute powerkey actions in queue */ static void pwrkey_queue_execute(void) { while( !pwrkey_queue_is_delayed() ) { pwrkey_queued_t *curr = pwrkey_queue_current_action(); if( !curr ) break; if( mce_log_p(LL_DEBUG) ) { gchar *names = pwrkey_mask_to_names(curr->actions); mce_log(LL_DEBUG, "EXECUTE actions: %s; predicate: %s", names, pwrkey_unblank_predicate_name[curr->predicate]); g_free(names); } bool psensor = false; bool allowed = pwrkey_predicate(curr->predicate, &psensor); bool unknown = psensor && proximity_sensor_actual == COVER_UNDEF; if( mce_log_p(LL_DEBUG) ) { mce_log(LL_DEBUG, "predicate(%s) => %s (psensor %s)", pwrkey_unblank_predicate_name[curr->predicate], unknown ? "unknown" : allowed ? "allowed" : "denied", psensor ? "needed" : "not needed"); } if( unknown ) { /* Predicate result depends on proximity sensor state and * we do not know it at the moment -> break out to trigger * "on-demand" condition and wait for sensor ramp up. */ break; } if( allowed ) { for( size_t i = 0; i < G_N_ELEMENTS(pwrkey_action_lut); ++i ) { if( curr->actions & (1 << i) ) { mce_log(LL_DEBUG, "* exec action(%s)", pwrkey_action_lut[i].name); pwrkey_action_lut[i].func(); } } pwrkey_queue_start_delay(curr->actions); } else if( mce_log_p(LL_DEBUG) ) { for( size_t i = 0; i < G_N_ELEMENTS(pwrkey_action_lut); ++i ) { if( curr->actions & (1 << i) ) { mce_log(LL_DEBUG, "* skip action(%s)", pwrkey_action_lut[i].name); } } } pwrkey_queue_finish_action(); } pwrkey_queue_rethink_psensor(); } /** Peek at the current powerkey action in queue * * @return Queued actions object, or NULL if queue is empty */ static pwrkey_queued_t * pwrkey_queue_current_action(void) { pwrkey_queued_t *curr = NULL; if( pwrkey_queue_items ) curr = g_queue_peek_head(pwrkey_queue_items); return curr; } /** Proceed to the next powerkey action in queue * * Removes object at the head of the queue */ static void pwrkey_queue_finish_action(void) { if( pwrkey_queue_items ) pwrkey_queued_delete(g_queue_pop_head(pwrkey_queue_items)); } /** Check if having unblank delay makes sense * * @return false if display is already unblanked, true otherwise */ static bool pwrkey_queue_unblank_delay_needed(void) { bool is_relevant = true; switch( display_state_curr ) { case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: is_relevant = false; break; default: break; } return is_relevant; } /** Start unblank delay * * @param delay Delay in milliseconds */ static void pwrkey_queue_start_unblank_delay(guint delay) { pwrkey_queue_stop_all_delays(); if( pwrkey_queue_unblank_delay_needed() ) { mce_log(LL_DEBUG, "unblank delay = %u ms", delay); pwrkey_queue_unblank_delay_id = g_timeout_add(delay, pwrkey_queue_unblank_delay_cb, NULL); } } /** Cancel unblank delay * * Called from pwrkey_datapipe_display_state_curr_cb() * when display changes to powered on state. */ static void pwrkey_queue_stop_unblank_delay(void) { if( pwrkey_queue_unblank_delay_id ) { g_source_remove(pwrkey_queue_unblank_delay_id), pwrkey_queue_unblank_delay_id = 0; mce_log(LL_DEBUG, "unblank delay stopped"); pwrkey_queue_execute(); } } /** Timer callback for ending unblank delay * * @param aptr (unused user data pointer) */ static gboolean pwrkey_queue_unblank_delay_cb(gpointer aptr) { (void)aptr; pwrkey_queue_unblank_delay_id = 0; mce_log(LL_DEBUG, "unblank delay ended"); pwrkey_queue_execute(); return G_SOURCE_REMOVE; } /** Start action delay * * @param delay Delay in milliseconds */ static void pwrkey_queue_start_action_delay(guint delay) { pwrkey_queue_stop_all_delays(); mce_log(LL_DEBUG, "action delay = %u ms", delay); pwrkey_queue_action_delay_id = g_timeout_add(delay, pwrkey_queue_action_delay_cb, NULL); } /** Cancel action delay * * Called from pwrkey_datapipe_display_state_curr_cb() * when display changes to powered on state. */ static void pwrkey_queue_stop_action_delay(void) { if( pwrkey_queue_action_delay_id ) { g_source_remove(pwrkey_queue_action_delay_id), pwrkey_queue_action_delay_id = 0; mce_log(LL_DEBUG, "action delay stopped"); pwrkey_queue_execute(); } } /** Timer callback for ending action delay * * @param aptr (unused user data pointer) */ static gboolean pwrkey_queue_action_delay_cb(gpointer aptr) { (void)aptr; pwrkey_queue_action_delay_id = 0; mce_log(LL_DEBUG, "action delay ended"); pwrkey_queue_execute(); return G_SOURCE_REMOVE; } /** Check if having blank delay makes sense * * @return false if display is already blanked, true otherwise */ static bool pwrkey_queue_blank_delay_needed(void) { bool is_relevant = true; switch( display_state_curr ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: is_relevant = false; break; default: case MCE_DISPLAY_POWER_DOWN: break; } return is_relevant; } /** Start blank delay * * @param delay Delay in milliseconds */ static void pwrkey_queue_start_blank_delay(guint delay) { pwrkey_queue_stop_all_delays(); if( pwrkey_queue_blank_delay_needed() ) { mce_log(LL_DEBUG, "blank delay = %u ms", delay); pwrkey_queue_blank_delay_id = g_timeout_add(delay, pwrkey_queue_blank_delay_cb, NULL); } } /** Cancel blank delay * * Called from pwrkey_datapipe_display_state_curr_cb() * when display changes to powered on state. */ static void pwrkey_queue_stop_blank_delay(void) { if( pwrkey_queue_blank_delay_id ) { g_source_remove(pwrkey_queue_blank_delay_id), pwrkey_queue_blank_delay_id = 0; mce_log(LL_DEBUG, "blank delay stopped"); pwrkey_queue_execute(); } } /** Timer callback for ending blank delay * * @param aptr (unused user data pointer) */ static gboolean pwrkey_queue_blank_delay_cb(gpointer aptr) { (void)aptr; pwrkey_queue_blank_delay_id = 0; mce_log(LL_DEBUG, "blank delay ended"); pwrkey_queue_execute(); return G_SOURCE_REMOVE; } /** Start delay timer depending on actions just executed * * @param actions Bitmap of just executed actions */ static void pwrkey_queue_start_delay(uint32_t actions) { if( actions & pwrkey_mask_from_name("unblank") ) pwrkey_queue_start_unblank_delay(500); else if( actions & pwrkey_mask_from_name("blank") ) pwrkey_queue_start_blank_delay(500); else pwrkey_queue_start_action_delay(0); } /** Stop all timers that delay queue execution */ static void pwrkey_queue_stop_all_delays(void) { pwrkey_queue_stop_unblank_delay(); pwrkey_queue_stop_action_delay(); pwrkey_queue_stop_blank_delay(); } /** Predicate for: pending unblank delay * * @returns true if queue processing is delayed, false otherwise */ static bool pwrkey_queue_is_delayed(void) { return (pwrkey_queue_unblank_delay_id || pwrkey_queue_action_delay_id || pwrkey_queue_blank_delay_id); } /** Predicate for: pending queued actions * * @returns true if queue is busy, false otherwise */ static bool pwrkey_queue_is_busy(void) { return pwrkey_queue_is_delayed() || pwrkey_queue_current_action(); } /** Check if blank/unblank delays can be canceled */ static void pwrkey_queue_rethink_delays(void) { if( !pwrkey_queue_unblank_delay_needed() ) pwrkey_queue_stop_unblank_delay(); if( !pwrkey_queue_blank_delay_needed() ) pwrkey_queue_stop_blank_delay(); } /** Re-evaluate need for on-demand proximity sensor * * Request "on-demand" proximity sensor activation / deactivation * based on queue status. */ static void pwrkey_queue_rethink_psensor(void) { static const char enable[] = PROXIMITY_SENSOR_REQUIRED_ADD MODULE_NAME; static const char disable[] = PROXIMITY_SENSOR_REQUIRED_REM MODULE_NAME; static bool active = false; bool required = pwrkey_queue_is_busy(); if( active != required ) { active = required; /* Note: logging from datapipe functions */ datapipe_exec_full(&proximity_sensor_required_pipe, required ? enable : disable); pwrkey_stm_rethink_wakelock(); } } /* ========================================================================= * * MODULE_INTEFACE * ========================================================================= */ /** * Init function for the powerkey component * * @return TRUE on success, FALSE on failure */ gboolean mce_powerkey_init(void) { pwrkey_datapipe_init(); pwrkey_dbus_init(); pwrkey_setting_init(); xngf_init(); pwrkey_queue_init(); return TRUE; } /** * Exit function for the powerkey component * * @todo D-Bus unregistration */ void mce_powerkey_exit(void) { pwrkey_queue_quit(); xngf_quit(); pwrkey_dbus_quit(); pwrkey_setting_quit(); pwrkey_datapipe_quit(); /* Remove all timer sources & release wakelock */ pwrkey_stm_terminate(); common_on_proximity_cancel(MODULE_NAME, 0, 0); return; }