]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
4368277c | 2 | |
da3920c3 | 3 | #include "qrcode-util.h" |
f1b82359 ZJS |
4 | |
5 | #if HAVE_QRENCODE | |
6 | #include <qrencode.h> | |
69a283c5 | 7 | #include <stdio.h> |
f1b82359 | 8 | |
b7120388 | 9 | #include "ansi-color.h" |
f1b82359 ZJS |
10 | #include "dlfcn-util.h" |
11 | #include "locale-util.h" | |
1622ef77 | 12 | #include "log.h" |
3f5225d7 | 13 | #include "strv.h" |
da3920c3 LP |
14 | #include "terminal-util.h" |
15 | ||
16 | #define ANSI_WHITE_ON_BLACK "\033[40;37;1m" | |
e193378d YW |
17 | #define UNICODE_FULL_BLOCK UTF8("█") |
18 | #define UNICODE_LOWER_HALF_BLOCK UTF8("▄") | |
19 | #define UNICODE_UPPER_HALF_BLOCK UTF8("▀") | |
da3920c3 | 20 | |
ad914843 LP |
21 | static void *qrcode_dl = NULL; |
22 | ||
5c672e90 ZJS |
23 | static DLSYM_PROTOTYPE(QRcode_encodeString) = NULL; |
24 | static DLSYM_PROTOTYPE(QRcode_free) = NULL; | |
ad914843 LP |
25 | |
26 | int dlopen_qrencode(void) { | |
3f5225d7 DDM |
27 | int r; |
28 | ||
cd7c2077 LP |
29 | ELF_NOTE_DLOPEN("qrencode", |
30 | "Support for generating QR codes", | |
31 | ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, | |
32 | "libqrencode.so.4", "libqrencode.so.3"); | |
33 | ||
3f5225d7 DDM |
34 | FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { |
35 | r = dlopen_many_sym_or_warn( | |
36 | &qrcode_dl, s, LOG_DEBUG, | |
ad914843 | 37 | DLSYM_ARG(QRcode_encodeString), |
1622ef77 | 38 | DLSYM_ARG(QRcode_free)); |
3f5225d7 DDM |
39 | if (r >= 0) |
40 | break; | |
41 | } | |
42 | ||
43 | return r; | |
ad914843 LP |
44 | } |
45 | ||
fc7eb132 OJ |
46 | static void print_border(FILE *output, unsigned width, unsigned row, unsigned column) { |
47 | assert(output); | |
48 | assert(width); | |
da3920c3 | 49 | |
fc7eb132 OJ |
50 | if (row != UINT_MAX && column != UINT_MAX) { |
51 | int r, fd; | |
da3920c3 | 52 | |
fc7eb132 OJ |
53 | fd = fileno(output); |
54 | if (fd < 0) | |
55 | return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); | |
56 | ||
53f0ab51 | 57 | r = terminal_set_cursor_position(fd, row, column); |
fc7eb132 OJ |
58 | if (r < 0) |
59 | log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); | |
60 | ||
61 | /* Four rows of border */ | |
62 | for (unsigned y = 0; y < 4; y += 2) { | |
63 | fputs(ANSI_WHITE_ON_BLACK, output); | |
64 | ||
65 | for (unsigned x = 0; x < 4 + width + 4; x++) | |
66 | fputs(UNICODE_FULL_BLOCK, output); | |
67 | ||
68 | fputs(ANSI_NORMAL "\n", output); | |
53f0ab51 | 69 | r = terminal_set_cursor_position(fd, row + 1, column); |
fc7eb132 OJ |
70 | if (r < 0) |
71 | log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); | |
72 | } | |
73 | } else { | |
74 | /* Four rows of border */ | |
75 | for (unsigned y = 0; y < 4; y += 2) { | |
76 | fputs(ANSI_WHITE_ON_BLACK, output); | |
77 | ||
78 | for (unsigned x = 0; x < 4 + width + 4; x++) | |
79 | fputs(UNICODE_FULL_BLOCK, output); | |
80 | ||
81 | fputs(ANSI_NORMAL "\n", output); | |
82 | } | |
da3920c3 LP |
83 | } |
84 | } | |
85 | ||
9ec7d7ae | 86 | static void write_qrcode(FILE *output, QRcode *qr, unsigned row, unsigned column) { |
da3920c3 LP |
87 | assert(qr); |
88 | ||
89 | if (!output) | |
90 | output = stdout; | |
91 | ||
fc7eb132 OJ |
92 | print_border(output, qr->width, row, column); |
93 | ||
94 | if (row != UINT_MAX && column != UINT_MAX) { | |
95 | /* After printing two rows of top border, we need to move the cursor down two rows before starting to print the actual QR code */ | |
96 | int r, fd, move_down = 2; | |
97 | fd = fileno(output); | |
98 | if (fd < 0) | |
99 | return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); | |
100 | ||
53f0ab51 | 101 | r = terminal_set_cursor_position(fd, row + move_down, column); |
fc7eb132 OJ |
102 | if (r < 0) |
103 | log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); | |
104 | ||
105 | for (unsigned y = 0; y < (unsigned) qr->width; y += 2) { | |
106 | const uint8_t *row1 = qr->data + qr->width * y; | |
107 | const uint8_t *row2 = row1 + qr->width; | |
108 | ||
109 | fputs(ANSI_WHITE_ON_BLACK, output); | |
110 | ||
111 | for (unsigned x = 0; x < 4; x++) | |
112 | fputs(UNICODE_FULL_BLOCK, output); | |
113 | ||
114 | for (unsigned x = 0; x < (unsigned) qr->width; x++) { | |
115 | bool a, b; | |
da3920c3 | 116 | |
fc7eb132 OJ |
117 | a = row1[x] & 1; |
118 | b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; | |
da3920c3 | 119 | |
fc7eb132 OJ |
120 | if (a && b) |
121 | fputc(' ', output); | |
122 | else if (a) | |
123 | fputs(UNICODE_LOWER_HALF_BLOCK, output); | |
124 | else if (b) | |
125 | fputs(UNICODE_UPPER_HALF_BLOCK, output); | |
126 | else | |
127 | fputs(UNICODE_FULL_BLOCK, output); | |
128 | } | |
129 | ||
130 | for (unsigned x = 0; x < 4; x++) | |
131 | fputs(UNICODE_FULL_BLOCK, output); | |
53f0ab51 | 132 | r = terminal_set_cursor_position(fd, row + move_down, column); |
fc7eb132 OJ |
133 | if (r < 0) |
134 | log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); | |
135 | move_down += 1; | |
136 | fputs(ANSI_NORMAL "\n", output); | |
137 | } | |
da3920c3 | 138 | |
fc7eb132 OJ |
139 | print_border(output, qr->width, row + move_down, column); |
140 | } else { | |
da3920c3 | 141 | |
fc7eb132 OJ |
142 | for (unsigned y = 0; y < (unsigned) qr->width; y += 2) { |
143 | const uint8_t *row1 = qr->data + qr->width * y; | |
144 | const uint8_t *row2 = row1 + qr->width; | |
da3920c3 | 145 | |
fc7eb132 OJ |
146 | fputs(ANSI_WHITE_ON_BLACK, output); |
147 | for (unsigned x = 0; x < 4; x++) | |
5535484e | 148 | fputs(UNICODE_FULL_BLOCK, output); |
fc7eb132 OJ |
149 | |
150 | for (unsigned x = 0; x < (unsigned) qr->width; x++) { | |
151 | bool a, b; | |
152 | ||
153 | a = row1[x] & 1; | |
154 | b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; | |
155 | ||
156 | if (a && b) | |
157 | fputc(' ', output); | |
158 | else if (a) | |
159 | fputs(UNICODE_LOWER_HALF_BLOCK, output); | |
160 | else if (b) | |
161 | fputs(UNICODE_UPPER_HALF_BLOCK, output); | |
162 | else | |
163 | fputs(UNICODE_FULL_BLOCK, output); | |
164 | } | |
165 | ||
166 | for (unsigned x = 0; x < 4; x++) | |
167 | fputs(UNICODE_FULL_BLOCK, output); | |
168 | fputs(ANSI_NORMAL "\n", output); | |
da3920c3 LP |
169 | } |
170 | ||
fc7eb132 | 171 | print_border(output, qr->width, row, column); |
da3920c3 LP |
172 | } |
173 | ||
da3920c3 LP |
174 | fflush(output); |
175 | } | |
f1b82359 | 176 | |
439306da ZJS |
177 | DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(QRcode*, sym_QRcode_free, NULL); |
178 | ||
5a64c869 ZJS |
179 | int print_qrcode_full( |
180 | FILE *out, | |
181 | const char *header, | |
182 | const char *string, | |
183 | unsigned row, | |
184 | unsigned column, | |
185 | unsigned tty_width, | |
186 | unsigned tty_height, | |
187 | bool check_tty) { | |
188 | ||
f1b82359 ZJS |
189 | int r; |
190 | ||
5bc9ea07 | 191 | /* If this is not a UTF-8 system or ANSI colors aren't supported/disabled don't print any QR |
f1b82359 | 192 | * codes */ |
f0764b98 ZJS |
193 | if (!is_locale_utf8()) |
194 | return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not an UTF-8 system, cannot print qrcode"); | |
5a64c869 | 195 | if (check_tty && !colors_enabled()) |
f0764b98 | 196 | return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Colors are disabled, cannot print qrcode"); |
f1b82359 | 197 | |
ad914843 | 198 | r = dlopen_qrencode(); |
f1b82359 ZJS |
199 | if (r < 0) |
200 | return r; | |
201 | ||
439306da ZJS |
202 | _cleanup_(sym_QRcode_freep) QRcode *qr = |
203 | sym_QRcode_encodeString(string, 0, QR_ECLEVEL_L, QR_MODE_8, 1); | |
f1b82359 | 204 | if (!qr) |
f0764b98 | 205 | return log_oom_debug(); |
f1b82359 | 206 | |
fc7eb132 | 207 | if (row != UINT_MAX && column != UINT_MAX) { |
fc7eb132 | 208 | unsigned qr_code_width, qr_code_height; |
f0764b98 ZJS |
209 | |
210 | int fd = fileno(out); | |
fc7eb132 OJ |
211 | if (fd < 0) |
212 | return log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); | |
fc7eb132 | 213 | |
f0764b98 | 214 | qr_code_width = qr_code_height = qr->width + 8; |
fc7eb132 OJ |
215 | if (column + qr_code_width > tty_width) |
216 | column = tty_width - qr_code_width; | |
217 | ||
218 | /* Terminal characters are twice as high as they are wide so it's qr_code_height / 2, | |
f0764b98 | 219 | * our QR code prints an extra new line, so we have -1 as well */ |
fc7eb132 OJ |
220 | if (row + qr_code_height > tty_height) |
221 | row = tty_height - (qr_code_height / 2 ) - 1; | |
222 | ||
223 | if (header) { | |
53f0ab51 | 224 | r = terminal_set_cursor_position(fd, row - 2, tty_width - qr_code_width - 2); |
fc7eb132 OJ |
225 | if (r < 0) |
226 | log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); | |
227 | ||
228 | fprintf(out, "%s:\n\n", header); | |
229 | } | |
230 | } else | |
231 | if (header) | |
232 | fprintf(out, "\n%s:\n\n", header); | |
f1b82359 | 233 | |
fc7eb132 | 234 | write_qrcode(out, qr, row, column); |
f1b82359 ZJS |
235 | fputc('\n', out); |
236 | ||
f1b82359 ZJS |
237 | return 0; |
238 | } | |
239 | #endif |