xremote_app.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. #define XREMOTE_APP_SETTINGS ANY_PATH("infrared/assets/xremote.cfg")
  10. #define TAG "XRemoteApp"
  11. const NotificationSequence g_sequence_blink_purple_50 = {
  12. &message_red_255,
  13. &message_blue_255,
  14. &message_delay_50,
  15. NULL,
  16. };
  17. XRemoteAppSettings* xremote_app_settings_alloc()
  18. {
  19. XRemoteAppSettings* settings = malloc(sizeof(XRemoteAppSettings));
  20. settings->orientation = ViewOrientationHorizontal;
  21. settings->exit_behavior = XRemoteAppExitPress;
  22. settings->repeat_count = 2;
  23. return settings;
  24. }
  25. void xremote_app_settings_free(XRemoteAppSettings* settings)
  26. {
  27. xremote_app_assert_void(settings);
  28. free(settings);
  29. }
  30. bool xremote_app_settings_store(XRemoteAppSettings* settings)
  31. {
  32. Storage* storage = furi_record_open(RECORD_STORAGE);
  33. FlipperFormat* ff = flipper_format_file_alloc(storage);
  34. FURI_LOG_I(TAG, "store config file: \'%s\'", XREMOTE_APP_SETTINGS);
  35. bool success = false;
  36. do {
  37. /* Write header in config file */
  38. if (!flipper_format_file_open_always(ff, XREMOTE_APP_SETTINGS)) break;
  39. if (!flipper_format_write_header_cstr(ff, "XRemote settings file", 1)) break;
  40. if (!flipper_format_write_comment_cstr(ff, "")) break;
  41. /* Write actual configuration to the settings file */
  42. if (!flipper_format_write_uint32(ff, "orientation", (uint32_t*)&settings->orientation, 1)) break;
  43. if (!flipper_format_write_uint32(ff, "repeat", (uint32_t*)&settings->repeat_count, 1)) break;
  44. if (!flipper_format_write_uint32(ff, "exit", (uint32_t*)&settings->exit_behavior, 1)) break;
  45. success = true;
  46. } while(false);
  47. furi_record_close(RECORD_STORAGE);
  48. flipper_format_free(ff);
  49. return success;
  50. }
  51. bool xremote_app_settings_load(XRemoteAppSettings* settings)
  52. {
  53. Storage* storage = furi_record_open(RECORD_STORAGE);
  54. FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
  55. FuriString* header = furi_string_alloc();
  56. FURI_LOG_I(TAG, "load config file: \'%s\'", XREMOTE_APP_SETTINGS);
  57. uint32_t version = 0;
  58. uint32_t value = 0;
  59. bool success = false;
  60. do {
  61. /* Open file and read the header */
  62. if (!flipper_format_buffered_file_open_existing(ff, XREMOTE_APP_SETTINGS)) break;
  63. if (!flipper_format_read_header(ff, header, &version)) break;
  64. if (!furi_string_equal(header, "XRemote settings file") || (version != 1)) break;
  65. /* Parse config data from the buffer */
  66. if (!flipper_format_read_uint32(ff, "orientation", &value, 1)) break;
  67. settings->orientation = value;
  68. if (!flipper_format_read_uint32(ff, "repeat", &value, 1)) break;
  69. settings->repeat_count = value;
  70. if (!flipper_format_read_uint32(ff, "exit", &value, 1)) break;
  71. settings->exit_behavior = value;
  72. success = true;
  73. } while(false);
  74. furi_record_close(RECORD_STORAGE);
  75. furi_string_free(header);
  76. flipper_format_free(ff);
  77. return success;
  78. }
  79. XRemoteAppContext* xremote_app_context_alloc(void *arg)
  80. {
  81. XRemoteAppContext* ctx = malloc(sizeof(XRemoteAppContext));
  82. ctx->app_argument = arg;
  83. ctx->file_path = NULL;
  84. /* Open GUI and norification records */
  85. ctx->gui = furi_record_open(RECORD_GUI);
  86. ctx->notifications = furi_record_open(RECORD_NOTIFICATION);
  87. /* Allocate and load global app settings */
  88. ctx->app_settings = xremote_app_settings_alloc();
  89. xremote_app_settings_load(ctx->app_settings);
  90. /* Allocate and setup view dispatcher */
  91. ctx->view_dispatcher = view_dispatcher_alloc();
  92. view_dispatcher_enable_queue(ctx->view_dispatcher);
  93. view_dispatcher_attach_to_gui(ctx->view_dispatcher, ctx->gui, ViewDispatcherTypeFullscreen);
  94. return ctx;
  95. }
  96. void xremote_app_context_free(XRemoteAppContext* ctx)
  97. {
  98. xremote_app_assert_void(ctx);
  99. notification_internal_message(ctx->notifications, &sequence_reset_blue);
  100. xremote_app_settings_free(ctx->app_settings);
  101. view_dispatcher_free(ctx->view_dispatcher);
  102. furi_record_close(RECORD_NOTIFICATION);
  103. furi_record_close(RECORD_GUI);
  104. if (ctx->file_path != NULL)
  105. {
  106. furi_string_free(ctx->file_path);
  107. ctx->file_path = NULL;
  108. }
  109. free(ctx);
  110. }
  111. const char* xremote_app_context_get_exit_str(XRemoteAppContext* ctx)
  112. {
  113. XRemoteAppExit exit_behavior = ctx->app_settings->exit_behavior;
  114. return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
  115. }
  116. void xremote_app_notification_blink(NotificationApp* notifications)
  117. {
  118. xremote_app_assert_void(notifications);
  119. notification_message(notifications, &g_sequence_blink_purple_50);
  120. }
  121. void xremote_app_context_notify_led(XRemoteAppContext* app_ctx)
  122. {
  123. xremote_app_assert_void(app_ctx);
  124. xremote_app_notification_blink(app_ctx->notifications);
  125. }
  126. bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal)
  127. {
  128. xremote_app_assert(signal, false);
  129. XRemoteAppSettings* settings = app_ctx->app_settings;
  130. infrared_signal_transmit_times(signal, settings->repeat_count);
  131. xremote_app_context_notify_led(app_ctx);
  132. return true;
  133. }
  134. void xremote_app_view_alloc(XRemoteApp *app, uint32_t view_id, XRemoteViewAllocator allocator)
  135. {
  136. furi_assert(app);
  137. xremote_app_assert_void(app->app_ctx);
  138. if (app->view_id == view_id &&
  139. app->view_ctx != NULL) return;
  140. xremote_app_view_free(app);
  141. app->view_id = view_id;
  142. app->view_ctx = allocator(app->app_ctx);
  143. View* app_view = xremote_view_get_view(app->view_ctx);
  144. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  145. view_dispatcher_add_view(view_disp, app->view_id, app_view);
  146. }
  147. void xremote_app_view_free(XRemoteApp* app)
  148. {
  149. xremote_app_assert_void(app);
  150. if (app->app_ctx != NULL && app->view_id != XRemoteViewNone)
  151. {
  152. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  153. view_dispatcher_remove_view(view_disp, app->view_id);
  154. app->view_id = XRemoteViewNone;
  155. }
  156. if (app->view_ctx != NULL)
  157. {
  158. xremote_view_free(app->view_ctx);
  159. app->view_ctx = NULL;
  160. }
  161. }
  162. bool xremote_app_has_view(XRemoteApp *app, uint32_t view_id)
  163. {
  164. xremote_app_assert(app, false);
  165. return (app->view_id == view_id ||
  166. app->submenu_id == view_id);
  167. }
  168. void xremote_app_switch_to_view(XRemoteApp *app, uint32_t view_id)
  169. {
  170. furi_assert(app);
  171. xremote_app_assert_void(app->app_ctx);
  172. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  173. view_dispatcher_switch_to_view(view_disp, view_id);
  174. }
  175. void xremote_app_switch_to_submenu(XRemoteApp *app)
  176. {
  177. furi_assert(app);
  178. xremote_app_assert_void(app->app_ctx);
  179. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  180. view_dispatcher_switch_to_view(view_disp, app->submenu_id);
  181. }
  182. void xremote_app_submenu_add(XRemoteApp* app, const char *name, uint32_t index, SubmenuItemCallback callback)
  183. {
  184. furi_assert(app);
  185. xremote_app_assert_void(app->submenu);
  186. submenu_add_item(app->submenu, name, index, callback, app);
  187. }
  188. void xremote_app_submenu_alloc(XRemoteApp* app, uint32_t index, ViewNavigationCallback prev_cb)
  189. {
  190. furi_assert(app);
  191. app->submenu = submenu_alloc();
  192. app->submenu_id = index;
  193. XRemoteAppSettings *settings = app->app_ctx->app_settings;
  194. submenu_set_orientation(app->submenu, settings->orientation);
  195. view_set_previous_callback(submenu_get_view(app->submenu), prev_cb);
  196. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  197. view_dispatcher_add_view(view_disp, app->submenu_id, submenu_get_view(app->submenu));
  198. }
  199. void xremote_app_submenu_free(XRemoteApp *app)
  200. {
  201. xremote_app_assert_void(app);
  202. /* Remove submenu view from dispatcher */
  203. if (app->submenu_id != XRemoteViewNone && app->app_ctx != NULL)
  204. {
  205. ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
  206. view_dispatcher_remove_view(view_disp, app->submenu_id);
  207. app->submenu_id = XRemoteViewNone;
  208. }
  209. /* Free submenu */
  210. if (app->submenu != NULL)
  211. {
  212. submenu_free(app->submenu);
  213. app->submenu = NULL;
  214. }
  215. }
  216. void xremote_app_view_set_previous_callback(XRemoteApp* app, ViewNavigationCallback callback)
  217. {
  218. furi_assert(app);
  219. xremote_app_assert_void(app->view_ctx);
  220. View* view = xremote_view_get_view(app->view_ctx);
  221. view_set_previous_callback(view, callback);
  222. }
  223. void xremote_app_set_view_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear)
  224. {
  225. furi_assert(app);
  226. xremote_app_assert_void(app->view_ctx);
  227. xremote_view_set_context(app->view_ctx, context, on_clear);
  228. }
  229. void xremote_app_set_user_context(XRemoteApp* app, void *context, XRemoteClearCallback on_clear)
  230. {
  231. furi_assert(app);
  232. app->on_clear = on_clear;
  233. app->context = context;
  234. }
  235. void xremote_app_user_context_free(XRemoteApp* app)
  236. {
  237. furi_assert(app);
  238. xremote_app_assert_void(app->context);
  239. xremote_app_assert_void(app->on_clear);
  240. app->on_clear(app->context);
  241. app->on_clear = NULL;
  242. app->context = NULL;
  243. }
  244. XRemoteApp* xremote_app_alloc(XRemoteAppContext *ctx)
  245. {
  246. furi_assert(ctx);
  247. XRemoteApp* app = malloc(sizeof(XRemoteApp));
  248. app->submenu_id = XRemoteViewNone;
  249. app->view_id = XRemoteViewNone;
  250. app->app_ctx = ctx;
  251. app->submenu = NULL;
  252. app->view_ctx = NULL;
  253. app->on_clear = NULL;
  254. app->context = NULL;
  255. return app;
  256. }
  257. void xremote_app_free(XRemoteApp* app)
  258. {
  259. xremote_app_assert_void(app);
  260. xremote_app_submenu_free(app);
  261. xremote_app_view_free(app);
  262. if (app->on_clear != NULL)
  263. app->on_clear(app->context);
  264. free(app);
  265. }