Desktop / MNTM Settings: Directories and generic files support for Keybinds / Main Menu (#331)

* feat(Desktop): Directories support for keybinds

- Adds *RIGHT* button select in the file browser dialogs and changing the `Open File` action to `Open File/Directory` in `Settings > Desktop > Keybinds Setup`. This adds the ability to open to any directory in the Archive app, in addition to the default behavior of opening a file in it's default app.

* line order mixup

* Main Menu: Allow adding JS files (or any file)

- Normal files and directories are now able to be added to then main menu and are run in their appropriate apps.
- e.g. .txt files shown in text viewer, .js files are run in the JS Runner app, and folders are navigated to by the Archive app. All similar to the desktop keybinds functionality.
- Icons are also assigned appropriately based on the extensions, though more could probably be added to the `loader_menu_get_ext_icon` function.
- Also replaced some of the long arduous is_dir checks and just used the `storage_dir_exists` function since its already there and does the same.

* should be checking `ext` for NULL

* Move select_right at end of structs for binary compatibility

apps may blindly reach into these structs so need to keep the basics in same structure

for DialogsFileBrowserOptions this is even in public api and after compilation this would be incompatible with other firmwares even without reaching into private structs

* Select menu item / folder for directories too

* Move api below too

* Keep ofw order here too

* Refactor starting archive into desktop, less FuriString passing around

* Dont leave main menu when launching archive

* Simplify/fix a few things

* Handle folders in run_with_default_app()

* Update App -> Item naming in MNTM settings

* Fix build

* Explain pressing right

* Update changelog

---------

Co-authored-by: WillyJL <me@willyjl.dev>
This commit is contained in:
Alexander Bays
2025-07-22 01:51:33 +00:00
committed by GitHub
parent 3193361e49
commit 0e3e1b352b
25 changed files with 207 additions and 79 deletions

View File

@@ -8,7 +8,10 @@
- UL: Keeloq Comunello add manually support (by @xMasterX)
- RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek)
- BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439 by @aaronjamt & @WillyJL)
- MNTM Settings: Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt)
- MNTM Settings:
- Add Main Menu support for directories and generic files (including JS files) (#331 by @956MB & @WillyJL)
- Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt)
- Desktop: Add Keybinds support for directories (#331 by @956MB & @WillyJL)
- Input Settings: Add Vibro Trigger option (#429 by @956MB)
### Updated:

View File

@@ -1,4 +1,5 @@
#include "archive_i.h"
#include "helpers/archive_browser.h"
static bool archive_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
@@ -136,11 +137,25 @@ void archive_show_loading_popup(ArchiveApp* context, bool show) {
}
int32_t archive_app(void* p) {
UNUSED(p);
FuriString* path = (FuriString*)p;
ArchiveApp* archive = archive_alloc();
view_dispatcher_attach_to_gui(
archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);
// If we are sent a path from context, set it in the browser
if(path && !furi_string_empty(path)) {
archive_set_tab(archive->browser, ArchiveTabBrowser);
furi_string_set(archive->browser->path, path);
archive->browser->is_root = false;
archive_file_browser_set_path(
archive->browser,
archive->browser->path,
archive_get_tab_ext(ArchiveTabBrowser),
false,
!momentum_settings.show_hidden_files);
}
scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser);
view_dispatcher_run(archive->view_dispatcher);

View File

@@ -116,7 +116,7 @@ static void archive_long_load_cb(void* context) {
browser->view, ArchiveBrowserViewModel * model, { model->folder_loading = true; }, true);
}
static void archive_file_browser_set_path(
void archive_file_browser_set_path(
ArchiveBrowserView* browser,
FuriString* path,
const char* filter_ext,

View File

@@ -80,6 +80,12 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) {
return type < ArchiveFileTypeUnknown;
}
void archive_file_browser_set_path(
ArchiveBrowserView* browser,
FuriString* path,
const char* filter_ext,
bool skip_assets,
bool hide_dot_files);
bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx);
bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model);
void archive_update_offset(ArchiveBrowserView* browser);
@@ -104,6 +110,7 @@ void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const ch
void archive_show_file_menu(ArchiveBrowserView* browser, bool show, bool manage);
void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active);
void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab);
void archive_switch_tab(ArchiveBrowserView* browser, InputKey key);
void archive_enter_dir(ArchiveBrowserView* browser, FuriString* name);
void archive_leave_dir(ArchiveBrowserView* browser);

View File

@@ -15,6 +15,8 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
file->is_app = is_app;
if(is_app) {
file->type = archive_get_app_filetype(archive_get_app_type(path));
} else if(is_folder) {
file->type = ArchiveFileTypeFolder;
} else {
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
@@ -53,11 +55,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
}
}
if(is_folder) {
file->type = ArchiveFileTypeFolder;
} else {
file->type = ArchiveFileTypeUnknown;
}
file->type = ArchiveFileTypeUnknown;
}
}

View File

@@ -6,6 +6,8 @@
#include "../views/archive_browser_view.h"
#include "archive/scenes/archive_scene.h"
#include <desktop/desktop_i.h>
#define TAG "ArchiveSceneBrowser"
#define SCENE_STATE_DEFAULT (0)
@@ -182,6 +184,12 @@ static void
}
} else if(selected->type == ArchiveFileTypeApplication) {
loader_start_detached_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
} else if(selected->type == ArchiveFileTypeFolder) {
// Folders are handled by archive, so we should only get here with run_with_default_app() outside archive
furi_check(browser == NULL, "What you doin?");
Desktop* desktop = furi_record_open(RECORD_DESKTOP);
desktop_launch_archive(desktop, furi_string_get_cstr(selected->path));
furi_record_close(RECORD_DESKTOP);
} else {
archive_show_file(loader, furi_string_get_cstr(selected->path));
}

View File

@@ -201,22 +201,23 @@ void momentum_app_load_mainmenu_apps(MomentumApp* app) {
if(furi_string_start_with(line, "/")) {
if(!flipper_application_load_name_and_icon(
line, app->storage, &unused_icon, label)) {
furi_string_reset(label);
const char* end = strrchr(furi_string_get_cstr(line), '/');
furi_string_set(label, end ? end + 1 : furi_string_get_cstr(line));
}
} else {
furi_string_reset(label);
bool found = false;
for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) {
if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) {
furi_string_set(label, FLIPPER_APPS[i].name);
found = true;
}
momentum_app_push_mainmenu_app(app, label, line);
continue;
}
bool found = false;
for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) {
if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) {
furi_string_set(label, FLIPPER_APPS[i].name);
found = true;
}
for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) {
furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name);
found = true;
}
}
for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) {
furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name);
found = true;
}
}
if(furi_string_empty(label)) {

View File

@@ -3,10 +3,10 @@
enum VarItemListIndex {
VarItemListIndexMenuStyle,
VarItemListIndexResetMenu,
VarItemListIndexApp,
VarItemListIndexAddApp,
VarItemListIndexMoveApp,
VarItemListIndexRemoveApp,
VarItemListIndexItem,
VarItemListIndexAddItem,
VarItemListIndexMoveItem,
VarItemListIndexRemoveItem,
};
void momentum_app_scene_interface_mainmenu_var_item_list_callback(void* context, uint32_t index) {
@@ -40,7 +40,7 @@ static void momentum_app_scene_interface_mainmenu_app_changed(VariableItem* item
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
size_t count = CharList_size(app->mainmenu_app_labels);
char label[20];
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
variable_item_set_item_label(item, label);
}
@@ -61,7 +61,7 @@ static void momentum_app_scene_interface_mainmenu_move_app_changed(VariableItem*
CharList_swap_at(app->mainmenu_app_exes, idx, idx - 1);
app->mainmenu_app_index--;
}
view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveApp);
view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveItem);
}
variable_item_set_current_value_index(item, 1);
}
@@ -84,11 +84,11 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) {
size_t count = CharList_size(app->mainmenu_app_labels);
item = variable_item_list_add(
var_item_list, "App", count, momentum_app_scene_interface_mainmenu_app_changed, app);
var_item_list, "Item", count, momentum_app_scene_interface_mainmenu_app_changed, app);
if(count) {
app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U);
char label[20];
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
char label[21];
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
variable_item_set_item_label(item, label);
variable_item_set_current_value_text(
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
@@ -98,15 +98,15 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) {
}
variable_item_set_current_value_index(item, app->mainmenu_app_index);
variable_item_list_add(var_item_list, "Add App", 0, NULL, app);
variable_item_list_add(var_item_list, "Add Item", 0, NULL, app);
item = variable_item_list_add(
var_item_list, "Move App", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app);
var_item_list, "Move Item", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app);
variable_item_set_current_value_text(item, "");
variable_item_set_current_value_index(item, 1);
variable_item_set_locked(item, count < 2, "Can't move\nwith less\nthan 2 apps!");
variable_item_list_add(var_item_list, "Remove App", 0, NULL, app);
variable_item_list_add(var_item_list, "Remove Item", 0, NULL, app);
variable_item_list_set_enter_callback(
var_item_list, momentum_app_scene_interface_mainmenu_var_item_list_callback, app);
@@ -133,7 +133,7 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE
case VarItemListIndexResetMenu:
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuReset);
break;
case VarItemListIndexRemoveApp:
case VarItemListIndexRemoveItem:
if(!CharList_size(app->mainmenu_app_labels)) break;
if(!CharList_size(app->mainmenu_app_exes)) break;
free(*CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
@@ -143,27 +143,27 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE
CharList_remove_v(
app->mainmenu_app_exes, app->mainmenu_app_index, app->mainmenu_app_index + 1);
/* fall through */
case VarItemListIndexMoveApp: {
case VarItemListIndexMoveItem: {
app->save_mainmenu_apps = true;
size_t count = CharList_size(app->mainmenu_app_labels);
VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexApp);
VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexItem);
if(count) {
app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U);
char label[20];
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
char label[21];
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
variable_item_set_item_label(item, label);
variable_item_set_current_value_text(
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
} else {
app->mainmenu_app_index = 0;
variable_item_set_item_label(item, "App");
variable_item_set_item_label(item, "Item");
variable_item_set_current_value_text(item, "None");
}
variable_item_set_current_value_index(item, app->mainmenu_app_index);
variable_item_set_values_count(item, count);
break;
}
case VarItemListIndexAddApp:
case VarItemListIndexAddItem:
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAdd);
break;
default:

View File

@@ -3,6 +3,7 @@
enum SubmenuIndex {
SubmenuIndexMainApp,
SubmenuIndexExternalApp,
SubmenuIndexFileDirectory,
};
static bool fap_selector_item_callback(
@@ -26,28 +27,39 @@ static void
case SubmenuIndexMainApp:
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAddMain);
break;
case SubmenuIndexExternalApp: {
case SubmenuIndexExternalApp:
case SubmenuIndexFileDirectory: {
const bool is_file_dir = index == SubmenuIndexFileDirectory;
const DialogsFileBrowserOptions browser_options = {
.extension = ".fap",
.extension = is_file_dir ? "*" : ".fap",
.icon = &I_unknown_10px,
.skip_assets = true,
.hide_ext = true,
.hide_ext = !is_file_dir,
.item_loader_callback = fap_selector_item_callback,
.item_loader_context = app,
.base_path = EXT_PATH("apps"),
.base_path = is_file_dir ? STORAGE_EXT_PATH_PREFIX : EXT_PATH("apps"),
.select_right = is_file_dir,
};
FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps"));
FuriString* temp_path = furi_string_alloc_set_str(browser_options.base_path);
if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) {
CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(temp_path)));
Storage* storage = furi_record_open(RECORD_STORAGE);
uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE);
flipper_application_load_name_and_icon(temp_path, storage, &unused_icon, temp_path);
free(unused_icon);
furi_record_close(RECORD_STORAGE);
if(furi_string_start_with_str(temp_path, "[")) {
size_t trim = furi_string_search_str(temp_path, "] ", 1);
if(trim != FURI_STRING_FAILURE) {
furi_string_right(temp_path, trim + 2);
if(is_file_dir) {
const char* path = furi_string_get_cstr(temp_path);
const char* end = strrchr(path, '/');
furi_string_set_str(temp_path, end ? end + 1 : path);
} else {
Storage* storage = furi_record_open(RECORD_STORAGE);
uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE);
flipper_application_load_name_and_icon(
temp_path, storage, &unused_icon, temp_path);
free(unused_icon);
furi_record_close(RECORD_STORAGE);
if(furi_string_start_with_str(temp_path, "[")) {
size_t trim = furi_string_search_str(temp_path, "] ", 1);
if(trim != FURI_STRING_FAILURE) {
furi_string_right(temp_path, trim + 2);
}
}
}
CharList_push_back(app->mainmenu_app_labels, strdup(furi_string_get_cstr(temp_path)));
@@ -68,7 +80,7 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) {
MomentumApp* app = context;
Submenu* submenu = app->submenu;
submenu_set_header(submenu, "Add Menu App:");
submenu_set_header(submenu, "Add Menu Item:");
submenu_add_item(
submenu,
@@ -84,6 +96,13 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) {
momentum_app_scene_interface_mainmenu_add_submenu_callback,
app);
submenu_add_item(
submenu,
"File / Directory (right btn)",
SubmenuIndexFileDirectory,
momentum_app_scene_interface_mainmenu_add_submenu_callback,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewSubmenu);
}

View File

@@ -12,7 +12,7 @@ void momentum_app_scene_interface_mainmenu_reset_on_enter(void* context) {
MomentumApp* app = context;
DialogEx* dialog_ex = app->dialog_ex;
dialog_ex_set_header(dialog_ex, "Reset Menu Apps?", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_header(dialog_ex, "Reset Menu Items?", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text(dialog_ex, "Your edits will be lost!", 64, 32, AlignCenter, AlignCenter);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Reset");

View File

@@ -379,6 +379,8 @@ static Desktop* desktop_alloc(void) {
furi_record_create(RECORD_DESKTOP, desktop);
desktop->archive_dir = furi_string_alloc();
return desktop;
}
@@ -494,6 +496,15 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
desktop->in_transition = false;
}
void desktop_launch_archive(Desktop* desktop, const char* open_dir) {
if(open_dir) {
furi_string_set(desktop->archive_dir, open_dir);
} else {
furi_string_reset(desktop->archive_dir);
}
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive);
}
/*
* Public API
*/

View File

@@ -89,9 +89,12 @@ struct Desktop {
FuriPubSub* ascii_events_pubsub;
FuriPubSubSubscription* ascii_events_subscription;
FuriString* archive_dir;
};
void desktop_lock(Desktop* desktop, bool pin_lock);
void desktop_unlock(Desktop* desktop);
int32_t desktop_shutdown(void* context);
void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled);
void desktop_launch_archive(Desktop* desktop, const char* open_dir);

View File

@@ -203,7 +203,7 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
} else if(furi_string_equal(keybind, "Apps Menu")) {
loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL);
} else if(furi_string_equal(keybind, "Archive")) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive);
desktop_launch_archive(desktop, NULL);
} else if(furi_string_equal(keybind, "Clock")) {
loader_start_detached_with_gui_error(
desktop->loader, EXT_PATH("apps/Tools/nightstand.fap"), "");
@@ -218,11 +218,11 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
} else if(furi_string_equal(keybind, "Wipe Device")) {
loader_start_detached_with_gui_error(desktop->loader, "Storage", "Wipe Device");
} else {
if(storage_common_exists(desktop->storage, furi_string_get_cstr(keybind))) {
run_with_default_app(furi_string_get_cstr(keybind));
const char* str = furi_string_get_cstr(keybind);
if(storage_common_exists(desktop->storage, str)) {
run_with_default_app(str);
} else {
loader_start_detached_with_gui_error(
desktop->loader, furi_string_get_cstr(keybind), NULL);
loader_start_detached_with_gui_error(desktop->loader, str, NULL);
}
}

View File

@@ -34,8 +34,10 @@ static void desktop_scene_main_interact_animation_callback(void* context) {
}
#ifdef APP_ARCHIVE
static void
desktop_switch_to_app(Desktop* desktop, const FlipperInternalApplication* flipper_app) {
static void desktop_switch_to_app(
Desktop* desktop,
const FlipperInternalApplication* flipper_app,
void* context) {
furi_assert(desktop);
furi_assert(flipper_app);
furi_assert(flipper_app->app);
@@ -56,6 +58,7 @@ static void
furi_thread_set_name(desktop->scene_thread, flipper_app->name);
furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size);
furi_thread_set_callback(desktop->scene_thread, flipper_app->app);
furi_thread_set_context(desktop->scene_thread, context);
furi_thread_start(desktop->scene_thread);
}
@@ -114,7 +117,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
case DesktopMainEventOpenArchive:
#ifdef APP_ARCHIVE
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE, desktop->archive_dir);
#endif
consumed = true;
break;

View File

@@ -16,6 +16,7 @@ void dialog_file_browser_set_basic_options(
options->hide_ext = true;
options->item_loader_callback = NULL;
options->item_loader_context = NULL;
options->select_right = false;
}
static DialogsApp* dialogs_app_alloc(void) {

View File

@@ -26,6 +26,7 @@ typedef struct DialogsApp DialogsApp;
* @param hide_ext true - hide extensions for files
* @param item_loader_callback callback function for providing custom icon & entry name
* @param hide_ext callback context
* @param select_right true - select with right key, allows selecting directories
*/
typedef struct {
const char* extension;
@@ -36,6 +37,8 @@ typedef struct {
bool hide_ext;
FileBrowserLoadItemCallback item_loader_callback;
void* item_loader_context;
bool select_right;
} DialogsFileBrowserOptions;
/**

View File

@@ -43,6 +43,7 @@ bool dialog_file_browser_show(
.item_callback = options ? options->item_loader_callback : NULL,
.item_callback_context = options ? options->item_loader_context : NULL,
.base_path = furi_string_get_cstr(base_path),
.select_right = options ? options->select_right : false,
}};
DialogsAppReturn return_data;

View File

@@ -18,6 +18,8 @@ typedef struct {
FileBrowserLoadItemCallback item_callback;
void* item_callback_context;
const char* base_path;
bool select_right;
} DialogsAppMessageDataFileBrowser;
typedef struct {

View File

@@ -46,6 +46,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
data->file_icon,
data->hide_ext);
file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context);
file_browser_set_select_right(file_browser, data->select_right);
file_browser_start(file_browser, data->preselected_filename);
view_holder_set_view(view_holder, file_browser_get_view(file_browser));

View File

@@ -127,6 +127,8 @@ struct FileBrowser {
FuriString* result_path;
FuriTimer* scroll_timer;
bool select_right;
};
typedef struct {
@@ -234,10 +236,11 @@ void file_browser_configure(
furi_check(browser);
browser->ext_filter = extension;
browser->skip_assets = skip_assets;
browser->hide_ext = hide_ext;
browser->base_path = base_path;
browser->skip_assets = skip_assets;
browser->hide_dot_files = hide_dot_files;
browser->hide_ext = hide_ext;
browser->select_right = false;
with_view_model(
browser->view,
@@ -296,6 +299,11 @@ void file_browser_set_item_callback(
browser->item_callback = callback;
}
void file_browser_set_select_right(FileBrowser* browser, bool select_right) {
furi_check(browser);
browser->select_right = select_right;
}
static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) {
size_t array_size = items_array_size(model->items);
@@ -793,6 +801,31 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
}
consumed = true;
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypeShort && browser->select_right) {
BrowserItem_t* selected_item = NULL;
with_view_model(
browser->view,
FileBrowserModel * model,
{
if(browser_is_item_in_array(model, model->item_idx)) {
selected_item =
items_array_get(model->items, model->item_idx - model->array_offset);
}
},
false);
if(selected_item) {
if(selected_item->type == BrowserItemTypeFile ||
selected_item->type == BrowserItemTypeFolder) {
furi_string_set(browser->result_path, selected_item->path);
if(browser->callback) {
browser->callback(browser->context);
}
}
}
consumed = true;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypeShort) {
bool is_root = false;

View File

@@ -48,6 +48,8 @@ void file_browser_set_item_callback(
FileBrowserLoadItemCallback callback,
void* context);
void file_browser_set_select_right(FileBrowser* browser, bool select_right);
#ifdef __cplusplus
}
#endif

View File

@@ -4,6 +4,7 @@
#include <applications.h>
#include <archive/helpers/archive_favorites.h>
#include <toolbox/run_parallel.h>
#include <archive/helpers/archive_helpers_ext.h>
#include "loader.h"
#include "loader_i.h"
@@ -130,7 +131,12 @@ static void loader_menu_apps_callback(void* context, uint32_t index) {
LoaderMenuApp* app = context;
const MenuApp* menu_app = MenuAppList_get(app->apps_list, index);
const char* name = menu_app->path ? menu_app->path : menu_app->name;
loader_menu_start(name);
if(menu_app->path && !strstr(menu_app->path, ".fap")) {
run_with_default_app(menu_app->path);
} else {
loader_menu_start(name);
}
}
static void loader_menu_last_callback(void* context, uint32_t index) {
@@ -225,6 +231,14 @@ static void loader_menu_add_app_entry(
app);
}
static const Icon* loader_menu_get_ext_icon(Storage* storage, const char* path) {
if(storage_dir_exists(storage, path)) return &I_dir_10px;
const char* ext = strrchr(path, '.');
if(ext && strcasecmp(ext, ".js") == 0) return &I_js_script_10px;
return &I_file_10px;
}
bool loader_menu_load_fap_meta(
Storage* storage,
FuriString* path,
@@ -254,11 +268,9 @@ static void loader_menu_find_add_app(LoaderMenuApp* app, Storage* storage, FuriS
if(furi_string_start_with(line, "/")) {
path = strdup(furi_string_get_cstr(line));
if(!loader_menu_load_fap_meta(storage, line, line, &icon)) {
free((void*)path);
path = NULL;
} else {
name = strdup(furi_string_get_cstr(line));
icon = loader_menu_get_ext_icon(storage, path);
}
name = strdup(furi_string_get_cstr(line));
} else {
for(size_t i = 0; !name && i < FLIPPER_APPS_COUNT; i++) {
if(furi_string_equal(line, FLIPPER_APPS[i].name)) {

View File

@@ -31,7 +31,7 @@ typedef enum {
typedef enum {
DesktopSettingsAppKeybindActionTypeMainApp,
DesktopSettingsAppKeybindActionTypeExternalApp,
DesktopSettingsAppKeybindActionTypeOpenFile,
DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory,
DesktopSettingsAppKeybindActionTypeMoreActions,
DesktopSettingsAppKeybindActionTypeRemoveKeybind,
} DesktopSettingsAppKeybindActionType;

View File

@@ -31,7 +31,7 @@ static void
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneKeybindsAction);
break;
case DesktopSettingsAppKeybindActionTypeExternalApp:
case DesktopSettingsAppKeybindActionTypeOpenFile: {
case DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory: {
const char* base_path;
const char* extension;
bool hide_ext;
@@ -53,9 +53,10 @@ static void
.item_loader_callback = keybinds_fap_selector_item_callback,
.item_loader_context = app,
.base_path = base_path,
.select_right = true,
};
FuriString* temp_path = furi_string_alloc_set_str(base_path);
if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
if(storage_common_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
furi_string_set(temp_path, keybind);
}
furi_record_close(RECORD_STORAGE);
@@ -98,8 +99,8 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) {
submenu_add_item(
submenu,
"Open File",
DesktopSettingsAppKeybindActionTypeOpenFile,
"File / Directory (right btn)",
DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory,
desktop_settings_scene_keybinds_action_type_submenu_callback,
app);
@@ -131,12 +132,15 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) {
}
}
if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_file_exists(storage, furi_string_get_cstr(keybind))) {
if(furi_string_end_with_str(keybind, ".fap")) {
selected = DesktopSettingsAppKeybindActionTypeExternalApp;
} else {
selected = DesktopSettingsAppKeybindActionTypeOpenFile;
selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory;
}
} else if(storage_dir_exists(storage, furi_string_get_cstr(keybind))) {
selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory;
}
furi_record_close(RECORD_STORAGE);

View File

@@ -1146,6 +1146,7 @@ Function,+,file_browser_free,void,FileBrowser*
Function,+,file_browser_get_view,View*,FileBrowser*
Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*"
Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*"
Function,+,file_browser_set_select_right,void,"FileBrowser*, _Bool"
Function,+,file_browser_start,void,"FileBrowser*, FuriString*"
Function,+,file_browser_stop,void,FileBrowser*
Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool"
1 entry status name type params
1146 Function + file_browser_get_view View* FileBrowser*
1147 Function + file_browser_set_callback void FileBrowser*, FileBrowserCallback, void*
1148 Function + file_browser_set_item_callback void FileBrowser*, FileBrowserLoadItemCallback, void*
1149 Function + file_browser_set_select_right void FileBrowser*, _Bool
1150 Function + file_browser_start void FileBrowser*, FuriString*
1151 Function + file_browser_stop void FileBrowser*
1152 Function + file_browser_worker_alloc BrowserWorker* FuriString*, const char*, const char*, _Bool, _Bool