Chromium Code Reviews| 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 |