1 /*
2  * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #include "luaaddonstate.h"
8 #include "baselua.h"
9 #include <fcitx-utils/standardpath.h>
10 #include <fcitx-utils/utf8.h>
11 #include <fcitx/addonmanager.h>
12 #include <fcntl.h>
13 
14 namespace fcitx {
15 
LuaPError(int err,const char * s)16 static void LuaPError(int err, const char *s) {
17     switch (err) {
18     case LUA_ERRSYNTAX:
19         FCITX_LUA_ERROR() << "syntax error during pre-compilation " << s;
20         break;
21     case LUA_ERRMEM:
22         FCITX_LUA_ERROR() << "memory allocation error " << s;
23         break;
24     case LUA_ERRFILE:
25         FCITX_LUA_ERROR() << "cannot open/read the file " << s;
26         break;
27     case LUA_ERRRUN:
28         FCITX_LUA_ERROR() << "a runtime error " << s;
29         break;
30     case LUA_ERRERR:
31         FCITX_LUA_ERROR() << "error while running the error handler function "
32                           << s;
33         break;
34     case LUA_OK:
35         FCITX_LUA_ERROR() << "ok: " << s;
36         break;
37     default:
38         FCITX_LUA_ERROR() << "unknown error: " << err << " " << s;
39         break;
40     }
41 }
42 
LuaPrintError(LuaState * lua)43 static void LuaPrintError(LuaState *lua) {
44     if (lua_gettop(lua) > 0) {
45         FCITX_LUA_ERROR() << lua_tostring(lua, -1);
46     }
47 }
48 
LuaAddonState(Library & luaLibrary,const std::string & name,const std::string & library,AddonManager * manager)49 LuaAddonState::LuaAddonState(Library &luaLibrary, const std::string &name,
50                              const std::string &library, AddonManager *manager)
51     : instance_(manager->instance()),
52       state_(std::make_unique<LuaState>(luaLibrary)) {
53     if (!state_) {
54         throw std::runtime_error("Failed to create lua state.");
55     }
56 
57     auto path = StandardPath::global().locate(
58         StandardPath::Type::PkgData,
59         stringutils::joinPath("lua", name, library));
60     if (path.empty()) {
61         throw std::runtime_error("Couldn't find lua source.");
62     }
63     LuaAddonState **ppmodule = reinterpret_cast<LuaAddonState **>(
64         lua_newuserdata(state_, sizeof(LuaAddonState *)));
65     *ppmodule = this;
66     lua_setglobal(state_, kLuaModuleName);
67     luaL_openlibs(state_);
68     auto open_fcitx_core = [](lua_State *state) {
69         static const luaL_Reg fcitxlib[] = {
70             {"version", &LuaAddonState::version},
71             {"lastCommit", &LuaAddonState::lastCommit},
72             {"splitString", &LuaAddonState::splitString},
73             {"log", &LuaAddonState::log},
74             {"watchEvent", &LuaAddonState::watchEvent},
75             {"unwatchEvent", &LuaAddonState::unwatchEvent},
76             {"currentInputMethod", &LuaAddonState::currentInputMethod},
77             {"setCurrentInputMethod", &LuaAddonState::setCurrentInputMethod},
78             {"currentProgram", &LuaAddonState::currentProgram},
79             {"addConverter", &LuaAddonState::addConverter},
80             {"removeConverter", &LuaAddonState::removeConverter},
81             {"addQuickPhraseHandler", &LuaAddonState::addQuickPhraseHandler},
82             {"removeQuickPhraseHandler",
83              &LuaAddonState::removeQuickPhraseHandler},
84             {"commitString", &LuaAddonState::commitString},
85             {"standardPathLocate", &LuaAddonState::standardPathLocate},
86             {"UTF16ToUTF8", &LuaAddonState::UTF16ToUTF8},
87             {"UTF8ToUTF16", &LuaAddonState::UTF8ToUTF16},
88         };
89         auto addon = GetLuaAddonState(state);
90         luaL_newlib(addon->state_, fcitxlib);
91         return 1;
92     };
93     auto open_fcitx = [](lua_State *state) {
94         auto s = GetLuaAddonState(state)->state_.get();
95         if (int rv = luaL_loadstring(s, baselua) ||
96                      lua_pcallk(s, 0, LUA_MULTRET, 0, 0, nullptr);
97             rv != LUA_OK) {
98             LuaPError(rv, "luaL_loadbuffer() failed");
99             LuaPrintError(GetLuaAddonState(state)->state_.get());
100             return 0;
101         }
102         return 1;
103     };
104     luaL_requiref(state_, "fcitx.core", open_fcitx_core, false);
105     luaL_requiref(state_, "fcitx", open_fcitx, false);
106     if (int rv = luaL_loadfile(state_, path.data()); rv != 0) {
107         LuaPError(rv, "luaL_loadfilex() failed");
108         LuaPrintError(*this);
109         throw std::runtime_error("Failed to load lua source.");
110     }
111 
112     if (int rv = lua_pcall(state_, 0, 0, 0); rv != 0) {
113         LuaPError(rv, "lua_pcall() failed");
114         LuaPrintError(*this);
115         throw std::runtime_error("Failed to run lua source.");
116     }
117 
118     commitHandler_ = instance_->watchEvent(
119         EventType::InputContextCommitString, EventWatcherPhase::PreInputMethod,
120         [this](Event &event) {
121             auto &commitEvent = static_cast<CommitStringEvent &>(event);
122             lastCommit_ = commitEvent.text();
123         });
124 }
125 
logImpl(const char * msg)126 std::tuple<> LuaAddonState::logImpl(const char *msg) {
127     FCITX_LUA_DEBUG() << msg;
128     return {};
129 }
130 
131 template <typename T>
watchEvent(EventType type,int id,std::function<int (std::unique_ptr<LuaState> &,T &)> pushArguments,std::function<void (std::unique_ptr<LuaState> &,T &)> handleReturnValue)132 std::unique_ptr<HandlerTableEntry<EventHandler>> LuaAddonState::watchEvent(
133     EventType type, int id,
134     std::function<int(std::unique_ptr<LuaState> &, T &)> pushArguments,
135     std::function<void(std::unique_ptr<LuaState> &, T &)> handleReturnValue) {
136     return instance_->watchEvent(
137         type, EventWatcherPhase::PreInputMethod,
138         [this, id, pushArguments, handleReturnValue](Event &event_) {
139             auto iter = eventHandler_.find(id);
140             int argc = 0;
141             if (iter == eventHandler_.end()) {
142                 return;
143             }
144             auto &event = static_cast<T &>(event_);
145             ScopedICSetter setter(inputContext_, event.inputContext()->watch());
146             lua_getglobal(state_, iter->second.function().data());
147             if (pushArguments) {
148                 argc = pushArguments(state_, event);
149             }
150             int rv = lua_pcall(state_, argc, 1, 0);
151             if (rv != 0) {
152                 LuaPError(rv, "lua_pcall() failed");
153                 LuaPrintError(*this);
154             } else if (lua_gettop(state_) >= 1) {
155                 if (handleReturnValue) {
156                     handleReturnValue(state_, event);
157                 }
158             }
159             lua_pop(state_, lua_gettop(state_));
160         });
161 }
162 
watchEventImpl(int eventType,const char * function)163 std::tuple<int> LuaAddonState::watchEventImpl(int eventType,
164                                               const char *function) {
165     int newId = currentId_ + 1;
166     std::unique_ptr<HandlerTableEntry<EventHandler>> handler = nullptr;
167 
168     switch (static_cast<EventType>(eventType)) {
169     case EventType::InputContextCreated:
170     case EventType::InputContextDestroyed:
171     case EventType::InputContextFocusIn:
172     case EventType::InputContextFocusOut:
173     case EventType::InputContextSurroundingTextUpdated:
174     case EventType::InputContextCursorRectChanged:
175     case EventType::InputContextUpdatePreedit:
176         handler = watchEvent<InputContextEvent>(
177             static_cast<EventType>(eventType), newId);
178         break;
179     case EventType::InputContextKeyEvent:
180         handler = watchEvent<KeyEvent>(
181             EventType::InputContextKeyEvent, newId,
182             [](std::unique_ptr<LuaState> &state, KeyEvent &event) -> int {
183                 lua_pushinteger(state, event.key().sym());
184                 lua_pushinteger(state, event.key().states());
185                 lua_pushboolean(state, event.isRelease());
186                 return 3;
187             },
188             [](std::unique_ptr<LuaState> &state, KeyEvent &event) {
189                 auto b = lua_toboolean(state, -1);
190                 if (b) {
191                     event.filterAndAccept();
192                 }
193             });
194         break;
195     case EventType::InputContextCommitString:
196         handler = watchEvent<CommitStringEvent>(
197             EventType::InputContextCommitString, newId,
198             [](std::unique_ptr<LuaState> &state,
199                CommitStringEvent &event) -> int {
200                 lua_pushstring(state, event.text().c_str());
201                 return 1;
202             });
203         break;
204     case EventType::InputContextInputMethodActivated:
205     case EventType::InputContextInputMethodDeactivated:
206         handler = watchEvent<InputMethodNotificationEvent>(
207             static_cast<EventType>(eventType), newId,
208             [](std::unique_ptr<LuaState> &state,
209                InputMethodNotificationEvent &event) -> int {
210                 lua_pushstring(state, event.name().c_str());
211                 return 1;
212             });
213         break;
214     case EventType::InputContextSwitchInputMethod:
215         handler = watchEvent<InputContextSwitchInputMethodEvent>(
216             static_cast<EventType>(eventType), newId,
217             [](std::unique_ptr<LuaState> &state,
218                InputContextSwitchInputMethodEvent &event) -> int {
219                 lua_pushstring(state, event.oldInputMethod().c_str());
220                 return 1;
221             });
222         break;
223     default:
224         throw std::runtime_error("Invalid eventype");
225     }
226     currentId_++;
227     eventHandler_.emplace(std::piecewise_construct,
228                           std::forward_as_tuple(newId),
229                           std::forward_as_tuple(function, std::move(handler)));
230     return {newId};
231 }
232 
unwatchEventImpl(int id)233 std::tuple<> LuaAddonState::unwatchEventImpl(int id) {
234     eventHandler_.erase(id);
235     return {};
236 }
237 
currentInputMethodImpl()238 std::tuple<std::string> LuaAddonState::currentInputMethodImpl() {
239     auto ic = inputContext_.get();
240     if (ic) {
241         return {instance_->inputMethod(ic)};
242     }
243     return {""};
244 }
245 
currentProgramImpl()246 std::tuple<std::string> LuaAddonState::currentProgramImpl() {
247     auto ic = inputContext_.get();
248     if (ic) {
249         return {ic->program()};
250     }
251     return {""};
252 }
253 
setCurrentInputMethodImpl(const char * str,bool local)254 std::tuple<> LuaAddonState::setCurrentInputMethodImpl(const char *str,
255                                                       bool local) {
256     auto ic = inputContext_.get();
257     if (ic) {
258         instance_->setCurrentInputMethod(ic, str, local);
259     }
260     return {};
261 }
262 
addConverterImpl(const char * function)263 std::tuple<int> LuaAddonState::addConverterImpl(const char *function) {
264     int newId = ++currentId_;
265     converter_.emplace(
266         std::piecewise_construct, std::forward_as_tuple(newId),
267         std::forward_as_tuple(
268             function,
269             instance_->connect<Instance::CommitFilter>(
270                 [this, newId](InputContext *inputContext, std::string &orig) {
271                     auto iter = converter_.find(newId);
272                     if (iter == converter_.end()) {
273                         return;
274                     }
275 
276                     ScopedICSetter setter(inputContext_, inputContext->watch());
277                     lua_getglobal(state_, iter->second.function().data());
278                     lua_pushstring(state_, orig.data());
279                     if (int rv = lua_pcall(state_, 1, 1, 0); rv != 0) {
280                         LuaPError(rv, "lua_pcall() failed");
281                         LuaPrintError(*this);
282                     } else if (lua_gettop(state_) >= 1) {
283                         auto s = lua_tostring(state_, -1);
284                         if (s) {
285                             orig = s;
286                         }
287                     }
288                     lua_pop(state_, lua_gettop(state_));
289                 })));
290     return {newId};
291 }
292 
removeConverterImpl(int id)293 std::tuple<> LuaAddonState::removeConverterImpl(int id) {
294     converter_.erase(id);
295     return {};
296 }
297 
commitStringImpl(const char * str)298 std::tuple<> LuaAddonState::commitStringImpl(const char *str) {
299     if (auto ic = inputContext_.get()) {
300         ic->commitString(str);
301     }
302     return {};
303 }
304 
handleQuickPhrase(InputContext * ic,const std::string & input,const QuickPhraseAddCandidateCallback & callback)305 bool LuaAddonState::handleQuickPhrase(
306     InputContext *ic, const std::string &input,
307     const QuickPhraseAddCandidateCallback &callback) {
308     ScopedICSetter setter(inputContext_, ic->watch());
309     bool flag = true;
310     for (auto iter = quickphraseHandler_.begin(),
311               end = quickphraseHandler_.end();
312          iter != end; ++iter) {
313         lua_getglobal(state_, iter->second.data());
314         lua_pushstring(state_, input.data());
315         int rv = lua_pcall(state_, 1, 1, 0);
316         if (rv != 0) {
317             LuaPError(rv, "lua_pcall() failed");
318             LuaPrintError(*this);
319         } else if (lua_gettop(state_) >= 1) {
320             do {
321                 int type = lua_type(state_, -1);
322                 if (type != LUA_TTABLE) {
323                     break;
324                 }
325                 auto len = luaL_len(state_, -1);
326                 if (len < 1) {
327                     break;
328                 }
329                 for (int i = 1; i <= len; ++i) {
330                     lua_pushinteger(state_, i);
331                     /* stack, table, integer */
332                     lua_gettable(state_, -2);
333                     std::string result, display;
334                     int action;
335                     if (lua_type(state_, -1) == LUA_TTABLE) {
336                         lua_pushinteger(state_, 1);
337                         lua_gettable(state_, -2);
338                         const char *str = lua_tostring(state_, -1);
339                         result = str;
340                         lua_pop(state_, 1);
341                         if (!str) {
342                             continue;
343                         }
344                         lua_pushinteger(state_, 2);
345                         lua_gettable(state_, -2);
346                         str = lua_tostring(state_, -1);
347                         display = str;
348                         lua_pop(state_, 1);
349                         if (!str) {
350                             continue;
351                         }
352                         lua_pushinteger(state_, 3);
353                         lua_gettable(state_, -2);
354                         action = lua_tointeger(state_, -1);
355                         lua_pop(state_, 1);
356                         // -1 is lua's custom value for break.
357                         if (action == -1) {
358                             flag = false;
359                         } else {
360                             callback(result, display,
361                                      static_cast<QuickPhraseAction>(action));
362                         }
363                     }
364                     /* stack, table */
365                     lua_pop(state_, 1);
366                 }
367             } while (0);
368         }
369         if (!flag) {
370             return false;
371         }
372         lua_pop(state_, lua_gettop(state_));
373     }
374     return true;
375 }
376 
addQuickPhraseHandlerImpl(const char * function)377 std::tuple<int> LuaAddonState::addQuickPhraseHandlerImpl(const char *function) {
378     int newId = ++currentId_;
379     quickphraseHandler_.emplace(newId, function);
380     if (!quickphraseCallback_ && quickphrase()) {
381         quickphraseCallback_ = quickphrase()->call<IQuickPhrase::addProvider>(
382             [this](InputContext *ic, const std::string &input,
383                    QuickPhraseAddCandidateCallback callback) {
384                 return handleQuickPhrase(ic, input, callback);
385             });
386     }
387     return {newId};
388 }
389 
removeQuickPhraseHandlerImpl(int id)390 std::tuple<> LuaAddonState::removeQuickPhraseHandlerImpl(int id) {
391     quickphraseHandler_.erase(id);
392     if (quickphraseHandler_.empty()) {
393         quickphraseCallback_.reset();
394     }
395     return {};
396 }
397 
398 std::tuple<std::vector<std::string>>
standardPathLocateImpl(int type,const char * path,const char * suffix)399 LuaAddonState::standardPathLocateImpl(int type, const char *path,
400                                       const char *suffix) {
401     std::vector<std::string> result;
402     auto files = StandardPath::global().multiOpen(
403         static_cast<StandardPath::Type>(type), path, O_RDONLY,
404         filter::Suffix(suffix));
405     for (const auto &file : files) {
406         result.push_back(file.second.path());
407     }
408     return {std::move(result)};
409 }
410 
UTF16ToUTF8Impl(const char * str)411 std::tuple<std::string> LuaAddonState::UTF16ToUTF8Impl(const char *str) {
412     auto data = reinterpret_cast<const uint16_t *>(str);
413     std::string result;
414     size_t i = 0;
415     while (data[i]) {
416         uint32_t ucs4 = 0;
417         if (data[i] < 0xD800 || data[i] > 0xDFFF) {
418             ucs4 = data[i];
419             i += 1;
420         } else if (0xD800 <= data[i] && data[i] <= 0xDBFF) {
421             if (!data[i + 1]) {
422                 return {};
423             }
424             if (0xDC00 <= data[i + 1] && data[i + 1] <= 0xDFFF) {
425                 /* We have a valid surrogate pair.  */
426                 ucs4 = (((data[i] & 0x3FF) << 10) | (data[i + 1] & 0x3FF)) +
427                        (1 << 16);
428                 i += 2;
429             }
430         } else if (0xDC00 <= data[i] && data[i] <= 0xDFFF) {
431             return {};
432         }
433         result.append(utf8::UCS4ToUTF8(ucs4));
434     }
435     return result;
436 }
437 
UTF8ToUTF16Impl(const char * str)438 std::tuple<std::string> LuaAddonState::UTF8ToUTF16Impl(const char *str) {
439     std::string s(str);
440     if (!utf8::validate(s)) {
441         return {};
442     }
443     std::vector<uint16_t> result;
444     for (const auto ucs4 : utf8::MakeUTF8CharRange(s)) {
445         if (ucs4 < 0x10000) {
446             result.push_back(static_cast<uint16_t>(ucs4));
447         } else if (ucs4 < 0x110000) {
448             result.push_back(0xD800 | (((ucs4 - 0x10000) >> 10) & 0x3ff));
449             result.push_back(0xDC00 | (ucs4 & 0x3ff));
450         } else {
451             return {};
452         }
453     }
454     result.push_back(0);
455     return std::string(reinterpret_cast<char *>(result.data()),
456                        result.size() * sizeof(uint16_t));
457 }
458 
rawConfigToLua(LuaState * state,const RawConfig & config)459 void rawConfigToLua(LuaState *state, const RawConfig &config) {
460     if (!config.hasSubItems()) {
461         lua_pushlstring(state, config.value().data(), config.value().size());
462         return;
463     }
464 
465     lua_newtable(state);
466     if (!config.value().empty()) {
467         lua_pushstring(state, "");
468         lua_pushlstring(state, config.value().data(), config.value().size());
469         lua_rawset(state, -3);
470     }
471     if (config.hasSubItems()) {
472         auto options = config.subItems();
473         for (auto &option : options) {
474             auto subConfig = config.get(option);
475             lua_pushstring(state, option.data());
476             rawConfigToLua(state, *subConfig);
477             lua_rawset(state, -3);
478         }
479     }
480 }
481 
luaToRawConfig(LuaState * state,RawConfig & config)482 void luaToRawConfig(LuaState *state, RawConfig &config) {
483     int type = lua_type(state, -1);
484     if (type == LUA_TSTRING) {
485         if (auto str = lua_tostring(state, -1)) {
486             auto l = lua_rawlen(state, -1);
487             config.setValue(std::string(str, l));
488         }
489         return;
490     }
491 
492     if (type == LUA_TTABLE) {
493         /* table is in the stack at index 't' */
494         lua_pushnil(state); /* first key */
495         while (lua_next(state, -2) != 0) {
496             if (lua_type(state, -2) == LUA_TSTRING) {
497                 if (auto str = lua_tostring(state, -2)) {
498                     if (str[0]) {
499                         luaToRawConfig(state, config[str]);
500                     } else if (lua_type(state, -1) == LUA_TSTRING) {
501                         luaToRawConfig(state, config);
502                     }
503                 }
504             }
505             lua_pop(state, 1);
506         }
507     }
508 }
509 
invokeLuaFunction(InputContext * ic,const std::string & name,const RawConfig & config)510 RawConfig LuaAddonState::invokeLuaFunction(InputContext *ic,
511                                            const std::string &name,
512                                            const RawConfig &config) {
513     TrackableObjectReference<InputContext> icRef;
514     if (ic) {
515         icRef = ic->watch();
516     }
517     ScopedICSetter setter(inputContext_, icRef);
518     lua_getglobal(state_, name.data());
519     rawConfigToLua(state_.get(), config);
520     int rv = lua_pcall(state_, 1, 1, 0);
521     RawConfig ret;
522     if (rv != 0) {
523         LuaPError(rv, "lua_pcall() failed");
524         LuaPrintError(state_.get());
525     } else if (lua_gettop(state_) >= 1) {
526         luaToRawConfig(state_.get(), ret);
527     }
528 
529     lua_pop(state_, lua_gettop(state_));
530     return ret;
531 }
532 
533 } // namespace fcitx
534