From: Piotr Caban piotr@codeweavers.com
--- dlls/localspl/Makefile.in | 2 + dlls/localspl/cups.c | 288 ++++++++++++++++++++++++++++++- dlls/localspl/localmon.c | 24 ++- dlls/localspl/localspl_private.h | 2 + dlls/localspl/provider.c | 3 +- 5 files changed, 310 insertions(+), 9 deletions(-)
diff --git a/dlls/localspl/Makefile.in b/dlls/localspl/Makefile.in index 61ca097d9e4..837467211ef 100644 --- a/dlls/localspl/Makefile.in +++ b/dlls/localspl/Makefile.in @@ -1,6 +1,8 @@ MODULE = localspl.dll UNIXLIB = localspl.so IMPORTS = spoolss user32 advapi32 +UNIX_CFLAGS = $(CUPS_CFLAGS) +UNIX_LIBS = $(APPLICATIONSERVICES_LIBS)
EXTRADLLFLAGS = -Wb,--prefer-native
diff --git a/dlls/localspl/cups.c b/dlls/localspl/cups.c index 47362c1f138..bc0f9c1d317 100644 --- a/dlls/localspl/cups.c +++ b/dlls/localspl/cups.c @@ -27,10 +27,14 @@ #include <stdarg.h> #include <stdlib.h> #include <unistd.h> +#include <dlfcn.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> +#ifdef HAVE_CUPS_CUPS_H +#include <cups/cups.h> +#endif
#include "ntstatus.h" #define WIN32_NO_STATUS @@ -40,6 +44,32 @@
WINE_DEFAULT_DEBUG_CHANNEL(localspl);
+#ifdef SONAME_LIBCUPS + +static void *libcups_handle; + +#define CUPS_FUNCS \ + DO_FUNC(cupsAddOption); \ + DO_FUNC(cupsCreateJob); \ + DO_FUNC(cupsFinishDocument); \ + DO_FUNC(cupsFreeDests); \ + DO_FUNC(cupsFreeOptions); \ + DO_FUNC(cupsGetOption); \ + DO_FUNC(cupsParseOptions); \ + DO_FUNC(cupsStartDocument); \ + DO_FUNC(cupsWriteRequestData) +#define CUPS_OPT_FUNCS \ + DO_FUNC(cupsGetNamedDest); \ + DO_FUNC(cupsLastErrorString) + +#define DO_FUNC(f) static typeof(f) *p##f +CUPS_FUNCS; +#undef DO_FUNC +static cups_dest_t * (*pcupsGetNamedDest)(http_t *, const char *, const char *); +static const char * (*pcupsLastErrorString)(void); + +#endif /* SONAME_LIBCUPS */ + typedef struct _doc_t { BOOL (*write_doc)(struct _doc_t *, const BYTE *buf, unsigned int size); @@ -56,6 +86,23 @@ typedef struct _doc_t { int fd; } unixname; + struct + { + char *queue; + char *doc_title; + enum + { + doc_parse_header = 0, + doc_parse_options, + doc_create_job, + doc_initialized, + } state; + BOOL restore_ps_header; + int num_options; + cups_option_t *options; + int buf_len; + char buf[257]; /* DSC max of 256 + '\0' */ + } cups; }; } doc_t;
@@ -178,10 +225,242 @@ static BOOL lpr_start_doc(doc_t *doc, const WCHAR *printer_name) return pipe_start_doc(doc, cmd); }
+#ifdef SONAME_LIBCUPS +static int get_cups_default_options(const char *printer, int num_options, cups_option_t **options) +{ + cups_dest_t *dest; + int i; + + if (!pcupsGetNamedDest) return num_options; + + dest = pcupsGetNamedDest(NULL, printer, NULL); + if (!dest) return num_options; + + for (i = 0; i < dest->num_options; i++) + { + if (!pcupsGetOption(dest->options[i].name, num_options, *options)) + num_options = pcupsAddOption(dest->options[i].name, + dest->options[i].value, num_options, options); + } + + pcupsFreeDests(1, dest); + return num_options; +} + +static BOOL cups_gets(doc_t *doc, const BYTE **buf, unsigned int *size) +{ + BYTE b; + + while(doc->cups.buf_len < sizeof(doc->cups.buf) && *size) + { + b = (*buf)[0]; + doc->cups.buf[doc->cups.buf_len++] = b; + (*buf)++; + (*size)--; + + if (b == '\n') + return TRUE; + } + return FALSE; +} + +static BOOL cups_write(const char *buf, unsigned int size) +{ + if (!size) + return TRUE; + + if (pcupsWriteRequestData(CUPS_HTTP_DEFAULT, buf, size) != HTTP_STATUS_CONTINUE) + { + if (pcupsLastErrorString) + WARN("cupsWriteRequestData failed: %s\n", debugstr_a(pcupsLastErrorString())); + return FALSE; + } + return TRUE; +} + +static BOOL cups_write_doc(doc_t *doc, const BYTE *buf, unsigned int size) +{ + const char ps_adobe[] = "%!PS-Adobe-3.0\n"; + const char cups_job[] = "%cupsJobTicket:"; + + if (doc->cups.state == doc_parse_header) + { + if (!cups_gets(doc, &buf, &size)) + { + if (doc->cups.buf_len != sizeof(doc->cups.buf)) + return TRUE; + doc->cups.state = doc_create_job; + } + else if (!strncmp(doc->cups.buf, ps_adobe, sizeof(ps_adobe) - 1)) + { + doc->cups.restore_ps_header = TRUE; + doc->cups.state = doc_parse_options; + doc->cups.buf_len = 0; + } + else + { + doc->cups.state = doc_create_job; + } + } + + /* Explicitly set CUPS options based on any %cupsJobTicket lines. + * The CUPS scheduler only looks for these in Print-File requests, and since + * we use Create-Job / Send-Document, the ticket lines don't get parsed. + */ + if (doc->cups.state == doc_parse_options) + { + while (1) + { + if (!cups_gets(doc, &buf, &size)) + { + if (doc->cups.buf_len != sizeof(doc->cups.buf)) + return TRUE; + doc->cups.state = doc_create_job; + break; + } + else if (!strncmp(doc->cups.buf, cups_job, sizeof(cups_job) - 1)) + { + doc->cups.buf[doc->cups.buf_len - 1] = 0; + doc->cups.num_options = pcupsParseOptions(doc->cups.buf + sizeof(cups_job) - 1, + doc->cups.num_options, &doc->cups.options); + doc->cups.buf_len = 0; + } + else + { + doc->cups.state = doc_create_job; + break; + } + } + } + + if (doc->cups.state == doc_create_job) + { + const char *format; + int i, job_id; + + doc->cups.num_options = get_cups_default_options(doc->cups.queue, + doc->cups.num_options, &doc->cups.options); + + TRACE("printing via cups with options:\n"); + for (i = 0; i < doc->cups.num_options; i++) + TRACE("\t%d: %s = %s\n", i, doc->cups.options[i].name, doc->cups.options[i].value); + + if (pcupsGetOption("raw", doc->cups.num_options, doc->cups.options)) + format = CUPS_FORMAT_RAW; + else if (!(format = pcupsGetOption("document-format", doc->cups.num_options, doc->cups.options))) + format = CUPS_FORMAT_AUTO; + + job_id = pcupsCreateJob(CUPS_HTTP_DEFAULT, doc->cups.queue, + doc->cups.doc_title, doc->cups.num_options, doc->cups.options); + if (!job_id) + { + if (pcupsLastErrorString) + WARN("cupsCreateJob failed: %s\n", debugstr_a(pcupsLastErrorString())); + return FALSE; + } + + if (pcupsStartDocument(CUPS_HTTP_DEFAULT, doc->cups.queue, job_id, + doc->cups.doc_title, format, TRUE) != HTTP_STATUS_CONTINUE) + { + if (pcupsLastErrorString) + WARN("cupsStartDocument failed: %s\n", debugstr_a(pcupsLastErrorString())); + return FALSE; + } + + doc->cups.state = doc_initialized; + } + + if (doc->cups.restore_ps_header) + { + if (!cups_write(ps_adobe, sizeof(ps_adobe) - 1)) + return FALSE; + doc->cups.restore_ps_header = FALSE; + } + + if (doc->cups.buf_len) + { + if (!cups_write(doc->cups.buf, doc->cups.buf_len)) + return FALSE; + doc->cups.buf_len = 0; + } + + return cups_write((const char *)buf, size); +} + +static BOOL cups_end_doc(doc_t *doc) +{ + if (doc->cups.buf_len) + { + if (doc->cups.state != doc_initialized) + doc->cups.state = doc_create_job; + cups_write_doc(doc, NULL, 0); + } + + if (doc->cups.state == doc_initialized) + pcupsFinishDocument(CUPS_HTTP_DEFAULT, doc->cups.queue); + + free(doc->cups.queue); + free(doc->cups.doc_title); + pcupsFreeOptions(doc->cups.num_options, doc->cups.options); + return TRUE; +} +#endif + +static BOOL cups_start_doc(doc_t *doc, const WCHAR *printer_name, const WCHAR *document_title) +{ +#ifdef SONAME_LIBCUPS + if (pcupsWriteRequestData) + { + int len; + + doc->write_doc = cups_write_doc; + doc->end_doc = cups_end_doc; + + len = wcslen(printer_name); + doc->cups.queue = malloc(len * 3 + 1); + ntdll_wcstoumbs(printer_name, len + 1, doc->cups.queue, len * 3 + 1, FALSE); + + len = wcslen(document_title); + doc->cups.doc_title = malloc(len * 3 + 1); + ntdll_wcstoumbs(document_title, len + 1, doc->cups.doc_title, len + 3 + 1, FALSE); + + return TRUE; + } +#endif + + return lpr_start_doc(doc, printer_name); +} + +static NTSTATUS process_attach(void *args) +{ +#ifdef SONAME_LIBCUPS + libcups_handle = dlopen(SONAME_LIBCUPS, RTLD_NOW); + TRACE("%p: %s loaded\n", libcups_handle, SONAME_LIBCUPS); + if (!libcups_handle) return STATUS_DLL_NOT_FOUND; + +#define DO_FUNC(x) \ + p##x = dlsym(libcups_handle, #x); \ + if (!p##x) \ + { \ + ERR("failed to load symbol %s\n", #x); \ + libcups_handle = NULL; \ + return STATUS_ENTRYPOINT_NOT_FOUND; \ + } + CUPS_FUNCS; +#undef DO_FUNC +#define DO_FUNC(x) p##x = dlsym(libcups_handle, #x) + CUPS_OPT_FUNCS; +#undef DO_FUNC + return STATUS_SUCCESS; +#else /* SONAME_LIBCUPS */ + return STATUS_NOT_SUPPORTED; +#endif /* SONAME_LIBCUPS */ +} + static NTSTATUS start_doc(void *args) { const struct start_doc_params *params = args; - doc_t *doc = malloc(sizeof(*doc)); + doc_t *doc = calloc(1, sizeof(*doc)); BOOL ret = FALSE;
if (!doc) return STATUS_NO_MEMORY; @@ -192,6 +471,9 @@ static NTSTATUS start_doc(void *args) ret = unixname_start_doc(doc, params->port); else if (params->type == PORT_IS_LPR) ret = lpr_start_doc(doc, params->port + 4 /* strlen("lpr:") */); + else if (params->type == PORT_IS_CUPS) + ret = cups_start_doc(doc, params->port + 5 /*strlen("cups:") */, + params->document_title);
if (ret) *params->doc = (size_t)doc; @@ -221,6 +503,7 @@ static NTSTATUS end_doc(void *args)
const unixlib_entry_t __wine_unix_call_funcs[] = { + process_attach, start_doc, write_doc, end_doc, @@ -236,6 +519,7 @@ static NTSTATUS wow64_start_doc(void *args) { unsigned int type; const PTR32 port; + const PTR32 document_title; INT64 *doc; } const *params32 = args;
@@ -243,6 +527,7 @@ static NTSTATUS wow64_start_doc(void *args) { params32->type, ULongToPtr(params32->port), + ULongToPtr(params32->document_title), params32->doc, };
@@ -270,6 +555,7 @@ static NTSTATUS wow64_write_doc(void *args)
const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { + process_attach, wow64_start_doc, wow64_write_doc, end_doc, diff --git a/dlls/localspl/localmon.c b/dlls/localspl/localmon.c index 8cef23787e7..ce0b7c9caea 100644 --- a/dlls/localspl/localmon.c +++ b/dlls/localspl/localmon.c @@ -96,7 +96,10 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls( hinstDLL ); localspl_instance = hinstDLL; - return !__wine_init_unix_call(); + if (__wine_init_unix_call()) + return FALSE; + UNIX_CALL(process_attach, NULL); + break; } return TRUE; } @@ -506,8 +509,7 @@ static BOOL WINAPI localmon_StartDocPort(HANDLE hport, WCHAR *printer_name, TRACE("(%p %s %ld %ld %p)\n", hport, debugstr_w(printer_name), job_id, level, doc_info);
- if (port->type == PORT_IS_PIPE || port->type == PORT_IS_UNIXNAME || - port->type == PORT_IS_LPR) + if (port->type >= PORT_IS_WINE) { struct start_doc_params params;
@@ -516,6 +518,7 @@ static BOOL WINAPI localmon_StartDocPort(HANDLE hport, WCHAR *printer_name,
params.type = port->type; params.port = port->nameW; + params.document_title = doc_info ? doc_info->pDocName : NULL; params.doc = &port->doc_handle; return UNIX_CALL(start_doc, ¶ms); } @@ -547,12 +550,17 @@ static BOOL WINAPI localmon_WritePort(HANDLE hport, BYTE *buf, DWORD size,
TRACE("(%p %p %lu %p)\n", hport, buf, size, written);
- if (port->type == PORT_IS_PIPE || port->type == PORT_IS_UNIXNAME || - port->type == PORT_IS_LPR) + if (port->type >= PORT_IS_WINE) { struct write_doc_params params; BOOL ret;
+ if (!port->doc_handle) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + params.doc = port->doc_handle; params.buf = buf; params.size = size; @@ -570,11 +578,13 @@ static BOOL WINAPI localmon_EndDocPort(HANDLE hport)
TRACE("(%p)\n", hport);
- if (port->type == PORT_IS_PIPE || port->type == PORT_IS_UNIXNAME || - port->type == PORT_IS_LPR) + if (port->type >= PORT_IS_WINE) { struct end_doc_params params;
+ if (!port->doc_handle) + return TRUE; + params.doc = port->doc_handle; port->doc_handle = 0; return UNIX_CALL(end_doc, ¶ms); diff --git a/dlls/localspl/localspl_private.h b/dlls/localspl/localspl_private.h index b76071f049a..89abc46ce14 100644 --- a/dlls/localspl/localspl_private.h +++ b/dlls/localspl/localspl_private.h @@ -171,6 +171,7 @@ struct start_doc_params { unsigned int type; const WCHAR *port; + const WCHAR *document_title; INT64 *doc; };
@@ -190,6 +191,7 @@ struct end_doc_params
enum cups_funcs { + unix_process_attach, unix_start_doc, unix_write_doc, unix_end_doc, diff --git a/dlls/localspl/provider.c b/dlls/localspl/provider.c index 7b5a94b52f6..fd7827e1f28 100644 --- a/dlls/localspl/provider.c +++ b/dlls/localspl/provider.c @@ -988,7 +988,8 @@ static monitor_t * monitor_load_by_port(LPCWSTR portname) TRACE("(%s)\n", debugstr_w(portname));
/* wine specific ports */ - if (portname[0] == '|' || portname[0] == '/') + if (portname[0] == '|' || portname[0] == '/' || + !wcsncmp(portname, L"LPR:", 4) || !wcsncmp(portname, L"CUPS:", 5)) return monitor_load(L"Local Port", NULL);
/* Try the Local Monitor first */