diff options
Diffstat (limited to 'apps/plugins')
182 files changed, 9648 insertions, 1405 deletions
diff --git a/apps/plugins/2048.c b/apps/plugins/2048.c index 2633753071..2f4eb4c001 100644 --- a/apps/plugins/2048.c +++ b/apps/plugins/2048.c @@ -91,9 +91,16 @@ static const int BACKGROUND_Y = (BASE_Y-MIN_SPACE); #define KEY_DOWN PLA_DOWN #define KEY_LEFT PLA_LEFT #define KEY_RIGHT PLA_RIGHT -#define KEY_EXIT PLA_CANCEL #define KEY_UNDO PLA_SELECT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define KEY_EXIT PLA_SELECT_REPEAT +#else +#define KEY_EXIT PLA_CANCEL +#endif + /* notice how "color" is spelled :P */ #ifdef HAVE_LCD_COLOR @@ -148,9 +155,7 @@ static inline int rand_range(int min, int max) /* prepares for exit */ static void cleanup(void) { -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif } /* returns 2 or 4 */ @@ -700,9 +705,8 @@ static void init_game(bool newgame) max_numeral_width = rb->font_get_width(rb->font_get(WHAT_FONT), '0'); #endif -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + draw(); } diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index bb0960f501..5320015772 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -22,6 +22,8 @@ clock,apps codebuster,games credits,viewers cube,demos +dart_scorer,apps +db_commit,apps db_folder_select,viewers demystify,demos dice,games @@ -47,7 +49,10 @@ jackpot,games jewels,games jpeg,viewers keybox,apps +keyremap,apps lamp,apps +lastfm_scrobbler,apps +lastfm_scrobbler_viewer,viewers logo,demos lrcplayer,apps lua,viewers @@ -67,6 +72,7 @@ minesweeper,games mosaique,demos mp3_encoder,apps mpegplayer,viewers +multiboot_select,apps nim,games open_plugins,viewers oscilloscope,demos @@ -80,6 +86,7 @@ pitch_detector,apps pitch_screen,viewers pixel-painter,games plasma,demos +playing_time,apps png,viewers gif,viewers pong,games @@ -170,10 +177,12 @@ test_disk,apps test_fps,apps test_grey,apps test_gfx,apps +test_kbd,apps test_resize,apps test_sampr,apps test_scanrate,apps test_touchscreen,apps +test_usb,apps test_viewports,apps test_greylib_bitmap_scale,viewers text_editor,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index ab77dcde58..bf36d50a40 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -4,17 +4,23 @@ battery_bench.c #endif #ifdef HAVE_TAGCACHE db_folder_select.c +tagcache/tagcache.c #endif chessclock.c credits.c cube.c +dart_scorer.c dict.c jackpot.c keybox.c +keyremap.c +lastfm_scrobbler.c +lastfm_scrobbler_viewer.c logo.c lrcplayer.c mosaique.c main_menu_config.c +playing_time.c properties.c random_folder_advance_config.c rb_info.c @@ -80,8 +86,7 @@ crypt_firmware.c /* Overlays loaders */ -#if defined(HAVE_LCD_COLOR) && \ - (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE)) +#if defined(HAVE_LCD_COLOR) && (LCD_STRIDEFORMAT == HORIZONTAL_STRIDE) #if (PLUGIN_BUFFER_SIZE > 0x14000) && (CONFIG_PLATFORM & (PLATFORM_NATIVE |PLATFORM_HOSTED)) && (defined(CPU_ARM) || defined(CPU_MIPS)) duke3d.c quake.c @@ -156,6 +161,11 @@ wormlet.c announce_status.c #endif +/* can't build in the sim for some reason */ +#if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) +multiboot_select.c +#endif + /* Plugins needing the grayscale lib on low-depth LCDs */ fire.c @@ -202,6 +212,7 @@ test_core_jpeg.c test_disk.c test_fps.c test_gfx.c +test_kbd.c #if LCD_DEPTH < 4 && !defined(SIMULATOR) test_scanrate.c #endif @@ -218,5 +229,6 @@ test_sampr.c #ifdef HAVE_TOUCHSCREEN test_touchscreen.c #endif +test_usb.c test_viewports.c #endif /* HAVE_TEST_PLUGINS */ diff --git a/apps/plugins/SOURCES.app_build b/apps/plugins/SOURCES.app_build index ac9f01a896..50c79ec016 100644 --- a/apps/plugins/SOURCES.app_build +++ b/apps/plugins/SOURCES.app_build @@ -19,6 +19,7 @@ stopwatch.lua #ifdef HAVE_TEST_PLUGINS /* enable in advanced build options */ +test_kbd.c test_fps.c #ifdef HAVE_ADJUSTABLE_CPU_FREQ test_boost.c diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 8479e4b3dd..b9c18b5fd9 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -9,8 +9,7 @@ clock #endif /* color horizontal-stride LCDs */ -#if defined(HAVE_LCD_COLOR) && \ - (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE)) +#if defined(HAVE_LCD_COLOR) && (LCD_STRIDEFORMAT == HORIZONTAL_STRIDE) xworld /* for duke3d, wolf3d and quake */ @@ -32,6 +31,7 @@ rockboy #if defined(HAVE_TAGCACHE) pictureflow +tagcache #endif #if PLUGIN_BUFFER_SIZE > 0x20000 diff --git a/apps/plugins/SUBDIRS.app_build b/apps/plugins/SUBDIRS.app_build index 1fc283d6c4..fbf83f01da 100644 --- a/apps/plugins/SUBDIRS.app_build +++ b/apps/plugins/SUBDIRS.app_build @@ -17,6 +17,7 @@ reversi #ifdef HAVE_TAGCACHE pictureflow +tagcache #endif /* For all the swcodec targets */ diff --git a/apps/plugins/alarmclock.c b/apps/plugins/alarmclock.c index ecafceddc7..535097724f 100644 --- a/apps/plugins/alarmclock.c +++ b/apps/plugins/alarmclock.c @@ -136,14 +136,20 @@ enum plugin_status plugin_start(const void* parameter) pause_audio(); while(!quit) { + FOR_NB_SCREENS(i) { + draw(rb->screens[i]); + } button = get_button(); +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + if (button == PLA_EXIT || button == PLA_CANCEL || button == PLA_UP) +#else if (button == PLA_EXIT || button == PLA_CANCEL) +#endif quit = true; - FOR_NB_SCREENS(i) { - draw(rb->screens[i]); - } if (waiting) { if (rem_seconds() <= 0) { quit = done = true; diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c index 77e9015000..21518b4d68 100644 --- a/apps/plugins/announce_status.c +++ b/apps/plugins/announce_status.c @@ -97,6 +97,7 @@ enum plugin_status plugin_start(const void* parameter); /* entry */ static struct { bool exiting; /* signal to the thread that we want to exit */ + bool resume; unsigned int id; /* worker thread id */ struct event_queue queue; /* thread event queue */ long stack[THREAD_STACK_SIZE / sizeof(long)]; @@ -382,7 +383,7 @@ static int settings_menu(void) break; case 1: rb->set_option(rb->str(LANG_ANNOUNCE_ON), - &gAnnounce.announce_on, INT, announce_options, 2, NULL); + &gAnnounce.announce_on, RB_INT, announce_options, 2, NULL); break; case 2: rb->set_int(rb->str(LANG_GROUPING), "", 1, @@ -393,7 +394,7 @@ static int settings_menu(void) break; case 4: /*sep*/ continue; - case 5: + case 5: /* quit the plugin */ return -1; break; case 6: @@ -433,7 +434,8 @@ void thread(void) in_usb = false; /*fall through*/ case EV_STARTUP: - rb->beep_play(1500, 100, 1000); + if (!gThread.resume) + rb->beep_play(1500, 100, 1000); break; case EV_EXIT: return; @@ -479,17 +481,45 @@ void thread_quit(void) } } +static bool check_user_input(void) +{ + int i = 0; + rb->button_clear_queue(); + if (rb->button_get_w_tmo(HZ) > BUTTON_NONE) + { + while ((rb->button_get(false) & BUTTON_REL) != BUTTON_REL) + { + if (i & 1) + rb->beep_play(800, 100, 1000 - i * (1000 / 15)); + + if (++i > 15) + { + return true; + } + rb->sleep(HZ / 5); + } + } + return false; +} + /* callback to end the TSR plugin, called before a new one gets loaded */ -static bool exit_tsr(bool reenter) +static int exit_tsr(bool reenter) { if (reenter) { rb->queue_post(&gThread.queue, EV_OTHINSTANCE, 0); - return false; /* dont let it start again */ + + /* quit the plugin if user holds a button */ + if (check_user_input() == true) + { + if (settings_menu() < 0) + return PLUGIN_TSR_TERMINATE; /*kill TSR dont let it start again */ + } + return PLUGIN_TSR_CONTINUE; /* dont let new plugin start*/ } thread_quit(); - return true; + return PLUGIN_TSR_SUSPEND; } @@ -497,58 +527,35 @@ static bool exit_tsr(bool reenter) int plugin_main(const void* parameter) { - (void)parameter; - bool settings = false; - int i = 0; - rb->memset(&gThread, 0, sizeof(gThread)); gAnnounce.index = 0; gAnnounce.timeout = 0; - - rb->splash(HZ / 2, "Announce Status"); - - if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) - { - /* If the loading failed, save a new config file */ - config_set_defaults(); - configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); - - rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); - } - - if (gAnnounce.show_prompt) + /* Resume plugin ? */ + if (parameter == rb->plugin_tsr) { - if (rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_PLAYING) - { - rb->talk_id(LANG_HOLD_FOR_SETTINGS, false); - } - rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); + gThread.resume = true; } - - rb->button_clear_queue(); - if (rb->button_get_w_tmo(HZ) > BUTTON_NONE) + else { - while ((rb->button_get(false) & BUTTON_REL) != BUTTON_REL) + rb->splash(HZ / 2, "Announce Status"); + if (gAnnounce.show_prompt) { - if (i & 1) - rb->beep_play(800, 100, 1000); - - if (++i > 15) + if (rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_PLAYING) { - settings = true; - break; + rb->talk_id(LANG_HOLD_FOR_SETTINGS, false); } - rb->sleep(HZ / 5); + rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); } - } - if (settings) - { - rb->splash(100, ID2P(LANG_SETTINGS)); - int ret = settings_menu(); - if (ret < 0) - return 0; + + if (check_user_input() == true) + { + rb->splash(100, ID2P(LANG_SETTINGS)); + int ret = settings_menu(); + if (ret < 0) + return 0; + } } gAnnounce.timeout = *rb->current_tick; @@ -571,6 +578,16 @@ enum plugin_status plugin_start(const void* parameter) /* now go ahead and have fun! */ if (rb->usb_inserted() == true) return PLUGIN_USB_CONNECTED; + + config_set_defaults(); + if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) + { + /* If the loading failed, save a new config file */ + config_set_defaults(); + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); + } + int ret = plugin_main(parameter); return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; } diff --git a/apps/plugins/battery_bench.c b/apps/plugins/battery_bench.c index d8e29d73ca..f258492363 100644 --- a/apps/plugins/battery_bench.c +++ b/apps/plugins/battery_bench.c @@ -289,11 +289,11 @@ static struct event_queue thread_q SHAREDBSS_ATTR; static bool in_usb_mode; static unsigned int buf_idx; -static bool exit_tsr(bool reenter) +static int exit_tsr(bool reenter) { - bool is_exit; + int exit_status; long button; - (void)reenter; + rb->lcd_clear_display(); rb->lcd_puts_scroll(0, 0, "Batt.Bench is currently running."); rb->lcd_puts_scroll(0, 1, "Press " BATTERY_OFF_TXT " to cancel the test"); @@ -313,16 +313,17 @@ static bool exit_tsr(bool reenter) rb->thread_wait(gThread.id); /* remove the thread's queue from the broadcast list */ rb->queue_delete(&thread_q); - is_exit = true; + exit_status = (reenter ? PLUGIN_TSR_TERMINATE : PLUGIN_TSR_SUSPEND); + } - else is_exit = false; + else exit_status = PLUGIN_TSR_CONTINUE; break; } FOR_NB_SCREENS(idx) rb->screens[idx]->scroll_stop(); - return is_exit; + return exit_status; } #define BIT_CHARGER 0x1 @@ -462,6 +463,7 @@ static void thread(void) in_usb_mode = false; break; case SYS_POWEROFF: + case SYS_REBOOT: exit_reason = "power off"; exit = true; break; @@ -501,14 +503,19 @@ static void put_centered_str(const char* str, plcdfunc putsxy, int lcd_width, in enum plugin_status plugin_start(const void* parameter) { - (void)parameter; int button, fd; + bool resume = false; bool on = false; start_tick = *rb->current_tick; int i; const char *msgs[] = { "Battery Benchmark","Check file", BATTERY_LOG, "for more info", BATTERY_ON_TXT, BATTERY_OFF_TXT " - quit" }; - rb->lcd_clear_display(); + + if (parameter == rb->plugin_tsr) + { + resume = true; + on = true; + } rb->lcd_clear_display(); rb->lcd_setfont(FONT_SYSFIXED); @@ -528,36 +535,38 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_remote_putsxy,LCD_REMOTE_WIDTH,2); rb->lcd_remote_update(); #endif - - do + if (!resume) { - button = rb->button_get(true); - switch (button) + do { - case BATTERY_ON: -#ifdef BATTERY_RC_ON - case BATTERY_RC_ON: -#endif - on = true; - break; - case BATTERY_OFF: -#ifdef BATTERY_RC_OFF - case BATTERY_RC_OFF: -#endif - return PLUGIN_OK; - - default: - if (rb->default_event_handler(button) == SYS_USB_CONNECTED) - return PLUGIN_USB_CONNECTED; - } - }while(!on); - + button = rb->button_get(true); + switch (button) + { + case BATTERY_ON: + #ifdef BATTERY_RC_ON + case BATTERY_RC_ON: + #endif + on = true; + break; + case BATTERY_OFF: + #ifdef BATTERY_RC_OFF + case BATTERY_RC_OFF: + #endif + return PLUGIN_OK; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + return PLUGIN_USB_CONNECTED; + } + }while(!on); + } fd = rb->open(BATTERY_LOG, O_RDONLY); if (fd < 0) { fd = rb->open(BATTERY_LOG, O_RDWR | O_CREAT, 0666); if (fd >= 0) { + rb->fdprintf(fd, "# This plugin will log your battery performance in a\n" "# file (%s) every minute.\n" diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES index f207f358b2..814845dc5b 100644 --- a/apps/plugins/bitmaps/native/SOURCES +++ b/apps/plugins/bitmaps/native/SOURCES @@ -548,8 +548,7 @@ pegbox_pieces.9x7x1.bmp #endif /* Puzzles */ -#if defined(HAVE_LCD_COLOR) && \ - (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE)) +#if defined(HAVE_LCD_COLOR) && (LCD_STRIDEFORMAT == HORIZONTAL_STRIDE) puzzles_cursor.11x16x24.bmp #endif @@ -974,6 +973,45 @@ rockboxlogo.91x32x1.bmp #endif #endif +/* Credits logo */ +#if (LCD_DEPTH == 1) +#if (LCD_WIDTH == 160) +creditslogo.160x53x1.bmp +#elif (LCD_WIDTH == 128) +creditslogo.128x42x1.bmp +#else +creditslogo.112x30x1.bmp +#endif +#elif (LCD_WIDTH == 96) && (LCD_DEPTH >= 16) +creditslogo.96x30x16.bmp +#elif (LCD_WIDTH == 128) && (LCD_DEPTH == 2) +creditslogo.128x42x2.bmp +#elif (LCD_WIDTH == 128) && (LCD_DEPTH >= 16) +creditslogo.128x40x16.bmp +#elif (LCD_WIDTH == 132) && (LCD_DEPTH >= 16) +creditslogo.132x40x16.bmp +#elif (LCD_WIDTH == 138) && (LCD_DEPTH >= 2) +creditslogo.138x46x2.bmp +#elif (LCD_WIDTH == 160) && (LCD_DEPTH == 2) +creditslogo.160x53x2.bmp +#elif (LCD_WIDTH == 320) && (LCD_DEPTH == 2) +creditslogo.160x53x2.bmp +#elif (LCD_WIDTH == 160) && (LCD_DEPTH >= 16) +creditslogo.160x50x16.bmp +#elif (LCD_WIDTH == 176) && (LCD_DEPTH >= 16) +creditslogo.176x54x16.bmp +#elif (LCD_WIDTH == 220) && (LCD_DEPTH >= 16) +creditslogo.220x68x16.bmp +#elif (LCD_WIDTH == 240) && (LCD_DEPTH >= 16) +creditslogo.240x74x16.bmp +#elif (LCD_WIDTH >= 320) && (LCD_WIDTH < 480) && (LCD_DEPTH >= 16) +creditslogo.320x98x16.bmp +#elif (LCD_WIDTH >= 480) && (LCD_WIDTH < 640) && (LCD_DEPTH >= 16) +creditslogo.480x149x16.bmp +#elif (LCD_WIDTH >= 640) && (LCD_DEPTH >= 16) +creditslogo.640x198x16.bmp +#endif + /* Pitch detector */ /* The following preprocessor condition must match the condition */ /* for pitch detector from plugins/SOURCES */ diff --git a/apps/plugins/bitmaps/native/creditslogo.112x30x1.bmp b/apps/plugins/bitmaps/native/creditslogo.112x30x1.bmp Binary files differnew file mode 100644 index 0000000000..c414ffb27b --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.112x30x1.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.128x40x16.bmp b/apps/plugins/bitmaps/native/creditslogo.128x40x16.bmp Binary files differnew file mode 100644 index 0000000000..228dbddbd5 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.128x40x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.128x42x1.bmp b/apps/plugins/bitmaps/native/creditslogo.128x42x1.bmp Binary files differnew file mode 100644 index 0000000000..27d8e5fc26 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.128x42x1.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.128x42x2.bmp b/apps/plugins/bitmaps/native/creditslogo.128x42x2.bmp Binary files differnew file mode 100644 index 0000000000..fa12aa4bd7 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.128x42x2.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.132x40x16.bmp b/apps/plugins/bitmaps/native/creditslogo.132x40x16.bmp Binary files differnew file mode 100644 index 0000000000..865a8ed59d --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.132x40x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.138x46x2.bmp b/apps/plugins/bitmaps/native/creditslogo.138x46x2.bmp Binary files differnew file mode 100644 index 0000000000..4b5d7beb16 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.138x46x2.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.160x50x16.bmp b/apps/plugins/bitmaps/native/creditslogo.160x50x16.bmp Binary files differnew file mode 100644 index 0000000000..59e472def1 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.160x50x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.160x53x1.bmp b/apps/plugins/bitmaps/native/creditslogo.160x53x1.bmp Binary files differnew file mode 100644 index 0000000000..7e34b23c6b --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.160x53x1.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.160x53x2.bmp b/apps/plugins/bitmaps/native/creditslogo.160x53x2.bmp Binary files differnew file mode 100644 index 0000000000..240cf10686 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.160x53x2.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.176x54x16.bmp b/apps/plugins/bitmaps/native/creditslogo.176x54x16.bmp Binary files differnew file mode 100644 index 0000000000..bf43a65e42 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.176x54x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.220x68x16.bmp b/apps/plugins/bitmaps/native/creditslogo.220x68x16.bmp Binary files differnew file mode 100644 index 0000000000..1dc68a09f2 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.220x68x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.240x74x16.bmp b/apps/plugins/bitmaps/native/creditslogo.240x74x16.bmp Binary files differnew file mode 100644 index 0000000000..fd335b2fb8 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.240x74x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.320x98x16.bmp b/apps/plugins/bitmaps/native/creditslogo.320x98x16.bmp Binary files differnew file mode 100644 index 0000000000..26dcac89c3 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.320x98x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.480x149x16.bmp b/apps/plugins/bitmaps/native/creditslogo.480x149x16.bmp Binary files differnew file mode 100644 index 0000000000..c73d9374e4 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.480x149x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.640x198x16.bmp b/apps/plugins/bitmaps/native/creditslogo.640x198x16.bmp Binary files differnew file mode 100644 index 0000000000..ec6a16da5d --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.640x198x16.bmp diff --git a/apps/plugins/bitmaps/native/creditslogo.96x30x16.bmp b/apps/plugins/bitmaps/native/creditslogo.96x30x16.bmp Binary files differnew file mode 100644 index 0000000000..e032ebae34 --- /dev/null +++ b/apps/plugins/bitmaps/native/creditslogo.96x30x16.bmp diff --git a/apps/plugins/bitmaps/remote_native/SOURCES b/apps/plugins/bitmaps/remote_native/SOURCES index b3cc2157ff..2142674c67 100644 --- a/apps/plugins/bitmaps/remote_native/SOURCES +++ b/apps/plugins/bitmaps/remote_native/SOURCES @@ -22,3 +22,10 @@ remote_rockboxlogo.91x32x1.bmp #elif (LCD_REMOTE_DEPTH == 2) remote_rockboxlogo.91x32x2.bmp #endif + +/* Credits logo */ +#if (LCD_REMOTE_DEPTH == 1) +remote_creditslogo.128x42x1.bmp +#elif (LCD_REMOTE_DEPTH == 2) +remote_creditslogo.128x42x2.bmp +#endif diff --git a/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x1.bmp b/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x1.bmp Binary files differnew file mode 100644 index 0000000000..27d8e5fc26 --- /dev/null +++ b/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x1.bmp diff --git a/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x2.bmp b/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x2.bmp Binary files differnew file mode 100644 index 0000000000..fa12aa4bd7 --- /dev/null +++ b/apps/plugins/bitmaps/remote_native/remote_creditslogo.128x42x2.bmp diff --git a/apps/plugins/bounce.c b/apps/plugins/bounce.c index e6f29817d6..a42a0af6ba 100644 --- a/apps/plugins/bounce.c +++ b/apps/plugins/bounce.c @@ -34,10 +34,6 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; /* We set button maping with PLA */ -#define BOUNCE_UP PLA_UP -#define BOUNCE_UP_REPEAT PLA_UP_REPEAT -#define BOUNCE_DOWN PLA_DOWN -#define BOUNCE_DOWN_REPEAT PLA_DOWN_REPEAT #ifdef HAVE_SCROLLWHEEL #define BOUNCE_LEFT PLA_SCROLL_BACK @@ -52,7 +48,22 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #endif #define BOUNCE_QUIT PLA_EXIT + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define BOUNCE_QUIT2 PLA_UP +#define BOUNCE_DOWN PLA_LEFT +#define BOUNCE_DOWN_REPEAT PLA_LEFT_REPEAT +#define BOUNCE_UP PLA_RIGHT +#define BOUNCE_UP_REPEAT PLA_RIGHT_REPEAT +#else #define BOUNCE_QUIT2 PLA_CANCEL +#define BOUNCE_DOWN PLA_DOWN +#define BOUNCE_DOWN_REPEAT PLA_DOWN_REPEAT +#define BOUNCE_UP PLA_UP +#define BOUNCE_UP_REPEAT PLA_UP_REPEAT +#endif #define BOUNCE_MODE PLA_SELECT #define LETTER_WIDTH 11 @@ -471,6 +482,10 @@ enum plugin_status plugin_start(const void* parameter) #if (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) || \ (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) "[Rew] to stop"; +#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + "[Menu] to stop"; #else "[Off] to stop"; #endif diff --git a/apps/plugins/brickmania.c b/apps/plugins/brickmania.c index 4983d5a417..a26ff8edd8 100644 --- a/apps/plugins/brickmania.c +++ b/apps/plugins/brickmania.c @@ -111,8 +111,7 @@ #elif CONFIG_KEYPAD == SANSA_C200_PAD || \ CONFIG_KEYPAD == SANSA_CLIP_PAD || \ -CONFIG_KEYPAD == SANSA_M200_PAD || \ -CONFIG_KEYPAD == SANSA_CONNECT_PAD +CONFIG_KEYPAD == SANSA_M200_PAD #define QUIT BUTTON_POWER #define LEFT BUTTON_LEFT #define RIGHT BUTTON_RIGHT @@ -122,6 +121,18 @@ CONFIG_KEYPAD == SANSA_CONNECT_PAD #define UP BUTTON_UP #define DOWN BUTTON_DOWN +#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD +#define QUIT BUTTON_POWER +#define LEFT BUTTON_LEFT +#define RIGHT BUTTON_RIGHT +#define SELECT BUTTON_SELECT +#define UP BUTTON_UP +#define DOWN BUTTON_DOWN + +#define SCROLL_FWD(x) ((x) & BUTTON_SCROLL_FWD) +#define SCROLL_BACK(x) ((x) & BUTTON_SCROLL_BACK) + + #elif CONFIG_KEYPAD == IRIVER_H10_PAD #define QUIT BUTTON_POWER #define LEFT BUTTON_LEFT @@ -1554,7 +1565,7 @@ static int brickmania_menu(void) brickmania_init_game(true); return 0; case 2: - rb->set_option("Difficulty", &difficulty, INT, + rb->set_option("Difficulty", &difficulty, RB_INT, options, 2, NULL); break; case 3: @@ -2522,10 +2533,10 @@ enum plugin_status plugin_start(const void* parameter) #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + /* now go ahead and have fun! */ rb->srand( *rb->current_tick ); brickmania_loadgame(); @@ -2554,9 +2565,9 @@ enum plugin_status plugin_start(const void* parameter) configfile_save(CONFIG_FILE_NAME,config,1,0); /* Restore user's original backlight setting */ rb->lcd_setfont(FONT_UI); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return PLUGIN_OK; } diff --git a/apps/plugins/bubbles.c b/apps/plugins/bubbles.c index 88d7228d72..a64093492f 100644 --- a/apps/plugins/bubbles.c +++ b/apps/plugins/bubbles.c @@ -79,7 +79,6 @@ enum { #define ANGLE_STEP_REP 6 #define BUBBLES_QUIT1 PLA_EXIT -#define BUBBLES_QUIT2 PLA_CANCEL /* these are better off shooting with up */ #if (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) \ @@ -92,10 +91,19 @@ enum { #define BUBBLES_FIRE PLA_UP #define BUBBLES_FIRE_REPEAT PLA_UP_REPEAT #define BUBBLES_PAUSE PLA_SELECT +#define BUBBLES_QUIT2 PLA_CANCEL +#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define BUBBLES_FIRE PLA_SELECT +#define BUBBLES_FIRE_REPEAT PLA_SELECT_REPEAT +#define BUBBLES_PAUSE PLA_DOWN +#define BUBBLES_QUIT2 PLA_UP #else #define BUBBLES_FIRE PLA_SELECT #define BUBBLES_FIRE_REPEAT PLA_SELECT_REPEAT #define BUBBLES_PAUSE PLA_UP +#define BUBBLES_QUIT2 PLA_CANCEL #endif /* external bitmaps */ diff --git a/apps/plugins/calculator.c b/apps/plugins/calculator.c index 5bab15d7ed..743192dab6 100644 --- a/apps/plugins/calculator.c +++ b/apps/plugins/calculator.c @@ -732,12 +732,15 @@ static void oneOperand(void); static void drawLines(void); static void drawButtons(int group); +#ifndef _WIN32 double strtod(const char *nptr, char **endptr); +#endif long long atoll(const char *nptr); /* ----------------------------------------------------------------------- Standard library function ----------------------------------------------------------------------- */ +#ifndef _WIN32 double strtod(const char *nptr, char **endptr) { double out; @@ -774,6 +777,7 @@ double strtod(const char *nptr, char **endptr) *endptr=(char *) nptr; return out; } +#endif // WARNING Unsafe: Use strtoll instead long long atoll(const char *nptr) diff --git a/apps/plugins/calendar.c b/apps/plugins/calendar.c index 3299a81273..6d1091159f 100644 --- a/apps/plugins/calendar.c +++ b/apps/plugins/calendar.c @@ -39,14 +39,12 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define CALENDAR_QUIT (BUTTON_SELECT|BUTTON_MENU) +#define CALENDAR_QUIT (BUTTON_MENU|BUTTON_REL) #define CALENDAR_SELECT (BUTTON_SELECT|BUTTON_REL) #define CALENDAR_NEXT_WEEK BUTTON_SCROLL_FWD #define CALENDAR_PREV_WEEK BUTTON_SCROLL_BACK #define CALENDAR_NEXT_DAY BUTTON_RIGHT #define CALENDAR_PREV_DAY BUTTON_LEFT -#define CALENDAR_NEXT_MONTH BUTTON_PLAY -#define CALENDAR_PREV_MONTH (BUTTON_MENU|BUTTON_REL) #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) #define CALENDAR_QUIT BUTTON_POWER @@ -913,7 +911,7 @@ static bool edit_memo(int change, struct shown *shown) case 6: /* weekday */ rb->set_option("First Day of Week", &info.first_wday, - INT, modes, 7, NULL); + RB_INT, modes, 7, NULL); break; case 7: /* playback control */ @@ -964,7 +962,7 @@ static bool view_events(int selected, struct shown *shown) while (!exit) { button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - rb->gui_synclist_do_button(&gui_memos, &button, LIST_WRAP_UNLESS_HELD); + rb->gui_synclist_do_button(&gui_memos, &button); switch (button) { @@ -1095,17 +1093,18 @@ enum plugin_status plugin_start(const void* parameter) case CALENDAR_QUIT: exit = true; break; - +#ifdef CALENDAR_NEXT_MONTH case CALENDAR_NEXT_MONTH: case CALENDAR_NEXT_MONTH | BUTTON_REPEAT: next_month(&shown, 0); break; - +#endif +#ifdef CALENDAR_PREV_MONTH case CALENDAR_PREV_MONTH: case CALENDAR_PREV_MONTH | BUTTON_REPEAT: prev_month(&shown, 0); break; - +#endif case CALENDAR_NEXT_WEEK: case CALENDAR_NEXT_WEEK | BUTTON_REPEAT: next_day(&shown, 7); diff --git a/apps/plugins/chessbox/chessbox.c b/apps/plugins/chessbox/chessbox.c index 13df4f177e..089cf7c400 100644 --- a/apps/plugins/chessbox/chessbox.c +++ b/apps/plugins/chessbox/chessbox.c @@ -222,6 +222,7 @@ static void cb_wt_callback ( void ) { button = rb->button_get(false); switch (button) { case SYS_POWEROFF: + case SYS_REBOOT: cb_sysevent = button; #ifdef CB_RC_QUIT case CB_RC_QUIT: @@ -585,6 +586,7 @@ static struct cb_command cb_get_viewer_command (void) { button = rb->button_get(true); switch (button) { case SYS_POWEROFF: + case SYS_REBOOT: cb_sysevent = button; #ifdef CB_RC_QUIT case CB_RC_QUIT: @@ -848,6 +850,7 @@ static struct cb_command cb_getcommand (void) { button = rb->button_get(true); switch (button) { case SYS_POWEROFF: + case SYS_REBOOT: cb_sysevent = button; #ifdef CB_RC_QUIT case CB_RC_QUIT: diff --git a/apps/plugins/chessbox/chessbox_pgn.c b/apps/plugins/chessbox/chessbox_pgn.c index 0d9da441b1..bb35bec726 100644 --- a/apps/plugins/chessbox/chessbox_pgn.c +++ b/apps/plugins/chessbox/chessbox_pgn.c @@ -678,7 +678,6 @@ struct pgn_game_node* pgn_show_game_list(struct pgn_game_node* first_game){ if (rb->global_settings->talk_menu) rb->gui_synclist_set_voice_callback(&games_list, speak_game_selection); rb->gui_synclist_set_nb_items(&games_list, i); - rb->gui_synclist_limit_scroll(&games_list, true); rb->gui_synclist_select_item(&games_list, 0); rb->gui_synclist_draw(&games_list); @@ -687,9 +686,8 @@ struct pgn_game_node* pgn_show_game_list(struct pgn_game_node* first_game){ while (true) { curr_selection = rb->gui_synclist_get_sel_pos(&games_list); button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&games_list,&button,LIST_WRAP_OFF)){ + if (rb->gui_synclist_do_button(&games_list, &button)) continue; - } switch (button) { case ACTION_STD_OK: return get_game_info(curr_selection, first_game); diff --git a/apps/plugins/chessbox/chessbox_pgn.h b/apps/plugins/chessbox/chessbox_pgn.h index 9d4f369ecc..7d0cb795cf 100644 --- a/apps/plugins/chessbox/chessbox_pgn.h +++ b/apps/plugins/chessbox/chessbox_pgn.h @@ -33,7 +33,7 @@ #define CB_PLAY (BUTTON_SELECT | BUTTON_PLAY) #define CB_LEVEL (BUTTON_SELECT | BUTTON_RIGHT) #define CB_RESTART (BUTTON_SELECT | BUTTON_LEFT) -#define CB_MENU (BUTTON_SELECT | BUTTON_MENU) +#define CB_MENU (BUTTON_SELECT | BUTTON_REPEAT) #define CB_SCROLL_UP (BUTTON_SCROLL_FWD|BUTTON_REPEAT) #define CB_SCROLL_DOWN (BUTTON_SCROLL_BACK|BUTTON_REPEAT) #define CB_SCROLL_LEFT (BUTTON_LEFT|BUTTON_REPEAT) diff --git a/apps/plugins/chopper.c b/apps/plugins/chopper.c index 70763a1b67..78cc292147 100644 --- a/apps/plugins/chopper.c +++ b/apps/plugins/chopper.c @@ -523,19 +523,18 @@ static void chopAddBlock(int x,int y,int sx,int sy, int indexOverride) static void chopAddParticle(int x,int y,int sx,int sy) { - int i=0; - - while(mParticles[i].bIsActive && i < NUMBER_OF_PARTICLES) - i++; - - if(i==NUMBER_OF_PARTICLES) - return; - - mParticles[i].bIsActive = 1; - mParticles[i].iWorldX = x; - mParticles[i].iWorldY = y; - mParticles[i].iSpeedX = sx; - mParticles[i].iSpeedY = sy; + for(int i = 0; i < NUMBER_OF_PARTICLES; ++i) + { + if(!mParticles[i].bIsActive) + { + mParticles[i].bIsActive = 1; + mParticles[i].iWorldX = x; + mParticles[i].iWorldY = y; + mParticles[i].iSpeedX = sx; + mParticles[i].iSpeedY = sy; + return; + } + } } static void chopGenerateBlockIfNeeded(void) @@ -785,7 +784,7 @@ static int chopMenu(int menunum) res = -1; break; case 2: - rb->set_option("Level", &iLevelMode, INT, levels, 2, NULL); + rb->set_option("Level", &iLevelMode, RB_INT, levels, 2, NULL); break; case 3: playback_control(NULL); @@ -1084,10 +1083,10 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_set_foreground(LCD_WHITE); #endif -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + rb->srand( *rb->current_tick ); @@ -1099,10 +1098,10 @@ enum plugin_status plugin_start(const void* parameter) configfile_save(CFG_FILE, config, 1, 0); rb->lcd_setfont(FONT_UI); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return ret; } diff --git a/apps/plugins/clix.c b/apps/plugins/clix.c index 9cd66a8034..06813f3a4d 100644 --- a/apps/plugins/clix.c +++ b/apps/plugins/clix.c @@ -66,9 +66,7 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define CLIX_BUTTON_QUIT (BUTTON_SELECT | BUTTON_MENU) -#define CLIX_BUTTON_UP BUTTON_MENU -#define CLIX_BUTTON_DOWN BUTTON_PLAY +#define CLIX_BUTTON_QUIT BUTTON_MENU #define CLIX_BUTTON_SCROLL_FWD BUTTON_SCROLL_FWD #define CLIX_BUTTON_SCROLL_BACK BUTTON_SCROLL_BACK #define CLIX_BUTTON_CLICK BUTTON_SELECT @@ -921,8 +919,10 @@ static int clix_handle_game(struct clix_game_state_t* state) case CLIX_BUTTON_SCROLL_BACK: case CLIX_BUTTON_SCROLL_BACK|BUTTON_REPEAT: #endif +#ifdef CLIX_BUTTON_UP case CLIX_BUTTON_UP: case CLIX_BUTTON_UP|BUTTON_REPEAT: +#endif if( state->y == 0 || state->board[ XYPOS( state->x, state->y - 1)] == CC_BLACK @@ -938,8 +938,10 @@ static int clix_handle_game(struct clix_game_state_t* state) case CLIX_BUTTON_SCROLL_FWD: case CLIX_BUTTON_SCROLL_FWD|BUTTON_REPEAT: #endif +#ifdef CLIX_BUTTON_DOWN case CLIX_BUTTON_DOWN: case CLIX_BUTTON_DOWN|BUTTON_REPEAT: +#endif if( state->y == (BOARD_HEIGHT - 1)) state->y = 0; else diff --git a/apps/plugins/clock/clock.c b/apps/plugins/clock/clock.c index d287c75598..c61b466aba 100644 --- a/apps/plugins/clock/clock.c +++ b/apps/plugins/clock/clock.c @@ -44,13 +44,19 @@ const struct button_mapping* plugin_contexts[]={ #define ACTION_COUNTER_TOGGLE PLA_SELECT #define ACTION_COUNTER_RESET PLA_SELECT_REPEAT -#define ACTION_MENU PLA_CANCEL #define ACTION_MODE_NEXT PLA_RIGHT #define ACTION_MODE_NEXT_REPEAT PLA_RIGHT_REPEAT #define ACTION_MODE_PREV PLA_LEFT #define ACTION_MODE_PREV_REPEAT PLA_LEFT_REPEAT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define ACTION_MENU PLA_UP +#else +#define ACTION_MENU PLA_CANCEL #define ACTION_SKIN_NEXT PLA_UP #define ACTION_SKIN_NEXT_REPEAT PLA_UP_REPEAT +#endif #define ACTION_SKIN_PREV PLA_DOWN #define ACTION_SKIN_PREV_REPEAT PLA_DOWN_REPEAT @@ -165,10 +171,12 @@ enum plugin_status plugin_start(const void* parameter){ case ACTION_SKIN_PREV: clock_settings_skin_next(&clock_settings); break; +#if defined(ACTION_SKIN_NEXT) && defined(ACTION_SKIN_NEXT_REPEAT) case ACTION_SKIN_NEXT_REPEAT: case ACTION_SKIN_NEXT: clock_settings_skin_previous(&clock_settings); break; +#endif case ACTION_MENU: clock_draw_restore_colors(); exit_clock=main_menu(); diff --git a/apps/plugins/clock/clock_menu.c b/apps/plugins/clock/clock_menu.c index a597664f49..8b7a2c82be 100644 --- a/apps/plugins/clock/clock_menu.c +++ b/apps/plugins/clock/clock_menu.c @@ -81,17 +81,17 @@ static void menu_analog_settings(void) switch(result){ case 0: rb->set_option("Show Date", &clock_settings.analog.show_date, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; case 1: rb->set_option("Show Second Hand", &clock_settings.analog.show_seconds, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; case 2: rb->set_option("Show Border", &clock_settings.analog.show_border, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; } } @@ -112,12 +112,12 @@ static void menu_digital_settings(void){ case 0: rb->set_option("Show Seconds", &clock_settings.digital.show_seconds, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; case 1: rb->set_option("Blinking Colon", &clock_settings.digital.blinkcolon, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; } } @@ -129,7 +129,7 @@ static void menu_digital_settings(void){ static void confirm_reset(void){ int result=0; - rb->set_option("Reset all settings?", &result, INT, noyes_text, 2, NULL); + rb->set_option("Reset all settings?", &result, RB_INT, noyes_text, 2, NULL); if(result == 1){ /* reset! */ clock_settings_reset(&clock_settings); @@ -157,16 +157,16 @@ static void menu_general_settings(void){ case 0: rb->set_option("Hour format", &clock_settings.general.hour_format, - INT, hour_format_text, 2, NULL); + RB_INT, hour_format_text, 2, NULL); break; case 1: rb->set_option("Date format", &clock_settings.general.date_format, - INT, date_format_text, 4, NULL); + RB_INT, date_format_text, 4, NULL); break; case 2: rb->set_option("Show Counter", &clock_settings.general.show_counter, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); break; case 3: confirm_reset(); @@ -180,7 +180,7 @@ static void menu_general_settings(void){ case 5: rb->set_option("Save On Exit", &clock_settings.general.save_settings, - BOOL, noyes_text, 2, NULL); + RB_BOOL, noyes_text, 2, NULL); /* if we no longer save on exit, we better save now to remember that */ @@ -190,14 +190,14 @@ static void menu_general_settings(void){ case 6: rb->set_option("Backlight Settings", &clock_settings.general.backlight, - INT, backlight_settings_text, 3, NULL); + RB_INT, backlight_settings_text, 3, NULL); apply_backlight_setting(clock_settings.general.backlight); break; case 7: rb->set_option("Idle Poweroff (temporary)", &clock_settings.general.idle_poweroff, - BOOL, idle_poweroff_text, 2, NULL); + RB_BOOL, idle_poweroff_text, 2, NULL); break; } } diff --git a/apps/plugins/codebuster.c b/apps/plugins/codebuster.c index 956991575d..401ce6085f 100644 --- a/apps/plugins/codebuster.c +++ b/apps/plugins/codebuster.c @@ -438,6 +438,13 @@ enum plugin_status plugin_start(const void* parameter) { if (button == PLA_SELECT) break; +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + if (button == PLA_UP) /* Menu button */ + button = PLA_CANCEL; +#endif + switch (button) { /* Exit */ diff --git a/apps/plugins/credits.c b/apps/plugins/credits.c index 9e43aab2a7..f4716651fd 100644 --- a/apps/plugins/credits.c +++ b/apps/plugins/credits.c @@ -21,7 +21,19 @@ #include "plugin.h" #include "lib/helper.h" - +#ifdef HAVE_REMOTE_LCD +#define REMOTE_WIDTH LCD_REMOTE_WIDTH +#define REMOTE_HEIGHT LCD_REMOTE_HEIGHT +#include "pluginbitmaps/remote_creditslogo.h" +#define REMOTE_LOGO_WIDTH BMPWIDTH_remote_creditslogo +#define REMOTE_LOGO_HEIGHT BMPHEIGHT_remote_creditslogo +#define REMOTE_LOGO (const fb_remote_data*)remote_creditslogo +#endif /* HAVE_REMOTE_LCD */ + +#define LOGO (const fb_data*)creditslogo +#include "pluginbitmaps/creditslogo.h" +#define LOGO_WIDTH BMPWIDTH_creditslogo +#define LOGO_HEIGHT BMPHEIGHT_creditslogo static const char* const credits[] = { #include "credits.raw" /* generated list of names from docs/CREDITS */ @@ -299,29 +311,78 @@ static void roll_credits(void) } } +int show_logo(void) +{ + unsigned char version[32]; + int font_h, ver_w; + rb->snprintf(version, sizeof(version), "Ver. %s", rb->rbversion); + ver_w = rb->font_getstringsize(version, NULL, &font_h, FONT_SYSFIXED); + rb->lcd_clear_display(); + rb->lcd_setfont(FONT_SYSFIXED); +#if defined(SANSA_CLIP) || defined(SANSA_CLIPV2) || defined(SANSA_CLIPPLUS) + /* display the logo in the blue area of the screen (bottom 48 pixels) */ + if (ver_w > LCD_WIDTH) + rb->lcd_puts_scroll(0, 0, version); + else + rb->lcd_putsxy((LCD_WIDTH/2) - (ver_w/2), 0, version); + rb->lcd_bitmap(LOGO, (LCD_WIDTH - LOGO_WIDTH) / 2, 16, + LOGO_WIDTH, LOGO_HEIGHT); +#else + rb->lcd_bitmap(LOGO, (LCD_WIDTH - LOGO_WIDTH) / 2, 10, + LOGO_WIDTH, LOGO_HEIGHT); + if (ver_w > LCD_WIDTH) + rb->lcd_puts_scroll(0, (LCD_HEIGHT-font_h) / font_h, version); + else + rb->lcd_putsxy((LCD_WIDTH/2) - (ver_w/2), LCD_HEIGHT-font_h, version); +#endif + rb->lcd_setfont(FONT_UI); + rb->lcd_update(); +#ifdef HAVE_REMOTE_LCD + rb->lcd_remote_clear_display(); + rb->lcd_remote_bitmap(REMOTE_LOGO, 0, 10, + REMOTE_LOGO_WIDTH, REMOTE_LOGO_HEIGHT); + rb->lcd_remote_setfont(FONT_SYSFIXED); + if (ver_w > LCD_REMOTE_WIDTH) + rb->lcd_remote_puts_scroll(0, (LCD_REMOTE_HEIGHT-font_h) / font_h, version); + else + rb->lcd_remote_putsxy((LCD_REMOTE_WIDTH/2) - (ver_w/2), + LCD_REMOTE_HEIGHT-font_h, version); + rb->lcd_remote_setfont(FONT_UI); + rb->lcd_remote_update(); +#endif + + return 0; +} + enum plugin_status plugin_start(const void* parameter) { (void)parameter; -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif -#if LCD_DEPTH>=16 +#if LCD_DEPTH >= 16 rb->lcd_set_foreground (LCD_WHITE); rb->lcd_set_background (LCD_BLACK); #endif - rb->show_logo(); - /* Show the logo for about 3 secs allowing the user to stop */ - if(!rb->action_userabort(3*HZ)) + show_logo(); + + /* Show the logo for about 5 secs allowing the user to stop */ + if(!rb->action_userabort(5*HZ)) + { + rb->lcd_scroll_stop(); roll_credits(); + } + +#ifdef HAVE_REMOTE_LCD + rb->lcd_remote_scroll_stop(); +#endif -#ifdef HAVE_BACKLIGHT /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return PLUGIN_OK; } diff --git a/apps/plugins/cube.c b/apps/plugins/cube.c index dfd7df8951..43318f5aee 100644 --- a/apps/plugins/cube.c +++ b/apps/plugins/cube.c @@ -52,12 +52,14 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define CUBE_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define CUBE_QUIT_PRE BUTTON_MENU +#define CUBE_QUIT (BUTTON_MENU | BUTTON_REL) #define CUBE_NEXT BUTTON_RIGHT #define CUBE_PREV BUTTON_LEFT #define CUBE_INC BUTTON_SCROLL_FWD #define CUBE_DEC BUTTON_SCROLL_BACK -#define CUBE_MODE BUTTON_MENU +#define CUBE_MODE_PRE BUTTON_MENU +#define CUBE_MODE (BUTTON_MENU | BUTTON_REPEAT) #define CUBE_PAUSE BUTTON_PLAY #define CUBE_HIGHSPEED_PRE BUTTON_SELECT #define CUBE_HIGHSPEED (BUTTON_SELECT | BUTTON_REL) @@ -729,6 +731,7 @@ enum plugin_status plugin_start(const void* parameter) int button; #if defined(CUBE_MODE_PRE) || \ + defined(CUBE_QUIT_PRE) || \ defined(CUBE_PAUSE_PRE) || \ defined(CUBE_HIGHSPEED_PRE) int lastbutton = BUTTON_NONE; @@ -903,6 +906,10 @@ enum plugin_status plugin_start(const void* parameter) case CUBE_RC_QUIT: #endif case CUBE_QUIT: +#ifdef CUBE_QUIT_PRE + if (lastbutton != CUBE_QUIT_PRE) + break; +#endif quit = true; break; @@ -911,6 +918,7 @@ enum plugin_status plugin_start(const void* parameter) break; } #if defined(CUBE_MODE_PRE) || \ + defined(CUBE_QUIT_PRE) || \ defined(CUBE_PAUSE_PRE) || \ defined(CUBE_HIGHSPEED_PRE) if (button != BUTTON_NONE) diff --git a/apps/plugins/dart_scorer.c b/apps/plugins/dart_scorer.c new file mode 100644 index 0000000000..1e8dd8f37b --- /dev/null +++ b/apps/plugins/dart_scorer.c @@ -0,0 +1,601 @@ +#include "plugin.h" +#include "lib/display_text.h" +#include "lib/helper.h" +#include "lib/playback_control.h" +#include "lib/pluginlib_exit.h" +#include "lib/pluginlib_actions.h" + +#define BUTTON_ROWS 6 +#define BUTTON_COLS 5 + +#define REC_HEIGHT (int)(LCD_HEIGHT / (BUTTON_ROWS + 1)) +#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS) + +#define Y_7_POS (LCD_HEIGHT) /* Leave room for the border */ +#define Y_6_POS (Y_7_POS - REC_HEIGHT) /* y6 = 63 */ +#define Y_5_POS (Y_6_POS - REC_HEIGHT) /* y5 = 53 */ +#define Y_4_POS (Y_5_POS - REC_HEIGHT) /* y4 = 43 */ +#define Y_3_POS (Y_4_POS - REC_HEIGHT) /* y3 = 33 */ +#define Y_2_POS (Y_3_POS - REC_HEIGHT) /* y2 = 23 */ +#define Y_1_POS (Y_2_POS - REC_HEIGHT) /* y1 = 13 */ +#define Y_0_POS 0 /* y0 = 0 */ + +#define X_0_POS 0 /* x0 = 0 */ +#define X_1_POS (X_0_POS + REC_WIDTH) /* x1 = 22 */ +#define X_2_POS (X_1_POS + REC_WIDTH) /* x2 = 44 */ +#define X_3_POS (X_2_POS + REC_WIDTH) /* x3 = 66 */ +#define X_4_POS (X_3_POS + REC_WIDTH) /* x4 = 88 */ +#define X_5_POS (X_4_POS + REC_WIDTH) /* x5 = 110, column 111 left blank */ + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define DARTS_QUIT PLA_SELECT_REPEAT +#else +#define DARTS_QUIT PLA_CANCEL +#endif +#define DARTS_SELECT PLA_SELECT +#define DARTS_RIGHT PLA_RIGHT +#define DARTS_LEFT PLA_LEFT +#define DARTS_UP PLA_UP +#define DARTS_DOWN PLA_DOWN +#define DARTS_RRIGHT PLA_RIGHT_REPEAT +#define DARTS_RLEFT PLA_LEFT_REPEAT +#define DARTS_RUP PLA_UP_REPEAT +#define DARTS_RDOWN PLA_DOWN_REPEAT + +#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/dart_scorer.save" +/* leave first line blank on bitmap display, for pause icon */ +#define FIRST_LINE 1 + +#define NUM_PLAYERS 2 +#define MAX_UNDO 100 + +static const struct button_mapping *plugin_contexts[] = {pla_main_ctx}; + +/* game data structures */ +enum game_mode +{ + five, + three +}; +static struct settings_struct +{ + enum game_mode mode; + int scores[2]; + bool turn; + int throws; + int history[MAX_UNDO]; + int history_ptr; +} settings; + +/* temporary data */ +static bool loaded = false; /* has a save been loaded? */ +int btn_row, btn_col; /* current position index for button */ +int prev_btn_row, prev_btn_col; /* previous cursor position */ +unsigned char *buttonChar[6][5] = { + {"", "Single", "Double", "Triple", ""}, + {"1", "2", "3", "4", "5"}, + {"6", "7", "8", "9", "10"}, + {"11", "12", "13", "14", "15"}, + {"16", "17", "18", "19", "20"}, + {"", "Missed", "Bull", "Undo", ""}}; +int modifier; + +static int do_dart_scorer_pause_menu(void); +static void drawButtons(void); + +/* First, increases *dimen1 by dimen1_delta modulo dimen1_modulo. + If dimen1 wraps, increases *dimen2 by dimen2_delta modulo dimen2_modulo. +*/ +static void move_with_wrap_and_shift( + int *dimen1, int dimen1_delta, int dimen1_modulo, + int *dimen2, int dimen2_delta, int dimen2_modulo) +{ + bool wrapped = false; + + *dimen1 += dimen1_delta; + if (*dimen1 < 0) + { + *dimen1 = dimen1_modulo - 1; + wrapped = true; + } + else if (*dimen1 >= dimen1_modulo) + { + *dimen1 = 0; + wrapped = true; + } + + if (wrapped) + { + /* Make the dividend always positive to be sure about the result. + Adding dimen2_modulo does not change it since we do it modulo. */ + *dimen2 = (*dimen2 + dimen2_modulo + dimen2_delta) % dimen2_modulo; + } +} + +static void drawButtons() +{ + int i, j, w, h; + for (i = 0; i <= 5; i++) + { + for (j = 0; j <= 4; j++) + { + unsigned char button_text[16]; + char *selected_prefix = (i == 0 && modifier > 0 && j == modifier) ? "*" : ""; + rb->snprintf(button_text, sizeof(button_text), "%s%s", selected_prefix, buttonChar[i][j]); + rb->lcd_getstringsize(button_text, &w, &h); + if (i == btn_row && j == btn_col) /* selected item */ + rb->lcd_set_drawmode(DRMODE_SOLID); + else + rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + rb->lcd_fillrect(X_0_POS + j * REC_WIDTH, + Y_1_POS + i * REC_HEIGHT, + REC_WIDTH, REC_HEIGHT + 1); + if (i == btn_row && j == btn_col) /* selected item */ + rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + else + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_putsxy(X_0_POS + j * REC_WIDTH + (REC_WIDTH - w) / 2, + Y_1_POS + i * REC_HEIGHT + (REC_HEIGHT - h) / 2 + 1, + button_text); + } + } + rb->lcd_set_drawmode(DRMODE_SOLID); +} + +static void draw(void) +{ + rb->lcd_clear_display(); + + char buf[32]; + + int x = 5; + int y = 10; + for (int i = 0; i < NUM_PLAYERS; ++i, x = x + 95) + { + char *turn_marker = (i == settings.turn) ? "*" : ""; + rb->snprintf(buf, sizeof(buf), "%sPlayer %d: %d", turn_marker, i + 1, settings.scores[i]); + rb->lcd_putsxy(x, y, buf); + } + int throws_x = (LCD_WIDTH / 2) - 10; + char throws_buf[3]; + for (int i = 0; i < settings.throws; ++i, throws_x += 5) + { + rb->strcat(throws_buf, "1"); + rb->lcd_putsxy(throws_x, y, "|"); + } + + drawButtons(); + + rb->lcd_update(); +} + +/***************************************************************************** +* save_game() saves the current game state. +******************************************************************************/ +static void save_game(void) +{ + int fd = rb->open(RESUME_FILE, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + return; + + rb->write(fd, &settings, sizeof(struct settings_struct)); + + rb->close(fd); + rb->lcd_update(); +} + +/* load_game() loads the saved game and returns load success.*/ +static bool load_game(void) +{ + signed int fd; + bool loaded = false; + + /* open game file */ + fd = rb->open(RESUME_FILE, O_RDONLY); + if (fd < 0) + return false; + + /* read in saved game */ + if (rb->read(fd, &settings, sizeof(struct settings_struct)) == (long)sizeof(struct settings_struct)) + { + loaded = true; + } + + rb->close(fd); + + return loaded; + return false; +} + +/* asks the user if they wish to quit */ +static bool confirm_quit(void) +{ + const struct text_message prompt = {(const char *[]){"Are you sure?", "This will clear your current game."}, 2}; + enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); + if (response == YESNO_NO) + return false; + else + return true; +} + +/* displays the help text */ +static bool do_help(void) +{ + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + + rb->lcd_setfont(FONT_UI); + + static char *help_text[] = {"Dart Scorer", "", "", "Keep score of your darts game."}; + + struct style_text style[] = { + {0, TEXT_CENTER | TEXT_UNDERLINE}, + }; + + return display_text(ARRAYLEN(help_text), help_text, style, NULL, true); +} + +static void undo(void) +{ + if (!settings.history_ptr) + { + rb->splash(HZ * 2, "Out of undos!"); + return; + } + + /* jumping back to previous player? */ + int turn = settings.throws == 3 ? !settings.turn : settings.turn; + if (turn != settings.turn) + { + settings.throws = 0; + settings.turn ^= true; + } + + if (settings.history[settings.history_ptr - 1] >= 0) + { + settings.scores[turn] += settings.history[--settings.history_ptr]; + ++settings.throws; + } + else + { + /* + negative history means we bust. negative filled for all skipped throws + from being bust so consume back until no more + */ + for (; settings.throws < 3 && settings.history[settings.history_ptr - 1] < 0; --settings.history_ptr, ++settings.throws) + { + } + } +} + +static void init_game(bool newgame) +{ + if (newgame) + { + /* initialize the game context */ + modifier = 1; + btn_row = 1; + btn_col = 0; + + int game_mode = -1; + MENUITEM_STRINGLIST(menu, "Game Mode", NULL, "501", "301"); + while (game_mode < 0) + { + switch (rb->do_menu(&menu, &game_mode, NULL, false)) + { + case 0: + { + settings.mode = five; + break; + } + case 1: + { + settings.mode = three; + break; + } + } + + for (int i = 0; i < NUM_PLAYERS; ++i) + { + settings.scores[i] = (settings.mode == five) ? 501 : 301; + } + settings.turn = false; + settings.throws = 3; + settings.history_ptr = 0; + rb->lcd_clear_display(); + } + } +} + +/* main game loop */ +static enum plugin_status do_game(bool newgame) +{ + init_game(newgame); + draw(); + + while (1) + { + /* wait for button press */ + int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); + unsigned char *selected = buttonChar[btn_row][btn_col]; + switch (button) + { + case DARTS_SELECT: + if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "Single"))) + modifier = 1; + else if (!rb->strcmp(selected, "Double")) + modifier = 2; + else if (!rb->strcmp(selected, "Triple")) + modifier = 3; + else if (!rb->strcmp(selected, "Undo")) + { + undo(); + continue; + } + else + { + /* main logic of score keeping */ + if (modifier == 0) + modifier = 1; + int hit = (!rb->strcmp(selected, "Bull")) ? 25 : rb->atoi(selected); + if (hit == 25 && modifier == 3) + { + /* no triple bullseye! */ + rb->splash(HZ * 2, "Triple Bull... Don't be silly!"); + continue; + } + hit *= modifier; + if (hit > settings.scores[settings.turn]) + { + rb->splash(HZ * 2, "Bust! End of turn."); + for (int i = 0; i < settings.throws; ++i) + settings.history[settings.history_ptr++] = -1; + settings.throws = 0; + } + else if (hit == settings.scores[settings.turn] - 1) + { + rb->splash(HZ * 2, "1 left! Must checkout with a double. End of turn."); + for (int i = 0; i < settings.throws; ++i) + settings.history[settings.history_ptr++] = -1; + settings.throws = 0; + } + else if (hit == settings.scores[settings.turn] && modifier != 2) + { + rb->splash(HZ * 2, "Must checkout with a double! End of turn."); + for (int i = 0; i < settings.throws; ++i) + settings.history[settings.history_ptr++] = -1; + settings.throws = 0; + } + else + { + settings.scores[settings.turn] -= hit; + --settings.throws; + settings.history[settings.history_ptr++] = hit; + modifier = 1; + if (!settings.scores[settings.turn]) + goto GAMEOVER; + } + + if (!settings.throws) + { + settings.throws = 3; + settings.turn ^= true; + } + } + break; + case DARTS_LEFT: + case DARTS_RLEFT: + move_with_wrap_and_shift( + &btn_col, -1, BUTTON_COLS, + &btn_row, 0, BUTTON_ROWS); + break; + case DARTS_RIGHT: + case DARTS_RRIGHT: + move_with_wrap_and_shift( + &btn_col, 1, BUTTON_COLS, + &btn_row, 0, BUTTON_ROWS); + break; +#ifdef DARTS_UP + case DARTS_UP: + case DARTS_RUP: +#ifdef HAVE_SCROLLWHEEL + case PLA_SCROLL_BACK: + case PLA_SCROLL_BACK_REPEAT: +#endif + move_with_wrap_and_shift( + &btn_row, -1, BUTTON_ROWS, + &btn_col, 0, BUTTON_COLS); + break; +#endif +#ifdef DARTS_DOWN + case DARTS_DOWN: + case DARTS_RDOWN: +#ifdef HAVE_SCROLLWHEEL + case PLA_SCROLL_FWD: + case PLA_SCROLL_FWD_REPEAT: +#endif + move_with_wrap_and_shift( + &btn_row, 1, BUTTON_ROWS, + &btn_col, 0, BUTTON_COLS); + break; +#endif + case DARTS_QUIT: + switch (do_dart_scorer_pause_menu()) + { + case 0: /* resume */ + break; + case 1: + init_game(true); + continue; + case 2: /* quit w/o saving */ + rb->remove(RESUME_FILE); + return PLUGIN_ERROR; + case 3: /* save & quit */ + save_game(); + return PLUGIN_ERROR; + break; + } + break; + default: + { + exit_on_usb(button); /* handle poweroff and USB */ + break; + } + } + draw(); + } + +GAMEOVER: + rb->splashf(HZ * 3, "Gameover. Player %d wins!", settings.turn + 1); + + return PLUGIN_OK; +} + +/* decide if this_item should be shown in the main menu */ +/* used to hide resume option when there is no save */ +static int mainmenu_cb(int action, + const struct menu_item_ex *this_item, + struct gui_synclist *this_list) +{ + (void)this_list; + int idx = ((intptr_t)this_item); + if (action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5)) + return ACTION_EXIT_MENUITEM; + return action; +} + +/* show the pause menu */ +static int do_dart_scorer_pause_menu(void) +{ + int sel = 0; + MENUITEM_STRINGLIST(menu, "Dart Scorer", NULL, + "Resume Game", + "Start New Game", + "Playback Control", + "Help", + "Quit without Saving", + "Quit"); + while (1) + { + switch (rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + { + rb->splash(HZ * 2, "Resume"); + return 0; + } + case 1: + { + if (!confirm_quit()) + break; + else + { + rb->splash(HZ * 2, "New Game"); + return 1; + } + } + case 2: + playback_control(NULL); + break; + case 3: + do_help(); + break; + case 4: /* quit w/o saving */ + { + if (!confirm_quit()) + break; + else + { + return 2; + } + } + case 5: + return 3; + default: + break; + } + } +} + +/* show the main menu */ +static enum plugin_status do_dart_scorer_menu(void) +{ + int sel = 0; + loaded = load_game(); + MENUITEM_STRINGLIST(menu, + "Dart Scorer Menu", + mainmenu_cb, + "Resume Game", + "Start New Game", + "Playback Control", + "Help", + "Quit without Saving", + "Quit"); + while (true) + { + switch (rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: /* Start new game or resume a game */ + case 1: + { + if (sel == 1 && loaded) + { + if (!confirm_quit()) + break; + } + enum plugin_status ret = do_game(sel == 1); + switch (ret) + { + case PLUGIN_OK: + { + loaded = false; + rb->remove(RESUME_FILE); + break; + } + case PLUGIN_USB_CONNECTED: + save_game(); + return ret; + case PLUGIN_ERROR: /* exit without menu */ + return PLUGIN_OK; + default: + break; + } + break; + } + case 2: + playback_control(NULL); + break; + case 3: + do_help(); + break; + case 4: + if (confirm_quit()) + return PLUGIN_OK; + break; + case 5: + if (loaded) + save_game(); + return PLUGIN_OK; + default: + break; + } + } +} + +/* prepares for exit */ +static void cleanup(void) +{ + backlight_use_settings(); +} + +enum plugin_status plugin_start(const void *parameter) +{ + (void)parameter; + /* now start the game menu */ + enum plugin_status ret = do_dart_scorer_menu(); + cleanup(); + return ret; +} diff --git a/apps/plugins/demystify.c b/apps/plugins/demystify.c index 74537e94f8..cb8e4ddf72 100644 --- a/apps/plugins/demystify.c +++ b/apps/plugins/demystify.c @@ -37,26 +37,42 @@ #define MIN_POLYGONS 1 /* Key assignement */ -#define DEMYSTIFY_QUIT PLA_CANCEL #ifdef HAVE_SCROLLWHEEL +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define DEMYSTIFY_QUIT PLA_UP +#else +#define DEMYSTIFY_QUIT PLA_CANCEL +#endif + #define DEMYSTIFY_INCREASE_SPEED PLA_SCROLL_FWD #define DEMYSTIFY_DECREASE_SPEED PLA_SCROLL_BACK #define DEMYSTIFY_INCREASE_SPEED_REPEAT PLA_SCROLL_FWD_REPEAT #define DEMYSTIFY_DECREASE_SPEED_REPEAT PLA_SCROLL_BACK_REPEAT + +#define DEMYSTIFY_ADD_POLYGON PLA_RIGHT +#define DEMYSTIFY_REMOVE_POLYGON PLA_LEFT +#define DEMYSTIFY_ADD_POLYGON_REPEAT PLA_RIGHT_REPEAT +#define DEMYSTIFY_REMOVE_POLYGON_REPEAT PLA_LEFT_REPEAT + #else + +#define DEMYSTIFY_QUIT PLA_CANCEL + #define DEMYSTIFY_INCREASE_SPEED PLA_RIGHT #define DEMYSTIFY_DECREASE_SPEED PLA_LEFT #define DEMYSTIFY_INCREASE_SPEED_REPEAT PLA_RIGHT_REPEAT #define DEMYSTIFY_DECREASE_SPEED_REPEAT PLA_LEFT_REPEAT -#endif #define DEMYSTIFY_ADD_POLYGON PLA_UP #define DEMYSTIFY_REMOVE_POLYGON PLA_DOWN #define DEMYSTIFY_ADD_POLYGON_REPEAT PLA_UP_REPEAT #define DEMYSTIFY_REMOVE_POLYGON_REPEAT PLA_DOWN_REPEAT +#endif const struct button_mapping *plugin_contexts[] = {pla_main_ctx, #if defined(HAVE_REMOTE_LCD) @@ -262,12 +278,10 @@ static void polygons_draw(struct polygon_fifo * polygons, struct screen * displa static void cleanup(void) { -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#ifdef HAVE_REMOTE_LCD remote_backlight_use_settings(); -#endif -#endif + } #ifdef HAVE_LCD_COLOR @@ -438,12 +452,10 @@ enum plugin_status plugin_start(const void* parameter) #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#ifdef HAVE_REMOTE_LCD remote_backlight_ignore_timeout(); -#endif -#endif + ret = plugin_main(); return ret; diff --git a/apps/plugins/dice.c b/apps/plugins/dice.c index 622c58d71d..8f2315cde8 100644 --- a/apps/plugins/dice.c +++ b/apps/plugins/dice.c @@ -28,7 +28,14 @@ #define INITIAL_NB_DICES 1 #define INITIAL_NB_SIDES 2 /* corresponds to 6 sides in the array */ + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define DICE_QUIT PLA_UP +#else #define DICE_QUIT PLA_CANCEL +#endif #define DICE_ROLL PLA_SELECT @@ -194,7 +201,7 @@ static bool dice_menu(struct dices * dice) { break; case 2: - rb->set_option("Number of Sides", &sides_index, INT, + rb->set_option("Number of Sides", &sides_index, RB_INT, nb_sides_option, sizeof(nb_sides_values)/sizeof(int), NULL); dice->nb_sides=nb_sides_values[sides_index]; diff --git a/apps/plugins/disktidy.c b/apps/plugins/disktidy.c index 4f2bf6e992..e018c3f80d 100644 --- a/apps/plugins/disktidy.c +++ b/apps/plugins/disktidy.c @@ -176,7 +176,7 @@ static void tidy_load_file(const char* file) str++; unsigned i = find_file_string(str, last_group); - tidy_types[i].remove = rb->strcmp(remove, "yes"); + tidy_types[i].remove = !rb->strcmp(remove, "yes"); if (i >= tidy_type_count) { diff --git a/apps/plugins/doom/d_deh.c b/apps/plugins/doom/d_deh.c index 1a399e3b49..0a67aa0aad 100644 --- a/apps/plugins/doom/d_deh.c +++ b/apps/plugins/doom/d_deh.c @@ -63,7 +63,7 @@ char* strlwr(char* str) typedef struct { const byte *inp; // Pointer to string size_t size; // Bytes remaining in string - int fd; // Current file descriptor + int fd; // Current file descriptor } DEHFILE; // killough 10/98: emulate IO whether input really comes from a file or not @@ -2868,6 +2868,7 @@ boolean deh_GetData(char *s, char *k, uint_64_t *l, char **strval, int fpout) if (*t == '=') break; buffer[i] = *t; // copy it } + if (i == 0) i = 1; /* Just in case */ buffer[--i] = '\0'; // terminate the key before the '=' if (!*t) // end of string with no equal sign { diff --git a/apps/plugins/doom/i_video.c b/apps/plugins/doom/i_video.c index f5d07a8354..79f3212467 100644 --- a/apps/plugins/doom/i_video.c +++ b/apps/plugins/doom/i_video.c @@ -453,6 +453,9 @@ void I_ShutdownGraphics(void) #define DOOMBUTTON_MAP BUTTON_BOTTOMRIGHT #elif CONFIG_KEYPAD == SANSA_CONNECT_PAD +#define DOOMBUTTON_SCROLLWHEEL +#define DOOMBUTTON_SCROLLWHEEL_CC BUTTON_SCROLL_BACK +#define DOOMBUTTON_SCROLLWHEEL_CW BUTTON_SCROLL_FWD #define DOOMBUTTON_UP BUTTON_UP #define DOOMBUTTON_DOWN BUTTON_DOWN #define DOOMBUTTON_LEFT BUTTON_LEFT diff --git a/apps/plugins/doom/rockdoom.c b/apps/plugins/doom/rockdoom.c index 6594859c08..04817d4722 100644 --- a/apps/plugins/doom/rockdoom.c +++ b/apps/plugins/doom/rockdoom.c @@ -490,7 +490,7 @@ int Oset_keys() else { *keys[result]=translatekey(*keys[result]); - rb->set_option(menu_[result], keys[result], INT, doomkeys, numdoomkeys, NULL ); + rb->set_option(menu_[result], keys[result], RB_INT, doomkeys, numdoomkeys, NULL ); *keys[result]=translatekey(*keys[result]); } } @@ -540,7 +540,7 @@ static bool Doptions() if(result==0) Oset_keys(); else if (result > 0) - rb->set_option(menu_[result], options[result-1], INT, onoff, 2, NULL ); + rb->set_option(menu_[result], options[result-1], RB_INT, onoff, 2, NULL ); else menuquit=1; } @@ -620,7 +620,7 @@ int doom_menu() result = rb->do_menu(&menu, &selected, NULL, false); switch (result) { case 0: /* Game picker */ - rb->set_option("Game WAD", &gamever, INT, names, status, NULL ); + rb->set_option("Game WAD", &gamever, RB_INT, names, status, NULL ); break; case 1: /* Addon picker */ @@ -722,9 +722,9 @@ enum plugin_status plugin_start(const void* parameter) systemvol= rb->global_settings->volume-rb->global_settings->volume%mod; general_translucency = default_translucency; // phares -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + #ifdef RB_PROFILE rb->profile_thread(); #endif @@ -738,9 +738,9 @@ enum plugin_status plugin_start(const void* parameter) #ifdef RB_PROFILE rb->profstop(); #endif -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + M_SaveDefaults (); I_Quit(); // Make SURE everything was closed out right diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c index 35498227bf..4d36302ddf 100644 --- a/apps/plugins/fft/fft.c +++ b/apps/plugins/fft/fft.c @@ -40,13 +40,22 @@ GREY_INFO_STRUCT /* this set the context to use with PLA */ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; -#define FFT_PREV_GRAPH PLA_LEFT -#define FFT_NEXT_GRAPH PLA_RIGHT -#define FFT_ORIENTATION PLA_CANCEL -#define FFT_WINDOW PLA_SELECT -#define FFT_AMP_SCALE PLA_UP -#define FFT_FREQ_SCALE PLA_DOWN -#define FFT_QUIT PLA_EXIT +#define FFT_PREV_GRAPH PLA_LEFT +#define FFT_NEXT_GRAPH PLA_RIGHT +#define FFT_ORIENTATION PLA_CANCEL +#define FFT_WINDOW PLA_SELECT_REL +#define FFT_AMP_SCALE PLA_SELECT_REPEAT +#define FFT_AMP_SCALE_PRE PLA_SELECT +#define FFT_FREQ_SCALE PLA_DOWN + + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define FFT_QUIT PLA_UP +#else +#define FFT_QUIT PLA_EXIT +#endif #ifdef HAVE_LCD_COLOR #include "pluginbitmaps/fft_colors.h" @@ -1184,9 +1193,8 @@ static void fft_cleanup(void) #ifndef HAVE_LCD_COLOR grey_release(); #endif -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif /* save settings if changed */ if (rb->memcmp(&fft, &fft_disk, sizeof(fft))) @@ -1237,9 +1245,8 @@ static bool fft_setup(void) mylcd_clear_display(); myosd_lcd_update(); #endif -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->trigger_cpu_boost(); @@ -1259,6 +1266,10 @@ enum plugin_status plugin_start(const void* parameter) if(!fft_setup()) return PLUGIN_ERROR; +#if defined(FFT_AMP_SCALE_PRE) + int lastbutton = BUTTON_NONE; +#endif + while(run) { long delay = fft_draw(); @@ -1302,6 +1313,10 @@ enum plugin_status plugin_start(const void* parameter) break; case FFT_AMP_SCALE: +#ifdef FFT_AMP_SCALE_PRE + if (lastbutton != FFT_AMP_SCALE_PRE) + break; +#endif if (++fft.amp_scale >= FFT_MAX_AS) fft.amp_scale = FFT_MIN_AS; @@ -1330,6 +1345,11 @@ enum plugin_status plugin_start(const void* parameter) exit_on_usb(button); break; } + +#if defined(FFT_AMP_SCALE_PRE) + if (button != BUTTON_NONE) + lastbutton = button; +#endif } return PLUGIN_OK; diff --git a/apps/plugins/fire.c b/apps/plugins/fire.c index f3e6fb35e4..5cdf4a2d3a 100644 --- a/apps/plugins/fire.c +++ b/apps/plugins/fire.c @@ -56,10 +56,17 @@ const struct button_mapping* plugin_contexts[]= { }; #define FIRE_QUIT PLA_CANCEL -#define FIRE_QUIT2 PLA_EXIT #define FIRE_SWITCH_FLAMES_TYPE PLA_LEFT #define FIRE_SWITCH_FLAMES_MOVING PLA_RIGHT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define FIRE_QUIT2 PLA_UP +#else +#define FIRE_QUIT2 PLA_EXIT +#endif + #ifdef HAVE_SCROLLWHEEL #define FIRE_INCREASE_MULT PLA_SCROLL_FWD #define FIRE_INCREASE_MULT_REP PLA_SCROLL_FWD_REPEAT @@ -279,10 +286,9 @@ static void cleanup(void *parameter) #ifndef HAVE_LCD_COLOR grey_release(); #endif -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif } @@ -371,10 +377,9 @@ enum plugin_status plugin_start(const void* parameter) #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256) rb->lcd_set_mode(LCD_MODE_PAL256); diff --git a/apps/plugins/fireworks.c b/apps/plugins/fireworks.c index b7dad0d8ba..5f547ed5e0 100644 --- a/apps/plugins/fireworks.c +++ b/apps/plugins/fireworks.c @@ -35,10 +35,17 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; /* We use PLA */ #define BTN_EXIT PLA_EXIT -#define BTN_MENU PLA_CANCEL #define BTN_FIRE PLA_SELECT #define BTN_FIRE_REPEAT PLA_SELECT_REPEAT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define BTN_MENU PLA_UP +#else +#define BTN_MENU PLA_CANCEL +#endif + /* The lowdown on source terminology: * a ROCKET is launched from the LCD bottom. * FIREWORKs are ejected from the rocket when it explodes. */ @@ -305,32 +312,32 @@ static void fireworks_menu(void) break; case 1: - rb->set_option("Auto-Fire", &autofire_delay, INT, + rb->set_option("Auto-Fire", &autofire_delay, RB_INT, autofire_delay_settings, 15, NULL); break; case 2: rb->set_option("Particles Per Firework", &particles_per_firework, - INT, particle_settings, 8, NULL); + RB_INT, particle_settings, 8, NULL); break; case 3: - rb->set_option("Particle Life", &particle_life, INT, + rb->set_option("Particle Life", &particle_life, RB_INT, particle_life_settings, 9, NULL); break; case 4: - rb->set_option("Gravity", &gravity, INT, + rb->set_option("Gravity", &gravity, RB_INT, gravity_settings, 4, NULL); break; case 5: - rb->set_option("Show Rockets", &show_rockets, INT, + rb->set_option("Show Rockets", &show_rockets, RB_INT, rocket_settings, 3, NULL); break; case 6: - rb->set_option("FPS (Speed)", &frames_per_second, INT, + rb->set_option("FPS (Speed)", &frames_per_second, RB_INT, fps_settings, 9, NULL); break; @@ -358,9 +365,9 @@ enum plugin_status plugin_start(const void* parameter) /* set everything up.. no BL timeout, no backdrop, white-text-on-black-background. */ -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); rb->lcd_set_background(LCD_BLACK); @@ -524,10 +531,10 @@ enum plugin_status plugin_start(const void* parameter) break; } } -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif diff --git a/apps/plugins/flipit.c b/apps/plugins/flipit.c index c659cf5ba5..278c77e188 100644 --- a/apps/plugins/flipit.c +++ b/apps/plugins/flipit.c @@ -49,7 +49,7 @@ #define FLIPIT_DOWN BUTTON_PLAY #define FLIPIT_NEXT BUTTON_SCROLL_FWD #define FLIPIT_PREV BUTTON_SCROLL_BACK -#define FLIPIT_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define FLIPIT_QUIT (BUTTON_SELECT | BUTTON_REPEAT) #define FLIPIT_SHUFFLE (BUTTON_SELECT | BUTTON_LEFT) #define FLIPIT_SOLVE (BUTTON_SELECT | BUTTON_PLAY) #define FLIPIT_STEP_BY_STEP (BUTTON_SELECT | BUTTON_RIGHT) @@ -841,7 +841,7 @@ enum plugin_status plugin_start(const void* parameter) #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) - rb->lcd_putsxy(2, 8, "[S-MENU] to stop"); + rb->lcd_putsxy(2, 8, "Long [SELECT] to stop"); rb->lcd_putsxy(2, 18, "[SELECT] toggle"); rb->lcd_putsxy(2, 28, "[S-LEFT] shuffle"); rb->lcd_putsxy(2, 38, "[S-PLAY] solution"); diff --git a/apps/plugins/fractals/fractal.h b/apps/plugins/fractals/fractal.h index 8e9446df0d..8432546c1e 100644 --- a/apps/plugins/fractals/fractal.h +++ b/apps/plugins/fractals/fractal.h @@ -40,7 +40,7 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define FRACTAL_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define FRACTAL_QUIT (BUTTON_SELECT | BUTTON_REPEAT) #define FRACTAL_UP BUTTON_MENU #define FRACTAL_DOWN BUTTON_PLAY #define FRACTAL_LEFT BUTTON_LEFT diff --git a/apps/plugins/imageviewer/image_decoder.c b/apps/plugins/imageviewer/image_decoder.c index eab1c01dbc..0c1776daaa 100644 --- a/apps/plugins/imageviewer/image_decoder.c +++ b/apps/plugins/imageviewer/image_decoder.c @@ -155,7 +155,10 @@ const struct image_decoder *load_decoder(struct loader_info *loader_info) goto error_close; } - if (lc_hdr->api_version != IMGDEC_API_VERSION) + if (lc_hdr->api_version != IMGDEC_API_VERSION || + hdr->img_api_size > sizeof(struct imgdec_api) || + hdr->plugin_api_version != PLUGIN_API_VERSION || + hdr->plugin_api_size > sizeof(struct plugin_api)) { rb->splashf(2*HZ, "%s decoder: Incompatible version.", name); goto error_close; diff --git a/apps/plugins/imageviewer/imageviewer.c b/apps/plugins/imageviewer/imageviewer.c index 0dd140d1ab..d1a512c4fd 100644 --- a/apps/plugins/imageviewer/imageviewer.c +++ b/apps/plugins/imageviewer/imageviewer.c @@ -127,6 +127,9 @@ static int curfile = -1, direction = DIR_NEXT, entries = 0; /* list of the supported image files */ static char **file_pt; +/* progress update tick */ +static long next_progress_tick; + static const struct image_decoder *imgdec = NULL; static enum image_type image_type = IMAGE_UNKNOWN; @@ -192,7 +195,11 @@ static int change_filename(int direct) return PLUGIN_ERROR; } - rb->strcpy(rb->strrchr(np_file, '/')+1, file_pt[curfile]); + size_t np_file_length = rb->strlen(np_file); + size_t np_file_name_length = rb->strlen(rb->strrchr(np_file, '/')+1); + size_t avail_length = sizeof(np_file) - (np_file_length - np_file_name_length); + + rb->snprintf(rb->strrchr(np_file, '/')+1, avail_length, "%s", file_pt[curfile]); return PLUGIN_OTHER; } @@ -223,15 +230,15 @@ static bool set_option_dithering(void) [DITHER_DIFFUSION] = { STR(LANG_DIFFUSION) }, }; - rb->set_option(rb->str(LANG_DITHERING), &settings.jpeg_dither_mode, INT, + rb->set_option(rb->str(LANG_DITHERING), &settings.jpeg_dither_mode, RB_INT, dithering, DITHER_NUM_MODES, NULL); return false; } MENUITEM_FUNCTION(grayscale_item, 0, ID2P(LANG_GRAYSCALE), - set_option_grayscale, NULL, NULL, Icon_NOICON); + set_option_grayscale, NULL, Icon_NOICON); MENUITEM_FUNCTION(dithering_item, 0, ID2P(LANG_DITHERING), - set_option_dithering, NULL, NULL, Icon_NOICON); + set_option_dithering, NULL, Icon_NOICON); MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON, &grayscale_item, &dithering_item); @@ -283,7 +290,7 @@ static int show_menu(void) /* return 1 to quit */ case MIID_RETURN: break; case MIID_TOGGLE_SS_MODE: - rb->set_option(rb->str(LANG_SLIDESHOW_MODE), &iv_api.slideshow_enabled, BOOL, + rb->set_option(rb->str(LANG_SLIDESHOW_MODE), &iv_api.slideshow_enabled, RB_BOOL, slideshow , 2, NULL); break; case MIID_CHANGE_SS_MODE: @@ -346,7 +353,7 @@ static int show_menu(void) /* return 1 to quit */ static int ask_and_get_audio_buffer(const char *filename) { int button; -#if defined(IMGVIEW_ZOOM_PRE) +#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE) int lastbutton = BUTTON_NONE; #endif rb->lcd_setfont(FONT_SYSFIXED); @@ -385,6 +392,10 @@ static int ask_and_get_audio_buffer(const char *filename) #endif #ifdef IMGVIEW_QUIT case IMGVIEW_QUIT: +#ifdef IMGVIEW_QUIT_PRE + if (lastbutton != IMGVIEW_QUIT_PRE) + break; +#endif #endif case IMGVIEW_MENU: return PLUGIN_OK; @@ -417,7 +428,7 @@ static int ask_and_get_audio_buffer(const char *filename) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; } -#if defined(IMGVIEW_ZOOM_PRE) +#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE) if (button != BUTTON_NONE) lastbutton = button; #endif @@ -428,7 +439,14 @@ static int ask_and_get_audio_buffer(const char *filename) /* callback updating a progress meter while image decoding */ static void cb_progress(int current, int total) { - rb->yield(); /* be nice to the other threads */ + /* do not yield or update the progress bar if we did so too recently */ + long now = *rb->current_tick; + if(!TIME_AFTER(now, next_progress_tick)) + return; + + /* limit to 20fps */ + next_progress_tick = now + HZ/20; + #ifndef USEGSLIB /* in slideshow mode, keep gui interference to a minimum */ const int size = (!iv_api.running_slideshow ? 8 : 4); @@ -442,6 +460,8 @@ static void cb_progress(int current, int total) total, 0, current, HORIZONTAL); rb->lcd_update_rect(0, LCD_HEIGHT-size, LCD_WIDTH, size); } + + rb->yield(); /* be nice to the other threads */ } #define VSCROLL (LCD_HEIGHT/8) @@ -556,14 +576,19 @@ static void pan_view_down(struct image_info *info) } /* interactively scroll around the image */ -static int scroll_bmp(struct image_info *info) +static int scroll_bmp(struct image_info *info, bool initial_frame) { static long ss_timeout = 0; int button; #if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) \ - || defined(IMGVIEW_SLIDE_SHOW_PRE) - int lastbutton = BUTTON_NONE; + || defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE) + static int lastbutton; + if (initial_frame) + lastbutton = BUTTON_NONE; + +#else + (void) initial_frame; #endif if (!ss_timeout && iv_api.slideshow_enabled) @@ -616,11 +641,19 @@ static int scroll_bmp(struct image_info *info) case IMGVIEW_UP: case IMGVIEW_UP | BUTTON_REPEAT: +#ifdef IMGVIEW_SCROLL_UP + case IMGVIEW_SCROLL_UP: + case IMGVIEW_SCROLL_UP | BUTTON_REPEAT: +#endif pan_view_up(info); break; case IMGVIEW_DOWN: case IMGVIEW_DOWN | BUTTON_REPEAT: +#ifdef IMGVIEW_SCROLL_DOWN + case IMGVIEW_SCROLL_DOWN: + case IMGVIEW_SCROLL_DOWN | BUTTON_REPEAT: +#endif pan_view_down(info); break; @@ -721,6 +754,10 @@ static int scroll_bmp(struct image_info *info) #ifdef IMGVIEW_QUIT case IMGVIEW_QUIT: +#ifdef IMGVIEW_QUIT_PRE + if (lastbutton != IMGVIEW_QUIT_PRE) + break; +#endif return PLUGIN_OK; break; #endif @@ -732,7 +769,8 @@ static int scroll_bmp(struct image_info *info) break; } /* switch */ -#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) || defined(IMGVIEW_SLIDE_SHOW_PRE) +#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) ||\ + defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE) if (button != BUTTON_NONE) lastbutton = button; #endif @@ -898,6 +936,7 @@ static int load_and_show(char* filename, struct image_info *info) /* used to loop through subimages in animated gifs */ int frame = 0; + bool initial_frame = true; do /* loop the image prepare and decoding when zoomed */ { status = imgdec->get_image(info, frame, ds); /* decode or fetch from cache */ @@ -930,7 +969,8 @@ static int load_and_show(char* filename, struct image_info *info) */ while (1) { - status = scroll_bmp(info); + status = scroll_bmp(info, initial_frame); + initial_frame = false; if (status == ZOOM_IN) { @@ -1040,10 +1080,8 @@ enum plugin_status plugin_start(const void* parameter) ARRAYLEN(config), IMGVIEW_SETTINGS_MINVERSION); rb->memcpy(&old_settings, &settings, sizeof (settings)); -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); @@ -1070,10 +1108,8 @@ enum plugin_status plugin_start(const void* parameter) rb->storage_spindown(rb->global_settings->disk_spindown); #endif -#ifdef HAVE_BACKLIGHT /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif #ifdef USEGSLIB grey_release(); /* deinitialize */ diff --git a/apps/plugins/imageviewer/imageviewer.h b/apps/plugins/imageviewer/imageviewer.h index 19b5db15bb..ac15df5960 100644 --- a/apps/plugins/imageviewer/imageviewer.h +++ b/apps/plugins/imageviewer/imageviewer.h @@ -136,14 +136,17 @@ struct image_decoder { int x, int y, int width, int height); }; -#define IMGDEC_API_VERSION (PLUGIN_API_VERSION << 4 | 0) +#define IMGDEC_API_VERSION 1 /* image decoder header */ struct imgdec_header { struct lc_header lc_hdr; /* must be the first */ const struct image_decoder *decoder; const struct plugin_api **api; + unsigned short plugin_api_version; + size_t plugin_api_size; const struct imgdec_api **img_api; + size_t img_api_size; }; #ifdef IMGDEC @@ -157,15 +160,18 @@ extern const struct image_decoder image_decoder; const struct imgdec_header __header \ __attribute__ ((section (".header")))= { \ { PLUGIN_MAGIC, TARGET_ID, IMGDEC_API_VERSION, \ - plugin_start_addr, plugin_end_addr }, &image_decoder, &rb, &iv }; + plugin_start_addr, plugin_end_addr, }, &image_decoder, \ + &rb, PLUGIN_API_VERSION, sizeof(struct plugin_api), \ + &iv, sizeof(struct imgdec_api) }; #else /* PLATFORM_HOSTED */ #define IMGDEC_HEADER \ const struct plugin_api *rb DATA_ATTR; \ const struct imgdec_api *iv DATA_ATTR; \ const struct imgdec_header __header \ __attribute__((visibility("default"))) = { \ - { PLUGIN_MAGIC, TARGET_ID, IMGDEC_API_VERSION, \ - NULL, NULL }, &image_decoder, &rb, &iv }; + { PLUGIN_MAGIC, TARGET_ID, IMGDEC_API_VERSION, NULL, NULL }, \ + &image_decoder, &rb, PLUGIN_API_VERSION, sizeof(struct plugin_api), \ + &iv, sizeof(struct imgdec_api), }; #endif /* CONFIG_PLATFORM */ #endif diff --git a/apps/plugins/imageviewer/imageviewer_button.h b/apps/plugins/imageviewer/imageviewer_button.h index e6cd2ac089..d324f93292 100644 --- a/apps/plugins/imageviewer/imageviewer_button.h +++ b/apps/plugins/imageviewer/imageviewer_button.h @@ -53,8 +53,10 @@ #define IMGVIEW_RIGHT BUTTON_RIGHT #define IMGVIEW_NEXT (BUTTON_SELECT | BUTTON_RIGHT) #define IMGVIEW_PREVIOUS (BUTTON_SELECT | BUTTON_LEFT) -#define IMGVIEW_MENU (BUTTON_SELECT | BUTTON_MENU) -#define IMGVIEW_QUIT (BUTTON_SELECT | BUTTON_PLAY) +#define IMGVIEW_MENU_PRE BUTTON_SELECT +#define IMGVIEW_MENU (BUTTON_SELECT | BUTTON_REPEAT) +#define IMGVIEW_QUIT_PRE BUTTON_SELECT +#define IMGVIEW_QUIT (BUTTON_SELECT | BUTTON_REL) #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD #define IMGVIEW_ZOOM_PRE BUTTON_SELECT @@ -529,7 +531,9 @@ #define IMGVIEW_ZOOM_IN BUTTON_VOL_UP #define IMGVIEW_ZOOM_OUT BUTTON_VOL_DOWN #define IMGVIEW_UP BUTTON_UP +#define IMGVIEW_SCROLL_UP BUTTON_SCROLL_BACK #define IMGVIEW_DOWN BUTTON_DOWN +#define IMGVIEW_SCROLL_DOWN BUTTON_SCROLL_FWD #define IMGVIEW_LEFT BUTTON_LEFT #define IMGVIEW_RIGHT BUTTON_RIGHT #define IMGVIEW_NEXT BUTTON_BACK diff --git a/apps/plugins/imageviewer/jpeg/yuv2rgb.c b/apps/plugins/imageviewer/jpeg/yuv2rgb.c index 61d7fd6487..3e7f08d8bc 100644 --- a/apps/plugins/imageviewer/jpeg/yuv2rgb.c +++ b/apps/plugins/imageviewer/jpeg/yuv2rgb.c @@ -238,7 +238,7 @@ static fb_data (* const pixel_funcs[COLOUR_NUM_MODES][DITHER_NUM_MODES])(void) = }; /* These defines are used fornormal horizontal strides and vertical strides. */ -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE #define LCDADDR(x, y) (lcd_fb + LCD_HEIGHT*(x) + (y)) #define ROWENDOFFSET (width*LCD_HEIGHT) #define ROWOFFSET (1) diff --git a/apps/plugins/imageviewer/ppm/ppm_decoder.c b/apps/plugins/imageviewer/ppm/ppm_decoder.c index 4a86be1a3a..ccb208b80b 100644 --- a/apps/plugins/imageviewer/ppm/ppm_decoder.c +++ b/apps/plugins/imageviewer/ppm/ppm_decoder.c @@ -177,7 +177,7 @@ static int read_ppm_row(int fd, struct ppm_info *ppm, int row) int col; int r, g, b; #ifdef HAVE_LCD_COLOR -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE fb_data *dst = (fb_data *) ppm->buf + row; const int stride = ppm->x; #else diff --git a/apps/plugins/invadrox.c b/apps/plugins/invadrox.c index a164b95cf0..d130ab6108 100644 --- a/apps/plugins/invadrox.c +++ b/apps/plugins/invadrox.c @@ -785,7 +785,7 @@ static fb_data *lcd_fb; /* No standard get_pixel function yet, use this hack instead */ #if (LCD_DEPTH >= 8) -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE static inline fb_data get_pixel(int x, int y) { return lcd_fb[x*LCD_HEIGHT+y]; diff --git a/apps/plugins/jackpot.c b/apps/plugins/jackpot.c index 78f74568ca..fd878509cf 100644 --- a/apps/plugins/jackpot.c +++ b/apps/plugins/jackpot.c @@ -25,6 +25,13 @@ #include "lib/pluginlib_exit.h" +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define JACKPOT_QUIT PLA_UP +#else +#define JACKPOT_QUIT PLA_CANCEL +#endif const struct button_mapping* plugin_contexts[]={pla_main_ctx}; #define NB_PICTURES 9 @@ -248,7 +255,7 @@ enum plugin_status plugin_start(const void* parameter) plugin_contexts, ARRAYLEN(plugin_contexts)); switch ( action ) { - case PLA_CANCEL: + case JACKPOT_QUIT: return PLUGIN_OK; case PLA_SELECT: jackpot_play_turn(&game); diff --git a/apps/plugins/jewels.c b/apps/plugins/jewels.c index 3f209ae556..3a65e8ba64 100644 --- a/apps/plugins/jewels.c +++ b/apps/plugins/jewels.c @@ -49,9 +49,9 @@ #define JEWELS_PREV BUTTON_SCROLL_BACK #define JEWELS_NEXT BUTTON_SCROLL_FWD #define JEWELS_SELECT BUTTON_SELECT -#define JEWELS_CANCEL (BUTTON_SELECT | BUTTON_MENU) +#define JEWELS_CANCEL (BUTTON_SELECT | BUTTON_REPEAT) #define HK_SELECT "SELECT" -#define HK_CANCEL "SEL + MENU" +#define HK_CANCEL "Long SELECT" #elif (CONFIG_KEYPAD == IPOD_3G_PAD) #define JEWELS_LEFT BUTTON_LEFT @@ -1481,7 +1481,7 @@ static int jewels_game_menu(struct game_context* bj, bool ingame) jewels_init(bj); return 0; case 2: - rb->set_option("Mode", &bj->tmp_type, INT, mode, 2, NULL); + rb->set_option("Mode", &bj->tmp_type, RB_INT, mode, 2, NULL); break; case 3: if(jewels_help()) diff --git a/apps/plugins/keybox.c b/apps/plugins/keybox.c index f8c6800a4d..cb2e23a94a 100644 --- a/apps/plugins/keybox.c +++ b/apps/plugins/keybox.c @@ -567,7 +567,7 @@ static int keybox(void) { rb->gui_synclist_draw(&kb_list); button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&kb_list, &button, LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&kb_list, &button)) continue; switch (button) @@ -659,7 +659,6 @@ enum plugin_status plugin_start(const void *parameter) rb->gui_synclist_set_title(&kb_list, "Keybox", NOICON); rb->gui_synclist_set_icon_callback(&kb_list, NULL); rb->gui_synclist_set_nb_items(&kb_list, 0); - rb->gui_synclist_limit_scroll(&kb_list, false); rb->gui_synclist_select_item(&kb_list, 0); init_ll(); diff --git a/apps/plugins/keyremap.c b/apps/plugins/keyremap.c new file mode 100644 index 0000000000..202d5fcfa4 --- /dev/null +++ b/apps/plugins/keyremap.c @@ -0,0 +1,2228 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ / + * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) ( + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 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" +#include "lang_enum.h" + +#include "lib/action_helper.h" +#include "lib/button_helper.h" +#include "lib/pluginlib_actions.h" +#include "lib/printcell_helper.h" +#include "lib/kbd_helper.h" + +#ifdef ROCKBOX_HAS_LOGF +#define logf rb->logf +#else +#define logf(...) do { } while(0) +#endif + +/* CORE_KEYREMAP_FILE */ +#include "../core_keymap.h" +#define KMFDIR ROCKBOX_DIR +#define KMFEXT1 ".kmf" +#define KMFEXT2 ".kmf.old" +#define KMFUSER "user_keyremap" +#define MAX_BUTTON_COMBO 5 +#define MAX_BUTTON_NAME 32 +#define MAX_MENU_NAME 64 +#define TEST_COUNTDOWN_MS 1590 + +struct context_flags { + char * name; + int flag; +}; + +/* flags added to context_name[] */ +static struct context_flags context_flags[] = { + {"UNKNOWN", 0},/* index 0 is an Error */ +#ifndef HAS_BUTTON_HOLD + {"LOCKED", CONTEXT_LOCKED}, +#endif + /*{"PLUGIN", CONTEXT_PLUGIN}, need a custom action list and a way to supply */ +#if BUTTON_REMOTE != 0 + {"REMOTE", CONTEXT_REMOTE}, +#ifndef HAS_BUTTON_HOLD + {"REMOTE_LOCKED", CONTEXT_REMOTE | CONTEXT_LOCKED}, +#endif +#endif /* BUTTON_REMOTE != 0 */ +}; + +static struct keyremap_buffer_t { + char * buffer; + size_t buf_size; + char *front; + char *end; +} keyremap_buffer; + + +struct action_mapping_t { + int context; + int display_pos; + struct button_mapping map; +}; + +static struct user_context_data_t { + struct action_mapping_t *ctx_map; + int ctx_count; + struct action_mapping_t *act_map; + int act_count; +} ctx_data; + +/* set keys keymap */ +static struct setkeys_data_t { + /* save state in the set keys action view list */ + int view_columns; + int view_lastcol; + uint32_t crc32; +} keyset; + +/* test keys data */ +static struct testkey_data_t { + struct button_mapping *keymap; + int action; + int context; + int index; + int countdown; +} keytest; + +static struct context_menu_data_t { + char *menuid; + int ctx_index; + int act_index; + int act_edit_index; + //const char * ctx_fmt; + const char * act_fmt; +} ctx_menu_data; +#define ACTIONFMT_LV0 "$%s$%s$%s$%s" +#define ACTVIEW_HEADER "$Context$Action$Button$PreBtn" + +#define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1) +#define MENU_ID(x) (((void*)&mainmenu[x])) +#define MENU_MAX_DEPTH 4 +/* this enum sets menu order */ +enum { + M_ROOT = 0, + M_SETKEYS, + M_TESTKEYS, + M_RESETKEYS, + M_EXPORTKEYS, + M_IMPORTKEYS, + M_SAVEKEYS, + M_LOADKEYS, + M_DELKEYS, + M_TMPCORE, + M_SETCORE, + M_DELCORE, + M_EXIT, + M_LAST_MAINITEM, //MAIN MENU ITEM COUNT +/*Menus not directly accessible from main menu*/ + M_ACTIONS = M_LAST_MAINITEM, + M_BUTTONS, + M_CONTEXTS, + M_CONTEXT_EDIT, + M_LAST_ITEM, //ITEM COUNT +}; + +struct mainmenu { const char *name; void *menuid; int index; int items;}; +static struct mainmenu mainmenu[M_LAST_ITEM] = { +#define MENU_ITEM(ID, NAME, COUNT) [ID]{NAME, MENU_ID(ID), (int)ID, (int)COUNT} +MENU_ITEM(M_ROOT, "Key Remap Plugin", M_LAST_MAINITEM - 1), +MENU_ITEM(M_SETKEYS, "Edit Keymap", 1), +MENU_ITEM(M_TESTKEYS, "Test Keymap", 4), +MENU_ITEM(M_RESETKEYS, "Reset Keymap", 1), +MENU_ITEM(M_EXPORTKEYS, "Export Text Keymap", 1), +MENU_ITEM(M_IMPORTKEYS, "Import Text Keymap", 1), +MENU_ITEM(M_SAVEKEYS, "Save Native Keymap", 1), +MENU_ITEM(M_LOADKEYS, "Load Native Keymaps", 1), +MENU_ITEM(M_DELKEYS, "Delete Keymaps", 1), +MENU_ITEM(M_TMPCORE, "Temp Core Remap", 1), +MENU_ITEM(M_SETCORE, "Set Core Remap", 1), +MENU_ITEM(M_DELCORE, "Remove Core Remap", 1), +MENU_ITEM(M_EXIT, ID2P(LANG_MENU_QUIT), 0), +MENU_ITEM(M_ACTIONS, "Actions", LAST_ACTION_PLACEHOLDER), +MENU_ITEM(M_BUTTONS, "Buttons", -1), /* Set at runtime in plugin_start: */ +MENU_ITEM(M_CONTEXTS, "Contexts", LAST_CONTEXT_PLACEHOLDER * ARRAYLEN(context_flags)), +MENU_ITEM(M_CONTEXT_EDIT, "", 5), +#undef MENU_ITEM +}; + +DIR* kmffiles_dirp = NULL; +static int core_savecount = 0;/* so we don't overwrite the last .old file with revisions */ + +/* global lists, for everything */ +static struct gui_synclist lists; + +static void menu_useract_set_positions(void); +static size_t lang_strlcpy(char * dst, const char *src, size_t len) +{ + unsigned char **language_strings = rb->language_strings; + return rb->strlcpy(dst, P2STR((unsigned char*)src), len); +} + +/* Menu stack macros */ +static int mlastsel[MENU_MAX_DEPTH * 2 + 1] = {0}; +#define PUSH_MENU(id, item) \ + if(mlastsel[0] < MENU_MAX_DEPTH * 2) {mlastsel[++mlastsel[0]] = id;mlastsel[++mlastsel[0]] = item;} +#define POP_MENU(id, item) { item = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]--]:0); \ + id = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]--]:0); } +#define PEEK_MENU_ITEM(item) { item = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]]:0);} +#define PEEK_MENU_ID(id) {id = (mlastsel[0] > 1 ? mlastsel[mlastsel[0]-1]:0);} +#define PEEK_MENU(id, item) PEEK_MENU_ITEM(item) PEEK_MENU_ID(id) +#define SET_MENU_ITEM(item) \ + if(mlastsel[0] > 1) {mlastsel[mlastsel[0]] = item;} + +static struct mainmenu *mainitem(int selected_item) +{ + static struct mainmenu empty = {0}; + if (selected_item >= 0 && selected_item < (int) ARRAYLEN(mainmenu)) + return &mainmenu[selected_item]; + else + return ∅ +} +/* Forward Declarations */ +static const char *edit_keymap_name_cb(int selected_item, void* data,char* buf, size_t buf_len); +static int keyremap_import_file(char *filenamebuf, size_t bufsz); +static void synclist_set(int id, int selected_item, int items, int sel_size); + +static int prompt_filename(char *buf, size_t bufsz) +{ +#define KBD_LAYOUT "abcdefghijklmnop\nqrstuvwxyz |()[]\n1234567890 /._-+\n\n" \ + "\nABCDEFGHIJKLMNOP\nQRSTUVWXYZ |()[]\n1234567890 /._-+" + unsigned short kbd[sizeof(KBD_LAYOUT) + 10]; + unsigned short *kbd_p = kbd; + if (!kbd_create_layout(KBD_LAYOUT, kbd, sizeof(kbd))) + kbd_p = NULL; + +#undef KBD_LAYOUT + return rb->kbd_input(buf, bufsz, kbd_p) + 1; +} + +static void synclist_set_update(int id, int selected_item, int items, int sel_size) +{ + SET_MENU_ITEM(lists.selected_item + 1); /* update selected for previous menu*/ + synclist_set(id, selected_item, items, sel_size); +} + +/* returns the actual context & index of the flag is passed in *flagidx */ +static int ctx_strip_flagidx(int ctx, int *flagidx) +{ + int ctx_out = ctx % (LAST_CONTEXT_PLACEHOLDER); + *flagidx = 0; + if (ctx_out != ctx) + { + while (ctx >= LAST_CONTEXT_PLACEHOLDER) + { + (*flagidx)++; + ctx -= LAST_CONTEXT_PLACEHOLDER; + } + if (*flagidx >= (int)ARRAYLEN(context_flags)) + *flagidx = 0; /* unknown flag */ + + logf("%s ctx: (%d) %s flag idx: (%d) %s\n", __func__, + ctx, context_name(ctx), *flagidx, context_flags[*flagidx].name); + } + return ctx_out; +} + +/* combines context name and flag name using '_' to join them */ +static char *ctx_name_and_flag(int index) +{ + static char ctx_namebuf[MAX_MENU_NAME]; + char *ctx_name = "?"; + int flagidx; + int ctx = ctx_strip_flagidx(index, &flagidx); + if (flagidx == 0) + { + ctx_name = context_name(ctx); + } + else if (flagidx >= 0 && flagidx < (int)ARRAYLEN(context_flags)) + { + rb->snprintf(ctx_namebuf, sizeof(ctx_namebuf), "%s_%s", + context_name(ctx), context_flags[flagidx].name); + ctx_name = ctx_namebuf; + } + return ctx_name; +} + +static int btnval_to_index(unsigned int btnvalue) +{ + int index = -1; + for (int i = 0; i < available_button_count; i++) + { + const struct available_button *btn = &available_buttons[i]; + if (btnvalue == btn->value) + { + index = i; + break; + } + } + return index; +} + +static int btnval_to_name(char *buf, size_t bufsz, unsigned int btnvalue) +{ + int index = btnval_to_index(btnvalue); + if (index >= 0) + { + return rb->snprintf(buf, bufsz, "%s", available_buttons[index].name); + } + else /* this provides names for button combos e.g.(BUTTON_UP|BUTTON_REPEAT) */ + { + int res = get_button_names(buf, bufsz, btnvalue); + if (res > 0 && res <= (int)bufsz) + return res; + } + return rb->snprintf(buf, bufsz, "%s", "BUTTON_UNKNOWN"); +} + +static int keyremap_check_extension(const char* filename) +{ + int found = 0; + unsigned int len = rb->strlen(filename); + if (len > sizeof(KMFEXT1) && + rb->strcmp(&filename[len - sizeof(KMFEXT1) + 1], KMFEXT1) == 0) + { + found = 1; + } + else if (len > sizeof(KMFEXT2) && + rb->strcmp(&filename[len - sizeof(KMFEXT2) + 1], KMFEXT2) == 0) + { + found = 2; + } + return found; +} + +static int keyremap_count_files(const char *directory) +{ + int nfiles = 0; + DIR* kmfdirp = NULL; + kmfdirp = rb->opendir(directory); + if (kmfdirp != NULL) + { + struct dirent *entry; + while ((entry = rb->readdir(kmfdirp))) + { + /* skip directories */ + if ((rb->dir_get_info(kmfdirp, entry).attribute & ATTR_DIRECTORY) != 0) + continue; + if (keyremap_check_extension(entry->d_name) > 0) + nfiles++; + } + rb->closedir(kmfdirp); + } + return nfiles; +} + +static void keyremap_reset_buffer(void) +{ + keyremap_buffer.front = keyremap_buffer.buffer; + keyremap_buffer.end = keyremap_buffer.front + keyremap_buffer.buf_size; + rb->memset(keyremap_buffer.front, 0, keyremap_buffer.buf_size); + ctx_data.ctx_map = (struct action_mapping_t*)keyremap_buffer.front; + ctx_data.ctx_count = 0; + ctx_data.act_map = (struct action_mapping_t*) keyremap_buffer.end; + ctx_data.act_count = 0; + +} + +static size_t keyremap_write_entries(int fd, struct button_mapping *data, int entry_count) +{ + if (fd < 0 || entry_count <= 0 || !data) + goto fail; + size_t bytes_req = sizeof(struct button_mapping) * entry_count; + size_t bytes = rb->write(fd, data, bytes_req); + if (bytes == bytes_req) + return bytes_req; +fail: + return 0; +} + +static size_t keyremap_write_header(int fd, int entry_count) +{ + if (fd < 0 || entry_count < 0) + goto fail; + struct button_mapping header = {KEYREMAP_VERSION, KEYREMAP_HEADERID, entry_count}; + return keyremap_write_entries(fd, &header, 1); +fail: + return 0; +} + +static int keyremap_open_file(const char *filename, int *fd, size_t *fsize) +{ + int count = 0; + + while (filename && fd && fsize) + { + *fsize = 0; + *fd = rb->open(filename, O_RDONLY); + if (*fd) + { + *fsize = rb->filesize(*fd); + + count = *fsize / sizeof(struct button_mapping); + + if (count * sizeof(struct button_mapping) != *fsize) + { + count = -10; + break; + } + + if (count > 1) + { + struct button_mapping header = {0}; + rb->read(*fd, &header, sizeof(struct button_mapping)); + if (KEYREMAP_VERSION == header.action_code && + KEYREMAP_HEADERID == header.button_code && + header.pre_button_code == count) + { + count--; + *fsize -= sizeof(struct button_mapping); + } + else /* Header mismatch */ + { + count = -20; + break; + } + } + } + break; + } + return count; +} + +static int keyremap_map_is_valid(struct action_mapping_t *amap, int context) +{ + int ret = (amap->context == context ? 1: 0); + struct button_mapping entry = amap->map; + if (entry.action_code == (int)CONTEXT_STOPSEARCHING || + entry.button_code == BUTTON_NONE) + { + ret = 0; + } + + return ret; +} + +static struct button_mapping *keyremap_create_temp(int *entries) +{ + logf("%s()", __func__); + struct button_mapping *tempkeymap; + int action_offset = 1; /* start of action entries (ctx_count + 1)*/ + int entry_count = 1;/* (ctx_count + ctx_count + act_count) */ + int index = 0; + int i, j; + + /* count includes a single stop sentinel for the list of contexts */ + /* and a stop sentinel for each group of action remaps as well */ + for (i = 0; i < ctx_data.ctx_count; i++) + { + int actions_this_ctx = 0; + int context = ctx_data.ctx_map[i].context; + /* how many actions are contained in each context? */ + for (j = 0; j < ctx_data.act_count; j++) + { + if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0) + { + actions_this_ctx++; + } + } + /* only count contexts with remapped actions */ + if (actions_this_ctx != 0) + { + action_offset++; + entry_count += actions_this_ctx + 2; + } + } + size_t keymap_bytes = (entry_count) * sizeof(struct button_mapping); + *entries = entry_count; + logf("%s() entry count: %d", __func__, entry_count); + logf("keyremap bytes: %zu, avail: %zu", keymap_bytes, + (keyremap_buffer.end - keyremap_buffer.front)); + if (keyremap_buffer.front + keymap_bytes < keyremap_buffer.end) + { + tempkeymap = (struct button_mapping *) keyremap_buffer.front; + rb->memset(tempkeymap, 0, keymap_bytes); + struct button_mapping *entry = &tempkeymap[index++]; + for (i = 0; i < ctx_data.ctx_count; i++) + { + int actions_this_ctx = 0; + int flagidx; + int context = ctx_data.ctx_map[i].context; + /* how many actions are contained in each context? */ + for (j = 0; j < ctx_data.act_count; j++) + { + if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0) + { + actions_this_ctx += 1; + } + } + /*Don't save contexts with no actions */ + if (actions_this_ctx == 0){ continue; } + + /* convert context x flag to context | flag */ + context = ctx_strip_flagidx(ctx_data.ctx_map[i].context, &flagidx); + context |= context_flags[flagidx].flag; + + entry->action_code = CORE_CONTEXT_REMAP(context); + entry->button_code = action_offset; /* offset of first action entry */ + entry->pre_button_code = actions_this_ctx; /* entries (excluding sentinel) */ + entry = &tempkeymap[index++]; + logf("keyremap found context: %d index: %d entries: %d", + context, action_offset, actions_this_ctx); + action_offset += actions_this_ctx + 1; /* including sentinel */ + } + /* context sentinel */ + entry->action_code = CONTEXT_STOPSEARCHING;; + entry->button_code = BUTTON_NONE; + entry->pre_button_code = BUTTON_NONE; + entry = &tempkeymap[index++]; + + for (i = 0; i < ctx_data.ctx_count; i++) + { + int actions_this_ctx = 0; + int context = ctx_data.ctx_map[i].context; + + for (int j = 0; j < ctx_data.act_count; j++) + { + if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0) + { + struct button_mapping map = ctx_data.act_map[j].map; + entry->action_code = map.action_code; + entry->button_code = map.button_code; + entry->pre_button_code = map.pre_button_code; + entry = &tempkeymap[index++]; + actions_this_ctx++; + logf("keyremap: found ctx: %d, act: %d btn: %d pbtn: %d", + context, map.action_code, map.button_code, map.pre_button_code); + } + } + /*Don't save sentinel for contexts with no actions */ + if (actions_this_ctx == 0){ continue; } + + /* action sentinel */ + entry->action_code = CONTEXT_STOPSEARCHING;; + entry->button_code = BUTTON_NONE; + entry->pre_button_code = BUTTON_NONE; + entry = &tempkeymap[index++]; + } + } + else + { + rb->splashf(HZ *2, "Out of Memory"); + logf("keyremap: create temp OOM"); + *entries = 0; + tempkeymap = NULL; + } + + return tempkeymap; +} + +static int keyremap_save_current(const char *filename) +{ + int status = 0; + int entry_count;/* (ctx_count + ctx_count + act_count + 1) */ + + struct button_mapping *keymap = keyremap_create_temp(&entry_count); + + if (keymap == NULL || entry_count <= 3) /* there should be atleast 4 entries */ + return status; + keyset.crc32 = + rb->crc_32(keymap, entry_count * sizeof(struct button_mapping), 0xFFFFFFFF); + int keyfd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (keyremap_write_header(keyfd, entry_count + 1) > 0) + { + if (keyremap_write_entries(keyfd, keymap, entry_count) > 0) + status = 1; + } + rb->close(keyfd); + + return status; +} + +static void keyremap_save_user_keys(bool notify) +{ + char buf[MAX_PATH]; + int i = 0; + do + { + i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", KMFDIR, KMFUSER, i, KMFEXT1); + } while (i < 100 && rb->file_exists(buf)); + + if (notify && prompt_filename(buf, sizeof(buf)) <= 0) + { + return; + } + + if (keyremap_save_current(buf) == 0) + { + logf("Error Saving"); + if(notify) + rb->splash(HZ *2, "Error Saving"); + } + else if (notify) + rb->splashf(HZ *2, "Saved %s", buf); +} + +static int keyremap_export_current(char *filenamebuf, size_t bufsz) +{ + filenamebuf[bufsz - 1] = '\0'; + int i, j; + int ctx_count = 0; + size_t entrylen; + + int entry_count = ctx_data.ctx_count + ctx_data.act_count + 1; + + if (entry_count < 3) /* the header is not counted should be at least 3 entries */ + { + logf("%s: Not enough entries", __func__); + return 0; + } + int fd = rb->open(filenamebuf, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if (fd < 0) + return -1; + rb->fdprintf(fd, "# Key Remap\n# Device: %s\n" \ + "# Entries: %d\n\n", MODEL_NAME, entry_count - 1); + rb->fdprintf(fd, "# Each entry should be PROPER_CASE and on its own line\n" \ + "# Comments run to end of line \n"); + + for (i = 0; i <= entry_count; i++) + { + entrylen = 0; + rb->memset(filenamebuf, 0, bufsz); + edit_keymap_name_cb(i, MENU_ID(M_EXPORTKEYS), filenamebuf, bufsz); + if (i == 0) + { + ctx_menu_data.act_fmt = " {%s%s, %s, %s},\n\n"; + continue; + } + else if (i == entry_count) + { + rb->strlcpy(filenamebuf, "}\n\n", bufsz); + } + char last = '\0'; + for (j = 0; j < (int)bufsz ;j++) + { + char ch = filenamebuf[j]; + if (ch == '$' && last == '\0') /*CONTEXT*/ + { + if (ctx_count > 0) + rb->fdprintf(fd, "}\n"); + ctx_count++; + filenamebuf[j] = '\n'; + } + if (ch == '\n' && last == '\n') + { + entrylen = j; + break; + } + else if (ch == '\0') + filenamebuf[j] = ','; + last = ch; + } + + size_t bytes = rb->write(fd, filenamebuf, entrylen); + if (bytes != entrylen) + { + entry_count = -2; + goto fail; + } + } + +fail: + rb->close(fd); + + return entry_count; +} + +static void keyremap_export_user_keys(void) +{ + const bool notify = true; + char buf[MAX_PATH]; + int i = 0; + do + { + i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt"); + } while (i < 100 && rb->file_exists(buf)); + + if (notify && prompt_filename(buf, sizeof(buf)) <= 0) + { + return; + } + + if (keyremap_export_current(buf, sizeof(buf)) <= 0) + { + if(notify) + rb->splash(HZ *2, "Error Saving"); + } + else if (notify) + { + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt"); + rb->splashf(HZ *2, "Saved %s", buf); + } +} + +static void keyremap_import_user_keys(void) +{ + char buf[MAX_PATH]; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY, + .title = "Select Keymap", + .icon = Icon_Plugin, + .buf = buf, + .bufsize = sizeof(buf), + .root = "/", + }; + + if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) + { + int ret = keyremap_import_file(buf, sizeof(buf)); + if (ret <= 0) + { + keyremap_reset_buffer(); + rb->splash(HZ *2, "Error Opening"); + } + else + rb->splashf(HZ * 2, "Loaded Text Keymap "); + } +} + +static int keymap_add_context_entry(int context) +{ + int remap_context = CORE_CONTEXT_REMAP(context); + for (int i = 0; i < ctx_data.ctx_count; i++) + { + if (ctx_data.ctx_map[i].map.action_code == remap_context) + goto fail; /* context exists */ + } + if (keyremap_buffer.front + sizeof(struct action_mapping_t) > keyremap_buffer.end) + goto fail; + keyremap_buffer.front += sizeof(struct action_mapping_t); + ctx_data.ctx_map[ctx_data.ctx_count].context = context; + ctx_data.ctx_map[ctx_data.ctx_count].display_pos = -1; + ctx_data.ctx_map[ctx_data.ctx_count].map.action_code = remap_context; + ctx_data.ctx_map[ctx_data.ctx_count].map.button_code = 0; + ctx_data.ctx_map[ctx_data.ctx_count].map.pre_button_code = 0; + ctx_data.ctx_count++; + menu_useract_set_positions(); + return ctx_data.ctx_count; +fail: + return 0; +} + +static int keymap_add_button_entry(int context, int action_code, + int button_code, int pre_button_code) +{ + bool hasctx = false; + for (int i = 0; i < ctx_data.ctx_count; i++) + { + if (ctx_data.ctx_map[i].context == context) + { + hasctx = true; + break; + } + } + if (!hasctx || keyremap_buffer.end - sizeof(struct action_mapping_t) < keyremap_buffer.front) + goto fail; + keyremap_buffer.end -= sizeof(struct action_mapping_t); + ctx_data.act_map = (struct action_mapping_t*) keyremap_buffer.end; + ctx_data.act_map[0].context = context; + ctx_data.act_map[0].display_pos = -1; + ctx_data.act_map[0].map.action_code = action_code; + ctx_data.act_map[0].map.button_code = button_code; + ctx_data.act_map[0].map.pre_button_code = pre_button_code; + ctx_data.act_count++; + menu_useract_set_positions(); + return ctx_data.act_count; +fail: + return 0; +} + +static int get_action_from_str(char *pfield, size_t bufsz) +{ + int act = -1; + for (int i=0;i < LAST_ACTION_PLACEHOLDER; i++) + { + if (rb->strncasecmp(pfield, action_name(i), bufsz) == 0) + { + logf("Action Found: %s (%d)", pfield, i); + act = i; + break; + } + } + return act; +} + +static int get_button_from_str(char *pfield, size_t bufsz) +{ + int btn = -1; + for (int i=0;i < available_button_count; i++) + { + const struct available_button* abtn = &available_buttons[i]; + if (rb->strncasecmp(pfield, abtn->name, bufsz) == 0) + { + logf("Button Found: %s (%lx)", abtn->name, abtn->value); + btn = abtn->value; + break; + } + } + if (btn < 0) /* Not Fatal */ + { + logf("Invalid Button %s", pfield); + rb->splashf(HZ, "Invalid Button %s", pfield); + } + return btn; +} + +static int parse_action_import_entry(int context, char * pbuf, size_t bufsz) +{ + size_t bufleft; + int count = 0; + char ch; + char *pfirst = NULL; + char *pfield; + int field = -1; + int act = -1; + int button = BUTTON_NONE; + int prebtn = BUTTON_NONE; + while ((ch = *(pbuf)) != '\0') + { + pfield = NULL; /* Valid names */ + if ((ch >= 'A' && ch <= 'Z') || ch == '_') + { + if (pfirst == NULL) + pfirst = pbuf; + } + else if (ch == ',') + { + if (pfirst != NULL) + { + field++; + pfield = pfirst; + pfirst = NULL; + *pbuf = '\0'; + } + } + else if (ch == ' ' || ch == '|'){;} + else + pfirst = NULL; + + if (field == 1 && pfirst != NULL && pbuf[1] == '\0') + { + field++; + pfield = pfirst; + pfirst = NULL; + } + + if (pfield != NULL) + { + char *pf; + + if (field == 0) /* action */ + { + char *pact = pfield; + pf = pfield; + while ((ch = *(pf)) != '\0') + { + if(ch == ' ') + *pf = '\0'; + else + pf++; + } + bufleft = bufsz - (pact - pbuf); + act = get_action_from_str(pact, bufleft); + + if (act < 0) + { + logf("Error Action Expected: %s", pact); + return -1; + } + } + else if (field == 1 || field == 2) /* button / pre_btn */ + { + char *pbtn = pfield; + pf = pfield; + while ((ch = *(pf)) != '\0') + { + if (pf[1] == '\0') /* last item? */ + { + pf++; + ch = '|'; + pfield = NULL; + } + else if (ch == ' ' || ch == '|') + { + *pf = '\0'; + } + + if(ch == '|') + { + bufleft = bufsz - (pbtn - pbuf); + int btn = get_button_from_str(pbtn, bufleft); + + if (btn > BUTTON_NONE) + { + if (field == 1) + button |= btn; + else if (field == 2) + prebtn |= btn; + } + + if (pfield != NULL) + { + pf++; + while ((ch = *(pf)) != '\0') + { + if (*pf == ' ') + pf++; + else + break; + } + pbtn = pf; + } + } + else + pf++; + } + + if (act < 0) + { + logf("Error Action Expected: %s", pfield); + return -1; + } + } + + pfield = NULL; + } + + pbuf++; + } + if (field == 2) + { + count = keymap_add_button_entry(context, act, button, prebtn); + if (count > 0) + { + logf("Added: Ctx: %d, Act: %d, Btn: %d PBtn: %d", + context, act, button, prebtn); + } + } + return count; +} + +static int keyremap_import_file(char *filenamebuf, size_t bufsz) +{ + size_t bufleft; + int count = 0; + int line = 0; + filenamebuf[bufsz - 1] = '\0'; + logf("keyremap: import %s", filenamebuf); + int fd = rb->open(filenamebuf, O_RDONLY); + if (fd < 0) + return -1; + + char ch; + char *pbuf; + char *pfirst; + + char *pctx = NULL; + char *pact; + int ctx = -1; + + keyremap_reset_buffer(); +next_line: + while (rb->read_line(fd, filenamebuf, (int) bufsz) > 0) + { + line++; + + + pbuf = filenamebuf; + pfirst = NULL; + pact = NULL; + char *pcomment = rb->strchr(pbuf, '#'); + if (pcomment != NULL) + { + logf("ln: %d: Skipped Comment: %s", line, pcomment); + *pcomment = '\0'; + } + + while ((ch = *(pbuf)) != '\0') + { + /* PARSE CONTEXT = { */ + if ((ch >= 'A' && ch <= 'Z') || ch == '_') + { + if (pfirst == NULL) + pfirst = pbuf; + } + else if (ch == ' ') + { + if (ctx < 0 && pfirst != NULL) + { + *pbuf = '\0'; + pctx = pfirst; + pfirst = NULL; + } + } + else if (ch == '=') + { + if (ctx < 0 && pfirst != NULL) + { + *pbuf = '\0'; + pbuf++; + pctx = pfirst; + pfirst = NULL; + } + while ((ch = *(pbuf)) != '\0') + { + if (ch == '{') + break; + pbuf++; + } + if (ch == '{' && pctx != NULL) + { + bufleft = bufsz - (pctx - filenamebuf); + ctx = -1; + int ctx_x_flag_count = (LAST_CONTEXT_PLACEHOLDER + * ARRAYLEN(context_flags)); + + for (int i=0;i < ctx_x_flag_count ;i++) + { + /* context x flag */ + if (rb->strncasecmp(pctx, ctx_name_and_flag(i), bufleft) == 0) + { + logf("ln: %d: Context Found: %s (%d)", line, pctx, i); + if (keymap_add_context_entry(i) <= 0) + logf("ln: %d: Context Exists: %s (%d)", line, pctx, i); + ctx = i; + goto next_line; + + } + } + logf("ln: %d: ERROR { Context Expected got: %s", line, pctx); + goto fail; + } + } + else if (ch == '}') + { + if (ctx >= 0) + ctx = -1; + else + { + logf("ln: %d: ERROR no context, unexpected close {", line); + goto fail; + } + } + else if (ch == '{') /* PARSE FIELDS { ACTION, BUTTON, PREBTN } */ + { + int res = 0; + if (ctx >= 0) + { + pfirst = pbuf; + + while ((ch = *(pbuf)) != '\0') + { + if (ch == '}') + { + pact = pfirst + 1; + pfirst = NULL; + *pbuf = '\0'; + pbuf = ""; + continue; + } + pbuf++; + } + if (pact != NULL) + { + bufleft = bufsz - (pact - filenamebuf); + logf("ln: %d: Entry Found: {%s} (%d)", line, pact, 0); + res = parse_action_import_entry(ctx, pact, bufleft); + } + } + if (res <= 0) + { + logf("ln: %d: ERROR action entry expected", line); + goto fail; + } + else + { + pbuf = ""; + continue; + } + } + else + pfirst = NULL; + pbuf++; + } + + } + rb->close(fd); + count = ctx_data.ctx_count + ctx_data.act_count; + return count; + +fail: + rb->close(fd); + rb->splashf(HZ * 2, "Error @ line %d", line); + return 0; +} + +static int keyremap_load_file(const char *filename) +{ + logf("keyremap: load %s", filename); + int fd = -1; + size_t fsize = 0; + size_t bytes; + struct button_mapping entry; + int count = keyremap_open_file(filename, &fd, &fsize); + logf("keyremap: entries %d", count); + /* actions are indexed from the first entry after the header save this pos */ + off_t firstpos = rb->lseek(fd, 0, SEEK_CUR); + off_t ctxpos = firstpos; + + if (count > 0) + { + keyremap_reset_buffer(); + while(--count > 0) + { + rb->lseek(fd, ctxpos, SEEK_SET); /* next context remap entry */ + bytes = rb->read(fd, &entry, sizeof(struct button_mapping)); + ctxpos = rb->lseek(fd, 0, SEEK_CUR); + if (bytes != sizeof(struct button_mapping)) + { + count = -10; + goto fail; + } + if (entry.action_code == (int)CONTEXT_STOPSEARCHING) + { + logf("keyremap: end of context entries "); + break; + } + if ((entry.action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + int context = (entry.action_code & ~CONTEXT_REMAPPED); + for (int i = ARRAYLEN(context_flags) - 1; i > 0; i--) /* don't check idx 0*/ + { + /* convert context | flag to context x flag */ + if ((context & context_flags[i].flag) == context_flags[i].flag) + { + logf("found ctx flag %s", context_flags[i].name); + context &= ~context_flags[i].flag; + context += i * LAST_CONTEXT_PLACEHOLDER; + } + } + int offset = entry.button_code; + int entries = entry.pre_button_code; + if (offset == 0 || entries <= 0) + { + logf("keyremap: error reading offset"); + count = -15; + goto fail; + } + logf("keyremap found context: %d file offset: %d entries: %d", + context, offset, entries); + + keymap_add_context_entry(context); + + off_t entrypos = firstpos + (offset * sizeof(struct button_mapping)); + rb->lseek(fd, entrypos, SEEK_SET); + for (int i = 0; i < entries; i++) + { + bytes = rb->read(fd, &entry, sizeof(struct button_mapping)); + if (bytes == sizeof(struct button_mapping)) + { + int act = entry.action_code; + int button = entry.button_code; + int prebtn = entry.pre_button_code; + + if (act == (int)CONTEXT_STOPSEARCHING || button == BUTTON_NONE) + { + logf("keyremap: entry invalid"); + goto fail; + } + logf("keyremap: found ctx: %d, act: %d btn: %d pbtn: %d", + context, act, button, prebtn); + keymap_add_button_entry(context, act, button, prebtn); + } + else + goto fail; + } + } + else + { + logf("keyremap: Invalid context entry"); + keyremap_reset_buffer(); + count = -20; + goto fail; + } + } + } + + int entries = 0; + struct button_mapping *keymap = keyremap_create_temp(&entries); + if (keymap != NULL) + { + keyset.crc32 = + rb->crc_32(keymap, entries * sizeof(struct button_mapping), 0xFFFFFFFF); + } +fail: + rb->close(fd); + if (count <= 0) + rb->splashf(HZ * 2, "Error Loading %sz", filename); + return count; +} + +static const struct button_mapping* test_get_context_map(int context) +{ + (void)context; + + if (keytest.keymap != NULL && keytest.context >= 0) + { + int mapidx = keytest.keymap[keytest.index].button_code; + int mapcnt = keytest.keymap[keytest.index].pre_button_code; + /* make fallthrough to the test context*/ + keytest.keymap[mapidx + mapcnt].action_code = keytest.context; + static const struct button_mapping *testctx[] = { NULL }; + testctx[0] = &keytest.keymap[mapidx]; + return testctx[0]; + } + else + return NULL; +} + +static const char *kmffiles_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + /* found kmf filenames returned by each call kmffiles_dirp keeps state + * selected_item = 0 resets state */ + + (void)data; + buf[0] = '\0'; + if (selected_item == 0) + { + rb->closedir(kmffiles_dirp); + kmffiles_dirp = rb->opendir(KMFDIR); + } + if (kmffiles_dirp != NULL) + { + struct dirent *entry; + while ((entry = rb->readdir(kmffiles_dirp))) + { + /* skip directories */ + if ((rb->dir_get_info(kmffiles_dirp, entry).attribute & ATTR_DIRECTORY) != 0) + continue; + if (keyremap_check_extension(entry->d_name) > 0) + { + rb->snprintf(buf, buf_len, "%s", entry->d_name); + return buf; + } + } + } + return "Error!"; +} + +static void menu_useract_set_positions(void) +{ + /* responsible for item ordering to display action edit interface */ + int display_pos = 0; /* start at item 0*/ + int i, j; + for (i = 0; i < ctx_data.ctx_count; i++) + { + int context = ctx_data.ctx_map[i].context; + ctx_data.ctx_map[i].display_pos = display_pos++; + /* how many actions are contained in this context? */ + for (j = 0; j < ctx_data.act_count; j++) + { + if (ctx_data.act_map[j].context == context) + { + ctx_data.act_map[j].display_pos = display_pos++; + } + } + } +} + +static const char *menu_useract_items_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + static char buf_button[MAX_BUTTON_NAME * MAX_BUTTON_COMBO]; + static char buf_prebtn[MAX_BUTTON_NAME * MAX_BUTTON_COMBO]; + int i; + int szbtn = sizeof("BUTTON"); + int szctx = sizeof("CONTEXT"); + int szact = sizeof("ACTION"); + const char* ctxfmt = "%s"; + + if (data == MENU_ID(M_EXPORTKEYS)) + { + szbtn = 0; + szctx = 0; + szact = 0; + ctxfmt = "$%s = {\n\n"; + } + buf[0] = '\0'; + for(i = 0; i < ctx_data.ctx_count; i ++) + { + if (ctx_data.ctx_map[i].display_pos == selected_item) + { + if (ctx_data.act_count == 0) + rb->snprintf(buf, buf_len, "%s$%s", + ctx_name_and_flag(ctx_data.ctx_map[i].context), + "Select$to add$actions"); + else + rb->snprintf(buf, buf_len, ctxfmt, ctx_name_and_flag(ctx_data.ctx_map[i].context)); + return buf; + } + } + for(i = 0; i < ctx_data.act_count; i ++) + { + if (ctx_data.act_map[i].display_pos == selected_item) + { + int context = ctx_data.act_map[i].context; + char ctxbuf[action_helper_maxbuffer]; + char *pctxbuf = "\0"; + char *pactname; + if (data != MENU_ID(M_EXPORTKEYS)) + { + pctxbuf = ctxbuf; + rb->snprintf(ctxbuf, sizeof(ctxbuf), ctxfmt, ctx_name_and_flag(context)); + pctxbuf += szctx;//sizeof("CONTEXT") + } + struct button_mapping * bm = &ctx_data.act_map[i].map; + pactname = action_name(bm->action_code); + pactname += szact;//sizeof("ACTION") + /* BUTTON & PRE_BUTTON */ + btnval_to_name(buf_button, sizeof(buf_button), bm->button_code); + btnval_to_name(buf_prebtn, sizeof(buf_prebtn), bm->pre_button_code); + + rb->snprintf(buf, buf_len, ctx_menu_data.act_fmt, pctxbuf, + pactname, buf_button + szbtn, buf_prebtn + szbtn); + return buf; + } + } + return "Error!"; +} + +static const char *edit_keymap_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + buf[0] = '\0'; + if (selected_item == 0) + { + rb->snprintf(buf, buf_len, "Add Context"); + ctx_menu_data.act_index = -1; + ctx_menu_data.ctx_index = -1; + ctx_menu_data.act_fmt = ACTIONFMT_LV0; + } + else if (ctx_data.ctx_count > 0) + { + return menu_useract_items_cb(selected_item - 1, data, buf, buf_len); + } + return buf; +} + +static const char *test_keymap_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + (void)data; + buf[0] = '\0'; + if (keytest.context >= 0) + { + if (selected_item == 0) + rb->snprintf(buf, buf_len, "< %s >", ctx_name_and_flag(keytest.context)); + else if (selected_item == 1) + { + if (keytest.countdown >= 10) + rb->snprintf(buf, buf_len, "Testing %d", keytest.countdown / 10); + else + rb->snprintf(buf, buf_len, "Start test"); + } + else if (selected_item == 2) + rb->snprintf(buf, buf_len, "%s", action_name(keytest.action)); + } + return buf; +} + +static const char* list_get_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + buf[0] = '\0'; + const struct mainmenu *cur = (struct mainmenu *) data; + if (data == MENU_ID(M_ROOT)) + return mainitem(selected_item + 1)->name; + else if (selected_item >= cur->items - 1) + { + return ID2P(LANG_BACK); + } + if (data == MENU_ID(M_SETKEYS)) + { + return edit_keymap_name_cb(selected_item, data, buf, buf_len); + } + else if (data == MENU_ID(M_BUTTONS)) + { + const struct available_button *btn = &available_buttons[selected_item]; + rb->snprintf(buf, buf_len, "%s: [0x%X] ", btn->name, (unsigned int) btn->value); + return buf; + } + else if (data == MENU_ID(M_ACTIONS)) + { + return action_name(selected_item); + } + else if (data == MENU_ID(M_CONTEXTS)) + { + return ctx_name_and_flag(selected_item); + } + else if (data == MENU_ID(M_DELKEYS) || data == MENU_ID(M_LOADKEYS)) + { + /* need to iterate the callback for the items off screen to + * keep ordering, this limits the menu to only the main screen :( */ + int start_item = lists.start_item[SCREEN_MAIN]; + if (start_item != 0 && start_item == selected_item) + { + for (int i = 0; i < start_item; i++) + kmffiles_name_cb(i, data, buf, buf_len); + } + return kmffiles_name_cb(selected_item, data, buf, buf_len); + } + else if (data == MENU_ID(M_TESTKEYS)) + { + return test_keymap_name_cb(selected_item, 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_SETKEYS)) + { + char buf[MAX_MENU_NAME]; + int selcol = printcell_get_column_selected(); + const char* name = printcell_get_column_text(selcol, buf, sizeof(buf)); + long id = P2ID((const unsigned char *)name); + if(id>=0) + rb->talk_id(id, true); + else + rb->talk_spell(name, true); + } + else + { + char buf[MAX_MENU_NAME]; + 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_root(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ +#ifdef ROCKBOX_HAS_LOGF + char logfnamebuf[64]; +#endif + + if (*action == ACTION_STD_OK) + { + struct mainmenu *cur = mainitem(selected_item + 1); + if (cur != NULL) + { +#ifdef ROCKBOX_HAS_LOGF + lang_strlcpy(logfnamebuf, cur->name, sizeof(logfnamebuf)); + logf("Root select menu -> %s(%d) ", logfnamebuf, cur->index); +#endif + if (cur->menuid == MENU_ID(M_SETKEYS)) + { + keyset.view_lastcol = -1; + } + else if (cur->menuid == MENU_ID(M_TMPCORE)) + { + int entry_count;/* (ctx_count + ctx_count + act_count + 1) */ + struct button_mapping *keymap = keyremap_create_temp(&entry_count); + if (rb->core_set_keyremap(keymap, entry_count) >= 0) + rb->splash(HZ *2, "Keymap Applied"); + else + rb->splash(HZ *2, "Error Applying"); + + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_SETCORE)) + { + if (rb->file_exists(CORE_KEYREMAP_FILE) && 0 == core_savecount++) + { + rb->rename(CORE_KEYREMAP_FILE, CORE_KEYREMAP_FILE".old"); /* make a backup */ + } + if (keyremap_save_current(CORE_KEYREMAP_FILE) == 0) + rb->splash(HZ *2, "Error Saving"); + else + { + rb->splash(HZ *2, "Saved"); + int entry_count;/* (ctx_count + ctx_count + act_count + 1) */ + struct button_mapping *keymap = keyremap_create_temp(&entry_count); + rb->core_set_keyremap(keymap, entry_count); + } + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_DELCORE)) + { + rb->core_set_keyremap(NULL, -1); + if (rb->file_exists(CORE_KEYREMAP_FILE)) + { + rb->rename(CORE_KEYREMAP_FILE, KMFDIR "/core_deleted" KMFEXT2); + rb->splash(HZ *2, "Removed"); + } + else + rb->splash(HZ *2, "Error Removing"); + + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_SAVEKEYS)) + { + keyremap_save_user_keys(true); + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_EXPORTKEYS)) + { + keyremap_export_user_keys(); + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_IMPORTKEYS)) + { + keyremap_import_user_keys(); + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_DELKEYS) || + cur->menuid == MENU_ID(M_LOADKEYS)) + { + cur->items = keyremap_count_files(KMFDIR) + 1; + } + else if (cur->menuid == MENU_ID(M_TESTKEYS)) + { + int entries = 0; + keytest.keymap = keyremap_create_temp(&entries); + if (entries > 0) + { + struct button_mapping *entry = &keytest.keymap[0]; + if (entry->action_code != (int)CONTEXT_STOPSEARCHING + && (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + keytest.context = (entry->action_code & ~CONTEXT_REMAPPED); + } + else + keytest.context = -1; + } + else + { + keytest.keymap = NULL; + keytest.context = -1; + } + keytest.action = ACTION_NONE; + keytest.countdown = 0; + } + else if (cur->menuid == MENU_ID(M_RESETKEYS)) + { + if (rb->yesno_pop("Delete Current Entries?") == true) + { + keyremap_reset_buffer(); + } + goto default_handler; + } + } + + if (cur->menuid == NULL || cur->menuid == MENU_ID(M_EXIT)) + { + logf("Root menu %s", (cur->menuid) == NULL ? "NULL":"Exit"); + *action = ACTION_STD_CANCEL; + *exit = true; + } + else + { +#ifdef ROCKBOX_HAS_LOGF + lang_strlcpy(logfnamebuf, cur->name, sizeof(logfnamebuf)); + logf("Root load menu -> %s(%d) ", logfnamebuf, cur->index); +#endif + synclist_set_update(cur->index, 0, cur->items, 1); + rb->gui_synclist_draw(lists); + *action = ACTION_NONE; + } + } + + return PLUGIN_OK; +default_handler: + return GOTO_ACTION_DEFAULT_HANDLER; +} + +int menu_action_setkeys(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + (void) exit; + int i; + struct mainmenu *cur = (struct mainmenu *)lists->data; + if (*action == ACTION_STD_OK) + { + if (selected_item == 0) /*add_context*/ + { + const struct mainmenu *mainm = &mainmenu[M_CONTEXTS]; + synclist_set_update(mainm->index, 0, mainm->items, 1); + rb->gui_synclist_draw(lists); + goto default_handler; + } + else if (selected_item < lists->nb_items - 1)/* not back*/ + { + bool add_action = false; + for (i = 0; i < ctx_data.ctx_count; i++) + { + if (ctx_data.ctx_map[i].display_pos == selected_item - 1) + { + add_action = true; + break; + } + } + + if (add_action) + { + keymap_add_button_entry(ctx_data.ctx_map[i].context, ACTION_NONE, BUTTON_NONE, BUTTON_NONE); + cur->items++; + lists->nb_items++; + goto default_handler; + } + else + { + keyset.view_lastcol = printcell_increment_column(1, true); + *action = ACTION_NONE; + } + } + } + else if (*action == ACTION_STD_CANCEL) + { + keyset.view_lastcol = printcell_increment_column(-1, true); + if (keyset.view_lastcol != keyset.view_columns - 1) + { + *action = ACTION_NONE; + } + } + else if (*action == ACTION_STD_CONTEXT) + { + int col = keyset.view_lastcol; + for (i = 0; i < ctx_data.act_count; i++) + { + if (ctx_data.act_map[i].display_pos == selected_item - 1) + { + int context = ctx_data.act_map[i].context; + int act = ctx_data.act_map[i].map.action_code; + int button = ctx_data.act_map[i].map.button_code; + int prebtn = ctx_data.act_map[i].map.pre_button_code; + + if (col < 0) + { + rb->splashf(HZ, "short press increments columns"); + + } + else if (col == 0) /* Context */ + { + const struct mainmenu *mainm = &mainmenu[M_CONTEXTS]; + synclist_set_update(mainm->index, context, mainm->items, 1); + rb->gui_synclist_draw(lists); + goto default_handler; + + } + else if (col == 1) /* Action */ + { + const struct mainmenu *mainm = &mainmenu[M_ACTIONS]; + synclist_set_update(mainm->index, act, mainm->items, 1); + rb->gui_synclist_draw(lists); + goto default_handler; + } + else if (col == 2) /* Button */ + { + const struct mainmenu *mainm = &mainmenu[M_BUTTONS]; + int btnidx = btnval_to_index(button); + synclist_set_update(mainm->index, btnidx, mainm->items, 1); + rb->gui_synclist_draw(lists); + goto default_handler; + } + else if (col == 3) /* PreBtn */ + { + const struct mainmenu *mainm = &mainmenu[M_BUTTONS]; + int pbtnidx = btnval_to_index(prebtn); + synclist_set_update(mainm->index, pbtnidx, mainm->items, 1); + rb->gui_synclist_draw(lists); + goto default_handler; + } + } + } + } + return PLUGIN_OK; +default_handler: + return GOTO_ACTION_DEFAULT_HANDLER; +} + +int menu_action_testkeys(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + (void) exit; + (void) lists; + static int last_count = 0; + if (keytest.keymap != NULL && keytest.countdown >= 10 && keytest.context >= 0) + { + int newact = rb->get_custom_action(CONTEXT_PLUGIN, HZ / 10, test_get_context_map); + if (newact == ACTION_NONE) + { + keytest.countdown--; + if (last_count - keytest.countdown > 10) + keytest.action = newact; + } + else + { + last_count = keytest.countdown; + keytest.action = newact; + } + *action = ACTION_REDRAW; + goto default_handler; + } + + if (*action == ACTION_STD_CANCEL && selected_item == 0 && keytest.keymap != NULL) + { + keytest.index -= 2; + if (keytest.index < 0) + keytest.index = 0; + *action = ACTION_STD_OK; + } + + if (*action == ACTION_STD_OK) + { + if (selected_item == 0) + { + if (keytest.keymap != NULL) + { + struct button_mapping *entry = &keytest.keymap[++keytest.index]; + if (entry->action_code != (int) CONTEXT_STOPSEARCHING + && (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + keytest.context = (entry->action_code & ~CONTEXT_REMAPPED); + } + else + { + entry = &keytest.keymap[0]; + if (entry->action_code != (int)CONTEXT_STOPSEARCHING + && (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + keytest.context = (entry->action_code & ~CONTEXT_REMAPPED); + keytest.index = 0; + } + else + { + keytest.keymap = NULL; + keytest.context = -1; + keytest.index = -1; + keytest.action = ACTION_NONE; + keytest.countdown = 0; + } + } + } + } + else if (selected_item == 1) + { + keytest.countdown = TEST_COUNTDOWN_MS / 10; + keytest.action = ACTION_NONE; + } + } + + return PLUGIN_OK; +default_handler: + return GOTO_ACTION_DEFAULT_HANDLER; +} + +int menu_action_listfiles(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + (void) exit; + int i; + if (*action == ACTION_STD_OK) + { + struct mainmenu *cur = (struct mainmenu *)lists->data; + if (selected_item >= (cur->items) - 1)/*back*/ + { + *action = ACTION_STD_CANCEL; + } + else + { + /* iterate to the desired file */ + char buf[MAX_PATH]; + int len = rb->snprintf(buf, sizeof(buf), "%s/", KMFDIR); + if (len < (int) sizeof(buf)) + { + char *name = &buf[len]; + size_t bufleft = sizeof(buf) - len; + list_get_name_cb(0, lists->data, name, bufleft); + for (i = 1; i <= selected_item; i++) + { + list_get_name_cb(i, lists->data, name, bufleft); + } + + if (lists->data == MENU_ID(M_DELKEYS)) + { + const char *lines[] = {ID2P(LANG_DELETE), buf}; + const struct text_message message={lines, 2}; + if (rb->gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES) + { + rb->remove(buf); + cur->items--; + lists->nb_items--; + *action = ACTION_NONE; + } + } + else if (lists->data == MENU_ID(M_LOADKEYS)) + { + if (keyremap_load_file(buf) > 0) + { + rb->splashf(HZ * 2, "Loaded %s ", buf); + *action = ACTION_STD_CANCEL; + } + } + } + } + } + return PLUGIN_OK; +} + +int menu_action_submenus(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ +#ifdef ROCKBOX_HAS_LOGF + char logfnamebuf[64]; +#endif + (void) exit; + if (*action == ACTION_STD_OK) + { + struct mainmenu *cur = (struct mainmenu *)lists->data; + if (selected_item >= (cur->items) - 1)/*back*/ + { + *action = ACTION_STD_CANCEL; + } + else if (lists->data == MENU_ID(M_ACTIONS)) + { +#ifdef ROCKBOX_HAS_LOGF + const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf)); + logf("ACT %s %d (0x%X)", name, selected_item, selected_item); +#endif + int id, item; + POP_MENU(id, item); + POP_MENU(id, item); + const struct mainmenu *lastm = &mainmenu[id]; + + if (id == M_SETKEYS) + { + for (int i = 0; i < ctx_data.act_count; i++) + { + if (ctx_data.act_map[i].display_pos == item - 2) + { + int col = keyset.view_lastcol; + struct button_mapping * bm = &ctx_data.act_map[i].map; + if (col == 1) /* Action */ + { + bm->action_code = selected_item; + } + break; + } + } + } + synclist_set(lastm->index, item-1, lastm->items, 1); + rb->gui_synclist_draw(lists); + } + else if (lists->data == MENU_ID(M_CONTEXTS)) + { +#ifdef ROCKBOX_HAS_LOGF + const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf)); + logf("CTX %s %d (0x%X)", name, selected_item, selected_item); +#endif + int id, item; + POP_MENU(id, item); + POP_MENU(id, item); + struct mainmenu *lastm = &mainmenu[id]; + if (id == M_SETKEYS) + { + bool found = false; + int newctx = selected_item; + + for (int i = 0; i < ctx_data.act_count; i++) + { + if (ctx_data.act_map[i].display_pos == item - 2) + { + int col = keyset.view_lastcol; + if (col == 0) /* Context */ + { + ctx_data.act_map[i].context = newctx; + /* check if this context exists (if not create it) */ + for (int j = 0; j < ctx_data.ctx_count; j++) + { + if (ctx_data.ctx_map[j].context == newctx) + { + found = true;; + break; + } + } + } + break; + } + } + if (found == false) + { + keymap_add_context_entry(newctx); + lastm->items++; + } + } + synclist_set(lastm->index, item-1, lastm->items, 1); + rb->gui_synclist_draw(lists); + } + else if (lists->data == MENU_ID(M_BUTTONS)) + { +#ifdef ROCKBOX_HAS_LOGF + const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf)); + logf("BTN %s", name); +#endif + int id, item; + POP_MENU(id, item); + POP_MENU(id, item); + const struct mainmenu *lastm = &mainmenu[id]; + + if (id == M_SETKEYS) + { + for (int i = 0; i < ctx_data.act_count; i++) + { + if (ctx_data.act_map[i].display_pos == item - 2) + { + int col = keyset.view_lastcol; + struct button_mapping * bm = &ctx_data.act_map[i].map; + const struct available_button *btn = &available_buttons[selected_item]; + if (col == 2) /* BUTTON*/ + { + if (btn->value == BUTTON_NONE) + bm->button_code = btn->value; + else + bm->button_code |= btn->value; + } + else if (col == 3) /* PREBTN */ + { + if (btn->value == BUTTON_NONE) + bm->pre_button_code = btn->value; + else + bm->pre_button_code |= btn->value; + } + break; + } + } + } + synclist_set(lastm->index, item-1, lastm->items, 1); + rb->gui_synclist_draw(lists); + } + } + return PLUGIN_OK; +} + +static void cleanup(void *parameter) +{ + (void)parameter; + keyremap_save_user_keys(false); +} + +int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + int status = PLUGIN_OK; + /* Top Level Menu Actions */ + if (lists->data == MENU_ID(M_ROOT)) + { + status = menu_action_root(action, selected_item, exit, lists); + } + else if (lists->data == MENU_ID(M_SETKEYS)) + { + status = menu_action_setkeys(action, selected_item, exit, lists); + } + else if (lists->data == MENU_ID(M_TESTKEYS)) + { + status = menu_action_testkeys(action, selected_item, exit, lists); + } + else if (lists->data == MENU_ID(M_DELKEYS)) + { + status = menu_action_listfiles(action, selected_item, exit, lists); + } + else if (lists->data == MENU_ID(M_LOADKEYS)) + { + status = menu_action_listfiles(action, selected_item, exit, lists); + } + + if (status == GOTO_ACTION_DEFAULT_HANDLER) + goto default_handler; + /* Submenu Actions */ + menu_action_submenus(action, selected_item, exit, lists); + + /* Global cancel */ + if (*action == ACTION_STD_CANCEL) + { + logf("CANCEL BUTTON"); + if (lists->data == MENU_ID(M_TESTKEYS)) + { + keytest.keymap = NULL; + keytest.context = -1; + } + + if (lists->data != MENU_ID(M_ROOT)) + { + int id, item; + POP_MENU(id, item); + POP_MENU(id, item); + const struct mainmenu *lastm = &mainmenu[id]; + synclist_set(lastm->index, item-1, lastm->items, 1); + rb->gui_synclist_draw(lists); + } + else + { + /* save changes? */ + if (ctx_data.act_count + ctx_data.ctx_count >= 2) + { + int entries = 0; + struct button_mapping *keymap = keyremap_create_temp(&entries); + if (keymap != NULL && keyset.crc32 != rb->crc_32(keymap, + entries * sizeof(struct button_mapping), 0xFFFFFFFF)) + { + if (rb->yesno_pop("Save Keymap?") == true) + { + keyremap_save_user_keys(true); + goto default_handler; + } + } + } + *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(int id, int selected_item, int items, int sel_size) +{ + void* menu_id = MENU_ID(id); + static char menu_title[64]; /* title is used by every menu */ + PUSH_MENU(id, selected_item); + + 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_select_item(&lists, selected_item); + printcell_enable(false); + + if (menu_id == MENU_ID(M_ROOT)) + { + lang_strlcpy(menu_title, mainmenu[M_ROOT].name, sizeof(menu_title)); + rb->gui_synclist_set_title(&lists, menu_title, Icon_Plugin); + } + else if (menu_id == MENU_ID(M_SETKEYS)) + { + keyset.view_columns = printcell_set_columns(&lists, NULL, + ACTVIEW_HEADER, Icon_Rockbox); + printcell_enable(true); + int curcol = printcell_get_column_selected(); + if (keyset.view_lastcol >= keyset.view_columns) + keyset.view_lastcol = -1; + /* restore column position */ + while (keyset.view_lastcol > -1 && curcol != keyset.view_lastcol) + { + curcol = printcell_increment_column(1, true); + } + keyset.view_lastcol = curcol; + } + else + { + int id; + PEEK_MENU_ID(id); + lang_strlcpy(menu_title, mainitem(id)->name, sizeof(menu_title)); + rb->gui_synclist_set_title(&lists, menu_title, Icon_Submenu_Entered); + /* if (menu_title[0] == '$'){ printcell_enable(true); } */ + } +} + +static void keyremap_set_buffer(void* buffer, size_t buf_size) +{ + /* set up the keyremap action buffer + * contexts start at the front and work towards the back + * actions start at the back and work towards front + * if they meet buffer is out of space (checked by ctx & btn add entry fns) + */ + keyremap_buffer.buffer = buffer; + keyremap_buffer.buf_size = buf_size; + keyremap_reset_buffer(); +} + +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) + { + // + } + + size_t buf_size; + void* buffer = rb->plugin_get_buffer(&buf_size); + keyremap_set_buffer(buffer, buf_size); + + mainmenu[M_BUTTONS].items = available_button_count; + /* add back item to each submenu */ + for (int i = 1; i < M_LAST_ITEM; i++) + mainmenu[i].items += 1; + +#if 0 + keymap_add_context_entry(CONTEXT_WPS); + keymap_add_button_entry(CONTEXT_WPS, ACTION_WPS_PLAY, BUTTON_VOL_UP, BUTTON_NONE); + keymap_add_button_entry(CONTEXT_WPS, ACTION_WPS_STOP, BUTTON_VOL_DOWN | BUTTON_REPEAT, BUTTON_NONE); + + keymap_add_context_entry(CONTEXT_MAINMENU); + keymap_add_button_entry(CONTEXT_MAINMENU, ACTION_STD_PREV, BUTTON_VOL_UP, BUTTON_NONE); + keymap_add_button_entry(CONTEXT_MAINMENU, ACTION_STD_NEXT, BUTTON_VOL_DOWN, BUTTON_NONE); + + keymap_add_context_entry(CONTEXT_STD); + keymap_add_button_entry(CONTEXT_STD, ACTION_STD_OK, BUTTON_VOL_UP, BUTTON_NONE); + keymap_add_button_entry(CONTEXT_STD, ACTION_STD_OK, BUTTON_VOL_DOWN, BUTTON_NONE); +#endif + + keyset.crc32 = 0; + keyset.view_lastcol = -1; + if (!exit) + { + const struct mainmenu *mainm = &mainmenu[M_ROOT]; + synclist_set(mainm->index, 0, mainm->items, 1); + rb->gui_synclist_draw(&lists); + + while (!exit) + { + action = rb->get_action(CONTEXT_LIST, HZ / 10); + + if (action == ACTION_NONE) + { + if (redraw) + { + action = ACTION_REDRAW; + redraw = false; + } + } + else + redraw = true; + + ret = menu_action_cb(&action, selected_item, &exit, &lists); + if (rb->gui_synclist_do_button(&lists, &action)) + continue; + selected_item = rb->gui_synclist_get_sel_pos(&lists); + + mainmenu[M_SETKEYS].items = ctx_data.ctx_count + ctx_data.act_count + 2; + } + } + rb->closedir(kmffiles_dirp); + + return ret; +} + + + +#if 0 /* Alt Example */ +static int write_keyremap(const char*filename, struct button_mapping *remap_data, int count) +{ + size_t res = 0; + if (!filename || !remap_data || count <= 0) + goto fail; + int fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + goto fail; + if (keyremap_write_header(fd, count + 1) > 0) + { + res = keyremap_write_entries(fd, remap_data, count); + res += sizeof(struct button_mapping); /*for header */ + } +fail: + rb->close(fd); + return res; +} + +void alt_example(void) +{ + struct button_mapping kmap[8]; + + kmap[0].action_code = CORE_CONTEXT_REMAP(CONTEXT_WPS); + kmap[0].button_code = 3; /*OFFSET*/ + kmap[0].pre_button_code = 1; /*COUNT*/ + + kmap[1].action_code = CORE_CONTEXT_REMAP(CONTEXT_MAINMENU); + kmap[1].button_code = 5; /*OFFSET*/ + kmap[1].pre_button_code = 2; /*COUNT*/ + + kmap[2].action_code = CONTEXT_STOPSEARCHING; + kmap[2].button_code = BUTTON_NONE; + kmap[2].pre_button_code = BUTTON_NONE; + + kmap[3].action_code = ACTION_WPS_PLAY; + kmap[3].button_code = BUTTON_VOL_UP; + kmap[3].pre_button_code = BUTTON_NONE; + + kmap[4].action_code = CONTEXT_STOPSEARCHING; + kmap[4].button_code = BUTTON_NONE; + kmap[4].pre_button_code = BUTTON_NONE; + + kmap[5].action_code = ACTION_STD_NEXT; + kmap[5].button_code = BUTTON_VOL_DOWN; + kmap[5].pre_button_code = BUTTON_NONE; + + kmap[6].action_code = ACTION_STD_PREV; + kmap[6].button_code = BUTTON_VOL_UP; + kmap[6].pre_button_code = BUTTON_NONE; + + kmap[7].action_code = CONTEXT_STOPSEARCHING; + kmap[7].button_code = BUTTON_NONE; + kmap[7].pre_button_code = BUTTON_NONE; + + write_keyremap(CORE_KEYREMAP_FILE, kmap, 8); + + return ret; +} +#endif diff --git a/apps/plugins/lamp.c b/apps/plugins/lamp.c index 6c9ae6626d..5aa06f4d2d 100644 --- a/apps/plugins/lamp.c +++ b/apps/plugins/lamp.c @@ -58,7 +58,14 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #define LAMP_EXIT PLA_EXIT + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define LAMP_EXIT2 PLA_UP +#else #define LAMP_EXIT2 PLA_CANCEL +#endif #ifdef HAVE_LCD_COLOR @@ -103,9 +110,8 @@ enum plugin_status plugin_start(const void* parameter) int current_brightness = MAX_BRIGHTNESS_SETTING; backlight_brightness_set(MAX_BRIGHTNESS_SETTING); #endif /* HAVE_BACKLIGHT_BRIGHTNESS */ -#ifdef HAVE_BUTTONLIGHT_BRIGHTNESS + buttonlight_brightness_set(MAX_BRIGHTNESS_SETTING); -#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */ #ifdef HAVE_LCD_INVERT #ifdef HAVE_NEGATIVE_LCD @@ -116,9 +122,8 @@ enum plugin_status plugin_start(const void* parameter) #endif /* HAVE_LCD_INVERT */ backlight_force_on(); -#ifdef HAVE_BUTTON_LIGHT buttonlight_force_on(); -#endif /* HAVE_BUTTON_LIGHT */ + rb->lcd_clear_display(); rb->lcd_update(); @@ -207,20 +212,17 @@ enum plugin_status plugin_start(const void* parameter) /* restore */ backlight_use_settings(); -#ifdef HAVE_BUTTON_LIGHT + buttonlight_use_settings(); -#endif /* HAVE_BUTTON_LIGHT */ #ifdef HAVE_LCD_INVERT rb->lcd_set_invert_display(rb->global_settings->invert); #endif /* HAVE_LCD_INVERT */ -#ifdef HAVE_BACKLIGHT_BRIGHTNESS + backlight_brightness_use_setting(); -#endif /* HAVE_BACKLIGHT_BRIGHTNESS */ -#ifdef HAVE_BUTTONLIGHT_BRIGHTNESS + buttonlight_brightness_use_setting(); -#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */ #if LCD_DEPTH > 1 rb->lcd_set_background(bg_color); diff --git a/apps/plugins/lastfm_scrobbler.c b/apps/plugins/lastfm_scrobbler.c new file mode 100644 index 0000000000..2c0597656c --- /dev/null +++ b/apps/plugins/lastfm_scrobbler.c @@ -0,0 +1,938 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2008 Robert Keevil + * Converted to Plugin + * Copyright (C) 2022 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. + * + ****************************************************************************/ +/* Scrobbler Plugin +Audioscrobbler spec at: (use wayback machine) +http://www.audioscrobbler.net/wiki/Portable_Player_Logging +* EXCERPT: +* The first lines of .scrobbler.log should be header lines, indicated by the leading '#' character: + +#AUDIOSCROBBLER/1.1 +#TZ/[UNKNOWN|UTC] +#CLIENT/<IDENTIFICATION STRING> + +Where 1.1 is the version for this file format + + If the device knows what timezone it is in, + it must convert all logged times to UTC (aka GMT+0) + eg: #TZ/UTC + If the device knows the time, but not the timezone + eg: #TZ/UNKNOWN + +<IDENTIFICATION STRING> should be replaced by the name/model of the hardware device + and the revision of the software producing the log file. + +After the header lines, simply append one line of text for every song + that is played or skipped. + +The following fields comprise each line, and are tab (\t) + separated (strip any tab characters from the data): + + - artist name + - album name (optional) + - track name + - track position on album (optional) + - song duration in seconds + - rating (L if listened at least 50% or S if skipped) + - unix timestamp when song started playing + - MusicBrainz Track ID (optional) +lines should be terminated with \n +Example +(listened to enter sandman, skipped cowboys, listened to the pusher) : + #AUDIOSCROBBLER/1.0 + #TZ/UTC + #CLIENT/Rockbox h3xx 1.1 + Metallica Metallica Enter Sandman 1 365 L 1143374412 62c2e20a?-559e-422f-a44c-9afa7882f0c4? + Portishead Roseland NYC Live Cowboys 2 312 S 1143374777 db45ed76-f5bf-430f-a19f-fbe3cd1c77d3 + Steppenwolf Live The Pusher 12 350 L 1143374779 58ddd581-0fcc-45ed-9352-25255bf80bfb? + If the data for optional fields is not available to you, leave the field blank (\t\t). + All strings should be written as UTF-8, although the file does not use a BOM. + All fields except those marked (optional) above are required. +*/ + +#include "plugin.h" +#include "lib/configfile.h" + +#ifndef UNTAGGED + #define UNTAGGED "<UNTAGGED>" +#endif + +#ifdef ROCKBOX_HAS_LOGF +#define logf rb->logf +#else +#define logf(...) do { } while(0) +#endif + +/****************** constants ******************/ +#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF) +#define EV_FLUSHCACHE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFE) +#define EV_USER_ERROR MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFD) +#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01) +#define EV_TRACKCHANGE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02) +#define EV_TRACKFINISH MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x03) + +#define ERR_NONE (0) +#define ERR_WRITING_FILE (-1) +#define ERR_ENTRY_LENGTH (-2) +#define ERR_WRITING_DATA (-3) + +/* increment this on any code change that effects output */ +#define SCROBBLER_VERSION "1.1" + +#define SCROBBLER_REVISION " $Revision$" + +#define SCROBBLER_BAD_ENTRY "# FAILED - " + +/* longest entry I've had is 323, add a safety margin */ +#define SCROBBLER_CACHE_LEN (512) +#define SCROBBLER_MAX_CACHE (32 * SCROBBLER_CACHE_LEN) + +#define SCROBBLER_MAX_TRACK_MRU (32) /* list of hashes to detect repeats */ + +#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n" + +#define CFG_FILE "/lastfm_scrobbler.cfg" +#define CFG_VER 1 + +#if CONFIG_RTC +static time_t timestamp; +#define BASE_FILENAME HOME_DIR "/.scrobbler.log" +#define HDR_STR_TIMELESS +#define get_timestamp() ((long)timestamp) +#define record_timestamp() ((void)(timestamp = rb->mktime(rb->get_time()))) +#else /* !CONFIG_RTC */ +#define HDR_STR_TIMELESS " Timeless" +#define BASE_FILENAME HOME_DIR "/.scrobbler-timeless.log" +#define get_timestamp() (0l) +#define record_timestamp() ({}) +#endif /* CONFIG_RTC */ + +#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE + +/****************** prototypes ******************/ +enum plugin_status plugin_start(const void* parameter); /* entry */ +void play_tone(unsigned int frequency, unsigned int duration); +/****************** globals ******************/ +/* communication to the worker thread */ +static struct +{ + bool exiting; /* signal to the thread that we want to exit */ + bool hide_reentry; /* we may return on WPS fail, hide next invocation */ + unsigned int id; /* worker thread id */ + struct event_queue queue; /* thread event queue */ + struct queue_sender_list queue_send; + long stack[THREAD_STACK_SIZE / sizeof(long)]; +} gThread; + +struct cache_entry +{ + size_t len; + uint32_t crc; + char buf[ ]; +}; + +static struct scrobbler_cache +{ + int entries; + char *buf; + size_t pos; + size_t size; + bool pending; + bool force_flush; + struct mutex mtx; +} gCache; + +static struct scrobbler_cfg +{ + int uniqct; + int savepct; + int minms; + int beeplvl; + bool playback; + bool verbose; +} gConfig; + +static struct configdata config[] = +{ + #define MAX_MRU (SCROBBLER_MAX_TRACK_MRU) + {TYPE_INT, 0, MAX_MRU, { .int_p = &gConfig.uniqct }, "UniqCt", NULL}, + {TYPE_INT, 0, 100, { .int_p = &gConfig.savepct }, "SavePct", NULL}, + {TYPE_INT, 0, 10000, { .int_p = &gConfig.minms }, "MinMs", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.playback }, "Playback", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.verbose }, "Verbose", NULL}, + {TYPE_INT, 0, 10, { .int_p = &gConfig.beeplvl }, "BeepLvl", NULL}, + #undef MAX_MRU +}; +const int gCfg_sz = sizeof(config)/sizeof(*config); + +/****************** config functions *****************/ +static void config_set_defaults(void) +{ + gConfig.uniqct = SCROBBLER_MAX_TRACK_MRU; + gConfig.savepct = 50; + gConfig.minms = 500; + gConfig.playback = false; + gConfig.verbose = true; + gConfig.beeplvl = 10; +} + +static int config_settings_menu(void) +{ + int selection = 0; + + static uint32_t crc = 0; + + struct viewport parentvp[NB_SCREENS]; + FOR_NB_SCREENS(l) + { + rb->viewport_set_defaults(&parentvp[l], l); + rb->viewport_set_fullscreen(&parentvp[l], l); + } + + #define MENUITEM_STRINGLIST_CUSTOM(name, str, callback, ... ) \ + static const char *name##_[] = {__VA_ARGS__}; \ + static const struct menu_callback_with_desc name##__ = \ + {callback,str, Icon_NOICON}; \ + struct menu_item_ex name = \ + {MT_RETURN_ID|MENU_HAS_DESC| \ + MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ + { .strings = name##_},{.callback_and_desc = & name##__}}; + + MENUITEM_STRINGLIST_CUSTOM(settings_menu, ID2P(LANG_SETTINGS), NULL, + ID2P(LANG_RESUME_PLAYBACK), + "Save Threshold", + "Minimum Elapsed", + "Verbose", + "Beep Level", + "Unique Track MRU", + ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS), + ID2P(VOICE_BLANK), + ID2P(LANG_CANCEL_0), + ID2P(LANG_SAVE_EXIT)); + + #undef MENUITEM_STRINGLIST_CUSTOM + + const int items = MENU_GET_COUNT(settings_menu.flags); + const unsigned int flags = settings_menu.flags & (~MENU_ITEM_COUNT(MENU_COUNT_MASK)); + if (crc == 0) + { + crc = rb->crc_32(&gConfig, sizeof(struct scrobbler_cfg), 0xFFFFFFFF); + } + + do { + if (crc == rb->crc_32(&gConfig, sizeof(struct scrobbler_cfg), 0xFFFFFFFF)) + { + /* hide save item -- there are no changes to save */ + settings_menu.flags = flags|MENU_ITEM_COUNT((items - 1)); + } + else + { + settings_menu.flags = flags|MENU_ITEM_COUNT(items); + } + selection=rb->do_menu(&settings_menu,&selection, parentvp, true); + switch(selection) { + + case 0: /* resume playback on plugin start */ + rb->set_bool(rb->str(LANG_RESUME_PLAYBACK), &gConfig.playback); + break; + case 1: /* % of track played to indicate listened status */ + rb->set_int("Save Threshold", "%", UNIT_PERCENT, + &gConfig.savepct, NULL, 10, 0, 100, NULL ); + break; + case 2: /* tracks played less than this will not be logged */ + rb->set_int("Minimum Elapsed", "ms", UNIT_MS, + &gConfig.minms, NULL, 100, 0, 10000, NULL ); + break; + case 3: /* suppress non-error messages */ + rb->set_bool("Verbose", &gConfig.verbose); + break; + case 4: /* set volume of start-up beep */ + rb->set_int("Beep Level", "", UNIT_INT, + &gConfig.beeplvl, NULL, 1, 0, 10, NULL); + play_tone(1500, 100); + break; + case 5: /* keep a list of tracks to prevent repeat [Skipped] entries */ + rb->set_int("Unique Track MRU Size", "", UNIT_INT, + &gConfig.uniqct, NULL, 1, 0, SCROBBLER_MAX_TRACK_MRU, NULL); + break; + case 6: /* set defaults */ + { + const struct text_message prompt = { + (const char*[]){ ID2P(LANG_AUDIOSCROBBLER), + ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS)}, 2}; + if(rb->gui_syncyesno_run(&prompt, NULL, NULL) == YESNO_YES) + { + config_set_defaults(); + if (gConfig.verbose) + rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS)); + } + break; + } + case 7: /*sep*/ + continue; + case 8: /* Cancel */ + return -1; + break; + case 9: /* Save & exit */ + { + int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + if (res >= 0) + { + crc = rb->crc_32(&gConfig, sizeof(struct scrobbler_cfg), 0xFFFFFFFF); + logf("SCROBBLER: cfg saved %s %d bytes", CFG_FILE, gCfg_sz); + return PLUGIN_OK; + } + logf("SCROBBLER: cfg FAILED (%d) %s", res, CFG_FILE); + return PLUGIN_ERROR; + } + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return PLUGIN_OK; + } + } while ( selection >= 0 ); + return 0; +} + +/****************** helper fuctions ******************/ +void play_tone(unsigned int frequency, unsigned int duration) +{ + if (gConfig.beeplvl > 0) + rb->beep_play(frequency, duration, 100 * gConfig.beeplvl); +} + +int scrobbler_init_cache(void) +{ + memset(&gCache, 0, sizeof(struct scrobbler_cache)); + gCache.buf = rb->plugin_get_buffer(&gCache.size); + + /* we need to reserve the space we want for our use in TSR plugins since + * someone else could call plugin_get_buffer() and corrupt our memory */ + size_t reqsz = SCROBBLER_MAX_CACHE; + gCache.size = PLUGIN_BUFFER_SIZE - rb->plugin_reserve_buffer(reqsz); + + if (gCache.size < reqsz) + { + logf("SCROBBLER: OOM , %ld < req:%ld", gCache.size, reqsz); + return -1; + } + gCache.force_flush = true; + rb->mutex_init(&gCache.mtx); + logf("SCROBBLER: Initialized"); + return 1; +} + +static inline size_t cache_get_entry_size(int str_len) +{ + /* entry_sz consists of the cache entry + str_len + \0NULL terminator */ + return ALIGN_UP(str_len + 1 + sizeof(struct cache_entry), alignof(struct cache_entry)); +} + +static inline const char* str_chk_valid(const char *s, const char *alt) +{ + return (s != NULL ? s : alt); +} + +static bool track_is_unique(uint32_t hash1, uint32_t hash2) +{ + bool is_unique = false; + static uint8_t mru_len = 0; + + struct hash64 { uint32_t hash1; uint32_t hash2; }; + + static struct hash64 hash_mru[SCROBBLER_MAX_TRACK_MRU]; + struct hash64 i = {0}; + struct hash64 itmp; + uint8_t mru; + + if (mru_len > gConfig.uniqct) + mru_len = gConfig.uniqct; + + if (gConfig.uniqct < 1) + return true; + + /* Search in MRU */ + for (mru = 0; mru < mru_len; mru++) + { + /* Items shifted >> 1 */ + itmp = i; + i = hash_mru[mru]; + hash_mru[mru] = itmp; + + /* Found in MRU */ + if ((i.hash1 == hash1) && (i.hash2 == hash2)) + { + logf("SCROBBLER: hash [%x, %x] found in MRU @ %d", i.hash1, i.hash2, mru); + goto Found; + } + } + + /* Add MRU entry */ + is_unique = true; + if (mru_len < SCROBBLER_MAX_TRACK_MRU && mru_len < gConfig.uniqct) + { + hash_mru[mru_len] = i; + mru_len++; + } + else + { + logf("SCROBBLER: hash [%x, %x] evicted from MRU", i.hash1, i.hash2); + } + + i = (struct hash64){.hash1 = hash1, .hash2 = hash2}; + logf("SCROBBLER: hash [%x, %x] added to MRU[%d]", i.hash1, i.hash2, mru_len); + +Found: + + /* Promote MRU item to top of MRU */ + hash_mru[0] = i; + + return is_unique; +} + +static void get_scrobbler_filename(char *path, size_t size) +{ + int used; + + used = rb->snprintf(path, size, "/%s", BASE_FILENAME); + + if (used >= (int)size) + { + logf("%s: not enough buffer space for log filename", __func__); + rb->memset(path, 0, size); + } +} + +static void scrobbler_write_cache(void) +{ + int i; + int fd; + logf("%s", __func__); + char scrobbler_file[MAX_PATH]; + + rb->mutex_lock(&gCache.mtx); + + get_scrobbler_filename(scrobbler_file, sizeof(scrobbler_file)); + + /* If the file doesn't exist, create it. + Check at each write since file may be deleted at any time */ + if(!rb->file_exists(scrobbler_file)) + { + fd = rb->open(scrobbler_file, O_RDWR | O_CREAT, 0666); + if(fd >= 0) + { + /* write file header */ + rb->fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n" + "#TZ/UNKNOWN\n" "#CLIENT/Rockbox " + TARGET_NAME SCROBBLER_REVISION + HDR_STR_TIMELESS "\n"); + rb->fdprintf(fd, ITEM_HDR); + + rb->close(fd); + } + else + { + logf("SCROBBLER: cannot create log file (%s)", scrobbler_file); + } + } + + int entries = gCache.entries; + size_t used = gCache.pos; + size_t pos = 0; + /* clear even if unsuccessful - we don't want to overflow the buffer */ + gCache.pos = 0; + gCache.entries = 0; + + /* write the cache entries */ + fd = rb->open(scrobbler_file, O_WRONLY | O_APPEND); + if(fd >= 0) + { + logf("SCROBBLER: writing %d entries", entries); + /* copy cached data to storage */ + uint32_t prev_crc = 0x0; + uint32_t crc; + size_t entry_sz, len; + bool err = false; + + for (i = 0; i < entries && pos < used; i++) + { + logf("SCROBBLER: write %d read pos [%ld]", i, pos); + + struct cache_entry *entry = (struct cache_entry*)&gCache.buf[pos]; + + entry_sz = cache_get_entry_size(entry->len); + crc = rb->crc_32(entry->buf, entry->len, 0xFFFFFFFF) ^ prev_crc; + prev_crc = crc; + + len = rb->strlen(entry->buf); + logf("SCROBBLER: write entry %d sz [%ld] len [%ld]", i, entry_sz, len); + + if (len != entry->len || crc != entry->crc) /* the entry is corrupted */ + { + rb->write(fd, SCROBBLER_BAD_ENTRY, sizeof(SCROBBLER_BAD_ENTRY)-1); + logf("SCROBBLER: Bad entry %d", i); + if(!err) + { + rb->queue_post(&gThread.queue, EV_USER_ERROR, ERR_WRITING_DATA); + err = true; + } + } + + logf("SCROBBLER: writing %s", entry->buf); + + if (rb->write(fd, entry->buf, len) != (ssize_t)len) + break; + + if (entry->buf[len - 1] != '\n') + rb->write(fd, "\n", 1); /* ensure newline termination */ + + pos += entry_sz; + } + rb->close(fd); + } + else + { + logf("SCROBBLER: error writing file"); + rb->queue_post(&gThread.queue, EV_USER_ERROR, ERR_WRITING_FILE); + } + rb->mutex_unlock(&gCache.mtx); +} + +#if USING_STORAGE_CALLBACK +static void scrobbler_flush_callback(void) +{ + if(gCache.pos == 0) + return; +#if (CONFIG_STORAGE & STORAGE_ATA) + else +#else + if ((gCache.pos >= SCROBBLER_MAX_CACHE / 2) || gCache.force_flush == true) +#endif + { + gCache.force_flush = false; + logf("%s", __func__); + scrobbler_write_cache(); + } +} +#endif + +static unsigned long scrobbler_get_threshold(unsigned long length) +{ + /* length is assumed to be in miliseconds */ + return length / 100 * gConfig.savepct; +} + +static int create_log_entry(const struct mp3entry *id, + struct cache_entry *entry, int *trk_info_len) +{ + #define SEP "\t" + #define EOL "\n" + char* artist = id->artist ? id->artist : id->albumartist; + char rating = 'S'; /* Skipped */ + if (id->elapsed >= scrobbler_get_threshold(id->length)) + rating = 'L'; /* Listened */ + + char tracknum[11] = { "" }; + + if (id->tracknum > 0) + rb->snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum); + + int ret = rb->snprintf(entry->buf, + SCROBBLER_CACHE_LEN, + "%s"SEP"%s"SEP"%s"SEP"%s"SEP"%d%n"SEP"%c"SEP"%ld"SEP"%s"EOL"", + str_chk_valid(artist, UNTAGGED), + str_chk_valid(id->album, ""), + str_chk_valid(id->title, id->path), + tracknum, + (int)(id->length / 1000), + trk_info_len, /* receives len of the string written so far */ + rating, + get_timestamp(), + str_chk_valid(id->mb_track_id, "")); + + #undef SEP + #undef EOL + return ret; +} + +static void scrobbler_add_to_cache(const struct mp3entry *id) +{ + logf("%s", __func__); + int trk_info_len = 0; + + if (id->elapsed < (unsigned long) gConfig.minms) + { + logf("SCROBBLER: skipping entry < %d ms: %s", gConfig.minms, id->path); + return; + } + + rb->mutex_lock(&gCache.mtx); + + /* not enough room left to guarantee next entry will fit so flush the cache */ + if ( gCache.pos > SCROBBLER_MAX_CACHE - SCROBBLER_CACHE_LEN ) + scrobbler_write_cache(); + + logf("SCROBBLER: add_to_cache[%d] write pos[%ld]", gCache.entries, gCache.pos); + /* use prev_crc to allow whole buffer to be checked for consistency */ + static uint32_t prev_crc = 0x0; + if (gCache.pos == 0) + prev_crc = 0x0; + + void *buf = &gCache.buf[gCache.pos]; + memset(buf, 0, SCROBBLER_CACHE_LEN); + + struct cache_entry *entry = buf; + + int ret = create_log_entry(id, entry, &trk_info_len); + + if (ret <= 0 || (size_t) ret >= SCROBBLER_CACHE_LEN) + { + logf("SCROBBLER: entry too long:"); + logf("SCROBBLER: %s", id->path); + rb->queue_post(&gThread.queue, EV_USER_ERROR, ERR_ENTRY_LENGTH); + } + else if (ret > 0) + { + /* first generate a crc over the static portion of the track info data + this and a crc of the filename will be used to detect repeat entries + */ + static uint32_t last_crc = 0; + uint32_t crc_entry = rb->crc_32(entry->buf, trk_info_len, 0xFFFFFFFF); + uint32_t crc_path = rb->crc_32(id->path, rb->strlen(id->path), 0xFFFFFFFF); + bool is_unique = track_is_unique(crc_entry, crc_path); + bool is_listened = (id->elapsed >= scrobbler_get_threshold(id->length)); + + if (is_unique || is_listened) + { + /* finish calculating the CRC of the whole entry */ + const void *src = entry->buf + trk_info_len; + entry->crc = rb->crc_32(src, ret - trk_info_len, crc_entry) ^ prev_crc; + prev_crc = entry->crc; + entry->len = ret; + + /* since Listened entries are written regardless + make sure this isn't a direct repeat */ + if ((entry->crc ^ crc_path) != last_crc) + { + + if (is_listened) + last_crc = (entry->crc ^ crc_path); + else + last_crc = 0; + + size_t entry_sz = cache_get_entry_size(ret); + + logf("SCROBBLER: Added (#%d) sz[%ld] len[%d], %s", + gCache.entries, entry_sz, ret, entry->buf); + + gCache.entries++; + /* increase pos by string len + null terminator + sizeof entry */ + gCache.pos += entry_sz; + +#if USING_STORAGE_CALLBACK + rb->register_storage_idle_func(scrobbler_flush_callback); +#endif + } + } + else + logf("SCROBBLER: skipping repeat entry: %s", id->path); + } + rb->mutex_unlock(&gCache.mtx); +} + +static void scrobbler_flush_cache(void) +{ + logf("%s", __func__); + /* Add any pending entries to the cache */ + if (gCache.pending) + { + logf("SCROBBLER: pending entry"); + gCache.pending = false; + if (rb->audio_status()) + scrobbler_add_to_cache(rb->audio_current_track()); + } + + /* Write the cache to disk if needed */ + if (gCache.pos > 0) + { + scrobbler_write_cache(); + } +} + +static void track_change_event(unsigned short id, void *ev_data) +{ + (void)id; + logf("%s", __func__); + struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; + + /* check if track was resumed > %threshold played ( likely got saved ) */ + if ((id3->elapsed > scrobbler_get_threshold(id3->length))) + { + gCache.pending = false; + logf("SCROBBLER: skipping file %s", id3->path); + } + else + { + logf("SCROBBLER: add pending %s",id3->path); + record_timestamp(); + gCache.pending = true; + } +} + +#ifdef ROCKBOX_HAS_LOGF +static const char* track_event_info(struct track_event* te) +{ + + static const char *strflags[] = {"TEF_NONE", "TEF_CURRENT", + "TEF_AUTOSKIP", "TEF_CUR|ASKIP", + "TEF_REWIND", "TEF_CUR|REW", + "TEF_ASKIP|REW", "TEF_CUR|ASKIP|REW"}; +/*TEF_NONE = 0x0, no flags are set +* TEF_CURRENT = 0x1, event is for the current track +* TEF_AUTO_SKIP = 0x2, event is sent in context of auto skip +* TEF_REWIND = 0x4, interpret as rewind, id3->elapsed is the + position before the seek back to 0 +*/ + logf("SCROBBLER: flag %d", te->flags); + return strflags[te->flags&0x7]; +} +#endif + +static void track_finish_event(unsigned short id, void *ev_data) +{ + (void)id; + struct track_event *te = ((struct track_event *)ev_data); + logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te)); + /* add entry using the currently ending track */ + if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND)) + { + gCache.pending = false; + + scrobbler_add_to_cache(te->id3); + } +} + +/****************** main thread + helpers ******************/ +static void events_unregister(void) +{ + /* we don't want any more events */ + rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, track_change_event); + rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, track_finish_event); +} + +static void events_register(void) +{ + rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, track_change_event); + rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, track_finish_event); +} + +void thread(void) +{ + bool in_usb = false; + + struct queue_event ev; + while (!gThread.exiting) + { + rb->queue_wait(&gThread.queue, &ev); + + switch (ev.id) + { + case SYS_USB_CONNECTED: + scrobbler_flush_cache(); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + in_usb = true; + break; + case SYS_USB_DISCONNECTED: + in_usb = false; + /*fall through*/ + case EV_STARTUP: + logf("SCROBBLER: Thread Started"); + events_register(); + play_tone(1500, 100); + break; + case SYS_POWEROFF: + logf("SYS_POWEROFF"); + /*fall through*/ + case SYS_REBOOT: + gCache.force_flush = true; + /*fall through*/ + case EV_EXIT: +#if USING_STORAGE_CALLBACK + rb->unregister_storage_idle_func(scrobbler_flush_callback, false); +#endif + if (!in_usb) + scrobbler_flush_cache(); + + events_unregister(); + return; + case EV_FLUSHCACHE: + scrobbler_flush_cache(); + rb->queue_reply(&gThread.queue, 0); + break; + case EV_USER_ERROR: + if (!in_usb) + { + if (ev.data == ERR_WRITING_FILE) + rb->splash(HZ, "SCROBBLER: error writing log"); + else if (ev.data == ERR_ENTRY_LENGTH) + rb->splash(HZ, "SCROBBLER: error entry too long"); + else if (ev.data == ERR_WRITING_DATA) + rb->splash(HZ, "SCROBBLER: error bad entry data"); + } + break; + default: + logf("default %ld", ev.id); + break; + } + } +} + +void thread_create(void) +{ + /* put the thread's queue in the broadcast list */ + rb->queue_init(&gThread.queue, true); + gThread.id = rb->create_thread(thread, gThread.stack, sizeof(gThread.stack), + 0, "Last.Fm_TSR" + IF_PRIO(, PRIORITY_BACKGROUND) + IF_COP(, CPU)); + rb->queue_enable_queue_send(&gThread.queue, &gThread.queue_send, gThread.id); + rb->queue_post(&gThread.queue, EV_STARTUP, 0); + rb->yield(); +} + +void thread_quit(void) +{ + if (!gThread.exiting) { + gThread.exiting = true; + rb->queue_post(&gThread.queue, EV_EXIT, 0); + rb->thread_wait(gThread.id); + /* remove the thread's queue from the broadcast list */ + rb->queue_delete(&gThread.queue); + } +} + +/* callback to end the TSR plugin, called before a new plugin gets loaded */ +static int plugin_exit_tsr(bool reenter) +{ + MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, ID2P(LANG_SETTINGS), + "Flush Cache", "Exit Plugin", ID2P(LANG_BACK)); + + const struct text_message quit_prompt = { + (const char*[]){ ID2P(LANG_AUDIOSCROBBLER), + "is currently running.", + "Quit scrobbler?" }, 3 + }; + + if (gThread.hide_reentry && + (rb->audio_status() & (AUDIO_STATUS_PLAY | AUDIO_STATUS_PAUSE)) == 0) + { + gThread.hide_reentry = false; + return PLUGIN_TSR_CONTINUE; + } + + while(true) + { + int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 2; + switch(result) + { + case 0: /* settings */ + config_settings_menu(); + break; + case 1: /* flush cache */ + if (gCache.entries > 0) + { + rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0); + if (gConfig.verbose) + rb->splashf(2*HZ, "%s Cache Flushed", rb->str(LANG_AUDIOSCROBBLER)); + } + break; + + case 2: /* exit plugin - quit */ + if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES) + { + scrobbler_flush_cache(); + thread_quit(); + return (reenter ? PLUGIN_TSR_TERMINATE : PLUGIN_TSR_SUSPEND); + } + /* Fall Through */ + case 3: /* back to menu */ + return PLUGIN_TSR_CONTINUE; + } + } +} + +/****************** main ******************/ +static int plugin_main(const void* parameter) +{ + struct scrobbler_cfg cfg; + rb->memcpy(&cfg, &gConfig, sizeof(struct scrobbler_cfg)); /* store settings */ + + /* Resume plugin ? -- silences startup */ + if (parameter == rb->plugin_tsr) + { + gConfig.beeplvl = 0; + gConfig.playback = false; + gConfig.verbose = false; + } + + rb->memset(&gThread, 0, sizeof(gThread)); + if (gConfig.verbose) + rb->splashf(HZ / 2, "%s Started",rb->str(LANG_AUDIOSCROBBLER)); + logf("%s: %s Started", __func__, rb->str(LANG_AUDIOSCROBBLER)); + + rb->plugin_tsr(plugin_exit_tsr); /* stay resident */ + + thread_create(); + rb->memcpy(&gConfig, &cfg, sizeof(struct scrobbler_cfg)); /*restore settings */ + + if (gConfig.playback) + { + gThread.hide_reentry = true; + return PLUGIN_GOTO_WPS; + } + return PLUGIN_OK; +} + +/***************** Plugin Entry Point *****************/ + +enum plugin_status plugin_start(const void* parameter) +{ + /* now go ahead and have fun! */ + if (rb->usb_inserted() == true) + return PLUGIN_USB_CONNECTED; + + if (scrobbler_init_cache() < 0) + return PLUGIN_ERROR; + + config_set_defaults(); + + if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) + { + /* If the loading failed, save a new config file */ + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + if (gConfig.verbose) + rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS)); + } + + int ret = plugin_main(parameter); + return ret; +} diff --git a/apps/plugins/lastfm_scrobbler_viewer.c b/apps/plugins/lastfm_scrobbler_viewer.c new file mode 100644 index 0000000000..c35ba64918 --- /dev/null +++ b/apps/plugins/lastfm_scrobbler_viewer.c @@ -0,0 +1,1033 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ / + * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) ( + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 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" +#include "lang_enum.h" + +#include "lib/printcell_helper.h" + +#include "lib/configfile.h" +#define CFG_FILE "/lastfm_scrobbler_viewer.cfg" +#define TMP_FILE ""PLUGIN_DATA_DIR "/lscrobbler_viewer_%d.tmp" +#define CFG_VER 1 + +#ifdef ROCKBOX_HAS_LOGF +#define logf rb->logf +#else +#define logf(...) do { } while(0) +#endif + +/*#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID*/ + +#define SCROBBLE_HDR_FMT "$*%d$%s$*%d$%s$*%d$%s$Track#$Length$Rating$TimeStamp$TrackId" +//#define SCROBBLE_HDR "$*128$Artist$*128$Album$*128$Title$Track#$Length$Rating$TimeStamp$TrackId" + +#define SCROBBLER_MIN_COLUMNS (6) /* a valid scrobbler file should have at least this many columns */ + +#define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1) +#define CANCEL_CONTEXT_MENU (PLUGIN_OK + 2) +#define QUIT_CONTEXT_MENU (PLUGIN_OK + 3) +/* global lists, for everything */ +static struct gui_synclist lists; + +/* printcell data for the current file */ +struct printcell_data_t { + int view_columns; + int view_lastcol; + + int items_buffered; + int items_total; + int fd_cur; + char *filename; + + char *buf; + size_t buf_size; + off_t buf_used; + char header[PRINTCELL_MAXLINELEN]; + +}; + +enum e_find_type { + FIND_ALL = 0, + FIND_EXCLUDE, + FIND_EXCLUDE_CASE, + FIND_EXCLUDE_ANY, + FIND_INCLUDE, + FIND_INCLUDE_CASE, + FIND_INCLUDE_ANY, + FIND_CUSTOM, +}; + +static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data); +static void pc_data_set_header(struct printcell_data_t *pc_data); + +static void browse_file(char *buf, size_t bufsz) +{ + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY, + .title = "Select a scrobbler log file", + .icon = Icon_Playlist, + .buf = buf, + .bufsize = bufsz, + .root = "/", + }; + + if (rb->rockbox_browse(&browse) != GO_TO_PREVIOUS) + { + buf[0] = '\0'; + } +} + +static struct plugin_config +{ + bool separator; + bool talk; + int col_width; + int hidecol_flags; +} gConfig; + +static struct configdata config[] = +{ + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.separator }, "Cell Separator", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.talk }, "Voice", NULL}, + {TYPE_INT, 32, LCD_WIDTH, { .int_p = &gConfig.col_width }, "Cell Width", NULL}, + {TYPE_INT, INT_MIN, INT_MAX, { .int_p = &gConfig.hidecol_flags }, "Hidden Columns", NULL}, +}; +const int gCfg_sz = sizeof(config)/sizeof(*config); +/****************** config functions *****************/ +static void config_set_defaults(void) +{ + gConfig.col_width = MIN(LCD_WIDTH, 128); + gConfig.hidecol_flags = 0; + gConfig.separator = true; + gConfig.talk = rb->global_settings->talk_menu; +} + +static void config_save(void) +{ + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); +} + +static int config_settings_menu(struct printcell_data_t *pc_data) +{ + int selection = 0; + + struct viewport parentvp[NB_SCREENS]; + FOR_NB_SCREENS(l) + { + rb->viewport_set_defaults(&parentvp[l], l); + rb->viewport_set_fullscreen(&parentvp[l], l); + } + + MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL, + ID2P(LANG_LIST_SEPARATOR), + "Cell Width", + ID2P(LANG_VOICE), + ID2P(VOICE_BLANK), + ID2P(VOICE_BLANK), + ID2P(LANG_CANCEL_0), + ID2P(LANG_SAVE_EXIT)); + + do { + selection=rb->do_menu(&settings_menu,&selection, parentvp, true); + switch(selection) { + + case 0: + rb->set_bool(rb->str(LANG_LIST_SEPARATOR), &gConfig.separator); + break; + case 1: + rb->set_int("Cell Width", "", UNIT_INT, + &gConfig.col_width, NULL, 1, 32, LCD_WIDTH, NULL ); + break; + case 2: + rb->set_bool(rb->str(LANG_VOICE), &gConfig.talk); + break; + case 3: + continue; + case 4: /*sep*/ + continue; + case 5: + return -1; + break; + case 6: + { + pc_data_set_header(pc_data); + synclist_set(0, pc_data->items_total, 1, pc_data); + int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + if (res >= 0) + { + logf("Cfg saved %s %d bytes", CFG_FILE, gCfg_sz); + return PLUGIN_OK; + } + logf("Cfg FAILED (%d) %s", res, CFG_FILE); + return PLUGIN_ERROR; + } + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return PLUGIN_OK; + } + } while ( selection >= 0 ); + return 0; +} + +/* open file pass back file descriptor and return file size */ +static size_t file_open(const char *filename, int *fd) +{ + size_t fsize = 0; + + if (filename && fd) + { + *fd = rb->open(filename, O_RDONLY); + if (*fd >= 0) + { + fsize = rb->filesize(*fd); + } + } + return fsize; +} + +static const char* list_get_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + const int slush_pos = 32; /* entries around the current entry to keep buffered */ + /* keep track of the last position in the buffer */ + static int buf_line_num = 0; + static off_t buf_last_pos = 0; + /* keep track of the last position in the file */ + static int file_line_num = 0; + static off_t file_last_seek = 0; + + if (selected_item < 0) + { + logf("[%s] Reset positions", __func__); + buf_line_num = 0; + buf_last_pos = 0; + file_line_num = 0; + file_last_seek = 0; + return ""; + } + + int line_num; + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; + bool found = false; + + if (pc_data->buf && selected_item < pc_data->items_buffered) + { + int buf_pos; + + if (buf_line_num > selected_item || buf_last_pos >= pc_data->buf_used + || buf_line_num < 0) + { + logf("[%s] Set pos buffer 0", __func__); + buf_line_num = 0; + buf_last_pos = 0; + } + buf_pos = buf_last_pos; + line_num = buf_line_num; + + while (buf_pos < pc_data->buf_used + && line_num < pc_data->items_buffered) + { + size_t len = rb->strlen(&pc_data->buf[buf_pos]); + if(line_num == selected_item) + { + rb->strlcpy(buf, &pc_data->buf[buf_pos], MIN(buf_len, len)); + logf("(%d) in buffer: %s", line_num, buf); + found = true; + break; + } + else + { + buf_pos += len + 1; /* need to go past the NULL terminator */ + line_num++; + + if (buf_line_num + slush_pos < selected_item) + { + logf("[%s] Set pos buffer %d", __func__, line_num); + buf_line_num = line_num; + buf_last_pos = buf_pos; + } + } + } + } + + /* didn't find the item try the file */ + if(!found && pc_data->fd_cur >= 0) + { + int fd = pc_data->fd_cur; + + if (file_line_num < 0 || file_line_num > selected_item) + { + logf("[%s] Set seek file 0", __func__); + file_line_num = 0; + file_last_seek = 0; + } + + rb->lseek(fd, file_last_seek, SEEK_SET); + line_num = file_line_num; + + while ((rb->read_line(fd, buf, buf_len)) > 0) + { + if(buf[0] == '#') + continue; + if(line_num == selected_item) + { + logf("(%d) in file: %s", line_num, buf); + found = true; + break; + } + else + { + line_num++; + + if (file_line_num + slush_pos < selected_item) + { + logf("[%s] Set seek file %d", __func__, line_num); + file_line_num = line_num; + file_last_seek = rb->lseek(fd, 0, SEEK_CUR); + } + } + } + } + + if(!found) + { + logf("(%d) Not Found!", selected_item); + buf_line_num = -1; + file_line_num = -1; + buf[0] = '\0'; + } + return buf; +} + +static int list_voice_cb(int list_index, void* data) +{ + (void) list_index; + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; + if (!gConfig.talk) + return -1; + + int selcol = printcell_get_column_selected(); + char buf[MAX_PATH]; + char* name; + long id; + + if (pc_data->view_lastcol != selcol) + { + name = printcell_get_title_text(selcol, buf, sizeof(buf)); + + id = P2ID((const unsigned char *)name); + + if(id>=0) + rb->talk_id(id, true); + else if (selcol >= 0) + { + switch (selcol) + { + case 0: + rb->talk_id(LANG_ID3_ARTIST, true); + break; + case 1: + rb->talk_id(LANG_ID3_ALBUM, true); + break; + case 2: + rb->talk_id(LANG_ID3_TITLE, true); + break; + case 3: + rb->talk_id(LANG_ID3_TRACKNUM, true); + break; + case 4: + rb->talk_id(LANG_ID3_LENGTH, true); + break; + + default: + rb->talk_spell(name, true); + break; + } + } + else + rb->talk_id(LANG_ALL, true); + + rb->talk_id(VOICE_PAUSE, true); + } + + name = printcell_get_column_text(selcol, buf, sizeof(buf)); + + id = P2ID((const unsigned char *)name); + + if(id>=0) + rb->talk_id(id, true); + else if (selcol >= 0) + rb->talk_spell(name, true); + + return 0; +} + +static enum themable_icons list_icon_cb(int selected_item, void *data) +{ + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; + if (lists.selected_item == selected_item) + { + if (pc_data->view_lastcol < 0) + return Icon_Config; + return Icon_Audio; + } + return Icon_NOICON; +} + +/* load file entries into pc_data buffer, file should already be opened + * and will be closed if the whole file was buffered */ +static int file_load_entries(struct printcell_data_t *pc_data) +{ + int items = 0; + int count = 0; + int buffered = 0; + unsigned int pos = 0; + bool comment = false; + char ch; + int fd = pc_data->fd_cur; + if (fd < 0) + return 0; + + rb->lseek(fd, 0, SEEK_SET); + + while (rb->read(fd, &ch, 1) > 0) + { + if (count++ == 0 && ch == '#') /* skip comments */ + comment = true; + else if (!comment && ch != '\r' && pc_data->buf_size > pos) + pc_data->buf[pos++] = ch; + + if (items == 0 && pos > PRINTCELL_MAXLINELEN * 2) + break; + + if (ch == '\n') + { + if (!comment) + { + pc_data->buf[pos] = '\0'; + if (pc_data->buf_size > pos) + { + pos++; + buffered++; + } + items++; + } + comment = false; + count = 0; + rb->yield(); + } + } + + logf("[%s] items: %d buffered: %d", __func__, items, buffered); + + pc_data->items_total = items; + pc_data->items_buffered = buffered; + pc_data->buf[pos] = '\0'; + pc_data->buf_used = pos; + + if (items == buffered) /* whole file fit into buffer; close file */ + { + rb->close(pc_data->fd_cur); + pc_data->fd_cur = -1; + } + + list_get_name_cb(-1, NULL, NULL, 0); /* prime name cb */ + return items; +} + +static int filter_items(struct printcell_data_t *pc_data, + enum e_find_type find_type, int col) +{ + /* saves filtered items to a temp file and loads it */ + int fd; + bool reload = false; + char buf[PRINTCELL_MAXLINELEN]; + char find_exclude_buf[PRINTCELL_MAXLINELEN]; + int selcol = printcell_get_column_selected(); + char *find_exclude = printcell_get_column_text(selcol, find_exclude_buf, + sizeof(find_exclude_buf)); + const char colsep = '\t'; + int find_len = rb->strlen(find_exclude); + + if (find_type == FIND_CUSTOM || find_len == 0) + { + int option = 0; + struct opt_items find_types[] = { + {"Exclude", -1}, {"Exclude Case Sensitive", -1}, + {"Exclude Any", -1}, {"Include", -1}, + {"Include Case Sensitive", -1}, {"Include Any", -1} + }; + if (rb->set_option("Find Type", &option, RB_INT, + find_types, 6, NULL)) + { + return 0; + } + switch (option) + { + case 0: + find_type = FIND_EXCLUDE; + break; + case 1: + find_type = FIND_EXCLUDE_CASE; + break; + case 2: + find_type = FIND_EXCLUDE_ANY; + break; + case 3: + find_type = FIND_INCLUDE; + break; + case 4: + find_type = FIND_INCLUDE_CASE; + break; + case 5: + find_type = FIND_INCLUDE_ANY; + break; + default: + find_type = FIND_ALL; + break; + } + + /* copy data to beginning of buf */ + rb->memmove(find_exclude_buf, find_exclude, find_len); + find_exclude_buf[find_len] = '\0'; + + if (rb->kbd_input(find_exclude_buf, sizeof(find_exclude_buf), NULL) < 0) + return -1; + find_exclude = find_exclude_buf; + find_len = rb->strlen(find_exclude); + } + + char tmp_filename[MAX_PATH]; + static int tmp_num = 0; + rb->snprintf(tmp_filename, sizeof(tmp_filename), TMP_FILE, tmp_num); + tmp_num++; + + fd = rb->open(tmp_filename, O_RDWR | O_CREAT | O_TRUNC, 0666); + if(fd >= 0) + { + rb->splash_progress_set_delay(HZ * 5); + for (int i = 0; i < pc_data->items_total; i++) + { + rb->splash_progress(i, pc_data->items_total, "Filtering..."); + const char * data = list_get_name_cb(i, pc_data, buf, sizeof(buf)); + + if (find_type != FIND_ALL) + { + int index = col; + + if (index < 0) + index = 0; + if (find_len > 0) + { + char *bcol = buf; + while (*bcol != '\0' && index > 0) + { + if (*bcol == colsep) + index--; + bcol++; + } + if (index > 0) + continue; + + if (find_type == FIND_EXCLUDE) + { + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) + { + logf("[%s] exclude [%s]", find_exclude, bcol); + continue; + } + } + else if (find_type == FIND_INCLUDE) + { + if (rb->strncasecmp(bcol, find_exclude, find_len) != 0) + { + logf("%s include %s", find_exclude, bcol); + continue; + } + } + else if (find_type == FIND_EXCLUDE_CASE) + { + if (rb->strncmp(bcol, find_exclude, find_len) == 0) + { + logf("[%s] exclude case [%s]", find_exclude, bcol); + continue; + } + } + else if (find_type == FIND_INCLUDE_CASE) + { + if (rb->strncmp(bcol, find_exclude, find_len) != 0) + { + logf("%s include case %s", find_exclude, bcol); + continue; + } + } + else if (find_type == FIND_EXCLUDE_ANY) + { + bool found = false; + while (*bcol != '\0' && *bcol != colsep) + { + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) + { + logf("[%s] exclude any [%s]", find_exclude, bcol); + found = true; + break; + } + bcol++; + } + if (found) + continue; + } + else if (find_type == FIND_INCLUDE_ANY) + { + bool found = false; + while (*bcol != '\0' && *bcol != colsep) + { + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) + { + found = true; + logf("[%s] include any [%s]", find_exclude, bcol); + break; + } + bcol++; + } + if (!found) + continue; + } + } + } + int len = rb->strlen(data); + if (len > 0) + { + logf("writing [%d bytes][%s]", len + 1, data); + rb->write(fd, data, len); + rb->write(fd, "\n", 1); + } + } + reload = true; + } + + if (reload) + { + if (pc_data->fd_cur >= 0) + rb->close(pc_data->fd_cur); + + pc_data->fd_cur = fd; + int items = file_load_entries(pc_data); + if (items >= 0) + { + lists.selected_item = 0; + rb->gui_synclist_set_nb_items(&lists, items); + } + } + logf("Col text '%s'", find_exclude); + logf("text '%s'", find_exclude_buf); + + return pc_data->items_total; +} + +static int scrobbler_context_menu(struct printcell_data_t *pc_data) +{ + struct viewport parentvp[NB_SCREENS]; + FOR_NB_SCREENS(l) + { + rb->viewport_set_defaults(&parentvp[l], l); + rb->viewport_set_fullscreen(&parentvp[l], l); + } + + int col = printcell_get_column_selected(); + int selection = 0; + int items = 0; + uint32_t visible = printcell_get_column_visibility(-1); + bool hide_col = PRINTCELL_COLUMN_IS_VISIBLE(visible, col); + + char namebuf[PRINTCELL_MAXLINELEN]; + enum e_find_type find_type = FIND_ALL; + + char *colname = pc_data->filename; + if (col >= 0) + colname = printcell_get_title_text(col, namebuf, sizeof(namebuf)); + +#define MENUITEM_STRINGLIST_CUSTOM(name, str, callback, ... ) \ + const char *name##_[] = {__VA_ARGS__}; \ + const struct menu_callback_with_desc name##__ = \ + {callback,str, Icon_NOICON}; \ + const struct menu_item_ex name = \ + {MT_RETURN_ID|MENU_HAS_DESC| \ + MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ + { .strings = name##_},{.callback_and_desc = & name##__}}; + + const char *menu_item[4]; + + menu_item[0]= hide_col ? "Hide":"Show"; + menu_item[1]= "Exclude"; + menu_item[2]= "Include"; + menu_item[3]= "Custom Filter"; + + if (col == -1) + { + menu_item[0]= hide_col ? "Hide All":"Show All"; + menu_item[1]= "Open"; + menu_item[2]= "Reload"; + menu_item[3]= ID2P(LANG_SETTINGS); + } + + MENUITEM_STRINGLIST_CUSTOM(context_menu, colname, NULL, + menu_item[0], + menu_item[1], + menu_item[2], + menu_item[3], + ID2P(VOICE_BLANK), + ID2P(LANG_CANCEL_0), + ID2P(LANG_MENU_QUIT)); + +#undef MENUITEM_STRINGLIST_CUSTOM + do { + selection=rb->do_menu(&context_menu,&selection, parentvp, true); + switch(selection) { + + case 0: + { + printcell_set_column_visible(col, !hide_col); + if (hide_col) + { + do + { + col = printcell_increment_column(1, true); + } while (col >= 0 && printcell_get_column_visibility(col) == 1); + pc_data->view_lastcol = col; + } + gConfig.hidecol_flags = printcell_get_column_visibility(-1); + break; + } + case 1: /* Exclude / Open */ + { + if (col == -1)/*Open*/ + { + char buf[MAX_PATH]; + browse_file(buf, sizeof(buf)); + if (rb->file_exists(buf)) + { + rb->strlcpy(pc_data->filename, buf, MAX_PATH); + if (pc_data->fd_cur >= 0) + rb->close(pc_data->fd_cur); + if (file_open(pc_data->filename, &pc_data->fd_cur) > 0) + items = file_load_entries(pc_data); + if (items >= 0) + synclist_set(0, items, 1, pc_data); + } + else + rb->splash(HZ *2, "Error Opening"); + + return CANCEL_CONTEXT_MENU; + } + + find_type = FIND_EXCLUDE; + } + /* fall-through */ + case 2: /* Include / Reload */ + { + if (col == -1) /*Reload*/ + { + if (pc_data->fd_cur >= 0) + rb->close(pc_data->fd_cur); + if (file_open(pc_data->filename, &pc_data->fd_cur) > 0) + items = file_load_entries(pc_data); + if (items >= 0) + rb->gui_synclist_set_nb_items(&lists, items); + return CANCEL_CONTEXT_MENU; + } + + if (find_type == FIND_ALL) + find_type = FIND_INCLUDE; + /* fall-through */ + } + case 3: /*Custom Filter / Settings */ + { + if (col == -1)/*Settings*/ + return config_settings_menu(pc_data); + + if (find_type == FIND_ALL) + find_type = FIND_CUSTOM; + items = filter_items(pc_data, find_type, col); + + if (items >= 0) + rb->gui_synclist_set_nb_items(&lists, items); + break; + } + case 4: /*sep*/ + continue; + case 5: + return CANCEL_CONTEXT_MENU; + break; + case 6: /* Quit */ + return QUIT_CONTEXT_MENU; + break; + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return PLUGIN_OK; + } + } while ( selection < 0 ); + return 0; +} + +static void cleanup(void *parameter) +{ + struct printcell_data_t *pc_data = (struct printcell_data_t*) parameter; + if (pc_data->fd_cur >= 0) + rb->close(pc_data->fd_cur); + pc_data->fd_cur = -1; +} + +static void menu_action_printcell(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + (void) exit; + struct printcell_data_t *pc_data = (struct printcell_data_t*) lists->data; + if (*action == ACTION_STD_OK) + { + if (selected_item < lists->nb_items) + { + pc_data->view_lastcol = printcell_increment_column(1, true); + *action = ACTION_NONE; + } + } + else if (*action == ACTION_STD_CANCEL) + { + pc_data->view_lastcol = printcell_increment_column(-1, true); + if (pc_data->view_lastcol != pc_data->view_columns - 1) + { + *action = ACTION_NONE; + } + } + else if (*action == ACTION_STD_CONTEXT) + { + int ctxret = scrobbler_context_menu(pc_data); + if (ctxret == QUIT_CONTEXT_MENU) + *exit = true; + } +} + +int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists) +{ + + menu_action_printcell(action, selected_item, exit, lists); + + if (rb->default_event_handler_ex(*action, cleanup, lists->data) == SYS_USB_CONNECTED) + { + *exit = true; + return PLUGIN_USB_CONNECTED; + } + return PLUGIN_OK; +} + +static int count_max_columns(int items, char delimeter, + int expected_cols, struct printcell_data_t *pc_data) +{ + int max_cols = 0; + int cols = 0; + char buf[PRINTCELL_MAXLINELEN]; + for (int i = 0; i < items; i++) + { + const char *txt = list_get_name_cb(i, pc_data, buf, sizeof(buf)); + while (*txt != '\0') + { + if (*txt == delimeter) + { + cols++; + if (cols == expected_cols) + { + max_cols = cols; + break; + } + } + txt++; + } + + if(max_cols < expected_cols && i > 32) + break; + + if (cols > max_cols) + max_cols = cols; + cols = 0; + } + return max_cols; +} + +static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data) +{ + if (items <= 0) + return; + if (selected_item < 0) + selected_item = 0; + + rb->gui_synclist_init(&lists,list_get_name_cb, + pc_data, false, sel_size, NULL); + + rb->gui_synclist_set_icon_callback(&lists, list_icon_cb); + rb->gui_synclist_set_voice_callback(&lists, list_voice_cb); + rb->gui_synclist_set_nb_items(&lists,items); + rb->gui_synclist_select_item(&lists, selected_item); + + struct printcell_settings pcs = {.cell_separator = gConfig.separator, + .title_delimeter = '$', + .text_delimeter = '\t', + .hidecol_flags = gConfig.hidecol_flags}; + + pc_data->view_columns = printcell_set_columns(&lists, &pcs, + pc_data->header, Icon_Rockbox); + printcell_enable(true); + + + int max_cols = count_max_columns(items, pcs.text_delimeter, + SCROBBLER_MIN_COLUMNS, pc_data); + if (max_cols < SCROBBLER_MIN_COLUMNS) /* not a scrobbler file? */ + { + rb->gui_synclist_set_voice_callback(&lists, NULL); + pc_data->view_columns = printcell_set_columns(&lists, NULL, + "$*512$", Icon_Questionmark); + } + + int curcol = printcell_get_column_selected(); + while (curcol >= 0) + curcol = printcell_increment_column(-1, false); + + if (pc_data->view_lastcol >= pc_data->view_columns) + pc_data->view_lastcol = -1; + + /* restore column position */ + while (pc_data->view_lastcol > -1 && curcol != pc_data->view_lastcol) + { + curcol = printcell_increment_column(1, true); + } + pc_data->view_lastcol = curcol; + list_voice_cb(0, pc_data); +} + +static void pc_data_set_header(struct printcell_data_t *pc_data) +{ + int col_w = gConfig.col_width; + rb->snprintf(pc_data->header, PRINTCELL_MAXLINELEN, + SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST), + col_w, rb->str(LANG_ID3_ALBUM), + col_w, rb->str(LANG_ID3_TITLE)); +} + +enum plugin_status plugin_start(const void* parameter) +{ + int ret = PLUGIN_OK; + int selected_item = -1; + int action; + int items = 0; +#if CONFIG_RTC + static char filename[MAX_PATH] = HOME_DIR "/.scrobbler.log"; +#else /* !CONFIG_RTC */ + static char filename[MAX_PATH] = HOME_DIR "/.scrobbler-timeless.log"; +#endif /* CONFIG_RTC */ + bool redraw = true; + bool exit = false; + + static struct printcell_data_t printcell_data; + + if (parameter) + { + rb->strlcpy(filename, (const char*)parameter, MAX_PATH); + filename[MAX_PATH - 1] = '\0'; + } + + if (!rb->file_exists(filename)) + { + browse_file(filename, sizeof(filename)); + if (!rb->file_exists(filename)) + { + rb->splash(HZ, "No Scrobbler file Goodbye."); + return PLUGIN_ERROR; + } + + } + + config_set_defaults(); + if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) + { + /* If the loading failed, save a new config file */ + config_set_defaults(); + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + + rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS)); + } + + rb->memset(&printcell_data, 0, sizeof (struct printcell_data_t)); + printcell_data.fd_cur = -1; + printcell_data.view_lastcol = -1; + + if (rb->file_exists(filename)) + { + if (file_open(filename, &printcell_data.fd_cur) == 0) + printcell_data.fd_cur = -1; + else + { + size_t buf_size; + printcell_data.buf = rb->plugin_get_buffer(&buf_size); + printcell_data.buf_size = buf_size; + printcell_data.buf_used = 0; + printcell_data.filename = filename; + items = file_load_entries(&printcell_data); + } + } + int col_w = gConfig.col_width; + rb->snprintf(printcell_data.header, PRINTCELL_MAXLINELEN, + SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST), + col_w, rb->str(LANG_ID3_ALBUM), + col_w, rb->str(LANG_ID3_TITLE)); + + if (!exit && items > 0) + { + synclist_set(0, items, 1, &printcell_data); + rb->gui_synclist_draw(&lists); + + while (!exit) + { + action = rb->get_action(CONTEXT_LIST, HZ / 10); + + if (action == ACTION_NONE) + { + if (redraw) + { + action = ACTION_REDRAW; + redraw = false; + } + } + else + redraw = true; + + ret = menu_action_cb(&action, selected_item, &exit, &lists); + if (rb->gui_synclist_do_button(&lists, &action)) + continue; + selected_item = rb->gui_synclist_get_sel_pos(&lists); + + } + } + config_save(); + cleanup(&printcell_data); + return ret; +} diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES index bff9017404..09a8b1c5ed 100644 --- a/apps/plugins/lib/SOURCES +++ b/apps/plugins/lib/SOURCES @@ -69,3 +69,9 @@ kbd_helper.c #ifdef HAVE_TOUCHSCREEN pluginlib_touchscreen.c #endif + +id3.c + +#ifdef HAVE_TAGCACHE +mul_id3.c +#endif diff --git a/apps/plugins/lib/action_helper.h b/apps/plugins/lib/action_helper.h index 58d9c6c303..53f5c840f8 100644 --- a/apps/plugins/lib/action_helper.h +++ b/apps/plugins/lib/action_helper.h @@ -27,7 +27,7 @@ */ #ifndef _ACTION_HELPER_H_ #define _ACTION_HELPER_H_ - +extern const size_t action_helper_maxbuffer; char* action_name(int action); char* context_name(int context); diff --git a/apps/plugins/lib/action_helper.pl b/apps/plugins/lib/action_helper.pl index 1dfdcfd070..742419e23b 100755 --- a/apps/plugins/lib/action_helper.pl +++ b/apps/plugins/lib/action_helper.pl @@ -140,10 +140,12 @@ printf "#define CONTEXTBUFSZ %d\n\n", $len_max_context; if ($len_max_action > $len_max_context) { + print "const size_t action_helper_maxbuffer = ACTIONBUFSZ;\n"; print "static char name_buf[ACTIONBUFSZ];\n"; } else { + print "const size_t action_helper_maxbuffer = CONTEXTBUFSZ;\n"; print "static char name_buf[CONTEXTBUFSZ];\n"; } print <<EOF diff --git a/apps/plugins/lib/bmp_smooth_scale.c b/apps/plugins/lib/bmp_smooth_scale.c index c5f258cdbf..378ff96448 100644 --- a/apps/plugins/lib/bmp_smooth_scale.c +++ b/apps/plugins/lib/bmp_smooth_scale.c @@ -78,7 +78,7 @@ void smooth_resize_bitmap(struct bitmap *src_bmp, struct bitmap *dest_bmp) fb_data *sptr, *dptr; int x, y, end; int val_y = 0, val_x; -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE const int sw = src_bmp->height; const int sh = src_bmp->width; const int dw = dest_bmp->height; diff --git a/apps/plugins/lib/button_helper.h b/apps/plugins/lib/button_helper.h index 1197b172b0..4087ba898a 100644 --- a/apps/plugins/lib/button_helper.h +++ b/apps/plugins/lib/button_helper.h @@ -32,7 +32,9 @@ struct available_button * 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 size_t button_helper_maxbuffer; 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 index 45c3fd9073..192df18d7f 100755 --- a/apps/plugins/lib/button_helper.pl +++ b/apps/plugins/lib/button_helper.pl @@ -26,12 +26,15 @@ my @buttons = (); my $count = 1; #null sentinel my $val; my $def; +my $len_max_button = 0; while(my $line = <STDIN>) { chomp($line); if($line =~ /^#define (BUTTON_[^\s]+) (.+)$/) { $def = "{\"$1\", $2},\n"; + my $slen = length($1) + 1; # NULL terminator + if ($slen > $len_max_button) { $len_max_button = $slen; } $val = $2; if($val =~ /^0/) { @@ -53,6 +56,8 @@ print <<EOF #include "button.h" #include "button_helper.h" +const size_t button_helper_maxbuffer = $len_max_button; + static const struct available_button buttons[$count] = { EOF ; diff --git a/apps/plugins/lib/helper.c b/apps/plugins/lib/helper.c index 018c1616c8..92d9ec905e 100644 --- a/apps/plugins/lib/helper.c +++ b/apps/plugins/lib/helper.c @@ -58,7 +58,12 @@ void backlight_use_settings(void) backlight_timeout_plugged); #endif /* CONFIG_CHARGING */ } -#endif /* HAVE_BACKLIGHT */ +#else /* HAVE_BACKLIGHT */ +/* DUMMY FUNCTIONS */ +void backlight_force_on(void){} +void backlight_ignore_timeout(void){} +void backlight_use_settings(void){} +#endif /* !HAVE_BACKLIGHT */ #ifdef HAVE_SW_POWEROFF static bool original_sw_poweroff_state = true; @@ -73,7 +78,11 @@ void sw_poweroff_restore(void) { rb->button_set_sw_poweroff_state(original_sw_poweroff_state); } -#endif +#else /* HAVE_SW_POWEROFF */ +/* DUMMY FUNCTIONS */ +void sw_poweroff_disable(void){} +void sw_poweroff_restore(void){} +#endif /* !HAVE_SW_POWEROFF */ #ifdef HAVE_REMOTE_LCD /* Force the backlight on */ @@ -106,7 +115,12 @@ void remote_backlight_use_settings(void) remote_backlight_timeout_plugged); #endif /* CONFIG_CHARGING */ } -#endif /* HAVE_REMOTE_LCD */ +#else /* HAVE_REMOTE_LCD */ +/* DUMMY FUNCTIONS */ +void remote_backlight_force_on(void){} +void remote_backlight_ignore_timeout(void){} +void remote_backlight_use_settings(void){} +#endif /* !HAVE_REMOTE_LCD */ #ifdef HAVE_BUTTON_LIGHT /* Force the buttonlight on */ @@ -133,7 +147,13 @@ void buttonlight_use_settings(void) { rb->buttonlight_set_timeout(rb->global_settings->buttonlight_timeout); } -#endif /* HAVE_BUTTON_LIGHT */ +#else /* HAVE_BUTTON_LIGHT */ +/* DUMMY FUNCTIONS */ +void buttonlight_force_on(void){} +void buttonlight_force_off(void){} +void buttonlight_ignore_timeout(void){} +void buttonlight_use_settings(void){} +#endif /* !HAVE_BUTTON_LIGHT */ #ifdef HAVE_BACKLIGHT_BRIGHTNESS void backlight_brightness_set(int brightness) @@ -145,7 +165,15 @@ void backlight_brightness_use_setting(void) { rb->backlight_set_brightness(rb->global_settings->brightness); } -#endif /* HAVE_BACKLIGHT_BRIGHTNESS */ +#else /* HAVE_BACKLIGHT_BRIGHTNESS */ +/* DUMMY FUNCTIONS */ +void backlight_brightness_set(int brightness) +{ + (void)brightness; +} +void backlight_brightness_use_setting(void){} + +#endif /* !HAVE_BACKLIGHT_BRIGHTNESS */ #ifdef HAVE_BUTTONLIGHT_BRIGHTNESS void buttonlight_brightness_set(int brightness) @@ -157,4 +185,12 @@ void buttonlight_brightness_use_setting(void) { rb->buttonlight_set_brightness(rb->global_settings->buttonlight_brightness); } -#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */ +#else /* HAVE_BUTTONLIGHT_BRIGHTNESS */ +/* DUMMY FUNCTIONS */ +void buttonlight_brightness_set(int brightness) +{ + (void)brightness; +} + +void buttonlight_brightness_use_setting(void){} +#endif /* !HAVE_BUTTONLIGHT_BRIGHTNESS */ diff --git a/apps/plugins/lib/helper.h b/apps/plugins/lib/helper.h index 00ad8ac087..6aee4dc581 100644 --- a/apps/plugins/lib/helper.h +++ b/apps/plugins/lib/helper.h @@ -23,6 +23,16 @@ #include "plugin.h" +#ifndef MAX_BRIGHTNESS_SETTING +#define MAX_BRIGHTNESS_SETTING 0 +#endif +#ifndef MIN_BRIGHTNESS_SETTING +#define MIN_BRIGHTNESS_SETTING 0 +#endif +#ifndef DEFAULT_BRIGHTNESS_SETTING +#define DEFAULT_BRIGHTNESS_SETTING 0 +#endif + int talk_val(long n, int unit, bool enqueue); /** @@ -32,39 +42,29 @@ void backlight_force_on(void); void backlight_ignore_timeout(void); void backlight_use_settings(void); -#ifdef HAVE_SW_POWEROFF /** * Disable and restore software poweroff (i.e. holding PLAY on iPods). * Only call _restore() if _disable() was called earlier! */ void sw_poweroff_disable(void); void sw_poweroff_restore(void); -#endif -#ifdef HAVE_REMOTE_LCD void remote_backlight_force_on(void); void remote_backlight_ignore_timeout(void); void remote_backlight_use_settings(void); -#endif -#ifdef HAVE_BUTTON_LIGHT void buttonlight_force_on(void); void buttonlight_force_off(void); void buttonlight_ignore_timeout(void); void buttonlight_use_settings(void); -#endif /** * Backlight brightness adjustment settings */ -#ifdef HAVE_BACKLIGHT_BRIGHTNESS void backlight_brightness_set(int brightness); void backlight_brightness_use_setting(void); -#endif -#ifdef HAVE_BUTTONLIGHT_BRIGHTNESS void buttonlight_brightness_set(int brightness); void buttonlight_brightness_use_setting(void); -#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */ #endif /* _LIB_HELPER_H_ */ diff --git a/apps/plugins/lib/highscore.c b/apps/plugins/lib/highscore.c index 3aae955bfc..04faf27891 100644 --- a/apps/plugins/lib/highscore.c +++ b/apps/plugins/lib/highscore.c @@ -124,11 +124,16 @@ void highscore_show(int position, struct highscore *scores, int num_scores, bool show_level) { int i, w, h; -#ifdef HAVE_LCD_COLOR +#if defined(HAVE_LCD_COLOR) || LCD_DEPTH >= 2 unsigned bgcolor = rb->lcd_get_background(); unsigned fgcolor = rb->lcd_get_foreground(); +#ifdef HAVE_LCD_COLOR rb->lcd_set_background(LCD_BLACK); rb->lcd_set_foreground(LCD_WHITE); +#else + rb->lcd_set_background(LCD_WHITE); + rb->lcd_set_foreground(LCD_BLACK); +#endif #endif rb->lcd_clear_display(); @@ -173,7 +178,7 @@ void highscore_show(int position, struct highscore *scores, int num_scores, rb->button_clear_queue(); rb->button_get(true); rb->lcd_setfont(FONT_SYSFIXED); -#ifdef HAVE_LCD_COLOR +#if defined(HAVE_LCD_COLOR) || LCD_DEPTH >= 2 rb->lcd_set_background(bgcolor); rb->lcd_set_foreground(fgcolor); #endif diff --git a/apps/plugins/lib/id3.c b/apps/plugins/lib/id3.c new file mode 100644 index 0000000000..b0202b1d9c --- /dev/null +++ b/apps/plugins/lib/id3.c @@ -0,0 +1,39 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 Christian Soffke + * + * 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" + +/* Fills mp3entry with metadata retrieved from RAM, if possible, or by reading from + * the file directly. Note that the tagcache only stores a subset of metadata and + * will thus not return certain properties of the file, such as frequency, size, or + * codec. + */ +bool retrieve_id3(struct mp3entry *id3, const char* file) +{ +#if defined (HAVE_TAGCACHE) && defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) + if (rb->tagcache_fill_tags(id3, file)) + { + rb->strlcpy(id3->path, file, sizeof(id3->path)); + return true; + } +#endif + + return !rb->mp3info(id3, file); +} diff --git a/apps/plugins/lib/id3.h b/apps/plugins/lib/id3.h new file mode 100644 index 0000000000..6ae1688798 --- /dev/null +++ b/apps/plugins/lib/id3.h @@ -0,0 +1,26 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 Christian Soffke + * + * 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 ID3_H +#define ID3_H + +bool retrieve_id3(struct mp3entry *id3, const char* file); + +#endif /* ID3_H */ diff --git a/apps/plugins/lib/mul_id3.c b/apps/plugins/lib/mul_id3.c new file mode 100644 index 0000000000..edf44f7282 --- /dev/null +++ b/apps/plugins/lib/mul_id3.c @@ -0,0 +1,174 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 Christian Soffke + * + * 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 "mul_id3.h" + +struct multiple_tracks_id3 { + unsigned long long filesize; + unsigned long long length; + unsigned long frequency; + unsigned int artist_hash; + unsigned int composer_hash; + unsigned int albumartist_hash; + unsigned int grouping_hash; + unsigned int comment_hash; + unsigned int album_hash; + unsigned int genre_hash; + unsigned int codectype; + unsigned int bitrate; + int year; + bool vbr; +}; + +static struct multiple_tracks_id3 mul_id3; + + +/* Calculate modified FNV hash of string + * has good avalanche behaviour and uniform distribution + * see http://home.comcast.net/~bretm/hash/ */ +static unsigned int mfnv(char *str) +{ + const unsigned int p = 16777619; + unsigned int hash = 0x811C9DC5; // 2166136261; + + if (!str) + return 0; + + while(*str) + hash = (hash ^ *str++) * p; + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + return hash; +} + +static void init_mul_id3(void) +{ + mul_id3.artist_hash = 0; + mul_id3.album_hash = 0; + mul_id3.genre_hash = 0; + mul_id3.composer_hash = 0; + mul_id3.albumartist_hash = 0; + mul_id3.grouping_hash = 0; + mul_id3.comment_hash = 0; + mul_id3.codectype = 0; + mul_id3.vbr = false; + mul_id3.bitrate = 0; + mul_id3.frequency = 0; + mul_id3.length = 0; + mul_id3.filesize = 0; + mul_id3.year = 0; +} + +void collect_id3(struct mp3entry *id3, bool is_first_track) +{ + if (is_first_track) + { + init_mul_id3(); + mul_id3.artist_hash = mfnv(id3->artist); + mul_id3.album_hash = mfnv(id3->album); + mul_id3.genre_hash = mfnv(id3->genre_string); + mul_id3.composer_hash = mfnv(id3->composer); + mul_id3.albumartist_hash = mfnv(id3->albumartist); + mul_id3.grouping_hash = mfnv(id3->grouping); + mul_id3.comment_hash = mfnv(id3->comment); + mul_id3.codectype = id3->codectype; + mul_id3.vbr = id3->vbr; + mul_id3.bitrate = id3->bitrate; + mul_id3.frequency = id3->frequency; + mul_id3.year = id3->year; + } + else + { + if (mul_id3.artist_hash && (mfnv(id3->artist) != mul_id3.artist_hash)) + mul_id3.artist_hash = 0; + if (mul_id3.album_hash && (mfnv(id3->album) != mul_id3.album_hash)) + mul_id3.album_hash = 0; + if (mul_id3.genre_hash && (mfnv(id3->genre_string) != mul_id3.genre_hash)) + mul_id3.genre_hash = 0; + if (mul_id3.composer_hash && (mfnv(id3->composer) != mul_id3.composer_hash)) + mul_id3.composer_hash = 0; + if (mul_id3.albumartist_hash && (mfnv(id3->albumartist) != + mul_id3.albumartist_hash)) + mul_id3.albumartist_hash = 0; + if (mul_id3.grouping_hash && (mfnv(id3->grouping) != mul_id3.grouping_hash)) + mul_id3.grouping_hash = 0; + if (mul_id3.comment_hash && (mfnv(id3->comment) != mul_id3.comment_hash)) + mul_id3.comment_hash = 0; + + if (mul_id3.codectype && (id3->codectype != mul_id3.codectype)) + mul_id3.codectype = AFMT_UNKNOWN; + if (mul_id3.bitrate && (id3->bitrate != mul_id3.bitrate || + id3->vbr != mul_id3.vbr)) + mul_id3.bitrate = 0; + if (mul_id3.frequency && (id3->frequency != mul_id3.frequency)) + mul_id3.frequency = 0; + if (mul_id3.year && (id3->year != mul_id3.year)) + mul_id3.year = 0; + } + mul_id3.length += id3->length; + mul_id3.filesize += id3->filesize; +} + +/* (!) Note scale factor applied to returned metadata: + * - Unit for filesize will be KiB instead of Bytes + * - Unit for length will be s instead of ms + * + * Use result only as input for browse_id3, + * with the track_ct parameter set to > 1. + */ +void finalize_id3(struct mp3entry *id3) +{ + id3->path[0] = '\0'; + id3->title = NULL; + if (!mul_id3.artist_hash) + id3->artist = NULL; + if (!mul_id3.album_hash) + id3->album = NULL; + if (!mul_id3.genre_hash) + id3->genre_string = NULL; + if (!mul_id3.composer_hash) + id3->composer = NULL; + if (!mul_id3.albumartist_hash) + id3->albumartist = NULL; + if (!mul_id3.grouping_hash) + id3->grouping = NULL; + if (!mul_id3.comment_hash) + id3->comment = NULL; + id3->disc_string = NULL; + id3->track_string = NULL; + id3->year_string = NULL; + id3->year = mul_id3.year; + mul_id3.length /= 1000; + mul_id3.filesize >>= 10; + id3->length = mul_id3.length > ULONG_MAX ? 0 : mul_id3.length; + id3->filesize = mul_id3.filesize > INT_MAX ? 0 : mul_id3.filesize; + id3->frequency = mul_id3.frequency; + id3->bitrate = mul_id3.bitrate; + id3->codectype = mul_id3.codectype; + id3->vbr = mul_id3.vbr; + id3->discnum = 0; + id3->tracknum = 0; + id3->track_level = 0; + id3->album_level = 0; +} diff --git a/apps/plugins/lib/mul_id3.h b/apps/plugins/lib/mul_id3.h new file mode 100644 index 0000000000..d08095de5c --- /dev/null +++ b/apps/plugins/lib/mul_id3.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 Christian Soffke + * + * 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 MUL_ID3_H +#define MUL_ID3_H + +void collect_id3(struct mp3entry *id3, bool is_first_track); +void finalize_id3(struct mp3entry *id3); + +#endif /* MUL_ID3_H */ diff --git a/apps/plugins/lib/osd.c b/apps/plugins/lib/osd.c index 7d6e10a410..99f77da7dc 100644 --- a/apps/plugins/lib/osd.c +++ b/apps/plugins/lib/osd.c @@ -106,10 +106,10 @@ static struct osd grey_osd; # error Unknown 2-bit format; please define macros # endif /* LCD_PIXELFORMAT */ #elif LCD_DEPTH == 16 -# if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +# if LCD_STRIDEFORMAT == VERTICAL_STRIDE # define _OSD_HEIGHT2BYTES(h) ((h)*2) # define _OSD_BYTES2HEIGHT(b) ((b)/2) -# else /* !defined(LCD_STRIDEFORMAT) || LCD_STRIDEFORMAT != VERTICAL_STRIDE */ +# else /* LCD_STRIDEFORMAT != VERTICAL_STRIDE */ # define _OSD_WIDTH2BYTES(w) ((w)*2) # define _OSD_BYTES2WIDTH(b) ((b)/2) # endif /* end stride type selection */ @@ -160,7 +160,7 @@ static void * _osd_lcd_init_buffers(struct osd *osd, unsigned flags, rb->viewport_set_fullscreen(&osd->vp, SCREEN_MAIN); -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE int colbytes = _OSD_HEIGHT2BYTES(LCD_HEIGHT); int bytecols = *bufsize / colbytes; int w = _OSD_BYTES2WIDTH(bytecols); @@ -193,7 +193,7 @@ static void * _osd_lcd_init_buffers(struct osd *osd, unsigned flags, w = _OSD_BYTES2WIDTH(_OSD_WIDTH2BYTES(w)); osd->lcd_bitmap_stride = _OSD_BYTES2HEIGHT(_OSD_HEIGHT2BYTES(LCD_HEIGHT)); osd->back_bitmap_stride = h; -#else /* !defined(LCD_STRIDEFORMAT) || LCD_STRIDEFORMAT != VERTICAL_STRIDE */ +#else /* LCD_STRIDEFORMAT != VERTICAL_STRIDE */ int rowbytes = _OSD_WIDTH2BYTES(LCD_WIDTH); int byterows = *bufsize / rowbytes; int w = _OSD_BYTES2WIDTH(rowbytes); diff --git a/apps/plugins/lib/overlay.c b/apps/plugins/lib/overlay.c index 0ecc1bf3e7..065273534e 100644 --- a/apps/plugins/lib/overlay.c +++ b/apps/plugins/lib/overlay.c @@ -83,9 +83,8 @@ enum plugin_status run_overlay(const void* parameter, goto error_close; } - - if (hdr->api_version > PLUGIN_API_VERSION - || hdr->api_version < PLUGIN_MIN_API_VERSION) + if (hdr->api_version != PLUGIN_API_VERSION || + p_hdr->api_size > sizeof(struct plugin_api)) { rb->splashf(2*HZ, "%s overlay: Incompatible version.", name); goto error_close; diff --git a/apps/plugins/lib/playback_control.c b/apps/plugins/lib/playback_control.c index 363033b1f2..3e97916400 100644 --- a/apps/plugins/lib/playback_control.c +++ b/apps/plugins/lib/playback_control.c @@ -65,21 +65,21 @@ static bool nexttrack(void) static bool volume(void) { const struct settings_list* vol = - rb->find_setting(&rb->global_settings->volume, NULL); + rb->find_setting(&rb->global_settings->volume); return rb->option_screen((struct settings_list*)vol, parentvp, false, "Volume"); } static bool shuffle(void) { const struct settings_list* shuffle = - rb->find_setting(&rb->global_settings->playlist_shuffle, NULL); + rb->find_setting(&rb->global_settings->playlist_shuffle); return rb->option_screen((struct settings_list*)shuffle, parentvp, false, "Shuffle"); } static bool repeat_mode(void) { const struct settings_list* repeat = - rb->find_setting(&rb->global_settings->repeat_mode, NULL); + rb->find_setting(&rb->global_settings->repeat_mode); int old_repeat = rb->global_settings->repeat_mode; rb->option_screen((struct settings_list*)repeat, parentvp, false, "Repeat"); @@ -91,19 +91,19 @@ static bool repeat_mode(void) return false; } MENUITEM_FUNCTION(prevtrack_item, 0, ID2P(LANG_PREVTRACK), - prevtrack, NULL, NULL, Icon_NOICON); + prevtrack, NULL, Icon_NOICON); MENUITEM_FUNCTION(playpause_item, 0, ID2P(LANG_PLAYPAUSE), - play, NULL, NULL, Icon_NOICON); + play, NULL, Icon_NOICON); MENUITEM_FUNCTION(stop_item, 0, ID2P(LANG_STOP_PLAYBACK), - stop, NULL, NULL, Icon_NOICON); + stop, NULL, Icon_NOICON); MENUITEM_FUNCTION(nexttrack_item, 0, ID2P(LANG_NEXTTRACK), - nexttrack, NULL, NULL, Icon_NOICON); + nexttrack, NULL, Icon_NOICON); MENUITEM_FUNCTION(volume_item, 0, ID2P(LANG_CHANGE_VOLUME), - volume, NULL, NULL, Icon_NOICON); + volume, NULL, Icon_NOICON); MENUITEM_FUNCTION(shuffle_item, 0, ID2P(LANG_CHANGE_SHUFFLE_MODE), - shuffle, NULL, NULL, Icon_NOICON); + shuffle, NULL, Icon_NOICON); MENUITEM_FUNCTION(repeat_mode_item, 0, ID2P(LANG_CHANGE_REPEAT_MODE), - repeat_mode, NULL, NULL, Icon_NOICON); + repeat_mode, NULL, Icon_NOICON); MAKE_MENU(playback_control_menu, ID2P(LANG_PLAYBACK_CONTROL), NULL, Icon_NOICON, &prevtrack_item, &playpause_item, &stop_item, &nexttrack_item, &volume_item, &shuffle_item, &repeat_mode_item); diff --git a/apps/plugins/lib/pluginlib_bmp.c b/apps/plugins/lib/pluginlib_bmp.c index f3edfbf425..82f84b05af 100644 --- a/apps/plugins/lib/pluginlib_bmp.c +++ b/apps/plugins/lib/pluginlib_bmp.c @@ -94,7 +94,7 @@ int save_bmp_file( char* filename, struct bitmap *bm ) */ void simple_resize_bitmap(struct bitmap *src, struct bitmap *dst) { -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE const int srcw = src->height; const int srch = src->width; const int dstw = dst->height; diff --git a/apps/plugins/lib/printcell_helper.c b/apps/plugins/lib/printcell_helper.c index 328f74e318..48b8b2c9d2 100644 --- a/apps/plugins/lib/printcell_helper.c +++ b/apps/plugins/lib/printcell_helper.c @@ -22,10 +22,6 @@ #include "plugin.h" #include "lib/printcell_helper.h" -#ifndef PRINTCELL_MAX_COLUMNS -#define PRINTCELL_MAX_COLUMNS 16 -#endif - #define COLUMN_ENDLEN 3 #define TITLE_FLAG 0xFF #define SELECTED_FLAG 0x1 @@ -37,48 +33,54 @@ #endif struct printcell_info_t { - int offw[NB_SCREENS]; - int iconw[NB_SCREENS]; - int selcol_offw[NB_SCREENS]; - int totalcolw[NB_SCREENS]; - int firstcolxw[NB_SCREENS]; - uint16_t colw[NB_SCREENS][PRINTCELL_MAX_COLUMNS]; - int ncols; - int selcol; - int selcol_index; - char title[PRINTCELL_MAXLINELEN]; - bool separator; + struct gui_synclist *gui_list; /* list to display */ + int offw[NB_SCREENS]; /* padding between column boundries and text */ + int iconw[NB_SCREENS]; /* width of an icon */ + int selcol_offw[NB_SCREENS]; /* offset width calculated for selected item */ + int totalcolw[NB_SCREENS]; /* total width of all columns */ + int firstcolxw[NB_SCREENS]; /* first column x + width, save recalculating */ + int ncols; /* number of columns */ + int selcol; /* selected column (-1 to ncols-1) */ + uint32_t hidecol_flags; /*bits 0-31 set bit to 1 to hide a column (1<<col#) */ + uint16_t colw[NB_SCREENS][PRINTCELL_MAX_COLUMNS]; /* width of title text + or MIN(or user defined width / screen width) */ + char title[PRINTCELL_MAXLINELEN]; /* title buffer */ + char titlesep; /* character that separates title column items (ex '$') */ + char colsep; /* character that separates text column items (ex ',') */ + bool separator; /* draw grid */ }; + static struct printcell_info_t printcell; -static void parse_dsptext(int ncols, const char *dsp_text, char* buffer, uint16_t *sidx) +static void parse_dsptext(char splitchr, int ncols, const char *dsp_text, + char* buffer, size_t bufsz, uint16_t *sidx) { /*Internal function loads sidx with split offsets indexing - the buffer of null terminated strings, splits on '$' + the buffer of null terminated strings, splits on 'splitchr' _assumptions_: dsp_text[len - 1] = \0, - buffer[PRINTCELL_MAXLINELEN], sidx[PRINTCELL_MAX_COLUMNS] */ int i = 0; - int j = 0; - int ch = '$'; /* first column $ is optional */ - if (*dsp_text == '$') + size_t j = 0; + int ch = splitchr; /* first column $ is optional */ + if (*dsp_text == splitchr) dsp_text++; /* add null to the start of the text buffer */ buffer[j++] = '\0'; do { - if (ch == '$') + if (ch == splitchr) { - sidx[i] = j; - if (*dsp_text == '$') /* $$ escaped user must want to display $*/ - buffer[j++] = *dsp_text++; - while (*dsp_text != '\0' && *dsp_text != '$' && j < PRINTCELL_MAXLINELEN - 1) + sidx[i] = j; /* save start index and copy next column to the buffer */ + while (*dsp_text != '\0' && *dsp_text != splitchr && j < (bufsz - 1)) + { buffer[j++] = *dsp_text++; + } buffer[j++] = '\0'; + i++; - if (i >= ncols || j >= (PRINTCELL_MAXLINELEN - 1)) + if (i >= ncols || j >= (bufsz - 1)) break; } ch = *dsp_text++; @@ -146,23 +148,39 @@ static inline void set_cell_width(struct viewport *vp, int max_w, int new_w) static inline int printcells(struct screen *display, char* buffer, uint16_t *sidx, struct line_desc *linedes, struct viewport *vp, int vp_w, int separator, - int x, int y, int offw, int selected_flag, bool scroll) + int x, int y, int offw, int selected_flag, int last_col, + bool scroll, bool is_title) { /* Internal function prints remaining cells */ int text_offset = offw + offw; - int ncols = printcell.ncols; int screen = display->screen_type; int height = linedes->height; int selsep = (selected_flag == 0) ? 0: separator; uint16_t *screencolwidth = printcell.colw[screen]; - for(int i = 1; i < ncols; i++) + for(int i = 1; i <= last_col; i++) { int ny = y; int nw = screencolwidth[i] + text_offset; + int offx = 0; + + if (i == last_col || x + nw >= vp_w - offw + 1) + { /* not enough space for next column use up excess */ + if (nw < (vp_w - x)) + { + if (is_title) + offx = ((vp_w - x) - nw) / 2; + nw = vp_w - x; + } + } + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + nw = 0; + int nx = x + nw; char *buftext; - if (nx > 0 && x < vp_w) + + if (nx > 0 && nw > offw && x < vp_w) { set_cell_width(vp, vp_w, nx); @@ -173,11 +191,15 @@ static inline int printcells(struct screen *display, char* buffer, } else { + if (vp_w < x + text_offset) + { + scroll = false; + } linedes->scroll = scroll; linedes->separator_height = separator; } buftext = &buffer[sidx[i]]; - display->put_line(x + offw, ny, linedes, "$t", buftext); + display->put_line(x + offw + offx, ny, linedes, "$t", buftext); vp->width += COLUMN_ENDLEN + 1; draw_selector(display, linedes, selected_flag, i, separator, x, ny, nw, height); } @@ -192,12 +214,18 @@ static inline int calcvisible(int screen, int vp_w, int text_offset, int sbwidth uint16_t *screencolwidth = printcell.colw[screen]; int screenicnwidth = printcell.iconw[screen]; int offset = 0; - int selcellw = screencolwidth[printcell.selcol] + text_offset; + int selcellw = 0; + if (printcell.selcol >= 0) + selcellw = screencolwidth[printcell.selcol] + text_offset; int maxw = vp_w - (sbwidth + selcellw + 1); for (int i = printcell.selcol - 1; i >= 0; i--) { int cw = screencolwidth[i] + text_offset; + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + cw = 0; + if (i == 0) cw += screenicnwidth; if (offset > 0 || cw > maxw) @@ -234,7 +262,7 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) bool show_cursor = list_info->show_cursor; bool have_icons = list_info->have_icons; struct line_desc *linedes = list_info->linedes; - char *dsp_text = list_info->dsp_text; + const char *dsp_text = list_info->dsp_text; struct viewport *vp = list_info->vp; int line = list_info->line; @@ -255,15 +283,30 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) separator = 1; int nx = x; + int last_col = printcell.ncols - 1; + int hidden_w = 0; int nw, colxw; - + char *buftext; printcell_buffer[0] = '\0'; - parse_dsptext(printcell.ncols, dsp_text, printcell_buffer, sidx); - char *buftext = &printcell_buffer[sidx[0]]; + uint16_t *screencolwidth = printcell.colw[screen]; + if (printcell.hidecol_flags > 0) + { + for (int i = 0; i < printcell.ncols; i++) + { + if (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + last_col = i; + else + hidden_w += (screencolwidth[i] + text_offset); + } + } if (is_title) { + parse_dsptext(printcell.titlesep, printcell.ncols, dsp_text, + printcell_buffer, sizeof(printcell_buffer), sidx); + + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ int sbwidth = 0; if (rb->global_settings->scrollbar == SCROLLBAR_LEFT) sbwidth = rb->global_settings->scrollbar_width; @@ -276,6 +319,9 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) nx -= printcell.selcol_offw[screen]; nw = screencolwidth[0] + printcell.iconw[screen] + text_offset; + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, 0)) + nw = printcell.iconw[screen] - 1; nw += sbwidth; colxw = nx + nw; @@ -299,16 +345,21 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) } else { + parse_dsptext(printcell.colsep, printcell.ncols, dsp_text, + printcell_buffer, sizeof(printcell_buffer), sidx); + + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ int cursor = Icon_NOICON; nx -= printcell.selcol_offw[screen]; if (selected_flag & SELECTED_FLAG) { - printcell.selcol_index = sidx[printcell.selcol]; /* save the item offset*/ cursor = Icon_Cursor; /* limit length of selection if columns don't reach end */ int maxw = nx + printcell.totalcolw[screen] + printcell.iconw[screen]; maxw += text_offset * printcell.ncols; + maxw -= hidden_w; + if (vp_w > maxw) vp->width = maxw; /* display a blank line first to draw selector across all cells */ @@ -358,7 +409,7 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) nx += nw; /* display remaining cells */ printcells(display, printcell_buffer, sidx, linedes, vp, vp_w, separator, - nx, y, col_offset_width, selected_flag, scroll_items); + nx, y, col_offset_width, selected_flag, last_col, scroll_items, is_title); /* draw a line at the bottom of the list */ if (separator > 0 && line == list->nb_items - 1) @@ -371,14 +422,17 @@ static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) vp->width = vp_w; } -void printcell_enable(struct gui_synclist *gui_list, bool enable, bool separator) +void printcell_enable(bool enable) { - printcell.separator = separator; + if (!printcell.gui_list) + return; + struct gui_synclist *gui_list = printcell.gui_list; #ifdef HAVE_LCD_COLOR static int list_sep_color = INT_MIN; if (enable) { - list_sep_color = rb->global_settings->list_separator_color; + if (list_sep_color == INT_MIN) + list_sep_color = rb->global_settings->list_separator_color; rb->global_settings->list_separator_color = rb->global_settings->fg_color; gui_list->callback_draw_item = printcell_listdraw_fn; } @@ -398,8 +452,11 @@ void printcell_enable(struct gui_synclist *gui_list, bool enable, bool separator } -int printcell_increment_column(struct gui_synclist *gui_list, int increment, bool wrap) +int printcell_increment_column(int increment, bool wrap) { + if (!printcell.gui_list) + return -1; + struct gui_synclist *gui_list = printcell.gui_list; int item = printcell.selcol + increment; int imin = -1; int imax = printcell.ncols - 1; @@ -419,36 +476,94 @@ int printcell_increment_column(struct gui_synclist *gui_list, int increment, boo FOR_NB_SCREENS(n) /* offset needs recalculated */ printcell.selcol_offw[n] = 0; printcell.selcol = item; - printcell.selcol_index = 0; + rb->gui_synclist_draw(gui_list); + rb->gui_synclist_speak_item(gui_list); } return item; } +int printcell_get_column_selected(void) +{ + if (!printcell.gui_list) + return -1; + return printcell.selcol; +} + +uint32_t printcell_get_column_visibility(int col) +{ + if (col >= 0) + return (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, col) ? 0:1); + else /* return flag of all columns */ + return printcell.hidecol_flags; +} + +void printcell_set_column_visible(int col, bool visible) +{ + /* visible columns have 0 for the column bit hidden columns have the bit set */ + if (col >= 0) + { + if (visible) + printcell.hidecol_flags &= ~(PRINTCELL_COLUMN_FLAG(col)); + else + printcell.hidecol_flags |= PRINTCELL_COLUMN_FLAG(col); + } + else + { + if (visible) /* set to everything visible */ + printcell.hidecol_flags = 0; + else /* set to everything hidden */ + printcell.hidecol_flags = ((uint32_t)-1); + } +} + int printcell_set_columns(struct gui_synclist *gui_list, - char * title, enum themable_icons icon) + struct printcell_settings * pcs, + char * title, enum themable_icons icon) { + if (title == NULL) title = "$PRINTCELL NOT SETUP"; + + if (pcs == NULL) /* DEFAULTS */ + { +#if LCD_DEPTH > 1 + /* If line sep is set to automatic then outline cells */ + bool sep = (rb->global_settings->list_separator_height < 0); +#else + bool sep = (rb->global_settings->cursor_style == 0); +#endif + pcs = &(struct printcell_settings){ .cell_separator = sep, + .title_delimeter = '$', + .text_delimeter = '$', + .hidecol_flags = 0}; + } + uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /* starting position of column in title string */ int width, height, user_minwidth; int i = 0; - int j = 0; - int ch = '$'; /* first column $ is optional */ - + size_t j = 0; rb->memset(&printcell, 0, sizeof(struct printcell_info_t)); + printcell.gui_list = gui_list; + printcell.separator = pcs->cell_separator; + printcell.titlesep = pcs->title_delimeter; + printcell.colsep = pcs->text_delimeter; + printcell.hidecol_flags = pcs->hidecol_flags; + + int ch = printcell.titlesep; /* first column $ is optional */ + FOR_NB_SCREENS(n) { rb->screens[n]->getstringsize("W", &width, &height); printcell.offw[n] = width; /* set column text offset */ } - if (*title == '$') + if (*title == printcell.titlesep) title++; do { - if (ch == '$') + if (ch == printcell.titlesep) { printcell.title[j++] = ch; user_minwidth = 0; @@ -460,7 +575,7 @@ int printcell_set_columns(struct gui_synclist *gui_list, user_minwidth = 10*user_minwidth + *title - '0'; title++; } - if (*title != '$') /* user forgot $ or wants to display '*' */ + if (*title != printcell.titlesep) /* user forgot titlesep or wants to display '*' */ { title = dspst; user_minwidth = 0; @@ -470,17 +585,25 @@ int printcell_set_columns(struct gui_synclist *gui_list, } sidx[i] = j; - if (*title == '$') /* $$ escaped user must want to display $*/ - printcell.title[j++] = *title++; - while (*title != '\0' && *title != '$' && j < PRINTCELL_MAXLINELEN - 1) + while (*title != '\0' + && *title != printcell.titlesep + && j < PRINTCELL_MAXLINELEN - 1) + { printcell.title[j++] = *title++; + } FOR_NB_SCREENS(n) { - rb->screens[n]->getstringsize(&printcell.title[sidx[i]], &width, &height); + rb->screens[n]->getstringsize(&printcell.title[sidx[i]], + &width, &height); + if (width < user_minwidth) width = user_minwidth; + + if (width > LCD_WIDTH) + width = LCD_WIDTH; + printcell.colw[n][i] = width; printcell.totalcolw[n] += width; } @@ -492,37 +615,67 @@ int printcell_set_columns(struct gui_synclist *gui_list, printcell.ncols = i; printcell.title[j] = '\0'; printcell.selcol = -1; - printcell.selcol_index = 0; rb->gui_synclist_set_title(gui_list, printcell.title, icon); return printcell.ncols; } -char *printcell_get_selected_column_text(struct gui_synclist *gui_list, char *buf, size_t bufsz) +char *printcell_get_title_text(int selcol, char *buf, size_t bufsz) { - int selected = gui_list->selected_item; - int index = printcell.selcol_index - 1; + /* note offsets are calculated everytime this function is called + * shouldn't be used in hot code paths */ + int index = 0; + buf[0] = '\0'; + if (selcol < 0) /* return entire string incld col formatting '$'*/ + return printcell.title; + + if (selcol < printcell.ncols) + { + uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/ + parse_dsptext(printcell.titlesep, selcol + 1, printcell.title, buf, bufsz, sidx); + index = sidx[selcol]; + } + return &buf[index]; +} - if (index < 0) - index = 0; +char *printcell_get_column_text(int selcol, char *buf, size_t bufsz) +{ + int index = 0; char *bpos; + struct gui_synclist *gui_list = printcell.gui_list; - if (gui_list->callback_get_item_name(selected, gui_list->data, buf, bufsz) == buf) + if (gui_list && gui_list->callback_draw_item == printcell_listdraw_fn) { - bpos = &buf[index]; - if (printcell.selcol < 0) /* return entire string incld col formatting '$'*/ - return bpos; - while(bpos < &buf[bufsz - 1]) + int col = selcol; + int item = gui_list->selected_item; + void *data = gui_list->data; + + if (col < printcell.ncols + && gui_list->callback_get_item_name(item, data, buf, bufsz) == buf) { - if (*bpos == '$' || *bpos == '\0') - goto success; - bpos++; + bpos = buf; + if (col < 0) /* return entire string incld col formatting '$'*/ + { + return bpos; + } + bpos++; /* Skip sep/NULL */ + + while(bpos < &buf[bufsz - 1]) + { + if (*bpos == printcell.colsep || *bpos == '\0') + { + if (col-- == 0) + goto success; + index = bpos - buf + 1; /* Skip sep/NULL */ + } + bpos++; + } } } /*failure*/ - bpos = buf; - index = 0; + bpos = buf; + index = 0; success: - *bpos = '\0'; - return &buf[index]; + *bpos = '\0'; + return &buf[index]; } diff --git a/apps/plugins/lib/printcell_helper.h b/apps/plugins/lib/printcell_helper.h index adc98e5a5f..f58e73c0a5 100644 --- a/apps/plugins/lib/printcell_helper.h +++ b/apps/plugins/lib/printcell_helper.h @@ -21,25 +21,67 @@ #ifndef _PRINTCELL_LIST_H_ #define _PRINTCELL_LIST_H_ +#ifndef PRINTCELL_MAX_COLUMNS +#define PRINTCELL_MAX_COLUMNS 16 /* Max 32 (hidecol_flags)*/ +#endif + #define PRINTCELL_MAXLINELEN MAX_PATH +#define PC_COL_FLAG(col) ((uint32_t)(col >= 0 \ + && col < PRINTCELL_MAX_COLUMNS) ? 1u<<col : -1u) + +#define PRINTCELL_COLUMN_IS_VISIBLE(flag, col) ((flag & PC_COL_FLAG(col)) == 0) +#define PRINTCELL_COLUMN_FLAG(col) (PC_COL_FLAG(col)) -/* sets the printcell function enabled */ -void printcell_enable(struct gui_synclist *gui_list, bool enable, bool separator); +struct printcell_settings +{ + bool cell_separator; + char title_delimeter; + char text_delimeter; + uint32_t hidecol_flags; +}; -/* sets title and calculates cell widths each column is identified by '$' character - ex 3 columns title = "Col1$Col2$Col3" also accepts $*WIDTH$ - ex 3 columns varying width title = "$*64$Col1$*128$Col2$Col3 - returns number of columns +/* Printcell initialization - Sets title and calculates cell widths +* by default each column is identified by '$' character +* ex 3 columns title = "Col1$Col2$Col3" also accepts $*WIDTH$ +* ex 3 columns varying width title = "$*64$Col1$*128$Col2$Col3 +* supplying struct printcell_settings pcs allows changing default settings +* supply NULL to use defaults +* +* Returns number of columns */ -int printcell_set_columns(struct gui_synclist *gui_list, - char * title, enum themable_icons icon); +int printcell_set_columns(struct gui_synclist *gui_list, + struct printcell_settings * pcs, + char * title, enum themable_icons icon); + +/* Sets the printcell function enabled (use after initializing with set_column) + * Note you should call printcell_enable(false) if the list might be reused */ +void printcell_enable(bool enable); -/* increments the current selected column negative increment is allowed - returns the selected column +/* Increments the current selected column negative increment is allowed + returns the selected column range: -1(no selection) to ncols - 1 */ -int printcell_increment_column(struct gui_synclist *gui_list, int increment, bool wrap); +int printcell_increment_column(int increment, bool wrap); + +/* Return index of the currently selected column (-1 to ncols - 1) */ +int printcell_get_column_selected(void); + +/* Return the text of currently selected column buffer should be sized + * for max item len, buf[PRINTCELL_MAXLINELEN] is a safe bet */ +char *printcell_get_column_text(int selcol, char *buf, size_t bufsz); -/* return the text of currently selected column buffer should be sized +/* Return the text of currently selected column title should be sized * for max item len, buf[PRINTCELL_MAXLINELEN] is a safe bet */ -char *printcell_get_selected_column_text(struct gui_synclist *gui_list, char *buf, size_t bufsz); +char *printcell_get_title_text(int selcol, char *buf, size_t bufsz); + + +/* Hide or show a specified column - supply col = -1 to affect all columns */ +void printcell_set_column_visible(int col, bool visible); + +/* Return visibility of a specified column +* returns (1 visible or 0 hidden) +* if supply col == -1 a flag with visibility of all columns will be returned +* NOTE: flag denotes a hidden column by a 1 in the column bit (1 << col#) +* PRINTCELL_COLUMN_IS_VISIBLE(flag,col) macro will convert to bool +*/ +uint32_t printcell_get_column_visibility(int col); #endif /*_PRINTCELL_LIST_H_*/ diff --git a/apps/plugins/lib/wrappers.h b/apps/plugins/lib/wrappers.h index b6fbd51a39..761854fa05 100644 --- a/apps/plugins/lib/wrappers.h +++ b/apps/plugins/lib/wrappers.h @@ -53,6 +53,7 @@ #define strlen rb->strlen #define strlcpy rb->strlcpy #define strrchr rb->strrchr +#define fix_path_part rb->fix_path_part #endif diff --git a/apps/plugins/lib/xlcd_scroll.c b/apps/plugins/lib/xlcd_scroll.c index 5ac4a366e8..906f4eaae1 100644 --- a/apps/plugins/lib/xlcd_scroll.c +++ b/apps/plugins/lib/xlcd_scroll.c @@ -30,7 +30,7 @@ static const unsigned short patterns[4] = {0xFFFF, 0xFF00, 0x00FF, 0x0000}; #endif -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE void xlcd_scroll_left(int count) { /*size_t dst_stride;*/ @@ -668,4 +668,4 @@ void xlcd_scroll_down(int count) } #endif /* LCD_PIXELFORMAT, LCD_DEPTH */ -#endif /* defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE */ +#endif /* LCD_STRIDEFORMAT == VERTICAL_STRIDE */ diff --git a/apps/plugins/logo.c b/apps/plugins/logo.c index 984a65aa34..9df73a9d0b 100644 --- a/apps/plugins/logo.c +++ b/apps/plugins/logo.c @@ -49,15 +49,27 @@ static const struct button_mapping *plugin_contexts[] /* We use PLA */ #define LP_QUIT PLA_EXIT -#define LP_QUIT2 PLA_CANCEL #define LP_DEC_X PLA_LEFT #define LP_DEC_X_REPEAT PLA_LEFT_REPEAT #define LP_INC_X PLA_RIGHT #define LP_INC_X_REPEAT PLA_RIGHT_REPEAT + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define LP_QUIT2 PLA_UP +#define LP_DEC_Y PLA_SCROLL_BACK +#define LP_DEC_Y_REPEAT PLA_SCROLL_BACK_REPEAT +#define LP_INC_Y PLA_SCROLL_FWD +#define LP_INC_Y_REPEAT PLA_SCROLL_FWD_REPEAT +#else +#define LP_QUIT2 PLA_CANCEL #define LP_DEC_Y PLA_DOWN #define LP_DEC_Y_REPEAT PLA_DOWN_REPEAT #define LP_INC_Y PLA_UP #define LP_INC_Y_REPEAT PLA_UP_REPEAT +#endif + enum plugin_status plugin_start(const void* parameter) { int button; diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c index f42b96b5b3..d341e6b7a5 100644 --- a/apps/plugins/lrcplayer.c +++ b/apps/plugins/lrcplayer.c @@ -643,22 +643,6 @@ static void init_time_tag(void) * /ddd.lrc */ -/* taken from apps/recorder/albumart.c */ -static void fix_filename(char* name) -{ - static const char invalid_chars[] = "*/:<>?\\|"; - - while (1) - { - if (*name == 0) - return; - if (*name == '"') - *name = '\''; - else if (rb->strchr(invalid_chars, *name)) - *name = '_'; - name++; - } -} static bool find_lrc_file_helper(const char *base_dir) { char fname[MAX_PATH]; @@ -678,7 +662,7 @@ static bool find_lrc_file_helper(const char *base_dir) if (current.id3->title && rb->strcmp(names[0], current.id3->title)) { rb->strlcpy(fname, current.id3->title, sizeof(fname)); - fix_filename(fname); + rb->fix_path_part(fname, 0, sizeof(fname) - 1); names[1] = fname; } @@ -2078,8 +2062,7 @@ static int timetag_editor(void) while (!exit) { button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&gui_editor, &button, - LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&gui_editor, &button)) continue; switch (button) @@ -2305,7 +2288,7 @@ static bool lrc_display_menu(void) usb = rb->set_bool("Wipe", &prefs.wipe); break; case LRC_MENU_ALIGN: - usb = rb->set_option("Alignment", &prefs.align, INT, + usb = rb->set_option("Alignment", &prefs.align, RB_INT, align_names, 3, NULL); break; case LRC_MENU_LINE_MODE: @@ -2362,7 +2345,7 @@ static bool lrc_lyrics_menu(void) case LRC_MENU_ENCODING: prefs.encoding++; old_val = prefs.encoding; - usb = rb->set_option("Encoding", &prefs.encoding, INT, + usb = rb->set_option("Encoding", &prefs.encoding, RB_INT, cp_names, NUM_CODEPAGES+1, NULL); if (prefs.encoding != old_val) { @@ -2437,7 +2420,6 @@ static bool lrc_debug_menu(void) { struct simplelist_info info; rb->simplelist_info_init(&info, "Debug Menu", 6, NULL); - info.hide_selection = true; info.scroll_all = true; info.get_name = lrc_debug_data; return rb->simplelist_show_list(&info); @@ -2643,16 +2625,10 @@ static int handle_button(void) ff_rewind(0, false); break; case ACTION_WPS_VOLDOWN: - limit = rb->sound_min(SOUND_VOLUME); - if (--rb->global_settings->volume < limit) - rb->global_settings->volume = limit; - rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + rb->adjust_volume(-1); break; case ACTION_WPS_VOLUP: - limit = rb->sound_max(SOUND_VOLUME); - if (++rb->global_settings->volume > limit) - rb->global_settings->volume = limit; - rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + rb->adjust_volume(1); break; case ACTION_WPS_CONTEXT: ret = LRC_GOTO_EDITOR; diff --git a/apps/plugins/lua/include_lua/playlist.lua b/apps/plugins/lua/include_lua/playlist.lua index e11b30fe7d..dfa839aab1 100644 --- a/apps/plugins/lua/include_lua/playlist.lua +++ b/apps/plugins/lua/include_lua/playlist.lua @@ -28,7 +28,7 @@ rb.playlist_amount = function() return rb.playlist("amount") end rb.playlist_add = function (filename) - return rb.playlist("add", filename) + return rb.playlist("insert_track", filename, rb.PLAYLIST_INSERT_LAST, false, true) end rb.playlist_create = function(dir, filename) return rb.playlist("create", dir, filename) diff --git a/apps/plugins/lua/rbdefines_helper.pl b/apps/plugins/lua/rbdefines_helper.pl index 5fb0946a6a..095ec55515 100755 --- a/apps/plugins/lua/rbdefines_helper.pl +++ b/apps/plugins/lua/rbdefines_helper.pl @@ -55,6 +55,8 @@ if ($def_type eq "rb_defines") { '^PLUGIN(_APPS_|_GAMES_|_)DATA_DIR$', '^ROCKBOX_DIR$', '^STYLE_(NONE|DEFAULT|INVERT|COLORBAR|GRADIENT|COLORED)', + '^CORE_KEYMAP_FILE$', + 'CONTEXT_(STOPSEARCHING|REMOTE|CUSTOM|CUSTOM2|PLUGIN|REMAPPED)$', '^VIEWERS_DATA_DIR$'); } elsif ($def_type eq "sound_defines") { diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c index 6131a479db..070fdb4991 100644 --- a/apps/plugins/lua/rocklib.c +++ b/apps/plugins/lua/rocklib.c @@ -317,13 +317,13 @@ RB_WRAP(splash_scroller) RB_WRAP(playlist) { /* just passes NULL to work with the current playlist */ - enum e_playlist {PLAYL_AMOUNT = 0, PLAYL_ADD, PLAYL_CREATE, + enum e_playlist {PLAYL_AMOUNT = 0, PLAYL_CREATE, PLAYL_START, PLAYL_RESUMETRACK, PLAYL_RESUME, PLAYL_SHUFFLE, PLAYL_SYNC, PLAYL_REMOVEALLTRACKS, PLAYL_INSERTTRACK, PLAYL_INSERTDIRECTORY, PLAYL_INSERTPLAYL, PLAYL_ECOUNT}; - const char *playlist_option[] = {"amount", "add", "create", "start", "resume_track", + const char *playlist_option[] = {"amount", "create", "start", "resume_track", "resume", "shuffle", "sync", "remove_all_tracks", "insert_track", "insert_directory", "insert_playlist", NULL}; @@ -339,10 +339,6 @@ RB_WRAP(playlist) case PLAYL_AMOUNT: result = rb->playlist_amount(); break; - case PLAYL_ADD: - filename = luaL_checkstring(L, 2); - result = rb->playlist_add(filename); - break; case PLAYL_CREATE: dir = luaL_checkstring(L, 2); filename = luaL_checkstring(L, 3); @@ -930,12 +926,6 @@ RB_WRAP(restart_lua) return -1; } -RB_WRAP(show_logo) -{ - rb->show_logo(); - return 0; -} - RB_WRAP(mem_stats) { /* used, allocd, free = rb.mem_stats() */ @@ -1032,7 +1022,6 @@ static const luaL_Reg rocklib[] = /* MISC */ RB_FUNC(restart_lua), - RB_FUNC(show_logo), RB_FUNC(mem_stats), {NULL, NULL} @@ -1090,6 +1079,7 @@ LUALIB_API int luaopen_rock(lua_State *L) RB_CONSTANT(SYS_USB_DISCONNECTED), RB_CONSTANT(SYS_TIMEOUT), RB_CONSTANT(SYS_POWEROFF), + RB_CONSTANT(SYS_REBOOT), RB_CONSTANT(SYS_CHARGER_CONNECTED), RB_CONSTANT(SYS_CHARGER_DISCONNECTED), diff --git a/apps/plugins/lua/rocklib_events.c b/apps/plugins/lua/rocklib_events.c index 1c13a6758f..52e87f3d61 100644 --- a/apps/plugins/lua/rocklib_events.c +++ b/apps/plugins/lua/rocklib_events.c @@ -253,7 +253,9 @@ static int lua_rev_callback(lua_State *L, struct cb_data *evt) lua_pushlightuserdata(L, evt->data); lua_status = lua_resume(L, 2); /* call the saved function */ - if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */ + if (lua_status == LUA_SUCCESS) + lua_settop(L, 0); /* eat any value(s) returned */ + else if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */ luaL_where(L, 1); /* push error string on stack */ return lua_status; @@ -421,7 +423,7 @@ static void init_event_thread(bool init, struct event_data *ev_data) 0, EVENT_THREAD IF_PRIO(, PRIORITY_SYSTEM) - IF_COP(, COP)); + IF_COP(, CPU)); /* Timer is used to poll waiting events */ if (!rb->timer_register(1, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU))) diff --git a/apps/plugins/lua/rocklib_img.c b/apps/plugins/lua/rocklib_img.c index b0ca769ca4..68e5325ce0 100644 --- a/apps/plugins/lua/rocklib_img.c +++ b/apps/plugins/lua/rocklib_img.c @@ -380,7 +380,7 @@ static inline fb_data* rli_get_element(struct rocklua_image* img, int x, int y) pixel_to_native(x, y, &x, &y); -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE /* column major address */ size_t data_address = (stride * (x - 1)) + (y - 1); diff --git a/apps/plugins/main_menu_config.c b/apps/plugins/main_menu_config.c index 9f651094b1..a5488ed2c0 100644 --- a/apps/plugins/main_menu_config.c +++ b/apps/plugins/main_menu_config.c @@ -188,7 +188,7 @@ enum plugin_status plugin_start(const void* parameter) { cur_sel = rb->gui_synclist_get_sel_pos(&list); action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&list,&action,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&list, &action)) continue; switch (action) diff --git a/apps/plugins/matrix.c b/apps/plugins/matrix.c index 1b2f6d465a..6e96aae10c 100644 --- a/apps/plugins/matrix.c +++ b/apps/plugins/matrix.c @@ -52,6 +52,14 @@ /* this set the context to use with PLA */ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define MATRIX_EXIT2 PLA_UP +#else +#define MATRIX_EXIT2 PLA_CANCEL +#endif + #ifdef HAVE_SCROLLWHEEL #define MATRIX_SLEEP_MORE PLA_SCROLL_BACK #define MATRIX_SLEEP_MORE_REPEAT PLA_SCROLL_BACK_REPEAT @@ -65,7 +73,6 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #endif /* HAVE_SCROLLWHEEL */ #define MATRIX_PAUSE PLA_SELECT #define MATRIX_EXIT PLA_EXIT -#define MATRIX_EXIT2 PLA_CANCEL #define SLEEP HZ/50 diff --git a/apps/plugins/maze.c b/apps/plugins/maze.c index 20d5c82495..ebb83ab15c 100644 --- a/apps/plugins/maze.c +++ b/apps/plugins/maze.c @@ -37,11 +37,14 @@ /* key assignments */ -#if (CONFIG_KEYPAD == IPOD_3G_PAD) +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) # define MAZE_NEW (BUTTON_SELECT | BUTTON_REPEAT) # define MAZE_NEW_PRE BUTTON_SELECT # define MAZE_QUIT BUTTON_MENU -# define MAZE_SOLVE (BUTTON_SELECT | BUTTON_PLAY) +# define MAZE_SOLVE (BUTTON_SELECT | BUTTON_REL) +# define MAZE_SOLVE_PRE BUTTON_SELECT # define MAZE_RIGHT BUTTON_RIGHT # define MAZE_RIGHT_REPEAT BUTTON_RIGHT|BUTTON_REPEAT # define MAZE_LEFT BUTTON_LEFT @@ -491,17 +494,15 @@ static void maze_move_player_left(struct maze* maze) enum plugin_status plugin_start(const void* parameter) { int button; -#ifdef MAZE_NEW_PRE +#if defined(MAZE_NEW_PRE) || defined(MAZE_SOLVE_PRE) int lastbutton = BUTTON_NONE; #endif int quit = 0; struct maze maze; (void)parameter; -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif /* Seed the RNG */ rb->srand(*rb->current_tick); @@ -546,6 +547,10 @@ enum plugin_status plugin_start(const void* parameter) maze_draw(&maze, rb->screens[i]); break; case MAZE_SOLVE: +#ifdef MAZE_SOLVE_PRE + if(lastbutton != MAZE_SOLVE_PRE) + break; +#endif maze_solve(&maze); FOR_NB_SCREENS(i) maze_draw(&maze, rb->screens[i]); @@ -585,14 +590,13 @@ enum plugin_status plugin_start(const void* parameter) } break; } -#ifdef MAZE_NEW_PRE +#if defined(MAZE_NEW_PRE) || defined(MAZE_SOLVE_PRE) if( button != BUTTON_NONE ) lastbutton = button; #endif } /* Turn on backlight timeout (revert to settings) */ -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + return ((quit == 1) ? PLUGIN_OK : PLUGIN_USB_CONNECTED); } diff --git a/apps/plugins/mazezam.c b/apps/plugins/mazezam.c index cd7b6e22a8..1183f8f502 100644 --- a/apps/plugins/mazezam.c +++ b/apps/plugins/mazezam.c @@ -27,7 +27,9 @@ /* Include standard plugin macro */ -#if (CONFIG_KEYPAD == IPOD_3G_PAD) +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) # define MAZEZAM_MENU BUTTON_MENU # define MAZEZAM_RIGHT BUTTON_RIGHT # define MAZEZAM_LEFT BUTTON_LEFT @@ -256,9 +258,7 @@ static void store_lcd_settings(void) ******************************************************************************/ static void restore_lcd_settings(void) { /* Turn on backlight timeout (revert to settings) */ -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif /* Restore the old settings */ #if LCD_DEPTH > 1 @@ -272,10 +272,9 @@ static void restore_lcd_settings(void) { * Adjust the LCD settings to suit MazezaM levels ******************************************************************************/ static void plugin_lcd_settings(void) { -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + /* Set the new settings */ #ifdef HAVE_LCD_COLOR rb->lcd_set_background(MAZEZAM_BG_COLOR); diff --git a/apps/plugins/metronome.c b/apps/plugins/metronome.c index 157d116ff9..9d61c067fd 100644 --- a/apps/plugins/metronome.c +++ b/apps/plugins/metronome.c @@ -82,7 +82,13 @@ enum metronome_errors #define MET_SYNC #endif +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define METRONOME_QUIT PLA_UP +#else #define METRONOME_QUIT PLA_EXIT +#endif #ifdef HAVE_SCROLLWHEEL #define METRONOME_VOL_UP PLA_SCROLL_FWD diff --git a/apps/plugins/mikmod/mikmod.c b/apps/plugins/mikmod/mikmod.c index 6622b5fdb6..5179848549 100644 --- a/apps/plugins/mikmod/mikmod.c +++ b/apps/plugins/mikmod/mikmod.c @@ -623,7 +623,7 @@ static int settings_menu(void) break; case 6: - rb->set_option(rb->str(LANG_MIKMOD_SAMPLERATE), &(settings.sample_rate), INT, sr_names, + rb->set_option(rb->str(LANG_MIKMOD_SAMPLERATE), &(settings.sample_rate), RB_INT, sr_names, HW_NUM_FREQ, NULL); applysettings(); break; @@ -710,7 +710,6 @@ static void mm_errorhandler(void) static int playfile(char* filename) { - int vol = 0; int button; int retval = PLUGIN_OK; bool changingpos = false; @@ -789,13 +788,8 @@ static int playfile(char* filename) } break; } - vol = rb->global_settings->volume; - if (vol < rb->sound_max(SOUND_VOLUME)) - { - vol++; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } + + rb->adjust_volume(1); break; case ACTION_WPS_VOLDOWN: @@ -808,13 +802,8 @@ static int playfile(char* filename) } break; } - vol = rb->global_settings->volume; - if (vol > rb->sound_min(SOUND_VOLUME)) - { - vol--; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } + + rb->adjust_volume(-1); break; case ACTION_WPS_SKIPPREV: diff --git a/apps/plugins/mosaique.c b/apps/plugins/mosaique.c index caf5346dc5..cf3f42521a 100644 --- a/apps/plugins/mosaique.c +++ b/apps/plugins/mosaique.c @@ -37,10 +37,17 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #define MOSAIQUE_QUIT PLA_EXIT -#define MOSAIQUE_QUIT2 PLA_CANCEL -#define MOSAIQUE_SPEED PLA_UP +#define MOSAIQUE_SPEED PLA_RIGHT #define MOSAIQUE_RESTART PLA_SELECT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define MOSAIQUE_QUIT2 PLA_UP +#else +#define MOSAIQUE_QUIT2 PLA_CANCEL +#endif + enum plugin_status plugin_start(const void* parameter) { int button; diff --git a/apps/plugins/mpegplayer/libmpeg2/idct_arm.S b/apps/plugins/mpegplayer/libmpeg2/idct_arm.S index 97a87a8b59..90eb5031c7 100644 --- a/apps/plugins/mpegplayer/libmpeg2/idct_arm.S +++ b/apps/plugins/mpegplayer/libmpeg2/idct_arm.S @@ -43,8 +43,8 @@ ldrsh r7, [r0, #12] /* d2 */ ldrsh r8, [r0, #14] /* d3 */ orrs r9, r2, r3 - orreqs r9, r4, r5 - orreqs r9, r6, r7 + orrseq r9, r4, r5 + orrseq r9, r6, r7 cmpeq r8, #0 bne 2f mov r1, r1, asl #15 @@ -320,7 +320,7 @@ mpeg2_idct_copy: mpeg2_idct_add: cmp r0, #129 mov r0, r1 - ldreqsh r1, [r0, #0] + ldrsheq r1, [r0, #0] bne 1f and r1, r1, #0x70 cmp r1, #0x40 diff --git a/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S b/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S index dc53cbd7bd..a259721410 100644 --- a/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S +++ b/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S @@ -19,6 +19,7 @@ * ****************************************************************************/ +#include "config.h" .global mpeg2_idct_copy .type mpeg2_idct_copy, %function @@ -228,7 +229,7 @@ mpeg2_idct_copy: mpeg2_idct_add: cmp r0, #129 mov r0, r1 - ldreqsh r1, [r0, #0] + ldrsheq r1, [r0, #0] bne 1f and r1, r1, #0x70 cmp r1, #0x40 @@ -260,7 +261,7 @@ mpeg2_idct_add: strd r4, [r1] @ r4, r5 add r1, r1, r2 cmp r0, r3 - ldrlod r8, [r1] @ r8, r9 + ldrdlo r8, [r1] @ r8, r9 blo 2b ldmfd sp!, {r4-r11, pc} @@ -291,7 +292,7 @@ mpeg2_idct_add: strd r0, [r2] @ r0, r1 add r2, r2, r3 cmp r2, r12 - ldrlod r0, [r2] @ r0, r1 + ldrdlo r0, [r2] @ r0, r1 blo 4b ldmfd sp!, {r4, pc} diff --git a/apps/plugins/mpegplayer/mpeg_misc.c b/apps/plugins/mpegplayer/mpeg_misc.c index c85285f4f8..31f0644212 100644 --- a/apps/plugins/mpegplayer/mpeg_misc.c +++ b/apps/plugins/mpegplayer/mpeg_misc.c @@ -192,6 +192,7 @@ int mpeg_sysevent_callback(int btn, { case SYS_USB_CONNECTED: case SYS_POWEROFF: + case SYS_REBOOT: mpeg_sysevent_id = btn; return ACTION_STD_CANCEL; } diff --git a/apps/plugins/mpegplayer/mpeg_misc.h b/apps/plugins/mpegplayer/mpeg_misc.h index e04db0e19d..233b815493 100644 --- a/apps/plugins/mpegplayer/mpeg_misc.h +++ b/apps/plugins/mpegplayer/mpeg_misc.h @@ -53,12 +53,14 @@ enum state_enum #define CMP_3_CONST(_a, _b) \ ({ int _x; \ asm volatile ( \ + BEGIN_ARM_ASM_SYNTAX_UNIFIED \ "ldrb %[x], [%[a], #0] \n" \ "eors %[x], %[x], %[b0] \n" \ - "ldreqb %[x], [%[a], #1] \n" \ - "eoreqs %[x], %[x], %[b1] \n" \ - "ldreqb %[x], [%[a], #2] \n" \ - "eoreqs %[x], %[x], %[b2] \n" \ + "ldrbeq %[x], [%[a], #1] \n" \ + "eorseq %[x], %[x], %[b1] \n" \ + "ldrbeq %[x], [%[a], #2] \n" \ + "eorseq %[x], %[x], %[b2] \n" \ + END_ARM_ASM_SYNTAX_UNIFIED \ : [x]"=&r"(_x) \ : [a]"r"(_a), \ [b0]"i"(((_b) >> 24) & 0xff), \ @@ -70,14 +72,16 @@ enum state_enum #define CMP_4_CONST(_a, _b) \ ({ int _x; \ asm volatile ( \ + BEGIN_ARM_ASM_SYNTAX_UNIFIED \ "ldrb %[x], [%[a], #0] \n" \ "eors %[x], %[x], %[b0] \n" \ - "ldreqb %[x], [%[a], #1] \n" \ - "eoreqs %[x], %[x], %[b1] \n" \ - "ldreqb %[x], [%[a], #2] \n" \ - "eoreqs %[x], %[x], %[b2] \n" \ - "ldreqb %[x], [%[a], #3] \n" \ - "eoreqs %[x], %[x], %[b3] \n" \ + "ldrbeq %[x], [%[a], #1] \n" \ + "eorseq %[x], %[x], %[b1] \n" \ + "ldrbeq %[x], [%[a], #2] \n" \ + "eorseq %[x], %[x], %[b2] \n" \ + "ldrbeq %[x], [%[a], #3] \n" \ + "eorseq %[x], %[x], %[b3] \n" \ + END_ARM_ASM_SYNTAX_UNIFIED \ : [x]"=&r"(_x) \ : [a]"r"(_a), \ [b0]"i"(((_b) >> 24) & 0xff), \ diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index c904de466d..6464f37217 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -1199,7 +1199,7 @@ static void display_options(void) #if MPEG_OPTION_DITHERING_ENABLED case MPEG_OPTION_DITHERING: result = (settings.displayoptions & LCD_YUV_DITHER) ? 1 : 0; - mpeg_set_option(rb->str(LANG_DITHERING), &result, INT, noyes, 2, NULL); + mpeg_set_option(rb->str(LANG_DITHERING), &result, RB_INT, noyes, 2, NULL); settings.displayoptions = (settings.displayoptions & ~LCD_YUV_DITHER) | ((result != 0) ? LCD_YUV_DITHER : 0); @@ -1208,17 +1208,17 @@ static void display_options(void) #endif /* MPEG_OPTION_DITHERING_ENABLED */ case MPEG_OPTION_DISPLAY_FPS: - mpeg_set_option(rb->str(LANG_DISPLAY_FPS), &settings.showfps, INT, + mpeg_set_option(rb->str(LANG_DISPLAY_FPS), &settings.showfps, RB_INT, noyes, 2, NULL); break; case MPEG_OPTION_LIMIT_FPS: - mpeg_set_option(rb->str(LANG_LIMIT_FPS), &settings.limitfps, INT, + mpeg_set_option(rb->str(LANG_LIMIT_FPS), &settings.limitfps, RB_INT, noyes, 2, NULL); break; case MPEG_OPTION_SKIP_FRAMES: - mpeg_set_option(rb->str(LANG_SKIP_FRAMES), &settings.skipframes, INT, + mpeg_set_option(rb->str(LANG_SKIP_FRAMES), &settings.skipframes, RB_INT, noyes, 2, NULL); break; @@ -1269,31 +1269,31 @@ static void audio_options(void) switch (result) { case MPEG_AUDIO_TONE_CONTROLS: - mpeg_set_option(rb->str(LANG_TONE_CONTROLS), &settings.tone_controls, INT, + mpeg_set_option(rb->str(LANG_TONE_CONTROLS), &settings.tone_controls, RB_INT, globaloff, 2, NULL); sync_audio_setting(result, false); break; case MPEG_AUDIO_CHANNEL_MODES: mpeg_set_option(rb->str(LANG_CHANNEL_CONFIGURATION), &settings.channel_modes, - INT, globaloff, 2, NULL); + RB_INT, globaloff, 2, NULL); sync_audio_setting(result, false); break; case MPEG_AUDIO_CROSSFEED: - mpeg_set_option(rb->str(LANG_CROSSFEED), &settings.crossfeed, INT, + mpeg_set_option(rb->str(LANG_CROSSFEED), &settings.crossfeed, RB_INT, globaloff, 2, NULL); sync_audio_setting(result, false); break; case MPEG_AUDIO_EQUALIZER: - mpeg_set_option(rb->str(LANG_EQUALIZER), &settings.equalizer, INT, + mpeg_set_option(rb->str(LANG_EQUALIZER), &settings.equalizer, RB_INT, globaloff, 2, NULL); sync_audio_setting(result, false); break; case MPEG_AUDIO_DITHERING: - mpeg_set_option(rb->str(LANG_DITHERING), &settings.dithering, INT, + mpeg_set_option(rb->str(LANG_DITHERING), &settings.dithering, RB_INT, globaloff, 2, NULL); sync_audio_setting(result, false); break; @@ -1322,7 +1322,7 @@ static void resume_options(void) }; mpeg_set_option(rb->str(LANG_MENU_RESUME_OPTIONS), &settings.resume_options, - INT, items, MPEG_RESUME_NUM_OPTIONS, NULL); + RB_INT, items, MPEG_RESUME_NUM_OPTIONS, NULL); } static void clear_resume_count(void) @@ -1369,7 +1369,7 @@ static void mpeg_settings(void) case MPEG_SETTING_PLAY_MODE: mpeg_set_option(rb->str(LANG_MENU_PLAY_MODE), &settings.play_mode, - INT, singleall, 2, NULL); + RB_INT, singleall, 2, NULL); break; case MPEG_SETTING_CLEAR_RESUMES: diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index e66b4df146..654a348959 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -1215,10 +1215,9 @@ static void osd_lcd_enable_hook(unsigned short id, void* param) static void osdbacklight_hw_on_video_mode(bool video_on) { if (video_on) { -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook); #endif @@ -1226,10 +1225,8 @@ static void osdbacklight_hw_on_video_mode(bool video_on) #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) rb->add_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook); #endif -#ifdef HAVE_BACKLIGHT /* Revert to user's backlight settings */ backlight_use_settings(); -#endif } } diff --git a/apps/plugins/multiboot_select.c b/apps/plugins/multiboot_select.c new file mode 100644 index 0000000000..63babbb2c1 --- /dev/null +++ b/apps/plugins/multiboot_select.c @@ -0,0 +1,346 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 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. + * + ****************************************************************************/ + +#include "plugin.h" + +/* should be more than enough */ +#define MAX_ROOTS 128 + +static enum plugin_status plugin_status = PLUGIN_OK; +static char tmpbuf[MAX_PATH+1]; +static char tmpbuf2[MAX_PATH+1]; +static char cur_root[MAX_PATH]; +static char roots[MAX_ROOTS][MAX_PATH]; +static int nroots; + +/* Read a redirect file and return the path */ +static char* read_redirect_file(const char* filename) +{ + int fd = rb->open(filename, O_RDONLY); + if(fd < 0) + return NULL; + + ssize_t ret = rb->read(fd, tmpbuf, sizeof(tmpbuf)); + rb->close(fd); + if(ret < 0 || (size_t)ret >= sizeof(tmpbuf)) + return NULL; + + /* relative paths are ignored */ + if(tmpbuf[0] != '/') + ret = 0; + + /* remove trailing control chars (newlines etc.) */ + for(ssize_t i = ret - 1; i >= 0 && tmpbuf[i] < 0x20; --i) + tmpbuf[i] = '\0'; + + return tmpbuf; +} + +/* Search for a redirect file, like get_redirect_dir() */ +static const char* read_redirect(void) +{ + for(int vol = NUM_VOLUMES-1; vol >= MULTIBOOT_MIN_VOLUME; --vol) { + rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); + const char* path = read_redirect_file(tmpbuf); + if(path && path[0] == '/') { + /* prepend the volume because that's what we expect */ + rb->snprintf(tmpbuf2, sizeof(tmpbuf2), "/<%d>%s", vol, path); + return tmpbuf2; + } + } + + tmpbuf[0] = '\0'; + return tmpbuf; +} + +/* Remove all redirect files except for the one on volume 'exclude_vol' */ +static int clear_redirect(int exclude_vol) +{ + int ret = 0; + for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) { + if(vol == exclude_vol) + continue; + + rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); + if(rb->file_exists(tmpbuf) && rb->remove(tmpbuf) < 0) + ret = -1; + } + + return ret; +} + +/* Save a path to the redirect file */ +static int write_redirect(const char* abspath) +{ + /* get the volume (required) */ + const char* path = abspath; + int vol = rb->path_strip_volume(abspath, &path, false); + if(path == abspath) + return -1; + + /* remove all other redirect files */ + if(clear_redirect(vol)) + return -1; + + /* open the redirect file on the same volume */ + rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); + int fd = rb->open(tmpbuf, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if(fd < 0) + return fd; + + size_t pathlen = rb->strlen(path); + ssize_t ret = rb->write(fd, path, pathlen); + if(ret < 0 || (size_t)ret != pathlen) { + rb->close(fd); + return -1; + } + + ret = rb->write(fd, "\n", 1); + rb->close(fd); + if(ret != 1) + return -1; + + return 0; +} + +/* Check if the firmware file is valid + * TODO: this should at least check model number or something */ +static bool check_firmware(const char* path) +{ + return rb->file_exists(path); +} + +static int root_compare(const void* a, const void* b) +{ + const char* as = a; + const char* bs = b; + return rb->strcmp(as, bs); +} + +/* Scan the filesystem for possible redirect targets. To prevent this from + * taking too long we only check the directories in the root of each volume + * look check for a rockbox firmware binary underneath /dir/.rockbox. If it + * exists then we report /<vol#>/dir as a root. */ +static int find_roots(void) +{ + const char* bootdir = *BOOTDIR == '/' ? &BOOTDIR[1] : BOOTDIR; + nroots = 0; + + for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) { + rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>", vol); + + /* try to open the volume root; ignore failures since they'll + * occur if the volume is unmounted */ + DIR* dir = rb->opendir(tmpbuf); + if(!dir) + continue; + + struct dirent* ent; + while((ent = rb->readdir(dir))) { + int r = rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s/%s/%s", + vol, ent->d_name, bootdir, BOOTFILE); + if(r < 0 || (size_t)r >= sizeof(tmpbuf)) + continue; + + if(check_firmware(tmpbuf)) { + rb->snprintf(roots[nroots], MAX_PATH, "/<%d>/%s", + vol, ent->d_name); + nroots += 1; + + /* quit if we hit the maximum */ + if(nroots == MAX_ROOTS) { + vol = NUM_VOLUMES; + break; + } + } + } + + rb->closedir(dir); + } + + rb->qsort(roots, nroots, MAX_PATH, root_compare); + return nroots; +} + +static const char* picker_get_name_cb(int selected, void* data, + char* buffer, size_t buffer_len) +{ + (void)data; + (void)buffer; + (void)buffer_len; + return roots[selected]; +} + +static int picker_action_cb(int action, struct gui_synclist* lists) +{ + (void)lists; + if(action == ACTION_STD_OK) + action = ACTION_STD_CANCEL; + return action; +} + +static bool show_picker_menu(int* selection) +{ + struct simplelist_info info; + rb->simplelist_info_init(&info, "Select new root", nroots, NULL); + info.selection = *selection; + info.get_name = picker_get_name_cb; + info.action_callback = picker_action_cb; + if(rb->simplelist_show_list(&info)) + return true; + + if(0 <= info.selection && info.selection < nroots) + *selection = info.selection; + + return false; +} + +enum { + MB_SELECT_ROOT, + MB_CLEAR_REDIRECT, + MB_CURRENT_ROOT, + MB_SAVE_AND_EXIT, + MB_SAVE_AND_REBOOT, + MB_EXIT, + MB_NUM_ITEMS, +}; + +static const char* menu_get_name_cb(int selected, void* data, + char* buffer, size_t buffer_len) +{ + (void)data; + + switch(selected) { + case MB_SELECT_ROOT: + return "Select root"; + + case MB_CLEAR_REDIRECT: + return "Clear redirect"; + + case MB_CURRENT_ROOT: + if(cur_root[0]) { + rb->snprintf(buffer, buffer_len, "Using root: %s", cur_root); + return buffer; + } + + return "Using default root"; + + case MB_SAVE_AND_EXIT: + return "Save and Exit"; + + case MB_SAVE_AND_REBOOT: + return "Save and Reboot"; + + case MB_EXIT: + default: + return "Exit"; + } +} + +static int menu_action_cb(int action, struct gui_synclist* lists) +{ + int selected = rb->gui_synclist_get_sel_pos(lists); + + if(action != ACTION_STD_OK) + return action; + + switch(selected) { + case MB_SELECT_ROOT: { + if(find_roots() <= 0) { + rb->splashf(3*HZ, "No roots found"); + break; + } + + int root_sel = nroots-1; + for(; root_sel > 0; --root_sel) + if(!rb->strcmp(roots[root_sel], cur_root)) + break; + + if(show_picker_menu(&root_sel)) + return SYS_USB_CONNECTED; + + rb->strcpy(cur_root, roots[root_sel]); + action = ACTION_REDRAW; + } break; + + case MB_CLEAR_REDIRECT: + if(cur_root[0]) { + memset(cur_root, 0, sizeof(cur_root)); + rb->splashf(HZ, "Cleared redirect"); + } + + action = ACTION_REDRAW; + break; + + case MB_SAVE_AND_REBOOT: + case MB_SAVE_AND_EXIT: { + int ret; + if(cur_root[0]) + ret = write_redirect(cur_root); + else + ret = clear_redirect(-1); + + if(ret < 0) + rb->splashf(3*HZ, "Couldn't save settings"); + else + rb->splashf(HZ, "Settings saved"); + + action = ACTION_STD_CANCEL; + plugin_status = (ret < 0) ? PLUGIN_ERROR : PLUGIN_OK; + + if(ret >= 0 && selected == MB_SAVE_AND_REBOOT) + rb->sys_reboot(); + } break; + + case MB_EXIT: + return ACTION_STD_CANCEL; + + default: + action = ACTION_REDRAW; + break; + } + + return action; +} + +static bool show_menu(void) +{ + struct simplelist_info info; + rb->simplelist_info_init(&info, "Multiboot Settings", MB_NUM_ITEMS, NULL); + info.get_name = menu_get_name_cb; + info.action_callback = menu_action_cb; + return rb->simplelist_show_list(&info); +} + +enum plugin_status plugin_start(const void* param) +{ + (void)param; + + /* load the current root */ + const char* myroot = read_redirect(); + rb->strcpy(cur_root, myroot); + + /* display the menu */ + if(show_menu()) + return PLUGIN_USB_CONNECTED; + else + return plugin_status; +} diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c index 3a0c34d8d6..b608aff789 100644 --- a/apps/plugins/open_plugins.c +++ b/apps/plugins/open_plugins.c @@ -87,14 +87,23 @@ static size_t pathbasename(const char *name, const char **nameptr) *nameptr = q; return r - q; } +static int op_entry_checksum(void) +{ + if (op_entry.checksum != open_plugin_csum + + (op_entry.lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY)) + { + return 0; + } + return 1; +} static bool op_entry_read(int fd, int selected_item, off_t data_sz) { rb->memset(&op_entry, 0, op_entry_sz); op_entry.lang_id = -1; - return ((selected_item >= 0) && + return ((selected_item >= 0) && (fd >= 0) && (rb->lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) && - (rb->read(fd, &op_entry, data_sz) == data_sz)); + (rb->read(fd, &op_entry, data_sz) == data_sz) && op_entry_checksum() > 0); } static bool op_entry_read_name(int fd, int selected_item) @@ -102,15 +111,6 @@ 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; @@ -174,7 +174,8 @@ failure: static void op_entry_set_checksum(void) { - op_entry.checksum = open_plugin_csum; + op_entry.checksum = open_plugin_csum + + (op_entry.lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY); } static void op_entry_set_name(void) @@ -188,17 +189,20 @@ static void op_entry_set_name(void) static int op_entry_set_path(void) { int ret = 0; - struct browse_context browse; char tmp_buf[OPEN_PLUGIN_BUFSZ+1]; if (op_entry.path[0] == '\0') rb->strcpy(op_entry.path, PLUGIN_DIR"/"); - rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, rb->str(LANG_ADD), - Icon_Plugin, op_entry.path, NULL); - - browse.buf = tmp_buf; - browse.bufsize = OPEN_PLUGIN_BUFSZ; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER, + .title = rb->str(LANG_ADD), + .icon = Icon_Plugin, + .root = op_entry.path, + .buf = tmp_buf, + .bufsize = sizeof(tmp_buf), + }; if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) { @@ -212,7 +216,6 @@ static int op_entry_set_path(void) static int op_entry_set_param_path(void) { int ret = 0; - struct browse_context browse; char tmp_buf[OPEN_PLUGIN_BUFSZ+1]; if (op_entry.param[0] == '\0') @@ -220,11 +223,15 @@ static int op_entry_set_param_path(void) else rb->strcpy(tmp_buf, op_entry.param); - rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "", - Icon_Plugin, tmp_buf, NULL); - - browse.buf = tmp_buf; - browse.bufsize = OPEN_PLUGIN_BUFSZ; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER, + .title = rb->str(LANG_PARAMETER), + .icon = Icon_Plugin, + .root = tmp_buf, + .buf = tmp_buf, + .bufsize = sizeof(tmp_buf), + }; if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) { @@ -405,6 +412,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha { rb->close(fd_tmp); rb->close(fd_dat); + fd_dat = -1; rb->remove(OPEN_PLUGIN_DAT); rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT); } @@ -461,12 +469,12 @@ static void op_entry_remove(int selection) static void op_entry_remove_empty(void) { bool resave = false; - if (fd_dat && rb->lseek(fd_dat, 0, SEEK_SET) == 0) + if (fd_dat >= 0 && rb->lseek(fd_dat, 0, SEEK_SET) == 0) { while (resave == false && rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz) { - if (op_entry.hash == 0) + if (op_entry.hash == 0 || !op_entry_checksum()) resave = true; } } @@ -482,6 +490,7 @@ static void op_entry_remove_empty(void) { rb->close(fd_tmp); rb->close(fd_dat); + fd_dat = -1; rb->remove(OPEN_PLUGIN_DAT); rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT); } @@ -623,7 +632,6 @@ static void synclist_set(char* menu_id, int selection, int items, int sel_size) 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, selection); list_voice_cb(selection, menu_id); } @@ -682,7 +690,7 @@ static void edit_menu(int selection) { action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &action)) continue; selected_item = rb->gui_synclist_get_sel_pos(&lists); switch (action) @@ -840,6 +848,12 @@ reopen_datfile: }/* OP_EXT */ } + for (int i = items - 1; i > 0 && !exit; i--) + { + if (!op_entry_read(fd_dat, i, op_entry_sz)) + items--; + } + if (items < 1 && !exit) { char* cur_filename = rb->plugin_get_current_filename(); @@ -865,7 +879,7 @@ reopen_datfile: { action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &action)) continue; selection = rb->gui_synclist_get_sel_pos(&lists); switch (action) @@ -892,6 +906,7 @@ reopen_datfile: } break; case ACTION_STD_CANCEL: + case ACTION_STD_MENU: { selection = -2; exit = true; diff --git a/apps/plugins/oscilloscope.c b/apps/plugins/oscilloscope.c index ae84e14f7f..00d03fb03e 100644 --- a/apps/plugins/oscilloscope.c +++ b/apps/plugins/oscilloscope.c @@ -47,12 +47,14 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define OSCILLOSCOPE_QUIT (BUTTON_SELECT | BUTTON_MENU) -#define OSCILLOSCOPE_DRAWMODE (BUTTON_SELECT | BUTTON_PLAY) +#define OSCILLOSCOPE_QUIT BUTTON_MENU +#define OSCILLOSCOPE_GRAPHMODE_PRE BUTTON_SELECT +#define OSCILLOSCOPE_GRAPHMODE (BUTTON_SELECT | BUTTON_REL) +#define OSCILLOSCOPE_DRAWMODE_PRE BUTTON_SELECT +#define OSCILLOSCOPE_DRAWMODE (BUTTON_SELECT | BUTTON_REPEAT) #define OSCILLOSCOPE_ADVMODE (BUTTON_SELECT | BUTTON_RIGHT) #define OSCILLOSCOPE_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT) -#define OSCILLOSCOPE_GRAPHMODE BUTTON_MENU -#define OSCILLOSCOPE_PAUSE BUTTON_PLAY +#define OSCILLOSCOPE_PAUSE BUTTON_PLAY | BUTTON_REL #define OSCILLOSCOPE_SPEED_UP BUTTON_RIGHT #define OSCILLOSCOPE_SPEED_DOWN BUTTON_LEFT #define OSCILLOSCOPE_VOL_UP BUTTON_SCROLL_FWD @@ -1939,10 +1941,9 @@ static void osc_cleanup(void) rb->lcd_set_foreground(LCD_DEFAULT_FG); rb->lcd_set_background(LCD_DEFAULT_BG); #endif -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif /* save settings if changed */ if (rb->memcmp(&osc, &osc_disk, sizeof(osc))) @@ -1975,10 +1976,9 @@ static void osc_setup(void) mixer_sampr = rb->mixer_get_frequency(); #endif -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + graphmode_setup(); } diff --git a/apps/plugins/otp.c b/apps/plugins/otp.c index 4d302563fb..356e1e5eb6 100644 --- a/apps/plugins/otp.c +++ b/apps/plugins/otp.c @@ -208,17 +208,16 @@ static int base32_encode(const uint8_t *data, int length, uint8_t *result, static bool browse( char *dst, int dst_size, const char *start ) { - struct browse_context browse; - - rb->browse_context_init(&browse, SHOW_ALL, - BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, - NULL, NOICON, start, NULL); - - browse.buf = dst; - browse.bufsize = dst_size; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, + .icon = Icon_NOICON, + .root = start, + .buf = dst, + .bufsize = dst_size, + }; rb->rockbox_browse(&browse); - return (browse.flags & BROWSE_SELECTED); } diff --git a/apps/plugins/pacbox/pacbox.c b/apps/plugins/pacbox/pacbox.c index 71c9751cad..b306503862 100755 --- a/apps/plugins/pacbox/pacbox.c +++ b/apps/plugins/pacbox/pacbox.c @@ -266,7 +266,7 @@ static bool pacbox_menu(void) { case PBMI_DIFFICULTY: new_setting=settings.difficulty; - rb->set_option("Difficulty", &new_setting, INT, + rb->set_option("Difficulty", &new_setting, RB_INT, difficulty_options , 2, NULL); if (new_setting != settings.difficulty) { settings.difficulty=new_setting; @@ -275,7 +275,7 @@ static bool pacbox_menu(void) break; case PBMI_PACMEN_PER_GAME: new_setting=settings.numlives; - rb->set_option("Pacmen Per Game", &new_setting, INT, + rb->set_option("Pacmen Per Game", &new_setting, RB_INT, numlives_options , 4, NULL); if (new_setting != settings.numlives) { settings.numlives=new_setting; @@ -284,7 +284,7 @@ static bool pacbox_menu(void) break; case PBMI_BONUS_LIFE: new_setting=settings.bonus; - rb->set_option("Bonus Life", &new_setting, INT, + rb->set_option("Bonus Life", &new_setting, RB_INT, bonus_options , 4, NULL); if (new_setting != settings.bonus) { settings.bonus=new_setting; @@ -293,7 +293,7 @@ static bool pacbox_menu(void) break; case PBMI_GHOST_NAMES: new_setting=settings.ghostnames; - rb->set_option("Ghost Names", &new_setting, INT, + rb->set_option("Ghost Names", &new_setting, RB_INT, ghostname_options , 2, NULL); if (new_setting != settings.ghostnames) { settings.ghostnames=new_setting; @@ -301,16 +301,16 @@ static bool pacbox_menu(void) } break; case PBMI_DISPLAY_FPS: - rb->set_option("Display FPS",&settings.showfps,INT, + rb->set_option("Display FPS",&settings.showfps, RB_INT, noyes, 2, NULL); break; case PBMI_SOUND: - rb->set_option("Sound",&settings.sound, INT, + rb->set_option("Sound",&settings.sound, RB_INT, noyes, 2, NULL); break; #ifdef AI case PBMI_AI: - rb->set_option("AI",&settings.ai, INT, + rb->set_option("AI",&settings.ai, RB_INT, noyes, 2, NULL); break; #endif @@ -809,8 +809,8 @@ enum plugin_status plugin_start(const void* parameter) #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + return PLUGIN_OK; } diff --git a/apps/plugins/pdbox/PDa/src/m_glob.c b/apps/plugins/pdbox/PDa/src/m_glob.c index 9a036a984e..43f7ca8510 100644 --- a/apps/plugins/pdbox/PDa/src/m_glob.c +++ b/apps/plugins/pdbox/PDa/src/m_glob.c @@ -16,7 +16,7 @@ void glob_quit(void *dummy); void glob_dsp(void *dummy, t_symbol *s, int argc, t_atom *argv); void glob_meters(void *dummy, t_floatarg f); void glob_key(void *dummy, t_symbol *s, int ac, t_atom *av); -void glob_audiostatus(void *dummy); +void glob_audiostatus(void); void glob_finderror(t_pd *dummy); void glob_audio_properties(t_pd *dummy, t_floatarg flongform); void glob_audio_dialog(t_pd *dummy, t_symbol *s, int argc, t_atom *argv); diff --git a/apps/plugins/pdbox/PDa/src/m_obj.c b/apps/plugins/pdbox/PDa/src/m_obj.c index d06caa12fd..8d4947f202 100644 --- a/apps/plugins/pdbox/PDa/src/m_obj.c +++ b/apps/plugins/pdbox/PDa/src/m_obj.c @@ -272,6 +272,9 @@ static int outlet_eventno; recursion */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" +#if __GNUC__ >= 13 +#pragma GCC diagnostic ignored "-Wdangling-pointer" +#endif void outlet_setstacklim(void) { char c; diff --git a/apps/plugins/pdbox/pdbox.c b/apps/plugins/pdbox/pdbox.c index f21913788b..08236bfeba 100644 --- a/apps/plugins/pdbox/pdbox.c +++ b/apps/plugins/pdbox/pdbox.c @@ -229,7 +229,7 @@ enum plugin_status plugin_start(const void* parameter) 0, /* FIXME Which flags? */ "PD core" IF_PRIO(, PRIORITY_REALTIME) - IF_COP(, COP)); + IF_COP(, CPU)); gui_thread_id = rb->create_thread(&gui_thread, diff --git a/apps/plugins/pegbox.c b/apps/plugins/pegbox.c index cb6361547d..8b88aad052 100644 --- a/apps/plugins/pegbox.c +++ b/apps/plugins/pegbox.c @@ -65,7 +65,7 @@ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) #define PEGBOX_SELECT (BUTTON_SELECT|BUTTON_RIGHT) -#define PEGBOX_QUIT (BUTTON_SELECT|BUTTON_PLAY) +#define PEGBOX_QUIT (BUTTON_SELECT|BUTTON_REPEAT) #define PEGBOX_RESTART (BUTTON_SELECT|BUTTON_LEFT) #define PEGBOX_LVL_UP (BUTTON_SELECT|BUTTON_MENU) #define PEGBOX_UP BUTTON_MENU @@ -74,7 +74,7 @@ #define PEGBOX_LEFT BUTTON_LEFT #define SELECT_TEXT "SELECT+RIGHT" -#define QUIT_TEXT "SELECT+PLAY" +#define QUIT_TEXT "Long SELECT" #define RESTART_TEXT "SELECT+LEFT" #define LVL_UP_TEXT "SELECT+MENU" #define LVL_DOWN_TEXT "-" diff --git a/apps/plugins/periodic_table.c b/apps/plugins/periodic_table.c index b77dd07432..2dd84baacd 100644 --- a/apps/plugins/periodic_table.c +++ b/apps/plugins/periodic_table.c @@ -619,7 +619,6 @@ enum plugin_status plugin_start(const void* parameter) switch (button) { case PERIODIC_KEY_SELECT: - break; case PERIODIC_KEY_MENU: return PLUGIN_OK; break; diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c index bee1f580f7..87ad1a403f 100644 --- a/apps/plugins/pictureflow/pictureflow.c +++ b/apps/plugins/pictureflow/pictureflow.c @@ -33,6 +33,7 @@ #include "lib/grey.h" #include "lib/mylcd.h" #include "lib/feature_wrappers.h" +#include "lib/id3.h" /******************************* Globals ***********************************/ static fb_data *lcd_fb; @@ -44,6 +45,7 @@ static fb_data *lcd_fb; #if PF_PLAYBACK_CAPABLE #include "lib/playback_control.h" +#include "lib/mul_id3.h" #endif #define PF_PREV ACTION_STD_PREV @@ -60,6 +62,8 @@ static fb_data *lcd_fb; #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1) #define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2) +#define PF_SORTING_NEXT (LAST_ACTION_PLACEHOLDER + 3) +#define PF_SORTING_PREV (LAST_ACTION_PLACEHOLDER + 4) #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \ CONFIG_KEYPAD == MPIO_HD300_PAD @@ -149,12 +153,12 @@ const struct button_mapping pf_context_buttons[] = {PF_QUIT, BUTTON_POWER, BUTTON_NONE}, #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ || (CONFIG_KEYPAD == IPOD_3G_PAD) \ - || (CONFIG_KEYPAD == IPOD_4G_PAD) \ - || (CONFIG_KEYPAD == MPIO_HD300_PAD) - {PF_JMP_PREV, BUTTON_LEFT, BUTTON_NONE}, - {PF_JMP_PREV, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, - {PF_JMP, BUTTON_RIGHT, BUTTON_NONE}, - {PF_JMP, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + || (CONFIG_KEYPAD == IPOD_4G_PAD) + {PF_MENU, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, + {PF_QUIT, BUTTON_MENU|BUTTON_REL, BUTTON_MENU}, + {PF_SORTING_NEXT, BUTTON_SELECT|BUTTON_MENU, BUTTON_NONE}, + {PF_SORTING_PREV, BUTTON_SELECT|BUTTON_PLAY, BUTTON_NONE}, +#elif CONFIG_KEYPAD == MPIO_HD300_PAD {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, #elif CONFIG_KEYPAD == IAUDIO_M3_PAD {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE}, @@ -177,6 +181,8 @@ const struct button_mapping pf_context_buttons[] = {PF_JMP, BUTTON_RIGHT, BUTTON_NONE}, {PF_JMP, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, {PF_MENU, BUTTON_POWER|BUTTON_REL, BUTTON_POWER}, + {PF_SORTING_NEXT, BUTTON_VOL_UP, BUTTON_NONE}, + {PF_SORTING_PREV, BUTTON_VOL_DOWN, BUTTON_NONE}, {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER}, {PF_CONTEXT, BUTTON_MENU|BUTTON_REL, BUTTON_MENU}, {PF_TRACKLIST, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, @@ -268,7 +274,6 @@ typedef fb_data pix_t; /* some magic numbers for cache_version. */ #define CACHE_REBUILD 0 -#define CACHE_UPDATE 1 /* Error return values */ #define SUCCESS 0 @@ -278,7 +283,7 @@ typedef fb_data pix_t; #define ERROR_USER_ABORT -4 /* current version for cover cache */ -#define CACHE_VERSION 3 +#define CACHE_VERSION 4 #define CONFIG_VERSION 1 #define CONFIG_FILE "pictureflow.cfg" #define INDEX_HDR "PFID" @@ -300,8 +305,14 @@ struct pf_config_t int cache_version; int show_album_name; + int sort_albums_by; + int year_sort_order; + bool show_year; + bool resize; bool show_fps; + + bool update_albumart; }; struct pf_index_t { @@ -368,6 +379,7 @@ struct slide_cache { struct album_data { int name_idx; /* offset to the album name */ int artist_idx; /* offset to the artist name */ + int year; /* album year */ long artist_seek; /* artist taglist position */ long seek; /* album taglist position */ }; @@ -451,6 +463,31 @@ static char* show_album_name_conf[] = "both bottom", }; +enum sort_albums_by_values { + SORT_BY_ARTIST_AND_NAME = 0, + SORT_BY_ARTIST_AND_YEAR, + SORT_BY_YEAR, + SORT_BY_NAME, + + SORT_VALUES_SIZE +}; +static char* sort_albums_by_conf[] = +{ + "artist + name", + "artist + year", + "year", + "name" +}; +enum year_sort_order_values { + ASCENDING = 0, + DESCENDING +}; +static char* year_sort_order_conf[] = +{ + "ascending", + "descending" +}; + #define MAX_SPACING 40 #define MAX_MARGIN 80 @@ -475,7 +512,13 @@ 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 } + { TYPE_INT, 0, 999999, { .int_p = &aa_cache.inspected }, "art cache inspected", NULL }, + { TYPE_ENUM, 0, 4, { .int_p = &pf_cfg.sort_albums_by }, "sort albums by", + sort_albums_by_conf }, + { TYPE_ENUM, 0, 2, { .int_p = &pf_cfg.year_sort_order }, "year order", + year_sort_order_conf }, + { TYPE_BOOL, 0, 1, { .bool_p = &pf_cfg.show_year }, "show year", NULL }, + { TYPE_BOOL, 0, 1, { .bool_p = &pf_cfg.update_albumart }, "update albumart", NULL } }; #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata)) @@ -496,6 +539,8 @@ static int itilt; static PFreal offsetX; static PFreal offsetY; static int number_of_slides; +static bool is_initial_slide = true; +static bool show_tracks_while_browsing = false; static struct pf_slide_cache pf_sldcache; @@ -516,6 +561,8 @@ static struct pf_index_t pf_idx; static struct pf_track_t pf_tracks; +static struct mp3entry id3; + void reset_track_list(void); static bool thread_is_running; @@ -588,7 +635,7 @@ static inline void buf_ctx_unlock(void) buf_ctx_locked = false; } -static bool check_database(bool prompt) +static bool check_database(void) { bool needwarn = true; int spin = 5; @@ -606,9 +653,7 @@ static bool check_database(bool prompt) needwarn = false; rb->splash(0, ID2P(LANG_TAGCACHE_BUSY)); } - else if (!prompt) - return false; - else if (rb->action_userabort(HZ/5)) + else return false; rb->yield(); @@ -631,9 +676,10 @@ static bool confirm_quit(void) return true; } -static void config_save(int cache_version) +static void config_save(int cache_version, bool update_albumart) { pf_cfg.cache_version = cache_version; + pf_cfg.update_albumart = update_albumart; configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); } @@ -648,9 +694,13 @@ static void config_set_defaults(struct pf_config_t *cfg) cfg->last_album = 0; cfg->backlight_mode = 0; cfg->resize = true; - cfg->cache_version = 0; + cfg->cache_version = CACHE_REBUILD; cfg->show_album_name = (LCD_HEIGHT > 100) - ? ALBUM_NAME_TOP : ALBUM_NAME_BOTTOM; + ? ALBUM_AND_ARTIST_BOTTOM : ALBUM_NAME_BOTTOM; + cfg->sort_albums_by = SORT_BY_ARTIST_AND_NAME; + cfg->year_sort_order = ASCENDING; + cfg->show_year = false; + cfg->update_albumart = false; } static inline PFreal fmul(PFreal a, PFreal b) @@ -920,6 +970,7 @@ const struct custom_format format_transposed = { static const struct button_mapping* get_context_map(int context) { + context &= ~CONTEXT_LOCKED; return pf_contexts[context & ~CONTEXT_PLUGIN]; } @@ -1001,6 +1052,51 @@ static void init_reflect_table(void) (5 * REFLECT_HEIGHT); } + +static int compare_albums (const void *a_v, const void *b_v) +{ + uint32_t artist_a = ((struct album_data *)a_v)->artist_idx; + uint32_t artist_b = ((struct album_data *)b_v)->artist_idx; + + uint32_t album_a = ((struct album_data *)a_v)->name_idx; + uint32_t album_b = ((struct album_data *)b_v)->name_idx; + + int year_a = ((struct album_data *)a_v)->year; + int year_b = ((struct album_data *)b_v)->year; + + switch (pf_cfg.sort_albums_by) + { + case SORT_BY_ARTIST_AND_NAME: + if (artist_a - artist_b == 0) + return (int)(album_a - album_b); + break; + case SORT_BY_ARTIST_AND_YEAR: + if (artist_a - artist_b == 0) + { + if (pf_cfg.year_sort_order == ASCENDING) + return year_a - year_b; + else + return year_b - year_a; + } + break; + case SORT_BY_YEAR: + if (year_a - year_b != 0) + { + if (pf_cfg.year_sort_order == ASCENDING) + return year_a - year_b; + else + return year_b - year_a; + } + break; + case SORT_BY_NAME: + if (album_a - album_b != 0) + return (int)(album_a - album_b); + break; + } + + return (int)(artist_a - artist_b); +} + static int compare_album_artists (const void *a_v, const void *b_v) { uint32_t a = ((struct album_data *)a_v)->artist_idx; @@ -1015,6 +1111,7 @@ static void write_album_index(int idx, int name_idx, pf_idx.album_index[idx].seek = album_seek; pf_idx.album_index[idx].artist_idx = artist_idx; pf_idx.album_index[idx].artist_seek = artist_seek; + pf_idx.album_index[idx].year = 0; } static inline void write_album_entry(struct tagcache_search *tcs, @@ -1044,6 +1141,8 @@ static void write_artist_entry(struct tagcache_search *tcs, static int get_tcs_search_res(int type, struct tagcache_search *tcs, void **buf, size_t *bufsz) { + char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); int ret = SUCCESS; unsigned int l, name_idx = 0; void (*writefn)(struct tagcache_search *, int, unsigned int); @@ -1059,7 +1158,7 @@ static int get_tcs_search_res(int type, struct tagcache_search *tcs, data_size = sizeof(struct album_data); } - while (rb->tagcache_get_next(tcs)) + while (rb->tagcache_get_next(tcs, tcs_buf, tcs_bufsz)) { if (rb->button_get(false) > BUTTON_NONE) { @@ -1099,6 +1198,8 @@ static int get_tcs_search_res(int type, struct tagcache_search *tcs, static int create_album_untagged(struct tagcache_search *tcs, void **buf, size_t *bufsz) { + static char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); int ret = SUCCESS; int album_count = pf_idx.album_ct; /* store existing count */ int total_count = pf_idx.album_ct + pf_idx.artist_ct * 2; @@ -1113,7 +1214,7 @@ static int create_album_untagged(struct tagcache_search *tcs, { rb->tagcache_search_add_filter(tcs, tag_album, pf_idx.album_untagged_seek); - while (rb->tagcache_get_next(tcs)) + while (rb->tagcache_get_next(tcs, tcs_buf, tcs_bufsz)) { if (rb->button_get(false) > BUTTON_NONE) { if (confirm_quit()) @@ -1218,9 +1319,8 @@ static int build_artist_index(struct tagcache_search *tcs, if (res < SUCCESS) return res; - ALIGN_BUFFER(*buf, *bufsz, 4); - /* finalize the artist index */ + ALIGN_BUFFER(*buf, *bufsz, alignof(struct artist_data)); tmp_artist = (struct artist_data*)*buf; for (i = pf_idx.artist_ct - 1; i >= 0; i--) tmp_artist[i] = pf_idx.artist_index[-i]; @@ -1228,7 +1328,6 @@ static int build_artist_index(struct tagcache_search *tcs, pf_idx.artist_index = tmp_artist; /* move buf ptr to end of artist_index */ *buf = pf_idx.artist_index + pf_idx.artist_ct; - ALIGN_BUFFER(*buf, *bufsz, 4); if (res == SUCCESS) { @@ -1242,12 +1341,59 @@ static int build_artist_index(struct tagcache_search *tcs, } +static int assign_album_year(void) +{ + char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); + draw_progressbar(0, pf_idx.album_ct, "Assigning Album Year"); + for (int album_idx = 0; album_idx < pf_idx.album_ct; album_idx++) + { + /* Prevent idle poweroff */ + rb->reset_poweroff_timer(); + + if (rb->button_get(false) > BUTTON_NONE) + { + if (confirm_quit()) + return ERROR_USER_ABORT; + else + { + rb->lcd_clear_display(); + draw_progressbar(album_idx, pf_idx.album_ct, "Assigning Album Year"); + } + } + draw_progressbar(album_idx, pf_idx.album_ct, NULL); + int album_year = 0; + + if (rb->tagcache_search(&tcs, tag_year)) + { + rb->tagcache_search_add_filter(&tcs, tag_album, + pf_idx.album_index[album_idx].seek); + + if (pf_idx.album_index[album_idx].artist_idx >= 0) + rb->tagcache_search_add_filter(&tcs, tag_albumartist, + pf_idx.album_index[album_idx].artist_seek); + + while (rb->tagcache_get_next(&tcs, tcs_buf, tcs_bufsz)) { + int track_year = rb->tagcache_get_numeric(&tcs, tag_year); + if (track_year > album_year) + album_year = track_year; + } + } + rb->tagcache_search_finish(&tcs); + + pf_idx.album_index[album_idx].year = album_year; + } + return SUCCESS; +} + /** Create an index of all artists and albums from the database. Also store the artists and album names so we can access them later. */ static int create_album_index(void) { + static char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); void *buf = pf_idx.buf; size_t buf_size = pf_idx.buf_sz; @@ -1256,14 +1402,14 @@ static int create_album_index(void) int i, j, last, final, retry, res; draw_splashscreen(buf, buf_size); - ALIGN_BUFFER(buf, buf_size, 4); + ALIGN_BUFFER(buf, buf_size, sizeof(long)); -/* Artists */ + /* Artists */ res = build_artist_index(&tcs, &buf, &buf_size); if (res < SUCCESS) return res; -/* Albums */ + /* Albums */ pf_idx.album_ct = 0; pf_idx.album_len =0; pf_idx.album_untagged_idx = 0; @@ -1279,7 +1425,6 @@ static int create_album_index(void) rb->tagcache_search_finish(&tcs); if (res < SUCCESS) return res; - ALIGN_BUFFER(buf, buf_size, 4); /* Build artist list for untagged albums */ res = create_album_untagged(&tcs, &buf, &buf_size); @@ -1287,9 +1432,8 @@ static int create_album_index(void) if (res < SUCCESS) return res; - ALIGN_BUFFER(buf, buf_size, 4); - /* finalize the album index */ + ALIGN_BUFFER(buf, buf_size, alignof(struct album_data)); tmp_album = (struct album_data*)buf; for (i = pf_idx.album_ct - 1; i >= 0; i--) tmp_album[i] = pf_idx.album_index[-i]; @@ -1297,9 +1441,8 @@ static int create_album_index(void) pf_idx.album_index = tmp_album; /* move buf ptr to end of album_index */ buf = pf_idx.album_index + pf_idx.album_ct; - ALIGN_BUFFER(buf, buf_size, 4); -/* Assign indices */ + /* Assign indices */ draw_splashscreen(buf, buf_size); draw_progressbar(0, pf_idx.album_ct, "Assigning Albums"); for (j = 0; j < pf_idx.album_ct; j++) @@ -1328,7 +1471,7 @@ static int create_album_index(void) last = 0; final = pf_idx.artist_ct; retry = 0; - if (rb->tagcache_get_next(&tcs)) + if (rb->tagcache_get_next(&tcs, tcs_buf, tcs_bufsz)) { retry_artist_lookup: @@ -1356,6 +1499,14 @@ retry_artist_lookup: } rb->tagcache_search_finish(&tcs); } + + draw_splashscreen(buf, buf_size); + + res = assign_album_year(); + + if (res < SUCCESS) + return res; + /* sort list order to find duplicates */ rb->qsort(pf_idx.album_index, pf_idx.album_ct, sizeof(struct album_data), compare_album_artists); @@ -1405,11 +1556,13 @@ retry_artist_lookup: } } - ALIGN_BUFFER(buf, buf_size, 4); pf_idx.buf = buf; pf_idx.buf_sz = buf_size; pf_idx.artist_index = 0; + rb->qsort(pf_idx.album_index, pf_idx.album_ct, + sizeof(struct album_data), compare_albums); + return (pf_idx.album_ct > 0) ? 0 : ERROR_NO_ALBUMS; } @@ -1479,7 +1632,6 @@ static int load_album_index(void){ //rb->lseek(fr, sizeof(data) + 1, SEEK_SET); /* artist names */ - ALIGN_BUFFER(buf, buf_size, 4); if (read2buf(fr, buf, data.artist_len) == 0) goto failure; @@ -1488,7 +1640,6 @@ static int load_album_index(void){ buf_size -= data.artist_len; /* album names */ - ALIGN_BUFFER(buf, buf_size, 4); if (read2buf(fr, buf, data.album_len) == 0) goto failure; @@ -1497,7 +1648,7 @@ static int load_album_index(void){ buf_size -= data.album_len; /* index of album names */ - ALIGN_BUFFER(buf, buf_size, 4); + ALIGN_BUFFER(buf, buf_size, alignof(struct album_data)); if (read2buf(fr, buf, album_idx_sz) == 0) goto failure; @@ -1523,6 +1674,9 @@ static int load_album_index(void){ pf_idx.buf = buf; pf_idx.buf_sz = buf_size; + rb->qsort(pf_idx.album_index, pf_idx.album_ct, + sizeof(struct album_data), compare_albums); + return 0; } } @@ -1576,6 +1730,14 @@ static char* get_album_artist(const int slide_index) } +static char* get_slide_name(const int slide_index, bool artist) +{ + if (artist) + return get_album_artist(slide_index); + + return get_album_name(slide_index); +} + /** Return a pointer to the track name of the active album create_track_index has to be called first. @@ -1597,29 +1759,91 @@ static char* get_track_filename(const int track_index) -static int get_album_artist_alpha_prev_index(void) +static int jmp_idx_prev(void) { - char* current_album_artist = get_album_artist(center_index); - for (int i = center_index - 1; i > 0; i-- ) + if (aa_cache.inspected < pf_idx.album_ct) + { +#ifdef USEGSLIB + grey_show(false); + rb->lcd_clear_display(); + rb->lcd_update(); +#endif + rb->splash(HZ*2, rb->str(LANG_WAIT_FOR_CACHE)); +#ifdef USEGSLIB + grey_show(true); +#endif + return center_index; + } + + if (pf_cfg.sort_albums_by == SORT_BY_YEAR) { - int artist_idx = pf_idx.album_index[i].artist_idx; - if(rb->strncmp(pf_idx.artist_names + artist_idx, current_album_artist, 1)) - current_album_artist = pf_idx.artist_names + artist_idx; - while (i > 0 && !rb->strncmp(pf_idx.artist_names + pf_idx.album_index[i-1].artist_idx, current_album_artist, 1)) - i--; - return i; + int current_year = pf_idx.album_index[center_index].year; + + for (int i = center_index - 1; i > 0; i-- ) + { + if(pf_idx.album_index[i].year != current_year) + current_year = pf_idx.album_index[i].year; + while (i > 0) + { + if (pf_idx.album_index[i-1].year != current_year) + break; + i--; + } + return i; + } } + else + { + bool by_artist = pf_cfg.sort_albums_by != SORT_BY_NAME; + char *current_selection = get_slide_name(center_index, by_artist); + + for (int i = center_index - 1; i > 0; i-- ) + { + if(rb->strncmp(get_slide_name(i, by_artist), current_selection, 1)) + current_selection = get_slide_name(i, by_artist); + while (i > 0) + { + if (rb->strncmp(get_slide_name(i-1, by_artist), current_selection, 1)) + break; + i--; + } + return i; + } + } + return 0; } -static int get_album_artist_alpha_next_index(void) +static int jmp_idx_next(void) { - char* current_album_artist = get_album_artist(center_index); - for (int i = center_index + 1; i < pf_idx.album_ct; i++ ) + if (aa_cache.inspected < pf_idx.album_ct) { - int artist_idx = pf_idx.album_index[i].artist_idx; - if(rb->strncmp(pf_idx.artist_names + artist_idx, current_album_artist, 1)) - return i; +#ifdef USEGSLIB + grey_show(false); + rb->lcd_clear_display(); + rb->lcd_update(); +#endif + rb->splash(HZ*2, rb->str(LANG_WAIT_FOR_CACHE)); +#ifdef USEGSLIB + grey_show(true); +#endif + return center_index; + } + + if (pf_cfg.sort_albums_by == SORT_BY_YEAR) + { + int current_year = pf_idx.album_index[center_index].year; + for (int i = center_index + 1; i < pf_idx.album_ct; i++ ) + if(pf_idx.album_index[i].year != current_year) + return i; + } + else + { + bool by_artist = pf_cfg.sort_albums_by != SORT_BY_NAME; + char *current_selection = get_slide_name(center_index, by_artist); + for (int i = center_index + 1; i < pf_idx.album_ct; i++ ) + if(rb->strncmp(get_slide_name(i, by_artist), current_selection, 1)) + return i; } return pf_idx.album_ct - 1; } @@ -1719,17 +1943,10 @@ static int pf_tcs_retrieve_track_title(int string_index, int disc_num, int track if (rb->strcmp(UNTAGGED, tcs.result) == 0) { /* show filename instead of <untaggged> */ - if (!rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename, + if (!rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_virt_basename, file_name, MAX_PATH)) return 0; track_title = file_name; - if (track_title) - { - /* if filename remove the '/' */ - track_title = rb->strrchr(track_title, PATH_SEPCH); - if (track_title) - track_title++; - } } if (!track_title) @@ -1772,6 +1989,8 @@ static int pf_tcs_retrieve_file_name(int fn_idx) */ static void create_track_index(const int slide_index) { + char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); buf_ctx_lock(); if ( slide_index == pf_tracks.cur_idx ) return; @@ -1789,7 +2008,7 @@ static void create_track_index(const int slide_index) int string_index = 0; pf_tracks.count = 0; - while (rb->tagcache_get_next(&tcs)) + while (rb->tagcache_get_next(&tcs, tcs_buf, tcs_bufsz)) { int disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber); int track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber); @@ -1863,15 +2082,12 @@ static inline void free_borrowed_tracks(void) static bool get_albumart_for_index_from_db(const int slide_index, char *buf, int buflen) { - if ( slide_index == -1 ) - { - rb->strlcpy( buf, EMPTY_SLIDE, buflen ); - } - + bool ret; + char tcs_buf[TAGCACHE_BUFSZ]; + const long tcs_bufsz = sizeof(tcs_buf); if (tcs.valid || !rb->tagcache_search(&tcs, tag_filename)) return false; - bool result; /* find the first track of the album */ rb->tagcache_search_add_filter(&tcs, tag_album, pf_idx.album_index[slide_index].seek); @@ -1879,36 +2095,12 @@ static bool get_albumart_for_index_from_db(const int slide_index, char *buf, rb->tagcache_search_add_filter(&tcs, tag_albumartist, pf_idx.album_index[slide_index].artist_seek); - if ( rb->tagcache_get_next(&tcs) ) { - struct mp3entry id3; - int fd; - -#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) - if (rb->tagcache_fill_tags(&id3, tcs.result)) - { - rb->strlcpy(id3.path, tcs.result, sizeof(id3.path)); - } - else -#endif - { - fd = rb->open(tcs.result, O_RDONLY); - if (fd) { - rb->get_metadata(&id3, fd, tcs.result); - rb->close(fd); - } - } + ret = rb->tagcache_get_next(&tcs, tcs_buf, tcs_bufsz) && + retrieve_id3(&id3, tcs.result) && + search_albumart_files(&id3, ":", buf, buflen); - if ( search_albumart_files(&id3, ":", buf, buflen) ) - result = true; - else - result = false; - } - else { - /* did not find a matching track */ - result = false; - } rb->tagcache_search_finish(&tcs); - return result; + return ret; } /** @@ -2005,6 +2197,9 @@ static unsigned int mfnv(char *str) const unsigned int p = 16777619; unsigned int hash = 0x811C9DC5; // 2166136261; + if (!str) + return 0; + while(*str) hash = (hash ^ *str++) * p; hash += hash << 13; @@ -2026,13 +2221,7 @@ static bool save_pfraw(char* filename, struct bitmap *bm) int fh = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); if( fh < 0 ) return false; rb->write( fh, &bmph, sizeof( struct pfraw_header ) ); - pix_t *data = (pix_t*)( bm->data ); - int y; - for( y = 0; y < bm->height; y++ ) - { - rb->write( fh, data , sizeof( pix_t ) * bm->width ); - data += bm->width; - } + rb->write( fh, bm->data , sizeof( pix_t ) * bm->width * bm->height ); rb->close( fh ); return true; } @@ -2052,8 +2241,6 @@ static bool incremental_albumart_cache(bool verbose) unsigned int hash_artist, hash_album; unsigned int format = FORMAT_NATIVE; - bool update = (pf_cfg.cache_version == CACHE_UPDATE); - if (pf_cfg.resize) format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT; @@ -2065,8 +2252,6 @@ static bool incremental_albumart_cache(bool verbose) aa_cache.inspected++; if (aa_cache.idx >= pf_idx.album_ct) { aa_cache.idx = 0; } /* Rollover */ - if (!get_albumart_for_index_from_db(idx, aa_cache.file, sizeof(aa_cache.file))) - goto aa_failure; //rb->strcpy(aa_cache.file, EMPTY_SLIDE_BMP); hash_artist = mfnv(get_album_artist(idx)); hash_album = mfnv(get_album_name(idx)); @@ -2074,13 +2259,15 @@ static bool incremental_albumart_cache(bool verbose) rb->snprintf(aa_cache.pfraw_file, sizeof(aa_cache.pfraw_file), CACHE_PREFIX "/%x%x.pfraw", hash_album, hash_artist); - if(rb->file_exists(aa_cache.pfraw_file)) { - if(update) { - aa_cache.slides++; - goto aa_success; - } + if(pf_cfg.update_albumart && rb->file_exists(aa_cache.pfraw_file)) { + aa_cache.slides++; + goto aa_success; } + if (!get_albumart_for_index_from_db(idx, aa_cache.file, sizeof(aa_cache.file))) + goto aa_failure; //rb->strcpy(aa_cache.file, EMPTY_SLIDE_BMP); + + aa_cache.input_bmp.data = aa_cache.buf; aa_cache.input_bmp.width = DISPLAY_WIDTH; aa_cache.input_bmp.height = DISPLAY_HEIGHT; @@ -2118,6 +2305,8 @@ aa_success: configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); free_all_slide_prio(0); + if (pf_state == pf_idle) + rb->queue_post(&thread_q, EV_WAKEUP, 0); } if(verbose)/* direct interaction with user */ @@ -2195,12 +2384,15 @@ static void thread(void) /* we just woke up */ break; } - if(ev.id != SYS_TIMEOUT) - while ( load_new_slide() ) { - rb->yield(); - switch (ev.id) { - case EV_EXIT: - return; + + if(ev.id != SYS_TIMEOUT) { + while ( rb->queue_empty(&thread_q) ) { + buf_ctx_lock(); + bool slide_loaded = load_new_slide(); + buf_ctx_unlock(); + if (!slide_loaded) + break; + rb->yield(); } } } @@ -2527,7 +2719,6 @@ 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; @@ -2581,7 +2772,6 @@ 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; } } @@ -2615,7 +2805,6 @@ bool load_new_slide(void) { if (pf_sldcache.free == -1 && !free_slide_prio(prio_l)) { - buf_ctx_unlock(); return false; } @@ -2624,14 +2813,12 @@ bool load_new_slide(void) { 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; } @@ -2640,7 +2827,6 @@ bool load_new_slide(void) { lla_insert_after(i, pf_sldcache.right_idx); pf_sldcache.right_idx = i; - buf_ctx_unlock(); return true; } } @@ -2655,7 +2841,6 @@ insert_first_slide: pf_sldcache.left_idx = i; pf_sldcache.right_idx = i; pf_sldcache.used = i; - buf_ctx_unlock(); return true; } } @@ -2664,12 +2849,10 @@ fail_and_refree: { lla_insert_tail(&pf_sldcache.free, i); } - buf_ctx_unlock(); return false; fatal_fail: free_all_slide_prio(0); initialize_slide_cache(); - buf_ctx_unlock(); return false; } @@ -2704,13 +2887,19 @@ static inline struct dim *surface(const int slide_index) { int j = 0; do { - if (pf_sldcache.cache[i].index == slide_index) + if (pf_sldcache.cache[i].index == slide_index) { + if (is_initial_slide && slide_index == center_index) + is_initial_slide = false; return get_slide(pf_sldcache.cache[i].hid); + } i = pf_sldcache.cache[i].next; j++; } while (i != pf_sldcache.used && j < SLIDE_CACHE_SIZE); } - return get_slide(empty_slide_hid); + if (is_initial_slide && slide_index == center_index) + return NULL; + else + return get_slide(empty_slide_hid); } /** @@ -2898,7 +3087,7 @@ static void render_slide(struct slide_data *slide, const int alpha) const pix_t *ptr = &src[column * bmp->height]; -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE #define PIXELSTEP_Y 1 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)]) #else @@ -2971,17 +3160,106 @@ static inline void set_current_slide(const int slide_index) { int old_center_index = center_index; step = 0; - center_index = fbound(slide_index, 0, number_of_slides - 1); + center_index = fbound(0, slide_index, number_of_slides - 1); if (old_center_index != center_index) { rb->queue_remove_from_head(&thread_q, EV_WAKEUP); rb->queue_post(&thread_q, EV_WAKEUP, 0); } target = center_index; - slide_frame = slide_index << 16; + slide_frame = center_index << 16; reset_slides(); } + +static void skip_animation_to_idle_state(void); +static bool sort_albums(int new_sorting, bool from_settings) +{ + int i, album_idx, artist_idx; + char* current_album_name = NULL; + char* current_album_artist = NULL; + static const char* sort_options[] = { + ID2P(LANG_ARTIST_PLUS_NAME), + ID2P(LANG_ARTIST_PLUS_YEAR), + ID2P(LANG_ID3_YEAR), + ID2P(LANG_NAME) + }; + + /* Only change sorting once artwork has been inspected */ + if (aa_cache.inspected < pf_idx.album_ct) + { +#ifdef USEGSLIB + if (!from_settings) + grey_show(false); +#endif + rb->splash(HZ*2, rb->str(LANG_WAIT_FOR_CACHE)); +#ifdef USEGSLIB + if (!from_settings) + grey_show(true); +#endif + return false; + } + + /* set idle state */ + if (pf_state == pf_show_tracks) + free_borrowed_tracks(); + if (pf_state == pf_show_tracks || + pf_state == pf_cover_in || + pf_state == pf_cover_out) + skip_animation_to_idle_state(); + else if (pf_state == pf_scrolling) + set_current_slide(target); + pf_state = pf_idle; + + pf_cfg.sort_albums_by = new_sorting; + if (!from_settings) + { +#ifdef USEGSLIB + grey_show(false); +#if LCD_DEPTH > 1 + rb->lcd_set_background(N_BRIGHT(0)); + rb->lcd_set_foreground(N_BRIGHT(255)); +#endif + rb->lcd_clear_display(); + rb->lcd_update(); +#endif + rb->splash(HZ, sort_options[pf_cfg.sort_albums_by]); +#ifdef USEGSLIB + grey_show(true); +#endif + } + + current_album_artist = get_album_artist(center_index); + current_album_name = get_album_name(center_index); + + end_pf_thread(); /* stop loading of covers */ + + rb->qsort(pf_idx.album_index, pf_idx.album_ct, + sizeof(struct album_data), compare_albums); + + /* Empty cache and restart cover loading thread */ + rb->buflib_init(&buf_ctx, (void *)pf_idx.buf, pf_idx.buf_sz); + empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0); + initialize_slide_cache(); + is_initial_slide = true; + create_pf_thread(); + + /* Go to previously selected slide */ + for (i = 0; i < pf_idx.album_ct; i++ ) + { + album_idx = pf_idx.album_index[i].name_idx; + artist_idx = pf_idx.album_index[i].artist_idx; + + if(!rb->strcmp(pf_idx.album_names + album_idx, current_album_name) && + !rb->strcmp(pf_idx.artist_names + artist_idx, current_album_artist)) + { + set_current_slide(i); + pf_cfg.last_album = i; + } + } + return true; +} + /** Start the animation for changing slides */ @@ -2991,6 +3269,8 @@ static void start_animation(void) pf_state = pf_scrolling; } +static void update_scroll_animation(void); + /** Go to the previous slide */ @@ -3004,6 +3284,8 @@ static void show_previous_slide(void) } else if ( step > 0 ) { target = center_index; step = (target <= center_slide.slide_index) ? -1 : 1; + if (step < 0) + update_scroll_animation(); } else { target = fmax(0, center_index - 2); } @@ -3023,6 +3305,8 @@ static void show_next_slide(void) } else if ( step < 0 ) { target = center_index; step = (target < center_slide.slide_index) ? -1 : 1; + if (step > 0) + update_scroll_animation(); } else { target = fmin(center_index + 2, number_of_slides - 1); } @@ -3199,31 +3483,47 @@ static void cleanup(void) rb->cpu_boost(false); #endif end_pf_thread(); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif #ifdef USEGSLIB grey_release(); #endif } +static void skip_animation_to_show_tracks(void); +static void adjust_album_display_for_setting(int old_val, int new_val) +{ + if (old_val == new_val) + return; + + reset_track_list(); + recalc_offsets(); + reset_slides(); + + if (pf_state == pf_show_tracks) + skip_animation_to_show_tracks(); +} + /** Shows the settings menu */ static int settings_menu(void) { int selection = 0; - bool old_val; + int old_val; MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, + ID2P(LANG_SHOW_ALBUM_TITLE), + ID2P(LANG_SHOW_YEAR_IN_ALBUM_TITLE), + ID2P(LANG_SORT_ALBUMS_BY), + ID2P(LANG_YEAR_SORT_ORDER), ID2P(LANG_DISPLAY_FPS), ID2P(LANG_SPACING), ID2P(LANG_CENTRE_MARGIN), ID2P(LANG_NUMBER_OF_SLIDES), ID2P(LANG_ZOOM), - ID2P(LANG_SHOW_ALBUM_TITLE), ID2P(LANG_RESIZE_COVERS), ID2P(LANG_REBUILD_CACHE), ID2P(LANG_UPDATE_CACHE), @@ -3237,6 +3537,16 @@ static int settings_menu(void) { STR(LANG_SHOW_ALL_AT_THE_TOP) }, { STR(LANG_SHOW_ALL_AT_THE_BOTTOM) }, }; + static const struct opt_items sort_options[] = { + { STR(LANG_ARTIST_PLUS_NAME) }, + { STR(LANG_ARTIST_PLUS_YEAR) }, + { STR(LANG_ID3_YEAR) }, + { STR(LANG_NAME) } + }; + static const struct opt_items year_sort_order_options[] = { + { STR(LANG_ASCENDING) }, + { STR(LANG_DESCENDING) } + }; static const struct opt_items wps_options[] = { { STR(LANG_OFF) }, { STR(LANG_DIRECT) }, @@ -3251,73 +3561,96 @@ static int settings_menu(void) selection=rb->do_menu(&settings_menu,&selection, NULL, false); switch(selection) { case 0: + old_val = pf_cfg.show_album_name; + rb->set_option(rb->str(LANG_SHOW_ALBUM_TITLE), + &pf_cfg.show_album_name, RB_INT, album_name_options, 5, NULL); + adjust_album_display_for_setting(old_val, pf_cfg.show_album_name); + break; + case 1: + rb->set_bool(rb->str(LANG_SHOW_YEAR_IN_ALBUM_TITLE), &pf_cfg.show_year); + break; + case 2: + old_val = pf_cfg.sort_albums_by; + rb->set_option(rb->str(LANG_SORT_ALBUMS_BY), + &pf_cfg.sort_albums_by, RB_INT, sort_options, 4, NULL); + if (old_val != pf_cfg.sort_albums_by && + !sort_albums(pf_cfg.sort_albums_by, true)) + pf_cfg.sort_albums_by = old_val; + break; + case 3: + old_val = pf_cfg.year_sort_order; + rb->set_option(rb->str(LANG_YEAR_SORT_ORDER), + &pf_cfg.year_sort_order, RB_INT, year_sort_order_options, 2, NULL); + if (old_val != pf_cfg.year_sort_order && + !sort_albums(pf_cfg.sort_albums_by, true)) + pf_cfg.year_sort_order = old_val; + break; + case 4: + old_val = pf_cfg.show_fps; rb->set_bool(rb->str(LANG_DISPLAY_FPS), &pf_cfg.show_fps); - reset_track_list(); + if (old_val != pf_cfg.show_fps) + reset_track_list(); break; - case 1: + case 5: + old_val = pf_cfg.slide_spacing; rb->set_int(rb->str(LANG_SPACING), "", 1, &pf_cfg.slide_spacing, NULL, 1, 0, 100, NULL ); - recalc_offsets(); - reset_slides(); + adjust_album_display_for_setting(old_val, pf_cfg.slide_spacing); break; - case 2: + case 6: + old_val = pf_cfg.center_margin; rb->set_int(rb->str(LANG_CENTRE_MARGIN), "", 1, &pf_cfg.center_margin, NULL, 1, 0, 80, NULL ); - recalc_offsets(); - reset_slides(); + adjust_album_display_for_setting(old_val, pf_cfg.center_margin); break; - case 3: + case 7: + old_val = pf_cfg.num_slides; rb->set_int(rb->str(LANG_NUMBER_OF_SLIDES), "", 1, &pf_cfg.num_slides, NULL, 1, 1, MAX_SLIDES_COUNT, NULL ); - recalc_offsets(); - reset_slides(); + adjust_album_display_for_setting(old_val, pf_cfg.num_slides); break; - case 4: + case 8: + old_val = pf_cfg.zoom; rb->set_int(rb->str(LANG_ZOOM), "", 1, &pf_cfg.zoom, NULL, 1, 10, 300, NULL ); - recalc_offsets(); - reset_slides(); + adjust_album_display_for_setting(old_val, pf_cfg.zoom); break; - case 5: - rb->set_option(rb->str(LANG_SHOW_ALBUM_TITLE), - &pf_cfg.show_album_name, INT, album_name_options, 5, NULL); - reset_track_list(); - recalc_offsets(); - reset_slides(); - break; - case 6: + + case 9: old_val = pf_cfg.resize; rb->set_bool(rb->str(LANG_RESIZE_COVERS), &pf_cfg.resize); if (old_val == pf_cfg.resize) /* changed? */ break; /* fallthrough if changed, since cache needs to be rebuilt */ - case 7: + case 10: + pf_cfg.update_albumart = false; pf_cfg.cache_version = CACHE_REBUILD; rb->remove(EMPTY_SLIDE); configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); rb->splash(HZ, ID2P(LANG_CACHE_REBUILT_NEXT_RESTART)); break; - case 8: - pf_cfg.cache_version = CACHE_UPDATE; + case 11: + pf_cfg.update_albumart = true; + pf_cfg.cache_version = CACHE_REBUILD; rb->remove(EMPTY_SLIDE); configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); rb->splash(HZ, ID2P(LANG_CACHE_REBUILT_NEXT_RESTART)); break; - case 9: + case 12: rb->set_option(rb->str(LANG_WPS_INTEGRATION), - &pf_cfg.auto_wps, INT, wps_options, 3, NULL); + &pf_cfg.auto_wps, RB_INT, wps_options, 3, NULL); break; - case 10: + case 13: rb->set_option(rb->str(LANG_BACKLIGHT), - &pf_cfg.backlight_mode, INT, backlight_options, 2, NULL); + &pf_cfg.backlight_mode, RB_INT, backlight_options, 2, NULL); break; case MENU_ATTACHED_USB: @@ -3331,45 +3664,67 @@ static int settings_menu(void) Show the main menu */ enum { + PF_SHOW_TRACKS_WHILE_BROWSING, + PF_GOTO_LAST_ALBUM, PF_GOTO_WPS, #if PF_PLAYBACK_CAPABLE - PF_MENU_CLEAR_PLAYLIST, PF_MENU_PLAYBACK_CONTROL, #endif PF_MENU_SETTINGS, - PF_MENU_RETURN, PF_MENU_QUIT, }; static int main_menu(void) { int selection = 0; - int result; + int result, curr_album; #if LCD_DEPTH > 1 rb->lcd_set_foreground(N_BRIGHT(255)); #endif MENUITEM_STRINGLIST(main_menu, "PictureFlow Main Menu", NULL, + ID2P(LANG_SHOW_TRACKS_WHILE_BROWSING), + ID2P(LANG_GOTO_LAST_ALBUM), ID2P(LANG_GOTO_WPS), #if PF_PLAYBACK_CAPABLE - ID2P(LANG_CLEAR_PLAYLIST), ID2P(LANG_PLAYBACK_CONTROL), #endif ID2P(LANG_SETTINGS), - ID2P(LANG_RETURN), ID2P(LANG_MENU_QUIT)); while (1) { switch (rb->do_menu(&main_menu,&selection, NULL, false)) { + case PF_SHOW_TRACKS_WHILE_BROWSING: + if (pf_state != pf_show_tracks) + { + if (pf_state == pf_scrolling) + set_current_slide(target); + + skip_animation_to_show_tracks(); + } + show_tracks_while_browsing = true; + return 0; + case PF_GOTO_LAST_ALBUM: + if (pf_state == pf_scrolling) + curr_album = target; + else + curr_album = center_index; + + if (pf_state == pf_show_tracks) + free_borrowed_tracks(); + if (pf_state == pf_show_tracks || + pf_state == pf_cover_in || + pf_state == pf_cover_out) + skip_animation_to_idle_state(); + + set_current_slide(pf_cfg.last_album); + pf_cfg.last_album = curr_album; + + pf_state = pf_idle; + return 0; case PF_GOTO_WPS: /* WPS */ return -2; #if PF_PLAYBACK_CAPABLE - case PF_MENU_CLEAR_PLAYLIST: - if(rb->warn_on_pl_erase() && rb->playlist_remove_all_tracks(NULL) == 0) { - rb->playlist_create(NULL, NULL); - rb->splash(HZ*2, ID2P(LANG_PLAYLIST_CLEARED)); - } - break; case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */ playback_control(NULL); break; @@ -3378,8 +3733,6 @@ static int main_menu(void) result = settings_menu(); if ( result != 0 ) return result; break; - case PF_MENU_RETURN: - return 0; case PF_MENU_QUIT: return -1; @@ -3392,21 +3745,33 @@ static int main_menu(void) } } +#define ZOOMIN_FRAME_COUNT 19 +#define ZOOMIN_FRAME_DIST -5 +#define ZOOMIN_FRAME_ANGLE 1 +#define ZOOMIN_FRAME_FADE 13 + +#define ROTATE_FRAME_COUNT 15 +#define ROTATE_FRAME_ANGLE 16 + +#define KEYFRAME_COUNT ZOOMIN_FRAME_COUNT + ROTATE_FRAME_COUNT + /** Animation step for zooming into the current cover */ static void update_cover_in_animation(void) { cover_animation_keyframe++; - if( cover_animation_keyframe < 20 ) { - center_slide.distance-=5; - center_slide.angle+=1; - extra_fade += 13; - } - else if( cover_animation_keyframe < 35 ) { - center_slide.angle+=16; + + if(cover_animation_keyframe <= ZOOMIN_FRAME_COUNT) + { + center_slide.distance += ZOOMIN_FRAME_DIST; + center_slide.angle += ZOOMIN_FRAME_ANGLE; + extra_fade += ZOOMIN_FRAME_FADE; } - else { + else if(cover_animation_keyframe <= KEYFRAME_COUNT) + center_slide.angle += ROTATE_FRAME_ANGLE; + else + { cover_animation_keyframe = 0; pf_state = pf_show_tracks; } @@ -3418,36 +3783,40 @@ static void update_cover_in_animation(void) static void update_cover_out_animation(void) { cover_animation_keyframe++; - if( cover_animation_keyframe <= 15 ) { - center_slide.angle-=16; - } - else if( cover_animation_keyframe < 35 ) { - center_slide.distance+=5; - center_slide.angle-=1; - extra_fade -= 13; + + if(cover_animation_keyframe <= ROTATE_FRAME_COUNT) + center_slide.angle -= ROTATE_FRAME_ANGLE; + else if(cover_animation_keyframe <= KEYFRAME_COUNT) + { + center_slide.distance -= ZOOMIN_FRAME_DIST; + center_slide.angle -= ZOOMIN_FRAME_ANGLE; + extra_fade -= ZOOMIN_FRAME_FADE; } - else { + else + { cover_animation_keyframe = 0; pf_state = pf_idle; } } /** - Skip steps for zooming into the current cover + Immediately show tracks and skip any animation frames */ -static void interrupt_cover_in_animation(void) +static void skip_animation_to_show_tracks(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); + + extra_fade = ZOOMIN_FRAME_COUNT * ZOOMIN_FRAME_FADE; + center_slide.distance = ZOOMIN_FRAME_COUNT * ZOOMIN_FRAME_DIST; + center_slide.angle = (ZOOMIN_FRAME_COUNT * ZOOMIN_FRAME_ANGLE) + + (ROTATE_FRAME_COUNT * ROTATE_FRAME_ANGLE); } /** - Skip steps for zooming out the current cover + Immediately transition to idle state and skip any animation frames */ -static void interrupt_cover_out_animation(void) +static void skip_animation_to_idle_state(void) { pf_state = pf_idle; cover_animation_keyframe = 0; @@ -3456,21 +3825,12 @@ static void interrupt_cover_out_animation(void) } /** - Stop zooming out the current cover and start zooming in + Change direction during cover in/out animation */ -static void revert_cover_out_animation(void) +static void reverse_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; + pf_state = pf_state == pf_cover_out ? pf_cover_in : pf_cover_out; + cover_animation_keyframe = KEYFRAME_COUNT - cover_animation_keyframe; } /** @@ -3577,7 +3937,10 @@ static void show_track_list(void) { mylcd_clear_display(); if ( center_slide.slide_index != pf_tracks.cur_idx ) { - show_track_list_loading(); +#ifdef HAVE_TC_RAMCACHE + if (!rb->tagcache_is_in_ram()) +#endif + show_track_list_loading(); create_track_index(center_slide.slide_index); if (pf_tracks.count == 0) { @@ -3598,7 +3961,7 @@ static void show_track_list(void) for (; track_i < pf_tracks.list_visible + pf_tracks.list_start; track_i++) { char *trackname = get_track_name(track_i); - if ( track_i == pf_tracks.sel ) { + if (track_i == pf_tracks.sel && !show_tracks_while_browsing) { if (pf_tracks.sel != pf_tracks.last_sel) { set_scroll_line(trackname, PF_SCROLL_TRACK); pf_tracks.last_sel = pf_tracks.sel; @@ -3650,7 +4013,7 @@ static void select_next_album(void) free_borrowed_tracks(); target = center_index + 1; set_current_slide(target); - interrupt_cover_in_animation(); + skip_animation_to_show_tracks(); } } @@ -3660,13 +4023,50 @@ static void select_prev_album(void) free_borrowed_tracks(); target = center_index - 1; set_current_slide(target); - interrupt_cover_in_animation(); + skip_animation_to_show_tracks(); } } #if PF_PLAYBACK_CAPABLE +static int show_id3_info(const char *selected_file) +{ + int i; + unsigned long last_tick; + const char *file_name; + bool is_multiple_tracks = insert_whole_album && pf_tracks.count > 1; + + last_tick = *(rb->current_tick) + HZ/2; + rb->splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ + i = 0; + do { + file_name = i == 0 ? selected_file : get_track_filename(i); + if (rb->mp3info(&id3, file_name)) + return 0; + + if (is_multiple_tracks) + { + rb->splash_progress(i, pf_tracks.count, + "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); + if (TIME_AFTER(*(rb->current_tick), last_tick + HZ/4)) + { + if (rb->action_userabort(TIMEOUT_NOBLOCK)) + return 0; + last_tick = *(rb->current_tick); + } + + collect_id3(&id3, i == 0); + rb->yield(); + } + } while (++i < pf_tracks.count && is_multiple_tracks); + + if (is_multiple_tracks) + finalize_id3(&id3); + + return rb->browse_id3(&id3, 0, 0, NULL, i) ? PLUGIN_USB_CONNECTED : 0; +} + -static bool playlist_insert(int position, bool queue, bool create_new) +static bool pf_current_playlist_insert(int position, bool queue, bool create_new) { if (position == PLAYLIST_REPLACE) { @@ -3697,11 +4097,52 @@ static bool playlist_insert(int position, bool queue, bool create_new) return true; } + +static int pf_add_to_playlist(const char* playlist, bool new_playlist) +{ + int fd; + int result = 0; + + if (new_playlist) + fd = rb->open_utf8(playlist, O_CREAT|O_WRONLY|O_TRUNC); + else + fd = rb->open(playlist, O_CREAT|O_WRONLY|O_APPEND, 0666); + + if(fd < 0) + return -1; + + rb->reload_directory(); + + if (!insert_whole_album) + { + if (rb->fdprintf(fd, "%s\n", get_track_filename(pf_tracks.sel)) <= 0) + result = -1; + } + else + { + int i = 0; + do { + if (rb->fdprintf(fd, "%s\n", get_track_filename(i)) <= 0) + { + result = -1; + break; + } + rb->yield(); + } while(++i < pf_tracks.count); + } + rb->close(fd); + return result; +} + + static bool track_list_ready(void) { if (pf_state != pf_show_tracks) { - rb->splash(0, ID2P(LANG_WAIT)); +#ifdef HAVE_TC_RAMCACHE + if (!rb->tagcache_is_in_ram()) +#endif + rb->splash(0, ID2P(LANG_WAIT)); create_track_index(center_slide.slide_index); if (pf_tracks.count == 0) { @@ -3713,14 +4154,8 @@ static bool track_list_ready(void) return true; } -/** - Brings up "Current Playlist" menu with first - track of selection. - Onplay menu code calls back playlist_insert for - adding all of the tracks. -*/ -static void show_current_playlist_menu(void) +static bool context_menu_ready(void) { #ifdef USEGSLIB grey_show(false); @@ -3732,16 +4167,26 @@ static void show_current_playlist_menu(void) #ifdef USEGSLIB grey_show(true); #endif - return; + return false; } - insert_whole_album = pf_state != pf_show_tracks; +#if LCD_DEPTH > 1 +#ifdef USEGSLIB + rb->lcd_set_foreground(N_BRIGHT(0)); + rb->lcd_set_background(N_BRIGHT(255)); +#endif +#endif + insert_whole_album = (pf_state != pf_show_tracks) || show_tracks_while_browsing; FOR_NB_SCREENS(i) rb->viewportmanager_theme_enable(i, true, NULL); - rb->onplay_show_playlist_menu(get_track_filename(pf_tracks.sel), - &playlist_insert); + + return true; +} + +static void context_menu_cleanup(void) +{ FOR_NB_SCREENS(i) rb->viewportmanager_theme_undo(i, false); - if (insert_whole_album) + if (pf_state != pf_show_tracks) free_borrowed_tracks(); #ifdef USEGSLIB grey_show(true); @@ -3750,6 +4195,60 @@ static void show_current_playlist_menu(void) } +static int context_menu(void) +{ + char album_name[MAX_PATH]; + char *file_name = get_track_filename(show_tracks_while_browsing ? 0 : pf_tracks.sel); + int attr = FILE_ATTR_AUDIO; + + enum { + PF_CURRENT_PLAYLIST = 0, + PF_CATALOG, + PF_ID3_INFO + }; + MENUITEM_STRINGLIST(context_menu, ID2P(LANG_ONPLAY_MENU_TITLE), NULL, + ID2P(LANG_PLAYING_NEXT), + ID2P(LANG_ADD_TO_PL), + ID2P(LANG_MENU_SHOW_ID3_INFO)); + + while (1) { + switch (rb->do_menu(&context_menu, + NULL, NULL, false)) { + + case PF_CURRENT_PLAYLIST: + if (insert_whole_album && pf_tracks.count > 1) + { + attr = ATTR_DIRECTORY; + file_name = NULL; + } + rb->onplay_show_playlist_menu(file_name, attr, &pf_current_playlist_insert); + return 0; + case PF_CATALOG: + if (insert_whole_album) + { + /* add a leading slash so that catalog_add_to_a_playlist + later prefills the name when creating a new playlist */ + rb->snprintf(album_name, MAX_PATH, "/%s", get_album_name(center_index)); + rb->fix_path_part(album_name, 1, sizeof(album_name) - 2); + file_name = album_name; + attr = ATTR_DIRECTORY; + } + + rb->onplay_show_playlist_cat_menu(file_name, attr, &pf_add_to_playlist); + return 0; + case PF_ID3_INFO: + return show_id3_info(file_name); + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return 0; + + } + } +} + + + /* * Puts selected album's tracks into a newly created playlist and starts playing */ @@ -3763,7 +4262,9 @@ static bool start_playback(bool return_to_WPS) #endif rb->lcd_clear_display(); rb->lcd_update(); -#endif /* USEGSLIB */ +#else /* if !USEGSLIB */ + (void) return_to_WPS; +#endif if (!rb->warn_on_pl_erase() || !track_list_ready()) { @@ -3780,7 +4281,7 @@ static bool start_playback(bool return_to_WPS) if (shuffle || center_slide.slide_index != old_playlist || (old_shuffle != shuffle)) { - if (!playlist_insert(PLAYLIST_REPLACE, false, true)) + if (!pf_current_playlist_insert(PLAYLIST_REPLACE, false, true)) { #ifdef USEGSLIB grey_show(true); @@ -3791,12 +4292,9 @@ static bool start_playback(bool return_to_WPS) start_index = rb->playlist_shuffle(*rb->current_tick, pf_tracks.sel); } rb->playlist_start(start_index, 0, 0); - rb->playlist_get_current()->num_inserted_tracks = 0; /* prevent warn_on_pl_erase */ old_shuffle = shuffle; - if (return_to_WPS) - pf_cfg.last_album = center_index; #ifdef USEGSLIB - else + if (!return_to_WPS) grey_show(true); #endif return true; @@ -3808,10 +4306,13 @@ static bool start_playback(bool return_to_WPS) */ static void draw_album_text(void) { + char album_and_year[MAX_PATH]; + if (pf_cfg.show_album_name == ALBUM_NAME_HIDE) return; static int prev_albumtxt_index = -1; + static bool prev_show_year = false; int albumtxt_index; int char_height; int albumtxt_x, albumtxt_y, artisttxt_x; @@ -3839,10 +4340,18 @@ static void draw_album_text(void) } albumtxt = get_album_name_idx(albumtxt_index, &album_idx); + if (pf_cfg.show_year && pf_idx.album_index[albumtxt_index].year > 0) + { + rb->snprintf(album_and_year, sizeof(album_and_year), "%s – %d", + albumtxt, pf_idx.album_index[albumtxt_index].year); + } else + rb->snprintf(album_and_year, sizeof(album_and_year), "%s", albumtxt); + mylcd_set_foreground(G_BRIGHT(c)); - if (albumtxt_index != prev_albumtxt_index) { - set_scroll_line(albumtxt, PF_SCROLL_ALBUM); + if (albumtxt_index != prev_albumtxt_index || pf_cfg.show_year != prev_show_year) { + set_scroll_line(album_and_year, PF_SCROLL_ALBUM); prev_albumtxt_index = albumtxt_index; + prev_show_year = pf_cfg.show_year; } char_height = rb->screens[SCREEN_MAIN]->getcharheight(); @@ -3867,7 +4376,7 @@ static void draw_album_text(void) || (pf_cfg.show_album_name == ALBUM_AND_ARTIST_BOTTOM)){ if (album_idx != (int) pf_idx.album_untagged_idx) - mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt); + mylcd_putsxy(albumtxt_x, albumtxt_y, album_and_year); artisttxt = get_album_artist(albumtxt_index); set_scroll_line(artisttxt, PF_SCROLL_ARTIST); @@ -3875,37 +4384,22 @@ static void draw_album_text(void) int y_offset = char_height + char_height/2; mylcd_putsxy(artisttxt_x, albumtxt_y + y_offset, artisttxt); } else { - mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt); + mylcd_putsxy(albumtxt_x, albumtxt_y, album_and_year); } } static void set_initial_slide(const char* selected_file) { - if (selected_file == NULL) - set_current_slide(id3_get_index(rb->audio_current_track())); + if (selected_file) + set_current_slide(retrieve_id3(&id3, selected_file) ? + id3_get_index(&id3) : + pf_cfg.last_album); else - { - struct mp3entry id3; -#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) - if (rb->tagcache_fill_tags(&id3, selected_file)) - set_current_slide(id3_get_index(&id3)); - else -#endif - { - int fd = rb->open(selected_file, O_RDONLY); - if (fd >= 0) - { - if (rb->get_metadata(&id3, fd, selected_file)) - set_current_slide(id3_get_index(&id3)); - else - set_current_slide(pf_cfg.last_album); - rb->close(fd); - } - else - set_current_slide(pf_cfg.last_album); - } - } + set_current_slide(rb->audio_status() ? + id3_get_index(rb->audio_current_track()) : + pf_cfg.last_album); + } /** @@ -3940,22 +4434,17 @@ static int pictureflow_main(const char* selected_file) config_set_defaults(&pf_cfg); configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); - if(pf_cfg.auto_wps == 0) - draw_splashscreen(pf_idx.buf, pf_idx.buf_sz); - if(pf_cfg.backlight_mode == 0) { - /* Turn off backlight timeout */ + #ifdef HAVE_BACKLIGHT + if(pf_cfg.backlight_mode == 0) backlight_ignore_timeout(); #endif - } rb->mutex_init(&buf_ctx_mutex); init_scroll_lines(); init_reflect_table(); - ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4); - /*Scan will trigger when no file is found or the option was activated*/ if ((pf_cfg.cache_version != CACHE_VERSION)||(load_album_index() < 0)){ rb->splash(HZ/2,"Creating index, please wait"); @@ -3980,32 +4469,30 @@ static int pictureflow_main(const char* selected_file) return PLUGIN_ERROR; } - ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4); - number_of_slides = pf_idx.album_ct; - - size_t aa_bufsz = ALIGN_DOWN(pf_idx.buf_sz / 4, 0x4); + number_of_slides = pf_idx.album_ct; + size_t aa_bufsz = ALIGN_DOWN(pf_idx.buf_sz / 4, sizeof(long)); if (aa_bufsz < DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(pix_t)) { error_wait("Not enough memory for album art cache"); return PLUGIN_ERROR; } - pf_idx.buf_sz -= aa_bufsz; - ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4); + ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, sizeof(long)); aa_cache.buf = (char*) pf_idx.buf; aa_cache.buf_sz = aa_bufsz; + pf_idx.buf += aa_bufsz; - ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4); + pf_idx.buf_sz -= aa_bufsz; if (!create_empty_slide(pf_cfg.cache_version != CACHE_VERSION)) { - config_save(CACHE_REBUILD); + config_save(CACHE_REBUILD, false); error_wait("Could not load the empty slide"); return PLUGIN_ERROR; } if ((pf_cfg.cache_version != CACHE_VERSION) && !create_albumart_cache()) { - config_save(CACHE_REBUILD); + config_save(CACHE_REBUILD, false); error_wait("Could not create album art cache"); } else if(aa_cache.inspected < pf_idx.album_ct) { rb->splash(HZ * 2, "Updating album art cache in background"); @@ -4013,7 +4500,7 @@ static int pictureflow_main(const char* selected_file) if (pf_cfg.cache_version != CACHE_VERSION) { - config_save(CACHE_VERSION); + config_save(CACHE_VERSION, pf_cfg.update_albumart); } rb->buflib_init(&buf_ctx, (void *)pf_idx.buf, pf_idx.buf_sz); @@ -4087,6 +4574,7 @@ static int pictureflow_main(const char* selected_file) instant_update = true; break; case pf_cover_out: + show_tracks_while_browsing = false; update_cover_out_animation(); render_all_slides(); instant_update = true; @@ -4095,8 +4583,14 @@ static int pictureflow_main(const char* selected_file) show_track_list(); break; case pf_idle: + show_tracks_while_browsing = false; render_all_slides(); - incremental_albumart_cache(false); + if (aa_cache.inspected < pf_idx.album_ct) + { + buf_ctx_lock(); + incremental_albumart_cache(false); + buf_ctx_unlock(); + } break; } @@ -4122,7 +4616,8 @@ static int pictureflow_main(const char* selected_file) rb->snprintf(fpstxt, sizeof(fpstxt), "%d %%", progress_pct); } - if (pf_cfg.show_album_name == ALBUM_NAME_TOP) + if (pf_cfg.show_album_name == ALBUM_NAME_TOP || + pf_cfg.show_album_name == ALBUM_AND_ARTIST_TOP) fpstxt_y = LCD_HEIGHT - rb->screens[SCREEN_MAIN]->getcharheight(); else @@ -4133,7 +4628,8 @@ static int pictureflow_main(const char* selected_file) /* Copy offscreen buffer to LCD and give time to other threads */ - mylcd_update(); + if (is_initial_slide == false) + mylcd_update(); rb->yield(); /*/ Handle buttons */ @@ -4150,15 +4646,17 @@ static int pictureflow_main(const char* selected_file) case PF_WPS: return PLUGIN_GOTO_WPS; case PF_BACK: - if ( pf_state == pf_show_tracks ) + if (show_tracks_while_browsing) + show_tracks_while_browsing = false; + else if (pf_state == pf_show_tracks) { pf_state = pf_cover_out; free_borrowed_tracks(); } else if (pf_state == pf_cover_in) - revert_cover_in_animation(); + reverse_animation(); else if (pf_state == pf_cover_out) - interrupt_cover_out_animation(); + skip_animation_to_idle_state(); else if (pf_state == pf_idle || pf_state == pf_scrolling) return PLUGIN_OK; break; @@ -4183,11 +4681,16 @@ static int pictureflow_main(const char* selected_file) case PF_NEXT: case PF_NEXT_REPEAT: if ( pf_state == pf_show_tracks ) - select_next_track(); + { + if (show_tracks_while_browsing) + select_next_album(); + else + select_next_track(); + } else if (pf_state == pf_cover_in) - interrupt_cover_in_animation(); + skip_animation_to_show_tracks(); else if (pf_state == pf_cover_out) - interrupt_cover_out_animation(); + skip_animation_to_idle_state(); if ( pf_state == pf_idle || pf_state == pf_scrolling ) show_next_slide(); @@ -4196,47 +4699,75 @@ static int pictureflow_main(const char* selected_file) case PF_PREV: case PF_PREV_REPEAT: if ( pf_state == pf_show_tracks ) - select_prev_track(); + { + if (show_tracks_while_browsing) + select_prev_album(); + else + select_prev_track(); + } else if (pf_state == pf_cover_in) - interrupt_cover_in_animation(); + skip_animation_to_show_tracks(); else if (pf_state == pf_cover_out) - interrupt_cover_out_animation(); + skip_animation_to_idle_state(); if ( pf_state == pf_idle || pf_state == pf_scrolling ) show_previous_slide(); break; + case PF_SORTING_NEXT: + sort_albums((pf_cfg.sort_albums_by + 1) % SORT_VALUES_SIZE, false); + break; + case PF_SORTING_PREV: + sort_albums((pf_cfg.sort_albums_by + (SORT_VALUES_SIZE - 1)) % SORT_VALUES_SIZE, false); + break; case PF_JMP: - if (pf_state == pf_idle || pf_state == pf_scrolling) + if (pf_state == pf_idle || pf_state == pf_scrolling) + { + int new_idx = jmp_idx_next(); + if (new_idx != center_index) { pf_state = pf_idle; - set_current_slide(get_album_artist_alpha_next_index()); + set_current_slide(new_idx); } - else if ( pf_state == pf_show_tracks ) - select_next_album(); - break; + } + else if ( pf_state == pf_show_tracks ) + select_next_album(); + break; case PF_JMP_PREV: - if (pf_state == pf_idle || pf_state == pf_scrolling) + if (pf_state == pf_idle || pf_state == pf_scrolling) + { + int new_idx = jmp_idx_prev(); + if (new_idx != center_index) { pf_state = pf_idle; - set_current_slide(get_album_artist_alpha_prev_index()); + set_current_slide(new_idx); } - else if ( pf_state == pf_show_tracks ) - select_prev_album(); - break; + } + else if ( pf_state == pf_show_tracks ) + select_prev_album(); + else if (pf_state == pf_cover_in) + reverse_animation(); + else if (pf_state == pf_cover_out) + skip_animation_to_idle_state(); + break; #if PF_PLAYBACK_CAPABLE case PF_CONTEXT: - 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_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(); + } + else if (pf_state == pf_cover_out) + skip_animation_to_idle_state(); - show_current_playlist_menu(); + if (context_menu_ready()) + { + ret = context_menu(); + context_menu_cleanup(); + if ( ret != 0 ) return ret; + } } break; #endif @@ -4259,19 +4790,22 @@ static int pictureflow_main(const char* selected_file) pf_state = pf_cover_in; } else if (pf_state == pf_cover_out) - revert_cover_out_animation(); + reverse_animation(); else if (pf_state == pf_cover_in) - interrupt_cover_in_animation(); + skip_animation_to_show_tracks(); + else if (pf_state == pf_show_tracks) + { + if (show_tracks_while_browsing) + show_tracks_while_browsing = false; #if PF_PLAYBACK_CAPABLE - else if (pf_state == pf_show_tracks) { - if(pf_cfg.auto_wps != 0) { + else if(pf_cfg.auto_wps != 0) { if (start_playback(true)) return PLUGIN_GOTO_WPS; } else start_playback(false); - } #endif + } break; default: exit_on_usb(button); @@ -4292,17 +4826,12 @@ enum plugin_status plugin_start(const void *parameter) void * buf; size_t buf_size; - bool prompt = (parameter && (((char *) parameter)[0] == ACTIVITY_MAINMENU)); bool file_id3 = (parameter && (((char *) parameter)[0] == '/')); - if (!check_database(prompt)) + if (!check_database()) { - if (prompt) - return PLUGIN_OK; - else - error_wait("Please enable database"); - - return PLUGIN_ERROR; + error_wait("Please enable database"); + return PLUGIN_OK; } atexit(cleanup); @@ -4342,6 +4871,10 @@ enum plugin_status plugin_start(const void *parameter) ret = file_id3 ? pictureflow_main(file) : pictureflow_main(NULL); if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) { + if (pf_state == pf_scrolling) + pf_cfg.last_album = target; + else + pf_cfg.last_album = center_index; if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION)) { diff --git a/apps/plugins/pitch_detector.c b/apps/plugins/pitch_detector.c index 4ae43b3236..f7d1219445 100644 --- a/apps/plugins/pitch_detector.c +++ b/apps/plugins/pitch_detector.c @@ -503,19 +503,19 @@ static bool main_menu(void) rb->set_option( "Algorithm Pickiness (Lower -> more discriminating)", &settings.yin_threshold, - INT, yin_threshold_text, + RB_INT, yin_threshold_text, sizeof(yin_threshold_text) / sizeof(yin_threshold_text[0]), NULL); break; case 5: rb->set_option("Display Accidentals As", &settings.use_sharps, - BOOL, accidental_text, 2, NULL); + RB_BOOL, accidental_text, 2, NULL); break; case 6: rb->set_option("Key Transposition", &settings.key_transposition, - INT, transpose_text, 12, NULL); + RB_INT, transpose_text, 12, NULL); break; case 7: rb->set_bool("Display Frequency (Hz)", @@ -1013,6 +1013,11 @@ static void record_and_get_pitch(void) break; case PLA_CANCEL: +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + case PLA_UP: +#endif rb->pcm_stop_recording(); quit = main_menu(); if(!quit) diff --git a/apps/plugins/plasma.c b/apps/plugins/plasma.c index f944d3d775..cdb6e66569 100644 --- a/apps/plugins/plasma.c +++ b/apps/plugins/plasma.c @@ -140,10 +140,10 @@ static void cleanup(void) #ifndef HAVE_LCD_COLOR grey_release(); #endif -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256) rb->lcd_set_mode(LCD_MODE_RGB565); #endif @@ -268,6 +268,11 @@ int main(void) switch(action) { +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + case PLA_UP: +#endif case PLA_EXIT: case PLA_CANCEL: return PLUGIN_OK; @@ -277,7 +282,11 @@ int main(void) case PLA_SCROLL_FWD: case PLA_SCROLL_FWD_REPEAT: #endif +#if (CONFIG_KEYPAD != IPOD_1G2G_PAD) \ + && (CONFIG_KEYPAD != IPOD_3G_PAD) \ + && (CONFIG_KEYPAD != IPOD_4G_PAD) case PLA_UP: +#endif case PLA_UP_REPEAT: ++plasma_frequency; wave_table_generate(); @@ -321,10 +330,10 @@ enum plugin_status plugin_start(const void* parameter) #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256) rb->lcd_set_mode(LCD_MODE_PAL256); #endif diff --git a/apps/plugins/playing_time.c b/apps/plugins/playing_time.c new file mode 100644 index 0000000000..354c5a3e06 --- /dev/null +++ b/apps/plugins/playing_time.c @@ -0,0 +1,392 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * + * 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" + +#define rb_talk_ids(enqueue, ids...) rb->talk_idarray(TALK_IDARRAY(ids), enqueue) + +/* units used with output_dyn_value */ +const unsigned char * const byte_units[] = +{ + ID2P(LANG_BYTE), + ID2P(LANG_KIBIBYTE), + ID2P(LANG_MEBIBYTE), + ID2P(LANG_GIBIBYTE) +}; + +const unsigned char * const * const kibyte_units = &byte_units[1]; + +enum ePT_SECS { + ePT_SECS_TTL = 0, + ePT_SECS_BEF, + ePT_SECS_AFT, + ePT_SECS_COUNT +}; + +enum ePT_KBS { + /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */ + ePT_KBS_TTL = 0, + ePT_KBS_BEF, + ePT_KBS_AFT, + ePT_KBS_COUNT +}; + +/* playing_time screen context */ +struct playing_time_info { + int curr_index; /* index of currently playing track in playlist */ + int curr_display_index; /* display index of currently playing track in playlist */ + int nb_tracks; /* how many tracks in playlist */ + + /* seconds total, before, and after current position. Datatype + allows for values up to 68years. If I had kept it in ms + though, it would have overflowed at 24days, which takes + something like 8.5GB at 32kbps, and so we could conceivably + have playlists lasting longer than that. */ + long secs[ePT_SECS_COUNT]; + long trk_secs[ePT_SECS_COUNT]; + + /* kilobytes played total, before, and after current pos. + Kilobytes because bytes would overflow. Data type range is up + to 2TB. */ + long kbs[ePT_KBS_COUNT]; +}; + +/* list callback for playing_time screen */ +static const char * playing_time_get_or_speak_info(int selected_item, void * data, + char *buf, size_t buffer_len, + bool say_it) +{ + long elapsed_pct; /* percentage of duration elapsed */ + struct playing_time_info *pti = (struct playing_time_info *)data; + switch(selected_item) { + case 0: { /* elapsed and total time */ + char timestr1[25], timestr2[25]; + rb->format_time_auto(timestr1, sizeof(timestr1), + pti->secs[ePT_SECS_BEF], UNIT_SEC, false); + + rb->format_time_auto(timestr2, sizeof(timestr2), + pti->secs[ePT_SECS_TTL], UNIT_SEC, false); + + if (pti->secs[ePT_SECS_TTL] == 0) + elapsed_pct = 0; + else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF) + { + elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100 + / pti->secs[ePT_SECS_TTL]); + } + else /* sacrifice some precision to avoid overflow */ + { + elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100 + / (pti->secs[ePT_SECS_TTL] >> 7); + } + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_ELAPSED), + timestr1, timestr2, elapsed_pct); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_ELAPSED, + TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME), + VOICE_OF, + TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME), + VOICE_PAUSE, + TALK_ID(elapsed_pct, UNIT_PERCENT)); + break; + } + case 1: { /* playlist remaining time */ + char timestr[25]; + rb->format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_SECS_AFT], + UNIT_SEC, false); + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_REMAINING), timestr); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_REMAINING, + TALK_ID(pti->secs[ePT_SECS_AFT], UNIT_TIME)); + break; + } + case 2: { /* track elapsed and duration */ + char timestr1[25], timestr2[25]; + + rb->format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs[ePT_SECS_BEF], + UNIT_SEC, false); + rb->format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL], + UNIT_SEC, false); + + if (pti->trk_secs[ePT_SECS_TTL] == 0) + elapsed_pct = 0; + else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF) + { + elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100 + / pti->trk_secs[ePT_SECS_TTL]); + } + else /* sacrifice some precision to avoid overflow */ + { + elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100 + / (pti->trk_secs[ePT_SECS_TTL] >> 7); + } + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_ELAPSED), + timestr1, timestr2, elapsed_pct); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED, + TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME), + VOICE_OF, + TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME), + VOICE_PAUSE, + TALK_ID(elapsed_pct, UNIT_PERCENT)); + break; + } + case 3: { /* track remaining time */ + char timestr[25]; + rb->format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_SECS_AFT], + UNIT_SEC, false); + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_REMAINING), timestr); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_TRK_REMAINING, + TALK_ID(pti->trk_secs[ePT_SECS_AFT], UNIT_TIME)); + break; + } + case 4: { /* track index */ + int track_pct = pti->curr_display_index * 100 / pti->nb_tracks; + + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRACK), + pti->curr_display_index, pti->nb_tracks, track_pct); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_TRACK, + TALK_ID(pti->curr_display_index, UNIT_INT), + VOICE_OF, + TALK_ID(pti->nb_tracks, UNIT_INT), + VOICE_PAUSE, + TALK_ID(track_pct, UNIT_PERCENT)); + break; + } + case 5: { /* storage size */ + int i; + char kbstr[ePT_KBS_COUNT][10]; + + for (i = 0; i < ePT_KBS_COUNT; i++) { + rb->output_dyn_value(kbstr[i], sizeof(kbstr[i]), + pti->kbs[i], kibyte_units, 3, true); + } + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_STORAGE), + kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]); + + if (say_it) { + int32_t voice_ids[ePT_KBS_COUNT]; + voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE; + voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE; + voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING; + + for (i = 0; i < ePT_KBS_COUNT; i++) { + rb_talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]); + rb->output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true); + } + } + break; + } + case 6: { /* Average track file size */ + char str[10]; + long avg_track_size = pti->kbs[ePT_KBS_TTL] / pti->nb_tracks; + rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true); + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_TRACK_SIZE), str); + + if (say_it) { + rb->talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false); + rb->output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true); + } + break; + } + case 7: { /* Average bitrate */ + /* Convert power of 2 kilobytes to power of 10 kilobits */ + long avg_bitrate = (pti->kbs[ePT_KBS_TTL] / pti->secs[ePT_SECS_TTL] + * 1024 * 8 / 1000); + rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_BITRATE), avg_bitrate); + + if (say_it) + rb_talk_ids(false, LANG_PLAYTIME_AVG_BITRATE, + TALK_ID(avg_bitrate, UNIT_KBIT)); + break; + } + } + return buf; +} + +static const char * playing_time_get_info(int selected_item, void * data, + char *buffer, size_t buffer_len) +{ + return playing_time_get_or_speak_info(selected_item, data, + buffer, buffer_len, false); +} + +static int playing_time_speak_info(int selected_item, void * data) +{ + static char buffer[MAX_PATH]; + playing_time_get_or_speak_info(selected_item, data, + buffer, MAX_PATH, true); + return 0; +} + +/* playing time screen: shows total and elapsed playlist duration and + other stats */ +static bool playing_time(void) +{ + int error_count = 0; + unsigned long talked_tick = *rb->current_tick; + struct playing_time_info pti; + struct playlist_track_info pltrack; + struct mp3entry id3; + int i, index, fd; + + pti.nb_tracks = rb->playlist_amount(); + rb->playlist_get_resume_info(&pti.curr_index); + struct mp3entry *curr_id3 = rb->audio_current_track(); + if (pti.curr_index == -1 || !curr_id3) + return false; + pti.curr_display_index = rb->playlist_get_display_index(); + + pti.secs[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000; + pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT] + = (curr_id3->length -curr_id3->elapsed) / 1000; + pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024; + pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024; + + rb->splash(0, ID2P(LANG_WAIT)); + rb->splash_progress_set_delay(5 * HZ); + /* Go through each file in the playlist and get its stats. For + huge playlists this can take a while... The reference position + is the position at the moment this function was invoked, + although playback continues forward. */ + index = rb->playlist_get_first_index(NULL); + for (i = 0; i < pti.nb_tracks; i++, index++) { + + if (index == pti.nb_tracks) + index = 0; + + /* Show a splash while we are loading. */ + rb->splash_progress(i, pti.nb_tracks, + "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); + + /* Voice equivalent */ + if (TIME_AFTER(*rb->current_tick, talked_tick + 5 * HZ)) { + talked_tick = *rb->current_tick; + rb_talk_ids(false, LANG_LOADING_PERCENT, + TALK_ID(i * 100 / pti.nb_tracks, UNIT_PERCENT)); + } + if (rb->action_userabort(TIMEOUT_NOBLOCK)) + goto exit; + + if (index == pti.curr_index) + continue; + + if (rb->playlist_get_track_info(NULL, index, &pltrack) >= 0) + { + bool ret = false; + if ((fd = rb->open(pltrack.filename, O_RDONLY)) >= 0) + { + ret = rb->get_metadata(&id3, fd, pltrack.filename); + rb->close(fd); + if (ret) + { + if (pltrack.display_index < pti.curr_display_index) { + pti.secs[ePT_SECS_BEF] += id3.length / 1000; + pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024; + } else { + pti.secs[ePT_SECS_AFT] += id3.length / 1000; + pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024; + } + } + } + + if (!ret) + { + error_count++; + continue; + } + } + else + { + error_count++; + break; + } + } + + if (error_count > 0) + { + rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR)); + } + + pti.nb_tracks -= error_count; + pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT]; + pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT]; + pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_KBS_AFT]; + + struct gui_synclist pt_lists; + int key; + + rb->gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL); + if (rb->global_settings->talk_menu) + rb->gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info); + rb->gui_synclist_set_nb_items(&pt_lists, 8); + rb->gui_synclist_set_title(&pt_lists, rb->str(LANG_PLAYING_TIME), NOICON); + rb->gui_synclist_draw(&pt_lists); + rb->gui_synclist_speak_item(&pt_lists); + while (true) { + if (rb->list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0 + && key!=ACTION_NONE && key!=ACTION_UNKNOWN) + { + bool usb = rb->default_event_handler(key) == SYS_USB_CONNECTED; + + if (!usb && IS_SYSEVENT(key)) + continue; + + rb->talk_force_shutup(); + return usb; + } + + } + + exit: + return false; +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + enum plugin_status status = PLUGIN_OK; + + (void)parameter; + + if (!rb->audio_status()) + { + rb->splash(HZ*2, "Nothing Playing"); + return status; + } + + FOR_NB_SCREENS(i) + rb->viewportmanager_theme_enable(i, true, NULL); + + if (playing_time()) + status = PLUGIN_USB_CONNECTED; + + FOR_NB_SCREENS(i) + rb->viewportmanager_theme_undo(i, false); + + return status; +} diff --git a/apps/plugins/plugin_crt0.c b/apps/plugins/plugin_crt0.c index 680bb0723d..5564c5575f 100644 --- a/apps/plugins/plugin_crt0.c +++ b/apps/plugins/plugin_crt0.c @@ -141,6 +141,7 @@ void exit_on_usb(int button) long result = rb->default_event_handler_ex(button, cleanup_wrapper, NULL); if (result == SYS_USB_CONNECTED) _exit(PLUGIN_USB_CONNECTED); - else if (result == SYS_POWEROFF) + else if (result == SYS_POWEROFF || result == SYS_REBOOT) + /* note: nobody actually pays attention to this exit code */ _exit(PLUGIN_POWEROFF); } diff --git a/apps/plugins/pong.c b/apps/plugins/pong.c index b49fec2459..17e6e6ed73 100644 --- a/apps/plugins/pong.c +++ b/apps/plugins/pong.c @@ -750,10 +750,9 @@ enum plugin_status plugin_start(const void* parameter) this to avoid the compiler warning about it */ (void)parameter; -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + /* Clear screen */ rb->lcd_clear_display(); @@ -792,9 +791,9 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_clear_display(); } } -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return (game == 0) ? PLUGIN_OK : PLUGIN_USB_CONNECTED; } diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index fa3517726a..ce3c03694c 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -20,39 +20,50 @@ ****************************************************************************/ #include "plugin.h" +#ifdef HAVE_TAGCACHE +#include "lib/mul_id3.h" +#endif + +#if !defined(ARRAY_SIZE) + #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +struct dir_stats { + char dirname[MAX_PATH]; + int len; + unsigned int dir_count; + unsigned int file_count; + unsigned long long byte_count; +}; +enum props_types { + PROPS_FILE = 0, + PROPS_ID3, + PROPS_MUL_ID3, + PROPS_DIR +}; -bool its_a_dir = false; - -char str_filename[MAX_PATH]; -char str_dirname[MAX_PATH]; -char str_size[64]; -char str_dircount[64]; -char str_filecount[64]; -char str_date[64]; -char str_time[64]; - -char str_title[MAX_PATH]; -char str_composer[MAX_PATH]; -char str_artist[MAX_PATH]; -char str_albumartist[MAX_PATH]; -char str_album[MAX_PATH]; -char str_genre[MAX_PATH]; -char str_comment[MAX_PATH]; -char str_year[MAX_PATH]; -char str_discnum[MAX_PATH]; -char str_tracknum[MAX_PATH]; -char str_duration[32]; -char str_bitrate[32]; -char str_frequency[32]; - -unsigned nseconds; -unsigned long nsize; -int32_t size_unit; -struct tm tm; - -int num_properties; +static int props_type = PROPS_FILE; +static struct mp3entry id3; +#ifdef HAVE_TAGCACHE +static int mul_id3_count; +static int skipped_count; +#endif + +static char str_filename[MAX_PATH]; +static char str_dirname[MAX_PATH]; +static char str_size[64]; +static char str_dircount[64]; +static char str_filecount[64]; +static char str_date[64]; +static char str_time[64]; + +static unsigned long nsize; +static int32_t size_unit; +static struct tm tm; + +#define NUM_FILE_PROPERTIES 5 static const unsigned char* const props_file[] = { ID2P(LANG_PROPERTIES_PATH), str_dirname, @@ -60,20 +71,9 @@ static const unsigned char* const props_file[] = ID2P(LANG_PROPERTIES_SIZE), str_size, ID2P(LANG_PROPERTIES_DATE), str_date, ID2P(LANG_PROPERTIES_TIME), str_time, - ID2P(LANG_PROPERTIES_COMPOSER), str_composer, - ID2P(LANG_PROPERTIES_ARTIST), str_artist, - ID2P(LANG_PROPERTIES_ALBUMARTIST), str_albumartist, - ID2P(LANG_PROPERTIES_TITLE), str_title, - ID2P(LANG_PROPERTIES_ALBUM), str_album, - ID2P(LANG_PROPERTIES_GENRE), str_genre, - ID2P(LANG_PROPERTIES_COMMENT), str_comment, - ID2P(LANG_PROPERTIES_YEAR), str_year, - ID2P(LANG_PROPERTIES_DISCNUM), str_discnum, - ID2P(LANG_PROPERTIES_TRACKNUM), str_tracknum, - ID2P(LANG_PROPERTIES_DURATION), str_duration, - ID2P(LANG_PROPERTIES_BITRATE), str_bitrate, - ID2P(LANG_PROPERTIES_FREQUENCY), str_frequency, }; + +#define NUM_DIR_PROPERTIES 4 static const unsigned char* const props_dir[] = { ID2P(LANG_PROPERTIES_PATH), str_dirname, @@ -107,7 +107,6 @@ static bool file_properties(const char* selected_file) bool found = false; DIR* dir; struct dirent* entry; - static struct mp3entry id3; dir = rb->opendir(str_dirname); if (dir) @@ -128,81 +127,8 @@ static bool file_properties(const char* selected_file) rb->snprintf(str_time, sizeof str_time, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - num_properties = 5; - - int fd = rb->open(selected_file, O_RDONLY); - if (fd >= 0 && - rb->get_metadata(&id3, fd, selected_file)) - { - long dur = id3.length / 1000; /* seconds */ - rb->snprintf(str_composer, sizeof str_composer, - "%s", id3.composer ? id3.composer : ""); - rb->snprintf(str_artist, sizeof str_artist, - "%s", id3.artist ? id3.artist : ""); - rb->snprintf(str_albumartist, sizeof str_albumartist, - "%s", id3.albumartist ? id3.albumartist : ""); - rb->snprintf(str_title, sizeof str_title, - "%s", id3.title ? id3.title : ""); - rb->snprintf(str_album, sizeof str_album, - "%s", id3.album ? id3.album : ""); - rb->snprintf(str_genre, sizeof str_genre, - "%s", id3.genre_string ? id3.genre_string : ""); - rb->snprintf(str_comment, sizeof str_comment, - "%s", id3.comment ? id3.comment : ""); - - if (id3.year_string) - rb->snprintf(str_year, sizeof str_year, - "%s", id3.year_string); - else if (id3.year) - rb->snprintf(str_year, sizeof str_year, - "%d", id3.year); - else - rb->snprintf(str_year, sizeof str_year, - "%s", ""); - - if (id3.disc_string) - rb->snprintf(str_discnum, sizeof str_discnum, - "%s", id3.disc_string); - else if (id3.discnum) - rb->snprintf(str_discnum, sizeof str_discnum, - "%d", id3.discnum); - else - rb->snprintf(str_discnum, sizeof str_discnum, - "%s", ""); - - if (id3.track_string) - rb->snprintf(str_tracknum, sizeof str_tracknum, - "%s", id3.track_string); - else if(id3.tracknum) - rb->snprintf(str_tracknum, sizeof str_tracknum, - "%d", id3.tracknum); - else - rb->snprintf(str_tracknum, sizeof str_tracknum, - "%s", ""); - - rb->snprintf(str_bitrate, sizeof str_bitrate, - "%d kbps", id3.bitrate ? : 0); - rb->snprintf(str_frequency, sizeof str_frequency, - "%ld Hz", id3.frequency ? : 0); - num_properties += 12; - - if (dur > 0) - { - nseconds = dur; - if (dur < 3600) - rb->snprintf(str_duration, sizeof str_duration, - "%d:%02d", (int)(dur / 60), - (int)(dur % 60)); - else - rb->snprintf(str_duration, sizeof str_duration, - "%d:%02d:%02d", - (int)(dur / 3600), - (int)(dur % 3600 / 60), - (int)(dur % 60)); - num_properties++; - } - } - rb->close(fd); + if (!rb->mp3info(&id3, selected_file)) + props_type = PROPS_ID3; found = true; break; } @@ -212,15 +138,7 @@ static bool file_properties(const char* selected_file) return found; } -typedef struct { - char dirname[MAX_PATH]; - int len; - unsigned int dc; - unsigned int fc; - unsigned long long bc; -} DPS; - -static bool _dir_properties(DPS *dps) +static bool _dir_properties(struct dir_stats *stats) { /* recursively scan directories in search of files and informs the user of the progress */ @@ -231,11 +149,11 @@ static bool _dir_properties(DPS *dps) struct dirent* entry; result = true; - dirlen = rb->strlen(dps->dirname); - dir = rb->opendir(dps->dirname); + dirlen = rb->strlen(stats->dirname); + dir = rb->opendir(stats->dirname); if (!dir) { - rb->splashf(HZ*2, "%s", dps->dirname); + rb->splashf(HZ*2, "%s", stats->dirname); return false; /* open error */ } @@ -244,7 +162,7 @@ static bool _dir_properties(DPS *dps) { struct dirinfo info = rb->dir_get_info(dir, entry); /* append name to current directory */ - rb->snprintf(dps->dirname+dirlen, dps->len-dirlen, "/%s", + rb->snprintf(stats->dirname+dirlen, stats->len-dirlen, "/%s", entry->d_name); if (info.attribute & ATTR_DIRECTORY) @@ -253,29 +171,30 @@ static bool _dir_properties(DPS *dps) !rb->strcmp((char *)entry->d_name, "..")) continue; /* skip these */ - dps->dc++; /* new directory */ + stats->dir_count++; /* new directory */ if (*rb->current_tick - lasttick > (HZ/8)) { unsigned log; lasttick = *rb->current_tick; rb->lcd_clear_display(); rb->lcd_puts(0,0,"SCANNING..."); - rb->lcd_puts(0,1,dps->dirname); - rb->lcd_putsf(0,2,"Directories: %d", dps->dc); - rb->lcd_putsf(0,3,"Files: %d", dps->fc); - log = human_size_log(dps->bc); - rb->lcd_putsf(0,4,"Size: %lu %cB", (unsigned long)(dps->bc >> (10*log)), - rb->str(units[log])); + rb->lcd_puts(0,1,stats->dirname); + rb->lcd_putsf(0,2,"Directories: %d", stats->dir_count); + rb->lcd_putsf(0,3,"Files: %d", stats->file_count); + log = human_size_log(stats->byte_count); + rb->lcd_putsf(0,4,"Size: %lu %s", + (unsigned long)(stats->byte_count >> (10*log)), + rb->str(units[log])); rb->lcd_update(); } /* recursion */ - result = _dir_properties(dps); + result = _dir_properties(stats); } else { - dps->fc++; /* new file */ - dps->bc += info.size; + stats->file_count++; /* new file */ + stats->byte_count += info.size; } if(ACTION_STD_CANCEL == rb->get_action(CONTEXT_STD,TIMEOUT_NOBLOCK)) result = false; @@ -285,17 +204,17 @@ static bool _dir_properties(DPS *dps) return result; } -static bool dir_properties(const char* selected_file, DPS *dps) +static bool dir_properties(const char* selected_file, struct dir_stats *stats) { unsigned log; - rb->strlcpy(dps->dirname, selected_file, MAX_PATH); + rb->strlcpy(stats->dirname, selected_file, MAX_PATH); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif - if (!_dir_properties(dps)) + if (!_dir_properties(stats)) { #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); @@ -308,13 +227,12 @@ static bool dir_properties(const char* selected_file, DPS *dps) #endif rb->strlcpy(str_dirname, selected_file, MAX_PATH); - rb->snprintf(str_dircount, sizeof str_dircount, "%d", dps->dc); - rb->snprintf(str_filecount, sizeof str_filecount, "%d", dps->fc); - log = human_size_log(dps->bc); - nsize = (long) (dps->bc >> (log*10)); + rb->snprintf(str_dircount, sizeof str_dircount, "%d", stats->dir_count); + rb->snprintf(str_filecount, sizeof str_filecount, "%d", stats->file_count); + log = human_size_log(stats->byte_count); + nsize = (long) (stats->byte_count >> (log*10)); size_unit = units[log]; rb->snprintf(str_size, sizeof str_size, "%ld %s", nsize, rb->str(size_unit)); - num_properties = 4; return true; } @@ -328,35 +246,20 @@ static const char * get_props(int selected_item, void* data, char *buffer, size_t buffer_len) { (void)data; - if(its_a_dir) - { - if(selected_item >= (int)(sizeof(props_dir) / sizeof(props_dir[0]))) - { - rb->strlcpy(buffer, "ERROR", buffer_len); - } - else - { - rb->strlcpy(buffer, p2str(props_dir[selected_item]), buffer_len); - } - } - else - { - if(selected_item >= (int)(sizeof(props_file) / sizeof(props_file[0]))) - { - rb->strlcpy(buffer, "ERROR", buffer_len); - } - else - { - rb->strlcpy(buffer, p2str(props_file[selected_item]), buffer_len); - } - } + if (PROPS_DIR == props_type) + rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_dir)) ? "ERROR" : + (char *) p2str(props_dir[selected_item]), buffer_len); + else if (PROPS_FILE == props_type) + rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_file)) ? "ERROR" : + (char *) p2str(props_file[selected_item]), buffer_len); + return buffer; } static int speak_property_selection(int selected_item, void *data) { - DPS *dps = data; - int32_t id = P2ID((its_a_dir ? props_dir : props_file)[selected_item]); + struct dir_stats *stats = data; + int32_t id = P2ID((props_type == PROPS_DIR ? props_dir : props_file)[selected_item]); rb->talk_id(id, false); switch (id) { @@ -394,14 +297,11 @@ static int speak_property_selection(int selected_item, void *data) case LANG_PROPERTIES_TIME: rb->talk_time(&tm, true); break; - case LANG_PROPERTIES_DURATION: - rb->talk_value_decimal(nseconds, UNIT_TIME, 0, true); - break; case LANG_PROPERTIES_SUBDIRS: - rb->talk_number(dps->dc, true); + rb->talk_number(stats->dir_count, true); break; case LANG_PROPERTIES_FILES: - rb->talk_number(dps->fc, true); + rb->talk_number(stats->file_count, true); break; default: rb->talk_spell(props_file[selected_item + 1], true); @@ -410,33 +310,49 @@ static int speak_property_selection(int selected_item, void *data) return 0; } -enum plugin_status plugin_start(const void* parameter) +static int browse_file_or_dir(struct dir_stats *stats) { struct gui_synclist properties_lists; int button; - bool quit = false, usb = false; - const char *file = parameter; - if(!parameter) return PLUGIN_ERROR; -#ifdef HAVE_TOUCHSCREEN - rb->touchscreen_set_mode(rb->global_settings->touch_mode); -#endif - static DPS dps = { - .len = MAX_PATH, - .dc = 0, - .fc = 0, - .bc = 0, - }; + rb->gui_synclist_init(&properties_lists, &get_props, stats, false, 2, NULL); + rb->gui_synclist_set_title(&properties_lists, + rb->str(props_type == PROPS_DIR ? + LANG_PROPERTIES_DIRECTORY_PROPERTIES : + LANG_PROPERTIES_FILE_PROPERTIES), + NOICON); + rb->gui_synclist_set_icon_callback(&properties_lists, NULL); + if (rb->global_settings->talk_menu) + rb->gui_synclist_set_voice_callback(&properties_lists, speak_property_selection); + rb->gui_synclist_set_nb_items(&properties_lists, + 2 * (props_type == PROPS_FILE ? NUM_FILE_PROPERTIES : + NUM_DIR_PROPERTIES)); + rb->gui_synclist_select_item(&properties_lists, 0); + rb->gui_synclist_draw(&properties_lists); + rb->gui_synclist_speak_item(&properties_lists); - /* determine if it's a file or a directory */ - bool found = false; + while(true) + { + button = rb->get_action(CONTEXT_LIST, HZ); + /* HZ so the status bar redraws corectly */ + if (rb->gui_synclist_do_button(&properties_lists,&button)) + continue; + switch(button) + { + case ACTION_STD_CANCEL: + return false; + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + return true; + break; + } + } +} + +static bool determine_file_or_dir(void) +{ DIR* dir; struct dirent* entry; - char* ptr = rb->strrchr(file, '/') + 1; - int dirlen = (ptr - file); - - rb->strlcpy(str_dirname, file, dirlen + 1); - rb->snprintf(str_filename, sizeof str_filename, "%s", file+dirlen); dir = rb->opendir(str_dirname); if (dir) @@ -446,69 +362,104 @@ enum plugin_status plugin_start(const void* parameter) if(!rb->strcmp(entry->d_name, str_filename)) { struct dirinfo info = rb->dir_get_info(dir, entry); - its_a_dir = info.attribute & ATTR_DIRECTORY ? true : false; - found = true; - break; + props_type = info.attribute & ATTR_DIRECTORY ? PROPS_DIR : PROPS_FILE; + rb->closedir(dir); + return true; } } rb->closedir(dir); } - /* now we know if it's a file or a dir or maybe something failed */ + return false; +} - if(!found) +#ifdef HAVE_TAGCACHE +bool mul_id3_add(const char *file_name) +{ + if (!file_name || rb->mp3info(&id3, file_name)) + skipped_count++; + else { - /* weird: we couldn't find the entry. This Should Never Happen (TM) */ - rb->splashf(0, "File/Dir not found: %s", file); - rb->action_userabort(TIMEOUT_BLOCK); - return PLUGIN_OK; + collect_id3(&id3, mul_id3_count == 0); + mul_id3_count++; } - /* get the info depending on its_a_dir */ - if(!(its_a_dir ? dir_properties(file, &dps) : file_properties(file))) + return true; +} +#endif + +enum plugin_status plugin_start(const void* parameter) +{ + static struct dir_stats stats = { - /* something went wrong (to do: tell user what it was (nesting,...) */ - rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); - rb->action_userabort(TIMEOUT_BLOCK); - return PLUGIN_OK; - } + .len = MAX_PATH, + .dir_count = 0, + .file_count = 0, + .byte_count = 0, + }; - FOR_NB_SCREENS(i) - rb->viewportmanager_theme_enable(i, true, NULL); + const char *file = parameter; + if(!parameter) + return PLUGIN_ERROR; - rb->gui_synclist_init(&properties_lists, &get_props, &dps, false, 2, NULL); - rb->gui_synclist_set_title(&properties_lists, rb->str(its_a_dir ? LANG_PROPERTIES_DIRECTORY_PROPERTIES : LANG_PROPERTIES_FILE_PROPERTIES), NOICON); - rb->gui_synclist_set_icon_callback(&properties_lists, NULL); - if (rb->global_settings->talk_menu) - rb->gui_synclist_set_voice_callback(&properties_lists, speak_property_selection); - rb->gui_synclist_set_nb_items(&properties_lists, num_properties * 2); - rb->gui_synclist_limit_scroll(&properties_lists, true); - rb->gui_synclist_select_item(&properties_lists, 0); - rb->gui_synclist_draw(&properties_lists); - rb->gui_synclist_speak_item(&properties_lists); +#ifdef HAVE_TOUCHSCREEN + rb->touchscreen_set_mode(rb->global_settings->touch_mode); +#endif - while(!quit) +#ifdef HAVE_TAGCACHE + if (!rb->strcmp(file, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER))) /* db table selected */ { - button = rb->get_action(CONTEXT_LIST, HZ); - /* HZ so the status bar redraws corectly */ - if (rb->gui_synclist_do_button(&properties_lists,&button,LIST_WRAP_UNLESS_HELD)) - continue; - switch(button) + props_type = PROPS_MUL_ID3; + mul_id3_count = skipped_count = 0; + + if (!rb->tagtree_subentries_do_action(&mul_id3_add) || mul_id3_count == 0) + return PLUGIN_ERROR; + else if (mul_id3_count > 1) /* otherwise, the retrieved id3 can be used as-is */ + finalize_id3(&id3); + + if (skipped_count > 0) + rb->splashf(HZ*2, "Skipped %d", skipped_count); + } + else +#endif + if (file[0] == '/') /* single track selected */ + { + const char* file_name = rb->strrchr(file, '/') + 1; + int dirlen = (file_name - file); + + rb->strlcpy(str_dirname, file, dirlen + 1); + rb->snprintf(str_filename, sizeof str_filename, "%s", file+dirlen); + + if(!determine_file_or_dir()) { - case ACTION_STD_CANCEL: - quit = true; - break; - default: - if (rb->default_event_handler(button) == SYS_USB_CONNECTED) - { - quit = true; - usb = true; - } - break; + /* weird: we couldn't find the entry. This Should Never Happen (TM) */ + rb->splashf(0, "File/Dir not found: %s", file); + rb->action_userabort(TIMEOUT_BLOCK); + return PLUGIN_OK; + } + + /* get the info depending on its_a_dir */ + if(!(props_type == PROPS_DIR ? dir_properties(file, &stats) : file_properties(file))) + { + /* something went wrong (to do: tell user what it was (nesting,...) */ + rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); + rb->action_userabort(TIMEOUT_BLOCK); + return PLUGIN_OK; } } + else + return PLUGIN_ERROR; + + FOR_NB_SCREENS(i) + rb->viewportmanager_theme_enable(i, true, NULL); + + bool usb = props_type == PROPS_ID3 ? rb->browse_id3(&id3, 0, 0, &tm, 1) : +#ifdef HAVE_TAGCACHE + props_type == PROPS_MUL_ID3 ? rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count) : +#endif + browse_file_or_dir(&stats); FOR_NB_SCREENS(i) rb->viewportmanager_theme_undo(i, false); - return usb? PLUGIN_USB_CONNECTED: PLUGIN_OK; + return usb ? PLUGIN_USB_CONNECTED : PLUGIN_OK; } diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index 6e34adb1db..e175429075 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -1465,7 +1465,7 @@ static void rb_blitter_free(void *handle, blitter *bl) static void rb_blitter_save(void *handle, blitter *bl, int x, int y) { /* no viewport offset */ -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE #error no vertical stride #else if(bl && bl->bmp.data) @@ -2450,7 +2450,6 @@ static int list_choose(const char *list_str, const char *title, int sel) rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL); rb->gui_synclist_set_icon_callback(&list, NULL); rb->gui_synclist_set_nb_items(&list, n); - rb->gui_synclist_limit_scroll(&list, false); rb->gui_synclist_select_item(&list, sel); @@ -2459,7 +2458,7 @@ static int list_choose(const char *list_str, const char *title, int sel) { rb->gui_synclist_draw(&list); int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_UNLESS_HELD)) + if(rb->gui_synclist_do_button(&list, &button)) continue; switch(button) { @@ -2664,7 +2663,6 @@ static bool config_menu(void) rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL); rb->gui_synclist_set_icon_callback(&list, NULL); rb->gui_synclist_set_nb_items(&list, n); - rb->gui_synclist_limit_scroll(&list, false); rb->gui_synclist_select_item(&list, 0); @@ -2674,7 +2672,7 @@ static bool config_menu(void) { rb->gui_synclist_draw(&list); int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_UNLESS_HELD)) + if(rb->gui_synclist_do_button(&list, &button)) continue; switch(button) { @@ -2750,7 +2748,6 @@ static int do_preset_menu(struct preset_menu *menu, char *title, int selected) rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL); rb->gui_synclist_set_icon_callback(&list, NULL); rb->gui_synclist_set_nb_items(&list, menu->n_entries); - rb->gui_synclist_limit_scroll(&list, false); rb->gui_synclist_select_item(&list, selected); @@ -2760,7 +2757,7 @@ static int do_preset_menu(struct preset_menu *menu, char *title, int selected) { rb->gui_synclist_draw(&list); int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); - if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_UNLESS_HELD)) + if(rb->gui_synclist_do_button(&list, &button)) continue; switch(button) { @@ -3393,9 +3390,7 @@ static void shutdown_tlsf(void) static void exit_handler(void) { -#ifdef HAVE_SW_POWEROFF sw_poweroff_restore(); -#endif unload_fonts(); shutdown_tlsf(); @@ -3655,9 +3650,7 @@ static void puzzles_main(void) { rb_atexit(exit_handler); -#ifdef HAVE_SW_POWEROFF sw_poweroff_disable(); -#endif init_default_settings(); init_fonttab(); diff --git a/apps/plugins/random_folder_advance_config.c b/apps/plugins/random_folder_advance_config.c index 2c9fb411ac..5688ff93d3 100644 --- a/apps/plugins/random_folder_advance_config.c +++ b/apps/plugins/random_folder_advance_config.c @@ -311,14 +311,13 @@ static int edit_list(void) 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); button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&lists,&button,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &button)) continue; selection = rb->gui_synclist_get_sel_pos(&lists); switch (button) diff --git a/apps/plugins/rb_info.c b/apps/plugins/rb_info.c index e0ec117dfb..a89cc658cc 100644 --- a/apps/plugins/rb_info.c +++ b/apps/plugins/rb_info.c @@ -335,6 +335,17 @@ static int list_voice_cb(int list_index, void* data) rb->talk_spell(name, true); } } + else if (data == MENU_ID(M_TESTPUT)) + { + char buf[64]; + const char* name = printcell_get_column_text(printcell_get_column_selected(), + buf, sizeof(buf)); + long id = P2ID((const unsigned char *)name); + if(id>=0) + rb->talk_id(id, true); + else + rb->talk_spell(name, true); + } else { char buf[64]; @@ -354,12 +365,12 @@ int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_syncli { if (*action == ACTION_STD_OK) { - printcell_increment_column(lists, 1, true); + printcell_increment_column(1, true); *action = ACTION_NONE; } else if (*action == ACTION_STD_CANCEL) { - if (printcell_increment_column(lists, -1, true) != testput_cols - 1) + if (printcell_increment_column(-1, true) != testput_cols - 1) { *action = ACTION_NONE; } @@ -368,7 +379,8 @@ int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_syncli { char buf[PRINTCELL_MAXLINELEN]; char* bufp = buf; - bufp = printcell_get_selected_column_text(lists, bufp, PRINTCELL_MAXLINELEN); + int selcol = printcell_get_column_selected(); + bufp = printcell_get_column_text(selcol, bufp, PRINTCELL_MAXLINELEN); rb->splashf(HZ * 2, "Item: %s", bufp); } } @@ -428,20 +440,13 @@ int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_syncli if (cur->menuid == MENU_ID(M_TESTPUT)) { - //rb->gui_list_screen_scroll_out_of_view(true); synclist_set(cur->menuid, 0, cur->items, 1); -#if LCD_DEPTH > 1 - /* If line sep is set to automatic then outline cells */ - bool showlinesep = (rb->global_settings->list_separator_height < 0); -#else - bool showlinesep = (rb->global_settings->cursor_style == 0); -#endif - printcell_enable(lists, true, showlinesep); + printcell_enable(true); //lists->callback_draw_item = test_listdraw_fn; } else { - printcell_enable(lists, false, false); + printcell_enable(false); synclist_set(cur->menuid, 1, cur->items, 1); } rb->gui_synclist_draw(lists); @@ -473,9 +478,8 @@ int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_syncli { if (lists->data == MENU_ID(M_TESTPUT)) { - //rb->gui_list_screen_scroll_out_of_view(false); //lists->callback_draw_item = NULL; - printcell_enable(lists, false, false); + printcell_enable(false); } if (lists->data != MENU_ID(M_ROOT)) { @@ -507,7 +511,8 @@ static void synclist_set(char* menu_id, int selected_item, int items, int sel_si menu_id, false, sel_size, NULL); if (menu_id == MENU_ID(M_TESTPUT)) { - testput_cols = printcell_set_columns(&lists, TESTPUT_HEADER, Icon_Rockbox); + testput_cols = printcell_set_columns(&lists, NULL, + TESTPUT_HEADER, Icon_Rockbox); } else { @@ -516,7 +521,6 @@ static void synclist_set(char* menu_id, int selected_item, int items, int sel_si 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); } @@ -560,11 +564,11 @@ enum plugin_status plugin_start(const void* parameter) else redraw = true; ret = menu_action_cb(&action, selected_item, &exit, &lists); - if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &action)) continue; selected_item = rb->gui_synclist_get_sel_pos(&lists); } } - + printcell_enable(false); return ret; } diff --git a/apps/plugins/resistor.c b/apps/plugins/resistor.c index d32ac3fad9..4461dc0dea 100644 --- a/apps/plugins/resistor.c +++ b/apps/plugins/resistor.c @@ -574,9 +574,8 @@ static void display_helpfile(void) static void led_resistance_calc(void) { -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + int voltage_menu_selection, button_press, j, k, l, foreward_current = 0; int fwd_current_selection = 0; bool quit = false; @@ -769,9 +768,8 @@ static void led_resistance_calc(void) default: quit = true; -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + break; } } @@ -784,9 +782,8 @@ static void led_resistance_calc(void) static void resistance_to_color(void) { -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + int menu_selection; int menu_selection_tol; int button_press; @@ -910,9 +907,9 @@ static void resistance_to_color(void) break; default: quit = true; -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + break; } } @@ -924,9 +921,8 @@ static void resistance_to_color(void) static void color_to_resistance(void) { -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + bool quit = false; int button_input = 0; @@ -995,9 +991,7 @@ static void color_to_resistance(void) case PLA_SELECT: default: quit = true; -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif break; } } diff --git a/apps/plugins/reversi/reversi-gui.c b/apps/plugins/reversi/reversi-gui.c index 74dd98b676..e4bb232a26 100644 --- a/apps/plugins/reversi/reversi-gui.c +++ b/apps/plugins/reversi/reversi-gui.c @@ -400,7 +400,7 @@ static bool reversi_gui_choose_strategy( } result = - rb->set_option(prompt, &index, INT, strategy_settings, num_items, NULL); + rb->set_option(prompt, &index, RB_INT, strategy_settings, num_items, NULL); (*player) = strategy_values[index]; @@ -450,7 +450,7 @@ static bool reversi_gui_menu(void) { break; } } - rb->set_option(MENU_TEXT_WRAP_MODE, &index, INT, + rb->set_option(MENU_TEXT_WRAP_MODE, &index, RB_INT, cursor_wrap_mode_settings, 3, NULL); cursor_wrap_mode = cursor_wrap_mode_values[index]; break; diff --git a/apps/plugins/robotfindskitten.c b/apps/plugins/robotfindskitten.c index 4f228423b6..31c419e33d 100644 --- a/apps/plugins/robotfindskitten.c +++ b/apps/plugins/robotfindskitten.c @@ -469,7 +469,13 @@ static char* messages[] = #define RFK_VERSION "v1.4142135.406" +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +# define RFK_QUIT PLA_SELECT_REPEAT +#else # define RFK_QUIT PLA_CANCEL +#endif # define RFK_RIGHT PLA_RIGHT # define RFK_LEFT PLA_LEFT # define RFK_UP PLA_UP diff --git a/apps/plugins/rockblox.c b/apps/plugins/rockblox.c index 927710b37b..a0105a1ffb 100644 --- a/apps/plugins/rockblox.c +++ b/apps/plugins/rockblox.c @@ -33,7 +33,7 @@ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define ROCKBLOX_OFF (BUTTON_MENU | BUTTON_SELECT) +#define ROCKBLOX_OFF (BUTTON_SELECT | BUTTON_REPEAT) #define ROCKBLOX_ROTATE_CCW BUTTON_SCROLL_BACK #define ROCKBLOX_ROTATE_CCW2 (BUTTON_MENU | BUTTON_REL) #define ROCKBLOX_ROTATE_CW BUTTON_SCROLL_FWD @@ -41,6 +41,7 @@ #define ROCKBLOX_RIGHT BUTTON_RIGHT #define ROCKBLOX_DOWN BUTTON_PLAY #define ROCKBLOX_RESTART (BUTTON_SELECT | BUTTON_PLAY) +#define ROCKBLOX_DROP_PRE BUTTON_SELECT #define ROCKBLOX_DROP (BUTTON_SELECT | BUTTON_REL) #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ @@ -1498,16 +1499,13 @@ static int rockblox_loop (void) #ifdef HAS_BUTTON_HOLD if (rb->button_hold ()) { /* Turn on backlight timeout (revert to settings) */ -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif rb->splash(0, "Paused"); while (rb->button_hold ()) rb->sleep(HZ/10); -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + /* get rid of the splash text */ rb->lcd_bitmap (rockblox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT); show_details (); @@ -1677,10 +1675,9 @@ enum plugin_status plugin_start (const void *parameter) rb->lcd_setfont (FONT_SYSFIXED); -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + load_game(); resume_file = resume; @@ -1728,9 +1725,8 @@ enum plugin_status plugin_start (const void *parameter) /* Save user's HighScore */ highscore_save(SCORE_FILE, highscores, NUM_SCORES); -#ifdef HAVE_BACKLIGNT + backlight_use_settings(); -#endif return PLUGIN_OK; } diff --git a/apps/plugins/rockblox1d.c b/apps/plugins/rockblox1d.c index 6a2b013c44..6d535bbcd7 100644 --- a/apps/plugins/rockblox1d.c +++ b/apps/plugins/rockblox1d.c @@ -28,7 +28,14 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; #define ONEDROCKBLOX_DOWN PLA_DOWN #define ONEDROCKBLOX_DOWN_REPEAT PLA_DOWN_REPEAT #define ONEDROCKBLOX_QUIT PLA_EXIT + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define ONEDROCKBLOX_QUIT2 PLA_UP +#else #define ONEDROCKBLOX_QUIT2 PLA_CANCEL +#endif #define mrand(max) (short)(rb->rand()%max) diff --git a/apps/plugins/rockboy/menu.c b/apps/plugins/rockboy/menu.c index 6fafdc11a6..870ea389fb 100644 --- a/apps/plugins/rockboy/menu.c +++ b/apps/plugins/rockboy/menu.c @@ -83,9 +83,8 @@ int do_user_menu(void) { rb->lcd_set_mode(LCD_MODE_RGB565); #endif -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + /* Clean out the button Queue */ while (rb->button_get(false) != BUTTON_NONE) rb->yield(); @@ -139,10 +138,9 @@ int do_user_menu(void) { rb->lcd_set_mode(LCD_MODE_PAL256); #endif -#ifdef HAVE_BACKLIGHT /* ignore backlight time out */ backlight_ignore_timeout(); -#endif + return ret; } @@ -430,7 +428,7 @@ static void do_opt_menu(void) options.dirty=1; /* Assume that the settings have been changed */ struct viewport *parentvp = NULL; - const struct settings_list* vol = rb->find_setting(&rb->global_settings->volume, NULL); + const struct settings_list* vol = rb->find_setting(&rb->global_settings->volume); while(!done) { @@ -439,39 +437,39 @@ static void do_opt_menu(void) switch (result) { case 0: /* Frameskip */ - rb->set_option("Max Frameskip", &options.maxskip, INT, frameskip, + rb->set_option("Max Frameskip", &options.maxskip, RB_INT, frameskip, sizeof(frameskip)/sizeof(*frameskip), NULL ); break; case 1: /* Autosave */ - rb->set_option("Autosave", &options.autosave, INT, onoff, 2, NULL ); + rb->set_option("Autosave", &options.autosave, RB_INT, onoff, 2, NULL ); break; case 2: /* Sound */ if(options.sound>1) options.sound=1; - rb->set_option("Sound", &options.sound, INT, onoff, 2, NULL ); + rb->set_option("Sound", &options.sound, RB_INT, onoff, 2, NULL ); if(options.sound) sound_dirty(); break; case 3: /* Volume */ rb->option_screen((struct settings_list*)vol, parentvp, false, "Volume"); break; case 4: /* Stats */ - rb->set_option("Stats", &options.showstats, INT, stats, 3, NULL ); + rb->set_option("Stats", &options.showstats, RB_INT, stats, 3, NULL ); break; case 5: /* Keys */ setupkeys(); break; #ifdef HAVE_LCD_COLOR case 6: /* Screen Size */ - rb->set_option("Screen Size", &options.scaling, INT, scaling, + rb->set_option("Screen Size", &options.scaling, RB_INT, scaling, sizeof(scaling)/sizeof(*scaling), NULL ); setvidmode(); break; case 7: /* Screen rotate */ - rb->set_option("Screen Rotate", &options.rotate, INT, rotate, + rb->set_option("Screen Rotate", &options.rotate, RB_INT, rotate, sizeof(rotate)/sizeof(*rotate), NULL ); setvidmode(); break; case 8: /* Palette */ - rb->set_option("Set Palette", &options.pal, INT, palette, 17, NULL ); + rb->set_option("Set Palette", &options.pal, RB_INT, palette, 17, NULL ); set_pal(); break; #endif diff --git a/apps/plugins/rockboy/rockboy.c b/apps/plugins/rockboy/rockboy.c index 2c5c6e4dbf..2d0c349507 100644 --- a/apps/plugins/rockboy/rockboy.c +++ b/apps/plugins/rockboy/rockboy.c @@ -602,10 +602,9 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_set_mode(LCD_MODE_PAL256); #endif -#ifdef HAVE_BACKLIGHT /* ignore backlight time out */ backlight_ignore_timeout(); -#endif + gnuboy_main(parameter); #ifdef HAVE_WHEEL_POSITION @@ -616,9 +615,7 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_set_mode(LCD_MODE_RGB565); #endif -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif if(!rb->audio_status()) rockboy_pcm_close(); diff --git a/apps/plugins/rocklife.c b/apps/plugins/rocklife.c index 99297abd0f..c4c7842fe0 100644 --- a/apps/plugins/rocklife.c +++ b/apps/plugins/rocklife.c @@ -70,7 +70,13 @@ #define ROCKLIFE_INIT PLA_DOWN #define ROCKLIFE_NEXT PLA_RIGHT #define ROCKLIFE_NEXT_REP PLA_RIGHT_REPEAT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define ROCKLIFE_QUIT PLA_UP +#else #define ROCKLIFE_QUIT PLA_CANCEL +#endif #define ROCKLIFE_STATUS PLA_LEFT #define PATTERN_RANDOM 0 @@ -474,9 +480,8 @@ enum plugin_status plugin_start(const void* parameter) char *ptemp; (void)(parameter); -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #ifdef HAVE_LCD_COLOR @@ -580,8 +585,7 @@ enum plugin_status plugin_start(const void* parameter) rb->yield(); } -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + return usb? PLUGIN_USB_CONNECTED: PLUGIN_OK; } diff --git a/apps/plugins/rockpaint.c b/apps/plugins/rockpaint.c index 09fa2c8c5f..52281edfb0 100644 --- a/apps/plugins/rockpaint.c +++ b/apps/plugins/rockpaint.c @@ -1084,15 +1084,15 @@ static bool callback_show_item(char *name, int attr, struct tree_context *tc) static bool browse( char *dst, int dst_size, const char *start ) { - struct browse_context browse; - - rb->browse_context_init(&browse, SHOW_ALL, - BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, - NULL, NOICON, start, NULL); - - browse.callback_show_item = callback_show_item; - browse.buf = dst; - browse.bufsize = dst_size; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, + .icon = Icon_NOICON, + .root = start, + .buf = dst, + .bufsize = dst_size, + .callback_show_item = callback_show_item, + }; rb->rockbox_browse(&browse); @@ -2869,7 +2869,7 @@ static void goto_menu(void) case MAIN_MENU_BRUSH_SIZE: for(multi = 0; multi<4; multi++) if(bsize == times_list[multi]) break; - rb->set_option( "Brush Size", &multi, INT, times_options, 4, NULL ); + rb->set_option( "Brush Size", &multi, RB_INT, times_options, 4, NULL ); if( multi >= 0 ) bsize = times_list[multi]; break; @@ -2877,7 +2877,7 @@ static void goto_menu(void) case MAIN_MENU_BRUSH_SPEED: for(multi = 0; multi<3; multi++) if(bspeed == times_list[multi]) break; - rb->set_option( "Brush Speed", &multi, INT, times_options, 3, NULL ); + rb->set_option( "Brush Speed", &multi, RB_INT, times_options, 3, NULL ); if( multi >= 0 ) { bspeed = times_list[multi]; incdec_x.step[0] = bspeed; @@ -2894,7 +2894,7 @@ static void goto_menu(void) case MAIN_MENU_GRID_SIZE: for(multi = 0; multi<4; multi++) if(gridsize == gridsize_list[multi]) break; - rb->set_option( "Grid Size", &multi, INT, gridsize_options, 4, NULL ); + rb->set_option( "Grid Size", &multi, RB_INT, gridsize_options, 4, NULL ); if( multi >= 0 ) gridsize = gridsize_list[multi]; break; diff --git a/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c b/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c index 1638732dc5..38f7109b13 100644 --- a/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c +++ b/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c @@ -21,6 +21,8 @@ #include "tables.h" +/* ROCKBOX HACK: avoid a conflict with adjust_volume() in misc.h */ +#define adjust_volume adjust_midi_volume static int opt_expression_curve = 2; static int opt_volume_curve = 2; diff --git a/apps/plugins/sdl/main.c b/apps/plugins/sdl/main.c index 6efb072faf..7220c7cfd9 100644 --- a/apps/plugins/sdl/main.c +++ b/apps/plugins/sdl/main.c @@ -64,9 +64,8 @@ void cleanup(void) if(audiobuf) memset(audiobuf, 0, 4); /* clear */ -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif @@ -219,9 +218,9 @@ enum plugin_status plugin_start(const void *param) #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + /* set the real exit handler */ #undef rb_atexit rb_atexit(cleanup); diff --git a/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c b/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c index cb72687d48..ac268b4a19 100644 --- a/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c +++ b/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c @@ -132,38 +132,42 @@ static void ROCKBOXAUD_WaitAudio(_THIS) } /* when this is called, SDL wants us to play the samples in mixbuf */ -static void ROCKBOXAUD_PlayAudio(_THIS) +static void +ROCKBOXAUD_PlayAudio(_THIS) { /* There are two cases in which we should be called: * - There is an empty buffer (marked with status = 0) * - There are more than two buffers marked as playing, meaning at least one is stale. */ - int idx = -1; /* Find the next empty or stale buffer and fill. */ - for(int i = 1; i < this->hidden->n_buffers; ++i) + for(int i = 1; i <= this->hidden->n_buffers; ++i) { - idx = (this->hidden->current_playing + i) % this->hidden->n_buffers; + int idx = (this->hidden->current_playing + i) % this->hidden->n_buffers; /* Empty or stale. */ if(this->hidden->status[idx] == 0 || - this->hidden->status[idx] == 2) - break; - } - if(idx < 0) - return; + this->hidden->status[idx] == 2) { + + LOGF("found empty buffer: %d (status: %d)", idx, this->hidden->status[idx]); + + /* probably premature optimization here */ + char *dst = (char*)this->hidden->rb_buf[idx], *src = this->hidden->mixbuf; + int size = this->spec.size / 2; + memcpy(dst, src, size); - /* probably premature optimization here */ - char *dst = (char*)this->hidden->rb_buf[idx], *src = this->hidden->mixbuf; - int size = this->hidden->mixlen / 2; - memcpy(dst, src, size); + this->hidden->status[idx] = 1; + rb->yield(); - this->hidden->status[idx] = 1; - rb->yield(); + memcpy(dst + size, src + size, this->spec.size - size); - memcpy(dst + size, src + size, this->hidden->mixlen - size); + LOGF("filled buffer %d (status %d %d %d %d)", idx, this->hidden->status[0], this->hidden->status[1], this->hidden->status[2], this->hidden->status[3]); + + return; + } + } - //LOGF("filled buffer %d (status %d %d %d)", idx, this->hidden->status[0], this->hidden->status[1], this->hidden->status[2]); + LOGF("WARNING: PlayDevice could not find buffer to fill; DROPPING SAMPLES!"); } static SDL_AudioDevice *ROCKBOXAUD_CreateDevice(int devindex) diff --git a/apps/plugins/shopper.c b/apps/plugins/shopper.c index 7129291c10..25a484a31e 100644 --- a/apps/plugins/shopper.c +++ b/apps/plugins/shopper.c @@ -304,7 +304,6 @@ enum plugin_status plugin_start(const void* parameter) /* now dump it in the list */ rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL); rb->gui_synclist_set_icon_callback(&lists, list_get_icon_cb); - rb->gui_synclist_limit_scroll(&lists,true); create_view(&lists); rb->gui_synclist_set_nb_items(&lists,view_item_count); rb->gui_synclist_select_item(&lists, 0); @@ -316,7 +315,7 @@ enum plugin_status plugin_start(const void* parameter) rb->gui_synclist_draw(&lists); cur_sel = rb->gui_synclist_get_sel_pos(&lists); button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&lists,&button,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &button)) continue; switch (button) { diff --git a/apps/plugins/shortcuts/shortcuts_view.c b/apps/plugins/shortcuts/shortcuts_view.c index f4c4b58bc1..e0146f3174 100644 --- a/apps/plugins/shortcuts/shortcuts_view.c +++ b/apps/plugins/shortcuts/shortcuts_view.c @@ -32,7 +32,7 @@ enum sc_list_action_type SCLA_USB, }; - +static size_t root_len; static char *link_filename; static bool user_file; @@ -59,13 +59,8 @@ enum sc_list_action_type draw_sc_list(struct gui_synclist *gui_sc) /* user input */ button = rb->get_action(CONTEXT_LIST, HZ); /* HZ so the status bar redraws corectly */ - if (rb->gui_synclist_do_button(gui_sc, &button, - LIST_WRAP_UNLESS_HELD)) { - /* automatic handling of user input. - * _UNLESS_HELD can be _ON or _OFF also - * selection changed, so redraw */ + if (rb->gui_synclist_do_button(gui_sc, &button)) continue; - } switch (button) { /* process the user input */ case ACTION_STD_OK: return SCLA_SELECT; @@ -115,7 +110,6 @@ int list_sc(void) rb->gui_synclist_set_title(&gui_sc, (user_file?"Shortcuts (sealed)":"Shortcuts (editable)"), NOICON); rb->gui_synclist_set_nb_items(&gui_sc, sc_file.entry_cnt); - rb->gui_synclist_limit_scroll(&gui_sc, false); rb->gui_synclist_select_item(&gui_sc, 0); /* Draw the prepared widget to the LCD now */ @@ -181,6 +175,42 @@ bool goto_entry(char *file_or_dir) } #endif +static bool callback_show_item(char *name, int attr, struct tree_context *tc) +{ + (void)name; + if(attr & ATTR_DIRECTORY) + { + if ((tc->browse->flags & BROWSE_SELECTED) == 0 && + rb->strlen(tc->currdir) < root_len) + { + tc->is_browsing = false; /* exit immediately */ + } + } + + return true; +} + +bool open_browse(char *path, char *buf, size_t bufsz) +{ + struct browse_context browse = { + .dirfilter = rb->global_settings->dirfilter, + .flags = BROWSE_DIRFILTER| BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, + .title = path, + .icon = Icon_Plugin, + .root = path, + .buf = buf, + .bufsize = bufsz, + .callback_show_item = callback_show_item, + }; + root_len = 0; + char *name = rb->strrchr(path, '/'); + if (name) + root_len = name - path; + rb->rockbox_browse(&browse); + + return (browse.flags & BROWSE_SELECTED); +} + int goto_entry(char *file_or_dir) { DEBUGF("Trying to go to '%s'...\n", file_or_dir); @@ -208,14 +238,19 @@ int goto_entry(char *file_or_dir) } else { - /* Set the browsers dirfilter to the global setting - * This is required in case the plugin was launched - * from the plugins browser, in which case the - * dirfilter is set to only display .rock files */ - rb->set_dirfilter(rb->global_settings->dirfilter); - - /* Change directory to the entry selected by the user */ - rb->set_current_file(file_or_dir); + if (!is_dir) + { + rb->set_current_file(file_or_dir); + return LOOP_EXIT; + } + char tmp_buf[MAX_PATH]; + if (open_browse(file_or_dir, tmp_buf, sizeof(tmp_buf))) + { + DEBUGF("Trying to load '%s'...\n", tmp_buf); + rb->set_dirfilter(rb->global_settings->dirfilter); + rb->set_current_file(tmp_buf); + return LOOP_EXIT; + } } return PLUGIN_OK; } diff --git a/apps/plugins/sliding_puzzle.c b/apps/plugins/sliding_puzzle.c index a34cb77669..33d2bb68f6 100644 --- a/apps/plugins/sliding_puzzle.c +++ b/apps/plugins/sliding_puzzle.c @@ -36,7 +36,7 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define PUZZLE_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define PUZZLE_QUIT (BUTTON_SELECT | BUTTON_REPEAT) #define PUZZLE_LEFT BUTTON_LEFT #define PUZZLE_RIGHT BUTTON_RIGHT #define PUZZLE_UP BUTTON_MENU @@ -468,8 +468,8 @@ static const char * initial_bmp_path=NULL; static const char * get_albumart_bmp_path(void) { struct mp3entry* track = rb->audio_current_track(); - - if (!track || !track->path || track->path[0] == '\0') + /* Note rb->audio_current_track->path should never be null */ + if (!track || track->path[0] == '\0') return NULL; if (!rb->search_albumart_files(track, "", albumart_path, MAX_PATH ) ) @@ -848,7 +848,7 @@ enum plugin_status plugin_start( #if (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) - rb->lcd_putsxy(0, 18, "[S-MENU] to stop"); + rb->lcd_putsxy(0, 18, "Long [SELECT] to stop"); rb->lcd_putsxy(0, 28, "[S-LEFT] shuffle"); rb->lcd_putsxy(0, 38, "[S-RIGHT] change pic"); #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ diff --git a/apps/plugins/snake.c b/apps/plugins/snake.c index 25c89b264b..359077c9fa 100644 --- a/apps/plugins/snake.c +++ b/apps/plugins/snake.c @@ -55,12 +55,12 @@ dir is the current direction of the snake - 0=up, 1=right, 2=down, 3=left; #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define SNAKE_QUIT (BUTTON_SELECT|BUTTON_MENU) +#define SNAKE_QUIT (BUTTON_SELECT|BUTTON_REPEAT) #define SNAKE_LEFT BUTTON_LEFT #define SNAKE_RIGHT BUTTON_RIGHT #define SNAKE_UP BUTTON_MENU #define SNAKE_DOWN BUTTON_PLAY -#define SNAKE_PLAYPAUSE BUTTON_SELECT +#define SNAKE_PLAYPAUSE (BUTTON_SELECT|BUTTON_REL) #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) #define SNAKE_QUIT BUTTON_POWER diff --git a/apps/plugins/snake2.c b/apps/plugins/snake2.c index 094fd854eb..c71fa7f247 100644 --- a/apps/plugins/snake2.c +++ b/apps/plugins/snake2.c @@ -181,8 +181,8 @@ Head and Tail are stored #define SNAKE2_RIGHT BUTTON_RIGHT #define SNAKE2_UP BUTTON_MENU #define SNAKE2_DOWN BUTTON_PLAY -#define SNAKE2_QUIT (BUTTON_SELECT | BUTTON_MENU) -#define SNAKE2_PLAYPAUSE BUTTON_SELECT +#define SNAKE2_QUIT (BUTTON_SELECT | BUTTON_REPEAT) +#define SNAKE2_PLAYPAUSE (BUTTON_SELECT | BUTTON_REL) #define SNAKE2_PLAYPAUSE_TEXT "Select" #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) @@ -1599,7 +1599,7 @@ static void game_init(void) speed = level*20; return; case 1: - rb->set_option("Game Type", &game_type, INT, + rb->set_option("Game Type", &game_type, RB_INT, type_options, 2, NULL); break; case 2: diff --git a/apps/plugins/snow.c b/apps/plugins/snow.c index 10b41c972b..c7d7ad31d8 100644 --- a/apps/plugins/snow.c +++ b/apps/plugins/snow.c @@ -30,8 +30,14 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; /* PLA definitions */ #define SNOW_QUIT PLA_EXIT -#define SNOW_QUIT2 PLA_CANCEL +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define SNOW_QUIT2 PLA_UP +#else +#define SNOW_QUIT2 PLA_CANCEL +#endif static short particles[NUM_PARTICLES][2]; #if LCD_WIDTH >= 160 diff --git a/apps/plugins/sokoban.c b/apps/plugins/sokoban.c index 247663a5c2..bf61db7d88 100644 --- a/apps/plugins/sokoban.c +++ b/apps/plugins/sokoban.c @@ -125,7 +125,7 @@ #define SOKOBAN_RIGHT BUTTON_RIGHT #define SOKOBAN_UP BUTTON_MENU #define SOKOBAN_DOWN BUTTON_PLAY -#define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_MENU) +#define SOKOBAN_MENU (BUTTON_SELECT | BUTTON_REPEAT) #define SOKOBAN_UNDO_PRE BUTTON_SELECT #define SOKOBAN_UNDO (BUTTON_SELECT | BUTTON_REL) #define SOKOBAN_REDO (BUTTON_SELECT | BUTTON_PLAY) diff --git a/apps/plugins/solitaire.c b/apps/plugins/solitaire.c index b1ede16f90..fde3d04a0b 100644 --- a/apps/plugins/solitaire.c +++ b/apps/plugins/solitaire.c @@ -56,7 +56,7 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -# define SOL_QUIT (BUTTON_SELECT | BUTTON_MENU) +# define SOL_QUIT (BUTTON_SELECT | BUTTON_REPEAT) # define SOL_UP BUTTON_SCROLL_BACK # define SOL_DOWN BUTTON_SCROLL_FWD # define SOL_LEFT_PRE BUTTON_LEFT @@ -1088,7 +1088,7 @@ static int solitaire_menu(bool in_game) case 2: if (rb->set_option("Draw Cards Option", &sol.draw_type, - INT, drawcards, 2, NULL)) + RB_INT, drawcards, 2, NULL)) result = MENU_USB; break; @@ -2149,6 +2149,7 @@ static int solitaire( int skipmenu ) break; case SYS_POWEROFF: + case SYS_REBOOT: return SOLITAIRE_SAVE_AND_QUIT; default: diff --git a/apps/plugins/spacerocks.c b/apps/plugins/spacerocks.c index 8203fad612..36729f8453 100644 --- a/apps/plugins/spacerocks.c +++ b/apps/plugins/spacerocks.c @@ -51,10 +51,10 @@ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define AST_PAUSE (BUTTON_SELECT | BUTTON_PLAY) -#define AST_QUIT (BUTTON_SELECT | BUTTON_MENU) -#define AST_THRUST BUTTON_MENU -#define AST_HYPERSPACE BUTTON_PLAY +#define AST_PAUSE BUTTON_PLAY +#define AST_QUIT BUTTON_MENU +#define AST_THRUST BUTTON_RIGHT +#define AST_HYPERSPACE BUTTON_LEFT #define AST_LEFT BUTTON_SCROLL_BACK #define AST_RIGHT BUTTON_SCROLL_FWD #define AST_FIRE BUTTON_SELECT @@ -259,8 +259,8 @@ #define AST_QUIT BUTTON_POWER #define AST_THRUST BUTTON_UP #define AST_HYPERSPACE BUTTON_DOWN -#define AST_LEFT BUTTON_LEFT -#define AST_RIGHT BUTTON_RIGHT +#define AST_LEFT BUTTON_SCROLL_BACK +#define AST_RIGHT BUTTON_SCROLL_FWD #define AST_FIRE BUTTON_SELECT #elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) @@ -2128,10 +2128,10 @@ enum plugin_status plugin_start(const void* parameter) #endif /* universal font */ rb->lcd_setfont(FONT_SYSFIXED); -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + highscore_load(SCORE_FILE, highscores, NUM_SCORES); rb->srand(*rb->current_tick); @@ -2143,10 +2143,9 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_setfont(FONT_UI); highscore_save(SCORE_FILE, highscores, NUM_SCORES); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif return ret; } diff --git a/apps/plugins/speedread.c b/apps/plugins/speedread.c index 42634fb536..7a9ab61e7c 100644 --- a/apps/plugins/speedread.c +++ b/apps/plugins/speedread.c @@ -141,9 +141,9 @@ static void cleanup(void) { if(custom_font != FONT_UI) rb->font_unload(custom_font); -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + } /* returns height of drawn area */ @@ -302,9 +302,8 @@ static void begin_anim(void) static void init_drawing(void) { -#ifdef HAVE_BACKLIGHT backlight_ignore_timeout(); -#endif + atexit(cleanup); rb->lcd_set_background(OUTSIDE_COLOR); @@ -483,16 +482,19 @@ static void load_font(void) static void font_menu(void) { /* taken from text_viewer */ - struct browse_context browse; char font[MAX_PATH], name[MAX_FILENAME+10]; - rb->snprintf(name, sizeof(name), "%s.fnt", rb->global_settings->font_file); - rb->browse_context_init(&browse, SHOW_FONT, - BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, - "Font", Icon_Menu_setting, FONT_DIR, name); - browse.buf = font; - browse.bufsize = sizeof(font); + struct browse_context browse = { + .dirfilter = SHOW_FONT, + .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, + .title = rb->str(LANG_CUSTOM_FONT), + .icon = Icon_Menu_setting, + .root = FONT_DIR, + .selected = name, + .buf = font, + .bufsize = sizeof(font), + }; rb->rockbox_browse(&browse); diff --git a/apps/plugins/star.c b/apps/plugins/star.c index 874afc1cf1..59cefa2c15 100644 --- a/apps/plugins/star.c +++ b/apps/plugins/star.c @@ -80,7 +80,7 @@ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define STAR_QUIT (BUTTON_SELECT | BUTTON_MENU) +#define STAR_QUIT (BUTTON_SELECT | BUTTON_REPEAT) #define STAR_LEFT BUTTON_LEFT #define STAR_RIGHT BUTTON_RIGHT #define STAR_UP BUTTON_MENU @@ -91,7 +91,7 @@ #define STAR_LEVEL_DOWN (BUTTON_SELECT | BUTTON_LEFT) #define STAR_LEVEL_REPEAT (BUTTON_SELECT | BUTTON_PLAY) #define STAR_TOGGLE_CONTROL_NAME "SELECT" -#define STAR_QUIT_NAME "S + MENU" +#define STAR_QUIT_NAME "Long SELECT" #define STAR_LEVEL_UP_NAME "S >" #define STAR_LEVEL_DOWN_NAME "S <" #define STAR_LEVEL_REPEAT_NAME "S + PLAY" diff --git a/apps/plugins/starfield.c b/apps/plugins/starfield.c index 7fc400d0ee..239b7c1396 100644 --- a/apps/plugins/starfield.c +++ b/apps/plugins/starfield.c @@ -27,11 +27,21 @@ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; /* Key assignement */ #define STARFIELD_QUIT PLA_EXIT +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define STARFIELD_QUIT2 PLA_UP +#define STARFIELD_INCREASE_ZMOVE PLA_SCROLL_FWD +#define STARFIELD_INCREASE_ZMOVE_REPEAT PLA_SCROLL_FWD_REPEAT +#define STARFIELD_DECREASE_ZMOVE PLA_SCROLL_BACK +#define STARFIELD_DECREASE_ZMOVE_REPEAT PLA_SCROLL_BACK_REPEAT +#else #define STARFIELD_QUIT2 PLA_CANCEL #define STARFIELD_INCREASE_ZMOVE PLA_UP #define STARFIELD_INCREASE_ZMOVE_REPEAT PLA_UP_REPEAT #define STARFIELD_DECREASE_ZMOVE PLA_DOWN #define STARFIELD_DECREASE_ZMOVE_REPEAT PLA_DOWN_REPEAT +#endif #define STARFIELD_INCREASE_NB_STARS PLA_RIGHT #define STARFIELD_INCREASE_NB_STARS_REPEAT PLA_RIGHT_REPEAT #define STARFIELD_DECREASE_NB_STARS PLA_LEFT @@ -324,14 +334,14 @@ enum plugin_status plugin_start(const void* parameter) int ret; (void)parameter; -#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + ret = plugin_main(); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return ret; } diff --git a/apps/plugins/stats.c b/apps/plugins/stats.c index 19ccd9f452..b48259a0e4 100644 --- a/apps/plugins/stats.c +++ b/apps/plugins/stats.c @@ -29,7 +29,15 @@ static bool cancel; /* we use PLA */ #define STATS_STOP PLA_EXIT + +#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) +#define STATS_STOP2 PLA_UP +#else #define STATS_STOP2 PLA_CANCEL +#endif + /* this set the context to use with PLA */ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; @@ -149,7 +157,7 @@ static void traversedir(char* location, char* name) lasttick = *rb->current_tick; button = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); - if (button == STATS_STOP) { + if (button == STATS_STOP || button == STATS_STOP2) { cancel = true; break; } diff --git a/apps/plugins/sudoku/sudoku.c b/apps/plugins/sudoku/sudoku.c index 34a1f6dd07..3ede4e8af6 100644 --- a/apps/plugins/sudoku/sudoku.c +++ b/apps/plugins/sudoku/sudoku.c @@ -867,7 +867,7 @@ static bool numdisplay_setting(void) {"Coloured", -1}, }; - return rb->set_option("Number Display", &sudcfg.number_display, INT, names, + return rb->set_option("Number Display", &sudcfg.number_display, RB_INT, names, sizeof(names) / sizeof(names[0]), NULL); } #endif @@ -880,7 +880,7 @@ static bool showmarkings_setting(void) {"Show", -1}, }; - return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names, + return rb->set_option("Show Markings", &sudcfg.show_markings, RB_INT, names, sizeof(names) / sizeof(names[0]), NULL); } #endif diff --git a/apps/plugins/superdom.c b/apps/plugins/superdom.c index 50027a30c6..79a6d1a8f2 100644 --- a/apps/plugins/superdom.c +++ b/apps/plugins/superdom.c @@ -583,7 +583,7 @@ static int settings_menu(void) }; static int sel=1; rb->set_option("Computer difficulty", &sel, - INT, difficulty_options, 3, NULL); + RB_INT, difficulty_options, 3, NULL); superdom_settings.compdiff=sel+1; break; } @@ -1450,7 +1450,6 @@ static int show_inventory(void) { struct simplelist_info info; rb->simplelist_info_init(&info, "Inventory", 9, NULL); - info.hide_selection = true; info.get_name = inventory_data; if(rb->simplelist_show_list(&info)) { diff --git a/apps/plugins/tagcache/SOURCES b/apps/plugins/tagcache/SOURCES new file mode 100644 index 0000000000..2541f3e87c --- /dev/null +++ b/apps/plugins/tagcache/SOURCES @@ -0,0 +1,2 @@ +tagcache.c + diff --git a/apps/plugins/tagcache/tagcache.c b/apps/plugins/tagcache/tagcache.c new file mode 100644 index 0000000000..cce9efbed9 --- /dev/null +++ b/apps/plugins/tagcache/tagcache.c @@ -0,0 +1,1021 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2023 by William Wilgus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/*Plugin Includes*/ + +#include "plugin.h" +#include "errno.h" + +/* Redefinitons of ANSI C functions. */ +#include "lib/wrappers.h" +#include "lib/helper.h" + +static void thread_create(void); +static void thread(void); /* the thread running commit*/ +static void allocate_tempbuf(void); +static void free_tempbuf(void); +static bool do_timed_yield(void); +static void _log(const char *fmt, ...); +static bool logdump(bool append); +/*Aliases*/ +#if 0 +#ifdef ROCKBOX_HAS_LOGF + #define logf rb->logf +#else + #define logf(...) {} +#endif +#endif + +#define logf _log +#define sleep rb->sleep +#define qsort rb->qsort + +#define write(x,y,z) rb->write(x,y,z) +#define ftruncate rb->ftruncate +#define remove rb->remove +#define rename rb->rename + +#define vsnprintf rb->vsnprintf +#define mkdir rb->mkdir +#define filesize rb->filesize + +#define strtok_r rb->strtok_r +#define strncasecmp rb->strncasecmp +#define strcasecmp rb->strcasecmp + +#define current_tick (*rb->current_tick) +#define crc_32(x,y,z) rb->crc_32(x,y,z) +#define plugin_get_buffer rb->plugin_get_buffer + +#define MAX_LOG_SIZE 16384 + +#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF) +#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01) + +#define BACKUP_DIRECTORY PLUGIN_APPS_DATA_DIR "/db_commit" + +#define RC_SUCCESS 0 +#define RC_USER_CANCEL 2 + +enum fcpy_op_flags +{ + FCPY_MOVE = 0x00, /* Is a move operation (default) */ + FCPY_COPY = 0x01, /* Is a copy operation */ + FCPY_OVERWRITE = 0x02, /* Overwrite destination */ + FCPY_EXDEV = 0x04, /* Actually copy/move across volumes */ +}; + +/* communication to the worker thread */ +static struct +{ + int user_index; + bool exiting; /* signal to the thread that we want to exit */ + bool resume; + unsigned int id; /* worker thread id */ + struct event_queue queue; /* thread event queue */ + long last_useraction_tick; +} gThread; + +/* status of db and commit */ +static struct +{ + long last_check; + bool do_commit; + bool auto_commit; + bool commit_ready; + bool db_exists; + bool have_backup; + bool bu_exists; +} gStatus; + +static unsigned char logbuffer[MAX_LOG_SIZE + 1]; +static int log_font_h = -1; +static int logindex; +static bool logwrap; +static bool logenabled = true; + +/*Support Fns*/ +/* open but with a builtin printf for assembling the path */ +int open_pathfmt(char *buf, size_t size, int oflag, const char *pathfmt, ...) +{ + va_list ap; + va_start(ap, pathfmt); + vsnprintf(buf, size, pathfmt, ap); + va_end(ap); + if ((oflag & O_PATH) == O_PATH) + return -1; + int handle = open(buf, oflag, 0666); + //logf("Open: %s %d flag: %x", buf, handle, oflag); + return handle; +} + +static void sleep_yield(void) +{ + sleep(1); + #undef yield + rb->yield(); + #define yield sleep_yield +} + +/* make sure tag can be displayed by font pf*/ +static bool text_is_displayable(struct font *pf, unsigned char *src) +{ + unsigned short code; + const unsigned char *ptr = src; + while(*ptr) + { + ptr = rb->utf8decode(ptr, &code); + + if(!rb->font_get_bits(pf, code)) + { + return false; + } + } + return true; +} + +/* callback for each tag if false returned tag will not be added */ +bool user_check_tag(int index_type, char* build_idx_buf) +{ + static struct font *pf = NULL; + if (!pf) + pf = rb->font_get(FONT_UI); + + if (index_type == tag_artist || index_type == tag_album || + index_type == tag_genre || index_type == tag_title || + index_type == tag_composer || index_type == tag_comment || + index_type == tag_albumartist || index_type == tag_virt_canonicalartist) + { + /* this could be expanded with more rules / transformations */ + if (rb->utf8length(build_idx_buf) != strlen(build_idx_buf)) + { + if (!text_is_displayable(pf, build_idx_buf)) + { + logf("Can't display (%d) %s", index_type, build_idx_buf); + } + } + } + return true; +} + +/* undef features we don't need */ +#undef HAVE_DIRCACHE +#undef HAVE_TC_RAMCACHE +#undef HAVE_EEPROM_SETTINGS +/* paste the whole tagcache.c file */ +#include "../tagcache.c" + +static void check_logindex(void) +{ + if(logindex >= MAX_LOG_SIZE) + { + logdump(true); + //logwrap = true; + logindex = 0; + } +} + +static int log_push(void *userp, int c) +{ + (void)userp; + + logbuffer[logindex++] = c; + check_logindex(); + + return 1; +} + +/* our logf function */ +static void _log(const char *fmt, ...) +{ + if (!logenabled) + { + rb->splash(10, "log not enabled"); + return; + } + + #ifdef USB_ENABLE_SERIAL + int old_logindex = logindex; + #endif + va_list ap; + + va_start(ap, fmt); + +#if (CONFIG_PLATFORM & PLATFORM_HOSTED) + char buf[1024]; + vsnprintf(buf, sizeof buf, fmt, ap); + DEBUGF("%s\n", buf); + /* restart va_list otherwise the result if undefined when vuprintf is called */ + va_end(ap); + va_start(ap, fmt); +#endif + + rb->vuprintf(log_push, NULL, fmt, ap); + va_end(ap); + + /* add trailing zero */ + log_push(NULL, '\0'); +} + + +int compute_nb_lines(int w, struct font* font) +{ + int i, nb_lines; + int cur_x, delta_x; + + if(logindex>= MAX_LOG_SIZE || (logindex == 0 && !logwrap)) + return 0; + + if(logwrap) + i = logindex; + else + i = 0; + + cur_x = 0; + nb_lines = 0; + + do { + if(logbuffer[i] == '\0') + { + cur_x = 0; + nb_lines++; + } + else + { + /* does character fit on this line ? */ + delta_x = rb->font_get_width(font, logbuffer[i]); + + if(cur_x + delta_x > w) + { + cur_x = 0; + nb_lines++; + } + + /* update pointer */ + cur_x += delta_x; + } + + i++; + if(i >= MAX_LOG_SIZE) + i = 0; + } while(i != logindex); + + return nb_lines; +} + +static bool logdisplay(void) +{ + + int w, h, i, index; + int fontnr; + int cur_x, cur_y, delta_x; + struct font* font; + + char buf[2]; + + fontnr = FONT_FIRSTUSERFONT; + font = rb->font_get(fontnr); + buf[1] = '\0'; + w = LCD_WIDTH; + h = LCD_HEIGHT; + + if (log_font_h < 0) /* init, get the horizontal size of each line */ + { + rb->font_getstringsize("A", NULL, &log_font_h, fontnr); + /* start at the end of the log */ + gThread.user_index = compute_nb_lines(w, font) - h/log_font_h -1; + /* user_index will be number of the first line to display (warning: line!=log entry) */ + /* if negative, will be set 0 to zero later */ + } + + rb->lcd_clear_display(); + + if(gThread.user_index < 0 || gThread.user_index >= MAX_LOG_SIZE) + gThread.user_index = 0; + + if(logwrap) + i = logindex; + else + i = 0; + + index = 0; + cur_x = 0; + cur_y = 0; + + /* nothing to print ? */ + if(logindex == 0 && !logwrap) + goto end_print; + + do { + if(logbuffer[i] == '\0') + { + /* should be display a newline ? */ + if(index >= gThread.user_index) + cur_y += log_font_h; + cur_x = 0; + index++; + } + else + { + /* does character fit on this line ? */ + delta_x = rb->font_get_width(font, logbuffer[i]); + + if(cur_x + delta_x > w) + { + /* should be display a newline ? */ + if(index >= gThread.user_index) + cur_y += log_font_h; + cur_x = 0; + index++; + } + + /* should we print character ? */ + if(index >= gThread.user_index) + { + buf[0] = logbuffer[i]; + rb->lcd_putsxy(cur_x, cur_y, buf); + } + + /* update pointer */ + cur_x += delta_x; + } + i++; + /* did we fill the screen ? */ + if(cur_y > h - log_font_h) + { + if (TIME_AFTER(current_tick, gThread.last_useraction_tick + HZ)) + gThread.user_index++; + break; + } + + if(i >= MAX_LOG_SIZE) + i = 0; + } while(i != logindex); + + end_print: + rb->lcd_update(); + + return false; +} + +static bool logdump(bool append) +{ + int fd; + int flags = O_CREAT|O_WRONLY|O_TRUNC; + /* nothing to print ? */ + if(!logenabled || (logindex == 0 && !logwrap)) + { + /* nothing is logged just yet */ + return false; + } + if (append) + { + flags = O_CREAT|O_WRONLY|O_APPEND; + } + + fd = open(ROCKBOX_DIR "/db_commit_log.txt", flags, 0666); + logenabled = false; + if(-1 != fd) { + int i; + + if(logwrap) + i = logindex; + else + i = 0; + + do { + if(logbuffer[i]=='\0') + rb->fdprintf(fd, "\n"); + else + rb->fdprintf(fd, "%c", logbuffer[i]); + + i++; + if(i >= MAX_LOG_SIZE) + i = 0; + } while(i != logindex); + + close(fd); + } + + logenabled = true; + + return false; +} + +static void allocate_tempbuf(void) +{ + tempbuf_size = 0; + tempbuf = rb->plugin_get_audio_buffer(&tempbuf_size); + tempbuf_size &= ~0x03; + +} + +static void free_tempbuf(void) +{ + if (tempbuf_size == 0) + return ; + + rb->plugin_release_audio_buffer(); + tempbuf = NULL; + tempbuf_size = 0; +} + +static bool do_timed_yield(void) +{ + /* Sorting can lock up for quite a while, so yield occasionally */ + static long wakeup_tick = 0; + if (TIME_AFTER(current_tick, wakeup_tick)) + { + yield(); + + wakeup_tick = current_tick + (HZ/5); + return true; + } + return false; +} + +/* copy/move a file */ +static int fcpy(const char *src, const char *target, + unsigned int flags, bool (*poll_cancel)(const char *)) +{ + int rc = -1; + + while (!(flags & (FCPY_COPY | FCPY_EXDEV))) { + if ((flags & FCPY_OVERWRITE) || !file_exists(target)) { + /* Rename and possibly overwrite the file */ + if (poll_cancel && poll_cancel(src)) { + rc = RC_USER_CANCEL; + } else { + rc = rename(src, target); + } + + #ifdef HAVE_MULTIVOLUME + if (rc < 0 && errno == EXDEV) { + /* Failed because cross volume rename doesn't work; force + a move instead */ + flags |= FCPY_EXDEV; + break; + } + #endif /* HAVE_MULTIVOLUME */ + } + + return rc; + } + + /* See if we can get the plugin buffer for the file copy buffer */ + size_t buffersize; + char *buffer = (char *) plugin_get_buffer(&buffersize); + if (buffer == NULL || buffersize < 512) { + /* Not large enough, try for a disk sector worth of stack + instead */ + buffersize = 512; + buffer = (char *)alloca(buffersize); + } + + if (buffer == NULL) { + return -1; + } + + buffersize &= ~0x1ff; /* Round buffer size to multiple of sector + size */ + + int src_fd = open(src, O_RDONLY); + if (src_fd >= 0) { + int oflag = O_WRONLY|O_CREAT; + + if (!(flags & FCPY_OVERWRITE)) { + oflag |= O_EXCL; + } + + int target_fd = open(target, oflag, 0666); + if (target_fd >= 0) { + off_t total_size = 0; + off_t next_cancel_test = 0; /* No excessive button polling */ + + rc = RC_SUCCESS; + + while (rc == RC_SUCCESS) { + if (total_size >= next_cancel_test) { + next_cancel_test = total_size + 0x10000; + if (poll_cancel && poll_cancel(src)) { + rc = RC_USER_CANCEL; + break; + } + } + + ssize_t bytesread = read(src_fd, buffer, buffersize); + if (bytesread <= 0) { + if (bytesread < 0) { + rc = -1; + } + /* else eof on buffer boundary; nothing to write */ + break; + } + + ssize_t byteswritten = write(target_fd, buffer, bytesread); + if (byteswritten < bytesread) { + /* Some I/O error */ + rc = -1; + break; + } + + total_size += byteswritten; + + if (bytesread < (ssize_t)buffersize) { + /* EOF with trailing bytes */ + break; + } + } + + if (rc == RC_SUCCESS) { + /* If overwriting, set the correct length if original was + longer */ + rc = ftruncate(target_fd, total_size); + } + + close(target_fd); + + if (rc != RC_SUCCESS) { + /* Copy failed. Cleanup. */ + remove(target); + } + } + + close(src_fd); + } + + if (rc == RC_SUCCESS && !(flags & FCPY_COPY)) { + /* Remove the source file */ + rc = remove(src); + } + + return rc; +} + +static bool backup_restore_tagcache(bool backup) +{ + struct master_header tcmh; + char path[MAX_PATH]; + char bu_path[MAX_PATH]; + int fd; + int rc; + + if (backup) + { + if (!rb->dir_exists(BACKUP_DIRECTORY)) + mkdir(BACKUP_DIRECTORY); + snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path); + snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY); + } + else + { + if (!rb->dir_exists(BACKUP_DIRECTORY)) + return false; + snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY); + snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path); + } + + fd = open(path, O_RDONLY, 0666); + + if (fd >= 0) + { + rc = read(fd, &tcmh, sizeof(struct master_header)); + close(fd); + if (rc != sizeof(struct master_header)) + { + logf("master file read failed"); + return false; + } + int entries = tcmh.tch.entry_count; + + logf("master file %d entries", entries); + if (backup) + logf("backup db to %s", BACKUP_DIRECTORY); + else + logf("restore db to %s", tc_stat.db_path); + + if (entries > 0) + { + logf("%s", path); + fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL); + + for (int i = 0; i < TAG_COUNT; i++) + { + if (TAGCACHE_IS_NUMERIC(i)) + continue; + snprintf(path, sizeof(path), + "%s/"TAGCACHE_FILE_INDEX, tc_stat.db_path, i); + + snprintf(bu_path, sizeof(bu_path), + "%s/"TAGCACHE_FILE_INDEX, BACKUP_DIRECTORY, i); + /* Note: above we swapped paths in the snprintf call here we swap variables */ + if (backup) + { + logf("%s", path); + if (fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0) + goto failed; + gStatus.have_backup = true; + } + else + { + logf("%s", bu_path); + if (fcpy(bu_path, path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0) + goto failed; + } + } + } + return true; + } +failed: + if (backup) + { + logf("failed backup"); + } + + return false; +} + +/* asks the user if they wish to quit */ +static bool confirm_quit(void) +{ + const struct text_message prompt = + { (const char*[]) {"Are you sure?", "This will abort commit."}, 2}; + enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); + return (response == YESNO_YES); +} + +/* asks the user if they wish to backup/restore */ +static bool prompt_backup_restore(bool backup) +{ + const struct text_message bu_prompt = { (const char*[]) {"Backup database?"}, 1}; + const struct text_message re_prompt = + { (const char*[]) {"Error committing,", "Restore database?"}, 2}; + enum yesno_res response = + rb->gui_syncyesno_run(backup ? &bu_prompt:&re_prompt, NULL, NULL); + if(response == YESNO_YES) + return backup_restore_tagcache(backup); + return true; +} + +static const char* list_get_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + (void) data; + (void) buf; + (void) buf_len; + + /* buf supplied isn't used so lets use it for a filename buffer */ + if (TIME_AFTER(current_tick, gStatus.last_check)) + { + snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path); + gStatus.auto_commit = !file_exists(buf); + snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_TEMP, tc_stat.db_path); + gStatus.commit_ready = file_exists(buf); + snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path); + gStatus.db_exists = file_exists(buf); + snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY); + gStatus.bu_exists = file_exists(buf); + gStatus.last_check = current_tick + HZ; + buf[0] = '\0'; + } + + switch(selected_item) + { + + case 0: /* exit */ + return ID2P(LANG_MENU_QUIT); + case 1: /*sep*/ + return ID2P(VOICE_BLANK); + case 2: /*backup*/ + if (!gStatus.db_exists) + return ID2P(VOICE_BLANK); + return "Backup"; + case 3: /*restore*/ + if (!gStatus.bu_exists) + return ID2P(VOICE_BLANK); + return "Restore"; + case 4: /*sep*/ + return ID2P(VOICE_BLANK); + case 5: /*auto commit*/ + if (gStatus.auto_commit) + return "Disable auto commit"; + else + return "Enable auto commit"; + case 6: /*destroy*/ + if (gStatus.db_exists) + return "Delete database"; + /*fall through*/ + case 7: /*sep*/ + return ID2P(VOICE_BLANK); + case 8: /*commit*/ + if (gStatus.commit_ready) + return "Commit"; + else + return "Nothing to commit"; + default: + return "?"; + } +} + +static int list_voice_cb(int list_index, void* data) +{ + #define MAX_MENU_NAME 32 + if (!rb->global_settings->talk_menu) + return -1; + else + { + char buf[MAX_MENU_NAME]; + 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; +} + +static int commit_menu(void) +{ + struct gui_synclist lists; + bool exit = false; + int button,i; + int selection, ret = 0; + + 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, 9); + rb->gui_synclist_select_item(&lists, 0); + + while (!exit) + { + rb->gui_synclist_draw(&lists); + button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); + if (rb->gui_synclist_do_button(&lists, &button)) + continue; + selection = rb->gui_synclist_get_sel_pos(&lists); + + if (button == ACTION_STD_CANCEL) + return 0; + else if (button == ACTION_STD_OK) + { + switch(selection) + { + case 0: /* exit */ + exit = true; + break; + case 1: /*sep*/ + continue; + case 2: /*backup*/ + if (!gStatus.db_exists) + break; + if (!backup_restore_tagcache(true)) + rb->splash(HZ, "Backup failed!"); + else + { + rb->splash(HZ, "Backup success!"); + gStatus.bu_exists = true; + } + break; + case 3: /*restore*/ + if (!gStatus.bu_exists) + break; + if (!backup_restore_tagcache(false)) + rb->splash(HZ, "Restore failed!"); + else + rb->splash(HZ, "Restore success!"); + break; + case 4: /*sep*/ + continue; + case 5: /*auto commit*/ + { + /* build_idx_buf supplied by tagcache.c isn't being used + * so lets use it for a filename buffer */ + snprintf(build_idx_buf, build_idx_bufsz, + "%s/" TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path); + if(gStatus.auto_commit) + close(open(build_idx_buf, O_WRONLY | O_CREAT | O_TRUNC, 0666)); + else + remove(build_idx_buf); + gStatus.auto_commit = !file_exists(build_idx_buf); + break; + } + case 6: /*destroy*/ + { + if (!gStatus.db_exists) + break; + const struct text_message prompt = + { (const char*[]) {"Are you sure?", "This will destroy database."}, 2}; + if (rb->gui_syncyesno_run(&prompt, NULL, NULL) == YESNO_YES) + remove_files(); + break; + } + case 7: /*sep*/ + continue; + case 8: /*commit*/ + if (gStatus.commit_ready) + { + gStatus.do_commit = true; + exit = true; + } + break; + + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return 0; + } + } + } /*while*/ + return ret; +} + +/*-----------------------------------------------------------------------------*/ +/******* plugin_start ******* */ +/*-----------------------------------------------------------------------------*/ + +enum plugin_status plugin_start(const void* parameter) +{ + (void) parameter; + + /* Turn off backlight timeout */ + backlight_ignore_timeout(); + + memset(&gThread, 0, sizeof(gThread)); + memset(&gStatus, 0, sizeof(gStatus)); + memset(&tc_stat, 0, sizeof(struct tagcache_stat)); + memset(¤t_tcmh, 0, sizeof(struct master_header)); + filenametag_fd = -1; + + strlcpy(tc_stat.db_path, rb->global_settings->tagcache_db_path, sizeof(tc_stat.db_path)); + if (!rb->dir_exists(tc_stat.db_path)) /* on fail use default DB path */ + strlcpy(tc_stat.db_path, ROCKBOX_DIR, sizeof(tc_stat.db_path)); + tc_stat.initialized = true; + tc_stat.commit_step = -1; + + logf("started"); + + int result = commit_menu(); + + if (!gStatus.do_commit) + { + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(); + return PLUGIN_OK; + } + + logdump(false); + allocate_tempbuf(); + if (gStatus.db_exists && !gStatus.have_backup && !prompt_backup_restore(true)) + rb->splash(HZ, "Backup failed!"); + thread_create(); + gThread.user_index = 0; + logdisplay(); /* get something on the screen while user waits */ + + while (!gThread.exiting) + { + logdisplay(); + + int action = rb->get_action(CONTEXT_STD, HZ/20); + + switch( action ) + { + case ACTION_NONE: + break; + case ACTION_STD_NEXT: + case ACTION_STD_NEXTREPEAT: + gThread.last_useraction_tick = current_tick; + gThread.user_index++; + break; + case ACTION_STD_PREV: + case ACTION_STD_PREVREPEAT: + gThread.last_useraction_tick = current_tick; + gThread.user_index--; + break; + case ACTION_STD_OK: + gThread.last_useraction_tick = current_tick; + gThread.user_index = 0; + break; + case SYS_POWEROFF: + case ACTION_STD_CANCEL: + if (tc_stat.commit_step >= 0 && !tc_stat.ready) + { + if (!confirm_quit()) + break; + tc_stat.commit_delayed = true; /* Cancel the commit */ + } + rb->queue_remove_from_head(&gThread.queue, EV_EXIT); + rb->queue_post(&gThread.queue, EV_EXIT, 0); + break; +#ifdef HAVE_TOUCHSCREEN + case ACTION_TOUCHSCREEN: + { + gThread.last_useraction_tick = current_tick; + short x, y; + static int prev_y; + + action = rb->action_get_touchscreen_press(&x, &y); + + if(action & BUTTON_REL) + prev_y = 0; + else + { + if(prev_y != 0) + gThread.user_index += (prev_y - y) / log_font_h; + + prev_y = y; + } + } +#endif + default: + break; + } + yield(); + } + + rb->thread_wait(gThread.id); + rb->queue_delete(&gThread.queue); + free_tempbuf(); + + if (tc_stat.commit_delayed || !tc_stat.ready) + { + remove_files(); + if (gStatus.bu_exists && !prompt_backup_restore(false)) + rb->splash(HZ, "Restore failed!"); + } + + if (tc_stat.ready) + rb->tagcache_commit_finalize(); + + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(); + return PLUGIN_OK; +} + +/****************** main thread + helper ******************/ +static void thread(void) +{ + struct queue_event ev; + while (!gThread.exiting) + { + rb->queue_wait_w_tmo(&gThread.queue, &ev, 1); + switch (ev.id) + { + case SYS_USB_CONNECTED: + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + logenabled = false; + break; + case SYS_USB_DISCONNECTED: + logenabled = true; + /*fall through*/ + case EV_STARTUP: + logf("Thread Started"); + cpu_boost(true); + if (commit()) + tc_stat.ready = true; + cpu_boost(false); + logdump(true); + gThread.user_index++; + logdisplay(); + break; + case EV_EXIT: + gThread.exiting = true; + return; + default: + break; + } + yield(); + } +} + +static void thread_create(void) +{ + /* put the thread's queue in the bcast list */ + rb->queue_init(&gThread.queue, true); + /*Note: tagcache_stack is defined in apps/tagcache.c */ + gThread.last_useraction_tick = current_tick; + gThread.id = rb->create_thread(thread, tagcache_stack, sizeof(tagcache_stack), + 0, "db_commit" + IF_PRIO(, PRIORITY_USER_INTERFACE) + IF_COP(, CPU)); + rb->queue_post(&gThread.queue, EV_STARTUP, 0); + yield(); +} diff --git a/apps/plugins/tagcache/tagcache.h b/apps/plugins/tagcache/tagcache.h new file mode 100644 index 0000000000..e416289741 --- /dev/null +++ b/apps/plugins/tagcache/tagcache.h @@ -0,0 +1,25 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) + * + * 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 _TC_PLUGIN_ +#define _TC_PLUGIN_ +#endif /*_TC_PLUGIN_ */ + + diff --git a/apps/plugins/tagcache/tagcache.make b/apps/plugins/tagcache/tagcache.make new file mode 100644 index 0000000000..5d6d65cb0e --- /dev/null +++ b/apps/plugins/tagcache/tagcache.make @@ -0,0 +1,30 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +TCPLUG_SRCDIR := $(APPSDIR)/plugins/tagcache +TCPLUG_BUILDDIR := $(BUILDDIR)/apps/plugins/tagcache + +ROCKS += $(TCPLUG_BUILDDIR)/db_commit.rock + +TCPLUG_FLAGS = $(PLUGINFLAGS) -fno-strict-aliasing -Wno-unused \ + -I$(TCPLUG_SRCDIR) -ffunction-sections \ + -fdata-sections -Wl,--gc-sections +TCPLUG_SRC := $(call preprocess, $(TCPLUG_SRCDIR)/SOURCES) +TCPLUG_OBJ := $(call c2obj, $(TCPLUG_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(APPSDIR)/tagcache.c $(TCPLUG_SRC) + +$(TCPLUG_BUILDDIR)/db_commit.rock: $(TCPLUG_OBJ) + +# special pattern rule for compiling with extra flags +$(TCPLUG_BUILDDIR)/%.o: $(TCPLUG_SRCDIR)/%.c $(TCPLUG_SRCDIR)/tagcache.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(TCPLUG_FLAGS) -c $< -o $@ + diff --git a/apps/plugins/test_codec.c b/apps/plugins/test_codec.c index ac0dbf1633..91599bfc5d 100644 --- a/apps/plugins/test_codec.c +++ b/apps/plugins/test_codec.c @@ -915,7 +915,7 @@ menu: #ifdef HAVE_ADJUSTABLE_CPU_FREQ if (result == BOOST) { - rb->set_option("Boosting", &boost, INT, + rb->set_option("Boosting", &boost, RB_INT, boost_settings, 2, NULL); goto menu; } diff --git a/apps/plugins/test_disk.c b/apps/plugins/test_disk.c index 1429668556..fee6c4d0b0 100644 --- a/apps/plugins/test_disk.c +++ b/apps/plugins/test_disk.c @@ -467,10 +467,9 @@ enum plugin_status plugin_start(const void* parameter) rb->srand(*rb->current_tick); -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + while(!quit) { @@ -489,9 +488,8 @@ enum plugin_status plugin_start(const void* parameter) } /* Turn on backlight timeout (revert to settings) */ -#ifdef HAVE_BACKLIGHT backlight_use_settings(); -#endif + rb->rmdir(testbasedir); return PLUGIN_OK; diff --git a/apps/plugins/test_fps.c b/apps/plugins/test_fps.c index ddf938ac25..2f4e9bb13e 100644 --- a/apps/plugins/test_fps.c +++ b/apps/plugins/test_fps.c @@ -401,9 +401,9 @@ enum plugin_status plugin_start(const void* parameter) #if (CONFIG_PLATFORM & PLATFORM_NATIVE) cpu_freq = *rb->cpu_frequency; /* remember CPU frequency */ #endif -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + time_main_update(); rb->sleep(HZ); #if defined(HAVE_LCD_COLOR) && (MEMORYSIZE > 2) @@ -424,9 +424,9 @@ enum plugin_status plugin_start(const void* parameter) (cpu_freq + 500000) / 1000000); log_text(str); #endif -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + /* wait until user closes plugin */ plugin_quit(); diff --git a/apps/plugins/test_gfx.c b/apps/plugins/test_gfx.c index 7734179159..60e2836463 100644 --- a/apps/plugins/test_gfx.c +++ b/apps/plugins/test_gfx.c @@ -495,9 +495,9 @@ enum plugin_status plugin_start(const void* parameter) rb->lcd_set_backdrop(NULL); rb->lcd_clear_display(); #endif -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + rb->splashf(0, "LCD driver performance test, please wait %d sec", 7*4*DURATION/HZ); init_rand_table(); @@ -522,9 +522,9 @@ enum plugin_status plugin_start(const void* parameter) (cpu_freq + 500000) / 1000000); #endif rb->close(log_fd); -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + #ifdef TEST_GREYLIB grey_release(); #endif diff --git a/apps/plugins/test_grey.c b/apps/plugins/test_grey.c index 121cbad051..07fda1b9c6 100644 --- a/apps/plugins/test_grey.c +++ b/apps/plugins/test_grey.c @@ -123,9 +123,9 @@ enum plugin_status plugin_start(const void* parameter) } for (i = 0; i <= STEPS; i++) input_levels[i] = lcd_levels[i] = (255 * i + (STEPS/2)) / STEPS; -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif + grey_set_background(0); /* set background to black */ grey_clear_display(); grey_show(true); @@ -221,8 +221,8 @@ enum plugin_status plugin_start(const void* parameter) } grey_release(); -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif + return PLUGIN_OK; } diff --git a/apps/plugins/test_kbd.c b/apps/plugins/test_kbd.c new file mode 100644 index 0000000000..a40aa4c76a --- /dev/null +++ b/apps/plugins/test_kbd.c @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +/* welcome to the example rockbox plugin */ + +/* mandatory include for all plugins */ +#include "plugin.h" + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + /* if you don't use the parameter, you can do like + this to avoid the compiler warning about it */ + (void)parameter; + + /* "rb->" marks a plugin api call. Rockbox offers many of its built-in + * functions to plugins */ + /* now go ahead and have fun! */ + char buffer[MAX_PATH]; + rb->snprintf(buffer, sizeof(buffer), "Keyboard test; Current plugin filename: '%s'", + rb->plugin_get_current_filename()); + + if (rb->kbd_input(buffer, sizeof(buffer), NULL) == 0) + rb->splash(HZ*2, buffer); + + /* tell Rockbox that we have completed successfully */ + return PLUGIN_OK; +} diff --git a/apps/plugins/test_sampr.c b/apps/plugins/test_sampr.c index c13392f069..3006faeea5 100644 --- a/apps/plugins/test_sampr.c +++ b/apps/plugins/test_sampr.c @@ -252,7 +252,7 @@ static void play_tone(bool volume_set) else #endif /* HAVE_VOLUME_IN_LIST */ { - rb->set_option("Sample Rate", &freq, INT, names, + rb->set_option("Sample Rate", &freq, RB_INT, names, HW_NUM_FREQ, set_frequency); (void)volume_set; } diff --git a/apps/plugins/test_usb.c b/apps/plugins/test_usb.c new file mode 100644 index 0000000000..28ef8f7e5f --- /dev/null +++ b/apps/plugins/test_usb.c @@ -0,0 +1,137 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 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. + * + ****************************************************************************/ + +#include "plugin.h" +#include "logf.h" + +#undef DEBUGF +#define DEBUGF(...) +//#define DEBUGF printf + +#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF) + +unsigned char stack[DEFAULT_STACK_SIZE]; +struct event_queue queue; +int thread_id; +const char* state = "none"; +const char* prev_state = "none"; + +static void main_loop(void) +{ + bool exiting = false; + struct queue_event ev; + + while(true) { + rb->queue_wait(&queue, &ev); + + /* events that are handled whether exiting or not */ + switch(ev.id) { + case EV_EXIT: + return; + } + + if(exiting) + continue; + + /* events handled only when not exiting */ + switch(ev.id) { + case SYS_USB_CONNECTED: + prev_state = state; + state = "connected"; + logf("test_usb: connect ack %ld", *rb->current_tick); + DEBUGF("test_usb: connect ack %ld\n", *rb->current_tick); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + break; + + case SYS_USB_DISCONNECTED: + prev_state = state; + state = "disconnected"; + logf("test_usb: disconnect %ld", *rb->current_tick); + DEBUGF("test_usb: disconnect %ld\n", *rb->current_tick); + break; + + case SYS_POWEROFF: + case SYS_REBOOT: + prev_state = state; + state = "exiting"; + exiting = true; + break; + } + } +} + +static void kill_tsr(void) +{ + rb->queue_post(&queue, EV_EXIT, 0); + rb->thread_wait(thread_id); + rb->queue_delete(&queue); +} + +static int exit_tsr(bool reenter) +{ + MENUITEM_STRINGLIST(menu, "USB test menu", NULL, + "Status", "Stop plugin", "Back"); + + while(true) { + int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 1; + switch(result) { + case 0: + rb->splashf(HZ, "State: %s", state); + rb->splashf(HZ, "Prev: %s", prev_state); + break; + case 1: + rb->splashf(HZ, "Stopping USB test thread"); + kill_tsr(); + return (reenter ? PLUGIN_TSR_TERMINATE : PLUGIN_TSR_SUSPEND); + case 2: + return PLUGIN_TSR_CONTINUE; + } + } +} + +static void run_tsr(void) +{ + rb->queue_init(&queue, true); + thread_id = rb->create_thread(main_loop, stack, sizeof(stack), + 0, "test_usb TSR" + IF_PRIO(, PRIORITY_BACKGROUND) + IF_COP(, CPU)); + rb->plugin_tsr(exit_tsr); +} + +enum plugin_status plugin_start(const void* parameter) +{ + bool resume = (parameter == rb->plugin_tsr); + + MENUITEM_STRINGLIST(menu, "USB test menu", NULL, + "Start", "Quit"); + + switch(!resume ? rb->do_menu(&menu, NULL, NULL, false) : 0) { + case 0: + run_tsr(); + rb->splashf(HZ, "USB test thread started"); + return PLUGIN_OK; + case 1: + return PLUGIN_OK; + default: + return PLUGIN_ERROR; + } +} diff --git a/apps/plugins/test_viewports.c b/apps/plugins/test_viewports.c index 2bada01f79..6eff0249a2 100644 --- a/apps/plugins/test_viewports.c +++ b/apps/plugins/test_viewports.c @@ -130,7 +130,7 @@ static void *test_address_fn(int x, int y) struct frame_buffer_t *fb = vp0.buffer; /* LCD_STRIDEFORMAT & LCD_NATIVE_STRIDE macros allow Horiz screens to work with RB */ -#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE size_t element = (x * LCD_NATIVE_STRIDE(fb->stride)) + y; #else size_t element = (y * LCD_NATIVE_STRIDE(fb->stride)) + x; @@ -249,9 +249,11 @@ enum plugin_status plugin_start(const void* parameter) rb->button_get(true); + rb->screens[SCREEN_MAIN]->scroll_stop(); /* Restore the default viewport */ rb->screens[SCREEN_MAIN]->set_viewport(NULL); #ifdef HAVE_REMOTE_LCD + rb->screens[SCREEN_REMOTE]->scroll_stop(); rb->screens[SCREEN_REMOTE]->set_viewport(NULL); #endif diff --git a/apps/plugins/text_editor.c b/apps/plugins/text_editor.c index 0cbb61c774..8740606c58 100644 --- a/apps/plugins/text_editor.c +++ b/apps/plugins/text_editor.c @@ -214,7 +214,6 @@ static void setup_lists(struct gui_synclist *lists, int sel) 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,line_count); - rb->gui_synclist_limit_scroll(lists,true); rb->gui_synclist_select_item(lists, sel); rb->gui_synclist_draw(lists); } @@ -395,7 +394,7 @@ enum plugin_status plugin_start(const void* parameter) rb->gui_synclist_draw(&lists); cur_sel = rb->gui_synclist_get_sel_pos(&lists); button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); - if (rb->gui_synclist_do_button(&lists,&button,LIST_WRAP_UNLESS_HELD)) + if (rb->gui_synclist_do_button(&lists, &button)) continue; switch (button) { diff --git a/apps/plugins/text_viewer/tv_menu.c b/apps/plugins/text_viewer/tv_menu.c index 53e0adaf67..74a964ba98 100644 --- a/apps/plugins/text_viewer/tv_menu.c +++ b/apps/plugins/text_viewer/tv_menu.c @@ -46,15 +46,14 @@ static bool tv_horizontal_scroll_mode_setting(void) {"Scroll by Column", -1}, }; - return rb->set_option("Scroll Mode", &new_prefs.horizontal_scroll_mode, INT, + return rb->set_option("Scroll Mode", &new_prefs.horizontal_scroll_mode, RB_INT, names, 2, NULL); } MENUITEM_FUNCTION(horizontal_scrollbar_item, 0, "Scrollbar", - tv_horizontal_scrollbar_setting, - NULL, NULL, Icon_NOICON); + tv_horizontal_scrollbar_setting, NULL, Icon_NOICON); MENUITEM_FUNCTION(horizontal_scroll_mode_item, 0, "Scroll Mode", - tv_horizontal_scroll_mode_setting, NULL, NULL, Icon_NOICON); + tv_horizontal_scroll_mode_setting, NULL, Icon_NOICON); MAKE_MENU(horizontal_scroll_menu, "Horizontal", NULL, Icon_NOICON, &horizontal_scrollbar_item, @@ -76,7 +75,7 @@ static bool tv_vertical_scroll_mode_setting(void) {"Scroll by Line", -1}, }; - return rb->set_option("Scroll Mode", &new_prefs.vertical_scroll_mode, INT, + return rb->set_option("Scroll Mode", &new_prefs.vertical_scroll_mode, RB_INT, names, 2, NULL); } @@ -98,21 +97,19 @@ static bool tv_narrow_mode_setting(void) {"Top/Bottom Page", -1}, }; - return rb->set_option("Left/Right Key", &new_prefs.narrow_mode, INT, + return rb->set_option("Left/Right Key", &new_prefs.narrow_mode, RB_INT, names, 2, NULL); } MENUITEM_FUNCTION(vertical_scrollbar_item, 0, "Scrollbar", - tv_vertical_scrollbar_setting, - NULL, NULL, Icon_NOICON); + tv_vertical_scrollbar_setting, NULL, Icon_NOICON); MENUITEM_FUNCTION(vertical_scroll_mode_item, 0, "Scroll Mode", - tv_vertical_scroll_mode_setting, NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(overlap_page_mode_item, 0, "Overlap Pages", tv_overlap_page_mode_setting, - NULL, NULL, Icon_NOICON); + tv_vertical_scroll_mode_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(overlap_page_mode_item, 0, "Overlap Pages", tv_overlap_page_mode_setting, NULL, Icon_NOICON); MENUITEM_FUNCTION(autoscroll_speed_item, 0, "Auto-Scroll Speed", - tv_autoscroll_speed_setting, NULL, NULL, Icon_NOICON); + tv_autoscroll_speed_setting, NULL, Icon_NOICON); MENUITEM_FUNCTION(narrow_mode_item, 0, "Left/Right Key (Narrow mode)", - tv_narrow_mode_setting, NULL, NULL, Icon_NOICON); + tv_narrow_mode_setting, NULL, Icon_NOICON); MAKE_MENU(vertical_scroll_menu, "Vertical", NULL, Icon_NOICON, &vertical_scrollbar_item, @@ -141,7 +138,7 @@ static bool tv_encoding_setting(void) names[idx].voice_id = -1; } - return rb->set_option("Encoding", &new_prefs.encoding, INT, names, + return rb->set_option("Encoding", &new_prefs.encoding, RB_INT, names, sizeof(names) / sizeof(names[0]), NULL); } @@ -152,7 +149,7 @@ static bool tv_word_wrap_setting(void) {"Off (Chop Words)", -1}, }; - return rb->set_option("Word Wrap", &new_prefs.word_mode, INT, + return rb->set_option("Word Wrap", &new_prefs.word_mode, RB_INT, names, 2, NULL); } @@ -165,7 +162,7 @@ static bool tv_line_mode_setting(void) {"Reflow Lines", -1}, }; - return rb->set_option("Line Mode", &new_prefs.line_mode, INT, names, + return rb->set_option("Line Mode", &new_prefs.line_mode, RB_INT, names, sizeof(names) / sizeof(names[0]), NULL); } @@ -182,7 +179,7 @@ static bool tv_alignment_setting(void) {"Right", -1}, }; - return rb->set_option("Alignment", &new_prefs.alignment, INT, + return rb->set_option("Alignment", &new_prefs.alignment, RB_INT, names , 2, NULL); } @@ -203,16 +200,19 @@ static bool tv_statusbar_setting(void) static bool tv_font_setting(void) { - struct browse_context browse; char font[MAX_PATH], name[MAX_FILENAME+10]; - rb->snprintf(name, sizeof(name), "%s.fnt", new_prefs.font_name); - rb->browse_context_init(&browse, SHOW_FONT, - BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, - "Font", Icon_Menu_setting, FONT_DIR, name); - browse.buf = font; - browse.bufsize = sizeof(font); + struct browse_context browse = { + .dirfilter = SHOW_FONT, + .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, + .title = "Font", /* XXX: Translate? */ + .icon = Icon_Menu_setting, + .root = FONT_DIR, + .selected = name, + .buf = font, + .bufsize = sizeof(font), + }; rb->rockbox_browse(&browse); @@ -240,29 +240,29 @@ static bool tv_night_mode_setting(void) } #endif -MENUITEM_FUNCTION(encoding_item, 0, "Encoding", tv_encoding_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(word_wrap_item, 0, "Word Wrap", tv_word_wrap_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(line_mode_item, 0, "Line Mode", tv_line_mode_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(windows_item, 0, "Screens Per Page", tv_windows_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(alignment_item, 0, "Alignment", tv_alignment_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(header_item, 0, "Show Header", tv_header_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(footer_item, 0, "Show Footer", tv_footer_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(statusbar_item, 0, "Show Statusbar", tv_statusbar_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(font_item, 0, "Font", tv_font_setting, - NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(indent_spaces_item, 0, "Indent Spaces", tv_indent_spaces_setting, - NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(encoding_item, 0, "Encoding", + tv_encoding_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(word_wrap_item, 0, "Word Wrap", + tv_word_wrap_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(line_mode_item, 0, "Line Mode", + tv_line_mode_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(windows_item, 0, "Screens Per Page", + tv_windows_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(alignment_item, 0, "Alignment", + tv_alignment_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(header_item, 0, "Show Header", + tv_header_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(footer_item, 0, "Show Footer", + tv_footer_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(statusbar_item, 0, "Show Statusbar", + tv_statusbar_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(font_item, 0, "Font", + tv_font_setting, NULL, Icon_NOICON); +MENUITEM_FUNCTION(indent_spaces_item, 0, "Indent Spaces", + tv_indent_spaces_setting, NULL, Icon_NOICON); #ifdef HAVE_LCD_COLOR -MENUITEM_FUNCTION(night_mode_item, 0, "Night Mode", tv_night_mode_setting, - NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(night_mode_item, 0, "Night Mode", + tv_night_mode_setting, NULL, Icon_NOICON); #endif MAKE_MENU(option_menu, "Viewer Options", NULL, Icon_NOICON, diff --git a/apps/plugins/theme_remove.c b/apps/plugins/theme_remove.c index f39d224868..6f5353f3f6 100644 --- a/apps/plugins/theme_remove.c +++ b/apps/plugins/theme_remove.c @@ -589,7 +589,7 @@ static bool option_menu(void) { struct remove_setting *setting = &remove_list[result]; int prev_option = setting->option; - if (rb->set_option(option_menu_[result], &setting->option, INT, + if (rb->set_option(option_menu_[result], &setting->option, RB_INT, remove_names, NUM_REMOVE_OPTION, NULL)) return true; if (prev_option != setting->option) diff --git a/apps/plugins/vbrfix.c b/apps/plugins/vbrfix.c index 88f0a6579e..79fa134499 100644 --- a/apps/plugins/vbrfix.c +++ b/apps/plugins/vbrfix.c @@ -150,7 +150,7 @@ static bool vbr_fix(const char *selected_file) xingupdate(0); rc = rb->mp3info(&entry, selected_file); - if(rc < 0) { + if(rc) { fileerror(rc); return true; } @@ -258,6 +258,8 @@ static bool vbr_fix(const char *selected_file) } else { + rb->close(fd); + /* Not a VBR file */ DEBUGF("Not a VBR file\n"); rb->splash(HZ*2, ID2P(LANG_NOT_A_VBR_FILE)); diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 8aa0ac370a..45bbfeef0b 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -3,6 +3,10 @@ txt,viewers/text_viewer,1 txt,apps/text_editor,2 txt,viewers/sort,- txt,viewers/speedread,1 +md,viewers/text_viewer,1 +md,apps/text_editor,2 +md,viewers/sort,- +md,viewers/speedread,1 nfo,viewers/text_viewer,1 bmp,viewers/imageviewer,2 bmp,apps/rockpaint,11 @@ -105,4 +109,5 @@ shopper,viewers/shopper,1 lnk,viewers/windows_lnk,- #ifdef HAVE_TAGCACHE *,demos/pictureflow,- -#endif
\ No newline at end of file +log,viewers/lastfm_scrobbler_viewer,4 +#endif diff --git a/apps/plugins/vu_meter.c b/apps/plugins/vu_meter.c index 356a7fdd93..0dafd05845 100644 --- a/apps/plugins/vu_meter.c +++ b/apps/plugins/vu_meter.c @@ -20,6 +20,8 @@ #include "plugin.h" #include "fixedpoint.h" #include "lib/playback_control.h" +#include "lib/helper.h" +#include "lib/pluginlib_exit.h" @@ -666,7 +668,7 @@ static bool vu_meter_menu(void) switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: - rb->set_option("Meter Type", &vumeter_settings.meter_type, INT, + rb->set_option("Meter Type", &vumeter_settings.meter_type, RB_INT, meter_type_option, 2, NULL); break; @@ -699,12 +701,12 @@ static bool vu_meter_menu(void) case 3: if(vumeter_settings.meter_type==ANALOG) { - rb->set_option("Decay Speed", &vumeter_settings.analog_decay, INT, + rb->set_option("Decay Speed", &vumeter_settings.analog_decay, RB_INT, decay_speed_option, 7, NULL); } else { - rb->set_option("Decay Speed", &vumeter_settings.digital_decay, INT, + rb->set_option("Decay Speed", &vumeter_settings.digital_decay, RB_INT, decay_speed_option, 7, NULL); } break; @@ -910,6 +912,12 @@ static void digital_meter(void) { rb->lcd_hline(0,LCD_WIDTH-1,half_height+3); } +static void vu_meter_cleanup(void) +{ + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(); +} + enum plugin_status plugin_start(const void* parameter) { int button; #if defined(VUMETER_HELP_PRE) || defined(VUMETER_MENU_PRE) @@ -920,12 +928,17 @@ enum plugin_status plugin_start(const void* parameter) { calc_scales(); + atexit(vu_meter_cleanup); + load_settings(); rb->lcd_setfont(FONT_SYSFIXED); #ifdef HAVE_LCD_COLOR screen_foreground = rb->lcd_get_foreground(); #endif + /* Turn off backlight timeout */ + backlight_ignore_timeout(); + while (1) { rb->lcd_clear_display(); diff --git a/apps/plugins/wormlet.c b/apps/plugins/wormlet.c index 162cea6208..7dd814bc08 100644 --- a/apps/plugins/wormlet.c +++ b/apps/plugins/wormlet.c @@ -54,7 +54,7 @@ static long max_cycle; #define BTN_DIR_LEFT BUTTON_LEFT #define BTN_DIR_RIGHT BUTTON_RIGHT #define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL) -#define BTN_QUIT (BUTTON_SELECT|BUTTON_MENU) +#define BTN_QUIT (BUTTON_SELECT|BUTTON_REPEAT) #define BTN_STOPRESET (BUTTON_SELECT|BUTTON_PLAY) #elif (CONFIG_KEYPAD == IRIVER_H300_PAD) || (CONFIG_KEYPAD == IRIVER_H100_PAD) @@ -2428,10 +2428,9 @@ static bool launch_wormlet(void) rb->lcd_clear_display(); -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + /* start the game */ while (game_result == 1) game_result = run(); @@ -2439,10 +2438,10 @@ static bool launch_wormlet(void) switch (game_result) { case 2: -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + return false; break; } @@ -2563,24 +2562,24 @@ enum plugin_status plugin_start(const void* parameter) case 3: switch(players) { case 0: - rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote,INT, + rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT, nokey_option, 1, NULL); break; case 1: - rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote,INT, + rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT, key24_option, 2, NULL); break; case 2: #ifdef REMOTE - rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote,INT, + rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT, remote_option, 2, NULL); #else - rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote,INT, + rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT, key2_option, 1, NULL); #endif break; case 3: - rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote,INT, + rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT, remoteonly_option, 1, NULL); break; } @@ -2609,7 +2608,7 @@ enum plugin_status plugin_start(const void* parameter) break; case 9: new_setting = 0; - rb->set_option(rb->str(LANG_RESET), &new_setting, INT, noyes , 2, NULL); + rb->set_option(rb->str(LANG_RESET), &new_setting, RB_INT, noyes , 2, NULL); if (new_setting == 1) default_settings(); break; diff --git a/apps/plugins/xobox.c b/apps/plugins/xobox.c index b8b1964db4..336d92bd4e 100644 --- a/apps/plugins/xobox.c +++ b/apps/plugins/xobox.c @@ -42,10 +42,10 @@ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) -#define QUIT (BUTTON_SELECT | BUTTON_MENU) +#define QUIT (BUTTON_SELECT | BUTTON_REPEAT) #define LEFT BUTTON_LEFT #define RIGHT BUTTON_RIGHT -#define PAUSE BUTTON_SELECT +#define PAUSE (BUTTON_SELECT | BUTTON_REL) #define MENU_UP BUTTON_SCROLL_FWD #define MENU_DOWN BUTTON_SCROLL_BACK #define UP BUTTON_MENU @@ -1297,10 +1297,9 @@ enum plugin_status plugin_start (const void *parameter) rb->lcd_set_backdrop(NULL); #endif -#ifdef HAVE_BACKLIGHT /* Turn off backlight timeout */ backlight_ignore_timeout(); -#endif + highscore_load(SCORE_FILE, highscores, NUM_SCORES); if (!load_game()) { @@ -1310,10 +1309,10 @@ enum plugin_status plugin_start (const void *parameter) randomize (); ret = xobox_loop (); -#ifdef HAVE_BACKLIGHT + /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); -#endif + rb->lcd_setfont (FONT_UI); highscore_save(SCORE_FILE, highscores, NUM_SCORES); diff --git a/apps/plugins/xworld/engine.c b/apps/plugins/xworld/engine.c index b09e320078..d99d9df6c6 100644 --- a/apps/plugins/xworld/engine.c +++ b/apps/plugins/xworld/engine.c @@ -293,7 +293,7 @@ void engine_processInput(struct Engine* e) { e->sys->input.save = false; } if (e->sys->input.fastMode) { - e->vm._fastMode = !&e->vm._fastMode; + e->vm._fastMode = !e->vm._fastMode; e->sys->input.fastMode = false; } if (e->sys->input.stateSlot != 0) { diff --git a/apps/plugins/xworld/sys.c b/apps/plugins/xworld/sys.c index c57da9456b..4a80152b70 100644 --- a/apps/plugins/xworld/sys.c +++ b/apps/plugins/xworld/sys.c @@ -122,9 +122,8 @@ void exit_handler(void) #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif -#ifdef HAVE_BACKLIGHT + backlight_use_settings(); -#endif } static bool sys_do_help(void) @@ -207,7 +206,7 @@ static void do_video_settings(struct System* sys) #else case 2: #endif - rb->set_option("Scaling", &sys->settings.scaling_quality, INT, scaling_settings, + rb->set_option("Scaling", &sys->settings.scaling_quality, RB_INT, scaling_settings, #ifdef HAVE_LCD_COLOR 3 #else @@ -226,7 +225,7 @@ static void do_video_settings(struct System* sys) #else case 3: #endif - rb->set_option("Rotation", &sys->settings.rotation_option, INT, rotation_settings, 3, NULL); + rb->set_option("Rotation", &sys->settings.rotation_option, RB_INT, rotation_settings, 3, NULL); if(sys->settings.rotation_option && sys->settings.zoom) { @@ -287,7 +286,7 @@ static void do_sound_settings(struct System* sys) case 2: { const struct settings_list* vol = - rb->find_setting(&rb->global_settings->volume, NULL); + rb->find_setting(&rb->global_settings->volume); rb->option_screen((struct settings_list*)vol, NULL, false, "Volume"); break; } @@ -428,9 +427,8 @@ void sys_menu(struct System* sys) void sys_init(struct System* sys, const char* title) { (void) title; -#ifdef HAVE_BACKLIGHT + backlight_ignore_timeout(); -#endif rb_atexit(exit_handler); save_sys = sys; rb->memset(&sys->input, 0, sizeof(sys->input)); @@ -496,7 +494,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 for (int i = 0; i < w / 2; ++i) { uint8_t pix1 = *(buf + i) >> 4; uint8_t pix2 = *(buf + i) & 0xF; -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE framebuffer[( (h * 2) ) * 320 + i] = sys->palette[pix1]; framebuffer[( (h * 2) + 1) * 320 + i] = sys->palette[pix2]; #else @@ -515,7 +513,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 for (int i = 0; i < w / 2; ++i) { uint8_t pix1 = *(buf + i) >> 4; uint8_t pix2 = *(buf + i) & 0xF; -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE framebuffer[(200 - h * 2 ) * 320 + ( 320 - i )] = sys->palette[pix1]; framebuffer[(200 - h * 2 - 1) * 320 + ( 320 - i )] = sys->palette[pix2]; #else @@ -531,7 +529,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 else { int next = 0; -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE for(int x = 0; x < w / 2; ++x) { for(int y = 0; y < h; ++y) @@ -565,7 +563,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 struct bitmap in_bmp; if(sys->settings.rotation_option) { -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE in_bmp.width = 320; in_bmp.height = 200; #else @@ -575,7 +573,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 } else { -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE in_bmp.width = 200; in_bmp.height = 320; #else @@ -600,7 +598,7 @@ void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16 } else { -#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) +#if LCD_STRIDEFORMAT == VERTICAL_STRIDE for(int x = 0; x < 320; ++x) { for(int y = 0; y < 200; ++y) diff --git a/apps/plugins/zxbox/spmain.c b/apps/plugins/zxbox/spmain.c index 6c6b59c3fb..38cc175a85 100644 --- a/apps/plugins/zxbox/spmain.c +++ b/apps/plugins/zxbox/spmain.c @@ -257,21 +257,21 @@ static void options_menu(void){ { case 0: new_setting=settings.kempston; - rb->set_option("Map Keys to kempston",&new_setting,INT, + rb->set_option("Map Keys to kempston",&new_setting, RB_INT, no_yes, 2, NULL); if (new_setting != settings.kempston ) settings.kempston=new_setting; break; case 1: new_setting = settings.showfps; - rb->set_option("Display Speed",&new_setting,INT, + rb->set_option("Display Speed",&new_setting, RB_INT, no_yes, 2, NULL); if (new_setting != settings.showfps ) settings.showfps=new_setting; break; case 2: new_setting = settings.invert_colors; - rb->set_option("Invert Colors",&new_setting,INT, + rb->set_option("Invert Colors",&new_setting, RB_INT, no_yes, 2, NULL); if (new_setting != settings.invert_colors ) settings.invert_colors=new_setting; @@ -279,14 +279,14 @@ static void options_menu(void){ break; case 3: new_setting = settings.frameskip; - rb->set_option("Frameskip",&new_setting,INT, + rb->set_option("Frameskip",&new_setting, RB_INT, frameskip_items, 10, NULL); if (new_setting != settings.frameskip ) settings.frameskip=new_setting; break; case 4: new_setting = settings.sound; - rb->set_option("Sound",&new_setting,INT, + rb->set_option("Sound",&new_setting, RB_INT, no_yes, 2, NULL); if (new_setting != settings.sound ) settings.sound=new_setting; @@ -296,7 +296,7 @@ static void options_menu(void){ break; case 5: new_setting = 9 - settings.volume; - rb->set_option("Volume",&new_setting,INT, + rb->set_option("Volume",&new_setting, RB_INT, frameskip_items, 10, NULL); new_setting = 9 - new_setting; if (new_setting != settings.volume ) |