rockbox/apps/action.c
Dana Conrad c067b344e8 FS#13297: M3K Autolock allows one action before disabling touchpad
Intercept buttonpress and action, and nullify both if the button
is part of the touchpad. Only affects autolock functionality.

Adding removal of note about autolock in the manual - the lock
button no longer needs to be pressed at least once to prime
the autolock, if enabled, since commit 14f7a95

Change-Id: Ic3582764df490d96abc2d78116f23cbe0fdd6173
2021-06-12 21:11:28 +00:00

1270 lines
38 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Jonathan Gordon
* Copyright (C) 2017 William Wilgus
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "config.h"
#include "lang.h"
#if !defined(BOOTLOADER)
#include "language.h"
#endif
#include "appevents.h"
#include "button.h"
#include "action.h"
#include "kernel.h"
#include "splash.h"
#include "settings.h"
#include "misc.h"
#ifdef HAVE_TOUCHSCREEN
#include "statusbar-skinned.h"
#include "viewport.h"
#endif
#ifdef HAVE_BACKLIGHT
#include "backlight.h"
#if CONFIG_CHARGING
#include "power.h"
#endif
#endif /* HAVE_BACKLIGHT */
/*#define LOGF_ENABLE*/
#include "logf.h"
#define REPEAT_WINDOW_TICKS HZ/4
#define ACTION_FILTER_TICKS HZ/2 /* timeout between filtered actions SL/BL */
/* holds the action state between calls to get_action \ get_action_custom) */
static action_last_t action_last =
{
.action = ACTION_NONE,
.button = BUTTON_NONE | BUTTON_REL, /* allow the ipod wheel to
work on startup */
.context = CONTEXT_STD,
.data = 0,
.repeated = false,
.tick = 0,
.wait_for_release = false,
#ifdef HAVE_TOUCHSCREEN
.ts_data = 0,
.ts_short_press = false,
#endif
#ifdef HAVE_BACKLIGHT
.backlight_mask = SEL_ACTION_NONE,
.bl_filter_tick = 0,
#endif
#ifndef HAS_BUTTON_HOLD
.keys_locked = false,
.screen_has_lock = false,
.sl_filter_tick = 0,
.softlock_mask = SEL_ACTION_NONE,
.unlock_combo = BUTTON_NONE,
#endif
}; /* action_last_t action_last */
/******************************************************************************
** INTERNAL ACTION FUNCTIONS **************************************************
*******************************************************************************
*/
/******************************************
* has_flag compares value to a (SINGLE) flag
* returns true if set, false otherwise
*/
static inline bool has_flag(unsigned int value, unsigned int flag)
{
return ((value & flag) == flag);
}
#if defined(HAVE_BACKLIGHT) || !defined(HAS_BUTTON_HOLD)
/* HELPER FUNCTIONS selective softlock and backlight */
/****************************************************************
* is_action_filtered, selective softlock and backlight use this
* to lookup which actions are filtered, matches are only true if
* action is found and supplied SEL_ACTION mask has the flag.
* returns false if the action isn't found or isn't enabled,
* true if the action is found and is enabled
*/
static bool is_action_filtered(int action, unsigned int mask, int context)
{
bool match = false;
switch (action)
{
case ACTION_NONE:
break;
/*Actions that are not mapped will not turn on the backlight option NOUNMAPPED*/
case ACTION_UNKNOWN:
match = has_flag(mask, SEL_ACTION_NOUNMAPPED);
break;
case ACTION_WPS_PLAY:
case ACTION_FM_PLAY:
match = has_flag(mask, SEL_ACTION_PLAY);
break;
//case ACTION_STD_PREVREPEAT: // seek not exempted outside of WPS
//case ACTION_STD_NEXTREPEAT:
case ACTION_WPS_SEEKBACK:
case ACTION_WPS_SEEKFWD:
case ACTION_WPS_STOPSEEK:
match = has_flag(mask, SEL_ACTION_SEEK);
break;
//case ACTION_STD_PREV: // skip/scrollwheel not exempted outside of WPS
//case ACTION_STD_NEXT:
case ACTION_WPS_SKIPNEXT:
case ACTION_WPS_SKIPPREV:
case ACTION_FM_NEXT_PRESET:
case ACTION_FM_PREV_PRESET:
match = has_flag(mask, SEL_ACTION_SKIP);
break;
#ifdef HAVE_VOLUME_IN_LIST
case ACTION_LIST_VOLUP: // volume exempted outside of WPS if the device supports it
case ACTION_LIST_VOLDOWN:
#endif
case ACTION_WPS_VOLUP:
case ACTION_WPS_VOLDOWN:
match = has_flag(mask, SEL_ACTION_VOL);
break;
case ACTION_SETTINGS_INC:/*FMS*/
case ACTION_SETTINGS_INCREPEAT:/*FMS*/
case ACTION_SETTINGS_DEC:/*FMS*/
case ACTION_SETTINGS_DECREPEAT:/*FMS*/
match = (context == CONTEXT_FM) && has_flag(mask, SEL_ACTION_VOL);
break;
default:
/* display action code of unfiltered actions */
logf ("unfiltered actions: context: %d action: %d, last btn: %d, \
mask: %d", context, action, action_last.button, mask);
break;
}/*switch*/
return match;
}
/*******************************************************************************
* is_action_discarded:
* Most every action takes two rounds through get_action_worker,
* once for the keypress and once for the key release,
* actions with pre_button codes take even more, some actions however, only
* take once; actions defined with only a button and no release/repeat event,
* these actions should be acted upon immediately except when we have
* selective backlighting/softlock enabled and in this case we only act upon
* them immediately if there is no chance they have another event tied to them
* determined using !is_prebutton or if action is completed
* returns true if event was discarded and false if it was kept
*/
static bool is_action_discarded(action_cur_t *cur, bool filtered, long *tick)
{
bool ret = true;
bool completed = (cur->button & (BUTTON_REPEAT | BUTTON_REL)) != 0;
#ifdef HAVE_SCROLLWHEEL
/* Scrollwheel doesn't generate release events */
completed |= (cur->button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) != 0;
#endif
/*directly after a match a key release event may trigger another*/
if (filtered && cur->action != ACTION_UNKNOWN)
{
*tick = current_tick + ACTION_FILTER_TICKS;
}
/* has button been released/repeat or is this the only action it could be */
if (completed || !cur->is_prebutton)
{
/* if the action is not filtered and this isn't just a
* key release event then return false
* keeping action, and reset tick
*/
if (!filtered && *tick < current_tick)
{
*tick = 0;
ret = false;
}
}
return ret;
}
/*******************************************************
* action_handle_backlight is used to both delay
* and activate the backlight if HAVE_BACKLIGHT
* and SEL_ACTION_ENABLED; backlight state is
* set true/false and ignore_next sets the backlight
* driver to ignore backlight_on commands from
* other modules for a finite duration;
* Ignore is set each time the action system
* handles the backlight as a precaution since, if
* the action system was not triggered the device would
* appear unresponsive to the user.
* If a backlight_on event hasn't been handled in the
* ignore duration it will timeout and the next call
* to backlight_on will trigger as normal
*/
static void action_handle_backlight(bool backlight, bool ignore_next)
{
#if !defined(HAVE_BACKLIGHT)
(void) backlight;
(void) ignore_next;
return;
#else /* HAVE_BACKLIGHT */
if (backlight)
{
backlight_on_ignore(false, 0);
backlight_on();
}
backlight_on_ignore(ignore_next, 5*HZ);/*must be set everytime we handle bl*/
#ifdef HAVE_BUTTON_LIGHT
if (backlight)
{
buttonlight_on_ignore(false, 0);
buttonlight_on();
}
buttonlight_on_ignore(ignore_next, 5*HZ);/* as a precautionary fallback */
#endif /* HAVE_BUTTON_LIGHT */
#endif/* HAVE_BACKLIGHT */
}
#endif /*defined(HAVE_BACKLIGHT) || !defined(HAS_BUTTON_HOLD) HELPER FUNCTIONS*/
/******************************************************************
* action_poll_button filters button presses for get_action_worker;
* if button_get_w_tmo returns...
* BUTTON_NONE, SYS_EVENTS, MULTIMEDIA BUTTONS, ACTION_REDRAW
* they are allowed to pass immediately through to handler.
* if waiting for button release ACTION_NONE is returned until
* button is released/repeated.
*/
static inline bool action_poll_button(action_last_t *last, action_cur_t *cur)
{
bool ret = true;
int *button = &cur->button;
*button = button_get_w_tmo(cur->timeout);
/* **************************************************************************
* if action_wait_for_release() was called without a button being pressed
* then actually waiting for release would do the wrong thing, i.e.
* the next key press is entirely ignored. So, if here comes a normal
* button press (neither release nor repeat) the press is a fresh one and
* no point in waiting for release
*
* This logic doesn't work for touchscreen which can send normal
* button events repeatedly before the first repeat (as in BUTTON_REPEAT).
* These cannot be distinguished from the very first touch
* but there's nothing we can do about it here
*/
if (*button == BUTTON_NONE || (*button & (BUTTON_REPEAT|BUTTON_REL)) == 0)
{
last->wait_for_release = false;
}
/* ********************************************************
* Can return button immediately, sys_event & multimedia
* button presses don't use the action system, Data from
* sys events can be pulled with button_get_data.
* BUTTON_REDRAW should result in a screen refresh
*/
if (*button == BUTTON_NONE || (*button & (SYS_EVENT|BUTTON_MULTIMEDIA)) != 0)
{
return true;
}
else if (*button == BUTTON_REDRAW)
{ /* screen refresh */
*button = ACTION_REDRAW;
return true;
}
/* *************************************************
* If waiting for release, Don't send any buttons
* through until we see the release event
*/
if (last->wait_for_release)
{
if (has_flag(*button, BUTTON_REL))
{ /* remember the button for button eating on context change */
last->wait_for_release = false;
last->button = *button;
}
*button = ACTION_NONE;
}
#ifdef HAVE_SCROLLWHEEL
/* *********************************************
* Scrollwheel doesn't generate release events
* further processing needed
*/
else if ((last->button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) != 0)
{
ret = false;
}
#endif
/* *************************************************************
* On Context Changed eat all buttons until the previous button
* was |BUTTON_REL (also eat the |BUTTON_REL button)
*/
else if ((cur->context != last->context) && ((last->button & BUTTON_REL) == 0))
{
if (has_flag(*button, BUTTON_REL))
{
last->button = *button;
last->action = ACTION_NONE;
}
*button = ACTION_NONE; /* "safest" return value */
}
/* ****************************
* regular button press,
* further processing needed
*/
else
{
ret = false;
}
/* current context might contain ALLOW_SOFTLOCK save prior to stripping it */
if (!ret)
{
last->context = cur->context;
}
return ret;
}
/*********************************************
* update_screen_has_lock sets screen_has_lock
* if passed context contains ALLOW_SOFTLOCK
* and removes ALLOW_SOFTLOCK from the passed
* context flag
*/
static inline void update_screen_has_lock(action_last_t *last, action_cur_t *cur)
{
#if defined(HAS_BUTTON_HOLD)
(void) last;
(void) cur;
return;
#else
last->screen_has_lock = has_flag(cur->context, ALLOW_SOFTLOCK);
cur->context &= ~ALLOW_SOFTLOCK;
#endif
}
/***********************************************
* get_action_touchscreen allows touchscreen
* presses to have short_press and repeat events
*/
static inline bool get_action_touchscreen(action_last_t *last, action_cur_t *cur)
{
#if !defined(HAVE_TOUCHSCREEN)
(void) last;
(void) cur;
return false;
#else
if (has_flag(cur->button, BUTTON_TOUCHSCREEN))
{
last->repeated = false;
last->ts_short_press = false;
if (has_flag(last->button, BUTTON_TOUCHSCREEN))
{
if (has_flag(cur->button, BUTTON_REL) &&
!has_flag(last->button, BUTTON_REPEAT))
{
last->ts_short_press = true;
}
else if (has_flag(cur->button, BUTTON_REPEAT))
{
last->repeated = true;
}
}
last->button = cur->button;
cur->action = ACTION_TOUCHSCREEN;
return true;
}
return false;
#endif
}
/******************************************************************************
* button_flip_horizontally, passed button is horizontally inverted to support
* RTL language if the given language and context combination require it
* Affected contexts: CONTEXT_STD, CONTEXT_TREE, CONTEXT_LIST, CONTEXT_MAINMENU
* Affected buttons with rtl language:
* BUTTON_LEFT, BUTTON_RIGHT,
* Affected buttons with rtl language and !simulator:
* BUTTON_SCROLL_BACK, BUTTON_SCROLL_FWD, BUTTON_MINUS, BUTTON_PLUS
*/
static inline void button_flip_horizontally(int context, int *button)
{
#if defined(BOOTLOADER)
(void) context;
(void) *button;
return;
#else
int newbutton = *button;
if (!(lang_is_rtl() && ((context == CONTEXT_STD) ||
(context == CONTEXT_TREE) || (context == CONTEXT_LIST) ||
(context == CONTEXT_MAINMENU))))
{
return;
}
#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
newbutton &= ~(BUTTON_LEFT | BUTTON_RIGHT);
if (has_flag(*button, BUTTON_LEFT))
{
newbutton |= BUTTON_RIGHT;
}
if (has_flag(*button, BUTTON_RIGHT))
{
newbutton |= BUTTON_LEFT;
}
#else
#warning "BUTTON_LEFT / BUTTON_RIGHT not defined!"
#endif
#ifndef SIMULATOR
#ifdef HAVE_SCROLLWHEEL
newbutton &= ~(BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD);
if (has_flag(*button, BUTTON_SCROLL_BACK))
{
newbutton |= BUTTON_SCROLL_FWD;
}
if (has_flag(*button, BUTTON_SCROLL_FWD))
{
newbutton |= BUTTON_SCROLL_BACK;
}
#endif
#if defined(BUTTON_MINUS) && defined(BUTTON_PLUS)
newbutton &= ~(BUTTON_MINUS | BUTTON_PLUS);
if (has_flag(*button, BUTTON_MINUS))
{
newbutton |= BUTTON_PLUS;
}
if (has_flag(*button, BUTTON_PLUS))
{
newbutton |= BUTTON_MINUS;
}
#endif
#endif /* !SIMULATOR */
*button = newbutton;
#endif /* !BOOTLOADER */
} /* button_flip_horizontally */
/**********************************************************************
* action_code_worker is the worker function for action_code_lookup.
* returns ACTION_UNKNOWN or the requested return value from the list.
* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
* *** is_prebutton can miss pre_buttons
* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
* * will be returned from the higher context
*/
static inline int action_code_worker(action_last_t *last,
action_cur_t *cur,
int *end )
{
int ret = ACTION_UNKNOWN;
int i = 0;
unsigned int found = 0;
while (cur->items[i].button_code != BUTTON_NONE)
{
if (cur->items[i].button_code == cur->button)
{
/********************************************************
* { Action Code, Button code, Prereq button code }
* CAVEAT: This will allways return the action without
* pre_button_code (pre_button_code = BUTTON_NONE)
* if it is found before 'falling through'
* to a lower 'chained' context.
*
* Example: button = UP|REL, last_button = UP;
* while looking in CONTEXT_WPS there is an action defined
* {ACTION_FOO, BUTTON_UP|BUTTON_REL, BUTTON_NONE}
* then ACTION_FOO in CONTEXT_WPS will be returned
* EVEN THOUGH you are expecting a fully matched
* ACTION_BAR from CONTEXT_STD
* {ACTION_BAR, BUTTON_UP|BUTTON_REL, BUTTON_UP}
*/
if (cur->items[i].pre_button_code == last->button)
{ /* Always allow an exact match */
found++;
*end = i;
}
else if (!found && cur->items[i].pre_button_code == BUTTON_NONE)
{ /* Only allow Loose match if exact match wasn't found */
found++;
*end = i;
}
}
else if (has_flag(cur->items[i].pre_button_code, cur->button))
{ /* This could be another action depending on next button press */
cur->is_prebutton = true;
if (found > 1) /* There is already an exact match */
{
break;
}
}
i++;
}
if (!found)
{
*end = i;
}
else
{
ret = cur->items[*end].action_code;
}
return ret;
}
/***************************************************************************
* get_next_context returns the next CONTEXT to be searched for action_code
* by action_code_lookup(); if needed it first continues incrementing till
* the end of current context map is reached; If there is another
* 'chained' context below the current context this new context is returned
* if there is not a 'chained' context to return, CONTEXT_STD is returned;
*/
static inline int get_next_context(const struct button_mapping *items, int i)
{
while (items[i].button_code != BUTTON_NONE)
{
i++;
}
return (items[i].action_code == ACTION_NONE ) ?
CONTEXT_STD : items[i].action_code;
}
/************************************************************************
* action_code_lookup passes current button, last button and is_prebutton
* to action_code_worker() which uses the current button map to
* lookup action_code.
* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
* *** is_prebutton can miss pre_buttons
* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
* * will be returned from the higher context see action_code_worker()
* for a more in-depth explanation
* places action into current_action
*/
static inline void action_code_lookup(action_last_t *last, action_cur_t *cur)
{
int action = ACTION_NONE;
int context = cur->context;
int i = 0;
cur->is_prebutton = false;
#ifdef HAVE_LOCKED_ACTIONS
/* This only applies to the first context, to allow locked contexts to
* specify a fall through to their non-locked version */
if (is_keys_locked())
context |= CONTEXT_LOCKED;
#endif
for(;;)
{
/* logf("context = %x",context); */
#if (BUTTON_REMOTE != 0)
if ((cur->button & BUTTON_REMOTE) != 0)
{
context |= CONTEXT_REMOTE;
}
#endif
if ((context & CONTEXT_PLUGIN) && cur->get_context_map)
cur->items = cur->get_context_map(context);
else
cur->items = get_context_mapping(context);
if (cur->items != NULL)
{
action = action_code_worker(last, cur, &i);
if (action == ACTION_UNKNOWN)
{
context = get_next_context(cur->items, i);
if (context != (int)CONTEXT_STOPSEARCHING)
{
i = 0;
continue;
}
}
}
/* No more items, action was found, or STOPSEARCHING was specified */
break;
}
cur->action = action;
}
#ifndef HAS_BUTTON_HOLD
/*************************************
* do_key_lock (dis)/enables softlock
* based on lock flag, last button and
* buttons still in queue are purged
* if HAVE_TOUCHSCREEN then depending
* on user selection it will be locked
* or unlocked as well
*/
static inline void do_key_lock(bool lock)
{
action_last.keys_locked = lock;
action_last.button = BUTTON_NONE;
button_clear_queue();
#if defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN)
/* disable touch device on keylock if std behavior or selected disable touch */
if (!has_flag(action_last.softlock_mask, SEL_ACTION_ENABLED) ||
has_flag(action_last.softlock_mask, SEL_ACTION_NOTOUCH))
{
button_enable_touch(!lock);
}
#endif
}
/**********************************************
* do_auto_softlock when user selects autolock
* unlock_combo stored for later unlock
* activates autolock on backlight timeout
* toggles autolock on / off by
* ACTION_STD_KEYLOCK presses;
*/
static inline int do_auto_softlock(action_last_t *last, action_cur_t *cur)
{
#if !defined(HAVE_BACKLIGHT)
(void) last;
return cur->action;
#else
int action = cur->action;
bool is_timeout = false;
int timeout;
if (has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK))
{
timeout = backlight_get_current_timeout();
is_timeout = (timeout > 0 && (current_tick > action_last.tick + timeout));
}
if (is_timeout)
{
do_key_lock(true);
#if defined(HAVE_TOUCHPAD)
/* if the touchpad is supposed to be off and the current buttonpress
* is from the touchpad, nullify both button and action. */
if (!has_flag(action_last.softlock_mask, SEL_ACTION_ENABLED) ||
has_flag(action_last.softlock_mask, SEL_ACTION_NOTOUCH))
{
cur->button = touchpad_filter(cur->button);
if (cur->button == BUTTON_NONE)
{
action = ACTION_NONE;
}
}
#endif
}
else if (action == ACTION_STD_KEYLOCK)
{
if (!has_flag(last->softlock_mask, SEL_ACTION_ALWAYSAUTOLOCK)) // normal operation, clear/arm autolock
{
last->unlock_combo = cur->button;/* set unlock combo to allow unlock */
last->softlock_mask ^= SEL_ACTION_ALOCK_OK;
action_handle_backlight(true, false);
/* If we don't wait for a moment for the backlight queue
* to process, the user will never see the message */
if (!is_backlight_on(false))
{
sleep(HZ/2);
}
if (has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK))
{
splash(HZ/2, ID2P(LANG_ACTION_AUTOLOCK_ON));
action = ACTION_REDRAW;
}
else
{
splash(HZ/2, ID2P(LANG_ACTION_AUTOLOCK_OFF));
}
} else if (!has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK)) // always autolock, but not currently armed
{
last->unlock_combo = cur->button;/* set unlock combo to allow unlock */
last->softlock_mask ^= SEL_ACTION_ALOCK_OK;
}
}
return action;
#endif /* HAVE_BACKLIGHT */
}
#endif /* HAS_BUTTON_HOLD */
/*****************************************************
* do_softlock Handles softlock once action is known
* selective softlock allows user selected actions to
* bypass a currently locked state, special lock state
* autolock is handled here as well if HAVE_BACKLIGHT
*/
static inline void do_softlock(action_last_t *last, action_cur_t *cur)
{
#if defined(HAS_BUTTON_HOLD)
(void) last;
(void) cur;
return;
#else
int action = cur->action;
/* check to make sure we don't get stuck without a way to unlock - if locked,
* we can still use unlock_combo to unlock */
if (!last->screen_has_lock && !last->keys_locked)
{
/* no need to check softlock return immediately */
return;
}
bool filtered = true;
bool notify_user = false;
bool sl_activate = true; /* standard softlock behavior */
if ((!last->keys_locked) && has_flag(last->softlock_mask, SEL_ACTION_AUTOLOCK))
{
action = do_auto_softlock(last, cur);
}
/* Lock/Unlock toggled by ACTION_STD_KEYLOCK presses*/
if ((action == ACTION_STD_KEYLOCK)
|| (last->keys_locked && last->unlock_combo == cur->button))
{
#ifdef HAVE_BACKLIGHT
// if backlight is off and keys are unlocked, do nothing and exit.
// The backlight should come on without locking keypad.
if ((!last->keys_locked) && (!is_backlight_on(false)))
{
return;
}
#endif
last->unlock_combo = cur->button;
do_key_lock(!last->keys_locked);
notify_user = true;
}
#if (BUTTON_REMOTE != 0)/* Allow remote actions through */
else if (has_flag(cur->button, BUTTON_REMOTE))
{
return;
}
#endif
else if (last->keys_locked && action != ACTION_REDRAW)
{
if (has_flag(last->softlock_mask, SEL_ACTION_ENABLED))
{
filtered = is_action_filtered(action, last->softlock_mask, cur->context);
sl_activate = !is_action_discarded(cur, filtered, &last->sl_filter_tick);
}
if (sl_activate)
{ /*All non-std softlock options are set to 0 if advanced sl is disabled*/
if (!has_flag(last->softlock_mask, SEL_ACTION_NONOTIFY))
{ /* always true on standard softlock behavior*/
notify_user = has_flag(cur->button, BUTTON_REL);
action = ACTION_REDRAW;
}
else
action = ACTION_NONE;
}
else if (!filtered)
{ /* catch blocked actions on fast repeated presses */
action = ACTION_NONE;
}
}/* keys_locked */
#ifdef BUTTON_POWER /*always notify if power button pressed while keys locked*/
notify_user |= (has_flag(cur->button, BUTTON_POWER|BUTTON_REL)
&& last->keys_locked);
#endif
if (notify_user)
{
action_handle_backlight(true, false);
#ifdef HAVE_BACKLIGHT
/* If we don't wait for a moment for the backlight queue to process,
* the user will never see the message
*/
if (!is_backlight_on(false))
{
sleep(HZ/2);
}
#endif
if (!has_flag(last->softlock_mask, SEL_ACTION_ALLNONOTIFY))
{
if (last->keys_locked)
{
splash(HZ/2, ID2P(LANG_KEYLOCK_ON));
}
else
{
splash(HZ/2, ID2P(LANG_KEYLOCK_OFF));
}
}
action = ACTION_REDRAW;
last->button = BUTTON_NONE;
button_clear_queue();
}
cur->action = action;
#endif/*!HAS_BUTTON_HOLD*/
}
/**********************************************************************
* update_action_last copies the current action values into action_last
* saving the current state & allowing get_action_worker() to return
* while waiting for the next button press; Since some actions take
* multiple buttons, this allows those actions to be looked up and
* returned in a non-blocking way;
* Returns action, checks\sets repeated, plays keyclick (if applicable)
*/
static inline int update_action_last(action_last_t *last, action_cur_t *cur)
{
int action = cur->action;
logf ("action system: context: %d last context: %d, action: %d, \
last action: %d, button %d, last btn: %d, last repeated: %d, \
last_data: %d", cur->context, last->context, cur->action,
last->action, cur->button, last->button, last->repeated, last->data);
if (action == last->action)
{
last->repeated = (current_tick < last->tick + REPEAT_WINDOW_TICKS);
}
else
{
last->repeated = false;
}
last->action = action;
last->button = cur->button;
last->data = button_get_data();
last->tick = current_tick;
/* Produce keyclick */
keyclick_click(false, action);
return action;
}
/********************************************************
* init_act_cur initializes passed struct action_cur_t
* with context, timeout,and get_context_map.
* other values set to default
* if get_context_map is NULL standard
* context mapping will be used
*/
static void init_act_cur(action_cur_t *cur,
int context, int timeout,
const struct button_mapping* (*get_context_map)(int))
{
cur->action = ACTION_UNKNOWN;
cur->button = BUTTON_NONE;
cur->context = context;
cur->is_prebutton = false;
cur->items = NULL;
cur->timeout = timeout;
cur->get_context_map = get_context_map;
}
/*******************************************************
* do_backlight allows exemptions to the backlight on
* user selected actions; Actions need to be looked up
* before the decision to turn on backlight is made,
* if selective backlighting is enabled then
* filter first keypress events may need
* to be taken into account as well
* IF SEL_ACTION_ENABLED then:
* Returns action or is FFKeypress is enabled,
* ACTION_NONE on first keypress
* delays backlight_on until action is known
* handles backlight_on if needed
*/
static inline int do_backlight(action_last_t *last, action_cur_t *cur, int action)
{
#if !defined(HAVE_BACKLIGHT)
(void) last;
(void) cur;
return action;
#else
if (!has_flag(last->backlight_mask, SEL_ACTION_ENABLED)
|| (action & (SYS_EVENT|BUTTON_MULTIMEDIA)) != 0
|| action == ACTION_REDRAW)
{
return action;
}
bool filtered;
bool bl_activate = false;
bool bl_is_off = !is_backlight_on(false);
#if CONFIG_CHARGING /* disable if on external power */
bl_is_off &= !(has_flag(last->backlight_mask, SEL_ACTION_NOEXT)
&& power_input_present());
#endif
/* skip if backlight on | incorrect context | SEL_ACTION_NOEXT + ext pwr */
if ((cur->context == CONTEXT_FM || cur->context == CONTEXT_WPS) && bl_is_off)
{
filtered = is_action_filtered(action, last->backlight_mask, cur->context);
bl_activate = !is_action_discarded(cur, filtered, &last->bl_filter_tick);
}
else /* standard backlight behaviour */
{
bl_activate = true;
}
if (action != ACTION_NONE && bl_activate)
{
action_handle_backlight(true, true);
/* Handle first keypress enables backlight only */
if (has_flag(last->backlight_mask, SEL_ACTION_FFKEYPRESS) && bl_is_off)
{
action = ACTION_NONE;
last->button = BUTTON_NONE;
}
}
else
{
action_handle_backlight(false, true);/* set ignore next true */
}
return action;
#endif /* !HAVE_BACKLIGHT */
}
/********************************************************************
* get_action_worker() searches the button list of the passed context
* for the just pressed button. If there is a match it returns the
* value from the list. If there is no match, the last item in the
* list "points" to the next context in a chain so the "chain" is
* followed until the button is found. ACTION_NONE int the button
* list will get CONTEXT_STD which is always the last list checked.
*
* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
* *** is_prebutton can miss pre_buttons
* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
* * will be returned from the higher context see action_code_worker()
* for a more in-depth explanation
*
* Timeout can be: TIMEOUT_NOBLOCK to return immediatly
* TIMEOUT_BLOCK to wait for a button press
* Any number >0 to wait that many ticks for a press
*/
static int get_action_worker(action_last_t *last, action_cur_t *cur)
{
send_event(GUI_EVENT_ACTIONUPDATE, NULL);
/*if button = none/special; returns immediately*/
if (action_poll_button(last, cur))
{
return cur->button;
}
update_screen_has_lock(last, cur);
if (get_action_touchscreen(last, cur))
{
return cur->action;
}
button_flip_horizontally(cur->context, &cur->button);
action_code_lookup(last, cur);
do_softlock(last, cur);
return update_action_last(last, cur);
}
/*
*******************************************************************************
* END INTERNAL ACTION FUNCTIONS ***********************************************
*******************************************************************************/
/******************************************************************************
* EXPORTED ACTION FUNCTIONS ***************************************************
*******************************************************************************
*/
#ifdef HAVE_TOUCHSCREEN
/* return BUTTON_NONE on error
* BUTTON_REPEAT if repeated press
* BUTTON_REPEAT|BUTTON_REL if release after repeated press
* BUTTON_REL if it's a short press = release after press
* BUTTON_TOUCHSCREEN if press
*/
int action_get_touchscreen_press(short *x, short *y)
{
int data;
int ret = BUTTON_TOUCHSCREEN;
if (!has_flag(action_last.button, BUTTON_TOUCHSCREEN))
{
return BUTTON_NONE;
}
data = button_get_data();
if (has_flag(action_last.button, BUTTON_REL))
{
*x = (action_last.ts_data&0xffff0000)>>16;
*y = (action_last.ts_data&0xffff);
}
else
{
*x = (data&0xffff0000)>>16;
*y = (data&0xffff);
}
action_last.ts_data = data;
if (action_last.repeated)
{
ret = BUTTON_REPEAT;
}
else if (action_last.ts_short_press)
{
ret = BUTTON_REL;
}
/* This is to return a BUTTON_REL after a BUTTON_REPEAT. */
else if (has_flag(action_last.button, BUTTON_REL))
{
ret = BUTTON_REPEAT|BUTTON_REL;
}
return ret;
}
int action_get_touchscreen_press_in_vp(short *x1, short *y1, struct viewport *vp)
{
short x, y;
int ret;
ret = action_get_touchscreen_press(&x, &y);
if (ret != BUTTON_NONE && viewport_point_within_vp(vp, x, y))
{
*x1 = x - vp->x;
*y1 = y - vp->y;
return ret;
}
if (has_flag(ret, BUTTON_TOUCHSCREEN))
{
return ACTION_UNKNOWN;
}
return BUTTON_NONE;
}
#endif
bool action_userabort(int timeout)
{
int action = get_custom_action(CONTEXT_STD, timeout, NULL);
bool ret = (action == ACTION_STD_CANCEL);
if (!ret)
{
default_event_handler(action);
}
return ret;
}
void action_wait_for_release(void)
{
action_last.wait_for_release = true;
}
int get_action(int context, int timeout)
{
action_cur_t current;
init_act_cur(&current, context, timeout, NULL);
int action = get_action_worker(&action_last, &current);
#ifdef HAVE_TOUCHSCREEN
if (action == ACTION_TOUCHSCREEN)
{
action = sb_touch_to_button(context);
}
#endif
action = do_backlight(&action_last, &current, action);
return action;
}
int get_custom_action(int context,int timeout,
const struct button_mapping* (*get_context_map)(int))
{
action_cur_t current;
init_act_cur(&current, context, timeout, get_context_map);
return get_action_worker(&action_last, &current);
}
intptr_t get_action_data(void)
{
return action_last.data;
}
int get_action_statuscode(int *button)
{
int ret = 0;
if (button)
{
*button = action_last.button;
}
if (has_flag(action_last.button, BUTTON_REMOTE))
{
ret |= ACTION_REMOTE;
}
if (action_last.repeated)
{
ret |= ACTION_REPEAT;
}
return ret;
}
#ifdef HAVE_BACKLIGHT
/* Enable selected actions to leave the backlight off */
void set_selective_backlight_actions(bool selective, unsigned int mask,
bool filter_fkp)
{
action_handle_backlight(true, selective);
if (selective) /* we will handle filter_first_keypress here so turn it off*/
{
set_backlight_filter_keypress(false);/* turnoff ffkp in button.c */
action_last.backlight_mask = mask | SEL_ACTION_ENABLED;
if (filter_fkp)
{
action_last.backlight_mask |= SEL_ACTION_FFKEYPRESS;
}
}
else
{
set_backlight_filter_keypress(filter_fkp);
action_last.backlight_mask = SEL_ACTION_NONE;
}
}
#endif /* HAVE_BACKLIGHT */
#ifndef HAS_BUTTON_HOLD
bool is_keys_locked(void)
{
return (action_last.keys_locked);
}
/* Enable selected actions to bypass a locked state */
void set_selective_softlock_actions(bool selective, unsigned int mask)
{
action_last.keys_locked = false;
if (selective)
{
action_last.softlock_mask = mask | SEL_ACTION_ENABLED;
}
else
{
action_last.softlock_mask = SEL_ACTION_NONE;
}
}
void action_autosoftlock_init(void)
{
action_cur_t cur;
int i = 0;
if (action_last.unlock_combo == BUTTON_NONE)
{
/* search CONTEXT_WPS, should be here */
cur.items = get_context_mapping(CONTEXT_WPS);
while (cur.items[i].button_code != BUTTON_NONE)
{
if (cur.items[i].action_code == ACTION_STD_KEYLOCK)
{
action_last.unlock_combo = cur.items[i].button_code;
break;
}
i = i + 1;
}
/* not there... let's try std
* I doubt any targets will need this, but... */
if (action_last.unlock_combo == BUTTON_NONE)
{
i = 0;
cur.items = get_context_mapping(CONTEXT_STD);
while (cur.items[i].button_code != BUTTON_NONE)
{
if (cur.items[i].action_code == ACTION_STD_KEYLOCK)
{
action_last.unlock_combo = cur.items[i].button_code;
break;
}
i = i + 1;
}
}
}
/* if we have autolock and alwaysautolock, go ahead and arm it */
if (has_flag(action_last.softlock_mask, SEL_ACTION_AUTOLOCK) &&
has_flag(action_last.softlock_mask, SEL_ACTION_ALWAYSAUTOLOCK) &&
(action_last.unlock_combo != BUTTON_NONE))
{
action_last.softlock_mask = action_last.softlock_mask | SEL_ACTION_ALOCK_OK;
}
return;
}
#endif /* !HAS_BUTTON_HOLD */
/*
*******************************************************************************
* END EXPORTED ACTION FUNCTIONS ***********************************************
*******************************************************************************/