summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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.c4
-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.c20
-rw-r--r--apps/plugin.h28
-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.c173
-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.c14
-rw-r--r--apps/talk.c72
-rw-r--r--apps/tree.c46
-rw-r--r--apps/voice_thread.c12
-rw-r--r--bootloader/main-pp.c17
-rw-r--r--docs/usb-api.md144
-rw-r--r--firmware/core_alloc.c19
-rw-r--r--firmware/drivers/audio/ak4376.c22
-rw-r--r--firmware/drivers/button.c33
-rw-r--r--firmware/drivers/ft6x06.c64
-rw-r--r--firmware/drivers/isp1583.c2
-rw-r--r--firmware/drivers/lcd-16bit-common.c20
-rw-r--r--firmware/drivers/lcd-bitmap-common.c5
-rw-r--r--firmware/drivers/m66591.c2
-rw-r--r--firmware/drivers/tuner/si4700.c55
-rw-r--r--firmware/drivers/usb-designware.c564
-rw-r--r--firmware/export/button.h2
-rw-r--r--firmware/export/config.h11
-rw-r--r--firmware/export/config/erosqnative.h6
-rw-r--r--firmware/export/config/fiiom3k.h7
-rw-r--r--firmware/export/config/samsungypr0.h2
-rw-r--r--firmware/export/config/samsungypr1.h2
-rw-r--r--firmware/export/config/sansaclipplus.h6
-rw-r--r--firmware/export/config/shanlingq1.h8
-rw-r--r--firmware/export/events.h12
-rw-r--r--firmware/export/ft6x06.h23
-rw-r--r--firmware/export/jz4760b.h5
-rw-r--r--firmware/export/si4700.h8
-rw-r--r--firmware/export/usb-designware.h3
-rw-r--r--firmware/export/usb.h2
-rw-r--r--firmware/export/usb_core.h12
-rw-r--r--firmware/export/usb_drv.h8
-rw-r--r--firmware/kernel/thread.c7
-rw-r--r--firmware/powermgmt.c10
-rw-r--r--firmware/system.c6
-rw-r--r--firmware/target/arm/as3525/debug-as3525.c4
-rw-r--r--firmware/target/arm/as3525/fmradio-i2c-as3525.c6
-rw-r--r--firmware/target/arm/as3525/system-as3525.c2
-rw-r--r--firmware/target/arm/as3525/usb-as3525.c13
-rw-r--r--firmware/target/arm/as3525/usb-drv-as3525.c4
-rw-r--r--firmware/target/arm/rk27xx/usb-drv-rk27xx.c2
-rw-r--r--firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c2
-rw-r--r--firmware/target/arm/usb-drv-arc.c2
-rw-r--r--firmware/target/arm/usb-s3c6400x.c2
-rw-r--r--firmware/target/arm/usb-tcc.c2
-rw-r--r--firmware/target/hosted/samsungypr/radio-ypr.c59
-rw-r--r--firmware/target/hosted/xduoo/button-xduoo.c6
-rw-r--r--firmware/target/mips/ingenic_jz47xx/usb-jz4740.c6
-rw-r--r--firmware/target/mips/ingenic_jz47xx/usb-jz4760.c23
-rw-r--r--firmware/target/mips/ingenic_x1000/aic-x1000.c6
-rw-r--r--firmware/target/mips/ingenic_x1000/debug-x1000.c6
-rw-r--r--firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c17
-rw-r--r--firmware/target/mips/ingenic_x1000/erosqnative/boot.make30
-rw-r--r--firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c7
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/boot.make30
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c9
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c2
-rw-r--r--firmware/target/mips/ingenic_x1000/installer-x1000.c20
-rw-r--r--firmware/target/mips/ingenic_x1000/pcm-x1000.c2
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/boot.make31
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c83
-rw-r--r--firmware/target/mips/ingenic_x1000/usb-x1000.c11
-rw-r--r--firmware/target/mips/ingenic_x1000/x1000/ost.h10
-rw-r--r--firmware/target/mips/ingenic_x1000/x1000boot.make43
-rw-r--r--firmware/usb.c11
-rw-r--r--firmware/usbstack/usb_class_driver.h7
-rw-r--r--firmware/usbstack/usb_core.c331
-rw-r--r--firmware/usbstack/usb_hid.c56
-rw-r--r--firmware/usbstack/usb_hid.h2
-rw-r--r--firmware/usbstack/usb_serial.c41
-rw-r--r--firmware/usbstack/usb_serial.h2
-rw-r--r--firmware/usbstack/usb_storage.c22
-rw-r--r--firmware/usbstack/usb_storage.h2
-rw-r--r--lib/microtar/.gitignore3
-rw-r--r--lib/microtar/LICENSE1
-rw-r--r--lib/microtar/Makefile25
-rw-r--r--lib/microtar/README.md312
-rw-r--r--lib/microtar/README.rockbox14
-rw-r--r--lib/microtar/mtar.c243
-rw-r--r--lib/microtar/src/microtar-rockbox.c115
-rw-r--r--lib/microtar/src/microtar-rockbox.h29
-rw-r--r--lib/microtar/src/microtar-stdio.c113
-rw-r--r--lib/microtar/src/microtar-stdio.h39
-rw-r--r--lib/microtar/src/microtar.c886
-rw-r--r--lib/microtar/src/microtar.h193
-rw-r--r--lib/x1000-installer/.gitignore5
-rw-r--r--lib/x1000-installer/Makefile82
-rw-r--r--lib/x1000-installer/SOURCES6
-rw-r--r--lib/x1000-installer/include/xf_error.h43
-rw-r--r--lib/x1000-installer/include/xf_flashmap.h91
-rw-r--r--lib/x1000-installer/include/xf_nandio.h130
-rw-r--r--lib/x1000-installer/include/xf_package.h65
-rw-r--r--lib/x1000-installer/include/xf_stream.h64
-rw-r--r--lib/x1000-installer/include/xf_update.h53
-rw-r--r--lib/x1000-installer/src/xf_error.c42
-rw-r--r--lib/x1000-installer/src/xf_flashmap.c327
-rw-r--r--lib/x1000-installer/src/xf_nandio.c295
-rw-r--r--lib/x1000-installer/src/xf_package.c264
-rw-r--r--lib/x1000-installer/src/xf_stream.c211
-rw-r--r--lib/x1000-installer/src/xf_update.c149
-rw-r--r--lib/x1000-installer/test/data/lines_shuffled.txt108
-rw-r--r--lib/x1000-installer/test/data/lines_sorted.txt36
-rw-r--r--lib/x1000-installer/test/main.c86
-rw-r--r--lib/x1000-installer/test/test.h39
-rw-r--r--lib/x1000-installer/test/test_flashmap.c140
-rw-r--r--lib/x1000-installer/test/test_stream.c108
-rw-r--r--lib/x1000-installer/test_lib/core_alloc.c65
-rw-r--r--lib/x1000-installer/test_lib/core_alloc.h33
-rw-r--r--lib/x1000-installer/test_lib/fakenand.c270
-rw-r--r--lib/x1000-installer/test_lib/file.c11
-rw-r--r--lib/x1000-installer/test_lib/file.h18
-rw-r--r--lib/x1000-installer/test_lib/md5.c245
-rw-r--r--lib/x1000-installer/test_lib/md5.h18
-rw-r--r--lib/x1000-installer/test_lib/nand-x1000.h112
-rw-r--r--lib/x1000-installer/test_lib/pathfuncs.c130
-rw-r--r--lib/x1000-installer/test_lib/pathfuncs.h39
-rw-r--r--lib/x1000-installer/test_lib/strlcpy.c50
-rw-r--r--lib/x1000-installer/test_lib/strlcpy.h4
-rw-r--r--lib/x1000-installer/test_lib/system.h10
-rw-r--r--lib/x1000-installer/x1000-installer.make21
-rw-r--r--manual/appendix/config_file_options.tex4
-rw-r--r--manual/appendix/file_formats.tex9
-rwxr-xr-xmanual/configure_rockbox/display_options.tex15
-rw-r--r--manual/configure_rockbox/playback_options.tex5
-rw-r--r--manual/main_menu/fmradio.tex4
-rw-r--r--rbutil/jztool/src/x1000.c13
-rw-r--r--tools/builds.pm13
-rwxr-xr-xtools/configure6
-rwxr-xr-xtools/updatelang34
-rwxr-xr-xtools/voice.pl2
-rw-r--r--uisimulator/battery.c102
-rw-r--r--uisimulator/buttonmap/fiio-m3k.c6
-rw-r--r--uisimulator/common/sim_tasks.c7
-rw-r--r--utils/reggen-ng/x1000.reggen4
212 files changed, 10952 insertions, 3133 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..f8233da92c 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -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..a19b3ee226 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,11 +800,6 @@ 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 */
@@ -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..e1b7c69f96 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 245
/* 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,21 +919,13 @@ 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 */
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..07403083f0 100644
--- a/apps/plugins/pictureflow/pictureflow.c
+++ b/apps/plugins/pictureflow/pictureflow.c
@@ -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);
@@ -2369,6 +2405,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 +2455,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 +2486,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 +2527,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 +2536,7 @@ fail_and_refree:
{
lla_insert_tail(&pf_sldcache.free, i);
}
+ buf_ctx_unlock();
return false;
}
@@ -3002,6 +3053,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
@@ -3240,6 +3295,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 +3464,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 +3476,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;
@@ -3550,6 +3646,8 @@ static int pictureflow_main(void)
#endif
}
+ rb->mutex_init(&buf_ctx_mutex);
+
init_scroll_lines();
init_reflect_table();
@@ -3757,12 +3855,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 +3883,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 +3896,52 @@ 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));
}
+#ifdef USEGSLIB
+ /*
+ calling splash() without switching off the grayscale overlay
+ beforehand, will lead to image corruption and a crash
+ in testing on device (iPod 4G,iPod mini)
+ */
+ grey_show(false);
+ rb->lcd_clear_display();
+#endif
+ rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
+#ifdef USEGSLIB
+ grey_show(true);
+#endif
}
break;
#endif
@@ -3818,7 +3951,9 @@ 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) {
create_track_index(center_slide.slide_index);
@@ -3831,6 +3966,10 @@ static int pictureflow_main(void)
#endif
pf_state = pf_cover_in;
}
+ else if (pf_state == pf_cover_out)
+ revert_cover_out_animation();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
else if ( pf_state == pf_show_tracks ) {
#if PF_PLAYBACK_CAPABLE
start_playback(false);
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..443183934b 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)
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;
diff --git a/bootloader/main-pp.c b/bootloader/main-pp.c
index 08b5ea3ef5..15f6ad4fb7 100644
--- a/bootloader/main-pp.c
+++ b/bootloader/main-pp.c
@@ -51,6 +51,9 @@
#if defined(SANSA_E200) || defined(SANSA_C200) || defined(PHILIPS_SA9200)
#include "usb_drv.h"
#endif
+#if defined(SANSA_E200) && defined(HAVE_BOOTLOADER_USB_MODE)
+#include "core_alloc.h"
+#endif
#if defined(SAMSUNG_YH925)
/* this function (in lcd-yh925.c) resets the screen orientation for the OF
* for use with dualbooting */
@@ -231,7 +234,18 @@ static int handle_usb(int connect_timeout)
usb = USB_HANDLED;
usb_acknowledge(SYS_USB_CONNECTED_ACK);
+#if defined(SANSA_E200) && defined(HAVE_BOOTLOADER_USB_MODE)
+ /* E200 misses unplug randomly
+ probably fine for other targets too but needs tested */
+ while (usb_wait_for_disconnect_w_tmo(&q, HZ * 5) > 0)
+ {
+ /* timeout */
+ if (!usb_plugged())
+ break;
+ }
+#else
usb_wait_for_disconnect(&q);
+#endif
break;
}
@@ -299,6 +313,9 @@ void* main(void)
int usb = USB_EXTRACTED;
system_init();
+#if defined(SANSA_E200) && defined(HAVE_BOOTLOADER_USB_MODE)
+ core_allocator_init();
+#endif
kernel_init();
#ifdef HAVE_BOOTLOADER_USB_MODE
diff --git a/docs/usb-api.md b/docs/usb-api.md
new file mode 100644
index 0000000000..a21d6fd703
--- /dev/null
+++ b/docs/usb-api.md
@@ -0,0 +1,144 @@
+Handling USB control requests
+=============================
+
+API overview
+------------
+
+ enum usb_control_response {
+ USB_CONTROL_ACK,
+ USB_CONTROL_STALL,
+ USB_CONTROL_RECEIVE,
+ };
+
+ void usb_core_control_request(struct usb_ctrlrequest* req, void* reqdata);
+ void usb_core_control_complete(int status);
+ void usb_drv_control_response(enum usb_control_response resp,
+ void* data, int length);
+
+The two `usb_core` functions are common to all targets with a USB stack and
+are implemented in `usb_core.c`. The USB driver calls them to inform the core
+when a control request arrives or is completed.
+
+Each USB driver implements `usb_drv_control_response()`. The core calls this
+to let the driver know how to respond to each control request.
+
+### Legacy API
+
+ void usb_core_legacy_control_request(struct usb_ctrlrequest* req);
+
+The old control request API is available through this function. Drivers which
+don't yet implement the new API can use the legacy API instead. To support
+legacy drivers, the USB core implements all functions in the new API and
+emulates the old control request handling behavior, bugs included.
+
+This is intended as a stopgap measure so that old drivers keep working as-is.
+The core can start using the new API right away, and drivers can be ported
+one-by-one as time allows. Once all drivers are ported to the new API, all
+legacy driver support can be removed.
+
+Request handling process
+------------------------
+
+The driver submits control requests to the USB core one at a time. Once a
+request is submitted, it must be completed before the next request can be
+submitted. This mirrors normal USB operation.
+
+When the USB driver receives a setup packet from the host, it submits it
+to the core to begin handling the control transfer. The driver calls
+`usb_core_control_request(req, NULL)`, passing the setup packet in `req`.
+The second argument, `reqdata`, is not used at this time and is passed
+as `NULL`.
+
+The core processes the setup packet and calls `usb_drv_control_response()`
+when it's done. The allowed responses depend on the type of control transfer
+being processed.
+
+### Non-data transfers
+
+- `USB_CONTROL_ACK`, to indicate the request was processed successfully.
+- `USB_CONTROL_STALL`, if the request is unsupported or cannot be processed.
+
+### Control read transfers
+
+- `USB_CONTROL_ACK`, to indicate the request was processed successfully.
+ The core must provide a valid `data` buffer with `length` not exceeding
+ the `wLength` field in the setup packet; otherwise, driver behavior is
+ undefined. The driver will transfer this data to the host during the
+ data phase of the control transfer, and then acknowledge the host's OUT
+ packet to complete the transfer successfully.
+- `USB_CONTROL_STALL`, if the request is unsupported or cannot be processed.
+
+### Control write transfers
+
+The driver calls `usb_core_control_request()` twice to handle control writes.
+The first call allows the core to handle the setup packet, and if the core
+decides to accept the data phase, the second call is made when the data has
+been received without error.
+
+#### Setup phase
+
+The first call is made at the end of the setup phase, after receiving the
+setup packet. The driver passes `reqdata = NULL` to indicate this.
+
+The core can decide whether it wants to receive the data phase:
+
+- `USB_CONTROL_RECEIVE`, if the core wishes to continue to the data phase.
+ The core must provide a valid `data` buffer with `length` greater than or
+ equal to the `wLength` specified in the setup packet; otherwise, driver
+ behavior is undefined. The driver will proceed to the data phase and store
+ received data into the provided buffer.
+- `USB_CONTROL_STALL`, if the request is unsupported or cannot be processed.
+
+If the core accepts the data phase, the driver will re-submit the request
+when the data phase is completed correctly. If any error occurs during the
+data phase, the driver will not re-submit the request; instead, it will
+call `usb_core_control_complete()` with a non-zero status code.
+
+#### Status phase
+
+The second call to `usb_core_control_request()` is made at the end of the data
+phase. The `reqdata` passed by the driver is the same one that the core passed
+in its `USB_CONTROL_RECEIVE` response.
+
+The core's allowed responses are:
+
+- `USB_CONTROL_ACK`, to indicate the request was processed successfully.
+- `USB_CONTROL_STALL`, if the request is unsupported or cannot be processed.
+
+### Request completion
+
+The driver will notify the core when a request has completed by calling
+`usb_core_control_complete()`. A status code of zero means the request was
+completed successfully; a non-zero code means it failed. Note that failure
+can occur even if the request was successful from the core's perspective.
+
+If the core response is `USB_CONTROL_STALL` at any point, the request is
+considered complete. In this case, the driver won't deliver a completion
+notification because it would be redundant.
+
+The driver may only complete a request after the core has provided a response
+to any pending `usb_core_control_request()` call. Specifically, if the core
+has not yet responded to a request, the driver needs to defer the completion
+notification until it sees the core's response. If the core's response is a
+stall, then the notification should be silently dropped.
+
+### Notes
+
+- Driver behavior is undefined if the core makes an inappropriate response
+ to a request, for example, responding with `USB_CONTROL_ACK` in the setup
+ phase of a control write or `USB_CONTROL_RECEIVE` to a non-data request.
+ The only permissible responses are the documented ones.
+
+- If a response requires a buffer, then `data` must be non-NULL unless the
+ `length` is also zero. If a buffer is not required, the core must pass
+ `data = NULL` and `length = 0`. Otherwise, driver behavior is undefined.
+ There are two responses which require a buffer:
+ + `USB_CONTROL_ACK` to a control read
+ + `USB_CONTROL_RECEIVE` to the setup phase of a control write
+
+- Drivers must be prepared to accept a setup packet at any time, including
+ in the middle of a control request. In such a case, devices are required
+ to abort the ongoing request and start handling the new request. (This is
+ intended as an error recovery mechanism and should not be abused by hosts
+ in normal operation.) The driver must take care to notify the core of the
+ current request's failure, and then submit the new request.
diff --git a/firmware/core_alloc.c b/firmware/core_alloc.c
index df1b4d3213..bf2f8e8298 100644
--- a/firmware/core_alloc.c
+++ b/firmware/core_alloc.c
@@ -8,16 +8,27 @@
/* not static so it can be discovered by core_get_data() */
struct buflib_context core_ctx;
-/* defined in linker script */
#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && !defined(__PCTOOL__)
+
#if defined(IPOD_VIDEO) && !defined(BOOTLOADER)
+/* defined in linker script */
+extern unsigned char audiobuffer[];
extern unsigned char *audiobufend_lds[];
+/* pointer to end of audio buffer filled at runtime allocator_init */
unsigned char *audiobufend;
-#else /* !IPOD_VIDEO */
-extern unsigned char audiobufend[];
-#endif
+#elif defined(SANSA_E200) && defined(HAVE_BOOTLOADER_USB_MODE)
+/* defined in linker script */
+extern unsigned char freebuffer[];
+extern unsigned char freebufferend[];
+/* map linker symbol to the audiobuffer in order to use core_alloc */
+unsigned char *audiobuffer = (unsigned char *)freebuffer;
+unsigned char *audiobufend = (unsigned char *)freebufferend;
+#else /* !IPOD_VIDEO, !SANSA_E200&&BOOTLOADERUSB */
/* defined in linker script */
extern unsigned char audiobuffer[];
+extern unsigned char audiobufend[];
+#endif
+
#else /* PLATFORM_HOSTED */
static unsigned char audiobuffer[((MEMORYSIZE)*1024-768)*1024];
unsigned char *audiobufend = audiobuffer + sizeof(audiobuffer);
diff --git a/firmware/drivers/audio/ak4376.c b/firmware/drivers/audio/ak4376.c
index 11714b210d..8206bcf7e0 100644
--- a/firmware/drivers/audio/ak4376.c
+++ b/firmware/drivers/audio/ak4376.c
@@ -83,11 +83,11 @@ void ak4376_open(void)
ak4376_set_pdn_pin(1);
mdelay(1);
- static const int init_config[] = {
+ static const uint8_t init_config[] = {
/* Ensure HPRHZ, HPLHZ are 0 */
AK4376_REG_OUTPUT_MODE, 0x00,
/* Mute all volume controls */
- AK4376_REG_MIXER, 0x00,
+ AK4376_REG_MIXER, AK4376_MIX_LCH | (AK4376_MIX_RCH << 4),
AK4376_REG_LCH_VOLUME, 0x80,
AK4376_REG_RCH_VOLUME, 0x00,
AK4376_REG_AMP_VOLUME, 0x00,
@@ -150,14 +150,9 @@ static int round_step_up(int x, int step)
return x - rem;
}
-static void calc_volumes(int vol, int* mix, int* dig, int* sw)
+static void calc_volumes(int vol, int* dig, int* sw)
{
- /* Mixer can divide by 2, which gives an extra -6 dB adjustment */
- if(vol < AK4376_DIG_VOLUME_MIN) {
- *mix |= AK4376_MIX_HALF;
- vol += 60;
- }
-
+ /* Apply digital volume */
*dig = round_step_up(vol, AK4376_DIG_VOLUME_STEP);
*dig = MIN(*dig, AK4376_DIG_VOLUME_MAX);
*dig = MAX(*dig, AK4376_DIG_VOLUME_MIN);
@@ -186,8 +181,8 @@ static int amp_vol_to_hw(int vol)
void ak4376_set_volume(int vol_l, int vol_r)
{
int amp;
- int mix_l = AK4376_MIX_LCH, dig_l, sw_l;
- int mix_r = AK4376_MIX_RCH, dig_r, sw_r;
+ int dig_l, sw_l;
+ int dig_r, sw_r;
if(vol_l <= AK4376_MIN_VOLUME && vol_r <= AK4376_MIN_VOLUME) {
/* Special case for full mute */
@@ -202,11 +197,10 @@ void ak4376_set_volume(int vol_l, int vol_r)
amp = MAX(amp, AK4376_AMP_VOLUME_MIN);
/* Other controls are stereo */
- calc_volumes(vol_l - amp, &mix_l, &dig_l, &sw_l);
- calc_volumes(vol_r - amp, &mix_r, &dig_r, &sw_r);
+ calc_volumes(vol_l - amp, &dig_l, &sw_l);
+ calc_volumes(vol_r - amp, &dig_r, &sw_r);
}
- ak4376_write(AK4376_REG_MIXER, (mix_l & 0xf) | ((mix_r & 0xf) << 4));
ak4376_write(AK4376_REG_LCH_VOLUME, dig_vol_to_hw(dig_l) | (1 << 7));
ak4376_write(AK4376_REG_RCH_VOLUME, dig_vol_to_hw(dig_r));
ak4376_write(AK4376_REG_AMP_VOLUME, amp_vol_to_hw(amp));
diff --git a/firmware/drivers/button.c b/firmware/drivers/button.c
index 5addfee3de..9979bc0155 100644
--- a/firmware/drivers/button.c
+++ b/firmware/drivers/button.c
@@ -70,27 +70,21 @@ static bool enable_sw_poweroff = true;
/* how long until repeat kicks in, in centiseconds */
#define REPEAT_START (30*HZ/100)
-#ifndef HAVE_TOUCHSCREEN
-/* the next two make repeat "accelerate", which is nice for lists
+/* The next two make repeat "accelerate", which is nice for lists
* which begin to scroll a bit faster when holding until the
- * real list accerelation kicks in (this smoothes acceleration)
+ * real list accerelation kicks in (this smooths acceleration).
+ *
+ * Note that touchscreen pointing events are not subject to this
+ * acceleration and always use REPEAT_INTERVAL_TOUCH. (Do repeat
+ * events even do anything sane for touchscreens??)
*/
/* the speed repeat starts at, in centiseconds */
#define REPEAT_INTERVAL_START (16*HZ/100)
/* speed repeat finishes at, in centiseconds */
#define REPEAT_INTERVAL_FINISH (5*HZ/100)
-#else
-/*
- * on touchscreen it's different, scrolling is done by swiping over the
- * screen (potentially very quickly) and is completely different from button
- * targets
- * So, on touchscreen we don't want to artifically slow down early repeats,
- * it'd have the contrary effect of making rockbox appear lagging
- */
-#define REPEAT_INTERVAL_START (5*HZ/100)
-#define REPEAT_INTERVAL_FINISH (5*HZ/100)
-#endif
+/* repeat interval for touch events */
+#define REPEAT_INTERVAL_TOUCH (5*HZ/100)
#ifdef HAVE_BUTTON_DATA
static int button_read(int *data);
@@ -100,7 +94,7 @@ static int button_read(void);
#endif
#ifdef HAVE_TOUCHSCREEN
-static int last_touchscreen_touch;
+static long last_touchscreen_touch;
#endif
#if defined(HAVE_HEADPHONE_DETECTION)
static struct timeout hp_detect_timeout; /* Debouncer for headphone plug/unplug */
@@ -275,6 +269,11 @@ static void button_tick(void)
/* yes we have repeat */
if (repeat_speed > REPEAT_INTERVAL_FINISH)
repeat_speed--;
+#ifdef HAVE_TOUCHSCREEN
+ if(btn & BUTTON_TOUCHSCREEN)
+ repeat_speed = REPEAT_INTERVAL_TOUCH;
+#endif
+
count = repeat_speed;
repeat_count++;
@@ -543,7 +542,7 @@ void button_init(void)
#endif
#endif
#ifdef HAVE_TOUCHSCREEN
- last_touchscreen_touch = 0xffff;
+ last_touchscreen_touch = -1;
#endif
/* Start polling last */
tick_add_task(button_tick);
@@ -715,7 +714,7 @@ void button_clear_queue(void)
}
#ifdef HAVE_TOUCHSCREEN
-int touchscreen_last_touch(void)
+long touchscreen_last_touch(void)
{
return last_touchscreen_touch;
}
diff --git a/firmware/drivers/ft6x06.c b/firmware/drivers/ft6x06.c
index 538ca10480..c605ee0944 100644
--- a/firmware/drivers/ft6x06.c
+++ b/firmware/drivers/ft6x06.c
@@ -24,6 +24,16 @@
#include "i2c-async.h"
#include <string.h>
+#define BYTES_PER_POINT 6
+
+#ifdef FT6x06_SWAP_AXES
+# define POS_X pos_y
+# define POS_Y pos_x
+#else
+# define POS_X pos_x
+# define POS_Y pos_y
+#endif
+
struct ft6x06_driver {
/* i2c bus data */
int i2c_cookie;
@@ -33,39 +43,42 @@ struct ft6x06_driver {
ft6x06_event_cb event_cb;
/* buffer for I2C transfers */
- uint8_t raw_data[6];
+ uint8_t raw_data[1 + 2 + BYTES_PER_POINT * FT6x06_NUM_POINTS];
};
static struct ft6x06_driver ft_drv;
struct ft6x06_state ft6x06_state;
+static inline void ft6x06_convert_point(const uint8_t* raw,
+ struct ft6x06_point* pt)
+{
+ pt->event = (raw[0] >> 6) & 0x3;
+ pt->touch_id = (raw[2] >> 4) & 0xf;
+ pt->POS_X = ((raw[0] & 0xf) << 8) | raw[1];
+ pt->POS_Y = ((raw[2] & 0xf) << 8) | raw[3];
+ pt->weight = raw[4];
+ pt->area = (raw[5] >> 4) & 0xf;
+}
+
static void ft6x06_i2c_callback(int status, i2c_descriptor* desc)
{
(void)desc;
if(status != I2C_STATUS_OK)
return;
- int evt = ft_drv.raw_data[1] >> 6;
- int tx = ft_drv.raw_data[2] | ((ft_drv.raw_data[1] & 0xf) << 8);
- int ty = ft_drv.raw_data[4] | ((ft_drv.raw_data[3] & 0xf) << 8);
-
- ft6x06_state.event = evt;
-#ifdef FT6x06_SWAP_AXES
- ft6x06_state.pos_x = ty;
- ft6x06_state.pos_y = tx;
-#else
- ft6x06_state.pos_x = tx;
- ft6x06_state.pos_y = ty;
-#endif
+ ft6x06_state.gesture = ft_drv.raw_data[1];
+ ft6x06_state.nr_points = ft_drv.raw_data[2] & 0xf;
+ for(int i = 0; i < FT6x06_NUM_POINTS; ++i) {
+ ft6x06_convert_point(&ft_drv.raw_data[3 + i * BYTES_PER_POINT],
+ &ft6x06_state.points[i]);
+ }
- ft_drv.event_cb(evt, ft6x06_state.pos_x, ft6x06_state.pos_y);
+ ft_drv.event_cb(&ft6x06_state);
}
-static void ft6x06_dummy_event_cb(int evt, int tx, int ty)
+static void ft6x06_dummy_event_cb(struct ft6x06_state* state)
{
- (void)evt;
- (void)tx;
- (void)ty;
+ (void)state;
}
void ft6x06_init(void)
@@ -74,9 +87,10 @@ void ft6x06_init(void)
memset(&ft_drv, 0, sizeof(ft_drv));
ft_drv.event_cb = ft6x06_dummy_event_cb;
- ft6x06_state.event = FT6x06_EVT_NONE;
- ft6x06_state.pos_x = 0;
- ft6x06_state.pos_y = 0;
+ memset(&ft6x06_state, 0, sizeof(struct ft6x06_state));
+ ft6x06_state.gesture = -1;
+ for(int i = 0; i < FT6x06_NUM_POINTS; ++i)
+ ft6x06_state.points[i].event = FT6x06_EVT_NONE;
/* Reserve bus management cookie */
ft_drv.i2c_cookie = i2c_async_reserve_cookies(FT6x06_BUS, 1);
@@ -85,16 +99,16 @@ void ft6x06_init(void)
ft_drv.i2c_desc.slave_addr = FT6x06_ADDR;
ft_drv.i2c_desc.bus_cond = I2C_START | I2C_STOP;
ft_drv.i2c_desc.tran_mode = I2C_READ;
- ft_drv.i2c_desc.buffer[0] = &ft_drv.raw_data[5];
+ ft_drv.i2c_desc.buffer[0] = &ft_drv.raw_data[0];
ft_drv.i2c_desc.count[0] = 1;
- ft_drv.i2c_desc.buffer[1] = &ft_drv.raw_data[0];
- ft_drv.i2c_desc.count[1] = 5;
+ ft_drv.i2c_desc.buffer[1] = &ft_drv.raw_data[1];
+ ft_drv.i2c_desc.count[1] = sizeof(ft_drv.raw_data) - 1;
ft_drv.i2c_desc.callback = ft6x06_i2c_callback;
ft_drv.i2c_desc.arg = 0;
ft_drv.i2c_desc.next = NULL;
/* Set I2C register address */
- ft_drv.raw_data[5] = 0x02;
+ ft_drv.raw_data[0] = 0x01;
}
void ft6x06_set_event_cb(ft6x06_event_cb cb)
diff --git a/firmware/drivers/isp1583.c b/firmware/drivers/isp1583.c
index 18a4e9c720..e60339d9fc 100644
--- a/firmware/drivers/isp1583.c
+++ b/firmware/drivers/isp1583.c
@@ -341,7 +341,7 @@ static void usb_handle_setup_rx(void)
if (len == 8)
{
ISP1583_DFLOW_CTRLFUN |= DFLOW_CTRLFUN_STATUS; /* Acknowledge packet */
- usb_core_control_request((struct usb_ctrlrequest*)setup_pkt_buf);
+ usb_core_legacy_control_request((struct usb_ctrlrequest*)setup_pkt_buf);
}
else
{
diff --git a/firmware/drivers/lcd-16bit-common.c b/firmware/drivers/lcd-16bit-common.c
index 25e3b89dc3..b9c9060216 100644
--- a/firmware/drivers/lcd-16bit-common.c
+++ b/firmware/drivers/lcd-16bit-common.c
@@ -316,6 +316,18 @@ void ICODE_ATTR lcd_mono_bitmap_part(const unsigned char *src, int src_x,
x += lcd_current_viewport->x;
y += lcd_current_viewport->y;
+ /* 'Bugfix' mono_bitmap_part reads ahead in the buffer, While this is a bug
+ * if the height is <= char bit pixels other memory gets read but is not used
+ * the other option is to check in the hot code path but this appears
+ * sufficient, limit to the sim to stop Address Sanitizer errors
+ */
+#if defined(SIMULATOR) && \
+ (!defined(LCD_STRIDEFORMAT) || LCD_STRIDEFORMAT != VERTICAL_STRIDE)
+ /* vertical stride targets don't seem affected by this */
+ if (height <= CHAR_BIT)
+ stride = 0;
+#endif
+
#if defined(HAVE_VIEWPORT_CLIP)
/********************* Viewport on screen clipping ********************/
/* nothing to draw? */
@@ -331,14 +343,6 @@ void ICODE_ATTR lcd_mono_bitmap_part(const unsigned char *src, int src_x,
x = 0;
}
- /* 'Bugfix' mono_bitmap_part reads ahead in the buffer,
- * if the height is <= char bit pixels other memory gets read
- * the other option is to check in the hot code path but this appears
- * sufficient
- */
- if (height <= CHAR_BIT)
- stride = 0;
-
if (y < 0)
{
height += y;
diff --git a/firmware/drivers/lcd-bitmap-common.c b/firmware/drivers/lcd-bitmap-common.c
index 170ad374ea..9a5f865f2a 100644
--- a/firmware/drivers/lcd-bitmap-common.c
+++ b/firmware/drivers/lcd-bitmap-common.c
@@ -511,9 +511,10 @@ static bool LCDFN(puts_scroll_worker)(int x, int y, const unsigned char *string,
x = x * (linebased ? cwidth : 1);
width = vp->width - x;
- if (y >= vp->height || (height + y) > (vp->height))
+ if (y >= vp->height)
return false;
-
+ if ((height + y) > (vp->height))
+ height = vp->height - y;
s = find_scrolling_line(x, y);
restart = !s;
diff --git a/firmware/drivers/m66591.c b/firmware/drivers/m66591.c
index 5da1908290..822585d882 100644
--- a/firmware/drivers/m66591.c
+++ b/firmware/drivers/m66591.c
@@ -208,7 +208,7 @@ static void control_received(void) {
/* acknowledge packet recieved (clear valid) */
M66591_INTSTAT_MAIN &= ~(1<<3);
- usb_core_control_request(&temp);
+ usb_core_legacy_control_request(&temp);
}
/* This is a helper function, it is used to notife the stack that a transfer is
diff --git a/firmware/drivers/tuner/si4700.c b/firmware/drivers/tuner/si4700.c
index 88ff6c69f7..57006b4e3c 100644
--- a/firmware/drivers/tuner/si4700.c
+++ b/firmware/drivers/tuner/si4700.c
@@ -587,12 +587,67 @@ void si4700_rds_process(void)
if (tuner_powered())
{
si4700_read_reg(RDSD);
+#if (CONFIG_RDS & RDS_CFG_POLL)
+ /* we need to keep track of the ready bit because it stays set for 80ms
+ * and we must avoid processing it twice */
+
+ static bool old_rdsr = false;
+ bool rdsr = (cache[STATUSRSSI] & STATUSRSSI_RDSR);
+ if (rdsr && !old_rdsr)
+ rds_process(&cache[RDSA]);
+ old_rdsr = rdsr;
+#else
rds_process(&cache[RDSA]);
+#endif /* !(CONFIG_RDS & RDS_CFG_POLL) */
}
mutex_unlock(&fmr_mutex);
}
#endif /* (CONFIG_RDS & RDS_CFG_ISR) */
+#if (CONFIG_RDS & RDS_CFG_POLL)
+static struct event_queue rds_queue;
+static uint32_t rds_stack[DEFAULT_STACK_SIZE / sizeof(uint32_t)];
+
+enum {
+ Q_POWERUP,
+};
+
+static void NORETURN_ATTR rds_thread(void)
+{
+ /* start up frozen */
+ int timeout = TIMEOUT_BLOCK;
+ struct queue_event ev;
+
+ while (true) {
+ queue_wait_w_tmo(&rds_queue, &ev, timeout);
+ switch (ev.id) {
+ case Q_POWERUP:
+ /* power up: timeout after 1 tick, else block indefinitely */
+ timeout = ev.data ? CONFIG_RDS_POLL_TICKS : TIMEOUT_BLOCK;
+ break;
+ case SYS_TIMEOUT:;
+ /* Captures RDS data and processes it */
+ si4700_rds_process();
+ break;
+ }
+ }
+}
+
+/* true after full radio power up, and false before powering down */
+void si4700_rds_powerup(bool on)
+{
+ queue_post(&rds_queue, Q_POWERUP, on);
+}
+
+/* One-time RDS init at startup */
+void si4700_rds_init(void)
+{
+ queue_init(&rds_queue, false);
+ create_thread(rds_thread, rds_stack, sizeof(rds_stack), 0, "rds"
+ IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU));
+}
+#endif /* !(CONFIG_RDS & RDS_CFG_POLL) */
+
#endif /* HAVE_RDS_CAP */
diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c
index ab4c6037b5..beecb5ea7a 100644
--- a/firmware/drivers/usb-designware.c
+++ b/firmware/drivers/usb-designware.c
@@ -53,13 +53,31 @@
#define COMMIT_DCACHE_RANGE(b,s) commit_dcache_range(b,s)
#endif
-/* On some platforms, virtual addresses must be mangled to
- * get a physical address for DMA
+/* USB_DW_PHYSADDR(x) converts the address of buffer x to one usable with DMA.
+ * For example, converting a virtual address to a physical address.
+ *
+ * USB_DW_UNCACHEDADDR(x) is used to get an uncached pointer to a buffer.
+ * If the platform doesn't support this, define NO_UNCACHED_ADDR instead.
+ *
+ * Define POST_DMA_FLUSH if the driver should discard DMA RX buffers after a
+ * transfer completes. Needed if the CPU can speculatively fetch cache lines
+ * in any way, eg. due to speculative execution / prefetching.
*/
#if CONFIG_CPU == X1000
-# define DMA_ADDR2PHYS(x) PHYSADDR(x)
-#else
-# define DMA_ADDR2PHYS(x) x
+# define USB_DW_PHYSADDR(x) PHYSADDR(x)
+# define USB_DW_UNCACHEDADDR(x) ((typeof(x))UNCACHEDADDR(x))
+# define POST_DMA_FLUSH
+#elif CONFIG_CPU == AS3525v2
+# define USB_DW_PHYSADDR(x) AS3525_PHYSICAL_ADDR(x)
+# define USB_DW_UNCACHEDADDR(x) AS3525_UNCACHED_ADDR(x)
+#elif CONFIG_CPU == S5L8701
+# define USB_DW_PHYSADDR(x) x
+# define NO_UNCACHED_ADDR /* Not known how to form uncached addresses */
+#elif CONFIG_CPU == S5L8702
+# define USB_DW_PHYSADDR(x) S5L8702_PHYSICAL_ADDR(x)
+# define USB_DW_UNCACHEDADDR(x) S5L8702_UNCACHED_ADDR(x)
+#elif !defined(USB_DW_ARCH_SLAVE)
+# error "Must define USB_DW_PHYSADDR / USB_DW_UNCACHEDADDR!"
#endif
#ifndef USB_DW_TOUTCAL
@@ -77,19 +95,35 @@ enum usb_dw_epdir
USB_DW_EPDIR_OUT = 1,
};
-union usb_ep0_buffer
+enum usb_dw_ep0_state
{
- struct usb_ctrlrequest setup;
- uint8_t raw[64];
+ /* Waiting for a setup packet to arrive. This is the default state. */
+ EP0_SETUP,
+
+ /* Request wait states -- after submitting a request, we enter EP0_REQ
+ * (or EP0_REQ_CTRLWRITE for control writes). EP0_REQ is also used for
+ * the 2nd phase of a control write. EP0_REQ_CANCELLED is entered if we
+ * receive a setup packet before getting a response from the USB stack. */
+ EP0_REQ,
+ EP0_REQ_CTRLWRITE,
+ EP0_REQ_CANCELLED,
+
+ /* Waiting for a data phase to complete. */
+ EP0_DATA_IN,
+ EP0_DATA_OUT,
+
+ /* Waiting for the status phase */
+ EP0_STATUS_IN,
+ EP0_STATUS_OUT,
+
+ EP0_NUM_STATES
};
-static union usb_ep0_buffer ep0_buffer USB_DEVBSS_ATTR;
-
/* Internal EP state/info */
struct usb_dw_ep
{
struct semaphore complete;
- uint32_t* req_addr;
+ void* req_addr;
uint32_t req_size;
uint32_t* addr;
uint32_t sizeleft;
@@ -99,7 +133,42 @@ struct usb_dw_ep
uint8_t busy;
};
+/* Additional state for EP0 */
+struct usb_dw_ep0
+{
+ enum usb_dw_ep0_state state;
+ struct usb_ctrlrequest active_req;
+ struct usb_ctrlrequest pending_req;
+};
+
+static const char* const dw_dir_str[USB_DW_NUM_DIRS] =
+{
+ [USB_DW_EPDIR_IN] = "IN",
+ [USB_DW_EPDIR_OUT] = "OUT",
+};
+
+static const char* const dw_state_str[EP0_NUM_STATES] =
+{
+ [EP0_SETUP] = "setup",
+ [EP0_REQ] = "req",
+ [EP0_REQ_CTRLWRITE] = "req_cw",
+ [EP0_DATA_IN] = "dat_in",
+ [EP0_DATA_OUT] = "dat_out",
+ [EP0_STATUS_IN] = "sts_in",
+ [EP0_STATUS_OUT] = "sts_out",
+};
+
+static const char* const dw_resp_str[3] =
+{
+ [USB_CONTROL_ACK] = "ACK",
+ [USB_CONTROL_RECEIVE] = "RECV",
+ [USB_CONTROL_STALL] = "STALL",
+};
+
static struct usb_dw_ep usb_dw_ep_list[USB_NUM_ENDPOINTS][USB_DW_NUM_DIRS];
+static struct usb_dw_ep0 ep0;
+uint8_t _ep0_buffer[64] USB_DEVBSS_ATTR __attribute__((aligned(32)));
+uint8_t* ep0_buffer; /* Uncached, unless NO_UNCACHED_ADDR is defined */
static uint32_t usb_endpoints; /* available EPs mask */
@@ -117,35 +186,30 @@ static uint32_t epmis_msk;
static uint32_t ep_periodic_msk;
#endif
-static const char *dw_dir_str[USB_DW_NUM_DIRS] =
-{
- [USB_DW_EPDIR_IN] = "IN",
- [USB_DW_EPDIR_OUT] = "OUT",
-};
-
-
static struct usb_dw_ep *usb_dw_get_ep(int epnum, enum usb_dw_epdir epdir)
{
return &usb_dw_ep_list[epnum][epdir];
}
-static int usb_dw_maxpktsize(int epnum, enum usb_dw_epdir epdir)
+static uint32_t usb_dw_maxpktsize(int epnum, enum usb_dw_epdir epdir)
{
return epnum ? DWC_EPCTL(epnum, epdir) & 0x3ff : 64;
}
-static int usb_dw_maxxfersize(int epnum, enum usb_dw_epdir epdir)
+static uint32_t usb_dw_maxxfersize(int epnum, enum usb_dw_epdir epdir)
{
- return epnum ? ALIGN_DOWN_P2(MIN(hw_maxbytes,
- hw_maxpackets*usb_dw_maxpktsize(epnum, epdir)), CACHEALIGN_BITS) : 64;
+ /* EP0 can only transfer one packet at a time. */
+ if(epnum == 0)
+ return 64;
+
+ uint32_t maxpktsize = usb_dw_maxpktsize(epnum, epdir);
+ return CACHEALIGN_DOWN(MIN(hw_maxbytes, hw_maxpackets * maxpktsize));
}
/* Calculate number of packets (if size == 0 an empty packet will be sent) */
-static int usb_dw_calc_packets(uint32_t size, uint32_t maxpktsize)
+static uint32_t usb_dw_calc_packets(uint32_t size, uint32_t maxpktsize)
{
- int packets = (size + maxpktsize - 1) / maxpktsize;
- if (!packets) packets = 1;
- return packets;
+ return MAX(1, (size + maxpktsize - 1) / maxpktsize);
}
static int usb_dw_get_stall(int epnum, enum usb_dw_epdir epdir)
@@ -184,8 +248,8 @@ static unsigned usb_dw_bytes_in_txfifo(int epnum, uint32_t *sentbytes)
uint32_t dieptsiz = DWC_DIEPTSIZ(epnum);
uint32_t packetsleft = (dieptsiz >> 19) & 0x3ff;
if (!packetsleft) return 0;
- int maxpktsize = usb_dw_maxpktsize(epnum, USB_DW_EPDIR_IN);
- int packets = usb_dw_calc_packets(size, maxpktsize);
+ uint32_t maxpktsize = usb_dw_maxpktsize(epnum, USB_DW_EPDIR_IN);
+ uint32_t packets = usb_dw_calc_packets(size, maxpktsize);
uint32_t bytesleft = dieptsiz & 0x7ffff;
uint32_t bytespushed = size - bytesleft;
uint32_t bytespulled = (packets - packetsleft) * maxpktsize;
@@ -200,7 +264,7 @@ static unsigned usb_dw_bytes_in_txfifo(int epnum, uint32_t *sentbytes)
static void usb_dw_handle_rxfifo(void)
{
uint32_t rxsts = DWC_GRXSTSP;
- int pktsts = (rxsts >> 17) & 0xf;
+ uint32_t pktsts = (rxsts >> 17) & 0xf;
switch (pktsts)
{
@@ -208,19 +272,31 @@ static void usb_dw_handle_rxfifo(void)
case PKTSTS_SETUPRX:
{
int ep = rxsts & 0xf;
- int words = (((rxsts >> 4) & 0x7ff) + 3) >> 2;
- struct usb_dw_ep* dw_ep = usb_dw_get_ep(ep, USB_DW_EPDIR_OUT);
- if (dw_ep->busy)
+ uint32_t words = (((rxsts >> 4) & 0x7ff) + 3) >> 2;
+
+ /* Annoyingly, we need to special-case EP0. */
+ if(ep == 0)
{
+ uint32_t* addr = (uint32_t*)ep0_buffer;
while (words--)
- *dw_ep->addr++ = DWC_DFIFO(0);
+ *addr++ = DWC_DFIFO(0);
}
else
{
- /* Discard data */
- while (words--)
- (void) DWC_DFIFO(0);
+ struct usb_dw_ep* dw_ep = usb_dw_get_ep(ep, USB_DW_EPDIR_OUT);
+ if (dw_ep->busy)
+ {
+ while (words--)
+ *dw_ep->addr++ = DWC_DFIFO(0);
+ }
+ else
+ {
+ /* Discard data */
+ while (words--)
+ (void) DWC_DFIFO(0);
+ }
}
+
break;
}
case PKTSTS_OUTDONE:
@@ -292,7 +368,7 @@ static void usb_dw_handle_dtxfifo(int epnum)
{
/* We push whole packets to read consistent info on DIEPTSIZ
(i.e. when FIFO size is not maxpktsize multiplo). */
- int maxpktwords = usb_dw_maxpktsize(epnum, USB_DW_EPDIR_IN) >> 2;
+ uint32_t maxpktwords = usb_dw_maxpktsize(epnum, USB_DW_EPDIR_IN) >> 2;
words = (fifospace / maxpktwords) * maxpktwords;
}
@@ -458,7 +534,7 @@ static void usb_dw_nptx_unqueue(int epnum)
dw_ep->addr -= (bytesinfifo + 3) >> 2;
#else
(void) bytesinfifo;
- DWC_DIEPDMA(ep) = DMA_ADDR2PHYS((uint32_t)(dw_ep->addr) + sentbytes);
+ DWC_DIEPDMA(ep) = USB_DW_PHYSADDR((uint32_t)(dw_ep->addr) + sentbytes);
#endif
DWC_DIEPTSIZ(ep) = PKTCNT(packetsleft) | (dw_ep->size - sentbytes);
@@ -664,57 +740,72 @@ static void usb_dw_reset_endpoints(void)
#endif
}
-static void usb_dw_start_xfer(int epnum,
- enum usb_dw_epdir epdir, const void* buf, int size)
+static void usb_dw_epstart(int epnum, enum usb_dw_epdir epdir,
+ void* buf, uint32_t size)
{
if ((uint32_t)buf & ((epdir == USB_DW_EPDIR_IN) ? 3 : CACHEALIGN_SIZE-1))
logf("%s: %s%d %p unaligned", __func__, dw_dir_str[epdir], epnum, buf);
struct usb_dw_ep* dw_ep = usb_dw_get_ep(epnum, epdir);
+ uint32_t xfersize = MIN(size, usb_dw_maxxfersize(epnum, epdir));
- dw_ep->busy = true;
- dw_ep->status = -1;
+ dw_ep->addr = (uint32_t*)buf;
+ dw_ep->size = xfersize;
dw_ep->sizeleft = size;
- size = MIN(size, usb_dw_maxxfersize(epnum, epdir));
- dw_ep->size = size;
+ dw_ep->status = -1;
+ dw_ep->busy = true;
- int packets = usb_dw_calc_packets(size, usb_dw_maxpktsize(epnum, epdir));
- uint32_t eptsiz = PKTCNT(packets) | size;
- uint32_t nak;
+ if (epnum == 0 && epdir == USB_DW_EPDIR_OUT)
+ {
+ /* FIXME: there's an extremely rare race condition here.
+ *
+ * 1. Host sends a control write.
+ * 2. We process the request.
+ * 3. (time passes)
+ * 4. This function is called via USB_CONTROL_RECEIVE response.
+ * 5. Right before we set CNAK, host sends another control write.
+ *
+ * So we may unintentionally receive data from the second request.
+ * It's possible to detect this when we see a setup packet because
+ * EP0 OUT will be busy. In principle it should even be possible to
+ * handle the 2nd request correctly. Currently we don't attempt to
+ * detect or recover from this error.
+ */
+ DWC_DOEPCTL(0) |= CNAK;
+ return;
+ }
- /* Set up data source */
- dw_ep->addr = (uint32_t*)buf;
-#ifndef USB_DW_ARCH_SLAVE
- DWC_EPDMA(epnum, epdir) = DMA_ADDR2PHYS((uint32_t)buf);
-#endif
+ uint32_t maxpktsize = usb_dw_maxpktsize(epnum, epdir);
+ uint32_t packets = usb_dw_calc_packets(xfersize, maxpktsize);
+ uint32_t eptsiz = PKTCNT(packets) | xfersize;
+ uint32_t nak = CNAK;
if (epdir == USB_DW_EPDIR_IN)
{
#ifndef USB_DW_ARCH_SLAVE
- COMMIT_DCACHE_RANGE(buf, size);
+ COMMIT_DCACHE_RANGE(buf, xfersize);
#endif
#ifdef USB_DW_SHARED_FIFO
eptsiz |= MCCNT((ep_periodic_msk >> epnum) & 1);
#endif
- nak = CNAK;
+
}
else
{
#ifndef USB_DW_ARCH_SLAVE
- DISCARD_DCACHE_RANGE(buf, size);
+ DISCARD_DCACHE_RANGE(buf, xfersize);
#endif
- eptsiz |= STUPCNT(!epnum);
- nak = epnum ? CNAK : SNAK;
}
+#ifndef USB_DW_ARCH_SLAVE
+ DWC_EPDMA(epnum, epdir) = USB_DW_PHYSADDR((uint32_t)buf);
+#endif
DWC_EPTSIZ(epnum, epdir) = eptsiz;
-
- /* Enable the endpoint */
DWC_EPCTL(epnum, epdir) |= EPENA | nak;
#ifdef USB_DW_ARCH_SLAVE
/* Enable interrupts to start pushing data into the FIFO */
- if ((epdir == USB_DW_EPDIR_IN) && size)
+ if ((epdir == USB_DW_EPDIR_IN) && dw_ep->size > 0)
#ifdef USB_DW_SHARED_FIFO
DWC_GINTMSK |= ((ep_periodic_msk & (1 << epnum)) ? PTXFE : NPTXFE);
#else
@@ -723,25 +814,31 @@ static void usb_dw_start_xfer(int epnum,
#endif
}
-static void usb_dw_ep0_wait_setup(void)
-{
- usb_dw_start_xfer(0, USB_DW_EPDIR_OUT, ep0_buffer.raw, 64);
-}
-
-static void usb_dw_handle_setup_received(void)
+static void usb_dw_transfer(int epnum, enum usb_dw_epdir epdir,
+ void* buf, uint32_t size)
{
- static struct usb_ctrlrequest usb_ctrlsetup;
-
- usb_dw_flush_endpoint(0, USB_DW_EPDIR_IN);
+ struct usb_dw_ep* dw_ep = usb_dw_get_ep(epnum, epdir);
- memcpy(&usb_ctrlsetup, ep0_buffer.raw, sizeof(usb_ctrlsetup));
+ if (!dw_ep->active)
+ logf("%s: %s%d inactive", __func__, dw_dir_str[epdir], epnum);
+ if (dw_ep->busy)
+ logf("%s: %s%d busy", __func__, dw_dir_str[epdir], epnum);
- if (((usb_ctrlsetup.bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE)
- && ((usb_ctrlsetup.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
- && (usb_ctrlsetup.bRequest == USB_REQ_SET_ADDRESS))
- usb_dw_set_address(usb_ctrlsetup.wValue);
+ dw_ep->req_addr = buf;
+ dw_ep->req_size = size;
+ usb_dw_epstart(epnum, epdir, buf, size);
+}
- usb_core_control_request(&usb_ctrlsetup);
+static void usb_dw_ep0_recv(void)
+{
+#ifndef USB_DW_ARCH_SLAVE
+#ifdef NO_UNCACHED_ADDR
+ DISCARD_DCACHE_RANGE(&_ep0_buffer[0], 64);
+#endif
+ DWC_DOEPDMA(0) = USB_DW_PHYSADDR((uint32_t)&_ep0_buffer[0]);
+#endif
+ DWC_DOEPTSIZ(0) = STUPCNT(1) | PKTCNT(1) | 64;
+ DWC_DOEPCTL(0) |= EPENA | SNAK;
}
static void usb_dw_abort_endpoint(int epnum, enum usb_dw_epdir epdir)
@@ -755,60 +852,239 @@ static void usb_dw_abort_endpoint(int epnum, enum usb_dw_epdir epdir)
}
}
+static void usb_dw_control_received(struct usb_ctrlrequest* req)
+{
+ logf("%s(%p) state=%s", __func__, req, dw_state_str[ep0.state]);
+ logf(" bRequestType=%02x bRequest=%02x", req->bRequestType, req->bRequest);
+ logf(" wValue=%04x wIndex=%u wLength=%u", req->wValue, req->wIndex, req->wLength);
+
+ switch(ep0.state) {
+ case EP0_REQ:
+ case EP0_REQ_CTRLWRITE:
+ case EP0_REQ_CANCELLED:
+ /* Save the request for later */
+ memcpy(&ep0.pending_req, req, sizeof(*req));
+ ep0.state = EP0_REQ_CANCELLED;
+ break;
+
+ case EP0_DATA_IN:
+ case EP0_STATUS_IN:
+ case EP0_DATA_OUT:
+ case EP0_STATUS_OUT:
+ usb_core_control_complete(-1);
+ /* fallthrough */
+
+ case EP0_SETUP:
+ /* Save the request */
+ memcpy(&ep0.active_req, req, sizeof(*req));
+ req = &ep0.active_req;
+
+ /* Check for a SET ADDRESS request, which we must handle here */
+ if ((req->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE &&
+ (req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
+ (req->bRequest == USB_REQ_SET_ADDRESS))
+ usb_dw_set_address(req->wValue);
+
+ /* Check for control writes */
+ if (req->wLength > 0 && !(req->bRequestType & USB_DIR_IN))
+ ep0.state = EP0_REQ_CTRLWRITE;
+ else
+ ep0.state = EP0_REQ;
+
+ usb_dw_flush_endpoint(0, USB_DW_EPDIR_IN);
+ usb_core_control_request(req, NULL);
+ break;
+
+ default:
+ panicf("%s: bad state=%s", __func__, dw_state_str[ep0.state]);
+ }
+}
+
+/* note: must be called with IRQs disabled */
+static void usb_dw_control_response(enum usb_control_response resp,
+ void* data, int length)
+{
+ struct usb_ctrlrequest* req = &ep0.active_req;
+
+ switch(ep0.state) {
+ case EP0_REQ:
+ case EP0_REQ_CTRLWRITE:
+ switch(resp) {
+ case USB_CONTROL_ACK:
+ if(req->wLength > 0 && (req->bRequestType & USB_DIR_IN))
+ ep0.state = EP0_DATA_IN; /* control read */
+ else
+ ep0.state = EP0_STATUS_IN; /* non-data or write */
+
+ usb_dw_transfer(0, USB_DW_EPDIR_IN, data, length);
+ break;
+
+ case USB_CONTROL_RECEIVE:
+ if(ep0.state != EP0_REQ_CTRLWRITE)
+ panicf("%s: bad response", __func__);
+
+ ep0.state = EP0_DATA_OUT;
+ usb_dw_transfer(0, USB_DW_EPDIR_OUT, data, length);
+ break;
+
+ case USB_CONTROL_STALL:
+ if(ep0.state == EP0_REQ_CTRLWRITE)
+ usb_dw_set_stall(0, USB_DW_EPDIR_OUT, 1);
+ else
+ usb_dw_set_stall(0, USB_DW_EPDIR_IN, 1);
+
+ ep0.state = EP0_SETUP;
+ break;
+ }
+ break;
+
+ case EP0_REQ_CANCELLED:
+ /* Terminate the old request */
+ usb_core_control_complete(-3);
+
+ /* Submit the pending request */
+ ep0.state = EP0_SETUP;
+ usb_dw_control_received(&ep0.pending_req);
+ break;
+
+ default:
+ panicf("%s: bad state=%s", __func__, dw_state_str[ep0.state]);
+ }
+}
+
+static void usb_dw_ep0_xfer_complete(enum usb_dw_epdir epdir,
+ int status, int transferred)
+{
+ struct usb_dw_ep* dw_ep = usb_dw_get_ep(0, epdir);
+
+ switch((ep0.state << 1) | epdir)
+ {
+ case (EP0_DATA_IN << 1) | USB_DW_EPDIR_IN:
+ ep0.state = EP0_STATUS_OUT;
+ usb_dw_transfer(0, USB_DW_EPDIR_OUT, NULL, 0);
+ break;
+
+ case (EP0_DATA_OUT << 1) | USB_DW_EPDIR_OUT:
+ ep0.state = EP0_REQ;
+ usb_core_control_request(&ep0.active_req, dw_ep->req_addr);
+ break;
+
+ case (EP0_STATUS_IN << 1) | USB_DW_EPDIR_IN:
+ case (EP0_STATUS_OUT << 1) | USB_DW_EPDIR_OUT:
+ if(status != 0 || transferred != 0)
+ usb_core_control_complete(-2);
+ else
+ usb_core_control_complete(0);
+
+ ep0.state = EP0_SETUP;
+ break;
+
+ default:
+ panicf("%s: state=%s dir=%s", __func__,
+ dw_state_str[ep0.state], dw_dir_str[epdir]);
+ }
+}
+
static void usb_dw_handle_xfer_complete(int epnum, enum usb_dw_epdir epdir)
{
struct usb_dw_ep* dw_ep = usb_dw_get_ep(epnum, epdir);
+ bool is_ep0out = (epnum == 0 && epdir == USB_DW_EPDIR_OUT);
if (!dw_ep->busy)
+ {
+ if(is_ep0out)
+ usb_dw_ep0_recv();
return;
+ }
- uint32_t bytesleft = DWC_EPTSIZ(epnum, epdir) & 0x7ffff;
+ uint32_t bytes_left = DWC_EPTSIZ(epnum, epdir) & 0x7ffff;
+ uint32_t transferred = (is_ep0out ? 64 : dw_ep->size) - bytes_left;
- if (!epnum && (epdir == USB_DW_EPDIR_OUT)) /* OUT0 */
+ if(transferred > dw_ep->sizeleft)
{
- int recvbytes = 64 - bytesleft;
- dw_ep->sizeleft = dw_ep->req_size - recvbytes;
- if (dw_ep->req_addr)
- memcpy(dw_ep->req_addr, ep0_buffer.raw, dw_ep->req_size);
+ /* Host sent more data than expected.
+ * Shouldn't happen for IN endpoints. */
+ dw_ep->status = -2;
+ goto complete;
}
- else
+
+ if(is_ep0out)
+ {
+#if defined(NO_UNCACHED_ADDR) && defined(POST_DMA_FLUSH)
+ DISCARD_DCACHE_RANGE(ep0_buffer, 64);
+#endif
+ memcpy(dw_ep->addr, ep0_buffer, transferred);
+ usb_dw_ep0_recv();
+ }
+
+ dw_ep->sizeleft -= transferred;
+
+ /* Start a new transfer if there is still more to go */
+ if(bytes_left == 0 && dw_ep->sizeleft > 0)
{
- dw_ep->sizeleft -= (dw_ep->size - bytesleft);
- if (!bytesleft && dw_ep->sizeleft)
- {
#ifndef USB_DW_ARCH_SLAVE
- dw_ep->addr += (dw_ep->size >> 2); /* words */
+ dw_ep->addr += (dw_ep->size >> 2); /* offset in words */
#endif
- usb_dw_start_xfer(epnum, epdir, dw_ep->addr, dw_ep->sizeleft);
- return;
- }
+ usb_dw_epstart(epnum, epdir, dw_ep->addr, dw_ep->sizeleft);
+ return;
+ }
- if (epdir == USB_DW_EPDIR_IN)
- {
- /* SNAK the disabled EP, otherwise IN tokens for this
- EP could raise unwanted EPMIS interrupts. Useful for
- usbserial when there is no data to send. */
- DWC_DIEPCTL(epnum) |= SNAK;
+ if(epdir == USB_DW_EPDIR_IN)
+ {
+ /* SNAK the disabled EP, otherwise IN tokens for this
+ EP could raise unwanted EPMIS interrupts. Useful for
+ usbserial when there is no data to send. */
+ DWC_DIEPCTL(epnum) |= SNAK;
#ifdef USB_DW_SHARED_FIFO
- /* See usb-s5l8701.c */
- if (usb_dw_config.use_ptxfifo_as_plain_buffer)
- {
- int dtxfnum = GET_DTXFNUM(epnum);
- if (dtxfnum)
- usb_dw_flush_fifo(TXFFLSH, dtxfnum);
- }
-#endif
+ /* See usb-s5l8701.c */
+ if (usb_dw_config.use_ptxfifo_as_plain_buffer)
+ {
+ int dtxfnum = GET_DTXFNUM(epnum);
+ if (dtxfnum)
+ usb_dw_flush_fifo(TXFFLSH, dtxfnum);
}
+#endif
+ }
+ else
+ {
+#if !defined(USB_DW_ARCH_SLAVE) && defined(POST_DMA_FLUSH)
+ /* On EP0 OUT we do not DMA into the request buffer,
+ * so do not discard the cache in this case. */
+ if(!is_ep0out)
+ DISCARD_DCACHE_RANGE(dw_ep->req_addr, dw_ep->req_size);
+#endif
}
- dw_ep->busy = false;
dw_ep->status = 0;
+
+ complete:
+ dw_ep->busy = false;
semaphore_release(&dw_ep->complete);
- int transfered = dw_ep->req_size - dw_ep->sizeleft;
- usb_core_transfer_complete(epnum, (epdir == USB_DW_EPDIR_OUT) ?
- USB_DIR_OUT : USB_DIR_IN, dw_ep->status, transfered);
+ int total_bytes = dw_ep->req_size - dw_ep->sizeleft;
+ if (epnum == 0)
+ {
+ usb_dw_ep0_xfer_complete(epdir, dw_ep->status, total_bytes);
+ }
+ else
+ {
+ usb_core_transfer_complete(epnum, (epdir == USB_DW_EPDIR_OUT) ?
+ USB_DIR_OUT : USB_DIR_IN, dw_ep->status, total_bytes);
+ }
+}
+
+static void usb_dw_handle_setup_received(void)
+{
+#if defined(NO_UNCACHED_ADDR) && defined(POST_DMA_FLUSH)
+ DISCARD_DCACHE_RANGE(ep0_buffer, 64);
+#endif
+ struct usb_ctrlrequest req;
+ memcpy(&req, ep0_buffer, sizeof(struct usb_ctrlrequest));
+
+ usb_dw_ep0_recv();
+
+ usb_dw_control_received(&req);
}
#ifdef USB_DW_SHARED_FIFO
@@ -822,7 +1098,7 @@ static int usb_dw_get_epmis(void)
/* Get the EP on the top of the queue, 0 < idx < number of available
IN endpoints */
- int idx = (gnptxsts >> 27) & 0xf;
+ uint32_t idx = (gnptxsts >> 27) & 0xf;
for (epmis = 0; epmis < USB_NUM_ENDPOINTS; epmis++)
if ((usb_endpoints & (1 << epmis)) && !idx--)
break;
@@ -960,6 +1236,7 @@ static void usb_dw_irq(void)
if (daint & (1 << (ep + 16)))
{
uint32_t epints = DWC_DOEPINT(ep);
+ DWC_DOEPINT(ep) = epints;
if (!ep)
{
@@ -967,17 +1244,31 @@ static void usb_dw_irq(void)
{
usb_dw_handle_setup_received();
}
- else if (epints & XFRC)
+
+ if (epints & XFRC)
{
- usb_dw_handle_xfer_complete(0, USB_DW_EPDIR_OUT);
+ if(epints & STATUSRECVD)
+ {
+ /* At the end of a control write's data phase, the
+ * controller writes a spurious OUTDONE token to the
+ * FIFO and raises StatusRecvd | XferCompl.
+ *
+ * We do not need or want this -- we've already handled
+ * the data phase by this point -- but EP0 is stoppped
+ * as a side effect of XferCompl, so we need to restart
+ * it to keep receiving packets. */
+ usb_dw_ep0_recv();
+ }
+ else if(!(epints & SETUPRECVD))
+ {
+ /* Only call this for normal data packets. Setup
+ * packets use the STUP interrupt handler instead. */
+ usb_dw_handle_xfer_complete(0, USB_DW_EPDIR_OUT);
+ }
}
- usb_dw_ep0_wait_setup();
- /* Clear interrupt after the current EP0 packet is handled */
- DWC_DOEPINT(0) = epints;
}
else
{
- DWC_DOEPINT(ep) = epints;
if (epints & XFRC)
{
usb_dw_handle_xfer_complete(ep, USB_DW_EPDIR_OUT);
@@ -991,14 +1282,14 @@ static void usb_dw_irq(void)
DWC_GINTSTS = USBRST;
usb_dw_set_address(0);
usb_dw_reset_endpoints();
- usb_dw_ep0_wait_setup();
usb_core_bus_reset();
}
if (DWC_GINTSTS & ENUMDNE)
{
DWC_GINTSTS = ENUMDNE;
- /* Nothing to do? */
+ ep0.state = EP0_SETUP;
+ usb_dw_ep0_recv();
}
}
@@ -1083,9 +1374,17 @@ static void usb_dw_init(void)
if (!initialized)
{
+#if !defined(USB_DW_ARCH_SLAVE) && !defined(NO_UNCACHED_ADDR)
+ ep0_buffer = USB_DW_UNCACHEDADDR(&_ep0_buffer[0]);
+#else
+ /* DMA is not used so we can operate on cached addresses */
+ ep0_buffer = &_ep0_buffer[0];
+#endif
+
for (int ep = 0; ep < USB_NUM_ENDPOINTS; ep++)
for (int dir = 0; dir < USB_DW_NUM_DIRS; dir++)
semaphore_init(&usb_dw_get_ep(ep, dir)->complete, 1, 0);
+
initialized = true;
}
@@ -1335,37 +1634,16 @@ void usb_drv_release_endpoint(int endpoint)
int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length)
{
- int epnum = EP_NUM(endpoint);
- struct usb_dw_ep* dw_ep = usb_dw_get_ep(epnum, USB_DW_EPDIR_OUT);
-
usb_dw_target_disable_irq();
- if (dw_ep->active)
- {
- dw_ep->req_addr = ptr;
- dw_ep->req_size = length;
- /* OUT0 is always launched waiting for SETUP packet,
- it is CNAKed to receive app data */
- if (epnum == 0)
- DWC_DOEPCTL(0) |= CNAK;
- else
- usb_dw_start_xfer(epnum, USB_DW_EPDIR_OUT, ptr, length);
- }
+ usb_dw_transfer(EP_NUM(endpoint), USB_DW_EPDIR_OUT, ptr, length);
usb_dw_target_enable_irq();
return 0;
}
int usb_drv_send_nonblocking(int endpoint, void *ptr, int length)
{
- int epnum = EP_NUM(endpoint);
- struct usb_dw_ep* dw_ep = usb_dw_get_ep(epnum, USB_DW_EPDIR_IN);
-
usb_dw_target_disable_irq();
- if (dw_ep->active)
- {
- dw_ep->req_addr = ptr;
- dw_ep->req_size = length;
- usb_dw_start_xfer(epnum, USB_DW_EPDIR_IN, ptr, length);
- }
+ usb_dw_transfer(EP_NUM(endpoint), USB_DW_EPDIR_IN, ptr, length);
usb_dw_target_enable_irq();
return 0;
}
@@ -1388,3 +1666,11 @@ int usb_drv_send(int endpoint, void *ptr, int length)
return dw_ep->status;
}
+
+void usb_drv_control_response(enum usb_control_response resp,
+ void* data, int length)
+{
+ usb_dw_target_disable_irq();
+ usb_dw_control_response(resp, data, length);
+ usb_dw_target_enable_irq();
+}
diff --git a/firmware/export/button.h b/firmware/export/button.h
index 1745947230..3abad2d4f0 100644
--- a/firmware/export/button.h
+++ b/firmware/export/button.h
@@ -107,7 +107,7 @@ int button_apply_acceleration(const unsigned int data);
BUTTON_MULTIMEDIA_FFWD)
#ifdef HAVE_TOUCHSCREEN
-int touchscreen_last_touch(void);
+long touchscreen_last_touch(void);
#if (!defined(BUTTON_TOPLEFT) || !defined(BUTTON_TOPMIDDLE) \
|| !defined(BUTTON_TOPRIGHT) || !defined(BUTTON_MIDLEFT) \
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 5700fad37f..bb3f405d6a 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -718,6 +718,7 @@ Lyre prototype 1 */
#define RDS_CFG_ISR 0x1 /* uses ISR to process packets */
#define RDS_CFG_PROCESS 0x2 /* uses raw packet processing */
#define RDS_CFG_PUSH 0x4 /* pushes processed information */
+#define RDS_CFG_POLL 0x8 /* tuner driver provides a polling function */
#ifndef CONFIG_RDS
#define CONFIG_RDS RDS_CFG_PROCESS /* thread processing+raw processing */
#endif /* CONFIG_RDS */
@@ -1172,20 +1173,26 @@ Lyre prototype 1 */
/* Define the implemented USB transport classes */
#if CONFIG_USBOTG == USBOTG_ISP1583
#define USB_HAS_BULK
-#elif (CONFIG_USBOTG == USBOTG_ARC) || \
+#define USB_LEGACY_CONTROL_API
+#elif (CONFIG_USBOTG == USBOTG_DESIGNWARE)
+#define USB_HAS_BULK
+#define USB_HAS_INTERRUPT
+#elif (CONFIG_USBOTG == USBOTG_ARC) || \
(CONFIG_USBOTG == USBOTG_JZ4740) || \
(CONFIG_USBOTG == USBOTG_JZ4760) || \
(CONFIG_USBOTG == USBOTG_M66591) || \
- (CONFIG_USBOTG == USBOTG_DESIGNWARE) || \
(CONFIG_USBOTG == USBOTG_AS3525) || \
(CONFIG_USBOTG == USBOTG_RK27XX) || \
(CONFIG_USBOTG == USBOTG_TNETV105)
#define USB_HAS_BULK
#define USB_HAS_INTERRUPT
+#define USB_LEGACY_CONTROL_API
#elif defined(CPU_TCC780X)
#define USB_HAS_BULK
+#define USB_LEGACY_CONTROL_API
#elif CONFIG_USBOTG == USBOTG_S3C6400X
#define USB_HAS_BULK
+#define USB_LEGACY_CONTROL_API
//#define USB_HAS_INTERRUPT -- seems to be broken
#endif /* CONFIG_USBOTG */
diff --git a/firmware/export/config/erosqnative.h b/firmware/export/config/erosqnative.h
index e0a668d82d..f68830e211 100644
--- a/firmware/export/config/erosqnative.h
+++ b/firmware/export/config/erosqnative.h
@@ -106,7 +106,6 @@
/* USB support */
#ifndef SIMULATOR
#define CONFIG_USBOTG USBOTG_DESIGNWARE
-#define USB_DW_ARCH_SLAVE
#define USB_DW_TURNAROUND 5
#define HAVE_USBSTACK
#define USB_VENDOR_ID 0xc502
@@ -114,8 +113,13 @@
#define USB_DEVBSS_ATTR __attribute__((aligned(32)))
#define HAVE_USB_POWER
#define HAVE_USB_CHARGING_ENABLE
+#define HAVE_USB_CHARGING_IN_THREAD
#define TARGET_USB_CHARGING_DEFAULT USB_CHARGING_FORCE
#define HAVE_BOOTLOADER_USB_MODE
+/* This appears to improve transfer performance (the default is 64 KiB).
+ * Going any higher doesn't help but we're still slower than the OF. */
+#define USB_READ_BUFFER_SIZE (128 * 1024)
+#define USB_WRITE_BUFFER_SIZE (128 * 1024)
#endif
/* Rockbox capabilities */
diff --git a/firmware/export/config/fiiom3k.h b/firmware/export/config/fiiom3k.h
index dc56f0a5cc..61b6123a67 100644
--- a/firmware/export/config/fiiom3k.h
+++ b/firmware/export/config/fiiom3k.h
@@ -24,6 +24,7 @@
#define HAVE_I2C_ASYNC
#define HAVE_FT6x06
#define FT6x06_SWAP_AXES
+#define FT6x06_NUM_POINTS 1
/* Buffer for plugins and codecs. */
#define PLUGIN_BUFFER_SIZE 0x200000 /* 2 MiB */
@@ -106,7 +107,6 @@
/* USB support */
#ifndef SIMULATOR
#define CONFIG_USBOTG USBOTG_DESIGNWARE
-#define USB_DW_ARCH_SLAVE
#define USB_DW_TURNAROUND 5
#define HAVE_USBSTACK
#define USB_VENDOR_ID 0x2972
@@ -114,8 +114,13 @@
#define USB_DEVBSS_ATTR __attribute__((aligned(32)))
#define HAVE_USB_POWER
#define HAVE_USB_CHARGING_ENABLE
+#define HAVE_USB_CHARGING_IN_THREAD
#define TARGET_USB_CHARGING_DEFAULT USB_CHARGING_FORCE
#define HAVE_BOOTLOADER_USB_MODE
+/* This appears to improve transfer performance (the default is 64 KiB).
+ * Going any higher doesn't help but we're still slower than the OF. */
+#define USB_READ_BUFFER_SIZE (128 * 1024)
+#define USB_WRITE_BUFFER_SIZE (128 * 1024)
#endif
/* Rockbox capabilities */
diff --git a/firmware/export/config/samsungypr0.h b/firmware/export/config/samsungypr0.h
index e29ea6a974..520eb08d01 100644
--- a/firmware/export/config/samsungypr0.h
+++ b/firmware/export/config/samsungypr0.h
@@ -105,6 +105,8 @@
#define CONFIG_TUNER SI4700
#define HAVE_TUNER_PWR_CTRL
#define HAVE_RDS_CAP
+#define CONFIG_RDS (RDS_CFG_POLL | RDS_CFG_PROCESS)
+#define CONFIG_RDS_POLL_TICKS 4
/* Define this for FM radio input available */
#define HAVE_FMRADIO_IN
diff --git a/firmware/export/config/samsungypr1.h b/firmware/export/config/samsungypr1.h
index d091e3ed1e..50abfa323e 100644
--- a/firmware/export/config/samsungypr1.h
+++ b/firmware/export/config/samsungypr1.h
@@ -150,6 +150,8 @@
#define CONFIG_TUNER SI4700
#define HAVE_TUNER_PWR_CTRL
#define HAVE_RDS_CAP
+#define CONFIG_RDS (RDS_CFG_POLL | RDS_CFG_PROCESS)
+#define CONFIG_RDS_POLL_TICKS 4
/* Define this for FM radio input available */
#define HAVE_FMRADIO_IN
diff --git a/firmware/export/config/sansaclipplus.h b/firmware/export/config/sansaclipplus.h
index 3108bace61..e0df0c28c3 100644
--- a/firmware/export/config/sansaclipplus.h
+++ b/firmware/export/config/sansaclipplus.h
@@ -17,6 +17,9 @@
#ifndef BOOTLOADER
#define HAVE_HOTSWAP
+#define HAVE_RDS_CAP
+#define CONFIG_RDS (RDS_CFG_POLL | RDS_CFG_PROCESS)
+#define CONFIG_RDS_POLL_TICKS 4
#endif
#define HW_SAMPR_CAPS SAMPR_CAP_ALL_96
@@ -146,6 +149,9 @@
/* define this if the flash memory uses the SecureDigital Memory Card protocol */
#define CONFIG_STORAGE STORAGE_SD
+/* Define this if target has an additional number of threads specific to it */
+#define TARGET_EXTRA_THREADS 1 /* RDS thread */
+
#define BATTERY_CAPACITY_DEFAULT 290 /* default battery capacity */
#define BATTERY_CAPACITY_MIN 290 /* min. capacity selectable */
#define BATTERY_CAPACITY_MAX 290 /* max. capacity selectable */
diff --git a/firmware/export/config/shanlingq1.h b/firmware/export/config/shanlingq1.h
index 88175b9160..16ce888958 100644
--- a/firmware/export/config/shanlingq1.h
+++ b/firmware/export/config/shanlingq1.h
@@ -59,6 +59,7 @@
#define HAVE_TOUCHSCREEN
#define HAVE_BUTTON_DATA
#define HAVE_FT6x06
+#define FT6x06_NUM_POINTS 5
#define HAVE_HEADPHONE_DETECTION
/* Storage defines */
@@ -96,7 +97,6 @@
/* USB support */
#ifndef SIMULATOR
#define CONFIG_USBOTG USBOTG_DESIGNWARE
-#define USB_DW_ARCH_SLAVE
#define USB_DW_TURNAROUND 5
#define HAVE_USBSTACK
#define USB_VENDOR_ID 0x0525 /* Same as the xDuuo X3, for some reason. */
@@ -104,7 +104,13 @@
#define USB_DEVBSS_ATTR __attribute__((aligned(32)))
#define HAVE_USB_POWER
#define HAVE_USB_CHARGING_ENABLE
+#define HAVE_USB_CHARGING_IN_THREAD
+#define TARGET_USB_CHARGING_DEFAULT USB_CHARGING_FORCE
#define HAVE_BOOTLOADER_USB_MODE
+/* This appears to improve transfer performance (the default is 64 KiB).
+ * Going any higher doesn't help but we're still slower than the OF. */
+#define USB_READ_BUFFER_SIZE (128 * 1024)
+#define USB_WRITE_BUFFER_SIZE (128 * 1024)
#endif
/* Rockbox capabilities */
diff --git a/firmware/export/events.h b/firmware/export/events.h
index 4591058d4f..8bdf1b55e2 100644
--- a/firmware/export/events.h
+++ b/firmware/export/events.h
@@ -52,7 +52,7 @@
#define EVENT_CLASS_RECORDING 0x1000
#define EVENT_CLASS_LCD 0x2000
#define EVENT_CLASS_VOICE 0x4000
-
+#define EVENT_CLASS_SYSTEM 0x8000 /*LAST ONE */
/**
* Subscribe to an event with a simple callback. The callback will be called
* synchronously everytime the event fires, passing the event id and data to
@@ -99,4 +99,14 @@ void remove_event_ex(unsigned short id, void (*handler)(unsigned short id, void
*/
void send_event(unsigned short id, void *data);
+/** System events **/
+enum {
+ /* USB_INSERTED
+ data = &usbmode */
+ SYS_EVENT_USB_INSERTED = (EVENT_CLASS_SYSTEM|1),
+ /* USB_EXTRACTED
+ data = NULL */
+ SYS_EVENT_USB_EXTRACTED,
+};
+
#endif
diff --git a/firmware/export/ft6x06.h b/firmware/export/ft6x06.h
index de1fdd0979..6596f89272 100644
--- a/firmware/export/ft6x06.h
+++ b/firmware/export/ft6x06.h
@@ -25,23 +25,32 @@
#include "config.h"
#include <stdbool.h>
-typedef void(*ft6x06_event_cb)(int, int, int);
+enum ft6x06_event {
+ FT6x06_EVT_NONE = -1,
+ FT6x06_EVT_PRESS = 0,
+ FT6x06_EVT_RELEASE = 1,
+ FT6x06_EVT_CONTACT = 2,
+};
-struct ft6x06_state {
+struct ft6x06_point {
int event;
+ int touch_id;
int pos_x;
int pos_y;
+ int weight;
+ int area;
};
-enum ft6x06_event {
- FT6x06_EVT_NONE = -1,
- FT6x06_EVT_PRESS = 0,
- FT6x06_EVT_RELEASE = 1,
- FT6x06_EVT_CONTACT = 2,
+struct ft6x06_state {
+ int gesture;
+ int nr_points;
+ struct ft6x06_point points[FT6x06_NUM_POINTS];
};
extern struct ft6x06_state ft6x06_state;
+typedef void(*ft6x06_event_cb)(struct ft6x06_state* state);
+
void ft6x06_init(void);
void ft6x06_set_event_cb(ft6x06_event_cb fn);
void ft6x06_enable(bool en);
diff --git a/firmware/export/jz4760b.h b/firmware/export/jz4760b.h
index 589f67800a..af1f36270b 100644
--- a/firmware/export/jz4760b.h
+++ b/firmware/export/jz4760b.h
@@ -7017,6 +7017,11 @@ do { \
#define USB_INTR_SUSPEND 0x01
#define USB_INTR_RESUME 0x02
#define USB_INTR_RESET 0x04
+#define USB_INTR_SOF 0x08
+#define USB_INTR_CONNECT 0x10
+#define USB_INTR_DISCONNECT 0x20
+#define USB_INTR_SESS_REQ 0x40
+#define USB_INTR_VBUS_ERR 0x80
#define USB_INTR_EP(n) (1 << (n))
diff --git a/firmware/export/si4700.h b/firmware/export/si4700.h
index bd75bf0817..033b435f2a 100644
--- a/firmware/export/si4700.h
+++ b/firmware/export/si4700.h
@@ -55,7 +55,13 @@ void si4700_rds_read_raw_async(unsigned char *buf, int count); /* implemented by
void si4700_rds_interrupt(void);
#endif /* (CONFIG_RDS & RDS_CFG_ISR) */
-/* Read raw RDS info for processing */
+/* Read raw RDS info for processing.
+ * - If RDS_CFG_ISR is set, the tuner driver will call si4700_rds_read_raw_async() which should
+ * perform an asynchronous read and call this function when the data has been read.
+ * - If RDS_CFG_POLL is set, this function will read status and RDS data and process it if a new
+ * packet is available.
+ * - Otherwise this function will read a RDS packet and process it under the assumption that it is
+ * new. */
void si4700_rds_process(void);
#endif /* HAVE_RDS_CAP */
diff --git a/firmware/export/usb-designware.h b/firmware/export/usb-designware.h
index 428733b4f5..9e5496f0db 100644
--- a/firmware/export/usb-designware.h
+++ b/firmware/export/usb-designware.h
@@ -216,8 +216,11 @@
#define DWC_DIEPINT(x) (*((REG32_PTR_T)(OTGBASE + 0x908 + 0x20*(x))))
#define DWC_DOEPINT(x) (*((REG32_PTR_T)(OTGBASE + 0xb08 + 0x20*(x))))
+ #define SETUPRECVD (1<<15) /* control OUT */
#define TXFE (1<<7) /* IN */
+ #define BACK2BACKSETUP (1<<6) /* control OUT */
#define INEPNE (1<<6) /* IN */
+ #define STATUSRECVD (1<<5) /* control OUT */
#define ITEPMIS (1<<5) /* IN */
#define ITTXFE (1<<4) /* IN */
#define OTEPDIS (1<<4) /* OUT */
diff --git a/firmware/export/usb.h b/firmware/export/usb.h
index 2bcd95ef81..fe9f3bcfa1 100644
--- a/firmware/export/usb.h
+++ b/firmware/export/usb.h
@@ -178,7 +178,7 @@ struct usb_transfer_completion_event_data
int dir;
int status;
int length;
- void* data;
+ void* data[2];
};
#endif /* HAVE_USBSTACK */
diff --git a/firmware/export/usb_core.h b/firmware/export/usb_core.h
index 75fafc06a8..fe1f7459cf 100644
--- a/firmware/export/usb_core.h
+++ b/firmware/export/usb_core.h
@@ -39,11 +39,21 @@
extern int usb_max_pkt_size;
+enum {
+ USB_STRING_INDEX_LANGUAGE,
+ USB_STRING_INDEX_MANUFACTURER,
+ USB_STRING_INDEX_PRODUCT,
+ USB_STRING_INDEX_SERIAL,
+ USB_STRING_INDEX_MAX,
+};
+
struct usb_class_driver;
void usb_core_init(void);
void usb_core_exit(void);
-void usb_core_control_request(struct usb_ctrlrequest* req);
+void usb_core_control_request(struct usb_ctrlrequest* req, void* data);
+void usb_core_control_complete(int status);
+void usb_core_legacy_control_request(struct usb_ctrlrequest* req);
void usb_core_transfer_complete(int endpoint,int dir,int status,int length);
void usb_core_bus_reset(void);
bool usb_core_any_exclusive_storage(void);
diff --git a/firmware/export/usb_drv.h b/firmware/export/usb_drv.h
index 01535c2786..3ef4db3c9c 100644
--- a/firmware/export/usb_drv.h
+++ b/firmware/export/usb_drv.h
@@ -56,6 +56,12 @@
* -> usb_drv_int_enable(false) [ditto]
* -> soc specific controller/clock deinit */
+enum usb_control_response {
+ USB_CONTROL_ACK,
+ USB_CONTROL_STALL,
+ USB_CONTROL_RECEIVE,
+};
+
/* one-time initialisation of the USB driver */
void usb_drv_startup(void);
void usb_drv_int_enable(bool enable); /* Target implemented */
@@ -69,6 +75,8 @@ bool usb_drv_stalled(int endpoint,bool in);
int usb_drv_send(int endpoint, void* ptr, int length);
int usb_drv_send_nonblocking(int endpoint, void* ptr, int length);
int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length);
+void usb_drv_control_response(enum usb_control_response resp,
+ void* data, int length);
void usb_drv_set_address(int address);
void usb_drv_reset_endpoint(int endpoint, bool send);
bool usb_drv_powered(void);
diff --git a/firmware/kernel/thread.c b/firmware/kernel/thread.c
index 307be7116a..a422624df7 100644
--- a/firmware/kernel/thread.c
+++ b/firmware/kernel/thread.c
@@ -1501,7 +1501,14 @@ static inline void boost_thread(struct thread_entry *thread, bool boost)
if ((thread->cpu_boost != 0) != boost)
{
thread->cpu_boost = boost;
+#ifdef CPU_BOOST_LOGGING
+ const char fmt[] = __FILE__" thread[%s]";
+ char pathbuf[sizeof(fmt) + 32]; /* thread name 32 */
+ snprintf(pathbuf, sizeof(pathbuf), fmt, thread->name);
+ cpu_boost_(boost, pathbuf, __LINE__);
+#else
cpu_boost(boost);
+#endif
}
}
diff --git a/firmware/powermgmt.c b/firmware/powermgmt.c
index aaec959f50..519823611f 100644
--- a/firmware/powermgmt.c
+++ b/firmware/powermgmt.c
@@ -693,10 +693,18 @@ static void power_thread(void)
/* Delay reading the first battery level */
#ifdef MROBE_100
while (_battery_voltage() > 4200) /* gives false readings initially */
+ {
#elif defined(DX50) || defined(DX90)
while (_battery_voltage() < 1) /* can give false readings initially */
-#endif
{
+#elif defined(EROS_QN) || defined(FIIO_M3K) || defined(SHANLING_Q1)
+
+ /* wait until the first battery read is ready */
+ while (_battery_voltage() <= 0)
+ {
+#else
+ {
+#endif
sleep(HZ/100);
}
diff --git a/firmware/system.c b/firmware/system.c
index 537e901b05..e2fdff0e59 100644
--- a/firmware/system.c
+++ b/firmware/system.c
@@ -89,6 +89,7 @@ char * cpu_boost_log_getlog_next(void)
void cpu_boost_(bool on_off, char* location, int line)
{
+ int item = cpu_boost_calls_count;
if (!cpu_boost_lock())
return;
@@ -98,12 +99,13 @@ void cpu_boost_(bool on_off, char* location, int line)
cpu_boost_calls_count--;
if (cpu_boost_calls_count < 0)
cpu_boost_calls_count = 0;
+ item += cpu_boost_first;
}
if (cpu_boost_calls_count < MAX_BOOST_LOG)
{
int message = (cpu_boost_first+cpu_boost_calls_count)%MAX_BOOST_LOG;
- snprintf(cpu_boost_calls[message], MAX_PATH,
- "%c %s:%d",on_off?'B':'U',location,line);
+ snprintf(cpu_boost_calls[message], MAX_PATH,"%d.) %c %d %s:%d",
+ item,on_off?'B':'U',boost_counter,location,line);
cpu_boost_calls_count++;
}
#else
diff --git a/firmware/target/arm/as3525/debug-as3525.c b/firmware/target/arm/as3525/debug-as3525.c
index 57161541fd..4848f0e5c5 100644
--- a/firmware/target/arm/as3525/debug-as3525.c
+++ b/firmware/target/arm/as3525/debug-as3525.c
@@ -366,7 +366,7 @@ bool dbg_hw_info(void)
calc_freq(CLK_IDE)/D_MHZ);
lcd_putsf(x, line++, "%s:%3dMHz %3dMHz", "DBOP", AS3525_DBOP_FREQ/D_MHZ,
calc_freq(CLK_DBOP)/D_MHZ);
- lcd_putsf(x, line++, "%s:%3dMHz %3dMHz", "I2C ", AS3525_I2C_FREQ/D_KHZ,
+ lcd_putsf(x, line++, "%s:%3dkHz %3dkHz", "I2C ", AS3525_I2C_FREQ/D_KHZ,
calc_freq(CLK_I2C)/D_KHZ);
lcd_putsf(x, line++, "I2SI: %s %3dMHz", (CGU_AUDIO & (1<<23)) ?
"on " : "off" , calc_freq(CLK_I2SI)/D_MHZ);
@@ -395,7 +395,7 @@ bool dbg_hw_info(void)
lcd_putsf(x, line++, "%s:%3dMHz %3dMHz", "SSP ", AS3525_SSP_FREQ/D_MHZ,
calc_freq(CLK_SSP)/D_MHZ);
else
- lcd_putsf(x, line++, "SSP :%3dMHz %3dKHz", AS3525_SSP_FREQ/D_MHZ,
+ lcd_putsf(x, line++, "SSP :%3dMHz %3dkHz", AS3525_SSP_FREQ/D_MHZ,
calc_freq(CLK_SSP)/D_KHZ);
#endif /* CONFIG_CPU == AS3525 */
lcd_putsf(x, line++, "USB : %3dMHz", calc_freq(CLK_USB)/D_MHZ);
diff --git a/firmware/target/arm/as3525/fmradio-i2c-as3525.c b/firmware/target/arm/as3525/fmradio-i2c-as3525.c
index 5b629f5ad4..7f6cb8366b 100644
--- a/firmware/target/arm/as3525/fmradio-i2c-as3525.c
+++ b/firmware/target/arm/as3525/fmradio-i2c-as3525.c
@@ -185,6 +185,11 @@ int fmradio_i2c_read(unsigned char address, unsigned char* buf, int count)
}
#ifdef HAVE_RDS_CAP
+/* On the Sansa Clip Zip, the tuner interrupt line is routed to the SoC so we
+ * can use to detect when a RDS packet is ready. On the Clip+, we have to
+ * regularly poll. */
+
+#if !(CONFIG_RDS & RDS_CFG_POLL)
/* Low-level RDS Support */
static struct semaphore rds_sema;
static uint32_t rds_stack[DEFAULT_STACK_SIZE/sizeof(uint32_t)];
@@ -231,4 +236,5 @@ void si4700_rds_init(void)
create_thread(rds_thread, rds_stack, sizeof(rds_stack), 0, "rds"
IF_PRIO(, PRIORITY_REALTIME) IF_COP(, CPU));
}
+#endif /* !(CONFIG_RDS & RDS_CFG_POLL) */
#endif /* HAVE_RDS_CAP */
diff --git a/firmware/target/arm/as3525/system-as3525.c b/firmware/target/arm/as3525/system-as3525.c
index 83ccb55f79..0450c8b410 100644
--- a/firmware/target/arm/as3525/system-as3525.c
+++ b/firmware/target/arm/as3525/system-as3525.c
@@ -180,7 +180,7 @@ void INT_GPIOA(void)
void button_gpioa_isr(void);
button_gpioa_isr();
#endif
-#ifdef HAVE_RDS_CAP
+#if defined(HAVE_RDS_CAP) && !(CONFIG_RDS & RDS_CFG_POLL)
void tuner_isr(void);
tuner_isr();
#endif
diff --git a/firmware/target/arm/as3525/usb-as3525.c b/firmware/target/arm/as3525/usb-as3525.c
index d798d4da83..3f636256a8 100644
--- a/firmware/target/arm/as3525/usb-as3525.c
+++ b/firmware/target/arm/as3525/usb-as3525.c
@@ -53,12 +53,21 @@ static int usb_status = USB_EXTRACTED;
void usb_enable(bool on)
{
#if defined(HAVE_USBSTACK)
+ static int boosted = 0;
if (on){
- cpu_boost(1);
+ if (boosted == 0)
+ {
+ cpu_boost(1);
+ boosted = 1;
+ }
usb_core_init();
} else {
usb_core_exit();
- cpu_boost(0);
+ if(boosted == 1)
+ {
+ cpu_boost(0);
+ boosted = 0;
+ }
}
#else
(void)on;
diff --git a/firmware/target/arm/as3525/usb-drv-as3525.c b/firmware/target/arm/as3525/usb-drv-as3525.c
index 8369edc400..d0875ed48c 100644
--- a/firmware/target/arm/as3525/usb-drv-as3525.c
+++ b/firmware/target/arm/as3525/usb-drv-as3525.c
@@ -655,7 +655,7 @@ static void handle_out_ep(int ep)
req->wIndex,
req->wLength);
- usb_core_control_request(&req_copy);
+ usb_core_legacy_control_request(&req_copy);
setup_desc_init(setup_desc);
ep_sts &= ~USB_EP_STAT_SETUP_RCVD;
@@ -760,7 +760,7 @@ void INT_USB_FUNC(void)
got_set_configuration = 1;
set_config.wValue = USB_DEV_STS & USB_DEV_STS_MASK_CFG;
- usb_core_control_request(&set_config);
+ usb_core_legacy_control_request(&set_config);
intr &= ~USB_DEV_INTR_SET_CONFIG;
}
if (intr & USB_DEV_INTR_EARLY_SUSPEND) {/* idle >3ms detected */
diff --git a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c
index aac271c47a..77860b5494 100644
--- a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c
+++ b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c
@@ -117,7 +117,7 @@ static void setup_received(void)
setup_data[1] = SETUP2;
/* pass setup data to the upper layer */
- usb_core_control_request((struct usb_ctrlrequest*)setup_data);
+ usb_core_legacy_control_request((struct usb_ctrlrequest*)setup_data);
}
static int max_pkt_size(struct endpoint_t *endp)
diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c
index f1acc9c964..e5a9000f40 100644
--- a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c
+++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c
@@ -1181,7 +1181,7 @@ void VLYNQ(void)
}
/* Process control packet */
- usb_core_control_request(&setup);
+ usb_core_legacy_control_request(&setup);
}
if (sysIntrStatus.f.ep0_in_ack)
diff --git a/firmware/target/arm/usb-drv-arc.c b/firmware/target/arm/usb-drv-arc.c
index 4c53108f12..22751f27f0 100644
--- a/firmware/target/arm/usb-drv-arc.c
+++ b/firmware/target/arm/usb-drv-arc.c
@@ -877,7 +877,7 @@ static void control_received(void)
}
}
- usb_core_control_request((struct usb_ctrlrequest*)tmp);
+ usb_core_legacy_control_request((struct usb_ctrlrequest*)tmp);
}
static void transfer_completed(void)
diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c
index 71d04e6f2a..0f3ecf8c00 100644
--- a/firmware/target/arm/usb-s3c6400x.c
+++ b/firmware/target/arm/usb-s3c6400x.c
@@ -522,7 +522,7 @@ static void handle_ep_int(int ep, bool out)
ep0_setup_pkt->bRequest == USB_REQ_SET_ADDRESS)
DCFG = (DCFG & ~bitm(DCFG, devadr)) | (ep0_setup_pkt->wValue << DCFG_devadr_bitp);
- usb_core_control_request(ep0_setup_pkt);
+ usb_core_legacy_control_request(ep0_setup_pkt);
}
}
diff --git a/firmware/target/arm/usb-tcc.c b/firmware/target/arm/usb-tcc.c
index 8ce75b6764..53f101c471 100644
--- a/firmware/target/arm/usb-tcc.c
+++ b/firmware/target/arm/usb-tcc.c
@@ -251,7 +251,7 @@ void handle_control(void)
DEBUG(2, "req: %02x %02d", req->bRequestType, req->bRequest);
}
- usb_core_control_request(req);
+ usb_core_legacy_control_request(req);
}
static
diff --git a/firmware/target/hosted/samsungypr/radio-ypr.c b/firmware/target/hosted/samsungypr/radio-ypr.c
index 42d485231d..ef7fb84aa0 100644
--- a/firmware/target/hosted/samsungypr/radio-ypr.c
+++ b/firmware/target/hosted/samsungypr/radio-ypr.c
@@ -126,62 +126,3 @@ int fmradio_i2c_read(unsigned char address, unsigned char* buf, int count)
(void)address;
return read(radio_dev, buf, count);
}
-
-#ifdef HAVE_RDS_CAP
-
-/* Register we are going to poll */
-#define STATUSRSSI (0xA)
-#define STATUSRSSI_RDSR (0x1 << 15)
-
-/* Low-level RDS Support */
-static struct event_queue rds_queue;
-static uint32_t rds_stack[DEFAULT_STACK_SIZE / sizeof(uint32_t)];
-
-enum {
- Q_POWERUP,
-};
-
-static void NORETURN_ATTR rds_thread(void)
-{
- /* start up frozen */
- int timeout = TIMEOUT_BLOCK;
- struct queue_event ev;
- bool rds_rdy = false;
- struct si4700_dbg_info radio_regs;
-
- while (true) {
- queue_wait_w_tmo(&rds_queue, &ev, timeout);
- switch (ev.id) {
- case Q_POWERUP:
- /* power up: timeout after 1 tick, else block indefinitely */
- timeout = ev.data ? 1 : TIMEOUT_BLOCK;
- break;
- case SYS_TIMEOUT:
- /* Captures RDS data and processes it */
- si4700_dbg_info(&radio_regs);
- bool rdsr = radio_regs.regs[STATUSRSSI] & STATUSRSSI_RDSR;
- if (rdsr != rds_rdy) {
- rds_rdy = rdsr;
- if (rdsr) {
- si4700_rds_process();
- }
- }
- break;
- }
- }
-}
-
-/* true after full radio power up, and false before powering down */
-void si4700_rds_powerup(bool on)
-{
- queue_post(&rds_queue, Q_POWERUP, on);
-}
-
-/* One-time RDS init at startup */
-void si4700_rds_init(void)
-{
- queue_init(&rds_queue, false);
- create_thread(rds_thread, rds_stack, sizeof(rds_stack), 0, "rds"
- IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU));
-}
-#endif /* HAVE_RDS_CAP */
diff --git a/firmware/target/hosted/xduoo/button-xduoo.c b/firmware/target/hosted/xduoo/button-xduoo.c
index 568ad2614d..c06b60bd9f 100644
--- a/firmware/target/hosted/xduoo/button-xduoo.c
+++ b/firmware/target/hosted/xduoo/button-xduoo.c
@@ -58,13 +58,13 @@ int button_map(int keycode)
#if defined(XDUOO_X3II) && defined(USE_REMOTE) /* Headphone remote */
case KEY_NEXTSONG:
- return (BUTTON_NEXT | BUTTON_DELAY_RELEASE);
+ return headphones_inserted()? (BUTTON_NEXT | BUTTON_DELAY_RELEASE) : 0;
case KEY_PLAYPAUSE:
- return (BUTTON_PLAY | BUTTON_DELAY_RELEASE);
+ return headphones_inserted()? (BUTTON_PLAY | BUTTON_DELAY_RELEASE) : 0;
case KEY_PREVIOUSSONG:
- return (BUTTON_PREV | BUTTON_DELAY_RELEASE);
+ return headphones_inserted()? (BUTTON_PREV | BUTTON_DELAY_RELEASE) : 0;
#endif
default:
diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c
index 8d04c54a68..07697da723 100644
--- a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c
@@ -75,7 +75,7 @@ static unsigned char ep0_rx_buf[64];
static struct usb_endpoint endpoints[] =
{
{ .type = ep_control, .fifo_addr = USB_FIFO_EP0, .fifo_size = 64 },
- { .type = ep_control, .fifo_addr = USB_FIFO_EP0, .buf = &ep0_rx_buf },
+ { .type = ep_control, .fifo_addr = USB_FIFO_EP0, .buf = ep0_rx_buf },
{ .type = ep_bulk, .fifo_addr = USB_FIFO_EP1, .fifo_size = 512 },
{ .type = ep_bulk, .fifo_addr = USB_FIFO_EP1, .fifo_size = 512 },
{ .type = ep_interrupt, .fifo_addr = USB_FIFO_EP2, .fifo_size = 64 },
@@ -193,7 +193,7 @@ static void EP0_send(void)
if(ep->sent >= ep->length)
{
REG_USB_REG_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */
- usb_core_transfer_complete(0, USB_DIR_IN, 0, ep->sent);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, ep->sent);
ep_transfer_completed(ep);
}
else
@@ -239,7 +239,7 @@ static void EP0_handler(void)
{
readFIFO(ep_recv, REG_USB_REG_COUNT0);
REG_USB_REG_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */
- usb_core_control_request((struct usb_ctrlrequest*)ep_recv->buf);
+ usb_core_legacy_control_request((struct usb_ctrlrequest*)ep_recv->buf);
}
}
diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c
index 5dbf9455e3..474d45edee 100644
--- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c
+++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c
@@ -106,7 +106,7 @@ static volatile bool ep0_data_requested = false;
static struct usb_endpoint endpoints[] =
{
EP_INIT(ep_control, USB_FIFO_EP(0), 64, NULL),
- EP_INIT(ep_control, USB_FIFO_EP(0), 64, &ep0_rx.buf),
+ EP_INIT(ep_control, USB_FIFO_EP(0), 64, ep0_rx.buf),
EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL),
EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL),
EP_INIT(ep_interrupt, USB_FIFO_EP(2), 512, NULL),
@@ -230,6 +230,8 @@ static void EP0_send(void)
select_endpoint(0);
csr0 = REG_USB_CSR0;
+ logf("%s(): 0x%x %d %d", __func__, csr0, ep->sent, ep->length);
+
if(ep->sent == 0)
{
length = MIN(ep->length, ep->fifo_size);
@@ -245,7 +247,7 @@ static void EP0_send(void)
{
REG_USB_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */
if (!ep->wait)
- usb_core_transfer_complete(0, USB_DIR_IN, 0, ep->sent);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, ep->sent);
ep->rc = 0;
ep_transfer_completed(ep);
}
@@ -263,7 +265,7 @@ static void EP0_handler(void)
select_endpoint(0);
csr0 = REG_USB_CSR0;
- logf("%s(): 0x%x", __func__, csr0);
+ logf("%s(): 0x%x %d", __func__, csr0, ep_send->busy);
/* Check for SentStall:
This bit is set when a STALL handshake is transmitted. The CPU should clear this bit.
@@ -288,12 +290,12 @@ static void EP0_handler(void)
if (ep_send->busy)
{
if (!ep_send->wait)
- usb_core_transfer_complete(0, USB_DIR_IN, -1, 0);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0);
ep_transfer_completed(ep_send);
}
if (ep_recv->busy)
{
- usb_core_transfer_complete(0, USB_DIR_OUT, -1, 0);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, -1, 0);
ep_transfer_completed(ep_recv);
}
}
@@ -304,7 +306,7 @@ static void EP0_handler(void)
if (ep_send->busy)
{
if (!ep_send->wait)
- usb_core_transfer_complete(0, USB_DIR_IN, -1, 0);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0);
ep_transfer_completed(ep_send);
}
if (ep_recv->busy && ep_recv->buf && ep_recv->length)
@@ -315,7 +317,7 @@ static void EP0_handler(void)
if (size < ep_recv->fifo_size || ep_recv->received >= ep_recv->length)
{
REG_USB_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND; /* Set data end! */
- usb_core_transfer_complete(0, USB_DIR_OUT, 0, ep_recv->received);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, 0, ep_recv->received);
ep_transfer_completed(ep_recv);
}
else REG_USB_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */
@@ -335,7 +337,7 @@ static void EP0_handler(void)
ep0_data_requested = true;
else ep0_data_supplied = true;
REG_USB_CSR0 = csr0;
- usb_core_control_request(&ep0_rx.request);
+ usb_core_legacy_control_request(&ep0_rx.request);
ep_transfer_completed(ep_recv);
}
}
@@ -815,7 +817,8 @@ static void udc_reset(void)
{
if (endpoints[0].wait)
semaphore_release(&endpoints[0].complete);
- else usb_core_transfer_complete(0, USB_DIR_IN, -1, 0);
+ else
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0);
}
endpoints[0].busy = false;
@@ -825,7 +828,7 @@ static void udc_reset(void)
endpoints[0].allocated = true;
if (endpoints[1].busy)
- usb_core_transfer_complete(0, USB_DIR_OUT, -1, 0);
+ usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, -1, 0);
endpoints[1].busy = false;
endpoints[1].wait = false;
diff --git a/firmware/target/mips/ingenic_x1000/aic-x1000.c b/firmware/target/mips/ingenic_x1000/aic-x1000.c
index 1d1768d4f9..ff9802501d 100644
--- a/firmware/target/mips/ingenic_x1000/aic-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/aic-x1000.c
@@ -84,8 +84,10 @@ static int calc_i2s_clock_params(x1000_clk_t clksrc,
*i2sdiv = X1000_EXCLK_FREQ / 64 / fs;
/* clamp to maximum value */
- if(*i2sdiv > 0x200)
- *i2sdiv = 0x200;
+ if(*i2sdiv > 512)
+ *i2sdiv = 512;
+ if(*i2sdiv == 0)
+ *i2sdiv = 1;
return 0;
}
diff --git a/firmware/target/mips/ingenic_x1000/debug-x1000.c b/firmware/target/mips/ingenic_x1000/debug-x1000.c
index 1965b0b74e..98b8f95fb5 100644
--- a/firmware/target/mips/ingenic_x1000/debug-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/debug-x1000.c
@@ -149,6 +149,9 @@ static bool dbg_cpuidle(void)
#ifdef FIIO_M3K
extern bool dbg_fiiom3k_touchpad(void);
#endif
+#ifdef SHANLING_Q1
+extern bool dbg_shanlingq1_touchscreen(void);
+#endif
#ifdef HAVE_AXP_PMU
extern bool axp_debug_menu(void);
#endif
@@ -170,6 +173,9 @@ static const struct {
#ifdef FIIO_M3K
{"Touchpad", &dbg_fiiom3k_touchpad},
#endif
+#ifdef SHANLING_Q1
+ {"Touchscreen", &dbg_shanlingq1_touchscreen},
+#endif
#ifdef HAVE_AXP_PMU
{"Power stats", &axp_debug_menu},
#endif
diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c
index 7bb985650c..b32a32a3a3 100644
--- a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c
+++ b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c
@@ -42,6 +42,7 @@ void audiohw_init(void)
gpio_set_level(GPIO_ISL54405_MUTE, 1);
gpio_set_level(GPIO_PCM5102A_XMIT, 0);
+ aic_set_play_last_sample(true);
aic_set_external_codec(true);
aic_set_i2s_mode(AIC_I2S_MASTER_MODE);
audiohw_set_frequency(HW_FREQ_48);
@@ -58,6 +59,22 @@ void audiohw_init(void)
void audiohw_postinit(void)
{
+ /*
+ * enable playback, fill FIFO buffer with -1 to prevent
+ * the DAC from auto-muting, wait, and then stop playback.
+ * This seems to completely prevent power-on or first-track
+ * clicking.
+ */
+ jz_writef(AIC_CCR, ERPL(1));
+ for (int i = 0; i < 32; i++)
+ {
+ jz_write(AIC_DR, 0xFFFFFF);
+ }
+ /* Wait until all samples are through the FIFO. */
+ while(jz_readf(AIC_SR, TFL) != 0);
+ mdelay(20); /* This seems to silence the power-on click */
+ jz_writef(AIC_CCR, ERPL(0));
+
/* unmute - attempt to make power-on pop-free */
gpio_set_level(GPIO_ISL54405_SEL, 0);
gpio_set_level(GPIO_MAX97220_SHDN, 1);
diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/boot.make b/firmware/target/mips/ingenic_x1000/erosqnative/boot.make
deleted file mode 100644
index b6d0844cd0..0000000000
--- a/firmware/target/mips/ingenic_x1000/erosqnative/boot.make
+++ /dev/null
@@ -1,30 +0,0 @@
-# __________ __ ___.
-# Open \______ \ ____ ____ | | _\_ |__ _______ ___
-# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
-# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
-# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
-# \/ \/ \/ \/ \/
-# $Id$
-#
-
-include $(ROOTDIR)/lib/microtar/microtar.make
-
-.SECONDEXPANSION:
-
-$(BUILDDIR)/spl.erosq: $(BUILDDIR)/spl.bin
- $(call PRINTS,MKSPL $(@F))$(TOOLSDIR)/mkspl-x1000 -type=nand -ppb=2 -bpp=2 $< $@
-
-$(BUILDDIR)/bootloader.ucl: $(BUILDDIR)/bootloader.bin
- $(call PRINTS,UCLPACK $(@F))$(TOOLSDIR)/uclpack --nrv2e -9 $< $@ >/dev/null
-
-.PHONY: $(BUILDDIR)/bootloader-info.txt
-$(BUILDDIR)/bootloader-info.txt:
- $(call PRINTS,GEN $(@F))echo $(SVNVERSION) > $@
-
-$(BUILDDIR)/$(BINARY): $(BUILDDIR)/spl.erosq \
- $(BUILDDIR)/bootloader.ucl \
- $(BUILDDIR)/bootloader-info.txt
- $(call PRINTS,TAR $(@F))tar -C $(BUILDDIR) \
- --numeric-owner --no-acls --no-xattrs --no-selinux \
- --mode=0644 --owner=0 --group=0 \
- -cf $@ $(call full_path_subst,$(BUILDDIR)/%,%,$^)
diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c
index 325893a4b7..5573919aa2 100644
--- a/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c
+++ b/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c
@@ -78,8 +78,11 @@ void power_init(void)
* OF's setting, although it's not strictly within the USB spec. */
axp_set_charge_current(780);
- /* Short delay to give power outputs time to stabilize */
- mdelay(5);
+ /* Delay to give power outputs time to stabilize.
+ * With the power thread delay, this can apparently go as low as 50,
+ * Keeping a higher value here just to ensure the bootloader works
+ * correctly. */
+ mdelay(200);
}
#ifdef HAVE_USB_CHARGING_ENABLE
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/boot.make b/firmware/target/mips/ingenic_x1000/fiiom3k/boot.make
deleted file mode 100644
index 77b23167c2..0000000000
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/boot.make
+++ /dev/null
@@ -1,30 +0,0 @@
-# __________ __ ___.
-# Open \______ \ ____ ____ | | _\_ |__ _______ ___
-# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
-# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
-# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
-# \/ \/ \/ \/ \/
-# $Id$
-#
-
-include $(ROOTDIR)/lib/microtar/microtar.make
-
-.SECONDEXPANSION:
-
-$(BUILDDIR)/spl.m3k: $(BUILDDIR)/spl.bin
- $(call PRINTS,MKSPL $(@F))$(TOOLSDIR)/mkspl-x1000 -type=nand -ppb=2 -bpp=2 $< $@
-
-$(BUILDDIR)/bootloader.ucl: $(BUILDDIR)/bootloader.bin
- $(call PRINTS,UCLPACK $(@F))$(TOOLSDIR)/uclpack --nrv2e -9 $< $@ >/dev/null
-
-.PHONY: $(BUILDDIR)/bootloader-info.txt
-$(BUILDDIR)/bootloader-info.txt:
- $(call PRINTS,GEN $(@F))echo $(SVNVERSION) > $@
-
-$(BUILDDIR)/$(BINARY): $(BUILDDIR)/spl.m3k \
- $(BUILDDIR)/bootloader.ucl \
- $(BUILDDIR)/bootloader-info.txt
- $(call PRINTS,TAR $(@F))tar -C $(BUILDDIR) \
- --numeric-owner --no-acls --no-xattrs --no-selinux \
- --mode=0644 --owner=0 --group=0 \
- -cf $@ $(call full_path_subst,$(BUILDDIR)/%,%,$^)
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
index 4354257f7b..24daf2ef69 100644
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
+++ b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
@@ -45,11 +45,11 @@
#define STATE_SCROLLING 4
/* Assume there's no active touch if no event is reported in this time */
-#define AUTORELEASE_TIME (10000 * OST_TICKS_PER_US)
+#define AUTORELEASE_TIME (40 * 1000 * OST_TICKS_PER_US)
/* If there's no significant motion on the scrollbar for this time,
* then report it as a button press instead */
-#define SCROLL_PRESS_TIME (100000 * OST_TICKS_PER_US)
+#define SCROLL_PRESS_TIME (400 * 1000 * OST_TICKS_PER_US)
/* If a press on the scrollbar moves more than this during SCROLL_PRESS_TIME,
* then we enter scrolling mode. */
@@ -318,7 +318,7 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty)
}
}
-static void ft_event_cb(int evt, int tx, int ty)
+static void ft_event_cb(struct ft6x06_state* state)
{
/* TODO: convert the touch positions to linear positions.
*
@@ -327,7 +327,8 @@ static void ft_event_cb(int evt, int tx, int ty)
* the middle of the touchpad than on the edges, so scrolling feels slow
* in the middle and faster near the edge.
*/
- ft_step_state(__ost_read32(), evt, tx, ty);
+ struct ft6x06_point* pt = &state->points[0];
+ ft_step_state(__ost_read32(), pt->event, pt->pos_x, pt->pos_y);
}
static void ft_init(void)
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c
index 6b1ad2dbb5..5c92fa81e2 100644
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c
+++ b/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c
@@ -77,7 +77,7 @@ void power_init(void)
axp_set_charge_current(780);
/* Short delay to give power outputs time to stabilize */
- mdelay(5);
+ mdelay(200);
}
#ifdef HAVE_USB_CHARGING_ENABLE
diff --git a/firmware/target/mips/ingenic_x1000/installer-x1000.c b/firmware/target/mips/ingenic_x1000/installer-x1000.c
index 617e6645b7..0a09ad0e91 100644
--- a/firmware/target/mips/ingenic_x1000/installer-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/installer-x1000.c
@@ -23,7 +23,7 @@
#include "nand-x1000.h"
#include "core_alloc.h"
#include "file.h"
-#include "microtar.h"
+#include "microtar-rockbox.h"
#include <stddef.h>
struct update_part {
@@ -90,21 +90,21 @@ static void get_image_loc(nand_drv* ndrv, size_t* offptr, size_t* lenptr)
static int patch_part(mtar_t* tar, const struct update_part* part,
uint8_t* img_buf, size_t img_off)
{
- mtar_header_t h;
- int rc = mtar_find(tar, part->filename, &h);
+ int rc = mtar_find(tar, part->filename);
if(rc != MTAR_ESUCCESS)
return IERR_BAD_FORMAT;
- if(h.type != 0 && h.type != MTAR_TREG)
+ const mtar_header_t* h = mtar_get_header(tar);
+ if(h->type != 0 && h->type != MTAR_TREG)
return IERR_BAD_FORMAT;
- if(h.size > part->length)
+ if(h->size > part->length)
return IERR_BAD_FORMAT;
/* wipe the patched area, and read in the new data */
memset(&img_buf[part->offset - img_off], 0xff, part->length);
- rc = mtar_read_data(tar, &img_buf[part->offset - img_off], h.size);
- if(rc != MTAR_ESUCCESS)
+ rc = mtar_read_data(tar, &img_buf[part->offset - img_off], h->size);
+ if(rc < 0 || (unsigned)rc != h->size)
return IERR_FILE_IO;
return IERR_SUCCESS;
@@ -165,7 +165,7 @@ static int updater_init(struct updater* u)
CACHEALIGN_BUFFER(buffer, buf_len);
u->tar = (mtar_t*)buffer;
- memset(u->tar, 0, sizeof(struct mtar_t));
+ memset(u->tar, 0, sizeof(mtar_t));
rc = IERR_SUCCESS;
@@ -175,7 +175,7 @@ static int updater_init(struct updater* u)
static void updater_cleanup(struct updater* u)
{
- if(u->tar && u->tar->close)
+ if(u->tar && mtar_is_open(u->tar))
mtar_close(u->tar);
if(u->buf_hnd >= 0)
@@ -202,7 +202,7 @@ int install_bootloader(const char* filename)
}
/* get the tarball */
- rc = mtar_open(u.tar, filename, "r");
+ rc = mtar_open(u.tar, filename, O_RDONLY);
if(rc != MTAR_ESUCCESS) {
if(rc == MTAR_EOPENFAIL)
rc = IERR_FILE_NOT_FOUND;
diff --git a/firmware/target/mips/ingenic_x1000/pcm-x1000.c b/firmware/target/mips/ingenic_x1000/pcm-x1000.c
index ce2fbb17a9..ef54d45e62 100644
--- a/firmware/target/mips/ingenic_x1000/pcm-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/pcm-x1000.c
@@ -58,7 +58,7 @@ void pcm_play_dma_init(void)
/* Configure AIC with some sane defaults */
jz_writef(AIC_CFG, RST(1));
jz_writef(AIC_I2SCR, STPBK(1));
- jz_writef(AIC_CFG, MSB(0), LSMP(1), ICDC(0), AUSEL(1), BCKD(0), SYNCD(0));
+ jz_writef(AIC_CFG, MSB(0), LSMP(0), ICDC(0), AUSEL(1), BCKD(0), SYNCD(0));
jz_writef(AIC_CCR, ENDSW(0), ASVTSU(0));
jz_writef(AIC_I2SCR, RFIRST(0), ESCLK(0), AMSL(0));
jz_write(AIC_SPENA, 0);
diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make b/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make
deleted file mode 100644
index 639f570ea3..0000000000
--- a/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make
+++ /dev/null
@@ -1,31 +0,0 @@
-# __________ __ ___.
-# Open \______ \ ____ ____ | | _\_ |__ _______ ___
-# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
-# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
-# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
-# \/ \/ \/ \/ \/
-# $Id$
-#
-
-include $(ROOTDIR)/lib/microtar/microtar.make
-
-.SECONDEXPANSION:
-
-# FIXME(q1): verify NAND parameters
-$(BUILDDIR)/spl.q1: $(BUILDDIR)/spl.bin
- $(call PRINTS,MKSPL $(@F))$(TOOLSDIR)/mkspl-x1000 -type=nand -ppb=2 -bpp=2 $< $@
-
-$(BUILDDIR)/bootloader.ucl: $(BUILDDIR)/bootloader.bin
- $(call PRINTS,UCLPACK $(@F))$(TOOLSDIR)/uclpack --nrv2e -9 $< $@ >/dev/null
-
-.PHONY: $(BUILDDIR)/bootloader-info.txt
-$(BUILDDIR)/bootloader-info.txt:
- $(call PRINTS,GEN $(@F))echo $(SVNVERSION) > $@
-
-$(BUILDDIR)/$(BINARY): $(BUILDDIR)/spl.q1 \
- $(BUILDDIR)/bootloader.ucl \
- $(BUILDDIR)/bootloader-info.txt
- $(call PRINTS,TAR $(@F))tar -C $(BUILDDIR) \
- --numeric-owner --no-acls --no-xattrs --no-selinux \
- --mode=0644 --owner=0 --group=0 \
- -cf $@ $(call full_path_subst,$(BUILDDIR)/%,%,$^)
diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c
index 27c49a7bd7..13b0cdd078 100644
--- a/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c
+++ b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c
@@ -32,6 +32,11 @@
#include "i2c-x1000.h"
#include <stdbool.h>
+#ifndef BOOTLOADER
+# include "lcd.h"
+# include "font.h"
+#endif
+
/* Volume wheel rotation */
static volatile int wheel_pos = 0;
@@ -109,6 +114,7 @@ void button_init_device(void)
int button_read_device(int* data)
{
+ const struct ft6x06_point* point;
int r = 0;
/* Read GPIO buttons, these are all active low */
@@ -138,16 +144,22 @@ int button_read_device(int* data)
reset_poweroff_timer();
}
- /* Handle touchscreen
- *
- * TODO: Support 2-point multitouch (useful for 3x3 grid mode)
- * TODO: Support simple gestures by converting them to fake buttons
- */
- int t = touchscreen_to_pixels(ft6x06_state.pos_x, ft6x06_state.pos_y, data);
- if(ft6x06_state.event == FT6x06_EVT_PRESS ||
- ft6x06_state.event == FT6x06_EVT_CONTACT) {
- /* Only set the button bit if the screen is being touched. */
- r |= t;
+ if(touchscreen_get_mode() == TOUCHSCREEN_POINT) {
+ /* Pointing mode can't use multitouch since we can only pass
+ * along coordinates for one touch event at a time */
+ point = &ft6x06_state.points[0];
+ int t = touchscreen_to_pixels(point->pos_x, point->pos_y, data);
+ if(point->event == FT6x06_EVT_PRESS ||
+ point->event == FT6x06_EVT_CONTACT)
+ r |= t;
+ } else {
+ /* 3x3 mode can have simultaneous 'button' presses via multitouch */
+ for(int i = 0; i < ft6x06_state.nr_points; ++i) {
+ point = &ft6x06_state.points[i];
+ if(point->event == FT6x06_EVT_PRESS ||
+ point->event == FT6x06_EVT_CONTACT)
+ r |= touchscreen_to_pixels(point->pos_x, point->pos_y, NULL);
+ }
}
return r;
@@ -193,3 +205,54 @@ void GPIOD03(void)
handle_wheel_irq();
gpio_flip_edge_irq(GPIO_WHEEL2);
}
+
+#ifndef BOOTLOADER
+static int getbtn(void)
+{
+ int btn;
+ do {
+ btn = button_get_w_tmo(1);
+ } while(btn & (BUTTON_REL|BUTTON_REPEAT));
+ return btn;
+}
+
+bool dbg_shanlingq1_touchscreen(void)
+{
+ /* definition of box used to represent the touchpad */
+ const int pad_w = LCD_WIDTH;
+ const int pad_h = LCD_HEIGHT;
+ const int box_h = pad_h - SYSFONT_HEIGHT*5;
+ const int box_w = pad_w * box_h / pad_h;
+ const int box_x = (LCD_WIDTH - box_w) / 2;
+ const int box_y = SYSFONT_HEIGHT * 9 / 2;
+
+ bool draw_border = true;
+
+ do {
+ int line = 0;
+ lcd_clear_display();
+ lcd_putsf(0, line++, "nr_points: %d gesture: %d",
+ ft6x06_state.nr_points, ft6x06_state.gesture);
+
+ /* draw touchpad box borders */
+ if(draw_border)
+ lcd_drawrect(box_x, box_y, box_w, box_h);
+
+ for(int i = 0; i < ft6x06_state.nr_points; ++i) {
+ const struct ft6x06_point* point = &ft6x06_state.points[i];
+ lcd_putsf(0, line++, "pt%d id:%d pos: %d,%d wgt: %d area:%d",
+ i, point->touch_id, point->pos_x, point->pos_y,
+ point->weight, point->area);
+
+ /* draw crosshair */
+ int tx = box_x + point->pos_x * box_w / pad_w;
+ int ty = box_y + point->pos_y * box_h / pad_h;
+ lcd_hline(tx-2, tx+2, ty);
+ lcd_vline(tx, ty-2, ty+2);
+ }
+
+ lcd_update();
+ } while(getbtn() != BUTTON_POWER);
+ return false;
+}
+#endif
diff --git a/firmware/target/mips/ingenic_x1000/usb-x1000.c b/firmware/target/mips/ingenic_x1000/usb-x1000.c
index 1a31d8db2e..398528c6c4 100644
--- a/firmware/target/mips/ingenic_x1000/usb-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/usb-x1000.c
@@ -35,10 +35,13 @@
const struct usb_dw_config usb_dw_config = {
.phytype = DWC_PHYTYPE_UTMI_16,
- /* Available FIFO memory: 3576 words */
- .rx_fifosz = 1024,
- .nptx_fifosz = 128, /* 1 dedicated FIFO for EP0 */
- .ptx_fifosz = 768, /* 3 dedicated FIFOs */
+ /* Available FIFO memory: 3576 words
+ * Number of endpoints: 9
+ * Max packet size: 512 bytes
+ */
+ .rx_fifosz = 816, /* shared RxFIFO */
+ .nptx_fifosz = 32, /* only used for EP0 IN */
+ .ptx_fifosz = 384, /* room for 7 IN EPs */
#ifndef USB_DW_ARCH_SLAVE
.ahb_burst_len = HBSTLEN_INCR16,
diff --git a/firmware/target/mips/ingenic_x1000/x1000/ost.h b/firmware/target/mips/ingenic_x1000/x1000/ost.h
index 8f2619e0e7..9444712e86 100644
--- a/firmware/target/mips/ingenic_x1000/x1000/ost.h
+++ b/firmware/target/mips/ingenic_x1000/x1000/ost.h
@@ -31,21 +31,21 @@
#define JT_OST_CTRL JIO_32_RW
#define JN_OST_CTRL OST_CTRL
#define JI_OST_CTRL
-#define BP_OST_CTRL_PRESCALE2 3
-#define BM_OST_CTRL_PRESCALE2 0x38
+#define BP_OST_CTRL_PRESCALE2 2
+#define BM_OST_CTRL_PRESCALE2 0xc
#define BV_OST_CTRL_PRESCALE2__BY_1 0x0
#define BV_OST_CTRL_PRESCALE2__BY_4 0x1
#define BV_OST_CTRL_PRESCALE2__BY_16 0x2
-#define BF_OST_CTRL_PRESCALE2(v) (((v) & 0x7) << 3)
+#define BF_OST_CTRL_PRESCALE2(v) (((v) & 0x3) << 2)
#define BFM_OST_CTRL_PRESCALE2(v) BM_OST_CTRL_PRESCALE2
#define BF_OST_CTRL_PRESCALE2_V(e) BF_OST_CTRL_PRESCALE2(BV_OST_CTRL_PRESCALE2__##e)
#define BFM_OST_CTRL_PRESCALE2_V(v) BM_OST_CTRL_PRESCALE2
#define BP_OST_CTRL_PRESCALE1 0
-#define BM_OST_CTRL_PRESCALE1 0x7
+#define BM_OST_CTRL_PRESCALE1 0x3
#define BV_OST_CTRL_PRESCALE1__BY_1 0x0
#define BV_OST_CTRL_PRESCALE1__BY_4 0x1
#define BV_OST_CTRL_PRESCALE1__BY_16 0x2
-#define BF_OST_CTRL_PRESCALE1(v) (((v) & 0x7) << 0)
+#define BF_OST_CTRL_PRESCALE1(v) (((v) & 0x3) << 0)
#define BFM_OST_CTRL_PRESCALE1(v) BM_OST_CTRL_PRESCALE1
#define BF_OST_CTRL_PRESCALE1_V(e) BF_OST_CTRL_PRESCALE1(BV_OST_CTRL_PRESCALE1__##e)
#define BFM_OST_CTRL_PRESCALE1_V(v) BM_OST_CTRL_PRESCALE1
diff --git a/firmware/target/mips/ingenic_x1000/x1000boot.make b/firmware/target/mips/ingenic_x1000/x1000boot.make
index e69b6a1439..0bdf5cf7b4 100644
--- a/firmware/target/mips/ingenic_x1000/x1000boot.make
+++ b/firmware/target/mips/ingenic_x1000/x1000boot.make
@@ -7,6 +7,8 @@
# $Id$
#
+include $(ROOTDIR)/lib/microtar/microtar.make
+
INCLUDES += -I$(APPSDIR)
SRC += $(call preprocess, $(APPSDIR)/SOURCES)
@@ -14,16 +16,23 @@ LDSDEP := $(FIRMDIR)/export/cpu.h $(FIRMDIR)/export/config/$(MODELNAME).h
BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds
BOOTLINK := $(BUILDDIR)/boot.link
+BOOTEXT := $(suffix $(BINARY))
SPLLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/spl.lds
SPLLINK := $(BUILDDIR)/spl.link
+SPLBINARY := spl$(BOOTEXT)
+
+BLINFO = $(BUILDDIR)/bootloader-info.txt
CLEANOBJS += $(BUILDDIR)/bootloader.* $(BUILDDIR)/spl.*
-include $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/$(MODELNAME)/boot.make
+# Currently not needed
+#include $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/$(MODELNAME)/boot.make
.SECONDEXPANSION:
+### Bootloader
+
$(BOOTLINK): $(BOOTLDS) $(LDSDEP)
$(call PRINTS,PP $(@F))
$(call preprocess2file,$<,$@,)
@@ -38,6 +47,12 @@ $(BUILDDIR)/bootloader.elf: $$(OBJ) $(FIRMLIB) $(CORE_LIBS) $$(BOOTLINK)
$(BUILDDIR)/bootloader.bin: $(BUILDDIR)/bootloader.elf
$(call PRINTS,OC $(@F))$(call objcopy,$<,$@)
+$(BUILDDIR)/bootloader.ucl: $(BUILDDIR)/bootloader.bin
+ $(call PRINTS,UCLPACK $(@F))$(TOOLSDIR)/uclpack --nrv2e -9 $< $@ >/dev/null
+
+
+### SPL
+
$(SPLLINK): $(SPLLDS) $(LDSDEP)
$(call PRINTS,PP $(@F))
$(call preprocess2file,$<,$@,)
@@ -51,3 +66,29 @@ $(BUILDDIR)/spl.elf: $$(OBJ) $(FIRMLIB) $(CORE_LIBS) $$(SPLLINK)
$(BUILDDIR)/spl.bin: $(BUILDDIR)/spl.elf
$(call PRINTS,OC $(@F))$(call objcopy,$<,$@)
+
+$(BUILDDIR)/$(SPLBINARY): $(BUILDDIR)/spl.bin
+ $(call PRINTS,MKSPL $(@F))$(MKFIRMWARE) $< $@
+
+
+### Generating the update package
+
+# suppress regenerating bootloader-info if nothing has changed
+BLVERSION:=$(SVNVERSION)
+OLDBLVERSION:=$(shell head -n1 $(BLINFO) 2>/dev/null || echo "NOREVISION")
+
+ifneq ($(BLVERSION),$(OLDBLVERSION))
+.PHONY: $(BLINFO)
+endif
+
+$(BLINFO):
+ $(call PRINTS,GEN $(@F))echo $(SVNVERSION) > $@
+
+# The "binary" is actually an update package which is just a tar archive
+$(BUILDDIR)/$(BINARY): $(BUILDDIR)/$(SPLBINARY) \
+ $(BUILDDIR)/bootloader.ucl \
+ $(BLINFO)
+ $(call PRINTS,TAR $(@F))tar -C $(BUILDDIR) \
+ --numeric-owner --no-acls --no-xattrs --no-selinux \
+ --mode=0644 --owner=0 --group=0 \
+ -cf $@ $(call full_path_subst,$(BUILDDIR)/%,%,$^)
diff --git a/firmware/usb.c b/firmware/usb.c
index b919fe468d..4c122e8eea 100644
--- a/firmware/usb.c
+++ b/firmware/usb.c
@@ -476,7 +476,12 @@ static void NORETURN_ATTR usb_thread(void)
usb_state = USB_POWERED;
usb_stack_enable(true);
-
+#ifndef BOOTLOADER
+#ifndef HAVE_USB_POWER
+ int usb_mode = -1;
+#endif
+ send_event(SYS_EVENT_USB_INSERTED, &usb_mode);
+#endif
/* Power (charging-only) button */
#ifdef HAVE_USB_POWER
new_usbmode = usb_mode;
@@ -547,7 +552,9 @@ static void NORETURN_ATTR usb_thread(void)
#ifdef HAVE_USB_POWER
new_usbmode = usb_mode;
#endif
-
+#ifndef BOOTLOADER
+ send_event(SYS_EVENT_USB_EXTRACTED, NULL);
+#endif
usb_set_host_present(false);
break;
/* USB_EXTRACTED: */
diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h
index 20ee26a3d0..bffc994d9e 100644
--- a/firmware/usbstack/usb_class_driver.h
+++ b/firmware/usbstack/usb_class_driver.h
@@ -22,6 +22,11 @@
#ifndef _USB_CLASS_DRIVER_H_
#define _USB_CLASS_DRIVER_H_
+#include "usb_ch9.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
/* Common api, implemented by all class drivers */
struct usb_class_driver {
@@ -75,7 +80,7 @@ struct usb_class_driver {
able to handle it, it should ack the request, and return true. Otherwise
it should return false.
Optional function */
- bool (*control_request)(struct usb_ctrlrequest* req, unsigned char *dest);
+ bool (*control_request)(struct usb_ctrlrequest* req, void* reqdata, unsigned char *dest);
#ifdef HAVE_HOTSWAP
/* Tells the driver that a hotswappable disk/card was inserted or
diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c
index bf73c58abc..63df173033 100644
--- a/firmware/usbstack/usb_core.c
+++ b/firmware/usbstack/usb_core.c
@@ -22,6 +22,7 @@
#include "thread.h"
#include "kernel.h"
#include "string.h"
+#include "panic.h"
/*#define LOGF_ENABLE*/
#include "logf.h"
@@ -90,9 +91,9 @@ static struct usb_device_descriptor __attribute__((aligned(2)))
.idVendor = USB_VENDOR_ID,
.idProduct = USB_PRODUCT_ID,
.bcdDevice = 0x0100,
- .iManufacturer = 1,
- .iProduct = 2,
- .iSerialNumber = 3,
+ .iManufacturer = USB_STRING_INDEX_MANUFACTURER,
+ .iProduct = USB_STRING_INDEX_PRODUCT,
+ .iSerialNumber = USB_STRING_INDEX_SERIAL,
.bNumConfigurations = 1
} ;
@@ -141,12 +142,12 @@ static const struct usb_string_descriptor __attribute__((aligned(2)))
lang_descriptor =
USB_STRING_INITIALIZER(u"\x0409"); /* LANGID US English */
-static const struct usb_string_descriptor* const usb_strings[] =
+static const struct usb_string_descriptor* const usb_strings[USB_STRING_INDEX_MAX] =
{
- &lang_descriptor,
- &usb_string_iManufacturer,
- &usb_string_iProduct,
- &usb_string_iSerial
+ [USB_STRING_INDEX_LANGUAGE] = &lang_descriptor,
+ [USB_STRING_INDEX_MANUFACTURER] = &usb_string_iManufacturer,
+ [USB_STRING_INDEX_PRODUCT] = &usb_string_iProduct,
+ [USB_STRING_INDEX_SERIAL] = &usb_string_iSerial,
};
static int usb_address = 0;
@@ -172,7 +173,7 @@ static int usb_no_host_callback(struct timeout *tmo)
static int usb_core_num_interfaces;
typedef void (*completion_handler_t)(int ep, int dir, int status, int length);
-typedef bool (*control_handler_t)(struct usb_ctrlrequest* req,
+typedef bool (*control_handler_t)(struct usb_ctrlrequest* req, void* reqdata,
unsigned char* dest);
static struct
@@ -262,7 +263,13 @@ static struct usb_class_driver drivers[USB_NUM_DRIVERS] =
#endif
};
-static void usb_core_control_request_handler(struct usb_ctrlrequest* req);
+#ifdef USB_LEGACY_CONTROL_API
+static struct usb_ctrlrequest* volatile active_request = NULL;
+static void* volatile control_write_data = NULL;
+static volatile bool control_write_data_done = false;
+#endif
+
+static void usb_core_control_request_handler(struct usb_ctrlrequest* req, void* reqdata);
static unsigned char response_data[256] USB_DEVBSS_ATTR;
@@ -446,10 +453,10 @@ void usb_core_handle_transfer_completion(
case EP_CONTROL:
logf("ctrl handled %ld req=0x%x",
current_tick,
- ((struct usb_ctrlrequest*)event->data)->bRequest);
+ ((struct usb_ctrlrequest*)event->data[0])->bRequest);
usb_core_control_request_handler(
- (struct usb_ctrlrequest*)event->data);
+ (struct usb_ctrlrequest*)event->data[0], event->data[1]);
break;
default:
handler = ep_data[ep].completion_handler[EP_DIR(event->dir)];
@@ -560,7 +567,7 @@ static void allocate_interfaces_and_endpoints(void)
}
-static void control_request_handler_drivers(struct usb_ctrlrequest* req)
+static void control_request_handler_drivers(struct usb_ctrlrequest* req, void* reqdata)
{
int i, interface = req->wIndex & 0xff;
bool handled = false;
@@ -571,7 +578,7 @@ static void control_request_handler_drivers(struct usb_ctrlrequest* req)
drivers[i].first_interface <= interface &&
drivers[i].last_interface > interface)
{
- handled = drivers[i].control_request(req, response_data);
+ handled = drivers[i].control_request(req, reqdata, response_data);
if(handled)
break;
}
@@ -579,11 +586,11 @@ static void control_request_handler_drivers(struct usb_ctrlrequest* req)
if(!handled) {
/* nope. flag error */
logf("bad req:desc %d:%d", req->bRequest, req->wValue >> 8);
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
}
}
-static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req)
+static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, void* reqdata)
{
int size;
const void* ptr = NULL;
@@ -637,8 +644,7 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req)
case USB_DT_STRING:
logf("STRING %d", index);
- if((unsigned)index < (sizeof(usb_strings) /
- sizeof(struct usb_string_descriptor*))) {
+ if((unsigned)index < USB_STRING_INDEX_MAX) {
size = usb_strings[index]->bLength;
ptr = usb_strings[index];
}
@@ -652,7 +658,7 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req)
}
else {
logf("bad string id %d", index);
- usb_drv_stall(EP_CONTROL, true, true);
+ ptr = NULL;
}
break;
@@ -663,8 +669,8 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req)
default:
logf("ctrl desc.");
- control_request_handler_drivers(req);
- break;
+ control_request_handler_drivers(req, reqdata);
+ return;
}
if(ptr) {
@@ -674,8 +680,9 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req)
if (ptr != response_data)
memcpy(response_data, ptr, length);
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, length);
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, length);
+ } else {
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
}
}
@@ -719,105 +726,105 @@ static void usb_core_do_clear_feature(int recip, int recip_nr, int feature)
}
}
-static void request_handler_device(struct usb_ctrlrequest* req)
+static void request_handler_device(struct usb_ctrlrequest* req, void* reqdata)
{
+ unsigned address;
+
switch(req->bRequest) {
- case USB_REQ_GET_CONFIGURATION: {
- logf("usb_core: GET_CONFIG");
- response_data[0] = (usb_state == ADDRESS ? 0 : 1);
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, 1);
- break;
- }
- case USB_REQ_SET_CONFIGURATION: {
- usb_drv_cancel_all_transfers();
- usb_core_do_set_config(req->wValue);
- usb_drv_send(EP_CONTROL, NULL, 0);
- break;
- }
- case USB_REQ_SET_ADDRESS: {
- unsigned char address = req->wValue;
- usb_drv_send(EP_CONTROL, NULL, 0);
- usb_drv_cancel_all_transfers();
- usb_drv_set_address(address);
- usb_core_do_set_addr(address);
- break;
- }
+ case USB_REQ_GET_CONFIGURATION:
+ logf("usb_core: GET_CONFIG");
+ response_data[0] = (usb_state == ADDRESS ? 0 : 1);
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, 1);
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ usb_drv_cancel_all_transfers();
+ usb_core_do_set_config(req->wValue);
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
+ break;
+ case USB_REQ_SET_ADDRESS:
+ /* NOTE: We really have no business handling this and drivers
+ * should just handle it themselves. We don't care beyond
+ * knowing if we've been assigned an address yet, or not. */
+ address = req->wValue;
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
+ usb_drv_cancel_all_transfers();
+ usb_drv_set_address(address);
+ usb_core_do_set_addr(address);
+ break;
case USB_REQ_GET_DESCRIPTOR:
logf("usb_core: GET_DESC %d", req->wValue >> 8);
- request_handler_device_get_descriptor(req);
- break;
- case USB_REQ_CLEAR_FEATURE:
+ request_handler_device_get_descriptor(req, reqdata);
break;
case USB_REQ_SET_FEATURE:
if(req->wValue==USB_DEVICE_TEST_MODE) {
int mode = req->wIndex >> 8;
- usb_drv_send(EP_CONTROL, NULL, 0);
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
usb_drv_set_test_mode(mode);
+ } else {
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
}
break;
case USB_REQ_GET_STATUS:
response_data[0] = 0;
response_data[1] = 0;
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, 2);
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, 2);
break;
default:
logf("bad req:desc %d:%d", req->bRequest, req->wValue);
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
break;
}
}
-static void request_handler_interface_standard(struct usb_ctrlrequest* req)
+static void request_handler_interface_standard(struct usb_ctrlrequest* req, void* reqdata)
{
switch (req->bRequest)
{
case USB_REQ_SET_INTERFACE:
logf("usb_core: SET_INTERFACE");
- usb_drv_send(EP_CONTROL, NULL, 0);
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
break;
case USB_REQ_GET_INTERFACE:
logf("usb_core: GET_INTERFACE");
response_data[0] = 0;
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, 1);
- break;
- case USB_REQ_CLEAR_FEATURE:
- break;
- case USB_REQ_SET_FEATURE:
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, 1);
break;
case USB_REQ_GET_STATUS:
response_data[0] = 0;
response_data[1] = 0;
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, 2);
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, 2);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ case USB_REQ_SET_FEATURE:
+ /* TODO: These used to be ignored (erroneously).
+ * Should they be passed to the drivers instead? */
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
break;
default:
- control_request_handler_drivers(req);
+ control_request_handler_drivers(req, reqdata);
break;
}
}
-static void request_handler_interface(struct usb_ctrlrequest* req)
+static void request_handler_interface(struct usb_ctrlrequest* req, void* reqdata)
{
switch(req->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
- request_handler_interface_standard(req);
+ request_handler_interface_standard(req, reqdata);
break;
case USB_TYPE_CLASS:
- control_request_handler_drivers(req);
+ control_request_handler_drivers(req, reqdata);
break;
case USB_TYPE_VENDOR:
default:
logf("bad req:desc %d", req->bRequest);
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
break;
}
}
-static void request_handler_endoint_drivers(struct usb_ctrlrequest* req)
+static void request_handler_endpoint_drivers(struct usb_ctrlrequest* req, void* reqdata)
{
bool handled = false;
control_handler_t control_handler = NULL;
@@ -827,30 +834,30 @@ static void request_handler_endoint_drivers(struct usb_ctrlrequest* req)
ep_data[EP_NUM(req->wIndex)].control_handler[EP_DIR(req->wIndex)];
if(control_handler)
- handled = control_handler(req, response_data);
+ handled = control_handler(req, reqdata, response_data);
if(!handled) {
/* nope. flag error */
logf("usb bad req %d", req->bRequest);
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
}
}
-static void request_handler_endpoint_standard(struct usb_ctrlrequest* req)
+static void request_handler_endpoint_standard(struct usb_ctrlrequest* req, void* reqdata)
{
switch (req->bRequest) {
case USB_REQ_CLEAR_FEATURE:
usb_core_do_clear_feature(USB_RECIP_ENDPOINT,
req->wIndex,
req->wValue);
- usb_drv_send(EP_CONTROL, NULL, 0);
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
break;
case USB_REQ_SET_FEATURE:
logf("usb_core: SET FEATURE (%d)", req->wValue);
if(req->wValue == USB_ENDPOINT_HALT)
usb_drv_stall(EP_NUM(req->wIndex), true, EP_DIR(req->wIndex));
-
- usb_drv_send(EP_CONTROL, NULL, 0);
+
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
break;
case USB_REQ_GET_STATUS:
response_data[0] = 0;
@@ -859,35 +866,34 @@ static void request_handler_endpoint_standard(struct usb_ctrlrequest* req)
if(req->wIndex > 0)
response_data[0] = usb_drv_stalled(EP_NUM(req->wIndex),
EP_DIR(req->wIndex));
-
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
- usb_drv_send(EP_CONTROL, response_data, 2);
+
+ usb_drv_control_response(USB_CONTROL_ACK, response_data, 2);
break;
default:
- request_handler_endoint_drivers(req);
+ request_handler_endpoint_drivers(req, reqdata);
break;
}
}
-static void request_handler_endpoint(struct usb_ctrlrequest* req)
+static void request_handler_endpoint(struct usb_ctrlrequest* req, void* reqdata)
{
switch(req->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
- request_handler_endpoint_standard(req);
+ request_handler_endpoint_standard(req, reqdata);
break;
case USB_TYPE_CLASS:
- request_handler_endoint_drivers(req);
+ request_handler_endpoint_drivers(req, reqdata);
break;
case USB_TYPE_VENDOR:
default:
logf("bad req:desc %d", req->bRequest);
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
break;
}
}
/* Handling USB requests starts here */
-static void usb_core_control_request_handler(struct usb_ctrlrequest* req)
+static void usb_core_control_request_handler(struct usb_ctrlrequest* req, void* reqdata)
{
#ifdef HAVE_USB_CHARGING_ENABLE
timeout_cancel(&usb_no_host_timeout);
@@ -905,20 +911,19 @@ static void usb_core_control_request_handler(struct usb_ctrlrequest* req)
switch(req->bRequestType & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
- request_handler_device(req);
+ request_handler_device(req, reqdata);
break;
case USB_RECIP_INTERFACE:
- request_handler_interface(req);
+ request_handler_interface(req, reqdata);
break;
case USB_RECIP_ENDPOINT:
- request_handler_endpoint(req);
+ request_handler_endpoint(req, reqdata);
break;
- case USB_RECIP_OTHER:
+ default:
logf("unsupported recipient");
- usb_drv_stall(EP_CONTROL, true, true);
+ usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
break;
}
- //logf("control handled");
}
/* called by usb_drv_int() */
@@ -928,32 +933,47 @@ void usb_core_bus_reset(void)
usb_address = 0;
usb_state = DEFAULT;
#ifdef HAVE_USB_CHARGING_ENABLE
+#ifdef HAVE_USB_CHARGING_IN_THREAD
+ /* On some targets usb_charging_maxcurrent_change() cannot be called
+ * from an interrupt handler; get the USB thread to do it instead. */
+ usb_charger_update();
+#else
usb_charging_maxcurrent_change(usb_charging_maxcurrent());
#endif
+#endif
}
/* called by usb_drv_transfer_completed() */
void usb_core_transfer_complete(int endpoint, int dir, int status, int length)
{
- struct usb_transfer_completion_event_data *completion_event;
+ struct usb_transfer_completion_event_data* completion_event =
+ &ep_data[endpoint].completion_event[EP_DIR(dir)];
- switch (endpoint) {
- case EP_CONTROL:
- /* already handled */
- break;
+ void* data0 = NULL;
+ void* data1 = NULL;
- default:
- completion_event = &ep_data[endpoint].completion_event[EP_DIR(dir)];
+#ifdef USB_LEGACY_CONTROL_API
+ if(endpoint == EP_CONTROL) {
+ bool cwdd = control_write_data_done;
+ struct usb_ctrlrequest* req = active_request;
- completion_event->endpoint = endpoint;
- completion_event->dir = dir;
- completion_event->data = 0;
- completion_event->status = status;
- completion_event->length = length;
- /* All other endpoints. Let the thread deal with it */
- usb_signal_transfer_completion(completion_event);
- break;
+ if(dir == USB_DIR_OUT && req && cwdd) {
+ data0 = req;
+ data1 = control_write_data;
+ } else {
+ return;
+ }
}
+#endif
+
+ completion_event->endpoint = endpoint;
+ completion_event->dir = dir;
+ completion_event->data[0] = data0;
+ completion_event->data[1] = data1;
+ completion_event->status = status;
+ completion_event->length = length;
+
+ usb_signal_transfer_completion(completion_event);
}
void usb_core_handle_notify(long id, intptr_t data)
@@ -971,21 +991,118 @@ void usb_core_handle_notify(long id, intptr_t data)
}
}
-/* called by usb_drv_int() */
-void usb_core_control_request(struct usb_ctrlrequest* req)
+void usb_core_control_request(struct usb_ctrlrequest* req, void* reqdata)
{
struct usb_transfer_completion_event_data* completion_event =
&ep_data[EP_CONTROL].completion_event[EP_DIR(USB_DIR_IN)];
completion_event->endpoint = EP_CONTROL;
completion_event->dir = 0;
- completion_event->data = (void*)req;
+ completion_event->data[0] = (void*)req;
+ completion_event->data[1] = reqdata;
completion_event->status = 0;
completion_event->length = 0;
logf("ctrl received %ld, req=0x%x", current_tick, req->bRequest);
usb_signal_transfer_completion(completion_event);
}
+void usb_core_control_complete(int status)
+{
+ /* We currently don't use this, it's here to make the API look good ;)
+ * It makes sense to #define it away on normal builds.
+ */
+ (void)status;
+ logf("ctrl complete %ld, %d", current_tick, status);
+}
+
+#ifdef USB_LEGACY_CONTROL_API
+/* Only needed if the driver does not support the new API yet */
+void usb_core_legacy_control_request(struct usb_ctrlrequest* req)
+{
+ active_request = req;
+ control_write_data = NULL;
+ control_write_data_done = false;
+
+ usb_core_control_request(req, NULL);
+}
+
+void usb_drv_control_response(enum usb_control_response resp,
+ void* data, int length)
+{
+ struct usb_ctrlrequest* req = active_request;
+
+ if(!req)
+ panicf("null ctrl req");
+
+ if(req->wLength == 0)
+ {
+ active_request = NULL;
+
+ /* No-data request */
+ if(resp == USB_CONTROL_ACK)
+ usb_drv_send(EP_CONTROL, data, length);
+ else if(resp == USB_CONTROL_STALL)
+ usb_drv_stall(EP_CONTROL, true, true);
+ else
+ panicf("RECEIVE on non-data req");
+ }
+ else if(req->bRequestType & USB_DIR_IN)
+ {
+ /* Control read request */
+ if(resp == USB_CONTROL_ACK)
+ {
+ active_request = NULL;
+ usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0);
+ usb_drv_send(EP_CONTROL, data, length);
+ }
+ else if(resp == USB_CONTROL_STALL)
+ {
+ active_request = NULL;
+ usb_drv_stall(EP_CONTROL, true, true);
+ }
+ else
+ {
+ panicf("RECEIVE on ctrl read req");
+ }
+ }
+ else if(!control_write_data_done)
+ {
+ /* Control write request, data phase */
+ if(resp == USB_CONTROL_RECEIVE)
+ {
+ control_write_data = data;
+ control_write_data_done = true;
+ usb_drv_recv_nonblocking(EP_CONTROL, data, length);
+ }
+ else if(resp == USB_CONTROL_STALL)
+ {
+ /* We should stall the OUT endpoint here, but the old code did
+ * not do so and some drivers may not handle it correctly. */
+ active_request = NULL;
+ usb_drv_stall(EP_CONTROL, true, true);
+ }
+ else
+ {
+ panicf("ACK on ctrl write data");
+ }
+ }
+ else
+ {
+ active_request = NULL;
+ control_write_data = NULL;
+ control_write_data_done = false;
+
+ /* Control write request, status phase */
+ if(resp == USB_CONTROL_ACK)
+ usb_drv_send(EP_CONTROL, NULL, 0);
+ else if(resp == USB_CONTROL_STALL)
+ usb_drv_stall(EP_CONTROL, true, true);
+ else
+ panicf("RECEIVE on ctrl write status");
+ }
+}
+#endif
+
void usb_core_notify_set_address(uint8_t addr)
{
logf("notify set addr received %ld", current_tick);
diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c
index 16dddb7db1..64aa123ced 100644
--- a/firmware/usbstack/usb_hid.c
+++ b/firmware/usbstack/usb_hid.c
@@ -664,10 +664,9 @@ void usb_hid_transfer_complete(int ep, int dir, int status, int length)
* In order to allow sending info to the DAP, the Set Report mechanism can be
* used by defining vendor specific output reports and send them from the host
* to the DAP using the host's custom driver */
-static int usb_hid_set_report(struct usb_ctrlrequest *req)
+static int usb_hid_set_report(struct usb_ctrlrequest *req, void *reqdata)
{
- static unsigned char buf[SET_REPORT_BUF_LEN] USB_DEVBSS_ATTR
- __attribute__((aligned(32)));
+ static unsigned char buf[64] USB_DEVBSS_ATTR __attribute__((aligned(32)));
int length;
if ((req->wValue >> 8) != REPORT_TYPE_OUTPUT)
@@ -692,8 +691,11 @@ static int usb_hid_set_report(struct usb_ctrlrequest *req)
return 4;
}
- memset(buf, 0, length);
- usb_drv_recv_nonblocking(EP_CONTROL, buf, length);
+ if(!reqdata) {
+ memset(buf, 0, length);
+ usb_drv_control_response(USB_CONTROL_RECEIVE, buf, length);
+ return 0;
+ }
#ifdef LOGF_ENABLE
if (buf[1] & 0x01)
@@ -710,10 +712,11 @@ static int usb_hid_set_report(struct usb_ctrlrequest *req)
/* Defining other LEDs and setting them from the USB host (OS) can be used
* to send messages to the DAP */
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
return 0;
}
-static int usb_hid_get_report(struct usb_ctrlrequest *req, unsigned char** dest)
+static int usb_hid_get_report(struct usb_ctrlrequest *req, unsigned char* dest)
{
if ((req->wValue >> 8) != REPORT_TYPE_FEATURE)
{
@@ -739,16 +742,17 @@ static int usb_hid_get_report(struct usb_ctrlrequest *req, unsigned char** dest)
return 4;
}
- (*dest)[0] = 0;
- (*dest)[1] = battery_level();
- *dest += GET_REPORT_BUF_LEN;
-
+ dest[0] = 0;
+ dest[1] = battery_level();
+ usb_drv_control_response(USB_CONTROL_ACK, dest, 2);
return 0;
}
/* called by usb_core_control_request() */
-bool usb_hid_control_request(struct usb_ctrlrequest *req, unsigned char *dest)
+bool usb_hid_control_request(struct usb_ctrlrequest *req, void *reqdata, unsigned char *dest)
{
+ (void)reqdata;
+
unsigned char *orig_dest = dest;
switch (req->bRequestType & USB_TYPE_MASK)
{
@@ -772,8 +776,7 @@ bool usb_hid_control_request(struct usb_ctrlrequest *req, unsigned char *dest)
if (dest != orig_dest)
{
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0); /* ack */
- usb_drv_send(EP_CONTROL, orig_dest, dest - orig_dest);
+ usb_drv_control_response(USB_CONTROL_ACK, orig_dest, dest - orig_dest);
return true;
}
break;
@@ -790,34 +793,21 @@ bool usb_hid_control_request(struct usb_ctrlrequest *req, unsigned char *dest)
switch (req->bRequest)
{
case USB_HID_SET_REPORT:
- rc = usb_hid_set_report(req);
+ rc = usb_hid_set_report(req, reqdata);
break;
case USB_HID_GET_REPORT:
- rc = usb_hid_get_report(req, &dest);
+ rc = usb_hid_get_report(req, dest);
break;
case USB_HID_SET_IDLE:
- rc = 0;
- break;
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
+ return true;
default:
/* all other requests are errors */
- rc = -1;
- break;
- }
-
- if(rc != 0)
- break;
-
- if (dest != orig_dest)
- {
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0); /* ack */
- usb_drv_send(EP_CONTROL, orig_dest, dest - orig_dest);
- }
- else
- {
- usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
+ return false;
}
- return true;
+ if(rc == 0)
+ return true;
}
case USB_TYPE_VENDOR:
diff --git a/firmware/usbstack/usb_hid.h b/firmware/usbstack/usb_hid.h
index 917992cd35..41e6662e29 100644
--- a/firmware/usbstack/usb_hid.h
+++ b/firmware/usbstack/usb_hid.h
@@ -32,7 +32,7 @@ void usb_hid_init_connection(void);
void usb_hid_init(void);
void usb_hid_disconnect(void);
void usb_hid_transfer_complete(int ep, int dir, int status, int length);
-bool usb_hid_control_request(struct usb_ctrlrequest* req, unsigned char* dest);
+bool usb_hid_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest);
void usb_hid_send(usage_page_t usage_page, int id);
diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c
index c96936f1d4..ae90b57078 100644
--- a/firmware/usbstack/usb_serial.c
+++ b/firmware/usbstack/usb_serial.c
@@ -174,7 +174,13 @@ static struct usb_endpoint_descriptor
.bInterval = 0
};
-static struct cdc_line_coding line_coding;
+union line_coding_buffer
+{
+ struct cdc_line_coding data;
+ unsigned char raw[64];
+};
+
+static union line_coding_buffer line_coding USB_DEVBSS_ATTR;
/* send_buffer: local ring buffer.
* transit_buffer: used to store aligned data that will be sent by the USB
@@ -184,10 +190,11 @@ static struct cdc_line_coding line_coding;
*/
#define BUFFER_SIZE 512
#define TRANSIT_BUFFER_SIZE 32
+#define RECV_BUFFER_SIZE 32
static unsigned char send_buffer[BUFFER_SIZE];
static unsigned char transit_buffer[TRANSIT_BUFFER_SIZE]
USB_DEVBSS_ATTR __attribute__((aligned(4)));
-static unsigned char receive_buffer[32]
+static unsigned char receive_buffer[512]
USB_DEVBSS_ATTR __attribute__((aligned(32)));
static void sendout(void);
@@ -277,11 +284,13 @@ int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size)
}
/* called by usb_core_control_request() */
-bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest)
+bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest)
{
bool handled = false;
(void)dest;
+ (void)reqdata;
+
if (req->wIndex != control_interface)
{
return false;
@@ -291,11 +300,19 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest
{
if (req->bRequest == SET_LINE_CODING)
{
- if (req->wLength == sizeof(line_coding))
+ if (req->wLength == sizeof(struct cdc_line_coding))
{
/* Receive line coding into local copy */
- usb_drv_recv_nonblocking(EP_CONTROL, &line_coding, sizeof(line_coding));
- usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
+ if (!reqdata)
+ {
+ usb_drv_control_response(USB_CONTROL_RECEIVE, line_coding.raw,
+ sizeof(struct cdc_line_coding));
+ }
+ else
+ {
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
+ }
+
handled = true;
}
}
@@ -304,7 +321,7 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest
if (req->wLength == 0)
{
/* wValue holds Control Signal Bitmap that is simply ignored here */
- usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
handled = true;
}
}
@@ -313,11 +330,11 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest
{
if (req->bRequest == GET_LINE_CODING)
{
- if (req->wLength == sizeof(line_coding))
+ if (req->wLength == sizeof(struct cdc_line_coding))
{
/* Send back line coding so host is happy */
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0); /* ack */
- usb_drv_send(EP_CONTROL, &line_coding, sizeof(line_coding));
+ usb_drv_control_response(USB_CONTROL_ACK, line_coding.raw,
+ sizeof(struct cdc_line_coding));
handled = true;
}
}
@@ -329,7 +346,7 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest
void usb_serial_init_connection(void)
{
/* prime rx endpoint */
- usb_drv_recv_nonblocking(ep_out, receive_buffer, sizeof receive_buffer);
+ usb_drv_recv_nonblocking(ep_out, receive_buffer, RECV_BUFFER_SIZE);
/* we come here too after a bus reset, so reset some data */
buffer_transitlength = 0;
@@ -420,7 +437,7 @@ void usb_serial_transfer_complete(int ep,int dir, int status, int length)
/* Data received. TODO : Do something with it ? */
/* Get the next bit */
- usb_drv_recv_nonblocking(ep_out, receive_buffer, sizeof receive_buffer);
+ usb_drv_recv_nonblocking(ep_out, receive_buffer, RECV_BUFFER_SIZE);
break;
case USB_DIR_IN:
diff --git a/firmware/usbstack/usb_serial.h b/firmware/usbstack/usb_serial.h
index f1a603c4a3..c4c0e78724 100644
--- a/firmware/usbstack/usb_serial.h
+++ b/firmware/usbstack/usb_serial.h
@@ -30,7 +30,7 @@ void usb_serial_init_connection(void);
void usb_serial_init(void);
void usb_serial_disconnect(void);
void usb_serial_transfer_complete(int ep,int dir, int status, int length);
-bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char *dest);
+bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char *dest);
void usb_serial_send(const unsigned char *data, int length);
diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c
index 947006c1ec..a32cf185e7 100644
--- a/firmware/usbstack/usb_storage.c
+++ b/firmware/usbstack/usb_storage.c
@@ -71,7 +71,7 @@
#endif /* USB_READ_BUFFER_SIZE */
/* We don't use sizeof() here, because we *need* a multiple of 32 */
-#define MAX_CBW_SIZE 32
+#define MAX_CBW_SIZE 512
#ifdef USB_WRITE_BUFFER_SIZE
#define WRITE_BUFFER_SIZE USB_WRITE_BUFFER_SIZE
@@ -589,17 +589,6 @@ void usb_storage_transfer_complete(int ep,int dir,int status,int length)
}
handle_scsi(cbw);
break;
-#if 0
- if(cur_cmd.cur_cmd == SCSI_WRITE_10)
- {
- queue_broadcast(SYS_USB_WRITE_DATA, (cur_cmd.lun<<16)+cur_cmd.orig_count);
- }
- else if(cur_cmd.cur_cmd == SCSI_READ_10)
- {
- queue_broadcast(SYS_USB_READ_DATA, (cur_cmd.lun<<16)+cur_cmd.orig_count);
- }
-#endif
- break;
case SENDING_RESULT:
if(dir==USB_DIR_OUT) {
logf("OUT received in SENDING");
@@ -673,11 +662,13 @@ void usb_storage_transfer_complete(int ep,int dir,int status,int length)
}
/* called by usb_core_control_request() */
-bool usb_storage_control_request(struct usb_ctrlrequest* req, unsigned char* dest)
+bool usb_storage_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest)
{
bool handled = false;
(void)dest;
+ (void)reqdata;
+
switch (req->bRequest) {
case USB_BULK_GET_MAX_LUN: {
*tb.max_lun = storage_num_drives() - 1;
@@ -685,8 +676,7 @@ bool usb_storage_control_request(struct usb_ctrlrequest* req, unsigned char* des
if(skip_first) (*tb.max_lun) --;
#endif
logf("ums: getmaxlun");
- usb_drv_recv_nonblocking(EP_CONTROL, NULL, 0); /* ack */
- usb_drv_send(EP_CONTROL, tb.max_lun, 1);
+ usb_drv_control_response(USB_CONTROL_ACK, tb.max_lun, 1);
handled = true;
break;
}
@@ -701,7 +691,7 @@ bool usb_storage_control_request(struct usb_ctrlrequest* req, unsigned char* des
usb_drv_reset_endpoint(ep_in, false);
usb_drv_reset_endpoint(ep_out, true);
#endif
- usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
+ usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
handled = true;
break;
}
diff --git a/firmware/usbstack/usb_storage.h b/firmware/usbstack/usb_storage.h
index 3591d285d8..af17689110 100644
--- a/firmware/usbstack/usb_storage.h
+++ b/firmware/usbstack/usb_storage.h
@@ -30,7 +30,7 @@ void usb_storage_init_connection(void);
void usb_storage_disconnect(void);
void usb_storage_init(void);
void usb_storage_transfer_complete(int ep,int dir,int state,int length);
-bool usb_storage_control_request(struct usb_ctrlrequest* req, unsigned char* dest);
+bool usb_storage_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest);
#ifdef HAVE_HOTSWAP
void usb_storage_notify_hotswap(int volume,bool inserted);
#endif
diff --git a/lib/microtar/.gitignore b/lib/microtar/.gitignore
new file mode 100644
index 0000000000..cccd994a85
--- /dev/null
+++ b/lib/microtar/.gitignore
@@ -0,0 +1,3 @@
+*.o
+*.a
+/mtar
diff --git a/lib/microtar/LICENSE b/lib/microtar/LICENSE
index 7e3bf17ccb..72cf80d63b 100644
--- a/lib/microtar/LICENSE
+++ b/lib/microtar/LICENSE
@@ -1,4 +1,5 @@
Copyright (c) 2017 rxi
+Copyright (c) 2021 Aidan MacDonald
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/lib/microtar/Makefile b/lib/microtar/Makefile
new file mode 100644
index 0000000000..ae2c4f6d6e
--- /dev/null
+++ b/lib/microtar/Makefile
@@ -0,0 +1,25 @@
+CPPFLAGS = -Isrc
+CFLAGS = -std=c99 -Wall -Wextra -O2
+
+MTAR_OBJ = mtar.o
+MTAR_BIN = mtar
+
+MICROTAR_OBJ = src/microtar.o src/microtar-stdio.o
+MICROTAR_LIB = libmicrotar.a
+
+$(MTAR_BIN): $(MTAR_OBJ) $(MICROTAR_LIB)
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
+
+$(MICROTAR_LIB): $(MICROTAR_OBJ)
+ $(AR) cr $@ $^
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+src/microtar.o: src/microtar.h
+src/microtar-stdio.o: src/microtar.h src/microtar-stdio.h
+mtar.o: src/microtar.h src/microtar-stdio.h
+
+clean:
+ rm -f $(MICROTAR_LIB) $(MICROTAR_OBJ)
+ rm -f $(MTAR_BIN) $(MTAR_OBJ)
diff --git a/lib/microtar/README.md b/lib/microtar/README.md
index 18153caa21..109626cdd4 100644
--- a/lib/microtar/README.md
+++ b/lib/microtar/README.md
@@ -1,128 +1,268 @@
# microtar
-A lightweight tar library written in ANSI C
+A lightweight tar library written in ANSI C.
-## Modifications from upstream
+This version is a fork of [rxi's microtar](https://github.com/rxi/microtar)
+with bugfixes and API changes aimed at improving usability, but still keeping
+with the minimal design of the original library.
-[Upstream](https://github.com/rxi/microtar) has numerous bugs and gotchas,
-which I fixed in order to improve the overall robustness of the library.
+## License
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the MIT license. See [LICENSE](LICENSE) for details.
-A summary of my changes, in no particular order:
-- Fix possible sscanf beyond the bounds of the input buffer
-- Fix possible buffer overruns due to strcpy on untrusted input
-- Fix incorrect octal formatting by sprintf and possible output overrruns
-- Catch read/writes which are too big and handle them gracefully
-- Handle over-long names in `mtar_write_file_header` / `mtar_write_dir_header`
-- Ensure strings in `mtar_header_t` are always null-terminated
-- Save and load group information so we don't lose information
-- Move `mtar_open()` to `microtar-stdio.c` so `microtar.c` can be used in
- a freestanding environment
-- Allow control of stack usage by moving temporary variables into `mtar_t`,
- so the caller can decide whether to use the stack or heap
+## Supported format variants
-An up-to-date copy of this modified version can be found
-[here](https://github.com/amachronic/microtar).
+No effort has been put into handling every tar format variant. Basically
+what is accepted is the "old-style" format, which appears to work well
+enough to access basic archives created by GNU `tar`.
-## Modifications for Rockbox
+## Basic usage
-Added file `microtar-rockbox.c` implementing `mtar_open()` with native
-Rockbox filesystem API.
+The library consists of two files, `microtar.c` and `microtar.h`, which only
+depend on a tiny part of the standard C library & can be easily incorporated
+into a host project's build system.
+The core library does not include any I/O hooks as these are supposed to be
+provided by the host application. If the C library's `fopen` and friends is
+good enough, you can use `microtar-stdio.c`.
-## Basic Usage
-The library consists of `microtar.c` and `microtar.h`. These two files can be
-dropped into an existing project and compiled along with it.
+### Initialization
+
+Initialization is very simple. Everything the library needs is contained in
+the `mtar_t` struct; there is no memory allocation and no global state. It is
+enough to zero-initialize an `mtar_t` object to put it into a "closed" state.
+You can use `mtar_is_open()` to query whether the archive is open or not.
+
+An archive can be opened for reading _or_ writing, but not both. You have to
+specify which access mode you're using when you create the archive.
-#### Reading
```c
mtar_t tar;
-mtar_header_t h;
-char *p;
+mtar_init(&tar, MTAR_READ, my_io_ops, my_stream);
+```
-/* Open archive for reading */
-mtar_open(&tar, "test.tar", "r");
+Or if using `microtar-stdio.c`:
-/* Print all file names and sizes */
-while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) {
- printf("%s (%d bytes)\n", h.name, h.size);
- mtar_next(&tar);
+```c
+int error = mtar_open(&tar, "file.tar", "rb");
+if(error) {
+ /* do something about it */
}
+```
-/* Load and print contents of file "test.txt" */
-mtar_find(&tar, "test.txt", &h);
-p = calloc(1, h.size + 1);
-mtar_read_data(&tar, p, h.size);
-printf("%s", p);
-free(p);
+Note that `mtar_init()` is called for you in this case and the access mode is
+deduced from the mode flags.
-/* Close archive */
-mtar_close(&tar);
-```
-#### Writing
+### Iterating and locating files
+
+If you opened an archive for reading, you'll likely want to iterate over
+all the files. Here's the long way of doing it:
+
```c
mtar_t tar;
-const char *str1 = "Hello world";
-const char *str2 = "Goodbye world";
+int err;
+
+/* Go to the start of the archive... Not necessary if you've
+ * just opened the archive and are already at the beginning.
+ * (And of course you normally want to check the return value.) */
+mtar_rewind(&tar);
-/* Open archive for writing */
-mtar_open(&tar, "test.tar", "w");
+/* Iterate over the archive members */
+while((err = mtar_next(&tar)) == MTAR_ESUCCESS) {
+ /* Get a pointer to the current file header. It will
+ * remain valid until you move to another record with
+ * mtar_next() or call mtar_rewind() */
+ const mtar_header_t* header = mtar_get_header(&tar);
-/* Write strings to files `test1.txt` and `test2.txt` */
-mtar_write_file_header(&tar, "test1.txt", strlen(str1));
-mtar_write_data(&tar, str1, strlen(str1));
-mtar_write_file_header(&tar, "test2.txt", strlen(str2));
-mtar_write_data(&tar, str2, strlen(str2));
+ printf("%s (%d bytes)\n", header->name, header->size);
+}
+
+if(err != MTAR_ENULLRECORD) {
+ /* ENULLRECORD means we hit end of file; any
+ * other return value is an actual error. */
+}
+```
-/* Finalize -- this needs to be the last thing done before closing */
-mtar_finalize(&tar);
+There's a useful shortcut for this type of iteration which removes the
+loop boilerplate, replacing it with another kind of boilerplate that may
+be more palatable in some cases.
+
+```c
+/* Will be called for each archive member visited by mtar_foreach().
+ * The member's header is passed in as an argument so you don't need
+ * to fetch it manually with mtar_get_header(). You can freely read
+ * data (if present) and seek around. There is no special cleanup
+ * required and it is not necessary to read to the end of the stream.
+ *
+ * The callback should return zero (= MTAR_SUCCESS) to continue the
+ * iteration or return nonzero to abort. On abort, the value returned
+ * by the callback will be returned from mtar_foreach(). Since it may
+ * also return normal microtar error codes, it is suggested to use a
+ * positive value or pass the result via 'arg'.
+ */
+int foreach_cb(mtar_t* tar, const mtar_header_t* header, void* arg)
+{
+ // ...
+ return 0;
+}
-/* Close archive */
-mtar_close(&tar);
+void main()
+{
+ mtar_t tar;
+
+ // ...
+
+ int ret = mtar_foreach(&tar, foreach_cb, NULL);
+ if(ret < 0) {
+ /* Microtar error codes are negative and may be returned if
+ * there is a problem with the iteration. */
+ } else if(ret == MTAR_ESUCCESS) {
+ /* If the iteration reaches the end of the archive without
+ * errors, the return code is MTAR_ESUCCESS. */
+ } else if(ret > 0) {
+ /* Positive values might be returned by the callback to
+ * signal some condition was met; they'll never be returned
+ * by microtar */
+ }
+}
+```
+
+The other thing you're likely to do is look for a specific file:
+
+```c
+/* Seek to a specific member in the archive */
+int err = mtar_find(&tar, "foo.txt");
+if(err == MTAR_ESUCCESS) {
+ /* File was found -- read the header with mtar_get_header() */
+} else if(err == MTAR_ENOTFOUND) {
+ /* File wasn't in the archive */
+} else {
+ /* Some error occurred */
+}
```
+Note this isn't terribly efficient since it scans the entire archive
+looking for the file.
+
+
+### Reading file data
+
+Once pointed at a file via `mtar_next()` or `mtar_find()` you can read the
+data with a simple POSIX-like API.
+
+- `mtar_read_data(tar, buf, count)` reads up to `count` bytes into `buf`,
+ returning the actual number of bytes read, or a negative error value.
+ If at EOF, this returns zero.
+
+- `mtar_seek_data(tar, offset, whence)` works exactly like `fseek()` with
+ `whence` being one of `SEEK_SET`, `SEEK_CUR`, or `SEEK_END` and `offset`
+ indicating a point relative to the beginning, current position, or end
+ of the file. Returns zero on success, or a negative error code.
+
+- `mtar_eof_data(tar)` returns nonzero if the end of the file has been
+ reached. It is possible to seek backward to clear this condition.
+
+
+### Writing archives
+
+Microtar has limited support for creating archives. When an archive is opened
+for writing, you can add new members using `mtar_write_header()`.
+
+- `mtar_write_header(tar, header)` writes out the header for a new member.
+ The amount of data that follows is dictated by `header->size`, though if
+ the underlying stream supports seeking and re-writing data, this size can
+ be updated later with `mtar_update_header()` or `mtar_update_file_size()`.
+
+- `mtar_update_header(tar, header)` will re-write the previously written
+ header. This may be used to change any header field. The underlying stream
+ must support seeking. On a successful return the stream will be returned
+ to the position it was at before the call.
+
+File data can be written with `mtar_write_data()`, and if the underlying stream
+supports seeking, you can seek with `mtar_seek_data()` and read back previously
+written data with `mtar_read_data()`. Note that it is not possible to truncate
+the file stream by any means.
+
+- `mtar_write_data(tar, buf, count)` will write up to `count` bytes from
+ `buf` to the current member's data. Returns the number of bytes actually
+ written or a negative error code.
+
+- `mtar_update_file_size(tar)` will update the header size to reflect the
+ actual amount of written data. This is intended to be called right before
+ `mtar_end_data()` if you are not declaring file sizes in advance.
+
+- `mtar_end_data(tar)` will end the current member. It will complain if you
+ did not write the correct amount data provided in the header. This must be
+ called before writing the next header.
+
+- `mtar_finalize(tar)` is called after you have written all members to the
+ archive. It writes out some null records which mark the end of the archive,
+ so you cannot write any more archive members after this.
+
+Note that `mtar_close()` can fail if there was a problem flushing buffered
+data to disk, so its return value should always be checked.
+
## Error handling
-All functions which return an `int` will return `MTAR_ESUCCESS` if the operation
-is successful. If an error occurs an error value less-than-zero will be
-returned; this value can be passed to the function `mtar_strerror()` to get its
-corresponding error string.
+Most functions that return `int` return an error code from `enum mtar_error`.
+Zero is success and all other error codes are negative. `mtar_strerror()` can
+return a string describing the error code.
-## Wrapping a stream
-If you want to read or write from something other than a file, the `mtar_t`
-struct can be manually initialized with your own callback functions and a
-`stream` pointer.
+A couple of functions use a different return value convention:
-All callback functions are passed a pointer to the `mtar_t` struct as their
-first argument. They should return `MTAR_ESUCCESS` if the operation succeeds
-without an error, or an integer below zero if an error occurs.
+- `mtar_foreach()` may error codes or an arbitrary nonzero value provided
+ by the callback.
+- `mtar_read_data()` and `mtar_write_data()` returns the number of bytes read
+ or written, or a negative error code. In particular zero means that no bytes
+ were read or written.
+- `mtar_get_header()` may return `NULL` if there is no valid header.
+ It is only possible to see a null pointer if misusing the API or after
+ a previous error so checking for this is usually not necessary.
-After the `stream` field has been set, all required callbacks have been set and
-all unused fields have been zeroset the `mtar_t` struct can be safely used with
-the microtar functions. `mtar_open` *should not* be called if the `mtar_t`
-struct was initialized manually.
+There is essentially no support for error recovery. After an error you can
+only do two things reliably: close the archive with `mtar_close()` or try
+rewinding to the beginning with `mtar_rewind()`.
-#### Reading
-The following callbacks should be set for reading an archive from a stream:
-Name | Arguments | Description
---------|------------------------------------------|---------------------------
-`read` | `mtar_t *tar, void *data, unsigned size` | Read data from the stream
-`seek` | `mtar_t *tar, unsigned pos` | Set the position indicator
-`close` | `mtar_t *tar` | Close the stream
+## I/O hooks
-#### Writing
-The following callbacks should be set for writing an archive to a stream:
+You can provide your own I/O hooks in a `mtar_ops_t` struct. The same ops
+struct can be shared among multiple `mtar_t` objects but each object gets
+its own `void* stream` pointer.
-Name | Arguments | Description
---------|------------------------------------------------|---------------------
-`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream
+Name | Arguments | Required
+--------|-------------------------------------------|------------
+`read` | `void* stream, void* data, unsigned size` | If reading
+`write` | `void* stream, void* data, unsigned size` | If writing
+`seek` | `void* stream, unsigned pos` | If reading
+`close` | `void* stream` | Always
+`read` and `write` should transfer the number of bytes indicated
+and return the number of bytes actually read or written, or a negative
+`enum mtar_error` code on error.
-## License
-This library is free software; you can redistribute it and/or modify it under
-the terms of the MIT license. See [LICENSE](LICENSE) for details.
+`seek` must have semantics like `lseek(..., pos, SEEK_SET)`; that is,
+the position is an absolute byte offset in the stream. Seeking is not
+optional for read support, but the library only performs backward
+seeks under two circumstances:
+
+- `mtar_rewind()` seeks to position 0.
+- `mtar_seek_data()` may seek backward if the user requests it.
+
+Therefore, you will be able to get away with a limited forward-only
+seek function if you're able to read everything in a single pass use
+the API carefully. Note `mtar_find()` and `mtar_foreach()` will call
+`mtar_rewind()`.
+
+`close` is called by `mtar_close()` to clean up the stream. Note the
+library assumes that the stream handle is cleaned up by `close` even
+if an error occurs.
+
+`seek` and `close` should return an `enum mtar_error` code, either
+`MTAR_SUCCESS`, or a negative value on error.
diff --git a/lib/microtar/README.rockbox b/lib/microtar/README.rockbox
new file mode 100644
index 0000000000..944f792996
--- /dev/null
+++ b/lib/microtar/README.rockbox
@@ -0,0 +1,14 @@
+Upstream commit/repo
+ 1e921369b2c92bb219fcef84a37d4d2347794c0f
+ https://github.com/amachronic/microtar
+
+Summary of Rockbox-specific changes:
+
+- microtar.make, SOURCES
+ Target build system integration.
+
+- microtar-rockbox.c, microtar-rockbox.h
+ Implements mtar ops with Rockbox filesystem API.
+
+- microtar.c
+ Define strncpy since Rockbox's libc doesn't define it.
diff --git a/lib/microtar/mtar.c b/lib/microtar/mtar.c
new file mode 100644
index 0000000000..4a9a27b782
--- /dev/null
+++ b/lib/microtar/mtar.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2021 Aidan MacDonald
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "microtar-stdio.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* exit codes */
+#define E_TAR 1
+#define E_FS 2
+#define E_OTHER 4
+#define E_ARGS 8
+
+enum {
+ OP_LIST,
+ OP_CREATE,
+ OP_EXTRACT,
+};
+
+void die(int err, const char* msg, ...)
+{
+ fprintf(stderr, "mtar: ");
+
+ va_list ap;
+ va_start(ap, msg);
+ vfprintf(stderr, msg, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+ exit(err);
+}
+
+int list_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
+{
+ (void)tar;
+ (void)arg;
+ printf("%s\n", h->name);
+ return 0;
+}
+
+void list_files(mtar_t* tar)
+{
+ int err = mtar_foreach(tar, list_foreach_cb, NULL);
+ if(err)
+ die(E_TAR, "listing failed: %s", mtar_strerror(err));
+}
+
+struct extract_args {
+ char** names;
+ int count;
+};
+
+int extract_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
+{
+ struct extract_args* args = arg;
+ (void)args; /* TODO */
+
+ if(h->type == MTAR_TDIR) {
+ if(mkdir(h->name, h->mode) != 0)
+ die(E_FS, "cannot create directory \"%s\"", h->name);
+ return 0;
+ }
+
+ if(h->type != MTAR_TREG) {
+ fprintf(stderr, "warning: not extracting unsupported type \"%s\"", h->name);
+ return 0;
+ }
+
+ int fd = open(h->name, O_CREAT|O_WRONLY|O_TRUNC, h->mode);
+ if(fd < 0)
+ die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
+
+ char iobuf[1024];
+ while(!mtar_eof_data(tar)) {
+ int rcount = mtar_read_data(tar, iobuf, sizeof(iobuf));
+ if(rcount < 0)
+ die(E_TAR, "extracting \"%s\" failed: %s", h->name, mtar_strerror(rcount));
+
+ int wcount = write(fd, iobuf, rcount);
+ if(wcount != rcount)
+ die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
+ }
+
+ close(fd);
+ return 0;
+}
+
+void extract_files(mtar_t* tar, char** files, int num_files)
+{
+ struct extract_args args;
+ args.names = files;
+ args.count = num_files;
+
+ int err = mtar_foreach(tar, extract_foreach_cb, &args);
+ if(err)
+ die(E_TAR, "extraction failed: %s", mtar_strerror(err));
+}
+
+void add_files(mtar_t* tar, char** files, int num_files)
+{
+ for(int i = 0; i < num_files; ++i) {
+ int fd = open(files[i], O_RDONLY);
+ if(fd < 0)
+ die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
+
+ off_t off = lseek(fd, 0, SEEK_END);
+ if(off < 0)
+ die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
+
+ unsigned filesize = off;
+ lseek(fd, 0, SEEK_SET);
+
+ int err = mtar_write_file_header(tar, files[i], filesize);
+ if(err)
+ die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
+
+ char iobuf[1024];
+ while(1) {
+ int rcount = read(fd, iobuf, sizeof(iobuf));
+ if(rcount < 0)
+ die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
+ if(rcount == 0)
+ break;
+
+ int wcount = mtar_write_data(tar, iobuf, rcount);
+ if(wcount < 0)
+ die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(wcount));
+ if(wcount != rcount)
+ die(E_TAR, "adding \"%s\" failed: write too short %d/%d", files[i], wcount, rcount);
+ }
+
+ close(fd);
+
+ err = mtar_end_data(tar);
+ if(err)
+ die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ ++argv, --argc;
+ if(argc == 0)
+ die(E_ARGS, "no input files");
+
+ if(!strcmp(*argv, "--help")) {
+ printf(
+"usage:\n"
+" mtar list tar-file\n"
+" List the members of the given tar archive, one filename per line.\n"
+"\n"
+" mtar create tar-file members...\n"
+" mtar add tar-file members...\n"
+" Create a new tar archive from the files listed on the command line.\n"
+" WARNING: Any existing file at tar-file will be overwritten!\n"
+"\n"
+" mtar extract tar-file [members...]\n"
+" Extract the contents of the tar archive to the current directory.\n"
+" If filenames are given, only the named members will be extracted.\n"
+"\n");
+ exit(E_ARGS);
+ }
+
+ int op;
+ if(!strcmp(*argv, "list"))
+ op = OP_LIST;
+ else if(!strcmp(*argv, "create"))
+ op = OP_CREATE;
+ else if(!strcmp(*argv, "extract"))
+ op = OP_EXTRACT;
+ else
+ die(E_ARGS, "invalid operation \"%s\"", *argv);
+ ++argv, --argc;
+
+ if(argc == 0)
+ die(E_ARGS, "missing archive name");
+ const char* archive_name = *argv;
+ ++argv, --argc;
+
+ if(op == OP_LIST && argc != 0)
+ die(E_ARGS, "excess arguments on command line");
+
+ const char* mode = "rb";
+ if(op == OP_CREATE)
+ mode = "wb";
+
+ mtar_t tar;
+ int err = mtar_open(&tar, archive_name, mode);
+ if(err)
+ die(E_TAR, "can't open archive: %s", mtar_strerror(err));
+
+ switch(op) {
+ case OP_LIST:
+ list_files(&tar);
+ break;
+
+ case OP_EXTRACT:
+ extract_files(&tar, argv, argc);
+ break;
+
+ case OP_CREATE:
+ add_files(&tar, argv, argc);
+ err = mtar_finalize(&tar);
+ if(err)
+ die(E_TAR, "failed to finalize archive: %s", mtar_strerror(err));
+ break;
+
+ default:
+ die(E_OTHER, "not implemented");
+ break;
+ }
+
+ err = mtar_close(&tar);
+ if(err)
+ die(E_TAR, "failed to close archive: %s", mtar_strerror(err));
+
+ return 0;
+}
diff --git a/lib/microtar/src/microtar-rockbox.c b/lib/microtar/src/microtar-rockbox.c
index 04ba93cc42..d0f579fefb 100644
--- a/lib/microtar/src/microtar-rockbox.c
+++ b/lib/microtar/src/microtar-rockbox.c
@@ -20,81 +20,72 @@
****************************************************************************/
#include "file.h"
-#include "microtar.h"
+#include "microtar-rockbox.h"
#include <stdint.h>
-static int fd_write(mtar_t *tar, const void *data, unsigned size) {
- intptr_t fd = (intptr_t)tar->stream;
- ssize_t res = write(fd, data, size);
- if(res < 0 || ((unsigned)res != size))
- return MTAR_EWRITEFAIL;
- else
- return MTAR_ESUCCESS;
-}
+static int fd_read(void* stream, void* data, unsigned size)
+{
+ ssize_t res = read((intptr_t)stream, data, size);
+ if(res < 0)
+ return MTAR_EREADFAIL;
-static int fd_read(mtar_t *tar, void *data, unsigned size) {
- intptr_t fd = (intptr_t)tar->stream;
- ssize_t res = read(fd, data, size);
- if(res < 0 || ((unsigned)res != size))
- return MTAR_EREADFAIL;
- else
- return MTAR_ESUCCESS;
+ return res;
}
-static int fd_seek(mtar_t *tar, unsigned offset) {
- intptr_t fd = (intptr_t)tar->stream;
- off_t res = lseek(fd, offset, SEEK_SET);
- if(res < 0 || ((unsigned)res != offset))
- return MTAR_ESEEKFAIL;
- else
- return MTAR_ESUCCESS;
-}
+static int fd_write(void* stream, const void* data, unsigned size)
+{
+ ssize_t res = write((intptr_t)stream, data, size);
+ if(res < 0)
+ return MTAR_EWRITEFAIL;
-static int fd_close(mtar_t *tar) {
- intptr_t fd = (intptr_t)tar->stream;
- close(fd);
- return MTAR_ESUCCESS;
+ return res;
}
+static int fd_seek(void* stream, unsigned offset)
+{
+ off_t res = lseek((intptr_t)stream, offset, SEEK_SET);
+ if(res < 0 || ((unsigned)res != offset))
+ return MTAR_ESEEKFAIL;
+ else
+ return MTAR_ESUCCESS;
+}
-int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
- int err;
- int openmode;
- int fd;
-
- /* Init tar struct and functions */
- memset(tar, 0, sizeof(*tar));
- tar->write = fd_write;
- tar->read = fd_read;
- tar->seek = fd_seek;
- tar->close = fd_close;
+static int fd_close(void* stream)
+{
+ close((intptr_t)stream);
+ return MTAR_ESUCCESS;
+}
- /* Get correct mode flags */
- if ( strchr(mode, 'r') )
- openmode = O_RDONLY;
- else if ( strchr(mode, 'w') )
- openmode = O_CREAT|O_TRUNC|O_WRONLY;
- else if ( strchr(mode, 'a') )
- openmode = O_WRONLY|O_APPEND;
- else
- return MTAR_EFAILURE;
+static const mtar_ops_t fd_ops = {
+ .read = fd_read,
+ .write = fd_write,
+ .seek = fd_seek,
+ .close = fd_close,
+};
- /* Open file */
- fd = open(filename, openmode);
- if(fd < 0)
- return MTAR_EOPENFAIL;
+int mtar_open(mtar_t* tar, const char* filename, int flags)
+{
+ /* Determine access mode */
+ int access;
+ switch(flags & O_ACCMODE) {
+ case O_RDONLY:
+ access = MTAR_READ;
+ break;
- tar->stream = (void*)(intptr_t)fd;
+ case O_WRONLY:
+ access = MTAR_WRITE;
+ break;
- /* Read first header to check it is valid if mode is `r` */
- if ( openmode & O_RDONLY ) {
- err = mtar_read_header(tar, &tar->header);
- if (err != MTAR_ESUCCESS) {
- mtar_close(tar);
- return err;
+ default:
+ /* Note - O_RDWR isn't very useful so we don't allow it */
+ return MTAR_EAPI;
}
- }
- /* Return ok */
- return MTAR_ESUCCESS;
+ int fd = open(filename, flags);
+ if(fd < 0)
+ return MTAR_EOPENFAIL;
+
+ /* Init tar struct and functions */
+ mtar_init(tar, access, &fd_ops, (void*)(intptr_t)fd);
+ return MTAR_ESUCCESS;
}
diff --git a/lib/microtar/src/microtar-rockbox.h b/lib/microtar/src/microtar-rockbox.h
new file mode 100644
index 0000000000..36acbb2bfe
--- /dev/null
+++ b/lib/microtar/src/microtar-rockbox.h
@@ -0,0 +1,29 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2021 Aidan MacDonald
+ *
+ * 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 MICROTAR_ROCKBOX_H
+#define MICROTAR_ROCKBOX_H
+
+#include "microtar.h"
+
+int mtar_open(mtar_t* tar, const char* filename, int flags);
+
+#endif
diff --git a/lib/microtar/src/microtar-stdio.c b/lib/microtar/src/microtar-stdio.c
index 215000aa98..2697d005cd 100644
--- a/lib/microtar/src/microtar-stdio.c
+++ b/lib/microtar/src/microtar-stdio.c
@@ -1,65 +1,88 @@
-/**
+/*
* Copyright (c) 2017 rxi
+ * Copyright (c) 2021 Aidan MacDonald
*
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the MIT license. See `microtar.c` for details.
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
*/
+#include "microtar-stdio.h"
#include <stdio.h>
#include <string.h>
-#include "microtar.h"
+static int file_read(void* stream, void* data, unsigned size)
+{
+ unsigned res = fread(data, 1, size, (FILE*)stream);
+ if(res != size && ferror((FILE*)stream))
+ return MTAR_EREADFAIL;
-static int file_write(mtar_t *tar, const void *data, unsigned size) {
- unsigned res = fwrite(data, 1, size, tar->stream);
- return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
+ return res;
}
-static int file_read(mtar_t *tar, void *data, unsigned size) {
- unsigned res = fread(data, 1, size, tar->stream);
- return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL;
-}
+static int file_write(void* stream, const void* data, unsigned size)
+{
+ unsigned res = fwrite(data, 1, size, (FILE*)stream);
+ if(res != size && ferror((FILE*)stream))
+ return MTAR_EWRITEFAIL;
-static int file_seek(mtar_t *tar, unsigned offset) {
- int res = fseek(tar->stream, offset, SEEK_SET);
- return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
+ return res;
}
-static int file_close(mtar_t *tar) {
- fclose(tar->stream);
- return MTAR_ESUCCESS;
+static int file_seek(void* stream, unsigned offset)
+{
+ int res = fseek((FILE*)stream, offset, SEEK_SET);
+ return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
}
+static int file_close(void* stream)
+{
+ int err = fclose((FILE*)stream);
+ return (err == 0 ? MTAR_ESUCCESS : MTAR_EFAILURE);
+}
-int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
- int err;
- mtar_header_t h;
-
- /* Init tar struct and functions */
- memset(tar, 0, sizeof(*tar));
- tar->write = file_write;
- tar->read = file_read;
- tar->seek = file_seek;
- tar->close = file_close;
+static const mtar_ops_t file_ops = {
+ .read = file_read,
+ .write = file_write,
+ .seek = file_seek,
+ .close = file_close,
+};
- /* Assure mode is always binary */
- if ( strchr(mode, 'r') ) mode = "rb";
- if ( strchr(mode, 'w') ) mode = "wb";
- if ( strchr(mode, 'a') ) mode = "ab";
- /* Open file */
- tar->stream = fopen(filename, mode);
- if (!tar->stream) {
- return MTAR_EOPENFAIL;
- }
- /* Read first header to check it is valid if mode is `r` */
- if (*mode == 'r') {
- err = mtar_read_header(tar, &h);
- if (err != MTAR_ESUCCESS) {
- mtar_close(tar);
- return err;
+int mtar_open(mtar_t* tar, const char* filename, const char* mode)
+{
+ /* Determine access mode */
+ int access;
+ char* read = strchr(mode, 'r');
+ char* write = strchr(mode, 'w');
+ if(read) {
+ if(write)
+ return MTAR_EAPI;
+ access = MTAR_READ;
+ } else if(write) {
+ access = MTAR_WRITE;
+ } else {
+ return MTAR_EAPI;
}
- }
- /* Return ok */
- return MTAR_ESUCCESS;
+ /* Open file */
+ FILE* file = fopen(filename, mode);
+ if(!file)
+ return MTAR_EOPENFAIL;
+
+ mtar_init(tar, access, &file_ops, file);
+ return MTAR_ESUCCESS;
}
diff --git a/lib/microtar/src/microtar-stdio.h b/lib/microtar/src/microtar-stdio.h
new file mode 100644
index 0000000000..ad46af0390
--- /dev/null
+++ b/lib/microtar/src/microtar-stdio.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017 rxi
+ * Copyright (c) 2021 Aidan MacDonald
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MICROTAR_STDIO_H
+#define MICROTAR_STDIO_H
+
+#include "microtar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int mtar_open(mtar_t* tar, const char* filename, const char* mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/microtar/src/microtar.c b/lib/microtar/src/microtar.c
index 04cd4ea132..8ab9a8c5f4 100644
--- a/lib/microtar/src/microtar.c
+++ b/lib/microtar/src/microtar.c
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2017 rxi
+ * Copyright (c) 2021 Aidan MacDonald
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,16 +21,14 @@
* IN THE SOFTWARE.
*/
-#include <stdlib.h>
-#include <stddef.h>
+#include "microtar.h"
#include <limits.h>
#include <string.h>
-#include "microtar.h"
-
#ifdef ROCKBOX
/* Rockbox lacks strncpy in its libc */
-static char* strncpy(char* dest, const char* src, size_t n) {
+#define strncpy my_strncpy
+static char* my_strncpy(char* dest, const char* src, size_t n) {
size_t i;
for(i = 0; i < n && *src; ++i)
@@ -42,362 +41,685 @@ static char* strncpy(char* dest, const char* src, size_t n) {
}
#endif
+enum {
+ S_HEADER_VALID = 1 << 0,
+ S_WROTE_HEADER = 1 << 1,
+ S_WROTE_DATA = 1 << 2,
+ S_WROTE_DATA_EOF = 1 << 3,
+ S_WROTE_FINALIZE = 1 << 4,
+};
+
+enum {
+ NAME_OFF = 0, NAME_LEN = 100,
+ MODE_OFF = NAME_OFF+NAME_LEN, MODE_LEN = 8,
+ OWNER_OFF = MODE_OFF+MODE_LEN, OWNER_LEN = 8,
+ GROUP_OFF = OWNER_OFF+OWNER_LEN, GROUP_LEN = 8,
+ SIZE_OFF = GROUP_OFF+GROUP_LEN, SIZE_LEN = 12,
+ MTIME_OFF = SIZE_OFF+SIZE_LEN, MTIME_LEN = 12,
+ CHKSUM_OFF = MTIME_OFF+MTIME_LEN, CHKSUM_LEN = 8,
+ TYPE_OFF = CHKSUM_OFF+CHKSUM_LEN,
+ LINKNAME_OFF = TYPE_OFF+1, LINKNAME_LEN = 100,
+
+ HEADER_LEN = 512,
+};
+
+static int parse_octal(const char* str, size_t len, unsigned* ret)
+{
+ unsigned n = 0;
+
+ while(len-- > 0 && *str != 0) {
+ if(*str < '0' || *str > '9')
+ return MTAR_EOVERFLOW;
+
+ if(n > UINT_MAX/8)
+ return MTAR_EOVERFLOW;
+ else
+ n *= 8;
+
+ char r = *str++ - '0';
+
+ if(n > UINT_MAX - r)
+ return MTAR_EOVERFLOW;
+ else
+ n += r;
+ }
-static int parse_octal(const char* str, size_t len, unsigned* ret) {
- unsigned n = 0;
- while(len-- > 0 && *str != 0) {
- if(*str < '0' || *str > '9')
- return MTAR_EOVERFLOW;
-
- if(n > UINT_MAX/8)
- return MTAR_EOVERFLOW;
- else
- n *= 8;
-
- char r = *str++ - '0';
- if(n > UINT_MAX - r)
- return MTAR_EOVERFLOW;
- else
- n += r;
- }
-
- *ret = n;
- return MTAR_ESUCCESS;
+ *ret = n;
+ return MTAR_ESUCCESS;
}
+static int print_octal(char* str, size_t len, unsigned value)
+{
+ /* move backwards over the output string */
+ char* ptr = str + len - 1;
+ *ptr = 0;
-static int print_octal(char* str, size_t len, unsigned value) {
- /* move backwards over the output string */
- char* ptr = str + len - 1;
- *ptr = 0;
+ /* output the significant digits */
+ while(value > 0) {
+ if(ptr == str)
+ return MTAR_EOVERFLOW;
- /* output the significant digits */
- while(value > 0) {
- if(ptr == str)
- return MTAR_EOVERFLOW;
+ --ptr;
+ *ptr = '0' + (value % 8);
+ value /= 8;
+ }
- --ptr;
- *ptr = '0' + (value % 8);
- value /= 8;
- }
+ /* pad the remainder of the field with zeros */
+ while(ptr != str) {
+ --ptr;
+ *ptr = '0';
+ }
- /* pad the remainder of the field with zeros */
- while(ptr != str) {
- --ptr;
- *ptr = '0';
- }
+ return MTAR_ESUCCESS;
+}
- return MTAR_ESUCCESS;
+static unsigned round_up_512(unsigned n)
+{
+ return (n + 511u) & ~511u;
}
+static int tread(mtar_t* tar, void* data, unsigned size)
+{
+ int ret = tar->ops->read(tar->stream, data, size);
+ if(ret >= 0)
+ tar->pos += ret;
-static unsigned round_up(unsigned n, unsigned incr) {
- return n + (incr - n % incr) % incr;
+ return ret;
}
+static int twrite(mtar_t* tar, const void* data, unsigned size)
+{
+ int ret = tar->ops->write(tar->stream, data, size);
+ if(ret >= 0)
+ tar->pos += ret;
-static unsigned checksum(const mtar_raw_header_t* rh) {
- unsigned i;
- unsigned char *p = (unsigned char*) rh;
- unsigned res = 256;
- for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) {
- res += p[i];
- }
- for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) {
- res += p[i];
- }
- return res;
+ return ret;
}
-
-static int tread(mtar_t *tar, void *data, unsigned size) {
- int err = tar->read(tar, data, size);
- tar->pos += size;
- return err;
+static int tseek(mtar_t* tar, unsigned pos)
+{
+ int err = tar->ops->seek(tar->stream, pos);
+ tar->pos = pos;
+ return err;
}
+static int write_null_bytes(mtar_t* tar, size_t count)
+{
+ int ret;
+ size_t n;
+
+ memset(tar->buffer, 0, sizeof(tar->buffer));
+ while(count > 0) {
+ n = count < sizeof(tar->buffer) ? count : sizeof(tar->buffer);
+ ret = twrite(tar, tar->buffer, n);
+ if(ret < 0)
+ return ret;
+ if(ret != (int)n)
+ return MTAR_EWRITEFAIL;
+
+ count -= n;
+ }
-static int twrite(mtar_t *tar, const void *data, unsigned size) {
- int err = tar->write(tar, data, size);
- tar->pos += size;
- return err;
+ return MTAR_ESUCCESS;
}
+static unsigned checksum(const char* raw)
+{
+ unsigned i;
+ unsigned char* p = (unsigned char*)raw;
+ unsigned res = 256;
-static int write_null_bytes(mtar_t *tar, int n) {
- int i, err;
- char nul = '\0';
- for (i = 0; i < n; i++) {
- err = twrite(tar, &nul, 1);
- if (err) {
- return err;
- }
- }
- return MTAR_ESUCCESS;
+ for(i = 0; i < CHKSUM_OFF; i++)
+ res += p[i];
+ for(i = TYPE_OFF; i < HEADER_LEN; i++)
+ res += p[i];
+
+ return res;
}
+static int raw_to_header(mtar_header_t* h, const char* raw)
+{
+ unsigned chksum;
+ int rc;
+
+ /* If the checksum starts with a null byte we assume the record is NULL */
+ if(raw[CHKSUM_OFF] == '\0')
+ return MTAR_ENULLRECORD;
+
+ /* Compare the checksum */
+ if((rc = parse_octal(&raw[CHKSUM_OFF], CHKSUM_LEN, &chksum)))
+ return rc;
+ if(chksum != checksum(raw))
+ return MTAR_EBADCHKSUM;
+
+ /* Load raw header into header */
+ if((rc = parse_octal(&raw[MODE_OFF], MODE_LEN, &h->mode)))
+ return rc;
+ if((rc = parse_octal(&raw[OWNER_OFF], OWNER_LEN, &h->owner)))
+ return rc;
+ if((rc = parse_octal(&raw[GROUP_OFF], GROUP_LEN, &h->group)))
+ return rc;
+ if((rc = parse_octal(&raw[SIZE_OFF], SIZE_LEN, &h->size)))
+ return rc;
+ if((rc = parse_octal(&raw[MTIME_OFF], MTIME_LEN, &h->mtime)))
+ return rc;
+
+ h->type = raw[TYPE_OFF];
+ if(!h->type)
+ h->type = MTAR_TREG;
+
+ memcpy(h->name, &raw[NAME_OFF], NAME_LEN);
+ h->name[sizeof(h->name) - 1] = 0;
+
+ memcpy(h->linkname, &raw[LINKNAME_OFF], LINKNAME_LEN);
+ h->linkname[sizeof(h->linkname) - 1] = 0;
+
+ return MTAR_ESUCCESS;
+}
-static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
- unsigned chksum1, chksum2;
- int rc;
+static int header_to_raw(char* raw, const mtar_header_t* h)
+{
+ unsigned chksum;
+ int rc;
+
+ memset(raw, 0, HEADER_LEN);
+
+ /* Load header into raw header */
+ if((rc = print_octal(&raw[MODE_OFF], MODE_LEN, h->mode)))
+ return rc;
+ if((rc = print_octal(&raw[OWNER_OFF], OWNER_LEN, h->owner)))
+ return rc;
+ if((rc = print_octal(&raw[GROUP_OFF], GROUP_LEN, h->group)))
+ return rc;
+ if((rc = print_octal(&raw[SIZE_OFF], SIZE_LEN, h->size)))
+ return rc;
+ if((rc = print_octal(&raw[MTIME_OFF], MTIME_LEN, h->mtime)))
+ return rc;
+
+ raw[TYPE_OFF] = h->type ? h->type : MTAR_TREG;
+
+#if defined(__GNUC__) && (__GNUC__ >= 8)
+/* Sigh. GCC wrongly assumes the output of strncpy() is supposed to be
+ * a null-terminated string -- which it is not, and we are relying on
+ * that fact here -- and tries to warn about 'string truncation' because
+ * the null terminator might not be copied. Just suppress the warning. */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstringop-truncation"
+#endif
- /* If the checksum starts with a null byte we assume the record is NULL */
- if (*rh->checksum == '\0') {
- return MTAR_ENULLRECORD;
- }
+ strncpy(&raw[NAME_OFF], h->name, NAME_LEN);
+ strncpy(&raw[LINKNAME_OFF], h->linkname, LINKNAME_LEN);
- /* Build and compare checksum */
- chksum1 = checksum(rh);
- if((rc = parse_octal(rh->checksum, sizeof(rh->checksum), &chksum2)))
- return rc;
- if (chksum1 != chksum2) {
- return MTAR_EBADCHKSUM;
- }
+#if defined(__GNUC__) && (__GNUC__ >= 8)
+# pragma GCC diagnostic pop
+#endif
- /* Load raw header into header */
- if((rc = parse_octal(rh->mode, sizeof(rh->mode), &h->mode)))
- return rc;
- if((rc = parse_octal(rh->owner, sizeof(rh->owner), &h->owner)))
- return rc;
- if((rc = parse_octal(rh->group, sizeof(rh->group), &h->group)))
- return rc;
- if((rc = parse_octal(rh->size, sizeof(rh->size), &h->size)))
- return rc;
- if((rc = parse_octal(rh->mtime, sizeof(rh->mtime), &h->mtime)))
- return rc;
+ /* Calculate and write checksum */
+ chksum = checksum(raw);
+ if((rc = print_octal(&raw[CHKSUM_OFF], CHKSUM_LEN-1, chksum)))
+ return rc;
- h->type = rh->type;
+ raw[CHKSUM_OFF + CHKSUM_LEN - 1] = ' ';
- memcpy(h->name, rh->name, sizeof(rh->name));
- h->name[sizeof(h->name) - 1] = 0;
+ return MTAR_ESUCCESS;
+}
- memcpy(h->linkname, rh->linkname, sizeof(rh->linkname));
- h->linkname[sizeof(h->linkname) - 1] = 0;
+static unsigned data_beg_pos(const mtar_t* tar)
+{
+ return tar->header_pos + HEADER_LEN;
+}
- return MTAR_ESUCCESS;
+static unsigned data_end_pos(const mtar_t* tar)
+{
+ return tar->end_pos;
}
+static int ensure_header(mtar_t* tar)
+{
+ int ret, err;
-static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) {
- unsigned chksum;
- int rc;
+ if(tar->state & S_HEADER_VALID)
+ return MTAR_ESUCCESS;
- /* Load header into raw header */
- memset(rh, 0, sizeof(*rh));
- if((rc = print_octal(rh->mode, sizeof(rh->mode), h->mode)))
- return rc;
- if((rc = print_octal(rh->owner, sizeof(rh->owner), h->owner)))
- return rc;
- if((rc = print_octal(rh->group, sizeof(rh->group), h->group)))
- return rc;
- if((rc = print_octal(rh->size, sizeof(rh->size), h->size)))
- return rc;
- if((rc = print_octal(rh->mtime, sizeof(rh->mtime), h->mtime)))
- return rc;
- rh->type = h->type ? h->type : MTAR_TREG;
- strncpy(rh->name, h->name, sizeof(rh->name));
- strncpy(rh->linkname, h->linkname, sizeof(rh->linkname));
+ if(tar->pos > UINT_MAX - HEADER_LEN)
+ return MTAR_EOVERFLOW;
- /* Calculate and write checksum */
- chksum = checksum(rh);
- if((rc = print_octal(rh->checksum, 7, chksum)))
- return rc;
- rh->checksum[7] = ' ';
+ tar->header_pos = tar->pos;
+ tar->end_pos = data_beg_pos(tar);
- return MTAR_ESUCCESS;
-}
+ ret = tread(tar, tar->buffer, HEADER_LEN);
+ if(ret < 0)
+ return ret;
+ if(ret != HEADER_LEN)
+ return MTAR_EREADFAIL;
+ err = raw_to_header(&tar->header, tar->buffer);
+ if(err)
+ return err;
-const char* mtar_strerror(int err) {
- switch (err) {
- case MTAR_ESUCCESS : return "success";
- case MTAR_EFAILURE : return "failure";
- case MTAR_EOPENFAIL : return "could not open";
- case MTAR_EREADFAIL : return "could not read";
- case MTAR_EWRITEFAIL : return "could not write";
- case MTAR_ESEEKFAIL : return "could not seek";
- case MTAR_EBADCHKSUM : return "bad checksum";
- case MTAR_ENULLRECORD : return "null record";
- case MTAR_ENOTFOUND : return "file not found";
- case MTAR_EOVERFLOW : return "overflow";
- }
- return "unknown error";
+ if(tar->end_pos > UINT_MAX - tar->header.size)
+ return MTAR_EOVERFLOW;
+ tar->end_pos += tar->header.size;
+
+ tar->state |= S_HEADER_VALID;
+ return MTAR_ESUCCESS;
}
+const char* mtar_strerror(int err)
+{
+ switch(err) {
+ case MTAR_ESUCCESS: return "success";
+ case MTAR_EFAILURE: return "failure";
+ case MTAR_EOPENFAIL: return "could not open";
+ case MTAR_EREADFAIL: return "could not read";
+ case MTAR_EWRITEFAIL: return "could not write";
+ case MTAR_ESEEKFAIL: return "could not seek";
+ case MTAR_ESEEKRANGE: return "seek out of bounds";
+ case MTAR_EBADCHKSUM: return "bad checksum";
+ case MTAR_ENULLRECORD: return "null record";
+ case MTAR_ENOTFOUND: return "file not found";
+ case MTAR_EOVERFLOW: return "overflow";
+ case MTAR_EAPI: return "API usage error";
+ case MTAR_ENAMETOOLONG: return "name too long";
+ case MTAR_EWRONGSIZE: return "wrong amount of data written";
+ case MTAR_EACCESS: return "wrong access mode";
+ default: return "unknown error";
+ }
+}
-int mtar_close(mtar_t *tar) {
- return tar->close(tar);
+void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream)
+{
+ memset(tar, 0, sizeof(mtar_t));
+ tar->access = access;
+ tar->ops = ops;
+ tar->stream = stream;
}
+int mtar_close(mtar_t* tar)
+{
+ int err = tar->ops->close(tar->stream);
+ tar->ops = NULL;
+ tar->stream = NULL;
+ return err;
+}
-int mtar_seek(mtar_t *tar, unsigned pos) {
- int err = tar->seek(tar, pos);
- tar->pos = pos;
- return err;
+int mtar_is_open(mtar_t* tar)
+{
+ return (tar->ops != NULL) ? 1 : 0;
}
+mtar_header_t* mtar_get_header(mtar_t* tar)
+{
+ if(tar->state & S_HEADER_VALID)
+ return &tar->header;
+ else
+ return NULL;
+}
-int mtar_rewind(mtar_t *tar) {
- tar->remaining_data = 0;
- tar->last_header = 0;
- return mtar_seek(tar, 0);
+int mtar_access_mode(const mtar_t* tar)
+{
+ return tar->access;
}
+int mtar_rewind(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_READ)
+ return MTAR_EAPI;
+#endif
-int mtar_next(mtar_t *tar) {
- int err, n;
- /* Load header */
- err = mtar_read_header(tar, &tar->header);
- if (err) {
+ int err = tseek(tar, 0);
+ tar->state = 0;
return err;
- }
- /* Seek to next record */
- n = round_up(tar->header.size, 512) + sizeof(mtar_raw_header_t);
- return mtar_seek(tar, tar->pos + n);
}
+int mtar_next(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_READ)
+ return MTAR_EACCESS;
+#endif
-int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) {
- int err;
- /* Start at beginning */
- err = mtar_rewind(tar);
- if (err) {
- return err;
- }
- /* Iterate all files until we hit an error or find the file */
- while ( (err = mtar_read_header(tar, &tar->header)) == MTAR_ESUCCESS ) {
- if ( !strcmp(tar->header.name, name) ) {
- if (h) {
- *h = tar->header;
- }
- return MTAR_ESUCCESS;
- }
- err = mtar_next(tar);
- if (err) {
- return err;
+ if(tar->state & S_HEADER_VALID) {
+ tar->state &= ~S_HEADER_VALID;
+
+ /* seek to the next header */
+ int err = tseek(tar, round_up_512(data_end_pos(tar)));
+ if(err)
+ return err;
}
- }
- /* Return error */
- if (err == MTAR_ENULLRECORD) {
- err = MTAR_ENOTFOUND;
- }
- return err;
-}
-
-
-int mtar_read_header(mtar_t *tar, mtar_header_t *h) {
- int err;
- /* Save header position */
- tar->last_header = tar->pos;
- /* Read raw header */
- err = tread(tar, &tar->raw_header, sizeof(tar->raw_header));
- if (err) {
+
+ return ensure_header(tar);
+}
+
+int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_READ)
+ return MTAR_EACCESS;
+#endif
+
+ int err = mtar_rewind(tar);
+ if(err)
+ return err;
+
+ while((err = mtar_next(tar)) == MTAR_ESUCCESS)
+ if((err = cb(tar, &tar->header, arg)) != 0)
+ return err;
+
+ if(err == MTAR_ENULLRECORD)
+ err = MTAR_ESUCCESS;
+
return err;
- }
- /* Seek back to start of header */
- err = mtar_seek(tar, tar->last_header);
- if (err) {
+}
+
+static int find_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
+{
+ (void)tar;
+ const char* name = (const char*)arg;
+ return strcmp(name, h->name) ? 0 : 1;
+}
+
+int mtar_find(mtar_t* tar, const char* name)
+{
+ int err = mtar_foreach(tar, find_foreach_cb, (void*)name);
+ if(err == 1)
+ err = MTAR_ESUCCESS;
+ else if(err == MTAR_ESUCCESS)
+ err = MTAR_ENOTFOUND;
+
return err;
- }
- /* Load raw header into header struct and return */
- return raw_to_header(h, &tar->raw_header);
}
+int mtar_read_data(mtar_t* tar, void* ptr, unsigned size)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(!(tar->state & S_HEADER_VALID))
+ return MTAR_EAPI;
+#endif
-int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) {
- int err;
- /* If we have no remaining data then this is the first read, we get the size,
- * set the remaining data and seek to the beginning of the data */
- if (tar->remaining_data == 0) {
- /* Read header */
- err = mtar_read_header(tar, &tar->header);
- if (err) {
- return err;
- }
- /* Seek past header and init remaining data */
- err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t));
- if (err) {
- return err;
+ /* have we reached end of file? */
+ unsigned data_end = data_end_pos(tar);
+ if(tar->pos >= data_end)
+ return 0;
+
+ /* truncate the read if it would go beyond EOF */
+ unsigned data_left = data_end - tar->pos;
+ if(data_left < size)
+ size = data_left;
+
+ return tread(tar, ptr, size);
+}
+
+int mtar_seek_data(mtar_t* tar, int offset, int whence)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(!(tar->state & S_HEADER_VALID))
+ return MTAR_EAPI;
+#endif
+
+ unsigned data_beg = data_beg_pos(tar);
+ unsigned data_end = data_end_pos(tar);
+ unsigned newpos;
+
+ switch(whence) {
+ case SEEK_SET:
+ if(offset < 0)
+ return MTAR_ESEEKRANGE;
+
+ newpos = data_beg + offset;
+ break;
+
+ case SEEK_CUR:
+ if((offset > 0 && (unsigned) offset > data_end - tar->pos) ||
+ (offset < 0 && (unsigned)-offset > tar->pos - data_beg))
+ return MTAR_ESEEKRANGE;
+
+ newpos = tar->pos + offset;
+ break;
+
+ case SEEK_END:
+ if(offset > 0)
+ return MTAR_ESEEKRANGE;
+
+ newpos = data_end + offset;
+ break;
+
+ default:
+ return MTAR_EAPI;
}
- tar->remaining_data = tar->header.size;
- }
- /* Ensure caller does not read too much */
- if(size > tar->remaining_data)
- return MTAR_EOVERFLOW;
- /* Read data */
- err = tread(tar, ptr, size);
- if (err) {
- return err;
- }
- tar->remaining_data -= size;
- /* If there is no remaining data we've finished reading and seek back to the
- * header */
- if (tar->remaining_data == 0) {
- return mtar_seek(tar, tar->last_header);
- }
- return MTAR_ESUCCESS;
+
+ return tseek(tar, newpos);
+}
+
+unsigned mtar_tell_data(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(!(tar->state & S_HEADER_VALID))
+ return MTAR_EAPI;
+#endif
+
+ return tar->pos - data_beg_pos(tar);
}
+int mtar_eof_data(mtar_t* tar)
+{
+ /* API usage error, but just claim EOF. */
+ if(!(tar->state & S_HEADER_VALID))
+ return 1;
-int mtar_write_header(mtar_t *tar, const mtar_header_t *h) {
- /* Build raw header and write */
- header_to_raw(&tar->raw_header, h);
- tar->remaining_data = h->size;
- return twrite(tar, &tar->raw_header, sizeof(tar->raw_header));
+ return tar->pos >= data_end_pos(tar) ? 1 : 0;
}
+int mtar_write_header(mtar_t* tar, const mtar_header_t* h)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ tar->state &= ~(S_HEADER_VALID | S_WROTE_HEADER |
+ S_WROTE_DATA | S_WROTE_DATA_EOF);
+
+ /* ensure we have enough space to write the declared amount of data */
+ if(tar->pos > UINT_MAX - HEADER_LEN - round_up_512(h->size))
+ return MTAR_EOVERFLOW;
+
+ tar->header_pos = tar->pos;
+ tar->end_pos = data_beg_pos(tar);
+
+ if(h != &tar->header)
+ tar->header = *h;
+
+ int err = header_to_raw(tar->buffer, &tar->header);
+ if(err)
+ return err;
+
+ int ret = twrite(tar, tar->buffer, HEADER_LEN);
+ if(ret < 0)
+ return ret;
+ if(ret != HEADER_LEN)
+ return MTAR_EWRITEFAIL;
-int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) {
- /* Build header */
- memset(&tar->header, 0, sizeof(tar->header));
- /* Ensure name fits within header */
- if(strlen(name) > sizeof(tar->header.name))
- return MTAR_EOVERFLOW;
- strncpy(tar->header.name, name, sizeof(tar->header.name));
- tar->header.size = size;
- tar->header.type = MTAR_TREG;
- tar->header.mode = 0664;
- /* Write header */
- return mtar_write_header(tar, &tar->header);
+ tar->state |= (S_HEADER_VALID | S_WROTE_HEADER);
+ return MTAR_ESUCCESS;
}
+int mtar_update_header(mtar_t* tar, const mtar_header_t* h)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ unsigned beg_pos = data_beg_pos(tar);
+ if(beg_pos > UINT_MAX - h->size)
+ return MTAR_EOVERFLOW;
+
+ unsigned old_pos = tar->pos;
+ int err = tseek(tar, tar->header_pos);
+ if(err)
+ return err;
+
+ if(h != &tar->header)
+ tar->header = *h;
-int mtar_write_dir_header(mtar_t *tar, const char *name) {
- /* Build header */
- memset(&tar->header, 0, sizeof(tar->header));
- /* Ensure name fits within header */
- if(strlen(name) > sizeof(tar->header.name))
- return MTAR_EOVERFLOW;
- strncpy(tar->header.name, name, sizeof(tar->header.name));
- tar->header.type = MTAR_TDIR;
- tar->header.mode = 0775;
- /* Write header */
- return mtar_write_header(tar, &tar->header);
+ err = header_to_raw(tar->buffer, &tar->header);
+ if(err)
+ return err;
+
+ int len = twrite(tar, tar->buffer, HEADER_LEN);
+ if(len < 0)
+ return len;
+ if(len != HEADER_LEN)
+ return MTAR_EWRITEFAIL;
+
+ return tseek(tar, old_pos);
}
+int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
-int mtar_write_data(mtar_t *tar, const void *data, unsigned size) {
- int err;
+ size_t namelen = strlen(name);
+ if(namelen > NAME_LEN)
+ return MTAR_ENAMETOOLONG;
- /* Ensure we are writing the correct amount of data */
- if(size > tar->remaining_data)
- return MTAR_EOVERFLOW;
+ tar->header.mode = 0644;
+ tar->header.owner = 0;
+ tar->header.group = 0;
+ tar->header.size = size;
+ tar->header.mtime = 0;
+ tar->header.type = MTAR_TREG;
+ memcpy(tar->header.name, name, namelen + 1);
+ tar->header.linkname[0] = '\0';
+
+ return mtar_write_header(tar, &tar->header);
+}
+
+int mtar_write_dir_header(mtar_t* tar, const char* name)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ size_t namelen = strlen(name);
+ if(namelen > NAME_LEN)
+ return MTAR_ENAMETOOLONG;
+
+ tar->header.mode = 0755;
+ tar->header.owner = 0;
+ tar->header.group = 0;
+ tar->header.size = 0;
+ tar->header.mtime = 0;
+ tar->header.type = MTAR_TDIR;
+ memcpy(tar->header.name, name, namelen + 1);
+ tar->header.linkname[0] = '\0';
+
+ return mtar_write_header(tar, &tar->header);
+}
+
+int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ tar->state |= S_WROTE_DATA;
+
+ int err = twrite(tar, ptr, size);
+ if(tar->pos > tar->end_pos)
+ tar->end_pos = tar->pos;
- /* Write data */
- err = twrite(tar, data, size);
- if (err) {
return err;
- }
- tar->remaining_data -= size;
- /* Write padding if we've written all the data for this file */
- if (tar->remaining_data == 0) {
- return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos);
- }
- return MTAR_ESUCCESS;
}
+int mtar_update_file_size(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ unsigned new_size = data_end_pos(tar) - data_beg_pos(tar);
+ if(new_size == tar->header.size)
+ return MTAR_ESUCCESS;
+ else {
+ tar->header.size = new_size;
+ return mtar_update_header(tar, &tar->header);
+ }
+}
+
+int mtar_end_data(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ int err;
+
+ /* ensure the caller wrote the correct amount of data */
+ unsigned expected_end = data_beg_pos(tar) + tar->header.size;
+ if(tar->end_pos != expected_end)
+ return MTAR_EWRONGSIZE;
+
+ /* ensure we're positioned at the end of the stream */
+ if(tar->pos != tar->end_pos) {
+ err = tseek(tar, tar->end_pos);
+ if(err)
+ return err;
+ }
+
+ /* write remainder of the 512-byte record */
+ err = write_null_bytes(tar, round_up_512(tar->pos) - tar->pos);
+ if(err)
+ return err;
+
+ tar->state |= S_WROTE_DATA_EOF;
+ return MTAR_ESUCCESS;
+}
+
+int mtar_finalize(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
-int mtar_finalize(mtar_t *tar) {
- /* Write two NULL records */
- return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2);
+ tar->state |= S_WROTE_FINALIZE;
+ return write_null_bytes(tar, 1024);
}
diff --git a/lib/microtar/src/microtar.h b/lib/microtar/src/microtar.h
index d30c829521..7a9be2ac92 100644
--- a/lib/microtar/src/microtar.h
+++ b/lib/microtar/src/microtar.h
@@ -1,105 +1,132 @@
-/**
+/*
* Copyright (c) 2017 rxi
+ * Copyright (c) 2021 Aidan MacDonald
*
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the MIT license. See `microtar.c` for details.
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
*/
#ifndef MICROTAR_H
#define MICROTAR_H
#ifdef __cplusplus
-extern "C"
-{
+extern "C" {
#endif
-#include <stdio.h>
-#include <stdlib.h>
-
-#define MTAR_VERSION "0.1.0"
-
-enum {
- MTAR_ESUCCESS = 0,
- MTAR_EFAILURE = -1,
- MTAR_EOPENFAIL = -2,
- MTAR_EREADFAIL = -3,
- MTAR_EWRITEFAIL = -4,
- MTAR_ESEEKFAIL = -5,
- MTAR_EBADCHKSUM = -6,
- MTAR_ENULLRECORD = -7,
- MTAR_ENOTFOUND = -8,
- MTAR_EOVERFLOW = -9,
+#include <stdio.h> /* SEEK_SET et al. */
+
+enum mtar_error {
+ MTAR_ESUCCESS = 0,
+ MTAR_EFAILURE = -1,
+ MTAR_EOPENFAIL = -2,
+ MTAR_EREADFAIL = -3,
+ MTAR_EWRITEFAIL = -4,
+ MTAR_ESEEKFAIL = -5,
+ MTAR_ESEEKRANGE = -6,
+ MTAR_EBADCHKSUM = -7,
+ MTAR_ENULLRECORD = -8,
+ MTAR_ENOTFOUND = -9,
+ MTAR_EOVERFLOW = -10,
+ MTAR_EAPI = -11,
+ MTAR_ENAMETOOLONG = -12,
+ MTAR_EWRONGSIZE = -13,
+ MTAR_EACCESS = -14,
+};
+
+enum mtar_type {
+ MTAR_TREG = '0',
+ MTAR_TLNK = '1',
+ MTAR_TSYM = '2',
+ MTAR_TCHR = '3',
+ MTAR_TBLK = '4',
+ MTAR_TDIR = '5',
+ MTAR_TFIFO = '6',
};
-enum {
- MTAR_TREG = '0',
- MTAR_TLNK = '1',
- MTAR_TSYM = '2',
- MTAR_TCHR = '3',
- MTAR_TBLK = '4',
- MTAR_TDIR = '5',
- MTAR_TFIFO = '6'
+enum mtar_access {
+ MTAR_READ,
+ MTAR_WRITE,
};
-typedef struct {
- unsigned mode;
- unsigned owner;
- unsigned group;
- unsigned size;
- unsigned mtime;
- unsigned type;
- char name[101]; /* +1 byte in order to ensure null termination */
- char linkname[101];
-} mtar_header_t;
-
-
-typedef struct {
- char name[100];
- char mode[8];
- char owner[8];
- char group[8];
- char size[12];
- char mtime[12];
- char checksum[8];
- char type;
- char linkname[100];
- char _padding[255];
-} mtar_raw_header_t;
-
-
-typedef struct mtar_t mtar_t;
-
-struct mtar_t {
- int (*read)(mtar_t *tar, void *data, unsigned size);
- int (*write)(mtar_t *tar, const void *data, unsigned size);
- int (*seek)(mtar_t *tar, unsigned pos);
- int (*close)(mtar_t *tar);
- void *stream;
- unsigned pos;
- unsigned remaining_data;
- unsigned last_header;
- mtar_header_t header;
- mtar_raw_header_t raw_header;
+typedef struct mtar_header mtar_header_t;
+typedef struct mtar mtar_t;
+typedef struct mtar_ops mtar_ops_t;
+
+typedef int(*mtar_foreach_cb)(mtar_t*, const mtar_header_t*, void*);
+
+struct mtar_header {
+ unsigned mode;
+ unsigned owner;
+ unsigned group;
+ unsigned size;
+ unsigned mtime;
+ unsigned type;
+ char name[101]; /* +1 byte in order to ensure null termination */
+ char linkname[101];
};
+struct mtar_ops {
+ int(*read)(void* stream, void* data, unsigned size);
+ int(*write)(void* stream, const void* data, unsigned size);
+ int(*seek)(void* stream, unsigned pos);
+ int(*close)(void* stream);
+};
+
+struct mtar {
+ char buffer[512]; /* IO buffer, put first to allow library users to
+ * control its alignment */
+ int state; /* Used to simplify the API and verify API usage */
+ int access; /* Access mode */
+ unsigned pos; /* Current position in file */
+ unsigned end_pos; /* End position of the current file */
+ unsigned header_pos; /* Position of the current header */
+ mtar_header_t header; /* Most recently parsed header */
+ const mtar_ops_t* ops; /* Stream operations */
+ void* stream; /* Stream handle */
+};
const char* mtar_strerror(int err);
-int mtar_open(mtar_t *tar, const char *filename, const char *mode);
-int mtar_close(mtar_t *tar);
-
-int mtar_seek(mtar_t *tar, unsigned pos);
-int mtar_rewind(mtar_t *tar);
-int mtar_next(mtar_t *tar);
-int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h);
-int mtar_read_header(mtar_t *tar, mtar_header_t *h);
-int mtar_read_data(mtar_t *tar, void *ptr, unsigned size);
-
-int mtar_write_header(mtar_t *tar, const mtar_header_t *h);
-int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size);
-int mtar_write_dir_header(mtar_t *tar, const char *name);
-int mtar_write_data(mtar_t *tar, const void *data, unsigned size);
-int mtar_finalize(mtar_t *tar);
+void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream);
+int mtar_close(mtar_t* tar);
+int mtar_is_open(mtar_t* tar);
+
+mtar_header_t* mtar_get_header(mtar_t* tar);
+int mtar_access_mode(const mtar_t* tar);
+
+int mtar_rewind(mtar_t* tar);
+int mtar_next(mtar_t* tar);
+int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg);
+int mtar_find(mtar_t* tar, const char* name);
+
+int mtar_read_data(mtar_t* tar, void* ptr, unsigned size);
+int mtar_seek_data(mtar_t* tar, int offset, int whence);
+unsigned mtar_tell_data(mtar_t* tar);
+int mtar_eof_data(mtar_t* tar);
+
+int mtar_write_header(mtar_t* tar, const mtar_header_t* h);
+int mtar_update_header(mtar_t* tar, const mtar_header_t* h);
+int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size);
+int mtar_write_dir_header(mtar_t* tar, const char* name);
+int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size);
+int mtar_update_file_size(mtar_t* tar);
+int mtar_end_data(mtar_t* tar);
+int mtar_finalize(mtar_t* tar);
#ifdef __cplusplus
}
diff --git a/lib/x1000-installer/.gitignore b/lib/x1000-installer/.gitignore
new file mode 100644
index 0000000000..e7ad25b29f
--- /dev/null
+++ b/lib/x1000-installer/.gitignore
@@ -0,0 +1,5 @@
+xf_test
+fakeNAND.bin
+fakeNAND_meta.bin
+*.profraw
+*.profdata
diff --git a/lib/x1000-installer/Makefile b/lib/x1000-installer/Makefile
new file mode 100644
index 0000000000..54c590dce4
--- /dev/null
+++ b/lib/x1000-installer/Makefile
@@ -0,0 +1,82 @@
+OBJ = src/xf_error.o \
+ src/xf_flashmap.o \
+ src/xf_nandio.o \
+ src/xf_package.o \
+ src/xf_stream.o \
+ src/xf_update.o
+LIB = libx1000-installer.a
+
+TOBJ = test_lib/core_alloc.o \
+ test_lib/fakenand.o \
+ test_lib/file.o \
+ test_lib/pathfuncs.o \
+ test_lib/md5.o \
+ test_lib/strlcpy.o \
+ test/main.o \
+ test/test_flashmap.o \
+ test/test_stream.o
+TBIN = xf_test
+
+# dependency needs to be built manually
+MTARINC = -I../microtar/src
+MTARLIB = ../microtar/libmicrotar.a
+
+CPPFLAGS = -Iinclude -Itest_lib $(MTARINC) -D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE=200809L
+CFLAGS = -std=c99 -Wall -Wextra
+LDFLAGS =
+
+PROFRAW_FILE=$(TBIN).profraw
+PROFDATA_FILE=$(TBIN).profdata
+
+export LLVM_PROFILE_FILE=$(PROFRAW_FILE)
+
+ifeq ($(COVERAGE),1)
+ CC = clang
+ CFLAGS += -g -Og -fprofile-instr-generate -fcoverage-mapping
+ LDFLAGS += -fprofile-instr-generate -fcoverage-mapping
+else
+ CFLAGS += -O2
+endif
+
+ifeq ($(SANITIZE),1)
+ CFLAGS += -fsanitize=address -fsanitize=undefined
+ LDFLAGS += -fsanitize=address -fsanitize=undefined
+endif
+
+.PHONY: all
+all: $(LIB) $(TBIN)
+
+.PHONY: test
+test: $(TBIN)
+ @./$(TBIN)
+
+.PHONY: cov
+cov: $(PROFDATA_FILE)
+ @llvm-cov report $(TBIN) -instr-profile=$(PROFDATA_FILE)
+
+.PHONY: cov-show
+cov-show: $(PROFDATA_FILE)
+ @llvm-cov show $(TBIN) --use-color -instr-profile=$(PROFDATA_FILE) $(f) | less -R
+
+.PHONY: clean
+clean:
+ rm -f $(LIB) $(OBJ)
+ rm -f $(TBIN) $(TOBJ)
+ rm -f $(PROFRAW_FILE) $(PROFDATA_FILE)
+ rm -f fakeNAND.bin fakeNAND_meta.bin
+
+$(LIB): $(OBJ)
+ $(AR) rcs $@ $^ >/dev/null
+
+$(TBIN): $(TOBJ) $(LIB) $(MTARLIB)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+# use separate rule instead of depending on 'test' to avoid re-running
+$(PROFRAW_FILE): $(TBIN)
+ @./$(TBIN)
+
+$(PROFDATA_FILE): $(PROFRAW_FILE)
+ @llvm-profdata merge -sparse $(PROFRAW_FILE) -o $(PROFDATA_FILE)
diff --git a/lib/x1000-installer/SOURCES b/lib/x1000-installer/SOURCES
new file mode 100644
index 0000000000..cdbd9148d8
--- /dev/null
+++ b/lib/x1000-installer/SOURCES
@@ -0,0 +1,6 @@
+src/xf_error.c
+src/xf_flashmap.c
+src/xf_nandio.c
+src/xf_package.c
+src/xf_stream.c
+src/xf_update.c
diff --git a/lib/x1000-installer/include/xf_error.h b/lib/x1000-installer/include/xf_error.h
new file mode 100644
index 0000000000..2f3f6a1a4f
--- /dev/null
+++ b/lib/x1000-installer/include/xf_error.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2021 Aidan MacDonald
+ *
+ * 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 _XF_ERROR_H_
+#define _XF_ERROR_H_
+
+enum {
+ XF_E_SUCCESS = 0,
+ XF_E_IO = -1,
+ XF_E_LINE_TOO_LONG = -2,
+ XF_E_FILENAME_TOO_LONG = -3,
+ XF_E_INT_OVERFLOW = -4,
+ XF_E_BUF_OVERFLOW = -5,
+ XF_E_SYNTAX_ERROR = -6,
+ XF_E_INVALID_PARAMETER = -7,
+ XF_E_NAND = -8,
+ XF_E_OUT_OF_MEMORY = -9,
+ XF_E_OUT_OF_RANGE = -10,
+ XF_E_VERIFY_FAILED = -11,
+ XF_E_CANNOT_OPEN_FILE = -12,
+};
+
+const char* xf_strerror(int err);
+
+#endif /* _XF_ERROR_H_ */
diff --git a/lib/x1000-installer/include/xf_flashmap.h b/lib/x1000-installer/include/xf_flashmap.h
new file mode 100644
index 0000000000..b0470e58e0
--- /dev/null
+++ b/lib/x1000-installer/include/xf_flashmap.h
@@ -0,0 +1,91 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2021 Aidan MacDonald
+ *
+ * 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 _XF_FLASHMAP_H_
+#define _XF_FLASHMAP_H_
+
+#include "xf_stream.h"
+#include <stdint.h>
+
+#define XF_MAP_NAMELEN 63
+
+enum {
+ XF_MAP_HAS_MD5 = 0x01, /* 'md5' field is valid */
+ XF_MAP_HAS_FILE_LENGTH = 0x02, /* 'file_length' field is valid */
+};
+
+struct xf_map {
+ char name[XF_MAP_NAMELEN+1]; /* file name */
+ uint8_t md5[16]; /* MD5 sum of file */
+ uint32_t file_length; /* length of file in bytes */
+ uint32_t offset; /* offset in flash */
+ uint32_t length; /* region length in flash, in bytes */
+ int flags;
+};
+
+/* Parse a line with space- or tab-delimited fields of the form
+ * <name> <md5> <file_length> <offset> <length>
+ * <name> '-' <offset> <length>
+ *
+ * - name can be up to XF_FMAP_NAMELEN characters long
+ * - md5 is 32 hexadecimal characters (case insensitive)
+ * - file_length, offset, and length are 32-bit unsigned integers
+ * and can be given in decimal or (with '0x' prefix) hexadecimal
+ *
+ * Parsed data is written to *map. Returns zero on success and
+ * nonzero on error.
+ */
+int xf_map_parseline(const char* line, struct xf_map* map);
+
+/* Parse a file calling xf_map_parseline() on each line to populate
+ * a map of up to 'maxnum' regions. Blank and comment lines are
+ * ignored (comments start with '#').
+ *
+ * Returns the number of regions in the resulting map if the file was
+ * parsed successfully, or a negative value on error.
+ */
+int xf_map_parse(struct xf_stream* s, struct xf_map* map, int maxnum);
+
+/* Sort the map so its members are in ascending order with the lowest
+ * flash offset region first. After sorting, xf_map_validate() is used
+ * to check for overlapping regions.
+ *
+ * The return value is that of xf_map_validate().
+ */
+int xf_map_sort(struct xf_map* map, int num);
+
+/* Check if the input map is sorted and contains no overlap.
+ *
+ * Returns 0 if the map is sorted and contains no overlapping regions,
+ * -1 if the map isn't sorted, or if an overlapping region is detected,
+ * the index of the first overlapping region. (A returned index i is
+ * always positive: the two overlapped entries are map[i] and map[i-1].)
+ */
+int xf_map_validate(const struct xf_map* map, int num);
+
+/* Write the map to a stream. This does not check that the map is valid.
+ * Returns the number of bytes written to the stream or a negative value
+ * on error. The stream may be NULL, in which case the number of bytes
+ * that would be written are returned (similar to snprintf).
+ */
+int xf_map_write(struct xf_map* map, int num, struct xf_stream* s);
+
+#endif /* _XF_FLASHMAP_H_ */
diff --git a/lib/x1000-installer/include/xf_nandio.h b/lib/x1000-installer/include/xf_nandio.h
new file mode 100644
index 0000000000..a10b71992c
--- /dev/null
+++ b/lib/x1000-installer/include/xf_nandio.h
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2021 Aidan MacDonald
+ *
+ * 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 _XF_NANDIO_H_
+#define _XF_NANDIO_H_
+
+#include "nand-x1000.h"
+#include <stdint.h>
+#include <stddef.h>
+
+enum xf_nandio_mode {
+ XF_NANDIO_READ = 0,
+ XF_NANDIO_PROGRAM,
+ XF_NANDIO_VERIFY,
+};
+
+struct xf_nandio {
+ nand_drv* ndrv;
+ int nand_err; /* copy of the last NAND error code */
+ int alloc_handle;
+ enum xf_nandio_mode mode;
+
+ size_t block_size; /* size of a block, in bytes */
+ size_t page_size; /* size of a page, in bytes */
+
+ uint8_t* old_buf; /* contains the 'old' block data already on chip */
+ uint8_t* new_buf; /* contains the possibly modified 'new' block data */
+
+ nand_block_t cur_block; /* address of the current block */
+ size_t offset_in_block; /* byte offset in the current block */
+ unsigned block_valid: 1; /* 1 if buffered block data is valid */
+};
+
+int xf_nandio_init(struct xf_nandio* nio);
+void xf_nandio_destroy(struct xf_nandio* nio);
+
+/** Sets the operational mode, which determines read/write/flush semantics.
+ *
+ * - XF_NANDIO_READ: Accesses the chip in read-only mode. Writes are allowed,
+ * but should not be used. (Writes will modify a temporary buffer but this
+ * will not alter the flash contents.)
+ *
+ * - XF_NANDIO_PROGRAM: Writes are allowed to modify the flash contents.
+ * Writes within a block are accumulated in a temporary buffer. When
+ * crossing a block boundary, either by writing past the end the current
+ * block or by seeking to a new one, the data written to the temporary
+ * buffer is compared against the current flash contents. If the block
+ * has been modified, it is erased and any non-blank pages are programmed
+ * with the new data.
+ *
+ * - XF_NANDIO_VERIFY: This mode allows callers to easily check whether the
+ * flash contents match some expected contents. Callers "write" the expected
+ * contents as if programming it with XF_NANDIO_PROGRAM. When a block is
+ * flushed, if the written data doesn't match the block contents, an
+ * XF_E_VERIFY_FAILED error is returned. The flash contents will not be
+ * altered in this mode.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_set_mode(struct xf_nandio* nio, enum xf_nandio_mode mode);
+
+/** Seek to a given byte offset in the NAND flash.
+ *
+ * If the new offset is outside the current block, the current block will
+ * be automatically flushed. Note this can result in verification or program
+ * failures as with any other flush.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_seek(struct xf_nandio* nio, size_t offset);
+
+/** Read or write a contiguous sequence of bytes from flash.
+ *
+ * The read or write starts at the current position and continues for `count`
+ * bytes. Both reads and writes may cross block boundaries. Modified blocks
+ * will be flushed automatically if the operation crosses a block boundary.
+ *
+ * After a successful read or write, the current position is advanced by
+ * exactly `count` bytes. After a failure, the position is indeterminate.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_read(struct xf_nandio* nio, void* buf, size_t count);
+int xf_nandio_write(struct xf_nandio* nio, const void* buf, size_t count);
+
+/** Get a pointer to the block buffer for direct read/write access.
+ *
+ * These functions can be used to read or write data without intermediate
+ * buffers. The caller passes in the amount of data to be transferred in
+ * `*count`. A pointer to part of the block buffer is returned in `*buf`
+ * and the number of bytes available in `*buf` is returned in `*count`.
+ *
+ * Data at the current position can be read from the returned buffer and
+ * it may be modified by writing to the buffer. The buffer is only valid
+ * until the next call to an `xf_nandio` function.
+ *
+ * The read/write position is advanced by the returned `*count` on success,
+ * and is unchanged on failure.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_get_buffer(struct xf_nandio* nio, void** buf, size_t* count);
+
+/** Flush the buffered block to ensure all outstanding program or verification
+ * operations have been performed. This should only be called to ensure the
+ * final modified block is flushed after you have finished writing all data.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_flush(struct xf_nandio* nio);
+
+#endif /* _XF_NANDIO_H_ */
diff --git a/lib/x1000-installer/include/xf_package.h b/lib/x1000-installer/include/xf_package.h
new file mode 100644
index 0000000000..6633766bd7
--- /dev/null
+++ b/lib/x1000-installer/include/xf_package.h
@@ -0,0 +1,65 @@
+/***************************************************