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/utf8.h"
15 #include "focusgroup.h"
16 #include "inputcontext_p.h"
17 #include "inputcontextmanager.h"
18 #include "instance.h"
19 #include "misc_p.h"
20 #include "userinterfacemanager.h"
21 
22 namespace fcitx {
23 
24 namespace {
25 
26 bool shouldDisablePreeditByDefault(const std::string &program) {
27  static const std::vector<std::regex> matchers = []() {
28  std::vector<std::regex> matchers;
29  const char *apps = getenv("FCITX_NO_PREEDIT_APPS");
30  if (!apps) {
31  apps = NO_PREEDIT_APPS;
32  }
33  auto matcherStrings = stringutils::split(apps, ",");
34  for (const auto &matcherString : matcherStrings) {
35  try {
36  matchers.emplace_back(matcherString, std::regex::icase |
37  std::regex::extended |
38  std::regex::nosubs);
39  } catch (...) {
40  }
41  }
42  return matchers;
43  }();
44 
45  return std::any_of(matchers.begin(), matchers.end(),
46  [&program](const std::regex &regex) {
47  return std::regex_match(program, regex);
48  });
49 }
50 } // namespace
51 
52 InputContextPrivate::InputContextPrivate(InputContext *q,
53  InputContextManager &manager,
54  const std::string &program)
55  : QPtrHolder(q), manager_(manager), group_(nullptr), inputPanel_(q),
56  statusArea_(q), program_(program),
57  isPreeditEnabled_(manager.isPreeditEnabledByDefault() &&
58  !shouldDisablePreeditByDefault(program)) {}
59 
60 #define RETURN_IF_HAS_NO_FOCUS(...) \
61  do { \
62  if (!hasFocus()) { \
63  return __VA_ARGS__; \
64  } \
65  } while (0);
66 
67 InputContext::InputContext(InputContextManager &manager,
68  const std::string &program)
69  : d_ptr(std::make_unique<InputContextPrivate>(this, manager, program)) {
70  manager.registerInputContext(*this);
71 }
72 
73 InputContext::~InputContext() { assert(d_ptr->destroyed_); }
74 
76  FCITX_D();
77  d->emplaceEvent<InputContextCreatedEvent>(this);
78 }
79 
80 std::string_view InputContext::frontendName() const { return frontend(); }
81 
83  FCITX_D();
84  assert(!d->destroyed_);
85  if (d->group_) {
86  d->group_->removeInputContext(this);
87  }
88  d->emplaceEvent<InputContextDestroyedEvent>(this);
89  d->manager_.unregisterInputContext(*this);
90  d->destroyed_ = true;
91 }
92 
93 const ICUUID &InputContext::uuid() const {
94  FCITX_D();
95  return d->uuid_;
96 }
97 
98 const std::string &InputContext::program() const {
99  FCITX_D();
100  return d->program_;
101 }
102 
103 std::string InputContext::display() const {
104  FCITX_D();
105  return d->group_ ? d->group_->display() : "";
106 }
107 
109  FCITX_D();
110  return d->cursorRect_;
111 }
112 
114  FCITX_D();
115  return d->scale_;
116 }
117 
118 InputContextProperty *InputContext::property(const std::string &name) {
119  FCITX_D();
120  auto *factory = d->manager_.factoryForName(name);
121  if (!factory) {
122  return nullptr;
123  }
124  return d->manager_.property(*this, factory);
125 }
126 
129  FCITX_D();
130  return d->manager_.property(*this, factory);
131 }
132 
133 void InputContext::updateProperty(const std::string &name) {
134  FCITX_D();
135  auto *factory = d->manager_.factoryForName(name);
136  if (!factory) {
137  return;
138  }
139  updateProperty(factory);
140 }
141 
143  FCITX_D();
144  auto *property = d->manager_.property(*this, factory);
145  if (!property->needCopy()) {
146  return;
147  }
148  d->manager_.propagateProperty(*this, factory);
149 }
150 
151 bool InputContext::isVirtualKeyboardVisible() const {
152  FCITX_D();
153  if (auto *instance = d->manager_.instance()) {
154  return instance->userInterfaceManager().isVirtualKeyboardVisible();
155  }
156  return false;
157 }
158 
159 void InputContext::showVirtualKeyboard() const {
160  FCITX_D();
161  if (auto *instance = d->manager_.instance()) {
162  return instance->userInterfaceManager().showVirtualKeyboard();
163  }
164 }
165 
166 void InputContext::hideVirtualKeyboard() const {
167  FCITX_D();
168  if (auto *instance = d->manager_.instance()) {
169  return instance->userInterfaceManager().hideVirtualKeyboard();
170  }
171 }
172 
173 bool InputContext::clientControlVirtualkeyboardShow() const {
174  FCITX_D();
175  return d->clientControlVirtualkeyboardShow_;
176 }
177 
178 void InputContext::setClientControlVirtualkeyboardShow(bool show) {
179  FCITX_D();
180  d->clientControlVirtualkeyboardShow_ = show;
181 }
182 
183 bool InputContext::clientControlVirtualkeyboardHide() const {
184  FCITX_D();
185  return d->clientControlVirtualkeyboardHide_;
186 }
187 
188 void InputContext::setClientControlVirtualkeyboardHide(bool hide) {
189  FCITX_D();
190  d->clientControlVirtualkeyboardHide_ = hide;
191 }
192 
193 CapabilityFlags calculateFlags(CapabilityFlags flag, bool isPreeditEnabled) {
194  if (!isPreeditEnabled) {
195  flag = flag.unset(CapabilityFlag::Preedit)
196  .unset(CapabilityFlag::FormattedPreedit);
197  }
198  return flag;
199 }
200 
202  FCITX_D();
203  if (d->capabilityFlags_ == flags) {
204  return;
205  }
206  const auto oldFlags = capabilityFlags();
207  auto newFlags = calculateFlags(flags, d->isPreeditEnabled_);
208  if (oldFlags != newFlags) {
209  d->emplaceEvent<CapabilityAboutToChangeEvent>(this, oldFlags, flags);
210  }
211  d->capabilityFlags_ = flags;
212  if (oldFlags != newFlags) {
213  d->emplaceEvent<CapabilityChangedEvent>(this, oldFlags, flags);
214  }
215 }
216 
218  FCITX_D();
219  return calculateFlags(d->capabilityFlags_, d->isPreeditEnabled_);
220 }
221 
223  FCITX_D();
224  if (enable == d->isPreeditEnabled_) {
225  return;
226  }
227  const auto oldFlags = capabilityFlags();
228  auto newFlags = calculateFlags(d->capabilityFlags_, enable);
229  if (oldFlags != newFlags) {
230  d->emplaceEvent<CapabilityAboutToChangeEvent>(this, oldFlags, newFlags);
231  }
232  d->isPreeditEnabled_ = enable;
233  if (oldFlags != newFlags) {
234  d->emplaceEvent<CapabilityChangedEvent>(this, oldFlags, newFlags);
235  }
236 }
237 
239  FCITX_D();
240  return d->isPreeditEnabled_;
241 }
242 
243 void InputContext::setCursorRect(Rect rect) { setCursorRect(rect, 1.0); }
244 
245 void InputContext::setCursorRect(Rect rect, double scale) {
246  FCITX_D();
247  if (d->cursorRect_ == rect && d->scale_ == scale) {
248  return;
249  }
250  d->cursorRect_ = rect;
251  d->scale_ = scale;
252  d->emplaceEvent<CursorRectChangedEvent>(this);
253 }
254 
256  FCITX_D();
257  focusOut();
258  if (d->group_) {
259  d->group_->removeInputContext(this);
260  }
261  d->group_ = group;
262  if (d->group_) {
263  d->group_->addInputContext(this);
264  }
265 }
266 
268  FCITX_D();
269  return d->group_;
270 }
271 
273  FCITX_D();
274  if (d->group_) {
275  d->group_->setFocusedInputContext(this);
276  } else {
277  setHasFocus(true);
278  }
279 }
280 
282  FCITX_D();
283  if (d->group_) {
284  if (d->group_->focusedInputContext() == this) {
285  d->group_->setFocusedInputContext(nullptr);
286  }
287  } else {
288  setHasFocus(false);
289  }
290 }
291 
293  FCITX_D();
294  return d->hasFocus_;
295 }
296 
297 void InputContext::setHasFocus(bool hasFocus) {
298  FCITX_D();
299  if (hasFocus == d->hasFocus_) {
300  return;
301  }
302  d->hasFocus_ = hasFocus;
303  d->manager_.notifyFocus(*this, d->hasFocus_);
304  // trigger event
305  if (d->hasFocus_) {
306  d->emplaceEvent<FocusInEvent>(this);
307  } else {
308  d->emplaceEvent<FocusOutEvent>(this);
309  }
310 }
311 
313  FCITX_D();
314  RETURN_IF_HAS_NO_FOCUS(false);
315  decltype(std::chrono::steady_clock::now()) start;
316  // Don't query time if we don't want log.
317  if (::keyTrace().checkLogLevel(LogLevel::Debug)) {
318  start = std::chrono::steady_clock::now();
319  }
320  auto result = d->postEvent(event);
321  FCITX_KEYTRACE() << "KeyEvent handling time: "
322  << std::chrono::duration_cast<std::chrono::milliseconds>(
323  std::chrono::steady_clock::now() - start)
324  .count()
325  << "ms result:" << result;
326  return result;
327 }
328 
330  FCITX_D();
331  RETURN_IF_HAS_NO_FOCUS(false);
332  decltype(std::chrono::steady_clock::now()) start;
333  // Don't query time if we don't want log.
334  if (::keyTrace().checkLogLevel(LogLevel::Debug)) {
335  start = std::chrono::steady_clock::now();
336  }
337  auto result = d->postEvent(event);
338  FCITX_KEYTRACE() << "VirtualKeyboardEvent handling time: "
339  << std::chrono::duration_cast<std::chrono::milliseconds>(
340  std::chrono::steady_clock::now() - start)
341  .count()
342  << "ms result:" << result;
343  return result;
344 }
345 
347  FCITX_D();
348  RETURN_IF_HAS_NO_FOCUS();
349  d->postEvent(event);
350 }
351 
352 void InputContext::reset(ResetReason) { reset(); }
353 
355  FCITX_D();
356  RETURN_IF_HAS_NO_FOCUS();
357  d->emplaceEvent<ResetEvent>(this);
358 }
359 
361  FCITX_D();
362  return d->surroundingText_;
363 }
364 
366  FCITX_D();
367  return d->surroundingText_;
368 }
369 
371  FCITX_D();
372  d->emplaceEvent<SurroundingTextUpdatedEvent>(this);
373 }
374 
376  FCITX_D();
377  if (d->blockEventToClient_ == block) {
378  throw std::invalid_argument(
379  "setBlockEventToClient has invalid argument. Probably a bug in the "
380  "implementation.");
381  }
382  d->blockEventToClient_ = block;
383  if (!block) {
384  d->deliverBlockedEvents();
385  }
386 }
387 
388 bool InputContext::hasPendingEvents() const {
389  FCITX_D();
390  return !d->blockedEvents_.empty();
391 }
392 
394  FCITX_D();
395  if (d->blockedEvents_.empty()) {
396  return false;
397  }
398 
399  // Check we only have update preedit.
400  if (std::any_of(d->blockedEvents_.begin(), d->blockedEvents_.end(),
401  [](const auto &event) {
402  return event->type() !=
403  EventType::InputContextUpdatePreedit;
404  })) {
405  return true;
406  }
407 
408  // Check whether the preedit is non-empty.
409  // If key event may produce anything, it still may trigger the clear
410  // preedit. In that case, preedit order does matter.
411  return !inputPanel().clientPreedit().toString().empty();
412 }
413 
414 void InputContext::commitString(const std::string &text) {
415  FCITX_D();
416  if (auto *instance = d->manager_.instance()) {
417  auto newString = instance->commitFilter(this, text);
418  d->pushEvent<CommitStringEvent>(std::move(newString), this);
419  } else {
420  d->pushEvent<CommitStringEvent>(text, this);
421  }
422 }
423 
424 void InputContext::commitStringWithCursor(const std::string &text,
425  size_t cursor) {
426  FCITX_D();
427  if (cursor > utf8::length(text)) {
428  throw std::invalid_argument(text);
429  }
430 
431  if (auto *instance = d->manager_.instance()) {
432  auto newString = instance->commitFilter(this, text);
433  d->pushEvent<CommitStringWithCursorEvent>(std::move(newString), cursor,
434  this);
435  } else {
436  d->pushEvent<CommitStringWithCursorEvent>(text, cursor, this);
437  }
438 }
439 
440 void InputContext::deleteSurroundingText(int offset, unsigned int size) {
441  deleteSurroundingTextImpl(offset, size);
442 }
443 
444 void InputContext::forwardKey(const Key &rawKey, bool isRelease, int time) {
445  FCITX_D();
446  d->pushEvent<ForwardKeyEvent>(this, rawKey, isRelease, time);
447 }
448 
450  FCITX_D();
451  if (!capabilityFlags().test(CapabilityFlag::Preedit)) {
452  return;
453  }
454 
455  const bool preeditIsEmpty = inputPanel().clientPreedit().empty();
456  if (preeditIsEmpty && d->lastPreeditUpdateIsEmpty_) {
457  return;
458  }
459  d->lastPreeditUpdateIsEmpty_ = preeditIsEmpty;
460  d->pushEvent<UpdatePreeditEvent>(this);
461 }
462 
464  bool immediate) {
465  FCITX_D();
466  d->emplaceEvent<InputContextUpdateUIEvent>(component, this, immediate);
467 }
468 
470  FCITX_D();
471  return d->inputPanel_;
472 }
473 
475  FCITX_D();
476  return d->inputPanel_;
477 }
478 
480  FCITX_D();
481  return d->statusArea_;
482 }
483 
485  FCITX_D();
486  return d->statusArea_;
487 }
488 
490 
491 InputContextV2::~InputContextV2() = default;
492 
493 InputContextEventBlocker::InputContextEventBlocker(InputContext *inputContext)
494  : inputContext_(inputContext->watch()) {
495  inputContext->setBlockEventToClient(true);
496 }
497 
498 InputContextEventBlocker::~InputContextEventBlocker() {
499  if (auto *ic = inputContext_.get()) {
500  ic->setBlockEventToClient(false);
501  }
502 }
503 
504 } // namespace fcitx
Describe a Key in fcitx.
Definition: key.h:40
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:26
Definition: action.cpp:12
Definition: matchrule.h:77
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:428
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:45
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.