19 #include <unordered_map> 20 #include <unordered_set> 23 #include "fcitx-config/iniparser.h" 24 #include "fcitx-config/rawconfig.h" 26 #include "fcitx-utils/handlertable.h" 27 #include "fcitx-utils/i18n.h" 29 #include "fcitx-utils/macros.h" 30 #include "fcitx-utils/misc_p.h" 33 #include "addoninfo.h" 35 #include "inputmethodconfig_p.h" 36 #include "inputmethodengine.h" 37 #include "inputmethodgroup.h" 44 bool checkEntry(
const InputMethodEntry &entry,
45 const std::unordered_set<std::string> &inputMethods) {
46 return !entry.name().empty() && !entry.uniqueName().empty() &&
47 !entry.addon().empty() && inputMethods.contains(entry.addon());
55 :
QPtrHolder(q), addonManager_(addonManager_) {}
58 &buildDefaultGroupCallback);
60 &buildDefaultGroupCallback);
61 void setGroupOrder(
const std::vector<std::string> &groupOrder);
64 void loadStaticEntries(
const std::unordered_set<std::string> &addonNames);
66 void loadDynamicEntries(
const std::unordered_set<std::string> &addonNames);
74 std::list<std::string> groupOrder_;
75 bool buildingGroup_ =
false;
76 std::unordered_map<std::string, InputMethodGroup> groups_;
77 std::unordered_map<std::string, InputMethodEntry> entries_;
79 std::unique_ptr<HandlerTableEntry<EventHandler>> eventWatcher_;
80 int64_t timestamp_ = 0;
83 void InputMethodManagerPrivate::loadConfig(
85 &buildDefaultGroupCallback) {
87 auto file = path.open(StandardPathsType::PkgConfig,
"profile");
90 readFromIni(config, file.fd());
92 InputMethodConfig imConfig;
93 imConfig.load(config);
96 std::vector<std::string> tempOrder;
97 if (!imConfig.groups.value().empty()) {
98 const auto &groupsConfig = imConfig.groups.value();
99 for (
const auto &groupConfig : groupsConfig) {
101 if (groupConfig.name.value().empty() ||
102 groupConfig.defaultLayout.value().empty()) {
106 groups_.emplace(groupConfig.name.value(),
108 tempOrder.push_back(groupConfig.name.value());
109 auto &group = result.first->second;
110 group.setDefaultLayout(groupConfig.defaultLayout.value());
111 const auto &items = groupConfig.items.value();
112 for (
const auto &item : items) {
113 if (!entries_.contains(item.name.value())) {
114 FCITX_WARN() <<
"Group Item " << item.name.value()
115 <<
" in group " << groupConfig.name.value()
116 <<
" is not valid. Removed.";
119 group.inputMethodList().emplace_back(
121 .setLayout(item.layout.value())));
124 if (group.inputMethodList().empty()) {
125 FCITX_WARN() <<
"Group " << groupConfig.name.value()
126 <<
" is empty. Removed.";
127 groups_.erase(groupConfig.name.value());
130 group.setDefaultInputMethod(groupConfig.defaultInputMethod.value());
134 if (groups_.empty()) {
135 FCITX_INFO() <<
"No valid input method group in configuration. " 136 <<
"Building a default one";
137 buildDefaultGroup(buildDefaultGroupCallback);
139 if (!imConfig.groupOrder.value().empty()) {
140 setGroupOrder(imConfig.groupOrder.value());
142 setGroupOrder(tempOrder);
147 void InputMethodManagerPrivate::buildDefaultGroup(
149 &buildDefaultGroupCallback) {
151 if (buildDefaultGroupCallback) {
152 buildingGroup_ =
true;
153 buildDefaultGroupCallback(*q_func());
154 buildingGroup_ =
false;
156 std::string name = _(
"Default");
158 auto &group = result.first->second;
159 group.inputMethodList().emplace_back(
161 group.setDefaultInputMethod(
"");
162 group.setDefaultLayout(
"us");
163 setGroupOrder({std::move(name)});
165 assert(!groups_.empty());
166 assert(!groupOrder_.empty());
169 void InputMethodManagerPrivate::loadStaticEntries(
170 const std::unordered_set<std::string> &addonNames) {
172 timestamp_ = path.timestamp(StandardPathsType::PkgData,
"inputmethod");
173 auto filesMap = path.locate(StandardPathsType::PkgData,
"inputmethod",
174 pathfilter::extension(
".conf"));
175 for (
const auto &[fileName, fullName] : filesMap) {
176 const auto u8name = fileName.stem().u8string();
177 std::string name(u8name.begin(), u8name.end());
178 if (entries_.contains(name)) {
183 readFromIni(config, fd.
fd());
185 InputMethodInfo imInfo;
187 if (!*imInfo.im->enable) {
192 if (checkEntry(entry, addonNames)) {
193 entries_.emplace(std::string(entry.uniqueName()), std::move(entry));
198 void InputMethodManagerPrivate::loadDynamicEntries(
199 const std::unordered_set<std::string> &addonNames) {
200 for (
const auto &addonName : addonNames) {
201 const auto *addonInfo = addonManager_->
addonInfo(addonName);
203 if (!addonInfo || addonInfo->onDemand()) {
209 FCITX_WARN() <<
"Failed to load input method addon: " << addonName;
212 auto newEntries = engine->listInputMethods();
213 FCITX_INFO() <<
"Found " << newEntries.size() <<
" input method(s) " 214 <<
"in addon " << addonName;
215 for (
auto &newEntry : newEntries) {
217 if (checkEntry(newEntry, addonNames) &&
218 newEntry.addon() == addonName &&
219 !entries_.contains(newEntry.uniqueName())) {
220 entries_.emplace(std::string(newEntry.uniqueName()),
221 std::move(newEntry));
227 InputMethodManager::InputMethodManager(
AddonManager *addonManager)
228 : d_ptr(std::make_unique<InputMethodManagerPrivate>(addonManager,
this)) {}
230 InputMethodManager::~InputMethodManager() {}
233 &buildDefaultGroupCallback) {
235 emit<InputMethodManager::CurrentGroupAboutToChange>(
236 d->groupOrder_.empty() ?
"" : d->groupOrder_.front());
238 auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
239 d->loadStaticEntries(addonNames);
240 d->loadDynamicEntries(addonNames);
242 d->loadConfig(buildDefaultGroupCallback);
244 emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
248 &buildDefaultGroupCallback) {
250 emit<InputMethodManager::CurrentGroupAboutToChange>(
251 d->groupOrder_.empty() ?
"" : d->groupOrder_.front());
252 d->buildDefaultGroup(buildDefaultGroupCallback);
254 emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
259 auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
260 d->loadStaticEntries(addonNames);
261 d->loadDynamicEntries(addonNames);
266 return {d->groupOrder_.begin(), d->groupOrder_.end()};
271 return d->groups_.size();
276 if (groupName == d->groupOrder_.front()) {
279 auto iter = std::ranges::find(d->groupOrder_, groupName);
280 if (iter != d->groupOrder_.end()) {
281 emit<InputMethodManager::CurrentGroupAboutToChange>(
282 d->groupOrder_.front());
283 d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_, iter);
284 emit<InputMethodManager::CurrentGroupChanged>(groupName);
290 if (groupName == d->groupOrder_.front()) {
293 auto iter = std::ranges::find(d->groupOrder_, groupName);
294 if (iter != d->groupOrder_.end()) {
295 emit<InputMethodManager::CurrentGroupAboutToChange>(
296 d->groupOrder_.front());
297 d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_, iter,
298 d->groupOrder_.end());
299 emit<InputMethodManager::CurrentGroupChanged>(groupName);
305 return d->groups_.find(d->groupOrder_.front())->second;
310 if (groupCount() < 2) {
313 emit<InputMethodManager::CurrentGroupAboutToChange>(d->groupOrder_.front());
315 d->groupOrder_.splice(d->groupOrder_.end(), d->groupOrder_,
316 d->groupOrder_.begin());
318 d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_,
319 std::prev(d->groupOrder_.end()));
321 emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
326 auto ¤tGroup = d->groups_.find(d->groupOrder_.front())->second;
327 currentGroup.setDefaultInputMethod(name);
333 return findValue(d->groups_, name);
338 auto *group = findValue(d->groups_, newGroupInfo.name());
342 bool isCurrent =
false;
343 if (!d->buildingGroup_) {
344 isCurrent = (group == ¤tGroup());
346 emit<InputMethodManager::CurrentGroupAboutToChange>(
347 d->groupOrder_.front());
350 auto &list = newGroupInfo.inputMethodList();
352 return !d->entries_.contains(item.name());
354 auto defaultInputMethod = newGroupInfo.defaultInputMethod();
355 if (defaultInputMethod.empty()) {
356 defaultInputMethod = group->defaultInputMethod();
358 newGroupInfo.setDefaultInputMethod(defaultInputMethod);
359 *group = std::move(newGroupInfo);
360 if (!d->buildingGroup_ && isCurrent) {
361 emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
365 void InputMethodManagerPrivate::setGroupOrder(
366 const std::vector<std::string> &groupOrder) {
368 std::unordered_set<std::string> added;
369 for (
const auto &groupName : groupOrder) {
370 if (groups_.contains(groupName)) {
371 groupOrder_.push_back(groupName);
372 added.insert(groupName);
375 for (
auto &p : groups_) {
376 if (!added.contains(p.first)) {
377 groupOrder_.push_back(p.first);
380 assert(groupOrder_.size() == groups_.size());
390 newGroup.setDefaultLayout(currentGroup().defaultLayout());
392 if (newGroup.defaultLayout().empty()) {
393 newGroup.setDefaultLayout(
"us");
395 d->groups_.emplace(name, std::move(newGroup));
396 d->groupOrder_.push_back(name);
397 if (!d->buildingGroup_) {
398 emit<InputMethodManager::GroupAdded>(name);
403 if (groupCount() == 1) {
407 bool isCurrent = d->groupOrder_.front() == name;
408 auto iter = d->groups_.find(name);
409 if (iter != d->groups_.end()) {
411 emit<InputMethodManager::CurrentGroupAboutToChange>(
412 d->groupOrder_.front());
414 d->groups_.erase(iter);
415 d->groupOrder_.remove(name);
418 emit<InputMethodManager::CurrentGroupChanged>(
419 d->groupOrder_.front());
421 if (!d->buildingGroup_) {
422 emit<InputMethodManager::GroupRemoved>(name);
429 InputMethodConfig config;
430 std::vector<InputMethodGroupConfig> groups;
431 config.groupOrder.setValue(
432 std::vector<std::string>{d->groupOrder_.begin(), d->groupOrder_.end()});
434 for (
auto &p : d->groups_) {
435 auto &group = p.second;
436 groups.emplace_back();
437 auto &groupConfig = groups.back();
438 groupConfig.name.setValue(group.name());
439 groupConfig.defaultLayout.setValue(group.defaultLayout());
440 groupConfig.defaultInputMethod.setValue(group.defaultInputMethod());
441 std::vector<InputMethodGroupItemConfig> itemsConfig;
442 for (
auto &item : group.inputMethodList()) {
443 itemsConfig.emplace_back();
444 auto &itemConfig = itemsConfig.back();
445 itemConfig.name.setValue(item.name());
446 itemConfig.layout.setValue(item.layout());
449 groupConfig.items.setValue(std::move(itemsConfig));
451 config.groups.setValue(std::move(groups));
453 safeSaveAsIni(config,
"profile");
459 return findValue(d->entries_, name);
464 for (
auto &p : d->entries_) {
465 if (!callback(p.second)) {
474 if (!d->buildingGroup_) {
475 throw std::runtime_error(
"Called not within building group");
477 d->setGroupOrder(groups);
483 StandardPathsType::PkgData,
"inputmethod");
484 return timestamp > d->timestamp_;
void addEmptyGroup(const std::string &name)
Create a new empty group with given name.
Class wrap around the unix fd.
std::vector< std::string > groups() const
Return all the names of group by order.
static const StandardPaths & global()
Return the global instance of StandardPath.
const InputMethodEntry * entry(const std::string &name) const
Return a given input method entry by name.
void setCurrentGroup(const std::string &group)
Set the name of current group, rest of the group order will be adjusted accordingly.
void setGroupOrder(const std::vector< std::string > &groups)
Update the initial order of groups.
bool checkUpdate() const
Check if there is new entries could be loaded.
void load(const std::function< void(InputMethodManager &)> &buildDefaultGroupCallback={})
Load the input method information from disk.
AddonInstance * addon(const std::string &name, bool load=false)
Get the loaded addon instance.
An instance represents a standalone Fcitx instance.
void removeGroup(const std::string &name)
Remove an existing group by name.
A raw configuration tree that stores key-value pairs in a hierarchical structure. ...
void enumerateGroup(bool forward)
Simply enumerate input method groups.
Utilities to enable use object with signal.
Utility class to handle unix file descriptor.
New Utility classes to handle application specific path.
void setGroup(InputMethodGroup newGroupInfo)
Update the information of an existing group.
int fd() const noexcept
Get the internal fd.
void reset(const std::function< void(InputMethodManager &)> &buildDefaultGroupCallback={})
Reset all the group information to initial state.
void setDefaultInputMethod(const std::string &name)
Set default input method for current group.
static UnixFD openPath(const std::filesystem::path &path, std::optional< int > flags=std::nullopt, std::optional< mode_t > mode=std::nullopt)
Open the path.
void save()
Save the input method information to disk.
void enumerateGroupTo(const std::string &groupName)
enumerate group to a certain group.
const InputMethodGroup * group(const std::string &name) const
Return the input method group of given name.
const AddonInfo * addonInfo(const std::string &name) const
Get addon information for given addon.
bool foreachEntries(const std::function< bool(const InputMethodEntry &entry)> &callback)
Enumerate all the input method entries.
const InputMethodGroup & currentGroup() const
Return the current group.
int groupCount() const
Return the number of groups.
void refresh()
Load new input method configuration file from disk.