Fcitx
inputmethodmanager.cpp
1 /*
2  * SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "inputmethodmanager.h"
9 #include <fcntl.h>
10 #include <algorithm>
11 #include <cassert>
12 #include <cstdint>
13 #include <functional>
14 #include <iterator>
15 #include <list>
16 #include <memory>
17 #include <stdexcept>
18 #include <string>
19 #include <unordered_map>
20 #include <unordered_set>
21 #include <utility>
22 #include <vector>
23 #include "fcitx-config/iniparser.h"
24 #include "fcitx-config/rawconfig.h"
26 #include "fcitx-utils/handlertable.h"
27 #include "fcitx-utils/i18n.h"
28 #include "fcitx-utils/log.h"
29 #include "fcitx-utils/macros.h"
30 #include "fcitx-utils/misc_p.h"
32 #include "fcitx-utils/unixfd.h"
33 #include "addoninfo.h"
34 #include "addonmanager.h"
35 #include "inputmethodconfig_p.h"
36 #include "inputmethodengine.h"
37 #include "inputmethodgroup.h"
38 #include "instance.h"
39 
40 namespace fcitx {
41 
42 namespace {
43 
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());
48 }
49 } // namespace
50 
51 class InputMethodManagerPrivate : QPtrHolder<InputMethodManager> {
52 public:
55  : QPtrHolder(q), addonManager_(addonManager_) {}
56 
57  void loadConfig(const std::function<void(InputMethodManager &)>
58  &buildDefaultGroupCallback);
59  void buildDefaultGroup(const std::function<void(InputMethodManager &)>
60  &buildDefaultGroupCallback);
61  void setGroupOrder(const std::vector<std::string> &groupOrder);
62 
63  // Read entries from inputmethod/*.conf
64  void loadStaticEntries(const std::unordered_set<std::string> &addonNames);
65  // Read entries from addon->listInputMethods();
66  void loadDynamicEntries(const std::unordered_set<std::string> &addonNames);
67 
68  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, CurrentGroupAboutToChange);
69  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, CurrentGroupChanged);
70  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, GroupAdded);
71  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, GroupRemoved);
72 
73  AddonManager *addonManager_;
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_;
78  Instance *instance_ = nullptr;
79  std::unique_ptr<HandlerTableEntry<EventHandler>> eventWatcher_;
80  int64_t timestamp_ = 0;
81 };
82 
83 void InputMethodManagerPrivate::loadConfig(
84  const std::function<void(InputMethodManager &)>
85  &buildDefaultGroupCallback) {
86  const auto &path = StandardPaths::global();
87  auto file = path.open(StandardPathsType::PkgConfig, "profile");
88  RawConfig config;
89  if (file.isValid()) {
90  readFromIni(config, file.fd());
91  }
92  InputMethodConfig imConfig;
93  imConfig.load(config);
94 
95  groups_.clear();
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) {
100  // group must have a name
101  if (groupConfig.name.value().empty() ||
102  groupConfig.defaultLayout.value().empty()) {
103  continue;
104  }
105  auto result =
106  groups_.emplace(groupConfig.name.value(),
107  InputMethodGroup(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.";
117  continue;
118  }
119  group.inputMethodList().emplace_back(
120  std::move(InputMethodGroupItem(item.name.value())
121  .setLayout(item.layout.value())));
122  }
123 
124  if (group.inputMethodList().empty()) {
125  FCITX_WARN() << "Group " << groupConfig.name.value()
126  << " is empty. Removed.";
127  groups_.erase(groupConfig.name.value());
128  continue;
129  }
130  group.setDefaultInputMethod(groupConfig.defaultInputMethod.value());
131  }
132  }
133 
134  if (groups_.empty()) {
135  FCITX_INFO() << "No valid input method group in configuration. "
136  << "Building a default one";
137  buildDefaultGroup(buildDefaultGroupCallback);
138  } else {
139  if (!imConfig.groupOrder.value().empty()) {
140  setGroupOrder(imConfig.groupOrder.value());
141  } else {
142  setGroupOrder(tempOrder);
143  }
144  }
145 }
146 
147 void InputMethodManagerPrivate::buildDefaultGroup(
148  const std::function<void(InputMethodManager &)>
149  &buildDefaultGroupCallback) {
150  groups_.clear();
151  if (buildDefaultGroupCallback) {
152  buildingGroup_ = true;
153  buildDefaultGroupCallback(*q_func());
154  buildingGroup_ = false;
155  } else {
156  std::string name = _("Default");
157  auto result = groups_.emplace(name, InputMethodGroup(name));
158  auto &group = result.first->second;
159  group.inputMethodList().emplace_back(
160  InputMethodGroupItem("keyboard-us"));
161  group.setDefaultInputMethod("");
162  group.setDefaultLayout("us");
163  setGroupOrder({std::move(name)});
164  }
165  assert(!groups_.empty());
166  assert(!groupOrder_.empty());
167 }
168 
169 void InputMethodManagerPrivate::loadStaticEntries(
170  const std::unordered_set<std::string> &addonNames) {
171  const auto &path = StandardPaths::global();
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)) {
179  continue;
180  }
181  RawConfig config;
182  UnixFD fd = StandardPaths::openPath(fullName);
183  readFromIni(config, fd.fd());
184 
185  InputMethodInfo imInfo;
186  imInfo.load(config);
187  if (!*imInfo.im->enable) {
188  continue;
189  }
190  // Remove ".conf"
191  InputMethodEntry entry = toInputMethodEntry(name, imInfo);
192  if (checkEntry(entry, addonNames)) {
193  entries_.emplace(std::string(entry.uniqueName()), std::move(entry));
194  }
195  }
196 }
197 
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);
202  // on request input method should always provides entry with config file
203  if (!addonInfo || addonInfo->onDemand()) {
204  continue;
205  }
206  auto *engine =
207  static_cast<InputMethodEngine *>(addonManager_->addon(addonName));
208  if (!engine) {
209  FCITX_WARN() << "Failed to load input method addon: " << addonName;
210  continue;
211  }
212  auto newEntries = engine->listInputMethods();
213  FCITX_INFO() << "Found " << newEntries.size() << " input method(s) "
214  << "in addon " << addonName;
215  for (auto &newEntry : newEntries) {
216  // ok we can't let you register something weird.
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));
222  }
223  }
224  }
225 }
226 
227 InputMethodManager::InputMethodManager(AddonManager *addonManager)
228  : d_ptr(std::make_unique<InputMethodManagerPrivate>(addonManager, this)) {}
229 
230 InputMethodManager::~InputMethodManager() {}
231 
232 void InputMethodManager::load(const std::function<void(InputMethodManager &)>
233  &buildDefaultGroupCallback) {
234  FCITX_D();
235  emit<InputMethodManager::CurrentGroupAboutToChange>(
236  d->groupOrder_.empty() ? "" : d->groupOrder_.front());
237 
238  auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
239  d->loadStaticEntries(addonNames);
240  d->loadDynamicEntries(addonNames);
241 
242  d->loadConfig(buildDefaultGroupCallback);
243  // groupOrder guarantee to be non-empty at this point.
244  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
245 }
246 
247 void InputMethodManager::reset(const std::function<void(InputMethodManager &)>
248  &buildDefaultGroupCallback) {
249  FCITX_D();
250  emit<InputMethodManager::CurrentGroupAboutToChange>(
251  d->groupOrder_.empty() ? "" : d->groupOrder_.front());
252  d->buildDefaultGroup(buildDefaultGroupCallback);
253  // groupOrder guarantee to be non-empty at this point.
254  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
255 }
256 
258  FCITX_D();
259  auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
260  d->loadStaticEntries(addonNames);
261  d->loadDynamicEntries(addonNames);
262 }
263 
264 std::vector<std::string> InputMethodManager::groups() const {
265  FCITX_D();
266  return {d->groupOrder_.begin(), d->groupOrder_.end()};
267 }
268 
270  FCITX_D();
271  return d->groups_.size();
272 }
273 
274 void InputMethodManager::setCurrentGroup(const std::string &groupName) {
275  FCITX_D();
276  if (groupName == d->groupOrder_.front()) {
277  return;
278  }
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);
285  }
286 }
287 
288 void InputMethodManager::enumerateGroupTo(const std::string &groupName) {
289  FCITX_D();
290  if (groupName == d->groupOrder_.front()) {
291  return;
292  }
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);
300  }
301 }
302 
304  FCITX_D();
305  return d->groups_.find(d->groupOrder_.front())->second;
306 }
307 
309  FCITX_D();
310  if (groupCount() < 2) {
311  return;
312  }
313  emit<InputMethodManager::CurrentGroupAboutToChange>(d->groupOrder_.front());
314  if (forward) {
315  d->groupOrder_.splice(d->groupOrder_.end(), d->groupOrder_,
316  d->groupOrder_.begin());
317  } else {
318  d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_,
319  std::prev(d->groupOrder_.end()));
320  }
321  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
322 }
323 
324 void InputMethodManager::setDefaultInputMethod(const std::string &name) {
325  FCITX_D();
326  auto &currentGroup = d->groups_.find(d->groupOrder_.front())->second;
327  currentGroup.setDefaultInputMethod(name);
328 }
329 
330 const InputMethodGroup *
331 InputMethodManager::group(const std::string &name) const {
332  FCITX_D();
333  return findValue(d->groups_, name);
334 }
335 
337  FCITX_D();
338  auto *group = findValue(d->groups_, newGroupInfo.name());
339  if (!group) {
340  return;
341  }
342  bool isCurrent = false;
343  if (!d->buildingGroup_) {
344  isCurrent = (group == &currentGroup());
345  if (isCurrent) {
346  emit<InputMethodManager::CurrentGroupAboutToChange>(
347  d->groupOrder_.front());
348  }
349  }
350  auto &list = newGroupInfo.inputMethodList();
351  std::erase_if(list, [d](const InputMethodGroupItem &item) {
352  return !d->entries_.contains(item.name());
353  });
354  auto defaultInputMethod = newGroupInfo.defaultInputMethod();
355  if (defaultInputMethod.empty()) {
356  defaultInputMethod = group->defaultInputMethod();
357  }
358  newGroupInfo.setDefaultInputMethod(defaultInputMethod);
359  *group = std::move(newGroupInfo);
360  if (!d->buildingGroup_ && isCurrent) {
361  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
362  }
363 }
364 
365 void InputMethodManagerPrivate::setGroupOrder(
366  const std::vector<std::string> &groupOrder) {
367  groupOrder_.clear();
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);
373  }
374  }
375  for (auto &p : groups_) {
376  if (!added.contains(p.first)) {
377  groupOrder_.push_back(p.first);
378  }
379  }
380  assert(groupOrder_.size() == groups_.size());
381 }
382 
383 void InputMethodManager::addEmptyGroup(const std::string &name) {
384  if (group(name)) {
385  return;
386  }
387  FCITX_D();
388  InputMethodGroup newGroup(name);
389  if (groupCount()) {
390  newGroup.setDefaultLayout(currentGroup().defaultLayout());
391  }
392  if (newGroup.defaultLayout().empty()) {
393  newGroup.setDefaultLayout("us");
394  }
395  d->groups_.emplace(name, std::move(newGroup));
396  d->groupOrder_.push_back(name);
397  if (!d->buildingGroup_) {
398  emit<InputMethodManager::GroupAdded>(name);
399  }
400 }
401 
402 void InputMethodManager::removeGroup(const std::string &name) {
403  if (groupCount() == 1) {
404  return;
405  }
406  FCITX_D();
407  bool isCurrent = d->groupOrder_.front() == name;
408  auto iter = d->groups_.find(name);
409  if (iter != d->groups_.end()) {
410  if (isCurrent) {
411  emit<InputMethodManager::CurrentGroupAboutToChange>(
412  d->groupOrder_.front());
413  }
414  d->groups_.erase(iter);
415  d->groupOrder_.remove(name);
416 
417  if (isCurrent) {
418  emit<InputMethodManager::CurrentGroupChanged>(
419  d->groupOrder_.front());
420  }
421  if (!d->buildingGroup_) {
422  emit<InputMethodManager::GroupRemoved>(name);
423  }
424  }
425 }
426 
428  FCITX_D();
429  InputMethodConfig config;
430  std::vector<InputMethodGroupConfig> groups;
431  config.groupOrder.setValue(
432  std::vector<std::string>{d->groupOrder_.begin(), d->groupOrder_.end()});
433 
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());
447  }
448 
449  groupConfig.items.setValue(std::move(itemsConfig));
450  }
451  config.groups.setValue(std::move(groups));
452 
453  safeSaveAsIni(config, "profile");
454 }
455 
456 const InputMethodEntry *
457 InputMethodManager::entry(const std::string &name) const {
458  FCITX_D();
459  return findValue(d->entries_, name);
460 }
462  const std::function<bool(const InputMethodEntry &entry)> &callback) {
463  FCITX_D();
464  for (auto &p : d->entries_) {
465  if (!callback(p.second)) {
466  return false;
467  }
468  }
469  return true;
470 }
471 
472 void InputMethodManager::setGroupOrder(const std::vector<std::string> &groups) {
473  FCITX_D();
474  if (!d->buildingGroup_) {
475  throw std::runtime_error("Called not within building group");
476  }
477  d->setGroupOrder(groups);
478 }
479 
481  FCITX_D();
482  auto timestamp = StandardPaths::global().timestamp(
483  StandardPathsType::PkgData, "inputmethod");
484  return timestamp > d->timestamp_;
485 }
486 
487 } // namespace fcitx
void addEmptyGroup(const std::string &name)
Create a new empty group with given name.
Class wrap around the unix fd.
Definition: unixfd.h:22
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.
Definition: instance.h:95
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. ...
Definition: rawconfig.h:37
Definition: action.cpp:17
void enumerateGroup(bool forward)
Simply enumerate input method groups.
Utilities to enable use object with signal.
Class to manage all the input method relation information.
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.
Definition: unixfd.cpp:41
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.
Input Method Manager For fcitx.
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.
Log utilities.
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.
Addon Manager class.
void refresh()
Load new input method configuration file from disk.