1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "qrcode-util.h"
9 #include "ansi-color.h"
10 #include "dlfcn-util.h"
11 #include "locale-util.h"
14 #include "terminal-util.h"
16 #define ANSI_WHITE_ON_BLACK "\033[40;37;1m"
17 #define UNICODE_FULL_BLOCK UTF8("█")
18 #define UNICODE_LOWER_HALF_BLOCK UTF8("▄")
19 #define UNICODE_UPPER_HALF_BLOCK UTF8("▀")
21 static void *qrcode_dl
= NULL
;
23 static DLSYM_PROTOTYPE(QRcode_encodeString
) = NULL
;
24 static DLSYM_PROTOTYPE(QRcode_free
) = NULL
;
26 int dlopen_qrencode(void) {
29 ELF_NOTE_DLOPEN("qrencode",
30 "Support for generating QR codes",
31 ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED
,
32 "libqrencode.so.4", "libqrencode.so.3");
34 FOREACH_STRING(s
, "libqrencode.so.4", "libqrencode.so.3") {
35 r
= dlopen_many_sym_or_warn(
36 &qrcode_dl
, s
, LOG_DEBUG
,
37 DLSYM_ARG(QRcode_encodeString
),
38 DLSYM_ARG(QRcode_free
));
46 static void print_border(FILE *output
, unsigned width
, unsigned row
, unsigned column
) {
50 if (row
!= UINT_MAX
&& column
!= UINT_MAX
) {
55 return (void)log_debug_errno(errno
, "Failed to get file descriptor from the file stream: %m");
57 r
= terminal_set_cursor_position(fd
, row
, column
);
59 log_warning_errno(r
, "Failed to move terminal cursor position, ignoring: %m");
61 /* Four rows of border */
62 for (unsigned y
= 0; y
< 4; y
+= 2) {
63 fputs(ANSI_WHITE_ON_BLACK
, output
);
65 for (unsigned x
= 0; x
< 4 + width
+ 4; x
++)
66 fputs(UNICODE_FULL_BLOCK
, output
);
68 fputs(ANSI_NORMAL
"\n", output
);
69 r
= terminal_set_cursor_position(fd
, row
+ 1, column
);
71 log_warning_errno(r
, "Failed to move terminal cursor position, ignoring: %m");
74 /* Four rows of border */
75 for (unsigned y
= 0; y
< 4; y
+= 2) {
76 fputs(ANSI_WHITE_ON_BLACK
, output
);
78 for (unsigned x
= 0; x
< 4 + width
+ 4; x
++)
79 fputs(UNICODE_FULL_BLOCK
, output
);
81 fputs(ANSI_NORMAL
"\n", output
);
86 static void write_qrcode(FILE *output
, QRcode
*qr
, unsigned row
, unsigned column
) {
92 print_border(output
, qr
->width
, row
, column
);
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;
99 return (void)log_debug_errno(errno
, "Failed to get file descriptor from the file stream: %m");
101 r
= terminal_set_cursor_position(fd
, row
+ move_down
, column
);
103 log_warning_errno(r
, "Failed to move terminal cursor position, ignoring: %m");
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
;
109 fputs(ANSI_WHITE_ON_BLACK
, output
);
111 for (unsigned x
= 0; x
< 4; x
++)
112 fputs(UNICODE_FULL_BLOCK
, output
);
114 for (unsigned x
= 0; x
< (unsigned) qr
->width
; x
++) {
118 b
= (y
+1) < (unsigned) qr
->width
? (row2
[x
] & 1) : false;
123 fputs(UNICODE_LOWER_HALF_BLOCK
, output
);
125 fputs(UNICODE_UPPER_HALF_BLOCK
, output
);
127 fputs(UNICODE_FULL_BLOCK
, output
);
130 for (unsigned x
= 0; x
< 4; x
++)
131 fputs(UNICODE_FULL_BLOCK
, output
);
132 r
= terminal_set_cursor_position(fd
, row
+ move_down
, column
);
134 log_warning_errno(r
, "Failed to move terminal cursor position, ignoring: %m");
136 fputs(ANSI_NORMAL
"\n", output
);
139 print_border(output
, qr
->width
, row
+ move_down
, column
);
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
;
146 fputs(ANSI_WHITE_ON_BLACK
, output
);
147 for (unsigned x
= 0; x
< 4; x
++)
148 fputs(UNICODE_FULL_BLOCK
, output
);
150 for (unsigned x
= 0; x
< (unsigned) qr
->width
; x
++) {
154 b
= (y
+1) < (unsigned) qr
->width
? (row2
[x
] & 1) : false;
159 fputs(UNICODE_LOWER_HALF_BLOCK
, output
);
161 fputs(UNICODE_UPPER_HALF_BLOCK
, output
);
163 fputs(UNICODE_FULL_BLOCK
, output
);
166 for (unsigned x
= 0; x
< 4; x
++)
167 fputs(UNICODE_FULL_BLOCK
, output
);
168 fputs(ANSI_NORMAL
"\n", output
);
171 print_border(output
, qr
->width
, row
, column
);
177 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(QRcode
*, sym_QRcode_free
, NULL
);
179 int print_qrcode_full(
191 /* If this is not a UTF-8 system or ANSI colors aren't supported/disabled don't print any QR
193 if (!is_locale_utf8())
194 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "Not an UTF-8 system, cannot print qrcode");
195 if (check_tty
&& !colors_enabled())
196 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "Colors are disabled, cannot print qrcode");
198 r
= dlopen_qrencode();
202 _cleanup_(sym_QRcode_freep
) QRcode
*qr
=
203 sym_QRcode_encodeString(string
, 0, QR_ECLEVEL_L
, QR_MODE_8
, 1);
205 return log_oom_debug();
207 if (row
!= UINT_MAX
&& column
!= UINT_MAX
) {
208 unsigned qr_code_width
, qr_code_height
;
210 int fd
= fileno(out
);
212 return log_debug_errno(errno
, "Failed to get file descriptor from the file stream: %m");
214 qr_code_width
= qr_code_height
= qr
->width
+ 8;
215 if (column
+ qr_code_width
> tty_width
)
216 column
= tty_width
- qr_code_width
;
218 /* Terminal characters are twice as high as they are wide so it's qr_code_height / 2,
219 * our QR code prints an extra new line, so we have -1 as well */
220 if (row
+ qr_code_height
> tty_height
)
221 row
= tty_height
- (qr_code_height
/ 2 ) - 1;
224 r
= terminal_set_cursor_position(fd
, row
- 2, tty_width
- qr_code_width
- 2);
226 log_warning_errno(r
, "Failed to move terminal cursor position, ignoring: %m");
228 fprintf(out
, "%s:\n\n", header
);
232 fprintf(out
, "\n%s:\n\n", header
);
234 write_qrcode(out
, qr
, row
, column
);