/* * Copyright 2017 DiUS Computing Pty Ltd. All rights reserved. * * Released under GPLv3, see LICENSE for details. * * @author Johny Mattsson */ #include "ql.h" #include #include #include #include #include struct ql_ctx { char *printer; int fd; }; #define ESC 0x1b #define NUM_STATUS_READ_RETRIES 100 #define full_write(fd, buf) (retry_write(fd, buf, sizeof(buf)) == (ssize_t)sizeof(buf)) ssize_t retry_write(int fd, const char *buf, size_t len) { size_t written = 0; while (written != len) { ssize_t n = write(fd, buf + written, len - written); if (n == -1) { if (errno == EAGAIN || errno == EINTR) continue; else break; } else written += n; } return written; } ql_ctx_t ql_open(const char *printer) { int fd = open(printer, O_RDWR); if (fd < 0) return NULL; ql_ctx_t ctx = malloc(sizeof(struct ql_ctx)); if (!ctx) return NULL; ctx->printer = strdup(printer); ctx->fd = fd; const char clear[200] = { 0, }; (void)full_write(fd, clear); // recommended to clear old/errored jobs return ctx; } void ql_close(ql_ctx_t ctx) { free(ctx->printer); close(ctx->fd); free(ctx); } bool ql_init(ql_ctx_t ctx) { const char init[] = { ESC, '@' }; return full_write(ctx->fd, init); } bool ql_request_status(ql_ctx_t ctx) { const char status_req[] = { ESC, 'i', 'S' }; return full_write(ctx->fd, status_req); } bool ql_read_status(ql_ctx_t ctx, ql_status_t * status) { for (int i = 0; i < NUM_STATUS_READ_RETRIES; ++i) { int ret = read(ctx->fd, status, sizeof(*status)); if (ret == 32) return true; else if ((ret == 0) // "no data yet, too bad we just eof'd your fd, sucker" || (ret == -1 && errno == EBADF)) // in case we messed up, somehow { close(ctx->fd); ctx->fd = open(ctx->printer, O_RDWR); if (ctx->fd < 0) return false; } else if (ret == -1 && (errno != EAGAIN || errno != EINTR)) return false; // non-recoverable } errno = ETIME; return false; } bool ql_set_mode(ql_ctx_t ctx, unsigned mode) { char cmd[] = { ESC, 'i', 'M', mode }; return full_write(ctx->fd, cmd); } bool ql_set_expanded_mode(ql_ctx_t ctx, unsigned mode) { char cmd[] = { ESC, 'i', 'K', mode }; return full_write(ctx->fd, cmd); } bool ql_set_autocut_every_n(ql_ctx_t ctx, uint8_t n) { char cmd[] = { ESC, 'i', 'A', n }; return full_write(ctx->fd, cmd); } bool ql_set_margin(ql_ctx_t ctx, uint16_t dots) { char cmd[] = { ESC, 'i', 'd', dots & 0xff, dots >> 8 }; return full_write(ctx->fd, cmd); } bool ql_needs_mode_switch(const ql_status_t * status) { switch (status->model_code) { case '3': case '4': case 'P': case 'Q': return true; default: break; } return false; } bool ql_switch_to_raster_mode(ql_ctx_t ctx) { #define MODE_ESC_P 0 #define MODE_RASTER 1 #define MODE_P_TOUCH_TEMPLATE 3 char cmd[] = { ESC, 'i', 'a', MODE_RASTER }; return full_write(ctx->fd, 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) { for (unsigned n = 0; n < bytes; ++n, ++out) { *out = 0; for (unsigned i = 0; i < 8; ++i) { unsigned img_row = n * 8 + i; if (img_row < img->height) if (img->data[img_row * img->width + colno] < black_below_v) *out |= 1 << (7 - i); } //for(int i = 7; i >= 0; --i) fprintf(stderr, "%c", (*out & (1<model_code == 'P' || status->model_code == '4') dn = 162; // 1296 pixels if (img->height > dn * 8) return false; // image too wide for printer char print_info[] = { ESC, 'i', 'z', cfg->flags | 0x80, (cfg->flags & QL_PRINT_CFG_MEDIA_TYPE) ? cfg->media_type : 0, (cfg->flags & QL_PRINT_CFG_MEDIA_WIDTH) ? cfg->media_width : 0, (cfg->flags & QL_PRINT_CFG_MEDIA_LENGTH) ? cfg->media_length : 0, img->width & 0xff, img->width >> 8, 0, 0, cfg->first_page ? 0 : 1, 0 }; if (!full_write(ctx->fd, print_info)) return false; for (unsigned w = 0; w < img->width; ++w) { char block[dn + 3]; block[0] = 'g'; block[1] = 0; block[2] = dn; pack_column((uint8_t *) block + 3, dn, w, img, cfg->threshold); if (!full_write(ctx->fd, block)) return false; } char done[] = { 0x1a }; // print with feeding return full_write(ctx->fd, done); } const char *ql_decode_model(const ql_status_t * status) { switch (status->model_code) { case '1': return "QL-560"; case '2': return "QL-570"; case '3': return "QL-580N"; case '4': return "QL-1060N"; case '5': return "QL-700"; case '6': return "QL-710W"; case '7': return "QL-720NW"; case 'O': return "QL-500/550"; case 'P': return "QL-1050"; case 'Q': return "QL-650TD"; default: { static char buf[] = "unrecognised (type code 0x$$)"; char *q = strchr(buf, '$'); sprintf(q, "%02hhx)", status->model_code); return buf; } } } const char *ql_decode_mode(const ql_status_t * status) { if (status->mode & QL_MODE_AUTOCUT) return "auto-cut"; else return "no-auto-cut"; } const char *ql_decode_errors(const ql_status_t * status) { typedef struct { uint16_t bit; const char *str; } strmap_t; #define ERR1(x) (x << 0) #define ERR2(x) (x << 8) const strmap_t strmap[] = { {ERR1(QL_ERR_1_NO_MEDIA), "no-media "}, {ERR1(QL_ERR_1_END_OF_MEDIA), "end-of-media "}, {ERR1(QL_ERR_1_CUTTER_JAM), "cutter-jam "}, {ERR1(QL_ERR_1_PRINTER_IN_USE), "printer-in-use "}, {ERR1(QL_ERR_1_PRINTER_TURNED_OFF), "printer-turned-off "}, {ERR1(QL_ERR_1_HIGH_VOLTAGE_ADAPTER), "high-voltage-adapter "}, {ERR1(QL_ERR_1_FAN_MOTOR_ERROR), "fan-motor-error "}, {ERR2(QL_ERR_2_REPLACE_MEDIA), "replace-media "}, {ERR2(QL_ERR_2_EXPANSION_BUFFER_FULL), "expansion-buffer-full "}, {ERR2(QL_ERR_2_COMMUNICATION_ERROR), "communication-error "}, {ERR2(QL_ERR_2_COMMUNICATION_BUFFER_FULL), "communication-buffer-full "}, {ERR2(QL_ERR_2_COVER_OPEN), "cover-open "}, {ERR2(QL_ERR_2_CANCEL_KEY), "cancel-key-pressed "}, {ERR2(QL_ERR_2_MEDIA_CANNOT_BE_FED), "media-cannot-be-fed "}, {ERR2(QL_ERR_2_SYSTEM_ERROR), "system-error "} }; static char *buf = 0; if (!buf) { int len = 0; for (unsigned i = 0; i < (sizeof(strmap) / sizeof(strmap[0])); ++i) len += strlen(strmap[i].str); buf = malloc(len + 1); if (!buf) return ""; } uint16_t errs = ERR1(status->err_info_1) | ERR2(status->err_info_2); buf[0] = 0; for (unsigned i = 0; i < (sizeof(strmap) / sizeof(strmap[0])); ++i) { if (errs & strmap[i].bit) strcat(buf, strmap[i].str); } return (buf[0] == 0) ? "none" : buf; } const char *ql_decode_media_type(const ql_status_t * status) { switch (status->media_type) { case QL_MEDIA_TYPE_NO_MEDIA: return "no-media"; case QL_MEDIA_TYPE_CONTINUOUS: case QL_MEDIA_TYPE_CONTINUOUS_ALT: return "continuous-length-tape"; case QL_MEDIA_TYPE_DIECUT_LABELS: case QL_MEDIA_TYPE_DIECUT_LABELS_ALT: return "die-cut-labels"; default:{ static char buf[] = "unknown (code 0x$$)"; char *q = strchr(buf, '$'); sprintf(q, "%02hhx)", status->media_type); return buf; } } } void ql_decode_print_status(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) fprintf(f, fmt_s, "Printer", ql_decode_model(status)); if (flags & QL_DECODE_MODE) fprintf(f, fmt_s, "Mode", ql_decode_mode(status)); if (flags & QL_DECODE_ERROR) 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); } }