kopia lustrzana https://gitlab.com/sane-project/backends
Changes for ADF simplex and duplex scan, MP970 4800 dpi and TPU scan.
rodzic
7fa6ae708c
commit
1aa1677ef2
17
ChangeLog
17
ChangeLog
|
@ -1,3 +1,20 @@
|
|||
2008-10-04 Nicolas Martin <nicols-guest at users.alioth.debian.org>
|
||||
* backend/pixma.c, backend/pixma.h, backend/pixma_common.c,
|
||||
backend/pixma_io_sanei.c, backend/pixma_mp150.c,
|
||||
doc/sane-pixma.man, doc/description/pixma.desc:
|
||||
MP970 scanning improvements, up to 4800 dpi. On the way soon,
|
||||
network BJNP protocol designed by Louis Lagendijk to be added to CVS.
|
||||
MX7600 reported to work fine with the backend.
|
||||
ADF scanning:
|
||||
- improved for latest PIXMAs like MX850, MX310.
|
||||
- bug fix in Sane_start, when scanning several pages with ADF.
|
||||
ADF DUPLEX scanning:
|
||||
- new code for ADF Duplex, (to be tested) based on a MX850 Snoop. Changes
|
||||
might fit also MP830 (To be confirmed).
|
||||
TPU scanning:
|
||||
- MP970 TPU scanning: Protocol works, get scanned TPU images with 48 bits
|
||||
to 24 bits conversion, full 48 bit version yet to be debugged.
|
||||
|
||||
2008-10-03 m. allan noah <kitno455 a t gmail d o t com>
|
||||
* backend/epjitsu.[ch]: backend v17:
|
||||
- increase scan height ~1/2 inch due to head offset
|
||||
|
|
|
@ -582,6 +582,7 @@ calc_scan_param (pixma_sane_t * ss, pixma_scan_param_t * sp)
|
|||
|
||||
sp->gamma_table = (OVAL (opt_custom_gamma).b) ? ss->gamma_table : NULL;
|
||||
sp->source = ss->source_map[OVAL (opt_source).w];
|
||||
sp->adf_pageid = ss->page_count;
|
||||
|
||||
error = pixma_check_scan_param (ss->s, sp);
|
||||
if (error < 0)
|
||||
|
@ -646,7 +647,7 @@ init_option_descriptors (pixma_sane_t * ss)
|
|||
ss->source_map[i] = PIXMA_SOURCE_ADFDUP;
|
||||
i++;
|
||||
}
|
||||
#if 0
|
||||
#if 1
|
||||
if (cfg->cap & PIXMA_CAP_TPU)
|
||||
{
|
||||
ss->source_list[i] = SANE_I18N ("Transparency Unit");
|
||||
|
@ -1220,9 +1221,20 @@ sane_start (SANE_Handle h)
|
|||
if (!ss)
|
||||
return SANE_STATUS_INVAL;
|
||||
if (!ss->idle && ss->scanning)
|
||||
{
|
||||
PDBG (pixma_dbg (3, "Warning in Sane_start: !idle && scanning. idle=%d, ss->scanning=%d\n",
|
||||
ss->idle, ss->scanning));
|
||||
if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
|
||||
return SANE_STATUS_INVAL;
|
||||
}
|
||||
|
||||
ss->cancel = SANE_FALSE;
|
||||
if (ss->idle ||
|
||||
ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED ||
|
||||
ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU)
|
||||
ss->page_count = 0; /* start from idle state or scan from flatbed or TPU */
|
||||
else
|
||||
ss->page_count++;
|
||||
if (calc_scan_param (ss, &ss->sp) < 0)
|
||||
return SANE_STATUS_INVAL;
|
||||
ss->image_bytes_read = 0;
|
||||
|
@ -1233,12 +1245,6 @@ sane_start (SANE_Handle h)
|
|||
{
|
||||
ss->output_line_size = ss->sp.w * ss->sp.channels * (ss->sp.depth / 8);
|
||||
ss->byte_pos_in_line = 0;
|
||||
if (ss->idle ||
|
||||
ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED ||
|
||||
ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU)
|
||||
ss->page_count = 0; /* start from idle state or scan from flatbed or TPU */
|
||||
else
|
||||
ss->page_count++;
|
||||
ss->last_read_status = SANE_STATUS_GOOD;
|
||||
ss->scanning = SANE_TRUE;
|
||||
ss->idle = SANE_FALSE;
|
||||
|
@ -1296,9 +1302,7 @@ sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
|
|||
n = ss->sp.line_size - ss->byte_pos_in_line;
|
||||
if (n > (int) sizeof (temp))
|
||||
{
|
||||
PDBG (pixma_dbg (3,
|
||||
"Inefficient skip buffer. Should be %d\n",
|
||||
n));
|
||||
PDBG (pixma_dbg (3, "Inefficient skip buffer. Should be %d\n", n));
|
||||
n = sizeof (temp);
|
||||
}
|
||||
status = read_image (ss, temp, n, &n);
|
||||
|
|
|
@ -266,6 +266,9 @@ struct pixma_scan_param_t
|
|||
|
||||
/** \see #pixma_paper_source_t */
|
||||
pixma_paper_source_t source;
|
||||
|
||||
/** The current page # in the same ADF scan session, 0 in non ADF */
|
||||
unsigned adf_pageid;
|
||||
};
|
||||
|
||||
/** PIXMA model information */
|
||||
|
|
|
@ -644,7 +644,6 @@ pixma_read_image (pixma_t * s, void *buf, unsigned len)
|
|||
if (result == 0)
|
||||
{ /* end of image? */
|
||||
s->ops->finish_scan (s);
|
||||
#ifndef NDEBUG
|
||||
if (s->cur_image_size != s->param->image_size)
|
||||
{
|
||||
pixma_dbg (1, "WARNING:image size mismatches\n");
|
||||
|
@ -659,7 +658,6 @@ pixma_read_image (pixma_t * s, void *buf, unsigned len)
|
|||
"BUG:received data not multiple of line_size\n");
|
||||
}
|
||||
}
|
||||
#endif /* !NDEBUG */
|
||||
if (s->cur_image_size < s->param->image_size)
|
||||
{
|
||||
s->underrun = 1;
|
||||
|
|
|
@ -57,6 +57,19 @@
|
|||
#include "pixma_common.h"
|
||||
#include "pixma_io.h"
|
||||
|
||||
/* Some macro code to enhance readability */
|
||||
#define RET_IF_ERR(x) do { \
|
||||
if ((error = (x)) < 0) \
|
||||
return error; \
|
||||
} while(0)
|
||||
|
||||
#define WAIT_INTERRUPT(x) do { \
|
||||
error = handle_interrupt (s, x); \
|
||||
if (s->cancel) \
|
||||
return PIXMA_ECANCELED; \
|
||||
if (error != PIXMA_ECANCELED && error < 0) \
|
||||
return error; \
|
||||
} while(0)
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define UNUSED(v) (void) v
|
||||
|
@ -136,6 +149,8 @@ enum mp150_cmd_t
|
|||
cmd_scan_param_3 = 0xd820,
|
||||
cmd_scan_start_3 = 0xd920,
|
||||
cmd_status_3 = 0xda20,
|
||||
cmd_get_tpu_info_3 = 0xf520,
|
||||
cmd_set_tpu_info_3 = 0xea20,
|
||||
|
||||
cmd_e920 = 0xe920 /* seen in MP800 */
|
||||
};
|
||||
|
@ -153,10 +168,12 @@ typedef struct mp150_t
|
|||
uint8_t *data_left_ofs;
|
||||
unsigned data_left_len;
|
||||
int shift[3];
|
||||
unsigned lines_shift;
|
||||
unsigned color_shift;
|
||||
unsigned stripe_shift;
|
||||
uint8_t tpu_datalen;
|
||||
uint8_t tpu_data[0x40];
|
||||
} mp150_t;
|
||||
|
||||
|
||||
/*
|
||||
STAT: 0x0606 = ok,
|
||||
0x1515 = failed (PIXMA_ECANCELED),
|
||||
|
@ -212,11 +229,58 @@ typedef struct mp150_t
|
|||
|
||||
static void mp150_finish_scan (pixma_t * s);
|
||||
|
||||
static int
|
||||
is_scanning_from_adf (pixma_t * s)
|
||||
{
|
||||
return (s->param->source == PIXMA_SOURCE_ADF
|
||||
|| s->param->source == PIXMA_SOURCE_ADFDUP);
|
||||
}
|
||||
|
||||
static int
|
||||
is_scanning_from_adfdup (pixma_t * s)
|
||||
{
|
||||
return (s->param->source == PIXMA_SOURCE_ADFDUP);
|
||||
}
|
||||
|
||||
static int
|
||||
is_scanning_from_tpu (pixma_t * s)
|
||||
{
|
||||
return (s->param->source == PIXMA_SOURCE_TPU);
|
||||
}
|
||||
|
||||
static void
|
||||
new_cmd_tpu_msg (pixma_t *s, pixma_cmdbuf_t * cb, uint16_t cmd)
|
||||
{
|
||||
pixma_newcmd (cb, cmd, 0, 0);
|
||||
cb->buf[3] = (is_scanning_from_tpu (s)) ? 0x01 : 0x00;
|
||||
}
|
||||
|
||||
static int
|
||||
start_session (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
|
||||
new_cmd_tpu_msg (s, &mp->cb, cmd_start_session);
|
||||
return pixma_exec (s, &mp->cb);
|
||||
}
|
||||
|
||||
static int
|
||||
start_scan_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
return pixma_exec_short_cmd (s, &mp->cb, cmd_scan_start_3);
|
||||
|
||||
new_cmd_tpu_msg (s, &mp->cb, cmd_scan_start_3);
|
||||
return pixma_exec (s, &mp->cb);
|
||||
}
|
||||
|
||||
static int
|
||||
send_cmd_start_calibrate_ccd_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
|
||||
pixma_newcmd (&mp->cb, cmd_start_calibrate_ccd_3, 0, 0);
|
||||
mp->cb.buf[3] = 0x01;
|
||||
return pixma_exec (s, &mp->cb);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -241,6 +305,10 @@ static int
|
|||
has_paper (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
|
||||
if (is_scanning_from_adfdup (s))
|
||||
return (mp->current_status[1] == 0 || mp->current_status[2] == 0);
|
||||
else
|
||||
return (mp->current_status[1] == 0);
|
||||
}
|
||||
|
||||
|
@ -265,20 +333,6 @@ send_cmd_e920 (pixma_t * s)
|
|||
return pixma_exec_short_cmd (s, &mp->cb, cmd_e920);
|
||||
}
|
||||
|
||||
static int
|
||||
send_cmd_start_calibrate_ccd_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
return pixma_exec_short_cmd (s, &mp->cb, cmd_start_calibrate_ccd_3);
|
||||
}
|
||||
|
||||
static int
|
||||
start_session (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
return pixma_exec_short_cmd (s, &mp->cb, cmd_start_session);
|
||||
}
|
||||
|
||||
static int
|
||||
select_source (pixma_t * s)
|
||||
{
|
||||
|
@ -325,6 +379,32 @@ select_source (pixma_t * s)
|
|||
return pixma_exec (s, &mp->cb);
|
||||
}
|
||||
|
||||
static int
|
||||
send_get_tpu_info_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
uint8_t *data;
|
||||
int error;
|
||||
|
||||
data = pixma_newcmd (&mp->cb, cmd_get_tpu_info_3, 0, 0x34);
|
||||
RET_IF_ERR (pixma_exec (s, &mp->cb));
|
||||
memcpy (mp->tpu_data, data, 0x34);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int
|
||||
send_set_tpu_info_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
uint8_t *data;
|
||||
|
||||
if (mp->tpu_datalen == 0)
|
||||
return 0;
|
||||
data = pixma_newcmd (&mp->cb, cmd_set_tpu_info_3, 0x34, 0);
|
||||
memcpy (data, mp->tpu_data, 0x34);
|
||||
return pixma_exec (s, &mp->cb);
|
||||
}
|
||||
|
||||
static int
|
||||
send_gamma_table (pixma_t * s)
|
||||
{
|
||||
|
@ -414,36 +494,35 @@ get_cis_ccd_line_size (pixma_t * s)
|
|||
return (s->param->line_size * ((is_ccd_grayscale (s)) ? 3 : 1));
|
||||
}
|
||||
|
||||
static int
|
||||
is_scanning_from_adf (pixma_t * s)
|
||||
{
|
||||
return (s->param->source == PIXMA_SOURCE_ADF
|
||||
|| s->param->source == PIXMA_SOURCE_ADFDUP);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
calc_shifting (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
unsigned base_shift;
|
||||
|
||||
/* Default: no shift to apply (e.g. CIS sensord) */
|
||||
mp->stripe_shift = 0;
|
||||
mp->color_shift = 0;
|
||||
|
||||
/* If color plane shift (CCD devices), how many pixels shift */
|
||||
switch (s->cfg->pid)
|
||||
{
|
||||
case MP970_PID: /* MP970 at 4800 dpi */
|
||||
if (s->param->xdpi == 4800)
|
||||
mp->stripe_shift = 3;
|
||||
/* fall through */
|
||||
case MP800_PID:
|
||||
case MP810_PID:
|
||||
case MP830_PID:
|
||||
case MP960_PID:
|
||||
case MP970_PID:
|
||||
mp->lines_shift = 0;
|
||||
if (s->param->ydpi > 75)
|
||||
mp->lines_shift = s->param->ydpi / 50;
|
||||
mp->color_shift = s->param->ydpi / 50;
|
||||
break;
|
||||
|
||||
default: /* all CIS devices */
|
||||
mp->lines_shift = 0;
|
||||
break;
|
||||
}
|
||||
base_shift = get_cis_ccd_line_size (s) * mp->lines_shift;
|
||||
base_shift = get_cis_ccd_line_size (s) * mp->color_shift;
|
||||
|
||||
/* If color plane shift, how to apply the shift */
|
||||
switch (s->cfg->pid)
|
||||
|
@ -463,7 +542,7 @@ calc_shifting (pixma_t * s)
|
|||
mp->shift[1] = base_shift;
|
||||
mp->shift[2] = 0;
|
||||
}
|
||||
return mp->lines_shift;
|
||||
return (2 * mp->color_shift + mp->stripe_shift);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -472,7 +551,7 @@ send_scan_param (pixma_t * s)
|
|||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
uint8_t *data;
|
||||
unsigned raw_width = calc_raw_width (mp, s->param);
|
||||
unsigned h = MIN (s->param->h + 2 * calc_shifting (s),
|
||||
unsigned h = MIN (s->param->h + calc_shifting (s),
|
||||
s->cfg->height * s->param->ydpi / 75);
|
||||
|
||||
if (mp->generation <= 2)
|
||||
|
@ -494,9 +573,20 @@ send_scan_param (pixma_t * s)
|
|||
else
|
||||
{
|
||||
data = pixma_newcmd (&mp->cb, cmd_scan_param_3, 0x38, 0);
|
||||
data[0x00] = (s->param->source == PIXMA_SOURCE_ADF) ? 0x02 : 0x01;
|
||||
data[0x00] = (is_scanning_from_adf (s)) ? 0x02 : 0x01;
|
||||
data[0x01] = 0x01;
|
||||
if (is_scanning_from_tpu (s))
|
||||
{
|
||||
data[0x00] = 0x04;
|
||||
data[0x01] = 0x02;
|
||||
data[0x1e] = 0x02;
|
||||
}
|
||||
data[0x02] = 0x01;
|
||||
if (is_scanning_from_adfdup (s))
|
||||
{
|
||||
data[0x02] = 0x03;
|
||||
data[0x03] = 0x03;
|
||||
}
|
||||
data[0x05] = 0x01; /* This one also seen at 0. Don't know yet what's used for */
|
||||
pixma_set_be16 (s->param->xdpi | 0x8000, data + 0x08);
|
||||
pixma_set_be16 (s->param->ydpi | 0x8000, data + 0x0a);
|
||||
|
@ -505,7 +595,9 @@ send_scan_param (pixma_t * s)
|
|||
pixma_set_be32 (raw_width, data + 0x14);
|
||||
pixma_set_be32 (h, data + 0x18);
|
||||
data[0x1c] = ((s->param->channels != 1) || is_ccd_grayscale (s)) ? 0x08 : 0x04;
|
||||
data[0x1d] = s->param->depth * ((is_ccd_grayscale (s)) ? 3 : s->param->channels); /* bits per pixel */
|
||||
data[0x1d] = s->param->depth *
|
||||
((is_ccd_grayscale (s)) ? 3 : s->param->channels) *
|
||||
((is_scanning_from_tpu (s)) ? 2 : 1); /* bits per pixel */
|
||||
data[0x1f] = 0x01;
|
||||
data[0x20] = 0xff;
|
||||
data[0x21] = 0x81;
|
||||
|
@ -525,32 +617,8 @@ query_status_3 (pixma_t * s)
|
|||
|
||||
status_len = 8;
|
||||
data = pixma_newcmd (&mp->cb, cmd_status_3, 0, status_len);
|
||||
error = pixma_exec (s, &mp->cb);
|
||||
if (error >= 0)
|
||||
{
|
||||
RET_IF_ERR (pixma_exec (s, &mp->cb));
|
||||
memcpy (mp->current_status, data, status_len);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int
|
||||
init_ccd_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
uint8_t *data;
|
||||
int error, status_len;
|
||||
|
||||
status_len = 8;
|
||||
error = send_cmd_start_calibrate_ccd_3 (s);
|
||||
if (error >= 0)
|
||||
{
|
||||
data = pixma_newcmd (&mp->cb, cmd_end_calibrate_ccd_3, 0, status_len);
|
||||
error = pixma_exec (s, &mp->cb);
|
||||
if (error >= 0)
|
||||
{
|
||||
memcpy (mp->current_status, data, status_len);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -563,13 +631,10 @@ query_status (pixma_t * s)
|
|||
|
||||
status_len = (mp->generation == 1) ? 12 : 16;
|
||||
data = pixma_newcmd (&mp->cb, cmd_status, 0, status_len);
|
||||
error = pixma_exec (s, &mp->cb);
|
||||
if (error >= 0)
|
||||
{
|
||||
RET_IF_ERR (pixma_exec (s, &mp->cb));
|
||||
memcpy (mp->current_status, data, status_len);
|
||||
PDBG (pixma_dbg (3, "Current status: paper=%u cal=%u lamp=%u busy=%u\n",
|
||||
data[1], data[8], data[7], data[9]));
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -625,17 +690,14 @@ read_image_block (pixma_t * s, uint8_t * header, uint8_t * data)
|
|||
if (mp->cb.reslen == 512)
|
||||
{
|
||||
error = pixma_read (s->io, data, IMAGE_BLOCK_SIZE - 512 + hlen);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (error);
|
||||
datalen += error;
|
||||
}
|
||||
}
|
||||
|
||||
mp->state = state_scanning;
|
||||
mp->cb.expected_reslen = 0;
|
||||
error = pixma_check_result (&mp->cb);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (pixma_check_result (&mp->cb));
|
||||
if (mp->cb.reslen < hlen)
|
||||
return PIXMA_EPROTO;
|
||||
return datalen;
|
||||
|
@ -650,10 +712,9 @@ read_error_info (pixma_t * s, void *buf, unsigned size)
|
|||
int error;
|
||||
|
||||
data = pixma_newcmd (&mp->cb, cmd_error_info, 0, len);
|
||||
error = pixma_exec (s, &mp->cb);
|
||||
if (error >= 0 && buf)
|
||||
RET_IF_ERR (pixma_exec (s, &mp->cb));
|
||||
if (buf && len < size)
|
||||
{
|
||||
if (len < size)
|
||||
size = len;
|
||||
/* NOTE: I've absolutely no idea what the returned data mean. */
|
||||
memcpy (buf, data, size);
|
||||
|
@ -704,24 +765,45 @@ handle_interrupt (pixma_t * s, int timeout)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
init_ccd_lamp_3 (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
uint8_t *data;
|
||||
int error, status_len, tmo;
|
||||
|
||||
status_len = 8;
|
||||
RET_IF_ERR (query_status (s));
|
||||
RET_IF_ERR (query_status (s));
|
||||
RET_IF_ERR (send_cmd_start_calibrate_ccd_3 (s));
|
||||
RET_IF_ERR (query_status (s));
|
||||
tmo = 20; /* like Windows driver, CCD lamp adjustment */
|
||||
while (--tmo >= 0)
|
||||
{
|
||||
data = pixma_newcmd (&mp->cb, cmd_end_calibrate_ccd_3, 0, status_len);
|
||||
RET_IF_ERR (pixma_exec (s, &mp->cb));
|
||||
memcpy (mp->current_status, data, status_len);
|
||||
PDBG (pixma_dbg (3, "Lamp status: %u , timeout in: %u\n", data[0], tmo));
|
||||
if (mp->current_status[0] == 3 || !is_scanning_from_tpu (s))
|
||||
break;
|
||||
WAIT_INTERRUPT (1000);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int
|
||||
wait_until_ready (pixma_t * s)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
int error, tmo = 60;
|
||||
|
||||
error = (mp->generation == 3) ? query_status_3 (s) : query_status (s);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR ((mp->generation == 3) ? query_status_3 (s)
|
||||
: query_status (s));
|
||||
while (!is_calibrated (s))
|
||||
{
|
||||
error = handle_interrupt (s, 1000);
|
||||
if (mp->generation == 3)
|
||||
error = query_status_3 (s);
|
||||
if (s->cancel)
|
||||
return PIXMA_ECANCELED;
|
||||
if (error != PIXMA_ECANCELED && error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (query_status_3 (s));
|
||||
WAIT_INTERRUPT (1000);
|
||||
if (--tmo == 0)
|
||||
{
|
||||
PDBG (pixma_dbg (1, "WARNING:Timed out in wait_until_ready()\n"));
|
||||
|
@ -731,14 +813,146 @@ wait_until_ready (pixma_t * s)
|
|||
#if 0
|
||||
/* If we use sanei_usb_*, we sometimes lose interrupts! So poll the
|
||||
* status here. */
|
||||
error = query_status (s);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (query_status (s));
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
shift_colors (uint8_t * dptr, uint8_t * sptr,
|
||||
unsigned w, unsigned dpi, unsigned pid,
|
||||
int * colshft, unsigned strshft)
|
||||
{
|
||||
unsigned i, sr, sg, sb, st;
|
||||
sr = colshft[0]; sg = colshft[1]; sb = colshft[2];
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
/* MP970 at 4800 dpi exception stripes shift */
|
||||
st = (pid == MP970_PID && dpi == 4800 && (i % 2) == 0) ? strshft : 0;
|
||||
|
||||
*sptr++ = *(dptr++ + sr + st);
|
||||
*sptr++ = *(dptr++ + sg + st);
|
||||
*sptr++ = *(dptr++ + sb + st);
|
||||
}
|
||||
return dptr;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w)
|
||||
{
|
||||
unsigned i, g;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
g = *sptr++;
|
||||
g += *sptr++;
|
||||
*gptr++ = (*sptr++ + g) / 3;
|
||||
}
|
||||
return gptr;
|
||||
}
|
||||
|
||||
static void
|
||||
reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, unsigned n,
|
||||
unsigned m, unsigned w, unsigned line_size)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c);
|
||||
}
|
||||
memcpy (sptr, linebuf, line_size);
|
||||
}
|
||||
|
||||
static void
|
||||
mp970_reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c,
|
||||
unsigned w, unsigned line_size)
|
||||
{
|
||||
unsigned i, i8;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
i8 = i % 8;
|
||||
memcpy (linebuf + c * (i + i8 - ((i8 > 3) ? 7 : 0)), sptr + c * i, c);
|
||||
}
|
||||
memcpy (sptr, linebuf, line_size);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
pack_48_24_bpc (uint8_t * sptr, unsigned n)
|
||||
{
|
||||
unsigned i;
|
||||
uint8_t *cptr;
|
||||
|
||||
cptr = sptr;
|
||||
if (n % 2 != 0)
|
||||
PDBG (pixma_dbg (3, "Warning: Odd number of bytes received, misaligned image.\n"));
|
||||
for (i = 0; i < n; i += 2)
|
||||
{
|
||||
sptr++;
|
||||
*cptr++ = *sptr++;
|
||||
}
|
||||
return (n / 2);
|
||||
}
|
||||
|
||||
/* This function deals both with PIXMA CCD sensors producing shifted color
|
||||
* planes images, Grayscale CCD scan and Generation 3 high dpi images.
|
||||
* Each complete line in mp->imgbuf is processed for shifting CCD sensor
|
||||
* color planes, reordering pixels above 600 dpi for Generation 3, and
|
||||
* converting to Grayscale for CCD sensors. */
|
||||
static unsigned
|
||||
post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
unsigned c, lines, i, line_size, n, m;
|
||||
uint8_t *sptr, *dptr, *gptr;
|
||||
|
||||
c = (is_ccd_grayscale (s)) ? 3 : s->param->channels;
|
||||
|
||||
if (mp->generation == 3)
|
||||
n = s->param->xdpi / 600;
|
||||
else /* FIXME: maybe need different values for CIS and CCD sensors */
|
||||
n = s->param->xdpi / 2400;
|
||||
if (s->cfg->pid == MP970_PID)
|
||||
n = MIN (n, 4);
|
||||
|
||||
m = (n > 0) ? s->param->w / n : 1;
|
||||
sptr = dptr = gptr = mp->imgbuf;
|
||||
line_size = get_cis_ccd_line_size (s);
|
||||
|
||||
lines = (mp->data_left_ofs - mp->imgbuf) / line_size;
|
||||
if (lines > 2 * mp->color_shift + mp->stripe_shift)
|
||||
{
|
||||
lines -= 2 * mp->color_shift + mp->stripe_shift;
|
||||
for (i = 0; i < lines; i++, sptr += line_size)
|
||||
{
|
||||
/* Color plane and stripes shift needed by e.g. CCD */
|
||||
if (c == 3)
|
||||
dptr = shift_colors (dptr, sptr,
|
||||
s->param->w, s->param->xdpi, s->cfg->pid,
|
||||
mp->shift, mp->stripe_shift);
|
||||
|
||||
/* special image format for *most* devices at high dpi.
|
||||
* MP220 is a gen3 exception */
|
||||
if (s->cfg->pid != MP220_PID && n > 0)
|
||||
reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->w, line_size);
|
||||
|
||||
/* MP970 specific reordering for 4800 dpi */
|
||||
if (s->cfg->pid == MP970_PID && s->param->xdpi == 4800)
|
||||
mp970_reorder_pixels (mp->linebuf, sptr, c, s->param->w, line_size);
|
||||
|
||||
/* Color to Grayscale convert for CCD sensor */
|
||||
if (is_ccd_grayscale (s))
|
||||
gptr = rgb_to_gray (gptr, sptr, s->param->w);
|
||||
}
|
||||
}
|
||||
ib->rptr = mp->imgbuf;
|
||||
ib->rend = (is_ccd_grayscale (s)) ? gptr : sptr;
|
||||
return mp->data_left_ofs - sptr; /* # of non processed bytes */
|
||||
}
|
||||
|
||||
static int
|
||||
mp150_open (pixma_t * s)
|
||||
{
|
||||
|
@ -776,8 +990,13 @@ mp150_open (pixma_t * s)
|
|||
if (s->cfg->pid == MP140_PID)
|
||||
mp->generation = 2;
|
||||
|
||||
/* TPU info data setup */
|
||||
mp->tpu_datalen = 0;
|
||||
|
||||
query_status (s);
|
||||
handle_interrupt (s, 200);
|
||||
if (mp->generation == 3 && has_ccd_sensor (s))
|
||||
send_cmd_start_calibrate_ccd_3 (s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -798,25 +1017,24 @@ mp150_check_param (pixma_t * s, pixma_scan_param_t * sp)
|
|||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
|
||||
sp->depth = 8; /* MP150 only supports 8 bit per channel. */
|
||||
/* if (mp->generation == 3 && sp->source == PIXMA_SOURCE_TPU)
|
||||
sp->depth = 16; */ /* TPU in 16 bits mode */
|
||||
|
||||
if (mp->generation >= 2)
|
||||
{
|
||||
/* mod 32 and expansion of the X scan limits */
|
||||
sp->w += (sp->x) % 32;
|
||||
sp->w = calc_raw_width (mp, sp);
|
||||
sp->x = ALIGN_INF (sp->x, 32);
|
||||
/* FIXME: TBC, if all gen2 and gen3 devices do not need
|
||||
* modulo 32 alignment on Y axis. If needed, uncomment next 2 lines */
|
||||
/* sp->h += (sp->y) % 32;
|
||||
sp->y = ALIGN_INF (sp->y, 32);*/
|
||||
}
|
||||
sp->line_size = calc_raw_width (mp, sp) * sp->channels;
|
||||
sp->line_size = calc_raw_width (mp, sp) * sp->channels * (sp->depth / 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp150_scan (pixma_t * s)
|
||||
{
|
||||
int error = 0, tmo;
|
||||
int error = 0, tmo, i;
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
|
||||
if (mp->state != state_idle)
|
||||
|
@ -835,11 +1053,7 @@ mp150_scan (pixma_t * s)
|
|||
tmo = 10;
|
||||
while (!has_paper (s) && --tmo >= 0)
|
||||
{
|
||||
error = handle_interrupt (s, 1000);
|
||||
if (s->cancel)
|
||||
return PIXMA_ECANCELED;
|
||||
if (error != PIXMA_ECANCELED && error < 0)
|
||||
return error;
|
||||
WAIT_INTERRUPT (1000);
|
||||
PDBG (pixma_dbg
|
||||
(2, "No paper in ADF. Timed out in %d sec.\n", tmo));
|
||||
}
|
||||
|
@ -847,40 +1061,36 @@ mp150_scan (pixma_t * s)
|
|||
return PIXMA_ENO_PAPER;
|
||||
}
|
||||
|
||||
if (has_ccd_sensor (s))
|
||||
if (has_ccd_sensor (s) && (mp->generation <= 2))
|
||||
{
|
||||
error = (mp->generation <= 2) ? send_cmd_e920 (s)
|
||||
: send_cmd_start_calibrate_ccd_3 (s);
|
||||
error = send_cmd_e920 (s);
|
||||
switch (error)
|
||||
{
|
||||
case PIXMA_ECANCELED:
|
||||
case PIXMA_EBUSY:
|
||||
PDBG (pixma_dbg
|
||||
(2, "cmd e920 or d520 returned %s\n", pixma_strerror (error)));
|
||||
PDBG (pixma_dbg (2, "cmd e920 or d520 returned %s\n",
|
||||
pixma_strerror (error)));
|
||||
/* fall through */
|
||||
case 0:
|
||||
query_status (s);
|
||||
break;
|
||||
default:
|
||||
PDBG (pixma_dbg
|
||||
(1, "WARNING:cmd e920 or d520 failed %s\n", pixma_strerror (error)));
|
||||
PDBG (pixma_dbg (1, "WARNING:cmd e920 or d520 failed %s\n",
|
||||
pixma_strerror (error)));
|
||||
return error;
|
||||
}
|
||||
tmo = 3; /* like Windows driver, CCD calibration ? */
|
||||
while (--tmo >= 0)
|
||||
{
|
||||
error = handle_interrupt (s, 1000);
|
||||
if (s->cancel)
|
||||
return PIXMA_ECANCELED;
|
||||
if (error != PIXMA_ECANCELED && error < 0)
|
||||
return error;
|
||||
PDBG (pixma_dbg
|
||||
(2, "CCD Calibration ends in %d sec.\n", tmo));
|
||||
WAIT_INTERRUPT (1000);
|
||||
PDBG (pixma_dbg (2, "CCD Calibration ends in %d sec.\n", tmo));
|
||||
}
|
||||
/* pixma_sleep(2000000); */
|
||||
}
|
||||
|
||||
tmo = 10;
|
||||
if (s->param->adf_pageid == 0 || mp->generation <= 2)
|
||||
{
|
||||
error = start_session (s);
|
||||
while (error == PIXMA_EBUSY && --tmo >= 0)
|
||||
{
|
||||
|
@ -909,13 +1119,17 @@ mp150_scan (pixma_t * s)
|
|||
if ((error >= 0) && (mp->generation <= 2))
|
||||
error = select_source (s);
|
||||
if ((error >= 0) && (mp->generation == 3) && has_ccd_sensor (s))
|
||||
error = init_ccd_3 (s);
|
||||
if (error >= 0)
|
||||
error = send_gamma_table (s);
|
||||
if ((error >= 0) && (mp->generation == 3))
|
||||
error = send_gamma_table (s);
|
||||
if ((error >= 0) && (mp->generation == 3))
|
||||
error = init_ccd_lamp_3 (s);
|
||||
if ((error >= 0) && !is_scanning_from_tpu (s))
|
||||
for (i = (mp->generation == 3) ? 3 : 1 ; i > 0 && error >= 0; i--)
|
||||
error = send_gamma_table (s);
|
||||
else if (mp->generation == 3) /* FIXME: Does this apply also to gen2 ? */
|
||||
error = send_set_tpu_info_3 (s);
|
||||
}
|
||||
else /* ADF pageid != 0 and gen3 */
|
||||
pixma_sleep (1000000);
|
||||
if ((error >= 0) || (mp->generation == 3))
|
||||
mp->state = state_warmup;
|
||||
if (error >= 0)
|
||||
error = send_scan_param (s);
|
||||
if ((error >= 0) && (mp->generation == 3))
|
||||
|
@ -928,98 +1142,6 @@ mp150_scan (pixma_t * s)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
shift_colors (uint8_t * dptr, uint8_t * sptr, unsigned w, int sr, int sg, int sb)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
*sptr++ = *(dptr++ + sr);
|
||||
*sptr++ = *(dptr++ + sg);
|
||||
*sptr++ = *(dptr++ + sb);
|
||||
}
|
||||
return dptr;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w)
|
||||
{
|
||||
unsigned i, g;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
g = *sptr++;
|
||||
g += *sptr++;
|
||||
*gptr++ = (*sptr++ + g) / 3;
|
||||
}
|
||||
return gptr;
|
||||
}
|
||||
|
||||
static void
|
||||
reorder_pixels (uint8_t * linebuf, uint8_t * sptr, unsigned c, unsigned n,
|
||||
unsigned m, unsigned w, unsigned line_size)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < w; i++)
|
||||
{
|
||||
memcpy (linebuf + c * (n * (i % m) + i / m), sptr + c * i, c);
|
||||
}
|
||||
memcpy (sptr, linebuf, line_size);
|
||||
}
|
||||
|
||||
/* This function deals both with PIXMA CCD sensors producing shifted color
|
||||
* planes images, Grayscale CCD scan and Generation 3 high dpi images.
|
||||
* Each complete line in mp->imgbuf is processed for shifting CCD sensor
|
||||
* color planes, reordering pixels above 600 dpi for Generation 3, and
|
||||
* converting to Grayscale for CCD sensors. */
|
||||
static unsigned
|
||||
post_process_image_data (pixma_t * s, pixma_imagebuf_t * ib)
|
||||
{
|
||||
mp150_t *mp = (mp150_t *) s->subdriver;
|
||||
unsigned c, lines, i, line_size, n, m;
|
||||
uint8_t *sptr, *dptr, *gptr;
|
||||
|
||||
c = (is_ccd_grayscale (s)) ? 3 : s->param->channels;
|
||||
|
||||
if (mp->generation == 3)
|
||||
n = s->param->xdpi / 600;
|
||||
else /* FIXME: maybe need different values for CIS and CCD sensors */
|
||||
n = s->param->xdpi / 2400;
|
||||
if (s->cfg->pid == MP970_PID)
|
||||
n = MIN (n, 4);
|
||||
|
||||
m = (n > 0) ? s->param->w / n : 1;
|
||||
sptr = dptr = gptr = mp->imgbuf;
|
||||
line_size = get_cis_ccd_line_size (s);
|
||||
|
||||
lines = (mp->data_left_ofs - mp->imgbuf) / line_size;
|
||||
if (lines > 2 * mp->lines_shift)
|
||||
{
|
||||
lines -= 2 * mp->lines_shift;
|
||||
for (i = 0; i < lines; i++, sptr += line_size)
|
||||
{
|
||||
/* Color plane shift needed by e.g. CCD */
|
||||
if (c == 3)
|
||||
dptr = shift_colors (dptr, sptr, s->param->w,
|
||||
mp->shift[0], mp->shift[1], mp->shift[2]);
|
||||
|
||||
/* special image format for *most* devices at high dpi.
|
||||
* MP220 is a gen3 exception */
|
||||
if (s->cfg->pid != MP220_PID && n > 0)
|
||||
reorder_pixels (mp->linebuf, sptr, c, n, m, s->param->w, line_size);
|
||||
|
||||
/* Color to Grayscale convert for CCD sensor */
|
||||
if (is_ccd_grayscale (s))
|
||||
gptr = rgb_to_gray (gptr, sptr, s->param->w);
|
||||
}
|
||||
}
|
||||
ib->rptr = mp->imgbuf;
|
||||
ib->rend = (is_ccd_grayscale (s)) ? gptr : sptr;
|
||||
return mp->data_left_ofs - sptr; /* # of non processed bytes */
|
||||
}
|
||||
|
||||
static int
|
||||
mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
|
||||
{
|
||||
|
@ -1030,9 +1152,7 @@ mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
|
|||
|
||||
if (mp->state == state_warmup)
|
||||
{
|
||||
error = wait_until_ready (s);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (wait_until_ready (s));
|
||||
pixma_sleep (1000000); /* No need to sleep, actually, but Window's driver
|
||||
* sleep 1.5 sec. */
|
||||
mp->state = state_scanning;
|
||||
|
@ -1054,8 +1174,7 @@ mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
|
|||
if (s->cancel)
|
||||
return PIXMA_ECANCELED;
|
||||
if ((mp->last_block & 0x28) == 0x28)
|
||||
{
|
||||
/* end of image */
|
||||
{ /* end of image */
|
||||
mp->state = state_finished;
|
||||
return 0;
|
||||
}
|
||||
|
@ -1082,11 +1201,15 @@ mp150_fill_buffer (pixma_t * s, pixma_imagebuf_t * ib)
|
|||
PASSERT (bytes_received == block_size);
|
||||
|
||||
if (block_size == 0)
|
||||
{
|
||||
/* no image data at this moment. */
|
||||
{ /* no image data at this moment. */
|
||||
pixma_sleep (10000);
|
||||
}
|
||||
|
||||
/* In case of TPU, at 48 bits/col scan, pack the newly received data */
|
||||
if (mp->generation == 3 && is_scanning_from_tpu (s))
|
||||
bytes_received = pack_48_24_bpc (mp->imgbuf + mp->data_left_len, bytes_received);
|
||||
|
||||
/* Post-process the image data */
|
||||
mp->data_left_ofs = mp->imgbuf + mp->data_left_len + bytes_received;
|
||||
mp->data_left_len = post_process_image_data (s, ib);
|
||||
mp->data_left_ofs -= mp->data_left_len;
|
||||
|
@ -1109,17 +1232,20 @@ mp150_finish_scan (pixma_t * s)
|
|||
/* fall through */
|
||||
case state_scanning:
|
||||
case state_warmup:
|
||||
error = abort_session (s);
|
||||
if (error < 0)
|
||||
PDBG (pixma_dbg (1, "WARNING:abort_session() failed %d\n", error));
|
||||
/* fall through */
|
||||
case state_finished:
|
||||
/* For gen3 TPU , send the get TPU info message */
|
||||
if (mp->generation == 3 && is_scanning_from_tpu (s) && mp->tpu_datalen == 0)
|
||||
send_get_tpu_info_3 (s);
|
||||
/* FIXME: to process several pages ADF scan, must not send
|
||||
* abort_session and start_session between pages (last_block=0x28)
|
||||
* if (mp->last_block != 0x28 || !is_scanning_from_adf(s)) */
|
||||
if (mp->last_block != 0x38)
|
||||
abort_session (s); /* FIXME: it probably doesn't work in duplex mode! */
|
||||
if (mp->generation <= 2 || !is_scanning_from_adf (s) || mp->last_block == 0x38)
|
||||
{
|
||||
error = abort_session (s); /* FIXME: it probably doesn't work in duplex mode! */
|
||||
if (error < 0)
|
||||
PDBG (pixma_dbg (1, "WARNING:abort_session() failed %d\n", error));
|
||||
mp->state = state_idle;
|
||||
}
|
||||
/* fall through */
|
||||
case state_idle:
|
||||
break;
|
||||
|
@ -1141,9 +1267,7 @@ mp150_get_status (pixma_t * s, pixma_device_status_t * status)
|
|||
{
|
||||
int error;
|
||||
|
||||
error = query_status (s);
|
||||
if (error < 0)
|
||||
return error;
|
||||
RET_IF_ERR (query_status (s));
|
||||
status->hardware = PIXMA_HARDWARE_OK;
|
||||
status->adf = (has_paper (s)) ? PIXMA_ADF_OK : PIXMA_ADF_NO_PAPER;
|
||||
status->cal =
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
:interface "USB"
|
||||
:usbid "0x04a9" "0x1713"
|
||||
:status :basic
|
||||
:comment "Single-side ADF works but duplex doesn't work yet."
|
||||
:comment "Single-side ADF works, Duplex ADF to be tested."
|
||||
|
||||
:model "PIXMA MP960"
|
||||
:interface "USB"
|
||||
|
@ -169,7 +169,7 @@
|
|||
:interface "USB"
|
||||
:usbid "0x04a9" "0x1726"
|
||||
:status :good
|
||||
:comment "All resolutions supported (up to 4800DPI)"
|
||||
:comment "All resolutions supported (up to 4800DPI). TPU support currently experimental."
|
||||
|
||||
:model "SmartBase MP360"
|
||||
:interface "USB"
|
||||
|
@ -240,8 +240,8 @@
|
|||
:model "PIXMA MX7600"
|
||||
:interface "USB"
|
||||
:usbid "0x04a9" "0x171c"
|
||||
:status :untested
|
||||
:comment "Generation 3 protocol? Testers needed!"
|
||||
:status :good
|
||||
:comment "Flatbed and ADF scan. All resolutions supported (up to 4800DPI)"
|
||||
|
||||
:model "imageCLASS MF5630"
|
||||
:interface "USB"
|
||||
|
|
|
@ -18,7 +18,7 @@ PIXMA MP600, MP600R, MP610, MP710
|
|||
.br
|
||||
PIXMA MP800, MP800R, MP810, MP830, MP960, MP970
|
||||
.br
|
||||
PIXMA MX300, MX310, MX700
|
||||
PIXMA MX300, MX310, MX700, MX850, MX7600
|
||||
.br
|
||||
MultiPASS MP700, PIXMA MP750 (no grayscale)
|
||||
.br
|
||||
|
@ -47,13 +47,13 @@ ImageCLASS MF3110, MF3240
|
|||
ImageCLASS MF5630, MF5650, MF5730, MF5750, MF5770, MF8170c
|
||||
.RE
|
||||
.PP
|
||||
The following models may use partly the same Pixma protocol as MPs listed
|
||||
above, but may still need some work. They are declared in the backend as
|
||||
experimental. Snoop logs are required to further investigate, please contact
|
||||
the sane\-devel mailing list.
|
||||
\#The following models may use partly the same Pixma protocol as MPs listed
|
||||
\#above, but may still need some work. They are declared in the backend as
|
||||
\#experimental. Snoop logs are required to further investigate, please contact
|
||||
\#the sane\-devel mailing list.
|
||||
.PP
|
||||
.RS
|
||||
PIXMA MX850
|
||||
\#PIXMA MX850
|
||||
.RE
|
||||
.PP
|
||||
The backend supports
|
||||
|
@ -64,7 +64,9 @@ The backend supports
|
|||
.br
|
||||
* a custom gamma table and
|
||||
.br
|
||||
* automatic document feeder (only single side, duplex needs some work).
|
||||
* Automatic Document Feeder (Duplex for some models).
|
||||
.br
|
||||
* Transparency Unit support is still experimental.
|
||||
.PP
|
||||
The device name is in the form pixma:xxxxyyyy_zzzzz
|
||||
where x, y and z are vendor ID, product ID and serial number respectively.
|
||||
|
|
Ładowanie…
Reference in New Issue