blob: 7010fad613134417e2becd47fad8068aca65cf3d [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/tip/tip_text_service.h"
#include <Ime.h>
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <objbase.h>
#include <memory>
#include <string>
#include <unordered_map>
#include "base/const.h"
#include "base/logging.h"
#include "base/port.h"
#include "base/process.h"
#include "base/update_util.h"
#include "base/util.h"
#include "base/win_util.h"
#include "session/commands.pb.h"
#include "win32/base/conversion_mode_util.h"
#include "win32/base/indicator_visibility_tracker.h"
#include "win32/base/input_state.h"
#include "win32/base/win32_window_util.h"
#include "win32/tip/tip_class_factory.h"
#include "win32/tip/tip_composition_util.h"
#include "win32/tip/tip_display_attributes.h"
#include "win32/tip/tip_dll_module.h"
#include "win32/tip/tip_edit_session.h"
#include "win32/tip/tip_edit_session_impl.h"
#include "win32/tip/tip_enum_display_attributes.h"
#include "win32/tip/tip_input_mode_manager.h"
#include "win32/tip/tip_keyevent_handler.h"
#include "win32/tip/tip_lang_bar.h"
#include "win32/tip/tip_lang_bar_menu.h"
#include "win32/tip/tip_preferred_touch_keyboard.h"
#include "win32/tip/tip_private_context.h"
#include "win32/tip/tip_reconvert_function.h"
#include "win32/tip/tip_ref_count.h"
#include "win32/tip/tip_resource.h"
#include "win32/tip/tip_status.h"
#include "win32/tip/tip_thread_context.h"
#include "win32/tip/tip_ui_handler.h"
namespace mozc {
namespace win32 {
namespace tsf {
namespace {
using ATL::CComBSTR;
using ATL::CComPtr;
using ATL::CComQIPtr;
// Represents the module handle of this module.
volatile HMODULE g_module = nullptr;
// True if the the DLL received DLL_PROCESS_DETACH notification.
volatile bool g_module_unloaded = false;
// Thread Local Storage (TLS) index to specify the current UI thread is
// initialized or not. if ::GetTlsValue(g_tls_index) returns non-zero
// value, the current thread is initialized.
volatile DWORD g_tls_index = TLS_OUT_OF_INDEXES;
const UINT kUpdateUIMessage = WM_USER;
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
const char kHelpUrl[] = "http://www.google.com/support/ime/japanese";
const char kLogFileName[] = "GoogleJapaneseInput_tsf_ui";
const wchar_t kTaskWindowClassName[] =
L"Google Japanese Input Task Message Window";
// {67526BED-E4BE-47CA-97F8-3C84D5B408DA}
const GUID kTipPreservedKey_Kanji = {
0x67526bed, 0xe4be, 0x47ca, {0x97, 0xf8, 0x3c, 0x84, 0xd5, 0xb4, 0x08, 0xda}
};
// {B62565AA-288A-432B-B517-EC333E0F99F3}
const GUID kTipPreservedKey_F10 = {
0xb62565aa, 0x288a, 0x432b, {0xb5, 0x17, 0xec, 0x33, 0x3e, 0xf, 0x99, 0xf3}
};
// {CF6E26FB-1A11-4D81-BD92-52FA852A42EB}
const GUID kTipPreservedKey_Romaji = {
0xcf6e26fb, 0x1a11, 0x4d81, {0xbd, 0x92, 0x52, 0xfa, 0x85, 0x2a, 0x42, 0xeb}
};
// {EEBABC50-7FEC-4A08-9E1D-0BEF628B5F0E}
const GUID kTipFunctionProvider = {
0xeebabc50, 0x7fec, 0x4a08, {0x9e, 0x1d, 0xb, 0xef, 0x62, 0x8b, 0x5f, 0x0e}
};
#else
const char kHelpUrl[] = "http://code.google.com/p/mozc/";
const char kLogFileName[] = "Mozc_tsf_ui";
const wchar_t kTaskWindowClassName[] = L"Mozc Immersive Task Message Window";
// {F16B7D92-84B0-4AC6-A35B-06EA77180A18}
const GUID kTipPreservedKey_Kanji = {
0xf16b7d92, 0x84b0, 0x4ac6, {0xa3, 0x5b, 0x6, 0xea, 0x77, 0x18, 0x0a, 0x18}
};
// {80DAD291-1981-46FA-998D-B84D6C1BA02C}
const GUID kTipPreservedKey_F10 = {
0x80dad291, 0x1981, 0x46fa, {0x99, 0x8d, 0xb8, 0x4d, 0x6c, 0x1b, 0xa0, 0x2c}
};
// {95571C08-B05A-4ABA-B038-F3DEAE532F91}
const GUID kTipPreservedKey_Romaji = {
0x95571c08, 0xb05a, 0x4aba, {0xb0, 0x38, 0xf3, 0xde, 0xae, 0x53, 0x2f, 0x91}
};
// {ECFB2528-E7D2-4CA0-BBE4-32FE08C148F4}
const GUID kTipFunctionProvider = {
0xecfb2528, 0xe7d2, 0x4ca0, {0xbb, 0xe4, 0x32, 0xfe, 0x8, 0xc1, 0x48, 0xf4}
};
#endif
// This flag is available in Windows SDK 8.0 and later.
#ifndef TF_TMF_IMMERSIVEMODE
#define TF_TMF_IMMERSIVEMODE 0x40000000
#endif // !TF_TMF_IMMERSIVEMODE
HRESULT SpawnTool(const string &command) {
if (!Process::SpawnMozcProcess(kMozcTool, "--mode=" + command)) {
return E_FAIL;
}
return S_OK;
}
commands::CompositionMode GetMozcMode(TipLangBarCallback::ItemId menu_id) {
switch (menu_id) {
case TipLangBarCallback::kDirect:
return commands::DIRECT;
case TipLangBarCallback::kHiragana:
return commands::HIRAGANA;
case TipLangBarCallback::kFullKatakana:
return commands::FULL_KATAKANA;
case TipLangBarCallback::kHalfAlphanumeric:
return commands::HALF_ASCII;
case TipLangBarCallback::kFullAlphanumeric:
return commands::FULL_ASCII;
case TipLangBarCallback::kHalfKatakana:
return commands::HALF_KATAKANA;
default:
DLOG(FATAL) << "Must not reach here.";
return commands::DIRECT;
}
}
string GetMozcToolCommand(TipLangBarCallback::ItemId menu_id) {
switch (menu_id) {
case TipLangBarCallback::kProperty:
// Open the config dialog.
return "config_dialog";
case TipLangBarCallback::kDictionary:
// Open the dictionary tool.
return "dictionary_tool";
case TipLangBarCallback::kWordRegister:
// Open the word register dialog.
return "word_register_dialog";
case TipLangBarCallback::kHandWriting:
// Open the Hand Writing Tool.
return "hand_writing";
case TipLangBarCallback::kCharacterPalette:
// Open the Character Palette dialog.
return "character_palette";
case TipLangBarCallback::kAbout:
// Open the about dialog.
return "about_dialog";
default:
DLOG(FATAL) << "Must not reach here.";
return "";
}
}
void EnsureKanaLockUnlocked() {
// Clear Kana-lock state so that users can input their passwords.
BYTE keyboard_state[256];
::GetKeyboardState(keyboard_state);
keyboard_state[VK_KANA] = 0;
::SetKeyboardState(keyboard_state);
}
// A COM-independent way to instantiate Category Manager object.
CComPtr<ITfCategoryMgr> GetCategoryMgr() {
const HMODULE module = WinUtil::GetSystemModuleHandle(L"msctf.dll");
if (module == nullptr) {
return nullptr;
}
void *function = ::GetProcAddress(module, "TF_CreateCategoryMgr");
if (function == nullptr) {
return nullptr;
}
typedef HRESULT (WINAPI *FPTF_CreateCategoryMgr)(ITfCategoryMgr **object);
CComPtr<ITfCategoryMgr> ptr;
const HRESULT result =
reinterpret_cast<FPTF_CreateCategoryMgr>(function)(&ptr);
if (FAILED(result)) {
return nullptr;
}
return ptr;
}
// Custom hash function for ATL::CComPtr.
template <typename T>
struct CComPtrHash {
size_t operator()(const CComPtr<T> &value) const {
// Caveats: On x86 environment, both _M_X64 and _M_IX86 are defined. So we
// need to check _M_X64 first.
#if defined(_M_X64)
const size_t kUnusedBits = 3; // assuming 8-byte aligned
#elif defined(_M_IX86)
const size_t kUnusedBits = 2; // assuming 4-byte aligned
#else
#error "unsupported platform"
#endif
// Compress the data by shifting unused bits.
return reinterpret_cast<size_t>(value.p) >> kUnusedBits;
}
};
// Custom hash function for GUID.
struct GuidHash {
size_t operator()(const GUID &value) const {
// Compress the data by shifting unused bits.
return value.Data1;
}
};
// An observer that binds ITfCompositionSink::OnCompositionTerminated callback
// to TipEditSession::OnCompositionTerminated.
class CompositionSinkImpl : public ITfCompositionSink {
public:
CompositionSinkImpl(TipTextService *text_service, ITfContext *context)
: text_service_(text_service),
context_(context) {
}
// The IUnknown interface methods.
virtual STDMETHODIMP QueryInterface(REFIID interface_id, void **object) {
if (!object) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object.
// This object implements IUnknown and ITfEditSession.
if (::IsEqualIID(interface_id, IID_IUnknown)) {
*object = static_cast<IUnknown *>(this);
} else if (IsEqualIID(interface_id, IID_ITfCompositionSink)) {
*object = static_cast<ITfCompositionSink *>(this);
} else {
*object = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ref_count_.AddRefImpl();
}
virtual ULONG STDMETHODCALLTYPE Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// Implements the ITfCompositionSink::OnCompositionTerminated() function.
// This function is called by Windows when an ongoing composition is
// terminated by applications.
virtual STDMETHODIMP OnCompositionTerminated(TfEditCookie write_cookie,
ITfComposition *composition) {
return TipEditSessionImpl::OnCompositionTerminated(
text_service_, context_, composition, write_cookie);
}
private:
TipRefCount ref_count_;
CComPtr<TipTextService> text_service_;
CComPtr<ITfContext> context_;
DISALLOW_COPY_AND_ASSIGN(CompositionSinkImpl);
};
void CloseUIElement(ITfUIElementMgr *ui_element_mgr, DWORD id) {
CComPtr<ITfUIElement> element;
ui_element_mgr->GetUIElement(id, &element);
if (element) {
element->Show(FALSE);
}
ui_element_mgr->EndUIElement(id);
if (element) {
// This corresponds to the additional AddRef just after
// ITfUIElementMgr::BeginUIElement. See the comment in
// tip_edit_session.cc.
static_cast<ITfUIElement *>(element)->Release();
}
}
// Represents preserved keys used by this class.
const wchar_t kTipKeyTilde[] = L"OnOff";
const wchar_t kTipKeyKanji[] = L"Kanji";
const wchar_t kTipKeyF10[] = L"Function 10";
const wchar_t kTipKeyRoman[] = L"Roman";
const wchar_t kTipKeyNoRoman[] = L"NoRoman";
struct PreserveKeyItem {
const GUID &guid;
TF_PRESERVEDKEY key;
DWORD mapped_vkey;
const wchar_t *description;
size_t length;
};
const PreserveKeyItem kPreservedKeyItems[] = {
{
kTipPreservedKey_Kanji,
{ VK_OEM_3, TF_MOD_ALT },
VK_OEM_3,
&kTipKeyTilde[0],
arraysize(kTipKeyTilde) - 1
}, {
kTipPreservedKey_Kanji,
{ VK_KANJI, TF_MOD_IGNORE_ALL_MODIFIER },
// KeyEventHandler maps VK_KANJI to KeyEvent::NO_SPECIALKEY instead of
// KeyEvent::KANJI because of an anomaly of IMM32 behavior. So, in TSF
// mode, we treat VK_KANJI as if it was VK_DBE_DBCSCHAR. See b/7592743 and
// b/7970379 about what happened.
VK_DBE_DBCSCHAR,
&kTipKeyKanji[0],
arraysize(kTipKeyKanji) - 1
}, {
kTipPreservedKey_Romaji,
{ VK_DBE_ROMAN, TF_MOD_IGNORE_ALL_MODIFIER },
VK_DBE_ROMAN,
&kTipKeyRoman[0],
arraysize(kTipKeyRoman) - 1
}, {
kTipPreservedKey_Romaji,
{ VK_DBE_NOROMAN, TF_MOD_IGNORE_ALL_MODIFIER },
VK_DBE_NOROMAN,
&kTipKeyNoRoman[0],
arraysize(kTipKeyNoRoman) - 1
}, {
kTipPreservedKey_F10,
{ VK_F10, 0 },
VK_F10,
&kTipKeyF10[0],
arraysize(kTipKeyF10) - 1
},
};
class UpdateUiEditSessionImpl : public ITfEditSession {
public:
// This destructor is non-virtual because the instance of this class is
// deleted by and only by "delete this" in the Release method.
~UpdateUiEditSessionImpl() {}
// The IUnknown interface methods.
virtual STDMETHODIMP QueryInterface(REFIID interface_id, void **object) {
if (!object) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object.
// This object implements IUnknown and ITfEditSession.
if (::IsEqualIID(interface_id, IID_IUnknown)) {
*object = static_cast<IUnknown *>(this);
} else if (IsEqualIID(interface_id, IID_ITfEditSession)) {
*object = static_cast<ITfEditSession *>(this);
} else {
*object = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ref_count_.AddRefImpl();
}
virtual ULONG STDMETHODCALLTYPE Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// The ITfEditSession interface method.
// This function is called back by the TSF thread manager when an edit
// request is granted.
virtual HRESULT STDMETHODCALLTYPE DoEditSession(TfEditCookie edit_cookie) {
TipUiHandler::Update(text_service_, context_, edit_cookie);
return S_OK;
}
static bool BeginRequest(TipTextService *text_service, ITfContext *context) {
// When RequestEditSession fails, it does not maintain the reference count.
// So we need to ensure that AddRef/Release should be called at least once
// per object.
CComPtr<ITfEditSession> edit_session(new UpdateUiEditSessionImpl(
text_service, context));
HRESULT edit_session_result = S_OK;
const HRESULT result = context->RequestEditSession(
text_service->GetClientID(),
edit_session,
TF_ES_ASYNCDONTCARE | TF_ES_READ, &edit_session_result);
return SUCCEEDED(result);
}
private:
UpdateUiEditSessionImpl(TipTextService *text_service,
ITfContext *context)
: text_service_(text_service),
context_(context) {
}
TipRefCount ref_count_;
CComPtr<TipTextService> text_service_;
CComPtr<ITfContext> context_;
DISALLOW_COPY_AND_ASSIGN(UpdateUiEditSessionImpl);
};
bool RegisterWindowClass(
HINSTANCE module_handle, const wchar_t *class_name,
WNDPROC window_procedure) {
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = window_procedure;
wc.hInstance = module_handle;
wc.lpszClassName = class_name;
const ATOM atom = ::RegisterClassExW(&wc);
return atom != INVALID_ATOM;
}
class TipTextServiceImpl
: public ITfTextInputProcessorEx,
public ITfDisplayAttributeProvider,
public ITfThreadMgrEventSink,
public ITfThreadFocusSink,
public ITfTextEditSink,
public ITfTextLayoutSink,
public ITfKeyEventSink,
public ITfFnConfigure,
public ITfFunctionProvider,
public ITfCompartmentEventSink,
public TipLangBarCallback,
public TipTextService {
public:
TipTextServiceImpl()
: client_id_(TF_CLIENTID_NULL),
activate_flags_(0),
thread_mgr_cookie_(TF_INVALID_COOKIE),
thread_focus_cookie_(TF_INVALID_COOKIE),
keyboard_openclose_cookie_(TF_INVALID_COOKIE),
keyboard_disabled_cookie_(TF_INVALID_COOKIE),
keyboard_inputmode_conversion_cookie_(TF_INVALID_COOKIE),
empty_context_cookie_(TF_INVALID_COOKIE),
input_attribute_(TF_INVALID_GUIDATOM),
converted_attribute_(TF_INVALID_GUIDATOM),
thread_context_(nullptr),
task_window_handle_(nullptr),
renderer_callback_window_handle_(nullptr) {}
static bool OnDllProcessAttach(HMODULE module_handle) {
if (!RegisterWindowClass(
module_handle, kTaskWindowClassName, TaskWindowProc)) {
return false;
}
if (!RegisterWindowClass(
module_handle, kMessageReceiverClassName,
RendererCallbackWidnowProc)) {
return false;
}
return true;
}
static void OnDllProcessDetach(HMODULE module_handle) {
::UnregisterClass(kTaskWindowClassName, module_handle);
::UnregisterClass(kMessageReceiverClassName, module_handle);
}
private:
virtual ~TipTextServiceImpl() {}
// IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID interface_id, void **object) {
if (object == nullptr) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object.
if (::IsEqualIID(interface_id, IID_IUnknown)) {
*object = static_cast<IUnknown *>(
static_cast<ITfTextInputProcessorEx *>(this));
} else if (::IsEqualIID(interface_id, IID_ITfTextInputProcessor)) {
*object = static_cast<ITfTextInputProcessor *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfTextInputProcessorEx)) {
*object = static_cast<ITfTextInputProcessorEx *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfDisplayAttributeProvider)) {
*object = static_cast<ITfDisplayAttributeProvider *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfThreadMgrEventSink)) {
*object = static_cast<ITfThreadMgrEventSink *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfThreadFocusSink)) {
*object = static_cast<ITfThreadFocusSink *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfTextEditSink)) {
*object = static_cast<ITfTextEditSink *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfTextLayoutSink)) {
*object = static_cast<ITfTextLayoutSink *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfKeyEventSink)) {
*object = static_cast<ITfKeyEventSink *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfFunction)) {
*object = static_cast<ITfFnConfigure *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfFnConfigure)) {
*object = static_cast<ITfFnConfigure *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfFunctionProvider)) {
*object = static_cast<ITfFunctionProvider *>(this);
} else if (::IsEqualIID(interface_id, IID_ITfCompartmentEventSink)) {
*object = static_cast<ITfCompartmentEventSink *>(this);
} else {
*object = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ref_count_.AddRefImpl();
}
virtual ULONG STDMETHODCALLTYPE Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// ITfTextInputProcessorEx
virtual HRESULT STDMETHODCALLTYPE Activate(
ITfThreadMgr *thread_mgr, TfClientId client_id) {
return ActivateEx(thread_mgr, client_id, 0);
}
virtual HRESULT STDMETHODCALLTYPE Deactivate() {
if (TipDllModule::IsUnloaded()) {
// Crash report indicates that this method is called after the DLL is
// unloaded. In such case, we can do nothing safely.
return S_OK;
}
// Stop advising the ITfThreadFocusSink events.
UninitThreadFocusSink();
// Unregister the hot keys.
UninitPreservedKey();
// Stop advising the ITfCompartmentEventSink events.
UninitCompartmentEventSink();
// Stop advising the ITfKeyEvent events.
UninitKeyEventSink();
// Remove our button menus from the language bar.
if (!IsImmersiveUI()) {
UninitLanguageBar();
}
// Stop advising the ITfFunctionProvider events.
UninitFunctionProvider();
// Stop advising the ITfThreadMgrEventSink events.
UninitThreadManagerEventSink();
UninitPrivateContexts();
UninitRendererCallbackWindow();
UninitTaskWindow();
// Release the ITfCategoryMgr.
category_.Release();
// Release the client ID who communicates with this IME.
client_id_ = TF_CLIENTID_NULL;
// Release the ITfThreadMgr object who owns this object.
thread_mgr_.Release();
TipUiHandler::OnDeactivate(this);
thread_context_.reset(nullptr);
StorePointerForCurrentThread(nullptr);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE ActivateEx(
ITfThreadMgr *thread_mgr, TfClientId client_id, DWORD flags) {
if (TipDllModule::IsUnloaded()) {
// Crash report indicates that this method is called after the DLL is
// unloaded. In such case, we can do nothing safely. b/7915484.
return S_OK; // the returned value will be ignored according to the MSDN.
}
thread_context_.reset(new TipThreadContext());
StorePointerForCurrentThread(this);
HRESULT result = E_UNEXPECTED;
Logging::InitLogStream(kLogFileName);
EnsureKanaLockUnlocked();
// A stack trace reported in http://b/2243760 implies that a
// call of DestroyWindow API during the Deactivation may invokes another
// message dispatch, which, in turn, may cause a problematic reentrant
// activation.
// There are potential code paths to cause such a reentrance so we
// return E_UNEXPECTED if |thread_| has been initialized.
// TODO(yukawa): Fix this problem.
if (thread_mgr_ != nullptr) {
LOG(ERROR) << "Recursive Activation found.";
return E_UNEXPECTED;
}
// Copy the given thread manager.
{
CComQIPtr<ITfThreadMgr> tmp_thread_mgr = thread_mgr;
thread_mgr_ = tmp_thread_mgr;
}
if (!thread_mgr_) {
LOG(ERROR) << "Failed to retrieve ITfThreadMgr interface.";
return E_UNEXPECTED;
}
// Copy the given client ID.
// An IME can identify an application with this ID.
client_id_ = client_id;
// Copy the given activation flags.
activate_flags_ = flags;
result = InitTaskWindow();
if (FAILED(result)) {
LOG(ERROR) << "InitTaskWindow failed: " << result;
return Deactivate();
}
// Do nothing even when we fail to initialize the renderer callback
// because 1), it is not so critical, and 2) it actually fails in
// Internet Explorer 10 on Windows 8.
InitRendererCallbackWindow();
// Start advising thread events to this object.
result = InitThreadManagerEventSink();
if (FAILED(result)) {
LOG(ERROR) << "InitThreadManagerEventSink failed: " << result;
return Deactivate();
}
// Start advising function provider events to this object.
result = InitFunctionProvider();
if (FAILED(result)) {
LOG(ERROR) << "InitFunctionProvider failed: " << result;
return Deactivate();
}
category_ = GetCategoryMgr();
if (!category_) {
LOG(ERROR) << "GetCategoryMgr failed";
return Deactivate();
}
if (!IsImmersiveUI()) {
result = InitLanguageBar();
if (FAILED(result)) {
LOG(ERROR) << "InitLanguageBar failed: " << result;
return result;
}
}
// Start advising the keyboard events (ITfKeyEvent) to this object.
result = InitKeyEventSink();
if (FAILED(result)) {
LOG(ERROR) << "InitKeyEventSink failed: " << result;
return result;
}
// Start advising ITfCompartmentEventSink to this object.
result = InitCompartmentEventSink();
if (FAILED(result)) {
LOG(ERROR) << "InitCompartmentEventSink failed: " << result;
return Deactivate();
}
// Register the hot-keys used by this object to Windows.
result = InitPreservedKey();
if (FAILED(result)) {
LOG(ERROR) << "InitPreservedKey failed: " << result;
return Deactivate();
}
// Start advising ITfThreadFocusSink to this object.
result = InitThreadFocusSink();
if (FAILED(result)) {
LOG(ERROR) << "InitThreadFocusSink failed: " << result;
return Deactivate();
}
// Initialize text attributes used by this object.
result = InitDisplayAttributes();
if (FAILED(result)) {
LOG(ERROR) << "InitDisplayAttributes failed: " << result;
return Deactivate();
}
// Write a registry value for usage tracking by Omaha.
// We ignore the returned value by the function because we should not
// disturb the application by the result of this function.
if (!mozc::UpdateUtil::WriteActiveUsageInfo()) {
LOG(WARNING) << "WriteActiveUsageInfo failed";
}
// Copy the initial mode.
DWORD native_mode = 0;
if (TipStatus::GetInputModeConversion(
thread_mgr, client_id, &native_mode)) {
GetThreadContext()->GetInputModeManager()->OnInitialize(
TipStatus::IsOpen(thread_mgr), native_mode);
}
// Initialize focus hierarchy observer.
thread_context_->InitializeFocusHierarchyObserver();
// Emulate document changed event against the current document manager.
{
CComPtr<ITfDocumentMgr> document_mgr;
result = thread_mgr_->GetFocus(&document_mgr);
if (FAILED(result)) {
return Deactivate();
}
if (document_mgr != nullptr) {
CComPtr<ITfContext> context;
result = document_mgr->GetBase(&context);
if (SUCCEEDED(result)) {
EnsurePrivateContextExists(context);
}
}
TipUiHandler::OnActivate(this);
result = OnDocumentMgrChanged(document_mgr);
if (FAILED(result)) {
return Deactivate();
}
}
return result;
}
// ITfDisplayAttributeProvider
virtual HRESULT STDMETHODCALLTYPE EnumDisplayAttributeInfo(
IEnumTfDisplayAttributeInfo **attributes) {
if (attributes == nullptr) {
return E_INVALIDARG;
}
*attributes = new TipEnumDisplayAttributes;
(*attributes)->AddRef();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetDisplayAttributeInfo(
REFGUID guid, ITfDisplayAttributeInfo **attribute) {
if (attribute == nullptr) {
return E_INVALIDARG;
}
// Compare the given GUID with known ones and creates a new instance of the
// specified display attribute.
if (::IsEqualGUID(guid, TipDisplayAttributeInput::guid())) {
*attribute = new TipDisplayAttributeInput;
} else if (::IsEqualGUID(guid, TipDisplayAttributeConverted::guid())) {
*attribute = new TipDisplayAttributeConverted;
} else {
*attribute = nullptr;
return E_INVALIDARG;
}
(*attribute)->AddRef();
return S_OK;
}
// ITfThreadMgrEventSink
virtual HRESULT STDMETHODCALLTYPE OnInitDocumentMgr(
ITfDocumentMgr *document) {
// In order to defer the initialization timing of TipPrivateContext,
// we won't call OnDocumentMgrChanged against |document| here.
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnUninitDocumentMgr(
ITfDocumentMgr *document) {
// Usually |document| no longer has any context here: all the contexts are
// likely to be destroyed through ITfThreadMgrEventSink::OnPushContext.
// We enumerate remaining contexts just in case.
HRESULT hr = S_OK;
if (!document) {
return E_INVALIDARG;
}
CComPtr<IEnumTfContexts> enum_context;
hr = document->EnumContexts(&enum_context);
if (FAILED(hr)) {
return hr;
}
while (true) {
CComPtr<ITfContext> context;
ULONG fetched = 0;
hr = enum_context->Next(1, &context, &fetched);
if (FAILED(hr)) {
return hr;
}
if (hr == S_FALSE || fetched == 0) {
break;
}
RemovePrivateContextIfExists(context);
}
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnSetFocus(ITfDocumentMgr *focused,
ITfDocumentMgr *previous) {
GetThreadContext()->IncrementFocusRevision();
OnDocumentMgrChanged(focused);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnPushContext(ITfContext *context) {
EnsurePrivateContextExists(context);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnPopContext(ITfContext *context) {
RemovePrivateContextIfExists(context);
return S_OK;
}
// ITfThreadFocusSink
virtual HRESULT STDMETHODCALLTYPE OnSetThreadFocus() {
EnsureKanaLockUnlocked();
// While ITfThreadMgrEventSink::OnSetFocus notifies the logical focus inside
// the application, ITfThreadFocusSink notifies the OS-level keyboard focus
// events. In both cases, Mozc's UI visibility should be updated.
CComPtr<ITfDocumentMgr> document_manager;
if (FAILED(thread_mgr_->GetFocus(&document_manager))) {
return S_OK;
}
if (!document_manager) {
return S_OK;
}
TipUiHandler::OnFocusChange(this, document_manager);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnKillThreadFocus() {
// See the comment in OnSetThreadFocus().
TipUiHandler::OnFocusChange(this, nullptr);
return S_OK;
}
// ITfTextEditSink
virtual HRESULT STDMETHODCALLTYPE OnEndEdit(
ITfContext *context,
TfEditCookie edit_cookie,
ITfEditRecord *edit_record) {
HRESULT result = S_OK;
return TipEditSessionImpl::OnEndEdit(
this, context, edit_cookie, edit_record);
}
virtual HRESULT STDMETHODCALLTYPE OnLayoutChange(
ITfContext *context,
TfLayoutCode layout_code,
ITfContextView *context_view) {
TipEditSession::OnLayoutChangedAsync(this, context);
return S_OK;
}
// ITfKeyEventSink
virtual HRESULT STDMETHODCALLTYPE OnSetFocus(BOOL foreground) {
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnTestKeyDown(
ITfContext *context, WPARAM wparam, LPARAM lparam, BOOL *eaten) {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnTestKeyDown(this, context, wparam, lparam,
eaten);
}
// ITfKeyEventSink
virtual HRESULT STDMETHODCALLTYPE OnTestKeyUp(
ITfContext *context, WPARAM wparam, LPARAM lparam, BOOL *eaten) {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnTestKeyUp(
this, context, wparam, lparam, eaten);
}
virtual HRESULT STDMETHODCALLTYPE OnKeyDown(
ITfContext *context, WPARAM wparam, LPARAM lparam, BOOL *eaten) {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnKeyDown(this, context, wparam, lparam, eaten);
}
virtual HRESULT STDMETHODCALLTYPE OnKeyUp(
ITfContext *context, WPARAM wparam, LPARAM lparam, BOOL *eaten) {
HRESULT result = S_OK;
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnKeyUp(this, context, wparam, lparam, eaten);
}
virtual HRESULT STDMETHODCALLTYPE OnPreservedKey(
ITfContext *context, REFGUID guid, BOOL *eaten) {
HRESULT result = S_OK;
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
PreservedKeyMap::const_iterator it = preserved_key_map_.find(guid);
if (it == preserved_key_map_.end()) {
return result;
}
const UINT vk = it->second;
const UINT alt_down = (::GetKeyState(VK_MENU) & 0x8000) != 0 ? 1 : 0;
const UINT scan_code = ::MapVirtualKey(VK_F10, 0);
const UINT lparam = (alt_down << 29) | (scan_code << 16) | 1;
result = TipKeyeventHandler::OnKeyDown(this, context, vk, lparam, eaten);
if (*eaten == FALSE && vk == VK_F10) {
// Special treatment for F10:
// Setting FALSE to |*eaten| is not enough when F10 key is handled by the
// application. So here manually compose WM_SYSKEYDOWN message to emulate
// F10 key.
// http://msdn.microsoft.com/en-us/library/ms646286.aspx
::PostMessage(::GetFocus(), WM_SYSKEYDOWN, VK_F10, lparam);
}
return result;
}
// ITfFnConfigure
virtual HRESULT STDMETHODCALLTYPE GetDisplayName(BSTR *name) {
if (name == nullptr) {
return E_INVALIDARG;
}
*name = ::SysAllocString(kConfigurationDisplayname);
return (*name != nullptr) ? S_OK : E_FAIL;
}
virtual HRESULT STDMETHODCALLTYPE Show(
HWND parent, LANGID langid, REFGUID profile) {
return SpawnTool("config_dialog");
}
// ITfFunctionProvider
virtual HRESULT STDMETHODCALLTYPE GetType(GUID *guid) {
if (guid == nullptr) {
return E_INVALIDARG;
}
*guid = kTipFunctionProvider;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetDescription(BSTR *description) {
if (description == nullptr) {
return E_INVALIDARG;
}
*description = CComBSTR().Detach();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetFunction(
const GUID &guid, const IID &iid, IUnknown **unknown) {
if (unknown == nullptr) {
return E_INVALIDARG;
}
*unknown = nullptr;
if (::IsEqualGUID(IID_ITfFnReconversion, iid)) {
*unknown = TipReconvertFunction::New(this);
} else if (::IsEqualGUID(TipPreferredTouchKeyboard::GetIID(), iid)) {
*unknown = TipPreferredTouchKeyboard::New();
} else {
return E_NOINTERFACE;
}
if (*unknown == nullptr) {
return E_FAIL;
}
(*unknown)->AddRef();
return S_OK;
}
// ITfCompartmentEventSink
virtual HRESULT STDMETHODCALLTYPE OnChange(const GUID &guid) {
if (!thread_mgr_) {
return E_FAIL;
}
if (::IsEqualGUID(guid, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION)) {
TipEditSession::OnModeChangedAsync(this);
} else if (::IsEqualGUID(guid, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE)) {
TipEditSession::OnOpenCloseChangedAsync(this);
}
return S_OK;
}
// TipLangBarCallback
virtual HRESULT STDMETHODCALLTYPE OnMenuSelect(ItemId menu_id) {
switch (menu_id) {
case TipLangBarCallback::kDirect:
case TipLangBarCallback::kHiragana:
case TipLangBarCallback::kFullKatakana:
case TipLangBarCallback::kHalfAlphanumeric:
case TipLangBarCallback::kFullAlphanumeric:
case TipLangBarCallback::kHalfKatakana: {
const commands::CompositionMode mozc_mode = GetMozcMode(menu_id);
return TipEditSession::SwitchInputModeAsync(this, mozc_mode);
}
case TipLangBarCallback::kProperty:
case TipLangBarCallback::kDictionary:
case TipLangBarCallback::kWordRegister:
case TipLangBarCallback::kHandWriting:
case TipLangBarCallback::kCharacterPalette:
case TipLangBarCallback::kAbout:
return SpawnTool(GetMozcToolCommand(menu_id));
case TipLangBarCallback::kHelp:
// Open the about dialog.
return Process::OpenBrowser(kHelpUrl) ? S_OK : E_FAIL;
default:
return S_OK;
}
}
virtual HRESULT STDMETHODCALLTYPE OnItemClick(const wchar_t *description) {
// Change input mode to be consistent with MSIME 2012 on Windows 8.
const bool open =
thread_context_->GetInputModeManager()->GetEffectiveOpenClose();
if (open) {
return TipStatus::SetIMEOpen(thread_mgr_, client_id_, false)
? S_OK : E_FAIL;
}
// Like MSIME 2012, switch to Hiragana mode when the LangBar button is
// clicked.
return TipEditSession::SwitchInputModeAsync(this, commands::HIRAGANA);
}
// TipTextService
virtual TfClientId GetClientID() const {
return client_id_;
}
virtual ITfThreadMgr *GetThreadManager() const {
return thread_mgr_;
}
virtual TfGuidAtom input_attribute() const {
return input_attribute_;
}
virtual TfGuidAtom converted_attribute() const {
return converted_attribute_;
}
virtual HWND renderer_callback_window_handle() const {
return renderer_callback_window_handle_;
}
virtual ITfCompositionSink *CreateCompositionSink(
ITfContext *context) {
return new CompositionSinkImpl(this, context);
}
virtual bool IsImmersiveUI() const {
return (activate_flags_ & TF_TMF_IMMERSIVEMODE) == TF_TMF_IMMERSIVEMODE;
}
virtual TipPrivateContext *GetPrivateContext(ITfContext *context) {
if (context == nullptr) {
return nullptr;
}
const PrivateContextMap::iterator it = private_context_map_.find(context);
if (it == private_context_map_.end()) {
return nullptr;
}
return it->second;
}
virtual TipThreadContext *GetThreadContext() {
return thread_context_.get();
}
virtual void PostUIUpdateMessage() {
if (!::IsWindow(task_window_handle_)) {
return;
}
PostMessageW(task_window_handle_, kUpdateUIMessage, 0, 0);
}
virtual void UpdateLangbar(bool enabled, uint32 mozc_mode) {
langbar_.UpdateMenu(enabled, mozc_mode);
}
// Following functions are private utilities.
static void StorePointerForCurrentThread(TipTextServiceImpl *impl) {
if (g_module_unloaded) {
return;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return;
}
::TlsSetValue(g_tls_index, impl);
}
static TipTextServiceImpl *Self() {
if (g_module_unloaded) {
return nullptr;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return nullptr;
}
return static_cast<TipTextServiceImpl *>(::TlsGetValue(g_tls_index));
}
HRESULT OnDocumentMgrChanged(ITfDocumentMgr *document_mgr) {
// nullptr document is not an error.
if (document_mgr != nullptr) {
CComPtr<ITfContext> context;
const HRESULT result = document_mgr->GetTop(&context);
if (FAILED(result)) {
return result;
}
EnsurePrivateContextExists(context);
}
TipEditSession::OnSetFocusAsync(this, document_mgr);
return S_OK;
}
void EnsurePrivateContextExists(ITfContext *context) {
if (context == nullptr) {
// Do not care about nullptr context.
return;
}
const PrivateContextMap::const_iterator it =
private_context_map_.find(context);
if (it == private_context_map_.end()) {
// If this |context| has not been registered, create our own private data
// then associate it with |context|.
DWORD text_edit_sink_cookie = TF_INVALID_COOKIE;
DWORD text_layout_sink_cookie = TF_INVALID_COOKIE;
{
CComPtr<ITfSource> source;
CComPtr<ITfContext> context_ptr(context);
if (SUCCEEDED(context_ptr.QueryInterface(&source))) {
if (FAILED(source->AdviseSink(IID_ITfTextEditSink,
static_cast<ITfTextEditSink *>(this),
&text_edit_sink_cookie))) {
text_edit_sink_cookie = TF_INVALID_COOKIE;
}
if (FAILED(source->AdviseSink(IID_ITfTextLayoutSink,
static_cast<ITfTextLayoutSink *>(this),
&text_layout_sink_cookie))) {
text_layout_sink_cookie = TF_INVALID_COOKIE;
}
}
}
private_context_map_[context] = new TipPrivateContext(
text_edit_sink_cookie, text_layout_sink_cookie);
}
return;
}
void RemovePrivateContextIfExists(ITfContext *context) {
// Remove private context associated with |context|.
const PrivateContextMap::iterator it = private_context_map_.find(context);
if (it == private_context_map_.end()) {
return;
}
// Transfer the ownership.
unique_ptr<TipPrivateContext> private_context(it->second);
private_context_map_.erase(it);
if (private_context.get() == nullptr) {
return;
}
CComQIPtr<ITfSource> source = context;
if (source) {
if (private_context->text_edit_sink_cookie() != TF_INVALID_COOKIE) {
source->UnadviseSink(private_context->text_edit_sink_cookie());
}
if (private_context->text_layout_sink_cookie() != TF_INVALID_COOKIE) {
source->UnadviseSink(private_context->text_layout_sink_cookie());
}
}
}
void UninitPrivateContexts() {
while (private_context_map_.size() > 0) {
RemovePrivateContextIfExists(private_context_map_.begin()->first);
}
}
TipPrivateContext *GetFocusedPrivateContext() {
CComPtr<ITfDocumentMgr> focused_document;
if (FAILED(thread_mgr_->GetFocus(&focused_document))) {
return nullptr;
}
if (!focused_document) {
return nullptr;
}
CComPtr<ITfContext> current_context;
if (FAILED(focused_document->GetTop(&current_context))) {
return nullptr;
}
return GetPrivateContext(current_context);
}
HRESULT InitThreadManagerEventSink() {
HRESULT result = S_OK;
// Retrieve the event source for this thread and start advising the
// ITfThreadMgrEventSink events to this object, i.e. register this object
// as a listener for the TSF thread events.
CComPtr<ITfSource> source;
result = thread_mgr_.QueryInterface(&source);
if (FAILED(result)) {
return result;
}
result = source->AdviseSink(IID_ITfThreadMgrEventSink,
static_cast<ITfThreadMgrEventSink *>(this),
&thread_mgr_cookie_);
if (FAILED(result)) {
thread_mgr_cookie_ = TF_INVALID_COOKIE;
}
return result;
}
HRESULT UninitThreadManagerEventSink() {
HRESULT result = S_OK;
// If we have started advising the TSF thread events, retrieve the event
// source for the events and stop advising them.
if (thread_mgr_cookie_ == TF_INVALID_COOKIE) {
return S_OK;
}
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfSource> source;
result = thread_mgr_.QueryInterface(&source);
if (SUCCEEDED(result)) {
result = source->UnadviseSink(thread_mgr_cookie_);
}
thread_mgr_cookie_ = TF_INVALID_COOKIE;
return result;
}
HRESULT InitLanguageBar() {
return langbar_.InitLangBar(this);
}
HRESULT UninitLanguageBar() {
return langbar_.UninitLangBar();
}
HRESULT InitKeyEventSink() {
HRESULT result = S_OK;
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfKeystrokeMgr> keystroke;
result = thread_mgr_.QueryInterface(&keystroke);
if (FAILED(result)) {
return result;
}
return keystroke->AdviseKeyEventSink(
client_id_, static_cast<ITfKeyEventSink *>(this), TRUE);
}
HRESULT UninitKeyEventSink() {
HRESULT result = S_OK;
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfKeystrokeMgr> keystroke;
result = thread_mgr_.QueryInterface(&keystroke);
if (FAILED(result)) {
return result;
}
return keystroke->UnadviseKeyEventSink(client_id_);
}
HRESULT InitCompartmentEventSink() {
HRESULT result = S_OK;
CComPtr<ITfCompartmentMgr> manager;
result = thread_mgr_.QueryInterface(&manager);
if (FAILED(result)) {
return result;
}
result = AdviseCompartmentEventSink(
manager,
GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
&keyboard_openclose_cookie_);
if (FAILED(result)) {
return result;
}
result = AdviseCompartmentEventSink(
manager,
GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION,
&keyboard_inputmode_conversion_cookie_);
if (FAILED(result)) {
return result;
}
return result;
}
HRESULT UninitCompartmentEventSink() {
HRESULT result = S_OK;
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfCompartmentMgr> manager;
result = thread_mgr_.QueryInterface(&manager);
if (FAILED(result)) {
return result;
}
UnadviseCompartmentEventSink(
manager,
GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
&keyboard_openclose_cookie_);
UnadviseCompartmentEventSink(
manager,
GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION,
&keyboard_inputmode_conversion_cookie_);
return result;
}
HRESULT AdviseCompartmentEventSink(
ITfCompartmentMgr *manager, REFGUID guid, DWORD *cookie) {
HRESULT result = S_OK;
if (manager == nullptr || cookie == nullptr) {
return E_INVALIDARG;
}
CComPtr<ITfCompartment> compartment;
result = manager->GetCompartment(guid, &compartment);
if (FAILED(result)) {
return result;
}
CComPtr<ITfSource> source;
result = compartment.QueryInterface(&source);
if (FAILED(result)) {
return result;
}
result = source->AdviseSink(
IID_ITfCompartmentEventSink,
static_cast<ITfCompartmentEventSink*>(this),
cookie);
return result;
}
HRESULT UnadviseCompartmentEventSink(
ITfCompartmentMgr *manager, REFGUID guid, DWORD *cookie) {
HRESULT result = S_OK;
if (manager == nullptr || cookie == nullptr) {
return E_INVALIDARG;
}
if (*cookie == TF_INVALID_COOKIE) {
return E_UNEXPECTED;
}
CComPtr<ITfCompartment> compartment;
result = manager->GetCompartment(guid, &compartment);
if (FAILED(result)) {
return result;
}
CComPtr<ITfSource> source;
result = compartment.QueryInterface(&source);
if (FAILED(result)) {
return result;
}
result = source->UnadviseSink(*cookie);
*cookie = TF_INVALID_COOKIE;
return result;
}
HRESULT InitPreservedKey() {
HRESULT result = S_OK;
// Retrieve the keyboard-stroke manager from the thread manager, and
// add the hot keys defined in the kPreservedKeyItems[] array.
// A keyboard-stroke manager belongs to a thread manager because Windows
// allows each thread to have its own keyboard (and Language) settings.
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfKeystrokeMgr> keystroke;
result = thread_mgr_.QueryInterface(&keystroke);
if (FAILED(result)) {
return result;
}
for (size_t i = 0; i < arraysize(kPreservedKeyItems); ++i) {
const PreserveKeyItem &item = kPreservedKeyItems[i];
// Register a hot key to the keystroke manager.
result = keystroke->PreserveKey(
client_id_, item.guid, &item.key, item.description, item.length);
if (SUCCEEDED(result)) {
preserved_key_map_[item.guid] = item.mapped_vkey;
}
}
return result;
}
HRESULT UninitPreservedKey() {
HRESULT result = S_OK;
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComPtr<ITfKeystrokeMgr> keystroke;
result = thread_mgr_.QueryInterface(&keystroke);
if (FAILED(result)) {
return result;
}
for (size_t i = 0; i < arraysize(kPreservedKeyItems); ++i) {
result = keystroke->UnpreserveKey(kPreservedKeyItems[i].guid,
&kPreservedKeyItems[i].key);
}
preserved_key_map_.clear();
return result;
}
HRESULT InitThreadFocusSink() {
if (thread_focus_cookie_ != TF_INVALID_COOKIE) {
return S_OK;
}
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
HRESULT result = E_FAIL;
CComPtr<ITfSource> source = nullptr;
result = thread_mgr_.QueryInterface(&source);
if (FAILED(result)) {
return result;
}
result = source->AdviseSink(IID_ITfThreadFocusSink,
static_cast<ITfThreadFocusSink *>(this),
&thread_focus_cookie_);
if (FAILED(result)) {
thread_focus_cookie_ = TF_INVALID_COOKIE;
}
return result;
}
HRESULT UninitThreadFocusSink() {
if (thread_focus_cookie_ == TF_INVALID_COOKIE) {
return S_OK;
}
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
HRESULT result = E_FAIL;
CComPtr<ITfSource> source;
result = thread_mgr_.QueryInterface(&source);
if (FAILED(result)) {
return result;
}
result = source->UnadviseSink(thread_focus_cookie_);
thread_focus_cookie_ = TF_INVALID_COOKIE;
return result;
}
HRESULT InitFunctionProvider() {
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComQIPtr<ITfSourceSingle> source = thread_mgr_;
if (!source) {
return E_FAIL;
}
return source->AdviseSingleSink(
client_id_, IID_ITfFunctionProvider,
static_cast<ITfFunctionProvider *>(this));
}
HRESULT UninitFunctionProvider() {
if (thread_mgr_ == nullptr) {
return E_FAIL;
}
CComQIPtr<ITfSourceSingle> source = thread_mgr_;
if (!source) {
return E_FAIL;
}
return source->UnadviseSingleSink(client_id_, IID_ITfFunctionProvider);
}
HRESULT InitDisplayAttributes() {
HRESULT result = S_OK;
if (!category_) {
return E_UNEXPECTED;
}
// register the display attribute for input strings and the one for
// converted strings.
result = category_->RegisterGUID(
TipDisplayAttributeInput::guid(), &input_attribute_);
if (FAILED(result)) {
return result;
}
result = category_->RegisterGUID(
TipDisplayAttributeConverted::guid(), &converted_attribute_);
return result;
}
HRESULT InitTaskWindow() {
if (::IsWindow(task_window_handle_)) {
return S_FALSE;
}
task_window_handle_ = ::CreateWindowExW(
0,
kTaskWindowClassName,
L"",
0,
0, 0, 0, 0,
HWND_MESSAGE,
nullptr,
g_module,
nullptr);
if (!::IsWindow(task_window_handle_)) {
return E_FAIL;
}
return S_OK;
}
HRESULT UninitTaskWindow() {
if (!::IsWindow(task_window_handle_)) {
return S_FALSE;
}
::DestroyWindow(task_window_handle_);
task_window_handle_ = nullptr;
return S_OK;
}
static LRESULT WINAPI TaskWindowProc(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam) {
TipTextServiceImpl *self = Self();
if (self == nullptr) {
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
if (window_handle == self->task_window_handle_) {
if (message == kUpdateUIMessage) {
self->OnUpdateUI();
return 0;
}
}
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
void OnUpdateUI() {
CComPtr<ITfDocumentMgr> document_manager;
if (FAILED(thread_mgr_->GetFocus(&document_manager))) {
return;
}
if (!document_manager) {
return;
}
CComPtr<ITfContext> context;
if (FAILED(document_manager->GetBase(&context))) {
return;
}
if (!context) {
return;
}
UpdateUiEditSessionImpl::BeginRequest(this, context);
}
HRESULT InitRendererCallbackWindow() {
if (IsImmersiveUI()) {
// The renderer callback is not required for Immersive mode.
return S_FALSE;
}
if (::IsWindow(renderer_callback_window_handle_)) {
return S_FALSE;
}
renderer_callback_window_handle_ = ::CreateWindowExW(
0,
kMessageReceiverClassName,
L"",
0,
0, 0, 0, 0,
HWND_MESSAGE,
nullptr,
g_module,
nullptr);
if (!::IsWindow(renderer_callback_window_handle_)) {
return E_FAIL;
}
// Do not care about thread safety.
static UINT renderer_callback_message =
::RegisterWindowMessage(mozc::kMessageReceiverMessageName);
if (!WindowUtil::ChangeMessageFilter(
renderer_callback_window_handle_, renderer_callback_message)) {
::DestroyWindow(renderer_callback_window_handle_);
renderer_callback_window_handle_ = nullptr;
return E_FAIL;
}
return S_OK;
}
HRESULT UninitRendererCallbackWindow() {
if (IsImmersiveUI()) {
// The renderer callback is not required for Immersive mode.
return S_FALSE;
}
if (!::IsWindow(renderer_callback_window_handle_)) {
return S_FALSE;
}
::DestroyWindow(renderer_callback_window_handle_);
renderer_callback_window_handle_ = nullptr;
return S_OK;
}
static LRESULT WINAPI RendererCallbackWidnowProc(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam) {
TipTextServiceImpl *self = Self();
if (self == nullptr) {
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
// Do not care about thread safety.
static UINT renderer_callback_message =
::RegisterWindowMessage(mozc::kMessageReceiverMessageName);
if (window_handle == self->renderer_callback_window_handle_) {
if (message == renderer_callback_message) {
self->OnRendererCallback(wparam, lparam);
return 0;
}
}
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
void OnRendererCallback(WPARAM wparam, LPARAM lparam) {
CComPtr<ITfDocumentMgr> document_manager;
if (FAILED(thread_mgr_->GetFocus(&document_manager))) {
return;
}
if (!document_manager) {
return;
}
CComPtr<ITfContext> context;
if (FAILED(document_manager->GetBase(&context))) {
return;
}
if (!context) {
return;
}
TipEditSession::OnRendererCallbackAsync(this, context, wparam, lparam);
}
TipRefCount ref_count_;
// Represents the status of the thread manager which owns this IME object.
CComPtr<ITfThreadMgr> thread_mgr_;
// Represents the ID of the client application using this IME object.
TfClientId client_id_;
// Stores the flag passed to ActivateEx.
DWORD activate_flags_;
// Represents the cookie ID for the thread manager.
DWORD thread_mgr_cookie_;
// The cookie issued for installing ITfThreadFocusSink.
DWORD thread_focus_cookie_;
// The cookie issued for installing ITfCompartmentEventSink.
DWORD keyboard_openclose_cookie_;
DWORD keyboard_disabled_cookie_;
DWORD keyboard_inputmode_conversion_cookie_;
DWORD empty_context_cookie_;
// The category manager object to register or query a GUID.
CComPtr<ITfCategoryMgr> category_;
// Represents the display attributes.
TfGuidAtom input_attribute_;
TfGuidAtom converted_attribute_;
// Used for LangBar integration.
TipLangBar langbar_;
using PreservedKeyMap = std::unordered_map<GUID, UINT, GuidHash>;
using PrivateContextMap = std::unordered_map<CComPtr<ITfContext>,
TipPrivateContext *,
CComPtrHash<ITfContext>>;
PrivateContextMap private_context_map_;
PreservedKeyMap preserved_key_map_;
unique_ptr<TipThreadContext> thread_context_;
HWND task_window_handle_;
HWND renderer_callback_window_handle_;
DISALLOW_COPY_AND_ASSIGN(TipTextServiceImpl);
};
} // namespace
TipTextService *TipTextServiceFactory::Create() {
return new TipTextServiceImpl();
}
bool TipTextServiceFactory::OnDllProcessAttach(HINSTANCE module_handle,
bool static_loading) {
g_module = module_handle;
g_tls_index = ::TlsAlloc();
if (!TipTextServiceImpl::OnDllProcessAttach(module_handle)) {
return false;
}
return true;
}
void TipTextServiceFactory::OnDllProcessDetach(HINSTANCE module_handle,
bool process_shutdown) {
TipTextServiceImpl::OnDllProcessDetach(module_handle);
if (g_tls_index != TLS_OUT_OF_INDEXES) {
::TlsFree(g_tls_index);
g_tls_index = TLS_OUT_OF_INDEXES;
}
g_module_unloaded = true;
g_module = nullptr;
}
} // namespace tsf
} // namespace win32
} // namespace mozc