[Printing] Part of GDI Support
[reactos.git] / win32ss / printing / base / winspool / printers.c
1 /*
2 * PROJECT: ReactOS Spooler API
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Functions related to Printers and printing
5 * COPYRIGHT: Copyright 2015-2018 Colin Finck (colin@reactos.org)
6 */
7
8 #include "precomp.h"
9 #include <marshalling/printers.h>
10 #include <marshalling/printerdrivers.h>
11 #include <strsafe.h>
12
13 // Local Constants
14
15 /** And the award for the most confusingly named setting goes to "Device", for storing the default printer of the current user.
16 Ok, I admit that this has historical reasons. It's still not straightforward in any way though! */
17 static const WCHAR wszWindowsKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
18 static const WCHAR wszDeviceValue[] = L"Device";
19
20 static DWORD
21 _StartDocPrinterSpooled(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1, PADDJOB_INFO_1W pAddJobInfo1)
22 {
23 DWORD cbNeeded;
24 DWORD dwErrorCode;
25 PJOB_INFO_1W pJobInfo1 = NULL;
26
27 // Create the spool file.
28 pHandle->hSPLFile = CreateFileW(pAddJobInfo1->Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
29 if (pHandle->hSPLFile == INVALID_HANDLE_VALUE)
30 {
31 dwErrorCode = GetLastError();
32 ERR("CreateFileW failed for \"%S\" with error %lu!\n", pAddJobInfo1->Path, dwErrorCode);
33 goto Cleanup;
34 }
35
36 // Get the size of the job information.
37 GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, NULL, 0, &cbNeeded);
38 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
39 {
40 dwErrorCode = GetLastError();
41 ERR("GetJobW failed with error %lu!\n", dwErrorCode);
42 goto Cleanup;
43 }
44
45 // Allocate enough memory for the returned job information.
46 pJobInfo1 = HeapAlloc(hProcessHeap, 0, cbNeeded);
47 if (!pJobInfo1)
48 {
49 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
50 ERR("HeapAlloc failed!\n");
51 goto Cleanup;
52 }
53
54 // Get the job information.
55 if (!GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, cbNeeded, &cbNeeded))
56 {
57 dwErrorCode = GetLastError();
58 ERR("GetJobW failed with error %lu!\n", dwErrorCode);
59 goto Cleanup;
60 }
61
62 // Add our document information.
63 if (pDocInfo1->pDatatype)
64 pJobInfo1->pDatatype = pDocInfo1->pDatatype;
65
66 pJobInfo1->pDocument = pDocInfo1->pDocName;
67
68 // Set the new job information.
69 if (!SetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, 0))
70 {
71 dwErrorCode = GetLastError();
72 ERR("SetJobW failed with error %lu!\n", dwErrorCode);
73 goto Cleanup;
74 }
75
76 // We were successful!
77 pHandle->dwJobID = pAddJobInfo1->JobId;
78 dwErrorCode = ERROR_SUCCESS;
79
80 Cleanup:
81 if (pJobInfo1)
82 HeapFree(hProcessHeap, 0, pJobInfo1);
83
84 return dwErrorCode;
85 }
86
87 static DWORD
88 _StartDocPrinterWithRPC(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1)
89 {
90 DWORD dwErrorCode;
91 WINSPOOL_DOC_INFO_CONTAINER DocInfoContainer;
92
93 DocInfoContainer.Level = 1;
94 DocInfoContainer.DocInfo.pDocInfo1 = (WINSPOOL_DOC_INFO_1*)pDocInfo1;
95
96 RpcTryExcept
97 {
98 dwErrorCode = _RpcStartDocPrinter(pHandle->hPrinter, &DocInfoContainer, &pHandle->dwJobID);
99 }
100 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
101 {
102 dwErrorCode = RpcExceptionCode();
103 ERR("_RpcStartDocPrinter failed with exception code %lu!\n", dwErrorCode);
104 }
105 RpcEndExcept;
106
107 return dwErrorCode;
108 }
109
110 BOOL WINAPI
111 AbortPrinter(HANDLE hPrinter)
112 {
113 TRACE("AbortPrinter(%p)\n", hPrinter);
114 UNIMPLEMENTED;
115 return FALSE;
116 }
117
118 HANDLE WINAPI
119 AddPrinterA(PSTR pName, DWORD Level, PBYTE pPrinter)
120 {
121 TRACE("AddPrinterA(%s, %lu, %p)\n", pName, Level, pPrinter);
122 UNIMPLEMENTED;
123 return NULL;
124 }
125
126 HANDLE WINAPI
127 AddPrinterW(PWSTR pName, DWORD Level, PBYTE pPrinter)
128 {
129 TRACE("AddPrinterW(%S, %lu, %p)\n", pName, Level, pPrinter);
130 UNIMPLEMENTED;
131 return NULL;
132 }
133
134 BOOL WINAPI
135 ClosePrinter(HANDLE hPrinter)
136 {
137 DWORD dwErrorCode;
138 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
139
140 TRACE("ClosePrinter(%p)\n", hPrinter);
141
142 // Sanity checks.
143 if (!pHandle)
144 {
145 dwErrorCode = ERROR_INVALID_HANDLE;
146 goto Cleanup;
147 }
148
149 // Do the RPC call.
150 RpcTryExcept
151 {
152 dwErrorCode = _RpcClosePrinter(&pHandle->hPrinter);
153 }
154 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
155 {
156 dwErrorCode = RpcExceptionCode();
157 ERR("_RpcClosePrinter failed with exception code %lu!\n", dwErrorCode);
158 }
159 RpcEndExcept;
160
161 // Close any open file handle.
162 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
163 CloseHandle(pHandle->hSPLFile);
164
165 // Free the memory for the handle.
166 HeapFree(hProcessHeap, 0, pHandle);
167
168 Cleanup:
169 SetLastError(dwErrorCode);
170 return (dwErrorCode == ERROR_SUCCESS);
171 }
172
173 BOOL WINAPI
174 DeletePrinter(HANDLE hPrinter)
175 {
176 TRACE("DeletePrinter(%p)\n", hPrinter);
177 UNIMPLEMENTED;
178 return FALSE;
179 }
180
181 DWORD WINAPI
182 DeviceCapabilitiesA(LPCSTR pDevice, LPCSTR pPort, WORD fwCapability, LPSTR pOutput, const DEVMODEA* pDevMode)
183 {
184 TRACE("DeviceCapabilitiesA(%s, %s, %hu, %p, %p)\n", pDevice, pPort, fwCapability, pOutput, pDevMode);
185 UNIMPLEMENTED;
186 return 0;
187 }
188
189 DWORD WINAPI
190 DeviceCapabilitiesW(LPCWSTR pDevice, LPCWSTR pPort, WORD fwCapability, LPWSTR pOutput, const DEVMODEW* pDevMode)
191 {
192 TRACE("DeviceCapabilitiesW(%S, %S, %hu, %p, %p)\n", pDevice, pPort, fwCapability, pOutput, pDevMode);
193 UNIMPLEMENTED;
194 return 0;
195 }
196
197 INT WINAPI
198 DocumentEvent( HANDLE hPrinter, HDC hdc, int iEsc, ULONG cbIn, PVOID pvIn, ULONG cbOut, PVOID pvOut)
199 {
200 ERR("DocumentEvent(%p, %p, %lu, %lu, %p, %lu, %p)\n", hPrinter, hdc, iEsc, cbIn, pvIn, cbOut, pvOut);
201 UNIMPLEMENTED;
202 return DOCUMENTEVENT_UNSUPPORTED;
203 }
204
205 LONG WINAPI
206 DocumentPropertiesA(HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode)
207 {
208 TRACE("DocumentPropertiesA(%p, %p, %s, %p, %p, %lu)\n", hWnd, hPrinter, pDeviceName, pDevModeOutput, pDevModeInput, fMode);
209 UNIMPLEMENTED;
210 return -1;
211 }
212
213 static PRINTER_INFO_9W * get_devmodeW(HANDLE hprn)
214 {
215 PRINTER_INFO_9W *pi9 = NULL;
216 DWORD needed = 0;
217 BOOL res;
218
219 res = GetPrinterW(hprn, 9, NULL, 0, &needed);
220 if (!res && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
221 {
222 pi9 = HeapAlloc(hProcessHeap, 0, needed);
223 res = GetPrinterW(hprn, 9, (LPBYTE)pi9, needed, &needed);
224 }
225
226 if (res)
227 return pi9;
228
229 ERR("GetPrinterW failed with %u\n", GetLastError());
230 HeapFree(hProcessHeap, 0, pi9);
231 return NULL;
232 }
233
234 LONG WINAPI
235 DocumentPropertiesW(HWND hWnd, HANDLE hPrinter, LPWSTR pDeviceName, PDEVMODEW pDevModeOutput, PDEVMODEW pDevModeInput, DWORD fMode)
236 {
237 HANDLE hUseHandle = NULL;
238 PRINTER_INFO_9W *pi9 = NULL;
239 LONG Result = -1, Length;
240
241 TRACE("DocumentPropertiesW(%p, %p, %S, %p, %p, %lu)\n", hWnd, hPrinter, pDeviceName, pDevModeOutput, pDevModeInput, fMode);
242 if (hPrinter)
243 {
244 hUseHandle = hPrinter;
245 }
246 else if (!OpenPrinterW(pDeviceName, &hUseHandle, NULL))
247 {
248 ERR("No handle, and no usable printer name passed in\n");
249 return -1;
250 }
251
252 pi9 = get_devmodeW(hUseHandle);
253
254 if (pi9)
255 {
256 Length = pi9->pDevMode->dmSize + pi9->pDevMode->dmDriverExtra;
257 // See wineps.drv PSDRV_ExtDeviceMode
258 if (fMode)
259 {
260 Result = 1; /* IDOK */
261
262 if (fMode & DM_IN_BUFFER)
263 {
264 FIXME("Merge pDevModeInput with pi9, write back to driver!\n");
265 // See wineps.drv PSDRV_MergeDevmodes
266 }
267
268 if (fMode & DM_IN_PROMPT)
269 {
270 FIXME("Show property sheet!\n");
271 Result = 2; /* IDCANCEL */
272 }
273
274 if (fMode & (DM_OUT_BUFFER | DM_OUT_DEFAULT))
275 {
276 if (pDevModeOutput)
277 {
278 memcpy(pDevModeOutput, pi9->pDevMode, pi9->pDevMode->dmSize + pi9->pDevMode->dmDriverExtra);
279 }
280 else
281 {
282 ERR("No pDevModeOutput\n");
283 Result = -1;
284 }
285 }
286 }
287 else
288 {
289 Result = Length;
290 }
291
292 HeapFree(hProcessHeap, 0, pi9);
293 }
294
295 if (hUseHandle && !hPrinter)
296 ClosePrinter(hUseHandle);
297 return Result;
298 }
299
300 BOOL WINAPI
301 EndDocPrinter(HANDLE hPrinter)
302 {
303 DWORD dwErrorCode;
304 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
305
306 TRACE("EndDocPrinter(%p)\n", hPrinter);
307
308 // Sanity checks.
309 if (!pHandle)
310 {
311 dwErrorCode = ERROR_INVALID_HANDLE;
312 goto Cleanup;
313 }
314
315 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
316 {
317 // For spooled jobs, the document is finished by calling _RpcScheduleJob.
318 RpcTryExcept
319 {
320 dwErrorCode = _RpcScheduleJob(pHandle->hPrinter, pHandle->dwJobID);
321 }
322 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
323 {
324 dwErrorCode = RpcExceptionCode();
325 ERR("_RpcScheduleJob failed with exception code %lu!\n", dwErrorCode);
326 }
327 RpcEndExcept;
328
329 // Close the spool file handle.
330 CloseHandle(pHandle->hSPLFile);
331 }
332 else
333 {
334 // In all other cases, just call _RpcEndDocPrinter.
335 RpcTryExcept
336 {
337 dwErrorCode = _RpcEndDocPrinter(pHandle->hPrinter);
338 }
339 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
340 {
341 dwErrorCode = RpcExceptionCode();
342 ERR("_RpcEndDocPrinter failed with exception code %lu!\n", dwErrorCode);
343 }
344 RpcEndExcept;
345 }
346
347 // A new document can now be started again.
348 pHandle->bStartedDoc = FALSE;
349
350 Cleanup:
351 SetLastError(dwErrorCode);
352 return (dwErrorCode == ERROR_SUCCESS);
353 }
354
355 BOOL WINAPI
356 EndPagePrinter(HANDLE hPrinter)
357 {
358 DWORD dwErrorCode;
359 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
360
361 TRACE("EndPagePrinter(%p)\n", hPrinter);
362
363 // Sanity checks.
364 if (!pHandle)
365 {
366 dwErrorCode = ERROR_INVALID_HANDLE;
367 goto Cleanup;
368 }
369
370 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
371 {
372 // For spooled jobs, we don't need to do anything.
373 dwErrorCode = ERROR_SUCCESS;
374 }
375 else
376 {
377 // In all other cases, just call _RpcEndPagePrinter.
378 RpcTryExcept
379 {
380 dwErrorCode = _RpcEndPagePrinter(pHandle->hPrinter);
381 }
382 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
383 {
384 dwErrorCode = RpcExceptionCode();
385 ERR("_RpcEndPagePrinter failed with exception code %lu!\n", dwErrorCode);
386 }
387 RpcEndExcept;
388 }
389
390 Cleanup:
391 SetLastError(dwErrorCode);
392 return (dwErrorCode == ERROR_SUCCESS);
393 }
394
395 BOOL WINAPI
396 EnumPrintersA(DWORD Flags, PSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
397 {
398 BOOL bReturnValue = FALSE;
399 DWORD cch;
400 PWSTR pwszName = NULL;
401 PSTR pszPrinterName = NULL;
402 PSTR pszServerName = NULL;
403 PSTR pszDescription = NULL;
404 PSTR pszName = NULL;
405 PSTR pszComment = NULL;
406 PSTR pszShareName = NULL;
407 PSTR pszPortName = NULL;
408 PSTR pszDriverName = NULL;
409 PSTR pszLocation = NULL;
410 PSTR pszSepFile = NULL;
411 PSTR pszPrintProcessor = NULL;
412 PSTR pszDatatype = NULL;
413 PSTR pszParameters = NULL;
414 DWORD i;
415 PPRINTER_INFO_1W ppi1w = NULL;
416 PPRINTER_INFO_1A ppi1a = NULL;
417 PPRINTER_INFO_2W ppi2w = NULL;
418 PPRINTER_INFO_2A ppi2a = NULL;
419 PPRINTER_INFO_4W ppi4w = NULL;
420 PPRINTER_INFO_4A ppi4a = NULL;
421 PPRINTER_INFO_5W ppi5w = NULL;
422 PPRINTER_INFO_5A ppi5a = NULL;
423
424 TRACE("EnumPrintersA(%lu, %s, %lu, %p, %lu, %p, %p)\n", Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
425
426 // Check for invalid levels here for early error return. MSDN says that only 1, 2, 4, and 5 are allowable.
427 if (Level != 1 && Level != 2 && Level != 4 && Level != 5)
428 {
429 SetLastError(ERROR_INVALID_LEVEL);
430 ERR("Invalid Level!\n");
431 goto Cleanup;
432 }
433
434 if (Name)
435 {
436 // Convert pName to a Unicode string pwszName.
437 cch = strlen(Name);
438
439 pwszName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
440 if (!pwszName)
441 {
442 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
443 ERR("HeapAlloc failed!\n");
444 goto Cleanup;
445 }
446
447 MultiByteToWideChar(CP_ACP, 0, Name, -1, pwszName, cch + 1);
448 }
449
450 /* Ref: https://stackoverflow.com/questions/41147180/why-enumprintersa-and-enumprintersw-request-the-same-amount-of-memory */
451 bReturnValue = EnumPrintersW(Flags, pwszName, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
452 HeapFree(hProcessHeap, 0, pwszName);
453
454 TRACE("*pcReturned is '%d' and bReturnValue is '%d' and GetLastError is '%ld'.\n", *pcReturned, bReturnValue, GetLastError());
455
456 /* We are mapping multiple different pointers to the same pPrinterEnum pointer here so that */
457 /* we can do in-place conversion. We read the Unicode response from the EnumPrintersW and */
458 /* then we write back the ANSI conversion into the same buffer for our EnumPrintersA output */
459
460 /* mapping to pPrinterEnum for Unicode (w) characters for Levels 1, 2, 4, and 5 */
461 ppi1w = (PPRINTER_INFO_1W)pPrinterEnum;
462 ppi2w = (PPRINTER_INFO_2W)pPrinterEnum;
463 ppi4w = (PPRINTER_INFO_4W)pPrinterEnum;
464 ppi5w = (PPRINTER_INFO_5W)pPrinterEnum;
465 /* mapping to pPrinterEnum for ANSI (a) characters for Levels 1, 2, 4, and 5 */
466 ppi1a = (PPRINTER_INFO_1A)pPrinterEnum;
467 ppi2a = (PPRINTER_INFO_2A)pPrinterEnum;
468 ppi4a = (PPRINTER_INFO_4A)pPrinterEnum;
469 ppi5a = (PPRINTER_INFO_5A)pPrinterEnum;
470
471 for (i = 0; i < *pcReturned; i++)
472 {
473 switch (Level)
474 {
475 case 1:
476 {
477 if (ppi1w[i].pDescription)
478 {
479 // Convert Unicode pDescription to a ANSI string pszDescription.
480 cch = wcslen(ppi1w[i].pDescription);
481
482 pszDescription = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
483 if (!pszDescription)
484 {
485 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
486 ERR("HeapAlloc failed!\n");
487 goto Cleanup;
488 }
489
490 WideCharToMultiByte(CP_ACP, 0, ppi1w[i].pDescription, -1, pszDescription, cch + 1, NULL, NULL);
491 StringCchCopyA(ppi1a[i].pDescription, cch + 1, pszDescription);
492
493 HeapFree(hProcessHeap, 0, pszDescription);
494 }
495
496 if (ppi1w[i].pName)
497 {
498 // Convert Unicode pName to a ANSI string pszName.
499 cch = wcslen(ppi1w[i].pName);
500
501 pszName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
502 if (!pszName)
503 {
504 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
505 ERR("HeapAlloc failed!\n");
506 goto Cleanup;
507 }
508
509 WideCharToMultiByte(CP_ACP, 0, ppi1w[i].pName, -1, pszName, cch + 1, NULL, NULL);
510 StringCchCopyA(ppi1a[i].pName, cch + 1, pszName);
511
512 HeapFree(hProcessHeap, 0, pszName);
513 }
514
515 if (ppi1w[i].pComment)
516 {
517 // Convert Unicode pComment to a ANSI string pszComment.
518 cch = wcslen(ppi1w[i].pComment);
519
520 pszComment = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
521 if (!pszComment)
522 {
523 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
524 ERR("HeapAlloc failed!\n");
525 goto Cleanup;
526 }
527
528 WideCharToMultiByte(CP_ACP, 0, ppi1w[i].pComment, -1, pszComment, cch + 1, NULL, NULL);
529 StringCchCopyA(ppi1a[i].pComment, cch + 1, pszComment);
530
531 HeapFree(hProcessHeap, 0, pszComment);
532 }
533 break;
534 }
535
536
537 case 2:
538 {
539 if (ppi2w[i].pServerName)
540 {
541 // Convert Unicode pServerName to a ANSI string pszServerName.
542 cch = wcslen(ppi2w[i].pServerName);
543
544 pszServerName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
545 if (!pszServerName)
546 {
547 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
548 ERR("HeapAlloc failed!\n");
549 goto Cleanup;
550 }
551
552 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pServerName, -1, pszServerName, cch + 1, NULL, NULL);
553 StringCchCopyA(ppi2a[i].pServerName, cch + 1, pszServerName);
554
555 HeapFree(hProcessHeap, 0, pszServerName);
556 }
557
558 if (ppi2w[i].pPrinterName)
559 {
560 // Convert Unicode pPrinterName to a ANSI string pszPrinterName.
561 cch = wcslen(ppi2w[i].pPrinterName);
562
563 pszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
564 if (!pszPrinterName)
565 {
566 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
567 ERR("HeapAlloc failed!\n");
568 goto Cleanup;
569 }
570
571 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pPrinterName, -1, pszPrinterName, cch + 1, NULL, NULL);
572 StringCchCopyA(ppi2a[i].pPrinterName, cch + 1, pszPrinterName);
573
574 HeapFree(hProcessHeap, 0, pszPrinterName);
575 }
576
577 if (ppi2w[i].pShareName)
578 {
579 // Convert Unicode pShareName to a ANSI string pszShareName.
580 cch = wcslen(ppi2w[i].pShareName);
581
582 pszShareName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
583 if (!pszShareName)
584 {
585 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
586 ERR("HeapAlloc failed!\n");
587 goto Cleanup;
588 }
589
590 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pShareName, -1, pszShareName, cch + 1, NULL, NULL);
591 StringCchCopyA(ppi2a[i].pShareName, cch + 1, pszShareName);
592
593 HeapFree(hProcessHeap, 0, pszShareName);
594 }
595
596 if (ppi2w[i].pPortName)
597 {
598 // Convert Unicode pPortName to a ANSI string pszPortName.
599 cch = wcslen(ppi2w[i].pPortName);
600
601 pszPortName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
602 if (!pszPortName)
603 {
604 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
605 ERR("HeapAlloc failed!\n");
606 goto Cleanup;
607 }
608
609 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pPortName, -1, pszPortName, cch + 1, NULL, NULL);
610 StringCchCopyA(ppi2a[i].pPortName, cch + 1, pszPortName);
611
612 HeapFree(hProcessHeap, 0, pszPortName);
613 }
614
615 if (ppi2w[i].pDriverName)
616 {
617 // Convert Unicode pDriverName to a ANSI string pszDriverName.
618 cch = wcslen(ppi2w[i].pDriverName);
619
620 pszDriverName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
621 if (!pszDriverName)
622 {
623 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
624 ERR("HeapAlloc failed!\n");
625 goto Cleanup;
626 }
627
628 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pDriverName, -1, pszDriverName, cch + 1, NULL, NULL);
629 StringCchCopyA(ppi2a[i].pDriverName, cch + 1, pszDriverName);
630
631 HeapFree(hProcessHeap, 0, pszDriverName);
632 }
633
634 if (ppi2w[i].pComment)
635 {
636 // Convert Unicode pComment to a ANSI string pszComment.
637 cch = wcslen(ppi2w[i].pComment);
638
639 pszComment = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
640 if (!pszComment)
641 {
642 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
643 ERR("HeapAlloc failed!\n");
644 goto Cleanup;
645 }
646
647 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pComment, -1, pszComment, cch + 1, NULL, NULL);
648 StringCchCopyA(ppi2a[i].pComment, cch + 1, pszComment);
649
650 HeapFree(hProcessHeap, 0, pszComment);
651 }
652
653 if (ppi2w[i].pLocation)
654 {
655 // Convert Unicode pLocation to a ANSI string pszLocation.
656 cch = wcslen(ppi2w[i].pLocation);
657
658 pszLocation = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
659 if (!pszLocation)
660 {
661 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
662 ERR("HeapAlloc failed!\n");
663 goto Cleanup;
664 }
665
666 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pLocation, -1, pszLocation, cch + 1, NULL, NULL);
667 StringCchCopyA(ppi2a[i].pLocation, cch + 1, pszLocation);
668
669 HeapFree(hProcessHeap, 0, pszLocation);
670 }
671
672
673 if (ppi2w[i].pSepFile)
674 {
675 // Convert Unicode pSepFile to a ANSI string pszSepFile.
676 cch = wcslen(ppi2w[i].pSepFile);
677
678 pszSepFile = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
679 if (!pszSepFile)
680 {
681 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
682 ERR("HeapAlloc failed!\n");
683 goto Cleanup;
684 }
685
686 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pSepFile, -1, pszSepFile, cch + 1, NULL, NULL);
687 StringCchCopyA(ppi2a[i].pSepFile, cch + 1, pszSepFile);
688
689 HeapFree(hProcessHeap, 0, pszSepFile);
690 }
691
692 if (ppi2w[i].pPrintProcessor)
693 {
694 // Convert Unicode pPrintProcessor to a ANSI string pszPrintProcessor.
695 cch = wcslen(ppi2w[i].pPrintProcessor);
696
697 pszPrintProcessor = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
698 if (!pszPrintProcessor)
699 {
700 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
701 ERR("HeapAlloc failed!\n");
702 goto Cleanup;
703 }
704
705 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pPrintProcessor, -1, pszPrintProcessor, cch + 1, NULL, NULL);
706 StringCchCopyA(ppi2a[i].pPrintProcessor, cch + 1, pszPrintProcessor);
707
708 HeapFree(hProcessHeap, 0, pszPrintProcessor);
709 }
710
711
712 if (ppi2w[i].pDatatype)
713 {
714 // Convert Unicode pDatatype to a ANSI string pszDatatype.
715 cch = wcslen(ppi2w[i].pDatatype);
716
717 pszDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
718 if (!pszDatatype)
719 {
720 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
721 ERR("HeapAlloc failed!\n");
722 goto Cleanup;
723 }
724
725 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pDatatype, -1, pszDatatype, cch + 1, NULL, NULL);
726 StringCchCopyA(ppi2a[i].pDatatype, cch + 1, pszDatatype);
727
728 HeapFree(hProcessHeap, 0, pszDatatype);
729 }
730
731 if (ppi2w[i].pParameters)
732 {
733 // Convert Unicode pParameters to a ANSI string pszParameters.
734 cch = wcslen(ppi2w[i].pParameters);
735
736 pszParameters = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
737 if (!pszParameters)
738 {
739 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
740 ERR("HeapAlloc failed!\n");
741 goto Cleanup;
742 }
743
744 WideCharToMultiByte(CP_ACP, 0, ppi2w[i].pParameters, -1, pszParameters, cch + 1, NULL, NULL);
745 StringCchCopyA(ppi2a[i].pParameters, cch + 1, pszParameters);
746
747 HeapFree(hProcessHeap, 0, pszParameters);
748 }
749 break;
750
751 }
752
753 case 4:
754 {
755 if (ppi4w[i].pPrinterName)
756 {
757 // Convert Unicode pPrinterName to a ANSI string pszPrinterName.
758 cch = wcslen(ppi4w[i].pPrinterName);
759
760 pszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
761 if (!pszPrinterName)
762 {
763 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
764 ERR("HeapAlloc failed!\n");
765 goto Cleanup;
766 }
767
768 WideCharToMultiByte(CP_ACP, 0, ppi4w[i].pPrinterName, -1, pszPrinterName, cch + 1, NULL, NULL);
769 StringCchCopyA(ppi4a[i].pPrinterName, cch + 1, pszPrinterName);
770
771 HeapFree(hProcessHeap, 0, pszPrinterName);
772 }
773
774 if (ppi4w[i].pServerName)
775 {
776 // Convert Unicode pServerName to a ANSI string pszServerName.
777 cch = wcslen(ppi4w[i].pServerName);
778
779 pszServerName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
780 if (!pszServerName)
781 {
782 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
783 ERR("HeapAlloc failed!\n");
784 goto Cleanup;
785 }
786
787 WideCharToMultiByte(CP_ACP, 0, ppi4w[i].pServerName, -1, pszServerName, cch + 1, NULL, NULL);
788 StringCchCopyA(ppi4a[i].pServerName, cch + 1, pszServerName);
789
790 HeapFree(hProcessHeap, 0, pszServerName);
791 }
792 break;
793 }
794
795 case 5:
796 {
797 if (ppi5w[i].pPrinterName)
798 {
799 // Convert Unicode pPrinterName to a ANSI string pszPrinterName.
800 cch = wcslen(ppi5w[i].pPrinterName);
801
802 pszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
803 if (!pszPrinterName)
804 {
805 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
806 ERR("HeapAlloc failed!\n");
807 goto Cleanup;
808 }
809
810 WideCharToMultiByte(CP_ACP, 0, ppi5w[i].pPrinterName, -1, pszPrinterName, cch + 1, NULL, NULL);
811 StringCchCopyA(ppi5a[i].pPrinterName, cch + 1, pszPrinterName);
812
813 HeapFree(hProcessHeap, 0, pszPrinterName);
814 }
815
816 if (ppi5w[i].pPortName)
817 {
818 // Convert Unicode pPortName to a ANSI string pszPortName.
819 cch = wcslen(ppi5w[i].pPortName);
820
821 pszPortName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(CHAR));
822 if (!pszPortName)
823 {
824 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
825 ERR("HeapAlloc failed!\n");
826 goto Cleanup;
827 }
828
829 WideCharToMultiByte(CP_ACP, 0, ppi5w[i].pPortName, -1, pszPortName, cch + 1, NULL, NULL);
830 StringCchCopyA(ppi5a[i].pPortName, cch + 1, pszPortName);
831
832 HeapFree(hProcessHeap, 0, pszPortName);
833 }
834 break;
835 }
836
837 } // switch
838 } // for
839
840 Cleanup:
841
842 return bReturnValue;
843 }
844
845 BOOL WINAPI
846 EnumPrintersW(DWORD Flags, PWSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
847 {
848 DWORD dwErrorCode;
849
850 TRACE("EnumPrintersW(%lu, %S, %lu, %p, %lu, %p, %p)\n", Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
851
852 // Dismiss invalid levels already at this point.
853 if (Level == 3 || Level > 5)
854 {
855 dwErrorCode = ERROR_INVALID_LEVEL;
856 goto Cleanup;
857 }
858
859 if (cbBuf && pPrinterEnum)
860 ZeroMemory(pPrinterEnum, cbBuf);
861
862 // Do the RPC call
863 RpcTryExcept
864 {
865 dwErrorCode = _RpcEnumPrinters(Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
866 }
867 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
868 {
869 dwErrorCode = RpcExceptionCode();
870 ERR("_RpcEnumPrinters failed with exception code %lu!\n", dwErrorCode);
871 }
872 RpcEndExcept;
873
874 if (dwErrorCode == ERROR_SUCCESS)
875 {
876 // Replace relative offset addresses in the output by absolute pointers.
877 ASSERT(Level <= 9);
878 MarshallUpStructuresArray(cbBuf, pPrinterEnum, *pcReturned, pPrinterInfoMarshalling[Level]->pInfo, pPrinterInfoMarshalling[Level]->cbStructureSize, TRUE);
879 }
880
881 Cleanup:
882 SetLastError(dwErrorCode);
883 return (dwErrorCode == ERROR_SUCCESS);
884 }
885
886 BOOL WINAPI
887 FlushPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pcWritten, DWORD cSleep)
888 {
889 TRACE("FlushPrinter(%p, %p, %lu, %p, %lu)\n", hPrinter, pBuf, cbBuf, pcWritten, cSleep);
890 UNIMPLEMENTED;
891 return FALSE;
892 }
893
894 BOOL WINAPI
895 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer)
896 {
897 DWORD dwErrorCode;
898 PWSTR pwszBuffer = NULL;
899
900 TRACE("GetDefaultPrinterA(%p, %p)\n", pszBuffer, pcchBuffer);
901
902 // Sanity check.
903 if (!pcchBuffer)
904 {
905 dwErrorCode = ERROR_INVALID_PARAMETER;
906 goto Cleanup;
907 }
908
909 // Check if an ANSI buffer was given and if so, allocate a Unicode buffer of the same size.
910 if (pszBuffer && *pcchBuffer)
911 {
912 pwszBuffer = HeapAlloc(hProcessHeap, 0, *pcchBuffer * sizeof(WCHAR));
913 if (!pwszBuffer)
914 {
915 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
916 ERR("HeapAlloc failed!\n");
917 goto Cleanup;
918 }
919 }
920
921 if (!GetDefaultPrinterW(pwszBuffer, pcchBuffer))
922 {
923 dwErrorCode = GetLastError();
924 goto Cleanup;
925 }
926
927 // We successfully got a string in pwszBuffer, so convert the Unicode string to ANSI.
928 WideCharToMultiByte(CP_ACP, 0, pwszBuffer, -1, pszBuffer, *pcchBuffer, NULL, NULL);
929
930 dwErrorCode = ERROR_SUCCESS;
931
932 Cleanup:
933 if (pwszBuffer)
934 HeapFree(hProcessHeap, 0, pwszBuffer);
935
936 SetLastError(dwErrorCode);
937 return (dwErrorCode == ERROR_SUCCESS);
938 }
939
940 BOOL WINAPI
941 GetDefaultPrinterW(LPWSTR pszBuffer, LPDWORD pcchBuffer)
942 {
943 DWORD cbNeeded;
944 DWORD cchInputBuffer;
945 DWORD dwErrorCode;
946 HKEY hWindowsKey = NULL;
947 PWSTR pwszDevice = NULL;
948 PWSTR pwszComma;
949
950 TRACE("GetDefaultPrinterW(%p, %p)\n", pszBuffer, pcchBuffer);
951
952 // Sanity check.
953 if (!pcchBuffer)
954 {
955 dwErrorCode = ERROR_INVALID_PARAMETER;
956 goto Cleanup;
957 }
958
959 cchInputBuffer = *pcchBuffer;
960
961 // Open the registry key where the default printer for the current user is stored.
962 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_READ, &hWindowsKey);
963 if (dwErrorCode != ERROR_SUCCESS)
964 {
965 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
966 goto Cleanup;
967 }
968
969 // Determine the size of the required buffer.
970 dwErrorCode = (DWORD)RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, NULL, &cbNeeded);
971 if (dwErrorCode != ERROR_SUCCESS)
972 {
973 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
974 goto Cleanup;
975 }
976
977 // Allocate it.
978 pwszDevice = HeapAlloc(hProcessHeap, 0, cbNeeded);
979 if (!pwszDevice)
980 {
981 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
982 ERR("HeapAlloc failed!\n");
983 goto Cleanup;
984 }
985
986 // Now get the actual value.
987 dwErrorCode = RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, (PBYTE)pwszDevice, &cbNeeded);
988 if (dwErrorCode != ERROR_SUCCESS)
989 {
990 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
991 goto Cleanup;
992 }
993
994 // We get a string "<Printer Name>,winspool,<Port>:".
995 // Extract the printer name from it.
996 pwszComma = wcschr(pwszDevice, L',');
997 if (!pwszComma)
998 {
999 ERR("Found no or invalid default printer: %S!\n", pwszDevice);
1000 dwErrorCode = ERROR_INVALID_NAME;
1001 goto Cleanup;
1002 }
1003
1004 // Store the length of the Printer Name (including the terminating NUL character!) in *pcchBuffer.
1005 *pcchBuffer = pwszComma - pwszDevice + 1;
1006
1007 // Check if the supplied buffer is large enough.
1008 if (cchInputBuffer < *pcchBuffer)
1009 {
1010 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
1011 goto Cleanup;
1012 }
1013
1014 // Copy the default printer.
1015 *pwszComma = 0;
1016 CopyMemory(pszBuffer, pwszDevice, *pcchBuffer * sizeof(WCHAR));
1017
1018 dwErrorCode = ERROR_SUCCESS;
1019
1020 Cleanup:
1021 if (hWindowsKey)
1022 RegCloseKey(hWindowsKey);
1023
1024 if (pwszDevice)
1025 HeapFree(hProcessHeap, 0, pwszDevice);
1026
1027 SetLastError(dwErrorCode);
1028 return (dwErrorCode == ERROR_SUCCESS);
1029 }
1030
1031 BOOL WINAPI
1032 GetPrinterA(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
1033 {
1034 TRACE("GetPrinterA(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pPrinter, cbBuf, pcbNeeded);
1035 if(pcbNeeded) *pcbNeeded = 0;
1036 return FALSE;
1037 }
1038
1039 BOOL WINAPI
1040 GetPrinterDriverA(HANDLE hPrinter, LPSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
1041 {
1042 TRACE("GetPrinterDriverA(%p, %s, %lu, %p, %lu, %p)\n", hPrinter, pEnvironment, Level, pDriverInfo, cbBuf, pcbNeeded);
1043 if(pcbNeeded) *pcbNeeded = 0;
1044 return FALSE;
1045 }
1046
1047 BOOL WINAPI
1048 GetPrinterDriverW(HANDLE hPrinter, LPWSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
1049 {
1050 DWORD dwErrorCode;
1051 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1052
1053 ERR("GetPrinterDriverW(%p, %S, %lu, %p, %lu, %p)\n", hPrinter, pEnvironment, Level, pDriverInfo, cbBuf, pcbNeeded);
1054
1055 // Sanity checks.
1056 if (!pHandle)
1057 {
1058 dwErrorCode = ERROR_INVALID_HANDLE;
1059 goto Cleanup;
1060 }
1061
1062 // Dismiss invalid levels already at this point.
1063 if (Level > 8 || Level < 1)
1064 {
1065 dwErrorCode = ERROR_INVALID_LEVEL;
1066 goto Cleanup;
1067 }
1068
1069 if (cbBuf && pDriverInfo)
1070 ZeroMemory(pDriverInfo, cbBuf);
1071
1072 // Do the RPC call
1073 RpcTryExcept
1074 {
1075 dwErrorCode = _RpcGetPrinterDriver(pHandle->hPrinter, pEnvironment, Level, pDriverInfo, cbBuf, pcbNeeded);
1076 }
1077 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1078 {
1079 dwErrorCode = RpcExceptionCode();
1080 ERR("_RpcGetPrinter failed with exception code %lu!\n", dwErrorCode);
1081 }
1082 RpcEndExcept;
1083
1084 if (dwErrorCode == ERROR_SUCCESS)
1085 {
1086 // Replace relative offset addresses in the output by absolute pointers.
1087 ASSERT(Level <= 5);
1088 MarshallUpStructure(cbBuf, pDriverInfo, pPrinterDriverMarshalling[Level]->pInfo, pPrinterDriverMarshalling[Level]->cbStructureSize, TRUE);
1089 }
1090
1091 Cleanup:
1092 SetLastError(dwErrorCode);
1093 return (dwErrorCode == ERROR_SUCCESS);
1094 }
1095
1096 BOOL WINAPI
1097 GetPrinterW(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
1098 {
1099 DWORD dwErrorCode;
1100 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1101
1102 TRACE("GetPrinterW(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pPrinter, cbBuf, pcbNeeded);
1103
1104 // Sanity checks.
1105 if (!pHandle)
1106 {
1107 dwErrorCode = ERROR_INVALID_HANDLE;
1108 goto Cleanup;
1109 }
1110
1111 // Dismiss invalid levels already at this point.
1112 if (Level > 9)
1113 {
1114 dwErrorCode = ERROR_INVALID_LEVEL;
1115 goto Cleanup;
1116 }
1117
1118 if (cbBuf && pPrinter)
1119 ZeroMemory(pPrinter, cbBuf);
1120
1121 // Do the RPC call
1122 RpcTryExcept
1123 {
1124 dwErrorCode = _RpcGetPrinter(pHandle->hPrinter, Level, pPrinter, cbBuf, pcbNeeded);
1125 }
1126 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1127 {
1128 dwErrorCode = RpcExceptionCode();
1129 ERR("_RpcGetPrinter failed with exception code %lu!\n", dwErrorCode);
1130 }
1131 RpcEndExcept;
1132
1133 if (dwErrorCode == ERROR_SUCCESS)
1134 {
1135 // Replace relative offset addresses in the output by absolute pointers.
1136 ASSERT(Level <= 9);
1137 MarshallUpStructure(cbBuf, pPrinter, pPrinterInfoMarshalling[Level]->pInfo, pPrinterInfoMarshalling[Level]->cbStructureSize, TRUE);
1138 }
1139
1140 Cleanup:
1141 SetLastError(dwErrorCode);
1142 return (dwErrorCode == ERROR_SUCCESS);
1143 }
1144
1145 BOOL WINAPI
1146 OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefault)
1147 {
1148 BOOL bReturnValue = FALSE;
1149 DWORD cch;
1150 PWSTR pwszPrinterName = NULL;
1151 PRINTER_DEFAULTSW wDefault = { 0 };
1152
1153 TRACE("OpenPrinterA(%s, %p, %p)\n", pPrinterName, phPrinter, pDefault);
1154
1155 if (pPrinterName)
1156 {
1157 // Convert pPrinterName to a Unicode string pwszPrinterName
1158 cch = strlen(pPrinterName);
1159
1160 pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1161 if (!pwszPrinterName)
1162 {
1163 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1164 ERR("HeapAlloc failed!\n");
1165 goto Cleanup;
1166 }
1167
1168 MultiByteToWideChar(CP_ACP, 0, pPrinterName, -1, pwszPrinterName, cch + 1);
1169 }
1170
1171 if (pDefault)
1172 {
1173 wDefault.DesiredAccess = pDefault->DesiredAccess;
1174
1175 if (pDefault->pDatatype)
1176 {
1177 // Convert pDefault->pDatatype to a Unicode string wDefault.pDatatype
1178 cch = strlen(pDefault->pDatatype);
1179
1180 wDefault.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1181 if (!wDefault.pDatatype)
1182 {
1183 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1184 ERR("HeapAlloc failed!\n");
1185 goto Cleanup;
1186 }
1187
1188 MultiByteToWideChar(CP_ACP, 0, pDefault->pDatatype, -1, wDefault.pDatatype, cch + 1);
1189 }
1190
1191 if (pDefault->pDevMode)
1192 wDefault.pDevMode = GdiConvertToDevmodeW(pDefault->pDevMode);
1193 }
1194
1195 bReturnValue = OpenPrinterW(pwszPrinterName, phPrinter, &wDefault);
1196
1197 Cleanup:
1198 if (wDefault.pDatatype)
1199 HeapFree(hProcessHeap, 0, wDefault.pDatatype);
1200
1201 if (wDefault.pDevMode)
1202 HeapFree(hProcessHeap, 0, wDefault.pDevMode);
1203
1204 if (pwszPrinterName)
1205 HeapFree(hProcessHeap, 0, pwszPrinterName);
1206
1207 return bReturnValue;
1208 }
1209
1210 BOOL WINAPI
1211 OpenPrinterW(LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSW pDefault)
1212 {
1213 DWORD dwErrorCode;
1214 HANDLE hPrinter;
1215 PSPOOLER_HANDLE pHandle;
1216 PWSTR pDatatype = NULL;
1217 WINSPOOL_DEVMODE_CONTAINER DevModeContainer = { 0 };
1218 ACCESS_MASK AccessRequired = 0;
1219
1220 TRACE("OpenPrinterW(%S, %p, %p)\n", pPrinterName, phPrinter, pDefault);
1221
1222 // Sanity check
1223 if (!phPrinter)
1224 {
1225 dwErrorCode = ERROR_INVALID_PARAMETER;
1226 goto Cleanup;
1227 }
1228
1229 // Prepare the additional parameters in the format required by _RpcOpenPrinter
1230 if (pDefault)
1231 {
1232 pDatatype = pDefault->pDatatype;
1233 DevModeContainer.cbBuf = sizeof(DEVMODEW);
1234 DevModeContainer.pDevMode = (BYTE*)pDefault->pDevMode;
1235 AccessRequired = pDefault->DesiredAccess;
1236 }
1237
1238 // Do the RPC call
1239 RpcTryExcept
1240 {
1241 dwErrorCode = _RpcOpenPrinter(pPrinterName, &hPrinter, pDatatype, &DevModeContainer, AccessRequired);
1242 }
1243 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1244 {
1245 dwErrorCode = RpcExceptionCode();
1246 ERR("_RpcOpenPrinter failed with exception code %lu!\n", dwErrorCode);
1247 }
1248 RpcEndExcept;
1249
1250 if (dwErrorCode == ERROR_SUCCESS)
1251 {
1252 // Create a new SPOOLER_HANDLE structure.
1253 pHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(SPOOLER_HANDLE));
1254 if (!pHandle)
1255 {
1256 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1257 ERR("HeapAlloc failed!\n");
1258 goto Cleanup;
1259 }
1260
1261 pHandle->hPrinter = hPrinter;
1262 pHandle->hSPLFile = INVALID_HANDLE_VALUE;
1263
1264 // Return it as phPrinter.
1265 *phPrinter = (HANDLE)pHandle;
1266 }
1267
1268 Cleanup:
1269 SetLastError(dwErrorCode);
1270 return (dwErrorCode == ERROR_SUCCESS);
1271 }
1272
1273 BOOL WINAPI
1274 ReadPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pNoBytesRead)
1275 {
1276 DWORD dwErrorCode;
1277 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1278
1279 TRACE("ReadPrinter(%p, %p, %lu, %p)\n", hPrinter, pBuf, cbBuf, pNoBytesRead);
1280
1281 // Sanity checks.
1282 if (!pHandle)
1283 {
1284 dwErrorCode = ERROR_INVALID_HANDLE;
1285 goto Cleanup;
1286 }
1287
1288 // Do the RPC call
1289 RpcTryExcept
1290 {
1291 dwErrorCode = _RpcReadPrinter(pHandle->hPrinter, pBuf, cbBuf, pNoBytesRead);
1292 }
1293 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1294 {
1295 dwErrorCode = RpcExceptionCode();
1296 ERR("_RpcReadPrinter failed with exception code %lu!\n", dwErrorCode);
1297 }
1298 RpcEndExcept;
1299
1300 Cleanup:
1301 SetLastError(dwErrorCode);
1302 return (dwErrorCode == ERROR_SUCCESS);
1303 }
1304
1305 BOOL WINAPI
1306 ResetPrinterA(HANDLE hPrinter, PPRINTER_DEFAULTSA pDefault)
1307 {
1308 TRACE("ResetPrinterA(%p, %p)\n", hPrinter, pDefault);
1309 UNIMPLEMENTED;
1310 return FALSE;
1311 }
1312
1313 BOOL WINAPI
1314 ResetPrinterW(HANDLE hPrinter, PPRINTER_DEFAULTSW pDefault)
1315 {
1316 TRACE("ResetPrinterW(%p, %p)\n", hPrinter, pDefault);
1317 UNIMPLEMENTED;
1318 return FALSE;
1319 }
1320
1321 BOOL WINAPI
1322 SetDefaultPrinterA(LPCSTR pszPrinter)
1323 {
1324 BOOL bReturnValue = FALSE;
1325 DWORD cch;
1326 PWSTR pwszPrinter = NULL;
1327
1328 TRACE("SetDefaultPrinterA(%s)\n", pszPrinter);
1329
1330 if (pszPrinter)
1331 {
1332 // Convert pszPrinter to a Unicode string pwszPrinter
1333 cch = strlen(pszPrinter);
1334
1335 pwszPrinter = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1336 if (!pwszPrinter)
1337 {
1338 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1339 ERR("HeapAlloc failed!\n");
1340 goto Cleanup;
1341 }
1342
1343 MultiByteToWideChar(CP_ACP, 0, pszPrinter, -1, pwszPrinter, cch + 1);
1344 }
1345
1346 bReturnValue = SetDefaultPrinterW(pwszPrinter);
1347
1348 Cleanup:
1349 if (pwszPrinter)
1350 HeapFree(hProcessHeap, 0, pwszPrinter);
1351
1352 return bReturnValue;
1353 }
1354
1355 BOOL WINAPI
1356 SetDefaultPrinterW(LPCWSTR pszPrinter)
1357 {
1358 const WCHAR wszDevicesKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Devices";
1359
1360 DWORD cbDeviceValueData;
1361 DWORD cbPrinterValueData = 0;
1362 DWORD cchPrinter;
1363 DWORD dwErrorCode;
1364 HKEY hDevicesKey = NULL;
1365 HKEY hWindowsKey = NULL;
1366 PWSTR pwszDeviceValueData = NULL;
1367 WCHAR wszPrinter[MAX_PRINTER_NAME + 1];
1368
1369 TRACE("SetDefaultPrinterW(%S)\n", pszPrinter);
1370
1371 // Open the Devices registry key.
1372 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszDevicesKey, 0, KEY_READ, &hDevicesKey);
1373 if (dwErrorCode != ERROR_SUCCESS)
1374 {
1375 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
1376 goto Cleanup;
1377 }
1378
1379 // Did the caller give us a printer to set as default?
1380 if (pszPrinter && *pszPrinter)
1381 {
1382 // Check if the given printer exists and query the value data size.
1383 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, NULL, &cbPrinterValueData);
1384 if (dwErrorCode == ERROR_FILE_NOT_FOUND)
1385 {
1386 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
1387 goto Cleanup;
1388 }
1389 else if (dwErrorCode != ERROR_SUCCESS)
1390 {
1391 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
1392 goto Cleanup;
1393 }
1394
1395 cchPrinter = wcslen(pszPrinter);
1396 }
1397 else
1398 {
1399 // If there is already a default printer, we're done!
1400 cchPrinter = _countof(wszPrinter);
1401 if (GetDefaultPrinterW(wszPrinter, &cchPrinter))
1402 {
1403 dwErrorCode = ERROR_SUCCESS;
1404 goto Cleanup;
1405 }
1406
1407 // Otherwise, get us the first printer from the "Devices" key to later set it as default and query the value data size.
1408 cchPrinter = _countof(wszPrinter);
1409 dwErrorCode = (DWORD)RegEnumValueW(hDevicesKey, 0, wszPrinter, &cchPrinter, NULL, NULL, NULL, &cbPrinterValueData);
1410 if (dwErrorCode != ERROR_MORE_DATA)
1411 goto Cleanup;
1412
1413 pszPrinter = wszPrinter;
1414 }
1415
1416 // We now need to query the value data, which has the format "winspool,<Port>:"
1417 // and make "<Printer Name>,winspool,<Port>:" out of it.
1418 // Allocate a buffer large enough for the final data.
1419 cbDeviceValueData = (cchPrinter + 1) * sizeof(WCHAR) + cbPrinterValueData;
1420 pwszDeviceValueData = HeapAlloc(hProcessHeap, 0, cbDeviceValueData);
1421 if (!pwszDeviceValueData)
1422 {
1423 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1424 ERR("HeapAlloc failed!\n");
1425 goto Cleanup;
1426 }
1427
1428 // Copy the Printer Name and a comma into it.
1429 CopyMemory(pwszDeviceValueData, pszPrinter, cchPrinter * sizeof(WCHAR));
1430 pwszDeviceValueData[cchPrinter] = L',';
1431
1432 // Append the value data, which has the format "winspool,<Port>:"
1433 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, (PBYTE)&pwszDeviceValueData[cchPrinter + 1], &cbPrinterValueData);
1434 if (dwErrorCode != ERROR_SUCCESS)
1435 goto Cleanup;
1436
1437 // Open the Windows registry key.
1438 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_SET_VALUE, &hWindowsKey);
1439 if (dwErrorCode != ERROR_SUCCESS)
1440 {
1441 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
1442 goto Cleanup;
1443 }
1444
1445 // Store our new default printer.
1446 dwErrorCode = (DWORD)RegSetValueExW(hWindowsKey, wszDeviceValue, 0, REG_SZ, (PBYTE)pwszDeviceValueData, cbDeviceValueData);
1447 if (dwErrorCode != ERROR_SUCCESS)
1448 {
1449 ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode);
1450 goto Cleanup;
1451 }
1452
1453 Cleanup:
1454 if (hDevicesKey)
1455 RegCloseKey(hDevicesKey);
1456
1457 if (hWindowsKey)
1458 RegCloseKey(hWindowsKey);
1459
1460 if (pwszDeviceValueData)
1461 HeapFree(hProcessHeap, 0, pwszDeviceValueData);
1462
1463 SetLastError(dwErrorCode);
1464 return (dwErrorCode == ERROR_SUCCESS);
1465 }
1466
1467 BOOL WINAPI
1468 SetPrinterA(HANDLE hPrinter, DWORD Level, PBYTE pPrinter, DWORD Command)
1469 {
1470 TRACE("SetPrinterA(%p, %lu, %p, %lu)\n", hPrinter, Level, pPrinter, Command);
1471 UNIMPLEMENTED;
1472 return FALSE;
1473 }
1474
1475 BOOL WINAPI
1476 SetPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pPrinter, DWORD Command)
1477 {
1478 TRACE("SetPrinterW(%p, %lu, %p, %lu)\n", hPrinter, Level, pPrinter, Command);
1479 UNIMPLEMENTED;
1480 return FALSE;
1481 }
1482
1483 BOOL WINAPI
1484 SplDriverUnloadComplete(LPWSTR pDriverFile)
1485 {
1486 ERR("DriverUnloadComplete(%S)\n", pDriverFile);
1487 UNIMPLEMENTED;
1488 return TRUE; // return true for now.
1489 }
1490
1491 DWORD WINAPI
1492 StartDocPrinterA(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
1493 {
1494 DOC_INFO_1W wDocInfo1 = { 0 };
1495 DWORD cch;
1496 DWORD dwErrorCode;
1497 DWORD dwReturnValue = 0;
1498 PDOC_INFO_1A pDocInfo1 = (PDOC_INFO_1A)pDocInfo;
1499
1500 TRACE("StartDocPrinterA(%p, %lu, %p)\n", hPrinter, Level, pDocInfo);
1501
1502 // Only check the minimum required for accessing pDocInfo.
1503 // Additional sanity checks are done in StartDocPrinterW.
1504 if (!pDocInfo1)
1505 {
1506 dwErrorCode = ERROR_INVALID_PARAMETER;
1507 goto Cleanup;
1508 }
1509
1510 if (Level != 1)
1511 {
1512 dwErrorCode = ERROR_INVALID_LEVEL;
1513 goto Cleanup;
1514 }
1515
1516 if (pDocInfo1->pDatatype)
1517 {
1518 // Convert pDocInfo1->pDatatype to a Unicode string wDocInfo1.pDatatype
1519 cch = strlen(pDocInfo1->pDatatype);
1520
1521 wDocInfo1.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1522 if (!wDocInfo1.pDatatype)
1523 {
1524 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1525 ERR("HeapAlloc failed!\n");
1526 goto Cleanup;
1527 }
1528
1529 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDatatype, -1, wDocInfo1.pDatatype, cch + 1);
1530 }
1531
1532 if (pDocInfo1->pDocName)
1533 {
1534 // Convert pDocInfo1->pDocName to a Unicode string wDocInfo1.pDocName
1535 cch = strlen(pDocInfo1->pDocName);
1536
1537 wDocInfo1.pDocName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1538 if (!wDocInfo1.pDocName)
1539 {
1540 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1541 ERR("HeapAlloc failed!\n");
1542 goto Cleanup;
1543 }
1544
1545 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDocName, -1, wDocInfo1.pDocName, cch + 1);
1546 }
1547
1548 if (pDocInfo1->pOutputFile)
1549 {
1550 // Convert pDocInfo1->pOutputFile to a Unicode string wDocInfo1.pOutputFile
1551 cch = strlen(pDocInfo1->pOutputFile);
1552
1553 wDocInfo1.pOutputFile = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
1554 if (!wDocInfo1.pOutputFile)
1555 {
1556 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1557 ERR("HeapAlloc failed!\n");
1558 goto Cleanup;
1559 }
1560
1561 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pOutputFile, -1, wDocInfo1.pOutputFile, cch + 1);
1562 }
1563
1564 dwReturnValue = StartDocPrinterW(hPrinter, Level, (PBYTE)&wDocInfo1);
1565 dwErrorCode = GetLastError();
1566
1567 Cleanup:
1568 if (wDocInfo1.pDatatype)
1569 HeapFree(hProcessHeap, 0, wDocInfo1.pDatatype);
1570
1571 if (wDocInfo1.pDocName)
1572 HeapFree(hProcessHeap, 0, wDocInfo1.pDocName);
1573
1574 if (wDocInfo1.pOutputFile)
1575 HeapFree(hProcessHeap, 0, wDocInfo1.pOutputFile);
1576
1577 SetLastError(dwErrorCode);
1578 return dwReturnValue;
1579 }
1580
1581 DWORD WINAPI
1582 StartDocPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
1583 {
1584 DWORD cbAddJobInfo1;
1585 DWORD cbNeeded;
1586 DWORD dwErrorCode;
1587 DWORD dwReturnValue = 0;
1588 PADDJOB_INFO_1W pAddJobInfo1 = NULL;
1589 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;
1590 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1591
1592 TRACE("StartDocPrinterW(%p, %lu, %p)\n", hPrinter, Level, pDocInfo);
1593
1594 // Sanity checks.
1595 if (!pHandle)
1596 {
1597 dwErrorCode = ERROR_INVALID_HANDLE;
1598 goto Cleanup;
1599 }
1600
1601 if (!pDocInfo1)
1602 {
1603 dwErrorCode = ERROR_INVALID_PARAMETER;
1604 goto Cleanup;
1605 }
1606
1607 if (Level != 1)
1608 {
1609 dwErrorCode = ERROR_INVALID_LEVEL;
1610 goto Cleanup;
1611 }
1612
1613 if (pHandle->bStartedDoc)
1614 {
1615 dwErrorCode = ERROR_INVALID_PRINTER_STATE;
1616 goto Cleanup;
1617 }
1618
1619 // Check if we want to redirect output into a file.
1620 if (pDocInfo1->pOutputFile)
1621 {
1622 // Do a StartDocPrinter RPC call in this case.
1623 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1624 }
1625 else
1626 {
1627 // Allocate memory for the ADDJOB_INFO_1W structure and a path.
1628 cbAddJobInfo1 = sizeof(ADDJOB_INFO_1W) + MAX_PATH * sizeof(WCHAR);
1629 pAddJobInfo1 = HeapAlloc(hProcessHeap, 0, cbAddJobInfo1);
1630 if (!pAddJobInfo1)
1631 {
1632 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1633 ERR("HeapAlloc failed!\n");
1634 goto Cleanup;
1635 }
1636
1637 // Try to add a new job.
1638 // This only succeeds if the printer is set to do spooled printing.
1639 if (AddJobW((HANDLE)pHandle, 1, (PBYTE)pAddJobInfo1, cbAddJobInfo1, &cbNeeded))
1640 {
1641 // Do spooled printing.
1642 dwErrorCode = _StartDocPrinterSpooled(pHandle, pDocInfo1, pAddJobInfo1);
1643 }
1644 else if (GetLastError() == ERROR_INVALID_ACCESS)
1645 {
1646 // ERROR_INVALID_ACCESS is returned when the printer is set to do direct printing.
1647 // In this case, we do a StartDocPrinter RPC call.
1648 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1649 }
1650 else
1651 {
1652 dwErrorCode = GetLastError();
1653 ERR("AddJobW failed with error %lu!\n", dwErrorCode);
1654 goto Cleanup;
1655 }
1656 }
1657
1658 if (dwErrorCode == ERROR_SUCCESS)
1659 {
1660 pHandle->bStartedDoc = TRUE;
1661 dwReturnValue = pHandle->dwJobID;
1662 }
1663
1664 Cleanup:
1665 if (pAddJobInfo1)
1666 HeapFree(hProcessHeap, 0, pAddJobInfo1);
1667
1668 SetLastError(dwErrorCode);
1669 return dwReturnValue;
1670 }
1671
1672 BOOL WINAPI
1673 StartPagePrinter(HANDLE hPrinter)
1674 {
1675 DWORD dwErrorCode;
1676 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1677
1678 TRACE("StartPagePrinter(%p)\n", hPrinter);
1679
1680 // Sanity checks.
1681 if (!pHandle)
1682 {
1683 dwErrorCode = ERROR_INVALID_HANDLE;
1684 goto Cleanup;
1685 }
1686
1687 // Do the RPC call
1688 RpcTryExcept
1689 {
1690 dwErrorCode = _RpcStartPagePrinter(pHandle->hPrinter);
1691 }
1692 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1693 {
1694 dwErrorCode = RpcExceptionCode();
1695 ERR("_RpcStartPagePrinter failed with exception code %lu!\n", dwErrorCode);
1696 }
1697 RpcEndExcept;
1698
1699 Cleanup:
1700 SetLastError(dwErrorCode);
1701 return (dwErrorCode == ERROR_SUCCESS);
1702 }
1703
1704 BOOL WINAPI
1705 WritePrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pcWritten)
1706 {
1707 DWORD dwErrorCode;
1708 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1709
1710 TRACE("WritePrinter(%p, %p, %lu, %p)\n", hPrinter, pBuf, cbBuf, pcWritten);
1711
1712 // Sanity checks.
1713 if (!pHandle)
1714 {
1715 dwErrorCode = ERROR_INVALID_HANDLE;
1716 goto Cleanup;
1717 }
1718
1719 if (!pHandle->bStartedDoc)
1720 {
1721 dwErrorCode = ERROR_SPL_NO_STARTDOC;
1722 goto Cleanup;
1723 }
1724
1725 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
1726 {
1727 // Write to the spool file. This doesn't need an RPC request.
1728 if (!WriteFile(pHandle->hSPLFile, pBuf, cbBuf, pcWritten, NULL))
1729 {
1730 dwErrorCode = GetLastError();
1731 ERR("WriteFile failed with error %lu!\n", dwErrorCode);
1732 goto Cleanup;
1733 }
1734
1735 dwErrorCode = ERROR_SUCCESS;
1736 }
1737 else
1738 {
1739 // TODO: This case (for direct printing or remote printing) has bad performance if multiple small-sized WritePrinter calls are performed.
1740 // We may increase performance by writing into a buffer and only doing a single RPC call when the buffer is full.
1741
1742 // Do the RPC call
1743 RpcTryExcept
1744 {
1745 dwErrorCode = _RpcWritePrinter(pHandle->hPrinter, pBuf, cbBuf, pcWritten);
1746 }
1747 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1748 {
1749 dwErrorCode = RpcExceptionCode();
1750 ERR("_RpcWritePrinter failed with exception code %lu!\n", dwErrorCode);
1751 }
1752 RpcEndExcept;
1753 }
1754
1755 Cleanup:
1756 SetLastError(dwErrorCode);
1757 return (dwErrorCode == ERROR_SUCCESS);
1758 }
1759
1760 BOOL WINAPI
1761 XcvDataW(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded, PDWORD pdwStatus)
1762 {
1763 TRACE("XcvDataW(%p, %S, %p, %lu, %p, %lu, %p, %p)\n", hXcv, pszDataName, pInputData, cbInputData, pOutputData, cbOutputData, pcbOutputNeeded, pdwStatus);
1764 return FALSE;
1765 }