summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/ql.h11
-rw-r--r--src/main.c193
-rw-r--r--src/ql.c69
3 files changed, 208 insertions, 65 deletions
diff --git a/include/ql.h b/include/ql.h
index 5e32fda..e1e2b11 100644
--- a/include/ql.h
+++ b/include/ql.h
@@ -115,6 +115,12 @@ typedef struct {
bool first_page; // used for autocut pagination
} ql_print_cfg_t;
+typedef struct {
+ const char *printer;
+ const char *image;
+ bool output_json;
+} ql_print_json_t;
+
#define QL_PRINT_CFG_MEDIA_TYPE 0x02
#define QL_PRINT_CFG_MEDIA_WIDTH 0x04
#define QL_PRINT_CFG_MEDIA_LENGTH 0x08
@@ -150,6 +156,7 @@ const char *ql_decode_media_type(const ql_status_t * status);
#define QL_DECODE_ERROR 0x02
#define QL_DECODE_MEDIA 0x04
#define QL_DECODE_MODE 0x08
-void ql_decode_print_status(FILE * out, const ql_status_t * status, unsigned flags);
-
+void ql_decode_print_status(FILE * out, const ql_status_t * status,
+ const ql_print_json_t * json_ctx, unsigned flags);
+const char *ql_json_escape(const char *src);
#endif
diff --git a/src/main.c b/src/main.c
index ea55e6d..a66c6a6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;
}
diff --git a/src/ql.c b/src/ql.c
index 31bd24b..3a94c03 100644
--- a/src/ql.c
+++ b/src/ql.c
@@ -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);
+ }
+}