| // 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. |
| |
| // An implementation of the IMM32 interface. |
| // |
| |
| #include "win32/ime/ime_impl_imm.h" |
| |
| #include <ime.h> |
| |
| #include <strsafe.h> |
| |
| #include "google/protobuf/stubs/common.h" |
| #include "base/const.h" |
| #include "base/crash_report_handler.h" |
| #include "base/logging.h" |
| #include "base/process.h" |
| #include "base/singleton.h" |
| #include "base/system_util.h" |
| #include "base/update_util.h" |
| #include "base/util.h" |
| #include "config/stats_config_util.h" |
| #include "win32/base/browser_info.h" |
| #include "win32/base/conversion_mode_util.h" |
| #include "win32/base/deleter.h" |
| #include "win32/base/focus_hierarchy_observer.h" |
| #include "win32/base/indicator_visibility_tracker.h" |
| #include "win32/base/immdev.h" |
| #include "win32/base/input_state.h" |
| #include "win32/base/string_util.h" |
| #include "win32/base/surrogate_pair_observer.h" |
| #include "win32/ime/ime_candidate_info.h" |
| #include "win32/ime/ime_composition_string.h" |
| #include "win32/ime/ime_core.h" |
| #include "win32/ime/ime_input_context.h" |
| #include "win32/ime/ime_message_queue.h" |
| #include "win32/ime/ime_private_context.h" |
| #include "win32/ime/ime_scoped_context.h" |
| #include "win32/ime/ime_ui_context.h" |
| #include "win32/ime/ime_ui_visibility_tracker.h" |
| #include "win32/ime/ime_ui_window.h" |
| |
| namespace { |
| |
| HINSTANCE g_instance = nullptr; |
| DWORD g_ime_system_info = MAXDWORD; |
| // True if the boot mode is safe mode. |
| bool g_in_safe_mode = true; |
| // True when SystemUtil::EnsureVitalImmutableDataIsAvailable() returns false. |
| bool g_fundamental_data_is_not_available = false; |
| |
| // TLS index for context update count. See b/3282221 for details. |
| const DWORD kInvalidTlsIndex = 0xffffffff; |
| DWORD g_context_revision_tls_index = kInvalidTlsIndex; |
| |
| // Breakpad never be enabled except for official builds. |
| #if defined(GOOGLE_JAPANESE_INPUT_BUILD) |
| #define USE_BREAKPAD |
| #endif // GOOGLE_JAPANESE_INPUT_BUILD |
| |
| #if defined(USE_BREAKPAD) |
| // A critical section object for breakpad because its initialization routine is |
| // not thread-safe. See b/3100365 why we need this. |
| CRITICAL_SECTION g_critical_section_for_breakpad; |
| // 4,000 is a typical value of per-heap critical sections. |
| // See http://msdn.microsoft.com/en-us/library/ms683476.aspx |
| const int kSpinCountForCriticalSection = 4000; |
| #endif // USE_BREAKPAD |
| |
| // True if the DLL received DLL_PROCESS_DETACH notification as a result of |
| // process shutting down. If this bit is true, you must not call any |
| // function exported from other DLLs nor function implemented by CRT because |
| // they might also have been uninitialized. |
| bool g_process_is_shutting_down = false; |
| |
| // Some troublesome DLLs such as msctf.dll on XP may violate the rule that |
| // a DLL should not call functions exported from other DLLs except for |
| // kernel32.dll in DllMain. As a result, function in other DLLs including |
| // this DLL might be called even after it received DLL_PROCESS_DETACH |
| // notification. You are likely to notice this issue especially on |
| // CUAS-enabled XP, as filed in b/3088049. |
| // This macro can be used to mitigate this scenario. |
| #define DANGLING_CALLBACK_GUARD(return_code) \ |
| do { \ |
| if (g_process_is_shutting_down) { \ |
| return (return_code); \ |
| } \ |
| } while (false) |
| |
| static_assert(arraysize(mozc::kIMEUIWndClassName) |
| <= mozc::kIMEUIwndClassNameLimitInTchars, |
| "Window Class Name has length limit."); |
| |
| // Maximum number of characters for |REGISTERWORD::lpWord| and |
| // |REGISTERWORD::lpReading|. |
| const size_t kMaxCharsForRegisterWord = 64; |
| |
| // If given string is too long, returns an empty string instead. |
| wstring GetStringIfWithinLimit(const wchar_t *src, size_t size_limit) { |
| if (src == nullptr) { |
| return wstring(); |
| } |
| for (size_t i = 0; i < size_limit; ++i) { |
| if (src[i] == L'\0') { |
| return wstring(src, i); |
| } |
| } |
| return wstring(); |
| } |
| |
| void SetEnveronmentVariablesForWordRegisterDialog( |
| const wstring &word_value, const wstring &word_reading) { |
| wstring word_value_env_name; |
| wstring word_reading_env_name; |
| mozc::Util::UTF8ToWide( |
| mozc::kWordRegisterEnvironmentName, &word_value_env_name); |
| mozc::Util::UTF8ToWide( |
| mozc::kWordRegisterEnvironmentReadingName, &word_reading_env_name); |
| |
| if (word_value.empty()) { |
| // Remove relevant variables. |
| ::SetEnvironmentVariable(word_value_env_name.c_str(), nullptr); |
| ::SetEnvironmentVariable(word_reading_env_name.c_str(), nullptr); |
| } |
| |
| ::SetEnvironmentVariable(word_value_env_name.c_str(), |
| word_value.c_str()); |
| if (word_reading.empty()) { |
| // Remove relevant variable. |
| ::SetEnvironmentVariable(word_reading_env_name.c_str(), |
| nullptr); |
| } else { |
| ::SetEnvironmentVariable(word_reading_env_name.c_str(), |
| word_reading.c_str()); |
| } |
| } |
| |
| static HIMCC InitializeHIMCC(HIMCC himcc, DWORD size) { |
| if (himcc == nullptr) { |
| return ::ImmCreateIMCC(size); |
| } else { |
| return ::ImmReSizeIMCC(himcc, size); |
| } |
| } |
| |
| int32 GetContextRevision() { |
| if (g_context_revision_tls_index == kInvalidTlsIndex) { |
| return 0; |
| } |
| const int32 revision = |
| reinterpret_cast<int32>(::TlsGetValue(g_context_revision_tls_index)); |
| return revision; |
| } |
| |
| void IncrementContextRevision() { |
| if (g_context_revision_tls_index == kInvalidTlsIndex) { |
| return; |
| } |
| const int32 next_age = GetContextRevision() + 1; |
| TlsSetValue(g_context_revision_tls_index, reinterpret_cast<void *>(next_age)); |
| } |
| |
| void FillContext(HIMC himc, mozc::commands::Context *context) { |
| if (context == nullptr) { |
| return; |
| } |
| context->set_revision(GetContextRevision()); |
| |
| if (himc == nullptr) { |
| return; |
| } |
| const INPUTCONTEXT *input_context = ::ImmLockIMC(himc); |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(input_context->hPrivate); |
| const HWND attached_window = input_context->hWnd; |
| ::ImmUnlockIMC(himc); |
| |
| const auto &focus_hierarchy_observer = |
| *private_context->focus_hierarchy_observer; |
| if (focus_hierarchy_observer.IsAbailable()) { |
| if (mozc::win32::BrowserInfo::IsOnChromeOmnibox(focus_hierarchy_observer)) { |
| context->add_experimental_features("chrome_omnibox"); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| HINSTANCE ImeGetResource() { |
| return g_instance; |
| } |
| |
| namespace mozc { |
| namespace win32 { |
| bool IsInLockdownMode() { |
| if (g_in_safe_mode) { |
| return true; |
| } |
| if ((g_ime_system_info & IME_SYSINFO_WINLOGON) == IME_SYSINFO_WINLOGON) { |
| return true; |
| } |
| if (g_fundamental_data_is_not_available) { |
| return true; |
| } |
| return false; |
| } |
| |
| BOOL OnDllProcessAttach(HINSTANCE instance, bool static_loading) { |
| g_instance = instance; |
| #if defined(USE_BREAKPAD) |
| if (!::InitializeCriticalSectionAndSpinCount(&g_critical_section_for_breakpad, |
| kSpinCountForCriticalSection)) { |
| return FALSE; |
| } |
| mozc::CrashReportHandler::SetCriticalSection( |
| &g_critical_section_for_breakpad); |
| #endif // USE_BREAKPAD |
| |
| BrowserInfo::OnDllProcessAttach(instance, static_loading); |
| FocusHierarchyObserver::OnDllProcessAttach(instance, static_loading); |
| |
| if (!UIWindowManager::OnDllProcessAttach(instance, static_loading)) { |
| return FALSE; |
| } |
| |
| if (g_context_revision_tls_index == kInvalidTlsIndex) { |
| g_context_revision_tls_index = ::TlsAlloc(); |
| } |
| return TRUE; |
| } |
| |
| BOOL OnDllProcessDetach(HINSTANCE instance, bool process_shutdown) { |
| if (g_context_revision_tls_index != kInvalidTlsIndex) { |
| ::TlsFree(g_context_revision_tls_index); |
| g_context_revision_tls_index = kInvalidTlsIndex; |
| } |
| |
| UIWindowManager::OnDllProcessDetach(instance, process_shutdown); |
| FocusHierarchyObserver::OnDllProcessDetach(instance, process_shutdown); |
| BrowserInfo::OnDllProcessDetach(instance, process_shutdown); |
| |
| g_instance = nullptr; |
| |
| if (!g_in_safe_mode) { |
| // It is our responsibility to make sure that our code never touch protobuf |
| // library after google::protobuf::ShutdownProtobufLibrary is called. |
| // Unfortunately, DllMain is the only place that satisfies this condition. |
| // So we carefully call it here, even though DllMain is expected to be |
| // dangerous for potential deadlocks. See b/2126375 for details. |
| google::protobuf::ShutdownProtobufLibrary(); |
| } |
| |
| if (process_shutdown) { |
| g_process_is_shutting_down |= true; |
| } |
| |
| #if defined(USE_BREAKPAD) |
| mozc::CrashReportHandler::SetCriticalSection(nullptr); |
| ::DeleteCriticalSection(&g_critical_section_for_breakpad); |
| #endif // USE_BREAKPAD |
| |
| return TRUE; |
| } |
| } // namespace win32 |
| } // namespace mozc |
| |
| BOOL WINAPI ImeInquire(LPIMEINFO ime_info, |
| LPTSTR class_name, |
| DWORD system_info_flags) { |
| // Cache the boot mode here so that we need not call user32.dll functions |
| // from DllMain. If it is safe mode, we omit some initializations/ |
| // uninitializations to reduce potential crashes around them. (b/2728123) |
| // 0: Normal boot |
| // 1: Fail-safe boot |
| // 2: Fail-safe with network boot |
| g_in_safe_mode = (::GetSystemMetrics(SM_CLEANBOOT) > 0); |
| if (g_in_safe_mode) { |
| // Fail immediately in safe mode |
| return FALSE; |
| } |
| |
| ::ZeroMemory(ime_info, sizeof(IMEINFO)); |
| |
| // Although |IME_PROP_NO_KEYS_ON_CLOSE| might be beneficial from performance |
| // perspective, we actually have to check all key events, even when the IME |
| // is turned off, to allow users to use an arbitrary key combination to turn |
| // on IME. |
| ime_info->fdwProperty = 0 |
| | IME_PROP_END_UNLOAD |
| | IME_PROP_KBD_CHAR_FIRST |
| | IME_PROP_ACCEPT_WIDE_VKEY |
| | IME_PROP_AT_CARET |
| | IME_PROP_NEED_ALTKEY |
| | IME_PROP_CANDLIST_START_FROM_1 |
| | IME_PROP_UNICODE; |
| |
| #if !defined(UNICODE) |
| // Actually, we have never tested on non-unicode build. |
| #error "Non-Unicode build is not supported."; |
| #endif // !UNICODE |
| |
| ime_info->fdwConversionCaps = IME_CMODE_LANGUAGE |
| | IME_CMODE_KATAKANA |
| | IME_CMODE_FULLSHAPE |
| | IME_CMODE_ROMAN; |
| |
| // Currently, only IME_SMODE_PHRASEPREDICT is supported. |
| // See b/2913510, b/2954777, and b/2955175 for details. |
| ime_info->fdwSentenceCaps = IME_SMODE_PHRASEPREDICT; |
| |
| ime_info->fdwUICaps = UI_CAP_2700; |
| |
| ime_info->fdwSCSCaps = SCS_CAP_MAKEREAD |
| | SCS_CAP_SETRECONVERTSTRING; |
| |
| ime_info->fdwSelectCaps = SELECT_CAP_CONVERSION |
| | SELECT_CAP_SENTENCE; |
| |
| const PTSTR strcpy_result = |
| lstrcpyn(class_name, mozc::kIMEUIWndClassName, |
| mozc::kIMEUIwndClassNameLimitInTchars); |
| |
| if (!strcpy_result) { |
| return FALSE; |
| } |
| |
| g_ime_system_info = system_info_flags; |
| if (!mozc::SystemUtil::EnsureVitalImmutableDataIsAvailable()) { |
| // This process might be sandboxed. |
| g_fundamental_data_is_not_available = true; |
| } |
| |
| if (!mozc::win32::IsInLockdownMode()) { |
| #if defined(USE_BREAKPAD) |
| if (mozc::config::StatsConfigUtil::IsEnabled()) { |
| mozc::CrashReportHandler::Initialize(true); |
| } |
| #endif // USE_BREAKPAD |
| } |
| |
| return TRUE; |
| } |
| |
| DWORD WINAPI ImeConversionList(HIMC himc, |
| LPCTSTR source, |
| LPCANDIDATELIST candidate_list, |
| DWORD buffer_length, |
| UINT flags) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| return 0; |
| } |
| |
| BOOL WINAPI ImeDestroy(UINT force) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| // Free all singleton instances |
| mozc::SingletonFinalizer::Finalize(); |
| |
| // We deliberately call google::protobuf::ShutdownProtobufLibrary from |
| // OnDllProcessDetach rather than here. See b/2126375 for details. |
| |
| #if defined(USE_BREAKPAD) |
| if (mozc::CrashReportHandler::IsInitialized()) { |
| // Uninitialize the breakpad. |
| mozc::CrashReportHandler::Uninitialize(); |
| } |
| #endif // !USE_BREAKPAD |
| |
| return TRUE; |
| } |
| |
| LRESULT WINAPI ImeEscape(HIMC himc, UINT sub_func, LPVOID data) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| switch (sub_func) { |
| case IME_ESC_IME_NAME: { |
| // Application wants to retrieve the name of the IME. |
| // Currently, we returns English name. |
| // According to the document, the buffer is guaranteed to be greater |
| // than or equal to 64 characters in Windows NT. |
| // http://msdn.microsoft.com/en-us/library/dd318166.aspx |
| wstring name; |
| mozc::Util::UTF8ToWide(mozc::kProductNameInEnglish, &name); |
| wchar_t *dest = static_cast<wchar_t *>(data); |
| const HRESULT result = ::StringCchCopyN( |
| dest, |
| mozc::kSafeIMENameLengthForNTInTchars, |
| name.c_str(), |
| mozc::kSafeIMENameLengthForNTInTchars); |
| return (SUCCEEDED(result) ? TRUE : FALSE); |
| } |
| default: |
| // not implemented |
| return FALSE; |
| } |
| return FALSE; |
| } |
| |
| BOOL WINAPI ImeSetActiveContext(HIMC himc, BOOL flag) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| // Clear kana-lock state so that users can input their passwords. |
| // TODO(yukawa): Move this code to somewhere appropriate. |
| BYTE keyboard_state[256]; |
| ::GetKeyboardState(keyboard_state); |
| keyboard_state[VK_KANA] = 0; |
| ::SetKeyboardState(keyboard_state); |
| |
| // Occasionally this function is called with nullptr in |himc|. |
| if (himc == nullptr) { |
| return TRUE; |
| } |
| |
| const bool activated = (flag != FALSE); |
| |
| // A temporal workaround for b/3046497. Occasionally ImmLockIMC fails |
| // to lock the context. |
| // TODO(yukawa): Refactor HIMCLockerT to support this scenario. |
| { |
| INPUTCONTEXT *context = ::ImmLockIMC(himc); |
| if (context == nullptr) { |
| return FALSE; |
| } |
| |
| // Clear |INPUTCONTEXT::hWnd| so that IMM subsystem will not generate UI |
| // messages when a deactivated input contexts is specified. |
| // Background: |
| // It seems that IMM does not clear |INPUTCONTEXT::hWnd| by default when |
| // an input context is about being deactivated. However, an application |
| // still can access to a deactivated input context via IMM APIs such as |
| // ImmSetOpenStatus and IMM subsystem generates UI messages as long as |
| // the |INPUTCONTEXT::hWnd| contains a valid window handle. |
| if (!activated) { |
| context->hWnd = nullptr; |
| } |
| ::ImmUnlockIMC(himc); |
| } |
| |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| if (!mozc::win32::PrivateContextUtil::IsValidPrivateContext( |
| context->hPrivate)) { |
| return FALSE; |
| } |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(context->hPrivate); |
| |
| mozc::win32::MessageQueue message_queue(himc); |
| if (activated) { |
| private_context->ui_visibility_tracker->OnFocus(); |
| } else { |
| private_context->ui_visibility_tracker->OnBlur(); |
| } |
| message_queue.AddMessage( |
| WM_IME_NOTIFY, IMN_PRIVATE, mozc::win32::kNotifyUpdateUI); |
| |
| return (message_queue.Send() ? TRUE : FALSE); |
| } |
| |
| BOOL WINAPI ImeProcessKey(HIMC himc, |
| UINT virtual_key, |
| LPARAM lParam, |
| CONST LPBYTE key_state) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| if (!mozc::win32::ImeCore::IsInputContextInitialized(himc)) { |
| return FALSE; |
| } |
| |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(context->hPrivate); |
| |
| // Because of IME_PROP_ACCEPT_WIDE_VKEY, |HIWORD(virtual_key)| contains an |
| // Unicode character if |HIWORD(virtual_key) == VK_PACKET|. |
| // You cannot assume that |virtual_key| is in [0, 255]. |
| mozc::win32::VirtualKey vk = |
| mozc::win32::VirtualKey::FromCombinedVirtualKey(virtual_key); |
| |
| const mozc::win32::KeyboardStatus keyboard_status(key_state); |
| const mozc::win32::LParamKeyInfo key_info(lParam); |
| |
| // Check if this key event is handled by VKBackBasedDeleter to support |
| // *deletion_range* rule. |
| const mozc::win32::VKBackBasedDeleter::ClientAction vk_back_action = |
| private_context->deleter->OnKeyEvent( |
| vk.virtual_key(), key_info.IsKeyDownInImeProcessKey(), true); |
| switch (vk_back_action) { |
| case mozc::win32::VKBackBasedDeleter::DO_DEFAULT_ACTION: |
| // do nothing. |
| break; |
| case mozc::win32::VKBackBasedDeleter:: |
| CALL_END_DELETION_THEN_DO_DEFAULT_ACTION: |
| private_context->deleter->EndDeletion(); |
| break; |
| case mozc::win32::VKBackBasedDeleter::SEND_KEY_TO_APPLICATION: |
| return FALSE; // Do not consume this key. |
| case mozc::win32::VKBackBasedDeleter::CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER: |
| return TRUE; // Consume this key but do not send this key to server. |
| case mozc::win32::VKBackBasedDeleter:: |
| CALL_END_DELETION_BUT_NEVER_SEND_TO_SERVER: |
| case mozc::win32::VKBackBasedDeleter::APPLY_PENDING_STATUS: |
| default: |
| DLOG(FATAL) << "this action is not applicable to ImeProcessKey."; |
| break; |
| } |
| |
| if (context->fOpen) { |
| const mozc::win32::SurrogatePairObserver::ClientAction surrogate_action = |
| private_context->surrogate_pair_observer->OnTestKeyEvent( |
| vk, key_info.IsKeyDownInImeProcessKey()); |
| switch (surrogate_action.type) { |
| case mozc::win32::SurrogatePairObserver::DO_DEFAULT_ACTION: |
| break; |
| case mozc::win32::SurrogatePairObserver:: |
| DO_DEFAULT_ACTION_WITH_RETURNED_UCS4: |
| vk = mozc::win32::VirtualKey::FromUnicode(surrogate_action.ucs4); |
| break; |
| case mozc::win32::SurrogatePairObserver:: |
| CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER: |
| return TRUE; // Consume this key but do not send this key to server. |
| default: |
| DLOG(FATAL) << "this action is not applicable to ImeProcessKey."; |
| break; |
| } |
| } |
| |
| mozc::win32::InputState ime_state = *private_context->ime_state; |
| |
| // Update |private_context->ime_behavior| to support Kana/Roman input toggle |
| // keys. See b/3118905 for details. |
| mozc::win32::KeyEventHandler::UpdateBehaviorInImeProcessKey( |
| vk, key_info.IsKeyDownInImeProcessKey(), |
| ime_state, private_context->ime_behavior); |
| |
| // Make an snapshot of |private_context->ime_behavior|, which |
| // cannot be substituted for by const reference. |
| mozc::win32::InputBehavior behavior = *private_context->ime_behavior; |
| mozc::commands::Context mozc_context; |
| FillContext(himc, &mozc_context); |
| |
| ime_state.logical_conversion_mode = context->fdwConversion; |
| ime_state.open = (context->fOpen != FALSE); |
| mozc::win32::InputState next_state; |
| mozc::commands::Output temporal_output; |
| const mozc::win32::KeyEventHandlerResult result = |
| mozc::win32::ImeCore::ImeProcessKey(private_context->client, |
| vk, key_info, keyboard_status, behavior, ime_state, mozc_context, |
| &next_state, &temporal_output); |
| if (!result.succeeded) { |
| return FALSE; |
| } |
| |
| *private_context->ime_state = next_state; |
| |
| if (result.should_be_sent_to_server && temporal_output.has_consumed()) { |
| private_context->last_output->CopyFrom(temporal_output); |
| } |
| |
| const mozc::win32::IndicatorVisibilityTracker::Action indicator_action = |
| private_context->indicator_visibility_tracker-> |
| OnTestKey(vk, key_info.IsKeyDownInImeProcessKey(), |
| result.should_be_eaten); |
| if (indicator_action == mozc::win32::IndicatorVisibilityTracker::kUpdateUI) { |
| mozc::win32::MessageQueue message_queue(himc); |
| message_queue.AddMessage( |
| WM_IME_NOTIFY, IMN_PRIVATE, mozc::win32::kNotifyUpdateUI); |
| message_queue.Send(); |
| } |
| |
| return result.should_be_eaten ? TRUE : FALSE; |
| } |
| |
| // TODO(yukawa): Refactor the implementation. |
| BOOL WINAPI NotifyIME(HIMC himc, DWORD action, DWORD index, DWORD value) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| if (!mozc::win32::ImeCore::IsInputContextInitialized(himc)) { |
| return FALSE; |
| } |
| |
| const bool generate_message = mozc::win32::ImeCore::IsActiveContext(himc); |
| |
| switch (action) { |
| case NI_CLOSECANDIDATE: { |
| const DWORD candidate_window_index = index; |
| if (candidate_window_index != 0) { |
| return FALSE; |
| } |
| return mozc::win32::ImeCore::CloseCandidate(himc, generate_message); |
| } |
| case NI_SELECTCANDIDATESTR: { |
| const DWORD candidate_window_index = index; |
| if (candidate_window_index != 0) { |
| return FALSE; |
| } |
| const int32 candidate_index = static_cast<int32>(value); |
| return mozc::win32::ImeCore::HighlightCandidate( |
| himc, candidate_index, generate_message); |
| } |
| case NI_OPENCANDIDATE: |
| case NI_CHANGECANDIDATELIST: |
| case NI_FINALIZECONVERSIONRESULT: |
| case NI_SETCANDIDATE_PAGESTART: |
| case NI_SETCANDIDATE_PAGESIZE: |
| case NI_IMEMENUSELECTED: |
| // not implemented |
| // TODO(yukawa): implement them. |
| return FALSE; |
| } |
| |
| if (action == NI_COMPOSITIONSTR) { |
| mozc::win32::UIContext context(himc); |
| if (context.IsCompositionStringEmpty()) { |
| return TRUE; |
| } |
| |
| switch (index) { |
| case CPS_COMPLETE: { |
| if (!mozc::win32::ImeCore::SubmitComposition( |
| himc, generate_message)) { |
| return FALSE; |
| } |
| return TRUE; |
| } |
| case CPS_CANCEL: { |
| if (!mozc::win32::ImeCore::CancelComposition( |
| himc, generate_message)) { |
| return FALSE; |
| } |
| return TRUE; |
| } |
| case CPS_REVERT: |
| case CPS_CONVERT: |
| // not implemented |
| // TODO(yukawa): implement them. |
| return FALSE; |
| default: |
| return FALSE; |
| } |
| // never reaches here. |
| CHECK(FALSE); |
| } |
| |
| if (action != NI_CONTEXTUPDATED) { |
| return FALSE; |
| } |
| |
| CHECK_EQ(NI_CONTEXTUPDATED, action); |
| |
| switch (value) { |
| case IMC_SETCONVERSIONMODE: { |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| context->fdwConversion = |
| mozc::win32::ImeCore::GetSupportableConversionMode( |
| context->fdwConversion); |
| mozc::win32::ImeCore::SwitchInputMode( |
| himc, context->fdwConversion, generate_message); |
| // We need not to generate WM_IME_NOTIFY/IMN_SETSENTENCEMODE because |
| // ImmSetOpenStatus API generates it anyway. |
| return TRUE; |
| } |
| case IMC_SETSENTENCEMODE: { |
| // See b/2913510, b/2954777, and b/2955175 for details. |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| // We need not to generate WM_IME_NOTIFY/IMN_SETSENTENCEMODE because |
| // ImmSetOpenStatus API generates it anyway. |
| context->fdwSentence = |
| mozc::win32::ImeCore::GetSupportableSentenceMode( |
| context->fdwSentence); |
| return TRUE; |
| } |
| case IMC_SETOPENSTATUS: { |
| mozc::win32::UIContext context(himc); |
| if (!context.GetOpenStatus()) { |
| // If this is an active context, we have to generate message because |
| // ImmSetOpenStatus API is responsible for generating only |
| // a WM_IME_NOTIFY(IMC_SETOPENSTATUS) message. Any other UI messages |
| // including composition messages should be delivered when an on-going |
| // composition is terminated. See b/3186132 for details. |
| mozc::win32::ImeCore::IMEOff(himc, generate_message); |
| return TRUE; |
| } |
| |
| DCHECK(context.GetOpenStatus()); |
| DWORD mode = 0; |
| if (!context.GetConversionMode(&mode)) { |
| return FALSE; |
| } |
| // This is OK because ImeCore::OpenIME generates no UI messages. |
| mozc::win32::ImeCore::OpenIME(context.client(), mode); |
| // We need not to generate WM_IME_NOTIFY/IMN_SETOPENSTATUS because |
| // ImmSetOpenStatus API generates it anyway. |
| return TRUE; |
| } |
| case IMC_SETCANDIDATEPOS: |
| case IMC_SETCOMPOSITIONFONT: |
| case IMC_SETCOMPOSITIONWINDOW: |
| // We need not to generate corresponding UI messages because Imm API |
| // generate it anyway. |
| return TRUE; |
| default: |
| return FALSE; |
| } |
| return FALSE; |
| } |
| |
| // We need not to generate any UI message in this callback. |
| // UI window is responsible for updating its UI when it receives |
| // WM_IME_SETCONTEXT. |
| BOOL WINAPI ImeSelect(HIMC himc, BOOL select) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| // Clear kana-lock state so that users can input their passwords. |
| // TODO(yukawa): Move this code to somewhere appropriate. |
| BYTE keyboard_state[256]; |
| ::GetKeyboardState(keyboard_state); |
| keyboard_state[VK_KANA] = 0; |
| ::SetKeyboardState(keyboard_state); |
| |
| // In "lockdown" mode, it would be definitely better do nothing in our DLL. |
| // For example, lots of fundamental stop working in a sandboxed process as |
| // reported in b/3216603. In such a situation, remaining CHECK macro is |
| // likely to cause process crash. |
| if (mozc::win32::IsInLockdownMode()) { |
| return FALSE; |
| } |
| |
| IncrementContextRevision(); |
| |
| if (himc == nullptr) { |
| return TRUE; |
| } |
| |
| if (select == FALSE) { |
| // If there exist any on-going composition, IMM32 automatically calls |
| // NotifyIME with CPS_CANCEL or CPS_COMPLETE in advance based on |
| // IME_PROP_COMPLETE_ON_UNSELECT property. |
| // You need not to submit nor cancel composition here. |
| |
| // Clean up resources in PrivateContext. |
| mozc::win32::ScopedHIMC<INPUTCONTEXT> context(himc); |
| if (mozc::win32::PrivateContextUtil::IsValidPrivateContext( |
| context->hPrivate)) { |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(context->hPrivate); |
| private_context->Uninitialize(); |
| } |
| return TRUE; |
| } |
| |
| // Unfortunately, InitLogStream cannot be placed inside DllMain because it |
| // may internally call LoadSystemLibrary to retrieve user profile directory. |
| // We should definitely avoid using LoadSystemLibrary when the thread owns |
| // loader lock. |
| mozc::Logging::InitLogStream(kProductPrefix "_imm32_ui"); |
| |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| if (context.get() == nullptr) { |
| return FALSE; |
| } |
| |
| if (!context->Initialize()) { |
| return FALSE; |
| } |
| |
| // If the private area of the input context is not initialized, |
| // allocate the new region in which new client management object is |
| // stored. |
| if (!mozc::win32::PrivateContextUtil::EnsurePrivateContextIsInitialized( |
| &context->hPrivate)) { |
| return FALSE; |
| } |
| |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(context->hPrivate); |
| DCHECK(private_context->Validate()); |
| |
| // Normalize the conversion mode. |
| context->fdwConversion = mozc::win32::ImeCore::GetSupportableConversionMode( |
| context->fdwConversion); |
| |
| // Then, copy the initial mode into private context. |
| private_context->ime_state->logical_conversion_mode = context->fdwConversion; |
| private_context->ime_state->visible_conversion_mode = context->fdwConversion; |
| |
| // Allocate composition string buffer. |
| const HIMCC composition_string_handle = InitializeHIMCC( |
| context->hCompStr, sizeof(mozc::win32::CompositionString)); |
| if (composition_string_handle == nullptr) { |
| return FALSE; |
| } |
| mozc::win32::ScopedHIMCC<mozc::win32::CompositionString> |
| composition_string(composition_string_handle); |
| if (!composition_string->Initialize()) { |
| return FALSE; |
| } |
| |
| context->hCandInfo = |
| mozc::win32::CandidateInfoUtil::Initialize(context->hCandInfo); |
| if (context->hCandInfo == nullptr) { |
| return FALSE; |
| } |
| |
| // When this is an active context, notify it because ImeSetActiveContext will |
| // not be called when IME is changed. |
| if (mozc::win32::ImeCore::IsActiveContext(himc)) { |
| private_context->ui_visibility_tracker->OnFocus(); |
| } |
| |
| // Sync initial focus hierarchy. |
| private_context->focus_hierarchy_observer->SyncFocusHierarchy(); |
| |
| // Send the local status to the server when IME is ON. |
| if (context->fOpen) { |
| if ((context->fdwInit & INIT_CONVERSION) != INIT_CONVERSION) { |
| return FALSE; |
| } |
| // This is OK because ImeCore::OpenIME does not generate any UI message. |
| if (!mozc::win32::ImeCore::OpenIME( |
| private_context->client, context->fdwConversion)) { |
| return FALSE; |
| } |
| } |
| |
| // 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"; |
| } |
| |
| return TRUE; |
| } |
| |
| BOOL WINAPI ImeSetCompositionString(HIMC himc, |
| DWORD index, |
| LPVOID comp, |
| DWORD comp_length, |
| LPVOID read, |
| DWORD read_length) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| if (index == SCS_QUERYRECONVERTSTRING) { |
| // In this case, IMEs are supposed to update |composition_info| and/or |
| // |reading_info| if necessary. |
| RECONVERTSTRING *composition_info = |
| reinterpret_cast<RECONVERTSTRING *>(comp); |
| RECONVERTSTRING *reading_info = |
| reinterpret_cast<RECONVERTSTRING *>(read); |
| return mozc::win32::ImeCore::QueryReconversionFromApplication( |
| himc, composition_info, reading_info); |
| } else if (index == SCS_SETRECONVERTSTRING) { |
| // In this case, IMEs must not update |composition_info| nor |
| // |reading_info|. This is why they are treated as const pointer. |
| const RECONVERTSTRING *composition_info = |
| reinterpret_cast<const RECONVERTSTRING *>(comp); |
| const RECONVERTSTRING *reading_info = |
| reinterpret_cast<const RECONVERTSTRING *>(read); |
| return mozc::win32::ImeCore::ReconversionFromApplication( |
| himc, composition_info, reading_info); |
| } |
| |
| return FALSE; |
| } |
| |
| DWORD WINAPI ImeGetImeMenuItems(HIMC himc, |
| DWORD flags, |
| DWORD type, |
| LPIMEMENUITEMINFOW ime_parent_menu, |
| LPIMEMENUITEMINFOW ime_menu, |
| DWORD size) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| return 0; |
| } |
| |
| // TODO(yukawa): Refactor the implementation. |
| UINT WINAPI ImeToAsciiEx(UINT virtual_key, |
| UINT scan_code, |
| CONST LPBYTE key_state, |
| LPTRANSMSGLIST trans_buf, |
| UINT state, |
| HIMC himc) { |
| // If fails, no message generated. |
| DANGLING_CALLBACK_GUARD(0); |
| if (!mozc::win32::ImeCore::IsInputContextInitialized(himc)) { |
| // no message generated. |
| return 0; |
| } |
| |
| mozc::win32::ScopedHIMC<mozc::win32::InputContext> context(himc); |
| mozc::win32::ScopedHIMCC<mozc::win32::PrivateContext> |
| private_context(context->hPrivate); |
| |
| mozc::win32::VirtualKey vk = |
| mozc::win32::VirtualKey::FromCombinedVirtualKey(virtual_key); |
| const mozc::win32::KeyboardStatus keyboard_status(key_state); |
| mozc::win32::InputState ime_state = *private_context->ime_state; |
| ime_state.logical_conversion_mode = context->fdwConversion; |
| ime_state.open = (context->fOpen != FALSE); |
| mozc::win32::InputState next_state; |
| const BYTE raw_scan_code = static_cast<BYTE>(scan_code & 0xff); |
| const bool is_key_down = ((scan_code & 0x8000) == 0); |
| mozc::commands::Output temporal_output; |
| |
| const mozc::win32::VKBackBasedDeleter::ClientAction vk_back_action = |
| private_context->deleter->OnKeyEvent( |
| vk.virtual_key(), is_key_down, false); |
| |
| // Check if this key event is handled by VKBackBasedDeleter to support |
| // *deletion_range* rule. |
| bool use_pending_status = false; |
| bool ignore_this_keyevent = false; |
| switch (vk_back_action) { |
| case mozc::win32::VKBackBasedDeleter::DO_DEFAULT_ACTION: |
| // do nothing. |
| break; |
| case mozc::win32::VKBackBasedDeleter:: |
| CALL_END_DELETION_THEN_DO_DEFAULT_ACTION: |
| private_context->deleter->EndDeletion(); |
| break; |
| case mozc::win32::VKBackBasedDeleter::APPLY_PENDING_STATUS: |
| use_pending_status = true; |
| break; |
| case mozc::win32::VKBackBasedDeleter::CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER: |
| ignore_this_keyevent = true; |
| break; |
| case mozc::win32::VKBackBasedDeleter:: |
| CALL_END_DELETION_BUT_NEVER_SEND_TO_SERVER: |
| ignore_this_keyevent = true; |
| private_context->deleter->EndDeletion(); |
| break; |
| case mozc::win32::VKBackBasedDeleter::SEND_KEY_TO_APPLICATION: |
| default: |
| DLOG(FATAL) << "this action is not applicable to ImeProcessKey."; |
| break; |
| } |
| |
| if (ignore_this_keyevent) { |
| // no message generated. |
| return 0; |
| } |
| |
| if (context->fOpen) { |
| ignore_this_keyevent = false; |
| const mozc::win32::SurrogatePairObserver::ClientAction surrogate_action = |
| private_context->surrogate_pair_observer->OnKeyEvent(vk, is_key_down); |
| switch (surrogate_action.type) { |
| case mozc::win32::SurrogatePairObserver::DO_DEFAULT_ACTION: |
| break; |
| case mozc::win32::SurrogatePairObserver:: |
| DO_DEFAULT_ACTION_WITH_RETURNED_UCS4: |
| vk = mozc::win32::VirtualKey::FromUnicode(surrogate_action.ucs4); |
| break; |
| case mozc::win32::SurrogatePairObserver:: |
| CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER: |
| ignore_this_keyevent = true; |
| break; |
| default: |
| DLOG(FATAL) << "this action is not applicable to ImeProcessKey."; |
| break; |
| } |
| if (ignore_this_keyevent) { |
| // no message generated. |
| return 0; |
| } |
| } |
| |
| bool send_update_ui = false; |
| bool should_be_sent_to_server = false; |
| if (use_pending_status) { |
| next_state = private_context->deleter->pending_ime_state(); |
| temporal_output.CopyFrom( |
| private_context->deleter->pending_output()); |
| should_be_sent_to_server = true; |
| } else { |
| mozc::win32::InputBehavior behavior = *private_context->ime_behavior; |
| mozc::commands::Context mozc_context; |
| FillContext(himc, &mozc_context); |
| |
| // Update |mozc_context| with surrounding text information when available. |
| { |
| mozc::win32::UIContext ui_context(himc); |
| if (ui_context.IsCompositionStringEmpty()) { |
| mozc::win32::ImeCore::UpdateContextWithSurroundingText(himc, |
| &mozc_context); |
| } |
| } |
| |
| const mozc::win32::KeyEventHandlerResult result = |
| mozc::win32::ImeCore::ImeToAsciiEx( |
| private_context->client, vk, raw_scan_code, is_key_down, |
| keyboard_status, behavior, ime_state, mozc_context, &next_state, |
| &temporal_output); |
| |
| if (!result.succeeded) { |
| // no message generated. |
| return 0; |
| } |
| |
| const mozc::win32::IndicatorVisibilityTracker::Action indicator_action = |
| private_context->indicator_visibility_tracker->OnKey( |
| vk, is_key_down, result.should_be_eaten); |
| send_update_ui = |
| (indicator_action == |
| mozc::win32::IndicatorVisibilityTracker::kUpdateUI); |
| should_be_sent_to_server = result.should_be_sent_to_server; |
| } |
| |
| mozc::win32::MessageQueue message_queue(himc); |
| message_queue.Attach(trans_buf); |
| |
| if (should_be_sent_to_server) { |
| mozc::win32::ImeCore::UpdateContext( |
| himc, next_state, temporal_output, &message_queue); |
| } |
| |
| // Generate NotifyUpdateUI message if not exists. |
| { |
| bool has_ui_message = false; |
| const vector<TRANSMSG> &messages = message_queue.messages(); |
| for (size_t i = 0; i < messages.size(); ++i) { |
| const TRANSMSG &msg = messages[i]; |
| if (msg.message == WM_IME_NOTIFY && |
| msg.wParam == IMN_PRIVATE && |
| msg.lParam == mozc::win32::kNotifyUpdateUI) { |
| has_ui_message = true; |
| break; |
| } |
| } |
| if (!has_ui_message) { |
| message_queue.AddMessage( |
| WM_IME_NOTIFY, IMN_PRIVATE, mozc::win32::kNotifyUpdateUI); |
| } |
| } |
| |
| // MessageQueue::Detach returns the number of messages. |
| return message_queue.Detach(); |
| } |
| |
| BOOL WINAPI ImeConfigure(HKL hkl, HWND wnd, DWORD mode, LPVOID data) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| if (mode == IME_CONFIG_GENERAL) { |
| if (!mozc::Process::SpawnMozcProcess( |
| mozc::kMozcTool, "--mode=config_dialog")) { |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| if (mode == IME_CONFIG_SELECTDICTIONARY) { |
| if (!mozc::Process::SpawnMozcProcess( |
| mozc::kMozcTool, "--mode=dictionary_tool")) { |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| if (mode == IME_CONFIG_REGISTERWORD) { |
| if (data == nullptr) { |
| // |data| must not be nullptr if |mode| is IME_CONFIG_REGISTERWORD. |
| // http://msdn.microsoft.com/en-us/library/dd318173.aspx |
| return FALSE; |
| } |
| |
| // Retrieves word registration data. |
| const REGISTERWORD *reg_word = static_cast<REGISTERWORD *>(data); |
| const wstring word = GetStringIfWithinLimit(reg_word->lpWord, |
| kMaxCharsForRegisterWord); |
| const wstring reading = GetStringIfWithinLimit(reg_word->lpReading, |
| kMaxCharsForRegisterWord); |
| |
| SetEnveronmentVariablesForWordRegisterDialog(word, reading); |
| const bool spawn_succeeded = mozc::Process::SpawnMozcProcess( |
| mozc::kMozcTool, "--mode=word_register_dialog"); |
| // Delete all environment variables used. |
| SetEnveronmentVariablesForWordRegisterDialog(L"", L""); |
| |
| if (!spawn_succeeded) { |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL WINAPI ImeRegisterWord(LPCTSTR reading, DWORD style, LPCTSTR value) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| return FALSE; |
| } |
| |
| BOOL WINAPI ImeUnregisterWord(LPCTSTR lpRead, DWORD style, LPCTSTR value) { |
| DANGLING_CALLBACK_GUARD(FALSE); |
| return FALSE; |
| } |
| |
| UINT WINAPI ImeGetRegisterWordStyle(UINT item, LPSTYLEBUF style_buffer) { |
| DANGLING_CALLBACK_GUARD(0); |
| return 0; |
| } |
| |
| UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROC enum_proc, |
| LPCTSTR reading, |
| DWORD style, |
| LPCTSTR value, |
| LPVOID data) { |
| DANGLING_CALLBACK_GUARD(0); |
| return 0; |
| } |