/***************************************************************************
 *
 *             __________               __   ___.
 *   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 <dir.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 "string-extra.h"
#include "lang.h"
#include "menu.h"
#include "misc.h"
#include "open_plugin.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 64
#define SHORTCUTS_HDR "[shortcut]"
#define SHORTCUTS_FILENAME ROCKBOX_DIR "/shortcuts.txt"
static const char * const type_strings[SHORTCUT_TYPE_COUNT] = {
    [SHORTCUT_SETTING] = "setting",
    [SHORTCUT_SETTING_APPLY] = "apply",
    [SHORTCUT_DEBUGITEM] = "debug",
    [SHORTCUT_BROWSER] = "browse",
    [SHORTCUT_PLAYLISTMENU] = "playlist menu",
    [SHORTCUT_SEPARATOR] = "separator",
    [SHORTCUT_SHUTDOWN] = "shutdown",
    [SHORTCUT_REBOOT] = "reboot",
    [SHORTCUT_TIME] = "time",
    [SHORTCUT_FILE] = "file",
};

struct shortcut {
    enum shortcut_type type;
    int8_t icon;
    char name[MAX_SHORTCUT_NAME];
    char talk_clip[MAX_PATH];
    const struct settings_list *setting;
    union {
        char path[MAX_PATH];
        struct {
#if CONFIG_RTC
            bool talktime;
#endif
            int sleep_timeout;
        } timedata;
    } u;
};

#define SHORTCUTS_PER_HANDLE 4
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_handle * alloc_first_sc_handle(void)
{
    struct shortcut_handle *h = NULL;
#if 0 /* all paths are guarded, compiler doesn't recognize INIT_ATTR callers */
    if (first_handle != 0)
        return h; /* No re-init allowed */
#endif
    first_handle = core_alloc_ex(sizeof(struct shortcut_handle), &shortcut_ops);
    if (first_handle > 0)
    {
        h = core_get_data(first_handle);
        h->next_handle = 0;
    }

    return h;
}

static struct shortcut* get_shortcut(int index, struct shortcut *fail)
{
    int handle_count, handle_index;
    int current_handle = first_handle;
    struct shortcut_handle *h;

    if (first_handle == 0)
    {
        h = alloc_first_sc_handle();
        if (!h)
            return fail;
        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--;
        if(handle_count <= 0)
            return &h->shortcuts[handle_index];
    } while (current_handle > 0);

    if (handle_index == 0)
    {
        /* prevent invalidation of 'h' during compaction */
        ++buflib_move_lock;
        h->next_handle = core_alloc_ex(sizeof(struct shortcut_handle), &shortcut_ops);
        --buflib_move_lock;
        if (h->next_handle <= 0)
            return fail;
        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, NULL);

    while (next <= shortcut_count)
    {
        struct shortcut *sc = get_shortcut(next, NULL);
        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_APPLY:
        case SHORTCUT_SETTING:
            return sc->setting != NULL;
        case SHORTCUT_TIME:
        case SHORTCUT_DEBUGITEM:
        case SHORTCUT_SEPARATOR:
        case SHORTCUT_SHUTDOWN:
        case SHORTCUT_REBOOT:
        default:
            break;
    }
    return true;
}

static void init_shortcut(struct shortcut* sc)
{
    memset(sc, 0, sizeof(*sc));
    sc->type = SHORTCUT_UNDEFINED;
    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;

    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++, NULL);
        const char *type;

        if (!sc)
            break;
        type = type_strings[sc->type];

        fdprintf(fd, SHORTCUTS_HDR "\ntype: %s\ndata: ", type);

        if (sc->type == SHORTCUT_TIME)
        {
#if CONFIG_RTC
            if (sc->u.timedata.talktime)
                write(fd, "talk", 4);
            else
#endif
            {
                write(fd, "sleep", 5);
                if(sc->u.timedata.sleep_timeout >= 0)
                    fdprintf(fd, " %d", sc->u.timedata.sleep_timeout);
            }
        }
        else if (sc->type == SHORTCUT_SETTING_APPLY)
        {
            fdprintf(fd, "%s: %s", sc->setting->cfg_name, sc->u.path);
        }
        else if (sc->type == SHORTCUT_SETTING)
        {
            write(fd, sc->setting->cfg_name, strlen(sc->setting->cfg_name));
        }
        else
            write(fd, sc->u.path, strlen(sc->u.path));

        /* write name:, icon:, talkclip: */
        fdprintf(fd, "\nname: %s\nicon: %d\ntalkclip: %s\n\n",
                 sc->name, sc->icon, sc->talk_clip);
    }
    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++, NULL);
    if (!sc)
        return;
    init_shortcut(sc);
    sc->type = type;
    if (type == SHORTCUT_SETTING || type == SHORTCUT_SETTING_APPLY)
    {
        sc->setting = (void*)value;
        /* write the current value, will be ignored for SHORTCUT_SETTING */
        cfg_to_string(sc->setting, sc->u.path,  sizeof(sc->u.path));
    }
    else
        strmemccpy(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;

    buf = skip_whitespace(buf);

    if (!strcasecmp(buf, SHORTCUTS_HDR))
    {
        if (sc && verify_shortcut(sc))
            shortcut_count++;
        sc = get_shortcut(shortcut_count, NULL);
        if (!sc)
            return 1;
        init_shortcut(sc);
        *param = sc;
        return 0;
    }

    if (!sc || !settings_parseline(buf, &name, &value))
        return 0;

    static const char * const nm_options[] = {"type", "name", "data",
                                              "icon", "talkclip", NULL};
    int nm_op = string_option(name, nm_options, false);
    if (nm_op == 0) /*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 (nm_op == 1) /*name*/
        strmemccpy(sc->name, value, MAX_SHORTCUT_NAME);
    else if (nm_op == 2) /*data*/
    {
        switch (sc->type)
        {
            case SHORTCUT_UNDEFINED:
            case SHORTCUT_TYPE_COUNT:
                *param = NULL;
                break;
            case SHORTCUT_BROWSER:
            {
                char *p = strmemccpy(sc->u.path, value, MAX_PATH);
                if (p && dir_exists(value))
                {
                    /* ensure ending slash */
                    *p = '\0';
                    if (*(p-2) != '/')
                        *(p-1) = '/';
                }
                break;
            }
            case SHORTCUT_FILE:
            case SHORTCUT_DEBUGITEM:
            case SHORTCUT_PLAYLISTMENU:
            {
                strmemccpy(sc->u.path, value, MAX_PATH);
                break;
            }
            case SHORTCUT_SETTING_APPLY:
            case SHORTCUT_SETTING:
                /* can handle 'name: value' pair for either type */
                if (settings_parseline(value, &name, &value))
                {
                    sc->setting = find_setting_by_cfgname(name);
                    strmemccpy(sc->u.path, value, MAX_PATH);
                }
                else /* force SHORTCUT_SETTING, no 'name: value' pair */
                {
                    sc->type = SHORTCUT_SETTING;
                    sc->setting = find_setting_by_cfgname(value);
                }
                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", sizeof("sleep")-1))
                {
                    /* 'sleep' may appear alone or followed by number after a space */
                    if (strlen(value) > sizeof("sleep")) /* sizeof 1 larger (+space chr..) */
                        sc->u.timedata.sleep_timeout = atoi(&value[sizeof("sleep")-1]);
                    else
                        sc->u.timedata.sleep_timeout = -1;
                }
                else
                    sc->type = SHORTCUT_UNDEFINED; /* error */
                break;
            case SHORTCUT_SEPARATOR:
            case SHORTCUT_SHUTDOWN:
            case SHORTCUT_REBOOT:
                break;
        }
    }
    else if (nm_op == 3) /*icon*/
    {
        if (!strcmp(value, "filetype") && sc->type != SHORTCUT_SETTING
            && sc->type != SHORTCUT_SETTING_APPLY && sc->u.path[0])
        {
            sc->icon = filetype_get_icon(filetype_get_attr(sc->u.path));
        }
        else
            sc->icon = atoi(value);
    }
    else if (nm_op == 4) /*talkclip*/
        strmemccpy(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;
    h = alloc_first_sc_handle();
    if (!h) {
        close(fd);
        return;
    }

    /* we enter readline_cb() multiple times with a buffer
       obtained when we encounter a SHORTCUTS_HDR ("[shortcut]") section.
       fast_readline() might yield() -> protect buffer */
    ++buflib_move_lock;

    fast_readline(fd, buf, sizeof buf, &param, 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;
    struct shortcut *sc = get_shortcut(selected_item, NULL);
    if (!sc)
        return "";
    const char *ret = sc->u.path;

    if (sc->type == SHORTCUT_SETTING && sc->name[0] == '\0')
        return P2STR(ID2P(sc->setting->lang_id));
    else if (sc->type == SHORTCUT_SETTING_APPLY && sc->name[0] == '\0')
    {
        snprintf(buffer, buffer_len, "%s (%s)",
                 P2STR(ID2P(sc->setting->lang_id)), sc->u.path);
        return buffer;
    }
    else if (sc->type == SHORTCUT_SEPARATOR)
        return sc->name;
    else if (sc->type == SHORTCUT_TIME)
    {
        if (sc->u.timedata.sleep_timeout < 0
#if CONFIG_RTC
            && !sc->u.timedata.talktime
#endif
        ) /* String representation for toggling sleep timer */
            return string_sleeptimer(buffer, buffer_len);

        if (sc->name[0])
            return sc->name;

#if CONFIG_RTC
        if (sc->u.timedata.talktime)
            return P2STR(ID2P(LANG_CURRENT_TIME));
        else
#endif
        {
            format_sleeptimer(sc->name, sizeof(sc->name),
                              sc->u.timedata.sleep_timeout, NULL);
            snprintf(buffer, buffer_len, "%s (%s)",
                     P2STR(ID2P(LANG_SLEEP_TIMER)),
                     sc->name[0] ? sc->name : P2STR(ID2P(LANG_OFF)));
            sc->name[0] = '\0';
            return buffer;
        }

    }
    else if ((sc->type == SHORTCUT_SHUTDOWN || sc->type == SHORTCUT_REBOOT) &&
             sc->name[0] == '\0')
    {
        /* No translation support as only soft_shutdown has LANG_SHUTDOWN defined */
        return type_strings[sc->type];
    }
    else if (sc->type == SHORTCUT_BROWSER && sc->name[0] == '\0' && (sc->u.path)[0] != '\0')
    {
        char* pos;
        if (path_basename(sc->u.path, (const char **)&pos) > 0)
        {
            if (snprintf(buffer, buffer_len, "%s (%s)", pos, sc->u.path) < (int)buffer_len)
                return buffer;
        }
    }

    return sc->name[0] ? sc->name : ret;
}

static int shortcut_menu_speak_item(int selected_item, void * data)
{
    (void)data;
    struct shortcut *sc = get_shortcut(selected_item, NULL);

    if (!sc)
        return 0;

    if (sc->talk_clip[0])
    {
        talk_file(NULL, NULL, sc->talk_clip, NULL, NULL, false);
        return 0;
    }
    switch (sc->type)
    {
    case SHORTCUT_BROWSER:;
        DIR* dir;
        struct dirent* entry;
        char* slash = strrchr(sc->u.path, PATH_SEPCH);
        char* filename = slash + 1;

        if (!slash || *filename == '\0')
        {
            talk_dir_or_spell(sc->u.path, NULL, false);
            break;
        }

        *slash = '\0'; /* terminate the path to open the directory */
        dir = opendir(sc->u.path);
        *slash = PATH_SEPCH; /* restore fullpath */
        if (dir)
        {
            while ((entry = readdir(dir)))
            {
                if (strcmp(entry->d_name, filename))
                    continue;

                struct dirinfo info = dir_get_info(dir, entry);

                if (info.attribute & ATTR_DIRECTORY)
                    talk_dir_or_spell(sc->u.path, NULL, false);
                else
                    talk_file_or_spell(NULL, sc->u.path, NULL, false);

                closedir(dir);
                return 0;
            }
            closedir(dir);
        }
        talk_spell(sc->u.path, false);
        break;
    case SHORTCUT_FILE:
    case SHORTCUT_PLAYLISTMENU:
        talk_file_or_spell(NULL, sc->u.path, NULL, false);
        break;
    case SHORTCUT_SETTING_APPLY:
    case SHORTCUT_SETTING:
        talk_id(sc->setting->lang_id, false);
        if (sc->type == SHORTCUT_SETTING_APPLY)
            talk_spell(sc->u.path, true);
        break;

    case SHORTCUT_TIME:
#if CONFIG_RTC
        if (sc->u.timedata.talktime)
            talk_timedate();
        else
#endif
        if (sc->name[0] && sc->u.timedata.sleep_timeout >= 0)
            talk_spell(sc->name, false);
        else
            talk_sleeptimer(sc->u.timedata.sleep_timeout);
        break;
    case SHORTCUT_SHUTDOWN:
    case SHORTCUT_REBOOT:
        if (!sc->name[0])
        {
            talk_spell(type_strings[sc->type], false);
            break;
        }
        /* fall-through */
    default:
        talk_spell(sc->name[0] ? sc->name : sc->u.path, false);
        break;
    }
    return 0;
}

static int shortcut_menu_get_action(int action, struct gui_synclist *lists)
{
    (void)lists;
    if (action == ACTION_STD_CONTEXT)
    {
        int selection = gui_synclist_get_sel_pos(lists);

        if (confirm_delete_yesno("", str(LANG_SHORTCUTS)) != YESNO_YES)
        {
            gui_synclist_set_title(lists, lists->title, lists->title_icon);
            if (global_settings.talk_menu)
                shortcut_menu_speak_item(selection, NULL);
            return ACTION_REDRAW;
        }

        remove_shortcut(selection);
        gui_synclist_set_nb_items(lists, shortcut_count);
        gui_synclist_set_title(lists, lists->title, lists->title_icon);
        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)
        {
            if (global_settings.talk_menu)
                shortcut_menu_speak_item(gui_synclist_get_sel_pos(lists), NULL);
            return ACTION_REDRAW;
        }

        return ACTION_STD_CANCEL;
    }

    if (action == ACTION_STD_OK
     || action == ACTION_STD_MENU
     || action == ACTION_STD_QUICKSCREEN)
    {
        return ACTION_STD_CANCEL;
    }

    return action;
}

static enum themable_icons shortcut_menu_get_icon(int selected_item, void * data)
{
    static const int8_t type_icons[SHORTCUT_TYPE_COUNT] = {
        [SHORTCUT_SETTING] = Icon_Menu_setting,
        [SHORTCUT_SETTING_APPLY] = Icon_Queued,
        [SHORTCUT_DEBUGITEM] = Icon_Menu_functioncall,
        [SHORTCUT_BROWSER] = Icon_Folder,
        [SHORTCUT_PLAYLISTMENU] = Icon_Playlist,
        [SHORTCUT_SEPARATOR] = Icon_NOICON,
        [SHORTCUT_SHUTDOWN] = Icon_System_menu,
        [SHORTCUT_REBOOT] = Icon_System_menu,
        [SHORTCUT_TIME] = Icon_Menu_functioncall,
        [SHORTCUT_FILE] = Icon_NOICON,
    };

    (void)data;
    int icon;
    struct shortcut *sc = get_shortcut(selected_item, NULL);
    if (!sc)
        return Icon_NOICON;
    if (sc->icon == Icon_NOICON)
    {
        if (sc->type == SHORTCUT_BROWSER || sc->type == SHORTCUT_FILE)
        {
            icon = filetype_get_icon(filetype_get_attr(sc->u.path));
            if (icon > 0)
                return icon;
        }
        /* excluding SHORTCUT_UNDEFINED (-1) */
        if (sc->type >= 0 && sc->type < SHORTCUT_TYPE_COUNT)
            return type_icons[sc->type];
    }
    return sc->icon;
}

static void apply_new_setting(const struct settings_list *setting)
{
    settings_apply(false);
    if ((setting->flags & (F_THEMESETTING|F_NEEDAPPLY)) == (F_THEMESETTING|F_NEEDAPPLY))
    {
            settings_apply_skins();
    }
    if (setting->setting == &global_settings.sleeptimer_duration && get_sleep_timer())
    {
        set_sleeptimer_duration(global_settings.sleeptimer_duration);
    }
}

int do_shortcut_menu(void *ignored)
{
    (void)ignored;
    char timer_buf[10];
    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;
    list.title_icon = Icon_Bookmark;

    if (shortcut_count == 0)
    {
        splash(HZ, ID2P(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)
    {
        list.count = shortcut_count; /* in case shortcut was deleted */
        list.title = P2STR(ID2P(LANG_SHORTCUTS)); /* in case language changed */
        list.get_icon = global_settings.show_icons ? shortcut_menu_get_icon : NULL;
        list.get_talk = global_settings.talk_menu ? shortcut_menu_speak_item : NULL;

        if (simplelist_show_list(&list))
            break; /* returning from USB screen */

        if (list.selection == -1)
            break; /* canceled */

        sc = get_shortcut(list.selection, NULL);
        if (!sc)
            continue;

        switch (sc->type)
        {
            case SHORTCUT_PLAYLISTMENU:
                if (!file_exists(sc->u.path))
                    splash(HZ, ID2P(LANG_NO_FILES));
                else
                    onplay_show_playlist_menu(sc->u.path,
                                              dir_exists(sc->u.path) ?
                                              ATTR_DIRECTORY :
                                              filetype_get_attr(sc->u.path),
                                              NULL);
                break;
            case SHORTCUT_FILE:
                if (!file_exists(sc->u.path))
                {
                    splash(HZ, ID2P(LANG_NO_FILES));
                    break;
                }
                /* else fall through */
            case SHORTCUT_BROWSER:
                if (open_plugin_add_path(ID2P(LANG_SHORTCUTS),
                                         sc->u.path, NULL) != 0)
                {
                    done = GO_TO_PLUGIN;
                    break;
                }
                struct browse_context browse = {
                    .dirfilter = SHOW_ALL, /* ignored for SHORTCUT_BROWSER */
                    .icon = Icon_NOICON,
                    .root = sc->u.path,
                };
                if (sc->type == SHORTCUT_FILE)
                    browse.flags = BROWSE_RUNFILE | BROWSE_DIRFILTER;
                done = rockbox_browse(&browse);
                break;
            case SHORTCUT_SETTING_APPLY:;
                bool theme_changed;
                string_to_cfg(sc->setting->cfg_name, sc->u.path, &theme_changed);
                settings_save();
                apply_new_setting(sc->setting);
                break;
            case SHORTCUT_SETTING:
                do_setting_screen(sc->setting, sc->name[0] ?
                                  sc->name : P2STR(ID2P(sc->setting->lang_id)),
                                  NULL);
                apply_new_setting(sc->setting);
                break;
            case SHORTCUT_DEBUGITEM:
                run_debug_screen(sc->u.path);
                break;
            case SHORTCUT_SHUTDOWN:
#if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
                if (charger_inserted())
                    charging_splash();
                else
#endif
                    sys_poweroff();
                break;
            case SHORTCUT_REBOOT:
#if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
                if (charger_inserted())
                    charging_splash();
                else
#endif
                    sys_reboot();
                break;
            case SHORTCUT_TIME:
#if CONFIG_RTC
                if (sc->u.timedata.talktime)
                    break;
#endif
                if (sc->u.timedata.sleep_timeout < 0)
                {
                    toggle_sleeptimer();
                    break;
                }
                set_sleeptimer_duration(sc->u.timedata.sleep_timeout);
                splashf(HZ, "%s (%s)", str(LANG_SLEEP_TIMER),
                        format_sleeptimer(timer_buf, sizeof(timer_buf),
                                          sc->u.timedata.sleep_timeout,
                                          NULL));
                break;
            case SHORTCUT_UNDEFINED:
            default:
                break;
        }
    }
    if (GO_TO_PLUGIN == done)
        pop_current_activity_without_refresh();
    else
        pop_current_activity();
    --buflib_move_lock;

    return done;
}
