A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1453 lines 44 kB view raw
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(&current, context, timeout, NULL); 1214 1215 int action = get_action_worker(&action_last, &current); 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, &current, 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(&current, context, timeout, get_context_map); 1320 1321 int action = get_action_worker(&action_last, &current); 1322 1323 action = do_backlight(&action_last, &current, 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*******************************************************************************/