From 0f240c4dbc8d2ceb93ad025d893bb5337505fecf Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 20 Feb 2025 03:58:55 +0400 Subject: [PATCH] [FL-3949] Universal IR signal selection (#4085) * feat: universal ir signal selection * fix: f18, format specifiers * update labels with suggestions from the ui team Co-authored-by: Aleksandr Kutuzov --- .../main/infrared/infrared_brute_force.c | 93 ++++++++++--- .../main/infrared/infrared_brute_force.h | 20 ++- applications/main/infrared/infrared_cli.c | 13 +- .../main/infrared/infrared_custom_event.h | 6 +- .../common/infrared_scene_universal_common.c | 128 +++++++++++++++--- .../scenes/infrared_scene_universal_ac.c | 2 +- .../scenes/infrared_scene_universal_audio.c | 2 +- .../infrared_scene_universal_projector.c | 2 +- .../scenes/infrared_scene_universal_tv.c | 2 +- .../infrared/views/infrared_progress_view.c | 116 +++++++++++----- .../infrared/views/infrared_progress_view.h | 37 +++-- applications/services/gui/scene_manager.c | 5 + applications/services/gui/scene_manager.h | 8 ++ lib/flipper_format/flipper_format.c | 16 +++ lib/flipper_format/flipper_format.h | 24 ++++ targets/f18/api_symbols.csv | 5 +- targets/f7/api_symbols.csv | 5 +- 17 files changed, 376 insertions(+), 108 deletions(-) diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 8c7422d5e..636635894 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -2,26 +2,61 @@ #include #include +#include #include #include "infrared_signal.h" +ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); + typedef struct { - uint32_t index; - uint32_t count; + size_t index; + SignalPositionArray_t signals; } InfraredBruteForceRecord; +static inline void ir_bf_record_init(InfraredBruteForceRecord* record) { + record->index = 0; + SignalPositionArray_init(record->signals); +} +#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r))) + +static inline void + ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) { + dest->index = src->index; + SignalPositionArray_init_set(dest->signals, src->signals); +} +#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s))) + +static inline void + ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) { + dest->index = src->index; + SignalPositionArray_set(dest->signals, src->signals); +} +#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s))) + +static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) { + SignalPositionArray_clear(record->signals); +} +#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r))) + +#define IR_BF_RECORD_OPLIST \ + (INIT(IR_BF_RECORD_INIT), \ + INIT_SET(IR_BF_RECORD_INIT_SET), \ + SET(IR_BF_RECORD_SET), \ + CLEAR(IR_BF_RECORD_CLEAR)) + DICT_DEF2( InfraredBruteForceRecordDict, FuriString*, FURI_STRING_OPLIST, InfraredBruteForceRecord, - M_POD_OPLIST); + IR_BF_RECORD_OPLIST); struct InfraredBruteForce { FlipperFormat* ff; const char* db_filename; FuriString* current_record_name; + InfraredBruteForceRecord current_record; InfraredSignal* current_signal; InfraredBruteForceRecordDict_t records; bool is_started; @@ -39,6 +74,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) { } void infrared_brute_force_free(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_clear(brute_force->records); furi_string_free(brute_force->current_record_name); @@ -46,11 +82,13 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) { } void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) { + furi_check(brute_force); furi_assert(!brute_force->is_started); brute_force->db_filename = db_filename; } InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); InfraredErrorCode error = InfraredErrorCodeNone; @@ -66,19 +104,19 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br break; } - bool signals_valid = false; + size_t signal_start = flipper_format_tell(ff); + bool signal_valid = false; while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) { error = infrared_signal_read_body(signal, ff); - signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); - if(!signals_valid) break; + signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); + if(!signal_valid) break; InfraredBruteForceRecord* record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name); - if(record) { //-V547 - ++(record->count); - } + furi_assert(record); + SignalPositionArray_push_back(record->signals, signal_start); } - if(!signals_valid) break; + if(!signal_valid) break; } while(false); infrared_signal_free(signal); @@ -93,6 +131,7 @@ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count) { + furi_check(brute_force); furi_assert(!brute_force->is_started); bool success = false; *record_count = 0; @@ -103,9 +142,10 @@ bool infrared_brute_force_start( InfraredBruteForceRecordDict_next(it)) { const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it); if(record->value.index == index) { - *record_count = record->value.count; + *record_count = SignalPositionArray_size(record->value.signals); if(*record_count) { furi_string_set(brute_force->current_record_name, record->key); + brute_force->current_record = record->value; } break; } @@ -124,10 +164,12 @@ bool infrared_brute_force_start( } bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) { + furi_check(brute_force); return brute_force->is_started; } void infrared_brute_force_stop(InfraredBruteForce* brute_force) { + furi_check(brute_force); furi_assert(brute_force->is_started); furi_string_reset(brute_force->current_record_name); infrared_signal_free(brute_force->current_signal); @@ -138,25 +180,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { furi_record_close(RECORD_STORAGE); } -bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { +bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) { + furi_check(brute_force); furi_assert(brute_force->is_started); - const bool success = infrared_signal_search_by_name_and_read( - brute_force->current_signal, - brute_force->ff, - furi_string_get_cstr(brute_force->current_record_name)) == - InfraredErrorCodeNone; - if(success) { - infrared_signal_transmit(brute_force->current_signal); - } - return success; + if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false; + + size_t signal_start = + *SignalPositionArray_cget(brute_force->current_record.signals, signal_index); + if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart)) + return false; + + if(INFRARED_ERROR_PRESENT( + infrared_signal_read_body(brute_force->current_signal, brute_force->ff))) + return false; + + infrared_signal_transmit(brute_force->current_signal); + return true; } void infrared_brute_force_add_record( InfraredBruteForce* brute_force, uint32_t index, const char* name) { - InfraredBruteForceRecord value = {.index = index, .count = 0}; + InfraredBruteForceRecord value; + ir_bf_record_init(&value); + value.index = index; FuriString* key; key = furi_string_alloc_set(name); InfraredBruteForceRecordDict_set_at(brute_force->records, key, value); diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 879642257..2c75d37f2 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -78,18 +78,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force); void infrared_brute_force_stop(InfraredBruteForce* brute_force); /** - * @brief Send the next signal from the chosen category. - * - * This function is called repeatedly until no more signals are left - * in the chosen signal category. - * - * @warning Transmission must be started first by calling infrared_brute_force_start() - * before calling this function. - * - * @param[in,out] brute_force pointer to the instance to be used. - * @returns true if the next signal existed and could be transmitted, false otherwise. + * @brief Send an arbitrary signal from the chosen category. + * + * @param[in] brute_force pointer to the instance + * @param signal_index the index of the signal within the category, must be + * between 0 and `record_count` as told by + * `infrared_brute_force_start` + * + * @returns true on success, false otherwise */ -bool infrared_brute_force_send_next(InfraredBruteForce* brute_force); +bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index); /** * @brief Add a signal category to an InfraredBruteForce instance's dictionary. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b700cf121..85ae95658 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -475,25 +475,24 @@ static void break; } - uint32_t record_count; + uint32_t signal_count, current_signal = 0; bool running = infrared_brute_force_start( - brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count); + brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count); - if(record_count <= 0) { + if(signal_count <= 0) { printf("Invalid signal name.\r\n"); break; } - printf("Sending %lu signal(s)...\r\n", record_count); + printf("Sending %lu signal(s)...\r\n", signal_count); printf("Press Ctrl-C to stop.\r\n"); - int records_sent = 0; while(running) { - running = infrared_brute_force_send_next(brute_force); + running = infrared_brute_force_send(brute_force, current_signal); if(cli_cmd_interrupt_received(cli)) break; - printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100)); + printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100)); fflush(stdout); } diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 2efc99f4b..7109a48b7 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -3,7 +3,7 @@ #include #include -enum InfraredCustomEventType { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 InfraredCustomEventTypeReserved = 100, InfraredCustomEventTypeMenuSelected, @@ -13,7 +13,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypeTextEditDone, InfraredCustomEventTypePopupClosed, InfraredCustomEventTypeButtonSelected, - InfraredCustomEventTypeBackPressed, + InfraredCustomEventTypePopupInput, InfraredCustomEventTypeTaskFinished, InfraredCustomEventTypeRpcLoadFile, @@ -27,7 +27,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypeGpioTxPinChanged, InfraredCustomEventTypeGpioOtgChanged, -}; +} InfraredCustomEventType; #pragma pack(push, 1) typedef union { diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index a52f141c4..62b7350e7 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -2,15 +2,28 @@ #include +#pragma pack(push, 1) +typedef union { + uint32_t packed_value; + struct { + bool is_paused; + uint8_t padding; + uint16_t signal_index; + }; +} InfraredSceneState; +#pragma pack(pop) + void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { InfraredApp* infrared = context; uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } -static void infrared_scene_universal_common_progress_back_callback(void* context) { +static void infrared_scene_universal_common_progress_input_callback( + void* context, + InfraredProgressViewInput input) { InfraredApp* infrared = context; - uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1); + uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } @@ -19,8 +32,8 @@ static void ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; infrared_progress_view_set_progress_total(progress, record_count); - infrared_progress_view_set_back_callback( - progress, infrared_scene_universal_common_progress_back_callback, infrared); + infrared_progress_view_set_input_callback( + progress, infrared_scene_universal_common_progress_input_callback, infrared); view_stack_add_view(view_stack, infrared_progress_view_get_view(progress)); infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } @@ -51,29 +64,111 @@ void infrared_scene_universal_common_on_enter(void* context) { infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); } +static void infrared_scene_universal_common_handle_popup_input( + InfraredApp* infrared, + InfraredProgressViewInput input) { + InfraredBruteForce* brute_force = infrared->brute_force; + SceneManager* scene_manager = infrared->scene_manager; + uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager); + switch(input) { + case InfraredProgressViewInputStop: { + infrared_brute_force_stop(brute_force); + infrared_scene_universal_common_hide_popup(infrared); + break; + } + + case InfraredProgressViewInputPause: { + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); + infrared_progress_view_set_paused(infrared->progress, true); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.is_paused = true; + if(scene_state.signal_index) + scene_state.signal_index--; // when running, the state stores the next index + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputResume: { + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); + infrared_progress_view_set_paused(infrared->progress, false); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.is_paused = false; + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputNextSignal: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + scene_state.signal_index++; + if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1)) + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + break; + } + + case InfraredProgressViewInputPreviousSignal: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + if(scene_state.signal_index) { + scene_state.signal_index--; + if(infrared_progress_view_set_progress( + infrared->progress, scene_state.signal_index + 1)) + scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value); + } + break; + } + + case InfraredProgressViewInputSendSingle: { + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); + infrared_brute_force_send(infrared->brute_force, scene_state.signal_index); + infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); + break; + } + + default: + furi_crash(); + } +} + bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; InfraredBruteForce* brute_force = infrared->brute_force; + uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager); bool consumed = false; if(infrared_brute_force_is_started(brute_force)) { if(event.type == SceneManagerEventTypeTick) { - bool success = infrared_brute_force_send_next(brute_force); - if(success) { - success = infrared_progress_view_increase_progress(infrared->progress); + InfraredSceneState scene_state = { + .packed_value = scene_manager_get_scene_state(scene_manager, scene_id)}; + + if(!scene_state.is_paused) { + bool success = infrared_brute_force_send(brute_force, scene_state.signal_index); + if(success) { + success = infrared_progress_view_set_progress( + infrared->progress, scene_state.signal_index + 1); + scene_state.signal_index++; + scene_manager_set_scene_state( + scene_manager, scene_id, scene_state.packed_value); + } + if(!success) { + infrared_brute_force_stop(brute_force); + infrared_scene_universal_common_hide_popup(infrared); + } + consumed = true; } - if(!success) { - infrared_brute_force_stop(brute_force); - infrared_scene_universal_common_hide_popup(infrared); - } - consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { - if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { - infrared_brute_force_stop(brute_force); - infrared_scene_universal_common_hide_popup(infrared); + uint16_t event_type; + int16_t event_value; + infrared_custom_event_unpack(event.event, &event_type, &event_value); + if(event_type == InfraredCustomEventTypePopupInput) { + infrared_scene_universal_common_handle_popup_input(infrared, event_value); + consumed = true; } - consumed = true; } } else { if(event.type == SceneManagerEventTypeBack) { @@ -87,6 +182,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(event_type == InfraredCustomEventTypeButtonSelected) { uint32_t record_count; if(infrared_brute_force_start(brute_force, event_value, &record_count)) { + scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0); dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 9288a4a4d..15c4b8db2 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -118,7 +118,7 @@ void infrared_scene_universal_ac_on_enter(void* context) { button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51); button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51); - button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); + button_panel_add_label(button_panel, 24, 10, FontPrimary, "AC"); infrared_scene_universal_common_on_enter(context); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index a15b2ce99..223251f10 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -114,7 +114,7 @@ void infrared_scene_universal_audio_on_enter(void* context) { context); infrared_brute_force_add_record(brute_force, i++, "Vol_up"); - button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); + button_panel_add_label(button_panel, 1, 10, FontPrimary, "Audio player"); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); infrared_scene_universal_common_on_enter(context); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index c665444fb..c939a0f1b 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -63,7 +63,7 @@ void infrared_scene_universal_projector_on_enter(void* context) { context); infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); - button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); + button_panel_add_label(button_panel, 10, 11, FontPrimary, "Projector"); button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30); infrared_scene_universal_common_on_enter(context); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 16633e29c..6130861ee 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -91,7 +91,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { context); infrared_brute_force_add_record(brute_force, i++, "Ch_prev"); - button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); + button_panel_add_label(button_panel, 25, 10, FontPrimary, "TV"); infrared_scene_universal_common_on_enter(context); } diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 432da7ff1..716027297 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -14,54 +14,80 @@ struct InfraredProgressView { View* view; - InfraredProgressViewBackCallback back_callback; + InfraredProgressViewInputCallback input_callback; void* context; }; typedef struct { size_t progress; size_t progress_total; + bool is_paused; } InfraredProgressViewModel; -bool infrared_progress_view_increase_progress(InfraredProgressView* progress) { - furi_assert(progress); - bool result = false; - - InfraredProgressViewModel* model = view_get_model(progress->view); - if(model->progress < model->progress_total) { - ++model->progress; - result = model->progress < model->progress_total; - } - view_commit_model(progress->view, true); - - return result; -} - static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model; uint8_t x = 0; - uint8_t y = 36; + uint8_t y = 25; uint8_t width = 63; - uint8_t height = 59; + uint8_t height = 81; elements_bold_rounded_frame(canvas, x, y, width, height); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( - canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ..."); + canvas, + x + 32, + y + 9, + AlignCenter, + AlignCenter, + model->is_paused ? "Paused" : "Sending..."); float progress_value = (float)model->progress / model->progress_total; elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value); - uint8_t percent_value = 100 * model->progress / model->progress_total; - char percents_string[10] = {0}; - snprintf(percents_string, sizeof(percents_string), "%d%%", percent_value); + char progress_string[16] = {0}; + if(model->is_paused) { + snprintf( + progress_string, + sizeof(progress_string), + "%zu/%zu", + model->progress, + model->progress_total); + } else { + uint8_t percent_value = 100 * model->progress / model->progress_total; + snprintf(progress_string, sizeof(progress_string), "%d%%", percent_value); + } elements_multiline_text_aligned( - canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string); + canvas, x + 33, y + 37, AlignCenter, AlignCenter, progress_string); - canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8); - canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); + uint8_t buttons_x = x + (model->is_paused ? 10 : 14); + uint8_t buttons_y = y + (model->is_paused ? 46 : 50); + + canvas_draw_icon(canvas, buttons_x + 0, buttons_y + 0, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 8, model->is_paused ? "resume" : "stop"); + + canvas_draw_icon(canvas, buttons_x + 1, buttons_y + 10, &I_Ok_btn_9x9); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 17, model->is_paused ? "send 1" : "pause"); + + if(model->is_paused) { + canvas_draw_icon(canvas, buttons_x + 2, buttons_y + 21, &I_ButtonLeftSmall_3x5); + canvas_draw_icon(canvas, buttons_x + 7, buttons_y + 21, &I_ButtonRightSmall_3x5); + canvas_draw_str(canvas, buttons_x + 14, buttons_y + 26, "select"); + } +} + +bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress) { + bool result; + with_view_model( + instance->view, + InfraredProgressViewModel * model, + { + result = progress <= model->progress_total; + if(result) model->progress = progress; + }, + true); + return result; } void infrared_progress_view_set_progress_total( @@ -74,14 +100,40 @@ void infrared_progress_view_set_progress_total( view_commit_model(progress->view, false); } +void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused) { + with_view_model( + instance->view, InfraredProgressViewModel * model, { model->is_paused = is_paused; }, true); +} + bool infrared_progress_view_input_callback(InputEvent* event, void* context) { InfraredProgressView* instance = context; + if(event->type != InputTypeShort && event->type != InputTypeRepeat) return false; + if(!instance->input_callback) return false; - if((event->type == InputTypeShort) && (event->key == InputKeyBack)) { - if(instance->back_callback) { - instance->back_callback(instance->context); - } - } + with_view_model( + instance->view, + InfraredProgressViewModel * model, + { + if(model->is_paused) { + if(event->key == InputKeyLeft) + instance->input_callback( + instance->context, InfraredProgressViewInputPreviousSignal); + else if(event->key == InputKeyRight) + instance->input_callback( + instance->context, InfraredProgressViewInputNextSignal); + else if(event->key == InputKeyOk) + instance->input_callback( + instance->context, InfraredProgressViewInputSendSingle); + else if(event->key == InputKeyBack) + instance->input_callback(instance->context, InfraredProgressViewInputResume); + } else { + if(event->key == InputKeyOk) + instance->input_callback(instance->context, InfraredProgressViewInputPause); + else if(event->key == InputKeyBack) + instance->input_callback(instance->context, InfraredProgressViewInputStop); + } + }, + false); return true; } @@ -106,12 +158,12 @@ void infrared_progress_view_free(InfraredProgressView* progress) { free(progress); } -void infrared_progress_view_set_back_callback( +void infrared_progress_view_set_input_callback( InfraredProgressView* instance, - InfraredProgressViewBackCallback callback, + InfraredProgressViewInputCallback callback, void* context) { furi_assert(instance); - instance->back_callback = callback; + instance->input_callback = callback; instance->context = context; } diff --git a/applications/main/infrared/views/infrared_progress_view.h b/applications/main/infrared/views/infrared_progress_view.h index c44f1a482..c33f1e553 100644 --- a/applications/main/infrared/views/infrared_progress_view.h +++ b/applications/main/infrared/views/infrared_progress_view.h @@ -10,11 +10,20 @@ extern "C" { #endif -/** Anonumous instance */ +/** Anonymous instance */ typedef struct InfraredProgressView InfraredProgressView; -/** Callback for back button handling */ -typedef void (*InfraredProgressViewBackCallback)(void*); +typedef enum { + InfraredProgressViewInputStop, + InfraredProgressViewInputPause, + InfraredProgressViewInputResume, + InfraredProgressViewInputPreviousSignal, + InfraredProgressViewInputNextSignal, + InfraredProgressViewInputSendSingle, +} InfraredProgressViewInput; + +/** Callback for input handling */ +typedef void (*InfraredProgressViewInputCallback)(void* context, InfraredProgressViewInput event); /** Allocate and initialize Infrared view * @@ -35,13 +44,12 @@ void infrared_progress_view_free(InfraredProgressView* instance); */ View* infrared_progress_view_get_view(InfraredProgressView* instance); -/** Increase progress on progress view module +/** Set progress of progress view module * * @param instance view module - * @retval true - value is incremented and maximum is reached, - * false - value is incremented and maximum is not reached + * @param progress progress value */ -bool infrared_progress_view_increase_progress(InfraredProgressView* instance); +bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress); /** Set maximum progress value * @@ -52,15 +60,22 @@ void infrared_progress_view_set_progress_total( InfraredProgressView* instance, uint16_t progress_max); -/** Set back button callback +/** Selects the variant of the View + * + * @param instance view instance + * @param is_paused the "paused" variant is displayed if true; the "sending" one if false + */ +void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused); + +/** Set input callback * * @param instance - view module - * @param callback - callback to call for back button + * @param callback - callback to call for input * @param context - context to pass to callback */ -void infrared_progress_view_set_back_callback( +void infrared_progress_view_set_input_callback( InfraredProgressView* instance, - InfraredProgressViewBackCallback callback, + InfraredProgressViewInputCallback callback, void* context); #ifdef __cplusplus diff --git a/applications/services/gui/scene_manager.c b/applications/services/gui/scene_manager.c index 11acc0796..485e31d11 100644 --- a/applications/services/gui/scene_manager.c +++ b/applications/services/gui/scene_manager.c @@ -230,6 +230,11 @@ bool scene_manager_search_and_switch_to_another_scene( } } +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager) { + furi_check(scene_manager); + return *SceneManagerIdStack_back(scene_manager->scene_id_stack); +} + void scene_manager_stop(SceneManager* scene_manager) { furi_check(scene_manager); diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index 54dfa9cd4..8dad92aac 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -170,6 +170,14 @@ bool scene_manager_search_and_switch_to_another_scene( SceneManager* scene_manager, uint32_t scene_id); +/** Get id of current scene + * + * @param scene_manager SceneManager instance + * + * @return Scene ID + */ +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager); + /** Exit from current scene * * @param scene_manager SceneManager instance diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index d07022e12..8aebf853f 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -8,6 +8,11 @@ #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" +// permits direct casting between `FlipperFormatOffset` and `StreamOffset` +static_assert((size_t)FlipperFormatOffsetFromCurrent == (size_t)StreamOffsetFromCurrent); +static_assert((size_t)FlipperFormatOffsetFromStart == (size_t)StreamOffsetFromStart); +static_assert((size_t)FlipperFormatOffsetFromEnd == (size_t)StreamOffsetFromEnd); + /********************************** Private **********************************/ struct FlipperFormat { Stream* stream; @@ -127,6 +132,17 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) { return stream_rewind(flipper_format->stream); } +size_t flipper_format_tell(FlipperFormat* flipper_format) { + furi_check(flipper_format); + return stream_tell(flipper_format->stream); +} + +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor) { + furi_check(flipper_format); + // direct usage of `anchor` made valid by `static_assert`s at the top of this file + return stream_seek(flipper_format->stream, offset, (StreamOffset)anchor); +} + bool flipper_format_seek_to_end(FlipperFormat* flipper_format) { furi_check(flipper_format); return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd); diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 4a1bb767b..5b13496e1 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -94,6 +94,12 @@ extern "C" { typedef struct FlipperFormat FlipperFormat; +typedef enum { + FlipperFormatOffsetFromCurrent, + FlipperFormatOffsetFromStart, + FlipperFormatOffsetFromEnd, +} FlipperFormatOffset; + /** Allocate FlipperFormat as string. * * @return FlipperFormat* pointer to a FlipperFormat instance @@ -216,6 +222,24 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m */ bool flipper_format_rewind(FlipperFormat* flipper_format); +/** Get the RW pointer position + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return RW pointer position + */ +size_t flipper_format_tell(FlipperFormat* flipper_format); + +/** Set the RW pointer position to an arbitrary value + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param offset Offset relative to the anchor point + * @param anchor Anchor point (e.g. start of file) + * + * @return True on success + */ +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor); + /** Move the RW pointer at the end. Can be useful if you want to add some data * after reading. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d158b4f68..57eaf6ab3 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.1,, +Version,+,80.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1038,6 +1038,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1046,6 +1047,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -2468,6 +2470,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2c3b696cb..900539019 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.1,, +Version,+,80.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1149,6 +1149,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1157,6 +1158,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -3105,6 +3107,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t"