A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2006 Jonathan Gordon
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#include <stdio.h>
23#include <string.h>
24#include <stdlib.h>
25
26#include "config.h"
27#include "lang.h"
28
29#if !defined(BOOTLOADER)
30#include "language.h"
31#endif
32
33#include "appevents.h"
34#include "button.h"
35#include "action.h"
36#include "kernel.h"
37#include "core_alloc.h"
38
39#include "splash.h"
40#include "settings.h"
41#include "misc.h"
42
43#ifdef HAVE_TOUCHSCREEN
44#include "statusbar-skinned.h"
45#include "viewport.h"
46#endif
47
48#ifdef HAVE_BACKLIGHT
49#include "backlight.h"
50#if CONFIG_CHARGING
51#include "power.h"
52#endif
53#endif /* HAVE_BACKLIGHT */
54
55/*#define LOGF_ENABLE*/
56#include "logf.h"
57
58#define REPEAT_WINDOW_TICKS HZ/4
59#define ACTION_FILTER_TICKS HZ/2 /* timeout between filtered actions SL/BL */
60
61/* act_cur holds action state during get_action() call */
62typedef struct
63{
64 int action;
65 int button;
66 int context;
67 int timeout;
68 const struct button_mapping *items;
69 const struct button_mapping* (*get_context_map)(int);
70 bool is_prebutton;
71} action_cur_t;
72
73/* act_last holds action state between get_action() calls */
74typedef struct
75{
76 int action;
77 long tick;
78 int button;
79 int context;
80 intptr_t data;
81
82#if defined(HAVE_BACKLIGHT)
83 unsigned int backlight_mask;
84 long bl_filter_tick;
85#endif
86
87#if !defined(HAS_BUTTON_HOLD)
88 long sl_filter_tick;
89 unsigned int softlock_mask;
90 int unlock_combo;
91 bool keys_locked;
92 bool screen_has_lock;
93
94#endif
95
96 bool repeated;
97 bool wait_for_release;
98
99#ifndef DISABLE_ACTION_REMAP
100 int key_remap;
101#endif
102
103#ifdef HAVE_TOUCHSCREEN
104 bool ts_short_press;
105 int ts_data;
106#endif
107} action_last_t;
108
109/* holds the action state between calls to get_action \ get_action_custom) */
110static action_last_t action_last =
111{
112 .action = ACTION_NONE,
113 .button = BUTTON_NONE | BUTTON_REL, /* allow the ipod wheel to
114 work on startup */
115 .context = CONTEXT_STD,
116 .data = 0,
117 .repeated = false,
118 .tick = 0,
119 .wait_for_release = false,
120
121#ifndef DISABLE_ACTION_REMAP
122 .key_remap = 0,
123#endif
124
125#ifdef HAVE_TOUCHSCREEN
126 .ts_data = 0,
127 .ts_short_press = false,
128#endif
129
130#ifdef HAVE_BACKLIGHT
131 .backlight_mask = SEL_ACTION_NONE,
132 .bl_filter_tick = 0,
133#endif
134
135#ifndef HAS_BUTTON_HOLD
136 .keys_locked = false,
137 .screen_has_lock = false,
138 .sl_filter_tick = 0,
139 .softlock_mask = SEL_ACTION_NONE,
140 .unlock_combo = BUTTON_NONE,
141#endif
142}; /* action_last_t action_last */
143
144/******************************************************************************
145** INTERNAL ACTION FUNCTIONS **************************************************
146*******************************************************************************
147*/
148
149/******************************************
150* has_flag compares value to a (SINGLE) flag
151* returns true if set, false otherwise
152*/
153static inline bool has_flag(unsigned int value, unsigned int flag)
154{
155 return ((value & flag) == flag);
156}
157
158#if defined(HAVE_BACKLIGHT) || !defined(HAS_BUTTON_HOLD)
159/* HELPER FUNCTIONS selective softlock and backlight */
160
161/****************************************************************
162* is_action_filtered, selective softlock and backlight use this
163* to lookup which actions are filtered, matches are only true if
164* action is found and supplied SEL_ACTION mask has the flag.
165* returns false if the action isn't found or isn't enabled,
166* true if the action is found and is enabled
167*/
168static bool is_action_filtered(int action, unsigned int mask, int context)
169{
170 bool match = false;
171
172 switch (action)
173 {
174 case ACTION_NONE:
175 break;
176 /* Actions that are not mapped will not turn on the backlight */
177 case ACTION_UNKNOWN:
178 match = has_flag(mask, SEL_ACTION_NOUNMAPPED);
179 break;
180 case ACTION_WPS_PLAY:
181 case ACTION_FM_PLAY:
182 match = has_flag(mask, SEL_ACTION_PLAY);
183 break;
184 /* case ACTION_STD_PREVREPEAT:*/ /* seek not exempted outside of WPS */
185 /* case ACTION_STD_NEXTREPEAT: */
186 case ACTION_WPS_SEEKBACK:
187 case ACTION_WPS_SEEKFWD:
188 case ACTION_WPS_STOPSEEK:
189 match = has_flag(mask, SEL_ACTION_SEEK);
190 break;
191 /* case ACTION_STD_PREV: */ /* skip/scrollwheel not */
192 /* case ACTION_STD_NEXT: */ /* exempted outside of WPS */
193 case ACTION_WPS_SKIPNEXT:
194 case ACTION_WPS_SKIPPREV:
195 case ACTION_FM_NEXT_PRESET:
196 case ACTION_FM_PREV_PRESET:
197 match = has_flag(mask, SEL_ACTION_SKIP);
198 break;
199#ifdef HAVE_VOLUME_IN_LIST
200 case ACTION_LIST_VOLUP: /* volume exempted outside of WPS */
201 case ACTION_LIST_VOLDOWN: /* ( if the device supports it )*/
202#endif
203 case ACTION_WPS_VOLUP:
204 case ACTION_WPS_VOLDOWN:
205 match = has_flag(mask, SEL_ACTION_VOL);
206 break;
207 case ACTION_SETTINGS_INC:/*FMS*/
208 case ACTION_SETTINGS_INCREPEAT:/*FMS*/
209 case ACTION_SETTINGS_DEC:/*FMS*/
210 case ACTION_SETTINGS_DECREPEAT:/*FMS*/
211 match = (context == CONTEXT_FM) && has_flag(mask, SEL_ACTION_VOL);
212 break;
213 default:
214 /* display action code of unfiltered actions */
215 logf ("unfiltered actions: context: %d action: %d, last btn: %d, \
216 mask: %d", context, action, action_last.button, mask);
217 break;
218 }/*switch*/
219
220 return match;
221}
222
223/*******************************************************************************
224* is_action_discarded:
225* Most every action takes two rounds through get_action_worker,
226* once for the keypress and once for the key release,
227* actions with pre_button codes take even more, some actions however, only
228* take once; actions defined with only a button and no release/repeat event,
229* these actions should be acted upon immediately except when we have
230* selective backlighting/softlock enabled and in this case we only act upon
231* them immediately if there is no chance they have another event tied to them
232* determined using !is_prebutton or if action is completed
233* returns true if event was discarded and false if it was kept
234*/
235static bool is_action_discarded(action_cur_t *cur, bool filtered, long *tick)
236{
237 bool ret = true;
238 bool completed = (cur->button & (BUTTON_REPEAT | BUTTON_REL)) != 0;
239
240#ifdef HAVE_SCROLLWHEEL
241 /* Scrollwheel doesn't generate release events */
242 completed |= (cur->button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) != 0;
243#endif
244
245 /*directly after a match a key release event may trigger another*/
246 if (filtered && cur->action != ACTION_UNKNOWN)
247 {
248 *tick = current_tick + ACTION_FILTER_TICKS;
249 }
250 /* has button been released/repeat or is this the only action it could be */
251 if (completed || !cur->is_prebutton)
252 {
253 /* if the action is not filtered and this isn't just a
254 * key release event then return false
255 * keeping action, and reset tick
256 */
257 if (!filtered && *tick < current_tick)
258 {
259 *tick = 0;
260 ret = false;
261 }
262 }
263
264 return ret;
265}
266
267/*******************************************************
268* action_handle_backlight is used to both delay
269* and activate the backlight if HAVE_BACKLIGHT
270* and SEL_ACTION_ENABLED; backlight state is
271* set true/false and ignore_next sets the backlight
272* driver to ignore backlight_on commands from
273* other modules for a finite duration;
274* Ignore is set each time the action system
275* handles the backlight as a precaution since, if
276* the action system was not triggered the device would
277* appear unresponsive to the user.
278* If a backlight_on event hasn't been handled in the
279* ignore duration it will timeout and the next call
280* to backlight_on will trigger as normal
281*/
282static void action_handle_backlight(bool backlight, bool ignore_next)
283{
284#if !defined(HAVE_BACKLIGHT)
285 (void) backlight;
286 (void) ignore_next;
287 return;
288#else /* HAVE_BACKLIGHT */
289 if (backlight)
290 {
291 backlight_on_ignore(false, 0);
292 backlight_on();
293 }
294
295 backlight_on_ignore(ignore_next, 5*HZ);/*must be set everytime we handle bl*/
296
297#ifdef HAVE_BUTTON_LIGHT
298 if (backlight)
299 {
300 buttonlight_on_ignore(false, 0);
301 buttonlight_on();
302 }
303
304 buttonlight_on_ignore(ignore_next, 5*HZ);/* as a precautionary fallback */
305#endif /* HAVE_BUTTON_LIGHT */
306
307#endif/* HAVE_BACKLIGHT */
308}
309
310#endif /*defined(HAVE_BACKLIGHT) || !defined(HAS_BUTTON_HOLD) HELPER FUNCTIONS*/
311
312/******************************************************************
313* action_poll_button filters button presses for get_action_worker;
314* if button_get_w_tmo returns...
315* BUTTON_NONE, SYS_EVENTS, MULTIMEDIA BUTTONS, ACTION_REDRAW
316* they are allowed to pass immediately through to handler.
317* if waiting for button release ACTION_NONE is returned until
318* button is released/repeated.
319*/
320static inline bool action_poll_button(action_last_t *last, action_cur_t *cur)
321{
322 bool ret = true;
323 int *button = &cur->button;
324
325 *button = button_get_w_tmo(cur->timeout);
326
327 /* ********************************************************
328 * Can return button immediately, sys_event & multimedia
329 * button presses don't use the action system, Data from
330 * sys events can be pulled with button_get_data.
331 * BUTTON_REDRAW should result in a screen refresh
332 */
333 if (*button == BUTTON_NONE || (*button & (SYS_EVENT|BUTTON_MULTIMEDIA)) != 0)
334 {
335 return true;
336 }
337 else if (*button == BUTTON_REDRAW)
338 { /* screen refresh */
339 *button = ACTION_REDRAW;
340 return true;
341 }
342 /* *************************************************
343 * If waiting for release, Don't send any buttons
344 * through until we see the release event
345 */
346 if (last->wait_for_release)
347 {
348 if (has_flag(*button, BUTTON_REL))
349 { /* remember the button for button eating on context change */
350 last->wait_for_release = false;
351 last->button = *button;
352 }
353
354 *button = ACTION_NONE;
355 }
356#ifdef HAVE_SCROLLWHEEL
357 /* *********************************************
358 * Scrollwheel doesn't generate release events
359 * further processing needed
360 */
361 else if ((last->button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) != 0)
362 {
363 ret = false;
364 }
365#endif
366 /* *************************************************************
367 * On Context Changed eat all buttons until the previous button
368 * was |BUTTON_REL (also eat the |BUTTON_REL button)
369 */
370 else if ((cur->context != last->context) && ((last->button & BUTTON_REL) == 0))
371 {
372 if (has_flag(*button, BUTTON_REL))
373 {
374 last->button = *button;
375 last->action = ACTION_NONE;
376 }
377
378 *button = ACTION_NONE; /* "safest" return value */
379 }
380 /* ****************************
381 * regular button press,
382 * further processing needed
383 */
384 else
385 {
386 ret = false;
387 }
388
389 /* current context might contain ALLOW_SOFTLOCK save prior to stripping it */
390 if (!ret)
391 {
392 last->context = cur->context;
393 }
394
395 return ret;
396}
397
398/*********************************************
399* update_screen_has_lock sets screen_has_lock
400* if passed context contains ALLOW_SOFTLOCK
401* and removes ALLOW_SOFTLOCK from the passed
402* context flag
403*/
404static inline void update_screen_has_lock(action_last_t *last, action_cur_t *cur)
405{
406#if defined(HAS_BUTTON_HOLD)
407 (void) last;
408 (void) cur;
409 return;
410#else
411 last->screen_has_lock = has_flag(cur->context, ALLOW_SOFTLOCK);
412 cur->context &= ~ALLOW_SOFTLOCK;
413#endif
414}
415
416/***********************************************
417* get_action_touchscreen allows touchscreen
418* presses to have short_press and repeat events
419*/
420static inline bool get_action_touchscreen(action_last_t *last, action_cur_t *cur)
421{
422
423#if !defined(HAVE_TOUCHSCREEN)
424 (void) last;
425 (void) cur;
426 return false;
427#else
428 if (has_flag(cur->button, BUTTON_TOUCHSCREEN))
429 {
430 last->repeated = false;
431 last->ts_short_press = false;
432 if (has_flag(last->button, BUTTON_TOUCHSCREEN))
433 {
434 if (has_flag(cur->button, BUTTON_REL) &&
435 !has_flag(last->button, BUTTON_REPEAT))
436 {
437 last->ts_short_press = true;
438 }
439 else if (has_flag(cur->button, BUTTON_REPEAT))
440 {
441 last->repeated = true;
442 }
443 }
444
445 last->button = cur->button;
446 last->tick = current_tick;
447 cur->action = ACTION_TOUCHSCREEN;
448 return true;
449 }
450
451 return false;
452#endif
453}
454
455/******************************************************************************
456* button_flip_horizontally, passed button is horizontally inverted to support
457* RTL language if the given language and context combination require it
458* Affected contexts: CONTEXT_STD, CONTEXT_TREE, CONTEXT_LIST, CONTEXT_MAINMENU
459* Affected buttons with rtl language:
460* BUTTON_LEFT, BUTTON_RIGHT,
461* Affected buttons with rtl language and !simulator:
462* BUTTON_SCROLL_BACK, BUTTON_SCROLL_FWD, BUTTON_MINUS, BUTTON_PLUS
463*/
464static inline void button_flip_horizontally(int context, int *button)
465{
466
467#if defined(BOOTLOADER)
468 (void) context;
469 (void) *button;
470 return;
471#else
472 int newbutton = *button;
473 if (!(lang_is_rtl() && ((context == CONTEXT_STD) ||
474 (context == CONTEXT_TREE) || (context == CONTEXT_LIST) ||
475 (context == CONTEXT_MAINMENU))))
476 {
477 return;
478 }
479
480#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
481 newbutton &= ~(BUTTON_LEFT | BUTTON_RIGHT);
482 if (has_flag(*button, BUTTON_LEFT))
483 {
484 newbutton |= BUTTON_RIGHT;
485 }
486
487 if (has_flag(*button, BUTTON_RIGHT))
488 {
489 newbutton |= BUTTON_LEFT;
490 }
491#elif !defined(NO_BUTTON_LR)
492#warning "BUTTON_LEFT / BUTTON_RIGHT not defined!"
493#endif
494
495#ifndef SIMULATOR
496#ifdef HAVE_SCROLLWHEEL
497 newbutton &= ~(BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD);
498 if (has_flag(*button, BUTTON_SCROLL_BACK))
499 {
500 newbutton |= BUTTON_SCROLL_FWD;
501 }
502
503 if (has_flag(*button, BUTTON_SCROLL_FWD))
504 {
505 newbutton |= BUTTON_SCROLL_BACK;
506 }
507#endif
508
509#if defined(BUTTON_MINUS) && defined(BUTTON_PLUS)
510 newbutton &= ~(BUTTON_MINUS | BUTTON_PLUS);
511 if (has_flag(*button, BUTTON_MINUS))
512 {
513 newbutton |= BUTTON_PLUS;
514 }
515
516 if (has_flag(*button, BUTTON_PLUS))
517 {
518 newbutton |= BUTTON_MINUS;
519 }
520#endif
521#endif /* !SIMULATOR */
522
523 *button = newbutton;
524#endif /* !BOOTLOADER */
525} /* button_flip_horizontally */
526
527/**********************************************************************
528* action_code_worker is the worker function for action_code_lookup.
529* returns ACTION_UNKNOWN or the requested return value from the list.
530* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
531* *** is_prebutton can miss pre_buttons
532* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
533* * will be returned from the higher context
534*/
535static inline int action_code_worker(action_last_t *last,
536 action_cur_t *cur,
537 int *end )
538{
539 int ret = ACTION_UNKNOWN;
540 int i = *end;
541 unsigned int found = 0;
542 while (cur->items[i].button_code != BUTTON_NONE)
543 {
544 if (cur->items[i].button_code == cur->button)
545 {
546 /********************************************************
547 * { Action Code, Button code, Prereq button code }
548 * CAVEAT: This will allways return the action without
549 * pre_button_code (pre_button_code = BUTTON_NONE)
550 * if it is found before 'falling through'
551 * to a lower 'chained' context.
552 *
553 * Example: button = UP|REL, last_button = UP;
554 * while looking in CONTEXT_WPS there is an action defined
555 * {ACTION_FOO, BUTTON_UP|BUTTON_REL, BUTTON_NONE}
556 * then ACTION_FOO in CONTEXT_WPS will be returned
557 * EVEN THOUGH you are expecting a fully matched
558 * ACTION_BAR from CONTEXT_STD
559 * {ACTION_BAR, BUTTON_UP|BUTTON_REL, BUTTON_UP}
560 */
561 if (cur->items[i].pre_button_code == last->button)
562 { /* Always allow an exact match */
563 found++;
564 *end = i;
565 }
566 else if (!found && cur->items[i].pre_button_code == BUTTON_NONE)
567 { /* Only allow Loose match if exact match wasn't found */
568 found++;
569 *end = i;
570 }
571 }
572 else if (has_flag(cur->items[i].pre_button_code, cur->button))
573 { /* This could be another action depending on next button press */
574 cur->is_prebutton = true;
575 if (found > 1) /* There is already an exact match */
576 {
577 break;
578 }
579 }
580 i++;
581 }
582
583 if (!found)
584 {
585 *end = i;
586 }
587 else
588 {
589 ret = cur->items[*end].action_code;
590 }
591
592 return ret;
593}
594
595/***************************************************************************
596* get_next_context returns the next CONTEXT to be searched for action_code
597* by action_code_lookup(); if needed it first continues incrementing till
598* the end of current context map is reached; If there is another
599* 'chained' context below the current context this new context is returned
600* if there is not a 'chained' context to return, CONTEXT_STD is returned;
601*/
602static inline int get_next_context(const struct button_mapping *items, int i)
603{
604 while (items[i].button_code != BUTTON_NONE)
605 {
606 i++;
607 }
608
609 return (items[i].action_code == ACTION_NONE ) ?
610 CONTEXT_STD : items[i].action_code;
611}
612
613/************************************************************************
614* action_code_lookup passes current button, last button and is_prebutton
615* to action_code_worker() which uses the current button map to
616* lookup action_code.
617* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
618* *** is_prebutton can miss pre_buttons
619* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
620* * will be returned from the higher context see action_code_worker()
621* for a more in-depth explanation
622* places action into current_action
623*/
624
625static inline void action_code_lookup(action_last_t *last, action_cur_t *cur)
626{
627 int action, i;
628 int context = cur->context;
629 cur->is_prebutton = false;
630
631#if !defined(HAS_BUTTON_HOLD) && !defined(BOOTLOADER)
632 /* This only applies to the first context, to allow locked contexts to
633 * specify a fall through to their non-locked version */
634 if (is_keys_locked())
635 context |= CONTEXT_LOCKED;
636#endif
637
638#ifndef DISABLE_ACTION_REMAP
639 /* attempt to look up the button in user supplied remap */
640 if(last->key_remap && (context & CONTEXT_PLUGIN) == 0)
641 {
642 if ((cur->button & BUTTON_REMOTE) != 0)
643 {
644 context |= CONTEXT_REMOTE;
645 }
646 cur->items = core_get_data(last->key_remap);
647 i = 0;
648 action = ACTION_UNKNOWN;
649 /* check the lut at the beginning for the desired context */
650 while (cur->items[i].button_code != BUTTON_NONE)
651 {
652 if (cur->items[i].action_code == CORE_CONTEXT_REMAP(context))
653 {
654 i = cur->items[i].button_code;
655 action = action_code_worker(last, cur, &i);
656 if (action != ACTION_UNKNOWN)
657 {
658 cur->action = action;
659 return;
660 }
661 }
662 i++;
663 }
664 }
665#endif
666
667 i = 0;
668 action = ACTION_NONE;
669 /* attempt to look up the button in the in-built keymaps */
670 for(;;)
671 {
672 /* logf("context = %x",context); */
673#if (BUTTON_REMOTE != 0)
674 if ((cur->button & BUTTON_REMOTE) != 0)
675 {
676 context |= CONTEXT_REMOTE;
677 }
678#endif
679
680 if ((context & CONTEXT_PLUGIN) && cur->get_context_map)
681 {
682 cur->items = cur->get_context_map(context);
683 }
684 else
685 {
686 cur->items = get_context_mapping(context);
687 }
688
689 if (cur->items != NULL)
690 {
691 action = action_code_worker(last, cur, &i);
692
693 if (action == ACTION_UNKNOWN)
694 {
695 context = get_next_context(cur->items, i);
696
697 if (context != (int)CONTEXT_STOPSEARCHING)
698 {
699 i = 0;
700 continue;
701 }
702 }
703 }
704 /* No more items, action was found, or STOPSEARCHING was specified */
705 break;
706 }
707 cur->action = action;
708}
709
710#ifndef HAS_BUTTON_HOLD
711/*************************************
712* do_key_lock (dis)/enables softlock
713* based on lock flag, last button and
714* buttons still in queue are purged
715* if HAVE_TOUCHSCREEN then depending
716* on user selection it will be locked
717* or unlocked as well
718*/
719static inline void do_key_lock(bool lock)
720{
721 action_last.keys_locked = lock;
722 action_last.button = BUTTON_NONE;
723 button_clear_queue();
724#if defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN)
725 /* disable touch device on keylock if std behavior or selected disable touch */
726 if (!has_flag(action_last.softlock_mask, SEL_ACTION_ENABLED) ||
727 has_flag(action_last.softlock_mask, SEL_ACTION_NOTOUCH))
728 {
729 button_enable_touch(!lock);
730 }
731#endif
732}
733
734/**********************************************
735* do_auto_softlock when user selects autolock
736* unlock_combo stored for later unlock
737* activates autolock on backlight timeout
738* toggles autolock on / off by
739* ACTION_STD_KEYLOCK presses;
740*/
741static inline int do_auto_softlock(action_last_t *last, action_cur_t *cur)
742{
743
744#if !defined(HAVE_BACKLIGHT)
745 (void) last;
746 return cur->action;
747#else
748 int action = cur->action;
749 bool is_timeout = false;
750 int timeout;
751 if (has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK))
752 {
753 timeout = backlight_get_current_timeout();
754 is_timeout = (timeout > 0 && (current_tick > action_last.tick + timeout));
755 }
756
757 if (is_timeout)
758 {
759 do_key_lock(true);
760
761#if defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN)
762 /* if the touchpad is supposed to be off and the current buttonpress
763 * is from the touchpad, nullify both button and action. */
764 if (!has_flag(action_last.softlock_mask, SEL_ACTION_ENABLED) ||
765 has_flag(action_last.softlock_mask, SEL_ACTION_NOTOUCH))
766 {
767#if defined(HAVE_TOUCHPAD)
768 cur->button = touchpad_filter(cur->button);
769#endif
770#if defined(HAVE_TOUCHSCREEN)
771 const int touch_fakebuttons =
772 BUTTON_TOPLEFT | BUTTON_TOPMIDDLE | BUTTON_TOPRIGHT |
773 BUTTON_MIDLEFT | BUTTON_CENTER | BUTTON_MIDRIGHT |
774 BUTTON_BOTTOMLEFT | BUTTON_BOTTOMMIDDLE | BUTTON_BOTTOMRIGHT;
775 if (has_flag(cur->button, BUTTON_TOUCHSCREEN))
776 cur->button = BUTTON_NONE;
777 else
778 cur->button &= ~touch_fakebuttons;
779#endif
780 if (cur->button == BUTTON_NONE)
781 {
782 action = ACTION_NONE;
783 }
784 }
785#endif
786 }
787 else if (action == ACTION_STD_KEYLOCK)
788 {
789 if (!has_flag(last->softlock_mask, SEL_ACTION_ALWAYSAUTOLOCK)) // normal operation, clear/arm autolock
790 {
791 last->unlock_combo = cur->button;/* set unlock combo to allow unlock */
792 last->softlock_mask ^= SEL_ACTION_ALOCK_OK;
793 action_handle_backlight(true, false);
794 /* If we don't wait for a moment for the backlight queue
795 * to process, the user will never see the message */
796 if (!is_backlight_on(false))
797 {
798 sleep(HZ/2);
799 }
800
801 if (has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK))
802 {
803 splash(HZ/2, ID2P(LANG_ACTION_AUTOLOCK_ON));
804 action = ACTION_REDRAW;
805 }
806 else
807 {
808 splash(HZ/2, ID2P(LANG_ACTION_AUTOLOCK_OFF));
809 }
810 } else if (!has_flag(last->softlock_mask, SEL_ACTION_ALOCK_OK)) // always autolock, but not currently armed
811 {
812 last->unlock_combo = cur->button;/* set unlock combo to allow unlock */
813 last->softlock_mask ^= SEL_ACTION_ALOCK_OK;
814 }
815 }
816
817 return action;
818#endif /* HAVE_BACKLIGHT */
819}
820
821#endif /* HAS_BUTTON_HOLD */
822
823/*****************************************************
824* do_softlock Handles softlock once action is known
825* selective softlock allows user selected actions to
826* bypass a currently locked state, special lock state
827* autolock is handled here as well if HAVE_BACKLIGHT
828*/
829static inline void do_softlock(action_last_t *last, action_cur_t *cur)
830{
831#if defined(HAS_BUTTON_HOLD)
832 (void) last;
833 (void) cur;
834 return;
835#else
836 int action = cur->action;
837
838 /* check to make sure we don't get stuck without a way to unlock - if locked,
839 * we can still use unlock_combo to unlock */
840 if (!last->screen_has_lock && !last->keys_locked)
841 {
842 /* no need to check softlock return immediately */
843 return;
844 }
845
846 bool filtered = true;
847 bool notify_user = false;
848 bool sl_activate = true; /* standard softlock behavior */
849
850 if ((!last->keys_locked) && has_flag(last->softlock_mask, SEL_ACTION_AUTOLOCK))
851 {
852 action = do_auto_softlock(last, cur);
853 }
854
855 /* Lock/Unlock toggled by ACTION_STD_KEYLOCK presses*/
856 if ((action == ACTION_STD_KEYLOCK)
857 || (last->keys_locked && last->unlock_combo == cur->button))
858 {
859#ifdef HAVE_BACKLIGHT
860 // if backlight is off and keys are unlocked, do nothing and exit.
861 // The backlight should come on without locking keypad.
862 if ((!last->keys_locked) && (!is_backlight_on(false)))
863 {
864 return;
865 }
866#endif
867 last->unlock_combo = cur->button;
868 do_key_lock(!last->keys_locked);
869 notify_user = true;
870 }
871#if (BUTTON_REMOTE != 0)/* Allow remote actions through */
872 else if (has_flag(cur->button, BUTTON_REMOTE))
873 {
874 return;
875 }
876#endif
877
878 else if (last->keys_locked && action != ACTION_REDRAW)
879 {
880 if (has_flag(last->softlock_mask, SEL_ACTION_ENABLED))
881 {
882 filtered = is_action_filtered(action, last->softlock_mask, cur->context);
883
884 sl_activate = !is_action_discarded(cur, filtered, &last->sl_filter_tick);
885 }
886
887 if (sl_activate)
888 { /*All non-std softlock options are set to 0 if advanced sl is disabled*/
889 if (!has_flag(last->softlock_mask, SEL_ACTION_NONOTIFY))
890 { /* always true on standard softlock behavior*/
891 notify_user = has_flag(cur->button, BUTTON_REL);
892 action = ACTION_REDRAW;
893 }
894 else
895 action = ACTION_NONE;
896 }
897 else if (!filtered)
898 { /* catch blocked actions on fast repeated presses */
899 action = ACTION_NONE;
900 }
901 }/* keys_locked */
902
903#ifdef BUTTON_POWER /*always notify if power button pressed while keys locked*/
904 notify_user |= (has_flag(cur->button, BUTTON_POWER|BUTTON_REL)
905 && last->keys_locked);
906#endif
907
908 if (notify_user)
909 {
910 action_handle_backlight(true, false);
911
912#ifdef HAVE_BACKLIGHT
913 /* If we don't wait for a moment for the backlight queue to process,
914 * the user will never see the message
915 */
916 if (!is_backlight_on(false))
917 {
918 sleep(HZ/2);
919 }
920#endif
921 if (!has_flag(last->softlock_mask, SEL_ACTION_ALLNONOTIFY))
922 {
923 if (last->keys_locked)
924 {
925 splash(HZ/2, ID2P(LANG_KEYLOCK_ON));
926 }
927 else
928 {
929 splash(HZ/2, ID2P(LANG_KEYLOCK_OFF));
930 }
931 }
932
933 action = ACTION_REDRAW;
934 last->button = BUTTON_NONE;
935 button_clear_queue();
936 }
937
938 cur->action = action;
939#endif/*!HAS_BUTTON_HOLD*/
940}
941
942/**********************************************************************
943* update_action_last copies the current action values into action_last
944* saving the current state & allowing get_action_worker() to return
945* while waiting for the next button press; Since some actions take
946* multiple buttons, this allows those actions to be looked up and
947* returned in a non-blocking way;
948* Returns action, checks\sets repeated, plays keyclick (if applicable)
949*/
950static inline int update_action_last(action_last_t *last, action_cur_t *cur)
951{
952 int action = cur->action;
953
954 logf ("action system: context: %d last context: %d, action: %d, \
955 last action: %d, button %d, last btn: %d, last repeated: %d, \
956 last_data: %d", cur->context, last->context, cur->action,
957 last->action, cur->button, last->button, last->repeated, last->data);
958
959 if (action == last->action)
960 {
961 last->repeated = (current_tick < last->tick + REPEAT_WINDOW_TICKS);
962 }
963 else
964 {
965 last->repeated = false;
966 }
967
968 last->action = action;
969 last->button = cur->button;
970 last->data = button_get_data();
971 last->tick = current_tick;
972
973 /* Produce keyclick */
974 keyclick_click(false, action);
975
976 return action;
977}
978
979/********************************************************
980* init_act_cur initializes passed struct action_cur_t
981* with context, timeout,and get_context_map.
982* other values set to default
983* if get_context_map is NULL standard
984* context mapping will be used
985*/
986static void init_act_cur(action_cur_t *cur,
987 int context, int timeout,
988 const struct button_mapping* (*get_context_map)(int))
989{
990 cur->action = ACTION_UNKNOWN;
991 cur->button = BUTTON_NONE;
992 cur->context = context;
993 cur->is_prebutton = false;
994 cur->items = NULL;
995 cur->timeout = timeout;
996 cur->get_context_map = get_context_map;
997}
998
999/*******************************************************
1000* do_backlight allows exemptions to the backlight on
1001* user selected actions; Actions need to be looked up
1002* before the decision to turn on backlight is made,
1003* if selective backlighting is enabled then
1004* filter first keypress events may need
1005* to be taken into account as well
1006* IF SEL_ACTION_ENABLED then:
1007* Returns action or is FFKeypress is enabled,
1008* ACTION_NONE on first keypress
1009* delays backlight_on until action is known
1010* handles backlight_on if needed
1011*/
1012static inline int do_backlight(action_last_t *last, action_cur_t *cur, int action)
1013{
1014
1015#if !defined(HAVE_BACKLIGHT)
1016 (void) last;
1017 (void) cur;
1018 return action;
1019#else
1020 if (!has_flag(last->backlight_mask, SEL_ACTION_ENABLED)
1021 || (action & (SYS_EVENT|BUTTON_MULTIMEDIA)) != 0
1022 || action == ACTION_REDRAW)
1023 {
1024 return action;
1025 }
1026
1027 bool filtered;
1028 bool bl_activate = false;
1029 bool bl_is_off = !is_backlight_on(false);
1030
1031#if CONFIG_CHARGING /* disable if on external power */
1032 bl_is_off &= !(has_flag(last->backlight_mask, SEL_ACTION_NOEXT)
1033 && power_input_present());
1034#endif
1035 /* skip if backlight on | incorrect context | SEL_ACTION_NOEXT + ext pwr */
1036 if (bl_is_off && (cur->context == CONTEXT_FM || cur->context == CONTEXT_WPS ||
1037 cur->context == CONTEXT_MAINMENU))
1038 {
1039 filtered = is_action_filtered(action, last->backlight_mask, cur->context);
1040 bl_activate = !is_action_discarded(cur, filtered, &last->bl_filter_tick);
1041 }
1042 else /* standard backlight behaviour */
1043 {
1044 bl_activate = true;
1045 }
1046
1047 if (action != ACTION_NONE && bl_activate)
1048 {
1049 action_handle_backlight(true, true);
1050 /* Handle first keypress enables backlight only */
1051 if (has_flag(last->backlight_mask, SEL_ACTION_FFKEYPRESS) && bl_is_off)
1052 {
1053 action = ACTION_NONE;
1054 last->button = BUTTON_NONE;
1055 }
1056 }
1057 else
1058 {
1059 action_handle_backlight(false, true);/* set ignore next true */
1060 }
1061
1062 return action;
1063#endif /* !HAVE_BACKLIGHT */
1064}
1065
1066/********************************************************************
1067* get_action_worker() searches the button list of the passed context
1068* for the just pressed button. If there is a match it returns the
1069* value from the list. If there is no match, the last item in the
1070* list "points" to the next context in a chain so the "chain" is
1071* followed until the button is found. ACTION_NONE int the button
1072* list will get CONTEXT_STD which is always the last list checked.
1073*
1074* BE AWARE IF YOUR DESIRED ACTION IS IN A LOWER 'CHAINED' CONTEXT::
1075* *** is_prebutton can miss pre_buttons
1076* ** An action without pre_button_code (pre_button_code = BUTTON_NONE)
1077* * will be returned from the higher context see action_code_worker()
1078* for a more in-depth explanation
1079*
1080* Timeout can be: TIMEOUT_NOBLOCK to return immediatly
1081* TIMEOUT_BLOCK to wait for a button press
1082* Any number >0 to wait that many ticks for a press
1083*/
1084static int get_action_worker(action_last_t *last, action_cur_t *cur)
1085{
1086 send_event(GUI_EVENT_ACTIONUPDATE, NULL);
1087
1088 /*if button = none/special; returns immediately*/
1089 if (action_poll_button(last, cur))
1090 {
1091 return cur->button;
1092 }
1093
1094 update_screen_has_lock(last, cur);
1095
1096 if (get_action_touchscreen(last, cur))
1097 {
1098 do_softlock(last, cur);
1099 return cur->action;
1100 }
1101
1102 button_flip_horizontally(cur->context, &cur->button);
1103
1104 action_code_lookup(last, cur);
1105
1106 do_softlock(last, cur);
1107
1108 return update_action_last(last, cur);
1109}
1110/*
1111*******************************************************************************
1112* END INTERNAL ACTION FUNCTIONS ***********************************************
1113*******************************************************************************/
1114
1115/******************************************************************************
1116* EXPORTED ACTION FUNCTIONS ***************************************************
1117*******************************************************************************
1118*/
1119#ifdef HAVE_TOUCHSCREEN
1120/* return BUTTON_NONE on error
1121 * BUTTON_REPEAT if repeated press
1122 * BUTTON_REPEAT|BUTTON_REL if release after repeated press
1123 * BUTTON_REL if it's a short press = release after press
1124 * BUTTON_TOUCHSCREEN if press
1125 */
1126int action_get_touchscreen_press(short *x, short *y)
1127{
1128
1129 int data;
1130 int ret = BUTTON_TOUCHSCREEN;
1131
1132 if (!has_flag(action_last.button, BUTTON_TOUCHSCREEN))
1133 {
1134 return BUTTON_NONE;
1135 }
1136
1137 data = button_get_data();
1138 if (has_flag(action_last.button, BUTTON_REL))
1139 {
1140 *x = (action_last.ts_data&0xffff0000)>>16;
1141 *y = (action_last.ts_data&0xffff);
1142 }
1143 else
1144 {
1145 *x = (data&0xffff0000)>>16;
1146 *y = (data&0xffff);
1147 }
1148
1149 action_last.ts_data = data;
1150
1151 if (action_last.repeated)
1152 {
1153 ret = BUTTON_REPEAT;
1154 }
1155 else if (action_last.ts_short_press)
1156 {
1157 ret = BUTTON_REL;
1158 }
1159 /* This is to return a BUTTON_REL after a BUTTON_REPEAT. */
1160 else if (has_flag(action_last.button, BUTTON_REL))
1161 {
1162 ret = BUTTON_REPEAT|BUTTON_REL;
1163 }
1164
1165 return ret;
1166}
1167
1168int action_get_touchscreen_press_in_vp(short *x1, short *y1, struct viewport *vp)
1169{
1170 short x, y;
1171 int ret;
1172
1173 ret = action_get_touchscreen_press(&x, &y);
1174
1175 if (ret != BUTTON_NONE && viewport_point_within_vp(vp, x, y))
1176 {
1177 *x1 = x - vp->x;
1178 *y1 = y - vp->y;
1179 return ret;
1180 }
1181
1182 if (has_flag(ret, BUTTON_TOUCHSCREEN))
1183 {
1184 return ACTION_UNKNOWN;
1185 }
1186
1187 return BUTTON_NONE;
1188}
1189#endif
1190
1191bool action_userabort(int timeout)
1192{
1193 int action = get_custom_action(CONTEXT_STD, timeout, NULL);
1194 bool ret = (action == ACTION_STD_CANCEL);
1195 if (!ret)
1196 {
1197 default_event_handler(action);
1198 }
1199
1200 return ret;
1201}
1202
1203void action_wait_for_release(void)
1204{
1205 if (!(action_last.button & BUTTON_REL))
1206 action_last.wait_for_release = true;
1207 button_clear_pressed();
1208}
1209
1210int get_action(int context, int timeout)
1211{
1212 action_cur_t current;
1213 init_act_cur(¤t, context, timeout, NULL);
1214
1215 int action = get_action_worker(&action_last, ¤t);
1216
1217#ifdef HAVE_TOUCHSCREEN
1218 if (action == ACTION_TOUCHSCREEN)
1219 {
1220 action = sb_touch_to_button(context);
1221 }
1222#endif
1223
1224 action = do_backlight(&action_last, ¤t, action);
1225
1226 return action;
1227}
1228
1229int action_set_keymap(struct button_mapping* core_keymap, int count)
1230{
1231#ifdef DISABLE_ACTION_REMAP
1232 (void)core_keymap;
1233 (void)count;
1234 return -1;
1235#else
1236 if (count <= 0 || core_keymap == NULL)
1237 return action_set_keymap_handle(0, 0);
1238
1239 size_t keyremap_buf_size = count * sizeof(struct button_mapping);
1240 int handle = core_alloc(keyremap_buf_size);
1241 if (handle < 0)
1242 return -6;
1243
1244 memcpy(core_get_data(handle), core_keymap, keyremap_buf_size);
1245 return action_set_keymap_handle(handle, count);
1246#endif
1247}
1248
1249int action_set_keymap_handle(int handle, int count)
1250{
1251#ifdef DISABLE_ACTION_REMAP
1252 (void)core_keymap;
1253 (void)count;
1254 return -1;
1255#else
1256 /* free an existing remap */
1257 action_last.key_remap = core_free(action_last.key_remap);
1258
1259 /* if clearing the remap, we're done */
1260 if (count <= 0 || handle <= 0)
1261 return 0;
1262
1263 /* validate the keymap */
1264 struct button_mapping* core_keymap = core_get_data(handle);
1265 struct button_mapping* entry = &core_keymap[count - 1];
1266 if (entry->action_code != (int) CONTEXT_STOPSEARCHING ||
1267 entry->button_code != BUTTON_NONE) /* check for sentinel at end*/
1268 {
1269 /* missing sentinel entry */
1270 return -1;
1271 }
1272
1273 /* check the lut at the beginning for invalid offsets */
1274 for (int i = 0; i < count; ++i)
1275 {
1276 entry = &core_keymap[i];
1277 if (entry->action_code == (int)CONTEXT_STOPSEARCHING)
1278 break;
1279
1280 if ((entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED)
1281 {
1282 int firstbtn = entry->button_code;
1283 int endpos = firstbtn + entry->pre_button_code;
1284 if (firstbtn > count || firstbtn < i || endpos > count)
1285 {
1286 /* offset out of bounds */
1287 return -2;
1288 }
1289
1290 if (core_keymap[endpos].button_code != BUTTON_NONE)
1291 {
1292 /* stop sentinel is not at end of action lut */
1293 return -3;
1294 }
1295 }
1296 else
1297 {
1298 /* something other than a context remap in the lut */
1299 return -4;
1300 }
1301
1302 if (i+1 >= count)
1303 {
1304 /* no sentinel in the lut */
1305 return -5;
1306 }
1307 }
1308
1309 /* success */
1310 action_last.key_remap = handle;
1311 return count;
1312#endif
1313}
1314
1315int get_custom_action(int context,int timeout,
1316 const struct button_mapping* (*get_context_map)(int))
1317{
1318 action_cur_t current;
1319 init_act_cur(¤t, context, timeout, get_context_map);
1320
1321 int action = get_action_worker(&action_last, ¤t);
1322
1323 action = do_backlight(&action_last, ¤t, action);
1324
1325 return action;
1326}
1327
1328intptr_t get_action_data(void)
1329{
1330 return action_last.data;
1331}
1332
1333int get_action_statuscode(int *button)
1334{
1335 int ret = 0;
1336 if (button)
1337 {
1338 *button = action_last.button;
1339 }
1340
1341 if (has_flag(action_last.button, BUTTON_REMOTE))
1342 {
1343 ret |= ACTION_REMOTE;
1344 }
1345
1346 if (action_last.repeated)
1347 {
1348 ret |= ACTION_REPEAT;
1349 }
1350
1351 return ret;
1352}
1353
1354#ifdef HAVE_BACKLIGHT
1355/* Enable selected actions to leave the backlight off */
1356void set_selective_backlight_actions(bool selective, unsigned int mask,
1357 bool filter_fkp)
1358{
1359 action_handle_backlight(true, selective);
1360 if (selective) /* we will handle filter_first_keypress here so turn it off*/
1361 {
1362 set_backlight_filter_keypress(false);/* turnoff ffkp in button.c */
1363 action_last.backlight_mask = mask | SEL_ACTION_ENABLED;
1364 if (filter_fkp)
1365 {
1366 action_last.backlight_mask |= SEL_ACTION_FFKEYPRESS;
1367 }
1368 }
1369 else
1370 {
1371 set_backlight_filter_keypress(filter_fkp);
1372 action_last.backlight_mask = SEL_ACTION_NONE;
1373 }
1374}
1375#endif /* HAVE_BACKLIGHT */
1376
1377#ifndef HAS_BUTTON_HOLD
1378bool is_keys_locked(void)
1379{
1380 return (action_last.keys_locked);
1381}
1382
1383/* Enable selected actions to bypass a locked state */
1384void set_selective_softlock_actions(bool selective, unsigned int mask)
1385{
1386 action_last.keys_locked = false;
1387 if (selective)
1388 {
1389 action_last.softlock_mask = mask | SEL_ACTION_ENABLED;
1390 }
1391 else
1392 {
1393 action_last.softlock_mask = SEL_ACTION_NONE;
1394 }
1395}
1396
1397/* look for an action in the given context, return button which triggers it.
1398 * (note: pre_button isn't taken into account here) */
1399static int find_button_for_action(int context, int action)
1400{
1401 const struct button_mapping *items;
1402 int i;
1403
1404 do
1405 {
1406 items = get_context_mapping(context);
1407 if (items == NULL)
1408 break;
1409
1410 for (i = 0; items[i].button_code != BUTTON_NONE; ++i)
1411 {
1412 if (items[i].action_code == action)
1413 return items[i].button_code;
1414 }
1415
1416 /* get chained context, if none it will be CONTEXT_STOPSEARCHING */
1417 context = items[i].action_code;
1418 } while (context != (int)CONTEXT_STOPSEARCHING);
1419
1420 return BUTTON_NONE;
1421}
1422
1423void action_autosoftlock_init(void)
1424{
1425 /* search in WPS and STD contexts for the keylock button combo */
1426 static const int contexts[2] = { CONTEXT_WPS, CONTEXT_STD };
1427
1428 for (int i = 0; i < 2; ++i)
1429 {
1430 int button = find_button_for_action(contexts[i], ACTION_STD_KEYLOCK);
1431 if (button != BUTTON_NONE)
1432 {
1433 action_last.unlock_combo = button;
1434 break;
1435 }
1436 }
1437
1438 /* if we have autolock and alwaysautolock, go ahead and arm it */
1439 if (has_flag(action_last.softlock_mask, SEL_ACTION_AUTOLOCK) &&
1440 has_flag(action_last.softlock_mask, SEL_ACTION_ALWAYSAUTOLOCK) &&
1441 (action_last.unlock_combo != BUTTON_NONE))
1442 {
1443 action_last.softlock_mask = action_last.softlock_mask | SEL_ACTION_ALOCK_OK;
1444 }
1445
1446 return;
1447}
1448#endif /* !HAS_BUTTON_HOLD */
1449
1450/*
1451*******************************************************************************
1452* END EXPORTED ACTION FUNCTIONS ***********************************************
1453*******************************************************************************/