| /*************************************************************************** |
| * |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * |
| * Copyright (C) 2011 Jonathan Gordon |
| * |
| * 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 <stdbool.h> |
| #include <stdlib.h> |
| #include "config.h" |
| #include "system.h" |
| #include "powermgmt.h" |
| #include "power.h" |
| #include "action.h" |
| #include "ata_idle_notify.h" |
| #include "debug_menu.h" |
| #include "core_alloc.h" |
| #include "list.h" |
| #include "settings.h" |
| #include "settings_list.h" |
| #include "lang.h" |
| #include "menu.h" |
| #include "misc.h" |
| #include "tree.h" |
| #include "splash.h" |
| #include "pathfuncs.h" |
| #include "filetypes.h" |
| #include "shortcuts.h" |
| #include "onplay.h" |
| #include "screens.h" |
| #include "talk.h" |
| #include "yesno.h" |
| |
| |
| #define MAX_SHORTCUT_NAME 32 |
| #define SHORTCUTS_FILENAME ROCKBOX_DIR "/shortcuts.txt" |
| static const char * const type_strings[SHORTCUT_TYPE_COUNT] = { |
| [SHORTCUT_SETTING] = "setting", |
| [SHORTCUT_FILE] = "file", |
| [SHORTCUT_DEBUGITEM] = "debug", |
| [SHORTCUT_BROWSER] = "browse", |
| [SHORTCUT_PLAYLISTMENU] = "playlist menu", |
| [SHORTCUT_SEPARATOR] = "separator", |
| [SHORTCUT_SHUTDOWN] = "shutdown", |
| [SHORTCUT_TIME] = "time", |
| }; |
| |
| struct shortcut { |
| enum shortcut_type type; |
| char name[MAX_SHORTCUT_NAME]; |
| char talk_clip[MAX_PATH]; |
| int icon; |
| union { |
| char path[MAX_PATH]; |
| const struct settings_list *setting; |
| struct { |
| #if CONFIG_RTC |
| bool talktime; |
| #endif |
| int sleep_timeout; |
| } timedata; |
| } u; |
| }; |
| #define SHORTCUTS_PER_HANDLE 32 |
| struct shortcut_handle { |
| struct shortcut shortcuts[SHORTCUTS_PER_HANDLE]; |
| int next_handle; |
| }; |
| static int first_handle = 0; |
| static int shortcut_count = 0; |
| |
| static int buflib_move_lock; |
| static int move_callback(int handle, void *current, void *new) |
| { |
| (void)handle; |
| (void)current; |
| (void)new; |
| |
| if (buflib_move_lock > 0) |
| return BUFLIB_CB_CANNOT_MOVE; |
| |
| return BUFLIB_CB_OK; |
| } |
| static struct buflib_callbacks shortcut_ops = { |
| .move_callback = move_callback, |
| }; |
| |
| static void reset_shortcuts(void) |
| { |
| int current_handle = first_handle; |
| struct shortcut_handle *h = NULL; |
| while (current_handle > 0) |
| { |
| int next; |
| h = core_get_data(current_handle); |
| next = h->next_handle; |
| core_free(current_handle); |
| current_handle = next; |
| } |
| first_handle = 0; |
| shortcut_count = 0; |
| } |
| |
| static struct shortcut* get_shortcut(int index) |
| { |
| int handle_count, handle_index; |
| int current_handle = first_handle; |
| struct shortcut_handle *h = NULL; |
| |
| if (first_handle == 0) |
| { |
| first_handle = core_alloc_ex("shortcuts_head", |
| sizeof(struct shortcut_handle), &shortcut_ops); |
| if (first_handle <= 0) |
| return NULL; |
| h = core_get_data(first_handle); |
| h->next_handle = 0; |
| current_handle = first_handle; |
| } |
| |
| handle_count = index/SHORTCUTS_PER_HANDLE + 1; |
| handle_index = index%SHORTCUTS_PER_HANDLE; |
| do { |
| h = core_get_data(current_handle); |
| current_handle = h->next_handle; |
| handle_count--; |
| } while (handle_count > 0 && current_handle > 0); |
| if (handle_count > 0 && handle_index == 0) |
| { |
| char buf[32]; |
| snprintf(buf, sizeof buf, "shortcuts_%d", index/SHORTCUTS_PER_HANDLE); |
| /* prevent invalidation of 'h' during compaction */ |
| ++buflib_move_lock; |
| h->next_handle = core_alloc_ex(buf, sizeof(struct shortcut_handle), &shortcut_ops); |
| --buflib_move_lock; |
| if (h->next_handle <= 0) |
| return NULL; |
| h = core_get_data(h->next_handle); |
| h->next_handle = 0; |
| } |
| return &h->shortcuts[handle_index]; |
| } |
| |
| static void remove_shortcut(int index) |
| { |
| int this = index, next = index + 1; |
| struct shortcut *prev = get_shortcut(this); |
| |
| while (next <= shortcut_count) |
| { |
| struct shortcut *sc = get_shortcut(next); |
| memcpy(prev, sc, sizeof(struct shortcut)); |
| next++; |
| prev = sc; |
| } |
| shortcut_count--; |
| } |
| |
| static bool verify_shortcut(struct shortcut* sc) |
| { |
| switch (sc->type) |
| { |
| case SHORTCUT_UNDEFINED: |
| return false; |
| case SHORTCUT_BROWSER: |
| case SHORTCUT_FILE: |
| case SHORTCUT_PLAYLISTMENU: |
| return sc->u.path[0] != '\0'; |
| case SHORTCUT_SETTING: |
| return sc->u.setting != NULL; |
| case SHORTCUT_TIME: |
| return sc->name[0] != '\0'; |
| case SHORTCUT_DEBUGITEM: |
| case SHORTCUT_SEPARATOR: |
| case SHORTCUT_SHUTDOWN: |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| static void init_shortcut(struct shortcut* sc) |
| { |
| sc->type = SHORTCUT_UNDEFINED; |
| sc->name[0] = '\0'; |
| sc->u.path[0] = '\0'; |
| sc->talk_clip[0] = '\0'; |
| sc->icon = Icon_NOICON; |
| } |
| |
| static int first_idx_to_writeback = -1; |
| static bool overwrite_shortcuts = false; |
| static void shortcuts_ata_idle_callback(void) |
| { |
| int fd; |
| char buf[MAX_PATH]; |
| int current_idx = first_idx_to_writeback; |
| int append = overwrite_shortcuts ? O_TRUNC : O_APPEND; |
| |
| if (first_idx_to_writeback < 0) |
| return; |
| fd = open(SHORTCUTS_FILENAME, append|O_RDWR|O_CREAT, 0644); |
| if (fd < 0) |
| return; |
| |
| /* ensure pointer returned by get_shortcut() |
| survives the yield() of write() */ |
| ++buflib_move_lock; |
| |
| while (current_idx < shortcut_count) |
| { |
| struct shortcut* sc = get_shortcut(current_idx++); |
| const char *type; |
| int len; |
| if (!sc) |
| break; |
| type = type_strings[sc->type]; |
| len = snprintf(buf, MAX_PATH, "[shortcut]\ntype: %s\ndata: ", type); |
| write(fd, buf, len); |
| if (sc->type == SHORTCUT_SETTING) |
| write(fd, sc->u.setting->cfg_name, strlen(sc->u.setting->cfg_name)); |
| else |
| write(fd, sc->u.path, strlen(sc->u.path)); |
| |
| /* write name:, icon:, talkclip: */ |
| len = snprintf(buf, MAX_PATH, "\nname: %s\nicon: %d\ntalkclip: ", |
| sc->name, sc->icon); |
| write(fd, buf, len); |
| write(fd, sc->talk_clip, strlen(sc->talk_clip)); |
| write(fd, "\n\n", 2); |
| } |
| close(fd); |
| if (first_idx_to_writeback == 0) |
| { |
| /* reload all shortcuts because we appended to the shortcuts file which |
| * has not been read yet. |
| */ |
| reset_shortcuts(); |
| shortcuts_init(); |
| } |
| --buflib_move_lock; |
| first_idx_to_writeback = -1; |
| } |
| |
| void shortcuts_add(enum shortcut_type type, const char* value) |
| { |
| struct shortcut* sc = get_shortcut(shortcut_count++); |
| if (!sc) |
| return; |
| init_shortcut(sc); |
| sc->type = type; |
| if (type == SHORTCUT_SETTING) |
| sc->u.setting = (void*)value; |
| else |
| strlcpy(sc->u.path, value, MAX_PATH); |
| |
| if (first_idx_to_writeback < 0) |
| first_idx_to_writeback = shortcut_count - 1; |
| overwrite_shortcuts = false; |
| register_storage_idle_func(shortcuts_ata_idle_callback); |
| } |
| |
| static int readline_cb(int n, char *buf, void *parameters) |
| { |
| (void)n; |
| (void)parameters; |
| struct shortcut **param = (struct shortcut**)parameters; |
| struct shortcut* sc = *param; |
| char *name, *value; |
| |
| if (!strcasecmp(skip_whitespace(buf), "[shortcut]")) |
| { |
| if (sc && verify_shortcut(sc)) |
| shortcut_count++; |
| sc = get_shortcut(shortcut_count); |
| if (!sc) |
| return 1; |
| init_shortcut(sc); |
| *param = sc; |
| } |
| else if (sc && settings_parseline(buf, &name, &value)) |
| { |
| if (!strcmp(name, "type")) |
| { |
| int t = 0; |
| for (t=0; t<SHORTCUT_TYPE_COUNT && sc->type == SHORTCUT_UNDEFINED; t++) |
| if (!strcmp(value, type_strings[t])) |
| sc->type = t; |
| } |
| else if (!strcmp(name, "name")) |
| { |
| strlcpy(sc->name, value, MAX_SHORTCUT_NAME); |
| } |
| else if (!strcmp(name, "data")) |
| { |
| switch (sc->type) |
| { |
| case SHORTCUT_UNDEFINED: |
| case SHORTCUT_TYPE_COUNT: |
| *param = NULL; |
| break; |
| case SHORTCUT_BROWSER: |
| case SHORTCUT_FILE: |
| case SHORTCUT_DEBUGITEM: |
| case SHORTCUT_PLAYLISTMENU: |
| strlcpy(sc->u.path, value, MAX_PATH); |
| break; |
| case SHORTCUT_SETTING: |
| sc->u.setting = find_setting_by_cfgname(value, NULL); |
| break; |
| case SHORTCUT_TIME: |
| #if CONFIG_RTC |
| sc->u.timedata.talktime = false; |
| if (!strcasecmp(value, "talk")) |
| sc->u.timedata.talktime = true; |
| else |
| #endif |
| if (!strncasecmp(value, "sleep ", strlen("sleep "))) |
| sc->u.timedata.sleep_timeout = atoi(&value[strlen("sleep ")]); |
| else |
| sc->type = SHORTCUT_UNDEFINED; /* error */ |
| break; |
| case SHORTCUT_SEPARATOR: |
| case SHORTCUT_SHUTDOWN: |
| break; |
| } |
| } |
| else if (!strcmp(name, "icon")) |
| { |
| if (!strcmp(value, "filetype") && sc->type != SHORTCUT_SETTING && sc->u.path[0]) |
| { |
| sc->icon = filetype_get_icon(filetype_get_attr(sc->u.path)); |
| } |
| else |
| { |
| sc->icon = atoi(value); |
| } |
| } |
| else if (!strcmp(name, "talkclip")) |
| { |
| strlcpy(sc->talk_clip, value, MAX_PATH); |
| } |
| } |
| return 0; |
| } |
| |
| void shortcuts_init(void) |
| { |
| int fd; |
| char buf[512]; |
| struct shortcut *param = NULL; |
| struct shortcut_handle *h; |
| shortcut_count = 0; |
| fd = open_utf8(SHORTCUTS_FILENAME, O_RDONLY); |
| if (fd < 0) |
| return; |
| first_handle = core_alloc_ex("shortcuts_head", sizeof(struct shortcut_handle), &shortcut_ops); |
| if (first_handle <= 0) |
| return; |
| h = core_get_data(first_handle); |
| h->next_handle = 0; |
| |
| /* we enter readline_cb() multiple times with a buffer |
| obtained when we encounter a "[shortcut]" section. |
| fast_readline() might yield() -> protect buffer */ |
| ++buflib_move_lock; |
| |
| fast_readline(fd, buf, sizeof buf, ¶m, readline_cb); |
| close(fd); |
| if (param && verify_shortcut(param)) |
| shortcut_count++; |
| |
| /* invalidate at scope end since "param" contains |
| the shortcut pointer */ |
| --buflib_move_lock; |
| } |
| |
| static const char * shortcut_menu_get_name(int selected_item, void * data, |
| char * buffer, size_t buffer_len) |
| { |
| (void)data; |
| (void)buffer; |
| (void)buffer_len; |
| struct shortcut *sc = get_shortcut(selected_item); |
| if (!sc) |
| return ""; |
| if (sc->type == SHORTCUT_SETTING) |
| return sc->name[0] ? sc->name : P2STR(ID2P(sc->u.setting->lang_id)); |
| else if (sc->type == SHORTCUT_SEPARATOR || sc->type == SHORTCUT_TIME) |
| return sc->name; |
| else if (sc->type == SHORTCUT_SHUTDOWN && sc->name[0] == '\0') |
| { |
| /* No translation support as only soft_shutdown has LANG_SHUTDOWN defined */ |
| return type_strings[SHORTCUT_SHUTDOWN]; |
| } |
| return sc->name[0] ? sc->name : sc->u.path; |
| } |
| |
| static int shortcut_menu_get_action(int action, struct gui_synclist *lists) |
| { |
| (void)lists; |
| if (action == ACTION_STD_OK) |
| return ACTION_STD_CANCEL; |
| else if (action == ACTION_STD_CONTEXT) |
| { |
| int selection = gui_synclist_get_sel_pos(lists); |
| |
| if (!yesno_pop(ID2P(LANG_REALLY_DELETE))) |
| return ACTION_REDRAW; |
| |
| remove_shortcut(selection); |
| gui_synclist_set_nb_items(lists, shortcut_count); |
| if (selection >= shortcut_count) |
| gui_synclist_select_item(lists, shortcut_count - 1); |
| first_idx_to_writeback = 0; |
| overwrite_shortcuts = true; |
| shortcuts_ata_idle_callback(); |
| if (shortcut_count == 0) |
| return ACTION_STD_CANCEL; |
| return ACTION_REDRAW; |
| } |
| return action; |
| } |
| |
| static enum themable_icons shortcut_menu_get_icon(int selected_item, void * data) |
| { |
| (void)data; |
| struct shortcut *sc = get_shortcut(selected_item); |
| if (!sc) |
| return Icon_NOICON; |
| if (sc->icon == Icon_NOICON) |
| { |
| switch (sc->type) |
| { |
| case SHORTCUT_FILE: |
| return filetype_get_icon(filetype_get_attr(sc->u.path)); |
| case SHORTCUT_BROWSER: |
| return Icon_Folder; |
| case SHORTCUT_SETTING: |
| return Icon_Menu_setting; |
| case SHORTCUT_DEBUGITEM: |
| return Icon_Menu_functioncall; |
| case SHORTCUT_PLAYLISTMENU: |
| return Icon_Playlist; |
| case SHORTCUT_SHUTDOWN: |
| return Icon_System_menu; |
| case SHORTCUT_TIME: |
| return Icon_Menu_functioncall; |
| default: |
| break; |
| } |
| } |
| return sc->icon; |
| } |
| |
| static int shortcut_menu_speak_item(int selected_item, void * data) |
| { |
| (void)data; |
| struct shortcut *sc = get_shortcut(selected_item); |
| if (sc && sc->talk_clip[0]) |
| talk_file(NULL, NULL, sc->talk_clip, NULL, NULL, false); |
| return 0; |
| } |
| |
| void talk_timedate(void); |
| const char* sleep_timer_formatter(char* buffer, size_t buffer_size, |
| int value, const char* unit); |
| |
| int do_shortcut_menu(void *ignored) |
| { |
| (void)ignored; |
| struct simplelist_info list; |
| struct shortcut *sc; |
| int done = GO_TO_PREVIOUS; |
| if (first_handle == 0) |
| shortcuts_init(); |
| simplelist_info_init(&list, P2STR(ID2P(LANG_SHORTCUTS)), shortcut_count, NULL); |
| list.get_name = shortcut_menu_get_name; |
| list.action_callback = shortcut_menu_get_action; |
| if (global_settings.show_icons) |
| list.get_icon = shortcut_menu_get_icon; |
| list.title_icon = Icon_Bookmark; |
| if (global_settings.talk_menu) |
| list.get_talk = shortcut_menu_speak_item; |
| |
| if (shortcut_count == 0) |
| { |
| splash(HZ, str(LANG_NO_FILES)); |
| return GO_TO_PREVIOUS; |
| } |
| push_current_activity(ACTIVITY_SHORTCUTSMENU); |
| |
| /* prevent buffer moves while the menu is active. |
| Playing talk files or other I/O actions call yield() */ |
| ++buflib_move_lock; |
| |
| while (done == GO_TO_PREVIOUS) |
| { |
| if (simplelist_show_list(&list)) |
| break; /* some error happened?! */ |
| if (list.selection == -1) |
| break; |
| else |
| { |
| sc = get_shortcut(list.selection); |
| if (!sc) |
| continue; |
| switch (sc->type) |
| { |
| case SHORTCUT_PLAYLISTMENU: |
| if (!file_exists(sc->u.path)) |
| { |
| splash(HZ, ID2P(LANG_NO_FILES)); |
| break; |
| } |
| else |
| { |
| onplay_show_playlist_menu(sc->u.path); |
| } |
| break; |
| case SHORTCUT_FILE: |
| if (!file_exists(sc->u.path)) |
| { |
| splash(HZ, ID2P(LANG_NO_FILES)); |
| break; |
| } |
| /* else fall through */ |
| case SHORTCUT_BROWSER: |
| { |
| struct browse_context browse; |
| browse_context_init(&browse, global_settings.dirfilter, 0, |
| NULL, NOICON, sc->u.path, NULL); |
| if (sc->type == SHORTCUT_FILE) |
| browse.flags |= BROWSE_RUNFILE; |
| done = rockbox_browse(&browse); |
| } |
| break; |
| case SHORTCUT_SETTING: |
| do_setting_screen(sc->u.setting, |
| sc->name[0] ? sc->name : P2STR(ID2P(sc->u.setting->lang_id)),NULL); |
| break; |
| case SHORTCUT_DEBUGITEM: |
| run_debug_screen(sc->u.path); |
| break; |
| case SHORTCUT_SHUTDOWN: |
| #if CONFIG_CHARGING |
| if (charger_inserted()) |
| charging_splash(); |
| else |
| #endif |
| sys_poweroff(); |
| break; |
| case SHORTCUT_TIME: |
| #if CONFIG_RTC |
| if (sc->u.timedata.talktime) { |
| talk_timedate(); |
| talk_force_enqueue_next(); |
| } else |
| #endif |
| { |
| char timer_buf[10]; |
| set_sleeptimer_duration(sc->u.timedata.sleep_timeout); |
| splashf(HZ, "%s (%s)", str(LANG_SLEEP_TIMER), |
| sleep_timer_formatter(timer_buf, sizeof(timer_buf), |
| sc->u.timedata.sleep_timeout, NULL)); |
| } |
| break; |
| case SHORTCUT_UNDEFINED: |
| default: |
| break; |
| } |
| } |
| } |
| pop_current_activity(); |
| --buflib_move_lock; |
| |
| return done; |
| } |