Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(566)

Side by Side Diff: chromeos_language.cc

Issue 521058: Support intra-IME switching. Cros part. (Closed)
Patch Set: addressed issues Created 10 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chromeos_language.h ('k') | load.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. 1 // Copyright (c) 2009 The Chromium OS 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 4
5 #include "chromeos_language.h" 5 #include "chromeos_language.h"
6 6
7 #include <base/logging.h>
8 #include <ibus.h> 7 #include <ibus.h>
8 #include <dbus/dbus-glib-lowlevel.h> // for dbus_g_connection_get_connection.
9 9
10 #include <algorithm> // for std::sort. 10 #include <algorithm> // for std::sort.
11 #include <sstream>
12 #include <stack>
13 #include <utility>
11 14
12 #include "chromeos/dbus/dbus.h" 15 #include "chromeos/dbus/dbus.h"
13 #include "chromeos/glib/object.h" 16 #include "chromeos/glib/object.h"
14 17
15 namespace { 18 namespace {
16 19
17 const char kCandidateWindowService[] = "org.freedesktop.IBus.Panel"; 20 const char kCandidateWindowService[] = "org.freedesktop.IBus.Panel";
18 const char kCandidateWindowObjectPath[] = "/org/chromium/Chrome/LanguageBar"; 21 const char kCandidateWindowObjectPath[] = "/org/chromium/Chrome/LanguageBar";
19 const char kCandidateWindowInterface[] = "org.freedesktop.IBus.Panel"; 22 const char kCandidateWindowInterface[] = "org.freedesktop.IBus.Panel";
20 23
24 // The list of IME property keys that we don't handle.
25 const char* kImePropertyKeysBlacklist[] = {
26 "setup", // menu for showing setup dialog used in anthy and hangul.
27 "chewing_settings_prop", // menu for showing setup dialog used in chewing.
28 "status", // used in m17n.
29 };
30
21 // Copies IME names in |engines| to |out|. 31 // Copies IME names in |engines| to |out|.
22 void AddIMELanguages(const GList* engines, chromeos::InputLanguageList* out) { 32 void AddIMELanguages(const GList* engines, chromeos::InputLanguageList* out) {
23 DCHECK(out); 33 DCHECK(out);
24 for (; engines; engines = g_list_next(engines)) { 34 for (; engines; engines = g_list_next(engines)) {
25 IBusEngineDesc* engine_desc = IBUS_ENGINE_DESC(engines->data); 35 IBusEngineDesc* engine_desc = IBUS_ENGINE_DESC(engines->data);
26 out->push_back(chromeos::InputLanguage( 36 out->push_back(chromeos::InputLanguage(
27 chromeos::LANGUAGE_CATEGORY_IME, 37 chromeos::LANGUAGE_CATEGORY_IME,
28 engine_desc->name, engine_desc->longname, engine_desc->icon)); 38 engine_desc->name, engine_desc->longname, engine_desc->icon));
29 g_object_unref(engine_desc); 39 g_object_unref(engine_desc);
30 } 40 }
31 } 41 }
32 42
33 // Copies XKB layout names in (TBD) to |out|. 43 // Copies XKB layout names in (TBD) to |out|.
34 void AddXKBLayouts(chromeos::InputLanguageList* out) { 44 void AddXKBLayouts(chromeos::InputLanguageList* out) {
35 DCHECK(out); 45 DCHECK(out);
36 // TODO(yusukes): implement this. 46 // TODO(yusukes): implement this.
37 out->push_back(chromeos::InputLanguage( 47 out->push_back(chromeos::InputLanguage(
38 chromeos::LANGUAGE_CATEGORY_XKB, 48 chromeos::LANGUAGE_CATEGORY_XKB,
39 kFallbackXKBId, 49 kFallbackXKBId,
40 kFallbackXKBDisplayName, 50 kFallbackXKBDisplayName,
41 "" /* no icon */)); // mock 51 "" /* no icon */)); // mock
42 } 52 }
43 53
54 // Returns IBusInputContext for |input_context_path|. NULL on errors.
55 IBusInputContext* GetInputContext(
56 const std::string& input_context_path, IBusBus* ibus) {
57 IBusInputContext* context = ibus_input_context_get_input_context(
58 input_context_path.c_str(), ibus_bus_get_connection(ibus));
59 if (!context) {
60 LOG(ERROR) << "IBusInputContext is null: " << input_context_path;
61 }
62 return context;
63 }
64
65 // Returns true if |key| is blacklisted.
66 bool KeyIsBlacklisted(const char* key) {
67 for (size_t i = 0; i < arraysize(kImePropertyKeysBlacklist); ++i) {
68 if (!std::strcmp(key, kImePropertyKeysBlacklist[i])) {
69 return true;
70 }
71 }
72 return false;
73 }
74
75 // Returns true if |prop| has children.
76 bool PropertyHasChildren(IBusProperty* prop) {
77 return prop && prop->sub_props && ibus_prop_list_get(prop->sub_props, 0);
78 }
79
80 // This function is called by and FlattenProperty() and converts IBus
81 // representation of a property, |ibus_prop|, to our own and push_back the
82 // result to |out_prop_list|. This function returns true on success, and
83 // returns false if sanity checks for |ibus_prop| fail.
84 bool ConvertProperty(IBusProperty* ibus_prop,
85 int selection_item_id,
86 chromeos::ImePropertyList* out_prop_list) {
87 DCHECK(ibus_prop);
88 DCHECK(ibus_prop->key);
89 DCHECK(out_prop_list);
90
91 // Sanity checks.
92 const bool has_sub_props = PropertyHasChildren(ibus_prop);
93 if (has_sub_props && (ibus_prop->type != PROP_TYPE_MENU)) {
94 LOG(ERROR) << "The property has sub properties, "
95 << "but the type of the property is not PROP_TYPE_MENU";
96 return false;
97 }
98 if ((!has_sub_props) && (ibus_prop->type == PROP_TYPE_MENU)) {
99 LOG(ERROR) << "The property does not have sub properties, "
100 << "but the type of the property is PROP_TYPE_MENU";
101 return false;
102 }
103 if (ibus_prop->type == PROP_TYPE_SEPARATOR ||
104 ibus_prop->type == PROP_TYPE_MENU) {
105 // This is not an error, but we don't push an item for these types.
106 return true;
107 }
108
109 const bool is_selection_item = (ibus_prop->type == PROP_TYPE_RADIO);
110 selection_item_id
111 = is_selection_item ? selection_item_id : kInvalidSelectionItemId;
112
113 bool is_selection_item_checked = false;
114 if (ibus_prop->state == PROP_STATE_INCONSISTENT) {
115 LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, "
116 << "which is not supported.";
117 } else if ((!is_selection_item) && (ibus_prop->state == PROP_STATE_CHECKED)) {
118 LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is "
119 << "PROP_TYPE_RADIO.";
120 } else {
121 is_selection_item_checked = (ibus_prop->state == PROP_STATE_CHECKED);
122 }
123
124 // TODO(yusukes): Probably it's better to generate our own label from the key?
125 std::string label = (ibus_prop->tooltip ? ibus_prop->tooltip->text : "");
126 if (label.empty()) {
127 // Usually tooltips are more descriptive than labels.
128 label = (ibus_prop->label ? ibus_prop->label->text : "");
129 }
130 if (label.empty()) {
131 // ibus-pinyin has a property whose label and tooltip are empty. Fall back
132 // to the key.
133 label = ibus_prop->key;
134 }
135
136 out_prop_list->push_back(chromeos::ImeProperty(ibus_prop->key,
137 ibus_prop->icon,
138 label,
139 is_selection_item,
140 is_selection_item_checked,
141 selection_item_id));
142 return true;
143 }
144
145 // Converts |ibus_prop| to |out_prop_list|. Please note that |ibus_prop|
146 // may or may not have children. See the comment for FlattenPropertyList
147 // for details. Returns true if no error is found.
148 bool FlattenProperty(
149 IBusProperty* ibus_prop, chromeos::ImePropertyList* out_prop_list) {
150 // TODO(yusukes): Find a good way to write unit tests for this function.
151 DCHECK(ibus_prop);
152 DCHECK(out_prop_list);
153
154 int selection_item_id = -1;
155 std::stack<std::pair<IBusProperty*, int> > prop_stack;
156 prop_stack.push(std::make_pair(ibus_prop, selection_item_id));
157
158 while (!prop_stack.empty()) {
159 IBusProperty* prop = prop_stack.top().first;
160 const int current_selection_item_id = prop_stack.top().second;
161 prop_stack.pop();
162
163 // Filter out unnecessary properties.
164 if (KeyIsBlacklisted(prop->key)) {
165 continue;
166 }
167
168 // Convert |prop| to ImeProperty and push it to |out_prop_list|.
169 if (!ConvertProperty(prop, current_selection_item_id, out_prop_list)) {
170 return false;
171 }
172
173 // Process childrens iteratively (if any). Push all sub properties to the
174 // stack.
175 if (PropertyHasChildren(prop)) {
176 ++selection_item_id;
177 for (int i = 0;; ++i) {
178 IBusProperty* sub_prop = ibus_prop_list_get(prop->sub_props, i);
179 if (!sub_prop) {
180 break;
181 }
182 prop_stack.push(std::make_pair(sub_prop, selection_item_id));
183 }
184 ++selection_item_id;
185 }
186 }
187 std::reverse(out_prop_list->begin(), out_prop_list->end());
188
189 return true;
190 }
191
192 // Converts IBus representation of a property list, |ibus_prop_list| to our
193 // own. This function also flatten the original list (actually it's a tree).
194 // Returns true if no error is found. The conversion to our own type is
195 // necessary since our language switcher in Chrome tree don't (or can't) know
196 // IBus types. Here is an example:
197 //
198 // ======================================================================
199 // Input:
200 //
201 // --- Item-1
202 // |- Item-2
203 // |- SubMenuRoot --- Item-3-1
204 // | |- Item-3-2
205 // | |- Item-3-3
206 // |- Item-4
207 //
208 // (Note: Item-3-X is a selection item since they're on a sub menu.)
209 //
210 // Output:
211 //
212 // Item-1, Item-2, Item-3-1, Item-3-2, Item-3-3, Item-4
213 // (Note: SubMenuRoot does not appear in the output.)
214 // ======================================================================
215 bool FlattenPropertyList(
216 IBusPropList* ibus_prop_list, chromeos::ImePropertyList* out_prop_list) {
217 // TODO(yusukes): Find a good way to write unit tests for this function.
218 DCHECK(ibus_prop_list);
219 DCHECK(out_prop_list);
220
221 IBusProperty* fake_root_prop = ibus_property_new("Dummy.Key",
222 PROP_TYPE_MENU,
223 NULL, /* label */
224 "", /* icon */
225 NULL, /* tooltip */
226 FALSE, /* sensitive */
227 FALSE, /* visible */
228 PROP_STATE_UNCHECKED,
229 ibus_prop_list);
230
231 const bool result
232 = fake_root_prop && FlattenProperty(fake_root_prop, out_prop_list);
233 g_object_unref(fake_root_prop);
234
235 return result;
236 }
237
238 // Debug print functions.
239 const char* PropTypeToString(int prop_type) {
240 switch (static_cast<IBusPropType>(prop_type)) {
241 case PROP_TYPE_NORMAL:
242 return "NORMAL";
243 case PROP_TYPE_TOGGLE:
244 return "TOGGLE";
245 case PROP_TYPE_RADIO:
246 return "RADIO";
247 case PROP_TYPE_MENU:
248 return "MENU";
249 case PROP_TYPE_SEPARATOR:
250 return "SEPARATOR";
251 }
252 return "UNKNOWN";
253 }
254
255 const char* PropStateToString(int prop_state) {
256 switch (static_cast<IBusPropState>(prop_state)) {
257 case PROP_STATE_UNCHECKED:
258 return "UNCHECKED";
259 case PROP_STATE_CHECKED:
260 return "CHECKED";
261 case PROP_STATE_INCONSISTENT:
262 return "INCONSISTENT";
263 }
264 return "UNKNOWN";
265 }
266
267 std::string Spacer(int n) {
268 return std::string(n, ' ');
269 }
270
271 std::string PrintPropList(IBusPropList *prop_list, int tree_level);
272 std::string PrintProp(IBusProperty *prop, int tree_level) {
273 if (!prop) {
274 return "";
275 }
276
277 std::stringstream stream;
278 stream << Spacer(tree_level) << "=========================" << std::endl;
279 stream << Spacer(tree_level) << "key: " << (prop->key ? prop->key : "<none>")
280 << std::endl;
281 stream << Spacer(tree_level) << "icon: "
282 << (prop->icon ? prop->icon : "<none>") << std::endl;
283 stream << Spacer(tree_level) << "label: "
284 << (prop->label ? prop->label->text : "<none>") << std::endl;
285 stream << Spacer(tree_level) << "tooptip: "
286 << (prop->tooltip ? prop->tooltip->text : "<none>") << std::endl;
287 stream << Spacer(tree_level) << "sensitive: "
288 << (prop->sensitive ? "YES" : "NO") << std::endl;
289 stream << Spacer(tree_level) << "visible: " << (prop->visible ? "YES" : "NO")
290 << std::endl;
291 stream << Spacer(tree_level) << "type: " << PropTypeToString(prop->type)
292 << std::endl;
293 stream << Spacer(tree_level) << "state: " << PropStateToString(prop->state)
294 << std::endl;
295 stream << Spacer(tree_level) << "sub_props: "
296 << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl;
297 stream << PrintPropList(prop->sub_props, tree_level + 1);
298 stream << Spacer(tree_level) << "=========================" << std::endl;
299
300 return stream.str();
301 }
302
303 std::string PrintPropList(IBusPropList *prop_list, int tree_level) {
304 if (!prop_list) {
305 return "";
306 }
307
308 std::stringstream stream;
309 for (int i = 0;; ++i) {
310 IBusProperty* prop = ibus_prop_list_get(prop_list, i);
311 if (!prop) {
312 break;
313 }
314 stream << PrintProp(prop, tree_level);
315 }
316 return stream.str();
317 }
318
44 } // namespace 319 } // namespace
45 320
46 namespace chromeos { 321 namespace chromeos {
47 322
48 // A class that holds IBus and DBus connections. 323 // A class that holds IBus and DBus connections.
49 class LanguageStatusConnection { 324 class LanguageStatusConnection {
50 public: 325 public:
51 LanguageStatusConnection(LanguageStatusMonitorFunction monitor_function, 326 LanguageStatusConnection(LanguageStatusMonitorFunctions monitor_functions,
52 void* language_library) 327 void* language_library)
53 : monitor_function_(monitor_function), 328 : monitor_functions_(monitor_functions),
54 language_library_(language_library), 329 language_library_(language_library),
55 ibus_(NULL), 330 ibus_(NULL),
56 ibus_config_(NULL), 331 ibus_config_(NULL),
57 dbus_focus_in_(NULL),
58 dbus_focus_out_(NULL),
59 dbus_state_changed_(NULL),
60 input_context_path_("") { 332 input_context_path_("") {
61 DCHECK(monitor_function_); 333 DCHECK(monitor_functions_.current_language);
334 DCHECK(monitor_functions_.register_ime_properties);
335 DCHECK(monitor_functions_.update_ime_property);
62 DCHECK(language_library_); 336 DCHECK(language_library_);
63 } 337 }
64 338
65 ~LanguageStatusConnection() { 339 ~LanguageStatusConnection() {
66 // Close IBus and DBus connections. 340 // Close IBus and DBus connections.
67 if (ibus_config_) { 341 if (ibus_config_) {
68 g_object_unref(ibus_config_); 342 g_object_unref(ibus_config_);
69 } 343 }
70 if (ibus_) { 344 if (ibus_) {
71 g_object_unref(ibus_); 345 g_object_unref(ibus_);
72 } 346 }
73 if (dbus_focus_in_) {
74 dbus::Disconnect(dbus_focus_in_);
75 }
76 if (dbus_focus_out_) {
77 dbus::Disconnect(dbus_focus_out_);
78 }
79 if (dbus_state_changed_) {
80 dbus::Disconnect(dbus_state_changed_);
81 }
82 } 347 }
83 348
84 // Initializes IBus and DBus connections. 349 // Initializes IBus and DBus connections.
85 bool Init() { 350 bool Init() {
86 // Establish IBus connection between ibus-daemon to retrieve the list of 351 // Establish IBus connection between ibus-daemon to retrieve the list of
87 // available IME engines, change the current IME engine, and so on. 352 // available IME engines, change the current IME engine, and so on.
88 ibus_init(); 353 ibus_init();
89 ibus_ = ibus_bus_new(); 354 ibus_ = ibus_bus_new();
90 355
91 // Check the IBus connection status. 356 // Check the IBus connection status.
(...skipping 13 matching lines...) Expand all
105 return false; 370 return false;
106 } 371 }
107 372
108 // Create the IBus config. 373 // Create the IBus config.
109 ibus_config_ = ibus_config_new(ibus_connection); 374 ibus_config_ = ibus_config_new(ibus_connection);
110 if (!ibus_config_) { 375 if (!ibus_config_) {
111 LOG(ERROR) << "ibus_bus_config_new() failed"; 376 LOG(ERROR) << "ibus_bus_config_new() failed";
112 return false; 377 return false;
113 } 378 }
114 379
380 // Establish a DBus connection between the candidate_window process for
381 // Chromium OS to handle signals (e.g. "FocusIn") from the process.
382 const char* address = ibus_get_address();
383 dbus_connection_.reset(
384 new dbus::BusConnection(dbus::GetPrivateBusConnection(address)));
385 LOG(INFO) << "Established private D-Bus connection to: '" << address << "'";
115 386
116 // Establish a DBus connection between the candidate_window process for 387 // Connect to the candidate_window. Note that dbus_connection_add_filter()
117 // Chromium OS to handle "FocusIn", "FocusOut", and "StateChanged" signals 388 // does not work without calling dbus::Proxy().
118 // from the process. 389 // TODO(yusukes): Investigate how we can eliminate the call.
119 const char* address = ibus_get_address();
120 dbus::BusConnection dbus(dbus::GetPrivateBusConnection(address));
121 LOG(INFO) << "Established private D-Bus connection to: '" << address << "'";
122 390
123 const bool kConnectToNameOwner = true; 391 const bool kConnectToNameOwner = true;
124 // TODO(yusukes): dbus::Proxy instantiation might fail (and abort due to 392 // TODO(yusukes): dbus::Proxy instantiation might fail (and abort due to
125 // DCHECK failure) when candidate_window process does not exist yet. 393 // DCHECK failure) when candidate_window process does not exist yet.
126 // Would be better to add "bool dbus::Proxy::Init()" or something like that 394 // Would be better to add "bool dbus::Proxy::Init()" or something like that
127 // to handle such case? 395 // to handle such case?
128 dbus::Proxy candidate_window(dbus, 396 dbus_proxy_.reset(new dbus::Proxy(*dbus_connection_,
129 kCandidateWindowService, 397 kCandidateWindowService,
130 kCandidateWindowObjectPath, 398 kCandidateWindowObjectPath,
131 kCandidateWindowInterface, 399 kCandidateWindowInterface,
132 kConnectToNameOwner); 400 kConnectToNameOwner));
133 401
134 if (!candidate_window) { 402 // Register DBus signal handler.
135 LOG(ERROR) << "Can't construct proxy for the candidate window. " 403 dbus_connection_add_filter(
136 << "candidate window is not running?"; 404 dbus_g_connection_get_connection(dbus_connection_->g_connection()),
137 return false; 405 &LanguageStatusConnection::DispatchSignalFromCandidateWindow,
138 } 406 this, NULL);
139
140 dbus_focus_in_ = dbus::Monitor(candidate_window,
141 "FocusIn",
142 &LanguageStatusConnection::FocusIn,
143 this);
144 dbus_focus_out_ = dbus::Monitor(candidate_window,
145 "FocusOut",
146 &LanguageStatusConnection::FocusOut,
147 this);
148 dbus_state_changed_ = dbus::Monitor(candidate_window,
149 "StateChanged",
150 &LanguageStatusConnection::StateChanged,
151 this);
152 407
153 // TODO(yusukes): Investigate what happens if IBus/DBus connections are 408 // TODO(yusukes): Investigate what happens if IBus/DBus connections are
154 // suddenly closed. 409 // suddenly closed.
155 // TODO(yusukes): Investigate what happens if candidate_window process is 410 // TODO(yusukes): Investigate what happens if candidate_window process is
156 // restarted. I'm not sure but we should use dbus_g_proxy_new_for_name(), 411 // restarted. I'm not sure but we should use dbus_g_proxy_new_for_name(),
157 // not dbus_g_proxy_new_for_name_owner()? 412 // not dbus_g_proxy_new_for_name_owner()?
158 413
159 return true; 414 return true;
160 } 415 }
161 416
(...skipping 22 matching lines...) Expand all
184 } 439 }
185 InputLanguageList* language_list = new InputLanguageList; 440 InputLanguageList* language_list = new InputLanguageList;
186 AddIMELanguages(engines, language_list); 441 AddIMELanguages(engines, language_list);
187 AddXKBLayouts(language_list); 442 AddXKBLayouts(language_list);
188 std::sort(language_list->begin(), language_list->end()); 443 std::sort(language_list->begin(), language_list->end());
189 444
190 g_list_free(engines); 445 g_list_free(engines);
191 return language_list; 446 return language_list;
192 } 447 }
193 448
194 // Called by cros API ChromeOSChangeLanguage(). 449 // Called by cros API ChromeOS(Activate|Deactive)ImeProperty().
195 void SwitchXKB(const char* name) { 450 void ActivateOrDeactiveImeProperty(const char* key, bool active) {
196 // TODO(yusukes): implement XKB switching.
197
198 if (input_context_path_.empty()) { 451 if (input_context_path_.empty()) {
199 LOG(ERROR) << "Input context is unknown"; 452 LOG(ERROR) << "Input context is unknown";
200 return; 453 return;
201 } 454 }
202 455
203 IBusInputContext* context 456 IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
204 = ibus_input_context_get_input_context(input_context_path_.c_str(), 457 if (!context) {
205 ibus_bus_get_connection(ibus_)); 458 return;
206 ibus_input_context_disable(context); 459 }
460 ibus_input_context_property_activate(
461 context, key, (active ? PROP_STATE_CHECKED : PROP_STATE_UNCHECKED));
207 g_object_unref(context); 462 g_object_unref(context);
463
208 UpdateUI(); 464 UpdateUI();
209 } 465 }
210 466
211 // Called by cros API ChromeOSChangeLanguage() as well. 467 // Called by cros API ChromeOSChangeLanguage().
212 void SwitchIME(const char* name) { 468 void ChangeLanguage(LanguageCategory category, const char* name) {
213 if (input_context_path_.empty()) { 469 // Clear all IME properties unconditionally.
214 LOG(ERROR) << "Input context is unknown"; 470 // - When switching to XKB, it's necessary since XKB layout does not have
215 return; 471 // IME properties.
472 // - When switching to IME and a text area is focused, it's okay to clear
473 // IME properties here since RegisterProperties signal for the new IME
474 // will be sent anyway.
475 // - When switching to IME and no text area is focused, RegisterProperties
476 // signal for the new IME will NOT be sent until a text area is focused.
477 // Therefore, we have to clear the old IME properties here to keep the
478 // IME switcher status consistent.
479 RegisterProperties(NULL);
480
481 switch (category) {
482 case LANGUAGE_CATEGORY_XKB:
483 SwitchToXKB(name);
484 break;
485 case LANGUAGE_CATEGORY_IME:
486 SwitchToIME(name);
487 break;
216 } 488 }
217
218 IBusInputContext* context
219 = ibus_input_context_get_input_context(input_context_path_.c_str(),
220 ibus_bus_get_connection(ibus_));
221 ibus_input_context_set_engine(context, name);
222 g_object_unref(context);
223 UpdateUI();
224 } 489 }
225 490
226 // UpdateMode is used for specifying whether we are activating or 491 // UpdateMode is used for specifying whether we are activating or
227 // deactivating an input language. We use this mode to consolidate logic 492 // deactivating an input language. We use this mode to consolidate logic
228 // for activation and deactivation. 493 // for activation and deactivation.
229 enum UpdateMode { 494 enum UpdateMode {
230 kActivate, // Activate an input language. 495 kActivate, // Activate an input language.
231 kDeactivate, // Deactivate an input language. 496 kDeactivate, // Deactivate an input language.
232 }; 497 };
233 498
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
280 "general", 545 "general",
281 "preload_engines", 546 "preload_engines",
282 &value); 547 &value);
283 g_value_unset(&value); 548 g_value_unset(&value);
284 g_list_free(engines); 549 g_list_free(engines);
285 550
286 return success; 551 return success;
287 } 552 }
288 553
289 InputLanguage* GetCurrentLanguage() { 554 InputLanguage* GetCurrentLanguage() {
290 IBusInputContext* context 555 IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
291 = ibus_input_context_get_input_context( 556 if (!context) {
292 input_context_path_.c_str(), ibus_bus_get_connection(ibus_)); 557 return NULL;
558 }
293 559
294 const bool ime_is_enabled = ibus_input_context_is_enabled(context); 560 const bool ime_is_enabled = ibus_input_context_is_enabled(context);
295 g_object_unref(context); 561 g_object_unref(context);
296 562
297 InputLanguage* current_language = NULL; 563 InputLanguage* current_language = NULL;
298 if (ime_is_enabled) { 564 if (ime_is_enabled) {
299 DLOG(INFO) << "IME is active"; 565 DLOG(INFO) << "IME is active";
300 // Set IME name on current_language. 566 // Set IME name on current_language.
301 const IBusEngineDesc* engine_desc 567 const IBusEngineDesc* engine_desc
302 = ibus_input_context_get_engine(context); 568 = ibus_input_context_get_engine(context);
(...skipping 11 matching lines...) Expand all
314 current_language = new InputLanguage(LANGUAGE_CATEGORY_XKB, 580 current_language = new InputLanguage(LANGUAGE_CATEGORY_XKB,
315 kFallbackXKBId, 581 kFallbackXKBId,
316 kFallbackXKBDisplayName, 582 kFallbackXKBDisplayName,
317 "" /* no icon */); // mock 583 "" /* no icon */); // mock
318 // TODO(yusukes): implemente this. 584 // TODO(yusukes): implemente this.
319 } 585 }
320 return current_language; 586 return current_language;
321 } 587 }
322 588
323 private: 589 private:
324 // Handles "FocusIn" signal from candidate_window. 590 // Changes the current language to |name|, which is XKB layout.
325 static void FocusIn(void* object, const char* input_context_path) { 591 void SwitchToXKB(const char* name) {
592 // TODO(yusukes): implement XKB switching.
593 if (input_context_path_.empty()) {
594 LOG(ERROR) << "Input context is unknown";
595 return;
596 }
597
598 IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
599 if (!context) {
600 return;
601 }
602 ibus_input_context_disable(context);
603 g_object_unref(context);
604 UpdateUI();
605 }
606
607 // Changes the current language to |name|, which is IME.
608 void SwitchToIME(const char* name) {
609 if (input_context_path_.empty()) {
610 LOG(ERROR) << "Input context is unknown";
611 return;
612 }
613
614 IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
615 if (!context) {
616 return;
617 }
618 ibus_input_context_set_engine(context, name);
619 g_object_unref(context);
620 UpdateUI();
621 }
622
623 // Handles "FocusIn" signal from the candidate_window process.
624 void FocusIn(const char* input_context_path) {
326 DCHECK(input_context_path) << "NULL context passed"; 625 DCHECK(input_context_path) << "NULL context passed";
327 DLOG(INFO) << "FocusIn: " << input_context_path; 626 DLOG(INFO) << "FocusIn: " << input_context_path;
328 DCHECK(object);
329 627
330 // Remember the current ic path. 628 // Remember the current ic path.
331 LanguageStatusConnection* self 629 input_context_path_ = input_context_path;
332 = static_cast<LanguageStatusConnection*>(object); 630 UpdateUI(); // This is necessary since IME status is held per ic.
333 self->input_context_path_ = input_context_path;
334 self->UpdateUI(); // This is necessary since IME status is held per ic.
335 } 631 }
336 632
337 // Handles "FocusOut" signal from candidate_window. 633 // Handles "FocusOut" signal from the candidate_window process.
338 static void FocusOut(void* object, const char* input_context_path) { 634 void FocusOut(const char* input_context_path) {
339 DCHECK(input_context_path) << "NULL context passed"; 635 DCHECK(input_context_path) << "NULL context passed";
340 DLOG(INFO) << "FocusOut: " << input_context_path; 636 DLOG(INFO) << "FocusOut: " << input_context_path;
341 DCHECK(object);
342 } 637 }
343 638
344 // Handles "StateChanged" signal from candidate_window process. 639 // Handles "StateChanged" signal from the candidate_window process.
345 static void StateChanged(void* object, const char* dummy) { 640 void StateChanged() {
346 // TODO(yusukes): Modify common/chromeos/dbus/dbus.h so that we can handle 641 // TODO(yusukes): Modify common/chromeos/dbus/dbus.h so that we can handle
347 // signals without argument. Then remove the |dummy|. 642 // signals without argument. Then remove the |dummy|.
348 DLOG(INFO) << "StateChanged"; 643 DLOG(INFO) << "StateChanged";
349 DCHECK(object); 644 UpdateUI();
350 LanguageStatusConnection* self 645 }
351 = static_cast<LanguageStatusConnection*>(object); 646
352 self->UpdateUI(); 647 // Handles "RegisterProperties" signal from the candidate_window process.
648 void RegisterProperties(IBusPropList* ibus_prop_list) {
649 DLOG(INFO) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)");
650
651 ImePropertyList prop_list; // our representation.
652 if (ibus_prop_list) {
653 // You can call
654 // LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0);
655 // here to dump |ibus_prop_list|.
656 if (!FlattenPropertyList(ibus_prop_list, &prop_list)) {
657 LOG(WARNING) << "Malformed properties are detected";
658 }
659 }
660 // Notify the change.
661 monitor_functions_.register_ime_properties(language_library_, prop_list);
662 }
663
664 // Handles "UpdateProperty" signal from the candidate_window process.
665 void UpdateProperty(IBusProperty* ibus_prop) {
666 DLOG(INFO) << "UpdateProperty";
667 DCHECK(ibus_prop);
668
669 // You can call
670 // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0);
671 // here to dump |ibus_prop|.
672
673 ImePropertyList prop_list; // our representation.
674 if (!FlattenProperty(ibus_prop, &prop_list)) {
675 LOG(WARNING) << "Malformed properties are detected";
676 }
677 // Notify the change.
678 if (!prop_list.empty()) {
679 monitor_functions_.update_ime_property(language_library_, prop_list);
680 }
353 } 681 }
354 682
355 // Retrieve IME/XKB status and notify them to the UI. 683 // Retrieve IME/XKB status and notify them to the UI.
356 void UpdateUI() { 684 void UpdateUI() {
357 if (input_context_path_.empty()) { 685 if (input_context_path_.empty()) {
358 LOG(ERROR) << "Input context is unknown"; 686 LOG(ERROR) << "Input context is unknown";
359 return; 687 return;
360 } 688 }
361 689
362 IBusInputContext* context 690 IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
363 = ibus_input_context_get_input_context( 691 if (!context) {
364 input_context_path_.c_str(), ibus_bus_get_connection(ibus_)); 692 return;
693 }
365 694
366 InputLanguage current_language; 695 InputLanguage current_language;
367 const bool ime_is_enabled = ibus_input_context_is_enabled(context); 696 const bool ime_is_enabled = ibus_input_context_is_enabled(context);
368 if (ime_is_enabled) { 697 if (ime_is_enabled) {
369 DLOG(INFO) << "IME is active"; 698 DLOG(INFO) << "IME is active";
370 // Set IME name on current_language. 699 // Set IME name on current_language.
371 const IBusEngineDesc* engine_desc 700 const IBusEngineDesc* engine_desc
372 = ibus_input_context_get_engine(context); 701 = ibus_input_context_get_engine(context);
373 DCHECK(engine_desc); 702 DCHECK(engine_desc);
374 if (!engine_desc) { 703 if (!engine_desc) {
375 return; 704 return;
376 } 705 }
377 current_language = InputLanguage(LANGUAGE_CATEGORY_IME, 706 current_language = InputLanguage(LANGUAGE_CATEGORY_IME,
378 engine_desc->name, 707 engine_desc->name,
379 engine_desc->longname, 708 engine_desc->longname,
380 engine_desc->icon); 709 engine_desc->icon);
381 } else { 710 } else {
382 DLOG(INFO) << "IME is not active"; 711 DLOG(INFO) << "IME is not active";
383 // Set XKB layout name on current_languages. 712 // Set XKB layout name on current_languages.
384 current_language = InputLanguage(LANGUAGE_CATEGORY_XKB, 713 current_language = InputLanguage(LANGUAGE_CATEGORY_XKB,
385 kFallbackXKBId, 714 kFallbackXKBId,
386 kFallbackXKBDisplayName, 715 kFallbackXKBDisplayName,
387 "" /* no icon */); // mock 716 "" /* no icon */); // mock
388 // TODO(yusukes): implemente this. 717 // TODO(yusukes): implemente this.
389 } 718 }
390 DLOG(INFO) << "Updating the UI. ID:" << current_language.id 719 DLOG(INFO) << "Updating the UI. ID:" << current_language.id
391 << ", display_name:" << current_language.display_name; 720 << ", display_name:" << current_language.display_name;
392 721
393 // Notify the change to update UI. 722 // Notify the change to update UI.
394 monitor_function_(language_library_, current_language); 723 monitor_functions_.current_language(language_library_, current_language);
395 g_object_unref(context); 724 g_object_unref(context);
396 } 725 }
397 726
398 // A function pointer which points LanguageLibrary::LanguageChangedHandler 727 // Dispatches signals from candidate_window. In this function, we use the
399 // function. |monitor_funcion_| is called when Chrome UI needs to be updated. 728 // IBus's DBus binding (rather than the dbus-glib and its C++ wrapper).
400 LanguageStatusMonitorFunction monitor_function_; 729 // This is because arguments of "RegisterProperties" and "UpdateProperty"
730 // are fairly complex IBus types, and thus it's probably not a good idea
731 // to write a deserializer for these types from scratch using dbus-glib.
732 static DBusHandlerResult DispatchSignalFromCandidateWindow(
733 DBusConnection* connection, DBusMessage* message, void* object) {
734 DCHECK(message);
735 DCHECK(object);
736
737 LanguageStatusConnection* self
738 = static_cast<LanguageStatusConnection*>(object);
739 IBusError* error = NULL;
740
741 if (ibus_message_is_signal(message,
742 kCandidateWindowInterface,
743 "FocusIn")) {
744 gchar* input_context_path = NULL;
745 const gboolean retval = ibus_message_get_args(message, &error,
746 G_TYPE_STRING,
747 &input_context_path,
748 G_TYPE_INVALID);
749 if (!retval) {
750 LOG(ERROR) << "FocusIn";
751 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
752 }
753 self->FocusIn(input_context_path);
754 return DBUS_HANDLER_RESULT_HANDLED;
755 }
756
757 if (ibus_message_is_signal(message,
758 kCandidateWindowInterface,
759 "FocusOut")) {
760 gchar* input_context_path = NULL;
761 const gboolean retval = ibus_message_get_args(message, &error,
762 G_TYPE_STRING,
763 &input_context_path,
764 G_TYPE_INVALID);
765 if (!retval) {
766 LOG(ERROR) << "FocusOut";
767 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
768 }
769 self->FocusOut(input_context_path);
770 return DBUS_HANDLER_RESULT_HANDLED;
771 }
772
773 if (ibus_message_is_signal(message,
774 kCandidateWindowInterface,
775 "StateChanged")) {
776 const gboolean retval = ibus_message_get_args(message, &error,
777 /* no arguments */
778 G_TYPE_INVALID);
779 if (!retval) {
780 LOG(ERROR) << "StateChanged";
781 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
782 }
783 self->StateChanged();
784 return DBUS_HANDLER_RESULT_HANDLED;
785 }
786
787 if (ibus_message_is_signal(message,
788 kCandidateWindowInterface,
789 "RegisterProperties")) {
790 IBusPropList* prop_list = NULL;
791
792 // The ibus_message_get_args() function automagically deserializes the
793 // complex IBus structure.
794 const gboolean retval = ibus_message_get_args(message, &error,
795 IBUS_TYPE_PROP_LIST,
796 &prop_list,
797 G_TYPE_INVALID);
798
799 if (!retval) {
800 LOG(ERROR) << "RegisterProperties";
801 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
802 }
803 self->RegisterProperties(prop_list);
804 g_object_unref(prop_list);
805 return DBUS_HANDLER_RESULT_HANDLED;
806 }
807
808 if (ibus_message_is_signal(message,
809 kCandidateWindowInterface,
810 "UpdateProperty")) {
811 IBusProperty* prop = NULL;
812 const gboolean retval = ibus_message_get_args(message, &error,
813 IBUS_TYPE_PROPERTY,
814 &prop,
815 G_TYPE_INVALID);
816 if (!retval) {
817 LOG(ERROR) << "UpdateProperty";
818 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
819 }
820 self->UpdateProperty(prop);
821 g_object_unref(prop);
822 return DBUS_HANDLER_RESULT_HANDLED;
823 }
824
825 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
826 }
827
828 // A function pointers which point LanguageLibrary::XXXHandler functions.
829 // |monitor_funcions_| is used when libcros receives signals from the
830 // candidate_window.
831 LanguageStatusMonitorFunctions monitor_functions_;
832
401 // Points to a chromeos::LanguageLibrary object. |language_library_| is used 833 // Points to a chromeos::LanguageLibrary object. |language_library_| is used
402 // as the first argument of the |monitor_function_| function. 834 // as the first argument of the |monitor_functions_| functions.
403 void* language_library_; 835 void* language_library_;
404 836
405 // Connections to IBus and DBus. 837 // Connections to IBus and DBus.
406 typedef dbus::MonitorConnection<void (const char*)>* DBusConnectionType;
407 IBusBus* ibus_; 838 IBusBus* ibus_;
408 IBusConfig* ibus_config_; 839 IBusConfig* ibus_config_;
409 DBusConnectionType dbus_focus_in_; 840 scoped_ptr<dbus::BusConnection> dbus_connection_;
410 DBusConnectionType dbus_focus_out_; 841 scoped_ptr<dbus::Proxy> dbus_proxy_;
411 DBusConnectionType dbus_state_changed_;
412 842
413 // Current input context path. 843 // Current input context path.
414 std::string input_context_path_; 844 std::string input_context_path_;
415 }; 845 };
416 846
417 // 847 //
418 // cros APIs 848 // cros APIs
419 // 849 //
420 850
421 // The function will be bound to chromeos::MonitorLanguageStatus with dlsym() 851 // The function will be bound to chromeos::MonitorLanguageStatus with dlsym()
422 // in load.cc so it needs to be in the C linkage, so the symbol name does not 852 // in load.cc so it needs to be in the C linkage, so the symbol name does not
423 // get mangled. 853 // get mangled.
424 extern "C" 854 extern "C"
425 LanguageStatusConnection* ChromeOSMonitorLanguageStatus( 855 LanguageStatusConnection* ChromeOSMonitorLanguageStatus(
426 LanguageStatusMonitorFunction monitor_function, void* language_library) { 856 LanguageStatusMonitorFunctions monitor_functions, void* language_library) {
427 LOG(INFO) << "MonitorLanguageStatus"; 857 LOG(INFO) << "MonitorLanguageStatus";
428 LanguageStatusConnection* connection 858 LanguageStatusConnection* connection
429 = new LanguageStatusConnection(monitor_function, language_library); 859 = new LanguageStatusConnection(monitor_functions, language_library);
430 if (!connection->Init()) { 860 if (!connection->Init()) {
431 LOG(WARNING) << "Failed to Init() LanguageStatusConnection. " 861 LOG(WARNING) << "Failed to Init() LanguageStatusConnection. "
432 << "Returning NULL"; 862 << "Returning NULL";
433 delete connection; 863 delete connection;
434 connection = NULL; 864 connection = NULL;
435 } 865 }
436 return connection; 866 return connection;
437 } 867 }
438 868
439 extern "C" 869 extern "C"
(...skipping 21 matching lines...) Expand all
461 if (!connection) { 891 if (!connection) {
462 LOG(WARNING) << "LanguageStatusConnection is NULL"; 892 LOG(WARNING) << "LanguageStatusConnection is NULL";
463 return NULL; 893 return NULL;
464 } 894 }
465 // Pass ownership to a caller. Note: GetLanguages() might return NULL. 895 // Pass ownership to a caller. Note: GetLanguages() might return NULL.
466 return connection->GetLanguages( 896 return connection->GetLanguages(
467 LanguageStatusConnection::kSupportedLanguages); 897 LanguageStatusConnection::kSupportedLanguages);
468 } 898 }
469 899
470 extern "C" 900 extern "C"
901 void ChromeOSActivateImeProperty(
902 LanguageStatusConnection* connection, const char* key) {
903 DLOG(INFO) << "ActivateImeProperty";
904 DCHECK(key);
905 // TODO(yusukes): Add DCHECK(connection); here when candidate_window for
906 // Chrome OS gets ready.
907 if (!connection) {
908 LOG(WARNING) << "LanguageStatusConnection is NULL";
909 return;
910 }
911 connection->ActivateOrDeactiveImeProperty(key, true);
912 }
913
914 extern "C"
915 void ChromeOSDeactivateImeProperty(
916 LanguageStatusConnection* connection, const char* key) {
917 DLOG(INFO) << "DeactivateImeProperty";
918 DCHECK(key);
919 // TODO(yusukes): Add DCHECK(connection); here when candidate_window for
920 // Chrome OS gets ready.
921 if (!connection) {
922 LOG(WARNING) << "LanguageStatusConnection is NULL";
923 return;
924 }
925 connection->ActivateOrDeactiveImeProperty(key, false);
926 }
927
928 extern "C"
471 void ChromeOSChangeLanguage(LanguageStatusConnection* connection, 929 void ChromeOSChangeLanguage(LanguageStatusConnection* connection,
472 LanguageCategory category, 930 LanguageCategory category,
473 const char* name) { 931 const char* name) {
932 DCHECK(name);
933 DLOG(INFO) << "ChangeLanguage: " << name;
474 // TODO(yusukes): Add DCHECK(connection); here when candidate_window for 934 // TODO(yusukes): Add DCHECK(connection); here when candidate_window for
475 // Chrome OS gets ready. 935 // Chrome OS gets ready.
476 if (!connection) { 936 if (!connection) {
477 LOG(WARNING) << "LanguageStatusConnection is NULL"; 937 LOG(WARNING) << "LanguageStatusConnection is NULL";
478 return; 938 return;
479 } 939 }
480 DCHECK(name); 940 connection->ChangeLanguage(category, name);
481 DLOG(INFO) << "ChangeLanguage: " << name << " [category " << category << "]";
482 switch (category) {
483 case LANGUAGE_CATEGORY_XKB:
484 connection->SwitchXKB(name);
485 break;
486 case LANGUAGE_CATEGORY_IME:
487 connection->SwitchIME(name);
488 break;
489 }
490 } 941 }
491 942
492 // Helper function for ChromeOSActivateLanguage() and 943 // Helper function for ChromeOSActivateLanguage() and
493 // ChromeOSDeactivateLanguage(). 944 // ChromeOSDeactivateLanguage().
494 static bool ActivateOrDeactivateLanguage( 945 static bool ActivateOrDeactivateLanguage(
495 LanguageStatusConnection::UpdateMode mode, 946 LanguageStatusConnection::UpdateMode mode,
496 LanguageStatusConnection* connection, 947 LanguageStatusConnection* connection,
497 LanguageCategory category, 948 LanguageCategory category,
498 const char* name) { 949 const char* name) {
499 if (!connection) { 950 if (!connection) {
(...skipping 27 matching lines...) Expand all
527 bool ChromeOSDeactivateLanguage(LanguageStatusConnection* connection, 978 bool ChromeOSDeactivateLanguage(LanguageStatusConnection* connection,
528 LanguageCategory category, 979 LanguageCategory category,
529 const char* name) { 980 const char* name) {
530 DLOG(INFO) << "DeactivateLanguage: " << name << " [category " 981 DLOG(INFO) << "DeactivateLanguage: " << name << " [category "
531 << category << "]"; 982 << category << "]";
532 return ActivateOrDeactivateLanguage( 983 return ActivateOrDeactivateLanguage(
533 LanguageStatusConnection::kDeactivate, connection, category, name); 984 LanguageStatusConnection::kDeactivate, connection, category, name);
534 } 985 }
535 986
536 } // namespace chromeos 987 } // namespace chromeos
OLDNEW
« no previous file with comments | « chromeos_language.h ('k') | load.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698