/* * SPDX-FileCopyrightText: 2020~2020 CSSlayer * * SPDX-License-Identifier: LGPL-2.1-or-later * */ #include "luaaddonstate.h" #include "baselua.h" #include #include #include #include namespace fcitx { static void LuaPError(int err, const char *s) { switch (err) { case LUA_ERRSYNTAX: FCITX_LUA_ERROR() << "syntax error during pre-compilation " << s; break; case LUA_ERRMEM: FCITX_LUA_ERROR() << "memory allocation error " << s; break; case LUA_ERRFILE: FCITX_LUA_ERROR() << "cannot open/read the file " << s; break; case LUA_ERRRUN: FCITX_LUA_ERROR() << "a runtime error " << s; break; case LUA_ERRERR: FCITX_LUA_ERROR() << "error while running the error handler function " << s; break; case LUA_OK: FCITX_LUA_ERROR() << "ok: " << s; break; default: FCITX_LUA_ERROR() << "unknown error: " << err << " " << s; break; } } static void LuaPrintError(LuaState *lua) { if (lua_gettop(lua) > 0) { FCITX_LUA_ERROR() << lua_tostring(lua, -1); } } LuaAddonState::LuaAddonState(Library &luaLibrary, const std::string &name, const std::string &library, AddonManager *manager) : instance_(manager->instance()), state_(std::make_unique(luaLibrary)) { if (!state_) { throw std::runtime_error("Failed to create lua state."); } auto path = StandardPath::global().locate( StandardPath::Type::PkgData, stringutils::joinPath("lua", name, library)); if (path.empty()) { throw std::runtime_error("Couldn't find lua source."); } LuaAddonState **ppmodule = reinterpret_cast( lua_newuserdata(state_, sizeof(LuaAddonState *))); *ppmodule = this; lua_setglobal(state_, kLuaModuleName); luaL_openlibs(state_); auto open_fcitx_core = [](lua_State *state) { static const luaL_Reg fcitxlib[] = { {"version", &LuaAddonState::version}, {"lastCommit", &LuaAddonState::lastCommit}, {"splitString", &LuaAddonState::splitString}, {"log", &LuaAddonState::log}, {"watchEvent", &LuaAddonState::watchEvent}, {"unwatchEvent", &LuaAddonState::unwatchEvent}, {"currentInputMethod", &LuaAddonState::currentInputMethod}, {"setCurrentInputMethod", &LuaAddonState::setCurrentInputMethod}, {"currentProgram", &LuaAddonState::currentProgram}, {"addConverter", &LuaAddonState::addConverter}, {"removeConverter", &LuaAddonState::removeConverter}, {"addQuickPhraseHandler", &LuaAddonState::addQuickPhraseHandler}, {"removeQuickPhraseHandler", &LuaAddonState::removeQuickPhraseHandler}, {"commitString", &LuaAddonState::commitString}, {"standardPathLocate", &LuaAddonState::standardPathLocate}, {"UTF16ToUTF8", &LuaAddonState::UTF16ToUTF8}, {"UTF8ToUTF16", &LuaAddonState::UTF8ToUTF16}, }; auto addon = GetLuaAddonState(state); luaL_newlib(addon->state_, fcitxlib); return 1; }; auto open_fcitx = [](lua_State *state) { auto s = GetLuaAddonState(state)->state_.get(); if (int rv = luaL_loadstring(s, baselua) || lua_pcallk(s, 0, LUA_MULTRET, 0, 0, nullptr); rv != LUA_OK) { LuaPError(rv, "luaL_loadbuffer() failed"); LuaPrintError(GetLuaAddonState(state)->state_.get()); return 0; } return 1; }; luaL_requiref(state_, "fcitx.core", open_fcitx_core, false); luaL_requiref(state_, "fcitx", open_fcitx, false); if (int rv = luaL_loadfile(state_, path.data()); rv != 0) { LuaPError(rv, "luaL_loadfilex() failed"); LuaPrintError(*this); throw std::runtime_error("Failed to load lua source."); } if (int rv = lua_pcall(state_, 0, 0, 0); rv != 0) { LuaPError(rv, "lua_pcall() failed"); LuaPrintError(*this); throw std::runtime_error("Failed to run lua source."); } commitHandler_ = instance_->watchEvent( EventType::InputContextCommitString, EventWatcherPhase::PreInputMethod, [this](Event &event) { auto &commitEvent = static_cast(event); lastCommit_ = commitEvent.text(); }); } std::tuple<> LuaAddonState::logImpl(const char *msg) { FCITX_LUA_DEBUG() << msg; return {}; } template std::unique_ptr> LuaAddonState::watchEvent( EventType type, int id, std::function &, T &)> pushArguments, std::function &, T &)> handleReturnValue) { return instance_->watchEvent( type, EventWatcherPhase::PreInputMethod, [this, id, pushArguments, handleReturnValue](Event &event_) { auto iter = eventHandler_.find(id); int argc = 0; if (iter == eventHandler_.end()) { return; } auto &event = static_cast(event_); ScopedICSetter setter(inputContext_, event.inputContext()->watch()); lua_getglobal(state_, iter->second.function().data()); if (pushArguments) { argc = pushArguments(state_, event); } int rv = lua_pcall(state_, argc, 1, 0); if (rv != 0) { LuaPError(rv, "lua_pcall() failed"); LuaPrintError(*this); } else if (lua_gettop(state_) >= 1) { if (handleReturnValue) { handleReturnValue(state_, event); } } lua_pop(state_, lua_gettop(state_)); }); } std::tuple LuaAddonState::watchEventImpl(int eventType, const char *function) { int newId = currentId_ + 1; std::unique_ptr> handler = nullptr; switch (static_cast(eventType)) { case EventType::InputContextCreated: case EventType::InputContextDestroyed: case EventType::InputContextFocusIn: case EventType::InputContextFocusOut: case EventType::InputContextSurroundingTextUpdated: case EventType::InputContextCursorRectChanged: case EventType::InputContextUpdatePreedit: handler = watchEvent( static_cast(eventType), newId); break; case EventType::InputContextKeyEvent: handler = watchEvent( EventType::InputContextKeyEvent, newId, [](std::unique_ptr &state, KeyEvent &event) -> int { lua_pushinteger(state, event.key().sym()); lua_pushinteger(state, event.key().states()); lua_pushboolean(state, event.isRelease()); return 3; }, [](std::unique_ptr &state, KeyEvent &event) { auto b = lua_toboolean(state, -1); if (b) { event.filterAndAccept(); } }); break; case EventType::InputContextCommitString: handler = watchEvent( EventType::InputContextCommitString, newId, [](std::unique_ptr &state, CommitStringEvent &event) -> int { lua_pushstring(state, event.text().c_str()); return 1; }); break; case EventType::InputContextInputMethodActivated: case EventType::InputContextInputMethodDeactivated: handler = watchEvent( static_cast(eventType), newId, [](std::unique_ptr &state, InputMethodNotificationEvent &event) -> int { lua_pushstring(state, event.name().c_str()); return 1; }); break; case EventType::InputContextSwitchInputMethod: handler = watchEvent( static_cast(eventType), newId, [](std::unique_ptr &state, InputContextSwitchInputMethodEvent &event) -> int { lua_pushstring(state, event.oldInputMethod().c_str()); return 1; }); break; default: throw std::runtime_error("Invalid eventype"); } currentId_++; eventHandler_.emplace(std::piecewise_construct, std::forward_as_tuple(newId), std::forward_as_tuple(function, std::move(handler))); return {newId}; } std::tuple<> LuaAddonState::unwatchEventImpl(int id) { eventHandler_.erase(id); return {}; } std::tuple LuaAddonState::currentInputMethodImpl() { auto ic = inputContext_.get(); if (ic) { return {instance_->inputMethod(ic)}; } return {""}; } std::tuple LuaAddonState::currentProgramImpl() { auto ic = inputContext_.get(); if (ic) { return {ic->program()}; } return {""}; } std::tuple<> LuaAddonState::setCurrentInputMethodImpl(const char *str, bool local) { auto ic = inputContext_.get(); if (ic) { instance_->setCurrentInputMethod(ic, str, local); } return {}; } std::tuple LuaAddonState::addConverterImpl(const char *function) { int newId = ++currentId_; converter_.emplace( std::piecewise_construct, std::forward_as_tuple(newId), std::forward_as_tuple( function, instance_->connect( [this, newId](InputContext *inputContext, std::string &orig) { auto iter = converter_.find(newId); if (iter == converter_.end()) { return; } ScopedICSetter setter(inputContext_, inputContext->watch()); lua_getglobal(state_, iter->second.function().data()); lua_pushstring(state_, orig.data()); if (int rv = lua_pcall(state_, 1, 1, 0); rv != 0) { LuaPError(rv, "lua_pcall() failed"); LuaPrintError(*this); } else if (lua_gettop(state_) >= 1) { auto s = lua_tostring(state_, -1); if (s) { orig = s; } } lua_pop(state_, lua_gettop(state_)); }))); return {newId}; } std::tuple<> LuaAddonState::removeConverterImpl(int id) { converter_.erase(id); return {}; } std::tuple<> LuaAddonState::commitStringImpl(const char *str) { if (auto ic = inputContext_.get()) { ic->commitString(str); } return {}; } bool LuaAddonState::handleQuickPhrase( InputContext *ic, const std::string &input, const QuickPhraseAddCandidateCallback &callback) { ScopedICSetter setter(inputContext_, ic->watch()); bool flag = true; for (auto iter = quickphraseHandler_.begin(), end = quickphraseHandler_.end(); iter != end; ++iter) { lua_getglobal(state_, iter->second.data()); lua_pushstring(state_, input.data()); int rv = lua_pcall(state_, 1, 1, 0); if (rv != 0) { LuaPError(rv, "lua_pcall() failed"); LuaPrintError(*this); } else if (lua_gettop(state_) >= 1) { do { int type = lua_type(state_, -1); if (type != LUA_TTABLE) { break; } auto len = luaL_len(state_, -1); if (len < 1) { break; } for (int i = 1; i <= len; ++i) { lua_pushinteger(state_, i); /* stack, table, integer */ lua_gettable(state_, -2); std::string result, display; int action; if (lua_type(state_, -1) == LUA_TTABLE) { lua_pushinteger(state_, 1); lua_gettable(state_, -2); const char *str = lua_tostring(state_, -1); result = str; lua_pop(state_, 1); if (!str) { continue; } lua_pushinteger(state_, 2); lua_gettable(state_, -2); str = lua_tostring(state_, -1); display = str; lua_pop(state_, 1); if (!str) { continue; } lua_pushinteger(state_, 3); lua_gettable(state_, -2); action = lua_tointeger(state_, -1); lua_pop(state_, 1); // -1 is lua's custom value for break. if (action == -1) { flag = false; } else { callback(result, display, static_cast(action)); } } /* stack, table */ lua_pop(state_, 1); } } while (0); } if (!flag) { return false; } lua_pop(state_, lua_gettop(state_)); } return true; } std::tuple LuaAddonState::addQuickPhraseHandlerImpl(const char *function) { int newId = ++currentId_; quickphraseHandler_.emplace(newId, function); if (!quickphraseCallback_ && quickphrase()) { quickphraseCallback_ = quickphrase()->call( [this](InputContext *ic, const std::string &input, QuickPhraseAddCandidateCallback callback) { return handleQuickPhrase(ic, input, callback); }); } return {newId}; } std::tuple<> LuaAddonState::removeQuickPhraseHandlerImpl(int id) { quickphraseHandler_.erase(id); if (quickphraseHandler_.empty()) { quickphraseCallback_.reset(); } return {}; } std::tuple> LuaAddonState::standardPathLocateImpl(int type, const char *path, const char *suffix) { std::vector result; auto files = StandardPath::global().multiOpen( static_cast(type), path, O_RDONLY, filter::Suffix(suffix)); for (const auto &file : files) { result.push_back(file.second.path()); } return {std::move(result)}; } std::tuple LuaAddonState::UTF16ToUTF8Impl(const char *str) { auto data = reinterpret_cast(str); std::string result; size_t i = 0; while (data[i]) { uint32_t ucs4 = 0; if (data[i] < 0xD800 || data[i] > 0xDFFF) { ucs4 = data[i]; i += 1; } else if (0xD800 <= data[i] && data[i] <= 0xDBFF) { if (!data[i + 1]) { return {}; } if (0xDC00 <= data[i + 1] && data[i + 1] <= 0xDFFF) { /* We have a valid surrogate pair. */ ucs4 = (((data[i] & 0x3FF) << 10) | (data[i + 1] & 0x3FF)) + (1 << 16); i += 2; } } else if (0xDC00 <= data[i] && data[i] <= 0xDFFF) { return {}; } result.append(utf8::UCS4ToUTF8(ucs4)); } return result; } std::tuple LuaAddonState::UTF8ToUTF16Impl(const char *str) { std::string s(str); if (!utf8::validate(s)) { return {}; } std::vector result; for (const auto ucs4 : utf8::MakeUTF8CharRange(s)) { if (ucs4 < 0x10000) { result.push_back(static_cast(ucs4)); } else if (ucs4 < 0x110000) { result.push_back(0xD800 | (((ucs4 - 0x10000) >> 10) & 0x3ff)); result.push_back(0xDC00 | (ucs4 & 0x3ff)); } else { return {}; } } result.push_back(0); return std::string(reinterpret_cast(result.data()), result.size() * sizeof(uint16_t)); } void rawConfigToLua(LuaState *state, const RawConfig &config) { if (!config.hasSubItems()) { lua_pushlstring(state, config.value().data(), config.value().size()); return; } lua_newtable(state); if (!config.value().empty()) { lua_pushstring(state, ""); lua_pushlstring(state, config.value().data(), config.value().size()); lua_rawset(state, -3); } if (config.hasSubItems()) { auto options = config.subItems(); for (auto &option : options) { auto subConfig = config.get(option); lua_pushstring(state, option.data()); rawConfigToLua(state, *subConfig); lua_rawset(state, -3); } } } void luaToRawConfig(LuaState *state, RawConfig &config) { int type = lua_type(state, -1); if (type == LUA_TSTRING) { if (auto str = lua_tostring(state, -1)) { auto l = lua_rawlen(state, -1); config.setValue(std::string(str, l)); } return; } if (type == LUA_TTABLE) { /* table is in the stack at index 't' */ lua_pushnil(state); /* first key */ while (lua_next(state, -2) != 0) { if (lua_type(state, -2) == LUA_TSTRING) { if (auto str = lua_tostring(state, -2)) { if (str[0]) { luaToRawConfig(state, config[str]); } else if (lua_type(state, -1) == LUA_TSTRING) { luaToRawConfig(state, config); } } } lua_pop(state, 1); } } } RawConfig LuaAddonState::invokeLuaFunction(InputContext *ic, const std::string &name, const RawConfig &config) { TrackableObjectReference icRef; if (ic) { icRef = ic->watch(); } ScopedICSetter setter(inputContext_, icRef); lua_getglobal(state_, name.data()); rawConfigToLua(state_.get(), config); int rv = lua_pcall(state_, 1, 1, 0); RawConfig ret; if (rv != 0) { LuaPError(rv, "lua_pcall() failed"); LuaPrintError(state_.get()); } else if (lua_gettop(state_) >= 1) { luaToRawConfig(state_.get(), ret); } lua_pop(state_, lua_gettop(state_)); return ret; } } // namespace fcitx