xremote_app.c 9.9 KB

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