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