Skip to content

Commit

Permalink
getConfig, setConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj committed Sep 3, 2024
1 parent 4850ad8 commit 12602bd
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 0 deletions.
20 changes: 20 additions & 0 deletions page/Fcitx5.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
type Child = ({
Description: string
Option: string
Type: string
Value: string
} & ({
Children: null
DefaultValue: any
} | {
Children: Child[]
}) & { [key: string]: any })

export type Config = {
Children: Child[]
} | {
ERROR: string
}

export interface FCITX {
enable: () => void
disable: () => void
Expand All @@ -8,6 +26,8 @@ export interface FCITX {
getAllInputMethods: () => { name: string, displayName: string, languageCode: string }[]
setStatusAreaCallback: (callback: () => void) => void
updateStatusArea: () => void
getConfig: (uri: string) => Config
setConfig: (uri: string, json: object) => void
}

export const fcitxReady: Promise<null>
9 changes: 9 additions & 0 deletions page/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Module from './module'

export function getConfig(uri: string) {
return JSON.parse(Module.ccall('get_config', 'string', ['string'], [uri]))
}

export function setConfig(uri: string, json: object) {
return Module.ccall('set_config', 'void', ['string', 'string'], [uri, JSON.stringify(json)])
}
3 changes: 3 additions & 0 deletions page/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { blur, clickPanel, focus } from './focus'
import { keyEvent } from './keycode'
import { commit, hidePanel, placePanel, setPreedit } from './client'
import { currentInputMethod, getAllInputMethods, getInputMethods, setCurrentInputMethod, setInputMethods } from './input-method'
import { getConfig, setConfig } from './config'

let res: (value: any) => void

Expand Down Expand Up @@ -40,6 +41,8 @@ window.fcitx = {
getInputMethods,
setInputMethods,
getAllInputMethods,
getConfig,
setConfig,
enable() {
document.addEventListener('focus', focus, true)
document.addEventListener('blur', blur, true)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_executable(Fcitx5
fcitx.cpp
keycode.cpp
input_method.cpp
config.cpp
)

target_include_directories(Fcitx5 PRIVATE
Expand Down
267 changes: 267 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#include <emscripten.h>
#include <fcitx-config/configuration.h>
#include <fcitx-config/rawconfig.h>
#include <fcitx-utils/stringutils.h>
#include <fcitx/addonmanager.h>
#include <fcitx/inputmethodengine.h>
#include <fcitx/inputmethodentry.h>
#include <fcitx/inputmethodmanager.h>
#include <fcitx/instance.h>
#include <nlohmann/json.hpp>
#include <string>

namespace fcitx {
extern std::unique_ptr<Instance> instance;

constexpr char globalConfigPath[] = "fcitx://config/global";
constexpr char addonConfigPrefix[] = "fcitx://config/addon/";
constexpr char imConfigPrefix[] = "fcitx://config/inputmethod/";

/// Convert configuration into a json object.
nlohmann::json configToJson(const Configuration &config);

nlohmann::json configValueToJson(const Configuration &config);

using namespace std::literals::string_literals;

static nlohmann::json &jsonLocate(nlohmann::json &j, const std::string &group,
const std::string &option);
static nlohmann::json configValueToJson(const RawConfig &config);
static nlohmann::json configSpecToJson(const RawConfig &config);
static nlohmann::json configSpecToJson(const Configuration &config);
static void mergeSpecAndValue(nlohmann::json &specJson,
const nlohmann::json &valueJson);
static RawConfig jsonToRawConfig(const nlohmann::json &);
static std::tuple<std::string, std::string>
parseAddonUri(const std::string &uri);

nlohmann::json getConfig(const std::string &uri) {
FCITX_DEBUG() << "getConfig " << uri;
if (uri == globalConfigPath) {
auto &config = instance->globalConfig().config();
return configToJson(config);
} else if (stringutils::startsWith(uri, addonConfigPrefix)) {
auto [addonName, subPath] = parseAddonUri(uri);
auto *addonInfo = instance->addonManager().addonInfo(addonName);
if (!addonInfo) {
return {{"ERROR", "Addon \""s + addonName + "\" does not exist"}};
} else if (!addonInfo->isConfigurable()) {
return {
{"ERROR", "Addon \""s + addonName + "\" is not configurable"}};
}
auto *addon = instance->addonManager().addon(addonName, true);
if (!addon) {
return {{"ERROR",
"Failed to get config for addon \""s + addonName + "\""}};
}
auto *config =
subPath.empty() ? addon->getConfig() : addon->getSubConfig(subPath);
if (!config) {
return {{"ERROR",
"Failed to get config for addon \""s + addonName + "\""}};
}
return configToJson(*config);
} else if (stringutils::startsWith(uri, imConfigPrefix)) {
auto imName = uri.substr(sizeof(imConfigPrefix) - 1);
auto *entry = instance->inputMethodManager().entry(imName);
if (!entry) {
return {
{"ERROR", "Input method \""s + imName + "\" doesn't exist"}};
}
if (!entry->isConfigurable()) {
return {{"ERROR",
"Input method \""s + imName + "\" is not configurable"}};
}
auto *engine = instance->inputMethodEngine(imName);
if (!engine) {
return {{"ERROR", "Failed to get engine for input method \""s +
imName + "\""}};
}
auto *config = engine->getConfigForInputMethod(*entry);
if (!config) {
return {{"ERROR", "Failed to get config for input method \""s +
imName + "\""}};
}
return configToJson(*config);
} else {
return {{"ERROR", "Bad config URI \""s + uri + "\""}};
}
}

extern "C" {
EMSCRIPTEN_KEEPALIVE const char *get_config(const char *uri) {
static std::string ret;
ret = getConfig(std::string(uri)).dump();
return ret.c_str();
}

EMSCRIPTEN_KEEPALIVE bool set_config(const char *uri_, const char *json_) {
FCITX_DEBUG() << "setConfig " << uri_;
auto config = jsonToRawConfig(nlohmann::json::parse(json_));
auto uri = std::string(uri_);
if (uri == globalConfigPath) {
auto &gc = instance->globalConfig();
gc.load(config, true);
if (gc.safeSave()) {
instance->reloadConfig();
return true;
} else {
return false;
}
} else if (stringutils::startsWith(uri, addonConfigPrefix)) {
auto [addonName, subPath] = parseAddonUri(uri);
auto *addon = instance->addonManager().addon(addonName, true);
if (addon) {
FCITX_DEBUG() << "Saving addon config to: " << uri;
if (subPath.empty()) {
addon->setConfig(config);
} else {
addon->setSubConfig(subPath, config);
}
return true;
} else {
FCITX_ERROR() << "Failed to get addon";
return false;
}
} else if (stringutils::startsWith(uri, imConfigPrefix)) {
auto im = uri.substr(sizeof(imConfigPrefix) - 1);
const auto *entry = instance->inputMethodManager().entry(im);
auto *engine = instance->inputMethodEngine(im);
if (entry && engine) {
FCITX_DEBUG() << "Saving input method config to: " << uri;
engine->setConfigForInputMethod(*entry, config);
return true;
} else {
FCITX_ERROR() << "Failed to get input method";
return false;
}
} else {
return false;
}
}
}

void jsonFillRawConfigValues(const nlohmann::json &j, RawConfig &config) {
if (j.is_string()) {
config = j.get<std::string>();
return;
}
if (j.is_object()) {
for (const auto [key, subJson] : j.items()) {
auto subConfig = config.get(key, true);
jsonFillRawConfigValues(subJson, *subConfig);
}
return;
}
FCITX_FATAL() << "Unknown value json: " << j.dump();
}

RawConfig jsonToRawConfig(const nlohmann::json &j) {
RawConfig config;
jsonFillRawConfigValues(j, config);
return config;
}

nlohmann::json &jsonLocate(nlohmann::json &j, const std::string &groupPath,
const std::string &option) {
auto paths = stringutils::split(groupPath, "$");
paths.pop_back(); // remove type
paths.push_back(option);
nlohmann::json *cur = &j;
for (const auto &part : paths) {
auto &children =
*cur->emplace("Children", nlohmann::json::array()).first;
bool exist = false;
for (auto &child : children) {
if (child["Option"] == part) {
exist = true;
cur = &child;
break;
}
}
if (!exist) {
cur = &children.emplace_back(nlohmann::json::object());
}
}
return *cur;
}

nlohmann::json configValueToJson(const RawConfig &config) {
if (!config.hasSubItems()) {
return nlohmann::json(config.value());
}
nlohmann::json j;
for (auto &subItem : config.subItems()) {
auto subConfig = config.get(subItem);
j[subItem] = configValueToJson(*subConfig);
}
return j;
}

nlohmann::json configValueToJson(const Configuration &config) {
RawConfig raw;
config.save(raw);
return configValueToJson(raw);
}

nlohmann::json configSpecToJson(const RawConfig &config) {
// first level -> Path1$Path2$...$Path_n$ConfigType
// second level -> OptionName
nlohmann::json spec;
auto groups = config.subItems();
for (const auto &group : groups) {
auto groupConfig = config.get(group);
auto options = groupConfig->subItems();
for (const auto &option : options) {
auto optionConfig = groupConfig->get(option);
nlohmann::json &optSpec = jsonLocate(spec, group, option);
optSpec["Option"] = option;
optionConfig->visitSubItems(
[&](const RawConfig &config, const std::string &path) {
optSpec[path] = configValueToJson(config);
return true;
});
}
}
return spec;
}

void mergeSpecAndValue(nlohmann::json &specJson,
const nlohmann::json &valueJson) {
if (specJson.find("Type") != specJson.end()) {
specJson["Value"] = valueJson;
}
for (auto &child : specJson["Children"]) {
const auto iter = valueJson.find(child["Option"]);
if (iter != valueJson.end()) {
mergeSpecAndValue(child, *iter);
}
}
}

nlohmann::json configSpecToJson(const Configuration &config) {
RawConfig rawDesc;
config.dumpDescription(rawDesc);
return configSpecToJson(rawDesc);
}

nlohmann::json configToJson(const Configuration &config) {
// specJson contains config definitions
auto specJson = configSpecToJson(config);
// valueJson contains actual values that user could change
auto valueJson = configValueToJson(config);
mergeSpecAndValue(specJson, valueJson);
return specJson;
}

static std::tuple<std::string, std::string>
parseAddonUri(const std::string &uri) {
auto addon = uri.substr(sizeof(addonConfigPrefix) - 1);
auto pos = addon.find('/');
if (pos == std::string::npos) {
return {addon, ""};
} else {
return {addon.substr(0, pos), addon.substr(pos + 1)};
}
}
} // namespace fcitx

0 comments on commit 12602bd

Please sign in to comment.