1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "unix/ibus/property_handler.h"
31
32 #include <string>
33
34 #include "base/const.h"
35 #include "base/file_util.h"
36 #include "base/logging.h"
37 #include "base/system_util.h"
38 #include "client/client.h" // For client interface
39 #include "unix/ibus/message_translator.h"
40 #include "unix/ibus/mozc_engine_property.h"
41 #include "unix/ibus/path_util.h"
42
43 // On Gnome Shell with IBus 1.5, new property named "symbol" is used to
44 // represent the mode indicator on the system panel. Note that "symbol" does
45 // not exist in IBus 1.4.x.
46 #if IBUS_CHECK_VERSION(1, 5, 0)
47 #define MOZC_IBUS_HAS_SYMBOL
48 #endif // IBus >= 1.5
49
50 namespace mozc {
51 namespace ibus {
52
53 namespace {
54
55 // A key which associates an IBusProperty object with MozcEngineProperty.
56 const char kGObjectDataKey[] = "ibus-mozc-aux-data";
57
58 // Icon path for MozcTool
59 const char kMozcToolIconPath[] = "tool.png";
60
61 // Returns true if mozc_tool is installed.
IsMozcToolAvailable()62 bool IsMozcToolAvailable() {
63 return FileUtil::FileExists(SystemUtil::GetToolPath());
64 }
65
GetDisabled(IBusEngine * engine)66 bool GetDisabled(IBusEngine *engine) {
67 bool disabled = false;
68 #if defined(MOZC_ENABLE_IBUS_INPUT_PURPOSE)
69 guint purpose = IBUS_INPUT_PURPOSE_FREE_FORM;
70 guint hints = IBUS_INPUT_HINT_NONE;
71 ibus_engine_get_content_type(engine, &purpose, &hints);
72 disabled = (purpose == IBUS_INPUT_PURPOSE_PASSWORD ||
73 purpose == IBUS_INPUT_PURPOSE_PIN);
74 #endif // MOZC_ENABLE_IBUS_INPUT_PURPOSE
75 return disabled;
76 }
77
78 // Some users expect that Mozc is turned off by default on IBus 1.5.0 and later.
79 // https://github.com/google/mozc/issues/201
80 // On IBus 1.4.x, IBus expects that an IME should always be turned on and
81 // IME on/off keys are handled by IBus itself rather than each IME.
82 #if IBUS_CHECK_VERSION(1, 5, 0)
83 const bool kActivatedOnLaunch = false;
84 #else
85 const bool kActivatedOnLaunch = true;
86 #endif // IBus>=1.5.0
87
88 } // namespace
89
PropertyHandler(MessageTranslatorInterface * translator,client::ClientInterface * client)90 PropertyHandler::PropertyHandler(MessageTranslatorInterface *translator,
91 client::ClientInterface *client)
92 : prop_root_(ibus_prop_list_new()),
93 prop_composition_mode_(NULL),
94 prop_mozc_tool_(NULL),
95 client_(client),
96 translator_(translator),
97 original_composition_mode_(kMozcEngineInitialCompositionMode),
98 is_activated_(kActivatedOnLaunch),
99 is_disabled_(false) {
100 commands::SessionCommand command;
101 if (is_activated_) {
102 command.set_type(commands::SessionCommand::TURN_ON_IME);
103 } else {
104 command.set_type(commands::SessionCommand::TURN_OFF_IME);
105 }
106 command.set_composition_mode(original_composition_mode_);
107 commands::Output output;
108 if (!client->SendCommand(command, &output)) {
109 LOG(ERROR) << "SendCommand failed";
110 }
111
112 AppendCompositionPropertyToPanel();
113 AppendToolPropertyToPanel();
114
115 // We have to sink |prop_root_| as well so ibus_engine_register_properties()
116 // in FocusIn() does not destruct it.
117 g_object_ref_sink(prop_root_);
118 }
119
~PropertyHandler()120 PropertyHandler::~PropertyHandler() {
121 if (prop_composition_mode_) {
122 // The ref counter will drop to one.
123 g_object_unref(prop_composition_mode_);
124 prop_composition_mode_ = NULL;
125 }
126
127 if (prop_mozc_tool_) {
128 // The ref counter will drop to one.
129 g_object_unref(prop_mozc_tool_);
130 prop_mozc_tool_ = NULL;
131 }
132
133 if (prop_root_) {
134 // Destroy all objects under the root.
135 g_object_unref(prop_root_);
136 prop_root_ = NULL;
137 }
138 }
139
Register(IBusEngine * engine)140 void PropertyHandler::Register(IBusEngine *engine) {
141 ibus_engine_register_properties(engine, prop_root_);
142 UpdateContentType(engine);
143 }
144
145 // TODO(nona): do not use kMozcEngine*** directory.
AppendCompositionPropertyToPanel()146 void PropertyHandler::AppendCompositionPropertyToPanel() {
147 if (kMozcEngineProperties == NULL || kMozcEnginePropertiesSize == 0) {
148 return;
149 }
150
151 // |sub_prop_list| is a radio menu which is shown when a button in the
152 // language panel (i.e. |prop_composition_mode_| below) is clicked.
153 IBusPropList *sub_prop_list = ibus_prop_list_new();
154
155 // Create items for the radio menu.
156 const commands::CompositionMode initial_mode = is_activated_ ?
157 original_composition_mode_ :
158 kMozcEnginePropertyIMEOffState->composition_mode;
159
160 string icon_path_for_panel;
161 const char *mode_symbol = NULL;
162 for (size_t i = 0; i < kMozcEnginePropertiesSize; ++i) {
163 const MozcEngineProperty &entry = kMozcEngineProperties[i];
164 IBusText *label = ibus_text_new_from_string(
165 translator_->MaybeTranslate(entry.label).c_str());
166 IBusPropState state = PROP_STATE_UNCHECKED;
167 if (entry.composition_mode == initial_mode) {
168 state = PROP_STATE_CHECKED;
169 icon_path_for_panel = GetIconPath(entry.icon);
170 mode_symbol = entry.label_for_panel;
171 }
172 IBusProperty *item = ibus_property_new(entry.key,
173 PROP_TYPE_RADIO,
174 label,
175 NULL /* icon */,
176 NULL /* tooltip */,
177 TRUE /* sensitive */,
178 TRUE /* visible */,
179 state,
180 NULL /* sub props */);
181 g_object_set_data(G_OBJECT(item), kGObjectDataKey, (gpointer)&entry);
182 ibus_prop_list_append(sub_prop_list, item);
183 // |sub_prop_list| owns |item| by calling g_object_ref_sink for the |item|.
184 }
185 DCHECK(!icon_path_for_panel.empty());
186 DCHECK(mode_symbol != NULL);
187
188 const string &mode_label =
189 translator_->MaybeTranslate("Input Mode") + " (" + mode_symbol + ")";
190 IBusText *label = ibus_text_new_from_string(mode_label.c_str());
191
192 // The label of |prop_composition_mode_| is shown in the language panel.
193 // Note that the property name "InputMode" is hard-coded in the Gnome shell.
194 // Do not change the name. Othewise the Gnome shell fails to recognize that
195 // this property indicates Mozc's input mode.
196 // See /usr/share/gnome-shell/js/ui/status/keyboard.js for details.
197 prop_composition_mode_ = ibus_property_new("InputMode",
198 PROP_TYPE_MENU,
199 label,
200 icon_path_for_panel.c_str(),
201 NULL /* tooltip */,
202 TRUE /* sensitive */,
203 TRUE /* visible */,
204 PROP_STATE_UNCHECKED,
205 sub_prop_list);
206
207 // Gnome shell uses symbol property for the mode indicator text icon iff the
208 // property name is "InputMode".
209 #ifdef MOZC_IBUS_HAS_SYMBOL
210 IBusText *symbol = ibus_text_new_from_static_string(mode_symbol);
211 ibus_property_set_symbol(prop_composition_mode_, symbol);
212 #endif // MOZC_IBUS_HAS_SYMBOL
213
214 // Likewise, |prop_composition_mode_| owns |sub_prop_list|. We have to sink
215 // |prop_composition_mode_| here so ibus_engine_update_property() call in
216 // PropertyActivate() does not destruct the object.
217 g_object_ref_sink(prop_composition_mode_);
218
219 ibus_prop_list_append(prop_root_, prop_composition_mode_);
220 }
221
UpdateContentTypeImpl(IBusEngine * engine,bool disabled)222 void PropertyHandler::UpdateContentTypeImpl(IBusEngine *engine,
223 bool disabled) {
224 const bool prev_is_disabled = is_disabled_;
225 is_disabled_ = disabled;
226 if (prev_is_disabled == is_disabled_) {
227 return;
228 }
229 const auto visible_mode = (prev_is_disabled && !is_disabled_ && IsActivated())
230 ? original_composition_mode_ :
231 kMozcEnginePropertyIMEOffState->composition_mode;
232 UpdateCompositionModeIcon(engine, visible_mode);
233 }
234
ResetContentType(IBusEngine * engine)235 void PropertyHandler::ResetContentType(IBusEngine *engine) {
236 UpdateContentTypeImpl(engine, false);
237 }
238
UpdateContentType(IBusEngine * engine)239 void PropertyHandler::UpdateContentType(IBusEngine *engine) {
240 UpdateContentTypeImpl(engine, GetDisabled(engine));
241 }
242
243 // TODO(nona): do not use kMozcEngine*** directory.
AppendToolPropertyToPanel()244 void PropertyHandler::AppendToolPropertyToPanel() {
245 if (kMozcEngineToolProperties == NULL || kMozcEngineToolPropertiesSize == 0 ||
246 !IsMozcToolAvailable()) {
247 return;
248 }
249
250 // |sub_prop_list| is a radio menu which is shown when a button in the
251 // language panel (i.e. |prop_composition_mode_| below) is clicked.
252 IBusPropList *sub_prop_list = ibus_prop_list_new();
253
254 for (size_t i = 0; i < kMozcEngineToolPropertiesSize; ++i) {
255 const MozcEngineToolProperty &entry = kMozcEngineToolProperties[i];
256 IBusText *label = ibus_text_new_from_string(
257 translator_->MaybeTranslate(entry.label).c_str());
258 // TODO(yusukes): It would be better to use entry.icon here?
259 IBusProperty *item = ibus_property_new(entry.mode,
260 PROP_TYPE_NORMAL,
261 label,
262 NULL /* icon */,
263 NULL /* tooltip */,
264 TRUE,
265 TRUE,
266 PROP_STATE_UNCHECKED,
267 NULL);
268 g_object_set_data(G_OBJECT(item), kGObjectDataKey, (gpointer)&entry);
269 ibus_prop_list_append(sub_prop_list, item);
270 }
271
272 IBusText *tool_label = ibus_text_new_from_string(
273 translator_->MaybeTranslate("Tools").c_str());
274 const string icon_path = GetIconPath(kMozcToolIconPath);
275 prop_mozc_tool_ = ibus_property_new("MozcTool",
276 PROP_TYPE_MENU,
277 tool_label,
278 icon_path.c_str(),
279 NULL /* tooltip */,
280 TRUE /* sensitive */,
281 TRUE /* visible */,
282 PROP_STATE_UNCHECKED,
283 sub_prop_list);
284
285 // Likewise, |prop_mozc_tool_| owns |sub_prop_list|. We have to sink
286 // |prop_mozc_tool_| here so ibus_engine_update_property() call in
287 // PropertyActivate() does not destruct the object.
288 g_object_ref_sink(prop_mozc_tool_);
289
290 ibus_prop_list_append(prop_root_, prop_mozc_tool_);
291 }
292
Update(IBusEngine * engine,const commands::Output & output)293 void PropertyHandler::Update(IBusEngine *engine,
294 const commands::Output &output) {
295 if (IsDisabled()) {
296 return;
297 }
298
299 if (output.has_status() &&
300 (output.status().activated() != is_activated_ ||
301 output.status().mode() != original_composition_mode_)) {
302 if (output.status().activated()) {
303 UpdateCompositionModeIcon(engine, output.status().mode());
304 } else {
305 DCHECK(kMozcEnginePropertyIMEOffState);
306 UpdateCompositionModeIcon(
307 engine, kMozcEnginePropertyIMEOffState->composition_mode);
308 }
309 is_activated_ = output.status().activated();
310 original_composition_mode_ = output.status().mode();
311 }
312 }
313
UpdateCompositionModeIcon(IBusEngine * engine,const commands::CompositionMode new_composition_mode)314 void PropertyHandler::UpdateCompositionModeIcon(
315 IBusEngine *engine, const commands::CompositionMode new_composition_mode) {
316 if (prop_composition_mode_ == NULL) {
317 return;
318 }
319
320 const MozcEngineProperty *entry = NULL;
321 for (size_t i = 0; i < kMozcEnginePropertiesSize; ++i) {
322 if (kMozcEngineProperties[i].composition_mode ==
323 new_composition_mode) {
324 entry = &(kMozcEngineProperties[i]);
325 break;
326 }
327 }
328 DCHECK(entry);
329
330 for (guint prop_index = 0; ; ++prop_index) {
331 IBusProperty *prop = ibus_prop_list_get(
332 ibus_property_get_sub_props(prop_composition_mode_), prop_index);
333 if (prop == NULL) {
334 break;
335 }
336 if (!g_strcmp0(entry->key, ibus_property_get_key(prop))) {
337 // Update the language panel.
338 ibus_property_set_icon(prop_composition_mode_,
339 GetIconPath(entry->icon).c_str());
340 // Update the radio menu item.
341 ibus_property_set_state(prop, PROP_STATE_CHECKED);
342 } else {
343 ibus_property_set_state(prop, PROP_STATE_UNCHECKED);
344 }
345 // No need to call unref since ibus_prop_list_get does not add ref.
346 }
347
348 const char *mode_symbol = entry->label_for_panel;
349 // Update the text icon for Gnome shell.
350 #ifdef MOZC_IBUS_HAS_SYMBOL
351 IBusText *symbol = ibus_text_new_from_static_string(mode_symbol);
352 ibus_property_set_symbol(prop_composition_mode_, symbol);
353 #endif // MOZC_IBUS_HAS_SYMBOL
354
355 const string &mode_label =
356 translator_->MaybeTranslate("Input Mode") + " (" + mode_symbol + ")";
357 IBusText *label = ibus_text_new_from_string(mode_label.c_str());
358 ibus_property_set_label(prop_composition_mode_, label);
359
360 ibus_engine_update_property(engine, prop_composition_mode_);
361 }
362
SetCompositionMode(IBusEngine * engine,commands::CompositionMode composition_mode)363 void PropertyHandler::SetCompositionMode(
364 IBusEngine *engine, commands::CompositionMode composition_mode) {
365 commands::SessionCommand command;
366 commands::Output output;
367
368 // In the case of Mozc, there are two state values of IME, IMEOn/IMEOff and
369 // composition_mode. However in IBus we can only control composition mode, not
370 // IMEOn/IMEOff. So we use one composition state as IMEOff and the others as
371 // IMEOn. This setting can be configured with setting
372 // kMozcEnginePropertyIMEOffState. If kMozcEnginePropertyIMEOffState is NULL,
373 // it means current IME should not be off.
374 if (kMozcEnginePropertyIMEOffState
375 && is_activated_
376 && composition_mode == kMozcEnginePropertyIMEOffState->composition_mode) {
377 command.set_type(commands::SessionCommand::TURN_OFF_IME);
378 command.set_composition_mode(original_composition_mode_);
379 client_->SendCommand(command, &output);
380 } else {
381 command.set_type(commands::SessionCommand::SWITCH_INPUT_MODE);
382 command.set_composition_mode(composition_mode);
383 client_->SendCommand(command, &output);
384 }
385 DCHECK(output.has_status());
386 original_composition_mode_ = output.status().mode();
387 is_activated_ = output.status().activated();
388 }
389
ProcessPropertyActivate(IBusEngine * engine,const gchar * property_name,guint property_state)390 void PropertyHandler::ProcessPropertyActivate(IBusEngine *engine,
391 const gchar *property_name,
392 guint property_state) {
393 if (IsDisabled()) {
394 return;
395 }
396
397 if (prop_mozc_tool_) {
398 for (guint prop_index = 0; ; ++prop_index) {
399 IBusProperty *prop = ibus_prop_list_get(
400 ibus_property_get_sub_props(prop_mozc_tool_), prop_index);
401 if (prop == NULL) {
402 break;
403 }
404 if (!g_strcmp0(property_name, ibus_property_get_key(prop))) {
405 const MozcEngineToolProperty *entry =
406 reinterpret_cast<const MozcEngineToolProperty*>(
407 g_object_get_data(G_OBJECT(prop), kGObjectDataKey));
408 DCHECK(entry->mode);
409 if (!client_->LaunchTool(entry->mode, "")) {
410 LOG(ERROR) << "cannot launch: " << entry->mode;
411 }
412 return;
413 }
414 }
415 }
416
417 if (property_state != PROP_STATE_CHECKED) {
418 return;
419 }
420
421 if (prop_composition_mode_) {
422 for (guint prop_index = 0; ; ++prop_index) {
423 IBusProperty *prop = ibus_prop_list_get(
424 ibus_property_get_sub_props(prop_composition_mode_), prop_index);
425 if (prop == NULL) {
426 break;
427 }
428 if (!g_strcmp0(property_name, ibus_property_get_key(prop))) {
429 const MozcEngineProperty *entry =
430 reinterpret_cast<const MozcEngineProperty*>(
431 g_object_get_data(G_OBJECT(prop), kGObjectDataKey));
432 SetCompositionMode(engine, entry->composition_mode);
433 UpdateCompositionModeIcon(
434 engine, entry->composition_mode);
435 break;
436 }
437 }
438 }
439 }
440
IsActivated() const441 bool PropertyHandler::IsActivated() const {
442 return is_activated_;
443 }
444
IsDisabled() const445 bool PropertyHandler::IsDisabled() const {
446 return is_disabled_;
447 }
448
GetOriginalCompositionMode() const449 commands::CompositionMode PropertyHandler::GetOriginalCompositionMode() const {
450 return original_composition_mode_;
451 }
452
453 } // namespace ibus
454 } // namespace mozc
455