Fcitx
userinterfacemanager.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 "userinterfacemanager.h"
9 #include <algorithm>
10 #include <cassert>
11 #include <iterator>
12 #include <list>
13 #include <memory>
14 #include <set>
15 #include <string>
16 #include <tuple>
17 #include <type_traits>
18 #include <unordered_map>
19 #include <unordered_set>
20 #include <utility>
21 #include <vector>
24 #include "fcitx-utils/log.h"
25 #include "fcitx-utils/macros.h"
26 #include "fcitx-utils/misc.h"
27 #include "fcitx-utils/signals.h"
29 #include "fcitx/addoninfo.h"
30 #include "action.h"
31 #include "event.h"
32 #include "inputcontext.h"
33 #include "instance.h"
34 #include "userinterface.h"
35 
36 namespace fcitx {
37 
38 namespace {
39 
40 struct UserInterfaceComponentHash {
41  template <typename T>
42  std::underlying_type_t<T> operator()(T t) const {
43  return static_cast<std::underlying_type_t<T>>(t);
44  }
45 };
46 
47 class IdAllocator {
48 public:
49  int allocId() {
50  if (freeList_.empty()) {
51  ++maxId_;
52  return maxId_;
53  }
54  auto value = *freeList_.begin();
55  freeList_.erase(freeList_.begin());
56  return value;
57  }
58  void returnId(int id) {
59  assert(id <= maxId_ && !freeList_.contains(id));
60  freeList_.insert(id);
61  }
62 
63  std::set<int> freeList_;
64  int maxId_ = 0;
65 };
66 
67 bool isUserInterfaceValid(UserInterface *userInterface,
68  InputMethodMode inputMethodMode) {
69  if (userInterface == nullptr || !userInterface->available() ||
70  userInterface->addonInfo() == nullptr) {
71  return false;
72  }
73 
74  return (userInterface->addonInfo()->uiType() == UIType::OnScreenKeyboard &&
75  inputMethodMode == InputMethodMode::OnScreenKeyboard) ||
76  (userInterface->addonInfo()->uiType() == UIType::PhyscialKeyboard &&
77  inputMethodMode == InputMethodMode::PhysicalKeyboard);
78 }
79 
80 } // namespace
81 
83 public:
85  : addonManager_(addonManager) {}
86 
87  void registerAction(const std::string &name, int id, Action *action) {
88  ScopedConnection conn = action->connect<ObjectDestroyed>(
89  [this, action](void *) { unregisterAction(action); });
90  action->setName(name);
91  action->setId(id);
92  actions_.emplace(name, std::make_pair(action, std::move(conn)));
93  idToAction_.emplace(action->id(), action);
94  }
95 
96  void unregisterAction(Action *action) {
97  auto iter = actions_.find(action->name());
98  if (iter == actions_.end()) {
99  return;
100  }
101  if (std::get<0>(iter->second) != action) {
102  return;
103  }
104  actions_.erase(iter);
105  idToAction_.erase(action->id());
106  ids_.returnId(action->id());
107  action->setName(std::string());
108  action->setId(0);
109  }
110 
111  void updateDispatch(UserInterfaceComponent comp, InputContext *ic);
112 
113  template <UserInterfaceComponent component>
114  void updateSingleComponent(InputContext *ic);
115 
116  UserInterface *ui_ = nullptr;
117  std::string uiName_;
118  std::vector<std::string> uis_;
119 
120  std::unordered_map<std::string, std::pair<Action *, ScopedConnection>>
121  actions_;
122  std::unordered_map<int, Action *> idToAction_;
123 
124  using UIUpdateList = std::list<std::pair<
125  InputContext *, std::unordered_set<UserInterfaceComponent, EnumHash>>>;
126  UIUpdateList updateList_;
127  std::unordered_map<InputContext *, UIUpdateList::iterator> updateIndex_;
128  AddonManager *addonManager_;
129 
130  IdAllocator ids_;
131  bool isVirtualKeyboardVisible_ = false;
132 };
133 
134 template <>
135 void UserInterfaceManagerPrivate::updateSingleComponent<
137  if (const auto &virtualKeyboardCallback =
138  ic->inputPanel().customVirtualKeyboardCallback()) {
139  virtualKeyboardCallback(ic);
140  } else if (ui_ != nullptr && ui_->addonInfo() != nullptr &&
141  ui_->addonInfo()->uiType() == UIType::OnScreenKeyboard) {
142  ui_->update(UserInterfaceComponent::InputPanel, ic);
143  } else if (const auto &callback =
144  ic->inputPanel().customInputPanelCallback()) {
145  callback(ic);
146  } else if (ic->capabilityFlags().test(
148  ic->updateClientSideUIImpl();
149  } else if (ui_) {
150  ui_->update(UserInterfaceComponent::InputPanel, ic);
151  }
152 }
153 
154 template <>
155 void UserInterfaceManagerPrivate::updateSingleComponent<
157  if (ui_) {
158  ui_->update(UserInterfaceComponent::StatusArea, ic);
159  }
160 }
161 
162 void UserInterfaceManagerPrivate::updateDispatch(UserInterfaceComponent comp,
163  InputContext *ic) {
164 #define _UI_CASE(COMP) \
165  case COMP: \
166  updateSingleComponent<COMP>(ic); \
167  break
168 
169  switch (comp) {
172  }
173 #undef _UI_CASE
174 }
175 
176 UserInterfaceManager::UserInterfaceManager(AddonManager *addonManager)
177  : d_ptr(std::make_unique<UserInterfaceManagerPrivate>(addonManager)) {}
178 
179 UserInterfaceManager::~UserInterfaceManager() = default;
180 
181 void UserInterfaceManager::load(const std::string &uiName) {
182  FCITX_D();
183  auto names = d->addonManager_->addonNames(AddonCategory::UI);
184 
185  d->uis_.clear();
186  if (names.contains(uiName)) {
187  auto *ui = d->addonManager_->addon(uiName, true);
188  if (ui) {
189  d->uis_.push_back(uiName);
190  }
191  }
192 
193  if (d->uis_.empty()) {
194  d->uis_.insert(d->uis_.end(), names.begin(), names.end());
195  std::ranges::sort(
196  d->uis_, [d](const std::string &lhs, const std::string &rhs) {
197  const auto *linfo = d->addonManager_->addonInfo(lhs);
198  const auto *rinfo = d->addonManager_->addonInfo(rhs);
199  if (!linfo) {
200  return false;
201  }
202  if (!rinfo) {
203  return true;
204  }
205  auto lp = linfo->uiPriority();
206  auto rp = rinfo->uiPriority();
207  if (lp == rp) {
208  return lhs > rhs;
209  }
210  return lp > rp;
211  });
212  }
213  updateAvailability();
214 }
215 
217  FCITX_D();
218  auto id = d->ids_.allocId();
219  auto name = stringutils::concat("$", id);
220  auto iter = d->actions_.find(name);
221  // This should never happen.
222  if (iter != d->actions_.end()) {
223  FCITX_ERROR() << "Reserved id is used, how can this be possible?";
224  d->ids_.returnId(id);
225  return false;
226  }
227  d->registerAction(name, id, action);
228  return true;
229 }
230 
231 bool UserInterfaceManager::registerAction(const std::string &name,
232  Action *action) {
233  FCITX_D();
234  if (!action->name().empty() || name.empty()) {
235  return false;
236  }
237  if (name.starts_with("$")) {
238  FCITX_ERROR() << "Action name starts with $ is reserved.";
239  return false;
240  }
241  auto iter = d->actions_.find(name);
242  if (iter != d->actions_.end()) {
243  return false;
244  }
245 
246  d->registerAction(name, d->ids_.allocId(), action);
247  return true;
248 }
249 
251  FCITX_D();
252  d->unregisterAction(action);
253 }
254 
255 Action *UserInterfaceManager::lookupAction(const std::string &name) const {
256  FCITX_D();
257  auto iter = d->actions_.find(name);
258  if (iter == d->actions_.end()) {
259  return nullptr;
260  }
261  return std::get<0>(iter->second);
262 }
263 
265  FCITX_D();
266  auto iter = d->idToAction_.find(id);
267  if (iter == d->idToAction_.end()) {
268  return nullptr;
269  }
270  return iter->second;
271 }
272 
274  InputContext *inputContext) {
275  FCITX_D();
276  auto iter = d->updateIndex_.find(inputContext);
277  decltype(d->updateList_)::iterator listIter;
278  if (d->updateIndex_.end() == iter) {
279  d->updateList_.emplace_back(std::piecewise_construct,
280  std::forward_as_tuple(inputContext),
281  std::forward_as_tuple());
282  d->updateIndex_[inputContext] = listIter =
283  std::prev(d->updateList_.end());
284  } else {
285  listIter = iter->second;
286  }
287  listIter->second.insert(component);
288 }
289 
291  FCITX_D();
292  auto iter = d->updateIndex_.find(inputContext);
293  if (d->updateIndex_.end() != iter) {
294  d->updateList_.erase(iter->second);
295  d->updateIndex_.erase(iter);
296  }
297 }
298 
299 void UserInterfaceManager::flush() {
300  FCITX_D();
301  auto *instance = d->addonManager_->instance();
302  for (auto &p : d->updateList_) {
303  for (auto comp : p.second) {
304  instance->postEvent(InputContextFlushUIEvent(comp, p.first));
305  d->updateDispatch(comp, p.first);
306  }
307  }
308  d->updateIndex_.clear();
309  d->updateList_.clear();
310 }
311 
313  FCITX_D();
314  auto *instance = d->addonManager_->instance();
315  auto *oldUI = d->ui_;
316  UserInterface *newUI = nullptr;
317  std::string newUIName;
318  for (auto &name : d->uis_) {
319  auto *ui =
320  static_cast<UserInterface *>(d->addonManager_->addon(name, true));
321  if (isUserInterfaceValid(ui, instance
322  ? instance->inputMethodMode()
323  : InputMethodMode::PhysicalKeyboard)) {
324  newUI = ui;
325  newUIName = name;
326  break;
327  }
328  }
329  if (oldUI != newUI) {
330  FCITX_DEBUG() << "Switching UI addon to " << newUIName;
331  if (oldUI) {
332  oldUI->suspend();
333  }
334  if (newUI) {
335  newUI->resume();
336  }
337  d->ui_ = newUI;
338  d->uiName_ = std::move(newUIName);
339  if (instance) {
340  instance->postEvent(UIChangedEvent());
341  }
342  }
343 
344  updateVirtualKeyboardVisibility();
345 }
346 
347 std::string UserInterfaceManager::currentUI() const {
348  FCITX_D();
349  return d->uiName_;
350 }
351 
353  FCITX_D();
354  return d->isVirtualKeyboardVisible_;
355 }
356 
358  FCITX_D();
359 
360  auto *instance = d->addonManager_->instance();
361  if (!instance->virtualKeyboardAutoShow()) {
362  return;
363  }
364 
365  auto *ui = d->ui_;
366  if (ui == nullptr || ui->addonInfo() == nullptr ||
367  ui->addonInfo()->uiType() != UIType::OnScreenKeyboard) {
368  return;
369  }
370 
371  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
372  vkui->showVirtualKeyboard();
373 }
374 
376  FCITX_D();
377 
378  auto *instance = d->addonManager_->instance();
379  if (!instance->virtualKeyboardAutoHide()) {
380  return;
381  }
382 
383  auto *ui = d->ui_;
384  if (ui == nullptr || ui->addonInfo() == nullptr ||
385  ui->addonInfo()->uiType() != UIType::OnScreenKeyboard) {
386  return;
387  }
388 
389  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
390  vkui->hideVirtualKeyboard();
391 }
392 
394  FCITX_D();
395  bool oldVisible = d->isVirtualKeyboardVisible_;
396  bool newVisible = false;
397 
398  auto *ui = d->ui_;
399  if (ui && ui->addonInfo() &&
400  ui->addonInfo()->uiType() == UIType::OnScreenKeyboard) {
401  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
402  newVisible = vkui->isVirtualKeyboardVisible();
403  }
404 
405  if (oldVisible != newVisible) {
406  d->isVirtualKeyboardVisible_ = newVisible;
407 
408  if (auto *instance = d->addonManager_->instance()) {
409  instance->postEvent(VirtualKeyboardVisibilityChangedEvent());
410  }
411  }
412 }
413 
414 } // namespace fcitx
void hideVirtualKeyboard() const
Hide the virtual keyboard.
bool registerAction(const std::string &name, Action *action)
Register an named action.
void unregisterAction(Action *action)
Unregister the action.
Definition: action.cpp:17
The Action class provides an abstraction for user commands that can be added to user interfaces...
Definition: action.h:34
void load(const std::string &ui={})
Initialize the UI Addon.
Utilities to enable use object with signal.
std::string currentUI() const
Return the current active addon ui name.
int id()
Return the unique integer id of action.
Definition: action.cpp:55
Manager class for user interface.
Enum type for input context capability.
Action * lookupActionById(int id) const
Lookup an action by id.
Action class.
Base class for User Interface addon.
void updateAvailability()
Invoke by user interface addon to notify if there is any availability change.
Events triggered that user interface manager that flush the UI update.
Definition: event.h:536
void update(UserInterfaceComponent component, InputContext *inputContext)
Mark a user interface component to be updated for given input context.
UserInterfaceComponent
Definition: userinterface.h:21
void expire(InputContext *inputContext)
Remove all pending updates for a given input context.
Input Context for Fcitx.
Connection that will disconnection when it goes out of scope.
Definition: signals.h:88
bool isVirtualKeyboardVisible() const
Return if virtual keyboard is visible.
Whether client display input panel by itself.
A signal-slot implementation.
void updateVirtualKeyboardVisibility()
Invoke by user interface addon to notify if there is any virtual keyboard visibility change...
const std::string & name() const
The action name when this action is registered.
Definition: action.cpp:94
void showVirtualKeyboard() const
Show the virtual keyboard.
Action * lookupAction(const std::string &name) const
Lookup an action by the name.
String handle utilities.
An input context represents a client of Fcitx.
Definition: inputcontext.h:50
Log utilities.