1 /*
2  * Copyright (C) 2011~2017 by CSSlayer
3  * wengxt@gmail.com
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above Copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above Copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of the authors nor the names of its contributors
17  *    may be used to endorse or promote products derived from this
18  *    software without specific prior written permission.
19  */
20 
21 #include "fcitxinputcontextproxy.h"
22 #include "fcitxwatcher.h"
23 #include <QCoreApplication>
24 #include <QDBusConnectionInterface>
25 #include <QDBusInterface>
26 #include <QDBusMetaType>
27 #include <QFileInfo>
28 #include <QTimer>
29 #include <unistd.h>
30 
FcitxInputContextProxy(FcitxWatcher * watcher,QObject * parent)31 FcitxInputContextProxy::FcitxInputContextProxy(FcitxWatcher *watcher,
32                                                QObject *parent)
33     : QObject(parent), m_fcitxWatcher(watcher), m_portal(false) {
34     FcitxFormattedPreedit::registerMetaType();
35     FcitxInputContextArgument::registerMetaType();
36     connect(m_fcitxWatcher, SIGNAL(availabilityChanged(bool)), this,
37             SLOT(availabilityChanged()));
38     m_watcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
39     connect(&m_watcher, SIGNAL(serviceUnregistered(QString)), this,
40             SLOT(serviceUnregistered()));
41     availabilityChanged();
42 }
43 
~FcitxInputContextProxy()44 FcitxInputContextProxy::~FcitxInputContextProxy() {
45     if (isValid()) {
46         if (m_portal) {
47             m_ic1proxy->DestroyIC();
48         } else {
49             m_icproxy->DestroyIC();
50         }
51     }
52 }
53 
setDisplay(const QString & display)54 void FcitxInputContextProxy::setDisplay(const QString &display) {
55     m_display = display;
56 }
57 
serviceUnregistered()58 void FcitxInputContextProxy::serviceUnregistered() {
59     cleanUp();
60     availabilityChanged();
61 }
62 
availabilityChanged()63 void FcitxInputContextProxy::availabilityChanged() {
64     QTimer::singleShot(100, this, SLOT(recheck()));
65 }
66 
recheck()67 void FcitxInputContextProxy::recheck() {
68     if (!isValid() && m_fcitxWatcher->availability()) {
69         createInputContext();
70     }
71     if (!m_fcitxWatcher->availability()) {
72         cleanUp();
73     }
74 }
75 
cleanUp()76 void FcitxInputContextProxy::cleanUp() {
77     auto services = m_watcher.watchedServices();
78     for (const auto &service : services) {
79         m_watcher.removeWatchedService(service);
80     }
81 
82     delete m_improxy;
83     m_improxy = nullptr;
84     delete m_im1proxy;
85     m_im1proxy = nullptr;
86     delete m_icproxy;
87     m_icproxy = nullptr;
88     delete m_ic1proxy;
89     m_ic1proxy = nullptr;
90     delete m_createInputContextWatcher;
91     m_createInputContextWatcher = nullptr;
92 }
93 
createInputContext()94 void FcitxInputContextProxy::createInputContext() {
95     if (!m_fcitxWatcher->availability()) {
96         return;
97     }
98 
99     cleanUp();
100 
101     auto service = m_fcitxWatcher->service();
102     auto connection = m_fcitxWatcher->connection();
103 
104     auto owner = connection.interface()->serviceOwner(service);
105     if (!owner.isValid()) {
106         return;
107     }
108 
109     m_watcher.setConnection(connection);
110     m_watcher.setWatchedServices(QStringList() << owner);
111     // Avoid race, query again.
112     if (!connection.interface()->isServiceRegistered(owner)) {
113         cleanUp();
114         return;
115     }
116 
117     QFileInfo info(QCoreApplication::applicationFilePath());
118     if (service == "org.freedesktop.portal.Fcitx") {
119         m_portal = true;
120         m_im1proxy = new org::fcitx::Fcitx::InputMethod1(
121             owner, "/org/freedesktop/portal/inputmethod", connection, this);
122         FcitxInputContextArgumentList list;
123         FcitxInputContextArgument arg;
124         arg.setName("program");
125         arg.setValue(info.fileName());
126         list << arg;
127         if (!m_display.isEmpty()) {
128             FcitxInputContextArgument arg2;
129             arg2.setName("display");
130             arg2.setValue(m_display);
131             list << arg2;
132         }
133 
134         auto result = m_im1proxy->CreateInputContext(list);
135         m_createInputContextWatcher = new QDBusPendingCallWatcher(result);
136         connect(m_createInputContextWatcher,
137                 SIGNAL(finished(QDBusPendingCallWatcher *)), this,
138                 SLOT(createInputContextFinished()));
139     } else {
140         m_portal = false;
141         m_improxy = new org::fcitx::Fcitx::InputMethod(owner, "/inputmethod",
142                                                        connection, this);
143         auto result = m_improxy->CreateICv3(info.fileName(), getpid());
144         m_createInputContextWatcher = new QDBusPendingCallWatcher(result);
145         connect(m_createInputContextWatcher,
146                 SIGNAL(finished(QDBusPendingCallWatcher *)), this,
147                 SLOT(createInputContextFinished()));
148     }
149 }
150 
createInputContextFinished()151 void FcitxInputContextProxy::createInputContextFinished() {
152     if (m_createInputContextWatcher->isError()) {
153         cleanUp();
154         return;
155     }
156 
157     if (m_portal) {
158         QDBusPendingReply<QDBusObjectPath, QByteArray> reply(
159             *m_createInputContextWatcher);
160         m_ic1proxy = new org::fcitx::Fcitx::InputContext1(
161             m_im1proxy->service(), reply.value().path(),
162             m_im1proxy->connection(), this);
163         connect(m_ic1proxy, SIGNAL(CommitString(QString)), this,
164                 SIGNAL(commitString(QString)));
165         connect(m_ic1proxy, SIGNAL(CurrentIM(QString, QString, QString)), this,
166                 SIGNAL(currentIM(QString, QString, QString)));
167         connect(m_ic1proxy, SIGNAL(DeleteSurroundingText(int, uint)), this,
168                 SIGNAL(deleteSurroundingText(int, uint)));
169         connect(m_ic1proxy, SIGNAL(ForwardKey(uint, uint, bool)), this,
170                 SIGNAL(forwardKey(uint, uint, bool)));
171         connect(m_ic1proxy,
172                 SIGNAL(UpdateFormattedPreedit(FcitxFormattedPreeditList, int)),
173                 this,
174                 SIGNAL(updateFormattedPreedit(FcitxFormattedPreeditList, int)));
175     } else {
176         QDBusPendingReply<int, bool, uint, uint, uint, uint> reply(
177             *m_createInputContextWatcher);
178         QString path = QString("/inputcontext_%1").arg(reply.value());
179         m_icproxy = new org::fcitx::Fcitx::InputContext(
180             m_improxy->service(), path, m_improxy->connection(), this);
181         connect(m_icproxy, SIGNAL(CommitString(QString)), this,
182                 SIGNAL(commitString(QString)));
183         connect(m_icproxy, SIGNAL(CurrentIM(QString, QString, QString)), this,
184                 SIGNAL(currentIM(QString, QString, QString)));
185         connect(m_icproxy, SIGNAL(DeleteSurroundingText(int, uint)), this,
186                 SIGNAL(deleteSurroundingText(int, uint)));
187         connect(m_icproxy, SIGNAL(ForwardKey(uint, uint, int)), this,
188                 SLOT(forwardKeyWrapper(uint, uint, int)));
189         connect(m_icproxy,
190                 SIGNAL(UpdateFormattedPreedit(FcitxFormattedPreeditList, int)),
191                 this,
192                 SLOT(updateFormattedPreeditWrapper(FcitxFormattedPreeditList,
193                                                    int)));
194     }
195 
196     delete m_createInputContextWatcher;
197     m_createInputContextWatcher = nullptr;
198     Q_EMIT inputContextCreated();
199 }
200 
isValid() const201 bool FcitxInputContextProxy::isValid() const {
202     return (m_icproxy && m_icproxy->isValid()) ||
203            (m_ic1proxy && m_ic1proxy->isValid());
204 }
205 
forwardKeyWrapper(uint keyval,uint state,int type)206 void FcitxInputContextProxy::forwardKeyWrapper(uint keyval, uint state,
207                                                int type) {
208     Q_EMIT forwardKey(keyval, state, type == 1);
209 }
210 
updateFormattedPreeditWrapper(const FcitxFormattedPreeditList & list,int cursorpos)211 void FcitxInputContextProxy::updateFormattedPreeditWrapper(
212     const FcitxFormattedPreeditList &list, int cursorpos) {
213     auto newList = list;
214     for (auto item : newList) {
215         const qint32 underlineBit = (1 << 3);
216         // revert non underline and "underline"
217         item.setFormat(item.format() ^ underlineBit);
218     }
219 
220     Q_EMIT updateFormattedPreedit(list, cursorpos);
221 }
222 
focusIn()223 QDBusPendingReply<> FcitxInputContextProxy::focusIn() {
224     if (m_portal) {
225         return m_ic1proxy->FocusIn();
226     } else {
227         return m_icproxy->FocusIn();
228     }
229 }
230 
focusOut()231 QDBusPendingReply<> FcitxInputContextProxy::focusOut() {
232     if (m_portal) {
233         return m_ic1proxy->FocusOut();
234     } else {
235         return m_icproxy->FocusOut();
236     }
237 }
238 
processKeyEvent(uint keyval,uint keycode,uint state,bool type,uint time)239 QDBusPendingCall FcitxInputContextProxy::processKeyEvent(uint keyval,
240                                                          uint keycode,
241                                                          uint state, bool type,
242                                                          uint time) {
243     if (m_portal) {
244         return m_ic1proxy->ProcessKeyEvent(keyval, keycode, state, type, time);
245     } else {
246         return m_icproxy->ProcessKeyEvent(keyval, keycode, state, type ? 1 : 0,
247                                           time);
248     }
249 }
250 
reset()251 QDBusPendingReply<> FcitxInputContextProxy::reset() {
252     if (m_portal) {
253         return m_ic1proxy->Reset();
254     } else {
255         return m_icproxy->Reset();
256     }
257 }
258 
setCapability(qulonglong caps)259 QDBusPendingReply<> FcitxInputContextProxy::setCapability(qulonglong caps) {
260     if (m_portal) {
261         return m_ic1proxy->SetCapability(caps);
262     } else {
263         return m_icproxy->SetCapacity(static_cast<uint>(caps));
264     }
265 }
266 
setCursorRect(int x,int y,int w,int h)267 QDBusPendingReply<> FcitxInputContextProxy::setCursorRect(int x, int y, int w,
268                                                           int h) {
269     if (m_portal) {
270         return m_ic1proxy->SetCursorRect(x, y, w, h);
271     } else {
272         return m_icproxy->SetCursorRect(x, y, w, h);
273     }
274 }
275 
276 QDBusPendingReply<>
setSurroundingText(const QString & text,uint cursor,uint anchor)277 FcitxInputContextProxy::setSurroundingText(const QString &text, uint cursor,
278                                            uint anchor) {
279     if (m_portal) {
280         return m_ic1proxy->SetSurroundingText(text, cursor, anchor);
281     } else {
282         return m_icproxy->SetSurroundingText(text, cursor, anchor);
283     }
284 }
285 
286 QDBusPendingReply<>
setSurroundingTextPosition(uint cursor,uint anchor)287 FcitxInputContextProxy::setSurroundingTextPosition(uint cursor, uint anchor) {
288     if (m_portal) {
289         return m_ic1proxy->SetSurroundingTextPosition(cursor, anchor);
290     } else {
291         return m_icproxy->SetSurroundingTextPosition(cursor, anchor);
292     }
293 }
294 
processKeyEventResult(const QDBusPendingCall & call)295 bool FcitxInputContextProxy::processKeyEventResult(
296     const QDBusPendingCall &call) {
297     if (call.isError()) {
298         return false;
299     }
300     if (m_portal) {
301         QDBusPendingReply<bool> reply = call;
302         return reply.value();
303     } else {
304         QDBusPendingReply<int> reply = call;
305         return reply.value() > 0;
306     }
307 }
308