Rearange menu of mpegplayer. Add new menu with "settings" and "quit", and remove...
[kugel-rb.git] / apps / misc.c
blob1715fe305bea1f6105c5eb13ab62492ae8c57f32
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by Daniel Stenberg
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include <stdlib.h>
22 #include <ctype.h>
23 #include <string.h>
24 #include "config.h"
25 #include "misc.h"
26 #include "lcd.h"
27 #include "file.h"
28 #ifdef __PCTOOL__
29 #include <stdarg.h>
30 #include <stdio.h>
31 #else
32 #include "sprintf.h"
33 #include "lang.h"
34 #include "dir.h"
35 #include "lcd-remote.h"
36 #include "errno.h"
37 #include "system.h"
38 #include "timefuncs.h"
39 #include "screens.h"
40 #include "talk.h"
41 #include "mpeg.h"
42 #include "audio.h"
43 #include "mp3_playback.h"
44 #include "settings.h"
45 #include "storage.h"
46 #include "ata_idle_notify.h"
47 #include "kernel.h"
48 #include "power.h"
49 #include "powermgmt.h"
50 #include "backlight.h"
51 #include "version.h"
52 #include "font.h"
53 #include "splash.h"
54 #include "tagcache.h"
55 #include "scrobbler.h"
56 #include "sound.h"
57 #include "playlist.h"
58 #include "yesno.h"
59 #include "viewport.h"
61 #ifdef IPOD_ACCESSORY_PROTOCOL
62 #include "iap.h"
63 #endif
65 #if (CONFIG_STORAGE & STORAGE_MMC)
66 #include "ata_mmc.h"
67 #endif
68 #include "tree.h"
69 #include "eeprom_settings.h"
70 #if defined(HAVE_RECORDING) && !defined(__PCTOOL__)
71 #include "recording.h"
72 #endif
73 #if defined(HAVE_LCD_BITMAP) && !defined(__PCTOOL__)
74 #include "bmp.h"
75 #include "icons.h"
76 #endif /* End HAVE_LCD_BITMAP */
77 #include "bookmark.h"
78 #include "wps.h"
79 #include "playback.h"
81 #ifdef BOOTFILE
82 #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF)
83 #include "rolo.h"
84 #include "yesno.h"
85 #endif
86 #endif
88 /* Format a large-range value for output, using the appropriate unit so that
89 * the displayed value is in the range 1 <= display < 1000 (1024 for "binary"
90 * units) if possible, and 3 significant digits are shown. If a buffer is
91 * given, the result is snprintf()'d into that buffer, otherwise the result is
92 * voiced.*/
93 char *output_dyn_value(char *buf, int buf_size, int value,
94 const unsigned char **units, bool bin_scale)
96 int scale = bin_scale ? 1024 : 1000;
97 int fraction = 0;
98 int unit_no = 0;
99 char tbuf[5];
101 while (value >= scale)
103 fraction = value % scale;
104 value /= scale;
105 unit_no++;
107 if (bin_scale)
108 fraction = fraction * 1000 / 1024;
110 if (value >= 100 || !unit_no)
111 tbuf[0] = '\0';
112 else if (value >= 10)
113 snprintf(tbuf, sizeof(tbuf), "%01d", fraction / 100);
114 else
115 snprintf(tbuf, sizeof(tbuf), "%02d", fraction / 10);
117 if (buf)
119 if (strlen(tbuf))
120 snprintf(buf, buf_size, "%d%s%s%s", value, str(LANG_POINT),
121 tbuf, P2STR(units[unit_no]));
122 else
123 snprintf(buf, buf_size, "%d%s", value, P2STR(units[unit_no]));
125 else
127 talk_fractional(tbuf, value, P2ID(units[unit_no]));
129 return buf;
132 /* Ask the user if they really want to erase the current dynamic playlist
133 * returns true if the playlist should be replaced */
134 bool warn_on_pl_erase(void)
136 if (global_settings.warnon_erase_dynplaylist &&
137 !global_settings.party_mode &&
138 playlist_modified(NULL))
140 static const char *lines[] =
141 {ID2P(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
142 static const struct text_message message={lines, 1};
144 return (gui_syncyesno_run(&message, NULL, NULL) == YESNO_YES);
146 else
147 return true;
150 /* Read (up to) a line of text from fd into buffer and return number of bytes
151 * read (which may be larger than the number of bytes stored in buffer). If
152 * an error occurs, -1 is returned (and buffer contains whatever could be
153 * read). A line is terminated by a LF char. Neither LF nor CR chars are
154 * stored in buffer.
156 int read_line(int fd, char* buffer, int buffer_size)
158 int count = 0;
159 int num_read = 0;
161 errno = 0;
163 while (count < buffer_size)
165 unsigned char c;
167 if (1 != read(fd, &c, 1))
168 break;
170 num_read++;
172 if ( c == '\n' )
173 break;
175 if ( c == '\r' )
176 continue;
178 buffer[count++] = c;
181 buffer[MIN(count, buffer_size - 1)] = 0;
183 return errno ? -1 : num_read;
186 /* Performance optimized version of the previous function. */
187 int fast_readline(int fd, char *buf, int buf_size, void *parameters,
188 int (*callback)(int n, const char *buf, void *parameters))
190 char *p, *next;
191 int rc, pos = 0;
192 int count = 0;
194 while ( 1 )
196 next = NULL;
198 rc = read(fd, &buf[pos], buf_size - pos - 1);
199 if (rc >= 0)
200 buf[pos+rc] = '\0';
202 if ( (p = strchr(buf, '\r')) != NULL)
204 *p = '\0';
205 next = ++p;
207 else
208 p = buf;
210 if ( (p = strchr(p, '\n')) != NULL)
212 *p = '\0';
213 next = ++p;
216 rc = callback(count, buf, parameters);
217 if (rc < 0)
218 return rc;
220 count++;
221 if (next)
223 pos = buf_size - ((long)next - (long)buf) - 1;
224 memmove(buf, next, pos);
226 else
227 break ;
230 return 0;
233 /* parse a line from a configuration file. the line format is:
235 name: value
237 Any whitespace before setting name or value (after ':') is ignored.
238 A # as first non-whitespace character discards the whole line.
239 Function sets pointers to null-terminated setting name and value.
240 Returns false if no valid config entry was found.
243 bool settings_parseline(char* line, char** name, char** value)
245 char* ptr;
247 line = skip_whitespace(line);
249 if ( *line == '#' )
250 return false;
252 ptr = strchr(line, ':');
253 if ( !ptr )
254 return false;
256 *name = line;
257 *ptr = 0;
258 ptr++;
259 ptr = skip_whitespace(ptr);
260 *value = ptr;
261 return true;
264 static void system_flush(void)
266 scrobbler_shutdown();
267 playlist_shutdown();
268 tree_flush();
269 call_storage_idle_notifys(true); /*doesnt work on usb and shutdown from ata thread */
272 static void system_restore(void)
274 tree_restore();
275 scrobbler_init();
278 static bool clean_shutdown(void (*callback)(void *), void *parameter)
280 #ifdef SIMULATOR
281 (void)callback;
282 (void)parameter;
283 bookmark_autobookmark();
284 call_storage_idle_notifys(true);
285 exit(0);
286 #else
287 long msg_id = -1;
288 int i;
290 scrobbler_poweroff();
292 #if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
293 if(!charger_inserted())
294 #endif
296 bool batt_safe = battery_level_safe();
297 int audio_stat = audio_status();
299 FOR_NB_SCREENS(i)
301 screens[i].clear_display();
302 screens[i].update();
305 if (batt_safe)
307 #ifdef HAVE_TAGCACHE
308 if (!tagcache_prepare_shutdown())
310 cancel_shutdown();
311 splash(HZ, ID2P(LANG_TAGCACHE_BUSY));
312 return false;
314 #endif
315 if (battery_level() > 10)
316 splash(0, str(LANG_SHUTTINGDOWN));
317 else
319 msg_id = LANG_WARNING_BATTERY_LOW;
320 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_LOW),
321 str(LANG_SHUTTINGDOWN));
324 else
326 msg_id = LANG_WARNING_BATTERY_EMPTY;
327 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_EMPTY),
328 str(LANG_SHUTTINGDOWN));
331 if (global_settings.fade_on_stop
332 && (audio_stat & AUDIO_STATUS_PLAY))
334 fade(false, false);
337 if (batt_safe) /* do not save on critical battery */
339 #if defined(HAVE_RECORDING) && CONFIG_CODEC == SWCODEC
340 if (audio_stat & AUDIO_STATUS_RECORD)
342 rec_command(RECORDING_CMD_STOP);
343 /* wait for stop to complete */
344 while (audio_status() & AUDIO_STATUS_RECORD)
345 sleep(1);
347 #endif
348 bookmark_autobookmark();
350 /* audio_stop_recording == audio_stop for HWCODEC */
351 audio_stop();
353 if (callback != NULL)
354 callback(parameter);
356 #if CONFIG_CODEC != SWCODEC
357 /* wait for audio_stop or audio_stop_recording to complete */
358 while (audio_status())
359 sleep(1);
360 #endif
362 #if defined(HAVE_RECORDING) && CONFIG_CODEC == SWCODEC
363 audio_close_recording();
364 #endif
366 if(global_settings.talk_menu)
368 bool enqueue = false;
369 if(msg_id != -1)
371 talk_id(msg_id, enqueue);
372 enqueue = true;
374 talk_id(LANG_SHUTTINGDOWN, enqueue);
375 #if CONFIG_CODEC == SWCODEC
376 voice_wait();
377 #endif
380 system_flush();
381 #ifdef HAVE_EEPROM_SETTINGS
382 if (firmware_settings.initialized)
384 firmware_settings.disk_clean = true;
385 firmware_settings.bl_version = 0;
386 eeprom_settings_store();
388 #endif
390 #ifdef HAVE_DIRCACHE
391 else
392 dircache_disable();
393 #endif
395 shutdown_hw();
397 #endif
398 return false;
401 bool list_stop_handler(void)
403 bool ret = false;
405 /* Stop the music if it is playing */
406 if(audio_status())
408 if (!global_settings.party_mode)
410 if (global_settings.fade_on_stop)
411 fade(false, false);
412 bookmark_autobookmark();
413 audio_stop();
414 ret = true; /* bookmarking can make a refresh necessary */
417 #if CONFIG_CHARGING
418 #if (CONFIG_KEYPAD == RECORDER_PAD) && !defined(HAVE_SW_POWEROFF)
419 else
421 if (charger_inserted())
422 charging_splash();
423 else
424 shutdown_screen(); /* won't return if shutdown actually happens */
426 ret = true; /* screen is dirty, caller needs to refresh */
428 #endif
429 #ifndef HAVE_POWEROFF_WHILE_CHARGING
431 static long last_off = 0;
433 if (TIME_BEFORE(current_tick, last_off + HZ/2))
435 if (charger_inserted())
437 charging_splash();
438 ret = true; /* screen is dirty, caller needs to refresh */
441 last_off = current_tick;
443 #endif
444 #endif /* CONFIG_CHARGING */
445 return ret;
448 #if CONFIG_CHARGING
449 static bool waiting_to_resume_play = false;
450 static long play_resume_tick;
452 static void car_adapter_mode_processing(bool inserted)
454 if (global_settings.car_adapter_mode)
456 if(inserted)
459 * Just got plugged in, delay & resume if we were playing
461 if (audio_status() & AUDIO_STATUS_PAUSE)
463 /* delay resume a bit while the engine is cranking */
464 play_resume_tick = current_tick + HZ*5;
465 waiting_to_resume_play = true;
468 else
471 * Just got unplugged, pause if playing
473 if ((audio_status() & AUDIO_STATUS_PLAY) &&
474 !(audio_status() & AUDIO_STATUS_PAUSE))
476 if (global_settings.fade_on_stop)
477 fade(false, false);
478 else
479 audio_pause();
481 waiting_to_resume_play = false;
486 static void car_adapter_tick(void)
488 if (waiting_to_resume_play)
490 if (TIME_AFTER(current_tick, play_resume_tick))
492 if (audio_status() & AUDIO_STATUS_PAUSE)
494 queue_broadcast(SYS_CAR_ADAPTER_RESUME, 0);
496 waiting_to_resume_play = false;
501 void car_adapter_mode_init(void)
503 tick_add_task(car_adapter_tick);
505 #endif
507 #ifdef HAVE_HEADPHONE_DETECTION
508 static void unplug_change(bool inserted)
510 static bool headphone_caused_pause = false;
512 if (global_settings.unplug_mode)
514 int audio_stat = audio_status();
515 if (inserted)
517 if ((audio_stat & AUDIO_STATUS_PLAY) &&
518 headphone_caused_pause &&
519 global_settings.unplug_mode > 1 )
520 audio_resume();
521 backlight_on();
522 headphone_caused_pause = false;
523 } else {
524 if ((audio_stat & AUDIO_STATUS_PLAY) &&
525 !(audio_stat & AUDIO_STATUS_PAUSE))
527 headphone_caused_pause = true;
528 audio_pause();
530 if (global_settings.unplug_rw)
532 if (audio_current_track()->elapsed >
533 (unsigned long)(global_settings.unplug_rw*1000))
534 audio_ff_rewind(audio_current_track()->elapsed -
535 (global_settings.unplug_rw*1000));
536 else
537 audio_ff_rewind(0);
543 #endif
545 long default_event_handler_ex(long event, void (*callback)(void *), void *parameter)
547 switch(event)
549 case SYS_BATTERY_UPDATE:
550 if(global_settings.talk_battery_level)
552 talk_ids(true, VOICE_PAUSE, VOICE_PAUSE,
553 LANG_BATTERY_TIME,
554 TALK_ID(battery_level(), UNIT_PERCENT),
555 VOICE_PAUSE);
556 talk_force_enqueue_next();
558 break;
559 case SYS_USB_CONNECTED:
560 if (callback != NULL)
561 callback(parameter);
562 #if (CONFIG_STORAGE & STORAGE_MMC)
563 if (!mmc_touched() ||
564 (mmc_remove_request() == SYS_HOTSWAP_EXTRACTED))
565 #endif
567 system_flush();
568 #ifdef BOOTFILE
569 #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF)
570 check_bootfile(false); /* gets initial size */
571 #endif
572 #endif
573 usb_screen();
574 #ifdef BOOTFILE
575 #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF)
576 check_bootfile(true);
577 #endif
578 #endif
579 system_restore();
581 return SYS_USB_CONNECTED;
582 case SYS_POWEROFF:
583 if (!clean_shutdown(callback, parameter))
584 return SYS_POWEROFF;
585 break;
586 #if CONFIG_CHARGING
587 case SYS_CHARGER_CONNECTED:
588 car_adapter_mode_processing(true);
589 return SYS_CHARGER_CONNECTED;
591 case SYS_CHARGER_DISCONNECTED:
592 car_adapter_mode_processing(false);
593 return SYS_CHARGER_DISCONNECTED;
595 case SYS_CAR_ADAPTER_RESUME:
596 audio_resume();
597 return SYS_CAR_ADAPTER_RESUME;
598 #endif
599 #ifdef HAVE_HEADPHONE_DETECTION
600 case SYS_PHONE_PLUGGED:
601 unplug_change(true);
602 return SYS_PHONE_PLUGGED;
604 case SYS_PHONE_UNPLUGGED:
605 unplug_change(false);
606 return SYS_PHONE_UNPLUGGED;
607 #endif
608 #ifdef IPOD_ACCESSORY_PROTOCOL
609 case SYS_IAP_PERIODIC:
610 iap_periodic();
611 return SYS_IAP_PERIODIC;
612 case SYS_IAP_HANDLEPKT:
613 iap_handlepkt();
614 return SYS_IAP_HANDLEPKT;
615 #endif
617 return 0;
620 long default_event_handler(long event)
622 return default_event_handler_ex(event, NULL, NULL);
625 int show_logo( void )
627 #ifdef HAVE_LCD_BITMAP
628 char version[32];
629 int font_h, font_w;
631 snprintf(version, sizeof(version), "Ver. %s", appsversion);
633 lcd_clear_display();
634 #ifdef SANSA_CLIP /* display the logo in the blue area of the screen */
635 lcd_setfont(FONT_SYSFIXED);
636 lcd_getstringsize((unsigned char *)"A", &font_w, &font_h);
637 lcd_putsxy((LCD_WIDTH/2) - ((strlen(version)*font_w)/2),
638 0, (unsigned char *)version);
639 lcd_bitmap(rockboxlogo, 0, 16, BMPWIDTH_rockboxlogo, BMPHEIGHT_rockboxlogo);
640 #else
641 lcd_bitmap(rockboxlogo, 0, 10, BMPWIDTH_rockboxlogo, BMPHEIGHT_rockboxlogo);
642 lcd_setfont(FONT_SYSFIXED);
643 lcd_getstringsize((unsigned char *)"A", &font_w, &font_h);
644 lcd_putsxy((LCD_WIDTH/2) - ((strlen(version)*font_w)/2),
645 LCD_HEIGHT-font_h, (unsigned char *)version);
646 #endif
647 lcd_setfont(FONT_UI);
649 #else
650 char *rockbox = " ROCKbox!";
652 lcd_clear_display();
653 lcd_double_height(true);
654 lcd_puts(0, 0, rockbox);
655 lcd_puts_scroll(0, 1, appsversion);
656 #endif
657 lcd_update();
659 #ifdef HAVE_REMOTE_LCD
660 lcd_remote_clear_display();
661 lcd_remote_bitmap(remote_rockboxlogo, 0, 10, BMPWIDTH_remote_rockboxlogo,
662 BMPHEIGHT_remote_rockboxlogo);
663 lcd_remote_setfont(FONT_SYSFIXED);
664 lcd_remote_getstringsize((unsigned char *)"A", &font_w, &font_h);
665 lcd_remote_putsxy((LCD_REMOTE_WIDTH/2) - ((strlen(version)*font_w)/2),
666 LCD_REMOTE_HEIGHT-font_h, (unsigned char *)version);
667 lcd_remote_setfont(FONT_UI);
668 lcd_remote_update();
669 #endif
671 return 0;
674 #if CONFIG_CODEC == SWCODEC
675 int get_replaygain_mode(bool have_track_gain, bool have_album_gain)
677 int type;
679 bool track = ((global_settings.replaygain_type == REPLAYGAIN_TRACK)
680 || ((global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
681 && global_settings.playlist_shuffle));
683 type = (!track && have_album_gain) ? REPLAYGAIN_ALBUM
684 : have_track_gain ? REPLAYGAIN_TRACK : -1;
686 return type;
688 #endif
690 #ifdef BOOTFILE
691 #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF)
693 memorize/compare details about the BOOTFILE
694 we don't use dircache because it may not be up to date after
695 USB disconnect (scanning in the background)
697 void check_bootfile(bool do_rolo)
699 static unsigned short wrtdate = 0;
700 static unsigned short wrttime = 0;
701 DIR* dir = NULL;
702 struct dirent* entry = NULL;
704 /* 1. open BOOTDIR and find the BOOTFILE dir entry */
705 dir = opendir(BOOTDIR);
707 if(!dir) return; /* do we want an error splash? */
709 /* loop all files in BOOTDIR */
710 while(0 != (entry = readdir(dir)))
712 if(!strcasecmp(entry->d_name, BOOTFILE))
714 /* found the bootfile */
715 if(wrtdate && do_rolo)
717 if((entry->wrtdate != wrtdate) ||
718 (entry->wrttime != wrttime))
720 static const char *lines[] = { ID2P(LANG_BOOT_CHANGED),
721 ID2P(LANG_REBOOT_NOW) };
722 static const struct text_message message={ lines, 2 };
723 button_clear_queue(); /* Empty the keyboard buffer */
724 if(gui_syncyesno_run(&message, NULL, NULL) == YESNO_YES)
725 rolo_load(BOOTDIR "/" BOOTFILE);
728 wrtdate = entry->wrtdate;
729 wrttime = entry->wrttime;
732 closedir(dir);
734 #endif
735 #endif
737 /* check range, set volume and save settings */
738 void setvol(void)
740 const int min_vol = sound_min(SOUND_VOLUME);
741 const int max_vol = sound_max(SOUND_VOLUME);
742 if (global_settings.volume < min_vol)
743 global_settings.volume = min_vol;
744 if (global_settings.volume > max_vol)
745 global_settings.volume = max_vol;
746 sound_set_volume(global_settings.volume);
747 settings_save();
750 char* strrsplt(char* str, int c)
752 char* s = strrchr(str, c);
754 if (s != NULL)
756 *s++ = '\0';
758 else
760 s = str;
763 return s;
766 /* Test file existence, using dircache of possible */
767 bool file_exists(const char *file)
769 int fd;
771 if (!file || strlen(file) <= 0)
772 return false;
774 #ifdef HAVE_DIRCACHE
775 if (dircache_is_enabled())
776 return (dircache_get_entry_ptr(file) != NULL);
777 #endif
779 fd = open(file, O_RDONLY);
780 if (fd < 0)
781 return false;
782 close(fd);
783 return true;
786 bool dir_exists(const char *path)
788 DIR* d = opendir(path);
789 if (!d)
790 return false;
791 closedir(d);
792 return true;
796 * removes the extension of filename (if it doesn't start with a .)
797 * puts the result in buffer
799 char *strip_extension(char* buffer, int buffer_size, const char *filename)
801 char *dot = strrchr(filename, '.');
802 int len;
804 if (buffer_size <= 0)
806 return NULL;
809 buffer_size--; /* Make room for end nil */
811 if (dot != 0 && filename[0] != '.')
813 len = dot - filename;
814 len = MIN(len, buffer_size);
816 else
818 len = buffer_size;
821 strlcpy(buffer, filename, len + 1);
823 return buffer;
825 #endif /* !defined(__PCTOOL__) */
827 char* skip_whitespace(char* const str)
829 char *s = str;
831 while (isspace(*s))
832 s++;
834 return s;
837 /* Format time into buf.
839 * buf - buffer to format to.
840 * buf_size - size of buffer.
841 * t - time to format, in milliseconds.
843 void format_time(char* buf, int buf_size, long t)
845 if ( t < 3600000 )
847 snprintf(buf, buf_size, "%d:%02d",
848 (int) (t / 60000), (int) (t % 60000 / 1000));
850 else
852 snprintf(buf, buf_size, "%d:%02d:%02d",
853 (int) (t / 3600000), (int) (t % 3600000 / 60000),
854 (int) (t % 60000 / 1000));
859 /** Open a UTF-8 file and set file descriptor to first byte after BOM.
860 * If no BOM is present this behaves like open().
861 * If the file is opened for writing and O_TRUNC is set, write a BOM to
862 * the opened file and leave the file pointer set after the BOM.
864 #define BOM "\xef\xbb\xbf"
865 #define BOM_SIZE 3
867 int open_utf8(const char* pathname, int flags)
869 int fd;
870 unsigned char bom[BOM_SIZE];
872 fd = open(pathname, flags);
873 if(fd < 0)
874 return fd;
876 if(flags & (O_TRUNC | O_WRONLY))
878 write(fd, BOM, BOM_SIZE);
880 else
882 read(fd, bom, BOM_SIZE);
883 /* check for BOM */
884 if(memcmp(bom, BOM, BOM_SIZE))
885 lseek(fd, 0, SEEK_SET);
887 return fd;
891 #ifdef HAVE_LCD_COLOR
893 * Helper function to convert a string of 6 hex digits to a native colour
896 static int hex2dec(int c)
898 return (((c) >= '0' && ((c) <= '9')) ? (c) - '0' :
899 (toupper(c)) - 'A' + 10);
902 int hex_to_rgb(const char* hex, int* color)
904 int red, green, blue;
905 int i = 0;
907 while ((i < 6) && (isxdigit(hex[i])))
908 i++;
910 if (i < 6)
911 return -1;
913 red = (hex2dec(hex[0]) << 4) | hex2dec(hex[1]);
914 green = (hex2dec(hex[2]) << 4) | hex2dec(hex[3]);
915 blue = (hex2dec(hex[4]) << 4) | hex2dec(hex[5]);
917 *color = LCD_RGBPACK(red,green,blue);
919 return 0;
921 #endif /* HAVE_LCD_COLOR */
923 #ifdef HAVE_LCD_BITMAP
924 /* A simplified scanf - used (at time of writing) by wps parsing functions.
926 fmt - char array specifying the format of each list option. Valid values
927 are: d - int
928 s - string (sets pointer to string, without copying)
929 c - hex colour (RGB888 - e.g. ff00ff)
930 g - greyscale "colour" (0-3)
931 set_vals - if not NULL 1 is set in the bitplace if the item was read OK
932 0 if not read.
933 first item is LSB, (max 32 items! )
934 Stops parseing if an item is invalid unless the item == '-'
935 sep - list separator (e.g. ',' or '|')
936 str - string to parse, must be terminated by 0 or sep
937 ... - pointers to store the parsed values
939 return value - pointer to char after parsed data, 0 if there was an error.
943 /* '0'-'3' are ASCII 0x30 to 0x33 */
944 #define is0123(x) (((x) & 0xfc) == 0x30)
946 const char* parse_list(const char *fmt, uint32_t *set_vals,
947 const char sep, const char* str, ...)
949 va_list ap;
950 const char* p = str, *f = fmt;
951 const char** s;
952 int* d;
953 bool set;
954 int i=0;
956 va_start(ap, str);
957 if (set_vals)
958 *set_vals = 0;
959 while (*fmt)
961 /* Check for separator, if we're not at the start */
962 if (f != fmt)
964 if (*p != sep)
965 goto err;
966 p++;
968 set = false;
969 switch (*fmt++)
971 case 's': /* string - return a pointer to it (not a copy) */
972 s = va_arg(ap, const char **);
974 *s = p;
975 while (*p && *p != sep)
976 p++;
977 set = (s[0][0]!='-') && (s[0][1]!=sep) ;
978 break;
980 case 'd': /* int */
981 d = va_arg(ap, int*);
982 if (!isdigit(*p))
984 if (!set_vals || *p != '-')
985 goto err;
986 while (*p && *p != sep)
987 p++;
989 else
991 *d = *p++ - '0';
992 while (isdigit(*p))
993 *d = (*d * 10) + (*p++ - '0');
994 set = true;
997 break;
999 #ifdef HAVE_LCD_COLOR
1000 case 'c': /* colour (rrggbb - e.g. f3c1a8) */
1001 d = va_arg(ap, int*);
1003 if (hex_to_rgb(p, d) < 0)
1005 if (!set_vals || *p != '-')
1006 goto err;
1007 while (*p && *p != sep)
1008 p++;
1010 else
1012 p += 6;
1013 set = true;
1016 break;
1017 #endif
1019 #if LCD_DEPTH == 2 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 2)
1020 case 'g': /* greyscale colour (0-3) */
1021 d = va_arg(ap, int*);
1023 if (is0123(*p))
1025 *d = *p++ - '0';
1026 set = true;
1028 else if (!set_vals || *p != '-')
1029 goto err;
1030 else
1032 while (*p && *p != sep)
1033 p++;
1036 break;
1037 #endif
1039 default: /* Unknown format type */
1040 goto err;
1041 break;
1043 if (set_vals && set)
1044 *set_vals |= BIT_N(i);
1045 i++;
1048 va_end(ap);
1049 return p;
1051 err:
1052 va_end(ap);
1053 return 0;
1055 #endif