| // Copyright 2010-2014, 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/ime/ime_ui_context.h" |
| |
| #define _ATL_NO_AUTOMATIC_NAMESPACE |
| #define _WTL_NO_AUTOMATIC_NAMESPACE |
| #define _ATL_NO_HOSTING |
| // Workaround against KB813540 |
| #include <atlbase_mozc.h> |
| #include <atlapp.h> |
| #include <atlstr.h> |
| #include <atlmisc.h> |
| #include <CommCtrl.h> // for CCSIZEOF_STRUCT |
| |
| #include <strsafe.h> |
| |
| #ifdef HAS_MSIME_HEADER |
| #include <msime.h> |
| #endif // HAS_MSIME_HEADER |
| |
| #include "base/singleton.h" |
| #include "client/client_interface.h" |
| #include "renderer/renderer_command.pb.h" |
| #include "renderer/win32/win32_font_util.h" |
| #include "win32/base/immdev.h" |
| #include "win32/base/indicator_visibility_tracker.h" |
| #include "win32/base/input_state.h" |
| #include "win32/base/win32_window_util.h" |
| #include "win32/ime/ime_composition_string.h" |
| #include "win32/ime/ime_private_context.h" |
| |
| #ifndef RWM_QUERYPOSITION |
| #define RWM_QUERYPOSITION TEXT("MSIMEQueryPosition") |
| #endif // RWM_QUERYPOSITION |
| |
| #ifndef VERSION_QUERYPOSITION |
| #define VERSION_QUERYPOSITION 1 |
| #endif // VERSION_QUERYPOSITION |
| |
| namespace mozc { |
| namespace win32 { |
| using WTL::CPoint; |
| using WTL::CRect; |
| |
| typedef mozc::commands::RendererCommand RendererCommand; |
| typedef mozc::commands::RendererCommand::ApplicationInfo ApplicationInfo; |
| |
| namespace { |
| const size_t kSizeOfImeCharPositionV1 = |
| CCSIZEOF_STRUCT(IMECHARPOSITION, rcDocument); |
| const size_t kSizeOfGUIThreadInfoV1 = |
| CCSIZEOF_STRUCT(GUITHREADINFO, rcCaret); |
| |
| const wchar_t *kTroublesomeWindowClassNames[] = { |
| L"EXCEL6", // b/4285222 |
| }; |
| |
| HIMCC GetPrivateContextHandle(const INPUTCONTEXT *input_context) { |
| if (input_context == nullptr) { |
| return nullptr; |
| } |
| if (!PrivateContextUtil::IsValidPrivateContext( |
| input_context->hPrivate)) { |
| return nullptr; |
| } |
| return input_context->hPrivate; |
| } |
| |
| class MSIMEPrivateMessageInitializer { |
| public: |
| MSIMEPrivateMessageInitializer() |
| : wm_msime_queryposition_(::RegisterWindowMessageW(RWM_QUERYPOSITION)) {} |
| UINT wm_msime_queryposition() const { |
| return wm_msime_queryposition_; |
| } |
| private: |
| UINT wm_msime_queryposition_; |
| }; |
| |
| void SetCharPosition(const IMECHARPOSITION &position, |
| commands::RendererCommand::CharacterPosition *target) { |
| target->set_line_height(position.cLineHeight); |
| target->set_position(position.dwCharPos); |
| |
| RendererCommand::Point *point = target->mutable_top_left(); |
| point->set_x(position.pt.x); |
| point->set_y(position.pt.y); |
| |
| RendererCommand::Rectangle *rect = target->mutable_document_area(); |
| rect->set_left(position.rcDocument.left); |
| rect->set_top(position.rcDocument.top); |
| rect->set_bottom(position.rcDocument.bottom); |
| rect->set_right(position.rcDocument.right); |
| } |
| } // Anonymous namespace |
| |
| UIContext::UIContext(HIMC context_handle) |
| : context_handle_(context_handle), |
| input_context_(context_handle), |
| private_context_(GetPrivateContextHandle(input_context_.get())) {} |
| |
| bool UIContext::GetLastOutput(mozc::commands::Output *output) const { |
| if (output == nullptr) { |
| return false; |
| } |
| if (private_context_.get() == nullptr) { |
| return false; |
| } |
| if (!private_context_->Validate()) { |
| return false; |
| } |
| output->CopyFrom(*private_context_->last_output); |
| return true; |
| } |
| |
| // TODO(yukawa): Check if this procedure is safe or not. |
| HWND UIContext::GetAttachedWindow() const { |
| if (input_context_.get() == nullptr) { |
| return nullptr; |
| } |
| return input_context_->hWnd; |
| } |
| |
| bool UIContext::IsEmpty() const { |
| return (context_handle_ == nullptr); |
| } |
| |
| bool UIContext::IsCompositionStringEmpty() const { |
| if (input_context_.get() == nullptr) { |
| return true; |
| } |
| ScopedHIMCC<COMPOSITIONSTRING> composition_string( |
| input_context_->hCompStr); |
| if (composition_string.get() == nullptr) { |
| return true; |
| } |
| return (composition_string->dwCompStrLen == 0); |
| } |
| |
| bool UIContext::GetFocusedCharacterIndexInComposition(DWORD *index) const { |
| DWORD dummy_dword = 0; |
| if (index == nullptr) { |
| index = &dummy_dword; |
| } |
| |
| *index = 0; |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (input_context_->hCompStr == nullptr) { |
| return false; |
| } |
| if (::ImmGetIMCCSize(input_context_->hCompStr) != |
| sizeof(CompositionString)) { |
| return false; |
| } |
| ScopedHIMCC<CompositionString> composition_string( |
| input_context_->hCompStr); |
| if (composition_string.get() == nullptr) { |
| return false; |
| } |
| *index = composition_string->focused_character_index(); |
| return true; |
| } |
| |
| bool UIContext::GetCompositionForm(COMPOSITIONFORM *composition_form) const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (composition_form == nullptr) { |
| return false; |
| } |
| if ((input_context_->fdwInit & INIT_COMPFORM) != INIT_COMPFORM) { |
| return false; |
| } |
| *composition_form = input_context_->cfCompForm; |
| return true; |
| } |
| |
| bool UIContext::GetCandidateForm( |
| DWORD form_index, CANDIDATEFORM *candidate_form) const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (candidate_form == nullptr) { |
| return false; |
| } |
| |
| // Currently the array size is 4. |
| if (form_index >= arraysize(input_context_->cfCandForm)) { |
| return false; |
| } |
| |
| if (input_context_->cfCandForm[form_index].dwIndex != form_index) { |
| return false; |
| } |
| *candidate_form = input_context_->cfCandForm[form_index]; |
| return true; |
| } |
| |
| bool UIContext::GetCompositionFont(LOGFONTW *font) const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (font == nullptr) { |
| return false; |
| } |
| // ImmGetCompositionFontW internally checks if INPUTCONTEXT::fdwInit has |
| // INIT_LOGFONT bit or not. ImmGetCompositionFontW works well even when |
| // the target window is a native Unicode window except that |
| // INPUTCONTEXT::lfFont has already been corrupted by some IMEs such as |
| // ATOK 2009. See b/3042347 for details. |
| if (::ImmGetCompositionFontW(context_handle_, font) == FALSE) { |
| return false; |
| } |
| |
| // There exist some troublesome applications which set broken composition |
| // font. We ignore such composition font if its face-name is empty. |
| // See b/4506404 for details. |
| bool null_terminated = false; |
| bool empty_facename = true; |
| for (size_t i = 0; i < arraysize(font->lfFaceName); ++i) { |
| if (font->lfFaceName[i] == L'\0') { |
| null_terminated = true; |
| break; |
| } |
| empty_facename = false; |
| } |
| if (!null_terminated) { |
| return false; |
| } |
| if (empty_facename) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool UIContext::GetConversionMode(DWORD *conversion) const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (conversion == nullptr) { |
| return false; |
| } |
| if ((input_context_->fdwInit & INIT_CONVERSION) != INIT_CONVERSION) { |
| return false; |
| } |
| *conversion = input_context_->fdwConversion; |
| return true; |
| } |
| |
| bool UIContext::GetVisibleConversionMode(DWORD *conversion) const { |
| if (conversion == nullptr) { |
| return false; |
| } |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (private_context_.get() == nullptr) { |
| return false; |
| } |
| if (!private_context_->Validate()) { |
| return false; |
| } |
| *conversion = private_context_->ime_state->visible_conversion_mode; |
| return true; |
| } |
| |
| bool UIContext::GetLogicalConversionMode(DWORD *conversion) const { |
| if (conversion == nullptr) { |
| return false; |
| } |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (private_context_.get() == nullptr) { |
| return false; |
| } |
| if (!private_context_->Validate()) { |
| return false; |
| } |
| *conversion = private_context_->ime_state->logical_conversion_mode; |
| return true; |
| } |
| |
| bool UIContext::GetOpenStatus() const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| return (input_context_->fOpen != FALSE); |
| } |
| |
| bool UIContext::IsKanaInputPreferred() const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (private_context_.get() == nullptr) { |
| return false; |
| } |
| if (!private_context_->Validate()) { |
| return false; |
| } |
| if (private_context_->ime_behavior == nullptr) { |
| return false; |
| } |
| return private_context_->ime_behavior->prefer_kana_input; |
| } |
| |
| bool UIContext::IsModeIndicatorEnabled() const { |
| if (input_context_.get() == nullptr) { |
| return false; |
| } |
| if (private_context_.get() == nullptr) { |
| return false; |
| } |
| if (!private_context_->Validate()) { |
| return false; |
| } |
| if (private_context_->ime_behavior == nullptr) { |
| return false; |
| } |
| return private_context_->ime_behavior->use_mode_indicator; |
| } |
| |
| mozc::client::ClientInterface *UIContext::client() const { |
| if (input_context_.get() == nullptr) { |
| return nullptr; |
| } |
| if (private_context_.get() == nullptr) { |
| return nullptr; |
| } |
| return private_context_->client; |
| } |
| |
| const INPUTCONTEXT *UIContext::input_context() const { |
| if (input_context_.get() == nullptr) { |
| return nullptr; |
| } |
| return input_context_.get(); |
| } |
| |
| UIVisibilityTracker *UIContext::ui_visibility_tracker() const { |
| if (input_context_.get() == nullptr) { |
| return nullptr; |
| } |
| if (private_context_.get() == nullptr) { |
| return nullptr; |
| } |
| return private_context_->ui_visibility_tracker; |
| } |
| |
| IndicatorVisibilityTracker *UIContext::indicator_visibility_tracker() const { |
| if (input_context_.get() == nullptr) { |
| return nullptr; |
| } |
| if (private_context_.get() == nullptr) { |
| return nullptr; |
| } |
| return private_context_->indicator_visibility_tracker; |
| } |
| |
| |
| bool UIContext::FillCompositionForm(ApplicationInfo *info) const { |
| COMPOSITIONFORM composition_form = {0}; |
| if (!GetCompositionForm(&composition_form)) { |
| return false; |
| } |
| |
| commands::RendererCommand::CompositionForm *form = |
| info->mutable_composition_form(); |
| form->set_style_bits(composition_form.dwStyle); |
| |
| // Set |current_pos|. |
| RendererCommand::Point *point = form->mutable_current_position(); |
| point->set_x(composition_form.ptCurrentPos.x); |
| point->set_y(composition_form.ptCurrentPos.y); |
| |
| // Set |area| |
| RendererCommand::Rectangle *area = form->mutable_area(); |
| area->set_left(composition_form.rcArea.left); |
| area->set_top(composition_form.rcArea.top); |
| area->set_right(composition_form.rcArea.right); |
| area->set_bottom(composition_form.rcArea.bottom); |
| |
| return true; |
| } |
| |
| bool UIContext::FillCandidateForm(ApplicationInfo *info) const { |
| CANDIDATEFORM candidate_form = {0}; |
| if (!GetCandidateForm(0, &candidate_form)) { |
| return false; |
| } |
| |
| RendererCommand::CandidateForm *form = info->mutable_candidate_form(); |
| form->set_style_bits(candidate_form.dwStyle); |
| |
| // Set |current_pos|. |
| RendererCommand::Point *point = form->mutable_current_position(); |
| point->set_x(candidate_form.ptCurrentPos.x); |
| point->set_y(candidate_form.ptCurrentPos.y); |
| |
| // Set |area| if available. |
| RendererCommand::Rectangle *area = form->mutable_area(); |
| area->set_left(candidate_form.rcArea.left); |
| area->set_top(candidate_form.rcArea.top); |
| area->set_right(candidate_form.rcArea.right); |
| area->set_bottom(candidate_form.rcArea.bottom); |
| |
| return true; |
| } |
| |
| bool UIContext::FillCharPosition(ApplicationInfo *info) const { |
| // Some applications such as Excel sometimes get stuck in the message handler |
| // against WM_MSIME_QUERYPOSITION. b/4285222. |
| // To reduce the risk of hung-up, do nothing if the target window is not the |
| // focused window. |
| const HWND window_handle = input_context()->hWnd; |
| if (!::IsWindow(window_handle)) { |
| return false; |
| } |
| if (window_handle != ::GetFocus()) { |
| return false; |
| } |
| |
| // Do not request character position to some troublesome windows. |
| // See b/4285222 for details. |
| const wstring &window_class_name = |
| WindowUtil::GetWindowClassName(window_handle); |
| for (size_t i = 0; i < arraysize(kTroublesomeWindowClassNames); ++i) { |
| if (window_class_name == kTroublesomeWindowClassNames[i]) { |
| return false; |
| } |
| } |
| |
| IMECHARPOSITION position = {}; |
| |
| // This index must be calculated in unit of wide characters to support |
| // surrogate pair. See b/4159275 for details. |
| DWORD target_char_index = 0; |
| if (!GetFocusedCharacterIndexInComposition(&target_char_index)) { |
| return false; |
| } |
| |
| position.dwSize = kSizeOfImeCharPositionV1; |
| position.dwCharPos = target_char_index; |
| |
| // WM_MSIME_QUERYPOSITION and IMR_QUERYCHARPOSITION might have some side |
| // effects on some applications. For example, RichEdit changes its scroll |
| // position so that the cursor will be shown. b/3223011. |
| // Some old applications can sometimes crash upon receiving |
| // WM_MSIME_QUERYPOSITION. b/3208669, b/3096191, and b/3212271. |
| |
| const UINT WM_MSIME_QUERYPOSITION = |
| ::mozc::Singleton<MSIMEPrivateMessageInitializer>::get()-> |
| wm_msime_queryposition(); |
| LRESULT result = ::SendMessage( |
| window_handle, WM_MSIME_QUERYPOSITION, |
| VERSION_QUERYPOSITION, reinterpret_cast<LPARAM>(&position)); |
| if (result != FALSE) { |
| SetCharPosition(position, info->mutable_composition_target()); |
| return true; |
| } |
| |
| position.dwSize = kSizeOfImeCharPositionV1; |
| position.dwCharPos = target_char_index; |
| result = ::ImmRequestMessageW(context_handle_, |
| IMR_QUERYCHARPOSITION, |
| reinterpret_cast<LPARAM>(&position)); |
| if (result != FALSE) { |
| SetCharPosition(position, info->mutable_composition_target()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool UIContext::FillCaretInfo(ApplicationInfo *info) const { |
| GUITHREADINFO thread_info = {0}; |
| thread_info.cbSize = kSizeOfGUIThreadInfoV1; |
| if (::GetGUIThreadInfo(::GetCurrentThreadId(), &thread_info) == FALSE) { |
| return false; |
| } |
| |
| RendererCommand::CaretInfo *caret = info->mutable_caret_info(); |
| caret->set_blinking((thread_info.flags & GUI_CARETBLINKING) == |
| GUI_CARETBLINKING); |
| |
| // Set |caret_rect| |
| const CRect caret_rect(thread_info.rcCaret); |
| RendererCommand::Rectangle *rect = caret->mutable_caret_rect(); |
| rect->set_left(thread_info.rcCaret.left); |
| rect->set_top(thread_info.rcCaret.top); |
| rect->set_right(thread_info.rcCaret.right); |
| rect->set_bottom(thread_info.rcCaret.bottom); |
| |
| caret->set_target_window_handle(reinterpret_cast<uint32>( |
| thread_info.hwndCaret)); |
| |
| return true; |
| } |
| |
| bool UIContext::FillFontInfo(ApplicationInfo *info) const { |
| LOGFONT composition_font = {0}; |
| if (!GetCompositionFont(&composition_font)) { |
| return false; |
| } |
| |
| FontUtil::ToWinLogFont(composition_font, |
| info->mutable_composition_font()); |
| return true; |
| } |
| |
| } // namespace win32 |
| } // namespace mozc |