A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 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__ */