esubghz_chat.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/elements.h>
  4. #include <gui/gui.h>
  5. #include <gui/modules/text_box.h>
  6. #include <gui/modules/text_input.h>
  7. #include <gui/view_dispatcher_i.h>
  8. #include <gui/view_port_i.h>
  9. #include <gui/scene_manager.h>
  10. #include <toolbox/sha256.h>
  11. #include <notification/notification_messages.h>
  12. #include "esubghz_chat_icons.h"
  13. #include "crypto/gcm.h"
  14. #define APPLICATION_NAME "ESubGhzChat"
  15. #define DEFAULT_FREQ 433920000
  16. #define KEY_BITS 256
  17. #define IV_BYTES 12
  18. #define TAG_BYTES 16
  19. #define RX_TX_BUFFER_SIZE 1024
  20. #define CHAT_BOX_STORE_SIZE 4096
  21. #define TEXT_INPUT_STORE_SIZE 512
  22. #define TICK_INTERVAL 50
  23. #define MESSAGE_COMPLETION_TIMEOUT 200
  24. #define TIMEOUT_BETWEEN_MESSAGES 500
  25. #define KBD_UNLOCK_CNT 3
  26. #define KBD_UNLOCK_TIMEOUT 1000
  27. typedef struct {
  28. SceneManager *scene_manager;
  29. ViewDispatcher *view_dispatcher;
  30. NotificationApp *notification;
  31. TextBox *chat_box;
  32. FuriString *chat_box_store;
  33. TextInput *text_input;
  34. char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
  35. FuriString *name_prefix;
  36. FuriString *msg_input;
  37. bool encrypted;
  38. uint32_t frequency;
  39. gcm_context gcm_ctx;
  40. uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
  41. uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
  42. char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
  43. FuriStreamBuffer *rx_collection_buffer;
  44. uint32_t last_time_rx_data;
  45. // for locking
  46. ViewPortDrawCallback orig_draw_cb;
  47. ViewPortInputCallback orig_input_cb;
  48. bool kbd_locked;
  49. uint32_t kbd_lock_msg_ticks;
  50. uint8_t kbd_lock_count;
  51. bool kbd_ok_input_ongoing;
  52. } ESubGhzChatState;
  53. typedef enum {
  54. ESubGhzChatScene_FreqInput,
  55. ESubGhzChatScene_PassInput,
  56. ESubGhzChatScene_ChatInput,
  57. ESubGhzChatScene_ChatBox,
  58. ESubGhzChatScene_MAX
  59. } ESubGhzChatScene;
  60. typedef enum {
  61. ESubGhzChatView_Input,
  62. ESubGhzChatView_ChatBox,
  63. } ESubGhzChatView;
  64. typedef enum {
  65. ESubGhzChatEvent_FreqEntered,
  66. ESubGhzChatEvent_PassEntered,
  67. ESubGhzChatEvent_MsgEntered
  68. } ESubGhzChatEvent;
  69. static void esubghz_chat_explicit_bzero(void *s, size_t len)
  70. {
  71. memset(s, 0, len);
  72. asm volatile("" ::: "memory");
  73. }
  74. static void post_rx(ESubGhzChatState *state, size_t rx_size)
  75. {
  76. furi_assert(state);
  77. if (rx_size == 0) {
  78. return;
  79. }
  80. furi_check(rx_size <= RX_TX_BUFFER_SIZE);
  81. if (!state->encrypted) {
  82. memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
  83. state->rx_str_buffer[rx_size] = 0;
  84. } else {
  85. if (rx_size < IV_BYTES + TAG_BYTES + 1) {
  86. return;
  87. }
  88. int ret = gcm_auth_decrypt(&(state->gcm_ctx),
  89. state->rx_buffer, IV_BYTES,
  90. NULL, 0,
  91. state->rx_buffer + IV_BYTES,
  92. (uint8_t *) state->rx_str_buffer,
  93. rx_size - (IV_BYTES + TAG_BYTES),
  94. state->rx_buffer + rx_size - TAG_BYTES,
  95. TAG_BYTES);
  96. state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
  97. if (ret != 0) {
  98. strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
  99. }
  100. }
  101. furi_string_cat_printf(state->chat_box_store, "\n%s",
  102. state->rx_str_buffer);
  103. notification_message(state->notification, &sequence_single_vibro);
  104. // Reset Text Box contents and focus
  105. text_box_set_text(state->chat_box,
  106. furi_string_get_cstr(state->chat_box_store));
  107. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  108. }
  109. static void freq_input_cb(void *context)
  110. {
  111. furi_assert(context);
  112. ESubGhzChatState* state = context;
  113. furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
  114. state->frequency);
  115. scene_manager_handle_custom_event(state->scene_manager,
  116. ESubGhzChatEvent_FreqEntered);
  117. }
  118. static bool freq_input_validator(const char *text, FuriString *error,
  119. void *context)
  120. {
  121. furi_assert(text);
  122. furi_assert(error);
  123. furi_assert(context);
  124. ESubGhzChatState* state = context;
  125. int ret = sscanf(text, "%lu", &(state->frequency));
  126. if (ret != 1) {
  127. furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
  128. return false;
  129. }
  130. if (!furi_hal_subghz_is_frequency_valid(state->frequency)) {
  131. furi_string_printf(error, "Frequency\n%lu\n is invalid!",
  132. state->frequency);
  133. return false;
  134. }
  135. if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
  136. furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
  137. state->frequency);
  138. return false;
  139. }
  140. return true;
  141. }
  142. static void pass_input_cb(void *context)
  143. {
  144. furi_assert(context);
  145. ESubGhzChatState* state = context;
  146. furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
  147. (state->encrypted ? "yes" : "no"));
  148. scene_manager_handle_custom_event(state->scene_manager,
  149. ESubGhzChatEvent_PassEntered);
  150. }
  151. static bool pass_input_validator(const char *text, FuriString *error,
  152. void *context)
  153. {
  154. furi_assert(text);
  155. furi_assert(error);
  156. furi_assert(context);
  157. ESubGhzChatState* state = context;
  158. if (strlen(text) == 0) {
  159. state->encrypted = false;
  160. return true;
  161. }
  162. unsigned char key[KEY_BITS / 8];
  163. state->encrypted = true;
  164. sha256((unsigned char *) text, strlen(text), key);
  165. // TODO: remove this
  166. furi_string_cat_printf(state->chat_box_store, "\nKey:");
  167. int i;
  168. for (i = 0; i < KEY_BITS / 8; i++) {
  169. furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
  170. }
  171. int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
  172. esubghz_chat_explicit_bzero(key, sizeof(key));
  173. if (ret != 0) {
  174. gcm_zero_ctx(&(state->gcm_ctx));
  175. furi_string_printf(error, "Failed to\nset key!");
  176. return false;
  177. }
  178. return true;
  179. }
  180. static void chat_input_cb(void *context)
  181. {
  182. furi_assert(context);
  183. ESubGhzChatState* state = context;
  184. if (strlen(state->text_input_store) == 0) {
  185. scene_manager_handle_custom_event(state->scene_manager,
  186. ESubGhzChatEvent_MsgEntered);
  187. return;
  188. }
  189. furi_string_set(state->msg_input, state->name_prefix);
  190. furi_string_cat_str(state->msg_input, state->text_input_store);
  191. furi_string_cat_printf(state->chat_box_store, "\n%s",
  192. furi_string_get_cstr(state->msg_input));
  193. size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
  194. size_t tx_size = msg_len;
  195. if (state->encrypted) {
  196. tx_size += IV_BYTES + TAG_BYTES;
  197. furi_check(tx_size <= sizeof(state->tx_buffer));
  198. furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
  199. gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
  200. state->tx_buffer, IV_BYTES,
  201. NULL, 0,
  202. (unsigned char *)
  203. furi_string_get_cstr(state->msg_input),
  204. state->tx_buffer + IV_BYTES,
  205. msg_len,
  206. state->tx_buffer + IV_BYTES + msg_len,
  207. TAG_BYTES);
  208. } else {
  209. furi_check(tx_size <= sizeof(state->tx_buffer));
  210. memcpy(state->tx_buffer,
  211. furi_string_get_cstr(state->msg_input),
  212. tx_size);
  213. }
  214. furi_string_set_char(state->msg_input, 0, 0);
  215. // TODO: remove this
  216. furi_string_cat_printf(state->chat_box_store, "\nTXed (HEX):");
  217. size_t i;
  218. for (i = 0; i < tx_size; i++) {
  219. furi_string_cat_printf(state->chat_box_store, " %02x",
  220. state->tx_buffer[i]);
  221. }
  222. // TODO: remove this
  223. state->last_time_rx_data = furi_get_tick();
  224. furi_stream_buffer_send(state->rx_collection_buffer,
  225. state->tx_buffer, tx_size, 0);
  226. // TODO: actually transmit
  227. scene_manager_handle_custom_event(state->scene_manager,
  228. ESubGhzChatEvent_MsgEntered);
  229. }
  230. static void scene_on_enter_freq_input(void* context)
  231. {
  232. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
  233. furi_assert(context);
  234. ESubGhzChatState* state = context;
  235. snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
  236. (uint32_t) DEFAULT_FREQ);
  237. text_input_reset(state->text_input);
  238. text_input_set_result_callback(
  239. state->text_input,
  240. freq_input_cb,
  241. state,
  242. state->text_input_store,
  243. sizeof(state->text_input_store),
  244. true);
  245. text_input_set_validator(
  246. state->text_input,
  247. freq_input_validator,
  248. state);
  249. text_input_set_header_text(
  250. state->text_input,
  251. "Frequency");
  252. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  253. }
  254. static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
  255. {
  256. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
  257. furi_assert(context);
  258. ESubGhzChatState* state = context;
  259. bool consumed = false;
  260. switch(event.type) {
  261. case SceneManagerEventTypeCustom:
  262. switch(event.event) {
  263. case ESubGhzChatEvent_FreqEntered:
  264. scene_manager_next_scene(state->scene_manager,
  265. ESubGhzChatScene_PassInput);
  266. consumed = true;
  267. break;
  268. }
  269. break;
  270. case SceneManagerEventTypeBack:
  271. view_dispatcher_stop(state->view_dispatcher);
  272. consumed = true;
  273. break;
  274. default:
  275. consumed = false;
  276. break;
  277. }
  278. return consumed;
  279. }
  280. static void scene_on_exit_freq_input(void* context)
  281. {
  282. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
  283. furi_assert(context);
  284. ESubGhzChatState* state = context;
  285. text_input_reset(state->text_input);
  286. }
  287. static void scene_on_enter_pass_input(void* context)
  288. {
  289. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
  290. furi_assert(context);
  291. ESubGhzChatState* state = context;
  292. state->text_input_store[0] = 0;
  293. text_input_reset(state->text_input);
  294. text_input_set_result_callback(
  295. state->text_input,
  296. pass_input_cb,
  297. state,
  298. state->text_input_store,
  299. sizeof(state->text_input_store),
  300. true);
  301. text_input_set_validator(
  302. state->text_input,
  303. pass_input_validator,
  304. state);
  305. text_input_set_header_text(
  306. state->text_input,
  307. "Password (empty for no encr.)");
  308. text_input_set_minimum_length(state->text_input, 0);
  309. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  310. }
  311. static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
  312. {
  313. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
  314. furi_assert(context);
  315. ESubGhzChatState* state = context;
  316. bool consumed = false;
  317. switch(event.type) {
  318. case SceneManagerEventTypeCustom:
  319. switch(event.event) {
  320. case ESubGhzChatEvent_PassEntered:
  321. scene_manager_next_scene(state->scene_manager,
  322. ESubGhzChatScene_ChatInput);
  323. consumed = true;
  324. break;
  325. }
  326. break;
  327. case SceneManagerEventTypeBack:
  328. view_dispatcher_stop(state->view_dispatcher);
  329. consumed = true;
  330. break;
  331. default:
  332. consumed = false;
  333. break;
  334. }
  335. return consumed;
  336. }
  337. static void scene_on_exit_pass_input(void* context)
  338. {
  339. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
  340. furi_assert(context);
  341. ESubGhzChatState* state = context;
  342. text_input_reset(state->text_input);
  343. }
  344. static void scene_on_enter_chat_input(void* context)
  345. {
  346. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
  347. furi_assert(context);
  348. ESubGhzChatState* state = context;
  349. state->text_input_store[0] = 0;
  350. text_input_reset(state->text_input);
  351. text_input_set_result_callback(
  352. state->text_input,
  353. chat_input_cb,
  354. state,
  355. state->text_input_store,
  356. sizeof(state->text_input_store),
  357. true);
  358. text_input_set_validator(
  359. state->text_input,
  360. NULL,
  361. NULL);
  362. text_input_set_header_text(
  363. state->text_input,
  364. "Message");
  365. text_input_set_minimum_length(state->text_input, 0);
  366. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  367. }
  368. static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
  369. {
  370. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
  371. furi_assert(context);
  372. ESubGhzChatState* state = context;
  373. bool consumed = false;
  374. switch(event.type) {
  375. case SceneManagerEventTypeCustom:
  376. switch(event.event) {
  377. case ESubGhzChatEvent_MsgEntered:
  378. scene_manager_next_scene(state->scene_manager,
  379. ESubGhzChatScene_ChatBox);
  380. consumed = true;
  381. break;
  382. }
  383. break;
  384. case SceneManagerEventTypeBack:
  385. view_dispatcher_stop(state->view_dispatcher);
  386. consumed = true;
  387. break;
  388. default:
  389. consumed = false;
  390. break;
  391. }
  392. return consumed;
  393. }
  394. static void scene_on_exit_chat_input(void* context)
  395. {
  396. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
  397. furi_assert(context);
  398. ESubGhzChatState* state = context;
  399. text_input_reset(state->text_input);
  400. }
  401. static void scene_on_enter_chat_box(void* context)
  402. {
  403. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
  404. furi_assert(context);
  405. ESubGhzChatState* state = context;
  406. text_box_reset(state->chat_box);
  407. text_box_set_text(state->chat_box,
  408. furi_string_get_cstr(state->chat_box_store));
  409. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  410. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  411. }
  412. static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
  413. {
  414. UNUSED(event);
  415. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
  416. furi_assert(context);
  417. return false;
  418. }
  419. static void scene_on_exit_chat_box(void* context)
  420. {
  421. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
  422. furi_assert(context);
  423. ESubGhzChatState* state = context;
  424. text_box_reset(state->chat_box);
  425. }
  426. static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
  427. scene_on_enter_freq_input,
  428. scene_on_enter_pass_input,
  429. scene_on_enter_chat_input,
  430. scene_on_enter_chat_box
  431. };
  432. static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
  433. scene_on_event_freq_input,
  434. scene_on_event_pass_input,
  435. scene_on_event_chat_input,
  436. scene_on_event_chat_box
  437. };
  438. static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
  439. scene_on_exit_freq_input,
  440. scene_on_exit_pass_input,
  441. scene_on_exit_chat_input,
  442. scene_on_exit_chat_box
  443. };
  444. static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
  445. .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
  446. .on_event_handlers = esubghz_chat_scene_on_event_handlers,
  447. .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
  448. .scene_num = ESubGhzChatScene_MAX};
  449. static bool kbd_lock_msg_display(ESubGhzChatState *state)
  450. {
  451. return (state->kbd_lock_msg_ticks != 0);
  452. }
  453. static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
  454. {
  455. if (state->kbd_lock_msg_ticks == 0) {
  456. return false;
  457. }
  458. if (furi_get_tick() - state->kbd_lock_msg_ticks > KBD_UNLOCK_TIMEOUT) {
  459. return true;
  460. }
  461. return false;
  462. }
  463. static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
  464. {
  465. state->kbd_lock_msg_ticks = 0;
  466. state->kbd_lock_count = 0;
  467. if (backlight_off) {
  468. notification_message(state->notification,
  469. &sequence_display_backlight_off);
  470. }
  471. }
  472. static void kbd_lock(ESubGhzChatState *state)
  473. {
  474. state->kbd_locked = true;
  475. kbd_lock_msg_reset(state, true);
  476. }
  477. static void kbd_unlock(ESubGhzChatState *state)
  478. {
  479. state->kbd_locked = false;
  480. kbd_lock_msg_reset(state, false);
  481. }
  482. static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
  483. {
  484. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
  485. furi_assert(context);
  486. ESubGhzChatState* state = context;
  487. return scene_manager_handle_custom_event(state->scene_manager, event);
  488. }
  489. static bool esubghz_chat_navigation_event_callback(void* context)
  490. {
  491. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
  492. furi_assert(context);
  493. ESubGhzChatState* state = context;
  494. return scene_manager_handle_back_event(state->scene_manager);
  495. }
  496. static void esubghz_chat_tick_event_callback(void* context)
  497. {
  498. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
  499. furi_assert(context);
  500. ESubGhzChatState* state = context;
  501. if (kbd_lock_msg_reset_timeout(state)) {
  502. kbd_lock_msg_reset(state, true);
  503. }
  504. size_t avail = furi_stream_buffer_bytes_available(
  505. state->rx_collection_buffer);
  506. if (avail > 0) {
  507. uint32_t since_last_rx = furi_get_tick() -
  508. state->last_time_rx_data;
  509. if (avail == RX_TX_BUFFER_SIZE || since_last_rx >
  510. MESSAGE_COMPLETION_TIMEOUT) {
  511. size_t rx_size = furi_stream_buffer_receive(
  512. state->rx_collection_buffer,
  513. state->rx_buffer,
  514. avail, 0);
  515. post_rx(state, rx_size);
  516. furi_stream_buffer_reset(state->rx_collection_buffer);
  517. }
  518. }
  519. scene_manager_handle_tick_event(state->scene_manager);
  520. }
  521. static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
  522. {
  523. FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
  524. furi_assert(canvas);
  525. furi_assert(context);
  526. ESubGhzChatState* state = context;
  527. state->orig_draw_cb(canvas, state->view_dispatcher);
  528. if (state->kbd_locked) {
  529. canvas_set_font(canvas, FontPrimary);
  530. elements_multiline_text_framed(canvas, 42, 30, "Locked");
  531. }
  532. if (kbd_lock_msg_display(state)) {
  533. canvas_set_font(canvas, FontSecondary);
  534. elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
  535. elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
  536. canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
  537. canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
  538. canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
  539. canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
  540. }
  541. }
  542. static void esubghz_hooked_input_callback(InputEvent* event, void* context)
  543. {
  544. FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
  545. furi_assert(event);
  546. furi_assert(context);
  547. ESubGhzChatState* state = context;
  548. if (state->kbd_locked) {
  549. if (state->kbd_lock_count == 0) {
  550. state->kbd_lock_msg_ticks = furi_get_tick();
  551. }
  552. if (event->key == InputKeyBack && event->type ==
  553. InputTypeShort) {
  554. state->kbd_lock_count++;
  555. }
  556. if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
  557. kbd_unlock(state);
  558. }
  559. // do not handle the event
  560. return;
  561. }
  562. if (event->key == InputKeyOk) {
  563. /* if we are in the chat view and no input is ongoing, allow
  564. * locking */
  565. if (state->view_dispatcher->current_view ==
  566. text_box_get_view(state->chat_box) &&
  567. !(state->kbd_ok_input_ongoing)) {
  568. if (event->type == InputTypeLong) {
  569. kbd_lock(state);
  570. }
  571. /* do not handle any Ok key events to prevent blocking
  572. * of other keys */
  573. return;
  574. }
  575. /* handle ongoing inputs when chaning to chat view */
  576. if (event->type == InputTypePress) {
  577. state->kbd_ok_input_ongoing = true;
  578. } else if (event->type == InputTypeRelease) {
  579. state->kbd_ok_input_ongoing = false;
  580. }
  581. }
  582. state->orig_input_cb(event, state->view_dispatcher);
  583. }
  584. static bool helper_strings_alloc(ESubGhzChatState *state)
  585. {
  586. furi_assert(state);
  587. state->name_prefix = furi_string_alloc();
  588. if (state->name_prefix == NULL) {
  589. return false;
  590. }
  591. state->msg_input = furi_string_alloc();
  592. if (state->msg_input == NULL) {
  593. furi_string_free(state->name_prefix);
  594. return false;
  595. }
  596. return true;
  597. }
  598. static void helper_strings_free(ESubGhzChatState *state)
  599. {
  600. furi_assert(state);
  601. furi_string_free(state->name_prefix);
  602. furi_string_free(state->msg_input);
  603. }
  604. static bool chat_box_alloc(ESubGhzChatState *state)
  605. {
  606. furi_assert(state);
  607. state->chat_box = text_box_alloc();
  608. if (state->chat_box == NULL) {
  609. return false;
  610. }
  611. state->chat_box_store = furi_string_alloc();
  612. if (state->chat_box_store == NULL) {
  613. text_box_free(state->chat_box);
  614. return false;
  615. }
  616. furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
  617. furi_string_set_char(state->chat_box_store, 0, 0);
  618. text_box_set_text(state->chat_box,
  619. furi_string_get_cstr(state->chat_box_store));
  620. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  621. return true;
  622. }
  623. static void chat_box_free(ESubGhzChatState *state)
  624. {
  625. furi_assert(state);
  626. text_box_free(state->chat_box);
  627. furi_string_free(state->chat_box_store);
  628. }
  629. int32_t esubghz_chat(void)
  630. {
  631. gcm_initialize();
  632. int32_t err = -1;
  633. FURI_LOG_I(APPLICATION_NAME, "Starting...");
  634. ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
  635. if (state == NULL) {
  636. goto err_alloc;
  637. }
  638. memset(state, 0, sizeof(*state));
  639. state->scene_manager = scene_manager_alloc(
  640. &esubghz_chat_scene_event_handlers, state);
  641. if (state->scene_manager == NULL) {
  642. goto err_alloc_sm;
  643. }
  644. state->view_dispatcher = view_dispatcher_alloc();
  645. if (state->view_dispatcher == NULL) {
  646. goto err_alloc_vd;
  647. }
  648. if (!helper_strings_alloc(state)) {
  649. goto err_alloc_hs;
  650. }
  651. state->text_input = text_input_alloc();
  652. if (state->text_input == NULL) {
  653. goto err_alloc_ti;
  654. }
  655. if (!chat_box_alloc(state)) {
  656. goto err_alloc_cb;
  657. }
  658. state->rx_collection_buffer = furi_stream_buffer_alloc(
  659. RX_TX_BUFFER_SIZE,
  660. RX_TX_BUFFER_SIZE);
  661. if (state->rx_collection_buffer == NULL) {
  662. goto err_alloc_rcb;
  663. }
  664. // set chat name prefix
  665. // TODO: handle escape chars here somehow
  666. furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
  667. furi_hal_version_get_name_ptr());
  668. /* no error handling here, don't know how */
  669. state->notification = furi_record_open(RECORD_NOTIFICATION);
  670. state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
  671. state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
  672. view_port_draw_callback_set(state->view_dispatcher->view_port,
  673. esubghz_hooked_draw_callback, state);
  674. view_port_input_callback_set(state->view_dispatcher->view_port,
  675. esubghz_hooked_input_callback, state);
  676. view_dispatcher_enable_queue(state->view_dispatcher);
  677. view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
  678. view_dispatcher_set_custom_event_callback(
  679. state->view_dispatcher,
  680. esubghz_chat_custom_event_callback);
  681. view_dispatcher_set_navigation_event_callback(
  682. state->view_dispatcher,
  683. esubghz_chat_navigation_event_callback);
  684. view_dispatcher_set_tick_event_callback(
  685. state->view_dispatcher,
  686. esubghz_chat_tick_event_callback,
  687. TICK_INTERVAL);
  688. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
  689. text_input_get_view(state->text_input));
  690. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
  691. text_box_get_view(state->chat_box));
  692. /* no error handling here, don't know how */
  693. Gui *gui = furi_record_open(RECORD_GUI);
  694. view_dispatcher_attach_to_gui(state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  695. scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
  696. view_dispatcher_run(state->view_dispatcher);
  697. err = 0;
  698. furi_record_close(RECORD_GUI);
  699. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_Input);
  700. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  701. furi_record_close(RECORD_NOTIFICATION);
  702. // clear the key and potential password
  703. esubghz_chat_explicit_bzero(state->text_input_store,
  704. sizeof(state->text_input_store));
  705. esubghz_chat_explicit_bzero(&(state->gcm_ctx), sizeof(state->gcm_ctx));
  706. furi_stream_buffer_free(state->rx_collection_buffer);
  707. err_alloc_rcb:
  708. chat_box_free(state);
  709. err_alloc_cb:
  710. text_input_free(state->text_input);
  711. err_alloc_ti:
  712. helper_strings_free(state);
  713. err_alloc_hs:
  714. view_dispatcher_free(state->view_dispatcher);
  715. err_alloc_vd:
  716. scene_manager_free(state->scene_manager);
  717. err_alloc_sm:
  718. free(state);
  719. err_alloc:
  720. if (err != 0) {
  721. FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
  722. } else {
  723. FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
  724. }
  725. return err;
  726. }