kopia lustrzana https://gitlab.com/sane-project/backends
* backend/canon_pp.c backend/canon_pp-dev.c backend/caon_pp-io.c
backend/canon_pp-dev.h: Many changes: Bug fixes, less memory leaks (none left now?), more relaible, faster. Biggest changes are speculative reads (ask scanner to read more while data processing occurs) and more reliable sending of commands. Slight performance increase over previous version. * doc/sane-canon_pp.man: Added discussion noting that scanning greyscale in green is bad for colour.DEVEL_2_0_BRANCH-1
rodzic
f7806b4492
commit
4a552e1540
|
@ -65,7 +65,7 @@ static void DBG(int level, const char *format, ...)
|
|||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
if (level < 50) vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
#else
|
||||
|
@ -88,12 +88,8 @@ static void DBG(int level, const char *format, ...)
|
|||
*/
|
||||
static int ieee_mode = M1284_NIBBLE;
|
||||
|
||||
static const int verbose = 0;
|
||||
static const int dump_packets = 0;
|
||||
/* This is just for the time being. We need a way to turn on
|
||||
and off status messages so that wait loops involving sending
|
||||
commands don't obscure the program's status log. */
|
||||
|
||||
/* For super-verbose debugging */
|
||||
/* #define DUMP_PACKETS 0; */
|
||||
|
||||
/* Unknown command 1 */
|
||||
static unsigned char command_1[10] = { 0xec, 0x20, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
@ -176,22 +172,21 @@ int sanei_canon_pp_wake_scanner(struct parport *port)
|
|||
|
||||
int sanei_canon_pp_write(struct parport *port, int length, unsigned char *data)
|
||||
{
|
||||
|
||||
#ifdef DUMP_PACKETS
|
||||
ssize_t count;
|
||||
|
||||
if (dump_packets)
|
||||
DBG(10,"Sent: ");
|
||||
for (count = 0; count < length; count++)
|
||||
{
|
||||
DBG(10,"Sent: ");
|
||||
for (count = 0; count < length; count++)
|
||||
{
|
||||
DBG(10,"%02x ", data[count]);
|
||||
if (count % 20 == 19)
|
||||
DBG(10,"\n ");
|
||||
}
|
||||
if (count % 20 != 19) DBG(10,"\n");
|
||||
}
|
||||
DBG(10,"%02x ", data[count]);
|
||||
if (count % 20 == 19)
|
||||
DBG(10,"\n ");
|
||||
}
|
||||
if (count % 20 != 19) DBG(10,"\n");
|
||||
#endif
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "NEW Send Command (length %i):\n", length);
|
||||
DBG(100, "NEW Send Command (length %i):\n", length);
|
||||
if (ieee_mode == M1284_ECP)
|
||||
ieee_negotiation(port, ieee_mode);
|
||||
|
||||
|
@ -216,8 +211,7 @@ int sanei_canon_pp_read(struct parport *port, int length, unsigned char *data)
|
|||
{
|
||||
int count, offset;
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "NEW read_data (%i bytes):\n", length);
|
||||
DBG(200, "NEW read_data (%i bytes):\n", length);
|
||||
ieee_negotiation(port, ieee_mode);
|
||||
|
||||
/* This is special; Nibble mode needs a little
|
||||
|
@ -282,25 +276,24 @@ int sanei_canon_pp_read(struct parport *port, int length, unsigned char *data)
|
|||
|
||||
}
|
||||
|
||||
if (dump_packets)
|
||||
#ifdef DUMP_PACKETS
|
||||
if (length <= 60)
|
||||
{
|
||||
if (length <= 60)
|
||||
DBG(10,"Read: ");
|
||||
for (count = 0; count < length; count++)
|
||||
{
|
||||
DBG(10,"Read: ");
|
||||
for (count = 0; count < length; count++)
|
||||
{
|
||||
DBG(10,"%02x ", data[count]);
|
||||
if (count % 20 == 19)
|
||||
DBG(10,"\n ");
|
||||
}
|
||||
DBG(10,"%02x ", data[count]);
|
||||
if (count % 20 == 19)
|
||||
DBG(10,"\n ");
|
||||
}
|
||||
|
||||
if (count % 20 != 19) DBG(10,"\n");
|
||||
if (count % 20 != 19) DBG(10,"\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG(10,"Read: %i bytes\n", length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG(10,"Read: %i bytes\n", length);
|
||||
}
|
||||
#endif
|
||||
|
||||
scanner_endtransfer(port);
|
||||
return 0;
|
||||
|
@ -317,13 +310,11 @@ static int ieee_negotiation(struct parport *port, int e)
|
|||
{
|
||||
int temp;
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "IEEE Negotiation (mode %i)\n", e);
|
||||
DBG(200, "IEEE Negotiation (mode %i)\n", e);
|
||||
|
||||
temp = ieee1284_negotiate(port, e);
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "result: %i)\n", temp);
|
||||
DBG(200, "(result: %i)\n", temp);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
@ -332,8 +323,7 @@ static int ieee_transfer(struct parport *port, int length, unsigned char *data)
|
|||
{
|
||||
int result = 0;
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "IEEE transfer (%i bytes)\n", length);
|
||||
DBG(200, "IEEE transfer (%i bytes)\n", length);
|
||||
|
||||
switch (ieee_mode)
|
||||
{
|
||||
|
@ -355,8 +345,7 @@ int sanei_canon_pp_check_status(struct parport *port)
|
|||
int status;
|
||||
unsigned char data[2];
|
||||
|
||||
if (verbose)
|
||||
DBG(10, "* Check Status:\n");
|
||||
DBG(200, "* Check Status:\n");
|
||||
|
||||
if (sanei_canon_pp_read(port, 2, data))
|
||||
return -1;
|
||||
|
@ -366,15 +355,15 @@ int sanei_canon_pp_check_status(struct parport *port)
|
|||
switch(status)
|
||||
{
|
||||
case 0x0606:
|
||||
if (verbose) DBG(10, "Ready - 0x0606\n");
|
||||
DBG(200, "Ready - 0x0606\n");
|
||||
return 0;
|
||||
break;
|
||||
case 0x1414:
|
||||
if (verbose) DBG(10, "Busy - 0x1414\n");
|
||||
DBG(200, "Busy - 0x1414\n");
|
||||
return 1;
|
||||
break;
|
||||
case 0x0805:
|
||||
if (verbose) DBG(10, "Resetting - 0x0805\n");
|
||||
DBG(200, "Resetting - 0x0805\n");
|
||||
return 3;
|
||||
break;
|
||||
case 0x1515:
|
||||
|
@ -390,8 +379,7 @@ int sanei_canon_pp_check_status(struct parport *port)
|
|||
/* This is a subfunction of scanner_readdata() */
|
||||
static int scanner_endtransfer(struct parport *port)
|
||||
{
|
||||
if (verbose)
|
||||
DBG(40, ">> IEEE Terminate\n");
|
||||
DBG(200, ">> IEEE Terminate\n");
|
||||
ieee1284_terminate(port);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
#define MM_PER_IN 25.4
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -405,9 +406,9 @@ sane_get_devices (const SANE_Device ***dl, SANE_Bool local)
|
|||
*dl = devlist;
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
|
||||
devlist = malloc((num_devices + 1) * sizeof(*devlist));
|
||||
if (devlist == NULL)
|
||||
return (SANE_STATUS_NO_MEM);
|
||||
return SANE_STATUS_NO_MEM;
|
||||
|
||||
i = 0;
|
||||
for (dev = first_dev; dev != NULL; dev = dev->next)
|
||||
|
@ -576,28 +577,28 @@ sane_open (SANE_String_Const name, SANE_Handle *h)
|
|||
|
||||
|
||||
/* TL-X */
|
||||
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
||||
if(!(tmp_range = malloc(sizeof(*tmp_range))))
|
||||
return SANE_STATUS_NO_MEM;
|
||||
(*tmp_range).min = 0;
|
||||
(*tmp_range).max = 215;
|
||||
cs->opt[OPT_TL_X].constraint.range = tmp_range;
|
||||
|
||||
/* TL-Y */
|
||||
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
||||
if(!(tmp_range = malloc(sizeof(*tmp_range))))
|
||||
return SANE_STATUS_NO_MEM;
|
||||
(*tmp_range).min = 0;
|
||||
(*tmp_range).max = 296;
|
||||
cs->opt[OPT_TL_Y].constraint.range = tmp_range;
|
||||
|
||||
/* BR-X */
|
||||
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
||||
if(!(tmp_range = malloc(sizeof(*tmp_range))))
|
||||
return SANE_STATUS_NO_MEM;
|
||||
(*tmp_range).min = 22;
|
||||
(*tmp_range).max = 216;
|
||||
cs->opt[OPT_BR_X].constraint.range = tmp_range;
|
||||
|
||||
/* BR-Y */
|
||||
if(!(tmp_range = (SANE_Range *)malloc(sizeof(SANE_Range))))
|
||||
if(!(tmp_range = malloc(sizeof(*tmp_range))))
|
||||
return SANE_STATUS_NO_MEM;
|
||||
(*tmp_range).min = 1;
|
||||
(*tmp_range).max = 297;
|
||||
|
@ -854,7 +855,6 @@ sane_get_parameters (SANE_Handle h, SANE_Parameters *params)
|
|||
{
|
||||
int res, max_width, max_height, max_res;
|
||||
CANONP_Scanner *cs = ((CANONP_Scanner *)h);
|
||||
SANE_Int total_lines = 0;
|
||||
DBG(2, ">> sane_get_parameters (h=%p, params=%p)\n", h, params);
|
||||
|
||||
if (h == NULL) return SANE_STATUS_INVAL;
|
||||
|
@ -879,7 +879,7 @@ sane_get_parameters (SANE_Handle h, SANE_Parameters *params)
|
|||
/* Copy the options stored in the vals into the scaninfo */
|
||||
params->pixels_per_line =
|
||||
((cs->vals[OPT_BR_X] - cs->vals[OPT_TL_X]) * res) / MM_PER_IN;
|
||||
total_lines = ((cs->vals[OPT_BR_Y] - cs->vals[OPT_TL_Y]) * res)
|
||||
params->lines = ((cs->vals[OPT_BR_Y] - cs->vals[OPT_TL_Y]) * res)
|
||||
/ MM_PER_IN;
|
||||
|
||||
/* FIXME: Magic numbers ahead! */
|
||||
|
@ -899,7 +899,7 @@ sane_get_parameters (SANE_Handle h, SANE_Parameters *params)
|
|||
|
||||
if(params->pixels_per_line > max_width)
|
||||
params->pixels_per_line = max_width;
|
||||
if(total_lines > max_height) total_lines = max_height;
|
||||
if(params->lines > max_height) params->lines = max_height;
|
||||
|
||||
|
||||
params->depth = cs->vals[OPT_DEPTH] ? 16 : 8;
|
||||
|
@ -923,41 +923,18 @@ sane_get_parameters (SANE_Handle h, SANE_Parameters *params)
|
|||
params->lines = 0;
|
||||
}
|
||||
|
||||
/* Always assume next packet will be the last
|
||||
* - frontends seem to like it that way */
|
||||
/* Always the "last frame" */
|
||||
params->last_frame = SANE_TRUE;
|
||||
|
||||
switch (cs->vals[OPT_COLOUR_MODE])
|
||||
{
|
||||
case 0: /* Grey */
|
||||
params->bytes_per_line =
|
||||
params->pixels_per_line * (params->depth/8);
|
||||
break;
|
||||
case 1: /* Colour */
|
||||
params->bytes_per_line =
|
||||
params->pixels_per_line * (params->depth/8) * 3;
|
||||
break;
|
||||
default:
|
||||
/* shouldn't happen */
|
||||
break;
|
||||
}
|
||||
|
||||
if (cs->bytes_sent > 0)
|
||||
/* we want to round up the number of lines still to come */
|
||||
params->lines = total_lines -
|
||||
ceilf((float)(cs->bytes_sent) /
|
||||
(float)(params->bytes_per_line));
|
||||
else
|
||||
params->lines = total_lines;
|
||||
params->bytes_per_line = params->pixels_per_line * (params->depth/8) *
|
||||
(cs->vals[OPT_COLOUR_MODE] ? 3 : 1);
|
||||
|
||||
DBG(10, "get_params: bytes_per_line=%d, pixels_per_line=%d, lines=%d\n"
|
||||
"max_res=%d, res=%d, max_height=%d, total_lines=%d\n"
|
||||
"br_y=%d, tl_y=%d, mm_per_in=%f\n",
|
||||
"max_res=%d, res=%d, max_height=%d, br_y=%d, tl_y=%d, "
|
||||
"mm_per_in=%f\n",
|
||||
params->bytes_per_line, params->pixels_per_line, params->lines,
|
||||
max_res, res, max_height, total_lines,
|
||||
cs->vals[OPT_BR_Y], cs->vals[OPT_TL_Y], MM_PER_IN);
|
||||
|
||||
/* FIXME: Do we need to account for the scanner's max buffer here? */
|
||||
max_res, res, max_height, cs->vals[OPT_BR_Y],
|
||||
cs->vals[OPT_TL_Y], MM_PER_IN);
|
||||
|
||||
DBG(2, "<< sane_get_parameters\n");
|
||||
return SANE_STATUS_GOOD;
|
||||
|
@ -1133,7 +1110,7 @@ sane_read (SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenp)
|
|||
/* feed some more data in until we've run out - don't care
|
||||
* whether or not we _think_ the scanner is scanning now,
|
||||
* because we may still have data left over to send */
|
||||
DBG(100, "sane_read: didn't send it all last time\n");
|
||||
DBG(200, "sane_read: didn't send it all last time\n");
|
||||
|
||||
/* Now feed it some data from lbuf */
|
||||
if (bytesleft <= (unsigned int)maxlen)
|
||||
|
@ -1248,7 +1225,7 @@ sane_read (SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenp)
|
|||
(cs->params.id_string)+8);
|
||||
DBG(10, "scan_params->: width=%d, height=%d, xoffset=%d, "
|
||||
"yoffset=%d\n\txresolution=%d, yresolution=%d, "
|
||||
"mode=%d\n\n",
|
||||
"mode=%d\n",
|
||||
cs->scan.width, cs->scan.height,
|
||||
cs->scan.xoffset, cs->scan.yoffset,
|
||||
cs->scan.xresolution, cs->scan.yresolution,
|
||||
|
@ -1256,10 +1233,12 @@ sane_read (SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenp)
|
|||
);
|
||||
DBG(10, "lines=%d\n",lines);
|
||||
|
||||
DBG(2, ">> read_segment(%p, %p, %p, %d)\n",
|
||||
&is, &(cs->params), &(cs->scan), lines);
|
||||
DBG(2, ">> read_segment(%p, %p, %p, %d, %d, %d)\n",
|
||||
&is, &(cs->params), &(cs->scan), lines,
|
||||
cs->cal_valid, cs->scan.height - cs->lines_scanned);
|
||||
tmp = sanei_canon_pp_read_segment(&is, &(cs->params),
|
||||
&(cs->scan), lines, cs->cal_valid);
|
||||
&(cs->scan), lines, cs->cal_valid,
|
||||
cs->scan.height - cs->lines_scanned);
|
||||
DBG(2, "<< %d read_segment\n", tmp);
|
||||
|
||||
if (tmp != 0) {
|
||||
|
@ -1272,66 +1251,42 @@ sane_read (SANE_Handle h, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenp)
|
|||
cs->lines_scanned += lines;
|
||||
|
||||
/* translate data out of buffer */
|
||||
if (cs->vals[OPT_COLOUR_MODE] == 0)
|
||||
if (cs->vals[OPT_DEPTH] == 0)
|
||||
{
|
||||
/* fudge because simon's code is slack and returns greyscale as
|
||||
* RGB data, just with the same stuff in R,G,B. */
|
||||
DBG(10, "sane_read: Initialising FUDGE engine 1.0\n");
|
||||
if (cs->vals[OPT_DEPTH] == 0)
|
||||
/* 8bpp */
|
||||
for(i = 0; i < bytes; i++)
|
||||
{
|
||||
/* 8bpp */
|
||||
for(i = 0; i < bytes * 3; i += 3)
|
||||
charptr = lbuf + i;
|
||||
if (cs->vals[OPT_COLOUR_MODE])
|
||||
{
|
||||
*((char *)lbuf + i/3) =
|
||||
*((char *)(is->image_data) + (i*2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 16bpp */
|
||||
for(i = 0; i < (bytes/2) * 3; i += 3)
|
||||
{
|
||||
/* Convert bigendian data to the local system */
|
||||
*((short *)lbuf + i/3) = MAKE_SHORT(
|
||||
*((char *)(is->image_data)
|
||||
+ (i*2)),
|
||||
*((char *)(is->image_data)
|
||||
+ (i*2)+1)
|
||||
);
|
||||
if (i % 3 == 0) charptr += 2;
|
||||
if (i % 3 == 2) charptr -= 2;
|
||||
}
|
||||
*charptr = *((char *)(is->image_data) + (i*2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cs->vals[OPT_DEPTH] == 0)
|
||||
/* 16bpp */
|
||||
for(i = 0; i < (bytes/2); i++)
|
||||
{
|
||||
/* 8bpp */
|
||||
for(i = 0; i < bytes; i++)
|
||||
shortptr = ((short *)lbuf + i);
|
||||
if (cs->vals[OPT_COLOUR_MODE])
|
||||
{
|
||||
charptr = lbuf + i;
|
||||
if (i % 3 == 0) charptr += 2;
|
||||
if (i % 3 == 2) charptr -= 2;
|
||||
*(charptr) =
|
||||
*((char *)(is->image_data) + (i*2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 16bpp */
|
||||
for(i = 0; i < (bytes/2); i++)
|
||||
{
|
||||
shortptr = ((short *)lbuf + i);
|
||||
if (i % 3 == 0) shortptr += 2;
|
||||
if (i % 3 == 2) shortptr -= 2;
|
||||
*shortptr = MAKE_SHORT(
|
||||
*((char *)(is->image_data) +
|
||||
(i*2)),
|
||||
*((char *)(is->image_data) +
|
||||
(i*2)+1));
|
||||
}
|
||||
*shortptr = MAKE_SHORT(
|
||||
*((char *)(is->image_data) + (i*2)),
|
||||
*((char *)(is->image_data) + (i*2)+1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free data structures allocated in read_segment */
|
||||
free(is->image_data);
|
||||
free(is);
|
||||
|
||||
/* Now feed it some data from lbuf */
|
||||
if (bytes <= (unsigned int)maxlen)
|
||||
{
|
||||
|
@ -1577,15 +1532,15 @@ static SANE_Status init_device(struct parport *pp)
|
|||
|
||||
DBG(2, ">> init_device\n");
|
||||
|
||||
cs = malloc (sizeof (CANONP_Scanner));
|
||||
cs = malloc(sizeof(*cs));
|
||||
if (cs == NULL)
|
||||
{
|
||||
return SANE_STATUS_NO_MEM;
|
||||
}
|
||||
memset (cs, 0, sizeof (CANONP_Scanner));
|
||||
memset(cs, 0, sizeof(*cs));
|
||||
|
||||
#if 0
|
||||
if ((cs->params.port = malloc(sizeof(*pp))) == NULL)
|
||||
if ((cs->params.port = malloc(sizeof(*(cs->params.port)))) == NULL)
|
||||
return SANE_STATUS_NO_MEM;
|
||||
|
||||
memcpy(cs->params.port, pp, sizeof(*pp));
|
||||
|
@ -1839,7 +1794,7 @@ static SANE_Status fix_weights_file(CANONP_Scanner *cs)
|
|||
cs->weights_file = tmp;
|
||||
}
|
||||
|
||||
if ((f_stat = malloc(sizeof(struct stat))) == NULL)
|
||||
if ((f_stat = malloc(sizeof(*f_stat))) == NULL)
|
||||
return SANE_STATUS_NO_MEM;
|
||||
|
||||
if(stat(cs->weights_file, f_stat))
|
||||
|
@ -1859,8 +1814,6 @@ static SANE_Status fix_weights_file(CANONP_Scanner *cs)
|
|||
}
|
||||
else
|
||||
{
|
||||
free(f_stat);
|
||||
f_stat = NULL;
|
||||
|
||||
/* No error returned.. Check read/writability */
|
||||
i = open(cs->weights_file, O_RDWR | O_APPEND);
|
||||
|
@ -1896,6 +1849,9 @@ static SANE_Status fix_weights_file(CANONP_Scanner *cs)
|
|||
}
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
free(f_stat);
|
||||
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,9 +42,9 @@ This backend expects device names of the form presented by libieee1284. These n
|
|||
|
||||
On Linux 2.4 kernels this will be of the form
|
||||
.I "parport0"
|
||||
or older (2.2) kernels may produce names like
|
||||
or older (2.2 and before) kernels may produce names like
|
||||
.IR "0x378"
|
||||
or simply
|
||||
(the base address of your port) or simply
|
||||
.IR "0"
|
||||
depending on your module configuration. Check the contents of
|
||||
.I /proc/parport
|
||||
|
@ -144,10 +144,10 @@ anti-aliasing filter. Again, it seems to be implemeneted entirely in
|
|||
software, so GIMP is your best bet for now.
|
||||
.TP
|
||||
.B Gamma Tables
|
||||
This is under investigation, but for now only a simple gamma profile will be
|
||||
loaded.
|
||||
This is under investigation, but for now only a simple gamma profile (ie: the
|
||||
one returned during calibration) will be loaded.
|
||||
.PP
|
||||
.B Other Notes
|
||||
.B Communication Problems
|
||||
.PP
|
||||
ECP mode in libieee1284 doesn't always work properly, even with new hardware.
|
||||
We beleive that this is a ppdev problem. If you change the configuration file
|
||||
|
@ -175,6 +175,14 @@ movement for a start/stop event is greater than 1/600 inches. I've never
|
|||
tried the windows driver so I'm not sure how (or if) it works around
|
||||
this problem, but as we don't know how to rewind the scanner head to do these
|
||||
bits again, there's currently no nice way to deal with the problem.
|
||||
.PP
|
||||
.B Greyscale Scans
|
||||
.PP
|
||||
Be aware that the scanner uses the green LEDs to read greyscale scans,
|
||||
meaning green coloured things will appear lighter than normal, and red
|
||||
and blue coloured items will appear darker than normal. For high-accuracy
|
||||
greyscale scans of colour items, it's best just to scan in colour and convert
|
||||
to greyscale in graphics software such as the GIMP.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
sane(7), sane-dll(5)
|
||||
|
|
Ładowanie…
Reference in New Issue