1 /*
2 * SPDX-FileCopyrightText: 2020-2021 Vifly <viflythink@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 */
7
8 #include "fcitx4frontend.h"
9 #include <fstream>
10 #include "fcitx-utils/dbus/message.h"
11 #include "fcitx-utils/dbus/objectvtable.h"
12 #include "fcitx-utils/dbus/servicewatcher.h"
13 #include "fcitx-utils/log.h"
14 #include "fcitx-utils/metastring.h"
15 #include "fcitx-utils/standardpath.h"
16 #include "fcitx/inputcontext.h"
17 #include "fcitx/inputmethodentry.h"
18 #include "fcitx/inputmethodmanager.h"
19 #include "fcitx/instance.h"
20 #include "fcitx/misc_p.h"
21 #include "dbus_public.h"
22
23 #define FCITX_INPUTMETHOD_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod"
24 #define FCITX_INPUTCONTEXT_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext"
25 #define FCITX_DBUS_SERVICE "org.fcitx.Fcitx"
26
27 namespace fcitx {
28
29 namespace {
30
31 std::vector<dbus::DBusStruct<std::string, int>>
buildFormattedTextVector(const Text & text)32 buildFormattedTextVector(const Text &text) {
33 std::vector<dbus::DBusStruct<std::string, int>> vector;
34 for (size_t i = 0, e = text.size(); i < e; i++) {
35 // In fcitx 4, underline bit means "no underline", so we need to reverse
36 // it.
37 const auto flag = text.formatAt(i) ^ TextFormatFlag::Underline;
38 vector.emplace_back(
39 std::make_tuple(text.stringAt(i), static_cast<int>(flag)));
40 }
41 return vector;
42 }
43
getDisplayNumber(const std::string & var)44 int getDisplayNumber(const std::string &var) {
45 auto pos = var.find(':');
46 if (pos == std::string::npos) {
47 return 0;
48 }
49 // skip :
50 pos += 1;
51 // Handle address like :0.0
52 auto period = var.find(pos, '.');
53 if (period != std::string::npos) {
54 period -= pos;
55 }
56
57 try {
58 std::string num(var.substr(pos, period));
59 int displayNumber = std::stoi(num);
60 return displayNumber;
61 } catch (...) {
62 }
63 return 0;
64 }
65 } // namespace
66
67 class Fcitx4InputMethod : public dbus::ObjectVTable<Fcitx4InputMethod> {
68 public:
Fcitx4InputMethod(int display,Fcitx4FrontendModule * module,dbus::Bus * bus)69 Fcitx4InputMethod(int display, Fcitx4FrontendModule *module, dbus::Bus *bus)
70 : display_(display), module_(module), instance_(module->instance()),
71 bus_(std::make_unique<dbus::Bus>(bus->address())),
72 watcher_(std::make_unique<dbus::ServiceWatcher>(*bus_)) {
73 bus_->attachEventLoop(&instance_->eventLoop());
74 bus_->addObjectVTable("/inputmethod", FCITX_INPUTMETHOD_DBUS_INTERFACE,
75 *this);
76 Flags<dbus::RequestNameFlag> requestFlag =
77 dbus::RequestNameFlag::ReplaceExisting;
78 auto dbusServiceName =
79 stringutils::concat(FCITX_DBUS_SERVICE, "-", display);
80 bus_->requestName(dbusServiceName, requestFlag);
81
82 auto localMachineId = getLocalMachineId(/*fallback=*/"machine-id");
83 auto path = stringutils::joinPath(
84 "fcitx", "dbus", stringutils::concat(localMachineId, "-", display));
85 bool res = StandardPath::global().safeSave(
86 StandardPath::Type::Config, path, [this](int fd) {
87 auto address = bus_->address();
88 fs::safeWrite(fd, address.c_str(), address.size() + 1);
89 // Because fcitx5 don't launch dbus by itself, we write 0
90 // on purpose to make address resolve fail, except WPS.
91 pid_t pid = 0;
92 fs::safeWrite(fd, &pid, sizeof(pid_t));
93 fs::safeWrite(fd, &pid, sizeof(pid_t));
94 return true;
95 });
96 if (res) {
97 // Failed to write address file does not matter if we could use
98 // regular dbus.
99 pathWrote_ =
100 stringutils::joinPath(StandardPath::global().userDirectory(
101 StandardPath::Type::Config),
102 path);
103 }
104 }
105
~Fcitx4InputMethod()106 ~Fcitx4InputMethod() {
107 if (!pathWrote_.empty()) {
108 unlink(pathWrote_.data());
109 }
110 }
111
112 std::tuple<int, bool, uint32_t, uint32_t, uint32_t, uint32_t>
113 createICv3(const std::string &appname, int pid);
114
serviceWatcher()115 dbus::ServiceWatcher &serviceWatcher() { return *watcher_; }
bus()116 dbus::Bus *bus() { return bus_.get(); }
instance()117 Instance *instance() { return module_->instance(); }
118
119 private:
120 // V1 and V2 are too old, so we just ignore them.
121 FCITX_OBJECT_VTABLE_METHOD(createICv3, "CreateICv3", "si", "ibuuuu");
122
123 int display_;
124 Fcitx4FrontendModule *module_;
125 Instance *instance_;
126 std::unique_ptr<dbus::Bus> bus_;
127 std::unique_ptr<dbus::ServiceWatcher> watcher_;
128 std::string pathWrote_;
129 };
130
131 class Fcitx4InputContext : public InputContext,
132 public dbus::ObjectVTable<Fcitx4InputContext> {
133 public:
Fcitx4InputContext(int id,InputContextManager & icManager,Fcitx4InputMethod * im,const std::string & sender,const std::string & program)134 Fcitx4InputContext(int id, InputContextManager &icManager,
135 Fcitx4InputMethod *im, const std::string &sender,
136 const std::string &program)
137 : InputContext(icManager, program),
138 path_(stringutils::concat("/inputcontext_", id)), im_(im),
139 handler_(im_->serviceWatcher().watchService(
140 sender,
141 [this](const std::string &, const std::string &,
142 const std::string &newName) {
143 if (newName.empty()) {
144 delete this;
145 }
146 })),
147 name_(sender) {
148 created();
149 }
150
~Fcitx4InputContext()151 ~Fcitx4InputContext() { InputContext::destroy(); }
152
frontend() const153 const char *frontend() const override { return "fcitx4"; }
154
path() const155 const dbus::ObjectPath &path() const { return path_; }
156
updateIM(const InputMethodEntry * entry)157 void updateIM(const InputMethodEntry *entry) {
158 currentIMTo(name_, entry->name(), entry->uniqueName(),
159 entry->languageCode());
160 }
161
commitStringImpl(const std::string & text)162 void commitStringImpl(const std::string &text) override {
163 commitStringDBusTo(name_, text);
164 }
165
updatePreeditImpl()166 void updatePreeditImpl() override {
167 auto preedit =
168 im_->instance()->outputFilter(this, inputPanel().clientPreedit());
169 std::vector<dbus::DBusStruct<std::string, int>> strs =
170 buildFormattedTextVector(preedit);
171 updateFormattedPreeditTo(name_, strs, preedit.cursor());
172 }
173
deleteSurroundingTextImpl(int offset,unsigned int size)174 void deleteSurroundingTextImpl(int offset, unsigned int size) override {
175 deleteSurroundingTextDBusTo(name_, offset, size);
176 }
177
forwardKeyImpl(const ForwardKeyEvent & key)178 void forwardKeyImpl(const ForwardKeyEvent &key) override {
179 forwardKeyDBusTo(name_, static_cast<uint32_t>(key.rawKey().sym()),
180 static_cast<uint32_t>(key.rawKey().states()),
181 key.isRelease() ? 1 : 0);
182 bus()->flush();
183 }
184 #define CHECK_SENDER_OR_RETURN \
185 if (currentMessage()->sender() != name_) \
186 return
187
enableInputContext()188 void enableInputContext() {}
189
closeInputContext()190 void closeInputContext() {}
191
mouseEvent(int)192 void mouseEvent(int) {}
193
setCursorLocation(int x,int y)194 void setCursorLocation(int x, int y) {
195 CHECK_SENDER_OR_RETURN;
196 setCursorRect(Rect{x, y, 0, 0});
197 }
198
focusInDBus()199 void focusInDBus() {
200 CHECK_SENDER_OR_RETURN;
201 focusIn();
202 }
203
focusOutDBus()204 void focusOutDBus() {
205 CHECK_SENDER_OR_RETURN;
206 focusOut();
207 }
208
resetDBus()209 void resetDBus() {
210 CHECK_SENDER_OR_RETURN;
211 reset();
212 }
213
setCursorRectDBus(int x,int y,int w,int h)214 void setCursorRectDBus(int x, int y, int w, int h) {
215 CHECK_SENDER_OR_RETURN;
216 setCursorRect(Rect{x, y, x + w, y + h});
217 }
218
setCapability(uint32_t cap)219 void setCapability(uint32_t cap) {
220 CHECK_SENDER_OR_RETURN;
221 setCapabilityFlags(CapabilityFlags{cap});
222 }
223
setSurroundingText(const std::string & str,uint32_t cursor,uint32_t anchor)224 void setSurroundingText(const std::string &str, uint32_t cursor,
225 uint32_t anchor) {
226 CHECK_SENDER_OR_RETURN;
227 surroundingText().setText(str, cursor, anchor);
228 updateSurroundingText();
229 }
230
setSurroundingTextPosition(uint32_t cursor,uint32_t anchor)231 void setSurroundingTextPosition(uint32_t cursor, uint32_t anchor) {
232 CHECK_SENDER_OR_RETURN;
233 surroundingText().setCursor(cursor, anchor);
234 updateSurroundingText();
235 }
236
destroyDBus()237 void destroyDBus() {
238 CHECK_SENDER_OR_RETURN;
239 delete this;
240 }
241
processKeyEvent(uint32_t keyval,uint32_t keycode,uint32_t state,int isRelease,uint32_t time)242 int processKeyEvent(uint32_t keyval, uint32_t keycode, uint32_t state,
243 int isRelease, uint32_t time) {
244 CHECK_SENDER_OR_RETURN false;
245 KeyEvent event(
246 this, Key(static_cast<KeySym>(keyval), KeyStates(state), keycode),
247 isRelease, time);
248 // Force focus if there's keyevent.
249 if (!hasFocus()) {
250 focusIn();
251 }
252
253 return keyEvent(event) ? 1 : 0;
254 }
255
256 private:
257 // Because there is no application to use, don't impl CommitPreedit and
258 // UpdateClientSideUI.
259 FCITX_OBJECT_VTABLE_METHOD(enableInputContext, "EnableIC", "", "");
260 FCITX_OBJECT_VTABLE_METHOD(closeInputContext, "CloseIC", "", "");
261 FCITX_OBJECT_VTABLE_METHOD(focusInDBus, "FocusIn", "", "");
262 FCITX_OBJECT_VTABLE_METHOD(focusOutDBus, "FocusOut", "", "");
263 FCITX_OBJECT_VTABLE_METHOD(resetDBus, "Reset", "", "");
264 FCITX_OBJECT_VTABLE_METHOD(mouseEvent, "MouseEvent", "i", "");
265 FCITX_OBJECT_VTABLE_METHOD(setCursorLocation, "SetCursorLocation", "ii",
266 "");
267 FCITX_OBJECT_VTABLE_METHOD(setCursorRectDBus, "SetCursorRect", "iiii", "");
268 FCITX_OBJECT_VTABLE_METHOD(setCapability, "SetCapacity", "u", "");
269 FCITX_OBJECT_VTABLE_METHOD(setSurroundingText, "SetSurroundingText", "suu",
270 "");
271 FCITX_OBJECT_VTABLE_METHOD(setSurroundingTextPosition,
272 "SetSurroundingTextPosition", "uu", "");
273 FCITX_OBJECT_VTABLE_METHOD(destroyDBus, "DestroyIC", "", "");
274 FCITX_OBJECT_VTABLE_METHOD(processKeyEvent, "ProcessKeyEvent", "uuuiu",
275 "i");
276
277 FCITX_OBJECT_VTABLE_SIGNAL(commitStringDBus, "CommitString", "s");
278 FCITX_OBJECT_VTABLE_SIGNAL(currentIM, "CurrentIM", "sss");
279 FCITX_OBJECT_VTABLE_SIGNAL(updateFormattedPreedit, "UpdateFormattedPreedit",
280 "a(si)i");
281 FCITX_OBJECT_VTABLE_SIGNAL(deleteSurroundingTextDBus,
282 "DeleteSurroundingText", "iu");
283 FCITX_OBJECT_VTABLE_SIGNAL(forwardKeyDBus, "ForwardKey", "uui");
284
285 dbus::ObjectPath path_;
286 Fcitx4InputMethod *im_;
287 std::unique_ptr<HandlerTableEntry<dbus::ServiceWatcherCallback>> handler_;
288 std::string name_;
289 };
290
291 std::tuple<int, bool, uint32_t, uint32_t, uint32_t, uint32_t>
createICv3(const std::string & appname,int)292 Fcitx4InputMethod::createICv3(const std::string &appname, int /*pid*/) {
293 auto sender = currentMessage()->sender();
294 int icid = module_->nextIcIdx();
295 auto *ic = new Fcitx4InputContext(icid, instance_->inputContextManager(),
296 this, sender, appname);
297 auto group =
298 instance_->defaultFocusGroup(stringutils::concat("x11::", display_));
299 if (!group) {
300 group = instance_->defaultFocusGroup("x11:");
301 }
302 ic->setFocusGroup(group);
303 bus_->addObjectVTable(ic->path().path(), FCITX_INPUTCONTEXT_DBUS_INTERFACE,
304 *ic);
305
306 return std::make_tuple(icid, true, 0, 0, 0, 0);
307 }
308
Fcitx4FrontendModule(Instance * instance)309 Fcitx4FrontendModule::Fcitx4FrontendModule(Instance *instance)
310 : instance_(instance),
311 table_(
312 [this](int display) {
313 try {
314 fcitx4InputMethod_.emplace(
315 display, std::make_unique<Fcitx4InputMethod>(
316 display, this, bus()));
317 return true;
318 } catch (...) {
319 }
320 return false;
321 },
__anone2fb45700502(int display) 322 [this](int display) { fcitx4InputMethod_.erase(display); }) {
323 #ifdef ENABLE_X11
324 if (xcb()) {
325 createdCallback_ =
326 xcb()->call<IXCBModule::addConnectionCreatedCallback>(
327 [this](const std::string &name, xcb_connection_t *, int,
__anone2fb45700602(const std::string &name, xcb_connection_t *, int, FocusGroup *) 328 FocusGroup *) { addDisplay(name); });
329 closedCallback_ = xcb()->call<IXCBModule::addConnectionClosedCallback>(
__anone2fb45700702(const std::string &name, xcb_connection_t *) 330 [this](const std::string &name, xcb_connection_t *) {
331 removeDisplay(name);
332 });
333 }
334 #endif
335 // Always create display number zero.
336 addDisplay("");
337
338 event_ = instance_->watchEvent(
339 EventType::InputContextInputMethodActivated, EventWatcherPhase::Default,
__anone2fb45700802(Event &event) 340 [this](Event &event) {
341 auto &activated = static_cast<InputMethodActivatedEvent &>(event);
342 auto *ic = activated.inputContext();
343 if (strcmp(ic->frontend(), "fcitx4") == 0) {
344 if (const auto *entry = instance_->inputMethodManager().entry(
345 activated.name())) {
346 static_cast<Fcitx4InputContext *>(ic)->updateIM(entry);
347 }
348 }
349 });
350 }
351
bus()352 dbus::Bus *Fcitx4FrontendModule::bus() {
353 return dbus()->call<IDBusModule::bus>();
354 }
355
addDisplay(const std::string & name)356 void Fcitx4FrontendModule::addDisplay(const std::string &name) {
357 displayToHandle_.emplace(name, table_.add(getDisplayNumber(name), name));
358 }
359
removeDisplay(const std::string & name)360 void Fcitx4FrontendModule::removeDisplay(const std::string &name) {
361 displayToHandle_.erase(name);
362 }
363
364 class Fcitx4FrontendModuleFactory : public AddonFactory {
365 public:
create(AddonManager * manager)366 AddonInstance *create(AddonManager *manager) override {
367 return new Fcitx4FrontendModule(manager->instance());
368 }
369 };
370 } // namespace fcitx
371
372 FCITX_ADDON_FACTORY(fcitx::Fcitx4FrontendModuleFactory);
373