diff options
1433 files changed, 64355 insertions, 38164 deletions
diff --git a/.gitignore b/.gitignore index 93253775a8..5db3eaed58 100644 --- a/.gitignore +++ b/.gitignore @@ -60,45 +60,45 @@ __pycache__ /firmware/test/buflib/test_shrink_startchanged /firmware/test/buflib/test_shrink_unaligned -# /rbutil/ -/rbutil/Makefile -/rbutil/rbutil - -# /rbutil/ipodpatcher/ -/rbutil/ipodpatcher/ipodpatcher - -# /rbutil/chinachippatcher/ -/rbutil/chinachippatcher/chinachip - -# /rbutil/bspatch/ -/rbutil/bspatch/bspatch - -# /rbutil/rbutilqt/ -/rbutil/rbutilqt/build -/rbutil/rbutilqt/RockboxUtility -/rbutil/rbutilqt/RockboxUtility.* -/rbutil/rbutilqt/Makefile -/rbutil/rbutilqt/*.qm -/rbutil/rbutilqt/release -/rbutil/rbutilqt/debug -/rbutil/rbutilqt/*.Debug -/rbutil/rbutilqt/*.Release -/rbutil/rbutilqt/tags -/rbutil/rbutilqt/.qmake.stash - -# /rbutil/rbutilqt/lang/ -/rbutil/rbutilqt/lang/*.qm +# /utils/ +/utils/Makefile +/utils/rbutil + +# /utils/ipodpatcher/ +/utils/ipodpatcher/ipodpatcher + +# /utils/chinachippatcher/ +/utils/chinachippatcher/chinachip + +# /utils/bspatch/ +/utils/bspatch/bspatch + +# /utils/rbutilqt/ +/utils/rbutilqt/build +/utils/rbutilqt/RockboxUtility +/utils/rbutilqt/RockboxUtility.* +/utils/rbutilqt/Makefile +/utils/rbutilqt/*.qm +/utils/rbutilqt/release +/utils/rbutilqt/debug +/utils/rbutilqt/*.Debug +/utils/rbutilqt/*.Release +/utils/rbutilqt/tags +/utils/rbutilqt/.qmake.stash + +# /utils/rbutilqt/lang/ +/utils/rbutilqt/lang/*.qm # More rbutil -/rbutil/*/buildposix -/rbutil/*/uninstall_script.[ch] -/rbutil/*/install_script.[ch] -/rbutil/mkimxboot/mkimxboot -/rbutil/mknwzboot/mknwzboot -/rbutil/mkzenboot/mkzenboot -/rbutil/sansapatcher/sansapatcher -/rbutil/jztool/jztool -/rbutil/tools/bin2c +/utils/*/buildposix +/utils/*/uninstall_script.[ch] +/utils/*/install_script.[ch] +/utils/mkimxboot/mkimxboot +/utils/mknwzboot/mknwzboot +/utils/mkzenboot/mkzenboot +/utils/sansapatcher/sansapatcher +/utils/jztool/jztool +/utils/tools/bin2c # /tools/ /tools/bdf2bmp diff --git a/apps/SOURCES b/apps/SOURCES index 1628524805..444951bbcb 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -7,6 +7,7 @@ alarm_menu.c #endif abrepeat.c bookmark.c +core_keymap.c debug_menu.c filetypes.c language.c @@ -57,7 +58,6 @@ tree.c tagtree.c #endif filetree.c -scrobbler.c #ifdef IPOD_ACCESSORY_PROTOCOL iap/iap-core.c iap/iap-lingo0.c diff --git a/apps/abrepeat.h b/apps/abrepeat.h index f7ee65247c..4d2c4ea001 100644 --- a/apps/abrepeat.h +++ b/apps/abrepeat.h @@ -20,9 +20,38 @@ ****************************************************************************/ #ifndef _ABREPEAT_H_ #define _ABREPEAT_H_ - -#ifdef AB_REPEAT_ENABLE #include <stdbool.h> + +#ifndef AB_REPEAT_ENABLE /* Dummy functions */ +static inline bool ab_repeat_mode_enabled(void) +{ + return false; +} +static inline bool ab_bool_dummy_marker(unsigned int song_position) +{ + (void) song_position; + return false; +} +static inline void ab_void_dummy_marker(unsigned int song_position) +{ + (void) song_position; +} +static inline void ab_dummy_voidfn(void){} + +#define ab_repeat_init ab_dummy_voidfn +#define ab_before_A_marker ab_bool_dummy_marker +#define ab_after_A_marker ab_bool_dummy_marker +#define ab_jump_to_A_marker ab_dummy_voidfn +#define ab_reset_markers ab_dummy_voidfn +#define ab_set_A_marker ab_void_dummy_marker +#define ab_set_B_marker ab_void_dummy_marker +#define ab_get_A_marker ab_bool_dummy_marker +#define ab_get_B_marker ab_bool_dummy_marker +#define ab_end_of_track_report ab_dummy_voidfn +#define ab_reached_B_marker ab_bool_dummy_marker +#define ab_position_report ab_void_dummy_marker + +#else /*def AB_REPEAT_ENABLE*/ #include "audio.h" #include "kernel.h" /* needed for HZ */ diff --git a/apps/action.c b/apps/action.c index b31c4fa927..208fea4a97 100644 --- a/apps/action.c +++ b/apps/action.c @@ -34,6 +34,7 @@ #include "button.h" #include "action.h" #include "kernel.h" +#include "core_alloc.h" #include "splash.h" #include "settings.h" @@ -69,6 +70,10 @@ static action_last_t action_last = .tick = 0, .wait_for_release = false, +#ifndef DISABLE_ACTION_REMAP + .key_remap = 0, +#endif + #ifdef HAVE_TOUCHSCREEN .ts_data = 0, .ts_short_press = false, @@ -120,7 +125,7 @@ static bool is_action_filtered(int action, unsigned int mask, int context) { case ACTION_NONE: break; -/*Actions that are not mapped will not turn on the backlight option NOUNMAPPED*/ + /* Actions that are not mapped will not turn on the backlight */ case ACTION_UNKNOWN: match = has_flag(mask, SEL_ACTION_NOUNMAPPED); break; @@ -128,15 +133,15 @@ static bool is_action_filtered(int action, unsigned int mask, int context) case ACTION_FM_PLAY: match = has_flag(mask, SEL_ACTION_PLAY); break; - //case ACTION_STD_PREVREPEAT: // seek not exempted outside of WPS - //case ACTION_STD_NEXTREPEAT: + /* case ACTION_STD_PREVREPEAT:*/ /* seek not exempted outside of WPS */ + /* case ACTION_STD_NEXTREPEAT: */ case ACTION_WPS_SEEKBACK: case ACTION_WPS_SEEKFWD: case ACTION_WPS_STOPSEEK: match = has_flag(mask, SEL_ACTION_SEEK); break; - //case ACTION_STD_PREV: // skip/scrollwheel not exempted outside of WPS - //case ACTION_STD_NEXT: + /* case ACTION_STD_PREV: */ /* skip/scrollwheel not */ + /* case ACTION_STD_NEXT: */ /* exempted outside of WPS */ case ACTION_WPS_SKIPNEXT: case ACTION_WPS_SKIPPREV: case ACTION_FM_NEXT_PRESET: @@ -144,8 +149,8 @@ static bool is_action_filtered(int action, unsigned int mask, int context) match = has_flag(mask, SEL_ACTION_SKIP); break; #ifdef HAVE_VOLUME_IN_LIST - case ACTION_LIST_VOLUP: // volume exempted outside of WPS if the device supports it - case ACTION_LIST_VOLDOWN: + case ACTION_LIST_VOLUP: /* volume exempted outside of WPS */ + case ACTION_LIST_VOLDOWN: /* ( if the device supports it )*/ #endif case ACTION_WPS_VOLUP: case ACTION_WPS_VOLDOWN: @@ -499,7 +504,7 @@ static inline int action_code_worker(action_last_t *last, int *end ) { int ret = ACTION_UNKNOWN; - int i = 0; + int i = *end; unsigned int found = 0; while (cur->items[i].button_code != BUTTON_NONE) { @@ -583,21 +588,52 @@ static inline int get_next_context(const struct button_mapping *items, int i) * for a more in-depth explanation * places action into current_action */ + static inline void action_code_lookup(action_last_t *last, action_cur_t *cur) { - int action = ACTION_NONE; + int action, i; int context = cur->context; - int i = 0; - cur->is_prebutton = false; -#ifdef HAVE_LOCKED_ACTIONS +#if !defined(HAS_BUTTON_HOLD) && !defined(BOOTLOADER) /* This only applies to the first context, to allow locked contexts to * specify a fall through to their non-locked version */ if (is_keys_locked()) context |= CONTEXT_LOCKED; #endif +#ifndef DISABLE_ACTION_REMAP + /* attempt to look up the button in user supplied remap */ + if(last->key_remap && (context & CONTEXT_PLUGIN) == 0) + { + if ((cur->button & BUTTON_REMOTE) != 0) + { + context |= CONTEXT_REMOTE; + } + cur->items = core_get_data(last->key_remap); + i = 0; + action = ACTION_UNKNOWN; + /* check the lut at the beginning for the desired context */ + while (cur->items[i].button_code != BUTTON_NONE) + { + if (cur->items[i].action_code == CORE_CONTEXT_REMAP(context)) + { + i = cur->items[i].button_code; + action = action_code_worker(last, cur, &i); + if (action != ACTION_UNKNOWN) + { + cur->action = action; + return; + } + } + i++; + } + } +#endif + + i = 0; + action = ACTION_NONE; + /* attempt to look up the button in the in-built keymaps */ for(;;) { /* logf("context = %x",context); */ @@ -609,9 +645,13 @@ static inline void action_code_lookup(action_last_t *last, action_cur_t *cur) #endif if ((context & CONTEXT_PLUGIN) && cur->get_context_map) + { cur->items = cur->get_context_map(context); + } else + { cur->items = get_context_mapping(context); + } if (cur->items != NULL) { @@ -960,7 +1000,8 @@ static inline int do_backlight(action_last_t *last, action_cur_t *cur, int actio && power_input_present()); #endif /* skip if backlight on | incorrect context | SEL_ACTION_NOEXT + ext pwr */ - if ((cur->context == CONTEXT_FM || cur->context == CONTEXT_WPS) && bl_is_off) + if (bl_is_off && (cur->context == CONTEXT_FM || cur->context == CONTEXT_WPS || + cur->context == CONTEXT_MAINMENU)) { filtered = is_action_filtered(action, last->backlight_mask, cur->context); bl_activate = !is_action_discarded(cur, filtered, &last->bl_filter_tick); @@ -1150,6 +1191,92 @@ int get_action(int context, int timeout) return action; } +int action_set_keymap(struct button_mapping* core_keymap, int count) +{ +#ifdef DISABLE_ACTION_REMAP + (void)core_keymap; + (void)count; + return -1; +#else + if (count <= 0 || core_keymap == NULL) + return action_set_keymap_handle(0, 0); + + size_t keyremap_buf_size = count * sizeof(struct button_mapping); + int handle = core_alloc(keyremap_buf_size); + if (handle < 0) + return -6; + + memcpy(core_get_data(handle), core_keymap, keyremap_buf_size); + return action_set_keymap_handle(handle, count); +#endif +} + +int action_set_keymap_handle(int handle, int count) +{ +#ifdef DISABLE_ACTION_REMAP + (void)core_keymap; + (void)count; + return -1; +#else + /* free an existing remap */ + action_last.key_remap = core_free(action_last.key_remap); + + /* if clearing the remap, we're done */ + if (count <= 0 || handle <= 0) + return 0; + + /* validate the keymap */ + struct button_mapping* core_keymap = core_get_data(handle); + struct button_mapping* entry = &core_keymap[count - 1]; + if (entry->action_code != (int) CONTEXT_STOPSEARCHING || + entry->button_code != BUTTON_NONE) /* check for sentinel at end*/ + { + /* missing sentinel entry */ + return -1; + } + + /* check the lut at the beginning for invalid offsets */ + for (int i = 0; i < count; ++i) + { + entry = &core_keymap[i]; + if (entry->action_code == (int)CONTEXT_STOPSEARCHING) + break; + + if ((entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + int firstbtn = entry->button_code; + int endpos = firstbtn + entry->pre_button_code; + if (firstbtn > count || firstbtn < i || endpos > count) + { + /* offset out of bounds */ + return -2; + } + + if (core_keymap[endpos].button_code != BUTTON_NONE) + { + /* stop sentinel is not at end of action lut */ + return -3; + } + } + else + { + /* something other than a context remap in the lut */ + return -4; + } + + if (i+1 >= count) + { + /* no sentinel in the lut */ + return -5; + } + } + + /* success */ + action_last.key_remap = handle; + return count; +#endif +} + int get_custom_action(int context,int timeout, const struct button_mapping* (*get_context_map)(int)) { diff --git a/apps/action.h b/apps/action.h index ad91f31535..6e1278b33c 100644 --- a/apps/action.h +++ b/apps/action.h @@ -28,13 +28,15 @@ #define TIMEOUT_NOBLOCK 0 #define CONTEXT_STOPSEARCHING 0xFFFFFFFF + #define CONTEXT_REMOTE 0x80000000 /* | this against another context to get remote buttons for that context */ #define CONTEXT_CUSTOM 0x40000000 /* | this against anything to get your context number */ #define CONTEXT_CUSTOM2 0x20000000 /* as above */ #define CONTEXT_PLUGIN 0x10000000 /* for plugins using get_custom_action */ -#ifdef HAVE_LOCKED_ACTIONS +#define CONTEXT_REMAPPED 0x08000000 /* marker for key remap context table */ +#define CORE_CONTEXT_REMAP(context) (CONTEXT_REMAPPED | context) #define CONTEXT_LOCKED 0x04000000 /* flag to use alternate keymap when screen is locked */ -#endif + #define LAST_ITEM_IN_LIST { CONTEXT_STOPSEARCHING, BUTTON_NONE, BUTTON_NONE } #define LAST_ITEM_IN_LIST__NEXTLIST(a) { a, BUTTON_NONE, BUTTON_NONE } @@ -92,7 +94,7 @@ void set_selective_backlight_actions(bool selective, unsigned int mask, enum { CONTEXT_STD = 0, /* These CONTEXT_ values were here before me, - there values may have significance, so dont touch! */ + their values may have significance, so dont touch! */ CONTEXT_WPS = 1, CONTEXT_TREE = 2, CONTEXT_RECORD = 3, @@ -129,6 +131,7 @@ enum { CONTEXT_USB_HID_MODE_PRESENTATION, CONTEXT_USB_HID_MODE_BROWSER, CONTEXT_USB_HID_MODE_MOUSE, + LAST_CONTEXT_PLACEHOLDER, }; @@ -244,7 +247,6 @@ enum { ACTION_SETTINGS_DEC, ACTION_SETTINGS_DECREPEAT, ACTION_SETTINGS_DECBIGSTEP, - ACTION_SETTINGS_RESET, ACTION_SETTINGS_SET, /* Used by touchscreen targets */ /* bookmark screen */ @@ -415,6 +417,10 @@ typedef struct bool repeated; bool wait_for_release; +#ifndef DISABLE_ACTION_REMAP + int key_remap; +#endif + #ifdef HAVE_TOUCHSCREEN bool ts_short_press; int ts_data; @@ -441,6 +447,11 @@ bool action_userabort(int timeout); /* no other code should need this apart from action.c */ const struct button_mapping* get_context_mapping(int context); +/* load a key map to allow buttons for actions to be remapped see: core_keymap */ +int action_set_keymap(struct button_mapping* core_keymap, int count); +/* load keymap in a handle: takes ownership of the handle on success */ +int action_set_keymap_handle(int handle, int count); + /* returns the status code variable from action.c for the button just pressed If button != NULL it will be set to the actual button code */ #define ACTION_REMOTE 0x1 /* remote was pressed */ diff --git a/apps/alarm_menu.c b/apps/alarm_menu.c index 62b54a84bb..67f8d1e8dd 100644 --- a/apps/alarm_menu.c +++ b/apps/alarm_menu.c @@ -38,161 +38,56 @@ #include "splash.h" #include "viewport.h" -static void speak_time(int hours, int minutes, bool speak_hours, bool enqueue) -{ - if (global_settings.talk_menu){ - if(speak_hours) { - talk_value(hours, UNIT_HOUR, enqueue); - talk_value(minutes, UNIT_MIN, true); - } else { - talk_value(minutes, UNIT_MIN, enqueue); - } - } -} - int alarm_screen(void) { - int h, m; - bool done = false; - struct tm *tm; - int togo; - int button; - bool update = true; - bool hour_wrapped = false; - struct viewport vp[NB_SCREENS]; - struct viewport * last_vp; - - rtc_get_alarm(&h, &m); + bool usb, update; + struct tm *now = get_time(); + struct tm atm; + memcpy(&atm, now, sizeof(struct tm)); + rtc_get_alarm(&atm.tm_hour, &atm.tm_min); /* After a battery change the RTC values are out of range */ - if (m > 60 || h > 24) { - m = 0; - h = 12; - } else { - m = m / 5 * 5; /* 5 min accuracy should be enough */ - } - FOR_NB_SCREENS(i) - { - viewport_set_defaults(&vp[i], i); - } + if (!valid_time(&atm)) + memcpy(&atm, now, sizeof(struct tm)); + atm.tm_sec = 0; - while(!done) { - if(update) - { - FOR_NB_SCREENS(i) - { - screens[i].set_viewport(&vp[i]); - screens[i].clear_viewport(); - screens[i].puts(0, 4, str(LANG_ALARM_MOD_KEYS)); - } - /* Talk when entering the wakeup screen */ - speak_time(h, m, true, true); - update = false; - } + usb = set_time_screen(str(LANG_ALARM_MOD_TIME), &atm, false); + update = valid_time(&atm); /* set_time returns invalid time if canceled */ - FOR_NB_SCREENS(i) - { - last_vp = screens[i].set_viewport(&vp[i]); - screens[i].putsf(0, 1, str(LANG_ALARM_MOD_TIME)); - screens[i].putsf(0, 2, "%02d:%02d", h, m); - screens[i].update_viewport(); - screens[i].set_viewport(last_vp); - } - button = get_action(CONTEXT_SETTINGS,HZ); + if (!usb && update) + { - switch(button) { - case ACTION_STD_OK: + now = get_time(); + int nmins = now->tm_min + (now->tm_hour * 60); + int amins = atm.tm_min + (atm.tm_hour * 60); + int mins_togo = (amins - nmins + 1440) % 1440; /* prevent that an alarm occurs in the shutdown procedure */ /* accept alarms only if they are in 2 minutes or more */ - tm = get_time(); - togo = (m + h * 60 - tm->tm_min - tm->tm_hour * 60 + 1440) % 1440; - - if (togo > 1) { + if (mins_togo > 1) { rtc_init(); - rtc_set_alarm(h,m); + rtc_set_alarm(atm.tm_hour,atm.tm_min); rtc_enable_alarm(true); if (global_settings.talk_menu) { talk_id(LANG_ALARM_MOD_TIME_TO_GO, true); - talk_value(togo / 60, UNIT_HOUR, true); - talk_value(togo % 60, UNIT_MIN, true); + talk_value(mins_togo / 60, UNIT_HOUR, true); + talk_value(mins_togo % 60, UNIT_MIN, true); talk_force_enqueue_next(); } splashf(HZ*2, str(LANG_ALARM_MOD_TIME_TO_GO), - togo / 60, togo % 60); - done = true; + mins_togo / 60, mins_togo % 60); } else { splash(HZ, ID2P(LANG_ALARM_MOD_ERROR)); - update = true; - } - break; - - /* inc(m) */ - case ACTION_SETTINGS_INC: - case ACTION_SETTINGS_INCREPEAT: - m += 5; - if (m == 60) { - h += 1; - m = 0; - hour_wrapped = true; + update = false; } - if (h == 24) - h = 0; - - speak_time(h, m, hour_wrapped, false); - break; - - /* dec(m) */ - case ACTION_SETTINGS_DEC: - case ACTION_SETTINGS_DECREPEAT: - m -= 5; - if (m == -5) { - h -= 1; - m = 55; - hour_wrapped = true; - } - if (h == -1) - h = 23; - - speak_time(h, m, hour_wrapped, false); - break; - - /* inc(h) */ - case ACTION_STD_NEXT: - case ACTION_STD_NEXTREPEAT: - h = (h+1) % 24; - - if (global_settings.talk_menu) - talk_value(h, UNIT_HOUR, false); - break; - - /* dec(h) */ - case ACTION_STD_PREV: - case ACTION_STD_PREVREPEAT: - h = (h+23) % 24; - - if (global_settings.talk_menu) - talk_value(h, UNIT_HOUR, false); - break; + } - case ACTION_STD_CANCEL: - rtc_enable_alarm(false); + if (usb || !update) + { + if (!usb) splash(HZ*2, ID2P(LANG_ALARM_MOD_DISABLE)); - done = true; - break; - - case ACTION_NONE: - hour_wrapped = false; - break; - - default: - if(default_event_handler(button) == SYS_USB_CONNECTED) - { - rtc_enable_alarm(false); - return 1; - } - break; - } + rtc_enable_alarm(false); + return 1; } return 0; } diff --git a/apps/apps.make b/apps/apps.make index 6afcd12b5c..47b015bc92 100644 --- a/apps/apps.make +++ b/apps/apps.make @@ -24,7 +24,7 @@ $(BUILDDIR)/apps/features: $(APPSDIR)/features.txt $(BUILDDIR)/firmware/common/ $(call PRINTS,PP $(<F)) $(SILENT)$(CC) $(PPCFLAGS) \ -E -P -imacros "config.h" -imacros "button.h" -x c $< | \ - grep -v "^\#" | grep -v "^ *$$" > $(BUILDDIR)/apps/features; \ + grep -v "^#" | grep -v "^ *$$" > $(BUILDDIR)/apps/features; \ $(BUILDDIR)/apps/genlang-features: $(BUILDDIR)/apps/features $(call PRINTS,GEN $(subst $(BUILDDIR)/,,$@))tr \\n : < $< > $@ diff --git a/apps/bookmark.c b/apps/bookmark.c index 70dbd8075d..68c10b36e7 100644 --- a/apps/bookmark.c +++ b/apps/bookmark.c @@ -43,6 +43,9 @@ #include "file.h" #include "pathfuncs.h" +/*#define LOGF_ENABLE*/ +#include "logf.h" + #define MAX_BOOKMARKS 10 #define MAX_BOOKMARK_SIZE 350 #define RECENT_BOOKMARK_FILE ROCKBOX_DIR "/most-recent.bmark" @@ -66,286 +69,289 @@ struct bookmark_list #define BM_SPEED 0x02 /* bookmark values */ -static struct { +struct resume_info{ + const struct mp3entry *id3; int resume_index; unsigned long resume_offset; int resume_seed; - long resume_time; + long resume_elapsed; int repeat_mode; bool shuffle; /* optional values */ int pitch; int speed; -} bm; - -static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, - bool most_recent); -static char* create_bookmark(void); -static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id); -static void say_bookmark(const char* bookmark, - int bookmark_id, bool show_playlist_name); -static bool play_bookmark(const char* bookmark); -static bool generate_bookmark_file_name(const char *in); -static bool parse_bookmark(const char *bookmark, const bool get_filenames, const bool strip_dir); -static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line); -static const char* get_bookmark_info(int list_index, - void* data, - char *buffer, - size_t buffer_len); -static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume, char** selected_bookmark); -static bool write_bookmark(bool create_bookmark_file, const char *bookmark); -static int get_bookmark_count(const char* bookmark_file_name); +}; -#define TEMP_BUF_SIZE (MAX_PATH + 1) +/* Temp buffer used for reading, create_bookmark and filename creation */ +#define TEMP_BUF_SIZE (MAX(MAX_BOOKMARK_SIZE, MAX_PATH + 1)) static char global_temp_buffer[TEMP_BUF_SIZE]; -/* File name created by generate_bookmark_file_name */ -static char global_bookmark_file_name[MAX_PATH]; -static char global_read_buffer[MAX_BOOKMARK_SIZE]; -/* Bookmark created by create_bookmark*/ -static char global_bookmark[MAX_BOOKMARK_SIZE]; -/* Filename from parsed bookmark (can be made local where needed) */ -static char global_filename[MAX_PATH]; -/* ----------------------------------------------------------------------- */ -/* This is an interface function from the context menu. */ -/* Returns true on successful bookmark creation. */ -/* ----------------------------------------------------------------------- */ -bool bookmark_create_menu(void) +static inline void get_hash(const char *key, uint32_t *hash, int len) { - return write_bookmark(true, create_bookmark()); + *hash = crc_32(key, len, *hash); /* this is probably sufficient */ } -/* ----------------------------------------------------------------------- */ -/* This function acts as the load interface from the context menu. */ -/* This function determines the bookmark file name and then loads that file*/ -/* for the user. The user can then select or delete previous bookmarks. */ -/* This function returns BOOKMARK_SUCCESS on the selection of a track to */ -/* resume, BOOKMARK_FAIL if the menu is exited without a selection and */ -/* BOOKMARK_USB_CONNECTED if the menu is forced to exit due to a USB */ -/* connection. */ -/* ----------------------------------------------------------------------- */ -int bookmark_load_menu(void) +static const char* skip_tokens(const char* s, int ntokens) { - char* bookmark; - int ret = BOOKMARK_FAIL; - - push_current_activity(ACTIVITY_BOOKMARKSLIST); - - char* name = playlist_get_name(NULL, global_temp_buffer, - sizeof(global_temp_buffer)); - if (generate_bookmark_file_name(name)) + for (int i = 0; i < ntokens; i++) { - ret = select_bookmark(global_bookmark_file_name, false, &bookmark); - if (bookmark != NULL) + while (*s && *s != ';') { - ret = play_bookmark(bookmark) ? BOOKMARK_SUCCESS : BOOKMARK_FAIL; + s++; + } + + if (*s) + { + s++; } } + return s; +} - pop_current_activity(); +static int int_token(const char **s) +{ + int ret = atoi(*s); + *s = skip_tokens(*s, 1); return ret; } -/* ----------------------------------------------------------------------- */ -/* Gives the user a list of the Most Recent Bookmarks. This is an */ -/* interface function */ -/* Returns true on the successful selection of a recent bookmark. */ -/* ----------------------------------------------------------------------- */ -bool bookmark_mrb_load() +static long long_token(const char **s) { - char* bookmark; - bool ret = false; + /* Should be atol, but we don't have it. */ + return int_token(s); +} - push_current_activity(ACTIVITY_BOOKMARKSLIST); - select_bookmark(RECENT_BOOKMARK_FILE, false, &bookmark); - if (bookmark != NULL) +/*-------------------------------------------------------------------------*/ +/* Get the name of the playlist and the name of the track from a bookmark. */ +/* Returns true iff both were extracted. */ +/*-------------------------------------------------------------------------*/ +static bool bookmark_get_playlist_and_track_hash(const char *bookmark, + uint32_t *pl_hash, + uint32_t *track_hash) +{ + *pl_hash = 0; + *track_hash = 0; + int pl_len; + const char *pl_start, *pl_end, *track; + + logf("%s", __func__); + + pl_start = strchr(bookmark,'/'); + if (!(pl_start)) + return false; + + pl_end = skip_tokens(pl_start, 1) - 1; + pl_len = pl_end - pl_start; + + track = pl_end + 1; + get_hash(pl_start, pl_hash, pl_len); + + if (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK) { - ret = play_bookmark(bookmark); + get_hash(track, track_hash, strlen(track)); } - pop_current_activity(); - return ret; + + return true; } /* ----------------------------------------------------------------------- */ -/* This function handles an autobookmark creation. This is an interface */ -/* function. */ -/* Returns true on successful bookmark creation. */ +/* This function takes a bookmark and parses it. This function also */ +/* validates the bookmark. Valid filenamebuf indicates whether */ +/* the filename tokens are to be extracted. */ +/* Returns true on successful bookmark parse. */ /* ----------------------------------------------------------------------- */ -bool bookmark_autobookmark(bool prompt_ok) +static bool parse_bookmark(char *filenamebuf, + size_t filenamebufsz, + const char *bookmark, + struct resume_info *resume_info, + const bool strip_dir) { - char* bookmark; - bool update; - - if (!bookmark_is_bookmarkable_state()) - return false; + const char* s = bookmark; + const char* end; - audio_pause(); /* first pause playback */ - update = (global_settings.autoupdatebookmark && bookmark_exists()); - bookmark = create_bookmark(); +#define GET_INT_TOKEN(var) var = int_token(&s) +#define GET_LONG_TOKEN(var) var = long_token(&s) +#define GET_BOOL_TOKEN(var) var = (int_token(&s) != 0) - if (update) - return write_bookmark(true, bookmark); + /* if new format bookmark, extract the optional content flags, + otherwise treat as an original format bookmark */ + int opt_flags = 0; + int opt_pitch = 0; + int opt_speed = 0; + int old_format = ((strchr(s, '>') == s) ? 0 : 1); + if (old_format == 0) /* this is a new format bookmark */ + { + s++; + GET_INT_TOKEN(opt_flags); + opt_pitch = (opt_flags & BM_PITCH) ? 1:0; + opt_speed = (opt_flags & BM_SPEED) ? 1:0; + } - switch (global_settings.autocreatebookmark) + /* extract all original bookmark tokens */ + if (resume_info) { - case BOOKMARK_YES: - return write_bookmark(true, bookmark); + GET_INT_TOKEN(resume_info->resume_index); + GET_LONG_TOKEN(resume_info->resume_offset); + GET_INT_TOKEN(resume_info->resume_seed); - case BOOKMARK_NO: - return false; + s = skip_tokens(s, old_format); /* skip deprecated token */ - case BOOKMARK_RECENT_ONLY_YES: - return write_bookmark(false, bookmark); - } - const char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY)}; - const struct text_message message={lines, 1}; + GET_LONG_TOKEN(resume_info->resume_elapsed); + GET_INT_TOKEN(resume_info->repeat_mode); + GET_BOOL_TOKEN(resume_info->shuffle); - if(prompt_ok && gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES) + /* extract all optional bookmark tokens */ + if (opt_pitch != 0) + GET_INT_TOKEN(resume_info->pitch); + if (opt_speed != 0) + GET_INT_TOKEN(resume_info->speed); + } + else /* no resume info we just want the file name strings */ { - if (global_settings.autocreatebookmark == BOOKMARK_RECENT_ONLY_ASK) - return write_bookmark(false, bookmark); - else - return write_bookmark(true, bookmark); + #define DEFAULT_BM_TOKENS 6 + int skipct = DEFAULT_BM_TOKENS + old_format + opt_pitch + opt_speed; + s = skip_tokens(s, skipct); + #undef DEFAULT_BM_TOKENS } - return false; -} -/* ----------------------------------------------------------------------- */ -/* This function takes the current current resume information and writes */ -/* that to the beginning of the bookmark file. */ -/* This file will contain N number of bookmarks in the following format: */ -/* resume_index*resume_offset*resume_seed*resume_first_index* */ -/* resume_file*milliseconds*MP3 Title* */ -/* Returns true on successful bookmark write. */ -/* Returns false if any part of the bookmarking process fails. It is */ -/* possible that a bookmark is successfully added to the most recent */ -/* bookmark list but fails to be added to the bookmark file or vice versa. */ -/* ------------------------------------------------------------------------*/ -static bool write_bookmark(bool create_bookmark_file, const char *bookmark) -{ - bool ret=true; - - if (!bookmark) + if (*s == 0) { - ret = false; /* something didn't happen correctly, do nothing */ + return false; } - else - { - if (global_settings.usemrb) - ret = add_bookmark(RECENT_BOOKMARK_FILE, bookmark, true); + end = strchr(s, ';'); - /* writing the bookmark */ - if (create_bookmark_file) + /* extract file names */ + if(filenamebuf) + { + size_t len = (end == NULL) ? strlen(s) : (size_t) (end - s); + len = MIN(TEMP_BUF_SIZE - 1, len); + strmemccpy(global_temp_buffer, s, len + 1); + + if (end != NULL) { - char* name = playlist_get_name(NULL, global_temp_buffer, - sizeof(global_temp_buffer)); - if (generate_bookmark_file_name(name)) - { - ret = ret & add_bookmark(global_bookmark_file_name, bookmark, false); - } - else + end++; + if (strip_dir) { - ret = false; /* generating bookmark file failed */ + s = strrchr(end, '/'); + if (s) + { + end = s; + end++; + } } + strmemccpy(filenamebuf, end, filenamebufsz); } - } - - splash(HZ, ret ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS) - : ID2P(LANG_BOOKMARK_CREATE_FAILURE)); + } - return ret; + return true; } -/* Get the name of the playlist and the name of the track from a bookmark. */ -/* Returns true iff both were extracted. */ -static bool get_playlist_and_track(const char *bookmark, char **pl_start, - char **pl_end, char **track) +/* ------------------------------------------------------------------------- */ +/* This function takes a filename and appends .tmp. This function also opens */ +/* the resulting file based on oflags, filename will be in buf on return */ +/* Returns file descriptor */ +/* --------------------------------------------------------------------------*/ +static int open_temp_bookmark(char *buf, + size_t bufsz, + int oflags, + const char* filename) { - *pl_start = strchr(bookmark,'/'); - if (!(*pl_start)) - return false; - *pl_end = strrchr(bookmark,';'); - *track = *pl_end + 1; - return true; + if(filename[0] == '/') + filename++; + /* Opening up a temp bookmark file */ + int fd = open_pathfmt(buf, bufsz, oflags, "/%s.tmp", filename); +#ifdef LOGF_ENABLE + if (oflags & O_PATH) + logf("tempfile path %s", buf); + else + logf("opening tempfile %s", buf); +#endif + return fd; } /* ----------------------------------------------------------------------- */ /* This function adds a bookmark to a file. */ /* Returns true on successful bookmark add. */ /* ------------------------------------------------------------------------*/ -static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, +static bool add_bookmark(const char* bookmark_file_name, + const char* bookmark, bool most_recent) { - int temp_bookmark_file = 0; - int bookmark_file = 0; - int bookmark_count = 0; - char *pl_start = NULL, *bm_pl_start; - char *pl_end = NULL, *bm_pl_end; - int pl_len = 0, bm_pl_len; - char *track = NULL, *bm_track; - bool comp_playlist = false; - bool comp_track = false; - bool equal; + char fnamebuf[MAX_PATH]; + int temp_bookmark_file = 0; + int bookmark_file = 0; + int bookmark_count = 0; + bool comp_playlist = false; + bool comp_track = false; + bool equal; + uint32_t pl_hash, pl_track_hash; + uint32_t bm_pl_hash, bm_pl_track_hash; /* Opening up a temp bookmark file */ - snprintf(global_temp_buffer, sizeof(global_temp_buffer), - "%s.tmp", bookmark_file_name); - temp_bookmark_file = open(global_temp_buffer, - O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (temp_bookmark_file < 0) - return false; /* can't open the temp file */ + temp_bookmark_file = open_temp_bookmark(fnamebuf, + sizeof(fnamebuf), + O_WRONLY | O_CREAT | O_TRUNC, + bookmark_file_name); + + if (temp_bookmark_file < 0 || !bookmark) + return false; /* can't open the temp file or no bookmark */ if (most_recent && ((global_settings.usemrb == BOOKMARK_ONE_PER_PLAYLIST) || (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK))) { - if (get_playlist_and_track(bookmark, &pl_start, &pl_end, &track)) + + if (bookmark_get_playlist_and_track_hash(bookmark, &pl_hash, &pl_track_hash)) { comp_playlist = true; - pl_len = pl_end - pl_start; - if (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK) - comp_track = true; + comp_track = (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK); } } + logf("adding bookmark to %s [%s]", fnamebuf, bookmark); /* Writing the new bookmark to the begining of the temp file */ write(temp_bookmark_file, bookmark, strlen(bookmark)); write(temp_bookmark_file, "\n", 1); bookmark_count++; + /* WARNING underlying buffer to *bookmrk gets overwritten after this point! */ + /* Reading in the previous bookmarks and writing them to the temp file */ + logf("opening old bookmark %s", bookmark_file_name); bookmark_file = open(bookmark_file_name, O_RDONLY); if (bookmark_file >= 0) { - while (read_line(bookmark_file, global_read_buffer, - sizeof(global_read_buffer)) > 0) + while (read_line(bookmark_file, global_temp_buffer, + sizeof(global_temp_buffer)) > 0) { /* The MRB has a max of MAX_BOOKMARKS in it */ /* This keeps it from getting too large */ if (most_recent && (bookmark_count >= MAX_BOOKMARKS)) break; - if (!parse_bookmark(global_read_buffer, false, false)) + if (!parse_bookmark(NULL, 0, global_temp_buffer, NULL, false)) break; equal = false; if (comp_playlist) { - if (get_playlist_and_track(global_read_buffer, &bm_pl_start, - &bm_pl_end, &bm_track)) + if (bookmark_get_playlist_and_track_hash(global_temp_buffer, + &bm_pl_hash, &bm_pl_track_hash)) { - bm_pl_len = bm_pl_end - bm_pl_start; - equal = (pl_len == bm_pl_len) && !strncmp(pl_start, bm_pl_start, pl_len); + equal = (pl_hash == bm_pl_hash); if (equal && comp_track) - equal = !strcmp(track, bm_track); + { + equal = (pl_track_hash == bm_pl_track_hash); + } } } if (!equal) { bookmark_count++; - write(temp_bookmark_file, global_read_buffer, - strlen(global_read_buffer)); + /*logf("copying old bookmark [%s]", global_temp_buffer);*/ + write(temp_bookmark_file, global_temp_buffer, + strlen(global_temp_buffer)); write(temp_bookmark_file, "\n", 1); } } @@ -354,180 +360,233 @@ static bool add_bookmark(const char* bookmark_file_name, const char* bookmark, close(temp_bookmark_file); remove(bookmark_file_name); - rename(global_temp_buffer, bookmark_file_name); + rename(fnamebuf, bookmark_file_name); return true; } +/* ----------------------------------------------------------------------- */ +/* This function is used by multiple functions and is used to generate a */ +/* bookmark named based off of the input. */ +/* Changing this function could result in how the bookmarks are stored. */ +/* it would be here that the centralized/decentralized bookmark code */ +/* could be placed. */ +/* Returns true if the file name is generated, false if it was too long */ +/* ----------------------------------------------------------------------- */ +static bool generate_bookmark_file_name(char *filenamebuf, + size_t filenamebufsz, + const char *bmarknamein, + size_t bmarknamelen) +{ + /* if this is a root dir MP3, rename the bookmark file root_dir.bmark */ + /* otherwise, name it based on the bmarknamein variable */ + if (!strcmp("/", bmarknamein)) + strmemccpy(filenamebuf, "/root_dir.bmark", filenamebufsz); + else + { + size_t buflen, len; + /* strmemccpy considers the NULL so bmarknamelen is one off */ + buflen = MIN(filenamebufsz -1 , bmarknamelen); + if (buflen >= filenamebufsz) + return false; + + strmemccpy(filenamebuf, bmarknamein, buflen + 1); + + len = strlen(filenamebuf); + +#ifdef HAVE_MULTIVOLUME + /* The "root" of an extra volume need special handling too. */ + const char *filename; + path_strip_volume(filenamebuf, &filename, true); + bool volume_root = *filename == '\0'; +#endif + if(filenamebuf[len-1] == '/') { + filenamebuf[len-1] = '\0'; + } + + const char *name = ".bmark"; +#ifdef HAVE_MULTIVOLUME + if (volume_root) + name = "/volume_dir.bmark"; +#endif + len = strlcat(filenamebuf, name, filenamebufsz); + + if(len >= filenamebufsz) + return false; + } + logf ("generated name '%s' from '%.*s'", + filenamebuf, (int)bmarknamelen, bmarknamein); + return true; +} + /* GCC 7 and up complain about the snprintf in create_bookmark() when compiled with -D_FORTIFY_SOURCE or -Wformat-truncation This is a false positive, so disable it here only */ -#if __GNUC__ >= 7 +/* SHOULD NO LONGER BE NEEDED --Bilgus 11-2022 */ +#if 0 /* __GNUC__ >= 7 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" #endif /* ----------------------------------------------------------------------- */ /* This function takes the system resume data and formats it into a valid */ /* bookmark. */ -/* Returns not NULL on successful bookmark format. */ +/* playlist name and name len are passed back through the name/namelen */ +/* Return is not NULL on successful bookmark format. */ /* ----------------------------------------------------------------------- */ -static char* create_bookmark() +static char* create_bookmark(char **name, + size_t *namelen, + struct resume_info *resume_info) { - int resume_index = 0; - char *file; - - if (!bookmark_is_bookmarkable_state()) - return NULL; /* something didn't happen correctly, do nothing */ + const char *file; + char *buf = global_temp_buffer; + size_t bufsz = sizeof(global_temp_buffer); - /* grab the currently playing track */ - struct mp3entry *id3 = audio_current_track(); - if(!id3) - return NULL; - - /* Get some basic resume information */ - /* queue_resume and queue_resume_index are not used and can be ignored.*/ - playlist_get_resume_info(&resume_index); - - /* Get the currently playing file minus the path */ - /* This is used when displaying the available bookmarks */ - file = strrchr(id3->path,'/'); - if(NULL == file) + if(!resume_info->id3) return NULL; - /* create the bookmark */ - playlist_get_name(NULL, global_temp_buffer, sizeof(global_temp_buffer)); - if (global_temp_buffer[strlen(global_temp_buffer) - 1] != '/') - file = id3->path; - else file++; - snprintf(global_bookmark, sizeof(global_bookmark), - /* new optional bookmark token descriptors should be inserted - just before the "%s;%s" in this line... */ + size_t bmarksz= snprintf(buf, bufsz, + /* new optional bookmark token descriptors should + be inserted just after ';"' in this line... */ #if defined(HAVE_PITCHCONTROL) - ">%d;%d;%ld;%d;%ld;%d;%d;%ld;%ld;%s;%s", + ">%d;%d;%ld;%d;%ld;%d;%d;%ld;%ld;", #else - ">%d;%d;%ld;%d;%ld;%d;%d;%s;%s", + ">%d;%d;%ld;%d;%ld;%d;%d;", #endif - /* ... their flags should go here ... */ + /* ... their flags should go here ... */ #if defined(HAVE_PITCHCONTROL) - BM_PITCH | BM_SPEED, + BM_PITCH | BM_SPEED, #else - 0, + 0, #endif - resume_index, - id3->offset, - playlist_get_seed(NULL), - id3->elapsed, - global_settings.repeat_mode, - global_settings.playlist_shuffle, - /* ...and their values should go here */ + resume_info->resume_index, + resume_info->id3->offset, + resume_info->resume_seed, + resume_info->id3->elapsed, + resume_info->repeat_mode, + resume_info->shuffle, + /* ...and their values should go here */ #if defined(HAVE_PITCHCONTROL) - (long)sound_get_pitch(), - (long)dsp_get_timestretch(), + (long)resume_info->pitch, + (long)resume_info->speed #endif - /* more mandatory tokens */ - global_temp_buffer, - file); + ); /*sprintf*/ +/* mandatory tokens */ + if (bmarksz >= bufsz) /* include NULL*/ + return NULL; + buf += bmarksz; + bufsz -= bmarksz; + + /* create the bookmark */ + playlist_get_name(NULL, buf, bufsz); + bmarksz = strlen(buf); + + if (bmarksz == 0 || (bmarksz + 1) >= bufsz) /* include the separator & NULL*/ + return NULL; + + *name = buf; /* return the playlist name through the *pointer */ + *namelen = bmarksz; /* return the name length through the pointer */ + + /* Get the currently playing file minus the path */ + /* This is used when displaying the available bookmarks */ + file = strrchr(resume_info->id3->path,'/'); + if(NULL == file) + return NULL; + + if (buf[bmarksz - 1] != '/') + file = resume_info->id3->path; + else file++; + + buf += bmarksz; + bufsz -= (bmarksz + 1); + buf[0] = ';'; + buf[1] = '\0'; + + strlcat(buf, file, bufsz); + logf("%s [%s]", __func__, global_temp_buffer); /* checking to see if the bookmark is valid */ - if (parse_bookmark(global_bookmark, false, false)) - return global_bookmark; + if (parse_bookmark(NULL, 0, global_temp_buffer, NULL, false)) + return global_temp_buffer; else return NULL; } -#if __GNUC__ >= 7 +#if 0/* __GNUC__ >= 7*/ #pragma GCC diagnostic pop /* -Wformat-truncation */ #endif /* ----------------------------------------------------------------------- */ -/* This function will determine if an autoload is necessary. This is an */ -/* interface function. */ -/* Returns true on bookmark load or bookmark selection. */ -/* ------------------------------------------------------------------------*/ -bool bookmark_autoload(const char* file) +/* This function gets some basic resume information for the current song */ +/* from rockbox, */ +/* ----------------------------------------------------------------------- */ +static void get_track_resume_info(struct resume_info *resume_info) { - char* bookmark; - - if(global_settings.autoloadbookmark == BOOKMARK_NO) - return false; - - /*Checking to see if a bookmark file exists.*/ - if(!generate_bookmark_file_name(file)) - { - return false; - } - - if(!file_exists(global_bookmark_file_name)) - return false; - - if(global_settings.autoloadbookmark == BOOKMARK_YES) - { - return bookmark_load(global_bookmark_file_name, true); - } - else - { - int ret = select_bookmark(global_bookmark_file_name, true, &bookmark); - - if (bookmark != NULL) - { - if (!play_bookmark(bookmark)) - { - /* Selected bookmark not found. */ - splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME)); - } - - /* Act as if autoload was done even if it failed, since the - * user did make an active selection. - */ - return true; - } - - return ret != BOOKMARK_SUCCESS; - } + playlist_get_resume_info(&(resume_info->resume_index)); + resume_info->resume_seed = playlist_get_seed(NULL); + resume_info->id3 = audio_current_track(); + resume_info->repeat_mode = global_settings.repeat_mode; + resume_info->shuffle = global_settings.playlist_shuffle; +#if defined(HAVE_PITCHCONTROL) + resume_info->pitch = sound_get_pitch(); + resume_info->speed = dsp_get_timestretch(); +#endif } /* ----------------------------------------------------------------------- */ -/* This function loads the bookmark information into the resume memory. */ -/* This is an interface function. */ -/* Returns true on successful bookmark load. */ +/* This function takes the current current resume information and writes */ +/* that to the beginning of the bookmark file. */ +/* This file will contain N number of bookmarks in the following format: */ +/* resume_index*resume_offset*resume_seed*resume_first_index* */ +/* resume_file*milliseconds*MP3 Title* */ +/* Returns true on successful bookmark write. */ +/* Returns false if any part of the bookmarking process fails. It is */ +/* possible that a bookmark is successfully added to the most recent */ +/* bookmark list but fails to be added to the bookmark file or vice versa. */ /* ------------------------------------------------------------------------*/ -bool bookmark_load(const char* file, bool autoload) +static bool write_bookmark(bool create_bookmark_file) { - int fd; - char* bookmark = NULL; + logf("%s", __func__); + char bm_filename[MAX_PATH]; + bool ret=true; - if(autoload) + char *name = NULL; + size_t namelen = 0; + char* bm; + struct resume_info resume_info; + + if (bookmark_is_bookmarkable_state()) { - fd = open(file, O_RDONLY); - if(fd >= 0) + get_track_resume_info(&resume_info); + /* writing the most recent bookmark */ + if (global_settings.usemrb) { - if(read_line(fd, global_read_buffer, sizeof(global_read_buffer)) > 0) - bookmark=global_read_buffer; - close(fd); + /* since we use the same buffer bookmark needs created each time */ + bm = create_bookmark(&name, &namelen, &resume_info); + ret = add_bookmark(RECENT_BOOKMARK_FILE, bm, true); } - } - else - { - /* This is not an auto-load, so list the bookmarks */ - select_bookmark(file, false, &bookmark); - } - if (bookmark != NULL) - { - if (!play_bookmark(bookmark)) + /* writing the directory bookmark */ + if (create_bookmark_file) { - /* Selected bookmark not found. */ - if (!autoload) + bm = create_bookmark(&name, &namelen, &resume_info); + if (generate_bookmark_file_name(bm_filename, + sizeof(bm_filename), name, namelen)) { - splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME)); + ret &= add_bookmark(bm_filename, bm, false); + } + else + { + ret = false; /* generating bookmark file failed */ } - - return false; } } + else + ret = false; - return true; -} + splash(HZ, ret ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS) + : ID2P(LANG_BOOKMARK_CREATE_FAILURE)); + return ret; +} static int get_bookmark_count(const char* bookmark_file_name) { @@ -537,7 +596,7 @@ static int get_bookmark_count(const char* bookmark_file_name) if(file < 0) return -1; - while(read_line(file, global_read_buffer, sizeof(global_read_buffer)) > 0) + while(read_line(file, global_temp_buffer, sizeof(global_temp_buffer)) > 0) { read_count++; } @@ -568,13 +627,13 @@ static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line) bookmarks->count = 0; bookmarks->reload = false; - while(read_line(file, global_read_buffer, sizeof(global_read_buffer)) > 0) + while(read_line(file, global_temp_buffer, sizeof(global_temp_buffer)) > 0) { read_count++; if (read_count >= first_line) { - dest -= strlen(global_read_buffer) + 1; + dest -= strlen(global_temp_buffer) + 1; if (dest < ((char*) bookmarks) + sizeof(*bookmarks) + (sizeof(char*) * (bookmarks->count + 1))) @@ -582,7 +641,7 @@ static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line) break; } - strcpy(dest, global_read_buffer); + strcpy(dest, global_temp_buffer); bookmarks->items[bookmarks->count] = dest; bookmarks->count++; } @@ -597,6 +656,8 @@ static const char* get_bookmark_info(int list_index, char *buffer, size_t buffer_len) { + char fnamebuf[MAX_PATH]; + struct resume_info resume_info; struct bookmark_list* bookmarks = (struct bookmark_list*) data; int index = list_index / 2; @@ -652,7 +713,8 @@ static const char* get_bookmark_info(int list_index, } } - if (!parse_bookmark(bookmarks->items[index - bookmarks->start], true, true)) + if (!parse_bookmark(fnamebuf, sizeof(fnamebuf), + bookmarks->items[index - bookmarks->start], &resume_info, true)) { return list_index % 2 == 0 ? (char*) str(LANG_BOOKMARK_INVALID) : " "; } @@ -686,25 +748,73 @@ static const char* get_bookmark_info(int list_index, } else { - name = global_filename; + name = fnamebuf; format = "%s"; } - strrsplt(global_filename, '.'); - snprintf(buffer, buffer_len, format, name, global_filename); + strrsplt(fnamebuf, '.'); + snprintf(buffer, buffer_len, format, name, fnamebuf); return buffer; } else { char time_buf[32]; - format_time(time_buf, sizeof(time_buf), bm.resume_time); - snprintf(buffer, buffer_len, "%s, %d%s", time_buf, bm.resume_index + 1, - bm.shuffle ? (char*) str(LANG_BOOKMARK_SHUFFLE) : ""); + format_time(time_buf, sizeof(time_buf), resume_info.resume_elapsed); + snprintf(buffer, buffer_len, "%s, %d%s", time_buf, + resume_info.resume_index + 1, + resume_info.shuffle ? (char*) str(LANG_BOOKMARK_SHUFFLE) : ""); return buffer; } } +/* ----------------------------------------------------------------------- */ +/* This function parses a bookmark, says the voice UI part of it. */ +/* ------------------------------------------------------------------------*/ +static void say_bookmark(const char* bookmark, + int bookmark_id, + bool show_playlist_name) +{ + char fnamebuf[MAX_PATH]; + struct resume_info resume_info; + if (!parse_bookmark(fnamebuf, sizeof(fnamebuf), bookmark, &resume_info, false)) + { + talk_id(LANG_BOOKMARK_INVALID, false); + return; + } + + talk_number(bookmark_id + 1, false); + + bool is_dir = (global_temp_buffer[0] + && global_temp_buffer[strlen(global_temp_buffer)-1] == '/'); + + /* HWCODEC cannot enqueue voice file entries and .talk thumbnails + together, because there is no guarantee that the same mp3 + parameters are used. */ + if(show_playlist_name) + { /* It's useful to know which playlist this is */ + if(is_dir) + talk_dir_or_spell(global_temp_buffer, + TALK_IDARRAY(VOICE_DIR), true); + else talk_file_or_spell(NULL, global_temp_buffer, + TALK_IDARRAY(LANG_PLAYLIST), true); + } + + if(resume_info.shuffle) + talk_id(LANG_SHUFFLE, true); + + talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true); + talk_number(resume_info.resume_index + 1, true); + talk_id(LANG_TIME, true); + talk_value(resume_info.resume_elapsed / 1000, UNIT_TIME, true); + + /* Track filename */ + if(!is_dir) + global_temp_buffer[0] = 0; + talk_file_or_spell(global_temp_buffer, fnamebuf, + TALK_IDARRAY(VOICE_FILE), true); +} + static int bookmark_list_voice_cb(int list_index, void* data) { struct bookmark_list* bookmarks = (struct bookmark_list*) data; @@ -722,6 +832,57 @@ static int bookmark_list_voice_cb(int list_index, void* data) } /* ----------------------------------------------------------------------- */ +/* This function takes a location in a bookmark file and deletes that */ +/* bookmark. */ +/* Returns true on successful bookmark deletion. */ +/* ------------------------------------------------------------------------*/ +static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id) +{ + int temp_bookmark_file = 0; + int bookmark_file = 0; + int bookmark_count = 0; + + /* Opening up a temp bookmark file */ + temp_bookmark_file = open_temp_bookmark(global_temp_buffer, + sizeof(global_temp_buffer), + O_WRONLY | O_CREAT | O_TRUNC, + bookmark_file_name); + + if (temp_bookmark_file < 0) + return false; /* can't open the temp file */ + + /* Reading in the previous bookmarks and writing them to the temp file */ + bookmark_file = open(bookmark_file_name, O_RDONLY); + if (bookmark_file >= 0) + { + while (read_line(bookmark_file, global_temp_buffer, + sizeof(global_temp_buffer)) > 0) + { + if (bookmark_id != bookmark_count) + { + write(temp_bookmark_file, global_temp_buffer, + strlen(global_temp_buffer)); + write(temp_bookmark_file, "\n", 1); + } + bookmark_count++; + } + close(bookmark_file); + } + close(temp_bookmark_file); + + /* only retrieve the path*/ + open_temp_bookmark(global_temp_buffer, + sizeof(global_temp_buffer), + O_PATH, + bookmark_file_name); + + remove(bookmark_file_name); + rename(global_temp_buffer, bookmark_file_name); + + return true; +} + +/* ----------------------------------------------------------------------- */ /* This displays the bookmarks in a file and allows the user to */ /* select one to play. */ /* *selected_bookmark contains a non NULL value on successful bookmark */ @@ -730,7 +891,9 @@ static int bookmark_list_voice_cb(int list_index, void* data) /* if no selection was made and BOOKMARK_USB_CONNECTED if the selection */ /* menu is forced to exit due to a USB connection. */ /* ------------------------------------------------------------------------*/ -static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume, char** selected_bookmark) +static int select_bookmark(const char* bookmark_file_name, + bool show_dont_resume, + char** selected_bookmark) { struct bookmark_list* bookmarks; struct gui_synclist list; @@ -747,12 +910,13 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume bookmarks->filename = bookmark_file_name; bookmarks->start = 0; bookmarks->show_playlist_name - = strcmp(bookmark_file_name, RECENT_BOOKMARK_FILE) == 0; - gui_synclist_init(&list, &get_bookmark_info, (void*) bookmarks, false, 2, NULL); + = (strcmp(bookmark_file_name, RECENT_BOOKMARK_FILE) == 0); + + gui_synclist_init(&list, &get_bookmark_info, + (void*) bookmarks, false, 2, NULL); + if(global_settings.talk_menu) gui_synclist_set_voice_callback(&list, bookmark_list_voice_cb); - gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), - Icon_Bookmark); while (!exit) { @@ -787,14 +951,15 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume } buffer_bookmarks(bookmarks, bookmarks->start); + gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), + Icon_Bookmark); gui_synclist_draw(&list); cond_talk_ids_fq(VOICE_EXT_BMARK); gui_synclist_speak_item(&list); refresh = false; } - list_do_action(CONTEXT_BOOKMARKSCREEN, HZ / 2, - &list, &action, LIST_WRAP_UNLESS_HELD); + list_do_action(CONTEXT_BOOKMARKSCREEN, HZ / 2, &list, &action); item = gui_synclist_get_sel_pos(&list) / 2; if (bookmarks->show_dont_resume) @@ -843,17 +1008,7 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume case ACTION_BMS_DELETE: if (item >= 0) { - const char *lines[]={ - ID2P(LANG_REALLY_DELETE) - }; - const char *yes_lines[]={ - ID2P(LANG_DELETING) - }; - - const struct text_message message={lines, 1}; - const struct text_message yes_message={yes_lines, 1}; - - if(gui_syncyesno_run(&message, &yes_message, NULL)==YESNO_YES) + if (confirm_delete_yesno("") == YESNO_YES) { delete_bookmark(bookmark_file_name, item); bookmarks->reload = true; @@ -879,264 +1034,247 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume } /* ----------------------------------------------------------------------- */ -/* This function takes a location in a bookmark file and deletes that */ -/* bookmark. */ -/* Returns true on successful bookmark deletion. */ +/* This function parses a bookmark and then plays it. */ +/* Returns true on successful bookmark play. */ /* ------------------------------------------------------------------------*/ -static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id) +static bool play_bookmark(const char* bookmark) { - int temp_bookmark_file = 0; - int bookmark_file = 0; - int bookmark_count = 0; - - /* Opening up a temp bookmark file */ - snprintf(global_temp_buffer, sizeof(global_temp_buffer), - "%s.tmp", bookmark_file_name); - temp_bookmark_file = open(global_temp_buffer, - O_WRONLY | O_CREAT | O_TRUNC, 0666); - - if (temp_bookmark_file < 0) - return false; /* can't open the temp file */ + char fnamebuf[MAX_PATH]; + struct resume_info resume_info; +#if defined(HAVE_PITCHCONTROL) + /* preset pitch and speed to 100% in case bookmark doesn't have info */ + resume_info.pitch = sound_get_pitch(); + resume_info.speed = dsp_get_timestretch(); +#endif - /* Reading in the previous bookmarks and writing them to the temp file */ - bookmark_file = open(bookmark_file_name, O_RDONLY); - if (bookmark_file >= 0) + if (parse_bookmark(fnamebuf, sizeof(fnamebuf), bookmark, &resume_info, true)) { - while (read_line(bookmark_file, global_read_buffer, - sizeof(global_read_buffer)) > 0) + global_settings.repeat_mode = resume_info.repeat_mode; + global_settings.playlist_shuffle = resume_info.shuffle; +#if defined(HAVE_PITCHCONTROL) + sound_set_pitch(resume_info.pitch); + dsp_set_timestretch(resume_info.speed); +#endif + if (!warn_on_pl_erase()) + return false; + bool success = bookmark_play(global_temp_buffer, resume_info.resume_index, + resume_info.resume_elapsed, resume_info.resume_offset, + resume_info.resume_seed, fnamebuf); + if (success) /* verify we loaded the correct track */ { - if (bookmark_id != bookmark_count) + const struct mp3entry *id3 = audio_current_track(); + if (id3) { - write(temp_bookmark_file, global_read_buffer, - strlen(global_read_buffer)); - write(temp_bookmark_file, "\n", 1); + const char *path; + const char *track; + path_basename(id3->path, &path); + path_basename(fnamebuf, &track); + if (strcmp(path, track) == 0) + { + return true; + } } - bookmark_count++; + audio_stop(); } - close(bookmark_file); } - close(temp_bookmark_file); - - remove(bookmark_file_name); - rename(global_temp_buffer, bookmark_file_name); - return true; + return false; } +/*-------------------------------------------------------------------------*/ +/* PUBLIC INTERFACE -------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ + + /* ----------------------------------------------------------------------- */ -/* This function parses a bookmark, says the voice UI part of it. */ -/* ------------------------------------------------------------------------*/ -static void say_bookmark(const char* bookmark, - int bookmark_id, bool show_playlist_name) +/* This is an interface function from the context menu. */ +/* Returns true on successful bookmark creation. */ +/* ----------------------------------------------------------------------- */ +bool bookmark_create_menu(void) { - if (!parse_bookmark(bookmark, true, false)) - { - talk_id(LANG_BOOKMARK_INVALID, false); - return; - } - - talk_number(bookmark_id + 1, false); + return write_bookmark(true); +} +/* ----------------------------------------------------------------------- */ +/* This function acts as the load interface from the context menu. */ +/* This function determines the bookmark file name and then loads that file*/ +/* for the user. The user can then select or delete previous bookmarks. */ +/* This function returns BOOKMARK_SUCCESS on the selection of a track to */ +/* resume, BOOKMARK_FAIL if the menu is exited without a selection and */ +/* BOOKMARK_USB_CONNECTED if the menu is forced to exit due to a USB */ +/* connection. */ +/* ----------------------------------------------------------------------- */ +int bookmark_load_menu(void) +{ + char bm_filename[MAX_PATH]; + char* bookmark; + int ret = BOOKMARK_FAIL; - bool is_dir = (global_temp_buffer[0] - && global_temp_buffer[strlen(global_temp_buffer)-1] == '/'); + push_current_activity(ACTIVITY_BOOKMARKSLIST); - /* HWCODEC cannot enqueue voice file entries and .talk thumbnails - together, because there is no guarantee that the same mp3 - parameters are used. */ - if(show_playlist_name) - { /* It's useful to know which playlist this is */ - if(is_dir) - talk_dir_or_spell(global_temp_buffer, - TALK_IDARRAY(VOICE_DIR), true); - else talk_file_or_spell(NULL, global_temp_buffer, - TALK_IDARRAY(LANG_PLAYLIST), true); + char* name = playlist_get_name(NULL, global_temp_buffer, + sizeof(global_temp_buffer)); + if (generate_bookmark_file_name(bm_filename, sizeof(bm_filename), name, -1)) + { + ret = select_bookmark(bm_filename, false, &bookmark); + if (bookmark != NULL) + { + ret = play_bookmark(bookmark) ? BOOKMARK_SUCCESS : BOOKMARK_FAIL; + } } - if(bm.shuffle) - talk_id(LANG_SHUFFLE, true); - - talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true); - talk_number(bm.resume_index + 1, true); - talk_id(LANG_TIME, true); - talk_value(bm.resume_time / 1000, UNIT_TIME, true); - - /* Track filename */ - if(!is_dir) - global_temp_buffer[0] = 0; - talk_file_or_spell(global_temp_buffer, global_filename, - TALK_IDARRAY(VOICE_FILE), true); + pop_current_activity(); + return ret; } /* ----------------------------------------------------------------------- */ -/* This function parses a bookmark and then plays it. */ -/* Returns true on successful bookmark play. */ -/* ------------------------------------------------------------------------*/ -static bool play_bookmark(const char* bookmark) +/* Gives the user a list of the Most Recent Bookmarks. This is an */ +/* interface function */ +/* Returns true on the successful selection of a recent bookmark. */ +/* ----------------------------------------------------------------------- */ +bool bookmark_mrb_load() { -#if defined(HAVE_PITCHCONTROL) - /* preset pitch and speed to 100% in case bookmark doesn't have info */ - bm.pitch = sound_get_pitch(); - bm.speed = dsp_get_timestretch(); -#endif + char* bookmark; + bool ret = false; - if (parse_bookmark(bookmark, true, true)) + push_current_activity(ACTIVITY_BOOKMARKSLIST); + select_bookmark(RECENT_BOOKMARK_FILE, false, &bookmark); + if (bookmark != NULL) { - global_settings.repeat_mode = bm.repeat_mode; - global_settings.playlist_shuffle = bm.shuffle; -#if defined(HAVE_PITCHCONTROL) - sound_set_pitch(bm.pitch); - dsp_set_timestretch(bm.speed); -#endif - if (!warn_on_pl_erase()) - return false; - return bookmark_play(global_temp_buffer, bm.resume_index, - bm.resume_time, bm.resume_offset, bm.resume_seed, global_filename); + ret = play_bookmark(bookmark); } - return false; + pop_current_activity(); + return ret; } -static const char* skip_token(const char* s) +/* ----------------------------------------------------------------------- */ +/* This function handles an autobookmark creation. This is an interface */ +/* function. */ +/* Returns true on successful bookmark creation. */ +/* ----------------------------------------------------------------------- */ +bool bookmark_autobookmark(bool prompt_ok) { - while (*s && *s != ';') - { - s++; - } + logf("%s", __func__); + bool update; + + if (!bookmark_is_bookmarkable_state()) + return false; + + audio_pause(); /* first pause playback */ + update = (global_settings.autoupdatebookmark && bookmark_exists()); - if (*s) + if (update) + return write_bookmark(true); + + switch (global_settings.autocreatebookmark) { - s++; - } + case BOOKMARK_YES: + return write_bookmark(true); - return s; -} + case BOOKMARK_NO: + return false; -static const char* int_token(const char* s, int* dest) -{ - *dest = atoi(s); - return skip_token(s); -} + case BOOKMARK_RECENT_ONLY_YES: + return write_bookmark(false); + } + const char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY)}; + const struct text_message message={lines, 1}; -static const char* long_token(const char* s, long* dest) -{ - *dest = atoi(s); /* Should be atol, but we don't have it. */ - return skip_token(s); + if(prompt_ok && gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES) + { + if (global_settings.autocreatebookmark == BOOKMARK_RECENT_ONLY_ASK) + return write_bookmark(false); + else + return write_bookmark(true); + } + return false; } /* ----------------------------------------------------------------------- */ -/* This function takes a bookmark and parses it. This function also */ -/* validates the bookmark. The parse_filenames flag indicates whether */ -/* the filename tokens are to be extracted. */ -/* Returns true on successful bookmark parse. */ -/* ----------------------------------------------------------------------- */ -static bool parse_bookmark(const char *bookmark, const bool parse_filenames, const bool strip_dir) +/* This function will determine if an autoload is necessary. This is an */ +/* interface function. */ +/* Returns */ +/* BOOKMARK_DO_RESUME on bookmark load or bookmark selection. */ +/* BOOKMARK_DONT_RESUME if we're not going to resume */ +/* BOOKMARK_CANCEL if user canceled */ +/* ------------------------------------------------------------------------*/ +int bookmark_autoload(const char* file) { - const char* s = bookmark; - const char* end; + logf("%s", __func__); + char bm_filename[MAX_PATH]; + char* bookmark; -#define GET_INT_TOKEN(var) s = int_token(s, &var) -#define GET_LONG_TOKEN(var) s = long_token(s, &var) -#define GET_BOOL_TOKEN(var) var = (atoi(s)!=0); s = skip_token(s) + if(global_settings.autoloadbookmark == BOOKMARK_NO) + return BOOKMARK_DONT_RESUME; - /* if new format bookmark, extract the optional content flags, - otherwise treat as an original format bookmark */ - int opt_flags = 0; - bool new_format = (strchr(s, '>') == s); - if (new_format) + /*Checking to see if a bookmark file exists.*/ + if(!generate_bookmark_file_name(bm_filename, sizeof(bm_filename), file, -1)) { - s++; - GET_INT_TOKEN(opt_flags); + return BOOKMARK_DONT_RESUME; } - /* extract all original bookmark tokens */ - GET_INT_TOKEN(bm.resume_index); - GET_LONG_TOKEN(bm.resume_offset); - GET_INT_TOKEN(bm.resume_seed); - if (!new_format) /* skip deprecated token */ - s = skip_token(s); - GET_LONG_TOKEN(bm.resume_time); - GET_INT_TOKEN(bm.repeat_mode); - GET_BOOL_TOKEN(bm.shuffle); - - /* extract all optional bookmark tokens */ - if (opt_flags & BM_PITCH) - GET_INT_TOKEN(bm.pitch); - if (opt_flags & BM_SPEED) - GET_INT_TOKEN(bm.speed); + if(!file_exists(bm_filename)) + return BOOKMARK_DONT_RESUME; - if (*s == 0) + if(global_settings.autoloadbookmark == BOOKMARK_YES) { - return false; + return (bookmark_load(bm_filename, true) + ? BOOKMARK_DO_RESUME : BOOKMARK_DONT_RESUME); } - - end = strchr(s, ';'); - - /* extract file names */ - if (parse_filenames) + else { - size_t len = (end == NULL) ? strlen(s) : (size_t) (end - s); - len = MIN(TEMP_BUF_SIZE - 1, len); - strlcpy(global_temp_buffer, s, len + 1); + int ret = select_bookmark(bm_filename, true, &bookmark); - if (end != NULL) + if (bookmark != NULL) { - end++; - if (strip_dir) - { - s = strrchr(end, '/'); - if (s) - { - end = s; - end++; - } - } - strlcpy(global_filename, end, MAX_PATH); + if (!play_bookmark(bookmark)) + return BOOKMARK_CANCEL; + return BOOKMARK_DO_RESUME; } - } - return true; + return (ret != BOOKMARK_SUCCESS) ? BOOKMARK_CANCEL : BOOKMARK_DONT_RESUME; + } } /* ----------------------------------------------------------------------- */ -/* This function is used by multiple functions and is used to generate a */ -/* bookmark named based off of the input. */ -/* Changing this function could result in how the bookmarks are stored. */ -/* it would be here that the centralized/decentralized bookmark code */ -/* could be placed. */ -/* Returns true if the file name is generated, false if it was too long */ -/* ----------------------------------------------------------------------- */ -static bool generate_bookmark_file_name(const char *in) +/* This function loads the bookmark information into the resume memory. */ +/* This is an interface function. */ +/* Returns true on successful bookmark load. */ +/* ------------------------------------------------------------------------*/ +bool bookmark_load(const char* file, bool autoload) { - /* if this is a root dir MP3, rename the bookmark file root_dir.bmark */ - /* otherwise, name it based on the in variable */ - if (!strcmp("/", in)) - strcpy(global_bookmark_file_name, "/root_dir.bmark"); - else - { -#ifdef HAVE_MULTIVOLUME - /* The "root" of an extra volume need special handling too. */ - const char *filename; - path_strip_volume(in, &filename, true); - bool volume_root = *filename == '\0'; -#endif - size_t len = strlcpy(global_bookmark_file_name, in, MAX_PATH); - if(len >= MAX_PATH) - return false; + logf("%s", __func__); + int fd; + char* bookmark = NULL; - if(global_bookmark_file_name[len-1] == '/') { - global_bookmark_file_name[len-1] = '\0'; - len--; + if(autoload) + { + fd = open(file, O_RDONLY); + if(fd >= 0) + { + if(read_line(fd, global_temp_buffer, sizeof(global_temp_buffer)) > 0) + bookmark=global_temp_buffer; + close(fd); } + } + else + { + /* This is not an auto-load, so list the bookmarks */ + select_bookmark(file, false, &bookmark); + } -#ifdef HAVE_MULTIVOLUME - if (volume_root) - len = strlcat(global_bookmark_file_name, "/volume_dir.bmark", MAX_PATH); - else -#endif - len = strlcat(global_bookmark_file_name, ".bmark", MAX_PATH); + if (bookmark != NULL) + { + if (!play_bookmark(bookmark)) + { + /* Selected bookmark not found. */ + if (!autoload) + { + splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME)); + } - if(len >= MAX_PATH) return false; + } } return true; @@ -1148,13 +1286,14 @@ static bool generate_bookmark_file_name(const char *in) /* ----------------------------------------------------------------------- */ bool bookmark_exists(void) { + char bm_filename[MAX_PATH]; bool exist=false; char* name = playlist_get_name(NULL, global_temp_buffer, sizeof(global_temp_buffer)); - if (generate_bookmark_file_name(name)) + if (generate_bookmark_file_name(bm_filename, sizeof(bm_filename), name, -1)) { - exist = file_exists(global_bookmark_file_name); + exist = file_exists(bm_filename); } return exist; } @@ -1180,4 +1319,3 @@ bool bookmark_is_bookmarkable_state(void) return true; } - diff --git a/apps/bookmark.h b/apps/bookmark.h index ff7b87c1bf..192e577ce6 100644 --- a/apps/bookmark.h +++ b/apps/bookmark.h @@ -29,11 +29,17 @@ enum { BOOKMARK_USB_CONNECTED = 1 }; +enum { + BOOKMARK_CANCEL, + BOOKMARK_DONT_RESUME, + BOOKMARK_DO_RESUME +}; + int bookmark_load_menu(void); bool bookmark_autobookmark(bool prompt_ok); bool bookmark_create_menu(void); bool bookmark_mrb_load(void); -bool bookmark_autoload(const char* file); +int bookmark_autoload(const char* file); bool bookmark_load(const char* file, bool autoload); bool bookmark_exists(void); bool bookmark_is_bookmarkable_state(void); diff --git a/apps/buffering.c b/apps/buffering.c index 3adbc4a6b9..81b861ccf1 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -20,7 +20,6 @@ ****************************************************************************/ #include "config.h" #include <string.h> -#include "strlcpy.h" #include "system.h" #include "storage.h" #include "thread.h" @@ -71,8 +70,6 @@ /* amount of data to read in one read() call */ #define BUFFERING_DEFAULT_FILECHUNK (1024*32) -#define BUF_HANDLE_MASK 0x7FFFFFFF - enum handle_flags { H_CANWRAP = 0x1, /* Handle data may wrap in buffer */ @@ -295,12 +292,11 @@ static int next_handle_id(void) { static int cur_handle_id = 0; - /* Wrap signed int is safe and 0 doesn't happen */ - int next_hid = (cur_handle_id + 1) & BUF_HANDLE_MASK; - if (next_hid == 0) - next_hid = 1; - - cur_handle_id = next_hid; + int next_hid = cur_handle_id + 1; + if (next_hid == INT_MAX) + cur_handle_id = 0; /* next would overflow; reset the counter */ + else + cur_handle_id = next_hid; return next_hid; } @@ -420,7 +416,8 @@ add_handle(unsigned int flags, size_t data_size, const char *path, h->signaled = 0; /* Data can be waited for */ /* Save the provided path */ - memcpy(h->path, path, pathsize); + if (path) + memcpy(h->path, path, pathsize); /* Return the start of the data area */ *data_out = ringbuf_add(index, handlesize); @@ -851,8 +848,9 @@ static bool fill_buffer(void) Return value is the total size (struct + data). */ static int load_image(int fd, const char *path, struct bufopen_bitmap_data *data, - size_t bufidx) + size_t bufidx, size_t max_size) { + (void)path; int rc; struct bitmap *bmp = ringbuf_ptr(bufidx); struct dim *dim = data->dim; @@ -866,25 +864,20 @@ static int load_image(int fd, const char *path, #if (LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1) bmp->maskdata = NULL; #endif - int free = (int)MIN(buffer_len - bytes_used(), buffer_len - bufidx) - - sizeof(struct bitmap); - + const int format = FORMAT_NATIVE | FORMAT_DITHER | + FORMAT_RESIZE | FORMAT_KEEP_ASPECT; #ifdef HAVE_JPEG if (aa != NULL) { lseek(fd, aa->pos, SEEK_SET); - rc = clip_jpeg_fd(fd, aa->size, bmp, free, FORMAT_NATIVE|FORMAT_DITHER| - FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL); + rc = clip_jpeg_fd(fd, aa->size, bmp, (int)max_size, format, NULL); } else if (strcmp(path + strlen(path) - 4, ".bmp")) - rc = read_jpeg_fd(fd, bmp, free, FORMAT_NATIVE|FORMAT_DITHER| - FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL); + rc = read_jpeg_fd(fd, bmp, (int)max_size, format, NULL); else #endif - rc = read_bmp_fd(fd, bmp, free, FORMAT_NATIVE|FORMAT_DITHER| - FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL); + rc = read_bmp_fd(fd, bmp, (int)max_size, format, NULL); return rc + (rc > 0 ? sizeof(struct bitmap) : 0); - (void)path; } #endif /* HAVE_ALBUMART */ @@ -970,11 +963,18 @@ int bufopen(const char *file, off_t offset, enum data_type type, size_t size = 0; #ifdef HAVE_ALBUMART if (type == TYPE_BITMAP) { - /* If albumart is embedded, the complete file is not buffered, - * but only the jpeg part; filesize() would be wrong */ + /* Bitmaps are resized to the requested dimensions when loaded, + * so the file size should not be used as it may be too large + * or too small */ struct bufopen_bitmap_data *aa = user_data; - if (aa->embedded_albumart) - size = aa->embedded_albumart->size; + size = BM_SIZE(aa->dim->width, aa->dim->height, FORMAT_NATIVE, false); + size += sizeof(struct bitmap); + +#ifdef HAVE_JPEG + /* JPEG loading requires extra memory + * TODO: don't add unncessary overhead for .bmp images! */ + size += JPEG_DECODE_OVERHEAD; +#endif } #endif @@ -983,7 +983,10 @@ int bufopen(const char *file, off_t offset, enum data_type type, unsigned int hflags = 0; if (type == TYPE_PACKET_AUDIO || type == TYPE_CODEC) - hflags = H_CANWRAP; + hflags |= H_CANWRAP; + /* Bitmaps need their space allocated up front */ + if (type == TYPE_BITMAP) + hflags |= H_ALLOCALL; size_t adjusted_offset = offset; if (adjusted_offset > size) @@ -1034,7 +1037,7 @@ int bufopen(const char *file, off_t offset, enum data_type type, #ifdef HAVE_ALBUMART if (type == TYPE_BITMAP) { /* Bitmap file: we load the data instead of the file */ - int rc = load_image(fd, file, user_data, data); + int rc = load_image(fd, file, user_data, data, padded_size); if (rc <= 0) { handle_id = ERR_FILE_ERROR; } else { @@ -1433,62 +1436,6 @@ ssize_t bufgetdata(int handle_id, size_t size, void **data) return size; } -ssize_t bufgettail(int handle_id, size_t size, void **data) -{ - if (thread_self() != buffering_thread_id) - return ERR_WRONG_THREAD; /* only from buffering thread */ - - /* We don't support tail requests of > guardbuf_size, for simplicity */ - if (size > GUARD_BUFSIZE) - return ERR_INVALID_VALUE; - - const struct memory_handle *h = find_handle(handle_id); - if (!h) - return ERR_HANDLE_NOT_FOUND; - - if (h->end >= h->filesize) { - size_t tidx = ringbuf_sub_empty(h->widx, size); - - if (tidx + size > buffer_len) { - size_t copy_n = tidx + size - buffer_len; - memcpy(guard_buffer, ringbuf_ptr(0), copy_n); - } - - *data = ringbuf_ptr(tidx); - } - else { - size = ERR_HANDLE_NOT_DONE; - } - - return size; -} - -ssize_t bufcuttail(int handle_id, size_t size) -{ - if (thread_self() != buffering_thread_id) - return ERR_WRONG_THREAD; /* only from buffering thread */ - - struct memory_handle *h = find_handle(handle_id); - if (!h) - return ERR_HANDLE_NOT_FOUND; - - if (h->end >= h->filesize) { - /* Cannot trim to before read position */ - size_t available = h->end - MAX(h->start, h->pos); - if (available < size) - size = available; - - h->widx = ringbuf_sub_empty(h->widx, size); - h->filesize -= size; - h->end -= size; - } else { - size = ERR_HANDLE_NOT_DONE; - } - - return size; -} - - /* SECONDARY EXPORTED FUNCTIONS ============================ diff --git a/apps/buffering.h b/apps/buffering.h index 1a75d865ae..bc47d6b1a1 100644 --- a/apps/buffering.h +++ b/apps/buffering.h @@ -46,8 +46,7 @@ enum data_type { #define ERR_FILE_ERROR -4 #define ERR_HANDLE_NOT_DONE -5 #define ERR_UNSUPPORTED_TYPE -6 -#define ERR_WRONG_THREAD -7 -#define ERR_BITMAP_TOO_LARGE -8 +#define ERR_BITMAP_TOO_LARGE -7 /* Initialise the buffering subsystem */ void buffering_init(void) INIT_ATTR; @@ -68,8 +67,6 @@ bool buffering_reset(char *buf, size_t buflen); * bufftell : Return the handle's file read position * bufread : Copy data from a handle to a buffer * bufgetdata: Obtain a pointer for linear access to a "size" amount of data - * bufgettail: Out-of-band get the last size bytes of a handle. - * bufcuttail: Out-of-band remove the trailing 'size' bytes of a handle. * * NOTE: bufread and bufgetdata will block the caller until the requested * amount of data is ready (unless EOF is reached). @@ -85,8 +82,6 @@ int bufadvance(int handle_id, off_t offset); off_t bufftell(int handle_id); ssize_t bufread(int handle_id, size_t size, void *dest); ssize_t bufgetdata(int handle_id, size_t size, void **data); -ssize_t bufgettail(int handle_id, size_t size, void **data); -ssize_t bufcuttail(int handle_id, size_t size); /*************************************************************************** * SECONDARY FUNCTIONS diff --git a/apps/codecs.c b/apps/codecs.c index 4d2dd34ce0..9f34d26e14 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -203,8 +203,9 @@ static int codec_load_ram(struct codec_api *api) return CODEC_ERROR; } - if (hdr->api_version > CODEC_API_VERSION - || hdr->api_version < CODEC_MIN_API_VERSION) { + if (hdr->api_version != CODEC_API_VERSION || + c_hdr->api_size > sizeof(struct codec_api)) + { logf("codec api version error"); lc_close(curr_handle); curr_handle = NULL; diff --git a/apps/core_keymap.c b/apps/core_keymap.c new file mode 100644 index 0000000000..89e7913c33 --- /dev/null +++ b/apps/core_keymap.c @@ -0,0 +1,95 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2020 by William Wilgus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "action.h" +#include "core_alloc.h" +#include "core_keymap.h" + +/*#define LOGF_ENABLE*/ +#include "logf.h" + +#if !defined(__PCTOOL__) || defined(CHECKWPS) +int core_set_keyremap(struct button_mapping* core_keymap, int count) +{ + return action_set_keymap(core_keymap, count); +} + +static int open_key_remap(const char *filename, int *countp) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + + size_t fsize = filesize(fd); + int count = fsize / sizeof(struct button_mapping); + if (count == 0 || (size_t)(count * sizeof(struct button_mapping)) != fsize) + { + logf("core_keyremap: bad filesize %d / %lu", count, (unsigned long)fsize); + goto error; + } + + struct button_mapping header; + if(read(fd, &header, sizeof(header)) != (ssize_t)sizeof(header)) + { + logf("core_keyremap: read error"); + goto error; + } + + if (header.action_code != KEYREMAP_VERSION || + header.button_code != KEYREMAP_HEADERID || + header.pre_button_code != count) + { + logf("core_keyremap: bad header %d", count); + goto error; + } + + *countp = count - 1; + return fd; + + error: + close(fd); + return -1; +} + +int core_load_key_remap(const char *filename) +{ + int count = 0; /* gcc falsely believes this may be used uninitialized */ + int fd = open_key_remap(filename, &count); + if (fd < 0) + return -1; + + size_t bufsize = count * sizeof(struct button_mapping); + int handle = core_alloc(bufsize); + if (handle > 0) + { + void *data = core_get_data_pinned(handle); + + if (read(fd, data, bufsize) == (ssize_t)bufsize) + count = action_set_keymap_handle(handle, count); + + core_put_data_pinned(data); + } + + close(fd); + return count; +} + +#endif /* !defined(__PCTOOL__) */ diff --git a/apps/core_keymap.h b/apps/core_keymap.h new file mode 100644 index 0000000000..2077daa685 --- /dev/null +++ b/apps/core_keymap.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2020 by William Wilgus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef CORE_KEYMAP_H +#define CORE_KEYMAP_H + +#include <stdbool.h> +#include <inttypes.h> +#include "config.h" +#include "action.h" +#define KEYREMAP_VERSION 1 +#define KEYREMAP_HEADERID (LAST_ACTION_PLACEHOLDER | (TARGET_ID << 8)) + +/* If exists remap file will be loaded at startup */ +#define CORE_KEYREMAP_FILE ROCKBOX_DIR "/keyremap.kmf" + +/* Allocates core buffer, copies keymap to allow buttons for actions to be remapped*/ +int core_set_keyremap(struct button_mapping* core_keymap, int count); + +/* load a remap file to allow buttons for actions to be remapped */ +int core_load_key_remap(const char *filename); + +/* + * entries consist of 3 int [action, button, prebtn] + * the header (VERSION, LAST_DEFINED_ACTION, count) is stripped by open_key_remap + * + * context look up table is at the beginning + * action_code contains (context | CONTEXT_REMAPPED) + * button_code contains index of first remapped action for the matched context + * prebtn_code contains count of actions in this remapped context + * [-1] REMAP_VERSION, REMAP_HEADERID, entry count(9) / DISCARDED AFTER LOAD + * [0] CORE_CONTEXT_REMAP(ctx1), offset1=(3), count=(1) + * [1] CORE_CONTEXT_REMAP(ctx2, offset2=(5), count=(2) + * [2] sentinel, 0, 0 + * [3] act0, btn, 0 + * [4] sentinel 0, 0 + * [5] act1, btn, 0 + * [6] act2, btn1 + * [7] sentinel, 0, 0 + * + * Note: + * last entry of each group is always the sentinel [CONTEXT_STOPSEARCHING, BUTTON_NONE, BUTTON_NONE] + * contexts must match exactly -- re-mapped contexts run before the built in w/ fall through contexts + * ie. you can't remap std_context and expect it to match std_context actions from the WPS context. + */ + +#endif /* CORE_KEYMAP_H */ + diff --git a/apps/cuesheet.c b/apps/cuesheet.c index be89ef96cf..263fed154d 100644 --- a/apps/cuesheet.c +++ b/apps/cuesheet.c @@ -58,28 +58,28 @@ static bool search_for_cuesheet(const char *path, struct cuesheet_file *cue_file slash_cuepath = &cuepath[slash - path]; dot = strrchr(slash_cuepath, '.'); if (dot) - strlcpy(dot, ".cue", MAX_PATH - (dot-cuepath)); + strmemccpy(dot, ".cue", MAX_PATH - (dot-cuepath)); if (!dot || !file_exists(cuepath)) { strcpy(cuepath, CUE_DIR); if (strlcat(cuepath, slash, MAX_PATH) >= MAX_PATH) goto skip; /* overflow */ - char *dot = strrchr(cuepath, '.'); + dot = strrchr(cuepath, '.'); strcpy(dot, ".cue"); if (!file_exists(cuepath)) { skip: if ((len+4) >= MAX_PATH) return false; - strlcpy(cuepath, path, MAX_PATH); + strmemccpy(cuepath, path, MAX_PATH); strlcat(cuepath, ".cue", MAX_PATH); if (!file_exists(cuepath)) return false; } } - strlcpy(cue_file->path, cuepath, MAX_PATH); + strmemccpy(cue_file->path, cuepath, MAX_PATH); return true; } @@ -91,7 +91,7 @@ bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cu cue_file->pos = track_id3->embedded_cuesheet.pos; cue_file->size = track_id3->embedded_cuesheet.size; cue_file->encoding = track_id3->embedded_cuesheet.encoding; - strlcpy(cue_file->path, track_id3->path, MAX_PATH); + strmemccpy(cue_file->path, track_id3->path, MAX_PATH); return true; } @@ -123,6 +123,7 @@ static unsigned long parse_cue_index(const char *line) /* assumes strncmp(line, "INDEX 01", 8) & NULL terminated string */ /* INDEX 01 MM:SS:FF\0 (00:00:00\0 - 99:99:99\0)*/ const unsigned field_m[3] = {60 * 1000, 1000, 13}; /* MM:SS:~FF*/ + const unsigned field_max[3] = {30000, 59, 74}; /* MM, SS, FF */ const char f_sep = ':'; int field = -1; unsigned long offset = 0; /* ms from start of track */ @@ -138,7 +139,7 @@ static unsigned long parse_cue_index(const char *line) while (isdigit(*line)) { value = 10 * value + (*line - '0'); - if (value > 99) /* Sanity check bail early */ + if (field >= 0 && value > field_max[field]) /* Sanity check bail early */ return 0; line++; } @@ -165,6 +166,38 @@ static unsigned long parse_cue_index(const char *line) return offset; } +enum eCS_SUPPORTED_TAGS { + eCS_TRACK = 0, eCS_INDEX_01, eCS_TITLE, + eCS_PERFORMER, eCS_SONGWRITER, eCS_FILE, + eCS_COUNT_TAGS_COUNT, eCS_NOTFOUND = -1 +}; + +static enum eCS_SUPPORTED_TAGS cuesheet_tag_get_option(const char *option) +{ + #define CS_OPTN(str) {str, sizeof(str)-1} + static const struct cs_option_t { + const char *str; + const int len; + } cs_options[eCS_COUNT_TAGS_COUNT + 1] = { + [eCS_TRACK] = CS_OPTN("TRACK"), + [eCS_INDEX_01] = CS_OPTN("INDEX 01"), + [eCS_TITLE] =CS_OPTN("TITLE"), + [eCS_PERFORMER] =CS_OPTN("PERFORMER"), + [eCS_SONGWRITER] =CS_OPTN("SONGWRITER"), + [eCS_FILE] =CS_OPTN("FILE"), + [eCS_COUNT_TAGS_COUNT] = {NULL, 0} /*SENTINEL*/ + }; + + const struct cs_option_t *op; + for (int i=0; ((op=&cs_options[i]))->str != NULL; i++) + { + if (strncmp(option, op->str, op->len) == 0) + return i; + } + return eCS_NOTFOUND; +#undef CS_OPTN +} + /* parse cuesheet "cue_file" and store the information in "cue" */ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) { @@ -190,22 +223,25 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) /* Look for a Unicode BOM */ unsigned char bom_read = 0; - read(fd, line, BOM_UTF_8_SIZE); - if(!memcmp(line, BOM_UTF_8, BOM_UTF_8_SIZE)) - { - char_enc = CHAR_ENC_UTF_8; - bom_read = BOM_UTF_8_SIZE; - } - else if(!memcmp(line, BOM_UTF_16_LE, BOM_UTF_16_SIZE)) - { - char_enc = CHAR_ENC_UTF_16_LE; - bom_read = BOM_UTF_16_SIZE; - } - else if(!memcmp(line, BOM_UTF_16_BE, BOM_UTF_16_SIZE)) + if (read(fd, line, BOM_UTF_8_SIZE) > 0) { - char_enc = CHAR_ENC_UTF_16_BE; - bom_read = BOM_UTF_16_SIZE; + if(!memcmp(line, BOM_UTF_8, BOM_UTF_8_SIZE)) + { + char_enc = CHAR_ENC_UTF_8; + bom_read = BOM_UTF_8_SIZE; + } + else if(!memcmp(line, BOM_UTF_16_LE, BOM_UTF_16_SIZE)) + { + char_enc = CHAR_ENC_UTF_16_LE; + bom_read = BOM_UTF_16_SIZE; + } + else if(!memcmp(line, BOM_UTF_16_BE, BOM_UTF_16_SIZE)) + { + char_enc = CHAR_ENC_UTF_16_BE; + bom_read = BOM_UTF_16_SIZE; + } } + if (bom_read < BOM_UTF_8_SIZE) lseek(fd, cue_file->pos + bom_read, SEEK_SET); if (is_embedded) @@ -245,11 +281,16 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) } s = skip_whitespace(line); - if (!strncmp(s, "TRACK", 5)) +/* RECOGNIZED TAGS *********************** +* eCS_TRACK = 0, eCS_INDEX_01, eCS_TITLE, +* eCS_PERFORMER, eCS_SONGWRITER, eCS_FILE, +*/ + enum eCS_SUPPORTED_TAGS option = cuesheet_tag_get_option(s); + if (option == eCS_TRACK) { cue->track_count++; } - else if (!strncmp(s, "INDEX 01", 8)) + else if (option == eCS_INDEX_01) { #if 0 s = strchr(s,' '); @@ -265,10 +306,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) cue->tracks[cue->track_count-1].offset = parse_cue_index(s); #endif } - else if (!strncmp(s, "TITLE", 5) - || !strncmp(s, "PERFORMER", 9) - || !strncmp(s, "SONGWRITER", 10) - || !strncmp(s, "FILE", 4)) + else if (option != eCS_NOTFOUND) { char *dest = NULL; char *string = get_string(s); @@ -278,24 +316,24 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) size_t count = MAX_NAME*3 + 1; size_t count8859 = MAX_NAME; - switch (*s) + switch (option) { - case 'T': /* TITLE */ + case eCS_TITLE: /* TITLE */ dest = (cue->track_count <= 0) ? cue->title : cue->tracks[cue->track_count-1].title; break; - case 'P': /* PERFORMER */ + case eCS_PERFORMER: /* PERFORMER */ dest = (cue->track_count <= 0) ? cue->performer : cue->tracks[cue->track_count-1].performer; break; - case 'S': /* SONGWRITER */ + case eCS_SONGWRITER: /* SONGWRITER */ dest = (cue->track_count <= 0) ? cue->songwriter : cue->tracks[cue->track_count-1].songwriter; break; - case 'F': /* FILE */ + case eCS_FILE: /* FILE */ if (is_embedded || cue->track_count > 0) break; @@ -303,6 +341,16 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) count = MAX_PATH; count8859 = MAX_PATH/3; break; + case eCS_TRACK: + /*Fall-Through*/ + case eCS_INDEX_01: + /*Fall-Through*/ + case eCS_COUNT_TAGS_COUNT: + /*Fall-Through*/ + case eCS_NOTFOUND: /*Shouldn't happen*/ + logf(HZ * 2, "Bad Tag %d @ %s", (int) option, __func__); + dest = NULL; + break; } if (dest) @@ -315,10 +363,11 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) } else { - strlcpy(dest, string, count); + strmemccpy(dest, string, count); } } } + if (is_embedded) { bytes_left -= line_len; @@ -337,7 +386,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) strcpy(cue->file, cue->path); char *slash = strrchr(cue->file, '/'); if (!slash++) slash = cue->file; - strlcpy(slash, line, MAX_PATH - (slash - cue->file)); + strmemccpy(slash, line, MAX_PATH - (slash - cue->file)); } /* If some songs don't have performer info, we copy the cuesheet performer */ @@ -345,10 +394,10 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) for (i = 0; i < cue->track_count; i++) { if (*(cue->tracks[i].performer) == '\0') - strlcpy(cue->tracks[i].performer, cue->performer, MAX_NAME*3); + strmemccpy(cue->tracks[i].performer, cue->performer, MAX_NAME*3); if (*(cue->tracks[i].songwriter) == '\0') - strlcpy(cue->tracks[i].songwriter, cue->songwriter, MAX_NAME*3); + strmemccpy(cue->tracks[i].songwriter, cue->songwriter, MAX_NAME*3); } return true; @@ -392,7 +441,7 @@ static const char* list_get_name_cb(int selected_item, struct cuesheet *cue = (struct cuesheet *)data; if (selected_item & 1) - strlcpy(buffer, cue->tracks[selected_item/2].title, buffer_len); + strmemccpy(buffer, cue->tracks[selected_item/2].title, buffer_len); else snprintf(buffer, buffer_len, "%02d. %s", selected_item/2+1, cue->tracks[selected_item/2].performer); @@ -432,7 +481,7 @@ void browse_cuesheet(struct cuesheet *cue) { gui_synclist_draw(&lists); action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD)) + if (gui_synclist_do_button(&lists, &action)) continue; switch (action) { @@ -459,7 +508,7 @@ void browse_cuesheet(struct cuesheet *cue) /* check that this cue is the same one that would be found by a search from playback */ char file[MAX_PATH]; - strlcpy(file, cue->file, MAX_PATH); + strmemccpy(file, cue->file, MAX_PATH); if (!strcmp(cue->path, file) || /* if embedded */ (search_for_cuesheet(file, &cue_file) && @@ -486,7 +535,7 @@ bool display_cuesheet_content(char* filename) if (!cue || bufsize < sizeof(struct cuesheet)) return false; - strlcpy(cue_file.path, filename, MAX_PATH); + strmemccpy(cue_file.path, filename, MAX_PATH); cue_file.pos = 0; cue_file.size = 0; @@ -516,12 +565,12 @@ bool curr_cuesheet_skip(struct cuesheet *cue, int direction, unsigned long curr_ if (!(direction <= 0 && track == 0)) { /* If skipping forward, skip to next cuesheet segment. If skipping - backward before DEFAULT_SKIP_TRESH milliseconds have elapsed, skip + backward before DEFAULT_SKIP_THRESH milliseconds have elapsed, skip to previous cuesheet segment. If skipping backward after - DEFAULT_SKIP_TRESH seconds have elapsed, skip to the start of the + DEFAULT_SKIP_THRESH seconds have elapsed, skip to the start of the current cuesheet segment */ if (direction == 1 || - ((curr_pos - cue->tracks[track].offset) < DEFAULT_SKIP_TRESH)) + ((curr_pos - cue->tracks[track].offset) < DEFAULT_SKIP_THRESH)) { track += direction; } diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 33970da581..7fc93b315b 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -23,7 +23,7 @@ #include <stdlib.h> #include <stdio.h> #include <stdbool.h> -#include <string.h> +#include <string-extra.h> #include "lcd.h" #include "lang.h" #include "menu.h" @@ -39,7 +39,6 @@ #include "audio.h" #include "settings.h" #include "list.h" -#include "statusbar.h" #include "dir.h" #include "panic.h" #include "screens.h" @@ -128,12 +127,18 @@ #if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) #include "bootdata.h" +#include "multiboot.h" +#include "rbpaths.h" +#include "pathfuncs.h" +#include "rb-loader.h" #endif +#define SCREEN_MAX_CHARS (LCD_WIDTH / SYSFONT_WIDTH) + static const char* threads_getname(int selected_item, void *data, char *buffer, size_t buffer_len) { - (void)data; + int *x_offset = (int*) data; #if NUM_CORES > 1 if (selected_item < (int)NUM_CORES) @@ -153,36 +158,76 @@ static const char* threads_getname(int selected_item, void *data, struct thread_debug_info threadinfo; if (thread_get_debug_info(selected_item, &threadinfo) > 0) { - fmtstr = "%2d:" IF_COP(" (%d)") " %s" IF_PRIO(" %d %d") + fmtstr = "%2d:" IF_COP(" (%d)") " %s%n" IF_PRIO(" %d %d") IFN_SDL(" %2d%%") " %s"; } - - snprintf(buffer, buffer_len, fmtstr, + int status_len; + size_t len = snprintf(buffer, buffer_len, fmtstr, selected_item, IF_COP(threadinfo.core,) threadinfo.statusstr, + &status_len, IF_PRIO(threadinfo.base_priority, threadinfo.current_priority,) IFN_SDL(threadinfo.stack_usage,) threadinfo.name); - return buffer; + int start = 0; +#if LCD_WIDTH <= 128 + if (len >= SCREEN_MAX_CHARS) + { + int ch_offset = (*x_offset)%(len-1); + int rem = SCREEN_MAX_CHARS - (len - ch_offset); + if (rem > 0) + ch_offset -= rem; + + if (ch_offset > 0) + { + /* don't scroll the # and status */ + status_len++; + if ((unsigned int)ch_offset + status_len < buffer_len) + memmove(&buffer[ch_offset], &buffer[0], status_len); + start = ch_offset; + } + } +#else + (void) x_offset; + (void) len; +#endif + return &buffer[start]; } static int dbg_threads_action_callback(int action, struct gui_synclist *lists) { - (void)lists; + if (action == ACTION_NONE) + { + return ACTION_REDRAW; + } +#if LCD_WIDTH <= 128 + int *x_offset = ((int*) lists->data); + if (action == ACTION_STD_OK) + { + *x_offset += 1; action = ACTION_REDRAW; + } + else if (action != ACTION_UNKNOWN) + { + *x_offset = 0; + } +#else + (void) lists; +#endif return action; } /* Test code!!! */ static bool dbg_os(void) { struct simplelist_info info; + int xoffset = 0; + simplelist_info_init(&info, IF_COP("Core and ") "Stack usage:", - MAXTHREADS IF_COP( + NUM_CORES ), NULL); - info.hide_selection = true; - info.scroll_all = true; + MAXTHREADS IF_COP( + NUM_CORES ), &xoffset); + info.scroll_all = false; info.action_callback = dbg_threads_action_callback; info.get_name = threads_getname; return simplelist_show_list(&info); @@ -295,7 +340,6 @@ static bool dbg_cpuinfo(void) info.get_name = get_cpuinfo; info.action_callback = cpuinfo_cb; info.timeout = HZ; - info.hide_selection = true; info.scroll_all = true; return simplelist_show_list(&info); } @@ -330,6 +374,13 @@ static bool dbg_buffering_thread(void) struct buffering_debug d; size_t filebuflen = audio_get_filebuflen(); /* This is a size_t, but call it a long so it puts a - when it's bad. */ +#if LCD_WIDTH > 96 + #define STR_DATAREM "data_rem" + const char * const fmt_used = "%s: %6ld/%ld"; +#else /* clipzip, ?*/ + #define STR_DATAREM "remain" + const char * const fmt_used = "%s:%ld/%ld"; +#endif #ifndef CPU_MULTI_FREQUENCY boost_ticks = 0; @@ -366,13 +417,13 @@ static bool dbg_buffering_thread(void) screens[i].clear_display(); - screens[i].putsf(0, line++, "pcm: %6ld/%ld", (long) bufused, (long) bufsize); + screens[i].putsf(0, line++, fmt_used, "pcm", (long) bufused, (long) bufsize); gui_scrollbar_draw(&screens[i],0, line*8, screens[i].lcdwidth, 6, bufsize, 0, bufused, HORIZONTAL); line++; - screens[i].putsf(0, line++, "alloc: %6ld/%ld", audio_filebufused(), + screens[i].putsf(0, line++, fmt_used, "alloc", audio_filebufused(), (long) filebuflen); #if LCD_HEIGHT > 80 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_HEIGHT > 80) @@ -382,7 +433,7 @@ static bool dbg_buffering_thread(void) filebuflen, 0, audio_filebufused(), HORIZONTAL); line++; - screens[i].putsf(0, line++, "real: %6ld/%ld", (long)d.buffered_data, + screens[i].putsf(0, line++, fmt_used, "real", (long)d.buffered_data, (long)filebuflen); gui_scrollbar_draw(&screens[i],0, line*8, screens[i].lcdwidth, 6, @@ -391,7 +442,7 @@ static bool dbg_buffering_thread(void) } #endif - screens[i].putsf(0, line++, "usefl: %6ld/%ld", (long)(d.useful_data), + screens[i].putsf(0, line++, fmt_used, "usefl", (long)(d.useful_data), (long)filebuflen); #if LCD_HEIGHT > 80 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_HEIGHT > 80) @@ -403,7 +454,7 @@ static bool dbg_buffering_thread(void) } #endif - screens[i].putsf(0, line++, "data_rem: %ld", (long)d.data_rem); + screens[i].putsf(0, line++, "%s: %ld", STR_DATAREM, (long)d.data_rem); screens[i].putsf(0, line++, "track count: %2u", audio_track_count()); @@ -442,8 +493,10 @@ static bool dbg_buffering_thread(void) screens[i].setfont(FONT_UI); return false; +#undef STR_DATAREM } +#ifdef BUFLIB_DEBUG_PRINT static const char* bf_getname(int selected_item, void *data, char *buffer, size_t buffer_len) { @@ -463,13 +516,12 @@ static int bf_action_cb(int action, struct gui_synclist* list) else { splash(HZ/1, "Attempting a 64k allocation"); - int handle = core_alloc("test", 64<<10); + int handle = core_alloc(64<<10); splash(HZ/2, (handle > 0) ? "Success":"Fail"); /* for some reason simplelist doesn't allow adding items here if * info.get_name is given, so use normal list api */ gui_synclist_set_nb_items(list, core_get_num_blocks()); - if (handle > 0) - core_free(handle); + core_free(handle); } action = ACTION_REDRAW; } @@ -485,6 +537,7 @@ static bool dbg_buflib_allocs(void) info.timeout = HZ; return simplelist_show_list(&info); } +#endif /* BUFLIB_DEBUG_PRINT */ #if (CONFIG_PLATFORM & PLATFORM_NATIVE) static const char* dbg_partitions_getname(int selected_item, void *data, @@ -514,7 +567,6 @@ static bool dbg_partitions(void) struct simplelist_info info; simplelist_info_init(&info, "Partition Info", NUM_DRIVES * 4, NULL); info.selection_size = 2; - info.hide_selection = true; info.scroll_all = true; info.get_name = dbg_partitions_getname; return simplelist_show_list(&info); @@ -816,8 +868,7 @@ static int tsc2100debug_action_callback(int action, struct gui_synclist *lists) if (action == ACTION_STD_OK) { *page = (*page+1)%3; - snprintf(lists->title, 32, - "tsc2100 registers - Page %d", *page); + snprintf((char*)lists->title, 32, "tsc2100 registers - Page %d", *page); return ACTION_REDRAW; } return action; @@ -825,7 +876,8 @@ static int tsc2100debug_action_callback(int action, struct gui_synclist *lists) static bool tsc2100_debug(void) { int page = 0; - char title[32] = "tsc2100 registers - Page 0"; + char title[32]; + snprintf(title, 32, "tsc2100 registers - Page %d", page); struct simplelist_info info; simplelist_info_init(&info, title, 32, &page); info.timeout = HZ/100; @@ -876,7 +928,7 @@ static bool view_battery(void) else grid = 5; - lcd_putsf(0, 0, "battery %d.%03dV", power_history[0] / 1000, + lcd_putsf(0, 0, "%s %d.%03dV", "Battery", power_history[0] / 1000, power_history[0] % 1000); lcd_putsf(0, 1, "%d.%03d-%d.%03dV (%2dmV)", minv / 1000, minv % 1000, maxv / 1000, maxv % 1000, @@ -887,7 +939,7 @@ static bool view_battery(void) grid = 10; else grid = 1; - lcd_putsf(0, 0, "battery %d%%", power_history[0]); + lcd_putsf(0, 0, "%s %d%%", "Battery", power_history[0]); lcd_putsf(0, 1, "%d%%-%d%% (%d %%)", minv, maxv, grid); #endif @@ -942,16 +994,16 @@ static bool view_battery(void) lcd_putsf(0, 0, "Pwr status: %s", charging_state() ? "charging" : "discharging"); #else - lcd_puts(0, 0, "Power status: unknown"); + lcd_putsf(0, 0, "Pwr status: %s", "unknown"); #endif battery_read_info(&y, &z); if (y > 0) - lcd_putsf(0, 1, "Battery: %d.%03d V (%d %%)", y / 1000, y % 1000, z); + lcd_putsf(0, 1, "%s: %d.%03d V (%d %%)", "Battery", y / 1000, y % 1000, z); else if (z > 0) - lcd_putsf(0, 1, "Battery: %d %%", z); + lcd_putsf(0, 1, "%s: %d %%", "Battery", z); #ifdef ADC_EXT_POWER y = (adc_read(ADC_EXT_POWER) * EXT_SCALE_FACTOR) / 1000; - lcd_putsf(0, 2, "External: %d.%03d V", y / 1000, y % 1000); + lcd_putsf(0, 2, "%s: %d.%03d V", "External", y / 1000, y % 1000); #endif #if CONFIG_CHARGING #if defined IPOD_NANO || defined IPOD_VIDEO @@ -965,7 +1017,7 @@ static bool view_battery(void) usb_pwr ? "present" : "absent"); lcd_putsf(0, 4, "EXT pwr: %s", ext_pwr ? "present" : "absent"); - lcd_putsf(0, 5, "Battery: %s", + lcd_putsf(0, 5, "%s: %s", "Battery", charging ? "charging" : (usb_pwr||ext_pwr) ? "charged" : "discharging"); lcd_putsf(0, 6, "Dock mode: %s", dock ? "enabled" : "disabled"); @@ -1019,7 +1071,7 @@ static bool view_battery(void) lcd_putsf(0, line++, "State: %s", chrgstate_strings[y]); - lcd_putsf(0, line++, "Battery Switch: %s", + lcd_putsf(0, line++, "%s Switch: %s", "Battery", (st & POWER_INPUT_BATTERY) ? "On" : "Off"); y = chrgraw_adc_voltage(); @@ -1042,11 +1094,11 @@ static bool view_battery(void) y = battery_adc_temp(); if (y != INT_MIN) { - lcd_putsf(0, line++, "T Battery: %d\u00b0C (%d\u00b0F)", y, - (9*y + 160) / 5); + lcd_putsf(0, line++, "T %s: %d\u00b0C (%d\u00b0F)", + "Battery", y, (9*y + 160) / 5); } else { /* Conversion disabled */ - lcd_puts(0, line++, "T Battery: ?"); + lcd_putsf(0, line++, "T %s: ?", "Battery"); } #elif defined(HAVE_AS3514) && CONFIG_CHARGING static const char * const chrgstate_strings[] = @@ -1073,7 +1125,7 @@ static bool view_battery(void) y = pmu_read_battery_voltage(); lcd_putsf(17, 1, "RAW: %d.%03d V", y / 1000, y % 1000); y = pmu_read_battery_current(); - lcd_putsf(0, 2, "Battery current: %d mA", y); + lcd_putsf(0, 2, "%s current: %d mA", "Battery", y); lcd_putsf(0, 3, "PWRCON: %08x %08x", PWRCON, PWRCONEXT); lcd_putsf(0, 4, "CLKCON: %08x %03x %03x", CLKCON, CLKCON2, CLKCON3); lcd_putsf(0, 5, "PLL: %06x %06x %06x", PLL0PMS, PLL1PMS, PLL2PMS); @@ -1099,9 +1151,9 @@ static bool view_battery(void) lcd_putsf(0, 3, "Charger: %s", charger_inserted() ? "present" : "absent"); x = (avr_hid_hdq_read_short(HDQ_REG_TEMP) / 4) - 273; - lcd_putsf(0, 4, "Battery temperature: %d C", x); + lcd_putsf(0, 4, "%s temperature: %d C", "Battery", x); x = (avr_hid_hdq_read_short(HDQ_REG_AI) * 357) / 200; - lcd_putsf(0, 5, "Battery current: %d.%01d mA", x / 10, x % 10); + lcd_putsf(0, 5, "%s current: %d.%01d mA", "Battery", x / 10, x % 10); x = (avr_hid_hdq_read_short(HDQ_REG_AP) * 292) / 20; lcd_putsf(0, 6, "Discharge power: %d.%01d mW", x / 10, x % 10); x = (avr_hid_hdq_read_short(HDQ_REG_SAE) * 292) / 2; @@ -1139,13 +1191,17 @@ static bool view_battery(void) power_history[0] % 1000); #endif - lcd_putsf(0, 6, "battery level: %d%%", battery_level()); + lcd_putsf(0, 6, "%s level: %d%%", "Battery", battery_level()); int time_left = battery_time(); if (time_left >= 0) lcd_putsf(0, 7, "Est. remain: %d m", time_left); else lcd_puts(0, 7, "Estimation n/a"); + +#if (CONFIG_BATTERY_MEASURE & CURRENT_MEASURE) + lcd_putsf(0, 8, "%s current: %d mA", "Battery", battery_current()); +#endif break; } @@ -1189,7 +1245,8 @@ static int disk_callback(int btn, struct gui_synclist *lists) int *cardnum = (int*)lists->data; unsigned char card_name[6]; unsigned char pbuf[32]; - char *title = lists->title; + /* Casting away const is safe; the buffer is defined as non-const. */ + char *title = (char *)lists->title; static const unsigned char i_vmin[] = { 0, 1, 5, 10, 25, 35, 60, 100 }; static const unsigned char i_vmax[] = { 1, 5, 10, 25, 35, 45, 80, 200 }; static const unsigned char * const kbit_units[] = { "kBit/s", "MBit/s", "GBit/s" }; @@ -1219,7 +1276,7 @@ static int disk_callback(int btn, struct gui_synclist *lists) { card_name[i] = card_extract_bits(card->cid, (103-8*i), 8); } - strlcpy(card_name, card_name, sizeof(card_name)); + strmemccpy(card_name, card_name, sizeof(card_name)); simplelist_addline( "%s Rev %d.%d", card_name, (int) card_extract_bits(card->cid, 63, 4), @@ -1572,7 +1629,8 @@ static int ata_smart_attr_to_string( if (len >= name_sz) len = name_sz-1; slen += len; } - snprintf(str+slen, size-slen, "%s", buf); + + strmemccpy(str+slen, buf, size-slen); } return 1; /* ok */ @@ -1663,7 +1721,6 @@ static bool dbg_ata_smart(void) struct simplelist_info info; simplelist_info_init(&info, "S.M.A.R.T. Data [CONTEXT to dump]", 1, NULL); info.action_callback = ata_smart_callback; - info.hide_selection = true; info.scroll_all = true; return simplelist_show_list(&info); } @@ -1717,7 +1774,6 @@ static bool dbg_disk_info(void) info.title = title; #endif info.action_callback = disk_callback; - info.hide_selection = true; info.scroll_all = true; return simplelist_show_list(&info); } @@ -1786,7 +1842,6 @@ static bool dbg_dircache_info(void) int syncbuild = 0; simplelist_info_init(&info, "Dircache Info", 8, &syncbuild); info.action_callback = dircache_callback; - info.hide_selection = true; info.scroll_all = true; return simplelist_show_list(&info); } @@ -1799,6 +1854,7 @@ static int database_callback(int btn, struct gui_synclist *lists) (void)lists; struct tagcache_stat *stat = tagcache_get_stat(); static bool synced = false; + static int update_entries = 0; simplelist_set_line_count(0); @@ -1810,6 +1866,8 @@ static int database_callback(int btn, struct gui_synclist *lists) stat->ramcache ? "Yes" : "No"); simplelist_addline("RAM: %d/%d B", stat->ramcache_used, stat->ramcache_allocated); + simplelist_addline("Total entries: %d", + stat->total_entries); simplelist_addline("Progress: %d%% (%d entries)", stat->progress, stat->processed_entries); simplelist_addline("Curfile: %s", @@ -1831,12 +1889,19 @@ static int database_callback(int btn, struct gui_synclist *lists) if (!btn && stat->curentry) { synced = true; - return ACTION_REDRAW; + if (update_entries <= stat->processed_entries) + { + update_entries = stat->processed_entries + 100; + return ACTION_REDRAW; + } + return ACTION_NONE; } if (btn == ACTION_STD_CANCEL) + { + update_entries = 0; tagcache_screensync_enable(false); - + } return btn; } static bool dbg_tagcache_info(void) @@ -1844,7 +1909,6 @@ static bool dbg_tagcache_info(void) struct simplelist_info info; simplelist_info_init(&info, "Database Info", 8, NULL); info.action_callback = database_callback; - info.hide_selection = true; info.scroll_all = true; /* Don't do nonblock here, must give enough processing time @@ -2026,12 +2090,12 @@ static int radio_callback(int btn, struct gui_synclist *lists) tea5767_dbg_info(&nfo); simplelist_addline("Philips regs:"); simplelist_addline( - " Read: %02X %02X %02X %02X %02X", + " %s: %02X %02X %02X %02X %02X", "Read", (unsigned)nfo.read_regs[0], (unsigned)nfo.read_regs[1], (unsigned)nfo.read_regs[2], (unsigned)nfo.read_regs[3], (unsigned)nfo.read_regs[4]); simplelist_addline( - " Write: %02X %02X %02X %02X %02X", + " %s: %02X %02X %02X %02X %02X", "Write", (unsigned)nfo.write_regs[0], (unsigned)nfo.write_regs[1], (unsigned)nfo.write_regs[2], (unsigned)nfo.write_regs[3], (unsigned)nfo.write_regs[4]); @@ -2097,7 +2161,7 @@ static int radio_callback(int btn, struct gui_synclist *lists) struct tm* time = gmtime(&seconds); simplelist_addline( - "CT:%4d-%02d-%02d %02d:%02d", + "CT:%4d-%02d-%02d %02d:%02d:%02d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); } @@ -2117,7 +2181,6 @@ static bool dbg_fm_radio(void) radio_hardware_present() ? "yes" : "no"); info.action_callback = radio_hardware_present()?radio_callback : NULL; - info.hide_selection = true; return simplelist_show_list(&info); } #endif /* CONFIG_TUNER */ @@ -2246,11 +2309,6 @@ static bool cpu_boost_log(void) static bool cpu_boost_log_dump(void) { int fd; -#if CONFIG_RTC - struct tm *nowtm; - char fname[MAX_PATH]; -#endif - int count = cpu_boost_log_getcount(); char *str = cpu_boost_log_getlog_first(); @@ -2261,11 +2319,12 @@ static bool cpu_boost_log_dump(void) return false; #if CONFIG_RTC - nowtm = get_time(); - snprintf(fname, MAX_PATH, "%s/boostlog_%04d%02d%02d%02d%02d%02d.txt", ROCKBOX_DIR, - nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday, - nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec); - fd = open(fname, O_CREAT|O_WRONLY|O_TRUNC); + char fname[MAX_PATH]; + struct tm *nowtm = get_time(); + fd = open_pathfmt(fname, sizeof(fname), O_CREAT|O_WRONLY|O_TRUNC, + "%s/boostlog_%04d%02d%02d%02d%02d%02d.txt", ROCKBOX_DIR, + nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday, + nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec); #else fd = open(ROCKBOX_DIR "/boostlog.txt", O_CREAT|O_WRONLY|O_TRUNC, 0666); #endif @@ -2389,7 +2448,6 @@ static bool dbg_talk(void) else simplelist_info_init(&list, "Voice Information:", 2, &data); list.scroll_all = true; - list.hide_selection = true; list.timeout = HZ; list.get_name = dbg_talk_get_name; @@ -2429,7 +2487,6 @@ static bool dbg_isp1583(void) isp1583.scroll_all = true; simplelist_info_init(&isp1583, "ISP1583", dbg_usb_num_items(), NULL); isp1583.timeout = HZ/100; - isp1583.hide_selection = true; isp1583.get_name = dbg_usb_item; isp1583.action_callback = isp1583_action_callback; return simplelist_show_list(&isp1583); @@ -2455,83 +2512,34 @@ static bool dbg_pic(void) pic.scroll_all = true; simplelist_info_init(&pic, "PIC", pic_dbg_num_items(), NULL); pic.timeout = HZ/100; - pic.hide_selection = true; pic.get_name = pic_dbg_item; pic.action_callback = pic_action_callback; return simplelist_show_list(&pic); } #endif -static bool dbg_skin_engine(void) -{ - struct simplelist_info info; - int i, total = 0; -#if defined(HAVE_BACKDROP_IMAGE) - int ref_count; - char *path; - size_t bytes; - int path_prefix_len = strlen(ROCKBOX_DIR "/wps/"); -#endif - simplelist_info_init(&info, "Skin engine usage", 0, NULL); - simplelist_set_line_count(0); - info.hide_selection = true; - FOR_NB_SCREENS(j) { -#if NB_SCREENS > 1 - simplelist_addline("%s display:", - j == 0 ? "Main" : "Remote"); -#endif - for (i = 0; i < skin_get_num_skins(); i++) { - struct skin_stats *stats = skin_get_stats(i, j); - if (stats->buflib_handles) - { - simplelist_addline("Skin ID: %d, %d allocations", - i, stats->buflib_handles); - simplelist_addline("\tskin: %d bytes", - stats->tree_size); - simplelist_addline("\tImages: %d bytes", - stats->images_size); - simplelist_addline("\tTotal: %d bytes", - stats->tree_size + stats->images_size); - total += stats->tree_size + stats->images_size; - } - } - } - simplelist_addline("Skin total usage: %d bytes", total); -#if defined(HAVE_BACKDROP_IMAGE) - simplelist_addline("Backdrop Images:"); - i = 0; - while (skin_backdrop_get_debug(i++, &path, &ref_count, &bytes)) { - if (ref_count > 0) { - - if (!strncasecmp(path, ROCKBOX_DIR "/wps/", path_prefix_len)) - path += path_prefix_len; - simplelist_addline("%s", path); - simplelist_addline("\tref_count: %d", ref_count); - simplelist_addline("\tsize: %d", bytes); - total += bytes; - } - } - simplelist_addline("Total usage: %d bytes", total); -#endif - return simplelist_show_list(&info); -} - #if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) static bool dbg_boot_data(void) { - unsigned int crc = 0; + unsigned int crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); struct simplelist_info info; info.scroll_all = true; simplelist_info_init(&info, "Boot data", 1, NULL); simplelist_set_line_count(0); - crc = crc_32(boot_data.payload, boot_data.length, 0xffffffff); + #if defined(HAVE_MULTIBOOT) + char rootpath[MAX_PATH / 2] = RB_ROOT_CONTENTS_DIR; int boot_volume = 0; if(crc == boot_data.crc) { boot_volume = boot_data.boot_volume; /* boot volume contained in uint8_t payload */ + int rtlen = get_redirect_dir(rootpath, sizeof(rootpath), boot_volume, "", ""); + while (rtlen > 0 && rootpath[--rtlen] == PATH_SEPCH) /* remove extra separators */ + rootpath[rtlen] = '\0'; } simplelist_addline("Boot Volume: <%lu>", boot_volume); + simplelist_addline("Root:"); + simplelist_addline("%s", rootpath); simplelist_addline(""); #endif simplelist_addline("Bootdata RAW:"); @@ -2547,7 +2555,6 @@ static bool dbg_boot_data(void) boot_data.payload[i+1], boot_data.payload[i+2], boot_data.payload[i+3]); } - info.hide_selection = true; return simplelist_show_list(&info); } #endif /* defined(HAVE_BOOTDATA) && !defined(SIMULATOR) */ @@ -2624,7 +2631,9 @@ static const struct { #ifdef PM_DEBUG { "pm histogram", peak_meter_histogram}, #endif /* PM_DEBUG */ +#ifdef BUFLIB_DEBUG_PRINT { "View buflib allocs", dbg_buflib_allocs }, +#endif #ifndef SIMULATOR #if CONFIG_TUNER { "FM Radio", dbg_fm_radio }, @@ -2691,6 +2700,24 @@ static const char* menu_get_name(int item, void * data, return menuitems[item].desc; } +static int menu_get_talk(int item, void *data) +{ + (void)data; + if (global_settings.talk_menu && menuitems[item].desc) + { + talk_number(item + 1, true); + talk_id(VOICE_PAUSE, true); +#if 0 /* no debug items currently have lang ids */ + long id = P2ID((const unsigned char *)(menuitems[item].desc)); + if(id>=0) + talk_id(id, true); + else +#endif + talk_spell(menuitems[item].desc, true); + } + return 0; +} + int debug_menu(void) { struct simplelist_info info; @@ -2698,6 +2725,7 @@ int debug_menu(void) simplelist_info_init(&info, "Debug Menu", ARRAYLEN(menuitems), NULL); info.action_callback = menu_action_callback; info.get_name = menu_get_name; + info.get_talk = menu_get_talk; return (simplelist_show_list(&info)) ? 1 : 0; } diff --git a/apps/enc_config.c b/apps/enc_config.c index 65ef65667a..b70ac03888 100644 --- a/apps/enc_config.c +++ b/apps/enc_config.c @@ -193,9 +193,9 @@ static bool mp3_enc_bitrate(struct menucallback_data *data) } /* mp3_enc_bitrate */ /* mp3_enc configuration menu */ -MENUITEM_FUNCTION(mp3_bitrate, MENU_FUNC_USEPARAM, ID2P(LANG_BITRATE), - mp3_enc_bitrate, - &menu_callback_data, enc_menuitem_callback, Icon_NOICON); +MENUITEM_FUNCTION_W_PARAM(mp3_bitrate, 0, ID2P(LANG_BITRATE), + mp3_enc_bitrate, &menu_callback_data, + enc_menuitem_callback, Icon_NOICON); MAKE_MENU( mp3_enc_menu, ID2P(LANG_ENCODER_SETTINGS), enc_menuitem_enteritem, Icon_NOICON, &mp3_bitrate); @@ -286,7 +286,7 @@ static int enc_menuitem_callback(int action, { (void)this_list; struct menucallback_data *data = - (struct menucallback_data*)this_item->function->param; + (struct menucallback_data*)this_item->function_param->param; if (action == ACTION_EXIT_MENUITEM) { diff --git a/apps/features.txt b/apps/features.txt index 83c3f0a65f..bafaa11599 100644 --- a/apps/features.txt +++ b/apps/features.txt @@ -105,6 +105,10 @@ radio_remote #endif #endif +#if defined(HAVE_RDS_CAP) +rds +#endif + #if defined(HAVE_RECORDING) recording #if defined(HAVE_LINE_IN) @@ -262,6 +266,8 @@ recording_digital #if MEMORYSIZE <= 2 lowmem +#elif MEMORYSIZE > 8 +himem #endif #if defined(HAVE_HARDWARE_CLICK) @@ -283,3 +289,14 @@ multi_boot #if defined(HIBY_LINUX) hibylinux #endif + +#if defined(BUTTON_REC) || \ + (CONFIG_KEYPAD == GIGABEAT_PAD) || \ + (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H10_PAD) +clear_settings_on_hold +#endif + +#if defined(HAVE_PERCEPTUAL_VOLUME) +perceptual_volume +#endif diff --git a/apps/filetree.c b/apps/filetree.c index efe5e80a0f..b652b6515b 100644 --- a/apps/filetree.c +++ b/apps/filetree.c @@ -51,11 +51,28 @@ #endif #include "wps.h" -static int compare_sort_dir; /* qsort key for sorting directories */ +static struct compare_data +{ + int sort_dir; /* qsort key for sorting directories */ + int(*_compar)(const char*, const char*, size_t); +} cmp_data; + +/* dummmy functions to allow compatibility with strncmp & strncasecmp */ +static int strnatcmp_n(const char *a, const char *b, size_t n) +{ + (void)n; + return strnatcmp(a, b); +} +static int strnatcasecmp_n(const char *a, const char *b, size_t n) +{ + (void)n; + return strnatcasecmp(a, b); +} int ft_build_playlist(struct tree_context* c, int start_index) { int i; + int res = 0; int start=start_index; tree_lock_cache(c); @@ -65,7 +82,8 @@ int ft_build_playlist(struct tree_context* c, int start_index) { if((entries[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) { - if (playlist_add(entries[i].name) < 0) + res = playlist_add(entries[i].name); + if (res < 0) break; } else @@ -77,7 +95,6 @@ int ft_build_playlist(struct tree_context* c, int start_index) } tree_unlock_cache(c); - return start_index; } @@ -89,7 +106,8 @@ int ft_build_playlist(struct tree_context* c, int start_index) * avoid allocating yet another path buffer on the stack (and save some * code; the caller typically needs to create the full pathname anyway)... */ -bool ft_play_playlist(char* pathname, char* dirname, char* filename, bool skip_dyn_warning) +bool ft_play_playlist(char* pathname, char* dirname, + char* filename, bool skip_warn_and_bookmarks) { if (global_settings.party_mode && audio_status()) { @@ -97,22 +115,15 @@ bool ft_play_playlist(char* pathname, char* dirname, char* filename, bool skip_d return false; } - if (bookmark_autoload(pathname)) + if (!skip_warn_and_bookmarks) { - return false; + int res = bookmark_autoload(pathname); + if (res == BOOKMARK_CANCEL || res == BOOKMARK_DO_RESUME || !warn_on_pl_erase()) + return false; } splash(0, ID2P(LANG_WAIT)); - /* about to create a new current playlist... - * allow user to cancel the operation. - * Do not show if skip_dyn_warning is true */ - if (!skip_dyn_warning) - { - if (!warn_on_pl_erase()) - return false; - } - if (playlist_create(dirname, filename) != -1) { if (global_settings.playlist_shuffle) @@ -198,7 +209,7 @@ static int compare(const void* p1, const void* p2) if (e1->attr & ATTR_DIRECTORY && e2->attr & ATTR_DIRECTORY) { /* two directories */ - criteria = compare_sort_dir; + criteria = cmp_data.sort_dir; #ifdef HAVE_MULTIVOLUME if (e1->attr & ATTR_VOLUME || e2->attr & ATTR_VOLUME) @@ -233,41 +244,23 @@ static int compare(const void* p1, const void* p2) if (t1 != t2) /* if different */ return (t1 - t2) * (criteria == SORT_TYPE_REVERSED ? -1 : 1); - /* else fall through to alphabetical sorting */ + /* else alphabetical sorting */ + return cmp_data._compar(e1->name, e2->name, MAX_PATH); } case SORT_DATE: case SORT_DATE_REVERSED: - /* Ignore SORT_TYPE */ - if (criteria == SORT_DATE || criteria == SORT_DATE_REVERSED) - { - if (e1->time_write != e2->time_write) - return (e1->time_write - e2->time_write) - * (criteria == SORT_DATE_REVERSED ? -1 : 1); - /* else fall through to alphabetical sorting */ - } - + { + if (e1->time_write != e2->time_write) + return (e1->time_write - e2->time_write) + * (criteria == SORT_DATE_REVERSED ? -1 : 1); + /* else fall through to alphabetical sorting */ + } case SORT_ALPHA: case SORT_ALPHA_REVERSED: { - if (global_settings.sort_case) - { - if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) - return strnatcmp(e1->name, e2->name) - * (criteria == SORT_ALPHA_REVERSED ? -1 : 1); - else - return strncmp(e1->name, e2->name, MAX_PATH) - * (criteria == SORT_ALPHA_REVERSED ? -1 : 1); - } - else - { - if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) - return strnatcasecmp(e1->name, e2->name) - * (criteria == SORT_ALPHA_REVERSED ? -1 : 1); - else - return strncasecmp(e1->name, e2->name, MAX_PATH) - * (criteria == SORT_ALPHA_REVERSED ? -1 : 1); - } + return cmp_data._compar(e1->name, e2->name, MAX_PATH) * + (criteria == SORT_ALPHA_REVERSED ? -1 : 1); } } @@ -330,42 +323,43 @@ int ft_load(struct tree_context* c, const char* tempdir) } dptr->attr = info.attribute; + int dir_attr = (dptr->attr & ATTR_DIRECTORY); /* check for known file types */ - if ( !(dptr->attr & ATTR_DIRECTORY) ) + if ( !(dir_attr) ) dptr->attr |= filetype_get_attr((char *)entry->d_name); + int file_attr = (dptr->attr & FILE_ATTR_MASK); + /* filter out non-visible files */ - if ((!(dptr->attr & ATTR_DIRECTORY) && ( - (*c->dirfilter == SHOW_PLAYLIST && - (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) || - ((*c->dirfilter == SHOW_MUSIC && - (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) && - (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) || + if ((!(dir_attr) && ((*c->dirfilter == SHOW_PLAYLIST && + file_attr != FILE_ATTR_M3U) || + ((*c->dirfilter == SHOW_MUSIC && file_attr != FILE_ATTR_AUDIO) && + file_attr != FILE_ATTR_M3U) || (*c->dirfilter == SHOW_SUPPORTED && !filetype_supported(dptr->attr)))) || - (*c->dirfilter == SHOW_WPS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_WPS) || - (*c->dirfilter == SHOW_FONT && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_FONT) || - (*c->dirfilter == SHOW_SBS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_SBS) || + (*c->dirfilter == SHOW_WPS && file_attr != FILE_ATTR_WPS) || + (*c->dirfilter == SHOW_FONT && file_attr != FILE_ATTR_FONT) || + (*c->dirfilter == SHOW_SBS && file_attr != FILE_ATTR_SBS) || #if CONFIG_TUNER - (*c->dirfilter == SHOW_FMS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_FMS) || + (*c->dirfilter == SHOW_FMS && file_attr != FILE_ATTR_FMS) || #endif #ifdef HAVE_REMOTE_LCD - (*c->dirfilter == SHOW_RWPS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_RWPS) || - (*c->dirfilter == SHOW_RSBS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_RSBS) || + (*c->dirfilter == SHOW_RWPS && file_attr != FILE_ATTR_RWPS) || + (*c->dirfilter == SHOW_RSBS && file_attr != FILE_ATTR_RSBS) || #if CONFIG_TUNER - (*c->dirfilter == SHOW_RFMS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_RFMS) || + (*c->dirfilter == SHOW_RFMS && file_attr != FILE_ATTR_RFMS) || #endif #endif #if CONFIG_TUNER - (*c->dirfilter == SHOW_FMR && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_FMR) || + (*c->dirfilter == SHOW_FMR && file_attr != FILE_ATTR_FMR) || #endif - (*c->dirfilter == SHOW_M3U && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) || - (*c->dirfilter == SHOW_CFG && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_CFG) || - (*c->dirfilter == SHOW_LNG && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_LNG) || - (*c->dirfilter == SHOW_MOD && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_MOD) || - (*c->dirfilter == SHOW_PLUGINS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_ROCK && - (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_LUA && - (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_OPX) || + (*c->dirfilter == SHOW_M3U && file_attr != FILE_ATTR_M3U) || + (*c->dirfilter == SHOW_CFG && file_attr != FILE_ATTR_CFG) || + (*c->dirfilter == SHOW_LNG && file_attr != FILE_ATTR_LNG) || + (*c->dirfilter == SHOW_MOD && file_attr != FILE_ATTR_MOD) || + (*c->dirfilter == SHOW_PLUGINS && file_attr != FILE_ATTR_ROCK && + file_attr != FILE_ATTR_LUA && + file_attr != FILE_ATTR_OPX) || (callback_show_item && !callback_show_item(entry->d_name, dptr->attr, c))) { continue; @@ -384,14 +378,29 @@ int ft_load(struct tree_context* c, const char* tempdir) strcpy(dptr->name, (char *)entry->d_name); name_buffer_used += len + 1; - if (dptr->attr & ATTR_DIRECTORY) /* count the remaining dirs */ + if (dir_attr) /* count the remaining dirs */ c->dirsindir++; } c->filesindir = files_in_dir; c->dirlength = files_in_dir; closedir(dir); - compare_sort_dir = c->sort_dir; + cmp_data.sort_dir = c->sort_dir; + if (global_settings.sort_case) + { + if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) + cmp_data._compar = strnatcmp_n; + else + cmp_data._compar = strncmp; + } + else + { + if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) + cmp_data._compar = strnatcasecmp_n; + else + cmp_data._compar = strncasecmp; + } + qsort(tree_get_entries(c), files_in_dir, sizeof(struct entry), compare); /* If thumbnail talking is enabled, make an extra run to mark files with @@ -432,10 +441,17 @@ static void ft_load_font(char *file) viewportmanager_theme_changed(THEME_UI_VIEWPORT); } +static void ft_apply_skin_file(char *buf, char *file, const int maxlen) +{ + splash(0, ID2P(LANG_WAIT)); + set_file(buf, file, maxlen); + settings_apply_skins(); +} + int ft_enter(struct tree_context* c) { int rc = GO_TO_PREVIOUS; - static char buf[MAX_PATH]; + char buf[MAX_PATH]; struct entry* file = tree_get_entry_at(c, c->selected_item); if (!file) @@ -480,7 +496,9 @@ int ft_enter(struct tree_context* c) break; case FILE_ATTR_AUDIO: - if (bookmark_autoload(c->currdir)) + { + int res = bookmark_autoload(c->currdir); + if (res == BOOKMARK_CANCEL || res == BOOKMARK_DO_RESUME) break; splash(0, ID2P(LANG_WAIT)); @@ -514,7 +532,7 @@ int ft_enter(struct tree_context* c) play = true; } break; - + } #if CONFIG_TUNER /* fmr preset file */ case FILE_ATTR_FMR: @@ -539,49 +557,32 @@ int ft_enter(struct tree_context* c) break; case FILE_ATTR_FMS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.fms_file, MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.fms_file, MAX_FILENAME); break; #ifdef HAVE_REMOTE_LCD case FILE_ATTR_RFMS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.rfms_file, MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.rfms_file, MAX_FILENAME); break; #endif #endif - case FILE_ATTR_SBS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.sbs_file, MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.sbs_file, MAX_FILENAME); break; #ifdef HAVE_REMOTE_LCD case FILE_ATTR_RSBS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.rsbs_file, MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.rsbs_file, MAX_FILENAME); break; #endif /* wps config file */ case FILE_ATTR_WPS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.wps_file, - MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.wps_file, MAX_FILENAME); break; - #if defined(HAVE_REMOTE_LCD) && (NB_SCREENS > 1) /* remote-wps config file */ case FILE_ATTR_RWPS: - splash(0, ID2P(LANG_WAIT)); - set_file(buf, (char *)global_settings.rwps_file, - MAX_FILENAME); - settings_apply_skins(); + ft_apply_skin_file(buf, global_settings.rwps_file, MAX_FILENAME); break; #endif - case FILE_ATTR_CFG: splash(0, ID2P(LANG_WAIT)); if (!settings_load_config(buf,true)) @@ -641,6 +642,8 @@ int ft_enter(struct tree_context* c) splash(HZ, ID2P(LANG_PARTY_MODE)); break; } + +#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */ switch (plugin_load(plugin, argument)) { case PLUGIN_GOTO_WPS: @@ -664,6 +667,10 @@ int ft_enter(struct tree_context* c) default: break; } +#else /* Exit the filetree to run a plugin */ + plugin_open(plugin, argument); + rc = GO_TO_PLUGIN; +#endif break; } @@ -677,7 +684,7 @@ int ft_enter(struct tree_context* c) break; } - struct entry* file = tree_get_entry_at(c, c->selected_item); + file = tree_get_entry_at(c, c->selected_item); if (!file) { splashf(HZ, str(LANG_READ_FAILED), str(LANG_UNKNOWN)); @@ -687,6 +694,7 @@ int ft_enter(struct tree_context* c) plugin = filetype_get_plugin(file, plugin_path, sizeof(plugin_path)); if (plugin) { +#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */ switch (plugin_load(plugin, argument)) { case PLUGIN_USB_CONNECTED: @@ -705,6 +713,10 @@ int ft_enter(struct tree_context* c) default: break; } +#else /* Exit the filetree to run a plugin */ + plugin_open(plugin, argument); + rc = GO_TO_PLUGIN; +#endif } break; } diff --git a/apps/filetree.h b/apps/filetree.h index 178ba0e973..7931c3c454 100644 --- a/apps/filetree.h +++ b/apps/filetree.h @@ -26,6 +26,7 @@ int ft_load(struct tree_context* c, const char* tempdir); int ft_enter(struct tree_context* c); int ft_exit(struct tree_context* c); int ft_build_playlist(struct tree_context* c, int start_index); -bool ft_play_playlist(char* pathname, char* dirname, char* filename, bool skip_dyn_warning); +bool ft_play_playlist(char* pathname, char* dirname, + char* filename, bool skip_warn_and_bookmarks); #endif diff --git a/apps/filetypes.c b/apps/filetypes.c index d68bab3daa..1944ee9383 100644 --- a/apps/filetypes.c +++ b/apps/filetypes.c @@ -38,6 +38,7 @@ #include "splash.h" #include "core_alloc.h" #include "icons.h" +/*#define LOGF_ENABLE*/ #include "logf.h" /* max filetypes (plugins & icons stored here) */ @@ -45,111 +46,171 @@ /* max viewer plugins */ #define MAX_VIEWERS 56 +static void read_builtin_types_init(void) INIT_ATTR; +static void read_viewers_config_init(void) INIT_ATTR; +static void read_config_init(int fd) INIT_ATTR; + /* a table for the known file types */ static const struct filetype inbuilt_filetypes[] = { - { "mp3", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mp2", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mpa", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mp1", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "ogg", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "oga", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "wma", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "wmv", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "asf", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "wav", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "flac",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "ac3", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "a52", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mpc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "wv", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "m4a", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "m4b", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mp4", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mod", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "shn", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "aif", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "aiff",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "spx" ,FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "opus",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "sid", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "adx", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "nsf", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "nsfe",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "spc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "ape", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mac", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "sap" ,FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "rm", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "ra", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "rmvb",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "cmc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "cm3", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "cmr", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "cms", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "dmc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "dlt", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mpt", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mpd", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "rmt", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "tmc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "tm8", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "tm2", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "oma", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "aa3", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "at3", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "mmf", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "au", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "snd", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "vox", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "w64", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "tta", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "ay", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "vtx", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "gbs", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "hes", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "sgc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "vgm", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "vgz", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "kss", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "aac", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, - { "m3u", FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, - { "m3u8",FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, - { "cfg", FILE_ATTR_CFG, Icon_Config, VOICE_EXT_CFG }, - { "wps", FILE_ATTR_WPS, Icon_Wps, VOICE_EXT_WPS }, + { "mp3", FILE_ATTR_AUDIO }, + { "mp2", FILE_ATTR_AUDIO }, + { "mpa", FILE_ATTR_AUDIO }, + { "mp1", FILE_ATTR_AUDIO }, + { "ogg", FILE_ATTR_AUDIO }, + { "oga", FILE_ATTR_AUDIO }, + { "wma", FILE_ATTR_AUDIO }, + { "wmv", FILE_ATTR_AUDIO }, + { "asf", FILE_ATTR_AUDIO }, + { "wav", FILE_ATTR_AUDIO }, + { "flac", FILE_ATTR_AUDIO }, + { "ac3", FILE_ATTR_AUDIO }, + { "a52", FILE_ATTR_AUDIO }, + { "mpc", FILE_ATTR_AUDIO }, + { "wv", FILE_ATTR_AUDIO }, + { "m4a", FILE_ATTR_AUDIO }, + { "m4b", FILE_ATTR_AUDIO }, + { "mp4", FILE_ATTR_AUDIO }, + { "mod", FILE_ATTR_AUDIO }, + { "mpga", FILE_ATTR_AUDIO }, + { "shn", FILE_ATTR_AUDIO }, + { "aif", FILE_ATTR_AUDIO }, + { "aiff", FILE_ATTR_AUDIO }, + { "spx" , FILE_ATTR_AUDIO }, + { "opus", FILE_ATTR_AUDIO }, + { "sid", FILE_ATTR_AUDIO }, + { "adx", FILE_ATTR_AUDIO }, + { "nsf", FILE_ATTR_AUDIO }, + { "nsfe", FILE_ATTR_AUDIO }, + { "spc", FILE_ATTR_AUDIO }, + { "ape", FILE_ATTR_AUDIO }, + { "mac", FILE_ATTR_AUDIO }, + { "sap" , FILE_ATTR_AUDIO }, + { "rm", FILE_ATTR_AUDIO }, + { "ra", FILE_ATTR_AUDIO }, + { "rmvb", FILE_ATTR_AUDIO }, + { "cmc", FILE_ATTR_AUDIO }, + { "cm3", FILE_ATTR_AUDIO }, + { "cmr", FILE_ATTR_AUDIO }, + { "cms", FILE_ATTR_AUDIO }, + { "dmc", FILE_ATTR_AUDIO }, + { "dlt", FILE_ATTR_AUDIO }, + { "mpt", FILE_ATTR_AUDIO }, + { "mpd", FILE_ATTR_AUDIO }, + { "rmt", FILE_ATTR_AUDIO }, + { "tmc", FILE_ATTR_AUDIO }, + { "tm8", FILE_ATTR_AUDIO }, + { "tm2", FILE_ATTR_AUDIO }, + { "oma", FILE_ATTR_AUDIO }, + { "aa3", FILE_ATTR_AUDIO }, + { "at3", FILE_ATTR_AUDIO }, + { "mmf", FILE_ATTR_AUDIO }, + { "au", FILE_ATTR_AUDIO }, + { "snd", FILE_ATTR_AUDIO }, + { "vox", FILE_ATTR_AUDIO }, + { "w64", FILE_ATTR_AUDIO }, + { "tta", FILE_ATTR_AUDIO }, + { "ay", FILE_ATTR_AUDIO }, + { "vtx", FILE_ATTR_AUDIO }, + { "gbs", FILE_ATTR_AUDIO }, + { "hes", FILE_ATTR_AUDIO }, + { "sgc", FILE_ATTR_AUDIO }, + { "vgm", FILE_ATTR_AUDIO }, + { "vgz", FILE_ATTR_AUDIO }, + { "kss", FILE_ATTR_AUDIO }, + { "aac", FILE_ATTR_AUDIO }, + { "m3u", FILE_ATTR_M3U }, + { "m3u8", FILE_ATTR_M3U }, + { "cfg", FILE_ATTR_CFG }, + { "wps", FILE_ATTR_WPS }, #ifdef HAVE_REMOTE_LCD - { "rwps",FILE_ATTR_RWPS, Icon_Wps, VOICE_EXT_RWPS }, + { "rwps", FILE_ATTR_RWPS }, #endif #if CONFIG_TUNER - { "fmr", FILE_ATTR_FMR, Icon_Preset, LANG_FMR }, - { "fms", FILE_ATTR_FMS, Icon_Wps, VOICE_EXT_FMS }, + { "fmr", FILE_ATTR_FMR }, + { "fms", FILE_ATTR_FMS }, #endif - { "lng", FILE_ATTR_LNG, Icon_Language, LANG_LANGUAGE }, - { "rock",FILE_ATTR_ROCK,Icon_Plugin, VOICE_EXT_ROCK }, - { "lua", FILE_ATTR_LUA, Icon_Plugin, VOICE_EXT_ROCK }, - { "opx", FILE_ATTR_OPX, Icon_Plugin, VOICE_EXT_ROCK }, - { "fnt", FILE_ATTR_FONT,Icon_Font, VOICE_EXT_FONT }, - { "kbd", FILE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD }, - { "bmark",FILE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK }, - { "cue", FILE_ATTR_CUE, Icon_Bookmark, VOICE_EXT_CUESHEET }, - { "sbs", FILE_ATTR_SBS, Icon_Wps, VOICE_EXT_SBS }, + { "lng", FILE_ATTR_LNG }, + { "rock", FILE_ATTR_ROCK }, + { "lua", FILE_ATTR_LUA }, + { "opx", FILE_ATTR_OPX }, + { "fnt", FILE_ATTR_FONT }, + { "kbd", FILE_ATTR_KBD }, + { "bmark",FILE_ATTR_BMARK }, + { "cue", FILE_ATTR_CUE }, + { "sbs", FILE_ATTR_SBS }, #ifdef HAVE_REMOTE_LCD - { "rsbs", FILE_ATTR_RSBS, Icon_Wps, VOICE_EXT_RSBS }, + { "rsbs", FILE_ATTR_RSBS }, #if CONFIG_TUNER - { "rfms", FILE_ATTR_RFMS, Icon_Wps, VOICE_EXT_RFMS }, + { "rfms", FILE_ATTR_RFMS }, #endif #endif #ifdef BOOTFILE_EXT - { BOOTFILE_EXT, FILE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ }, + { BOOTFILE_EXT, FILE_ATTR_MOD }, #endif #ifdef BOOTFILE_EXT2 - { BOOTFILE_EXT2, FILE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ }, + { BOOTFILE_EXT2, FILE_ATTR_MOD }, +#endif +}; + +struct fileattr_icon_voice { + int tree_attr; + uint16_t icon; + uint16_t voiceclip; +}; + +/* a table for the known file types icons & voice clips */ +static const struct fileattr_icon_voice inbuilt_attr_icons_voices[] = { + { FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, + { FILE_ATTR_CFG, Icon_Config, VOICE_EXT_CFG }, + { FILE_ATTR_WPS, Icon_Wps, VOICE_EXT_WPS }, +#ifdef HAVE_REMOTE_LCD + {FILE_ATTR_RWPS, Icon_Wps, VOICE_EXT_RWPS }, +#endif +#if CONFIG_TUNER + { FILE_ATTR_FMR, Icon_Preset, LANG_FMR }, + { FILE_ATTR_FMS, Icon_Wps, VOICE_EXT_FMS }, +#endif + { FILE_ATTR_LNG, Icon_Language, LANG_LANGUAGE }, + { FILE_ATTR_ROCK, Icon_Plugin, VOICE_EXT_ROCK }, + { FILE_ATTR_LUA, Icon_Plugin, VOICE_EXT_ROCK }, + { FILE_ATTR_OPX, Icon_Plugin, VOICE_EXT_ROCK }, + { FILE_ATTR_FONT, Icon_Font, VOICE_EXT_FONT }, + { FILE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD }, + { FILE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK }, + { FILE_ATTR_CUE, Icon_Bookmark, VOICE_EXT_CUESHEET }, + { FILE_ATTR_SBS, Icon_Wps, VOICE_EXT_SBS }, +#ifdef HAVE_REMOTE_LCD + { FILE_ATTR_RSBS, Icon_Wps, VOICE_EXT_RSBS }, +#if CONFIG_TUNER + { FILE_ATTR_RFMS, Icon_Wps, VOICE_EXT_RFMS }, +#endif +#endif +#if defined(BOOTFILE_EXT) || defined(BOOTFILE_EXT2) + { FILE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ }, #endif }; -void tree_get_filetypes(const struct filetype** types, int* count) +long tree_get_filetype_voiceclip(int attr) { - *types = inbuilt_filetypes; - *count = sizeof(inbuilt_filetypes) / sizeof(*inbuilt_filetypes); + if (global_settings.talk_filetype) + { + size_t count = ARRAY_SIZE(inbuilt_attr_icons_voices); + /* try to find a voice ID for the extension, if known */ + attr &= FILE_ATTR_MASK; /* file type */ + + for (size_t i = count - 1; i < count; i--) + { + if (attr == inbuilt_attr_icons_voices[i].tree_attr) + { + logf("%s found attr %d id %d", __func__, attr, + inbuilt_attr_icons_voices[i].voiceclip); + return inbuilt_attr_icons_voices[i].voiceclip; + } + } + } + logf("%s not found attr %d", __func__, attr); + return -1; } #define ROCK_EXTENSION "rock" @@ -243,8 +304,6 @@ static int find_extension(const char* extension) return -1; } -static void read_builtin_types(void); -static void read_config(int fd); #ifdef HAVE_LCD_COLOR /* Colors file format is similar to icons: * ext:hex_color @@ -261,9 +320,9 @@ void read_color_theme_file(void) { unknown_file.color = -1; if (!global_settings.colors_file[0] || global_settings.colors_file[0] == '-') return; - snprintf(buffer, MAX_PATH, THEME_DIR "/%s.colours", - global_settings.colors_file); - fd = open(buffer, O_RDONLY); + + fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY, + THEME_DIR "/%s.colours", global_settings.colors_file); if (fd < 0) return; while (read_line(fd, buffer, MAX_PATH) > 0) @@ -301,12 +360,12 @@ void read_viewer_theme_file(void) { custom_filetype_icons[i] = filetypes[i].icon; } - - snprintf(buffer, MAX_PATH, "%s/%s.icons", ICON_DIR, - global_settings.viewers_icon_file); - fd = open(buffer, O_RDONLY); + + fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY, + ICON_DIR "/%s.icons", global_settings.viewers_icon_file); if (fd < 0) return; + while (read_line(fd, buffer, MAX_PATH) > 0) { if (!settings_parseline(buffer, &ext, &icon)) @@ -338,7 +397,7 @@ void read_viewer_theme_file(void) custom_icons_loaded = true; } -static void read_viewers_config(void) +static void read_viewers_config_init(void) { int fd = open(VIEWERS_CONFIG, O_RDONLY); if(fd < 0) @@ -350,18 +409,18 @@ static void read_viewers_config(void) /* estimate bufsize with the filesize, will not be larger */ strdup_bufsize = (size_t)filesz; - strdup_handle = core_alloc_ex("filetypes", strdup_bufsize, &ops); + strdup_handle = core_alloc_ex(strdup_bufsize, &ops); if(strdup_handle <= 0) goto out; - read_config(fd); - core_shrink(strdup_handle, core_get_data(strdup_handle), strdup_cur_idx); + read_config_init(fd); + core_shrink(strdup_handle, NULL, strdup_cur_idx); out: close(fd); } -void filetype_init(void) +void filetype_init(void) { /* set the directory item first */ filetypes[0].extension = NULL; @@ -372,8 +431,8 @@ void filetype_init(void) viewer_count = 0; filetype_count = 1; - read_builtin_types(); - read_viewers_config(); + read_builtin_types_init(); + read_viewers_config_init(); read_viewer_theme_file(); #ifdef HAVE_LCD_COLOR read_color_theme_file(); @@ -396,22 +455,35 @@ static void rm_whitespaces(char* str) *s = '\0'; } -static void read_builtin_types(void) +static void read_builtin_types_init(void) { - int count = sizeof(inbuilt_filetypes)/sizeof(*inbuilt_filetypes), i; - for(i=0; i<count && (filetype_count < MAX_FILETYPES); i++) + int tree_attr; + size_t count = ARRAY_SIZE(inbuilt_filetypes); + size_t icon_count = ARRAY_SIZE(inbuilt_attr_icons_voices); + for(size_t i = 0; (i < count) && (filetype_count < MAX_FILETYPES); i++) { filetypes[filetype_count].extension = inbuilt_filetypes[i].extension; filetypes[filetype_count].plugin = NULL; - filetypes[filetype_count].attr = inbuilt_filetypes[i].tree_attr>>8; + + tree_attr = inbuilt_filetypes[i].tree_attr; + filetypes[filetype_count].attr = tree_attr>>8; if (filetypes[filetype_count].attr > highest_attr) highest_attr = filetypes[filetype_count].attr; - filetypes[filetype_count].icon = inbuilt_filetypes[i].icon; + + filetypes[filetype_count].icon = unknown_file.icon; + for (size_t j = icon_count - 1; j < icon_count; j--) + { + if (tree_attr == inbuilt_attr_icons_voices[j].tree_attr) + { + filetypes[filetype_count].icon = inbuilt_attr_icons_voices[j].icon; + break; + } + } filetype_count++; } } -static void read_config(int fd) +static void read_config_init(int fd) { char line[64], *s, *e; char *extension, *plugin; @@ -617,7 +689,7 @@ int filetype_list_viewers(const char* current_file) int i = viewers[info.selection]; snprintf(plugin, MAX_PATH, "%s/%s." ROCK_EXTENSION, PLUGIN_DIR, filetypes[i].plugin); - plugin_load(plugin, current_file); + ret = plugin_load(plugin, current_file); } return ret; } diff --git a/apps/filetypes.h b/apps/filetypes.h index efe9f3f5df..5aae772a9c 100644 --- a/apps/filetypes.h +++ b/apps/filetypes.h @@ -53,14 +53,14 @@ struct filetype { char* extension; int tree_attr; - enum themable_icons icon; - int voiceclip; }; -void tree_get_filetypes(const struct filetype**, int*) INIT_ATTR; + +long tree_get_filetype_voiceclip(int attr); /* init the filetypes structs. uses audio buffer for storage, so call early in init... */ void filetype_init(void) INIT_ATTR; + void read_viewer_theme_file(void); #ifdef HAVE_LCD_COLOR void read_color_theme_file(void); diff --git a/apps/gui/bitmap/list-skinned.c b/apps/gui/bitmap/list-skinned.c index a67ac8cb0a..bebff821f8 100644 --- a/apps/gui/bitmap/list-skinned.c +++ b/apps/gui/bitmap/list-skinned.c @@ -213,8 +213,7 @@ bool skinlist_draw(struct screen *display, struct gui_synclist *list) if (list_start_item+cur_line+1 > list->nb_items) break; current_drawing_line = list_start_item+cur_line; - is_selected = list->show_selection_marker && - list_start_item+cur_line == list->selected_item; + is_selected = list_start_item+cur_line == list->selected_item; for (viewport = SKINOFFSETTOPTR(get_skin_buffer(wps.data), listcfg[screen]->data->tree); viewport; diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c index ff0f5a29c1..1c9b73a5fe 100644 --- a/apps/gui/bitmap/list.c +++ b/apps/gui/bitmap/list.c @@ -90,11 +90,57 @@ static int list_icon_width(enum screen_type screen) return get_icon_width(screen) + ICON_PADDING * 2; } -static bool draw_title(struct screen *display, struct gui_synclist *list) +static void _default_listdraw_fn(struct list_putlineinfo_t *list_info) +{ + struct screen *display = list_info->display; + int x = list_info->x; + int y = list_info->y; + int item_indent = list_info->item_indent; + int item_offset = list_info->item_offset; + int icon = list_info->icon; + bool is_selected = list_info->is_selected; + bool is_title = list_info->is_title; + bool show_cursor = list_info->show_cursor; + bool have_icons = list_info->have_icons; + struct line_desc *linedes = list_info->linedes; + const char *dsp_text = list_info->dsp_text; + + if (is_title) + { + if (have_icons) + display->put_line(x, y, linedes, "$"ICON_PADDING_S"I$t", + icon, dsp_text); + else + display->put_line(x, y, linedes, "$t", dsp_text); + } + else if (show_cursor && have_icons) + { + /* the list can have both, one of or neither of cursor and item icons, + * if both don't apply icon padding twice between the icons */ + display->put_line(x, y, + linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t", + item_indent, is_selected ? Icon_Cursor : Icon_NOICON, + icon, item_offset, dsp_text); + } + else if (show_cursor || have_icons) + { + display->put_line(x, y, linedes, "$*s$"ICON_PADDING_S"I$*t", item_indent, + show_cursor ? (is_selected ? Icon_Cursor:Icon_NOICON):icon, + item_offset, dsp_text); + } + else + { + display->put_line(x, y, linedes, "$*s$*t", item_indent, item_offset, dsp_text); + } +} + +static bool draw_title(struct screen *display, + struct gui_synclist *list, + list_draw_item *callback_draw_item) { const int screen = display->screen_type; struct viewport *title_text_vp = &title_text[screen]; - struct line_desc line = LINE_DESC_DEFINIT; + struct line_desc linedes = LINE_DESC_DEFINIT; if (sb_set_title_text(list->title, list->title_icon, screen)) return false; /* the sbs is handling the title */ @@ -102,29 +148,41 @@ static bool draw_title(struct screen *display, struct gui_synclist *list) if (!list_display_title(list, screen)) return false; *title_text_vp = *(list->parent[screen]); - line.height = list->line_height[screen]; - title_text_vp->height = line.height; + linedes.height = list->line_height[screen]; + title_text_vp->height = linedes.height; #if LCD_DEPTH > 1 /* XXX: Do we want to support the separator on remote displays? */ if (display->screen_type == SCREEN_MAIN && global_settings.list_separator_height != 0) - line.separator_height = abs(global_settings.list_separator_height) + linedes.separator_height = abs(global_settings.list_separator_height) + (lcd_get_dpi() > 200 ? 2 : 1); #endif #ifdef HAVE_LCD_COLOR if (list->title_color >= 0) - line.style |= (STYLE_COLORED|list->title_color); + linedes.style |= (STYLE_COLORED|list->title_color); #endif - line.scroll = true; + linedes.scroll = true; display->set_viewport(title_text_vp); + int icon = list->title_icon; + int icon_w = list_icon_width(display->screen_type); + bool have_icons = false; + if (icon != Icon_NOICON && list->show_icons) + { + have_icons = true; + } - if (list->title_icon != Icon_NOICON && global_settings.show_icons) - put_line(display, 0, 0, &line, "$"ICON_PADDING_S"I$t", - list->title_icon, list->title); - else - put_line(display, 0, 0, &line, "$t", list->title); + struct list_putlineinfo_t list_info = + { + .x = 0, .y = 0, .item_indent = 0, .item_offset = 0, + .line = -1, .icon = icon, .icon_width = icon_w, + .display = display, .vp = title_text_vp, .linedes = &linedes, .list = list, + .dsp_text = list->title, + .is_selected = false, .is_title = true, .show_cursor = false, + .have_icons = have_icons + }; + callback_draw_item(&list_info); return true; } @@ -133,23 +191,30 @@ void list_draw(struct screen *display, struct gui_synclist *list) { int start, end, item_offset, i; const int screen = display->screen_type; + list_draw_item *callback_draw_item; + const int list_start_item = list->start_item[screen]; - const bool scrollbar_in_left = (global_settings.scrollbar == SCROLLBAR_LEFT); - const bool scrollbar_in_right = (global_settings.scrollbar == SCROLLBAR_RIGHT); - const bool show_cursor = !global_settings.cursor_style && - list->show_selection_marker; - const bool have_icons = global_settings.show_icons && list->callback_get_item_icon; + const bool scrollbar_in_left = (list->scrollbar == SCROLLBAR_LEFT); + const bool scrollbar_in_right = (list->scrollbar == SCROLLBAR_RIGHT); + const bool show_cursor = (list->cursor_style == SYNCLIST_CURSOR_NOSTYLE); + const bool have_icons = list->callback_get_item_icon && list->show_icons; + struct viewport *parent = (list->parent[screen]); struct line_desc linedes = LINE_DESC_DEFINIT; bool show_title; struct viewport *list_text_vp = &list_text[screen]; int indent = 0; + if (list->callback_draw_item != NULL) + callback_draw_item = list->callback_draw_item; + else + callback_draw_item = _default_listdraw_fn; + struct viewport * last_vp = display->set_viewport(parent); display->clear_viewport(); display->scroll_stop_viewport(list_text_vp); *list_text_vp = *parent; - if ((show_title = draw_title(display, list))) + if ((show_title = draw_title(display, list, callback_draw_item))) { int title_height = title_text[screen].height; list_text_vp->y += title_height; @@ -169,6 +234,12 @@ void list_draw(struct screen *display, struct gui_synclist *list) end = start + nb_lines; #ifdef HAVE_TOUCHSCREEN + /* y_pos needs to be clamped now since it can overflow the maximum + * in some cases, and we have no easy way to prevent this beforehand */ + int max_y_pos = list->nb_items * linedes.height - list_text[screen].height; + if (max_y_pos > 0 && list->y_pos > max_y_pos) + list->y_pos = max_y_pos; + int draw_offset = list_start_item * linedes.height - list->y_pos; /* draw some extra items to not have empty lines at the top and bottom */ if (draw_offset > 0) @@ -179,31 +250,55 @@ void list_draw(struct screen *display, struct gui_synclist *list) if (start > 0) start--; } - else if (draw_offset < 0) - end++; + else if (draw_offset < 0) { + if(end < list->nb_items) + end++; + } + + /* If the viewport is not an exact multiple of the line height, then + * there will be space for one more partial line. */ + int spare_space = list_text_vp->height - linedes.height * nb_lines; + if(nb_lines < list->nb_items && spare_space > 0 && end < list->nb_items) + if(end < list->nb_items) + end++; #else #define draw_offset 0 #endif /* draw the scrollbar if its needed */ - if (global_settings.scrollbar != SCROLLBAR_OFF) + if (list->scrollbar != SCROLLBAR_OFF) { /* if the scrollbar is shown the text viewport needs to shrink */ if (nb_lines < list->nb_items) { struct viewport vp = *list_text_vp; vp.width = SCROLLBAR_WIDTH; +#ifndef HAVE_TOUCHSCREEN + /* touchscreens must use full viewport height + * due to pixelwise rendering */ vp.height = linedes.height * nb_lines; +#endif list_text_vp->width -= SCROLLBAR_WIDTH; if (scrollbar_in_right) vp.x += list_text_vp->width; else /* left */ list_text_vp->x += SCROLLBAR_WIDTH; struct viewport *last = display->set_viewport(&vp); + +#ifndef HAVE_TOUCHSCREEN + /* button targets go itemwise */ + int scrollbar_items = list->nb_items; + int scrollbar_min = list_start_item; + int scrollbar_max = list_start_item + nb_lines; +#else + /* touchscreens use pixelwise scrolling */ + int scrollbar_items = list->nb_items * linedes.height; + int scrollbar_min = list->y_pos; + int scrollbar_max = list->y_pos + list_text_vp->height; +#endif gui_scrollbar_draw(display, (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height, - list->nb_items, list_start_item, list_start_item + nb_lines, - VERTICAL); + scrollbar_items, scrollbar_min, scrollbar_max, VERTICAL); display->set_viewport(last); } /* shift everything a bit in relation to the title */ @@ -214,6 +309,16 @@ void list_draw(struct screen *display, struct gui_synclist *list) } display->set_viewport(list_text_vp); + int icon_w = list_icon_width(screen); + int character_width = display->getcharwidth(); + + struct list_putlineinfo_t list_info = + { + .x = 0, .y = 0, .vp = list_text_vp, .list = list, + .icon_width = icon_w, .is_title = false, .show_cursor = show_cursor, + .have_icons = have_icons, .linedes = &linedes, .display = display + }; + for (i=start; i<end && i<list->nb_items; i++) { /* do the text */ @@ -221,7 +326,6 @@ void list_draw(struct screen *display, struct gui_synclist *list) unsigned const char *s; char entry_buffer[MAX_PATH]; unsigned char *entry_name; - int text_pos = 0; int line = i - start; int line_indent = 0; int style = STYLE_DEFAULT; @@ -237,17 +341,17 @@ void list_draw(struct screen *display, struct gui_synclist *list) } if (line_indent) { - if (global_settings.show_icons) - line_indent *= list_icon_width(screen); + if (list->show_icons) + line_indent *= icon_w; else - line_indent *= display->getcharwidth(); + line_indent *= character_width; } line_indent += indent; /* position the string at the correct offset place */ int item_width,h; display->getstringsize(entry_name, &item_width, &h); - item_offset = gui_list_get_item_offset(list, item_width, text_pos, + item_offset = gui_list_get_item_offset(list, item_width, indent + (list->show_icons ? icon_w : 0), display, list_text_vp); /* draw the selected line */ @@ -257,8 +361,7 @@ void list_draw(struct screen *display, struct gui_synclist *list) !hide_selection && #endif i >= list->selected_item - && i < list->selected_item + list->selected_size - && list->show_selection_marker) + && i < list->selected_item + list->selected_size) {/* The selected item must be displayed scrolling */ #ifdef HAVE_LCD_COLOR if (list->selection_color) @@ -271,12 +374,12 @@ void list_draw(struct screen *display, struct gui_synclist *list) } else #endif - if (global_settings.cursor_style == 1 + if (list->cursor_style == SYNCLIST_CURSOR_INVERT #ifdef HAVE_REMOTE_LCD /* the global_settings.cursor_style check is here to make * sure if they want the cursor instead of bar it will work */ - || (display->depth < 16 && global_settings.cursor_style) + || (display->depth < 16 && list->cursor_style) #endif ) { @@ -284,14 +387,14 @@ void list_draw(struct screen *display, struct gui_synclist *list) style = STYLE_INVERT; } #ifdef HAVE_LCD_COLOR - else if (global_settings.cursor_style == 2) + else if (list->cursor_style == SYNCLIST_CURSOR_COLOR) { /* Display colour line selector */ style = STYLE_COLORBAR; linedes.text_color = global_settings.lst_color; linedes.line_color = global_settings.lss_color; } - else if (global_settings.cursor_style == 3) + else if (list->cursor_style == SYNCLIST_CURSOR_GRADIENT) { /* Display gradient line selector */ style = STYLE_GRADIENT; @@ -315,27 +418,22 @@ void list_draw(struct screen *display, struct gui_synclist *list) } } #endif - linedes.style = style; linedes.scroll = is_selected ? true : list->scroll_all; linedes.line = i % list->selected_size; icon = list->callback_get_item_icon ? list->callback_get_item_icon(i, list->data) : Icon_NOICON; - /* the list can have both, one of or neither of cursor and item icons, - * if both don't apply icon padding twice between the icons */ - if (show_cursor && have_icons) - put_line(display, 0, line * linedes.height + draw_offset, - &linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t", - line_indent, is_selected ? Icon_Cursor : Icon_NOICON, - icon, item_offset, entry_name); - else if (show_cursor || have_icons) - put_line(display, 0, line * linedes.height + draw_offset, - &linedes, "$*s$"ICON_PADDING_S"I$*t", line_indent, - show_cursor ? (is_selected ? Icon_Cursor:Icon_NOICON):icon, - item_offset, entry_name); - else - put_line(display, 0, line * linedes.height + draw_offset, - &linedes, "$*s$*t", line_indent, item_offset, entry_name); + + + list_info.y = line * linedes.height + draw_offset; + list_info.is_selected = is_selected; + list_info.item_indent = line_indent; + list_info.line = i; + list_info.icon = icon; + list_info.dsp_text = entry_name; + list_info.item_offset = item_offset; + + callback_draw_item(&list_info); } display->set_viewport(parent); display->update_viewport(); @@ -360,21 +458,28 @@ static int scrollbar_scroll(struct gui_synclist * gui_list, int y) const int screen = screens[SCREEN_MAIN].screen_type; const int nb_lines = list_get_nb_lines(gui_list, screen); - if (nb_lines < gui_list->nb_items) + if (nb_lines < gui_list->nb_items) { - /* scrollbar scrolling is still line based */ - int scrollbar_size = nb_lines * gui_list->line_height[screen]; - int actual_y = y - list_text[screen].y; - int new_selection = (actual_y * gui_list->nb_items) / scrollbar_size; + const int line_height = gui_list->line_height[screen]; - int start_item = new_selection - nb_lines/2; - if(start_item < 0) - start_item = 0; - else if(start_item > gui_list->nb_items - nb_lines) - start_item = gui_list->nb_items - nb_lines; + /* try to position the center of the scrollbar at the touch point */ + int scrollbar_size = list_text[screen].height; + int actual_y = y - list_text[screen].y; + int new_y_pos = (actual_y * gui_list->nb_items * line_height) / scrollbar_size; + int new_start = (actual_y * gui_list->nb_items) / scrollbar_size; + + new_start -= nb_lines / 2; + new_y_pos -= (nb_lines * line_height) / 2; + if(new_start < 0) { + new_start = 0; + new_y_pos = 0; + } else if(new_start > gui_list->nb_items - nb_lines) { + new_start = gui_list->nb_items - nb_lines; + new_y_pos = new_start * line_height; + } - gui_list->start_item[screen] = start_item; - gui_list->y_pos = start_item * gui_list->line_height[screen]; + gui_list->start_item[screen] = new_start; + gui_list->y_pos = new_y_pos; return ACTION_REDRAW; } @@ -509,6 +614,7 @@ static bool swipe_scroll(struct gui_synclist * gui_list, int difference) const int old_start = gui_list->start_item[screen]; int new_start_item = -1; int line_diff = 0; + int max_y_pos = gui_list->nb_items * line_height - list_text[screen].height; /* Track whether we hit the end of the list for sake of kinetic scroll */ bool hit_end = true; @@ -517,8 +623,8 @@ static bool swipe_scroll(struct gui_synclist * gui_list, int difference) gui_list->y_pos -= difference; if(gui_list->y_pos < 0) gui_list->y_pos = 0; - else if(gui_list->y_pos > (gui_list->nb_items - nb_lines) * line_height) - gui_list->y_pos = (gui_list->nb_items - nb_lines) * line_height; + else if(gui_list->y_pos > max_y_pos) + gui_list->y_pos = max_y_pos; else hit_end = false; @@ -647,7 +753,7 @@ static int get_click_location(struct gui_synclist *list, int x, int y) if (viewport_point_within_vp(title, x, y)) retval = TITLE_TEXT; /* check the icon too */ - if (list->title_icon != Icon_NOICON && global_settings.show_icons) + if (list->title_icon != Icon_NOICON && list->show_icons) { int width = list_icon_width(screen); struct viewport vp = *title; @@ -665,14 +771,19 @@ static int get_click_location(struct gui_synclist *list, int x, int y) { bool on_scrollbar_clicked; int adj_x = x - parent->x; - switch (global_settings.scrollbar) + switch (list->scrollbar) { + case SCROLLBAR_OFF: + /*fall-through*/ + default: + on_scrollbar_clicked = false; + break; case SCROLLBAR_LEFT: - on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH; break; + on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH; + break; case SCROLLBAR_RIGHT: - on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH); break; - default: - on_scrollbar_clicked = false; break; + on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH); + break; } if (on_scrollbar_clicked) retval = SCROLLBAR; diff --git a/apps/gui/color_picker.c b/apps/gui/color_picker.c index a32f1ee179..ef17c0a230 100644 --- a/apps/gui/color_picker.c +++ b/apps/gui/color_picker.c @@ -154,7 +154,6 @@ static void draw_screen(struct screen *display, char *title, { unsigned text_color = LCD_BLACK; unsigned background_color = LCD_WHITE; - char buf[32]; int i, char_height, line_height; int max_label_width; int text_x, text_top; @@ -253,17 +252,16 @@ static void draw_screen(struct screen *display, char *title, set_drawinfo(display, mode, fg, bg); /* Draw label */ - buf[0] = str(LANG_COLOR_RGB_LABELS)[i]; - buf[1] = '\0'; vp.flags &= ~VP_FLAG_ALIGNMENT_MASK; - display->putsxy(text_x, text_top, buf); + display->putsxyf(text_x, text_top, "%c", str(LANG_COLOR_RGB_LABELS)[i]); /* Draw color value */ + vp.flags |= VP_FLAG_ALIGN_RIGHT; if (display->depth >= 24) - snprintf(buf, 4, "%03d", rgb->rgb_val[i] & 0xFF); + display->putsxyf(text_x, text_top, "%03d", rgb->rgb_val[i] & 0xFF); else - snprintf(buf, 3, "%02d", rgb->rgb_val[i] & 0x3F); - vp.flags |= VP_FLAG_ALIGN_RIGHT; - display->putsxy(text_x, text_top, buf); + display->putsxyf(text_x, text_top, "%02d", rgb->rgb_val[i] & 0x3F); + + /* Draw scrollbar */ gui_scrollbar_draw(display, /* screen */ @@ -280,9 +278,6 @@ static void draw_screen(struct screen *display, char *title, text_top += line_height; } /* end for */ - /* Format RGB: #rrggbb */ - snprintf(buf, sizeof(buf), str(LANG_COLOR_RGB_VALUE), - rgb->red, rgb->green, rgb->blue); vp.flags |= VP_FLAG_ALIGN_CENTER; if (display->depth >= 16) { @@ -301,8 +296,9 @@ static void draw_screen(struct screen *display, char *title, /* Draw RGB: #rrggbb in middle of swatch */ set_drawinfo(display, DRMODE_FG, get_black_or_white(rgb), background_color); - - display->putsxy(0, top + (height - char_height) / 2, buf); + /* Format RGB: #rrggbb */ + display->putsxyf(0, top + (height - char_height) / 2, + str(LANG_COLOR_RGB_VALUE), rgb->red, rgb->green, rgb->blue); /* Draw border around the rect */ set_drawinfo(display, DRMODE_SOLID, text_color, background_color); @@ -318,7 +314,9 @@ static void draw_screen(struct screen *display, char *title, if (height >= char_height) { set_drawinfo(display, DRMODE_SOLID, text_color, background_color); - display->putsxy(0, top + (height - char_height) / 2, buf); + /* Format RGB: #rrggbb */ + display->putsxyf(0, top + (height - char_height) / 2, + str(LANG_COLOR_RGB_VALUE), rgb->red, rgb->green, rgb->blue); } } diff --git a/apps/gui/folder_select.c b/apps/gui/folder_select.c index 706b166941..a76d77562b 100644 --- a/apps/gui/folder_select.c +++ b/apps/gui/folder_select.c @@ -8,6 +8,7 @@ * * Copyright (C) 2012 Jonathan Gordon * Copyright (C) 2012 Thomas Martitz +* * Copyright (C) 2021 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,7 +31,11 @@ #include "language.h" #include "list.h" #include "plugin.h" +#include "splash.h" +/* Define LOGF_ENABLE to enable logf output in this file */ +//#define LOGF_ENABLE +#include "logf.h" /* * Order for changing child states: @@ -56,18 +61,31 @@ struct child { struct folder { char *name; struct child *children; - int children_count; - int depth; - struct folder* previous; + uint16_t children_count; + uint16_t depth; }; static char *buffer_front, *buffer_end; + +static struct +{ + int32_t len; /* keeps count versus maxlen to give buffer full notification */ + uint32_t val; /* hash of all selected items */ + char buf[3];/* address used as identifier -- only \0 written to it */ + char maxlen_exceeded; /*0,1*/ +} hashed; + +static inline void get_hash(const char *key, uint32_t *hash, int len) +{ + *hash = crc_32(key, len, *hash); +} + static char* folder_alloc(size_t size) { char* retval; /* 32-bit aligned */ - size = (size + 3) & ~3; + size = ALIGN_UP(size, 4); if (buffer_front + size > buffer_end) { return NULL; @@ -86,32 +104,57 @@ static char* folder_alloc_from_end(size_t size) buffer_end -= size; return buffer_end; } - -static void get_full_path_r(struct folder *start, char* dst) +#if 0 +/* returns the buffer size required to store the path + \0 */ +static int get_full_pathsz(struct folder *start) { - if (start->previous) - get_full_path_r(start->previous, dst); - - if (start->name && start->name[0] && strcmp(start->name, "/")) + int reql = 0; + struct folder *next = start; + do { - strlcat(dst, "/", MAX_PATH); - strlcat(dst, start->name, MAX_PATH); - } + reql += strlen(next->name) + 1; + } while ((next = next->previous)); + + if (start->name[0] != '/') reql--; + if (--reql < 0) reql = 0; + return reql; } +#endif -static char* get_full_path(struct folder *start) +static size_t get_full_path(struct folder *start, char *dst, size_t dst_sz) { - static char buffer[MAX_PATH]; - - if (strcmp(start->name, "/")) + size_t pos = 0; + struct folder *prev, *cur = NULL, *next = start; + dst[0] = '\0'; /* for strlcat to do its thing */ + /* First traversal R->L mutate nodes->previous to point at child */ + while (next->previous != NULL) /* stop at the root */ { - buffer[0] = 0; - get_full_path_r(start, buffer); +#define PATHMUTATE() \ + ({ \ + prev = cur; \ + cur = next; \ + next = cur->previous;\ + cur->previous = prev; \ + }) + PATHMUTATE(); } - else /* get_full_path_r() does the wrong thing for / */ - return "/"; - - return buffer; + /*swap the next and cur nodes to reverse direction */ + prev = next; + next = cur; + cur = prev; + /* Second traversal L->R mutate nodes->previous to point back at parent + * copy strings to buf as they go by */ + while (next != NULL) + { + PATHMUTATE(); + pos = strlcat(dst, cur->name, dst_sz); + /* do not append slash to paths starting with slash */ + if (cur->name[0] != '/') + pos = strlcat(dst, "/", dst_sz); + } + logf("get_full_path: (%d)[%s]", (int)pos, dst); + return pos; +#undef PATHMUTATE } /* support function for qsort() */ @@ -125,49 +168,52 @@ static int compare(const void* p1, const void* p2) static struct folder* load_folder(struct folder* parent, char *folder) { DIR *dir; - char* path = get_full_path(parent); char fullpath[MAX_PATH]; + struct dirent *entry; - struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder)); int child_count = 0; char *first_child = NULL; + size_t len = 0; - if (!strcmp(folder,"/")) - strlcpy(fullpath, folder, 2); - else - snprintf(fullpath, MAX_PATH, "%s/%s", parent ? path : "", folder); + struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder)); + if (this == NULL) + goto fail; + + if (parent) + { + len = get_full_path(parent, fullpath, sizeof(fullpath)); + if (len >= sizeof(fullpath)) + goto fail; + } + strmemccpy(&fullpath[len], folder, sizeof(fullpath) - len); + logf("load_folder: [%s]", fullpath); - if (!this) - return NULL; dir = opendir(fullpath); - if (!dir) - return NULL; + if (dir == NULL) + goto fail; this->previous = parent; this->name = folder; this->children = NULL; this->children_count = 0; - this->depth = parent ? parent->depth + 1 : 0; + if (parent) + this->depth = parent->depth + 1; while ((entry = readdir(dir))) { - int len = strlen((char *)entry->d_name); - struct dirinfo info; - - info = dir_get_info(dir, entry); - /* skip anything not a directory */ - if ((info.attribute & ATTR_DIRECTORY) == 0) { + if ((dir_get_info(dir, entry).attribute & ATTR_DIRECTORY) == 0) { continue; } - /* skip directories . and .. */ - if ((!strcmp((char *)entry->d_name, ".")) || - (!strcmp((char *)entry->d_name, ".."))) { + /* skip . and .. */ + char *dn = entry->d_name; + if ((dn[0] == '.') && (dn[1] == '\0' || (dn[1] == '.' && dn[2] == '\0'))) continue; - } - char *name = folder_alloc_from_end(len+1); - if (!name) + /* copy entry name to end of buffer, save pointer */ + len = strlen((char *)entry->d_name); + char *name = folder_alloc_from_end(len+1); /*for NULL*/ + if (name == NULL) { closedir(dir); - return NULL; + goto fail; } memcpy(name, (char *)entry->d_name, len+1); child_count++; @@ -177,26 +223,29 @@ static struct folder* load_folder(struct folder* parent, char *folder) /* now put the names in the array */ this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count); - if (!this->children) - return NULL; + if (this->children == NULL) + goto fail; + while (child_count) { - this->children[this->children_count].name = first_child; - this->children[this->children_count].folder = NULL; - this->children[this->children_count].state = COLLAPSED; - this->children_count++; - first_child += strlen(first_child) + 1; + struct child *child = &this->children[this->children_count++]; + child->name = first_child; + child->folder = NULL; + child->state = COLLAPSED; + while(*first_child++ != '\0'){};/* move to next name entry */ child_count--; } qsort(this->children, this->children_count, sizeof(struct child), compare); return this; +fail: + return NULL; } struct folder* load_root(void) { static struct child root_child; - + /* reset the root for each call */ root_child.name = "/"; root_child.folder = NULL; root_child.state = COLLAPSED; @@ -205,7 +254,7 @@ struct folder* load_root(void) .name = "", .children = &root_child, .children_count = 1, - .depth = -1, + .depth = 0, .previous = NULL, }; @@ -230,7 +279,6 @@ static int count_items(struct folder *start) static struct child* find_index(struct folder *start, int index, struct folder **parent) { int i = 0; - *parent = NULL; while (i < start->children_count) @@ -262,22 +310,22 @@ static const char * folder_get_name(int selected_item, void * data, struct folder *parent; struct child *this = find_index(root, selected_item , &parent); - buffer[0] = '\0'; - - if (parent->depth >= 0) - for(int i = 0; i <= parent->depth; i++) - strcat(buffer, "\t"); - + char *buf = buffer; + if ((int)buffer_len > parent->depth) + { + int i = parent->depth; + while(--i > 0) /* don't indent the parent /folders */ + *buf++ = '\t'; + } + *buf = '\0'; strlcat(buffer, this->name, buffer_len); if (this->state == EACCESS) { /* append error message to the entry if unaccessible */ - size_t len = strlcat(buffer, " (", buffer_len); + size_t len = strlcat(buffer, " ( ", buffer_len); if (buffer_len > len) { - snprintf(&buffer[len], buffer_len - len, str(LANG_READ_FAILED), - this->name); - strlcat(buffer, ")", buffer_len); + snprintf(&buffer[len], buffer_len - len, str(LANG_READ_FAILED), ")"); } } @@ -304,6 +352,23 @@ static enum themable_icons folder_get_icon(int selected_item, void * data) return Icon_NOICON; } +static int child_set_state_expand(struct child *this, struct folder *parent) +{ + int newstate = EACCESS; + if (this->folder == NULL) + this->folder = load_folder(parent, this->name); + + if (this->folder != NULL) + { + if(this->folder->children_count == 0) + newstate = SELECTED; + else + newstate = EXPANDED; + } + this->state = newstate; + return newstate; +} + static int folder_action_callback(int action, struct gui_synclist *list) { struct folder *root = (struct folder*)list->data; @@ -322,17 +387,13 @@ static int folder_action_callback(int action, struct gui_synclist *list) this->state = COLLAPSED; break; case COLLAPSED: - if (this->folder == NULL) - this->folder = load_folder(parent, this->name); - this->state = this->folder ? (this->folder->children_count == 0 ? - SELECTED : EXPANDED) : EACCESS; + child_set_state_expand(this, parent); break; case EACCESS: /* cannot open, do nothing */ return action; } - list->nb_items = count_items(root); - return ACTION_REDRAW; + action = ACTION_REDRAW; } else if (action == ACTION_STD_CONTEXT) { @@ -342,140 +403,198 @@ static int folder_action_callback(int action, struct gui_synclist *list) for (i = 0; i < this->folder->children_count; i++) { child = &this->folder->children[i]; - if (child->state == SELECTED || - child->state == EXPANDED) - child->state = COLLAPSED; - else if (child->state == COLLAPSED) - child->state = SELECTED; + switch (child->state) + { + case SELECTED: + case EXPANDED: + child->state = COLLAPSED; + break; + case COLLAPSED: + child->state = SELECTED; + break; + case EACCESS: + break; + } } break; case SELECTED: case COLLAPSED: - if (this->folder == NULL) - this->folder = load_folder(parent, this->name); - this->state = this->folder ? (this->folder->children_count == 0 ? - SELECTED : EXPANDED) : EACCESS; - if (this->state == EACCESS) - break; - for (i = 0; i < this->folder->children_count; i++) + if (child_set_state_expand(this, parent) != EACCESS) { - child = &this->folder->children[i]; - child->state = SELECTED; + for (i = 0; i < (this->folder->children_count); i++) + { + child = &this->folder->children[i]; + child->state = SELECTED; + } } break; case EACCESS: /* cannot open, do nothing */ return action; } - list->nb_items = count_items(root); - return ACTION_REDRAW; + action = ACTION_REDRAW; } - - + if (action == ACTION_REDRAW) + list->nb_items = count_items(root); return action; } -static struct child* find_from_filename(char* filename, struct folder *root) +static struct child* find_from_filename(const char* filename, struct folder *root) { - char *slash = strchr(filename, '/'); - int i = 0; - if (slash) - *slash = '\0'; if (!root) return NULL; - + const char *slash = strchr(filename, '/'); struct child *this; /* filenames beginning with a / are specially treated as the * loop below can't handle them. they can only occur on the first, * and not recursive, calls to this function.*/ - if (slash == filename) + if (filename[0] == '/') /* in the loop nothing starts with '/' */ { + logf("find_from_filename [%s]", filename); /* filename begins with /. in this case root must be the * top level folder */ this = &root->children[0]; - if (!slash[1]) + if (filename[1] == '\0') { /* filename == "/" */ return this; } - else - { - /* filename == "/XXX/YYY". cascade down */ - if (!this->folder) - this->folder = load_folder(root, this->name); - this->state = EXPANDED; - /* recurse with XXX/YYY */ - return find_from_filename(slash+1, this->folder); - } + else /* filename == "/XXX/YYY". cascade down */ + goto cascade; } - while (i < root->children_count) + for (int i = 0; i < root->children_count; i++) { this = &root->children[i]; - if (!strcasecmp(this->name, filename)) + /* when slash == NULL n will be really large but \0 stops the compare */ + if (strncasecmp(this->name, filename, slash - filename) == 0) { - if (!slash) + if (slash == NULL) { /* filename == XXX */ return this; } else - { - /* filename == XXX/YYY. cascade down */ - if (!this->folder) - this->folder = load_folder(root, this->name); - this->state = EXPANDED; - return find_from_filename(slash+1, this->folder); - } + goto cascade; } - i++; } return NULL; + +cascade: + /* filename == XXX/YYY. cascade down */ + child_set_state_expand(this, root); + while (slash[0] == '/') slash++; /* eat slashes */ + return find_from_filename(slash, this->folder); } -/* _modifies_ buf */ -int select_paths(struct folder* root, char* buf) +static int select_paths(struct folder* root, const char* filenames) { - struct child *item = find_from_filename(buf, root); - if (item) - item->state = SELECTED; + /* Takes a list of filenames in a ':' delimited string + splits filenames at the ':' character loads each into buffer + selects each file in the folder list + + if last item or only item the rest of the string is copied to the buffer + *End the last item WITHOUT the ':' character /.rockbox/eqs:/.rockbox/wps\0* + */ + char buf[MAX_PATH]; + const int buflen = sizeof(buf); + + const char *fnp = filenames; + const char *lastfnp = fnp; + const char *sstr; + off_t len; + + while (fnp) + { + fnp = strchr(fnp, ':'); + if (fnp) + { + len = fnp - lastfnp; + fnp++; + } + else /* no ':' get the rest of the string */ + len = strlen(lastfnp); + + sstr = lastfnp; + lastfnp = fnp; + if (len <= 0 || len + 1 >= buflen) + continue; + strmemccpy(buf, sstr, len + 1); + struct child *item = find_from_filename(buf, root); + if (item) + item->state = SELECTED; + } + return 0; } -static void save_folders_r(struct folder *root, char* dst, size_t maxlen) +static void save_folders_r(struct folder *root, char* dst, size_t maxlen, size_t buflen) { - int i = 0; + size_t len; + struct folder *curfolder; + char* name; - while (i < root->children_count) + for (int i = 0; i < root->children_count; i++) { struct child *this = &root->children[i]; if (this->state == SELECTED) { - if (this->folder) - snprintf(buffer_front, buffer_end - buffer_front, - "%s:", get_full_path(this->folder)); + if (this->folder == NULL) + { + curfolder = root; + name = this->name; + logf("save_folders_r: this->name[%s]", name); + } else { - char *p = get_full_path(root); - snprintf(buffer_front, buffer_end - buffer_front, - "%s/%s:", strcmp(p, "/") ? p : "", - strcmp(this->name, "/") ? this->name : ""); + curfolder = this->folder->previous; + name = this->folder->name; + logf("save_folders_r: this->folder->name[%s]", name); + } + + len = get_full_path(curfolder, buffer_front, buflen); + + if (len + 2 >= buflen) + continue; + + len += snprintf(&buffer_front[len], buflen - len, "%s:", name); + logf("save_folders_r: [%s]", buffer_front); + if (dst != hashed.buf) + { + int dlen = strlen(dst); + if (dlen + len >= maxlen) + continue; + strmemccpy(&dst[dlen], buffer_front, maxlen - dlen); + } + else + { + if (hashed.len + len >= maxlen) + { + hashed.maxlen_exceeded = 1; + continue; + } + get_hash(buffer_front, &hashed.val, len); + hashed.len += len; } - strlcat(dst, buffer_front, maxlen); } else if (this->state == EXPANDED) - save_folders_r(this->folder, dst, maxlen); - i++; + save_folders_r(this->folder, dst, maxlen, buflen); } } -static void save_folders(struct folder *root, char* dst, size_t maxlen) +static uint32_t save_folders(struct folder *root, char* dst, size_t maxlen) { - int len; + hashed.len = 0; + hashed.val = 0; + hashed.maxlen_exceeded = 0; + size_t len = buffer_end - buffer_front; dst[0] = '\0'; - save_folders_r(root, dst, maxlen); + save_folders_r(root, dst, maxlen, len); len = strlen(dst); /* fix trailing ':' */ if (len > 1) dst[len-1] = '\0'; + /*Notify - user will probably not see save dialog if nothing new got added*/ + if (hashed.maxlen_exceeded > 0) splash(HZ *2, ID2P(LANG_SHOWDIR_BUFFER_FULL)); + return hashed.val; } bool folder_select(char* setting, int setting_len) @@ -483,40 +602,32 @@ bool folder_select(char* setting, int setting_len) struct folder *root; struct simplelist_info info; size_t buf_size; - /* 32 separate folders should be Enough For Everybody(TM) */ - char *vect[32]; - char copy[setting_len]; - int nb_items; - - /* copy onto stack as split_string() modifies it */ - strlcpy(copy, setting, setting_len); - nb_items = split_string(copy, ':', vect, ARRAYLEN(vect)); buffer_front = plugin_get_buffer(&buf_size); buffer_end = buffer_front + buf_size; + logf("%d bytes free", (int)(buffer_end - buffer_front)); root = load_root(); - if (nb_items > 0) - { - for(int i = 0; i < nb_items; i++) - select_paths(root, vect[i]); - } - + logf("folders in: %s", setting); + /* Load previous selection(s) */ + select_paths(root, setting); + /* get current hash to check for changes later */ + uint32_t hash = save_folders(root, hashed.buf, setting_len); simplelist_info_init(&info, str(LANG_SELECT_FOLDER), count_items(root), root); info.get_name = folder_get_name; info.action_callback = folder_action_callback; info.get_icon = folder_get_icon; simplelist_show_list(&info); - + logf("%d bytes free", (int)(buffer_end - buffer_front)); /* done editing. check for changes */ - save_folders(root, copy, setting_len); - if (strcmp(copy, setting)) - { /* prompt for saving changes and commit if yes */ + if (hash != save_folders(root, hashed.buf, setting_len)) + { /* prompt for saving changes and commit if yes */ if (yesno_pop(ID2P(LANG_SAVE_CHANGES))) { - strcpy(setting, copy); + save_folders(root, setting, setting_len); settings_save(); + logf("folders out: %s", setting); return true; } } diff --git a/apps/gui/icon.c b/apps/gui/icon.c index 9fe7090f4a..7a59a72151 100644 --- a/apps/gui/icon.c +++ b/apps/gui/icon.c @@ -32,6 +32,7 @@ #include "bmp.h" #include "filetypes.h" #include "language.h" +#include "misc.h" #include "bitmaps/default_icons.h" #if defined(HAVE_REMOTE_LCD) && (NB_SCREENS > 1) @@ -63,7 +64,6 @@ static struct iconset { struct bitmap bmp; bool loaded; int handle; - int handle_locked; } iconsets[Iconset_Count][NB_SCREENS]; #define ICON_HEIGHT(screen) (!iconsets[Iconset_user][screen].loaded ? \ @@ -160,8 +160,6 @@ static int buflib_move_callback(int handle, void* current, void* new) struct iconset *set = &iconsets[i][j]; if (set->bmp.data == current) { - if (set->handle_locked > 0) - return BUFLIB_CB_CANNOT_MOVE; set->bmp.data = new; return BUFLIB_CB_OK; } @@ -169,59 +167,27 @@ static int buflib_move_callback(int handle, void* current, void* new) } return BUFLIB_CB_OK; } -static struct buflib_callbacks buflib_ops = {buflib_move_callback, NULL, NULL}; static void load_icons(const char* filename, enum Iconset iconset, enum screen_type screen) { - ssize_t size_read; - ssize_t buf_size; - int fd; - int bmpformat = (FORMAT_ANY|FORMAT_DITHER|FORMAT_TRANSPARENT); + static struct buflib_callbacks buflib_ops = {buflib_move_callback, NULL, NULL}; + const int bmpformat = (FORMAT_ANY|FORMAT_DITHER|FORMAT_TRANSPARENT); struct iconset *ic = &iconsets[iconset][screen]; + ssize_t buf_reqd; ic->loaded = false; - ic->handle = 0; + ic->handle = CLB_ALOC_ERR; if (filename[0] && filename[0] != '-') { - char path[MAX_PATH]; - - snprintf(path, sizeof(path), ICON_DIR "/%s.bmp", filename); - fd = open(path, O_RDONLY); - if (fd < 0) - return; - buf_size = read_bmp_fd(fd, &ic->bmp, 0, - bmpformat|FORMAT_RETURN_SIZE, NULL); - if (buf_size > 0) - ic->handle = core_alloc_ex(filename, (size_t) buf_size, &buflib_ops); - - if (ic->handle <= 0) - { - /* error */ - goto finished; - } - lseek(fd, 0, SEEK_SET); - ic->bmp.data = core_get_data(ic->handle); - - ic->handle_locked = 1; - size_read = read_bmp_fd(fd, &ic->bmp, buf_size, bmpformat, NULL); - ic->handle_locked = 0; - - if (size_read < 0) + char fname[MAX_PATH]; + snprintf(fname, sizeof(fname), ICON_DIR "/%s.bmp", filename); + ic->handle = core_load_bmp(fname, &ic->bmp, bmpformat, &buf_reqd, &buflib_ops); + if (ic->handle != CLB_ALOC_ERR) { - /* error */ - size_read = 0; - } - /* free unused alpha channel, if any */ - core_shrink(ic->handle, ic->bmp.data, size_read); - - if (size_read == 0) - ic->handle = core_free(ic->handle); - else + ic->bmp.data = core_get_data(ic->handle); ic->loaded = true; -finished: - close(fd); - return; + } } } diff --git a/apps/gui/line.c b/apps/gui/line.c index d319ff3c51..7e84aa7b31 100644 --- a/apps/gui/line.c +++ b/apps/gui/line.c @@ -264,8 +264,7 @@ next: else { /* any other character here is an erroneous format string */ - snprintf(tempbuf, sizeof(tempbuf), "<E:%c>", ch); - display->putsxy(xpos, y, tempbuf); + display->putsxyf(xpos, y, "<E:%c>", ch); /* Don't consider going forward, fix the caller */ return; } diff --git a/apps/gui/list.c b/apps/gui/list.c index 13a850bd7b..652279a9de 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -47,12 +47,6 @@ */ #define FRAMEDROP_TRIGGER 6 -static int offset_step = 16; /* pixels per screen scroll step */ -/* should lines scroll out of the screen */ -static bool offset_out_of_view = false; - -static void gui_list_select_at_offset(struct gui_synclist * gui_list, - int offset); void list_draw(struct screen *display, struct gui_synclist *list); static long last_dirty_tick; @@ -129,6 +123,18 @@ void list_init_item_height(struct gui_synclist *list, enum screen_type screen) #endif } +static void gui_synclist_init_display_settings(struct gui_synclist * list) +{ + struct user_settings *gs = &global_settings; + list->scrollbar = gs->scrollbar; + list->show_icons = gs->show_icons; + list->scroll_paginated = gs->scroll_paginated; + list->keyclick = gs->keyclick; + list->talk_menu = gs->talk_menu; + list->wraparound = gs->list_wraparound; + list->cursor_style = gs->cursor_style; +} + /* * Initializes a scrolling list * - gui_list : the list structure to initialize @@ -150,8 +156,11 @@ void gui_synclist_init(struct gui_synclist * gui_list, gui_list->callback_get_item_icon = NULL; gui_list->callback_get_item_name = callback_get_item_name; gui_list->callback_speak_item = NULL; + gui_list->callback_draw_item = NULL; gui_list->nb_items = 0; gui_list->selected_item = 0; + gui_synclist_init_display_settings(gui_list); + #ifdef HAVE_TOUCHSCREEN gui_list->y_pos = 0; #endif @@ -167,7 +176,6 @@ void gui_synclist_init(struct gui_synclist * gui_list, list_init_viewports(gui_list); FOR_NB_SCREENS(i) list_init_item_height(gui_list, i); - gui_list->limit_scroll = false; gui_list->data = data; gui_list->scroll_all = scroll_all; gui_list->selected_size = selected_size; @@ -176,7 +184,6 @@ void gui_synclist_init(struct gui_synclist * gui_list, gui_list->scheduled_talk_tick = gui_list->last_talked_tick = 0; gui_list->dirty_tick = current_tick; - gui_list->show_selection_marker = true; #ifdef HAVE_LCD_COLOR gui_list->title_color = -1; @@ -185,13 +192,6 @@ void gui_synclist_init(struct gui_synclist * gui_list, #endif } -/* this toggles the selection bar or cursor */ -void gui_synclist_hide_selection_marker(struct gui_synclist * lists, bool hide) -{ - lists->show_selection_marker = !hide; -} - - int gui_list_get_item_offset(struct gui_synclist * gui_list, int item_width, int text_pos, @@ -200,7 +200,7 @@ int gui_list_get_item_offset(struct gui_synclist * gui_list, { int item_offset; - if (offset_out_of_view) + if (global_settings.offset_out_of_view) { item_offset = gui_list->offset_position[display->screen_type]; } @@ -254,15 +254,11 @@ static void gui_list_put_selection_on_screen(struct gui_synclist * gui_list, const int scroll_limit_up = (nb_lines < gui_list->selected_size+2 ? 0:1); const int scroll_limit_down = (scroll_limit_up+gui_list->selected_size); - if (gui_list->show_selection_marker == false) - { - new_start_item = gui_list->selected_item; - } - else if (gui_list->selected_size >= nb_lines) + if (gui_list->selected_size >= nb_lines) { new_start_item = gui_list->selected_item; } - else if (global_settings.scroll_paginated) + else if (gui_list->scroll_paginated) { nb_lines -= nb_lines%gui_list->selected_size; if (difference < 0 || difference >= nb_lines) @@ -292,7 +288,7 @@ static void gui_list_put_selection_on_screen(struct gui_synclist * gui_list, static void edge_beep(struct gui_synclist * gui_list, bool wrap) { - if (global_settings.keyclick) + if (gui_list->keyclick) { list_speak_item *cb = gui_list->callback_speak_item; if (!wrap) /* a bounce */ @@ -355,7 +351,7 @@ static void _gui_synclist_speak_item(struct gui_synclist *lists) void gui_synclist_speak_item(struct gui_synclist *lists) { - if (global_settings.talk_menu) + if (lists->talk_menu) { if (lists->nb_items == 0) talk_id(VOICE_EMPTY_LIST, true); @@ -383,7 +379,7 @@ void gui_synclist_select_item(struct gui_synclist * gui_list, int item_number) } static void gui_list_select_at_offset(struct gui_synclist * gui_list, - int offset) + int offset, bool allow_wrap) { int new_selection; if (gui_list->selected_size > 1) @@ -395,41 +391,15 @@ static void gui_list_select_at_offset(struct gui_synclist * gui_list, if (new_selection >= gui_list->nb_items) { - new_selection = gui_list->limit_scroll ? - gui_list->nb_items - gui_list->selected_size : 0; - edge_beep(gui_list, !gui_list->limit_scroll); + new_selection = allow_wrap ? 0 : gui_list->nb_items - gui_list->selected_size; + edge_beep(gui_list, allow_wrap); } else if (new_selection < 0) { - new_selection = gui_list->limit_scroll ? - 0 : gui_list->nb_items - gui_list->selected_size; - edge_beep(gui_list, !gui_list->limit_scroll); + new_selection = allow_wrap ? gui_list->nb_items - gui_list->selected_size : 0; + edge_beep(gui_list, allow_wrap); } - else if (gui_list->show_selection_marker == false) - { - FOR_NB_SCREENS(i) - { - int nb_lines = list_get_nb_lines(gui_list, i); - if (offset > 0) - { - int screen_top = MAX(0, gui_list->nb_items - nb_lines); - gui_list->start_item[i] = MIN(screen_top, gui_list->start_item[i] + - gui_list->selected_size); - gui_list->selected_item = gui_list->start_item[i]; - } - else - { - gui_list->start_item[i] = MAX(0, gui_list->start_item[i] - - gui_list->selected_size); - gui_list->selected_item = gui_list->start_item[i] + nb_lines; - } -#ifdef HAVE_TOUCHSCREEN - gui_list->y_pos = gui_list->start_item[SCREEN_MAIN] * gui_list->line_height[SCREEN_MAIN]; -#endif - } - return; - } gui_synclist_select_item(gui_list, new_selection); } @@ -460,22 +430,12 @@ void gui_synclist_del_item(struct gui_synclist * gui_list) } } -void gui_list_screen_scroll_step(int ofs) -{ - offset_step = ofs; -} - -void gui_list_screen_scroll_out_of_view(bool enable) -{ - offset_out_of_view = enable; -} - /* * Set the title and title icon of the list. Setting title to NULL disables * both the title and icon. Use NOICON if there is no icon. */ void gui_synclist_set_title(struct gui_synclist * gui_list, - char * title, enum themable_icons icon) + const char * title, enum themable_icons icon) { gui_list->title = title; gui_list->title_icon = icon; @@ -543,26 +503,25 @@ void gui_synclist_set_sel_color(struct gui_synclist * lists, #endif static void gui_synclist_select_next_page(struct gui_synclist * lists, - enum screen_type screen) + enum screen_type screen, + bool allow_wrap) { int nb_lines = list_get_nb_lines(lists, screen); if (lists->selected_size > 1) nb_lines = MAX(1, nb_lines/lists->selected_size); - gui_list_select_at_offset(lists, nb_lines); + + gui_list_select_at_offset(lists, nb_lines, allow_wrap); } static void gui_synclist_select_previous_page(struct gui_synclist * lists, - enum screen_type screen) + enum screen_type screen, + bool allow_wrap) { int nb_lines = list_get_nb_lines(lists, screen); if (lists->selected_size > 1) nb_lines = MAX(1, nb_lines/lists->selected_size); - gui_list_select_at_offset(lists, -nb_lines); -} -void gui_synclist_limit_scroll(struct gui_synclist * lists, bool scroll) -{ - lists->limit_scroll = scroll; + gui_list_select_at_offset(lists, -nb_lines, allow_wrap); } /* @@ -577,7 +536,7 @@ static void gui_synclist_scroll_right(struct gui_synclist * lists) /* FIXME: This is a fake right boundry limiter. there should be some * callback function to find the longest item on the list in pixels, * to stop the list from scrolling past that point */ - lists->offset_position[i] += offset_step; + lists->offset_position[i] += global_settings.screen_scroll_step; if (lists->offset_position[i] > 1000) lists->offset_position[i] = 1000; } @@ -591,7 +550,7 @@ static void gui_synclist_scroll_left(struct gui_synclist * lists) { FOR_NB_SCREENS(i) { - lists->offset_position[i] -= offset_step; + lists->offset_position[i] -= global_settings.screen_scroll_step; if (lists->offset_position[i] < 0) lists->offset_position[i] = 0; } @@ -601,16 +560,22 @@ bool gui_synclist_keyclick_callback(int action, void* data) { struct gui_synclist *lists = (struct gui_synclist *)data; - /* block the beep if we are at the end of the list and we are not wrapping. - * CAVEAT: mosts lists don't set limit_scroll untill it sees a repeat - * press at the end of the list so this can cause an extra beep. - */ - if (lists->limit_scroll == false) - return true; + /* Block the beep if we're at the end of the list and we're not wrapping. */ if (lists->selected_item == 0) - return (action != ACTION_STD_PREV && action != ACTION_STD_PREVREPEAT); + { + if (action == ACTION_STD_PREVREPEAT) + return false; + if (action == ACTION_STD_PREV && !lists->wraparound) + return false; + } + if (lists->selected_item == lists->nb_items - lists->selected_size) - return (action != ACTION_STD_NEXT && action != ACTION_STD_NEXTREPEAT); + { + if (action == ACTION_STD_NEXTREPEAT) + return false; + if (action == ACTION_STD_NEXT && !lists->wraparound) + return false; + } return action != ACTION_NONE; } @@ -635,8 +600,7 @@ static void _lists_uiviewport_update_callback(unsigned short id, void *data) gui_synclist_draw(current_lists); } -bool gui_synclist_do_button(struct gui_synclist * lists, - int *actionptr, enum list_wrap wrap) +bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr) { int action = *actionptr; static bool pgleft_allow_cancel = false; @@ -683,23 +647,13 @@ bool gui_synclist_do_button(struct gui_synclist * lists, /* Disable the skin redraw callback */ current_lists = NULL; - switch (wrap) - { - case LIST_WRAP_ON: - gui_synclist_limit_scroll(lists, false); - break; - case LIST_WRAP_OFF: - gui_synclist_limit_scroll(lists, true); - break; - case LIST_WRAP_UNLESS_HELD: - if (action == ACTION_STD_PREVREPEAT || - action == ACTION_STD_NEXTREPEAT || - action == ACTION_LISTTREE_PGUP || - action == ACTION_LISTTREE_PGDOWN) - gui_synclist_limit_scroll(lists, true); - else gui_synclist_limit_scroll(lists, false); - break; - }; + /* Prevent list wraparound by repeating actions */ + bool allow_wrap = lists->wraparound; + if (action == ACTION_STD_PREVREPEAT || + action == ACTION_STD_NEXTREPEAT || + action == ACTION_LISTTREE_PGUP || + action == ACTION_LISTTREE_PGDOWN) + allow_wrap = false; switch (action) { @@ -709,17 +663,15 @@ bool gui_synclist_do_button(struct gui_synclist * lists, #ifdef HAVE_VOLUME_IN_LIST case ACTION_LIST_VOLUP: - global_settings.volume += sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(1); return true; case ACTION_LIST_VOLDOWN: - global_settings.volume -= sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(-1); return true; #endif case ACTION_STD_PREV: case ACTION_STD_PREVREPEAT: - gui_list_select_at_offset(lists, -next_item_modifier); + gui_list_select_at_offset(lists, -next_item_modifier, allow_wrap); #ifndef HAVE_WHEEL_ACCELERATION if (button_queue_count() < FRAMEDROP_TRIGGER) #endif @@ -730,7 +682,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, case ACTION_STD_NEXT: case ACTION_STD_NEXTREPEAT: - gui_list_select_at_offset(lists, next_item_modifier); + gui_list_select_at_offset(lists, next_item_modifier, allow_wrap); #ifndef HAVE_WHEEL_ACCELERATION if (button_queue_count() < FRAMEDROP_TRIGGER) #endif @@ -754,7 +706,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, if (lists->offset_position[0] == 0) { pgleft_allow_cancel = true; - *actionptr = ACTION_STD_CANCEL; + *actionptr = ACTION_STD_MENU; return true; } *actionptr = ACTION_TREE_PGLEFT; @@ -781,7 +733,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, SCREEN_REMOTE : #endif SCREEN_MAIN; - gui_synclist_select_previous_page(lists, screen); + gui_synclist_select_previous_page(lists, screen, allow_wrap); gui_synclist_draw(lists); yield(); *actionptr = ACTION_STD_NEXT; @@ -796,7 +748,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, SCREEN_REMOTE : #endif SCREEN_MAIN; - gui_synclist_select_next_page(lists, screen); + gui_synclist_select_next_page(lists, screen, allow_wrap); gui_synclist_draw(lists); yield(); *actionptr = ACTION_STD_PREV; @@ -835,8 +787,7 @@ int list_do_action_timeout(struct gui_synclist *lists, int timeout) } bool list_do_action(int context, int timeout, - struct gui_synclist *lists, int *action, - enum list_wrap wrap) + struct gui_synclist *lists, int *action) /* Combines the get_action() (with possibly overridden timeout) and gui_synclist_do_button() calls. Returns the list action from do_button, and places the action from get_action in *action. */ @@ -844,14 +795,7 @@ bool list_do_action(int context, int timeout, timeout = list_do_action_timeout(lists, timeout); keyclick_set_callback(gui_synclist_keyclick_callback, lists); *action = get_action(context, timeout); - return gui_synclist_do_button(lists, action, wrap); -} - -bool gui_synclist_item_is_onscreen(struct gui_synclist *lists, - enum screen_type screen, int item) -{ - int nb_lines = list_get_nb_lines(lists, screen); - return (unsigned)(item - lists->start_item[screen]) < (unsigned) nb_lines; + return gui_synclist_do_button(lists, action); } /* Simple use list implementation */ @@ -911,7 +855,6 @@ bool simplelist_show_list(struct simplelist_info *info) struct gui_synclist lists; int action, old_line_count = simplelist_line_count; list_get_name *getname; - int wrap = LIST_WRAP_UNLESS_HELD; if (info->get_name) getname = info->get_name; else @@ -936,12 +879,6 @@ bool simplelist_show_list(struct simplelist_info *info) gui_synclist_set_sel_color(&lists, info->selection_color); #endif - if (info->hide_selection) - { - gui_synclist_hide_selection_marker(&lists, true); - wrap = LIST_WRAP_OFF; - } - if (info->action_callback) info->action_callback(ACTION_REDRAW, &lists); @@ -960,8 +897,7 @@ bool simplelist_show_list(struct simplelist_info *info) while(1) { - list_do_action(CONTEXT_LIST, info->timeout, - &lists, &action, wrap); + list_do_action(CONTEXT_LIST, info->timeout, &lists, &action); /* We must yield in this case or no other thread can run */ if (info->timeout == TIMEOUT_NOBLOCK) @@ -1025,7 +961,6 @@ void simplelist_info_init(struct simplelist_info *info, char* title, info->title = title; info->count = count; info->selection_size = 1; - info->hide_selection = false; info->scroll_all = false; info->hide_theme = false; info->speak_onshow = true; diff --git a/apps/gui/list.h b/apps/gui/list.h index 64ff3e3fdd..40a27d1061 100644 --- a/apps/gui/list.h +++ b/apps/gui/list.h @@ -30,10 +30,12 @@ #define SCROLLBAR_WIDTH global_settings.scrollbar_width -enum list_wrap { - LIST_WRAP_ON = 0, - LIST_WRAP_OFF, - LIST_WRAP_UNLESS_HELD, +enum synclist_cursor +{ + SYNCLIST_CURSOR_NOSTYLE = 0, + SYNCLIST_CURSOR_INVERT, + SYNCLIST_CURSOR_COLOR, + SYNCLIST_CURSOR_GRADIENT, }; /* @@ -69,6 +71,35 @@ typedef enum themable_icons list_get_icon(int selected_item, void * data); typedef const char * list_get_name(int selected_item, void * data, char * buffer, size_t buffer_len); /* + * Draw callback + * - display : functions supplied depends on the screen call originated from (typ: MAIN) + * - list_info : a pointer to an internal struct containing item display information + */ +/* owner drawn lists need to know this info */ +struct list_putlineinfo_t { + int x; + int y; + int item_indent; + int item_offset; + int line; + + int icon; + int icon_width; + + struct screen *display; + struct viewport *vp; + struct line_desc *linedes; + struct gui_synclist * list; + const char *dsp_text; + + bool is_selected; + bool is_title; + bool show_cursor; + bool have_icons; +}; + +typedef void list_draw_item(struct list_putlineinfo_t *list_info); +/* * Voice callback * - selected_item : an integer that tells the number of the item to speak * - data : a void pointer to the data you gave to the list when you @@ -110,14 +141,20 @@ struct list_selection_color struct gui_synclist { - /* defines wether the list should stop when reaching the top/bottom - * or should continue (by going to bottom/top) */ - bool limit_scroll; - /* wether the text of the whole items of the list have to be + /*flags to hold settings show: icons, scrollbar etc..*/ + int scrollbar; + int cursor_style; + bool show_icons; + bool keyclick; + bool talk_menu; + bool wraparound; + bool scroll_paginated; + /* whether the text of the whole items of the list have to be * scrolled or only for the selected item */ bool scroll_all; int nb_items; int selected_item; + #ifdef HAVE_TOUCHSCREEN /* absolute Y coordinate, used for smooth scrolling */ int y_pos; @@ -133,14 +170,14 @@ struct gui_synclist list_get_icon *callback_get_item_icon; list_get_name *callback_get_item_name; list_speak_item *callback_speak_item; + list_draw_item *callback_draw_item; /* The data that will be passed to the callback function YOU implement */ void * data; /* The optional title, set to NULL for none */ - char * title; + const char * title; /* Optional title icon */ enum themable_icons title_icon; - bool show_selection_marker; /* set to true by default */ #ifdef HAVE_LCD_COLOR int title_color; @@ -152,11 +189,6 @@ struct gui_synclist extern void list_init(void); -/* parse global setting to static int */ -extern void gui_list_screen_scroll_step(int ofs); - -/* parse global setting to static bool */ -extern void gui_list_screen_scroll_out_of_view(bool enable); extern void gui_synclist_init( struct gui_synclist * lists, @@ -185,13 +217,8 @@ extern void gui_synclist_select_item(struct gui_synclist * lists, int item_number); extern void gui_synclist_add_item(struct gui_synclist * lists); extern void gui_synclist_del_item(struct gui_synclist * lists); -extern void gui_synclist_limit_scroll(struct gui_synclist * lists, bool scroll); -extern void gui_synclist_set_title(struct gui_synclist * lists, char * title, +extern void gui_synclist_set_title(struct gui_synclist * lists, const char * title, enum themable_icons icon); -extern void gui_synclist_hide_selection_marker(struct gui_synclist *lists, - bool hide); -extern bool gui_synclist_item_is_onscreen(struct gui_synclist *lists, - enum screen_type screen, int item); extern bool gui_synclist_keyclick_callback(int action, void* data); /* @@ -199,20 +226,9 @@ extern bool gui_synclist_keyclick_callback(int action, void* data); * returns true if the action was handled. * NOTE: *action may be changed regardless of return value */ -extern bool gui_synclist_do_button(struct gui_synclist * lists, - int *action, - enum list_wrap); +extern bool gui_synclist_do_button(struct gui_synclist * lists, int *action); #if !defined(PLUGIN) -struct listitem_viewport_cfg { - struct wps_data *data; - OFFSETTYPE(char *) label; - int width; - int height; - int xmargin; - int ymargin; - bool tile; - struct skin_viewport selected_item_vp; -}; +struct listitem_viewport_cfg; bool skinlist_get_item(struct screen *display, struct gui_synclist *list, int x, int y, int *item); bool skinlist_draw(struct screen *display, struct gui_synclist *list); @@ -244,8 +260,7 @@ extern int list_do_action_timeout(struct gui_synclist *lists, int timeout); list_do_action_timeout) with the gui_synclist_do_button call, for convenience. */ extern bool list_do_action(int context, int timeout, - struct gui_synclist *lists, int *action, - enum list_wrap wrap); + struct gui_synclist *lists, int *action); /** Simplelist implementation. @@ -254,10 +269,9 @@ extern bool list_do_action(int context, int timeout, **/ struct simplelist_info { - char *title; /* title to show on the list */ + const char *title; /* title to show on the list */ int count; /* number of items in the list, each item is selection_size high */ int selection_size; /* list selection size, usually 1 */ - bool hide_selection; bool scroll_all; bool hide_theme; bool speak_onshow; /* list speaks first item or 'empty list' */ @@ -303,7 +317,6 @@ void simplelist_addline(const char *fmt, ...); /* setup the info struct. members not setup in this function need to be assigned manually members set in this function: info.selection_size = 1; - info.hide_selection = false; info.scroll_all = false; info.hide_theme = false; info.speak_onshow = true; diff --git a/apps/gui/option_select.c b/apps/gui/option_select.c index 9f1f0a64e3..8839f42e42 100644 --- a/apps/gui/option_select.c +++ b/apps/gui/option_select.c @@ -68,8 +68,8 @@ const char *option_get_valuestring(const struct settings_list *setting, if ((setting->flags & F_BOOL_SETTING) == F_BOOL_SETTING) { bool val = (bool)temp_var; - strlcpy(buffer, str(val? setting->bool_setting->lang_yes : - setting->bool_setting->lang_no), buf_len); + strmemccpy(buffer, str(val? setting->bool_setting->lang_yes : + setting->bool_setting->lang_no), buf_len); } #if 0 /* probably dont need this one */ else if ((setting->flags & F_FILENAME) == F_FILENAME) @@ -117,23 +117,21 @@ const char *option_get_valuestring(const struct settings_list *setting, { if (setting->flags & F_CHOICETALKS) { - int setting_id; const struct choice_setting *info = setting->choice_setting; if (info->talks[(int)temp_var] < LANG_LAST_INDEX_IN_ARRAY) { - strlcpy(buffer, str(info->talks[(int)temp_var]), buf_len); + strmemccpy(buffer, str(info->talks[(int)temp_var]), buf_len); } else { - find_setting(setting->setting, &setting_id); - cfg_int_to_string(setting_id, (int)temp_var, buffer, buf_len); + cfg_int_to_string(setting, (int)temp_var, buffer, buf_len); } } else { int value = (int)temp_var; char *val = P2STR(setting->choice_setting->desc[value]); - strlcpy(buffer, val, buf_len); + strmemccpy(buffer, val, buf_len); } } return str; @@ -334,29 +332,35 @@ static int selection_to_val(const struct settings_list *setting, int selection) else if ((setting->flags & F_T_SOUND) == F_T_SOUND) { int setting_id = setting->sound_setting->setting; -#ifndef ASCENDING_INT_SETTINGS - step = sound_steps(setting_id); - max = (setting_id == SOUND_VOLUME) ? - global_settings.volume_limit : sound_max(setting_id); - /* min = sound_min(setting_id); */ -#else - step = -sound_steps(setting_id); - /* min = sound_max(setting_id); */ - max = sound_min(setting_id); -#endif + if(global_settings.list_order == LIST_ORDER_DESCENDING) + { + step = sound_steps(setting_id); + max = (setting_id == SOUND_VOLUME) ? + global_settings.volume_limit : sound_max(setting_id); + /* min = sound_min(setting_id); */ + } + else + { + step = -sound_steps(setting_id); + /* min = sound_max(setting_id); */ + max = sound_min(setting_id); + } } else if ((setting->flags & F_INT_SETTING) == F_INT_SETTING) { const struct int_setting *info = setting->int_setting; -#ifndef ASCENDING_INT_SETTINGS - /* min = info->min; */ - max = info->max; - step = info->step; -#else - max = info->min; - /* min = info->max; */ - step = -info->step; -#endif + if(global_settings.list_order == LIST_ORDER_DESCENDING) + { + /* min = info->min; */ + max = info->max; + step = info->step; + } + else + { + max = info->min; + /* min = info->max; */ + step = -info->step; + } } return max- (selection * step); } @@ -424,11 +428,10 @@ static void val_to_selection(const struct settings_list *setting, int oldvalue, int max = (setting_id == SOUND_VOLUME) ? global_settings.volume_limit : sound_max(setting_id); *nb_items = (max-min)/steps + 1; -#ifndef ASCENDING_INT_SETTINGS - *selected = (max - oldvalue) / steps; -#else - *selected = (oldvalue - min) / steps; -#endif + if (global_settings.list_order == LIST_ORDER_DESCENDING) + *selected = (max - oldvalue) / steps; + else + *selected = (oldvalue - min) / steps; *function = sound_get_fn(setting_id); } else @@ -439,11 +442,10 @@ static void val_to_selection(const struct settings_list *setting, int oldvalue, min = info->min; step = info->step; *nb_items = (max-min)/step + 1; -#ifndef ASCENDING_INT_SETTINGS - *selected = (max - oldvalue) / step; -#else - *selected = (oldvalue - min) / step; -#endif + if(global_settings.list_order == LIST_ORDER_DESCENDING) + *selected = (max - oldvalue) / step; + else + *selected = (oldvalue - min) / step; *function = info->option_callback; } } @@ -502,15 +504,18 @@ bool option_screen(const struct settings_list *setting, gui_synclist_set_nb_items(&lists, nb_items); gui_synclist_select_item(&lists, selected); - gui_synclist_limit_scroll(&lists, true); gui_synclist_draw(&lists); /* talk the item */ gui_synclist_speak_item(&lists); while (!done) { + /* override user wraparound setting; used mainly by EQ settings. + * Not sure this is justified? */ + if (!allow_wrap) + lists.wraparound = false; + if (list_do_action(CONTEXT_LIST, HZ, /* HZ so the status bar redraws */ - &lists, &action, - allow_wrap? LIST_WRAP_UNLESS_HELD: LIST_WRAP_OFF)) + &lists, &action)) { /* setting changed */ selected = gui_synclist_get_sel_pos(&lists); @@ -520,7 +525,7 @@ bool option_screen(const struct settings_list *setting, } else if (action == ACTION_NONE) continue; - else if (action == ACTION_STD_CANCEL) + else if (action == ACTION_STD_CANCEL || action == ACTION_STD_MENU) { /* setting canceled, restore old value if changed */ if (*variable != oldvalue) @@ -560,7 +565,10 @@ bool option_screen(const struct settings_list *setting, function(*variable); } else if(default_event_handler(action) == SYS_USB_CONNECTED) + { + pop_current_activity(); return true; + } /* callback */ if (function && !cb_on_select_only) function(*variable); @@ -573,9 +581,8 @@ bool option_screen(const struct settings_list *setting, return false; } -int get_setting_info_for_bar(int setting_id, int *count, int *val) +int get_setting_info_for_bar(const struct settings_list *setting, int *count, int *val) { - const struct settings_list *setting = &settings[setting_id]; int var_type = setting->flags&F_T_MASK; void (*function)(int) = NULL; int oldvalue; @@ -600,9 +607,8 @@ int get_setting_info_for_bar(int setting_id, int *count, int *val) } #ifdef HAVE_TOUCHSCREEN -void update_setting_value_from_touch(int setting_id, int selection) +void update_setting_value_from_touch(const struct settings_list *setting, int selection) { - const struct settings_list *setting = &settings[setting_id]; int new_val = selection_to_val(setting, selection); int var_type = setting->flags&F_T_MASK; diff --git a/apps/gui/option_select.h b/apps/gui/option_select.h index 476e7b81bd..eabe5825e7 100644 --- a/apps/gui/option_select.h +++ b/apps/gui/option_select.h @@ -25,11 +25,10 @@ #include "screen_access.h" #include "settings.h" -#if defined (HAVE_SCROLLWHEEL) && !defined(FIIO_M3K) -/* Define this if your target makes sense to have - smaller values at the top of the list increasing down the list */ -#define ASCENDING_INT_SETTINGS -#endif +enum { + LIST_ORDER_DESCENDING = 0, + LIST_ORDER_ASCENDING = 1, +}; bool option_screen(const struct settings_list *setting, struct viewport parent[NB_SCREENS], @@ -47,9 +46,9 @@ void option_talk_value(const struct settings_list *setting, int value, bool enqu /* only use this for int and bool settings */ int option_value_as_int(const struct settings_list *setting); -int get_setting_info_for_bar(int setting_id, int *count, int *val); +int get_setting_info_for_bar(const struct settings_list *setting, int *count, int *val); #ifdef HAVE_TOUCHSCREEN -void update_setting_value_from_touch(int setting_id, int selection); +void update_setting_value_from_touch(const struct settings_list *setting, int selection); #endif #endif /* _GUI_OPTION_SELECT_H_ */ diff --git a/apps/gui/pitchscreen.c b/apps/gui/pitchscreen.c index 871921a10f..9f42aedb5d 100644 --- a/apps/gui/pitchscreen.c +++ b/apps/gui/pitchscreen.c @@ -18,1059 +18,8 @@ * KIND, either express or implied. * ****************************************************************************/ - -#include <stdbool.h> -#include <string.h> -#include <stdio.h> -#include <math.h> -#include <stdlib.h> /* for abs() */ -#include "config.h" -#include "action.h" -#include "sound.h" -#include "pcmbuf.h" -#include "lang.h" -#include "icons.h" -#include "screens.h" -#include "talk.h" -#include "viewport.h" -#include "font.h" -#include "system.h" -#include "misc.h" -#include "pitchscreen.h" -#include "settings.h" -#include "tdspeed.h" - -#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */ - /* on both sides when drawing */ - -#define PITCH_MAX (200 * PITCH_SPEED_PRECISION) -#define PITCH_MIN (50 * PITCH_SPEED_PRECISION) -#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */ -#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */ -#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */ - -#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */ -#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */ - -#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */ -#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */ - -enum -{ - PITCH_TOP = 0, - PITCH_MID, - PITCH_BOTTOM, - PITCH_ITEM_COUNT, -}; - - -/* This is a table of semitone percentage values of the appropriate - precision (based on PITCH_SPEED_PRECISION). Note that these are - all constant expressions, which will be evaluated at compile time, - so no need to worry about how complex the expressions look. - That's just to get the precision right. - - I calculated these values, starting from 50, as - - x(n) = 50 * 2^(n/12) - - All that math in each entry simply converts the float constant - to an integer equal to PITCH_SPEED_PRECISION times the float value, - with as little precision loss as possible (i.e. correctly rounding - the last digit). -*/ -#define TO_INT_WITH_PRECISION(x) \ - ( (unsigned short)(((x) * PITCH_SPEED_PRECISION * 10 + 5) / 10) ) - -static const unsigned short semitone_table[] = -{ - TO_INT_WITH_PRECISION(50.00000000), /* Octave lower */ - TO_INT_WITH_PRECISION(52.97315472), - TO_INT_WITH_PRECISION(56.12310242), - TO_INT_WITH_PRECISION(59.46035575), - TO_INT_WITH_PRECISION(62.99605249), - TO_INT_WITH_PRECISION(66.74199271), - TO_INT_WITH_PRECISION(70.71067812), - TO_INT_WITH_PRECISION(74.91535384), - TO_INT_WITH_PRECISION(79.37005260), - TO_INT_WITH_PRECISION(84.08964153), - TO_INT_WITH_PRECISION(89.08987181), - TO_INT_WITH_PRECISION(94.38743127), - TO_INT_WITH_PRECISION(100.0000000), /* Normal sound */ - TO_INT_WITH_PRECISION(105.9463094), - TO_INT_WITH_PRECISION(112.2462048), - TO_INT_WITH_PRECISION(118.9207115), - TO_INT_WITH_PRECISION(125.9921049), - TO_INT_WITH_PRECISION(133.4839854), - TO_INT_WITH_PRECISION(141.4213562), - TO_INT_WITH_PRECISION(149.8307077), - TO_INT_WITH_PRECISION(158.7401052), - TO_INT_WITH_PRECISION(168.1792831), - TO_INT_WITH_PRECISION(178.1797436), - TO_INT_WITH_PRECISION(188.7748625), - TO_INT_WITH_PRECISION(200.0000000) /* Octave higher */ -}; - -#define NUM_SEMITONES ((int)(sizeof(semitone_table)/sizeof(semitone_table[0]))) -#define SEMITONE_END (NUM_SEMITONES/2) -#define SEMITONE_START (-SEMITONE_END) - -/* A table of values for approximating the cent curve with - linear interpolation. Multipy the next lowest semitone - by this much to find the corresponding cent percentage. - - These values were calculated as - x(n) = 100 * 2^(n * 20/1200) -*/ - -static const unsigned short cent_interp[] = -{ - TO_INT_WITH_PRECISION(100.0000000), - TO_INT_WITH_PRECISION(101.1619440), - TO_INT_WITH_PRECISION(102.3373892), - TO_INT_WITH_PRECISION(103.5264924), - TO_INT_WITH_PRECISION(104.7294123), - /* this one's the next semitone but we have it here for convenience */ - TO_INT_WITH_PRECISION(105.9463094), -}; - -/* Number of cents between entries in the cent_interp table */ -#define CENT_INTERP_INTERVAL 20 -#define CENT_INTERP_NUM |