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