1 // Copyright (c) 2012 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 "cloud_print/virtual_driver/win/port_monitor/port_monitor.h"
6
7 #include <windows.h>
8 #include <lmcons.h>
9 #include <shellapi.h>
10 #include <shlobj.h>
11 #include <strsafe.h>
12 #include <userenv.h>
13 #include <winspool.h>
14
15 #include "base/at_exit.h"
16 #include "base/command_line.h"
17 #include "base/files/file_enumerator.h"
18 #include "base/files/file_util.h"
19 #include "base/logging.h"
20 #include "base/path_service.h"
21 #include "base/process/launch.h"
22 #include "base/process/process.h"
23 #include "base/strings/string16.h"
24 #include "base/win/registry.h"
25 #include "base/win/scoped_handle.h"
26 #include "base/win/windows_version.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
29 #include "cloud_print/common/win/cloud_print_utils.h"
30 #include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
31 #include "cloud_print/virtual_driver/win/virtual_driver_consts.h"
32 #include "cloud_print/virtual_driver/win/virtual_driver_helpers.h"
33
34 namespace cloud_print {
35
36 namespace {
37
38 const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe";
39
40 const char kChromeInstallUrl[] =
41 "http://google.com/cloudprint/learn/chrome.html";
42
43 const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint";
44
45 const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
46
47 const wchar_t kAppDataDir[] = L"Google\\Cloud Printer";
48
49 struct MonitorData {
50 scoped_ptr<base::AtExitManager> at_exit_manager;
51 };
52
53 struct PortData {
PortDatacloud_print::__anon84b907ad0111::PortData54 PortData() : job_id(0), printer_handle(NULL), file(0) {
55 }
~PortDatacloud_print::__anon84b907ad0111::PortData56 ~PortData() {
57 Close();
58 }
Closecloud_print::__anon84b907ad0111::PortData59 void Close() {
60 if (printer_handle) {
61 ClosePrinter(printer_handle);
62 printer_handle = NULL;
63 }
64 if (file) {
65 base::CloseFile(file);
66 file = NULL;
67 }
68 }
69 DWORD job_id;
70 HANDLE printer_handle;
71 FILE* file;
72 base::FilePath file_path;
73 };
74
75 typedef struct {
76 ACCESS_MASK granted_access;
77 } XcvUiData;
78
79
80 MONITORUI g_monitor_ui = {
81 sizeof(MONITORUI),
82 MonitorUiAddPortUi,
83 MonitorUiConfigureOrDeletePortUI,
84 MonitorUiConfigureOrDeletePortUI
85 };
86
87 MONITOR2 g_monitor_2 = {
88 sizeof(MONITOR2),
89 Monitor2EnumPorts,
90 Monitor2OpenPort,
91 NULL, // OpenPortEx is not supported.
92 Monitor2StartDocPort,
93 Monitor2WritePort,
94 Monitor2ReadPort,
95 Monitor2EndDocPort,
96 Monitor2ClosePort,
97 NULL, // AddPort is not supported.
98 NULL, // AddPortEx is not supported.
99 NULL, // ConfigurePort is not supported.
100 NULL, // DeletePort is not supported.
101 NULL,
102 NULL, // SetPortTimeOuts is not supported.
103 Monitor2XcvOpenPort,
104 Monitor2XcvDataPort,
105 Monitor2XcvClosePort,
106 Monitor2Shutdown
107 };
108
GetAppDataDir()109 base::FilePath GetAppDataDir() {
110 base::FilePath file_path;
111 base::win::Version version = base::win::GetVersion();
112 int path_id = (version >= base::win::VERSION_VISTA) ?
113 base::DIR_LOCAL_APP_DATA_LOW : base::DIR_LOCAL_APP_DATA;
114 if (!PathService::Get(path_id, &file_path)) {
115 LOG(ERROR) << "Can't get DIR_LOCAL_APP_DATA";
116 return base::FilePath();
117 }
118 return file_path.Append(kAppDataDir);
119 }
120
121 // Delete files which where not deleted by chrome.
DeleteLeakedFiles(const base::FilePath & dir)122 void DeleteLeakedFiles(const base::FilePath& dir) {
123 base::Time delete_before = base::Time::Now() - base::TimeDelta::FromDays(1);
124 base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES);
125 for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
126 file_path = enumerator.Next()) {
127 if (enumerator.GetInfo().GetLastModifiedTime() < delete_before)
128 base::DeleteFile(file_path, false);
129 }
130 }
131
132 // Attempts to retrieve the title of the specified print job.
133 // On success returns TRUE and the first title_chars characters of the job title
134 // are copied into title.
135 // On failure returns FALSE and title is unmodified.
GetJobTitle(HANDLE printer_handle,DWORD job_id,base::string16 * title)136 bool GetJobTitle(HANDLE printer_handle,
137 DWORD job_id,
138 base::string16 *title) {
139 DCHECK(printer_handle != NULL);
140 DCHECK(title != NULL);
141 DWORD bytes_needed = 0;
142 GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed);
143 if (bytes_needed == 0) {
144 LOG(ERROR) << "Unable to get bytes needed for job info.";
145 return false;
146 }
147 scoped_ptr<BYTE[]> buffer(new BYTE[bytes_needed]);
148 if (!GetJob(printer_handle,
149 job_id,
150 1,
151 buffer.get(),
152 bytes_needed,
153 &bytes_needed)) {
154 LOG(ERROR) << "Unable to get job info.";
155 return false;
156 }
157 JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
158 *title = job_info->pDocument;
159 return true;
160 }
161
162 // Handler for the UI functions exported by the port monitor.
163 // Verifies that a valid parent Window exists and then just displays an
164 // error message to let the user know that there is no interactive
165 // configuration.
HandlePortUi(HWND hwnd,const base::string16 & caption)166 void HandlePortUi(HWND hwnd, const base::string16& caption) {
167 if (hwnd != NULL && IsWindow(hwnd)) {
168 DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName);
169 }
170 }
171
172 // Gets the primary token for the user that submitted the print job.
GetUserToken(HANDLE * primary_token)173 bool GetUserToken(HANDLE* primary_token) {
174 HANDLE token = NULL;
175 if (!OpenThreadToken(GetCurrentThread(),
176 TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
177 FALSE,
178 &token)) {
179 LOG(ERROR) << "Unable to get thread token.";
180 return false;
181 }
182 base::win::ScopedHandle token_scoped(token);
183 if (!DuplicateTokenEx(token,
184 TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
185 NULL,
186 SecurityImpersonation,
187 TokenPrimary,
188 primary_token)) {
189 LOG(ERROR) << "Unable to get primary thread token.";
190 return false;
191 }
192 return true;
193 }
194
195 // Launches the Cloud Print dialog in Chrome.
196 // xps_path references a file to print.
197 // job_title is the title to be used for the resulting print job.
LaunchPrintDialog(const base::FilePath & xps_path,const base::string16 & job_title)198 bool LaunchPrintDialog(const base::FilePath& xps_path,
199 const base::string16& job_title) {
200 HANDLE token = NULL;
201 if (!GetUserToken(&token)) {
202 LOG(ERROR) << "Unable to get user token.";
203 return false;
204 }
205 base::win::ScopedHandle primary_token_scoped(token);
206
207 base::FilePath chrome_path = GetChromeExePath();
208 if (chrome_path.empty()) {
209 LOG(ERROR) << "Unable to get chrome exe path.";
210 return false;
211 }
212
213 CommandLine command_line(chrome_path);
214
215 base::FilePath chrome_profile = GetChromeProfilePath();
216 if (!chrome_profile.empty())
217 command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile);
218
219 command_line.AppendSwitchPath(switches::kCloudPrintFile, xps_path);
220 command_line.AppendSwitchNative(switches::kCloudPrintFileType, kXpsMimeType);
221 command_line.AppendSwitchNative(switches::kCloudPrintJobTitle, job_title);
222 base::LaunchOptions options;
223 options.as_user = primary_token_scoped.Get();
224 base::LaunchProcess(command_line, options, NULL);
225 return true;
226 }
227
228 // Launches a page to allow the user to download chrome.
229 // TODO(abodenha@chromium.org) Point to a custom page explaining what's wrong
230 // rather than the generic chrome download page. See
231 // http://code.google.com/p/chromium/issues/detail?id=112019
LaunchChromeDownloadPage()232 void LaunchChromeDownloadPage() {
233 if (kIsUnittest)
234 return;
235 HANDLE token = NULL;
236 if (!GetUserToken(&token)) {
237 LOG(ERROR) << "Unable to get user token.";
238 return;
239 }
240 base::win::ScopedHandle token_scoped(token);
241
242 base::FilePath ie_path;
243 PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path);
244 ie_path = ie_path.Append(kIePath);
245 CommandLine command_line(ie_path);
246 command_line.AppendArg(kChromeInstallUrl);
247
248 base::LaunchOptions options;
249 options.as_user = token_scoped.Get();
250 base::LaunchProcess(command_line, options, NULL);
251 }
252
253 // Returns false if the print job is being run in a context
254 // that shouldn't be launching Chrome.
ValidateCurrentUser()255 bool ValidateCurrentUser() {
256 HANDLE token = NULL;
257 if (!GetUserToken(&token)) {
258 // If we can't get the token we're probably not impersonating
259 // the user, so validation should fail.
260 return false;
261 }
262 base::win::ScopedHandle token_scoped(token);
263
264 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
265 DWORD session_id = 0;
266 DWORD dummy;
267 if (!GetTokenInformation(token_scoped.Get(),
268 TokenSessionId,
269 reinterpret_cast<void *>(&session_id),
270 sizeof(DWORD),
271 &dummy)) {
272 return false;
273 }
274 if (session_id == 0) {
275 return false;
276 }
277 }
278 return true;
279 }
280 } // namespace
281
ReadPathFromRegistry(HKEY root,const wchar_t * path_name)282 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) {
283 base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ);
284 base::string16 data;
285 if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) &&
286 base::PathExists(base::FilePath(data))) {
287 return base::FilePath(data);
288 }
289 return base::FilePath();
290 }
291
ReadPathFromAnyRegistry(const wchar_t * path_name)292 base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) {
293 base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name);
294 if (!result.empty())
295 return result;
296 return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name);
297 }
298
GetChromeExePath()299 base::FilePath GetChromeExePath() {
300 base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue);
301 if (!path.empty())
302 return path;
303 return chrome_launcher_support::GetAnyChromePath();
304 }
305
GetChromeProfilePath()306 base::FilePath GetChromeProfilePath() {
307 base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue);
308 if (!path.empty() && base::DirectoryExists(path))
309 return path;
310 return base::FilePath();
311 }
312
Monitor2EnumPorts(HANDLE,wchar_t *,DWORD level,BYTE * ports,DWORD ports_size,DWORD * needed_bytes,DWORD * returned)313 BOOL WINAPI Monitor2EnumPorts(HANDLE,
314 wchar_t*,
315 DWORD level,
316 BYTE* ports,
317 DWORD ports_size,
318 DWORD* needed_bytes,
319 DWORD* returned) {
320 if (needed_bytes == NULL) {
321 LOG(ERROR) << "needed_bytes should not be NULL.";
322 SetLastError(ERROR_INVALID_PARAMETER);
323 return FALSE;
324 }
325 if (level == 1) {
326 *needed_bytes = sizeof(PORT_INFO_1);
327 } else if (level == 2) {
328 *needed_bytes = sizeof(PORT_INFO_2);
329 } else {
330 LOG(ERROR) << "Level " << level << "is not supported.";
331 SetLastError(ERROR_INVALID_LEVEL);
332 return FALSE;
333 }
334 *needed_bytes += static_cast<DWORD>(cloud_print::kPortNameSize);
335 if (ports_size < *needed_bytes) {
336 LOG(WARNING) << *needed_bytes << " bytes are required. Only "
337 << ports_size << " were allocated.";
338 SetLastError(ERROR_INSUFFICIENT_BUFFER);
339 return FALSE;
340 }
341 if (ports == NULL) {
342 LOG(ERROR) << "ports should not be NULL.";
343 SetLastError(ERROR_INVALID_PARAMETER);
344 return FALSE;
345 }
346 if (returned == NULL) {
347 LOG(ERROR) << "returned should not be NULL.";
348 SetLastError(ERROR_INVALID_PARAMETER);
349 return FALSE;
350 }
351
352 // Windows expects any strings refernced by PORT_INFO_X structures to
353 // appear at the END of the buffer referenced by ports. Placing
354 // strings immediately after the PORT_INFO_X structure will cause
355 // EnumPorts to fail until the spooler is restarted.
356 // This is NOT mentioned in the documentation.
357 wchar_t* string_target =
358 reinterpret_cast<wchar_t*>(ports + ports_size -
359 cloud_print::kPortNameSize);
360 if (level == 1) {
361 PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports);
362 port_info->pName = string_target;
363 StringCbCopy(port_info->pName,
364 cloud_print::kPortNameSize,
365 cloud_print::kPortName);
366 } else {
367 PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports);
368 port_info->pPortName = string_target;
369 StringCbCopy(port_info->pPortName,
370 cloud_print::kPortNameSize,
371 cloud_print::kPortName);
372 port_info->pMonitorName = NULL;
373 port_info->pDescription = NULL;
374 port_info->fPortType = PORT_TYPE_WRITE;
375 port_info->Reserved = 0;
376 }
377 *returned = 1;
378 return TRUE;
379 }
380
Monitor2OpenPort(HANDLE,wchar_t *,HANDLE * handle)381 BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) {
382 if (handle == NULL) {
383 LOG(ERROR) << "handle should not be NULL.";
384 SetLastError(ERROR_INVALID_PARAMETER);
385 return FALSE;
386 }
387 *handle = new PortData();
388 return TRUE;
389 }
390
Monitor2StartDocPort(HANDLE port_handle,wchar_t * printer_name,DWORD job_id,DWORD,BYTE *)391 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
392 wchar_t* printer_name,
393 DWORD job_id,
394 DWORD,
395 BYTE*) {
396 SetGoogleUpdateUsage(kGoogleUpdateProductId);
397 if (port_handle == NULL) {
398 LOG(ERROR) << "port_handle should not be NULL.";
399 SetLastError(ERROR_INVALID_PARAMETER);
400 return FALSE;
401 }
402 if (printer_name == NULL) {
403 LOG(ERROR) << "printer_name should not be NULL.";
404 SetLastError(ERROR_INVALID_PARAMETER);
405 return FALSE;
406 }
407 if (!ValidateCurrentUser()) {
408 // TODO(abodenha@chromium.org) Abort the print job.
409 return FALSE;
410 }
411 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
412 port_data->job_id = job_id;
413 if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) {
414 LOG(WARNING) << "Unable to open printer " << printer_name << ".";
415 // We can continue without a handle to the printer.
416 // It just means we can't get the job title or tell the spooler that
417 // the print job is complete.
418 // This is the normal flow during a unit test.
419 port_data->printer_handle = NULL;
420 }
421 base::FilePath& file_path = port_data->file_path;
422 base::FilePath app_data_dir = GetAppDataDir();
423 if (app_data_dir.empty())
424 return FALSE;
425 DeleteLeakedFiles(app_data_dir);
426 if (!base::CreateDirectory(app_data_dir) ||
427 !base::CreateTemporaryFileInDir(app_data_dir, &file_path)) {
428 LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value();
429 return FALSE;
430 }
431 port_data->file = base::OpenFile(file_path, "wb+");
432 if (port_data->file == NULL) {
433 LOG(ERROR) << "Error opening file " << file_path.value() << ".";
434 return FALSE;
435 }
436 return TRUE;
437 }
438
Monitor2WritePort(HANDLE port_handle,BYTE * buffer,DWORD buffer_size,DWORD * bytes_written)439 BOOL WINAPI Monitor2WritePort(HANDLE port_handle,
440 BYTE* buffer,
441 DWORD buffer_size,
442 DWORD* bytes_written) {
443 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
444 if (!ValidateCurrentUser()) {
445 // TODO(abodenha@chromium.org) Abort the print job.
446 return FALSE;
447 }
448 *bytes_written =
449 static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file));
450 if (*bytes_written > 0) {
451 return TRUE;
452 } else {
453 return FALSE;
454 }
455 }
456
Monitor2ReadPort(HANDLE,BYTE *,DWORD,DWORD * read_bytes)457 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) {
458 LOG(ERROR) << "Read is not supported.";
459 *read_bytes = 0;
460 SetLastError(ERROR_NOT_SUPPORTED);
461 return FALSE;
462 }
463
Monitor2EndDocPort(HANDLE port_handle)464 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
465 if (!ValidateCurrentUser()) {
466 // TODO(abodenha@chromium.org) Abort the print job.
467 return FALSE;
468 }
469 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
470 if (port_data == NULL) {
471 SetLastError(ERROR_INVALID_PARAMETER);
472 return FALSE;
473 }
474
475 if (port_data->file != NULL) {
476 base::CloseFile(port_data->file);
477 port_data->file = NULL;
478 bool delete_file = true;
479 int64 file_size = 0;
480 base::GetFileSize(port_data->file_path, &file_size);
481 if (file_size > 0) {
482 base::string16 job_title;
483 if (port_data->printer_handle != NULL) {
484 GetJobTitle(port_data->printer_handle,
485 port_data->job_id,
486 &job_title);
487 }
488 if (!LaunchPrintDialog(port_data->file_path, job_title)) {
489 LaunchChromeDownloadPage();
490 } else {
491 delete_file = false;
492 }
493 }
494 if (delete_file)
495 base::DeleteFile(port_data->file_path, false);
496 }
497 if (port_data->printer_handle != NULL) {
498 // Tell the spooler that the job is complete.
499 SetJob(port_data->printer_handle,
500 port_data->job_id,
501 0,
502 NULL,
503 JOB_CONTROL_SENT_TO_PRINTER);
504 }
505 port_data->Close();
506 // Return success even if we can't display the dialog.
507 // TODO(abodenha@chromium.org) Come up with a better way of handling
508 // this situation.
509 return TRUE;
510 }
511
Monitor2ClosePort(HANDLE port_handle)512 BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) {
513 if (port_handle == NULL) {
514 LOG(ERROR) << "port_handle should not be NULL.";
515 SetLastError(ERROR_INVALID_PARAMETER);
516 return FALSE;
517 }
518 delete reinterpret_cast<PortData*>(port_handle);
519 return TRUE;
520 }
521
Monitor2Shutdown(HANDLE monitor_handle)522 VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) {
523 if (monitor_handle != NULL) {
524 delete reinterpret_cast<MonitorData*>(monitor_handle);
525 }
526 }
527
Monitor2XcvOpenPort(HANDLE,const wchar_t *,ACCESS_MASK granted_access,HANDLE * handle)528 BOOL WINAPI Monitor2XcvOpenPort(HANDLE,
529 const wchar_t*,
530 ACCESS_MASK granted_access,
531 HANDLE* handle) {
532 if (handle == NULL) {
533 LOG(ERROR) << "handle should not be NULL.";
534 SetLastError(ERROR_INVALID_PARAMETER);
535 return FALSE;
536 }
537 XcvUiData* xcv_data = new XcvUiData();
538 xcv_data->granted_access = granted_access;
539 *handle = xcv_data;
540 return TRUE;
541 }
542
Monitor2XcvDataPort(HANDLE xcv_handle,const wchar_t * data_name,BYTE *,DWORD,BYTE * output_data,DWORD output_data_bytes,DWORD * output_data_bytes_needed)543 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle,
544 const wchar_t* data_name,
545 BYTE*,
546 DWORD,
547 BYTE* output_data,
548 DWORD output_data_bytes,
549 DWORD* output_data_bytes_needed) {
550 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle);
551 DWORD ret_val = ERROR_SUCCESS;
552 if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) {
553 return ERROR_ACCESS_DENIED;
554 }
555 if (output_data == NULL || output_data_bytes == 0) {
556 return ERROR_INVALID_PARAMETER;
557 }
558 // We don't handle AddPort or DeletePort since we don't support
559 // dynamic creation of ports.
560 if (lstrcmp(L"MonitorUI", data_name) == 0) {
561 DWORD dll_path_len = 0;
562 base::FilePath dll_path(GetPortMonitorDllName());
563 dll_path_len = static_cast<DWORD>(dll_path.value().length());
564 if (output_data_bytes_needed != NULL) {
565 *output_data_bytes_needed = dll_path_len;
566 }
567 if (output_data_bytes < dll_path_len) {
568 return ERROR_INSUFFICIENT_BUFFER;
569 } else {
570 ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data),
571 output_data_bytes,
572 dll_path.value().c_str());
573 }
574 } else {
575 return ERROR_INVALID_PARAMETER;
576 }
577 return ret_val;
578 }
579
Monitor2XcvClosePort(HANDLE handle)580 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) {
581 delete reinterpret_cast<XcvUiData*>(handle);
582 return TRUE;
583 }
584
MonitorUiAddPortUi(const wchar_t *,HWND hwnd,const wchar_t * monitor_name,wchar_t **)585 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
586 HWND hwnd,
587 const wchar_t* monitor_name,
588 wchar_t**) {
589 HandlePortUi(hwnd, monitor_name);
590 return TRUE;
591 }
592
MonitorUiConfigureOrDeletePortUI(const wchar_t *,HWND hwnd,const wchar_t * port_name)593 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
594 HWND hwnd,
595 const wchar_t* port_name) {
596 HandlePortUi(hwnd, port_name);
597 return TRUE;
598 }
599
600 } // namespace cloud_print
601
InitializePrintMonitor2(MONITORINIT *,HANDLE * handle)602 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*,
603 HANDLE* handle) {
604 if (handle == NULL) {
605 SetLastError(ERROR_INVALID_PARAMETER);
606 return NULL;
607 }
608 cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData;
609 *handle = monitor_data;
610 if (!cloud_print::kIsUnittest) {
611 // Unit tests set up their own AtExitManager
612 monitor_data->at_exit_manager.reset(new base::AtExitManager());
613 // Single spooler.exe handles verbose users.
614 PathService::DisableCache();
615 }
616 return &cloud_print::g_monitor_2;
617 }
618
InitializePrintMonitorUI(void)619 MONITORUI* WINAPI InitializePrintMonitorUI(void) {
620 return &cloud_print::g_monitor_ui;
621 }
622
623