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 class InputMethodManagerPrivate : QPtrHolder<InputMethodManager> {
43 public:
46  : QPtrHolder(q), addonManager_(addonManager_) {}
47 
48  void loadConfig(const std::function<void(InputMethodManager &)>
49  &buildDefaultGroupCallback);
50  void buildDefaultGroup(const std::function<void(InputMethodManager &)>
51  &buildDefaultGroupCallback);
52  void setGroupOrder(const std::vector<std::string> &groupOrder);
53 
54  // Read entries from inputmethod/*.conf
55  void loadStaticEntries(const std::unordered_set<std::string> &addonNames);
56  // Read entries from addon->listInputMethods();
57  void loadDynamicEntries(const std::unordered_set<std::string> &addonNames);
58 
59  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, CurrentGroupAboutToChange);
60  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, CurrentGroupChanged);
61  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, GroupAdded);
62  FCITX_DEFINE_SIGNAL_PRIVATE(InputMethodManager, GroupRemoved);
63 
64  AddonManager *addonManager_;
65  std::list<std::string> groupOrder_;
66  bool buildingGroup_ = false;
67  std::unordered_map<std::string, InputMethodGroup> groups_;
68  std::unordered_map<std::string, InputMethodEntry> entries_;
69  Instance *instance_ = nullptr;
70  std::unique_ptr<HandlerTableEntry<EventHandler>> eventWatcher_;
71  int64_t timestamp_ = 0;
72 };
73 
74 bool checkEntry(const InputMethodEntry &entry,
75  const std::unordered_set<std::string> &inputMethods) {
76  return !(entry.name().empty() || entry.uniqueName().empty() ||
77  entry.addon().empty() || inputMethods.count(entry.addon()) == 0);
78 }
79 
80 void InputMethodManagerPrivate::loadConfig(
81  const std::function<void(InputMethodManager &)>
82  &buildDefaultGroupCallback) {
83  const auto &path = StandardPaths::global();
84  auto file = path.open(StandardPathsType::PkgConfig, "profile");
85  RawConfig config;
86  if (file.isValid()) {
87  readFromIni(config, file.fd());
88  }
89  InputMethodConfig imConfig;
90  imConfig.load(config);
91 
92  groups_.clear();
93  std::vector<std::string> tempOrder;
94  if (!imConfig.groups.value().empty()) {
95  const auto &groupsConfig = imConfig.groups.value();
96  for (const auto &groupConfig : groupsConfig) {
97  // group must have a name
98  if (groupConfig.name.value().empty() ||
99  groupConfig.defaultLayout.value().empty()) {
100  continue;
101  }
102  auto result =
103  groups_.emplace(groupConfig.name.value(),
104  InputMethodGroup(groupConfig.name.value()));
105  tempOrder.push_back(groupConfig.name.value());
106  auto &group = result.first->second;
107  group.setDefaultLayout(groupConfig.defaultLayout.value());
108  const auto &items = groupConfig.items.value();
109  for (const auto &item : items) {
110  if (!entries_.count(item.name.value())) {
111  FCITX_WARN() << "Group Item " << item.name.value()
112  << " in group " << groupConfig.name.value()
113  << " is not valid. Removed.";
114  continue;
115  }
116  group.inputMethodList().emplace_back(
117  std::move(InputMethodGroupItem(item.name.value())
118  .setLayout(item.layout.value())));
119  }
120 
121  if (group.inputMethodList().empty()) {
122  FCITX_WARN() << "Group " << groupConfig.name.value()
123  << " is empty. Removed.";
124  groups_.erase(groupConfig.name.value());
125  continue;
126  }
127  group.setDefaultInputMethod(groupConfig.defaultInputMethod.value());
128  }
129  }
130 
131  if (groups_.empty()) {
132  FCITX_INFO() << "No valid input method group in configuration. "
133  << "Building a default one";
134  buildDefaultGroup(buildDefaultGroupCallback);
135  } else {
136  if (!imConfig.groupOrder.value().empty()) {
137  setGroupOrder(imConfig.groupOrder.value());
138  } else {
139  setGroupOrder(tempOrder);
140  }
141  }
142 }
143 
144 void InputMethodManagerPrivate::buildDefaultGroup(
145  const std::function<void(InputMethodManager &)>
146  &buildDefaultGroupCallback) {
147  groups_.clear();
148  if (buildDefaultGroupCallback) {
149  buildingGroup_ = true;
150  buildDefaultGroupCallback(*q_func());
151  buildingGroup_ = false;
152  } else {
153  std::string name = _("Default");
154  auto result = groups_.emplace(name, InputMethodGroup(name));
155  auto &group = result.first->second;
156  group.inputMethodList().emplace_back(
157  InputMethodGroupItem("keyboard-us"));
158  group.setDefaultInputMethod("");
159  group.setDefaultLayout("us");
160  setGroupOrder({std::move(name)});
161  }
162  assert(!groups_.empty());
163  assert(!groupOrder_.empty());
164 }
165 
166 void InputMethodManagerPrivate::loadStaticEntries(
167  const std::unordered_set<std::string> &addonNames) {
168  const auto &path = StandardPaths::global();
169  timestamp_ = path.timestamp(StandardPathsType::PkgData, "inputmethod");
170  auto filesMap = path.locate(StandardPathsType::PkgData, "inputmethod",
171  pathfilter::extension(".conf"));
172  for (const auto &[fileName, fullName] : filesMap) {
173  const auto u8name = fileName.stem().u8string();
174  std::string name(u8name.begin(), u8name.end());
175  if (entries_.count(name) != 0) {
176  continue;
177  }
178  RawConfig config;
179  UnixFD fd = StandardPaths::openPath(fullName);
180  readFromIni(config, fd.fd());
181 
182  InputMethodInfo imInfo;
183  imInfo.load(config);
184  if (!*imInfo.im->enable) {
185  continue;
186  }
187  // Remove ".conf"
188  InputMethodEntry entry = toInputMethodEntry(name, imInfo);
189  if (checkEntry(entry, addonNames)) {
190  entries_.emplace(std::string(entry.uniqueName()), std::move(entry));
191  }
192  }
193 }
194 
195 void InputMethodManagerPrivate::loadDynamicEntries(
196  const std::unordered_set<std::string> &addonNames) {
197  for (const auto &addonName : addonNames) {
198  const auto *addonInfo = addonManager_->addonInfo(addonName);
199  // on request input method should always provides entry with config file
200  if (!addonInfo || addonInfo->onDemand()) {
201  continue;
202  }
203  auto *engine =
204  static_cast<InputMethodEngine *>(addonManager_->addon(addonName));
205  if (!engine) {
206  FCITX_WARN() << "Failed to load input method addon: " << addonName;
207  continue;
208  }
209  auto newEntries = engine->listInputMethods();
210  FCITX_INFO() << "Found " << newEntries.size() << " input method(s) "
211  << "in addon " << addonName;
212  for (auto &newEntry : newEntries) {
213  // ok we can't let you register something weird.
214  if (checkEntry(newEntry, addonNames) &&
215  newEntry.addon() == addonName &&
216  entries_.count(newEntry.uniqueName()) == 0) {
217  entries_.emplace(std::string(newEntry.uniqueName()),
218  std::move(newEntry));
219  }
220  }
221  }
222 }
223 
224 InputMethodManager::InputMethodManager(AddonManager *addonManager)
225  : d_ptr(std::make_unique<InputMethodManagerPrivate>(addonManager, this)) {}
226 
227 InputMethodManager::~InputMethodManager() {}
228 
229 void InputMethodManager::load(const std::function<void(InputMethodManager &)>
230  &buildDefaultGroupCallback) {
231  FCITX_D();
232  emit<InputMethodManager::CurrentGroupAboutToChange>(
233  d->groupOrder_.empty() ? "" : d->groupOrder_.front());
234 
235  auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
236  d->loadStaticEntries(addonNames);
237  d->loadDynamicEntries(addonNames);
238 
239  d->loadConfig(buildDefaultGroupCallback);
240  // groupOrder guarantee to be non-empty at this point.
241  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
242 }
243 
244 void InputMethodManager::reset(const std::function<void(InputMethodManager &)>
245  &buildDefaultGroupCallback) {
246  FCITX_D();
247  emit<InputMethodManager::CurrentGroupAboutToChange>(
248  d->groupOrder_.empty() ? "" : d->groupOrder_.front());
249  d->buildDefaultGroup(buildDefaultGroupCallback);
250  // groupOrder guarantee to be non-empty at this point.
251  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
252 }
253 
255  FCITX_D();
256  auto addonNames = d->addonManager_->addonNames(AddonCategory::InputMethod);
257  d->loadStaticEntries(addonNames);
258  d->loadDynamicEntries(addonNames);
259 }
260 
261 std::vector<std::string> InputMethodManager::groups() const {
262  FCITX_D();
263  return {d->groupOrder_.begin(), d->groupOrder_.end()};
264 }
265 
267  FCITX_D();
268  return d->groups_.size();
269 }
270 
271 void InputMethodManager::setCurrentGroup(const std::string &groupName) {
272  FCITX_D();
273  if (groupName == d->groupOrder_.front()) {
274  return;
275  }
276  auto iter =
277  std::find(d->groupOrder_.begin(), d->groupOrder_.end(), groupName);
278  if (iter != d->groupOrder_.end()) {
279  emit<InputMethodManager::CurrentGroupAboutToChange>(
280  d->groupOrder_.front());
281  d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_, iter);
282  emit<InputMethodManager::CurrentGroupChanged>(groupName);
283  }
284 }
285 
286 void InputMethodManager::enumerateGroupTo(const std::string &groupName) {
287  FCITX_D();
288  if (groupName == d->groupOrder_.front()) {
289  return;
290  }
291  auto iter =
292  std::find(d->groupOrder_.begin(), d->groupOrder_.end(), groupName);
293  if (iter != d->groupOrder_.end()) {
294  emit<InputMethodManager::CurrentGroupAboutToChange>(
295  d->groupOrder_.front());
296  d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_, iter,
297  d->groupOrder_.end());
298  emit<InputMethodManager::CurrentGroupChanged>(groupName);
299  }
300 }
301 
303  FCITX_D();
304  return d->groups_.find(d->groupOrder_.front())->second;
305 }
306 
308  FCITX_D();
309  if (groupCount() < 2) {
310  return;
311  }
312  emit<InputMethodManager::CurrentGroupAboutToChange>(d->groupOrder_.front());
313  if (forward) {
314  d->groupOrder_.splice(d->groupOrder_.end(), d->groupOrder_,
315  d->groupOrder_.begin());
316  } else {
317  d->groupOrder_.splice(d->groupOrder_.begin(), d->groupOrder_,
318  std::prev(d->groupOrder_.end()));
319  }
320  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
321 }
322 
323 void InputMethodManager::setDefaultInputMethod(const std::string &name) {
324  FCITX_D();
325  auto &currentGroup = d->groups_.find(d->groupOrder_.front())->second;
326  currentGroup.setDefaultInputMethod(name);
327 }
328 
329 const InputMethodGroup *
330 InputMethodManager::group(const std::string &name) const {
331  FCITX_D();
332  return findValue(d->groups_, name);
333 }
334 
336  FCITX_D();
337  auto *group = findValue(d->groups_, newGroupInfo.name());
338  if (!group) {
339  return;
340  }
341  bool isCurrent = false;
342  if (!d->buildingGroup_) {
343  isCurrent = (group == &currentGroup());
344  if (isCurrent) {
345  emit<InputMethodManager::CurrentGroupAboutToChange>(
346  d->groupOrder_.front());
347  }
348  }
349  auto &list = newGroupInfo.inputMethodList();
350  auto iter = std::remove_if(list.begin(), list.end(),
351  [d](const InputMethodGroupItem &item) {
352  return !d->entries_.count(item.name());
353  });
354  list.erase(iter, list.end());
355  auto defaultInputMethod = newGroupInfo.defaultInputMethod();
356  if (defaultInputMethod.empty()) {
357  defaultInputMethod = group->defaultInputMethod();
358  }
359  newGroupInfo.setDefaultInputMethod(defaultInputMethod);
360  *group = std::move(newGroupInfo);
361  if (!d->buildingGroup_ && isCurrent) {
362  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
363  }
364 }
365 
366 void InputMethodManagerPrivate::setGroupOrder(
367  const std::vector<std::string> &groupOrder) {
368  groupOrder_.clear();
369  std::unordered_set<std::string> added;
370  for (const auto &groupName : groupOrder) {
371  if (groups_.count(groupName)) {
372  groupOrder_.push_back(groupName);
373  added.insert(groupName);
374  }
375  }
376  for (auto &p : groups_) {
377  if (!added.count(p.first)) {
378  groupOrder_.push_back(p.first);
379  }
380  }
381  assert(groupOrder_.size() == groups_.size());
382 }
383 
384 void InputMethodManager::addEmptyGroup(const std::string &name) {
385  if (group(name)) {
386  return;
387  }
388  FCITX_D();
389  InputMethodGroup newGroup(name);
390  if (groupCount()) {
391  newGroup.setDefaultLayout(currentGroup().defaultLayout());
392  }
393  if (newGroup.defaultLayout().empty()) {
394  newGroup.setDefaultLayout("us");
395  }
396  d->groups_.emplace(name, std::move(newGroup));
397  d->groupOrder_.push_back(name);
398  if (!d->buildingGroup_) {
399  emit<InputMethodManager::GroupAdded>(name);
400  }
401 }
402 
403 void InputMethodManager::removeGroup(const std::string &name) {
404  if (groupCount() == 1) {
405  return;
406  }
407  FCITX_D();
408  bool isCurrent = d->groupOrder_.front() == name;
409  auto iter = d->groups_.find(name);
410  if (iter != d->groups_.end()) {
411  if (isCurrent) {
412  emit<InputMethodManager::CurrentGroupAboutToChange>(
413  d->groupOrder_.front());
414  }
415  d->groups_.erase(iter);
416  d->groupOrder_.remove(name);
417 
418  if (isCurrent) {
419  emit<InputMethodManager::CurrentGroupChanged>(
420  d->groupOrder_.front());
421  }
422  if (!d->buildingGroup_) {
423  emit<InputMethodManager::GroupRemoved>(name);
424  }
425  }
426 }
427 
429  FCITX_D();
430  InputMethodConfig config;
431  std::vector<InputMethodGroupConfig> groups;
432  config.groupOrder.setValue(
433  std::vector<std::string>{d->groupOrder_.begin(), d->groupOrder_.end()});
434 
435  for (auto &p : d->groups_) {
436  auto &group = p.second;
437  groups.emplace_back();
438  auto &groupConfig = groups.back();
439  groupConfig.name.setValue(group.name());
440  groupConfig.defaultLayout.setValue(group.defaultLayout());
441  groupConfig.defaultInputMethod.setValue(group.defaultInputMethod());
442  std::vector<InputMethodGroupItemConfig> itemsConfig;
443  for (auto &item : group.inputMethodList()) {
444  itemsConfig.emplace_back();
445  auto &itemConfig = itemsConfig.back();
446  itemConfig.name.setValue(item.name());
447  itemConfig.layout.setValue(item.layout());
448  }
449 
450  groupConfig.items.setValue(std::move(itemsConfig));
451  }
452  config.groups.setValue(std::move(groups));
453 
454  safeSaveAsIni(config, "profile");
455 }
456 
457 const InputMethodEntry *
458 InputMethodManager::entry(const std::string &name) const {
459  FCITX_D();
460  return findValue(d->entries_, name);
461 }
463  const std::function<bool(const InputMethodEntry &entry)> &callback) {
464  FCITX_D();
465  for (auto &p : d->entries_) {
466  if (!callback(p.second)) {
467  return false;
468  }
469  }
470  return true;
471 }
472 
473 void InputMethodManager::setGroupOrder(const std::vector<std::string> &groups) {
474  FCITX_D();
475  if (!d->buildingGroup_) {
476  throw std::runtime_error("Called not within building group");
477  }
478  d->setGroupOrder(groups);
479 }
480 
482  FCITX_D();
483  auto timestamp = StandardPaths::global().timestamp(
484  StandardPathsType::PkgData, "inputmethod");
485  return timestamp > d->timestamp_;
486 }
487 
488 } // 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:92
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.