OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/service/cloud_print/printer_info.h" | |
6 | |
7 #include <windows.h> | |
8 #include <objidl.h> | |
9 #include <ocidl.h> | |
10 #include <olectl.h> | |
11 #include <prntvpt.h> | |
12 #include <winspool.h> | |
13 | |
14 #include "base/lock.h" | |
15 #include "base/object_watcher.h" | |
16 #include "base/scoped_bstr_win.h" | |
17 #include "base/scoped_comptr_win.h" | |
18 #include "base/scoped_handle_win.h" | |
19 #include "base/scoped_ptr.h" | |
20 #include "base/utf_string_conversions.h" | |
21 | |
22 #pragma comment(lib, "prntvpt.lib") | |
23 #pragma comment(lib, "rpcrt4.lib") | |
24 | |
25 namespace { | |
26 | |
27 class DevMode { | |
28 public: | |
29 DevMode() : dm_(NULL) {} | |
30 ~DevMode() { Free(); } | |
31 | |
32 void Allocate(int size) { | |
33 Free(); | |
34 dm_ = reinterpret_cast<DEVMODE*>(new char[size]); | |
35 } | |
36 | |
37 void Free() { | |
38 if (dm_) | |
39 delete dm_; | |
40 dm_ = NULL; | |
41 } | |
42 | |
43 DEVMODE* dm_; | |
44 | |
45 private: | |
46 DISALLOW_COPY_AND_ASSIGN(DevMode); | |
47 }; | |
48 | |
49 bool InitXPSModule() { | |
50 HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll"); | |
51 return (NULL != prntvpt_module); | |
52 } | |
53 | |
54 inline HRESULT GetLastErrorHR() { | |
55 LONG error = GetLastError(); | |
56 return HRESULT_FROM_WIN32(error); | |
57 } | |
58 | |
59 HRESULT StreamFromPrintTicket(const std::string& print_ticket, | |
60 IStream** stream) { | |
61 DCHECK(stream); | |
62 HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream); | |
63 if (FAILED(hr)) { | |
64 return hr; | |
65 } | |
66 ULONG bytes_written = 0; | |
67 (*stream)->Write(print_ticket.c_str(), print_ticket.length(), &bytes_written); | |
68 DCHECK(bytes_written == print_ticket.length()); | |
69 LARGE_INTEGER pos = {0}; | |
70 ULARGE_INTEGER new_pos = {0}; | |
71 (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos); | |
72 return S_OK; | |
73 } | |
74 | |
75 HRESULT StreamOnHGlobalToString(IStream* stream, std::string* out) { | |
76 DCHECK(stream); | |
77 DCHECK(out); | |
78 HGLOBAL hdata = NULL; | |
79 HRESULT hr = GetHGlobalFromStream(stream, &hdata); | |
80 if (SUCCEEDED(hr)) { | |
81 DCHECK(hdata); | |
82 ScopedHGlobal<char> locked_data(hdata); | |
83 out->assign(locked_data.release(), locked_data.Size()); | |
84 } | |
85 return hr; | |
86 } | |
87 | |
88 HRESULT PrintTicketToDevMode(const std::string& printer_name, | |
89 const std::string& print_ticket, | |
90 DevMode* dev_mode) { | |
91 DCHECK(dev_mode); | |
92 | |
93 ScopedComPtr<IStream> pt_stream; | |
94 HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive()); | |
95 if (FAILED(hr)) | |
96 return hr; | |
97 | |
98 HPTPROVIDER provider = NULL; | |
99 hr = PTOpenProvider(UTF8ToWide(printer_name).c_str(), 1, &provider); | |
100 if (SUCCEEDED(hr)) { | |
101 ULONG size = 0; | |
102 DEVMODE* dm = NULL; | |
103 hr = PTConvertPrintTicketToDevMode(provider, | |
104 pt_stream, | |
105 kUserDefaultDevmode, | |
106 kPTDocumentScope, | |
107 &size, | |
108 &dm, | |
109 NULL); | |
110 if (SUCCEEDED(hr)) { | |
111 dev_mode->Allocate(size); | |
112 memcpy(dev_mode->dm_, dm, size); | |
113 PTReleaseMemory(dm); | |
114 } | |
115 PTCloseProvider(provider); | |
116 } | |
117 return hr; | |
118 } | |
119 | |
120 HRESULT PrintPdf2DC(HDC dc, const FilePath& pdf_filename) { | |
121 HRESULT hr = E_NOTIMPL; | |
122 // TODO(sanjeevr): Implement this. | |
123 NOTIMPLEMENTED(); | |
124 return hr; | |
125 } | |
126 | |
127 } // namespace | |
128 | |
129 namespace cloud_print { | |
130 | |
131 void EnumeratePrinters(PrinterList* printer_list) { | |
132 DCHECK(printer_list); | |
133 DWORD bytes_needed = 0; | |
134 DWORD count_returned = 0; | |
135 BOOL ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, | |
136 NULL, 0, &bytes_needed, &count_returned); | |
137 if (0 != bytes_needed) { | |
138 scoped_ptr<BYTE> printer_info_buffer(new BYTE[bytes_needed]); | |
139 ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, | |
140 printer_info_buffer.get(), bytes_needed, &bytes_needed, | |
141 &count_returned); | |
142 DCHECK(ret); | |
143 PRINTER_INFO_2* printer_info = | |
144 reinterpret_cast<PRINTER_INFO_2*>(printer_info_buffer.get()); | |
145 for (DWORD index = 0; index < count_returned; index++) { | |
146 PrinterBasicInfo info; | |
147 info.printer_name = WideToUTF8(printer_info[index].pPrinterName); | |
148 if (printer_info[index].pComment) | |
149 info.printer_description = WideToUTF8(printer_info[index].pComment); | |
150 info.printer_status = printer_info[index].Status; | |
151 printer_list->push_back(info); | |
152 } | |
153 } | |
154 } | |
155 | |
156 bool GetPrinterCapsAndDefaults(const std::string& printer_name, | |
157 PrinterCapsAndDefaults* printer_info) { | |
158 if (!InitXPSModule()) { | |
159 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) | |
160 return false; | |
161 } | |
162 if (!IsValidPrinter(printer_name)) { | |
163 return false; | |
164 } | |
165 DCHECK(printer_info); | |
166 HPTPROVIDER provider = NULL; | |
167 std::wstring printer_name_wide = UTF8ToWide(printer_name); | |
168 HRESULT hr = PTOpenProvider(printer_name_wide.c_str(), 1, &provider); | |
169 DCHECK(SUCCEEDED(hr)); | |
170 if (provider) { | |
171 ScopedComPtr<IStream> print_capabilities_stream; | |
172 hr = CreateStreamOnHGlobal(NULL, TRUE, | |
173 print_capabilities_stream.Receive()); | |
174 DCHECK(SUCCEEDED(hr)); | |
175 if (print_capabilities_stream) { | |
176 ScopedBstr error; | |
177 hr = PTGetPrintCapabilities(provider, NULL, print_capabilities_stream, | |
178 error.Receive()); | |
179 DCHECK(SUCCEEDED(hr)); | |
180 if (FAILED(hr)) { | |
181 return false; | |
182 } | |
183 hr = StreamOnHGlobalToString(print_capabilities_stream.get(), | |
184 &printer_info->printer_capabilities); | |
185 DCHECK(SUCCEEDED(hr)); | |
186 printer_info->caps_mime_type = "text/xml"; | |
187 } | |
188 // TODO(sanjeevr): Add ScopedPrinterHandle | |
189 HANDLE printer_handle = NULL; | |
190 OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, | |
191 NULL); | |
192 DCHECK(printer_handle); | |
193 if (printer_handle) { | |
194 DWORD devmode_size = DocumentProperties( | |
195 NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), | |
196 NULL, NULL, 0); | |
197 DCHECK(0 != devmode_size); | |
198 scoped_ptr<BYTE> devmode_out_buffer(new BYTE[devmode_size]); | |
199 DEVMODE* devmode_out = | |
200 reinterpret_cast<DEVMODE*>(devmode_out_buffer.get()); | |
201 DocumentProperties( | |
202 NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), | |
203 devmode_out, NULL, DM_OUT_BUFFER); | |
204 ScopedComPtr<IStream> printer_defaults_stream; | |
205 hr = CreateStreamOnHGlobal(NULL, TRUE, | |
206 printer_defaults_stream.Receive()); | |
207 DCHECK(SUCCEEDED(hr)); | |
208 if (printer_defaults_stream) { | |
209 hr = PTConvertDevModeToPrintTicket(provider, devmode_size, | |
210 devmode_out, kPTJobScope, | |
211 printer_defaults_stream); | |
212 DCHECK(SUCCEEDED(hr)); | |
213 if (SUCCEEDED(hr)) { | |
214 hr = StreamOnHGlobalToString(printer_defaults_stream.get(), | |
215 &printer_info->printer_defaults); | |
216 DCHECK(SUCCEEDED(hr)); | |
217 printer_info->defaults_mime_type = "text/xml"; | |
218 } | |
219 } | |
220 ClosePrinter(printer_handle); | |
221 } | |
222 PTCloseProvider(provider); | |
223 } | |
224 return true; | |
225 } | |
226 | |
227 bool ValidatePrintTicket(const std::string& printer_name, | |
228 const std::string& print_ticket_data) { | |
229 if (!InitXPSModule()) { | |
230 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) | |
231 return false; | |
232 } | |
233 bool ret = false; | |
234 HPTPROVIDER provider = NULL; | |
235 PTOpenProvider(UTF8ToWide(printer_name.c_str()).c_str(), 1, &provider); | |
236 if (provider) { | |
237 ScopedComPtr<IStream> print_ticket_stream; | |
238 CreateStreamOnHGlobal(NULL, TRUE, print_ticket_stream.Receive()); | |
239 ULONG bytes_written = 0; | |
240 print_ticket_stream->Write(print_ticket_data.c_str(), | |
241 print_ticket_data.length(), | |
242 &bytes_written); | |
243 DCHECK(bytes_written == print_ticket_data.length()); | |
244 LARGE_INTEGER pos = {0}; | |
245 ULARGE_INTEGER new_pos = {0}; | |
246 print_ticket_stream->Seek(pos, STREAM_SEEK_SET, &new_pos); | |
247 ScopedBstr error; | |
248 ScopedComPtr<IStream> result_ticket_stream; | |
249 CreateStreamOnHGlobal(NULL, TRUE, result_ticket_stream.Receive()); | |
250 ret = SUCCEEDED(PTMergeAndValidatePrintTicket(provider, | |
251 print_ticket_stream.get(), | |
252 NULL, | |
253 kPTJobScope, | |
254 result_ticket_stream.get(), | |
255 error.Receive())); | |
256 PTCloseProvider(provider); | |
257 } | |
258 return ret; | |
259 } | |
260 | |
261 std::string GenerateProxyId() { | |
262 GUID proxy_id = {0}; | |
263 HRESULT hr = UuidCreate(&proxy_id); | |
264 DCHECK(SUCCEEDED(hr)); | |
265 wchar_t* proxy_id_as_string = NULL; | |
266 UuidToString(&proxy_id, reinterpret_cast<RPC_WSTR *>(&proxy_id_as_string)); | |
267 DCHECK(proxy_id_as_string); | |
268 std::string ret; | |
269 WideToUTF8(proxy_id_as_string, wcslen(proxy_id_as_string), &ret); | |
270 RpcStringFree(reinterpret_cast<RPC_WSTR *>(&proxy_id_as_string)); | |
271 return ret; | |
272 } | |
273 | |
274 bool SpoolPrintJob(const std::string& print_ticket, | |
275 const FilePath& print_data_file_path, | |
276 const std::string& print_data_mime_type, | |
277 const std::string& printer_name, | |
278 const std::string& job_title, | |
279 PlatformJobId* job_id_ret) { | |
280 if (!InitXPSModule()) { | |
281 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) | |
282 return false; | |
283 } | |
284 DevMode pt_dev_mode; | |
285 HRESULT hr = PrintTicketToDevMode(printer_name, print_ticket, &pt_dev_mode); | |
286 if (FAILED(hr)) { | |
287 NOTREACHED(); | |
288 return false; | |
289 } | |
290 ScopedHDC dc(CreateDC(L"WINSPOOL", UTF8ToWide(printer_name).c_str(), NULL, | |
291 pt_dev_mode.dm_)); | |
292 if (!dc.Get()) { | |
293 NOTREACHED(); | |
294 return false; | |
295 } | |
296 hr = E_FAIL; | |
297 DOCINFO di = {0}; | |
298 di.cbSize = sizeof(DOCINFO); | |
299 std::wstring doc_name = UTF8ToWide(job_title); | |
300 di.lpszDocName = doc_name.c_str(); | |
301 int job_id = StartDoc(dc.Get(), &di); | |
302 if (SP_ERROR != job_id) { | |
303 if (print_data_mime_type == "application/pdf") { | |
304 hr = PrintPdf2DC(dc.Get(), print_data_file_path); | |
305 } else { | |
306 NOTREACHED(); | |
307 } | |
308 EndDoc(dc.Get()); | |
309 if (SUCCEEDED(hr) && job_id_ret) { | |
310 *job_id_ret = job_id; | |
311 } | |
312 } | |
313 return SUCCEEDED(hr); | |
314 } | |
315 | |
316 bool GetJobDetails(const std::string& printer_name, | |
317 PlatformJobId job_id, | |
318 PrintJobDetails *job_details) { | |
319 DCHECK(job_details); | |
320 HANDLE printer_handle = NULL; | |
321 std::wstring printer_name_wide = UTF8ToWide(printer_name); | |
322 OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, | |
323 NULL); | |
324 DCHECK(printer_handle); | |
325 bool ret = false; | |
326 if (printer_handle) { | |
327 DWORD bytes_needed = 0; | |
328 GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed); | |
329 DWORD last_error = GetLastError(); | |
330 if (ERROR_INVALID_PARAMETER != last_error) { | |
331 // ERROR_INVALID_PARAMETER normally means that the job id is not valid. | |
332 DCHECK(last_error == ERROR_INSUFFICIENT_BUFFER); | |
333 scoped_ptr<BYTE> job_info_buffer(new BYTE[bytes_needed]); | |
334 if (GetJob(printer_handle, job_id, 1, job_info_buffer.get(), bytes_needed, | |
335 &bytes_needed)) { | |
336 JOB_INFO_1 *job_info = | |
337 reinterpret_cast<JOB_INFO_1 *>(job_info_buffer.get()); | |
338 if (job_info->pStatus) { | |
339 WideToUTF8(job_info->pStatus, wcslen(job_info->pStatus), | |
340 &job_details->status_message); | |
341 } | |
342 job_details->platform_status_flags = job_info->Status; | |
343 if ((job_info->Status & JOB_STATUS_COMPLETE) || | |
344 (job_info->Status & JOB_STATUS_PRINTED)) { | |
345 job_details->status = PRINT_JOB_STATUS_COMPLETED; | |
346 } else if (job_info->Status & JOB_STATUS_ERROR) { | |
347 job_details->status = PRINT_JOB_STATUS_ERROR; | |
348 } else { | |
349 job_details->status = PRINT_JOB_STATUS_IN_PROGRESS; | |
350 } | |
351 job_details->total_pages = job_info->TotalPages; | |
352 job_details->pages_printed = job_info->PagesPrinted; | |
353 ret = true; | |
354 } | |
355 } | |
356 ClosePrinter(printer_handle); | |
357 } | |
358 return ret; | |
359 } | |
360 | |
361 bool IsValidPrinter(const std::string& printer_name) { | |
362 std::wstring printer_name_wide = UTF8ToWide(printer_name); | |
363 HANDLE printer_handle = NULL; | |
364 OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, | |
365 NULL); | |
366 bool ret = false; | |
367 if (printer_handle) { | |
368 ret = true; | |
369 ClosePrinter(printer_handle); | |
370 } | |
371 return ret; | |
372 } | |
373 | |
374 class PrinterChangeNotifier::NotificationState | |
375 : public base::ObjectWatcher::Delegate { | |
376 public: | |
377 NotificationState() : printer_(NULL), printer_change_(NULL), delegate_(NULL) { | |
378 } | |
379 ~NotificationState() { | |
380 Stop(); | |
381 } | |
382 bool Start(const std::string& printer_name, | |
383 PrinterChangeNotifier::Delegate* delegate) { | |
384 delegate_ = delegate; | |
385 // An empty printer name means watch the current server, we need to pass | |
386 // NULL to OpenPrinter. | |
387 LPTSTR printer_name_to_use = NULL; | |
388 std::wstring printer_name_wide; | |
389 if (!printer_name.empty()) { | |
390 printer_name_wide = UTF8ToWide(printer_name); | |
391 printer_name_to_use = const_cast<LPTSTR>(printer_name_wide.c_str()); | |
392 } | |
393 bool ret = false; | |
394 OpenPrinter(printer_name_to_use, &printer_, NULL); | |
395 if (printer_) { | |
396 printer_change_ = FindFirstPrinterChangeNotification( | |
397 printer_, PRINTER_CHANGE_PRINTER|PRINTER_CHANGE_JOB, 0, NULL); | |
398 if (printer_change_) { | |
399 ret = watcher_.StartWatching(printer_change_, this); | |
400 } | |
401 } | |
402 if (!ret) { | |
403 Stop(); | |
404 } | |
405 return ret; | |
406 } | |
407 bool Stop() { | |
408 watcher_.StopWatching(); | |
409 if (printer_) { | |
410 ClosePrinter(printer_); | |
411 printer_ = NULL; | |
412 } | |
413 if (printer_change_) { | |
414 FindClosePrinterChangeNotification(printer_change_); | |
415 printer_change_ = NULL; | |
416 } | |
417 return true; | |
418 } | |
419 | |
420 void OnObjectSignaled(HANDLE object) { | |
421 DWORD change = 0; | |
422 FindNextPrinterChangeNotification(object, &change, NULL, NULL); | |
423 | |
424 if (change != ((PRINTER_CHANGE_PRINTER|PRINTER_CHANGE_JOB) & | |
425 (~PRINTER_CHANGE_FAILED_CONNECTION_PRINTER))) { | |
426 // For printer connections, we get spurious change notifications with | |
427 // all flags set except PRINTER_CHANGE_FAILED_CONNECTION_PRINTER. | |
428 // Ignore these. | |
429 if (change & PRINTER_CHANGE_ADD_PRINTER) { | |
430 delegate_->OnPrinterAdded(); | |
431 } else if (change & PRINTER_CHANGE_DELETE_PRINTER) { | |
432 delegate_->OnPrinterDeleted(); | |
433 } else if (change & PRINTER_CHANGE_SET_PRINTER) { | |
434 delegate_->OnPrinterChanged(); | |
435 } | |
436 if (change & PRINTER_CHANGE_JOB) { | |
437 delegate_->OnJobChanged(); | |
438 } | |
439 } | |
440 watcher_.StartWatching(printer_change_, this); | |
441 } | |
442 HANDLE printer_handle() const { | |
443 return printer_; | |
444 } | |
445 private: | |
446 base::ObjectWatcher watcher_; | |
447 HANDLE printer_; // The printer being watched | |
448 HANDLE printer_change_; // Returned by FindFirstPrinterChangeNotifier | |
449 PrinterChangeNotifier::Delegate* delegate_; // Delegate to notify | |
450 bool did_signal_; // DoneWaiting was called | |
451 }; | |
452 | |
453 PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { | |
454 } | |
455 | |
456 PrinterChangeNotifier::~PrinterChangeNotifier() { | |
457 StopWatching(); | |
458 } | |
459 | |
460 bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, | |
461 Delegate* delegate) { | |
462 if (state_) { | |
463 NOTREACHED(); | |
464 return false; | |
465 } | |
466 state_ = new NotificationState; | |
467 if (!state_->Start(printer_name, delegate)) { | |
468 StopWatching(); | |
469 return false; | |
470 } | |
471 return true; | |
472 } | |
473 | |
474 bool PrinterChangeNotifier::StopWatching() { | |
475 if (!state_) { | |
476 return false; | |
477 } | |
478 state_->Stop(); | |
479 delete state_; | |
480 state_ = NULL; | |
481 return true; | |
482 } | |
483 | |
484 bool PrinterChangeNotifier::GetCurrentPrinterInfo( | |
485 PrinterBasicInfo* printer_info) { | |
486 if (!state_) { | |
487 return false; | |
488 } | |
489 DCHECK(printer_info); | |
490 DWORD bytes_needed = 0; | |
491 bool ret = false; | |
492 GetPrinter(state_->printer_handle(), 2, NULL, 0, &bytes_needed); | |
493 if (0 != bytes_needed) { | |
494 scoped_ptr<BYTE> printer_info_buffer(new BYTE[bytes_needed]); | |
495 if (GetPrinter(state_->printer_handle(), 2, printer_info_buffer.get(), | |
496 bytes_needed, &bytes_needed)) { | |
497 PRINTER_INFO_2* printer_info_win = | |
498 reinterpret_cast<PRINTER_INFO_2*>(printer_info_buffer.get()); | |
499 printer_info->printer_name = WideToUTF8(printer_info_win->pPrinterName); | |
500 printer_info->printer_description = | |
501 WideToUTF8(printer_info_win->pComment); | |
502 printer_info->printer_status = printer_info_win->Status; | |
503 ret = true; | |
504 } | |
505 } | |
506 return ret; | |
507 } | |
508 } // namespace cloud_print | |
509 | |
OLD | NEW |