OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // TODO(nona): Remvoe IBusUiController | |
4 | 5 |
5 #include "chrome/browser/chromeos/input_method/ibus_ui_controller.h" | 6 #include "chrome/browser/chromeos/input_method/ibus_ui_controller.h" |
6 | 7 |
7 #if defined(HAVE_IBUS) | |
8 #include <ibus.h> | |
9 #endif | |
10 | |
11 #include <sstream> | 8 #include <sstream> |
12 | 9 |
13 #include "ash/shell.h" | 10 #include "ash/shell.h" |
14 #include "base/logging.h" | 11 #include "base/logging.h" |
15 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
16 #include "base/string_util.h" | 13 #include "base/string_util.h" |
17 #include "chrome/browser/chromeos/input_method/input_method_descriptor.h" | 14 #include "chrome/browser/chromeos/input_method/input_method_descriptor.h" |
18 #include "chrome/browser/chromeos/input_method/input_method_manager.h" | 15 #include "chrome/browser/chromeos/input_method/input_method_manager.h" |
19 #include "chrome/browser/chromeos/input_method/input_method_util.h" | 16 #include "chrome/browser/chromeos/input_method/input_method_util.h" |
17 #include "chromeos/dbus/dbus_thread_manager.h" | |
18 #include "chromeos/dbus/ibus/ibus_lookup_table.h" | |
20 #include "ui/aura/client/aura_constants.h" | 19 #include "ui/aura/client/aura_constants.h" |
21 #include "ui/aura/root_window.h" | 20 #include "ui/aura/root_window.h" |
22 #include "ui/base/ime/input_method_ibus.h" | 21 #include "ui/base/ime/input_method_ibus.h" |
23 | 22 |
24 namespace chromeos { | 23 namespace chromeos { |
25 namespace input_method { | 24 namespace input_method { |
26 namespace { | 25 namespace { |
27 | 26 |
28 bool IsActive(const std::string& input_method_id, | 27 bool IsActive(const std::string& input_method_id, |
29 const InputMethodDescriptors* descriptors) { | 28 const InputMethodDescriptors* descriptors) { |
30 for (size_t i = 0; i < descriptors->size(); ++i) { | 29 for (size_t i = 0; i < descriptors->size(); ++i) { |
31 if (descriptors->at(i).id() == input_method_id) { | 30 if (descriptors->at(i).id() == input_method_id) { |
32 return true; | 31 return true; |
33 } | 32 } |
34 } | 33 } |
35 return false; | 34 return false; |
36 } | 35 } |
37 | 36 |
37 ibus::IBusPanelService* GetIBusPanelService() { | |
satorux1
2012/11/30 05:37:34
function comment is missing. Can it return NULL?
Seigo Nonaka
2012/12/01 16:22:42
Done, and add NULL check for everywhere this funct
| |
38 return DBusThreadManager::Get()->GetIBusPanelService(); | |
39 } | |
40 | |
41 // Returns a ui::InputMethodIBus object which is associated with the root | |
42 // window. Returns NULL if the Ash shell has already been destructed. | |
43 static ui::InputMethodIBus* GetChromeInputMethod() { | |
44 if (!ash::Shell::HasInstance()) | |
45 return NULL; | |
46 aura::Window* root_window = ash::Shell::GetPrimaryRootWindow(); | |
47 if (!root_window) | |
48 return NULL; | |
49 return static_cast<ui::InputMethodIBus*>(root_window->GetProperty( | |
50 aura::client::kRootWindowInputMethodKey)); | |
51 } | |
52 | |
38 } // namespace | 53 } // namespace |
39 | 54 |
55 // A class for customizing the behavior of ui::InputMethodIBus for Chrome OS. | |
56 class IBusChromeOSClientImpl : public ui::internal::IBusClient { | |
57 public: | |
58 explicit IBusChromeOSClientImpl(IBusUiController* ui) : ui_(ui) {} | |
59 | |
60 // ui::IBusClient override. | |
61 virtual InputMethodType GetInputMethodType() OVERRIDE { | |
62 const std::string current_input_method_id = GetCurrentInputMethodId(); | |
63 return InputMethodUtil::IsKeyboardLayout(current_input_method_id) ? | |
64 INPUT_METHOD_XKB_LAYOUT : INPUT_METHOD_NORMAL; | |
65 } | |
66 | |
67 virtual void SetCursorLocation(const gfx::Rect& cursor_location, | |
68 const gfx::Rect& composition_head) OVERRIDE { | |
69 if (!ui_) | |
70 return; | |
71 // We don't have to call ibus_input_context_set_cursor_location() on | |
72 // Chrome OS because the candidate window for IBus is integrated with | |
73 // Chrome. | |
74 ui_->SetCursorLocation(cursor_location, composition_head); | |
75 } | |
76 | |
77 void set_ui(IBusUiController* ui) { | |
78 ui_ = ui; | |
79 } | |
80 | |
81 private: | |
82 std::string GetCurrentInputMethodId() { | |
satorux1
2012/11/30 05:37:34
function comment is missing.
Seigo Nonaka
2012/12/01 16:22:42
Sure, but the user of this function is only GetInp
| |
83 InputMethodManager* manager = InputMethodManager::GetInstance(); | |
84 return manager->GetCurrentInputMethod().id(); | |
85 } | |
86 | |
87 IBusUiController* ui_; | |
88 }; | |
satorux1
2012/11/30 05:37:34
add DISALLOW_COPY_AND_ASSIGN?
Seigo Nonaka
2012/12/01 16:22:42
Done.
| |
89 | |
40 InputMethodLookupTable::InputMethodLookupTable() | 90 InputMethodLookupTable::InputMethodLookupTable() |
41 : visible(false), | 91 : visible(false), |
42 cursor_absolute_index(0), | 92 cursor_absolute_index(0), |
43 page_size(0), | 93 page_size(0), |
44 orientation(kHorizontal) { | 94 orientation(kHorizontal) { |
45 } | 95 } |
46 | 96 |
47 InputMethodLookupTable::~InputMethodLookupTable() { | 97 InputMethodLookupTable::~InputMethodLookupTable() { |
48 } | 98 } |
49 | 99 |
50 std::string InputMethodLookupTable::ToString() const { | 100 std::string InputMethodLookupTable::ToString() const { |
51 std::stringstream stream; | 101 std::stringstream stream; |
52 stream << "visible: " << visible << "\n"; | 102 stream << "visible: " << visible << "\n"; |
53 stream << "cursor_absolute_index: " << cursor_absolute_index << "\n"; | 103 stream << "cursor_absolute_index: " << cursor_absolute_index << "\n"; |
54 stream << "page_size: " << page_size << "\n"; | 104 stream << "page_size: " << page_size << "\n"; |
55 stream << "orientation: " << orientation << "\n"; | 105 stream << "orientation: " << orientation << "\n"; |
56 stream << "candidates:"; | 106 stream << "candidates:"; |
57 for (size_t i = 0; i < candidates.size(); ++i) { | 107 for (size_t i = 0; i < candidates.size(); ++i) { |
58 stream << " [" << candidates[i] << "]"; | 108 stream << " [" << candidates[i] << "]"; |
59 } | 109 } |
60 stream << "\nlabels:"; | 110 stream << "\nlabels:"; |
61 for (size_t i = 0; i < labels.size(); ++i) { | 111 for (size_t i = 0; i < labels.size(); ++i) { |
62 stream << " [" << labels[i] << "]"; | 112 stream << " [" << labels[i] << "]"; |
63 } | 113 } |
64 return stream.str(); | 114 return stream.str(); |
65 } | 115 } |
66 | 116 |
67 #if defined(HAVE_IBUS) | |
68 | |
69 // Returns an string representation of |table| for debugging. | |
70 std::string IBusLookupTableToString(IBusLookupTable* table) { | |
71 std::stringstream stream; | |
72 stream << "page_size: " << table->page_size << "\n"; | |
73 stream << "cursor_pos: " << table->cursor_pos << "\n"; | |
74 stream << "cursor_visible: " << table->cursor_visible << "\n"; | |
75 stream << "round: " << table->round << "\n"; | |
76 stream << "orientation: " << table->orientation << "\n"; | |
77 stream << "candidates:"; | |
78 for (int i = 0; ; i++) { | |
79 IBusText *text = ibus_lookup_table_get_candidate(table, i); | |
80 if (!text) { | |
81 break; | |
82 } | |
83 stream << " " << text->text; | |
84 } | |
85 return stream.str(); | |
86 } | |
87 | |
88 // The real implementation of the IBusUiController. | 117 // The real implementation of the IBusUiController. |
89 class IBusUiControllerImpl : public IBusUiController { | 118 IBusUiController::IBusUiController() { |
90 public: | 119 ui::InputMethodIBus* input_method = GetChromeInputMethod(); |
91 IBusUiControllerImpl() | 120 DCHECK(input_method); |
92 : ibus_(NULL), | 121 input_method->set_ibus_client(scoped_ptr<ui::internal::IBusClient>( |
93 ibus_panel_service_(NULL) { | 122 new IBusChromeOSClientImpl(this)).Pass()); |
94 ui::InputMethodIBus* input_method = GetChromeInputMethod(); | |
95 DCHECK(input_method); | |
96 input_method->set_ibus_client(scoped_ptr<ui::internal::IBusClient>( | |
97 new IBusChromeOSClientImpl(this)).Pass()); | |
98 } | |
99 | |
100 ~IBusUiControllerImpl() { | |
101 ui::InputMethodIBus* input_method = GetChromeInputMethod(); | |
102 if (input_method) { | |
103 ui::internal::IBusClient* client = input_method->ibus_client(); | |
104 // We assume that no objects other than |this| set an IBus client. | |
105 DCHECK(client); | |
106 static_cast<IBusChromeOSClientImpl*>(client)->set_ui(NULL); | |
107 } | |
108 // ibus_panel_service_ depends on ibus_, thus unref it first. | |
109 if (ibus_panel_service_) { | |
110 DisconnectPanelServiceSignals(); | |
111 g_object_unref(ibus_panel_service_); | |
112 } | |
113 if (ibus_) { | |
114 DisconnectIBusSignals(); | |
115 g_object_unref(ibus_); | |
116 } | |
117 } | |
118 | |
119 // Creates IBusBus object if it's not created yet, and tries to connect to | |
120 // ibus-daemon. Returns true if IBusBus is successfully connected to the | |
121 // daemon. | |
122 bool ConnectToIBus() { | |
123 if (ibus_) { | |
124 return true; | |
125 } | |
126 ibus_init(); | |
127 ibus_ = ibus_bus_new(); | |
128 CHECK(ibus_) << "ibus_bus_new() failed. Out of memory?"; | |
129 | |
130 bool result = false; | |
131 // Check the IBus connection status. | |
132 if (ibus_bus_is_connected(ibus_)) { | |
133 DVLOG(1) << "ibus_bus_is_connected(). IBus connection is ready."; | |
134 FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true)); | |
135 result = true; | |
136 } | |
137 | |
138 // Start listening the gobject signals regardless of the bus connection | |
139 // status. | |
140 ConnectIBusSignals(); | |
141 return result; | |
142 } | |
143 | |
144 // Creates IBusPanelService object if |ibus_| is already connected. | |
145 bool MaybeRestorePanelService() { | |
146 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
147 return false; | |
148 } | |
149 | |
150 if (ibus_panel_service_) { | |
151 DVLOG(1) << "IBusPanelService is already available. Remove it first."; | |
152 g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, NULL); | |
153 g_object_unref(ibus_panel_service_); | |
154 ibus_panel_service_ = NULL; | |
155 } | |
156 | |
157 // Create an IBusPanelService object. | |
158 GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_); | |
159 if (!ibus_connection) { | |
160 DVLOG(1) << "ibus_bus_get_connection() failed"; | |
161 return false; | |
162 } | |
163 ibus_panel_service_ = ibus_panel_service_new(ibus_connection); | |
164 if (!ibus_panel_service_) { | |
165 DVLOG(1) << "ibus_chromeos_panel_service_new() failed"; | |
166 return false; | |
167 } | |
168 ConnectPanelServiceSignals(); | |
169 g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, ibus_panel_service_); | |
170 DVLOG(1) << "IBusPanelService object is successfully (re-)created."; | |
171 | |
172 // Request the well-known name *asynchronously*. | |
173 ibus_bus_request_name_async(ibus_, | |
174 IBUS_SERVICE_PANEL, | |
175 0 /* flags */, | |
176 -1 /* timeout */, | |
177 NULL /* cancellable */, | |
178 RequestNameCallback, | |
179 g_object_ref(ibus_)); | |
180 return true; | |
181 } | |
182 | |
183 // IBusUiController override. | |
184 virtual void NotifyCandidateClicked(int index, | |
185 int button, | |
186 int flags) OVERRIDE { | |
187 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
188 DVLOG(1) << "NotifyCandidateClicked: bus is not connected."; | |
189 return; | |
190 } | |
191 if (!ibus_panel_service_) { | |
192 DVLOG(1) << "NotifyCandidateClicked: panel service is not available."; | |
193 return; | |
194 } | |
195 | |
196 /* Send a D-Bus signal to ibus-daemon *asynchronously*. */ | |
197 ibus_panel_service_candidate_clicked(ibus_panel_service_, | |
198 index, | |
199 button, | |
200 flags); | |
201 } | |
202 | |
203 // IBusUiController override. | |
204 virtual void NotifyCursorUp() OVERRIDE { | |
205 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
206 DVLOG(1) << "NotifyCursorUp: bus is not connected."; | |
207 return; | |
208 } | |
209 if (!ibus_panel_service_) { | |
210 DVLOG(1) << "NotifyCursorUp: panel service is not available."; | |
211 return; | |
212 } | |
213 | |
214 /* Send a D-Bus signal to ibus-daemon *asynchronously*. */ | |
215 ibus_panel_service_cursor_up(ibus_panel_service_); | |
216 } | |
217 | |
218 // IBusUiController override. | |
219 virtual void NotifyCursorDown() OVERRIDE { | |
220 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
221 DVLOG(1) << "NotifyCursorDown: bus is not connected."; | |
222 return; | |
223 } | |
224 if (!ibus_panel_service_) { | |
225 DVLOG(1) << "NotifyCursorDown: panel service is not available."; | |
226 return; | |
227 } | |
228 /* Send a D-Bus signal to ibus-daemon *asynchronously*. */ | |
229 ibus_panel_service_cursor_down(ibus_panel_service_); | |
230 } | |
231 | |
232 // IBusUiController override. | |
233 virtual void NotifyPageUp() OVERRIDE { | |
234 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
235 DVLOG(1) << "NotifyPageUp: bus is not connected."; | |
236 return; | |
237 } | |
238 if (!ibus_panel_service_) { | |
239 DVLOG(1) << "NotifyPageUp: panel service is not available."; | |
240 return; | |
241 } | |
242 | |
243 /* Send a D-Bus signal to ibus-daemon *asynchronously*. */ | |
244 ibus_panel_service_page_up(ibus_panel_service_); | |
245 } | |
246 | |
247 // IBusUiController override. | |
248 virtual void NotifyPageDown() OVERRIDE { | |
249 if (!ibus_ || !ibus_bus_is_connected(ibus_)) { | |
250 DVLOG(1) << "NotifyPageDown: bus is not connected."; | |
251 return; | |
252 } | |
253 if (!ibus_panel_service_) { | |
254 DVLOG(1) << "NotifyPageDown: panel service is not available."; | |
255 return; | |
256 } | |
257 | |
258 /* Send a D-Bus signal to ibus-daemon *asynchronously*. */ | |
259 ibus_panel_service_page_down(ibus_panel_service_); | |
260 } | |
261 | |
262 // IBusUiController override. | |
263 virtual void Connect() OVERRIDE { | |
264 // It's totally fine if ConnectToIBus() fails here, as we'll get | |
265 // "connected" gobject signal once the connection becomes ready. | |
266 if (ConnectToIBus()) | |
267 MaybeRestorePanelService(); | |
268 } | |
269 | |
270 // IBusUiController override. | |
271 virtual void AddObserver(Observer* observer) OVERRIDE { | |
272 observers_.AddObserver(observer); | |
273 } | |
274 | |
275 // IBusUiController override. | |
276 virtual void RemoveObserver(Observer* observer) OVERRIDE { | |
277 observers_.RemoveObserver(observer); | |
278 } | |
279 | |
280 private: | |
281 // A class for customizing the behavior of ui::InputMethodIBus for Chrome OS. | |
282 class IBusChromeOSClientImpl : public ui::internal::IBusClient { | |
283 public: | |
284 explicit IBusChromeOSClientImpl(IBusUiControllerImpl* ui) | |
285 : ui_(ui) { | |
286 } | |
287 | |
288 // ui::IBusClient override. | |
289 virtual InputMethodType GetInputMethodType() OVERRIDE { | |
290 const std::string current_input_method_id = GetCurrentInputMethodId(); | |
291 return InputMethodUtil::IsKeyboardLayout(current_input_method_id) ? | |
292 INPUT_METHOD_XKB_LAYOUT : INPUT_METHOD_NORMAL; | |
293 } | |
294 | |
295 virtual void SetCursorLocation(const gfx::Rect& cursor_location, | |
296 const gfx::Rect& composition_head) OVERRIDE { | |
297 if (!ui_) | |
298 return; | |
299 // We don't have to call ibus_input_context_set_cursor_location() on | |
300 // Chrome OS because the candidate window for IBus is integrated with | |
301 // Chrome. | |
302 ui_->SetCursorLocation(NULL, cursor_location, composition_head); | |
303 } | |
304 | |
305 void set_ui(IBusUiControllerImpl* ui) { | |
306 ui_ = ui; | |
307 } | |
308 | |
309 private: | |
310 std::string GetCurrentInputMethodId() { | |
311 InputMethodManager* manager = InputMethodManager::GetInstance(); | |
312 return manager->GetCurrentInputMethod().id(); | |
313 } | |
314 | |
315 IBusUiControllerImpl* ui_; | |
316 }; | |
317 | |
318 // Returns a ui::InputMethodIBus object which is associated with the root | |
319 // window. Returns NULL if the Ash shell has already been destructed. | |
320 static ui::InputMethodIBus* GetChromeInputMethod() { | |
321 if (!ash::Shell::HasInstance()) | |
322 return NULL; | |
323 aura::Window* root_window = ash::Shell::GetPrimaryRootWindow(); | |
324 if (!root_window) | |
325 return NULL; | |
326 return static_cast<ui::InputMethodIBus*>(root_window->GetProperty( | |
327 aura::client::kRootWindowInputMethodKey)); | |
328 } | |
329 | |
330 // Functions that end with Thunk are used to deal with glib callbacks. | |
331 // | |
332 // Note that we cannot use CHROMEG_CALLBACK_0() here as we'll define | |
333 // IBusBusConnected() inline. If we are to define the function outside | |
334 // of the class definition, we should use CHROMEG_CALLBACK_0() here. | |
335 // | |
336 // CHROMEG_CALLBACK_0(Impl, | |
337 // void, IBusBusConnected, IBusBus*); | |
338 static void IBusBusConnectedThunk(IBusBus* sender, gpointer userdata) { | |
339 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
340 ->IBusBusConnected(sender); | |
341 } | |
342 static void IBusBusDisconnectedThunk(IBusBus* sender, gpointer userdata) { | |
343 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
344 ->IBusBusDisconnected(sender); | |
345 } | |
346 static void HideAuxiliaryTextThunk(IBusPanelService* sender, | |
347 gpointer userdata) { | |
348 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
349 ->HideAuxiliaryText(sender); | |
350 } | |
351 static void HideLookupTableThunk(IBusPanelService* sender, | |
352 gpointer userdata) { | |
353 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
354 ->HideLookupTable(sender); | |
355 } | |
356 static void UpdateAuxiliaryTextThunk(IBusPanelService* sender, | |
357 IBusText* text, gboolean visible, | |
358 gpointer userdata) { | |
359 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
360 ->UpdateAuxiliaryText(sender, text, visible); | |
361 } | |
362 static void SetCursorLocationThunk(IBusPanelService* sender, | |
363 gint x, gint y, gint width, gint height, | |
364 gpointer userdata) { | |
365 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
366 ->SetCursorLocation(sender, gfx::Rect(x, y, width, height), | |
367 gfx::Rect()); | |
368 } | |
369 static void UpdateLookupTableThunk(IBusPanelService* sender, | |
370 IBusLookupTable* table, gboolean visible, | |
371 gpointer userdata) { | |
372 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
373 ->UpdateLookupTable(sender, table, visible); | |
374 } | |
375 static void UpdatePreeditTextThunk(IBusPanelService* sender, | |
376 IBusText* text, | |
377 guint cursor_pos, | |
378 gboolean visible, | |
379 gpointer userdata) { | |
380 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
381 ->UpdatePreeditText(sender, text, cursor_pos, visible); | |
382 } | |
383 static void HidePreeditTextThunk(IBusPanelService* sender, | |
384 gpointer userdata) { | |
385 return reinterpret_cast<IBusUiControllerImpl*>(userdata) | |
386 ->HidePreeditText(sender); | |
387 } | |
388 | |
389 | |
390 // Installs gobject signal handlers to |ibus_|. | |
391 void ConnectIBusSignals() { | |
392 if (!ibus_) { | |
393 return; | |
394 } | |
395 g_signal_connect(ibus_, | |
396 "connected", | |
397 G_CALLBACK(IBusBusConnectedThunk), | |
398 this); | |
399 g_signal_connect(ibus_, | |
400 "disconnected", | |
401 G_CALLBACK(IBusBusDisconnectedThunk), | |
402 this); | |
403 } | |
404 | |
405 // Removes gobject signal handlers from |ibus_|. | |
406 void DisconnectIBusSignals() { | |
407 if (!ibus_) { | |
408 return; | |
409 } | |
410 g_signal_handlers_disconnect_by_func( | |
411 ibus_, | |
412 reinterpret_cast<gpointer>(G_CALLBACK(IBusBusConnectedThunk)), | |
413 this); | |
414 g_signal_handlers_disconnect_by_func( | |
415 ibus_, | |
416 reinterpret_cast<gpointer>(G_CALLBACK(IBusBusDisconnectedThunk)), | |
417 this); | |
418 } | |
419 | |
420 // Installs gobject signal handlers to |ibus_panel_service_|. | |
421 void ConnectPanelServiceSignals() { | |
422 if (!ibus_panel_service_) { | |
423 return; | |
424 } | |
425 g_signal_connect(ibus_panel_service_, | |
426 "hide-auxiliary-text", | |
427 G_CALLBACK(HideAuxiliaryTextThunk), | |
428 this); | |
429 g_signal_connect(ibus_panel_service_, | |
430 "hide-lookup-table", | |
431 G_CALLBACK(HideLookupTableThunk), | |
432 this); | |
433 g_signal_connect(ibus_panel_service_, | |
434 "update-auxiliary-text", | |
435 G_CALLBACK(UpdateAuxiliaryTextThunk), | |
436 this); | |
437 g_signal_connect(ibus_panel_service_, | |
438 "set-cursor-location", | |
439 G_CALLBACK(SetCursorLocationThunk), | |
440 this); | |
441 g_signal_connect(ibus_panel_service_, | |
442 "update-lookup-table", | |
443 G_CALLBACK(UpdateLookupTableThunk), | |
444 this); | |
445 g_signal_connect(ibus_panel_service_, | |
446 "update-preedit-text", | |
447 G_CALLBACK(UpdatePreeditTextThunk), | |
448 this); | |
449 g_signal_connect(ibus_panel_service_, | |
450 "hide-preedit-text", | |
451 G_CALLBACK(HidePreeditTextThunk), | |
452 this); | |
453 } | |
454 | |
455 // Removes gobject signal handlers from |ibus_panel_service_|. | |
456 void DisconnectPanelServiceSignals() { | |
457 if (!ibus_panel_service_) { | |
458 return; | |
459 } | |
460 g_signal_handlers_disconnect_by_func( | |
461 ibus_panel_service_, | |
462 reinterpret_cast<gpointer>(HideAuxiliaryTextThunk), | |
463 this); | |
464 g_signal_handlers_disconnect_by_func( | |
465 ibus_panel_service_, | |
466 reinterpret_cast<gpointer>(HideLookupTableThunk), | |
467 this); | |
468 g_signal_handlers_disconnect_by_func( | |
469 ibus_panel_service_, | |
470 reinterpret_cast<gpointer>(UpdateAuxiliaryTextThunk), | |
471 this); | |
472 g_signal_handlers_disconnect_by_func( | |
473 ibus_panel_service_, | |
474 reinterpret_cast<gpointer>(SetCursorLocationThunk), | |
475 this); | |
476 g_signal_handlers_disconnect_by_func( | |
477 ibus_panel_service_, | |
478 reinterpret_cast<gpointer>(UpdateLookupTableThunk), | |
479 this); | |
480 g_signal_handlers_disconnect_by_func( | |
481 ibus_panel_service_, | |
482 reinterpret_cast<gpointer>(UpdatePreeditTextThunk), | |
483 this); | |
484 g_signal_handlers_disconnect_by_func( | |
485 ibus_panel_service_, | |
486 reinterpret_cast<gpointer>(HidePreeditTextThunk), | |
487 this); | |
488 } | |
489 | |
490 // Handles "connected" signal from ibus-daemon. | |
491 void IBusBusConnected(IBusBus* bus) { | |
492 DVLOG(1) << "IBus connection is recovered."; | |
493 if (!MaybeRestorePanelService()) { | |
494 DVLOG(1) << "MaybeRestorePanelService() failed"; | |
495 return; | |
496 } | |
497 | |
498 FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true)); | |
499 } | |
500 | |
501 // Handles "disconnected" signal from ibus-daemon. Releases the | |
502 // |ibus_panel_service_| object since the connection the service has will be | |
503 // destroyed soon. | |
504 void IBusBusDisconnected(IBusBus* bus) { | |
505 DVLOG(1) << "IBus connection is terminated."; | |
506 if (ibus_panel_service_) { | |
507 DisconnectPanelServiceSignals(); | |
508 // Since the connection being disconnected is currently mutex-locked, | |
509 // we can't unref the panel service object directly here. Because when the | |
510 // service object is deleted, the connection, which the service also has, | |
511 // will be locked again. To avoid deadlock, we use g_idle_add instead. | |
512 g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, NULL); | |
513 g_idle_add(ReleasePanelService, ibus_panel_service_); | |
514 ibus_panel_service_ = NULL; | |
515 } | |
516 | |
517 FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(false)); | |
518 } | |
519 | |
520 // Releases |ibus_panel_service_|. See the comment above. | |
521 static gboolean ReleasePanelService(gpointer user_data) { | |
522 g_return_val_if_fail(IBUS_IS_PANEL_SERVICE(user_data), FALSE); | |
523 g_object_unref(user_data); | |
524 return FALSE; // stop the idle timer. | |
525 } | |
526 | |
527 // Handles IBusPanelService's |HideAuxiliaryText| method call. | |
528 void HideAuxiliaryText(IBusPanelService* panel) { | |
529 FOR_EACH_OBSERVER(Observer, observers_, OnHideAuxiliaryText()); | |
530 } | |
531 | |
532 // Handles IBusPanelService's |HideLookupTable| method call. | |
533 void HideLookupTable(IBusPanelService *panel) { | |
534 FOR_EACH_OBSERVER(Observer, observers_, OnHideLookupTable()); | |
535 } | |
536 | |
537 // Handles IBusPanelService's |UpdateAuxiliaryText| method call. | |
538 void UpdateAuxiliaryText(IBusPanelService* panel, | |
539 IBusText* text, | |
540 gboolean visible) { | |
541 g_return_if_fail(text); | |
542 g_return_if_fail(text->text); | |
543 // Convert IBusText to a std::string. IBusText is an attributed text, | |
544 const std::string simple_text = text->text; | |
545 FOR_EACH_OBSERVER(Observer, observers_, | |
546 OnUpdateAuxiliaryText(simple_text, visible == TRUE)); | |
547 } | |
548 | |
549 // Handles IBusPanelService's |SetCursorLocation| method call. | |
550 void SetCursorLocation(IBusPanelService *panel, | |
551 const gfx::Rect& cursor_location, | |
552 const gfx::Rect& composition_head) { | |
553 // Note: |panel| might be NULL. See IBusChromeOSClientImpl above. | |
554 FOR_EACH_OBSERVER(Observer, observers_, | |
555 OnSetCursorLocation(cursor_location, composition_head)); | |
556 } | |
557 | |
558 // Handles IBusPanelService's |UpdatePreeditText| method call. | |
559 void UpdatePreeditText(IBusPanelService *panel, | |
560 IBusText *text, | |
561 guint cursor_pos, | |
562 gboolean visible) { | |
563 FOR_EACH_OBSERVER(Observer, observers_, | |
564 OnUpdatePreeditText(text->text, cursor_pos, visible)); | |
565 } | |
566 | |
567 // Handles IBusPanelService's |UpdatePreeditText| method call. | |
568 void HidePreeditText(IBusPanelService *panel) { | |
569 FOR_EACH_OBSERVER(Observer, observers_, OnHidePreeditText()); | |
570 } | |
571 | |
572 // Handles IBusPanelService's |UpdateLookupTable| method call. | |
573 void UpdateLookupTable(IBusPanelService *panel, | |
574 IBusLookupTable *table, | |
575 gboolean visible) { | |
576 g_return_if_fail(table); | |
577 | |
578 InputMethodLookupTable lookup_table; | |
579 lookup_table.visible = (visible == TRUE); | |
580 | |
581 // Copy the orientation information. | |
582 const gint orientation = ibus_lookup_table_get_orientation(table); | |
583 if (orientation == IBUS_ORIENTATION_VERTICAL) { | |
584 lookup_table.orientation = InputMethodLookupTable::kVertical; | |
585 } else if (orientation == IBUS_ORIENTATION_HORIZONTAL) { | |
586 lookup_table.orientation = InputMethodLookupTable::kHorizontal; | |
587 } | |
588 | |
589 // The function ibus_serializable_get_attachment had been changed | |
590 // to use GVariant by the commit | |
591 // https://github.com/ibus/ibus/commit/ac9dfac13cef34288440a2ecdf067cd827fb2 f8f | |
592 GVariant* variant = ibus_serializable_get_attachment( | |
593 IBUS_SERIALIZABLE(table), "show_window_at_composition"); | |
594 if (variant) | |
595 lookup_table.show_at_composition_head = !!g_variant_get_boolean(variant); | |
596 else | |
597 lookup_table.show_at_composition_head = false; | |
598 | |
599 // Copy candidates and annotations to |lookup_table|. | |
600 for (int i = 0; ; i++) { | |
601 IBusText *text = ibus_lookup_table_get_candidate(table, i); | |
602 if (!text) | |
603 break; | |
604 lookup_table.candidates.push_back(text->text); | |
605 | |
606 IBusText *label = ibus_lookup_table_get_label(table, i); | |
607 if (label) | |
608 lookup_table.labels.push_back(label->text); | |
609 | |
610 GVariant* annotation_variant = | |
611 ibus_serializable_get_attachment(IBUS_SERIALIZABLE(text), | |
612 "annotation"); | |
613 lookup_table.annotations.push_back(""); | |
614 if (annotation_variant) { | |
615 const gchar* annotation = | |
616 g_variant_get_string(annotation_variant, NULL); | |
617 if (annotation) | |
618 lookup_table.annotations[i] = annotation; | |
619 } | |
620 | |
621 GVariant* description_title_variant = | |
622 ibus_serializable_get_attachment(IBUS_SERIALIZABLE(text), | |
623 "description_title"); | |
624 InputMethodLookupTable::Description description; | |
625 if (description_title_variant) { | |
626 const gchar* description_title = | |
627 g_variant_get_string(description_title_variant, NULL); | |
628 if (description_title) | |
629 description.title = description_title; | |
630 | |
631 } | |
632 | |
633 GVariant* description_body_variant = | |
634 ibus_serializable_get_attachment(IBUS_SERIALIZABLE(text), | |
635 "description_body"); | |
636 if (description_body_variant) { | |
637 const gchar* description_body = | |
638 g_variant_get_string(description_body_variant, NULL); | |
639 if (description_body) | |
640 description.body = description_body; | |
641 } | |
642 lookup_table.descriptions.push_back(description); | |
643 } | |
644 DCHECK_EQ(lookup_table.candidates.size(), | |
645 lookup_table.annotations.size()); | |
646 | |
647 lookup_table.cursor_absolute_index = | |
648 ibus_lookup_table_get_cursor_pos(table); | |
649 lookup_table.page_size = ibus_lookup_table_get_page_size(table); | |
650 // Ensure that the page_size is non-zero to avoid div-by-zero error. | |
651 if (lookup_table.page_size <= 0) { | |
652 DVLOG(1) << "Invalid page size: " << lookup_table.page_size; | |
653 lookup_table.page_size = 1; | |
654 } | |
655 | |
656 FOR_EACH_OBSERVER(Observer, observers_, | |
657 OnUpdateLookupTable(lookup_table)); | |
658 } | |
659 | |
660 // A callback function that will be called when ibus_bus_request_name_async() | |
661 // request is finished. | |
662 static void RequestNameCallback(GObject* source_object, | |
663 GAsyncResult* res, | |
664 gpointer user_data) { | |
665 IBusBus* bus = IBUS_BUS(user_data); | |
666 g_return_if_fail(bus); | |
667 | |
668 GError* error = NULL; | |
669 const guint service_id = | |
670 ibus_bus_request_name_async_finish(bus, res, &error); | |
671 | |
672 if (!service_id) { | |
673 std::string message = "(unknown error)"; | |
674 if (error && error->message) { | |
675 message = error->message; | |
676 } | |
677 DVLOG(1) << "Failed to register the panel service: " << message; | |
678 } else { | |
679 DVLOG(1) << "The panel service is registered: ID=" << service_id; | |
680 } | |
681 | |
682 if (error) { | |
683 g_error_free(error); | |
684 } | |
685 g_object_unref(bus); | |
686 } | |
687 | |
688 IBusBus* ibus_; | |
689 IBusPanelService* ibus_panel_service_; | |
690 ObserverList<Observer> observers_; | |
691 }; | |
692 #endif // defined(HAVE_IBUS) | |
693 | |
694 // The stub implementation is used if IBus is not present. | |
695 // | |
696 // Note that this class is intentionally built even if HAVE_IBUS is | |
697 // defined so that we can easily tell build breakage when we change the | |
698 // IBusUiControllerImpl but forget to update the stub implementation. | |
699 class IBusUiControllerStubImpl : public IBusUiController { | |
700 public: | |
701 IBusUiControllerStubImpl() { | |
702 } | |
703 | |
704 virtual void Connect() { | |
705 } | |
706 | |
707 virtual void AddObserver(Observer* observer) { | |
708 } | |
709 | |
710 virtual void RemoveObserver(Observer* observer) { | |
711 } | |
712 | |
713 virtual void NotifyCandidateClicked(int index, int button, int flags) { | |
714 } | |
715 | |
716 virtual void NotifyCursorUp() { | |
717 } | |
718 | |
719 virtual void NotifyCursorDown() { | |
720 } | |
721 | |
722 virtual void NotifyPageUp() { | |
723 } | |
724 | |
725 virtual void NotifyPageDown() { | |
726 } | |
727 }; | |
728 | |
729 IBusUiController* IBusUiController::Create() { | |
730 #if defined(HAVE_IBUS) | |
731 return new IBusUiControllerImpl; | |
732 #else | |
733 return new IBusUiControllerStubImpl; | |
734 #endif | |
735 } | 123 } |
736 | 124 |
737 IBusUiController::~IBusUiController() { | 125 IBusUiController::~IBusUiController() { |
126 ui::InputMethodIBus* input_method = GetChromeInputMethod(); | |
127 if (input_method) { | |
128 ui::internal::IBusClient* client = input_method->ibus_client(); | |
129 // We assume that no objects other than |this| set an IBus client. | |
130 DCHECK(client); | |
131 static_cast<IBusChromeOSClientImpl*>(client)->set_ui(NULL); | |
132 } | |
133 } | |
134 | |
135 void IBusUiController::NotifyCandidateClicked(int index, int button, | |
136 int flags) { | |
137 GetIBusPanelService()->CandidateClicked( | |
138 index, | |
139 static_cast<ibus::IBusMouseButton>(button), | |
140 flags); | |
141 } | |
142 | |
143 void IBusUiController::NotifyCursorUp() { | |
144 GetIBusPanelService()->CursorUp(); | |
145 } | |
146 | |
147 void IBusUiController::NotifyCursorDown() { | |
148 GetIBusPanelService()->CursorDown(); | |
149 } | |
150 | |
151 void IBusUiController::NotifyPageUp() { | |
152 GetIBusPanelService()->PageUp(); | |
153 } | |
154 | |
155 void IBusUiController::NotifyPageDown() { | |
156 GetIBusPanelService()->PageDown(); | |
157 } | |
158 | |
159 void IBusUiController::AddObserver(Observer* observer) { | |
160 observers_.AddObserver(observer); | |
161 } | |
162 | |
163 void IBusUiController::RemoveObserver(Observer* observer) { | |
164 observers_.RemoveObserver(observer); | |
165 } | |
166 | |
167 void IBusUiController::HideAuxiliaryText() { | |
168 FOR_EACH_OBSERVER(Observer, observers_, OnHideAuxiliaryText()); | |
169 } | |
170 | |
171 void IBusUiController::HideLookupTable() { | |
172 FOR_EACH_OBSERVER(Observer, observers_, OnHideLookupTable()); | |
173 } | |
174 | |
175 void IBusUiController::UpdateAuxiliaryText(const std::string& text, | |
176 bool visible) { | |
177 FOR_EACH_OBSERVER(Observer, observers_, | |
178 OnUpdateAuxiliaryText(text, visible)); | |
179 } | |
180 | |
181 void IBusUiController::SetCursorLocation(const gfx::Rect& cursor_location, | |
182 const gfx::Rect& composition_head) { | |
183 FOR_EACH_OBSERVER(Observer, observers_, | |
184 OnSetCursorLocation(cursor_location, composition_head)); | |
185 } | |
186 | |
187 void IBusUiController::UpdatePreeditText(const std::string& text, | |
188 uint32 cursor_pos, | |
189 bool visible) { | |
190 FOR_EACH_OBSERVER(Observer, observers_, | |
191 OnUpdatePreeditText(text, cursor_pos, visible)); | |
192 } | |
193 | |
194 void IBusUiController::HidePreeditText() { | |
195 FOR_EACH_OBSERVER(Observer, observers_, OnHidePreeditText()); | |
196 } | |
197 | |
198 void IBusUiController::UpdateLookupTable(const ibus::IBusLookupTable& table, | |
199 bool visible) { | |
200 // TODO(nona): Use ibus::IBusLookupTable instead. | |
201 InputMethodLookupTable lookup_table; | |
202 lookup_table.visible = visible; | |
203 | |
204 // Copy the orientation information. | |
205 if (table.orientation() == | |
206 ibus::IBusLookupTable::IBUS_LOOKUP_TABLE_ORIENTATION_VERTICAL) { | |
207 lookup_table.orientation = InputMethodLookupTable::kVertical; | |
208 } else { | |
209 lookup_table.orientation = InputMethodLookupTable::kHorizontal; | |
210 } | |
211 | |
212 lookup_table.show_at_composition_head = table.show_window_at_composition(); | |
213 | |
214 // Copy candidates and annotations to |lookup_table|. | |
215 for (size_t i = 0; i < table.candidates().size(); ++i) { | |
216 const ibus::IBusLookupTable::Entry& entry = table.candidates()[i]; | |
217 lookup_table.candidates.push_back(entry.value); | |
218 lookup_table.labels.push_back(entry.label); | |
219 lookup_table.annotations.push_back(entry.annotation); | |
220 | |
221 InputMethodLookupTable::Description description; | |
222 description.title = entry.description_title; | |
223 description.body = entry.description_body; | |
224 lookup_table.descriptions.push_back(description); | |
225 } | |
226 | |
227 lookup_table.cursor_absolute_index = table.cursor_position(); | |
228 lookup_table.page_size = table.page_size(); | |
229 // Ensure that the page_size is non-zero to avoid div-by-zero error. | |
230 if (lookup_table.page_size <= 0) { | |
231 DVLOG(1) << "Invalid page size: " << lookup_table.page_size; | |
232 lookup_table.page_size = 1; | |
233 } | |
234 | |
235 FOR_EACH_OBSERVER(Observer, observers_, | |
236 OnUpdateLookupTable(lookup_table)); | |
738 } | 237 } |
739 | 238 |
740 bool IsActiveForTesting(const std::string& input_method_id, | 239 bool IsActiveForTesting(const std::string& input_method_id, |
741 const InputMethodDescriptors* descriptors) { | 240 const InputMethodDescriptors* descriptors) { |
742 return IsActive(input_method_id, descriptors); | 241 return IsActive(input_method_id, descriptors); |
743 } | 242 } |
744 | 243 |
745 } // namespace input_method | 244 } // namespace input_method |
746 } // namespace chromeos | 245 } // namespace chromeos |
OLD | NEW |