key_copier.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <gui/view.h>
  5. #include <gui/view_dispatcher.h>
  6. #include <gui/modules/submenu.h>
  7. #include <gui/modules/text_input.h>
  8. #include <gui/modules/widget.h>
  9. #include <gui/modules/variable_item_list.h>
  10. #include <notification/notification.h>
  11. #include <notification/notification_messages.h>
  12. #include <applications/services/storage/storage.h>
  13. #include <applications/services/dialogs/dialogs.h>
  14. #include <stdbool.h>
  15. #include <math.h>
  16. #include <flipper_format.h>
  17. #include "key_copier_icons.h"
  18. #include "key_formats.h"
  19. #include "key_copier.h"
  20. #define TAG "KeyCopier"
  21. #define INCHES_PER_PX 0.00978
  22. // Change this to BACKLIGHT_AUTO if you don't want the backlight to be continuously on.
  23. #define BACKLIGHT_ON 1
  24. // Our application menu has 3 items. You can add more items if you want.
  25. typedef enum {
  26. KeyCopierSubmenuIndexMeasure,
  27. KeyCopierSubmenuIndexConfigure,
  28. KeyCopierSubmenuIndexSave,
  29. KeyCopierSubmenuIndexLoad,
  30. KeyCopierSubmenuIndexAbout,
  31. } KeyCopierSubmenuIndex;
  32. // Each view is a screen we show the user.
  33. typedef enum {
  34. KeyCopierViewSubmenu, // The menu when the app starts
  35. KeyCopierViewTextInput, // Input for configuring text settings
  36. KeyCopierViewConfigure_i, // The configuration screen
  37. KeyCopierViewConfigure_e, // The configuration screen
  38. KeyCopierViewSave,
  39. KeyCopierViewLoad,
  40. KeyCopierViewMeasure, // The main screen
  41. KeyCopierViewAbout, // The about screen with directions, link to social channel, etc.
  42. } KeyCopierView;
  43. typedef enum {
  44. KeyCopierEventIdRedrawScreen = 0, // Custom event to redraw the screen
  45. } KeyCopierEventId;
  46. typedef struct {
  47. ViewDispatcher* view_dispatcher; // Switches between our views
  48. NotificationApp* notifications; // Used for controlling the backlight
  49. Submenu* submenu; // The application menu
  50. TextInput* text_input; // The text input screen
  51. VariableItemList* variable_item_list_config; // The configuration screen
  52. View* view_measure; // The main screen
  53. View* view_config_e; // The configuration screen
  54. View* view_save;
  55. View* view_load; // The load view
  56. Widget* widget_about; // The about screen
  57. VariableItem* key_name_item; // The name setting item (so we can update the text)
  58. VariableItem* format_item;
  59. char* temp_buffer; // Temporary buffer for text input
  60. uint32_t temp_buffer_size; // Size of temporary buffer
  61. DialogsApp* dialogs;
  62. FuriString* file_path;
  63. FuriTimer* timer; // Timer for redrawing the screen
  64. } KeyCopierApp;
  65. typedef struct {
  66. uint32_t format_index; // The index for total number of pins
  67. FuriString* key_name_str; // The name setting
  68. uint8_t pin_slc; // The pin that is being adjusted
  69. uint8_t* depth; // The cutting depth
  70. bool data_loaded;
  71. KeyFormat format;
  72. } KeyCopierModel;
  73. void initialize_model(KeyCopierModel* model) {
  74. if(model->depth != NULL) {
  75. free(model->depth);
  76. }
  77. model->format_index = 0;
  78. memcpy(&model->format, &all_formats[model->format_index], sizeof(KeyFormat));
  79. model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
  80. for(uint8_t i = 0; i <= model->format.pin_num; i++) {
  81. model->depth[i] = model->format.min_depth_ind;
  82. }
  83. model->pin_slc = 1;
  84. model->data_loaded = 0;
  85. model->key_name_str = furi_string_alloc();
  86. }
  87. /**
  88. * @brief Callback for exiting the application.
  89. * @details This function is called when user press back button. We return VIEW_NONE to
  90. * indicate that we want to exit the application.
  91. * @param _context The context - unused
  92. * @return next view id
  93. */
  94. static uint32_t key_copier_navigation_exit_callback(void* _context) {
  95. UNUSED(_context);
  96. return VIEW_NONE;
  97. }
  98. /**
  99. * @brief Callback for returning to submenu.
  100. * @details This function is called when user press back button. We return VIEW_NONE to
  101. * indicate that we want to navigate to the submenu.
  102. * @param _context The context - unused
  103. * @return next view id
  104. */
  105. static uint32_t key_copier_navigation_submenu_callback(void* _context) {
  106. UNUSED(_context);
  107. return KeyCopierViewSubmenu;
  108. }
  109. /**
  110. * @brief Handle submenu item selection.
  111. * @details This function is called when user selects an item from the submenu.
  112. * @param context The context - KeyCopierApp object.
  113. * @param index The KeyCopierSubmenuIndex item that was clicked.
  114. */
  115. static void key_copier_submenu_callback(void* context, uint32_t index) {
  116. KeyCopierApp* app = (KeyCopierApp*)context;
  117. switch(index) {
  118. case KeyCopierSubmenuIndexMeasure:
  119. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewMeasure);
  120. break;
  121. case KeyCopierSubmenuIndexConfigure:
  122. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_e);
  123. break;
  124. case KeyCopierSubmenuIndexSave:
  125. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSave);
  126. break;
  127. case KeyCopierSubmenuIndexLoad:
  128. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewLoad);
  129. break;
  130. case KeyCopierSubmenuIndexAbout:
  131. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewAbout);
  132. break;
  133. default:
  134. break;
  135. }
  136. }
  137. char* manufacturers[COUNT_OF(all_formats)];
  138. void initialize_manufacturers(char** manufacturers) {
  139. // Populate the manufacturers array
  140. for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
  141. manufacturers[i] = all_formats[i].manufacturer;
  142. }
  143. }
  144. static void key_copier_format_change(VariableItem* item) {
  145. KeyCopierApp* app = variable_item_get_context(item);
  146. KeyCopierModel* model = view_get_model(app->view_measure);
  147. if(model->data_loaded) {
  148. variable_item_set_current_value_index(item, model->format_index);
  149. }
  150. uint8_t format_index = variable_item_get_current_value_index(item);
  151. if(format_index != model->format_index) {
  152. model->format_index = format_index;
  153. model->format = all_formats[format_index];
  154. if(model->depth != NULL) {
  155. free(model->depth);
  156. }
  157. model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
  158. for(uint8_t i = 0; i <= model->format.pin_num; i++) {
  159. model->depth[i] = model->format.min_depth_ind;
  160. }
  161. model->pin_slc = 1;
  162. }
  163. model->data_loaded = false;
  164. variable_item_set_current_value_text(item, model->format.format_name);
  165. model->format = all_formats[model->format_index];
  166. }
  167. static const char* format_config_label = "Key Format";
  168. static void key_copier_config_enter_callback(void* context) {
  169. KeyCopierApp* app = (KeyCopierApp*)context;
  170. KeyCopierModel* my_model = view_get_model(app->view_measure);
  171. variable_item_list_reset(app->variable_item_list_config);
  172. // Recreate this view every time we enter it so that it's always updated
  173. app->format_item = variable_item_list_add(
  174. app->variable_item_list_config,
  175. format_config_label,
  176. COUNT_OF(all_formats),
  177. key_copier_format_change,
  178. app);
  179. View* view_config_i = variable_item_list_get_view(app->variable_item_list_config);
  180. variable_item_set_current_value_index(app->format_item, my_model->format_index);
  181. key_copier_format_change(app->format_item);
  182. view_set_previous_callback(view_config_i, key_copier_navigation_submenu_callback);
  183. view_dispatcher_remove_view(
  184. app->view_dispatcher, KeyCopierViewConfigure_i); // delete the last one
  185. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_config_i);
  186. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_i); // recreate it
  187. }
  188. static const char* key_name_entry_text = "Enter name";
  189. static void key_copier_file_saver(void* context) {
  190. KeyCopierApp* app = (KeyCopierApp*)context;
  191. KeyCopierModel* model = view_get_model(app->view_measure);
  192. bool redraw = true;
  193. with_view_model(
  194. app->view_measure,
  195. KeyCopierModel * model,
  196. { furi_string_set(model->key_name_str, app->temp_buffer); },
  197. redraw);
  198. FuriString* file_path = furi_string_alloc();
  199. furi_string_printf(
  200. file_path,
  201. "%s/%s%s",
  202. STORAGE_APP_DATA_PATH_PREFIX,
  203. furi_string_get_cstr(model->key_name_str),
  204. KEY_COPIER_FILE_EXTENSION);
  205. Storage* storage = furi_record_open(RECORD_STORAGE);
  206. storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
  207. FURI_LOG_D(TAG, "mkdir finished");
  208. FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
  209. do {
  210. const uint32_t version = 1;
  211. const uint32_t pin_num_buffer = (uint32_t)model->format.pin_num;
  212. const uint32_t macs_buffer = (uint32_t)model->format.macs;
  213. FuriString* buffer = furi_string_alloc();
  214. if(!flipper_format_file_open_always(flipper_format, furi_string_get_cstr(file_path)))
  215. break;
  216. if(!flipper_format_write_header_cstr(flipper_format, "Flipper Key Copier File", version))
  217. break;
  218. if(!flipper_format_write_string_cstr(
  219. flipper_format, "Manufacturer", model->format.manufacturer))
  220. break;
  221. if(!flipper_format_write_string_cstr(
  222. flipper_format, "Format Name", model->format.format_name))
  223. break;
  224. if(!flipper_format_write_string_cstr(
  225. flipper_format, "Data Sheet", model->format.format_link))
  226. break;
  227. if(!flipper_format_write_uint32(flipper_format, "Number of Pins", &pin_num_buffer, 1))
  228. break;
  229. if(!flipper_format_write_uint32(
  230. flipper_format, "Maximum Adjacent Cut Specification (MACS)", &macs_buffer, 1))
  231. break;
  232. for(int i = 0; i < model->format.pin_num; i++) {
  233. if(i < model->format.pin_num - 1) {
  234. furi_string_cat_printf(buffer, "%d-", model->depth[i]);
  235. } else {
  236. furi_string_cat_printf(buffer, "%d", model->depth[i]);
  237. }
  238. }
  239. if(!flipper_format_write_string(flipper_format, "Bitting Pattern", buffer)) break;
  240. furi_string_free(buffer);
  241. // signal that the file was written successfully
  242. } while(0);
  243. flipper_format_free(flipper_format);
  244. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
  245. }
  246. /**
  247. * @brief Callback when item in configuration screen is clicked.
  248. * @details This function is called when user clicks OK on an item in the configuration screen.
  249. * If the item clicked is our text field then we switch to the text input screen.
  250. * @param context The context - KeyCopierApp object.
  251. * @param index - The index of the item that was clicked.
  252. */
  253. static void key_copier_view_save_callback(void* context) {
  254. KeyCopierApp* app = (KeyCopierApp*)context;
  255. // Header to display on the text input screen.
  256. text_input_set_header_text(app->text_input, key_name_entry_text);
  257. // Copy the current name into the temporary buffer.
  258. bool redraw = false;
  259. with_view_model(
  260. app->view_measure,
  261. KeyCopierModel * model,
  262. {
  263. strncpy(
  264. app->temp_buffer,
  265. furi_string_get_cstr(model->key_name_str),
  266. app->temp_buffer_size);
  267. },
  268. redraw);
  269. // Configure the text input. When user enters text and clicks OK, key_copier_setting_text_updated be called.
  270. bool clear_previous_text = false;
  271. text_input_set_result_callback(
  272. app->text_input,
  273. key_copier_file_saver,
  274. app,
  275. app->temp_buffer,
  276. app->temp_buffer_size,
  277. clear_previous_text);
  278. // Pressing the BACK button will reload the configure screen.
  279. view_set_previous_callback(
  280. text_input_get_view(app->text_input), key_copier_navigation_submenu_callback);
  281. // Show text input dialog.
  282. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewTextInput);
  283. }
  284. static void key_copier_view_load_callback(void* context) {
  285. KeyCopierApp* app = (KeyCopierApp*)context;
  286. KeyCopierModel* model = view_get_model(app->view_measure);
  287. DialogsFileBrowserOptions browser_options;
  288. Storage* storage = furi_record_open(RECORD_STORAGE);
  289. storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
  290. dialog_file_browser_set_basic_options(&browser_options, KEY_COPIER_FILE_EXTENSION, &I_icon);
  291. browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
  292. furi_string_set(app->file_path, browser_options.base_path);
  293. if(dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
  294. FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
  295. do {
  296. if(!flipper_format_file_open_existing(
  297. flipper_format, furi_string_get_cstr(app->file_path)))
  298. break;
  299. FuriString* format_buffer = furi_string_alloc();
  300. FuriString* depth_buffer = furi_string_alloc();
  301. if(!flipper_format_read_string(flipper_format, "Format Name", format_buffer)) break;
  302. if(!flipper_format_read_string(flipper_format, "Bitting Pattern", depth_buffer)) break;
  303. for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
  304. if(!strcmp(furi_string_get_cstr(format_buffer), all_formats[i].format_name)) {
  305. model->format_index = (uint32_t)i;
  306. model->format = all_formats[model->format_index];
  307. }
  308. }
  309. for(int i = 0; i < model->format.pin_num; i++) {
  310. model->depth[i] = (uint8_t)(furi_string_get_char(depth_buffer, i * 2) - '0');
  311. }
  312. model->data_loaded = true;
  313. // signal that the file was read successfully
  314. } while(0);
  315. flipper_format_free(flipper_format);
  316. furi_record_close(RECORD_STORAGE);
  317. }
  318. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
  319. }
  320. /**
  321. * @brief Callback for drawing the game screen.
  322. * @details This function is called when the screen needs to be redrawn, like when the model gets updated.
  323. * @param canvas The canvas to draw on.
  324. * @param model The model - MyModel object.
  325. */
  326. static void key_copier_view_measure_draw_callback(Canvas* canvas, void* model) {
  327. static double inches_per_px = (double)INCHES_PER_PX;
  328. canvas_set_bitmap_mode(canvas, true);
  329. KeyCopierModel* my_model = (KeyCopierModel*)model;
  330. KeyFormat my_format = my_model->format;
  331. FuriString* buffer = furi_string_alloc();
  332. int pin_half_width_px = (int)round((my_format.pin_width_inch / inches_per_px) / 2);
  333. int pin_step_px = (int)round(my_format.pin_increment_inch / inches_per_px);
  334. double drill_radians =
  335. (180 - my_format.drill_angle) / 2 / 180 * (double)M_PI; // Convert angle to radians
  336. double tangent = tan(drill_radians);
  337. int top_contour_px = (int)round(63 - my_format.uncut_depth_inch / inches_per_px);
  338. int post_extra_x_px = 0;
  339. int pre_extra_x_px = 0;
  340. for(int current_pin = 1; current_pin <= my_model->format.pin_num; current_pin += 1) {
  341. double current_center_px =
  342. my_format.first_pin_inch + (current_pin - 1) * my_format.pin_increment_inch;
  343. int pin_center_px = (int)round(current_center_px / inches_per_px);
  344. furi_string_printf(buffer, "%d", my_model->depth[current_pin - 1]);
  345. canvas_draw_str_aligned(
  346. canvas,
  347. pin_center_px,
  348. top_contour_px - 12,
  349. AlignCenter,
  350. AlignCenter,
  351. furi_string_get_cstr(buffer));
  352. canvas_draw_line(
  353. canvas,
  354. pin_center_px,
  355. top_contour_px - 5,
  356. pin_center_px,
  357. top_contour_px); // the vertical line to indicate pin center
  358. int current_depth = my_model->depth[current_pin - 1] - my_format.min_depth_ind;
  359. int current_depth_px =
  360. (int)round(current_depth * my_format.depth_step_inch / inches_per_px);
  361. canvas_draw_line(
  362. canvas,
  363. pin_center_px - pin_half_width_px,
  364. top_contour_px + current_depth_px,
  365. pin_center_px + pin_half_width_px,
  366. top_contour_px + current_depth_px); // draw pin width horizontal line
  367. int last_depth = my_model->depth[current_pin - 2] - my_format.min_depth_ind;
  368. int next_depth = my_model->depth[current_pin] - my_format.min_depth_ind;
  369. if(current_pin == 1) {
  370. canvas_draw_line(
  371. canvas,
  372. 0,
  373. top_contour_px,
  374. pin_center_px - pin_half_width_px - current_depth_px,
  375. top_contour_px);
  376. last_depth = 0;
  377. pre_extra_x_px = max(current_depth_px + pin_half_width_px, 0);
  378. }
  379. if(current_pin == my_model->format.pin_num) {
  380. next_depth = 0;
  381. }
  382. if((last_depth + current_depth) > my_format.clearance) { //yes intersection
  383. if(current_pin != 1) {
  384. pre_extra_x_px =
  385. min(max(pin_step_px - post_extra_x_px, pin_half_width_px),
  386. pin_step_px - pin_half_width_px);
  387. }
  388. canvas_draw_line(
  389. canvas,
  390. pin_center_px - pre_extra_x_px,
  391. top_contour_px +
  392. max((int)round(
  393. (current_depth_px - (pre_extra_x_px - pin_half_width_px)) * tangent),
  394. 0),
  395. pin_center_px - pin_half_width_px,
  396. top_contour_px + (int)round(current_depth_px * tangent));
  397. } else {
  398. int last_depth_px = (int)round(last_depth * my_format.depth_step_inch / inches_per_px);
  399. int down_slope_start_x_px = pin_center_px - pin_half_width_px - current_depth_px;
  400. canvas_draw_line(
  401. canvas,
  402. pin_center_px - pin_half_width_px - current_depth_px,
  403. top_contour_px,
  404. pin_center_px - pin_half_width_px,
  405. top_contour_px + (int)round(current_depth_px * tangent));
  406. canvas_draw_line(
  407. canvas,
  408. min(pin_center_px - pin_step_px + pin_half_width_px + last_depth_px,
  409. down_slope_start_x_px),
  410. top_contour_px,
  411. down_slope_start_x_px,
  412. top_contour_px);
  413. }
  414. if((current_depth + next_depth) > my_format.clearance) { //yes intersection
  415. double numerator = (double)current_depth;
  416. double denominator = (double)(current_depth + next_depth);
  417. double product = (numerator / denominator) * pin_step_px;
  418. post_extra_x_px =
  419. (int)min(max(product, pin_half_width_px), pin_step_px - pin_half_width_px);
  420. canvas_draw_line(
  421. canvas,
  422. pin_center_px + pin_half_width_px,
  423. top_contour_px + current_depth_px,
  424. pin_center_px + post_extra_x_px,
  425. top_contour_px +
  426. max(current_depth_px -
  427. (int)round((post_extra_x_px - pin_half_width_px) * tangent),
  428. 0));
  429. } else { // no intersection
  430. canvas_draw_line(
  431. canvas,
  432. pin_center_px + pin_half_width_px,
  433. top_contour_px + (int)round(current_depth_px * tangent),
  434. pin_center_px + pin_half_width_px + current_depth_px,
  435. top_contour_px);
  436. }
  437. }
  438. int level_contour_px =
  439. (int)round((my_format.last_pin_inch + my_format.elbow_inch) / inches_per_px);
  440. int elbow_px = (int)round(my_format.elbow_inch / inches_per_px);
  441. canvas_draw_line(canvas, 0, 62, level_contour_px, 62);
  442. canvas_draw_line(canvas, level_contour_px, 62, level_contour_px + elbow_px, 62 - elbow_px);
  443. int slc_pin_px = (int)round(
  444. (my_format.first_pin_inch + (my_model->pin_slc - 1) * my_format.pin_increment_inch) /
  445. inches_per_px);
  446. canvas_draw_icon(canvas, slc_pin_px - 2, top_contour_px - 25, &I_arrow_down);
  447. furi_string_printf(buffer, "%s", my_format.format_name);
  448. canvas_draw_str(canvas, 110, 10, furi_string_get_cstr(buffer));
  449. furi_string_free(buffer);
  450. }
  451. /**
  452. * @brief Callback for game screen input.
  453. * @details This function is called when the user presses a button while on the game screen.
  454. * @param event The event - InputEvent object.
  455. * @param context The context - KeyCopierApp object.
  456. * @return true if the event was handled, false otherwise.
  457. */
  458. static bool key_copier_view_measure_input_callback(InputEvent* event, void* context) {
  459. KeyCopierApp* app = (KeyCopierApp*)context;
  460. if(event->type == InputTypeShort) {
  461. switch(event->key) {
  462. case InputKeyLeft: {
  463. // Left button clicked, reduce x coordinate.
  464. bool redraw = true;
  465. with_view_model(
  466. app->view_measure,
  467. KeyCopierModel * model,
  468. {
  469. if(model->pin_slc > 1) {
  470. model->pin_slc--;
  471. }
  472. },
  473. redraw);
  474. break;
  475. }
  476. case InputKeyRight: {
  477. // Left button clicked, reduce x coordinate.
  478. bool redraw = true;
  479. with_view_model(
  480. app->view_measure,
  481. KeyCopierModel * model,
  482. {
  483. if(model->pin_slc < model->format.pin_num) {
  484. model->pin_slc++;
  485. }
  486. },
  487. redraw);
  488. break;
  489. }
  490. case InputKeyUp: {
  491. // Left button clicked, reduce x coordinate.
  492. bool redraw = true;
  493. with_view_model(
  494. app->view_measure,
  495. KeyCopierModel * model,
  496. {
  497. if(model->depth[model->pin_slc - 1] > model->format.min_depth_ind) {
  498. if(model->pin_slc == 1) { //first pin only limited by the next one
  499. if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
  500. model->format.macs)
  501. model->depth[model->pin_slc - 1]--;
  502. } else if(
  503. model->pin_slc ==
  504. model->format.pin_num) { //last pin only limited by the previous one
  505. if(model->depth[model->pin_slc - 2] -
  506. model->depth[model->pin_slc - 1] <
  507. model->format.macs) {
  508. model->depth[model->pin_slc - 1]--;
  509. }
  510. } else {
  511. if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
  512. model->format.macs &&
  513. model->depth[model->pin_slc - 2] -
  514. model->depth[model->pin_slc - 1] <
  515. model->format.macs) {
  516. model->depth[model->pin_slc - 1]--;
  517. }
  518. }
  519. }
  520. },
  521. redraw);
  522. break;
  523. }
  524. case InputKeyDown: {
  525. // Right button clicked, increase x coordinate.
  526. bool redraw = true;
  527. with_view_model(
  528. app->view_measure,
  529. KeyCopierModel * model,
  530. {
  531. if(model->depth[model->pin_slc - 1] < model->format.max_depth_ind) {
  532. if(model->pin_slc == 1) { //first pin only limited by the next one
  533. if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
  534. model->format.macs)
  535. model->depth[model->pin_slc - 1]++;
  536. } else if(
  537. model->pin_slc ==
  538. model->format.pin_num) { //last pin only limited by the previous one
  539. if(model->depth[model->pin_slc - 1] -
  540. model->depth[model->pin_slc - 2] <
  541. model->format.macs) {
  542. model->depth[model->pin_slc - 1]++;
  543. }
  544. } else {
  545. if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
  546. model->format.macs &&
  547. model->depth[model->pin_slc - 1] -
  548. model->depth[model->pin_slc - 2] <
  549. model->format.macs) {
  550. model->depth[model->pin_slc - 1]++;
  551. }
  552. }
  553. }
  554. },
  555. redraw);
  556. break;
  557. }
  558. default:
  559. // Handle other keys or do nothing
  560. break;
  561. }
  562. }
  563. return false;
  564. }
  565. /**
  566. * @brief Allocate the key_copier application.
  567. * @details This function allocates the key_copier application resources.
  568. * @return KeyCopierApp object.
  569. */
  570. static KeyCopierApp* key_copier_app_alloc() {
  571. KeyCopierApp* app = (KeyCopierApp*)malloc(sizeof(KeyCopierApp));
  572. Gui* gui = furi_record_open(RECORD_GUI);
  573. app->view_dispatcher = view_dispatcher_alloc();
  574. view_dispatcher_enable_queue(app->view_dispatcher);
  575. view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  576. view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
  577. app->dialogs = furi_record_open(RECORD_DIALOGS);
  578. app->file_path = furi_string_alloc();
  579. app->submenu = submenu_alloc();
  580. submenu_add_item(
  581. app->submenu, "Measure", KeyCopierSubmenuIndexMeasure, key_copier_submenu_callback, app);
  582. submenu_add_item(
  583. app->submenu, "Config", KeyCopierSubmenuIndexConfigure, key_copier_submenu_callback, app);
  584. submenu_add_item(
  585. app->submenu, "Save", KeyCopierSubmenuIndexSave, key_copier_submenu_callback, app);
  586. submenu_add_item(
  587. app->submenu, "Load", KeyCopierSubmenuIndexLoad, key_copier_submenu_callback, app);
  588. submenu_add_item(
  589. app->submenu, "About", KeyCopierSubmenuIndexAbout, key_copier_submenu_callback, app);
  590. view_set_previous_callback(
  591. submenu_get_view(app->submenu), key_copier_navigation_exit_callback);
  592. view_dispatcher_add_view(
  593. app->view_dispatcher, KeyCopierViewSubmenu, submenu_get_view(app->submenu));
  594. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
  595. app->text_input = text_input_alloc();
  596. view_dispatcher_add_view(
  597. app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
  598. app->temp_buffer_size = 32;
  599. app->temp_buffer = (char*)malloc(app->temp_buffer_size);
  600. app->temp_buffer = "";
  601. app->view_measure = view_alloc();
  602. view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
  603. view_set_input_callback(app->view_measure, key_copier_view_measure_input_callback);
  604. view_set_previous_callback(app->view_measure, key_copier_navigation_submenu_callback);
  605. view_set_context(app->view_measure, app);
  606. view_allocate_model(app->view_measure, ViewModelTypeLockFree, sizeof(KeyCopierModel));
  607. KeyCopierModel* model = view_get_model(app->view_measure);
  608. initialize_model(model);
  609. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewMeasure, app->view_measure);
  610. app->variable_item_list_config = variable_item_list_alloc();
  611. app->view_config_e = view_alloc();
  612. view_set_context(app->view_config_e, app);
  613. view_set_previous_callback(app->view_config_e, key_copier_navigation_submenu_callback);
  614. view_set_enter_callback(app->view_config_e, key_copier_config_enter_callback);
  615. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_e, app->view_config_e);
  616. View* view_buffer = view_alloc();
  617. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_buffer);
  618. app->view_save = view_alloc();
  619. view_set_context(app->view_save, app);
  620. view_set_enter_callback(app->view_save, key_copier_view_save_callback);
  621. view_set_previous_callback(app->view_save, key_copier_navigation_submenu_callback);
  622. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewSave, app->view_save);
  623. app->view_load = view_alloc();
  624. view_set_context(app->view_load, app);
  625. view_set_enter_callback(app->view_load, key_copier_view_load_callback);
  626. view_set_previous_callback(app->view_load, key_copier_navigation_submenu_callback);
  627. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewLoad, app->view_load);
  628. app->widget_about = widget_alloc();
  629. widget_add_text_scroll_element(
  630. app->widget_about,
  631. 0,
  632. 0,
  633. 128,
  634. 64,
  635. "Key Maker App 1.0\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place it on top of the screen.\n\n2. Use the contour to align your key.\n\n3. Adjust each pin's depth until they match. It's easier if you look with one eye closed.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial thanks to Derek Jamison's Skeleton App Template.");
  636. view_set_previous_callback(
  637. widget_get_view(app->widget_about), key_copier_navigation_submenu_callback);
  638. view_dispatcher_add_view(
  639. app->view_dispatcher, KeyCopierViewAbout, widget_get_view(app->widget_about));
  640. app->notifications = furi_record_open(RECORD_NOTIFICATION);
  641. #ifdef BACKLIGHT_ON
  642. notification_message(app->notifications, &sequence_display_backlight_enforce_on);
  643. #endif
  644. return app;
  645. }
  646. /**
  647. * @brief Free the key_copier application.
  648. * @details This function frees the key_copier application resources.
  649. * @param app The key_copier application object.
  650. */
  651. static void key_copier_app_free(KeyCopierApp* app) {
  652. #ifdef BACKLIGHT_ON
  653. notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
  654. #endif
  655. furi_record_close(RECORD_NOTIFICATION);
  656. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewTextInput);
  657. text_input_free(app->text_input);
  658. free(app->temp_buffer);
  659. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewAbout);
  660. widget_free(app->widget_about);
  661. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewMeasure);
  662. with_view_model(
  663. app->view_measure,
  664. KeyCopierModel * model,
  665. {
  666. if(model->depth != NULL) {
  667. free(model->depth);
  668. }
  669. },
  670. false);
  671. view_free(app->view_measure);
  672. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_e);
  673. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_i);
  674. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSave);
  675. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewLoad);
  676. variable_item_list_free(app->variable_item_list_config);
  677. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSubmenu);
  678. submenu_free(app->submenu);
  679. view_dispatcher_free(app->view_dispatcher);
  680. furi_record_close(RECORD_GUI);
  681. free(app);
  682. }
  683. /**
  684. * @brief Main function for key_copier application.
  685. * @details This function is the entry point for the key_copier application. It should be defined in
  686. * application.fam as the entry_point setting.
  687. * @param _p Input parameter - unused
  688. * @return 0 - Success
  689. */
  690. int32_t main_key_copier_app(void* _p) {
  691. UNUSED(_p);
  692. KeyCopierApp* app = key_copier_app_alloc();
  693. view_dispatcher_run(app->view_dispatcher);
  694. key_copier_app_free(app);
  695. return 0;
  696. }