Просмотр исходного кода

Refactoring: split out scenes into their own files

twisted_pear 2 лет назад
Родитель
Сommit
f7c730a74b

+ 2 - 574
esubghz_chat.c

@@ -1,96 +1,19 @@
-#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_wrapper.h"
-
-#define APPLICATION_NAME "ESubGhzChat"
-
-#define DEFAULT_FREQ 433920000
-
-#define RX_TX_BUFFER_SIZE 1024
-
-#define CHAT_BOX_STORE_SIZE 4096
-#define TEXT_INPUT_STORE_SIZE 256
+#include "esubghz_chat_i.h"
 
 #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;
-	ESubGhzChatCryptoCtx *crypto_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;
-
 /* 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)
@@ -161,7 +84,7 @@ static void post_rx(ESubGhzChatState *state, size_t rx_size)
 
 /* Reads the message from msg_input, encrypts it if necessary and then
  * transmits it. */
-static void tx_msg_input(ESubGhzChatState *state)
+void tx_msg_input(ESubGhzChatState *state)
 {
 	/* encrypt message if necessary */
 	size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
@@ -192,501 +115,6 @@ static void tx_msg_input(ESubGhzChatState *state)
 			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 */
-	crypto_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 crypto context */
-	bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
-
-	/* cleanup */
-	crypto_explicit_bzero(key, sizeof(key));
-
-	if (!ret) {
-		crypto_ctx_clear(state->crypto_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)
 {

+ 77 - 0
esubghz_chat_i.h

@@ -0,0 +1,77 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/view_dispatcher_i.h>
+#include <gui/view_port_i.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/text_input.h>
+#include <notification/notification_messages.h>
+#include <lib/subghz/subghz_tx_rx_worker.h>
+#include <toolbox/sha256.h>
+
+#include "crypto_wrapper.h"
+#include "scenes/esubghz_chat_scene.h"
+
+#define APPLICATION_NAME "ESubGhzChat"
+
+#define DEFAULT_FREQ 433920000
+
+#define CHAT_LEAVE_DELAY 10
+
+#define RX_TX_BUFFER_SIZE 1024
+
+#define CHAT_BOX_STORE_SIZE 4096
+#define TEXT_INPUT_STORE_SIZE 256
+
+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;
+	ESubGhzChatCryptoCtx *crypto_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 {
+	ESubGhzChatEvent_FreqEntered,
+	ESubGhzChatEvent_PassEntered,
+	ESubGhzChatEvent_MsgEntered
+} ESubGhzChatEvent;
+
+typedef enum {
+	ESubGhzChatView_Input,
+	ESubGhzChatView_ChatBox,
+} ESubGhzChatView;
+
+void tx_msg_input(ESubGhzChatState *state);

+ 41 - 0
scenes/esubghz_chat_chat_box.c

@@ -0,0 +1,41 @@
+#include "../esubghz_chat_i.h"
+
+/* Prepares the text box scene. */
+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. */
+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. */
+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);
+}

+ 138 - 0
scenes/esubghz_chat_chat_input.c

@@ -0,0 +1,138 @@
+#include "../esubghz_chat_i.h"
+
+/* 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 message input scene. */
+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. */
+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. */
+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);
+}

+ 127 - 0
scenes/esubghz_chat_freq_input.c

@@ -0,0 +1,127 @@
+#include "../esubghz_chat_i.h"
+
+/* 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;
+}
+
+/* Prepares the frequency input scene. */
+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. */
+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. */
+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);
+}

+ 162 - 0
scenes/esubghz_chat_pass_input.c

@@ -0,0 +1,162 @@
+#include "../esubghz_chat_i.h"
+
+/* 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 */
+	crypto_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 crypto context */
+	bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
+
+	/* cleanup */
+	crypto_explicit_bzero(key, sizeof(key));
+
+	if (!ret) {
+		crypto_ctx_clear(state->crypto_ctx);
+		furi_string_printf(error, "Failed to\nset key!");
+		return false;
+	}
+
+	state->encrypted = true;
+
+	return true;
+}
+
+/* Prepares the password input scene. */
+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. */
+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. */
+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);
+}

+ 30 - 0
scenes/esubghz_chat_scene.c

@@ -0,0 +1,30 @@
+#include "esubghz_chat_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_enter_##name,
+void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_event_##name,
+bool (*const esubghz_chat_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_exit_##name,
+void (*const esubghz_chat_scene_on_exit_handlers[])(void* context) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+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,
+};

+ 29 - 0
scenes/esubghz_chat_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) ESubGhzChatScene_##id,
+typedef enum {
+#include "esubghz_chat_scene_config.h"
+	ESubGhzChatScene_MAX
+} ESubGhzChatScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers esubghz_chat_scene_event_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_enter_##name(void*);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+	bool scene_on_event_##name(void* context, SceneManagerEvent event);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_exit_##name(void* context);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE

+ 4 - 0
scenes/esubghz_chat_scene_config.h

@@ -0,0 +1,4 @@
+ADD_SCENE(esubghz_chat, freq_input, FreqInput)
+ADD_SCENE(esubghz_chat, pass_input, PassInput)
+ADD_SCENE(esubghz_chat, chat_input, ChatInput)
+ADD_SCENE(esubghz_chat, chat_box, ChatBox)