key_copier.c 32 KB

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