summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/action.h1
-rw-r--r--apps/bookmark.c86
-rw-r--r--apps/cuesheet.c10
-rw-r--r--apps/debug_menu.c50
-rw-r--r--apps/features.txt2
-rw-r--r--apps/filetree.c45
-rw-r--r--apps/filetypes.c75
-rw-r--r--apps/filetypes.h2
-rw-r--r--apps/gui/bitmap/list.c74
-rw-r--r--apps/gui/folder_select.c429
-rw-r--r--apps/gui/list.c8
-rw-r--r--apps/gui/option_select.c16
-rw-r--r--apps/gui/pitchscreen.c1055
-rw-r--r--apps/gui/quickscreen.c42
-rw-r--r--apps/gui/skin_engine/skin_engine.c3
-rw-r--r--apps/gui/skin_engine/skin_parser.c6
-rw-r--r--apps/gui/skin_engine/skin_tokens.c7
-rw-r--r--apps/keymaps/keymap-fiiom3k.c58
-rw-r--r--apps/keymaps/keymap-hm60x.c2
-rw-r--r--apps/keymaps/keymap-hm801.c2
-rw-r--r--apps/keymaps/keymap-ihifi.c2
-rw-r--r--apps/keymaps/keymap-m3.c4
-rw-r--r--apps/keymaps/keymap-ma.c2
-rw-r--r--apps/keymaps/keymap-mpio-hd200.c6
-rw-r--r--apps/keymaps/keymap-mpio-hd300.c5
-rw-r--r--apps/keymaps/keymap-sansa-connect.c7
-rw-r--r--apps/keymaps/keymap-shanlingq1.c2
-rw-r--r--apps/keymaps/keymap-vibe500.c4
-rw-r--r--apps/keymaps/keymap-x5.c4
-rw-r--r--apps/lang/dansk.lang14
-rw-r--r--apps/lang/english-us.lang42
-rw-r--r--apps/lang/english.lang56
-rw-r--r--apps/lang/hebrew.lang14
-rw-r--r--apps/menu.c169
-rw-r--r--apps/menus/display_menu.c17
-rw-r--r--apps/menus/playback_menu.c3
-rw-r--r--apps/menus/plugin_menu.c39
-rw-r--r--apps/menus/settings_menu.c9
-rw-r--r--apps/onplay.c8
-rw-r--r--apps/open_plugin.c338
-rw-r--r--apps/open_plugin.h26
-rw-r--r--apps/pcmbuf.c3
-rw-r--r--apps/playback.c48
-rw-r--r--apps/playlist_viewer.c22
-rw-r--r--apps/plugin.c22
-rw-r--r--apps/plugin.h29
-rw-r--r--apps/plugins/CATEGORIES3
-rw-r--r--apps/plugins/SOURCES8
-rw-r--r--apps/plugins/announce_status.c21
-rw-r--r--apps/plugins/bitmaps/native/SOURCES4
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmpbin0 -> 6254 bytes
-rw-r--r--apps/plugins/db_folder_select.c652
-rw-r--r--apps/plugins/keybox.c2
-rw-r--r--apps/plugins/lib/SOURCES2
-rw-r--r--apps/plugins/lib/action_helper.c1
-rw-r--r--apps/plugins/lib/action_helper.h34
-rwxr-xr-xapps/plugins/lib/action_helper.pl209
-rw-r--r--apps/plugins/lib/arg_helper.c4
-rw-r--r--apps/plugins/lib/arg_helper.h2
-rw-r--r--apps/plugins/lib/button_helper.c1
-rw-r--r--apps/plugins/lib/button_helper.h38
-rwxr-xr-xapps/plugins/lib/button_helper.pl98
-rw-r--r--apps/plugins/lrcplayer.c4
-rwxr-xr-xapps/plugins/lua/rbdefines_helper.pl1
-rw-r--r--apps/plugins/lua/rocklua.c12
-rw-r--r--apps/plugins/lua_scripts/return2WPS.lua19
-rw-r--r--apps/plugins/main_menu_config.c2
-rw-r--r--apps/plugins/open_plugins.c45
-rw-r--r--apps/plugins/pictureflow/pictureflow.c223
-rw-r--r--apps/plugins/pitch_screen.c1279
-rw-r--r--apps/plugins/plugins.make29
-rw-r--r--apps/plugins/random_folder_advance_config.c50
-rw-r--r--apps/plugins/rb_info.c494
-rw-r--r--apps/recorder/keyboard.c43
-rw-r--r--apps/root_menu.c243
-rw-r--r--apps/settings.c34
-rw-r--r--apps/settings.h2
-rw-r--r--apps/settings_list.c3
-rw-r--r--apps/shortcuts.c23
-rw-r--r--apps/talk.c72
-rw-r--r--apps/tree.c46
-rw-r--r--apps/voice_thread.c12
82 files changed, 4750 insertions, 1833 deletions
diff --git a/apps/action.h b/apps/action.h
index ad91f31535..f94dd3086c 100644
--- a/apps/action.h
+++ b/apps/action.h
@@ -129,6 +129,7 @@ enum {
CONTEXT_USB_HID_MODE_PRESENTATION,
CONTEXT_USB_HID_MODE_BROWSER,
CONTEXT_USB_HID_MODE_MOUSE,
+ LAST_CONTEXT_PLACEHOLDER,
};
diff --git a/apps/bookmark.c b/apps/bookmark.c
index dece69dce6..70dbd8075d 100644
--- a/apps/bookmark.c
+++ b/apps/bookmark.c
@@ -75,10 +75,10 @@ static struct {
bool shuffle;
/* optional values */
int pitch;
- int speed;
+ int speed;
} bm;
-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);
static char* create_bookmark(void);
static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id);
@@ -275,7 +275,7 @@ static bool get_playlist_and_track(const char *bookmark, char **pl_start,
/* 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;
@@ -466,7 +466,7 @@ bool bookmark_autoload(const char* file)
else
{
int ret = select_bookmark(global_bookmark_file_name, true, &bookmark);
-
+
if (bookmark != NULL)
{
if (!play_bookmark(bookmark))
@@ -475,7 +475,7 @@ bool bookmark_autoload(const char* file)
splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
}
- /* Act as if autoload was done even if it failed, since the
+ /* Act as if autoload was done even if it failed, since the
* user did make an active selection.
*/
return true;
@@ -520,7 +520,7 @@ bool bookmark_load(const char* file, bool autoload)
{
splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
}
-
+
return false;
}
}
@@ -541,7 +541,7 @@ static int get_bookmark_count(const char* bookmark_file_name)
{
read_count++;
}
-
+
close(file);
return read_count;
}
@@ -563,25 +563,25 @@ static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line)
/* Entire file fits in buffer */
first_line = 0;
}
-
+
bookmarks->start = first_line;
bookmarks->count = 0;
bookmarks->reload = false;
-
+
while(read_line(file, global_read_buffer, sizeof(global_read_buffer)) > 0)
{
read_count++;
-
+
if (read_count >= first_line)
{
dest -= strlen(global_read_buffer) + 1;
-
+
if (dest < ((char*) bookmarks) + sizeof(*bookmarks)
+ (sizeof(char*) * (bookmarks->count + 1)))
{
break;
}
-
+
strcpy(dest, global_read_buffer);
bookmarks->items[bookmarks->count] = dest;
bookmarks->count++;
@@ -604,22 +604,22 @@ static const char* get_bookmark_info(int list_index,
{
if (index == 0)
{
- return list_index % 2 == 0
+ return list_index % 2 == 0
? (char*) str(LANG_BOOKMARK_DONT_RESUME) : " ";
}
-
+
index--;
}
- if (bookmarks->reload || (index >= bookmarks->start + bookmarks->count)
+ if (bookmarks->reload || (index >= bookmarks->start + bookmarks->count)
|| (index < bookmarks->start))
{
int read_index = index;
-
+
/* Using count as a guide on how far to move could possibly fail
* sometimes. Use byte count if that is a problem?
*/
-
+
if (read_index != 0)
{
/* Move count * 3 / 4 items in the direction the user is moving,
@@ -627,31 +627,31 @@ static const char* get_bookmark_info(int list_index,
*/
int offset = bookmarks->count;
int max = bookmarks->total_count - (bookmarks->count / 2);
-
+
if (read_index < bookmarks->start)
{
offset *= 3;
}
-
+
read_index = index - offset / 4;
if (read_index > max)
{
read_index = max;
}
-
+
if (read_index < 0)
{
read_index = 0;
}
}
-
+
if (buffer_bookmarks(bookmarks, read_index) <= index)
{
return "";
}
}
-
+
if (!parse_bookmark(bookmarks->items[index - bookmarks->start], true, true))
{
return list_index % 2 == 0 ? (char*) str(LANG_BOOKMARK_INVALID) : " ";
@@ -662,12 +662,12 @@ static const char* get_bookmark_info(int list_index,
char *name;
char *format;
int len = strlen(global_temp_buffer);
-
+
if (bookmarks->show_playlist_name && len > 0)
{
name = global_temp_buffer;
len--;
-
+
if (name[len] != '/')
{
strrsplt(name, '.');
@@ -689,7 +689,7 @@ static const char* get_bookmark_info(int list_index,
name = global_filename;
format = "%s";
}
-
+
strrsplt(global_filename, '.');
snprintf(buffer, buffer_len, format, name, global_filename);
return buffer;
@@ -751,12 +751,12 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume
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),
+ gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK),
Icon_Bookmark);
while (!exit)
{
-
+
if (refresh)
{
int count = get_bookmark_count(bookmark_file_name);
@@ -805,17 +805,17 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume
if (action == ACTION_STD_CONTEXT)
{
MENUITEM_STRINGLIST(menu_items, ID2P(LANG_BOOKMARK_CONTEXT_MENU),
- NULL, ID2P(LANG_BOOKMARK_CONTEXT_RESUME),
+ NULL, ID2P(LANG_BOOKMARK_CONTEXT_RESUME),
ID2P(LANG_BOOKMARK_CONTEXT_DELETE));
- static const int menu_actions[] =
+ static const int menu_actions[] =
{
ACTION_STD_OK, ACTION_BMS_DELETE
};
int selection = do_menu(&menu_items, NULL, NULL, false);
-
+
refresh = true;
- if (selection >= 0 && selection <=
+ if (selection >= 0 && selection <=
(int) (sizeof(menu_actions) / sizeof(menu_actions[0])))
{
action = menu_actions[selection];
@@ -842,7 +842,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)
};
@@ -854,7 +854,7 @@ static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume
const struct text_message yes_message={yes_lines, 1};
if(gui_syncyesno_run(&message, &yes_message, NULL)==YESNO_YES)
- {
+ {
delete_bookmark(bookmark_file_name, item);
bookmarks->reload = true;
}
@@ -1002,12 +1002,12 @@ static const char* skip_token(const char* s)
{
s++;
}
-
+
if (*s)
{
s++;
}
-
+
return s;
}
@@ -1033,11 +1033,11 @@ static bool parse_bookmark(const char *bookmark, const bool parse_filenames, con
{
const char* s = bookmark;
const char* end;
-
+
#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 new format bookmark, extract the optional content flags,
otherwise treat as an original format bookmark */
int opt_flags = 0;
@@ -1047,7 +1047,7 @@ static bool parse_bookmark(const char *bookmark, const bool parse_filenames, con
s++;
GET_INT_TOKEN(opt_flags);
}
-
+
/* extract all original bookmark tokens */
GET_INT_TOKEN(bm.resume_index);
GET_LONG_TOKEN(bm.resume_offset);
@@ -1057,18 +1057,18 @@ static bool parse_bookmark(const char *bookmark, const bool parse_filenames, con
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 (*s == 0)
{
return false;
}
-
+
end = strchr(s, ';');
/* extract file names */
@@ -1077,7 +1077,7 @@ static bool parse_bookmark(const char *bookmark, const bool parse_filenames, con
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);
-
+
if (end != NULL)
{
end++;
@@ -1093,7 +1093,7 @@ static bool parse_bookmark(const char *bookmark, const bool parse_filenames, con
strlcpy(global_filename, end, MAX_PATH);
}
}
-
+
return true;
}
diff --git a/apps/cuesheet.c b/apps/cuesheet.c
index 57c588ee9a..be89ef96cf 100644
--- a/apps/cuesheet.c
+++ b/apps/cuesheet.c
@@ -64,7 +64,7 @@ static bool search_for_cuesheet(const char *path, struct cuesheet_file *cue_file
{
strcpy(cuepath, CUE_DIR);
if (strlcat(cuepath, slash, MAX_PATH) >= MAX_PATH)
- goto skip; /* overflow */
+ goto skip; /* overflow */
char *dot = strrchr(cuepath, '.');
strcpy(dot, ".cue");
if (!file_exists(cuepath))
@@ -305,7 +305,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue)
break;
}
- if (dest)
+ if (dest)
{
if (char_enc == CHAR_ENC_ISO_8859_1)
{
@@ -317,7 +317,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue)
{
strlcpy(dest, string, count);
}
- }
+ }
}
if (is_embedded)
{
@@ -505,7 +505,7 @@ bool display_cuesheet_content(char* filename)
bool curr_cuesheet_skip(struct cuesheet *cue, int direction, unsigned long curr_pos)
{
int track = cue_find_current_track(cue, curr_pos);
-
+
if (direction >= 0 && track == cue->track_count - 1)
{
/* we want to get out of the cuesheet */
@@ -520,7 +520,7 @@ bool curr_cuesheet_skip(struct cuesheet *cue, int direction, unsigned long curr_
to previous cuesheet segment. If skipping backward after
DEFAULT_SKIP_TRESH seconds have elapsed, skip to the start of the
current cuesheet segment */
- if (direction == 1 ||
+ if (direction == 1 ||
((curr_pos - cue->tracks[track].offset) < DEFAULT_SKIP_TRESH))
{
track += direction;
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 800e485ce3..23deb6cff2 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -2097,7 +2097,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);
}
@@ -2242,6 +2242,51 @@ static bool cpu_boost_log(void)
lcd_setfont(FONT_UI);
return false;
}
+
+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();
+
+ splashf(HZ, "Boost Log File Dumped");
+
+ /* nothing to print ? */
+ if(count == 0)
+ 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);
+#else
+ fd = open(ROCKBOX_DIR "/boostlog.txt", O_CREAT|O_WRONLY|O_TRUNC, 0666);
+#endif
+ if(-1 != fd) {
+ for (int i = 0; i < count; i++)
+ {
+ if (!str)
+ str = cpu_boost_log_getlog_next();
+ if (str)
+ {
+ fdprintf(fd, "%s\n", str);
+ str = NULL;
+ }
+ }
+
+ close(fd);
+ return true;
+ }
+
+ return false;
+}
#endif
#if (defined(HAVE_WHEEL_ACCELERATION) && (CONFIG_KEYPAD==IPOD_4G_PAD) \
@@ -2604,7 +2649,8 @@ static const struct {
#endif
#endif /* HAVE_USBSTACK */
#ifdef CPU_BOOST_LOGGING
- {"cpu_boost log",cpu_boost_log},
+ {"Show cpu_boost log",cpu_boost_log},
+ {"Dump cpu_boost log",cpu_boost_log_dump},
#endif
#if (defined(HAVE_WHEEL_ACCELERATION) && (CONFIG_KEYPAD==IPOD_4G_PAD) \
&& !defined(IPOD_MINI) && !defined(SIMULATOR))
diff --git a/apps/features.txt b/apps/features.txt
index 83c3f0a65f..ec2113cbc8 100644
--- a/apps/features.txt
+++ b/apps/features.txt
@@ -262,6 +262,8 @@ recording_digital
#if MEMORYSIZE <= 2
lowmem
+#elif MEMORYSIZE > 8
+himem
#endif
#if defined(HAVE_HARDWARE_CLICK)
diff --git a/apps/filetree.c b/apps/filetree.c
index 5c6443cc34..75a32a9e42 100644
--- a/apps/filetree.c
+++ b/apps/filetree.c
@@ -436,6 +436,7 @@ int ft_enter(struct tree_context* c)
{
int rc = GO_TO_PREVIOUS;
char buf[MAX_PATH];
+
struct entry* file = tree_get_entry_at(c, c->selected_item);
if (!file)
{
@@ -628,32 +629,21 @@ int ft_enter(struct tree_context* c)
rolo_load(buf);
break;
#endif
+ case FILE_ATTR_CUE:
+ display_cuesheet_content(buf);
+ break;
/* plugin file */
case FILE_ATTR_ROCK:
- case FILE_ATTR_LUA:
- case FILE_ATTR_OPX:
{
- char *plugin = buf, *argument = NULL, lua_path[MAX_PATH];
- int ret;
-
- if ((file_attr & FILE_ATTR_MASK) == FILE_ATTR_LUA) {
- snprintf(lua_path, sizeof(lua_path)-1, "%s/lua.rock", VIEWERS_DIR); /* Use a #define here ? */
- plugin = lua_path;
- argument = buf;
- }
- else if ((file_attr & FILE_ATTR_MASK) == FILE_ATTR_OPX) {
- snprintf(lua_path, sizeof(lua_path)-1, "%s/open_plugins.rock", VIEWERS_DIR); /* Use a #define here ? */
- plugin = lua_path;
- argument = buf;
- }
-
+ char *plugin = buf, *argument = NULL;
if (global_settings.party_mode && audio_status()) {
splash(HZ, ID2P(LANG_PARTY_MODE));
break;
}
- ret = plugin_load(plugin, argument);
- switch (ret)
+
+#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */
+ switch (plugin_load(plugin, argument))
{
case PLUGIN_GOTO_WPS:
play = true;
@@ -676,16 +666,18 @@ 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;
}
- case FILE_ATTR_CUE:
- display_cuesheet_content(buf);
- break;
default:
{
const char* plugin;
-
+ char plugin_path[MAX_PATH];
+ const char *argument = buf;
if (global_settings.party_mode && audio_status()) {
splash(HZ, ID2P(LANG_PARTY_MODE));
break;
@@ -698,10 +690,11 @@ int ft_enter(struct tree_context* c)
return rc;
}
- plugin = filetype_get_plugin(file);
+ plugin = filetype_get_plugin(file, plugin_path, sizeof(plugin_path));
if (plugin)
{
- switch (plugin_load(plugin,buf))
+#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */
+ switch (plugin_load(plugin, argument))
{
case PLUGIN_USB_CONNECTED:
rc = GO_TO_FILEBROWSER;
@@ -719,6 +712,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/filetypes.c b/apps/filetypes.c
index 530ab18683..d68bab3daa 100644
--- a/apps/filetypes.c
+++ b/apps/filetypes.c
@@ -531,17 +531,37 @@ int filetype_get_icon(int attr)
return filetypes[index].icon;
}
-char* filetype_get_plugin(const struct entry* file)
+char* filetype_get_plugin(const struct entry* file, char *buffer, size_t buffer_len)
{
- static char plugin_name[MAX_PATH];
int index = find_attr(file->attr);
- if (index < 0)
+ if (index < 0 || !buffer)
return NULL;
- if (filetypes[index].plugin == NULL)
+ struct file_type *ft_indexed = &filetypes[index];
+
+ /* attempt to find a suitable viewer by file extension */
+ if(ft_indexed->plugin == NULL && ft_indexed->extension != NULL)
+ {
+ struct file_type *ft;
+ int i = filetype_count;
+ while (--i > index)
+ {
+ ft = &filetypes[i];
+ if (ft->plugin == NULL || ft->extension == NULL)
+ continue;
+ else if (strcmp(ft->extension, ft_indexed->extension) == 0)
+ {
+ /*splashf(HZ*3, "Found %d %s %s", i, ft->extension, ft->plugin);*/
+ ft_indexed = ft;
+ break;
+ }
+ }
+ }
+ if (ft_indexed->plugin == NULL)
return NULL;
- snprintf(plugin_name, MAX_PATH, "%s/%s.%s",
- PLUGIN_DIR, filetypes[index].plugin, ROCK_EXTENSION);
- return plugin_name;
+
+ snprintf(buffer, buffer_len, "%s/%s." ROCK_EXTENSION,
+ PLUGIN_DIR, ft_indexed->plugin);
+ return buffer;
}
bool filetype_supported(int attr)
@@ -574,39 +594,32 @@ static int openwith_get_talk(int selected_item, void * data)
{
(void)data;
char viewer_filename[MAX_FILENAME];
- snprintf(viewer_filename, MAX_FILENAME, "%s.%s",
- filetypes[viewers[selected_item]].plugin, ROCK_EXTENSION);
+ snprintf(viewer_filename, MAX_FILENAME, "%s." ROCK_EXTENSION,
+ filetypes[viewers[selected_item]].plugin);
talk_file_or_spell(PLUGIN_DIR, viewer_filename,
NULL, false);
return 0;
}
-static int openwith_action_callback(int action, struct gui_synclist *lists)
-{
- struct cb_data *info = (struct cb_data *)lists->data;
- int i;
- if (action == ACTION_STD_OK)
- {
- char plugin[MAX_PATH];
- i = viewers[gui_synclist_get_sel_pos(lists)];
- snprintf(plugin, MAX_PATH, "%s/%s.%s",
- PLUGIN_DIR, filetypes[i].plugin, ROCK_EXTENSION);
- plugin_load(plugin, info->current_file);
- return ACTION_STD_CANCEL;
- }
- return action;
-}
-
int filetype_list_viewers(const char* current_file)
{
struct simplelist_info info;
- struct cb_data data = { current_file };
- simplelist_info_init(&info, str(LANG_ONPLAY_OPEN_WITH), viewer_count, &data);
- info.action_callback = openwith_action_callback;
+ simplelist_info_init(&info, str(LANG_ONPLAY_OPEN_WITH), viewer_count, NULL);
info.get_name = openwith_get_name;
info.get_icon = global_settings.show_icons?openwith_get_icon:NULL;
info.get_talk = openwith_get_talk;
- return simplelist_show_list(&info);
+
+ int ret = simplelist_show_list(&info);
+
+ if (info.selection >= 0) /* run user selected viewer */
+ {
+ char plugin[MAX_PATH];
+ int i = viewers[info.selection];
+ snprintf(plugin, MAX_PATH, "%s/%s." ROCK_EXTENSION,
+ PLUGIN_DIR, filetypes[i].plugin);
+ plugin_load(plugin, current_file);
+ }
+ return ret;
}
int filetype_load_plugin(const char* plugin, const char* file)
@@ -631,7 +644,7 @@ int filetype_load_plugin(const char* plugin, const char* file)
}
if (i >= filetype_count)
return PLUGIN_ERROR;
- snprintf(plugin_name, MAX_PATH, "%s/%s.%s",
- PLUGIN_DIR, filetypes[i].plugin, ROCK_EXTENSION);
+ snprintf(plugin_name, MAX_PATH, "%s/%s." ROCK_EXTENSION,
+ PLUGIN_DIR, filetypes[i].plugin);
return plugin_load(plugin_name, file);
}
diff --git a/apps/filetypes.h b/apps/filetypes.h
index 23f259b3ca..efe9f3f5df 100644
--- a/apps/filetypes.h
+++ b/apps/filetypes.h
@@ -73,7 +73,7 @@ int filetype_get_color(const char* name, int attr);
#endif
int filetype_get_icon(int attr);
/* return the plugin filename associated with the file */
-char* filetype_get_plugin(const struct entry* file);
+char* filetype_get_plugin(const struct entry* file, char *buffer, size_t buffer_len);
/* returns true if the attr is supported */
bool filetype_supported(int attr);
diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c
index ff0f5a29c1..194f4c008b 100644
--- a/apps/gui/bitmap/list.c
+++ b/apps/gui/bitmap/list.c
@@ -169,6 +169,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,8 +185,17 @@ 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
@@ -193,17 +208,32 @@ void list_draw(struct screen *display, struct gui_synclist *list)
{
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 */
@@ -360,21 +390,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 +546,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 +555,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;
diff --git a/apps/gui/folder_select.c b/apps/gui/folder_select.c
index 706b166941..e324e8649a 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;
+ }
+ strlcpy(&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 */
+ int 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 > buflen)
+ continue;
+ strlcpy(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
+ {
+ 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;
+ strlcpy(&dst[dlen], buffer_front, maxlen - dlen);
+ }
else
{
- char *p = get_full_path(root);
- snprintf(buffer_front, buffer_end - buffer_front,
- "%s/%s:", strcmp(p, "/") ? p : "",
- strcmp(this->name, "/") ? this->name : "");
+ 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/list.c b/apps/gui/list.c
index 13a850bd7b..8ff075da7e 100644
--- a/apps/gui/list.c
+++ b/apps/gui/list.c
@@ -686,7 +686,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists,
switch (wrap)
{
case LIST_WRAP_ON:
- gui_synclist_limit_scroll(lists, false);
+ gui_synclist_limit_scroll(lists, !global_settings.list_wraparound);
break;
case LIST_WRAP_OFF:
gui_synclist_limit_scroll(lists, true);
@@ -697,7 +697,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists,
action == ACTION_LISTTREE_PGUP ||
action == ACTION_LISTTREE_PGDOWN)
gui_synclist_limit_scroll(lists, true);
- else gui_synclist_limit_scroll(lists, false);
+ else gui_synclist_limit_scroll(lists, !global_settings.list_wraparound);
break;
};
@@ -754,7 +754,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;
@@ -911,7 +911,7 @@ 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;
+ int wrap = global_settings.list_wraparound ? LIST_WRAP_UNLESS_HELD : LIST_WRAP_OFF;
if (info->get_name)
getname = info->get_name;
else
diff --git a/apps/gui/option_select.c b/apps/gui/option_select.c
index ff257a4925..9f1f0a64e3 100644
--- a/apps/gui/option_select.c
+++ b/apps/gui/option_select.c
@@ -60,7 +60,7 @@ static const char *option_get_timestring(char *buf, int buf_len,
/* these two vars are needed so arbitrary values can be added to the
TABLE_SETTING settings if the F_ALLOW_ARBITRARY_VALS flag is set */
static int table_setting_oldval = 0, table_setting_array_position = 0;
-const char *option_get_valuestring(const struct settings_list *setting,
+const char *option_get_valuestring(const struct settings_list *setting,
char *buffer, int buf_len,
intptr_t temp_var)
{
@@ -202,7 +202,7 @@ void option_talk_value(const struct settings_list *setting, int value, bool enqu
}
}
}
-
+
static int option_talk(int selected_item, void * data)
{
struct settings_list *setting = (struct settings_list *)data;
@@ -320,7 +320,7 @@ static int selection_to_val(const struct settings_list *setting, int selection)
else if ((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING)
{
const struct table_setting *info = setting->table_setting;
- if (setting->flags&F_ALLOW_ARBITRARY_VALS &&
+ if (setting->flags&F_ALLOW_ARBITRARY_VALS &&
table_setting_array_position != -1 &&
(selection >= table_setting_array_position))
{
@@ -361,7 +361,7 @@ static int selection_to_val(const struct settings_list *setting, int selection)
return max- (selection * step);
}
-static const char * value_setting_get_name_cb(int selected_item,
+static const char * value_setting_get_name_cb(int selected_item,
void * data,
char *buffer,
size_t buffer_len)
@@ -492,16 +492,16 @@ bool option_screen(const struct settings_list *setting,
title = (char*)setting->cfg_vals;
else
title = P2STR(option_title);
-
+
gui_synclist_set_title(&lists, title, Icon_Questionmark);
gui_synclist_set_icon_callback(&lists, NULL);
if(global_settings.talk_menu)
gui_synclist_set_voice_callback(&lists, option_talk);
-
+
val_to_selection(setting, oldvalue, &nb_items, &selected, &function);
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 */
@@ -551,7 +551,7 @@ bool option_screen(const struct settings_list *setting,
{
if (var_type == F_T_INT || var_type == F_T_UINT)
*(int*)setting->setting = *variable;
- else
+ else
*(bool*)setting->setting = (*variable==1);
}
settings_save();
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 ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
-
-/* This stores whether the pitch and speed are at their own limits */
-/* or that of the timestretching algorithm */
-static bool at_limit = false;
-
-/*
- *
- * The pitchscreen is divided into 3 viewports (each row is a viewport)
- * Then each viewport is again divided into 3 colums, each showsing some infos
- * Additionally, on touchscreen, each cell represents a button
- *
- * Below a sketch describing what each cell will show (what's drawn on it)
- * --------------------------
- * | | | | <-- pitch up in the middle (text and button)
- * | | | | <-- arrows for mode toggling on the sides for touchscreen
- * |------------------------|
- * | | | | <-- semitone/speed up/down on the sides
- * | | | | <-- reset pitch&speed in the middle
- * |------------------------|
- * | | | | <-- pitch down in the middle
- * | | | | <-- Two "OK" for exit on the sides for touchscreen
- * |------------------------|
- *
- *
- */
-
-static void speak_pitch_mode(bool enqueue)
-{
- bool timestretch_mode = global_settings.pitch_mode_timestretch && dsp_timestretch_available();
- if (timestretch_mode)
- talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
- if (global_settings.pitch_mode_semitone)
- talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
- else
- talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
- return;
-}
-
-/*
- * Fixes the viewports so they represent the 3 rows, and adds a little margin
- * on all sides for the icons (which are drawn outside of the grid
- *
- * The modified viewports need to be passed to the touchscreen handling function
- **/
-static void pitchscreen_fix_viewports(struct viewport *parent,
- struct viewport pitch_viewports[PITCH_ITEM_COUNT])
-{
- int i, font_height;
- font_height = font_get(parent->font)->height;
- for (i = 0; i < PITCH_ITEM_COUNT; i++)
- {
- pitch_viewports[i] = *parent;
- pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
- pitch_viewports[i].x += ICON_BORDER;
- pitch_viewports[i].width -= 2*ICON_BORDER;
- }
- pitch_viewports[PITCH_TOP].y += ICON_BORDER;
- pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
-
- if(pitch_viewports[PITCH_MID].height < font_height * 2)
- pitch_viewports[PITCH_MID].height = font_height * 2;
-
- pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
- + pitch_viewports[PITCH_TOP].height;
-
- pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
- + pitch_viewports[PITCH_MID].height;
-
- pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
-}
-
-/* must be called before pitchscreen_draw, or within
- * since it neither clears nor updates the display */
-static void pitchscreen_draw_icons(struct screen *display,
- struct viewport *parent)
-{
- display->set_viewport(parent);
- display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
- parent->width/2 - 3,
- 2, 7, 8);
- display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
- parent->width /2 - 3,
- parent->height - 10, 7, 8);
- display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
- parent->width - 10,
- parent->height /2 - 4, 7, 8);
- display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
- 2,
- parent->height /2 - 4, 7, 8);
- display->update_viewport();
-}
-
-static void pitchscreen_draw(struct screen *display, int max_lines,
- struct viewport pitch_viewports[PITCH_ITEM_COUNT],
- int32_t pitch, int32_t semitone
- ,int32_t speed
- )
-{
- const char* ptr;
- char buf[32];
- int w, h;
- bool show_lang_pitch;
- struct viewport *last_vp = NULL;
-
- /* "Pitch up/Pitch down" - hide for a small screen,
- * the text is drawn centered automatically
- *
- * note: this assumes 5 lines always fit on a touchscreen (should be
- * reasonable) */
- if (max_lines >= 5)
- {
- int w, h;
- struct viewport *vp = &pitch_viewports[PITCH_TOP];
- last_vp = display->set_viewport(vp);
- display->clear_viewport();
-#ifdef HAVE_TOUCHSCREEN
- /* two arrows in the top row, left and right column */
- char *arrows[] = { "<", ">"};
- display->getstringsize(arrows[0], &w, &h);
- display->putsxy(0, vp->height/2 - h/2, arrows[0]);
- display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
-#endif
- /* UP: Pitch Up */
- if (global_settings.pitch_mode_semitone)
- ptr = str(LANG_PITCH_UP_SEMITONE);
- else
- ptr = str(LANG_PITCH_UP);
-
- display->getstringsize(ptr, &w, NULL);
- /* draw text */
- display->putsxy(vp->width/2 - w/2, 0, ptr);
- display->update_viewport();
-
- /* DOWN: Pitch Down */
- vp = &pitch_viewports[PITCH_BOTTOM];
- display->set_viewport(vp);
- display->clear_viewport();
-
-#ifdef HAVE_TOUCHSCREEN
- ptr = str(LANG_KBD_OK);
- display->getstringsize(ptr, &w, &h);
- /* one OK in the middle first column of the vp (at half height) */
- display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
- /* one OK in the middle of the last column of the vp (at half height) */
- display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
-#endif
- if (global_settings.pitch_mode_semitone)
- ptr = str(LANG_PITCH_DOWN_SEMITONE);
- else
- ptr = str(LANG_PITCH_DOWN);
- display->getstringsize(ptr, &w, &h);
- /* draw text */
- display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
- display->update_viewport();
- }
-
- /* Middle section */
- display->set_viewport(&pitch_viewports[PITCH_MID]);
- display->clear_viewport();
- int width_used = 0;
-
- /* Middle section upper line - hide for a small screen */
- if ((show_lang_pitch = (max_lines >= 3)))
- {
- if(global_settings.pitch_mode_timestretch)
- {
- /* Pitch:XXX.X% */
- if(global_settings.pitch_mode_semitone)
- {
- snprintf(buf, sizeof(buf), "%s: %s%d.%02d", str(LANG_PITCH),
- semitone >= 0 ? "+" : "-",
- abs(semitone / PITCH_SPEED_PRECISION),
- abs((semitone % PITCH_SPEED_PRECISION) /
- (PITCH_SPEED_PRECISION / 100))
- );
- }
- else
- {
- snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_PITCH),
- pitch / PITCH_SPEED_PRECISION,
- (pitch % PITCH_SPEED_PRECISION) /
- (PITCH_SPEED_PRECISION / 10));
- }
- }
- else
- {
- /* Rate */
- snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
- }
- display->getstringsize(buf, &w, &h);
- display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
- (pitch_viewports[PITCH_MID].height / 2) - h, buf);
- if (w > width_used)
- width_used = w;
- }
-
- /* Middle section lower line */
- /* "Speed:XXX%" */
- if(global_settings.pitch_mode_timestretch)
- {
- snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
- speed / PITCH_SPEED_PRECISION,
- (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
- }
- else
- {
- if(global_settings.pitch_mode_semitone)
- {
- snprintf(buf, sizeof(buf), "%s%d.%02d",
- semitone >= 0 ? "+" : "-",
- abs(semitone / PITCH_SPEED_PRECISION),
- abs((semitone % PITCH_SPEED_PRECISION) /
- (PITCH_SPEED_PRECISION / 100))
- );
- }
- else
- {
- snprintf(buf, sizeof(buf), "%ld.%ld%%",
- pitch / PITCH_SPEED_PRECISION,
- (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
- }
- }
-
- display->getstringsize(buf, &w, &h);
- display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
- show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
- (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
- buf);
- if (w > width_used)
- width_used = w;
-
- /* "limit" and "timestretch" labels */
- if (max_lines >= 7)
- {
- if(at_limit)
- {
- const char * const p = str(LANG_STRETCH_LIMIT);
- display->getstringsize(p, &w, &h);
- display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
- (pitch_viewports[PITCH_MID].height / 2) + h, p);
- if (w > width_used)
- width_used = w;
- }
- }
-
- /* Middle section left/right labels */
- const char *leftlabel = "-2%";
- const char *rightlabel = "+2%";
- if (global_settings.pitch_mode_timestretch)
- {
- leftlabel = "<<";
- rightlabel = ">>";
- }
-
- /* Only display if they fit */
- display->getstringsize(leftlabel, &w, &h);
- width_used += w;
- display->getstringsize(rightlabel, &w, &h);
- width_used += w;
-
- if (width_used <= pitch_viewports[PITCH_MID].width)
- {
- display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
- leftlabel);
- display->putsxy((pitch_viewports[PITCH_MID].width - w),
- (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
- rightlabel);
- }
- display->update_viewport();
- display->set_viewport(last_vp);
-}
-
-static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
- /* need this to maintain correct pitch/speed caps */
- , int32_t speed
- )
-{
- int32_t new_pitch;
- int32_t new_stretch;
- at_limit = false;
-
- if (pitch_delta < 0)
- {
- /* for large jumps, snap up to whole numbers */
- if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
- (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
- {
- pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
- }
-
- new_pitch = pitch + pitch_delta;
-
- if (new_pitch < PITCH_MIN)
- {
- if (!allow_cutoff)
- {
- return pitch;
- }
- new_pitch = PITCH_MIN;
- at_limit = true;
- }
- }
- else if (pitch_delta > 0)
- {
- /* for large jumps, snap down to whole numbers */
- if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
- (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
- {
- pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
- }
-
- new_pitch = pitch + pitch_delta;
-
- if (new_pitch > PITCH_MAX)
- {
- if (!allow_cutoff)
- return pitch;
- new_pitch = PITCH_MAX;
- at_limit = true;
- }
- }
- else
- {
- /* pitch_delta == 0 -> no real change */
- return pitch;
- }
- if (dsp_timestretch_available())
- {
- /* increase the multiple to increase precision of this calculation */
- new_stretch = GET_STRETCH(new_pitch, speed);
- if(new_stretch < STRETCH_MIN)
- {
- /* we have to ignore allow_cutoff, because we can't have the */
- /* stretch go higher than STRETCH_MAX */
- new_pitch = GET_PITCH(speed, STRETCH_MIN);
- }
- else if(new_stretch > STRETCH_MAX)
- {
- /* we have to ignore allow_cutoff, because we can't have the */
- /* stretch go higher than STRETCH_MAX */
- new_pitch = GET_PITCH(speed, STRETCH_MAX);
- }
-
- if(new_stretch >= STRETCH_MAX ||
- new_stretch <= STRETCH_MIN)
- {
- at_limit = true;
- }
- }
-
- sound_set_pitch(new_pitch);
-
- return new_pitch;
-}
-
-static int32_t get_semitone_from_pitch(int32_t pitch)
-{
- int semitone = 0;
- int32_t fractional_index = 0;
-
- while(semitone < NUM_SEMITONES - 1 &&
- pitch >= semitone_table[semitone + 1])
- {
- semitone++;
- }
-
-
- /* now find the fractional part */
- while(pitch > (cent_interp[fractional_index + 1] *
- semitone_table[semitone] / PITCH_SPEED_100))
- {
- /* Check to make sure fractional_index isn't too big */
- /* This should never happen. */
- if(fractional_index >= CENT_INTERP_NUM - 1)
- {
- break;
- }
- fractional_index++;
- }
-
- int32_t semitone_pitch_a = cent_interp[fractional_index] *
- semitone_table[semitone] /
- PITCH_SPEED_100;
- int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
- semitone_table[semitone] /
- PITCH_SPEED_100;
- /* this will be the integer offset from the cent_interp entry */
- int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
- (semitone_pitch_b - semitone_pitch_a);
- semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
- fractional_index * CENT_INTERP_INTERVAL +
- semitone_frac_ofs;
-
- return semitone;
-}
-
-static int32_t get_pitch_from_semitone(int32_t semitone)
-{
- int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
-
- /* Find the index into the semitone table */
- int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
-
- /* set pitch to the semitone's integer part value */
- int32_t pitch = semitone_table[semitone_index];
- /* get the range of the cent modification for future calculation */
- int32_t pitch_mod_a =
- cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
- CENT_INTERP_INTERVAL];
- int32_t pitch_mod_b =
- cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
- CENT_INTERP_INTERVAL + 1];
- /* figure out the cent mod amount based on the semitone fractional value */
- int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
- (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
-
- /* modify pitch based on the mod amount we just calculated */
- return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
-}
-
-static int32_t pitch_increase_semitone(int32_t pitch,
- int32_t current_semitone,
- int32_t semitone_delta
- , int32_t speed
- )
-{
- int32_t new_semitone = current_semitone;
-
- /* snap to the delta interval */
- if(current_semitone % semitone_delta != 0)
- {
- if(current_semitone > 0 && semitone_delta > 0)
- new_semitone += semitone_delta;
- else if(current_semitone < 0 && semitone_delta < 0)
- new_semitone += semitone_delta;
-
- new_semitone -= new_semitone % semitone_delta;
- }
- else
- new_semitone += semitone_delta;
-
- /* clamp the pitch so it doesn't go beyond the pitch limits */
- if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
- {
- new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
- at_limit = true;
- }
- else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
- {
- new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
- at_limit = true;
- }
-
- int32_t new_pitch = get_pitch_from_semitone(new_semitone);
-
- int32_t new_stretch = GET_STRETCH(new_pitch, speed);
-
- /* clamp the pitch so it doesn't go beyond the stretch limits */
- if( new_stretch > STRETCH_MAX)
- {
- new_pitch = GET_PITCH(speed, STRETCH_MAX);
- new_semitone = get_semitone_from_pitch(new_pitch);
- at_limit = true;
- }
- else if (new_stretch < STRETCH_MIN)
- {
- new_pitch = GET_PITCH(speed, STRETCH_MIN);
- new_semitone = get_semitone_from_pitch(new_pitch);
- at_limit = true;
- }
-
- pitch_increase(pitch, new_pitch - pitch, false
- , speed
- );
-
- return new_semitone;
-}
-
-#ifdef HAVE_TOUCHSCREEN
-/*
- * Check for touchscreen presses as per sketch above in this file
- *
- * goes through each row of the, checks whether the touchscreen
- * was pressed in it. Then it looks the columns of each row for specific actions
- */
-static int pitchscreen_do_touchscreen(struct viewport vps[])
-{
- short x, y;
- struct viewport *this_vp = &vps[PITCH_TOP];
- int ret;
- static bool wait_for_release = false;
- ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
-
- /* top row */
- if (ret > ACTION_UNKNOWN)
- { /* press on top row, left or right column
- * only toggle mode if released */
- int column = this_vp->width / 3;
- if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
- return ACTION_PS_TOGGLE_MODE;
-
-
- else if (x >= column && x <= (2*column))
- { /* center column pressed */
- if (ret == BUTTON_REPEAT)
- return ACTION_PS_INC_BIG;
- else if (ret & BUTTON_REL)
- return ACTION_PS_INC_SMALL;
- }
- return ACTION_NONE;
- }
-
- /* now the center row */
- this_vp = &vps[PITCH_MID];
- ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
-
- if (ret > ACTION_UNKNOWN)
- {
- int column = this_vp->width / 3;
-
- if (x < column)
- { /* left column */
- if (ret & BUTTON_REL)
- {
- wait_for_release = false;
- return ACTION_PS_NUDGE_LEFTOFF;
- }
- else if (ret & BUTTON_REPEAT)
- return ACTION_PS_SLOWER;
- if (!wait_for_release)
- {
- wait_for_release = true;
- return ACTION_PS_NUDGE_LEFT;
- }
- }
- else if (x > (2*column))
- { /* right column */
- if (ret & BUTTON_REL)
- {
- wait_for_release = false;
- return ACTION_PS_NUDGE_RIGHTOFF;
- }
- else if (ret & BUTTON_REPEAT)
- return ACTION_PS_FASTER;
- if (!wait_for_release)
- {
- wait_for_release = true;
- return ACTION_PS_NUDGE_RIGHT;
- }
- }
- else
- /* center column was pressed */
- return ACTION_PS_RESET;
- }
-
- /* now the bottom row */
- this_vp = &vps[PITCH_BOTTOM];
- ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
-
- if (ret > ACTION_UNKNOWN)
- {
- int column = this_vp->width / 3;
-
- /* left or right column is exit */
- if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
- return ACTION_PS_EXIT;
- else if (x >= column && x <= (2*column))
- { /* center column was pressed */
- if (ret & BUTTON_REPEAT)
- return ACTION_PS_DEC_BIG;
- else if (ret & BUTTON_REL)
- return ACTION_PS_DEC_SMALL;
- }
- return ACTION_NONE;
- }
- return ACTION_NONE;
-}
-
-#endif
-/*
- returns:
- 0 on exit
- 1 if USB was connected
-*/
-
+#include "plugin.h"
int gui_syncpitchscreen_run(void)
{
- int button;
- int32_t pitch = sound_get_pitch();
- int32_t semitone;
-
- int32_t new_pitch;
- int32_t pitch_delta;
- bool nudged = false;
- int i, updated = 4, decimals = 0;
- bool exit = false;
- /* should maybe be passed per parameter later, not needed for now */
- struct viewport parent[NB_SCREENS];
- struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
- int max_lines[NB_SCREENS];
-
- push_current_activity(ACTIVITY_PITCHSCREEN);
-
- int32_t new_speed = 0, new_stretch;
-
- /* the speed variable holds the apparent speed of the playback */
- int32_t speed;
- if (dsp_timestretch_available())
- {
- speed = GET_SPEED(pitch, dsp_get_timestretch());
- }
- else
- {
- speed = pitch;
- }
-
- /* Figure out whether to be in timestretch mode */
- if (global_settings.pitch_mode_timestretch && !dsp_timestretch_available())
- {
- global_settings.pitch_mode_timestretch = false;
- settings_save();
- }
-
- /* Count decimals for speaking */
- for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
- decimals++;
-
- /* set the semitone index based on the current pitch */
- semitone = get_semitone_from_pitch(pitch);
-
- /* initialize pitchscreen vps */
- FOR_NB_SCREENS(i)
- {
- viewport_set_defaults(&parent[i], i);
- max_lines[i] = viewport_get_nb_lines(&parent[i]);
- pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
- screens[i].set_viewport(&parent[i]);
- screens[i].clear_viewport();
-
- /* also, draw the icons now, it's only needed once */
- pitchscreen_draw_icons(&screens[i], &parent[i]);
- }
- pcmbuf_set_low_latency(true);
-
- while (!exit)
- {
- FOR_NB_SCREENS(i)
- pitchscreen_draw(&screens[i], max_lines[i],
- pitch_viewports[i], pitch, semitone
- , speed
- );
- pitch_delta = 0;
- new_speed = 0;
-
- if (global_settings.talk_menu && updated)
- {
- talk_shutup();
- switch (updated)
- {
- case 1:
- if (global_settings.pitch_mode_semitone)
- talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
- else
- talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
- break;
- case 2:
- talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
- break;
- case 3:
- speak_pitch_mode(false);
- break;
- case 4:
- if (global_settings.pitch_mode_timestretch && dsp_timestretch_available())
- talk_id(LANG_PITCH, false);
- else
- talk_id(LANG_PLAYBACK_RATE, false);
- talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
- if (global_settings.pitch_mode_timestretch && dsp_timestretch_available())
- {
- talk_id(LANG_SPEED, true);
- talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
- }
- speak_pitch_mode(true);
- break;
- default:
- break;
- }
- }
- updated = 0;
-
- button = get_action(CONTEXT_PITCHSCREEN, HZ);
-
-#ifdef HAVE_TOUCHSCREEN
- if (button == ACTION_TOUCHSCREEN)
- {
- FOR_NB_SCREENS(i)
- button = pitchscreen_do_touchscreen(pitch_viewports[i]);
- }
-#endif
- switch (button)
- {
- case ACTION_PS_INC_SMALL:
- if(global_settings.pitch_mode_semitone)
- pitch_delta = SEMITONE_SMALL_DELTA;
- else
- pitch_delta = PITCH_SMALL_DELTA;
- updated = 1;
- break;
-
- case ACTION_PS_INC_BIG:
- if(global_settings.pitch_mode_semitone)
- pitch_delta = SEMITONE_BIG_DELTA;
- else
- pitch_delta = PITCH_BIG_DELTA;
- updated = 1;
- break;
-
- case ACTION_PS_DEC_SMALL:
- if(global_settings.pitch_mode_semitone)
- pitch_delta = -SEMITONE_SMALL_DELTA;
- else
- pitch_delta = -PITCH_SMALL_DELTA;
- updated = 1;
- break;
-
- case ACTION_PS_DEC_BIG:
- if(global_settings.pitch_mode_semitone)
- pitch_delta = -SEMITONE_BIG_DELTA;
- else
- pitch_delta = -PITCH_BIG_DELTA;
- updated = 1;
- break;
-
- case ACTION_PS_NUDGE_RIGHT:
- if (!global_settings.pitch_mode_timestretch)
- {
- new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
- , speed
- );
- nudged = (new_pitch != pitch);
- pitch = new_pitch;
- semitone = get_semitone_from_pitch(pitch);
- speed = pitch;
- updated = nudged ? 1 : 0;
- break;
- }
- else
- {
- new_speed = speed + SPEED_SMALL_DELTA;
- at_limit = false;
- updated = 2;
- }
- break;
-
- case ACTION_PS_FASTER:
- if (global_settings.pitch_mode_timestretch)
- {
- new_speed = speed + SPEED_BIG_DELTA;
- /* snap to whole numbers */
- if(new_speed % PITCH_SPEED_PRECISION != 0)
- new_speed -= new_speed % PITCH_SPEED_PRECISION;
- at_limit = false;
- updated = 2;
- }
- break;
-
- case ACTION_PS_NUDGE_RIGHTOFF:
- if (nudged)
- {
- pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
- , speed
- );
- speed = pitch;
- semitone = get_semitone_from_pitch(pitch);
- nudged = false;
- updated = 1;
- }
- break;
-
- case ACTION_PS_NUDGE_LEFT:
- if (!global_settings.pitch_mode_timestretch)
- {
- new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
- , speed
- );
- nudged = (new_pitch != pitch);
- pitch = new_pitch;
- semitone = get_semitone_from_pitch(pitch);
- speed = pitch;
- updated = nudged ? 1 : 0;
- break;
- }
- else
- {
- new_speed = speed - SPEED_SMALL_DELTA;
- at_limit = false;
- updated = 2;
- }
- break;
-
- case ACTION_PS_SLOWER:
- if (global_settings.pitch_mode_timestretch)
- {
- new_speed = speed - SPEED_BIG_DELTA;
- /* snap to whole numbers */
- if(new_speed % PITCH_SPEED_PRECISION != 0)
- new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
- at_limit = false;
- updated = 2;
- }
- break;
-
- case ACTION_PS_NUDGE_LEFTOFF:
- if (nudged)
- {
- pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
- , speed
- );
- speed = pitch;
- semitone = get_semitone_from_pitch(pitch);
- nudged = false;
- updated = 1;
- }
- break;
-
- case ACTION_PS_RESET:
- pitch = PITCH_SPEED_100;
- sound_set_pitch(pitch);
- speed = PITCH_SPEED_100;
- if (dsp_timestretch_available())
- {
- dsp_set_timestretch(PITCH_SPEED_100);
- at_limit = false;
- }
- semitone = get_semitone_from_pitch(pitch);
- updated = 4;
- break;
-
- case ACTION_PS_TOGGLE_MODE:
- global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
-
- if (dsp_timestretch_available() && !global_settings.pitch_mode_semitone)
- {
- global_settings.pitch_mode_timestretch = !global_settings.pitch_mode_timestretch;
- if(!global_settings.pitch_mode_timestretch)
- {
- /* no longer in timestretch mode. Reset speed */
- speed = pitch;
- dsp_set_timestretch(PITCH_SPEED_100);
- }
- }
- settings_save();
- updated = 3;
- break;
-
- case ACTION_PS_EXIT:
- exit = true;
- break;
-
- default:
- if (default_event_handler(button) == SYS_USB_CONNECTED)
- return 1;
- break;
- }
- if (pitch_delta)
- {
- if (global_settings.pitch_mode_semitone)
- {
- semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
- , speed
- );
- pitch = get_pitch_from_semitone(semitone);
- }
- else
- {
- pitch = pitch_increase(pitch, pitch_delta, true
- , speed
- );
- semitone = get_semitone_from_pitch(pitch);
- }
- if (global_settings.pitch_mode_timestretch)
- {
- /* do this to make sure we properly obey the stretch limits */
- new_speed = speed;
- }
- else
- {
- speed = pitch;
- }
- }
-
- if(new_speed)
- {
- new_stretch = GET_STRETCH(pitch, new_speed);
-
- /* limit the amount of stretch */
- if(new_stretch > STRETCH_MAX)
- {
- new_stretch = STRETCH_MAX;
- new_speed = GET_SPEED(pitch, new_stretch);
- }
- else if(new_stretch < STRETCH_MIN)
- {
- new_stretch = STRETCH_MIN;
- new_speed = GET_SPEED(pitch, new_stretch);
- }
-
- new_stretch = GET_STRETCH(pitch, new_speed);
- if(new_stretch >= STRETCH_MAX ||
- new_stretch <= STRETCH_MIN)
- {
- at_limit = true;
- }
-
- /* set the amount of stretch */
- dsp_set_timestretch(new_stretch);
-
- /* update the speed variable with the new speed */
- speed = new_speed;
-
- /* Reset new_speed so we only call dsp_set_timestretch */
- /* when needed */
- new_speed = 0;
- }
- }
-
- pcmbuf_set_low_latency(false);
- pop_current_activity();
-
- /* Clean up */
- FOR_NB_SCREENS(i)
- {
- screens[i].set_viewport(NULL);
- }
-
- return 0;
+ return (plugin_load(VIEWERS_DIR"/pitch_screen.rock", NULL) == PLUGIN_USB_CONNECTED);
}
diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c
index f8bf98d4ee..b2f5050ab3 100644
--- a/apps/gui/quickscreen.c
+++ b/apps/gui/quickscreen.c
@@ -248,20 +248,21 @@ static void talk_qs_option(const struct settings_list *opt, bool enqueue)
static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button)
{
int item;
- bool invert = false;
+ bool previous = false;
switch(button)
{
case ACTION_QS_TOP:
- invert = true;
item = QUICKSCREEN_TOP;
break;
+
case ACTION_QS_LEFT:
- invert = true;
item = QUICKSCREEN_LEFT;
+ previous = true;
break;
case ACTION_QS_DOWN:
item = QUICKSCREEN_BOTTOM;
+ previous = true;
break;
case ACTION_QS_RIGHT:
@@ -271,37 +272,40 @@ static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button)
default:
return false;
}
+
if (qs->items[item] == NULL)
return false;
-#ifdef ASCENDING_INT_SETTINGS
- if (((qs->items[item]->flags & F_INT_SETTING) == F_INT_SETTING) &&
- ( button == ACTION_QS_DOWN || button == ACTION_QS_TOP))
- {
- invert = !invert;
- }
-#endif
- option_select_next_val(qs->items[item], invert, true);
+
+ option_select_next_val(qs->items[item], previous, true);
talk_qs_option(qs->items[item], false);
return true;
}
#ifdef HAVE_TOUCHSCREEN
-static int quickscreen_touchscreen_button(const struct viewport
- vps[QUICKSCREEN_ITEM_COUNT])
+static int quickscreen_touchscreen_button(void)
{
short x,y;
/* only hitting the text counts, everything else is exit */
if (action_get_touchscreen_press(&x, &y) != BUTTON_REL)
return ACTION_NONE;
- else if (viewport_point_within_vp(&vps[QUICKSCREEN_TOP], x, y))
+
+ enum { left=1, right=2, top=4, bottom=8 };
+
+ int bits = (x < LCD_WIDTH/3 ? left : (x > 2*LCD_WIDTH/3 ? 2 : right)) |
+ (y < LCD_WIDTH/3 ? top : (y > 2*LCD_WIDTH/3 ? 8 : bottom));
+
+ switch(bits) {
+ case top:
return ACTION_QS_TOP;
- else if (viewport_point_within_vp(&vps[QUICKSCREEN_BOTTOM], x, y))
+ case bottom:
return ACTION_QS_DOWN;
- else if (viewport_point_within_vp(&vps[QUICKSCREEN_LEFT], x, y))
+ case left:
return ACTION_QS_LEFT;
- else if (viewport_point_within_vp(&vps[QUICKSCREEN_RIGHT], x, y))
+ case right:
return ACTION_QS_RIGHT;
- return ACTION_STD_CANCEL;
+ default:
+ return ACTION_STD_CANCEL;
+ }
}
#endif
@@ -343,7 +347,7 @@ static bool gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_ente
button = get_action(CONTEXT_QUICKSCREEN, HZ/5);
#ifdef HAVE_TOUCHSCREEN
if (button == ACTION_TOUCHSCREEN)
- button = quickscreen_touchscreen_button(vps[SCREEN_MAIN]);
+ button = quickscreen_touchscreen_button();
#endif
if (default_event_handler(button) == SYS_USB_CONNECTED)
{
diff --git a/apps/gui/skin_engine/skin_engine.c b/apps/gui/skin_engine/skin_engine.c
index ce3401f41c..b3626b681d 100644
--- a/apps/gui/skin_engine/skin_engine.c
+++ b/apps/gui/skin_engine/skin_engine.c
@@ -155,6 +155,9 @@ void settings_apply_skins(void)
char filename[MAX_PATH];
static bool first_run = true;
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ audio_stop();
+
skin_backdrop_init();
skins_initialised = true;
diff --git a/apps/gui/skin_engine/skin_parser.c b/apps/gui/skin_engine/skin_parser.c
index a1cc40ff6e..b3840f689f 100644
--- a/apps/gui/skin_engine/skin_parser.c
+++ b/apps/gui/skin_engine/skin_parser.c
@@ -768,7 +768,7 @@ static int parse_setting_and_lang(struct skin_element *element,
#ifndef __PCTOOL__
i = lang_english_to_id(temp);
if (i < 0)
- return WPS_ERROR_INVALID_PARAM;
+ i = LANG_LAST_INDEX_IN_ARRAY;
#endif
}
else if (element->params_count > 1)
@@ -1187,7 +1187,7 @@ static int parse_progressbar_tag(struct skin_element* element,
region->reverse_bar = false;
region->allow_while_locked = false;
region->press_length = PRESS;
- region->last_press = 0xffff;
+ region->last_press = -1;
region->armed = false;
region->bar = PTRTOSKINOFFSET(skin_buffer, pb);
@@ -1602,7 +1602,7 @@ static int parse_touchregion(struct skin_element *element,
region->armed = false;
region->reverse_bar = false;
region->value = 0;
- region->last_press = 0xffff;
+ region->last_press = -1;
region->press_length = PRESS;
region->allow_while_locked = false;
region->bar = PTRTOSKINOFFSET(skin_buffer, NULL);
diff --git a/apps/gui/skin_engine/skin_tokens.c b/apps/gui/skin_engine/skin_tokens.c
index a0de45d3e3..27022b87d1 100644
--- a/apps/gui/skin_engine/skin_tokens.c
+++ b/apps/gui/skin_engine/skin_tokens.c
@@ -821,7 +821,8 @@ const char *get_token_value(struct gui_wps *gwps,
return (char*)SKINOFFSETTOPTR(get_skin_buffer(data), token->value.data);
case SKIN_TOKEN_TRANSLATEDSTRING:
- return (char*)P2STR(ID2P(token->value.i));
+ return token->value.i < LANG_LAST_INDEX_IN_ARRAY ?
+ (char*)P2STR(ID2P(token->value.i)) : "<ERR>";
case SKIN_TOKEN_PLAYLIST_ENTRIES:
numeric_ret = playlist_amount();
@@ -1366,7 +1367,7 @@ const char *get_token_value(struct gui_wps *gwps,
case SKIN_TOKEN_LASTTOUCH:
{
#ifdef HAVE_TOUCHSCREEN
- unsigned int last_touch = touchscreen_last_touch();
+ long last_touch = touchscreen_last_touch();
char *skin_base = get_skin_buffer(data);
struct touchregion_lastpress *data = SKINOFFSETTOPTR(skin_base, token->value.data);
if (!data) return NULL;
@@ -1374,7 +1375,7 @@ const char *get_token_value(struct gui_wps *gwps,
if (region)
last_touch = region->last_press;
- if (last_touch != 0xffff &&
+ if (last_touch != -1 &&
TIME_BEFORE(current_tick, data->timeout + last_touch))
return "t";
#endif
diff --git a/apps/keymaps/keymap-fiiom3k.c b/apps/keymaps/keymap-fiiom3k.c
index 4cd2691d33..749f25a35d 100644
--- a/apps/keymaps/keymap-fiiom3k.c
+++ b/apps/keymaps/keymap-fiiom3k.c
@@ -40,39 +40,41 @@ static const struct button_mapping button_context_standard[] = {
{ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT},
{ACTION_STD_CANCEL, BUTTON_BACK|BUTTON_REL, BUTTON_BACK},
{ACTION_STD_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT},
- {ACTION_STD_CONTEXT, BUTTON_MENU|BUTTON_REL, BUTTON_MENU},
- {ACTION_STD_MENU, BUTTON_BACK|BUTTON_REPEAT, BUTTON_BACK},
- {ACTION_STD_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
+ {ACTION_STD_QUICKSCREEN, BUTTON_MENU|BUTTON_REL, BUTTON_MENU},
+ {ACTION_STD_MENU, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
{ACTION_STD_KEYLOCK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
- {ACTION_STD_HOTKEY, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY},
LAST_ITEM_IN_LIST
}; /* button_context_standard */
static const struct button_mapping button_context_wps[] = {
{ACTION_WPS_PLAY, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY},
- {ACTION_WPS_PLAY, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT},
+ {ACTION_WPS_VIEW_PLAYLIST, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT},
{ACTION_WPS_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
{ACTION_WPS_VOLUP, BUTTON_VOL_UP, BUTTON_NONE},
{ACTION_WPS_VOLUP, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_WPS_VOLDOWN, BUTTON_VOL_DOWN, BUTTON_NONE},
{ACTION_WPS_VOLDOWN, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_WPS_VOLUP, BUTTON_SCROLL_BACK, BUTTON_NONE},
+ {ACTION_WPS_VOLUP, BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_WPS_VOLDOWN, BUTTON_SCROLL_FWD, BUTTON_NONE},
+ {ACTION_WPS_VOLDOWN, BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_WPS_VOLUP, BUTTON_UP|BUTTON_REL, BUTTON_UP},
+ {ACTION_WPS_VOLDOWN, BUTTON_DOWN|BUTTON_REL, BUTTON_DOWN},
{ACTION_WPS_SKIPNEXT, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
{ACTION_WPS_SKIPPREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
{ACTION_WPS_SEEKFWD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_WPS_STOPSEEK, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT},
{ACTION_WPS_SEEKBACK, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_WPS_STOPSEEK, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT},
- {ACTION_WPS_BROWSE, BUTTON_BACK|BUTTON_REPEAT, BUTTON_BACK},
- {ACTION_WPS_MENU, BUTTON_BACK|BUTTON_REL, BUTTON_BACK},
- {ACTION_WPS_CONTEXT, BUTTON_MENU|BUTTON_REL, BUTTON_MENU},
- {ACTION_WPS_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
+ {ACTION_WPS_BROWSE, BUTTON_BACK|BUTTON_REL, BUTTON_BACK},
+ {ACTION_WPS_QUICKSCREEN, BUTTON_MENU|BUTTON_REL, BUTTON_MENU},
+ {ACTION_WPS_MENU, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
{ACTION_STD_KEYLOCK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
{ACTION_WPS_HOTKEY, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY},
- {ACTION_WPS_VIEW_PLAYLIST, BUTTON_SCROLL_FWD, BUTTON_NONE},
- {ACTION_WPS_VIEW_PLAYLIST, BUTTON_SCROLL_BACK, BUTTON_NONE},
+ {ACTION_WPS_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT},
{ACTION_WPS_ABSETA_PREVDIR, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
{ACTION_WPS_ABSETB_NEXTDIR, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_DOWN},
- {ACTION_WPS_ABRESET, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT},
+ {ACTION_WPS_ABRESET, BUTTON_BACK|BUTTON_REPEAT, BUTTON_BACK},
LAST_ITEM_IN_LIST
}; /* button_context_wps */
@@ -87,11 +89,20 @@ static const struct button_mapping button_context_wps_locked[] = {
}; /* button_context_wps_locked */
static const struct button_mapping button_context_tree[] = {
- {ACTION_TREE_STOP, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY},
- {ACTION_TREE_WPS, BUTTON_BACK|BUTTON_REPEAT, BUTTON_BACK},
+ {ACTION_TREE_WPS, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY },
+ {ACTION_TREE_HOTKEY, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY},
+ {ACTION_TREE_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST)
}; /* button_context_tree */
+
+static const struct button_mapping button_context_tree_scroll_lr[] = {
+ { ACTION_TREE_ROOT_INIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU },
+ { ACTION_TREE_PGLEFT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE },
+ { ACTION_TREE_PGRIGHT, BUTTON_BACK|BUTTON_REPEAT, BUTTON_NONE },
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|CONTEXT_TREE),
+}; /* button_context_tree_scroll_lr */
+
static const struct button_mapping button_context_bmark[] = {
{ACTION_BMS_DELETE, BUTTON_PLAY, BUTTON_NONE},
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE),
@@ -140,14 +151,25 @@ static const struct button_mapping button_context_settings_eq[] = {
static const struct button_mapping button_context_quickscreen[] = {
{ACTION_QS_TOP, BUTTON_UP, BUTTON_NONE},
+ {ACTION_QS_TOP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_QS_TOP, BUTTON_SCROLL_BACK, BUTTON_NONE},
+ {ACTION_QS_TOP, BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_QS_DOWN, BUTTON_DOWN, BUTTON_NONE},
+ {ACTION_QS_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_QS_DOWN, BUTTON_SCROLL_FWD, BUTTON_NONE},
+ {ACTION_QS_DOWN, BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_QS_LEFT, BUTTON_LEFT, BUTTON_NONE},
+ {ACTION_QS_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_QS_RIGHT, BUTTON_RIGHT, BUTTON_NONE},
+ {ACTION_QS_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_QS_VOLUP, BUTTON_VOL_UP, BUTTON_NONE},
+ {ACTION_QS_VOLUP, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_QS_VOLDOWN, BUTTON_VOL_DOWN, BUTTON_NONE},
+ {ACTION_QS_VOLDOWN, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_STD_CANCEL, BUTTON_SELECT, BUTTON_NONE},
{ACTION_STD_CANCEL, BUTTON_POWER, BUTTON_NONE},
{ACTION_STD_CANCEL, BUTTON_BACK, BUTTON_NONE},
+ {ACTION_STD_CANCEL, BUTTON_MENU, BUTTON_NONE},
LAST_ITEM_IN_LIST
}; /* button_context_quickscreen */
@@ -188,6 +210,10 @@ static const struct button_mapping button_context_keyboard[] = {
{ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE},
{ACTION_KBD_RIGHT, BUTTON_RIGHT, BUTTON_NONE},
{ACTION_KBD_UP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_KBD_UP, BUTTON_SCROLL_BACK, BUTTON_NONE},
+ {ACTION_KBD_UP, BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_KBD_DOWN, BUTTON_SCROLL_FWD, BUTTON_NONE},
+ {ACTION_KBD_DOWN, BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_KBD_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
{ACTION_KBD_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
@@ -279,6 +305,10 @@ const struct button_mapping* get_context_mapping(int context)
return button_context_wps;
case CONTEXT_TREE:
case CONTEXT_MAINMENU:
+ if (global_settings.hold_lr_for_scroll_in_list)
+ return button_context_tree_scroll_lr;
+ /* else fall through to CUSTOM|CONTEXT_TREE */
+ case CONTEXT_CUSTOM|CONTEXT_TREE:
return button_context_tree;
case CONTEXT_BOOKMARKSCREEN:
return button_context_bmark;
diff --git a/apps/keymaps/keymap-hm60x.c b/apps/keymaps/keymap-hm60x.c
index 1050ec7f31..2e1f3dd516 100644
--- a/apps/keymaps/keymap-hm60x.c
+++ b/apps/keymaps/keymap-hm60x.c
@@ -176,7 +176,7 @@ static const struct button_mapping button_context_tree_scroll_lr[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesnoscreen */
/* get_context_mapping returns a pointer to one of the above defined arrays depending on the context */
diff --git a/apps/keymaps/keymap-hm801.c b/apps/keymaps/keymap-hm801.c
index b69656a3f1..1f0a49c90c 100644
--- a/apps/keymaps/keymap-hm801.c
+++ b/apps/keymaps/keymap-hm801.c
@@ -202,7 +202,7 @@ static const struct button_mapping button_context_tree_scroll_lr[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesnoscreen */
/* get_context_mapping returns a pointer to one of the above defined arrays depending on the context */
diff --git a/apps/keymaps/keymap-ihifi.c b/apps/keymaps/keymap-ihifi.c
index 0d574c19e6..147c2f5f62 100644
--- a/apps/keymaps/keymap-ihifi.c
+++ b/apps/keymaps/keymap-ihifi.c
@@ -175,7 +175,7 @@ static const struct button_mapping button_context_tree_scroll_lr[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesnoscreen */
/* get_context_mapping returns a pointer to one of the above defined arrays depending on the context */
diff --git a/apps/keymaps/keymap-m3.c b/apps/keymaps/keymap-m3.c
index 6192d7ff6b..bcd96d664b 100644
--- a/apps/keymaps/keymap-m3.c
+++ b/apps/keymaps/keymap-m3.c
@@ -379,12 +379,12 @@ static const struct button_mapping remote_button_context_wps[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesnoscreen */
static const struct button_mapping remote_button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_RC_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* remote_button_context_yesnoscreen */
diff --git a/apps/keymaps/keymap-ma.c b/apps/keymaps/keymap-ma.c
index e1740b697b..28220d9f50 100644
--- a/apps/keymaps/keymap-ma.c
+++ b/apps/keymaps/keymap-ma.c
@@ -183,7 +183,7 @@ static const struct button_mapping button_context_tree_scroll_lr[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesnoscreen */
/* get_context_mapping returns a pointer to one of the above defined arrays depending on the context */
diff --git a/apps/keymaps/keymap-mpio-hd200.c b/apps/keymaps/keymap-mpio-hd200.c
index 71b3190384..019d4bfac4 100644
--- a/apps/keymaps/keymap-mpio-hd200.c
+++ b/apps/keymaps/keymap-mpio-hd200.c
@@ -105,8 +105,10 @@ static const struct button_mapping button_context_settings[] = {
static const struct button_mapping button_context_yesno[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_FUNC, BUTTON_NONE },
{ ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_VOL_UP, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_VOL_DOWN, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
static const struct button_mapping button_context_bmark[] = {
@@ -269,7 +271,7 @@ static const struct button_mapping button_rc_context_yesno[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_RC_FUNC, BUTTON_NONE },
{ ACTION_YESNO_ACCEPT, BUTTON_RC_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
static const struct button_mapping button_rc_context_radio[] = {
diff --git a/apps/keymaps/keymap-mpio-hd300.c b/apps/keymaps/keymap-mpio-hd300.c
index cdb617bed0..7941d1704b 100644
--- a/apps/keymaps/keymap-mpio-hd300.c
+++ b/apps/keymaps/keymap-mpio-hd300.c
@@ -109,8 +109,11 @@ static const struct button_mapping button_context_settings[] = {
static const struct button_mapping button_context_yesno[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_ENTER, BUTTON_NONE },
{ ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_REW, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_FF, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_REC, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
static const struct button_mapping button_context_bmark[] = {
diff --git a/apps/keymaps/keymap-sansa-connect.c b/apps/keymaps/keymap-sansa-connect.c
index 6a6d5de955..b339744b0f 100644
--- a/apps/keymaps/keymap-sansa-connect.c
+++ b/apps/keymaps/keymap-sansa-connect.c
@@ -70,7 +70,12 @@ static const struct button_mapping button_context_wps[] = {
static const struct button_mapping button_context_yesno[] = {
{ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE},
- LAST_ITEM_IN_LIST
+ {ACTION_STD_CANCEL, BUTTON_PREV, BUTTON_NONE},
+ {ACTION_STD_CANCEL, BUTTON_NEXT, BUTTON_NONE},
+ {ACTION_STD_CANCEL, BUTTON_VOL_UP, BUTTON_NONE},
+ {ACTION_STD_CANCEL, BUTTON_VOL_DOWN, BUTTON_NONE},
+ {ACTION_STD_CANCEL, BUTTON_POWER, BUTTON_NONE},
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
static const struct button_mapping button_context_settings_time[] = {
diff --git a/apps/keymaps/keymap-shanlingq1.c b/apps/keymaps/keymap-shanlingq1.c
index cb0e0a44ff..4caaa36fd2 100644
--- a/apps/keymaps/keymap-shanlingq1.c
+++ b/apps/keymaps/keymap-shanlingq1.c
@@ -64,7 +64,7 @@ static const struct button_mapping button_context_yesno[] = {
/* note: touchscreen buttons are usable in addition to physical keys */
{ACTION_YESNO_ACCEPT, BUTTON_PLAY, BUTTON_NONE},
{ACTION_STD_CANCEL, BUTTON_POWER, BUTTON_NONE},
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
const struct button_mapping* target_get_context_mapping(int context)
diff --git a/apps/keymaps/keymap-vibe500.c b/apps/keymaps/keymap-vibe500.c
index 49f77628ce..5116ec8575 100644
--- a/apps/keymaps/keymap-vibe500.c
+++ b/apps/keymaps/keymap-vibe500.c
@@ -113,7 +113,9 @@ static const struct button_mapping button_context_settings[] = {
static const struct button_mapping button_context_yesno[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_OK, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ { ACTION_STD_CANCEL, BUTTON_PLAY, BUTTON_NONE },
+ { ACTION_STD_CANCEL, BUTTON_REC, BUTTON_NONE },
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_yesno */
static const struct button_mapping button_context_bmark[] = {
diff --git a/apps/keymaps/keymap-x5.c b/apps/keymaps/keymap-x5.c
index fb2fbfa605..249df7bf3c 100644
--- a/apps/keymaps/keymap-x5.c
+++ b/apps/keymaps/keymap-x5.c
@@ -405,12 +405,12 @@ static const struct button_mapping remote_button_context_wps[] = {
/** Yes/No Screen **/
static const struct button_mapping button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesnoscreen */
static const struct button_mapping remote_button_context_yesnoscreen[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_RC_PLAY, BUTTON_NONE },
- LAST_ITEM_IN_LIST
+ LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* remote_button_context_settings_yesnoscreen */
diff --git a/apps/lang/dansk.lang b/apps/lang/dansk.lang
index 536ecb28ec..aff651cc52 100644
--- a/apps/lang/dansk.lang
+++ b/apps/lang/dansk.lang
@@ -12652,3 +12652,17 @@
*: "Start auto-sluk ved opstart"
</voice>
</phrase>
+<phrase>
+ id: VOICE_NUMERIC_TENS_SWAP_SEPARATOR
+ desc: voice only, for speaking numbers in languages that swap the tens and ones fields. Leave blank for languages that do not need it, such as English ("231" => "two hundred thirty one") but other languages may speak it as "two hundred one [AND] thirty"
+ user: core
+ <source>
+ *: ""
+ </source>
+ <dest>
+ *: ""
+ </dest>
+ <voice>
+ *: "og"
+ </voice>
+</phrase>
diff --git a/apps/lang/english-us.lang b/apps/lang/english-us.lang
index 14b33569d2..6fac22c381 100644
--- a/apps/lang/english-us.lang
+++ b/apps/lang/english-us.lang
@@ -1300,6 +1300,20 @@
</voice>
</phrase>
<phrase>
+ id: LANG_SINGLE_MODE
+ desc: single mode
+ user: core
+ <source>
+ *: "Single Mode"
+ </source>
+ <dest>
+ *: "Single Mode"
+ </dest>
+ <voice>
+ *: "Single Mode"
+ </voice>
+</phrase>
+<phrase>
id: LANG_PARTY_MODE
desc: party mode
user: core
@@ -16013,3 +16027,31 @@
*: "Bit rate"
</voice>
</phrase>
+<phrase>
+ id: VOICE_NUMERIC_TENS_SWAP_SEPARATOR
+ desc: voice only, for speaking numbers in languages that swap the tens and ones fields. Leave blank for languages that do not need it, such as English ("231" => "two hundred thirty one") but other languages may speak it as "two hundred one [AND] thirty"
+ user: core
+ <source>
+ *: ""
+ </source>
+ <dest>
+ *: ""
+ </dest>
+ <voice>
+ *: ""
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_VOICED_DATE_FORMAT
+ desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021"
+ user: core
+ <source>
+ *: "dAY"
+ </source>
+ <dest>
+ *: "AdY"
+ </dest>
+ <voice>
+ *: ""
+ </voice>
+</phrase>
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 67624aa2a3..50cec84b7c 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -1376,6 +1376,20 @@
</voice>
</phrase>
<phrase>
+ id: LANG_SINGLE_MODE
+ desc: single mode
+ user: core
+ <source>
+ *: "Single Mode"
+ </source>
+ <dest>
+ *: "Single Mode"
+ </dest>
+ <voice>
+ *: "Single Mode"
+ </voice>
+</phrase>
+<phrase>
id: LANG_PARTY_MODE
desc: party mode
user: core
@@ -16080,3 +16094,45 @@
*: "Bit rate"
</voice>
</phrase>
+<phrase>
+ id: VOICE_NUMERIC_TENS_SWAP_SEPARATOR
+ desc: voice only, for speaking numbers in languages that swap the tens and ones fields. Leave blank for languages that do not need it, such as English ("231" => "two hundred thirty one") but other languages may speak it as "two hundred one [AND] thirty"
+ user: core
+ <source>
+ *: ""
+ </source>
+ <dest>
+ *: ""
+ </dest>
+ <voice>
+ *: ""
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_VOICED_DATE_FORMAT
+ desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021"
+ user: core
+ <source>
+ *: "dAY"
+ </source>
+ <dest>
+ *: "dAY"
+ </dest>
+ <voice>
+ *: ""
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_LIST_WRAPAROUND
+ desc: in Settings
+ user: core
+ <source>
+ *: "List Wraparound"
+ </source>
+ <dest>
+ *: "List Wraparound"
+ </dest>
+ <voice>
+ *: "List Wraparound"
+ </voice>
+</phrase>
diff --git a/apps/lang/hebrew.lang b/apps/lang/hebrew.lang
index 2f732f0eef..d4da2443b8 100644
--- a/apps/lang/hebrew.lang
+++ b/apps/lang/hebrew.lang
@@ -1461,6 +1461,20 @@
</voice>
</phrase>
<phrase>
+ id: LANG_SINGLE_MODE
+ desc: single mode
+ user: core
+ <source>
+ *: "Single Mode"
+ </source>
+ <dest>
+ *: "מצב יחדי"
+ </dest>
+ <voice>
+ *: "מצב יחדי"
+ </voice>
+</phrase>
+<phrase>
id: LANG_PARTY_MODE
desc: party mode
user: core
diff --git a/apps/menu.c b/apps/menu.c
index c4f3024930..802a16bbb8 100644
--- a/apps/menu.c
+++ b/apps/menu.c
@@ -67,8 +67,14 @@ static int current_subitems[MAX_MENU_SUBITEMS];
static int current_subitems_count = 0;
static int talk_menu_item(int selected_item, void *data);
+struct menu_data_t
+{
+ const struct menu_item_ex *menu;
+ int selected;
+};
+
static void get_menu_callback(const struct menu_item_ex *m,
- menu_callback_type *menu_callback)
+ menu_callback_type *menu_callback)
{
if (m->flags&(MENU_HAS_DESC|MENU_DYNAMIC_DESC))
*menu_callback= m->callback_and_desc->menu_callback;
@@ -76,6 +82,19 @@ static void get_menu_callback(const struct menu_item_ex *m,
*menu_callback = m->menu_callback;
}
+static bool query_audio_status(int *old_audio_status)
+{
+ bool redraw_list = false;
+ /* query audio status to see if it changed */
+ int new_audio_status = audio_status();
+ if (*old_audio_status != new_audio_status)
+ { /* force a redraw if anything changed the audio status from outside */
+ *old_audio_status = new_audio_status;
+ redraw_list = true;
+ }
+ return redraw_list;
+}
+
static int get_menu_selection(int selected_item, const struct menu_item_ex *menu)
{
int type = (menu->flags&MENU_TYPE_MASK);
@@ -128,8 +147,7 @@ static const char* get_menu_item_name(int selected_item,
type = (menu->flags&MENU_TYPE_MASK);
if ((type == MT_SETTING) || (type == MT_SETTING_W_TEXT))
{
- const struct settings_list *v
- = find_setting(menu->variable, NULL);
+ const struct settings_list *v = find_setting(menu->variable, NULL);
if (v)
return str(v->lang_id);
else return "Not Done yet!";
@@ -158,35 +176,29 @@ static enum themable_icons menu_get_icon(int selected_item, void * data)
if (menu_icon == Icon_NOICON)
{
- switch (menu->flags&MENU_TYPE_MASK)
- {
- case MT_SETTING:
- case MT_SETTING_W_TEXT:
- menu_icon = Icon_Menu_setting;
- break;
- case MT_MENU:
- menu_icon = Icon_Submenu;
- break;
- case MT_FUNCTION_CALL:
- case MT_RETURN_VALUE:
- menu_icon = Icon_Menu_functioncall;
- break;
- }
+ unsigned int flags = (menu->flags&MENU_TYPE_MASK);
+ if(flags == MT_MENU)
+ menu_icon = Icon_Submenu;
+ else if (flags == MT_SETTING || flags == MT_SETTING_W_TEXT)
+ menu_icon = Icon_Menu_setting;
+ else if (flags == MT_FUNCTION_CALL || flags == MT_RETURN_VALUE)
+ menu_icon = Icon_Menu_functioncall;
}
return menu_icon;
}
-static void init_menu_lists(const struct menu_item_ex *menu,
+static int init_menu_lists(const struct menu_item_ex *menu,
struct gui_synclist *lists, int selected, bool callback,
struct viewport parent[NB_SCREENS])
{
if (!menu || !lists)
{
panicf("init_menu_lists, NULL pointer");
- return;
+ return 0;
}
int i;
+ int start_action = ACTION_ENTER_MENUITEM;
int count = MIN(MENU_GET_COUNT(menu->flags), MAX_MENU_SUBITEMS);
int type = (menu->flags&MENU_TYPE_MASK);
menu_callback_type menu_callback = NULL;
@@ -253,7 +265,9 @@ static void init_menu_lists(const struct menu_item_ex *menu,
get_menu_callback(menu,&menu_callback);
if (callback && menu_callback)
- menu_callback(ACTION_ENTER_MENUITEM, menu, lists);
+ start_action = menu_callback(start_action, menu, lists);
+
+ return start_action;
}
static int talk_menu_item(int selected_item, void *data)
@@ -340,7 +354,7 @@ void do_setting_screen(const struct settings_list *setting, const char * title,
option_screen((struct settings_list *)setting, parent,
setting->flags&F_TEMPVAR, (char*)title);
}
-
+
void do_setting_from_menu(const struct menu_item_ex *temp,
struct viewport parent[NB_SCREENS])
@@ -368,12 +382,16 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
struct viewport parent[NB_SCREENS], bool hide_theme)
{
int selected = start_selected? *start_selected : 0;
+ int ret = 0;
int action;
+ int start_action;
struct gui_synclist lists;
const struct menu_item_ex *temp = NULL;
const struct menu_item_ex *menu = start_menu;
- int ret = 0;
+
+ bool in_stringlist, done = false;
bool redraw_lists;
+
int old_audio_status = audio_status();
#ifdef HAVE_TOUCHSCREEN
@@ -386,16 +404,17 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
FOR_NB_SCREENS(i)
viewportmanager_theme_enable(i, !hide_theme, NULL);
- const struct menu_item_ex *menu_stack[MAX_MENUS];
- int menu_stack_selected_item[MAX_MENUS];
+ struct menu_data_t mstack[MAX_MENUS]; /* menu, selected */
int stack_top = 0;
- bool in_stringlist, done = false;
+
struct viewport *vps = NULL;
menu_callback_type menu_callback = NULL;
/* if hide_theme is true, assume parent has been fixed before passed into
- * this function, e.g. with viewport_set_defaults(parent, screen) */
- init_menu_lists(menu, &lists, selected, true, parent);
+ * this function, e.g. with viewport_set_defaults(parent, screen)
+ * start_action allows an action to be processed
+ * by menu logic by bypassing get_action on the initial run */
+ start_action = init_menu_lists(menu, &lists, selected, true, parent);
vps = *(lists.parent);
in_stringlist = ((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID);
/* load the callback, and only reload it if menu changes */
@@ -403,42 +422,36 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
gui_synclist_draw(&lists);
gui_synclist_speak_item(&lists);
+
while (!done)
{
- int new_audio_status;
- redraw_lists = false;
keyclick_set_callback(gui_synclist_keyclick_callback, &lists);
- action = get_action(CONTEXT_MAINMENU|ALLOW_SOFTLOCK,
- list_do_action_timeout(&lists, HZ));
- /* query audio status to see if it changed */
- new_audio_status = audio_status();
- if (old_audio_status != new_audio_status)
- { /* force a redraw if anything changed the audio status
- * from outside */
- redraw_lists = true;
- old_audio_status = new_audio_status;
+ if (UNLIKELY(start_action != ACTION_ENTER_MENUITEM))
+ {
+ action = start_action;
+ start_action = ACTION_ENTER_MENUITEM;
}
- /* HZ so the status bar redraws corectly */
+ else
+ action = get_action(CONTEXT_MAINMENU|ALLOW_SOFTLOCK,
+ list_do_action_timeout(&lists, HZ));
+ /* HZ so the status bar redraws corectly */
+
+ /* query audio status to see if it changed */
+ redraw_lists = query_audio_status(&old_audio_status);
if (menu_callback)
{
- int old_action = action;
- action = menu_callback(action, menu, &lists);
- if (action == ACTION_EXIT_AFTER_THIS_MENUITEM)
- {
- action = old_action;
- ret = MENU_SELECTED_EXIT; /* will exit after returning
- from selection */
- }
- else if (action == ACTION_REDRAW)
- {
- action = old_action;
+ int new_action = menu_callback(action, menu, &lists);
+ if (new_action == ACTION_EXIT_AFTER_THIS_MENUITEM)
+ ret = MENU_SELECTED_EXIT; /* exit after return from selection */
+ else if (new_action == ACTION_REDRAW)
redraw_lists = true;
- }
+ else
+ action = new_action;
}
- if (gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD))
+ if (LIKELY(gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD)))
continue;
#ifdef HAVE_QUICKSCREEN
else if (action == ACTION_STD_QUICKSCREEN)
@@ -501,12 +514,11 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
ID2P(LANG_RIGHT_QS_ITEM),
ID2P(LANG_ADD_TO_FAVES));
#endif
- MENUITEM_STRINGLIST(notquickscreen_able_option,
+ MENUITEM_STRINGLIST(notquickscreen_able_option,
ID2P(LANG_ONPLAY_MENU_TITLE), NULL,
ID2P(LANG_RESET_SETTING));
const struct menu_item_ex *menu;
- int menu_selection = 0;
- const struct settings_list *setting =
+ const struct settings_list *setting =
find_setting(temp->variable, NULL);
#ifdef HAVE_QUICKSCREEN
if (is_setting_quickscreenable(setting))
@@ -514,7 +526,10 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
else
#endif
menu = &notquickscreen_able_option;
- switch (do_menu(menu, &menu_selection, NULL, false))
+
+ int msel = do_menu(menu, NULL, NULL, false);
+
+ switch (msel)
{
case GO_TO_PREVIOUS:
break;
@@ -556,10 +571,12 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
}
else if (action == ACTION_STD_CANCEL)
{
- bool exiting_menu = false;
- in_stringlist = false;
/* might be leaving list, so stop scrolling */
gui_synclist_scroll_stop(&lists);
+
+ bool exiting_menu = false;
+ in_stringlist = false;
+
if (menu_callback)
menu_callback(ACTION_EXIT_MENUITEM, menu, &lists);
@@ -567,15 +584,16 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
done = true;
else if ((menu->flags&MENU_TYPE_MASK) == MT_MENU)
exiting_menu = true;
+
if (stack_top > 0)
{
stack_top--;
- menu = menu_stack[stack_top];
+ menu = mstack[stack_top].menu;
+ int msel = mstack[stack_top].selected;
if (!exiting_menu && (menu->flags&MENU_EXITAFTERTHISMENU))
done = true;
else
- init_menu_lists(menu, &lists,
- menu_stack_selected_item[stack_top], false, vps);
+ init_menu_lists(menu, &lists, msel, false, vps);
redraw_lists = true;
/* new menu, so reload the callback */
get_menu_callback(menu, &menu_callback);
@@ -588,19 +606,18 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
}
else if (action == ACTION_STD_OK)
{
- int type = (menu->flags&MENU_TYPE_MASK);
/* entering an item that may not be a list, so stop scrolling */
gui_synclist_scroll_stop(&lists);
+ redraw_lists = true;
+
+ int type = (menu->flags&MENU_TYPE_MASK);
selected = get_menu_selection(gui_synclist_get_sel_pos(&lists), menu);
if (type == MT_MENU)
temp = menu->submenus[selected];
- else
+ else if (!in_stringlist)
type = -1;
- redraw_lists = true;
- if (in_stringlist)
- type = (menu->flags&MENU_TYPE_MASK);
- else if (temp)
+ if (!in_stringlist && temp)
{
type = (temp->flags&MENU_TYPE_MASK);
get_menu_callback(temp, &menu_callback);
@@ -616,8 +633,8 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
case MT_MENU:
if (stack_top < MAX_MENUS)
{
- menu_stack[stack_top] = menu;
- menu_stack_selected_item[stack_top] = selected;
+ mstack[stack_top].menu = menu;
+ mstack[stack_top].selected = selected;
stack_top++;
menu = temp;
init_menu_lists(menu, &lists, 0, true, vps);
@@ -634,7 +651,8 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
if (!(menu->flags&MENU_EXITAFTERTHISMENU) ||
(temp->flags&MENU_EXITAFTERTHISMENU))
{
- init_menu_lists(menu, &lists, selected, true, vps);
+ /* Reload menu but don't run the calback again FS#8117 */
+ init_menu_lists(menu, &lists, selected, false, vps);
}
if (temp->flags&MENU_FUNC_CHECK_RETVAL)
{
@@ -661,8 +679,8 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
}
else if (stack_top < MAX_MENUS)
{
- menu_stack[stack_top] = menu;
- menu_stack_selected_item[stack_top] = selected;
+ mstack[stack_top].menu = menu;
+ mstack[stack_top].selected = selected;
stack_top++;
menu = temp;
init_menu_lists(menu,&lists,0,false, vps);
@@ -688,7 +706,7 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
init_menu_lists(menu,&lists,selected,true,vps);
/* callback was changed, so reload the menu's callback */
get_menu_callback(menu, &menu_callback);
- if ((menu->flags&MENU_EXITAFTERTHISMENU) &&
+ if ((menu->flags&MENU_EXITAFTERTHISMENU) &&
!(temp->flags&MENU_EXITAFTERTHISMENU))
{
done = true;
@@ -731,8 +749,8 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
the selected item from the menu do_menu() was called from */
if (stack_top > 0)
{
- menu = menu_stack[0];
- init_menu_lists(menu,&lists,menu_stack_selected_item[0],true, vps);
+ menu = mstack[0].menu;
+ init_menu_lists(menu,&lists,mstack[0].selected,true, vps);
}
*start_selected = get_menu_selection(
gui_synclist_get_sel_pos(&lists), menu);
@@ -748,6 +766,5 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
tsm == old_global_mode))
touchscreen_set_mode(tsm);
#endif
-
return ret;
}
diff --git a/apps/menus/display_menu.c b/apps/menus/display_menu.c
index c72fb08fae..7a4d81284a 100644
--- a/apps/menus/display_menu.c
+++ b/apps/menus/display_menu.c
@@ -351,6 +351,22 @@ MENUITEM_SETTING(offset_out_of_view, &global_settings.offset_out_of_view,
MENUITEM_SETTING(screen_scroll_step, &global_settings.screen_scroll_step, NULL);
MENUITEM_SETTING(scroll_paginated, &global_settings.scroll_paginated, NULL);
+static int listwraparound_callback(int action,
+ const struct menu_item_ex *this_item,
+ struct gui_synclist *this_list)
+{
+ (void)this_item;
+ switch (action)
+ {
+ case ACTION_EXIT_MENUITEM:
+ gui_synclist_limit_scroll(this_list, !global_settings.list_wraparound);
+ break;
+ }
+ return action;
+}
+
+MENUITEM_SETTING(list_wraparound, &global_settings.list_wraparound, listwraparound_callback);
+
MAKE_MENU(scroll_settings_menu, ID2P(LANG_SCROLL_MENU), 0, Icon_NOICON,
&scroll_speed, &scroll_delay,
&scroll_step,
@@ -360,6 +376,7 @@ MAKE_MENU(scroll_settings_menu, ID2P(LANG_SCROLL_MENU), 0, Icon_NOICON,
#endif
&offset_out_of_view, &screen_scroll_step,
&scroll_paginated,
+ &list_wraparound,
#ifndef HAVE_WHEEL_ACCELERATION
&list_accel_start_delay, &list_accel_wait
#endif
diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c
index 41c738725c..5f9479fae3 100644
--- a/apps/menus/playback_menu.c
+++ b/apps/menus/playback_menu.c
@@ -92,6 +92,7 @@ MENUITEM_SETTING(buffer_margin, &global_settings.buffer_margin,
buffermargin_callback);
#endif /*HAVE_DISK_STORAGE */
MENUITEM_SETTING(fade_on_stop, &global_settings.fade_on_stop, NULL);
+MENUITEM_SETTING(single_mode, &global_settings.single_mode, NULL);
MENUITEM_SETTING(party_mode, &global_settings.party_mode, NULL);
#ifdef HAVE_CROSSFADE
@@ -208,7 +209,7 @@ MAKE_MENU(playback_settings,ID2P(LANG_PLAYBACK),0,
#ifdef HAVE_DISK_STORAGE
&buffer_margin,
#endif
- &fade_on_stop, &party_mode,
+ &fade_on_stop, &single_mode, &party_mode,
#if defined(HAVE_CROSSFADE)
&crossfade_settings_menu,
diff --git a/apps/menus/plugin_menu.c b/apps/menus/plugin_menu.c
index 055cfce140..7edfc7acc6 100644
--- a/apps/menus/plugin_menu.c
+++ b/apps/menus/plugin_menu.c
@@ -24,12 +24,12 @@
#include "config.h"
#include "lang.h"
#include "menu.h"
+#include "action.h"
#include "settings.h"
#include "rbpaths.h"
#include "root_menu.h"
#include "tree.h"
-
enum {
GAMES,
APPS,
@@ -45,6 +45,12 @@ static const struct {
{ PLUGIN_DEMOS_DIR, LANG_PLUGIN_DEMOS },
};
+/* if handler is active we are waiting to reenter menu */
+static void pm_handler(unsigned short id, void *data)
+{
+ remove_event(id, data);
+}
+
static int plugins_menu(void* param)
{
intptr_t item = (intptr_t)param;
@@ -53,13 +59,40 @@ static int plugins_menu(void* param)
browse_context_init(&browse, SHOW_PLUGINS, 0, str(items[item].id),
Icon_Plugin, items[item].path, NULL);
-
+
ret = rockbox_browse(&browse);
+
if (ret == GO_TO_PREVIOUS)
return 0;
+ if (ret == GO_TO_PLUGIN)
+ add_event(SYS_EVENT_USB_INSERTED, pm_handler);
+
return ret;
}
+static int menu_callback(int action,
+ const struct menu_item_ex *this_item,
+ struct gui_synclist *this_list)
+{
+ (void)this_item;
+ static int selected = 0;
+
+ if (action == ACTION_ENTER_MENUITEM)
+ {
+ this_list->selected_item = selected;
+ if (!add_event(SYS_EVENT_USB_INSERTED, pm_handler))
+ {
+ action = ACTION_STD_OK; /* event exists -- reenter menu */
+ }
+ remove_event(SYS_EVENT_USB_INSERTED, pm_handler);
+ }
+ else if (action == ACTION_STD_OK)
+ {
+ selected = gui_synclist_get_sel_pos(this_list);
+ }
+ return action;
+}
+
#define ITEM_FLAG (MENU_FUNC_USEPARAM|MENU_FUNC_CHECK_RETVAL)
MENUITEM_FUNCTION(games_item, ITEM_FLAG, ID2P(LANG_PLUGIN_GAMES),
@@ -69,6 +102,6 @@ MENUITEM_FUNCTION(apps_item, ITEM_FLAG, ID2P(LANG_PLUGIN_APPS),
MENUITEM_FUNCTION(demos_item, ITEM_FLAG, ID2P(LANG_PLUGIN_DEMOS),
plugins_menu, (void*)DEMOS, NULL, Icon_Folder);
-MAKE_MENU(plugin_menu, ID2P(LANG_PLUGINS), NULL,
+MAKE_MENU(plugin_menu, ID2P(LANG_PLUGINS), &menu_callback,
Icon_Plugin,
&games_item, &apps_item, &demos_item);
diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c
index a5e747651a..94b697aada 100644
--- a/apps/menus/settings_menu.c
+++ b/apps/menus/settings_menu.c
@@ -48,7 +48,6 @@
#ifdef HAVE_DIRCACHE
#include "dircache.h"
#endif
-#include "folder_select.h"
#ifndef HAS_BUTTON_HOLD
#include "mask_select.h"
#endif
@@ -56,6 +55,7 @@
#include "governor-ibasso.h"
#include "usb-ibasso.h"
#endif
+#include "plugin.h"
#ifndef HAS_BUTTON_HOLD
static int selectivesoftlock_callback(int action,
@@ -133,8 +133,7 @@ static void tagcache_update_with_splash(void)
static int dirs_to_scan(void)
{
- if (folder_select(global_settings.tagcache_scan_paths,
- sizeof(global_settings.tagcache_scan_paths)))
+ if(plugin_load(VIEWERS_DIR"/db_folder_select.rock", NULL) > PLUGIN_OK)
{
static const char *lines[] = {ID2P(LANG_TAGCACHE_BUSY),
ID2P(LANG_TAGCACHE_FORCE_UPDATE)};
@@ -650,8 +649,8 @@ static int autoresume_nexttrack_callback(int action,
break;
case ACTION_EXIT_MENUITEM:
if (global_settings.autoresume_automatic == AUTORESUME_NEXTTRACK_CUSTOM
- && !folder_select(global_settings.autoresume_paths,
- MAX_PATHNAME+1))
+ && plugin_load(VIEWERS_DIR"/db_folder_select.rock",
+ str(LANG_AUTORESUME)) == PLUGIN_OK)
{
global_settings.autoresume_automatic = oldval;
}
diff --git a/apps/onplay.c b/apps/onplay.c
index a5a92e7c1c..8f2c3c4f4a 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -567,8 +567,8 @@ static bool view_playlist(void)
static int playlist_insert_func(void *param)
{
if (((intptr_t)param == PLAYLIST_REPLACE ||
- ((intptr_t)param == PLAYLIST_INSERT_SHUFFLED && !(audio_status() & AUDIO_STATUS_PLAY))) &&
- !warn_on_pl_erase())
+ (((intptr_t)param == PLAYLIST_INSERT_SHUFFLED || (intptr_t)param == PLAYLIST_INSERT)
+ && !(audio_status() & AUDIO_STATUS_PLAY))) && !warn_on_pl_erase())
return 0;
add_to_playlist((intptr_t)param, false);
return 0;
@@ -778,6 +778,7 @@ static int treeplaylist_callback(int action,
void onplay_show_playlist_menu(char* path)
{
+ context = CONTEXT_STD;
selected_file = path;
if (dir_exists(path))
selected_file_attr = ATTR_DIRECTORY;
@@ -825,6 +826,7 @@ MAKE_ONPLAYMENU(cat_playlist_menu, ID2P(LANG_CATALOG),
void onplay_show_playlist_cat_menu(char* track_name)
{
+ context = CONTEXT_STD;
selected_file = track_name;
selected_file_attr = FILE_ATTR_AUDIO;
do_menu(&cat_playlist_menu, NULL, NULL, false);
@@ -1576,6 +1578,8 @@ static bool onplay_load_plugin(void *param)
int ret = filetype_load_plugin((const char*)param, selected_file);
if (ret == PLUGIN_USB_CONNECTED)
onplay_result = ONPLAY_RELOAD_DIR;
+ else if (ret == PLUGIN_GOTO_PLUGIN)
+ onplay_result = ONPLAY_PLUGIN;
return false;
}
diff --git a/apps/open_plugin.c b/apps/open_plugin.c
index 7b5513a92b..f7f55d58cd 100644
--- a/apps/open_plugin.c
+++ b/apps/open_plugin.c
@@ -27,6 +27,10 @@
#include "splash.h"
#include "lang.h"
+/* Define LOGF_ENABLE to enable logf output in this file */
+//#define LOGF_ENABLE
+#include "logf.h"
+
#define ROCK_EXT "rock"
#define ROCK_LEN 5
#define OP_EXT "opx"
@@ -34,85 +38,228 @@
struct open_plugin_entry_t open_plugin_entry = {0};
+static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
+
static const int op_entry_sz = sizeof(struct open_plugin_entry_t);
-static int open_plugin_hash_get_entry(uint32_t hash,
- struct open_plugin_entry_t *entry,
- const char* dat_file);
+static const char* strip_rockbox_root(const char *path)
+{
+ int dlen = strlen(ROCKBOX_DIR);
+ if (strncmp(path, ROCKBOX_DIR, dlen) == 0)
+ path+= dlen;
+ return path;
+}
static inline void op_clear_entry(struct open_plugin_entry_t *entry)
{
- if (entry)
+ if (entry == NULL)
+ return;
+ memset(entry, 0, op_entry_sz);
+ entry->lang_id = -1;
+}
+
+static int op_entry_checksum(struct open_plugin_entry_t *entry)
+{
+ if (entry == NULL || entry->checksum != open_plugin_csum)
+ return 0;
+ return 1;
+}
+
+static int op_find_entry(int fd, struct open_plugin_entry_t *entry,
+ uint32_t hash, int32_t lang_id)
+{
+ int ret = OPEN_PLUGIN_NOT_FOUND;
+ int record = 0;
+ if (hash == 0)
+ hash = OPEN_PLUGIN_SEED;
+ if (fd >= 0)
+ {
+ logf("OP find_entry *Searching* hash: %x lang_id: %d", hash, lang_id);
+
+ while (read(fd, entry, op_entry_sz) == op_entry_sz)
+ {
+ if (entry->lang_id == lang_id || entry->hash == hash ||
+ (lang_id == OPEN_PLUGIN_LANG_IGNOREALL))/* return first entry found */
+ {
+ ret = record;
+ /* NULL terminate fields NOTE -- all are actually +1 larger */
+ entry->name[OPEN_PLUGIN_NAMESZ] = '\0';
+ /*entry->key[OPEN_PLUGIN_BUFSZ] = '\0';*/
+ entry->path[OPEN_PLUGIN_BUFSZ] = '\0';
+ entry->param[OPEN_PLUGIN_BUFSZ] = '\0';
+ logf("OP find_entry *Found* hash: %x lang_id: %d",
+ entry->hash, entry->lang_id);
+ logf("OP find_entry rec: %d name: %s %s %s", record,
+ entry->name, entry->path, entry->param);
+ break;
+ }
+ record++;
+ }
+ }
+
+ /* sanity check */
+ if (ret > OPEN_PLUGIN_NOT_FOUND && op_entry_checksum(entry) <= 0)
{
- memset(entry, 0, op_entry_sz);
- entry->lang_id = -1;
+ splash(HZ * 2, "OpenPlugin Invalid entry");
+ ret = OPEN_PLUGIN_NOT_FOUND;
}
+ if (ret == OPEN_PLUGIN_NOT_FOUND)
+ op_clear_entry(entry);
+
+ return ret;
}
static int op_update_dat(struct open_plugin_entry_t *entry, bool clear)
{
- int fd, fd1;
+ int fd;
uint32_t hash;
-
- if (!entry || entry->hash == 0)
- return -1;
+ int32_t lang_id;
+ if (entry == NULL|| entry->hash == 0)
+ {
+ logf("OP update *No entry*");
+ return OPEN_PLUGIN_NOT_FOUND;
+ }
hash = entry->hash;
+ lang_id = entry->lang_id;
+ if (lang_id <= OPEN_PLUGIN_LANG_INVALID)
+ lang_id = OPEN_PLUGIN_LANG_IGNORE;
+
+ logf("OP update hash: %x lang_id: %d", hash, lang_id);
+ logf("OP update name: %s clear: %d", entry->name, (int) clear);
+ logf("OP update %s %s %s", entry->name, entry->path, entry->param);
+
+#if (CONFIG_STORAGE & STORAGE_ATA) /* Harddrive -- update existing */
+ logf("OP update *Updating entries* %s", OPEN_PLUGIN_DAT);
+ fd = open(OPEN_PLUGIN_DAT, O_RDWR | O_CREAT, 0666);
+
+ if (fd < 0)
+ return OPEN_PLUGIN_NOT_FOUND;
+ /* Only read the hash lang id and checksum */
+ uint32_t hash_langid_csum[3] = {0};
+ const off_t hlc_sz = sizeof(hash_langid_csum);
+ while (read(fd, &hash_langid_csum, hlc_sz) == hlc_sz)
+ {
+ if ((hash_langid_csum[0] == hash || (int32_t)hash_langid_csum[1] == lang_id) &&
+ hash_langid_csum[2] == open_plugin_csum)
+ {
+ logf("OP update *Entry Exists* hash: %x langid: %d",
+ hash_langid_csum[0], (int32_t)hash_langid[1]);
+ lseek(fd, 0-hlc_sz, SEEK_CUR);/* back to the start of record */
+ break;
+ }
+ lseek(fd, op_entry_sz - hlc_sz, SEEK_CUR); /* finish record */
+ }
+ write(fd, entry, op_entry_sz);
+ close(fd);
+#else /* Everyone else make a temp file */
+ logf("OP update *Copying entries* %s", OPEN_PLUGIN_DAT ".tmp");
+ fd = open(OPEN_PLUGIN_DAT ".tmp", O_RDWR | O_CREAT | O_TRUNC, 0666);
- fd = open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
- return -1;
+ return OPEN_PLUGIN_NOT_FOUND;
write(fd, entry, op_entry_sz);
- fd1 = open(OPEN_PLUGIN_DAT, O_RDONLY);
+ int fd1 = open(OPEN_PLUGIN_DAT, O_RDONLY);
if (fd1 >= 0)
{
- while (read(fd1, &open_plugin_entry, op_entry_sz) == op_entry_sz)
+ /* copy non-duplicate entries back from original */
+ while (read(fd1, entry, op_entry_sz) == op_entry_sz)
{
- if (open_plugin_entry.hash != hash)
- write(fd, &open_plugin_entry, op_entry_sz);
+ if (entry->hash != hash && entry->lang_id != lang_id &&
+ op_entry_checksum(entry) > 0)
+ {
+ write(fd, entry, op_entry_sz);
+ }
}
close(fd1);
remove(OPEN_PLUGIN_DAT);
}
+ if (!clear) /* retrieve original entry */
+ {
+ logf("OP update *Loading original entry*");
+ lseek(fd, 0, SEEK_SET);
+ op_find_entry(fd, entry, hash, lang_id);
+ }
close(fd);
-
rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
+#endif
if (clear)
- op_clear_entry(&open_plugin_entry);
+ {
+ logf("OP update *Clearing entry*");
+ op_clear_entry(entry);
+ }
return 0;
}
+static int op_get_entry(uint32_t hash, int32_t lang_id,
+ struct open_plugin_entry_t *entry, const char *dat_file)
+{
+ int opret = OPEN_PLUGIN_NOT_FOUND;
+
+ if (entry != NULL)
+ {
+ /* Is the entry we want already loaded? */
+ if(hash != 0 && entry->hash == hash)
+ return OPEN_PLUGIN_NEEDS_FLUSHED;
+
+ if(lang_id <= OPEN_PLUGIN_LANG_INVALID)
+ {
+ lang_id = OPEN_PLUGIN_LANG_IGNORE;
+ if (hash == 0)/* no hash or langid -- returns first entry found */
+ lang_id = OPEN_PLUGIN_LANG_IGNOREALL;
+ }
+ else if(entry->lang_id == lang_id)
+ {
+ return OPEN_PLUGIN_NEEDS_FLUSHED;
+ }
+
+ /* if another entry is loaded; flush it to disk before we destroy it */
+ op_update_dat(&open_plugin_entry, true);
+
+ logf("OP get_entry hash: %x lang id: %d db: %s", hash, lang_id, dat_file);
+
+ int fd = open(dat_file, O_RDONLY);
+ opret = op_find_entry(fd, entry, hash, lang_id);
+ close(fd);
+ }
+
+ return opret;
+}
+
uint32_t open_plugin_add_path(const char *key, const char *plugin, const char *parameter)
{
int len;
- bool is_valid = false;
uint32_t hash;
int32_t lang_id;
char *pos = "\0";
- if(!key)
+ if(key == NULL)
{
+ logf("OP add_path No Key, *Clearing entry*");
op_clear_entry(&open_plugin_entry);
return 0;
}
lang_id = P2ID((unsigned char*)key);
- key = P2STR((unsigned char *)key);
-
- open_plugin_get_hash(key, &hash);
-
+ const char *skey = P2STR((unsigned char *)key);
+ logf("OP add_path key: %s lang id: %d", skey, lang_id);
+ open_plugin_get_hash(strip_rockbox_root(skey), &hash);
if(open_plugin_entry.hash != hash)
{
+ logf("OP add_path *Flush entry*");
/* the entry in ram needs saved */
op_update_dat(&open_plugin_entry, true);
}
if (plugin)
{
+ open_plugin_entry.hash = hash;
+ open_plugin_entry.lang_id = lang_id;
+ open_plugin_entry.checksum = open_plugin_csum;
/* name */
if (path_basename(plugin, (const char **)&pos) == 0)
pos = "\0";
@@ -120,45 +267,46 @@ uint32_t open_plugin_add_path(const char *key, const char *plugin, const char *p
len = strlcpy(open_plugin_entry.name, pos, OPEN_PLUGIN_NAMESZ);
if (len > ROCK_LEN && strcasecmp(&(pos[len-ROCK_LEN]), "." ROCK_EXT) == 0)
{
- is_valid = true;
-
/* path */
strlcpy(open_plugin_entry.path, plugin, OPEN_PLUGIN_BUFSZ);
- if(parameter)
- strlcpy(open_plugin_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
- else
- open_plugin_entry.param[0] = '\0';
+ if(!parameter)
+ parameter = "";
+ strlcpy(open_plugin_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
+ goto retnhash;
}
else if (len > OP_LEN && strcasecmp(&(pos[len-OP_LEN]), "." OP_EXT) == 0)
{
- is_valid = true;
- open_plugin_hash_get_entry(0, &open_plugin_entry, plugin);
+ op_get_entry(0, OPEN_PLUGIN_LANG_IGNORE, &open_plugin_entry, plugin);
+ goto retnhash;
}
}
- if (!is_valid)
- {
- if (lang_id != LANG_SHORTCUTS) /* from shortcuts menu */
- splashf(HZ / 2, str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
- op_clear_entry(&open_plugin_entry);
- hash = 0;
- }
- else
- {
- open_plugin_entry.hash = hash;
- open_plugin_entry.lang_id = lang_id;
- }
-
+ logf("OP add_path Invalid, *Clearing entry*");
+ if (lang_id != LANG_SHORTCUTS) /* from shortcuts menu */
+ splashf(HZ * 2, str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
+ op_clear_entry(&open_plugin_entry);
+ hash = 0;
+
+retnhash:
+ logf("OP add_path name: %s %s %s",
+ open_plugin_entry.name,
+ open_plugin_entry.path,
+ open_plugin_entry.param);
return hash;
}
void open_plugin_browse(const char *key)
{
+ logf("OP browse");
struct browse_context browse;
char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
open_plugin_get_entry(key, &open_plugin_entry);
+ logf("OP browse key: %s name: %s",
+ (key ? P2STR((unsigned char *)key):"No Key") ,open_plugin_entry.name);
+ logf("OP browse %s %s", open_plugin_entry.path, open_plugin_entry.param);
+
if (open_plugin_entry.path[0] == '\0')
strcpy(open_plugin_entry.path, PLUGIN_DIR"/");
@@ -172,78 +320,50 @@ void open_plugin_browse(const char *key)
open_plugin_add_path(key, tmp_buf, NULL);
}
-static int open_plugin_hash_get_entry(uint32_t hash,
- struct open_plugin_entry_t *entry,
- const char* dat_file)
-{
- int ret = -1, record = -1;
-
- if (entry)
- {
-
- if (hash != 0)
- {
- if(entry->hash == hash) /* hasn't been flushed yet? */
- return -2;
- else
- op_update_dat(&open_plugin_entry, true);
- }
-
- int fd = open(dat_file, O_RDONLY);
-
- if (fd >= 0)
- {
- while (read(fd, entry, op_entry_sz) == op_entry_sz)
- {
- record++;
- if (hash == 0 || entry->hash == hash)
- {
- /* NULL terminate fields NOTE -- all are actually +1 larger */
- entry->name[OPEN_PLUGIN_NAMESZ] = '\0';
- /*entry->key[OPEN_PLUGIN_BUFSZ] = '\0';*/
- entry->path[OPEN_PLUGIN_BUFSZ] = '\0';
- entry->param[OPEN_PLUGIN_BUFSZ] = '\0';
- ret = record;
- break;
- }
- }
- close(fd);
- }
- if (ret < 0)
- {
- memset(entry, 0, op_entry_sz);
- entry->lang_id = -1;
- }
- }
-
- return ret;
-}
-
int open_plugin_get_entry(const char *key, struct open_plugin_entry_t *entry)
{
- uint32_t hash;
- key = P2STR((unsigned char *)key);
+ if (key == NULL || entry == NULL)
+ return OPEN_PLUGIN_NOT_FOUND;
+ int opret;
+ uint32_t hash = 0;
+ int32_t lang_id = P2ID((unsigned char *)key);
+ const char* skey = P2STR((unsigned char *)key); /* string|LANGPTR => string */
+
+ if (lang_id <= OPEN_PLUGIN_LANG_INVALID)
+ open_plugin_get_hash(strip_rockbox_root(skey), &hash); /* in open_plugin.h */
+
+ opret = op_get_entry(hash, lang_id, entry, OPEN_PLUGIN_DAT);
+ logf("OP entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey);
+
+ if (opret == OPEN_PLUGIN_NOT_FOUND && lang_id > OPEN_PLUGIN_LANG_INVALID)
+ { /* try rb defaults */
+ opret = op_get_entry(hash, lang_id, entry, OPEN_RBPLUGIN_DAT);
+ logf("OP rb_entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey);
+ /* add to the user plugin.dat file if found */
+ op_update_dat(entry, false);
- open_plugin_get_hash(key, &hash); /* in open_plugin.h */
- return open_plugin_hash_get_entry(hash, entry, OPEN_PLUGIN_DAT);
+ }
+ logf("OP entry ret: %s", (opret == OPEN_PLUGIN_NOT_FOUND ? "Not Found":"Found"));
+ return opret;
}
int open_plugin_run(const char *key)
{
int ret = 0;
- const char *path;
- const char *param;
-
- if (open_plugin_get_entry(key, &open_plugin_entry) == -2) /* entry needs flushed */
+ int opret = open_plugin_get_entry(key, &open_plugin_entry);
+ if (opret == OPEN_PLUGIN_NEEDS_FLUSHED)
op_update_dat(&open_plugin_entry, false);
+ const char *path = open_plugin_entry.path;
+ const char *param = open_plugin_entry.param;
+
+ logf("OP run key: %s ret: %d name: %s",
+ (key ? P2STR((unsigned char *)key):"No Key"), opret, open_plugin_entry.name);
+ logf("OP run: %s %s %s", open_plugin_entry.name, path, param);
- path = open_plugin_entry.path;
- param = open_plugin_entry.param;
if (param[0] == '\0')
param = NULL;
- if (path)
- ret = plugin_load(path, param);
+ ret = plugin_load(path, param);
if (ret != GO_TO_PLUGIN)
op_clear_entry(&open_plugin_entry);
@@ -253,6 +373,16 @@ int open_plugin_run(const char *key)
void open_plugin_cache_flush(void)
{
+ logf("OP *cache flush*");
+ /* start_in_screen == 0 is 'Previous Screen' it is actually
+ * defined as (GO_TO_PREVIOUS = -2) + 2 for *Legacy?* reasons AFAICT */
+ if (global_settings.start_in_screen == 0 &&
+ global_status.last_screen == GO_TO_PLUGIN &&
+ open_plugin_entry.lang_id > OPEN_PLUGIN_LANG_INVALID)
+ {
+ /* flush the last item as LANG_PREVIOUS_SCREEN if the user wants to resume */
+ open_plugin_entry.lang_id = LANG_PREVIOUS_SCREEN;
+ }
op_update_dat(&open_plugin_entry, true);
}
diff --git a/apps/open_plugin.h b/apps/open_plugin.h
index 8c09c4ac58..d16be2052c 100644
--- a/apps/open_plugin.h
+++ b/apps/open_plugin.h
@@ -32,23 +32,47 @@
#ifndef __PCTOOL__
/* open_plugin path lookup */
#define OPEN_PLUGIN_DAT PLUGIN_DIR "/plugin.dat"
+#define OPEN_RBPLUGIN_DAT PLUGIN_DIR "/rb_plugins.dat"
#define OPEN_PLUGIN_BUFSZ MAX_PATH
#define OPEN_PLUGIN_NAMESZ 32
+
+enum {
+ OPEN_PLUGIN_LANG_INVALID = (-1),
+ OPEN_PLUGIN_LANG_IGNORE = (-2),
+ OPEN_PLUGIN_LANG_IGNOREALL = (-3),
+ OPEN_PLUGIN_NOT_FOUND = (-1),
+ OPEN_PLUGIN_NEEDS_FLUSHED = (-2),
+};
+
struct open_plugin_entry_t
{
+/* hash lang_id checksum need to be the first items */
uint32_t hash;
int32_t lang_id;
+ uint32_t checksum;
char name[OPEN_PLUGIN_NAMESZ+1];
/*char key[OPEN_PLUGIN_BUFSZ+1];*/
char path[OPEN_PLUGIN_BUFSZ+1];
char param[OPEN_PLUGIN_BUFSZ+1];
};
+#define OPEN_PLUGIN_CHECKSUM (uint32_t) \
+( \
+ (sizeof(struct open_plugin_entry_t) << 16) + \
+ offsetof(struct open_plugin_entry_t, hash) + \
+ offsetof(struct open_plugin_entry_t, lang_id) + \
+ offsetof(struct open_plugin_entry_t, checksum) + \
+ offsetof(struct open_plugin_entry_t, name) + \
+ /*offsetof(struct open_plugin_entry_t, key)+*/ \
+ offsetof(struct open_plugin_entry_t, path) + \
+ offsetof(struct open_plugin_entry_t, param))
+
+#define OPEN_PLUGIN_SEED 0x811C9DC5; //seed, 2166136261;
inline static void open_plugin_get_hash(const char *key, uint32_t *hash)
{
/* Calculate modified FNV1a hash of string */
const uint32_t p = 16777619;
- *hash = 0x811C9DC5; //seed, 2166136261;
+ *hash = OPEN_PLUGIN_SEED;
while(*key)
*hash = (*key++ ^ *hash) * p;
}
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 773e97cce0..c4164c3b4b 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -741,6 +741,9 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type)
}
}
+ if (auto_skip && global_settings.single_mode && !global_settings.party_mode)
+ crossfade = false;
+
if (crossfade)
{
logf("crossfade track change");
diff --git a/apps/playback.c b/apps/playback.c
index 5a6f18735b..5d980b5634 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -1872,10 +1872,37 @@ static int audio_load_track(void)
playlist_peek_offset);
/* Get track name from current playlist read position */
+ int fd = -1;
char path_buf[MAX_PATH + 1];
- const char *path = playlist_peek(playlist_peek_offset,
- path_buf,
- sizeof (path_buf));
+ const char *path;
+
+ while (1)
+ {
+ path = playlist_peek(playlist_peek_offset,
+ path_buf,
+ sizeof (path_buf));
+
+ if (!path)
+ break;
+
+ /* Test for broken playlists by probing for the files */
+ fd = open(path, O_RDONLY);
+ if (fd >= 0)
+ break;
+
+ logf("Open failed");
+
+ /* only skip if failed track has a successor in playlist */
+ if (!playlist_peek(playlist_peek_offset + 1, NULL, 0))
+ break;
+
+ /* Skip invalid entry from playlist */
+ playlist_skip_entry(NULL, playlist_peek_offset);
+
+ /* Sync the playlist if it isn't finished */
+ if (playlist_peek(playlist_peek_offset, NULL, 0))
+ playlist_next(0);
+ }
if (!path)
{
@@ -1911,14 +1938,12 @@ static int audio_load_track(void)
/* Load the metadata for the first unbuffered track */
ub_id3 = id3_get(UNBUFFERED_ID3);
- int fd = open(path, O_RDONLY);
if (fd >= 0)
{
id3_mutex_lock();
if(!get_metadata(ub_id3, fd, path))
wipe_mp3entry(ub_id3);
id3_mutex_unlock();
- close(fd);
}
if (filling != STATE_FULL)
@@ -1936,13 +1961,16 @@ static int audio_load_track(void)
{
track_list_free_info(&info);
track_list.in_progress_hid = 0;
+ if (fd >= 0)
+ close(fd);
return LOAD_TRACK_ERR_FAILED;
}
/* Successful load initiation */
track_list.in_progress_hid = info.self_hid;
}
-
+ if (fd >= 0)
+ close(fd);
return LOAD_TRACK_OK;
}
@@ -2396,6 +2424,14 @@ static void audio_finalise_track_change(void)
id3_write(PLAYING_ID3, track_id3);
+ if (global_settings.single_mode)
+ if ( ((skip_pending == TRACK_SKIP_AUTO) || (skip_pending == TRACK_SKIP_AUTO_NEW_PLAYLIST))
+ && (global_settings.party_mode == 0) )
+ {
+ play_status = PLAY_PAUSED;
+ pcmbuf_pause(true);
+ }
+
/* The skip is technically over */
skip_pending = TRACK_SKIP_NONE;
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index d04ca8b70c..21b61d4a67 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -98,7 +98,7 @@ struct playlist_viewer {
int selected_track; /* The selected track, relative (first is 0) */
int moving_track; /* The track to move, relative (first is 0)
or -1 if nothing is currently being moved */
- int moving_playlist_index; /* Playlist-relative index (as opposed to
+ int moving_playlist_index; /* Playlist-relative index (as opposed to
viewer-relative index) of moving track */
struct playlist_buffer buffer;
};
@@ -295,7 +295,7 @@ static struct playlist_entry * playlist_buffer_get_track(struct playlist_buffer
the name_buffer is probably too small to store enough
titles to fill the screen, and preload data in the short
direction.
-
+
If this happens then scrolling performance will probably
be quite low, but it's better then having Data Abort errors */
playlist_buffer_load_entries(pb, index, FORWARD);
@@ -319,7 +319,7 @@ static bool playlist_viewer_init(struct playlist_viewer * viewer,
}
if (!have_list && (playlist_amount() > 0))
{
- /*If dynamic playlist still exists, view it anyway even
+ /*If dynamic playlist still exists, view it anyway even
if playback has reached the end of the playlist */
have_list = true;
}
@@ -489,14 +489,11 @@ static int onplay_menu(int index)
playlist_buffer_get_track(&viewer.buffer, index);
MENUITEM_STRINGLIST(menu_items, ID2P(LANG_PLAYLIST), NULL,
ID2P(LANG_CURRENT_PLAYLIST), ID2P(LANG_CATALOG),
- ID2P(LANG_REMOVE), ID2P(LANG_MOVE), ID2P(LANG_PROPERTIES),
- ID2P(LANG_SHUFFLE), ID2P(LANG_SAVE),
+ ID2P(LANG_REMOVE), ID2P(LANG_MOVE), ID2P(LANG_SHUFFLE),
+ ID2P(LANG_SAVE),
ID2P(LANG_PLAYLISTVIEWER_SETTINGS));
bool current = (current_track->index == viewer.current_playing_track);
- struct playlist_track_info trackinfo;
- playlist_get_track_info(viewer.playlist, index, &trackinfo);
-
result = do_menu(&menu_items, NULL, NULL, false);
if (result == MENU_ATTACHED_USB)
{
@@ -550,21 +547,16 @@ static int onplay_menu(int index)
ret = 0;
break;
case 4:
- /* file properties */
- result = filetype_load_plugin((void *)"properties", trackinfo.filename);
- ret = (result == MENU_ATTACHED_USB) ? -1 : 0;
- break;
- case 5:
/* shuffle */
playlist_randomise(viewer.playlist, current_tick, false);
ret = 1;
break;
- case 6:
+ case 5:
/* save playlist */
save_playlist_screen(viewer.playlist);
ret = 0;
break;
- case 7:
+ case 6:
/* playlist viewer settings */
result = do_menu(&viewer_settings_menu, NULL, NULL, false);
ret = (result == MENU_ATTACHED_USB) ? -1 : 0;
diff --git a/apps/plugin.c b/apps/plugin.c
index 0193dc605e..0225491d85 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -346,12 +346,14 @@ static const struct plugin_api rockbox_api = {
gui_syncyesno_run,
simplelist_info_init,
simplelist_show_list,
+ yesno_pop,
/* action handling */
get_custom_action,
get_action,
#ifdef HAVE_TOUCHSCREEN
action_get_touchscreen_press,
+ action_get_touchscreen_press_in_vp,
#endif
action_userabort,
@@ -413,6 +415,7 @@ static const struct plugin_api rockbox_api = {
FS_PREFIX(file_exists),
strip_extension,
crc_32,
+ crc_32r,
filetype_get_attr,
/* dir */
@@ -593,6 +596,7 @@ static const struct plugin_api rockbox_api = {
sound_enum_hw_eq_band_setting,
#endif
#if defined (HAVE_PITCHCONTROL)
+ sound_get_pitch,
sound_set_pitch,
#endif
&audio_master_sampr_list[0],
@@ -622,7 +626,10 @@ static const struct plugin_api rockbox_api = {
dsp_eq_enable,
dsp_dither_enable,
#ifdef HAVE_PITCHCONTROL
+ dsp_get_timestretch,
dsp_set_timestretch,
+ dsp_timestretch_enable,
+ dsp_timestretch_available,
#endif
dsp_configure,
dsp_get_config,
@@ -641,6 +648,7 @@ static const struct plugin_api rockbox_api = {
mixer_get_frequency,
pcmbuf_fade,
+ pcmbuf_set_low_latency,
system_sound_play,
keyclick_click,
@@ -692,6 +700,9 @@ static const struct plugin_api rockbox_api = {
audio_current_track,
audio_flush_and_reload_tracks,
audio_get_file_pos,
+#ifdef PLUGIN_USE_IRAM
+ audio_hard_stop,
+#endif
/* menu */
root_menu_get_options,
@@ -735,6 +746,7 @@ static const struct plugin_api rockbox_api = {
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
__errno,
#endif
+ led,
srand,
rand,
(void *)qsort,
@@ -780,7 +792,6 @@ static const struct plugin_api rockbox_api = {
detect_flashed_ramimage,
detect_flashed_romimage,
#endif
- led,
/*plugin*/
plugin_open,
@@ -789,14 +800,9 @@ static const struct plugin_api rockbox_api = {
plugin_release_audio_buffer, /* defined in plugin.c */
plugin_tsr, /* defined in plugin.c */
plugin_get_current_filename,
-#ifdef PLUGIN_USE_IRAM
- audio_hard_stop,
-#endif
- crc_32r,
-
/* new stuff at the end, sort into place next time
the API gets incompatible */
-
+ warn_on_pl_erase,
};
static int plugin_buffer_handle;
@@ -1013,7 +1019,7 @@ static void plugin_tsr(bool (*exit_callback)(bool))
pfn_tsr_exit = exit_callback; /* remember the callback for later */
}
-int plugin_open(char *plugin, char *parameter)
+int plugin_open(const char *plugin, const char *parameter)
{
open_plugin_add_path(ID2P(LANG_OPEN_PLUGIN), plugin, parameter);
return PLUGIN_GOTO_PLUGIN;
diff --git a/apps/plugin.h b/apps/plugin.h
index 023b442295..6ad495b5a6 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -49,7 +49,7 @@
char* strncpy(char *, const char *, size_t);
void* plugin_get_buffer(size_t *buffer_size);
-int plugin_open(char *plugin, char *parameter);
+int plugin_open(const char *plugin, const char *parameter);
#ifndef __PCTOOL__
#include "config.h"
@@ -155,12 +155,12 @@ int plugin_open(char *plugin, char *parameter);
#define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 244
+#define PLUGIN_API_VERSION 246
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */
-#define PLUGIN_MIN_API_VERSION 244
+#define PLUGIN_MIN_API_VERSION 245
/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */
@@ -393,6 +393,7 @@ struct plugin_api {
void (*simplelist_info_init)(struct simplelist_info *info, char* title,
int count, void* data);
bool (*simplelist_show_list)(struct simplelist_info *info);
+ bool (*yesno_pop)(const char* text);
/* action handling */
int (*get_custom_action)(int context,int timeout,
@@ -400,6 +401,7 @@ struct plugin_api {
int (*get_action)(int context, int timeout);
#ifdef HAVE_TOUCHSCREEN
int (*action_get_touchscreen_press)(short *x, short *y);
+ int (*action_get_touchscreen_press_in_vp)(short *x1, short *y1, struct viewport *vp);
#endif
bool (*action_userabort)(int timeout);
@@ -462,6 +464,7 @@ struct plugin_api {
bool (*file_exists)(const char *path);
char* (*strip_extension)(char* buffer, int buffer_size, const char *filename);
uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32);
+ uint32_t (*crc_32r)(const void *src, uint32_t len, uint32_t crc32);
int (*filetype_get_attr)(const char* file);
@@ -667,6 +670,7 @@ struct plugin_api {
unsigned int band_setting);
#endif /* AUDIOHW_HAVE_EQ */
#if defined (HAVE_PITCHCONTROL)
+ int32_t (*sound_get_pitch)(void);
void (*sound_set_pitch)(int32_t pitch);
#endif
const unsigned long *audio_master_sampr_list;
@@ -701,7 +705,10 @@ struct plugin_api {
void (*dsp_eq_enable)(bool enable);
void (*dsp_dither_enable)(bool enable);
#ifdef HAVE_PITCHCONTROL
+ int32_t (*dsp_get_timestretch)(void);
void (*dsp_set_timestretch)(int32_t percent);
+ void (*dsp_timestretch_enable)(bool enabled);
+ bool (*dsp_timestretch_available)(void);
#endif
intptr_t (*dsp_configure)(struct dsp_config *dsp,
unsigned int setting, intptr_t value);
@@ -727,6 +734,7 @@ struct plugin_api {
void (*mixer_set_frequency)(unsigned int samplerate);
unsigned int (*mixer_get_frequency)(void);
void (*pcmbuf_fade)(bool fade, bool in);
+ void (*pcmbuf_set_low_latency)(bool state);
void (*system_sound_play)(enum system_sound sound);
void (*keyclick_click)(bool rawbutton, int action);
@@ -793,6 +801,9 @@ struct plugin_api {
struct mp3entry* (*audio_current_track)(void);
void (*audio_flush_and_reload_tracks)(void);
int (*audio_get_file_pos)(void);
+#ifdef PLUGIN_USE_IRAM
+ void (*audio_hard_stop)(void);
+#endif
/* menu */
struct menu_table *(*root_menu_get_options)(int *nb_options);
@@ -853,6 +864,7 @@ struct plugin_api {
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
int * (*__errno)(void);
#endif
+ void (*led)(bool on);
void (*srand)(unsigned int seed);
int (*rand)(void);
void (*qsort)(void *base, size_t nmemb, size_t size,
@@ -907,23 +919,16 @@ struct plugin_api {
bool (*detect_flashed_ramimage)(void);
bool (*detect_flashed_romimage)(void);
#endif
-
- void (*led)(bool on);
-
/*plugin*/
- int (*plugin_open)(char *path, char *parameter);
+ int (*plugin_open)(const char *path, const char *parameter);
void* (*plugin_get_buffer)(size_t *buffer_size);
void* (*plugin_get_audio_buffer)(size_t *buffer_size);
void (*plugin_release_audio_buffer)(void);
void (*plugin_tsr)(bool (*exit_callback)(bool reenter));
char* (*plugin_get_current_filename)(void);
-#ifdef PLUGIN_USE_IRAM
- void (*audio_hard_stop)(void);
-#endif
- uint32_t (*crc_32r)(const void *src, uint32_t len, uint32_t crc32);
-
/* new stuff at the end, sort into place next time
the API gets incompatible */
+ bool (*warn_on_pl_erase)(void);
};
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index d3093689f9..bb0960f501 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -22,6 +22,7 @@ clock,apps
codebuster,games
credits,viewers
cube,demos
+db_folder_select,viewers
demystify,demos
dice,games
dict,apps
@@ -76,6 +77,7 @@ pegbox,games
periodic_table,apps
pictureflow,demos
pitch_detector,apps
+pitch_screen,viewers
pixel-painter,games
plasma,demos
png,viewers
@@ -85,6 +87,7 @@ ppm,viewers
properties,viewers
quake,games
random_folder_advance_config,apps
+rb_info,demos
remote_control,apps
resistor,apps
reversi,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 910ffe4161..ab77dcde58 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -2,6 +2,9 @@
#if !defined(SIMULATOR) && (CONFIG_BATTERY_MEASURE != 0)
battery_bench.c
#endif
+#ifdef HAVE_TAGCACHE
+db_folder_select.c
+#endif
chessclock.c
credits.c
cube.c
@@ -14,6 +17,7 @@ mosaique.c
main_menu_config.c
properties.c
random_folder_advance_config.c
+rb_info.c
rockblox.c
search.c
settings_dumper.c
@@ -48,6 +52,10 @@ lamp.c
pitch_detector.c
#endif
+#ifdef HAVE_PITCHCONTROL
+pitch_screen.c
+#endif
+
mp3_encoder.c
wav2wv.c
diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c
index a9958f198d..77e9015000 100644
--- a/apps/plugins/announce_status.c
+++ b/apps/plugins/announce_status.c
@@ -191,6 +191,8 @@ void announce(void)
rb->talk_force_shutup();
rb->sleep(HZ / 2);
voice_general_info(false);
+ if (rb->talk_id(VOICE_PAUSE, true) < 0)
+ rb->beep_play(800, 100, 1000);
//rb->talk_force_enqueue_next();
}
@@ -412,6 +414,7 @@ static int settings_menu(void)
/****************** main thread + helper ******************/
void thread(void)
{
+ bool in_usb = false;
long interval;
long last_tick = *rb->current_tick; /* for 1 sec tick */
@@ -424,6 +427,14 @@ void thread(void)
{
case SYS_USB_CONNECTED:
rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
+ in_usb = true;
+ break;
+ case SYS_USB_DISCONNECTED:
+ in_usb = false;
+ /*fall through*/
+ case EV_STARTUP:
+ rb->beep_play(1500, 100, 1000);
+ break;
case EV_EXIT:
return;
case EV_OTHINSTANCE:
@@ -431,15 +442,12 @@ void thread(void)
{
last_tick += interval;
rb->sleep(HZ / 10);
- announce();
+ if (!in_usb) announce();
}
break;
- case EV_STARTUP:
- rb->beep_play(1500, 100, 1000);
- break;
case EV_TRACKCHANGE:
rb->sleep(HZ / 10);
- announce();
+ if (!in_usb) announce();
break;
}
}
@@ -464,6 +472,7 @@ void thread_quit(void)
rb->thread_wait(gThread.id);
/* we don't want any more events */
rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, playback_event_callback);
+
/* remove the thread's queue from the broadcast list */
rb->queue_delete(&gThread.queue);
gThread.exiting = true;
@@ -560,6 +569,8 @@ int plugin_main(const void* parameter)
enum plugin_status plugin_start(const void* parameter)
{
/* now go ahead and have fun! */
+ if (rb->usb_inserted() == true)
+ return PLUGIN_USB_CONNECTED;
int ret = plugin_main(parameter);
return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR;
}
diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES
index 37d0060213..f207f358b2 100644
--- a/apps/plugins/bitmaps/native/SOURCES
+++ b/apps/plugins/bitmaps/native/SOURCES
@@ -8,7 +8,11 @@ _2048_background.224x224x24.bmp
_2048_tiles.36x36x24.bmp
_2048_background.168x168x24.bmp
#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=132 || MIN(LCD_WIDTH, LCD_HEIGHT)>=128
+#if (LCD_DEPTH > 2)
_2048_tiles.26x26x24.bmp
+#else
+_2048_tiles.26x26x2.bmp
+#endif
_2048_background.121x121x24.bmp
#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=110
_2048_tiles.22x22x24.bmp
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp b/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp
new file mode 100644
index 0000000000..4b764c3e14
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp
Binary files differ
diff --git a/apps/plugins/db_folder_select.c b/apps/plugins/db_folder_select.c
new file mode 100644
index 0000000000..7f51e520cb
--- /dev/null
+++ b/apps/plugins/db_folder_select.c
@@ -0,0 +1,652 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * 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
+ * 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 "plugin.h"
+#ifdef ROCKBOX_HAS_LOGF
+#define logf rb->logf
+#else
+#define logf(...) do { } while(0)
+#endif
+
+/*
+ * Order for changing child states:
+ * 1) expand folder (skip to 3 if empty, skip to 4 if cannot be opened)
+ * 2) collapse and select
+ * 3) unselect (skip to 1)
+ * 4) do nothing
+ */
+
+enum child_state {
+ EXPANDED,
+ SELECTED,
+ COLLAPSED,
+ EACCESS,
+};
+
+struct child {
+ char* name;
+ struct folder *folder;
+ enum child_state state;
+};
+
+struct folder {
+ char *name;
+ struct child *children;
+ 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 = rb->crc_32(key, len, *hash);
+}
+
+static char* folder_alloc(size_t size)
+{
+ char* retval;
+ /* 32-bit aligned */
+ size = ALIGN_UP(size, 4);
+ if (buffer_front + size > buffer_end)
+ {
+ return NULL;
+ }
+ retval = buffer_front;
+ buffer_front += size;
+ return retval;
+}
+
+static char* folder_alloc_from_end(size_t size)
+{
+ if (buffer_end - size < buffer_front)
+ {
+ return NULL;
+ }
+ buffer_end -= size;
+ return buffer_end;
+}
+#if 0
+/* returns the buffer size required to store the path + \0 */
+static int get_full_pathsz(struct folder *start)
+{
+ int reql = 0;
+ struct folder *next = start;
+ do
+ {
+ reql += rb->strlen(next->name) + 1;
+ } while ((next = next->previous));
+
+ if (start->name[0] != '/') reql--;
+ if (--reql < 0) reql = 0;
+ return reql;
+}
+#endif
+
+static size_t get_full_path(struct folder *start, char *dst, size_t dst_sz)
+{
+ size_t pos = 0;
+ struct folder *prev, *cur = NULL, *next = start;
+ dst[0] = '\0'; /* for rb->strlcat to do its thing */
+ /* First traversal R->L mutate nodes->previous to point at child */
+ while (next->previous != NULL) /* stop at the root */
+ {
+#define PATHMUTATE() \
+ ({ \
+ prev = cur; \
+ cur = next; \
+ next = cur->previous;\
+ cur->previous = prev; \
+ })
+ PATHMUTATE();
+ }
+ /*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 = rb->strlcat(dst, cur->name, dst_sz);
+ /* do not append slash to paths starting with slash */
+ if (cur->name[0] != '/')
+ pos = rb->strlcat(dst, "/", dst_sz);
+ }
+ logf("get_full_path: (%d)[%s]", (int)pos, dst);
+ return pos;
+#undef PATHMUTATE
+}
+
+/* support function for rb->qsort() */
+static int compare(const void* p1, const void* p2)
+{
+ struct child *left = (struct child*)p1;
+ struct child *right = (struct child*)p2;
+ return rb->strcasecmp(left->name, right->name);
+}
+
+static struct folder* load_folder(struct folder* parent, char *folder)
+{
+ DIR *dir;
+ char fullpath[MAX_PATH];
+
+ struct dirent *entry;
+ int child_count = 0;
+ char *first_child = NULL;
+ size_t len = 0;
+
+ 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;
+ }
+ rb->strlcpy(&fullpath[len], folder, sizeof(fullpath) - len);
+ logf("load_folder: [%s]", fullpath);
+
+ dir = rb->opendir(fullpath);
+ if (dir == NULL)
+ goto fail;
+ this->previous = parent;
+ this->name = folder;
+ this->children = NULL;
+ this->children_count = 0;
+ if (parent)
+ this->depth = parent->depth + 1;
+
+ while ((entry = rb->readdir(dir))) {
+ /* skip anything not a directory */
+ if ((rb->dir_get_info(dir, entry).attribute & ATTR_DIRECTORY) == 0) {
+ continue;
+ }
+ /* skip . and .. */
+ char *dn = entry->d_name;
+ if ((dn[0] == '.') && (dn[1] == '\0' || (dn[1] == '.' && dn[2] == '\0')))
+ continue;
+ /* copy entry name to end of buffer, save pointer */
+ int len = rb->strlen((char *)entry->d_name);
+ char *name = folder_alloc_from_end(len+1); /*for NULL*/
+ if (name == NULL)
+ {
+ rb->closedir(dir);
+ goto fail;
+ }
+ memcpy(name, (char *)entry->d_name, len+1);
+ child_count++;
+ first_child = name;
+ }
+ rb->closedir(dir);
+ /* now put the names in the array */
+ this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count);
+
+ if (this->children == NULL)
+ goto fail;
+
+ while (child_count)
+ {
+ 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--;
+ }
+ rb->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;
+
+ static struct folder root = {
+ .name = "",
+ .children = &root_child,
+ .children_count = 1,
+ .depth = 0,
+ .previous = NULL,
+ };
+
+ return &root;
+}
+
+static int count_items(struct folder *start)
+{
+ int count = 0;
+ int i;
+
+ for (i=0; i<start->children_count; i++)
+ {
+ struct child *foo = &start->children[i];
+ if (foo->state == EXPANDED)
+ count += count_items(foo->folder);
+ count++;
+ }
+ return count;
+}
+
+static struct child* find_index(struct folder *start, int index, struct folder **parent)
+{
+ int i = 0;
+ *parent = NULL;
+
+ while (i < start->children_count)
+ {
+ struct child *foo = &start->children[i];
+ if (i == index)
+ {
+ *parent = start;
+ return foo;
+ }
+ i++;
+ if (foo->state == EXPANDED)
+ {
+ struct child *bar = find_index(foo->folder, index - i, parent);
+ if (bar)
+ {
+ return bar;
+ }
+ index -= count_items(foo->folder);
+ }
+ }
+ return NULL;
+}
+
+static const char * folder_get_name(int selected_item, void * data,
+ char * buffer, size_t buffer_len)
+{
+ struct folder *root = (struct folder*)data;
+ struct folder *parent;
+ struct child *this = find_index(root, selected_item , &parent);
+
+ 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';
+ rb->strlcat(buffer, this->name, buffer_len);
+
+ if (this->state == EACCESS)
+ { /* append error message to the entry if unaccessible */
+ size_t len = rb->strlcat(buffer, " ( ", buffer_len);
+ if (buffer_len > len)
+ {
+ rb->snprintf(&buffer[len], buffer_len - len, rb->str(LANG_READ_FAILED), ")");
+ }
+ }
+
+ return buffer;
+}
+
+static enum themable_icons folder_get_icon(int selected_item, void * data)
+{
+ struct folder *root = (struct folder*)data;
+ struct folder *parent;
+ struct child *this = find_index(root, selected_item, &parent);
+
+ switch (this->state)
+ {
+ case SELECTED:
+ return Icon_Cursor;
+ case COLLAPSED:
+ return Icon_Folder;
+ case EXPANDED:
+ return Icon_Submenu;
+ case EACCESS:
+ return Icon_Questionmark;
+ }
+ 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;
+ struct folder *parent;
+ struct child *this = find_index(root, list->selected_item, &parent), *child;
+ int i;
+
+ if (action == ACTION_STD_OK)
+ {
+ switch (this->state)
+ {
+ case EXPANDED:
+ this->state = SELECTED;
+ break;
+ case SELECTED:
+ this->state = COLLAPSED;
+ break;
+ case COLLAPSED:
+ child_set_state_expand(this, parent);
+ break;
+ case EACCESS:
+ /* cannot open, do nothing */
+ return action;
+ }
+ action = ACTION_REDRAW;
+ }
+ else if (action == ACTION_STD_CONTEXT)
+ {
+ switch (this->state)
+ {
+ case EXPANDED:
+ for (i = 0; i < this->folder->children_count; i++)
+ {
+ child = &this->folder->children[i];
+ 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 (child_set_state_expand(this, parent) != EACCESS)
+ {
+ 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;
+ }
+ action = ACTION_REDRAW;
+ }
+ if (action == ACTION_REDRAW)
+ list->nb_items = count_items(root);
+ return action;
+}
+
+static struct child* find_from_filename(const char* filename, struct folder *root)
+{
+ if (!root)
+ return NULL;
+ const char *slash = rb->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 (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 (filename[1] == '\0')
+ { /* filename == "/" */
+ return this;
+ }
+ else /* filename == "/XXX/YYY". cascade down */
+ goto cascade;
+ }
+
+ for (int i = 0; i < root->children_count; i++)
+ {
+ this = &root->children[i];
+ /* when slash == NULL n will be really large but \0 stops the compare */
+ if (rb->strncasecmp(this->name, filename, slash - filename) == 0)
+ {
+ if (slash == NULL)
+ { /* filename == XXX */
+ return this;
+ }
+ else
+ goto cascade;
+ }
+ }
+ 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);
+}
+
+static int select_paths(struct folder* root, const char* filenames)
+{
+ /* 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 = rb->strchr(fnp, ':');
+ if (fnp)
+ {
+ len = fnp - lastfnp;
+ fnp++;
+ }
+ else /* no ':' get the rest of the string */
+ len = rb->strlen(lastfnp);
+
+ sstr = lastfnp;
+ lastfnp = fnp;
+ if (len <= 0 || len > buflen)
+ continue;
+ rb->strlcpy(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, size_t buflen)
+{
+ size_t len;
+ struct folder *curfolder;
+ char* name;
+
+ for (int i = 0; i < root->children_count; i++)
+ {
+ struct child *this = &root->children[i];
+ if (this->state == SELECTED)
+ {
+ if (this->folder == NULL)
+ {
+ curfolder = root;
+ name = this->name;
+ logf("save_folders_r: this->name[%s]", name);
+ }
+ else
+ {
+ 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 += rb->snprintf(&buffer_front[len], buflen - len, "%s:", name);
+ logf("save_folders_r: [%s]", buffer_front);
+ if (dst != hashed.buf)
+ {
+ int dlen = rb->strlen(dst);
+ if (dlen + len >= maxlen)
+ continue;
+ rb->strlcpy(&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;
+ }
+ }
+ else if (this->state == EXPANDED)
+ save_folders_r(this->folder, dst, maxlen, buflen);
+ }
+}
+
+static uint32_t save_folders(struct folder *root, char* dst, size_t maxlen)
+{
+ 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, len);
+ len = rb->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) rb->splash(HZ *2, ID2P(LANG_SHOWDIR_BUFFER_FULL));
+ return hashed.val;
+}
+
+bool folder_select(char * header_text, char* setting, int setting_len)
+{
+ struct folder *root;
+ struct simplelist_info info;
+ size_t buf_size;
+
+ buffer_front = rb->plugin_get_buffer(&buf_size);
+ buffer_end = buffer_front + buf_size;
+ logf("folder_select %d bytes free", (int)(buffer_end - buffer_front));
+ root = load_root();
+
+ 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);
+ rb->simplelist_info_init(&info, header_text,
+ count_items(root), root);
+ info.get_name = folder_get_name;
+ info.action_callback = folder_action_callback;
+ info.get_icon = folder_get_icon;
+ rb->simplelist_show_list(&info);
+ logf("folder_select %d bytes free", (int)(buffer_end - buffer_front));
+ /* done editing. check for changes */
+ if (hash != save_folders(root, hashed.buf, setting_len))
+ { /* prompt for saving changes and commit if yes */
+ if (rb->yesno_pop(ID2P(LANG_SAVE_CHANGES)))
+ {
+ save_folders(root, setting, setting_len);
+ rb->settings_save();
+ logf("folders out: %s", setting);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* plugin entry point */
+enum plugin_status plugin_start(const void* parameter)
+{
+ (void) parameter;
+
+ if(parameter)
+ {
+
+ if (rb->strcmp(parameter, rb->str(LANG_AUTORESUME)) == 0)
+ {
+ if (folder_select(rb->str(LANG_AUTORESUME),
+ rb->global_settings->autoresume_paths,
+ MAX_PATHNAME+1))
+ {
+ return 1;
+ }
+ }
+ }
+ else if (folder_select(rb->str(LANG_SELECT_FOLDER),
+ rb->global_settings->tagcache_scan_paths,
+ sizeof(rb->global_settings->tagcache_scan_paths)))
+ {
+ return 1;
+ }
+
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/keybox.c b/apps/plugins/keybox.c
index 1689321abe..a074ffc598 100644
--- a/apps/plugins/keybox.c
+++ b/apps/plugins/keybox.c
@@ -75,7 +75,7 @@ static void encrypt_buffer(char *buf, size_t size, uint32_t *key);
static void decrypt_buffer(char *buf, size_t size, uint32_t *key);
/* the following two functions are the reference TEA implementation by
- David Wheeler and Roger Needham taken from
+ David Wheeler and Roger Needham taken from
http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm */
static void do_encrypt(uint32_t* v, uint32_t* k)
diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES
index bdea07315e..1cd092f8df 100644
--- a/apps/plugins/lib/SOURCES
+++ b/apps/plugins/lib/SOURCES
@@ -1,6 +1,8 @@
sha1.c
gcc-support.c
pluginlib_actions.c
+action_helper.c
+button_helper.c
helper.c
icon_helper.c
arg_helper.c
diff --git a/apps/plugins/lib/action_helper.c b/apps/plugins/lib/action_helper.c
new file mode 100644
index 0000000000..906051c1ea
--- /dev/null
+++ b/apps/plugins/lib/action_helper.c
@@ -0,0 +1 @@
+/*DUMMY_FILE_DONT_CHANGEME*/
diff --git a/apps/plugins/lib/action_helper.h b/apps/plugins/lib/action_helper.h
new file mode 100644
index 0000000000..58d9c6c303
--- /dev/null
+++ b/apps/plugins/lib/action_helper.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+* __________ __ ___.
+* Open \______ \ ____ ____ | | _\_ |__ _______ ___
+* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+* \/ \/ \/ \/ \/
+* $Id$
+*
+* 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
+* 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.
+*
+****************************************************************************/
+/* action_helper provides a way to turn numeric action/context into strings
+* the file action_helper.c is generated at compile time
+* ACTION_ and CONTEXT_ are stripped from the strings and replaced when
+* action_name and context_name are called,
+* NOTE: both share the same static buffer sized as the largest string possible
+*/
+#ifndef _ACTION_HELPER_H_
+#define _ACTION_HELPER_H_
+
+char* action_name(int action);
+char* context_name(int context);
+
+#endif /* _ACTION_HELPER_H_ */
diff --git a/apps/plugins/lib/action_helper.pl b/apps/plugins/lib/action_helper.pl
new file mode 100755
index 0000000000..1dfdcfd070
--- /dev/null
+++ b/apps/plugins/lib/action_helper.pl
@@ -0,0 +1,209 @@
+#!/usr/bin/env perl
+############################################################################
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+# $action_helper$
+#
+# Copyright (C) 2021 William Wilgus
+#
+# All files in this archive are subject to the GNU General Public License.
+# See the file COPYING in the source tree root for full license agreement.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+############################################################################
+#expects -E source input on STDIN
+use strict;
+use warnings;
+
+my @actions = ();
+my @contexts = ();
+my @action_offset = ();
+my @context_offset = ();
+my $action_ct = 0;
+my $context_ct = 0;
+my $len_max_action = 0;
+my $len_max_context = 0;
+my $len_min_action = -1;
+my $len_min_context = -1;
+while(my $line = <STDIN>)
+{
+ chomp($line);
+ if($line =~ /^\s*(ACTION_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $actions[$action_ct] = $1;
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(LAST_ACTION_PLACEHOLDER)(\s*=.*)?,\s*$/)
+ { #special case don't save actual name
+ $actions[$action_ct] = "";
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(PLA_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $actions[$action_ct] = $1;
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(CONTEXT_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $contexts[$context_ct] = $1;
+ $context_ct++;
+ }
+}
+
+print <<EOF
+/* Don't change this file! */
+/* It is automatically generated of action.h */
+#include "plugin.h"
+#include "action_helper.h"
+EOF
+;
+#dump actions
+my $offset = 0;
+print "static const char action_names[]= \n";
+for(my $i = 0; $i < $action_ct; $i++){
+ my $act = $actions[$i];
+ $act =~ s/ACTION_USB_HID_/%s/ig; # strip the common part
+ $act =~ s/ACTION_/%s/ig; # strip the common part
+ my $actlen = length($act);
+ if ($actlen < $len_min_action or $len_min_action == -1){
+ $len_min_action = $actlen;
+ }
+ if ($actions[$i] ne $act){
+ printf "/*%s*/\"%s\\0\"\n", substr($actions[$i], 0, -($actlen - 2)), $act;
+ } else {
+ print "\"$act\\0\" \n";
+ }
+ my $slen = length($actions[$i]) + 1; #NULL terminator
+ if ($slen > $len_max_action) { $len_max_action = $slen; }
+ push(@action_offset, {'name' => $actions[$i], 'offset' => $offset});
+ $offset += length($act) + 1; # NULL terminator
+}
+printf "\"\";/* %d + \\0 */\n\n", $offset;
+@actions = ();
+
+#dump contexts
+$offset = 0;
+print "static const char context_names[]= \n";
+for(my $i = 0; $i < $context_ct; $i++){
+ my $ctx = $contexts[$i];
+ $ctx =~ s/CONTEXT_/%s/ig; # strip the common part
+ my $ctxlen = length($ctx);
+
+ if ($ctxlen < 5){
+ $ctx = $contexts[$i];
+ $ctxlen = length($ctx);
+ }
+
+ if ($ctxlen < $len_min_context or $len_min_context == -1){
+ $len_min_context = $ctxlen;
+ }
+ if ($contexts[$i] ne $ctx){
+ printf "/*%s*/\"%s\\0\"\n", substr($contexts[$i], 0, -($ctxlen - 2)), $ctx;
+ } else {
+ print "\"$ctx\\0\" \n";
+ }
+ my $slen = length($contexts[$i]) + 1; # NULL terminator
+ if ($slen > $len_max_context) { $len_max_context = $slen; }
+ push(@context_offset, {'name' => $contexts[$i], 'offset' => $offset});
+ $offset += length($ctx) + 1; # NULL terminator
+}
+printf "\"\";/* %d + \\0 */\n\n", $offset;
+@contexts = ();
+
+printf "#define ACTION_CT %d\n", $action_ct;
+print "static const uint16_t action_offsets[ACTION_CT] = {\n";
+foreach my $define (@action_offset)
+{
+ printf("%d, /*%s*/\n", @$define{'offset'}, @$define{'name'});
+}
+print "};\n\n";
+@action_offset = ();
+
+printf "#define CONTEXT_CT %d\n", $context_ct;
+print "#if 0 /* context_names is small enough to walk the string instead */\n";
+print "static const uint16_t context_offsets[CONTEXT_CT] = {\n";
+foreach my $define (@context_offset)
+{
+ printf("%d, /*%s*/\n", @$define{'offset'}, @$define{'name'});
+}
+print "};\n#endif\n\n";
+@context_offset = ();
+
+printf "#define ACTIONBUFSZ %d\n", $len_max_action;
+printf "#define CONTEXTBUFSZ %d\n\n", $len_max_context;
+
+if ($len_max_action > $len_max_context)
+{
+ print "static char name_buf[ACTIONBUFSZ];\n";
+}
+else
+{
+ print "static char name_buf[CONTEXTBUFSZ];\n";
+}
+print <<EOF
+
+char* action_name(int action)
+{
+ if (action >= 0 && action < ACTION_CT)
+ {
+ uint16_t offset = action_offsets[action];
+ const char *act = &action_names[offset];
+ if (action < ACTION_USB_HID_FIRST)
+ rb->snprintf(name_buf, ACTIONBUFSZ, act, "ACTION_");
+ else
+ rb->snprintf(name_buf, ACTIONBUFSZ, act, "ACTION_USB_HID_");
+ }
+ else
+ rb->snprintf(name_buf, ACTIONBUFSZ, "ACTION_UNKNOWN");
+ return name_buf;
+}
+
+/* walk string increment offset for each NULL if desired offset found, return */
+static const char *context_getoffset(int offset)
+{
+ const char *names = context_names;
+ const size_t len = sizeof(context_names) - 1;
+ int current = 0;
+ if (offset > 0)
+ {
+ const char *pos = names;
+ const char *end = names + len;
+ while (pos < end)
+ {
+ if (*pos++ == '\\0')
+ {
+ current++;
+ if (offset == current)
+ return pos;
+ pos += $len_min_context; /* each string is at least this long */
+ }
+ }
+ }
+ return names;
+}
+
+char* context_name(int context)
+{
+ const char *ctx;
+ if (context >= 0 && context < CONTEXT_CT)
+ {
+#if 0
+ uint16_t offset = context_offsets[context];
+ ctx = &context_names[offset];
+#else
+ ctx = context_getoffset(context);
+#endif
+ }
+ else
+ ctx = "%sUNKNOWN";
+ rb->snprintf(name_buf, CONTEXTBUFSZ, ctx, "CONTEXT_");
+ return name_buf;
+}
+EOF
+;
diff --git a/apps/plugins/lib/arg_helper.c b/apps/plugins/lib/arg_helper.c
index d402300900..3ea5ba714d 100644
--- a/apps/plugins/lib/arg_helper.c
+++ b/apps/plugins/lib/arg_helper.c
@@ -28,7 +28,9 @@
#define SWCHAR '-'
#define DECSEPCHAR '.'
-
+#ifdef PLUGIN
+ #define strchr rb->strchr
+#endif
int string_parse(const char **parameter, char* buf, size_t buf_sz)
{
/* fills buf with a string upto buf_sz, null terminates the buffer
diff --git a/apps/plugins/lib/arg_helper.h b/apps/plugins/lib/arg_helper.h
index c7b14f7f7a..2cf94ba1dd 100644
--- a/apps/plugins/lib/arg_helper.h
+++ b/apps/plugins/lib/arg_helper.h
@@ -26,7 +26,7 @@
#define ARGPARSE_MAX_FRAC_DIGITS 9 /* Uses 30 bits max (0.999999999) */
#define ARGP_EXP(a, b) (a ##E## b)
-#define ARGP_FRAC_DEC_MULTIPLIER(n) AP_EXP(1,n) /*1x10^n*/
+#define ARGP_FRAC_DEC_MULTIPLIER(n) ARGP_EXP(1,n) /*1x10^n*/
#define ARGPARSE_FRAC_DEC_MULTIPLIER \
(long)ARGP_FRAC_DEC_MULTIPLIER(ARGPARSE_MAX_FRAC_DIGITS)
diff --git a/apps/plugins/lib/button_helper.c b/apps/plugins/lib/button_helper.c
new file mode 100644
index 0000000000..906051c1ea
--- /dev/null
+++ b/apps/plugins/lib/button_helper.c
@@ -0,0 +1 @@
+/*DUMMY_FILE_DONT_CHANGEME*/
diff --git a/apps/plugins/lib/button_helper.h b/apps/plugins/lib/button_helper.h
new file mode 100644
index 0000000000..1197b172b0
--- /dev/null
+++ b/apps/plugins/lib/button_helper.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* __________ __ ___.
+* Open \______ \ ____ ____ | | _\_ |__ _______ ___
+* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+* \/ \/ \/ \/ \/
+* $Id$
+*
+* 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
+* 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 _BUTTON_HELPER_H_
+#define _BUTTON_HELPER_H_
+struct available_button
+{
+ const char* name;
+ unsigned long value;
+};
+
+/* *available_buttons is holding a pointer to the first element of an array
+ * of struct available_button it is set up in such a way due to the file being
+ * generated at compile time you can still call it as such though
+* eg available_buttons[0] or available_buttons[available_button_count] (NULL SENTINEL, 0)*/
+
+extern const struct available_button * const available_buttons;
+extern const int available_button_count;
+int get_button_names(char *buf, size_t bufsz, unsigned long button);
+#endif /* _BUTTON_HELPER_H_ */
diff --git a/apps/plugins/lib/button_helper.pl b/apps/plugins/lib/button_helper.pl
new file mode 100755
index 0000000000..45c3fd9073
--- /dev/null
+++ b/apps/plugins/lib/button_helper.pl
@@ -0,0 +1,98 @@
+#!/usr/bin/env perl
+############################################################################
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+# $Id$
+#
+# Copyright (C) 2009 by Maurus Cuelenaere
+# Copyright (C) 2021 by William Wilgus
+#
+# All files in this archive are subject to the GNU General Public License.
+# See the file COPYING in the source tree root for full license agreement.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+############################################################################
+#expects -dM -E source input on STDIN
+use strict;
+use warnings;
+my $svnrev = '$Revision$';
+my @buttons = ();
+my $count = 1; #null sentinel
+my $val;
+my $def;
+while(my $line = <STDIN>)
+{
+ chomp($line);
+ if($line =~ /^#define (BUTTON_[^\s]+) (.+)$/)
+ {
+ $def = "{\"$1\", $2},\n";
+ $val = $2;
+ if($val =~ /^0/)
+ {
+ $val = oct($val)
+ }
+ else
+ {
+ $val = 0xFFFFFFFF; #only used for sorting
+ }
+ push(@buttons, {'name' => $1, 'value' => $val, 'def' => $def});
+ $count = $count + 1;
+ }
+}
+my @sorted = sort { @$a{'value'} <=> @$b{'value'} } @buttons;
+print <<EOF
+/* Don't change this file! */
+/* It is automatically generated of button.h */
+#include "plugin.h"
+#include "button.h"
+#include "button_helper.h"
+
+static const struct available_button buttons[$count] = {
+EOF
+;
+$count--; # don't count the sentinel
+foreach my $button (@sorted)
+{
+ printf " %s", @$button{'def'};
+}
+
+print <<EOF
+ {"\\0", 0} /* sentinel */
+};
+const int available_button_count = $count;
+const struct available_button * const available_buttons = buttons;
+
+int get_button_names(char *buf, size_t bufsz, unsigned long button)
+{
+ int len = 0;
+ buf[0] = '\\0';
+ const struct available_button *btn = buttons;
+ while(btn->name[0] != '\\0')
+ {
+ if(btn->value == 0)
+ {
+ if (button == 0)
+ {
+ buf[0] = '\\0';
+ len = rb->strlcat(buf, btn->name, bufsz);
+ return len;
+ }
+ }
+ else if ((button & btn->value) == btn->value)
+ {
+ if (len > 0)
+ rb->strlcat(buf, " | ", bufsz);
+ len = rb->strlcat(buf, btn->name, bufsz);
+ }
+ btn++;
+ }
+ return len;
+}
+EOF
+;
diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c
index 225f0c8689..f42b96b5b3 100644
--- a/apps/plugins/lrcplayer.c
+++ b/apps/plugins/lrcplayer.c
@@ -428,8 +428,8 @@ static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i)
int nword;
int word_count, word_width;
const unsigned char *str;
- }
- sp,
+ }
+ sp,
cr;
lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */
diff --git a/apps/plugins/lua/rbdefines_helper.pl b/apps/plugins/lua/rbdefines_helper.pl
index e788855e87..5fb0946a6a 100755
--- a/apps/plugins/lua/rbdefines_helper.pl
+++ b/apps/plugins/lua/rbdefines_helper.pl
@@ -50,6 +50,7 @@ if ($def_type eq "rb_defines") {
'^SYS_(TIMEOUT|POWEROFF|BATTERY_UPDATE)$',
'^SYS_USB_(DIS|)CONNECTED$',
'^HOME_DIR$',
+ '^PLUGIN(_OK|_USB_CONNECTED|_POWEROFF|_GOTO_WPS|_GOTO_PLUGIN)$',
'^PLUGIN_DIR$',
'^PLUGIN(_APPS_|_GAMES_|_)DATA_DIR$',
'^ROCKBOX_DIR$',
diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c
index 3909f3008f..3cf0fce945 100644
--- a/apps/plugins/lua/rocklua.c
+++ b/apps/plugins/lua/rocklua.c
@@ -175,9 +175,10 @@ static int loadfile_newstate(lua_State **L, const char *filename)
static void lua_atexit(void)
{
char *filename;
-
+ int err_n;
if(Ls && lua_gettop(Ls) > 1)
{
+ err_n = lua_tointeger(Ls, -1); /* os.exit? */
if (Ls == lua_touserdata(Ls, -1)) /* signal from restart_lua */
{
filename = (char *) malloc((MAX_PATH * 2) + 1);
@@ -195,7 +196,12 @@ static void lua_atexit(void)
free(filename);
plugin_start(NULL);
}
- else if (lua_tointeger(Ls, -1) != 0) /* os.exit */
+ else if (err_n >= PLUGIN_USB_CONNECTED) /* INTERNAL PLUGIN RETVAL */
+ {
+ lua_close(Ls);
+ _exit(err_n); /* don't call exit handler */
+ }
+ else if (err_n != 0)
{
ERR_RUN:
lu_status = LUA_ERRRUN;
@@ -205,7 +211,7 @@ ERR_RUN:
else
lua_close(Ls);
}
- _exit(0); /* don't call exit handler */
+ _exit(PLUGIN_OK); /* don't call exit handler */
}
/* split filename at argchar
diff --git a/apps/plugins/lua_scripts/return2WPS.lua b/apps/plugins/lua_scripts/return2WPS.lua
new file mode 100644
index 0000000000..9202237691
--- /dev/null
+++ b/apps/plugins/lua_scripts/return2WPS.lua
@@ -0,0 +1,19 @@
+--[[
+ __________ __ ___.
+ Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ \/ \/ \/ \/ \/
+ $Id$
+ Example Lua Return to WPS on exit
+ 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
+ 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.
+]]--
+
+os.exit(rb.PLUGIN_GOTO_WPS)
diff --git a/apps/plugins/main_menu_config.c b/apps/plugins/main_menu_config.c
index f66165e63d..9f651094b1 100644
--- a/apps/plugins/main_menu_config.c
+++ b/apps/plugins/main_menu_config.c
@@ -171,7 +171,7 @@ enum plugin_status plugin_start(const void* parameter)
struct gui_synclist list;
bool done = false;
int action, cur_sel;
-
+
menu_table = rb->root_menu_get_options(&menu_item_count);
load_from_cfg();
diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c
index d479bbd31c..3a0c34d8d6 100644
--- a/apps/plugins/open_plugins.c
+++ b/apps/plugins/open_plugins.c
@@ -49,7 +49,8 @@
static int fd_dat;
static struct gui_synclist lists;
struct open_plugin_entry_t op_entry;
-const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
+static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
+static const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
/* we only need the names for the first menu so don't bother reading paths yet */
const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry);
@@ -101,6 +102,15 @@ static bool op_entry_read_name(int fd, int selected_item)
return op_entry_read(fd, selected_item, op_name_sz);
}
+static int op_entry_checksum(void)
+{
+ if (op_entry.checksum != open_plugin_csum)
+ {
+ return 0;
+ }
+ return 1;
+}
+
static int op_entry_read_opx(const char *path)
{
int ret = -1;
@@ -112,13 +122,14 @@ static int op_entry_read_opx(const char *path)
if(len > OP_LEN && rb->strcasecmp(&((path)[len-OP_LEN]), "." OP_EXT) == 0)
{
fd_opx = rb->open(path, O_RDONLY);
- if (fd_opx)
+ if (fd_opx >= 0)
{
filesize = rb->filesize(fd_opx);
ret = filesize;
if (filesize == op_entry_sz && !op_entry_read(fd_opx, 0, op_entry_sz))
ret = 0;
-
+ else if (op_entry_checksum() <= 0)
+ ret = 0;
rb->close(fd_opx);
}
}
@@ -131,7 +142,7 @@ static void op_entry_export(int selection)
int fd = -1;
char filename [MAX_PATH + 1];
- if (!op_entry_read(fd_dat, selection, op_entry_sz))
+ if (!op_entry_read(fd_dat, selection, op_entry_sz) || op_entry_checksum() <= 0)
goto failure;
rb->snprintf(filename, MAX_PATH, "%s/%s", PLUGIN_APPS_DIR, op_entry.name);
@@ -161,6 +172,11 @@ failure:
}
+static void op_entry_set_checksum(void)
+{
+ op_entry.checksum = open_plugin_csum;
+}
+
static void op_entry_set_name(void)
{
char tmp_buf[OPEN_PLUGIN_NAMESZ+1];
@@ -277,12 +293,12 @@ static int op_entry_transfer(int fd, int fd_tmp,
void *data)
{
int entries = -1;
- if (fd_tmp && fd && rb->lseek(fd, 0, SEEK_SET) == 0)
+ if (fd_tmp >= 0 && fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0)
{
entries = 0;
while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz)
{
- if (compfn && compfn(&op_entry, entries, data) > 0)
+ if (compfn && compfn(&op_entry, entries, data) > 0 && op_entry_checksum() > 0)
{
rb->write(fd_tmp, &op_entry, op_entry_sz);
entries++;
@@ -296,6 +312,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
{
int len;
uint32_t hash;
+ uint32_t newhash;
char *pos = "";;
int fd_tmp = -1;
use_key = (use_key == true && key != NULL);
@@ -309,7 +326,8 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
{
/* need to keep the old hash so we can remove the old entry */
hash = op_entry.hash;
- open_plugin_get_hash(plugin, &op_entry.hash);
+ open_plugin_get_hash(plugin, &newhash);
+ op_entry.hash = newhash;
}
else
hash = op_entry.hash;
@@ -352,9 +370,12 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
/* hash on the parameter path if it is a file */
if (op_entry.lang_id <0 && key == op_entry.path &&
rb->file_exists(op_entry.param))
- open_plugin_get_hash(op_entry.path, &op_entry.hash);
+ {
+ open_plugin_get_hash(op_entry.path, &newhash);
+ op_entry.hash = newhash;
+ }
}
-
+ op_entry_set_checksum();
rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
}
else if(op_entry_read_opx(plugin) == op_entry_sz)
@@ -369,13 +390,13 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
open_plugin_get_hash(op_entry.path, &hash);
op_entry.hash = hash;
-
+ op_entry_set_checksum();
rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
}
else
{
if (op_entry.lang_id != LANG_SHORTCUTS)
- rb->splashf(HZ / 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
+ rb->splashf(HZ * 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
return 0;
}
}
@@ -840,7 +861,7 @@ reopen_datfile:
synclist_set(MENU_ID_MAIN, selection, items, 1);
rb->gui_synclist_draw(&lists);
- while (!exit)
+ while (!exit && fd_dat >= 0)
{
action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c
index a170a57ec4..2c0a7c3f33 100644
--- a/apps/plugins/pictureflow/pictureflow.c
+++ b/apps/plugins/pictureflow/pictureflow.c
@@ -137,7 +137,7 @@ const struct button_mapping pf_context_buttons[] =
{PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
#elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
{PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
-#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD
+#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD
{PF_QUIT, BUTTON_OFF, BUTTON_NONE},
#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
{PF_QUIT, BUTTON_REC, BUTTON_NONE},
@@ -451,6 +451,7 @@ static struct configdata config[] =
{ TYPE_INT, 0, 999999, { .int_p = &pf_cfg.last_album }, "last album", NULL },
{ TYPE_INT, 0, 1, { .int_p = &pf_cfg.backlight_mode }, "backlight", NULL },
{ TYPE_INT, 0, 999999, { .int_p = &aa_cache.idx }, "art cache pos", NULL },
+ { TYPE_INT, 0, 999999, { .int_p = &aa_cache.inspected }, "art cache inspected", NULL }
};
#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
@@ -494,6 +495,15 @@ static struct pf_track_t pf_tracks;
void reset_track_list(void);
static bool thread_is_running;
+static bool wants_to_quit = false;
+
+/*
+ Prevent picture loading thread from allocating
+ buflib memory while the main thread may be
+ performing buffer-shifting operations.
+*/
+static struct mutex buf_ctx_mutex;
+static bool buf_ctx_locked = false;
static int cover_animation_keyframe;
static int extra_fade;
@@ -536,6 +546,18 @@ static void draw_progressbar(int step, int count, char *msg);
static void draw_splashscreen(unsigned char * buf_tmp, size_t buf_tmp_size);
static void free_all_slide_prio(int prio);
+static inline void buf_ctx_lock(void)
+{
+ rb->mutex_lock(&buf_ctx_mutex);
+ buf_ctx_locked = true;
+}
+
+static inline void buf_ctx_unlock(void)
+{
+ rb->mutex_unlock(&buf_ctx_mutex);
+ buf_ctx_locked = false;
+}
+
static bool check_database(bool prompt)
{
bool needwarn = true;
@@ -1056,8 +1078,8 @@ static int create_album_untagged(struct tagcache_search *tcs,
draw_splashscreen(*buf, *bufsz);
draw_progressbar(0, total_count, "Searching " UNTAGGED);
- /* search tagcache for all <untagged> albums & save the canonicalartist seek pos */
- if (rb->tagcache_search(tcs, tag_virt_canonicalartist))
+ /* search tagcache for all <untagged> albums & save the albumartist seek pos */
+ if (rb->tagcache_search(tcs, tag_albumartist))
{
rb->tagcache_search_add_filter(tcs, tag_album, pf_idx.album_untagged_seek);
@@ -1160,7 +1182,7 @@ static int build_artist_index(struct tagcache_search *tcs,
/* artist names starts at beginning of buf */
pf_idx.artist_names = *buf;
- rb->tagcache_search(tcs, tag_virt_canonicalartist);
+ rb->tagcache_search(tcs, tag_albumartist);
res = get_tcs_search_res(ePFS_ARTIST, tcs, &(*buf), bufsz);
rb->tagcache_search_finish(tcs);
if (res < SUCCESS)
@@ -1267,7 +1289,7 @@ static int create_album_index(void)
draw_progressbar(j, pf_idx.album_ct, NULL);
if (pf_idx.album_index[j].artist_seek >= 0) { continue; }
- rb->tagcache_search(&tcs, tag_virt_canonicalartist);
+ rb->tagcache_search(&tcs, tag_albumartist);
rb->tagcache_search_add_filter(&tcs, tag_album, pf_idx.album_index[j].seek);
last = 0;
@@ -1589,6 +1611,7 @@ static int compare_tracks (const void *a_v, const void *b_v)
*/
static void create_track_index(const int slide_index)
{
+ buf_ctx_lock();
char temp[MAX_PATH + 1];
if ( slide_index == pf_tracks.cur_idx )
return;
@@ -1601,7 +1624,7 @@ static void create_track_index(const int slide_index)
if (pf_idx.album_index[slide_index].artist_idx >= 0)
{
- rb->tagcache_search_add_filter(&tcs, tag_virt_canonicalartist,
+ rb->tagcache_search_add_filter(&tcs, tag_albumartist,
pf_idx.album_index[slide_index].artist_seek);
}
@@ -1735,6 +1758,18 @@ fail:
}
/**
+ Re-grow the buflib buffer by returning space borrowed
+ for track list
+*/
+static inline void free_borrowed_tracks(void)
+{
+ rb->buflib_buffer_in(&buf_ctx, pf_tracks.borrowed);
+ pf_tracks.borrowed = 0;
+ pf_tracks.cur_idx = -1;
+ buf_ctx_unlock();
+}
+
+/**
Determine filename of the album art for the given slide_index and
store the result in buf.
The algorithm looks for the first track of the given album uses
@@ -1756,7 +1791,7 @@ static bool get_albumart_for_index_from_db(const int slide_index, char *buf,
rb->tagcache_search_add_filter(&tcs, tag_album,
pf_idx.album_index[slide_index].seek);
- rb->tagcache_search_add_filter(&tcs, tag_virt_canonicalartist,
+ rb->tagcache_search_add_filter(&tcs, tag_albumartist,
pf_idx.album_index[slide_index].artist_seek);
if ( rb->tagcache_get_next(&tcs) ) {
@@ -2002,6 +2037,7 @@ static bool create_albumart_cache(void)
{
draw_splashscreen(pf_idx.buf, pf_idx.buf_sz);
draw_progressbar(0, pf_idx.album_ct, "Preparing artwork");
+ aa_cache.inspected = 0;
for (int i=0; i < pf_idx.album_ct; i++)
{
incremental_albumart_cache(true);
@@ -2324,12 +2360,7 @@ static int read_pfraw(char* filename, int prio)
bm->height = bmph.height;
pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
- int y;
- for( y = 0; y < bm->height; y++ )
- {
- rb->read( fh, data , sizeof( pix_t ) * bm->width );
- data += bm->width;
- }
+ rb->read( fh, data , sizeof( pix_t ) * bm->width * bm->height );
rb->close( fh );
return hid;
}
@@ -2369,6 +2400,10 @@ static inline bool load_and_prepare_surface(const int slide_index,
*/
bool load_new_slide(void)
{
+ buf_ctx_lock();
+ if (wants_to_quit)
+ return false;
+
int i = -1;
if (pf_sldcache.center_idx != -1)
@@ -2415,6 +2450,7 @@ bool load_new_slide(void)
pf_sldcache.center_idx = i;
pf_sldcache.left_idx = i;
pf_sldcache.right_idx = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2445,25 +2481,33 @@ bool load_new_slide(void)
if ((prio_l < prio_r || right >= number_of_slides) && left > 0)
{
if (pf_sldcache.free == -1 && !free_slide_prio(prio_l))
+ {
+ buf_ctx_unlock();
return false;
+ }
i = lla_pop_head(&pf_sldcache.free);
if (load_and_prepare_surface(left - 1, i, prio_l))
{
lla_insert_before(&pf_sldcache.used, i, pf_sldcache.left_idx);
pf_sldcache.left_idx = i;
+ buf_ctx_unlock();
return true;
}
} else if(right < number_of_slides - 1)
{
if (pf_sldcache.free == -1 && !free_slide_prio(prio_r))
+ {
+ buf_ctx_unlock();
return false;
+ }
i = lla_pop_head(&pf_sldcache.free);
if (load_and_prepare_surface(right + 1, i, prio_r))
{
lla_insert_after(i, pf_sldcache.right_idx);
pf_sldcache.right_idx = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2478,6 +2522,7 @@ insert_first_slide:
pf_sldcache.left_idx = i;
pf_sldcache.right_idx = i;
pf_sldcache.used = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2486,6 +2531,7 @@ fail_and_refree:
{
lla_insert_tail(&pf_sldcache.free, i);
}
+ buf_ctx_unlock();
return false;
}
@@ -3002,6 +3048,10 @@ static void update_scroll_animation(void)
*/
static void cleanup(void)
{
+ wants_to_quit = true;
+ if (buf_ctx_locked)
+ buf_ctx_unlock();
+
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
@@ -3172,7 +3222,7 @@ static int main_menu(void)
return -2;
#if PF_PLAYBACK_CAPABLE
case PF_MENU_CLEAR_PLAYLIST:
- if(rb->playlist_remove_all_tracks(NULL) == 0) {
+ if(rb->warn_on_pl_erase() && rb->playlist_remove_all_tracks(NULL) == 0) {
rb->playlist_create(NULL, NULL);
rb->splash(HZ*2, ID2P(LANG_PLAYLIST_CLEARED));
}
@@ -3240,6 +3290,47 @@ static void update_cover_out_animation(void)
}
/**
+ Skip steps for zooming into the current cover
+*/
+static void interrupt_cover_in_animation(void)
+{
+ pf_state = pf_show_tracks;
+ cover_animation_keyframe = 0;
+ extra_fade = 13 * 19;
+ center_slide.distance = -5 * 19;
+ center_slide.angle = 19 + (15 * 16);
+}
+
+/**
+ Skip steps for zooming out the current cover
+*/
+static void interrupt_cover_out_animation(void)
+{
+ pf_state = pf_idle;
+ cover_animation_keyframe = 0;
+ extra_fade = 0;
+ set_current_slide(center_index);
+}
+
+/**
+ Stop zooming out the current cover and start zooming in
+*/
+static void revert_cover_out_animation(void)
+{
+ pf_state = pf_cover_in;
+ cover_animation_keyframe = 34 - cover_animation_keyframe;
+}
+
+/**
+ Stop zooming into the current cover and start zooming out
+*/
+static void revert_cover_in_animation(void)
+{
+ pf_state = pf_cover_out;
+ cover_animation_keyframe = 34 - cover_animation_keyframe;
+}
+
+/**
Draw a blue gradient at y with height h
*/
static inline void draw_gradient(int y, int h)
@@ -3368,7 +3459,7 @@ static void select_next_track(void)
pf_tracks.sel++;
if (pf_tracks.sel==(pf_tracks.list_visible+pf_tracks.list_start))
pf_tracks.list_start++;
- } else {
+ } else if (rb->global_settings->list_wraparound) {
/* Rollover */
pf_tracks.sel = 0;
pf_tracks.list_start = 0;
@@ -3380,7 +3471,7 @@ static void select_prev_track(void)
if (pf_tracks.sel > 0 ) {
if (pf_tracks.sel==pf_tracks.list_start) pf_tracks.list_start--;
pf_tracks.sel--;
- } else {
+ } else if (rb->global_settings->list_wraparound) {
/* Rolllover */
pf_tracks.sel = pf_tracks.count - 1;
pf_tracks.list_start = pf_tracks.count - pf_tracks.list_visible;
@@ -3388,6 +3479,19 @@ static void select_prev_track(void)
}
#if PF_PLAYBACK_CAPABLE
+
+static bool pf_warn_on_pl_erase(void)
+{
+#ifdef USEGSLIB
+ grey_show(false);
+#endif
+ bool ret = rb->warn_on_pl_erase();
+#ifdef USEGSLIB
+ grey_show(true);
+#endif
+ return ret;
+}
+
/*
* Puts the current tracklist into a newly created playlist and starts playling
*/
@@ -3420,15 +3524,21 @@ static void start_playback(bool append)
else
return;
- if (rb->global_settings->playlist_shuffle)
+ if (!append && rb->global_settings->playlist_shuffle)
position = rb->playlist_shuffle(*rb->current_tick, pf_tracks.sel);
play:
/* TODO: can we adjust selected_track if !play_selected ?
* if shuffle, we can't predict the playing track easily, and for either
* case the track list doesn't get auto scrolled*/
if(!append)
+ {
rb->playlist_start(position, 0, 0);
- old_playlist = center_slide.slide_index;
+ /* make warn on playlist erase work */
+ rb->playlist_get_current()->num_inserted_tracks = 0;
+ old_playlist = center_slide.slide_index;
+ }
+ else
+ old_playlist = -1;
old_shuffle = shuffle;
}
#endif
@@ -3509,6 +3619,28 @@ static void draw_album_text(void)
}
}
+#if PF_PLAYBACK_CAPABLE
+/**
+ Display an info message when items have been added to playlist
+*/
+static void rb_splash_added_to_playlist(void)
+{
+#ifdef USEGSLIB
+ grey_show(false);
+#if LCD_DEPTH > 1
+ rb->lcd_set_background(N_BRIGHT(0));
+ rb->lcd_set_foreground(N_BRIGHT(255));
+#endif
+ rb->lcd_clear_display();
+ rb->lcd_update();
+#endif
+ rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
+#ifdef USEGSLIB
+ grey_show(true);
+#endif
+}
+#endif
+
/**
Display an error message and wait for input.
*/
@@ -3550,6 +3682,8 @@ static int pictureflow_main(void)
#endif
}
+ rb->mutex_init(&buf_ctx_mutex);
+
init_scroll_lines();
init_reflect_table();
@@ -3757,12 +3891,14 @@ static int pictureflow_main(void)
case PF_BACK:
if ( pf_state == pf_show_tracks )
{
- rb->buflib_buffer_in(&buf_ctx, pf_tracks.borrowed);
- pf_tracks.borrowed = 0;
- pf_tracks.cur_idx = -1;
pf_state = pf_cover_out;
+ free_borrowed_tracks();
}
- if (pf_state == pf_idle || pf_state == pf_scrolling)
+ else if (pf_state == pf_cover_in)
+ revert_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+ else if (pf_state == pf_idle || pf_state == pf_scrolling)
return PLUGIN_OK;
break;
case PF_MENU:
@@ -3783,6 +3919,11 @@ static int pictureflow_main(void)
case PF_NEXT_REPEAT:
if ( pf_state == pf_show_tracks )
select_next_track();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if ( pf_state == pf_idle || pf_state == pf_scrolling )
show_next_slide();
break;
@@ -3791,24 +3932,40 @@ static int pictureflow_main(void)
case PF_PREV_REPEAT:
if ( pf_state == pf_show_tracks )
select_prev_track();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if ( pf_state == pf_idle || pf_state == pf_scrolling )
show_previous_slide();
break;
#if PF_PLAYBACK_CAPABLE
case PF_CONTEXT:
- if ( pf_cfg.auto_wps != 0 ) {
+ if (pf_cfg.auto_wps != 0 &&
+ (pf_state == pf_idle || pf_state == pf_scrolling ||
+ pf_state == pf_show_tracks || pf_state == pf_cover_out)) {
+
+ if ( pf_state == pf_scrolling)
+ {
+ set_current_slide(target);
+ pf_state = pf_idle;
+ } else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if( pf_state == pf_idle ) {
create_track_index(center_slide.slide_index);
reset_track_list();
start_playback(true);
- rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
+ free_borrowed_tracks();
}
- else if( pf_state == pf_show_tracks ) {
+ else
+ {
rb->playlist_insert_track(NULL, get_track_filename(pf_tracks.sel),
PLAYLIST_INSERT_LAST, false, true);
rb->playlist_sync(NULL);
- rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
}
+ rb_splash_added_to_playlist();
}
break;
#endif
@@ -3818,9 +3975,13 @@ static int pictureflow_main(void)
break;
}
case PF_SELECT:
- if ( pf_state == pf_idle ) {
+ if ( pf_state == pf_idle || pf_state == pf_scrolling) {
+ if (pf_state == pf_scrolling)
+ set_current_slide(target);
#if PF_PLAYBACK_CAPABLE
if(pf_cfg.auto_wps == 1) {
+ if (!pf_warn_on_pl_erase())
+ break;
create_track_index(center_slide.slide_index);
reset_track_list();
start_playback(false);
@@ -3831,15 +3992,19 @@ static int pictureflow_main(void)
#endif
pf_state = pf_cover_in;
}
- else if ( pf_state == pf_show_tracks ) {
+ else if (pf_state == pf_cover_out)
+ revert_cover_out_animation();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
#if PF_PLAYBACK_CAPABLE
+ else if (pf_state == pf_show_tracks && pf_warn_on_pl_erase()) {
start_playback(false);
if(pf_cfg.auto_wps != 0) {
pf_cfg.last_album = center_index;
return PLUGIN_GOTO_WPS;
}
-#endif
}
+#endif
break;
default:
exit_on_usb(button);
diff --git a/apps/plugins/pitch_screen.c b/apps/plugins/pitch_screen.c
new file mode 100644
index 0000000000..e24e0240a2
--- /dev/null
+++ b/apps/plugins/pitch_screen.c
@@ -0,0 +1,1279 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 Björn Stenberg
+ *
+ *
+ * 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 "plugin.h"
+#include "lib/icon_helper.h"
+#include "lib/arg_helper.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 */
+
+#define PVAR_VERBOSE 0x01
+#define PVAR_GUI 0x02
+struct pvars
+{
+ int32_t speed;
+ int32_t pitch;
+ int32_t stretch;
+ int32_t flags;
+};
+static struct pvars pitch_vars;
+
+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),
+};
+
+int viewport_get_nb_lines(const struct viewport *vp)
+{
+ return vp->height/rb->font_get(vp->font)->height;
+}
+#if 0 /* replaced with cbmp_get_icon(CBMP_Mono_7x8, Icon_ABCD, &w, &h) */
+enum icons_7x8 {
+ Icon_Plug,
+ Icon_USBPlug,
+ Icon_Mute,
+ Icon_Play,
+ Icon_Stop,
+ Icon_Pause,
+ Icon_FastForward,
+ Icon_FastBackward,
+ Icon_Record,
+ Icon_RecPause,
+ Icon_Radio,
+ Icon_Radio_Mute,
+ Icon_Repeat,
+ Icon_RepeatOne,
+ Icon_Shuffle,
+ Icon_DownArrow,
+ Icon_UpArrow,
+ Icon_RepeatAB,
+ Icon7x8Last
+};
+
+const unsigned char bitmap_icons_7x8[][7] =
+{
+ {0x08,0x1c,0x3e,0x3e,0x3e,0x14,0x14}, /* Power plug */
+ {0x1c,0x14,0x3e,0x2a,0x22,0x1c,0x08}, /* USB plug */
+ {0x01,0x1e,0x1c,0x3e,0x7f,0x20,0x40}, /* Speaker mute */
+ {0x00,0x7f,0x7f,0x3e,0x1c,0x08,0x00}, /* Play */
+ {0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f}, /* Stop */
+ {0x00,0x7f,0x7f,0x00,0x7f,0x7f,0x00}, /* Pause */
+ {0x7f,0x3e,0x1c,0x7f,0x3e,0x1c,0x08}, /* Fast forward */
+ {0x08,0x1c,0x3e,0x7f,0x1c,0x3e,0x7f}, /* Fast backward */
+ {0x1c,0x3e,0x7f,0x7f,0x7f,0x3e,0x1c}, /* Record */
+ {0x1c,0x3e,0x7f,0x00,0x7f,0x3e,0x1c}, /* Record pause */
+ {0x40,0xa0,0xa0,0xa0,0x7f,0x02,0x02}, /* Radio on */
+ {0x42,0xa4,0xa8,0xb0,0x7f,0x22,0x42}, /* Radio mute */
+ {0x44,0x4e,0x5f,0x44,0x44,0x44,0x38}, /* Repeat playmode */
+ {0x44,0x4e,0x5f,0x44,0x38,0x02,0x7f}, /* Repeat-one playmode */
+ {0x3e,0x41,0x51,0x41,0x45,0x41,0x3e}, /* Shuffle playmode (dice) */
+ {0x04,0x0c,0x1c,0x3c,0x1c,0x0c,0x04}, /* Down-arrow */
+ {0x20,0x30,0x38,0x3c,0x38,0x30,0x20}, /* Up-arrow */
+ {0x7f,0x04,0x4e,0x5f,0x44,0x38,0x7f} /* Repeat-AB playmode */
+};
+#endif
+
+/* Number of cents between entries in the cent_interp table */
+#define CENT_INTERP_INTERVAL 20
+#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
+
+/* This stores whether the pitch and speed are at their own limits */
+/* or that of the timestretching algorithm */
+static bool at_limit = false;
+
+/*
+ *
+ * The pitchscreen is divided into 3 viewports (each row is a viewport)
+ * Then each viewport is again divided into 3 colums, each showsing some infos
+ * Additionally, on touchscreen, each cell represents a button
+ *
+ * Below a sketch describing what each cell will show (what's drawn on it)
+ * --------------------------
+ * | | | | <-- pitch up in the middle (text and button)
+ * | | | | <-- arrows for mode toggling on the sides for touchscreen
+ * |------------------------|
+ * | | | | <-- semitone/speed up/down on the sides
+ * | | | | <-- reset pitch&speed in the middle
+ * |------------------------|
+ * | | | | <-- pitch down in the middle
+ * | | | | <-- Two "OK" for exit on the sides for touchscreen
+ * |------------------------|
+ *
+ *
+ */
+
+static void speak_pitch_mode(bool enqueue)
+{
+ bool timestretch_mode = rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available();
+ if (timestretch_mode)
+ rb->talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
+ if (rb->global_settings->pitch_mode_semitone)
+ rb->talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
+ else
+ rb->talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
+ return;
+}
+
+/*
+ * Fixes the viewports so they represent the 3 rows, and adds a little margin
+ * on all sides for the icons (which are drawn outside of the grid
+ *
+ * The modified viewports need to be passed to the touchscreen handling function
+ **/
+static void pitchscreen_fix_viewports(struct viewport *parent,
+ struct viewport pitch_viewports[PITCH_ITEM_COUNT])
+{
+ int i, font_height;
+ font_height = rb->font_get(parent->font)->height;
+ for (i = 0; i < PITCH_ITEM_COUNT; i++)
+ {
+ pitch_viewports[i] = *parent;
+ pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
+ pitch_viewports[i].x += ICON_BORDER;
+ pitch_viewports[i].width -= 2*ICON_BORDER;
+ }
+ pitch_viewports[PITCH_TOP].y += ICON_BORDER;
+ pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
+
+ if(pitch_viewports[PITCH_MID].height < font_height * 2)
+ pitch_viewports[PITCH_MID].height = font_height * 2;
+
+ pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
+ + pitch_viewports[PITCH_TOP].height;
+
+ pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
+ + pitch_viewports[PITCH_MID].height;
+
+ pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
+}
+
+/* must be called before pitchscreen_draw, or within
+ * since it neither clears nor updates the display */
+static void pitchscreen_draw_icons(struct screen *display,
+ struct viewport *parent)
+{
+
+ display->set_viewport(parent);
+ int w, h;
+ const unsigned char* uparrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_UpArrow, &w, &h);
+ if (uparrow)
+ display->mono_bitmap(uparrow, parent->width/2 - 3, 2, w, h);
+
+ const unsigned char* dnarrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_DownArrow, &w, &h);
+ if (dnarrow)
+ display->mono_bitmap(dnarrow, parent->width /2 - 3, parent->height - 10, w, h);
+
+ const unsigned char* fastfwd = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastForward, &w, &h);
+ if (fastfwd)
+ display->mono_bitmap(fastfwd, parent->width - 10, parent->height /2 - 4, 7, 8);
+
+ const unsigned char* fastrew = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastBackward, &w, &h);
+ if (fastrew)
+ display->mono_bitmap(fastrew, 2, parent->height /2 - 4, w, h);
+
+ display->update_viewport();
+
+}
+
+static void pitchscreen_draw(struct screen *display, int max_lines,
+ struct viewport pitch_viewports[PITCH_ITEM_COUNT],
+ int32_t pitch, int32_t semitone
+ ,int32_t speed
+ )
+{
+ const char* ptr;
+ char buf[32];
+ int w, h;
+ bool show_lang_pitch;
+ struct viewport *last_vp = NULL;
+
+ /* "Pitch up/Pitch down" - hide for a small screen,
+ * the text is drawn centered automatically
+ *
+ * note: this assumes 5 lines always fit on a touchscreen (should be
+ * reasonable) */
+ if (max_lines >= 5)
+ {
+ int w, h;
+ struct viewport *vp = &pitch_viewports[PITCH_TOP];
+ last_vp = display->set_viewport(vp);
+ display->clear_viewport();
+#ifdef HAVE_TOUCHSCREEN
+ /* two arrows in the top row, left and right column */
+ char *arrows[] = { "<", ">"};
+ display->getstringsize(arrows[0], &w, &h);
+ display->putsxy(0, vp->height/2 - h/2, arrows[0]);
+ display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
+#endif
+ /* UP: Pitch Up */
+ if (rb->global_settings->pitch_mode_semitone)
+ ptr = rb->str(LANG_PITCH_UP_SEMITONE);
+ else
+ ptr = rb->str(LANG_PITCH_UP);
+
+ display->getstringsize(ptr, &w, NULL);
+ /* draw text */
+ display->putsxy(vp->width/2 - w/2, 0, ptr);
+ display->update_viewport();
+
+ /* DOWN: Pitch Down */
+ vp = &pitch_viewports[PITCH_BOTTOM];
+ display->set_viewport(vp);
+ display->clear_viewport();
+
+#ifdef HAVE_TOUCHSCREEN
+ ptr = rb->str(LANG_KBD_OK);
+ display->getstringsize(ptr, &w, &h);
+ /* one OK in the middle first column of the vp (at half height) */
+ display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
+ /* one OK in the middle of the last column of the vp (at half height) */
+ display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
+#endif
+ if (rb->global_settings->pitch_mode_semitone)
+ ptr = rb->str(LANG_PITCH_DOWN_SEMITONE);
+ else
+ ptr = rb->str(LANG_PITCH_DOWN);
+ display->getstringsize(ptr, &w, &h);
+ /* draw text */
+ display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
+ display->update_viewport();
+ }
+
+ /* Middle section */
+ display->set_viewport(&pitch_viewports[PITCH_MID]);
+ display->clear_viewport();
+ int width_used = 0;
+
+ /* Middle section upper line - hide for a small screen */
+ if ((show_lang_pitch = (max_lines >= 3)))
+ {
+ if(rb->global_settings->pitch_mode_timestretch)
+ {
+ /* Pitch:XXX.X% */
+ if(rb->global_settings->pitch_mode_semitone)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %s%d.%02d", rb->str(LANG_PITCH),
+ semitone >= 0 ? "+" : "-",
+ abs(semitone / PITCH_SPEED_PRECISION),
+ abs((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_PITCH),
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 10));
+ }
+ }
+ else
+ {
+ /* Rate */
+ rb->snprintf(buf, sizeof(buf), "%s:", rb->str(LANG_PLAYBACK_RATE));
+ }
+ display->getstringsize(buf, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) - h, buf);
+ if (w > width_used)
+ width_used = w;
+ }
+
+ /* Middle section lower line */
+ /* "Speed:XXX%" */
+ if(rb->global_settings->pitch_mode_timestretch)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_SPEED),
+ speed / PITCH_SPEED_PRECISION,
+ (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
+ }
+ else
+ {
+ if(rb->global_settings->pitch_mode_semitone)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s%d.%02d",
+ semitone >= 0 ? "+" : "-",
+ abs(semitone / PITCH_SPEED_PRECISION),
+ abs((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ rb->snprintf(buf, sizeof(buf), "%ld.%ld%%",
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
+ }
+ }
+
+ display->getstringsize(buf, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ buf);
+ if (w > width_used)
+ width_used = w;
+
+ /* "limit" and "timestretch" labels */
+ if (max_lines >= 7)
+ {
+ if(at_limit)
+ {
+ const char * const p = rb->str(LANG_STRETCH_LIMIT);
+ display->getstringsize(p, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) + h, p);
+ if (w > width_used)
+ width_used = w;
+ }
+ }
+
+ /* Middle section left/right labels */
+ const char *leftlabel = "-2%";
+ const char *rightlabel = "+2%";
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ leftlabel = "<<";
+ rightlabel = ">>";
+ }
+
+ /* Only display if they fit */
+ display->getstringsize(leftlabel, &w, &h);
+ width_used += w;
+ display->getstringsize(rightlabel, &w, &h);
+ width_used += w;
+
+ if (width_used <= pitch_viewports[PITCH_MID].width)
+ {
+ display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ leftlabel);
+ display->putsxy((pitch_viewports[PITCH_MID].width - w),
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ rightlabel);
+ }
+ display->update_viewport();
+ display->set_viewport(last_vp);
+}
+
+static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
+ /* need this to maintain correct pitch/speed caps */
+ , int32_t speed
+ )
+{
+ int32_t new_pitch;
+ int32_t new_stretch;
+ at_limit = false;
+
+ if (pitch_delta < 0)
+ {
+ /* for large jumps, snap up to whole numbers */
+ if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch < PITCH_MIN)
+ {
+ if (!allow_cutoff)
+ {
+ return pitch;
+ }
+ new_pitch = PITCH_MIN;
+ at_limit = true;
+ }
+ }
+ else if (pitch_delta > 0)
+ {
+ /* for large jumps, snap down to whole numbers */
+ if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch > PITCH_MAX)
+ {
+ if (!allow_cutoff)
+ return pitch;
+ new_pitch = PITCH_MAX;
+ at_limit = true;
+ }
+ }
+ else
+ {
+ /* pitch_delta == 0 -> no real change */
+ return pitch;
+ }
+ if (rb->dsp_timestretch_available())
+ {
+ /* increase the multiple to increase precision of this calculation */
+ new_stretch = GET_STRETCH(new_pitch, speed);
+ if(new_stretch < STRETCH_MIN)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ }
+ else if(new_stretch > STRETCH_MAX)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ }
+
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+ }
+
+ rb->sound_set_pitch(new_pitch);
+
+ return new_pitch;
+}
+
+static int32_t get_semitone_from_pitch(int32_t pitch)
+{
+ int semitone = 0;
+ int32_t fractional_index = 0;
+
+ while(semitone < NUM_SEMITONES - 1 &&
+ pitch >= semitone_table[semitone + 1])
+ {
+ semitone++;
+ }
+
+
+ /* now find the fractional part */
+ while(pitch > (cent_interp[fractional_index + 1] *
+ semitone_table[semitone] / PITCH_SPEED_100))
+ {
+ /* Check to make sure fractional_index isn't too big */
+ /* This should never happen. */
+ if(fractional_index >= CENT_INTERP_NUM - 1)
+ {
+ break;
+ }
+ fractional_index++;
+ }
+
+ int32_t semitone_pitch_a = cent_interp[fractional_index] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ /* this will be the integer offset from the cent_interp entry */
+ int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
+ (semitone_pitch_b - semitone_pitch_a);
+ semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
+ fractional_index * CENT_INTERP_INTERVAL +
+ semitone_frac_ofs;
+
+ return semitone;
+}
+
+static int32_t get_pitch_from_semitone(int32_t semitone)
+{
+ int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
+
+ /* Find the index into the semitone table */
+ int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
+
+ /* set pitch to the semitone's integer part value */
+ int32_t pitch = semitone_table[semitone_index];
+ /* get the range of the cent modification for future calculation */
+ int32_t pitch_mod_a =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL];
+ int32_t pitch_mod_b =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL + 1];
+ /* figure out the cent mod amount based on the semitone fractional value */
+ int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
+ (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
+
+ /* modify pitch based on the mod amount we just calculated */
+ return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
+}
+
+static int32_t pitch_increase_semitone(int32_t pitch,
+ int32_t current_semitone,
+ int32_t semitone_delta
+ , int32_t speed
+ )
+{
+ int32_t new_semitone = current_semitone;
+
+ /* snap to the delta interval */
+ if(current_semitone % semitone_delta != 0)
+ {
+ if(current_semitone > 0 && semitone_delta > 0)
+ new_semitone += semitone_delta;
+ else if(current_semitone < 0 && semitone_delta < 0)
+ new_semitone += semitone_delta;
+
+ new_semitone -= new_semitone % semitone_delta;
+ }
+ else
+ new_semitone += semitone_delta;
+
+ /* clamp the pitch so it doesn't go beyond the pitch limits */
+ if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
+ {
+ new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
+ at_limit = true;
+ }
+ else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
+ {
+ new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
+ at_limit = true;
+ }
+
+ int32_t new_pitch = get_pitch_from_semitone(new_semitone);
+
+ int32_t new_stretch = GET_STRETCH(new_pitch, speed);
+
+ /* clamp the pitch so it doesn't go beyond the stretch limits */
+ if( new_stretch > STRETCH_MAX)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+ else if (new_stretch < STRETCH_MIN)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+
+ pitch_increase(pitch, new_pitch - pitch, false
+ , speed
+ );
+
+ return new_semitone;
+}
+
+#ifdef HAVE_TOUCHSCREEN
+/*
+ * Check for touchscreen presses as per sketch above in this file
+ *
+ * goes through each row of the, checks whether the touchscreen
+ * was pressed in it. Then it looks the columns of each row for specific actions
+ */
+static int pitchscreen_do_touchscreen(struct viewport vps[])
+{
+ short x, y;
+ struct viewport *this_vp = &vps[PITCH_TOP];
+ int ret;
+ static bool wait_for_release = false;
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ /* top row */
+ if (ret > ACTION_UNKNOWN)
+ { /* press on top row, left or right column
+ * only toggle mode if released */
+ int column = this_vp->width / 3;
+ if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
+ return ACTION_PS_TOGGLE_MODE;
+
+
+ else if (x >= column && x <= (2*column))
+ { /* center column pressed */
+ if (ret == BUTTON_REPEAT)
+ return ACTION_PS_INC_BIG;
+ else if (ret & BUTTON_REL)
+ return ACTION_PS_INC_SMALL;
+ }
+ return ACTION_NONE;
+ }
+
+ /* now the center row */
+ this_vp = &vps[PITCH_MID];
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ if (ret > ACTION_UNKNOWN)
+ {
+ int column = this_vp->width / 3;
+
+ if (x < column)
+ { /* left column */
+ if (ret & BUTTON_REL)
+ {
+ wait_for_release = false;
+ return ACTION_PS_NUDGE_LEFTOFF;
+ }
+ else if (ret & BUTTON_REPEAT)
+ return ACTION_PS_SLOWER;
+ if (!wait_for_release)
+ {
+ wait_for_release = true;
+ return ACTION_PS_NUDGE_LEFT;
+ }
+ }
+ else if (x > (2*column))
+ { /* right column */
+ if (ret & BUTTON_REL)
+ {
+ wait_for_release = false;
+ return ACTION_PS_NUDGE_RIGHTOFF;
+ }
+ else if (ret & BUTTON_REPEAT)
+ return ACTION_PS_FASTER;
+ if (!wait_for_release)
+ {
+ wait_for_release = true;
+ return ACTION_PS_NUDGE_RIGHT;
+ }
+ }
+ else
+ /* center column was pressed */
+ return ACTION_PS_RESET;
+ }
+
+ /* now the bottom row */
+ this_vp = &vps[PITCH_BOTTOM];
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ if (ret > ACTION_UNKNOWN)
+ {
+ int column = this_vp->width / 3;
+
+ /* left or right column is exit */
+ if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
+ return ACTION_PS_EXIT;
+ else if (x >= column && x <= (2*column))
+ { /* center column was pressed */
+ if (ret & BUTTON_REPEAT)
+ return ACTION_PS_DEC_BIG;
+ else if (ret & BUTTON_REL)
+ return ACTION_PS_DEC_SMALL;
+ }
+ return ACTION_NONE;
+ }
+ return ACTION_NONE;
+}
+
+#endif
+/*
+ returns:
+ 0 on exit
+ 1 if USB was connected
+*/
+
+int gui_syncpitchscreen_run(void)
+{
+ int button;
+ int32_t pitch = rb->sound_get_pitch();
+ int32_t semitone;
+
+ int32_t new_pitch;
+ int32_t pitch_delta;
+ bool nudged = false;
+ int i, updated = 4, decimals = 0;
+ bool exit = false;
+ /* should maybe be passed per parameter later, not needed for now */
+ struct viewport parent[NB_SCREENS];
+ struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
+ int max_lines[NB_SCREENS];
+
+ //push_current_activity(ACTIVITY_PITCHSCREEN);
+
+ int32_t new_speed = 0, new_stretch;
+
+ /* the speed variable holds the apparent speed of the playback */
+ int32_t speed;
+ if (rb->dsp_timestretch_available())
+ {
+ speed = GET_SPEED(pitch, rb->dsp_get_timestretch());
+ }
+ else
+ {
+ speed = pitch;
+ }
+
+
+
+ /* Count decimals for speaking */
+ for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
+ decimals++;
+
+ /* set the semitone index based on the current pitch */
+ semitone = get_semitone_from_pitch(pitch);
+
+ /* initialize pitchscreen vps */
+ FOR_NB_SCREENS(i)
+ {
+ rb->viewport_set_defaults(&parent[i], i);
+ max_lines[i] = viewport_get_nb_lines(&parent[i]);
+ pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
+ rb->screens[i]->set_viewport(&parent[i]);
+ rb->screens[i]->clear_viewport();
+
+ /* also, draw the icons now, it's only needed once */
+ pitchscreen_draw_icons(rb->screens[i], &parent[i]);
+ }
+
+
+ while (!exit)
+ {
+ FOR_NB_SCREENS(i)
+ pitchscreen_draw(rb->screens[i], max_lines[i],
+ pitch_viewports[i], pitch, semitone
+ , speed
+ );
+ pitch_delta = 0;
+ new_speed = 0;
+
+ if (rb->global_settings->talk_menu && updated)
+ {
+ rb->talk_shutup();
+ switch (updated)
+ {
+ case 1:
+ if (rb->global_settings->pitch_mode_semitone)
+ rb->talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
+ else
+ rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
+ break;
+ case 2:
+ rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
+ break;
+ case 3:
+ speak_pitch_mode(false);
+ break;
+ case 4:
+ if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
+ rb->talk_id(LANG_PITCH, false);
+ else
+ rb->talk_id(LANG_PLAYBACK_RATE, false);
+ rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
+ if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
+ {
+ rb->talk_id(LANG_SPEED, true);
+ rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
+ }
+ speak_pitch_mode(true);
+ break;
+ default:
+ break;
+ }
+ }
+ updated = 0;
+
+ button = rb->get_action(CONTEXT_PITCHSCREEN, HZ);
+
+#ifdef HAVE_TOUCHSCREEN
+ if (button == ACTION_TOUCHSCREEN)
+ {
+ FOR_NB_SCREENS(i)
+ button = pitchscreen_do_touchscreen(pitch_viewports[i]);
+ }
+#endif
+ switch (button)
+ {
+ case ACTION_PS_INC_SMALL:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = PITCH_SMALL_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_INC_BIG:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = PITCH_BIG_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_DEC_SMALL:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = -SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = -PITCH_SMALL_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_DEC_BIG:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = -SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = -PITCH_BIG_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_NUDGE_RIGHT:
+ if (!rb->global_settings->pitch_mode_timestretch)
+ {
+ new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ nudged = (new_pitch != pitch);
+ pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ speed = pitch;
+ updated = nudged ? 1 : 0;
+ break;
+ }
+ else
+ {
+ new_speed = speed + SPEED_SMALL_DELTA;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_FASTER:
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ new_speed = speed + SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed -= new_speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_RIGHTOFF:
+ if (nudged)
+ {
+ pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ speed = pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ nudged = false;
+ updated = 1;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_LEFT:
+ if (!rb->global_settings->pitch_mode_timestretch)
+ {
+ new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ nudged = (new_pitch != pitch);
+ pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ speed = pitch;
+ updated = nudged ? 1 : 0;
+ break;
+ }
+ else
+ {
+ new_speed = speed - SPEED_SMALL_DELTA;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_SLOWER:
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ new_speed = speed - SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_LEFTOFF:
+ if (nudged)
+ {
+ pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ speed = pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ nudged = false;
+ updated = 1;
+ }
+ break;
+
+ case ACTION_PS_RESET:
+ pitch = PITCH_SPEED_100;
+ rb->sound_set_pitch(pitch);
+ speed = PITCH_SPEED_100;
+ if (rb->dsp_timestretch_available())
+ {
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ at_limit = false;
+ }
+ semitone = get_semitone_from_pitch(pitch);
+ updated = 4;
+ break;
+
+ case ACTION_PS_TOGGLE_MODE:
+ rb->global_settings->pitch_mode_semitone = !rb->global_settings->pitch_mode_semitone;
+
+ if (rb->dsp_timestretch_available() && !rb->global_settings->pitch_mode_semitone)
+ {
+ rb->global_settings->pitch_mode_timestretch = !rb->global_settings->pitch_mode_timestretch;
+ if(!rb->global_settings->pitch_mode_timestretch)
+ {
+ /* no longer in timestretch mode. Reset speed */
+ speed = pitch;
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ }
+ }
+ rb->settings_save();
+ updated = 3;
+ break;
+
+ case ACTION_PS_EXIT:
+ exit = true;
+ break;
+
+ default:
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
+ return 1;
+ break;
+ }
+ if (pitch_delta)
+ {
+ if (rb->global_settings->pitch_mode_semitone)
+ {
+ semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
+ , speed
+ );
+ pitch = get_pitch_from_semitone(semitone);
+ }
+ else
+ {
+ pitch = pitch_increase(pitch, pitch_delta, true
+ , speed
+ );
+ semitone = get_semitone_from_pitch(pitch);
+ }
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ /* do this to make sure we properly obey the stretch limits */
+ new_speed = speed;
+ }
+ else
+ {
+ speed = pitch;
+ }
+ }
+
+ if(new_speed)
+ {
+ new_stretch = GET_STRETCH(pitch, new_speed);
+
+ /* limit the amount of stretch */
+ if(new_stretch > STRETCH_MAX)
+ {
+ new_stretch = STRETCH_MAX;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+ else if(new_stretch < STRETCH_MIN)
+ {
+ new_stretch = STRETCH_MIN;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+
+ new_stretch = GET_STRETCH(pitch, new_speed);
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+
+ /* set the amount of stretch */
+ rb->dsp_set_timestretch(new_stretch);
+
+ /* update the speed variable with the new speed */
+ speed = new_speed;
+
+ /* Reset new_speed so we only call dsp_set_timestretch */
+ /* when needed */
+ new_speed = 0;
+ }
+ }
+
+ //rb->pcmbuf_set_low_latency(false);
+ //pop_current_activity();
+
+ /* Clean up */
+ FOR_NB_SCREENS(i)
+ {
+ rb->screens[i]->set_viewport(NULL);
+ }
+
+ return 0;
+}
+
+static int arg_callback(char argchar, const char **parameter)
+{
+ int ret;
+ long num, dec;
+ bool bret;
+ //rb->splashf(100, "Arg: %c", argchar);
+ while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++;
+ switch (tolower(argchar))
+ {
+ case 'q' :
+ pitch_vars.flags &= ~PVAR_VERBOSE;
+ break;
+ case 'g' :
+ pitch_vars.flags |= PVAR_GUI;
+ break;
+ case 'p' :
+ ret = longnum_parse(parameter, &num, &dec);
+ if (ret)
+ {
+ dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
+ if (num < 0)
+ dec = -dec;
+ pitch_vars.pitch = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
+ }
+ break;
+ case 'k' :
+ ret = bool_parse(parameter, &bret);
+ if (ret)
+ {
+ if(!bret && rb->dsp_timestretch_available())
+ {
+ /* no longer in timestretch mode. Reset speed */
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ }
+ rb->dsp_timestretch_enable(bret);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "Timestretch: %s", bret ? "true" : "false");
+ int n = 10; /* 1 second */
+ while (bret && n-- > 0 && !rb->dsp_timestretch_available())
+ {
+ rb->sleep(HZ / 10);
+ }
+ }
+ break;
+ case 's' :
+ ret = longnum_parse(parameter, &num, &dec);
+ if (ret && rb->dsp_timestretch_available())
+ {
+ dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
+ if (num < 0)
+ break;
+ pitch_vars.speed = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
+
+ }
+ break;
+ default :
+ rb->splashf(HZ, "Unknown switch '%c'",argchar);
+ //return 0;
+ }
+
+ return 1;
+}
+
+void fill_pitchvars(struct pvars *pv)
+{
+ if (!pv)
+ return;
+ pv->pitch = rb->sound_get_pitch();
+
+ /* the speed variable holds the apparent speed of the playback */
+ if (rb->dsp_timestretch_available())
+ {
+ pv->speed = GET_SPEED(pv->pitch, rb->dsp_get_timestretch());
+ }
+ else
+ {
+ pv->speed = pv->pitch;
+ }
+
+ pv->stretch = GET_STRETCH(pv->pitch, pv->speed);
+ pv->flags |= PVAR_VERBOSE;
+
+}
+/* plugin entry point */
+enum plugin_status plugin_start(const void* parameter)
+{
+ /* pitch_screen
+ * accepts args -q, -g, -p=, -s=, -k=; (= sign is optional)
+ * -q silences output splash
+ * -g runs the gui (DEFAULT)
+ * -p100 would set pitch to 100%
+ * -s=90 sets speed to 90% if timestrech is enabled
+ * -k=true -k1 enables time stretch -k0 -kf-kn disables
+*/
+ bool gui = false;
+ rb->pcmbuf_set_low_latency(true);
+
+ /* Figure out whether to be in timestretch mode */
+ if (parameter == NULL) /* gui mode */
+ {
+ if (rb->global_settings->pitch_mode_timestretch && !rb->dsp_timestretch_available())
+ {
+ rb->global_settings->pitch_mode_timestretch = false;
+ rb->settings_save();
+ }
+ gui = true;
+ }
+ else
+ {
+ struct pvars cur;
+ fill_pitchvars(&cur);
+ fill_pitchvars(&pitch_vars);
+ argparse((const char*) parameter, -1, &arg_callback);
+ if (pitch_vars.pitch != cur.pitch)
+ {
+ rb->sound_set_pitch(pitch_vars.pitch);
+ pitch_vars.pitch = rb->sound_get_pitch();
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "pitch: %ld.%02ld%%",
+ pitch_vars.pitch / PITCH_SPEED_PRECISION,
+ pitch_vars.pitch % PITCH_SPEED_PRECISION);
+ }
+ if (pitch_vars.speed != cur.speed)
+ {
+ pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
+
+ /* limit the amount of stretch */
+ if(pitch_vars.stretch > STRETCH_MAX)
+ {
+ pitch_vars.stretch = STRETCH_MAX;
+ pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
+ }
+ else if(pitch_vars.stretch < STRETCH_MIN)
+ {
+ pitch_vars.stretch = STRETCH_MIN;
+ pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
+ }
+
+ pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "speed: %ld.%02ld%%",
+ pitch_vars.speed / PITCH_SPEED_PRECISION,
+ pitch_vars.speed % PITCH_SPEED_PRECISION);
+ /* set the amount of stretch */
+ rb->dsp_set_timestretch(pitch_vars.stretch);
+ }
+ gui = ((pitch_vars.flags & PVAR_GUI) == PVAR_GUI);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "GUI: %d", gui);
+
+ }
+
+ if (gui && gui_syncpitchscreen_run() == 1)
+ return PLUGIN_USB_CONNECTED;
+ rb->pcmbuf_set_low_latency(false);
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/plugins.make b/apps/plugins/plugins.make
index ac94a04c59..b430bd2792 100644
--- a/apps/plugins/plugins.make
+++ b/apps/plugins/plugins.make
@@ -101,8 +101,35 @@ else
PLUGINLIBFLAGS = $(PLUGINFLAGS) -ffunction-sections -fdata-sections
endif
+ROOT_PLUGINSLIB_DIR := $(ROOTDIR)/apps/plugins/lib
+BUILD_PLUGINSLIB_DIR := $(BUILDDIR)/apps/plugins/lib
+
+# action_helper #
+ACTION_REQ := $(addprefix $(ROOT_PLUGINSLIB_DIR)/,action_helper.pl action_helper.h) \
+ $(BUILD_PLUGINSLIB_DIR)/pluginlib_actions.o
+
+# special rule for generating and compiling action_helper
+$(BUILD_PLUGINSLIB_DIR)/action_helper.o: $(ACTION_REQ)
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -E -P \
+ $(ROOT_PLUGINSLIB_DIR)/pluginlib_actions.h - < /dev/null | $< > $(basename $@).c
+ $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(ROOT_PLUGINSLIB_DIR) \
+ $(PLUGINLIBFLAGS) -c $(basename $@).c -o $@
+
+# button_helper #
+BUTTON_REQ := $(addprefix $(ROOT_PLUGINSLIB_DIR)/,button_helper.pl button_helper.h) \
+ $(BUILD_PLUGINSLIB_DIR)/action_helper.o
+
+# special rule for generating and compiling button_helper
+$(BUILD_PLUGINSLIB_DIR)/button_helper.o: $(BUTTON_REQ) $(ROOTDIR)/firmware/export/button.h
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -dM -E -P \
+ $(addprefix -include ,button-target.h button.h) - < /dev/null | $< > $(basename $@).c
+ $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(ROOT_PLUGINSLIB_DIR) \
+ $(PLUGINLIBFLAGS) -c $(basename $@).c -o $@
+
# special pattern rule for compiling plugin lib (with function and data sections)
-$(BUILDDIR)/apps/plugins/lib/%.o: $(ROOTDIR)/apps/plugins/lib/%.c
+$(BUILD_PLUGINSLIB_DIR)/%.o: $(ROOT_PLUGINSLIB_DIR)/%.c
$(SILENT)mkdir -p $(dir $@)
$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(PLUGINLIBFLAGS) -c $< -o $@
diff --git a/apps/plugins/random_folder_advance_config.c b/apps/plugins/random_folder_advance_config.c
index ca0d5f397d..2c9fb411ac 100644
--- a/apps/plugins/random_folder_advance_config.c
+++ b/apps/plugins/random_folder_advance_config.c
@@ -211,7 +211,7 @@ static bool custom_dir(void)
rb->close(fd2);
if(errors)
/* Press button to continue */
- rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
+ rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
}
else
return false;
@@ -259,11 +259,11 @@ static int load_list(void)
{
return -2;
}
-
+
rb->read(myfd,buffer,buffer_size);
rb->close(myfd);
list = (struct file_format *)buffer;
-
+
return 0;
}
@@ -288,7 +288,7 @@ static int save_list(void)
rb->lseek(myfd,0,SEEK_SET);
rb->write(myfd,&dirs_count,sizeof(int));
rb->close(myfd);
-
+
return 1;
}
@@ -298,22 +298,22 @@ static int edit_list(void)
bool exit = false;
int button,i;
int selection, ret = 0;
-
+
/* load the dat file if not already done */
if ((list == NULL || list->count == 0) && (i = load_list()) != 0)
{
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return -1;
}
-
+
dirs_count = list->count;
-
+
rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
rb->gui_synclist_set_icon_callback(&lists,NULL);
rb->gui_synclist_set_nb_items(&lists,list->count);
rb->gui_synclist_limit_scroll(&lists,true);
rb->gui_synclist_select_item(&lists, 0);
-
+
while (!exit)
{
rb->gui_synclist_draw(&lists);
@@ -387,22 +387,22 @@ static int export_list_to_file_text(void)
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return 0;
}
-
+
if (list->count <= 0)
{
rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE);
return 0;
}
-
+
/* create and open the file */
int myfd = rb->creat(RFA_FILE_TEXT, 0666);
if (myfd < 0)
{
- rb->splashf(HZ*4, "failed to open: fd = %d, file = %s",
+ rb->splashf(HZ*4, "failed to open: fd = %d, file = %s",
myfd, RFA_FILE_TEXT);
return -1;
}
-
+
/* write each directory to file */
for (i = 0; i < list->count; i++)
{
@@ -411,7 +411,7 @@ static int export_list_to_file_text(void)
rb->fdprintf(myfd, "%s\n", list->folder[i]);
}
}
-
+
rb->close(myfd);
rb->splash(HZ, "Done");
return 1;
@@ -420,7 +420,7 @@ static int export_list_to_file_text(void)
static int import_list_from_file_text(void)
{
char line[MAX_PATH];
-
+
buffer = rb->plugin_get_audio_buffer(&buffer_size);
if (buffer == NULL)
{
@@ -434,11 +434,11 @@ static int import_list_from_file_text(void)
rb->splashf(HZ*2, "failed to open: %s", RFA_FILE_TEXT);
return -1;
}
-
+
/* set the list structure, and initialize count */
list = (struct file_format *)buffer;
list->count = 0;
-
+
while ((rb->read_line(myfd, line, MAX_PATH - 1)) > 0)
{
/* copy the dir name, and skip the newline */
@@ -448,16 +448,16 @@ static int import_list_from_file_text(void)
{
if (line[len-1] == 0x0A || line[len-1] == 0x0D)
line[len-1] = 0x00;
- if (len > 1 &&
+ if (len > 1 &&
(line[len-2] == 0x0A || line[len-2] == 0x0D))
line[len-2] = 0x00;
}
-
+
rb->strcpy(list->folder[list->count++], line);
}
-
+
rb->close(myfd);
-
+
if (list->count == 0)
{
load_list();
@@ -484,14 +484,14 @@ static int start_shuffled_play(void)
rb->splashf(HZ*2, "Not enough memory for shuffling");
return 0;
}
-
+
/* load the dat file if not already done */
if ((list == NULL || list->count == 0) && (i = load_list()) != 0)
{
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return 0;
}
-
+
if (list->count <= 0)
{
rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE);
@@ -507,7 +507,7 @@ static int start_shuffled_play(void)
}
for(i=0;i<list->count;i++)
order[i]=i;
-
+
for(i = list->count - 1; i >= 0; i--)
{
/* the rand is from 0 to RAND_MAX, so adjust to our value range */
@@ -518,7 +518,7 @@ static int start_shuffled_play(void)
order[candidate] = order[i];
order[i] = store;
}
-
+
/* We don't want whatever is playing */
if (!(rb->playlist_remove_all_tracks(NULL) == 0
&& rb->playlist_create(NULL, NULL) == 0))
@@ -643,6 +643,6 @@ enum plugin_status plugin_start(const void* parameter)
#endif
cancel = false;
-
+
return main_menu();
}
diff --git a/apps/plugins/rb_info.c b/apps/plugins/rb_info.c
new file mode 100644
index 0000000000..f82c80c0cf
--- /dev/null
+++ b/apps/plugins/rb_info.c
@@ -0,0 +1,494 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
+ * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2020 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.
+ *
+ ****************************************************************************/
+
+/* WIP rb_info common info that you wonder about when rockboxing?
+ */
+
+#include "plugin.h"
+#include "lang_enum.h"
+#include "../open_plugin.h"
+#include "logf.h"
+#include "lib/action_helper.h"
+#include "lib/button_helper.h"
+#include "lib/pluginlib_actions.h"
+
+#define MENU_ID(x) (((void*)&"RPBUTACNGX\0" + x))
+enum {
+ M_ROOT = 0,
+ M_PATHS,
+ M_BUFFERS,
+ M_BUTTONS,
+ M_BTNTEST,
+ M_ACTIONS,
+ M_CONTEXTS,
+ M_ACTTEST,
+ M_PLUGINS,
+ M_EXIT,
+ M_LAST_ITEM //ITEM COUNT
+};
+
+#define MENU_ID_PLUGINS_ITEMS 5
+
+/*Action test and Button test*/
+static struct menu_test_t {
+ int count;
+ int context;
+ int last_btn_or_act;
+} m_test;
+
+struct menu_buffer_t { const char *name; size_t size;};
+static const struct menu_buffer_t m_buffer[] =
+{
+#ifndef MAX_LOGF_SIZE
+#define MAX_LOGF_SIZE (0)
+#endif
+#ifndef CACHE_SIZE
+#define CACHE_SIZE (0)
+#endif
+ {"thread stack", DEFAULT_STACK_SIZE},
+ {"plugin buffer", PLUGIN_BUFFER_SIZE},
+ {"frame_buffer", FRAMEBUFFER_SIZE},
+ {"codec_buffer", CODEC_SIZE},
+ {"logf_buffer", MAX_LOGF_SIZE},
+ {"cache", CACHE_SIZE},
+};
+
+/* stringify the macro value */
+#define MACROVAL(x) MACROSTR(x)
+#define MACROSTR(x) #x
+static int main_last_sel = 0;
+static struct gui_synclist lists;
+static void synclist_set(char*, int, int, int);
+
+struct paths { const char *name; const char *path; };
+static const struct paths paths[] = {
+ {"Home", ""HOME_DIR},
+ {"Rockbox", ""ROCKBOX_DIR},
+ {"Plugins", ""PLUGIN_DIR},
+ {"Codecs", ""CODECS_DIR},
+ {"WPS", ""WPS_DIR},
+ {"SBS", ""SBS_DIR},
+ {"Theme", ""THEME_DIR},
+ {"Font", ""FONT_DIR},
+ {"Icon", ""ICON_DIR},
+ {"Backdrop", ""BACKDROP_DIR},
+ {"Eq", ""EQS_DIR},
+ {"Rec Presets", ""RECPRESETS_DIR},
+ {"Recordings", ""REC_BASE_DIR,},
+ {"Fm Presets", ""FMPRESET_PATH},
+ {"MAX_PATH", ""MACROVAL(MAX_PATH)" bytes"},
+};
+
+struct mainmenu { const char *name; void *menuid; int items;};
+static struct mainmenu mainmenu[M_LAST_ITEM] = {
+#define MENU_ITEM(ID, NAME, COUNT) [ID]{NAME, MENU_ID(ID), (int)COUNT}
+MENU_ITEM(M_ROOT, "Rockbox Info Plugin", M_LAST_ITEM),
+MENU_ITEM(M_PATHS, ID2P(LANG_SHOW_PATH), ARRAYLEN(paths)),
+MENU_ITEM(M_BUFFERS, ID2P(LANG_BUFFER_STAT), ARRAYLEN(m_buffer)),
+MENU_ITEM(M_BUTTONS, "Buttons", -1), /* Set at runtime in plugin_start: */
+MENU_ITEM(M_BTNTEST, "Button test", 2),
+MENU_ITEM(M_ACTIONS, "Actions", LAST_ACTION_PLACEHOLDER),
+MENU_ITEM(M_CONTEXTS, "Contexts", LAST_CONTEXT_PLACEHOLDER ),
+MENU_ITEM(M_ACTTEST, "Action test", 3),
+MENU_ITEM(M_PLUGINS, ID2P(LANG_PLUGINS), MENU_ID_PLUGINS_ITEMS),
+MENU_ITEM(M_EXIT, ID2P(LANG_MENU_QUIT), 0),
+#undef MENU_ITEM
+};
+
+static const struct mainmenu *mainitem(int selected_item)
+{
+ static const struct mainmenu empty = {0};
+ if (selected_item >= 0 && selected_item < (int) ARRAYLEN(mainmenu))
+ return &mainmenu[selected_item];
+ else
+ return &empty;
+}
+
+static void cleanup(void *parameter)
+{
+ (void)parameter;
+
+}
+
+static const char *menu_plugin_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin_api", (int)sizeof(struct plugin_api));
+ break;
+ case 1:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin buffer", PLUGIN_BUFFER_SIZE);
+ break;
+ case 2:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "frame_buffer", (int)FRAMEBUFFER_SIZE);
+ break;
+ case 3:
+ rb->snprintf(buf, buf_len, "%s: [W: %d H:%d] ", "LCD", LCD_WIDTH, LCD_HEIGHT);
+ break;
+ case 4:
+ rb->snprintf(buf, buf_len, "%s: [%d bits] ", "fb_data", (int)(sizeof(fb_data) * CHAR_BIT));
+ break;
+ case 5:
+ break;
+ }
+ return buf;
+}
+
+static const char *menu_button_test_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ int curbtn = BUTTON_NONE;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%s] ", "Button test",
+ m_test.count > 0 ? "true":"false");
+ break;
+ case 1:
+ if (m_test.count > 0)
+ {
+ if (m_test.count <= 2)
+ curbtn = rb->button_get_w_tmo(HZ * 2);
+ else
+ m_test.last_btn_or_act = BUTTON_NONE;
+ if (curbtn == BUTTON_NONE)
+ {
+ m_test.count--;
+ }
+ else
+ m_test.last_btn_or_act = curbtn;
+ }
+ get_button_names(buf, buf_len, m_test.last_btn_or_act);
+
+ break;
+ }
+ return buf;
+}
+
+static const char *menu_action_test_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ const char *fmtstr;
+ int curact = ACTION_NONE;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%s] ", "Action test",
+ m_test.count > 0 ? "true":"false");
+ break;
+ case 1:
+ if (m_test.count <= 0)
+ {
+ if (m_test.context <= 0)
+ fmtstr = "%s > ";
+ else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER - 1)
+ fmtstr = "< %s ";
+ else
+ fmtstr = "< %s > ";
+ }
+ else
+ fmtstr = "%s";
+
+ rb->snprintf(buf, buf_len, fmtstr, context_name(m_test.context));
+ break;
+ case 2:
+ if (m_test.count > 0)
+ {
+ if (m_test.count <= 2)
+ curact = rb->get_action(m_test.context, HZ * 2);
+ else
+ m_test.last_btn_or_act = ACTION_NONE;
+ if (curact == ACTION_NONE && rb->button_get(false) == BUTTON_NONE)
+ {
+ m_test.count--;
+ }
+ else
+ {
+ m_test.last_btn_or_act = curact;
+ m_test.count = 2;
+ }
+ }
+ return action_name(m_test.last_btn_or_act);
+
+ break;
+ }
+ return buf;
+}
+
+static const char* list_get_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ buf[0] = '\0';
+ if (data == MENU_ID(M_ROOT))
+ return mainitem(selected_item)->name;
+ else if (selected_item == 0) /*header text*/
+ return mainitem(main_last_sel)->name;
+ else if (selected_item >= mainitem(main_last_sel)->items - 1)
+ return ID2P(LANG_BACK);
+
+ if (data == MENU_ID(M_PATHS))
+ {
+ selected_item--;
+ if (selected_item >= 0 && selected_item < mainitem(M_PATHS)->items)
+ {
+ const struct paths *cur = &paths[selected_item];
+ rb->snprintf(buf, buf_len, "%s: [%s] ", cur->name, cur->path);
+ return buf;
+ }
+ }
+ else if (data == MENU_ID(M_BUTTONS))
+ {
+ const struct available_button *btn = &available_buttons[selected_item - 1];
+ rb->snprintf(buf, buf_len, "%s: [0x%X] ", btn->name, (unsigned int) btn->value);
+ return buf;
+ }
+ else if (data == MENU_ID(M_BTNTEST))
+ return menu_button_test_name_cb(selected_item - 1, data, buf, buf_len);
+ else if (data == MENU_ID(M_ACTIONS))
+ return action_name(selected_item - 1);
+ else if (data == MENU_ID(M_CONTEXTS))
+ return context_name(selected_item - 1);
+ else if (data == MENU_ID(M_ACTTEST))
+ return menu_action_test_name_cb(selected_item - 1, data, buf, buf_len);
+ else if (data == MENU_ID(M_BUFFERS))
+ {
+ const struct menu_buffer_t *bufm = &m_buffer[selected_item - 1];
+ rb->snprintf(buf, buf_len, "%s: [%ld bytes] ", bufm->name, (long)bufm->size);
+ return buf;
+ }
+ else if (data == MENU_ID(M_PLUGINS))
+ {
+ return menu_plugin_name_cb(selected_item - 1, data, buf, buf_len);
+ }
+ return buf;
+}
+
+static int list_voice_cb(int list_index, void* data)
+{
+ if (!rb->global_settings->talk_menu)
+ return -1;
+
+ if (data == MENU_ID(M_ROOT))
+ {
+ const char * name = mainitem(list_index)->name;
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ rb->talk_spell(name, true);
+ }
+ else if (data == MENU_ID(M_BUFFERS) || data == MENU_ID(M_PLUGINS))
+ {
+ char buf[64];
+ const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ {
+ char* bytstr = rb->strcasestr(name, "bytes");
+ if (bytstr != NULL)
+ *bytstr = '\0';
+ rb->talk_spell(name, true);
+ }
+ }
+ else
+ {
+ char buf[64];
+ const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ rb->talk_spell(name, true);
+ }
+ return 0;
+}
+
+int menu_action_cb(int action, int selected_item, bool* exit, struct gui_synclist *lists)
+{
+ if (lists->data == MENU_ID(M_ACTTEST))
+ {
+ if (selected_item == 2) /* context */
+ {
+ int ctx = m_test.context;
+ if (action == ACTION_STD_OK)
+ m_test.context++;
+ else if (action == ACTION_STD_CANCEL)
+ m_test.context--;
+
+ if (m_test.context < 0)
+ m_test.context = 0;
+ else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER)
+ m_test.context = LAST_CONTEXT_PLACEHOLDER - 1;
+
+ if (ctx != m_test.context)
+ rb->gui_synclist_speak_item(lists);
+
+ goto default_handler;
+ }
+ if (action == ACTION_STD_OK)
+ {
+ if (selected_item == 1 || selected_item == 3)
+ {
+ m_test.count = 3;
+ rb->gui_synclist_select_item(lists, 3);
+ }
+ }
+ }
+ else if (lists->data == MENU_ID(M_BTNTEST))
+ {
+ if (action == ACTION_STD_OK)
+ {
+ if (selected_item == 1 || selected_item == 2)
+ {
+ m_test.count = 3;
+ rb->gui_synclist_select_item(lists, 2);
+ }
+ }
+ }
+ if (action == ACTION_STD_OK)
+ {
+ if (lists->data == MENU_ID(M_ROOT))
+ {
+ rb->memset(&m_test, 0, sizeof(struct menu_test_t));
+ const struct mainmenu *cur = mainitem(selected_item);
+ if (cur->menuid == NULL || cur->menuid == MENU_ID(M_EXIT))
+ *exit = true;
+ else
+ {
+ main_last_sel = selected_item;
+ synclist_set(cur->menuid, 1, cur->items, 1);
+ rb->gui_synclist_draw(lists);
+ }
+ }
+ else if (selected_item <= 0) /* title */
+ {
+ rb->gui_synclist_select_item(lists, 1);
+ }
+ else if (selected_item >= (mainitem(main_last_sel)->items) - 1)/*back*/
+ {
+ action = ACTION_STD_CANCEL;
+ }
+ else if (lists->data == MENU_ID(M_ACTIONS) ||
+ lists->data == MENU_ID(M_CONTEXTS))
+ {
+ char buf[MAX_PATH];
+ const char *name = list_get_name_cb(selected_item, lists->data, buf, sizeof(buf));
+ /* splash long enough to get fingers off button then wait for new button press */
+ rb->splashf(HZ / 2, "%s %d (0x%X)", name, selected_item -1, selected_item -1);
+ rb->button_get(true);
+ }
+ }
+ if (action == ACTION_STD_CANCEL)
+ {
+ if (lists->data != MENU_ID(M_ROOT))
+ {
+ const struct mainmenu *mainm = &mainmenu[0];
+ synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
+ rb->gui_synclist_draw(lists);
+ }
+ else
+ *exit = true;
+ }
+default_handler:
+ if (rb->default_event_handler_ex(action, cleanup, NULL) == SYS_USB_CONNECTED)
+ {
+ *exit = true;
+ return PLUGIN_USB_CONNECTED;
+ }
+ return PLUGIN_OK;
+}
+
+static void synclist_set(char* menu_id, int selected_item, int items, int sel_size)
+{
+ if (items <= 0)
+ return;
+ if (selected_item < 0)
+ selected_item = 0;
+
+ list_voice_cb(0, menu_id);
+ rb->gui_synclist_init(&lists,list_get_name_cb,
+ menu_id, false, sel_size, NULL);
+
+ rb->gui_synclist_set_icon_callback(&lists,NULL);
+ rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
+ rb->gui_synclist_set_nb_items(&lists,items);
+ rb->gui_synclist_limit_scroll(&lists,true);
+ rb->gui_synclist_select_item(&lists, selected_item);
+
+}
+
+enum plugin_status plugin_start(const void* parameter)
+{
+ int ret = PLUGIN_OK;
+ int selected_item = -1;
+ int action;
+ bool redraw = true;
+ bool exit = false;
+ if (parameter)
+ {
+ //
+ }
+ mainmenu[M_BUTTONS].items = available_button_count;
+ /* add header and back item to each submenu */
+ for (int i = 1; i < M_LAST_ITEM; i++)
+ mainmenu[i].items += 2;
+
+ if (!exit)
+ {
+ const struct mainmenu *mainm = &mainmenu[0];
+ synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
+ rb->gui_synclist_draw(&lists);
+
+ while (!exit)
+ {
+ action = rb->get_action(CONTEXT_LIST, HZ / 10);
+ if (m_test.count > 0)
+ action = ACTION_REDRAW;
+
+ if (action == ACTION_NONE)
+ {
+ if (redraw)
+ {
+ action = ACTION_REDRAW;
+ redraw = false;
+ }
+ }
+ else
+ redraw = true;
+ if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
+ continue;
+ selected_item = rb->gui_synclist_get_sel_pos(&lists);
+ ret = menu_action_cb(action, selected_item, &exit, &lists);
+ }
+ }
+
+ return ret;
+}
diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c
index b211fad331..fd6099c8c6 100644
--- a/apps/recorder/keyboard.c
+++ b/apps/recorder/keyboard.c
@@ -342,6 +342,13 @@ int kbd_input(char* text, int buflen, unsigned short *kbd)
viewportmanager_theme_enable(l, false, NULL);
}
+#ifdef HAVE_TOUCHSCREEN
+ /* keyboard is unusuable in pointing mode so force 3x3 for now.
+ * TODO - fix properly by using a bigger font and changing the layout */
+ enum touchscreen_mode old_mode = touchscreen_get_mode();
+ touchscreen_set_mode(TOUCHSCREEN_BUTTON);
+#endif
+
/* initialize state */
state.text = text;
state.buflen = buflen;
@@ -680,6 +687,10 @@ int kbd_input(char* text, int buflen, unsigned short *kbd)
if (ret < 0)
splash(HZ/2, ID2P(LANG_CANCEL));
+#ifdef HAVE_TOUCHSCREEN
+ touchscreen_set_mode(old_mode);
+#endif
+
#if defined(HAVE_MORSE_INPUT) && defined(KBD_TOGGLE_INPUT)
if (global_settings.morse_input != state.morse_mode)
{
@@ -1215,16 +1226,18 @@ static void kbd_move_cursor(struct edit_state *state, int dir)
{
state->changed = CHANGED_CURSOR;
}
- else if (state->editpos > state->len_utf8)
+ else if (global_settings.list_wraparound && state->editpos > state->len_utf8)
{
state->editpos = 0;
if (global_settings.talk_menu) beep_play(1000, 150, 1500);
}
- else if (state->editpos < 0)
+ else if (global_settings.list_wraparound && state->editpos < 0)
{
state->editpos = state->len_utf8;
if (global_settings.talk_menu) beep_play(1000, 150, 1500);
}
+ else if (!global_settings.list_wraparound)
+ state->editpos -= dir;
}
static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
@@ -1235,12 +1248,22 @@ static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
pm->x += dir;
if (pm->x < 0)
{
+ if (!global_settings.list_wraparound && pm->page == 0)
+ {
+ pm->x = 0;
+ return;
+ }
if (--pm->page < 0)
pm->page = pm->pages - 1;
pm->x = pm->max_chars - 1;
}
else if (pm->x >= pm->max_chars)
{
+ if (!global_settings.list_wraparound && pm->page == pm->pages - 1)
+ {
+ pm->x = pm->max_chars - 1;
+ return;
+ }
if (++pm->page >= pm->pages)
pm->page = 0;
pm->x = 0;
@@ -1261,6 +1284,22 @@ static void kbd_move_picker_vertical(struct keyboard_parameters *pm,
#endif /* HAVE_MORSE_INPUT */
pm->y += dir;
+
+ if (!global_settings.list_wraparound)
+ {
+ if (pm->y >= pm->lines)
+ {
+ pm->y = pm->lines;
+ pm->line_edit = true;
+ }
+ else if (pm->y < 0)
+ pm->y = 0;
+ else if (pm->line_edit)
+ pm->line_edit = false;
+
+ return;
+ }
+
if (pm->line_edit)
{
pm->y = (dir > 0 ? 0 : pm->lines - 1);
diff --git a/apps/root_menu.c b/apps/root_menu.c
index 2eab43f504..e4020ae6c7 100644
--- a/apps/root_menu.c
+++ b/apps/root_menu.c
@@ -166,7 +166,7 @@ static int browser(void* param)
}
}
if (!in_hotswap)
-#endif
+#endif /*HAVE_HOTSWAP*/
strcpy(folder, last_folder);
}
push_current_activity(ACTIVITY_FILEBROWSER);
@@ -266,7 +266,7 @@ static int browser(void* param)
tc->selected_item = last_db_selection;
push_current_activity(ACTIVITY_DATABASEBROWSER);
break;
-#endif
+#endif /*HAVE_TAGCACHE*/
}
browse_context_init(&browse, filter, 0, NULL, NOICON, folder, NULL);
@@ -634,6 +634,7 @@ static int item_callback(int action,
}
return action;
}
+
static int get_selection(int last_screen)
{
int i;
@@ -683,6 +684,7 @@ static inline int load_screen(int screen)
last_screen = old_previous;
return ret_val;
}
+
static int load_context_screen(int selection)
{
const struct menu_item_ex *context_menu = NULL;
@@ -705,83 +707,154 @@ static int load_context_screen(int selection)
return retval;
}
-static int load_plugin_screen(char *plug_path, void* plug_param)
+static int load_plugin_screen(char *key)
{
- int ret_val;
+ int ret_val = PLUGIN_ERROR;
+ int loops = 100;
int old_previous = last_screen;
+ int old_global = global_status.last_screen;
last_screen = next_screen;
global_status.last_screen = (char)next_screen;
- status_save();
+ /*status_save(); //only needed if we crash */
- switch (plugin_load(plug_path, plug_param))
+ while(loops-- > 0) /* just to keep things from getting out of hand */
{
- case PLUGIN_GOTO_WPS:
- ret_val = GO_TO_WPS;
- break;
- case PLUGIN_GOTO_PLUGIN:
- ret_val = GO_TO_PLUGIN;
- break;
- case PLUGIN_OK:
- ret_val = audio_status() ? GO_TO_PREVIOUS : GO_TO_ROOT;
- break;
- default:
- ret_val = GO_TO_PREVIOUS;
- break;
- }
+ int opret = open_plugin_get_entry(key, &open_plugin_entry);
+ char *path = open_plugin_entry.path;
+ char *param = open_plugin_entry.param;
+ if (param[0] == '\0')
+ param = NULL;
+
+ int ret = plugin_load(path, param);
+
+ if (ret == PLUGIN_USB_CONNECTED || ret == PLUGIN_ERROR)
+ ret_val = GO_TO_ROOT;
+ else if (ret == PLUGIN_GOTO_WPS)
+ ret_val = GO_TO_WPS;
+ else if (ret == PLUGIN_GOTO_PLUGIN)
+ continue;
+ else
+ {
+ /* Prevents infinite loop with WPS, Plugins, Previous Screen*/
+ if (ret == PLUGIN_OK && old_global == GO_TO_WPS && !audio_status())
+ ret_val = GO_TO_ROOT;
+ ret_val = GO_TO_PREVIOUS;
+ last_screen = (old_previous == next_screen || old_global == GO_TO_ROOT)
+ ? GO_TO_ROOT : old_previous;
+ if (last_screen == GO_TO_ROOT)
+ global_status.last_screen = GO_TO_ROOT;
+ }
+ /* ret_val != GO_TO_PLUGIN */
- if (ret_val == GO_TO_PREVIOUS)
- last_screen = (old_previous == next_screen) ? GO_TO_ROOT : old_previous;
+ if (opret != OPEN_PLUGIN_NEEDS_FLUSHED || last_screen != GO_TO_WPS)
+ {
+ /* Keep the entry in case of GO_TO_PREVIOUS */
+ open_plugin_entry.hash = 0; /*remove hash -- prevents flush to disk */
+ open_plugin_entry.lang_id = LANG_PREVIOUS_SCREEN;
+ /*open_plugin_add_path(NULL, NULL, NULL);// clear entry */
+ }
+ break;
+ } /*while */
return ret_val;
}
-void root_menu(void)
+static void ignore_back_button_stub(bool ignore)
{
- int previous_browser = GO_TO_FILEBROWSER;
- int selected = 0;
- int shortcut_origin = GO_TO_ROOT;
-
- push_current_activity(ACTIVITY_MAINMENU);
+#if (CONFIG_PLATFORM&PLATFORM_ANDROID)
+ /* BACK button to be handled by Android instead of rockbox */
+ android_ignore_back_button(ignore);
+#else
+ (void) ignore;
+#endif
+}
+static int root_menu_setup_screens(void)
+{
+ int new_screen = next_screen;
if (global_settings.start_in_screen == 0)
- next_screen = (int)global_status.last_screen;
- else next_screen = global_settings.start_in_screen - 2;
+ new_screen = (int)global_status.last_screen;
+ else new_screen = global_settings.start_in_screen - 2;
+ if (new_screen == GO_TO_PLUGIN)
+ {
+ if (global_status.last_screen == GO_TO_SHORTCUTMENU)
+ {
+ /* Can make this any value other than GO_TO_SHORTCUTMENU
+ otherwise it takes over on startup when the user wanted
+ the plugin at key - LANG_START_SCREEN */
+ global_status.last_screen = GO_TO_PLUGIN;
+ }
+ if(global_status.last_screen == GO_TO_SHORTCUTMENU ||
+ global_status.last_screen == GO_TO_PLUGIN)
+ {
+ if (global_settings.start_in_screen == 0)
+ { /* Start in: Previous Screen */
+ last_screen = GO_TO_PREVIOUS;
+ global_status.last_screen = GO_TO_ROOT;
+ /* since the plugin has GO_TO_PLUGIN as origin it
+ will just return GO_TO_PREVIOUS <=> GO_TO_PLUGIN in a loop
+ To allow exit after restart we check for GO_TO_ROOT
+ if so exit to ROOT after the plugin exits */
+ }
+ }
+ }
#if CONFIG_TUNER
add_event(PLAYBACK_EVENT_START_PLAYBACK, rootmenu_start_playback_callback);
#endif
add_event(PLAYBACK_EVENT_TRACK_CHANGE, rootmenu_track_changed_callback);
#ifdef HAVE_RTC_ALARM
+ int alarm_wake_up_screen = 0;
if ( rtc_check_alarm_started(true) )
{
rtc_enable_alarm(false);
- next_screen = GO_TO_WPS;
+
+#if (defined(HAVE_RECORDING) || CONFIG_TUNER)
+ alarm_wake_up_screen = global_settings.alarm_wake_up_screen;
+#endif
+ switch (alarm_wake_up_screen)
+ {
#if CONFIG_TUNER
- if (global_settings.alarm_wake_up_screen == ALARM_START_FM)
- next_screen = GO_TO_FM;
+ case ALARM_START_FM:
+ new_screen = GO_TO_FM;
+ break;
#endif
#ifdef HAVE_RECORDING
- if (global_settings.alarm_wake_up_screen == ALARM_START_REC)
- {
- recording_start_automatic = true;
- next_screen = GO_TO_RECSCREEN;
- }
+ case ALARM_START_REC:
+ recording_start_automatic = true;
+ new_screen = GO_TO_RECSCREEN;
+ break;
#endif
+ default:
+ new_screen = GO_TO_WPS;
+ break;
+ } /* switch() */
}
#endif /* HAVE_RTC_ALARM */
#if defined(HAVE_HEADPHONE_DETECTION) || defined(HAVE_LINEOUT_DETECTION)
- if (next_screen == GO_TO_WPS && global_settings.unplug_autoresume)
+ if (new_screen == GO_TO_WPS && global_settings.unplug_autoresume)
{
- next_screen = GO_TO_ROOT;
+ new_screen = GO_TO_ROOT;
#ifdef HAVE_HEADPHONE_DETECTION
if (headphones_inserted())
- next_screen = GO_TO_WPS;
+ new_screen = GO_TO_WPS;
#endif
#ifdef HAVE_LINEOUT_DETECTION
if (lineout_inserted())
- next_screen = GO_TO_WPS;
+ new_screen = GO_TO_WPS;
#endif
}
#endif /*(HAVE_HEADPHONE_DETECTION) || (HAVE_LINEOUT_DETECTION)*/
+ return new_screen;
+}
+
+void root_menu(void)
+{
+ int previous_browser = GO_TO_FILEBROWSER;
+ int selected = 0;
+ int shortcut_origin = GO_TO_ROOT;
+
+ push_current_activity(ACTIVITY_MAINMENU);
+ next_screen = root_menu_setup_screens();
while (true)
{
@@ -793,22 +866,40 @@ void root_menu(void)
case GO_TO_ROOT:
if (last_screen != GO_TO_ROOT)
selected = get_selection(last_screen);
-#if (CONFIG_PLATFORM&PLATFORM_ANDROID)
+ global_status.last_screen = GO_TO_ROOT; /* We've returned to ROOT */
/* When we are in the main menu we want the hardware BACK
- * button to be handled by Android instead of rockbox */
- android_ignore_back_button(true);
-#endif
+ * button to be handled by HOST instead of rockbox */
+ ignore_back_button_stub(true);
+
next_screen = do_menu(&root_menu_, &selected, NULL, false);
-#if (CONFIG_PLATFORM&PLATFORM_ANDROID)
- android_ignore_back_button(false);
-#endif
+
+ ignore_back_button_stub(false);
+
if (next_screen != GO_TO_PREVIOUS)
last_screen = GO_TO_ROOT;
break;
+#ifdef HAVE_TAGCACHE
+ case GO_TO_FILEBROWSER:
+ case GO_TO_DBBROWSER:
+ previous_browser = next_screen;
+ goto load_next_screen;
+ break;
+#endif /* With !HAVE_TAGCACHE previous_browser is always GO_TO_FILEBROWSER */
+#if CONFIG_TUNER
+ case GO_TO_WPS:
+ case GO_TO_FM:
+ previous_music = next_screen;
+ goto load_next_screen;
+ break;
+#endif /* With !CONFIG_TUNER previous_music is always GO_TO_WPS */
case GO_TO_PREVIOUS:
+ {
next_screen = last_screen;
+ if (last_screen == GO_TO_PLUGIN)/* for WPS */
+ last_screen = GO_TO_PREVIOUS;
break;
+ }
case GO_TO_PREVIOUS_BROWSER:
next_screen = previous_browser;
@@ -822,11 +913,13 @@ void root_menu(void)
break;
case GO_TO_PLUGIN:
{
+
char *key;
if (global_status.last_screen == GO_TO_SHORTCUTMENU)
{
+ if (open_plugin_entry.lang_id == LANG_OPEN_PLUGIN)
+ open_plugin_entry.lang_id = LANG_SHORTCUTS;
shortcut_origin = last_screen;
- global_status.last_screen = last_screen;
key = ID2P(LANG_SHORTCUTS);
}
else
@@ -842,48 +935,44 @@ void root_menu(void)
case GO_TO_SHORTCUTMENU:
key = ID2P(LANG_SHORTCUTS);
break;
+ case GO_TO_PREVIOUS:
+ key = ID2P(LANG_PREVIOUS_SCREEN);
+ break;
default:
key = ID2P(LANG_OPEN_PLUGIN);
break;
}
}
- bool flush = (open_plugin_get_entry(key, &open_plugin_entry) == -2);
- char *path = open_plugin_entry.path;
- char *param = open_plugin_entry.param;
- if (param[0] == '\0')
- param = NULL;
-
- next_screen = load_plugin_screen(path, param);
-
- if (!flush && next_screen != GO_TO_PLUGIN)
- open_plugin_add_path(NULL, NULL, NULL);
+ next_screen = load_plugin_screen(key);
- /* shortcuts may take several trips through the GO_TO_PLUGIN case
- make sure we preserve and restore the origin */
- if (next_screen == GO_TO_PREVIOUS && shortcut_origin != GO_TO_ROOT)
+ if (next_screen == GO_TO_PREVIOUS)
{
- if (shortcut_origin != GO_TO_WPS)
- next_screen = shortcut_origin;
- shortcut_origin = GO_TO_ROOT;
+ /* shortcuts may take several trips through the GO_TO_PLUGIN
+ case make sure we preserve and restore the origin */
+ if (shortcut_origin != GO_TO_ROOT)
+ {
+ if (shortcut_origin != GO_TO_WPS)
+ next_screen = shortcut_origin;
+ shortcut_origin = GO_TO_ROOT;
+ }
+ /* skip GO_TO_PREVIOUS */
+ if (last_screen == GO_TO_BROWSEPLUGINS)
+ {
+ next_screen = last_screen;
+ last_screen = GO_TO_PLUGIN;
+ }
}
-
previous_browser = (next_screen != GO_TO_WPS) ? GO_TO_FILEBROWSER : GO_TO_PLUGIN;
break;
}
default:
-#ifdef HAVE_TAGCACHE
-/* With !HAVE_TAGCACHE previous_browser is always GO_TO_FILEBROWSER */
- if (next_screen == GO_TO_FILEBROWSER || next_screen == GO_TO_DBBROWSER)
- previous_browser = next_screen;
-#endif
-#if CONFIG_TUNER
-/* With !CONFIG_TUNER previous_music is always GO_TO_WPS */
- if (next_screen == GO_TO_WPS || next_screen == GO_TO_FM)
- previous_music = next_screen;
-#endif
- next_screen = load_screen(next_screen);
+ goto load_next_screen;
break;
} /* switch() */
+ continue;
+load_next_screen: /* load_screen is inlined */
+ next_screen = load_screen(next_screen);
}
+
}
diff --git a/apps/settings.c b/apps/settings.c
index 3f257e093a..c7dd2cf9ab 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -96,6 +96,29 @@ struct system_status global_status;
#include "usb-ibasso.h"
#endif
+#ifdef ROCKBOX_NO_TEMP_SETTINGS_FILE /* Overwrites same file each time */
+#define CONFIGFILE_TEMP CONFIGFILE
+#define NVRAM_FILE_TEMP NVRAM_FILE
+#define rename_temp_file(a,b,c)
+#else /* creates temp files on save, renames next load, saves old file if desired */
+#define CONFIGFILE_TEMP CONFIGFILE".new"
+#define NVRAM_FILE_TEMP NVRAM_FILE".new"
+static void rename_temp_file(const char *tempfile,
+ const char *file,
+ const char *oldfile)
+{
+ /* if tempfile does not exist -- Return
+ * if oldfile is supplied -- Rename file to oldfile
+ * if tempfile does exist -- Rename tempfile to file
+ */
+ if (file_exists(tempfile))
+ {
+ if (oldfile != NULL && file_exists(file))
+ rename(file, oldfile);
+ rename(tempfile, file);
+ }
+}
+#endif
long lasttime = 0;
@@ -112,6 +135,7 @@ static char nvram_buffer[NVRAM_BLOCK_SIZE];
static bool read_nvram_data(char* buf, int max_len)
{
+ rename_temp_file(NVRAM_FILE_TEMP, NVRAM_FILE, NVRAM_FILE".old");
unsigned crc32 = 0xffffffff;
int var_count = 0, i = 0, buf_pos = 0;
int fd = open(NVRAM_FILE, O_RDONLY);
@@ -182,7 +206,7 @@ static bool write_nvram_data(char* buf, int max_len)
crc32 = crc_32(&buf[NVRAM_DATA_START],
max_len-NVRAM_DATA_START-1,0xffffffff);
memcpy(&buf[4],&crc32,4);
- fd = open(NVRAM_FILE,O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ fd = open(NVRAM_FILE_TEMP,O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd >= 0)
{
int len = write(fd,buf,max_len);
@@ -203,6 +227,7 @@ void settings_load(int which)
read_nvram_data(nvram_buffer,NVRAM_BLOCK_SIZE);
if (which&SETTINGS_HD)
{
+ rename_temp_file(CONFIGFILE_TEMP, CONFIGFILE, CONFIGFILE".old");
settings_load_config(CONFIGFILE, false);
settings_load_config(FIXEDSETTINGSFILE, false);
}
@@ -571,7 +596,7 @@ static void flush_global_status_callback(void)
static void flush_config_block_callback(void)
{
write_nvram_data(nvram_buffer,NVRAM_BLOCK_SIZE);
- settings_write_config(CONFIGFILE, SETTINGS_SAVE_CHANGED);
+ settings_write_config(CONFIGFILE_TEMP, SETTINGS_SAVE_CHANGED);
}
void reset_runtime(void) {
@@ -609,6 +634,11 @@ int settings_save(void)
bool settings_save_config(int options)
{
+ /* if we have outstanding temp files it would be a good idea to flush
+ them before the user starts saving things */
+ rename_temp_file(NVRAM_FILE_TEMP, NVRAM_FILE, NULL); /* dont overwrite .old */
+ rename_temp_file(CONFIGFILE_TEMP, CONFIGFILE, NULL); /* files from last boot */
+
char filename[MAX_PATH];
const char *folder, *namebase;
switch (options)
diff --git a/apps/settings.h b/apps/settings.h
index fedec8e025..c0a913c1c6 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -476,6 +476,7 @@ struct user_settings
int default_codepage; /* set default codepage for tag conversion */
bool hold_lr_for_scroll_in_list; /* hold L/R scrolls the list left/right */
bool play_selected; /* Plays selected file even in shuffle mode */
+ bool single_mode; /* single mode - stop after every track */
bool party_mode; /* party mode - unstoppable music */
bool audioscrobbler; /* Audioscrobbler logging */
bool cuesheet;
@@ -529,6 +530,7 @@ struct user_settings
bool browse_current; /* 1=goto current song,
0=goto previous location */
bool scroll_paginated; /* 0=dont 1=do */
+ bool list_wraparound; /* wrap around to opposite end of list when scrolling */
int scroll_speed; /* long texts scrolling speed: 1-30 */
int bidir_limit; /* bidir scroll length limit */
int scroll_delay; /* delay (in 1/10s) before starting scroll */
diff --git a/apps/settings_list.c b/apps/settings_list.c
index d9ffd8cf3e..afab6dce5b 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1220,6 +1220,8 @@ const struct settings_list settings[] = {
gui_list_screen_scroll_step),
OFFON_SETTING(0,scroll_paginated,LANG_SCROLL_PAGINATED,
false,"scroll paginated",NULL),
+ OFFON_SETTING(0,list_wraparound,LANG_LIST_WRAPAROUND,
+ true,"list wraparound",NULL),
#ifdef HAVE_LCD_COLOR
{F_T_INT|F_RGB|F_THEMESETTING ,&global_settings.fg_color,-1,
@@ -1238,6 +1240,7 @@ const struct settings_list settings[] = {
#endif
/* more playback */
OFFON_SETTING(0,play_selected,LANG_PLAY_SELECTED,true,"play selected",NULL),
+ OFFON_SETTING(0,single_mode,LANG_SINGLE_MODE,false,"single mode",NULL),
OFFON_SETTING(0,party_mode,LANG_PARTY_MODE,false,"party mode",NULL),
OFFON_SETTING(0,fade_on_stop,LANG_FADE_ON_STOP,true,"volume fade",NULL),
INT_SETTING(F_TIME_SETTING, ff_rewind_min_step, LANG_FFRW_STEP, 1,
diff --git a/apps/shortcuts.c b/apps/shortcuts.c
index 7b224dde2f..3672178647 100644
--- a/apps/shortcuts.c
+++ b/apps/shortcuts.c
@@ -236,6 +236,19 @@ static void shortcuts_ata_idle_callback(void)
write(fd, buf, len);
if (sc->type == SHORTCUT_SETTING)
write(fd, sc->u.setting->cfg_name, strlen(sc->u.setting->cfg_name));
+ else if (sc->type == SHORTCUT_TIME)
+ {
+#if CONFIG_RTC
+ if (sc->u.timedata.talktime)
+ write(fd, "talk", 4);
+ else
+#endif
+ {
+ write(fd, "sleep ", 6);
+ len = snprintf(buf, MAX_PATH, "%d", sc->u.timedata.sleep_timeout);
+ write(fd, buf, len);
+ }
+ }
else
write(fd, sc->u.path, strlen(sc->u.path));
@@ -599,6 +612,7 @@ int do_shortcut_menu(void *ignored)
while (done == GO_TO_PREVIOUS)
{
+ list.count = shortcut_count;
if (simplelist_show_list(&list))
break; /* some error happened?! */
if (list.selection == -1)
@@ -645,9 +659,16 @@ int do_shortcut_menu(void *ignored)
}
break;
case SHORTCUT_SETTING:
+ {
+ int old_sleeptimer_duration = global_settings.sleeptimer_duration;
do_setting_screen(sc->u.setting,
sc->name[0] ? sc->name : P2STR(ID2P(sc->u.setting->lang_id)),NULL);
+
+ if (old_sleeptimer_duration != global_settings.sleeptimer_duration &&
+ get_sleep_timer())
+ set_sleeptimer_duration(global_settings.sleeptimer_duration);
break;
+ }
case SHORTCUT_DEBUGITEM:
run_debug_screen(sc->u.path);
break;
@@ -669,7 +690,7 @@ int do_shortcut_menu(void *ignored)
{
char timer_buf[10];
set_sleeptimer_duration(sc->u.timedata.sleep_timeout);
- splashf(HZ, "%s (%s)", str(LANG_SLEEP_TIMER),
+ splashf(HZ, "%s (%s)", str(LANG_SLEEP_TIMER),
sleep_timer_formatter(timer_buf, sizeof(timer_buf),
sc->u.timedata.sleep_timeout, NULL));
}
diff --git a/apps/talk.c b/apps/talk.c
index 73191c22c3..e440dd98b5 100644
--- a/apps/talk.c
+++ b/apps/talk.c
@@ -1188,17 +1188,44 @@ int talk_number(long n, bool enqueue)
talk_id(VOICE_HUNDRED, true);
}
- /* combination indexing */
- if (ones > 20)
+ struct queue_entry tens_swap;
+ if (get_clip(VOICE_NUMERIC_TENS_SWAP_SEPARATOR, &tens_swap) >= 0)
{
- int tens = ones/10 + 18;
- talk_id(VOICE_ZERO + tens, true);
- ones %= 10;
+ /* direct indexing */
+ if (ones <= 20)
+ {
+ talk_id(VOICE_ZERO + ones, true);
+ }
+ else if (ones)
+ {
+ int tmp = ones % 10;
+ if (tmp)
+ {
+ talk_id(VOICE_ZERO + tmp, true);
+ talk_id(VOICE_NUMERIC_TENS_SWAP_SEPARATOR, true);
+ }
+ }
+ /* combination indexing */
+ if (ones > 20)
+ {
+ int tens = ones/10 + 18;
+ talk_id(VOICE_ZERO + tens, true);
+ }
}
+ else
+ {
+ /* combination indexing */
+ if (ones > 20)
+ {
+ int tens = ones/10 + 18;
+ talk_id(VOICE_ZERO + tens, true);
+ ones %= 10;
+ }
- /* direct indexing */
- if (ones)
- talk_id(VOICE_ZERO + ones, true);
+ /* direct indexing */
+ if (ones)
+ talk_id(VOICE_ZERO + ones, true);
+ }
/* add billion, million, thousand */
if (mil)
@@ -1215,7 +1242,7 @@ int talk_number(long n, bool enqueue)
static int talk_year(long year, bool enqueue)
{
int rem;
- if(year < 1100 || year >=2000)
+ if(year < 1100 || (year >=2000 && year < 2100))
/* just say it as a regular number */
return talk_number(year, enqueue);
/* Say century */
@@ -1469,9 +1496,30 @@ void talk_setting(const void *global_settings_variable)
void talk_date(const struct tm *tm, bool enqueue)
{
- talk_id(LANG_MONTH_JANUARY + tm->tm_mon, enqueue);
- talk_number(tm->tm_mday, true);
- talk_number(1900 + tm->tm_year, true);
+ const char *format = str(LANG_VOICED_DATE_FORMAT);
+ const char *ptr;
+
+ if (!enqueue)
+ talk_shutup(); /* cut off all the pending stuff */
+
+ for (ptr = format ; *ptr ; ptr++) {
+ switch(*ptr) {
+ case 'Y':
+ talk_number(1900 + tm->tm_year, true);
+ break;
+ case 'A':
+ talk_id(LANG_MONTH_JANUARY + tm->tm_mon, true);
+ break;
+ case 'm':
+ talk_number(tm->tm_mon + 1, true);
+ break;
+ case 'd':
+ talk_number(tm->tm_mday, true);
+ break;
+ default:
+ break;
+ }
+ }
}
void talk_time(const struct tm *tm, bool enqueue)
diff --git a/apps/tree.c b/apps/tree.c
index 1f7102dbb9..63363422ba 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -128,7 +128,7 @@ static const char* tree_get_filename(int selected_item, void *data,
{
return tagtree_get_entry_name(&tc, selected_item, buffer, buffer_len);
}
- else
+ else
#endif
{
struct entry *entry = tree_get_entry_at(local_tc, selected_item);
@@ -137,7 +137,7 @@ static const char* tree_get_filename(int selected_item, void *data,
name = entry->name;
attr = entry->attr;
}
-
+
if(!(attr & ATTR_DIRECTORY))
{
switch(global_settings.show_filename_ext)
@@ -363,7 +363,7 @@ static int update_dir(void)
changed = true;
}
}
- else
+ else
#endif
{
tc.sort_dir = global_settings.sort_dir;
@@ -391,7 +391,7 @@ static int update_dir(void)
{
if(
#ifdef HAVE_TAGCACHE
- !id3db &&
+ !id3db &&
#endif
tc.dirfull )
{
@@ -474,7 +474,7 @@ void resume_directory(const char *dir)
#ifdef HAVE_TAGCACHE
if (!id3db)
#endif
- *tc.dirfilter = global_settings.dirfilter;
+ *tc.dirfilter = global_settings.dirfilter;
ret = ft_load(&tc, dir);
*tc.dirfilter = dirfilter;
if (ret < 0)
@@ -534,7 +534,7 @@ char* get_current_file(char* buffer, size_t buffer_len)
return NULL;
}
-/* Allow apps to change our dirfilter directly (required for sub browsers)
+/* Allow apps to change our dirfilter directly (required for sub browsers)
if they're suddenly going to become a file browser for example */
void set_dirfilter(int l_dirfilter)
{
@@ -712,7 +712,7 @@ static int dirbrowse(void)
#endif
if (ft_exit(&tc) == 3)
exit_func = true;
-
+
restore = true;
break;
@@ -805,6 +805,7 @@ static int dirbrowse(void)
{
case ONPLAY_MAINMENU:
return GO_TO_ROOT;
+ break;
case ONPLAY_OK:
restore = true;
@@ -817,6 +818,10 @@ static int dirbrowse(void)
case ONPLAY_START_PLAY:
return GO_TO_WPS;
break;
+
+ case ONPLAY_PLUGIN:
+ return GO_TO_PLUGIN;
+ break;
}
break;
}
@@ -962,11 +967,17 @@ int rockbox_browse(struct browse_context *browse)
if (*tc.dirfilter >= NUM_FILTER_MODES)
{
int last_context;
+ /* don't reset if its the same browse already loaded */
+ if (tc.browse != browse ||
+ !(tc.currdir[1] && strcmp(tc.currdir, browse->root) == 0))
+ {
+ tc.browse = browse;
+ tc.selected_item = 0;
+ tc.dirlevel = 0;
+
+ strlcpy(tc.currdir, browse->root, sizeof(tc.currdir));
+ }
- tc.browse = browse;
- tc.selected_item = 0;
- tc.dirlevel = 0;
- strlcpy(tc.currdir, browse->root, sizeof(tc.currdir));
start_wps = false;
last_context = curr_context;
@@ -976,7 +987,7 @@ int rockbox_browse(struct browse_context *browse)
browse->root, browse->selected);
set_current_file(current);
/* set_current_file changes dirlevel, change it back */
- tc.dirlevel = 0;
+ tc.dirlevel = 0;
}
ret_val = dirbrowse();
@@ -1100,7 +1111,7 @@ bool bookmark_play(char *resume_file, int index, unsigned long elapsed,
else search for it */
peek_filename = playlist_peek(index, filename_buf,
sizeof(filename_buf));
-
+
if (peek_filename == NULL)
{
/* playlist has shrunk, search from the top */
@@ -1110,7 +1121,7 @@ bool bookmark_play(char *resume_file, int index, unsigned long elapsed,
if (peek_filename == NULL)
return false;
}
-
+
if (strcmp(strrchr(peek_filename, '/') + 1, filename))
{
for ( i=0; i < playlist_amount(); i++ )
@@ -1185,6 +1196,7 @@ static int ft_play_filename(char *dir, char *file, int attr)
/* These two functions are called by the USB and shutdown handlers */
void tree_flush(void)
{
+ tc.browse = NULL; /* clear browse to prevent reentry to a possibly missing file */
#ifdef HAVE_TAGCACHE
tagcache_shutdown();
#endif
@@ -1209,7 +1221,7 @@ void tree_flush(void)
global_status.dircache_size = info.last_size;
#ifdef HAVE_EEPROM_SETTINGS
savecache = firmware_settings.initialized;
- #endif
+ #endif
}
else
{
@@ -1231,11 +1243,11 @@ void tree_restore(void)
#ifdef HAVE_EEPROM_SETTINGS
firmware_settings.disk_clean = false;
#endif
-
+
#ifdef HAVE_TC_RAMCACHE
remove(TAGCACHE_STATEFILE);
#endif
-
+
#ifdef HAVE_DIRCACHE
if (global_settings.dircache && dircache_resume() > 0)
{
diff --git a/apps/voice_thread.c b/apps/voice_thread.c
index 4b4a337508..6ce0f6a408 100644
--- a/apps/voice_thread.c
+++ b/apps/voice_thread.c
@@ -361,12 +361,11 @@ static enum voice_state voice_message(struct voice_thread_data *td)
{
case Q_VOICE_PLAY:
LOGFQUEUE("voice < Q_VOICE_PLAY");
- if (quiet_counter == 0)
- {
- /* Boost CPU now */
- trigger_cpu_boost();
- }
- else
+
+ /* Boost CPU now */
+ trigger_cpu_boost();
+
+ if (quiet_counter != 0)
{
/* Stop any clip still playing */
voice_stop_playback();
@@ -421,7 +420,6 @@ static enum voice_state voice_message(struct voice_thread_data *td)
/* Fall-through */
case Q_VOICE_STOP:
LOGFQUEUE("voice < Q_VOICE_STOP");
- quiet_counter = 0;
cancel_cpu_boost();
voice_stop_playback();
break;