summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ui/aura/remote_root_window_host_win.cc88
-rw-r--r--ui/aura/remote_root_window_host_win.h22
-rw-r--r--win8/metro_driver/chrome_app_view_ash.cc54
-rw-r--r--win8/metro_driver/chrome_app_view_ash.h30
-rw-r--r--win8/metro_driver/ime/ime.gypi16
-rw-r--r--win8/metro_driver/ime/input_scope.cc87
-rw-r--r--win8/metro_driver/ime/input_scope.h22
-rw-r--r--win8/metro_driver/ime/text_service.cc494
-rw-r--r--win8/metro_driver/ime/text_service.h53
-rw-r--r--win8/metro_driver/ime/text_service_delegate.h40
-rw-r--r--win8/metro_driver/ime/text_store.cc913
-rw-r--r--win8/metro_driver/ime/text_store.h308
-rw-r--r--win8/metro_driver/ime/text_store_delegate.h55
-rw-r--r--win8/metro_driver/metro_driver.gyp3
14 files changed, 2183 insertions, 2 deletions
diff --git a/ui/aura/remote_root_window_host_win.cc b/ui/aura/remote_root_window_host_win.cc
index ff52d07..0b58d1a 100644
--- a/ui/aura/remote_root_window_host_win.cc
+++ b/ui/aura/remote_root_window_host_win.cc
@@ -11,9 +11,15 @@
#include "base/message_loop/message_loop.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_sender.h"
+#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/root_window.h"
+#include "ui/aura/window_property.h"
#include "ui/base/cursor/cursor_loader_win.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/input_method.h"
+#include "ui/base/ime/remote_input_method_win.h"
+#include "ui/base/ime/text_input_client.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/base/view_prop.h"
@@ -54,6 +60,25 @@ void SetVirtualKeyStates(uint32 flags) {
::SetKeyboardState(keyboard_state);
}
+void FillCompositionText(
+ const string16& text,
+ int32 selection_start,
+ int32 selection_end,
+ const std::vector<metro_viewer::UnderlineInfo>& underlines,
+ ui::CompositionText* composition_text) {
+ composition_text->Clear();
+ composition_text->text = text;
+ composition_text->selection.set_start(selection_start);
+ composition_text->selection.set_end(selection_end);
+ composition_text->underlines.resize(underlines.size());
+ for (size_t i = 0; i < underlines.size(); ++i) {
+ composition_text->underlines[i].start_offset = underlines[i].start_offset;
+ composition_text->underlines[i].end_offset = underlines[i].end_offset;
+ composition_text->underlines[i].color = SK_ColorBLACK;
+ composition_text->underlines[i].thick = underlines[i].thick;
+ }
+}
+
} // namespace
void HandleOpenFile(const base::string16& title,
@@ -152,6 +177,10 @@ void RemoteRootWindowHostWin::Connected(IPC::Sender* host, HWND remote_window) {
void RemoteRootWindowHostWin::Disconnected() {
// Don't CHECK here, Disconnected is called on a channel error which can
// happen before we're successfully Connected.
+ ui::RemoteInputMethodPrivateWin* remote_input_method_private =
+ GetRemoteInputMethodPrivate();
+ if (remote_input_method_private)
+ remote_input_method_private->SetRemoteDelegate(NULL);
host_ = NULL;
remote_window_ = NULL;
}
@@ -184,6 +213,10 @@ bool RemoteRootWindowHostWin::OnMessageReceived(const IPC::Message& message) {
OnSetCursorPosAck)
IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ActivateDesktopDone,
OnDesktopActivated)
+ IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ImeCompositionChanged,
+ OnImeCompositionChanged)
+ IPC_MESSAGE_HANDLER(MetroViewerHostMsg_ImeTextCommitted,
+ OnImeTextCommitted)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -318,6 +351,10 @@ gfx::AcceleratedWidget RemoteRootWindowHostWin::GetAcceleratedWidget() {
}
void RemoteRootWindowHostWin::Show() {
+ ui::RemoteInputMethodPrivateWin* remote_input_method_private =
+ GetRemoteInputMethodPrivate();
+ if (remote_input_method_private)
+ remote_input_method_private->SetRemoteDelegate(this);
}
void RemoteRootWindowHostWin::Hide() {
@@ -422,6 +459,27 @@ void RemoteRootWindowHostWin::OnDeviceScaleFactorChanged(
void RemoteRootWindowHostWin::PrepareForShutdown() {
}
+void RemoteRootWindowHostWin::CancelComposition() {
+ host_->Send(new MetroViewerHostMsg_ImeCancelComposition);
+}
+
+void RemoteRootWindowHostWin::OnTextInputClientUpdated(
+ const std::vector<int32>& input_scopes,
+ const std::vector<gfx::Rect>& composition_character_bounds) {
+ std::vector<metro_viewer::CharacterBounds> character_bounds;
+ for (size_t i = 0; i < composition_character_bounds.size(); ++i) {
+ const gfx::Rect& rect = composition_character_bounds[i];
+ metro_viewer::CharacterBounds bounds;
+ bounds.left = rect.x();
+ bounds.top = rect.y();
+ bounds.right = rect.right();
+ bounds.bottom = rect.bottom();
+ character_bounds.push_back(bounds);
+ }
+ host_->Send(new MetroViewerHostMsg_ImeTextInputClientUpdated(
+ input_scopes, character_bounds));
+}
+
void RemoteRootWindowHostWin::OnMouseMoved(int32 x, int32 y, int32 flags) {
if (ignore_mouse_moves_until_set_cursor_ack_)
return;
@@ -581,6 +639,36 @@ void RemoteRootWindowHostWin::OnDesktopActivated() {
temp.Run();
}
+ui::RemoteInputMethodPrivateWin*
+RemoteRootWindowHostWin::GetRemoteInputMethodPrivate() {
+ ui::InputMethod* input_method = GetAshWindow()->GetProperty(
+ aura::client::kRootWindowInputMethodKey);
+ return ui::RemoteInputMethodPrivateWin::Get(input_method);
+}
+
+void RemoteRootWindowHostWin::OnImeCompositionChanged(
+ const string16& text,
+ int32 selection_start,
+ int32 selection_end,
+ const std::vector<metro_viewer::UnderlineInfo>& underlines) {
+ ui::RemoteInputMethodPrivateWin* remote_input_method_private =
+ GetRemoteInputMethodPrivate();
+ if (!remote_input_method_private)
+ return;
+ ui::CompositionText composition_text;
+ FillCompositionText(
+ text, selection_start, selection_end, underlines, &composition_text);
+ remote_input_method_private->OnCompositionChanged(composition_text);
+}
+
+void RemoteRootWindowHostWin::OnImeTextCommitted(const string16& text) {
+ ui::RemoteInputMethodPrivateWin* remote_input_method_private =
+ GetRemoteInputMethodPrivate();
+ if (!remote_input_method_private)
+ return;
+ remote_input_method_private->OnTextCommitted(text);
+}
+
void RemoteRootWindowHostWin::DispatchKeyboardMessage(ui::EventType type,
uint32 vkey,
uint32 repeat_count,
diff --git a/ui/aura/remote_root_window_host_win.h b/ui/aura/remote_root_window_host_win.h
index b7d9148..92788da 100644
--- a/ui/aura/remote_root_window_host_win.h
+++ b/ui/aura/remote_root_window_host_win.h
@@ -11,15 +11,18 @@
#include "base/compiler_specific.h"
#include "base/strings/string16.h"
#include "ui/aura/window_tree_host.h"
+#include "ui/base/ime/remote_input_method_delegate_win.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/native_widget_types.h"
+#include "ui/metro_viewer/ime_types.h"
namespace base {
class FilePath;
}
namespace ui {
+class RemoteInputMethodPrivateWin;
class ViewProp;
}
@@ -92,7 +95,9 @@ AURA_EXPORT void HandleActivateDesktop(
// RootWindowHost implementaton that receives events from a different
// process. In the case of Windows this is the Windows 8 (aka Metro)
// frontend process, which forwards input events to this class.
-class AURA_EXPORT RemoteRootWindowHostWin : public RootWindowHost {
+class AURA_EXPORT RemoteRootWindowHostWin
+ : public RootWindowHost,
+ public ui::internal::RemoteInputMethodDelegateWin {
public:
// Returns the only RemoteRootWindowHostWin, if this is the first time
// this function is called, it will call Create() wiht empty bounds.
@@ -185,6 +190,15 @@ class AURA_EXPORT RemoteRootWindowHostWin : public RootWindowHost {
void OnSetCursorPosAck();
void OnDesktopActivated();
+ // For Input Method support:
+ ui::RemoteInputMethodPrivateWin* GetRemoteInputMethodPrivate();
+ void OnImeCompositionChanged(
+ const string16& text,
+ int32 selection_start,
+ int32 selection_end,
+ const std::vector<metro_viewer::UnderlineInfo>& underlines);
+ void OnImeTextCommitted(const string16& text);
+
// RootWindowHost overrides:
virtual RootWindow* GetRootWindow() OVERRIDE;
virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE;
@@ -208,6 +222,12 @@ class AURA_EXPORT RemoteRootWindowHostWin : public RootWindowHost {
virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE;
virtual void PrepareForShutdown() OVERRIDE;
+ // ui::internal::RemoteInputMethodDelegateWin overrides:
+ virtual void CancelComposition() OVERRIDE;
+ virtual void OnTextInputClientUpdated(
+ const std::vector<int32>& input_scopes,
+ const std::vector<gfx::Rect>& composition_character_bounds) OVERRIDE;
+
// Helper function to dispatch a keyboard message to the desired target.
// The default target is the RootWindowHostDelegate. For nested message loop
// invocations we post a synthetic keyboard message directly into the message
diff --git a/win8/metro_driver/chrome_app_view_ash.cc b/win8/metro_driver/chrome_app_view_ash.cc
index 331ec16..c557bb5 100644
--- a/win8/metro_driver/chrome_app_view_ash.cc
+++ b/win8/metro_driver/chrome_app_view_ash.cc
@@ -24,6 +24,7 @@
#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/text_service.h"
#include "win8/metro_driver/metro_driver.h"
#include "win8/metro_driver/winrt_utils.h"
#include "win8/viewer/metro_viewer_constants.h"
@@ -164,6 +165,10 @@ class ChromeChannelListener : public IPC::Listener {
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;
@@ -241,6 +246,23 @@ class ChromeChannelListener : public IPC::Listener {
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_;
@@ -472,6 +494,8 @@ ChromeAppViewAsh::SetWindow(winui::Core::ICoreWindow* window) {
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_);
@@ -619,6 +643,7 @@ ChromeAppViewAsh::Run() {
IFACEMETHODIMP
ChromeAppViewAsh::Uninitialize() {
DVLOG(1) << __FUNCTION__;
+ text_service_.reset();
window_ = nullptr;
view_ = nullptr;
core_window_hwnd_ = NULL;
@@ -787,6 +812,33 @@ void ChromeAppViewAsh::OnFolderPickerCompleted(
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::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) {
@@ -1033,6 +1085,8 @@ HRESULT ChromeAppViewAsh::OnWindowActivated(
// 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;
diff --git a/win8/metro_driver/chrome_app_view_ash.h b/win8/metro_driver/chrome_app_view_ash.h
index 9efd3f7..d50255b 100644
--- a/win8/metro_driver/chrome_app_view_ash.h
+++ b/win8/metro_driver/chrome_app_view_ash.h
@@ -15,6 +15,7 @@
#include "base/strings/string16.h"
#include "ui/events/event_constants.h"
#include "win8/metro_driver/direct3d_helper.h"
+#include "win8/metro_driver/ime/text_service_delegate.h"
namespace base {
class FilePath;
@@ -25,6 +26,15 @@ class Listener;
class ChannelProxy;
}
+namespace metro_driver {
+class TextService;
+}
+
+namespace metro_viewer {
+struct CharacterBounds;
+struct UnderlineInfo;
+}
+
class OpenFilePickerSession;
class SaveFilePickerSession;
class FolderPickerSession;
@@ -33,7 +43,8 @@ class FilePickerSessionBase;
struct MetroViewerHostMsg_SaveAsDialogParams;
class ChromeAppViewAsh
- : public mswr::RuntimeClass<winapp::Core::IFrameworkView> {
+ : public mswr::RuntimeClass<winapp::Core::IFrameworkView>,
+ public metro_driver::TextServiceDelegate {
public:
ChromeAppViewAsh();
~ChromeAppViewAsh();
@@ -82,9 +93,23 @@ class ChromeAppViewAsh
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:
+ // 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);
@@ -163,6 +188,9 @@ class ChromeAppViewAsh
// UI message loop to allow message passing into this thread.
base::MessageLoop ui_loop_;
+
+ // For IME support.
+ scoped_ptr<metro_driver::TextService> text_service_;
};
#endif // WIN8_METRO_DRIVER_CHROME_APP_VIEW_ASH_H_
diff --git a/win8/metro_driver/ime/ime.gypi b/win8/metro_driver/ime/ime.gypi
new file mode 100644
index 0000000..c5d7f1e
--- /dev/null
+++ b/win8/metro_driver/ime/ime.gypi
@@ -0,0 +1,16 @@
+# 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.
+
+{
+ 'sources': [
+ 'input_scope.cc',
+ 'input_scope.h',
+ 'text_service.cc',
+ 'text_service.h',
+ 'text_service_delegate.h',
+ 'text_store.cc',
+ 'text_store.h',
+ 'text_store_delegate.h',
+ ],
+}
diff --git a/win8/metro_driver/ime/input_scope.cc b/win8/metro_driver/ime/input_scope.cc
new file mode 100644
index 0000000..82679c3
--- /dev/null
+++ b/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/win8/metro_driver/ime/input_scope.h b/win8/metro_driver/ime/input_scope.h
new file mode 100644
index 0000000..99ed283
--- /dev/null
+++ b/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/win8/metro_driver/ime/text_service.cc b/win8/metro_driver/ime/text_service.cc
new file mode 100644
index 0000000..ecb7ddf
--- /dev/null
+++ b/win8/metro_driver/ime/text_service.cc
@@ -0,0 +1,494 @@
+// 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 {
+typedef TSFTextStore TextStore;
+
+// 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/win8/metro_driver/ime/text_service.h b/win8/metro_driver/ime/text_service.h
new file mode 100644
index 0000000..6ddd21f
--- /dev/null
+++ b/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/win8/metro_driver/ime/text_service_delegate.h b/win8/metro_driver/ime/text_service_delegate.h
new file mode 100644
index 0000000..ea9cde7
--- /dev/null
+++ b/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/win8/metro_driver/ime/text_store.cc b/win8/metro_driver/ime/text_store.cc
new file mode 100644
index 0000000..960af56
--- /dev/null
+++ b/win8/metro_driver/ime/text_store.cc
@@ -0,0 +1,913 @@
+// 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.
+
+#define INITGUID // required for GUID_PROP_INPUTSCOPE
+#include "win8/metro_driver/ime/text_store.h"
+
+#include <InputScope.h>
+#include <OleCtl.h>
+
+#include <algorithm>
+
+#include "base/win/scoped_variant.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
+
+TSFTextStore::TSFTextStore(HWND window_handle,
+ ITfCategoryMgr* category_manager,
+ ITfDisplayAttributeMgr* display_attribute_manager,
+ ITfInputScope* input_scope,
+ TextStoreDelegate* delegate)
+ : ref_count_(0),
+ text_store_acp_sink_mask_(0),
+ window_handle_(window_handle),
+ delegate_(delegate),
+ committed_size_(0),
+ selection_start_(0),
+ selection_end_(0),
+ edit_flag_(false),
+ current_lock_type_(0),
+ category_manager_(category_manager),
+ display_attribute_manager_(display_attribute_manager),
+ input_scope_(input_scope) {
+}
+
+// static
+scoped_refptr<TSFTextStore> TSFTextStore::Create(
+ HWND window_handle,
+ const std::vector<InputScope>& input_scopes,
+ TextStoreDelegate* delegate) {
+ if (!delegate) {
+ LOG(ERROR) << "|delegate| must be non-NULL.";
+ return scoped_refptr<TSFTextStore>();
+ }
+ 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<TSFTextStore>();
+ }
+ 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<TSFTextStore>();
+ }
+ base::win::ScopedComPtr<ITfInputScope> input_scope =
+ CreteInputScope(input_scopes);
+ if (!input_scope) {
+ LOG(ERROR) << "Failed to initialize InputScope.";
+ return scoped_refptr<TSFTextStore>();
+ }
+ return scoped_refptr<TSFTextStore>(
+ new TSFTextStore(window_handle,
+ category_manager,
+ display_attribute_manager,
+ input_scope,
+ delegate));
+}
+
+TSFTextStore::~TSFTextStore() {
+}
+
+ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() {
+ return InterlockedIncrement(&ref_count_);
+}
+
+ULONG STDMETHODCALLTYPE TSFTextStore::Release() {
+ const LONG count = InterlockedDecrement(&ref_count_);
+ if (!count) {
+ delete this;
+ return 0;
+ }
+ return static_cast<ULONG>(count);
+}
+
+STDMETHODIMP TSFTextStore::QueryInterface(REFIID iid, void** result) {
+ if (iid == IID_IUnknown || iid == IID_ITextStoreACP) {
+ *result = static_cast<ITextStoreACP*>(this);
+ } else if (iid == IID_ITfContextOwnerCompositionSink) {
+ *result = static_cast<ITfContextOwnerCompositionSink*>(this);
+ } else if (iid == IID_ITfTextEditSink) {
+ *result = static_cast<ITfTextEditSink*>(this);
+ } else {
+ *result = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP TSFTextStore::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 TSFTextStore::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 TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie,
+ const POINT* point,
+ DWORD flags,
+ LONG* acp) {
+ NOTIMPLEMENTED();
+ if (view_cookie != kViewCookie)
+ return E_INVALIDARG;
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) {
+ if (!view_cookie)
+ return E_INVALIDARG;
+ // We support only one view.
+ *view_cookie = kViewCookie;
+ return S_OK;
+}
+
+STDMETHODIMP TSFTextStore::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 TSFTextStore::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 TSFTextStore::GetFormattedText(LONG acp_start, LONG acp_end,
+ IDataObject** data_object) {
+ NOTIMPLEMENTED();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::OnStartComposition(
+ ITfCompositionView* composition_view,
+ BOOL* ok) {
+ if (ok)
+ *ok = TRUE;
+ return S_OK;
+}
+
+STDMETHODIMP TSFTextStore::OnUpdateComposition(
+ ITfCompositionView* composition_view,
+ ITfRange* range) {
+ return S_OK;
+}
+
+STDMETHODIMP TSFTextStore::OnEndComposition(
+ ITfCompositionView* composition_view) {
+ return S_OK;
+}
+
+STDMETHODIMP TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::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 TSFTextStore::HasReadLock() const {
+ return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
+}
+
+bool TSFTextStore::HasReadWriteLock() const {
+ return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;
+}
+
+} // namespace metro_driver
diff --git a/win8/metro_driver/ime/text_store.h b/win8/metro_driver/ime/text_store.h
new file mode 100644
index 0000000..3a308cf
--- /dev/null
+++ b/win8/metro_driver/ime/text_store.h
@@ -0,0 +1,308 @@
+// 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 <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;
+
+// TSFTextStore is used to interact with the input method via TSF manager.
+// TSFTextStore have a string buffer which is manipulated by TSF manager through
+// ITextStoreACP interface methods such as SetText().
+// When the input method updates the composition, TSFTextStore calls
+// TextInputClient::SetCompositionText(). And when the input method finishes the
+// composition, TSFTextStore calls TextInputClient::InsertText() and clears the
+// buffer.
+//
+// How TSFTextStore works:
+// - The user enters "a".
+// - The input method set composition as "a".
+// - TSF manager calls TSFTextStore::RequestLock().
+// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
+// - In OnLockGranted(), TSF manager calls
+// - TSFTextStore::OnStartComposition()
+// - TSFTextStore::SetText()
+// The string buffer is set as "a".
+// - TSFTextStore::OnUpdateComposition()
+// - TSFTextStore::OnEndEdit()
+// TSFTextStore can get the composition information such as underlines.
+// - TSFTextStore 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 TSFTextStore::RequestLock().
+// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
+// - In OnLockGranted(), TSF manager calls
+// - TSFTextStore::SetText()
+// The string buffer is set as "A".
+// - TSFTextStore::OnUpdateComposition()
+// - TSFTextStore::OnEndEdit()
+// - TSFTextStore calls TextInputClient::SetCompositionText().
+// "A" is shown with an underline as composition string.
+// - The user enters <enter>.
+// - The input method commits "A".
+// - TSF manager calls TSFTextStore::RequestLock().
+// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
+// - In OnLockGranted(), TSF manager calls
+// - TSFTextStore::OnEndComposition()
+// - TSFTextStore::OnEndEdit()
+// TSFTextStore knows "A" is committed.
+// - TSFTextStore calls TextInputClient::InsertText().
+// "A" is shown as committed string.
+// - TSFTextStore clears the string buffer.
+// - TSFTextStore 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 TSFTextStore 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, TSFTextStore
+// 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
+// TODO(yukawa): Rename TSFTextStore to TextStore.
+class TSFTextStore : public ITextStoreACP,
+ public ITfContextOwnerCompositionSink,
+ public ITfTextEditSink {
+ public:
+ virtual ~TSFTextStore();
+
+ // ITextStoreACP:
+ STDMETHOD_(ULONG, AddRef)() OVERRIDE;
+ STDMETHOD_(ULONG, Release)() OVERRIDE;
+ STDMETHOD(QueryInterface)(REFIID iid, void** ppv) OVERRIDE;
+ 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 TSFTextStore. Returns NULL if fails.
+ static scoped_refptr<TSFTextStore> Create(
+ HWND window_handle,
+ const std::vector<InputScope>& input_scopes,
+ TextStoreDelegate* delegate);
+
+ private:
+ TSFTextStore(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);
+
+ // The refrence count of this instance.
+ volatile LONG ref_count_;
+
+ // 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_;
+
+ // The delegate attached to this text store.
+ TextStoreDelegate* delegate_;
+
+ // |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_;
+
+ DISALLOW_COPY_AND_ASSIGN(TSFTextStore);
+};
+
+} // namespace metro_driver
+
+#endif // WIN8_METRO_DRIVER_IME_TEXT_STORE_H_
diff --git a/win8/metro_driver/ime/text_store_delegate.h b/win8/metro_driver/ime/text_store_delegate.h
new file mode 100644
index 0000000..af06516
--- /dev/null
+++ b/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/win8/metro_driver/metro_driver.gyp b/win8/metro_driver/metro_driver.gyp
index 33e7e48..1b81a79 100644
--- a/win8/metro_driver/metro_driver.gyp
+++ b/win8/metro_driver/metro_driver.gyp
@@ -85,6 +85,9 @@
'file_picker_ash.cc',
'file_picker_ash.h',
],
+ 'includes': [
+ 'ime/ime.gypi',
+ ],
}, { # use_aura!=1
'sources': [
'chrome_app_view.cc',