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