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 <set>
10 #include "action.h"
11 #include "event.h"
12 #include "inputcontext.h"
13 #include "instance.h"
14 #include "userinterface.h"
15 
16 namespace fcitx {
17 
19  template <typename T>
20  std::underlying_type_t<T> operator()(T t) const {
21  return static_cast<std::underlying_type_t<T>>(t);
22  }
23 };
24 
25 class IdAllocator {
26 public:
27  int allocId() {
28  if (freeList_.empty()) {
29  ++maxId_;
30  return maxId_;
31  }
32  auto value = *freeList_.begin();
33  freeList_.erase(freeList_.begin());
34  return value;
35  }
36  void returnId(int id) {
37  assert(id <= maxId_ && freeList_.count(id) == 0);
38  freeList_.insert(id);
39  }
40 
41  std::set<int> freeList_;
42  int maxId_ = 0;
43 };
44 
46 public:
48  : addonManager_(addonManager) {}
49 
50  void registerAction(const std::string &name, int id, Action *action) {
51  ScopedConnection conn = action->connect<ObjectDestroyed>(
52  [this, action](void *) { unregisterAction(action); });
53  action->setName(name);
54  action->setId(id);
55  actions_.emplace(name, std::make_pair(action, std::move(conn)));
56  idToAction_.emplace(action->id(), action);
57  }
58 
59  void unregisterAction(Action *action) {
60  auto iter = actions_.find(action->name());
61  if (iter == actions_.end()) {
62  return;
63  }
64  if (std::get<0>(iter->second) != action) {
65  return;
66  }
67  actions_.erase(iter);
68  idToAction_.erase(action->id());
69  ids_.returnId(action->id());
70  action->setName(std::string());
71  action->setId(0);
72  }
73 
74  void updateDispatch(UserInterfaceComponent comp, InputContext *ic);
75 
76  template <UserInterfaceComponent component>
77  void updateSingleComponent(InputContext *ic);
78 
79  UserInterface *ui_ = nullptr;
80  std::string uiName_;
81  std::vector<std::string> uis_;
82 
83  std::unordered_map<std::string, std::pair<Action *, ScopedConnection>>
84  actions_;
85  std::unordered_map<int, Action *> idToAction_;
86 
87  using UIUpdateList = std::list<std::pair<
88  InputContext *, std::unordered_set<UserInterfaceComponent, EnumHash>>>;
89  UIUpdateList updateList_;
90  std::unordered_map<InputContext *, UIUpdateList::iterator> updateIndex_;
91  AddonManager *addonManager_;
92 
93  IdAllocator ids_;
94  bool isVirtualKeyboardVisible_ = false;
95 };
96 
97 template <>
98 void UserInterfaceManagerPrivate::updateSingleComponent<
100  if (const auto &virtualKeyboardCallback =
101  ic->inputPanel().customVirtualKeyboardCallback()) {
102  virtualKeyboardCallback(ic);
103  } else if (ui_ != nullptr && ui_->addonInfo() != nullptr &&
104  ui_->addonInfo()->uiType() == UIType::OnScreenKeyboard) {
105  ui_->update(UserInterfaceComponent::InputPanel, ic);
106  } else if (const auto &callback =
107  ic->inputPanel().customInputPanelCallback()) {
108  callback(ic);
109  } else if (ic->capabilityFlags().test(
111  ic->updateClientSideUIImpl();
112  } else if (ui_) {
113  ui_->update(UserInterfaceComponent::InputPanel, ic);
114  }
115 }
116 
117 template <>
118 void UserInterfaceManagerPrivate::updateSingleComponent<
120  if (ui_) {
121  ui_->update(UserInterfaceComponent::StatusArea, ic);
122  }
123 }
124 
125 void UserInterfaceManagerPrivate::updateDispatch(UserInterfaceComponent comp,
126  InputContext *ic) {
127 #define _UI_CASE(COMP) \
128  case COMP: \
129  updateSingleComponent<COMP>(ic); \
130  break
131 
132  switch (comp) {
135  }
136 #undef _UI_CASE
137 }
138 
139 UserInterfaceManager::UserInterfaceManager(AddonManager *addonManager)
140  : d_ptr(std::make_unique<UserInterfaceManagerPrivate>(addonManager)) {}
141 
142 UserInterfaceManager::~UserInterfaceManager() = default;
143 
144 void UserInterfaceManager::load(const std::string &uiName) {
145  FCITX_D();
146  auto names = d->addonManager_->addonNames(AddonCategory::UI);
147 
148  d->uis_.clear();
149  if (names.count(uiName)) {
150  auto *ui = d->addonManager_->addon(uiName, true);
151  if (ui) {
152  d->uis_.push_back(uiName);
153  }
154  }
155 
156  if (d->uis_.empty()) {
157  d->uis_.insert(d->uis_.end(), names.begin(), names.end());
158  std::sort(d->uis_.begin(), d->uis_.end(),
159  [d](const std::string &lhs, const std::string &rhs) {
160  const auto *linfo = d->addonManager_->addonInfo(lhs);
161  const auto *rinfo = d->addonManager_->addonInfo(rhs);
162  if (!linfo) {
163  return false;
164  }
165  if (!rinfo) {
166  return true;
167  }
168  auto lp = linfo->uiPriority();
169  auto rp = rinfo->uiPriority();
170  if (lp == rp) {
171  return lhs > rhs;
172  }
173  return lp > rp;
174  });
175  }
176  updateAvailability();
177 }
178 
180  FCITX_D();
181  auto id = d->ids_.allocId();
182  auto name = stringutils::concat("$", id);
183  auto iter = d->actions_.find(name);
184  // This should never happen.
185  if (iter != d->actions_.end()) {
186  FCITX_ERROR() << "Reserved id is used, how can this be possible?";
187  d->ids_.returnId(id);
188  return false;
189  }
190  d->registerAction(name, id, action);
191  return true;
192 }
193 
194 bool UserInterfaceManager::registerAction(const std::string &name,
195  Action *action) {
196  FCITX_D();
197  if (!action->name().empty() || name.empty()) {
198  return false;
199  }
200  if (stringutils::startsWith(name, "$")) {
201  FCITX_ERROR() << "Action name starts with $ is reserved.";
202  return false;
203  }
204  auto iter = d->actions_.find(name);
205  if (iter != d->actions_.end()) {
206  return false;
207  }
208 
209  d->registerAction(name, d->ids_.allocId(), action);
210  return true;
211 }
212 
214  FCITX_D();
215  d->unregisterAction(action);
216 }
217 
218 Action *UserInterfaceManager::lookupAction(const std::string &name) const {
219  FCITX_D();
220  auto iter = d->actions_.find(name);
221  if (iter == d->actions_.end()) {
222  return nullptr;
223  }
224  return std::get<0>(iter->second);
225 }
226 
228  FCITX_D();
229  auto iter = d->idToAction_.find(id);
230  if (iter == d->idToAction_.end()) {
231  return nullptr;
232  }
233  return iter->second;
234 }
235 
237  InputContext *inputContext) {
238  FCITX_D();
239  auto iter = d->updateIndex_.find(inputContext);
240  decltype(d->updateList_)::iterator listIter;
241  if (d->updateIndex_.end() == iter) {
242  d->updateList_.emplace_back(std::piecewise_construct,
243  std::forward_as_tuple(inputContext),
244  std::forward_as_tuple());
245  d->updateIndex_[inputContext] = listIter =
246  std::prev(d->updateList_.end());
247  } else {
248  listIter = iter->second;
249  }
250  listIter->second.insert(component);
251 }
252 
254  FCITX_D();
255  auto iter = d->updateIndex_.find(inputContext);
256  if (d->updateIndex_.end() != iter) {
257  d->updateList_.erase(iter->second);
258  d->updateIndex_.erase(iter);
259  }
260 }
261 
262 void UserInterfaceManager::flush() {
263  FCITX_D();
264  auto *instance = d->addonManager_->instance();
265  for (auto &p : d->updateList_) {
266  for (auto comp : p.second) {
267  instance->postEvent(InputContextFlushUIEvent(comp, p.first));
268  d->updateDispatch(comp, p.first);
269  }
270  }
271  d->updateIndex_.clear();
272  d->updateList_.clear();
273 }
274 
275 bool isUserInterfaceValid(UserInterface *userInterface,
276  InputMethodMode inputMethodMode) {
277  if (userInterface == nullptr || !userInterface->available() ||
278  userInterface->addonInfo() == nullptr) {
279  return false;
280  }
281 
282  return (userInterface->addonInfo()->uiType() == UIType::OnScreenKeyboard &&
283  inputMethodMode == InputMethodMode::OnScreenKeyboard) ||
284  (userInterface->addonInfo()->uiType() == UIType::PhyscialKeyboard &&
285  inputMethodMode == InputMethodMode::PhysicalKeyboard);
286 }
287 
289  FCITX_D();
290  auto *instance = d->addonManager_->instance();
291  auto *oldUI = d->ui_;
292  UserInterface *newUI = nullptr;
293  std::string newUIName;
294  for (auto &name : d->uis_) {
295  auto *ui =
296  static_cast<UserInterface *>(d->addonManager_->addon(name, true));
297  if (isUserInterfaceValid(ui, instance
298  ? instance->inputMethodMode()
299  : InputMethodMode::PhysicalKeyboard)) {
300  newUI = ui;
301  newUIName = name;
302  break;
303  }
304  }
305  if (oldUI != newUI) {
306  FCITX_DEBUG() << "Switching UI addon to " << newUIName;
307  if (oldUI) {
308  oldUI->suspend();
309  }
310  if (newUI) {
311  newUI->resume();
312  }
313  d->ui_ = newUI;
314  d->uiName_ = std::move(newUIName);
315  if (instance) {
316  instance->postEvent(UIChangedEvent());
317  }
318  }
319 
320  updateVirtualKeyboardVisibility();
321 }
322 
323 std::string UserInterfaceManager::currentUI() const {
324  FCITX_D();
325  return d->uiName_;
326 }
327 
329  FCITX_D();
330  return d->isVirtualKeyboardVisible_;
331 }
332 
334  FCITX_D();
335 
336  auto *instance = d->addonManager_->instance();
337  if (!instance->virtualKeyboardAutoShow()) {
338  return;
339  }
340 
341  auto *ui = d->ui_;
342  if (ui == nullptr || ui->addonInfo() == nullptr ||
343  ui->addonInfo()->uiType() != UIType::OnScreenKeyboard) {
344  return;
345  }
346 
347  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
348  vkui->showVirtualKeyboard();
349 }
350 
352  FCITX_D();
353 
354  auto *instance = d->addonManager_->instance();
355  if (!instance->virtualKeyboardAutoHide()) {
356  return;
357  }
358 
359  auto *ui = d->ui_;
360  if (ui == nullptr || ui->addonInfo() == nullptr ||
361  ui->addonInfo()->uiType() != UIType::OnScreenKeyboard) {
362  return;
363  }
364 
365  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
366  vkui->hideVirtualKeyboard();
367 }
368 
370  FCITX_D();
371  bool oldVisible = d->isVirtualKeyboardVisible_;
372  bool newVisible = false;
373 
374  auto *ui = d->ui_;
375  if (ui && ui->addonInfo() &&
376  ui->addonInfo()->uiType() == UIType::OnScreenKeyboard) {
377  auto *vkui = static_cast<VirtualKeyboardUserInterface *>(ui);
378  newVisible = vkui->isVirtualKeyboardVisible();
379  }
380 
381  if (oldVisible != newVisible) {
382  d->isVirtualKeyboardVisible_ = newVisible;
383 
384  if (auto *instance = d->addonManager_->instance()) {
385  instance->postEvent(VirtualKeyboardVisibilityChangedEvent());
386  }
387  }
388 }
389 
390 } // 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.
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.
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 avaiability change.
Events triggered that user interface manager that flush the UI update.
Definition: event.h:523
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.
void updateVirtualKeyboardVisibility()
Invoke by user interface addon to notify if there is any virtual keyboard visibility change...
bool startsWith(std::string_view str, std::string_view prefix)
Check if a string starts with a prefix.
Definition: stringutils.cpp:86
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.
An input context represents a client of Fcitx.
Definition: inputcontext.h:47