From 71ee4a2bac946c2d302500e147942488eeda073d Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:54:26 +1000 Subject: [PATCH] SUB DECODE: Add Save FIlename Dialog (No option flags) --- helpers/protopirate_types.h | 1 + scenes/protopirate_scene_sub_decode.c | 98 +++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/helpers/protopirate_types.h b/helpers/protopirate_types.h index 64145ae..b04acb8 100644 --- a/helpers/protopirate_types.h +++ b/helpers/protopirate_types.h @@ -44,6 +44,7 @@ typedef enum { ProtoPirateCustomEventSubDecodeEmulate, ProtoPirateCustomEventSubDecodeBruteforceStart, ProtoPirateCustomEventPsaBruteforceComplete, + ProtoPirateCustomEventSubDecodeSaveConfirm, // File Browser ProtoPirateCustomEventSavedFileSelected, // Need saving confirmation diff --git a/scenes/protopirate_scene_sub_decode.c b/scenes/protopirate_scene_sub_decode.c index e0e6817..90e22de 100644 --- a/scenes/protopirate_scene_sub_decode.c +++ b/scenes/protopirate_scene_sub_decode.c @@ -493,6 +493,12 @@ static bool protopirate_scene_sub_decode_open_browser_for_next_file(ProtoPirateA return true; } +static void protopirate_scene_sub_decode_text_input_callback(void* context) { + ProtoPirateApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSubDecodeSaveConfirm); +} + void protopirate_scene_sub_decode_on_enter(void* context) { ProtoPirateApp* app = context; @@ -621,9 +627,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even protopirate_history_get_raw_data(ctx->history, ctx->selected_history_index); if(ff) { - FuriString* saved_path = furi_string_alloc(); FuriString* file_name_str = furi_string_alloc(); - if(app->datetime_filenames) { //Get the date and time to save. DateTime date_time; @@ -638,6 +642,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even date_time.minute, date_time.second); } + // Extract protocol name FuriString* protocol = furi_string_alloc(); protopirate_storage_get_capture_display_protocol(ff, protocol); @@ -650,18 +655,53 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even furi_string_replace_all(file_name_str, "/", "_"); furi_string_replace_all(file_name_str, " ", "_"); - if(protopirate_storage_save_capture( - ff, - furi_string_get_cstr(file_name_str), - saved_path, - app->datetime_filenames)) { - notification_message(app->notifications, &sequence_success); - } else { - notification_message(app->notifications, &sequence_error); - } + // Get the next auto-generated filename (just the name part) + FuriString* auto_path = furi_string_alloc(); + if(protopirate_storage_get_next_filename( + furi_string_get_cstr(file_name_str), auto_path, app->datetime_filenames)) { + // Extract just the filename without folder and extension + const char* full = furi_string_get_cstr(auto_path); + const char* slash = strrchr(full, '/'); + const char* name_start = slash ? slash + 1 : full; + // Copy without extension + size_t name_len = strlen(name_start); + const char* dot = strrchr(name_start, '.'); + if(dot) name_len = dot - name_start; + if(name_len >= sizeof(app->save_filename)) + name_len = sizeof(app->save_filename) - 1; + + memcpy(app->save_filename, name_start, name_len); + app->save_filename[name_len] = '\0'; + } else { + snprintf(app->save_filename, sizeof(app->save_filename), "capture"); + } + furi_string_free(auto_path); + + // Store context for when text input confirms + app->save_history_idx = app->txrx->idx_menu_chosen; + app->save_from_saved_info = false; + + //Make sure we have a text input window. + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + ProtoPirateViewTextInput, + text_input_get_view(app->text_input)); + + // Configure and show text input + text_input_reset(app->text_input); + text_input_set_header_text(app->text_input, "Save filename:"); + text_input_set_result_callback( + app->text_input, + protopirate_scene_sub_decode_text_input_callback, + app, + app->save_filename, + sizeof(app->save_filename), + false); // don't clear default text + + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewTextInput); furi_string_free(file_name_str); - furi_string_free(saved_path); } else { FURI_LOG_E( TAG, @@ -670,6 +710,40 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even notification_message(app->notifications, &sequence_error); } consumed = true; + } else if(event.event == ProtoPirateCustomEventSubDecodeSaveConfirm) { + // User confirmed the filename in text input + FlipperFormat* ff = + protopirate_history_get_raw_data(app->txrx->history, app->save_history_idx); + if(ff) { + // Build full path: folder/filename.psf + FuriString* save_path = furi_string_alloc_printf( + "%s/%s%s", + PROTOPIRATE_APP_FOLDER, + app->save_filename, + PROTOPIRATE_APP_EXTENSION); + + if(protopirate_storage_save_capture_to_path(ff, furi_string_get_cstr(save_path))) { + notification_message(app->notifications, &sequence_success); + FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(save_path)); + } else { + notification_message(app->notifications, &sequence_error); + FURI_LOG_E(TAG, "Save failed"); + } + furi_string_free(save_path); + } + + // Return to the receiver info widget + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); + + //Kill the text_input view. + if(app->text_input) { + FURI_LOG_D(TAG, "Removing text_input view"); + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput); + text_input_free(app->text_input); + app->text_input = NULL; + } + consumed = true; + } #ifdef ENABLE_EMULATE_FEATURE else if(