[FL-3456] Allow for larger Infrared remotes (#3164)

* Do not load all signals at once (Draft)
* Minor cleanup
* Refactor remote renaming
* Improve function signatures
* Rename infrared_remote functions
* Optimise signal loading
* Implement adding signals to remote
* Add read_name() method
* Deprecate a function
* Partially implement deleting signals (draft)
* Use m-array instead of m-list for signal name directory
* Use plain C strings instead of furi_string
* Implement deleting signals
* Implement deleting signals via generalised callback
* Implement renaming signals
* Rename some types
* Some more renaming
* Remove unused type
* Implement inserting signals (internal use)
* Improve InfraredMoveView
* Send an event to move a signal
* Remove unused type
* Implement moving signals
* Implement creating new remotes with one signal
* Un-deprecate and rename a function
* Add InfraredRemote API docs
* Add InfraredSignal API docs
* Better error messages
* Show progress pop-up when moving buttons in a remote
* Copy labels to the InfraredMoveView to avoid pointer invalidation
* Improve file selection scene
* Show progress pop-up when renaming buttons in a remote
* Refactor a scene
* Show progress when deleting a button from remote
* Use a random name for temp files
* Add docs to infrared_brute_force.h
* Rename Infrared type to InfraredApp
* Add docs to infrared_app_i.h

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Georgii Surkov 2023-10-30 19:20:35 +03:00 committed by GitHub
parent 917410a0a8
commit c8180747db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1622 additions and 775 deletions

View File

@ -1,3 +0,0 @@
#pragma once
typedef struct Infrared Infrared;

View File

@ -1,48 +1,52 @@
#include "infrared_i.h"
#include "infrared_app_i.h"
#include <string.h>
#include <toolbox/path.h>
#include <dolphin/dolphin.h>
#define TAG "InfraredApp"
#define INFRARED_TX_MIN_INTERVAL_MS 50U
static const NotificationSequence* infrared_notification_sequences[] = {
&sequence_success,
&sequence_set_only_green_255,
&sequence_reset_green,
&sequence_solid_yellow,
&sequence_reset_rgb,
&sequence_blink_start_cyan,
&sequence_blink_start_magenta,
&sequence_blink_stop,
static const NotificationSequence*
infrared_notification_sequences[InfraredNotificationMessageCount] = {
&sequence_success,
&sequence_set_only_green_255,
&sequence_reset_green,
&sequence_solid_yellow,
&sequence_reset_rgb,
&sequence_blink_start_cyan,
&sequence_blink_start_magenta,
&sequence_blink_stop,
};
static void infrared_make_app_folder(Infrared* infrared) {
static void infrared_make_app_folder(InfraredApp* infrared) {
if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) {
dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder");
infrared_show_error_message(infrared, "Cannot create\napp folder");
}
}
static bool infrared_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
return scene_manager_handle_custom_event(infrared->scene_manager, event);
}
static bool infrared_back_event_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
return scene_manager_handle_back_event(infrared->scene_manager);
}
static void infrared_tick_event_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
scene_manager_handle_tick_event(infrared->scene_manager);
}
static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
furi_assert(infrared->rpc_ctx);
if(event == RpcAppEventSessionClose) {
@ -109,8 +113,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path)
furi_record_close(RECORD_STORAGE);
}
static Infrared* infrared_alloc() {
Infrared* infrared = malloc(sizeof(Infrared));
static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp));
infrared->file_path = furi_string_alloc();
@ -139,7 +143,7 @@ static Infrared* infrared_alloc() {
infrared->worker = infrared_worker_alloc();
infrared->remote = infrared_remote_alloc();
infrared->received_signal = infrared_signal_alloc();
infrared->current_signal = infrared_signal_alloc();
infrared->brute_force = infrared_brute_force_alloc();
infrared->submenu = submenu_alloc();
@ -184,7 +188,7 @@ static Infrared* infrared_alloc() {
return infrared;
}
static void infrared_free(Infrared* infrared) {
static void infrared_free(InfraredApp* infrared) {
furi_assert(infrared);
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state;
@ -229,7 +233,7 @@ static void infrared_free(Infrared* infrared) {
scene_manager_free(infrared->scene_manager);
infrared_brute_force_free(infrared->brute_force);
infrared_signal_free(infrared->received_signal);
infrared_signal_free(infrared->current_signal);
infrared_remote_free(infrared->remote);
infrared_worker_free(infrared->worker);
@ -248,65 +252,61 @@ static void infrared_free(Infrared* infrared) {
}
bool infrared_add_remote_with_button(
Infrared* infrared,
const InfraredApp* infrared,
const char* button_name,
InfraredSignal* signal) {
const InfraredSignal* signal) {
InfraredRemote* remote = infrared->remote;
FuriString *new_name, *new_path;
new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME);
new_path = furi_string_alloc_set(INFRARED_APP_FOLDER);
FuriString* new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME);
FuriString* new_path = furi_string_alloc_set(INFRARED_APP_FOLDER);
infrared_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path));
furi_string_cat_printf(
new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION);
infrared_remote_reset(remote);
infrared_remote_set_name(remote, furi_string_get_cstr(new_name));
infrared_remote_set_path(remote, furi_string_get_cstr(new_path));
bool success = false;
do {
if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break;
if(!infrared_remote_append_signal(remote, signal, button_name)) break;
success = true;
} while(false);
furi_string_free(new_name);
furi_string_free(new_path);
return infrared_remote_add_button(remote, button_name, signal);
return success;
}
bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) {
InfraredRemote* remote = infrared->remote;
const char* remote_path = infrared_remote_get_path(remote);
const char* old_path = infrared_remote_get_path(remote);
if(!strcmp(infrared_remote_get_name(remote), name)) {
if(!strcmp(infrared_remote_get_name(remote), new_name)) {
return true;
}
FuriString* new_name;
new_name = furi_string_alloc_set(name);
FuriString* new_name_fstr = furi_string_alloc_set(new_name);
FuriString* new_path_fstr = furi_string_alloc_set(old_path);
infrared_find_vacant_remote_name(new_name, remote_path);
infrared_find_vacant_remote_name(new_name_fstr, old_path);
FuriString* new_path;
new_path = furi_string_alloc_set(infrared_remote_get_path(remote));
if(furi_string_end_with(new_path, INFRARED_APP_EXTENSION)) {
size_t filename_start = furi_string_search_rchar(new_path, '/');
furi_string_left(new_path, filename_start);
if(furi_string_end_with(new_path_fstr, INFRARED_APP_EXTENSION)) {
path_extract_dirname(old_path, new_path_fstr);
}
furi_string_cat_printf(
new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION);
Storage* storage = furi_record_open(RECORD_STORAGE);
path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr));
furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION);
FS_Error status = storage_common_rename(
storage, infrared_remote_get_path(remote), furi_string_get_cstr(new_path));
infrared_remote_set_name(remote, furi_string_get_cstr(new_name));
infrared_remote_set_path(remote, furi_string_get_cstr(new_path));
const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr));
furi_string_free(new_name);
furi_string_free(new_path);
furi_string_free(new_name_fstr);
furi_string_free(new_path_fstr);
furi_record_close(RECORD_STORAGE);
return (status == FSE_OK || status == FSE_EXIST);
return success;
}
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
void infrared_tx_start(InfraredApp* infrared) {
if(infrared->app_state.is_transmitting) {
return;
}
@ -317,12 +317,12 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
return;
}
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
if(infrared_signal_is_raw(infrared->current_signal)) {
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(infrared->current_signal);
infrared_worker_set_raw_signal(
infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal);
infrared_worker_set_decoded_signal(infrared->worker, message);
}
@ -336,20 +336,20 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
infrared->app_state.is_transmitting = true;
}
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
furi_assert(button_index < infrared_remote_get_button_count(infrared->remote));
void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) {
furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote));
InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index);
InfraredSignal* signal = infrared_remote_button_get_signal(button);
infrared_tx_start_signal(infrared, signal);
if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) {
infrared_tx_start(infrared);
} else {
infrared_show_error_message(
infrared,
"Failed to load\n\"%s\"",
infrared_remote_get_signal_name(infrared->remote, button_index));
}
}
void infrared_tx_start_received(Infrared* infrared) {
infrared_tx_start_signal(infrared, infrared->received_signal);
}
void infrared_tx_stop(Infrared* infrared) {
void infrared_tx_stop(InfraredApp* infrared) {
if(!infrared->app_state.is_transmitting) {
return;
}
@ -363,25 +363,27 @@ void infrared_tx_stop(Infrared* infrared) {
infrared->app_state.last_transmit_time = furi_get_tick();
}
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) {
va_list args;
va_start(args, text);
va_start(args, fmt);
vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args);
vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, fmt, args);
va_end(args);
}
void infrared_text_store_clear(Infrared* infrared, uint32_t bank) {
void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank) {
memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE + 1);
}
void infrared_play_notification_message(Infrared* infrared, uint32_t message) {
furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*));
void infrared_play_notification_message(
const InfraredApp* infrared,
InfraredNotificationMessage message) {
furi_assert(message < InfraredNotificationMessageCount);
notification_message(infrared->notifications, infrared_notification_sequences[message]);
}
void infrared_show_loading_popup(Infrared* infrared, bool show) {
void infrared_show_loading_popup(const InfraredApp* infrared, bool show) {
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
ViewStack* view_stack = infrared->view_stack;
Loading* loading = infrared->loading;
@ -397,19 +399,30 @@ void infrared_show_loading_popup(Infrared* infrared, bool show) {
}
}
void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
FuriString* message = furi_string_alloc_vprintf(fmt, args);
dialog_message_show_storage_error(infrared->dialogs, furi_string_get_cstr(message));
furi_string_free(message);
va_end(args);
}
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
if(infrared_worker_signal_is_decoded(received_signal)) {
infrared_signal_set_message(
infrared->received_signal, infrared_worker_get_decoded_signal(received_signal));
infrared->current_signal, infrared_worker_get_decoded_signal(received_signal));
} else {
const uint32_t* timings;
size_t timings_size;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_size);
infrared_signal_set_raw_signal(
infrared->received_signal,
infrared->current_signal,
timings,
timings_size,
INFRARED_COMMON_CARRIER_FREQUENCY,
@ -422,20 +435,20 @@ void infrared_signal_received_callback(void* context, InfraredWorkerSignal* rece
void infrared_text_input_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone);
}
void infrared_popup_closed_callback(void* context) {
furi_assert(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypePopupClosed);
}
int32_t infrared_app(void* p) {
Infrared* infrared = infrared_alloc();
InfraredApp* infrared = infrared_alloc();
infrared_make_app_folder(infrared);
@ -451,13 +464,15 @@ int32_t infrared_app(void* p) {
rpc_system_app_send_started(infrared->rpc_ctx);
is_rpc_mode = true;
} else {
furi_string_set(infrared->file_path, (const char*)p);
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
const char* file_path = (const char*)p;
is_remote_loaded = infrared_remote_load(infrared->remote, file_path);
if(!is_remote_loaded) {
dialog_message_show_storage_error(
infrared->dialogs, "Failed to load\nselected remote");
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path);
return -1;
}
furi_string_set(infrared->file_path, file_path);
}
}

View File

@ -0,0 +1,15 @@
/**
* @file infrared_app.h
* @brief Infrared application - start here.
*
* @see infrared_app_i.h for the main application data structure and functions.
* @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals.
* @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes.
* @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals.
*/
#pragma once
/**
* @brief InfraredApp opaque type declaration.
*/
typedef struct InfraredApp InfraredApp;

View File

@ -0,0 +1,288 @@
/**
* @file infrared_app_i.h
* @brief Main Infrared application types and functions.
*/
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <assets_icons.h>
#include <gui/view_stack.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/text_input.h>
#include <gui/modules/button_menu.h>
#include <gui/modules/button_panel.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <infrared_worker.h>
#include "infrared_app.h"
#include "infrared_remote.h"
#include "infrared_brute_force.h"
#include "infrared_custom_event.h"
#include "scenes/infrared_scene.h"
#include "views/infrared_progress_view.h"
#include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h"
#include "rpc/rpc_app.h"
#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
#define INFRARED_APP_EXTENSION ".ir"
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
#define INFRARED_LOG_TAG "InfraredApp"
/**
* @brief Enumeration of invalid remote button indices.
*/
typedef enum {
InfraredButtonIndexNone = -1, /**< No button is currently selected. */
} InfraredButtonIndex;
/**
* @brief Enumeration of editing targets.
*/
typedef enum {
InfraredEditTargetNone, /**< No editing target is selected. */
InfraredEditTargetRemote, /**< Whole remote is selected as editing target. */
InfraredEditTargetButton, /**< Single button is selected as editing target. */
} InfraredEditTarget;
/**
* @brief Enumeration of editing modes.
*/
typedef enum {
InfraredEditModeNone, /**< No editing mode is selected. */
InfraredEditModeRename, /**< Rename mode is selected. */
InfraredEditModeDelete, /**< Delete mode is selected. */
} InfraredEditMode;
/**
* @brief Infrared application state type.
*/
typedef struct {
bool is_learning_new_remote; /**< Learning new remote or adding to an existing one. */
bool is_debug_enabled; /**< Whether to enable or disable debugging features. */
bool is_transmitting; /**< Whether a signal is currently being transmitted. */
InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */
InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */
int32_t current_button_index; /**< Selected button index (move destination). */
int32_t prev_button_index; /**< Previous button index (move source). */
uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */
} InfraredAppState;
/**
* @brief Infrared application type.
*/
struct InfraredApp {
SceneManager* scene_manager; /**< Pointer to a SceneManager instance. */
ViewDispatcher* view_dispatcher; /**< Pointer to a ViewDispatcher instance. */
Gui* gui; /**< Pointer to a Gui instance. */
Storage* storage; /**< Pointer to a Storage instance. */
DialogsApp* dialogs; /**< Pointer to a DialogsApp instance. */
NotificationApp* notifications; /**< Pointer to a NotificationApp instance. */
InfraredWorker* worker; /**< Used to send or receive signals. */
InfraredRemote* remote; /**< Holds the currently loaded remote. */
InfraredSignal* current_signal; /**< Holds the currently loaded signal. */
InfraredBruteForce* brute_force; /**< Used for the Universal Remote feature. */
Submenu* submenu; /**< Standard view for displaying application menus. */
TextInput* text_input; /**< Standard view for receiving user text input. */
DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */
ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */
Popup* popup; /**< Standard view for displaying messages. */
ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */
InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */
InfraredMoveView* move_view; /**< Custom view for rearranging buttons in a remote. */
ButtonPanel* button_panel; /**< Standard view for displaying control panels. */
Loading* loading; /**< Standard view for informing about long operations. */
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
FuriString* file_path; /**< Full path to the currently loaded file. */
/** Arbitrary text storage for various inputs. */
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state; /**< Application state. */
void* rpc_ctx; /**< Pointer to the RPC context object. */
};
/**
* @brief Enumeration of all used view types.
*/
typedef enum {
InfraredViewSubmenu,
InfraredViewTextInput,
InfraredViewDialogEx,
InfraredViewButtonMenu,
InfraredViewPopup,
InfraredViewStack,
InfraredViewDebugView,
InfraredViewMove,
} InfraredView;
/**
* @brief Enumeration of all notification message types.
*/
typedef enum {
InfraredNotificationMessageSuccess, /**< Play a short happy tune. */
InfraredNotificationMessageGreenOn, /**< Turn green LED on. */
InfraredNotificationMessageGreenOff, /**< Turn green LED off. */
InfraredNotificationMessageYellowOn, /**< Turn yellow LED on. */
InfraredNotificationMessageYellowOff, /**< Turn yellow LED off. */
InfraredNotificationMessageBlinkStartRead, /**< Blink the LED to indicate receiver mode. */
InfraredNotificationMessageBlinkStartSend, /**< Blink the LED to indicate transmitter mode. */
InfraredNotificationMessageBlinkStop, /**< Stop blinking the LED. */
InfraredNotificationMessageCount, /**< Special value equal to the message type count. */
} InfraredNotificationMessage;
/**
* @brief Add a new remote with a single signal.
*
* The filename will be automatically generated depending on
* the names and number of other files in the infrared data directory.
*
* @param[in] infrared pointer to the application instance.
* @param[in] name pointer to a zero-terminated string containing the signal name.
* @param[in] signal pointer to the signal to be added.
* @return true if the remote was successfully created, false otherwise.
*/
bool infrared_add_remote_with_button(
const InfraredApp* infrared,
const char* name,
const InfraredSignal* signal);
/**
* @brief Rename the currently loaded remote.
*
* @param[in] infrared pointer to the application instance.
* @param[in] new_name pointer to a zero-terminated string containing the new remote name.
* @return true if the remote was successfully renamed, false otherwise.
*/
bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name);
/**
* @brief Begin transmission of the currently loaded signal.
*
* The signal will be repeated indefinitely until stopped.
*
* @param[in,out] infrared pointer to the application instance.
*/
void infrared_tx_start(InfraredApp* infrared);
/**
* @brief Load a signal under the given index and begin transmission.
*
* The signal will be repeated indefinitely until stopped.
*
* @param[in,out] infrared pointer to the application instance.
* @param[in] button_index index of the signal to be loaded.
* @returns true if the signal could be loaded, false otherwise.
*/
void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index);
/**
* @brief Stop transmission of the currently loaded signal.
*
* @param[in,out] infrared pointer to the application instance.
*/
void infrared_tx_stop(InfraredApp* infrared);
/**
* @brief Set the internal text store with formatted text.
*
* @param[in,out] infrared pointer to the application instance.
* @param[in] bank index of text store bank (0 or 1).
* @param[in] fmt pointer to a zero-terminated string containing the format text.
* @param[in] ... additional arguments.
*/
void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...)
_ATTRIBUTE((__format__(__printf__, 3, 4)));
/**
* @brief Clear the internal text store.
*
* @param[in,out] infrared pointer to the application instance.
* @param[in] bank index of text store bank (0 or 1).
*/
void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank);
/**
* @brief Play a sound and/or blink the LED.
*
* @param[in] infrared pointer to the application instance.
* @param[in] message type of the message to play.
*/
void infrared_play_notification_message(
const InfraredApp* infrared,
InfraredNotificationMessage message);
/**
* @brief Show a loading pop-up screen.
*
* In order for this to work, a Stack view must be currently active and
* the main view must be added to it.
*
* @param[in] infrared pointer to the application instance.
* @param[in] show whether to show or hide the pop-up.
*/
void infrared_show_loading_popup(const InfraredApp* infrared, bool show);
/**
* @brief Show a formatted error messsage.
*
* @param[in] infrared pointer to the application instance.
* @param[in] fmt pointer to a zero-terminated string containing the format text.
* @param[in] ... additional arguments.
*/
void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...)
_ATTRIBUTE((__format__(__printf__, 2, 3)));
/**
* @brief Common received signal callback.
*
* Called when the worker has received a complete infrared signal.
*
* @param[in,out] context pointer to the user-specified context object.
* @param[in] received_signal pointer to the received signal.
*/
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
/**
* @brief Common text input callback.
*
* Called when the input has been accepted by the user.
*
* @param[in,out] context pointer to the user-specified context object.
*/
void infrared_text_input_callback(void* context);
/**
* @brief Common popup close callback.
*
* Called when the popup has been closed either by the user or after a timeout.
*
* @param[in,out] context pointer to the user-specified context object.
*/
void infrared_popup_closed_callback(void* context);

View File

@ -111,7 +111,7 @@ bool infrared_brute_force_start(
return success;
}
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
return brute_force->is_started;
}
@ -129,7 +129,9 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_and_read(
brute_force->current_signal, brute_force->ff, brute_force->current_record_name);
brute_force->current_signal,
brute_force->ff,
furi_string_get_cstr(brute_force->current_record_name));
if(success) {
infrared_signal_transmit(brute_force->current_signal);
}

View File

@ -1,23 +1,110 @@
/**
* @file infrared_brute_force.h
* @brief Infrared signal brute-forcing library.
*
* The BruteForce library is used to send large quantities of signals,
* sorted by a category. It is used to implement the Universal Remote
* feature.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
/**
* @brief InfraredBruteForce opaque type declaration.
*/
typedef struct InfraredBruteForce InfraredBruteForce;
/**
* @brief Create a new InfraredBruteForce instance.
*
* @returns pointer to the created instance.
*/
InfraredBruteForce* infrared_brute_force_alloc();
/**
* @brief Delete an InfraredBruteForce instance.
*
* @param[in,out] brute_force pointer to the instance to be deleted.
*/
void infrared_brute_force_free(InfraredBruteForce* brute_force);
/**
* @brief Set an InfraredBruteForce instance to use a signal database contained in a file.
*
* @param[in,out] brute_force pointer to the instance to be configured.
* @param[in] db_filename pointer to a zero-terminated string containing a full path to the database file.
*/
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename);
/**
* @brief Build a signal dictionary from a previously set database file.
*
* This function must be called each time after setting the database via
* a infrared_brute_force_set_db_filename() call.
*
* @param[in,out] brute_force pointer to the instance to be updated.
* @returns true on success, false otherwise.
*/
bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
/**
* @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary.
*
* @param[in,out] brute_force pointer to the instance to be started.
* @param[in] index index of the signal category in the dictionary.
* @returns true on success, false otherwise.
*/
bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count);
bool infrared_brute_force_is_started(InfraredBruteForce* brute_force);
/**
* @brief Determine whether the transmission was started.
*
* @param[in] brute_force pointer to the instance to be tested.
* @returns true if transmission was started, false otherwise.
*/
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
/**
* @brief Stop transmitting the signals.
*
* @param[in] brute_force pointer to the instance to be stopped.
*/
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.
*/
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
/**
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
*
* @param[in,out] brute_force pointer to the instance to be updated.
* @param[in] index index of the category to be added.
* @param[in] name name of the category to be added.
*/
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name);
/**
* @brief Reset an InfraredBruteForce instance.
*
* @param[in,out] brute_force pointer to the instance to be reset.
*/
void infrared_brute_force_reset(InfraredBruteForce* brute_force);

View File

@ -202,7 +202,7 @@ static bool
}
static bool infrared_cli_decode_raw_signal(
InfraredRawSignal* raw_signal,
const InfraredRawSignal* raw_signal,
InfraredDecoderHandler* decoder,
FlipperFormat* output_file,
const char* signal_name) {
@ -274,7 +274,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
continue;
}
}
InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal);
const InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal);
printf(
"Raw signal: %s, %zu samples\r\n",
furi_string_get_cstr(tmp),

View File

@ -1,146 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <assets_icons.h>
#include <gui/view_stack.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/text_input.h>
#include <gui/modules/button_menu.h>
#include <gui/modules/button_panel.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <infrared_worker.h>
#include "infrared.h"
#include "infrared_remote.h"
#include "infrared_brute_force.h"
#include "infrared_custom_event.h"
#include "scenes/infrared_scene.h"
#include "views/infrared_progress_view.h"
#include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h"
#include "rpc/rpc_app.h"
#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
#define INFRARED_APP_EXTENSION ".ir"
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
#define INFRARED_LOG_TAG "InfraredApp"
typedef enum {
InfraredButtonIndexNone = -1,
} InfraredButtonIndex;
typedef enum {
InfraredEditTargetNone,
InfraredEditTargetRemote,
InfraredEditTargetButton,
} InfraredEditTarget;
typedef enum {
InfraredEditModeNone,
InfraredEditModeRename,
InfraredEditModeDelete,
} InfraredEditMode;
typedef struct {
bool is_learning_new_remote;
bool is_debug_enabled;
bool is_transmitting;
InfraredEditTarget edit_target : 8;
InfraredEditMode edit_mode : 8;
int32_t current_button_index;
int32_t current_button_index_move_orig;
uint32_t last_transmit_time;
} InfraredAppState;
struct Infrared {
SceneManager* scene_manager;
ViewDispatcher* view_dispatcher;
Gui* gui;
Storage* storage;
DialogsApp* dialogs;
NotificationApp* notifications;
InfraredWorker* worker;
InfraredRemote* remote;
InfraredSignal* received_signal;
InfraredBruteForce* brute_force;
Submenu* submenu;
TextInput* text_input;
DialogEx* dialog_ex;
ButtonMenu* button_menu;
Popup* popup;
ViewStack* view_stack;
InfraredDebugView* debug_view;
InfraredMoveView* move_view;
ButtonPanel* button_panel;
Loading* loading;
InfraredProgressView* progress;
FuriString* file_path;
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state;
void* rpc_ctx;
};
typedef enum {
InfraredViewSubmenu,
InfraredViewTextInput,
InfraredViewDialogEx,
InfraredViewButtonMenu,
InfraredViewPopup,
InfraredViewStack,
InfraredViewDebugView,
InfraredViewMove,
} InfraredView;
typedef enum {
InfraredNotificationMessageSuccess,
InfraredNotificationMessageGreenOn,
InfraredNotificationMessageGreenOff,
InfraredNotificationMessageYellowOn,
InfraredNotificationMessageYellowOff,
InfraredNotificationMessageBlinkStartRead,
InfraredNotificationMessageBlinkStartSend,
InfraredNotificationMessageBlinkStop,
} InfraredNotificationMessage;
bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal);
bool infrared_rename_current_remote(Infrared* infrared, const char* name);
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal);
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index);
void infrared_tx_start_received(Infrared* infrared);
void infrared_tx_stop(Infrared* infrared);
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...);
void infrared_text_store_clear(Infrared* infrared, uint32_t bank);
void infrared_play_notification_message(Infrared* infrared, uint32_t message);
void infrared_show_loading_popup(Infrared* infrared, bool show);
void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
void infrared_text_input_callback(void* context);
void infrared_popup_closed_callback(void* context);

View File

@ -1,197 +1,428 @@
#include "infrared_remote.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <m-array.h>
#include <toolbox/m_cstr_dup.h>
#include <toolbox/path.h>
#include <storage/storage.h>
#include <core/common_defines.h>
#define TAG "InfraredRemote"
ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST);
#define INFRARED_FILE_HEADER "IR signals file"
#define INFRARED_FILE_VERSION (1)
ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575
struct InfraredRemote {
InfraredButtonArray_t buttons;
StringArray_t signal_names;
FuriString* name;
FuriString* path;
};
static void infrared_remote_clear_buttons(InfraredRemote* remote) {
InfraredButtonArray_it_t it;
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
InfraredButtonArray_next(it)) {
infrared_remote_button_free(*InfraredButtonArray_cref(it));
}
InfraredButtonArray_reset(remote->buttons);
}
typedef struct {
InfraredRemote* remote;
FlipperFormat* ff_in;
FlipperFormat* ff_out;
FuriString* signal_name;
InfraredSignal* signal;
size_t signal_index;
} InfraredBatch;
typedef struct {
size_t signal_index;
const char* signal_name;
const InfraredSignal* signal;
} InfraredBatchTarget;
typedef bool (
*InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target);
InfraredRemote* infrared_remote_alloc() {
InfraredRemote* remote = malloc(sizeof(InfraredRemote));
InfraredButtonArray_init(remote->buttons);
StringArray_init(remote->signal_names);
remote->name = furi_string_alloc();
remote->path = furi_string_alloc();
return remote;
}
void infrared_remote_free(InfraredRemote* remote) {
infrared_remote_clear_buttons(remote);
InfraredButtonArray_clear(remote->buttons);
StringArray_clear(remote->signal_names);
furi_string_free(remote->path);
furi_string_free(remote->name);
free(remote);
}
void infrared_remote_reset(InfraredRemote* remote) {
infrared_remote_clear_buttons(remote);
StringArray_reset(remote->signal_names);
furi_string_reset(remote->name);
furi_string_reset(remote->path);
}
void infrared_remote_set_name(InfraredRemote* remote, const char* name) {
furi_string_set(remote->name, name);
}
const char* infrared_remote_get_name(InfraredRemote* remote) {
const char* infrared_remote_get_name(const InfraredRemote* remote) {
return furi_string_get_cstr(remote->name);
}
void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
static void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
furi_string_set(remote->path, path);
path_extract_filename(remote->path, remote->name, true);
}
const char* infrared_remote_get_path(InfraredRemote* remote) {
const char* infrared_remote_get_path(const InfraredRemote* remote) {
return furi_string_get_cstr(remote->path);
}
size_t infrared_remote_get_button_count(InfraredRemote* remote) {
return InfraredButtonArray_size(remote->buttons);
size_t infrared_remote_get_signal_count(const InfraredRemote* remote) {
return StringArray_size(remote->signal_names);
}
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
return *InfraredButtonArray_get(remote->buttons, index);
const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index) {
furi_assert(index < infrared_remote_get_signal_count(remote));
return *StringArray_cget(remote->signal_names, index);
}
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
if(!strcmp(infrared_remote_button_get_name(button), name)) {
bool infrared_remote_load_signal(
const InfraredRemote* remote,
InfraredSignal* signal,
size_t index) {
furi_assert(index < infrared_remote_get_signal_count(remote));
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
bool success = false;
do {
const char* path = furi_string_get_cstr(remote->path);
if(!flipper_format_buffered_file_open_existing(ff, path)) break;
const char* name = infrared_remote_get_signal_name(remote, index);
if(!infrared_signal_search_and_read(signal, ff, name)) {
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path);
break;
}
success = true;
} while(false);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return success;
}
bool infrared_remote_get_signal_index(
const InfraredRemote* remote,
const char* name,
size_t* index) {
uint32_t i = 0;
StringArray_it_t it;
for(StringArray_it(it, remote->signal_names); !StringArray_end_p(it);
StringArray_next(it), ++i) {
if(strcmp(*StringArray_cref(it), name) == 0) {
*index = i;
return true;
}
}
return false;
}
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
InfraredRemoteButton* button = infrared_remote_button_alloc();
infrared_remote_button_set_name(button, name);
infrared_remote_button_set_signal(button, signal);
InfraredButtonArray_push_back(remote->buttons, button);
return infrared_remote_store(remote);
}
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index);
infrared_remote_button_set_name(button, new_name);
return infrared_remote_store(remote);
}
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
furi_assert(index < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button;
InfraredButtonArray_pop_at(&button, remote->buttons, index);
infrared_remote_button_free(button);
return infrared_remote_store(remote);
}
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) {
furi_assert(index_orig < InfraredButtonArray_size(remote->buttons));
furi_assert(index_dest < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button;
InfraredButtonArray_pop_at(&button, remote->buttons, index_orig);
InfraredButtonArray_push_at(remote->buttons, index_dest, button);
}
bool infrared_remote_store(InfraredRemote* remote) {
bool infrared_remote_append_signal(
InfraredRemote* remote,
const InfraredSignal* signal,
const char* name) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
bool success = false;
const char* path = furi_string_get_cstr(remote->path);
FURI_LOG_I(TAG, "store file: \'%s\'", path);
bool success = flipper_format_file_open_always(ff, path) &&
flipper_format_write_header_cstr(ff, "IR signals file", 1);
if(success) {
InfraredButtonArray_it_t it;
for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
InfraredButtonArray_next(it)) {
InfraredRemoteButton* button = *InfraredButtonArray_cref(it);
success = infrared_signal_save(
infrared_remote_button_get_signal(button),
ff,
infrared_remote_button_get_name(button));
if(!success) {
break;
}
}
}
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return success;
}
bool infrared_remote_load(InfraredRemote* remote, FuriString* path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
FuriString* buf;
buf = furi_string_alloc();
FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path));
bool success = false;
do {
if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
uint32_t version;
if(!flipper_format_read_header(ff, buf, &version)) break;
if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break;
if(!flipper_format_file_open_append(ff, path)) break;
if(!infrared_signal_save(signal, ff, name)) break;
path_extract_filename(path, buf, true);
infrared_remote_clear_buttons(remote);
infrared_remote_set_name(remote, furi_string_get_cstr(buf));
infrared_remote_set_path(remote, furi_string_get_cstr(path));
for(bool can_read = true; can_read;) {
InfraredRemoteButton* button = infrared_remote_button_alloc();
can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf);
if(can_read) {
infrared_remote_button_set_name(button, furi_string_get_cstr(buf));
InfraredButtonArray_push_back(remote->buttons, button);
} else {
infrared_remote_button_free(button);
}
}
StringArray_push_back(remote->signal_names, name);
success = true;
} while(false);
furi_string_free(buf);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return success;
}
static bool infrared_remote_batch_start(
InfraredRemote* remote,
InfraredBatchCallback batch_callback,
const InfraredBatchTarget* target) {
FuriString* tmp = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
InfraredBatch batch_context = {
.remote = remote,
.ff_in = flipper_format_buffered_file_alloc(storage),
.ff_out = flipper_format_buffered_file_alloc(storage),
.signal_name = furi_string_alloc(),
.signal = infrared_signal_alloc(),
.signal_index = 0,
};
const char* path_in = furi_string_get_cstr(remote->path);
const char* path_out;
FS_Error status;
do {
furi_string_printf(tmp, "%s.temp%08x.swp", path_in, rand());
path_out = furi_string_get_cstr(tmp);
status = storage_common_stat(storage, path_out, NULL);
} while(status == FSE_OK || status == FSE_EXIST);
bool success = false;
do {
if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break;
if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break;
if(!flipper_format_write_header_cstr(
batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION))
break;
const size_t signal_count = infrared_remote_get_signal_count(remote);
for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) {
if(!infrared_signal_read(
batch_context.signal, batch_context.ff_in, batch_context.signal_name))
break;
if(!batch_callback(&batch_context, target)) break;
}
if(batch_context.signal_index != signal_count) break;
if(!flipper_format_buffered_file_close(batch_context.ff_out)) break;
if(!flipper_format_buffered_file_close(batch_context.ff_in)) break;
const FS_Error status = storage_common_rename(storage, path_out, path_in);
success = (status == FSE_OK || status == FSE_EXIST);
} while(false);
infrared_signal_free(batch_context.signal);
furi_string_free(batch_context.signal_name);
flipper_format_free(batch_context.ff_out);
flipper_format_free(batch_context.ff_in);
furi_string_free(tmp);
furi_record_close(RECORD_STORAGE);
return success;
}
static bool infrared_remote_insert_signal_callback(
const InfraredBatch* batch,
const InfraredBatchTarget* target) {
// Insert a signal under the specified index
if(batch->signal_index == target->signal_index) {
if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false;
StringArray_push_at(
batch->remote->signal_names, target->signal_index, target->signal_name);
}
// Write the rest normally
return infrared_signal_save(
batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name));
}
bool infrared_remote_insert_signal(
InfraredRemote* remote,
const InfraredSignal* signal,
const char* name,
size_t index) {
if(index >= infrared_remote_get_signal_count(remote)) {
return infrared_remote_append_signal(remote, signal, name);
}
const InfraredBatchTarget insert_target = {
.signal_index = index,
.signal_name = name,
.signal = signal,
};
return infrared_remote_batch_start(
remote, infrared_remote_insert_signal_callback, &insert_target);
}
static bool infrared_remote_rename_signal_callback(
const InfraredBatch* batch,
const InfraredBatchTarget* target) {
const char* signal_name;
if(batch->signal_index == target->signal_index) {
// Rename the signal at requested index
signal_name = target->signal_name;
StringArray_set_at(batch->remote->signal_names, batch->signal_index, signal_name);
} else {
// Use the original name otherwise
signal_name = furi_string_get_cstr(batch->signal_name);
}
return infrared_signal_save(batch->signal, batch->ff_out, signal_name);
}
bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) {
furi_assert(index < infrared_remote_get_signal_count(remote));
const InfraredBatchTarget rename_target = {
.signal_index = index,
.signal_name = new_name,
.signal = NULL,
};
return infrared_remote_batch_start(
remote, infrared_remote_rename_signal_callback, &rename_target);
}
static bool infrared_remote_delete_signal_callback(
const InfraredBatch* batch,
const InfraredBatchTarget* target) {
if(batch->signal_index == target->signal_index) {
// Do not save the signal to be deleted, remove it from the signal name list instead
StringArray_remove_v(
batch->remote->signal_names, batch->signal_index, batch->signal_index + 1);
} else {
// Pass other signals through
return infrared_signal_save(
batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name));
}
return true;
}
bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) {
furi_assert(index < infrared_remote_get_signal_count(remote));
const InfraredBatchTarget delete_target = {
.signal_index = index,
.signal_name = NULL,
.signal = NULL,
};
return infrared_remote_batch_start(
remote, infrared_remote_delete_signal_callback, &delete_target);
}
bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) {
const size_t signal_count = infrared_remote_get_signal_count(remote);
furi_assert(index < signal_count);
furi_assert(new_index < signal_count);
if(index == new_index) return true;
InfraredSignal* signal = infrared_signal_alloc();
char* signal_name = strdup(infrared_remote_get_signal_name(remote, index));
bool success = false;
do {
if(!infrared_remote_load_signal(remote, signal, index)) break;
if(!infrared_remote_delete_signal(remote, index)) break;
if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break;
success = true;
} while(false);
free(signal_name);
infrared_signal_free(signal);
return success;
}
bool infrared_remote_create(InfraredRemote* remote, const char* path) {
FURI_LOG_I(TAG, "Creating new file: '%s'", path);
infrared_remote_reset(remote);
infrared_remote_set_path(remote, path);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
bool success = false;
do {
if(!flipper_format_file_open_always(ff, path)) break;
if(!flipper_format_write_header_cstr(ff, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION))
break;
success = true;
} while(false);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return success;
}
bool infrared_remote_load(InfraredRemote* remote, const char* path) {
FURI_LOG_I(TAG, "Loading file: '%s'", path);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
FuriString* tmp = furi_string_alloc();
bool success = false;
do {
if(!flipper_format_buffered_file_open_existing(ff, path)) break;
uint32_t version;
if(!flipper_format_read_header(ff, tmp, &version)) break;
if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION))
break;
infrared_remote_set_path(remote, path);
StringArray_reset(remote->signal_names);
while(infrared_signal_read_name(ff, tmp)) {
StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp));
}
success = true;
} while(false);
furi_string_free(tmp);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return success;
}
bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) {
const char* old_path = infrared_remote_get_path(remote);
Storage* storage = furi_record_open(RECORD_STORAGE);
const FS_Error status = storage_common_rename(storage, old_path, new_path);
furi_record_close(RECORD_STORAGE);
const bool success = (status == FSE_OK || status == FSE_EXIST);
if(success) {
infrared_remote_set_path(remote, new_path);
}
return success;
}
bool infrared_remote_remove(InfraredRemote* remote) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path));
infrared_remote_reset(remote);
const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote));
furi_record_close(RECORD_STORAGE);
return (status == FSE_OK || status == FSE_NOT_EXIST);
const bool success = (status == FSE_OK || status == FSE_NOT_EXIST);
if(success) {
infrared_remote_reset(remote);
}
return success;
}

View File

@ -1,30 +1,229 @@
/**
* @file infrared_remote.h
* @brief Infrared remote library.
*
* An infrared remote contains zero or more infrared signals which
* have a (possibly non-unique) name each.
*
* The current implementation does load only the names into the memory,
* while the signals themselves are loaded on-demand one by one. In theory,
* this should allow for quite large remotes with relatively bulky signals.
*/
#pragma once
#include <stdbool.h>
#include "infrared_remote_button.h"
#include "infrared_signal.h"
/**
* @brief InfraredRemote opaque type declaration.
*/
typedef struct InfraredRemote InfraredRemote;
/**
* @brief Create a new InfraredRemote instance.
*
* @returns pointer to the created instance.
*/
InfraredRemote* infrared_remote_alloc();
/**
* @brief Delete an InfraredRemote instance.
*
* @param[in,out] remote pointer to the instance to be deleted.
*/
void infrared_remote_free(InfraredRemote* remote);
/**
* @brief Reset an InfraredRemote instance.
*
* Resetting a remote clears its signal name list and
* the associated file path.
*
* @param[in,out] remote pointer to the instance to be deleted.
*/
void infrared_remote_reset(InfraredRemote* remote);
void infrared_remote_set_name(InfraredRemote* remote, const char* name);
const char* infrared_remote_get_name(InfraredRemote* remote);
/**
* @brief Get an InfraredRemote instance's name.
*
* The name is deduced from the file path.
*
* The return value remains valid unless one of the following functions is called:
* - infrared_remote_reset()
* - infrared_remote_load()
* - infrared_remote_create()
*
* @param[in] remote pointer to the instance to be queried.
* @returns pointer to a zero-terminated string containing the name.
*/
const char* infrared_remote_get_name(const InfraredRemote* remote);
void infrared_remote_set_path(InfraredRemote* remote, const char* path);
const char* infrared_remote_get_path(InfraredRemote* remote);
/**
* @brief Get an InfraredRemote instance's file path.
*
* Same return value validity considerations as infrared_remote_get_name().
*
* @param[in] remote pointer to the instance to be queried.
* @returns pointer to a zero-terminated string containing the path.
*/
const char* infrared_remote_get_path(const InfraredRemote* remote);
size_t infrared_remote_get_button_count(InfraredRemote* remote);
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index);
/**
* @brief Get the number of signals listed in an InfraredRemote instance.
*
* @param[in] remote pointer to the instance to be queried.
* @returns number of signals, zero or more
*/
size_t infrared_remote_get_signal_count(const InfraredRemote* remote);
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index);
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest);
/**
* @brief Get the name of a signal listed in an InfraredRemote instance.
*
* @param[in] remote pointer to the instance to be queried.
* @param[in] index index of the signal in question. Must be less than the total signal count.
*/
const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index);
bool infrared_remote_store(InfraredRemote* remote);
bool infrared_remote_load(InfraredRemote* remote, FuriString* path);
/**
* @brief Get the index of a signal listed in an InfraredRemote instance by its name.
*
* @param[in] remote pointer to the instance to be queried.
* @param[in] name pointer to a zero-terminated string containig the name of the signal in question.
* @param[out] index pointer to the variable to hold the signal index.
* @returns true if a signal with the given name was found, false otherwise.
*/
bool infrared_remote_get_signal_index(
const InfraredRemote* remote,
const char* name,
size_t* index);
/**
* @brief Load a signal listed in an InfraredRemote instance.
*
* As mentioned above, the signals are loaded on-demand. The user code must call this function
* each time it wants to interact with a new signal.
*
* @param[in] remote pointer to the instance to load from.
* @param[out] signal pointer to the signal to load into. Must be allocated.
* @param[in] index index of the signal to be loaded. Must be less than the total signal count.
* @return true if the signal was successfully loaded, false otherwise.
*/
bool infrared_remote_load_signal(
const InfraredRemote* remote,
InfraredSignal* signal,
size_t index);
/**
* @brief Append a signal to the file associated with an InfraredRemote instance.
*
* The file path must be somehow initialised first by calling either infrared_remote_load() or
* infrared_remote_create(). As the name suggests, the signal will be put in the end of the file.
*
* @param[in,out] remote pointer to the instance to append to.
* @param[in] signal pointer to the signal to be appended.
* @param[in] name pointer to a zero-terminated string containing the name of the signal.
* @returns true if the signal was successfully appended, false otherwise.
*/
bool infrared_remote_append_signal(
InfraredRemote* remote,
const InfraredSignal* signal,
const char* name);
/**
* @brief Insert a signal to the file associated with an InfraredRemote instance.
*
* Same behaviour as infrared_remote_append_signal(), but the user code can decide where to
* put the signal in the file.
*
* Index values equal to or greater than the total signal count will result in behaviour
* identical to infrared_remote_append_signal().
*
* @param[in,out] remote pointer to the instance to insert to.
* @param[in] signal pointer to the signal to be inserted.
* @param[in] name pointer to a zero-terminated string containing the name of the signal.
* @param[in] index the index under which the signal shall be inserted.
* @returns true if the signal was successfully inserted, false otherwise.
*/
bool infrared_remote_insert_signal(
InfraredRemote* remote,
const InfraredSignal* signal,
const char* name,
size_t index);
/**
* @brief Rename a signal in the file associated with an InfraredRemote instance.
*
* Only changes the signal's name, but neither its position nor contents.
*
* @param[in,out] remote pointer to the instance to be modified.
* @param[in] index index of the signal to be renamed. Must be less than the total signal count.
* @param[in] new_name pointer to a zero-terminated string containig the signal's new name.
* @returns true if the signal was successfully renamed, false otherwise.
*/
bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name);
/**
* @brief Change a signal's position in the file associated with an InfraredRemote instance.
*
* Only changes the signal's position (index), but neither its name nor contents.
*
* @param[in,out] remote pointer to the instance to be modified.
* @param[in] index index of the signal to be moved. Must be less than the total signal count.
* @param[in] new_index index of the signal to be moved. Must be less than the total signal count.
*/
bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index);
/**
* @brief Delete a signal in the file associated with an InfraredRemote instance.
*
* @param[in,out] remote pointer to the instance to be modified.
* @param[in] index index of the signal to be deleted. Must be less than the total signal count.
* @returns true if the signal was successfully deleted, false otherwise.
*/
bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index);
/**
* @brief Create a new file and associate it with an InfraredRemote instance.
*
* The instance will be reset and given a new empty file with just the header.
*
* @param[in,out] remote pointer to the instance to be assigned with a new file.
* @param[in] path pointer to a zero-terminated string containing the full file path.
* @returns true if the file was successfully created, false otherwise.
*/
bool infrared_remote_create(InfraredRemote* remote, const char* path);
/**
* @brief Associate an InfraredRemote instance with a file and load the signal names from it.
*
* The instance will be reset and fill its signal name list from the given file.
* The file must already exist and be valid.
*
* @param[in,out] remote pointer to the instance to be assigned with an existing file.
* @param[in] path pointer to a zero-terminated string containing the full file path.
* @returns true if the file was successfully loaded, false otherwise.
*/
bool infrared_remote_load(InfraredRemote* remote, const char* path);
/**
* @brief Rename the file associated with an InfraredRemote instance.
*
* Only renames the file, no signals are added, moved or deleted.
*
* @param[in,out] remote pointer to the instance to be modified.
* @param[in] new_path pointer to a zero-terminated string containing the new full file path.
* @returns true if the file was successfully renamed, false otherwise.
*/
bool infrared_remote_rename(InfraredRemote* remote, const char* new_path);
/**
* @brief Remove the file associated with an InfraredRemote instance.
*
* This operation is irreversible and fully deletes the remote file
* from the underlying filesystem.
* After calling this function, the instance becomes invalid until
* infrared_remote_create() or infrared_remote_load() are successfully executed.
*
* @param[in,out] remote pointer to the instance to be modified.
* @returns true if the file was successfully removed, false otherwise.
*/
bool infrared_remote_remove(InfraredRemote* remote);

View File

@ -1,37 +0,0 @@
#include "infrared_remote_button.h"
#include <stdlib.h>
struct InfraredRemoteButton {
FuriString* name;
InfraredSignal* signal;
};
InfraredRemoteButton* infrared_remote_button_alloc() {
InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton));
button->name = furi_string_alloc();
button->signal = infrared_signal_alloc();
return button;
}
void infrared_remote_button_free(InfraredRemoteButton* button) {
furi_string_free(button->name);
infrared_signal_free(button->signal);
free(button);
}
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) {
furi_string_set(button->name, name);
}
const char* infrared_remote_button_get_name(InfraredRemoteButton* button) {
return furi_string_get_cstr(button->name);
}
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
infrared_signal_set_signal(button->signal, signal);
}
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) {
return button->signal;
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "infrared_signal.h"
typedef struct InfraredRemoteButton InfraredRemoteButton;
InfraredRemoteButton* infrared_remote_button_alloc();
void infrared_remote_button_free(InfraredRemoteButton* button);
void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);

View File

@ -8,6 +8,8 @@
#define TAG "InfraredSignal"
#define INFRARED_SIGNAL_NAME_KEY "name"
struct InfraredSignal {
bool is_raw;
union {
@ -24,7 +26,7 @@ static void infrared_signal_clear_timings(InfraredSignal* signal) {
}
}
static bool infrared_signal_is_message_valid(InfraredMessage* message) {
static bool infrared_signal_is_message_valid(const InfraredMessage* message) {
if(!infrared_is_protocol_valid(message->protocol)) {
FURI_LOG_E(TAG, "Unknown protocol");
return false;
@ -57,7 +59,7 @@ static bool infrared_signal_is_message_valid(InfraredMessage* message) {
return true;
}
static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) {
static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) {
FURI_LOG_E(
TAG,
@ -83,7 +85,8 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) {
return true;
}
static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
static inline bool
infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) {
const char* protocol_name = infrared_get_protocol_name(message->protocol);
return flipper_format_write_string_cstr(ff, "type", "parsed") &&
flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
@ -91,7 +94,7 @@ static inline bool infrared_signal_save_message(InfraredMessage* message, Flippe
flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
}
static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) {
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
return flipper_format_write_string_cstr(ff, "type", "raw") &&
flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
@ -180,11 +183,11 @@ void infrared_signal_free(InfraredSignal* signal) {
free(signal);
}
bool infrared_signal_is_raw(InfraredSignal* signal) {
bool infrared_signal_is_raw(const InfraredSignal* signal) {
return signal->is_raw;
}
bool infrared_signal_is_valid(InfraredSignal* signal) {
bool infrared_signal_is_valid(const InfraredSignal* signal) {
return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) :
infrared_signal_is_message_valid(&signal->payload.message);
}
@ -218,7 +221,7 @@ void infrared_signal_set_raw_signal(
memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t));
}
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) {
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) {
furi_assert(signal->is_raw);
return &signal->payload.raw;
}
@ -230,14 +233,14 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage*
signal->payload.message = *message;
}
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) {
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) {
furi_assert(!signal->is_raw);
return &signal->payload.message;
}
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) {
if(!flipper_format_write_comment_cstr(ff, "") ||
!flipper_format_write_string_cstr(ff, "name", name)) {
!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) {
return false;
} else if(signal->is_raw) {
return infrared_signal_save_raw(&signal->payload.raw, ff);
@ -247,33 +250,30 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char*
}
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) {
FuriString* tmp = furi_string_alloc();
bool success = false;
do {
if(!flipper_format_read_string(ff, "name", tmp)) break;
furi_string_set(name, tmp);
if(!infrared_signal_read_name(ff, name)) break;
if(!infrared_signal_read_body(signal, ff)) break;
success = true;
} while(0);
furi_string_free(tmp);
success = true; //-V779
} while(false);
return success;
}
bool infrared_signal_search_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const FuriString* name) {
bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) {
return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name);
}
bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
bool success = false;
FuriString* tmp = furi_string_alloc();
do {
bool is_name_found = false;
while(flipper_format_read_string(ff, "name", tmp)) {
is_name_found = furi_string_equal(name, tmp);
if(is_name_found) break;
while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560
is_name_found = furi_string_equal(tmp, name);
}
if(!is_name_found) break; //-V547
if(!infrared_signal_read_body(signal, ff)) break; //-V779
@ -284,9 +284,9 @@ bool infrared_signal_search_and_read(
return success;
}
void infrared_signal_transmit(InfraredSignal* signal) {
void infrared_signal_transmit(const InfraredSignal* signal) {
if(signal->is_raw) {
InfraredRawSignal* raw_signal = &signal->payload.raw;
const InfraredRawSignal* raw_signal = &signal->payload.raw;
infrared_send_raw_ext(
raw_signal->timings,
raw_signal->timings_size,
@ -294,7 +294,7 @@ void infrared_signal_transmit(InfraredSignal* signal) {
raw_signal->frequency,
raw_signal->duty_cycle);
} else {
InfraredMessage* message = &signal->payload.message;
const InfraredMessage* message = &signal->payload.message;
infrared_send(message, 1);
}
}

View File

@ -1,45 +1,186 @@
/**
* @file infrared_signal.h
* @brief Infrared signal library.
*
* Infrared signals may be of two types:
* - known to the infrared signal decoder, or *parsed* signals
* - the rest, or *raw* signals, which are treated merely as a set of timings.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <infrared.h>
#include <flipper_format/flipper_format.h>
#include <infrared/encoder_decoder/infrared.h>
/**
* @brief InfraredSignal opaque type declaration.
*/
typedef struct InfraredSignal InfraredSignal;
/**
* @brief Raw signal type definition.
*
* Measurement units used:
* - time: microseconds (uS)
* - frequency: Hertz (Hz)
* - duty_cycle: no units, fraction between 0 and 1.
*/
typedef struct {
size_t timings_size;
uint32_t* timings;
uint32_t frequency;
float duty_cycle;
size_t timings_size; /**< Number of elements in the timings array. */
uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
uint32_t frequency; /**< Carrier frequency of the signal. */
float duty_cycle; /**< Duty cycle of the signal. */
} InfraredRawSignal;
/**
* @brief Create a new InfraredSignal instance.
*
* @returns pointer to the instance created.
*/
InfraredSignal* infrared_signal_alloc();
/**
* @brief Delete an InfraredSignal instance.
*
* @param[in,out] signal pointer to the instance to be deleted.
*/
void infrared_signal_free(InfraredSignal* signal);
bool infrared_signal_is_raw(InfraredSignal* signal);
bool infrared_signal_is_valid(InfraredSignal* signal);
/**
* @brief Test whether an InfraredSignal instance holds a raw signal.
*
* @param[in] signal pointer to the instance to be tested.
* @returns true if the instance holds a raw signal, false otherwise.
*/
bool infrared_signal_is_raw(const InfraredSignal* signal);
/**
* @brief Test whether an InfraredSignal instance holds any signal.
*
* @param[in] signal pointer to the instance to be tested.
* @returns true if the instance holds raw signal, false otherwise.
*/
bool infrared_signal_is_valid(const InfraredSignal* signal);
/**
* @brief Set an InfraredInstance to hold the signal from another one.
*
* Any instance's previous contents will be automatically deleted before
* copying the source instance's contents.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] other pointer to the source instance.
*/
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
/**
* @brief Set an InfraredInstance to hold a raw signal.
*
* Any instance's previous contents will be automatically deleted before
* copying the raw signal.
*
* After this call, infrared_signal_is_raw() will return true.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] timings pointer to an array containing the raw signal timings.
* @param[in] timings_size number of elements in the timings array.
* @param[in] frequency signal carrier frequency, in Hertz.
* @param[in] duty_cycle signal duty cycle, fraction between 0 and 1.
*/
void infrared_signal_set_raw_signal(
InfraredSignal* signal,
const uint32_t* timings,
size_t timings_size,
uint32_t frequency,
float duty_cycle);
InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal);
/**
* @brief Get the raw signal held by an InfraredSignal instance.
*
* @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur.
*
* @param[in] signal pointer to the instance to be queried.
* @returns pointer to the raw signal structure held by the instance.
*/
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal);
/**
* @brief Set an InfraredInstance to hold a parsed signal.
*
* Any instance's previous contents will be automatically deleted before
* copying the raw signal.
*
* After this call, infrared_signal_is_raw() will return false.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] message pointer to the message containing the parsed signal.
*/
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
InfraredMessage* infrared_signal_get_message(InfraredSignal* signal);
bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
/**
* @brief Get the parsed signal held by an InfraredSignal instance.
*
* @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur.
*
* @param[in] signal pointer to the instance to be queried.
* @returns pointer to the parsed signal structure held by the instance.
*/
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal);
/**
* @brief Read a signal from a FlipperFormat file into an InfraredSignal instance.
*
* The file must be allocated and open prior to this call. The seek position determines
* which signal will be read (if there is more than one in the file). Calling this function
* repeatedly will result in all signals in the file to be read until no more are left.
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
* @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read).
*/
bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name);
bool infrared_signal_search_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const FuriString* name);
void infrared_signal_transmit(InfraredSignal* signal);
/**
* @brief Read a signal name from a FlipperFormat file.
*
* Same behaviour as infrared_signal_read(), but only the name is read.
*
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
* @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read).
*/
bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
/**
* @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance.
*
* This function will look for a signal with the given name and if found, attempt to read it.
* Same considerations apply as to infrared_signal_read().
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[in] name pointer to a zero-terminated string containing the requested signal name.
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
*/
bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name);
/**
* @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file.
*
* The file must be allocated and open prior to this call. Additionally, an appropriate header
* must be already written into the file.
*
* @param[in] signal pointer to the instance holding the signal to be saved.
* @param[in,out] ff pointer to the FlipperFormat file instance to write to.
* @param[in] name pointer to a zero-terminated string contating the name of the signal.
*/
bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name);
/**
* @brief Transmit a signal contained in an InfraredSignal instance.
*
* The transmission happens once per call using the built-in hardware (via HAL calls).
*
* @param[in] signal pointer to the instance holding the signal to be transmitted.
*/
void infrared_signal_transmit(const InfraredSignal* signal);

View File

@ -1,20 +1,21 @@
#include "../../infrared_i.h"
#include "../../infrared_app_i.h"
#include <dolphin/dolphin.h>
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
Infrared* infrared = context;
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) {
Infrared* infrared = context;
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) {
static void
infrared_scene_universal_common_show_popup(InfraredApp* infrared, uint32_t record_count) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
infrared_progress_view_set_progress_total(progress, record_count);
@ -24,7 +25,7 @@ static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint3
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}
static void infrared_scene_universal_common_hide_popup(Infrared* infrared) {
static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress));
@ -32,12 +33,12 @@ static void infrared_scene_universal_common_hide_popup(Infrared* infrared) {
}
void infrared_scene_universal_common_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
}
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
InfraredBruteForce* brute_force = infrared->brute_force;
bool consumed = false;
@ -84,7 +85,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
}
void infrared_scene_universal_common_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel));
infrared_brute_force_reset(infrared->brute_force);

View File

@ -1,12 +1,12 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_ask_back_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
if(infrared->app_state.is_learning_new_remote) {
@ -28,7 +28,7 @@ void infrared_scene_ask_back_on_enter(void* context) {
}
bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -54,6 +54,6 @@ bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_ask_back_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
dialog_ex_reset(infrared->dialog_ex);
}

View File

@ -1,12 +1,12 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_ask_retry_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop);
@ -23,7 +23,7 @@ void infrared_scene_ask_retry_on_enter(void* context) {
}
bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -43,6 +43,6 @@ bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_ask_retry_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
dialog_ex_reset(infrared->dialog_ex);
}

View File

@ -1,7 +1,7 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_debug_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredWorker* worker = infrared->worker;
infrared_worker_rx_set_received_signal_callback(
@ -14,16 +14,16 @@ void infrared_scene_debug_on_enter(void* context) {
}
bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeSignalReceived) {
InfraredDebugView* debug_view = infrared->debug_view;
InfraredSignal* signal = infrared->received_signal;
InfraredSignal* signal = infrared->current_signal;
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size);
printf("RAW, %zu samples:\r\n", raw->timings_size);
@ -33,7 +33,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
printf("\r\n");
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
const InfraredMessage* message = infrared_signal_get_message(signal);
infrared_debug_view_set_text(
debug_view,
"%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
@ -61,7 +61,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_debug_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredWorker* worker = infrared->worker;
infrared_worker_rx_stop(worker);
infrared_worker_rx_enable_blink_on_receiving(worker, false);

View File

@ -1,4 +1,4 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
typedef enum {
SubmenuIndexAddButton,
@ -10,12 +10,12 @@ typedef enum {
} SubmenuIndex;
static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_edit_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Submenu* submenu = infrared->submenu;
SceneManager* scene_manager = infrared->scene_manager;
@ -64,7 +64,7 @@ void infrared_scene_edit_on_enter(void* context) {
}
bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -106,6 +106,6 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_edit_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
submenu_reset(infrared->submenu);
}

View File

@ -1,12 +1,12 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_edit_button_select_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Submenu* submenu = infrared->submenu;
InfraredRemote* remote = infrared->remote;
InfraredAppState* app_state = &infrared->app_state;
@ -16,16 +16,16 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
"Delete Button:";
submenu_set_header(submenu, header);
const size_t button_count = infrared_remote_get_button_count(remote);
const size_t button_count = infrared_remote_get_signal_count(remote);
for(size_t i = 0; i < button_count; ++i) {
InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
submenu_add_item(
submenu,
infrared_remote_button_get_name(button),
infrared_remote_get_signal_name(remote, i),
i,
infrared_scene_edit_button_select_submenu_callback,
context);
}
if(button_count && app_state->current_button_index != InfraredButtonIndexNone) {
submenu_set_selected_item(submenu, app_state->current_button_index);
app_state->current_button_index = InfraredButtonIndexNone;
@ -35,7 +35,7 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
}
bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredAppState* app_state = &infrared->app_state;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -57,6 +57,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent
}
void infrared_scene_edit_button_select_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
submenu_reset(infrared->submenu);
}

View File

@ -1,42 +1,49 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void
infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_edit_delete_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote;
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
int32_t current_button_index = infrared->app_state.current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop);
InfraredRemoteButton* current_button =
infrared_remote_get_button(remote, current_button_index);
InfraredSignal* signal = infrared_remote_button_get_signal(current_button);
if(infrared_signal_is_raw(signal)) {
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
const int32_t current_button_index = infrared->app_state.current_button_index;
furi_check(current_button_index != InfraredButtonIndexNone);
if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) {
infrared_show_error_message(
infrared,
"Failed to load\n\"%s\"",
infrared_remote_get_signal_name(remote, current_button_index));
scene_manager_previous_scene(infrared->scene_manager);
return;
}
if(infrared_signal_is_raw(infrared->current_signal)) {
const InfraredRawSignal* raw =
infrared_signal_get_raw_signal(infrared->current_signal);
infrared_text_store_set(
infrared,
0,
"%s\nRAW\n%ld samples",
infrared_remote_button_get_name(current_button),
"%s\nRAW\n%zu samples",
infrared_remote_get_signal_name(remote, current_button_index),
raw->timings_size);
} else {
const InfraredMessage* message = infrared_signal_get_message(signal);
const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal);
infrared_text_store_set(
infrared,
0,
"%s\n%s\nA=0x%0*lX C=0x%0*lX",
infrared_remote_button_get_name(current_button),
infrared_remote_get_signal_name(remote, current_button_index),
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
@ -49,9 +56,9 @@ void infrared_scene_edit_delete_on_enter(void* context) {
infrared_text_store_set(
infrared,
0,
"%s\n with %lu buttons",
"%s\n with %zu buttons",
infrared_remote_get_name(remote),
infrared_remote_get_button_count(remote));
infrared_remote_get_signal_count(remote));
} else {
furi_assert(0);
}
@ -63,11 +70,14 @@ void infrared_scene_edit_delete_on_enter(void* context) {
dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
}
bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -83,18 +93,24 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = infrared_remote_delete_button(remote, app_state->current_button_index);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_delete_signal(remote, app_state->current_button_index);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(remote);
app_state->current_button_index = InfraredButtonIndexNone;
} else {
furi_assert(0);
furi_crash(NULL);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
} else {
infrared_show_error_message(
infrared,
"Failed to\ndelete %s",
edit_target == InfraredEditTargetButton ? "button" : "file");
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
@ -107,6 +123,6 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
}
void infrared_scene_edit_delete_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
InfraredApp* infrared = context;
view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
}

View File

@ -1,7 +1,7 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_edit_delete_done_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
@ -16,7 +16,7 @@ void infrared_scene_edit_delete_done_on_enter(void* context) {
}
bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -43,6 +43,6 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e
}
void infrared_scene_edit_delete_done_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
UNUSED(infrared);
}

View File

@ -1,44 +1,69 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) {
InfraredRemote* remote = context;
furi_assert(remote);
infrared_remote_move_button(remote, index_old, index_new);
}
static void infrared_scene_edit_move_button_callback(
uint32_t index_old,
uint32_t index_new,
void* context) {
InfraredApp* infrared = context;
furi_assert(infrared);
static const char* infrared_scene_get_btn_name(uint32_t index, void* context) {
InfraredRemote* remote = context;
furi_assert(remote);
InfraredRemoteButton* button = infrared_remote_get_button(remote, index);
return (infrared_remote_button_get_name(button));
infrared->app_state.prev_button_index = index_old;
infrared->app_state.current_button_index = index_new;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeButtonSelected);
}
void infrared_scene_edit_move_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button);
for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) {
infrared_move_view_add_item(
infrared->move_view, infrared_remote_get_signal_name(remote, i));
}
uint32_t btn_count = infrared_remote_get_button_count(remote);
infrared_move_view_list_init(
infrared->move_view, btn_count, infrared_scene_get_btn_name, remote);
infrared_move_view_list_update(infrared->move_view);
infrared_move_view_set_callback(
infrared->move_view, infrared_scene_edit_move_button_callback, infrared);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
}
bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
UNUSED(event);
UNUSED(infrared);
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeButtonSelected) {
infrared_show_loading_popup(infrared, true);
const bool button_moved = infrared_remote_move_signal(
infrared->remote,
infrared->app_state.prev_button_index,
infrared->app_state.current_button_index);
infrared_show_loading_popup(infrared, false);
if(!button_moved) {
infrared_show_error_message(
infrared,
"Failed to move\n\"%s\"",
infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index));
scene_manager_search_and_switch_to_previous_scene(
infrared->scene_manager, InfraredSceneRemoteList);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_move_on_exit(void* context) {
Infrared* infrared = context;
InfraredRemote* remote = infrared->remote;
infrared_remote_store(remote);
InfraredApp* infrared = context;
view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
infrared_move_view_reset(infrared->move_view);
}

View File

@ -1,10 +1,10 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include <string.h>
#include <toolbox/path.h>
void infrared_scene_edit_rename_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
TextInput* text_input = infrared->text_input;
size_t enter_name_length = 0;
@ -14,14 +14,12 @@ void infrared_scene_edit_rename_on_enter(void* context) {
text_input_set_header_text(text_input, "Name the button");
const int32_t current_button_index = infrared->app_state.current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
furi_check(current_button_index != InfraredButtonIndexNone);
InfraredRemoteButton* current_button =
infrared_remote_get_button(remote, current_button_index);
enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH;
strncpy(
infrared->text_store[0],
infrared_remote_button_get_name(current_button),
infrared_remote_get_signal_name(remote, current_button_index),
enter_name_length);
} else if(edit_target == InfraredEditTargetRemote) {
@ -44,7 +42,7 @@ void infrared_scene_edit_rename_on_enter(void* context) {
furi_string_free(folder_path);
} else {
furi_assert(0);
furi_crash(NULL);
}
text_input_set_result_callback(
@ -55,11 +53,14 @@ void infrared_scene_edit_rename_on_enter(void* context) {
enter_name_length,
false);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
}
bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
SceneManager* scene_manager = infrared->scene_manager;
InfraredAppState* app_state = &infrared->app_state;
@ -72,18 +73,24 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event)
if(edit_target == InfraredEditTargetButton) {
const int32_t current_button_index = app_state->current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
success = infrared_remote_rename_button(
remote, infrared->text_store[0], current_button_index);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_rename_signal(
remote, current_button_index, infrared->text_store[0]);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
} else {
furi_assert(0);
furi_crash(NULL);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
} else {
infrared_show_error_message(
infrared,
"Failed to\nrename %s",
edit_target == InfraredEditTargetButton ? "button" : "file");
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemoteList);
}
@ -95,9 +102,11 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event)
}
void infrared_scene_edit_rename_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
TextInput* text_input = infrared->text_input;
view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input));
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);

View File

@ -1,7 +1,7 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_edit_rename_done_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
@ -16,7 +16,7 @@ void infrared_scene_edit_rename_done_on_enter(void* context) {
}
bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
@ -33,6 +33,6 @@ bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent e
}
void infrared_scene_edit_rename_done_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
UNUSED(infrared);
}

View File

@ -1,7 +1,7 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_error_databases_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 5, 11, &I_SDQuestion_35x43);
@ -16,7 +16,7 @@ void infrared_scene_error_databases_on_enter(void* context) {
}
bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
@ -31,7 +31,7 @@ bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent ev
}
void infrared_scene_error_databases_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
popup_reset(infrared->popup);
infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOff);
}

View File

@ -1,8 +1,8 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include <dolphin/dolphin.h>
void infrared_scene_learn_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
InfraredWorker* worker = infrared->worker;
@ -21,7 +21,7 @@ void infrared_scene_learn_on_enter(void* context) {
}
bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
@ -37,7 +37,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_learn_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL);
infrared_worker_rx_stop(infrared->worker);

View File

@ -1,7 +1,7 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_learn_done_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
@ -21,7 +21,7 @@ void infrared_scene_learn_done_on_enter(void* context) {
}
bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
@ -38,7 +38,7 @@ bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event)
}
void infrared_scene_learn_done_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
infrared->app_state.is_learning_new_remote = false;
popup_set_header(infrared->popup, NULL, 0, 0, AlignLeft, AlignTop);
}

View File

@ -1,16 +1,16 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include <dolphin/dolphin.h>
void infrared_scene_learn_enter_name_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
TextInput* text_input = infrared->text_input;
InfraredSignal* signal = infrared->received_signal;
InfraredSignal* signal = infrared->current_signal;
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(infrared, 0, "RAW_%d", raw->timings_size);
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(infrared, 0, "RAW_%zu", raw->timings_size);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
const InfraredMessage* message = infrared_signal_get_message(signal);
infrared_text_store_set(
infrared,
0,
@ -28,31 +28,32 @@ void infrared_scene_learn_enter_name_on_enter(void* context) {
infrared->text_store[0],
INFRARED_MAX_BUTTON_NAME_LENGTH,
true);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
}
bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredSignal* signal = infrared->received_signal;
InfraredApp* infrared = context;
InfraredSignal* signal = infrared->current_signal;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTextEditDone) {
bool success = false;
if(infrared->app_state.is_learning_new_remote) {
success =
infrared_add_remote_with_button(infrared, infrared->text_store[0], signal);
} else {
success =
infrared_remote_add_button(infrared->remote, infrared->text_store[0], signal);
}
const char* signal_name = infrared->text_store[0];
const bool success =
infrared->app_state.is_learning_new_remote ?
infrared_add_remote_with_button(infrared, signal_name, signal) :
infrared_remote_append_signal(infrared->remote, signal, signal_name);
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneLearnDone);
dolphin_deed(DolphinDeedIrSave);
} else {
dialog_message_show_storage_error(infrared->dialogs, "Failed to save file");
infrared_show_error_message(
infrared,
"Failed to\n%s",
infrared->app_state.is_learning_new_remote ? "create file" : "add signal");
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
@ -65,6 +66,6 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e
}
void infrared_scene_learn_enter_name_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
UNUSED(infrared);
}

View File

@ -1,26 +1,26 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
static void
infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
void infrared_scene_learn_success_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredSignal* signal = infrared->received_signal;
InfraredSignal* signal = infrared->current_signal;
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
if(infrared_signal_is_raw(signal)) {
InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
infrared_text_store_set(infrared, 0, "%d samples", raw->timings_size);
infrared_text_store_set(infrared, 0, "%zu samples", raw->timings_size);
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 75, 23, AlignLeft, AlignTop);
} else {
InfraredMessage* message = infrared_signal_get_message(signal);
const InfraredMessage* message = infrared_signal_get_message(signal);
uint8_t addr_digits =
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4);
uint8_t cmd_digits =
@ -56,7 +56,7 @@ void infrared_scene_learn_success_on_enter(void* context) {
}
bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
const bool is_transmitter_idle = !infrared->app_state.is_transmitting;
bool consumed = false;
@ -84,7 +84,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even
consumed = true;
} else if(event.event == DialogExPressCenter) {
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
infrared_tx_start_received(infrared);
infrared_tx_start(infrared);
consumed = true;
} else if(event.event == DialogExReleaseCenter) {
infrared_tx_stop(infrared);
@ -96,7 +96,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even
}
void infrared_scene_learn_success_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
dialog_ex_reset(infrared->dialog_ex);
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
}

View File

@ -1,4 +1,4 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
typedef enum {
ButtonIndexPlus = -2,
@ -8,7 +8,7 @@ typedef enum {
static void
infrared_scene_remote_button_menu_callback(void* context, int32_t index, InputType type) {
Infrared* infrared = context;
InfraredApp* infrared = context;
uint16_t custom_type;
if(type == InputTypePress) {
@ -26,17 +26,15 @@ static void
}
void infrared_scene_remote_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
ButtonMenu* button_menu = infrared->button_menu;
SceneManager* scene_manager = infrared->scene_manager;
size_t button_count = infrared_remote_get_button_count(remote);
for(size_t i = 0; i < button_count; ++i) {
InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) {
button_menu_add_item(
button_menu,
infrared_remote_button_get_name(button),
infrared_remote_get_signal_name(remote, i),
i,
infrared_scene_remote_button_menu_callback,
ButtonMenuItemTypeCommon,
@ -68,7 +66,7 @@ void infrared_scene_remote_on_enter(void* context) {
}
bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
const bool is_transmitter_idle = !infrared->app_state.is_transmitting;
bool consumed = false;
@ -116,6 +114,6 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_remote_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
button_menu_reset(infrared->button_menu);
}

View File

@ -1,31 +1,34 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
void infrared_scene_remote_list_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px);
browser_options.base_path = INFRARED_APP_FOLDER;
bool success = dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options);
if(success) {
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack);
while(dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) {
const char* file_path = furi_string_get_cstr(infrared->file_path);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_load(infrared->remote, infrared->file_path);
const bool remote_loaded = infrared_remote_load(infrared->remote, file_path);
infrared_show_loading_popup(infrared, false);
if(remote_loaded) {
scene_manager_next_scene(scene_manager, InfraredSceneRemote);
return;
} else {
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path);
}
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneRemote);
} else {
scene_manager_previous_scene(scene_manager);
}
scene_manager_previous_scene(scene_manager);
}
bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) {

View File

@ -1,4 +1,4 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include <gui/canvas.h>
typedef enum {
@ -8,7 +8,7 @@ typedef enum {
} InfraredRpcState;
void infrared_scene_rpc_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom);
@ -27,7 +27,7 @@ void infrared_scene_rpc_on_enter(void* context) {
}
bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
@ -43,7 +43,8 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
if(arg && (state == InfraredRpcStateIdle)) {
furi_string_set(infrared->file_path, arg);
result = infrared_remote_load(infrared->remote, infrared->file_path);
result = infrared_remote_load(
infrared->remote, furi_string_get_cstr(infrared->file_path));
if(result) {
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
@ -61,7 +62,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
if(arg && (state == InfraredRpcStateLoaded)) {
size_t button_index = 0;
if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) {
if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) {
infrared_tx_start_button_index(infrared, button_index);
result = true;
scene_manager_set_scene_state(
@ -91,7 +92,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_rpc_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
if(scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc) ==
InfraredRpcStateSending) {
infrared_tx_stop(infrared);

View File

@ -1,4 +1,4 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
enum SubmenuIndex {
SubmenuIndexUniversalRemotes,
@ -8,12 +8,12 @@ enum SubmenuIndex {
};
static void infrared_scene_start_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_start_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Submenu* submenu = infrared->submenu;
SceneManager* scene_manager = infrared->scene_manager;
@ -50,7 +50,7 @@ void infrared_scene_start_on_enter(void* context) {
}
bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -79,6 +79,6 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_start_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
submenu_reset(infrared->submenu);
}

View File

@ -1,4 +1,4 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
typedef enum {
SubmenuIndexUniversalTV,
@ -8,12 +8,12 @@ typedef enum {
} SubmenuIndex;
static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
Infrared* infrared = context;
InfraredApp* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
}
void infrared_scene_universal_on_enter(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
Submenu* submenu = infrared->submenu;
submenu_add_item(
@ -47,7 +47,7 @@ void infrared_scene_universal_on_enter(void* context) {
}
bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
@ -72,6 +72,6 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
}
void infrared_scene_universal_on_exit(void* context) {
Infrared* infrared = context;
InfraredApp* infrared = context;
submenu_reset(infrared->submenu);
}

View File

@ -1,11 +1,11 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_ac_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;

View File

@ -1,11 +1,11 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_audio_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;

View File

@ -1,11 +1,11 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_projector_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;

View File

@ -1,11 +1,11 @@
#include "../infrared_i.h"
#include "../infrared_app_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_tv_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;

View File

@ -1,10 +1,11 @@
#include "infrared_move_view.h"
#include <m-array.h>
#include <gui/canvas.h>
#include <gui/elements.h>
#include <stdlib.h>
#include <string.h>
#include <toolbox/m_cstr_dup.h>
#define LIST_ITEMS 4U
#define LIST_LINE_H 13U
@ -13,42 +14,41 @@
struct InfraredMoveView {
View* view;
InfraredMoveCallback move_cb;
void* cb_context;
InfraredMoveCallback callback;
void* callback_context;
};
typedef struct {
const char** btn_names;
uint32_t btn_number;
int32_t list_offset;
int32_t item_idx;
bool is_moving;
ARRAY_DEF(InfraredMoveViewItemArray, const char*, M_CSTR_DUP_OPLIST); //-V575
InfraredMoveGetItemCallback get_item_cb;
typedef struct {
InfraredMoveViewItemArray_t labels;
int32_t list_offset;
int32_t current_idx;
int32_t start_idx;
bool is_moving;
} InfraredMoveViewModel;
static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) {
InfraredMoveViewModel* model = _model;
UNUSED(model);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(
canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "Select a button to move");
bool show_scrollbar = model->btn_number > LIST_ITEMS;
const size_t btn_number = InfraredMoveViewItemArray_size(model->labels);
const bool show_scrollbar = btn_number > LIST_ITEMS;
canvas_set_font(canvas, FontSecondary);
for(uint32_t i = 0; i < MIN(model->btn_number, LIST_ITEMS); i++) {
int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->btn_number, 0u);
uint8_t x_offset = (model->is_moving && model->item_idx == idx) ? MOVE_X_OFFSET : 0;
for(uint32_t i = 0; i < MIN(btn_number, LIST_ITEMS); i++) {
int32_t idx = CLAMP((uint32_t)(i + model->list_offset), btn_number, 0U);
uint8_t x_offset = (model->is_moving && model->current_idx == idx) ? MOVE_X_OFFSET : 0;
uint8_t y_offset = HEADER_H + i * LIST_LINE_H;
uint8_t box_end_x = canvas_width(canvas) - (show_scrollbar ? 6 : 1);
canvas_set_color(canvas, ColorBlack);
if(model->item_idx == idx) {
if(model->current_idx == idx) {
canvas_draw_box(canvas, x_offset, y_offset, box_end_x - x_offset, LIST_LINE_H);
canvas_set_color(canvas, ColorWhite);
@ -60,7 +60,12 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) {
canvas_draw_dot(canvas, box_end_x - 1, y_offset + LIST_LINE_H - 1);
}
canvas_draw_str_aligned(
canvas, x_offset + 3, y_offset + 3, AlignLeft, AlignTop, model->btn_names[idx]);
canvas,
x_offset + 3,
y_offset + 3,
AlignLeft,
AlignTop,
*InfraredMoveViewItemArray_cget(model->labels, idx));
}
if(show_scrollbar) {
@ -69,22 +74,22 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) {
canvas_width(canvas),
HEADER_H,
canvas_height(canvas) - HEADER_H,
model->item_idx,
model->btn_number);
model->current_idx,
btn_number);
}
}
static void update_list_offset(InfraredMoveViewModel* model) {
int32_t bounds = model->btn_number > (LIST_ITEMS - 1) ? 2 : model->btn_number;
const size_t btn_number = InfraredMoveViewItemArray_size(model->labels);
const int32_t bounds = btn_number > (LIST_ITEMS - 1) ? 2 : btn_number;
if((model->btn_number > (LIST_ITEMS - 1)) &&
(model->item_idx >= ((int32_t)model->btn_number - 1))) {
model->list_offset = model->item_idx - (LIST_ITEMS - 1);
} else if(model->list_offset < model->item_idx - bounds) {
model->list_offset = CLAMP(
model->item_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)model->btn_number - bounds, 0);
} else if(model->list_offset > model->item_idx - bounds) {
model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->btn_number - bounds, 0);
if((btn_number > (LIST_ITEMS - 1)) && (model->current_idx >= ((int32_t)btn_number - 1))) {
model->list_offset = model->current_idx - (LIST_ITEMS - 1);
} else if(model->list_offset < model->current_idx - bounds) {
model->list_offset =
CLAMP(model->current_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)btn_number - bounds, 0);
} else if(model->list_offset > model->current_idx - bounds) {
model->list_offset = CLAMP(model->current_idx - 1, (int32_t)btn_number - bounds, 0);
}
}
@ -95,117 +100,130 @@ static bool infrared_move_view_input_callback(InputEvent* event, void* context)
if(((event->type == InputTypeShort || event->type == InputTypeRepeat)) &&
((event->key == InputKeyUp) || (event->key == InputKeyDown))) {
bool is_moving = false;
uint32_t index_old = 0;
uint32_t index_new = 0;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
is_moving = model->is_moving;
index_old = model->item_idx;
const size_t btn_number = InfraredMoveViewItemArray_size(model->labels);
const int32_t item_idx_prev = model->current_idx;
if(event->key == InputKeyUp) {
if(model->item_idx <= 0) {
model->item_idx = model->btn_number;
if(model->current_idx <= 0) {
model->current_idx = btn_number;
}
model->item_idx--;
model->current_idx--;
} else if(event->key == InputKeyDown) {
model->item_idx++;
if(model->item_idx >= (int32_t)(model->btn_number)) {
model->item_idx = 0;
model->current_idx++;
if(model->current_idx >= (int32_t)(btn_number)) {
model->current_idx = 0;
}
}
index_new = model->item_idx;
if(model->is_moving) {
InfraredMoveViewItemArray_swap_at(
model->labels, item_idx_prev, model->current_idx);
}
update_list_offset(model);
},
!is_moving);
if((is_moving) && (move_view->move_cb)) {
move_view->move_cb(index_old, index_new, move_view->cb_context);
infrared_move_view_list_update(move_view);
}
consumed = true;
}
true);
if((event->key == InputKeyOk) && (event->type == InputTypeShort)) {
consumed = true;
} else if((event->key == InputKeyOk) && (event->type == InputTypeShort)) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{ model->is_moving = !(model->is_moving); },
{
if(!model->is_moving) {
model->start_idx = model->current_idx;
} else if(move_view->callback) {
move_view->callback(
model->start_idx, model->current_idx, move_view->callback_context);
}
model->is_moving = !(model->is_moving);
},
true);
consumed = true;
} else if(event->key == InputKeyBack) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
if(model->is_moving && move_view->callback) {
move_view->callback(
model->start_idx, model->current_idx, move_view->callback_context);
}
model->is_moving = false;
},
false);
// Not consuming, Back event is passed thru
}
return consumed;
}
static void infrared_move_view_on_exit(void* context) {
furi_assert(context);
InfraredMoveView* move_view = context;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
if(model->btn_names) {
free(model->btn_names);
model->btn_names = NULL;
}
model->btn_number = 0;
model->get_item_cb = NULL;
},
false);
move_view->cb_context = NULL;
}
void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback) {
furi_assert(move_view);
move_view->move_cb = callback;
}
void infrared_move_view_list_init(
void infrared_move_view_set_callback(
InfraredMoveView* move_view,
uint32_t item_count,
InfraredMoveGetItemCallback load_cb,
InfraredMoveCallback callback,
void* context) {
furi_assert(move_view);
move_view->cb_context = context;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
furi_assert(model->btn_names == NULL);
model->btn_names = malloc(sizeof(char*) * item_count);
model->btn_number = item_count;
model->get_item_cb = load_cb;
},
false);
move_view->callback = callback;
move_view->callback_context = context;
}
void infrared_move_view_list_update(InfraredMoveView* move_view) {
furi_assert(move_view);
void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{ InfraredMoveViewItemArray_push_back(model->labels, label); },
true);
}
void infrared_move_view_reset(InfraredMoveView* move_view) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
for(uint32_t i = 0; i < model->btn_number; i++) {
if(!model->get_item_cb) break;
model->btn_names[i] = model->get_item_cb(i, move_view->cb_context);
}
InfraredMoveViewItemArray_reset(model->labels);
model->list_offset = 0;
model->start_idx = 0;
model->current_idx = 0;
model->is_moving = false;
},
true);
false);
move_view->callback_context = NULL;
}
InfraredMoveView* infrared_move_view_alloc(void) {
InfraredMoveView* move_view = malloc(sizeof(InfraredMoveView));
move_view->view = view_alloc();
view_allocate_model(move_view->view, ViewModelTypeLocking, sizeof(InfraredMoveViewModel));
view_set_draw_callback(move_view->view, infrared_move_view_draw_callback);
view_set_input_callback(move_view->view, infrared_move_view_input_callback);
view_set_exit_callback(move_view->view, infrared_move_view_on_exit);
view_set_context(move_view->view, move_view);
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{ InfraredMoveViewItemArray_init(model->labels); },
true);
return move_view;
}
void infrared_move_view_free(InfraredMoveView* move_view) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{ InfraredMoveViewItemArray_clear(model->labels); },
true);
view_free(move_view->view);
free(move_view);
}

View File

@ -6,20 +6,17 @@ typedef struct InfraredMoveView InfraredMoveView;
typedef void (*InfraredMoveCallback)(uint32_t index_old, uint32_t index_new, void* context);
typedef const char* (*InfraredMoveGetItemCallback)(uint32_t index, void* context);
InfraredMoveView* infrared_move_view_alloc(void);
void infrared_move_view_free(InfraredMoveView* debug_view);
View* infrared_move_view_get_view(InfraredMoveView* debug_view);
void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback);
void infrared_move_view_list_init(
void infrared_move_view_set_callback(
InfraredMoveView* move_view,
uint32_t item_count,
InfraredMoveGetItemCallback load_cb,
InfraredMoveCallback callback,
void* context);
void infrared_move_view_list_update(InfraredMoveView* move_view);
void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label);
void infrared_move_view_reset(InfraredMoveView* move_view);