diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/win8 | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/win8')
86 files changed, 12127 insertions, 3 deletions
diff --git a/chromium/win8/OWNERS b/chromium/win8/OWNERS new file mode 100644 index 00000000000..4c371fc00c7 --- /dev/null +++ b/chromium/win8/OWNERS @@ -0,0 +1,5 @@ +ananta@chromium.org +cpu@chromium.org +grt@chromium.org +mad@chromium.org +robertshield@chromium.org diff --git a/chromium/win8/delegate_execute/chrome_util.cc b/chromium/win8/delegate_execute/chrome_util.cc new file mode 100644 index 00000000000..3695b3331fb --- /dev/null +++ b/chromium/win8/delegate_execute/chrome_util.cc @@ -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. + +#include "win8/delegate_execute/chrome_util.h" + +#include <windows.h> +#include <atlbase.h> +#include <shlobj.h> + +#include <algorithm> +#include <limits> +#include <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/md5.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/process/process_handle.h" +#include "base/strings/string_util.h" +#include "base/strings/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 "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/util_constants.h" +#include "google_update/google_update_idl.h" + +namespace { + +#if defined(GOOGLE_CHROME_BUILD) + +// TODO(grt): These constants live in installer_util. Consider moving them +// into common_constants to allow for reuse. +const base::FilePath::CharType kNewChromeExe[] = + FILE_PATH_LITERAL("new_chrome.exe"); +const wchar_t kRenameCommandValue[] = L"cmd"; +const wchar_t kRegPathChromeClientBase[] = + L"Software\\Google\\Update\\Clients\\"; + +// 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 base::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 base::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 base::FilePath& chrome_exe) { + base::FilePath new_chrome_exe(chrome_exe.DirName().Append(kNewChromeExe)); + return base::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; + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + string16 reg_path_chrome_client = kRegPathChromeClientBase; + reg_path_chrome_client.append(dist->GetAppGuid()); + base::win::RegKey key(root, reg_path_chrome_client.c_str(), KEY_QUERY_VALUE); + + return key.ReadValue(kRenameCommandValue, update_command) == ERROR_SUCCESS; +} + +#endif // GOOGLE_CHROME_BUILD + +} // namespace + +namespace delegate_execute { + +void UpdateChromeIfNeeded(const base::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::win::ScopedHandle process_handle; + + if (InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())) { + // 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()); + } + } + } 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; + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + hr = process_launcher->LaunchCmdElevated( + dist->GetAppGuid().c_str(), kRenameCommandValue, + GetCurrentProcessId(), &handle); + if (FAILED(hr)) { + AtlTrace("%hs. Failed to launch command to finalize update; " + "hr=0x%X.\n", __FUNCTION__, hr); + } else { + process_handle.Set(reinterpret_cast<base::ProcessHandle>(handle)); + } + } + } + + // Wait for the update to complete and report the results. + if (process_handle.IsValid()) { + int exit_code = 0; + // WaitForExitCode will close the handle in all cases. + if (!base::WaitForExitCode(process_handle.Take(), &exit_code)) { + AtlTrace("%hs. Failed to get result when finalizing update.\n", + __FUNCTION__); + } else if (exit_code != installer::RENAME_SUCCESSFUL) { + AtlTrace("%hs. Failed to finalize update with exit code %d.\n", + __FUNCTION__, exit_code); + } else { + AtlTrace("%hs. Finalized pending update.\n", __FUNCTION__); + } + } +#endif +} + +} // delegate_execute diff --git a/chromium/win8/delegate_execute/chrome_util.h b/chromium/win8/delegate_execute/chrome_util.h new file mode 100644 index 00000000000..6310c25c7b1 --- /dev/null +++ b/chromium/win8/delegate_execute/chrome_util.h @@ -0,0 +1,21 @@ +// 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/strings/string16.h" + +namespace base { +class FilePath; +} + +namespace delegate_execute { + +// Finalizes a previously updated installation. +void UpdateChromeIfNeeded(const base::FilePath& chrome_exe); + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_CHROME_UTIL_H_ diff --git a/chromium/win8/delegate_execute/command_execute_impl.cc b/chromium/win8/delegate_execute/command_execute_impl.cc new file mode 100644 index 00000000000..cb68f54e573 --- /dev/null +++ b/chromium/win8/delegate_execute/command_execute_impl.cc @@ -0,0 +1,571 @@ +// 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 <shlguid.h> + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "base/process/process_handle.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/message_window.h" +#include "base/win/registry.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/win_util.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/shell_util.h" +#include "chrome/installer/util/util_constants.h" +#include "ui/base/clipboard/clipboard_util_win.h" +#include "win8/delegate_execute/chrome_util.h" +#include "win8/delegate_execute/delegate_execute_util.h" +#include "win8/viewer/metro_viewer_constants.h" + +namespace { + +// Helper function to retrieve the url from IShellItem interface passed in. +// Returns S_OK on success. +HRESULT GetUrlFromShellItem(IShellItem* shell_item, string16* url) { + DCHECK(shell_item); + DCHECK(url); + // First attempt to get the url from the underlying IDataObject if any. This + // ensures that we get the full url, i.e. including the anchor. + // If we fail to get the underlying IDataObject we retrieve the url via the + // IShellItem::GetDisplayName function. + CComPtr<IDataObject> object; + HRESULT hr = shell_item->BindToHandler(NULL, + BHID_DataObject, + IID_IDataObject, + reinterpret_cast<void**>(&object)); + if (SUCCEEDED(hr)) { + DCHECK(object); + if (ui::ClipboardUtil::GetPlainText(object, url)) + return S_OK; + } + + 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 hr; + } + + *url = static_cast<const wchar_t*>(name); + AtlTrace("Retrieved url from display name %ls\n", url->c_str()); + return S_OK; +} + +#if defined(USE_AURA) +bool LaunchChromeBrowserProcess() { + base::FilePath delegate_exe_path; + if (!PathService::Get(base::FILE_EXE, &delegate_exe_path)) + return false; + + // First try and go up a level to find chrome.exe. + base::FilePath chrome_exe_path = + delegate_exe_path.DirName() + .DirName() + .Append(chrome::kBrowserProcessExecutableName); + if (!base::PathExists(chrome_exe_path)) { + // Try looking in the current directory if we couldn't find it one up in + // order to support developer installs. + chrome_exe_path = + delegate_exe_path.DirName() + .Append(chrome::kBrowserProcessExecutableName); + } + + if (!base::PathExists(chrome_exe_path)) { + AtlTrace("Could not locate chrome.exe at: %ls\n", + chrome_exe_path.value().c_str()); + return false; + } + + CommandLine cl(chrome_exe_path); + + // Prevent a Chrome window from showing up on the desktop. + cl.AppendSwitch(switches::kSilentLaunch); + + // Tell Chrome to connect to the Metro viewer process. + cl.AppendSwitch(switches::kViewerConnect); + + base::LaunchOptions launch_options; + launch_options.start_hidden = true; + + return base::LaunchProcess(cl, launch_options, NULL); +} +#endif // defined(USE_AURA) + +} // namespace + +bool CommandExecuteImpl::path_provider_initialized_ = false; + +// 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) else we return what GetLaunchMode() tells us, which is: +// i) if the command line --force-xxx is present return that +// ii) if the registry 'launch_mode' exists return that +// iii) if IsTouchEnabledDevice() is true return AHE_IMMERSIVE +// iv) else return AHE_DESKTOP +// 6- If we returned AHE_IMMERSIVE in step 5 windows might not call us back +// and simply activate chrome in metro by itself, however in some cases +// it might proceed at step 7. +// As far as we know if we return AHE_DESKTOP then step 7 always happens. +// 7- Windows calls CommandExecuteImpl::Execute() +// Here we call GetLaunchMode() which returns the cached answer +// computed at step 5c. which can be: +// a) ECHUIM_DESKTOP then we call LaunchDesktopChrome() that calls +// ::CreateProcess and we exit at this point even on failure. +// b) else we call one of the IApplicationActivationManager activation +// functions depending on the parameters passed in step 4. +// c) If the activation returns E_APPLICATION_NOT_REGISTERED, then we fall +// back to launching chrome on the desktop via LaunchDestopChrome(). +// +// Note that if a command line --force-xxx is present we write that launch mode +// in the registry so next time the logic reaches 5c-ii it will use the same +// mode again. +// +// Also note that if we are not the default browser and IsTouchEnabledDevice() +// returns true, launching chrome can go all the way to 7c, which might be +// a slow way to start chrome. +// +CommandExecuteImpl::CommandExecuteImpl() + : parameters_(CommandLine::NO_PROGRAM), + launch_scheme_(INTERNET_SCHEME_DEFAULT), + integrity_level_(base::INTEGRITY_UNKNOWN), + 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. We can be created multiplie times in a single instance + // however so make sure we do this only once. + if (!path_provider_initialized_) { + chrome::RegisterPathProvider(); + path_provider_initialized_ = true; + } +} + +// 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); + parameters_ = delegate_execute::CommandLineFromParameters(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; + } + + if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_F11)) { + AtlTrace("Using Shift-F11 debug hook, returning AHE_IMMERSIVE\n"); + *pahe = AHE_IMMERSIVE; + +#if defined(USE_AURA) + // Launch the chrome browser process that metro chrome will connect to. + LaunchChromeBrowserProcess(); +#endif + + return S_OK; + } + + if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_F12)) { + AtlTrace("Using Shift-F12 debug hook, returning AHE_DESKTOP\n"); + *pahe = AHE_DESKTOP; + return S_OK; + } + + base::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; + } + + bool decision_made = false; + + // New Aura/Ash world we don't want to go throgh FindWindow path + // and instead take decision based on launch mode. +#if !defined(USE_AURA) + HWND chrome_window = base::win::MessageWindow::FindWindow( + user_data_dir.value()); + 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; + } + + decision_made = true; + } +#endif + + if (!decision_made) { + EC_HOST_UI_MODE mode = GetLaunchMode(); + *pahe = (mode == ECHUIM_DESKTOP) ? AHE_DESKTOP : AHE_IMMERSIVE; + } + +#if defined(USE_AURA) + if (*pahe == AHE_IMMERSIVE && verb_ != win8::kMetroViewerConnectVerb) + LaunchChromeBrowserProcess(); +#endif + + 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; + } + + BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); + bool is_per_user_install = InstallUtil::IsPerUserInstall( + chrome_exe_.value().c_str()); + string16 app_id = ShellUtil::GetBrowserModelId( + distribution, is_per_user_install); + + DWORD pid = 0; + if (launch_scheme_ == INTERNET_SCHEME_FILE && + display_name_.find(installer::kChromeExe) != string16::npos) { + AtlTrace("Activating for file\n"); + hr = activation_manager->ActivateApplication(app_id.c_str(), + verb_.c_str(), + AO_NONE, + &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(base::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. + base::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 (!base::PathExists(*chrome_exe)) { + *chrome_exe = dir_exe.Append(chrome::kBrowserProcessExecutableName); + if (!base::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; + } + + hr = GetUrlFromShellItem(shell_item, display_name); + if (FAILED(hr)) { + AtlTrace("Failed to get url. Error 0x%x\n", hr); + return false; + } + + AtlTrace("url [%ls]\n", display_name->c_str()); + + 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(display_name->c_str(), 0, 0, &components)) { + AtlTrace("Failed to crack url %ls\n", display_name->c_str()); + return false; + } + + AtlTrace("Launch scheme is [%ls] (%d)\n", scheme_name, components.nScheme); + *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(installer::kChromeExe) != string16::npos) + display_name.clear(); + break; + + default: + break; + } + + CommandLine chrome( + delegate_execute::MakeChromeCommandLine(chrome_exe_, parameters_, + display_name)); + string16 command_line(chrome.GetCommandLineString()); + + AtlTrace("Formatted command line is %ls\n", command_line.c_str()); + + PROCESS_INFORMATION temp_process_info = {}; + BOOL ret = CreateProcess(chrome_exe_.value().c_str(), + const_cast<LPWSTR>(command_line.c_str()), + NULL, NULL, FALSE, 0, NULL, NULL, &start_info_, + &temp_process_info); + if (ret) { + base::win::ScopedProcessInformation proc_info(temp_process_info); + AtlTrace("Process id is %d\n", proc_info.process_id()); + AllowSetForegroundWindow(proc_info.process_id()); + } else { + AtlTrace("Process launch failed, error %d\n", ::GetLastError()); + } + + return S_OK; +} + +EC_HOST_UI_MODE CommandExecuteImpl::GetLaunchMode() { + // See the header file for an explanation of the mode selection logic. + static bool launch_mode_determined = false; + static EC_HOST_UI_MODE launch_mode = ECHUIM_DESKTOP; + + const char* modes[] = { "Desktop", "Immersive", "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_.HasSwitch(switches::kForceImmersive)) { + launch_mode = ECHUIM_IMMERSIVE; + launch_mode_determined = true; + parameters_ = CommandLine(CommandLine::NO_PROGRAM); + } else if (parameters_.HasSwitch(switches::kForceDesktop)) { + launch_mode = ECHUIM_DESKTOP; + launch_mode_determined = true; + parameters_ = CommandLine(CommandLine::NO_PROGRAM); + } + + base::win::RegKey reg_key; + LONG key_result = reg_key.Create(HKEY_CURRENT_USER, + chrome::kMetroRegistryPath, + KEY_ALL_ACCESS); + if (key_result != ERROR_SUCCESS) { + AtlTrace("Failed to open HKCU %ls key, error 0x%x\n", + chrome::kMetroRegistryPath, + key_result); + if (!launch_mode_determined) { + launch_mode = ECHUIM_DESKTOP; + launch_mode_determined = true; + } + return launch_mode; + } + + if (launch_mode_determined) { + AtlTrace("Launch mode forced by cmdline to %s\n", modes[launch_mode]); + reg_key.WriteValue(chrome::kLaunchModeValue, + static_cast<DWORD>(launch_mode)); + return launch_mode; + } + + DWORD reg_value; + if (reg_key.ReadValueDW(chrome::kLaunchModeValue, + ®_value) != ERROR_SUCCESS) { + launch_mode = ECHUIM_DESKTOP; + AtlTrace("Launch mode forced by heuristics to %s\n", modes[launch_mode]); + } else if (reg_value >= ECHUIM_SYSTEM_LAUNCHER) { + AtlTrace("Invalid registry launch mode value %u\n", reg_value); + launch_mode = ECHUIM_DESKTOP; + } else { + launch_mode = static_cast<EC_HOST_UI_MODE>(reg_value); + AtlTrace("Launch mode forced by registry to %s\n", modes[launch_mode]); + } + + launch_mode_determined = true; + return launch_mode; +} diff --git a/chromium/win8/delegate_execute/command_execute_impl.h b/chromium/win8/delegate_execute/command_execute_impl.h new file mode 100644 index 00000000000..7b1490b21d1 --- /dev/null +++ b/chromium/win8/delegate_execute/command_execute_impl.h @@ -0,0 +1,108 @@ +// 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/command_line.h" +#include "base/files/file_path.h" +#include "base/process/process_handle.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. +// The CLSID here is a dummy CLSID not used for anything, since we register +// the class with a dynamic CLSID. However, a static CLSID is necessary +// so that we can force at least one entry into ATL's object map (it will +// treat a 0-element object map as an initialization failure case). +class ATL_NO_VTABLE DECLSPEC_UUID("071BB5F2-85A4-424F-BFE7-5F1609BE4C2C") + CommandExecuteImpl + : 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(base::FilePath* chrome_exe); + + static bool path_provider_initialized_; + + 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_; + CommandLine parameters_; + base::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/chromium/win8/delegate_execute/command_execute_impl.rgs b/chromium/win8/delegate_execute/command_execute_impl.rgs new file mode 100644 index 00000000000..4f1aba3de75 --- /dev/null +++ b/chromium/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/chromium/win8/delegate_execute/crash_server_init.cc b/chromium/win8/delegate_execute/crash_server_init.cc new file mode 100644 index 00000000000..76893164294 --- /dev/null +++ b/chromium/win8/delegate_execute/crash_server_init.cc @@ -0,0 +1,96 @@ +// 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/crash_server_init.h" + +#include <shlobj.h> +#include <windows.h> + +#include <cwchar> + +#include "base/file_version_info.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/win_util.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" + +const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; +const wchar_t kSystemPrincipalSid[] = L"S-1-5-18"; + +const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules | // Get unloaded modules when available. + MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack. + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace { + +bool IsRunningSystemInstall() { + wchar_t exe_path[MAX_PATH * 2] = {0}; + GetModuleFileName(reinterpret_cast<HMODULE>(&__ImageBase), + exe_path, + _countof(exe_path)); + + bool is_system = false; + + wchar_t program_files_path[MAX_PATH] = {0}; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, + SHGFP_TYPE_CURRENT, program_files_path))) { + if (wcsstr(exe_path, program_files_path) == exe_path) { + is_system = true; + } + } + + return is_system; +} + +google_breakpad::CustomClientInfo* GetCustomInfo() { + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + + static google_breakpad::CustomInfoEntry ver_entry( + L"ver", version_info->file_version().c_str()); + static google_breakpad::CustomInfoEntry prod_entry(L"prod", L"Chrome"); + static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32"); + static google_breakpad::CustomInfoEntry type_entry(L"ptype", + L"delegate_execute"); + static google_breakpad::CustomInfoEntry entries[] = { + ver_entry, prod_entry, plat_entry, type_entry }; + static google_breakpad::CustomClientInfo custom_info = { + entries, ARRAYSIZE(entries) }; + return &custom_info; +} + +} // namespace + +namespace delegate_execute { + +scoped_ptr<google_breakpad::ExceptionHandler> InitializeCrashReporting() { + wchar_t temp_path[MAX_PATH + 1] = {0}; + DWORD path_len = ::GetTempPath(MAX_PATH, temp_path); + + string16 pipe_name; + pipe_name = kGoogleUpdatePipeName; + if (IsRunningSystemInstall()) { + pipe_name += kSystemPrincipalSid; + } else { + string16 user_sid; + if (base::win::GetUserSidString(&user_sid)) { + pipe_name += user_sid; + } else { + // We don't think we're a system install, but we couldn't get the + // user SID. Try connecting to the system-level crash service as a + // last ditch effort. + pipe_name += kSystemPrincipalSid; + } + } + + return scoped_ptr<google_breakpad::ExceptionHandler>( + new google_breakpad::ExceptionHandler( + temp_path, NULL, NULL, NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL, kLargerDumpType, + pipe_name.c_str(), GetCustomInfo())); +} + +} // namespace delegate_execute diff --git a/chromium/win8/delegate_execute/crash_server_init.h b/chromium/win8/delegate_execute/crash_server_init.h new file mode 100644 index 00000000000..4173ef5a81c --- /dev/null +++ b/chromium/win8/delegate_execute/crash_server_init.h @@ -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. + +#ifndef WIN8_DELEGATE_EXECUTE_CRASH_SERVER_INIT_H_ +#define WIN8_DELEGATE_EXECUTE_CRASH_SERVER_INIT_H_ + +#include "base/memory/scoped_ptr.h" + +namespace google_breakpad { +class ExceptionHandler; +} + +namespace delegate_execute { + +// Initializes breakpad crash reporting and returns a pointer to a newly +// constructed ExceptionHandler object. It is the responsibility of the caller +// to delete this object which will shut down the crash reporting machinery. +scoped_ptr<google_breakpad::ExceptionHandler> InitializeCrashReporting(); + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_CRASH_SERVER_INIT_H_ diff --git a/chromium/win8/delegate_execute/delegate_execute.cc b/chromium/win8/delegate_execute/delegate_execute.cc new file mode 100644 index 00000000000..ddb9883daa2 --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute.cc @@ -0,0 +1,165 @@ +// 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 "build/intsafe_workaround.h" + +#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/memory/scoped_ptr.h" +#include "base/process/kill.h" +#include "base/strings/string16.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_handle.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/installer/util/browser_distribution.h" +#include "win8/delegate_execute/command_execute_impl.h" +#include "win8/delegate_execute/crash_server_init.h" +#include "win8/delegate_execute/delegate_execute_operation.h" +#include "win8/delegate_execute/resource.h" + +using namespace ATL; + +// Usually classes derived from CAtlExeModuleT, or other types of ATL +// COM module classes statically define their CLSID at compile time through +// the use of various macros, and ATL internals takes care of creating the +// class objects and registering them. However, we need to register the same +// object with different CLSIDs depending on a runtime setting, so we handle +// that logic here, before the main ATL message loop runs. +class DelegateExecuteModule + : public ATL::CAtlExeModuleT< DelegateExecuteModule > { + public : + typedef ATL::CAtlExeModuleT<DelegateExecuteModule> ParentClass; + typedef CComObject<CommandExecuteImpl> ImplType; + + DelegateExecuteModule() + : registration_token_(0) { + } + + HRESULT PreMessageLoop(int nShowCmd) { + HRESULT hr = S_OK; + string16 clsid_string; + GUID clsid; + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + if (!dist->GetCommandExecuteImplClsid(&clsid_string)) + return E_FAIL; + hr = ::CLSIDFromString(clsid_string.c_str(), &clsid); + if (FAILED(hr)) + return hr; + + // We use the same class creation logic as ATL itself. See + // _ATL_OBJMAP_ENTRY::RegisterClassObject() in atlbase.h + hr = ImplType::_ClassFactoryCreatorClass::CreateInstance( + ImplType::_CreatorClass::CreateInstance, IID_IUnknown, + instance_.ReceiveVoid()); + if (FAILED(hr)) + return hr; + hr = ::CoRegisterClassObject(clsid, instance_, CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED, ®istration_token_); + if (FAILED(hr)) + return hr; + + return ParentClass::PreMessageLoop(nShowCmd); + } + + HRESULT PostMessageLoop() { + if (registration_token_ != 0) { + ::CoRevokeClassObject(registration_token_); + registration_token_ = 0; + } + + instance_.Release(); + + return ParentClass::PostMessageLoop(); + } + + private: + base::win::ScopedComPtr<IUnknown> instance_; + DWORD registration_token_; +}; + +DelegateExecuteModule _AtlModule; + +using delegate_execute::DelegateExecuteOperation; +using base::win::ScopedHandle; + +int RelaunchChrome(const DelegateExecuteOperation& operation) { + AtlTrace("Relaunching [%ls] with flags [%s]\n", + operation.mutex().c_str(), operation.relaunch_flags()); + ScopedHandle mutex(OpenMutexW(SYNCHRONIZE, FALSE, operation.mutex().c_str())); + if (mutex.IsValid()) { + const int kWaitSeconds = 5; + DWORD result = ::WaitForSingleObject(mutex, kWaitSeconds * 1000); + if (result == WAIT_ABANDONED) { + // This is the normal case. Chrome exits and windows marks the mutex as + // abandoned. + } else if (result == WAIT_OBJECT_0) { + // This is unexpected. Check if somebody is not closing the mutex on + // RelaunchChromehelper, the mutex should not be closed. + AtlTrace("Unexpected release of the relaunch mutex!!\n"); + } else if (result == WAIT_TIMEOUT) { + // This could mean that Chrome is hung. Proceed to exterminate. + DWORD pid = operation.GetParentPid(); + AtlTrace("%ds timeout. Killing Chrome %d\n", kWaitSeconds, pid); + base::KillProcessById(pid, 0, false); + } else { + AtlTrace("Failed to wait for relaunch mutex, result is 0x%x\n", result); + } + } else { + // It is possible that chrome exits so fast that the mutex is not there. + AtlTrace("No relaunch mutex found\n"); + } + + base::win::ScopedCOMInitializer com_initializer; + + string16 relaunch_flags(operation.relaunch_flags()); + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_FLAG_LOG_USAGE; + sei.nShow = SW_SHOWNORMAL; + sei.lpFile = operation.shortcut().value().c_str(); + sei.lpParameters = relaunch_flags.c_str(); + + AtlTrace(L"Relaunching Chrome via shortcut [%ls]\n", sei.lpFile); + + if (!::ShellExecuteExW(&sei)) { + int error = HRESULT_FROM_WIN32(::GetLastError()); + AtlTrace("ShellExecute returned 0x%08X\n", error); + return error; + } + return S_OK; +} + +extern "C" int WINAPI _tWinMain(HINSTANCE , HINSTANCE, LPTSTR, int nShowCmd) { + scoped_ptr<google_breakpad::ExceptionHandler> breakpad = + delegate_execute::InitializeCrashReporting(); + + base::AtExitManager exit_manager; + AtlTrace("delegate_execute enter\n"); + + CommandLine::Init(0, NULL); + HRESULT ret_code = E_UNEXPECTED; + DelegateExecuteOperation operation; + if (operation.Init(CommandLine::ForCurrentProcess())) { + switch (operation.operation_type()) { + case DelegateExecuteOperation::DELEGATE_EXECUTE: + ret_code = _AtlModule.WinMain(nShowCmd); + break; + case DelegateExecuteOperation::RELAUNCH_CHROME: + ret_code = RelaunchChrome(operation); + break; + default: + NOTREACHED(); + } + } + AtlTrace("delegate_execute exit, code = %d\n", ret_code); + return ret_code; +} diff --git a/chromium/win8/delegate_execute/delegate_execute.gyp b/chromium/win8/delegate_execute/delegate_execute.gyp index 7461484cf38..935534f0a84 100644 --- a/chromium/win8/delegate_execute/delegate_execute.gyp +++ b/chromium/win8/delegate_execute/delegate_execute.gyp @@ -42,7 +42,9 @@ '../../base/base.gyp:base', '../../breakpad/breakpad.gyp:breakpad_handler', '../../chrome/chrome.gyp:installer_util', + '../../content/content.gyp:content_common', '../../google_update/google_update.gyp:google_update', + '../../ui/gfx/gfx.gyp:gfx', '../../ui/ui.gyp:ui', '../../win8/win8.gyp:check_sdk_patch', 'delegate_execute_version_resources', @@ -69,6 +71,13 @@ 'SubSystem': '2', # Set /SUBSYSTEM:WINDOWS }, }, + 'conditions': [ + ['use_aura==1', { + 'dependencies': [ + '../win8.gyp:metro_viewer_constants', + ], + }], + ], }, { 'target_name': 'delegate_execute_unittests', diff --git a/chromium/win8/delegate_execute/delegate_execute.rc b/chromium/win8/delegate_execute/delegate_execute.rc new file mode 100644 index 00000000000..74a3cc47b62 --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute.rc @@ -0,0 +1,60 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +#include "verrsrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "#include ""verrsrc.h""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +IDR_COMMANDEXECUTEIMPL REGISTRY "command_execute_impl.rgs" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_PROJNAME "DelegateExecute" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/chromium/win8/delegate_execute/delegate_execute_exe.ver b/chromium/win8/delegate_execute/delegate_execute_exe.ver new file mode 100644 index 00000000000..fb7fb127e6f --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_exe.ver @@ -0,0 +1,2 @@ +INTERNAL_NAME=delegate_execute_exe +ORIGINAL_FILENAME=delegate_execute.exe diff --git a/chromium/win8/delegate_execute/delegate_execute_operation.cc b/chromium/win8/delegate_execute/delegate_execute_operation.cc new file mode 100644 index 00000000000..a7333125ba5 --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_operation.cc @@ -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. + +#include "win8/delegate_execute/delegate_execute_operation.h" + +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "chrome/common/chrome_switches.h" +#include "win8/delegate_execute/delegate_execute_util.h" + +namespace delegate_execute { + +DelegateExecuteOperation::DelegateExecuteOperation() + : operation_type_(DELEGATE_EXECUTE) { +} + +DelegateExecuteOperation::~DelegateExecuteOperation() { +} + +bool DelegateExecuteOperation::Init(const CommandLine* cmd_line) { + base::FilePath shortcut( + cmd_line->GetSwitchValuePath(switches::kRelaunchShortcut)); + if (shortcut.empty()) { + operation_type_ = DELEGATE_EXECUTE; + return true; + } + relaunch_shortcut_ = shortcut; + mutex_ = cmd_line->GetSwitchValueNative(switches::kWaitForMutex); + if (mutex_.empty()) + return false; + // Add the mode forcing flags, if any. + const char* the_switch = NULL; + + if (cmd_line->HasSwitch(switches::kForceDesktop)) + the_switch = switches::kForceDesktop; + else if (cmd_line->HasSwitch(switches::kForceImmersive)) + the_switch = switches::kForceImmersive; + + relaunch_flags_ = ParametersFromSwitch(the_switch); + + operation_type_ = RELAUNCH_CHROME; + return true; +} + +DWORD DelegateExecuteOperation::GetParentPid() const { + std::vector<string16> parts; + base::SplitString(mutex_, L'.', &parts); + if (parts.size() != 3) + return 0; + DWORD pid; + if (!base::StringToUint(parts[2], reinterpret_cast<uint32*>(&pid))) + return 0; + return pid; +} + +} // namespace delegate_execute diff --git a/chromium/win8/delegate_execute/delegate_execute_operation.h b/chromium/win8/delegate_execute/delegate_execute_operation.h new file mode 100644 index 00000000000..7d1456cef52 --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_operation.h @@ -0,0 +1,78 @@ +// 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 <windows.h> +#include <atldef.h> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/strings/string16.h" + +class CommandLine; + +namespace delegate_execute { + +// Parses a portion of the DelegateExecute handler's command line to determine +// the desired operation. The operation type is decided by looking at the +// command line. The operations are: +// DELEGATE_EXECUTE: +// When the delegate_execute.exe is invoked by windows when a chrome +// activation via the shell, possibly using ShellExecute. Control must +// be given to ATLs WinMain. +// RELAUNCH_CHROME: +// When the delegate_execute.exe is launched by chrome, when chrome needs +// to re-launch itself. The required command line parameters are: +// --relaunch-shortcut=<PathToShortcut> +// --wait-for-mutex=<MutexNamePid> +// The PathToShortcut must be the fully qualified file name to the chrome +// shortcut that has the appId and other 'metro ready' parameters. +// The MutexNamePid is a mutex name that also encodes the process id and +// must follow the format <A>.<B>.<pid> where A and B are arbitray strings +// (usually chrome.relaunch) and pid is the process id of chrome. +class DelegateExecuteOperation { + public: + enum OperationType { + DELEGATE_EXECUTE, + RELAUNCH_CHROME, + }; + + DelegateExecuteOperation(); + ~DelegateExecuteOperation(); + + bool Init(const CommandLine* cmd_line); + + OperationType operation_type() const { + return operation_type_; + } + + const string16& relaunch_flags() const { + return relaunch_flags_; + } + + const string16& mutex() const { + return mutex_; + } + + // Returns the process id of the parent or 0 on failure. + DWORD GetParentPid() const; + + const base::FilePath& shortcut() const { + return relaunch_shortcut_; + } + + private: + OperationType operation_type_; + string16 relaunch_flags_; + base::FilePath relaunch_shortcut_; + string16 mutex_; + + DISALLOW_COPY_AND_ASSIGN(DelegateExecuteOperation); +}; + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_OPERATION_H_ diff --git a/chromium/win8/delegate_execute/delegate_execute_util.cc b/chromium/win8/delegate_execute/delegate_execute_util.cc new file mode 100644 index 00000000000..6a3dc048dbd --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_util.cc @@ -0,0 +1,50 @@ +// 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_util.h" + +#include "base/files/file_path.h" +#include "base/strings/string_util.h" + +namespace delegate_execute { + +CommandLine CommandLineFromParameters(const wchar_t* params) { + CommandLine command_line(CommandLine::NO_PROGRAM); + + if (params) { + string16 command_string(L"noprogram.exe "); + command_string.append(params); + command_line.ParseFromString(command_string); + command_line.SetProgram(base::FilePath()); + } + + return command_line; +} + +CommandLine MakeChromeCommandLine(const base::FilePath& chrome_exe, + const CommandLine& params, + const string16& argument) { + CommandLine chrome_cmd(params); + chrome_cmd.SetProgram(chrome_exe); + + if (!argument.empty()) + chrome_cmd.AppendArgNative(argument); + + return chrome_cmd; +} + +string16 ParametersFromSwitch(const char* a_switch) { + if (!a_switch) + return string16(); + + CommandLine cmd_line(CommandLine::NO_PROGRAM); + + cmd_line.AppendSwitch(a_switch); + + string16 command_string(cmd_line.GetCommandLineString()); + TrimWhitespace(command_string, TRIM_ALL, &command_string); + return command_string; +} + +} // namespace delegate_execute diff --git a/chromium/win8/delegate_execute/delegate_execute_util.h b/chromium/win8/delegate_execute/delegate_execute_util.h new file mode 100644 index 00000000000..adb2e7acc9a --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_util.h @@ -0,0 +1,32 @@ +// 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_UTIL_H_ +#define WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_UTIL_H_ + +#include "base/command_line.h" +#include "base/strings/string16.h" + +namespace base { +class FilePath; +} + +namespace delegate_execute { + +// Returns a CommandLine with an empty program parsed from |params|. +CommandLine CommandLineFromParameters(const wchar_t* params); + +// Returns a CommandLine to launch |chrome_exe| with all switches and arguments +// from |params| plus an optional |argument|. +CommandLine MakeChromeCommandLine(const base::FilePath& chrome_exe, + const CommandLine& params, + const string16& argument); + +// Returns a properly quoted command-line string less the program (argv[0]) +// containing |switch|. +string16 ParametersFromSwitch(const char* a_switch); + +} // namespace delegate_execute + +#endif // WIN8_DELEGATE_EXECUTE_DELEGATE_EXECUTE_UTIL_H_ diff --git a/chromium/win8/delegate_execute/delegate_execute_util_unittest.cc b/chromium/win8/delegate_execute/delegate_execute_util_unittest.cc new file mode 100644 index 00000000000..f01b15757bf --- /dev/null +++ b/chromium/win8/delegate_execute/delegate_execute_util_unittest.cc @@ -0,0 +1,79 @@ +// 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_util.h" + +#include <algorithm> + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const char kSomeSwitch[] = "some-switch"; + +} // namespace + +TEST(DelegateExecuteUtil, CommandLineFromParametersTest) { + CommandLine cl(CommandLine::NO_PROGRAM); + + // Empty parameters means empty command-line string. + cl = delegate_execute::CommandLineFromParameters(NULL); + EXPECT_EQ(std::wstring(), cl.GetProgram().value()); + EXPECT_EQ(CommandLine::StringType(), cl.GetCommandLineString()); + + // Parameters with a switch are parsed properly. + cl = delegate_execute::CommandLineFromParameters( + base::StringPrintf(L"--%ls", ASCIIToWide(kSomeSwitch).c_str()).c_str()); + EXPECT_EQ(std::wstring(), cl.GetProgram().value()); + EXPECT_TRUE(cl.HasSwitch(kSomeSwitch)); +} + +TEST(DelegateExecuteUtil, MakeChromeCommandLineTest) { + static const wchar_t kSomeArgument[] = L"http://some.url/"; + static const wchar_t kOtherArgument[] = L"http://some.other.url/"; + const base::FilePath this_exe(CommandLine::ForCurrentProcess()->GetProgram()); + + CommandLine cl(CommandLine::NO_PROGRAM); + + // Empty params and argument contains only the exe. + cl = delegate_execute::MakeChromeCommandLine( + this_exe, delegate_execute::CommandLineFromParameters(NULL), string16()); + EXPECT_EQ(1, cl.argv().size()); + EXPECT_EQ(this_exe.value(), cl.GetProgram().value()); + + // Empty params with arg contains the arg. + cl = delegate_execute::MakeChromeCommandLine( + this_exe, delegate_execute::CommandLineFromParameters(NULL), + string16(kSomeArgument)); + EXPECT_EQ(2, cl.argv().size()); + EXPECT_EQ(this_exe.value(), cl.GetProgram().value()); + EXPECT_EQ(1, cl.GetArgs().size()); + EXPECT_EQ(string16(kSomeArgument), cl.GetArgs()[0]); + + // Params with switchs and args plus arg contains the arg. + cl = delegate_execute::MakeChromeCommandLine( + this_exe, delegate_execute::CommandLineFromParameters( + base::StringPrintf(L"--%ls -- %ls", ASCIIToWide(kSomeSwitch).c_str(), + kOtherArgument).c_str()), + string16(kSomeArgument)); + EXPECT_EQ(5, cl.argv().size()); + EXPECT_EQ(this_exe.value(), cl.GetProgram().value()); + EXPECT_TRUE(cl.HasSwitch(kSomeSwitch)); + CommandLine::StringVector args(cl.GetArgs()); + EXPECT_EQ(2, args.size()); + EXPECT_NE(args.end(), + std::find(args.begin(), args.end(), string16(kOtherArgument))); + EXPECT_NE(args.end(), + std::find(args.begin(), args.end(), string16(kSomeArgument))); +} + +TEST(DelegateExecuteUtil, ParametersFromSwitchTest) { + EXPECT_EQ(string16(), delegate_execute::ParametersFromSwitch(NULL)); + EXPECT_EQ(string16(L"--some-switch"), + delegate_execute::ParametersFromSwitch(kSomeSwitch)); +} diff --git a/chromium/win8/delegate_execute/post_build.bat b/chromium/win8/delegate_execute/post_build.bat new file mode 100755 index 00000000000..7bb9f9e3f2c --- /dev/null +++ b/chromium/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/chromium/win8/delegate_execute/resource.h b/chromium/win8/delegate_execute/resource.h new file mode 100644 index 00000000000..971d6ae5b9a --- /dev/null +++ b/chromium/win8/delegate_execute/resource.h @@ -0,0 +1,21 @@ +// 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_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/chromium/win8/metro_driver/DEPS b/chromium/win8/metro_driver/DEPS new file mode 100644 index 00000000000..c767809f8dd --- /dev/null +++ b/chromium/win8/metro_driver/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + # Allow metro_driver to include from delegate_execute *temporarily*. + # TODO(ananta): Remove this by fixing toast_notification_handler.cc. + "+win8/delegate_execute", +]
\ No newline at end of file diff --git a/chromium/win8/metro_driver/OWNERS b/chromium/win8/metro_driver/OWNERS new file mode 100644 index 00000000000..38029375810 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/chrome_app_view.cc b/chromium/win8/metro_driver/chrome_app_view.cc new file mode 100644 index 00000000000..79bd58474bc --- /dev/null +++ b/chromium/win8/metro_driver/chrome_app_view.cc @@ -0,0 +1,1216 @@ +// 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 <corewindow.h> +#include <windows.applicationModel.datatransfer.h> +#include <windows.foundation.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/message_loop/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 "ui/base/ui_base_switches.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/metro_viewer/metro_viewer_messages.h" +#include "win8/metro_driver/metro_driver.h" +#include "win8/metro_driver/winrt_utils.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; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::PointerEventArgs*> PointerEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::KeyEventArgs*> KeyEventHandler; + +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; + +const wchar_t kChromeSubclassWindowProp[] = L"MetroChromeWindowProc"; + +namespace { + +enum Modifier { + NONE, + SHIFT = 1, + CONTROL = 2, + ALT = 4 +}; + +// Helper function to send keystrokes via the SendInput function. +// Params: +// mnemonic_char: The keystroke to be sent. +// modifiers: Combination with Alt, Ctrl, Shift, etc. +// extended: whether this is an extended key. +// unicode: whether this is a unicode key. +void SendMnemonic(WORD mnemonic_char, Modifier modifiers, bool extended, + bool unicode) { + INPUT keys[4] = {0}; // Keyboard events + int key_count = 0; // Number of generated events + + if (modifiers & SHIFT) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_SHIFT; + keys[key_count].ki.wScan = MapVirtualKey(VK_SHIFT, 0); + key_count++; + } + + if (modifiers & CONTROL) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_CONTROL; + keys[key_count].ki.wScan = MapVirtualKey(VK_CONTROL, 0); + key_count++; + } + + if (modifiers & ALT) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_MENU; + keys[key_count].ki.wScan = MapVirtualKey(VK_MENU, 0); + key_count++; + } + + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = mnemonic_char; + keys[key_count].ki.wScan = MapVirtualKey(mnemonic_char, 0); + + if (extended) + keys[key_count].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + if (unicode) + keys[key_count].ki.dwFlags |= KEYEVENTF_UNICODE; + key_count++; + + bool should_sleep = key_count > 1; + + // Send key downs + for (int i = 0; i < key_count; i++) { + SendInput(1, &keys[ i ], sizeof(keys[0])); + keys[i].ki.dwFlags |= KEYEVENTF_KEYUP; + if (should_sleep) { + Sleep(10); + } + } + + // Now send key ups in reverse order + for (int i = key_count; i; i--) { + SendInput(1, &keys[ i - 1 ], sizeof(keys[0])); + if (should_sleep) { + Sleep(10); + } + } +} + +// Helper function to Exit metro chrome cleanly. If we are in the foreground +// then we try and exit by sending an Alt+F4 key combination to the core +// window which ensures that the chrome application tile does not show up in +// the running metro apps list on the top left corner. We have seen cases +// where this does work. To workaround that we invoke the +// ICoreApplicationExit::Exit function in a background delayed task which +// ensures that chrome exits. +void MetroExit(bool send_alt_f4_mnemonic) { + if (send_alt_f4_mnemonic && globals.view && + globals.view->core_window_hwnd() == ::GetForegroundWindow()) { + DVLOG(1) << "We are in the foreground. Exiting via Alt F4"; + SendMnemonic(VK_F4, ALT, false, false); + DWORD core_window_process_id = 0; + DWORD core_window_thread_id = GetWindowThreadProcessId( + globals.view->core_window_hwnd(), &core_window_process_id); + if (core_window_thread_id != ::GetCurrentThreadId()) { + globals.appview_msg_loop->PostDelayedTask( + FROM_HERE, + base::Bind(&MetroExit, false), + base::TimeDelta::FromMilliseconds(100)); + } + } else { + globals.app_exit->Exit(); + } +} + +void AdjustToFitWindow(HWND hwnd, int flags) { + RECT rect = {0}; + ::GetWindowRect(globals.view->core_window_hwnd() , &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); +} + +LRESULT CALLBACK ChromeWindowProc(HWND window, + UINT message, + WPARAM wp, + LPARAM lp) { + if (message == WM_SETCURSOR) { + // Override the WM_SETCURSOR message to avoid showing the resize cursor + // as the mouse moves to the edge of the screen. + switch (LOWORD(lp)) { + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: + lp = MAKELPARAM(HTCLIENT, HIWORD(lp)); + break; + default: + break; + } + } + + WNDPROC old_proc = reinterpret_cast<WNDPROC>( + ::GetProp(window, kChromeSubclassWindowProp)); + DCHECK(old_proc); + return CallWindowProc(old_proc, window, message, wp, lp); +} + +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)); + + // Subclass the wndproc of the frame window, if it's not already there. + if (::GetProp(hwnd, kChromeSubclassWindowProp) == NULL) { + WNDPROC old_chrome_proc = + reinterpret_cast<WNDPROC>(::SetWindowLongPtr( + hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ChromeWindowProc))); + ::SetProp(hwnd, kChromeSubclassWindowProp, old_chrome_proc); + } + 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); + } + + // Visible frame windows always need to be at the head of the list. + // Check if the window being shown already exists in our global list. + // If no then add it at the head of the list. + // If yes, retrieve the osk window scrolled state, remove the window from the + // list and readd it at the head. + std::list<std::pair<HWND, bool> >::iterator index = + std::find_if(globals.host_windows.begin(), globals.host_windows.end(), + [hwnd](std::pair<HWND, bool>& item) { + return (item.first == hwnd); + }); + + bool window_scrolled_state = false; + bool new_window = (index == globals.host_windows.end()); + if (!new_window) { + window_scrolled_state = index->second; + globals.host_windows.erase(index); + } + + globals.host_windows.push_front(std::make_pair(hwnd, window_scrolled_state)); + + if (new_window) { + AdjustFrameWindowStyleForMetro(hwnd); + } else { + DVLOG(1) << "Adjusting new top window to core window size"; + AdjustToFitWindow(hwnd, 0); + } + if (globals.view->GetViewState() == + winui::ViewManagement::ApplicationViewState_Snapped) { + DVLOG(1) << "Enabling Metro snap state on new window: " << hwnd; + ::PostMessageW(hwnd, WM_SYSCOMMAND, IDC_METRO_SNAP_ENABLE, 0); + } +} + +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()."; + MetroExit(true); + } +} + +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 + +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; + } +} + +winui::ViewManagement::ApplicationViewState ChromeAppView::GetViewState() { + winui::ViewManagement::ApplicationViewState view_state = + winui::ViewManagement::ApplicationViewState_FullScreenLandscape; + app_view_->get_Value(&view_state); + return view_state; +} + +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.view->core_window_hwnd(); +} + +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, + base::win::MetroNotificationClickedHandler handler, + const wchar_t* handler_context) { + // TODO(ananta) + // Needs implementation. + DVLOG(1) << __FUNCTION__; + + ToastNotificationHandler::DesktopNotification notification(origin_url, + icon_url, + title, + body, + display_source, + notification_id, + handler, + handler_context); + 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)); +} + +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>(::SetWindowLongPtr( + globals.view->core_window_hwnd(), GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(ChromeAppView::CoreWindowProc))); + DWORD exit_code = globals.host_main(globals.host_context); + + DVLOG(1) << "host thread done, exit_code=" << exit_code; + MetroExit(true); + return exit_code; +} + +ChromeAppView::ChromeAppView() + : osk_visible_notification_received_(false), + osk_offset_adjustment_(0), + core_window_hwnd_(NULL) { + globals.previous_state = + winapp::Activation::ApplicationExecutionState_NotRunning; +} + +ChromeAppView::~ChromeAppView() { + DVLOG(1) << __FUNCTION__; +} + +IFACEMETHODIMP +ChromeAppView::Initialize(winapp::Core::ICoreApplicationView* view) { + view_ = view; + DVLOG(1) << __FUNCTION__; + + 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__; + + // Retrieve the native window handle via the interop layer. + mswr::ComPtr<ICoreWindowInterop> interop; + HRESULT hr = window->QueryInterface(interop.GetAddressOf()); + CheckHR(hr); + hr = interop->get_WindowHandle(&core_window_hwnd_); + CheckHR(hr); + + 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. + base::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. + base::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); + } + } + } + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ChromeAppView::CheckForOSKActivation, base::Unretained(this)), + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs)); +} + +IFACEMETHODIMP +ChromeAppView::Run() { + DVLOG(1) << __FUNCTION__; + 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. + base::MessageLoop msg_loop(base::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; + } + + DVLOG(1) << "Host thread exited"; + + ::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; + } + + if (!globals.host_thread) { + DWORD chrome_ui_thread_id = 0; + globals.host_thread = + ::CreateThread(NULL, 0, HostMainThreadProc, NULL, 0, + &chrome_ui_thread_id); + + if (!globals.host_thread) { + NOTREACHED() << "thread creation failed."; + return E_UNEXPECTED; + } + } + + if (RegisterHotKey(core_window_hwnd_, 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/chromium/win8/metro_driver/chrome_app_view.h b/chromium/win8/metro_driver/chrome_app_view.h new file mode 100644 index 00000000000..0554ab31366 --- /dev/null +++ b/chromium/win8/metro_driver/chrome_app_view.h @@ -0,0 +1,172 @@ +// 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_METRO_DRIVER_CHROME_APP_VIEW_H_ +#define WIN8_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/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" + +namespace IPC { + class Listener; + class ChannelProxy; +} + +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); + + 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); + + // Returns the current view state of the chrome window. + winui::ViewManagement::ApplicationViewState GetViewState(); + + HWND core_window_hwnd() { return core_window_hwnd_; } + + 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_; + + // The actual window behind the view surface. + HWND core_window_hwnd_; + + 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_; +}; + +// Global information used across the metro driver. +struct Globals { + LPTHREAD_START_ROUTINE host_main; + void* host_context; + // 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; + 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; + string16 metro_command_line_switches; +}; + +extern Globals globals; + +#endif // WIN8_METRO_DRIVER_CHROME_APP_VIEW_H_ diff --git a/chromium/win8/metro_driver/chrome_app_view_ash.cc b/chromium/win8/metro_driver/chrome_app_view_ash.cc new file mode 100644 index 00000000000..c1a8f4cd573 --- /dev/null +++ b/chromium/win8/metro_driver/chrome_app_view_ash.cc @@ -0,0 +1,1259 @@ +// 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_ash.h" + +#include <corewindow.h> +#include <shellapi.h> +#include <windows.foundation.h> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/threading/thread.h" +#include "base/win/metro.h" +#include "base/win/win_util.h" +#include "chrome/common/chrome_switches.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_sender.h" +#include "ui/events/gestures/gesture_sequence.h" +#include "ui/metro_viewer/metro_viewer_messages.h" +#include "win8/metro_driver/file_picker_ash.h" +#include "win8/metro_driver/ime/ime_popup_monitor.h" +#include "win8/metro_driver/ime/input_source.h" +#include "win8/metro_driver/ime/text_service.h" +#include "win8/metro_driver/metro_driver.h" +#include "win8/metro_driver/winrt_utils.h" +#include "win8/viewer/metro_viewer_constants.h" + +typedef winfoundtn::ITypedEventHandler< + winapp::Core::CoreApplicationView*, + winapp::Activation::IActivatedEventArgs*> ActivatedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::PointerEventArgs*> PointerEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::KeyEventArgs*> KeyEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreDispatcher*, + winui::Core::AcceleratorKeyEventArgs*> AcceleratorKeyEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::CharacterReceivedEventArgs*> CharEventHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::WindowActivatedEventArgs*> WindowActivatedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Core::CoreWindow*, + winui::Core::WindowSizeChangedEventArgs*> SizeChangedHandler; + +typedef winfoundtn::ITypedEventHandler< + winui::Input::EdgeGesture*, + winui::Input::EdgeGestureEventArgs*> EdgeEventHandler; + +// This function is exported by chrome.exe. +typedef int (__cdecl *BreakpadExceptionHandler)(EXCEPTION_POINTERS* info); + +// Global information used across the metro driver. +struct Globals { + winapp::Activation::ApplicationExecutionState previous_state; + winapp::Core::ICoreApplicationExit* app_exit; + BreakpadExceptionHandler breakpad_exception_handler; +} globals; + +namespace { + +enum KeyModifier { + NONE, + SHIFT = 1, + CONTROL = 2, + ALT = 4 +}; + +// Helper function to send keystrokes via the SendInput function. +// mnemonic_char: The keystroke to be sent. +// modifiers: Combination with Alt, Ctrl, Shift, etc. +void SendMnemonic( + WORD mnemonic_char, KeyModifier modifiers) { + INPUT keys[4] = {0}; // Keyboard events + int key_count = 0; // Number of generated events + + if (modifiers & SHIFT) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_SHIFT; + keys[key_count].ki.wScan = MapVirtualKey(VK_SHIFT, 0); + key_count++; + } + + if (modifiers & CONTROL) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_CONTROL; + keys[key_count].ki.wScan = MapVirtualKey(VK_CONTROL, 0); + key_count++; + } + + if (modifiers & ALT) { + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = VK_MENU; + keys[key_count].ki.wScan = MapVirtualKey(VK_MENU, 0); + key_count++; + } + + keys[key_count].type = INPUT_KEYBOARD; + keys[key_count].ki.wVk = mnemonic_char; + keys[key_count].ki.wScan = MapVirtualKey(mnemonic_char, 0); + key_count++; + + bool should_sleep = key_count > 1; + + // Send key downs. + for (int i = 0; i < key_count; i++) { + SendInput(1, &keys[ i ], sizeof(keys[0])); + keys[i].ki.dwFlags |= KEYEVENTF_KEYUP; + if (should_sleep) + Sleep(10); + } + + // Now send key ups in reverse order. + for (int i = key_count; i; i--) { + SendInput(1, &keys[ i - 1 ], sizeof(keys[0])); + if (should_sleep) + Sleep(10); + } +} + +// Helper function to Exit metro chrome cleanly. If we are in the foreground +// then we try and exit by sending an Alt+F4 key combination to the core +// window which ensures that the chrome application tile does not show up in +// the running metro apps list on the top left corner. +void MetroExit(HWND core_window) { + if ((core_window != NULL) && (core_window == ::GetForegroundWindow())) { + DVLOG(1) << "We are in the foreground. Exiting via Alt F4"; + SendMnemonic(VK_F4, ALT); + } else { + globals.app_exit->Exit(); + } +} + +class ChromeChannelListener : public IPC::Listener { + public: + ChromeChannelListener(base::MessageLoop* ui_loop, ChromeAppViewAsh* app_view) + : ui_proxy_(ui_loop->message_loop_proxy()), + app_view_(app_view) {} + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(ChromeChannelListener, message) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ActivateDesktop, + OnActivateDesktop) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_MetroExit, OnMetroExit) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_OpenURLOnDesktop, + OnOpenURLOnDesktop) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SetCursor, OnSetCursor) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_DisplayFileOpen, + OnDisplayFileOpenDialog) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_DisplayFileSaveAs, + OnDisplayFileSaveAsDialog) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_DisplaySelectFolder, + OnDisplayFolderPicker) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SetCursorPos, OnSetCursorPos) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ImeCancelComposition, + OnImeCancelComposition) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ImeTextInputClientUpdated, + OnImeTextInputClientChanged) + IPC_MESSAGE_UNHANDLED(__debugbreak()) + IPC_END_MESSAGE_MAP() + return true; + } + + virtual void OnChannelError() OVERRIDE { + DVLOG(1) << "Channel error. Exiting."; + MetroExit(app_view_->core_window_hwnd()); + // In early Windows 8 versions the code above sometimes fails so we call + // it a second time with a NULL window which just calls Exit(). + ui_proxy_->PostDelayedTask(FROM_HERE, + base::Bind(&MetroExit, HWND(NULL)), + base::TimeDelta::FromMilliseconds(100)); + } + + private: + void OnActivateDesktop(const base::FilePath& shortcut, bool ash_exit) { + ui_proxy_->PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnActivateDesktop, + base::Unretained(app_view_), + shortcut, ash_exit)); + } + + void OnMetroExit() { + MetroExit(app_view_->core_window_hwnd()); + } + + void OnOpenURLOnDesktop(const base::FilePath& shortcut, + const string16& url) { + ui_proxy_->PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnOpenURLOnDesktop, + base::Unretained(app_view_), + shortcut, url)); + } + + void OnSetCursor(int64 cursor) { + ui_proxy_->PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnSetCursor, + base::Unretained(app_view_), + reinterpret_cast<HCURSOR>(cursor))); + } + + void OnDisplayFileOpenDialog(const string16& title, + const string16& filter, + const base::FilePath& default_path, + bool allow_multiple_files) { + ui_proxy_->PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnDisplayFileOpenDialog, + base::Unretained(app_view_), + title, + filter, + default_path, + allow_multiple_files)); + } + + void OnDisplayFileSaveAsDialog( + const MetroViewerHostMsg_SaveAsDialogParams& params) { + ui_proxy_->PostTask( + FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnDisplayFileSaveAsDialog, + base::Unretained(app_view_), + params)); + } + + void OnDisplayFolderPicker(const string16& title) { + ui_proxy_->PostTask( + FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnDisplayFolderPicker, + base::Unretained(app_view_), + title)); + } + + void OnSetCursorPos(int x, int y) { + VLOG(1) << "In IPC OnSetCursorPos: " << x << ", " << y; + ui_proxy_->PostTask( + FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnSetCursorPos, + base::Unretained(app_view_), + x, y)); + } + + void OnImeCancelComposition() { + ui_proxy_->PostTask( + FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnImeCancelComposition, + base::Unretained(app_view_))); + } + + void OnImeTextInputClientChanged( + const std::vector<int32>& input_scopes, + const std::vector<metro_viewer::CharacterBounds>& character_bounds) { + ui_proxy_->PostTask( + FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnImeUpdateTextInputClient, + base::Unretained(app_view_), + input_scopes, + character_bounds)); + } + + scoped_refptr<base::MessageLoopProxy> ui_proxy_; + ChromeAppViewAsh* app_view_; +}; + +bool WaitForChromeIPCConnection(const std::string& channel_name) { + int ms_elapsed = 0; + while (!IPC::Channel::IsNamedServerInitialized(channel_name) && + ms_elapsed < 10000) { + ms_elapsed += 500; + Sleep(500); + } + return IPC::Channel::IsNamedServerInitialized(channel_name); +} + +// This class helps decoding the pointer properties of an event. +class PointerInfoHandler { + public: + PointerInfoHandler() + : x_(0), + y_(0), + wheel_delta_(0), + update_kind_(winui::Input::PointerUpdateKind_Other), + timestamp_(0), + pointer_id_(0) {} + + HRESULT Init(winui::Core::IPointerEventArgs* args) { + HRESULT hr = args->get_CurrentPoint(&pointer_point_); + if (FAILED(hr)) + return hr; + + winfoundtn::Point point; + hr = pointer_point_->get_Position(&point); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<winui::Input::IPointerPointProperties> properties; + hr = pointer_point_->get_Properties(&properties); + if (FAILED(hr)) + return hr; + + hr = properties->get_PointerUpdateKind(&update_kind_); + if (FAILED(hr)) + return hr; + + hr = properties->get_MouseWheelDelta(&wheel_delta_); + if (FAILED(hr)) + return hr; + x_ = point.X; + y_ = point.Y; + pointer_point_->get_Timestamp(×tamp_); + pointer_point_->get_PointerId(&pointer_id_); + // Map the OS touch event id to a range allowed by the gesture recognizer. + if (IsTouch()) + pointer_id_ %= ui::GestureSequence::kMaxGesturePoints; + return S_OK; + } + + bool IsType(windevs::Input::PointerDeviceType type) const { + mswr::ComPtr<windevs::Input::IPointerDevice> pointer_device; + CheckHR(pointer_point_->get_PointerDevice(&pointer_device)); + windevs::Input::PointerDeviceType device_type; + CheckHR(pointer_device->get_PointerDeviceType(&device_type)); + return (device_type == type); + } + + bool IsMouse() const { + return IsType(windevs::Input::PointerDeviceType_Mouse); + } + + bool IsTouch() const { + return IsType(windevs::Input::PointerDeviceType_Touch); + } + + int32 wheel_delta() const { + return wheel_delta_; + } + + ui::EventFlags flags() { + switch (update_kind_) { + case winui::Input::PointerUpdateKind_LeftButtonPressed: + return ui::EF_LEFT_MOUSE_BUTTON; + case winui::Input::PointerUpdateKind_LeftButtonReleased: + return ui::EF_LEFT_MOUSE_BUTTON; + case winui::Input::PointerUpdateKind_RightButtonPressed: + return ui::EF_RIGHT_MOUSE_BUTTON; + case winui::Input::PointerUpdateKind_RightButtonReleased: + return ui::EF_RIGHT_MOUSE_BUTTON; + case winui::Input::PointerUpdateKind_MiddleButtonPressed: + return ui::EF_MIDDLE_MOUSE_BUTTON; + case winui::Input::PointerUpdateKind_MiddleButtonReleased: + return ui::EF_MIDDLE_MOUSE_BUTTON; + default: + return ui::EF_NONE; + }; + } + + int x() const { return x_; } + int y() const { return y_; } + + uint32 pointer_id() const { + return pointer_id_; + } + + uint64 timestamp() const { return timestamp_; } + + private: + int x_; + int y_; + int wheel_delta_; + uint32 pointer_id_; + winui::Input::PointerUpdateKind update_kind_; + mswr::ComPtr<winui::Input::IPointerPoint> pointer_point_; + uint64 timestamp_; +}; + +void RunMessageLoop(winui::Core::ICoreDispatcher* dispatcher) { + // We're entering a nested message loop, let's allow dispatching + // tasks while we're in there. + base::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. + base::MessageLoop::current()->Quit(); +} + +// Helper to return the state of the shift/control/alt keys. +uint32 GetKeyboardEventFlags() { + uint32 flags = 0; + if (base::win::IsShiftPressed()) + flags |= ui::EF_SHIFT_DOWN; + if (base::win::IsCtrlPressed()) + flags |= ui::EF_CONTROL_DOWN; + if (base::win::IsAltPressed()) + flags |= ui::EF_ALT_DOWN; + return flags; +} + +bool LaunchChromeBrowserProcess(const wchar_t* additional_parameters, + winapp::Activation::IActivatedEventArgs* args) { + if (args) { + DVLOG(1) << __FUNCTION__ << ":" << ::GetCommandLineW(); + winapp::Activation::ActivationKind activation_kind; + CheckHR(args->get_Kind(&activation_kind)); + + DVLOG(1) << __FUNCTION__ << ", 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"; + mswrw::HString launch_args_str; + launch_args->get_Arguments(launch_args_str.GetAddressOf()); + string16 actual_launch_args(MakeStdWString(launch_args_str.Get())); + if (actual_launch_args == win8::kMetroViewerConnectVerb) { + DVLOG(1) << __FUNCTION__ << "Not launching chrome server"; + return true; + } + } + } + } + + DVLOG(1) << "Launching chrome server"; + base::FilePath chrome_exe_path; + + if (!PathService::Get(base::FILE_EXE, &chrome_exe_path)) + return false; + + string16 parameters = L"--silent-launch --viewer-connect "; + if (additional_parameters) + parameters += additional_parameters; + + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.nShow = SW_SHOWNORMAL; + sei.lpFile = chrome_exe_path.value().c_str(); + sei.lpDirectory = L""; + sei.lpParameters = parameters.c_str(); + ::ShellExecuteEx(&sei); + return true; +} + +} // namespace + +ChromeAppViewAsh::ChromeAppViewAsh() + : mouse_down_flags_(ui::EF_NONE), + ui_channel_(nullptr), + core_window_hwnd_(NULL), + ui_loop_(base::MessageLoop::TYPE_UI) { + DVLOG(1) << __FUNCTION__; + globals.previous_state = + winapp::Activation::ApplicationExecutionState_NotRunning; +} + +ChromeAppViewAsh::~ChromeAppViewAsh() { + DVLOG(1) << __FUNCTION__; +} + +IFACEMETHODIMP +ChromeAppViewAsh::Initialize(winapp::Core::ICoreApplicationView* view) { + view_ = view; + DVLOG(1) << __FUNCTION__; + HRESULT hr = view_->add_Activated(mswr::Callback<ActivatedHandler>( + this, &ChromeAppViewAsh::OnActivate).Get(), + &activated_token_); + CheckHR(hr); + return hr; +} + +IFACEMETHODIMP +ChromeAppViewAsh::SetWindow(winui::Core::ICoreWindow* window) { + window_ = window; + DVLOG(1) << __FUNCTION__; + + // Retrieve the native window handle via the interop layer. + mswr::ComPtr<ICoreWindowInterop> interop; + HRESULT hr = window->QueryInterface(interop.GetAddressOf()); + CheckHR(hr); + hr = interop->get_WindowHandle(&core_window_hwnd_); + CheckHR(hr); + + text_service_ = metro_driver::CreateTextService(this, core_window_hwnd_); + + hr = window_->add_SizeChanged(mswr::Callback<SizeChangedHandler>( + this, &ChromeAppViewAsh::OnSizeChanged).Get(), + &sizechange_token_); + CheckHR(hr); + + // Register for pointer and keyboard notifications. We forward + // them to the browser process via IPC. + hr = window_->add_PointerMoved(mswr::Callback<PointerEventHandler>( + this, &ChromeAppViewAsh::OnPointerMoved).Get(), + &pointermoved_token_); + CheckHR(hr); + + hr = window_->add_PointerPressed(mswr::Callback<PointerEventHandler>( + this, &ChromeAppViewAsh::OnPointerPressed).Get(), + &pointerpressed_token_); + CheckHR(hr); + + hr = window_->add_PointerReleased(mswr::Callback<PointerEventHandler>( + this, &ChromeAppViewAsh::OnPointerReleased).Get(), + &pointerreleased_token_); + CheckHR(hr); + + hr = window_->add_KeyDown(mswr::Callback<KeyEventHandler>( + this, &ChromeAppViewAsh::OnKeyDown).Get(), + &keydown_token_); + CheckHR(hr); + + hr = window_->add_KeyUp(mswr::Callback<KeyEventHandler>( + this, &ChromeAppViewAsh::OnKeyUp).Get(), + &keyup_token_); + CheckHR(hr); + + mswr::ComPtr<winui::Core::ICoreDispatcher> dispatcher; + hr = window_->get_Dispatcher(&dispatcher); + CheckHR(hr, "Get Dispatcher failed."); + + mswr::ComPtr<winui::Core::ICoreAcceleratorKeys> accelerator_keys; + hr = dispatcher.CopyTo(__uuidof(winui::Core::ICoreAcceleratorKeys), + reinterpret_cast<void**>( + accelerator_keys.GetAddressOf())); + CheckHR(hr, "QI for ICoreAcceleratorKeys failed."); + hr = accelerator_keys->add_AcceleratorKeyActivated( + mswr::Callback<AcceleratorKeyEventHandler>( + this, &ChromeAppViewAsh::OnAcceleratorKeyDown).Get(), + &accel_keydown_token_); + CheckHR(hr); + + hr = window_->add_PointerWheelChanged(mswr::Callback<PointerEventHandler>( + this, &ChromeAppViewAsh::OnWheel).Get(), + &wheel_token_); + CheckHR(hr); + + hr = window_->add_CharacterReceived(mswr::Callback<CharEventHandler>( + this, &ChromeAppViewAsh::OnCharacterReceived).Get(), + &character_received_token_); + CheckHR(hr); + + hr = window_->add_Activated(mswr::Callback<WindowActivatedHandler>( + this, &ChromeAppViewAsh::OnWindowActivated).Get(), + &window_activated_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); + + 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, &ChromeAppViewAsh::OnEdgeGestureCompleted).Get(), + &edgeevent_token_); + CheckHR(hr); + + // By initializing the direct 3D swap chain with the corewindow + // we can now directly blit to it from the browser process. + direct3d_helper_.Initialize(window); + DVLOG(1) << "Initialized Direct3D."; + return S_OK; +} + +IFACEMETHODIMP +ChromeAppViewAsh::Load(HSTRING entryPoint) { + DVLOG(1) << __FUNCTION__; + return S_OK; +} + +IFACEMETHODIMP +ChromeAppViewAsh::Run() { + DVLOG(1) << __FUNCTION__; + mswr::ComPtr<winui::Core::ICoreDispatcher> dispatcher; + HRESULT hr = window_->get_Dispatcher(&dispatcher); + CheckHR(hr, "Dispatcher failed."); + + hr = window_->Activate(); + if (FAILED(hr)) { + DLOG(WARNING) << "activation failed hr=" << hr; + return hr; + } + + // Create the IPC channel IO thread. It needs to out-live the ChannelProxy. + base::Thread io_thread("metro_IO_thread"); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + io_thread.StartWithOptions(options); + + // Start up Chrome and wait for the desired IPC server connection to exist. + WaitForChromeIPCConnection(win8::kMetroViewerIPCChannelName); + + // In Aura mode we create an IPC channel to the browser, then ask it to + // connect to us. + ChromeChannelListener ui_channel_listener(&ui_loop_, this); + IPC::ChannelProxy ui_channel(win8::kMetroViewerIPCChannelName, + IPC::Channel::MODE_NAMED_CLIENT, + &ui_channel_listener, + io_thread.message_loop_proxy()); + ui_channel_ = &ui_channel; + + // Upon receipt of the MetroViewerHostMsg_SetTargetSurface message the + // browser will use D3D from the browser process to present to our Window. + ui_channel_->Send(new MetroViewerHostMsg_SetTargetSurface( + gfx::NativeViewId(core_window_hwnd_))); + DVLOG(1) << "ICoreWindow sent " << core_window_hwnd_; + + // Send an initial size message so that the Ash root window host gets sized + // correctly. + RECT rect = {0}; + ::GetWindowRect(core_window_hwnd_, &rect); + ui_channel_->Send( + new MetroViewerHostMsg_WindowSizeChanged(rect.right - rect.left, + rect.bottom - rect.top)); + + input_source_ = metro_driver::InputSource::Create(); + if (input_source_) { + input_source_->AddObserver(this); + // Send an initial input source. + OnInputSourceChanged(); + } + + // Start receiving IME popup window notifications. + metro_driver::AddImePopupObserver(this); + + // And post the task that'll do the inner Metro message pumping to it. + ui_loop_.PostTask(FROM_HERE, base::Bind(&RunMessageLoop, dispatcher.Get())); + ui_loop_.Run(); + + DVLOG(0) << "ProcessEvents done, hr=" << hr; + return hr; +} + +IFACEMETHODIMP +ChromeAppViewAsh::Uninitialize() { + DVLOG(1) << __FUNCTION__; + metro_driver::RemoveImePopupObserver(this); + input_source_.reset(); + text_service_.reset(); + window_ = nullptr; + view_ = nullptr; + core_window_hwnd_ = NULL; + return S_OK; +} + +// static +HRESULT ChromeAppViewAsh::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 ChromeAppViewAsh::OnActivateDesktop(const base::FilePath& file_path, + bool ash_exit) { + DVLOG(1) << "ChannelAppViewAsh::OnActivateDesktop\n"; + + if (ash_exit) { + // As we are the top level window, the exiting is done async so we manage + // to execute the entire function including the final Send(). + MetroExit(core_window_hwnd()); + } + + // We are just executing delegate_execute here without parameters. Assumption + // here is that this process will be reused by shell when asking for + // IExecuteCommand interface. + + // TODO(shrikant): Consolidate ShellExecuteEx with SEE_MASK_FLAG_LOG_USAGE + // and place it metro.h or similar accessible file from all code code paths + // using this function. + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_FLAG_LOG_USAGE; + sei.nShow = SW_SHOWNORMAL; + sei.lpFile = file_path.value().c_str(); + sei.lpParameters = NULL; + if (!ash_exit) + sei.fMask |= SEE_MASK_NOCLOSEPROCESS; + ::ShellExecuteExW(&sei); + if (!ash_exit) { + ::TerminateProcess(sei.hProcess, 0); + ::CloseHandle(sei.hProcess); + } +} + +void ChromeAppViewAsh::OnOpenURLOnDesktop(const base::FilePath& shortcut, + const string16& url) { + base::FilePath::StringType file = shortcut.value(); + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_FLAG_LOG_USAGE; + sei.nShow = SW_SHOWNORMAL; + sei.lpFile = file.c_str(); + sei.lpDirectory = L""; + sei.lpParameters = url.c_str(); + BOOL result = ShellExecuteEx(&sei); +} + +void ChromeAppViewAsh::OnSetCursor(HCURSOR cursor) { + ::SetCursor(HCURSOR(cursor)); +} + +void ChromeAppViewAsh::OnDisplayFileOpenDialog( + const string16& title, + const string16& filter, + const base::FilePath& default_path, + bool allow_multiple_files) { + DVLOG(1) << __FUNCTION__; + + // The OpenFilePickerSession instance is deleted when we receive a + // callback from the OpenFilePickerSession class about the completion of the + // operation. + FilePickerSessionBase* file_picker_ = + new OpenFilePickerSession(this, + title, + filter, + default_path, + allow_multiple_files); + file_picker_->Run(); +} + +void ChromeAppViewAsh::OnDisplayFileSaveAsDialog( + const MetroViewerHostMsg_SaveAsDialogParams& params) { + DVLOG(1) << __FUNCTION__; + + // The SaveFilePickerSession instance is deleted when we receive a + // callback from the SaveFilePickerSession class about the completion of the + // operation. + FilePickerSessionBase* file_picker_ = + new SaveFilePickerSession(this, params); + file_picker_->Run(); +} + +void ChromeAppViewAsh::OnDisplayFolderPicker(const string16& title) { + DVLOG(1) << __FUNCTION__; + // The FolderPickerSession instance is deleted when we receive a + // callback from the FolderPickerSession class about the completion of the + // operation. + FilePickerSessionBase* file_picker_ = new FolderPickerSession(this, title); + file_picker_->Run(); +} + +void ChromeAppViewAsh::OnSetCursorPos(int x, int y) { + if (ui_channel_) { + ::SetCursorPos(x, y); + DVLOG(1) << "In UI OnSetCursorPos: " << x << ", " << y; + ui_channel_->Send(new MetroViewerHostMsg_SetCursorPosAck()); + // Generate a fake mouse move which matches the SetCursor coordinates as + // the browser expects to receive a mouse move for these coordinates. + // It is not clear why we don't receive a real mouse move in response to + // the SetCursorPos calll above. + ui_channel_->Send(new MetroViewerHostMsg_MouseMoved(x, y, 0)); + } +} + +void ChromeAppViewAsh::OnOpenFileCompleted( + OpenFilePickerSession* open_file_picker, + bool success) { + DVLOG(1) << __FUNCTION__; + DVLOG(1) << "Success: " << success; + if (ui_channel_) { + if (open_file_picker->allow_multi_select()) { + ui_channel_->Send(new MetroViewerHostMsg_MultiFileOpenDone( + success, open_file_picker->filenames())); + } else { + ui_channel_->Send(new MetroViewerHostMsg_FileOpenDone( + success, base::FilePath(open_file_picker->result()))); + } + } + delete open_file_picker; +} + +void ChromeAppViewAsh::OnSaveFileCompleted( + SaveFilePickerSession* save_file_picker, + bool success) { + DVLOG(1) << __FUNCTION__; + DVLOG(1) << "Success: " << success; + if (ui_channel_) { + ui_channel_->Send(new MetroViewerHostMsg_FileSaveAsDone( + success, + base::FilePath(save_file_picker->result()), + save_file_picker->filter_index())); + } + delete save_file_picker; +} + +void ChromeAppViewAsh::OnFolderPickerCompleted( + FolderPickerSession* folder_picker, + bool success) { + DVLOG(1) << __FUNCTION__; + DVLOG(1) << "Success: " << success; + if (ui_channel_) { + ui_channel_->Send(new MetroViewerHostMsg_SelectFolderDone( + success, + base::FilePath(folder_picker->result()))); + } + delete folder_picker; +} + +void ChromeAppViewAsh::OnImeCancelComposition() { + if (!text_service_) + return; + text_service_->CancelComposition(); +} + +void ChromeAppViewAsh::OnImeUpdateTextInputClient( + const std::vector<int32>& input_scopes, + const std::vector<metro_viewer::CharacterBounds>& character_bounds) { + if (!text_service_) + return; + text_service_->OnDocumentChanged(input_scopes, character_bounds); +} + +void ChromeAppViewAsh::OnImePopupChanged(ImePopupObserver::EventType event) { + if (!ui_channel_) + return; + switch (event) { + case ImePopupObserver::kPopupShown: + ui_channel_->Send(new MetroViewerHostMsg_ImeCandidatePopupChanged(true)); + return; + case ImePopupObserver::kPopupHidden: + ui_channel_->Send(new MetroViewerHostMsg_ImeCandidatePopupChanged(false)); + return; + case ImePopupObserver::kPopupUpdated: + // TODO(kochi): Support this event for W3C IME API proposal. + // See crbug.com/238585. + return; + default: + NOTREACHED() << "unknown event type: " << event; + return; + } +} + +void ChromeAppViewAsh::OnInputSourceChanged() { + if (!input_source_) + return; + + LANGID langid = 0; + bool is_ime = false; + if (!input_source_->GetActiveSource(&langid, &is_ime)) { + LOG(ERROR) << "GetActiveSource failed"; + return; + } + ui_channel_->Send(new MetroViewerHostMsg_ImeInputSourceChanged(langid, + is_ime)); +} + +void ChromeAppViewAsh::OnCompositionChanged( + const string16& text, + int32 selection_start, + int32 selection_end, + const std::vector<metro_viewer::UnderlineInfo>& underlines) { + ui_channel_->Send(new MetroViewerHostMsg_ImeCompositionChanged( + text, selection_start, selection_end, underlines)); +} + +void ChromeAppViewAsh::OnTextCommitted(const string16& text) { + ui_channel_->Send(new MetroViewerHostMsg_ImeTextCommitted(text)); +} + +HRESULT ChromeAppViewAsh::OnActivate( + winapp::Core::ICoreApplicationView*, + winapp::Activation::IActivatedEventArgs* args) { + DVLOG(1) << __FUNCTION__; + // Note: If doing more work in this function, you migth need to call + // get_PreviousExecutionState() and skip the work if the result is + // ApplicationExecutionState_Running and globals.previous_state is too. + args->get_PreviousExecutionState(&globals.previous_state); + DVLOG(1) << "Previous Execution State: " << globals.previous_state; + + winapp::Activation::ActivationKind activation_kind; + CheckHR(args->get_Kind(&activation_kind)); + DVLOG(1) << "Activation kind: " << activation_kind; + + if (activation_kind == winapp::Activation::ActivationKind_Search) + HandleSearchRequest(args); + else if (activation_kind == winapp::Activation::ActivationKind_Protocol) + HandleProtocolRequest(args); + else + LaunchChromeBrowserProcess(NULL, args); + // We call ICoreWindow::Activate after the handling for the search/protocol + // requests because Chrome can be launched to handle a search request which + // in turn launches the chrome browser process in desktop mode via + // ShellExecute. If we call ICoreWindow::Activate before this, then + // Windows kills the metro chrome process when it calls ShellExecute. Seems + // to be a bug. + window_->Activate(); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnPointerMoved(winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args) { + PointerInfoHandler pointer; + HRESULT hr = pointer.Init(args); + if (FAILED(hr)) + return hr; + + if (pointer.IsMouse()) { + ui_channel_->Send(new MetroViewerHostMsg_MouseMoved( + pointer.x(), + pointer.y(), + mouse_down_flags_ | GetKeyboardEventFlags())); + } else { + DCHECK(pointer.IsTouch()); + ui_channel_->Send(new MetroViewerHostMsg_TouchMoved(pointer.x(), + pointer.y(), + pointer.timestamp(), + pointer.pointer_id())); + } + return S_OK; +} + +// NOTE: From experimentation, it seems like Metro only sends a PointerPressed +// event for the first button pressed and the last button released in a sequence +// of mouse events. +// For example, a sequence of LEFT_DOWN, RIGHT_DOWN, LEFT_UP, RIGHT_UP results +// only in PointerPressed(LEFT)/PointerReleased(RIGHT) events. +HRESULT ChromeAppViewAsh::OnPointerPressed( + winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args) { + PointerInfoHandler pointer; + HRESULT hr = pointer.Init(args); + if (FAILED(hr)) + return hr; + + if (pointer.IsMouse()) { + // TODO: this is wrong, more than one pointer may be down at a time. + mouse_down_flags_ = pointer.flags(); + ui_channel_->Send(new MetroViewerHostMsg_MouseButton( + pointer.x(), + pointer.y(), + 0, + ui::ET_MOUSE_PRESSED, + static_cast<ui::EventFlags>( + mouse_down_flags_ | GetKeyboardEventFlags()))); + } else { + DCHECK(pointer.IsTouch()); + ui_channel_->Send(new MetroViewerHostMsg_TouchDown(pointer.x(), + pointer.y(), + pointer.timestamp(), + pointer.pointer_id())); + } + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnPointerReleased( + winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args) { + PointerInfoHandler pointer; + HRESULT hr = pointer.Init(args); + if (FAILED(hr)) + return hr; + + if (pointer.IsMouse()) { + // TODO: this is wrong, more than one pointer may be down at a time. + mouse_down_flags_ = ui::EF_NONE; + ui_channel_->Send(new MetroViewerHostMsg_MouseButton( + pointer.x(), + pointer.y(), + 0, + ui::ET_MOUSE_RELEASED, + static_cast<ui::EventFlags>( + pointer.flags() | GetKeyboardEventFlags()))); + } else { + DCHECK(pointer.IsTouch()); + ui_channel_->Send(new MetroViewerHostMsg_TouchUp(pointer.x(), + pointer.y(), + pointer.timestamp(), + pointer.pointer_id())); + } + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnWheel( + winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args) { + PointerInfoHandler pointer; + HRESULT hr = pointer.Init(args); + if (FAILED(hr)) + return hr; + DCHECK(pointer.IsMouse()); + ui_channel_->Send(new MetroViewerHostMsg_MouseButton(pointer.x(), pointer.y(), + pointer.wheel_delta(), + ui::ET_MOUSEWHEEL, + ui::EF_NONE)); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnKeyDown( + winui::Core::ICoreWindow* sender, + winui::Core::IKeyEventArgs* args) { + winsys::VirtualKey virtual_key; + HRESULT hr = args->get_VirtualKey(&virtual_key); + if (FAILED(hr)) + return hr; + winui::Core::CorePhysicalKeyStatus status; + hr = args->get_KeyStatus(&status); + if (FAILED(hr)) + return hr; + + ui_channel_->Send(new MetroViewerHostMsg_KeyDown(virtual_key, + status.RepeatCount, + status.ScanCode, + GetKeyboardEventFlags())); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnKeyUp( + winui::Core::ICoreWindow* sender, + winui::Core::IKeyEventArgs* args) { + winsys::VirtualKey virtual_key; + HRESULT hr = args->get_VirtualKey(&virtual_key); + if (FAILED(hr)) + return hr; + winui::Core::CorePhysicalKeyStatus status; + hr = args->get_KeyStatus(&status); + if (FAILED(hr)) + return hr; + + ui_channel_->Send(new MetroViewerHostMsg_KeyUp(virtual_key, + status.RepeatCount, + status.ScanCode, + GetKeyboardEventFlags())); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnAcceleratorKeyDown( + winui::Core::ICoreDispatcher* sender, + winui::Core::IAcceleratorKeyEventArgs* args) { + winsys::VirtualKey virtual_key; + HRESULT hr = args->get_VirtualKey(&virtual_key); + if (FAILED(hr)) + return hr; + winui::Core::CorePhysicalKeyStatus status; + hr = args->get_KeyStatus(&status); + if (FAILED(hr)) + return hr; + + winui::Core::CoreAcceleratorKeyEventType event_type; + hr = args->get_EventType(&event_type); + if (FAILED(hr)) + return hr; + + uint32 keyboard_flags = GetKeyboardEventFlags(); + + switch (event_type) { + case winui::Core::CoreAcceleratorKeyEventType_SystemCharacter: + ui_channel_->Send(new MetroViewerHostMsg_Character(virtual_key, + status.RepeatCount, + status.ScanCode, + keyboard_flags)); + break; + + case winui::Core::CoreAcceleratorKeyEventType_SystemKeyDown: + ui_channel_->Send(new MetroViewerHostMsg_KeyDown(virtual_key, + status.RepeatCount, + status.ScanCode, + keyboard_flags)); + break; + + case winui::Core::CoreAcceleratorKeyEventType_SystemKeyUp: + ui_channel_->Send(new MetroViewerHostMsg_KeyUp(virtual_key, + status.RepeatCount, + status.ScanCode, + keyboard_flags)); + break; + + default: + break; + } + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnCharacterReceived( + winui::Core::ICoreWindow* sender, + winui::Core::ICharacterReceivedEventArgs* args) { + unsigned int char_code = 0; + HRESULT hr = args->get_KeyCode(&char_code); + if (FAILED(hr)) + return hr; + + winui::Core::CorePhysicalKeyStatus status; + hr = args->get_KeyStatus(&status); + if (FAILED(hr)) + return hr; + + ui_channel_->Send(new MetroViewerHostMsg_Character(char_code, + status.RepeatCount, + status.ScanCode, + GetKeyboardEventFlags())); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnWindowActivated( + winui::Core::ICoreWindow* sender, + winui::Core::IWindowActivatedEventArgs* args) { + winui::Core::CoreWindowActivationState state; + HRESULT hr = args->get_WindowActivationState(&state); + if (FAILED(hr)) + return hr; + + // Treat both full activation (Ash was reopened from the Start Screen or from + // any other Metro entry point in Windows) and pointer activation (user + // clicked back in Ash after using another app on another monitor) the same. + if (state == winui::Core::CoreWindowActivationState_CodeActivated || + state == winui::Core::CoreWindowActivationState_PointerActivated) { + if (text_service_) + text_service_->OnWindowActivated(); + ui_channel_->Send(new MetroViewerHostMsg_WindowActivated()); + } + return S_OK; +} + +HRESULT ChromeAppViewAsh::HandleSearchRequest( + winapp::Activation::IActivatedEventArgs* args) { + mswr::ComPtr<winapp::Activation::ISearchActivatedEventArgs> search_args; + CheckHR(args->QueryInterface( + winapp::Activation::IID_ISearchActivatedEventArgs, &search_args)); + + if (!ui_channel_) { + DVLOG(1) << "Launched to handle search request"; + LaunchChromeBrowserProcess(L"--windows8-search", args); + } + + mswrw::HString search_string; + CheckHR(search_args->get_QueryText(search_string.GetAddressOf())); + string16 search_text(MakeStdWString(search_string.Get())); + + ui_loop_.PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnSearchRequest, + base::Unretained(this), + search_text)); + return S_OK; +} + +HRESULT ChromeAppViewAsh::HandleProtocolRequest( + winapp::Activation::IActivatedEventArgs* args) { + DVLOG(1) << __FUNCTION__; + if (!ui_channel_) + DVLOG(1) << "Launched to handle url request"; + + mswr::ComPtr<winapp::Activation::IProtocolActivatedEventArgs> + protocol_args; + CheckHR(args->QueryInterface( + winapp::Activation::IID_IProtocolActivatedEventArgs, + &protocol_args)); + + mswr::ComPtr<winfoundtn::IUriRuntimeClass> uri; + protocol_args->get_Uri(&uri); + mswrw::HString url; + uri->get_AbsoluteUri(url.GetAddressOf()); + string16 actual_url(MakeStdWString(url.Get())); + DVLOG(1) << "Received url request: " << actual_url; + + ui_loop_.PostTask(FROM_HERE, + base::Bind(&ChromeAppViewAsh::OnNavigateToUrl, + base::Unretained(this), + actual_url)); + return S_OK; +} + +HRESULT ChromeAppViewAsh::OnEdgeGestureCompleted( + winui::Input::IEdgeGesture* gesture, + winui::Input::IEdgeGestureEventArgs* args) { + // Swipe from edge gesture (and win+z) is equivalent to pressing F11. + // TODO(cpu): Make this cleaner for m33. + ui_channel_->Send(new MetroViewerHostMsg_KeyDown(VK_F11, 1, 0, 0)); + ::Sleep(15); + ui_channel_->Send(new MetroViewerHostMsg_KeyUp(VK_F11, 1, 0, 0)); + return S_OK; +} + +void ChromeAppViewAsh::OnSearchRequest(const string16& search_string) { + DCHECK(ui_channel_); + ui_channel_->Send(new MetroViewerHostMsg_SearchRequest(search_string)); +} + +void ChromeAppViewAsh::OnNavigateToUrl(const string16& url) { + DCHECK(ui_channel_); + ui_channel_->Send(new MetroViewerHostMsg_OpenURL(url)); +} + +HRESULT ChromeAppViewAsh::OnSizeChanged(winui::Core::ICoreWindow* sender, + winui::Core::IWindowSizeChangedEventArgs* args) { + if (!window_) { + return S_OK; + } + + winfoundtn::Size size; + HRESULT hr = args->get_Size(&size); + if (FAILED(hr)) + return hr; + + uint32 cx = static_cast<uint32>(size.Width); + uint32 cy = static_cast<uint32>(size.Height); + + DVLOG(1) << "Window size changed: width=" << cx << ", height=" << cy; + ui_channel_->Send(new MetroViewerHostMsg_WindowSizeChanged(cx, cy)); + return S_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +ChromeAppViewFactory::ChromeAppViewFactory( + winapp::Core::ICoreApplication* icore_app, + LPTHREAD_START_ROUTINE host_main, + void* 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) { + *view = mswr::Make<ChromeAppViewAsh>().Detach(); + return (*view) ? S_OK : E_OUTOFMEMORY; +} diff --git a/chromium/win8/metro_driver/chrome_app_view_ash.h b/chromium/win8/metro_driver/chrome_app_view_ash.h new file mode 100644 index 00000000000..f6577925119 --- /dev/null +++ b/chromium/win8/metro_driver/chrome_app_view_ash.h @@ -0,0 +1,208 @@ +// 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_METRO_DRIVER_CHROME_APP_VIEW_ASH_H_ +#define WIN8_METRO_DRIVER_CHROME_APP_VIEW_ASH_H_ + +#include <windows.applicationmodel.core.h> +#include <windows.ui.core.h> +#include <windows.ui.input.h> +#include <windows.ui.viewmanagement.h> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string16.h" +#include "ui/events/event_constants.h" +#include "win8/metro_driver/direct3d_helper.h" +#include "win8/metro_driver/ime/ime_popup_observer.h" +#include "win8/metro_driver/ime/input_source_observer.h" +#include "win8/metro_driver/ime/text_service_delegate.h" + +namespace base { +class FilePath; +} + +namespace IPC { +class Listener; +class ChannelProxy; +} + +namespace metro_driver { +class InputSource; +class TextService; +} + +namespace metro_viewer { +struct CharacterBounds; +struct UnderlineInfo; +} + +class OpenFilePickerSession; +class SaveFilePickerSession; +class FolderPickerSession; +class FilePickerSessionBase; + +struct MetroViewerHostMsg_SaveAsDialogParams; + +class ChromeAppViewAsh + : public mswr::RuntimeClass<winapp::Core::IFrameworkView>, + public metro_driver::ImePopupObserver, + public metro_driver::InputSourceObserver, + public metro_driver::TextServiceDelegate { + public: + ChromeAppViewAsh(); + ~ChromeAppViewAsh(); + + // IViewProvider overrides. + IFACEMETHOD(Initialize)(winapp::Core::ICoreApplicationView* view); + IFACEMETHOD(SetWindow)(winui::Core::ICoreWindow* window); + IFACEMETHOD(Load)(HSTRING entryPoint); + IFACEMETHOD(Run)(); + IFACEMETHOD(Uninitialize)(); + + // Helper function to unsnap the chrome metro app if it is snapped. + // Returns S_OK on success. + static HRESULT Unsnap(); + + void OnActivateDesktop(const base::FilePath& file_path, bool ash_exit); + void OnOpenURLOnDesktop(const base::FilePath& shortcut, const string16& url); + void OnSetCursor(HCURSOR cursor); + void OnDisplayFileOpenDialog(const string16& title, + const string16& filter, + const base::FilePath& default_path, + bool allow_multiple_files); + void OnDisplayFileSaveAsDialog( + const MetroViewerHostMsg_SaveAsDialogParams& params); + void OnDisplayFolderPicker(const string16& title); + void OnSetCursorPos(int x, int y); + + // This function is invoked when the open file operation completes. The + // result of the operation is passed in along with the OpenFilePickerSession + // instance which is deleted after we read the required information from + // the OpenFilePickerSession class. + void OnOpenFileCompleted(OpenFilePickerSession* open_file_picker, + bool success); + + // This function is invoked when the save file operation completes. The + // result of the operation is passed in along with the SaveFilePickerSession + // instance which is deleted after we read the required information from + // the SaveFilePickerSession class. + void OnSaveFileCompleted(SaveFilePickerSession* save_file_picker, + bool success); + + // This function is invoked when the folder picker operation completes. The + // result of the operation is passed in along with the FolderPickerSession + // instance which is deleted after we read the required information from + // the FolderPickerSession class. + void OnFolderPickerCompleted(FolderPickerSession* folder_picker, + bool success); + + void OnImeCancelComposition(); + void OnImeUpdateTextInputClient( + const std::vector<int32>& input_scopes, + const std::vector<metro_viewer::CharacterBounds>& character_bounds); + + HWND core_window_hwnd() const { return core_window_hwnd_; } + + + private: + // ImePopupObserver overrides. + virtual void OnImePopupChanged(ImePopupObserver::EventType event) OVERRIDE; + + // InputSourceObserver overrides. + virtual void OnInputSourceChanged() OVERRIDE; + + // TextServiceDelegate overrides. + virtual void OnCompositionChanged( + const string16& text, + int32 selection_start, + int32 selection_end, + const std::vector<metro_viewer::UnderlineInfo>& underlines) OVERRIDE; + virtual void OnTextCommitted(const string16& text) OVERRIDE; + + HRESULT OnActivate(winapp::Core::ICoreApplicationView* view, + winapp::Activation::IActivatedEventArgs* args); + + HRESULT OnPointerMoved(winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args); + + HRESULT OnPointerPressed(winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args); + + HRESULT OnPointerReleased(winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args); + + HRESULT OnWheel(winui::Core::ICoreWindow* sender, + winui::Core::IPointerEventArgs* args); + + HRESULT OnKeyDown(winui::Core::ICoreWindow* sender, + winui::Core::IKeyEventArgs* args); + + HRESULT OnKeyUp(winui::Core::ICoreWindow* sender, + winui::Core::IKeyEventArgs* args); + + // Invoked for system keys like Alt, etc. + HRESULT OnAcceleratorKeyDown(winui::Core::ICoreDispatcher* sender, + winui::Core::IAcceleratorKeyEventArgs* args); + + HRESULT OnCharacterReceived(winui::Core::ICoreWindow* sender, + winui::Core::ICharacterReceivedEventArgs* args); + + HRESULT OnWindowActivated(winui::Core::ICoreWindow* sender, + winui::Core::IWindowActivatedEventArgs* args); + + // Helper to handle search requests received via the search charm in ASH. + HRESULT HandleSearchRequest(winapp::Activation::IActivatedEventArgs* args); + // Helper to handle http/https url requests in ASH. + HRESULT HandleProtocolRequest(winapp::Activation::IActivatedEventArgs* args); + + HRESULT OnEdgeGestureCompleted(winui::Input::IEdgeGesture* gesture, + winui::Input::IEdgeGestureEventArgs* args); + + // Tasks posted to the UI thread to initiate the search/url navigation + // requests. + void OnSearchRequest(const string16& search_string); + void OnNavigateToUrl(const string16& url); + + HRESULT OnSizeChanged(winui::Core::ICoreWindow* sender, + winui::Core::IWindowSizeChangedEventArgs* args); + + mswr::ComPtr<winui::Core::ICoreWindow> window_; + mswr::ComPtr<winapp::Core::ICoreApplicationView> view_; + EventRegistrationToken activated_token_; + EventRegistrationToken pointermoved_token_; + EventRegistrationToken pointerpressed_token_; + EventRegistrationToken pointerreleased_token_; + EventRegistrationToken wheel_token_; + EventRegistrationToken keydown_token_; + EventRegistrationToken keyup_token_; + EventRegistrationToken character_received_token_; + EventRegistrationToken accel_keydown_token_; + EventRegistrationToken accel_keyup_token_; + EventRegistrationToken window_activated_token_; + EventRegistrationToken sizechange_token_; + EventRegistrationToken edgeevent_token_; + + // Keep state about which button is currently down, if any, as PointerMoved + // events do not contain that state, but Ash's MouseEvents need it. + ui::EventFlags mouse_down_flags_; + + // Set the D3D swap chain and nothing else. + metro_driver::Direct3DHelper direct3d_helper_; + + // The channel to Chrome, in particular to the MetroViewerProcessHost. + IPC::ChannelProxy* ui_channel_; + + // The actual window behind the view surface. + HWND core_window_hwnd_; + + // UI message loop to allow message passing into this thread. + base::MessageLoop ui_loop_; + + // For IME support. + scoped_ptr<metro_driver::InputSource> input_source_; + scoped_ptr<metro_driver::TextService> text_service_; +}; + +#endif // WIN8_METRO_DRIVER_CHROME_APP_VIEW_ASH_H_ diff --git a/chromium/win8/metro_driver/chrome_url_launch_handler.cc b/chromium/win8/metro_driver/chrome_url_launch_handler.cc new file mode 100644 index 00000000000..897276a9b12 --- /dev/null +++ b/chromium/win8/metro_driver/chrome_url_launch_handler.cc @@ -0,0 +1,206 @@ +// 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/command_line.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); +} + +// |launch_args| is an encoded command line, minus the executable name. To +// find the URL to launch the first argument is used. If any other parameters +// are encoded in |launch_args| they are ignored. +string16 ChromeUrlLaunchHandler::GetUrlFromLaunchArgs( + const string16& launch_args) { + if (launch_args == L"opennewwindow") { + VLOG(1) << "Returning new tab url"; + return L"chrome://newtab"; + } + string16 dummy_command_line(L"dummy.exe "); + dummy_command_line.append(launch_args); + CommandLine command_line = CommandLine::FromString(dummy_command_line); + CommandLine::StringVector args = command_line.GetArgs(); + if (args.size() > 0) + return args[0]; + + 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"); + + if (globals.host_windows.empty()) { + DVLOG(1) << "No chrome windows registered. Ignoring nav request"; + return; + } + + // 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/chromium/win8/metro_driver/chrome_url_launch_handler.h b/chromium/win8/metro_driver/chrome_url_launch_handler.h new file mode 100644 index 00000000000..d8c7ed01b7b --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/devices_handler.cc b/chromium/win8/metro_driver/devices_handler.cc new file mode 100644 index 00000000000..20fd413bf35 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/devices_handler.h b/chromium/win8/metro_driver/devices_handler.h new file mode 100644 index 00000000000..fdb2226c9fc --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/direct3d_helper.cc b/chromium/win8/metro_driver/direct3d_helper.cc new file mode 100644 index 00000000000..056e84b4cb9 --- /dev/null +++ b/chromium/win8/metro_driver/direct3d_helper.cc @@ -0,0 +1,128 @@ +// Copyright 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/direct3d_helper.h" +#include "win8/metro_driver/winrt_utils.h" + +#include "base/logging.h" + +#include <windows.graphics.display.h> + +namespace { + +void CheckIfFailed(HRESULT hr) { + DCHECK(!FAILED(hr)); + if (FAILED(hr)) + DVLOG(0) << "Direct3D call failed, hr = " << hr; +} + +float GetLogicalDpi() { + mswr::ComPtr<wingfx::Display::IDisplayPropertiesStatics> display_properties; + CheckIfFailed(winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Display_DisplayProperties, + display_properties.GetAddressOf())); + float dpi = 0.0; + CheckIfFailed(display_properties->get_LogicalDpi(&dpi)); + return dpi; +} + +float ConvertDipsToPixels(float dips) { + static const float dips_per_inch = 96.f; + float logical_dpi = GetLogicalDpi(); + return floor(dips * logical_dpi / dips_per_inch + 0.5f); +} + +} + +namespace metro_driver { + +Direct3DHelper::Direct3DHelper() { +} + +Direct3DHelper::~Direct3DHelper() { +} + +void Direct3DHelper::Initialize(winui::Core::ICoreWindow* window) { + window_ = window; + CreateDeviceResources(); + CreateWindowSizeDependentResources(); +} + +// TODO(scottmg): Need to handle resize messages and recreation. + +void Direct3DHelper::CreateDeviceResources() { + unsigned int creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + 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; + CheckIfFailed( + D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + creation_flags, + feature_levels, + ARRAYSIZE(feature_levels), + D3D11_SDK_VERSION, + &device, + &feature_level_, + &context)); + CheckIfFailed(device.As(&d3d_device_)); + CheckIfFailed(context.As(&d3d_context_)); +} + +void Direct3DHelper::CreateWindowSizeDependentResources() { + CheckIfFailed(window_->get_Bounds(&window_bounds_)); + float window_width = ConvertDipsToPixels(window_bounds_.Width); + float window_height = ConvertDipsToPixels(window_bounds_.Height); + + // TODO(scottmg): Orientation. + + if (swap_chain_ != nullptr) { + // TODO(scottmg): Resize if it already exists. + NOTIMPLEMENTED(); + } else { + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = { 0 }; + swap_chain_desc.Width = window_width; + swap_chain_desc.Height = window_height; + swap_chain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swap_chain_desc.Stereo = false; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; // TODO(scottmg): Probably 1 is fine. + swap_chain_desc.Scaling = DXGI_SCALING_NONE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + swap_chain_desc.Flags = 0; + + mswr::ComPtr<IDXGIDevice1> dxgi_device; + CheckIfFailed(d3d_device_.As(&dxgi_device)); + + mswr::ComPtr<IDXGIAdapter> dxgi_adapter; + CheckIfFailed(dxgi_device->GetAdapter(&dxgi_adapter)); + + mswr::ComPtr<IDXGIFactory2> dxgi_factory; + CheckIfFailed(dxgi_adapter->GetParent( + __uuidof(IDXGIFactory2), &dxgi_factory)); + + CheckIfFailed(dxgi_factory->CreateSwapChainForCoreWindow( + d3d_device_.Get(), + reinterpret_cast<IUnknown*>(window_), + &swap_chain_desc, + nullptr, + &swap_chain_)); + } +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/direct3d_helper.h b/chromium/win8/metro_driver/direct3d_helper.h new file mode 100644 index 00000000000..a5547ba08fe --- /dev/null +++ b/chromium/win8/metro_driver/direct3d_helper.h @@ -0,0 +1,44 @@ +// Copyright 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_METRO_DRIVER_DIRECT3D_HELPER_ +#define WIN8_METRO_DRIVER_DIRECT3D_HELPER_ + +#include "base/basictypes.h" + +#include <windows.ui.core.h> +#include <windows.foundation.h> +#include <d3d11_1.h> + +namespace metro_driver { + +// We need to initalize a Direct3D device and swapchain so that the browser +// can Present to our HWND. This class takes care of creating and keeping the +// swapchain up to date. +class Direct3DHelper { + public: + Direct3DHelper(); + ~Direct3DHelper(); + + void Initialize(winui::Core::ICoreWindow* window); + + private: + void CreateDeviceResources(); + void CreateWindowSizeDependentResources(); + + winui::Core::ICoreWindow* window_; + + mswr::ComPtr<ID3D11Device1> d3d_device_; + mswr::ComPtr<ID3D11DeviceContext1> d3d_context_; + mswr::ComPtr<IDXGISwapChain1> swap_chain_; + D3D_FEATURE_LEVEL feature_level_; + + ABI::Windows::Foundation::Rect window_bounds_; + + DISALLOW_COPY_AND_ASSIGN(Direct3DHelper); +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_DIRECT3D_HELPER_ diff --git a/chromium/win8/metro_driver/display_properties.cc b/chromium/win8/metro_driver/display_properties.cc new file mode 100644 index 00000000000..a7cf42794be --- /dev/null +++ b/chromium/win8/metro_driver/display_properties.cc @@ -0,0 +1,34 @@ +// Copyright 2013 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 <windows.h> +#include <wrl\implements.h> +#include <wrl\wrappers\corewrappers.h> +#include <windows.foundation.h> +#include <windows.graphics.display.h> +#include "base/win/scoped_com_initializer.h" +#include "winrt_utils.h" + +#pragma comment(lib, "runtimeobject.lib") + +extern "C" { + + __declspec(dllexport) float GetModernUIScale() { + base::win::ScopedCOMInitializer com_init; + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Display::IDisplayPropertiesStatics> + display_properties; + if (SUCCEEDED(winrt_utils::CreateActivationFactory( + RuntimeClass_Windows_Graphics_Display_DisplayProperties, + display_properties.GetAddressOf()))) { + ABI::Windows::Graphics::Display::ResolutionScale resolution_scale; + if (SUCCEEDED(display_properties->get_ResolutionScale(&resolution_scale))) + return static_cast<float>(resolution_scale) / 100.0f; + } + return 1.0f; +} + +} diff --git a/chromium/win8/metro_driver/file_picker.cc b/chromium/win8/metro_driver/file_picker.cc new file mode 100644 index 00000000000..cf1c878b1fd --- /dev/null +++ b/chromium/win8/metro_driver/file_picker.cc @@ -0,0 +1,619 @@ +// 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/files/file_path.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/metro.h" +#include "base/win/scoped_comptr.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) { + if (strings_.size() > UINT_MAX) + return E_UNEXPECTED; + *size = static_cast<unsigned>(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)) { + UINT32 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 " << static_cast<int>(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 " << static_cast<int>(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; + 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 = base::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(); + + unsigned int 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. + base::FilePath base_path; + + // Iterate through the collection and append the file paths to the result. + for (unsigned int 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; + + base::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. + base::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 = base::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 " << static_cast<int>(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/chromium/win8/metro_driver/file_picker.h b/chromium/win8/metro_driver/file_picker.h new file mode 100644 index 00000000000..ef56cb39082 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/file_picker_ash.cc b/chromium/win8/metro_driver/file_picker_ash.cc new file mode 100644 index 00000000000..343d7b4f99a --- /dev/null +++ b/chromium/win8/metro_driver/file_picker_ash.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2013 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_ash.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/metro.h" +#include "base/win/scoped_comptr.h" +#include "ui/metro_viewer/metro_viewer_messages.h" +#include "win8/metro_driver/chrome_app_view_ash.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) { + if (strings_.size() > UINT_MAX) + return E_UNEXPECTED; + *size = static_cast<unsigned>(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_; +}; + +} // namespace + +FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh* app_view, + const string16& title, + const string16& filter, + const base::FilePath& default_path) + : app_view_(app_view), + title_(title), + filter_(filter), + default_path_(default_path), + success_(false) { +} + +bool FilePickerSessionBase::Run() { + if (!DoFilePicker()) + return false; + return success_; +} + +bool 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 = ChromeAppViewAsh::Unsnap(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr; + return false; + } + hr = StartFilePicker(); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to start file picker, error 0x" + << std::hex << hr; + return false; + } + return true; +} + +OpenFilePickerSession::OpenFilePickerSession( + ChromeAppViewAsh* app_view, + const string16& title, + const string16& filter, + const base::FilePath& default_path, + bool allow_multi_select) + : FilePickerSessionBase(app_view, title, filter, default_path), + allow_multi_select_(allow_multi_select) { +} + +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)) { + UINT32 path_len = 0; + const wchar_t* path_str = + ::WindowsGetStringRawBuffer(file_path.Get(), &path_len); + + result_ = path_str; + success_ = true; + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); + } + app_view_->OnOpenFileCompleted(this, success_); + 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)) { + success_ = true; + // The code below has been copied from the + // SelectFileDialogImpl::RunOpenMultiFileDialog function in + // select_file_dialog_win.cc. + // TODO(ananta) + // Consolidate this into a common place. + const wchar_t* selection = result.c_str(); + std::vector<base::FilePath> files; + + while (*selection) { // Empty string indicates end of list. + files.push_back(base::FilePath(selection)); + // Skip over filename and null-terminator. + selection += files.back().value().length() + 1; + } + if (files.empty()) { + success_ = false; + } else if (files.size() == 1) { + // When there is one file, it contains the path and filename. + filenames_ = files; + } else if (files.size() > 1) { + // Otherwise, the first string is the path, and the remainder are + // filenames. + std::vector<base::FilePath>::iterator path = files.begin(); + for (std::vector<base::FilePath>::iterator file = path + 1; + file != files.end(); ++file) { + filenames_.push_back(path->Append(*file)); + } + } + } + } else { + LOG(ERROR) << "NULL StorageFileVectorCollection"; + } + } else { + LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); + } + app_view_->OnOpenFileCompleted(this, success_); + return S_OK; +} + +HRESULT OpenFilePickerSession::StartFilePicker() { + 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 (filter_.empty()) { + 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 = filter_.c_str(); + 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; + 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 = base::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 (allow_multi_select_) { + 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(); + + unsigned int 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. + base::FilePath base_path; + + // Iterate through the collection and append the file paths to the result. + for (unsigned int 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; + + base::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. + base::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( + ChromeAppViewAsh* app_view, + const MetroViewerHostMsg_SaveAsDialogParams& params) + : FilePickerSessionBase(app_view, + params.title, + params.filter, + params.suggested_name), + filter_index_(params.filter_index) { +} + +int SaveFilePickerSession::filter_index() const { + // TODO(ananta) + // Add support for returning the correct filter index. This does not work in + // regular Chrome metro on trunk as well. + // BUG: https://code.google.com/p/chromium/issues/detail?id=172704 + return filter_index_; +} + +HRESULT SaveFilePickerSession::StartFilePicker() { + 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 (!filter_.empty()) { + // 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 = filter_.c_str(); + 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 = base::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 (!default_path_.empty()) { + string16 file_part = default_path_.BaseName().value(); + // If the suggested_name is a root directory, then don't set it as the + // suggested name. + if (file_part.size() == 1 && file_part[0] == L'\\') + file_part.clear(); + hr = picker->put_SuggestedFileName( + mswrw::HStringReference(file_part.c_str()).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()); + result_ = path_str; + success_ = true; + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); + } + app_view_->OnSaveFileCompleted(this, success_); + return S_OK; +} + +FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view, + const string16& title) + : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {} + +HRESULT FolderPickerSession::StartFilePicker() { + mswrw::HStringReference class_name( + RuntimeClass_Windows_Storage_Pickers_FolderPicker); + + // Create the folder picker. + mswr::ComPtr<winstorage::Pickers::IFolderPicker> 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; + + hr = filter->Append(mswrw::HStringReference(L"*").Get()); + if (FAILED(hr)) + return hr; + + mswr::ComPtr<FolderPickerAsyncOp> completion; + hr = picker->PickSingleFolderAsync(&completion); + if (FAILED(hr)) + return hr; + + // Create the callback method. + typedef winfoundtn::IAsyncOperationCompletedHandler< + winstorage::StorageFolder*> HandlerDoneType; + mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>( + this, &FolderPickerSession::FolderPickerDone)); + DCHECK(handler.Get() != NULL); + hr = completion->put_Completed(handler.Get()); + return hr; +} + +HRESULT FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp* async, + AsyncStatus status) { + if (status == Completed) { + mswr::ComPtr<winstorage::IStorageFolder> folder; + HRESULT hr = async->GetResults(folder.GetAddressOf()); + + if (folder) { + mswr::ComPtr<winstorage::IStorageItem> storage_item; + if (SUCCEEDED(hr)) + hr = folder.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()); + result_ = path_str; + success_ = true; + } + } else { + LOG(ERROR) << "NULL IStorageItem"; + } + } else { + LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); + } + app_view_->OnFolderPickerCompleted(this, success_); + return S_OK; +} + diff --git a/chromium/win8/metro_driver/file_picker_ash.h b/chromium/win8/metro_driver/file_picker_ash.h new file mode 100644 index 00000000000..b08115698d5 --- /dev/null +++ b/chromium/win8/metro_driver/file_picker_ash.h @@ -0,0 +1,167 @@ +// Copyright (c) 2013 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_ASH_H_ +#define CHROME_BROWSER_UI_METRO_DRIVER_FILE_PICKER_ASH_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/strings/string16.h" + +class ChromeAppViewAsh; +struct MetroViewerHostMsg_SaveAsDialogParams; + +namespace base { +class FilePath; +} + +// Base class for the file pickers. +class FilePickerSessionBase { + public: + // Creates a file picker for open_file_name. + explicit FilePickerSessionBase(ChromeAppViewAsh* app_view, + const string16& title, + const string16& filter, + const base::FilePath& default_path); + + virtual ~FilePickerSessionBase() { + } + + // Runs the picker, returns true on success. + bool Run(); + + const string16& result() const { + return result_; + } + + bool success() const { + return success_; + } + + 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; + + // True iff a file picker has successfully finished. + bool success_; + + // The title of the file picker. + string16 title_; + + // The file type filter. + string16 filter_; + + // The starting directory/file name. + base::FilePath default_path_; + + // Pointer to the ChromeAppViewAsh instance. We notify the ChromeAppViewAsh + // instance when the file open/save operations complete. + ChromeAppViewAsh* app_view_; + + string16 result_; + + private: + // Initiate a file picker, must be called on the main metro thread. + // Returns true on success. + bool DoFilePicker(); + + DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase); +}; + +// Provides functionality to display the open file/multiple open file pickers +// metro dialog. +class OpenFilePickerSession : public FilePickerSessionBase { + public: + explicit OpenFilePickerSession(ChromeAppViewAsh* app_view, + const string16& title, + const string16& filter, + const base::FilePath& default_path, + bool allow_multi_select); + + const std::vector<base::FilePath>& filenames() const { + return filenames_; + } + + const bool allow_multi_select() const { + return allow_multi_select_; + } + + 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: + // True if the multi file picker is to be displayed. + bool allow_multi_select_; + // If multi select is true then this member contains the list of filenames + // to be returned back. + std::vector<base::FilePath> filenames_; + + DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession); +}; + +// Provides functionality to display the save file picker. +class SaveFilePickerSession : public FilePickerSessionBase { + public: + explicit SaveFilePickerSession( + ChromeAppViewAsh* app_view, + const MetroViewerHostMsg_SaveAsDialogParams& params); + + int SaveFilePickerSession::filter_index() const; + + 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); + + int filter_index_; + + DISALLOW_COPY_AND_ASSIGN(SaveFilePickerSession); +}; + +// Provides functionality to display the folder picker. +class FolderPickerSession : public FilePickerSessionBase { + public: + explicit FolderPickerSession(ChromeAppViewAsh* app_view, + const string16& title); + + private: + virtual HRESULT StartFilePicker() OVERRIDE; + + typedef winfoundtn::IAsyncOperation<winstorage::StorageFolder*> + FolderPickerAsyncOp; + + // Called asynchronously when the folder picker is done. + HRESULT FolderPickerDone(FolderPickerAsyncOp* async, AsyncStatus status); + + DISALLOW_COPY_AND_ASSIGN(FolderPickerSession); +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_FILE_PICKER_ASH_H_ + diff --git a/chromium/win8/metro_driver/ime/ime.gypi b/chromium/win8/metro_driver/ime/ime.gypi new file mode 100644 index 00000000000..ba9db464b74 --- /dev/null +++ b/chromium/win8/metro_driver/ime/ime.gypi @@ -0,0 +1,22 @@ +# Copyright (c) 2013 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. + +{ + 'sources': [ + 'ime_popup_monitor.cc', + 'ime_popup_monitor.h', + 'ime_popup_observer.h', + 'input_scope.cc', + 'input_scope.h', + 'input_source.cc', + 'input_source.h', + 'input_source_observer.h', + 'text_service.cc', + 'text_service.h', + 'text_service_delegate.h', + 'text_store.cc', + 'text_store.h', + 'text_store_delegate.h', + ], +} diff --git a/chromium/win8/metro_driver/ime/ime_popup_monitor.cc b/chromium/win8/metro_driver/ime/ime_popup_monitor.cc new file mode 100644 index 00000000000..a6368ae9e26 --- /dev/null +++ b/chromium/win8/metro_driver/ime/ime_popup_monitor.cc @@ -0,0 +1,82 @@ +// Copyright 2013 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/ime/ime_popup_monitor.h" + +#include <windows.h> + +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "win8/metro_driver/ime/ime_popup_observer.h" + +namespace metro_driver { +namespace { + +ImePopupObserver* g_observer_ = NULL; +HWINEVENTHOOK g_hook_handle_ = NULL; + +void CALLBACK ImeEventCallback(HWINEVENTHOOK win_event_hook_handle, + DWORD event, + HWND window_handle, + LONG object_id, + LONG child_id, + DWORD event_thread, + DWORD event_time) { + // This function is registered to SetWinEventHook to be called back on the UI + // thread. + DCHECK(base::MessageLoop::current()->IsType(base::MessageLoop::TYPE_UI)); + + if (!g_observer_) + return; + switch (event) { + case EVENT_OBJECT_IME_SHOW: + g_observer_->OnImePopupChanged(ImePopupObserver::kPopupShown); + return; + case EVENT_OBJECT_IME_HIDE: + g_observer_->OnImePopupChanged(ImePopupObserver::kPopupHidden); + return; + case EVENT_OBJECT_IME_CHANGE: + g_observer_->OnImePopupChanged(ImePopupObserver::kPopupUpdated); + return; + } +} + +} // namespace + +void AddImePopupObserver(ImePopupObserver* observer) { + CHECK(g_observer_ == NULL) + << "Currently only one observer is supported at the same time."; + g_observer_ = observer; + + // IMEs running under immersive mode are supposed to generate WinEvent + // whenever their popup UI such as candidate window is shown, updated, and + // hidden to support accessibility applications. + // http://msdn.microsoft.com/en-us/library/windows/apps/hh967425.aspx#accessibility + // Note that there is another mechanism in TSF, called ITfUIElementSink, to + // subscribe when the visibility of an IME's UI element is changed. However, + // MS-IME running under immersive mode does not fully support this API. + // Thus, WinEvent is more reliable for this purpose. + g_hook_handle_ = SetWinEventHook( + EVENT_OBJECT_IME_SHOW, + EVENT_OBJECT_IME_CHANGE, + NULL, + ImeEventCallback, + GetCurrentProcessId(), // monitor the metro_driver process only + 0, // hook all threads because MS-IME emits WinEvent in a worker thread + WINEVENT_OUTOFCONTEXT); // allows us to receive message in the UI thread + LOG_IF(ERROR, !g_hook_handle_) << "SetWinEventHook failed."; +} + +void RemoveImePopupObserver(ImePopupObserver* observer) { + if (g_observer_ != observer) + return; + g_observer_ = NULL; + if (!g_hook_handle_) + return; + const bool unhook_succeeded = !!UnhookWinEvent(g_hook_handle_); + LOG_IF(ERROR, !unhook_succeeded) << "UnhookWinEvent failed."; + g_hook_handle_ = NULL; +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/ime/ime_popup_monitor.h b/chromium/win8/metro_driver/ime/ime_popup_monitor.h new file mode 100644 index 00000000000..3bf65580e98 --- /dev/null +++ b/chromium/win8/metro_driver/ime/ime_popup_monitor.h @@ -0,0 +1,19 @@ +// Copyright 2013 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_METRO_DRIVER_IME_IME_POPUP_MONITOR_H_ +#define WIN8_METRO_DRIVER_IME_IME_POPUP_MONITOR_H_ + +namespace metro_driver { + +class ImePopupObserver; + +// Adds/Removes ImePopupObserver. Currently only one observer is supported at +// the same time. +void AddImePopupObserver(ImePopupObserver* observer); +void RemoveImePopupObserver(ImePopupObserver* observer); + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_IME_POPUP_MONITOR_H_ diff --git a/chromium/win8/metro_driver/ime/ime_popup_observer.h b/chromium/win8/metro_driver/ime/ime_popup_observer.h new file mode 100644 index 00000000000..b871084a8cc --- /dev/null +++ b/chromium/win8/metro_driver/ime/ime_popup_observer.h @@ -0,0 +1,27 @@ +// Copyright 2013 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_METRO_DRIVER_IME_IME_POPUP_OBSERVER_H_ +#define WIN8_METRO_DRIVER_IME_IME_POPUP_OBSERVER_H_ + +namespace metro_driver { + +// An observer interface implemented by objects that want to be informed when +// an IME shows or hides its popup window. +class ImePopupObserver { + public: + enum EventType { + kPopupShown, + kPopupHidden, + kPopupUpdated, + }; + virtual ~ImePopupObserver() {} + + // Called whenever an IME's popup window is changed. + virtual void OnImePopupChanged(EventType type) = 0; +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_IME_POPUP_OBSERVER_H_ diff --git a/chromium/win8/metro_driver/ime/input_scope.cc b/chromium/win8/metro_driver/ime/input_scope.cc new file mode 100644 index 00000000000..82679c3d4c9 --- /dev/null +++ b/chromium/win8/metro_driver/ime/input_scope.cc @@ -0,0 +1,87 @@ +// Copyright 2013 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/ime/input_scope.h" + +#include <atlbase.h> +#include <atlcom.h> + +#include "base/logging.h" +#include "ui/base/win/atl_module.h" + +namespace metro_driver { +namespace { + +// An implementation of ITfInputScope interface. +// This implementation only covers ITfInputScope::GetInputScopes since built-in +// on-screen keyboard on Windows 8+ changes its layout depending on the returned +// value of this method. +// Although other advanced features of ITfInputScope such as phase list or +// regex support might be useful for IMEs or on-screen keyboards in future, +// no IME seems to be utilizing such features as of Windows 8.1. +class ATL_NO_VTABLE InputScopeImpl + : public CComObjectRootEx<CComMultiThreadModel>, + public ITfInputScope { + public: + InputScopeImpl() {} + + BEGIN_COM_MAP(InputScopeImpl) + COM_INTERFACE_ENTRY(ITfInputScope) + END_COM_MAP() + + void Initialize(const std::vector<InputScope>& input_scopes) { + input_scopes_ = input_scopes; + } + + private: + // ITfInputScope overrides: + STDMETHOD(GetInputScopes)(InputScope** input_scopes, UINT* count) OVERRIDE { + if (!count || !input_scopes) + return E_INVALIDARG; + *input_scopes = static_cast<InputScope*>( + CoTaskMemAlloc(sizeof(InputScope) * input_scopes_.size())); + if (!input_scopes) { + *count = 0; + return E_OUTOFMEMORY; + } + std::copy(input_scopes_.begin(), input_scopes_.end(), *input_scopes); + *count = static_cast<UINT>(input_scopes_.size()); + return S_OK; + } + STDMETHOD(GetPhrase)(BSTR** phrases, UINT* count) OVERRIDE { + return E_NOTIMPL; + } + STDMETHOD(GetRegularExpression)(BSTR* regexp) OVERRIDE { + return E_NOTIMPL; + } + STDMETHOD(GetSRGS)(BSTR* srgs) OVERRIDE { + return E_NOTIMPL; + } + STDMETHOD(GetXML)(BSTR* xml) OVERRIDE { + return E_NOTIMPL; + } + + // Data which ITfInputScope::GetInputScopes should return. + std::vector<InputScope> input_scopes_; + + DISALLOW_COPY_AND_ASSIGN(InputScopeImpl); +}; + +} // namespace + +base::win::ScopedComPtr<ITfInputScope> +CreteInputScope(const std::vector<InputScope>& input_scopes) { + ui::win::CreateATLModuleIfNeeded(); + CComObject<InputScopeImpl>* object = NULL; + HRESULT hr = CComObject<InputScopeImpl>::CreateInstance(&object); + if (FAILED(hr)) { + LOG(ERROR) << "CComObject<InputScopeImpl>::CreateInstance failed. hr = " + << hr; + return base::win::ScopedComPtr<ITfInputScope>(); + } + object->Initialize(input_scopes); + return base::win::ScopedComPtr<ITfInputScope>(object); +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/ime/input_scope.h b/chromium/win8/metro_driver/ime/input_scope.h new file mode 100644 index 00000000000..99ed283e7c7 --- /dev/null +++ b/chromium/win8/metro_driver/ime/input_scope.h @@ -0,0 +1,22 @@ +// Copyright 2013 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_METRO_DRIVER_IME_INPUT_SCOPE_H_ +#define WIN8_METRO_DRIVER_IME_INPUT_SCOPE_H_ + +#include <InputScope.h> +#include <vector> + +#include "base/win/scoped_comptr.h" + +namespace metro_driver { + +// Returns an instance of ITfInputScope object, which represents an array of +// InputScope enumeration passed as |input_scopes|. +base::win::ScopedComPtr<ITfInputScope> +CreteInputScope(const std::vector<InputScope>& input_scopes); + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_TEXT_STORE_H_ diff --git a/chromium/win8/metro_driver/ime/input_source.cc b/chromium/win8/metro_driver/ime/input_source.cc new file mode 100644 index 00000000000..dd27380108d --- /dev/null +++ b/chromium/win8/metro_driver/ime/input_source.cc @@ -0,0 +1,170 @@ +// Copyright 2013 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/ime/input_source.h" + +#include <atlbase.h> +#include <atlcom.h> +#include <msctf.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/win/scoped_comptr.h" +#include "ui/base/win/atl_module.h" +#include "win8/metro_driver/ime/input_source_observer.h" + +namespace metro_driver { +namespace { + +// An implementation of ITfLanguageProfileNotifySink interface, which will be +// used to receive notifications when the text input source is changed. +class ATL_NO_VTABLE InputSourceMonitor + : public CComObjectRootEx<CComMultiThreadModel>, + public ITfLanguageProfileNotifySink { + public: + InputSourceMonitor() + : cookie_(TF_INVALID_COOKIE) { + } + + BEGIN_COM_MAP(InputSourceMonitor) + COM_INTERFACE_ENTRY(ITfLanguageProfileNotifySink) + END_COM_MAP() + + bool Initialize(ITfSource* source) { + DWORD cookie = TF_INVALID_COOKIE; + HRESULT hr = source->AdviseSink(IID_ITfLanguageProfileNotifySink, + this, + &cookie); + if (FAILED(hr)) { + LOG(ERROR) << "ITfSource::AdviseSink failed. hr = " << hr; + return false; + } + cookie_ = cookie; + source_ = source; + return true; + } + + void SetCallback(base::Closure on_language_chanaged) { + on_language_chanaged_ = on_language_chanaged; + } + + void Unadvise() { + if (cookie_ == TF_INVALID_COOKIE || !source_) + return; + if (FAILED(source_->UnadviseSink(cookie_))) + return; + cookie_ = TF_INVALID_COOKIE; + source_.Release(); + } + + private: + // ITfLanguageProfileNotifySink overrides: + STDMETHOD(OnLanguageChange)(LANGID langid, BOOL *accept) OVERRIDE { + if (!accept) + return E_INVALIDARG; + *accept = TRUE; + return S_OK; + } + + STDMETHOD(OnLanguageChanged)() OVERRIDE { + if (!on_language_chanaged_.is_null()) + on_language_chanaged_.Run(); + return S_OK; + } + + base::Closure on_language_chanaged_; + base::win::ScopedComPtr<ITfSource> source_; + DWORD cookie_; + + DISALLOW_COPY_AND_ASSIGN(InputSourceMonitor); +}; + +class InputSourceImpl : public InputSource { + public: + InputSourceImpl(ITfInputProcessorProfileMgr* profile_manager, + InputSourceMonitor* monitor) + : profile_manager_(profile_manager), + monitor_(monitor) { + monitor_->SetCallback(base::Bind(&InputSourceImpl::OnLanguageChanged, + base::Unretained(this))); + } + virtual ~InputSourceImpl() { + monitor_->SetCallback(base::Closure()); + monitor_->Unadvise(); + } + + private: + // InputSource overrides. + virtual bool GetActiveSource(LANGID* langid, bool* is_ime) OVERRIDE { + TF_INPUTPROCESSORPROFILE profile = {}; + HRESULT hr = profile_manager_->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, + &profile); + if (FAILED(hr)) { + LOG(ERROR) << "ITfInputProcessorProfileMgr::GetActiveProfile failed." + << " hr = " << hr; + return false; + } + *langid = profile.langid; + *is_ime = profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR; + return true; + } + virtual void AddObserver(InputSourceObserver* observer) OVERRIDE { + observer_list_.AddObserver(observer); + } + virtual void RemoveObserver(InputSourceObserver* observer) OVERRIDE { + observer_list_.RemoveObserver(observer); + } + void OnLanguageChanged() { + FOR_EACH_OBSERVER(InputSourceObserver, + observer_list_, + OnInputSourceChanged()); + } + + base::win::ScopedComPtr<ITfInputProcessorProfileMgr> profile_manager_; + scoped_refptr<InputSourceMonitor> monitor_; + ObserverList<InputSourceObserver> observer_list_; + + DISALLOW_COPY_AND_ASSIGN(InputSourceImpl); +}; + +} // namespace + +// static +scoped_ptr<InputSource> InputSource::Create() { + ui::win::CreateATLModuleIfNeeded(); + + base::win::ScopedComPtr<ITfInputProcessorProfileMgr> profile_manager; + HRESULT hr = profile_manager.CreateInstance(CLSID_TF_InputProcessorProfiles); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to instantiate CLSID_TF_InputProcessorProfiles." + << " hr = " << hr; + return scoped_ptr<InputSource>(); + } + base::win::ScopedComPtr<ITfSource> profiles_source; + hr = profiles_source.QueryFrom(profile_manager); + if (FAILED(hr)) { + LOG(ERROR) << "QueryFrom to ITfSource failed. hr = " << hr; + return scoped_ptr<InputSource>(); + } + + CComObject<InputSourceMonitor>* monitor = NULL; + hr = CComObject<InputSourceMonitor>::CreateInstance(&monitor); + if (FAILED(hr)) { + LOG(ERROR) << "CComObject<InputSourceMonitor>::CreateInstance failed." + << " hr = " << hr; + return scoped_ptr<InputSource>(); + } + if (!monitor->Initialize(profiles_source)) { + LOG(ERROR) << "Failed to initialize the monitor."; + return scoped_ptr<InputSource>(); + } + + // Transfer the ownership. + return scoped_ptr<InputSource>(new InputSourceImpl(profile_manager, monitor)); +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/ime/input_source.h b/chromium/win8/metro_driver/ime/input_source.h new file mode 100644 index 00000000000..8b057b2e083 --- /dev/null +++ b/chromium/win8/metro_driver/ime/input_source.h @@ -0,0 +1,36 @@ +// Copyright 2013 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_METRO_DRIVER_IME_INPUT_SOURCE_H_ +#define WIN8_METRO_DRIVER_IME_INPUT_SOURCE_H_ + +#include <Windows.h> + +#include "base/memory/scoped_ptr.h" + +namespace metro_driver { + +class InputSourceObserver; + +// An interface through which information about the input source can be +// retrieved, where an input source represents an IME or a keyboard layout. +class InputSource { + public: + virtual ~InputSource() {} + // Create an instance. Returns NULL if fails. + static scoped_ptr<InputSource> Create(); + + // Returns true if |langid| and |is_ime| are filled based on the current + // active input source. + virtual bool GetActiveSource(LANGID* langid, bool* is_ime) = 0; + + // Adds/Removes an observer to receive notifications when the active input + // source is changed. + virtual void AddObserver(InputSourceObserver* observer) = 0; + virtual void RemoveObserver(InputSourceObserver* observer) = 0; +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_INPUT_SOURCE_H_ diff --git a/chromium/win8/metro_driver/ime/input_source_observer.h b/chromium/win8/metro_driver/ime/input_source_observer.h new file mode 100644 index 00000000000..4be77a558b7 --- /dev/null +++ b/chromium/win8/metro_driver/ime/input_source_observer.h @@ -0,0 +1,26 @@ +// Copyright 2013 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_METRO_DRIVER_IME_INPUT_SOURCE_OBSERVER_H_ +#define WIN8_METRO_DRIVER_IME_INPUT_SOURCE_OBSERVER_H_ + +#include <Windows.h> + +#include "base/basictypes.h" + +namespace metro_driver { + +// An observer interface implemented by objects that want to be informed +// when the active language profile is changed. +class InputSourceObserver { + public: + virtual ~InputSourceObserver() {} + + // Called when the active language profile is changed. + virtual void OnInputSourceChanged() = 0; +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_INPUT_SOURCE_OBSERVER_H_ diff --git a/chromium/win8/metro_driver/ime/text_service.cc b/chromium/win8/metro_driver/ime/text_service.cc new file mode 100644 index 00000000000..7147d201f79 --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_service.cc @@ -0,0 +1,493 @@ +// Copyright 2013 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/ime/text_service.h" + +#include <msctf.h> + +#include "base/logging.h" +#include "base/win/scoped_variant.h" +#include "ui/metro_viewer/ime_types.h" +#include "win8/metro_driver/ime/text_service_delegate.h" +#include "win8/metro_driver/ime/text_store.h" +#include "win8/metro_driver/ime/text_store_delegate.h" + +// Architecture overview of input method support on Ash mode: +// +// Overview: +// On Ash mode, the system keyboard focus is owned by the metro_driver process +// while most of event handling are still implemented in the browser process. +// Thus the metro_driver basically works as a proxy that simply forwards +// keyevents to the metro_driver process. IME support must be involved somewhere +// in this flow. +// +// In short, we need to interact with an IME in the metro_driver process since +// TSF (Text Services Framework) runtime wants to processes keyevents while +// (and only while) the attached UI thread owns keyboard focus. +// +// Due to this limitation, we need to split IME handling into two parts, one +// is in the metro_driver process and the other is in the browser process. +// The metro_driver process is responsible for implementing the primary data +// store for the composition text and wiring it up with an IME via TSF APIs. +// On the other hand, the browser process is responsible for calculating +// character position in the composition text whenever the composition text +// is updated. +// +// IPC overview: +// Fortunately, we don't need so many IPC messages to support IMEs. In fact, +// only 4 messages are required to enable basic IME functionality. +// +// metro_driver process -> browser process +// Message Type: +// - MetroViewerHostMsg_ImeCompositionChanged +// - MetroViewerHostMsg_ImeTextCommitted +// Message Routing: +// TextServiceImpl +// -> ChromeAppViewAsh +// -- (process boundary) -- +// -> RemoteRootWindowHostWin +// -> RemoteInputMethodWin +// +// browser process -> metro_driver process +// Message Type: +// - MetroViewerHostMsg_ImeCancelComposition +// - MetroViewerHostMsg_ImeTextInputClientUpdated +// Message Routing: +// RemoteInputMethodWin +// -> RemoteRootWindowHostWin +// -- (process boundary) -- +// -> ChromeAppViewAsh +// -> TextServiceImpl +// +// Note that a keyevent may be forwarded through a different path. When a +// keyevent is not handled by an IME, such keyevent and subsequent character +// events will be sent from the metro_driver process to the browser process as +// following IPC messages. +// - MetroViewerHostMsg_KeyDown +// - MetroViewerHostMsg_KeyUp +// - MetroViewerHostMsg_Character +// +// How TextServiceImpl works: +// Here is the list of the major tasks that are handled in TextServiceImpl. +// - Manages a session object obtained from TSF runtime. We need them to call +// most of TSF APIs. +// - Handles OnDocumentChanged event. Whenever the document type is changed, +// TextServiceImpl destroyes the current document and initializes new one +// according to the given |input_scopes|. +// - Stores the |composition_character_bounds_| passed from OnDocumentChanged +// event so that an IME or on-screen keyboard can query the character +// position synchronously. +// The most complicated part is the OnDocumentChanged handler. Since some IMEs +// such as Japanese IMEs drastically change their behavior depending on +// properties exposed from the virtual document, we need to set up a lot +// properties carefully and correctly. See DocumentBinding class in this file +// about what will be involved in this multi-phase construction. See also +// text_store.cc and input_scope.cc for more underlying details. + +namespace metro_driver { +namespace { + +// Japanese IME expects the default value of this compartment is +// TF_SENTENCEMODE_PHRASEPREDICT to emulate IMM32 behavior. This value is +// managed per thread, thus setting this value at once is sufficient. This +// value never affects non-Japanese IMEs. +bool InitializeSentenceMode(ITfThreadMgr2* thread_manager, + TfClientId client_id) { + base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager; + HRESULT hr = thread_compartment_manager.QueryFrom(thread_manager); + if (FAILED(hr)) { + LOG(ERROR) << "QueryFrom failed. hr = " << hr; + return false; + } + base::win::ScopedComPtr<ITfCompartment> sentence_compartment; + hr = thread_compartment_manager->GetCompartment( + GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, + sentence_compartment.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr; + return false; + } + + base::win::ScopedVariant sentence_variant; + sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT); + hr = sentence_compartment->SetValue(client_id, &sentence_variant); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr; + return false; + } + return true; +} + +// Initializes |context| as disabled context where IMEs will be disabled. +bool InitializeDisabledContext(ITfContext* context, TfClientId client_id) { + base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr; + HRESULT hr = compartment_mgr.QueryFrom(context); + if (FAILED(hr)) { + LOG(ERROR) << "QueryFrom failed. hr = " << hr; + return false; + } + + base::win::ScopedComPtr<ITfCompartment> disabled_compartment; + hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED, + disabled_compartment.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr; + return false; + } + + base::win::ScopedVariant variant; + variant.Set(1); + hr = disabled_compartment->SetValue(client_id, &variant); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr; + return false; + } + + base::win::ScopedComPtr<ITfCompartment> empty_context; + hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT, + empty_context.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr; + return false; + } + + base::win::ScopedVariant empty_context_variant; + empty_context_variant.Set(static_cast<int32>(1)); + hr = empty_context->SetValue(client_id, &empty_context_variant); + if (FAILED(hr)) { + LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr; + return false; + } + + return true; +} + +bool IsPasswordField(const std::vector<InputScope>& input_scopes) { + return std::find(input_scopes.begin(), input_scopes.end(), IS_PASSWORD) != + input_scopes.end(); +} + +// A class that manages the lifetime of the event callback registration. When +// this object is destroyed, corresponding event callback will be unregistered. +class EventSink { + public: + EventSink(DWORD cookie, base::win::ScopedComPtr<ITfSource> source) + : cookie_(cookie), + source_(source) {} + ~EventSink() { + if (!source_ || cookie_ != TF_INVALID_COOKIE) + return; + source_->UnadviseSink(cookie_); + cookie_ = TF_INVALID_COOKIE; + source_.Release(); + } + + private: + DWORD cookie_; + base::win::ScopedComPtr<ITfSource> source_; + DISALLOW_COPY_AND_ASSIGN(EventSink); +}; + +scoped_ptr<EventSink> CreateTextEditSink(ITfContext* context, + ITfTextEditSink* text_store) { + DCHECK(text_store); + base::win::ScopedComPtr<ITfSource> source; + DWORD cookie = TF_INVALID_EDIT_COOKIE; + HRESULT hr = source.QueryFrom(context); + if (FAILED(hr)) { + LOG(ERROR) << "QueryFrom failed, hr = " << hr; + return scoped_ptr<EventSink>(); + } + hr = source->AdviseSink(IID_ITfTextEditSink, text_store, &cookie); + if (FAILED(hr)) { + LOG(ERROR) << "AdviseSink failed, hr = " << hr; + return scoped_ptr<EventSink>(); + } + return scoped_ptr<EventSink>(new EventSink(cookie, source)); +} + +// A set of objects that should have the same lifetime. Following things +// are maintained. +// - TextStore: a COM object that abstracts text buffer. This object is +// actually implemented by us in text_store.cc +// - ITfDocumentMgr: a focusable unit in TSF. This object is implemented by +// TSF runtime and works as a container of TextStore. +// - EventSink: an object that ensures that the event callback between +// TSF runtime and TextStore is unregistered when this object is destroyed. +class DocumentBinding { + public: + ~DocumentBinding() { + if (!document_manager_) + return; + document_manager_->Pop(TF_POPF_ALL); + } + + static scoped_ptr<DocumentBinding> Create( + ITfThreadMgr2* thread_manager, + TfClientId client_id, + const std::vector<InputScope>& input_scopes, + HWND window_handle, + TextStoreDelegate* delegate) { + base::win::ScopedComPtr<ITfDocumentMgr> document_manager; + HRESULT hr = thread_manager->CreateDocumentMgr(document_manager.Receive()); + if (FAILED(hr)) { + LOG(ERROR) << "ITfThreadMgr2::CreateDocumentMgr failed. hr = " << hr; + return scoped_ptr<DocumentBinding>(); + } + + // Note: In our IPC protocol, an empty |input_scopes| is used to indicate + // that an IME must be disabled in this context. In such case, we need not + // instantiate TextStore. + const bool use_null_text_store = input_scopes.empty(); + + scoped_refptr<TextStore> text_store; + if (!use_null_text_store) { + text_store = TextStore::Create(window_handle, input_scopes, delegate); + if (!text_store) { + LOG(ERROR) << "Failed to create TextStore."; + return scoped_ptr<DocumentBinding>(); + } + } + + base::win::ScopedComPtr<ITfContext> context; + DWORD edit_cookie = TF_INVALID_EDIT_COOKIE; + hr = document_manager->CreateContext( + client_id, + 0, + static_cast<ITextStoreACP*>(text_store.get()), + context.Receive(), + &edit_cookie); + if (FAILED(hr)) { + LOG(ERROR) << "ITfDocumentMgr::CreateContext failed. hr = " << hr; + return scoped_ptr<DocumentBinding>(); + } + + // If null-TextStore is used or |input_scopes| looks like a password field, + // set special properties to tell IMEs to be disabled. + if ((use_null_text_store || IsPasswordField(input_scopes)) && + !InitializeDisabledContext(context, client_id)) { + LOG(ERROR) << "InitializeDisabledContext failed."; + return scoped_ptr<DocumentBinding>(); + } + + scoped_ptr<EventSink> text_edit_sink; + if (!use_null_text_store) { + text_edit_sink = CreateTextEditSink(context, text_store); + if (!text_edit_sink) { + LOG(ERROR) << "CreateTextEditSink failed."; + return scoped_ptr<DocumentBinding>(); + } + } + hr = document_manager->Push(context); + if (FAILED(hr)) { + LOG(ERROR) << "ITfDocumentMgr::Push failed. hr = " << hr; + return scoped_ptr<DocumentBinding>(); + } + return scoped_ptr<DocumentBinding>( + new DocumentBinding(text_store, + document_manager, + text_edit_sink.Pass())); + } + + ITfDocumentMgr* document_manager() const { + return document_manager_; + } + + scoped_refptr<TextStore> text_store() const { + return text_store_; + } + + private: + DocumentBinding(scoped_refptr<TextStore> text_store, + base::win::ScopedComPtr<ITfDocumentMgr> document_manager, + scoped_ptr<EventSink> text_edit_sink) + : text_store_(text_store), + document_manager_(document_manager), + text_edit_sink_(text_edit_sink.Pass()) {} + + scoped_refptr<TextStore> text_store_; + base::win::ScopedComPtr<ITfDocumentMgr> document_manager_; + scoped_ptr<EventSink> text_edit_sink_; + + DISALLOW_COPY_AND_ASSIGN(DocumentBinding); +}; + +class TextServiceImpl : public TextService, + public TextStoreDelegate { + public: + TextServiceImpl(ITfThreadMgr2* thread_manager, + TfClientId client_id, + HWND window_handle, + TextServiceDelegate* delegate) + : client_id_(client_id), + window_handle_(window_handle), + delegate_(delegate), + thread_manager_(thread_manager) { + DCHECK_NE(TF_CLIENTID_NULL, client_id); + DCHECK(window_handle != NULL); + DCHECK(thread_manager_); + } + virtual ~TextServiceImpl() { + thread_manager_->Deactivate(); + } + + private: + // TextService overrides: + virtual void TextService::CancelComposition() OVERRIDE { + if (!current_document_) { + VLOG(0) << "|current_document_| is NULL due to the previous error."; + return; + } + TextStore* text_store = current_document_->text_store(); + if (!text_store) + return; + text_store->CancelComposition(); + } + + virtual void OnDocumentChanged( + const std::vector<int32>& input_scopes, + const std::vector<metro_viewer::CharacterBounds>& character_bounds) + OVERRIDE { + bool document_type_changed = input_scopes_ != input_scopes; + input_scopes_ = input_scopes; + composition_character_bounds_ = character_bounds; + if (document_type_changed) + OnDocumentTypeChanged(input_scopes); + } + + virtual void OnWindowActivated() OVERRIDE { + if (!current_document_) { + VLOG(0) << "|current_document_| is NULL due to the previous error."; + return; + } + ITfDocumentMgr* document_manager = current_document_->document_manager(); + if (!document_manager) { + VLOG(0) << "|document_manager| is NULL due to the previous error."; + return; + } + HRESULT hr = thread_manager_->SetFocus(document_manager); + if (FAILED(hr)) { + LOG(ERROR) << "ITfThreadMgr2::SetFocus failed. hr = " << hr; + return; + } + } + + virtual void OnCompositionChanged( + const string16& text, + int32 selection_start, + int32 selection_end, + const std::vector<metro_viewer::UnderlineInfo>& underlines) OVERRIDE { + if (!delegate_) + return; + delegate_->OnCompositionChanged(text, + selection_start, + selection_end, + underlines); + } + + virtual void OnTextCommitted(const string16& text) OVERRIDE { + if (!delegate_) + return; + delegate_->OnTextCommitted(text); + } + + virtual RECT GetCaretBounds() { + if (composition_character_bounds_.empty()) { + const RECT rect = {}; + return rect; + } + const metro_viewer::CharacterBounds& bounds = + composition_character_bounds_[0]; + POINT left_top = { bounds.left, bounds.top }; + POINT right_bottom = { bounds.right, bounds.bottom }; + ClientToScreen(window_handle_, &left_top); + ClientToScreen(window_handle_, &right_bottom); + const RECT rect = { + left_top.x, + left_top.y, + right_bottom.x, + right_bottom.y, + }; + return rect; + } + + virtual bool GetCompositionCharacterBounds(uint32 index, + RECT* rect) OVERRIDE { + if (index >= composition_character_bounds_.size()) { + return false; + } + const metro_viewer::CharacterBounds& bounds = + composition_character_bounds_[index]; + POINT left_top = { bounds.left, bounds.top }; + POINT right_bottom = { bounds.right, bounds.bottom }; + ClientToScreen(window_handle_, &left_top); + ClientToScreen(window_handle_, &right_bottom); + SetRect(rect, left_top.x, left_top.y, right_bottom.x, right_bottom.y); + return true; + } + + void OnDocumentTypeChanged(const std::vector<int32>& input_scopes) { + std::vector<InputScope> native_input_scopes(input_scopes.size()); + for (size_t i = 0; i < input_scopes.size(); ++i) + native_input_scopes[i] = static_cast<InputScope>(input_scopes[i]); + scoped_ptr<DocumentBinding> new_document = + DocumentBinding::Create(thread_manager_.get(), + client_id_, + native_input_scopes, + window_handle_, + this); + LOG_IF(ERROR, !new_document) << "Failed to create a new document."; + current_document_.swap(new_document); + OnWindowActivated(); + } + + TfClientId client_id_; + HWND window_handle_; + TextServiceDelegate* delegate_; + scoped_ptr<DocumentBinding> current_document_; + base::win::ScopedComPtr<ITfThreadMgr2> thread_manager_; + + // A vector of InputScope enumeration, which represents the document type of + // the focused text field. Note that in our IPC message protocol, an empty + // |input_scopes_| has special meaning that IMEs must be disabled on this + // document. + std::vector<int32> input_scopes_; + // Character bounds of the composition. When there is no composition but this + // vector is not empty, the first element contains the caret bounds. + std::vector<metro_viewer::CharacterBounds> composition_character_bounds_; + + DISALLOW_COPY_AND_ASSIGN(TextServiceImpl); +}; + +} // namespace + +scoped_ptr<TextService> +CreateTextService(TextServiceDelegate* delegate, HWND window_handle) { + if (!delegate) + return scoped_ptr<TextService>(); + base::win::ScopedComPtr<ITfThreadMgr2> thread_manager; + HRESULT hr = thread_manager.CreateInstance(CLSID_TF_ThreadMgr); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create instance of CLSID_TF_ThreadMgr. hr = " + << hr; + return scoped_ptr<TextService>(); + } + TfClientId client_id = TF_CLIENTID_NULL; + hr = thread_manager->ActivateEx(&client_id, 0); + if (FAILED(hr)) { + LOG(ERROR) << "ITfThreadMgr2::ActivateEx failed. hr = " << hr; + return scoped_ptr<TextService>(); + } + if (!InitializeSentenceMode(thread_manager, client_id)) { + LOG(ERROR) << "InitializeSentenceMode failed."; + thread_manager->Deactivate(); + return scoped_ptr<TextService>(); + } + return scoped_ptr<TextService>(new TextServiceImpl(thread_manager, + client_id, + window_handle, + delegate)); +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/ime/text_service.h b/chromium/win8/metro_driver/ime/text_service.h new file mode 100644 index 00000000000..6ddd21f3ccb --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_service.h @@ -0,0 +1,53 @@ +// Copyright 2013 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_METRO_DRIVER_IME_TEXT_SERVICE_H_ +#define WIN8_METRO_DRIVER_IME_TEXT_SERVICE_H_ + +#include <Windows.h> + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace metro_viewer { +struct CharacterBounds; +} + +namespace metro_driver { + +class TextServiceDelegate; + +// An interface to manage a virtual text store with which an IME communicates. +class TextService { + public: + virtual ~TextService() {} + + // Cancels on-going composition. Does nothing if there is no composition. + virtual void CancelComposition() = 0; + + // Updates document type with |input_scopes| and caret/composition position + // with |character_bounds|. An empty |input_scopes| indicates that IMEs + // should be disabled until non-empty |input_scopes| is specified. + // Note: |input_scopes| is defined as std::vector<int32> here rather than + // std::vector<InputScope> because the wire format of IPC message + // MetroViewerHostMsg_ImeTextInputClientUpdated uses std::vector<int32> to + // avoid dependency on <InputScope.h> header. + virtual void OnDocumentChanged( + const std::vector<int32>& input_scopes, + const std::vector<metro_viewer::CharacterBounds>& character_bounds) = 0; + + // Must be called whenever the attached window gains keyboard focus. + virtual void OnWindowActivated() = 0; +}; + +// Returns an instance of TextService that works together with +// |text_store_delegate| as if it was an text area owned by |window_handle|. +scoped_ptr<TextService> +CreateTextService(TextServiceDelegate* delegate, HWND window_handle); + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_TEXT_SERVICE_H_ diff --git a/chromium/win8/metro_driver/ime/text_service_delegate.h b/chromium/win8/metro_driver/ime/text_service_delegate.h new file mode 100644 index 00000000000..ea9cde72ec5 --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_service_delegate.h @@ -0,0 +1,40 @@ +// Copyright 2013 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_METRO_DRIVER_IME_TEXT_SERVICE_DELEGATE_H_ +#define WIN8_METRO_DRIVER_IME_TEXT_SERVICE_DELEGATE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace metro_viewer { +struct UnderlineInfo; +} + +namespace metro_driver { + +// A delegate which works together with virtual text service. +// Objects that implement this delegate will receive notifications from a +// virtual text service whenever an IME updates the composition or commits text. +class TextServiceDelegate { + public: + virtual ~TextServiceDelegate() {} + + // Called when on-going composition is updated. An empty |text| represents + // that the composition is canceled. + virtual void OnCompositionChanged( + const string16& text, + int32 selection_start, + int32 selection_end, + const std::vector<metro_viewer::UnderlineInfo>& underlines) = 0; + + // Called when |text| is committed. + virtual void OnTextCommitted(const string16& text) = 0; +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_TEXT_SERVICE_DELEGATE_H_ diff --git a/chromium/win8/metro_driver/ime/text_store.cc b/chromium/win8/metro_driver/ime/text_store.cc new file mode 100644 index 00000000000..a8f0d95e6c1 --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_store.cc @@ -0,0 +1,897 @@ +// Copyright 2013 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/ime/text_store.h" + +#include <algorithm> + +#include "base/win/scoped_variant.h" +#include "ui/base/win/atl_module.h" +#include "win8/metro_driver/ime/input_scope.h" +#include "win8/metro_driver/ime/text_store_delegate.h" + +namespace metro_driver { +namespace { + +// We support only one view. +const TsViewCookie kViewCookie = 1; + +} // namespace + +TextStore::TextStore() + : text_store_acp_sink_mask_(0), + window_handle_(NULL), + delegate_(NULL), + committed_size_(0), + selection_start_(0), + selection_end_(0), + edit_flag_(false), + current_lock_type_(0), + category_manager_(NULL), + display_attribute_manager_(NULL), + input_scope_(NULL) { +} + +TextStore::~TextStore() { +} + +// static +scoped_refptr<TextStore> TextStore::Create( + HWND window_handle, + const std::vector<InputScope>& input_scopes, + TextStoreDelegate* delegate) { + if (!delegate) { + LOG(ERROR) << "|delegate| must be non-NULL."; + return scoped_refptr<TextStore>(); + } + base::win::ScopedComPtr<ITfCategoryMgr> category_manager; + HRESULT hr = category_manager.CreateInstance(CLSID_TF_CategoryMgr); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to initialize CategoryMgr. hr = " << hr; + return scoped_refptr<TextStore>(); + } + base::win::ScopedComPtr<ITfDisplayAttributeMgr> display_attribute_manager; + hr = display_attribute_manager.CreateInstance(CLSID_TF_DisplayAttributeMgr); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to initialize DisplayAttributeMgr. hr = " << hr; + return scoped_refptr<TextStore>(); + } + base::win::ScopedComPtr<ITfInputScope> input_scope = + CreteInputScope(input_scopes); + if (!input_scope) { + LOG(ERROR) << "Failed to initialize InputScope."; + return scoped_refptr<TextStore>(); + } + + ui::win::CreateATLModuleIfNeeded(); + CComObject<TextStore>* object = NULL; + hr = CComObject<TextStore>::CreateInstance(&object); + if (FAILED(hr)) { + LOG(ERROR) << "CComObject<TextStore>::CreateInstance failed. hr = " + << hr; + return scoped_refptr<TextStore>(); + } + object->Initialize(window_handle, + category_manager, + display_attribute_manager, + input_scope, + delegate); + return scoped_refptr<TextStore>(object); +} + +void TextStore::Initialize(HWND window_handle, + ITfCategoryMgr* category_manager, + ITfDisplayAttributeMgr* display_attribute_manager, + ITfInputScope* input_scope, + TextStoreDelegate* delegate) { + window_handle_ = window_handle; + category_manager_ = category_manager; + display_attribute_manager_ = display_attribute_manager; + input_scope_ = input_scope; + delegate_ = delegate; +} + + +STDMETHODIMP TextStore::AdviseSink(REFIID iid, + IUnknown* unknown, + DWORD mask) { + if (!IsEqualGUID(iid, IID_ITextStoreACPSink)) + return E_INVALIDARG; + if (text_store_acp_sink_) { + if (text_store_acp_sink_.IsSameObject(unknown)) { + text_store_acp_sink_mask_ = mask; + return S_OK; + } else { + return CONNECT_E_ADVISELIMIT; + } + } + if (FAILED(text_store_acp_sink_.QueryFrom(unknown))) + return E_UNEXPECTED; + text_store_acp_sink_mask_ = mask; + + return S_OK; +} + +STDMETHODIMP TextStore::FindNextAttrTransition( + LONG acp_start, + LONG acp_halt, + ULONG num_filter_attributes, + const TS_ATTRID* filter_attributes, + DWORD flags, + LONG* acp_next, + BOOL* found, + LONG* found_offset) { + if (!acp_next || !found || !found_offset) + return E_INVALIDARG; + // We don't support any attributes. + // So we always return "not found". + *acp_next = 0; + *found = FALSE; + *found_offset = 0; + return S_OK; +} + +STDMETHODIMP TextStore::GetACPFromPoint(TsViewCookie view_cookie, + const POINT* point, + DWORD flags, + LONG* acp) { + NOTIMPLEMENTED(); + if (view_cookie != kViewCookie) + return E_INVALIDARG; + return E_NOTIMPL; +} + +STDMETHODIMP TextStore::GetActiveView(TsViewCookie* view_cookie) { + if (!view_cookie) + return E_INVALIDARG; + // We support only one view. + *view_cookie = kViewCookie; + return S_OK; +} + +STDMETHODIMP TextStore::GetEmbedded(LONG acp_pos, + REFGUID service, + REFIID iid, + IUnknown** unknown) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + if (!unknown) + return E_INVALIDARG; + *unknown = NULL; + return E_NOTIMPL; +} + +STDMETHODIMP TextStore::GetEndACP(LONG* acp) { + if (!acp) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + *acp = static_cast<LONG>(string_buffer_.size()); + return S_OK; +} + +STDMETHODIMP TextStore::GetFormattedText(LONG acp_start, + LONG acp_end, + IDataObject** data_object) { + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) { + if (view_cookie != kViewCookie) + return E_INVALIDARG; + if (!rect) + return E_INVALIDARG; + + // {0, 0, 0, 0} means that the document rect is not currently displayed. + SetRect(rect, 0, 0, 0, 0); + + RECT client_rect = {}; + if (!GetClientRect(window_handle_, &client_rect)) + return E_FAIL; + POINT left_top = {client_rect.left, client_rect.top}; + POINT right_bottom = {client_rect.right, client_rect.bottom}; + if (!ClientToScreen(window_handle_, &left_top)) + return E_FAIL; + if (!ClientToScreen(window_handle_, &right_bottom)) + return E_FAIL; + + rect->left = left_top.x; + rect->top = left_top.y; + rect->right = right_bottom.x; + rect->bottom = right_bottom.y; + return S_OK; +} + +STDMETHODIMP TextStore::GetSelection(ULONG selection_index, + ULONG selection_buffer_size, + TS_SELECTION_ACP* selection_buffer, + ULONG* fetched_count) { + if (!selection_buffer) + return E_INVALIDARG; + if (!fetched_count) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + *fetched_count = 0; + if ((selection_buffer_size > 0) && + ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) { + selection_buffer[0].acpStart = selection_start_; + selection_buffer[0].acpEnd = selection_end_; + selection_buffer[0].style.ase = TS_AE_END; + selection_buffer[0].style.fInterimChar = FALSE; + *fetched_count = 1; + } + return S_OK; +} + +STDMETHODIMP TextStore::GetStatus(TS_STATUS* status) { + if (!status) + return E_INVALIDARG; + + status->dwDynamicFlags = 0; + // We use transitory contexts and we don't support hidden text. + status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT; + + return S_OK; +} + +STDMETHODIMP TextStore::GetText(LONG acp_start, + LONG acp_end, + wchar_t* text_buffer, + ULONG text_buffer_size, + ULONG* text_buffer_copied, + TS_RUNINFO* run_info_buffer, + ULONG run_info_buffer_size, + ULONG* run_info_buffer_copied, + LONG* next_acp) { + if (!text_buffer_copied || !run_info_buffer_copied) + return E_INVALIDARG; + if (!text_buffer && text_buffer_size != 0) + return E_INVALIDARG; + if (!run_info_buffer && run_info_buffer_size != 0) + return E_INVALIDARG; + if (!next_acp) + return E_INVALIDARG; + if (!HasReadLock()) + return TF_E_NOLOCK; + const LONG string_buffer_size = static_cast<LONG>(string_buffer_.size()); + if (acp_end == -1) + acp_end = string_buffer_size; + if (!((0 <= acp_start) && + (acp_start <= acp_end) && + (acp_end <= string_buffer_size))) { + return TF_E_INVALIDPOS; + } + acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size)); + *text_buffer_copied = acp_end - acp_start; + + const string16& result = + string_buffer_.substr(acp_start, *text_buffer_copied); + for (size_t i = 0; i < result.size(); ++i) + text_buffer[i] = result[i]; + + if (run_info_buffer_size) { + run_info_buffer[0].uCount = *text_buffer_copied; + run_info_buffer[0].type = TS_RT_PLAIN; + *run_info_buffer_copied = 1; + } + + *next_acp = acp_end; + return S_OK; +} + +STDMETHODIMP TextStore::GetTextExt(TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end, + RECT* rect, + BOOL* clipped) { + if (!rect || !clipped) + return E_INVALIDARG; + if (view_cookie != kViewCookie) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + if (!((static_cast<LONG>(committed_size_) <= acp_start) && + (acp_start <= acp_end) && + (acp_end <= static_cast<LONG>(string_buffer_.size())))) { + return TS_E_INVALIDPOS; + } + + // According to a behavior of notepad.exe and wordpad.exe, top left corner of + // rect indicates a first character's one, and bottom right corner of rect + // indicates a last character's one. + // We use RECT instead of gfx::Rect since left position may be bigger than + // right position when composition has multiple lines. + RECT result; + RECT tmp_rect; + const uint32 start_pos = acp_start - committed_size_; + const uint32 end_pos = acp_end - committed_size_; + + if (start_pos == end_pos) { + // According to MSDN document, if |acp_start| and |acp_end| are equal it is + // OK to just return E_INVALIDARG. + // http://msdn.microsoft.com/en-us/library/ms538435 + // But when using Pinin IME of Windows 8, this method is called with the + // equal values of |acp_start| and |acp_end|. So we handle this condition. + if (start_pos == 0) { + if (delegate_->GetCompositionCharacterBounds(0, &tmp_rect)) { + tmp_rect.right = tmp_rect.right; + result = tmp_rect; + } else if (string_buffer_.size() == committed_size_) { + result = delegate_->GetCaretBounds(); + } else { + return TS_E_NOLAYOUT; + } + } else if (delegate_->GetCompositionCharacterBounds(start_pos - 1, + &tmp_rect)) { + tmp_rect.left = tmp_rect.right; + result = tmp_rect; + } else { + return TS_E_NOLAYOUT; + } + } else { + if (delegate_->GetCompositionCharacterBounds(start_pos, &tmp_rect)) { + result = tmp_rect; + if (delegate_->GetCompositionCharacterBounds(end_pos - 1, &tmp_rect)) { + result.right = tmp_rect.right; + result.bottom = tmp_rect.bottom; + } else { + // We may not be able to get the last character bounds, so we use the + // first character bounds instead of returning TS_E_NOLAYOUT. + } + } else { + // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so + // it's better to return previous caret rectangle instead. + // TODO(nona, kinaba): Remove this hack. + if (start_pos == 0) + result = delegate_->GetCaretBounds(); + else + return TS_E_NOLAYOUT; + } + } + + *rect = result; + *clipped = FALSE; + return S_OK; +} + +STDMETHODIMP TextStore::GetWnd(TsViewCookie view_cookie, + HWND* window_handle) { + if (!window_handle) + return E_INVALIDARG; + if (view_cookie != kViewCookie) + return E_INVALIDARG; + *window_handle = window_handle_; + return S_OK; +} + +STDMETHODIMP TextStore::InsertEmbedded(DWORD flags, + LONG acp_start, + LONG acp_end, + IDataObject* data_object, + TS_TEXTCHANGE* change) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TextStore::InsertEmbeddedAtSelection(DWORD flags, + IDataObject* data_object, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* change) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TextStore::InsertTextAtSelection(DWORD flags, + const wchar_t* text_buffer, + ULONG text_buffer_size, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* text_change) { + const LONG start_pos = selection_start_; + const LONG end_pos = selection_end_; + const LONG new_end_pos = start_pos + text_buffer_size; + + if (flags & TS_IAS_QUERYONLY) { + if (!HasReadLock()) + return TS_E_NOLOCK; + if (acp_start) + *acp_start = start_pos; + if (acp_end) + *acp_end = end_pos; + return S_OK; + } + + if (!HasReadWriteLock()) + return TS_E_NOLOCK; + if (!text_buffer) + return E_INVALIDARG; + + DCHECK_LE(start_pos, end_pos); + string_buffer_ = string_buffer_.substr(0, start_pos) + + string16(text_buffer, text_buffer + text_buffer_size) + + string_buffer_.substr(end_pos); + if (acp_start) + *acp_start = start_pos; + if (acp_end) + *acp_end = new_end_pos; + if (text_change) { + text_change->acpStart = start_pos; + text_change->acpOldEnd = end_pos; + text_change->acpNewEnd = new_end_pos; + } + selection_start_ = start_pos; + selection_end_ = new_end_pos; + return S_OK; +} + +STDMETHODIMP TextStore::QueryInsert( + LONG acp_test_start, + LONG acp_test_end, + ULONG text_size, + LONG* acp_result_start, + LONG* acp_result_end) { + if (!acp_result_start || !acp_result_end || acp_test_start > acp_test_end) + return E_INVALIDARG; + const LONG committed_size = static_cast<LONG>(committed_size_); + const LONG buffer_size = static_cast<LONG>(string_buffer_.size()); + *acp_result_start = std::min(std::max(committed_size, acp_test_start), + buffer_size); + *acp_result_end = std::min(std::max(committed_size, acp_test_end), + buffer_size); + return S_OK; +} + +STDMETHODIMP TextStore::QueryInsertEmbedded(const GUID* service, + const FORMATETC* format, + BOOL* insertable) { + if (!format) + return E_INVALIDARG; + // We don't support any embedded objects. + if (insertable) + *insertable = FALSE; + return S_OK; +} + +STDMETHODIMP TextStore::RequestAttrsAtPosition( + LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) { + // We don't support any document attributes. + // This method just returns S_OK, and the subsequently called + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. + return S_OK; +} + +STDMETHODIMP TextStore::RequestAttrsTransitioningAtPosition( + LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) { + // We don't support any document attributes. + // This method just returns S_OK, and the subsequently called + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. + return S_OK; +} + +STDMETHODIMP TextStore::RequestLock(DWORD lock_flags, HRESULT* result) { + if (!text_store_acp_sink_.get()) + return E_FAIL; + if (!result) + return E_INVALIDARG; + + if (current_lock_type_ != 0) { + if (lock_flags & TS_LF_SYNC) { + // Can't lock synchronously. + *result = TS_E_SYNCHRONOUS; + return S_OK; + } + // Queue the lock request. + lock_queue_.push_back(lock_flags & TS_LF_READWRITE); + *result = TS_S_ASYNC; + return S_OK; + } + + // Lock + current_lock_type_ = (lock_flags & TS_LF_READWRITE); + + edit_flag_ = false; + const uint32 last_committed_size = committed_size_; + + // Grant the lock. + *result = text_store_acp_sink_->OnLockGranted(current_lock_type_); + + // Unlock + current_lock_type_ = 0; + + // Handles the pending lock requests. + while (!lock_queue_.empty()) { + current_lock_type_ = lock_queue_.front(); + lock_queue_.pop_front(); + text_store_acp_sink_->OnLockGranted(current_lock_type_); + current_lock_type_ = 0; + } + + if (!edit_flag_) + return S_OK; + + // If the text store is edited in OnLockGranted(), we may need to call + // TextStoreDelegate::ConfirmComposition() or + // TextStoreDelegate::SetComposition(). + const uint32 new_committed_size = committed_size_; + const string16& new_committed_string = + string_buffer_.substr(last_committed_size, + new_committed_size - last_committed_size); + const string16& composition_string = + string_buffer_.substr(new_committed_size); + + // If there is new committed string, calls + // TextStoreDelegate::ConfirmComposition(). + if ((!new_committed_string.empty())) + delegate_->OnTextCommitted(new_committed_string); + + // Calls TextInputClient::SetCompositionText(). + std::vector<metro_viewer::UnderlineInfo> underlines = underlines_; + // Adjusts the offset. + for (size_t i = 0; i < underlines_.size(); ++i) { + underlines[i].start_offset -= new_committed_size; + underlines[i].end_offset -= new_committed_size; + } + int32 selection_start = 0; + int32 selection_end = 0; + if (selection_start_ >= new_committed_size) + selection_start = selection_start_ - new_committed_size; + if (selection_end_ >= new_committed_size) + selection_end = selection_end_ - new_committed_size; + delegate_->OnCompositionChanged( + composition_string, selection_start, selection_end, underlines); + + // If there is no composition string, clear the text store status. + // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange(). + if ((composition_string.empty()) && (new_committed_size != 0)) { + string_buffer_.clear(); + committed_size_ = 0; + selection_start_ = 0; + selection_end_ = 0; + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange; + textChange.acpStart = 0; + textChange.acpOldEnd = new_committed_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + } + + return S_OK; +} + +STDMETHODIMP TextStore::RequestSupportedAttrs( + DWORD /* flags */, // Seems that we should ignore this. + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer) { + if (!attribute_buffer) + return E_INVALIDARG; + if (!input_scope_) + return E_FAIL; + // We support only input scope attribute. + for (size_t i = 0; i < attribute_buffer_size; ++i) { + if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i])) + return S_OK; + } + return E_FAIL; +} + +STDMETHODIMP TextStore::RetrieveRequestedAttrs( + ULONG attribute_buffer_size, + TS_ATTRVAL* attribute_buffer, + ULONG* attribute_buffer_copied) { + if (!attribute_buffer_copied) + return E_INVALIDARG; + *attribute_buffer_copied = 0; + if (!attribute_buffer) + return E_INVALIDARG; + if (!input_scope_) + return E_UNEXPECTED; + // We support only input scope attribute. + *attribute_buffer_copied = 0; + if (attribute_buffer_size == 0) + return S_OK; + + attribute_buffer[0].dwOverlapId = 0; + attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE; + attribute_buffer[0].varValue.vt = VT_UNKNOWN; + attribute_buffer[0].varValue.punkVal = input_scope_.get(); + attribute_buffer[0].varValue.punkVal->AddRef(); + *attribute_buffer_copied = 1; + return S_OK; +} + +STDMETHODIMP TextStore::SetSelection( + ULONG selection_buffer_size, + const TS_SELECTION_ACP* selection_buffer) { + if (!HasReadWriteLock()) + return TF_E_NOLOCK; + if (selection_buffer_size > 0) { + const LONG start_pos = selection_buffer[0].acpStart; + const LONG end_pos = selection_buffer[0].acpEnd; + if (!((static_cast<LONG>(committed_size_) <= start_pos) && + (start_pos <= end_pos) && + (end_pos <= static_cast<LONG>(string_buffer_.size())))) { + return TF_E_INVALIDPOS; + } + selection_start_ = start_pos; + selection_end_ = end_pos; + } + return S_OK; +} + +STDMETHODIMP TextStore::SetText(DWORD flags, + LONG acp_start, + LONG acp_end, + const wchar_t* text_buffer, + ULONG text_buffer_size, + TS_TEXTCHANGE* text_change) { + if (!HasReadWriteLock()) + return TS_E_NOLOCK; + if (!((static_cast<LONG>(committed_size_) <= acp_start) && + (acp_start <= acp_end) && + (acp_end <= static_cast<LONG>(string_buffer_.size())))) { + return TS_E_INVALIDPOS; + } + + TS_SELECTION_ACP selection; + selection.acpStart = acp_start; + selection.acpEnd = acp_end; + selection.style.ase = TS_AE_NONE; + selection.style.fInterimChar = 0; + + HRESULT ret; + ret = SetSelection(1, &selection); + if (ret != S_OK) + return ret; + + TS_TEXTCHANGE change; + ret = InsertTextAtSelection(0, text_buffer, text_buffer_size, + &acp_start, &acp_end, &change); + if (ret != S_OK) + return ret; + + if (text_change) + *text_change = change; + + return S_OK; +} + +STDMETHODIMP TextStore::UnadviseSink(IUnknown* unknown) { + if (!text_store_acp_sink_.IsSameObject(unknown)) + return CONNECT_E_NOCONNECTION; + text_store_acp_sink_.Release(); + text_store_acp_sink_mask_ = 0; + return S_OK; +} + +STDMETHODIMP TextStore::OnStartComposition( + ITfCompositionView* composition_view, + BOOL* ok) { + if (ok) + *ok = TRUE; + return S_OK; +} + +STDMETHODIMP TextStore::OnUpdateComposition( + ITfCompositionView* composition_view, + ITfRange* range) { + return S_OK; +} + +STDMETHODIMP TextStore::OnEndComposition( + ITfCompositionView* composition_view) { + return S_OK; +} + +STDMETHODIMP TextStore::OnEndEdit(ITfContext* context, + TfEditCookie read_only_edit_cookie, + ITfEditRecord* edit_record) { + if (!context || !edit_record) + return E_INVALIDARG; + if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size_, + &underlines_)) { + return S_OK; + } + edit_flag_ = true; + return S_OK; +} + +bool TextStore::GetDisplayAttribute(TfGuidAtom guid_atom, + TF_DISPLAYATTRIBUTE* attribute) { + GUID guid; + if (FAILED(category_manager_->GetGUID(guid_atom, &guid))) + return false; + + base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info; + if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo( + guid, display_attribute_info.Receive(), NULL))) { + return false; + } + return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute)); +} + +bool TextStore::GetCompositionStatus( + ITfContext* context, + const TfEditCookie read_only_edit_cookie, + uint32* committed_size, + std::vector<metro_viewer::UnderlineInfo>* undelines) { + DCHECK(context); + DCHECK(committed_size); + DCHECK(undelines); + const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE}; + base::win::ScopedComPtr<ITfReadOnlyProperty> track_property; + if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0, + track_property.Receive()))) { + return false; + } + + *committed_size = 0; + undelines->clear(); + base::win::ScopedComPtr<ITfRange> start_to_end_range; + base::win::ScopedComPtr<ITfRange> end_range; + if (FAILED(context->GetStart(read_only_edit_cookie, + start_to_end_range.Receive()))) { + return false; + } + if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive()))) + return false; + if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie, + end_range, TF_ANCHOR_END))) { + return false; + } + + base::win::ScopedComPtr<IEnumTfRanges> ranges; + if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(), + start_to_end_range))) { + return false; + } + + while (true) { + base::win::ScopedComPtr<ITfRange> range; + if (ranges->Next(1, range.Receive(), NULL) != S_OK) + break; + base::win::ScopedVariant value; + base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value; + if (FAILED(track_property->GetValue(read_only_edit_cookie, range, + value.Receive()))) { + return false; + } + if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal))) + return false; + + TF_PROPERTYVAL property_value; + bool is_composition = false; + bool has_display_attribute = false; + TF_DISPLAYATTRIBUTE display_attribute; + while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) { + if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) { + is_composition = (property_value.varValue.lVal == TRUE); + } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) { + TfGuidAtom guid_atom = + static_cast<TfGuidAtom>(property_value.varValue.lVal); + if (GetDisplayAttribute(guid_atom, &display_attribute)) + has_display_attribute = true; + } + VariantClear(&property_value.varValue); + } + + base::win::ScopedComPtr<ITfRangeACP> range_acp; + range_acp.QueryFrom(range); + LONG start_pos, length; + range_acp->GetExtent(&start_pos, &length); + if (!is_composition) { + if (*committed_size < static_cast<size_t>(start_pos + length)) + *committed_size = start_pos + length; + } else { + metro_viewer::UnderlineInfo underline; + underline.start_offset = start_pos; + underline.end_offset = start_pos + length; + underline.thick = !!display_attribute.fBoldLine; + undelines->push_back(underline); + } + } + return true; +} + +bool TextStore::CancelComposition() { + // If there is an on-going document lock, we must not edit the text. + if (edit_flag_) + return false; + + if (string_buffer_.empty()) + return true; + + // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does + // not have a dedicated method to cancel composition. However, CUAS actually + // has a protocol conversion from CPS_CANCEL into TSF operations. According + // to the observations on Windows 7, TIPs are expected to cancel composition + // when an on-going composition text is replaced with an empty string. So + // we use the same operation to cancel composition here to minimize the risk + // of potential compatibility issues. + + const uint32 previous_buffer_size = + static_cast<uint32>(string_buffer_.size()); + string_buffer_.clear(); + committed_size_ = 0; + selection_start_ = 0; + selection_end_ = 0; + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange = {}; + textChange.acpStart = 0; + textChange.acpOldEnd = previous_buffer_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + return true; +} + +bool TextStore::ConfirmComposition() { + // If there is an on-going document lock, we must not edit the text. + if (edit_flag_) + return false; + + if (string_buffer_.empty()) + return true; + + // See the comment in TextStore::CancelComposition. + // This logic is based on the observation about how to emulate + // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS. + + const string16& composition_text = string_buffer_.substr(committed_size_); + if (!composition_text.empty()) + delegate_->OnTextCommitted(composition_text); + + const uint32 previous_buffer_size = + static_cast<uint32>(string_buffer_.size()); + string_buffer_.clear(); + committed_size_ = 0; + selection_start_ = 0; + selection_end_ = 0; + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange = {}; + textChange.acpStart = 0; + textChange.acpOldEnd = previous_buffer_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + return true; +} + +void TextStore::SendOnLayoutChange() { + if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); +} + +bool TextStore::HasReadLock() const { + return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; +} + +bool TextStore::HasReadWriteLock() const { + return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; +} + +} // namespace metro_driver diff --git a/chromium/win8/metro_driver/ime/text_store.h b/chromium/win8/metro_driver/ime/text_store.h new file mode 100644 index 00000000000..8ed4d612708 --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_store.h @@ -0,0 +1,315 @@ +// Copyright 2013 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_METRO_DRIVER_IME_TEXT_STORE_H_ +#define WIN8_METRO_DRIVER_IME_TEXT_STORE_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <initguid.h> +#include <inputscope.h> +#include <msctf.h> + +#include <deque> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/win/scoped_comptr.h" +#include "ui/metro_viewer/ime_types.h" + +namespace metro_driver { + +class TextStoreDelegate; + +// TextStore is used to interact with the input method via TSF manager. +// TextStore have a string buffer which is manipulated by TSF manager through +// ITextStoreACP interface methods such as SetText(). +// When the input method updates the composition, TextStore calls +// TextInputClient::SetCompositionText(). And when the input method finishes the +// composition, TextStore calls TextInputClient::InsertText() and clears the +// buffer. +// +// How TextStore works: +// - The user enters "a". +// - The input method set composition as "a". +// - TSF manager calls TextStore::RequestLock(). +// - TextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TextStore::OnStartComposition() +// - TextStore::SetText() +// The string buffer is set as "a". +// - TextStore::OnUpdateComposition() +// - TextStore::OnEndEdit() +// TextStore can get the composition information such as underlines. +// - TextStore calls TextInputClient::SetCompositionText(). +// "a" is shown with an underline as composition string. +// - The user enters <space>. +// - The input method set composition as "A". +// - TSF manager calls TextStore::RequestLock(). +// - TextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TextStore::SetText() +// The string buffer is set as "A". +// - TextStore::OnUpdateComposition() +// - TextStore::OnEndEdit() +// - TextStore calls TextInputClient::SetCompositionText(). +// "A" is shown with an underline as composition string. +// - The user enters <enter>. +// - The input method commits "A". +// - TSF manager calls TextStore::RequestLock(). +// - TextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TextStore::OnEndComposition() +// - TextStore::OnEndEdit() +// TextStore knows "A" is committed. +// - TextStore calls TextInputClient::InsertText(). +// "A" is shown as committed string. +// - TextStore clears the string buffer. +// - TextStore calls OnSelectionChange(), OnLayoutChange() and +// OnTextChange() of ITextStoreACPSink to let TSF manager know that the +// string buffer has been changed. +// +// About the locking scheme: +// When TSF manager manipulates the string buffer it calls RequestLock() to get +// the lock of the document. If TextStore can grant the lock request, it +// callbacks ITextStoreACPSink::OnLockGranted(). +// RequestLock() is called from only one thread, but called recursively in +// OnLockGranted() or OnSelectionChange() or OnLayoutChange() or OnTextChange(). +// If the document is locked and the lock request is asynchronous, TextStore +// queues the request. The queued requests will be handled after the current +// lock is removed. +// More information about document locks can be found here: +// http://msdn.microsoft.com/en-us/library/ms538064 +// +// More information about TSF can be found here: +// http://msdn.microsoft.com/en-us/library/ms629032 +class ATL_NO_VTABLE TextStore + : public CComObjectRootEx<CComMultiThreadModel>, + public ITextStoreACP, + public ITfContextOwnerCompositionSink, + public ITfTextEditSink { + public: + virtual ~TextStore(); + + BEGIN_COM_MAP(TextStore) + COM_INTERFACE_ENTRY(ITextStoreACP) + COM_INTERFACE_ENTRY(ITfContextOwnerCompositionSink) + COM_INTERFACE_ENTRY(ITfTextEditSink) + END_COM_MAP() + + // ITextStoreACP: + STDMETHOD(AdviseSink)(REFIID iid, IUnknown* unknown, DWORD mask) OVERRIDE; + STDMETHOD(FindNextAttrTransition)(LONG acp_start, + LONG acp_halt, + ULONG num_filter_attributes, + const TS_ATTRID* filter_attributes, + DWORD flags, + LONG* acp_next, + BOOL* found, + LONG* found_offset) OVERRIDE; + STDMETHOD(GetACPFromPoint)(TsViewCookie view_cookie, + const POINT* point, + DWORD flags, + LONG* acp) OVERRIDE; + STDMETHOD(GetActiveView)(TsViewCookie* view_cookie) OVERRIDE; + STDMETHOD(GetEmbedded)(LONG acp_pos, + REFGUID service, + REFIID iid, + IUnknown** unknown) OVERRIDE; + STDMETHOD(GetEndACP)(LONG* acp) OVERRIDE; + STDMETHOD(GetFormattedText)(LONG acp_start, + LONG acp_end, + IDataObject** data_object) OVERRIDE; + STDMETHOD(GetScreenExt)(TsViewCookie view_cookie, RECT* rect) OVERRIDE; + STDMETHOD(GetSelection)(ULONG selection_index, + ULONG selection_buffer_size, + TS_SELECTION_ACP* selection_buffer, + ULONG* fetched_count) OVERRIDE; + STDMETHOD(GetStatus)(TS_STATUS* pdcs) OVERRIDE; + STDMETHOD(GetText)(LONG acp_start, + LONG acp_end, + wchar_t* text_buffer, + ULONG text_buffer_size, + ULONG* text_buffer_copied, + TS_RUNINFO* run_info_buffer, + ULONG run_info_buffer_size, + ULONG* run_info_buffer_copied, + LONG* next_acp) OVERRIDE; + STDMETHOD(GetTextExt)(TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end, + RECT* rect, + BOOL* clipped) OVERRIDE; + STDMETHOD(GetWnd)(TsViewCookie view_cookie, HWND* window_handle) OVERRIDE; + STDMETHOD(InsertEmbedded)(DWORD flags, + LONG acp_start, + LONG acp_end, + IDataObject* data_object, + TS_TEXTCHANGE* change) OVERRIDE; + STDMETHOD(InsertEmbeddedAtSelection)(DWORD flags, + IDataObject* data_object, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* change) OVERRIDE; + STDMETHOD(InsertTextAtSelection)(DWORD flags, + const wchar_t* text_buffer, + ULONG text_buffer_size, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* text_change) OVERRIDE; + STDMETHOD(QueryInsert)(LONG acp_test_start, + LONG acp_test_end, + ULONG text_size, + LONG* acp_result_start, + LONG* acp_result_end) OVERRIDE; + STDMETHOD(QueryInsertEmbedded)(const GUID* service, + const FORMATETC* format, + BOOL* insertable) OVERRIDE; + STDMETHOD(RequestAttrsAtPosition)(LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) OVERRIDE; + STDMETHOD(RequestAttrsTransitioningAtPosition)( + LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) OVERRIDE; + STDMETHOD(RequestLock)(DWORD lock_flags, HRESULT* result) OVERRIDE; + STDMETHOD(RequestSupportedAttrs)(DWORD flags, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer) OVERRIDE; + STDMETHOD(RetrieveRequestedAttrs)(ULONG attribute_buffer_size, + TS_ATTRVAL* attribute_buffer, + ULONG* attribute_buffer_copied) OVERRIDE; + STDMETHOD(SetSelection)(ULONG selection_buffer_size, + const TS_SELECTION_ACP* selection_buffer) OVERRIDE; + STDMETHOD(SetText)(DWORD flags, + LONG acp_start, + LONG acp_end, + const wchar_t* text_buffer, + ULONG text_buffer_size, + TS_TEXTCHANGE* text_change) OVERRIDE; + STDMETHOD(UnadviseSink)(IUnknown* unknown) OVERRIDE; + + // ITfContextOwnerCompositionSink: + STDMETHOD(OnStartComposition)(ITfCompositionView* composition_view, + BOOL* ok) OVERRIDE; + STDMETHOD(OnUpdateComposition)(ITfCompositionView* composition_view, + ITfRange* range) OVERRIDE; + STDMETHOD(OnEndComposition)(ITfCompositionView* composition_view) OVERRIDE; + + // ITfTextEditSink: + STDMETHOD(OnEndEdit)(ITfContext* context, TfEditCookie read_only_edit_cookie, + ITfEditRecord* edit_record) OVERRIDE; + + // Cancels the ongoing composition if exists. + bool CancelComposition(); + + // Confirms the ongoing composition if exists. + bool ConfirmComposition(); + + // Sends OnLayoutChange() via |text_store_acp_sink_|. + void SendOnLayoutChange(); + + // Creates an instance of TextStore. Returns NULL if fails. + static scoped_refptr<TextStore> Create( + HWND window_handle, + const std::vector<InputScope>& input_scopes, + TextStoreDelegate* delegate); + + private: + friend CComObject<TextStore>; + TextStore(); + + void Initialize(HWND window_handle, + ITfCategoryMgr* category_manager, + ITfDisplayAttributeMgr* display_attribute_manager, + ITfInputScope* input_scope, + TextStoreDelegate* delegate); + + // Checks if the document has a read-only lock. + bool HasReadLock() const; + + // Checks if the document has a read and write lock. + bool HasReadWriteLock() const; + + // Gets the display attribute structure. + bool GetDisplayAttribute(TfGuidAtom guid_atom, + TF_DISPLAYATTRIBUTE* attribute); + + // Gets the committed string size and underline information of the context. + bool GetCompositionStatus( + ITfContext* context, + const TfEditCookie read_only_edit_cookie, + uint32* committed_size, + std::vector<metro_viewer::UnderlineInfo>* undelines); + + // A pointer of ITextStoreACPSink, this instance is given in AdviseSink. + base::win::ScopedComPtr<ITextStoreACPSink> text_store_acp_sink_; + + // The current mask of |text_store_acp_sink_|. + DWORD text_store_acp_sink_mask_; + + // HWND of the attached window. + HWND window_handle_; + + // |string_buffer_| contains committed string and composition string. + // Example: "aoi" is committed, and "umi" is under composition. + // |string_buffer_|: "aoiumi" + // |committed_size_|: 3 + string16 string_buffer_; + uint32 committed_size_; + + // |selection_start_| and |selection_end_| indicates the selection range. + // Example: "iue" is selected + // |string_buffer_|: "aiueo" + // |selection_start_|: 1 + // |selection_end_|: 4 + uint32 selection_start_; + uint32 selection_end_; + + // |start_offset| and |end_offset| of |composition_undelines_| indicates + // the offsets in |string_buffer_|. + // Example: "aoi" is committed. There are two underlines in "umi" and "no". + // |string_buffer_|: "aoiumino" + // |committed_size_|: 3 + // underlines_[0].start_offset: 3 + // underlines_[0].end_offset: 6 + // underlines_[1].start_offset: 6 + // underlines_[1].end_offset: 8 + std::vector<metro_viewer::UnderlineInfo> underlines_; + + // |edit_flag_| indicates that the status is edited during + // ITextStoreACPSink::OnLockGranted(). + bool edit_flag_; + + // The type of current lock. + // 0: No lock. + // TS_LF_READ: read-only lock. + // TS_LF_READWRITE: read/write lock. + DWORD current_lock_type_; + + // Queue of the lock request used in RequestLock(). + std::deque<DWORD> lock_queue_; + + // Category manager and Display attribute manager are used to obtain the + // attributes of the composition string. + base::win::ScopedComPtr<ITfCategoryMgr> category_manager_; + base::win::ScopedComPtr<ITfDisplayAttributeMgr> display_attribute_manager_; + + // Represents the context information of this text. + base::win::ScopedComPtr<ITfInputScope> input_scope_; + + // The delegate attached to this text store. + TextStoreDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(TextStore); +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_TEXT_STORE_H_ diff --git a/chromium/win8/metro_driver/ime/text_store_delegate.h b/chromium/win8/metro_driver/ime/text_store_delegate.h new file mode 100644 index 00000000000..af065164714 --- /dev/null +++ b/chromium/win8/metro_driver/ime/text_store_delegate.h @@ -0,0 +1,55 @@ +// Copyright 2013 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_METRO_DRIVER_IME_TEXT_STORE_DELEGATE_H_ +#define WIN8_METRO_DRIVER_IME_TEXT_STORE_DELEGATE_H_ + +#include <vector> + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace metro_viewer { +struct UnderlineInfo; +} + +namespace metro_driver { + +// A delegate which works together with virtual text stores. +// Objects that implement this delegate will receive notifications from a +// virtual text store whenever an IME updates the composition or commits text. +// Objects that implement this delegate are also responsible for calculating +// the character position of composition and caret position upon request. +class TextStoreDelegate { + public: + virtual ~TextStoreDelegate() {} + + // Called when on-going composition is updated. An empty |text| represents + // that the composition is canceled. + virtual void OnCompositionChanged( + const string16& text, + int32 selection_start, + int32 selection_end, + const std::vector<metro_viewer::UnderlineInfo>& underlines) = 0; + + // Called when |text| is committed. + virtual void OnTextCommitted(const string16& text) = 0; + + // Called when an IME requests the caret position. Objects that implement + // this method must return the caret position in screen coordinates. + virtual RECT GetCaretBounds() = 0; + + // Called when an IME requests the bounding box of an character whose + // index is |index| in the on-going composition. position. Objects that + // implement this method must return true and fill the character bounds into + // |rect| in screen coordinates. + // Should return false if |index| is invalid. + virtual bool GetCompositionCharacterBounds(uint32 index, RECT* rect) = 0; +}; + +} // namespace metro_driver + +#endif // WIN8_METRO_DRIVER_IME_TEXT_STORE_DELEGATE_H_ diff --git a/chromium/win8/metro_driver/metro_dialog_box.cc b/chromium/win8/metro_driver/metro_dialog_box.cc new file mode 100644 index 00000000000..25e70827710 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/metro_dialog_box.h b/chromium/win8/metro_driver/metro_dialog_box.h new file mode 100644 index 00000000000..5a6a94dbc63 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/metro_driver.cc b/chromium/win8/metro_driver/metro_driver.cc new file mode 100644 index 00000000000..940b5b141d1 --- /dev/null +++ b/chromium/win8/metro_driver/metro_driver.cc @@ -0,0 +1,124 @@ +// 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/metro_driver.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/winrt_utils.h" + +#if !defined(USE_AURA) +#include "win8/metro_driver/chrome_app_view.h" +#endif + +// 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 } }; + +} + +#if !defined(COMPONENT_BUILD) +// 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; +#endif + +extern "C" __declspec(dllexport) +int InitMetro(LPTHREAD_START_ROUTINE thread_proc, void* context) { + // Initialize the command line. + CommandLine::Init(0, NULL); + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + logging::InitLogging(settings); + +#if defined(NDEBUG) + logging::SetMinLogLevel(logging::LOG_ERROR); +#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; + + 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/chromium/win8/metro_driver/metro_driver.gyp b/chromium/win8/metro_driver/metro_driver.gyp index c4797fb5822..cc572d84ebb 100644 --- a/chromium/win8/metro_driver/metro_driver.gyp +++ b/chromium/win8/metro_driver/metro_driver.gyp @@ -64,6 +64,7 @@ 'metro_driver_version_resources', ], 'sources': [ + 'display_properties.cc', 'metro_driver.cc', 'metro_driver.h', 'stdafx.h', @@ -74,7 +75,7 @@ 'conditions': [ ['use_aura==1', { 'dependencies': [ - '../win8.gyp:metro_viewer', + '../win8.gyp:metro_viewer_constants', ], 'sources': [ 'chrome_app_view_ash.cc', @@ -84,6 +85,9 @@ 'file_picker_ash.cc', 'file_picker_ash.h', ], + 'includes': [ + 'ime/ime.gypi', + ], }, { # use_aura!=1 'sources': [ 'chrome_app_view.cc', diff --git a/chromium/win8/metro_driver/metro_driver.h b/chromium/win8/metro_driver/metro_driver.h new file mode 100644 index 00000000000..c4ea2abd636 --- /dev/null +++ b/chromium/win8/metro_driver/metro_driver.h @@ -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. + +#ifndef WIN8_METRO_DRIVER_METRO_DRIVER_H_ +#define WIN8_METRO_DRIVER_METRO_DRIVER_H_ + +#include "stdafx.h" + +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); +}; + +#endif // WIN8_METRO_DRIVER_METRO_DRIVER_H_
\ No newline at end of file diff --git a/chromium/win8/metro_driver/metro_driver_dll.ver b/chromium/win8/metro_driver/metro_driver_dll.ver new file mode 100644 index 00000000000..72d1cc8d82e --- /dev/null +++ b/chromium/win8/metro_driver/metro_driver_dll.ver @@ -0,0 +1,2 @@ +INTERNAL_NAME=metro_driver_dll +ORIGINAL_FILENAME=metro_driver.dll diff --git a/chromium/win8/metro_driver/metro_driver_win7.cc b/chromium/win8/metro_driver/metro_driver_win7.cc new file mode 100644 index 00000000000..253e527b8c2 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/print_document_source.cc b/chromium/win8/metro_driver/print_document_source.cc new file mode 100644 index 00000000000..91842b78c68 --- /dev/null +++ b/chromium/win8/metro_driver/print_document_source.cc @@ -0,0 +1,527 @@ +// 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" +#include "base/safe_numerics.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, + base::checked_numeric_cast<UINT32>(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/chromium/win8/metro_driver/print_document_source.h b/chromium/win8/metro_driver/print_document_source.h new file mode 100644 index 00000000000..fed333e3706 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/print_handler.cc b/chromium/win8/metro_driver/print_handler.cc new file mode 100644 index 00000000000..3539c9ed2f8 --- /dev/null +++ b/chromium/win8/metro_driver/print_handler.cc @@ -0,0 +1,490 @@ +// 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 "base/safe_numerics.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(base::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(base::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(base::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(base::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(base::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(base::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, + base::checked_numeric_cast<ULONG>(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/chromium/win8/metro_driver/print_handler.h b/chromium/win8/metro_driver/print_handler.h new file mode 100644 index 00000000000..f0779cf2ad8 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/resources/Logo.png b/chromium/win8/metro_driver/resources/Logo.png Binary files differnew file mode 100644 index 00000000000..49d67e4fde6 --- /dev/null +++ b/chromium/win8/metro_driver/resources/Logo.png diff --git a/chromium/win8/metro_driver/resources/SecondaryTile.png b/chromium/win8/metro_driver/resources/SecondaryTile.png Binary files differnew file mode 100644 index 00000000000..9c9e4532a41 --- /dev/null +++ b/chromium/win8/metro_driver/resources/SecondaryTile.png diff --git a/chromium/win8/metro_driver/resources/SmallLogo.png b/chromium/win8/metro_driver/resources/SmallLogo.png Binary files differnew file mode 100644 index 00000000000..c25cfb4002e --- /dev/null +++ b/chromium/win8/metro_driver/resources/SmallLogo.png diff --git a/chromium/win8/metro_driver/resources/VisualElementsManifest.xml b/chromium/win8/metro_driver/resources/VisualElementsManifest.xml new file mode 100644 index 00000000000..230e787c591 --- /dev/null +++ b/chromium/win8/metro_driver/resources/VisualElementsManifest.xml @@ -0,0 +1,17 @@ +<!-- This is only meant to be copied by chrome.exe in developer builds. --> +<Application + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <VisualElements + DisplayName="Developer Chrome" + Logo="Logo.png" + SmallLogo="SmallLogo.png" + ForegroundText="light" + BackgroundColor="white"> + <DefaultTile + ShortName="DevChrome" + ShowName="allLogos" + /> + <SplashScreen + Image="splash-620x300.png"/> + </VisualElements> +</Application> diff --git a/chromium/win8/metro_driver/resources/splash-620x300.png b/chromium/win8/metro_driver/resources/splash-620x300.png Binary files differnew file mode 100644 index 00000000000..41a3e02fa91 --- /dev/null +++ b/chromium/win8/metro_driver/resources/splash-620x300.png diff --git a/chromium/win8/metro_driver/run_all_unittests.cc b/chromium/win8/metro_driver/run_all_unittests.cc new file mode 100644 index 00000000000..4c6a548e2f4 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/secondary_tile.cc b/chromium/win8/metro_driver/secondary_tile.cc new file mode 100644 index 00000000000..97ff63d1f7f --- /dev/null +++ b/chromium/win8/metro_driver/secondary_tile.cc @@ -0,0 +1,228 @@ +// 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/bind.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "url/gurl.h" +#include "win8/metro_driver/chrome_app_view.h" +#include "win8/metro_driver/winrt_utils.h" + +namespace { + +using base::win::MetroPinUmaResultCallback; + +// Callback for asynchronous pin requests. +class TileRequestCompleter { + public: + enum PinType { + PIN, + UNPIN + }; + TileRequestCompleter(PinType type, const MetroPinUmaResultCallback& callback) + : type_(type), callback_(callback) {} + + void Complete(mswr::ComPtr<winfoundtn::IAsyncOperation<bool>>& completion); + + private: + // Callback that responds to user input on the pin request pop-up. This will + // run |callback_|, then delete |this| before returning. + HRESULT Respond(winfoundtn::IAsyncOperation<bool>* async, + AsyncStatus status); + + PinType type_; + MetroPinUmaResultCallback callback_; +}; + +void TileRequestCompleter::Complete( + mswr::ComPtr<winfoundtn::IAsyncOperation<bool>>& completion) { + typedef winfoundtn::IAsyncOperationCompletedHandler<bool> RequestDoneType; + mswr::ComPtr<RequestDoneType> handler(mswr::Callback<RequestDoneType>( + this, &TileRequestCompleter::Respond)); + DCHECK(handler.Get() != NULL); + HRESULT hr = completion->put_Completed(handler.Get()); + CheckHR(hr, "Failed to put_Completed"); +} + +HRESULT TileRequestCompleter::Respond(winfoundtn::IAsyncOperation<bool>* async, + AsyncStatus status) { + base::win::MetroSecondaryTilePinUmaResult pin_state = + base::win::METRO_PIN_STATE_NONE; + + if (status == Completed) { + unsigned char result; + CheckHR(async->GetResults(&result)); + LOG(INFO) << __FUNCTION__ << " result " << static_cast<int>(result); + switch (result) { + case 0: + pin_state = type_ == PIN ? + base::win::METRO_PIN_RESULT_CANCEL : + base::win::METRO_UNPIN_RESULT_CANCEL; + break; + case 1: + pin_state = type_ == PIN ? + base::win::METRO_PIN_RESULT_OK : + base::win::METRO_UNPIN_RESULT_OK; + break; + default: + pin_state = type_ == PIN ? + base::win::METRO_PIN_RESULT_OTHER : + base::win::METRO_UNPIN_RESULT_OTHER; + break; + } + } else { + LOG(ERROR) << __FUNCTION__ << " Unexpected async status " + << static_cast<int>(status); + pin_state = type_ == PIN ? + base::win::METRO_PIN_RESULT_ERROR : + base::win::METRO_UNPIN_RESULT_ERROR; + } + callback_.Run(pin_state); + + delete this; + return S_OK; +} + +void DeleteTileFromStartScreen(const string16& tile_id, + const MetroPinUmaResultCallback& callback) { + 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(tile_id)); + + 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"); + + if (FAILED(hr)) { + callback.Run(base::win::METRO_UNPIN_REQUEST_SHOW_ERROR); + return; + } + + // Deleted in TileRequestCompleter::Respond when the async operation + // completes. + TileRequestCompleter* completer = + new TileRequestCompleter(TileRequestCompleter::UNPIN, callback); + completer->Complete(completion); +} + +void CreateTileOnStartScreen(const string16& tile_id, + const string16& title_str, + const string16& url_str, + const base::FilePath& logo_path, + const MetroPinUmaResultCallback& callback) { + 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(tile_id)); + + mswrw::HString args; + // The url is just passed into the tile agruments as is. Metro and desktop + // chrome will see the arguments as command line parameters. + // A GURL is used to ensure any spaces are properly escaped. + GURL url(url_str); + args.Attach(MakeHString(UTF8ToUTF16(url.spec()))); + + 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(string16(L"file:///").append(logo_path.value()))); + 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"); + + if (FAILED(hr)) { + callback.Run(base::win::METRO_PIN_REQUEST_SHOW_ERROR); + return; + } + + // Deleted in TileRequestCompleter::Respond when the async operation + // completes. + TileRequestCompleter* completer = + new TileRequestCompleter(TileRequestCompleter::PIN, callback); + completer->Complete(completion); +} + +} // namespace + +BOOL MetroIsPinnedToStartScreen(const string16& tile_id) { + 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(tile_id), &exists); + CheckHR(hr, "ISecondaryTileStatics.Exists failed"); + return exists; +} + +void MetroUnPinFromStartScreen(const string16& tile_id, + const MetroPinUmaResultCallback& callback) { + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&DeleteTileFromStartScreen, + tile_id, + callback)); +} + +void MetroPinToStartScreen(const string16& tile_id, + const string16& title, + const string16& url, + const base::FilePath& logo_path, + const MetroPinUmaResultCallback& callback) { + globals.appview_msg_loop->PostTask( + FROM_HERE, base::Bind(&CreateTileOnStartScreen, + tile_id, + title, + url, + logo_path, + callback)); +} diff --git a/chromium/win8/metro_driver/secondary_tile.h b/chromium/win8/metro_driver/secondary_tile.h new file mode 100644 index 00000000000..9740864bbdf --- /dev/null +++ b/chromium/win8/metro_driver/secondary_tile.h @@ -0,0 +1,27 @@ +// 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/files/file_path.h" +#include "base/strings/string16.h" +#include "base/win/metro.h" + +extern "C" __declspec(dllexport) +BOOL MetroIsPinnedToStartScreen(const string16& tile_id); + +extern "C" __declspec(dllexport) +void MetroUnPinFromStartScreen( + const string16& tile_id, + const base::win::MetroPinUmaResultCallback& callback); + +extern "C" __declspec(dllexport) +void MetroPinToStartScreen( + const string16& tile_id, + const string16& title, + const string16& url, + const base::FilePath& logo_path, + const base::win::MetroPinUmaResultCallback& callback); + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_SECONDARY_TILE_H_ diff --git a/chromium/win8/metro_driver/settings_handler.cc b/chromium/win8/metro_driver/settings_handler.cc new file mode 100644 index 00000000000..6feae24320f --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/settings_handler.h b/chromium/win8/metro_driver/settings_handler.h new file mode 100644 index 00000000000..5b234ad12e1 --- /dev/null +++ b/chromium/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/chromium/win8/metro_driver/stdafx.h b/chromium/win8/metro_driver/stdafx.h new file mode 100644 index 00000000000..1ce382c77b6 --- /dev/null +++ b/chromium/win8/metro_driver/stdafx.h @@ -0,0 +1,40 @@ +// 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_METRO_DRIVER_STDAFX_H_ +#define WIN8_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.storage.pickers.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 winxml = ABI::Windows::Data::Xml; +namespace windevs = ABI::Windows::Devices; +namespace winfoundtn = ABI::Windows::Foundation; +namespace wingfx = ABI::Windows::Graphics; +namespace winui = ABI::Windows::UI; +namespace winsys = ABI::Windows::System; +namespace winstorage = ABI::Windows::Storage; + +#endif // WIN8_METRO_DRIVER_STDAFX_H_ diff --git a/chromium/win8/metro_driver/toast_notification_handler.cc b/chromium/win8/metro_driver/toast_notification_handler.cc new file mode 100644 index 00000000000..ab014d4ff06 --- /dev/null +++ b/chromium/win8/metro_driver/toast_notification_handler.cc @@ -0,0 +1,252 @@ +// 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/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/shell_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, + base::win::MetroNotificationClickedHandler handler, + const wchar_t* handler_context) + : origin_url(notification_origin), + icon_url(notification_icon), + title(notification_title), + body(notification_body), + display_source(notification_display_source), + id(notification_id), + notification_handler(handler) { + if (handler_context) + notification_context = handler_context; +} + +ToastNotificationHandler::DesktopNotification::DesktopNotification() + : notification_handler(NULL) { +} + +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); + + notification_info_ = notification; + + 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); + + base::FilePath chrome_path; + if (!PathService::Get(base::FILE_EXE, &chrome_path)) { + NOTREACHED() << "Failed to get chrome exe path"; + return; + } + + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + bool is_per_user_install = InstallUtil::IsPerUserInstall( + chrome_path.value().c_str()); + string16 appid = ShellUtil::GetBrowserModelId(dist, is_per_user_install); + DVLOG(1) << "Chrome Appid is " << appid.c_str(); + + 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__; + + if (notification_info_.notification_handler) { + notification_info_.notification_handler( + notification_info_.notification_context.c_str()); + } + return S_OK; +} diff --git a/chromium/win8/metro_driver/toast_notification_handler.h b/chromium/win8/metro_driver/toast_notification_handler.h new file mode 100644 index 00000000000..8f3587dc75f --- /dev/null +++ b/chromium/win8/metro_driver/toast_notification_handler.h @@ -0,0 +1,55 @@ +// 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/strings/string16.h" +#include "base/win/metro.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; + base::win::MetroNotificationClickedHandler notification_handler; + string16 notification_context; + + 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, + base::win::MetroNotificationClickedHandler handler, + const wchar_t* handler_context); + + DesktopNotification(); + }; + + 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_; + DesktopNotification notification_info_; +}; + +#endif // CHROME_BROWSER_UI_METRO_DRIVER_TOAST_NOTIFICATION_HANDLER_H_ diff --git a/chromium/win8/metro_driver/winrt_utils.cc b/chromium/win8/metro_driver/winrt_utils.cc new file mode 100644 index 00000000000..6d66cec2328 --- /dev/null +++ b/chromium/win8/metro_driver/winrt_utils.cc @@ -0,0 +1,226 @@ +// 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/files/file_path.h" +#include "base/logging.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_comptr.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/install_util.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(), static_cast<UINT32>(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 base::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))) { + base::FilePath shortcut(path_buffer); + shortcut = shortcut.Append( + L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); + + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + base::string16 link_name = dist->GetShortcutName( + BrowserDistribution::SHORTCUT_CHROME) + installer::kLnkExt; + shortcut = shortcut.Append(link_name); + + string16 arguments; + if (GetArgumentsFromShortcut(shortcut, &arguments)) { + return arguments; + } + } + + return L""; +} + +} // namespace winrt_utils diff --git a/chromium/win8/metro_driver/winrt_utils.h b/chromium/win8/metro_driver/winrt_utils.h new file mode 100644 index 00000000000..3016c852e88 --- /dev/null +++ b/chromium/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 WIN8_METRO_DRIVER_WINRT_UTILS_H_ +#define WIN8_METRO_DRIVER_WINRT_UTILS_H_ + +#include <string> + +#include <roapi.h> +#include <windows.applicationmodel.core.h> + +#include "base/strings/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 // WIN8_METRO_DRIVER_WINRT_UTILS_H_ diff --git a/chromium/win8/metro_driver/winrt_utils_unittest.cc b/chromium/win8/metro_driver/winrt_utils_unittest.cc new file mode 100644 index 00000000000..9ae869b2520 --- /dev/null +++ b/chromium/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/chromium/win8/util/check_sdk_patch.py b/chromium/win8/util/check_sdk_patch.py new file mode 100755 index 00000000000..0a8cb9ef355 --- /dev/null +++ b/chromium/win8/util/check_sdk_patch.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# 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. + +"""Script to check that the Windows 8 SDK has been appropriately patched so that + it can be used with VS 2010. + + In practice, this checks for the presence of 'enum class' in asyncinfo.h. + Changing that to 'enum' is the only thing needed to build with the WinRT + headers in VS 2010. +""" + +import os +import sys + + +def main(argv): + if len(argv) < 2: + print "Usage: check_sdk_patch.py path_to_windows_8_sdk [dummy_output_file]" + return 1 + + # Look for asyncinfo.h + async_info_path = os.path.join(argv[1], 'Include/winrt/asyncinfo.h') + if not os.path.exists(async_info_path): + print ("Could not find %s in provided SDK path. Please check input." % + async_info_path) + print "CWD: %s" % os.getcwd() + return 2 + else: + if 'enum class' in open(async_info_path).read(): + print ("\nERROR: You are using an unpatched Windows 8 SDK located at %s." + "\nPlease see instructions at" + "\nhttp://www.chromium.org/developers/how-tos/" + "build-instructions-windows\nfor how to apply the patch to build " + "with VS2010.\n" % argv[1]) + return 3 + else: + if len(argv) > 2: + with open(argv[2], 'w') as dummy_file: + dummy_file.write('Windows 8 SDK has been patched!') + + # Patched Windows 8 SDK found. + return 0 + + +if '__main__' == __name__: + sys.exit(main(sys.argv)) diff --git a/chromium/win8/util/win8_util.cc b/chromium/win8/util/win8_util.cc new file mode 100644 index 00000000000..b630b8fdc1e --- /dev/null +++ b/chromium/win8/util/win8_util.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 "win8/util/win8_util.h" + +#include "base/win/metro.h" + +namespace win8 { + +bool IsSingleWindowMetroMode() { +#if defined(USE_ASH) + return false; +#else + return base::win::IsMetroProcess(); +#endif +} + +} // namespace win8 diff --git a/chromium/win8/util/win8_util.h b/chromium/win8/util/win8_util.h new file mode 100644 index 00000000000..25d6ec09a83 --- /dev/null +++ b/chromium/win8/util/win8_util.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 WIN8_UTIL_WIN8_UTIL_H_ +#define WIN8_UTIL_WIN8_UTIL_H_ + +namespace win8 { + +// Returns true if this process is running in fullscreen Metro mode on Win8+. +// Callers should always prefer this method to base::win::IsMetroProcess() for +// UI properties dependent on the single window mode as this method is also +// aware of the Ash environment in which multiple Chrome windows can live. +bool IsSingleWindowMetroMode(); + +} // namespace win8 + +#endif // WIN8_UTIL_WIN8_UTIL_H_ diff --git a/chromium/win8/viewer/metro_viewer_constants.cc b/chromium/win8/viewer/metro_viewer_constants.cc new file mode 100644 index 00000000000..fd54fe461d1 --- /dev/null +++ b/chromium/win8/viewer/metro_viewer_constants.cc @@ -0,0 +1,13 @@ +// Copyright 2013 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/viewer/metro_viewer_constants.h" + +namespace win8 { + +const char kMetroViewerIPCChannelName[] = "viewer"; + +const wchar_t kMetroViewerConnectVerb[] = L"connect"; + +} // namespace win8 diff --git a/chromium/win8/viewer/metro_viewer_constants.h b/chromium/win8/viewer/metro_viewer_constants.h new file mode 100644 index 00000000000..560a4508d81 --- /dev/null +++ b/chromium/win8/viewer/metro_viewer_constants.h @@ -0,0 +1,20 @@ +// Copyright 2013 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_VIEWER_METRO_VIEWER_CONSTANTS_H_ +#define WIN8_VIEWER_METRO_VIEWER_CONSTANTS_H_ + +namespace win8 { + +// The name of the IPC channel between the browser process and the metro viewer +// process. +extern const char kMetroViewerIPCChannelName[]; + +// Tells the viewer process to simply connect back without needing to launch a +// browser process itself. +extern const wchar_t kMetroViewerConnectVerb[]; + +} // namespace win8 + +#endif // WIN8_VIEWER_METRO_VIEWER_CONSTANTS_H_ diff --git a/chromium/win8/viewer/metro_viewer_process_host.cc b/chromium/win8/viewer/metro_viewer_process_host.cc new file mode 100644 index 00000000000..6832dbae6b7 --- /dev/null +++ b/chromium/win8/viewer/metro_viewer_process_host.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2013 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/viewer/metro_viewer_process_host.h" + +#include <shlobj.h> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/process/process.h" +#include "base/strings/string16.h" +#include "base/synchronization/waitable_event.h" +#include "base/time/time.h" +#include "base/win/scoped_comptr.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_macros.h" +#include "ui/aura/remote_root_window_host_win.h" +#include "ui/metro_viewer/metro_viewer_messages.h" +#include "win8/viewer/metro_viewer_constants.h" + +namespace win8 { + +MetroViewerProcessHost::InternalMessageFilter::InternalMessageFilter( + MetroViewerProcessHost* owner) : owner_(owner) { +} + +void MetroViewerProcessHost::InternalMessageFilter::OnChannelConnected( + int32 /* peer_pid */) { + owner_->NotifyChannelConnected(); +} + +MetroViewerProcessHost::MetroViewerProcessHost( + base::SingleThreadTaskRunner* ipc_task_runner) { + + channel_.reset(new IPC::ChannelProxy( + kMetroViewerIPCChannelName, + IPC::Channel::MODE_NAMED_SERVER, + this, + ipc_task_runner)); +} + +MetroViewerProcessHost::~MetroViewerProcessHost() { +} + +base::ProcessId MetroViewerProcessHost::GetViewerProcessId() { + if (channel_) + return channel_->peer_pid(); + return base::kNullProcessId; +} + +bool MetroViewerProcessHost::LaunchViewerAndWaitForConnection( + const base::string16& app_user_model_id) { + DCHECK_EQ(base::kNullProcessId, channel_->peer_pid()); + + channel_connected_event_.reset(new base::WaitableEvent(false, false)); + + scoped_refptr<InternalMessageFilter> message_filter( + new InternalMessageFilter(this)); + channel_->AddFilter(message_filter); + + base::win::ScopedComPtr<IApplicationActivationManager> activator; + HRESULT hr = activator.CreateInstance(CLSID_ApplicationActivationManager); + if (SUCCEEDED(hr)) { + DWORD pid = 0; + // Use the "connect" verb to + hr = activator->ActivateApplication( + app_user_model_id.c_str(), kMetroViewerConnectVerb, AO_NONE, &pid); + } + + LOG_IF(ERROR, FAILED(hr)) << "Tried and failed to launch Metro Chrome. " + << "hr=" << std::hex << hr; + + // Having launched the viewer process, now we wait for it to connect. + bool success = + channel_connected_event_->TimedWait(base::TimeDelta::FromSeconds(60)); + channel_connected_event_.reset(); + + // |message_filter| is only used to signal |channel_connected_event_| above + // and can thus be removed after |channel_connected_event_| is no longer + // waiting. + channel_->RemoveFilter(message_filter); + return success; +} + +bool MetroViewerProcessHost::Send(IPC::Message* msg) { + return channel_->Send(msg); +} + +bool MetroViewerProcessHost::OnMessageReceived( + const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MetroViewerProcessHost, message) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SetTargetSurface, OnSetTargetSurface) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_OpenURL, OnOpenURL) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SearchRequest, OnHandleSearchRequest) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_WindowSizeChanged, + OnWindowSizeChanged) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled ? true : + aura::RemoteRootWindowHostWin::Instance()->OnMessageReceived(message); +} + +void MetroViewerProcessHost::NotifyChannelConnected() { + if (channel_connected_event_) + channel_connected_event_->Signal(); +} + +} // namespace win8 diff --git a/chromium/win8/viewer/metro_viewer_process_host.h b/chromium/win8/viewer/metro_viewer_process_host.h new file mode 100644 index 00000000000..ad034ada07f --- /dev/null +++ b/chromium/win8/viewer/metro_viewer_process_host.h @@ -0,0 +1,105 @@ +// Copyright (c) 2013 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_VIEWER_METRO_VIEWER_PROCESS_HOST_H_ +#define WIN8_VIEWER_METRO_VIEWER_PROCESS_HOST_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/threading/non_thread_safe.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_sender.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +class SingleThreadTaskRunner; +class WaitableEvent; +} + +namespace IPC { +class Message; +} + +namespace win8 { + +// Abstract base class for various Metro viewer process host implementations. +class MetroViewerProcessHost : public IPC::Listener, + public IPC::Sender, + public base::NonThreadSafe { + public: + // Initializes a viewer process host to connect to the Metro viewer process + // over IPC. The given task runner correspond to a thread on which + // IPC::Channel is created and used (e.g. IO thread). Instantly connects to + // the viewer process if one is already connected to |ipc_channel_name|; a + // viewer can otherwise be launched synchronously via + // LaunchViewerAndWaitForConnection(). + explicit MetroViewerProcessHost( + base::SingleThreadTaskRunner* ipc_task_runner); + virtual ~MetroViewerProcessHost(); + + // Returns the process id of the viewer process if one is connected to this + // host, returns base::kNullProcessId otherwise. + base::ProcessId GetViewerProcessId(); + + // Launches the viewer process associated with the given |app_user_model_id| + // and blocks until that viewer process connects or until a timeout is + // reached. Returns true if the viewer process connects before the timeout is + // reached. NOTE: this assumes that the app referred to by |app_user_model_id| + // is registered as the default browser. + bool LaunchViewerAndWaitForConnection( + const base::string16& app_user_model_id); + + private: + // IPC::Sender implementation: + virtual bool Send(IPC::Message* msg) OVERRIDE; + + // IPC::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnChannelError() OVERRIDE = 0; + + // Called over IPC by the viewer process to tell this host that it should be + // drawing to |target_surface|. + virtual void OnSetTargetSurface(gfx::NativeViewId target_surface) = 0; + + // Called over IPC by the viewer process to request that the url passed in be + // opened. + virtual void OnOpenURL(const string16& url) = 0; + + // Called over IPC by the viewer process to request that the search string + // passed in is passed to the default search provider and a URL navigation be + // performed. + virtual void OnHandleSearchRequest(const string16& search_string) = 0; + + // Called over IPC by the viewer process when the window size has changed. + virtual void OnWindowSizeChanged(uint32 width, uint32 height) = 0; + + void NotifyChannelConnected(); + + // Inner message filter used to handle connection event on the IPC channel + // proxy's background thread. This prevents consumers of + // MetroViewerProcessHost from having to pump messages on their own message + // loop. + class InternalMessageFilter : public IPC::ChannelProxy::MessageFilter { + public: + InternalMessageFilter(MetroViewerProcessHost* owner); + + // IPC::ChannelProxy::MessageFilter implementation. + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + + private: + MetroViewerProcessHost* owner_; + DISALLOW_COPY_AND_ASSIGN(InternalMessageFilter); + }; + + scoped_ptr<IPC::ChannelProxy> channel_; + scoped_ptr<base::WaitableEvent> channel_connected_event_; + + DISALLOW_COPY_AND_ASSIGN(MetroViewerProcessHost); +}; + +} // namespace win8 + +#endif // WIN8_VIEWER_METRO_VIEWER_PROCESS_HOST_H_ diff --git a/chromium/win8/win8.gyp b/chromium/win8/win8.gyp index 00bfa3bc663..dc73584f0e3 100644 --- a/chromium/win8/win8.gyp +++ b/chromium/win8/win8.gyp @@ -51,6 +51,17 @@ ], }, { + 'target_name': 'metro_viewer_constants', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'sources': [ + 'viewer/metro_viewer_constants.cc', + 'viewer/metro_viewer_constants.h', + ], + }, + { 'target_name': 'metro_viewer', 'type': 'static_library', 'dependencies': [ @@ -58,10 +69,9 @@ '../ipc/ipc.gyp:ipc', '../ui/aura/aura.gyp:aura', '../ui/metro_viewer/metro_viewer.gyp:metro_viewer_messages', + 'metro_viewer_constants' ], 'sources': [ - 'viewer/metro_viewer_constants.cc', - 'viewer/metro_viewer_constants.h', 'viewer/metro_viewer_process_host.cc', 'viewer/metro_viewer_process_host.h', ], |