xremote_app.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /*!
  2. * @file flipper-xremote/xremote_app.c
  3. @license This project is released under the GNU GPLv3 License
  4. * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
  5. *
  6. * @brief Shared functionality and data types between the apps.
  7. */
  8. #include "xremote_app.h"
  9. //////////////////////////////////////////////////////////////////////////////
  10. // XRemote generic functions and definitions
  11. //////////////////////////////////////////////////////////////////////////////
  12. #define XREMOTE_APP_SETTINGS APP_DATA_PATH("xremote.cfg")
  13. #define XREMOTE_ORIENTATION_TEXT_HORIZONTAL "Horizontal"
  14. #define XREMOTE_ORIENTATION_TEXT_VERTICAL "Vertical"
  15. #define XREMOTE_ORIENTATION_INDEX_HORIZONTAL 0
  16. #define XREMOTE_ORIENTATION_INDEX_VERTICAL 1
  17. #define XREMOTE_EXIT_BEHAVIOR_TEXT_PRESS "Press"
  18. #define XREMOTE_EXIT_BEHAVIOR_TEXT_HOLD "Hold"
  19. #define XREMOTE_EXIT_BEHAVIOR_INDEX_PRESS 0
  20. #define XREMOTE_EXIT_BEHAVIOR_INDEX_HOLD 1
  21. const NotificationSequence g_sequence_blink_purple_50 = {
  22. &message_red_255,
  23. &message_blue_255,
  24. &message_delay_50,
  25. NULL,
  26. };
  27. XRemoteAppExit xremote_app_get_exit_behavior(uint8_t exit_index) {
  28. return exit_index ? XRemoteAppExitHold : XRemoteAppExitPress;
  29. }
  30. ViewOrientation xremote_app_get_orientation(uint8_t orientation_index) {
  31. return orientation_index ? ViewOrientationVertical : ViewOrientationHorizontal;
  32. }
  33. const char* xremote_app_get_exit_str(XRemoteAppExit exit_behavior) {
  34. return exit_behavior == XRemoteAppExitPress ? "Press" : "Hold";
  35. }
  36. const char* xremote_app_get_alt_names_str(uint8_t alt_names_index) {
  37. return alt_names_index ? "On" : "Off";
  38. }
  39. const char* xremote_app_get_orientation_str(ViewOrientation view_orientation) {
  40. return view_orientation == ViewOrientationHorizontal ? "Horizontal" : "Vertical";
  41. }
  42. uint32_t xremote_app_get_orientation_index(ViewOrientation view_orientation) {
  43. return view_orientation == ViewOrientationHorizontal ? 0 : 1;
  44. }
  45. uint32_t xremote_app_get_exit_index(XRemoteAppExit exit_behavior) {
  46. return exit_behavior == XRemoteAppExitPress ? 0 : 1;
  47. }
  48. void xremote_app_notification_blink(NotificationApp* notifications) {
  49. xremote_app_assert_void(notifications);
  50. notification_message(notifications, &g_sequence_blink_purple_50);
  51. }
  52. //////////////////////////////////////////////////////////////////////////////
  53. // XRemote buttons and custom button pairs
  54. //////////////////////////////////////////////////////////////////////////////
  55. bool xremote_app_extension_load(XRemoteAppButtons* buttons, FuriString* path) {
  56. Storage* storage = furi_record_open(RECORD_STORAGE);
  57. FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
  58. FuriString* tmp = furi_string_alloc();
  59. bool success = false;
  60. do {
  61. /* Open file and read the header */
  62. if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
  63. if(!flipper_format_read_string(ff, "custom_ok", tmp)) break;
  64. furi_string_set(buttons->custom_ok, tmp);
  65. if(!flipper_format_read_string(ff, "custom_up", tmp)) break;
  66. furi_string_set(buttons->custom_up, tmp);
  67. if(!flipper_format_read_string(ff, "custom_down", tmp)) break;
  68. furi_string_set(buttons->custom_down, tmp);
  69. if(!flipper_format_read_string(ff, "custom_left", tmp)) break;
  70. furi_string_set(buttons->custom_left, tmp);
  71. if(!flipper_format_read_string(ff, "custom_right", tmp)) break;
  72. furi_string_set(buttons->custom_right, tmp);
  73. if(!flipper_format_read_string(ff, "custom_ok_hold", tmp)) break;
  74. furi_string_set(buttons->custom_ok_hold, tmp);
  75. if(!flipper_format_read_string(ff, "custom_up_hold", tmp)) break;
  76. furi_string_set(buttons->custom_up_hold, tmp);
  77. if(!flipper_format_read_string(ff, "custom_down_hold", tmp)) break;
  78. furi_string_set(buttons->custom_down_hold, tmp);
  79. if(!flipper_format_read_string(ff, "custom_left_hold", tmp)) break;
  80. furi_string_set(buttons->custom_left_hold, tmp);
  81. if(!flipper_format_read_string(ff, "custom_right_hold", tmp)) break;
  82. furi_string_set(buttons->custom_right_hold, tmp);
  83. success = true;
  84. } while(false);
  85. furi_record_close(RECORD_STORAGE);
  86. flipper_format_free(ff);
  87. furi_string_free(tmp);
  88. return success;
  89. }
  90. bool xremote_app_extension_store(XRemoteAppButtons* buttons, FuriString* path) {
  91. Storage* storage = furi_record_open(RECORD_STORAGE);
  92. FlipperFormat* ff = flipper_format_file_alloc(storage);
  93. bool success = false;
  94. do {
  95. if(!flipper_format_file_open_append(ff, furi_string_get_cstr(path))) break;
  96. if(!flipper_format_write_comment_cstr(ff, "XRemote extension")) break;
  97. if(!flipper_format_write_string(ff, "custom_ok", buttons->custom_ok)) break;
  98. if(!flipper_format_write_string(ff, "custom_up", buttons->custom_up)) break;
  99. if(!flipper_format_write_string(ff, "custom_down", buttons->custom_down)) break;
  100. if(!flipper_format_write_string(ff, "custom_left", buttons->custom_left)) break;
  101. if(!flipper_format_write_string(ff, "custom_right", buttons->custom_right)) break;
  102. if(!flipper_format_write_string(ff, "custom_ok_hold", buttons->custom_ok_hold)) break;
  103. if(!flipper_format_write_string(ff, "custom_up_hold", buttons->custom_up_hold)) break;
  104. if(!flipper_format_write_string(ff, "custom_down_hold", buttons->custom_down_hold)) break;
  105. if(!flipper_format_write_string(ff, "custom_left_hold", buttons->custom_left_hold)) break;
  106. if(!flipper_format_write_string(ff, "custom_right_hold", buttons->custom_right_hold))
  107. break;
  108. success = true;
  109. } while(false);
  110. furi_record_close(RECORD_STORAGE);
  111. flipper_format_free(ff);
  112. return success;
  113. }
  114. bool xremote_app_alt_names_check_and_init() {
  115. Storage* storage = furi_record_open(RECORD_STORAGE);
  116. FlipperFormat* ff = flipper_format_file_alloc(storage);
  117. bool success = false;
  118. do {
  119. if(!flipper_format_file_open_new(ff, XREMOTE_ALT_NAMES)) break;
  120. if(!flipper_format_write_header_cstr(ff, "XRemote Alt-Names", 1)) break;
  121. if(!flipper_format_write_comment_cstr(ff, "")) break;
  122. if(!flipper_format_write_string_cstr(ff, "Power", "shutdown,off,on,standby")) break;
  123. if(!flipper_format_write_string_cstr(ff, "Setup", "settings,config,cfg")) break;
  124. if(!flipper_format_write_string_cstr(ff, "Input", "source,select")) break;
  125. if(!flipper_format_write_string_cstr(ff, "Menu", "osd,gui")) break;
  126. if(!flipper_format_write_string_cstr(ff, "List", "guide")) break;
  127. if(!flipper_format_write_string_cstr(ff, "Info", "display")) break;
  128. if(!flipper_format_write_string_cstr(ff, "Mode", "aspect,format")) break;
  129. if(!flipper_format_write_string_cstr(ff, "Back", "return,exit")) break;
  130. if(!flipper_format_write_string_cstr(ff, "Ok", "enter,select")) break;
  131. if(!flipper_format_write_string_cstr(ff, "Up", "uparrow")) break;
  132. if(!flipper_format_write_string_cstr(ff, "Down", "downarrow")) break;
  133. if(!flipper_format_write_string_cstr(ff, "Left", "leftarrow")) break;
  134. if(!flipper_format_write_string_cstr(ff, "Right", "rightarrow")) break;
  135. if(!flipper_format_write_string_cstr(ff, "Mute", "silence,silent,unmute")) break;
  136. if(!flipper_format_write_string_cstr(ff, "Vol_up", "vol+,volume+,volup,+")) break;
  137. if(!flipper_format_write_string_cstr(ff, "Vol_dn", "vol-,volume-,voldown,-")) break;
  138. if(!flipper_format_write_string_cstr(ff, "Ch_next", "ch+,channel+,chup")) break;
  139. if(!flipper_format_write_string_cstr(ff, "Ch_prev", "ch-,channel-,chdown")) break;
  140. if(!flipper_format_write_string_cstr(ff, "Next", "next,skip,ffwd")) break;
  141. if(!flipper_format_write_string_cstr(ff, "Prev", "prev,back,rewind,rew")) break;
  142. if(!flipper_format_write_string_cstr(ff, "Fast_fo", "fastfwd,fastforward,ff")) break;
  143. if(!flipper_format_write_string_cstr(ff, "Fast_ba", "fastback,fastrewind,fb")) break;
  144. if(!flipper_format_write_string_cstr(ff, "Play_pa", "playpause,play,pause")) break;
  145. success = true;
  146. } while(false);
  147. furi_record_close(RECORD_STORAGE);
  148. flipper_format_free(ff);
  149. return success;
  150. }
  151. void xremote_app_buttons_free(XRemoteAppButtons* buttons) {
  152. xremote_app_assert_void(buttons);
  153. infrared_remote_free(buttons->remote);
  154. furi_string_free(buttons->custom_up);
  155. furi_string_free(buttons->custom_down);
  156. furi_string_free(buttons->custom_left);
  157. furi_string_free(buttons->custom_right);
  158. furi_string_free(buttons->custom_ok);
  159. furi_string_free(buttons->custom_up_hold);
  160. furi_string_free(buttons->custom_down_hold);
  161. furi_string_free(buttons->custom_left_hold);
  162. furi_string_free(buttons->custom_right_hold);
  163. furi_string_free(buttons->custom_ok_hold);
  164. free(buttons);
  165. }
  166. XRemoteAppButtons* xremote_app_buttons_alloc() {
  167. XRemoteAppButtons* buttons = malloc(sizeof(XRemoteAppButtons));
  168. buttons->remote = infrared_remote_alloc();
  169. buttons->app_ctx = NULL;
  170. /* Setup default buttons for custom layout */
  171. buttons->custom_up = furi_string_alloc_set_str(XREMOTE_COMMAND_UP);
  172. buttons->custom_down = furi_string_alloc_set_str(XREMOTE_COMMAND_DOWN);
  173. buttons->custom_left = furi_string_alloc_set_str(XREMOTE_COMMAND_LEFT);
  174. buttons->custom_right = furi_string_alloc_set_str(XREMOTE_COMMAND_RIGHT);
  175. buttons->custom_ok = furi_string_alloc_set_str(XREMOTE_COMMAND_OK);
  176. buttons->custom_up_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_INPUT);
  177. buttons->custom_down_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_SETUP);
  178. buttons->custom_left_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_MENU);
  179. buttons->custom_right_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_LIST);
  180. buttons->custom_ok_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_POWER);
  181. return buttons;
  182. }
  183. XRemoteAppButtons* xremote_app_buttons_load(XRemoteAppContext* app_ctx) {
  184. /* Show file selection dialog (returns selected file path with app_ctx->file_path) */
  185. if(!xremote_app_context_select_file(app_ctx, XREMOTE_APP_EXTENSION)) return NULL;
  186. XRemoteAppButtons* buttons = xremote_app_buttons_alloc();
  187. buttons->app_ctx = app_ctx;
  188. /* Load buttons from the selected path */
  189. if(!infrared_remote_load(buttons->remote, app_ctx->file_path)) {
  190. xremote_app_buttons_free(buttons);
  191. return NULL;
  192. }
  193. /* Load custom buttons from the selected path */
  194. xremote_app_extension_load(buttons, app_ctx->file_path);
  195. return buttons;
  196. }
  197. //////////////////////////////////////////////////////////////////////////////
  198. // XRemote application settings
  199. //////////////////////////////////////////////////////////////////////////////
  200. XRemoteAppSettings* xremote_app_settings_alloc() {
  201. XRemoteAppSettings* settings = malloc(sizeof(XRemoteAppSettings));
  202. settings->orientation = ViewOrientationHorizontal;
  203. settings->exit_behavior = XRemoteAppExitPress;
  204. settings->repeat_count = 2;
  205. settings->alt_names = 1;
  206. return settings;
  207. }
  208. void xremote_app_settings_free(XRemoteAppSettings* settings) {
  209. xremote_app_assert_void(settings);
  210. free(settings);
  211. }
  212. bool xremote_app_settings_store(XRemoteAppSettings* settings) {
  213. Storage* storage = furi_record_open(RECORD_STORAGE);
  214. FlipperFormat* ff = flipper_format_file_alloc(storage);
  215. FURI_LOG_I(XREMOTE_APP_TAG, "store config file: \'%s\'", XREMOTE_APP_SETTINGS);
  216. bool success = false;
  217. do {
  218. /* Write header in config file */
  219. if(!flipper_format_file_open_always(ff, XREMOTE_APP_SETTINGS)) break;
  220. if(!flipper_format_write_header_cstr(ff, "XRemote", 1)) break;
  221. if(!flipper_format_write_comment_cstr(ff, "")) break;
  222. /* Write actual configuration to the settings file */
  223. uint32_t value = xremote_app_get_orientation_index(settings->orientation);
  224. if(!flipper_format_write_uint32(ff, "orientation", &value, 1)) break;
  225. value = xremote_app_get_exit_index(settings->exit_behavior);
  226. if(!flipper_format_write_uint32(ff, "appexit", &value, 1)) break;
  227. value = settings->repeat_count;
  228. if(!flipper_format_write_uint32(ff, "repeat", &value, 1)) break;
  229. value = settings->alt_names;
  230. if(!flipper_format_write_uint32(ff, "altNames", &value, 1)) break;
  231. success = true;
  232. } while(false);
  233. furi_record_close(RECORD_STORAGE);
  234. flipper_format_free(ff);
  235. return success;
  236. }
  237. bool xremote_app_settings_load(XRemoteAppSettings* settings) {
  238. Storage* storage = furi_record_open(RECORD_STORAGE);
  239. FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
  240. FuriString* header = furi_string_alloc();
  241. FURI_LOG_I(XREMOTE_APP_TAG, "load config file: \'%s\'", XREMOTE_APP_SETTINGS);
  242. uint32_t version = 0;
  243. uint32_t value = 0;
  244. bool success = false;
  245. do {
  246. /* Open file and read the header */
  247. if(!flipper_format_buffered_file_open_existing(ff, XREMOTE_APP_SETTINGS)) break;
  248. if(!flipper_format_read_header(ff, header, &version)) break;
  249. if(!furi_string_equal(header, "XRemote") || (version != 1)) break;
  250. /* Parse config data from the buffer */
  251. if(!flipper_format_read_uint32(ff, "orientation", &value, 1)) break;
  252. settings->orientation = xremote_app_get_orientation(value);
  253. if(!flipper_format_read_uint32(ff, "appexit", &value, 1)) break;
  254. settings->exit_behavior = xremote_app_get_exit_behavior(value);
  255. if(!flipper_format_read_uint32(ff, "repeat", &value, 1)) break;
  256. settings->repeat_count = value;
  257. if(!flipper_format_read_uint32(ff, "altNames", &value, 1)) break;
  258. settings->alt_names = value;
  259. success = true;
  260. } while(false);
  261. furi_record_close(RECORD_STORAGE);
  262. furi_string_free(header);
  263. flipper_format_free(ff);
  264. return success;
  265. }
  266. //////////////////////////////////////////////////////////////////////////////
  267. // XRemote gloal context shared between every child application
  268. //////////////////////////////////////////////////////////////////////////////
  269. XRemoteAppContext* xremote_app_context_alloc(void* arg) {
  270. XRemoteAppContext* ctx = malloc(sizeof(XRemoteAppContext));
  271. ctx->app_argument = arg;
  272. ctx->file_path = NULL;
  273. /* Open GUI and norification records */
  274. ctx->gui = furi_record_open(RECORD_GUI);
  275. ctx->notifications = furi_record_open(RECORD_NOTIFICATION);
  276. /* Allocate and load global app settings */
  277. ctx->app_settings = xremote_app_settings_alloc();
  278. xremote_app_settings_load(ctx->app_settings);
  279. /* Initialize alternative names */
  280. if(ctx->app_settings->alt_names) xremote_app_alt_names_check_and_init();
  281. /* Allocate and setup view dispatcher */
  282. ctx->view_dispatcher = view_dispatcher_alloc();
  283. view_dispatcher_enable_queue(ctx->view_dispatcher);
  284. view_dispatcher_attach_to_gui(ctx->view_dispatcher, ctx->gui, ViewDispatcherTypeFullscreen);
  285. return ctx;
  286. }
  287. void xremote_app_context_free(XRemoteAppContext* ctx) {
  288. xremote_app_assert_void(ctx);
  289. notification_internal_message(ctx->notifications, &sequence_reset_blue);
  290. xremote_app_settings_free(ctx->app_settings);
  291. view_dispatcher_free(ctx->view_dispatcher);
  292. furi_record_close(RECORD_NOTIFICATION);
  293. furi_record_close(RECORD_GUI);
  294. if(ctx->file_path != NULL) {
  295. furi_string_free(ctx->file_path);
  296. ctx->file_path = NULL;
  297. }
  298. free(ctx);
  299. }
  300. bool xremote_app_browser_select_file(FuriString** file_path, const char* extension) {
  301. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  302. Storage* storage = furi_record_open(RECORD_STORAGE);
  303. storage_simply_mkdir(storage, XREMOTE_APP_FOLDER);
  304. if(*file_path == NULL) {
  305. *file_path = furi_string_alloc();
  306. furi_string_set(*file_path, XREMOTE_APP_FOLDER);
  307. }
  308. /* Open file browser (view and dialogs are managed by the browser itself) */
  309. DialogsFileBrowserOptions browser;
  310. dialog_file_browser_set_basic_options(&browser, extension, &I_IR_Icon_10x10);
  311. browser.base_path = XREMOTE_APP_FOLDER;
  312. /* Show file selection dialog (returns selected file path with file_path) */
  313. bool status = dialog_file_browser_show(dialogs, *file_path, *file_path, &browser);
  314. /* Cleanup file loading context */
  315. furi_record_close(RECORD_STORAGE);
  316. furi_record_close(RECORD_DIALOGS);
  317. return status;
  318. }
  319. bool xremote_app_context_select_file(XRemoteAppContext* app_ctx, const char* extension) {
  320. if(app_ctx == NULL) return false;
  321. return xremote_app_browser_select_file(&app_ctx->file_path, extension);
  322. }
  323. const char* xremote_app_context_get_exit_str(XRemoteAppContext* app_ctx) {
  324. XRemoteAppExit exit_behavior = app_ctx->app_settings->exit_behavior;
  325. return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
  326. }
  327. void xremote_app_context_notify_led(XRemoteAppContext* app_ctx) {
  328. xremote_app_assert_void(app_ctx);
  329. xremote_app_notification_blink(app_ctx->notifications);
  330. }
  331. bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal) {
  332. xremote_app_assert(signal, false);
  333. XRemoteAppSettings* settings = app_ctx->app_settings;
  334. infrared_signal_transmit_times(signal, settings->repeat_count);
  335. xremote_app_context_notify_led(app_ctx);
  336. return true;
  337. }
  338. //////////////////////////////////////////////////////////////////////////////
  339. // XRemote application factory
  340. //////////////////////////////////////////////////////////////////////////////
  341. void xremote_app_view_alloc(XRemoteApp* app, uint32_t view_id, XRemoteViewAllocator allocator) {
  342. furi_assert(app);
  343. xremote_app_assert_void(app->app_ctx);
  344. if(app->view_id == view_id && app->view_ctx != NULL) return;
  345. xremote_app_view_free(app);
  346. app->view_id = view_id;
  347. app->view_ctx = allocator(app->app_ctx);
  348. View* app_view = xremote_view_get_view(app->view_ctx);
  349. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  350. view_dispatcher_add_view(view_disp, app->view_id, app_view);
  351. }
  352. void xremote_app_view_alloc2(
  353. XRemoteApp* app,
  354. uint32_t view_id,
  355. XRemoteViewAllocator2 allocator,
  356. void* model_ctx) {
  357. furi_assert(app);
  358. xremote_app_assert_void(app->app_ctx);
  359. if(app->view_id == view_id && app->view_ctx != NULL) return;
  360. xremote_app_view_free(app);
  361. app->view_id = view_id;
  362. app->view_ctx = allocator(app->app_ctx, model_ctx);
  363. View* app_view = xremote_view_get_view(app->view_ctx);
  364. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  365. view_dispatcher_add_view(view_disp, app->view_id, app_view);
  366. }
  367. void xremote_app_view_free(XRemoteApp* app) {
  368. xremote_app_assert_void(app);
  369. if(app->app_ctx != NULL && app->view_id != XRemoteViewNone) {
  370. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  371. view_dispatcher_remove_view(view_disp, app->view_id);
  372. app->view_id = XRemoteViewNone;
  373. }
  374. if(app->view_ctx != NULL) {
  375. xremote_view_free(app->view_ctx);
  376. app->view_ctx = NULL;
  377. }
  378. }
  379. bool xremote_app_has_view(XRemoteApp* app, uint32_t view_id) {
  380. xremote_app_assert(app, false);
  381. return (app->view_id == view_id || app->submenu_id == view_id);
  382. }
  383. void xremote_app_switch_to_view(XRemoteApp* app, uint32_t view_id) {
  384. furi_assert(app);
  385. xremote_app_assert_void(app->app_ctx);
  386. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  387. view_dispatcher_switch_to_view(view_disp, view_id);
  388. }
  389. void xremote_app_switch_to_submenu(XRemoteApp* app) {
  390. furi_assert(app);
  391. xremote_app_assert_void(app->app_ctx);
  392. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  393. view_dispatcher_switch_to_view(view_disp, app->submenu_id);
  394. }
  395. void xremote_app_submenu_add(
  396. XRemoteApp* app,
  397. const char* name,
  398. uint32_t index,
  399. SubmenuItemCallback callback) {
  400. furi_assert(app);
  401. xremote_app_assert_void(app->submenu);
  402. submenu_add_item(app->submenu, name, index, callback, app);
  403. }
  404. void xremote_app_submenu_alloc(XRemoteApp* app, uint32_t index, ViewNavigationCallback prev_cb) {
  405. furi_assert(app);
  406. app->submenu = submenu_alloc();
  407. app->submenu_id = index;
  408. XRemoteAppSettings* settings = app->app_ctx->app_settings;
  409. View* view = submenu_get_view(app->submenu);
  410. view_set_previous_callback(view, prev_cb);
  411. #if defined(FW_ORIGIN_Unleashed) || defined(FW_ORIGIN_RM) || defined(FW_ORIGIN_Momentum)
  412. submenu_set_orientation(app->submenu, settings->orientation);
  413. #else
  414. view_set_orientation(view, settings->orientation);
  415. #endif
  416. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  417. view_dispatcher_add_view(view_disp, app->submenu_id, view);
  418. }
  419. void xremote_app_submenu_free(XRemoteApp* app) {
  420. xremote_app_assert_void(app);
  421. /* Remove submenu view from dispatcher */
  422. if(app->submenu_id != XRemoteViewNone && app->app_ctx != NULL) {
  423. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  424. view_dispatcher_remove_view(view_disp, app->submenu_id);
  425. app->submenu_id = XRemoteViewNone;
  426. }
  427. /* Free submenu */
  428. if(app->submenu != NULL) {
  429. submenu_free(app->submenu);
  430. app->submenu = NULL;
  431. }
  432. }
  433. void xremote_app_view_set_previous_callback(XRemoteApp* app, ViewNavigationCallback callback) {
  434. furi_assert(app);
  435. xremote_app_assert_void(app->view_ctx);
  436. View* view = xremote_view_get_view(app->view_ctx);
  437. view_set_previous_callback(view, callback);
  438. }
  439. void xremote_app_set_view_context(XRemoteApp* app, void* context, XRemoteClearCallback on_clear) {
  440. furi_assert(app);
  441. xremote_app_assert_void(app->view_ctx);
  442. xremote_view_set_context(app->view_ctx, context, on_clear);
  443. }
  444. void xremote_app_set_user_context(XRemoteApp* app, void* context, XRemoteClearCallback on_clear) {
  445. furi_assert(app);
  446. app->on_clear = on_clear;
  447. app->context = context;
  448. }
  449. void xremote_app_user_context_free(XRemoteApp* app) {
  450. furi_assert(app);
  451. xremote_app_assert_void(app->context);
  452. xremote_app_assert_void(app->on_clear);
  453. app->on_clear(app->context);
  454. app->on_clear = NULL;
  455. app->context = NULL;
  456. }
  457. XRemoteApp* xremote_app_alloc(XRemoteAppContext* ctx) {
  458. furi_assert(ctx);
  459. XRemoteApp* app = malloc(sizeof(XRemoteApp));
  460. xremote_app_assert(app, NULL);
  461. app->submenu_id = XRemoteViewNone;
  462. app->view_id = XRemoteViewNone;
  463. app->app_ctx = ctx;
  464. app->submenu = NULL;
  465. app->view_ctx = NULL;
  466. app->on_clear = NULL;
  467. app->context = NULL;
  468. return app;
  469. }
  470. void xremote_app_free(XRemoteApp* app) {
  471. xremote_app_assert_void(app);
  472. xremote_app_submenu_free(app);
  473. xremote_app_view_free(app);
  474. /* Call clear callback if there is an user context attached */
  475. if(app->on_clear != NULL) app->on_clear(app->context);
  476. free(app);
  477. }