summaryrefslogtreecommitdiffstats
path: root/cloud_print
diff options
context:
space:
mode:
authorabodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-06 20:01:57 +0000
committerabodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-06 20:01:57 +0000
commite08f6acd3e52d8d454d2f47edaac85cf752b147c (patch)
tree090bb96f1402c3564024dc39e623fd95f8cc429b /cloud_print
parent399092645ed91a419c53310ad45ca2a68c9a74f5 (diff)
downloadchromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.zip
chromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.tar.gz
chromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.tar.bz2
Initial CL for a simple port monitor for Windows to be used by the Cloud Print Virtual Print driver.
TEST=Everything should build and unit tests should pass. Review URL: http://codereview.chromium.org/6778001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80678 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cloud_print')
-rw-r--r--cloud_print/virtual_driver/virtual_driver.gyp83
-rw-r--r--cloud_print/virtual_driver/win/port_monitor/port_monitor.cc600
-rw-r--r--cloud_print/virtual_driver/win/port_monitor/port_monitor.def8
-rw-r--r--cloud_print/virtual_driver/win/port_monitor/port_monitor.h88
-rw-r--r--cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc220
-rw-r--r--cloud_print/virtual_driver/win/port_monitor/spooler_win.h109
6 files changed, 1108 insertions, 0 deletions
diff --git a/cloud_print/virtual_driver/virtual_driver.gyp b/cloud_print/virtual_driver/virtual_driver.gyp
new file mode 100644
index 0000000..853e027
--- /dev/null
+++ b/cloud_print/virtual_driver/virtual_driver.gyp
@@ -0,0 +1,83 @@
+# 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.
+
+{
+ 'includes': [
+ '../../build/common.gypi',
+ ],
+ 'target_defaults': {
+ 'variables': {
+ 'chromium_code': 1,
+ 'version_py_path': '../../chrome/tools/build/version.py',
+ 'version_path': 'VERSION',
+ },
+ 'include_dirs': [
+ '../..',
+ ],
+ 'libraries': [
+ 'userenv.lib',
+ ],
+ 'sources': [
+ 'win/port_monitor/port_monitor.cc',
+ 'win/port_monitor/port_monitor.h',
+ 'win/port_monitor/port_monitor.def',
+ ],
+ },
+ 'conditions': [
+ ['OS=="win"', {
+ 'targets' : [
+ {
+ 'target_name': 'gcp_portmon',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../chrome/chrome.gyp:common_constants',
+ ],
+ 'msvs_guid': 'ED3D7186-C94E-4D8B-A8E7-B7260F638F46',
+ },
+ {
+ 'target_name': 'gcp_portmon64',
+ 'type': 'loadable_module',
+ 'defines': [
+ '<@(nacl_win64_defines)',
+ ],
+ 'dependencies': [
+ '../../base/base.gyp:base_nacl_win64',
+ '../../chrome/chrome.gyp:common_constants_win64',
+ ],
+ 'msvs_guid': '9BB292F4-6104-495A-B415-C3E314F46D6F',
+ 'configurations': {
+ 'Common_Base': {
+ 'msvs_target_platform': 'x64',
+ },
+ },
+ },
+ {
+ 'target_name': 'virtual_driver_unittests',
+ 'type': 'executable',
+ 'msvs_guid': '97F82D29-58D8-4909-86C8-F2BBBCC4FEBF',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../chrome/chrome.gyp:common_constants',
+ '../../base/base.gyp:test_support_base',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ # Infrastructure files.
+ '../../base/test/run_all_unittests.cc',
+ 'win/port_monitor/port_monitor_unittest.cc'
+ ],
+ },
+ ],
+ },
+ ],
+ ]
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc b/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc
new file mode 100644
index 0000000..5cd870c
--- /dev/null
+++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc
@@ -0,0 +1,600 @@
+// 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 <shlobj.h>
+#include <strsafe.h>
+#include <userenv.h>
+#include <windows.h>
+#include <winspool.h>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process.h"
+#include "base/process_util.h"
+#include "base/string16.h"
+#include "base/win/registry.h"
+#include "base/win/scoped_handle.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
+
+namespace cloud_print {
+
+#ifndef UNIT_TEST
+const wchar_t kChromeExePath[] = L"google\\chrome\\application\\chrome.exe";
+const wchar_t kChromePathRegValue[] = L"PathToChromeExe";
+#endif
+
+const wchar_t kChromePathRegKey[] = L"Software\\Google\\CloudPrint";
+
+namespace {
+#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 kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
+
+const size_t kMaxCommandLineLen = 0x7FFF;
+
+const size_t kMaxMessageLen = 100;
+
+struct MonitorData {
+ base::AtExitManager* at_exit_manager;
+};
+
+struct PortData {
+ DWORD job_id;
+ HANDLE printer_handle;
+ FILE* file;
+ FilePath* file_path;
+};
+
+typedef struct {
+ ACCESS_MASK granted_access;
+} XcvUiData;
+
+
+MONITORUI g_monitor_ui = {
+ sizeof(MONITORUI),
+ MonitorUiAddPortUi,
+ MonitorUiConfigureOrDeletePortUI,
+ MonitorUiConfigureOrDeletePortUI
+};
+
+MONITOR2 g_monitor_2 = {
+ 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
+};
+
+// Frees any objects referenced by port_data and sets pointers to NULL.
+void CleanupPortData(PortData* port_data) {
+ delete port_data->file_path;
+ port_data->file_path = NULL;
+ if (port_data->printer_handle != NULL) {
+ ClosePrinter(port_data->printer_handle);
+ port_data->printer_handle = NULL;
+ }
+ if (port_data->file != NULL) {
+ file_util::CloseFile(port_data->file);
+ port_data->file = NULL;
+ }
+}
+
+// Attempts to retrieve the title of the specified print job.
+// On success returns TRUE and the first title_chars characters of the job title
+// are copied into title.
+// On failure returns FALSE and title is unmodified.
+bool GetJobTitle(HANDLE printer_handle,
+ DWORD job_id,
+ string16 *title) {
+ DCHECK(printer_handle != NULL);
+ DCHECK(title != NULL);
+ DWORD bytes_needed = 0;
+ GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed);
+ if (bytes_needed == 0) {
+ LOG(ERROR) << "Unable to get bytes needed for job info.";
+ return false;
+ }
+ scoped_ptr<BYTE> buffer(new BYTE[bytes_needed]);
+ if (!GetJob(printer_handle,
+ job_id,
+ 1,
+ buffer.get(),
+ bytes_needed,
+ &bytes_needed)) {
+ LOG(ERROR) << "Unable to get job info.";
+ return false;
+ }
+ JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
+ *title = job_info->pDocument;
+ return true;
+}
+
+// Handler for the UI functions exported by the port monitor.
+// Verifies that a valid parent Window exists and then just displays an
+// error message to let the user know that there is no interactive
+// configuration.
+void HandlePortUi(HWND hwnd, const string16& caption) {
+ 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.c_str(), MB_OK);
+ }
+}
+
+// Launches the Cloud Print dialog in Chrome.
+// xps_path references a file to print.
+// job_title is the title to be used for the resulting print job.
+// process_handle is set to the handle of the resulting process.
+bool LaunchPrintDialog(const string16& xps_path,
+ const string16& job_title,
+ base::ProcessHandle* process_handle) {
+ DCHECK(process_handle != NULL);
+ HANDLE token = NULL;
+ if (!OpenThreadToken(GetCurrentThread(),
+ TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
+ FALSE,
+ &token)) {
+ LOG(ERROR) << "Unable to get thread token.";
+ return false;
+ }
+ base::win::ScopedHandle token_scoped(token);
+ if (!DuplicateTokenEx(token,
+ TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
+ NULL,
+ SecurityImpersonation,
+ TokenPrimary,
+ &token)) {
+ LOG(ERROR) << "Unable to get primary thread token.";
+ return false;
+ }
+ base::win::ScopedHandle primary_token_scoped(token);
+ FilePath chrome_path;
+ if (!GetChromeExePath(&chrome_path)) {
+ LOG(ERROR) << "Unable to get chrome exe path.";
+ return false;
+ }
+ CommandLine command_line(chrome_path);
+ command_line.AppendSwitchPath(switches::kCloudPrintFile,
+ FilePath(xps_path));
+ command_line.AppendSwitchNative(switches::kCloudPrintFileType,
+ kXpsMimeType);
+ command_line.AppendSwitchNative(switches::kCloudPrintJobTitle,
+ job_title);
+ base::LaunchAppAsUser(primary_token_scoped,
+ command_line.command_line_string(),
+ false,
+ process_handle);
+ return true;
+}
+
+// Returns false if the print job is being run in a context
+// that shouldn't be launching Chrome.
+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;
+}
+} // namespace
+
+bool GetChromeExePath(FilePath* chrome_path) {
+ 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,
+ &reg_data))) {
+ if (!reg_data.empty() && file_util::PathExists(FilePath(reg_data))) {
+ *chrome_path = FilePath(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 = FilePath(path.value());
+ return true;
+ }
+
+ // Chrome doesn't appear to be installed per user.
+ // Now check %programfiles(x86)%\google\chrome\application
+ // TODO(abodenha@chromium.org) Extend PathService::Get to be able to
+ // return the X86 program files path. At a minimum, use SHGetKnownFolderPath
+ // instead.
+ wchar_t system_buffer[MAX_PATH] = L"";
+ SHGetFolderPath(NULL,
+ CSIDL_PROGRAM_FILESX86,
+ NULL,
+ SHGFP_TYPE_CURRENT,
+ system_buffer);
+ path = FilePath(system_buffer);
+ path = path.Append(kChromeExePath);
+ if (file_util::PathExists(path)) {
+ *chrome_path = FilePath(path.value());
+ return true;
+ }
+ LOG(WARNING) << kChromeExePath << " not found.";
+ return false;
+}
+
+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.";
+ 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;
+ }
+ *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_handle), 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_handle = NULL;
+ }
+ FilePath app_data;
+ 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;
+ }
+ 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+");
+ 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;
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+}
+
+BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
+ LOG(INFO) << "Monitor2EndDocPort";
+ 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;
+ string16 job_title;
+ if (port_data->printer_handle != NULL) {
+ GetJobTitle(port_data->printer_handle,
+ port_data->job_id,
+ &job_title);
+ }
+ LaunchPrintDialog(port_data->file_path->value().c_str(),
+ job_title,
+ &process_handle);
+
+ // 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_handle != NULL) {
+ // Tell the spooler that the job is complete.
+ SetJob(port_data->printer_handle,
+ port_data->job_id,
+ 0,
+ NULL,
+ JOB_CONTROL_SENT_TO_PRINTER);
+ }
+ CleanupPortData(port_data);
+ // Return success even if we can't display the dialog.
+ // TODO(abodenha@chromium.org) Come up with a better way of handling
+ // this situation.
+ return TRUE;
+}
+
+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);
+ CleanupPortData(port_data);
+ 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::g_monitor_2;
+}
+
+MONITORUI* WINAPI InitializePrintMonitorUI(void) {
+ LOG(INFO) << "InitializePrintMonitorUI";
+ return &cloud_print::g_monitor_ui;
+}
+
diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.def b/cloud_print/virtual_driver/win/port_monitor/port_monitor.def
new file mode 100644
index 0000000..af6da8a
--- /dev/null
+++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.def
@@ -0,0 +1,8 @@
+; 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.
+
+EXPORTS
+ InitializePrintMonitor2
+ InitializePrintMonitorUI
+
diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.h b/cloud_print/virtual_driver/win/port_monitor/port_monitor.h
new file mode 100644
index 0000000..5b3c1b9
--- /dev/null
+++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.h
@@ -0,0 +1,88 @@
+// 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.
+
+#ifndef CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_
+#define CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_
+#pragma once
+
+#include <windows.h>
+#include <string>
+#include "base/file_util.h"
+#include "base/process.h"
+#include "base/string16.h"
+
+namespace cloud_print {
+
+// Fills chrome_path with the path to be used for launching Chrome.
+bool GetChromeExePath(FilePath* chrome_path);
+
+// Implementations for the function pointers in the MONITOR2 structure
+// returned by InitializePrintMonitor2. The prototypes and behaviors
+// are as described in the MONITOR2 documentation from Microsoft.
+
+BOOL WINAPI Monitor2EnumPorts(HANDLE,
+ wchar_t*,
+ DWORD level,
+ BYTE* ports,
+ DWORD ports_size,
+ DWORD* needed_bytes,
+ DWORD* returned);
+
+BOOL WINAPI Monitor2OpenPort(HANDLE monitor_data, wchar_t*, HANDLE* handle);
+
+BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
+ wchar_t* printer_name,
+ DWORD job_id,
+ DWORD,
+ BYTE*);
+
+BOOL WINAPI Monitor2WritePort(HANDLE port,
+ BYTE* buffer,
+ DWORD buffer_size,
+ DWORD* bytes_written);
+
+BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* bytes_read);
+
+BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle);
+
+BOOL WINAPI Monitor2ClosePort(HANDLE port_handle);
+
+VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle);
+
+BOOL WINAPI Monitor2XcvOpenPort(HANDLE monitor,
+ const wchar_t*,
+ ACCESS_MASK granted_access,
+ HANDLE* handle);
+
+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);
+
+BOOL WINAPI Monitor2XcvClosePort(HANDLE handle);
+
+// Implementations for the function pointers in the MONITORUI structure
+// returned by InitializePrintMonitorUI. The prototypes and behaviors
+// are as described in the MONITORUI documentation from Microsoft.
+
+BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
+ HWND hwnd,
+ const wchar_t* monitor_name,
+ wchar_t**);
+
+BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
+ HWND hwnd,
+ const wchar_t* port_name);
+
+extern const wchar_t kChromeExePath[];
+extern const wchar_t kChromePathRegKey[];
+extern const wchar_t kChromePathRegValue[];
+
+} // namespace cloud_print
+
+#endif // CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_
+
diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc b/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc
new file mode 100644
index 0000000..61f949c9
--- /dev/null
+++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc
@@ -0,0 +1,220 @@
+// 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 <winspool.h>
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/string16.h"
+#include "base/win/registry.h"
+#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cloud_print {
+const wchar_t kChromeExePath[] = L"google\\chrome\\application\\chrometest.exe";
+const wchar_t kAlternateChromeExePath[] =
+ L"google\\chrome\\application\\chrometestalternate.exe";
+const wchar_t kChromePathRegValue[] =L"PathToChromeTestExe";
+
+class PortMonitorTest : public testing::Test {
+ public:
+ PortMonitorTest() {}
+ protected:
+ // Creates a registry entry pointing at a chrome
+ virtual void SetUpChromeExeRegistry() {
+ // Create a temporary chrome.exe location value.
+ base::win::RegKey key(HKEY_CURRENT_USER,
+ cloud_print::kChromePathRegKey,
+ KEY_ALL_ACCESS);
+
+ FilePath path;
+ PathService::Get(base::DIR_LOCAL_APP_DATA, &path);
+ path = path.Append(kAlternateChromeExePath);
+ ASSERT_EQ(ERROR_SUCCESS,
+ key.WriteValue(cloud_print::kChromePathRegValue,
+ path.value().c_str()));
+ }
+ // Deletes the registry entry created in SetUpChromeExeRegistry
+ virtual void DeleteChromeExeRegistry() {
+ base::win::RegKey key(HKEY_CURRENT_USER,
+ cloud_print::kChromePathRegKey,
+ KEY_ALL_ACCESS);
+ key.DeleteValue(cloud_print::kChromePathRegValue);
+ }
+
+ virtual void CreateTempChromeExeFiles() {
+ FilePath path;
+ PathService::Get(base::DIR_LOCAL_APP_DATA, &path);
+ FilePath main_path = path.Append(kChromeExePath);
+ ASSERT_TRUE(file_util::CreateDirectory(main_path));
+ FilePath alternate_path = path.Append(kAlternateChromeExePath);
+ ASSERT_TRUE(file_util::CreateDirectory(alternate_path));
+ }
+
+ virtual void DeleteTempChromeExeFiles() {
+ FilePath path;
+ PathService::Get(base::DIR_LOCAL_APP_DATA, &path);
+ FilePath main_path = path.Append(kChromeExePath);
+ ASSERT_TRUE(file_util::Delete(main_path, true));
+ PathService::Get(base::DIR_LOCAL_APP_DATA, &path);
+ FilePath alternate_path = path.Append(kAlternateChromeExePath);
+ ASSERT_TRUE(file_util::Delete(alternate_path, true));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PortMonitorTest);
+};
+
+TEST_F(PortMonitorTest, GetChromeExePathTest) {
+ FilePath chrome_path;
+ SetUpChromeExeRegistry();
+ CreateTempChromeExeFiles();
+ EXPECT_TRUE(cloud_print::GetChromeExePath(&chrome_path));
+ EXPECT_TRUE(
+ chrome_path.value().rfind(kAlternateChromeExePath) != std::string::npos);
+ DeleteChromeExeRegistry();
+ chrome_path.clear();
+ EXPECT_TRUE(cloud_print::GetChromeExePath(&chrome_path));
+ EXPECT_TRUE(chrome_path.value().rfind(kChromeExePath) != std::string::npos);
+ EXPECT_TRUE(file_util::PathExists(FilePath(chrome_path)));
+ DeleteTempChromeExeFiles();
+ EXPECT_FALSE(cloud_print::GetChromeExePath(&chrome_path));
+}
+
+TEST_F(PortMonitorTest, EnumPortsTest) {
+ DWORD needed_bytes = 0;
+ DWORD returned = 0;
+ EXPECT_FALSE(Monitor2EnumPorts(NULL,
+ NULL,
+ 1,
+ NULL,
+ 0,
+ &needed_bytes,
+ &returned));
+ EXPECT_EQ(ERROR_INSUFFICIENT_BUFFER, GetLastError());
+ EXPECT_NE(0u, needed_bytes);
+ EXPECT_EQ(0u, returned);
+
+ BYTE* buffer = new BYTE[needed_bytes];
+ ASSERT_TRUE(buffer != NULL);
+ EXPECT_TRUE(Monitor2EnumPorts(NULL,
+ NULL,
+ 1,
+ buffer,
+ needed_bytes,
+ &needed_bytes,
+ &returned));
+ EXPECT_NE(0u, needed_bytes);
+ EXPECT_EQ(1u, returned);
+ PORT_INFO_1* port_info_1 = reinterpret_cast<PORT_INFO_1*>(buffer);
+ EXPECT_TRUE(port_info_1->pName != NULL);
+ delete[] buffer;
+
+ returned = 0;
+ needed_bytes = 0;
+ EXPECT_FALSE(Monitor2EnumPorts(NULL,
+ NULL,
+ 2,
+ NULL,
+ 0,
+ &needed_bytes,
+ &returned));
+ EXPECT_EQ(ERROR_INSUFFICIENT_BUFFER, GetLastError());
+ EXPECT_NE(0u, needed_bytes);
+ EXPECT_EQ(0u, returned);
+
+ buffer = new BYTE[needed_bytes];
+ ASSERT_TRUE(buffer != NULL);
+ EXPECT_TRUE(Monitor2EnumPorts(NULL,
+ NULL,
+ 2,
+ buffer,
+ needed_bytes,
+ &needed_bytes,
+ &returned));
+ EXPECT_NE(0u, needed_bytes);
+ EXPECT_EQ(1u, returned);
+ PORT_INFO_2* port_info_2 = reinterpret_cast<PORT_INFO_2*>(buffer);
+ EXPECT_TRUE(port_info_2->pPortName != NULL);
+ delete[] buffer;
+}
+
+TEST_F(PortMonitorTest, FlowTest) {
+ const wchar_t kXcvDataItem[] = L"MonitorUI";
+ MONITORINIT monitor_init = {0};
+ HANDLE monitor_handle = NULL;
+ HANDLE port_handle = NULL;
+ HANDLE xcv_handle = NULL;
+ DWORD bytes_processed = 0;
+ DWORD bytes_needed = 0;
+ const size_t kBufferSize = 100;
+ BYTE buffer[kBufferSize] = {0};
+
+ // Initialize the print monitor
+ MONITOR2* monitor2 = InitializePrintMonitor2(&monitor_init, &monitor_handle);
+ EXPECT_TRUE(monitor2 != NULL);
+ EXPECT_TRUE(monitor_handle != NULL);
+
+ // Test the XCV functions. Used for reporting the location of the
+ // UI portion of the port monitor.
+ EXPECT_TRUE(monitor2->pfnXcvOpenPort != NULL);
+ EXPECT_TRUE(monitor2->pfnXcvOpenPort(monitor_handle, NULL, 0, &xcv_handle));
+ EXPECT_TRUE(xcv_handle != NULL);
+ EXPECT_TRUE(monitor2->pfnXcvDataPort != NULL);
+ EXPECT_EQ(ERROR_ACCESS_DENIED,
+ monitor2->pfnXcvDataPort(xcv_handle,
+ kXcvDataItem,
+ NULL,
+ 0,
+ buffer,
+ kBufferSize,
+ &bytes_needed));
+ EXPECT_TRUE(monitor2->pfnXcvClosePort != NULL);
+ EXPECT_TRUE(monitor2->pfnXcvClosePort(xcv_handle));
+ EXPECT_TRUE(monitor2->pfnXcvOpenPort(monitor_handle,
+ NULL,
+ SERVER_ACCESS_ADMINISTER,
+ &xcv_handle));
+ EXPECT_TRUE(xcv_handle != NULL);
+ EXPECT_TRUE(monitor2->pfnXcvDataPort != NULL);
+ EXPECT_EQ(ERROR_SUCCESS,
+ monitor2->pfnXcvDataPort(xcv_handle,
+ kXcvDataItem,
+ NULL,
+ 0,
+ buffer,
+ kBufferSize,
+ &bytes_needed));
+ EXPECT_TRUE(monitor2->pfnXcvClosePort != NULL);
+ EXPECT_TRUE(monitor2->pfnXcvClosePort(xcv_handle));
+
+ // Test opening the port and running a print job.
+ EXPECT_TRUE(monitor2->pfnOpenPort != NULL);
+ EXPECT_TRUE(monitor2->pfnOpenPort(monitor_handle, NULL, &port_handle));
+ EXPECT_TRUE(port_handle != NULL);
+ EXPECT_TRUE(monitor2->pfnStartDocPort != NULL);
+ EXPECT_TRUE(monitor2->pfnStartDocPort(port_handle, L"", 0, 0, NULL));
+ EXPECT_TRUE(monitor2->pfnWritePort != NULL);
+ EXPECT_TRUE(monitor2->pfnWritePort(port_handle,
+ buffer,
+ kBufferSize,
+ &bytes_processed));
+ EXPECT_EQ(kBufferSize, bytes_processed);
+ EXPECT_TRUE(monitor2->pfnReadPort != NULL);
+ EXPECT_FALSE(monitor2->pfnReadPort(port_handle,
+ buffer,
+ sizeof(buffer),
+ &bytes_processed));
+ EXPECT_EQ(0u, bytes_processed);
+ EXPECT_TRUE(monitor2->pfnEndDocPort != NULL);
+ EXPECT_TRUE(monitor2->pfnEndDocPort(port_handle));
+ EXPECT_TRUE(monitor2->pfnClosePort != NULL);
+ EXPECT_TRUE(monitor2->pfnClosePort(port_handle));
+
+ // Shutdown the port monitor.
+ Monitor2Shutdown(monitor_handle);
+}
+
+} // namespace cloud_print
+
diff --git a/cloud_print/virtual_driver/win/port_monitor/spooler_win.h b/cloud_print/virtual_driver/win/port_monitor/spooler_win.h
new file mode 100644
index 0000000..ea45c9d
--- /dev/null
+++ b/cloud_print/virtual_driver/win/port_monitor/spooler_win.h
@@ -0,0 +1,109 @@
+// 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.
+
+#ifndef CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_
+#define CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_
+#pragma once
+
+#include <windows.h>
+
+// Compatible structures and prototypes are also defined in the Windows DDK in
+// winsplp.h.
+#ifndef _WINSPLP_
+
+typedef struct {
+ DWORD size;
+ BOOL (WINAPI *pfnEnumPorts)(HANDLE,
+ wchar_t*,
+ DWORD level,
+ BYTE* ports,
+ DWORD ports_size,
+ DWORD* needed_bytes,
+ DWORD* returned);
+
+ BOOL (WINAPI *pfnOpenPort)(HANDLE monitor_data, wchar_t*, HANDLE* handle);
+
+ void* pfnOpenPortEx; // Unused.
+
+ BOOL (WINAPI *pfnStartDocPort)(HANDLE port_handle,
+ wchar_t* printer_name,
+ DWORD job_id,
+ DWORD,
+ BYTE*);
+
+ BOOL (WINAPI *pfnWritePort)(HANDLE port,
+ BYTE* buffer,
+ DWORD buffer_size,
+ DWORD* bytes_written);
+
+ BOOL (WINAPI *pfnReadPort)(HANDLE, BYTE*, DWORD, DWORD* bytes_read);
+
+ BOOL (WINAPI *pfnEndDocPort)(HANDLE port_handle);
+
+ BOOL (WINAPI *pfnClosePort)(HANDLE port_handle);
+
+ void* pfnAddPort; // Unused.
+
+ void* pfnAddPortEx; // Unused.
+
+ void* pfnConfigurePort; // Unused.
+
+ void* pfnDeletePort; // Unused.
+
+ void* pfnGetPrinterDataFromPort; // Unused.
+
+ void* pfnSetPortTimeOuts; // Unusued.
+
+ BOOL (WINAPI *pfnXcvOpenPort)(HANDLE monitor,
+ const wchar_t*,
+ ACCESS_MASK granted_access,
+ HANDLE* handle);
+
+ DWORD (WINAPI *pfnXcvDataPort)(HANDLE xcv_handle,
+ const wchar_t* data_name,
+ BYTE*,
+ DWORD,
+ BYTE* output_data,
+ DWORD output_data_bytes,
+ DWORD* output_data_bytes_needed);
+
+ BOOL (WINAPI *pfnXcvClosePort)(HANDLE handle);
+
+ VOID (WINAPI *pfnShutdown)(HANDLE monitor_handle);
+} MONITOR2;
+
+typedef struct {
+ DWORD size;
+
+ BOOL (WINAPI *pfnAddPortUI)(const wchar_t*,
+ HWND hwnd,
+ const wchar_t* monitor_name,
+ wchar_t**);
+
+ BOOL (WINAPI *pfnConfigurePortUI)(const wchar_t*,
+ HWND hwnd,
+ const wchar_t* port_name);
+
+ BOOL (WINAPI *pfnDeletePortUI)(const wchar_t*,
+ HWND hwnd,
+ const wchar_t* port_name);
+} MONITORUI;
+
+typedef struct {
+ DWORD cbSize;
+ HANDLE hSpooler;
+ HKEY hckRegistryRoot;
+ void* pMonitorReg; // Unused
+ BOOL bLocal;
+ LPCWSTR pszServerName;
+} MONITORINIT;
+
+MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT* monitor_init,
+ HANDLE* monitor_handle);
+
+MONITORUI* WINAPI InitializePrintMonitorUI(void);
+
+#endif // ifdef USE_WIN_DDK
+#endif // CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_
+