2 * PROJECT: ReactOS Local Spooler
3 * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation
4 * PURPOSE: Functions related to Printers and printing
5 * COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
15 * @name _PrinterListCompareRoutine
17 * SKIPLIST_COMPARE_ROUTINE for the Printer List.
18 * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Printers.
21 _PrinterListCompareRoutine(PVOID FirstStruct
, PVOID SecondStruct
)
23 PLOCAL_PRINTER A
= (PLOCAL_PRINTER
)FirstStruct
;
24 PLOCAL_PRINTER B
= (PLOCAL_PRINTER
)SecondStruct
;
26 return wcsicmp(A
->pwszPrinterName
, B
->pwszPrinterName
);
30 * @name InitializePrinterList
32 * Initializes a list of locally available Printers.
33 * The list is searchable by name and returns information about the printers, including their job queues.
34 * During this process, the job queues are also initialized.
37 InitializePrinterList()
39 const WCHAR wszPrintersKey
[] = L
"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers";
48 PLOCAL_PRINTER pPrinter
= NULL
;
49 PLOCAL_PRINT_PROCESSOR pPrintProcessor
;
50 PWSTR pwszPrintProcessor
= NULL
;
51 WCHAR wszPrinterName
[MAX_PRINTER_NAME
+ 1];
53 // Initialize an empty list for our printers.
54 InitializeSkiplist(&PrinterList
, DllAllocSplMem
, _PrinterListCompareRoutine
, (PSKIPLIST_FREE_ROUTINE
)DllFreeSplMem
);
56 // Open our printers registry key. Each subkey is a local printer there.
57 lStatus
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, wszPrintersKey
, 0, KEY_READ
, &hKey
);
58 if (lStatus
!= ERROR_SUCCESS
)
60 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus
);
64 // Get the number of subkeys.
65 lStatus
= RegQueryInfoKeyW(hKey
, NULL
, NULL
, NULL
, &dwSubKeys
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
66 if (lStatus
!= ERROR_SUCCESS
)
68 ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus
);
72 // Loop through all available local printers.
73 for (i
= 0; i
< dwSubKeys
; i
++)
75 // Cleanup tasks from the previous run
84 if (pPrinter
->pwszDefaultDatatype
)
85 DllFreeSplStr(pPrinter
->pwszDefaultDatatype
);
87 if (pPrinter
->pwszDescription
)
88 DllFreeSplStr(pPrinter
->pwszDescription
);
90 if (pPrinter
->pwszPrinterDriver
)
91 DllFreeSplStr(pPrinter
->pwszPrinterDriver
);
93 if (pPrinter
->pwszPrinterName
)
94 DllFreeSplStr(pPrinter
->pwszPrinterName
);
96 DllFreeSplMem(pPrinter
);
100 if (pwszPrintProcessor
)
102 DllFreeSplStr(pwszPrintProcessor
);
103 pwszPrintProcessor
= NULL
;
106 // Get the name of this printer.
107 cchPrinterName
= _countof(wszPrinterName
);
108 lStatus
= RegEnumKeyExW(hKey
, i
, wszPrinterName
, &cchPrinterName
, NULL
, NULL
, NULL
, NULL
);
109 if (lStatus
== ERROR_MORE_DATA
)
111 // This printer name exceeds the maximum length and is invalid.
114 else if (lStatus
!= ERROR_SUCCESS
)
116 ERR("RegEnumKeyExW failed for iteration %lu with status %ld!\n", i
, lStatus
);
120 // Open this Printer's registry key.
121 lStatus
= RegOpenKeyExW(hKey
, wszPrinterName
, 0, KEY_READ
, &hSubKey
);
122 if (lStatus
!= ERROR_SUCCESS
)
124 ERR("RegOpenKeyExW failed for Printer \"%S\" with status %ld!\n", wszPrinterName
, lStatus
);
128 // Get the Print Processor.
129 pwszPrintProcessor
= AllocAndRegQueryWSZ(hSubKey
, L
"Print Processor");
130 if (!pwszPrintProcessor
)
133 // Try to find it in the Print Processor List.
134 pPrintProcessor
= FindPrintProcessor(pwszPrintProcessor
);
135 if (!pPrintProcessor
)
137 ERR("Invalid Print Processor \"%S\" for Printer \"%S\"!\n", pwszPrintProcessor
, wszPrinterName
);
141 // Create a new LOCAL_PRINTER structure for it.
142 pPrinter
= DllAllocSplMem(sizeof(LOCAL_PRINTER
));
145 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
149 pPrinter
->pwszPrinterName
= AllocSplStr(wszPrinterName
);
150 pPrinter
->pPrintProcessor
= pPrintProcessor
;
151 InitializePrinterJobList(pPrinter
);
153 // Get the printer driver.
154 pPrinter
->pwszPrinterDriver
= AllocAndRegQueryWSZ(hSubKey
, L
"Printer Driver");
155 if (!pPrinter
->pwszPrinterDriver
)
158 // Get the description.
159 pPrinter
->pwszDescription
= AllocAndRegQueryWSZ(hSubKey
, L
"Description");
160 if (!pPrinter
->pwszDescription
)
163 // Get the default datatype.
164 pPrinter
->pwszDefaultDatatype
= AllocAndRegQueryWSZ(hSubKey
, L
"Datatype");
165 if (!pPrinter
->pwszDefaultDatatype
)
168 // Verify that it's valid.
169 if (!FindDatatype(pPrintProcessor
, pPrinter
->pwszDefaultDatatype
))
171 ERR("Invalid default datatype \"%S\" for Printer \"%S\"!\n", pPrinter
->pwszDefaultDatatype
, wszPrinterName
);
175 // Get the default DevMode.
176 cbDevMode
= sizeof(DEVMODEW
);
177 lStatus
= RegQueryValueExW(hSubKey
, L
"Default DevMode", NULL
, NULL
, (PBYTE
)&pPrinter
->DefaultDevMode
, &cbDevMode
);
178 if (lStatus
!= ERROR_SUCCESS
|| cbDevMode
!= sizeof(DEVMODEW
))
180 ERR("Couldn't query a valid DevMode for Printer \"%S\", status is %ld, cbDevMode is %lu!\n", wszPrinterName
, lStatus
, cbDevMode
);
184 // Add this printer to the printer list.
185 if (!InsertElementSkiplist(&PrinterList
, pPrinter
))
187 ERR("InsertElementSkiplist failed for Printer \"%S\"!\n", pPrinter
->pwszPrinterName
);
191 // Don't let the cleanup routines free this.
198 RegCloseKey(hSubKey
);
202 if (pPrinter
->pwszDefaultDatatype
)
203 DllFreeSplStr(pPrinter
->pwszDefaultDatatype
);
205 if (pPrinter
->pwszDescription
)
206 DllFreeSplStr(pPrinter
->pwszDescription
);
208 if (pPrinter
->pwszPrinterDriver
)
209 DllFreeSplStr(pPrinter
->pwszPrinterDriver
);
211 if (pPrinter
->pwszPrinterName
)
212 DllFreeSplStr(pPrinter
->pwszPrinterName
);
214 DllFreeSplMem(pPrinter
);
217 if (pwszPrintProcessor
)
218 DllFreeSplStr(pwszPrintProcessor
);
227 _LocalEnumPrintersLevel1(DWORD Flags
, LPWSTR Name
, LPBYTE pPrinterEnum
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
229 const WCHAR wszComma
[] = L
",";
234 DWORD cchComputerName
= 0;
237 PBYTE pPrinterString
;
238 PSKIPLIST_NODE pNode
;
239 PLOCAL_PRINTER pPrinter
;
240 PRINTER_INFO_1W PrinterInfo1
;
241 WCHAR wszComputerName
[2 + MAX_COMPUTERNAME_LENGTH
+ 1 + 1];
243 DWORD dwOffsets
[] = {
244 FIELD_OFFSET(PRINTER_INFO_1W
, pName
),
245 FIELD_OFFSET(PRINTER_INFO_1W
, pDescription
),
246 FIELD_OFFSET(PRINTER_INFO_1W
, pComment
),
250 if (Flags
& PRINTER_ENUM_NAME
)
254 // The user supplied a Computer Name (with leading double backslashes) or Print Provider Name.
255 // Only process what's directed at us and dismiss every other request with ERROR_INVALID_NAME.
256 if (Name
[0] == L
'\\' && Name
[1] == L
'\\')
258 // Prepend slashes to the computer name.
259 wszComputerName
[0] = L
'\\';
260 wszComputerName
[1] = L
'\\';
262 // Get the local computer name for comparison.
263 cchComputerName
= MAX_COMPUTERNAME_LENGTH
+ 1;
264 if (!GetComputerNameW(&wszComputerName
[2], &cchComputerName
))
266 ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
270 // Add the leading slashes to the total length.
271 cchComputerName
+= 2;
273 // Now compare this with the local computer name and reject if it doesn't match.
274 if (wcsicmp(&Name
[2], &wszComputerName
[2]) != 0)
276 SetLastError(ERROR_INVALID_NAME
);
280 // Add a trailing backslash to wszComputerName, which will later be prepended in front of the printer names.
281 wszComputerName
[cchComputerName
++] = L
'\\';
282 wszComputerName
[cchComputerName
] = 0;
284 else if (wcsicmp(Name
, wszPrintProviderInfo
[0]) != 0)
286 // The user supplied a name that cannot be processed by the local print provider.
287 SetLastError(ERROR_INVALID_NAME
);
293 // The caller wants information about this Print Provider.
294 // spoolss packs this into an array of information about all Print Providers.
295 *pcbNeeded
= sizeof(PRINTER_INFO_1W
);
297 for (i
= 0; i
< 3; i
++)
298 *pcbNeeded
+= (wcslen(wszPrintProviderInfo
[i
]) + 1) * sizeof(WCHAR
);
302 // Check if the supplied buffer is large enough.
303 if (cbBuf
< *pcbNeeded
)
305 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
309 // Copy over the print processor information.
310 ((PPRINTER_INFO_1W
)pPrinterEnum
)->Flags
= 0;
311 PackStrings(wszPrintProviderInfo
, pPrinterEnum
, dwOffsets
, &pPrinterEnum
[*pcbNeeded
]);
316 // Count the required buffer size and the number of printers.
317 for (pNode
= PrinterList
.Head
.Next
[0]; pNode
; pNode
= pNode
->Next
[0])
319 pPrinter
= (PLOCAL_PRINTER
)pNode
->Element
;
321 // This looks wrong, but is totally right. PRINTER_INFO_1W has three members pName, pComment and pDescription.
322 // But pComment equals the "Description" registry value while pDescription is concatenated out of pName and pComment.
323 // On top of this, the computer name is prepended to the printer name if the user supplied the local computer name during the query.
324 cbName
= (wcslen(pPrinter
->pwszPrinterName
) + 1) * sizeof(WCHAR
);
325 cbComment
= (wcslen(pPrinter
->pwszDescription
) + 1) * sizeof(WCHAR
);
326 cbDescription
= cchComputerName
* sizeof(WCHAR
) + cbName
+ cbComment
+ sizeof(WCHAR
);
328 *pcbNeeded
+= sizeof(PRINTER_INFO_1W
) + cchComputerName
* sizeof(WCHAR
) + cbName
+ cbComment
+ cbDescription
;
332 // Check if the supplied buffer is large enough.
333 if (cbBuf
< *pcbNeeded
)
335 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
339 // Put the strings right after the last PRINTER_INFO_1W structure.
340 // Due to all the required string processing, we can't just use PackStrings here :(
341 pPrinterInfo
= pPrinterEnum
;
342 pPrinterString
= pPrinterEnum
+ *pcReturned
* sizeof(PRINTER_INFO_1W
);
344 // Copy over the printer information.
345 for (pNode
= PrinterList
.Head
.Next
[0]; pNode
; pNode
= pNode
->Next
[0])
347 pPrinter
= (PLOCAL_PRINTER
)pNode
->Element
;
349 // FIXME: As for now, the Flags member returns no information.
350 PrinterInfo1
.Flags
= 0;
352 // Copy the printer name.
353 PrinterInfo1
.pName
= (PWSTR
)pPrinterString
;
354 CopyMemory(pPrinterString
, wszComputerName
, cchComputerName
* sizeof(WCHAR
));
355 pPrinterString
+= cchComputerName
* sizeof(WCHAR
);
356 cbName
= (wcslen(pPrinter
->pwszPrinterName
) + 1) * sizeof(WCHAR
);
357 CopyMemory(pPrinterString
, pPrinter
->pwszPrinterName
, cbName
);
358 pPrinterString
+= cbName
;
360 // Copy the printer comment (equals the "Description" registry value).
361 PrinterInfo1
.pComment
= (PWSTR
)pPrinterString
;
362 cbComment
= (wcslen(pPrinter
->pwszDescription
) + 1) * sizeof(WCHAR
);
363 CopyMemory(pPrinterString
, pPrinter
->pwszDescription
, cbComment
);
364 pPrinterString
+= cbComment
;
366 // Copy the description, which for PRINTER_INFO_1W has the form "Name,Comment,"
367 PrinterInfo1
.pDescription
= (PWSTR
)pPrinterString
;
368 CopyMemory(pPrinterString
, wszComputerName
, cchComputerName
* sizeof(WCHAR
));
369 pPrinterString
+= cchComputerName
* sizeof(WCHAR
);
370 CopyMemory(pPrinterString
, pPrinter
->pwszPrinterName
, cbName
- sizeof(WCHAR
));
371 pPrinterString
+= cbName
- sizeof(WCHAR
);
372 CopyMemory(pPrinterString
, wszComma
, sizeof(WCHAR
));
373 pPrinterString
+= sizeof(WCHAR
);
374 CopyMemory(pPrinterString
, pPrinter
->pwszDescription
, cbComment
- sizeof(WCHAR
));
375 pPrinterString
+= cbComment
- sizeof(WCHAR
);
376 CopyMemory(pPrinterString
, wszComma
, sizeof(wszComma
));
377 pPrinterString
+= sizeof(wszComma
);
379 // Finally copy the structure and advance to the next one in the output buffer.
380 CopyMemory(pPrinterInfo
, &PrinterInfo1
, sizeof(PRINTER_INFO_1W
));
381 pPrinterInfo
+= sizeof(PRINTER_INFO_1W
);
388 LocalEnumPrinters(DWORD Flags
, LPWSTR Name
, DWORD Level
, LPBYTE pPrinterEnum
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
390 // Do no sanity checks here. This is verified by localspl_apitest!
397 SetLastError(ERROR_SUCCESS
);
399 if (Flags
& PRINTER_ENUM_LOCAL
)
401 // The function behaves quite differently for each level.
403 return _LocalEnumPrintersLevel1(Flags
, Name
, pPrinterEnum
, cbBuf
, pcbNeeded
, pcReturned
);
405 // TODO: Handle other levels.
407 // The caller supplied an invalid level.
411 // Treat it as success if the caller queried no information and we don't need to return any.
416 LocalOpenPrinter(PWSTR lpPrinterName
, HANDLE
* phPrinter
, PPRINTER_DEFAULTSW pDefault
)
418 BOOL bReturnValue
= ROUTER_UNKNOWN
;
419 DWORD cchComputerName
;
420 DWORD cchPrinterName
;
422 PWSTR p
= lpPrinterName
;
423 PWSTR pwszPrinterName
= NULL
;
425 PLOCAL_HANDLE pHandle
;
426 PLOCAL_PRINTER pPrinter
;
427 PLOCAL_PRINTER_HANDLE pPrinterHandle
= NULL
;
428 WCHAR wszComputerName
[MAX_COMPUTERNAME_LENGTH
+ 1];
431 if (!lpPrinterName
|| !phPrinter
)
433 SetLastError(ERROR_INVALID_PARAMETER
);
437 // Does lpPrinterName begin with two backslashes to indicate a server name?
438 if (lpPrinterName
[0] == L
'\\' && lpPrinterName
[1] == L
'\\')
440 // Skip these two backslashes.
443 // Look for the closing backslash.
444 p
= wcschr(lpPrinterName
, L
'\\');
447 // We didn't get a proper server name.
448 SetLastError(ERROR_INVALID_PRINTER_NAME
);
452 // Null-terminate the string here to enable comparison.
455 // Get the local computer name for comparison.
456 cchComputerName
= _countof(wszComputerName
);
457 if (!GetComputerNameW(wszComputerName
, &cchComputerName
))
459 ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
463 // Now compare this with the local computer name and reject if it doesn't match, because this print provider only supports local printers.
464 if (wcsicmp(lpPrinterName
, wszComputerName
) != 0)
466 SetLastError(ERROR_INVALID_PRINTER_NAME
);
470 // We have checked the server name and don't need it anymore.
471 lpPrinterName
= p
+ 1;
474 // Look for a comma. If it exists, it indicates the end of the printer name.
475 p
= wcschr(lpPrinterName
, L
',');
477 cchPrinterName
= p
- lpPrinterName
;
479 cchPrinterName
= wcslen(lpPrinterName
);
481 // No printer name and no comma? This is invalid!
482 if (!cchPrinterName
&& !p
)
484 SetLastError(ERROR_INVALID_PRINTER_NAME
);
488 // Do we have a printer name?
492 pwszPrinterName
= DllAllocSplMem((cchPrinterName
+ 1) * sizeof(WCHAR
));
493 CopyMemory(pwszPrinterName
, lpPrinterName
, cchPrinterName
* sizeof(WCHAR
));
494 pwszPrinterName
[cchPrinterName
] = 0;
496 // Retrieve the associated printer from the list.
497 pPrinter
= LookupElementSkiplist(&PrinterList
, &pwszPrinterName
, NULL
);
500 // The printer does not exist.
501 SetLastError(ERROR_INVALID_PRINTER_NAME
);
505 // Create a new printer handle.
506 pPrinterHandle
= DllAllocSplMem(sizeof(LOCAL_PRINTER_HANDLE
));
507 pPrinterHandle
->Printer
= pPrinter
;
509 // Check if a datatype was given.
510 if (pDefault
&& pDefault
->pDatatype
)
512 // Use the datatype if it's valid.
513 if (!FindDatatype(pPrinter
->pPrintProcessor
, pDefault
->pDatatype
))
515 SetLastError(ERROR_INVALID_DATATYPE
);
519 pPrinterHandle
->pwszDatatype
= AllocSplStr(pDefault
->pDatatype
);
523 // Use the default datatype.
524 pPrinterHandle
->pwszDatatype
= AllocSplStr(pPrinter
->pwszDefaultDatatype
);
527 // Check if a DevMode was given, otherwise use the default.
528 if (pDefault
&& pDefault
->pDevMode
)
529 CopyMemory(&pPrinterHandle
->DevMode
, pDefault
->pDevMode
, sizeof(DEVMODEW
));
531 CopyMemory(&pPrinterHandle
->DevMode
, &pPrinter
->DefaultDevMode
, sizeof(DEVMODEW
));
533 // Did we have a comma? Then the user may want a handle to an existing job instead of creating a new job.
545 // The "Job " string has to follow now.
546 if (wcscmp(p
, L
"Job ") != 0)
548 SetLastError(ERROR_INVALID_PRINTER_NAME
);
552 // Skip the "Job " string.
553 p
+= sizeof("Job ") - 1;
555 // Skip even more whitespace.
559 // Finally extract the desired Job ID.
560 dwJobID
= wcstoul(p
, NULL
, 10);
561 if (!IS_VALID_JOB_ID(dwJobID
))
563 // The user supplied an invalid Job ID.
564 SetLastError(ERROR_INVALID_PRINTER_NAME
);
568 // Look for this job in the Global Job List.
569 pJob
= LookupElementSkiplist(&GlobalJobList
, &dwJobID
, NULL
);
570 if (!pJob
|| pJob
->Printer
!= pPrinter
)
572 // The user supplied a non-existing Job ID or the Job ID does not belong to the supplied printer name.
573 SetLastError(ERROR_INVALID_PRINTER_NAME
);
577 pPrinterHandle
->StartedJob
= pJob
;
580 // Create a new handle that references a printer.
581 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
582 pHandle
->HandleType
= Printer
;
583 pHandle
->SpecificHandle
= pPrinterHandle
;
587 // No printer name, but we have a comma!
588 // This may be a request to a XcvMonitor or XcvPort handle.
598 // Check if this is a request to a XcvMonitor.
599 if (wcscmp(p
, L
"XcvMonitor ") == 0)
601 // Skip the "XcvMonitor " string.
602 p
+= sizeof("XcvMonitor ") - 1;
604 ///////////// TODO /////////////////////
605 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
606 pHandle
->HandleType
= Monitor
;
607 //pHandle->SpecificHandle = pMonitorHandle;
609 else if (wcscmp(p
, L
"XcvPort ") == 0)
611 // Skip the "XcvPort " string.
612 p
+= sizeof("XcvPort ") - 1;
614 //////////// TODO //////////////////////
615 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
616 pHandle
->HandleType
= Port
;
617 //pHandle->SpecificHandle = pPortHandle;
621 SetLastError(ERROR_INVALID_PRINTER_NAME
);
626 *phPrinter
= (HANDLE
)pHandle
;
627 bReturnValue
= ROUTER_SUCCESS
;
629 // Don't let the cleanup routines free this.
630 pPrinterHandle
= NULL
;
631 pwszPrinterName
= NULL
;
636 if (pPrinterHandle
->pwszDatatype
)
637 DllFreeSplStr(pPrinterHandle
->pwszDatatype
);
639 DllFreeSplMem(pPrinterHandle
);
643 DllFreeSplMem(pwszPrinterName
);
649 LocalStartDocPrinter(HANDLE hPrinter
, DWORD Level
, LPBYTE pDocInfo
)
651 PDOC_INFO_1W pDocumentInfo1
;
652 PLOCAL_HANDLE pHandle
;
654 PLOCAL_PRINTER_HANDLE pPrinterHandle
;
659 SetLastError(ERROR_INVALID_PARAMETER
);
665 SetLastError(ERROR_INVALID_HANDLE
);
669 // Check if this is a printer handle.
670 pHandle
= (PLOCAL_HANDLE
)hPrinter
;
671 if (pHandle
->HandleType
!= Printer
)
673 SetLastError(ERROR_INVALID_HANDLE
);
677 pPrinterHandle
= (PLOCAL_PRINTER_HANDLE
)pHandle
->SpecificHandle
;
679 // Check if this is the right document information level.
682 SetLastError(ERROR_INVALID_LEVEL
);
686 pDocumentInfo1
= (PDOC_INFO_1W
)pDocInfo
;
689 pJob
= DllAllocSplMem(sizeof(LOCAL_JOB
));
690 pJob
->Printer
= pPrinterHandle
->Printer
;
691 pJob
->dwPriority
= DEF_PRIORITY
;
693 // Check if a datatype was given.
694 if (pDocumentInfo1
->pDatatype
)
696 // Use the datatype if it's valid.
697 if (!FindDatatype(pJob
->Printer
->pPrintProcessor
, pDocumentInfo1
->pDatatype
))
699 SetLastError(ERROR_INVALID_DATATYPE
);
703 pJob
->pwszDatatype
= AllocSplStr(pDocumentInfo1
->pDatatype
);
707 // Use the printer handle datatype.
708 pJob
->pwszDatatype
= AllocSplStr(pPrinterHandle
->pwszDatatype
);
711 // Copy over printer defaults.
712 CopyMemory(&pJob
->DevMode
, &pPrinterHandle
->DevMode
, sizeof(DEVMODEW
));
714 // Copy over supplied information.
715 if (pDocumentInfo1
->pDocName
)
716 pJob
->pwszDocumentName
= AllocSplStr(pDocumentInfo1
->pDocName
);
718 if (pDocumentInfo1
->pOutputFile
)
719 pJob
->pwszOutputFile
= AllocSplStr(pDocumentInfo1
->pOutputFile
);
721 // Get an ID for the new job.
722 if (!GetNextJobID(&pJob
->dwJobID
))
725 // Add the job to the Global Job List.
726 if (!InsertElementSkiplist(&GlobalJobList
, pJob
))
728 ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob
->dwJobID
);
732 // Add the job at the end of the Printer's Job List.
733 // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
734 if (!InsertTailElementSkiplist(&pJob
->Printer
->JobList
, pJob
))
736 ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob
->dwJobID
);
740 pPrinterHandle
->StartedJob
= pJob
;
741 return pJob
->dwJobID
;
745 LocalStartPagePrinter(HANDLE hPrinter
)
747 ///////////// TODO /////////////////////
752 LocalWritePrinter(HANDLE hPrinter
, LPVOID pBuf
, DWORD cbBuf
, LPDWORD pcWritten
)
754 ///////////// TODO /////////////////////
759 LocalEndPagePrinter(HANDLE hPrinter
)
761 ///////////// TODO /////////////////////
766 LocalEndDocPrinter(HANDLE hPrinter
)
768 ///////////// TODO /////////////////////
773 LocalClosePrinter(HANDLE hPrinter
)
775 PLOCAL_HANDLE pHandle
;
779 SetLastError(ERROR_INVALID_HANDLE
);
783 pHandle
= (PLOCAL_HANDLE
)hPrinter
;
785 ///////////// TODO /////////////////////
786 /// Check the handle type, do thoroughful checks on all data fields and clean them.
787 ////////////////////////////////////////
789 DllFreeSplMem(pHandle
);