1 /*
2 * SPDX-FileCopyrightText: 2017-2017 CSSlayer <wengxt@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 */
7
8 #include "engine.h"
9 #include "action.h"
10 #include "state.h"
11 #include <errno.h>
12 #include <fcitx-config/iniparser.h>
13 #include <fcitx-utils/log.h>
14 #include <fcitx-utils/standardpath.h>
15 #include <fcitx/addonfactory.h>
16 #include <fcitx/addonmanager.h>
17 #include <fcitx/inputcontext.h>
18 #include <fcitx/inputcontextmanager.h>
19 #include <fcitx/userinterfacemanager.h>
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <unistd.h>
24
25 FCITX_DEFINE_LOG_CATEGORY(anthy_logcategory, "anthy");
26
27 class AnthyFactory : public fcitx::AddonFactory {
create(fcitx::AddonManager * manager)28 fcitx::AddonInstance *create(fcitx::AddonManager *manager) override {
29 fcitx::registerDomain("fcitx5-anthy", FCITX_INSTALL_LOCALEDIR);
30 return new AnthyEngine(manager->instance());
31 }
32 };
33
34 struct AnthyStatus {
35 const char *icon;
36 const char *label;
37 const char *description;
38 };
39
40 #define UTF8_BRACKET_CORNER_BEGIN "\xE3\x80\x8C"
41 #define UTF8_BRACKET_CORNER_END "\xE3\x80\x8D"
42 #define UTF8_BRACKET_WIDE_BEGIN "\xEF\xBC\xBB"
43 #define UTF8_BRACKET_WIDE_END "\xEF\xBC\xBD"
44 #define UTF8_MIDDLE_DOT "\xE3\x83\xBB"
45 #define UTF8_SLASH_WIDE "\xEF\xBC\x8F"
46
47 constexpr std::array input_mode_status = {
48 AnthyStatus{"", "\xe3\x81\x82", N_("Hiragana")},
49 AnthyStatus{"", "\xe3\x82\xa2", N_("Katakana")},
50 AnthyStatus{"", "\xef\xbd\xb1", N_("Half width katakana")},
51 AnthyStatus{"", "A", N_("Direct input")},
52 AnthyStatus{"", "\xef\xbc\xa1", N_("Wide latin")},
53 };
54
55 constexpr std::array typing_method_status = {
56 AnthyStatus{"", N_("Romaji"), N_("Romaji")},
57 AnthyStatus{"", N_("Kana"), N_("Kana")},
58 AnthyStatus{"", N_("Nicola"), N_("Thumb shift")},
59 };
60
61 constexpr std::array conversion_mode_status = {
62 AnthyStatus{"", "\xE9\x80\xA3", N_("Multi segment")},
63 AnthyStatus{"", "\xE5\x8D\x98", N_("Single segment")},
64 AnthyStatus{"", "\xE9\x80\x90", N_("Convert as you type (Multi segment)")},
65 AnthyStatus{"", "\xE9\x80\x90", N_("Convert as you type (Single segment)")},
66 };
67
68 constexpr std::array period_style_status = {
69 AnthyStatus{"anthy-period-wide-latin", "\xEF\xBC\x8C\xEF\xBC\x8E",
70 "\xEF\xBC\x8C\xEF\xBC\x8E"},
71 AnthyStatus{"anthy-period-latin", ",.", ",."},
72 AnthyStatus{"anthy-period-japanese", "\xE3\x80\x81\xE3\x80\x82",
73 "\xE3\x80\x81\xE3\x80\x82"},
74 AnthyStatus{"anthy-period-wide-japanese", "\xEF\xBC\x8C\xE3\x80\x82",
75 "\xEF\xBC\x8C\xE3\x80\x82"},
76 };
77
78 constexpr std::array symbol_style_status = {
79 AnthyStatus{
80 "anthy-symbol",
81 UTF8_BRACKET_CORNER_BEGIN UTF8_BRACKET_CORNER_END UTF8_MIDDLE_DOT,
82 UTF8_BRACKET_CORNER_BEGIN UTF8_BRACKET_CORNER_END UTF8_MIDDLE_DOT},
83 AnthyStatus{
84 "anthy-symbol",
85 UTF8_BRACKET_CORNER_BEGIN UTF8_BRACKET_CORNER_END UTF8_SLASH_WIDE,
86 UTF8_BRACKET_CORNER_BEGIN UTF8_BRACKET_CORNER_END UTF8_SLASH_WIDE},
87 AnthyStatus{"anthy-symbol",
88 UTF8_BRACKET_WIDE_BEGIN UTF8_BRACKET_WIDE_END UTF8_MIDDLE_DOT,
89 UTF8_BRACKET_WIDE_BEGIN UTF8_BRACKET_WIDE_END UTF8_MIDDLE_DOT},
90 AnthyStatus{"anthy-symbol",
91 UTF8_BRACKET_WIDE_BEGIN UTF8_BRACKET_WIDE_END UTF8_SLASH_WIDE,
92 UTF8_BRACKET_WIDE_BEGIN UTF8_BRACKET_WIDE_END UTF8_SLASH_WIDE},
93 };
94
95 template <typename ModeType>
96 struct AnthyModeTraits;
97
98 template <typename ModeType, auto value>
99 struct AnthyStatusHelper {
statusAnthyStatusHelper100 static const AnthyStatus *status(ModeType mode) {
101 auto index = static_cast<int>(mode);
102 if (index < 0 || static_cast<size_t>(index) >= std::size(*value)) {
103 return nullptr;
104 }
105 return &(*value)[index];
106 }
iconAnthyStatusHelper107 static const char *icon(ModeType mode) {
108 if (auto *s = status(mode)) {
109 return s->icon;
110 }
111 return "";
112 }
labelAnthyStatusHelper113 static auto label(ModeType mode) {
114 if (auto *s = status(mode)) {
115 return s->label;
116 }
117 return "";
118 }
descriptionAnthyStatusHelper119 static auto description(ModeType mode) {
120 if (auto *s = status(mode)) {
121 return _(s->description);
122 }
123 return "";
124 }
125 };
126
127 template <>
128 struct AnthyModeTraits<InputMode>
129 : AnthyStatusHelper<InputMode, &input_mode_status> {
modeAnthyModeTraits130 static auto mode(AnthyState *state) { return state->inputMode(); }
setModeAnthyModeTraits131 static void setMode(AnthyState *state, InputMode mode) {
132 return state->setInputMode(mode);
133 }
iconAnthyModeTraits134 static const char *icon(InputMode mode) {
135 if (auto *s = status(mode)) {
136 return s->icon;
137 }
138 return "";
139 }
labelAnthyModeTraits140 static std::string label(InputMode mode) {
141 if (auto *s = status(mode)) {
142 return fcitx::stringutils::concat(s->label, " - ",
143 _(s->description));
144 }
145 return "";
146 }
147 };
148
149 template <>
150 struct AnthyModeTraits<TypingMethod>
151 : AnthyStatusHelper<TypingMethod, &typing_method_status> {
modeAnthyModeTraits152 static auto mode(AnthyState *state) { return state->typingMethod(); }
setModeAnthyModeTraits153 static void setMode(AnthyState *state, TypingMethod mode) {
154 return state->setTypingMethod(mode);
155 }
labelAnthyModeTraits156 static auto label(TypingMethod mode) {
157 if (auto *s = status(mode)) {
158 return _(s->label);
159 }
160 return "";
161 }
162 };
163
164 template <>
165 struct AnthyModeTraits<ConversionMode>
166 : AnthyStatusHelper<ConversionMode, &conversion_mode_status> {
modeAnthyModeTraits167 static auto mode(AnthyState *state) { return state->conversionMode(); }
setModeAnthyModeTraits168 static void setMode(AnthyState *state, ConversionMode mode) {
169 return state->setConversionMode(mode);
170 }
labelAnthyModeTraits171 static std::string label(ConversionMode mode) {
172 if (auto *s = status(mode)) {
173 return fcitx::stringutils::concat(s->label, " - ",
174 _(s->description));
175 }
176 return "";
177 }
178 };
179
180 template <>
181 struct AnthyModeTraits<PeriodCommaStyle>
182 : AnthyStatusHelper<PeriodCommaStyle, &period_style_status> {
modeAnthyModeTraits183 static auto mode(AnthyState *state) { return state->periodCommaStyle(); }
setModeAnthyModeTraits184 static void setMode(AnthyState *state, PeriodCommaStyle mode) {
185 return state->setPeriodCommaStyle(mode);
186 }
187 };
188
189 template <>
190 struct AnthyModeTraits<SymbolStyle>
191 : AnthyStatusHelper<SymbolStyle, &symbol_style_status> {
modeAnthyModeTraits192 static auto mode(AnthyState *state) { return state->symbolStyle(); }
setModeAnthyModeTraits193 static void setMode(AnthyState *state, SymbolStyle mode) {
194 return state->setSymbolStyle(mode);
195 }
196 };
197
198 template <typename ModeType>
199 class AnthyAction : public fcitx::Action {
200 public:
AnthyAction(AnthyEngine * engine)201 AnthyAction(AnthyEngine *engine) : engine_(engine) {}
shortText(fcitx::InputContext * ic) const202 std::string shortText(fcitx::InputContext *ic) const override {
203 auto state = engine_->state(ic);
204 auto mode = AnthyModeTraits<ModeType>::mode(state);
205 return AnthyModeTraits<ModeType>::label(mode);
206 }
longText(fcitx::InputContext * ic) const207 std::string longText(fcitx::InputContext *ic) const override {
208 auto state = engine_->state(ic);
209 auto mode = AnthyModeTraits<ModeType>::mode(state);
210 return AnthyModeTraits<ModeType>::description(mode);
211 }
icon(fcitx::InputContext * ic) const212 std::string icon(fcitx::InputContext *ic) const override {
213 auto state = engine_->state(ic);
214 auto mode = AnthyModeTraits<ModeType>::mode(state);
215 return AnthyModeTraits<ModeType>::icon(mode);
216 }
217
218 private:
219 AnthyEngine *engine_;
220 };
221
222 template <typename ModeType>
223 class AnthySubAction : public fcitx::SimpleAction {
224 public:
AnthySubAction(AnthyEngine * engine,ModeType mode)225 AnthySubAction(AnthyEngine *engine, ModeType mode)
226 : engine_(engine), mode_(mode) {
227 setShortText(AnthyModeTraits<ModeType>::label(mode));
228 setLongText(AnthyModeTraits<ModeType>::description(mode));
229 setIcon(AnthyModeTraits<ModeType>::icon(mode));
230 setCheckable(true);
231 }
isChecked(fcitx::InputContext * ic) const232 bool isChecked(fcitx::InputContext *ic) const override {
233 auto state = engine_->state(ic);
234 return mode_ == AnthyModeTraits<ModeType>::mode(state);
235 }
activate(fcitx::InputContext * ic)236 void activate(fcitx::InputContext *ic) override {
237 auto state = engine_->state(ic);
238 AnthyModeTraits<ModeType>::setMode(state, mode_);
239 }
240
241 private:
242 AnthyEngine *engine_;
243 const ModeType mode_;
244 };
245
246 using InputModeAction = AnthyAction<InputMode>;
247 using InputModeSubAction = AnthySubAction<InputMode>;
248
249 using TypingMethodAction = AnthyAction<TypingMethod>;
250 using TypingMethodSubAction = AnthySubAction<TypingMethod>;
251
252 using ConversionModeAction = AnthyAction<ConversionMode>;
253 using ConversionModeSubAction = AnthySubAction<ConversionMode>;
254
255 using PeriodCommaStyleAction = AnthyAction<PeriodCommaStyle>;
256 using PeriodCommaStyleSubAction = AnthySubAction<PeriodCommaStyle>;
257
258 using SymbolStyleAction = AnthyAction<SymbolStyle>;
259 using SymbolStyleSubAction = AnthySubAction<SymbolStyle>;
260
AnthyEngine(fcitx::Instance * instance)261 AnthyEngine::AnthyEngine(fcitx::Instance *instance)
262 : instance_(instance), factory_([this](fcitx::InputContext &ic) {
263 return new AnthyState(&ic, this, instance_);
264 }) {
265 if (anthy_init()) {
266 throw std::runtime_error("Failed to init anthy library.");
267 }
268
269 reloadConfig();
270 inputModeAction_ = std::make_unique<InputModeAction>(this);
271 inputModeAction_->setMenu(&inputModeMenu_);
272 instance_->userInterfaceManager().registerAction("anthy-input-mode",
273 inputModeAction_.get());
274
275 typingMethodAction_ = std::make_unique<TypingMethodAction>(this);
276 typingMethodAction_->setMenu(&typingMethodMenu_);
277 instance_->userInterfaceManager().registerAction("anthy-typing-method",
278 typingMethodAction_.get());
279
280 conversionModeAction_ = std::make_unique<ConversionModeAction>(this);
281 conversionModeAction_->setMenu(&conversionModeMenu_);
282 instance_->userInterfaceManager().registerAction(
283 "anthy-conversion-mode", conversionModeAction_.get());
284
285 periodStyleAction_ = std::make_unique<PeriodCommaStyleAction>(this);
286 periodStyleAction_->setMenu(&periodStyleMenu_);
287 instance_->userInterfaceManager().registerAction("anthy-period-style",
288 periodStyleAction_.get());
289
290 symbolStyleAction_ = std::make_unique<SymbolStyleAction>(this);
291 symbolStyleAction_->setMenu(&symbolStyleMenu_);
292 instance_->userInterfaceManager().registerAction("anthy-symbol-style",
293 symbolStyleAction_.get());
294
295 #define _ADD_ACTION(MODE, TYPE, type, NAME) \
296 subModeActions_.emplace_back( \
297 std::make_unique<TYPE##SubAction>(this, MODE)); \
298 instance_->userInterfaceManager().registerAction( \
299 NAME, subModeActions_.back().get()); \
300 type##Menu_.addAction(subModeActions_.back().get());
301
302 _ADD_ACTION(InputMode::HIRAGANA, InputMode, inputMode,
303 "anthy-input-mode-hiragana");
304 _ADD_ACTION(InputMode::KATAKANA, InputMode, inputMode,
305 "anthy-input-mode-katakana");
306 _ADD_ACTION(InputMode::HALF_KATAKANA, InputMode, inputMode,
307 "anthy-input-mode-half-katakana");
308 _ADD_ACTION(InputMode::LATIN, InputMode, inputMode,
309 "anthy-input-mode-latin");
310 _ADD_ACTION(InputMode::WIDE_LATIN, InputMode, inputMode,
311 "anthy-input-mode-wide-latin");
312
313 _ADD_ACTION(TypingMethod::ROMAJI, TypingMethod, typingMethod,
314 "anthy-typing-method-romaji");
315 _ADD_ACTION(TypingMethod::KANA, TypingMethod, typingMethod,
316 "anthy-typing-method-kana");
317 _ADD_ACTION(TypingMethod::NICOLA, TypingMethod, typingMethod,
318 "anthy-typing-method-nicola");
319
320 _ADD_ACTION(ConversionMode::MULTI_SEGMENT, ConversionMode, conversionMode,
321 "anthy-conversion-mode-multi");
322 _ADD_ACTION(ConversionMode::SINGLE_SEGMENT, ConversionMode, conversionMode,
323 "anthy-conversion-mode-single");
324 _ADD_ACTION(ConversionMode::MULTI_SEGMENT_IMMEDIATE, ConversionMode,
325 conversionMode, "anthy-conversion-mode-multi-imm");
326 _ADD_ACTION(ConversionMode::SINGLE_SEGMENT_IMMEDIATE, ConversionMode,
327 conversionMode, "anthy-conversion-mode-single-imm");
328
329 _ADD_ACTION(PeriodCommaStyle::WIDELATIN, PeriodCommaStyle, periodStyle,
330 "anthy-period-widelatin");
331 _ADD_ACTION(PeriodCommaStyle::LATIN, PeriodCommaStyle, periodStyle,
332 "anthy-period-latin");
333 _ADD_ACTION(PeriodCommaStyle::JAPANESE, PeriodCommaStyle, periodStyle,
334 "anthy-period-japanese");
335 _ADD_ACTION(PeriodCommaStyle::WIDELATIN_JAPANESE, PeriodCommaStyle,
336 periodStyle, "anthy-period-widelatin-japanese");
337
338 _ADD_ACTION(SymbolStyle::JAPANESE, SymbolStyle, symbolStyle,
339 "anthy-symbol-japanese");
340 _ADD_ACTION(SymbolStyle::WIDEBRACKET_WIDESLASH, SymbolStyle, symbolStyle,
341 "anthy-symbol-widebracket-wideslash");
342 _ADD_ACTION(SymbolStyle::CORNERBRACKET_MIDDLEDOT, SymbolStyle, symbolStyle,
343 "anthy-symbol-cornerbracket-middledot");
344 _ADD_ACTION(SymbolStyle::CORNERBRACKET_WIDESLASH, SymbolStyle, symbolStyle,
345 "anthy-symbol-cornerbracket-wideslash");
346
347 instance_->inputContextManager().registerProperty("anthyState", &factory_);
348
349 constructed_ = true;
350 }
351
~AnthyEngine()352 AnthyEngine::~AnthyEngine() { anthy_quit(); }
353
keyEvent(const fcitx::InputMethodEntry &,fcitx::KeyEvent & keyEvent)354 void AnthyEngine::keyEvent(const fcitx::InputMethodEntry &,
355 fcitx::KeyEvent &keyEvent) {
356 auto anthy = keyEvent.inputContext()->propertyFor(&factory_);
357 auto result = anthy->processKeyEvent(keyEvent);
358 anthy->updateUI();
359 if (result) {
360 keyEvent.filterAndAccept();
361 }
362 }
363
keyProfileName()364 std::string AnthyEngine::keyProfileName() {
365 const std::string key_profile[] = {"",
366 "atok.sty",
367 "canna.sty",
368 "msime.sty",
369 "vje-delta.sty",
370 "wnn.sty",
371 *config().keyProfile->keyThemeFile};
372
373 auto profile = static_cast<int>(*config().keyProfile->keyProfileEnum);
374 return key_profile[profile];
375 }
376
romajiTableName()377 std::string AnthyEngine::romajiTableName() {
378 const std::string key_profile[] = {
379 "", "atok.sty",
380 "azik.sty", "canna.sty",
381 "msime.sty", "vje-delta.sty",
382 "wnn.sty", *config().keyProfile->romajiFundamentalTable};
383
384 auto profile = static_cast<int>(*config().keyProfile->romajiTableEnum);
385
386 return key_profile[profile];
387 }
388
kanaTableName()389 std::string AnthyEngine::kanaTableName() {
390 const std::string key_profile[] = {
391 "",
392 "101kana.sty",
393 "tsuki-2-203-101.sty",
394 "tsuki-2-203-106.sty",
395 "qkana.sty",
396 *config().keyProfile->kanaFundamentalTable};
397
398 auto profile = static_cast<int>(*config().keyProfile->kanaTableEnum);
399
400 return key_profile[profile];
401 }
402
nicolaTableName()403 std::string AnthyEngine::nicolaTableName() {
404 const std::string key_profile[] = {
405 "",
406 "nicola-a.sty",
407 "nicola-f.sty",
408 "nicola-j.sty",
409 "oasys100j.sty"
410 "tron-dvorak.sty",
411 "tron-qwerty-jp.sty",
412 *config().keyProfile->nicolaFundamentalTable};
413
414 auto profile = static_cast<int>(*config().keyProfile->nicolaTableEnum);
415
416 return key_profile[profile];
417 }
418
fullFileName(const std::string & name)419 std::string AnthyEngine::fullFileName(const std::string &name) {
420 if (name.empty()) {
421 return {};
422 }
423 return fcitx::StandardPath::global().locate(
424 fcitx::StandardPath::Type::PkgData,
425 fcitx::stringutils::joinPath("anthy", name));
426 }
427
reloadConfig()428 void AnthyEngine::reloadConfig() {
429 fcitx::readAsIni(config_, "conf/anthy.conf");
430
431 std::string file;
432
433 StyleFile style;
434 // load key bindings
435 keyProfileFileLoaded_ = false;
436 auto filename = fullFileName(keyProfileName());
437 if (!filename.empty()) {
438 keyProfileFileLoaded_ = style.load(filename);
439 #define FOREACH_ACTION(key, _) \
440 do { \
441 std::string str = (ACTION_CONFIG_##key##_KEY); \
442 std::string keystr; \
443 style.getString(keystr, "KeyBindings", str); \
444 keyProfile_.hk_##key = fcitx::Key::keyListFromString(keystr); \
445 } while (0)
446 #include "action_defs.h"
447 #undef FOREACH_ACTION
448 }
449
450 customRomajiTableLoaded_ = false;
451 customRomajiTable_.clear();
452 const char *section_romaji = "RomajiTable/FundamentalTable";
453 filename = fullFileName(romajiTableName());
454 FCITX_ANTHY_DEBUG() << "Romaji: " << filename;
455 if (!filename.empty() && style.load(filename)) {
456 customRomajiTableLoaded_ = true;
457 customRomajiTable_ = style.key2kanaTable(section_romaji);
458 }
459
460 // load custom kana table
461 customKanaTableLoaded_ = false;
462 customKanaTable_.clear();
463 const char *section_kana = "KanaTable/FundamentalTable";
464 filename = fullFileName(kanaTableName());
465 if (!filename.empty() && style.load(filename)) {
466 customKanaTableLoaded_ = true;
467 customRomajiTable_ = style.key2kanaTable(section_kana);
468 }
469
470 customNicolaTableLoaded_ = false;
471 customNicolaTable_.clear();
472 const char *section_nicola = "NICOLATable/FundamentalTable";
473 filename = fullFileName(nicolaTableName());
474 if (!filename.empty() && style.load(filename)) {
475 customNicolaTableLoaded_ = true;
476 customNicolaTable_ = style.key2kanaTable(section_nicola);
477 }
478
479 if (factory_.registered()) {
480 instance_->inputContextManager().foreach(
481 [this](fcitx::InputContext *ic) {
482 auto state = this->state(ic);
483 state->configure();
484 return true;
485 });
486 };
487 }
488
subMode(const fcitx::InputMethodEntry &,fcitx::InputContext & ic)489 std::string AnthyEngine::subMode(const fcitx::InputMethodEntry &,
490 fcitx::InputContext &ic) {
491 auto state = this->state(&ic);
492 return AnthyModeTraits<InputMode>::description(state->inputMode());
493 }
494
activate(const fcitx::InputMethodEntry &,fcitx::InputContextEvent & event)495 void AnthyEngine::activate(const fcitx::InputMethodEntry &,
496 fcitx::InputContextEvent &event) {
497 // Setup action.
498 if (*config_.interface->showInputModeLabel) {
499 event.inputContext()->statusArea().addAction(
500 fcitx::StatusGroup::InputMethod, inputModeAction_.get());
501 }
502 if (*config_.interface->showTypingMethodLabel) {
503 event.inputContext()->statusArea().addAction(
504 fcitx::StatusGroup::InputMethod, typingMethodAction_.get());
505 }
506 if (*config_.interface->showConvModeLabel) {
507 event.inputContext()->statusArea().addAction(
508 fcitx::StatusGroup::InputMethod, conversionModeAction_.get());
509 }
510 if (*config_.interface->showPeriodStyleLabel) {
511 event.inputContext()->statusArea().addAction(
512 fcitx::StatusGroup::InputMethod, periodStyleAction_.get());
513 }
514 if (*config_.interface->showSymbolStyleLabel) {
515 event.inputContext()->statusArea().addAction(
516 fcitx::StatusGroup::InputMethod, symbolStyleAction_.get());
517 }
518 }
519
deactivate(const fcitx::InputMethodEntry &,fcitx::InputContextEvent & event)520 void AnthyEngine::deactivate(const fcitx::InputMethodEntry &,
521 fcitx::InputContextEvent &event) {
522 auto anthy = event.inputContext()->propertyFor(&factory_);
523 anthy->autoCommit(event);
524 }
525
reset(const fcitx::InputMethodEntry &,fcitx::InputContextEvent & event)526 void AnthyEngine::reset(const fcitx::InputMethodEntry &,
527 fcitx::InputContextEvent &event) {
528 auto anthy = event.inputContext()->propertyFor(&factory_);
529 anthy->reset();
530 }
531
532 FCITX_ADDON_FACTORY(AnthyFactory);
533