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