/** * FreeRDP: A Remote Desktop Protocol Implementation * Print Virtual Channel - WIN driver * * Copyright 2012 Gerald Richter * Copyright 2015 Thincast Technologies GmbH * Copyright 2015 DI (FH) Martin Haimberger * Copyright 2016 Armin Novak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #define WIDEN_INT(x) L##x #define WIDEN(x) WIDEN_INT(x) #define PRINTER_TAG CHANNELS_TAG("printer.client") #ifdef WITH_DEBUG_WINPR #define DEBUG_WINPR(...) WLog_DBG(PRINTER_TAG, __VA_ARGS__) #else #define DEBUG_WINPR(...) \ do \ { \ } while (0) #endif typedef struct { rdpPrinterDriver driver; size_t id_sequence; size_t references; } rdpWinPrinterDriver; typedef struct { rdpPrintJob printjob; DOC_INFO_1 di; DWORD handle; void* printjob_object; int printjob_id; } rdpWinPrintJob; typedef struct { rdpPrinter printer; HANDLE hPrinter; rdpWinPrintJob* printjob; } rdpWinPrinter; static WCHAR* printer_win_get_printjob_name(size_t id) { time_t tt; struct tm tres; errno_t err; WCHAR* str; size_t len = 1024; int rc; tt = time(NULL); err = localtime_s(&tres, &tt); str = calloc(len, sizeof(WCHAR)); if (!str) return NULL; rc = swprintf_s(str, len, WIDEN("FreeRDP Print %04d-%02d-%02d% 02d-%02d-%02d - Job %") WIDEN(PRIuz) WIDEN("\0"), tres.tm_year + 1900, tres.tm_mon + 1, tres.tm_mday, tres.tm_hour, tres.tm_min, tres.tm_sec, id); return str; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT printer_win_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) { rdpWinPrinter* printer; LPCVOID pBuf = data; DWORD cbBuf = size; DWORD pcWritten; if (!printjob || !data) return ERROR_BAD_ARGUMENTS; printer = (rdpWinPrinter*)printjob->printer; if (!printer) return ERROR_BAD_ARGUMENTS; if (!WritePrinter(printer->hPrinter, pBuf, cbBuf, &pcWritten)) return ERROR_INTERNAL_ERROR; return CHANNEL_RC_OK; } static void printer_win_close_printjob(rdpPrintJob* printjob) { rdpWinPrintJob* win_printjob = (rdpWinPrintJob*)printjob; rdpWinPrinter* win_printer; if (!printjob) return; win_printer = (rdpWinPrinter*)printjob->printer; if (!win_printer) return; if (!EndPagePrinter(win_printer->hPrinter)) { } if (!EndDocPrinter(win_printer->hPrinter)) { } win_printer->printjob = NULL; free(win_printjob->di.pDocName); free(win_printjob); } static rdpPrintJob* printer_win_create_printjob(rdpPrinter* printer, UINT32 id) { rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; rdpWinPrintJob* win_printjob; if (win_printer->printjob != NULL) return NULL; win_printjob = (rdpWinPrintJob*)calloc(1, sizeof(rdpWinPrintJob)); if (!win_printjob) return NULL; win_printjob->printjob.id = id; win_printjob->printjob.printer = printer; win_printjob->di.pDocName = printer_win_get_printjob_name(id); win_printjob->di.pDatatype = NULL; win_printjob->di.pOutputFile = NULL; win_printjob->handle = StartDocPrinter(win_printer->hPrinter, 1, (LPBYTE) & (win_printjob->di)); if (!win_printjob->handle) { free(win_printjob->di.pDocName); free(win_printjob); return NULL; } if (!StartPagePrinter(win_printer->hPrinter)) { free(win_printjob->di.pDocName); free(win_printjob); return NULL; } win_printjob->printjob.Write = printer_win_write_printjob; win_printjob->printjob.Close = printer_win_close_printjob; win_printer->printjob = win_printjob; return &win_printjob->printjob; } static rdpPrintJob* printer_win_find_printjob(rdpPrinter* printer, UINT32 id) { rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; if (!win_printer->printjob) return NULL; if (win_printer->printjob->printjob.id != id) return NULL; return (rdpPrintJob*)win_printer->printjob; } static void printer_win_free_printer(rdpPrinter* printer) { rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; if (win_printer->printjob) win_printer->printjob->printjob.Close((rdpPrintJob*)win_printer->printjob); if (win_printer->hPrinter) ClosePrinter(win_printer->hPrinter); if (printer->backend) printer->backend->ReleaseRef(printer->backend); free(printer->name); free(printer->driver); free(printer); } static void printer_win_add_ref_printer(rdpPrinter* printer) { if (printer) printer->references++; } static void printer_win_release_ref_printer(rdpPrinter* printer) { if (!printer) return; if (printer->references <= 1) printer_win_free_printer(printer); else printer->references--; } static rdpPrinter* printer_win_new_printer(rdpWinPrinterDriver* win_driver, const WCHAR* name, const WCHAR* drivername, BOOL is_default) { rdpWinPrinter* win_printer; DWORD needed = 0; PRINTER_INFO_2* prninfo = NULL; if (!name) return NULL; win_printer = (rdpWinPrinter*)calloc(1, sizeof(rdpWinPrinter)); if (!win_printer) return NULL; win_printer->printer.backend = &win_driver->driver; win_printer->printer.id = win_driver->id_sequence++; win_printer->printer.name = ConvertWCharToUtf8Alloc(name, NULL); if (!win_printer->printer.name) goto fail; if (!win_printer->printer.name) goto fail; win_printer->printer.is_default = is_default; win_printer->printer.CreatePrintJob = printer_win_create_printjob; win_printer->printer.FindPrintJob = printer_win_find_printjob; win_printer->printer.AddRef = printer_win_add_ref_printer; win_printer->printer.ReleaseRef = printer_win_release_ref_printer; if (!OpenPrinter(name, &(win_printer->hPrinter), NULL)) goto fail; /* How many memory should be allocated for printer data */ GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, 0, &needed); if (needed == 0) goto fail; prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed); if (!prninfo) goto fail; if (!GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, needed, &needed)) { GlobalFree(prninfo); goto fail; } if (drivername) win_printer->printer.driver = ConvertWCharToUtf8Alloc(drivername, NULL); else win_printer->printer.driver = ConvertWCharToUtf8Alloc(prninfo->pDriverName, NULL); GlobalFree(prninfo); if (!win_printer->printer.driver) goto fail; win_printer->printer.AddRef(&win_printer->printer); win_printer->printer.backend->AddRef(win_printer->printer.backend); return &win_printer->printer; fail: printer_win_free_printer(&win_printer->printer); return NULL; } static void printer_win_release_enum_printers(rdpPrinter** printers) { rdpPrinter** cur = printers; while ((cur != NULL) && ((*cur) != NULL)) { if ((*cur)->ReleaseRef) (*cur)->ReleaseRef(*cur); cur++; } free(printers); } static rdpPrinter** printer_win_enum_printers(rdpPrinterDriver* driver) { rdpPrinter** printers; int num_printers; PRINTER_INFO_2* prninfo = NULL; DWORD needed, returned; BOOL haveDefault = FALSE; LPWSTR defaultPrinter = NULL; GetDefaultPrinter(NULL, &needed); if (needed) { defaultPrinter = (LPWSTR)calloc(needed, sizeof(WCHAR)); if (!defaultPrinter) return NULL; if (!GetDefaultPrinter(defaultPrinter, &needed)) defaultPrinter[0] = '\0'; } /* find required size for the buffer */ EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, NULL, 0, &needed, &returned); /* allocate array of PRINTER_INFO structures */ prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed); if (!prninfo) { free(defaultPrinter); return NULL; } /* call again */ if (!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, (LPBYTE)prninfo, needed, &needed, &returned)) { } printers = (rdpPrinter**)calloc((returned + 1), sizeof(rdpPrinter*)); if (!printers) { GlobalFree(prninfo); free(defaultPrinter); return NULL; } num_printers = 0; for (int i = 0; i < (int)returned; i++) { rdpPrinter* current = printers[num_printers]; current = printer_win_new_printer((rdpWinPrinterDriver*)driver, prninfo[i].pPrinterName, prninfo[i].pDriverName, _wcscmp(prninfo[i].pPrinterName, defaultPrinter) == 0); if (!current) { printer_win_release_enum_printers(printers); printers = NULL; break; } if (current->is_default) haveDefault = TRUE; printers[num_printers++] = current; } if (!haveDefault && (returned > 0)) printers[0]->is_default = TRUE; GlobalFree(prninfo); free(defaultPrinter); return printers; } static rdpPrinter* printer_win_get_printer(rdpPrinterDriver* driver, const char* name, const char* driverName, BOOL isDefault) { WCHAR* driverNameW = NULL; WCHAR* nameW = NULL; rdpWinPrinterDriver* win_driver = (rdpWinPrinterDriver*)driver; rdpPrinter* myPrinter = NULL; if (name) { nameW = ConvertUtf8ToWCharAlloc(name, NULL); if (!nameW) return NULL; } if (driverName) { driverNameW = ConvertUtf8ToWCharAlloc(driverName, NULL); if (!driverNameW) return NULL; } myPrinter = printer_win_new_printer(win_driver, nameW, driverNameW, isDefault); free(driverNameW); free(nameW); return myPrinter; } static void printer_win_add_ref_driver(rdpPrinterDriver* driver) { rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; if (win) win->references++; } /* Singleton */ static rdpWinPrinterDriver* win_driver = NULL; static void printer_win_release_ref_driver(rdpPrinterDriver* driver) { rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; if (win->references <= 1) { free(win); win_driver = NULL; } else win->references--; } FREERDP_ENTRY_POINT(UINT win_freerdp_printer_client_subsystem_entry(void* arg)) { rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg; if (!ppPrinter) return ERROR_INVALID_PARAMETER; if (!win_driver) { win_driver = (rdpWinPrinterDriver*)calloc(1, sizeof(rdpWinPrinterDriver)); if (!win_driver) return ERROR_OUTOFMEMORY; win_driver->driver.EnumPrinters = printer_win_enum_printers; win_driver->driver.ReleaseEnumPrinters = printer_win_release_enum_printers; win_driver->driver.GetPrinter = printer_win_get_printer; win_driver->driver.AddRef = printer_win_add_ref_driver; win_driver->driver.ReleaseRef = printer_win_release_ref_driver; win_driver->id_sequence = 1; } win_driver->driver.AddRef(&win_driver->driver); *ppPrinter = &win_driver->driver; return CHANNEL_RC_OK; }