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