From 6154e96cc2cde78811deb1f7d51acd642b3e8b92 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Fri, 24 Oct 2014 12:31:18 +0300 Subject: [PATCH] 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 d453053f..f3d16be9 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 50193be1..3a1e5e2c 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 8915c2a0..6c169c7d 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 5094bda4..1be28e24 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 00000000..fdd821bf --- /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 e531367c..af8fe755 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 4da441ac..c6176cb6 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,