esubghz_chat.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <gui/modules/text_box.h>
  5. #include <gui/modules/text_input.h>
  6. #include <gui/view_dispatcher.h>
  7. #include <gui/scene_manager.h>
  8. #define APPLICATION_NAME "ESubGhzChat"
  9. #define CHAT_BOX_STORE_SIZE 4096
  10. #define TEXT_INPUT_STORE_SIZE 512
  11. typedef struct {
  12. SceneManager *scene_manager;
  13. ViewDispatcher *view_dispatcher;
  14. TextBox *chat_box;
  15. FuriString *chat_box_store;
  16. TextInput *text_input;
  17. char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
  18. bool encrypted;
  19. uint32_t frequency;
  20. } ESubGhzChatState;
  21. typedef enum {
  22. ESubGhzChatScene_FreqInput,
  23. ESubGhzChatScene_PassInput,
  24. ESubGhzChatScene_ChatInput,
  25. ESubGhzChatScene_ChatBox,
  26. ESubGhzChatScene_MAX
  27. } ESubGhzChatScene;
  28. typedef enum {
  29. ESubGhzChatView_Input,
  30. ESubGhzChatView_ChatBox,
  31. } ESubGhzChatView;
  32. typedef enum {
  33. ESubGhzChatEvent_FreqEntered,
  34. ESubGhzChatEvent_PassEntered,
  35. ESubGhzChatEvent_MsgEntered
  36. } ESubGhzChatEvent;
  37. static void freq_input_cb(void *context)
  38. {
  39. furi_assert(context);
  40. ESubGhzChatState* state = context;
  41. furi_string_cat_printf(state->chat_box_store, "Frequency: %lu\n",
  42. state->frequency);
  43. scene_manager_handle_custom_event(state->scene_manager,
  44. ESubGhzChatEvent_FreqEntered);
  45. }
  46. static bool freq_input_validator(const char *text, FuriString *error,
  47. void *context)
  48. {
  49. furi_assert(text);
  50. furi_assert(error);
  51. furi_assert(context);
  52. ESubGhzChatState* state = context;
  53. int ret = sscanf(text, "%lu", &(state->frequency));
  54. if (ret != 1) {
  55. furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
  56. return false;
  57. }
  58. if (!furi_hal_subghz_is_frequency_valid(state->frequency)) {
  59. furi_string_printf(error, "Frequency\n%lu\n is invalid!",
  60. state->frequency);
  61. return false;
  62. }
  63. if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
  64. furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
  65. state->frequency);
  66. return false;
  67. }
  68. return true;
  69. }
  70. static void pass_input_cb(void *context)
  71. {
  72. furi_assert(context);
  73. ESubGhzChatState* state = context;
  74. if (strlen(state->text_input_store) == 0) {
  75. state->encrypted = false;
  76. } else {
  77. state->encrypted = true;
  78. // TODO
  79. }
  80. furi_string_cat_printf(state->chat_box_store, "Encrypted: %s\n",
  81. (state->encrypted ? "true" : "false"));
  82. scene_manager_handle_custom_event(state->scene_manager,
  83. ESubGhzChatEvent_PassEntered);
  84. }
  85. static void chat_input_cb(void *context)
  86. {
  87. furi_assert(context);
  88. ESubGhzChatState* state = context;
  89. scene_manager_handle_custom_event(state->scene_manager,
  90. ESubGhzChatEvent_MsgEntered);
  91. }
  92. static void scene_on_enter_freq_input(void* context)
  93. {
  94. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
  95. furi_assert(context);
  96. ESubGhzChatState* state = context;
  97. state->text_input_store[0] = 0;
  98. text_input_reset(state->text_input);
  99. text_input_set_result_callback(
  100. state->text_input,
  101. freq_input_cb,
  102. state,
  103. state->text_input_store,
  104. sizeof(state->text_input_store),
  105. true);
  106. text_input_set_validator(
  107. state->text_input,
  108. freq_input_validator,
  109. state);
  110. text_input_set_header_text(
  111. state->text_input,
  112. "Frequency");
  113. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  114. }
  115. static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
  116. {
  117. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
  118. furi_assert(context);
  119. ESubGhzChatState* state = context;
  120. bool consumed = false;
  121. switch(event.type) {
  122. case SceneManagerEventTypeCustom:
  123. switch(event.event) {
  124. case ESubGhzChatEvent_FreqEntered:
  125. scene_manager_next_scene(state->scene_manager,
  126. ESubGhzChatScene_PassInput);
  127. consumed = true;
  128. break;
  129. }
  130. break;
  131. case SceneManagerEventTypeBack:
  132. view_dispatcher_stop(state->view_dispatcher);
  133. consumed = true;
  134. break;
  135. default:
  136. consumed = false;
  137. break;
  138. }
  139. return consumed;
  140. }
  141. static void scene_on_exit_freq_input(void* context)
  142. {
  143. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
  144. furi_assert(context);
  145. ESubGhzChatState* state = context;
  146. text_input_reset(state->text_input);
  147. }
  148. static void scene_on_enter_pass_input(void* context)
  149. {
  150. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
  151. furi_assert(context);
  152. ESubGhzChatState* state = context;
  153. state->text_input_store[0] = 0;
  154. text_input_reset(state->text_input);
  155. text_input_set_result_callback(
  156. state->text_input,
  157. pass_input_cb,
  158. state,
  159. state->text_input_store,
  160. sizeof(state->text_input_store),
  161. true);
  162. text_input_set_validator(
  163. state->text_input,
  164. NULL,
  165. NULL);
  166. text_input_set_header_text(
  167. state->text_input,
  168. "Password (empty for no encr.)");
  169. text_input_set_minimum_length(state->text_input, 0);
  170. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  171. }
  172. static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
  173. {
  174. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
  175. furi_assert(context);
  176. ESubGhzChatState* state = context;
  177. bool consumed = false;
  178. switch(event.type) {
  179. case SceneManagerEventTypeCustom:
  180. switch(event.event) {
  181. case ESubGhzChatEvent_PassEntered:
  182. scene_manager_next_scene(state->scene_manager,
  183. ESubGhzChatScene_ChatInput);
  184. consumed = true;
  185. break;
  186. }
  187. break;
  188. case SceneManagerEventTypeBack:
  189. view_dispatcher_stop(state->view_dispatcher);
  190. consumed = true;
  191. break;
  192. default:
  193. consumed = false;
  194. break;
  195. }
  196. return consumed;
  197. }
  198. static void scene_on_exit_pass_input(void* context)
  199. {
  200. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
  201. furi_assert(context);
  202. ESubGhzChatState* state = context;
  203. text_input_reset(state->text_input);
  204. }
  205. static void scene_on_enter_chat_input(void* context)
  206. {
  207. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
  208. furi_assert(context);
  209. ESubGhzChatState* state = context;
  210. state->text_input_store[0] = 0;
  211. text_input_reset(state->text_input);
  212. text_input_set_result_callback(
  213. state->text_input,
  214. chat_input_cb,
  215. state,
  216. state->text_input_store,
  217. sizeof(state->text_input_store),
  218. true);
  219. text_input_set_validator(
  220. state->text_input,
  221. NULL,
  222. NULL);
  223. text_input_set_header_text(
  224. state->text_input,
  225. "Message");
  226. text_input_set_minimum_length(state->text_input, 0);
  227. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  228. }
  229. static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
  230. {
  231. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
  232. furi_assert(context);
  233. ESubGhzChatState* state = context;
  234. bool consumed = false;
  235. switch(event.type) {
  236. case SceneManagerEventTypeCustom:
  237. switch(event.event) {
  238. case ESubGhzChatEvent_MsgEntered:
  239. scene_manager_next_scene(state->scene_manager,
  240. ESubGhzChatScene_ChatBox);
  241. consumed = true;
  242. break;
  243. }
  244. break;
  245. case SceneManagerEventTypeBack:
  246. view_dispatcher_stop(state->view_dispatcher);
  247. consumed = true;
  248. break;
  249. default:
  250. consumed = false;
  251. break;
  252. }
  253. return consumed;
  254. }
  255. static void scene_on_exit_chat_input(void* context)
  256. {
  257. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
  258. furi_assert(context);
  259. ESubGhzChatState* state = context;
  260. text_input_reset(state->text_input);
  261. }
  262. static void scene_on_enter_chat_box(void* context)
  263. {
  264. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
  265. furi_assert(context);
  266. ESubGhzChatState* state = context;
  267. text_box_reset(state->chat_box);
  268. text_box_set_text(state->chat_box,
  269. furi_string_get_cstr(state->chat_box_store));
  270. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  271. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  272. }
  273. static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
  274. {
  275. UNUSED(event);
  276. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
  277. furi_assert(context);
  278. // TODO
  279. return false;
  280. }
  281. static void scene_on_exit_chat_box(void* context)
  282. {
  283. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
  284. furi_assert(context);
  285. ESubGhzChatState* state = context;
  286. text_box_reset(state->chat_box);
  287. }
  288. static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
  289. scene_on_enter_freq_input,
  290. scene_on_enter_pass_input,
  291. scene_on_enter_chat_input,
  292. scene_on_enter_chat_box
  293. };
  294. static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
  295. scene_on_event_freq_input,
  296. scene_on_event_pass_input,
  297. scene_on_event_chat_input,
  298. scene_on_event_chat_box
  299. };
  300. static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
  301. scene_on_exit_freq_input,
  302. scene_on_exit_pass_input,
  303. scene_on_exit_chat_input,
  304. scene_on_exit_chat_box
  305. };
  306. static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
  307. .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
  308. .on_event_handlers = esubghz_chat_scene_on_event_handlers,
  309. .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
  310. .scene_num = ESubGhzChatScene_MAX};
  311. static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
  312. {
  313. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
  314. furi_assert(context);
  315. ESubGhzChatState* state = context;
  316. return scene_manager_handle_custom_event(state->scene_manager, event);
  317. }
  318. static bool esubghz_chat_navigation_event_callback(void* context)
  319. {
  320. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
  321. furi_assert(context);
  322. ESubGhzChatState* state = context;
  323. return scene_manager_handle_back_event(state->scene_manager);
  324. }
  325. static bool chat_box_alloc(ESubGhzChatState *state)
  326. {
  327. furi_assert(state);
  328. state->chat_box = text_box_alloc();
  329. if (state->chat_box == NULL) {
  330. return false;
  331. }
  332. state->chat_box_store = furi_string_alloc();
  333. if (state->chat_box_store == NULL) {
  334. text_box_free(state->chat_box);
  335. return false;
  336. }
  337. furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
  338. furi_string_set_char(state->chat_box_store, 0, 0);
  339. text_box_set_text(state->chat_box,
  340. furi_string_get_cstr(state->chat_box_store));
  341. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  342. return true;
  343. }
  344. static void chat_box_free(ESubGhzChatState *state)
  345. {
  346. furi_assert(state);
  347. text_box_free(state->chat_box);
  348. furi_string_free(state->chat_box_store);
  349. }
  350. int32_t esubghz_chat(void)
  351. {
  352. int32_t err = -1;
  353. FURI_LOG_I(APPLICATION_NAME, "Starting...");
  354. ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
  355. if (state == NULL) {
  356. goto err_alloc;
  357. }
  358. memset(state, 0, sizeof(*state));
  359. state->scene_manager = scene_manager_alloc(
  360. &esubghz_chat_scene_event_handlers, state);
  361. if (state->scene_manager == NULL) {
  362. goto err_alloc_sm;
  363. }
  364. state->view_dispatcher = view_dispatcher_alloc();
  365. if (state->view_dispatcher == NULL) {
  366. goto err_alloc_vd;
  367. }
  368. state->text_input = text_input_alloc();
  369. if (state->text_input == NULL) {
  370. goto err_alloc_ti;
  371. }
  372. if (!chat_box_alloc(state)) {
  373. goto err_alloc_cb;
  374. }
  375. view_dispatcher_enable_queue(state->view_dispatcher);
  376. view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
  377. view_dispatcher_set_custom_event_callback(
  378. state->view_dispatcher,
  379. esubghz_chat_custom_event_callback);
  380. view_dispatcher_set_navigation_event_callback(
  381. state->view_dispatcher,
  382. esubghz_chat_navigation_event_callback);
  383. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
  384. text_input_get_view(state->text_input));
  385. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
  386. text_box_get_view(state->chat_box));
  387. /* no error handling here, don't know how */
  388. Gui *gui = furi_record_open(RECORD_GUI);
  389. view_dispatcher_attach_to_gui(state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  390. scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
  391. view_dispatcher_run(state->view_dispatcher);
  392. err = 0;
  393. furi_record_close(RECORD_GUI);
  394. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_Input);
  395. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  396. chat_box_free(state);
  397. err_alloc_cb:
  398. text_input_free(state->text_input);
  399. err_alloc_ti:
  400. view_dispatcher_free(state->view_dispatcher);
  401. err_alloc_vd:
  402. scene_manager_free(state->scene_manager);
  403. err_alloc_sm:
  404. free(state);
  405. err_alloc:
  406. if (err != 0) {
  407. FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
  408. } else {
  409. FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
  410. }
  411. return err;
  412. }