/* * Copyright 2017 DiUS Computing Pty Ltd. All rights reserved. * * Released under GPLv3, see LICENSE for details. * * @author Johny Mattsson */ #include "ql.h" #include "loadpng.h" #include #include #include #include #include #include #include #include static bool timed_out = false; void on_alarm(int ignored) { (void)ignored; timed_out = true; } __attribute__((noreturn)) void syntax(void) { fprintf(stderr, "Syntax:\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" " -D Request die-cut-labels when printing (error if not)\n" " -W width Request particular width media when printing (error if not)\n" " -L length Request particular length media when printing (error if not)\n" " -Q Prioritise quality of speed\n" " -n num Print num copies\n" " -t threshold Threshold for black-vs-white (default 128, i.e. 0-127=black)\n" " -x timeout Time to wait for successful print, in seconds (default 5)\n" " png... One or more png files to print\n" "\n"); 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; int32_t margin = -1; bool autocut = false; int num = 1; ql_print_cfg_t cfg = { .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, "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; case 'm': margin = atoi(optarg); break; case 'a': autocut = true; break; case 'n': num = atoi(optarg); break; case 'C': cfg.media_type = QL_MEDIA_TYPE_CONTINUOUS; cfg.flags |= QL_PRINT_CFG_MEDIA_TYPE; break; case 'D': cfg.media_type = QL_MEDIA_TYPE_DIECUT_LABELS; cfg.flags |= QL_PRINT_CFG_MEDIA_TYPE; break; case 'W': cfg.media_width = atoi(optarg); cfg.flags |= QL_PRINT_CFG_MEDIA_WIDTH; break; case 'L': cfg.media_length = atoi(optarg); cfg.flags |= QL_PRINT_CFG_MEDIA_LENGTH; break; case 'Q': cfg.flags |= QL_PRINT_CFG_QUALITY_PRIO; break; case 'x': timeout = atoi(optarg); break; default: syntax(); } } if (optind >= argc && !info_only) syntax(); json_ctx.printer = printer; ql_ctx_t ctx = ql_open(printer); if (!ctx) bail_out(&json_ctx, "Unable to open printer '%s'", printer); if (!ql_init(ctx)) bail_out(&json_ctx, "Failed to send initialisation sequence to printer"); 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)) bail_out(&json_ctx, "Failed to read status from printer"); /* for (int i = 0; i < 32; ++i) printf("%02hhx ", ((char *)&status)[i]); printf("\n"); */ if (info_only) { 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)) 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))) bail_out(&json_ctx, "Failed to set autocut"); 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) { const char *img_name = argv[i]; json_ctx.image = img_name; ql_raster_image_t *img = loadpng(img_name); if (!img) { errno = 0; bail_out(&json_ctx, "Failed to load image '%s'", img_name); } /* for(int i = 0; i < img->height; ++i) { for(int j = 0; j < img->width; ++j) printf("%c", img->data[i*img->width + j] < cfg.threshold ? '#' : '.'); printf("\n"); } */ 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)) { if (!timed_out) // try again, soon { usleep(5000); continue; } 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); if (!json_ctx.output_json) printf("%s (%ux%u) OK\n", argv[i], img->width, img->height); free(img); cfg.first_page = false; } } ql_close(ctx); if (json_ctx.output_json) printf("{\"status\": \"ok\"}\n"); return EXIT_SUCCESS; }