[PRINTING]
[reactos.git] / reactos / win32ss / printing / base / winspool / printers.c
1 /*
2 * PROJECT: ReactOS Spooler API
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-2017 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 // Local Constants
11
12 /** And the award for the most confusingly named setting goes to "Device", for storing the default printer of the current user.
13 Ok, I admit that this has historical reasons. It's still not straightforward in any way though! */
14 static const WCHAR wszWindowsKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
15 static const WCHAR wszDeviceValue[] = L"Device";
16
17 static void
18 _MarshallUpPrinterInfo(PBYTE* ppPrinterInfo, DWORD Level)
19 {
20 // Replace relative offset addresses in the output by absolute pointers and advance to the next structure.
21 if (Level == 0)
22 {
23 PPRINTER_INFO_STRESS pPrinterInfo0 = (PPRINTER_INFO_STRESS)(*ppPrinterInfo);
24
25 pPrinterInfo0->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pPrinterName + (ULONG_PTR)pPrinterInfo0);
26
27 if (pPrinterInfo0->pServerName)
28 pPrinterInfo0->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pServerName + (ULONG_PTR)pPrinterInfo0);
29
30 *ppPrinterInfo += sizeof(PRINTER_INFO_STRESS);
31 }
32 else if (Level == 1)
33 {
34 PPRINTER_INFO_1W pPrinterInfo1 = (PPRINTER_INFO_1W)(*ppPrinterInfo);
35
36 pPrinterInfo1->pName = (PWSTR)((ULONG_PTR)pPrinterInfo1->pName + (ULONG_PTR)pPrinterInfo1);
37 pPrinterInfo1->pDescription = (PWSTR)((ULONG_PTR)pPrinterInfo1->pDescription + (ULONG_PTR)pPrinterInfo1);
38 pPrinterInfo1->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo1->pComment + (ULONG_PTR)pPrinterInfo1);
39
40 *ppPrinterInfo += sizeof(PRINTER_INFO_1W);
41 }
42 else if (Level == 2)
43 {
44 PPRINTER_INFO_2W pPrinterInfo2 = (PPRINTER_INFO_2W)(*ppPrinterInfo);
45
46 pPrinterInfo2->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrinterName + (ULONG_PTR)pPrinterInfo2);
47 pPrinterInfo2->pShareName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pShareName + (ULONG_PTR)pPrinterInfo2);
48 pPrinterInfo2->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPortName + (ULONG_PTR)pPrinterInfo2);
49 pPrinterInfo2->pDriverName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDriverName + (ULONG_PTR)pPrinterInfo2);
50 pPrinterInfo2->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo2->pComment + (ULONG_PTR)pPrinterInfo2);
51 pPrinterInfo2->pLocation = (PWSTR)((ULONG_PTR)pPrinterInfo2->pLocation + (ULONG_PTR)pPrinterInfo2);
52 pPrinterInfo2->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo2->pDevMode + (ULONG_PTR)pPrinterInfo2);
53 pPrinterInfo2->pSepFile = (PWSTR)((ULONG_PTR)pPrinterInfo2->pSepFile + (ULONG_PTR)pPrinterInfo2);
54 pPrinterInfo2->pPrintProcessor = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrintProcessor + (ULONG_PTR)pPrinterInfo2);
55 pPrinterInfo2->pDatatype = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDatatype + (ULONG_PTR)pPrinterInfo2);
56 pPrinterInfo2->pParameters = (PWSTR)((ULONG_PTR)pPrinterInfo2->pParameters + (ULONG_PTR)pPrinterInfo2);
57
58 if (pPrinterInfo2->pServerName)
59 pPrinterInfo2->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pServerName + (ULONG_PTR)pPrinterInfo2);
60
61 if (pPrinterInfo2->pSecurityDescriptor)
62 pPrinterInfo2->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo2->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo2);
63
64 *ppPrinterInfo += sizeof(PRINTER_INFO_2W);
65 }
66 else if (Level == 3)
67 {
68 PPRINTER_INFO_3 pPrinterInfo3 = (PPRINTER_INFO_3)(*ppPrinterInfo);
69
70 pPrinterInfo3->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo3->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo3);
71
72 *ppPrinterInfo += sizeof(PRINTER_INFO_3);
73 }
74 else if (Level == 4)
75 {
76 PPRINTER_INFO_4W pPrinterInfo4 = (PPRINTER_INFO_4W)(*ppPrinterInfo);
77
78 pPrinterInfo4->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pPrinterName + (ULONG_PTR)pPrinterInfo4);
79
80 if (pPrinterInfo4->pServerName)
81 pPrinterInfo4->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pServerName + (ULONG_PTR)pPrinterInfo4);
82
83 *ppPrinterInfo += sizeof(PRINTER_INFO_4W);
84 }
85 else if (Level == 5)
86 {
87 PPRINTER_INFO_5W pPrinterInfo5 = (PPRINTER_INFO_5W)(*ppPrinterInfo);
88
89 pPrinterInfo5->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPrinterName + (ULONG_PTR)pPrinterInfo5);
90 pPrinterInfo5->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPortName + (ULONG_PTR)pPrinterInfo5);
91
92 *ppPrinterInfo += sizeof(PRINTER_INFO_5W);
93 }
94 else if (Level == 6)
95 {
96 *ppPrinterInfo += sizeof(PRINTER_INFO_6);
97 }
98 else if (Level == 7)
99 {
100 PPRINTER_INFO_7W pPrinterInfo7 = (PPRINTER_INFO_7W)(*ppPrinterInfo);
101
102 if (pPrinterInfo7->pszObjectGUID)
103 pPrinterInfo7->pszObjectGUID = (PWSTR)((ULONG_PTR)pPrinterInfo7->pszObjectGUID + (ULONG_PTR)pPrinterInfo7);
104
105 *ppPrinterInfo += sizeof(PRINTER_INFO_7W);
106 }
107 else if (Level == 8)
108 {
109 PPRINTER_INFO_8W pPrinterInfo8 = (PPRINTER_INFO_8W)(*ppPrinterInfo);
110
111 pPrinterInfo8->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo8->pDevMode + (ULONG_PTR)pPrinterInfo8);
112
113 *ppPrinterInfo += sizeof(PRINTER_INFO_8W);
114 }
115 else if (Level == 9)
116 {
117 PPRINTER_INFO_9W pPrinterInfo9 = (PPRINTER_INFO_9W)(*ppPrinterInfo);
118
119 pPrinterInfo9->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo9->pDevMode + (ULONG_PTR)pPrinterInfo9);
120
121 *ppPrinterInfo += sizeof(PRINTER_INFO_9W);
122 }
123 }
124
125 static DWORD
126 _StartDocPrinterSpooled(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1, PADDJOB_INFO_1W pAddJobInfo1)
127 {
128 DWORD cbNeeded;
129 DWORD dwErrorCode;
130 PJOB_INFO_1W pJobInfo1 = NULL;
131
132 // Create the spool file.
133 pHandle->hSPLFile = CreateFileW(pAddJobInfo1->Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
134 if (pHandle->hSPLFile == INVALID_HANDLE_VALUE)
135 {
136 dwErrorCode = GetLastError();
137 ERR("CreateFileW failed for \"%S\" with error %lu!\n", pAddJobInfo1->Path, dwErrorCode);
138 goto Cleanup;
139 }
140
141 // Get the size of the job information.
142 GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, NULL, 0, &cbNeeded);
143 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
144 {
145 dwErrorCode = GetLastError();
146 ERR("GetJobW failed with error %lu!\n", dwErrorCode);
147 goto Cleanup;
148 }
149
150 // Allocate enough memory for the returned job information.
151 pJobInfo1 = HeapAlloc(hProcessHeap, 0, cbNeeded);
152 if (!pJobInfo1)
153 {
154 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
155 ERR("HeapAlloc failed with error %lu!\n", GetLastError());
156 goto Cleanup;
157 }
158
159 // Get the job information.
160 if (!GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, cbNeeded, &cbNeeded))
161 {
162 dwErrorCode = GetLastError();
163 ERR("GetJobW failed with error %lu!\n", dwErrorCode);
164 goto Cleanup;
165 }
166
167 // Add our document information.
168 if (pDocInfo1->pDatatype)
169 pJobInfo1->pDatatype = pDocInfo1->pDatatype;
170
171 pJobInfo1->pDocument = pDocInfo1->pDocName;
172
173 // Set the new job information.
174 if (!SetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, 0))
175 {
176 dwErrorCode = GetLastError();
177 ERR("SetJobW failed with error %lu!\n", dwErrorCode);
178 goto Cleanup;
179 }
180
181 // We were successful!
182 pHandle->dwJobID = pAddJobInfo1->JobId;
183 dwErrorCode = ERROR_SUCCESS;
184
185 Cleanup:
186 if (pJobInfo1)
187 HeapFree(hProcessHeap, 0, pJobInfo1);
188
189 return dwErrorCode;
190 }
191
192 static DWORD
193 _StartDocPrinterWithRPC(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1)
194 {
195 DWORD dwErrorCode;
196 WINSPOOL_DOC_INFO_CONTAINER DocInfoContainer;
197
198 DocInfoContainer.Level = 1;
199 DocInfoContainer.DocInfo.pDocInfo1 = (WINSPOOL_DOC_INFO_1*)pDocInfo1;
200
201 RpcTryExcept
202 {
203 dwErrorCode = _RpcStartDocPrinter(pHandle->hPrinter, &DocInfoContainer, &pHandle->dwJobID);
204 }
205 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
206 {
207 dwErrorCode = RpcExceptionCode();
208 ERR("_RpcStartDocPrinter failed with exception code %lu!\n", dwErrorCode);
209 }
210 RpcEndExcept;
211
212 return dwErrorCode;
213 }
214
215 HANDLE WINAPI
216 AddPrinterW(PWSTR pName, DWORD Level, PBYTE pPrinter)
217 {
218 UNIMPLEMENTED;
219 return NULL;
220 }
221
222 BOOL WINAPI
223 ClosePrinter(HANDLE hPrinter)
224 {
225 DWORD dwErrorCode;
226 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
227
228 // Sanity checks.
229 if (!pHandle)
230 {
231 dwErrorCode = ERROR_INVALID_HANDLE;
232 goto Cleanup;
233 }
234
235 // Do the RPC call.
236 RpcTryExcept
237 {
238 dwErrorCode = _RpcClosePrinter(pHandle->hPrinter);
239 }
240 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
241 {
242 dwErrorCode = RpcExceptionCode();
243 ERR("_RpcClosePrinter failed with exception code %lu!\n", dwErrorCode);
244 }
245 RpcEndExcept;
246
247 // Close any open file handle.
248 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
249 CloseHandle(pHandle->hSPLFile);
250
251 // Free the memory for the handle.
252 HeapFree(hProcessHeap, 0, pHandle);
253
254 Cleanup:
255 SetLastError(dwErrorCode);
256 return (dwErrorCode == ERROR_SUCCESS);
257
258 }
259
260 DWORD WINAPI
261 DeviceCapabilitiesA(LPCSTR pDevice, LPCSTR pPort, WORD fwCapability, LPSTR pOutput, const DEVMODEA* pDevMode)
262 {
263 return 0;
264 }
265
266 DWORD WINAPI
267 DeviceCapabilitiesW(LPCWSTR pDevice, LPCWSTR pPort, WORD fwCapability, LPWSTR pOutput, const DEVMODEW* pDevMode)
268 {
269 return 0;
270 }
271
272 LONG WINAPI
273 DocumentPropertiesA(HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode)
274 {
275 return 0;
276 }
277
278 LONG WINAPI
279 DocumentPropertiesW(HWND hWnd, HANDLE hPrinter, LPWSTR pDeviceName, PDEVMODEW pDevModeOutput, PDEVMODEW pDevModeInput, DWORD fMode)
280 {
281 return 0;
282 }
283
284 BOOL WINAPI
285 EndDocPrinter(HANDLE hPrinter)
286 {
287 DWORD dwErrorCode;
288 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
289
290 // Sanity checks.
291 if (!pHandle)
292 {
293 dwErrorCode = ERROR_INVALID_HANDLE;
294 goto Cleanup;
295 }
296
297 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
298 {
299 // For spooled jobs, the document is finished by calling _RpcScheduleJob.
300 RpcTryExcept
301 {
302 dwErrorCode = _RpcScheduleJob(pHandle->hPrinter, pHandle->dwJobID);
303 }
304 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
305 {
306 dwErrorCode = RpcExceptionCode();
307 ERR("_RpcScheduleJob failed with exception code %lu!\n", dwErrorCode);
308 }
309 RpcEndExcept;
310
311 // Close the spool file handle.
312 CloseHandle(pHandle->hSPLFile);
313 }
314 else
315 {
316 // In all other cases, just call _RpcEndDocPrinter.
317 RpcTryExcept
318 {
319 dwErrorCode = _RpcEndDocPrinter(pHandle->hPrinter);
320 }
321 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
322 {
323 dwErrorCode = RpcExceptionCode();
324 ERR("_RpcEndDocPrinter failed with exception code %lu!\n", dwErrorCode);
325 }
326 RpcEndExcept;
327 }
328
329 // A new document can now be started again.
330 pHandle->bStartedDoc = FALSE;
331
332 Cleanup:
333 SetLastError(dwErrorCode);
334 return (dwErrorCode == ERROR_SUCCESS);
335 }
336
337 BOOL WINAPI
338 EndPagePrinter(HANDLE hPrinter)
339 {
340 DWORD dwErrorCode;
341 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
342
343 // Sanity checks.
344 if (!pHandle)
345 {
346 dwErrorCode = ERROR_INVALID_HANDLE;
347 goto Cleanup;
348 }
349
350 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
351 {
352 // For spooled jobs, we don't need to do anything.
353 dwErrorCode = ERROR_SUCCESS;
354 }
355 else
356 {
357 // In all other cases, just call _RpcEndPagePrinter.
358 RpcTryExcept
359 {
360 dwErrorCode = _RpcEndPagePrinter(pHandle->hPrinter);
361 }
362 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
363 {
364 dwErrorCode = RpcExceptionCode();
365 ERR("_RpcEndPagePrinter failed with exception code %lu!\n", dwErrorCode);
366 }
367 RpcEndExcept;
368 }
369
370 Cleanup:
371 SetLastError(dwErrorCode);
372 return (dwErrorCode == ERROR_SUCCESS);
373 }
374
375 BOOL WINAPI
376 EnumPrintersA(DWORD Flags, PSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
377 {
378 return FALSE;
379 }
380
381 BOOL WINAPI
382 EnumPrintersW(DWORD Flags, PWSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
383 {
384 DWORD dwErrorCode;
385
386 // Dismiss invalid levels already at this point.
387 if (Level == 3 || Level > 5)
388 {
389 dwErrorCode = ERROR_INVALID_LEVEL;
390 goto Cleanup;
391 }
392
393 if (cbBuf && pPrinterEnum)
394 ZeroMemory(pPrinterEnum, cbBuf);
395
396 // Do the RPC call
397 RpcTryExcept
398 {
399 dwErrorCode = _RpcEnumPrinters(Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
400 }
401 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
402 {
403 dwErrorCode = RpcExceptionCode();
404 ERR("_RpcEnumPrinters failed with exception code %lu!\n", dwErrorCode);
405 }
406 RpcEndExcept;
407
408 if (dwErrorCode == ERROR_SUCCESS)
409 {
410 DWORD i;
411 PBYTE p = pPrinterEnum;
412
413 for (i = 0; i < *pcReturned; i++)
414 _MarshallUpPrinterInfo(&p, Level);
415 }
416
417 Cleanup:
418 SetLastError(dwErrorCode);
419 return (dwErrorCode == ERROR_SUCCESS);
420 }
421
422 BOOL WINAPI
423 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer)
424 {
425 DWORD dwErrorCode;
426 PWSTR pwszBuffer = NULL;
427
428 // Sanity check.
429 if (!pcchBuffer)
430 {
431 dwErrorCode = ERROR_INVALID_PARAMETER;
432 goto Cleanup;
433 }
434
435 // Check if an ANSI buffer was given and if so, allocate a Unicode buffer of the same size.
436 if (pszBuffer && *pcchBuffer)
437 {
438 pwszBuffer = HeapAlloc(hProcessHeap, 0, *pcchBuffer * sizeof(WCHAR));
439 if (!pwszBuffer)
440 {
441 dwErrorCode = GetLastError();
442 ERR("HeapAlloc failed with error %lu!\n", dwErrorCode);
443 goto Cleanup;
444 }
445 }
446
447 if (!GetDefaultPrinterW(pwszBuffer, pcchBuffer))
448 {
449 dwErrorCode = GetLastError();
450 goto Cleanup;
451 }
452
453 dwErrorCode = ERROR_SUCCESS;
454
455 Cleanup:
456 if (pwszBuffer)
457 HeapFree(hProcessHeap, 0, pwszBuffer);
458
459 SetLastError(dwErrorCode);
460 return (dwErrorCode == ERROR_SUCCESS);
461 }
462
463 BOOL WINAPI
464 GetDefaultPrinterW(LPWSTR pszBuffer, LPDWORD pcchBuffer)
465 {
466 DWORD cbNeeded;
467 DWORD cchInputBuffer;
468 DWORD dwErrorCode;
469 HKEY hWindowsKey = NULL;
470 PWSTR pwszDevice = NULL;
471 PWSTR pwszComma;
472
473 // Sanity check.
474 if (!pcchBuffer)
475 {
476 dwErrorCode = ERROR_INVALID_PARAMETER;
477 goto Cleanup;
478 }
479
480 cchInputBuffer = *pcchBuffer;
481
482 // Open the registry key where the default printer for the current user is stored.
483 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_READ, &hWindowsKey);
484 if (dwErrorCode != ERROR_SUCCESS)
485 {
486 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
487 goto Cleanup;
488 }
489
490 // Determine the size of the required buffer.
491 dwErrorCode = (DWORD)RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, NULL, &cbNeeded);
492 if (dwErrorCode != ERROR_SUCCESS)
493 {
494 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
495 goto Cleanup;
496 }
497
498 // Allocate it.
499 pwszDevice = HeapAlloc(hProcessHeap, 0, cbNeeded);
500 if (!pwszDevice)
501 {
502 dwErrorCode = GetLastError();
503 ERR("HeapAlloc failed with error %lu!\n", dwErrorCode);
504 goto Cleanup;
505 }
506
507 // Now get the actual value.
508 dwErrorCode = RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, (PBYTE)pwszDevice, &cbNeeded);
509 if (dwErrorCode != ERROR_SUCCESS)
510 {
511 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
512 goto Cleanup;
513 }
514
515 // We get a string "<Printer Name>,winspool,<Port>:".
516 // Extract the printer name from it.
517 pwszComma = wcschr(pwszDevice, L',');
518 if (!pwszComma)
519 {
520 ERR("Found no or invalid default printer: %S!\n", pwszDevice);
521 dwErrorCode = ERROR_INVALID_NAME;
522 goto Cleanup;
523 }
524
525 // Store the length of the Printer Name (including the terminating NUL character!) in *pcchBuffer.
526 *pcchBuffer = pwszComma - pwszDevice + 1;
527
528 // Check if the supplied buffer is large enough.
529 if (cchInputBuffer < *pcchBuffer)
530 {
531 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
532 goto Cleanup;
533 }
534
535 // Copy the default printer.
536 *pwszComma = 0;
537 CopyMemory(pszBuffer, pwszDevice, *pcchBuffer * sizeof(WCHAR));
538
539 dwErrorCode = ERROR_SUCCESS;
540
541 Cleanup:
542 if (hWindowsKey)
543 RegCloseKey(hWindowsKey);
544
545 if (pwszDevice)
546 HeapFree(hProcessHeap, 0, pwszDevice);
547
548 SetLastError(dwErrorCode);
549 return (dwErrorCode == ERROR_SUCCESS);
550 }
551
552 BOOL WINAPI
553 GetPrinterA(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
554 {
555 return FALSE;
556 }
557
558 BOOL WINAPI
559 GetPrinterDriverA(HANDLE hPrinter, LPSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
560 {
561 return FALSE;
562 }
563
564 BOOL WINAPI
565 GetPrinterDriverW(HANDLE hPrinter, LPWSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
566 {
567 return FALSE;
568 }
569
570 BOOL WINAPI
571 GetPrinterW(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
572 {
573 DWORD dwErrorCode;
574
575 // Dismiss invalid levels already at this point.
576 if (Level > 9)
577 {
578 dwErrorCode = ERROR_INVALID_LEVEL;
579 goto Cleanup;
580 }
581
582 if (cbBuf && pPrinter)
583 ZeroMemory(pPrinter, cbBuf);
584
585 // Do the RPC call
586 RpcTryExcept
587 {
588 dwErrorCode = _RpcGetPrinter(hPrinter, Level, pPrinter, cbBuf, pcbNeeded);
589 }
590 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
591 {
592 dwErrorCode = RpcExceptionCode();
593 ERR("_RpcGetPrinter failed with exception code %lu!\n", dwErrorCode);
594 }
595 RpcEndExcept;
596
597 if (dwErrorCode == ERROR_SUCCESS)
598 {
599 PBYTE p = pPrinter;
600 _MarshallUpPrinterInfo(&p, Level);
601 }
602
603 Cleanup:
604 SetLastError(dwErrorCode);
605 return (dwErrorCode == ERROR_SUCCESS);
606 }
607
608 BOOL WINAPI
609 OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefault)
610 {
611 BOOL bReturnValue = FALSE;
612 DWORD cch;
613 PWSTR pwszPrinterName = NULL;
614 PRINTER_DEFAULTSW wDefault = { 0 };
615
616 if (pPrinterName)
617 {
618 // Convert pPrinterName to a Unicode string pwszPrinterName
619 cch = strlen(pPrinterName);
620
621 pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
622 if (!pwszPrinterName)
623 {
624 ERR("HeapAlloc failed for pwszPrinterName with last error %lu!\n", GetLastError());
625 goto Cleanup;
626 }
627
628 MultiByteToWideChar(CP_ACP, 0, pPrinterName, -1, pwszPrinterName, cch + 1);
629 }
630
631 if (pDefault)
632 {
633 wDefault.DesiredAccess = pDefault->DesiredAccess;
634
635 if (pDefault->pDatatype)
636 {
637 // Convert pDefault->pDatatype to a Unicode string wDefault.pDatatype
638 cch = strlen(pDefault->pDatatype);
639
640 wDefault.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
641 if (!wDefault.pDatatype)
642 {
643 ERR("HeapAlloc failed for wDefault.pDatatype with last error %lu!\n", GetLastError());
644 goto Cleanup;
645 }
646
647 MultiByteToWideChar(CP_ACP, 0, pDefault->pDatatype, -1, wDefault.pDatatype, cch + 1);
648 }
649
650 if (pDefault->pDevMode)
651 wDefault.pDevMode = GdiConvertToDevmodeW(pDefault->pDevMode);
652 }
653
654 bReturnValue = OpenPrinterW(pwszPrinterName, phPrinter, &wDefault);
655
656 Cleanup:
657 if (wDefault.pDatatype)
658 HeapFree(hProcessHeap, 0, wDefault.pDatatype);
659
660 if (wDefault.pDevMode)
661 HeapFree(hProcessHeap, 0, wDefault.pDevMode);
662
663 if (pwszPrinterName)
664 HeapFree(hProcessHeap, 0, pwszPrinterName);
665
666 return bReturnValue;
667 }
668
669 BOOL WINAPI
670 OpenPrinterW(LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSW pDefault)
671 {
672 DWORD dwErrorCode;
673 HANDLE hPrinter;
674 PSPOOLER_HANDLE pHandle;
675 PWSTR pDatatype = NULL;
676 WINSPOOL_DEVMODE_CONTAINER DevModeContainer = { 0 };
677 ACCESS_MASK AccessRequired = 0;
678
679 // Prepare the additional parameters in the format required by _RpcOpenPrinter
680 if (pDefault)
681 {
682 pDatatype = pDefault->pDatatype;
683 DevModeContainer.cbBuf = sizeof(DEVMODEW);
684 DevModeContainer.pDevMode = (BYTE*)pDefault->pDevMode;
685 AccessRequired = pDefault->DesiredAccess;
686 }
687
688 // Do the RPC call
689 RpcTryExcept
690 {
691 dwErrorCode = _RpcOpenPrinter(pPrinterName, &hPrinter, pDatatype, &DevModeContainer, AccessRequired);
692 }
693 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
694 {
695 dwErrorCode = RpcExceptionCode();
696 ERR("_RpcOpenPrinter failed with exception code %lu!\n", dwErrorCode);
697 }
698 RpcEndExcept;
699
700 if (dwErrorCode == ERROR_SUCCESS)
701 {
702 // Create a new SPOOLER_HANDLE structure.
703 pHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(SPOOLER_HANDLE));
704 if (!pHandle)
705 {
706 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
707 ERR("HeapAlloc failed with error %lu!\n", GetLastError());
708 goto Cleanup;
709 }
710
711 pHandle->hPrinter = hPrinter;
712 pHandle->hSPLFile = INVALID_HANDLE_VALUE;
713
714 // Return it as phPrinter.
715 *phPrinter = (HANDLE)pHandle;
716 }
717
718 Cleanup:
719 SetLastError(dwErrorCode);
720 return (dwErrorCode == ERROR_SUCCESS);
721 }
722
723 BOOL WINAPI
724 ReadPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pNoBytesRead)
725 {
726 DWORD dwErrorCode;
727 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
728
729 // Sanity checks.
730 if (!pHandle)
731 {
732 dwErrorCode = ERROR_INVALID_HANDLE;
733 goto Cleanup;
734 }
735
736 // Do the RPC call
737 RpcTryExcept
738 {
739 dwErrorCode = _RpcReadPrinter(pHandle->hPrinter, pBuf, cbBuf, pNoBytesRead);
740 }
741 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
742 {
743 dwErrorCode = RpcExceptionCode();
744 ERR("_RpcReadPrinter failed with exception code %lu!\n", dwErrorCode);
745 }
746 RpcEndExcept;
747
748 Cleanup:
749 SetLastError(dwErrorCode);
750 return (dwErrorCode == ERROR_SUCCESS);
751 }
752
753 BOOL WINAPI
754 ResetPrinterW(HANDLE hPrinter, PPRINTER_DEFAULTSW pDefault)
755 {
756 UNIMPLEMENTED;
757 return FALSE;
758 }
759
760 BOOL WINAPI
761 SetDefaultPrinterA(LPCSTR pszPrinter)
762 {
763 BOOL bReturnValue = FALSE;
764 DWORD cch;
765 PWSTR pwszPrinter = NULL;
766
767 if (pszPrinter)
768 {
769 // Convert pszPrinter to a Unicode string pwszPrinter
770 cch = strlen(pszPrinter);
771
772 pwszPrinter = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
773 if (!pwszPrinter)
774 {
775 ERR("HeapAlloc failed for pwszPrinter with last error %lu!\n", GetLastError());
776 goto Cleanup;
777 }
778
779 MultiByteToWideChar(CP_ACP, 0, pszPrinter, -1, pwszPrinter, cch + 1);
780 }
781
782 bReturnValue = SetDefaultPrinterW(pwszPrinter);
783
784 Cleanup:
785 if (pwszPrinter)
786 HeapFree(hProcessHeap, 0, pwszPrinter);
787
788 return bReturnValue;
789 }
790
791 BOOL WINAPI
792 SetDefaultPrinterW(LPCWSTR pszPrinter)
793 {
794 const WCHAR wszDevicesKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Devices";
795
796 DWORD cbDeviceValueData;
797 DWORD cbPrinterValueData = 0;
798 DWORD cchPrinter;
799 DWORD dwErrorCode;
800 HKEY hDevicesKey = NULL;
801 HKEY hWindowsKey = NULL;
802 PWSTR pwszDeviceValueData = NULL;
803 WCHAR wszPrinter[MAX_PRINTER_NAME + 1];
804
805 // Open the Devices registry key.
806 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszDevicesKey, 0, KEY_READ, &hDevicesKey);
807 if (dwErrorCode != ERROR_SUCCESS)
808 {
809 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
810 goto Cleanup;
811 }
812
813 // Did the caller give us a printer to set as default?
814 if (pszPrinter && *pszPrinter)
815 {
816 // Check if the given printer exists and query the value data size.
817 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, NULL, &cbPrinterValueData);
818 if (dwErrorCode == ERROR_FILE_NOT_FOUND)
819 {
820 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
821 goto Cleanup;
822 }
823 else if (dwErrorCode != ERROR_SUCCESS)
824 {
825 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
826 goto Cleanup;
827 }
828
829 cchPrinter = wcslen(pszPrinter);
830 }
831 else
832 {
833 // If there is already a default printer, we're done!
834 cchPrinter = _countof(wszPrinter);
835 if (GetDefaultPrinterW(wszPrinter, &cchPrinter))
836 {
837 dwErrorCode = ERROR_SUCCESS;
838 goto Cleanup;
839 }
840
841 // Otherwise, get us the first printer from the "Devices" key to later set it as default and query the value data size.
842 cchPrinter = _countof(wszPrinter);
843 dwErrorCode = (DWORD)RegEnumValueW(hDevicesKey, 0, wszPrinter, &cchPrinter, NULL, NULL, NULL, &cbPrinterValueData);
844 if (dwErrorCode != ERROR_MORE_DATA)
845 goto Cleanup;
846
847 pszPrinter = wszPrinter;
848 }
849
850 // We now need to query the value data, which has the format "winspool,<Port>:"
851 // and make "<Printer Name>,winspool,<Port>:" out of it.
852 // Allocate a buffer large enough for the final data.
853 cbDeviceValueData = (cchPrinter + 1) * sizeof(WCHAR) + cbPrinterValueData;
854 pwszDeviceValueData = HeapAlloc(hProcessHeap, 0, cbDeviceValueData);
855 if (!pwszDeviceValueData)
856 {
857 dwErrorCode = GetLastError();
858 ERR("HeapAlloc failed with error %lu\n", dwErrorCode);
859 goto Cleanup;
860 }
861
862 // Copy the Printer Name and a comma into it.
863 CopyMemory(pwszDeviceValueData, pszPrinter, cchPrinter * sizeof(WCHAR));
864 pwszDeviceValueData[cchPrinter] = L',';
865
866 // Append the value data, which has the format "winspool,<Port>:"
867 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, (PBYTE)&pwszDeviceValueData[cchPrinter + 1], &cbPrinterValueData);
868 if (dwErrorCode != ERROR_SUCCESS)
869 goto Cleanup;
870
871 // Open the Windows registry key.
872 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_SET_VALUE, &hWindowsKey);
873 if (dwErrorCode != ERROR_SUCCESS)
874 {
875 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
876 goto Cleanup;
877 }
878
879 // Store our new default printer.
880 dwErrorCode = (DWORD)RegSetValueExW(hWindowsKey, wszDeviceValue, 0, REG_SZ, (PBYTE)pwszDeviceValueData, cbDeviceValueData);
881 if (dwErrorCode != ERROR_SUCCESS)
882 {
883 ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode);
884 goto Cleanup;
885 }
886
887 Cleanup:
888 if (hDevicesKey)
889 RegCloseKey(hDevicesKey);
890
891 if (hWindowsKey)
892 RegCloseKey(hWindowsKey);
893
894 if (pwszDeviceValueData)
895 HeapFree(hProcessHeap, 0, pwszDeviceValueData);
896
897 SetLastError(dwErrorCode);
898 return (dwErrorCode == ERROR_SUCCESS);
899 }
900
901 BOOL WINAPI
902 SetPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pPrinter, DWORD Command)
903 {
904 UNIMPLEMENTED;
905 return FALSE;
906 }
907
908 DWORD WINAPI
909 StartDocPrinterA(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
910 {
911 DOC_INFO_1W wDocInfo1 = { 0 };
912 DWORD cch;
913 DWORD dwErrorCode;
914 DWORD dwReturnValue = 0;
915 PDOC_INFO_1A pDocInfo1 = (PDOC_INFO_1A)pDocInfo;
916
917 // Only check the minimum required for accessing pDocInfo.
918 // Additional sanity checks are done in StartDocPrinterW.
919 if (!pDocInfo1)
920 {
921 dwErrorCode = ERROR_INVALID_PARAMETER;
922 goto Cleanup;
923 }
924
925 if (Level != 1)
926 {
927 dwErrorCode = ERROR_INVALID_LEVEL;
928 goto Cleanup;
929 }
930
931 if (pDocInfo1->pDatatype)
932 {
933 // Convert pDocInfo1->pDatatype to a Unicode string wDocInfo1.pDatatype
934 cch = strlen(pDocInfo1->pDatatype);
935
936 wDocInfo1.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
937 if (!wDocInfo1.pDatatype)
938 {
939 ERR("HeapAlloc failed for wDocInfo1.pDatatype with last error %lu!\n", GetLastError());
940 goto Cleanup;
941 }
942
943 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDatatype, -1, wDocInfo1.pDatatype, cch + 1);
944 }
945
946 if (pDocInfo1->pDocName)
947 {
948 // Convert pDocInfo1->pDocName to a Unicode string wDocInfo1.pDocName
949 cch = strlen(pDocInfo1->pDocName);
950
951 wDocInfo1.pDocName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
952 if (!wDocInfo1.pDocName)
953 {
954 ERR("HeapAlloc failed for wDocInfo1.pDocName with last error %lu!\n", GetLastError());
955 goto Cleanup;
956 }
957
958 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDocName, -1, wDocInfo1.pDocName, cch + 1);
959 }
960
961 if (pDocInfo1->pOutputFile)
962 {
963 // Convert pDocInfo1->pOutputFile to a Unicode string wDocInfo1.pOutputFile
964 cch = strlen(pDocInfo1->pOutputFile);
965
966 wDocInfo1.pOutputFile = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
967 if (!wDocInfo1.pOutputFile)
968 {
969 ERR("HeapAlloc failed for wDocInfo1.pOutputFile with last error %lu!\n", GetLastError());
970 goto Cleanup;
971 }
972
973 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pOutputFile, -1, wDocInfo1.pOutputFile, cch + 1);
974 }
975
976 dwReturnValue = StartDocPrinterW(hPrinter, Level, (PBYTE)&wDocInfo1);
977 dwErrorCode = GetLastError();
978
979 Cleanup:
980 if (wDocInfo1.pDatatype)
981 HeapFree(hProcessHeap, 0, wDocInfo1.pDatatype);
982
983 if (wDocInfo1.pDocName)
984 HeapFree(hProcessHeap, 0, wDocInfo1.pDocName);
985
986 if (wDocInfo1.pOutputFile)
987 HeapFree(hProcessHeap, 0, wDocInfo1.pOutputFile);
988
989 SetLastError(dwErrorCode);
990 return dwReturnValue;
991 }
992
993 DWORD WINAPI
994 StartDocPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
995 {
996 DWORD cbAddJobInfo1;
997 DWORD cbNeeded;
998 DWORD dwErrorCode;
999 DWORD dwReturnValue = 0;
1000 PADDJOB_INFO_1W pAddJobInfo1 = NULL;
1001 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;
1002 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1003
1004 // Sanity checks.
1005 if (!pHandle)
1006 {
1007 dwErrorCode = ERROR_INVALID_HANDLE;
1008 goto Cleanup;
1009 }
1010
1011 if (!pDocInfo1)
1012 {
1013 dwErrorCode = ERROR_INVALID_PARAMETER;
1014 goto Cleanup;
1015 }
1016
1017 if (Level != 1)
1018 {
1019 dwErrorCode = ERROR_INVALID_LEVEL;
1020 goto Cleanup;
1021 }
1022
1023 if (pHandle->bStartedDoc)
1024 {
1025 dwErrorCode = ERROR_INVALID_PRINTER_STATE;
1026 goto Cleanup;
1027 }
1028
1029 // Check if we want to redirect output into a file.
1030 if (pDocInfo1->pOutputFile)
1031 {
1032 // Do a StartDocPrinter RPC call in this case.
1033 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1034 }
1035 else
1036 {
1037 // Allocate memory for the ADDJOB_INFO_1W structure and a path.
1038 cbAddJobInfo1 = sizeof(ADDJOB_INFO_1W) + MAX_PATH * sizeof(WCHAR);
1039 pAddJobInfo1 = HeapAlloc(hProcessHeap, 0, cbAddJobInfo1);
1040 if (!pAddJobInfo1)
1041 {
1042 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1043 ERR("HeapAlloc failed with error %lu!\n", GetLastError());
1044 goto Cleanup;
1045 }
1046
1047 // Try to add a new job.
1048 // This only succeeds if the printer is set to do spooled printing.
1049 if (AddJobW((HANDLE)pHandle, 1, (PBYTE)pAddJobInfo1, cbAddJobInfo1, &cbNeeded))
1050 {
1051 // Do spooled printing.
1052 dwErrorCode = _StartDocPrinterSpooled(pHandle, pDocInfo1, pAddJobInfo1);
1053 }
1054 else if (GetLastError() == ERROR_INVALID_ACCESS)
1055 {
1056 // ERROR_INVALID_ACCESS is returned when the printer is set to do direct printing.
1057 // In this case, we do a StartDocPrinter RPC call.
1058 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1059 }
1060 else
1061 {
1062 dwErrorCode = GetLastError();
1063 ERR("AddJobW failed with error %lu!\n", dwErrorCode);
1064 goto Cleanup;
1065 }
1066 }
1067
1068 if (dwErrorCode == ERROR_SUCCESS)
1069 {
1070 pHandle->bStartedDoc = TRUE;
1071 dwReturnValue = pHandle->dwJobID;
1072 }
1073
1074 Cleanup:
1075 if (pAddJobInfo1)
1076 HeapFree(hProcessHeap, 0, pAddJobInfo1);
1077
1078 SetLastError(dwErrorCode);
1079 return dwReturnValue;
1080 }
1081
1082 BOOL WINAPI
1083 StartPagePrinter(HANDLE hPrinter)
1084 {
1085 DWORD dwErrorCode;
1086 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1087
1088 // Sanity checks.
1089 if (!pHandle)
1090 {
1091 dwErrorCode = ERROR_INVALID_HANDLE;
1092 goto Cleanup;
1093 }
1094
1095 // Do the RPC call
1096 RpcTryExcept
1097 {
1098 dwErrorCode = _RpcStartPagePrinter(pHandle->hPrinter);
1099 }
1100 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1101 {
1102 dwErrorCode = RpcExceptionCode();
1103 ERR("_RpcStartPagePrinter failed with exception code %lu!\n", dwErrorCode);
1104 }
1105 RpcEndExcept;
1106
1107 Cleanup:
1108 SetLastError(dwErrorCode);
1109 return (dwErrorCode == ERROR_SUCCESS);
1110 }
1111
1112 BOOL WINAPI
1113 WritePrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pcWritten)
1114 {
1115 DWORD dwErrorCode;
1116 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1117
1118 // Sanity checks.
1119 if (!pHandle)
1120 {
1121 dwErrorCode = ERROR_INVALID_HANDLE;
1122 goto Cleanup;
1123 }
1124
1125 if (!pHandle->bStartedDoc)
1126 {
1127 dwErrorCode = ERROR_SPL_NO_STARTDOC;
1128 goto Cleanup;
1129 }
1130
1131 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
1132 {
1133 // Write to the spool file. This doesn't need an RPC request.
1134 if (!WriteFile(pHandle->hSPLFile, pBuf, cbBuf, pcWritten, NULL))
1135 {
1136 dwErrorCode = GetLastError();
1137 ERR("WriteFile failed with error %lu!\n", dwErrorCode);
1138 goto Cleanup;
1139 }
1140
1141 dwErrorCode = ERROR_SUCCESS;
1142 }
1143 else
1144 {
1145 // TODO: This case (for direct printing or remote printing) has bad performance if multiple small-sized WritePrinter calls are performed.
1146 // We may increase performance by writing into a buffer and only doing a single RPC call when the buffer is full.
1147
1148 // Do the RPC call
1149 RpcTryExcept
1150 {
1151 dwErrorCode = _RpcWritePrinter(pHandle->hPrinter, pBuf, cbBuf, pcWritten);
1152 }
1153 RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1154 {
1155 dwErrorCode = RpcExceptionCode();
1156 ERR("_RpcWritePrinter failed with exception code %lu!\n", dwErrorCode);
1157 }
1158 RpcEndExcept;
1159 }
1160
1161 Cleanup:
1162 SetLastError(dwErrorCode);
1163 return (dwErrorCode == ERROR_SUCCESS);
1164 }
1165
1166 BOOL WINAPI
1167 XcvDataW(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded, PDWORD pdwStatus)
1168 {
1169 return FALSE;
1170 }