diff options
| author | Asbjørn Sloth Tønnesen <ast@2e8.dk> | 2025-12-05 21:50:06 +0000 |
|---|---|---|
| committer | Asbjørn Sloth Tønnesen <ast@2e8.dk> | 2025-12-05 21:51:57 +0000 |
| commit | a8c1c1f01c0443e5ed884316da618634c7aebe93 (patch) | |
| tree | 55e2f1b21df3d5da94cfac0c29e74214c8cf0dcd /src | |
| parent | cc48231d44d5ff59371acc46373adccf16cac149 (diff) | |
| download | qlprint-fixes.tar.gz qlprint-fixes.tar.xz qlprint-fixes.zip | |
Add JSON outputfixes
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 193 | ||||
| -rw-r--r-- | src/ql.c | 69 |
2 files changed, 199 insertions, 63 deletions
@@ -10,6 +10,7 @@ #include <errno.h> #include <getopt.h> #include <stdio.h> +#include <stdarg.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -22,15 +23,17 @@ void on_alarm(int ignored) timed_out = true; } +__attribute__((noreturn)) void syntax(void) { fprintf(stderr, "Syntax:\n" - " qlprint [-p lp] -i\n" - " [-p lp] [-m margin] [-a] [-C|-D] [-W width] [-L length] [-Q] [-n num] [-t threshold] [-x timeout] png...\n" + " qlprint [-p lp] [-j] -i\n" + " [-p lp] [-j] [-m margin] [-a] [-C|-D] [-W width] [-L length] [-Q] [-n num] [-t threshold] [-x timeout] png...\n" "Where:\n" " -p lp Printer port (default /dev/usb/lp0)\n" " -i Print status information only, then exit\n" + " -j Switch to JSON output\n" " -m margin Margin (dots)\n" " -a Enable auto-cut\n" " -C Request continuous-length-tape when printing (error if not)\n" @@ -46,6 +49,100 @@ void syntax(void) exit(EXIT_FAILURE); } +struct ql_json_context { + const char *printer; + const char *image; + bool output_json; +}; + +const char *ql_json_escape(const char *src) +{ + static char buf[100]; + char escape_ch; + int i = 0; + int n = sizeof(buf) - 3; + + for (; *src; ++src) { + if (i > n) + break; + escape_ch = '\0'; + switch (*src) { + case '\t': + escape_ch = 't'; + break; + case '\n': + escape_ch = 'n'; + break; + case '\r': + escape_ch = 'r'; + break; + case '\f': + escape_ch = 'f'; + break; + case '\b': + escape_ch = 'b'; + break; + case '\\': + escape_ch = '\\'; + break; + case '"': + escape_ch = '"'; + break; + default: + break; + } + if (escape_ch != '\0') { + buf[i] = '\\'; + buf[i + 1] = escape_ch; + i += 2; + } else { + buf[i++] = *src; + } + } + buf[i] = '\0'; + return buf; +} + +__attribute__((noreturn)) +void bail_out(ql_print_json_t *json_ctx, const char *fmt, ...) +{ + va_list ap; + char *msg = NULL; + int ret; + int errno_in = errno; + + va_start(ap, fmt); + ret = vasprintf(&msg, fmt, ap); + va_end(ap); + + if (ret == -1) { + perror("vasprintf"); + exit(EXIT_FAILURE); + } + + if (json_ctx->output_json) { + printf("{\n"); + printf("\t\"message\": \"%s\",\n", ql_json_escape(msg)); + if (json_ctx->printer) + printf("\t\"printer\": \"%s\",\n", ql_json_escape(json_ctx->printer)); + if (json_ctx->image) + printf("\t\"image\": \"%s\",\n", ql_json_escape(json_ctx->image)); + if (errno_in != 0) { + printf("\t\"errno\": %d,\n", errno_in); + printf("\t\"errstr\": \"%s\",\n", ql_json_escape(strerror(errno_in))); + } + printf("\t\"status\": \"error\"\n"); + printf("}\n"); + } else { + if (errno_in != 0) + fprintf(stderr, "%s: %s\n", msg, strerror(errno_in)); + else + fprintf(stderr, "%s\n", msg); + + } + exit(EXIT_FAILURE); +} + int main(int argc, char *argv[]) { bool info_only = false; @@ -56,14 +153,20 @@ int main(int argc, char *argv[]) .threshold = 0x80, .flags = 0 }; + ql_print_json_t json_ctx = { + .output_json = false, + }; const char *printer = "/dev/usb/lp0"; unsigned timeout = 5; int opt; - while ((opt = getopt(argc, argv, "ip:m:an:CDW:L:Qx:")) != -1) { + while ((opt = getopt(argc, argv, "ijp:m:an:CDW:L:Qx:")) != -1) { switch (opt) { case 'i': info_only = true; break; + case 'j': + json_ctx.output_json = true; + break; case 'p': printer = optarg; break; @@ -106,28 +209,20 @@ int main(int argc, char *argv[]) if (optind >= argc && !info_only) syntax(); + json_ctx.printer = printer; ql_ctx_t ctx = ql_open(printer); - if (!ctx) { - fprintf(stderr, "Unable to open '%s': %s\n", printer, strerror(errno)); - return EXIT_FAILURE; - } + if (!ctx) + bail_out(&json_ctx, "Unable to open printer '%s'", printer); - if (!ql_init(ctx)) { - fprintf(stderr, "Failed to send initialisation sequence to printer: %s\n", - strerror(errno)); - return EXIT_FAILURE; - } + if (!ql_init(ctx)) + bail_out(&json_ctx, "Failed to send initialisation sequence to printer"); - if (!ql_request_status(ctx)) { - fprintf(stderr, "Failed to request status from printer: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + if (!ql_request_status(ctx)) + bail_out(&json_ctx, "Failed to request status from printer"); ql_status_t status = { 0, }; - if (!ql_read_status(ctx, &status)) { - fprintf(stderr, "Failed to read status from printer: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + if (!ql_read_status(ctx, &status)) + bail_out(&json_ctx, "Failed to read status from printer"); /* for (int i = 0; i < 32; ++i) @@ -136,35 +231,33 @@ printf("\n"); */ if (info_only) { - ql_decode_print_status(stdout, &status, + ql_decode_print_status(stdout, &status, &json_ctx, QL_DECODE_MODEL | QL_DECODE_MEDIA | QL_DECODE_ERROR | QL_DECODE_MODE); return EXIT_SUCCESS; } - if (margin >= 0 && !ql_set_margin(ctx, (uint16_t) margin)) { - fprintf(stderr, "Failed to set margin: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + if (margin >= 0 && !ql_set_margin(ctx, (uint16_t) margin)) + bail_out(&json_ctx, "Failed to set margin"); + if (autocut && (!ql_set_mode(ctx, QL_MODE_AUTOCUT) - || !ql_set_autocut_every_n(ctx, argc - optind))) { - fprintf(stderr, "Failed to set autocut: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + || !ql_set_autocut_every_n(ctx, argc - optind))) + bail_out(&json_ctx, "Failed to set autocut"); - if (ql_needs_mode_switch(&status) && !ql_switch_to_raster_mode(ctx)) { - fprintf(stderr, "Failed to set raster mode: %s\n", strerror(errno)); - return EXIT_FAILURE; - } + if (ql_needs_mode_switch(&status) && !ql_switch_to_raster_mode(ctx)) + bail_out(&json_ctx, "Failed to set raster mode"); signal(SIGALRM, on_alarm); while (num--) { cfg.first_page = true; for (int i = optind; i < argc; ++i) { - ql_raster_image_t *img = loadpng(argv[i]); + const char *img_name = argv[i]; + + json_ctx.image = img_name; + ql_raster_image_t *img = loadpng(img_name); if (!img) { - fprintf(stderr, "Failed to load image '%s'\n", argv[i]); - return EXIT_FAILURE; + errno = 0; + bail_out(&json_ctx, "Failed to load image '%s'", img_name); } /* for(int i = 0; i < img->height; ++i) @@ -174,11 +267,11 @@ for(int i = 0; i < img->height; ++i) printf("\n"); } */ - if (!ql_print_raster_image(ctx, &status, img, &cfg)) { - fprintf(stderr, "Failed to print '%s' (%ux%u)\n", argv[i], - img->width, img->height); - return EXIT_FAILURE; - } + if (!ql_print_raster_image(ctx, &status, img, &cfg)) + bail_out(&json_ctx, + "Failed to print '%s' (%ux%u)", + img_name, img->width, img->height); + alarm(timeout); do { if (!ql_read_status(ctx, &status)) { @@ -187,18 +280,17 @@ for(int i = 0; i < img->height; ++i) usleep(5000); continue; } - fprintf(stderr, "Printer stopped responding!\n"); - return EXIT_FAILURE; - } - if (status.err_info_1 || status.err_info_2) { - fprintf(stderr, "Printer reported error(s): %s\n", - ql_decode_errors(&status)); - return EXIT_FAILURE; + bail_out(&json_ctx, "Printer stopped responding!"); } + if (status.err_info_1 || status.err_info_2) + bail_out(&json_ctx, + "Printer reported error(s): %s", + ql_decode_errors(&status)); } while (status.status_type != QL_STATUS_TYPE_PRINTING_DONE); alarm(0); - printf("%s (%ux%u) OK\n", argv[i], img->width, img->height); + if (!json_ctx.output_json) + printf("%s (%ux%u) OK\n", argv[i], img->width, img->height); free(img); cfg.first_page = false; @@ -207,5 +299,8 @@ for(int i = 0; i < img->height; ++i) ql_close(ctx); + if (json_ctx.output_json) + printf("{\"status\": \"ok\"}\n"); + return EXIT_SUCCESS; } @@ -128,7 +128,7 @@ bool ql_request_status(ql_ctx_t ctx) return full_write(ctx, status_req); } -bool ql_read_status(ql_ctx_t ctx, ql_status_t * status) +bool ql_read_status(ql_ctx_t ctx, ql_status_t *status) { int retval; fd_set rfds; @@ -200,7 +200,7 @@ bool ql_set_margin(ql_ctx_t ctx, uint16_t dots) return full_write(ctx, cmd); } -bool ql_needs_mode_switch(const ql_status_t * status) +bool ql_needs_mode_switch(const ql_status_t *status) { switch (status->model_code) { case '3': @@ -223,8 +223,8 @@ bool ql_switch_to_raster_mode(ql_ctx_t ctx) return full_write(ctx, cmd); } -static void pack_column(uint8_t * out, uint16_t bytes, uint16_t colno, - const ql_raster_image_t * img, uint8_t black_below_v) +static void pack_column(uint8_t *out, uint16_t bytes, uint16_t colno, + const ql_raster_image_t *img, uint8_t black_below_v) { for (unsigned n = 0; n < bytes; ++n, ++out) { *out = 0; @@ -239,8 +239,8 @@ static void pack_column(uint8_t * out, uint16_t bytes, uint16_t colno, //fprintf(stderr,"\n"); } -bool ql_print_raster_image(ql_ctx_t ctx, const ql_status_t * status, - const ql_raster_image_t * img, const ql_print_cfg_t * cfg) +bool ql_print_raster_image(ql_ctx_t ctx, const ql_status_t *status, + const ql_raster_image_t *img, const ql_print_cfg_t *cfg) { unsigned dn = 90; // default raster transmission block size (720 pixels) if (status->model_code == 'P' || status->model_code == '4') @@ -274,7 +274,7 @@ bool ql_print_raster_image(ql_ctx_t ctx, const ql_status_t * status, return full_write(ctx, done); } -const char *ql_decode_model(const ql_status_t * status) +const char *ql_decode_model(const ql_status_t *status) { switch (status->model_code) { case '1': @@ -307,7 +307,7 @@ const char *ql_decode_model(const ql_status_t * status) } } -const char *ql_decode_mode(const ql_status_t * status) +const char *ql_decode_mode(const ql_status_t *status) { if (status->mode & QL_MODE_AUTOCUT) return "auto-cut"; @@ -315,7 +315,7 @@ const char *ql_decode_mode(const ql_status_t * status) return "no-auto-cut"; } -const char *ql_decode_errors(const ql_status_t * status) +const char *ql_decode_errors(const ql_status_t *status) { typedef struct { uint16_t bit; @@ -362,7 +362,7 @@ const char *ql_decode_errors(const ql_status_t * status) return (buf[0] == 0) ? "none" : buf; } -const char *ql_decode_media_type(const ql_status_t * status) +const char *ql_decode_media_type(const ql_status_t *status) { switch (status->media_type) { case QL_MEDIA_TYPE_NO_MEDIA: @@ -382,11 +382,8 @@ const char *ql_decode_media_type(const ql_status_t * status) } } -void ql_decode_print_status(FILE * f, const ql_status_t * status, unsigned flags) +static void ql_decode_print_status_human(FILE *f, const ql_status_t *status, unsigned flags) { - if (!status) - return; - const char *fmt_s = "%17s: %s\n"; const char *fmt_u = "%17s: %u\n"; if (flags & QL_DECODE_MODEL) @@ -402,3 +399,47 @@ void ql_decode_print_status(FILE * f, const ql_status_t * status, unsigned flags fprintf(f, fmt_u, "Media length (mm)", status->media_length_mm); } } + +static void ql_decode_print_status_json(FILE *f, const ql_print_json_t *json_ctx, + const ql_status_t *status, unsigned flags) +{ + if (!status) + return; + + const char *fmt_s = "\t\"%s\": \"%s\",\n"; + const char *fmt_u = "\t\"%s\": %u,\n"; + bool status_ok = true; + fprintf(f, "{\n"); + fprintf(f, fmt_s, "printer", ql_json_escape(json_ctx->printer)); + if (flags & QL_DECODE_MODEL) + fprintf(f, fmt_s, "model", ql_decode_model(status)); + if (flags & QL_DECODE_MODE) + fprintf(f, fmt_s, "mode", ql_decode_mode(status)); + if (flags & QL_DECODE_ERROR) { + const char *errors = ql_decode_errors(status); + + if (strcmp(errors, "none") != 0) + status_ok = false; + fprintf(f, fmt_s, "errors", ql_decode_errors(status)); + } + if (flags & QL_DECODE_MEDIA) { + fprintf(f, fmt_s, "media_type", ql_decode_media_type(status)); + fprintf(f, fmt_u, "media_width_mm", status->media_width_mm); + if (status->media_type != QL_MEDIA_TYPE_CONTINUOUS) + fprintf(f, fmt_u, "media_length_mm", status->media_length_mm); + } + fprintf(f, "\t\"status\": \"%s\"\n}\n", status_ok ? "ok" : "error"); +} + +void ql_decode_print_status(FILE *f, const ql_status_t *status, + const ql_print_json_t *json_ctx, unsigned flags) +{ + if (!status) + return; + + if (json_ctx->output_json) { + ql_decode_print_status_json(f, json_ctx, status, flags); + } else { + ql_decode_print_status_human(f, status, flags); + } +} |
