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,