From 33978390a9e69fc0ddd09102ee7168dd7f10ad2c Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Thu, 23 Oct 2014 14:03:24 +0300 Subject: [PATCH 1/6] Make unblank and unlock via double tap work again Legacy option to allow bypassing of lockscreen with doubletap was broken. Make it work again. Also do not use raw numeric values to evaluate doubletap_gesture_policy. [mce] Make unblank and unlock via double tap work again --- tklock.c | 52 +++++++++++++++++++++++++++++++------------------ tklock.h | 22 +++++++++++++-------- tools/mcetool.c | 6 +++--- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/tklock.c b/tklock.c index 787ab61..c56e016 100644 --- a/tklock.c +++ b/tklock.c @@ -270,6 +270,8 @@ static void tklock_dtcalib_stop(void); // settings from gconf +static void tklock_gconf_sanitize_doubletap_gesture_policy(void); + static void tklock_gconf_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); static void tklock_gconf_init(void); @@ -360,12 +362,12 @@ static gboolean tk_autolock_enabled = DEFAULT_TK_AUTOLOCK; static guint tk_autolock_enabled_cb_id = 0; /** Touchscreen double tap gesture policy */ -static gint doubletap_gesture_policy = DEFAULT_DOUBLETAP_GESTURE_POLICY; +static gint doubletap_gesture_policy = DBLTAP_ACTION_DEFAULT; /** GConf callback ID for doubletap_gesture_policy */ static guint doubletap_gesture_policy_cb_id = 0; -/** Touchscreen double tap gesture mode */ -gint doubletap_enable_mode = DBLTAP_ENABLE_DEFAULT; +/** Touchscreen double tap gesture enable mode */ +static gint doubletap_enable_mode = DBLTAP_ENABLE_DEFAULT; /** GConf callback ID for doubletap_enable_mode */ static guint doubletap_enable_mode_cb_id = 0; @@ -1251,8 +1253,8 @@ static void tklock_datapipe_touchscreen_cb(gconstpointer const data) } switch( doubletap_gesture_policy ) { - case 1: // unblank - case 2: // unblank + unlock (= TODO) + case DBLTAP_ACTION_UNBLANK: // unblank + case DBLTAP_ACTION_TKUNLOCK: // unblank + unlock mce_log(LL_DEBUG, "double tap -> display on"); /* Double tap event that is about to be used for unblanking * the display counts as non-syntetized user activity */ @@ -1263,6 +1265,13 @@ static void tklock_datapipe_touchscreen_cb(gconstpointer const data) execute_datapipe(&display_state_req_pipe, GINT_TO_POINTER(MCE_DISPLAY_ON), USE_INDATA, CACHE_INDATA); + + /* Optionally remove tklock */ + if( doubletap_gesture_policy == DBLTAP_ACTION_TKUNLOCK ) { + execute_datapipe(&tk_lock_pipe, + GINT_TO_POINTER(LOCK_OFF), + USE_INDATA, CACHE_INDATA); + } break; default: mce_log(LL_ERR, "Got a double tap gesture " @@ -3030,7 +3039,7 @@ static void tklock_evctrl_rethink(void) } /* doubletap gesture policy must not be 0/disabled */ - if( doubletap_gesture_policy == 0 ) { + if( doubletap_gesture_policy == DBLTAP_ACTION_DISABLED ) { enable_dt = false; } @@ -3236,6 +3245,22 @@ static void tklock_dtcalib_start(void) * SETTINGS FROM GCONF * ========================================================================= */ +static void tklock_gconf_sanitize_doubletap_gesture_policy(void) +{ + switch( doubletap_gesture_policy ) { + case DBLTAP_ACTION_DISABLED: + case DBLTAP_ACTION_UNBLANK: + case DBLTAP_ACTION_TKUNLOCK: + break; + + default: + mce_log(LL_WARN, "Double tap gesture has invalid policy: %d; " + "using default", doubletap_gesture_policy); + doubletap_gesture_policy = DBLTAP_ACTION_DEFAULT; + break; + } +} + /** GConf callback for touchscreen/keypad lock related settings * * @param gcc Unused @@ -3264,13 +3289,7 @@ static void tklock_gconf_cb(GConfClient *const gcc, const guint id, } else if( id == doubletap_gesture_policy_cb_id ) { doubletap_gesture_policy = gconf_value_get_int(gcv); - - if( doubletap_gesture_policy < 0 || doubletap_gesture_policy > 2 ) { - mce_log(LL_WARN, "Double tap gesture has invalid policy: %d; " - "using default", doubletap_gesture_policy); - doubletap_gesture_policy = DEFAULT_DOUBLETAP_GESTURE_POLICY; - } - + tklock_gconf_sanitize_doubletap_gesture_policy(); tklock_evctrl_rethink(); } else if( id == tklock_blank_disable_id ) { @@ -3347,12 +3366,7 @@ static void tklock_gconf_init(void) mce_gconf_get_int(MCE_GCONF_TK_DOUBLE_TAP_GESTURE_PATH, &doubletap_gesture_policy); - - if( doubletap_gesture_policy < 0 || doubletap_gesture_policy > 2 ) { - mce_log(LL_WARN, "Double tap gesture has invalid policy: %d; " - "using default", doubletap_gesture_policy); - doubletap_gesture_policy = DEFAULT_DOUBLETAP_GESTURE_POLICY; - } + tklock_gconf_sanitize_doubletap_gesture_policy(); /** Touchscreen double tap gesture mode */ mce_gconf_notifier_add(MCE_GCONF_DOUBLETAP_PATH, diff --git a/tklock.h b/tklock.h index 3edc5a2..e5809dc 100644 --- a/tklock.h +++ b/tklock.h @@ -38,14 +38,20 @@ /** SysFS interface to enable/disable the RX-44/RX-48 keyboard IRQs */ #define MCE_RX44_KEYBOARD_SYSFS_DISABLE_PATH "/sys/devices/platform/i2c_omap.2/i2c-0/0-0045/disable_kp" -/** - * Default double tap gesture: - * - * 0 - Gesture disabled - * 1 - Show unlock screen - * 2 - Unlock tklock - */ -#define DEFAULT_DOUBLETAP_GESTURE_POLICY 1 +/** Double tap wakeup action modes */ +typedef enum +{ + /** Gesture disabled */ + DBLTAP_ACTION_DISABLED = 0, + + /** Show unlock screen */ + DBLTAP_ACTION_UNBLANK = 1, + + /* Unlock tklock */ + DBLTAP_ACTION_TKUNLOCK = 2, + + DBLTAP_ACTION_DEFAULT = DBLTAP_ACTION_UNBLANK +} dbltap_action_t; /** Proximity timeout for double tap gesture; in seconds */ #define DEFAULT_POCKET_MODE_PROXIMITY_TIMEOUT 5 diff --git a/tools/mcetool.c b/tools/mcetool.c index 04916bf..4da441a 100644 --- a/tools/mcetool.c +++ b/tools/mcetool.c @@ -2575,9 +2575,9 @@ static void xmce_get_display_off_override(void) * @note These must match the hardcoded values in mce itself. */ static const symbol_t doubletap_values[] = { - { "disabled", 0 }, - { "show-unlock-screen", 1 }, - { "unlock", 2 }, + { "disabled", DBLTAP_ACTION_DISABLED }, + { "show-unlock-screen", DBLTAP_ACTION_UNBLANK }, + { "unlock", DBLTAP_ACTION_TKUNLOCK }, { NULL, -1 } }; From 069584087125ade940abb9ece8dcb5588ca73b90 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Thu, 23 Oct 2014 13:33:38 +0300 Subject: [PATCH 2/6] Add helper functions for setting up configuration change tracking Having helper functions for both reading initial values and installing change notifiers allows builtin-gconf value tracking setup to be more compact and readable. --- mce-gconf.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mce-gconf.h | 5 +++++ 2 files changed, 70 insertions(+) diff --git a/mce-gconf.c b/mce-gconf.c index a59e290..b6c61f3 100644 --- a/mce-gconf.c +++ b/mce-gconf.c @@ -23,6 +23,9 @@ #include "mce-log.h" +#include +#include + /** Pointer to the GConf client */ static GConfClient *gconf_client = NULL; /** Is GConf disabled on purpose */ @@ -412,6 +415,68 @@ void mce_gconf_notifier_remove_cb(gpointer cb_id, gpointer user_data) mce_gconf_notifier_remove(GPOINTER_TO_INT(cb_id)); } +/** Helper for getting path of gconf key + */ +static gchar *mce_gconf_get_path(const gchar *key) +{ + gchar *res = 0; + const gchar *end = strrchr(key, '/'); + + if( end ) + res = g_strndup(key, end - key); + + return res; +} + +/** Get initial value of integer setting and start tracking changes + * + * @param key key name + * @param val where to store the initial value + * @param def default value to use if getting key value fails; + * or -1 to leave *val unmodified + * @param cb change notification callback + * @param cb_id where to store notification callback id + */ +void mce_gconf_track_int(const gchar *key, gint *val, gint def, + GConfClientNotifyFunc cb, guint *cb_id) +{ + gchar *path = mce_gconf_get_path(key); + + if( path && cb && cb_id ) + mce_gconf_notifier_add(path, key, cb, cb_id); + + if( !mce_gconf_get_int(key, val) && def != -1 ) + *val = def; + + g_free(path); +} + +/** Get initial value of integer setting and start tracking changes + * + * Note: Caller must release returned string with g_free() when it + * is no longer needed. + * + * @param key key name + * @param val where to store the initial value + * @param def default value to use if getting key value fails; + * or NULL to leave *val unmodified + * @param cb change notification callback + * @param cb_id where to store notification callback id + */ +void mce_gconf_track_string(const gchar *key, gchar **val, const gchar *def, + GConfClientNotifyFunc cb, guint *cb_id) +{ + gchar *path = mce_gconf_get_path(key); + + if( path && cb && cb_id ) + mce_gconf_notifier_add(path, key, cb, cb_id); + + if( !mce_gconf_get_string(key, val) && def != 0 ) + *val = g_strdup(def); + + g_free(path); +} + /** * Init function for the mce-gconf component * diff --git a/mce-gconf.h b/mce-gconf.h index 41a4bf2..69129ca 100644 --- a/mce-gconf.h +++ b/mce-gconf.h @@ -36,6 +36,11 @@ gboolean mce_gconf_notifier_add(const gchar *path, const gchar *key, void mce_gconf_notifier_remove(guint id); void mce_gconf_notifier_remove_cb(gpointer cb_id, gpointer user_data); +void mce_gconf_track_int(const gchar *key, gint *val, gint def, + GConfClientNotifyFunc cb, guint *cb_id); +void mce_gconf_track_string(const gchar *key, gchar **val, const gchar *def, + GConfClientNotifyFunc cb, guint *cb_id); + gboolean mce_gconf_init(void); void mce_gconf_exit(void); From 6154e96cc2cde78811deb1f7d51acd642b3e8b92 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Fri, 24 Oct 2014 12:31:18 +0300 Subject: [PATCH 3/6] Rewrite powerkey handler to allow more flexible configuration Taking double powerkey presses in use caused unconditional delay for single press handling. The actions that could be configured to be taken were not be differentiated based on display state, which made it impossible do one thing from display off and another one from display on. And since static configuration was used, changes did not take effect without restarting mce. Make possible actions as small and simple as possible. Allow combining them freely to choose actions taken. Use separate action combinations depending on whether display is on or off. Execute common parts of single and double press actions immediately when 1st powerkey press is released. Allow more flexibility for using custom dbus actions by allowing user configured method call to be made instead of signal broadcast. Use dynamic runtime changeable settings instead of static configuration from ini-files and add options to mcetool for changing all of them. Use the new functionality to: - apply device lock when double press is made from display on - unblank and hide lockscreen when double press is made from display off [mce] Rewrite powerkey handler to allow more flexible configuration. Fixes JB#23653 --- Makefile | 5 +- builtin-gconf.c | 50 + inifiles/mce.ini | 65 -- powerkey.c | 2915 +++++++++++++++++++++++++++++++--------------- powerkey.dot | 39 + powerkey.h | 167 ++- tools/mcetool.c | 642 +++++++++- 7 files changed, 2757 insertions(+), 1126 deletions(-) create mode 100644 powerkey.dot diff --git a/Makefile b/Makefile index d453053..f3d16be 100644 --- a/Makefile +++ b/Makefile @@ -567,6 +567,9 @@ NORMALIZE_USES_SPC =\ modules/radiostates.h\ modules/sensor-gestures.c\ ofono-dbus-names.h\ + powerkey.c\ + powerkey.h\ + powerkey.dot\ systemui/dbus-names.h\ tklock.c\ tklock.h\ @@ -616,8 +619,6 @@ NORMALIZE_USES_TAB =\ modules/proximity.c\ modules/proximity.h\ modules/radiostates.c\ - powerkey.c\ - powerkey.h\ systemui/tklock-dbus-names.h\ NORMALIZE_KNOWN := $(NORMALIZE_USES_SPC) $(NORMALIZE_USES_TAB) diff --git a/builtin-gconf.c b/builtin-gconf.c index 50193be..3a1e5e2 100644 --- a/builtin-gconf.c +++ b/builtin-gconf.c @@ -1482,6 +1482,56 @@ static const setting_t gconf_defaults[] = .type = "i", .def = "333", }, + { + .key = MCE_GCONF_POWERKEY_LONG_PRESS_DELAY, + .type = "i", + .def = G_STRINGIFY(DEFAULT_POWERKEY_LONG_DELAY), + }, + { + .key = MCE_GCONF_POWERKEY_DOUBLE_PRESS_DELAY, + .type = "i", + .def = G_STRINGIFY(DEFAULT_POWERKEY_DOUBLE_DELAY), + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_SINGLE_ON, + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_DOUBLE_ON, + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_LONG_ON, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_LONG_ON, + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_SINGLE_OFF, + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_DOUBLE_OFF, + }, + { + .key = MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF, + .type = "s", + .def = DEFAULT_POWERKEY_ACTIONS_LONG_OFF, + }, + { + .key = MCE_GCONF_POWERKEY_DBUS_ACTION1, + .type = "s", + .def = DEFAULT_POWERKEY_DBUS_ACTION1, + }, + { + .key = MCE_GCONF_POWERKEY_DBUS_ACTION2, + .type = "s", + .def = DEFAULT_POWERKEY_DBUS_ACTION2, + }, { .key = MCE_GCONF_MEMNOTIFY_WARNING_USED, .type = "i", diff --git a/inifiles/mce.ini b/inifiles/mce.ini index 8915c2a..6c169c7 100644 --- a/inifiles/mce.ini +++ b/inifiles/mce.ini @@ -29,71 +29,6 @@ Modules=radiostates;filter-brightness-als;display;keypad;led;battery-upower;inac # Timeout in milliseconds, default 800 HomeKeyLongDelay=800 -[PowerKey] - -# Timeout before keypress is regarded as a medium press -# This delay is used when powering up from charging -# -# Timeout in milliseconds, default 1000 -PowerKeyMediumDelay=1000 - -# Timeout before keypress is regarded as a long press -# -# Timeout in milliseconds, default 1500 -PowerKeyLongDelay=1500 - -# Timeout for double keypresses -# -# Timeout in milliseconds, default 500 -PowerKeyDoubleDelay=500 - -# Short [power] behaviour -# -# WARNING: -# Setting short, long, and double press to disabled will make it -# near impossible to turn off your device without removing the battery! -# -# Valid options: -# disabled - do nothing on short press -# poweroff - shutdown device -# softpoweroff - enter soft poweroff mode -# tklock-lock - lock touchscreen/keypad lock if not locked -# tklock-unlock - unlock the touchscreen/keypad lock if locked -# tklock-both - lock the touchscreen/keypad if not locked, -# unlock the touchscreen/keypad lock if locked -# dbus-signal- - send a D-Bus signal with the name -PowerKeyShortAction=tklock-lock - -# Long [power] behaviour -# -# Valid options: -# disabled - do nothing on long press -# poweroff - shutdown device -# softpoweroff - enter soft poweroff mode -# tklock-lock - lock touchscreen/keypad lock if not locked -# tklock-unlock - unlock the touchscreen/keypad lock if locked -# tklock-both - lock the touchscreen/keypad if not locked, -# unlock the touchscreen/keypad lock if locked -# dbus-signal- - send a D-Bus signal with the name -PowerKeyLongAction=poweroff - -# Double press [power] behaviour -# Note: the double press action is triggered on press, rather than release, -# to avoid the second press to be processed elsewhere before the -# double press action has taken place -# -# Valid options: -# disabled - do nothing on double press -# poweroff - shutdown device -# softpoweroff - enter soft poweroff mode -# tklock-lock - lock touchscreen/keypad lock if not locked -# tklock-unlock - unlock the touchscreen/keypad lock if locked -# tklock-both - lock the touchscreen/keypad if not locked, -# unlock the touchscreen/keypad lock if locked -# dbus-signal- - send a D-Bus signal with the name -# PowerKeyDoubleAction=dbus-signal-powerkey_double_ind -PowerKeyDoubleAction=disabled - [SoftPowerOff] # Charger connect policy diff --git a/powerkey.c b/powerkey.c index 5094bda..1be28e2 100644 --- a/powerkey.c +++ b/powerkey.c @@ -3,8 +3,10 @@ * Power key logic for the Mode Control Entity *

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

* @author David Weinehall + * @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License @@ -34,256 +36,434 @@ #include +#include +#include #include +#include +#include +#include #include -#if 0 // DEBUG: make all logging from this module "critical" -# undef mce_log -# define mce_log(LEV, FMT, ARGS...) \ - mce_log_file(LL_CRIT, __FILE__, __FUNCTION__, FMT , ## ARGS) -#endif +/* ========================================================================= * + * 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. + * ========================================================================= */ -/** - * The ID of the timeout used when determining - * whether the key press was short or long +/* ========================================================================= * + * 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 guint powerkey_timeout_cb_id = 0; +static inline bool eq(const char *s1, const char *s2) +{ + return (s1 && s2) ? !strcmp(s1, s2) : (s1 == s2); +} -/** - * The ID of the timeout used when determining - * whether the key press was a double press +/** String is NULL or empty predicate */ -static guint doublepress_timeout_cb_id = 0; - -/** Time in milliseconds before the key press is considered medium */ -static gint mediumdelay = DEFAULT_POWER_MEDIUM_DELAY; -/** Time in milliseconds before the key press is considered long */ -static gint longdelay = DEFAULT_POWER_LONG_DELAY; -/** Timeout in milliseconds during which key press is considered double */ -static gint doublepressdelay = DEFAULT_POWER_DOUBLE_DELAY; -/** Action to perform on a short key press */ -static poweraction_t shortpressaction = DEFAULT_POWERKEY_SHORT_ACTION; -/** Action to perform on a long key press */ -static poweraction_t longpressaction = DEFAULT_POWERKEY_LONG_ACTION; -/** Action to perform on a double key press */ -static poweraction_t doublepressaction = DEFAULT_POWERKEY_DOUBLE_ACTION; - -/** D-Bus signal to send on short [power] press */ -static gchar *shortpresssignal = NULL; -/** D-Bus signal to send on long [power] press */ -static gchar *longpresssignal = NULL; -/** D-Bus signal to send on double [power] press */ -static gchar *doublepresssignal = NULL; - -static void cancel_powerkey_timeout(void); +static inline bool empty(const char *s) +{ + return s == 0 || *s == 0; +} -/** Check if we need to hold a wakelock for power key handling +static int64_t pwrkey_get_boot_tick(void); +static char *pwrkey_get_token(char **ppos); +static bool pwrkey_create_flagfile(const char *path); +static bool pwrkey_delete_flagfile(const char *path); + +/* ------------------------------------------------------------------------- * + * PS_OVERRIDE * - * Effectively wakelock can be acquired only due to power key - * pressed handling in powerkey_trigger(). + * Provides escape from stuck proximity sensor. + * ------------------------------------------------------------------------- */ + +/** [setting] Power key press count for proximity sensor override */ +static gint pwrkey_ps_override_count = 3; + +/** GConf callback ID for pwrkey_ps_override_count */ +static guint pwrkey_ps_override_count_gconf_id = 0; + +/** [setting] Maximum time between power key presses for proximity sensor override */ +static gint pwrkey_ps_override_timeout = 333; + +/** GConf callback ID for pwrkey_ps_override_timeout */ +static guint pwrkey_ps_override_timeout_gconf_id = 0; + +static void pwrkey_ps_override_evaluate(void); + +/* ------------------------------------------------------------------------- * + * ACTION_EXEC * - * Releasing wakelock happens after power key is released - * and/or long/double tap timeouts get triggered. + * Individual actions that can be taken. + * ------------------------------------------------------------------------- */ + +static gint pwrkey_action_blank_mode = PWRKEY_BLANK_TO_OFF; +static guint pwrkey_action_blank_mode_gconf_id = 0; + +static void pwrkey_action_shutdown (void); +static void pwrkey_action_softoff (void); +static void pwrkey_action_tklock (void); +static void pwrkey_action_blank (void); +static void pwrkey_action_unblank (void); +static void pwrkey_action_tkunlock (void); +static void pwrkey_action_devlock (void); +static void pwrkey_action_dbus1 (void); +static void pwrkey_action_dbus2 (void); + +/* ------------------------------------------------------------------------- * + * ACTION_SETS * - * Timer re-programming does not affect wakelock status on purpose. - */ -static void powerkey_wakelock_rethink(void) + * Handle sets of individual actions. + * ------------------------------------------------------------------------- */ + +typedef struct { -#ifdef ENABLE_WAKELOCKS - static bool have_lock = false; - - bool want_lock = false; - - /* hold wakelock while we have active power key timers */ - if( powerkey_timeout_cb_id || doublepress_timeout_cb_id ) { - want_lock = true; - } - if( have_lock == want_lock ) - goto EXIT; - - if( (have_lock = want_lock) ) { - wakelock_lock("mce_powerkey_stm", -1); - mce_log(LL_DEBUG, "acquire wakelock"); - } - else { - mce_log(LL_DEBUG, "release wakelock"); - wakelock_unlock("mce_powerkey_stm"); - } -EXIT: - return; -#endif -} + const char *name; + void (*func)(void); +} pwrkey_bitconf_t; -/** Power key press actions mode */ -static gint powerkey_action_mode = PWRKEY_ENABLE_DEFAULT; +static void pwrkey_mask_execute (uint32_t mask); +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); -/** GConf callback ID for powerkey_action_mode */ -static guint powerkey_action_mode_cb_id = 0; +/* ------------------------------------------------------------------------- * + * ACTION_TRIGGERING + * ------------------------------------------------------------------------- */ -/** Power key press blanking mode */ -static gint powerkey_blanking_mode = PWRKEY_BLANK_TO_OFF; +typedef struct +{ + /** Actions common to single and double press */ + uint32_t mask_common; -/** GConf callback ID for powerkey_blanking_mode */ -static guint powerkey_blanking_mode_cb_id = 0; + /** Actions for single press */ + uint32_t mask_single; -/** Power key press count for proximity sensor override */ -static gint powerkey_ps_override_count = 3; + /** Actions for double press */ + uint32_t mask_double; -/** GConf callback ID for powerkey_ps_override_count */ -static guint powerkey_ps_override_count_cb_id = 0; + /** Actions for long press */ + uint32_t mask_long; +} pwrkey_actions_t; -/** Maximum time between power key presses for proximity sensor override */ -static gint powerkey_ps_override_timeout = 333; +/** Actions when power key is pressed while display is on */ +static pwrkey_actions_t pwrkey_actions_from_display_on = { 0, 0, 0, 0 }; -/** GConf callback ID for powerkey_ps_override_timeout */ -static guint powerkey_ps_override_timeout_cb_id = 0; +/** Actions when power key is pressed while display is off */ +static pwrkey_actions_t pwrkey_actions_from_display_off = { 0, 0, 0, 0 }; -/** GConf callback for powerkey related settings +/** 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_gconf_id = 0; + +static gchar *pwrkey_actions_double_on = 0; +static guint pwrkey_actions_double_on_gconf_id = 0; + +static gchar *pwrkey_actions_long_on = 0; +static guint pwrkey_actions_long_on_gconf_id = 0; + +static gchar *pwrkey_actions_single_off = 0; +static guint pwrkey_actions_single_off_gconf_id = 0; + +static gchar *pwrkey_actions_double_off = 0; +static guint pwrkey_actions_double_off_gconf_id = 0; + +static gchar *pwrkey_actions_long_off = 0; +static guint pwrkey_actions_long_off_gconf_id = 0; + +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_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_use_double_press(void); + +static void pwrkey_actions_select (bool display_is_on); + +/* ------------------------------------------------------------------------- * + * LONG_PRESS_TIMEOUT * - * @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 powerkey_gconf_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 == powerkey_action_mode_cb_id ) { - gint old = powerkey_action_mode; - powerkey_action_mode = gconf_value_get_int(gcv); - mce_log(LL_NOTICE, "powerkey_action_mode: %d -> %d", - old, powerkey_action_mode); - } - else if( id == powerkey_blanking_mode_cb_id ) { - gint old = powerkey_blanking_mode; - powerkey_blanking_mode = gconf_value_get_int(gcv); - mce_log(LL_NOTICE, "powerkey_blanking_mode: %d -> %d", - old, powerkey_blanking_mode); - } - else if( id == powerkey_ps_override_count_cb_id ) { - gint old = powerkey_ps_override_count; - powerkey_ps_override_count = gconf_value_get_int(gcv); - mce_log(LL_NOTICE, "powerkey_ps_override_count: %d -> %d", - old, powerkey_ps_override_count); - } - else if( id == powerkey_ps_override_timeout_cb_id ) { - gint old = powerkey_ps_override_timeout; - powerkey_ps_override_timeout = gconf_value_get_int(gcv); - mce_log(LL_NOTICE, "powerkey_ps_override_timeout: %d -> %d", - old, powerkey_ps_override_timeout); - } - else { - mce_log(LL_WARN, "Spurious GConf value received; confused!"); - } + * timer for telling apart short and long power key presses + * ------------------------------------------------------------------------- */ -EXIT: - return; -} +static gint pwrkey_long_press_delay = DEFAULT_POWERKEY_LONG_DELAY; +static guint pwrkey_long_press_delay_gconf_id = 0; -/** Get gconf values and add change notifiers +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 = DEFAULT_POWERKEY_DOUBLE_DELAY; +static guint pwrkey_double_press_delay_gconf_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); + +/* ------------------------------------------------------------------------- * + * 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 void powerkey_gconf_init(void) +static const char pwrkey_dbus_action_flag[] = + "/tmp/mce-powerkey-dbus-action.flag"; + +typedef struct { - /* Power key press handling mode */ - mce_gconf_notifier_add(MCE_GCONF_POWERKEY_PATH, - MCE_GCONF_POWERKEY_MODE, - powerkey_gconf_cb, - &powerkey_action_mode_cb_id); + char *destination; + char *object; + char *interface; + char *member; + char *argument; +} pwrkey_dbus_action_t; + +static pwrkey_dbus_action_t pwrkey_dbus_action[2] = { }; + +static gchar *pwrkey_dbus_action1 = 0; +static guint pwrkey_dbus_action1_gconf_id = 0; + +static gchar *pwrkey_dbus_action2 = 0; +static guint pwrkey_dbus_action2_gconf_id = 0; + +static void pwrkey_dbus_action_clear(pwrkey_dbus_action_t *self); +static void pwrkey_dbus_action_reset(pwrkey_dbus_action_t *self, const char *arg); +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, const char *data); +static gchar *pwrkey_dbus_action_to_string(const pwrkey_dbus_action_t *self); +static void pwrkey_dbus_action_sanitize(pwrkey_dbus_action_t *self, const char *arg); +static void pwrkey_dbus_action_execute(size_t index); + +/* ------------------------------------------------------------------------- * + * 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; - mce_gconf_get_int(MCE_GCONF_POWERKEY_MODE, &powerkey_action_mode); +/** [setting] Power key press enable mode */ +static gint pwrkey_stm_enable_mode = PWRKEY_ENABLE_DEFAULT; +static guint pwrkey_stm_enable_mode_gconf_id = 0; - /* Power key display blanking mode */ - mce_gconf_notifier_add(MCE_GCONF_POWERKEY_PATH, - MCE_GCONF_POWERKEY_BLANKING_MODE, - powerkey_gconf_cb, - &powerkey_blanking_mode_cb_id); +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); - mce_gconf_get_int(MCE_GCONF_POWERKEY_BLANKING_MODE, - &powerkey_blanking_mode); +static bool pwrkey_stm_ignore_action (void); +static bool pwrkey_stm_pending_timers (void); - /* Power key press count for proximity sensor override */ - mce_gconf_notifier_add(MCE_GCONF_POWERKEY_PATH, - MCE_GCONF_POWERKEY_PS_OVERRIDE_COUNT, - powerkey_gconf_cb, - &powerkey_ps_override_count_cb_id); +static void pwrkey_stm_rethink_wakelock (void); - mce_gconf_get_int(MCE_GCONF_POWERKEY_PS_OVERRIDE_COUNT, - &powerkey_ps_override_count); +static void pwrkey_stm_store_initial_state (void); +static void pwrkey_stm_terminate (void); + +/* ------------------------------------------------------------------------- * + * DBUS_IPC + * + * handling incoming and outgoing dbus messages + * ------------------------------------------------------------------------- */ - /* Maximum time between power key presses for ps override */ - mce_gconf_notifier_add(MCE_GCONF_POWERKEY_PATH, - MCE_GCONF_POWERKEY_PS_OVERRIDE_TIMEOUT, - powerkey_gconf_cb, - &powerkey_ps_override_timeout_cb_id); +static void pwrkey_dbus_send_signal(const char *sig, const char *arg); - mce_gconf_get_int(MCE_GCONF_POWERKEY_PS_OVERRIDE_TIMEOUT, - &powerkey_ps_override_timeout); +static gboolean pwrkey_dbus_trigger_event_cb(DBusMessage *const req); + +static void pwrkey_dbus_init(void); +static void pwrkey_dbus_quit(void); + +/* ------------------------------------------------------------------------- * + * GCONF_SETTINGS + * + * tracking powerkey related runtime changeable settings + * ------------------------------------------------------------------------- */ + +static gint pwrkey_gconf_sanitize_id = 0; + +static gboolean pwrkey_gconf_sanitize_cb (gpointer aptr); +static void pwrkey_gconf_sanitize_now (void); +static void pwrkey_gconf_sanitize_later (void); +static void pwrkey_gconf_sanitize_cancel (void); + +static void pwrkey_gconf_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); + +static void pwrkey_gconf_init(void); +static void pwrkey_gconf_quit(void); + +/* ------------------------------------------------------------------------- * + * DATAPIPE_HANDLING + * + * reacting to state changes / input from other mce modules + * ------------------------------------------------------------------------- */ +static void pwrkey_datapipes_keypress_cb(gconstpointer const data); + +static void pwrkey_datapipes_init(void); +static void pwrkey_datapipes_quit(void); + +/* ------------------------------------------------------------------------- * + * MODULE_INTEFACE + * ------------------------------------------------------------------------- */ + +gboolean mce_powerkey_init(void); +void mce_powerkey_exit(void); + +/* ========================================================================= * + * MISC_UTIL + * ========================================================================= */ + +/** Get CLOCK_BOOTTIME time stamp in milliseconds + */ +static int64_t +pwrkey_get_boot_tick(void) +{ + int64_t res = 0; + + struct timespec ts; + + if( clock_gettime(CLOCK_BOOTTIME, &ts) == 0 ) { + res = ts.tv_sec; + res *= 1000; + res += ts.tv_nsec / 1000000; + } + + return res; } -/** Remove gconf change notifiers +/** Parse element from comma separated string list */ -static void powerkey_gconf_quit(void) +static char * +pwrkey_get_token(char **ppos) { - /* Power key press handling mode */ - mce_gconf_notifier_remove(powerkey_action_mode_cb_id), - powerkey_action_mode_cb_id = 0; + char *pos = *ppos; + char *beg = pos; - /* Power key press blanking mode */ - mce_gconf_notifier_remove(powerkey_blanking_mode_cb_id), - powerkey_blanking_mode_cb_id = 0; + if( !pos ) + goto cleanup; - /* Power key press blanking mode */ - mce_gconf_notifier_remove(powerkey_ps_override_count_cb_id), - powerkey_ps_override_count_cb_id = 0; + for( ; *pos; ++pos ) { + if( *pos != ',' ) + continue; + *pos++ = 0; + break; + } - /* Power key press blanking mode */ - mce_gconf_notifier_remove(powerkey_ps_override_timeout_cb_id), - powerkey_ps_override_timeout_cb_id = 0; +cleanup: + return *ppos = pos, beg; } -/** Helper for sending powerkey feedback dbus signal +/** Create an empty flag file * - * @param sig name of the signal to send + * @param path Path to the file to create + * + * @return true if file was created, false otherwise */ -static void powerkey_send_feedback_signal(const char *sig) +static bool pwrkey_create_flagfile(const char *path) { - const char *arg = "powerkey"; - 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); + 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; } -/** Get CLOCK_BOOTTIME time stamp in milliseconds +/** Delete a flag file + * + * @param path Path to the file to create + * + * @return true if file was removed, false otherwise */ -static int64_t powerkey_get_boot_tick(void) -{ - int64_t res = 0; - struct timespec ts; +static bool pwrkey_delete_flagfile(const char *path) +{ + bool deleted = false; - if( clock_gettime(CLOCK_BOOTTIME, &ts) == 0 ) { - res = ts.tv_sec; - res *= 1000; - res += ts.tv_nsec / 1000000; - } + if( unlink(path) == 0 ) { + deleted = true; + } - return res; + return deleted; } +/* ========================================================================= * + * PS_OVERRIDE + * ========================================================================= */ + /** Provide an emergency way out from stuck proximity sensor * * If the proximity sensor is dirty/faulty and stuck to "covered" @@ -294,838 +474,1705 @@ static int64_t powerkey_get_boot_tick(void) * allows user to force proximity sensor to "uncovered" state * by rapidly pressing power button several times. */ -static void powerkey_ps_override_evaluate(void) -{ - static int64_t t_last = 0; - static gint count = 0; - - /* If the feature is disabled, just reset the counter */ - if( powerkey_ps_override_count <= 0 || - powerkey_ps_override_timeout <= 0 ) { - t_last = 0, count = 0; - goto EXIT; - } - - cover_state_t proximity_sensor_state = - datapipe_get_gint(proximity_sensor_pipe); - - /* If the sensor is not covered, just reset the counter */ - if( proximity_sensor_state != COVER_CLOSED ) { - t_last = 0, count = 0; - goto EXIT; - } - - int64_t t_now = powerkey_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 + powerkey_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 == powerkey_ps_override_count ) { - mce_log(LL_CRIT, "assuming stuck proximity sensor;" - " faking uncover event"); - execute_datapipe(&proximity_sensor_pipe, - GINT_TO_POINTER(COVER_OPEN), - USE_INDATA, CACHE_INDATA); - t_last = 0, count = 0; - } - else - mce_log(LL_DEBUG, "ps override count = %d", count); +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; + } + + cover_state_t proximity_sensor_state = + datapipe_get_gint(proximity_sensor_pipe); + + /* If the sensor is not covered, just reset the counter */ + if( proximity_sensor_state != COVER_CLOSED ) { + t_last = 0, count = 0; + goto EXIT; + } + + int64_t t_now = pwrkey_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_CRIT, "assuming stuck proximity sensor;" + " faking uncover event"); + execute_datapipe(&proximity_sensor_pipe, + GINT_TO_POINTER(COVER_OPEN), + USE_INDATA, CACHE_INDATA); + t_last = 0, count = 0; + } + else + mce_log(LL_DEBUG, "ps override count = %d", count); EXIT: - return; + return; } -/** Should power key action be ignored predicate - */ -static bool powerkey_ignore_action(void) -{ - /* Assume that power key action should not be ignored */ - bool ignore_powerkey = false; - - alarm_ui_state_t alarm_ui_state = - datapipe_get_gint(alarm_ui_state_pipe); - cover_state_t proximity_sensor_state = - datapipe_get_gint(proximity_sensor_pipe); - call_state_t call_state = - datapipe_get_gint(call_state_pipe); - display_state_t display_state = - datapipe_get_gint(display_state_pipe); - - /* Ignore keypress if the alarm UI is visible */ - switch( alarm_ui_state ) { - case MCE_ALARM_UI_VISIBLE_INT32: - case MCE_ALARM_UI_RINGING_INT32: - mce_log(LL_DEVEL, "[powerkey] ignored due to active alarm"); - ignore_powerkey = true; - powerkey_send_feedback_signal("alarm_ui_feedback_ind"); - break; - - default: - case MCE_ALARM_UI_OFF_INT32: - case MCE_ALARM_UI_INVALID_INT32: - // dontcare - break; - } - - /* Ignore keypress if we have incoming call */ - switch( call_state ) { - case CALL_STATE_RINGING: - mce_log(LL_DEVEL, "[powerkey] ignored due to incoming call"); - ignore_powerkey = true; - powerkey_send_feedback_signal("call_ui_feedback_ind"); - break; - - default: - case CALL_STATE_INVALID: - case CALL_STATE_NONE: - case CALL_STATE_ACTIVE: - case CALL_STATE_SERVICE: - // dontcare - break; - } - - /* Skip rest if already desided to ignore */ - if( ignore_powerkey ) - goto EXIT; - - /* Proximity sensor state vs power key press handling mode */ - switch( powerkey_action_mode ) { - case PWRKEY_ENABLE_NEVER: - mce_log(LL_DEVEL, "[powerkey] ignored due to setting=never"); - ignore_powerkey = true; - goto EXIT; - - case PWRKEY_ENABLE_ALWAYS: - break; - - case PWRKEY_ENABLE_NO_PROXIMITY2: - /* do not ignore if display is not off */ - if( display_state != MCE_DISPLAY_OFF ) - break; - /* fall through */ - default: - case PWRKEY_ENABLE_NO_PROXIMITY: - if( proximity_sensor_state != COVER_CLOSED ) - break; - - mce_log(LL_DEVEL, "[powerkey] ignored due to proximity"); - ignore_powerkey = true; - goto EXIT; - } +/* ========================================================================= * + * ACTION_EXEC + * ========================================================================= */ + +static void +pwrkey_action_shutdown(void) +{ + submode_t submode = mce_get_submode_int32(); + + /* Do not shutdown if the tklock is active */ + if( submode & MCE_TKLOCK_SUBMODE ) + goto EXIT; + + mce_log(LL_DEVEL, "Requesting shutdown"); + mce_dsme_request_normal_shutdown(); EXIT: - return ignore_powerkey; + return; } -/** Blank display according to current powerkey_blanking_mode - */ -static void powerkey_blank_display(void) +static void +pwrkey_action_softoff(void) { - display_state_t request = MCE_DISPLAY_OFF; + submode_t submode = mce_get_submode_int32(); - switch( powerkey_blanking_mode ) { - case PWRKEY_BLANK_TO_LPM: - request = MCE_DISPLAY_LPM_ON; - break; + /* Only soft poweroff if the tklock isn't active */ + if( submode & MCE_TKLOCK_SUBMODE ) + goto EXIT; - case PWRKEY_BLANK_TO_OFF: - default: - break; - } + mce_log(LL_DEVEL, "Requesting soft poweroff"); + mce_dsme_request_soft_poweroff(); - execute_datapipe(&display_state_req_pipe, - GINT_TO_POINTER(request), - USE_INDATA, CACHE_INDATA); +EXIT: + return; } -/** - * Generic logic for key presses - * - * @param action The action to take - * @param dbus_signal A D-Bus signal to send - */ -static void generic_powerkey_handler(poweraction_t action, - gchar *dbus_signal) -{ - mce_log(LL_DEVEL, "action=%d, signal=%s", (int)action, - dbus_signal ?: "n/a"); - - submode_t submode = mce_get_submode_int32(); - - if( powerkey_ignore_action() ) - goto EXIT; - - switch (action) { - case POWER_DISABLED: - break; - - case POWER_POWEROFF: - default: - /* Do not shutdown if the tklock is active - * or if we're in alarm state - */ - if ((submode & MCE_TKLOCK_SUBMODE) == 0) { - mce_log(LL_DEVEL, "Requesting shutdown"); - mce_dsme_request_normal_shutdown(); - } - break; - - case POWER_SOFT_POWEROFF: - /* Only soft poweroff if the tklock isn't active */ - if ((submode & MCE_TKLOCK_SUBMODE) == 0) { - mce_dsme_request_soft_poweroff(); - } - - break; - - case POWER_TKLOCK_LOCK: - /* FIXME: This just happens to be the default place to - * get hit when processing power key events. - * The rest should also be adjusted... */ - - switch( display_state_get() ) { - case MCE_DISPLAY_ON: - case MCE_DISPLAY_DIM: - /* 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 */ - - mce_log(LL_DEVEL, "display -> off, ui -> locked"); - - /* Do the locking before turning display off. - * - * The tklock requests get ignored in act dead - * etc, so we can just blindly request it. - */ - execute_datapipe(&tk_lock_pipe, - GINT_TO_POINTER(LOCK_ON), - USE_INDATA, CACHE_INDATA); - - powerkey_blank_display(); - break; - - default: - case MCE_DISPLAY_UNDEF: - case MCE_DISPLAY_OFF: - case MCE_DISPLAY_LPM_OFF: - case MCE_DISPLAY_LPM_ON: - case MCE_DISPLAY_POWER_UP: - case MCE_DISPLAY_POWER_DOWN: - /* If the display is not fully powered on, always - * request MCE_DISPLAY_ON */ - - mce_log(LL_DEVEL, "display -> on"); - execute_datapipe(&display_state_req_pipe, - GINT_TO_POINTER(MCE_DISPLAY_ON), - USE_INDATA, CACHE_INDATA); - break; - } - break; - - case POWER_TKLOCK_UNLOCK: - /* Request disabling of touchscreen/keypad lock - * if the tklock isn't already inactive - */ - if ((submode & MCE_TKLOCK_SUBMODE) != 0) { - execute_datapipe(&tk_lock_pipe, - GINT_TO_POINTER(LOCK_OFF), - USE_INDATA, CACHE_INDATA); - } - - break; - - case POWER_TKLOCK_BOTH: - /* Request enabling of touchscreen/keypad lock - * if the tklock isn't active, - * and disabling if the tklock is active - */ - if ((submode & MCE_TKLOCK_SUBMODE) == 0) { - execute_datapipe(&tk_lock_pipe, - GINT_TO_POINTER(LOCK_ON), - USE_INDATA, CACHE_INDATA); - } else { - execute_datapipe(&tk_lock_pipe, - GINT_TO_POINTER(LOCK_OFF), - USE_INDATA, CACHE_INDATA); - } - - break; - - case POWER_DBUS_SIGNAL: - /* Send a D-Bus signal */ - // NOTE: configurable signal name -> no introspection - dbus_send(NULL, MCE_REQUEST_PATH, - MCE_REQUEST_IF, dbus_signal, - NULL, - DBUS_TYPE_INVALID); - } +static void +pwrkey_action_tklock(void) +{ + mce_log(LL_DEBUG, "Requesting tklock=on"); + execute_datapipe(&tk_lock_pipe, + GINT_TO_POINTER(LOCK_ON), + USE_INDATA, CACHE_INDATA); +} +static void +pwrkey_action_tkunlock(void) +{ + display_state_t target = datapipe_get_gint(display_state_next_pipe); + + /* Only unlock if we are in/entering fully powered on display state */ + switch( target ) { + case MCE_DISPLAY_ON: + case MCE_DISPLAY_DIM: + break; + + default: + goto EXIT; + } + + mce_log(LL_DEBUG, "Requesting tklock=off"); + execute_datapipe(&tk_lock_pipe, + GINT_TO_POINTER(LOCK_OFF), + USE_INDATA, CACHE_INDATA); EXIT: - return; + return; } -/** - * Timeout callback for double key press - * - * @param data Unused - * @return Always returns FALSE, to disable the timeout - */ -static gboolean doublepress_timeout_cb(gpointer data) +static void +pwrkey_action_blank(void) { - system_state_t system_state = datapipe_get_gint(system_state_pipe); + display_state_t request = MCE_DISPLAY_OFF; - (void)data; + switch( pwrkey_action_blank_mode ) { + case PWRKEY_BLANK_TO_LPM: + request = MCE_DISPLAY_LPM_ON; + break; - doublepress_timeout_cb_id = 0; + case PWRKEY_BLANK_TO_OFF: + default: + break; + } - /* doublepress timer expired without any secondary press; - * thus this was a short press - */ - if (system_state == MCE_STATE_USER) - generic_powerkey_handler(shortpressaction, - shortpresssignal); + execute_datapipe(&display_state_req_pipe, + GINT_TO_POINTER(request), + USE_INDATA, CACHE_INDATA); +} - /* Release wakelock if all timers are inactive */ - powerkey_wakelock_rethink(); +static void +pwrkey_action_unblank(void) +{ + mce_log(LL_DEVEL, "Requesting device_lock=on"); + execute_datapipe(&display_state_req_pipe, + GINT_TO_POINTER(MCE_DISPLAY_ON), + USE_INDATA, CACHE_INDATA); - return FALSE; } -/** - * Cancel doublepress timeout - */ -static void cancel_doublepress_timeout(void) +static void +pwrkey_action_devlock(void) { - /* Remove the timeout source for the [power] double key press handler */ - if (doublepress_timeout_cb_id != 0) { - g_source_remove(doublepress_timeout_cb_id); - doublepress_timeout_cb_id = 0; - } + static const char service[] = "org.nemomobile.lipstick"; + static const char object[] = "/devicelock"; + static const char interface[] = "org.nemomobile.lipstick.devicelock"; + static const char method[] = "setState"; + dbus_int32_t locked = TRUE; + + dbus_send(service, object, interface, method, 0, + DBUS_TYPE_INT32, &locked, + DBUS_TYPE_INVALID); } -/** - * Setup doublepress timeout +static void +pwrkey_action_dbus1(void) +{ + pwrkey_dbus_action_execute(0); +} + +static void +pwrkey_action_dbus2(void) +{ + pwrkey_dbus_action_execute(1); +} + +/* ========================================================================= * + * ACTION_SETS + * ========================================================================= */ + +/** Config string to callback function mapping * - * @return TRUE if the doublepress action was setup, - * FALSE if no action was setup + * 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 gboolean setup_doublepress_timeout(void) -{ - submode_t submode = mce_get_submode_int32(); - gboolean status = FALSE; - - /* Only setup the doublepress timeout when needed */ - if (doublepressaction == POWER_DISABLED) - goto EXIT; - - cancel_doublepress_timeout(); - - /* If the tklock is enabled, but doublepress to unlock is disabled, - * or if the tklock isn't enabled and short press to lock is enabled, - * exit - */ - if (doublepressaction != POWER_DBUS_SIGNAL) { - if ((submode & MCE_TKLOCK_SUBMODE) != 0) { - if ((doublepressaction != POWER_TKLOCK_UNLOCK) && - (doublepressaction != POWER_TKLOCK_BOTH)) - goto EXIT; - } else { - if ((shortpressaction == POWER_TKLOCK_LOCK) || - (shortpressaction == POWER_TKLOCK_BOTH)) - goto EXIT; - } - } - - /* Setup new timeout */ - doublepress_timeout_cb_id = - g_timeout_add(doublepressdelay, doublepress_timeout_cb, NULL); - status = TRUE; +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 = "dbus1", + .func = pwrkey_action_dbus1, + }, + { + .name = "softoff", + .func = pwrkey_action_softoff, + }, + { + .name = "shutdown", + .func = pwrkey_action_shutdown, + }, + + // Direction: OFF->ON + { + .name = "unblank", + .func = pwrkey_action_unblank, + }, + { + .name = "tkunlock", + .func = pwrkey_action_tkunlock, + }, + { + .name = "dbus2", + .func = pwrkey_action_dbus2, + }, +}; + +static void +pwrkey_mask_execute(uint32_t mask) +{ + for( size_t i = 0; i < G_N_ELEMENTS(pwrkey_action_lut); ++i ) { + if( mask & (1u << i) ) { + mce_log(LL_DEBUG, "* exec(%s)", pwrkey_action_lut[i].name); + pwrkey_action_lut[i].func(); + } + } +} + +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: - return status; + free(work); + + return mask; } -/** - * Logic for short key press - */ -static void handle_shortpress(void) +static gchar * +pwrkey_mask_to_names(uint32_t mask) { - cancel_powerkey_timeout(); + 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); +} + +/* ========================================================================= * + * ACTION_TRIGGERING + * ========================================================================= */ - if (doublepress_timeout_cb_id == 0) { - if (setup_doublepress_timeout() == FALSE) - generic_powerkey_handler(shortpressaction, - shortpresssignal); - } else { - cancel_doublepress_timeout(); - generic_powerkey_handler(doublepressaction, - doublepresssignal); - } +static void +pwrkey_actions_do_common(void) +{ + pwrkey_mask_execute(pwrkey_actions_now->mask_common); } -/** - * Logic for long key press - * - * @return TRUE on success, FALSE on failure - */ -static gboolean handle_longpress(void) -{ - system_state_t state = datapipe_get_gint(system_state_pipe); - alarm_ui_state_t alarm_ui_state = - datapipe_get_gint(alarm_ui_state_pipe); - submode_t submode = mce_get_submode_int32(); - gboolean status = TRUE; - - /* Ignore keypress if the alarm UI is visible */ - if ((alarm_ui_state == MCE_ALARM_UI_VISIBLE_INT32) || - (alarm_ui_state == MCE_ALARM_UI_RINGING_INT32)) - goto EXIT; - - /* Ignore if we're already shutting down/rebooting */ - switch (state) { - case MCE_STATE_SHUTDOWN: - case MCE_STATE_REBOOT: - status = FALSE; - break; - - case MCE_STATE_ACTDEAD: - /* activate power on led pattern and power up to user mode*/ - mce_log(LL_DEBUG, "activate MCE_LED_PATTERN_POWER_ON"); - execute_datapipe_output_triggers(&led_pattern_activate_pipe, - MCE_LED_PATTERN_POWER_ON, - USE_INDATA); - mce_dsme_request_powerup(); - break; - - case MCE_STATE_USER: - /* If softoff is enabled, wake up - * Otherwise, perform long press action - */ - if ((submode & MCE_SOFTOFF_SUBMODE)) { - mce_dsme_request_soft_poweron(); - } else { - generic_powerkey_handler(longpressaction, - longpresssignal); - } - - break; - - default: - /* If no special cases are needed, - * just do a regular shutdown - */ - mce_log(LL_WARN, - "Requesting shutdown; state: %d", - state); - - mce_dsme_request_normal_shutdown(); - break; - } +static void +pwrkey_actions_do_single_press(void) +{ + pwrkey_mask_execute(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) +{ + pwrkey_mask_execute(pwrkey_actions_now->mask_double); +} + +static void +pwrkey_actions_do_long_press(void) +{ + system_state_t state = datapipe_get_gint(system_state_pipe); + submode_t submode = mce_get_submode_int32(); + + /* The action configuration applies only in the USER mode */ + + switch( state ) { + case MCE_STATE_SHUTDOWN: + case MCE_STATE_REBOOT: + /* Ignore if we're already shutting down/rebooting */ + break; + + case MCE_STATE_ACTDEAD: + /* Activate power on led pattern and power up to user mode*/ + mce_log(LL_DEBUG, "activate MCE_LED_PATTERN_POWER_ON"); + execute_datapipe_output_triggers(&led_pattern_activate_pipe, + MCE_LED_PATTERN_POWER_ON, + USE_INDATA); + mce_dsme_request_powerup(); + break; + + case MCE_STATE_USER: + if( submode & MCE_SOFTOFF_SUBMODE ) { + /* Wake up from softoff */ + mce_dsme_request_soft_poweron(); + } else { + /* Apply configured actions */ + pwrkey_mask_execute(pwrkey_actions_now->mask_long); + } + break; + + default: + /* Default to powering off */ + mce_log(LL_WARN, "Requesting shutdown; state: %d",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; + + auto void update(gchar **prev, gchar *curr) + { + if( !eq(*prev, curr) ) + changed = true, g_free(*prev), *prev = curr, curr = 0; + g_free(curr); + } + + update(names_single, + pwrkey_mask_to_names(self->mask_single | self->mask_common)); + + update(names_double, + pwrkey_mask_to_names(self->mask_double | self->mask_common)); + + update(names_long, + pwrkey_mask_to_names(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) +{ + /* 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); + + /* 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; +} + +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 status; + + return FALSE; } -/** - * Timeout callback for long key press - * - * @param data Unused - * @return Always returns FALSE, to disable the timeout - */ -static gboolean powerkey_timeout_cb(gpointer data) +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) { - (void)data; + 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; - powerkey_timeout_cb_id = 0; + pwrkey_double_press_timer_id = 0; - handle_longpress(); + pwrkey_stm_double_press_timeout(); - /* Release wakelock if all timers are inactive */ - powerkey_wakelock_rethink(); + pwrkey_stm_rethink_wakelock(); - return FALSE; +EXIT: + + return FALSE; } -/** - * Cancel powerkey timeout - */ -static void cancel_powerkey_timeout(void) +static bool +pwrkey_double_press_timer_pending(void) { - /* Remove the timeout source for the [power] long key press handler */ - if (powerkey_timeout_cb_id != 0) { - g_source_remove(powerkey_timeout_cb_id); - powerkey_timeout_cb_id = 0; - } + return pwrkey_double_press_timer_id != 0; } -/** - * Setup powerkey timeout - */ -static void setup_powerkey_timeout(gint powerkeydelay) +static bool +pwrkey_double_press_timer_cancel(void) { - cancel_powerkey_timeout(); + 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(); - /* Setup new timeout */ - powerkey_timeout_cb_id = - g_timeout_add(powerkeydelay, powerkey_timeout_cb, NULL); + pwrkey_double_press_timer_id = g_timeout_add(pwrkey_double_press_delay, + pwrkey_double_press_timer_cb, 0); } -/** - * D-Bus callback for powerkey event triggering +/* ========================================================================= * + * 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, const char *arg) +{ + pwrkey_dbus_action_clear(self); + self->argument = strdup(arg); +} + +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, const char *data) +{ + char *tmp = 0; + char *pos = 0; + char *arg = 0; + + pwrkey_dbus_action_clear(self); + + if( empty(data) ) + goto cleanup; + + pos = tmp = strdup(data); + 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, const char *arg) +{ + if( !pwrkey_dbus_action_is_methodcall(self) && + !pwrkey_dbus_action_is_signal(self) ) { + pwrkey_dbus_action_reset(self, arg); + } +} + +static bool +pwrkey_dbus_action_configure(size_t action_id, bool force_reset) +{ + bool changed = false; + + gchar **cfg = 0; + const char *def = 0; + gchar *use = 0; + + if( action_id >= G_N_ELEMENTS(pwrkey_dbus_action) ) + goto cleanup; + + switch( action_id ) { + case 0: + cfg = &pwrkey_dbus_action1; + def = DEFAULT_POWERKEY_DBUS_ACTION1; + break; + case 1: + cfg = &pwrkey_dbus_action2; + def = DEFAULT_POWERKEY_DBUS_ACTION2; + break; + default: + goto cleanup; + } + + if( !cfg || !def ) + goto cleanup; + + pwrkey_dbus_action_t *action = pwrkey_dbus_action + action_id; + + if( force_reset ) { + pwrkey_dbus_action_reset(action, def); + } + else { + pwrkey_dbus_action_parse(action, *cfg); + pwrkey_dbus_action_sanitize(action, def); + } + + use = pwrkey_dbus_action_to_string(action); + + if( !eq(*cfg, use) ) { + g_free(*cfg), *cfg = use, use = 0; + changed = true; + } + +cleanup: + + g_free(use); + + return changed; +} + +static void +pwrkey_dbus_action_execute(size_t action_id) +{ + bool flag_created = false; + + if( action_id >= G_N_ELEMENTS(pwrkey_dbus_action) ) + goto cleanup; + + 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("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; +} + +/* ========================================================================= * + * STATE_MACHINE + * ========================================================================= */ + +/** Check if we need to hold a wakelock for power key handling * - * @param msg D-Bus message - * @return TRUE on success, FALSE on failure + * Wakelock is held if there are pending timers. */ -static gboolean trigger_powerkey_event_req_dbus_cb(DBusMessage *const msg) -{ - dbus_bool_t no_reply = dbus_message_get_no_reply(msg); - DBusMessageIter iter; - dbus_uint32_t uintval; - dbus_bool_t boolval; - gint argcount = 0; - gint argtype; - gboolean status = FALSE; - DBusError error; - - /* Register error channel */ - dbus_error_init(&error); - - mce_log(LL_DEVEL, "Received [power] button trigger request from %s", - mce_dbus_get_message_sender_ident(msg)); - - if (dbus_message_iter_init(msg, &iter) == FALSE) { - // XXX: should we return an error instead? - mce_log(LL_ERR, - "Failed to initialise D-Bus message iterator; " - "message has no arguments"); - goto EXIT; - } - - argtype = dbus_message_iter_get_arg_type(&iter); - argcount++; - - switch (argtype) { - case DBUS_TYPE_BOOLEAN: - dbus_message_iter_get_basic(&iter, &boolval); - uintval = (boolval == TRUE) ? 1 : 0; - break; - - case DBUS_TYPE_UINT32: - dbus_message_iter_get_basic(&iter, &uintval); - - if (uintval > 2) { - mce_log(LL_ERR, - "Incorrect powerkey event passed to %s.%s; " - "ignoring request", - MCE_REQUEST_IF, MCE_TRIGGER_POWERKEY_EVENT_REQ); - goto EXIT; - } - - break; - - default: - mce_log(LL_ERR, - "Argument %d passed to %s.%s has incorrect type", - argcount, - MCE_REQUEST_IF, MCE_TRIGGER_POWERKEY_EVENT_REQ); - goto EXIT; - } - - while (dbus_message_iter_next(&iter) == TRUE) - argcount++; - - if (argcount > 1) { - mce_log(LL_WARN, - "Too many arguments passed to %s.%s; " - "got %d, expected %d -- ignoring extra arguments", - MCE_REQUEST_IF, MCE_TRIGGER_POWERKEY_EVENT_REQ, - argcount, 1); - } - - mce_log(LL_DEBUG, "[power] button event trigger value: %d", uintval); - - cancel_powerkey_timeout(); - cancel_doublepress_timeout(); - - switch (uintval) { - default: - case 0: - /* short press */ - generic_powerkey_handler(shortpressaction, - shortpresssignal); - break; - - case 1: - /* long press */ - handle_longpress(); - break; - - case 2: - /* double press */ - generic_powerkey_handler(doublepressaction, - doublepresssignal); - break; - } - - if (no_reply == FALSE) { - DBusMessage *reply = dbus_new_method_reply(msg); - - status = dbus_send_message(reply); - } else { - status = TRUE; - } +static void +pwrkey_stm_rethink_wakelock(void) +{ +#ifdef ENABLE_WAKELOCKS + static bool have_lock = false; + + bool want_lock = pwrkey_stm_pending_timers();; + + 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 status; + return; +#endif } -/** - * Datapipe trigger for the [power] key - * - * @param data A pointer to the input_event struct +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 */ + if( !pwrkey_stm_ignore_action() ) { + 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 state */ + + pwrkey_stm_display_state = datapipe_get_gint(display_state_pipe); + + /* 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); +} + +/** Should power key action be ignored predicate */ -static void powerkey_trigger(gconstpointer const data) -{ - system_state_t system_state = datapipe_get_gint(system_state_pipe); - submode_t submode = mce_get_submode_int32(); - struct input_event const *const *evp; - struct input_event const *ev; - - /* Don't dereference until we know it's safe */ - if (data == NULL) - goto EXIT; - - evp = data; - ev = *evp; - - if ((ev != NULL) && (ev->code == KEY_POWER)) { - /* If set, the [power] key was pressed */ - if (ev->value == 1) { - mce_log(LL_DEVEL, "[powerkey] pressed"); - - /* Are we waiting for a doublepress? */ - if (doublepress_timeout_cb_id != 0) { - handle_shortpress(); - } else if ((system_state == MCE_STATE_ACTDEAD) || - ((submode & MCE_SOFTOFF_SUBMODE) != 0)) { - /* Setup new timeout */ - - /* Shorter delay for startup - * than for shutdown - */ - setup_powerkey_timeout(mediumdelay); - } else { - setup_powerkey_timeout(longdelay); - } - } else if (ev->value == 0) { - mce_log(LL_DEVEL, "[powerkey] released"); - - /* 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 */ - powerkey_ps_override_evaluate(); - - /* Short key press */ - if (powerkey_timeout_cb_id != 0) { - handle_shortpress(); - } - } - - /* Acquire/release a wakelock depending on whether - * there are active powerkey timers or not */ - powerkey_wakelock_rethink(); - } +static bool +pwrkey_stm_ignore_action(void) +{ + /* Assume that power key action should not be ignored */ + bool ignore_powerkey = false; + + alarm_ui_state_t alarm_ui_state = + datapipe_get_gint(alarm_ui_state_pipe); + + cover_state_t proximity_sensor_state = + datapipe_get_gint(proximity_sensor_pipe); + + call_state_t call_state = + datapipe_get_gint(call_state_pipe); + + /* If alarm dialog is up, power key is used for snoozing */ + switch( alarm_ui_state ) { + case MCE_ALARM_UI_VISIBLE_INT32: + case MCE_ALARM_UI_RINGING_INT32: + mce_log(LL_DEVEL, "[powerkey] ignored due to active alarm"); + ignore_powerkey = true; + pwrkey_dbus_send_signal("alarm_ui_feedback_ind", "powerkey"); + break; + + default: + case MCE_ALARM_UI_OFF_INT32: + case MCE_ALARM_UI_INVALID_INT32: + // dontcare + break; + } + + /* During incoming call power key is used to silence ringing */ + switch( call_state ) { + case CALL_STATE_RINGING: + mce_log(LL_DEVEL, "[powerkey] ignored due to incoming call"); + ignore_powerkey = true; + pwrkey_dbus_send_signal("call_ui_feedback_ind", "powerkey"); + break; + + default: + case CALL_STATE_INVALID: + case CALL_STATE_NONE: + case CALL_STATE_ACTIVE: + case CALL_STATE_SERVICE: + // dontcare + break; + } + + /* Skip rest if already desided to ignore */ + if( ignore_powerkey ) + 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"); + ignore_powerkey = true; + 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( proximity_sensor_state != COVER_CLOSED ) + break; + + mce_log(LL_DEVEL, "[powerkey] ignored due to proximity"); + ignore_powerkey = true; + goto EXIT; + } EXIT: - return; + return ignore_powerkey; +} + +/* ========================================================================= * + * 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); } /** - * Parse the [power] action string + * D-Bus callback for powerkey event triggering * - * @todo Implement this using string to enum mappings instead, - * to allow for better debugging messages and a generic parser + * @param msg D-Bus message * - * @param string The string to parse - * @param dbus_signal A D-Bus signal to send - * @param action A pointer to the variable to store the action in - * @return TRUE if the string contained a valid action, - * FALSE if the action was invalid + * @return TRUE */ -static gboolean parse_action(char *string, - char **dbus_signal, - poweraction_t *action) -{ - gboolean status = FALSE; - - if (!strcmp(string, POWER_DISABLED_STR)) { - *action = POWER_DISABLED; - } else if (!strcmp(string, POWER_MENU_STR)) { - *action = POWER_MENU; - } else if (!strcmp(string, POWER_POWEROFF_STR)) { - *action = POWER_POWEROFF; - } else if (!strcmp(string, POWER_SOFT_POWEROFF_STR)) { - *action = POWER_SOFT_POWEROFF; - } else if (!strcmp(string, POWER_TKLOCK_LOCK_STR)) { - *action = POWER_TKLOCK_LOCK; - } else if (!strcmp(string, POWER_TKLOCK_UNLOCK_STR)) { - *action = POWER_TKLOCK_UNLOCK; - } else if (!strcmp(string, POWER_TKLOCK_BOTH_STR)) { - *action = POWER_TKLOCK_BOTH; - } else if (!strncmp(string, - POWER_DBUS_SIGNAL_STR, - strlen(POWER_DBUS_SIGNAL_STR))) { - gchar *tmp = string + strlen(POWER_DBUS_SIGNAL_STR); - - if (strlen(tmp) == 0) { - mce_log(LL_ERR, - "No signal name provided to action " - "`dbus-signal-'; ignoring"); - goto EXIT; - } - - *action = POWER_DBUS_SIGNAL; - *dbus_signal = g_strdup(tmp); - } else { - mce_log(LL_WARN, - "Unknown [power] action; " - "using default"); - goto EXIT; - } - - status = 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(); + + if( pwrkey_stm_ignore_action() ) + goto EXIT; + + switch (act) { + default: + case 0: + /* short press */ + pwrkey_actions_do_common(); + pwrkey_actions_do_single_press(); + break; + + case 1: + /* long press */ + pwrkey_actions_do_long_press(); + break; + + case 2: + /* double press */ + pwrkey_actions_do_common(); + pwrkey_actions_do_double_press(); + break; + } EXIT: - return status; + 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; } /** Array of dbus message handlers */ -static mce_dbus_handler_t powerkey_dbus_handlers[] = -{ - /* signals - outbound (for Introspect purposes only) */ - { - .interface = MCE_SIGNAL_IF, - .name = "alarm_ui_feedback_ind", - .type = DBUS_MESSAGE_TYPE_SIGNAL, - .args = - " \n" - }, - { - .interface = MCE_SIGNAL_IF, - .name = "call_ui_feedback_ind", - .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 = trigger_powerkey_event_req_dbus_cb, - .args = - " \n" - }, - /* sentinel */ - { - .interface = 0 - } +static mce_dbus_handler_t pwrkey_dbus_handlers[] = +{ + /* signals - outbound (for Introspect purposes only) */ + { + .interface = MCE_SIGNAL_IF, + .name = "alarm_ui_feedback_ind", + .type = DBUS_MESSAGE_TYPE_SIGNAL, + .args = + " \n" + }, + { + .interface = MCE_SIGNAL_IF, + .name = "call_ui_feedback_ind", + .type = DBUS_MESSAGE_TYPE_SIGNAL, + .args = + " \n" + }, + { + .interface = MCE_SIGNAL_IF, + .name = "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" + }, + /* sentinel */ + { + .interface = 0 + } }; /** Add dbus handlers */ -static void mce_powerkey_init_dbus(void) +static void +pwrkey_dbus_init(void) { - mce_dbus_handler_register_array(powerkey_dbus_handlers); + mce_dbus_handler_register_array(pwrkey_dbus_handlers); } /** Remove dbus handlers */ -static void mce_powerkey_quit_dbus(void) +static void +pwrkey_dbus_quit(void) { - mce_dbus_handler_unregister_array(powerkey_dbus_handlers); + mce_dbus_handler_unregister_array(pwrkey_dbus_handlers); } +/* ========================================================================= * + * GCONF_SETTINGS + * ========================================================================= */ + +static void +pwrkey_gconf_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_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON, + pwrkey_actions_single_on); + + mce_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON, + pwrkey_actions_double_on); + + mce_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_LONG_ON, + pwrkey_actions_long_on); + } + + if( off_changed ) { + mce_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF, + pwrkey_actions_single_off); + + mce_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF, + pwrkey_actions_double_off); + + mce_gconf_set_string(MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF, + pwrkey_actions_long_off); + } +} + +static void +pwrkey_gconf_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); + } + if( pwrkey_dbus_action_configure(0, force_reset) ) { + mce_gconf_set_string(MCE_GCONF_POWERKEY_DBUS_ACTION1, + pwrkey_dbus_action1); + } + if( pwrkey_dbus_action_configure(1, force_reset) ) { + mce_gconf_set_string(MCE_GCONF_POWERKEY_DBUS_ACTION2, + pwrkey_dbus_action2); + } +} + +static void +pwrkey_gconf_sanitize_now(void) +{ + pwrkey_gconf_sanitize_action_masks(); + pwrkey_gconf_sanitize_dbus_actions(); +} + +static gboolean pwrkey_gconf_sanitize_cb(gpointer aptr) +{ + (void)aptr; + + if( !pwrkey_gconf_sanitize_id ) + goto EXIT; + + pwrkey_gconf_sanitize_id = 0; + + pwrkey_gconf_sanitize_now(); + +EXIT: + return FALSE; +} + +static void pwrkey_gconf_sanitize_later(void) +{ + if( !pwrkey_gconf_sanitize_id ) + pwrkey_gconf_sanitize_id = g_idle_add(pwrkey_gconf_sanitize_cb, 0); +} + +static void pwrkey_gconf_sanitize_cancel(void) +{ + if( pwrkey_gconf_sanitize_id ) { + g_source_remove(pwrkey_gconf_sanitize_id), + pwrkey_gconf_sanitize_id = 0; + } +} + +/** GConf callback for powerkey related settings + * + * @param gcc (not used) + * @param id Connection ID from gconf_client_notify_add() + * @param entry The modified GConf entry + * @param data (not used) + */ +static void +pwrkey_gconf_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_gconf_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_gconf_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_gconf_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_gconf_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_gconf_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_gconf_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_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_actions_double_on_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_actions_long_on_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_actions_single_off_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_actions_double_off_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_actions_long_off_gconf_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_gconf_sanitize_later(); + } + } + else if( id == pwrkey_dbus_action1_gconf_id ) { + const char *val = gconf_value_get_string(gcv); + if( !eq(pwrkey_dbus_action1, val) ) { + mce_log(LL_NOTICE, "pwrkey_dbus_action1: '%s' -> '%s'", + pwrkey_dbus_action1, val); + g_free(pwrkey_dbus_action1); + pwrkey_dbus_action1 = g_strdup(val); + pwrkey_gconf_sanitize_later(); + } + } + else if( id == pwrkey_dbus_action2_gconf_id ) { + const char *val = gconf_value_get_string(gcv); + if( !eq(pwrkey_dbus_action2, val) ) { + mce_log(LL_NOTICE, "pwrkey_dbus_action2: '%s' -> '%s'", + pwrkey_dbus_action2, val); + g_free(pwrkey_dbus_action2); + pwrkey_dbus_action2 = g_strdup(val); + pwrkey_gconf_sanitize_later(); + } + } + else { + mce_log(LL_WARN, "Spurious GConf value received; confused!"); + } + +EXIT: + + return; +} + +/** Get gconf values and add change notifiers + */ +static void +pwrkey_gconf_init(void) +{ + /* Power key press handling mode */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_MODE, + &pwrkey_stm_enable_mode, -1, + pwrkey_gconf_cb, + &pwrkey_stm_enable_mode_gconf_id); + + /* Power key display blanking mode */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_BLANKING_MODE, + &pwrkey_action_blank_mode, -1, + pwrkey_gconf_cb, + &pwrkey_action_blank_mode_gconf_id); + + /* Power key press count for proximity sensor override */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_PS_OVERRIDE_COUNT, + &pwrkey_ps_override_count, -1, + pwrkey_gconf_cb, + &pwrkey_ps_override_count_gconf_id); + + /* Maximum time between power key presses for ps override */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_PS_OVERRIDE_TIMEOUT, + &pwrkey_ps_override_timeout, -1, + pwrkey_gconf_cb, + &pwrkey_ps_override_timeout_gconf_id); + + /* Delay for waiting long press */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_LONG_PRESS_DELAY, + &pwrkey_long_press_delay, -1, + pwrkey_gconf_cb, + &pwrkey_long_press_delay_gconf_id); + + /* Delay for waiting double press */ + mce_gconf_track_int(MCE_GCONF_POWERKEY_DOUBLE_PRESS_DELAY, + &pwrkey_double_press_delay, -1, + pwrkey_gconf_cb, + &pwrkey_double_press_delay_gconf_id); + + /* Action sets */ + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON, + &pwrkey_actions_single_on, + DEFAULT_POWERKEY_ACTIONS_SINGLE_ON, + pwrkey_gconf_cb, + &pwrkey_actions_single_on_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON, + &pwrkey_actions_double_on, + DEFAULT_POWERKEY_ACTIONS_DOUBLE_ON, + pwrkey_gconf_cb, + &pwrkey_actions_double_on_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_LONG_ON, + &pwrkey_actions_long_on, + DEFAULT_POWERKEY_ACTIONS_LONG_ON, + pwrkey_gconf_cb, + &pwrkey_actions_long_on_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF, + &pwrkey_actions_single_off, + DEFAULT_POWERKEY_ACTIONS_SINGLE_OFF, + pwrkey_gconf_cb, + &pwrkey_actions_single_off_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF, + &pwrkey_actions_double_off, + DEFAULT_POWERKEY_ACTIONS_DOUBLE_OFF, + pwrkey_gconf_cb, + &pwrkey_actions_double_off_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF, + &pwrkey_actions_long_off, + DEFAULT_POWERKEY_ACTIONS_LONG_OFF, + pwrkey_gconf_cb, + &pwrkey_actions_long_off_gconf_id); + + /* D-Bus actions */ + + mce_gconf_track_string(MCE_GCONF_POWERKEY_DBUS_ACTION1, + &pwrkey_dbus_action1, + DEFAULT_POWERKEY_DBUS_ACTION1, + pwrkey_gconf_cb, + &pwrkey_dbus_action1_gconf_id); + + mce_gconf_track_string(MCE_GCONF_POWERKEY_DBUS_ACTION2, + &pwrkey_dbus_action2, + DEFAULT_POWERKEY_DBUS_ACTION2, + pwrkey_gconf_cb, + &pwrkey_dbus_action2_gconf_id); + + /* Apply sanity checks */ + + pwrkey_gconf_sanitize_now(); +} + +/** Remove gconf change notifiers + */ +static void +pwrkey_gconf_quit(void) +{ + /* Power key press handling mode */ + mce_gconf_notifier_remove(pwrkey_stm_enable_mode_gconf_id), + pwrkey_stm_enable_mode_gconf_id = 0; + + /* Power key press blanking mode */ + mce_gconf_notifier_remove(pwrkey_action_blank_mode_gconf_id), + pwrkey_action_blank_mode_gconf_id = 0; + + /* Power key press blanking mode */ + mce_gconf_notifier_remove(pwrkey_ps_override_count_gconf_id), + pwrkey_ps_override_count_gconf_id = 0; + + /* Power key press blanking mode */ + mce_gconf_notifier_remove(pwrkey_ps_override_timeout_gconf_id), + pwrkey_ps_override_timeout_gconf_id = 0; + + /* Action sets */ + + mce_gconf_notifier_remove(pwrkey_actions_single_on_gconf_id), + pwrkey_actions_single_on_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_actions_double_on_gconf_id), + pwrkey_actions_double_on_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_actions_long_on_gconf_id), + pwrkey_actions_long_on_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_actions_single_off_gconf_id), + pwrkey_actions_single_off_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_actions_double_off_gconf_id), + pwrkey_actions_double_off_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_actions_long_off_gconf_id), + pwrkey_actions_long_off_gconf_id = 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;; + + /* Cancel pending delayed gconf setting sanitizing */ + pwrkey_gconf_sanitize_cancel(); + + /* D-Bus actions */ + + mce_gconf_notifier_remove(pwrkey_dbus_action1_gconf_id), + pwrkey_dbus_action1_gconf_id = 0; + + mce_gconf_notifier_remove(pwrkey_dbus_action2_gconf_id), + pwrkey_dbus_action2_gconf_id = 0; + + g_free(pwrkey_dbus_action1), + pwrkey_dbus_action1 = 0;; + + g_free(pwrkey_dbus_action2), + pwrkey_dbus_action2 = 0;; +} + +/* ========================================================================= * + * DATAPIPE_HANDLING + * ========================================================================= */ + /** - * Init function for the powerkey component + * Datapipe trigger for the [power] key * - * @return TRUE on success, FALSE on failure + * @param data A pointer to the input_event struct */ -gboolean mce_powerkey_init(void) +static void +pwrkey_datapipes_keypress_cb(gconstpointer const data) { - gboolean status = FALSE; - gchar *tmp = NULL; + const struct input_event * const *evp; + const struct input_event *ev; + + if( !(evp = data) ) + goto EXIT; + + if( !(ev = *evp) ) + goto EXIT; + + if( ev->type != EV_KEY ) + goto EXIT; - /* Append triggers/filters to datapipes */ - append_input_trigger_to_datapipe(&keypress_pipe, - powerkey_trigger); + if( ev->code != KEY_POWER ) + goto EXIT; - /* Add dbus handlers */ - mce_powerkey_init_dbus(); + if( ev->value == 1 ) { + /* 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(); - /* Get configuration options */ - longdelay = mce_conf_get_int(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_LONG_DELAY, - DEFAULT_POWER_LONG_DELAY); - mediumdelay = mce_conf_get_int(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_MEDIUM_DELAY, - DEFAULT_POWER_MEDIUM_DELAY); - tmp = mce_conf_get_string(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_SHORT_ACTION, - ""); + /* Power key pressed */ + pwrkey_stm_powerkey_pressed(); + } + else if( ev->value == 0 ) { + /* Power key released */ + pwrkey_stm_powerkey_released(); - /* Since we've set a default, error handling is unnecessary */ - (void)parse_action(tmp, &shortpresssignal, &shortpressaction); - g_free(tmp); + } - tmp = mce_conf_get_string(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_LONG_ACTION, - ""); + pwrkey_stm_rethink_wakelock(); + +EXIT: + return; +} + +/** Append triggers/filters to datapipes + */ +static void +pwrkey_datapipes_init(void) +{ + append_input_trigger_to_datapipe(&keypress_pipe, + pwrkey_datapipes_keypress_cb); +} - /* Since we've set a default, error handling is unnecessary */ - (void)parse_action(tmp, &longpresssignal, &longpressaction); - g_free(tmp); +/** Remove triggers/filters from datapipes + */ +static void +pwrkey_datapipes_quit(void) +{ + remove_input_trigger_from_datapipe(&keypress_pipe, + pwrkey_datapipes_keypress_cb); +} - doublepressdelay = mce_conf_get_int(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_DOUBLE_DELAY, - DEFAULT_POWER_DOUBLE_DELAY); - tmp = mce_conf_get_string(MCE_CONF_POWERKEY_GROUP, - MCE_CONF_POWERKEY_DOUBLE_ACTION, - ""); +/* ========================================================================= * + * MODULE_INTEFACE + * ========================================================================= */ - /* Since we've set a default, error handling is unnecessary */ - (void)parse_action(tmp, &doublepresssignal, &doublepressaction); - g_free(tmp); +/** + * Init function for the powerkey component + * + * @return TRUE on success, FALSE on failure + */ +gboolean mce_powerkey_init(void) +{ + pwrkey_datapipes_init(); - /* Setup gconf tracking */ - powerkey_gconf_init(); + pwrkey_dbus_init(); - status = TRUE; + pwrkey_gconf_init(); - return status; + return TRUE; } /** @@ -1135,26 +2182,14 @@ gboolean mce_powerkey_init(void) */ void mce_powerkey_exit(void) { - /* Remove D-Bus handlers */ - mce_powerkey_quit_dbus(); - - /* Remove gconf tracking */ - powerkey_gconf_quit(); - - /* Remove triggers/filters from datapipes */ - remove_input_trigger_from_datapipe(&keypress_pipe, - powerkey_trigger); + pwrkey_dbus_quit(); - /* Remove all timer sources */ - cancel_powerkey_timeout(); - cancel_doublepress_timeout(); + pwrkey_gconf_quit(); - g_free(doublepresssignal); - g_free(longpresssignal); - g_free(shortpresssignal); + pwrkey_datapipes_quit(); - /* Release wakelock */ - powerkey_wakelock_rethink(); + /* Remove all timer sources & release wakelock */ + pwrkey_stm_terminate(); - return; + return; } diff --git a/powerkey.dot b/powerkey.dot new file mode 100644 index 0000000..fdd821b --- /dev/null +++ b/powerkey.dot @@ -0,0 +1,39 @@ +/* -*- mode: c -*- */ + +/* Extra documentation for state transitions made while + * handling power key presses. + * + * To create PNG image, execute: + * dot -Tpng powerkey.dot -o powerkey.png + */ + +digraph display_state_machine { + fontsize=10; + label = "MCE POWERKEY STATE MACHINE"; + nodesep=0.3; + ranksep=0.2; + node[fontsize=6]; + edge[fontsize=6]; + edge[arrowsize=0.3]; + node[style=filled,fillcolor=skyblue,shape=box]; + node[width=0.00001]; + node[height=0.00001]; + + node[fillcolor=yellow]; + + IDLE; + PRESSED; + RELEASED; + + node[shape=oval,fillcolor=pink]; + + IDLE -> IDLE [label=" released"]; + IDLE -> PRESSED [label=" pressed:\l start long press timer\l"]; + + PRESSED -> IDLE [label=" long press timeout:\l execute long press actions\l"]; + + PRESSED -> RELEASED [label=" released:\l stop long press timer\l execute common actions\l start double press timer\l"]; + + RELEASED -> IDLE [label=" double press timeout:\l execute single press actions\l"]; + RELEASED -> IDLE [label=" pressed:\l stop double press timer\l execute double press actions\l"]; +} diff --git a/powerkey.h b/powerkey.h index e531367..af8fe75 100644 --- a/powerkey.h +++ b/powerkey.h @@ -26,137 +26,108 @@ /** Path to the GConf settings for the powerkey module */ # define MCE_GCONF_POWERKEY_PATH "/system/osso/dsm/powerkey" -/** Path to the powerkey mode GConf setting */ -# define MCE_GCONF_POWERKEY_MODE MCE_GCONF_POWERKEY_PATH "/mode" +/** Powerkey mode setting */ +# define MCE_GCONF_POWERKEY_MODE MCE_GCONF_POWERKEY_PATH "/mode" -/** Path to the powerkey blanking mode GConf setting */ -# define MCE_GCONF_POWERKEY_BLANKING_MODE MCE_GCONF_POWERKEY_PATH "/blanking_mode" +/** Powerkey blanking mode setting */ +# define MCE_GCONF_POWERKEY_BLANKING_MODE MCE_GCONF_POWERKEY_PATH "/blanking_mode" /** Powerkey press count for proximity sensor override */ -# define MCE_GCONF_POWERKEY_PS_OVERRIDE_COUNT MCE_GCONF_POWERKEY_PATH "/ps_override_count" +# define MCE_GCONF_POWERKEY_PS_OVERRIDE_COUNT MCE_GCONF_POWERKEY_PATH "/ps_override_count" /** Maximum delay between powerkey presses for ps override */ # define MCE_GCONF_POWERKEY_PS_OVERRIDE_TIMEOUT MCE_GCONF_POWERKEY_PATH "/ps_override_timeout" -/** Power key action enable modes */ -typedef enum -{ - /** Power key actions disabled */ - PWRKEY_ENABLE_NEVER, - - /** Power key actions always enabled */ - PWRKEY_ENABLE_ALWAYS, - - /** Power key actions enabled if PS is not covered */ - PWRKEY_ENABLE_NO_PROXIMITY, - - /** Power key actions enabled if PS is not covered or display is on */ - PWRKEY_ENABLE_NO_PROXIMITY2, - - PWRKEY_ENABLE_DEFAULT = PWRKEY_ENABLE_ALWAYS, -} pwrkey_mode_t; - -typedef enum -{ - /** Pressing power key turns display off */ - PWRKEY_BLANK_TO_OFF, - - /** Pressing power key puts display to lpm state */ - PWRKEY_BLANK_TO_LPM, -} pwrkey_blanking_mode_t; - -/** Configuration value used for the disabled policy */ -#define POWER_DISABLED_STR "disabled" +/** Long press timeout setting */ +# define MCE_GCONF_POWERKEY_LONG_PRESS_DELAY MCE_GCONF_POWERKEY_PATH "/long_press_delay" -/** Configuration value used for the device menu policy */ -#define POWER_MENU_STR "menu" +/** Double press timeout setting */ +# define MCE_GCONF_POWERKEY_DOUBLE_PRESS_DELAY MCE_GCONF_POWERKEY_PATH "/double_press_delay" -/** Configuration value used for poweroff */ -#define POWER_POWEROFF_STR "poweroff" +/** Setting for single press actions from display on */ +# define MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON MCE_GCONF_POWERKEY_PATH "/actions_single_on" -/** Configuration value used for soft poweroff */ -#define POWER_SOFT_POWEROFF_STR "softpoweroff" +/** Setting for double press actions from display on */ +# define MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON MCE_GCONF_POWERKEY_PATH "/actions_double_on" -/** Configuration value used for tklock lock */ -#define POWER_TKLOCK_UNLOCK_STR "tklock-unlock" +/** Setting for long press actions from display on */ +# define MCE_GCONF_POWERKEY_ACTIONS_LONG_ON MCE_GCONF_POWERKEY_PATH "/actions_long_on" -/** Configuration value used for tklock unlock */ -#define POWER_TKLOCK_LOCK_STR "tklock-lock" +/** Setting for single press actions from display off */ +# define MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF MCE_GCONF_POWERKEY_PATH "/actions_single_off" -/** Configuration value used for tklock both */ -#define POWER_TKLOCK_BOTH_STR "tklock-both" +/** Setting for double press actions from display off */ +# define MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF MCE_GCONF_POWERKEY_PATH "/actions_double_off" -/** Configuration value used for D-Bus signal */ -#define POWER_DBUS_SIGNAL_STR "dbus-signal-" +/** Setting for long press actions from display off */ +# define MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF MCE_GCONF_POWERKEY_PATH "/actions_long_off" -/** Action to perform on [power] keypress */ -typedef enum { +/** Setting for D-Bus action #1 */ +# define MCE_GCONF_POWERKEY_DBUS_ACTION1 MCE_GCONF_POWERKEY_PATH "/dbus_action1" - /** No action */ - POWER_DISABLED = 0, +/** Setting for D-Bus action #2 */ +# define MCE_GCONF_POWERKEY_DBUS_ACTION2 MCE_GCONF_POWERKEY_PATH "/dbus_action2" - /** Show device menu */ - POWER_MENU = 1, - - /** Default for long press */ - DEFAULT_POWERKEY_LONG_ACTION = 2, - - /** Shutdown */ - POWER_POWEROFF = 2, - - /** Soft poweroff */ - POWER_SOFT_POWEROFF = 3, +/** Power key action enable modes */ +typedef enum +{ + /** Power key actions disabled */ + PWRKEY_ENABLE_NEVER, - /** Default for short press */ - DEFAULT_POWERKEY_SHORT_ACTION = 4, + /** Power key actions always enabled */ + PWRKEY_ENABLE_ALWAYS, - /** Lock the TKLock if unlocked */ - POWER_TKLOCK_LOCK = 4, + /** Power key actions enabled if PS is not covered */ + PWRKEY_ENABLE_NO_PROXIMITY, - /** Unlock the TKLock if locked */ - POWER_TKLOCK_UNLOCK = 5, + /** Power key actions enabled if PS is not covered or display is on */ + PWRKEY_ENABLE_NO_PROXIMITY2, - /** Lock the TKLock if unlocked, unlock the TKLock if locked */ - POWER_TKLOCK_BOTH = 6, + PWRKEY_ENABLE_DEFAULT = PWRKEY_ENABLE_ALWAYS, +} pwrkey_enable_mode_t; - /** Default for double press */ - DEFAULT_POWERKEY_DOUBLE_ACTION = 7, +typedef enum +{ + /** Pressing power key turns display off */ + PWRKEY_BLANK_TO_OFF, - /** Send a D-Bus signal */ - POWER_DBUS_SIGNAL = 7 -} poweraction_t; + /** Pressing power key puts display to lpm state */ + PWRKEY_BLANK_TO_LPM, +} pwrkey_blanking_mode_t; -/** Name of Powerkey configuration group */ -#define MCE_CONF_POWERKEY_GROUP "PowerKey" +/** Long delay for the [power] button in milliseconds */ +#define DEFAULT_POWERKEY_LONG_DELAY 1500 -/** Name of configuration key for medium [power] press delay */ -#define MCE_CONF_POWERKEY_MEDIUM_DELAY "PowerKeyMediumDelay" +/** Double press timeout for the [power] button in milliseconds */ +#define DEFAULT_POWERKEY_DOUBLE_DELAY 400 -/** Name of configuration key for long [power] press delay */ -#define MCE_CONF_POWERKEY_LONG_DELAY "PowerKeyLongDelay" +/** Default actions for single press while display is on */ +#define DEFAULT_POWERKEY_ACTIONS_SINGLE_ON "blank,tklock" -/** Name of configuration key for double [power] press delay */ -#define MCE_CONF_POWERKEY_DOUBLE_DELAY "PowerKeyDoubleDelay" +/** Default actions for double press while display is on */ +#define DEFAULT_POWERKEY_ACTIONS_DOUBLE_ON "blank,tklock,devlock" -/** Name of configuration key for short [power] press action */ -#define MCE_CONF_POWERKEY_SHORT_ACTION "PowerKeyShortAction" +/** Default actions for long press while display is on */ +#define DEFAULT_POWERKEY_ACTIONS_LONG_ON "shutdown" -/** Name of configuration key for long [power] press action */ -#define MCE_CONF_POWERKEY_LONG_ACTION "PowerKeyLongAction" +/** Default actions for single press while display is off */ +#define DEFAULT_POWERKEY_ACTIONS_SINGLE_OFF "unblank" -/** Name of configuration key for double [power] press action */ -#define MCE_CONF_POWERKEY_DOUBLE_ACTION "PowerKeyDoubleAction" +/** Default actions for double press while display is off */ +#define DEFAULT_POWERKEY_ACTIONS_DOUBLE_OFF "unblank,tkunlock" -/** - * Long delay for the [power] button in milliseconds; 1.5 seconds - */ -#define DEFAULT_POWER_LONG_DELAY 1500 +/** Default actions for long press while display is off + * + * Note: If kernel side reports immediately power key press + release + * when device is suspended, detecting long presses might not + * work when display is off -> leave unset by default. */ +#define DEFAULT_POWERKEY_ACTIONS_LONG_OFF "" -/** Medium delay for the [power] button in milliseconds; 1 second */ -#define DEFAULT_POWER_MEDIUM_DELAY 1000 +/** Default argument for signal sent due to dbus1 action */ +#define DEFAULT_POWERKEY_DBUS_ACTION1 "event1" -/** Double press timeout for the [power] button in milliseconds; 0.5 seconds */ -#define DEFAULT_POWER_DOUBLE_DELAY 500 +/** Default argument for signal sent due to dbus2 action */ +#define DEFAULT_POWERKEY_DBUS_ACTION2 "event2" /* When MCE is made modular, this will be handled differently */ gboolean mce_powerkey_init(void); diff --git a/tools/mcetool.c b/tools/mcetool.c index 4da441a..c6176cb 100644 --- a/tools/mcetool.c +++ b/tools/mcetool.c @@ -531,6 +531,26 @@ static gboolean dbushelper_read_int(DBusMessageIter *iter, gint *value) return *value = data, TRUE; } +/** Helper for parsing string value from D-Bus message iterator + * + * @param iter D-Bus message iterator + * @param value Where to store the value (not modified on failure) + * + * @return TRUE if value could be read, FALSE on failure + */ +static gboolean dbushelper_read_string(DBusMessageIter *iter, gchar **value) +{ + char *data = 0; + + if( !dbushelper_require_type(iter, DBUS_TYPE_STRING) ) + return FALSE; + + dbus_message_iter_get_basic(iter, &data); + dbus_message_iter_next(iter); + + return *value = g_strdup(data ?: ""), TRUE; +} + /** Helper for parsing boolean value from D-Bus message iterator * * @param iter D-Bus message iterator @@ -673,6 +693,27 @@ static gboolean dbushelper_write_int(DBusMessageIter *iter, gint value) return TRUE; } +/** Helper for adding string value to D-Bus iterator + * + * @param iter Write iterator where to add the value + * @param value the value to add + * + * @return TRUE on success, FALSE on failure + */ +static gboolean dbushelper_write_string(DBusMessageIter *iter, const char *value) +{ + const char *data = value ?: ""; + int type = DBUS_TYPE_STRING; + + if( !dbus_message_iter_append_basic(iter, type, &data) ) { + errorf("failed to add %s data\n", + dbushelper_get_type_name(type)); + return FALSE; + } + + return TRUE; +} + /** Helper for adding int value array to D-Bus iterator * * @param iter Write iterator where to add the value @@ -935,6 +976,46 @@ static gboolean mcetool_gconf_get_int(const gchar *const key, gint *value) return res; } +/** Return a string from the specified GConf key + * + * @param key The GConf key to get the value from + * @param value Will contain the value on return + * + * @return TRUE on success, FALSE on failure + */ +static gboolean mcetool_gconf_get_string(const gchar *const key, gchar **value) +{ + debugf("@%s(%s)\n", __FUNCTION__, key); + + gboolean res = FALSE; + DBusMessage *req = 0; + DBusMessage *rsp = 0; + + DBusMessageIter body, variant; + + if( !(req = mcetool_config_request(MCE_DBUS_GET_CONFIG_REQ)) ) + goto EXIT; + if( !dbushelper_init_write_iterator(req, &body) ) + goto EXIT; + if( !dbushelper_write_path(&body, key) ) + goto EXIT; + + if( !(rsp = dbushelper_call_method(req)) ) + goto EXIT; + if( !dbushelper_init_read_iterator(rsp, &body) ) + goto EXIT; + if( !dbushelper_read_variant(&body, &variant) ) + goto EXIT; + + res = dbushelper_read_string(&variant, value); + +EXIT: + if( rsp ) dbus_message_unref(rsp); + if( req ) dbus_message_unref(req); + + return res; +} + /** Return an integer array from the specified GConf key * * @param key The GConf key to get the value from @@ -1083,6 +1164,60 @@ static gboolean mcetool_gconf_set_int(const gchar *const key, const gint value) return res; } +/** Set an string GConf key to the specified value + * + * @param key The GConf key to set the value of + * @param value The value to set the key to + * @return TRUE on success, FALSE on failure + */ +static gboolean mcetool_gconf_set_string(const gchar *const key, const char *value) +{ + debugf("@%s(%s, %d)\n", __FUNCTION__, key, value); + + static const char sig[] = DBUS_TYPE_STRING_AS_STRING; + + gboolean res = FALSE; + DBusMessage *req = 0; + DBusMessage *rsp = 0; + + DBusMessageIter stack[2]; + DBusMessageIter *wpos = stack; + DBusMessageIter *rpos = stack; + + // construct request + if( !(req = mcetool_config_request(MCE_DBUS_SET_CONFIG_REQ)) ) + goto EXIT; + if( !dbushelper_init_write_iterator(req, wpos) ) + goto EXIT; + if( !dbushelper_write_path(wpos, key) ) + goto EXIT; + if( !dbushelper_push_variant(&wpos, sig) ) + goto EXIT; + if( !dbushelper_write_string(wpos, value) ) + goto EXIT; + if( !dbushelper_pop_container(&wpos) ) + goto EXIT; + if( wpos != stack ) + abort(); + + // get reply and process it + if( !(rsp = dbushelper_call_method(req)) ) + goto EXIT; + if( !dbushelper_init_read_iterator(rsp, rpos) ) + goto EXIT; + if( !dbushelper_read_boolean(rpos, &res) ) + res = FALSE; + +EXIT: + // make sure write iterator stack is collapsed + dbushelper_abandon_stack(stack, wpos); + + if( rsp ) dbus_message_unref(rsp); + if( req ) dbus_message_unref(req); + + return res; +} + /** Set an integer array GConf key to the specified values * * @param key The GConf key to set the value of @@ -1650,27 +1785,6 @@ static void set_led_state(const gboolean enable) DBUS_TYPE_INVALID); } -/** Trigger a powerkey event - * - * @param type The type of event to trigger; valid types: - * "short", "double", "long" - */ -static bool xmce_powerkey_event(const char *args) -{ - debugf("%s(%s)\n", __FUNCTION__, args); - int val = xmce_parse_powerkeyevent(args); - if( val < 0 ) { - errorf("%s: invalid power key event\n", args); - exit(EXIT_FAILURE); - } - /* com.nokia.mce.request.req_trigger_powerkey_event */ - dbus_uint32_t data = val; - xmce_ipc_no_reply(MCE_TRIGGER_POWERKEY_EVENT_REQ, - DBUS_TYPE_UINT32, &data, - DBUS_TYPE_INVALID); - return true; -} - /** Activate/Deactivate a LED pattern * * @param pattern The name of the pattern to activate/deactivate @@ -2393,6 +2507,27 @@ static void xmce_get_blank_timeout(void) * powerkey * ------------------------------------------------------------------------- */ +/** Trigger a powerkey event + * + * @param type The type of event to trigger; valid types: + * "short", "double", "long" + */ +static bool xmce_powerkey_event(const char *args) +{ + debugf("%s(%s)\n", __FUNCTION__, args); + int val = xmce_parse_powerkeyevent(args); + if( val < 0 ) { + errorf("%s: invalid power key event\n", args); + exit(EXIT_FAILURE); + } + /* com.nokia.mce.request.req_trigger_powerkey_event */ + dbus_uint32_t data = val; + xmce_ipc_no_reply(MCE_TRIGGER_POWERKEY_EVENT_REQ, + DBUS_TYPE_UINT32, &data, + DBUS_TYPE_INVALID); + return true; +} + /** Lookup table for powerkey wakeup policies */ static const symbol_t powerkey_action[] = { @@ -2465,6 +2600,363 @@ static void xmce_get_powerkey_blanking(void) printf("%-"PAD1"s %s \n", "Powerkey blanking mode:", txt ?: "unknown"); } +/** Set powerkey long press delay + * + * @param args string that can be parsed to number + */ +static bool xmce_set_powerkey_long_press_delay(const char *args) +{ + const char *key = MCE_GCONF_POWERKEY_LONG_PRESS_DELAY; + gint val = xmce_parse_integer(args); + mcetool_gconf_set_int(key, val); + return true; +} + +/** Get current powerkey long press delay + */ +static void xmce_get_powerkey_long_press_delay(void) +{ + const char *tag = "Powerkey long press delay:"; + const char *key = MCE_GCONF_POWERKEY_LONG_PRESS_DELAY; + gint val = 0; + char txt[64]; + + if( !mcetool_gconf_get_int(key, &val) ) + snprintf(txt, sizeof txt, "unknown"); + else + snprintf(txt, sizeof txt, "%d [ms]", val); + + printf("%-"PAD1"s %s\n", tag, txt); +} + +/** Set powerkey double press delay + * + * @param args string that can be parsed to number + */ +static bool xmce_set_powerkey_double_press_delay(const char *args) +{ + const char *key = MCE_GCONF_POWERKEY_DOUBLE_PRESS_DELAY; + gint val = xmce_parse_integer(args); + mcetool_gconf_set_int(key, val); + return true; +} + +/** Get current powerkey double press delay + */ +static void xmce_get_powerkey_double_press_delay(void) +{ + const char *tag = "Powerkey double press delay:"; + const char *key = MCE_GCONF_POWERKEY_DOUBLE_PRESS_DELAY; + gint val = 0; + char txt[64]; + + if( !mcetool_gconf_get_int(key, &val) ) + snprintf(txt, sizeof txt, "unknown"); + else + snprintf(txt, sizeof txt, "%d [ms]", val); + + printf("%-"PAD1"s %s\n", tag, txt); +} + +/** Action name is valid predicate + */ +static bool xmce_is_powerkey_action(const char *name) +{ + static const char * const lut[] = + { + "blank", + "tklock", + "devlock", + "dbus1", + "softoff", + "shutdown", + "unblank", + "tkunlock", + "dbus2", + }; + + for( size_t i = 0; i < G_N_ELEMENTS(lut); ++i ) { + if( !strcmp(lut[i], name) ) + return true; + } + + return false; +} + +/** Comma separated list of action names is valid predicate + */ +static bool xmce_is_powerkey_action_mask(const char *names) +{ + bool valid = true; + + char *work = strdup(names); + + char *pos = work; + + while( *pos ) { + char *name = mcetool_parse_token(&pos); + if( xmce_is_powerkey_action(name) ) + continue; + fprintf(stderr, "invalid powerkey action: '%s'\n", name); + valid = false; + } + + free(work); + + return valid; +} + +/** Helper for setting powerkey action mask settings + */ +static void xmce_set_powerkey_action_mask(const char *key, const char *names) +{ + if( names && *names && !xmce_is_powerkey_action_mask(names) ) + exit(EXIT_FAILURE); + + mcetool_gconf_set_string(key, names); +} + +/** Set actions to perform on single power key press from display off + */ +static bool xmce_set_powerkey_actions_while_display_off_single(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF, + args); + return true; +} + +/** Set actions to perform on double power key press from display off + */ +static bool xmce_set_powerkey_actions_while_display_off_double(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF, + args); + return true; +} + +/** Set actions to perform on long power key press from display off + */ +static bool xmce_set_powerkey_actions_while_display_off_long(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF, + args); + return true; +} + +/** Set actions to perform on single power key press from display on + */ +static bool xmce_set_powerkey_actions_while_display_on_single(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON, + args); + return true; +} + +/** Set actions to perform on double power key press from display on + */ +static bool xmce_set_powerkey_actions_while_display_on_double(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON, + args); + return true; +} + +/** Set actions to perform on long power key press from display on + */ +static bool xmce_set_powerkey_actions_while_display_on_long(const char *args) +{ + xmce_set_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_LONG_ON, + args); + return true; +} + +/** Helper for getting powerkey action mask settings + */ +static void xmce_get_powerkey_action_mask(const char *key, const char *tag) +{ + gchar *val = 0; + mcetool_gconf_get_string(key, &val); + printf("\t%-"PAD2"s %s\n", tag, + val ? *val ? val : "(none)" : "unknown"); + g_free(val); +} + +/* Show current powerkey action mask settings */ +static void xmce_get_powerkey_action_masks(void) +{ + printf("Powerkey press from display on:\n"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_ON, + "single"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_ON, + "double"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_LONG_ON, + "long"); + + printf("Powerkey press from display of:\n"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_SINGLE_OFF, + "single"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_DOUBLE_OFF, + "double"); + xmce_get_powerkey_action_mask(MCE_GCONF_POWERKEY_ACTIONS_LONG_OFF, + "long"); +} + +/** Validate dbus action parameters given by the user + */ +static bool xmce_is_powerkey_dbus_action(const char *conf) +{ + bool valid = true; + + char *tmp = strdup(conf); + char *pos = tmp; + + const char *arg = mcetool_parse_token(&pos); + + if( *arg && !*pos ) { + // single item == argument to use for signal + } + else { + const char *destination = arg; + const char *object = mcetool_parse_token(&pos); + const char *interface = mcetool_parse_token(&pos); + const char *member = mcetool_parse_token(&pos); + + // string argument is optional + const char *argument = mcetool_parse_token(&pos); + + /* NOTE: libdbus will call abort() if invalid parameters are + * passed to dbus_message_new_method_call() function. + * We do not want values that can crash mce to + * end up in persitently stored settings + */ + + /* 1st try to validate given parameters ... */ + + if( !dbus_validate_bus_name(destination, 0) ) { + fprintf(stderr, "invalid service name: '%s'\n", + destination); + valid = false; + } + if( !dbus_validate_path(object, 0) ) { + fprintf(stderr, "invalid object path: '%s'\n", + object); + valid = false; + } + if( !dbus_validate_interface(interface, 0) ) { + fprintf(stderr, "invalid interface: '%s'\n", + interface); + valid = false; + } + if( !dbus_validate_member(member, 0) ) { + fprintf(stderr, "invalid method name: '%s'\n", + member); + valid = false; + } + if( !dbus_validate_utf8(argument, 0) ) { + fprintf(stderr, "invalid argument string: '%s'\n", + argument); + valid = false; + } + + /* ... then use the presumed safe parameters to create + * a dbus method call object -> if there is some + * reason for dbus_message_new_method_call() to abort, + * it happens within mcetool, not mce itself. + */ + + if( valid ) { + DBusMessage *msg = + dbus_message_new_method_call(destination, + object, + interface, + member); + if( msg ) + dbus_message_unref(msg); + } + } + + free(tmp); + + return valid; +} + +/** Helper for setting dbus action config + */ +static void xmce_set_powerkey_dbus_action(const char *key, const char *conf) +{ + if( conf && *conf && !xmce_is_powerkey_dbus_action(conf) ) + exit(EXIT_FAILURE); + + mcetool_gconf_set_string(key, conf); +} + +/** Configure "dbus1" powerkey action + */ +static bool xmce_set_powerkey_dbus_action1(const char *args) +{ + xmce_set_powerkey_dbus_action(MCE_GCONF_POWERKEY_DBUS_ACTION1, args); + return true; +} + +/** Configure "dbus2" powerkey action + */ +static bool xmce_set_powerkey_dbus_action2(const char *args) +{ + xmce_set_powerkey_dbus_action(MCE_GCONF_POWERKEY_DBUS_ACTION2, args); + return true; +} + +/** Helper for showing current dbus action config + */ +static void xmce_get_powerkey_dbus_action(const char *key, const char *tag) +{ + gchar *val = 0; + + if( !mcetool_gconf_get_string(key, &val) ) + goto cleanup; + + if( !val ) + goto cleanup; + + char *pos = val; + char *arg = mcetool_parse_token(&pos); + + char tmp[64]; + snprintf(tmp, sizeof tmp, "Powerkey D-Bus action '%s':", tag); + + if( *arg && !*pos ) { + printf("%-"PAD1"s send signal with arg '%s'\n", tmp, arg); + } + else { + const char *destination = arg; + const char *object = mcetool_parse_token(&pos); + const char *interface = mcetool_parse_token(&pos); + const char *member = mcetool_parse_token(&pos); + const char *argument = mcetool_parse_token(&pos); + + printf("%-"PAD1"s make method call\n", tmp); + printf("\t%-"PAD2"s %s\n", "destination", destination); + printf("\t%-"PAD2"s %s\n", "object", object); + printf("\t%-"PAD2"s %s\n", "interface", interface); + printf("\t%-"PAD2"s %s\n", "member", member); + printf("\t%-"PAD2"s %s\n", "argument", + *argument ? argument : "N/A");; + } + +cleanup: + g_free(val); +} + +/** Show current configuration for powerkey dbus actions + */ +static void xmce_get_powerkey_dbus_actions(void) +{ + xmce_get_powerkey_dbus_action(MCE_GCONF_POWERKEY_DBUS_ACTION1, + "dbus1"); + xmce_get_powerkey_dbus_action(MCE_GCONF_POWERKEY_DBUS_ACTION2, + "dbus2"); +} + /** Set powerkey proximity override press count * * @param args string that can be parsed to number @@ -3345,6 +3837,10 @@ static bool xmce_get_status(const char *args) xmce_get_doubletap_wakeup(); xmce_get_powerkey_action(); xmce_get_powerkey_blanking(); + xmce_get_powerkey_long_press_delay(); + xmce_get_powerkey_double_press_delay(); + xmce_get_powerkey_action_masks(); + xmce_get_powerkey_dbus_actions(); xmce_get_ps_override_count(); xmce_get_ps_override_timeout(); xmce_get_display_off_override(); @@ -3687,6 +4183,110 @@ static const mce_opt_t options[] = "set the doubletap blanking mode; valid modes are:\n" "'off', 'lpm'\n" }, + { + .name = "set-powerkey-long-press-delay", + .with_arg = xmce_set_powerkey_long_press_delay, + .values = "ms", + .usage = + "set minimum length of \"long\" power key press.\n" + }, + { + .name = "set-powerkey-double-press-delay", + .with_arg = xmce_set_powerkey_double_press_delay, + .values = "ms", + .usage = + "set maximum delay between \"double\" power key presses.\n" + }, + { + .name = "set-display-on-single-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_on_single, + .values = "actions", + .usage = + "set actions to execute on single power key press from display on state\n" + "\n" + "Valid actions are:\n" + " blank - turn display off\n" + " tklock - lock ui\n" + " devlock - lock device\n" + " dbus1 - send dbus signal or make method call\n" + " softoff - enter softoff mode (legacy, not supported)\n" + " shutdown - power off device\n" + " unblank - turn display on\n" + " tkunlock - unlock ui\n" + " dbus2 - send dbus signal or make method call\n" + "\n" + "Comma separated list of actions can be used.\n" + }, + { + .name = "set-display-on-double-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_on_double, + .values = "actions", + .usage = + "set actions to execute on double power key press from display on state\n" + "\n" + "See --set-display-on-single-powerkey-press-actions for details\n" + }, + { + .name = "set-display-on-long-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_on_long, + .values = "actions", + .usage = + "set actions to execute on long power key press from display on state\n" + "\n" + "See --set-display-on-single-powerkey-press-actions for details\n" + }, + { + .name = "set-display-off-single-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_off_single, + .values = "actions", + .usage = + "set actions to execute on single power key press from display off state\n" + "\n" + "See --set-display-on-single-powerkey-press-actions for details\n" + }, + { + .name = "set-display-off-double-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_off_double, + .values = "actions", + .usage = + "set actions to execute on double power key press from display off state\n" + "\n" + "See --set-display-on-single-powerkey-press-actions for details\n" + }, + { + .name = "set-display-off-long-powerkey-press-actions", + .with_arg = xmce_set_powerkey_actions_while_display_off_long, + .values = "actions", + .usage = + "set actions to execute on long power key press from display off state\n" + "\n" + "See --set-display-on-single-powerkey-press-actions for details\n" + }, + { + .name = "set-powerkey-dbus-action1", + .with_arg = xmce_set_powerkey_dbus_action1, + .values = "signal_argument|method_call_details", + .usage = + "define dbus ipc taking place when dbus1 powerkey action is triggered\n" + "\n" + "signal_argument: \n" + " MCE will still send a dbus signal, but uses the given string as argument\n" + " instead of using the built-in default.\n" + "\n" + "methdod_call_details: ,,,[,]\n" + " Instead of sending a signal, MCE will make dbus method call as specified.\n" + " The string argument for the method call is optional.\n" + }, + + { + .name = "set-powerkey-dbus-action2", + .with_arg = xmce_set_powerkey_dbus_action2, + .values = "signal_argument|method_call_details", + .usage = + "define dbus ipc taking place when dbus2 powerkey action is triggered\n" + "\n" + "See --set-powerkey-dbus-action1 for details.\n" + }, { .name = "set-powerkey-ps-override-count", .with_arg = xmce_set_ps_override_count, From 9efa7db55ec3f28c2acf94bbf846b81d7d065258 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Tue, 28 Oct 2014 09:55:10 +0200 Subject: [PATCH 4/6] Re-evaluate autolock if device wakes up from suspend When display is automatically dimmed and then blanked there is a 30 second grace period before tklock gets applied. Since the device most likely also suspends soon after blanking the screen and we do not want to wake up just to toggle the tklock state, the end of grace period is evaluated when display is about to be turned on. This can cause ipc timing problems if powerkey/doubletap is configured also to remove the tklock. Also do opportunistic grace period end detection if the device wakes up from suspend due to reasons that would not cause the display to turn on. This should make it less likely that both lock and unlock happen at the same time. [mce] Re-evaluate autolock if device wakes up from suspend --- datapipe.c | 6 ++++++ datapipe.h | 1 + mce-io.c | 5 +++++ tklock.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/datapipe.c b/datapipe.c index df6aad9..b4cec14 100644 --- a/datapipe.c +++ b/datapipe.c @@ -42,6 +42,9 @@ datapipe_struct led_pattern_activate_pipe; /** LED pattern to deactivate; read only */ datapipe_struct led_pattern_deactivate_pipe; +/** resumed from suspend notification; read only */ +datapipe_struct device_resumed_pipe; + /** Non-synthetized user activity; read only */ datapipe_struct user_activity_pipe; @@ -802,6 +805,8 @@ void mce_datapipe_init(void) 0, GINT_TO_POINTER(0)); setup_datapipe(&led_pattern_activate_pipe, READ_ONLY, FREE_CACHE, 0, NULL); + setup_datapipe(&device_resumed_pipe, READ_ONLY, DONT_FREE_CACHE, + 0, NULL); setup_datapipe(&led_pattern_deactivate_pipe, READ_ONLY, FREE_CACHE, 0, NULL); setup_datapipe(&user_activity_pipe, READ_ONLY, DONT_FREE_CACHE, @@ -905,6 +910,7 @@ void mce_datapipe_quit(void) free_datapipe(&user_activity_pipe); free_datapipe(&led_pattern_deactivate_pipe); free_datapipe(&led_pattern_activate_pipe); + free_datapipe(&device_resumed_pipe); free_datapipe(&led_brightness_pipe); free_datapipe(&lpm_brightness_pipe); free_datapipe(&display_brightness_pipe); diff --git a/datapipe.h b/datapipe.h index ecec0fa..d928ef2 100644 --- a/datapipe.h +++ b/datapipe.h @@ -79,6 +79,7 @@ extern datapipe_struct lpm_brightness_pipe; extern datapipe_struct device_inactive_pipe; extern datapipe_struct led_pattern_activate_pipe; extern datapipe_struct led_pattern_deactivate_pipe; +extern datapipe_struct device_resumed_pipe; extern datapipe_struct user_activity_pipe; extern datapipe_struct display_state_pipe; extern datapipe_struct display_state_req_pipe; diff --git a/mce-io.c b/mce-io.c index b68493d..8cef9db 100644 --- a/mce-io.c +++ b/mce-io.c @@ -187,6 +187,11 @@ static void io_detect_resume(void) mce_log(LL_DEVEL, "time skip: assume %"PRId64".%03"PRId64"s suspend", skip / 1000, skip % 1000); + // notify in case some timers need re-evaluating + execute_datapipe_output_triggers(&device_resumed_pipe, + &prev, + USE_INDATA); + EXIT: return; } diff --git a/tklock.c b/tklock.c index c56e016..9afacc0 100644 --- a/tklock.c +++ b/tklock.c @@ -173,6 +173,7 @@ static int64_t tklock_monotick_get(void); static void tklock_datapipe_system_state_cb(gconstpointer data); static void tklock_datapipe_device_lock_active_cb(gconstpointer data); +static void tklock_datapipe_device_resumed_cb(gconstpointer data); static void tklock_datapipe_lipstick_available_cb(gconstpointer data); static void tklock_datapipe_update_mode_cb(gconstpointer data); static void tklock_datapipe_display_state_cb(gconstpointer data); @@ -215,6 +216,7 @@ static void tklock_datapipe_quit(void); static gboolean tklock_autolock_cb(gpointer aptr); static bool tklock_autolock_exceeded(void); +static void tklock_autolock_reschedule(void); static void tklock_autolock_schedule(int delay); static void tklock_autolock_cancel(void); static void tklock_autolock_rethink(void); @@ -557,6 +559,19 @@ static void tklock_datapipe_device_lock_active_cb(gconstpointer data) return; } +/** Resumed from suspend notification */ +static void tklock_datapipe_device_resumed_cb(gconstpointer data) +{ + (void) data; + + /* We do not want to wakeup from suspend just to end the + * grace period, so regular timer is used for it. However, + * if we happen to resume for some other reason, check if + * the timeout has already passed */ + + tklock_autolock_reschedule(); +} + /** Lipstick dbus name is reserved; assume false */ static bool lipstick_available = false; @@ -1532,6 +1547,10 @@ static void tklock_datapipe_user_activity_cb(gconstpointer data) static datapipe_binding_t tklock_datapipe_triggers[] = { // output triggers + { + .datapipe = &device_resumed_pipe, + .output_cb = tklock_datapipe_device_resumed_cb, + }, { .datapipe = &lipstick_available_pipe, .output_cb = tklock_datapipe_lipstick_available_cb, @@ -1796,6 +1815,34 @@ static void tklock_autolock_cancel(void) } } +static void tklock_autolock_reschedule(void) +{ + /* Do we have a timer to re-evaluate? */ + if( !tklock_autolock_id ) + goto EXIT; + + /* Clear old timer */ + g_source_remove(tklock_autolock_id), tklock_autolock_id = 0; + + int64_t now = tklock_monotick_get(); + + if( now >= tklock_autolock_tick ) { + mce_log(LL_DEBUG, "autolock time passed while suspended; lock now"); + /* Trigger time passed while suspended */ + tklock_autolock_tick = MAX_TICK; + tklock_ui_set(true); + } + else { + /* Re-calculate wakeup time */ + mce_log(LL_DEBUG, "adjusting autolock time after resume"); + int delay = (int)(tklock_autolock_tick - now); + tklock_autolock_id = g_timeout_add(delay, tklock_autolock_cb, 0); + } + +EXIT: + return; +} + static void tklock_autolock_schedule(int delay) { if( tklock_autolock_id ) From f7411f9368485efb1008974beffa2a50a3d86d43 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Tue, 28 Oct 2014 09:59:02 +0200 Subject: [PATCH 5/6] Do not broadcast intermediate tklock changes over D-Bus If mce side state machines oscillate between tklock on and off, it can cause problems due to ui side trying to react to intermediate states. Use idle callback to delay sending of tklock state information so that only the final tklock state is communicated outside mce process. Hold a wakelock until the dbus ipc is actually made plus couple of seconds to make it more likely for ui side to have time to process the state change before device is allowed to suspend. Also clear last send state when tklock change requests are made over D-Bus. This way the state mce arrived at is always broadcast even if the request ends up getting ignored. [mce] Do not broadcast intermediate tklock changes over D-Bus --- mce.c | 1 + tklock.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/mce.c b/mce.c index 0d682ce..e003ca0 100644 --- a/mce.c +++ b/mce.c @@ -156,6 +156,7 @@ static void mce_cleanup_wakelocks(void) wakelock_unlock("mce_bluez_wait"); wakelock_unlock("mce_led_breathing"); wakelock_unlock("mce_lpm_off"); + wakelock_unlock("mce_tklock_notify"); } #endif // ENABLE_WAKELOCKS diff --git a/tklock.c b/tklock.c index 9afacc0..c23625a 100644 --- a/tklock.c +++ b/tklock.c @@ -498,7 +498,7 @@ static ps_history_t tklock_lpmui_hist[8]; static bool tklock_ui_enabled = false; /** Current tklock ui state that has been sent to lipstick */ -static int tklock_ui_sent = -1; // does not match bool values +static int tklock_ui_notified = -1; // does not match bool values /** System state; is undefined at bootup, can't assume anything */ static system_state_t system_state = MCE_STATE_UNDEF; @@ -589,7 +589,7 @@ static void tklock_datapipe_lipstick_available_cb(gconstpointer data) lipstick_available); // force tklock ipc - tklock_ui_sent = -1; + tklock_ui_notified = -1; tklock_ui_set(false); if( lipstick_available ) { @@ -3647,10 +3647,118 @@ static void tklock_ui_close(void) DBUS_TYPE_INVALID); } -static void tklock_ui_set(bool enable) +static guint tklock_ui_notify_end_id = 0; +static guint tklock_ui_notify_beg_id = 0; + +static void tklock_ui_notify_rethink_wakelock(void) { - bool requested = enable; + static bool have_lock = false; + + bool need_lock = (tklock_ui_notify_beg_id || tklock_ui_notify_end_id); + + if( have_lock == need_lock ) + goto EXIT; + + mce_log(LL_DEBUG, "ui notify wakelock: %s", + need_lock ? "OBTAIN" : "RELEASE"); + + if( (have_lock = need_lock) ) { + wakelock_lock("mce_tklock_notify", -1); + } + else + wakelock_unlock("mce_tklock_notify"); + +EXIT: + return; +} + +static gboolean tklock_ui_notify_end_cb(gpointer data) +{ + (void) data; + + if( !tklock_ui_notify_end_id ) + goto EXIT; + + tklock_ui_notify_end_id = 0; + +EXIT: + + tklock_ui_notify_rethink_wakelock(); + + return FALSE; +} + +static gboolean tklock_ui_notify_beg_cb(gpointer data) +{ + (void) data; + + if( !tklock_ui_notify_beg_id ) + goto EXIT; + + tklock_ui_notify_beg_id = 0; + + bool current = tklock_datapipe_have_tklock_submode(); + + if( tklock_ui_notified == current ) + goto EXIT; + + tklock_ui_notified = current; + /* do lipstick specific ipc */ + if( lipstick_available ) { + if( current ) + tklock_ui_open(); + else + tklock_ui_close(); + } + + /* broadcast signal */ + tklock_dbus_send_tklock_mode(0); + + /* give ui a chance to see the signal */ + if( tklock_ui_notify_end_id ) + g_source_remove(tklock_ui_notify_end_id); + + tklock_ui_notify_end_id = g_timeout_add(2000, + tklock_ui_notify_end_cb, + 0); + +EXIT: + + tklock_ui_notify_rethink_wakelock(); + + return FALSE; +} + +static void tklock_ui_notify_cancel(void) +{ + if( tklock_ui_notify_end_id ) { + g_source_remove(tklock_ui_notify_end_id), + tklock_ui_notify_end_id = 0; + } + if( tklock_ui_notify_beg_id ) { + g_source_remove(tklock_ui_notify_beg_id), + tklock_ui_notify_beg_id = 0; + } + + tklock_ui_notify_rethink_wakelock(); +} + +static void tklock_ui_notify_schdule(void) +{ + if( tklock_ui_notify_end_id ) { + g_source_remove(tklock_ui_notify_end_id), + tklock_ui_notify_end_id = 0; + } + if( !tklock_ui_notify_beg_id ) { + tklock_ui_notify_beg_id = g_idle_add(tklock_ui_notify_beg_cb, 0); + } + + tklock_ui_notify_rethink_wakelock(); +} + +static void tklock_ui_set(bool enable) +{ if( enable ) { if( system_state != MCE_STATE_USER ) { mce_log(LL_INFO, "deny tklock; not in user mode"); @@ -3666,21 +3774,14 @@ static void tklock_ui_set(bool enable) } } - if( tklock_ui_sent != enable || requested != enable ) { - mce_log(LL_DEVEL, "tklock state = %s", enable ? "locked" : "unlocked"); - - if( (tklock_ui_sent = tklock_ui_enabled = enable) ) { - if( lipstick_available ) - tklock_ui_open(); + if( tklock_ui_enabled != enable ) { + if( (tklock_ui_enabled = enable) ) mce_add_submode_int32(MCE_TKLOCK_SUBMODE); - } - else { - if( lipstick_available ) - tklock_ui_close(); + else mce_rem_submode_int32(MCE_TKLOCK_SUBMODE); - } - tklock_dbus_send_tklock_mode(0); } + + tklock_ui_notify_schdule(); } /** Handle reply to device lock state query @@ -3889,8 +3990,10 @@ static gboolean tklock_dbus_mode_change_req_cb(DBusMessage *const msg) mce_log(LL_DEBUG, "mode: %s/%d", mode, state); - if( state != LOCK_UNDEF ) + if( state != LOCK_UNDEF ) { + tklock_ui_notified = -1; tklock_datapipe_tk_lock_cb(GINT_TO_POINTER(state)); + } if( no_reply ) status = TRUE; @@ -3931,6 +4034,7 @@ static gboolean tklock_dbus_systemui_callback_cb(DBusMessage *const msg) switch( result ) { case TKLOCK_UNLOCK: + tklock_ui_notified = -1; tklock_ui_set(false); break; @@ -4636,6 +4740,7 @@ void mce_tklock_exit(void) tklock_dtcalib_stop(); tklock_datapipe_proximity_uncover_cancel(); tklock_notif_quit(); + tklock_ui_notify_cancel(); // FIXME: check that final state is sane From e74cf6c45ea4c81747a477aae63ed285f7342987 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Tue, 28 Oct 2014 10:00:34 +0200 Subject: [PATCH 6/6] Set tklock if devicelock gets applied immediately after unblanking If device is suspended, device lock might get applied at unblank time. This in turn can leave the previously used application visible and usable. While the ideal solution would be to use iphb timer for locking the device while still suspended, using heuristics at least denies full access to application content if delayed device locking happens. [mce] Set tklock if devicelock gets applied immediately after unblanking --- tklock.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tklock.c b/tklock.c index c23625a..8f657a4 100644 --- a/tklock.c +++ b/tklock.c @@ -230,6 +230,11 @@ static void tklock_proxlock_schedule(int delay); static void tklock_proxlock_cancel(void); static void tklock_proxlock_rethink(void); +// autolock based on device lock changes + +static void tklock_autolock_on_devlock_prime(void); +static void tklock_autolock_on_devlock_trigger(void); + // ui exception handling state machine static uiexctype_t topmost_active(uiexctype_t mask); @@ -555,6 +560,9 @@ static void tklock_datapipe_device_lock_active_cb(gconstpointer data) tklock_autolock_rethink(); + if( device_lock_active ) + tklock_autolock_on_devlock_trigger(); + EXIT: return; } @@ -668,9 +676,14 @@ static void tklock_datapipe_display_state_next_cb(gconstpointer data) { display_state_next = GPOINTER_TO_INT(data); + mce_log(LL_DEBUG, "display_state_next = %d -> %d", + display_state, display_state_next); + if( display_state_next == display_state ) goto EXIT; + tklock_autolock_on_devlock_prime(); + tklock_autolock_pre_transition_actions(); tklock_lpmui_pre_transition_actions(); @@ -1774,6 +1787,103 @@ static void tklock_datapipe_quit(void) tklock_datapipe_remove_triggers(tklock_datapipe_triggers); } +/* ========================================================================= * + * AUTOLOCK AFTER DEVICELOCK STATE MACHINE + * ========================================================================= */ + +static int64_t tklock_autolock_on_devlock_limit = 0; + +static void tklock_autolock_on_devlock_prime(void) +{ + /* While we want to trap only device lock that happens immediately + * after unblanking the display, scheduling etc makes it difficult + * to specify some exact figure for "immediately". + * + * Since devicelock timeouts have granularity of 1 minute, assume + * that device locking that happens less than 60 seconds after + * unblanking was related to what happened during display off time. */ + const int autolock_limit = 60 * 1000; + + /* Do nothing during startup */ + if( display_state == MCE_DISPLAY_UNDEF ) + goto EXIT; + + /* Unprime if we are going to powered off state */ + switch( display_state_next ) { + case MCE_DISPLAY_DIM: + case MCE_DISPLAY_ON: + break; + + default: + if( tklock_autolock_on_devlock_limit ) + mce_log(LL_DEBUG, "autolock after devicelock: unprimed"); + tklock_autolock_on_devlock_limit = 0; + goto EXIT; + } + + /* Prime if we are coming from powered off state */ + switch( display_state ) { + case MCE_DISPLAY_DIM: + case MCE_DISPLAY_ON: + break; + + default: + if( !tklock_autolock_on_devlock_limit ) + mce_log(LL_DEBUG, "autolock after devicelock: primed"); + tklock_autolock_on_devlock_limit = + tklock_monotick_get() + autolock_limit; + break; + } + +EXIT: + return; +} + +static void tklock_autolock_on_devlock_trigger(void) +{ + /* Device lock must be active */ + if( !device_lock_active ) + goto EXIT; + + /* Not while handling calls or alarms */ + switch( exception_state ) { + case UIEXC_CALL: + case UIEXC_ALARM: + goto EXIT; + + default: + break; + } + + /* Autolock time limit must be set and not reached yet */ + if( !tklock_autolock_on_devlock_limit ) + goto EXIT; + + if( tklock_monotick_get() >= tklock_autolock_on_devlock_limit ) + goto EXIT; + + /* We get here if: Device lock got applied right after + * display was powered up. + * + * Most likely the device lock should have been applied + * already when the display was off, but the devicelock + * timer did not trigger while the device was suspended. + * + * It is also possible that the last used application + * is still visible and active. + * + * Setting the tklock moves the application to background + * and lockscreen / devicelock is shown instead. + */ + + mce_log(LL_DEBUG, "autolock after devicelock: triggered"); + execute_datapipe(&tk_lock_pipe, + GINT_TO_POINTER(LOCK_ON), + USE_INDATA, CACHE_INDATA); +EXIT: + return; +} + /* ========================================================================= * * AUTOLOCK STATE MACHINE * ========================================================================= */