qrcode_app.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. QRCode* qrcode;
  50. bool too_long;
  51. } QRCodeApp;
  52. /**
  53. * Render
  54. * @param canvas The canvas to render to
  55. * @param ctx Context provided to the callback by view_port_draw_callback_set
  56. */
  57. static void render_callback(Canvas* canvas, void* ctx) {
  58. QRCodeApp* instance = ctx;
  59. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  60. canvas_clear(canvas);
  61. canvas_set_color(canvas, ColorBlack);
  62. uint8_t width = canvas_width(canvas);
  63. uint8_t height = canvas_height(canvas);
  64. if (instance->qrcode) {
  65. uint8_t size = instance->qrcode->size;
  66. uint8_t pixel_size = height / size;
  67. uint8_t top = (height - pixel_size * size) / 2;
  68. uint8_t left = (width - pixel_size * size) / 2;
  69. for (uint8_t y = 0; y < size; y++) {
  70. for (uint8_t x = 0; x < size; x++) {
  71. if (qrcode_getModule(instance->qrcode, x, y)) {
  72. if (pixel_size == 1) {
  73. canvas_draw_dot(canvas, left + x * pixel_size, top + y * pixel_size);
  74. } else {
  75. canvas_draw_box(canvas, left + x * pixel_size, top + y * pixel_size, pixel_size, pixel_size);
  76. }
  77. }
  78. }
  79. }
  80. } else {
  81. canvas_set_font(canvas, FontPrimary);
  82. uint8_t font_height = canvas_current_font_height(canvas);
  83. uint8_t margin = (height - font_height * 2) / 3;
  84. canvas_draw_str_aligned(canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
  85. if (instance->too_long) {
  86. canvas_set_font(canvas, FontSecondary);
  87. canvas_draw_str(canvas, width / 2, margin * 2 + font_height, "Message is too long.");
  88. }
  89. }
  90. furi_mutex_release(instance->mutex);
  91. }
  92. /**
  93. * Handle input
  94. * @param input_event The received input event
  95. * @param ctx Context provided to the callback by view_port_input_callback_set
  96. */
  97. static void input_callback(InputEvent* input_event, void* ctx) {
  98. if (input_event->type == InputTypeShort) {
  99. QRCodeApp* instance = ctx;
  100. furi_message_queue_put(instance->input_queue, input_event, 0);
  101. }
  102. }
  103. /**
  104. * Determine if the given string is all numeric
  105. * @param str The string to test
  106. * @returns true if the string is all numeric
  107. */
  108. static bool is_numeric(const char* str, uint16_t len) {
  109. while (len > 0) {
  110. char c = str[--len];
  111. if (c < '0' || c > '9') return false;
  112. }
  113. return true;
  114. }
  115. /**
  116. * Determine if the given string is alphanumeric
  117. * @param str The string to test
  118. * @returns true if the string is alphanumeric
  119. */
  120. static bool is_alphanumeric(const char* str, uint16_t len) {
  121. while (len > 0) {
  122. char c = str[--len];
  123. if (c >= '0' && c <= '9') continue;
  124. if (c >= 'A' && c <= 'Z') continue;
  125. if (c == ' '
  126. || c == '$'
  127. || c == '%'
  128. || c == '*'
  129. || c == '+'
  130. || c == '-'
  131. || c == '.'
  132. || c == '/'
  133. || c == ':')
  134. continue;
  135. return false;
  136. }
  137. return true;
  138. }
  139. /**
  140. * Allocate a qrcode
  141. * @param version qrcode version
  142. * @returns an allocated QRCode
  143. */
  144. static QRCode* qrcode_alloc(uint8_t version) {
  145. QRCode* qrcode = malloc(sizeof(QRCode));
  146. qrcode->modules = malloc(qrcode_getBufferSize(version));
  147. return qrcode;
  148. }
  149. /**
  150. * Free a QRCode
  151. * @param qrcode The QRCode to free
  152. */
  153. static void qrcode_free(QRCode* qrcode) {
  154. free(qrcode->modules);
  155. free(qrcode);
  156. }
  157. /**
  158. * Load a qrcode from a string
  159. * @param instance The qrcode app instance
  160. * @param str The message to encode as a qrcode
  161. * @returns true if the string was successfully loaded
  162. */
  163. static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
  164. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  165. if (instance->qrcode) {
  166. qrcode_free(instance->qrcode);
  167. instance->qrcode = NULL;
  168. }
  169. instance->too_long = false;
  170. bool result = false;
  171. do {
  172. const char* cstr = furi_string_get_cstr(str);
  173. uint16_t len = strlen(cstr);
  174. // figure out the qrcode "mode"
  175. uint8_t mode = MODE_BYTE;
  176. if (is_numeric(cstr, len)) mode = MODE_NUMERIC;
  177. else if (is_alphanumeric(cstr, len)) mode = MODE_ALPHANUMERIC;
  178. // Figure out the smallest qrcode version that'll fit all of the data -
  179. // we prefer the smallest version to maximize the pixel size of each
  180. // module to improve reader performance. Here, version is the 0-based
  181. // index. The qrcode_initBytes function will want a 1-based version
  182. // number, so we'll add one later.
  183. uint8_t ecc = ECC_LOW;
  184. uint8_t version = 0;
  185. while (version < MAX_QRCODE_VERSION && MAX_LENGTH[mode][ecc][version] < len) {
  186. version++;
  187. }
  188. if (version == MAX_QRCODE_VERSION) {
  189. instance->too_long = true;
  190. break;
  191. }
  192. // Figure out the maximum ECC we can use. I shouldn't need to
  193. // bounds-check ecc in this loop because I already know from the loop
  194. // above that ECC_LOW (0) works... don't forget to add one to that
  195. // version number...
  196. ecc = ECC_HIGH;
  197. while (MAX_LENGTH[mode][ecc][version] < len) {
  198. ecc--;
  199. }
  200. version++;
  201. // Build the qrcode
  202. instance->qrcode = qrcode_alloc(version);
  203. int8_t res = qrcode_initBytes(instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
  204. if (res != 0) {
  205. FURI_LOG_E(TAG, "Could not create qrcode");
  206. qrcode_free(instance->qrcode);
  207. instance->qrcode = NULL;
  208. break;
  209. }
  210. result = true;
  211. } while (false);
  212. furi_mutex_release(instance->mutex);
  213. return result;
  214. }
  215. /**
  216. * Load a qrcode from a file
  217. * @param instance The qrcode app instance
  218. * @param file_path Path to the file to read
  219. * @returns true if the file was successfully loaded
  220. */
  221. static bool qrcode_load_file(QRCodeApp* instance, const char* file_path) {
  222. furi_assert(instance);
  223. furi_assert(file_path);
  224. FuriString* temp_str = furi_string_alloc();
  225. bool result = false;
  226. Storage* storage = furi_record_open(RECORD_STORAGE);
  227. FlipperFormat* file = flipper_format_file_alloc(storage);
  228. do {
  229. if (!flipper_format_file_open_existing(file, file_path)) break;
  230. uint32_t version = 0;
  231. if (!flipper_format_read_header(file, temp_str, &version)) break;
  232. if (furi_string_cmp_str(temp_str, QRCODE_FILETYPE)
  233. || version != QRCODE_FILE_VERSION) {
  234. FURI_LOG_E(TAG, "Incorrect file format or version");
  235. break;
  236. }
  237. if (!flipper_format_read_string(file, "Message", temp_str)) {
  238. FURI_LOG_E(TAG, "Message is missing");
  239. break;
  240. }
  241. if (!qrcode_load_string(instance, temp_str)) {
  242. break;
  243. }
  244. result = true;
  245. } while (false);
  246. furi_record_close(RECORD_STORAGE);
  247. flipper_format_free(file);
  248. furi_string_free(temp_str);
  249. return result;
  250. }
  251. /**
  252. * Allocate the qrcode app
  253. * @returns a qrcode app instance
  254. */
  255. static QRCodeApp* qrcode_app_alloc() {
  256. QRCodeApp* instance = malloc(sizeof(QRCodeApp));
  257. instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  258. instance->view_port = view_port_alloc();
  259. view_port_draw_callback_set(instance->view_port, render_callback, instance);
  260. view_port_input_callback_set(instance->view_port, input_callback, instance);
  261. instance->gui = furi_record_open(RECORD_GUI);
  262. gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
  263. instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  264. return instance;
  265. }
  266. /**
  267. * Free the qrcode app
  268. * @param qrcode_app The app to free
  269. */
  270. static void qrcode_app_free(QRCodeApp* instance) {
  271. if (instance->qrcode) qrcode_free(instance->qrcode);
  272. gui_remove_view_port(instance->gui, instance->view_port);
  273. furi_record_close(RECORD_GUI);
  274. view_port_free(instance->view_port);
  275. furi_message_queue_free(instance->input_queue);
  276. furi_mutex_free(instance->mutex);
  277. free(instance);
  278. }
  279. /** App entrypoint */
  280. int32_t qrcode_app(void* p) {
  281. QRCodeApp* instance = qrcode_app_alloc();
  282. FuriString* file_path;
  283. file_path = furi_string_alloc();
  284. do {
  285. if (p && strlen(p)) {
  286. furi_string_set(file_path, (const char*)p);
  287. } else {
  288. furi_string_set(file_path, QRCODE_FOLDER);
  289. DialogsFileBrowserOptions browser_options;
  290. dialog_file_browser_set_basic_options(
  291. &browser_options, QRCODE_EXTENSION, &I_qrcode_10px);
  292. browser_options.hide_ext = true;
  293. browser_options.base_path = QRCODE_FOLDER;
  294. DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
  295. bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
  296. furi_record_close(RECORD_DIALOGS);
  297. if (!res) {
  298. FURI_LOG_E(TAG, "No file selected");
  299. break;
  300. }
  301. }
  302. if (!qrcode_load_file(instance, furi_string_get_cstr(file_path))) {
  303. FURI_LOG_E(TAG, "Unable to load file");
  304. }
  305. InputEvent input;
  306. while (furi_message_queue_get(instance->input_queue, &input, FuriWaitForever) == FuriStatusOk) {
  307. furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
  308. if (input.key == InputKeyBack) {
  309. if (instance->qrcode) {
  310. qrcode_free(instance->qrcode);
  311. instance->qrcode = NULL;
  312. }
  313. furi_mutex_release(instance->mutex);
  314. break;
  315. }
  316. furi_mutex_release(instance->mutex);
  317. view_port_update(instance->view_port);
  318. }
  319. if (p && strlen(p)) {
  320. // if started with an arg, exit instead of going to the browser
  321. break;
  322. }
  323. } while (1);
  324. furi_string_free(file_path);
  325. qrcode_app_free(instance);
  326. return 0;
  327. }