1 /*
2  * SPDX-FileCopyrightText: 2017~2017 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 
7 #include "rimeengine.h"
8 #include "notifications_public.h"
9 #include "rimestate.h"
10 #include <cstring>
11 #include <dirent.h>
12 #include <fcitx-utils/event.h>
13 #include <fcitx-utils/fs.h>
14 #include <fcitx-utils/i18n.h>
15 #include <fcitx-utils/log.h>
16 #include <fcitx-utils/standardpath.h>
17 #include <fcitx/candidatelist.h>
18 #include <fcitx/inputcontext.h>
19 #include <fcitx/inputcontextmanager.h>
20 #include <fcitx/inputpanel.h>
21 #include <fcitx/userinterfacemanager.h>
22 #include <rime_api.h>
23 
24 FCITX_DEFINE_LOG_CATEGORY(rime, "rime");
25 
26 namespace fcitx {
27 
28 class IMAction : public Action {
29 public:
IMAction(RimeEngine * engine)30     IMAction(RimeEngine *engine) : engine_(engine) {}
31 
shortText(InputContext * ic) const32     std::string shortText(InputContext *ic) const override {
33         auto state = engine_->state(ic);
34         RIME_STRUCT(RimeStatus, status);
35         std::string result;
36         if (state->getStatus(&status)) {
37             if (status.is_disabled) {
38                 result = "\xe2\x8c\x9b";
39             } else if (status.is_ascii_mode) {
40                 result = "A";
41             } else if (status.schema_name && status.schema_name[0] != '.') {
42                 result = status.schema_name;
43             } else {
44                 result = "中";
45             }
46             engine_->api()->free_status(&status);
47         } else {
48             result = "\xe2\x8c\x9b";
49         }
50         return result;
51     }
52 
longText(InputContext * ic) const53     std::string longText(InputContext *ic) const override {
54         auto state = engine_->state(ic);
55         std::string result;
56         RIME_STRUCT(RimeStatus, status);
57         if (state->getStatus(&status)) {
58             if (status.schema_name) {
59                 result = status.schema_name;
60             }
61             engine_->api()->free_status(&status);
62         }
63         return result;
64     }
65 
icon(InputContext * ic) const66     std::string icon(InputContext *ic) const override {
67         auto state = engine_->state(ic);
68         RIME_STRUCT(RimeStatus, status);
69         if (state->getStatus(&status)) {
70             if (status.is_disabled) {
71                 return "fcitx-rime-disabled";
72             }
73         }
74         return "fcitx-rime-im";
75     }
76 
77 private:
78     RimeEngine *engine_;
79 };
80 
RimeEngine(Instance * instance)81 RimeEngine::RimeEngine(Instance *instance)
82     : instance_(instance), api_(rime_get_api()),
83       factory_([this](InputContext &) { return new RimeState(this); }) {
84     imAction_ = std::make_unique<IMAction>(this);
85     instance_->userInterfaceManager().registerAction("fcitx-rime-im",
86                                                      imAction_.get());
87     imAction_->setMenu(&schemaMenu_);
88     eventDispatcher_.attach(&instance_->eventLoop());
89     deployAction_.setIcon("fcitx-rime-deploy");
90     deployAction_.setShortText(_("Deploy"));
__anon679035410202(InputContext *ic) 91     deployAction_.connect<SimpleAction::Activated>([this](InputContext *ic) {
92         deploy();
93         auto state = this->state(ic);
94         if (ic->hasFocus()) {
95             state->updateUI(ic, false);
96         }
97     });
98     instance_->userInterfaceManager().registerAction("fcitx-rime-deploy",
99                                                      &deployAction_);
100 
101     syncAction_.setIcon("fcitx-rime-sync");
102     syncAction_.setShortText(_("Synchronize"));
103 
__anon679035410302(InputContext *ic) 104     syncAction_.connect<SimpleAction::Activated>([this](InputContext *ic) {
105         sync();
106         auto state = this->state(ic);
107         if (ic->hasFocus()) {
108             state->updateUI(ic, false);
109         }
110     });
111     instance_->userInterfaceManager().registerAction("fcitx-rime-sync",
112                                                      &syncAction_);
113     reloadConfig();
114 }
115 
~RimeEngine()116 RimeEngine::~RimeEngine() {
117     factory_.unregister();
118     try {
119         if (api_) {
120             api_->finalize();
121         }
122     } catch (const std::exception &e) {
123         RIME_ERROR() << e.what();
124     }
125 }
126 
rimeStart(bool fullcheck)127 void RimeEngine::rimeStart(bool fullcheck) {
128     if (!api_) {
129         return;
130     }
131 
132     RIME_DEBUG() << "Rime Start (fullcheck: " << fullcheck << ")";
133 
134     auto userDir = stringutils::joinPath(
135         StandardPath::global().userDirectory(StandardPath::Type::PkgData),
136         "rime");
137     if (!fs::makePath(userDir)) {
138         if (!fs::isdir(userDir)) {
139             RIME_ERROR() << "Failed to create user directory: " << userDir;
140         }
141     }
142     const char *sharedDataDir = RIME_DATA_DIR;
143 
144     RIME_STRUCT(RimeTraits, fcitx_rime_traits);
145     fcitx_rime_traits.shared_data_dir = sharedDataDir;
146     fcitx_rime_traits.app_name = "rime.fcitx-rime";
147     fcitx_rime_traits.user_data_dir = userDir.c_str();
148     fcitx_rime_traits.distribution_name = "Rime";
149     fcitx_rime_traits.distribution_code_name = "fcitx-rime";
150     fcitx_rime_traits.distribution_version = FCITX_RIME_VERSION;
151 
152 #ifdef FCITX_RIME_LOAD_PLUGIN
153     std::vector<const char *> modules;
154     // When it is not test, rime will load the default set.
155     RIME_DEBUG() << "Modules: " << *config_.modules;
156     if (!config_.modules->empty()) {
157         modules.push_back("default");
158         for (const std::string &module : *config_.modules) {
159             modules.push_back(module.data());
160         }
161         modules.push_back(nullptr);
162         fcitx_rime_traits.modules = modules.data();
163     } else {
164         fcitx_rime_traits.modules = nullptr;
165     }
166 #else
167     fcitx_rime_traits.modules = nullptr;
168 #endif
169 
170     if (firstRun_) {
171         api_->setup(&fcitx_rime_traits);
172         firstRun_ = false;
173     }
174     api_->initialize(&fcitx_rime_traits);
175     api_->set_notification_handler(&rimeNotificationHandler, this);
176     api_->start_maintenance(fullcheck);
177 }
178 
reloadConfig()179 void RimeEngine::reloadConfig() {
180     readAsIni(config_, "conf/rime.conf");
181     updateConfig();
182 }
183 
setSubConfig(const std::string & path,const RawConfig &)184 void RimeEngine::setSubConfig(const std::string &path, const RawConfig &) {
185     if (path == "deploy") {
186         deploy();
187     } else if (path == "sync") {
188         sync();
189     }
190 }
191 
updateConfig()192 void RimeEngine::updateConfig() {
193     RIME_DEBUG() << "Rime UpdateConfig";
194     factory_.unregister();
195     if (api_) {
196         try {
197             api_->finalize();
198         } catch (const std::exception &e) {
199             RIME_ERROR() << e.what();
200         }
201     }
202 
203 #ifdef FCITX_RIME_LOAD_PLUGIN
204     std::vector<std::string> plugins;
205     if (*config_.autoloadPlugins) {
206         auto closedir0 = [](DIR *dir) {
207             if (dir) {
208                 closedir(dir);
209             }
210         };
211 
212         const char *libdir = StandardPath::fcitxPath("libdir");
213         std::unique_ptr<DIR, void (*)(DIR *)> scopedDir{opendir(libdir),
214                                                         closedir0};
215         if (scopedDir) {
216             auto dir = scopedDir.get();
217             struct dirent *drt;
218             while ((drt = readdir(dir)) != nullptr) {
219                 if (strcmp(drt->d_name, ".") == 0 ||
220                     strcmp(drt->d_name, "..") == 0) {
221                     continue;
222                 }
223 
224                 auto name = drt->d_name;
225                 if (stringutils::startsWith(name, "librime-") &&
226                     stringutils::endsWith(name, ".so")) {
227                     plugins.push_back(
228                         stringutils::joinPath(libdir, std::move(name)));
229                 }
230             }
231         }
232     } else {
233         plugins = *config_.plugins;
234     }
235 
236     for (const std::string &plugin : plugins) {
237         if (pluginPool_.count(plugin)) {
238             continue;
239         }
240         pluginPool_.emplace(plugin, Library(plugin));
241         pluginPool_[plugin].load({LibraryLoadHint::ExportExternalSymbolsHint});
242         RIME_DEBUG() << "Trying to load rime plugin: " << plugin;
243         if (!pluginPool_[plugin].loaded()) {
244             RIME_ERROR() << "Failed to load plugin: " << plugin
245                          << " error: " << pluginPool_[plugin].error();
246             pluginPool_.erase(plugin);
247         }
248     }
249 #endif
250 
251     rimeStart(false);
252     instance_->inputContextManager().registerProperty("rimeState", &factory_);
253     updateSchemaMenu();
254 }
activate(const InputMethodEntry &,InputContextEvent & event)255 void RimeEngine::activate(const InputMethodEntry &, InputContextEvent &event) {
256     event.inputContext()->statusArea().addAction(StatusGroup::InputMethod,
257                                                  imAction_.get());
258     event.inputContext()->statusArea().addAction(StatusGroup::InputMethod,
259                                                  &deployAction_);
260     event.inputContext()->statusArea().addAction(StatusGroup::InputMethod,
261                                                  &syncAction_);
262 }
deactivate(const InputMethodEntry & entry,InputContextEvent & event)263 void RimeEngine::deactivate(const InputMethodEntry &entry,
264                             InputContextEvent &event) {
265     if (event.type() == EventType::InputContextSwitchInputMethod &&
266         *config_.commitWhenDeactivate) {
267         auto inputContext = event.inputContext();
268         auto state = inputContext->propertyFor(&factory_);
269         state->commitPreedit(inputContext);
270     }
271     reset(entry, event);
272 }
keyEvent(const InputMethodEntry & entry,KeyEvent & event)273 void RimeEngine::keyEvent(const InputMethodEntry &entry, KeyEvent &event) {
274     FCITX_UNUSED(entry);
275     RIME_DEBUG() << "Rime receive key: " << event.rawKey() << " "
276                  << event.isRelease();
277     auto inputContext = event.inputContext();
278     auto state = inputContext->propertyFor(&factory_);
279     state->keyEvent(event);
280 }
281 
reset(const InputMethodEntry &,InputContextEvent & event)282 void RimeEngine::reset(const InputMethodEntry &, InputContextEvent &event) {
283     auto inputContext = event.inputContext();
284 
285     auto state = inputContext->propertyFor(&factory_);
286     state->clear();
287     inputContext->inputPanel().reset();
288     inputContext->updatePreedit();
289     inputContext->updateUserInterface(UserInterfaceComponent::InputPanel);
290 }
291 
save()292 void RimeEngine::save() {}
293 
rimeNotificationHandler(void * context,RimeSessionId session,const char * messageType,const char * messageValue)294 void RimeEngine::rimeNotificationHandler(void *context, RimeSessionId session,
295                                          const char *messageType,
296                                          const char *messageValue) {
297     RIME_DEBUG() << "Notification: " << session << " " << messageType << " "
298                  << messageValue;
299     RimeEngine *that = static_cast<RimeEngine *>(context);
300     that->eventDispatcher_.schedule(
301         [that, messageType = std::string(messageType),
302          messageValue = std::string(messageValue)]() {
303             that->notify(messageType, messageValue);
304         });
305 }
306 
notify(const std::string & messageType,const std::string & messageValue)307 void RimeEngine::notify(const std::string &messageType,
308                         const std::string &messageValue) {
309     const char *message = nullptr;
310     if (messageType == "deploy") {
311         if (messageValue == "start") {
312             message = _("Rime is under maintenance. It may take a few "
313                         "seconds. Please wait until it is finished...");
314         } else if (messageValue == "success") {
315             message = _("Rime is ready.");
316             updateSchemaMenu();
317         } else if (messageValue == "failure") {
318             message = _("Rime has encountered an error. "
319                         "See /tmp/rime.fcitx.ERROR for details.");
320         }
321     }
322 
323     auto notifications = this->notifications();
324     if (message && notifications) {
325         notifications->call<INotifications::showTip>(
326             "fcitx-rime-deploy", _("Rime"), "fcitx-rime-deploy", _("Rime"),
327             message, -1);
328     }
329     timeEvent_ = instance_->eventLoop().addTimeEvent(
330         CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 1000000, 0,
331         [this](EventSourceTime *, uint64_t) {
332             if (auto *ic = instance_->lastFocusedInputContext()) {
333                 imAction_->update(ic);
334                 ic->updateUserInterface(UserInterfaceComponent::StatusArea);
335             }
336             return true;
337         });
338 }
339 
state(InputContext * ic)340 RimeState *RimeEngine::state(InputContext *ic) {
341     return ic->propertyFor(&factory_);
342 }
343 
subMode(const InputMethodEntry &,InputContext & ic)344 std::string RimeEngine::subMode(const InputMethodEntry &, InputContext &ic) {
345     auto rimeState = state(&ic);
346     return rimeState->subMode();
347 }
348 
subModeLabelImpl(const InputMethodEntry &,InputContext & ic)349 std::string RimeEngine::subModeLabelImpl(const InputMethodEntry &,
350                                          InputContext &ic) {
351     auto rimeState = state(&ic);
352     return rimeState->subModeLabel();
353 }
354 
subModeIconImpl(const InputMethodEntry &,InputContext & ic)355 std::string RimeEngine::subModeIconImpl(const InputMethodEntry &,
356                                         InputContext &ic) {
357     std::string result = "fcitx-rime";
358     if (!api_ || !factory_.registered()) {
359         return result;
360     }
361     auto state = this->state(&ic);
362     RIME_STRUCT(RimeStatus, status);
363     if (state->getStatus(&status)) {
364         if (status.is_disabled) {
365             result = "fcitx-rime-disable";
366         } else if (status.is_ascii_mode) {
367             result = "fcitx-rime-latin";
368         } else {
369             result = "fcitx-rime";
370         }
371         api_->free_status(&status);
372     }
373     return result;
374 }
375 
deploy()376 void RimeEngine::deploy() {
377     RIME_DEBUG() << "Rime Deploy";
378     instance_->inputContextManager().foreach([this](InputContext *ic) {
379         auto state = this->state(ic);
380         state->release();
381         return true;
382     });
383     api_->finalize();
384     rimeStart(true);
385 }
386 
sync()387 void RimeEngine::sync() { api_->sync_user_data(); }
388 
updateSchemaMenu()389 void RimeEngine::updateSchemaMenu() {
390     if (!api_) {
391         return;
392     }
393 
394     schemActions_.clear();
395     RimeSchemaList list;
396     list.size = 0;
397     if (api_->get_schema_list(&list)) {
398         schemActions_.emplace_back();
399 
400         schemActions_.back().setShortText(_("Latin Mode"));
401         schemActions_.back().connect<SimpleAction::Activated>(
402             [this](InputContext *ic) {
403                 auto state = ic->propertyFor(&factory_);
404                 state->setLatinMode(true);
405                 imAction_->update(ic);
406             });
407         instance_->userInterfaceManager().registerAction(&schemActions_.back());
408         schemaMenu_.addAction(&schemActions_.back());
409         for (size_t i = 0; i < list.size; i++) {
410             schemActions_.emplace_back();
411             std::string schemaId = list.list[i].schema_id;
412             auto &schemaAction = schemActions_.back();
413             schemaAction.setShortText(list.list[i].name);
414             schemaAction.connect<SimpleAction::Activated>(
415                 [this, schemaId](InputContext *ic) {
416                     auto state = ic->propertyFor(&factory_);
417                     state->selectSchema(schemaId);
418                     imAction_->update(ic);
419                 });
420             instance_->userInterfaceManager().registerAction(&schemaAction);
421             schemaMenu_.addAction(&schemaAction);
422         }
423         api_->free_schema_list(&list);
424     }
425 }
426 
427 } // namespace fcitx
428 
429 FCITX_ADDON_FACTORY(fcitx::RimeEngineFactory)
430