key_copier.c 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  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* format_names[COUNT_OF(all_formats)];
  138. void initialize_format_names(char** format_names) {
  139. // Populate the format_names array
  140. for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
  141. format_names[i] = all_formats[i].format_name;
  142. }
  143. }
  144. static const char* format_config_label = "Key Format";
  145. static void key_copier_format_change(VariableItem* item) {
  146. KeyCopierApp* app = variable_item_get_context(item);
  147. KeyCopierModel* model = view_get_model(app->view_measure);
  148. if(model->data_loaded) {
  149. variable_item_set_current_value_index(item, model->format_index);
  150. } else {
  151. uint8_t format_index = variable_item_get_current_value_index(item);
  152. model->format_index = format_index;
  153. model->format = all_formats[format_index];
  154. }
  155. model->data_loaded = false;
  156. variable_item_set_current_value_text(item, model->format.format_name);
  157. model->format = all_formats[model->format_index];
  158. if(model->depth != NULL) {
  159. free(model->depth);
  160. }
  161. model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
  162. for(uint8_t i = 0; i <= model->format.pin_num; i++) {
  163. model->depth[i] = model->format.min_depth_ind;
  164. }
  165. }
  166. static void key_copier_config_enter_callback(void* context) {
  167. KeyCopierApp* app = (KeyCopierApp*)context;
  168. KeyCopierModel* my_model = view_get_model(app->view_measure);
  169. variable_item_list_reset(app->variable_item_list_config);
  170. // Recreate this view every time we enter it so that it's always updated
  171. app->format_item = variable_item_list_add(
  172. app->variable_item_list_config,
  173. format_config_label,
  174. COUNT_OF(all_formats),
  175. key_copier_format_change,
  176. app);
  177. View* view_config_i = variable_item_list_get_view(app->variable_item_list_config);
  178. variable_item_set_current_value_index(app->format_item, my_model->format_index);
  179. key_copier_format_change(app->format_item);
  180. view_set_previous_callback(view_config_i, key_copier_navigation_submenu_callback);
  181. view_dispatcher_remove_view(
  182. app->view_dispatcher, KeyCopierViewConfigure_i); // delete the last one
  183. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_config_i);
  184. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_i); // recreate it
  185. }
  186. /**
  187. * Our 2nd sample setting is a text field. When the user clicks OK on the configuration
  188. * setting we use a text input screen to allow the user to enter a name. This function is
  189. * called when the user clicks OK on the text input screen.
  190. */
  191. static const char* key_name_entry_text = "Enter name";
  192. static void key_copier_file_saver(void* context) {
  193. KeyCopierApp* app = (KeyCopierApp*)context;
  194. KeyCopierModel* model = view_get_model(app->view_measure);
  195. bool redraw = true;
  196. with_view_model(
  197. app->view_measure,
  198. KeyCopierModel * model,
  199. { furi_string_set(model->key_name_str, app->temp_buffer); },
  200. redraw);
  201. FuriString* file_path = furi_string_alloc();
  202. furi_string_printf(
  203. file_path,
  204. "%s/%s%s",
  205. STORAGE_APP_DATA_PATH_PREFIX,
  206. furi_string_get_cstr(model->key_name_str),
  207. KEY_COPIER_FILE_EXTENSION);
  208. Storage* storage = furi_record_open(RECORD_STORAGE);
  209. storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
  210. FURI_LOG_D(TAG, "mkdir finished");
  211. FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
  212. do {
  213. const uint32_t version = 1;
  214. const uint32_t pin_num_buffer = (uint32_t)model->format.pin_num;
  215. const uint32_t macs_buffer = (uint32_t)model->format.macs;
  216. FuriString* buffer = furi_string_alloc();
  217. if(!flipper_format_file_open_always(flipper_format, furi_string_get_cstr(file_path)))
  218. break;
  219. if(!flipper_format_write_header_cstr(flipper_format, "Flipper Key Copier File", version))
  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, "Format Short Name", model->format.format_short_name))
  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 t5577_writer_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. KeyCopierModel* my_model = (KeyCopierModel*)model;
  329. KeyFormat my_format = my_model->format;
  330. int pin_half_width_px = (int)round((my_format.pin_width_inch / inches_per_px) / 2);
  331. int pin_step_px = (int)round(my_format.pin_increment_inch / inches_per_px);
  332. double drill_radians =
  333. (180 - my_format.drill_angle) / 2 / 180 * (double)M_PI; // Convert angle to radians
  334. double tangent = tan(drill_radians);
  335. int post_extra_x_px = 0;
  336. int pre_extra_x_px = 0;
  337. for(int current_pin = 1; current_pin <= my_model->format.pin_num; current_pin += 1) {
  338. double current_center_px =
  339. my_format.first_pin_inch + (current_pin - 1) * my_format.pin_increment_inch;
  340. int pin_center_px = (int)round(current_center_px / inches_per_px);
  341. int top_contour_px = (int)round(63 - my_format.uncut_depth_inch / inches_per_px);
  342. canvas_draw_line(canvas, pin_center_px, 25, pin_center_px, 50);
  343. int current_depth = my_model->depth[current_pin - 1] - my_format.min_depth_ind;
  344. int current_depth_px =
  345. (int)round(current_depth * my_format.depth_step_inch / inches_per_px);
  346. canvas_draw_line(
  347. canvas,
  348. pin_center_px - pin_half_width_px,
  349. top_contour_px + current_depth_px,
  350. pin_center_px + pin_half_width_px,
  351. top_contour_px + current_depth_px);
  352. int last_depth = my_model->depth[current_pin - 2] - my_format.min_depth_ind;
  353. int next_depth = my_model->depth[current_pin] - my_format.min_depth_ind;
  354. if(current_pin == 1) {
  355. canvas_draw_line(
  356. canvas,
  357. 0,
  358. top_contour_px,
  359. pin_center_px - pin_half_width_px - current_depth_px,
  360. top_contour_px);
  361. last_depth = 0;
  362. pre_extra_x_px = max(current_depth_px + pin_half_width_px, 0);
  363. }
  364. if(current_pin == my_model->format.pin_num) {
  365. next_depth = 0;
  366. }
  367. if((last_depth + current_depth) > my_format.clearance &&
  368. current_depth != 0) { //yes intersection
  369. if(current_pin != 1) {
  370. pre_extra_x_px =
  371. min(max(pin_step_px - post_extra_x_px, pin_half_width_px),
  372. pin_step_px - pin_half_width_px);
  373. }
  374. canvas_draw_line(
  375. canvas,
  376. pin_center_px - pre_extra_x_px,
  377. top_contour_px +
  378. max((int)round(
  379. (current_depth_px - (pre_extra_x_px - pin_half_width_px)) * tangent),
  380. 0),
  381. pin_center_px - pin_half_width_px,
  382. top_contour_px + (int)round(current_depth_px * tangent));
  383. } else {
  384. int last_depth_px = (int)round(last_depth * my_format.depth_step_inch / inches_per_px);
  385. int down_slope_start_x_px = pin_center_px - pin_half_width_px - current_depth_px;
  386. canvas_draw_line(
  387. canvas,
  388. pin_center_px - pin_half_width_px - current_depth_px,
  389. top_contour_px,
  390. pin_center_px - pin_half_width_px,
  391. top_contour_px + (int)round(current_depth_px * tangent));
  392. canvas_draw_line(
  393. canvas,
  394. min(pin_center_px - pin_step_px + pin_half_width_px + last_depth_px,
  395. down_slope_start_x_px),
  396. top_contour_px,
  397. down_slope_start_x_px,
  398. top_contour_px);
  399. }
  400. if((current_depth + next_depth) > my_format.clearance &&
  401. current_depth != 0) { //yes intersection
  402. double numerator = (double)current_depth;
  403. double denominator = (double)(current_depth + next_depth);
  404. double product = (numerator / denominator) * pin_step_px;
  405. post_extra_x_px =
  406. (int)min(max(product, pin_half_width_px), pin_step_px - pin_half_width_px);
  407. canvas_draw_line(
  408. canvas,
  409. pin_center_px + pin_half_width_px,
  410. top_contour_px + current_depth_px,
  411. pin_center_px + post_extra_x_px,
  412. top_contour_px +
  413. max(current_depth_px -
  414. (int)round((post_extra_x_px - pin_half_width_px) * tangent),
  415. 0));
  416. } else { // no intersection
  417. canvas_draw_line(
  418. canvas,
  419. pin_center_px + pin_half_width_px,
  420. top_contour_px + (int)round(current_depth_px * tangent),
  421. pin_center_px + pin_half_width_px + current_depth_px,
  422. top_contour_px);
  423. }
  424. }
  425. int level_contour_px =
  426. (int)round((my_format.last_pin_inch + my_format.pin_increment_inch) / inches_per_px - 4);
  427. canvas_draw_line(canvas, 0, 62, level_contour_px, 62);
  428. int slc_pin_px = (int)round(
  429. (my_format.first_pin_inch + (my_model->pin_slc - 1) * my_format.pin_increment_inch) /
  430. inches_per_px);
  431. canvas_draw_str(canvas, slc_pin_px - 2, 23, "*");
  432. FuriString* xstr = furi_string_alloc();
  433. int buffer_size = my_model->format.pin_num + 1;
  434. char depth_str[buffer_size];
  435. depth_str[0] = '\0'; // Initialize the string
  436. // Manual string concatenation
  437. char* pos = depth_str;
  438. for(int i = 0; i < my_model->format.pin_num; i++) {
  439. int written = snprintf(pos, buffer_size - (pos - depth_str), "%u", my_model->depth[i]);
  440. if(written < 0 || written >= buffer_size - (pos - depth_str)) {
  441. // Handle error
  442. break;
  443. }
  444. pos += written;
  445. }
  446. furi_string_printf(xstr, "bitting: %s", depth_str);
  447. canvas_draw_str(canvas, 0, 10, furi_string_get_cstr(xstr));
  448. furi_string_printf(xstr, "%s", my_format.format_short_name);
  449. canvas_draw_str(canvas, 110, 10, furi_string_get_cstr(xstr));
  450. //furi_string_printf(xstr, "Num of Pins: %s", format_names[my_model->format_index]);
  451. //canvas_draw_str(canvas, 44, 24, furi_string_get_cstr(xstr));
  452. furi_string_free(xstr);
  453. }
  454. /**
  455. * @brief Callback for game screen input.
  456. * @details This function is called when the user presses a button while on the game screen.
  457. * @param event The event - InputEvent object.
  458. * @param context The context - KeyCopierApp object.
  459. * @return true if the event was handled, false otherwise.
  460. */
  461. static bool key_copier_view_measure_input_callback(InputEvent* event, void* context) {
  462. KeyCopierApp* app = (KeyCopierApp*)context;
  463. if(event->type == InputTypeShort) {
  464. switch(event->key) {
  465. case InputKeyLeft: {
  466. // Left button clicked, reduce x coordinate.
  467. bool redraw = true;
  468. with_view_model(
  469. app->view_measure,
  470. KeyCopierModel * model,
  471. {
  472. if(model->pin_slc > 1) {
  473. model->pin_slc--;
  474. }
  475. },
  476. redraw);
  477. break;
  478. }
  479. case InputKeyRight: {
  480. // Left button clicked, reduce x coordinate.
  481. bool redraw = true;
  482. with_view_model(
  483. app->view_measure,
  484. KeyCopierModel * model,
  485. {
  486. if(model->pin_slc < model->format.pin_num) {
  487. model->pin_slc++;
  488. }
  489. },
  490. redraw);
  491. break;
  492. }
  493. case InputKeyUp: {
  494. // Left button clicked, reduce x coordinate.
  495. bool redraw = true;
  496. with_view_model(
  497. app->view_measure,
  498. KeyCopierModel * model,
  499. {
  500. if(model->depth[model->pin_slc - 1] > model->format.min_depth_ind) {
  501. if(model->pin_slc == 1) { //first pin only limited by the next one
  502. if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
  503. model->format.macs)
  504. model->depth[model->pin_slc - 1]--;
  505. } else if(
  506. model->pin_slc ==
  507. model->format.pin_num) { //last pin only limited by the previous one
  508. if(model->depth[model->pin_slc - 2] -
  509. model->depth[model->pin_slc - 1] <
  510. model->format.macs) {
  511. model->depth[model->pin_slc - 1]--;
  512. }
  513. } else {
  514. if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
  515. model->format.macs &&
  516. model->depth[model->pin_slc - 2] -
  517. model->depth[model->pin_slc - 1] <
  518. model->format.macs) {
  519. model->depth[model->pin_slc - 1]--;
  520. }
  521. }
  522. }
  523. },
  524. redraw);
  525. break;
  526. }
  527. case InputKeyDown: {
  528. // Right button clicked, increase x coordinate.
  529. bool redraw = true;
  530. with_view_model(
  531. app->view_measure,
  532. KeyCopierModel * model,
  533. {
  534. if(model->depth[model->pin_slc - 1] < model->format.max_depth_ind) {
  535. if(model->pin_slc == 1) { //first pin only limited by the next one
  536. if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
  537. model->format.macs)
  538. model->depth[model->pin_slc - 1]++;
  539. } else if(
  540. model->pin_slc ==
  541. model->format.pin_num) { //last pin only limited by the previous one
  542. if(model->depth[model->pin_slc - 1] -
  543. model->depth[model->pin_slc - 2] <
  544. model->format.macs) {
  545. model->depth[model->pin_slc - 1]++;
  546. }
  547. } else {
  548. if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
  549. model->format.macs &&
  550. model->depth[model->pin_slc - 1] -
  551. model->depth[model->pin_slc - 2] <
  552. model->format.macs) {
  553. model->depth[model->pin_slc - 1]++;
  554. }
  555. }
  556. }
  557. },
  558. redraw);
  559. break;
  560. }
  561. default:
  562. // Handle other keys or do nothing
  563. break;
  564. }
  565. }
  566. return false;
  567. }
  568. /**
  569. * @brief Allocate the key_copier application.
  570. * @details This function allocates the key_copier application resources.
  571. * @return KeyCopierApp object.
  572. */
  573. static KeyCopierApp* key_copier_app_alloc() {
  574. KeyCopierApp* app = (KeyCopierApp*)malloc(sizeof(KeyCopierApp));
  575. Gui* gui = furi_record_open(RECORD_GUI);
  576. app->view_dispatcher = view_dispatcher_alloc();
  577. view_dispatcher_enable_queue(app->view_dispatcher);
  578. view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  579. view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
  580. app->dialogs = furi_record_open(RECORD_DIALOGS);
  581. app->file_path = furi_string_alloc();
  582. app->submenu = submenu_alloc();
  583. submenu_add_item(
  584. app->submenu, "Measure", KeyCopierSubmenuIndexMeasure, key_copier_submenu_callback, app);
  585. submenu_add_item(
  586. app->submenu, "Config", KeyCopierSubmenuIndexConfigure, key_copier_submenu_callback, app);
  587. submenu_add_item(
  588. app->submenu, "Save", KeyCopierSubmenuIndexSave, key_copier_submenu_callback, app);
  589. submenu_add_item(
  590. app->submenu, "Load", KeyCopierSubmenuIndexLoad, key_copier_submenu_callback, app);
  591. submenu_add_item(
  592. app->submenu, "About", KeyCopierSubmenuIndexAbout, key_copier_submenu_callback, app);
  593. view_set_previous_callback(
  594. submenu_get_view(app->submenu), key_copier_navigation_exit_callback);
  595. view_dispatcher_add_view(
  596. app->view_dispatcher, KeyCopierViewSubmenu, submenu_get_view(app->submenu));
  597. view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
  598. app->text_input = text_input_alloc();
  599. view_dispatcher_add_view(
  600. app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
  601. app->temp_buffer_size = 32;
  602. app->temp_buffer = (char*)malloc(app->temp_buffer_size);
  603. app->temp_buffer = "";
  604. app->view_measure = view_alloc();
  605. view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
  606. view_set_input_callback(app->view_measure, key_copier_view_measure_input_callback);
  607. view_set_previous_callback(app->view_measure, key_copier_navigation_submenu_callback);
  608. view_set_context(app->view_measure, app);
  609. view_allocate_model(app->view_measure, ViewModelTypeLockFree, sizeof(KeyCopierModel));
  610. KeyCopierModel* model = view_get_model(app->view_measure);
  611. initialize_model(model);
  612. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewMeasure, app->view_measure);
  613. app->variable_item_list_config = variable_item_list_alloc();
  614. app->view_config_e = view_alloc();
  615. view_set_context(app->view_config_e, app);
  616. view_set_previous_callback(app->view_config_e, key_copier_navigation_submenu_callback);
  617. view_set_enter_callback(app->view_config_e, key_copier_config_enter_callback);
  618. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_e, app->view_config_e);
  619. View* view_buffer = view_alloc();
  620. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_buffer);
  621. app->view_save = view_alloc();
  622. view_set_context(app->view_save, app);
  623. view_set_enter_callback(app->view_save, key_copier_view_save_callback);
  624. view_set_previous_callback(app->view_save, key_copier_navigation_submenu_callback);
  625. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewSave, app->view_save);
  626. app->view_load = view_alloc();
  627. view_set_context(app->view_load, app);
  628. view_set_enter_callback(app->view_load, t5577_writer_view_load_callback);
  629. view_set_previous_callback(app->view_load, key_copier_navigation_submenu_callback);
  630. view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewLoad, app->view_load);
  631. app->widget_about = widget_alloc();
  632. widget_add_text_scroll_element(
  633. app->widget_about,
  634. 0,
  635. 0,
  636. 128,
  637. 64,
  638. "Key Maker App 0.3\n\nTo measure your key:\n\n1. Place it on top of the screen.\n\n2. Use the horizontal lines to align your key.\n\n3. Adjust each pin's bitting depth until they match. It's easier if you close one eye.\n\n4. Save the bitting pattern and key format by pressing the OK button or in the Config menu.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial thanks to Derek Jamison's Skeleton App Template.");
  639. view_set_previous_callback(
  640. widget_get_view(app->widget_about), key_copier_navigation_submenu_callback);
  641. view_dispatcher_add_view(
  642. app->view_dispatcher, KeyCopierViewAbout, widget_get_view(app->widget_about));
  643. app->notifications = furi_record_open(RECORD_NOTIFICATION);
  644. #ifdef BACKLIGHT_ON
  645. notification_message(app->notifications, &sequence_display_backlight_enforce_on);
  646. #endif
  647. return app;
  648. }
  649. /**
  650. * @brief Free the key_copier application.
  651. * @details This function frees the key_copier application resources.
  652. * @param app The key_copier application object.
  653. */
  654. static void key_copier_app_free(KeyCopierApp* app) {
  655. #ifdef BACKLIGHT_ON
  656. notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
  657. #endif
  658. furi_record_close(RECORD_NOTIFICATION);
  659. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewTextInput);
  660. text_input_free(app->text_input);
  661. free(app->temp_buffer);
  662. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewAbout);
  663. widget_free(app->widget_about);
  664. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewMeasure);
  665. with_view_model(
  666. app->view_measure,
  667. KeyCopierModel * model,
  668. {
  669. if(model->depth != NULL) {
  670. free(model->depth);
  671. }
  672. },
  673. false);
  674. view_free(app->view_measure);
  675. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_e);
  676. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_i);
  677. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSave);
  678. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewLoad);
  679. variable_item_list_free(app->variable_item_list_config);
  680. view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSubmenu);
  681. submenu_free(app->submenu);
  682. view_dispatcher_free(app->view_dispatcher);
  683. furi_record_close(RECORD_GUI);
  684. free(app);
  685. }
  686. /**
  687. * @brief Main function for key_copier application.
  688. * @details This function is the entry point for the key_copier application. It should be defined in
  689. * application.fam as the entry_point setting.
  690. * @param _p Input parameter - unused
  691. * @return 0 - Success
  692. */
  693. int32_t main_key_copier_app(void* _p) {
  694. UNUSED(_p);
  695. KeyCopierApp* app = key_copier_app_alloc();
  696. view_dispatcher_run(app->view_dispatcher);
  697. key_copier_app_free(app);
  698. return 0;
  699. }