23#include <winpr/assert.h>
25#include <freerdp/config.h>
26#include <freerdp/utils/helpers.h>
37#include <winpr/file.h>
38#include <winpr/string.h>
40#include <freerdp/channels/rdpdr.h>
42#include <freerdp/client/printer.h>
44#include <freerdp/channels/log.h>
45#define TAG CHANNELS_TAG("printer.client.cups")
49#include <sys/sysctl.h>
51static bool is_mac_os_sonoma_or_later(
void)
53 char str[256] = { 0 };
54 size_t size =
sizeof(str);
57 int ret = sysctlbyname(
"kern.osrelease", str, &size, NULL, 0);
60 char buffer[256] = { 0 };
61 WLog_WARN(TAG,
"sysctlbyname('kern.osrelease') failed with %s [%d]",
62 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
71 const int rc = sscanf(str,
"%d.%d.%d", &major, &minor, &patch);
74 WLog_WARN(TAG,
"could not match '%s' to format '%d.%d.%d'", str, major, minor, patch);
86 rdpPrinterDriver driver;
90} rdpCupsPrinterDriver;
96 http_t* printjob_object;
104 rdpCupsPrintJob* printjob;
107WINPR_ATTR_MALLOC(free, 1)
108static
char* printer_cups_get_printjob_name(
size_t id)
110 struct tm tres = { 0 };
111 const time_t tt = time(NULL);
112 const struct tm* t = localtime_r(&tt, &tres);
117 winpr_asprintf(&str, &len,
"%s Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIuz,
118 freerdp_getApplicationDetailsString(), t->tm_year + 1900, t->tm_mon + 1,
119 t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
id);
129static bool http_status_ok(http_status_t status)
141static UINT write_printjob(rdpPrintJob* printjob,
const BYTE* data,
size_t size)
143 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
145 WINPR_ASSERT(cups_printjob);
148 cupsWriteRequestData(cups_printjob->printjob_object, (
const char*)data, size);
149 if (!http_status_ok(rc))
150 WLog_WARN(TAG,
"cupsWriteRequestData returned %s", httpStatus(rc));
152 return CHANNEL_RC_OK;
159static UINT printer_cups_write_printjob(rdpPrintJob* printjob,
const BYTE* data,
size_t size)
161 return write_printjob(printjob, data, size);
164static void printer_cups_close_printjob(rdpPrintJob* printjob)
166 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
167 rdpCupsPrinter* cups_printer = NULL;
169 WINPR_ASSERT(cups_printjob);
171 ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name);
173 WLog_WARN(TAG,
"cupsFinishDocument returned %s", ippErrorString(rc));
175 cups_printjob->printjob_id = 0;
176 httpClose(cups_printjob->printjob_object);
178 cups_printer = (rdpCupsPrinter*)printjob->printer;
179 WINPR_ASSERT(cups_printer);
181 cups_printer->printjob = NULL;
185static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32
id)
187 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
188 rdpCupsPrintJob* cups_printjob = NULL;
190 WINPR_ASSERT(cups_printer);
192 if (cups_printer->printjob != NULL)
194 WLog_WARN(TAG,
"printjob [printer '%s'] already existing, abort!", printer->name);
198 cups_printjob = (rdpCupsPrintJob*)calloc(1,
sizeof(rdpCupsPrintJob));
202 cups_printjob->printjob.id = id;
203 cups_printjob->printjob.printer = printer;
205 cups_printjob->printjob.Write = printer_cups_write_printjob;
206 cups_printjob->printjob.Close = printer_cups_close_printjob;
209 cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
210 HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL);
212 if (!cups_printjob->printjob_object)
214 WLog_WARN(TAG,
"httpConnect2 failed for '%s:%d", cupsServer(), ippPort());
219 char* jobTitle = printer_cups_get_printjob_name(cups_printjob->printjob.id);
222 httpClose(cups_printjob->printjob_object);
227 cups_printjob->printjob_id =
228 cupsCreateJob(cups_printjob->printjob_object, printer->name, jobTitle, 0, NULL);
230 if (!cups_printjob->printjob_id)
232 WLog_WARN(TAG,
"cupsCreateJob failed for printer '%s', driver '%s'", printer->name,
234 httpClose(cups_printjob->printjob_object);
241 cupsStartDocument(cups_printjob->printjob_object, printer->name,
242 cups_printjob->printjob_id, jobTitle, CUPS_FORMAT_AUTO, 1);
244 if (!http_status_ok(rc))
245 WLog_WARN(TAG,
"cupsStartDocument [printer '%s', driver '%s'] returned %s",
246 printer->name, printer->driver, httpStatus(rc));
249 cups_printer->printjob = cups_printjob;
251 return &cups_printjob->printjob;
254static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32
id)
256 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
258 WINPR_ASSERT(cups_printer);
260 if (cups_printer->printjob == NULL)
262 if (cups_printer->printjob->printjob.id !=
id)
265 return &cups_printer->printjob->printjob;
268static void printer_cups_free_printer(rdpPrinter* printer)
270 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
272 WINPR_ASSERT(cups_printer);
274 if (cups_printer->printjob)
276 WINPR_ASSERT(cups_printer->printjob->printjob.Close);
277 cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob);
280 if (printer->backend)
282 WINPR_ASSERT(printer->backend->ReleaseRef);
283 printer->backend->ReleaseRef(printer->backend);
286 free(printer->driver);
290static void printer_cups_add_ref_printer(rdpPrinter* printer)
293 printer->references++;
296static void printer_cups_release_ref_printer(rdpPrinter* printer)
300 if (printer->references <= 1)
301 printer_cups_free_printer(printer);
303 printer->references--;
306static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver,
const char* name,
307 const char* driverName, BOOL is_default)
309 rdpCupsPrinter* cups_printer = NULL;
311 cups_printer = (rdpCupsPrinter*)calloc(1,
sizeof(rdpCupsPrinter));
315 cups_printer->printer.backend = &cups_driver->driver;
317 cups_printer->printer.id = cups_driver->id_sequence++;
318 cups_printer->printer.name = _strdup(name);
319 if (!cups_printer->printer.name)
323 cups_printer->printer.driver = _strdup(driverName);
326 const char* dname =
"MS Publisher Imagesetter";
327#if defined(__APPLE__)
328 if (is_mac_os_sonoma_or_later())
329 dname =
"Microsoft Print to PDF";
331 cups_printer->printer.driver = _strdup(dname);
333 if (!cups_printer->printer.driver)
336 cups_printer->printer.is_default = is_default;
338 cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
339 cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
340 cups_printer->printer.AddRef = printer_cups_add_ref_printer;
341 cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
343 WINPR_ASSERT(cups_printer->printer.AddRef);
344 cups_printer->printer.AddRef(&cups_printer->printer);
346 WINPR_ASSERT(cups_printer->printer.backend->AddRef);
347 cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
349 return &cups_printer->printer;
352 printer_cups_free_printer(&cups_printer->printer);
356static void printer_cups_release_enum_printers(rdpPrinter** printers)
358 rdpPrinter** cur = printers;
360 while ((cur != NULL) && ((*cur) != NULL))
362 if ((*cur)->ReleaseRef)
363 (*cur)->ReleaseRef(*cur);
366 free((
void*)printers);
369static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
371 rdpPrinter** printers = NULL;
372 int num_printers = 0;
373 cups_dest_t* dests = NULL;
374 BOOL haveDefault = FALSE;
375 const int num_dests = cupsGetDests(&dests);
377 WINPR_ASSERT(driver);
379 printers = (rdpPrinter**)calloc((
size_t)num_dests + 1,
sizeof(rdpPrinter*));
383 for (
size_t i = 0; i < (size_t)num_dests; i++)
385 const cups_dest_t* dest = &dests[i];
386 if (dest->instance == NULL)
388 rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
389 dest->name, NULL, dest->is_default);
392 printer_cups_release_enum_printers(printers);
397 if (current->is_default)
400 printers[num_printers++] = current;
403 cupsFreeDests(num_dests, dests);
405 if (!haveDefault && (num_dests > 0) && printers)
408 printers[0]->is_default = TRUE;
414static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver,
const char* name,
415 const char* driverName, BOOL isDefault)
417 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
419 WINPR_ASSERT(cups_driver);
420 return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
423static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
425 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
427 cups_driver->references++;
431static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
433static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
435 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
437 WINPR_ASSERT(cups_driver);
439 if (cups_driver->references <= 1)
441 if (uniq_cups_driver == cups_driver)
442 uniq_cups_driver = NULL;
446 cups_driver->references--;
449FREERDP_ENTRY_POINT(UINT VCAPITYPE cups_freerdp_printer_client_subsystem_entry(
void* arg))
451 rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
453 return ERROR_INVALID_PARAMETER;
455 if (!uniq_cups_driver)
457 uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1,
sizeof(rdpCupsPrinterDriver));
459 if (!uniq_cups_driver)
460 return ERROR_OUTOFMEMORY;
462 uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
463 uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
464 uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
466 uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
467 uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
469 uniq_cups_driver->id_sequence = 1;
472 WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
473 uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
475 *ppPrinter = &uniq_cups_driver->driver;
476 return CHANNEL_RC_OK;