blob: 8b665245ce616d7b6a7018e237df6d16eb6a42ff [file] [log] [blame]
// 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