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