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