1 // Copyright 2012~2013, Weng Xuetian <wengxt@gmail.com>
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/fcitx/fcitx_mozc.h"
31 
32 #include <string>
33 #include <fcitx/candidate.h>
34 #include <fcitx/module.h>
35 #include <fcitx-config/xdg.h>
36 
37 #include "base/const.h"
38 #include "base/logging.h"
39 #include "base/process.h"
40 #include "base/util.h"
41 #include "base/file_util.h"
42 #include "base/system_util.h"
43 #include "unix/fcitx/mozc_connection.h"
44 #include "unix/fcitx/mozc_response_parser.h"
45 #include <fcitx/context.h>
46 
47 #define N_(x) (x)
48 
49 namespace
50 {
51 
52 static const std::string empty_string;
53 
54 const struct CompositionMode
55 {
56     const char *icon;
57     const char *label;
58     const char *description;
59     mozc::commands::CompositionMode mode;
60 } kPropCompositionModes[] =
61 {
62     {
63         "mozc-direct.png",
64         "A",
65         N_("Direct"),
66         mozc::commands::DIRECT,
67     }, {
68         "mozc-hiragana.png",
69         "\xe3\x81\x82",  // Hiragana letter A in UTF-8.
70         N_("Hiragana"),
71         mozc::commands::HIRAGANA,
72     }, {
73         "mozc-katakana_full.png",
74         "\xe3\x82\xa2",  // Katakana letter A.
75         N_("Full Katakana"),
76         mozc::commands::FULL_KATAKANA,
77     }, {
78         "mozc-alpha_half.png",
79         "A",
80         N_("Half ASCII"),
81         mozc::commands::HALF_ASCII,
82     }, {
83         "mozc-alpha_full.png",
84         "\xef\xbc\xa1",  // Full width ASCII letter A.
85         N_("Full ASCII"),
86         mozc::commands::FULL_ASCII,
87     }, {
88         "mozc-katakana_half.png",
89         "\xef\xbd\xb1",  // Half width Katakana letter A.
90         N_("Half Katakana"),
91         mozc::commands::HALF_KATAKANA,
92     },
93 };
94 const size_t kNumCompositionModes = arraysize ( kPropCompositionModes );
95 
96 // This array must correspond with the CompositionMode enum in the
97 // mozc/session/command.proto file.
98 static_assert (
99     mozc::commands::NUM_OF_COMPOSITIONS == arraysize ( kPropCompositionModes ),
100     "number of modes must match" );
101 
102 }  // namespace
103 
FcitxMozcGetCandidateWord(void * arg,FcitxCandidateWord * candWord)104 INPUT_RETURN_VALUE FcitxMozcGetCandidateWord(void* arg, FcitxCandidateWord* candWord)
105 {
106     mozc::fcitx::FcitxMozc* fcitx_mozc = (mozc::fcitx::FcitxMozc*) arg;
107     fcitx_mozc->select_candidate(candWord);
108 
109     return IRV_DISPLAY_CANDWORDS;
110 }
111 
112 
113 namespace mozc
114 {
115 
116 namespace fcitx
117 {
118 
119 // For unittests.
FcitxMozc(FcitxInstance * inst,MozcConnectionInterface * connection,MozcResponseParser * parser)120 FcitxMozc::FcitxMozc ( FcitxInstance* inst,
121                        MozcConnectionInterface *connection,
122                        MozcResponseParser *parser ) :
123         instance(inst),
124         input(FcitxInstanceGetInputState(inst)),
125         connection_ ( connection ),
126         parser_ ( parser ),
127         composition_mode_ ( mozc::commands::HIRAGANA )
128 {
129     // mozc::Logging::SetVerboseLevel(1);
130     VLOG ( 1 ) << "FcitxMozc created.";
131     const bool is_vertical = true;
132     parser_->set_use_annotation ( is_vertical );
133     InitializeBar();
134     InitializeMenu();
135     SetCompositionMode( mozc::commands::HIRAGANA );
136 }
137 
~FcitxMozc()138 FcitxMozc::~FcitxMozc()
139 {
140     VLOG ( 1 ) << "FcitxMozc destroyed.";
141 }
142 
143 // This function is called from SCIM framework when users press or release a
144 // key.
process_key_event(FcitxKeySym sym,uint32 keycode,uint32 state,bool layout_is_jp,bool is_key_up)145 bool FcitxMozc::process_key_event (FcitxKeySym sym, uint32 keycode, uint32 state, bool layout_is_jp, bool is_key_up)
146 {
147     string error;
148     mozc::commands::Output raw_response;
149     if ( !connection_->TrySendKeyEvent (
150                 GetInstance(), sym, keycode, state, composition_mode_, layout_is_jp, is_key_up, &raw_response, &error ) )
151     {
152         // TODO(yusukes): Show |error|.
153         return false;  // not consumed.
154     }
155 
156     return ParseResponse ( raw_response );
157 }
158 
159 // This function is called from SCIM framework when users click the candidate
160 // window.
select_candidate(FcitxCandidateWord * candWord)161 void FcitxMozc::select_candidate ( FcitxCandidateWord* candWord )
162 {
163     int32 *id = (int32*) candWord->priv;
164 
165     if ( *id == kBadCandidateId )
166     {
167         LOG ( ERROR ) << "The clicked candidate doesn't have unique ID.";
168         return;
169     }
170     VLOG ( 1 ) << "select_candidate, id=" << *id;
171 
172     string error;
173     mozc::commands::Output raw_response;
174     if ( !connection_->TrySendClick ( *id, &raw_response, &error ) )
175     {
176         LOG ( ERROR ) << "IPC failed. error=" << error;
177         SetAuxString ( error );
178         DrawAll();
179     }
180     else
181     {
182         ParseResponse ( raw_response );
183     }
184 }
185 
186 // This function is called from SCIM framework.
resetim()187 void FcitxMozc::resetim()
188 {
189     VLOG ( 1 ) << "resetim";
190     string error;
191     mozc::commands::Output raw_response;
192     if ( connection_->TrySendCommand (
193                 mozc::commands::SessionCommand::REVERT, &raw_response, &error ) )
194     {
195         parser_->ParseResponse ( raw_response, this );
196     }
197     ClearAll();  // just in case.
198     DrawAll();
199 
200 }
201 
reset()202 void FcitxMozc::reset()
203 {
204     FcitxIM* im = FcitxInstanceGetCurrentIM(instance);
205     if (!im || strcmp(im->uniqueName, "mozc") != 0) {
206         FcitxUISetStatusVisable(instance, "mozc-tool", false);
207         FcitxUISetStatusVisable(instance, "mozc-composition-mode", false);
208     }
209     else {
210         FcitxUISetStatusVisable(instance, "mozc-tool", true);
211         FcitxUISetStatusVisable(instance, "mozc-composition-mode", true);
212         connection_->UpdatePreeditMethod();
213     }
214 }
215 
paging(bool prev)216 bool FcitxMozc::paging(bool prev)
217 {
218     VLOG ( 1 ) << "paging";
219     string error;
220     mozc::commands::SessionCommand::CommandType command =
221         prev ? mozc::commands::SessionCommand::CONVERT_PREV_PAGE
222              : mozc::commands::SessionCommand::CONVERT_NEXT_PAGE;
223     mozc::commands::Output raw_response;
224     if ( connection_->TrySendCommand (
225         command, &raw_response, &error ) )
226     {
227         parser_->ParseResponse ( raw_response, this );
228         return true;
229     }
230     return false;
231 }
232 
233 // This function is called from SCIM framework when the ic gets focus.
init()234 void FcitxMozc::init()
235 {
236     VLOG ( 1 ) << "init";
237     boolean flag = true;
238     FcitxInstanceSetContext(instance, CONTEXT_DISABLE_AUTOENG, &flag);
239     FcitxInstanceSetContext(instance, CONTEXT_DISABLE_FULLWIDTH, &flag);
240     FcitxInstanceSetContext(instance, CONTEXT_DISABLE_QUICKPHRASE, &flag);
241     FcitxInstanceSetContext(instance, CONTEXT_IM_KEYBOARD_LAYOUT, "jp");
242     FcitxInstanceSetContext(instance, "CONTEXT_DISABLE_AUTO_FIRST_CANDIDATE_HIGHTLIGHT", &flag);
243 
244     connection_->UpdatePreeditMethod();
245     DrawAll();
246 }
247 
248 // This function is called when the ic loses focus.
focus_out()249 void FcitxMozc::focus_out()
250 {
251     VLOG ( 1 ) << "focus_out";
252     string error;
253     mozc::commands::Output raw_response;
254     if ( connection_->TrySendCommand (
255                 mozc::commands::SessionCommand::REVERT, &raw_response, &error ) )
256     {
257         parser_->ParseResponse ( raw_response, this );
258     }
259     ClearAll();  // just in case.
260     DrawAll();
261     // TODO(yusukes): Call client::SyncData() like ibus-mozc.
262 }
263 
264 
ParseResponse(const mozc::commands::Output & raw_response)265 bool FcitxMozc::ParseResponse ( const mozc::commands::Output &raw_response )
266 {
267     ClearAll();
268     const bool consumed = parser_->ParseResponse ( raw_response, this );
269     if ( !consumed )
270     {
271         VLOG ( 1 ) << "The input was not consumed by Mozc.";
272     }
273     OpenUrl();
274     DrawAll();
275     return consumed;
276 }
277 
SetResultString(const std::string & result_string)278 void FcitxMozc::SetResultString ( const std::string &result_string )
279 {
280     FcitxInstanceCommitString(instance, FcitxInstanceGetCurrentIC(instance), result_string.c_str());
281 }
282 
SetPreeditInfo(const PreeditInfo * preedit_info)283 void FcitxMozc::SetPreeditInfo ( const PreeditInfo *preedit_info )
284 {
285     preedit_info_.reset ( preedit_info );
286 }
287 
SetAuxString(const std::string & str)288 void FcitxMozc::SetAuxString ( const std::string &str )
289 {
290     aux_ = str;
291 }
292 
SetCompositionMode(mozc::commands::CompositionMode mode)293 void FcitxMozc::SetCompositionMode ( mozc::commands::CompositionMode mode )
294 {
295     composition_mode_ = mode;
296     DCHECK(composition_mode_ < kNumCompositionModes);
297     if (composition_mode_ < kNumCompositionModes) {
298         FcitxUISetStatusString(instance,
299                                "mozc-composition-mode",
300                                _(kPropCompositionModes[composition_mode_].label),
301                                _(kPropCompositionModes[composition_mode_].description));
302     }
303 }
304 
SendCompositionMode(mozc::commands::CompositionMode mode)305 void FcitxMozc::SendCompositionMode(mozc::commands::CompositionMode mode)
306 {
307     // Send the SWITCH_INPUT_MODE command.
308     string error;
309     mozc::commands::Output raw_response;
310     if (connection_->TrySendCompositionMode(
311             kPropCompositionModes[mode].mode, &raw_response, &error)) {
312         parser_->ParseResponse(raw_response, this);
313     }
314 }
315 
316 
SetUrl(const string & url)317 void FcitxMozc::SetUrl ( const string &url )
318 {
319     url_ = url;
320 }
321 
ClearAll()322 void FcitxMozc::ClearAll()
323 {
324     SetPreeditInfo ( NULL );
325     SetAuxString ( "" );
326     FcitxCandidateWordReset(FcitxInputStateGetCandidateList(input));
327     url_.clear();
328 }
329 
DrawPreeditInfo()330 void FcitxMozc::DrawPreeditInfo()
331 {
332     FcitxMessages* preedit = FcitxInputStateGetPreedit(input);
333     FcitxMessages* clientpreedit = FcitxInputStateGetClientPreedit(input);
334     FcitxMessagesSetMessageCount(preedit, 0);
335     FcitxMessagesSetMessageCount(clientpreedit, 0);
336     if ( preedit_info_.get() )
337     {
338         VLOG ( 1 ) << "DrawPreeditInfo: cursor=" << preedit_info_->cursor_pos;
339 
340         FcitxInputContext* ic = FcitxInstanceGetCurrentIC(instance);
341         boolean supportPreedit = FcitxInstanceICSupportPreedit(instance, ic);
342 
343         if (!supportPreedit)
344             FcitxInputStateSetShowCursor(input, true);
345 
346         for (int i = 0; i < preedit_info_->preedit.size(); i ++) {
347             if (!supportPreedit)
348                 FcitxMessagesAddMessageAtLast(preedit, preedit_info_->preedit[i].type, "%s", preedit_info_->preedit[i].str.c_str());
349             FcitxMessagesAddMessageAtLast(clientpreedit, preedit_info_->preedit[i].type, "%s", preedit_info_->preedit[i].str.c_str());
350         }
351         if (!supportPreedit)
352             FcitxInputStateSetCursorPos(input, preedit_info_->cursor_pos);
353         FcitxInputStateSetClientCursorPos(input, preedit_info_->cursor_pos);
354     }
355     else {
356         FcitxInputStateSetShowCursor(input, false);
357     }
358     if ( !aux_.empty() ) {
359         FcitxMessagesAddMessageAtLast(preedit, MSG_TIPS, "%s[%s]", preedit_info_.get() ? " " : "", aux_.c_str());
360     }
361 }
362 
DrawAux()363 void FcitxMozc::DrawAux()
364 {
365     FcitxMessages* auxUp = FcitxInputStateGetAuxUp(input);
366     FcitxMessages* auxDown = FcitxInputStateGetAuxDown(input);
367     FcitxMessagesSetMessageCount(auxUp, 0);
368     FcitxMessagesSetMessageCount(auxDown, 0);
369 }
370 
DrawAll()371 void FcitxMozc::DrawAll()
372 {
373     DrawPreeditInfo();
374     DrawAux();
375 }
376 
OpenUrl()377 void FcitxMozc::OpenUrl()
378 {
379     if ( url_.empty() )
380     {
381         return;
382     }
383     mozc::Process::OpenBrowser ( url_ );
384     url_.clear();
385 }
386 
GetCompositionIconName(void * arg)387 static const char* GetCompositionIconName(void* arg)
388 {
389     FcitxMozc* mozc = (FcitxMozc*) arg;
390     return mozc->GetCurrentCompositionModeIcon().c_str();
391 }
392 
393 
GetMozcToolIcon(void * arg)394 static const char* GetMozcToolIcon(void* arg)
395 {
396     FcitxMozc* mozc = (FcitxMozc*) arg;
397     return mozc->GetIconFile("mozc-tool.png").c_str();
398 }
399 
InitializeBar()400 void FcitxMozc::InitializeBar()
401 {
402     VLOG ( 1 ) << "Registering properties";
403 
404     FcitxUIRegisterComplexStatus(instance, this,
405         "mozc-composition-mode",
406         _("Composition Mode"),
407         _("Composition Mode"),
408         NULL,
409         GetCompositionIconName
410     );
411 
412     if ( mozc::FileUtil::FileExists ( mozc::FileUtil::JoinPath (
413                                       mozc::SystemUtil::GetServerDirectory(), mozc::kMozcTool ) ) )
414     {
415         FcitxUIRegisterComplexStatus(instance, this,
416             "mozc-tool",
417             _("Tool"),
418             _("Tool"),
419             NULL,
420             GetMozcToolIcon
421         );
422     }
423     FcitxUISetStatusVisable(instance, "mozc-tool", false);
424     FcitxUISetStatusVisable(instance, "mozc-composition-mode", false);
425 }
426 
CompositionMenuAction(struct _FcitxUIMenu * menu,int index)427 boolean CompositionMenuAction(struct _FcitxUIMenu *menu, int index)
428 {
429     FcitxMozc* mozc = (FcitxMozc*) menu->priv;
430     mozc->SendCompositionMode((mozc::commands::CompositionMode) index);
431     return true;
432 }
433 
UpdateCompositionMenu(struct _FcitxUIMenu * menu)434 void UpdateCompositionMenu(struct _FcitxUIMenu *menu)
435 {
436     FcitxMozc* mozc = (FcitxMozc*) menu->priv;
437     menu->mark = mozc->GetCompositionMode();
438 }
439 
ToolMenuAction(struct _FcitxUIMenu * menu,int index)440 boolean ToolMenuAction(struct _FcitxUIMenu *menu, int index)
441 {
442     string args;
443     size_t pid = 0;
444 
445     switch(index) {
446         case 0:
447             args = "--mode=config_dialog";
448             break;
449         case 1:
450             args = "--mode=dictionary_tool";
451             break;
452         case 2:
453             args = "--mode=hand_writing";
454             break;
455         case 3:
456             args = "--mode=character_palette";
457             break;
458         case 4:
459             args = "--mode=word_register_dialog";
460             break;
461         case 5:
462             args = "--mode=about_dialog";
463             break;
464     }
465 
466     mozc::Process::SpawnProcess(mozc::FileUtil::JoinPath(mozc::SystemUtil::GetToolPath(), "mozc_tool"), args, &pid);
467     return true;
468 }
469 
UpdateToolMenu(struct _FcitxUIMenu * menu)470 void UpdateToolMenu(struct _FcitxUIMenu *menu)
471 {
472     return;
473 }
474 
InitializeMenu()475 void FcitxMozc::InitializeMenu()
476 {
477     FcitxMenuInit(&this->compositionMenu);
478     compositionMenu.name = strdup(_("Composition Mode"));
479     compositionMenu.candStatusBind = strdup("mozc-composition-mode");
480     compositionMenu.UpdateMenu = UpdateCompositionMenu;
481     compositionMenu.MenuAction = CompositionMenuAction;
482     compositionMenu.priv = this;
483     compositionMenu.isSubMenu = false;
484     int i;
485     for (i = 0; i < kNumCompositionModes; i ++)
486         FcitxMenuAddMenuItem(&compositionMenu, _(kPropCompositionModes[i].description), MENUTYPE_SIMPLE, NULL);
487 
488     FcitxUIRegisterMenu(instance, &compositionMenu);
489 
490     FcitxMenuInit(&this->toolMenu);
491     toolMenu.name = strdup(_("Mozc Tool"));
492     toolMenu.candStatusBind = strdup("mozc-tool");
493     toolMenu.UpdateMenu = UpdateToolMenu;
494     toolMenu.MenuAction = ToolMenuAction;
495     toolMenu.priv = this;
496     toolMenu.isSubMenu = false;
497     FcitxMenuAddMenuItem(&toolMenu, _("Configuration Tool"), MENUTYPE_SIMPLE, NULL);
498     FcitxMenuAddMenuItem(&toolMenu, _("Dictionary Tool"), MENUTYPE_SIMPLE, NULL);
499     FcitxMenuAddMenuItem(&toolMenu, _("Hand Writing"), MENUTYPE_SIMPLE, NULL);
500     FcitxMenuAddMenuItem(&toolMenu, _("Character Palette"), MENUTYPE_SIMPLE, NULL);
501     FcitxMenuAddMenuItem(&toolMenu, _("Add Word"), MENUTYPE_SIMPLE, NULL);
502     FcitxMenuAddMenuItem(&toolMenu, _("About Mozc"), MENUTYPE_SIMPLE, NULL);
503     FcitxUIRegisterMenu(instance, &toolMenu);
504 }
505 
SendCommand(const mozc::commands::SessionCommand & session_command,commands::Output * new_output)506 bool FcitxMozc::SendCommand(const mozc::commands::SessionCommand& session_command, commands::Output* new_output)
507 {
508     string error;
509     return connection_->TrySendRawCommand(session_command, new_output, &error);
510 }
511 
512 
GetInputState()513 FcitxInputState* FcitxMozc::GetInputState()
514 {
515     return input;
516 }
517 
GetIconFile(const std::string key)518 const std::string& FcitxMozc::GetIconFile(const std::string key)
519 {
520     if (iconMap.count(key)) {
521         return iconMap[key];
522     }
523 
524     char* retFile;
525     FILE* fp = FcitxXDGGetFileWithPrefix("mozc/icon", key.c_str(), "r", &retFile);
526     if (fp)
527         fclose(fp);
528     if (retFile) {
529         iconMap[key] = std::string(retFile);
530         free(retFile);
531     }
532     else {
533         iconMap[key] = "";
534     }
535     return iconMap[key];
536 }
537 
538 
GetCurrentCompositionModeIcon()539 const std::string& FcitxMozc::GetCurrentCompositionModeIcon() {
540     DCHECK(composition_mode_ < kNumCompositionModes);
541     if (composition_mode_ < kNumCompositionModes) {
542         return GetIconFile(kPropCompositionModes[composition_mode_].icon);
543     }
544     return empty_string;
545 }
546 
SetUsage(const string & title_,const string & description_)547 void FcitxMozc::SetUsage(const string& title_, const string& description_)
548 {
549     title = title_;
550     description = description_;
551 }
552 
GetUsage()553 std::pair< string, string > FcitxMozc::GetUsage()
554 {
555     return make_pair(title, description);
556 }
557 
558 }  // namespace fcitx
559 
560 }  // namespace mozc_unix_scim
561