1 /*
2 * Magicard card printer family CUPS backend -- libusb-1.0 version
3 *
4 * (c) 2017-2018 Solomon Peachy <pizza@shaftnet.org>
5 *
6 * The latest version of this program can be found at:
7 *
8 * http://git.shaftnet.org/cgit/selphy_print.git
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the Free
12 * Software Foundation; either version 2 of the License, or (at your option)
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 * for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 *
23 * [http://www.gnu.org/licenses/gpl-2.0.html]
24 *
25 * SPDX-License-Identifier: GPL-2.0+
26 *
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <time.h>
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <string.h>
40
41 #define BACKEND magicard_backend
42
43 #include "backend_common.h"
44
45 /* Exported */
46 #define USB_VID_MAGICARD 0x0C1F
47 #define USB_PID_MAGICARD_TANGO2E 0x1800
48 #define USB_PID_MAGICARD_ENDURO 0x4800 // ??
49 #define USB_PID_MAGICARD_ENDUROPLUS 0x880A // ??
50
51 /* Gamma tables computed with this perl program:
52
53 my $input_bpp = 8;
54 my $output_bpp = 6;
55 my $gamma = 1/1.8; # or 1/2.2 or whatever.
56
57 my $i;
58
59 for (my $i = 0 ; $i < (2 ** $input_bpp) ; $i++) {
60 my $linear = $i / (2 ** $input_bpp);
61 my $gc = ($linear ** $gamma) * (2 ** $output_bpp);
62 $gc = int($gc);
63 print "$gc, ";
64 }
65
66 */
67
68 static uint8_t gammas[2][256] = {
69 /* Gamma = 2.2 */
70 {
71 0, 5, 7, 8, 9, 10, 11, 12, 13, 13, 14, 15, 15, 16, 17,
72 17, 18, 18, 19, 19, 20, 20, 20, 21, 21, 22, 22, 23, 23, 23,
73 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28,
74 29, 29, 29, 29, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32,
75 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 35, 36, 36,
76 36, 36, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39,
77 39, 39, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42,
78 42, 42, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 45, 45,
79 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47,
80 47, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 50,
81 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 52, 52,
82 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54,
83 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56,
84 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58,
85 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60,
86 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 61, 61, 62,
87 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63,
88 },
89 /* Gamma = 1.8 */
90 {
91 0, 2, 4, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 12,
92 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19,
93 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24,
94 24, 24, 24, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28,
95 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32,
96 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 34, 35, 35, 35,
97 35, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 38, 38, 38, 38,
98 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41,
99 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44,
100 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 47, 47, 47,
101 47, 47, 47, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49,
102 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 52, 52, 52,
103 52, 52, 52, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54,
104 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 57, 57,
105 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 58, 59, 59, 59,
106 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61,
107 61, 61, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63,
108 }
109 };
110
111 struct magicard_printjob {
112 uint8_t *databuf;
113 int datalen;
114
115 int hdr_len;
116 int copies;
117 };
118
119 /* Private data structure */
120 struct magicard_ctx {
121 struct libusb_device_handle *dev;
122 uint8_t endp_up;
123 uint8_t endp_down;
124 int type;
125
126 struct marker marker;
127 };
128
129 struct magicard_cmd_header {
130 uint8_t guard[9]; /* 0x05 */
131 uint8_t guard2[1]; /* 0x01 */
132 uint8_t cmd[4]; /* 'REQ,' */
133 uint8_t subcmd[4]; /* '???,' */
134 uint8_t arg[4]; /* '???,' */
135 uint8_t footer[2]; /* 0x1c 0x03 */
136 };
137
138 struct magicard_cmd_simple_header {
139 uint8_t guard[9]; /* 0x05 */
140 uint8_t guard2[1]; /* 0x01 */
141 uint8_t cmd[]; /* '???' */
142 // uint8_t footer[2]; /* 0x1c 0x03 */
143 };
144
145 struct magicard_resp_header {
146 uint8_t guard[1]; /* 0x01 */
147 uint8_t subcmd_arg[7]; /* '???,???' */
148 uint8_t data[0]; /* freeform resp */
149 // uint8_t term[2]; /* 0x2c 0x03 terminates! */
150 };
151
152 struct magicard_requests {
153 char *key;
154 char *desc;
155 uint8_t type;
156 };
157
158 enum {
159 TYPE_UNKNOWN = 0,
160 TYPE_STRING,
161 TYPE_STRINGINT,
162 TYPE_STRINGINT_HEX,
163 TYPE_IPADDR,
164 TYPE_YESNO,
165 TYPE_MODEL,
166 };
167
168 /* Data definitions */
169 static struct magicard_requests magicard_sta_requests[] = {
170 { "MSR", "Printer Serial Number", TYPE_STRING },
171 { "PSR", "Print Head Serial Number", TYPE_STRING },
172 { "BSR", "PCB Serial Number", TYPE_STRING },
173 { "VRS", "Firmware Version", TYPE_STRING },
174 { "FDC", "Head Density", TYPE_STRINGINT }, /* 25 per step */
175 { "FSP", "Image Start", TYPE_STRINGINT }, /* 8 steps per pixel */
176 { "FEP", "Image End", TYPE_STRINGINT }, /* 8 steps per pixel */
177 { "FSS", "Ramp Adjust", TYPE_STRINGINT },
178 { "FPP", "Head Position", TYPE_STRINGINT }, /* L-R alignment */
179 { "MDL", "Model", TYPE_MODEL }, /* 0 == Standard. Others? */
180 { "PID", "USB PID", TYPE_STRINGINT_HEX }, /* ASCII integer, but needs to be shown as hex */
181 { "VID", "USB VID", TYPE_STRINGINT_HEX }, /* ASCII integer, but needs to be shown as hex */
182 { "USN", "USB Serial Number", TYPE_STRING },
183 { "UPN", "USB Manufacturer", TYPE_STRING },
184 { "MAC", "Ethernet MAC Address", TYPE_STRING },
185 { "DYN", "Dynamic Address", TYPE_YESNO }, /* 1 == yes, 0 == no */
186 { "IPA", "IP Address", TYPE_IPADDR }, /* ASCII signed integer */
187 { "SNM", "IP Netmask", TYPE_IPADDR }, /* ASCII signed integer */
188 { "GWY", "IP Gateway", TYPE_IPADDR }, /* ASCII signed integer */
189
190 { "TCQ", "Total Cards Printed", TYPE_STRINGINT },
191 { "TCP", "Prints on Head", TYPE_STRINGINT },
192 { "TCN", "Cleaning Cycles", TYPE_STRINGINT },
193 { "CCQ", "Cards Since Last Cleaning", TYPE_STRINGINT },
194 { "TPQ", "Total Panels Printed", TYPE_STRINGINT },
195 { "CCP", "Cards between Cleaning Prompts", TYPE_STRINGINT },
196 { "CPQ", "Panels Since Last Cleaning", TYPE_STRINGINT },
197 { "DFR", "Panels Remaining", TYPE_STRINGINT }, // cook somehow?
198 { "CLP", "Cleaning Prompt", TYPE_STRING },
199
200 // CRQ: OFF ?? Cleaning overdue?
201 // CHK: checksum of fw? (8 chars, hex?)
202 // TES: ??? signed int? IP addr?
203 // RAMP: ??? hangs.
204
205 { NULL, NULL, 0 }
206 };
207
208 // Sensors: CAM1 CAM2 TACHO FLIP DYE BARCODE LID FRONT REAR BUTTON TEMP ON OFF
209 // Languages: ENG ITA POR FRA DEU ESP SCH
210
211 /* Helper functions */
magicard_build_cmd(uint8_t * buf,char * cmd,char * subcmd,char * arg)212 static int magicard_build_cmd(uint8_t *buf,
213 char *cmd, char *subcmd, char *arg)
214 {
215 struct magicard_cmd_header *hdr = (struct magicard_cmd_header *) buf;
216
217 memset(hdr->guard, 0x05, sizeof(hdr->guard));
218 hdr->guard2[0] = 0x01;
219 memcpy(hdr->cmd, cmd, 3);
220 hdr->cmd[3] = ',';
221 memcpy(hdr->subcmd, subcmd, 3);
222 hdr->subcmd[3] = ',';
223 memcpy(hdr->arg, arg, 3);
224 hdr->arg[3] = ',';
225 hdr->footer[0] = 0x1c;
226 hdr->footer[1] = 0x03;
227
228 return sizeof(*hdr);
229 }
230
magicard_build_cmd_simple(uint8_t * buf,char * cmd)231 static int magicard_build_cmd_simple(uint8_t *buf,
232 char *cmd)
233 {
234 struct magicard_cmd_simple_header *hdr = (struct magicard_cmd_simple_header *) buf;
235 int len = strlen(cmd);
236
237 memset(hdr->guard, 0x05, sizeof(hdr->guard));
238 hdr->guard2[0] = 0x01;
239 strncpy((char*)hdr->cmd, cmd, len);
240 hdr->cmd[len] = 0x1c;
241 hdr->cmd[len+1] = 0x03;
242
243 return (sizeof(*hdr) + len + 2);
244 }
245
246
magicard_parse_resp(uint8_t * buf,uint16_t len,uint16_t * resplen)247 static uint8_t * magicard_parse_resp(uint8_t *buf, uint16_t len, uint16_t *resplen)
248 {
249 struct magicard_resp_header *hdr = (struct magicard_resp_header *) buf;
250
251 *resplen = len - sizeof(hdr->guard) - sizeof(hdr->subcmd_arg) - 2;
252
253 return hdr->data;
254 }
255
magicard_query_sensors(struct magicard_ctx * ctx)256 static int magicard_query_sensors(struct magicard_ctx *ctx)
257 {
258 int ret = 0;
259 int i;
260 uint8_t buf[256];
261 char buf2[24];
262
263 for (i = 1 ; ; i++) {
264 int num = 0;
265
266 snprintf(buf2, sizeof(buf2), "SNR%d", i);
267 ret = magicard_build_cmd_simple(buf, buf2);
268
269 if ((ret = send_data(ctx->dev, ctx->endp_down,
270 buf, ret)))
271 return ret;
272
273 memset(buf, 0, sizeof(buf));
274
275 ret = read_data(ctx->dev, ctx->endp_up,
276 buf, sizeof(buf), &num);
277
278 if (ret < 0)
279 return ret;
280
281 if (!memcmp(buf, "END", 3))
282 break;
283
284 buf[num] = 0;
285 INFO("%s\n", buf);
286 }
287 return 0;
288 }
289
magicard_selftest_card(struct magicard_ctx * ctx)290 static int magicard_selftest_card(struct magicard_ctx *ctx)
291 {
292 int ret = 0;
293 uint8_t buf[256];
294
295 ret = magicard_build_cmd_simple(buf, "TST,");
296
297 ret = send_data(ctx->dev, ctx->endp_down,
298 buf, ret);
299 return ret;
300 }
301
magicard_reset(struct magicard_ctx * ctx)302 static int magicard_reset(struct magicard_ctx *ctx)
303 {
304 int ret = 0;
305 uint8_t buf[256];
306
307 ret = magicard_build_cmd_simple(buf, "RST,");
308
309 ret = send_data(ctx->dev, ctx->endp_down,
310 buf, ret);
311 return ret;
312 }
313
magicard_eject(struct magicard_ctx * ctx)314 static int magicard_eject(struct magicard_ctx *ctx)
315 {
316 int ret = 0;
317 uint8_t buf[256];
318
319 ret = magicard_build_cmd_simple(buf, "EJT,");
320
321 ret = send_data(ctx->dev, ctx->endp_down,
322 buf, ret);
323 return ret;
324 }
325
magicard_query_printer(struct magicard_ctx * ctx)326 static int magicard_query_printer(struct magicard_ctx *ctx)
327 {
328 int ret = 0;
329 int i;
330 uint8_t buf[256];
331 char buf2[24];
332
333 for (i = 1 ; ; i++) {
334 int num = 0;
335
336 snprintf(buf2, sizeof(buf2), "QPR%d", i);
337 ret = magicard_build_cmd_simple(buf, buf2);
338
339 if ((ret = send_data(ctx->dev, ctx->endp_down,
340 buf, ret)))
341 return ret;
342
343 memset(buf, 0, sizeof(buf));
344
345 ret = read_data(ctx->dev, ctx->endp_up,
346 buf, sizeof(buf), &num);
347
348 if (ret < 0)
349 return ret;
350
351 if (!memcmp(buf, "END", 3))
352 break;
353
354 buf[num] = 0;
355 INFO("%s\n", buf);
356 }
357 return 0;
358 }
359
magicard_query_status(struct magicard_ctx * ctx)360 static int magicard_query_status(struct magicard_ctx *ctx)
361 {
362 int ret = 0;
363 int i;
364 uint8_t buf[256];
365
366 for (i = 0 ; ; i++) {
367 uint16_t resplen = 0;
368 uint8_t *resp;
369 int num = 0;
370
371 if (magicard_sta_requests[i].key == NULL)
372 break;
373
374 ret = magicard_build_cmd(buf, "REQ", "STA",
375 magicard_sta_requests[i].key);
376
377 if ((ret = send_data(ctx->dev, ctx->endp_down,
378 buf, ret)))
379 return ret;
380
381 memset(buf, 0, sizeof(buf));
382
383 ret = read_data(ctx->dev, ctx->endp_up,
384 buf, sizeof(buf), &num);
385
386 if (ret < 0)
387 return ret;
388
389 resp = magicard_parse_resp(buf, num, &resplen);
390 resp[resplen] = 0;
391 switch(magicard_sta_requests[i].type) {
392 case TYPE_IPADDR: {
393 int32_t ipaddr;
394 uint8_t *addr = (uint8_t *) &ipaddr;
395 ipaddr = atoi((char*)resp);
396 INFO("%s:\t%d.%d.%d.%d\n",
397 magicard_sta_requests[i].desc,
398 addr[3], addr[2], addr[1], addr[0]);
399 break;
400 }
401 case TYPE_YESNO: {
402 int val = atoi((char*)resp);
403 INFO("%s:\t%s\n",
404 magicard_sta_requests[i].desc,
405 val? "Yes" : "No");
406 break;
407 }
408 case TYPE_MODEL: {
409 int val = atoi((char*)resp);
410 INFO("%s:\t%s\n",
411 magicard_sta_requests[i].desc,
412 val == 0? "Standard" : "Unknown");
413 break;
414 }
415 case TYPE_STRINGINT_HEX: {
416 int val = atoi((char*)resp);
417 INFO("%s:\t%X\n",
418 magicard_sta_requests[i].desc,
419 val);
420 break;
421 }
422 case TYPE_STRINGINT:
423 // treat differently?
424 case TYPE_STRING:
425 case TYPE_UNKNOWN:
426 default:
427 INFO("%s:\t%s\n",
428 magicard_sta_requests[i].desc,
429 resp);
430 }
431 }
432
433 return ret;
434 }
435
436 /* Main driver */
magicard_init(void)437 static void* magicard_init(void)
438 {
439 struct magicard_ctx *ctx = malloc(sizeof(struct magicard_ctx));
440 if (!ctx) {
441 ERROR("Memory Allocation Failure!");
442 return NULL;
443 }
444 memset(ctx, 0, sizeof(struct magicard_ctx));
445 return ctx;
446 }
447
magicard_attach(void * vctx,struct libusb_device_handle * dev,int type,uint8_t endp_up,uint8_t endp_down,uint8_t jobid)448 static int magicard_attach(void *vctx, struct libusb_device_handle *dev, int type,
449 uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
450 {
451 struct magicard_ctx *ctx = vctx;
452
453 UNUSED(jobid);
454
455 ctx->dev = dev;
456 ctx->endp_up = endp_up;
457 ctx->endp_down = endp_down;
458 ctx->type = type;
459
460 ctx->marker.color = "#00FFFF#FF00FF#FFFF00"; // XXX YMCK too!
461 ctx->marker.name = "Unknown"; // LC1/LC3/LC6/LC8
462 ctx->marker.levelmax = -1;
463 ctx->marker.levelnow = -2;
464
465 return CUPS_BACKEND_OK;
466 }
467
magicard_cleanup_job(const void * vjob)468 static void magicard_cleanup_job(const void *vjob)
469 {
470 const struct magicard_printjob *job = vjob;
471
472 if (job->databuf)
473 free(job->databuf);
474
475 free((void*)job);
476 }
477
downscale_and_extract(int gamma,uint32_t pixels,uint8_t * y_i,uint8_t * m_i,uint8_t * c_i,uint8_t * y_o,uint8_t * m_o,uint8_t * c_o,uint8_t * k_o)478 static void downscale_and_extract(int gamma, uint32_t pixels,
479 uint8_t *y_i, uint8_t *m_i, uint8_t *c_i,
480 uint8_t *y_o, uint8_t *m_o, uint8_t *c_o, uint8_t *k_o)
481 {
482 uint32_t i;
483
484 for (i = 0 ; i < pixels; i++)
485 {
486 uint8_t y, m, c;
487 uint8_t k = 0;
488 uint32_t j;
489 uint32_t row;
490 uint32_t col;
491 uint32_t b_offset;
492 uint8_t b_shift;
493
494 /* Downscale color planes from 8bpp -> 6bpp; */
495 if (gamma) {
496 if (gamma > 2)
497 gamma = 2;
498 gamma--;
499 y = gammas[gamma][*y_i++];
500 m = gammas[gamma][*m_i++];
501 c = gammas[gamma][*c_i++];
502 } else {
503 y = *y_i++ >> 2;
504 m = *m_i++ >> 2;
505 c = *c_i++ >> 2;
506 }
507
508 /* Extract "true black" from ymc data, if enabled */
509 if (k_o && y == 0x3f && m == 0x3f && c == 0x3f) {
510 k = 1;
511 y = m = c = 0;
512 }
513
514 /* Compute row number and offsets */
515 row = i / 672;
516 col = i - (row * 672);
517 b_offset = col / 8;
518 b_shift = 7 - (col - (b_offset * 8));
519
520 /* Now, for each row, break it down into sub-chunks */
521 for (j = 0 ; j < 6 ; j++) {
522 if (b_shift == 7) {
523 y_o[row * 504 + j * 84 + b_offset] = 0;
524 m_o[row * 504 + j * 84 + b_offset] = 0;
525 c_o[row * 504 + j * 84 + b_offset] = 0;
526 }
527 if (y & (1 << j))
528 y_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
529 if (m & (1 << j))
530 m_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
531 if (c & (1 << j))
532 c_o[row * 504 + j * 84 + b_offset] |= (1 << b_shift);
533 }
534
535 /* And resin black, if enabled */
536 if (k_o) {
537 if (b_shift == 7) {
538 k_o[row * 84 + b_offset] = 0;
539 }
540 if (k)
541 k_o[row * 84 + b_offset] |= (1 << b_shift);
542 }
543 }
544 }
545
546 #define MAX_HEADERS_LEN 2048
547 #define MAX_PRINTJOB_LEN (1016*672*4) + MAX_HEADERS_LEN /* 1016*672 * 4color */
548 #define INITIAL_BUF_LEN 1024
magicard_read_parse(void * vctx,const void ** vjob,int data_fd,int copies)549 static int magicard_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
550 struct magicard_ctx *ctx = vctx;
551 uint8_t initial_buf[INITIAL_BUF_LEN + 1];
552 uint32_t buf_offset = 0;
553 int i;
554
555 uint8_t *in_y, *in_m, *in_c;
556 uint8_t *out_y, *out_m, *out_c, *out_k;
557 uint32_t len_y = 0, len_m = 0, len_c = 0, len_k = 0;
558 int gamma = 0;
559
560 uint8_t x_gp_8bpp;
561 uint8_t x_gp_rk;
562 uint8_t k_only;
563
564 struct magicard_printjob *job = NULL;
565
566 if (!ctx)
567 return CUPS_BACKEND_FAILED;
568
569 job = malloc(sizeof(*job));
570 if (!job) {
571 ERROR("Memory allocation failure!\n");
572 return CUPS_BACKEND_RETRY_CURRENT;
573 }
574 memset(job, 0, sizeof(*job));
575 job->copies = copies;
576
577 /* Read in the first chunk */
578 i = read(data_fd, initial_buf, INITIAL_BUF_LEN);
579 if (i < 0) {
580 magicard_cleanup_job(job);
581 return i;
582 } else if (i == 0) {
583 magicard_cleanup_job(job);
584 return CUPS_BACKEND_CANCEL; /* Ie no data, we're done */
585 } else if (i < INITIAL_BUF_LEN) {
586 magicard_cleanup_job(job);
587 return CUPS_BACKEND_CANCEL;
588 }
589
590 /* Basic Sanity Check */
591 if (initial_buf[0] != 0x05 ||
592 initial_buf[64] != 0x01 ||
593 initial_buf[65] != 0x2c) {
594 ERROR("Unrecognized header data format @%d!\n", job->datalen);
595 magicard_cleanup_job(job);
596 return CUPS_BACKEND_CANCEL;
597 }
598
599 initial_buf[INITIAL_BUF_LEN] = 0;
600
601 /* We can start allocating! */
602 if (job->databuf) {
603 free(job->databuf);
604 job->databuf = NULL;
605 }
606 job->datalen = 0;
607 job->databuf = malloc(MAX_PRINTJOB_LEN);
608 if (!job->databuf) {
609 ERROR("Memory allocation failure!\n");
610 magicard_cleanup_job(job);
611 return CUPS_BACKEND_RETRY_CURRENT;
612 }
613
614 /* Copy over initial header */
615 memcpy(job->databuf + job->datalen, initial_buf + buf_offset, 65);
616 job->datalen += 65;
617 buf_offset += 65;
618
619 /* Start parsing headers */
620 x_gp_8bpp = x_gp_rk = k_only = job->hdr_len = 0;
621
622 char *ptr;
623 ptr = strtok((char*)initial_buf + ++buf_offset, ",\x1c");
624 while (ptr
625 && ((ptr - (char*)initial_buf) < INITIAL_BUF_LEN)
626 && ((ptr - (char*)initial_buf) + strnlen(ptr, INITIAL_BUF_LEN) < INITIAL_BUF_LEN)
627 && *ptr != 0x1c) {
628 if (!strcmp("X-GP-8", ptr)) {
629 x_gp_8bpp = 1;
630 } else if (!strncmp("TDT", ptr, 3)) {
631 /* Strip out the timestamp, replace it with one from the backend */
632 } else if (!strncmp("IMF", ptr,3)) {
633 /* Strip out the image format, replace it with backend */
634 // } else if (!strncmp("ESS", ptr, 3)) {
635 // /* Strip out copies */
636 } else if (!strcmp("X-GP-RK", ptr)) {
637 x_gp_rk = 1;
638 } else if (!strncmp("ICC", ptr,3)) {
639 /* Gamma curve is not handled by printer,
640 strip it out and use it! */
641 gamma = atoi(ptr + 3);
642 } else if (!strncmp("SZ", ptr, 2)) {
643 if (ptr[2] == 'B') {
644 len_y = atoi(ptr + 3);
645 } else if (ptr[2] == 'G') {
646 len_m = atoi(ptr + 3);
647 } else if (ptr[2] == 'R') {
648 len_c = atoi(ptr + 3);
649 } else if (ptr[2] == 'K') {
650 len_k = atoi(ptr + 3);
651 }
652 } else {
653 /* Safety valve */
654 if (strlen(ptr) + job->datalen > MAX_HEADERS_LEN) {
655 ERROR("headers too long, bogus job!\n");
656 magicard_cleanup_job(job);
657 return CUPS_BACKEND_CANCEL;
658 }
659
660 /* Everything else goes in */
661 job->datalen += sprintf((char*)job->databuf + job->datalen, ",%s", ptr);
662 }
663
664 /* Keep going */
665 buf_offset += strlen(ptr) + 1;
666 /* Peek ahead to see if this is it */
667 if (initial_buf[buf_offset + 1] == 0x1c)
668 break;
669 /* Otherwise continue to the next token */
670 ptr = strtok(NULL, ",\x1c");
671 }
672
673 /* Sanity checks */
674 if (!len_y || !len_m || !len_c) {
675 ERROR("Plane lengths missing? %u/%u/%u!\n", len_y, len_m, len_c);
676 magicard_cleanup_job(job);
677 return CUPS_BACKEND_CANCEL;
678 }
679 if (len_y != len_m || len_y != len_c) {
680 ERROR("Inconsistent data plane lengths! %u/%u/%u!\n", len_y, len_m, len_c);
681 magicard_cleanup_job(job);
682 return CUPS_BACKEND_CANCEL;
683 }
684 if (x_gp_rk && len_k) {
685 ERROR("Data stream already has a K layer!\n");
686 magicard_cleanup_job(job);
687 return CUPS_BACKEND_CANCEL;
688 }
689
690 /* Generate a timestamp */
691 job->datalen += sprintf((char*)job->databuf + job->datalen, ",TDT%08X", (uint32_t) time(NULL));
692
693 /* Generate image format tag */
694 if (k_only == 1) {
695 job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFK");
696 } else if (x_gp_rk || len_k) {
697 /* We're adding K, so make this BGRK */
698 job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFBGRK");
699 } else {
700 /* Just BGR */
701 job->datalen += sprintf((char*)job->databuf + job->datalen, ",IMFBGR");
702 }
703
704 /* Insert SZB/G/R/K length descriptors */
705 if (x_gp_8bpp) {
706 if (k_only == 1) {
707 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_c / 8);
708 } else {
709 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZB%u", len_y * 6 / 8);
710 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZG%u", len_m * 6 / 8);
711 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZR%u", len_c * 6 / 8);
712 /* Add in a SZK length indication if requested */
713 if (x_gp_rk == 1) {
714 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_c / 8);
715 }
716 }
717 } else {
718 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZB%u", len_y);
719 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZG%u", len_m);
720 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZR%u", len_c);
721 /* Add in a SZK length indication if requested */
722 if (len_k) {
723 job->datalen += sprintf((char*)job->databuf + job->datalen, ",SZK%u", len_k);
724 }
725 }
726
727 /* Terminate command stream */
728 job->databuf[job->datalen++] = 0x1c;
729
730 /* Let's figure out how long the image data stream is supposed to be. */
731 uint32_t remain;
732 if (k_only) {
733 remain = len_k + 3;
734 } else {
735 remain = len_y + len_m + len_c + 3 * 3;
736 if (len_k)
737 remain += len_k + 3;
738 }
739 /* Offset the stuff we already read in. */
740 remain -= INITIAL_BUF_LEN - buf_offset;
741 remain++; /* Add in a byte for the end of job marker. This is our final value. */
742
743 /* This is how much of the initial buffer is the header length. */
744 job->hdr_len = job->datalen;
745
746 if (x_gp_8bpp) {
747 uint32_t srcbuf_offset = INITIAL_BUF_LEN - buf_offset;
748 uint8_t *srcbuf = malloc(MAX_PRINTJOB_LEN);
749 if (!srcbuf) {
750 magicard_cleanup_job(job);
751 ERROR("Memory allocation failure!\n");
752 return CUPS_BACKEND_RETRY_CURRENT;
753 }
754
755 memcpy(srcbuf, initial_buf + buf_offset, srcbuf_offset);
756
757 /* Finish loading the data */
758 while (remain > 0) {
759 i = read(data_fd, srcbuf + srcbuf_offset, remain);
760 if (i < 0) {
761 ERROR("Data Read Error: %d (%u) @%u)\n", i, remain, srcbuf_offset);
762 magicard_cleanup_job(job);
763 free(srcbuf);
764 return i;
765 }
766 if (i == 0) {
767 ERROR("Short read! (%d/%u)\n", i, remain);
768 magicard_cleanup_job(job);
769 free(srcbuf);
770 return CUPS_BACKEND_CANCEL;
771 }
772 srcbuf_offset += i;
773 remain -= i;
774 }
775
776 // XXX handle conversion of K-only jobs. if needed.
777
778 /* set up source pointers */
779 in_y = srcbuf;
780 in_m = in_y + len_y + 3;
781 in_c = in_m + len_m + 3;
782
783 /* Set up destination pointers */
784 out_y = job->databuf + job->datalen;
785 out_m = out_y + (len_y * 6 / 8) + 3;
786 out_c = out_m + (len_m * 6 / 8) + 3;
787 out_k = out_c + (len_c * 6 / 8) + 3;
788
789 /* Termination of each plane */
790 memcpy(out_m - 3, in_y + len_y, 3);
791 memcpy(out_c - 3, in_m + len_m, 3);
792 memcpy(out_k - 3, in_c + len_c, 3);
793
794 if (!x_gp_rk)
795 out_k = NULL;
796
797 INFO("Converting image data to printer's native format %s\n", x_gp_rk ? "and extracting K channel" : "");
798
799 downscale_and_extract(gamma, len_y, in_y, in_m, in_c,
800 out_y, out_m, out_c, out_k);
801
802 /* Pad out the length appropriately. */
803 job->datalen += ((len_c * 6 / 8) + 3) * 3;
804
805 /* If there's a K plane, compute length.. */
806 if (out_k) {
807 job->datalen += (len_c / 8);
808 job->databuf[job->datalen++] = 0x1c;
809 job->databuf[job->datalen++] = 0x4b;
810 job->databuf[job->datalen++] = 0x3a;
811 }
812
813 /* Terminate the entire stream */
814 job->databuf[job->datalen++] = 0x03;
815
816 free(srcbuf);
817 } else {
818 uint32_t srcbuf_offset = INITIAL_BUF_LEN - buf_offset;
819 memcpy(job->databuf + job->datalen, initial_buf + buf_offset, srcbuf_offset);
820 job->datalen += srcbuf_offset;
821
822 /* Finish loading the data */
823 while (remain > 0) {
824 i = read(data_fd, job->databuf + job->datalen, remain);
825 if (i < 0) {
826 ERROR("Data Read Error: %d (%u) @%d)\n", i, remain, job->datalen);
827 magicard_cleanup_job(job);
828 return i;
829 }
830 if (i == 0) {
831 magicard_cleanup_job(job);
832 ERROR("Short read! (%d/%u)\n", i, remain);
833 return CUPS_BACKEND_CANCEL;
834 }
835 job->datalen += i;
836 remain -= i;
837 }
838 }
839
840 *vjob = job;
841
842 return CUPS_BACKEND_OK;
843 }
844
magicard_main_loop(void * vctx,const void * vjob)845 static int magicard_main_loop(void *vctx, const void *vjob) {
846 struct magicard_ctx *ctx = vctx;
847 int ret;
848 int copies;
849
850 const struct magicard_printjob *job = vjob;
851
852 // XXX printer handles copy generation..
853 // but it's a numeric parameter. Bleh.
854 if (!ctx)
855 return CUPS_BACKEND_FAILED;
856 if (!job)
857 return CUPS_BACKEND_FAILED;
858
859 copies = job->copies;
860 top:
861 if ((ret = send_data(ctx->dev, ctx->endp_down,
862 job->databuf, job->hdr_len)))
863 return CUPS_BACKEND_FAILED;
864
865 if ((ret = send_data(ctx->dev, ctx->endp_down,
866 job->databuf + job->hdr_len, job->datalen - job->hdr_len)))
867 return CUPS_BACKEND_FAILED;
868
869 /* Clean up */
870 if (terminate)
871 copies = 1;
872
873 INFO("Print complete (%d copies remaining)\n", copies - 1);
874
875 if (copies && --copies) {
876 goto top;
877 }
878
879 return CUPS_BACKEND_OK;
880 }
881
magicard_cmdline(void)882 static void magicard_cmdline(void)
883 {
884 DEBUG("\t\t[ -s ] # Query status\n");
885 DEBUG("\t\t[ -q ] # Query information\n");
886 DEBUG("\t\t[ -I ] # Query printer sensors\n");
887 DEBUG("\t\t[ -E ] # Eject card\n");
888 DEBUG("\t\t[ -T ] # Print self-test card\n");
889 DEBUG("\t\t[ -R ] # Reset printer\n");
890 }
891
magicard_cmdline_arg(void * vctx,int argc,char ** argv)892 static int magicard_cmdline_arg(void *vctx, int argc, char **argv)
893 {
894 struct magicard_ctx *ctx = vctx;
895 int i, j = 0;
896
897 if (!ctx)
898 return -1;
899
900 while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "sqEIRT")) >= 0) {
901 switch(i) {
902 GETOPT_PROCESS_GLOBAL
903 case 's':
904 j = magicard_query_status(ctx);
905 break;
906 case 'q':
907 j = magicard_query_printer(ctx);
908 break;
909 case 'E':
910 j = magicard_eject(ctx);
911 break;
912 case 'I':
913 j = magicard_query_sensors(ctx);
914 break;
915 case 'R':
916 j = magicard_reset(ctx);
917 break;
918 case 'T':
919 j = magicard_selftest_card(ctx);
920 break;
921 }
922
923 if (j) return j;
924 }
925
926 return 0;
927 }
928
magicard_query_markers(void * vctx,struct marker ** markers,int * count)929 static int magicard_query_markers(void *vctx, struct marker **markers, int *count)
930 {
931 struct magicard_ctx *ctx = vctx;
932
933 *markers = &ctx->marker;
934 *count = 1;
935
936 return CUPS_BACKEND_OK;
937 }
938
939 static const char *magicard_prefixes[] = {
940 "magicard", // Family name
941 "magicard-tango-2e", "magicard-enduro", "magicard-enduroplus",
942 // extras
943 "magicard-rio-2e",
944 // backwards compatibility
945 "tango2e", "enduro", "enduroplus",
946 NULL
947 };
948
949 struct dyesub_backend magicard_backend = {
950 .name = "Magicard family",
951 .version = "0.16",
952 .uri_prefixes = magicard_prefixes,
953 .cmdline_arg = magicard_cmdline_arg,
954 .cmdline_usage = magicard_cmdline,
955 .init = magicard_init,
956 .attach = magicard_attach,
957 .cleanup_job = magicard_cleanup_job,
958 .read_parse = magicard_read_parse,
959 .main_loop = magicard_main_loop,
960 .query_markers = magicard_query_markers,
961 .devices = {
962 { USB_VID_MAGICARD, USB_PID_MAGICARD_TANGO2E, P_MAGICARD, NULL, "magicard-tango2e"},
963 { USB_VID_MAGICARD, USB_PID_MAGICARD_ENDURO, P_MAGICARD, NULL, "magicard-enduro"},
964 { USB_VID_MAGICARD, USB_PID_MAGICARD_ENDUROPLUS, P_MAGICARD, NULL, "magicard-enduroplus"},
965 { USB_VID_MAGICARD, 0xFFFF, P_MAGICARD, NULL, "magicard"},
966 { 0, 0, 0, NULL, "magicard"}
967 }
968 };
969
970 /* Magicard family Spool file format (Tango2e/Rio2e/AvalonE family)
971
972 This one was rather fun to figure out.
973
974 * Job starts with a sequence of 64 '0x05'
975 * Command sequence starts with 0x01
976 * Commands are textual and comma-separated.
977 * Most are passed through ignored, except for:
978 * SZB, SZG, SZR, SZK -- indicate length of respective data plane
979 * IMF -- Image format (BGR/BGRK/K)
980 * X-GP-8 -- Tells backend to convert from Gutenprint's 8bpp data
981 * X-GP-RK -- Tells backend to extract K channel from color data
982 * Command sequence ends with 0x1c
983 * Image plane data follows, in the order of the SZ# entries
984 * Plane lengths are specified by the SZ# entry.
985 * Color planes are actually Y/M/C rather than B/G/R!
986 * Each plane terminates with 0x1c __ 0x3a, where __ is 0x42, 0x47, 0x52,
987 and 0x4b for B/G/R/K respectively. Terminator is _not_ part of length.
988 * Image data is 6bpp for B/G/R and 1bpp for K, 672*1016 pixels
989 * Organized in a series of 84-byte rows.
990 * Byte data is LSB first.
991 * Each row is a single stripe of a single bit of a pixel, so
992 color data is b0b0b0b0.. b1b1b1b1.. .. b5b5b5b5.
993 * Job ends with 0x03
994
995 ** ** ** ** ** **
996
997 Firmware updates:
998
999 0x05 (x9) 0x01 REQ,FRM###### 0x1c
1000
1001 Where ###### is the length of the firmware image.
1002
1003 Then send over 64 bytes at a time until it's done.
1004
1005 Then send 0x03 to mark end of job.
1006
1007 Follow it with:
1008
1009 0x01 STA,CHK########, 0x03 (8-digit checksum?)
1010
1011 0x05 (x9) 0x01 REQ,UPG, 0x1c 0x03
1012
1013 ** ** ** ** ** **
1014
1015 Known commands seen in print jobs:
1016
1017 BAC%s Backside format (CKO, KO, C, CO, K) -- Only used with Duplex.
1018 CKI%s Custom Holokote (ON or OFF)
1019 CPW%s Color power level (0-100, default 50)
1020 DPX%s Duplex (ON or OFF)
1021 EOI%d Card alignment end (0-100, default 50)
1022 ESS%d Number of copies (1-?)
1023 HGT%d Image Height (always seems to be 1016)
1024 HKM%06X Holokote hole. bitwise number, each bit corresponds to an area.
1025 HKT%d Holokote type (1 is "ultra secure, 2 is "interlocking rings", etc)
1026 HPH%s Holopatch (ON or OFF)
1027 IMF%s Image Data Format (BGR, BGRK, K)
1028 KPW%s Black power level (0-100, default 50)
1029 LAN%s Printer display lanaguage (ENG, ITA, POR, FRA, DEU, ESP, SCH)
1030 LC%d Force media type (LC1, LC3, LC6, LC8 for YMCKO/MONO/KO/YMCKOK)
1031 NCT%d,%d,%d,%d Overcoat hole
1032 OPW%s Overcoat power level (0-100, default 50)
1033 OVR%s Overcoat (ON or OFF)
1034 PAG%d Page number (always 1, except 2 if printing duplex backside)
1035 PAT%d Holopatch area (0-24)
1036 REJ%s Reject faulty cards (ON or OFF)
1037 SOI%d Card alignment start (0-100, default 50)
1038 SLW%s Colorsure (ON or OFF)
1039 SZB%d Blue data length
1040 SZG%d Green data length
1041 SZK%d Black data length
1042 SZR%d Red data length
1043 TDT%08X Driver-supplied timestamp of print job.
1044 USF%s Holokote (ON or OFF)
1045 VER%s Inform the printer of the driver version (seems to be ignored)
1046 WID%d Image Width (always seems to be 642)
1047
1048 Mag-stripe encoding:
1049
1050 MAG%d Magstripe position (1, 2, or 3)
1051 BPI%d Bits per Inch (75 or 210)
1052 MPC%d Character encoding (5 or 7)
1053 COE%s 'H'igh or 'L'ow coercivity
1054
1055 Unknown commands seen in print jobs:
1056
1057 DDD%s ? (only seen '50') -- Could it be K alignment?
1058 KEE ?
1059 NNN%s ? (Seen 'OFF')
1060 NOC%d ? (Seen '1') (Seems to start a job)
1061 PCT%d,%d,%d,%d ? Print area, seems fixed @ 0,0, 1025, 641)
1062 RT2 ?
1063 TRO%d ? (Seen '0', appears with Holokote)
1064 XCO%d ? X start offset (always seems to be 0)
1065 YCO%d ? Y start offset (always seems to be 0)
1066
1067 Unknown commands: (Seen in firmware guts)
1068
1069 AAA
1070 AMS
1071 BBB%d Numeric parameter
1072 CLR
1073 FBF
1074 FTC
1075 HFD%s String parameter
1076 IPM
1077 KKK
1078 LBL
1079 LLL
1080 LRC
1081 MGV%s "ON" or "OFF" but no idea
1082 MMM
1083 PAR
1084 RDM
1085 SNR
1086 SSP
1087
1088 Unknown commands unique to Tango +L (ie w/ Laminator support)
1089
1090 FRN
1091 LAM
1092 LAM_DLY
1093 LAM_SPD
1094 LAM_LEN
1095 LAM_END
1096 LAM_STA
1097 LAM_DEG
1098 LAM_FLM
1099 LAM_KBD
1100 LAM_MOD
1101
1102 Commands consumed by backend:
1103
1104 ICC%d Gamma curve (0, 1, 2) -- off, 2.2, or 1.8 respectively.
1105 X-GP-8 Raw data is 8bpp. needs to be converted.
1106 X-GP-RK Extract K channel from color data.
1107
1108 Open questions:
1109
1110 * How to query/read magstripe
1111 * How to set IP address (etc)
1112 * How to set other parameters
1113
1114 "Simple Commands" (REQ,....,)
1115
1116 RST Reset printer
1117 TST Generate self-test page
1118 EJT Eject card
1119
1120 Other "Simple commands" referenced in Rio Pro/Enduro+ docs
1121
1122 DEALERSERVICE%s ON/OFF (enter/exit dealer service mode)
1123 CAM Reset print head cam position
1124 CHP%s UP/DOWN Feed card into smart encoder
1125 CLN Cleaning cycle
1126 DYE Re-init dye film
1127 ENC Test encoding cycle
1128 FEED%d 0/1,+ 0/1, load card into standby, >1 feed N cards.
1129 FLIP Flip card in printer
1130 FRN%s ON/OFF -- Film saving
1131 HEAD%s UP/DOWN -- Raise or lower print head.
1132 RAMP%d 0-100 Density ramp, 50 default
1133 SET Saves settings into NVDATA
1134 STN Re-init Holokote
1135 SNS Soak cycle, test all sensors
1136 SHW%s CAM, TACHO, FLIP, DYE, LID, FRONT, MID, READ, BUTTON1, BUTTON2,
1137 SMART, TEMP, ON, OFF
1138 LNG%d 0/1/2/3/4/5 == ENG/POR/FRE/GER/SPA/ITA
1139 RUN%s CAM, FEED, DYE, MAIN, FLIPPER, FLIPROLL, FAN, PANEL, POUT, CAL, LCD,
1140 OFF
1141 FLM%s Y/M/C/K/O Align ribbon at corresponding panel
1142 FCL Init dye calibration routine
1143 FCL###### Set dye color to ###### (RGB hex)
1144
1145 */
1146