#include "momentum_app.h" #include static bool momentum_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); MomentumApp* app = context; return scene_manager_handle_custom_event(app->scene_manager, event); } void callback_reboot(void* context) { UNUSED(context); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeNormal); } bool momentum_app_apply(MomentumApp* app) { if(app->save_mainmenu_apps) { Stream* stream = file_stream_alloc(app->storage); if(file_stream_open(stream, MAINMENU_APPS_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { stream_write_format(stream, "MenuAppList Version %u\n", 1); for(size_t i = 0; i < CharList_size(app->mainmenu_app_exes); i++) { stream_write_format(stream, "%s\n", *CharList_get(app->mainmenu_app_exes, i)); } } file_stream_close(stream); stream_free(stream); } if(app->save_desktop) { desktop_api_set_settings(app->desktop, &app->desktop_settings); } if(app->save_subghz_freqs) { FlipperFormat* file = flipper_format_file_alloc(app->storage); do { FrequencyList_it_t it; if(!flipper_format_file_open_always(file, EXT_PATH("subghz/assets/setting_user"))) break; if(!flipper_format_write_header_cstr( file, SUBGHZ_SETTING_FILE_TYPE, SUBGHZ_SETTING_FILE_VERSION)) break; while(flipper_format_delete_key(file, "Add_standard_frequencies")) ; flipper_format_write_bool( file, "Add_standard_frequencies", &app->subghz_use_defaults, 1); if(!flipper_format_rewind(file)) break; while(flipper_format_delete_key(file, "Frequency")) ; FrequencyList_it(it, app->subghz_static_freqs); for(size_t i = 0; i < FrequencyList_size(app->subghz_static_freqs); i++) { flipper_format_write_uint32( file, "Frequency", FrequencyList_get(app->subghz_static_freqs, i), 1); } if(!flipper_format_rewind(file)) break; while(flipper_format_delete_key(file, "Hopper_frequency")) ; for(size_t i = 0; i < FrequencyList_size(app->subghz_hopper_freqs); i++) { flipper_format_write_uint32( file, "Hopper_frequency", FrequencyList_get(app->subghz_hopper_freqs, i), 1); } } while(false); flipper_format_free(file); } if(app->save_subghz) { FlipperFormat* file = flipper_format_file_alloc(app->storage); do { if(!flipper_format_file_open_always(file, "/ext/subghz/assets/extend_range.txt")) break; if(!flipper_format_write_header_cstr(file, "Flipper SubGhz Setting File", 1)) break; if(!flipper_format_write_comment_cstr( file, "Whether to allow extended ranges that can break your flipper")) break; if(!flipper_format_write_bool( file, "use_ext_range_at_own_risk", &app->subghz_extend, 1)) break; if(!flipper_format_write_bool(file, "ignore_default_tx_region", &app->subghz_bypass, 1)) break; } while(0); flipper_format_free(file); } if(app->save_name) { if(strcmp(app->device_name, "") == 0) { storage_simply_remove(app->storage, NAMESPOOF_PATH); } else { FlipperFormat* file = flipper_format_file_alloc(app->storage); do { if(!flipper_format_file_open_always(file, NAMESPOOF_PATH)) break; if(!flipper_format_write_header_cstr(file, NAMESPOOF_HEADER, NAMESPOOF_VERSION)) break; if(!flipper_format_write_string_cstr(file, "Name", app->device_name)) break; } while(0); flipper_format_free(file); } } if(app->save_dolphin) { if(app->save_xp) { app->dolphin->state->data.icounter = app->dolphin_xp; } if(app->save_angry) { app->dolphin->state->data.butthurt = app->dolphin_angry; } app->dolphin->state->dirty = true; dolphin_flush(app->dolphin); dolphin_reload_state(app->dolphin); } if(app->save_backlight) { rgb_backlight_save_settings(); } if(app->save_settings) { momentum_settings_save(); } if(app->show_slideshow) { callback_reboot(NULL); } else if(app->require_reboot) { popup_set_header(app->popup, "Rebooting...", 64, 26, AlignCenter, AlignCenter); popup_set_text(app->popup, "Applying changes...", 64, 40, AlignCenter, AlignCenter); popup_set_callback(app->popup, callback_reboot); popup_set_context(app->popup, app); popup_set_timeout(app->popup, 1000); popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewPopup); return true; } else if(app->apply_pack) { asset_packs_free(); popup_set_header(app->popup, "Reloading...", 64, 26, AlignCenter, AlignCenter); popup_set_text(app->popup, "Applying asset pack...", 64, 40, AlignCenter, AlignCenter); popup_set_callback(app->popup, NULL); popup_set_context(app->popup, NULL); popup_set_timeout(app->popup, 0); popup_disable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewPopup); asset_packs_init(); } return false; } static bool momentum_app_back_event_callback(void* context) { furi_assert(context); MomentumApp* app = context; if(!scene_manager_has_previous_scene(app->scene_manager, MomentumAppSceneStart)) { if(momentum_app_apply(app)) { return true; } } return scene_manager_handle_back_event(app->scene_manager); } static void momentum_app_push_mainmenu_app_raw(MomentumApp* app, FuriString* label, FuriString* exe) { CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(exe))); // Display logic mimics applications/services/gui/modules/menu.c if(furi_string_equal(label, "Momentum")) { furi_string_set(label, "MNTM"); } else if(furi_string_equal(label, "125 kHz RFID")) { furi_string_set(label, "RFID"); } else if(furi_string_equal(label, "Sub-GHz")) { furi_string_set(label, "SubGHz"); } else if(furi_string_start_with_str(label, "[")) { size_t trim = furi_string_search_str(label, "] ", 1); if(trim != FURI_STRING_FAILURE) { furi_string_right(label, trim + 2); } } CharList_push_back(app->mainmenu_app_labels, strdup(furi_string_get_cstr(label))); } void momentum_app_push_mainmenu_app(MomentumApp* app, FuriString* exe) { FuriString* label = furi_string_alloc(); if(furi_string_start_with(exe, "/")) { uint8_t unused_icon[FAP_MANIFEST_MAX_ICON_SIZE]; uint8_t* unused_icon_ptr = unused_icon; if(!flipper_application_load_name_and_icon(exe, app->storage, &unused_icon_ptr, label)) { const char* end = strrchr(furi_string_get_cstr(exe), '/'); furi_string_set(label, end ? end + 1 : furi_string_get_cstr(exe)); } momentum_app_push_mainmenu_app_raw(app, label, exe); } else { bool found = false; for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) { if(!strcmp(furi_string_get_cstr(exe), 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(exe), FLIPPER_EXTERNAL_APPS[i].name)) { furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name); found = true; } } // Ignore unknown apps just like in main menu, prevents "ghost" apps when saving if(!furi_string_empty(label)) { momentum_app_push_mainmenu_app_raw(app, label, exe); } } furi_string_free(label); } void momentum_app_load_mainmenu_apps(MomentumApp* app) { // Loading logic mimics applications/services/loader/loader_menu.c Stream* stream = file_stream_alloc(app->storage); FuriString* line = furi_string_alloc(); FuriString* label = furi_string_alloc(); uint32_t version; uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE); if(file_stream_open(stream, MAINMENU_APPS_PATH, FSAM_READ, FSOM_OPEN_EXISTING) && stream_read_line(stream, line) && sscanf(furi_string_get_cstr(line), "MenuAppList Version %lu", &version) == 1 && version <= 1) { while(stream_read_line(stream, line)) { furi_string_trim(line); if(version == 0) { if(furi_string_equal(line, "RFID")) { furi_string_set(line, "125 kHz RFID"); } else if(furi_string_equal(line, "SubGHz")) { furi_string_set(line, "Sub-GHz"); } else if(furi_string_equal(line, "Xtreme")) { furi_string_set(line, "Momentum"); } } momentum_app_push_mainmenu_app(app, line); } } else { for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { furi_string_set(label, FLIPPER_APPS[i].name); furi_string_set(line, FLIPPER_APPS[i].name); momentum_app_push_mainmenu_app_raw(app, label, line); } // Until count - 1 because last app is hardcoded below for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT - 1; i++) { furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name); furi_string_set(line, FLIPPER_EXTERNAL_APPS[i].name); momentum_app_push_mainmenu_app_raw(app, label, line); } } free(unused_icon); furi_string_free(label); furi_string_free(line); file_stream_close(stream); stream_free(stream); } void momentum_app_empty_mainmenu_apps(MomentumApp* app) { CharList_it_t it; for(CharList_it(it, app->mainmenu_app_labels); !CharList_end_p(it); CharList_next(it)) { free(*CharList_cref(it)); } CharList_reset(app->mainmenu_app_labels); for(CharList_it(it, app->mainmenu_app_exes); !CharList_end_p(it); CharList_next(it)) { free(*CharList_cref(it)); } CharList_reset(app->mainmenu_app_exes); } MomentumApp* momentum_app_alloc() { MomentumApp* app = malloc(sizeof(MomentumApp)); app->gui = furi_record_open(RECORD_GUI); app->storage = furi_record_open(RECORD_STORAGE); app->desktop = furi_record_open(RECORD_DESKTOP); app->dolphin = furi_record_open(RECORD_DOLPHIN); app->dialogs = furi_record_open(RECORD_DIALOGS); app->expansion = furi_record_open(RECORD_EXPANSION); app->notification = furi_record_open(RECORD_NOTIFICATION); // View Dispatcher and Scene Manager app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&momentum_app_scene_handlers, app); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); view_dispatcher_set_custom_event_callback( app->view_dispatcher, momentum_app_custom_event_callback); view_dispatcher_set_navigation_event_callback( app->view_dispatcher, momentum_app_back_event_callback); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // Gui Modules app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewVarItemList, variable_item_list_get_view(app->var_item_list)); app->submenu = submenu_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewSubmenu, submenu_get_view(app->submenu)); app->text_input = text_input_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewTextInput, text_input_get_view(app->text_input)); app->byte_input = byte_input_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewByteInput, byte_input_get_view(app->byte_input)); app->number_input = number_input_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewNumberInput, number_input_get_view(app->number_input)); app->popup = popup_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewPopup, popup_get_view(app->popup)); app->dialog_ex = dialog_ex_alloc(); view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewDialogEx, dialog_ex_get_view(app->dialog_ex)); // Settings init app->asset_pack_index = 0; CharList_init(app->asset_pack_names); File* folder = storage_file_alloc(app->storage); FileInfo info; char* name = malloc(ASSET_PACKS_NAME_LEN); if(storage_dir_open(folder, ASSET_PACKS_PATH)) { while(storage_dir_read(folder, &info, name, ASSET_PACKS_NAME_LEN)) { if(info.flags & FSF_DIRECTORY && name[0] != '.') { char* copy = strdup(name); size_t idx = 0; for(; idx < CharList_size(app->asset_pack_names); idx++) { char* comp = *CharList_get(app->asset_pack_names, idx); if(strcasecmp(copy, comp) < 0) { break; } } CharList_push_at(app->asset_pack_names, idx, copy); if(app->asset_pack_index != 0) { if(idx < app->asset_pack_index) app->asset_pack_index++; } else { if(strcmp(copy, momentum_settings.asset_pack) == 0) app->asset_pack_index = idx + 1; } } } } free(name); storage_file_free(folder); CharList_init(app->mainmenu_app_labels); CharList_init(app->mainmenu_app_exes); momentum_app_load_mainmenu_apps(app); desktop_api_get_settings(app->desktop, &app->desktop_settings); FlipperFormat* file = flipper_format_file_alloc(app->storage); FrequencyList_init(app->subghz_static_freqs); FrequencyList_init(app->subghz_hopper_freqs); app->subghz_use_defaults = true; do { uint32_t temp; if(!flipper_format_file_open_existing(file, EXT_PATH("subghz/assets/setting_user"))) break; flipper_format_read_bool(file, "Add_standard_frequencies", &app->subghz_use_defaults, 1); if(!flipper_format_rewind(file)) break; while(flipper_format_read_uint32(file, "Frequency", &temp, 1)) { if(furi_hal_subghz_is_frequency_valid(temp)) { FrequencyList_push_back(app->subghz_static_freqs, temp); } } if(!flipper_format_rewind(file)) break; while(flipper_format_read_uint32(file, "Hopper_frequency", &temp, 1)) { if(furi_hal_subghz_is_frequency_valid(temp)) { FrequencyList_push_back(app->subghz_hopper_freqs, temp); } } } while(false); flipper_format_free(file); file = flipper_format_file_alloc(app->storage); if(flipper_format_file_open_existing(file, "/ext/subghz/assets/extend_range.txt")) { flipper_format_read_bool(file, "use_ext_range_at_own_risk", &app->subghz_extend, 1); flipper_format_read_bool(file, "ignore_default_tx_region", &app->subghz_bypass, 1); } flipper_format_free(file); strlcpy(app->device_name, furi_hal_version_get_name_ptr(), FURI_HAL_VERSION_ARRAY_NAME_LENGTH); DolphinStats stats = dolphin_stats(app->dolphin); app->dolphin_xp = stats.icounter; app->dolphin_angry = stats.butthurt; // Will be "(version) (commit or date)" app->version_tag = furi_string_alloc_set(version_get_version(NULL)); size_t separator = furi_string_size(app->version_tag); // Need canvas to calculate text length Canvas* canvas = gui_direct_draw_acquire(app->gui); canvas_set_font(canvas, FontPrimary); if(furi_string_equal(app->version_tag, "mntm-dev")) { // Add space, add commit sha furi_string_cat_printf(app->version_tag, " %s", version_get_githash(NULL)); // Make uppercase for(size_t i = 0; i < furi_string_size(app->version_tag); ++i) { furi_string_set_char( app->version_tag, i, toupper(furi_string_get_char(app->version_tag, i))); } // Remove sha digits if necessary while(canvas_string_width(canvas, furi_string_get_cstr(app->version_tag)) >= canvas_width(canvas) - 8) { furi_string_left(app->version_tag, furi_string_size(app->version_tag) - 1); } } else { // Make uppercase, add space, add build date furi_string_replace(app->version_tag, "mntm", "MNTM"); furi_string_cat_printf(app->version_tag, " %s", version_get_builddate(NULL)); } // Add spaces to align right while(canvas_string_width(canvas, furi_string_get_cstr(app->version_tag)) <= canvas_width(canvas) - 13) { furi_string_replace_at(app->version_tag, separator, 0, " "); } gui_direct_draw_release(app->gui); return app; } void momentum_app_free(MomentumApp* app) { furi_assert(app); // Gui modules view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewVarItemList); variable_item_list_free(app->var_item_list); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewSubmenu); submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewTextInput); text_input_free(app->text_input); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewByteInput); byte_input_free(app->byte_input); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewNumberInput); number_input_free(app->number_input); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewPopup); popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewDialogEx); dialog_ex_free(app->dialog_ex); // View Dispatcher and Scene Manager view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); // Settings deinit CharList_it_t it; for(CharList_it(it, app->asset_pack_names); !CharList_end_p(it); CharList_next(it)) { free(*CharList_cref(it)); } CharList_clear(app->asset_pack_names); momentum_app_empty_mainmenu_apps(app); CharList_clear(app->mainmenu_app_labels); CharList_clear(app->mainmenu_app_exes); FrequencyList_clear(app->subghz_static_freqs); FrequencyList_clear(app->subghz_hopper_freqs); furi_string_free(app->version_tag); // Records furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_EXPANSION); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DOLPHIN); furi_record_close(RECORD_DESKTOP); furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_GUI); free(app); } extern int32_t momentum_app(void* p) { MomentumApp* app = momentum_app_alloc(); // Check for command line arguments to navigate to specific scenes uint32_t first_scene = MomentumAppSceneStart; if(p && strlen(p)) { if(!strcmp(p, "MiscScreen")) { first_scene = MomentumAppSceneMiscScreen; } } scene_manager_next_scene(app->scene_manager, first_scene); view_dispatcher_run(app->view_dispatcher); momentum_app_free(app); return 0; }