Fcitx
inputcontext.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 "inputcontext.h"
9 #include <cassert>
10 #include <chrono>
11 #include <regex>
12 #include <stdexcept>
13 #include <string>
14 #include "fcitx-utils/environ.h"
15 #include "fcitx-utils/utf8.h"
16 #include "focusgroup.h"
17 #include "inputcontext_p.h"
18 #include "inputcontextmanager.h"
19 #include "instance.h"
20 #include "misc_p.h"
21 #include "userinterfacemanager.h"
22 
23 namespace fcitx {
24 
25 namespace {
26 
27 bool shouldDisablePreeditByDefault(const std::string &program) {
28  static const std::vector<std::regex> matchers = []() {
29  std::vector<std::regex> matchers;
30  auto apps = getEnvironment("FCITX_NO_PREEDIT_APPS");
31  if (!apps) {
32  apps = NO_PREEDIT_APPS;
33  }
34  auto matcherStrings = stringutils::split(*apps, ",");
35  for (const auto &matcherString : matcherStrings) {
36  try {
37  matchers.emplace_back(matcherString, std::regex::icase |
38  std::regex::extended |
39  std::regex::nosubs);
40  } catch (...) {
41  }
42  }
43  return matchers;
44  }();
45 
46  return std::any_of(matchers.begin(), matchers.end(),
47  [&program](const std::regex &regex) {
48  return std::regex_match(program, regex);
49  });
50 }
51 } // namespace
52 
53 InputContextPrivate::InputContextPrivate(InputContext *q,
54  InputContextManager &manager,
55  const std::string &program)
56  : QPtrHolder(q), manager_(manager), group_(nullptr), inputPanel_(q),
57  statusArea_(q), program_(program),
58  isPreeditEnabled_(manager.isPreeditEnabledByDefault() &&
59  !shouldDisablePreeditByDefault(program)) {}
60 
61 #define RETURN_IF_HAS_NO_FOCUS(...) \
62  do { \
63  if (!hasFocus()) { \
64  return __VA_ARGS__; \
65  } \
66  } while (0);
67 
68 InputContext::InputContext(InputContextManager &manager,
69  const std::string &program)
70  : d_ptr(std::make_unique<InputContextPrivate>(this, manager, program)) {
71  manager.registerInputContext(*this);
72 }
73 
74 InputContext::~InputContext() { assert(d_ptr->destroyed_); }
75 
77  FCITX_D();
78  d->emplaceEvent<InputContextCreatedEvent>(this);
79 }
80 
81 std::string_view InputContext::frontendName() const { return frontend(); }
82 
84  FCITX_D();
85  assert(!d->destroyed_);
86  if (d->group_) {
87  d->group_->removeInputContext(this);
88  }
89  d->emplaceEvent<InputContextDestroyedEvent>(this);
90  d->manager_.unregisterInputContext(*this);
91  d->destroyed_ = true;
92 }
93 
94 const ICUUID &InputContext::uuid() const {
95  FCITX_D();
96  return d->uuid_;
97 }
98 
99 const std::string &InputContext::program() const {
100  FCITX_D();
101  return d->program_;
102 }
103 
104 std::string InputContext::display() const {
105  FCITX_D();
106  return d->group_ ? d->group_->display() : "";
107 }
108 
110  FCITX_D();
111  return d->cursorRect_;
112 }
113 
115  FCITX_D();
116  return d->scale_;
117 }
118 
119 InputContextProperty *InputContext::property(const std::string &name) {
120  FCITX_D();
121  auto *factory = d->manager_.factoryForName(name);
122  if (!factory) {
123  return nullptr;
124  }
125  return d->manager_.property(*this, factory);
126 }
127 
130  FCITX_D();
131  return d->manager_.property(*this, factory);
132 }
133 
134 void InputContext::updateProperty(const std::string &name) {
135  FCITX_D();
136  auto *factory = d->manager_.factoryForName(name);
137  if (!factory) {
138  return;
139  }
140  updateProperty(factory);
141 }
142 
144  FCITX_D();
145  auto *property = d->manager_.property(*this, factory);
146  if (!property->needCopy()) {
147  return;
148  }
149  d->manager_.propagateProperty(*this, factory);
150 }
151 
152 bool InputContext::isVirtualKeyboardVisible() const {
153  FCITX_D();
154  if (auto *instance = d->manager_.instance()) {
155  return instance->userInterfaceManager().isVirtualKeyboardVisible();
156  }
157  return false;
158 }
159 
160 void InputContext::showVirtualKeyboard() const {
161  FCITX_D();
162  if (auto *instance = d->manager_.instance()) {
163  return instance->userInterfaceManager().showVirtualKeyboard();
164  }
165 }
166 
167 void InputContext::hideVirtualKeyboard() const {
168  FCITX_D();
169  if (auto *instance = d->manager_.instance()) {
170  return instance->userInterfaceManager().hideVirtualKeyboard();
171  }
172 }
173 
174 bool InputContext::clientControlVirtualkeyboardShow() const {
175  FCITX_D();
176  return d->clientControlVirtualkeyboardShow_;
177 }
178 
179 void InputContext::setClientControlVirtualkeyboardShow(bool show) {
180  FCITX_D();
181  d->clientControlVirtualkeyboardShow_ = show;
182 }
183 
184 bool InputContext::clientControlVirtualkeyboardHide() const {
185  FCITX_D();
186  return d->clientControlVirtualkeyboardHide_;
187 }
188 
189 void InputContext::setClientControlVirtualkeyboardHide(bool hide) {
190  FCITX_D();
191  d->clientControlVirtualkeyboardHide_ = hide;
192 }
193 
194 CapabilityFlags calculateFlags(CapabilityFlags flag, bool isPreeditEnabled) {
195  if (!isPreeditEnabled) {
196  flag = flag.unset(CapabilityFlag::Preedit)
197  .unset(CapabilityFlag::FormattedPreedit);
198  }
199  return flag;
200 }
201 
203  FCITX_D();
204  if (d->capabilityFlags_ == flags) {
205  return;
206  }
207  const auto oldFlags = capabilityFlags();
208  auto newFlags = calculateFlags(flags, d->isPreeditEnabled_);
209  if (oldFlags != newFlags) {
210  d->emplaceEvent<CapabilityAboutToChangeEvent>(this, oldFlags, flags);
211  }
212  d->capabilityFlags_ = flags;
213  if (oldFlags != newFlags) {
214  d->emplaceEvent<CapabilityChangedEvent>(this, oldFlags, flags);
215  }
216 }
217 
219  FCITX_D();
220  return calculateFlags(d->capabilityFlags_, d->isPreeditEnabled_);
221 }
222 
224  FCITX_D();
225  if (enable == d->isPreeditEnabled_) {
226  return;
227  }
228  const auto oldFlags = capabilityFlags();
229  auto newFlags = calculateFlags(d->capabilityFlags_, enable);
230  if (oldFlags != newFlags) {
231  d->emplaceEvent<CapabilityAboutToChangeEvent>(this, oldFlags, newFlags);
232  }
233  d->isPreeditEnabled_ = enable;
234  if (oldFlags != newFlags) {
235  d->emplaceEvent<CapabilityChangedEvent>(this, oldFlags, newFlags);
236  }
237 }
238 
240  FCITX_D();
241  return d->isPreeditEnabled_;
242 }
243 
244 void InputContext::setCursorRect(Rect rect) { setCursorRect(rect, 1.0); }
245 
246 void InputContext::setCursorRect(Rect rect, double scale) {
247  FCITX_D();
248  if (d->cursorRect_ == rect && d->scale_ == scale) {
249  return;
250  }
251  d->cursorRect_ = rect;
252  d->scale_ = scale;
253  d->emplaceEvent<CursorRectChangedEvent>(this);
254 }
255 
257  FCITX_D();
258  focusOut();
259  if (d->group_) {
260  d->group_->removeInputContext(this);
261  }
262  d->group_ = group;
263  if (d->group_) {
264  d->group_->addInputContext(this);
265  }
266 }
267 
269  FCITX_D();
270  return d->group_;
271 }
272 
274  FCITX_D();
275  if (d->group_) {
276  d->group_->setFocusedInputContext(this);
277  } else {
278  setHasFocus(true);
279  }
280 }
281 
283  FCITX_D();
284  if (d->group_) {
285  if (d->group_->focusedInputContext() == this) {
286  d->group_->setFocusedInputContext(nullptr);
287  }
288  } else {
289  setHasFocus(false);
290  }
291 }
292 
294  FCITX_D();
295  return d->hasFocus_;
296 }
297 
298 void InputContext::setHasFocus(bool hasFocus) {
299  FCITX_D();
300  if (hasFocus == d->hasFocus_) {
301  return;
302  }
303  d->hasFocus_ = hasFocus;
304  d->manager_.notifyFocus(*this, d->hasFocus_);
305  // trigger event
306  if (d->hasFocus_) {
307  d->emplaceEvent<FocusInEvent>(this);
308  } else {
309  d->emplaceEvent<FocusOutEvent>(this);
310  }
311 }
312 
314  FCITX_D();
315  RETURN_IF_HAS_NO_FOCUS(false);
316  decltype(std::chrono::steady_clock::now()) start;
317  // Don't query time if we don't want log.
318  if (::keyTrace().checkLogLevel(LogLevel::Debug)) {
319  start = std::chrono::steady_clock::now();
320  }
321  auto result = d->postEvent(event);
322  FCITX_KEYTRACE() << "KeyEvent handling time: "
323  << std::chrono::duration_cast<std::chrono::milliseconds>(
324  std::chrono::steady_clock::now() - start)
325  .count()
326  << "ms result:" << result;
327  return result;
328 }
329 
331  FCITX_D();
332  RETURN_IF_HAS_NO_FOCUS(false);
333  decltype(std::chrono::steady_clock::now()) start;
334  // Don't query time if we don't want log.
335  if (::keyTrace().checkLogLevel(LogLevel::Debug)) {
336  start = std::chrono::steady_clock::now();
337  }
338  auto result = d->postEvent(event);
339  FCITX_KEYTRACE() << "VirtualKeyboardEvent handling time: "
340  << std::chrono::duration_cast<std::chrono::milliseconds>(
341  std::chrono::steady_clock::now() - start)
342  .count()
343  << "ms result:" << result;
344  return result;
345 }
346 
348  FCITX_D();
349  RETURN_IF_HAS_NO_FOCUS();
350  d->postEvent(event);
351 }
352 
353 void InputContext::reset(ResetReason) { reset(); }
354 
356  FCITX_D();
357  RETURN_IF_HAS_NO_FOCUS();
358  d->emplaceEvent<ResetEvent>(this);
359 }
360 
362  FCITX_D();
363  return d->surroundingText_;
364 }
365 
367  FCITX_D();
368  return d->surroundingText_;
369 }
370 
372  FCITX_D();
373  d->emplaceEvent<SurroundingTextUpdatedEvent>(this);
374 }
375 
377  FCITX_D();
378  if (d->blockEventToClient_ == block) {
379  throw std::invalid_argument(
380  "setBlockEventToClient has invalid argument. Probably a bug in the "
381  "implementation.");
382  }
383  d->blockEventToClient_ = block;
384  if (!block) {
385  d->deliverBlockedEvents();
386  }
387 }
388 
389 bool InputContext::hasPendingEvents() const {
390  FCITX_D();
391  return !d->blockedEvents_.empty();
392 }
393 
395  FCITX_D();
396  if (d->blockedEvents_.empty()) {
397  return false;
398  }
399 
400  // Check we only have update preedit.
401  if (std::any_of(d->blockedEvents_.begin(), d->blockedEvents_.end(),
402  [](const auto &event) {
403  return event->type() !=
404  EventType::InputContextUpdatePreedit;
405  })) {
406  return true;
407  }
408 
409  // Check whether the preedit is non-empty.
410  // If key event may produce anything, it still may trigger the clear
411  // preedit. In that case, preedit order does matter.
412  return !inputPanel().clientPreedit().toString().empty();
413 }
414 
415 void InputContext::commitString(const std::string &text) {
416  FCITX_D();
417  if (auto *instance = d->manager_.instance()) {
418  auto newString = instance->commitFilter(this, text);
419  d->pushEvent<CommitStringEvent>(std::move(newString), this);
420  } else {
421  d->pushEvent<CommitStringEvent>(text, this);
422  }
423 }
424 
425 void InputContext::commitStringWithCursor(const std::string &text,
426  size_t cursor) {
427  FCITX_D();
428  if (cursor > utf8::length(text)) {
429  throw std::invalid_argument(text);
430  }
431 
432  if (auto *instance = d->manager_.instance()) {
433  auto newString = instance->commitFilter(this, text);
434  d->pushEvent<CommitStringWithCursorEvent>(std::move(newString), cursor,
435  this);
436  } else {
437  d->pushEvent<CommitStringWithCursorEvent>(text, cursor, this);
438  }
439 }
440 
441 void InputContext::deleteSurroundingText(int offset, unsigned int size) {
442  deleteSurroundingTextImpl(offset, size);
443 }
444 
445 void InputContext::forwardKey(const Key &rawKey, bool isRelease, int time) {
446  FCITX_D();
447  d->pushEvent<ForwardKeyEvent>(this, rawKey, isRelease, time);
448 }
449 
451  FCITX_D();
452  if (!capabilityFlags().test(CapabilityFlag::Preedit)) {
453  return;
454  }
455 
456  const bool preeditIsEmpty = inputPanel().clientPreedit().empty();
457  if (preeditIsEmpty && d->lastPreeditUpdateIsEmpty_) {
458  return;
459  }
460  d->lastPreeditUpdateIsEmpty_ = preeditIsEmpty;
461  d->pushEvent<UpdatePreeditEvent>(this);
462 }
463 
465  bool immediate) {
466  FCITX_D();
467  d->emplaceEvent<InputContextUpdateUIEvent>(component, this, immediate);
468 }
469 
471  FCITX_D();
472  return d->inputPanel_;
473 }
474 
476  FCITX_D();
477  return d->inputPanel_;
478 }
479 
481  FCITX_D();
482  return d->statusArea_;
483 }
484 
486  FCITX_D();
487  return d->statusArea_;
488 }
489 
491 
492 InputContextV2::~InputContextV2() = default;
493 
494 InputContextEventBlocker::InputContextEventBlocker(InputContext *inputContext)
495  : inputContext_(inputContext->watch()) {
496  inputContext->setBlockEventToClient(true);
497 }
498 
499 InputContextEventBlocker::~InputContextEventBlocker() {
500  if (auto *ic = inputContext_.get()) {
501  ic->setBlockEventToClient(false);
502  }
503 }
504 
505 } // namespace fcitx
Describe a Key in fcitx.
Definition: key.h:41
void focusOut()
Called when input context losts the input focus.
CapabilityFlags capabilityFlags() const
Returns the current capability flags.
void deleteSurroundingText(int offset, unsigned int size)
Ask client to delete a range of surrounding text.
double scaleFactor() const
Return the client scale factor.
virtual void updateClientSideUIImpl()
Send the UI update to client.
void created()
Notifies the creation of input context.
const ICUUID & uuid() const
Returns the uuid of this input context.
size_t length(Iter start, Iter end)
Return the number UTF-8 characters in the string iterator range.
Definition: utf8.h:33
Definition: action.cpp:17
Definition: matchrule.h:78
C++ Utility functions for handling utf8 strings.
void setEnablePreedit(bool enable)
Override the preedit hint from client.
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
InputContextProperty * property(const std::string &name)
Returns the input context property by name.
bool hasFocus() const
Returns whether the input context holds the input focus.
void reset()
Called when input context state need to be reset.
Manager class for user interface.
void updateProperty(const std::string &name)
Notifies the change of a given input context property.
Input Panel is usually a floating window that is display at the cursor of input.
Definition: inputpanel.h:48
const std::string & program() const
Returns the program name of input context.
FocusGroup * focusGroup() const
Returns the current focus group of input context.
void setCapabilityFlags(CapabilityFlags flags)
Update the capability flags of the input context.
void updatePreedit()
Notifies client about changes in clientPreedit.
void destroy()
Notifies the destruction of the input context.
Class represents the current state of surrounding text of an input context.
void setCursorRect(Rect rect)
Update the current cursor rect of the input context.
void focusIn()
Called When input context gains the input focus.
UserInterfaceComponent
Definition: userinterface.h:21
std::string display() const
Returns the display server of the client.
Status area represent a list of actions and action may have sub actions.
Definition: statusarea.h:44
Factory class for input context property.
bool keyEvent(KeyEvent &event)
Send a key event to current input context.
void setFocusGroup(FocusGroup *group)
Set the focus group of this input context.
Input Context for Fcitx.
InputPanel & inputPanel()
Returns the associated input panel.
void commitStringWithCursor(const std::string &text, size_t cursor)
Commit a string to application with a cursor location after commit.
std::string_view frontendName() const
Return the frontend name.
bool hasPendingEventsStrictOrder() const
Has pending event that need to use key order fix.
Class provides bit flag support for Enum.
Definition: flags.h:33
Event for commit string with cursor.
Definition: event.h:433
StatusArea & statusArea()
Returns the associated StatusArea.
bool virtualKeyboardEvent(VirtualKeyboardEvent &event)
Send a virtual keyboard event to current input context.
SurroundingText & surroundingText()
Returns the mutable surrounding text of the input context.
An input context represents a client of Fcitx.
Definition: inputcontext.h:47
void updateUserInterface(UserInterfaceComponent component, bool immediate=false)
Notifies UI about changes in user interface.
void invokeAction(InvokeActionEvent &event)
Invoke an action on the preedit.
void updateSurroundingText()
Notifies the surrounding text modification from the client.
This is a class that designed to store state that is specific to certain input context.
void commitString(const std::string &text)
Commit a string to the client.
void forwardKey(const Key &rawKey, bool isRelease=false, int time=0)
Send a key event to client.
const Rect & cursorRect() const
Returns the cursor position of the client.
void setBlockEventToClient(bool block)
Prevent event deliver to input context, and re-send the event later.
bool isPreeditEnabled() const
Check if preedit is manually disabled.