diff options
Diffstat (limited to 'win8')
42 files changed, 6440 insertions, 0 deletions
diff --git a/win8/delegate_execute/chrome_util.cc b/win8/delegate_execute/chrome_util.cc new file mode 100644 index 0000000..701f2e8 --- /dev/null +++ b/win8/delegate_execute/chrome_util.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2012 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 "win8/delegate_execute/chrome_util.h" + +#include <atlbase.h> +#include <shlobj.h> +#include <windows.h> + +#include <algorithm> +#include <limits> +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/md5.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/win/registry.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_handle.h" +#include "base/win/win_util.h" +#include "google_update/google_update_idl.h" + +namespace { + +#if defined(GOOGLE_CHROME_BUILD) +const wchar_t kAppUserModelId[] = L"Chrome"; +#else // GOOGLE_CHROME_BUILD +const wchar_t kAppUserModelId[] = L"Chromium"; +#endif // GOOGLE_CHROME_BUILD + +#if defined(GOOGLE_CHROME_BUILD) + +// TODO(grt): These constants live in installer_util. Consider moving them +// into common_constants to allow for reuse. +const FilePath::CharType kNewChromeExe[] = FILE_PATH_LITERAL("new_chrome.exe"); +const wchar_t kRenameCommandValue[] = L"cmd"; +const wchar_t kChromeAppGuid[] = L"{8A69D345-D564-463c-AFF1-A69D9E530F96}"; +const wchar_t kRegPathChromeClient[] = + L"Software\\Google\\Update\\Clients\\" + L"{8A69D345-D564-463c-AFF1-A69D9E530F96}"; +const int kExitCodeRenameSuccessful = 23; + +// Returns the name of the global event used to detect if |chrome_exe| is in +// use by a browser process. +// TODO(grt): Move this somewhere central so it can be used by both this +// IsBrowserRunning (below) and IsBrowserAlreadyRunning (browser_util_win.cc). +string16 GetEventName(const FilePath& chrome_exe) { + static wchar_t const kEventPrefix[] = L"Global\\"; + const size_t prefix_len = arraysize(kEventPrefix) - 1; + string16 name; + name.reserve(prefix_len + chrome_exe.value().size()); + name.assign(kEventPrefix, prefix_len); + name.append(chrome_exe.value()); + std::replace(name.begin() + prefix_len, name.end(), '\\', '!'); + std::transform(name.begin() + prefix_len, name.end(), + name.begin() + prefix_len, tolower); + return name; +} + +// Returns true if |chrome_exe| is in use by a browser process. In this case, +// "in use" means past ChromeBrowserMainParts::PreMainMessageLoopRunImpl. +bool IsBrowserRunning(const FilePath& chrome_exe) { + base::win::ScopedHandle handle(::OpenEvent( + SYNCHRONIZE, FALSE, GetEventName(chrome_exe).c_str())); + if (handle.IsValid()) + return true; + DWORD last_error = ::GetLastError(); + if (last_error != ERROR_FILE_NOT_FOUND) { + AtlTrace("%hs. Failed to open browser event; error %u.\n", __FUNCTION__, + last_error); + } + return false; +} + +// Returns true if the file new_chrome.exe exists in the same directory as +// |chrome_exe|. +bool NewChromeExeExists(const FilePath& chrome_exe) { + FilePath new_chrome_exe(chrome_exe.DirName().Append(kNewChromeExe)); + return file_util::PathExists(new_chrome_exe); +} + +bool GetUpdateCommand(bool is_per_user, string16* update_command) { + const HKEY root = is_per_user ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + base::win::RegKey key(root, kRegPathChromeClient, KEY_QUERY_VALUE); + + return key.ReadValue(kRenameCommandValue, update_command) == ERROR_SUCCESS; +} + +#endif // GOOGLE_CHROME_BUILD + +// TODO(grt): This code also lives in installer_util. Refactor for reuse. +bool IsPerUserInstall(const FilePath& chrome_exe) { + wchar_t program_files_path[MAX_PATH] = {0}; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, + SHGFP_TYPE_CURRENT, program_files_path))) { + return !StartsWith(chrome_exe.value().c_str(), program_files_path, false); + } else { + NOTREACHED(); + } + return true; +} + +// TODO(gab): This code also lives in shell_util. Refactor for reuse. +string16 ByteArrayToBase32(const uint8* bytes, size_t size) { + static const char kEncoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + // Eliminate special cases first. + if (size == 0) { + return string16(); + } else if (size == 1) { + string16 ret; + ret.push_back(kEncoding[(bytes[0] & 0xf8) >> 3]); + ret.push_back(kEncoding[(bytes[0] & 0x07) << 2]); + return ret; + } else if (size >= std::numeric_limits<size_t>::max() / 8) { + // If |size| is too big, the calculation of |encoded_length| below will + // overflow. + AtlTrace("%hs. Byte array is too long.\n", __FUNCTION__); + return string16(); + } + + // Overestimate the number of bits in the string by 4 so that dividing by 5 + // is the equivalent of rounding up the actual number of bits divided by 5. + const size_t encoded_length = (size * 8 + 4) / 5; + + string16 ret; + ret.reserve(encoded_length); + + // A bit stream which will be read from the left and appended to from the + // right as it's emptied. + uint16 bit_stream = (bytes[0] << 8) + bytes[1]; + size_t next_byte_index = 2; + int free_bits = 0; + while (free_bits < 16) { + // Extract the 5 leftmost bits in the stream + ret.push_back(kEncoding[(bit_stream & 0xf800) >> 11]); + bit_stream <<= 5; + free_bits += 5; + + // If there is enough room in the bit stream, inject another byte (if there + // are any left...). + if (free_bits >= 8 && next_byte_index < size) { + free_bits -= 8; + bit_stream += bytes[next_byte_index++] << free_bits; + } + } + + if (ret.length() != encoded_length) { + AtlTrace("%hs. Encoding doesn't match expected length.\n", __FUNCTION__); + return string16(); + } + return ret; +} + +// TODO(gab): This code also lives in shell_util. Refactor for reuse. +bool GetUserSpecificRegistrySuffix(string16* suffix) { + string16 user_sid; + if (!base::win::GetUserSidString(&user_sid)) { + AtlTrace("%hs. GetUserSidString failed.\n", __FUNCTION__); + return false; + } + COMPILE_ASSERT(sizeof(base::MD5Digest) == 16, size_of_MD5_not_as_expected_); + base::MD5Digest md5_digest; + std::string user_sid_ascii(UTF16ToASCII(user_sid)); + base::MD5Sum(user_sid_ascii.c_str(), user_sid_ascii.length(), &md5_digest); + const string16 base32_md5( + ByteArrayToBase32(md5_digest.a, arraysize(md5_digest.a))); + // The value returned by the base32 algorithm above must never change and must + // always be 26 characters long (i.e. if someone ever moves this to base and + // implements the full base32 algorithm (i.e. with appended '=' signs in the + // output), they must provide a flag to allow this method to still request + // the output with no appended '=' signs). + if (base32_md5.length() != 26U) { + AtlTrace("%hs. Base32 encoding of md5 hash is incorrect.\n", __FUNCTION__); + return false; + } + suffix->reserve(base32_md5.length() + 1); + suffix->assign(1, L'.'); + suffix->append(base32_md5); + return true; +} + +} // namespace + +namespace delegate_execute { + +void UpdateChromeIfNeeded(const FilePath& chrome_exe) { +#if defined(GOOGLE_CHROME_BUILD) + // Nothing to do if a browser is already running or if there's no + // new_chrome.exe. + if (IsBrowserRunning(chrome_exe) || !NewChromeExeExists(chrome_exe)) + return; + + base::ProcessHandle process_handle = base::kNullProcessHandle; + + if (IsPerUserInstall(chrome_exe)) { + // Read the update command from the registry. + string16 update_command; + if (!GetUpdateCommand(true, &update_command)) { + AtlTrace("%hs. Failed to read update command from registry.\n", + __FUNCTION__); + } else { + // Run the update command. + base::LaunchOptions launch_options; + launch_options.start_hidden = true; + if (!base::LaunchProcess(update_command, launch_options, + &process_handle)) { + AtlTrace("%hs. Failed to launch command to finalize update; " + "error %u.\n", __FUNCTION__, ::GetLastError()); + process_handle = base::kNullProcessHandle; + } + } + } else { + // Run the update command via Google Update. + HRESULT hr = S_OK; + base::win::ScopedComPtr<IProcessLauncher> process_launcher; + hr = process_launcher.CreateInstance(__uuidof(ProcessLauncherClass)); + if (FAILED(hr)) { + AtlTrace("%hs. Failed to Create ProcessLauncher; hr=0x%X.\n", + __FUNCTION__, hr); + } else { + ULONG_PTR handle = 0; + hr = process_launcher->LaunchCmdElevated( + kChromeAppGuid, kRenameCommandValue, GetCurrentProcessId(), &handle); + if (FAILED(hr)) { + AtlTrace("%hs. Failed to launch command to finalize update; " + "hr=0x%X.\n", __FUNCTION__, hr); + } else { + process_handle = reinterpret_cast<base::ProcessHandle>(handle); + } + } + } + + // Wait for the update to complete and report the results. + if (process_handle != base::kNullProcessHandle) { + int exit_code = 0; + // WaitForExitCode will close the handle in all cases. + if (!base::WaitForExitCode(process_handle, &exit_code)) { + AtlTrace("%hs. Failed to get result when finalizing update.\n", + __FUNCTION__); + } else if (exit_code != kExitCodeRenameSuccessful) { + AtlTrace("%hs. Failed to finalize update with exit code %d.\n", + __FUNCTION__, exit_code); + } else { + AtlTrace("%hs. Finalized pending update.\n", __FUNCTION__); + } + } +#endif +} + +// TODO(gab): This code also lives in shell_util. Refactor for reuse. +string16 GetAppId(const FilePath& chrome_exe) { + string16 app_id(kAppUserModelId); + string16 suffix; + if (IsPerUserInstall(chrome_exe) && + !GetUserSpecificRegistrySuffix(&suffix)) { + AtlTrace("%hs. GetUserSpecificRegistrySuffix failed.\n", + __FUNCTION__); + } + return app_id.append(suffix); +} + +} // delegate_execute diff --git a/win8/delegate_execute/chrome_util.h b/win8/delegate_execute/chrome_util.h new file mode 100644 index 0000000..634ce6c --- /dev/null +++ b/win8/delegate_execute/chrome_util.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012 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 WIN8_DELEGATE_EXECUTE_CHROME_UTIL_H_ +#define WIN8_DELEGATE_EXECUTE_CHROME_UTIL_H_ + +#include "base/string16.h" + +class FilePath; + +namespace delegate_execute { + +// Finalizes a previously updated installation. +void UpdateChromeIfNeeded(const FilePath& chrome_exe); + +// Returns the appid of the Chrome pointed to by |chrome_exe|. +string16 GetAppId(const FilePath& chrome_exe); + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_CHROME_UTIL_H_ diff --git a/win8/delegate_execute/command_execute_impl.cc b/win8/delegate_execute/command_execute_impl.cc new file mode 100644 index 0000000..ef1378a --- /dev/null +++ b/win8/delegate_execute/command_execute_impl.cc @@ -0,0 +1,440 @@ +// Copyright (c) 2012 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. +// Implementation of the CommandExecuteImpl class which implements the +// IExecuteCommand and related interfaces for handling ShellExecute based +// launches of the Chrome browser. + +#include "win8/delegate_execute/command_execute_impl.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_handle.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "win8/delegate_execute/chrome_util.h" + +// CommandExecuteImpl is resposible for activating chrome in Windows 8. The +// flow is complicated and this tries to highlight the important events. +// The current approach is to have a single instance of chrome either +// running in desktop or metro mode. If there is no current instance then +// the desktop shortcut launches desktop chrome and the metro tile or search +// charm launches metro chrome. +// If chrome is running then focus/activation is given to the existing one +// regarless of what launch point the user used. +// +// The general flow when chrome is the default browser is as follows: +// +// 1- User interacts with launch point (icon, tile, search, shellexec, etc) +// 2- Windows finds the appid for launch item and resolves it to chrome +// 3- Windows activates CommandExecuteImpl inside a surrogate process +// 4- Windows calls the following sequence of entry points: +// CommandExecuteImpl::SetShowWindow +// CommandExecuteImpl::SetPosition +// CommandExecuteImpl::SetDirectory +// CommandExecuteImpl::SetParameter +// CommandExecuteImpl::SetNoShowUI +// CommandExecuteImpl::SetSelection +// CommandExecuteImpl::Initialize +// Up to this point the code basically just gathers values passed in, like +// the launch scheme (or url) and the activation verb. +// 5- Windows calls CommandExecuteImpl::Getvalue() +// Here we need to return AHE_IMMERSIVE or AHE_DESKTOP. That depends on: +// a) if run in high-integrity return AHE_DESKTOP +// b) if chrome is running return the AHE_ mode of chrome +// c) if the current process is inmmersive return AHE_IMMERSIVE +// d) if the protocol is file and IExecuteCommandHost::GetUIMode() is not +// ECHUIM_DESKTOP then return AHE_IMMERSIVE +// e) if none of the above return AHE_DESKTOP +// 6- If we returned AHE_DESKTOP in step 5 then CommandExecuteImpl::Execute() +// is called, here we call GetLaunchMode() which: +// a) if chrome is running return the mode of chrome or +// b) return IExecuteCommandHost::GetUIMode() +// 7- If GetLaunchMode() returns +// a) ECHUIM_DESKTOP we call LaunchDestopChrome() that calls ::CreateProcess +// b) else we call one of the IApplicationActivationManager activation +// functions depending on the parameters passed in step 4. +// +// Some examples will help clarify the common cases. +// +// I - No chrome running, taskbar icon launch: +// a) Scheme is 'file', Verb is 'open' +// b) GetValue() returns at e) step : AHE_DESKTOP +// c) Execute() calls LaunchDestopChrome() +// --> desktop chrome runs +// II- No chrome running, tile activation launch: +// a) Scheme is 'file', Verb is 'open' +// b) GetValue() returns at d) step : AHE_IMMERSIVE +// c) Windows does not call us back and just activates chrome +// --> metro chrome runs +// III- No chrome running, url link click on metro app: +// a) Scheme is 'http', Verb is 'open' +// b) Getvalue() returns at e) step : AHE_DESKTOP +// c) Execute() calls IApplicationActivationManager::ActivateForProtocol +// --> metro chrome runs +// +CommandExecuteImpl::CommandExecuteImpl() + : integrity_level_(base::INTEGRITY_UNKNOWN), + launch_scheme_(INTERNET_SCHEME_DEFAULT), + chrome_mode_(ECHUIM_SYSTEM_LAUNCHER) { + memset(&start_info_, 0, sizeof(start_info_)); + start_info_.cb = sizeof(start_info_); + // We need to query the user data dir of chrome so we need chrome's + // path provider. + chrome::RegisterPathProvider(); +} + +// CommandExecuteImpl +STDMETHODIMP CommandExecuteImpl::SetKeyState(DWORD key_state) { + AtlTrace("In %hs\n", __FUNCTION__); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetParameters(LPCWSTR params) { + AtlTrace("In %hs [%S]\n", __FUNCTION__, params); + if (params) { + parameters_ = params; + } + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetPosition(POINT pt) { + AtlTrace("In %hs\n", __FUNCTION__); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetShowWindow(int show) { + AtlTrace("In %hs show=%d\n", __FUNCTION__, show); + start_info_.wShowWindow = show; + start_info_.dwFlags |= STARTF_USESHOWWINDOW; + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetNoShowUI(BOOL no_show_ui) { + AtlTrace("In %hs no_show=%d\n", __FUNCTION__, no_show_ui); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetDirectory(LPCWSTR directory) { + AtlTrace("In %hs\n", __FUNCTION__); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::GetValue(enum AHE_TYPE* pahe) { + AtlTrace("In %hs\n", __FUNCTION__); + + if (!GetLaunchScheme(&display_name_, &launch_scheme_)) { + AtlTrace("Failed to get scheme, E_FAIL\n"); + return E_FAIL; + } + + if (integrity_level_ == base::HIGH_INTEGRITY) { + // Metro mode apps don't work in high integrity mode. + AtlTrace("High integrity, AHE_DESKTOP\n"); + *pahe = AHE_DESKTOP; + return S_OK; + } + + FilePath user_data_dir; + if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + AtlTrace("Failed to get chrome's data dir path, E_FAIL\n"); + return E_FAIL; + } + + HWND chrome_window = ::FindWindowEx(HWND_MESSAGE, NULL, + chrome::kMessageWindowClass, + user_data_dir.value().c_str()); + if (chrome_window) { + AtlTrace("Found chrome window %p\n", chrome_window); + // The failure cases below are deemed to happen due to the inherently racy + // procedure of going from chrome's window to process handle during which + // chrome might have exited. Failing here would probably just cause the + // user to retry at which point we would do the right thing. + DWORD chrome_pid = 0; + ::GetWindowThreadProcessId(chrome_window, &chrome_pid); + if (!chrome_pid) { + AtlTrace("Failed to get chrome's PID, E_FAIL\n"); + return E_FAIL; + } + base::win::ScopedHandle process( + ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, chrome_pid)); + if (!process.IsValid()) { + AtlTrace("Failed to open chrome's process [%d], E_FAIL\n", chrome_pid); + return E_FAIL; + } + if (IsImmersiveProcess(process.Get())) { + AtlTrace("Chrome [%d] is inmmersive, AHE_IMMERSIVE\n", chrome_pid); + chrome_mode_ = ECHUIM_IMMERSIVE; + *pahe = AHE_IMMERSIVE; + } else { + AtlTrace("Chrome [%d] is Desktop, AHE_DESKTOP\n"); + chrome_mode_ = ECHUIM_DESKTOP; + *pahe = AHE_DESKTOP; + } + return S_OK; + } + + if (IsImmersiveProcess(GetCurrentProcess())) { + AtlTrace("Current process is inmmersive, AHE_IMMERSIVE\n"); + *pahe = AHE_IMMERSIVE; + return S_OK; + } + + if ((launch_scheme_ == INTERNET_SCHEME_FILE) && + (GetLaunchMode() != ECHUIM_DESKTOP)) { + AtlTrace("INTERNET_SCHEME_FILE, mode != ECHUIM_DESKTOP, AHE_IMMERSIVE\n"); + *pahe = AHE_IMMERSIVE; + return S_OK; + } + + AtlTrace("Fallback is AHE_DESKTOP\n"); + *pahe = AHE_DESKTOP; + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::Execute() { + AtlTrace("In %hs\n", __FUNCTION__); + + if (integrity_level_ == base::HIGH_INTEGRITY) + return LaunchDesktopChrome(); + + EC_HOST_UI_MODE mode = GetLaunchMode(); + if (mode == ECHUIM_DESKTOP) + return LaunchDesktopChrome(); + + HRESULT hr = E_FAIL; + CComPtr<IApplicationActivationManager> activation_manager; + hr = activation_manager.CoCreateInstance(CLSID_ApplicationActivationManager); + if (!activation_manager) { + AtlTrace("Failed to get the activation manager, error 0x%x\n", hr); + return S_OK; + } + + string16 app_id = delegate_execute::GetAppId(chrome_exe_); + + DWORD pid = 0; + if (launch_scheme_ == INTERNET_SCHEME_FILE) { + AtlTrace("Activating for file\n"); + hr = activation_manager->ActivateApplication(app_id.c_str(), + verb_.c_str(), + AO_NOERRORUI, + &pid); + } else { + AtlTrace("Activating for protocol\n"); + hr = activation_manager->ActivateForProtocol(app_id.c_str(), + item_array_, + &pid); + } + if (hr == E_APPLICATION_NOT_REGISTERED) { + AtlTrace("Metro chrome is not registered, launching in desktop\n"); + return LaunchDesktopChrome(); + } + AtlTrace("Metro Chrome launch, pid=%d, returned 0x%x\n", pid, hr); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::Initialize(LPCWSTR name, + IPropertyBag* bag) { + AtlTrace("In %hs\n", __FUNCTION__); + if (!FindChromeExe(&chrome_exe_)) + return E_FAIL; + delegate_execute::UpdateChromeIfNeeded(chrome_exe_); + if (name) { + AtlTrace("Verb is %S\n", name); + verb_ = name; + } + + base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(), + &integrity_level_); + if (integrity_level_ == base::HIGH_INTEGRITY) { + AtlTrace("Delegate execute launched in high integrity level\n"); + } + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::SetSelection(IShellItemArray* item_array) { + AtlTrace("In %hs\n", __FUNCTION__); + item_array_ = item_array; + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::GetSelection(REFIID riid, void** selection) { + AtlTrace("In %hs\n", __FUNCTION__); + return S_OK; +} + +STDMETHODIMP CommandExecuteImpl::AllowForegroundTransfer(void* reserved) { + AtlTrace("In %hs\n", __FUNCTION__); + return S_OK; +} + +// Returns false if chrome.exe cannot be found. +// static +bool CommandExecuteImpl::FindChromeExe(FilePath* chrome_exe) { + AtlTrace("In %hs\n", __FUNCTION__); + // Look for chrome.exe one folder above delegate_execute.exe (as expected in + // Chrome installs). Failing that, look for it alonside delegate_execute.exe. + FilePath dir_exe; + if (!PathService::Get(base::DIR_EXE, &dir_exe)) { + AtlTrace("Failed to get current exe path\n"); + return false; + } + + *chrome_exe = dir_exe.DirName().Append(chrome::kBrowserProcessExecutableName); + if (!file_util::PathExists(*chrome_exe)) { + *chrome_exe = dir_exe.Append(chrome::kBrowserProcessExecutableName); + if (!file_util::PathExists(*chrome_exe)) { + AtlTrace("Failed to find chrome exe file\n"); + return false; + } + } + + AtlTrace("Got chrome exe path as %ls\n", chrome_exe->value().c_str()); + return true; +} + +bool CommandExecuteImpl::GetLaunchScheme( + string16* display_name, INTERNET_SCHEME* scheme) { + if (!item_array_) + return false; + + ATLASSERT(display_name); + ATLASSERT(scheme); + + DWORD count = 0; + item_array_->GetCount(&count); + + if (count != 1) { + AtlTrace("Cannot handle %d elements in the IShellItemArray\n", count); + return false; + } + + CComPtr<IEnumShellItems> items; + item_array_->EnumItems(&items); + CComPtr<IShellItem> shell_item; + HRESULT hr = items->Next(1, &shell_item, &count); + if (hr != S_OK) { + AtlTrace("Failed to read element from the IShellItemsArray\n"); + return false; + } + + base::win::ScopedCoMem<wchar_t> name; + hr = shell_item->GetDisplayName(SIGDN_URL, &name); + if (hr != S_OK) { + AtlTrace("Failed to get display name\n"); + return false; + } + + AtlTrace("Display name is [%ls]\n", name); + + wchar_t scheme_name[16]; + URL_COMPONENTS components = {0}; + components.lpszScheme = scheme_name; + components.dwSchemeLength = sizeof(scheme_name)/sizeof(scheme_name[0]); + + components.dwStructSize = sizeof(components); + if (!InternetCrackUrlW(name, 0, 0, &components)) { + AtlTrace("Failed to crack url %ls\n", name); + return false; + } + + AtlTrace("Launch scheme is [%ls] (%d)\n", scheme_name, components.nScheme); + *display_name = name; + *scheme = components.nScheme; + return true; +} + +HRESULT CommandExecuteImpl::LaunchDesktopChrome() { + AtlTrace("In %hs\n", __FUNCTION__); + string16 display_name = display_name_; + + switch (launch_scheme_) { + case INTERNET_SCHEME_FILE: + // If anything other than chrome.exe is passed in the display name we + // should honor it. For e.g. If the user clicks on a html file when + // chrome is the default we should treat it as a parameter to be passed + // to chrome. + if (display_name.find(L"chrome.exe") != string16::npos) + display_name.clear(); + break; + + default: + break; + } + + string16 command_line = L"\""; + command_line += chrome_exe_.value(); + command_line += L"\""; + + if (!parameters_.empty()) { + AtlTrace("Adding parameters %ls to command line\n", parameters_.c_str()); + command_line += L" "; + command_line += parameters_.c_str(); + } + + if (!display_name.empty()) { + command_line += L" -- "; + command_line += display_name; + } + + AtlTrace("Formatted command line is %ls\n", command_line.c_str()); + + PROCESS_INFORMATION proc_info = {0}; + BOOL ret = CreateProcess(NULL, const_cast<LPWSTR>(command_line.c_str()), + NULL, NULL, FALSE, 0, NULL, NULL, &start_info_, + &proc_info); + if (ret) { + AtlTrace("Process id is %d\n", proc_info.dwProcessId); + CloseHandle(proc_info.hProcess); + CloseHandle(proc_info.hThread); + } else { + AtlTrace("Process launch failed, error %d\n", ::GetLastError()); + } + + return S_OK; +} + +EC_HOST_UI_MODE CommandExecuteImpl::GetLaunchMode() { + // We are to return chrome's mode if chrome exists else we query our embedder + // IServiceProvider and learn the mode from them. + static bool launch_mode_determined = false; + static EC_HOST_UI_MODE launch_mode = ECHUIM_DESKTOP; + + const char* modes[] = { "Desktop", "Inmmersive", "SysLauncher", "??" }; + + if (launch_mode_determined) + return launch_mode; + + if (chrome_mode_ != ECHUIM_SYSTEM_LAUNCHER) { + launch_mode = chrome_mode_; + AtlTrace("Launch mode is that of chrome, %s\n", modes[launch_mode]); + launch_mode_determined = true; + return launch_mode; + } + + if (parameters_ == ASCIIToWide(switches::kForceImmersive)) { + launch_mode = ECHUIM_IMMERSIVE; + AtlTrace("Launch mode forced to %s\n", modes[launch_mode]); + launch_mode_determined = true; + return launch_mode; + } + + CComPtr<IExecuteCommandHost> host; + CComQIPtr<IServiceProvider> service_provider = m_spUnkSite; + if (service_provider) { + service_provider->QueryService(IID_IExecuteCommandHost, &host); + if (host) { + host->GetUIMode(&launch_mode); + } else { + AtlTrace("Failed to get IID_IExecuteCommandHost. Assuming desktop\n"); + } + } else { + AtlTrace("Failed to get IServiceProvider. Assuming desktop mode\n"); + } + AtlTrace("Launch mode is %s\n", modes[launch_mode]); + launch_mode_determined = true; + return launch_mode; +} diff --git a/win8/delegate_execute/command_execute_impl.h b/win8/delegate_execute/command_execute_impl.h new file mode 100644 index 0000000..5a1a95c --- /dev/null +++ b/win8/delegate_execute/command_execute_impl.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 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 <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> +#include <ShObjIdl.h> +#include <WinInet.h> + +#include <string> + +#include "base/file_path.h" +#include "base/process_util.h" +#include "win8/delegate_execute/resource.h" // main symbols + +using namespace ATL; + +EXTERN_C const GUID CLSID_CommandExecuteImpl; + +// CommandExecuteImpl +// This class implements the IExecuteCommand and related interfaces for +// handling ShellExecute launches of the Chrome browser, i.e. whether to +// launch Chrome in metro mode or desktop mode. +#if defined(GOOGLE_CHROME_BUILD) +class ATL_NO_VTABLE DECLSPEC_UUID("5C65F4B0-3651-4514-B207-D10CB699B14B") + CommandExecuteImpl +#else // GOOGLE_CHROME_BUILD +class ATL_NO_VTABLE DECLSPEC_UUID("A2DF06F9-A21A-44A8-8A99-8B9C84F29160") + CommandExecuteImpl +#endif // GOOGLE_CHROME_BUILD + : public CComObjectRootEx<CComSingleThreadModel>, + public CComCoClass<CommandExecuteImpl, &CLSID_CommandExecuteImpl>, + public IExecuteCommand, + public IObjectWithSiteImpl<CommandExecuteImpl>, + public IInitializeCommand, + public IObjectWithSelection, + public IExecuteCommandApplicationHostEnvironment, + public IForegroundTransfer { + public: + CommandExecuteImpl(); + + DECLARE_REGISTRY_RESOURCEID(IDR_COMMANDEXECUTEIMPL) + + BEGIN_COM_MAP(CommandExecuteImpl) + COM_INTERFACE_ENTRY(IExecuteCommand) + COM_INTERFACE_ENTRY(IObjectWithSite) + COM_INTERFACE_ENTRY(IInitializeCommand) + COM_INTERFACE_ENTRY(IObjectWithSelection) + COM_INTERFACE_ENTRY(IExecuteCommandApplicationHostEnvironment) + COM_INTERFACE_ENTRY(IForegroundTransfer) + END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + HRESULT FinalConstruct() { + return S_OK; + } + + void FinalRelease() { + } + + public: + // IExecuteCommand + STDMETHOD(SetKeyState)(DWORD key_state); + STDMETHOD(SetParameters)(LPCWSTR params); + STDMETHOD(SetPosition)(POINT pt); + STDMETHOD(SetShowWindow)(int show); + STDMETHOD(SetNoShowUI)(BOOL no_show_ui); + STDMETHOD(SetDirectory)(LPCWSTR directory); + STDMETHOD(Execute)(void); + + // IInitializeCommand + STDMETHOD(Initialize)(LPCWSTR name, IPropertyBag* bag); + + // IObjectWithSelection + STDMETHOD(SetSelection)(IShellItemArray* item_array); + STDMETHOD(GetSelection)(REFIID riid, void** selection); + + // IExecuteCommandApplicationHostEnvironment + STDMETHOD(GetValue)(enum AHE_TYPE* pahe); + + // IForegroundTransfer + STDMETHOD(AllowForegroundTransfer)(void* reserved); + + private: + static bool FindChromeExe(FilePath* chrome_exe); + bool GetLaunchScheme(string16* display_name, INTERNET_SCHEME* scheme); + HRESULT LaunchDesktopChrome(); + // Returns the launch mode, i.e. desktop launch/metro launch, etc. + EC_HOST_UI_MODE GetLaunchMode(); + + CComPtr<IShellItemArray> item_array_; + string16 parameters_; + FilePath chrome_exe_; + STARTUPINFO start_info_; + string16 verb_; + string16 display_name_; + INTERNET_SCHEME launch_scheme_; + + base::IntegrityLevel integrity_level_; + EC_HOST_UI_MODE chrome_mode_; +}; + +OBJECT_ENTRY_AUTO(__uuidof(CommandExecuteImpl), CommandExecuteImpl) diff --git a/win8/delegate_execute/command_execute_impl.rgs b/win8/delegate_execute/command_execute_impl.rgs new file mode 100644 index 0000000..4f1aba3 --- /dev/null +++ b/win8/delegate_execute/command_execute_impl.rgs @@ -0,0 +1,10 @@ +HKCR { + NoRemove CLSID { + ForceRemove '%DELEGATE_EXECUTE_CLSID%' = s 'CommandExecuteImpl Class' { + ForceRemove Programmable + LocalServer32 = s '%MODULE%' { + val ServerExecutable = s '%MODULE_RAW%' + } + } + } +} diff --git a/win8/delegate_execute/delegate_execute.cc b/win8/delegate_execute/delegate_execute.cc new file mode 100644 index 0000000..9f3430c --- /dev/null +++ b/win8/delegate_execute/delegate_execute.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 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 <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> +#include <initguid.h> +#include <shellapi.h> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_handle.h" +#include "chrome/common/chrome_switches.h" +#include "command_execute_impl.h" +#include "win8/delegate_execute/delegate_execute_operation.h" +#include "win8/delegate_execute/resource.h" + +using namespace ATL; + +class DelegateExecuteModule + : public ATL::CAtlExeModuleT< DelegateExecuteModule > { + public : + typedef ATL::CAtlExeModuleT<DelegateExecuteModule> ParentClass; + + DECLARE_REGISTRY_APPID_RESOURCEID(IDR_DELEGATEEXECUTE, + "{B1935DA1-112F-479A-975B-AB8588ABA636}") + + virtual HRESULT AddCommonRGSReplacements(IRegistrarBase* registrar) throw() { + AtlTrace(L"In %hs\n", __FUNCTION__); + HRESULT hr = ParentClass::AddCommonRGSReplacements(registrar); + if (FAILED(hr)) + return hr; + + wchar_t delegate_execute_clsid[MAX_PATH] = {0}; + if (!StringFromGUID2(__uuidof(CommandExecuteImpl), delegate_execute_clsid, + ARRAYSIZE(delegate_execute_clsid))) { + ATLASSERT(false); + return E_FAIL; + } + + hr = registrar->AddReplacement(L"DELEGATE_EXECUTE_CLSID", + delegate_execute_clsid); + ATLASSERT(SUCCEEDED(hr)); + return hr; + } +}; + +DelegateExecuteModule _AtlModule; + +// Relaunch metro Chrome by ShellExecute on |shortcut| with --force-immersive. +// |handle_value| is an optional handle on which this function will wait before +// performing the relaunch. +int RelaunchChrome(const FilePath& shortcut, const string16& handle_value) { + base::win::ScopedHandle handle; + + if (!handle_value.empty()) { + uint32 the_handle = 0; + if (!base::StringToUint(handle_value, &the_handle)) { + // Failed to parse the handle value. Skip the wait but proceed with the + // relaunch. + AtlTrace(L"Failed to parse handle value %ls\n", handle_value.c_str()); + } else { + handle.Set(reinterpret_cast<HANDLE>(the_handle)); + } + } + + if (handle.IsValid()) { + AtlTrace(L"Waiting for chrome.exe to exit.\n"); + DWORD result = ::WaitForSingleObject(handle, 10 * 1000); + AtlTrace(L"And we're back.\n"); + if (result != WAIT_OBJECT_0) { + AtlTrace(L"Failed to wait for parent to exit; result=%u.\n", result); + // This could mean that Chrome has hung. Conservatively proceed with + // the relaunch anyway. + } + handle.Close(); + } + + base::win::ScopedCOMInitializer com_initializer; + + AtlTrace(L"Launching Chrome via %ls.\n", shortcut.value().c_str()); + int ser = reinterpret_cast<int>( + ::ShellExecute(NULL, NULL, shortcut.value().c_str(), + ASCIIToWide(switches::kForceImmersive).c_str(), NULL, + SW_SHOWNORMAL)); + AtlTrace(L"ShellExecute returned %d.\n", ser); + return ser <= 32; +} + +// +extern "C" int WINAPI _tWinMain(HINSTANCE , HINSTANCE, + LPTSTR, int nShowCmd) { + using delegate_execute::DelegateExecuteOperation; + base::AtExitManager exit_manager; + + CommandLine::Init(0, NULL); + CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + DelegateExecuteOperation operation; + + operation.Initialize(cmd_line); + switch (operation.operation_type()) { + case DelegateExecuteOperation::EXE_MODULE: + return _AtlModule.WinMain(nShowCmd); + + case DelegateExecuteOperation::RELAUNCH_CHROME: + return RelaunchChrome(operation.relaunch_shortcut(), + cmd_line->GetSwitchValueNative(switches::kWaitForHandle)); + + default: + NOTREACHED(); + } + + return 1; +} diff --git a/win8/delegate_execute/delegate_execute.gyp b/win8/delegate_execute/delegate_execute.gyp new file mode 100644 index 0000000..e4feff4 --- /dev/null +++ b/win8/delegate_execute/delegate_execute.gyp @@ -0,0 +1,46 @@ +# Copyright (c) 2012 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. +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../../build/win_precompile.gypi', + ], + 'targets': [ + { + 'target_name': 'delegate_execute', + 'type': 'executable', + 'dependencies': [ + '../../base/base.gyp:base', + '../../chrome/chrome.gyp:installer_util', + '../../google_update/google_update.gyp:google_update', + '../../win8/win8.gyp:check_sdk_patch', + ], + 'sources': [ + 'chrome_util.cc', + 'chrome_util.h', + 'command_execute_impl.cc', + 'command_execute_impl.h', + 'command_execute_impl.rgs', + 'delegate_execute.cc', + 'delegate_execute.rc', + 'delegate_execute.rgs', + 'delegate_execute_operation.cc', + 'delegate_execute_operation.h', + 'resource.h', + ], + 'defines': [ + # This define is required to pull in the new Win8 interfaces from + # system headers like ShObjIdl.h + 'NTDDI_VERSION=0x06020000', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '2', # Set /SUBSYSTEM:WINDOWS + }, + }, + }, + ], +} diff --git a/win8/delegate_execute/delegate_execute.rgs b/win8/delegate_execute/delegate_execute.rgs new file mode 100644 index 0000000..e7d3740 --- /dev/null +++ b/win8/delegate_execute/delegate_execute.rgs @@ -0,0 +1,3 @@ +HKCR +{ +} diff --git a/win8/delegate_execute/delegate_execute_operation.cc b/win8/delegate_execute/delegate_execute_operation.cc new file mode 100644 index 0000000..e6823f0 --- /dev/null +++ b/win8/delegate_execute/delegate_execute_operation.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2012 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 "win8/delegate_execute/delegate_execute_operation.h" + +#include "base/command_line.h" +#include "chrome/common/chrome_switches.h" + +namespace delegate_execute { + +DelegateExecuteOperation::DelegateExecuteOperation() + : operation_type_(EXE_MODULE) { +} + +DelegateExecuteOperation::~DelegateExecuteOperation() { + Clear(); +} + +void DelegateExecuteOperation::Initialize(const CommandLine* command_line) { + Clear(); + + // --relaunch-shortcut=PathToShortcut triggers the relaunch Chrome operation. + FilePath shortcut( + command_line->GetSwitchValuePath(switches::kRelaunchShortcut)); + if (!shortcut.empty()) { + relaunch_shortcut_ = shortcut; + operation_type_ = RELAUNCH_CHROME; + } +} + +void DelegateExecuteOperation::Clear() { + operation_type_ = EXE_MODULE; + relaunch_shortcut_.clear(); +} + +} // namespace delegate_execute diff --git a/win8/delegate_execute/delegate_execute_operation.h b/win8/delegate_execute/delegate_execute_operation.h new file mode 100644 index 0000000..59acfd7a --- /dev/null +++ b/win8/delegate_execute/delegate_execute_operation.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 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 WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_OPERATION_H_ +#define WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_OPERATION_H_ + +#include <atldef.h> + +#include "base/basictypes.h" +#include "base/file_path.h" + +class CommandLine; + +namespace delegate_execute { + +// Parses a portion of the DelegateExecute handler's command line to determine +// the desired operation. +class DelegateExecuteOperation { + public: + enum OperationType { + EXE_MODULE, + RELAUNCH_CHROME, + }; + + DelegateExecuteOperation(); + ~DelegateExecuteOperation(); + + void Initialize(const CommandLine* command_line); + + OperationType operation_type() const { + return operation_type_; + } + + // Returns the argument to the --relaunch-shortcut switch. Valid only when + // the operation is RELAUNCH_CHROME. + const FilePath& relaunch_shortcut() const { + ATLASSERT(operation_type_ == RELAUNCH_CHROME); + return relaunch_shortcut_; + } + + private: + void Clear(); + + OperationType operation_type_; + FilePath relaunch_shortcut_; + DISALLOW_COPY_AND_ASSIGN(DelegateExecuteOperation); +}; + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_OPERATION_H_ diff --git a/win8/delegate_execute/post_build.bat b/win8/delegate_execute/post_build.bat new file mode 100644 index 0000000..8aa9a76 --- /dev/null +++ b/win8/delegate_execute/post_build.bat @@ -0,0 +1,3 @@ +REM invoke as: post_build.bat OUTPUT_PATH_TO_EXE PATH_TO_CHROME_ROOT CONFIGURATION
+mkdir %2\build\%3
+copy %1 %2\build\%3\
diff --git a/win8/delegate_execute/resource.h b/win8/delegate_execute/resource.h new file mode 100644 index 0000000..cb81bf0 --- /dev/null +++ b/win8/delegate_execute/resource.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012 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. + +// {{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DelegateExecute.rc +// +#define IDS_PROJNAME 100 +#define IDR_DELEGATEEXECUTE 101 +#define IDR_COMMANDEXECUTEIMPL 106 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_COMMAND_VALUE 32768 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 107 +#endif +#endif diff --git a/win8/metro_driver/OWNERS b/win8/metro_driver/OWNERS new file mode 100644 index 0000000..3802937 --- /dev/null +++ b/win8/metro_driver/OWNERS @@ -0,0 +1,5 @@ +ananta@chromium.org +cpu@chromium.org +grt@chromium.org +mad@chromium.org +robertshield@chromium.org
\ No newline at end of file diff --git a/win8/metro_driver/chrome_app_view.cc b/win8/metro_driver/chrome_app_view.cc new file mode 100644 index 0000000..7d5580e --- /dev/null +++ b/win8/metro_driver/chrome_app_view.cc @@ -0,0 +1,1059 @@ +// Copyright (c) 2012 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 "win8/metro_driver/stdafx.h" +#include "win8/metro_driver/chrome_app_view.h" + +#include <algorithm> +#include <windows.applicationModel.datatransfer.h> +#include <windows.foundation.h> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/win/metro.h" + +// This include allows to send WM_SYSCOMMANDs to chrome. +#include "chrome/app/chrome_command_ids.h" +#include "win8/metro_driver/winrt_utils.h" +#include "ui/base/ui_base_switches.h" + +typedef winfoundtn::ITypedEventHandler< + winapp::Core::CoreApplicationView*, + winapp::Activation::IActivatedEventArgs*> ActivatedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::WindowSizeChangedEventArgs*> SizeChangedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Input::EdgeGesture*, + winui::Input::EdgeGestureEventArgs*> EdgeEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winapp::DataTransfer::DataTransferManager*, + winapp::DataTransfer::DataRequestedEventArgs*> ShareDataRequestedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::ViewManagement::InputPane*, + winui::ViewManagement::InputPaneVisibilityEventArgs*> + InputPaneEventHandler; + +struct Globals globals; + +// TODO(ananta) +// Remove this once we consolidate metro driver with chrome. +const wchar_t kMetroGetCurrentTabInfoMessage[] = + L"CHROME_METRO_GET_CURRENT_TAB_INFO"; + +static const int kFlipWindowsHotKeyId = 0x0000baba; + +static const int kAnimateWindowTimeoutMs = 200; + +static const int kCheckOSKDelayMs = 300; + +const wchar_t kOSKClassName[] = L"IPTip_Main_Window"; + +static const int kOSKAdjustmentOffset = 20; + +namespace { + +void AdjustToFitWindow(HWND hwnd, int flags) { + RECT rect = {0}; + ::GetWindowRect(globals.core_window, &rect); + int cx = rect.right - rect.left; + int cy = rect.bottom - rect.top; + + ::SetWindowPos(hwnd, HWND_TOP, + rect.left, rect.top, cx, cy, + SWP_NOZORDER | flags); +} + +void AdjustFrameWindowStyleForMetro(HWND hwnd) { + DVLOG(1) << __FUNCTION__; + // Ajust the frame so the live preview works and the frame buttons dissapear. + ::SetWindowLong(hwnd, GWL_STYLE, + WS_POPUP | (::GetWindowLong(hwnd, GWL_STYLE) & + ~(WS_MAXIMIZE | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU))); + ::SetWindowLong(hwnd, GWL_EXSTYLE, + ::GetWindowLong(hwnd, GWL_EXSTYLE) & ~(WS_EX_DLGMODALFRAME | + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); + AdjustToFitWindow(hwnd, SWP_FRAMECHANGED | SWP_NOACTIVATE); +} + +void SetFrameWindowInternal(HWND hwnd) { + DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd); + + HWND current_top_frame = + !globals.host_windows.empty() ? globals.host_windows.front().first : NULL; + if (hwnd != current_top_frame && IsWindow(current_top_frame)) { + DVLOG(1) << "Hiding current top window, hwnd=" + << LONG_PTR(current_top_frame); + ::ShowWindow(current_top_frame, SW_HIDE); + } + + // If chrome opens a url in a foreground tab, it may call SetFrameWindow + // again. Ensure that we don't have dups. + globals.host_windows.remove_if([hwnd](std::pair<HWND, bool>& item) { + return (item.first == hwnd); + }); + + globals.host_windows.push_front(std::make_pair(hwnd, false)); + + AdjustFrameWindowStyleForMetro(hwnd); +} + +void CloseFrameWindowInternal(HWND hwnd) { + DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd); + + globals.host_windows.remove_if([hwnd](std::pair<HWND, bool>& item) { + return (item.first == hwnd); + }); + + if (globals.host_windows.size() > 0) { + DVLOG(1) << "Making new top frame window visible:" + << reinterpret_cast<int>(globals.host_windows.front().first); + AdjustToFitWindow(globals.host_windows.front().first, SWP_SHOWWINDOW); + } else { + // time to quit + DVLOG(1) << "Last host window closed. Calling Exit()."; + globals.app_exit->Exit(); + } +} + +void FlipFrameWindowsInternal() { + DVLOG(1) << __FUNCTION__; + // Get the first window in the frame windows queue and push it to the end. + // Metroize the next window in the queue. + if (globals.host_windows.size() > 1) { + std::pair<HWND, bool> current_top_window = globals.host_windows.front(); + globals.host_windows.pop_front(); + + DVLOG(1) << "Making new top frame window visible:" + << reinterpret_cast<int>(globals.host_windows.front().first); + + AdjustToFitWindow(globals.host_windows.front().first, SWP_SHOWWINDOW); + + DVLOG(1) << "Hiding current top window:" + << reinterpret_cast<int>(current_top_window.first); + AnimateWindow(current_top_window.first, kAnimateWindowTimeoutMs, + AW_HIDE | AW_HOR_POSITIVE | AW_SLIDE); + + globals.host_windows.push_back(current_top_window); + } +} + +} // namespace + +HRESULT ChromeAppView::TileRequestCreateDone( + winfoundtn::IAsyncOperation<bool>* async, + AsyncStatus status) { + if (status == Completed) { + unsigned char result; + CheckHR(async->GetResults(&result)); + DVLOG(1) << __FUNCTION__ << " result " << static_cast<int>(result); + } else { + LOG(ERROR) << __FUNCTION__ << " Unexpected async status " << status; + } + + return S_OK; +} + +void ChromeAppView::DisplayNotification( + const ToastNotificationHandler::DesktopNotification& notification) { + DVLOG(1) << __FUNCTION__; + + if (IsValidNotification(notification.id)) { + NOTREACHED() << "Duplicate notification id passed in."; + return; + } + + base::AutoLock lock(notification_lock_); + + ToastNotificationHandler* notification_handler = + new ToastNotificationHandler; + + notification_map_[notification.id].reset(notification_handler); + notification_handler->DisplayNotification(notification); +} + +void ChromeAppView::CancelNotification(const std::string& notification) { + DVLOG(1) << __FUNCTION__; + + base::AutoLock lock(notification_lock_); + + NotificationMap::iterator index = notification_map_.find(notification); + if (index == notification_map_.end()) { + NOTREACHED() << "Invalid notification:" << notification.c_str(); + return; + } + + scoped_ptr<ToastNotificationHandler> notification_handler( + index->second.release()); + + notification_map_.erase(index); + + notification_handler->CancelNotification(); +} + +// Returns true if the notification passed in is valid. +bool ChromeAppView::IsValidNotification(const std::string& notification) { + DVLOG(1) << __FUNCTION__; + + base::AutoLock lock(notification_lock_); + return notification_map_.find(notification) != notification_map_.end(); +} + +void ChromeAppView::ShowDialogBox( + const MetroDialogBox::DialogBoxInfo& dialog_box_info) { + VLOG(1) << __FUNCTION__; + dialog_box_.Show(dialog_box_info); +} + +void ChromeAppView::DismissDialogBox() { + VLOG(1) << __FUNCTION__; + dialog_box_.Dismiss(); +} + +// static +HRESULT ChromeAppView::Unsnap() { + mswr::ComPtr<winui::ViewManagement::IApplicationViewStatics> view_statics; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_ViewManagement_ApplicationView, + view_statics.GetAddressOf()); + CheckHR(hr); + + winui::ViewManagement::ApplicationViewState state = + winui::ViewManagement::ApplicationViewState_FullScreenLandscape; + hr = view_statics->get_Value(&state); + CheckHR(hr); + + if (state == winui::ViewManagement::ApplicationViewState_Snapped) { + boolean success = FALSE; + hr = view_statics->TryUnsnap(&success); + + if (FAILED(hr) || !success) { + LOG(ERROR) << "Failed to unsnap. Error 0x" << hr; + if (SUCCEEDED(hr)) + hr = E_UNEXPECTED; + } + } + return hr; +} + +void ChromeAppView::SetFullscreen(bool fullscreen) { + VLOG(1) << __FUNCTION__; + + if (osk_offset_adjustment_) { + VLOG(1) << "Scrolling the window down by: " + << osk_offset_adjustment_; + + ::ScrollWindowEx(globals.host_windows.front().first, + 0, + osk_offset_adjustment_, + NULL, + NULL, + NULL, + NULL, + SW_INVALIDATE | SW_SCROLLCHILDREN); + osk_offset_adjustment_ = 0; + } +} + +void UnsnapHelper() { + ChromeAppView::Unsnap(); +} + +extern "C" __declspec(dllexport) +void MetroUnsnap() { + DVLOG(1) << __FUNCTION__; + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&UnsnapHelper)); +} + +extern "C" __declspec(dllexport) +HWND GetRootWindow() { + DVLOG(1) << __FUNCTION__; + return globals.core_window; +} + +extern "C" __declspec(dllexport) +void SetFrameWindow(HWND hwnd) { + DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd); + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&SetFrameWindowInternal, hwnd)); +} + +// TODO(ananta) +// Handle frame window close by deleting it from the window list and making the +// next guy visible. +extern "C" __declspec(dllexport) + void CloseFrameWindow(HWND hwnd) { + DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(hwnd); + + // This is a hack to ensure that the BrowserViewLayout code layout happens + // just at the right time to hide the switcher button if it is visible. + globals.appview_msg_loop->PostDelayedTask( + FROM_HERE, base::Bind(&CloseFrameWindowInternal, hwnd), + base::TimeDelta::FromMilliseconds(50)); +} + +// Returns the initial url. This returns a valid url only if we were launched +// into metro via a url navigation. +extern "C" __declspec(dllexport) +const wchar_t* GetInitialUrl() { + DVLOG(1) << __FUNCTION__; + bool was_initial_activation = globals.is_initial_activation; + globals.is_initial_activation = false; + if (!was_initial_activation || globals.navigation_url.empty()) + return L""; + + const wchar_t* initial_url = globals.navigation_url.c_str(); + DVLOG(1) << initial_url; + return initial_url; +} + +// Returns the initial search string. This returns a valid url only if we were +// launched into metro via the search charm +extern "C" __declspec(dllexport) +const wchar_t* GetInitialSearchString() { + DVLOG(1) << __FUNCTION__; + bool was_initial_activation = globals.is_initial_activation; + globals.is_initial_activation = false; + if (!was_initial_activation || globals.search_string.empty()) + return L""; + + const wchar_t* initial_search_string = globals.search_string.c_str(); + DVLOG(1) << initial_search_string; + return initial_search_string; +} + +// Returns the launch type. +extern "C" __declspec(dllexport) +base::win::MetroLaunchType GetLaunchType( + base::win::MetroPreviousExecutionState* previous_state) { + if (previous_state) { + *previous_state = static_cast<base::win::MetroPreviousExecutionState>( + globals.previous_state); + } + return static_cast<base::win::MetroLaunchType>( + globals.initial_activation_kind); +} + +extern "C" __declspec(dllexport) +void FlipFrameWindows() { + DVLOG(1) << __FUNCTION__; + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&FlipFrameWindowsInternal)); +} + +extern "C" __declspec(dllexport) +void DisplayNotification(const char* origin_url, const char* icon_url, + const wchar_t* title, const wchar_t* body, + const wchar_t* display_source, + const char* notification_id) { + // TODO(ananta) + // Needs implementation. + DVLOG(1) << __FUNCTION__; + + ToastNotificationHandler::DesktopNotification notification(origin_url, + icon_url, + title, + body, + display_source, + notification_id); + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&ChromeAppView::DisplayNotification, + globals.view, notification)); +} + +extern "C" __declspec(dllexport) +bool CancelNotification(const char* notification_id) { + // TODO(ananta) + // Needs implementation. + DVLOG(1) << __FUNCTION__; + + if (!globals.view->IsValidNotification(notification_id)) { + NOTREACHED() << "Invalid notification id :" << notification_id; + return false; + } + + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&ChromeAppView::CancelNotification, + globals.view, std::string(notification_id))); + return true; +} + +// Returns command line switches if any to be used by metro chrome. +extern "C" __declspec(dllexport) +const wchar_t* GetMetroCommandLineSwitches() { + DVLOG(1) << __FUNCTION__; + // The metro_command_line_switches field should be filled up once. + // ideally in ChromeAppView::Activate. + return globals.metro_command_line_switches.c_str(); +} + +// Provides functionality to display a metro style dialog box with two buttons. +// Only one dialog box can be displayed at any given time. +extern "C" __declspec(dllexport) +void ShowDialogBox( + const wchar_t* title, + const wchar_t* content, + const wchar_t* button1_label, + const wchar_t* button2_label, + base::win::MetroDialogButtonPressedHandler button1_handler, + base::win::MetroDialogButtonPressedHandler button2_handler) { + VLOG(1) << __FUNCTION__; + + DCHECK(title); + DCHECK(content); + DCHECK(button1_label); + DCHECK(button2_label); + DCHECK(button1_handler); + DCHECK(button2_handler); + + MetroDialogBox::DialogBoxInfo dialog_box_info; + dialog_box_info.title = title; + dialog_box_info.content = content; + dialog_box_info.button1_label = button1_label; + dialog_box_info.button2_label = button2_label; + dialog_box_info.button1_handler = button1_handler; + dialog_box_info.button2_handler = button2_handler; + + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind( + &ChromeAppView::ShowDialogBox, globals.view, dialog_box_info)); +} + +// Provides functionality to dismiss the previously displayed metro style +// dialog box. +extern "C" __declspec(dllexport) +void DismissDialogBox() { + VLOG(1) << __FUNCTION__; + + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind( + &ChromeAppView::DismissDialogBox, + globals.view)); +} + +extern "C" __declspec(dllexport) +void SetFullscreen(bool fullscreen) { + VLOG(1) << __FUNCTION__; + + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind( + &ChromeAppView::SetFullscreen, + globals.view, fullscreen)); +} + +BOOL CALLBACK CoreWindowFinder(HWND hwnd, LPARAM) { + char classname[128]; + if (::GetClassNameA(hwnd, classname, ARRAYSIZE(classname))) { + if (lstrcmpiA("Windows.UI.Core.CoreWindow", classname) == 0) { + globals.core_window = hwnd; + return FALSE; + } + } + return TRUE; +} + +template <typename ContainerT> +void CloseSecondaryWindows(ContainerT& windows) { + DVLOG(1) << "Closing secondary windows", windows.size(); + std::for_each(windows.begin(), windows.end(), [](HWND hwnd) { + ::PostMessageW(hwnd, WM_CLOSE, 0, 0); + }); + windows.clear(); +} + +void EndChromeSession() { + DVLOG(1) << "Sending chrome WM_ENDSESSION window message."; + ::SendMessage(globals.host_windows.front().first, WM_ENDSESSION, FALSE, + ENDSESSION_CLOSEAPP); +} + +DWORD WINAPI HostMainThreadProc(void*) { + // Test feature - devs have requested the ability to easily add metro-chrome + // command line arguments. This is hard since shortcut arguments are ignored, + // by Metro, so we instead read them directly from the pinned taskbar + // shortcut. This may call Coinitialize and there is tell of badness + // occurring if CoInitialize is called on a metro thread. + globals.metro_command_line_switches = + winrt_utils::ReadArgumentsFromPinnedTaskbarShortcut(); + + globals.g_core_proc = reinterpret_cast<WNDPROC>( + ::SetWindowLong(globals.core_window, GWL_WNDPROC, + reinterpret_cast<long>(ChromeAppView::CoreWindowProc))); + DWORD exit_code = globals.host_main(globals.host_context); + DVLOG(1) << "host thread done, exit_code=" << exit_code; + globals.app_exit->Exit(); + return exit_code; +} + +ChromeAppView::ChromeAppView() + : osk_visible_notification_received_(false), + osk_offset_adjustment_(0) { + globals.previous_state = + winapp::Activation::ApplicationExecutionState_NotRunning; +} + +ChromeAppView::~ChromeAppView() { + DVLOG(1) << __FUNCTION__; +} + +IFACEMETHODIMP +ChromeAppView::Initialize(winapp::Core::ICoreApplicationView* view) { + view_ = view; + DVLOG(1) << __FUNCTION__; + globals.main_thread_id = ::GetCurrentThreadId(); + + HRESULT hr = view_->add_Activated(mswr::Callback<ActivatedHandler>( + this, &ChromeAppView::OnActivate).Get(), + &activated_token_); + CheckHR(hr); + return hr; +} + +IFACEMETHODIMP +ChromeAppView::SetWindow(winui::Core::ICoreWindow* window) { + window_ = window; + DVLOG(1) << __FUNCTION__; + + HRESULT hr = url_launch_handler_.Initialize(); + CheckHR(hr, "Failed to initialize url launch handler."); + + // Register for size notifications. + hr = window_->add_SizeChanged(mswr::Callback<SizeChangedHandler>( + this, &ChromeAppView::OnSizeChanged).Get(), + &sizechange_token_); + CheckHR(hr); + + // Register for edge gesture notifications. + mswr::ComPtr<winui::Input::IEdgeGestureStatics> edge_gesture_statics; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_Input_EdgeGesture, + edge_gesture_statics.GetAddressOf()); + CheckHR(hr, "Failed to activate IEdgeGestureStatics."); + + mswr::ComPtr<winui::Input::IEdgeGesture> edge_gesture; + hr = edge_gesture_statics->GetForCurrentView(&edge_gesture); + CheckHR(hr); + + hr = edge_gesture->add_Completed(mswr::Callback<EdgeEventHandler>( + this, &ChromeAppView::OnEdgeGestureCompleted).Get(), + &edgeevent_token_); + CheckHR(hr); + + // Register for share notifications. + mswr::ComPtr<winapp::DataTransfer::IDataTransferManagerStatics> + data_mgr_statics; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager, + data_mgr_statics.GetAddressOf()); + CheckHR(hr, "Failed to activate IDataTransferManagerStatics."); + + mswr::ComPtr<winapp::DataTransfer::IDataTransferManager> data_transfer_mgr; + hr = data_mgr_statics->GetForCurrentView(&data_transfer_mgr); + CheckHR(hr, "Failed to get IDataTransferManager for current view."); + + hr = data_transfer_mgr->add_DataRequested( + mswr::Callback<ShareDataRequestedHandler>( + this, &ChromeAppView::OnShareDataRequested).Get(), + &share_data_requested_token_); + CheckHR(hr); + + // TODO(ananta) + // The documented InputPane notifications don't fire on Windows 8 in metro + // chrome. Uncomment this once we figure out why they don't fire. + // RegisterInputPaneNotifications(); + + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_ViewManagement_ApplicationView, + app_view_.GetAddressOf()); + CheckHR(hr); + + DVLOG(1) << "Created appview instance."; + + hr = devices_handler_.Initialize(window); + // Don't check or return the failure here, we need to let the app + // initialization succeed. Even if we won't be able to access devices + // we still want to allow the app to start. + LOG_IF(ERROR, FAILED(hr)) << "Failed to initialize devices handler."; + return S_OK; +} + +IFACEMETHODIMP +ChromeAppView::Load(HSTRING entryPoint) { + DVLOG(1) << __FUNCTION__; + return S_OK; +} + +void RunMessageLoop(winui::Core::ICoreDispatcher* dispatcher) { + // We're entering a nested message loop, let's allow dispatching + // tasks while we're in there. + MessageLoop::current()->SetNestableTasksAllowed(true); + + // Enter main core message loop. There are several ways to exit it + // Nicely: + // 1 - User action like ALT-F4. + // 2 - Calling ICoreApplicationExit::Exit(). + // 3- Posting WM_CLOSE to the core window. + HRESULT hr = dispatcher->ProcessEvents( + winui::Core::CoreProcessEventsOption + ::CoreProcessEventsOption_ProcessUntilQuit); + + // Wind down the thread's chrome message loop. + MessageLoop::current()->Quit(); +} + +void ChromeAppView::CheckForOSKActivation() { + // Hack for checking if the OSK was displayed while we are in the foreground. + // The input pane notifications which are supposed to fire when the OSK is + // shown and hidden don't seem to be firing in Windows 8 metro for us. + // The current hack is supposed to workaround that issue till we figure it + // out. Logic is to find the OSK window and see if we are the foreground + // process. If yes then fire the notification once for when the OSK is shown + // and once for when it is hidden. + // TODO(ananta) + // Take this out when the documented input pane notifcation issues are + // addressed. + HWND osk = ::FindWindow(kOSKClassName, NULL); + if (::IsWindow(osk)) { + HWND foreground_window = ::GetForegroundWindow(); + if (globals.host_windows.size() > 0 && + foreground_window == globals.host_windows.front().first) { + RECT osk_rect = {0}; + ::GetWindowRect(osk, &osk_rect); + + if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) { + if (!globals.view->osk_visible_notification_received()) { + DVLOG(1) << "Found KB window while we are in the forground."; + HandleInputPaneVisible(osk_rect); + } + } else if (osk_visible_notification_received()) { + DVLOG(1) << "KB window hidden while we are in the foreground."; + HandleInputPaneHidden(osk_rect); + } + } + } + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ChromeAppView::CheckForOSKActivation, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs)); +} + +IFACEMETHODIMP +ChromeAppView::Run() { + DVLOG(1) << __FUNCTION__ << ", hwnd=" << LONG_PTR(window_.Get()); + mswr::ComPtr<winui::Core::ICoreDispatcher> dispatcher; + HRESULT hr = window_->get_Dispatcher(&dispatcher); + CheckHR(hr, "Dispatcher failed."); + + hr = window_->Activate(); + if (SUCCEEDED(hr)) { + // TODO(cpu): Draw something here. + } else { + DVLOG(1) << "Activate failed, hr=" << hr; + } + + // Create a message loop to allow message passing into this thread. + MessageLoop msg_loop(MessageLoop::TYPE_UI); + + // Announce our message loop to the world. + globals.appview_msg_loop = msg_loop.message_loop_proxy(); + + // And post the task that'll do the inner Metro message pumping to it. + msg_loop.PostTask(FROM_HERE, base::Bind(&RunMessageLoop, dispatcher.Get())); + + // Post the recurring task which checks for OSK activation in metro chrome. + // Please refer to the comments in the CheckForOSKActivation function for why + // this is needed. + // TODO(ananta) + // Take this out when the documented OSK notifications start working. + msg_loop.PostDelayedTask( + FROM_HERE, + base::Bind(&ChromeAppView::CheckForOSKActivation, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs)); + + msg_loop.Run(); + + globals.appview_msg_loop = NULL; + + DVLOG(0) << "ProcessEvents done, hr=" << hr; + + // We join here with chrome's main thread so that the chrome is not killed + // while a critical operation is still in progress. Now, if there are host + // windows active it is possible we end up stuck on the wait below therefore + // we tell chrome to close its windows. + if (!globals.host_windows.empty()) { + DVLOG(1) << "Chrome still has windows open!"; + EndChromeSession(); + } + DWORD wr = ::WaitForSingleObject(globals.host_thread, INFINITE); + if (wr != WAIT_OBJECT_0) { + DVLOG(1) << "Waiting for host thread failed : " << wr; + } + ::CloseHandle(globals.host_thread); + globals.host_thread = NULL; + + return hr; +} + +IFACEMETHODIMP +ChromeAppView::Uninitialize() { + DVLOG(1) << __FUNCTION__; + window_ = nullptr; + view_ = nullptr; + base::AutoLock lock(notification_lock_); + notification_map_.clear(); + return S_OK; +} + +HRESULT ChromeAppView::RegisterInputPaneNotifications() { + DVLOG(1) << __FUNCTION__; + + mswr::ComPtr<winui::ViewManagement::IInputPaneStatics> + input_pane_statics; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_ViewManagement_InputPane, + input_pane_statics.GetAddressOf()); + CheckHR(hr); + + hr = input_pane_statics->GetForCurrentView(&input_pane_); + CheckHR(hr); + DVLOG(1) << "Got input pane."; + + hr = input_pane_->add_Showing( + mswr::Callback<InputPaneEventHandler>( + this, &ChromeAppView::OnInputPaneVisible).Get(), + &input_pane_visible_token_); + CheckHR(hr); + + DVLOG(1) << "Added showing event handler for input pane", + input_pane_visible_token_.value; + + hr = input_pane_->add_Hiding( + mswr::Callback<InputPaneEventHandler>( + this, &ChromeAppView::OnInputPaneHiding).Get(), + &input_pane_hiding_token_); + CheckHR(hr); + + DVLOG(1) << "Added hiding event handler for input pane, value=" + << input_pane_hiding_token_.value; + return hr; +} + +HRESULT ChromeAppView::OnActivate(winapp::Core::ICoreApplicationView*, + winapp::Activation::IActivatedEventArgs* args) { + DVLOG(1) << __FUNCTION__; + + args->get_PreviousExecutionState(&globals.previous_state); + DVLOG(1) << "Previous Execution State: " << globals.previous_state; + + window_->Activate(); + url_launch_handler_.Activate(args); + + if (globals.previous_state == + winapp::Activation::ApplicationExecutionState_Running && + globals.host_thread) { + DVLOG(1) << "Already running. Skipping rest of OnActivate."; + return S_OK; + } + + do { + ::Sleep(10); + ::EnumThreadWindows(globals.main_thread_id, &CoreWindowFinder, 0); + } while (globals.core_window == NULL); + + DVLOG(1) << "CoreWindow found: " << std::hex << globals.core_window; + + if (!globals.host_thread) { + globals.host_thread = + ::CreateThread(NULL, 0, HostMainThreadProc, NULL, 0, NULL); + + if (!globals.host_thread) { + NOTREACHED() << "thread creation failed."; + return E_UNEXPECTED; + } + } + + if (RegisterHotKey(globals.core_window, kFlipWindowsHotKeyId, + MOD_CONTROL, VK_F12)) { + DVLOG(1) << "Registered flip window hotkey."; + } else { + VPLOG(1) << "Failed to register flip window hotkey."; + } + HRESULT hr = settings_handler_.Initialize(); + CheckHR(hr,"Failed to initialize settings handler."); + return hr; +} + +// We subclass the core window for moving the associated chrome window when the +// core window is moved around, typically in the snap view operation. The +// size changes are handled in the documented size changed event. +LRESULT CALLBACK ChromeAppView::CoreWindowProc( + HWND window, UINT message, WPARAM wp, LPARAM lp) { + + static const UINT kBrowserClosingMessage = + ::RegisterWindowMessage(L"DefaultBrowserClosing"); + + if (message == WM_WINDOWPOSCHANGED) { + WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lp); + if (!(pos->flags & SWP_NOMOVE)) { + DVLOG(1) << "WM_WINDOWPOSCHANGED. Moving the chrome window."; + globals.view->OnPositionChanged(pos->x, pos->y); + } + } else if (message == WM_HOTKEY && wp == kFlipWindowsHotKeyId) { + FlipFrameWindows(); + } else if (message == kBrowserClosingMessage) { + DVLOG(1) << "Received DefaultBrowserClosing window message."; + // Ensure that the view is uninitialized. The kBrowserClosingMessage + // means that the app is going to be terminated, i.e. the proper + // uninitialization sequence does not occur. + globals.view->Uninitialize(); + if (!globals.host_windows.empty()) { + EndChromeSession(); + } + } + return CallWindowProc(globals.g_core_proc, window, message, wp, lp); +} + +HRESULT ChromeAppView::OnSizeChanged(winui::Core::ICoreWindow* sender, + winui::Core::IWindowSizeChangedEventArgs* args) { + if (!globals.host_windows.size()) { + return S_OK; + } + + winfoundtn::Size size; + args->get_Size(&size); + + int cx = static_cast<int>(size.Width); + int cy = static_cast<int>(size.Height); + + if (!::SetWindowPos(globals.host_windows.front().first, NULL, 0, 0, cx, cy, + SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED)) { + DVLOG(1) << "SetWindowPos failed."; + } + DVLOG(1) << "size changed cx=" << cx; + DVLOG(1) << "size changed cy=" << cy; + + winui::ViewManagement::ApplicationViewState view_state = + winui::ViewManagement::ApplicationViewState_FullScreenLandscape; + app_view_->get_Value(&view_state); + + HWND top_level_frame = globals.host_windows.front().first; + if (view_state == winui::ViewManagement::ApplicationViewState_Snapped) { + DVLOG(1) << "Enabling metro snap mode."; + ::PostMessageW(top_level_frame, WM_SYSCOMMAND, IDC_METRO_SNAP_ENABLE, 0); + } else { + ::PostMessageW(top_level_frame, WM_SYSCOMMAND, IDC_METRO_SNAP_DISABLE, 0); + } + return S_OK; +} + +HRESULT ChromeAppView::OnPositionChanged(int x, int y) { + DVLOG(1) << __FUNCTION__; + + ::SetWindowPos(globals.host_windows.front().first, NULL, x, y, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOSIZE); + return S_OK; +} + +HRESULT ChromeAppView::OnEdgeGestureCompleted( + winui::Input::IEdgeGesture* gesture, + winui::Input::IEdgeGestureEventArgs* args) { + DVLOG(1) << "edge gesture completed."; + + winui::ViewManagement::ApplicationViewState view_state = + winui::ViewManagement::ApplicationViewState_FullScreenLandscape; + app_view_->get_Value(&view_state); + // We don't want fullscreen chrome unless we are fullscreen metro. + if ((view_state == winui::ViewManagement::ApplicationViewState_Filled) || + (view_state == winui::ViewManagement::ApplicationViewState_Snapped)) { + DVLOG(1) << "No full screen in snapped view state:" << view_state; + return S_OK; + } + + // Deactivate anything pending, e.g., the wrench or a context menu. + BOOL success = ::ReleaseCapture(); + DCHECK(success) << "Couldn't ReleaseCapture() before going full screen"; + + DVLOG(1) << "Going full screen."; + ::PostMessageW(globals.host_windows.front().first, WM_SYSCOMMAND, + IDC_FULLSCREEN, 0); + return S_OK; +} + +HRESULT ChromeAppView::OnShareDataRequested( + winapp::DataTransfer::IDataTransferManager* data_transfer_mgr, + winapp::DataTransfer::IDataRequestedEventArgs* event_args) { + + DVLOG(1) << "Share data requested."; + + // The current tab info is retrieved from Chrome via a registered window + // message. + + static const UINT get_current_tab_info = + RegisterWindowMessage(kMetroGetCurrentTabInfoMessage); + + static const int kGetTabInfoTimeoutMs = 1000; + + mswr::ComPtr<winapp::DataTransfer::IDataRequest> data_request; + HRESULT hr = event_args->get_Request(&data_request); + CheckHR(hr); + + mswr::ComPtr<winapp::DataTransfer::IDataPackage> data_package; + hr = data_request->get_Data(&data_package); + CheckHR(hr); + + base::win::CurrentTabInfo current_tab_info; + current_tab_info.title = NULL; + current_tab_info.url = NULL; + + DWORD_PTR result = 0; + + if (!SendMessageTimeout(globals.host_windows.front().first, + get_current_tab_info, + reinterpret_cast<WPARAM>(¤t_tab_info), + 0, + SMTO_ABORTIFHUNG, + kGetTabInfoTimeoutMs, + &result)) { + VPLOG(1) << "Failed to retrieve tab info from chrome."; + return E_FAIL; + } + + if (!current_tab_info.title || !current_tab_info.url) { + DVLOG(1) << "Failed to retrieve tab info from chrome."; + return E_FAIL; + } + + string16 current_title(current_tab_info.title); + string16 current_url(current_tab_info.url); + + LocalFree(current_tab_info.title); + LocalFree(current_tab_info.url); + + mswr::ComPtr<winapp::DataTransfer::IDataPackagePropertySet> data_properties; + hr = data_package->get_Properties(&data_properties); + + mswrw::HString title; + title.Attach(MakeHString(current_title)); + data_properties->put_Title(title.Get()); + + mswr::ComPtr<winfoundtn::IUriRuntimeClassFactory> uri_factory; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Foundation_Uri, + uri_factory.GetAddressOf()); + CheckHR(hr); + + mswrw::HString url; + url.Attach(MakeHString(current_url)); + mswr::ComPtr<winfoundtn::IUriRuntimeClass> uri; + hr = uri_factory->CreateUri(url.Get(), &uri); + CheckHR(hr); + + hr = data_package->SetUri(uri.Get()); + CheckHR(hr); + + return S_OK; +} + +void ChromeAppView::HandleInputPaneVisible(const RECT& osk_rect) { + DCHECK(!osk_visible_notification_received_); + + DVLOG(1) << __FUNCTION__; + DVLOG(1) << "OSK width:" << osk_rect.right - osk_rect.left; + DVLOG(1) << "OSK height:" << osk_rect.bottom - osk_rect.top; + + globals.host_windows.front().second = false; + + POINT cursor_pos = {0}; + GetCursorPos(&cursor_pos); + + osk_offset_adjustment_ = 0; + + if (::PtInRect(&osk_rect, cursor_pos)) { + DVLOG(1) << "OSK covering focus point."; + int osk_height = osk_rect.bottom - osk_rect.top; + + osk_offset_adjustment_ = osk_height + kOSKAdjustmentOffset; + + DVLOG(1) << "Scrolling window by offset: " << osk_offset_adjustment_; + ::ScrollWindowEx(globals.host_windows.front().first, + 0, + -osk_offset_adjustment_, + NULL, + NULL, + NULL, + NULL, + SW_INVALIDATE | SW_SCROLLCHILDREN); + + globals.host_windows.front().second = true; + } + osk_visible_notification_received_ = true; +} + +void ChromeAppView::HandleInputPaneHidden(const RECT& osk_rect) { + DCHECK(osk_visible_notification_received_); + DVLOG(1) << __FUNCTION__; + DVLOG(1) << "OSK width:" << osk_rect.right - osk_rect.left; + DVLOG(1) << "OSK height:" << osk_rect.bottom - osk_rect.top; + osk_visible_notification_received_ = false; + if (globals.host_windows.front().second == true) { + + if (osk_offset_adjustment_) { + DVLOG(1) << "Restoring scrolled window offset: " + << osk_offset_adjustment_; + + ::ScrollWindowEx(globals.host_windows.front().first, + 0, + osk_offset_adjustment_, + NULL, + NULL, + NULL, + NULL, + SW_INVALIDATE | SW_SCROLLCHILDREN); + } + + globals.host_windows.front().second = false; + } +} + +HRESULT ChromeAppView::OnInputPaneVisible( + winui::ViewManagement::IInputPane* input_pane, + winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args) { + DVLOG(1) << __FUNCTION__; + return S_OK; +} + +HRESULT ChromeAppView::OnInputPaneHiding( + winui::ViewManagement::IInputPane* input_pane, + winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args) { + DVLOG(1) << __FUNCTION__; + return S_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +ChromeAppViewFactory::ChromeAppViewFactory( + winapp::Core::ICoreApplication* icore_app, + LPTHREAD_START_ROUTINE host_main, + void* host_context) { + globals.host_main = host_main; + globals.host_context = host_context; + mswr::ComPtr<winapp::Core::ICoreApplication> core_app(icore_app); + mswr::ComPtr<winapp::Core::ICoreApplicationExit> app_exit; + CheckHR(core_app.As(&app_exit)); + globals.app_exit = app_exit.Detach(); +} + +IFACEMETHODIMP +ChromeAppViewFactory::CreateView(winapp::Core::IFrameworkView** view) { + globals.view = mswr::Make<ChromeAppView>().Detach(); + *view = globals.view; + return (*view) ? S_OK : E_OUTOFMEMORY; +} diff --git a/win8/metro_driver/chrome_app_view.h b/win8/metro_driver/chrome_app_view.h new file mode 100644 index 0000000..80f309d --- /dev/null +++ b/win8/metro_driver/chrome_app_view.h @@ -0,0 +1,177 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_CHROME_APP_VIEW_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_CHROME_APP_VIEW_H_ + +#include <windows.applicationmodel.core.h> +#include <windows.ui.core.h> +#include <windows.ui.input.h> +#include <windows.ui.viewmanagement.h> + +#include <list> +#include <map> +#include <string> +#include <utility> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/synchronization/lock.h" +#include "win8/metro_driver/chrome_url_launch_handler.h" +#include "win8/metro_driver/devices_handler.h" +#include "win8/metro_driver/metro_dialog_box.h" +#include "win8/metro_driver/settings_handler.h" +#include "win8/metro_driver/toast_notification_handler.h" + +class ChromeAppView + : public mswr::RuntimeClass<winapp::Core::IFrameworkView> { + public: + ChromeAppView(); + ~ChromeAppView(); + + // IViewProvider overrides. + IFACEMETHOD(Initialize)(winapp::Core::ICoreApplicationView* view); + IFACEMETHOD(SetWindow)(winui::Core::ICoreWindow* window); + IFACEMETHOD(Load)(HSTRING entryPoint); + IFACEMETHOD(Run)(); + IFACEMETHOD(Uninitialize)(); + + static LRESULT CALLBACK CoreWindowProc(HWND window, UINT message, WPARAM wp, + LPARAM lp); + + HRESULT TileRequestCreateDone(winfoundtn::IAsyncOperation<bool>* async, + AsyncStatus status); + + bool osk_visible_notification_received() const { + return osk_visible_notification_received_; + } + + // Displays the notification. + void DisplayNotification( + const ToastNotificationHandler::DesktopNotification& notification); + + // Cancels the notification. + void CancelNotification(const std::string& notification); + + // Returns true if the notification passed in is valid. + bool IsValidNotification(const std::string& notification); + + // Displays a dialog box. + void ShowDialogBox(const MetroDialogBox::DialogBoxInfo& dialog_box_info); + // Dismisses the dialog box. + void DismissDialogBox(); + + // Helper function to unsnap the chrome metro app if it is snapped. + // Returns S_OK on success. + static HRESULT Unsnap(); + + // Notification from chrome that a full screen operation is being performed. + void SetFullscreen(bool fullscreen); + + private: + HRESULT OnActivate(winapp::Core::ICoreApplicationView* view, + winapp::Activation::IActivatedEventArgs* args); + + HRESULT OnSizeChanged(winui::Core::ICoreWindow* sender, + winui::Core::IWindowSizeChangedEventArgs* args); + + HRESULT OnEdgeGestureCompleted(winui::Input::IEdgeGesture* gesture, + winui::Input::IEdgeGestureEventArgs* args); + + HRESULT OnShareDataRequested( + winapp::DataTransfer::IDataTransferManager* data_transfer_mgr, + winapp::DataTransfer::IDataRequestedEventArgs* event_args); + + HRESULT OnInputPaneVisible( + winui::ViewManagement::IInputPane* input_pane, + winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args); + + HRESULT OnInputPaneHiding( + winui::ViewManagement::IInputPane* input_pane, + winui::ViewManagement::IInputPaneVisibilityEventArgs* event_args); + + HRESULT OnPositionChanged(int x, int y); + + void CheckForOSKActivation(); + + HRESULT RegisterInputPaneNotifications(); + + void HandleInputPaneVisible(const RECT& osk_rect); + void HandleInputPaneHidden(const RECT& osk_rect); + + mswr::ComPtr<winui::Core::ICoreWindow> window_; + mswr::ComPtr<winapp::Core::ICoreApplicationView> view_; + EventRegistrationToken activated_token_; + EventRegistrationToken edgeevent_token_; + EventRegistrationToken sizechange_token_; + EventRegistrationToken share_data_requested_token_; + EventRegistrationToken input_pane_visible_token_; + EventRegistrationToken input_pane_hiding_token_; + EventRegistrationToken app_exit_token_; + + ChromeUrlLaunchHandler url_launch_handler_; + metro_driver::DevicesHandler devices_handler_; + SettingsHandler settings_handler_; + mswr::ComPtr<winui::ViewManagement::IInputPane> input_pane_; + mswr::ComPtr<winui::ViewManagement::IApplicationViewStatics> app_view_; + + bool osk_visible_notification_received_; + + // map of notification id to the ToastNotificationHandler instance. + typedef std::map<std::string, scoped_ptr<ToastNotificationHandler> > + NotificationMap; + NotificationMap notification_map_; + + // Synchronizes access to the notification_map_ member. + base::Lock notification_lock_; + + // If the OSK covers the input area we scroll the window by the height of the + // OSK + an additional offset. This member holds this offset. Set to 0 if the + // window was not scrolled. + int osk_offset_adjustment_; + + MetroDialogBox dialog_box_; +}; + +class ChromeAppViewFactory + : public mswr::RuntimeClass<winapp::Core::IFrameworkViewSource> { + public: + ChromeAppViewFactory(winapp::Core::ICoreApplication* icore_app, + LPTHREAD_START_ROUTINE host_main, + void* host_context); + IFACEMETHOD(CreateView)(winapp::Core::IFrameworkView** view); +}; + +// This function is exported by chrome.exe. +typedef int (__cdecl *BreakpadExceptionHandler)(EXCEPTION_POINTERS* info); + +// Global information used across the metro driver. +struct Globals { + LPTHREAD_START_ROUTINE host_main; + void* host_context; + HWND core_window; + // The pair below contains the HWND and a bool which indicates whether the + // window was displaced to ensure that the focused region is visible when + // the OSK is displayed. + std::list<std::pair<HWND, bool> > host_windows; + HANDLE host_thread; + DWORD main_thread_id; + ChromeAppView* view; + WNDPROC g_core_proc; + string16 navigation_url; + string16 search_string; + winapp::Activation::ApplicationExecutionState previous_state; + winapp::Activation::ActivationKind initial_activation_kind; + bool is_initial_activation; + // This message loop lives in the app view's thread. Some operations have + // to be initiated from that thread, notably spawning file pickers. + base::MessageLoopProxy* appview_msg_loop; + winapp::Core::ICoreApplicationExit* app_exit; + BreakpadExceptionHandler breakpad_exception_handler; + string16 metro_command_line_switches; +}; + +extern Globals globals; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_CHROME_APP_VIEW_H_ diff --git a/win8/metro_driver/chrome_url_launch_handler.cc b/win8/metro_driver/chrome_url_launch_handler.cc new file mode 100644 index 0000000..866bccc --- /dev/null +++ b/win8/metro_driver/chrome_url_launch_handler.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "chrome_url_launch_handler.h" +#include "chrome_app_view.h" + +#include <assert.h> +#include <shellapi.h> +#include <shlobj.h> +#include <string> + +#include "base/string_tokenizer.h" + +#include "winrt_utils.h" + +typedef winfoundtn::ITypedEventHandler< + winapp::Search::SearchPane*, + winapp::Search::SearchPaneQuerySubmittedEventArgs*> QuerySubmittedHandler; + +ChromeUrlLaunchHandler::ChromeUrlLaunchHandler() { + globals.is_initial_activation = true; + globals.initial_activation_kind = winapp::Activation::ActivationKind_Launch; + DVLOG(1) << __FUNCTION__; +} + +// TODO(ananta) +// Remove this once we consolidate metro driver with chrome. +const wchar_t kMetroNavigationAndSearchMessage[] = + L"CHROME_METRO_NAV_SEARCH_REQUEST"; + +ChromeUrlLaunchHandler::~ChromeUrlLaunchHandler() { + DVLOG(1) << __FUNCTION__; + search_pane_->remove_QuerySubmitted(query_submitted_token_); +} + +HRESULT ChromeUrlLaunchHandler::Initialize() { + mswr::ComPtr<winapp::Search::ISearchPaneStatics> search_pane_statics; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_ApplicationModel_Search_SearchPane, + search_pane_statics.GetAddressOf()); + CheckHR(hr, "Failed to activate ISearchPaneStatics"); + + hr = search_pane_statics->GetForCurrentView(&search_pane_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get search pane for current view"; + return hr; + } + + hr = search_pane_->add_QuerySubmitted(mswr::Callback<QuerySubmittedHandler>( + this, + &ChromeUrlLaunchHandler::OnQuerySubmitted).Get(), + &query_submitted_token_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to register for Query Submitted event"; + return hr; + } + return hr; +} + +HRESULT ChromeUrlLaunchHandler::OnQuerySubmitted( + winapp::Search::ISearchPane* search_pane, + winapp::Search::ISearchPaneQuerySubmittedEventArgs* args) { + DVLOG(1) << "OnQuerySubmitted"; + HandleSearchRequest(args); + return S_OK; +} + +template<class T> +void ChromeUrlLaunchHandler::HandleSearchRequest(T* args) { + DVLOG(1) << __FUNCTION__; + mswrw::HString search_string; + args->get_QueryText(search_string.GetAddressOf()); + string16 search_text(MakeStdWString(search_string.Get())); + globals.search_string = search_text; + DVLOG(1) << search_text.c_str(); + // If this is the initial activation then we wait for Chrome to initiate the + // navigation. In all other cases navigate right away. + if (!globals.is_initial_activation) + InitiateNavigationOrSearchRequest(NULL, globals.search_string.c_str()); +} + +void ChromeUrlLaunchHandler::HandleProtocolLaunch( + winapp::Activation::IProtocolActivatedEventArgs* args) { + DVLOG(1) << __FUNCTION__; + mswr::ComPtr<winfoundtn::IUriRuntimeClass> uri; + args->get_Uri(&uri); + mswrw::HString url; + uri->get_AbsoluteUri(url.GetAddressOf()); + string16 actual_url(MakeStdWString(url.Get())); + globals.navigation_url = actual_url; + + // If this is the initial activation then we wait for Chrome to initiate the + // navigation. In all other cases navigate right away. + if (!globals.is_initial_activation) + InitiateNavigationOrSearchRequest(globals.navigation_url.c_str(), 0); +} + +// The LaunchArgs are in a semi-color separated key_name=key_value list. At +// the moment the only key_name understaood is "url". +string16 ChromeUrlLaunchHandler::GetUrlFromLaunchArgs( + const string16& launch_args) { + WStringTokenizer tokenizer(launch_args, L";="); + bool next_is_url = false; + while (tokenizer.GetNext()) { + if (next_is_url) + return tokenizer.token(); + if (tokenizer.token() == L"url") + next_is_url = true; + } + if (launch_args == L"opennewwindow") { + DVLOG(1) << "Returning new tab url"; + return L"chrome://newtab"; + } + return string16(); +} + +void ChromeUrlLaunchHandler::HandleLaunch( + winapp::Activation::ILaunchActivatedEventArgs* args) { + mswrw::HString launch_args; + args->get_Arguments(launch_args.GetAddressOf()); + string16 actual_launch_args(MakeStdWString(launch_args.Get())); + globals.navigation_url = GetUrlFromLaunchArgs(actual_launch_args); + DVLOG(1) << __FUNCTION__ << ", launch_args=" << actual_launch_args + << ", url=" << globals.navigation_url + << ", is_initial_activation=" << globals.is_initial_activation; + + // If this is the initial launch then we wait for Chrome to initiate the + // navigation. In all other cases navigate right away. + if (!globals.is_initial_activation) + InitiateNavigationOrSearchRequest(globals.navigation_url.c_str(), 0); +} + +void ChromeUrlLaunchHandler::Activate( + winapp::Activation::IActivatedEventArgs* args) { + winapp::Activation::ActivationKind activation_kind; + CheckHR(args->get_Kind(&activation_kind)); + + DVLOG(1) << __FUNCTION__ << ", activation_kind=" << activation_kind; + + if (globals.is_initial_activation) + globals.initial_activation_kind = activation_kind; + + if (activation_kind == winapp::Activation::ActivationKind_Launch) { + mswr::ComPtr<winapp::Activation::ILaunchActivatedEventArgs> launch_args; + if (args->QueryInterface(winapp::Activation::IID_ILaunchActivatedEventArgs, + &launch_args) == S_OK) { + DVLOG(1) << "Activate: ActivationKind_Launch"; + HandleLaunch(launch_args.Get()); + } + } else if (activation_kind == + winapp::Activation::ActivationKind_Search) { + mswr::ComPtr<winapp::Activation::ISearchActivatedEventArgs> search_args; + if (args->QueryInterface(winapp::Activation::IID_ISearchActivatedEventArgs, + &search_args) == S_OK) { + DVLOG(1) << "Activate: ActivationKind_Search"; + HandleSearchRequest(search_args.Get()); + } + } else if (activation_kind == + winapp::Activation::ActivationKind_Protocol) { + mswr::ComPtr<winapp::Activation::IProtocolActivatedEventArgs> + protocol_args; + if (args->QueryInterface( + winapp::Activation::IID_IProtocolActivatedEventArgs, + &protocol_args) == S_OK) { + DVLOG(1) << "Activate: ActivationKind_Protocol"; + HandleProtocolLaunch(protocol_args.Get()); + } + } else { + DVLOG(1) << "Activate: Unhandled mode: " << activation_kind; + } +} + +void ChromeUrlLaunchHandler::InitiateNavigationOrSearchRequest( + const wchar_t* url, const wchar_t* search_string) { + DVLOG(1) << __FUNCTION__; + if (!url && !search_string) { + NOTREACHED(); + return; + } + + DVLOG(1) << (url ? url : L"NULL url"); + DVLOG(1) << (search_string ? search_string : L"NULL search string"); + + // Custom registered message to navigate or search in chrome. WPARAM + // points to the URL and LPARAM contains the search string. They are + // mutually exclusive. + static const UINT navigation_search_message = + RegisterWindowMessage(kMetroNavigationAndSearchMessage); + + if (url) { + VLOG(1) << "Posting url:" << url; + PostMessage(globals.host_windows.front().first, navigation_search_message, + reinterpret_cast<WPARAM>(url), 0); + } else { + VLOG(1) << "Posting search string:" << search_string; + PostMessage(globals.host_windows.front().first, navigation_search_message, + 0, reinterpret_cast<LPARAM>(search_string)); + } +} diff --git a/win8/metro_driver/chrome_url_launch_handler.h b/win8/metro_driver/chrome_url_launch_handler.h new file mode 100644 index 0000000..d8c7ed0 --- /dev/null +++ b/win8/metro_driver/chrome_url_launch_handler.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_CHROME_URL_LAUNCH_HANDLER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_CHROME_URL_LAUNCH_HANDLER_H_ + +#include <string> +#include <windows.applicationmodel.core.h> +#include <Windows.applicationModel.search.h> +#include <windows.ui.core.h> + +#include "winrt_utils.h" + +// This class handles the various flavors of URL launches in metro, i.e. +// via the search charm, via a url being navigated from a metro app, etc. +class ChromeUrlLaunchHandler { + public: + ChromeUrlLaunchHandler(); + ~ChromeUrlLaunchHandler(); + + HRESULT Initialize(); + + // If metro chrome was launched due to a URL navigation/search request then + // the navigation should be done when the frame window is initialized. This + // function is called to complete the pending navigation when we receive a + // notification from chrome that the frame window is initialized. + void PerformPendingNavigation(); + + void Activate(winapp::Activation::IActivatedEventArgs* args); + + private: + // Invoked when we receive search notifications in metro chrome. + template<class T> void HandleSearchRequest(T* args); + + HRESULT OnQuerySubmitted( + winapp::Search::ISearchPane* search_pane, + winapp::Search::ISearchPaneQuerySubmittedEventArgs* args); + + string16 GetUrlFromLaunchArgs(const string16& launch_args); + + // Invoked when a url is navigated from a metro app or in the metro + // shelf. + void HandleProtocolLaunch( + winapp::Activation::IProtocolActivatedEventArgs* args); + + // Invoked when the app is launched normally + void HandleLaunch(winapp::Activation::ILaunchActivatedEventArgs* args); + + // Helper function to initiate a navigation or search request in chrome. + void InitiateNavigationOrSearchRequest(const wchar_t* url, + const wchar_t* search_string); + + Microsoft::WRL::ComPtr<winapp::Search::ISearchPane> search_pane_; + EventRegistrationToken query_submitted_token_; +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_CHROME_URL_LAUNCH_HANDLER_H_ diff --git a/win8/metro_driver/devices_handler.cc b/win8/metro_driver/devices_handler.cc new file mode 100644 index 0000000..20fd413 --- /dev/null +++ b/win8/metro_driver/devices_handler.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "win8/metro_driver/devices_handler.h" + +#include "base/logging.h" + +namespace metro_driver { + +DevicesHandler::DevicesHandler() { +} + +DevicesHandler::~DevicesHandler() { +} + +HRESULT DevicesHandler::Initialize(winui::Core::ICoreWindow* window) { + HRESULT hr = print_handler_.Initialize(window); + return hr; +} + +} // namespace metro_driver diff --git a/win8/metro_driver/devices_handler.h b/win8/metro_driver/devices_handler.h new file mode 100644 index 0000000..fdb2226 --- /dev/null +++ b/win8/metro_driver/devices_handler.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_DEVICES_HANDLER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_DEVICES_HANDLER_H_ + +#include <windows.ui.core.h> + +#include "base/basictypes.h" +#include "win8/metro_driver/print_handler.h" + +namespace metro_driver { + +// This class handles the devices charm. +class DevicesHandler { + public: + DevicesHandler(); + ~DevicesHandler(); + + HRESULT Initialize(winui::Core::ICoreWindow* window); + + private: + PrintHandler print_handler_; + + DISALLOW_COPY_AND_ASSIGN(DevicesHandler); +}; + +} // namespace metro_driver + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_DEVICES_HANDLER_H_ diff --git a/win8/metro_driver/file_picker.cc b/win8/metro_driver/file_picker.cc new file mode 100644 index 0000000..4977be6 --- /dev/null +++ b/win8/metro_driver/file_picker.cc @@ -0,0 +1,618 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "win8/metro_driver/file_picker.h" + +#include <windows.storage.pickers.h> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/scoped_comptr.h" +#include "base/win/metro.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace { + +namespace winstorage = ABI::Windows::Storage; +typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf; + +// TODO(siggi): Complete this implementation and move it to a common place. +class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> { + public: + ~StringVectorImpl() { + std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString); + } + + HRESULT RuntimeClassInitialize(const std::vector<string16>& list) { + for (size_t i = 0; i < list.size(); ++i) + strings_.push_back(MakeHString(list[i])); + + return S_OK; + } + + // IVector<HSTRING> implementation. + STDMETHOD(GetAt)(unsigned index, HSTRING* item) { + if (index >= strings_.size()) + return E_INVALIDARG; + + return ::WindowsDuplicateString(strings_[index], item); + } + STDMETHOD(get_Size)(unsigned *size) { + *size = strings_.size(); + return S_OK; + } + STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) { + return E_NOTIMPL; + } + STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) { + return E_NOTIMPL; + } + + // write methods + STDMETHOD(SetAt)(unsigned index, HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(InsertAt)(unsigned index, HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(RemoveAt)(unsigned index) { + return E_NOTIMPL; + } + STDMETHOD(Append)(HSTRING item) { + return E_NOTIMPL; + } + STDMETHOD(RemoveAtEnd)() { + return E_NOTIMPL; + } + STDMETHOD(Clear)() { + return E_NOTIMPL; + } + + private: + std::vector<HSTRING> strings_; +}; + +class FilePickerSessionBase { + public: + // Creates a file picker for open_file_name. + explicit FilePickerSessionBase(OPENFILENAME* open_file_name); + + // Runs the picker, returns true on success. + bool Run(); + + protected: + // Creates, configures and starts a file picker. + // If the HRESULT returned is a failure code the file picker has not started, + // so no callbacks should be expected. + virtual HRESULT StartFilePicker() = 0; + + // The parameters to our picker. + OPENFILENAME* open_file_name_; + // The event Run waits on. + base::WaitableEvent event_; + // True iff a file picker has successfully finished. + bool success_; + + private: + // Initiate a file picker, must be called on the metro dispatcher's thread. + void DoFilePicker(); + + DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase); +}; + +class OpenFilePickerSession : public FilePickerSessionBase { + public: + explicit OpenFilePickerSession(OPENFILENAME* open_file_name); + + private: + virtual HRESULT StartFilePicker() OVERRIDE; + + typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> + SingleFileAsyncOp; + typedef winfoundtn::Collections::IVectorView< + winstorage::StorageFile*> StorageFileVectorCollection; + typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*> + MultiFileAsyncOp; + + // Called asynchronously when a single file picker is done. + HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status); + + // Called asynchronously when a multi file picker is done. + HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status); + + // Composes a multi-file result string suitable for returning to a + // from a storage file collection. + static HRESULT ComposeMultiFileResult(StorageFileVectorCollection* files, + string16* result); + private: + DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession); +}; + +class SaveFilePickerSession : public FilePickerSessionBase { + public: + explicit SaveFilePickerSession(OPENFILENAME* open_file_name); + + private: + virtual HRESULT StartFilePicker() OVERRIDE; + + typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> + SaveFileAsyncOp; + + // Called asynchronously when the save file picker is done. + HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status); +}; + +FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name) + : open_file_name_(open_file_name), + event_(true, false), + success_(false) { +} + +bool FilePickerSessionBase::Run() { + DCHECK(globals.appview_msg_loop != NULL); + + // Post the picker request over to the metro thread. + bool posted = globals.appview_msg_loop->PostTask(FROM_HERE, + base::Bind(&FilePickerSessionBase::DoFilePicker, base::Unretained(this))); + if (!posted) + return false; + + // Wait for the file picker to complete. + event_.Wait(); + + return success_; +} + +void FilePickerSessionBase::DoFilePicker() { + // The file picker will fail if spawned from a snapped application, + // so let's attempt to unsnap first if we're in that state. + HRESULT hr = ChromeAppView::Unsnap(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr; + } + + if (SUCCEEDED(hr)) + hr = StartFilePicker(); + + if (FAILED(hr)) { + LOG(ERROR) << "Failed to start file picker, error 0x" + << std::hex << hr; + + event_.Signal(); + } +} + +OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name) + : FilePickerSessionBase(open_file_name) { +} + +HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<winstorage::IStorageFile> file; + HRESULT hr = async->GetResults(file.GetAddressOf()); + + if (file) { + mswr::ComPtr<winstorage::IStorageItem> storage_item; + if (SUCCEEDED(hr)) + hr = file.As(&storage_item); + + mswrw::HString file_path; + if (SUCCEEDED(hr)) + hr = storage_item->get_Path(file_path.GetAddressOf()); + + if (SUCCEEDED(hr)) { + size_t path_len = 0; + const wchar_t* path_str = + ::WindowsGetStringRawBuffer(file_path.Get(), &path_len); + + // If the selected file name is longer than the supplied buffer, + // we return false as per GetOpenFileName documentation. + if (path_len < open_file_name_->nMaxFile) { + base::wcslcpy(open_file_name_->lpstrFile, + path_str, + open_file_name_->nMaxFile); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<StorageFileVectorCollection> files; + HRESULT hr = async->GetResults(files.GetAddressOf()); + + if (files) { + string16 result; + if (SUCCEEDED(hr)) + hr = ComposeMultiFileResult(files.Get(), &result); + + if (SUCCEEDED(hr)) { + if (result.size() + 1 < open_file_name_->nMaxFile) { + // Because the result has embedded nulls, we must memcpy. + memcpy(open_file_name_->lpstrFile, + result.c_str(), + (result.size() + 1) * sizeof(result[0])); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL StorageFileVectorCollection"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +HRESULT OpenFilePickerSession::StartFilePicker() { + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + DCHECK(open_file_name_ != NULL); + + mswrw::HStringReference class_name( + RuntimeClass_Windows_Storage_Pickers_FileOpenPicker); + + // Create the file picker. + mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker; + HRESULT hr = ::Windows::Foundation::ActivateInstance( + class_name.Get(), picker.GetAddressOf()); + CheckHR(hr); + + // Set the file type filter + mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter; + hr = picker->get_FileTypeFilter(filter.GetAddressOf()); + if (FAILED(hr)) + return hr; + + if (open_file_name_->lpstrFilter == NULL) { + hr = filter->Append(mswrw::HStringReference(L"*").Get()); + if (FAILED(hr)) + return hr; + } else { + // The filter is a concatenation of zero terminated string pairs, + // where each pair is {description, extension}. The concatenation ends + // with a zero length string - e.g. a double zero terminator. + const wchar_t* walk = open_file_name_->lpstrFilter; + while (*walk != L'\0') { + // Walk past the description. + walk += wcslen(walk) + 1; + + // We should have an extension, but bail on malformed filters. + if (*walk == L'\0') + break; + + // There can be a single extension, or a list of semicolon-separated ones. + std::vector<string16> extensions_win32_style; + size_t extension_count = Tokenize(walk, L";", &extensions_win32_style); + DCHECK_EQ(extension_count, extensions_win32_style.size()); + + // Metro wants suffixes only, not patterns. + mswrw::HString extension; + std::vector<string16> extensions; + for (size_t i = 0; i < extensions_win32_style.size(); ++i) { + if (extensions_win32_style[i] == L"*.*") { + // The wildcard filter is "*" for Metro. The string "*.*" produces + // an "invalid parameter" error. + hr = extension.Set(L"*"); + } else { + // Metro wants suffixes only, not patterns. + string16 ext = FilePath(extensions_win32_style[i]).Extension(); + if ((ext.size() < 2) || + (ext.find_first_of(L"*?") != string16::npos)) { + continue; + } + hr = extension.Set(ext.c_str()); + } + if (SUCCEEDED(hr)) + hr = filter->Append(extension.Get()); + if (FAILED(hr)) + return hr; + } + + // Walk past the extension. + walk += wcslen(walk) + 1; + } + } + + // Spin up a single or multi picker as appropriate. + if (open_file_name_->Flags & OFN_ALLOWMULTISELECT) { + mswr::ComPtr<MultiFileAsyncOp> completion; + hr = picker->PickMultipleFilesAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + StorageFileVectorCollection*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &OpenFilePickerSession::MultiPickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; + } else { + mswr::ComPtr<SingleFileAsyncOp> completion; + hr = picker->PickSingleFileAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + winstorage::StorageFile*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &OpenFilePickerSession::SinglePickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; + } +} + +HRESULT OpenFilePickerSession::ComposeMultiFileResult( + StorageFileVectorCollection* files, string16* result) { + DCHECK(files != NULL); + DCHECK(result != NULL); + + // Empty the output string. + result->clear(); + + size_t num_files = 0; + HRESULT hr = files->get_Size(&num_files); + if (FAILED(hr)) + return hr; + + // Make sure we return an error on an empty collection. + if (num_files == 0) { + DLOG(ERROR) << "Empty collection on input."; + return E_UNEXPECTED; + } + + // This stores the base path that should be the parent of all the files. + FilePath base_path; + + // Iterate through the collection and append the file paths to the result. + for (size_t i = 0; i < num_files; ++i) { + mswr::ComPtr<winstorage::IStorageFile> file; + hr = files->GetAt(i, file.GetAddressOf()); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<winstorage::IStorageItem> storage_item; + hr = file.As(&storage_item); + if (FAILED(hr)) + return hr; + + mswrw::HString file_path_str; + hr = storage_item->get_Path(file_path_str.GetAddressOf()); + if (FAILED(hr)) + return hr; + + FilePath file_path(MakeStdWString(file_path_str.Get())); + if (base_path.empty()) { + DCHECK(result->empty()); + base_path = file_path.DirName(); + + // Append the path, including the terminating zero. + // We do this only for the first file. + result->append(base_path.value().c_str(), base_path.value().size() + 1); + } + DCHECK(!result->empty()); + DCHECK(!base_path.empty()); + DCHECK(base_path == file_path.DirName()); + + // Append the base name, including the terminating zero. + FilePath base_name = file_path.BaseName(); + result->append(base_name.value().c_str(), base_name.value().size() + 1); + } + + DCHECK(!result->empty()); + + return S_OK; +} + +SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name) + : FilePickerSessionBase(open_file_name) { +} + +HRESULT SaveFilePickerSession::StartFilePicker() { + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + DCHECK(open_file_name_ != NULL); + + mswrw::HStringReference class_name( + RuntimeClass_Windows_Storage_Pickers_FileSavePicker); + + // Create the file picker. + mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker; + HRESULT hr = ::Windows::Foundation::ActivateInstance( + class_name.Get(), picker.GetAddressOf()); + CheckHR(hr); + + typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*> + StringVectorMap; + mswr::ComPtr<StringVectorMap> choices; + hr = picker->get_FileTypeChoices(choices.GetAddressOf()); + if (FAILED(hr)) + return hr; + + if (open_file_name_->lpstrFilter) { + // The filter is a concatenation of zero terminated string pairs, + // where each pair is {description, extension list}. The concatenation ends + // with a zero length string - e.g. a double zero terminator. + const wchar_t* walk = open_file_name_->lpstrFilter; + while (*walk != L'\0') { + mswrw::HString description; + hr = description.Set(walk); + if (FAILED(hr)) + return hr; + + // Walk past the description. + walk += wcslen(walk) + 1; + + // We should have an extension, but bail on malformed filters. + if (*walk == L'\0') + break; + + // There can be a single extension, or a list of semicolon-separated ones. + std::vector<string16> extensions_win32_style; + size_t extension_count = Tokenize(walk, L";", &extensions_win32_style); + DCHECK_EQ(extension_count, extensions_win32_style.size()); + + // Metro wants suffixes only, not patterns. Also, metro does not support + // the all files ("*") pattern in the save picker. + std::vector<string16> extensions; + for (size_t i = 0; i < extensions_win32_style.size(); ++i) { + string16 ext = FilePath(extensions_win32_style[i]).Extension(); + if ((ext.size() < 2) || + (ext.find_first_of(L"*?") != string16::npos)) + continue; + extensions.push_back(ext); + } + + if (!extensions.empty()) { + // Convert to a Metro collection class. + mswr::ComPtr<StringVectorItf> list; + hr = mswr::MakeAndInitialize<StringVectorImpl>( + list.GetAddressOf(), extensions); + if (FAILED(hr)) + return hr; + + // Finally set the filter. + boolean replaced = FALSE; + hr = choices->Insert(description.Get(), list.Get(), &replaced); + if (FAILED(hr)) + return hr; + DCHECK_EQ(FALSE, replaced); + } + + // Walk past the extension(s). + walk += wcslen(walk) + 1; + } + } + + // The save picker requires at least one choice. Callers are strongly advised + // to provide sensible choices. If none were given, fallback to .dat. + uint32 num_choices = 0; + hr = choices->get_Size(&num_choices); + if (FAILED(hr)) + return hr; + + if (num_choices == 0) { + mswrw::HString description; + // TODO(grt): Get a properly translated string. This can't be done from + // within metro_driver. Consider preprocessing the filter list in Chrome + // land to ensure it has this entry if all others are patterns. In that + // case, this whole block of code can be removed. + hr = description.Set(L"Data File"); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<StringVectorItf> list; + hr = mswr::MakeAndInitialize<StringVectorImpl>( + list.GetAddressOf(), std::vector<string16>(1, L".dat")); + if (FAILED(hr)) + return hr; + + boolean replaced = FALSE; + hr = choices->Insert(description.Get(), list.Get(), &replaced); + if (FAILED(hr)) + return hr; + DCHECK_EQ(FALSE, replaced); + } + + if (open_file_name_->lpstrFile != NULL) { + hr = picker->put_SuggestedFileName( + mswrw::HStringReference( + const_cast<const wchar_t*>(open_file_name_->lpstrFile)).Get()); + if (FAILED(hr)) + return hr; + } + + mswr::ComPtr<SaveFileAsyncOp> completion; + hr = picker->PickSaveFileAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + winstorage::StorageFile*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &SaveFilePickerSession::FilePickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + + return hr; +} + +HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<winstorage::IStorageFile> file; + HRESULT hr = async->GetResults(file.GetAddressOf()); + + if (file) { + mswr::ComPtr<winstorage::IStorageItem> storage_item; + if (SUCCEEDED(hr)) + hr = file.As(&storage_item); + + mswrw::HString file_path; + if (SUCCEEDED(hr)) + hr = storage_item->get_Path(file_path.GetAddressOf()); + + if (SUCCEEDED(hr)) { + string16 path_str = MakeStdWString(file_path.Get()); + + // If the selected file name is longer than the supplied buffer, + // we return false as per GetOpenFileName documentation. + if (path_str.size() < open_file_name_->nMaxFile) { + base::wcslcpy(open_file_name_->lpstrFile, + path_str.c_str(), + open_file_name_->nMaxFile); + success_ = true; + } + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << status; + } + + event_.Signal(); + + return S_OK; +} + +} // namespace + +BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) { + OpenFilePickerSession session(open_file_name); + + return session.Run(); +} + +BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) { + SaveFilePickerSession session(open_file_name); + + return session.Run(); +} diff --git a/win8/metro_driver/file_picker.h b/win8/metro_driver/file_picker.h new file mode 100644 index 0000000..ef56cb3 --- /dev/null +++ b/win8/metro_driver/file_picker.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_FILE_PICKER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_FILE_PICKER_H_ + +#include <commdlg.h> + +// This function behaves similarly to GetOpenFileName, except it uses a +// Metro file picker to pick a single or multiple file names. +extern "C" __declspec(dllexport) +BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name); + +extern "C" __declspec(dllexport) +BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name); + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_FILE_PICKER_H_ + diff --git a/win8/metro_driver/metro_dialog_box.cc b/win8/metro_driver/metro_dialog_box.cc new file mode 100644 index 0000000..25e7082 --- /dev/null +++ b/win8/metro_driver/metro_dialog_box.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2012 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 "win8/metro_driver/stdafx.h" + +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/metro_dialog_box.h" +#include "win8/metro_driver/winrt_utils.h" + +typedef winfoundtn::Collections:: + IVector<ABI::Windows::UI::Popups::IUICommand*> WindowsUICommands; + +typedef winfoundtn::IAsyncOperation<ABI::Windows::UI::Popups::IUICommand*> + AsyncCommandStatus; + +MetroDialogBox::MetroDialogBox() { + DVLOG(1) << __FUNCTION__; + dialog_box_info_.button1_handler = NULL; + dialog_box_info_.button2_handler = NULL; +} + +MetroDialogBox::~MetroDialogBox() { + DVLOG(1) << __FUNCTION__; +} + +void MetroDialogBox::Show( + const DialogBoxInfo& dialog_box_info) { + DVLOG(1) << __FUNCTION__; + + // Only one dialog can be displayed at a given time. + DCHECK(dialog_box_.Get() == NULL); + + // The message dialog display does not work correctly in snapped mode. + mswr::ComPtr<winui::Popups::IMessageDialogFactory> message_dialog_factory; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_Popups_MessageDialog, + message_dialog_factory.GetAddressOf()); + CheckHR(hr, "Failed to activate IMessageDialogFactory"); + + mswrw::HString message_title; + message_title.Attach(MakeHString(dialog_box_info.title)); + + mswrw::HString message_content; + message_content.Attach(MakeHString(dialog_box_info.content)); + + hr = message_dialog_factory->CreateWithTitle( + message_content.Get(), + message_title.Get(), + dialog_box_.GetAddressOf()); + CheckHR(hr, "Failed to create message dialog"); + + mswr::ComPtr<WindowsUICommands> commands; + hr = dialog_box_->get_Commands(commands.GetAddressOf()); + CheckHR(hr, "Failed to create ui command collection"); + + mswr::ComPtr<winui::Popups::IUICommandFactory> ui_command_factory; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_Popups_UICommand, + ui_command_factory.GetAddressOf()); + CheckHR(hr, "Failed to activate IUICommandFactory"); + + mswrw::HString label1; + label1.Attach(MakeHString(dialog_box_info.button1_label)); + + mswr::ComPtr<winui::Popups::IUICommand> label1_command; + hr = ui_command_factory->CreateWithHandler( + label1.Get(), this, label1_command.GetAddressOf()); + CheckHR(hr, "Failed to add button1"); + + mswrw::HString label2; + label2.Attach(MakeHString(dialog_box_info.button2_label)); + + mswr::ComPtr<winui::Popups::IUICommand> label2_command; + hr = ui_command_factory->CreateWithHandler(label2.Get(), this, + label2_command.GetAddressOf()); + CheckHR(hr, "Failed to add button2"); + + commands->Append(label1_command.Get()); + commands->Append(label2_command.Get()); + + mswr::ComPtr<AsyncCommandStatus> ret; + hr = dialog_box_->ShowAsync(ret.GetAddressOf()); + CheckHR(hr, "Failed to show dialog"); + + dialog_box_info_ = dialog_box_info; +} + +// The dialog box displayed via the MessageDialog interface has the class name +// 'Shell_Dialog'. The dialog box is top level window. To find it we enumerate +// all top level windows and compare the class names. If we find a matching +// window class we compare its process id with ours and return the same. +BOOL CALLBACK DialogBoxFinder(HWND hwnd, LPARAM lparam) { + char classname[MAX_PATH] = {0}; + + if (::GetClassNameA(hwnd, classname, ARRAYSIZE(classname))) { + if (lstrcmpiA("Shell_Dialog", classname) == 0) { + if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) { + DVLOG(1) << "Found top most dialog box: " << classname; + DVLOG(1) << "HWND: " << hwnd; + DWORD window_pid = 0; + DWORD window_tid = GetWindowThreadProcessId(hwnd, &window_pid); + DVLOG(1) << "Window tid: " << window_tid; + DVLOG(1) << "Window pid: " << window_pid; + + if (window_pid == ::GetCurrentProcessId()) { + HWND* dialog_window = reinterpret_cast<HWND*>(lparam); + *dialog_window = hwnd; + return FALSE; + } + } + } + } + return TRUE; +} + +void MetroDialogBox::Dismiss() { + DVLOG(1) << __FUNCTION__; + if (!dialog_box_) + return; + + dialog_box_info_.button1_handler = NULL; + dialog_box_info_.button2_handler = NULL; + dialog_box_info_.button1_label.clear(); + dialog_box_info_.button2_label.clear(); + dialog_box_.Reset(); + + // We don't have a good way to dismiss the dialog box. Hack for now is to + // find the dialog box class in our process and close it via the WM_CLOSE + // message. + HWND dialog_box = NULL; + ::EnumWindows(&DialogBoxFinder, reinterpret_cast<LPARAM>(&dialog_box)); + if (::IsWindow(dialog_box)) + PostMessage(dialog_box, WM_CLOSE, 0, 0); +} + +HRESULT STDMETHODCALLTYPE MetroDialogBox::Invoke( + winui::Popups::IUICommand* command) { + DVLOG(1) << __FUNCTION__; + + mswrw::HString label; + command->get_Label(label.GetAddressOf()); + + string16 button_label = MakeStdWString(label.Get()); + DVLOG(1) << "Clicked button label is : " << button_label; + if (button_label == dialog_box_info_.button1_label) { + DVLOG(1) << "Button1 clicked"; + DCHECK(dialog_box_info_.button1_handler); + dialog_box_info_.button1_handler(); + } else if (button_label == dialog_box_info_.button2_label) { + DVLOG(1) << "Button2 clicked"; + DCHECK(dialog_box_info_.button2_handler); + dialog_box_info_.button2_handler(); + } + // The dialog box is destroyed once we return from invoke. Go ahead and + // dismiss it. + Dismiss(); + return S_OK; +} + diff --git a/win8/metro_driver/metro_dialog_box.h b/win8/metro_driver/metro_dialog_box.h new file mode 100644 index 0000000..5a6a94d --- /dev/null +++ b/win8/metro_driver/metro_dialog_box.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_METRO_DIALOG_BOX_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_METRO_DIALOG_BOX_H_ + +#include <windows.ui.popups.h> +#include <string> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/win/metro.h" + +// Provides functionality to display a dialog box +class MetroDialogBox : public winui::Popups::IUICommandInvokedHandler { + public: + struct DialogBoxInfo { + string16 title; + string16 content; + string16 button1_label; + string16 button2_label; + base::win::MetroDialogButtonPressedHandler button1_handler; + base::win::MetroDialogButtonPressedHandler button2_handler; + }; + + MetroDialogBox(); + ~MetroDialogBox(); + + // Displays the dialog box. + void Show(const DialogBoxInfo& dialog_box_info); + + // Dismisses the dialog box. + void Dismiss(); + + // IUICommandInvokedHandler implementation. + // Dummy implementation of IUnknown. This is fine as the lifetime of this + // class is tied to the lifetime of the ChromeAppView instance. + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** object) { + DVLOG(1) << __FUNCTION__; + CHECK(false); + return E_NOINTERFACE; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) { + DVLOG(1) << __FUNCTION__; + return 1; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) { + DVLOG(1) << __FUNCTION__; + return 1; + } + + virtual HRESULT STDMETHODCALLTYPE Invoke(winui::Popups::IUICommand* command); + + private: + // The actual dialog box. + mswr::ComPtr<winui::Popups::IMessageDialog> dialog_box_; + DialogBoxInfo dialog_box_info_; +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_METRO_DIALOG_BOX_H_ + diff --git a/win8/metro_driver/metro_driver.cc b/win8/metro_driver/metro_driver.cc new file mode 100644 index 0000000..89bd35a --- /dev/null +++ b/win8/metro_driver/metro_driver.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2012 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 "stdafx.h" + +#include <roerrorapi.h> +#include <shobjidl.h> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/logging_win.h" +#include "base/win/scoped_comptr.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" +#include "sandbox/win/src/sidestep/preamble_patcher.h" + +// TODO(siggi): Move this to GYP. +#pragma comment(lib, "runtimeobject.lib") + +namespace { + +LONG WINAPI ErrorReportingHandler(EXCEPTION_POINTERS* ex_info) { + // See roerrorapi.h for a description of the + // exception codes and parameters. + DWORD code = ex_info->ExceptionRecord->ExceptionCode; + ULONG_PTR* info = ex_info->ExceptionRecord->ExceptionInformation; + if (code == EXCEPTION_RO_ORIGINATEERROR) { + string16 msg(reinterpret_cast<wchar_t*>(info[2]), info[1]); + LOG(ERROR) << "VEH: Metro error 0x" << std::hex << info[0] << ": " << msg; + } else if (code == EXCEPTION_RO_TRANSFORMERROR) { + string16 msg(reinterpret_cast<wchar_t*>(info[3]), info[2]); + LOG(ERROR) << "VEH: Metro old error 0x" << std::hex << info[0] + << " new error 0x" << info[1] << ": " << msg; + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +// TODO(robertshield): This GUID is hard-coded in a bunch of places that +// don't allow explicit includes. Find a single place for it to live. +// {7FE69228-633E-4f06-80C1-527FEA23E3A7} +const GUID kChromeTraceProviderName = { + 0x7fe69228, 0x633e, 0x4f06, + { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } }; + +} +// Required for base initialization. +// TODO(siggi): This should be handled better, as this way our at exit +// registrations will run under the loader's lock. However, +// once metro_driver is merged into Chrome.dll, this will go away anyhow. +base::AtExitManager at_exit; + +namespace Hacks { + +typedef BOOL (WINAPI* IsImmersiveFunctionPtr)(HANDLE process); +char* g_real_is_immersive_proc_stub = NULL; + +HMODULE g_webrtc_quartz_dll_handle = NULL; +bool g_fake_is_immersive_process_ret = false; + +BOOL WINAPI MetroChromeIsImmersiveIntercept(HANDLE process) { + if (g_fake_is_immersive_process_ret && process == ::GetCurrentProcess()) + return FALSE; + + IsImmersiveFunctionPtr real_proc = + reinterpret_cast<IsImmersiveFunctionPtr>( + static_cast<char*>(g_real_is_immersive_proc_stub)); + return real_proc(process); +} + +void MetroSpecificHacksInitialize() { + // The quartz dll which is used by the webrtc code in Chrome fails in a metro + // app. It checks this via the IsImmersiveProcess export in user32. We + // intercept the same and spoof the value as false. This is ok as technically + // a metro browser is not a real metro application. The webrtc functionality + // works fine with this hack. + // TODO(tommi) + // BUG:- https://code.google.com/p/chromium/issues/detail?id=140545 + // We should look into using media foundation on windows 8 in metro chrome. + IsImmersiveFunctionPtr is_immersive_func_address = + reinterpret_cast<IsImmersiveFunctionPtr>(::GetProcAddress( + ::GetModuleHandle(L"user32.dll"), "IsImmersiveProcess")); + DCHECK(is_immersive_func_address); + + // Allow the function to be patched by changing the protections on the page. + DWORD old_protect = 0; + ::VirtualProtect(is_immersive_func_address, 5, PAGE_EXECUTE_READWRITE, + &old_protect); + + DCHECK(g_real_is_immersive_proc_stub == NULL); + g_real_is_immersive_proc_stub = reinterpret_cast<char*>(VirtualAllocEx( + ::GetCurrentProcess(), NULL, sidestep::kMaxPreambleStubSize, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + DCHECK(g_real_is_immersive_proc_stub); + + sidestep::SideStepError patch_result = + sidestep::PreamblePatcher::Patch( + is_immersive_func_address, MetroChromeIsImmersiveIntercept, + g_real_is_immersive_proc_stub, sidestep::kMaxPreambleStubSize); + + DCHECK(patch_result == sidestep::SIDESTEP_SUCCESS); + + // Restore the permissions on the page in user32 containing the + // IsImmersiveProcess function code. + DWORD dummy = 0; + ::VirtualProtect(is_immersive_func_address, 5, old_protect, &dummy); + + // Mimic the original page permissions from the IsImmersiveProcess page + // on our stub. + ::VirtualProtect(g_real_is_immersive_proc_stub, + sidestep::kMaxPreambleStubSize, + old_protect, + &old_protect); + + g_fake_is_immersive_process_ret = true; + g_webrtc_quartz_dll_handle = LoadLibrary(L"quartz.dll"); + g_fake_is_immersive_process_ret = false; + + DCHECK(g_webrtc_quartz_dll_handle); + if (!g_webrtc_quartz_dll_handle) { + DVLOG(1) << "Quartz dll load failed with error: " << GetLastError(); + } else { + // Pin the quartz module to protect against it being inadvarently unloaded. + ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN, L"quartz.dll", + &g_webrtc_quartz_dll_handle); + } +} + +} // namespace Hacks + +extern "C" __declspec(dllexport) +int InitMetro(LPTHREAD_START_ROUTINE thread_proc, void* context) { + // Initialize the command line. + CommandLine::Init(0, NULL); + logging::InitLogging( + NULL, + logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE, + logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + +#if defined(NDEBUG) + logging::SetMinLogLevel(logging::LOG_ERROR); + // Bind to the breakpad handling function. + globals.breakpad_exception_handler = + reinterpret_cast<BreakpadExceptionHandler>( + ::GetProcAddress(::GetModuleHandle(NULL), + "CrashForException")); + if (!globals.breakpad_exception_handler) { + DVLOG(0) << "CrashForException export not found"; + } +#else + logging::SetMinLogLevel(logging::LOG_VERBOSE); + // Set the error reporting flags to always raise an exception, + // which is then processed by our vectored exception handling + // above to log the error message. + winfoundtn::Diagnostics::SetErrorReportingFlags( + winfoundtn::Diagnostics::UseSetErrorInfo | + winfoundtn::Diagnostics::ForceExceptions); + + HANDLE registration = + ::AddVectoredExceptionHandler(TRUE, ErrorReportingHandler); +#endif + + // Enable trace control and transport through event tracing for Windows. + logging::LogEventProvider::Initialize(kChromeTraceProviderName); + + DVLOG(1) << "InitMetro"; + + mswrw::RoInitializeWrapper ro_initializer(RO_INIT_MULTITHREADED); + CheckHR(ro_initializer, "RoInitialize failed"); + + mswr::ComPtr<winapp::Core::ICoreApplication> core_app; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_ApplicationModel_Core_CoreApplication, + core_app.GetAddressOf()); + CheckHR(hr, "Failed to create app factory"); + if (FAILED(hr)) + return 1; + + // The metro specific hacks code assumes that there is only one thread active + // at the moment. This better be the case or we may have race conditions. + Hacks::MetroSpecificHacksInitialize(); + + auto view_factory = mswr::Make<ChromeAppViewFactory>( + core_app.Get(), thread_proc, context); + hr = core_app->Run(view_factory.Get()); + DVLOG(1) << "exiting InitMetro, hr=" << hr; + +#if !defined(NDEBUG) + ::RemoveVectoredExceptionHandler(registration); +#endif + + return hr; +} + +// Activates the application known by |app_id|. Returns, among other things, +// E_APPLICATION_NOT_REGISTERED if |app_id| identifies Chrome and Chrome is not +// the default browser. +extern "C" __declspec(dllexport) +HRESULT ActivateApplication(const wchar_t* app_id) { + base::win::ScopedComPtr<IApplicationActivationManager> activator; + HRESULT hr = activator.CreateInstance(CLSID_ApplicationActivationManager); + if (SUCCEEDED(hr)) { + DWORD pid = 0; + hr = activator->ActivateApplication(app_id, L"", AO_NONE, &pid); + } + return hr; +} diff --git a/win8/metro_driver/metro_driver.gyp b/win8/metro_driver/metro_driver.gyp new file mode 100644 index 0000000..266c4b6 --- /dev/null +++ b/win8/metro_driver/metro_driver.gyp @@ -0,0 +1,98 @@ +# Copyright (c) 2012 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. +{ + 'conditions': [ + ['OS=="win" and (MSVS_VERSION=="2010" or MSVS_VERSION=="2010e")', { + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../../build/win_precompile.gypi', + ], + 'target_defaults': { + 'defines': [ + # This define is required to pull in the new Win8 interfaces from + # system headers like ShObjIdl.h + 'NTDDI_VERSION=0x06020000', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'AdditionalDependencies': [ + 'D2D1.lib', + 'D3D11.lib', + ], + }, + }, + }, + 'targets': [ + { + 'target_name': 'metro_driver', + 'type': 'shared_library', + 'dependencies': [ + '../../base/base.gyp:base', + '../../crypto/crypto.gyp:crypto', + '../../sandbox/sandbox.gyp:sandbox', + '../../google_update/google_update.gyp:google_update', + '../win8.gyp:check_sdk_patch', + ], + 'sources': [ + 'chrome_app_view.cc', + 'chrome_app_view.h', + 'chrome_url_launch_handler.cc', + 'chrome_url_launch_handler.h', + '../delegate_execute/chrome_util.cc', + '../win8/delegate_execute/chrome_util.h', + 'devices_handler.cc', + 'devices_handler.h', + 'file_picker.h', + 'file_picker.cc', + 'metro_dialog_box.cc', + 'metro_dialog_box.h', + 'metro_driver.cc', + 'print_handler.cc', + 'print_handler.h', + 'print_document_source.cc', + 'print_document_source.h', + 'secondary_tile.h', + 'secondary_tile.cc', + 'settings_handler.cc', + 'settings_handler.h', + 'stdafx.h', + 'toast_notification_handler.cc', + 'toast_notification_handler.h', + 'winrt_utils.cc', + 'winrt_utils.h', + ], + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)', + 'files': [ + 'resources/Logo.png', + 'resources/SecondaryTile.png', + 'resources/SmallLogo.png', + 'resources/splash-620x300.png', + 'resources/VisualElementsManifest.xml', + ], + }, + ], + }, + { + 'target_name': 'metro_driver_unittests', + 'type': 'executable', + 'dependencies': [ + '../../base/base.gyp:base', + '../../testing/gtest.gyp:gtest', + 'metro_driver', + ], + 'sources': [ + 'run_all_unittests.cc', + 'winrt_utils.cc', + 'winrt_utils.h', + 'winrt_utils_unittest.cc', + ], + }, + ], + },], + ], +} diff --git a/win8/metro_driver/metro_driver_win7.cc b/win8/metro_driver/metro_driver_win7.cc new file mode 100644 index 0000000..253e527 --- /dev/null +++ b/win8/metro_driver/metro_driver_win7.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 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 "stdafx.h" + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +struct Globals { + LPTHREAD_START_ROUTINE host_main; + void* host_context; + HWND core_window; + HWND host_window; + HANDLE host_thread; + DWORD main_thread_id; +} globals; + + +void ODS(const char* str, LONG_PTR val = 0) { + char buf[80]; + size_t len = strlen(str); + if (len > 50) { + ::OutputDebugStringA("ODS: buffer too long"); + return; + } + + if (str[0] == '!') { + // Fatal error. + DWORD gle = ::GetLastError(); + if (::IsDebuggerPresent()) + __debugbreak(); + wsprintfA(buf, "ODS:fatal %s (%p) gle=0x%x", str, val, gle); + ::MessageBoxA(NULL, buf, "!!!", MB_OK); + ::ExitProcess(gle); + } else { + // Just information. + wsprintfA(buf, "ODS:%s (%p)\n", str, val); + ::OutputDebugStringA(buf); + } +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + PAINTSTRUCT ps; + HDC hdc; + switch (message) { + case WM_PAINT: + hdc = BeginPaint(hwnd, &ps); + EndPaint(hwnd, &ps); + break; + case WM_DESTROY: + PostQuitMessage(0); + ODS("Metro WM_DESTROY received"); + break; + default: + return DefWindowProc(hwnd, message, wparam, lparam); + } + return 0; +} + +HWND CreateMetroTopLevelWindow() { + HINSTANCE hInst = reinterpret_cast<HINSTANCE>(&__ImageBase); + WNDCLASSEXW wcex; + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInst; + wcex.hIcon = 0; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_INACTIVECAPTION+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = L"Windows.UI.Core.CoreWindow"; + wcex.hIconSm = 0; + + HWND hwnd = ::CreateWindowExW(0, + MAKEINTATOM(::RegisterClassExW(&wcex)), + L"metro_metro", + WS_POPUP, + 0, 0, 0, 0, + NULL, NULL, hInst, NULL); + return hwnd; +} + +DWORD WINAPI HostThread(void*) { + // The sleeps simulates the delay we have in the actual metro code + // which takes in account the corewindow being created and some other + // unknown machinations of metro. + ODS("Chrome main thread", ::GetCurrentThreadId()); + ::Sleep(30); + return globals.host_main(globals.host_context); +} + +extern "C" __declspec(dllexport) +int InitMetro(LPTHREAD_START_ROUTINE thread_proc, void* context) { + ODS("InitMetro [Win7 emulation]"); + HWND window = CreateMetroTopLevelWindow(); + if (!window) + return 1; + // This magic incatation tells windows that the window is going fullscreen + // so the taskbar gets out of the wait automatically. + ::SetWindowPos(window, + HWND_TOP, + 0,0, + GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), + SWP_SHOWWINDOW); + + // Ready to start our caller. + globals.core_window = window; + globals.host_main = thread_proc; + globals.host_context = context; + HANDLE thread = ::CreateThread(NULL, 0, &HostThread, NULL, 0, NULL); + + // Main message loop. + MSG msg = {0}; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int) msg.wParam; +} + +extern "C" _declspec(dllexport) HWND GetRootWindow() { + ODS("GetRootWindow", ULONG_PTR(globals.core_window)); + return globals.core_window; +} + +extern "C" _declspec(dllexport) void SetFrameWindow(HWND window) { + ODS("SetFrameWindow", ULONG_PTR(window)); + globals.host_window = window; +} + +extern "C" __declspec(dllexport) const wchar_t* GetInitialUrl() { + return L""; +} + diff --git a/win8/metro_driver/print_document_source.cc b/win8/metro_driver/print_document_source.cc new file mode 100644 index 0000000..7469597 --- /dev/null +++ b/win8/metro_driver/print_document_source.cc @@ -0,0 +1,525 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "win8/metro_driver/print_document_source.h" + +#include <windows.graphics.display.h> + +#include "base/logging.h" + + +namespace { + +class D2DFactoryAutoLock { + public: + explicit D2DFactoryAutoLock(ID2D1Factory* d2d_factory) { + HRESULT hr = d2d_factory->QueryInterface(IID_PPV_ARGS(&d2d_multithread_)); + if (d2d_multithread_.Get()) + d2d_multithread_->Enter(); + else + NOTREACHED() << "Failed to QI for ID2D1Multithread " << std::hex << hr; + } + + ~D2DFactoryAutoLock() { + if (d2d_multithread_.Get()) + d2d_multithread_->Leave(); + } + + private: + mswr::ComPtr<ID2D1Multithread> d2d_multithread_; +}; + +// TODO(mad): remove once we don't run mixed SDK/OS anymore. +const GUID kOldPackageTargetGuid = + {0xfb2a33c0, 0x8c35, 0x465f, + {0xbe, 0xd5, 0x9f, 0x36, 0x89, 0x51, 0x77, 0x52}}; +const GUID kNewPackageTargetGuid = + {0x1a6dd0ad, 0x1e2a, 0x4e99, + {0xa5, 0xba, 0x91, 0xf1, 0x78, 0x18, 0x29, 0x0e}}; + + +} // namespace + +namespace metro_driver { + +PrintDocumentSource::PrintDocumentSource() + : page_count_ready_(true, false), + parent_lock_(NULL), + height_(0), + width_(0), + dpi_(96), + aborted_(false), + using_old_preview_interface_(false) { +} + +HRESULT PrintDocumentSource::RuntimeClassInitialize( + const DirectXContext& directx_context, + base::Lock* parent_lock) { + DCHECK(parent_lock != NULL); + DCHECK(directx_context.d2d_context.Get() != NULL); + DCHECK(directx_context.d2d_device.Get() != NULL); + DCHECK(directx_context.d2d_factory.Get() != NULL); + DCHECK(directx_context.d3d_device.Get() != NULL); + DCHECK(directx_context.wic_factory.Get() != NULL); + directx_context_ = directx_context; + + // No other method can be called before RuntimeClassInitialize which is called + // during the construction via mswr::MakeAndInitialize(), so it's safe for all + // other methods to use the parent_lock_ without checking if it's NULL. + DCHECK(parent_lock_ == NULL); + parent_lock_ = parent_lock; + + return S_OK; +} + +void PrintDocumentSource::Abort() { + base::AutoLock lock(*parent_lock_); + aborted_ = true; + if (page_count_ready_.IsSignaled()) { + pages_.clear(); + for (size_t i = 0; i < pages_ready_state_.size(); ++i) + pages_ready_state_[i]->Broadcast(); + } else { + DCHECK(pages_.empty() && pages_ready_state_.empty()); + } +} + +STDMETHODIMP PrintDocumentSource::GetPreviewPageCollection( + IPrintDocumentPackageTarget* package_target, + IPrintPreviewPageCollection** page_collection) { + DVLOG(1) << __FUNCTION__; + DCHECK(package_target != NULL); + DCHECK(page_collection != NULL); + + HRESULT hr = package_target->GetPackageTarget( + __uuidof(IPrintPreviewDxgiPackageTarget), + IID_PPV_ARGS(&dxgi_preview_target_)); + if (FAILED(hr)) { + // TODO(mad): remove once we don't run mixed SDK/OS anymore. + // The IID changed from one version of the SDK to another, so try the other + // one in case we are running a build from a different SDK than the one + // related to the OS version we are running. + GUID package_target_uuid = kNewPackageTargetGuid; + if (package_target_uuid == __uuidof(IPrintPreviewDxgiPackageTarget)) { + package_target_uuid = kOldPackageTargetGuid; + using_old_preview_interface_ = true; + } + hr = package_target->GetPackageTarget(package_target_uuid, + package_target_uuid, + &dxgi_preview_target_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get IPrintPreviewDXGIPackageTarget " << std::hex + << hr; + return hr; + } + } else { + using_old_preview_interface_ = (__uuidof(IPrintPreviewDxgiPackageTarget) == + kOldPackageTargetGuid); + } + + mswr::ComPtr<IPrintPreviewPageCollection> preview_page_collection; + mswr::ComPtr<PrintDocumentSource> print_document_source(this); + hr = print_document_source.As(&preview_page_collection); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get preview_page_collection " << std::hex << hr; + return hr; + } + + hr = preview_page_collection.CopyTo(page_collection); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to copy preview_page_collection " << std::hex << hr; + return hr; + } + return hr; +} + +STDMETHODIMP PrintDocumentSource::MakeDocument( + IInspectable* options, + IPrintDocumentPackageTarget* package_target) { + DVLOG(1) << __FUNCTION__; + DCHECK(options != NULL); + DCHECK(package_target != NULL); + + mswr::ComPtr<wingfx::Printing::IPrintTaskOptionsCore> print_task_options; + HRESULT hr = options->QueryInterface( + wingfx::Printing::IID_IPrintTaskOptionsCore, + reinterpret_cast<void**>(print_task_options.GetAddressOf())); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI for IPrintTaskOptionsCore " << std::hex << hr; + return hr; + } + + // Use the first page's description for the whole document. Page numbers + // are 1-based in this context. + // TODO(mad): Check if it would be useful to use per page descriptions. + wingfx::Printing::PrintPageDescription page_desc = {}; + hr = print_task_options->GetPageDescription(1 /* page */, &page_desc); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to GetPageDescription " << std::hex << hr; + return hr; + } + + D2D1_PRINT_CONTROL_PROPERTIES print_control_properties; + if (page_desc.DpiX > page_desc.DpiY) + print_control_properties.rasterDPI = static_cast<float>(page_desc.DpiY); + else + print_control_properties.rasterDPI = static_cast<float>(page_desc.DpiX); + + // Color space for vector graphics in D2D print control. + print_control_properties.colorSpace = D2D1_COLOR_SPACE_SRGB; + print_control_properties.fontSubset = D2D1_PRINT_FONT_SUBSET_MODE_DEFAULT; + + mswr::ComPtr<ID2D1PrintControl> print_control; + hr = directx_context_.d2d_device->CreatePrintControl( + directx_context_.wic_factory.Get(), + package_target, + print_control_properties, + print_control.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CreatePrintControl " << std::hex << hr; + return hr; + } + + D2D1_SIZE_F page_size = D2D1::SizeF(page_desc.PageSize.Width, + page_desc.PageSize.Height); + + // Wait for the number of pages to be available. + // If an abort occured, we'll get 0 and won't enter the loop below. + size_t page_count = WaitAndGetPageCount(); + + mswr::ComPtr<ID2D1GdiMetafile> gdi_metafile; + for (size_t page = 0; page < page_count; ++page) { + gdi_metafile.Reset(); + hr = WaitAndGetPage(page, gdi_metafile.GetAddressOf()); + LOG_IF(ERROR, FAILED(hr)) << "Failed to get page's metafile " << std::hex + << hr; + // S_FALSE means we got aborted. + if (hr == S_FALSE || FAILED(hr)) + break; + hr = PrintPage(print_control.Get(), gdi_metafile.Get(), page_size); + if (FAILED(hr)) + break; + } + + HRESULT close_hr = print_control->Close(); + if (FAILED(close_hr) && SUCCEEDED(hr)) + return close_hr; + else + return hr; +} + +STDMETHODIMP PrintDocumentSource::Paginate(uint32 page, + IInspectable* options) { + DVLOG(1) << __FUNCTION__ << ", page = " << page; + DCHECK(options != NULL); + // GetPreviewPageCollection must have been successfuly called. + DCHECK(dxgi_preview_target_.Get() != NULL); + + // Get print settings from PrintTaskOptions for preview, such as page + // description, which contains page size, imageable area, DPI. + // TODO(mad): obtain other print settings in the same way, such as ColorMode, + // NumberOfCopies, etc... + mswr::ComPtr<wingfx::Printing::IPrintTaskOptionsCore> print_options; + HRESULT hr = options->QueryInterface( + wingfx::Printing::IID_IPrintTaskOptionsCore, + reinterpret_cast<void**>(print_options.GetAddressOf())); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI for IPrintTaskOptionsCore " << std::hex << hr; + return hr; + } + + wingfx::Printing::PrintPageDescription page_desc = {}; + hr = print_options->GetPageDescription(1 /* page */, &page_desc); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to GetPageDescription " << std::hex << hr; + return hr; + } + + width_ = page_desc.PageSize.Width; + height_ = page_desc.PageSize.Height; + + hr = dxgi_preview_target_->InvalidatePreview(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to InvalidatePreview " << std::hex << hr; + return hr; + } + + size_t page_count = WaitAndGetPageCount(); + // A page_count of 0 means abort... + if (page_count == 0) + return S_FALSE; + hr = dxgi_preview_target_->SetJobPageCount(PageCountType::FinalPageCount, + page_count); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to SetJobPageCount " << std::hex << hr; + return hr; + } + return hr; +} + +STDMETHODIMP PrintDocumentSource::MakePage(uint32 job_page, + float width, + float height) { + DVLOG(1) << __FUNCTION__ << ", width: " << width << ", height: " << height + << ", job_page: " << job_page; + DCHECK(width > 0 && height > 0); + // Paginate must have been called before this. + if (width_ <= 0.0 || height_ <= 0.0) + return S_FALSE; + + // When job_page is JOB_PAGE_APPLICATION_DEFINED, it means a new preview + // begins. TODO(mad): Double check if we need to cancel pending resources. + if (job_page == JOB_PAGE_APPLICATION_DEFINED) + job_page = 1; + + winfoundtn::Size preview_size; + preview_size.Width = width; + preview_size.Height = height; + float scale = width_ / width; + + mswr::ComPtr<ID2D1Factory> factory; + directx_context_.d2d_device->GetFactory(&factory); + + mswr::ComPtr<ID2D1GdiMetafile> gdi_metafile; + HRESULT hr = WaitAndGetPage(job_page - 1, gdi_metafile.GetAddressOf()); + LOG_IF(ERROR, FAILED(hr)) << "Failed to get page's metafile " << std::hex + << hr; + // Again, S_FALSE means we got aborted. + if (hr == S_FALSE || FAILED(hr)) + return hr; + + // We are accessing D3D resources directly without D2D's knowledge, so we + // must manually acquire the D2D factory lock. + D2DFactoryAutoLock factory_lock(directx_context_.d2d_factory.Get()); + + CD3D11_TEXTURE2D_DESC texture_desc( + DXGI_FORMAT_B8G8R8A8_UNORM, + static_cast<UINT32>(ceil(width * dpi_ / 96)), + static_cast<UINT32>(ceil(height * dpi_ / 96)), + 1, + 1, + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE + ); + mswr::ComPtr<ID3D11Texture2D> texture; + hr = directx_context_.d3d_device->CreateTexture2D( + &texture_desc, NULL, &texture); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create a 2D texture " << std::hex << hr; + return hr; + } + + mswr::ComPtr<IDXGISurface> dxgi_surface; + hr = texture.As<IDXGISurface>(&dxgi_surface); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI for IDXGISurface " << std::hex << hr; + return hr; + } + + // D2D device contexts are stateful, and hence a unique device context must + // be used on each call. + mswr::ComPtr<ID2D1DeviceContext> d2d_context; + hr = directx_context_.d2d_device->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context); + + d2d_context->SetDpi(dpi_, dpi_); + + mswr::ComPtr<ID2D1Bitmap1> d2dSurfaceBitmap; + hr = d2d_context->CreateBitmapFromDxgiSurface(dxgi_surface.Get(), + NULL, // default properties. + &d2dSurfaceBitmap); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CreateBitmapFromDxgiSurface " << std::hex << hr; + return hr; + } + + d2d_context->SetTarget(d2dSurfaceBitmap.Get()); + d2d_context->BeginDraw(); + d2d_context->Clear(); + d2d_context->SetTransform(D2D1::Matrix3x2F(1/scale, 0, 0, 1/scale, 0, 0)); + d2d_context->DrawGdiMetafile(gdi_metafile.Get()); + + hr = d2d_context->EndDraw(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to EndDraw " << std::hex << hr; + return hr; + } + +// TODO(mad): remove once we don't run mixed SDK/OS anymore. +#ifdef __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__ + FLOAT dpi = dpi_; + if (using_old_preview_interface_) { + // We compiled with the new API but run on the old OS, so we must cheat + // and send something that looks like a float but has a UINT32 value. + *reinterpret_cast<UINT32*>(&dpi) = static_cast<UINT32>(dpi_); + } +#else + UINT32 dpi = static_cast<UINT32>(dpi_); + if (!using_old_preview_interface_) { + // We compiled with the old API but run on the new OS, so we must cheat + // and send something that looks like a UINT32 but has a float value. + *reinterpret_cast<FLOAT*>(&dpi) = dpi_; + } +#endif // __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__ + hr = dxgi_preview_target_->DrawPage(job_page, dxgi_surface.Get(), dpi, dpi); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to DrawPage " << std::hex << hr; + return hr; + } + return hr; +} + +void PrintDocumentSource::ResetDpi(float dpi) { + { + base::AutoLock lock(*parent_lock_); + if (dpi == dpi_) + return; + dpi_ = dpi; + } + directx_context_.d2d_context->SetDpi(dpi, dpi); +} + +void PrintDocumentSource::SetPageCount(size_t page_count) { + DCHECK(page_count > 0); + { + base::AutoLock lock(*parent_lock_); + DCHECK(!page_count_ready_.IsSignaled()); + DCHECK(pages_.empty() && pages_ready_state_.empty()); + + pages_.resize(page_count); + pages_ready_state_.resize(page_count); + + for (size_t i = 0; i < page_count; ++i) + pages_ready_state_[i].reset(new base::ConditionVariable(parent_lock_)); + } + page_count_ready_.Signal(); +} + +void PrintDocumentSource::AddPage(size_t page_number, + IStream* metafile_stream) { + DCHECK(metafile_stream != NULL); + base::AutoLock lock(*parent_lock_); + + DCHECK(page_count_ready_.IsSignaled()); + DCHECK(page_number < pages_.size()); + + pages_[page_number] = metafile_stream; + pages_ready_state_[page_number]->Signal(); +} + +HRESULT PrintDocumentSource::PrintPage(ID2D1PrintControl* print_control, + ID2D1GdiMetafile* gdi_metafile, + D2D1_SIZE_F page_size) { + DVLOG(1) << __FUNCTION__ << ", page_size: (" << page_size.width << ", " + << page_size.height << ")"; + DCHECK(print_control != NULL); + DCHECK(gdi_metafile != NULL); + + // D2D device contexts are stateful, and hence a unique device context must + // be used on each call. + mswr::ComPtr<ID2D1DeviceContext> d2d_context; + HRESULT hr = directx_context_.d2d_device->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CreateDeviceContext " << std::hex << hr; + return hr; + } + + mswr::ComPtr<ID2D1CommandList> print_command_list; + hr = d2d_context->CreateCommandList(&print_command_list); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CreateCommandList " << std::hex << hr; + return hr; + } + + d2d_context->SetTarget(print_command_list.Get()); + + d2d_context->BeginDraw(); + d2d_context->DrawGdiMetafile(gdi_metafile); + hr = d2d_context->EndDraw(); + LOG_IF(ERROR, FAILED(hr)) << "Failed to EndDraw " << std::hex << hr; + + // Make sure to always close the command list. + HRESULT close_hr = print_command_list->Close(); + LOG_IF(ERROR, FAILED(close_hr)) << "Failed to close command list " << std::hex + << hr; + if (SUCCEEDED(hr) && SUCCEEDED(close_hr)) + hr = print_control->AddPage(print_command_list.Get(), page_size, NULL); + if (FAILED(hr)) + return hr; + else + return close_hr; +} + +size_t PrintDocumentSource::WaitAndGetPageCount() { + // Properly protect the wait/access to the page count. + { + base::AutoLock lock(*parent_lock_); + if (aborted_) + return 0; + DCHECK(pages_.size() == pages_ready_state_.size()); + if (!pages_.empty()) + return pages_.size(); + } + page_count_ready_.Wait(); + { + base::AutoLock lock(*parent_lock_); + if (!aborted_) { + DCHECK(pages_.size() == pages_ready_state_.size()); + return pages_.size(); + } + } + // A page count of 0 means abort. + return 0; +} + +HRESULT PrintDocumentSource::WaitAndGetPage(size_t page_number, + ID2D1GdiMetafile** gdi_metafile) { + // Properly protect the wait/access to the page data. + base::AutoLock lock(*parent_lock_); + // Make sure we weren't canceled before getting here. + // And the page count should have been received before we get here too. + if (aborted_) + return S_FALSE; + + // We shouldn't be asked for a page until we got the page count. + DCHECK(page_count_ready_.IsSignaled()); + DCHECK(page_number <= pages_ready_state_.size()); + DCHECK(pages_.size() == pages_ready_state_.size()); + while (!aborted_ && pages_[page_number].Get() == NULL) + pages_ready_state_[page_number]->Wait(); + + // Make sure we weren't aborted while we waited unlocked. + if (aborted_) + return S_FALSE; + DCHECK(page_number < pages_.size()); + + mswr::ComPtr<ID2D1Factory> factory; + directx_context_.d2d_device->GetFactory(&factory); + + mswr::ComPtr<ID2D1Factory1> factory1; + HRESULT hr = factory.As(&factory1); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI for ID2D1Factory1 " << std::hex << hr; + return hr; + } + + ULARGE_INTEGER result; + LARGE_INTEGER seek_pos; + seek_pos.QuadPart = 0; + hr = pages_[page_number]->Seek(seek_pos, STREAM_SEEK_SET, &result); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to Seek page stream " << std::hex << hr; + return hr; + } + + hr = factory1->CreateGdiMetafile(pages_[page_number].Get(), gdi_metafile); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CreateGdiMetafile " << std::hex << hr; + return hr; + } + return hr; +} + +} // namespace metro_driver diff --git a/win8/metro_driver/print_document_source.h b/win8/metro_driver/print_document_source.h new file mode 100644 index 0000000..fed333e --- /dev/null +++ b/win8/metro_driver/print_document_source.h @@ -0,0 +1,164 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_PRINT_DOCUMENT_SOURCE_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_PRINT_DOCUMENT_SOURCE_H_ + +#include <documentsource.h> +#include <printpreview.h> +#include <windows.graphics.printing.h> + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/waitable_event.h" +#include "win8/metro_driver/winrt_utils.h" + +// Hack to be removed once we don't need to build with an SDK earlier than +// 8441 where the name of the interface has been changed. +// TODO(mad): remove once we don't run mixed SDK/OS anymore. +#ifndef __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__ +typedef IPrintPreviewDXGIPackageTarget IPrintPreviewDxgiPackageTarget; +#endif + + +namespace base { +class Lock; +}; // namespace base + +namespace metro_driver { + +// This class is given to Metro as a source for print documents. +// The methodless IPrintDocumentSource interface is used to identify it as such. +// Then, the other interfaces are used to generate preview and print documents. +// It also exposes a few methods for the print handler to control the document. +class PrintDocumentSource + : public mswr::RuntimeClass< + mswr::RuntimeClassFlags<mswr::WinRtClassicComMix>, + wingfx::Printing::IPrintDocumentSource, + IPrintDocumentPageSource, + IPrintPreviewPageCollection> { + public: + // A set of interfaces for the DirectX context that our parent owns + // and that don't need to change from document to document. + struct DirectXContext { + DirectXContext() {} + DirectXContext(ID3D11Device1* device_3d, + ID2D1Factory1* factory_2d, + ID2D1Device* device_2d, + ID2D1DeviceContext* context_2d, + IWICImagingFactory2* factory_wic) + : d3d_device(device_3d), + d2d_factory(factory_2d), + d2d_device(device_2d), + d2d_context(context_2d), + wic_factory(factory_wic) { + } + DirectXContext(const DirectXContext& other) + : d3d_device(other.d3d_device), + d2d_factory(other.d2d_factory), + d2d_device(other.d2d_device), + d2d_context(other.d2d_context), + wic_factory(other.wic_factory) { + } + mswr::ComPtr<ID3D11Device1> d3d_device; + mswr::ComPtr<ID2D1Factory1> d2d_factory; + mswr::ComPtr<ID2D1Device> d2d_device; + mswr::ComPtr<ID2D1DeviceContext> d2d_context; + mswr::ComPtr<IWICImagingFactory2> wic_factory; + }; + + // Construction / Initialization. + explicit PrintDocumentSource(); + HRESULT RuntimeClassInitialize(const DirectXContext& directx_context, + base::Lock* parent_lock); + // Aborts any pending asynchronous operation. + void Abort(); + + // classic COM interface IPrintDocumentPageSource methods + STDMETHOD(GetPreviewPageCollection) ( + IPrintDocumentPackageTarget* package_target, + IPrintPreviewPageCollection** page_collection); + STDMETHOD(MakeDocument)(IInspectable* options, + IPrintDocumentPackageTarget* package_target); + + // classic COM interface IPrintPreviewPageCollection methods + STDMETHOD(Paginate)(uint32 page, IInspectable* options); + STDMETHOD(MakePage)(uint32 desired_page, float width, float height); + + // If the screen DPI changes, we must be warned here. + void ResetDpi(float dpi); + + // When the page count is known, this is called and we can setup our data. + void SetPageCount(size_t page_count); + + // Every time a page is ready, this is called and we can read the data if + // we were waiting for it, or store it for later use. + void AddPage(size_t page_number, IStream* metafile_stream); + + private: + // Print the page given in the metafile stream to the given print control. + HRESULT PrintPage(ID2D1PrintControl* print_control, + ID2D1GdiMetafile* metafile_stream, + D2D1_SIZE_F pageSize); + + // Helper function to wait for the page count to be ready. + // Returns 0 when aborted. + size_t WaitAndGetPageCount(); + + // Helper function to wait for a given page to be ready. + // Returns S_FALSE when aborted. + HRESULT WaitAndGetPage(size_t page_number, + ID2D1GdiMetafile** metafile_stream); + + DirectXContext directx_context_; + + // Once page data is available, it's added to this vector. + std::vector<mswr::ComPtr<IStream>> pages_; + + // When page count is set, the size of this vector is set to that number. + // Then, every time page data is added to pages_, the associated condition + // variable in this vector is signaled. This is only filled when we receive + // the page count, so we must wait on page_count_ready_ before accessing + // the content of this vector. + std::vector<scoped_ptr<base::ConditionVariable> > pages_ready_state_; + + // This event is signaled when we receive a page count from Chrome. We should + // not receive any page data before the count, so we can check this event + // while waiting for pages too, in case we ask for page data before we got + // the count, so before we properly initialized pages_ready_state_. + base::WaitableEvent page_count_ready_; + + // The preview target interface set from within GetPreviewPageCollection + // but used from within MakePage. + mswr::ComPtr<IPrintPreviewDxgiPackageTarget> dxgi_preview_target_; + + // Our parent's lock (to make sure it is initialized and destroyed early + // enough), which we use to protect access to our data members. + base::Lock* parent_lock_; + + // The width/height requested in Paginate and used in MakePage. + // TODO(mad): Height is currently not used, and width is only used for + // scaling. We need to add a way to specify width and height when we request + // pages from Chrome. + float height_; + float width_; + + // The DPI is set by Windows and we need to give it to DirectX. + float dpi_; + + // A flag identiying that we have been aborted. Needed to properly handle + // asynchronous callbacks. + bool aborted_; + + // TODO(mad): remove once we don't run mixed SDK/OS anymore. + bool using_old_preview_interface_; +}; + +} // namespace metro_driver + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_PRINT_DOCUMENT_SOURCE_H_ diff --git a/win8/metro_driver/print_handler.cc b/win8/metro_driver/print_handler.cc new file mode 100644 index 0000000..dce321a --- /dev/null +++ b/win8/metro_driver/print_handler.cc @@ -0,0 +1,487 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "win8/metro_driver/print_handler.h" + +#include <windows.graphics.display.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "chrome/app/chrome_command_ids.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace { + +typedef winfoundtn::ITypedEventHandler< + wingfx::Printing::PrintManager*, + wingfx::Printing::PrintTaskRequestedEventArgs*> PrintRequestedHandler; + +typedef winfoundtn::ITypedEventHandler< + wingfx::Printing::PrintTask*, + wingfx::Printing::PrintTaskCompletedEventArgs*> PrintTaskCompletedHandler; + +typedef winfoundtn::ITypedEventHandler< + wingfx::Printing::PrintTask*, IInspectable*> PrintTaskInspectableHandler; + +typedef winfoundtn::ITypedEventHandler< + wingfx::Printing::PrintTask*, + wingfx::Printing::PrintTaskProgressingEventArgs*> + PrintTaskProgressingHandler; + +} // namespace + +namespace metro_driver { + +mswr::ComPtr<PrintDocumentSource> PrintHandler::current_document_source_; +bool PrintHandler::printing_enabled_ = false; +base::Lock* PrintHandler::lock_ = NULL; +base::Thread* PrintHandler::thread_ = NULL; + +PrintHandler::PrintHandler() { + DCHECK(lock_ == NULL); + lock_ = new base::Lock(); + + DCHECK(thread_ == NULL); + thread_ = new base::Thread("Metro Print Handler"); + thread_->Start(); +} + +PrintHandler::~PrintHandler() { + ClearPrintTask(); + DCHECK(current_document_source_.Get() == NULL); + + // Get all pending tasks to complete cleanly by Stopping the thread. + // They should complete quickly since current_document_source_ is NULL. + DCHECK(thread_ != NULL); + DCHECK(thread_->IsRunning()); + thread_->Stop(); + delete thread_; + thread_ = NULL; + + DCHECK(lock_ != NULL); + delete lock_; + lock_ = NULL; +} + +HRESULT PrintHandler::Initialize(winui::Core::ICoreWindow* window) { + // Register for Print notifications. + mswr::ComPtr<wingfx::Printing::IPrintManagerStatic> print_mgr_static; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Printing_PrintManager, + print_mgr_static.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create PrintManagerStatic " << std::hex << hr; + return hr; + } + + mswr::ComPtr<wingfx::Printing::IPrintManager> print_mgr; + hr = print_mgr_static->GetForCurrentView(&print_mgr); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get PrintManager for current view " << std::hex + << hr; + return hr; + } + + hr = print_mgr->add_PrintTaskRequested( + mswr::Callback<PrintRequestedHandler>( + this, &PrintHandler::OnPrintRequested).Get(), + &print_requested_token_); + LOG_IF(ERROR, FAILED(hr)) << "Failed to register PrintTaskRequested " + << std::hex << hr; + + mswr::ComPtr<wingfx::Display::IDisplayPropertiesStatics> display_properties; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Display_DisplayProperties, + display_properties.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create DisplayPropertiesStatics " << std::hex + << hr; + return hr; + } + + hr = display_properties->add_LogicalDpiChanged( + mswr::Callback< + wingfx::Display::IDisplayPropertiesEventHandler, + PrintHandler>(this, &PrintHandler::LogicalDpiChanged).Get(), + &dpi_change_token_); + LOG_IF(ERROR, FAILED(hr)) << "Failed to register LogicalDpiChanged " + << std::hex << hr; + + // This flag adds support for surfaces with a different color channel + // ordering than the API default. It is recommended usage, and is required + // for compatibility with Direct2D. + UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; +#if defined(_DEBUG) + creation_flags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + // This array defines the set of DirectX hardware feature levels we support. + // The ordering MUST be preserved. All applications are assumed to support + // 9.1 unless otherwise stated by the application, which is not our case. + D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 }; + + mswr::ComPtr<ID3D11Device> device; + mswr::ComPtr<ID3D11DeviceContext> context; + hr = D3D11CreateDevice( + NULL, // Specify null to use the default adapter. + D3D_DRIVER_TYPE_HARDWARE, + 0, // Leave as 0 unless software device. + creation_flags, + feature_levels, + ARRAYSIZE(feature_levels), + D3D11_SDK_VERSION, // Must always use this value in Metro apps. + &device, + NULL, // Returns feature level of device created. + &context); + if (hr == DXGI_ERROR_UNSUPPORTED) { + // The hardware is not supported, try a reference driver instead. + hr = D3D11CreateDevice( + NULL, // Specify null to use the default adapter. + D3D_DRIVER_TYPE_REFERENCE, + 0, // Leave as 0 unless software device. + creation_flags, + feature_levels, + ARRAYSIZE(feature_levels), + D3D11_SDK_VERSION, // Must always use this value in Metro apps. + &device, + NULL, // Returns feature level of device created. + &context); + } + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create D3D11 device/context " << std::hex << hr; + return hr; + } + + hr = device.As(&directx_context_.d3d_device); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI D3D11 device " << std::hex << hr; + return hr; + } + + D2D1_FACTORY_OPTIONS options; + ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS)); + +#if defined(_DEBUG) + options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; +#endif + + hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory1), + &options, + &directx_context_.d2d_factory); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create D2D1 factory " << std::hex << hr; + return hr; + } + + mswr::ComPtr<IDXGIDevice> dxgi_device; + hr = directx_context_.d3d_device.As(&dxgi_device); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to QI for IDXGIDevice " << std::hex << hr; + return hr; + } + + hr = directx_context_.d2d_factory->CreateDevice( + dxgi_device.Get(), &directx_context_.d2d_device); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to Create D2DDevice " << std::hex << hr; + return hr; + } + + hr = directx_context_.d2d_device->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + &directx_context_.d2d_context); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to Create D2DDeviceContext " << std::hex << hr; + return hr; + } + + hr = CoCreateInstance(CLSID_WICImagingFactory, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&directx_context_.wic_factory)); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to CoCreate WICImagingFactory " << std::hex << hr; + return hr; + } + return hr; +} + +void PrintHandler::EnablePrinting(bool printing_enabled) { + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::OnEnablePrinting, printing_enabled)); +} + +void PrintHandler::SetPageCount(size_t page_count) { + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::OnSetPageCount, page_count)); +} + +void PrintHandler::AddPage(size_t page_number, IStream* metafile_stream) { + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::OnAddPage, + page_number, + mswr::ComPtr<IStream>(metafile_stream))); +} + +void PrintHandler::ShowPrintUI() { + // Post the print UI request over to the metro thread. + DCHECK(globals.appview_msg_loop != NULL); + bool posted = globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&metro_driver::PrintHandler::OnShowPrintUI)); + DCHECK(posted); +} + +HRESULT PrintHandler::OnPrintRequested( + wingfx::Printing::IPrintManager* print_mgr, + wingfx::Printing::IPrintTaskRequestedEventArgs* event_args) { + DVLOG(1) << __FUNCTION__; + + HRESULT hr = S_OK; + if (printing_enabled_) { + mswr::ComPtr<wingfx::Printing::IPrintTaskRequest> print_request; + hr = event_args->get_Request(print_request.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get the Print Task request " << std::hex << hr; + return hr; + } + + mswrw::HString title; + title.Attach(MakeHString(L"Printing")); + hr = print_request->CreatePrintTask( + title.Get(), + mswr::Callback< + wingfx::Printing::IPrintTaskSourceRequestedHandler, + PrintHandler>(this, &PrintHandler::OnPrintTaskSourceRequest).Get(), + print_task_.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create the Print Task " << std::hex << hr; + return hr; + } + + hr = print_task_->add_Completed( + mswr::Callback<PrintTaskCompletedHandler>( + this, &PrintHandler::OnCompleted).Get(), &print_completed_token_); + LOG_IF(ERROR, FAILED(hr)) << "Failed to create the Print Task " << std::hex + << hr; + } + return hr; +} + +HRESULT PrintHandler::OnPrintTaskSourceRequest( + wingfx::Printing::IPrintTaskSourceRequestedArgs* args) { + DVLOG(1) << __FUNCTION__; + mswr::ComPtr<PrintDocumentSource> print_document_source; + HRESULT hr = mswr::MakeAndInitialize<PrintDocumentSource>( + &print_document_source, directx_context_, lock_); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create document source " << std::hex << hr; + return hr; + } + + print_document_source->ResetDpi(GetLogicalDpi()); + + mswr::ComPtr<wingfx::Printing::IPrintDocumentSource> print_source; + hr = print_document_source.As(&print_source); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to cast document Source " << std::hex << hr; + return hr; + } + + hr = args->SetSource(print_source.Get()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to set document Source " << std::hex << hr; + return hr; + } + + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::SetPrintDocumentSource, + print_document_source)); + + return hr; +} + +HRESULT PrintHandler::OnCompleted( + wingfx::Printing::IPrintTask* task, + wingfx::Printing::IPrintTaskCompletedEventArgs* args) { + DVLOG(1) << __FUNCTION__; + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + ClearPrintTask(); + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::ReleasePrintDocumentSource)); + + return S_OK; +} + +void PrintHandler::ClearPrintTask() { + if (!print_task_.Get()) + return; + + HRESULT hr = print_task_->remove_Completed(print_completed_token_); + LOG_IF(ERROR, FAILED(hr)) << "Failed to remove completed event from Task " + << std::hex << hr; + print_task_.Reset(); +} + +float PrintHandler::GetLogicalDpi() { + mswr::ComPtr<wingfx::Display::IDisplayPropertiesStatics> display_properties; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Display_DisplayProperties, + display_properties.GetAddressOf()); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to get display properties " << std::hex << hr; + return 0.0; + } + + FLOAT dpi = 0.0; + hr = display_properties->get_LogicalDpi(&dpi); + LOG_IF(ERROR, FAILED(hr)) << "Failed to get Logical DPI " << std::hex << hr; + + return dpi; +} + +HRESULT PrintHandler::LogicalDpiChanged(IInspectable *sender) { + DVLOG(1) << __FUNCTION__; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&PrintHandler::OnLogicalDpiChanged, GetLogicalDpi())); + return S_OK; +} + +void PrintHandler::OnLogicalDpiChanged(float dpi) { + DCHECK(MessageLoop::current() == thread_->message_loop()); + // No need to protect the access to the static variable, + // since it's set/released in this same thread. + if (current_document_source_.Get() != NULL) + current_document_source_->ResetDpi(dpi); +} + +void PrintHandler::SetPrintDocumentSource( + const mswr::ComPtr<PrintDocumentSource>& print_document_source) { + DCHECK(MessageLoop::current() == thread_->message_loop()); + DCHECK(current_document_source_.Get() == NULL); + { + // Protect against the other thread which might try to access it. + base::AutoLock lock(*lock_); + current_document_source_ = print_document_source; + } + // Start generating the images to print. + // TODO(mad): Use a registered message with more information about the print + // request, and at a more appropriate time too, and maybe one page at a time. + ::PostMessageW(globals.host_windows.front().first, + WM_SYSCOMMAND, + IDC_PRINT_TO_DESTINATION, + 0); +} + +void PrintHandler::ReleasePrintDocumentSource() { + DCHECK(MessageLoop::current() == thread_->message_loop()); + mswr::ComPtr<PrintDocumentSource> print_document_source; + { + // Must wait for other thread to be done with the pointer first. + base::AutoLock lock(*lock_); + current_document_source_.Swap(print_document_source); + } + // This may happen before we get a chance to set the value. + if (print_document_source.Get() != NULL) + print_document_source->Abort(); +} + +void PrintHandler::OnEnablePrinting(bool printing_enabled) { + DCHECK(MessageLoop::current() == thread_->message_loop()); + base::AutoLock lock(*lock_); + printing_enabled_ = printing_enabled; + // Don't abort if we are being disabled since we may be finishing a previous + // print request which was valid and should be finished. We just need to + // prevent any new print requests. And don't assert that we are NOT printing + // if we are becoming enabled since we may be finishing a long print while + // we got disabled and then enabled again... +} + +void PrintHandler::OnSetPageCount(size_t page_count) { + DCHECK(MessageLoop::current() == thread_->message_loop()); + // No need to protect the access to the static variable, + // since it's set/released in this same thread. + if (current_document_source_.Get() != NULL) + current_document_source_->SetPageCount(page_count); +} + +void PrintHandler::OnAddPage(size_t page_number, + mswr::ComPtr<IStream> metafile_stream) { + DCHECK(MessageLoop::current() == thread_->message_loop()); + // No need to protect the access to the static variable, + // since it's set/released in this same thread. + if (current_document_source_.Get() != NULL) + current_document_source_->AddPage(page_number, metafile_stream.Get()); +} + +void PrintHandler::OnShowPrintUI() { + DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); + mswr::ComPtr<wingfx::Printing::IPrintManagerStatic> print_mgr_static; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Printing_PrintManager, + print_mgr_static.GetAddressOf()); + if (SUCCEEDED(hr)) { + DCHECK(print_mgr_static.Get() != NULL); + // Note that passing NULL to ShowPrintUIAsync crashes, + // so we need to setup a temp pointer. + mswr::ComPtr<winfoundtn::IAsyncOperation<bool>> unused_async_op; + hr = print_mgr_static->ShowPrintUIAsync(unused_async_op.GetAddressOf()); + LOG_IF(ERROR, FAILED(hr)) << "Failed to ShowPrintUIAsync " + << std::hex << std::showbase << hr; + } else { + LOG(ERROR) << "Failed to create PrintManagerStatic " + << std::hex << std::showbase << hr; + } +} + +} // namespace metro_driver + +void MetroEnablePrinting(BOOL printing_enabled) { + metro_driver::PrintHandler::EnablePrinting(!!printing_enabled); +} + +void MetroSetPrintPageCount(size_t page_count) { + DVLOG(1) << __FUNCTION__ << " Page count: " << page_count; + metro_driver::PrintHandler::SetPageCount(page_count); +} + +void MetroSetPrintPageContent(size_t page_number, + void* data, + size_t data_size) { + DVLOG(1) << __FUNCTION__ << " Page number: " << page_number; + DCHECK(data != NULL); + DCHECK(data_size > 0); + mswr::ComPtr<IStream> metafile_stream; + HRESULT hr = ::CreateStreamOnHGlobal( + NULL, TRUE, metafile_stream.GetAddressOf()); + if (metafile_stream.Get() != NULL) { + ULONG bytes_written = 0; + hr = metafile_stream->Write(data, data_size, &bytes_written); + LOG_IF(ERROR, FAILED(hr)) << "Failed to Write to Stream " << std::hex << hr; + DCHECK(bytes_written == data_size); + + metro_driver::PrintHandler::AddPage(page_number, metafile_stream.Get()); + } else { + NOTREACHED() << "Failed to CreateStreamOnHGlobal " << std::hex << hr; + } +} + +void MetroShowPrintUI() { + metro_driver::PrintHandler::ShowPrintUI(); +} diff --git a/win8/metro_driver/print_handler.h b/win8/metro_driver/print_handler.h new file mode 100644 index 0000000..f0779cf --- /dev/null +++ b/win8/metro_driver/print_handler.h @@ -0,0 +1,116 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_PRINT_HANDLER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_PRINT_HANDLER_H_ + +#include <windows.media.playto.h> +#include <windows.graphics.printing.h> +#include <windows.ui.core.h> + +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" +#include "win8/metro_driver/print_document_source.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace base { + +class Lock; + +} // namespace base + +namespace metro_driver { + +// This class handles the print aspect of the devices charm. +class PrintHandler { + public: + PrintHandler(); + ~PrintHandler(); + + HRESULT Initialize(winui::Core::ICoreWindow* window); + + // Called by the exported C functions. + static void EnablePrinting(bool printing_enabled); + static void SetPageCount(size_t page_count); + static void AddPage(size_t page_number, IStream* metafile_stream); + static void ShowPrintUI(); + + private: + // Callbacks from Metro. + HRESULT OnPrintRequested( + wingfx::Printing::IPrintManager* print_mgr, + wingfx::Printing::IPrintTaskRequestedEventArgs* event_args); + + HRESULT OnPrintTaskSourceRequest( + wingfx::Printing::IPrintTaskSourceRequestedArgs* args); + + HRESULT OnCompleted(wingfx::Printing::IPrintTask* task, + wingfx::Printing::IPrintTaskCompletedEventArgs* args); + // Utility methods. + void ClearPrintTask(); + float GetLogicalDpi(); + + // Callback from Metro and entry point called on lockable thread. + HRESULT LogicalDpiChanged(IInspectable *sender); + static void OnLogicalDpiChanged(float dpi); + + // Called on the lockable thread to set/release the doc. + static void SetPrintDocumentSource( + const mswr::ComPtr<PrintDocumentSource>& print_document_source); + static void ReleasePrintDocumentSource(); + + // Called on the lockable thread for the exported C functions. + static void OnEnablePrinting(bool printing_enabled); + static void OnSetPageCount(size_t page_count); + static void OnAddPage(size_t page_number, + mswr::ComPtr<IStream> metafile_stream); + + // Opens the prit device charm. Must be called from the metro thread. + static void OnShowPrintUI(); + + mswr::ComPtr<wingfx::Printing::IPrintTask> print_task_; + EventRegistrationToken print_requested_token_; + EventRegistrationToken print_completed_token_; + EventRegistrationToken dpi_change_token_; + + mswr::ComPtr<wingfx::Printing::IPrintManager> print_manager_; + PrintDocumentSource::DirectXContext directx_context_; + + // Hack to give access to the Print Document from the C style entry points. + // This will go away once we can pass a pointer to this interface down to + // the Chrome Browser as we send the command to print. + static mswr::ComPtr<PrintDocumentSource> current_document_source_; + + // Another hack to enable/disable printing from an exported C function. + // TODO(mad): Find a better way to do this... + static bool printing_enabled_; + + // This is also a temporary hack until we can pass down the print document + // to Chrome so it can call directly into it. We need to lock the access to + // current_document_source_. + static base::Lock* lock_; + + // This thread is used to send blocking jobs + // out of threads we don't want to block. + static base::Thread* thread_; +}; + +} // namespace metro_driver + +// Exported C functions for Chrome to call into the Metro module. +extern "C" __declspec(dllexport) +void MetroEnablePrinting(BOOL printing_enabled); + +extern "C" __declspec(dllexport) +void MetroSetPrintPageCount(size_t page_count); + +extern "C" __declspec(dllexport) +void MetroSetPrintPageContent(size_t current_page, + void* data, + size_t data_size); + +extern "C" __declspec(dllexport) +void MetroShowPrintUI(); + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_PRINT_HANDLER_H_ diff --git a/win8/metro_driver/run_all_unittests.cc b/win8/metro_driver/run_all_unittests.cc new file mode 100644 index 0000000..4c6a548 --- /dev/null +++ b/win8/metro_driver/run_all_unittests.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2012 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 "stdafx.h" + +#include "base/at_exit.h" +#include "testing/gtest/include/gtest/gtest.h" + +#pragma comment(lib, "runtimeobject.lib") + +base::AtExitManager at_exit; + +int main(int argc, char** argv) { + mswrw::RoInitializeWrapper ro_init(RO_INIT_SINGLETHREADED); + + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/win8/metro_driver/secondary_tile.cc b/win8/metro_driver/secondary_tile.cc new file mode 100644 index 0000000..8a54bd7 --- /dev/null +++ b/win8/metro_driver/secondary_tile.cc @@ -0,0 +1,155 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "secondary_tile.h" + +#include <windows.ui.startscreen.h> + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "crypto/sha2.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace { + +string16 GenerateTileId(const string16& url_str) { + uint8 hash[crypto::kSHA256Length]; + crypto::SHA256HashString(UTF16ToUTF8(url_str), hash, sizeof(hash)); + std::string hash_str = base::HexEncode(hash, sizeof(hash)); + return UTF8ToUTF16(hash_str); +} + +string16 GetLogoUrlString() { + FilePath module_path; + PathService::Get(base::DIR_MODULE, &module_path); + string16 scheme(L"ms-appx:///"); + return scheme.append(module_path.BaseName().value()) + .append(L"/SecondaryTile.png"); +} + +BOOL IsPinnedToStartScreen(const string16& url_str) { + mswr::ComPtr<winui::StartScreen::ISecondaryTileStatics> tile_statics; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_StartScreen_SecondaryTile, + tile_statics.GetAddressOf()); + CheckHR(hr, "Failed to create instance of ISecondaryTileStatics"); + + boolean exists; + hr = tile_statics->Exists(MakeHString(GenerateTileId(url_str)), &exists); + CheckHR(hr, "ISecondaryTileStatics.Exists failed"); + return exists; +} + +void DeleteTileFromStartScreen(const string16& url_str) { + DVLOG(1) << __FUNCTION__; + mswr::ComPtr<winui::StartScreen::ISecondaryTileFactory> tile_factory; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_StartScreen_SecondaryTile, + tile_factory.GetAddressOf()); + CheckHR(hr, "Failed to create instance of ISecondaryTileFactory"); + + mswrw::HString id; + id.Attach(MakeHString(GenerateTileId(url_str))); + + mswr::ComPtr<winui::StartScreen::ISecondaryTile> tile; + hr = tile_factory->CreateWithId(id.Get(), tile.GetAddressOf()); + CheckHR(hr, "Failed to create tile"); + + mswr::ComPtr<winfoundtn::IAsyncOperation<bool>> completion; + hr = tile->RequestDeleteAsync(completion.GetAddressOf()); + CheckHR(hr, "RequestDeleteAsync failed"); + + typedef winfoundtn::IAsyncOperationCompletedHandler<bool> RequestDoneType; + mswr::ComPtr<RequestDoneType> handler(mswr::Callback<RequestDoneType>( + globals.view, &ChromeAppView::TileRequestCreateDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + CheckHR(hr, "Failed to put_Completed"); +} + +void CreateTileOnStartScreen(const string16& title_str, + const string16& url_str) { + VLOG(1) << __FUNCTION__; + mswr::ComPtr<winui::StartScreen::ISecondaryTileFactory> tile_factory; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_StartScreen_SecondaryTile, + tile_factory.GetAddressOf()); + CheckHR(hr, "Failed to create instance of ISecondaryTileFactory"); + + winui::StartScreen::TileOptions options = + winui::StartScreen::TileOptions_ShowNameOnLogo; + mswrw::HString title; + title.Attach(MakeHString(title_str)); + mswrw::HString id; + id.Attach(MakeHString(GenerateTileId(url_str))); + mswrw::HString args; + args.Attach(MakeHString(string16(L"url=").append(url_str))); + + mswr::ComPtr<winfoundtn::IUriRuntimeClassFactory> uri_factory; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Foundation_Uri, + uri_factory.GetAddressOf()); + CheckHR(hr, "Failed to create URIFactory"); + + mswrw::HString logo_url; + logo_url.Attach(MakeHString(GetLogoUrlString())); + mswr::ComPtr<winfoundtn::IUriRuntimeClass> uri; + hr = uri_factory->CreateUri(logo_url.Get(), &uri); + CheckHR(hr, "Failed to create URI"); + + mswr::ComPtr<winui::StartScreen::ISecondaryTile> tile; + hr = tile_factory->CreateTile(id.Get(), + title.Get(), + title.Get(), + args.Get(), + options, + uri.Get(), + tile.GetAddressOf()); + CheckHR(hr, "Failed to create tile"); + + hr = tile->put_ForegroundText(winui::StartScreen::ForegroundText_Light); + CheckHR(hr, "Failed to change foreground text color"); + + mswr::ComPtr<winfoundtn::IAsyncOperation<bool>> completion; + hr = tile->RequestCreateAsync(completion.GetAddressOf()); + CheckHR(hr, "RequestCreateAsync failed"); + + typedef winfoundtn::IAsyncOperationCompletedHandler<bool> RequestDoneType; + mswr::ComPtr<RequestDoneType> handler(mswr::Callback<RequestDoneType>( + globals.view, &ChromeAppView::TileRequestCreateDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + CheckHR(hr, "Failed to put_Completed"); +} + +void TogglePinnedToStartScreen(const string16& title_str, + const string16& url_str) { + if (IsPinnedToStartScreen(url_str)) { + DeleteTileFromStartScreen(url_str); + return; + } + + CreateTileOnStartScreen(title_str, url_str); +} + +} // namespace + +BOOL MetroIsPinnedToStartScreen(const string16& url) { + VLOG(1) << __FUNCTION__ << " url: " << url; + return IsPinnedToStartScreen(url); +} + +void MetroTogglePinnedToStartScreen(const string16& title, + const string16& url) { + DVLOG(1) << __FUNCTION__ << " title:" << title << " url: " << url; + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&TogglePinnedToStartScreen, title, url)); +} diff --git a/win8/metro_driver/secondary_tile.h b/win8/metro_driver/secondary_tile.h new file mode 100644 index 0000000..796e881 --- /dev/null +++ b/win8/metro_driver/secondary_tile.h @@ -0,0 +1,16 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_SECONDARY_TILE_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_SECONDARY_TILE_H_ + +#include "base/string16.h" + +extern "C" __declspec(dllexport) +BOOL MetroIsPinnedToStartScreen(const string16& url); + +extern "C" __declspec(dllexport) +void MetroTogglePinnedToStartScreen(const string16& title, const string16& url); + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_SECONDARY_TILE_H_ + diff --git a/win8/metro_driver/settings_handler.cc b/win8/metro_driver/settings_handler.cc new file mode 100644 index 0000000..6feae24 --- /dev/null +++ b/win8/metro_driver/settings_handler.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "settings_handler.h" + +// This include allows to send WM_SYSCOMMANDs to chrome. +#include "chrome/app/chrome_command_ids.h" +#include "chrome_app_view.h" +#include "winrt_utils.h" + +typedef winfoundtn::ITypedEventHandler< + winui::ApplicationSettings::SettingsPane*, + winui::ApplicationSettings::SettingsPaneCommandsRequestedEventArgs*> + CommandsRequestedHandler; + +namespace { + +// String identifiers for the settings pane commands. +const wchar_t* kSettingsId = L"settings"; +const wchar_t* kHelpId = L"help"; +const wchar_t* kAboutId = L"about"; + +} + +SettingsHandler::SettingsHandler() { + DVLOG(1) << __FUNCTION__; +} + +SettingsHandler::~SettingsHandler() { + DVLOG(1) << __FUNCTION__; +} + +HRESULT SettingsHandler::Initialize() { + mswr::ComPtr<winui::ApplicationSettings::ISettingsPaneStatics> + settings_pane_statics; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_ApplicationSettings_SettingsPane, + settings_pane_statics.GetAddressOf()); + CheckHR(hr, "Failed to activate ISettingsPaneStatics"); + + mswr::ComPtr<winui::ApplicationSettings::ISettingsPane> settings_pane; + hr = settings_pane_statics->GetForCurrentView(&settings_pane); + CheckHR(hr, "Failed to get ISettingsPane"); + + hr = settings_pane->add_CommandsRequested( + mswr::Callback<CommandsRequestedHandler>( + this, + &SettingsHandler::OnSettingsCommandsRequested).Get(), + &settings_token_); + CheckHR(hr, "Failed to add CommandsRequested"); + + return hr; +} + +HRESULT SettingsHandler::OnSettingsCommandsRequested( + winui::ApplicationSettings::ISettingsPane* settings_pane, + winui::ApplicationSettings::ISettingsPaneCommandsRequestedEventArgs* args) { + mswr::ComPtr<winui::ApplicationSettings::ISettingsCommandFactory> + settings_command_factory; + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_ApplicationSettings_SettingsCommand, + settings_command_factory.GetAddressOf()); + CheckHR(hr, "Failed to activate ISettingsCommandFactory"); + + mswr::ComPtr<winui::ApplicationSettings::ISettingsPaneCommandsRequest> + settings_command_request; + hr = args->get_Request(&settings_command_request); + CheckHR(hr, "Failed to get_Request"); + + mswr::ComPtr<SettingsHandler::ISettingsCommandVector> application_commands; + hr = settings_command_request->get_ApplicationCommands(&application_commands); + CheckHR(hr, "Failed to get_ApplicationCommands"); + + // TODO(mad): Internationalize the hard coded user visible strings. + hr = AppendNewSettingsCommand( + kSettingsId, L"Settings", settings_command_factory.Get(), + application_commands.Get()); + CheckHR(hr, "Failed to append new settings command"); + + hr = AppendNewSettingsCommand( + kHelpId, L"Help", settings_command_factory.Get(), + application_commands.Get()); + CheckHR(hr, "Failed to append new help command"); + + hr = AppendNewSettingsCommand( + kAboutId, L"About", settings_command_factory.Get(), + application_commands.Get()); + CheckHR(hr, "Failed to append new about command"); + + return hr; +} + +HRESULT SettingsHandler::AppendNewSettingsCommand( + const wchar_t* id, + const wchar_t* name, + winui::ApplicationSettings::ISettingsCommandFactory* + settings_command_factory, + SettingsHandler::ISettingsCommandVector* settings_command_vector) { + mswr::ComPtr<winfoundtn::IPropertyValue> settings_id; + HRESULT hr = GetSettingsId(id, &settings_id); + CheckHR(hr, "Can't get settings id"); + + mswrw::HString settings_name; + settings_name.Attach(MakeHString(name)); + mswr::ComPtr<winui::Popups::IUICommand> command; + hr = settings_command_factory->CreateSettingsCommand( + settings_id.Get(), + settings_name.Get(), + mswr::Callback<winui::Popups::IUICommandInvokedHandler>( + &SettingsHandler::OnSettings).Get(), + command.GetAddressOf()); + CheckHR(hr, "Can't create settings command"); + + hr = settings_command_vector->Append(command.Get()); + CheckHR(hr, "Failed to append settings command"); + + return hr; +} + +HRESULT SettingsHandler::OnSettings(winui::Popups::IUICommand* command) { + mswr::ComPtr<winfoundtn::IPropertyValue> settings_id; + HRESULT hr = GetSettingsId(kSettingsId, &settings_id); + CheckHR(hr, "Failed to get settings id"); + + mswr::ComPtr<winfoundtn::IPropertyValue> help_id; + hr = GetSettingsId(kHelpId, &help_id); + CheckHR(hr, "Failed to get settings id"); + + mswr::ComPtr<winfoundtn::IPropertyValue> about_id; + hr = GetSettingsId(kAboutId, &about_id); + CheckHR(hr, "Failed to get settings id"); + + mswr::ComPtr<winfoundtn::IPropertyValue> command_id; + hr = command->get_Id(&command_id); + CheckHR(hr, "Failed to get command id"); + + INT32 result = -1; + hr = winrt_utils::CompareProperties( + command_id.Get(), settings_id.Get(), &result); + CheckHR(hr, "Failed to compare ids"); + + HWND chrome_window = globals.host_windows.front().first; + + if (result == 0) { + ::PostMessageW(chrome_window, WM_SYSCOMMAND, IDC_OPTIONS, 0); + return S_OK; + } + + hr = winrt_utils::CompareProperties(command_id.Get(), help_id.Get(), &result); + CheckHR(hr, "Failed to compare ids"); + if (result == 0) { + ::PostMessageW(chrome_window, WM_SYSCOMMAND, IDC_HELP_PAGE_VIA_MENU, 0); + return S_OK; + } + + hr = winrt_utils::CompareProperties( + command_id.Get(), about_id.Get(), &result); + CheckHR(hr, "Failed to compare ids"); + if (result == 0) { + ::PostMessageW(chrome_window, WM_SYSCOMMAND, IDC_ABOUT, 0); + return S_OK; + } + + return S_OK; +} + +HRESULT SettingsHandler::GetSettingsId( + const wchar_t* value, winfoundtn::IPropertyValue** settings_id) { + mswrw::HString property_value_string; + property_value_string.Attach(MakeHString(value)); + return winrt_utils::CreateStringProperty(property_value_string.Get(), + settings_id); +} diff --git a/win8/metro_driver/settings_handler.h b/win8/metro_driver/settings_handler.h new file mode 100644 index 0000000..5b234ad --- /dev/null +++ b/win8/metro_driver/settings_handler.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_SETTINGS_HANDLER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_SETTINGS_HANDLER_H_ + +#include <windows.ui.applicationsettings.h> +#include <windows.ui.popups.h> + +#include "winrt_utils.h" + +// This class handles the settings charm. +class SettingsHandler { + public: + SettingsHandler(); + ~SettingsHandler(); + + HRESULT Initialize(); + + private: + typedef winfoundtn::Collections::IVector< + winui::ApplicationSettings::SettingsCommand*> ISettingsCommandVector; + + HRESULT OnSettingsCommandsRequested( + winui::ApplicationSettings::ISettingsPane* settings_pane, + winui::ApplicationSettings:: + ISettingsPaneCommandsRequestedEventArgs* args); + + HRESULT AppendNewSettingsCommand( + const wchar_t* id, + const wchar_t* name, + winui::ApplicationSettings::ISettingsCommandFactory* + settings_command_factory, + ISettingsCommandVector* settings_command_vector); + + static HRESULT OnSettings(winui::Popups::IUICommand* command); + static HRESULT GetSettingsId(const wchar_t* value, + winfoundtn::IPropertyValue** settings_id); + + EventRegistrationToken settings_token_; +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_SETTINGS_HANDLER_H_ diff --git a/win8/metro_driver/stdafx.h b/win8/metro_driver/stdafx.h new file mode 100644 index 0000000..d8c1a01 --- /dev/null +++ b/win8/metro_driver/stdafx.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_STDAFX_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_STDAFX_H_ + +#include <wrl\implements.h> +#include <wrl\module.h> +#include <wrl\event.h> +#include <wrl\wrappers\corewrappers.h> + +#include <activation.h> +#include <d2d1_1.h> +#include <d3d11_1.h> +#include <roapi.h> +#include <stdio.h> +#include <wincodec.h> +#include <windows.h> + +#include <windows.applicationmodel.core.h> +#include <windows.applicationModel.datatransfer.h> +#include <windows.graphics.printing.h> +#include <windows.ui.notifications.h> + +namespace mswr = Microsoft::WRL; +namespace mswrw = Microsoft::WRL::Wrappers; +namespace winapp = ABI::Windows::ApplicationModel; +namespace windata = ABI::Windows::Data; +namespace winfoundtn = ABI::Windows::Foundation; +namespace wingfx = ABI::Windows::Graphics; +namespace winui = ABI::Windows::UI; +namespace winxml = windata::Xml; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_STDAFX_H_ diff --git a/win8/metro_driver/toast_notification_handler.cc b/win8/metro_driver/toast_notification_handler.cc new file mode 100644 index 0000000..36bddd5 --- /dev/null +++ b/win8/metro_driver/toast_notification_handler.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2012 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 <string> + +#include "win8/metro_driver/stdafx.h" +#include "win8/metro_driver/toast_notification_handler.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +// TODO(ananta) +// Refactor the chrome_util and shell_util code from chrome into a common lib +#include "win8/delegate_execute/chrome_util.h" +#include "win8/metro_driver/winrt_utils.h" + +typedef winfoundtn::ITypedEventHandler< + winui::Notifications::ToastNotification*, IInspectable*> + ToastActivationHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Notifications::ToastNotification*, + winui::Notifications::ToastDismissedEventArgs*> ToastDismissedHandler; + +namespace { + +// Helper function to return the text node root identified by the index passed +// in. +HRESULT GetTextNodeRoot( + unsigned int index, + winxml::Dom::IXmlDocument* xml_doc, + winxml::Dom::IXmlNode** node) { + DCHECK(xml_doc); + DCHECK(node); + + mswr::ComPtr<winxml::Dom::IXmlElement> document_element; + HRESULT hr = xml_doc->get_DocumentElement(&document_element); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlNodeList> elements; + mswrw::HString tag_name; + tag_name.Attach(MakeHString(L"text")); + hr = document_element->GetElementsByTagName(tag_name.Get(), + &elements); + CheckHR(hr); + + unsigned int count = 0; + elements->get_Length(&count); + + if (index > count) { + DVLOG(1) << "Invalid text node index passed in : " << index; + return E_FAIL; + } + hr = elements->Item(index, node); + CheckHR(hr); + return hr; +} + +// Helper function to append a text element to the text section in the +// XML document passed in. +// The index parameter identifies which text node we append to. +HRESULT CreateTextNode(winxml::Dom::IXmlDocument* xml_doc, + int index, + const string16& text_string) { + DCHECK(xml_doc); + + mswr::ComPtr<winxml::Dom::IXmlElement> document_element; + HRESULT hr = xml_doc->get_DocumentElement(&document_element); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlText> xml_text_node; + mswrw::HString data_hstring; + data_hstring.Attach(MakeHString(text_string.c_str())); + hr = xml_doc->CreateTextNode(data_hstring.Get(), &xml_text_node); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlNode> created_node; + hr = xml_text_node.CopyTo( + winxml::Dom::IID_IXmlNode, + reinterpret_cast<void**>(created_node.GetAddressOf())); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlNode> text_node_root; + hr = GetTextNodeRoot(index, xml_doc, &text_node_root); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlNode> appended_node; + hr = text_node_root->AppendChild(created_node.Get(), &appended_node); + CheckHR(hr); + return hr; +} + +} // namespace + +ToastNotificationHandler::DesktopNotification::DesktopNotification( + const char* notification_origin, + const char* notification_icon, + const wchar_t* notification_title, + const wchar_t* notification_body, + const wchar_t* notification_display_source, + const char* notification_id) + : origin_url(notification_origin), + icon_url(notification_icon), + title(notification_title), + body(notification_body), + display_source(notification_display_source), + id(notification_id) { +} + + +ToastNotificationHandler::ToastNotificationHandler() { + DVLOG(1) << __FUNCTION__; +} + +ToastNotificationHandler::~ToastNotificationHandler() { + DVLOG(1) << __FUNCTION__; + + if (notifier_ && notification_) + CancelNotification(); +} + +void ToastNotificationHandler::DisplayNotification( + const DesktopNotification& notification) { + DVLOG(1) << __FUNCTION__; + + DCHECK(notifier_.Get() == NULL); + DCHECK(notification_.Get() == NULL); + + mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics> + toast_manager; + + HRESULT hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, + toast_manager.GetAddressOf()); + CheckHR(hr); + + mswr::ComPtr<winxml::Dom::IXmlDocument> toast_xml; + hr = toast_manager->GetTemplateContent( + winui::Notifications::ToastTemplateType_ToastText02, + &toast_xml); + CheckHR(hr); + + if (!toast_xml) + return; + + mswr::ComPtr<winxml::Dom::IXmlElement> document_element; + hr = toast_xml->get_DocumentElement(&document_element); + CheckHR(hr); + + if (!document_element) + return; + + hr = CreateTextNode(toast_xml.Get(), 0, notification.title); + CheckHR(hr); + + hr = CreateTextNode(toast_xml.Get(), 1, notification.body); + CheckHR(hr); + + mswrw::HString duration_attribute_name; + duration_attribute_name.Attach(MakeHString(L"duration")); + mswrw::HString duration_attribute_value; + duration_attribute_value.Attach(MakeHString(L"long")); + + hr = document_element->SetAttribute(duration_attribute_name.Get(), + duration_attribute_value.Get()); + CheckHR(hr); + + // TODO(ananta) + // We should set the image and launch params attribute in the notification + // XNL as described here: http://msdn.microsoft.com/en-us/library/hh465448 + // To set the image we may have to extract the image and specify it in the + // following url form. ms-appx:///images/foo.png + // The launch params as described don't get passed back to us via the + // winapp::Activation::ILaunchActivatedEventArgs argument. Needs to be + // investigated. + mswr::ComPtr<winui::Notifications::IToastNotificationFactory> + toast_notification_factory; + hr = winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_UI_Notifications_ToastNotification, + toast_notification_factory.GetAddressOf()); + CheckHR(hr); + + hr = toast_notification_factory->CreateToastNotification( + toast_xml.Get(), ¬ification_); + CheckHR(hr); + + FilePath chrome_path; + if (!PathService::Get(base::FILE_EXE, &chrome_path)) { + NOTREACHED() << "Failed to get chrome exe path"; + return; + } + string16 appid = delegate_execute::GetAppId(chrome_path); + DVLOG(1) << "Chrome Appid is " << appid.c_str(); + + // TODO(ananta) + // We should probably use BrowserDistribution here to get the product name. + mswrw::HString app_user_model_id; + app_user_model_id.Attach(MakeHString(appid)); + + hr = toast_manager->CreateToastNotifierWithId(app_user_model_id.Get(), + ¬ifier_); + CheckHR(hr); + + hr = notification_->add_Activated( + mswr::Callback<ToastActivationHandler>( + this, &ToastNotificationHandler::OnActivate).Get(), + &activated_token_); + CheckHR(hr); + + hr = notifier_->Show(notification_.Get()); + CheckHR(hr); +} + +void ToastNotificationHandler::CancelNotification() { + DVLOG(1) << __FUNCTION__; + + DCHECK(notifier_); + DCHECK(notification_); + + notifier_->Hide(notification_.Get()); +} + +HRESULT ToastNotificationHandler::OnActivate( + winui::Notifications::IToastNotification* notification, + IInspectable* inspectable) { + // TODO(ananta) + // We should pass back information from the notification like the source url + // etc to ChromeAppView which would enable it to ensure that the + // correct tab in chrome is activated. + DVLOG(1) << __FUNCTION__; + return S_OK; +} diff --git a/win8/metro_driver/toast_notification_handler.h b/win8/metro_driver/toast_notification_handler.h new file mode 100644 index 0000000..f560e34 --- /dev/null +++ b/win8/metro_driver/toast_notification_handler.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_TOAST_NOTIFICATION_HANDLER_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_TOAST_NOTIFICATION_HANDLER_H_ + +#include <windows.ui.notifications.h> + +#include "base/string16.h" + +// Provides functionality to display a metro style toast notification. +class ToastNotificationHandler { + public: + // Holds information about a desktop notification to be displayed. + struct DesktopNotification { + std::string origin_url; + std::string icon_url; + string16 title; + string16 body; + string16 display_source; + std::string id; + + DesktopNotification(const char* notification_origin, + const char* notification_icon, + const wchar_t* notification_title, + const wchar_t* notification_body, + const wchar_t* notification_display_source, + const char* notification_id); + }; + + ToastNotificationHandler(); + ~ToastNotificationHandler(); + + void DisplayNotification(const DesktopNotification& notification); + void CancelNotification(); + + HRESULT OnActivate(winui::Notifications::IToastNotification* notification, + IInspectable* inspectable); + + private: + mswr::ComPtr<winui::Notifications::IToastNotifier> notifier_; + mswr::ComPtr<winui::Notifications::IToastNotification> notification_; + + EventRegistrationToken activated_token_; +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_TOAST_NOTIFICATION_HANDLER_H_ diff --git a/win8/metro_driver/winrt_utils.cc b/win8/metro_driver/winrt_utils.cc new file mode 100644 index 0000000..cfddc5e --- /dev/null +++ b/win8/metro_driver/winrt_utils.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2012 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 "stdafx.h" +#include "winrt_utils.h" + +#include <shlobj.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_comptr.h" + +void CheckHR(HRESULT hr, const char* message) { + if (FAILED(hr)) { + if (message) + PLOG(DFATAL) << message << ", hr = " << std::hex << hr; + else + PLOG(DFATAL) << "COM ERROR" << ", hr = " << std::hex << hr; + } +} + +HSTRING MakeHString(const string16& str) { + HSTRING hstr; + if (FAILED(::WindowsCreateString(str.c_str(), str.size(), &hstr))) { + PLOG(DFATAL) << "Hstring creation failed"; + } + return hstr; +} + +string16 MakeStdWString(HSTRING hstring) { + const wchar_t* str; + UINT32 size = 0; + str = ::WindowsGetStringRawBuffer(hstring, &size); + if (!size) + return string16(); + return string16(str, size); +} + +namespace { + +#define IMPLEMENT_CREATE_PROPERTY(Name, Type) \ +HRESULT Create ## Name ## Property(Type value, \ + winfoundtn::IPropertyValue** prop) { \ + mswr::ComPtr<winfoundtn::IPropertyValueStatics> property_value_statics; \ + HRESULT hr = winrt_utils::CreateActivationFactory( \ + RuntimeClass_Windows_Foundation_PropertyValue, \ + property_value_statics.GetAddressOf()); \ + CheckHR(hr, "Can't create IPropertyValueStatics"); \ + hr = property_value_statics->Create ## Name ## ( \ + value, \ + reinterpret_cast<IInspectable**>(prop)); \ + CheckHR(hr, "Failed to create Property"); \ + return hr; \ +} + +#define COMPARE_ATOMIC_PROPERTY_VALUES(Name, Type) \ + Type lhs_value; \ + hr = lhs->Get ## Name ##(&lhs_value); \ + CheckHR(hr, "Can't get value for lhs"); \ + Type rhs_value; \ + hr = rhs->Get ## Name ##(&rhs_value); \ + CheckHR(hr, "Can't get value for rhs"); \ + if (lhs_value < rhs_value) \ + *result = -1; \ + else if (lhs_value > rhs_value) \ + *result = 1; \ + else \ + *result = 0; \ + hr = S_OK + +} // namespace + +namespace winrt_utils { + +IMPLEMENT_CREATE_PROPERTY(String, HSTRING); +IMPLEMENT_CREATE_PROPERTY(Int16, INT16); +IMPLEMENT_CREATE_PROPERTY(Int32, INT32); +IMPLEMENT_CREATE_PROPERTY(Int64, INT64); +IMPLEMENT_CREATE_PROPERTY(UInt8, UINT8); +IMPLEMENT_CREATE_PROPERTY(UInt16, UINT16); +IMPLEMENT_CREATE_PROPERTY(UInt32, UINT32); +IMPLEMENT_CREATE_PROPERTY(UInt64, UINT64); + +HRESULT CompareProperties(winfoundtn::IPropertyValue* lhs, + winfoundtn::IPropertyValue* rhs, + INT32* result) { + if (result == nullptr) { + PLOG(DFATAL) << "Invalid argument to CompareProperties."; + return E_INVALIDARG; + } + + if (lhs == rhs) { + *result = 0; + return S_OK; + } + + winfoundtn::PropertyType lhs_property_type; + HRESULT hr = lhs->get_Type(&lhs_property_type); + if (FAILED(hr)) { + PLOG(DFATAL) << "Can't get property type for lhs, hr=" << std::hex << hr; + } + + winfoundtn::PropertyType rhs_property_type; + hr = rhs->get_Type(&rhs_property_type); + CheckHR(hr, "Can't get property type for rhs"); + + if (lhs_property_type != rhs_property_type) + return E_INVALIDARG; + + switch (lhs_property_type) { + case winfoundtn::PropertyType::PropertyType_String: { + mswrw::HString lhs_string; + hr = lhs->GetString(lhs_string.GetAddressOf()); + CheckHR(hr, "Can't get string for lhs"); + + mswrw::HString rhs_string; + hr = rhs->GetString(rhs_string.GetAddressOf()); + CheckHR(hr, "Can't get string for rhs"); + + hr = WindowsCompareStringOrdinal( + lhs_string.Get(), rhs_string.Get(), result); + break; + } + case winfoundtn::PropertyType::PropertyType_Char16: { + COMPARE_ATOMIC_PROPERTY_VALUES(Char16, wchar_t); + break; + } + case winfoundtn::PropertyType::PropertyType_Double: { + COMPARE_ATOMIC_PROPERTY_VALUES(Double, double); + break; + } + case winfoundtn::PropertyType::PropertyType_Int16: { + COMPARE_ATOMIC_PROPERTY_VALUES(Int16, INT16); + break; + } + case winfoundtn::PropertyType::PropertyType_Int32: { + COMPARE_ATOMIC_PROPERTY_VALUES(Int32, INT32); + break; + } + case winfoundtn::PropertyType::PropertyType_Int64: { + COMPARE_ATOMIC_PROPERTY_VALUES(Int64, INT64); + break; + } + case winfoundtn::PropertyType::PropertyType_UInt8: { + COMPARE_ATOMIC_PROPERTY_VALUES(UInt8, UINT8); + break; + } + case winfoundtn::PropertyType::PropertyType_UInt16: { + COMPARE_ATOMIC_PROPERTY_VALUES(UInt16, UINT16); + break; + } + case winfoundtn::PropertyType::PropertyType_UInt32: { + COMPARE_ATOMIC_PROPERTY_VALUES(UInt32, UINT32); + break; + } + case winfoundtn::PropertyType::PropertyType_UInt64: { + COMPARE_ATOMIC_PROPERTY_VALUES(UInt64, UINT64); + break; + } + default: { + hr = E_NOTIMPL; + } + } + return hr; +} + +bool GetArgumentsFromShortcut(const FilePath& shortcut, + string16* arguments) { + HRESULT result; + base::win::ScopedComPtr<IShellLink> i_shell_link; + bool is_resolved = false; + + + base::win::ScopedCOMInitializer sta_com_initializer; + + // Get pointer to the IShellLink interface + result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER); + if (SUCCEEDED(result)) { + base::win::ScopedComPtr<IPersistFile> persist; + // Query IShellLink for the IPersistFile interface + result = persist.QueryFrom(i_shell_link); + if (SUCCEEDED(result)) { + WCHAR temp_arguments[MAX_PATH]; + // Load the shell link + result = persist->Load(shortcut.value().c_str(), STGM_READ); + if (SUCCEEDED(result)) { + result = i_shell_link->GetArguments(temp_arguments, MAX_PATH); + *arguments = temp_arguments; + is_resolved = true; + } + } + } + + return is_resolved; +} + +string16 ReadArgumentsFromPinnedTaskbarShortcut() { + wchar_t path_buffer[MAX_PATH] = {}; + + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, + SHGFP_TYPE_CURRENT, path_buffer))) { + FilePath shortcut(path_buffer); + shortcut = shortcut.Append( + L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); + + // TODO(robertshield): Get this stuff from BrowserDistribution. +#if defined(GOOGLE_CHROME_BUILD) + shortcut = shortcut.Append(L"Google Chrome.lnk"); +#else + shortcut = shortcut.Append(L"Chromium.lnk"); +#endif + + string16 arguments; + if (GetArgumentsFromShortcut(shortcut, &arguments)) { + return arguments; + } + } + + return L""; +} + +} // namespace winrt_utils diff --git a/win8/metro_driver/winrt_utils.h b/win8/metro_driver/winrt_utils.h new file mode 100644 index 0000000..28f1ead --- /dev/null +++ b/win8/metro_driver/winrt_utils.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_UI_METRO_DRIVER_WINRT_UTILS_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_WINRT_UTILS_H_ + +#include <string> + +#include <roapi.h> +#include <windows.applicationmodel.core.h> + +#include "base/string16.h" + +void CheckHR(HRESULT hr, const char* str = nullptr); + +HSTRING MakeHString(const string16& str); + +string16 MakeStdWString(HSTRING hstring); + +namespace winrt_utils { + +template<unsigned int size, typename T> +HRESULT CreateActivationFactory(wchar_t const (&class_name)[size], T** object) { + mswrw::HStringReference ref_class_name(class_name); + return winfoundtn::GetActivationFactory(ref_class_name.Get(), object); +} + +#define DECLARE_CREATE_PROPERTY(Name, Type) \ +HRESULT Create ## Name ## Property( \ + Type value, \ + winfoundtn::IPropertyValue** prop); + +DECLARE_CREATE_PROPERTY(String, HSTRING); +DECLARE_CREATE_PROPERTY(Int16, INT16); +DECLARE_CREATE_PROPERTY(Int32, INT32); +DECLARE_CREATE_PROPERTY(Int64, INT64); +DECLARE_CREATE_PROPERTY(UInt8, UINT8); +DECLARE_CREATE_PROPERTY(UInt16, UINT16); +DECLARE_CREATE_PROPERTY(UInt32, UINT32); +DECLARE_CREATE_PROPERTY(UInt64, UINT64); + +// Compares |lhs| with |rhs| and return the |result| as +// WindowsCompareStringOrdinal would do, i.e., +// -1 if |lhs| is less than |rhs|, 0 if they are equal, and +// +1 if |lhs| is greater than |rhs|. +HRESULT CompareProperties( + winfoundtn::IPropertyValue* lhs, winfoundtn::IPropertyValue* rhs, + INT32* result); + +// Looks for a pinned taskbar shortcut in the current user's profile. If it +// finds one, will return any arguments that have been appended to the +// shortcut's command line. This is intended for scenarios where those shortcut +// parameters are ordinarily ignored (i.e. metro apps on win8). Returns an +// empty string on failure. +string16 ReadArgumentsFromPinnedTaskbarShortcut(); + +} // namespace winrt_utils + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_WINRT_UTILS_H_ diff --git a/win8/metro_driver/winrt_utils_unittest.cc b/win8/metro_driver/winrt_utils_unittest.cc new file mode 100644 index 0000000..9ae869b --- /dev/null +++ b/win8/metro_driver/winrt_utils_unittest.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2012 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 "stdafx.h" + +#include "winrt_utils.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +template <typename Type> +static HRESULT CreateProperty(Type value, winfoundtn::IPropertyValue** prop) { + return E_NOTIMPL; +} + +template <> +static HRESULT CreateProperty<const wchar_t*>( + const wchar_t* value, winfoundtn::IPropertyValue** prop) { + mswrw::HString string_value; + string_value.Attach(MakeHString(value)); + return winrt_utils::CreateStringProperty(string_value.Get(), prop); +} + +template <> +static HRESULT CreateProperty<INT16>(INT16 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateInt16Property(value, prop); +} + +template <> +static HRESULT CreateProperty<INT32>(INT32 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateInt32Property(value, prop); +} + +template <> +static HRESULT CreateProperty<INT64>(INT64 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateInt64Property(value, prop); +} + +template <> +static HRESULT CreateProperty<UINT8>(UINT8 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateUInt8Property(value, prop); +} + +template <> +static HRESULT CreateProperty<UINT16>(UINT16 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateUInt16Property(value, prop); +} + +template <> +static HRESULT CreateProperty<UINT32>(UINT32 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateUInt32Property(value, prop); +} + +template <> +static HRESULT CreateProperty<UINT64>(UINT64 value, + winfoundtn::IPropertyValue** prop) { + return winrt_utils::CreateUInt64Property(value, prop); +} + +template<typename Type> +void TestCompareProperties(Type value1, Type value2) { + mswr::ComPtr<winfoundtn::IPropertyValue> property_1; + HRESULT hr = CreateProperty<Type>(value1, property_1.GetAddressOf()); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't create Property value 1"; + + mswr::ComPtr<winfoundtn::IPropertyValue> other_property_1; + hr = CreateProperty<Type>(value1, other_property_1.GetAddressOf()); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't create another Property value 1"; + + mswr::ComPtr<winfoundtn::IPropertyValue> property_2; + hr = CreateProperty<Type>(value2, property_2.GetAddressOf()); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't create Property value 2"; + + INT32 result = 42; + hr = winrt_utils::CompareProperties( + property_1.Get(), property_1.Get(), &result); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't compare property_1 to itself"; + EXPECT_EQ(0, result) << "Bad result value while comparing same property"; + + hr = winrt_utils::CompareProperties( + property_1.Get(), other_property_1.Get(), &result); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't compare property_1 to other_property_1"; + EXPECT_EQ(0, result) << "Bad result while comparing equal values"; + + hr = winrt_utils::CompareProperties( + property_1.Get(), property_2.Get(), &result); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't compare property_1 to property_2"; + EXPECT_EQ(-1, result) << "Bad result while comparing values for less than"; + + hr = winrt_utils::CompareProperties( + property_2.Get(), property_1.Get(), &result); + ASSERT_TRUE(SUCCEEDED(hr)) << "Can't compare property_1 to property_2"; + EXPECT_EQ(1, result) << "Bad result value while comparing for greater than"; +} + +TEST(PropertyValueCompareTest, CompareProperties) { + TestCompareProperties<INT16>(42, 43); + TestCompareProperties<INT32>(42, 43); + TestCompareProperties<INT64>(42, 43); + TestCompareProperties<UINT8>(42, 43); + TestCompareProperties<UINT16>(42, 43); + TestCompareProperties<UINT32>(42, 43); + TestCompareProperties<UINT64>(42, 43); + TestCompareProperties<const wchar_t*>(L"abc", L"bcd"); +} + +} // namespace diff --git a/win8/win8.gyp b/win8/win8.gyp new file mode 100644 index 0000000..bf64b1f --- /dev/null +++ b/win8/win8.gyp @@ -0,0 +1,33 @@ +# Copyright (c) 2012 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. +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../build/win_precompile.gypi', + ], + 'targets': [ + { + 'target_name': 'check_sdk_patch', + 'type': 'none', + 'variables': { + 'check_sdk_script': '<(DEPTH)/chrome/tools/build/win/check_sdk_patch.py', + }, + 'actions': [ + { + 'action_name': 'check_sdk_patch_action', + 'inputs': [ + '<@(windows_sdk_path)/Include/winrt/asyncinfo.h', + ], + 'outputs': [ + # This keeps the ninja build happy. + 'dummy', + ], + 'action': ['python', '<(check_sdk_script)', '<@(windows_sdk_path)'], + }, + ], + }, + ], +}
\ No newline at end of file |