| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132 |
- #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 256
- #define TICK_INTERVAL 50
- #define MESSAGE_COMPLETION_TIMEOUT 500
- #define TIMEOUT_BETWEEN_MESSAGES 500
- #define CHAT_LEAVE_DELAY 10
- #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;
- const SubGhzDevice *subghz_device;
- // 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;
- /* remove trailing newline if it is there, for compat with CLI
- * Sub-GHz chat */
- if (state->rx_str_buffer[rx_size - 1] == '\n') {
- state->rx_str_buffer[rx_size - 1] = 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",
- state->rx_str_buffer);
- /* 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);
- }
- /* Reads the message from msg_input, encrypts it if necessary and then
- * transmits it. */
- static void tx_msg_input(ESubGhzChatState *state)
- {
- /* 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 {
- tx_size += 2;
- furi_check(tx_size <= sizeof(state->tx_buffer));
- memcpy(state->tx_buffer,
- furi_string_get_cstr(state->msg_input),
- msg_len);
- /* append \r\n for compat with Sub-GHz CLI chat */
- state->tx_buffer[msg_len] = '\r';
- state->tx_buffer[msg_len + 1] = '\n';
- }
- /* transmit */
- subghz_tx_rx_worker_write(state->subghz_worker, state->tx_buffer,
- tx_size);
- }
- /* 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. After starting
- * the worker a join message is transmitted. */
- 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);
- /* concatenate the name prefix and join message */
- furi_string_set(state->msg_input, state->name_prefix);
- furi_string_cat_str(state->msg_input, " joined chat.");
- /* encrypt and transmit message */
- tx_msg_input(state);
- /* clear message input buffer */
- furi_string_set_char(state->msg_input, 0, 0);
- 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);
- /* 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, ": ");
- 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 and transmit message */
- tx_msg_input(state);
- /* clear message input buffer */
- furi_string_set_char(state->msg_input, 0, 0);
- /* 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 and send a leave message if the user
- * presses back here */
- /* concatenate the name prefix and leave message */
- furi_string_set(state->msg_input, state->name_prefix);
- furi_string_cat_str(state->msg_input, " left chat.");
- /* encrypt and transmit message */
- tx_msg_input(state);
- /* clear message input buffer */
- furi_string_set_char(state->msg_input, 0, 0);
- /* wait for leave message to be delivered */
- furi_delay_ms(CHAT_LEAVE_DELAY);
- 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 */
- furi_string_printf(state->name_prefix, "%s",
- 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;
- }
|