A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 2035 lines 57 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 by Daniel Stenberg 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21#include <stdlib.h> 22#include <ctype.h> 23#include <stdio.h> 24#include <stdarg.h> 25#include <errno.h> 26#include "string-extra.h" 27#include "config.h" 28#include "misc.h" 29#include "system.h" 30#include "lcd.h" 31#include "language.h" /* is_lang_rtl() */ 32 33#ifdef HAVE_DIRCACHE 34#include "dircache.h" 35#endif 36#include "file.h" 37#ifndef __PCTOOL__ 38#include "pathfuncs.h" 39#include "lang.h" 40#include "dir.h" 41#ifdef HAVE_REMOTE_LCD 42#include "lcd-remote.h" 43#endif 44#include "action.h" 45#include "timefuncs.h" 46#include "screens.h" 47#include "usb_screen.h" 48#include "talk.h" 49#include "audio.h" 50#include "settings.h" 51#include "storage.h" 52#include "ata_idle_notify.h" 53#include "kernel.h" 54#include "power.h" 55#include "powermgmt.h" 56#include "backlight.h" 57#include "font.h" 58#include "splash.h" 59#include "tagcache.h" 60#include "sound.h" 61#include "playlist.h" 62#include "yesno.h" 63#include "viewport.h" 64#include "list.h" 65#include "fixedpoint.h" 66#include "open_plugin.h" 67 68#include "debug.h" 69 70#if CONFIG_TUNER 71#include "radio.h" 72#endif 73 74#ifdef IPOD_ACCESSORY_PROTOCOL 75#include "iap.h" 76#endif 77 78#if (CONFIG_STORAGE & STORAGE_MMC) 79#include "ata_mmc.h" 80#endif 81#include "tree.h" 82#include "eeprom_settings.h" 83#if defined(HAVE_RECORDING) && !defined(__PCTOOL__) 84#include "recording.h" 85#endif 86#if !defined(__PCTOOL__) 87#include "bmp.h" 88#include "icons.h" 89#endif /* !__PCTOOL__ */ 90#include "bookmark.h" 91#include "wps.h" 92#include "playback.h" 93#include "voice_thread.h" 94 95#ifdef BOOTFILE 96#if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) \ 97 || defined(HAVE_HOTSWAP_STORAGE_AS_MAIN) 98#include "rolo.h" 99#endif 100#endif 101 102#ifndef PLUGIN 103#include "core_alloc.h" /*core_load_bmp()*/ 104#endif 105 106#ifdef HAVE_HARDWARE_CLICK 107#include "piezo.h" 108#endif 109 110/* units used with output_dyn_value */ 111const unsigned char * const byte_units[] = 112{ 113 ID2P(LANG_BYTE), 114 ID2P(LANG_KIBIBYTE), 115 ID2P(LANG_MEBIBYTE), 116 ID2P(LANG_GIBIBYTE) 117}; 118 119const unsigned char * const * const kibyte_units = &byte_units[1]; 120 121/* units used with format_time_auto, option_select.c->option_get_valuestring() */ 122const unsigned char * const unit_strings_core[] = 123{ 124 [UNIT_INT] = "", [UNIT_MS] = "ms", 125 [UNIT_SEC] = "s", [UNIT_MIN] = "min", 126 [UNIT_HOUR]= "hr", [UNIT_KHZ] = "kHz", 127 [UNIT_DB] = "dB", [UNIT_PERCENT] = "%", 128 [UNIT_MAH] = "mAh", [UNIT_PIXEL] = "px", 129 [UNIT_PER_SEC] = "per sec", 130 [UNIT_HERTZ] = "Hz", 131 [UNIT_MB] = "MB", [UNIT_KBIT] = "kb/s", 132 [UNIT_PM_TICK] = "units/10ms", 133}; 134 135/* Format a large-range value for output, using the appropriate unit so that 136 * the displayed value is in the range 1 <= display < 1000 (1024 for "binary" 137 * units) if possible, and 3 significant digits are shown. If a buffer is 138 * given, the result is snprintf()'d into that buffer, otherwise the result is 139 * voiced.*/ 140char *output_dyn_value(char *buf, 141 int buf_size, 142 int64_t value, 143 const unsigned char * const *units, 144 unsigned int unit_count, 145 bool binary_scale) 146{ 147 unsigned int scale = binary_scale ? 1024 : 1000; 148 unsigned int fraction = 0; 149 unsigned int unit_no = 0; 150 uint64_t value_abs = (value < 0) ? -value : value; 151 char tbuf[5]; 152 int value2; 153 154 while (value_abs >= scale && unit_no < (unit_count - 1)) 155 { 156 fraction = value_abs % scale; 157 value_abs /= scale; 158 unit_no++; 159 } 160 161 value2 = (value < 0) ? -value_abs : value_abs; /* preserve sign */ 162 fraction = (fraction * 1000 / scale) / 10; 163 164 if (value_abs >= 100 || fraction >= 100 || !unit_no) 165 tbuf[0] = '\0'; 166 else if (value_abs >= 10) 167 snprintf(tbuf, sizeof(tbuf), "%01u", fraction / 10); 168 else 169 snprintf(tbuf, sizeof(tbuf), "%02u", fraction); 170 171 if (buf) 172 { 173 if (*tbuf) 174 snprintf(buf, buf_size, "%d%s%s%s", value2, str(LANG_POINT), 175 tbuf, P2STR(units[unit_no])); 176 else 177 snprintf(buf, buf_size, "%d%s", value2, P2STR(units[unit_no])); 178 } 179 else 180 { 181 talk_fractional(tbuf, value2, P2ID(units[unit_no])); 182 } 183 return buf; 184} 185 186/* Ask the user if they really want to erase the current dynamic playlist 187 * returns true if the playlist should be replaced */ 188bool warn_on_pl_erase(void) 189{ 190 if (global_status.resume_index != -1 && 191 global_settings.warnon_erase_dynplaylist && 192 !global_settings.party_mode && 193 playlist_modified(NULL)) 194 { 195 static const char *lines[] = 196 {ID2P(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)}; 197 static const struct text_message message={lines, 1}; 198 199 if (gui_syncyesno_run(&message, NULL, NULL) == YESNO_YES) 200 return true; 201 else 202 { 203 splash(HZ, ID2P(LANG_CANCEL)); 204 return false; 205 } 206 } 207 else 208 return true; 209} 210 211bool show_search_progress(bool init, int display_count, int current, int total) 212{ 213 static long last_tick, talked_tick; 214 215 /* Don't show splashes for 1/2 second after starting search */ 216 if (init) 217 { 218 last_tick = current_tick + HZ/2; 219 talked_tick = 0; 220 return true; 221 } 222 223 /* Update progress every 1/10 of a second */ 224 if (TIME_AFTER(current_tick, last_tick + HZ/10)) 225 { 226 if (total != current) 227 /* (voiced) */ 228 splash_progress(current, total, str(LANG_PLAYLIST_SEARCH_MSG), 229 display_count, str(LANG_OFF_ABORT)); 230 else 231 { 232 if (global_settings.talk_menu && 233 TIME_AFTER(current_tick, talked_tick + (HZ * 5))) 234 { 235 talked_tick = current_tick; 236 talk_number(display_count, false); 237 talk_id(LANG_PLAYLIST_SEARCH_MSG, true); 238 } 239 /* (voiced above) */ 240 splashf(0, str(LANG_PLAYLIST_SEARCH_MSG), 241 display_count, str(LANG_OFF_ABORT)); 242 } 243 244 if (action_userabort(TIMEOUT_NOBLOCK)) 245 return false; 246 last_tick = current_tick; 247 yield(); 248 } 249 250 return true; 251} 252 253/* Performance optimized version of the read_line() (see below) function. */ 254int fast_readline(int fd, char *buf, int buf_size, void *parameters, 255 int (*callback)(int n, char *buf, void *parameters)) 256{ 257 char *p, *next; 258 int rc, pos = 0; 259 int count = 0; 260 261 while ( 1 ) 262 { 263 next = NULL; 264 265 rc = read(fd, &buf[pos], buf_size - pos - 1); 266 if (rc >= 0) 267 buf[pos+rc] = '\0'; 268 269 if ( (p = strchr(buf, '\n')) != NULL) 270 { 271 *p = '\0'; 272 next = ++p; 273 } 274 275 if ( (p = strchr(buf, '\r')) != NULL) 276 { 277 *p = '\0'; 278 if (!next) 279 next = ++p; 280 } 281 282 rc = callback(count, buf, parameters); 283 if (rc < 0) 284 return rc; 285 286 count++; 287 if (next) 288 { 289 pos = buf_size - ((intptr_t)next - (intptr_t)buf) - 1; 290 memmove(buf, next, pos); 291 } 292 else 293 break ; 294 } 295 296 return 0; 297} 298 299/* parse a line from a configuration file. the line format is: 300 301 name: value 302 303 Any whitespace before setting name or value (after ':') is ignored. 304 A # as first non-whitespace character discards the whole line. 305 Function sets pointers to null-terminated setting name and value. 306 Returns false if no valid config entry was found. 307*/ 308 309bool settings_parseline(char* line, char** name, char** value) 310{ 311 char* ptr; 312 313 line = skip_whitespace(line); 314 315 if ( *line == '#' ) 316 return false; 317 318 ptr = strchr(line, ':'); 319 if ( !ptr ) 320 return false; 321 322 *name = line; 323 *ptr = '\0'; /* terminate previous */ 324 ptr++; 325 ptr = skip_whitespace(ptr); 326 *value = ptr; 327 328 /* strip whitespace from the right side of value */ 329 ptr += strlen(ptr); 330 ptr--; 331 while ((ptr > (*value) - 1) && isspace(*ptr)) 332 { 333 *ptr = '\0'; 334 ptr--; 335 } 336 337 return true; 338} 339 340static void system_flush(void) 341{ 342 playlist_shutdown(); 343 tree_flush(); 344 open_plugin_cache_flush(); 345 call_storage_idle_notifys(true); /*doesnt work on usb and shutdown from ata thread */ 346} 347 348static void system_restore(void) 349{ 350 tree_restore(); 351} 352 353static bool clean_shutdown(enum shutdown_type sd_type, 354 void (*callback)(void *), void *parameter) 355{ 356 long msg_id = -1; 357 358 if (!global_settings.show_shutdown_message && get_sleep_timer_active()) 359 { 360 talk_force_shutup(); 361 talk_disable(true); 362 } 363 364 status_save(true); 365 366#if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING) 367 if(!charger_inserted()) 368#endif 369 { 370 bool batt_safe = battery_level_safe(); 371#if defined(HAVE_RECORDING) 372 int audio_stat = audio_status(); 373#endif 374 375 FOR_NB_SCREENS(i) 376 { 377 screens[i].clear_display(); 378 screens[i].update(); 379 } 380 381 if (batt_safe) 382 { 383 int level; 384#ifdef HAVE_TAGCACHE 385 if (!tagcache_prepare_shutdown()) 386 { 387 cancel_shutdown(); 388 splash(HZ, ID2P(LANG_TAGCACHE_BUSY)); 389 return false; 390 } 391#endif 392 level = battery_level(); 393 if (level > 10 || level < 0) 394 { 395 if (global_settings.show_shutdown_message) 396 splash(0, str(LANG_SHUTTINGDOWN)); 397 } 398 else 399 { 400 msg_id = LANG_WARNING_BATTERY_LOW; 401 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_LOW), 402 str(LANG_SHUTTINGDOWN)); 403 } 404 } 405 else 406 { 407 msg_id = LANG_WARNING_BATTERY_EMPTY; 408 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_EMPTY), 409 str(LANG_SHUTTINGDOWN)); 410 } 411 412#ifdef HAVE_DISK_STORAGE 413 if (batt_safe) /* do not save on critical battery */ 414#endif 415 { 416#if defined(HAVE_RECORDING) 417 if (audio_stat & AUDIO_STATUS_RECORD) 418 { 419 rec_command(RECORDING_CMD_STOP); 420 /* wait for stop to complete */ 421 while (audio_status() & AUDIO_STATUS_RECORD) 422 sleep(1); 423 } 424#endif 425 bookmark_autobookmark(false); 426 427 /* audio_stop_recording == audio_stop for HWCODEC */ 428 audio_stop(); 429 430 if (callback != NULL) 431 callback(parameter); 432 433#if defined(HAVE_RECORDING) 434 audio_close_recording(); 435#endif 436 437 system_flush(); 438#ifdef HAVE_EEPROM_SETTINGS 439 if (firmware_settings.initialized) 440 { 441 firmware_settings.disk_clean = true; 442 firmware_settings.bl_version = 0; 443 eeprom_settings_store(); 444 } 445#endif 446 } 447#if defined(HAVE_DIRCACHE) && defined(HAVE_DISK_STORAGE) 448 else 449 dircache_disable(); 450#endif 451 452 if(global_settings.talk_menu) 453 { 454 bool enqueue = false; 455 if(msg_id != -1) 456 { 457 talk_id(msg_id, enqueue); 458 enqueue = true; 459 } 460 talk_id(LANG_SHUTTINGDOWN, enqueue); 461 voice_wait(); 462 } 463 464 shutdown_hw(sd_type); 465 } 466 return false; 467} 468 469bool list_stop_handler(void) 470{ 471 bool ret = false; 472 473#if CONFIG_TUNER 474 radio_stop(); 475#endif 476 477 /* Stop the music if it is playing */ 478 if(audio_status()) 479 { 480 if (!global_settings.party_mode) 481 { 482 bookmark_autobookmark(true); 483 audio_stop(); 484 ret = true; /* bookmarking can make a refresh necessary */ 485 } 486 } 487#if CONFIG_CHARGING 488#ifndef HAVE_POWEROFF_WHILE_CHARGING 489 { 490 static long last_off = 0; 491 492 if (TIME_BEFORE(current_tick, last_off + HZ/2)) 493 { 494 if (charger_inserted()) 495 { 496 charging_splash(); 497 ret = true; /* screen is dirty, caller needs to refresh */ 498 } 499 } 500 last_off = current_tick; 501 } 502#endif 503#endif /* CONFIG_CHARGING */ 504 return ret; 505} 506 507#if CONFIG_CHARGING 508static bool waiting_to_resume_play = false; 509static bool paused_on_unplugged = false; 510static long play_resume_tick; 511 512static void car_adapter_mode_processing(bool inserted) 513{ 514 if (global_settings.car_adapter_mode) 515 { 516 if(inserted) 517 { 518 /* 519 * Just got plugged in, delay & resume if we were playing 520 */ 521 if ((audio_status() & AUDIO_STATUS_PAUSE) && paused_on_unplugged) 522 { 523 /* delay resume a bit while the engine is cranking */ 524 play_resume_tick = current_tick + HZ*global_settings.car_adapter_mode_delay; 525 waiting_to_resume_play = true; 526 } 527 } 528 else 529 { 530 /* 531 * Just got unplugged, pause if playing 532 */ 533 if ((audio_status() & AUDIO_STATUS_PLAY) && 534 !(audio_status() & AUDIO_STATUS_PAUSE)) 535 { 536 pause_action(true); 537 paused_on_unplugged = true; 538 } 539 else if (!waiting_to_resume_play) 540 paused_on_unplugged = false; 541 waiting_to_resume_play = false; 542 } 543 } 544} 545 546static void car_adapter_tick(void) 547{ 548 if (waiting_to_resume_play) 549 { 550 if ((audio_status() & AUDIO_STATUS_PLAY) && 551 !(audio_status() & AUDIO_STATUS_PAUSE)) 552 waiting_to_resume_play = false; 553 if (TIME_AFTER(current_tick, play_resume_tick)) 554 { 555 if (audio_status() & AUDIO_STATUS_PAUSE) 556 { 557 queue_broadcast(SYS_CAR_ADAPTER_RESUME, 0); 558 } 559 waiting_to_resume_play = false; 560 } 561 } 562} 563 564void car_adapter_mode_init(void) 565{ 566 tick_add_task(car_adapter_tick); 567} 568#endif 569 570#ifdef HAVE_HEADPHONE_DETECTION 571static void hp_unplug_change(bool inserted) 572{ 573 static bool headphone_caused_pause = true; 574 575 if (global_settings.unplug_mode) 576 { 577 int audio_stat = audio_status(); 578 if (inserted) 579 { 580 backlight_on(); 581 if ((audio_stat & AUDIO_STATUS_PLAY) && 582 headphone_caused_pause && 583 global_settings.unplug_mode > 1 ) 584 { 585 unpause_action(true); 586 } 587 headphone_caused_pause = false; 588 } else { 589 if ((audio_stat & AUDIO_STATUS_PLAY) && 590 !(audio_stat & AUDIO_STATUS_PAUSE)) 591 { 592 headphone_caused_pause = true; 593 pause_action(false); 594 } 595 } 596 } 597 598#ifdef HAVE_SPEAKER 599 /* update speaker status */ 600 audio_enable_speaker(global_settings.speaker_mode); 601#endif 602} 603#endif /*HAVE_HEADPHONE_DETECTION*/ 604 605#ifdef HAVE_LINEOUT_DETECTION 606static void lo_unplug_change(bool inserted) 607{ 608#ifdef HAVE_LINEOUT_POWEROFF 609 lineout_set(inserted); 610#else 611#ifdef AUDIOHW_HAVE_LINEOUT 612 audiohw_set_lineout_volume(0,0); /*hp vol re-set by this function as well*/ 613#endif 614 static bool lineout_caused_pause = true; 615 616 if (global_settings.unplug_mode) 617 { 618 int audio_stat = audio_status(); 619 if (inserted) 620 { 621 backlight_on(); 622 if ((audio_stat & AUDIO_STATUS_PLAY) && 623 lineout_caused_pause && 624 global_settings.unplug_mode > 1 ) 625 unpause_action(true); 626 lineout_caused_pause = false; 627 } else { 628 if ((audio_stat & AUDIO_STATUS_PLAY) && 629 !(audio_stat & AUDIO_STATUS_PAUSE)) 630 { 631 lineout_caused_pause = true; 632 pause_action(false); 633 } 634 } 635 } 636#endif /*HAVE_LINEOUT_POWEROFF*/ 637} 638#endif /*HAVE_LINEOUT_DETECTION*/ 639 640long default_event_handler_ex(long event, void (*callback)(void *), void *parameter) 641{ 642#if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO) 643 static bool resume = false; 644#endif 645 646 switch(event) 647 { 648 case SYS_BATTERY_UPDATE: 649 if(global_settings.talk_battery_level) 650 { 651 talk_ids(true, VOICE_PAUSE, VOICE_PAUSE, 652 LANG_BATTERY_TIME, 653 TALK_ID(battery_level(), UNIT_PERCENT), 654 VOICE_PAUSE); 655 talk_force_enqueue_next(); 656 } 657 break; 658 case SYS_USB_CONNECTED: 659 if (callback != NULL) 660 callback(parameter); 661 { 662 system_flush(); 663#ifdef BOOTFILE 664#if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) 665 check_bootfile(false); /* gets initial size */ 666#endif 667#endif 668 gui_usb_screen_run(false); 669#ifdef BOOTFILE 670#if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) 671 check_bootfile(true); 672#endif 673#endif 674 system_restore(); 675 } 676 return SYS_USB_CONNECTED; 677 678 case SYS_POWEROFF: 679 case SYS_REBOOT: 680 { 681 enum shutdown_type sd_type; 682 if (event == SYS_POWEROFF) 683 sd_type = SHUTDOWN_POWER_OFF; 684 else 685 sd_type = SHUTDOWN_REBOOT; 686 687 if (!clean_shutdown(sd_type, callback, parameter)) 688 return event; 689 } 690 break; 691#if CONFIG_CHARGING 692 case SYS_CHARGER_CONNECTED: 693 car_adapter_mode_processing(true); 694 return SYS_CHARGER_CONNECTED; 695 696 case SYS_CHARGER_DISCONNECTED: 697 car_adapter_mode_processing(false); 698 reset_runtime(); 699 return SYS_CHARGER_DISCONNECTED; 700 701 case SYS_CAR_ADAPTER_RESUME: 702 unpause_action(true); 703 return SYS_CAR_ADAPTER_RESUME; 704#endif 705#ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN 706 case SYS_FS_CHANGED: 707 { 708 /* simple sanity: assume rockbox is on the first hotswappable 709 * driver, abort out if that one isn't inserted */ 710 int i; 711 for (i = 0; i < NUM_DRIVES; i++) 712 { 713 if (storage_removable(i) && !storage_present(i)) 714 return SYS_FS_CHANGED; 715 } 716 system_flush(); 717 check_bootfile(true); /* state gotten in main.c:init() */ 718 system_restore(); 719 } 720 return SYS_FS_CHANGED; 721#endif 722#ifdef HAVE_HEADPHONE_DETECTION 723 case SYS_PHONE_PLUGGED: 724 hp_unplug_change(true); 725 return SYS_PHONE_PLUGGED; 726 727 case SYS_PHONE_UNPLUGGED: 728 hp_unplug_change(false); 729 return SYS_PHONE_UNPLUGGED; 730#endif 731#ifdef HAVE_LINEOUT_DETECTION 732 case SYS_LINEOUT_PLUGGED: 733 lo_unplug_change(true); 734 return SYS_LINEOUT_PLUGGED; 735 736 case SYS_LINEOUT_UNPLUGGED: 737 lo_unplug_change(false); 738 return SYS_LINEOUT_UNPLUGGED; 739#endif 740#if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO) 741 /* stop playback if we receive a call */ 742 case SYS_CALL_INCOMING: 743 resume = audio_status() == AUDIO_STATUS_PLAY; 744 list_stop_handler(); 745 return SYS_CALL_INCOMING; 746 /* resume playback if needed */ 747 case SYS_CALL_HUNG_UP: 748 if (resume && playlist_resume() != -1) 749 { 750 playlist_start(global_status.resume_index, 751 global_status.resume_elapsed, 752 global_status.resume_offset); 753 } 754 resume = false; 755 return SYS_CALL_HUNG_UP; 756#endif 757#if (CONFIG_PLATFORM & PLATFORM_HOSTED) && defined(PLATFORM_HAS_VOLUME_CHANGE) 758 case SYS_VOLUME_CHANGED: 759 { 760 static bool firstvolume = true; 761 /* kludge: since this events go to the button_queue, 762 * event data is available in the last button data */ 763 int volume = button_get_data(); 764 DEBUGF("SYS_VOLUME_CHANGED: %d\n", volume); 765 if (global_status.volume != volume) { 766 global_status.volume = volume; 767 if (firstvolume) { 768 setvol(); 769 firstvolume = false; 770 } 771 } 772 return 0; 773 } 774#endif 775#ifdef HAVE_MULTIMEDIA_KEYS 776 /* multimedia keys on keyboards, headsets */ 777 case BUTTON_MULTIMEDIA_PLAYPAUSE: 778 { 779 int status = audio_status(); 780 if (status & AUDIO_STATUS_PLAY) 781 { 782 if (status & AUDIO_STATUS_PAUSE) 783 unpause_action(true); 784 else 785 pause_action(true); 786 } 787 else 788 if (playlist_resume() != -1) 789 { 790 playlist_start(global_status.resume_index, 791 global_status.resume_elapsed, 792 global_status.resume_offset); 793 } 794 return event; 795 } 796 case BUTTON_MULTIMEDIA_NEXT: 797 audio_next(); 798 return event; 799 case BUTTON_MULTIMEDIA_PREV: 800 audio_prev(); 801 return event; 802 case BUTTON_MULTIMEDIA_STOP: 803 list_stop_handler(); 804 return event; 805 case BUTTON_MULTIMEDIA_REW: 806 case BUTTON_MULTIMEDIA_FFWD: 807 /* not supported yet, needs to be done in the WPS */ 808 return 0; 809#endif 810 } 811 return 0; 812} 813 814long default_event_handler(long event) 815{ 816 return default_event_handler_ex(event, NULL, NULL); 817} 818 819#ifdef BOOTFILE 820#if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) || defined(HAVE_HOTSWAP_STORAGE_AS_MAIN) 821/* 822 memorize/compare details about the BOOTFILE 823 we don't use dircache because it may not be up to date after 824 USB disconnect (scanning in the background) 825*/ 826void check_bootfile(bool do_rolo) 827{ 828 static time_t mtime = 0; 829 DIR* dir = NULL; 830 struct dirent* entry = NULL; 831 832 /* 1. open BOOTDIR and find the BOOTFILE dir entry */ 833 dir = opendir(BOOTDIR); 834 835 if(!dir) return; /* do we want an error splash? */ 836 837 /* loop all files in BOOTDIR */ 838 while(0 != (entry = readdir(dir))) 839 { 840 if(!strcasecmp(entry->d_name, BOOTFILE)) 841 { 842 struct dirinfo info = dir_get_info(dir, entry); 843 /* found the bootfile */ 844 if(mtime && do_rolo) 845 { 846 if(info.mtime != mtime) 847 { 848 static const char *lines[] = { ID2P(LANG_BOOT_CHANGED), 849 ID2P(LANG_REBOOT_NOW) }; 850 static const struct text_message message={ lines, 2 }; 851 button_clear_queue(); /* Empty the keyboard buffer */ 852 if(gui_syncyesno_run(&message, NULL, NULL) == YESNO_YES) 853 { 854 audio_hard_stop(); 855 rolo_load(BOOTDIR "/" BOOTFILE); 856 } 857 } 858 } 859 mtime = info.mtime; 860 break; 861 } 862 } 863 closedir(dir); 864} 865#endif 866#endif 867 868/* check range, set volume and save settings */ 869void setvol(void) 870{ 871 const int min_vol = sound_min(SOUND_VOLUME); 872 const int max_vol = sound_max(SOUND_VOLUME); 873 int volume = global_status.volume; 874 if (volume < min_vol) 875 volume = min_vol; 876 if (volume > max_vol) 877 volume = max_vol; 878 if (volume > global_settings.volume_limit) 879 volume = global_settings.volume_limit; 880 881 sound_set_volume(volume); 882 global_status.last_volume_change = current_tick; 883 status_save(false); 884} 885 886#ifdef HAVE_PERCEPTUAL_VOLUME 887static short norm_tab[MAX_NORM_VOLUME_STEPS+2]; 888static int norm_tab_num_steps; 889static int norm_tab_size; 890 891static void update_norm_tab(void) 892{ 893 const int lim = global_settings.volume_adjust_norm_steps; 894 if (lim == norm_tab_num_steps) 895 return; 896 norm_tab_num_steps = lim; 897 898 const int min = sound_min(SOUND_VOLUME); 899 const int max = sound_max(SOUND_VOLUME); 900 const int step = sound_steps(SOUND_VOLUME); 901 902 /* Ensure the table contains the minimum volume */ 903 norm_tab[0] = min; 904 norm_tab_size = 1; 905 906 for (int i = 0; i < lim; ++i) 907 { 908 int vol = from_normalized_volume(i, min, max, lim); 909 int rem = vol % step; 910 911 vol -= rem; 912 if (abs(rem) > step/2) 913 vol += rem < 0 ? -step : step; 914 915 /* Add volume step, ignoring any duplicate entries that may 916 * occur due to rounding */ 917 if (vol != norm_tab[norm_tab_size-1]) 918 norm_tab[norm_tab_size++] = vol; 919 } 920 921 /* Ensure the table contains the maximum volume */ 922 if (norm_tab[norm_tab_size-1] != max) 923 norm_tab[norm_tab_size++] = max; 924} 925 926void set_normalized_volume(int vol) 927{ 928 update_norm_tab(); 929 930 if (vol < 0) 931 vol = 0; 932 if (vol >= norm_tab_size) 933 vol = norm_tab_size - 1; 934 935 global_status.volume = norm_tab[vol]; 936} 937 938int get_normalized_volume(void) 939{ 940 update_norm_tab(); 941 942 int a = 0, b = norm_tab_size - 1; 943 while (a != b) 944 { 945 int i = (a + b + 1) / 2; 946 if (global_status.volume < norm_tab[i]) 947 b = i - 1; 948 else 949 a = i; 950 } 951 952 return a; 953} 954#else 955void set_normalized_volume(int vol) 956{ 957 global_status.volume = vol * sound_steps(SOUND_VOLUME); 958} 959 960int get_normalized_volume(void) 961{ 962 return global_status.volume / sound_steps(SOUND_VOLUME); 963} 964#endif 965 966void adjust_volume(int steps) 967{ 968#ifdef HAVE_PERCEPTUAL_VOLUME 969 adjust_volume_ex(steps, global_settings.volume_adjust_mode); 970#else 971 adjust_volume_ex(steps, VOLUME_ADJUST_DIRECT); 972#endif 973} 974 975void adjust_volume_ex(int steps, enum volume_adjust_mode mode) 976{ 977 switch (mode) 978 { 979 case VOLUME_ADJUST_PERCEPTUAL: 980#ifdef HAVE_PERCEPTUAL_VOLUME 981 set_normalized_volume(get_normalized_volume() + steps); 982 break; 983#endif 984 case VOLUME_ADJUST_DIRECT: 985 default: 986 global_status.volume += steps * sound_steps(SOUND_VOLUME); 987 break; 988 } 989 990 setvol(); 991} 992 993char* strrsplt(char* str, int c) 994{ 995 char* s = strrchr(str, c); 996 997 if (s != NULL) 998 { 999 *s++ = '\0'; 1000 } 1001 else 1002 { 1003 s = str; 1004 } 1005 1006 return s; 1007} 1008 1009/* 1010 * removes the extension of filename (if it doesn't start with a .) 1011 * puts the result in buffer 1012 */ 1013char *strip_extension(char* buffer, int buffer_size, const char *filename) 1014{ 1015 if (!buffer || !filename || buffer_size <= 0) 1016 { 1017 return NULL; 1018 } 1019 1020 off_t dotpos = (strrchr(filename, '.') - filename) + 1; 1021 1022 /* no match on filename beginning with '.' or beyond buffer_size */ 1023 if(dotpos > 1 && dotpos < buffer_size) 1024 buffer_size = dotpos; 1025 strmemccpy(buffer, filename, buffer_size); 1026 1027 return buffer; 1028} 1029 1030/* Play a standard sound */ 1031void system_sound_play(enum system_sound sound) 1032{ 1033 static const struct beep_params 1034 { 1035 int *setting; 1036 unsigned short frequency; 1037 unsigned short duration; 1038 unsigned short amplitude; 1039 } beep_params[] = 1040 { 1041 [SOUND_KEYCLICK] = 1042 { &global_settings.keyclick, 1043 4000, KEYCLICK_DURATION, 2500 }, 1044 [SOUND_TRACK_SKIP] = 1045 { &global_settings.beep, 1046 2000, 100, 2500 }, 1047 [SOUND_TRACK_NO_MORE] = 1048 { &global_settings.beep, 1049 1000, 100, 1500 }, 1050 [SOUND_LIST_EDGE_BEEP_NOWRAP] = 1051 { &global_settings.keyclick, 1052 1000, 40, 1500 }, 1053 [SOUND_LIST_EDGE_BEEP_WRAP] = 1054 { &global_settings.keyclick, 1055 2000, 20, 1500 }, 1056 1057 }; 1058 1059 const struct beep_params *params = &beep_params[sound]; 1060 1061 if (*params->setting) 1062 { 1063 beep_play(params->frequency, params->duration, 1064 params->amplitude * *params->setting); 1065 } 1066} 1067 1068static keyclick_callback keyclick_current_callback = NULL; 1069static void* keyclick_data = NULL; 1070void keyclick_set_callback(keyclick_callback cb, void* data) 1071{ 1072 keyclick_current_callback = cb; 1073 keyclick_data = data; 1074} 1075 1076/* Produce keyclick based upon button and global settings */ 1077void keyclick_click(bool rawbutton, int action) 1078{ 1079 int button = action; 1080 static long last_button = BUTTON_NONE; 1081 bool do_beep = false; 1082 1083 if (!rawbutton) 1084 get_action_statuscode(&button); 1085 1086 /* Settings filters */ 1087 if ( 1088#ifdef HAVE_HARDWARE_CLICK 1089 (global_settings.keyclick || global_settings.keyclick_hardware) 1090#else 1091 global_settings.keyclick 1092#endif 1093 ) 1094 { 1095 if (global_settings.keyclick_repeats || !(button & BUTTON_REPEAT)) 1096 { 1097 /* Button filters */ 1098 if (button != BUTTON_NONE && !(button & BUTTON_REL) 1099 && !(button & (SYS_EVENT|BUTTON_MULTIMEDIA)) ) 1100 { 1101 do_beep = true; 1102 } 1103 } 1104 else if ((button & BUTTON_REPEAT) && (last_button == BUTTON_NONE)) 1105 { 1106 do_beep = true; 1107 } 1108#ifdef HAVE_SCROLLWHEEL 1109 else if (button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) 1110 { 1111 do_beep = true; 1112 } 1113#endif 1114 } 1115 if (button&BUTTON_REPEAT) 1116 last_button = button; 1117 else 1118 last_button = BUTTON_NONE; 1119 1120 if (do_beep && keyclick_current_callback) 1121 do_beep = keyclick_current_callback(action, keyclick_data); 1122 keyclick_current_callback = NULL; 1123 1124 if (do_beep) 1125 { 1126#ifdef HAVE_HARDWARE_CLICK 1127 if (global_settings.keyclick) 1128 { 1129 system_sound_play(SOUND_KEYCLICK); 1130 } 1131 if (global_settings.keyclick_hardware) 1132 { 1133#if !defined(SIMULATOR) 1134 piezo_button_beep(false, false); 1135#endif 1136 } 1137#else 1138 system_sound_play(SOUND_KEYCLICK); 1139#endif 1140 } 1141} 1142 1143/* Return the ReplayGain mode adjusted by other relevant settings */ 1144static int replaygain_setting_mode(int type) 1145{ 1146 switch (type) 1147 { 1148 case REPLAYGAIN_SHUFFLE: 1149 type = global_settings.playlist_shuffle ? 1150 REPLAYGAIN_TRACK : REPLAYGAIN_ALBUM; 1151 case REPLAYGAIN_ALBUM: 1152 case REPLAYGAIN_TRACK: 1153 case REPLAYGAIN_OFF: 1154 default: 1155 break; 1156 } 1157 1158 return type; 1159} 1160 1161/* Return the ReplayGain mode adjusted for display purposes */ 1162int id3_get_replaygain_mode(const struct mp3entry *id3) 1163{ 1164 if (!id3) 1165 return -1; 1166 1167 int type = global_settings.replaygain_settings.type; 1168 type = replaygain_setting_mode(type); 1169 1170 return (type != REPLAYGAIN_TRACK && id3->album_gain != 0) ? 1171 REPLAYGAIN_ALBUM : (id3->track_gain != 0 ? REPLAYGAIN_TRACK : -1); 1172} 1173 1174/* Update DSP's replaygain from global settings */ 1175void replaygain_update(void) 1176{ 1177 struct replaygain_settings settings = global_settings.replaygain_settings; 1178 settings.type = replaygain_setting_mode(settings.type); 1179 dsp_replaygain_set_settings(&settings); 1180} 1181 1182void format_sound_value_ex(char *buf, size_t buf_sz, int snd, int val, bool skin_token) 1183{ 1184 int numdec = sound_numdecimals(snd); 1185 const char *unit = sound_unit(snd); 1186 int physval = sound_val2phys(snd, val); 1187 1188 unsigned int factor = ipow(10, numdec); 1189 if (factor == 0) 1190 { 1191 DEBUGF("DIVISION BY ZERO: format_sound_value s:%d v:%d", snd, val); 1192 factor = 1; 1193 } 1194 unsigned int av = abs(physval); 1195 unsigned int i = av / factor; 1196 unsigned int d = av - i*factor; 1197 1198 snprintf(buf, buf_sz, "%s%u%.*s%.*u%s%s", physval < 0 ? "-" : &" "[skin_token], 1199 i, numdec, ".", numdec, d, &" "[skin_token], skin_token ? "" : unit); 1200} 1201 1202/* format a sound value as "-1.05 dB", or " 1.05 dB" */ 1203void format_sound_value(char *buf, size_t buf_sz, int snd, int val) 1204{ 1205 format_sound_value_ex(buf, buf_sz, snd, val, false); 1206} 1207 1208#endif /* !defined(__PCTOOL__) */ 1209 1210/* Read (up to) a line of text from fd into buffer and return number of bytes 1211 * read (which may be larger than the number of bytes stored in buffer). If 1212 * an error occurs, -1 is returned (and buffer contains whatever could be 1213 * read). A line is terminated by a LF char. Neither LF nor CR chars are 1214 * stored in buffer. 1215 */ 1216int read_line(int fd, char* buffer, int buffer_size) 1217{ 1218 if (!buffer || buffer_size-- <= 0) 1219 { 1220 errno = EINVAL; 1221 return -1; 1222 } 1223 1224 unsigned char rdbuf[32]; 1225 off_t rdbufend = 0; 1226 int rdbufidx = 0; 1227 int count = 0; 1228 int num_read = 0; 1229 1230 while (count < buffer_size) 1231 { 1232 if (rdbufidx >= rdbufend) 1233 { 1234 rdbufidx = 0; 1235 rdbufend = read(fd, rdbuf, sizeof (rdbuf)); 1236 1237 if (rdbufend <= 0) 1238 break; 1239 1240 num_read += rdbufend; 1241 } 1242 1243 int c = rdbuf[rdbufidx++]; 1244 1245 if (c == '\n') 1246 break; 1247 1248 if (c == '\r') 1249 continue; 1250 1251 buffer[count++] = c; 1252 } 1253 1254 rdbufidx -= rdbufend; 1255 1256 if (rdbufidx < 0) 1257 { 1258 /* "put back" what wasn't read from the buffer */ 1259 num_read += rdbufidx; 1260 rdbufend = lseek(fd, rdbufidx, SEEK_CUR); 1261 } 1262 1263 buffer[count] = '\0'; 1264 1265 return rdbufend >= 0 ? num_read : -1; 1266} 1267 1268char* skip_whitespace(char* const str) 1269{ 1270 char *s = str; 1271 1272 while (isspace(*s)) 1273 s++; 1274 1275 return s; 1276} 1277 1278#if !defined(CHECKWPS) && !defined(DBTOOL) 1279 1280int confirm_delete_yesno(const char *name) 1281{ 1282 const char *lines[] = { ID2P(LANG_REALLY_DELETE), name }; 1283 const char *yes_lines[] = { ID2P(LANG_DELETING), name }; 1284 const struct text_message message = { lines, 2 }; 1285 const struct text_message yes_message = { yes_lines, 2 }; 1286 return gui_syncyesno_run(&message, &yes_message, NULL); 1287} 1288 1289/* time_split_units() 1290 split time values depending on base unit 1291 unit_idx: UNIT_HOUR, UNIT_MIN, UNIT_SEC, UNIT_MS 1292 abs_value: absolute time value 1293 units_in: array of unsigned ints with UNIT_IDX_TIME_COUNT fields 1294*/ 1295unsigned int time_split_units(int unit_idx, unsigned long abs_val, 1296 unsigned long (*units_in)[UNIT_IDX_TIME_COUNT]) 1297{ 1298 unsigned int base_idx = UNIT_IDX_HR; 1299 unsigned long hours; 1300 unsigned long minutes = 0; 1301 unsigned long seconds = 0; 1302 unsigned long millisec = 0; 1303 1304 switch (unit_idx & UNIT_IDX_MASK) /*Mask off upper bits*/ 1305 { 1306 case UNIT_MS: 1307 base_idx = UNIT_IDX_MS; 1308 millisec = abs_val; 1309 abs_val = abs_val / 1000U; 1310 millisec = millisec - (1000U * abs_val); 1311 /* fallthrough and calculate the rest of the units */ 1312 case UNIT_SEC: 1313 if (base_idx == UNIT_IDX_HR) 1314 base_idx = UNIT_IDX_SEC; 1315 seconds = abs_val; 1316 abs_val = abs_val / 60U; 1317 seconds = seconds - (60U * abs_val); 1318 /* fallthrough and calculate the rest of the units */ 1319 case UNIT_MIN: 1320 if (base_idx == UNIT_IDX_HR) 1321 base_idx = UNIT_IDX_MIN; 1322 minutes = abs_val; 1323 abs_val = abs_val / 60U; 1324 minutes = minutes -(60U * abs_val); 1325 /* fallthrough and calculate the rest of the units */ 1326 case UNIT_HOUR: 1327 default: 1328 hours = abs_val; 1329 break; 1330 } 1331 1332 (*units_in)[UNIT_IDX_HR] = hours; 1333 (*units_in)[UNIT_IDX_MIN] = minutes; 1334 (*units_in)[UNIT_IDX_SEC] = seconds; 1335 (*units_in)[UNIT_IDX_MS] = millisec; 1336 1337 return base_idx; 1338} 1339 1340/* format_time_auto - return an auto ranged time string; 1341 buffer: needs to be at least 25 characters for full range 1342 1343 unit_idx: specifies lowest or base index of the value 1344 add | UNIT_LOCK_ to keep place holder of units that would normally be 1345 discarded.. For instance, UNIT_LOCK_HR would keep the hours place, ex: string 1346 00:10:10 (0 HRS 10 MINS 10 SECONDS) normally it would return as 10:10 1347 add | UNIT_TRIM_ZERO to supress leading zero on the largest unit 1348 1349 value: should be passed in the same form as unit_idx 1350 1351 supress_unit: may be set to true and in this case the 1352 hr, min, sec, ms identifiers will be left off the resulting string but 1353 since right to left languages are handled it is advisable to leave units 1354 as an indication of the text direction 1355*/ 1356 1357const char *format_time_auto(char *buffer, int buf_len, long value, 1358 int unit_idx, bool supress_unit) 1359{ 1360 const char * const sign = &"-"[value < 0 ? 0 : 1]; 1361 bool is_rtl = lang_is_rtl(); 1362 char timebuf[25]; /* -2147483648:00:00.00\0 */ 1363 int len, left_offset; 1364 unsigned char base_idx, max_idx; 1365 1366 unsigned long units_in[UNIT_IDX_TIME_COUNT]; 1367 unsigned char fwidth[UNIT_IDX_TIME_COUNT] = 1368 { 1369 [UNIT_IDX_HR] = 0, /* hr is variable length */ 1370 [UNIT_IDX_MIN] = 2, 1371 [UNIT_IDX_SEC] = 2, 1372 [UNIT_IDX_MS] = 3, 1373 }; /* {0,2,2,3}; Field Widths */ 1374 unsigned char offsets[UNIT_IDX_TIME_COUNT] = 1375 { 1376 [UNIT_IDX_HR] = 10,/* ?:59:59.999 Std offsets */ 1377 [UNIT_IDX_MIN] = 7, /*0?:+1:+4.+7 need calculated */ 1378 [UNIT_IDX_SEC] = 4,/* 999.59:59:0 RTL offsets */ 1379 [UNIT_IDX_MS] = 0,/* 0 .4 :7 :10 won't change */ 1380 }; /* {10,7,4,0}; Offsets */ 1381 static const uint16_t unitlock[UNIT_IDX_TIME_COUNT] = 1382 { 1383 [UNIT_IDX_HR] = UNIT_LOCK_HR, 1384 [UNIT_IDX_MIN] = UNIT_LOCK_MIN, 1385 [UNIT_IDX_SEC] = UNIT_LOCK_SEC, 1386 [UNIT_IDX_MS] = 0, 1387 }; /* unitlock */ 1388 static const uint16_t units[UNIT_IDX_TIME_COUNT] = 1389 { 1390 [UNIT_IDX_HR] = UNIT_HOUR, 1391 [UNIT_IDX_MIN] = UNIT_MIN, 1392 [UNIT_IDX_SEC] = UNIT_SEC, 1393 [UNIT_IDX_MS] = UNIT_MS, 1394 }; /* units */ 1395 1396#if 0 /* unused */ 1397 if (idx_pos != NULL) 1398 { 1399 (*idx_pos)[0] = MIN((*idx_pos)[0], UNIT_IDX_TIME_COUNT - 1); 1400 unit_idx |= unitlock[(*idx_pos)[0]]; 1401 } 1402#endif 1403 1404 base_idx = time_split_units(unit_idx, labs(value), &units_in); 1405 1406 if (units_in[UNIT_IDX_HR] || (unit_idx & unitlock[UNIT_IDX_HR])) 1407 max_idx = UNIT_IDX_HR; 1408 else if (units_in[UNIT_IDX_MIN] || (unit_idx & unitlock[UNIT_IDX_MIN])) 1409 max_idx = UNIT_IDX_MIN; 1410 else if (units_in[UNIT_IDX_SEC] || (unit_idx & unitlock[UNIT_IDX_SEC])) 1411 max_idx = UNIT_IDX_SEC; 1412 else if (units_in[UNIT_IDX_MS]) 1413 max_idx = UNIT_IDX_MS; 1414 else /* value is 0 */ 1415 max_idx = base_idx; 1416 1417 if (!is_rtl) 1418 { 1419 len = snprintf(timebuf, sizeof(timebuf), 1420 "%02lu:%02lu:%02lu.%03lu", 1421 units_in[UNIT_IDX_HR], 1422 units_in[UNIT_IDX_MIN], 1423 units_in[UNIT_IDX_SEC], 1424 units_in[UNIT_IDX_MS]); 1425 1426 fwidth[UNIT_IDX_HR] = len - offsets[UNIT_IDX_HR]; 1427 1428 /* calculate offsets of the other fields based on length of previous */ 1429 offsets[UNIT_IDX_MS] = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_MIN]; 1430 offsets[UNIT_IDX_SEC] = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_SEC]; 1431 offsets[UNIT_IDX_MIN] = fwidth[UNIT_IDX_HR] + 1; 1432 offsets[UNIT_IDX_HR] = 0; 1433 1434 timebuf[offsets[base_idx] + fwidth[base_idx]] = '\0'; 1435 1436 left_offset = -(offsets[max_idx]); 1437 left_offset += strlcpy(buffer, sign, buf_len); 1438 1439 /* trim leading zero on the max_idx */ 1440 if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO && 1441 timebuf[offsets[max_idx]] == '0' && fwidth[max_idx] > 1) 1442 { 1443 offsets[max_idx]++; 1444 } 1445 1446 strlcat(buffer, &timebuf[offsets[max_idx]], buf_len); 1447 1448 if (!supress_unit) 1449 { 1450 strlcat(buffer, " ", buf_len); 1451 strlcat(buffer, unit_strings_core[units[max_idx]], buf_len); 1452 } 1453 } 1454 else /*RTL Languages*/ 1455 { 1456 len = snprintf(timebuf, sizeof(timebuf), 1457 "%03lu.%02lu:%02lu:%02lu", 1458 units_in[UNIT_IDX_MS], 1459 units_in[UNIT_IDX_SEC], 1460 units_in[UNIT_IDX_MIN], 1461 units_in[UNIT_IDX_HR]); 1462 1463 fwidth[UNIT_IDX_HR] = len - offsets[UNIT_IDX_HR]; 1464 1465 left_offset = -(offsets[base_idx]); 1466 1467 /* trim leading zero on the max_idx */ 1468 if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO && 1469 timebuf[offsets[max_idx]] == '0' && fwidth[max_idx] > 1) 1470 { 1471 timebuf[offsets[max_idx]] = timebuf[offsets[max_idx]+1]; 1472 fwidth[max_idx]--; 1473 } 1474 1475 timebuf[offsets[max_idx] + fwidth[max_idx]] = '\0'; 1476 1477 if (!supress_unit) 1478 { 1479 strmemccpy(buffer, unit_strings_core[units[max_idx]], buf_len); 1480 left_offset += strlcat(buffer, " ", buf_len); 1481 strlcat(buffer, &timebuf[offsets[base_idx]], buf_len); 1482 } 1483 else 1484 strmemccpy(buffer, &timebuf[offsets[base_idx]], buf_len); 1485 1486 strlcat(buffer, sign, buf_len); 1487 } 1488#if 0 /* unused */ 1489 if (idx_pos != NULL) 1490 { 1491 (*idx_pos)[1]= fwidth[*(idx_pos)[0]]; 1492 (*idx_pos)[0]= left_offset + offsets[(*idx_pos)[0]]; 1493 } 1494#endif 1495 1496 return buffer; 1497} 1498 1499/* Format time into buf. 1500 * 1501 * buf - buffer to format to. 1502 * buf_size - size of buffer. 1503 * t - time to format, in milliseconds. 1504 */ 1505void format_time(char* buf, int buf_size, long t) 1506{ 1507 unsigned long units_in[UNIT_IDX_TIME_COUNT] = {0}; 1508 time_split_units(UNIT_MS, labs(t), &units_in); 1509 int hashours = units_in[UNIT_IDX_HR] > 0; 1510 snprintf(buf, buf_size, "%.*s%.0lu%.*s%.*lu:%.2lu", 1511 t < 0, "-", units_in[UNIT_IDX_HR], hashours, ":", 1512 hashours+1, units_in[UNIT_IDX_MIN], units_in[UNIT_IDX_SEC]); 1513} 1514 1515const char* format_sleeptimer(char* buffer, size_t buffer_size, 1516 int value, const char* unit) 1517{ 1518 (void) unit; 1519 int minutes, hours; 1520 1521 if (value) { 1522 hours = value / 60; 1523 minutes = value - (hours * 60); 1524 snprintf(buffer, buffer_size, "%d:%02d", hours, minutes); 1525 return buffer; 1526 } else { 1527 return str(LANG_OFF); 1528 } 1529} 1530 1531static int seconds_to_min(int secs) 1532{ 1533 return (secs + 10) / 60; /* round up for 50+ seconds */ 1534} 1535 1536char* string_sleeptimer(char *buffer, size_t buffer_len) 1537{ 1538 int sec = get_sleep_timer(); 1539 char timer_buf[10]; 1540 1541 snprintf(buffer, buffer_len, "%s (%s)", 1542 str(sec ? LANG_SLEEP_TIMER_CANCEL_CURRENT 1543 : LANG_SLEEP_TIMER_START_CURRENT), 1544 format_sleeptimer(timer_buf, sizeof(timer_buf), 1545 sec ? seconds_to_min(sec) 1546 : global_settings.sleeptimer_duration, NULL)); 1547 return buffer; 1548} 1549 1550/* If a sleep timer is running, cancel it, otherwise start one */ 1551int toggle_sleeptimer(void) 1552{ 1553 set_sleeptimer_duration(get_sleep_timer() ? 0 1554 : global_settings.sleeptimer_duration); 1555 return 0; 1556} 1557 1558void talk_sleeptimer(int custom_duration) 1559{ 1560 int seconds = custom_duration < 0 ? get_sleep_timer() : custom_duration*60; 1561 long talk_ids[] = { 1562 custom_duration >= 0 ? LANG_SLEEP_TIMER : 1563 (seconds ? LANG_SLEEP_TIMER_CANCEL_CURRENT : LANG_SLEEP_TIMER_START_CURRENT), 1564 VOICE_PAUSE, 1565 custom_duration == 0 ? LANG_OFF : 1566 (seconds ? seconds_to_min(seconds) 1567 : global_settings.sleeptimer_duration) | UNIT_MIN << UNIT_SHIFT, 1568 TALK_FINAL_ID 1569 }; 1570 talk_idarray(talk_ids, true); 1571} 1572 1573#if CONFIG_RTC 1574void talk_timedate(void) 1575{ 1576 struct tm *tm = get_time(); 1577 if (!global_settings.talk_menu) 1578 return; 1579 talk_id(LANG_CURRENT_TIME, false); 1580 if (valid_time(tm)) 1581 { 1582 talk_time(tm, true); 1583 talk_date(get_time(), true); 1584 } 1585 else 1586 { 1587 talk_id(LANG_UNKNOWN, true); 1588 } 1589} 1590#endif /* CONFIG_RTC */ 1591#endif /* !defined(CHECKWPS) && !defined(DBTOOL)*/ 1592 1593/** 1594 * Splits str at each occurence of split_char and puts the substrings into vector, 1595 * but at most vector_lenght items. Empty substrings are ignored. 1596 * 1597 * Modifies str by replacing each split_char following a substring with nul 1598 * 1599 * Returns the number of substrings found, i.e. the number of valid strings 1600 * in vector 1601 */ 1602int split_string(char *str, const char split_char, char *vector[], const int vector_length) 1603{ 1604 int i; 1605 char sep[2] = {split_char, '\0'}; 1606 char *e, *p = strtok_r(str, sep, &e); 1607 1608 /* strtok takes care of leading & trailing splitters */ 1609 for(i = 0; i < vector_length; i++) 1610 { 1611 vector[i] = p; 1612 if (!p) 1613 break; 1614 p = strtok_r(NULL, sep, &e); 1615 } 1616 1617 return i; 1618} 1619 1620/* returns match index from option list 1621 * returns -1 if option was not found 1622 * option list is array of char pointers with the final item set to null 1623 * ex - const char * const option[] = { "op_a", "op_b", "op_c", NULL} 1624 */ 1625int string_option(const char *option, const char *const oplist[], bool ignore_case) 1626{ 1627 const char *op; 1628 int (*cmp_fn)(const char*, const char*) = &strcasecmp; 1629 if (!ignore_case) 1630 cmp_fn = strcmp; 1631 for (int i=0; (op=oplist[i]) != NULL; i++) 1632 { 1633 if (cmp_fn(op, option) == 0) 1634 return i; 1635 } 1636 return -1; 1637} 1638 1639/* Make sure part of path only contain chars valid for a FAT32 long name. 1640 * Double quotes are replaced with single quotes, other unsupported chars 1641 * are replaced with an underscore. 1642 * 1643 * path - path to modify. 1644 * offset - where in path to start checking. 1645 * count - number of chars to check. 1646 */ 1647void fix_path_part(char* path, int offset, int count) 1648{ 1649 static const char invalid_chars[] = "*/:<>?\\|"; 1650 int i; 1651 1652 path += offset; 1653 1654 for (i = 0; i <= count; i++, path++) 1655 { 1656 if (*path == 0) 1657 return; 1658 if (*path == '"') 1659 *path = '\''; 1660 else if (strchr(invalid_chars, *path)) 1661 *path = '_'; 1662 } 1663} 1664 1665/* open but with a builtin printf for assembling the path */ 1666int open_pathfmt(char *buf, size_t size, int oflag, const char *pathfmt, ...) 1667{ 1668 va_list ap; 1669 va_start(ap, pathfmt); 1670 vsnprintf(buf, size, pathfmt, ap); 1671 va_end(ap); 1672 if ((oflag & O_PATH) == O_PATH) 1673 return -1; 1674 return open(buf, oflag, 0666); 1675} 1676 1677/** Open a UTF-8 file and set file descriptor to first byte after BOM. 1678 * If no BOM is present this behaves like open(). 1679 * If the file is opened for writing and O_TRUNC is set, write a BOM to 1680 * the opened file and leave the file pointer set after the BOM. 1681 */ 1682 1683int open_utf8(const char* pathname, int flags) 1684{ 1685 ssize_t ret; 1686 int fd; 1687 unsigned char bom[BOM_UTF_8_SIZE]; 1688 1689 fd = open(pathname, flags, 0666); 1690 if(fd < 0) 1691 return fd; 1692 1693 if(flags & (O_TRUNC | O_WRONLY)) 1694 { 1695 ret = write(fd, BOM_UTF_8, BOM_UTF_8_SIZE); 1696 } 1697 else 1698 { 1699 ret = read(fd, bom, BOM_UTF_8_SIZE); 1700 /* check for BOM */ 1701 if (ret == BOM_UTF_8_SIZE) 1702 { 1703 if(memcmp(bom, BOM_UTF_8, BOM_UTF_8_SIZE)) 1704 lseek(fd, 0, SEEK_SET); 1705 } 1706 } 1707 /* read or write failure, do not continue */ 1708 if (ret < 0) 1709 close(fd); 1710 1711 return ret >= 0 ? fd : -1; 1712} 1713 1714 1715#ifdef HAVE_LCD_COLOR 1716/* 1717 * Helper function to convert a string of 6 hex digits to a native colour 1718 */ 1719 1720static int hex2dec(int c) 1721{ 1722 return (((c) >= '0' && ((c) <= '9')) ? (c) - '0' : 1723 (toupper(c)) - 'A' + 10); 1724} 1725 1726int hex_to_rgb(const char* hex, int* color) 1727{ 1728 int red, green, blue; 1729 int i = 0; 1730 1731 while ((i < 6) && (isxdigit(hex[i]))) 1732 i++; 1733 1734 if (i < 6) 1735 return -1; 1736 1737 red = (hex2dec(hex[0]) << 4) | hex2dec(hex[1]); 1738 green = (hex2dec(hex[2]) << 4) | hex2dec(hex[3]); 1739 blue = (hex2dec(hex[4]) << 4) | hex2dec(hex[5]); 1740 1741 *color = LCD_RGBPACK(red,green,blue); 1742 1743 return 0; 1744} 1745#endif /* HAVE_LCD_COLOR */ 1746 1747/* '0'-'3' are ASCII 0x30 to 0x33 */ 1748#define is0123(x) (((x) & 0xfc) == 0x30) 1749#if !defined(__PCTOOL__) || defined(CHECKWPS) 1750bool parse_color(enum screen_type screen, char *text, int *value) 1751{ 1752 (void)text; (void)value; /* silence warnings on mono bitmap */ 1753 (void)screen; 1754 1755#ifdef HAVE_LCD_COLOR 1756 if (screens[screen].depth > 2) 1757 { 1758 if (hex_to_rgb(text, value) < 0) 1759 return false; 1760 else 1761 return true; 1762 } 1763#endif 1764 1765#if LCD_DEPTH == 2 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 2) 1766 if (screens[screen].depth == 2) 1767 { 1768 if (text[1] != '\0' || !is0123(*text)) 1769 return false; 1770 *value = *text - '0'; 1771 return true; 1772 } 1773#endif 1774 return false; 1775} 1776#endif /* !defined(__PCTOOL__) || defined(CHECKWPS) */ 1777 1778/* only used in USB HID and set_time screen */ 1779#if defined(USB_ENABLE_HID) || (CONFIG_RTC != 0) 1780int clamp_value_wrap(int value, int max, int min) 1781{ 1782 if (value > max) 1783 return min; 1784 if (value < min) 1785 return max; 1786 return value; 1787} 1788#endif 1789 1790 1791#ifndef __PCTOOL__ 1792 1793#define MAX_ACTIVITY_DEPTH 12 1794static enum current_activity 1795 current_activity[MAX_ACTIVITY_DEPTH] = {ACTIVITY_UNKNOWN}; 1796static int current_activity_top = 0; 1797 1798static void push_current_activity_refresh(enum current_activity screen, bool refresh) 1799{ 1800 current_activity[current_activity_top++] = screen; 1801 FOR_NB_SCREENS(i) 1802 { 1803 skinlist_set_cfg(i, NULL); 1804 if (refresh) 1805 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 1806 } 1807} 1808 1809static void pop_current_activity_refresh(bool refresh) 1810{ 1811 current_activity_top--; 1812 FOR_NB_SCREENS(i) 1813 { 1814 skinlist_set_cfg(i, NULL); 1815 if (refresh) 1816 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 1817 } 1818} 1819 1820void push_current_activity(enum current_activity screen) 1821{ 1822 push_current_activity_refresh(screen, true); 1823} 1824 1825void push_activity_without_refresh(enum current_activity screen) 1826{ 1827 push_current_activity_refresh(screen, false); 1828} 1829 1830void pop_current_activity(void) 1831{ 1832 pop_current_activity_refresh(true); 1833#if 0 1834 current_activity_top--; 1835 FOR_NB_SCREENS(i) 1836 { 1837 skinlist_set_cfg(i, NULL); 1838 if (ACTIVITY_REFRESH_NOW == refresh) 1839 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 1840 } 1841#endif 1842} 1843 1844void pop_current_activity_without_refresh(void) 1845{ 1846 pop_current_activity_refresh(false); 1847} 1848 1849enum current_activity get_current_activity(void) 1850{ 1851 return current_activity[current_activity_top?current_activity_top-1:0]; 1852} 1853 1854/* core_load_bmp opens bitmp filename and allocates space for it 1855* you must set bm->data with the result from core_get_data(handle) 1856* you must also call core_free(handle) when finished with the bitmap 1857* returns handle, ALOC_ERR(0) on failure 1858* ** Extended error info truth table ** 1859* [ Handle ][buf_reqd] 1860* [ > 0 ][ > 0 ] buf_reqd indicates how many bytes were used 1861* [ALOC_ERR][ > 0 ] buf_reqd indicates how many bytes are needed 1862* [ALOC_ERR][READ_ERR] there was an error reading the file or it is empty 1863*/ 1864int core_load_bmp(const char * filename, struct bitmap *bm, const int bmformat, 1865 ssize_t *buf_reqd, struct buflib_callbacks *ops) 1866{ 1867 ssize_t buf_size; 1868 ssize_t size_read = 0; 1869 int handle = CLB_ALOC_ERR; 1870 1871 int fd = open(filename, O_RDONLY); 1872 *buf_reqd = CLB_READ_ERR; 1873 1874 if (fd < 0) /* Exit if file opening failed */ 1875 { 1876 DEBUGF("read_bmp_file: can't open '%s', rc: %d\n", filename, fd); 1877 return CLB_ALOC_ERR; 1878 } 1879 1880 buf_size = read_bmp_fd(fd, bm, 0, bmformat|FORMAT_RETURN_SIZE, NULL); 1881 1882 if (buf_size > 0) 1883 { 1884 handle = core_alloc_ex(buf_size, ops); 1885 if (handle > 0) 1886 { 1887 bm->data = core_get_data_pinned(handle); 1888 lseek(fd, 0, SEEK_SET); /* reset to beginning of file */ 1889 size_read = read_bmp_fd(fd, bm, buf_size, bmformat, NULL); 1890 1891 if (size_read > 0) /* free unused alpha channel, if any */ 1892 { 1893 core_shrink(handle, bm->data, size_read); 1894 *buf_reqd = size_read; 1895 } 1896 1897 core_put_data_pinned(bm->data); 1898 bm->data = NULL; /* do this to force a crash later if the 1899 caller doesnt call core_get_data() */ 1900 } 1901 else 1902 *buf_reqd = buf_size; /* couldn't allocate pass bytes needed */ 1903 1904 if (size_read <= 0) 1905 { 1906 /* error reading file */ 1907 core_free(handle); /* core_free() ignores free handles (<= 0) */ 1908 handle = CLB_ALOC_ERR; 1909 } 1910 } 1911 1912 close(fd); 1913 return handle; 1914} 1915 1916/* 1917 * Normalized volume routines adapted from alsamixer volume_mapping.c 1918 */ 1919/* 1920 * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de> 1921 * 1922 * Permission to use, copy, modify, and/or distribute this software for any 1923 * purpose with or without fee is hereby granted, provided that the above 1924 * copyright notice and this permission notice appear in all copies. 1925 * 1926 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1927 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1928 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1929 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1930 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1931 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1932 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1933 */ 1934/* 1935 * "The mapping is designed so that the position in the interval is proportional 1936 * to the volume as a human ear would perceive it (i.e., the position is the 1937 * cubic root of the linear sample multiplication factor). For controls with 1938 * a small range (24 dB or less), the mapping is linear in the dB values so 1939 * that each step has the same size visually. Only for controls without dB 1940 * information, a linear mapping of the hardware volume register values is used 1941 * (this is the same algorithm as used in the old alsamixer)." 1942 */ 1943 1944#define NVOL_FRACBITS 16 1945#define NVOL_UNITY (1L << NVOL_FRACBITS) 1946#define NVOL_FACTOR (600L << NVOL_FRACBITS) 1947 1948#define NVOL_MAX_LINEAR_DB_SCALE (240L << NVOL_FRACBITS) 1949 1950#define nvol_div(x,y) fp_div((x), (y), NVOL_FRACBITS) 1951#define nvol_mul(x,y) fp_mul((x), (y), NVOL_FRACBITS) 1952#define nvol_exp10(x) fp_exp10((x), NVOL_FRACBITS) 1953#define nvol_log10(x) fp_log10((x), NVOL_FRACBITS) 1954 1955static bool use_linear_dB_scale(long min_vol, long max_vol) 1956{ 1957 /* 1958 * Alsamixer uses a linear scale for small ranges. 1959 * Commented out so perceptual volume works as advertised on all targets. 1960 */ 1961 /* 1962 return max_vol - min_vol <= NVOL_MAX_LINEAR_DB_SCALE; 1963 */ 1964 1965 (void)min_vol; 1966 (void)max_vol; 1967 return false; 1968} 1969 1970long to_normalized_volume(long vol, long min_vol, long max_vol, long max_norm) 1971{ 1972 long norm, min_norm; 1973 1974 vol <<= NVOL_FRACBITS; 1975 min_vol <<= NVOL_FRACBITS; 1976 max_vol <<= NVOL_FRACBITS; 1977 max_norm <<= NVOL_FRACBITS; 1978 1979 if (use_linear_dB_scale(min_vol, max_vol)) 1980 { 1981 norm = nvol_div(vol - min_vol, max_vol - min_vol); 1982 } 1983 else 1984 { 1985 min_norm = nvol_exp10(nvol_div(min_vol - max_vol, NVOL_FACTOR)); 1986 norm = nvol_exp10(nvol_div(vol - max_vol, NVOL_FACTOR)); 1987 norm = nvol_div(norm - min_norm, NVOL_UNITY - min_norm); 1988 } 1989 1990 return nvol_mul(norm, max_norm) >> NVOL_FRACBITS; 1991} 1992 1993long from_normalized_volume(long norm, long min_vol, long max_vol, long max_norm) 1994{ 1995 long vol, min_norm; 1996 1997 norm <<= NVOL_FRACBITS; 1998 min_vol <<= NVOL_FRACBITS; 1999 max_vol <<= NVOL_FRACBITS; 2000 max_norm <<= NVOL_FRACBITS; 2001 2002 vol = nvol_div(norm, max_norm); 2003 2004 if (use_linear_dB_scale(min_vol, max_vol)) 2005 { 2006 vol = nvol_mul(vol, max_vol - min_vol) + min_vol; 2007 } 2008 else 2009 { 2010 min_norm = nvol_exp10(nvol_div(min_vol - max_vol, NVOL_FACTOR)); 2011 vol = nvol_mul(vol, NVOL_UNITY - min_norm) + min_norm; 2012 vol = nvol_mul(nvol_log10(vol), NVOL_FACTOR) + max_vol; 2013 } 2014 2015 return vol >> NVOL_FRACBITS; 2016} 2017 2018void clear_screen_buffer(bool update) 2019{ 2020 struct viewport vp; 2021 struct viewport *last_vp; 2022 FOR_NB_SCREENS(i) 2023 { 2024 struct screen * screen = &screens[i]; 2025 viewport_set_defaults(&vp, screen->screen_type); 2026 last_vp = screen->set_viewport(&vp); 2027 screen->clear_viewport(); 2028 if (update) { 2029 screen->update_viewport(); 2030 } 2031 screen->set_viewport(last_vp); 2032 } 2033} 2034 2035#endif /* ndef __PCTOOL__ */