| // 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(¤t_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 |