/**
* @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;
}