Files
Momentum-Firmware/applications/services/desktop/desktop_keybinds.c
Alexander Bays 0e3e1b352b 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>
2025-07-22 01:51:33 +00:00

231 lines
9.0 KiB
C

#include "desktop_keybinds.h"
#include "desktop_keybinds_filename.h"
#include "desktop_i.h"
#include <applications/main/archive/helpers/archive_helpers_ext.h>
#include <flipper_format/flipper_format.h>
#include <storage/storage.h>
#include <saved_struct.h>
#include <input/input.h>
#define TAG "DesktopKeybinds"
#define OLD_DESKTOP_KEYBINDS_VER (1)
#define OLD_DESKTOP_KEYBINDS_MAGIC (0x14)
#define OLD_DESKTOP_KEYBINDS_PATH DESKTOP_KEYBINDS_PATH_MIGRATE
#define OLD_MAX_KEYBIND_LENGTH (64)
typedef struct {
char data[OLD_MAX_KEYBIND_LENGTH];
} OldKeybind;
typedef OldKeybind OldKeybinds[DesktopKeybindTypeMAX][DesktopKeybindKeyMAX];
void desktop_keybinds_migrate(Desktop* desktop) {
if(!storage_common_exists(desktop->storage, DESKTOP_KEYBINDS_PATH)) {
OldKeybinds old;
const bool success = saved_struct_load(
OLD_DESKTOP_KEYBINDS_PATH,
&old,
sizeof(old),
OLD_DESKTOP_KEYBINDS_MAGIC,
OLD_DESKTOP_KEYBINDS_VER);
if(success) {
DesktopKeybinds new;
for(DesktopKeybindType type = 0; type < DesktopKeybindTypeMAX; type++) {
for(DesktopKeybindKey key = 0; key < DesktopKeybindKeyMAX; key++) {
FuriString* keybind = furi_string_alloc_set(old[type][key].data);
if(furi_string_empty(keybind)) {
furi_string_set_str(keybind, "_");
} else if(furi_string_equal(keybind, EXT_PATH("apps/Misc/nightstand.fap"))) {
furi_string_set(keybind, "Clock");
} else if(furi_string_equal(keybind, "RFID")) {
furi_string_set(keybind, "125 kHz RFID");
} else if(furi_string_equal(keybind, "SubGHz")) {
furi_string_set(keybind, "Sub-GHz");
} else if(furi_string_equal(keybind, "Xtreme")) {
furi_string_set(keybind, "Momentum");
}
new[type][key] = keybind;
}
}
desktop_keybinds_save(desktop, &new);
desktop_keybinds_free(&new);
}
}
storage_common_remove(desktop->storage, OLD_DESKTOP_KEYBINDS_PATH);
}
const char* desktop_keybinds_defaults[DesktopKeybindTypeMAX][DesktopKeybindKeyMAX] = {
[DesktopKeybindTypePress] =
{
[DesktopKeybindKeyUp] = "Lock Menu",
[DesktopKeybindKeyDown] = "Archive",
[DesktopKeybindKeyRight] = "Passport",
[DesktopKeybindKeyLeft] = "Clock",
},
[DesktopKeybindTypeHold] =
{
[DesktopKeybindKeyUp] = "_",
[DesktopKeybindKeyDown] = "_",
[DesktopKeybindKeyRight] = "Device Info",
[DesktopKeybindKeyLeft] = "Lock with PIN",
},
};
const char* desktop_keybind_types[DesktopKeybindTypeMAX] = {
[DesktopKeybindTypePress] = "Press",
[DesktopKeybindTypeHold] = "Hold",
};
const char* desktop_keybind_keys[DesktopKeybindKeyMAX] = {
[DesktopKeybindKeyUp] = "Up",
[DesktopKeybindKeyDown] = "Down",
[DesktopKeybindKeyRight] = "Right",
[DesktopKeybindKeyLeft] = "Left",
};
static FuriString*
desktop_keybinds_load_one(Desktop* desktop, DesktopKeybindType type, DesktopKeybindKey key) {
bool success = false;
FuriString* keybind = furi_string_alloc();
FlipperFormat* file = flipper_format_file_alloc(desktop->storage);
if(flipper_format_file_open_existing(file, DESKTOP_KEYBINDS_PATH)) {
FuriString* keybind_name = furi_string_alloc_printf(
"%s%s", desktop_keybind_types[type], desktop_keybind_keys[key]);
success = flipper_format_read_string(file, furi_string_get_cstr(keybind_name), keybind);
furi_string_free(keybind_name);
}
flipper_format_free(file);
if(!success) {
FURI_LOG_W(TAG, "Failed to load file, using defaults");
furi_string_set(keybind, desktop_keybinds_defaults[type][key]);
}
return keybind;
}
void desktop_keybinds_load(Desktop* desktop, DesktopKeybinds* keybinds) {
for(DesktopKeybindType type = 0; type < DesktopKeybindTypeMAX; type++) {
for(DesktopKeybindKey key = 0; key < DesktopKeybindKeyMAX; key++) {
const char* default_keybind = desktop_keybinds_defaults[type][key];
if((*keybinds)[type][key]) {
furi_string_set((*keybinds)[type][key], default_keybind);
} else {
(*keybinds)[type][key] = furi_string_alloc_set(default_keybind);
}
}
}
FlipperFormat* file = flipper_format_file_alloc(desktop->storage);
FuriString* keybind_name = furi_string_alloc();
if(flipper_format_file_open_existing(file, DESKTOP_KEYBINDS_PATH)) {
for(DesktopKeybindType type = 0; type < DesktopKeybindTypeMAX; type++) {
for(DesktopKeybindKey key = 0; key < DesktopKeybindKeyMAX; key++) {
furi_string_printf(
keybind_name, "%s%s", desktop_keybind_types[type], desktop_keybind_keys[key]);
if(!flipper_format_read_string(
file, furi_string_get_cstr(keybind_name), (*keybinds)[type][key])) {
furi_string_set((*keybinds)[type][key], desktop_keybinds_defaults[type][key]);
goto fail;
}
}
}
} else {
fail:
FURI_LOG_W(TAG, "Failed to load file, using defaults");
}
furi_string_free(keybind_name);
flipper_format_free(file);
}
void desktop_keybinds_save(Desktop* desktop, const DesktopKeybinds* keybinds) {
FlipperFormat* file = flipper_format_file_alloc(desktop->storage);
FuriString* keybind_name = furi_string_alloc();
if(flipper_format_file_open_always(file, DESKTOP_KEYBINDS_PATH)) {
for(DesktopKeybindType type = 0; type < DesktopKeybindTypeMAX; type++) {
for(DesktopKeybindKey key = 0; key < DesktopKeybindKeyMAX; key++) {
furi_string_printf(
keybind_name, "%s%s", desktop_keybind_types[type], desktop_keybind_keys[key]);
if(!flipper_format_write_string_cstr(
file,
furi_string_get_cstr(keybind_name),
furi_string_get_cstr((*keybinds)[type][key]))) {
goto fail;
}
}
}
} else {
fail:
FURI_LOG_E(TAG, "Failed to save file");
}
furi_string_free(keybind_name);
flipper_format_free(file);
}
void desktop_keybinds_free(DesktopKeybinds* keybinds) {
for(DesktopKeybindType type = 0; type < DesktopKeybindTypeMAX; type++) {
for(DesktopKeybindKey key = 0; key < DesktopKeybindKeyMAX; key++) {
furi_string_free((*keybinds)[type][key]);
}
}
}
static const DesktopKeybindType keybind_types[] = {
[InputTypeShort] = DesktopKeybindTypePress,
[InputTypeLong] = DesktopKeybindTypeHold,
};
static const DesktopKeybindKey keybind_keys[] = {
[InputKeyUp] = DesktopKeybindKeyUp,
[InputKeyDown] = DesktopKeybindKeyDown,
[InputKeyRight] = DesktopKeybindKeyRight,
[InputKeyLeft] = DesktopKeybindKeyLeft,
};
void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
if(_type != InputTypeShort && _type != InputTypeLong) return;
if(_key != InputKeyUp && _key != InputKeyDown && _key != InputKeyRight && _key != InputKeyLeft)
return;
DesktopKeybindType type = keybind_types[_type];
DesktopKeybindKey key = keybind_keys[_key];
FuriString* keybind = desktop_keybinds_load_one(desktop, type, key);
if(furi_string_equal(keybind, "_")) {
} 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")) {
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"), "");
} else if(furi_string_equal(keybind, "Device Info")) {
loader_start_detached_with_gui_error(desktop->loader, "Power", "about_battery");
} else if(furi_string_equal(keybind, "Lock Menu")) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenLockMenu);
} else if(furi_string_equal(keybind, "Lock Keypad")) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventLockKeypad);
} else if(furi_string_equal(keybind, "Lock with PIN")) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventLockWithPin);
} else if(furi_string_equal(keybind, "Wipe Device")) {
loader_start_detached_with_gui_error(desktop->loader, "Storage", "Wipe Device");
} else {
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, str, NULL);
}
}
furi_string_free(keybind);
}