23#include <winpr/assert.h> 
   25#include <freerdp/config.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") 
   48#include <sys/sysctl.h> 
   50static 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;
 
  106static void printer_cups_get_printjob_name(
char* buf, 
size_t size, 
size_t id)
 
  108  struct tm tres = { 0 };
 
  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,
 
  120static bool http_status_ok(http_status_t status)
 
  132static 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;
 
  150static UINT printer_cups_write_printjob(rdpPrintJob* printjob, 
const BYTE* data, 
size_t size)
 
  152  return write_printjob(printjob, data, size);
 
  155static 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;
 
  176static 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;
 
  238static 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;
 
  252static 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);
 
  274static void printer_cups_add_ref_printer(rdpPrinter* printer)
 
  277    printer->references++;
 
  280static void printer_cups_release_ref_printer(rdpPrinter* printer)
 
  284  if (printer->references <= 1)
 
  285    printer_cups_free_printer(printer);
 
  287    printer->references--;
 
  290static 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);
 
  340static 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);
 
  350  free((
void*)printers);
 
  353static 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;
 
  398static 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);
 
  407static void printer_cups_add_ref_driver(rdpPrinterDriver* driver)
 
  409  rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver;
 
  411    cups_driver->references++;
 
  415static rdpCupsPrinterDriver* uniq_cups_driver = NULL;
 
  417static 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--;
 
  433FREERDP_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;