23 #include <winpr/assert.h>
25 #include <freerdp/config.h>
33 #include <cups/cups.h>
35 #include <winpr/crt.h>
36 #include <winpr/file.h>
37 #include <winpr/string.h>
39 #include <freerdp/channels/rdpdr.h>
41 #include <freerdp/client/printer.h>
43 #include <freerdp/channels/log.h>
44 #define TAG CHANNELS_TAG("printer.client.cups")
46 #if defined(__APPLE__)
48 #include <sys/sysctl.h>
50 static bool is_mac_os_sonoma_or_later(
void)
52 char str[256] = { 0 };
53 size_t size =
sizeof(str);
56 int ret = sysctlbyname(
"kern.osrelease", str, &size, NULL, 0);
59 char buffer[256] = { 0 };
60 WLog_WARN(TAG,
"sysctlbyname('kern.osrelease') failed with %s [%d]",
61 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
70 const int rc = sscanf(str,
"%d.%d.%d", &major, &minor, &patch);
73 WLog_WARN(TAG,
"could not match '%s' to format '%d.%d.%d'");
85 rdpPrinterDriver driver;
89 } rdpCupsPrinterDriver;
95 http_t* printjob_object;
103 rdpCupsPrintJob* printjob;
106 static void printer_cups_get_printjob_name(
char* buf,
size_t size,
size_t id)
109 const time_t tt = time(NULL);
110 const struct tm* t = localtime_r(&tt, &tres);
113 WINPR_ASSERT(size > 0);
115 (void)sprintf_s(buf, size - 1,
"FreeRDP Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIuz,
116 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
120 static bool http_status_ok(http_status_t status)
132 static UINT write_printjob(rdpPrintJob* printjob,
const BYTE* data,
size_t size)
134 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
136 WINPR_ASSERT(cups_printjob);
139 cupsWriteRequestData(cups_printjob->printjob_object, (
const char*)data, size);
140 if (!http_status_ok(rc))
141 WLog_WARN(TAG,
"cupsWriteRequestData returned %s", httpStatus(rc));
143 return CHANNEL_RC_OK;
150 static UINT printer_cups_write_printjob(rdpPrintJob* printjob,
const BYTE* data,
size_t size)
152 return write_printjob(printjob, data, size);
155 static void printer_cups_close_printjob(rdpPrintJob* printjob)
157 rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob;
158 rdpCupsPrinter* cups_printer = NULL;
160 WINPR_ASSERT(cups_printjob);
162 ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name);
164 WLog_WARN(TAG,
"cupsFinishDocument returned %s", ippErrorString(rc));
166 cups_printjob->printjob_id = 0;
167 httpClose(cups_printjob->printjob_object);
169 cups_printer = (rdpCupsPrinter*)printjob->printer;
170 WINPR_ASSERT(cups_printer);
172 cups_printer->printjob = NULL;
176 static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32
id)
178 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
179 rdpCupsPrintJob* cups_printjob = NULL;
181 WINPR_ASSERT(cups_printer);
183 if (cups_printer->printjob != NULL)
185 WLog_WARN(TAG,
"printjob [printer '%s'] already existing, abort!", printer->name);
189 cups_printjob = (rdpCupsPrintJob*)calloc(1,
sizeof(rdpCupsPrintJob));
193 cups_printjob->printjob.id = id;
194 cups_printjob->printjob.printer = printer;
196 cups_printjob->printjob.Write = printer_cups_write_printjob;
197 cups_printjob->printjob.Close = printer_cups_close_printjob;
200 char buf[100] = { 0 };
202 cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
203 HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL);
205 if (!cups_printjob->printjob_object)
207 WLog_WARN(TAG,
"httpConnect2 failed for '%s:%d", cupsServer(), ippPort());
212 printer_cups_get_printjob_name(buf,
sizeof(buf), cups_printjob->printjob.id);
214 cups_printjob->printjob_id =
215 cupsCreateJob(cups_printjob->printjob_object, printer->name, buf, 0, NULL);
217 if (!cups_printjob->printjob_id)
219 WLog_WARN(TAG,
"cupsCreateJob failed for printer '%s', driver '%s'", printer->name,
221 httpClose(cups_printjob->printjob_object);
226 http_status_t rc = cupsStartDocument(cups_printjob->printjob_object, printer->name,
227 cups_printjob->printjob_id, buf, CUPS_FORMAT_AUTO, 1);
228 if (!http_status_ok(rc))
229 WLog_WARN(TAG,
"cupsStartDocument [printer '%s', driver '%s'] returned %s",
230 printer->name, printer->driver, httpStatus(rc));
233 cups_printer->printjob = cups_printjob;
235 return &cups_printjob->printjob;
238 static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32
id)
240 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
242 WINPR_ASSERT(cups_printer);
244 if (cups_printer->printjob == NULL)
246 if (cups_printer->printjob->printjob.id !=
id)
249 return &cups_printer->printjob->printjob;
252 static void printer_cups_free_printer(rdpPrinter* printer)
254 rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer;
256 WINPR_ASSERT(cups_printer);
258 if (cups_printer->printjob)
260 WINPR_ASSERT(cups_printer->printjob->printjob.Close);
261 cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob);
264 if (printer->backend)
266 WINPR_ASSERT(printer->backend->ReleaseRef);
267 printer->backend->ReleaseRef(printer->backend);
270 free(printer->driver);
274 static void printer_cups_add_ref_printer(rdpPrinter* printer)
277 printer->references++;
280 static void printer_cups_release_ref_printer(rdpPrinter* printer)
284 if (printer->references <= 1)
285 printer_cups_free_printer(printer);
287 printer->references--;
290 static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver,
const char* name,
291 const char* driverName, BOOL is_default)
293 rdpCupsPrinter* cups_printer = NULL;
295 cups_printer = (rdpCupsPrinter*)calloc(1,
sizeof(rdpCupsPrinter));
299 cups_printer->printer.backend = &cups_driver->driver;
301 cups_printer->printer.id = cups_driver->id_sequence++;
302 cups_printer->printer.name = _strdup(name);
303 if (!cups_printer->printer.name)
307 cups_printer->printer.driver = _strdup(driverName);
310 const char* dname =
"MS Publisher Imagesetter";
311 #if defined(__APPLE__)
312 if (is_mac_os_sonoma_or_later())
313 dname =
"Microsoft Print to PDF";
315 cups_printer->printer.driver = _strdup(dname);
317 if (!cups_printer->printer.driver)
320 cups_printer->printer.is_default = is_default;
322 cups_printer->printer.CreatePrintJob = printer_cups_create_printjob;
323 cups_printer->printer.FindPrintJob = printer_cups_find_printjob;
324 cups_printer->printer.AddRef = printer_cups_add_ref_printer;
325 cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer;
327 WINPR_ASSERT(cups_printer->printer.AddRef);
328 cups_printer->printer.AddRef(&cups_printer->printer);
330 WINPR_ASSERT(cups_printer->printer.backend->AddRef);
331 cups_printer->printer.backend->AddRef(cups_printer->printer.backend);
333 return &cups_printer->printer;
336 printer_cups_free_printer(&cups_printer->printer);
340 static void printer_cups_release_enum_printers(rdpPrinter** printers)
342 rdpPrinter** cur = printers;
344 while ((cur != NULL) && ((*cur) != NULL))
346 if ((*cur)->ReleaseRef)
347 (*cur)->ReleaseRef(*cur);
353 static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver)
355 rdpPrinter** printers = NULL;
356 int num_printers = 0;
357 cups_dest_t* dests = NULL;
358 BOOL haveDefault = FALSE;
359 const int num_dests = cupsGetDests(&dests);
361 WINPR_ASSERT(driver);
363 printers = (rdpPrinter**)calloc((
size_t)num_dests + 1,
sizeof(rdpPrinter*));
367 for (
size_t i = 0; i < (size_t)num_dests; i++)
369 const cups_dest_t* dest = &dests[i];
370 if (dest->instance == NULL)
372 rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver,
373 dest->name, NULL, dest->is_default);
376 printer_cups_release_enum_printers(printers);
381 if (current->is_default)
384 printers[num_printers++] = current;
387 cupsFreeDests(num_dests, dests);
389 if (!haveDefault && (num_dests > 0) && printers)
392 printers[0]->is_default = TRUE;
398 static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver,
const char* name,
399 const char* driverName, BOOL isDefault)
401 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
403 WINPR_ASSERT(cups_driver);
404 return printer_cups_new_printer(cups_driver, name, driverName, isDefault);
407 static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
409 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
411 cups_driver->references++;
415 static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
417 static void printer_cups_release_ref_driver(rdpPrinterDriver* driver)
419 rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
421 WINPR_ASSERT(cups_driver);
423 if (cups_driver->references <= 1)
425 if (uniq_cups_driver == cups_driver)
426 uniq_cups_driver = NULL;
430 cups_driver->references--;
433 FREERDP_ENTRY_POINT(UINT VCAPITYPE cups_freerdp_printer_client_subsystem_entry(
void* arg))
435 rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg;
437 return ERROR_INVALID_PARAMETER;
439 if (!uniq_cups_driver)
441 uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1,
sizeof(rdpCupsPrinterDriver));
443 if (!uniq_cups_driver)
444 return ERROR_OUTOFMEMORY;
446 uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers;
447 uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers;
448 uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer;
450 uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver;
451 uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver;
453 uniq_cups_driver->id_sequence = 1;
456 WINPR_ASSERT(uniq_cups_driver->driver.AddRef);
457 uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver);
459 *ppPrinter = &uniq_cups_driver->driver;
460 return CHANNEL_RC_OK;