1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "win8/metro_driver/ime/ime_popup_monitor.h"
6
7#include <windows.h>
8
9#include "base/logging.h"
10#include "base/message_loop/message_loop.h"
11#include "win8/metro_driver/ime/ime_popup_observer.h"
12
13namespace metro_driver {
14namespace {
15
16ImePopupObserver* g_observer_ = NULL;
17HWINEVENTHOOK g_hook_handle_ = NULL;
18
19void CALLBACK ImeEventCallback(HWINEVENTHOOK win_event_hook_handle,
20                               DWORD event,
21                               HWND window_handle,
22                               LONG object_id,
23                               LONG child_id,
24                               DWORD event_thread,
25                               DWORD event_time) {
26  // This function is registered to SetWinEventHook to be called back on the UI
27  // thread.
28  DCHECK(base::MessageLoopForUI::IsCurrent());
29
30  if (!g_observer_)
31    return;
32  switch (event) {
33    case EVENT_OBJECT_IME_SHOW:
34      g_observer_->OnImePopupChanged(ImePopupObserver::kPopupShown);
35      return;
36    case EVENT_OBJECT_IME_HIDE:
37      g_observer_->OnImePopupChanged(ImePopupObserver::kPopupHidden);
38      return;
39    case EVENT_OBJECT_IME_CHANGE:
40      g_observer_->OnImePopupChanged(ImePopupObserver::kPopupUpdated);
41      return;
42  }
43}
44
45}  // namespace
46
47void AddImePopupObserver(ImePopupObserver* observer) {
48  CHECK(g_observer_ == NULL)
49      << "Currently only one observer is supported at the same time.";
50  g_observer_ = observer;
51
52  // IMEs running under immersive mode are supposed to generate WinEvent
53  // whenever their popup UI such as candidate window is shown, updated, and
54  // hidden to support accessibility applications.
55  // http://msdn.microsoft.com/en-us/library/windows/apps/hh967425.aspx#accessibility
56  // Note that there is another mechanism in TSF, called ITfUIElementSink, to
57  // subscribe when the visibility of an IME's UI element is changed. However,
58  // MS-IME running under immersive mode does not fully support this API.
59  // Thus, WinEvent is more reliable for this purpose.
60  g_hook_handle_ = SetWinEventHook(
61      EVENT_OBJECT_IME_SHOW,
62      EVENT_OBJECT_IME_CHANGE,
63      NULL,
64      ImeEventCallback,
65      GetCurrentProcessId(),  // monitor the metro_driver process only
66      0,   // hook all threads because MS-IME emits WinEvent in a worker thread
67      WINEVENT_OUTOFCONTEXT);  // allows us to receive message in the UI thread
68  LOG_IF(ERROR, !g_hook_handle_) << "SetWinEventHook failed.";
69}
70
71void RemoveImePopupObserver(ImePopupObserver* observer) {
72  if (g_observer_ != observer)
73    return;
74  g_observer_ = NULL;
75  if (!g_hook_handle_)
76    return;
77  const bool unhook_succeeded = !!UnhookWinEvent(g_hook_handle_);
78  LOG_IF(ERROR, !unhook_succeeded) << "UnhookWinEvent failed.";
79  g_hook_handle_ = NULL;
80}
81
82}  // namespace metro_driver
83