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 werid.
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  newGroupInfo.setDefaultInputMethod(newGroupInfo.defaultInputMethod());
356  *group = std::move(newGroupInfo);
357  if (!d->buildingGroup_ && isCurrent) {
358  emit<InputMethodManager::CurrentGroupChanged>(d->groupOrder_.front());
359  }
360 }
361 
362 void InputMethodManagerPrivate::setGroupOrder(
363  const std::vector<std::string> &groupOrder) {
364  groupOrder_.clear();
365  std::unordered_set<std::string> added;
366  for (const auto &groupName : groupOrder) {
367  if (groups_.count(groupName)) {
368  groupOrder_.push_back(groupName);
369  added.insert(groupName);
370  }
371  }
372  for (auto &p : groups_) {
373  if (!added.count(p.first)) {
374  groupOrder_.push_back(p.first);
375  }
376  }
377  assert(groupOrder_.size() == groups_.size());
378 }
379 
380 void InputMethodManager::addEmptyGroup(const std::string &name) {
381  if (group(name)) {
382  return;
383  }
384  FCITX_D();
385  InputMethodGroup newGroup(name);
386  if (groupCount()) {
387  newGroup.setDefaultLayout(currentGroup().defaultLayout());
388  }
389  if (newGroup.defaultLayout().empty()) {
390  newGroup.setDefaultLayout("us");
391  }
392  d->groups_.emplace(name, std::move(newGroup));
393  d->groupOrder_.push_back(name);
394  if (!d->buildingGroup_) {
395  emit<InputMethodManager::GroupAdded>(name);
396  }
397 }
398 
399 void InputMethodManager::removeGroup(const std::string &name) {
400  if (groupCount() == 1) {
401  return;
402  }
403  FCITX_D();
404  bool isCurrent = d->groupOrder_.front() == name;
405  auto iter = d->groups_.find(name);
406  if (iter != d->groups_.end()) {
407  if (isCurrent) {
408  emit<InputMethodManager::CurrentGroupAboutToChange>(
409  d->groupOrder_.front());
410  }
411  d->groups_.erase(iter);
412  d->groupOrder_.remove(name);
413 
414  if (isCurrent) {
415  emit<InputMethodManager::CurrentGroupChanged>(
416  d->groupOrder_.front());
417  }
418  if (!d->buildingGroup_) {
419  emit<InputMethodManager::GroupRemoved>(name);
420  }
421  }
422 }
423 
425  FCITX_D();
426  InputMethodConfig config;
427  std::vector<InputMethodGroupConfig> groups;
428  config.groupOrder.setValue(
429  std::vector<std::string>{d->groupOrder_.begin(), d->groupOrder_.end()});
430 
431  for (auto &p : d->groups_) {
432  auto &group = p.second;
433  groups.emplace_back();
434  auto &groupConfig = groups.back();
435  groupConfig.name.setValue(group.name());
436  groupConfig.defaultLayout.setValue(group.defaultLayout());
437  groupConfig.defaultInputMethod.setValue(group.defaultInputMethod());
438  std::vector<InputMethodGroupItemConfig> itemsConfig;
439  for (auto &item : group.inputMethodList()) {
440  itemsConfig.emplace_back();
441  auto &itemConfig = itemsConfig.back();
442  itemConfig.name.setValue(item.name());
443  itemConfig.layout.setValue(item.layout());
444  }
445 
446  groupConfig.items.setValue(std::move(itemsConfig));
447  }
448  config.groups.setValue(std::move(groups));
449 
450  safeSaveAsIni(config, "profile");
451 }
452 
453 const InputMethodEntry *
454 InputMethodManager::entry(const std::string &name) const {
455  FCITX_D();
456  return findValue(d->entries_, name);
457 }
459  const std::function<bool(const InputMethodEntry &entry)> &callback) {
460  FCITX_D();
461  for (auto &p : d->entries_) {
462  if (!callback(p.second)) {
463  return false;
464  }
465  }
466  return true;
467 }
468 
469 void InputMethodManager::setGroupOrder(const std::vector<std::string> &groups) {
470  FCITX_D();
471  if (!d->buildingGroup_) {
472  throw std::runtime_error("Called not within building group");
473  }
474  d->setGroupOrder(groups);
475 }
476 
478  FCITX_D();
479  auto timestamp = StandardPaths::global().timestamp(
480  StandardPathsType::PkgData, "inputmethod");
481  return timestamp > d->timestamp_;
482 }
483 
484 } // 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:88
void removeGroup(const std::string &name)
Remove an existing group by name.
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 decriptor.
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 methdo 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.