summaryrefslogtreecommitdiffstats
path: root/apps/action.c
blob: 0c55227aacfd443bf1b322945499782b4c2c9fdb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
/***************************************************************************
 *             __________               __   ___.
 *   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:
        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:
        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;
        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;
    }

    newbutton &= ~(BUTTON_LEFT | BUTTON_RIGHT);
    if (has_flag(*button, BUTTON_LEFT))
    {
        newbutton |= BUTTON_RIGHT;
    }

    if (has_flag(*button, BUTTON_RIGHT))
    {
        newbutton |= BUTTON_LEFT;
    }
#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;

    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);
    }
    else if (action == ACTION_STD_KEYLOCK)
    {
        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));
        }
    }

    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;

    if (!last->screen_has_lock)
    { /* 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))
    {
        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 (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.screen_has_lock && 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;
    }
}
#endif /* !HAS_BUTTON_HOLD */
/*
*******************************************************************************
* END EXPORTED ACTION FUNCTIONS ***********************************************
*******************************************************************************/