Chromium Code Reviews| Index: cloud_print/virtual_driver/win/port_monitor/port_monitor.cc |
| =================================================================== |
| --- cloud_print/virtual_driver/win/port_monitor/port_monitor.cc (revision 0) |
| +++ cloud_print/virtual_driver/win/port_monitor/port_monitor.cc (revision 0) |
| @@ -0,0 +1,580 @@ |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "cloud_print/virtual_driver/win/port_monitor/port_monitor.h" |
| + |
| +#include <lmcons.h> |
| +#include <strsafe.h> |
| +#include <userenv.h> |
| +#include <windows.h> |
| +#include <winspool.h> |
| + |
| +#include "base/at_exit.h" |
| +#include "base/file_util.h" |
| +#include "base/logging.h" |
| +#include "base/path_service.h" |
| +#include "base/win/registry.h" |
| +#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h" |
| + |
| +namespace cloud_print { |
| + |
| +#ifdef _WIN64 |
| +const wchar_t kPortMonitorDllName[] = L"gcp_portmon64.dll"; |
| +#else |
| +const wchar_t kPortMonitorDllName[] = L"gcp_portmon.dll"; |
| +#endif |
| + |
| +const wchar_t kPortName[] = L"GCP:"; |
| +const wchar_t kChromeCommandLine[] = |
| + L"chrome.exe -cloud-print-file=\"%s\" " |
|
sanjeevr
2011/04/04 17:19:17
You should probably use chrome_switches.cc and .h.
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + L"-cloud-print-file-type=\"%s\" " |
| + L"-cloud-print-job-title=\"%s\""; |
| + |
| +const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument"; |
| + |
| +const wchar_t kChromePathRegKey[] = L"Software\\Google\\CloudPrint"; |
| + |
| + |
| +#ifndef UNIT_TEST |
| +const wchar_t kChromeExePath[] = L"google\\chrome\\application\\chrome.exe"; |
| +const wchar_t kChromePathRegValue[] = L"PathToChromeExe"; |
| +#endif |
| + |
| +const size_t kMaxCommandLineLen = 0x7FFF; |
| + |
| +const size_t kMaxMessageLen = 100; |
| + |
| +typedef struct { |
|
sanjeevr
2011/04/04 17:19:17
Please don't use C-style "typedef struct" (here an
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + base::AtExitManager* at_exit_manager; |
| +} MonitorData; |
| + |
| +typedef struct { |
| + DWORD job_id; |
| + HANDLE printer; |
| + FILE* file; |
| + FilePath* file_path; |
| +} PortData; |
| + |
| +typedef struct { |
| + ACCESS_MASK granted_access; |
| +} XcvUiData; |
| + |
| + |
| +MONITORUI MonitorUI = { |
|
sanjeevr
2011/04/04 17:19:17
g_monitor_ui ?
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + sizeof(MONITORUI), |
| + MonitorUiAddPortUi, |
| + MonitorUiConfigureOrDeletePortUI, |
| + MonitorUiConfigureOrDeletePortUI |
| +}; |
| + |
| +MONITOR2 Monitor2 = { |
|
sanjeevr
2011/04/04 17:19:17
g_monitor2
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + sizeof(MONITOR2), |
| + Monitor2EnumPorts, |
| + Monitor2OpenPort, |
| + NULL, // OpenPortEx is not supported. |
| + Monitor2StartDocPort, |
| + Monitor2WritePort, |
| + Monitor2ReadPort, |
| + Monitor2EndDocPort, |
| + Monitor2ClosePort, |
| + NULL, // AddPort is not supported. |
| + NULL, // AddPortEx is not supported. |
| + NULL, // ConfigurePort is not supported. |
| + NULL, // DeletePort is not supported. |
| + NULL, |
| + NULL, // SetPortTimeOuts is not supported. |
| + Monitor2XcvOpenPort, |
| + Monitor2XcvDataPort, |
| + Monitor2XcvClosePort, |
| + Monitor2Shutdown |
| +}; |
| + |
| +BOOL GetChromeExePath(std::wstring* chrome_path) { |
|
sanjeevr
2011/04/04 17:19:17
Use a C++ bool (here and for all other helper func
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + base::win::RegKey app_path_key(HKEY_CURRENT_USER, |
| + kChromePathRegKey, |
| + KEY_READ); |
| + DCHECK(chrome_path != NULL); |
| + std::wstring reg_data; |
| + if (SUCCEEDED(app_path_key.ReadValue(kChromePathRegValue, ®_data))) { |
| + if (!reg_data.empty() && file_util::PathExists(FilePath(reg_data))) { |
| + *chrome_path = reg_data; |
| + return TRUE; |
| + } |
| + } |
| + // First check %localappdata%\google\chrome\application\chrome.exe |
| + FilePath path; |
| + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); |
| + path = path.Append(kChromeExePath); |
| + if (file_util::PathExists(path)) { |
| + *chrome_path = path.value(); |
| + return TRUE; |
| + } |
| + |
| + // Chrome doesn't appear to be installed per user. |
| + // Now check %programfiles%\google\chrome\application |
| + PathService::Get(base::DIR_PROGRAM_FILES, &path); |
|
sanjeevr
2011/04/04 17:19:17
This may not be the right thing to do from a 64-bi
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + path = path.Append(kChromeExePath); |
| + if (file_util::PathExists(path)) { |
| + *chrome_path = path.value(); |
| + return TRUE; |
| + } |
| + return FALSE; |
| +} |
| + |
| +BOOL GetJobTitle(HANDLE printer, |
| + DWORD job_id, |
| + wchar_t* title, |
| + size_t title_chars) { |
|
sanjeevr
2011/04/04 17:19:17
Change this method to either return a string16 or
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + DCHECK(printer != NULL); |
| + DCHECK(title != NULL); |
| + DCHECK(title_chars != 0); |
| + BYTE* buffer = NULL; |
| + DWORD bytes_needed = 0; |
| + GetJob(printer, job_id, 1, NULL, 0, &bytes_needed); |
| + if (bytes_needed != 0) { |
| + buffer = new BYTE[bytes_needed]; |
|
sanjeevr
2011/04/04 17:19:17
buffer is leaked below. Use scoped_ptr
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + if (!GetJob(printer, job_id, 1, buffer, bytes_needed, &bytes_needed)) { |
| + LOG(ERROR) << "Unable to get job info"; |
| + return FALSE; |
| + } |
| + } else { |
| + return FALSE; |
| + } |
| + JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer); |
| + BOOL ret_val = |
| + SUCCEEDED(StringCchCopy(title, title_chars, job_info->pDocument)); |
| + delete[] buffer; |
| + return ret_val; |
| +} |
| + |
| +void HandlePortUi(HWND hwnd, const wchar_t* caption) { |
|
sanjeevr
2011/04/04 17:19:17
Chrome style is to use const std::string& (or cons
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + if (hwnd != NULL && IsWindow(hwnd)) { |
| + wchar_t message_text[kMaxMessageLen + 1] = L""; |
| + |
| + ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, |
| + NULL, |
| + CO_E_NOT_SUPPORTED, |
| + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| + message_text, |
| + kMaxMessageLen, |
| + NULL); |
| + ::MessageBox(hwnd, message_text, caption, MB_OK); |
| + } |
| +} |
| + |
| +BOOL LaunchPrintDialog(const wchar_t* xps_path, |
| + const wchar_t* job_title, |
| + HANDLE* process_handle) { |
| + DCHECK(xps_path != NULL); |
| + DCHECK(job_title != NULL); |
| + DCHECK(process_handle != NULL); |
| + BOOL ret_val = FALSE; |
| + HANDLE token = NULL; |
| + STARTUPINFO startup_info = {0}; |
| + PROCESS_INFORMATION process_info = {0}; |
| + startup_info.cb = sizeof(startup_info); |
| + if (OpenThreadToken(GetCurrentThread(), |
| + TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, |
| + FALSE, |
| + &token)) { |
|
sanjeevr
2011/04/04 17:19:17
Chrome style is to have multiple return points and
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + HANDLE primary_token = NULL; |
| + if (DuplicateTokenEx(token, |
| + TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, |
| + NULL, |
| + SecurityImpersonation, |
| + TokenPrimary, |
| + &primary_token)) { |
| + void* environment_block; |
| + if (CreateEnvironmentBlock(&environment_block, |
| + primary_token, |
| + FALSE)) { |
| + wchar_t command_line[kMaxCommandLineLen]; |
| + std::wstring chrome_path; |
| + if (GetChromeExePath(&chrome_path)) { |
| + StringCchPrintf(command_line, |
| + kMaxCommandLineLen, |
| + kChromeCommandLine, |
| + xps_path, |
| + kXpsMimeType, |
| + job_title); |
|
sanjeevr
2011/04/04 17:19:17
Use base::CommandLine
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + if (CreateProcessAsUser(primary_token, |
| + chrome_path.c_str(), |
| + command_line, |
| + NULL, |
| + NULL, |
| + FALSE, |
| + CREATE_UNICODE_ENVIRONMENT|CREATE_NEW_PROCESS_GROUP, |
| + environment_block, |
| + NULL, |
| + &startup_info, |
| + &process_info)) { |
| + *process_handle = process_info.hProcess; |
| + CloseHandle(process_info.hThread); |
| + ret_val = TRUE; |
| + } |
| + } |
| + DestroyEnvironmentBlock(environment_block); |
| + } |
| + CloseHandle(primary_token); |
| + } |
| + CloseHandle(token); |
| + } |
| + return ret_val; |
| +} |
| + |
| +bool ValidateCurrentUser() { |
| + wchar_t user_name[UNLEN + 1] = L""; |
| + DWORD name_size = sizeof(user_name); |
| + GetUserName(user_name, &name_size); |
| + LOG(INFO) << "Username is " << user_name; |
| + // TODO(abodenha@chromium.org) Return false if running as session 0 or |
| + // as local system. |
| + return true; |
| +} |
| + |
| +BOOL WINAPI Monitor2EnumPorts(HANDLE, |
| + wchar_t*, |
| + DWORD level, |
| + BYTE* ports, |
| + DWORD ports_size, |
| + DWORD* needed_bytes, |
| + DWORD* returned) { |
| + LOG(INFO) << "Monitor2EnumPorts"; |
| + |
| + if (needed_bytes == NULL) { |
| + LOG(ERROR) << "needed_bytes should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + if (level == 1) { |
| + *needed_bytes = sizeof(PORT_INFO_1); |
| + } else if (level == 2) { |
| + *needed_bytes = sizeof(PORT_INFO_2); |
| + } else { |
| + LOG(ERROR) << "Level " << level << "is not supported"; |
| + SetLastError(ERROR_INVALID_LEVEL); |
| + return FALSE; |
| + } |
| + *needed_bytes += sizeof(kPortName); |
| + if (ports_size < *needed_bytes) { |
| + LOG(WARNING) << *needed_bytes << " bytes are required. Only " |
| + << ports_size << " were allocated"; |
| + SetLastError(ERROR_INSUFFICIENT_BUFFER); |
| + return FALSE; |
| + } |
| + if (ports == NULL) { |
| + LOG(ERROR) << "ports should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + if (returned == NULL) { |
| + LOG(ERROR) << "returned should not be NULL\n"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + |
| + // Windows expects any strings refernced by PORT_INFO_X structures to |
| + // appear at the END of the buffer referenced by ports. Placing |
| + // strings immediately after the PORT_INFO_X structure will cause |
| + // EnumPorts to fail until the spooler is restarted. |
| + // This is NOT mentioned in the documentation. |
| + wchar_t* string_target = |
| + reinterpret_cast<wchar_t*>(ports + ports_size - sizeof(kPortName)); |
| + if (level == 1) { |
| + PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports); |
| + port_info->pName = string_target; |
| + StringCbCopy(port_info->pName, sizeof(kPortName), kPortName); |
| + } else { |
| + PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports); |
| + port_info->pPortName = string_target; |
| + StringCbCopy(port_info->pPortName, sizeof(kPortName), kPortName); |
| + port_info->pMonitorName = NULL; |
| + port_info->pDescription = NULL; |
| + port_info->fPortType = PORT_TYPE_WRITE; |
| + port_info->Reserved = 0; |
| + } |
| + *returned = 1; |
| + return TRUE; |
| +} |
| + |
| +BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) { |
| + LOG(INFO) << "Monitor2OpenPort"; |
| + |
| + PortData* port_data = |
| + reinterpret_cast<PortData*>(GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, |
| + sizeof(PortData))); |
| + if (port_data == NULL) { |
| + LOG(ERROR) << "Unable to allocate memory for internal structures."; |
| + SetLastError(E_OUTOFMEMORY); |
| + return FALSE; |
| + } |
| + if (handle == NULL) { |
| + LOG(ERROR) << "handle should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + port_data->file = NULL; |
| + port_data->file_path = new FilePath(); |
| + if (port_data->file_path == NULL) { |
| + LOG(ERROR) << "Unable to allocate memory for internal structures."; |
| + SetLastError(E_OUTOFMEMORY); |
| + return FALSE; |
| + } |
| + *handle = (HANDLE)port_data; |
| + return TRUE; |
| +} |
| + |
| +BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, |
| + wchar_t* printer_name, |
| + DWORD job_id, |
| + DWORD, |
| + BYTE*) { |
| + LOG(INFO) << "Monitor2StartDocPort"; |
| + if (port_handle == NULL) { |
| + LOG(ERROR) << "port_handle should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + if (printer_name == NULL) { |
| + LOG(ERROR) << "printer_name should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + if (!ValidateCurrentUser()) { |
| + // TODO(abodenha@chromium.org) Abort the print job. |
| + return FALSE; |
| + } |
| + PortData* port_data = reinterpret_cast<PortData*>(port_handle); |
| + port_data->job_id = job_id; |
| + if (!OpenPrinter(printer_name, &(port_data->printer), NULL)) { |
| + LOG(WARNING) << "Unable to open printer " << printer_name; |
| + // We can continue without a handle to the printer. |
| + // It just means we can't get the job title or tell the spooler that |
| + // the print job is complete. |
| + // This is the normal flow during a unit test. |
| + port_data->printer = NULL; |
| + } |
| + FilePath app_data; |
| + bool result = PathService::Get(base::DIR_LOCAL_APP_DATA_LOW, &app_data); |
| + file_util::CreateTemporaryFileInDir(app_data, port_data->file_path); |
| + port_data->file = file_util::OpenFile(*(port_data->file_path), "wb+"); |
|
sanjeevr
2011/04/04 17:19:17
Dealing in FILE* rather that file handles makes me
Albert Bodenhamer
2011/04/04 23:12:24
As discussed, we'll leave this as-is for now.
|
| + if (port_data->file == NULL) { |
| + LOG(ERROR) << "Error opening file " << port_data->file_path; |
| + return FALSE; |
| + } |
| + |
| + return TRUE; |
| +} |
| + |
| +BOOL WINAPI Monitor2WritePort(HANDLE port_handle, |
| + BYTE* buffer, |
| + DWORD buffer_size, |
| + DWORD* bytes_written) { |
| + LOG(INFO) << "Monitor2WritePort"; |
| + PortData* port_data = reinterpret_cast<PortData*>(port_handle); |
| + if (!ValidateCurrentUser()) { |
| + // TODO(abodenha@chromium.org) Abort the print job. |
| + return FALSE; |
| + } |
| + *bytes_written = |
| + static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file)); |
| + if (*bytes_written > 0) { |
| + return TRUE; |
| + } else { |
| + return FALSE; |
| + } |
| +} |
| + |
| +BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) { |
| + LOG(INFO) << "Monitor2ReadPort"; |
| + LOG(ERROR) << "Read is not supported"; |
| + *read_bytes = 0; |
|
sanjeevr
2011/04/04 17:19:17
Is there a well-known SetLastError that we should
Albert Bodenhamer
2011/04/04 23:12:24
A few make sense. I'm going with ERROR_NOT_SUPPOR
|
| + return FALSE; |
| +} |
| + |
| +BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) { |
| + LOG(INFO) << "Monitor2EndDocPort"; |
| + BOOL ret_val = FALSE; |
| + HANDLE process_handle = NULL; |
| + if (!ValidateCurrentUser()) { |
| + // TODO(abodenha@chromium.org) Abort the print job. |
| + return FALSE; |
| + } |
| + PortData* port_data = reinterpret_cast<PortData*>(port_handle); |
| + if (port_data == NULL) { |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + |
| + if (port_data->file != NULL) { |
| + file_util::CloseFile(port_data->file); |
| + port_data->file = NULL; |
| + wchar_t job_title[kMaxMessageLen] = L""; |
| + if (port_data->printer != NULL) { |
| + GetJobTitle(port_data->printer, |
| + port_data->job_id, |
| + job_title, |
| + kMaxMessageLen); |
| + } |
| + LaunchPrintDialog(port_data->file_path->value().c_str(), |
| + job_title, |
| + &process_handle); |
| + // Return success even if we can't display the dialog. |
| + // TODO(abodenha@chromium.org) Come up with a better way of handling |
| + // this situation. |
| + ret_val = TRUE; |
| + |
| + // Wait for the print dialog process to exit and then delete the file. |
| + // TODO(abodenha@chromium.org) Consider launching a thread to handle the |
| + // deletion. |
| + if (process_handle != NULL) { |
| + WaitForSingleObject(process_handle, INFINITE); |
| + } |
| + file_util::Delete(*(port_data->file_path), false); |
| + } |
| + if (port_data->printer != NULL) { |
| + // Tell the spooler that the job is complete. |
| + SetJob(port_data->printer, |
| + port_data->job_id, |
| + 0, |
| + NULL, |
| + JOB_CONTROL_SENT_TO_PRINTER); |
| + } |
| + ClosePrinter(port_data->printer); |
|
sanjeevr
2011/04/04 17:19:17
Nit: This should be inside the if block.
Albert Bodenhamer
2011/04/04 23:12:24
I pulled the PortData cleanup code into a helper f
|
| + port_data->printer = NULL; |
|
sanjeevr
2011/04/04 17:19:17
What about setting closing port_data->file and set
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + |
| + return ret_val; |
| +} |
| + |
| +BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) { |
| + LOG(INFO) << "Monitor2ClosePort"; |
| + if (port_handle == NULL) { |
| + LOG(ERROR) << "port_handle should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + PortData* port_data = reinterpret_cast<PortData*>(port_handle); |
| + |
| + delete port_data->file_path; |
|
sanjeevr
2011/04/04 17:19:17
We probably need to clear other fields too?
Albert Bodenhamer
2011/04/04 23:12:24
Done.
|
| + GlobalFree(port_handle); |
| + return TRUE; |
| +} |
| + |
| +VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) { |
| + LOG(INFO) << "Monitor2Shutdown"; |
| + if (monitor_handle != NULL) { |
| + MonitorData* monitor_data = |
| + reinterpret_cast<MonitorData*>(monitor_handle); |
| + delete monitor_data->at_exit_manager; |
| + GlobalFree(monitor_handle); |
| + } |
| +} |
| + |
| +BOOL WINAPI Monitor2XcvOpenPort(HANDLE, |
| + const wchar_t*, |
| + ACCESS_MASK granted_access, |
| + HANDLE* handle) { |
| + LOG(INFO) << "Monitor2XcvOpenPort"; |
| + if (handle == NULL) { |
| + LOG(ERROR) << "handle should not be NULL"; |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return FALSE; |
| + } |
| + XcvUiData* xcv_data; |
| + xcv_data = reinterpret_cast<XcvUiData*>(GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, |
| + sizeof(XcvUiData))); |
| + if (xcv_data == NULL) { |
| + LOG(ERROR) << "Unable to allocate memory for internal structures."; |
| + SetLastError(E_OUTOFMEMORY); |
| + return FALSE; |
| + } |
| + xcv_data->granted_access = granted_access; |
| + *handle = (HANDLE)xcv_data; |
| + return TRUE; |
| +} |
| + |
| +DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle, |
| + const wchar_t* data_name, |
| + BYTE*, |
| + DWORD, |
| + BYTE* output_data, |
| + DWORD output_data_bytes, |
| + DWORD* output_data_bytes_needed) { |
| + LOG(INFO) << "Monitor2XcvDataPort"; |
| + XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle); |
| + DWORD ret_val = ERROR_SUCCESS; |
| + if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) { |
| + return ERROR_ACCESS_DENIED; |
| + } |
| + if (output_data == NULL || output_data_bytes == 0) { |
| + return ERROR_INVALID_PARAMETER; |
| + } |
| + // We don't handle AddPort or DeletePort since we don't support |
| + // dynamic creation of ports. |
| + if (lstrcmp(L"MonitorUI", data_name) == 0) { |
| + if (output_data_bytes_needed != NULL) { |
| + *output_data_bytes_needed = sizeof(kPortMonitorDllName); |
| + } |
| + if (output_data_bytes < sizeof(kPortMonitorDllName)) { |
| + return ERROR_INSUFFICIENT_BUFFER; |
| + } else { |
| + ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data), |
| + output_data_bytes, |
| + kPortMonitorDllName); |
| + } |
| + } else { |
| + return ERROR_INVALID_PARAMETER; |
| + } |
| + return ret_val; |
| +} |
| + |
| +BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) { |
| + GlobalFree(handle); |
| + return TRUE; |
| +} |
| + |
| +BOOL WINAPI MonitorUiAddPortUi(const wchar_t*, |
| + HWND hwnd, |
| + const wchar_t* monitor_name, |
| + wchar_t**) { |
| + HandlePortUi(hwnd, monitor_name); |
| + return TRUE; |
| +} |
| + |
| +BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*, |
| + HWND hwnd, |
| + const wchar_t* port_name) { |
| + HandlePortUi(hwnd, port_name); |
| + return TRUE; |
| +} |
| + |
| +} // namespace cloud_print |
| + |
| +MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*, |
| + HANDLE* handle) { |
| + LOG(INFO) << "InitializePrintMonitor2"; |
| + cloud_print::MonitorData* monitor_data = |
| + reinterpret_cast<cloud_print::MonitorData*> |
| + (GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, sizeof(cloud_print::MonitorData))); |
| + if (monitor_data == NULL) { |
| + return NULL; |
| + } |
| + if (handle != NULL) { |
| + *handle = (HANDLE)monitor_data; |
| + #ifndef UNIT_TEST |
| + // Unit tests set up their own AtExitManager |
| + monitor_data->at_exit_manager = new base::AtExitManager(); |
| + #endif |
| + } else { |
| + SetLastError(ERROR_INVALID_PARAMETER); |
| + return NULL; |
| + } |
| + return &cloud_print::Monitor2; |
| +} |
| + |
| +MONITORUI* WINAPI InitializePrintMonitorUI(void) { |
| + LOG(INFO) << "InitializePrintMonitorUI"; |
| + return &cloud_print::MonitorUI; |
| +} |
| + |