blob: 30cda8f24995d7a8539877d9f8189359fd59b6b8 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/tip/tip_ui_handler_immersive.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
// Workaround against KB813540
#include <atlbase_mozc.h>
#include <atlcom.h>
#include <atlapp.h>
#include <atlmisc.h>
#include <atlwin.h>
#include <msctf.h>
#include "base/hash_tables.h"
#include "base/util.h"
#include "session/commands.pb.h"
#include "win32/tip/tip_composition_util.h"
#include "win32/tip/tip_private_context.h"
#include "win32/tip/tip_text_service.h"
#include "win32/tip/tip_ui_element_immersive.h"
#include "win32/tip/tip_ui_element_manager.h"
namespace mozc {
namespace win32 {
namespace tsf {
namespace {
using ATL::CComPtr;
using ATL::CComQIPtr;
using ATL::CWindow;
using WTL::CRect;
using ::mozc::commands::Output;
using ::mozc::commands::Preedit;
typedef ::mozc::commands::Preedit_Segment Segment;
typedef ::mozc::commands::Preedit_Segment::Annotation Annotation;
// 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;
// Visual C++ 2008 requires this.
#if 1310 <= _MSC_VER || _MSC_VER < 1600
using stdext::hash_compare;
#endif // 1310 <= _MSC_VER < 1600
// Custom hash function for ATL::CComPtr.
template <typename T>
struct PtrHashCompare : public hash_compare<T> {
std::size_t operator()(const 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) >> kUnusedBits;
}
bool operator()(const T &value1, const T &value2) const {
return value1 != value2;
}
};
class UiElementMap
: public hash_map<ITfUIElement *,
HWND,
PtrHashCompare<IUnknown *> > {
};
class ThreadLocalInfo {
public:
ThreadLocalInfo() {}
UiElementMap *ui_element_map() {
return &ui_element_map_;
}
private:
UiElementMap ui_element_map_;
DISALLOW_COPY_AND_ASSIGN(ThreadLocalInfo);
};
ThreadLocalInfo *GetThreadLocalInfo() {
if (g_module_unloaded) {
return nullptr;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return nullptr;
}
ThreadLocalInfo *info = static_cast<ThreadLocalInfo *>(
::TlsGetValue(g_tls_index));
if (info != nullptr) {
// already initialized.
return info;
}
info = new ThreadLocalInfo();
::TlsSetValue(g_tls_index, info);
return info;
}
void EnsureThreadLocalInfoDestroyed() {
if (g_module_unloaded) {
return;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return;
}
ThreadLocalInfo *info = static_cast<ThreadLocalInfo *>(
::TlsGetValue(g_tls_index));
if (info == nullptr) {
// already destroyes.
return;
}
delete info;
::TlsSetValue(g_tls_index, nullptr);
}
void UpdateUI(TipTextService *text_service, ITfContext *context) {
if (text_service == nullptr) {
return;
}
ThreadLocalInfo *info = GetThreadLocalInfo();
if (info == nullptr) {
return;
}
TipPrivateContext *private_context =
text_service->GetPrivateContext(context);
if (private_context == nullptr) {
return;
}
private_context->GetUiElementManager()->OnUpdate(text_service, context);
const UiElementMap &map = *info->ui_element_map();
const TipUiElementManager::UIElementFlags kUiFlags[] = {
TipUiElementManager::kSuggestWindow,
TipUiElementManager::kCandidateWindow,
};
for (size_t i = 0; i < arraysize(kUiFlags); ++i) {
const CComPtr<ITfUIElement> ui_element =
private_context->GetUiElementManager()->GetElement(kUiFlags[i]);
if (ui_element) {
const UiElementMap::const_iterator it = map.find(ui_element);
if (it == map.end()) {
continue;
}
::PostMessageW(it->second, WM_MOZC_IMMERSIVE_WINDOW_UPDATE, 0, 0);
}
}
}
} // namespace
ITfUIElement *TipUiHandlerImmersive::CreateUI(TipUiHandler::UiType type,
TipTextService *text_service,
ITfContext *context) {
switch (type) {
case TipUiHandler::kSuggestWindow:
case TipUiHandler::kCandidateWindow: {
ThreadLocalInfo *info = GetThreadLocalInfo();
if (info == nullptr) {
return nullptr;
}
HWND window_handle = nullptr;
CComPtr<ITfUIElement> element(TipUiElementImmersive::New(
text_service, context, &window_handle));
if (element == nullptr) {
return nullptr;
}
if (window_handle == nullptr) {
return nullptr;
}
(*info->ui_element_map())[element] = window_handle;
// pass the ownership to the caller.
return element.Detach();
}
default:
return nullptr;
}
}
void TipUiHandlerImmersive::OnDestroyElement(ITfUIElement *element) {
ThreadLocalInfo *info = GetThreadLocalInfo();
if (info == nullptr) {
return;
}
UiElementMap::iterator it = info->ui_element_map()->find(element);
if (it == info->ui_element_map()->end()) {
return;
}
::DestroyWindow(it->second);
info->ui_element_map()->erase(it);
}
void TipUiHandlerImmersive::OnActivate() {
TipUiElementImmersive::OnActivate();
}
void TipUiHandlerImmersive::OnDeactivate() {
EnsureThreadLocalInfoDestroyed();
TipUiElementImmersive::OnDeactivate();
}
void TipUiHandlerImmersive::OnFocusChange(
TipTextService *text_service, ITfDocumentMgr *focused_document_manager) {
if (!focused_document_manager) {
// Empty document is not an error.
return;
}
CComPtr<ITfContext> context;
if (FAILED(focused_document_manager->GetBase(&context))) {
return;
}
if (!context) {
return;
}
UpdateUI(text_service, context);
}
bool TipUiHandlerImmersive::Update(TipTextService *text_service,
ITfContext *context,
TfEditCookie read_cookie) {
UpdateUI(text_service, context);
return true;
}
bool TipUiHandlerImmersive::OnDllProcessAttach(HINSTANCE module_handle,
bool static_loading) {
g_module = module_handle;
g_tls_index = ::TlsAlloc();
return TipUiElementImmersive::OnDllProcessAttach(
module_handle, static_loading);
}
void TipUiHandlerImmersive::OnDllProcessDetach(HINSTANCE module_handle,
bool process_shutdown) {
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;
TipUiElementImmersive::OnDllProcessDetach(module_handle, process_shutdown);
}
} // namespace tsf
} // namespace win32
} // namespace mozc