qrcode_app.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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 ANY_PATH("qrcodes")
  11. #define QRCODE_EXTENSION ".qrcode"
  12. #define QRCODE_FILETYPE "QRCode"
  13. #define QRCODE_FILE_VERSION 0
  14. /**
  15. * Maximum version is 11 because the f0 screen is only 64 pixels high and
  16. * version 12 is 65x65. Version 11 is 61x61.
  17. */
  18. #define MAX_QRCODE_VERSION 11
  19. /** Maximum length by mode, ecc, and version */
  20. static const uint16_t MAX_LENGTH[3][4][MAX_QRCODE_VERSION] = {
  21. {
  22. // Numeric
  23. {41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772}, // Low
  24. {34, 63, 101, 149, 202, 255, 293, 365, 432, 513, 604}, // Medium
  25. {27, 48, 77, 111, 144, 178, 207, 259, 312, 364, 427}, // Quartile
  26. {17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331}, // High
  27. },
  28. {
  29. // Alphanumeric
  30. {25, 47, 77, 114, 154, 195, 224, 279, 335, 395, 468}, // Low
  31. {20, 38, 61, 90, 122, 154, 178, 221, 262, 311, 366}, // Medium
  32. {16, 29, 47, 67, 87, 108, 125, 157, 189, 221, 259}, // Quartile
  33. {10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200}, // High
  34. },
  35. {
  36. // Binary
  37. {17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321}, // Low
  38. {14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251}, // Medium
  39. {11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177}, // Quartile
  40. {7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137}, // High
  41. },
  42. };
  43. /** Main app instance */
  44. typedef struct {
  45. FuriMessageQueue* input_queue;
  46. Gui* gui;
  47. ViewPort* view_port;
  48. FuriMutex** mutex;
  49. FuriString* message;
  50. QRCode* qrcode;
  51. uint8_t min_version;
  52. uint8_t max_ecc_at_min_version;
  53. bool loading;
  54. bool too_long;
  55. bool show_stats;
  56. uint8_t selected_idx;
  57. bool edit;
  58. uint8_t set_version;
  59. uint8_t set_ecc;
  60. } QRCodeApp;
  61. /**
  62. * @param ecc ECC number
  63. * @returns a character corresponding to the ecc level
  64. */
  65. static char get_ecc_char(uint8_t ecc) {
  66. switch(ecc) {
  67. case 0:
  68. return 'L';
  69. case 1:
  70. return 'M';
  71. case 2:
  72. return 'Q';
  73. case 3:
  74. return 'H';
  75. default:
  76. return '?';
  77. }
  78. }
  79. /**
  80. * @param mode qrcode mode
  81. * @returns a character corresponding to the mode
  82. */
  83. static char get_mode_char(uint8_t mode) {
  84. switch(mode) {
  85. case 0:
  86. return 'N';
  87. case 1:
  88. return 'A';
  89. case 2:
  90. return 'B';
  91. case 3:
  92. return 'K';
  93. default:
  94. return '?';
  95. }
  96. }
  97. /**
  98. * Render
  99. * @param canvas The canvas to render to
  100. * @param ctx Context provided to the callback by view_port_draw_callback_set
  101. */
  102. static void render_callback(Canvas* canvas, void* ctx) {
  103. furi_assert(canvas);
  104. furi_assert(ctx);
  105. QRCodeApp* instance = ctx;
  106. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  107. canvas_clear(canvas);
  108. canvas_set_color(canvas, ColorBlack);
  109. canvas_set_font(canvas, FontPrimary);
  110. uint8_t font_height = canvas_current_font_height(canvas);
  111. uint8_t width = canvas_width(canvas);
  112. uint8_t height = canvas_height(canvas);
  113. if(instance->loading) {
  114. canvas_draw_str_aligned(
  115. canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
  116. } else if(instance->qrcode) {
  117. uint8_t size = instance->qrcode->size;
  118. uint8_t pixel_size = height / size;
  119. uint8_t top = (height - pixel_size * size) / 2;
  120. uint8_t left = ((instance->show_stats ? 65 : width) - pixel_size * size) / 2;
  121. for(uint8_t y = 0; y < size; y++) {
  122. for(uint8_t x = 0; x < size; x++) {
  123. if(qrcode_getModule(instance->qrcode, x, y)) {
  124. if(pixel_size == 1) {
  125. canvas_draw_dot(canvas, left + x * pixel_size, top + y * pixel_size);
  126. } else {
  127. canvas_draw_box(
  128. canvas,
  129. left + x * pixel_size,
  130. top + y * pixel_size,
  131. pixel_size,
  132. pixel_size);
  133. }
  134. }
  135. }
  136. }
  137. if(instance->show_stats) {
  138. top = 10;
  139. left = 66;
  140. FuriString* str = furi_string_alloc();
  141. if(!instance->edit || instance->selected_idx == 0) {
  142. furi_string_printf(str, "Ver: %i", instance->set_version);
  143. canvas_draw_str(canvas, left + 5, top + font_height, furi_string_get_cstr(str));
  144. if(instance->selected_idx == 0) {
  145. canvas_draw_triangle(
  146. canvas,
  147. left,
  148. top + font_height / 2,
  149. font_height - 4,
  150. 4,
  151. CanvasDirectionLeftToRight);
  152. }
  153. if(instance->edit) {
  154. uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "Ver: 8") / 2;
  155. canvas_draw_triangle(
  156. canvas, arrow_left, top, font_height - 4, 4, CanvasDirectionBottomToTop);
  157. canvas_draw_triangle(
  158. canvas,
  159. arrow_left,
  160. top + font_height + 1,
  161. font_height - 4,
  162. 4,
  163. CanvasDirectionTopToBottom);
  164. }
  165. }
  166. if(!instance->edit || instance->selected_idx == 1) {
  167. furi_string_printf(str, "ECC: %c", get_ecc_char(instance->set_ecc));
  168. canvas_draw_str(
  169. canvas, left + 5, 2 * font_height + top + 2, furi_string_get_cstr(str));
  170. if(instance->selected_idx == 1) {
  171. canvas_draw_triangle(
  172. canvas,
  173. left,
  174. 3 * font_height / 2 + top + 2,
  175. font_height - 4,
  176. 4,
  177. CanvasDirectionLeftToRight);
  178. }
  179. if(instance->edit) {
  180. uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "ECC: H") / 2;
  181. canvas_draw_triangle(
  182. canvas,
  183. arrow_left,
  184. font_height + top + 2,
  185. font_height - 4,
  186. 4,
  187. CanvasDirectionBottomToTop);
  188. canvas_draw_triangle(
  189. canvas,
  190. arrow_left,
  191. 2 * font_height + top + 3,
  192. font_height - 4,
  193. 4,
  194. CanvasDirectionTopToBottom);
  195. }
  196. }
  197. if(!instance->edit) {
  198. furi_string_printf(str, "Mod: %c", get_mode_char(instance->qrcode->mode));
  199. canvas_draw_str(
  200. canvas, left + 5, 3 * font_height + top + 4, furi_string_get_cstr(str));
  201. }
  202. furi_string_free(str);
  203. }
  204. } else {
  205. uint8_t margin = (height - font_height * 2) / 3;
  206. canvas_draw_str_aligned(
  207. canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
  208. if(instance->too_long) {
  209. canvas_set_font(canvas, FontSecondary);
  210. canvas_draw_str(canvas, width / 2, margin * 2 + font_height, "Message is too long.");
  211. }
  212. }
  213. furi_mutex_release(instance->mutex);
  214. }
  215. /**
  216. * Handle input
  217. * @param input_event The received input event
  218. * @param ctx Context provided to the callback by view_port_input_callback_set
  219. */
  220. static void input_callback(InputEvent* input_event, void* ctx) {
  221. furi_assert(input_event);
  222. furi_assert(ctx);
  223. if(input_event->type == InputTypeShort) {
  224. QRCodeApp* instance = ctx;
  225. furi_message_queue_put(instance->input_queue, input_event, 0);
  226. }
  227. }
  228. /**
  229. * Determine if the given string is all numeric
  230. * @param str The string to test
  231. * @returns true if the string is all numeric
  232. */
  233. static bool is_numeric(const char* str, uint16_t len) {
  234. furi_assert(str);
  235. while(len > 0) {
  236. char c = str[--len];
  237. if(c < '0' || c > '9') return false;
  238. }
  239. return true;
  240. }
  241. /**
  242. * Determine if the given string is alphanumeric
  243. * @param str The string to test
  244. * @returns true if the string is alphanumeric
  245. */
  246. static bool is_alphanumeric(const char* str, uint16_t len) {
  247. furi_assert(str);
  248. while(len > 0) {
  249. char c = str[--len];
  250. if(c >= '0' && c <= '9') continue;
  251. if(c >= 'A' && c <= 'Z') continue;
  252. if(c == ' ' || c == '$' || c == '%' || c == '*' || c == '+' || c == '-' || c == '.' ||
  253. c == '/' || c == ':')
  254. continue;
  255. return false;
  256. }
  257. return true;
  258. }
  259. /**
  260. * Allocate a qrcode
  261. * @param version qrcode version
  262. * @returns an allocated QRCode
  263. */
  264. static QRCode* qrcode_alloc(uint8_t version) {
  265. QRCode* qrcode = malloc(sizeof(QRCode));
  266. qrcode->modules = malloc(qrcode_getBufferSize(version));
  267. return qrcode;
  268. }
  269. /**
  270. * Free a QRCode
  271. * @param qrcode The QRCode to free
  272. */
  273. static void qrcode_free(QRCode* qrcode) {
  274. furi_assert(qrcode);
  275. free(qrcode->modules);
  276. free(qrcode);
  277. }
  278. /**
  279. * Rebuild the qrcode. Assumes that instance->message is the message to encode,
  280. * that the mutex has been acquired, and the specified version/ecc will be
  281. * sufficiently large enough to encode the full message. It is also assumed
  282. * that the old qrcode will be free'd by the caller.
  283. * @param instance The qrcode app instance
  284. * @param version The qrcode version to use
  285. * @param ecc The qrcode ECC level to use
  286. * @returns true if the qrcode was successfully created
  287. */
  288. static bool rebuild_qrcode(QRCodeApp* instance, uint8_t version, uint8_t ecc) {
  289. furi_assert(instance);
  290. furi_assert(instance->message);
  291. const char* cstr = furi_string_get_cstr(instance->message);
  292. uint16_t len = strlen(cstr);
  293. instance->qrcode = qrcode_alloc(version);
  294. int8_t res = qrcode_initBytes(
  295. instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
  296. if(res != 0) {
  297. FURI_LOG_E(TAG, "Could not create qrcode");
  298. qrcode_free(instance->qrcode);
  299. instance->qrcode = NULL;
  300. return false;
  301. }
  302. return true;
  303. }
  304. /**
  305. * Load a qrcode from a string
  306. * @param instance The qrcode app instance
  307. * @param str The message to encode as a qrcode
  308. * @returns true if the string was successfully loaded
  309. */
  310. static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
  311. furi_assert(instance);
  312. furi_assert(str);
  313. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  314. if(instance->message) {
  315. furi_string_free(instance->message);
  316. instance->message = NULL;
  317. }
  318. if(instance->qrcode) {
  319. qrcode_free(instance->qrcode);
  320. instance->qrcode = NULL;
  321. }
  322. instance->too_long = false;
  323. instance->show_stats = false;
  324. instance->selected_idx = 0;
  325. instance->edit = false;
  326. bool result = false;
  327. do {
  328. const char* cstr = furi_string_get_cstr(str);
  329. uint16_t len = strlen(cstr);
  330. instance->message = furi_string_alloc_set(str);
  331. if(!instance->message) {
  332. FURI_LOG_E(TAG, "Could not allocate message");
  333. break;
  334. }
  335. // figure out the qrcode "mode"
  336. uint8_t mode = MODE_BYTE;
  337. if(is_numeric(cstr, len))
  338. mode = MODE_NUMERIC;
  339. else if(is_alphanumeric(cstr, len))
  340. mode = MODE_ALPHANUMERIC;
  341. // Figure out the smallest qrcode version that'll fit all of the data -
  342. // we prefer the smallest version to maximize the pixel size of each
  343. // module to improve reader performance. Here, version is the 0-based
  344. // index. The qrcode_initBytes function will want a 1-based version
  345. // number, so we'll add one later.
  346. uint8_t ecc = ECC_LOW;
  347. uint8_t version = 0;
  348. while(version < MAX_QRCODE_VERSION && MAX_LENGTH[mode][ecc][version] < len) {
  349. version++;
  350. }
  351. if(version == MAX_QRCODE_VERSION) {
  352. instance->too_long = true;
  353. break;
  354. }
  355. // Figure out the maximum ECC we can use. I shouldn't need to
  356. // bounds-check ecc in this loop because I already know from the loop
  357. // above that ECC_LOW (0) works... don't forget to add one to that
  358. // version number...
  359. ecc = ECC_HIGH;
  360. while(MAX_LENGTH[mode][ecc][version] < len) {
  361. ecc--;
  362. }
  363. version++;
  364. // Build the qrcode
  365. if(!rebuild_qrcode(instance, version, ecc)) {
  366. furi_string_free(instance->message);
  367. instance->message = NULL;
  368. break;
  369. }
  370. instance->min_version = instance->set_version = version;
  371. instance->max_ecc_at_min_version = instance->set_ecc = ecc;
  372. result = true;
  373. } while(false);
  374. instance->loading = false;
  375. furi_mutex_release(instance->mutex);
  376. return result;
  377. }
  378. /**
  379. * Load a qrcode from a file
  380. * @param instance The qrcode app instance
  381. * @param file_path Path to the file to read
  382. * @returns true if the file was successfully loaded
  383. */
  384. static bool qrcode_load_file(QRCodeApp* instance, const char* file_path) {
  385. furi_assert(instance);
  386. furi_assert(file_path);
  387. FuriString* temp_str = furi_string_alloc();
  388. bool result = false;
  389. Storage* storage = furi_record_open(RECORD_STORAGE);
  390. FlipperFormat* file = flipper_format_file_alloc(storage);
  391. do {
  392. if(!flipper_format_file_open_existing(file, file_path)) break;
  393. uint32_t version = 0;
  394. if(!flipper_format_read_header(file, temp_str, &version)) break;
  395. if(furi_string_cmp_str(temp_str, QRCODE_FILETYPE) || version != QRCODE_FILE_VERSION) {
  396. FURI_LOG_E(TAG, "Incorrect file format or version");
  397. break;
  398. }
  399. if(!flipper_format_read_string(file, "Message", temp_str)) {
  400. FURI_LOG_E(TAG, "Message is missing");
  401. break;
  402. }
  403. if(!qrcode_load_string(instance, temp_str)) {
  404. break;
  405. }
  406. result = true;
  407. } while(false);
  408. furi_record_close(RECORD_STORAGE);
  409. flipper_format_free(file);
  410. furi_string_free(temp_str);
  411. return result;
  412. }
  413. /**
  414. * Allocate the qrcode app
  415. * @returns a qrcode app instance
  416. */
  417. static QRCodeApp* qrcode_app_alloc() {
  418. QRCodeApp* instance = malloc(sizeof(QRCodeApp));
  419. instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  420. instance->view_port = view_port_alloc();
  421. view_port_draw_callback_set(instance->view_port, render_callback, instance);
  422. view_port_input_callback_set(instance->view_port, input_callback, instance);
  423. instance->gui = furi_record_open(RECORD_GUI);
  424. gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
  425. instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  426. instance->message = NULL;
  427. instance->qrcode = NULL;
  428. instance->loading = true;
  429. instance->too_long = false;
  430. instance->show_stats = false;
  431. instance->selected_idx = 0;
  432. instance->edit = false;
  433. return instance;
  434. }
  435. /**
  436. * Free the qrcode app
  437. * @param qrcode_app The app to free
  438. */
  439. static void qrcode_app_free(QRCodeApp* instance) {
  440. if(instance->message) furi_string_free(instance->message);
  441. if(instance->qrcode) qrcode_free(instance->qrcode);
  442. gui_remove_view_port(instance->gui, instance->view_port);
  443. furi_record_close(RECORD_GUI);
  444. view_port_free(instance->view_port);
  445. furi_message_queue_free(instance->input_queue);
  446. furi_mutex_free(instance->mutex);
  447. free(instance);
  448. }
  449. /** App entrypoint */
  450. int32_t qrcode_app(void* p) {
  451. QRCodeApp* instance = qrcode_app_alloc();
  452. FuriString* file_path = furi_string_alloc();
  453. do {
  454. if(p && strlen(p)) {
  455. furi_string_set(file_path, (const char*)p);
  456. } else {
  457. furi_string_set(file_path, QRCODE_FOLDER);
  458. DialogsFileBrowserOptions browser_options;
  459. dialog_file_browser_set_basic_options(
  460. &browser_options, QRCODE_EXTENSION, &I_qrcode_10px);
  461. browser_options.hide_ext = true;
  462. browser_options.base_path = QRCODE_FOLDER;
  463. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  464. bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
  465. furi_record_close(RECORD_DIALOGS);
  466. if(!res) {
  467. FURI_LOG_E(TAG, "No file selected");
  468. break;
  469. }
  470. }
  471. if(!qrcode_load_file(instance, furi_string_get_cstr(file_path))) {
  472. FURI_LOG_E(TAG, "Unable to load file");
  473. }
  474. InputEvent input;
  475. while(furi_message_queue_get(instance->input_queue, &input, FuriWaitForever) ==
  476. FuriStatusOk) {
  477. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  478. if(input.key == InputKeyBack) {
  479. if(instance->message) {
  480. furi_string_free(instance->message);
  481. instance->message = NULL;
  482. }
  483. if(instance->qrcode) {
  484. qrcode_free(instance->qrcode);
  485. instance->qrcode = NULL;
  486. }
  487. instance->loading = true;
  488. instance->edit = false;
  489. furi_mutex_release(instance->mutex);
  490. break;
  491. } else if(input.key == InputKeyRight) {
  492. instance->show_stats = true;
  493. } else if(input.key == InputKeyLeft) {
  494. instance->show_stats = false;
  495. } else if(instance->show_stats && !instance->loading && instance->qrcode) {
  496. if(input.key == InputKeyUp) {
  497. if(!instance->edit) {
  498. instance->selected_idx = MAX(0, instance->selected_idx - 1);
  499. } else {
  500. if(instance->selected_idx == 0 &&
  501. instance->set_version < MAX_QRCODE_VERSION) {
  502. instance->set_version++;
  503. } else if(instance->selected_idx == 1) {
  504. uint8_t max_ecc = instance->set_version == instance->min_version ?
  505. instance->max_ecc_at_min_version :
  506. ECC_HIGH;
  507. if(instance->set_ecc < max_ecc) {
  508. instance->set_ecc++;
  509. }
  510. }
  511. }
  512. } else if(input.key == InputKeyDown) {
  513. if(!instance->edit) {
  514. instance->selected_idx = MIN(1, instance->selected_idx + 1);
  515. } else {
  516. if(instance->selected_idx == 0 &&
  517. instance->set_version > instance->min_version) {
  518. instance->set_version--;
  519. if(instance->set_version == instance->min_version) {
  520. instance->set_ecc =
  521. MAX(instance->set_ecc, instance->max_ecc_at_min_version);
  522. }
  523. } else if(instance->selected_idx == 1 && instance->set_ecc > 0) {
  524. instance->set_ecc--;
  525. }
  526. }
  527. } else if(input.key == InputKeyOk) {
  528. if(instance->edit && (instance->set_version != instance->qrcode->version ||
  529. instance->set_ecc != instance->qrcode->ecc)) {
  530. QRCode* qrcode = instance->qrcode;
  531. instance->loading = true;
  532. if(rebuild_qrcode(instance, instance->set_version, instance->set_ecc)) {
  533. qrcode_free(qrcode);
  534. } else {
  535. FURI_LOG_E(TAG, "Could not rebuild qrcode");
  536. instance->qrcode = qrcode;
  537. instance->set_version = qrcode->version;
  538. instance->set_ecc = qrcode->ecc;
  539. }
  540. instance->loading = false;
  541. }
  542. instance->edit = !instance->edit;
  543. }
  544. }
  545. furi_mutex_release(instance->mutex);
  546. view_port_update(instance->view_port);
  547. }
  548. if(p && strlen(p)) {
  549. // if started with an arg, exit instead
  550. // of looping back to the browser
  551. break;
  552. }
  553. } while(true);
  554. furi_string_free(file_path);
  555. qrcode_app_free(instance);
  556. return 0;
  557. }