| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103 |
- #include <furi.h>
- #include <furi_hal.h>
- #include <gui/elements.h>
- #include <gui/gui.h>
- #include <gui/modules/text_box.h>
- #include <gui/modules/text_input.h>
- #include <gui/view_dispatcher_i.h>
- #include <gui/view_port_i.h>
- #include <gui/scene_manager.h>
- #include <toolbox/sha256.h>
- #include <notification/notification_messages.h>
- #include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
- #include <lib/subghz/subghz_tx_rx_worker.h>
- #include "esubghz_chat_icons.h"
- #include "crypto/gcm.h"
- #define APPLICATION_NAME "ESubGhzChat"
- #define DEFAULT_FREQ 433920000
- #define KEY_BITS 256
- #define IV_BYTES 12
- #define TAG_BYTES 16
- #define RX_TX_BUFFER_SIZE 1024
- #define CHAT_BOX_STORE_SIZE 4096
- #define TEXT_INPUT_STORE_SIZE 512
- #define TICK_INTERVAL 50
- #define MESSAGE_COMPLETION_TIMEOUT 500
- #define TIMEOUT_BETWEEN_MESSAGES 500
- #define KBD_UNLOCK_CNT 3
- #define KBD_UNLOCK_TIMEOUT 1000
- typedef struct {
- SceneManager *scene_manager;
- ViewDispatcher *view_dispatcher;
- NotificationApp *notification;
- // UI elements
- TextBox *chat_box;
- FuriString *chat_box_store;
- TextInput *text_input;
- char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
- // for Sub-GHz
- uint32_t frequency;
- SubGhzTxRxWorker *subghz_worker;
- #ifdef FW_ORIGIN_Official
- const SubGhzDevice *subghz_device;
- #endif /* FW_ORIGIN_Official */
- // message assembly before TX
- FuriString *name_prefix;
- FuriString *msg_input;
- // encryption
- bool encrypted;
- gcm_context gcm_ctx;
- // RX and TX buffers
- uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
- uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
- char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
- volatile uint32_t last_time_rx_data;
- // for locking
- ViewPortDrawCallback orig_draw_cb;
- ViewPortInputCallback orig_input_cb;
- bool kbd_locked;
- uint32_t kbd_lock_msg_ticks;
- uint8_t kbd_lock_count;
- bool kbd_ok_input_ongoing;
- } ESubGhzChatState;
- typedef enum {
- ESubGhzChatScene_FreqInput,
- ESubGhzChatScene_PassInput,
- ESubGhzChatScene_ChatInput,
- ESubGhzChatScene_ChatBox,
- ESubGhzChatScene_MAX
- } ESubGhzChatScene;
- typedef enum {
- ESubGhzChatView_Input,
- ESubGhzChatView_ChatBox,
- } ESubGhzChatView;
- typedef enum {
- ESubGhzChatEvent_FreqEntered,
- ESubGhzChatEvent_PassEntered,
- ESubGhzChatEvent_MsgEntered
- } ESubGhzChatEvent;
- /* Function to clear sensitive memory. */
- static void esubghz_chat_explicit_bzero(void *s, size_t len)
- {
- memset(s, 0, len);
- asm volatile("" ::: "memory");
- }
- /* Callback for RX events from the Sub-GHz worker. Records the current ticks as
- * the time of the last reception. */
- static void have_read_cb(void* context)
- {
- furi_assert(context);
- ESubGhzChatState* state = context;
- state->last_time_rx_data = furi_get_tick();
- }
- /* Decrypts a message for post_rx(). */
- static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
- {
- if (rx_size < IV_BYTES + TAG_BYTES + 1) {
- return false;
- }
- int ret = gcm_auth_decrypt(&(state->gcm_ctx),
- state->rx_buffer, IV_BYTES,
- NULL, 0,
- state->rx_buffer + IV_BYTES,
- (uint8_t *) state->rx_str_buffer,
- rx_size - (IV_BYTES + TAG_BYTES),
- state->rx_buffer + rx_size - TAG_BYTES,
- TAG_BYTES);
- state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
- return (ret == 0);
- }
- /* Post RX handler, decrypts received messages, displays them in the text box
- * and sends a notification. */
- static void post_rx(ESubGhzChatState *state, size_t rx_size)
- {
- furi_assert(state);
- if (rx_size == 0) {
- return;
- }
- furi_check(rx_size <= RX_TX_BUFFER_SIZE);
- /* decrypt if necessary */
- if (!state->encrypted) {
- memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
- state->rx_str_buffer[rx_size] = 0;
- } else {
- /* if decryption fails output an error message */
- if (!post_rx_decrypt(state, rx_size)) {
- strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
- }
- }
- /* append message to text box */
- furi_string_cat_printf(state->chat_box_store, "\n%s [%u]",
- state->rx_str_buffer, rx_size);
- /* send notification (make the flipper vibrate) */
- notification_message(state->notification, &sequence_single_vibro);
- /* reset text box contents and focus */
- text_box_set_text(state->chat_box,
- furi_string_get_cstr(state->chat_box_store));
- text_box_set_focus(state->chat_box, TextBoxFocusEnd);
- }
- /* Sends FreqEntered event to scene manager and displays the frequency in the
- * text box. */
- static void freq_input_cb(void *context)
- {
- furi_assert(context);
- ESubGhzChatState* state = context;
- furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
- state->frequency);
- scene_manager_handle_custom_event(state->scene_manager,
- ESubGhzChatEvent_FreqEntered);
- }
- /* Validates the entered frequency. */
- static bool freq_input_validator(const char *text, FuriString *error,
- void *context)
- {
- furi_assert(text);
- furi_assert(error);
- furi_assert(context);
- ESubGhzChatState* state = context;
- int ret = sscanf(text, "%lu", &(state->frequency));
- if (ret != 1) {
- furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
- return false;
- }
- if (!subghz_devices_is_frequency_valid(state->subghz_device,
- state->frequency)) {
- furi_string_printf(error, "Frequency\n%lu\n is invalid!",
- state->frequency);
- return false;
- }
- #ifdef FW_ORIGIN_Official
- if (!furi_hal_region_is_frequency_allowed(state->frequency)) {
- #else /* FW_ORIGIN_Official */
- if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
- #endif /* FW_ORIGIN_Official */
- furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
- state->frequency);
- return false;
- }
- return true;
- }
- /* Sends PassEntered event to scene manager and displays whether or not
- * encryption has been enabled in the text box. Also clears the text input
- * buffer to remove the password and starts the Sub-GHz worker. */
- static void pass_input_cb(void *context)
- {
- furi_assert(context);
- ESubGhzChatState* state = context;
- furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
- (state->encrypted ? "yes" : "no"));
- /* clear the text input buffer to remove the password */
- esubghz_chat_explicit_bzero(state->text_input_store,
- sizeof(state->text_input_store));
- subghz_tx_rx_worker_start(state->subghz_worker, state->subghz_device,
- state->frequency);
- scene_manager_handle_custom_event(state->scene_manager,
- ESubGhzChatEvent_PassEntered);
- }
- /* If a password was entered this derives a key from the password using a
- * single pass of SHA256 and initiates the AES-GCM context for encryption. If
- * the initiation fails, the password is rejected. */
- static bool pass_input_validator(const char *text, FuriString *error,
- void *context)
- {
- furi_assert(text);
- furi_assert(error);
- furi_assert(context);
- ESubGhzChatState* state = context;
- #ifdef FW_ORIGIN_Official
- if (strlen(text) == 0) {
- furi_string_printf(error, "Enter a\npassword!");
- return false;
- }
- if (strcmp(text, " ") == 0) {
- #else /* FW_ORIGIN_Official */
- if (strlen(text) == 0) {
- #endif /* FW_ORIGIN_Official */
- state->encrypted = false;
- return true;
- }
- unsigned char key[KEY_BITS / 8];
- /* derive a key from the password */
- sha256((unsigned char *) text, strlen(text), key);
- // TODO: remove this
- furi_string_cat_printf(state->chat_box_store, "\nKey:");
- int i;
- for (i = 0; i < KEY_BITS / 8; i++) {
- furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
- }
- /* initiate the AES-GCM context */
- int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
- /* cleanup */
- esubghz_chat_explicit_bzero(key, sizeof(key));
- if (ret != 0) {
- esubghz_chat_explicit_bzero(&(state->gcm_ctx),
- sizeof(state->gcm_ctx));
- furi_string_printf(error, "Failed to\nset key!");
- return false;
- }
- state->encrypted = true;
- return true;
- }
- /* If no message was entred this simply emits a MsgEntered event to the scene
- * manager to switch to the text box. If a message was entered it is appended
- * to the name string. The result is encrypted, if encryption is enabled, and
- * then copied into the TX buffer. The contents of the TX buffer are then
- * transmitted. The sent message is appended to the text box and a MsgEntered
- * event is sent to the scene manager to switch to the text box view. */
- static void chat_input_cb(void *context)
- {
- furi_assert(context);
- ESubGhzChatState* state = context;
- /* no message, just switch to the text box view */
- #ifdef FW_ORIGIN_Official
- if (strcmp(state->text_input_store, " ") == 0) {
- #else /* FW_ORIGIN_Official */
- if (strlen(state->text_input_store) == 0) {
- #endif /* FW_ORIGIN_Official */
- scene_manager_handle_custom_event(state->scene_manager,
- ESubGhzChatEvent_MsgEntered);
- return;
- }
- /* concatenate the name prefix and the actual message */
- furi_string_set(state->msg_input, state->name_prefix);
- furi_string_cat_str(state->msg_input, state->text_input_store);
- /* append the message to the chat box */
- furi_string_cat_printf(state->chat_box_store, "\n%s",
- furi_string_get_cstr(state->msg_input));
- /* encrypt message if necessary */
- size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
- size_t tx_size = msg_len;
- if (state->encrypted) {
- tx_size += IV_BYTES + TAG_BYTES;
- furi_check(tx_size <= sizeof(state->tx_buffer));
- furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
- gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
- state->tx_buffer, IV_BYTES,
- NULL, 0,
- (unsigned char *)
- furi_string_get_cstr(state->msg_input),
- state->tx_buffer + IV_BYTES,
- msg_len,
- state->tx_buffer + IV_BYTES + msg_len,
- TAG_BYTES);
- } else {
- furi_check(tx_size <= sizeof(state->tx_buffer));
- memcpy(state->tx_buffer,
- furi_string_get_cstr(state->msg_input),
- tx_size);
- }
- /* clear message input buffer */
- furi_string_set_char(state->msg_input, 0, 0);
- // TODO: remove this
- furi_string_cat_printf(state->chat_box_store, "\nTXed (HEX):");
- size_t i;
- for (i = 0; i < tx_size; i++) {
- furi_string_cat_printf(state->chat_box_store, " %02x",
- state->tx_buffer[i]);
- }
- /* transmit */
- subghz_tx_rx_worker_write(state->subghz_worker, state->tx_buffer,
- tx_size);
- /* switch to text box view */
- scene_manager_handle_custom_event(state->scene_manager,
- ESubGhzChatEvent_MsgEntered);
- }
- /* Prepares the frequency input scene. */
- static void scene_on_enter_freq_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
- (uint32_t) DEFAULT_FREQ);
- text_input_reset(state->text_input);
- text_input_set_result_callback(
- state->text_input,
- freq_input_cb,
- state,
- state->text_input_store,
- sizeof(state->text_input_store),
- true);
- text_input_set_validator(
- state->text_input,
- freq_input_validator,
- state);
- text_input_set_header_text(
- state->text_input,
- "Frequency");
- view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
- }
- /* Handles scene manager events for the frequency input scene. */
- static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- bool consumed = false;
- switch(event.type) {
- case SceneManagerEventTypeCustom:
- switch(event.event) {
- /* switch to password input scene */
- case ESubGhzChatEvent_FreqEntered:
- scene_manager_next_scene(state->scene_manager,
- ESubGhzChatScene_PassInput);
- consumed = true;
- break;
- }
- break;
- case SceneManagerEventTypeBack:
- /* stop the application if the user presses back here */
- view_dispatcher_stop(state->view_dispatcher);
- consumed = true;
- break;
- default:
- consumed = false;
- break;
- }
- return consumed;
- }
- /* Cleans up the frequency input scene. */
- static void scene_on_exit_freq_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- text_input_reset(state->text_input);
- }
- /* Prepares the password input scene. */
- static void scene_on_enter_pass_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- state->text_input_store[0] = 0;
- text_input_reset(state->text_input);
- text_input_set_result_callback(
- state->text_input,
- pass_input_cb,
- state,
- state->text_input_store,
- sizeof(state->text_input_store),
- true);
- text_input_set_validator(
- state->text_input,
- pass_input_validator,
- state);
- text_input_set_header_text(
- state->text_input,
- #ifdef FW_ORIGIN_Official
- "Password (space for no encr.)");
- #else /* FW_ORIGIN_Official */
- "Password (empty for no encr.)");
- text_input_set_minimum_length(state->text_input, 0);
- #endif /* FW_ORIGIN_Official */
- view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
- }
- /* Handles scene manager events for the password input scene. */
- static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- bool consumed = false;
- switch(event.type) {
- case SceneManagerEventTypeCustom:
- switch(event.event) {
- /* switch to message input scene */
- case ESubGhzChatEvent_PassEntered:
- scene_manager_next_scene(state->scene_manager,
- ESubGhzChatScene_ChatInput);
- consumed = true;
- break;
- }
- break;
- case SceneManagerEventTypeBack:
- /* stop the application if the user presses back here */
- view_dispatcher_stop(state->view_dispatcher);
- consumed = true;
- break;
- default:
- consumed = false;
- break;
- }
- return consumed;
- }
- /* Cleans up the password input scene. */
- static void scene_on_exit_pass_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- text_input_reset(state->text_input);
- }
- /* Prepares the message input scene. */
- static void scene_on_enter_chat_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- state->text_input_store[0] = 0;
- text_input_reset(state->text_input);
- text_input_set_result_callback(
- state->text_input,
- chat_input_cb,
- state,
- state->text_input_store,
- sizeof(state->text_input_store),
- true);
- text_input_set_validator(
- state->text_input,
- NULL,
- NULL);
- text_input_set_header_text(
- state->text_input,
- #ifdef FW_ORIGIN_Official
- "Message (space for none)");
- #else /* FW_ORIGIN_Official */
- "Message");
- text_input_set_minimum_length(state->text_input, 0);
- #endif /* FW_ORIGIN_Official */
- view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
- }
- /* Handles scene manager events for the message input scene. */
- static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- bool consumed = false;
- switch(event.type) {
- case SceneManagerEventTypeCustom:
- switch(event.event) {
- /* switch to text box scene */
- case ESubGhzChatEvent_MsgEntered:
- scene_manager_next_scene(state->scene_manager,
- ESubGhzChatScene_ChatBox);
- consumed = true;
- break;
- }
- break;
- case SceneManagerEventTypeBack:
- /* stop the application if the user presses back here */
- view_dispatcher_stop(state->view_dispatcher);
- consumed = true;
- break;
- default:
- consumed = false;
- break;
- }
- return consumed;
- }
- /* Cleans up the password input scene. */
- static void scene_on_exit_chat_input(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
- furi_assert(context);
- ESubGhzChatState* state = context;
- text_input_reset(state->text_input);
- }
- /* Prepares the text box scene. */
- static void scene_on_enter_chat_box(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
- furi_assert(context);
- ESubGhzChatState* state = context;
- text_box_reset(state->chat_box);
- text_box_set_text(state->chat_box,
- furi_string_get_cstr(state->chat_box_store));
- text_box_set_focus(state->chat_box, TextBoxFocusEnd);
- view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
- }
- /* Handles scene manager events for the text box scene. No events are handled
- * here. */
- static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
- {
- UNUSED(event);
- FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
- furi_assert(context);
- return false;
- }
- /* Cleans up the text box scene. */
- static void scene_on_exit_chat_box(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
- furi_assert(context);
- ESubGhzChatState* state = context;
- text_box_reset(state->chat_box);
- }
- /* Scene entry handlers. */
- static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
- scene_on_enter_freq_input,
- scene_on_enter_pass_input,
- scene_on_enter_chat_input,
- scene_on_enter_chat_box
- };
- /* Scene event handlers. */
- static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
- scene_on_event_freq_input,
- scene_on_event_pass_input,
- scene_on_event_chat_input,
- scene_on_event_chat_box
- };
- /* Scene exit handlers. */
- static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
- scene_on_exit_freq_input,
- scene_on_exit_pass_input,
- scene_on_exit_chat_input,
- scene_on_exit_chat_box
- };
- /* Handlers for the scene manager. */
- static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
- .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
- .on_event_handlers = esubghz_chat_scene_on_event_handlers,
- .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
- .scene_num = ESubGhzChatScene_MAX};
- /* Whether or not to display the locked message. */
- static bool kbd_lock_msg_display(ESubGhzChatState *state)
- {
- return (state->kbd_lock_msg_ticks != 0);
- }
- /* Whether or not to hide the locked message again. */
- static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
- {
- if (state->kbd_lock_msg_ticks == 0) {
- return false;
- }
- if (furi_get_tick() - state->kbd_lock_msg_ticks > KBD_UNLOCK_TIMEOUT) {
- return true;
- }
- return false;
- }
- /* Resets the timeout for the locked message and turns off the backlight if
- * specified. */
- static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
- {
- state->kbd_lock_msg_ticks = 0;
- state->kbd_lock_count = 0;
- if (backlight_off) {
- notification_message(state->notification,
- &sequence_display_backlight_off);
- }
- }
- /* Locks the keyboard. */
- static void kbd_lock(ESubGhzChatState *state)
- {
- state->kbd_locked = true;
- kbd_lock_msg_reset(state, true);
- }
- /* Unlocks the keyboard. */
- static void kbd_unlock(ESubGhzChatState *state)
- {
- state->kbd_locked = false;
- kbd_lock_msg_reset(state, false);
- }
- /* Custom event callback for view dispatcher. Just calls scene manager. */
- static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
- {
- FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
- furi_assert(context);
- ESubGhzChatState* state = context;
- return scene_manager_handle_custom_event(state->scene_manager, event);
- }
- /* Navigation event callback for view dispatcher. Just calls scene manager. */
- static bool esubghz_chat_navigation_event_callback(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
- furi_assert(context);
- ESubGhzChatState* state = context;
- return scene_manager_handle_back_event(state->scene_manager);
- }
- /* Tick event callback for view dispatcher. Called every TICK_INTERVAL. Resets
- * the locked message if necessary. Retrieves a received message from the
- * Sub-GHz worker and calls post_rx(). Then calls the scene manager. */
- static void esubghz_chat_tick_event_callback(void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
- furi_assert(context);
- ESubGhzChatState* state = context;
- /* reset locked message if necessary */
- if (kbd_lock_msg_reset_timeout(state)) {
- kbd_lock_msg_reset(state, true);
- }
- /* if the maximum message size was reached or the
- * MESSAGE_COMPLETION_TIMEOUT has expired, retrieve a message and call
- * post_rx() */
- size_t avail = 0;
- while ((avail = subghz_tx_rx_worker_available(state->subghz_worker)) >
- 0) {
- volatile uint32_t since_last_rx = furi_get_tick() -
- state->last_time_rx_data;
- if (avail < RX_TX_BUFFER_SIZE && since_last_rx <
- MESSAGE_COMPLETION_TIMEOUT) {
- break;
- }
- size_t rx_size = subghz_tx_rx_worker_read(state->subghz_worker,
- state->rx_buffer, RX_TX_BUFFER_SIZE);
- post_rx(state, rx_size);
- }
- /* call scene manager */
- scene_manager_handle_tick_event(state->scene_manager);
- }
- /* Hooks into the view port's draw callback to overlay the keyboard locked
- * message. */
- static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
- furi_assert(canvas);
- furi_assert(context);
- ESubGhzChatState* state = context;
- /* call original callback */
- state->orig_draw_cb(canvas, state->view_dispatcher);
- /* display if the keyboard is locked */
- if (state->kbd_locked) {
- canvas_set_font(canvas, FontPrimary);
- elements_multiline_text_framed(canvas, 42, 30, "Locked");
- }
- /* display the unlock message if necessary */
- if (kbd_lock_msg_display(state)) {
- canvas_set_font(canvas, FontSecondary);
- elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
- elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
- canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
- canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
- canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
- canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
- }
- }
- /* Hooks into the view port's input callback to handle the user locking the
- * keyboard. */
- static void esubghz_hooked_input_callback(InputEvent* event, void* context)
- {
- FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
- furi_assert(event);
- furi_assert(context);
- ESubGhzChatState* state = context;
- /* if the keyboard is locked no key presses are forwarded */
- if (state->kbd_locked) {
- /* key has been pressed, display the unlock message and
- * initiate the timer */
- if (state->kbd_lock_count == 0) {
- state->kbd_lock_msg_ticks = furi_get_tick();
- }
- /* back button has been pressed, increase the lock counter */
- if (event->key == InputKeyBack && event->type ==
- InputTypeShort) {
- state->kbd_lock_count++;
- }
- /* unlock the keyboard */
- if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
- kbd_unlock(state);
- }
- /* do not handle the event */
- return;
- }
- if (event->key == InputKeyOk) {
- /* if we are in the chat view and no input is ongoing, allow
- * locking */
- if (state->view_dispatcher->current_view ==
- text_box_get_view(state->chat_box) &&
- !(state->kbd_ok_input_ongoing)) {
- /* lock keyboard upon long press of Ok button */
- if (event->type == InputTypeLong) {
- kbd_lock(state);
- }
- /* do not handle any Ok key events to prevent blocking
- * of other keys */
- return;
- }
- /* handle ongoing inputs when chaning to chat view */
- if (event->type == InputTypePress) {
- state->kbd_ok_input_ongoing = true;
- } else if (event->type == InputTypeRelease) {
- state->kbd_ok_input_ongoing = false;
- }
- }
- /* call original callback */
- state->orig_input_cb(event, state->view_dispatcher);
- }
- static bool helper_strings_alloc(ESubGhzChatState *state)
- {
- furi_assert(state);
- state->name_prefix = furi_string_alloc();
- if (state->name_prefix == NULL) {
- return false;
- }
- state->msg_input = furi_string_alloc();
- if (state->msg_input == NULL) {
- furi_string_free(state->name_prefix);
- return false;
- }
- return true;
- }
- static void helper_strings_free(ESubGhzChatState *state)
- {
- furi_assert(state);
- furi_string_free(state->name_prefix);
- furi_string_free(state->msg_input);
- }
- static bool chat_box_alloc(ESubGhzChatState *state)
- {
- furi_assert(state);
- state->chat_box = text_box_alloc();
- if (state->chat_box == NULL) {
- return false;
- }
- state->chat_box_store = furi_string_alloc();
- if (state->chat_box_store == NULL) {
- text_box_free(state->chat_box);
- return false;
- }
- furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
- furi_string_set_char(state->chat_box_store, 0, 0);
- text_box_set_text(state->chat_box,
- furi_string_get_cstr(state->chat_box_store));
- text_box_set_focus(state->chat_box, TextBoxFocusEnd);
- return true;
- }
- static void chat_box_free(ESubGhzChatState *state)
- {
- furi_assert(state);
- text_box_free(state->chat_box);
- furi_string_free(state->chat_box_store);
- }
- int32_t esubghz_chat(void)
- {
- /* init the GCM and AES tables */
- gcm_initialize();
- int32_t err = -1;
- FURI_LOG_I(APPLICATION_NAME, "Starting...");
- /* allocate necessary structs and buffers */
- ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
- if (state == NULL) {
- goto err_alloc;
- }
- memset(state, 0, sizeof(*state));
- state->scene_manager = scene_manager_alloc(
- &esubghz_chat_scene_event_handlers, state);
- if (state->scene_manager == NULL) {
- goto err_alloc_sm;
- }
- state->view_dispatcher = view_dispatcher_alloc();
- if (state->view_dispatcher == NULL) {
- goto err_alloc_vd;
- }
- if (!helper_strings_alloc(state)) {
- goto err_alloc_hs;
- }
- state->text_input = text_input_alloc();
- if (state->text_input == NULL) {
- goto err_alloc_ti;
- }
- if (!chat_box_alloc(state)) {
- goto err_alloc_cb;
- }
- state->subghz_worker = subghz_tx_rx_worker_alloc();
- if (state->subghz_worker == NULL) {
- goto err_alloc_worker;
- }
- /* set the have_read callback of the Sub-GHz worker */
- subghz_tx_rx_worker_set_callback_have_read(state->subghz_worker,
- have_read_cb, state);
- /* enter suppress charge mode */
- furi_hal_power_suppress_charge_enter();
- /* init internal device */
- subghz_devices_init();
- state->subghz_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
- /* set chat name prefix */
- // TODO: handle escape chars here somehow
- furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
- furi_hal_version_get_name_ptr());
- /* get notification record, we use this to make the flipper vibrate */
- /* no error handling here, don't know how */
- state->notification = furi_record_open(RECORD_NOTIFICATION);
- /* hook into the view port's draw and input callbacks */
- state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
- state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
- view_port_draw_callback_set(state->view_dispatcher->view_port,
- esubghz_hooked_draw_callback, state);
- view_port_input_callback_set(state->view_dispatcher->view_port,
- esubghz_hooked_input_callback, state);
- view_dispatcher_enable_queue(state->view_dispatcher);
- /* set callbacks for view dispatcher */
- view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
- view_dispatcher_set_custom_event_callback(
- state->view_dispatcher,
- esubghz_chat_custom_event_callback);
- view_dispatcher_set_navigation_event_callback(
- state->view_dispatcher,
- esubghz_chat_navigation_event_callback);
- view_dispatcher_set_tick_event_callback(
- state->view_dispatcher,
- esubghz_chat_tick_event_callback,
- TICK_INTERVAL);
- /* add our two views to the view dispatcher */
- view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
- text_input_get_view(state->text_input));
- view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
- text_box_get_view(state->chat_box));
- /* get the GUI record and attach the view dispatcher to the GUI */
- /* no error handling here, don't know how */
- Gui *gui = furi_record_open(RECORD_GUI);
- view_dispatcher_attach_to_gui(state->view_dispatcher, gui,
- ViewDispatcherTypeFullscreen);
- /* switch to the frequency input scene */
- scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
- /* run the view dispatcher, this call only returns when we close the
- * application */
- view_dispatcher_run(state->view_dispatcher);
- /* if it is running, stop the Sub-GHz worker */
- if (subghz_tx_rx_worker_is_running(state->subghz_worker)) {
- subghz_tx_rx_worker_stop(state->subghz_worker);
- }
- err = 0;
- /* close GUI record */
- furi_record_close(RECORD_GUI);
- /* remove our two views from the view dispatcher */
- view_dispatcher_remove_view(state->view_dispatcher,
- ESubGhzChatView_Input);
- view_dispatcher_remove_view(state->view_dispatcher,
- ESubGhzChatView_ChatBox);
- /* close notification record */
- furi_record_close(RECORD_NOTIFICATION);
- /* clear the key and potential password */
- esubghz_chat_explicit_bzero(state->text_input_store,
- sizeof(state->text_input_store));
- esubghz_chat_explicit_bzero(&(state->gcm_ctx), sizeof(state->gcm_ctx));
- /* deinit devices */
- subghz_devices_deinit();
- /* exit suppress charge mode */
- furi_hal_power_suppress_charge_exit();
- /* free everything we allocated */
- subghz_tx_rx_worker_free(state->subghz_worker);
- err_alloc_worker:
- chat_box_free(state);
- err_alloc_cb:
- text_input_free(state->text_input);
- err_alloc_ti:
- helper_strings_free(state);
- err_alloc_hs:
- view_dispatcher_free(state->view_dispatcher);
- err_alloc_vd:
- scene_manager_free(state->scene_manager);
- err_alloc_sm:
- free(state);
- err_alloc:
- if (err != 0) {
- FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
- } else {
- FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
- }
- return err;
- }
|