qrcode_app.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. #include <furi.h>
  2. #include <dialogs/dialogs.h>
  3. #include <gui/gui.h>
  4. #include <storage/storage.h>
  5. #include <lib/flipper_format/flipper_format.h>
  6. // this file is generated by the build script
  7. #include <qrcode_icons.h>
  8. #include "qrcode.h"
  9. #define TAG "qrcode"
  10. #define QRCODE_FOLDER EXT_PATH("qrcodes")
  11. #define QRCODE_EXTENSION ".qrcode"
  12. #define QRCODE_FILETYPE "QRCode"
  13. #define QRCODE_FILE_VERSION 1
  14. /** Valid modes are Numeric (0), Alpha-Numeric (1), and Binary (2) */
  15. #define MAX_QRCODE_MODE 2
  16. /**
  17. * Maximum version is 11 because the f0 screen is only 64 pixels high and
  18. * version 12 is 65x65. Version 11 is 61x61.
  19. */
  20. #define MAX_QRCODE_VERSION 11
  21. /** Valid ECC levels are Low (0), Medium (1), Quartile (2), and High (3) */
  22. #define MAX_QRCODE_ECC 3
  23. /** Maximum length by mode, ecc, and version */
  24. static const uint16_t MAX_LENGTH[3][4][MAX_QRCODE_VERSION] = {
  25. {
  26. // Numeric
  27. {41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772}, // Low
  28. {34, 63, 101, 149, 202, 255, 293, 365, 432, 513, 604}, // Medium
  29. {27, 48, 77, 111, 144, 178, 207, 259, 312, 364, 427}, // Quartile
  30. {17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331}, // High
  31. },
  32. {
  33. // Alphanumeric
  34. {25, 47, 77, 114, 154, 195, 224, 279, 335, 395, 468}, // Low
  35. {20, 38, 61, 90, 122, 154, 178, 221, 262, 311, 366}, // Medium
  36. {16, 29, 47, 67, 87, 108, 125, 157, 189, 221, 259}, // Quartile
  37. {10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200}, // High
  38. },
  39. {
  40. // Binary
  41. {17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321}, // Low
  42. {14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251}, // Medium
  43. {11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177}, // Quartile
  44. {7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137}, // High
  45. },
  46. };
  47. /** Main app instance */
  48. typedef struct {
  49. FuriMessageQueue* input_queue;
  50. Gui* gui;
  51. ViewPort* view_port;
  52. FuriMutex** mutex;
  53. FuriString* message;
  54. QRCode* qrcode;
  55. uint8_t min_mode;
  56. uint8_t max_mode;
  57. uint8_t min_version;
  58. uint8_t max_ecc_at_min_version;
  59. bool loading;
  60. bool too_long;
  61. bool show_stats;
  62. uint8_t selected_idx;
  63. bool edit;
  64. uint8_t set_mode;
  65. uint8_t set_version;
  66. uint8_t set_ecc;
  67. } QRCodeApp;
  68. /**
  69. * @param ecc ECC number
  70. * @returns a character corresponding to the ecc level
  71. */
  72. static char get_ecc_char(uint8_t ecc) {
  73. switch(ecc) {
  74. case 0:
  75. return 'L';
  76. case 1:
  77. return 'M';
  78. case 2:
  79. return 'Q';
  80. case 3:
  81. return 'H';
  82. default:
  83. return '?';
  84. }
  85. }
  86. /**
  87. * @param ecc A character representing an ECC mode (L, M, Q, or H)
  88. * @returns the ecc level or 255 representing an unknown ECC mode
  89. */
  90. static uint8_t get_ecc_value(char ecc) {
  91. switch(ecc) {
  92. case 'L':
  93. case 'l':
  94. return 0;
  95. case 'M':
  96. case 'm':
  97. return 1;
  98. case 'Q':
  99. case 'q':
  100. return 2;
  101. case 'H':
  102. case 'h':
  103. return 3;
  104. default:
  105. return 255;
  106. }
  107. }
  108. /**
  109. * @param mode qrcode mode
  110. * @returns a character corresponding to the mode
  111. */
  112. static char get_mode_char(uint8_t mode) {
  113. switch(mode) {
  114. case 0:
  115. return 'N';
  116. case 1:
  117. return 'A';
  118. case 2:
  119. return 'B';
  120. case 3:
  121. return 'K';
  122. default:
  123. return '?';
  124. }
  125. }
  126. /**
  127. * @param mode A character representing a qrcode mode (N, A, or B)
  128. * @returns the mode or 255 representing an unknown mode
  129. */
  130. static uint8_t get_mode_value(char mode) {
  131. switch(mode) {
  132. case 'N':
  133. case 'n':
  134. return 0;
  135. case 'A':
  136. case 'a':
  137. return 1;
  138. case 'B':
  139. case 'b':
  140. return 2;
  141. default:
  142. return 255;
  143. }
  144. }
  145. /**
  146. * Render
  147. * @param canvas The canvas to render to
  148. * @param ctx Context provided to the callback by view_port_draw_callback_set
  149. */
  150. static void render_callback(Canvas* canvas, void* ctx) {
  151. furi_assert(canvas);
  152. furi_assert(ctx);
  153. QRCodeApp* instance = ctx;
  154. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  155. canvas_clear(canvas);
  156. canvas_set_color(canvas, ColorBlack);
  157. canvas_set_font(canvas, FontPrimary);
  158. uint8_t font_height = canvas_current_font_height(canvas);
  159. uint8_t width = canvas_width(canvas);
  160. uint8_t height = canvas_height(canvas);
  161. if(instance->loading) {
  162. canvas_draw_str_aligned(
  163. canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
  164. } else if(instance->qrcode) {
  165. uint8_t size = instance->qrcode->size;
  166. uint8_t pixel_size = height / size;
  167. uint8_t top = (height - pixel_size * size) / 2;
  168. uint8_t left = ((instance->show_stats ? 65 : width) - pixel_size * size) / 2;
  169. for(uint8_t y = 0; y < size; y++) {
  170. for(uint8_t x = 0; x < size; x++) {
  171. if(qrcode_getModule(instance->qrcode, x, y)) {
  172. if(pixel_size == 1) {
  173. canvas_draw_dot(canvas, left + x * pixel_size, top + y * pixel_size);
  174. } else {
  175. canvas_draw_box(
  176. canvas,
  177. left + x * pixel_size,
  178. top + y * pixel_size,
  179. pixel_size,
  180. pixel_size);
  181. }
  182. }
  183. }
  184. }
  185. if(instance->show_stats) {
  186. top = 10;
  187. left = 66;
  188. FuriString* str = furi_string_alloc();
  189. if(!instance->edit || instance->selected_idx == 0) {
  190. furi_string_printf(str, "Mod: %c", get_mode_char(instance->set_mode));
  191. canvas_draw_str(canvas, left + 5, font_height + top, furi_string_get_cstr(str));
  192. if(instance->selected_idx == 0) {
  193. canvas_draw_triangle(
  194. canvas,
  195. left,
  196. top + font_height / 2,
  197. font_height - 4,
  198. 4,
  199. CanvasDirectionLeftToRight);
  200. }
  201. if(instance->edit) {
  202. uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "Mod: B") / 2;
  203. canvas_draw_triangle(
  204. canvas, arrow_left, top, font_height - 4, 4, CanvasDirectionBottomToTop);
  205. canvas_draw_triangle(
  206. canvas,
  207. arrow_left,
  208. top + font_height + 1,
  209. font_height - 4,
  210. 4,
  211. CanvasDirectionTopToBottom);
  212. }
  213. }
  214. if(!instance->edit || instance->selected_idx == 1) {
  215. furi_string_printf(str, "Ver: %i", instance->set_version);
  216. canvas_draw_str(
  217. canvas, left + 5, 2 * font_height + top + 2, furi_string_get_cstr(str));
  218. if(instance->selected_idx == 1) {
  219. canvas_draw_triangle(
  220. canvas,
  221. left,
  222. 3 * font_height / 2 + top + 2,
  223. font_height - 4,
  224. 4,
  225. CanvasDirectionLeftToRight);
  226. }
  227. if(instance->edit) {
  228. uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "Ver: 8") / 2;
  229. canvas_draw_triangle(
  230. canvas,
  231. arrow_left,
  232. font_height + top + 2,
  233. font_height - 4,
  234. 4,
  235. CanvasDirectionBottomToTop);
  236. canvas_draw_triangle(
  237. canvas,
  238. arrow_left,
  239. 2 * font_height + top + 3,
  240. font_height - 4,
  241. 4,
  242. CanvasDirectionTopToBottom);
  243. }
  244. }
  245. if(!instance->edit || instance->selected_idx == 2) {
  246. furi_string_printf(str, "ECC: %c", get_ecc_char(instance->set_ecc));
  247. canvas_draw_str(
  248. canvas, left + 5, 3 * font_height + top + 4, furi_string_get_cstr(str));
  249. if(instance->selected_idx == 2) {
  250. canvas_draw_triangle(
  251. canvas,
  252. left,
  253. 5 * font_height / 2 + top + 4,
  254. font_height - 4,
  255. 4,
  256. CanvasDirectionLeftToRight);
  257. }
  258. if(instance->edit) {
  259. uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "ECC: H") / 2;
  260. canvas_draw_triangle(
  261. canvas,
  262. arrow_left,
  263. 2 * font_height + top + 4,
  264. font_height - 4,
  265. 4,
  266. CanvasDirectionBottomToTop);
  267. canvas_draw_triangle(
  268. canvas,
  269. arrow_left,
  270. 3 * font_height + top + 5,
  271. font_height - 4,
  272. 4,
  273. CanvasDirectionTopToBottom);
  274. }
  275. }
  276. furi_string_free(str);
  277. }
  278. } else {
  279. uint8_t margin = (height - font_height * 2) / 3;
  280. canvas_draw_str_aligned(
  281. canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
  282. if(instance->too_long) {
  283. canvas_set_font(canvas, FontSecondary);
  284. canvas_draw_str(canvas, width / 2, margin * 2 + font_height, "Message is too long.");
  285. }
  286. }
  287. furi_mutex_release(instance->mutex);
  288. }
  289. /**
  290. * Handle input
  291. * @param input_event The received input event
  292. * @param ctx Context provided to the callback by view_port_input_callback_set
  293. */
  294. static void input_callback(InputEvent* input_event, void* ctx) {
  295. furi_assert(input_event);
  296. furi_assert(ctx);
  297. if(input_event->type == InputTypeShort) {
  298. QRCodeApp* instance = ctx;
  299. furi_message_queue_put(instance->input_queue, input_event, 0);
  300. }
  301. }
  302. /**
  303. * Determine if the given string is all numeric
  304. * @param str The string to test
  305. * @returns true if the string is all numeric
  306. */
  307. static bool is_numeric(const char* str, uint16_t len) {
  308. furi_assert(str);
  309. while(len > 0) {
  310. char c = str[--len];
  311. if(c < '0' || c > '9') return false;
  312. }
  313. return true;
  314. }
  315. /**
  316. * Determine if the given string is alphanumeric
  317. * @param str The string to test
  318. * @returns true if the string is alphanumeric
  319. */
  320. static bool is_alphanumeric(const char* str, uint16_t len) {
  321. furi_assert(str);
  322. while(len > 0) {
  323. char c = str[--len];
  324. if(c >= '0' && c <= '9') continue;
  325. if(c >= 'A' && c <= 'Z') continue;
  326. if(c == ' ' || c == '$' || c == '%' || c == '*' || c == '+' || c == '-' || c == '.' ||
  327. c == '/' || c == ':')
  328. continue;
  329. return false;
  330. }
  331. return true;
  332. }
  333. /**
  334. * Allocate a qrcode
  335. * @param version qrcode version
  336. * @returns an allocated QRCode
  337. */
  338. static QRCode* qrcode_alloc(uint8_t version) {
  339. QRCode* qrcode = malloc(sizeof(QRCode));
  340. qrcode->modules = malloc(qrcode_getBufferSize(version));
  341. return qrcode;
  342. }
  343. /**
  344. * Free a QRCode
  345. * @param qrcode The QRCode to free
  346. */
  347. static void qrcode_free(QRCode* qrcode) {
  348. furi_assert(qrcode);
  349. free(qrcode->modules);
  350. free(qrcode);
  351. }
  352. /**
  353. * Rebuild the qrcode. Assumes that instance->message is the message to encode,
  354. * that the mutex has been acquired, and the specified version/ecc will be
  355. * sufficiently large enough to encode the full message. It is also assumed
  356. * that the old qrcode will be free'd by the caller.
  357. * @param instance The qrcode app instance
  358. * @param mode The qrcode mode to use
  359. * @param version The qrcode version to use
  360. * @param ecc The qrcode ECC level to use
  361. * @returns true if the qrcode was successfully created
  362. */
  363. static bool rebuild_qrcode(QRCodeApp* instance, uint8_t mode, uint8_t version, uint8_t ecc) {
  364. furi_assert(instance);
  365. furi_assert(instance->message);
  366. const char* cstr = furi_string_get_cstr(instance->message);
  367. uint16_t len = (uint16_t)furi_string_size(instance->message);
  368. instance->qrcode = qrcode_alloc(version);
  369. int8_t res = qrcode_initBytes(
  370. instance->qrcode,
  371. instance->qrcode->modules,
  372. (int8_t)mode,
  373. version,
  374. ecc,
  375. (uint8_t*)cstr,
  376. len);
  377. if(res != 0) {
  378. FURI_LOG_E(TAG, "Could not create qrcode");
  379. qrcode_free(instance->qrcode);
  380. instance->qrcode = NULL;
  381. return false;
  382. }
  383. return true;
  384. }
  385. /**
  386. * Determine the minimum version and maximum ECC for a message of a given
  387. * length and mode.
  388. * @param len The length of the message
  389. * @param mode The mode of the encoded message
  390. * @param version Pointer to variable that will receive the minimum version
  391. * @param ecc Pointer to variable that will receive the maximum ECC
  392. * @returns false if the data is too long for the given mode, true otherwise.
  393. */
  394. static bool find_min_version_max_ecc(uint16_t len, uint8_t mode, uint8_t* version, uint8_t* ecc) {
  395. // Figure out the smallest qrcode version that'll fit all of the data - we
  396. // prefer the smallest version to maximize the pixel size of each module to
  397. // improve reader performance. Here, version is the 0-based index. The
  398. // qrcode_initBytes function will want a 1-based version number, so we'll
  399. // add one later.
  400. *ecc = ECC_LOW;
  401. *version = 0;
  402. while(*version < MAX_QRCODE_VERSION && MAX_LENGTH[mode][*ecc][*version] < len) {
  403. (*version)++;
  404. }
  405. if(*version == MAX_QRCODE_VERSION) {
  406. return false;
  407. }
  408. // Figure out the maximum ECC we can use. I shouldn't need to bounds-check
  409. // ecc in this loop because I already know from the loop above that ECC_LOW
  410. // (0) works... don't forget to add one to that version number, since we're
  411. // using it as a 0-based number here, but qrcode_initBytes will want a
  412. // 1-based number...
  413. *ecc = ECC_HIGH;
  414. while(MAX_LENGTH[mode][*ecc][*version] < len) {
  415. (*ecc)--;
  416. }
  417. (*version)++;
  418. return true;
  419. }
  420. /**
  421. * Load a qrcode from a string
  422. * @param instance The qrcode app instance
  423. * @param str The message to encode as a qrcode
  424. * @param desired_mode User selected mode, 255 = unset
  425. * @param desired_version User selected version, 255 = unset
  426. * @param desired_ecc User selected ECC, 255 = unset
  427. * @returns true if the string was successfully loaded
  428. */
  429. static bool qrcode_load_string(
  430. QRCodeApp* instance,
  431. FuriString* str,
  432. uint8_t desired_mode,
  433. uint8_t desired_version,
  434. uint8_t desired_ecc) {
  435. furi_assert(instance);
  436. furi_assert(str);
  437. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  438. if(instance->message) {
  439. furi_string_free(instance->message);
  440. instance->message = NULL;
  441. }
  442. if(instance->qrcode) {
  443. qrcode_free(instance->qrcode);
  444. instance->qrcode = NULL;
  445. }
  446. instance->too_long = false;
  447. instance->show_stats = false;
  448. instance->selected_idx = 0;
  449. instance->edit = false;
  450. bool result = false;
  451. do {
  452. const char* cstr = furi_string_get_cstr(str);
  453. uint16_t len = (uint16_t)furi_string_size(str);
  454. instance->message = furi_string_alloc_set(str);
  455. if(!instance->message) {
  456. FURI_LOG_E(TAG, "Could not allocate message");
  457. break;
  458. }
  459. // figure out the minimum qrcode "mode"
  460. int8_t min_mode = MODE_BYTE;
  461. if(is_numeric(cstr, len))
  462. min_mode = MODE_NUMERIC;
  463. else if(is_alphanumeric(cstr, len))
  464. min_mode = MODE_ALPHANUMERIC;
  465. // determine the maximum "mode"
  466. int8_t max_mode = MAX_QRCODE_MODE;
  467. uint8_t min_version = 0;
  468. uint8_t max_ecc_at_min_version = 0;
  469. while(max_mode >= min_mode &&
  470. !find_min_version_max_ecc(
  471. len, (uint8_t)max_mode, &min_version, &max_ecc_at_min_version)) {
  472. max_mode--;
  473. }
  474. // if the max is less than the min, the message is too long
  475. if(max_mode < min_mode) {
  476. instance->too_long = true;
  477. break;
  478. }
  479. // pick a mode based on the min/max and desired mode
  480. if(desired_mode == 255 || desired_mode < (uint8_t)min_mode) {
  481. desired_mode = (uint8_t)min_mode;
  482. } else if(desired_mode > (uint8_t)max_mode) {
  483. desired_mode = (uint8_t)max_mode;
  484. }
  485. if(desired_mode != (uint8_t)max_mode) {
  486. // if the desired mode equals the max mode, then min_version and
  487. // max_ecc_at_min_version are already set appropriately by the max
  488. // mode loop above... otherwise, we need to calculate them... this
  489. // should always return true because we already know the desired
  490. // mode is appropriate for the data, but, just in case...
  491. if(!find_min_version_max_ecc(
  492. len, desired_mode, &min_version, &max_ecc_at_min_version)) {
  493. instance->too_long = true;
  494. break;
  495. }
  496. }
  497. // ensure desired version and ecc are appropriate
  498. if(desired_version == 255 || desired_version < min_version) {
  499. desired_version = min_version;
  500. } else if(desired_version > MAX_QRCODE_VERSION) {
  501. desired_version = MAX_QRCODE_VERSION;
  502. }
  503. if(desired_version == min_version) {
  504. if(desired_ecc > max_ecc_at_min_version) {
  505. desired_ecc = max_ecc_at_min_version;
  506. }
  507. } else if(desired_ecc > MAX_QRCODE_ECC) {
  508. desired_ecc = MAX_QRCODE_ECC;
  509. }
  510. // Build the qrcode
  511. if(!rebuild_qrcode(instance, desired_mode, desired_version, desired_ecc)) {
  512. break;
  513. }
  514. instance->min_mode = (uint8_t)min_mode;
  515. instance->max_mode = (uint8_t)max_mode;
  516. instance->set_mode = desired_mode;
  517. instance->min_version = min_version;
  518. instance->set_version = desired_version;
  519. instance->max_ecc_at_min_version = max_ecc_at_min_version;
  520. instance->set_ecc = desired_ecc;
  521. result = true;
  522. } while(false);
  523. if(!result) {
  524. if(instance->message) {
  525. furi_string_free(instance->message);
  526. instance->message = NULL;
  527. }
  528. if(instance->qrcode) {
  529. qrcode_free(instance->qrcode);
  530. instance->qrcode = NULL;
  531. }
  532. }
  533. instance->loading = false;
  534. furi_mutex_release(instance->mutex);
  535. return result;
  536. }
  537. /**
  538. * Load a qrcode from a file
  539. * @param instance The qrcode app instance
  540. * @param file_path Path to the file to read
  541. * @returns true if the file was successfully loaded
  542. */
  543. static bool qrcode_load_file(QRCodeApp* instance, const char* file_path) {
  544. furi_assert(instance);
  545. furi_assert(file_path);
  546. FuriString* temp_str = furi_string_alloc();
  547. bool result = false;
  548. Storage* storage = furi_record_open(RECORD_STORAGE);
  549. FlipperFormat* file = flipper_format_file_alloc(storage);
  550. do {
  551. if(!flipper_format_file_open_existing(file, file_path)) break;
  552. uint32_t file_version = 0;
  553. if(!flipper_format_read_header(file, temp_str, &file_version)) break;
  554. if(furi_string_cmp_str(temp_str, QRCODE_FILETYPE) || file_version > QRCODE_FILE_VERSION) {
  555. FURI_LOG_E(TAG, "Incorrect file format or version");
  556. break;
  557. }
  558. uint32_t desired_mode = 255;
  559. uint32_t desired_version = 255;
  560. uint32_t desired_ecc = 255;
  561. if(file_version > 0) {
  562. if(flipper_format_key_exist(file, "QRMode")) {
  563. if(flipper_format_read_string(file, "QRMode", temp_str)) {
  564. if(furi_string_size(temp_str) > 0) {
  565. desired_mode = get_mode_value(furi_string_get_char(temp_str, 0));
  566. }
  567. } else {
  568. FURI_LOG_E(TAG, "Could not read QRMode");
  569. desired_mode = 255;
  570. }
  571. }
  572. if(flipper_format_key_exist(file, "QRVersion")) {
  573. if(flipper_format_read_uint32(file, "QRVersion", &desired_version, 1)) {
  574. if(desired_version > MAX_QRCODE_VERSION) {
  575. FURI_LOG_E(TAG, "Invalid QRVersion");
  576. desired_version = 255;
  577. }
  578. } else {
  579. FURI_LOG_E(TAG, "Could not read QRVersion");
  580. desired_version = 255;
  581. }
  582. }
  583. if(flipper_format_key_exist(file, "QRECC")) {
  584. if(flipper_format_read_string(file, "QRECC", temp_str)) {
  585. if(furi_string_size(temp_str) > 0) {
  586. desired_ecc = get_ecc_value(furi_string_get_char(temp_str, 0));
  587. }
  588. } else {
  589. FURI_LOG_E(TAG, "Could not read QRECC");
  590. desired_ecc = 255;
  591. }
  592. }
  593. }
  594. if(!flipper_format_read_string(file, "Message", temp_str)) {
  595. FURI_LOG_E(TAG, "Message is missing");
  596. break;
  597. }
  598. if(file_version > 0) {
  599. FuriString* msg_cont = furi_string_alloc();
  600. while(flipper_format_key_exist(file, "Message")) {
  601. if(!flipper_format_read_string(file, "Message", msg_cont)) {
  602. FURI_LOG_E(TAG, "Could not read next Message");
  603. break;
  604. }
  605. furi_string_push_back(temp_str, '\n');
  606. furi_string_cat(temp_str, msg_cont);
  607. }
  608. furi_string_free(msg_cont);
  609. }
  610. if(!qrcode_load_string(
  611. instance,
  612. temp_str,
  613. (uint8_t)desired_mode,
  614. (uint8_t)desired_version,
  615. (uint8_t)desired_ecc)) {
  616. break;
  617. }
  618. result = true;
  619. } while(false);
  620. furi_record_close(RECORD_STORAGE);
  621. flipper_format_free(file);
  622. furi_string_free(temp_str);
  623. return result;
  624. }
  625. /**
  626. * Allocate the qrcode app
  627. * @returns a qrcode app instance
  628. */
  629. static QRCodeApp* qrcode_app_alloc() {
  630. QRCodeApp* instance = malloc(sizeof(QRCodeApp));
  631. instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  632. instance->view_port = view_port_alloc();
  633. view_port_draw_callback_set(instance->view_port, render_callback, instance);
  634. view_port_input_callback_set(instance->view_port, input_callback, instance);
  635. instance->gui = furi_record_open(RECORD_GUI);
  636. gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
  637. instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  638. instance->message = NULL;
  639. instance->qrcode = NULL;
  640. instance->loading = true;
  641. instance->too_long = false;
  642. instance->show_stats = false;
  643. instance->selected_idx = 0;
  644. instance->edit = false;
  645. return instance;
  646. }
  647. /**
  648. * Free the qrcode app
  649. * @param qrcode_app The app to free
  650. */
  651. static void qrcode_app_free(QRCodeApp* instance) {
  652. if(instance->message) furi_string_free(instance->message);
  653. if(instance->qrcode) qrcode_free(instance->qrcode);
  654. gui_remove_view_port(instance->gui, instance->view_port);
  655. furi_record_close(RECORD_GUI);
  656. view_port_free(instance->view_port);
  657. furi_message_queue_free(instance->input_queue);
  658. furi_mutex_free(instance->mutex);
  659. free(instance);
  660. }
  661. /** App entrypoint */
  662. int32_t qrcode_app(void* p) {
  663. QRCodeApp* instance = qrcode_app_alloc();
  664. FuriString* file_path = furi_string_alloc();
  665. do {
  666. if(p && strlen(p)) {
  667. furi_string_set(file_path, (const char*)p);
  668. } else {
  669. furi_string_set(file_path, QRCODE_FOLDER);
  670. DialogsFileBrowserOptions browser_options;
  671. dialog_file_browser_set_basic_options(
  672. &browser_options, QRCODE_EXTENSION, &I_qrcode_10px);
  673. browser_options.hide_ext = true;
  674. browser_options.base_path = QRCODE_FOLDER;
  675. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  676. bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
  677. furi_record_close(RECORD_DIALOGS);
  678. if(!res) {
  679. FURI_LOG_E(TAG, "No file selected");
  680. break;
  681. }
  682. }
  683. if(!qrcode_load_file(instance, furi_string_get_cstr(file_path))) {
  684. FURI_LOG_E(TAG, "Unable to load file");
  685. }
  686. InputEvent input;
  687. while(furi_message_queue_get(instance->input_queue, &input, FuriWaitForever) ==
  688. FuriStatusOk) {
  689. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  690. if(input.key == InputKeyBack) {
  691. if(instance->message) {
  692. furi_string_free(instance->message);
  693. instance->message = NULL;
  694. }
  695. if(instance->qrcode) {
  696. qrcode_free(instance->qrcode);
  697. instance->qrcode = NULL;
  698. }
  699. instance->loading = true;
  700. instance->edit = false;
  701. furi_mutex_release(instance->mutex);
  702. break;
  703. } else if(input.key == InputKeyRight) {
  704. instance->show_stats = true;
  705. } else if(input.key == InputKeyLeft) {
  706. instance->show_stats = false;
  707. } else if(instance->show_stats && !instance->loading && instance->qrcode) {
  708. if(input.key == InputKeyUp) {
  709. if(!instance->edit) {
  710. instance->selected_idx = MAX(0, instance->selected_idx - 1);
  711. } else {
  712. if(instance->selected_idx == 0 &&
  713. instance->set_mode < instance->max_mode) {
  714. instance->set_mode++;
  715. } else if(
  716. instance->selected_idx == 1 &&
  717. instance->set_version < MAX_QRCODE_VERSION) {
  718. instance->set_version++;
  719. } else if(instance->selected_idx == 2) {
  720. uint8_t max_ecc = instance->set_version == instance->min_version ?
  721. instance->max_ecc_at_min_version :
  722. ECC_HIGH;
  723. if(instance->set_ecc < max_ecc) {
  724. instance->set_ecc++;
  725. }
  726. }
  727. }
  728. } else if(input.key == InputKeyDown) {
  729. if(!instance->edit) {
  730. instance->selected_idx = MIN(2, instance->selected_idx + 1);
  731. } else {
  732. if(instance->selected_idx == 0 &&
  733. instance->set_mode > instance->min_mode) {
  734. instance->set_mode--;
  735. } else if(
  736. instance->selected_idx == 1 &&
  737. instance->set_version > instance->min_version) {
  738. instance->set_version--;
  739. if(instance->set_version == instance->min_version) {
  740. instance->set_ecc =
  741. MIN(instance->set_ecc, instance->max_ecc_at_min_version);
  742. }
  743. } else if(instance->selected_idx == 2 && instance->set_ecc > 0) {
  744. instance->set_ecc--;
  745. }
  746. }
  747. } else if(input.key == InputKeyOk) {
  748. if(instance->edit && (instance->set_mode != instance->qrcode->mode ||
  749. instance->set_version != instance->qrcode->version ||
  750. instance->set_ecc != instance->qrcode->ecc)) {
  751. uint8_t orig_min_version = instance->min_version;
  752. uint8_t orig_max_ecc_at_min_version = instance->max_ecc_at_min_version;
  753. if(instance->set_mode != instance->qrcode->mode) {
  754. uint16_t len = (uint16_t)furi_string_size(instance->message);
  755. uint8_t min_version = 0;
  756. uint8_t max_ecc_at_min_version = 0;
  757. if(find_min_version_max_ecc(
  758. len,
  759. instance->set_mode,
  760. &min_version,
  761. &max_ecc_at_min_version)) {
  762. if(instance->set_version < min_version) {
  763. instance->set_version = min_version;
  764. }
  765. if(instance->set_version == min_version &&
  766. instance->set_ecc > max_ecc_at_min_version) {
  767. instance->set_ecc = max_ecc_at_min_version;
  768. }
  769. instance->min_version = min_version;
  770. instance->max_ecc_at_min_version = max_ecc_at_min_version;
  771. } else {
  772. instance->set_mode = instance->qrcode->mode;
  773. }
  774. }
  775. QRCode* qrcode = instance->qrcode;
  776. instance->loading = true;
  777. if(rebuild_qrcode(
  778. instance,
  779. instance->set_mode,
  780. instance->set_version,
  781. instance->set_ecc)) {
  782. qrcode_free(qrcode);
  783. } else {
  784. FURI_LOG_E(TAG, "Could not rebuild qrcode");
  785. instance->qrcode = qrcode;
  786. instance->set_mode = qrcode->mode;
  787. instance->set_version = qrcode->version;
  788. instance->set_ecc = qrcode->ecc;
  789. instance->min_version = orig_min_version;
  790. instance->max_ecc_at_min_version = orig_max_ecc_at_min_version;
  791. }
  792. instance->loading = false;
  793. }
  794. instance->edit = !instance->edit;
  795. }
  796. }
  797. furi_mutex_release(instance->mutex);
  798. view_port_update(instance->view_port);
  799. }
  800. if(p && strlen(p)) {
  801. // if started with an arg, exit instead
  802. // of looping back to the browser
  803. break;
  804. }
  805. } while(true);
  806. furi_string_free(file_path);
  807. qrcode_app_free(instance);
  808. return 0;
  809. }