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