1 /*
2  * SPDX-FileCopyrightText: 2012~2012 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 #include "fcitxgclient.h"
7 #include "fcitxgwatcher.h"
8 #include "marshall.h"
9 
10 typedef struct _ProcessKeyStruct ProcessKeyStruct;
11 
12 /**
13  * FcitxGClient:
14  *
15  * A #FcitxGClient allow to create a input context via DBus
16  */
17 
18 enum {
19     PROP_0,
20     PROP_WATCHER,
21 };
22 
23 struct _ProcessKeyStruct {
24     FcitxGClient *self;
25     GAsyncReadyCallback callback;
26     void *user_data;
27 };
28 
29 struct _FcitxGClientClass {
30     GObjectClass parent_class;
31     /* signals */
32 
33     /*< private >*/
34     /* padding */
35 };
36 
37 struct _FcitxGClient {
38     GObject parent_instance;
39     /* instance member */
40     FcitxGClientPrivate *priv;
41 };
42 
43 struct _FcitxGClientPrivate {
44     GDBusProxy *improxy;
45     GDBusProxy *icproxy;
46     gchar *icname;
47     guint8 uuid[16];
48     gchar *display;
49     gchar *program;
50 
51     GCancellable *cancellable;
52     FcitxGWatcher *watcher;
53     guint watch_id;
54 };
55 
56 static const gchar introspection_xml[] =
57     "<node>"
58     "  <interface name=\"org.fcitx.Fcitx.InputMethod1\">"
59     "    <method name=\"CreateInputContext\">\n"
60     "      <arg direction=\"in\" type=\"a(ss)\"/>\n"
61     "      <arg direction=\"out\" type=\"o\"/>\n"
62     "      <arg direction=\"out\" type=\"ay\"/>\n"
63     "    </method>\n"
64     "  </interface>"
65     "</node>";
66 
67 static const gchar ic_introspection_xml[] =
68     "<node>\n"
69     "  <interface name=\"org.fcitx.Fcitx.InputContext1\">\n"
70     "    <method name=\"FocusIn\">\n"
71     "    </method>\n"
72     "    <method name=\"FocusOut\">\n"
73     "    </method>\n"
74     "    <method name=\"Reset\">\n"
75     "    </method>\n"
76     "    <method name=\"SetCursorRect\">\n"
77     "      <arg name=\"x\" direction=\"in\" type=\"i\"/>\n"
78     "      <arg name=\"y\" direction=\"in\" type=\"i\"/>\n"
79     "      <arg name=\"w\" direction=\"in\" type=\"i\"/>\n"
80     "      <arg name=\"h\" direction=\"in\" type=\"i\"/>\n"
81     "    </method>\n"
82     "    <method name=\"SetCursorRectV2\">\n"
83     "      <arg name=\"x\" direction=\"in\" type=\"i\"/>\n"
84     "      <arg name=\"y\" direction=\"in\" type=\"i\"/>\n"
85     "      <arg name=\"w\" direction=\"in\" type=\"i\"/>\n"
86     "      <arg name=\"h\" direction=\"in\" type=\"i\"/>\n"
87     "      <arg name=\"scale\" direction=\"in\" type=\"d\"/>\n"
88     "    </method>\n"
89     "    <method name=\"SetCapability\">\n"
90     "      <arg name=\"caps\" direction=\"in\" type=\"t\"/>\n"
91     "    </method>\n"
92     "    <method name=\"SetSurroundingText\">\n"
93     "      <arg name=\"text\" direction=\"in\" type=\"s\"/>\n"
94     "      <arg name=\"cursor\" direction=\"in\" type=\"u\"/>\n"
95     "      <arg name=\"anchor\" direction=\"in\" type=\"u\"/>\n"
96     "    </method>\n"
97     "    <method name=\"SetSurroundingTextPosition\">\n"
98     "      <arg name=\"cursor\" direction=\"in\" type=\"u\"/>\n"
99     "      <arg name=\"anchor\" direction=\"in\" type=\"u\"/>\n"
100     "    </method>\n"
101     "    <method name=\"DestroyIC\">\n"
102     "    </method>\n"
103     "    <method name=\"ProcessKeyEvent\">\n"
104     "      <arg name=\"keyval\" direction=\"in\" type=\"u\"/>\n"
105     "      <arg name=\"keycode\" direction=\"in\" type=\"u\"/>\n"
106     "      <arg name=\"state\" direction=\"in\" type=\"u\"/>\n"
107     "      <arg name=\"isRelease\" direction=\"in\" type=\"b\"/>\n"
108     "      <arg name=\"time\" direction=\"in\" type=\"u\"/>\n"
109     "      <arg name=\"ret\" direction=\"out\" type=\"b\"/>\n"
110     "    </method>\n"
111     "    <method name=\"PrevPage\">\n"
112     "    </method>\n"
113     "    <method name=\"NextPage\">\n"
114     "    </method>\n"
115     "    <method name=\"SelectCandidate\">\n"
116     "      <arg name=\"index\" direction=\"in\" type=\"i\"/>\n"
117     "    </method>\n"
118     "    <signal name=\"CommitString\">\n"
119     "      <arg name=\"str\" type=\"s\"/>\n"
120     "    </signal>\n"
121     "    <signal name=\"CurrentIM\">\n"
122     "      <arg name=\"name\" type=\"s\"/>\n"
123     "      <arg name=\"uniqueName\" type=\"s\"/>\n"
124     "      <arg name=\"langCode\" type=\"s\"/>\n"
125     "    </signal>\n"
126     "    <signal name=\"DeleteSurroundingText\">\n"
127     "      <arg name=\"offset\" type=\"i\"/>\n"
128     "      <arg name=\"nchar\" type=\"u\"/>\n"
129     "    </signal>\n"
130     "    <signal name=\"UpdateFormattedPreedit\">\n"
131     "      <arg name=\"str\" type=\"a(si)\"/>\n"
132     "      <arg name=\"cursorpos\" type=\"i\"/>\n"
133     "    </signal>\n"
134     "    <signal name=\"UpdateClientSideUI\">\n"
135     "      <arg name=\"preedit\" type=\"a(si)\"/>\n"
136     "      <arg name=\"cursorpos\" type=\"i\"/>\n"
137     "      <arg name=\"auxUp\" type=\"a(si)\"/>\n"
138     "      <arg name=\"auxDown\" type=\"a(si)\"/>\n"
139     "      <arg name=\"candidates\" type=\"a(ss)\"/>\n"
140     "      <arg name=\"candidateIndex\" type=\"i\"/>\n"
141     "      <arg name=\"layoutHint\" type=\"i\"/>\n"
142     "      <arg name=\"hasPrev\" type=\"b\"/>\n"
143     "      <arg name=\"hasNext\" type=\"b\"/>\n"
144     "    </signal>\n"
145     "    <signal name=\"ForwardKey\">\n"
146     "      <arg name=\"keyval\" type=\"u\"/>\n"
147     "      <arg name=\"state\" type=\"u\"/>\n"
148     "      <arg name=\"type\" type=\"b\"/>\n"
149     "    </signal>\n"
150     "  </interface>\n"
151     "</node>\n";
152 
153 G_DEFINE_TYPE_WITH_PRIVATE(FcitxGClient, fcitx_g_client, G_TYPE_OBJECT);
154 
155 enum {
156     CONNECTED_SIGNAL,
157     FORWARD_KEY_SIGNAL,
158     COMMIT_STRING_SIGNAL,
159     DELETE_SURROUNDING_TEXT_SIGNAL,
160     UPDATED_FORMATTED_PREEDIT_SIGNAL,
161     UPDATE_CLIENT_SIDE_UI_SIGNAL,
162     CURRENT_IM_SIGNAL,
163     LAST_SIGNAL
164 };
165 
166 static guint signals[LAST_SIGNAL] = {0};
167 
168 static GDBusInterfaceInfo *_fcitx_g_client_get_interface_info(void);
169 static GDBusInterfaceInfo *_fcitx_g_client_get_clientic_info(void);
170 
171 static void _fcitx_g_client_update_availability(FcitxGClient *self);
172 static void _fcitx_g_client_availability_changed(FcitxGWatcher *connection,
173                                                  gboolean avail,
174                                                  gpointer user_data);
175 static void _fcitx_g_client_service_vanished(GDBusConnection *conn,
176                                              const gchar *name,
177                                              gpointer user_data);
178 static void _fcitx_g_client_create_ic(FcitxGClient *self);
179 static void _fcitx_g_client_create_ic_phase1_finished(GObject *source_object,
180                                                       GAsyncResult *res,
181                                                       gpointer user_data);
182 static void _fcitx_g_client_create_ic_cb(GObject *source_object,
183                                          GAsyncResult *res, gpointer user_data);
184 static void _fcitx_g_client_create_ic_phase2_finished(GObject *source_object,
185                                                       GAsyncResult *res,
186                                                       gpointer user_data);
187 static void _fcitx_g_client_g_signal(GDBusProxy *proxy, gchar *sender_name,
188                                      gchar *signal_name, GVariant *parameters,
189                                      gpointer user_data);
190 static void _fcitx_g_client_clean_up(FcitxGClient *self);
191 static gboolean _fcitx_g_client_recheck(gpointer user_data);
192 
193 static void fcitx_g_client_finalize(GObject *object);
194 static void fcitx_g_client_dispose(GObject *object);
195 static void fcitx_g_client_constructed(GObject *object);
196 static void fcitx_g_client_set_property(GObject *gobject, guint prop_id,
197                                         const GValue *value, GParamSpec *pspec);
198 
199 static void _item_free(gpointer arg);
200 
201 #define STATIC_INTERFACE_INFO(FUNCTION, XML)                                   \
202     static GDBusInterfaceInfo *FUNCTION(void) {                                \
203         static gsize has_info = 0;                                             \
204         static GDBusInterfaceInfo *info = NULL;                                \
205         if (g_once_init_enter(&has_info)) {                                    \
206             GDBusNodeInfo *introspection_data;                                 \
207             introspection_data = g_dbus_node_info_new_for_xml(XML, NULL);      \
208             info = introspection_data->interfaces[0];                          \
209             g_once_init_leave(&has_info, 1);                                   \
210         }                                                                      \
211         return info;                                                           \
212     }
213 
STATIC_INTERFACE_INFO(_fcitx_g_client_get_interface_info,introspection_xml)214 STATIC_INTERFACE_INFO(_fcitx_g_client_get_interface_info, introspection_xml)
215 STATIC_INTERFACE_INFO(_fcitx_g_client_get_clientic_info, ic_introspection_xml)
216 
217 static void fcitx_g_client_class_init(FcitxGClientClass *klass) {
218     GObjectClass *gobject_class;
219 
220     gobject_class = G_OBJECT_CLASS(klass);
221     gobject_class->set_property = fcitx_g_client_set_property;
222     gobject_class->dispose = fcitx_g_client_dispose;
223     gobject_class->finalize = fcitx_g_client_finalize;
224     gobject_class->constructed = fcitx_g_client_constructed;
225 
226     g_object_class_install_property(
227         gobject_class, PROP_WATCHER,
228         g_param_spec_object("watcher", "Fcitx Watcher", "Fcitx Watcher",
229                             FCITX_G_TYPE_WATCHER,
230                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
231 
232     /* install signals */
233     /**
234      * FcitxGClient::connected:
235      * @self: A #FcitxGClient
236      *
237      * Emit when connected to fcitx and created ic
238      */
239     signals[CONNECTED_SIGNAL] =
240         g_signal_new("connected", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0,
241                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
242 
243     /**
244      * FcitxGClient::forward-key:
245      * @self: A #FcitxGClient
246      * @keyval: key value
247      * @state: key state
248      * @type: event type
249      *
250      * Emit when input method ask for forward a key
251      */
252     signals[FORWARD_KEY_SIGNAL] =
253         g_signal_new("forward-key", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0,
254                      NULL, NULL, fcitx_marshall_VOID__UINT_UINT_INT,
255                      G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INT);
256     /**
257      * FcitxGClient::commit-string:
258      * @self: A #FcitxGClient
259      * @string: string to be commited
260      *
261      * Emit when input method commit one string
262      */
263     signals[COMMIT_STRING_SIGNAL] = g_signal_new(
264         "commit-string", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
265         g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
266 
267     /**
268      * FcitxGClient::delete-surrounding-text:
269      * @self: A #FcitxGClient
270      * @cursor: deletion start
271      * @len: deletion length
272      *
273      * Emit when input method need to delete surrounding text
274      */
275     signals[DELETE_SURROUNDING_TEXT_SIGNAL] = g_signal_new(
276         "delete-surrounding-text", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0,
277         NULL, NULL, fcitx_marshall_VOID__INT_UINT, G_TYPE_NONE, 2, G_TYPE_INT,
278         G_TYPE_UINT);
279 
280     /**
281      * FcitxGClient::update-formatted-preedit:
282      * @self: A #FcitxGClient
283      * @preedit: (transfer none) (element-type FcitxGPreeditItem): A
284      * #FcitxGPreeditItem List
285      * @cursor: cursor position by utf8 byte
286      *
287      * Emit when input method needs to update formatted preedit
288      */
289     signals[UPDATED_FORMATTED_PREEDIT_SIGNAL] = g_signal_new(
290         "update-formatted-preedit", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0,
291         NULL, NULL, fcitx_marshall_VOID__BOXED_INT, G_TYPE_NONE, 2,
292         G_TYPE_PTR_ARRAY, G_TYPE_INT);
293 
294     /**
295      * FcitxGClient::update-client-side-ui:
296      * @self: A #FcitxGClient
297      * @preedit: (transfer none) (element-type FcitxGPreeditItem): A
298      * #FcitxGPreeditItem List
299      * @preedit_cursor: preedit cursor position by utf8 byte
300      * @aux_up: (transfer none) (element-type FcitxGPreeditItem): A
301      * #FcitxGPreeditItem List
302      * @aux_down: (transfer none) (element-type FcitxGPreeditItem): A
303      * #FcitxGPreeditItem List
304      * @candidate_list: (transfer none) (element-type FcitxGCandidateItem): A
305      * #FcitxGCandidateItem List
306      * @candidate_cursor: candidate cursor position
307      * @candidate_layout_hint: candidate layout hint
308      * @has_prev: has prev page
309      * @has_next: has next page
310      *
311      * Emit when input method needs to update the client-side user interface
312      */
313     signals[UPDATE_CLIENT_SIDE_UI_SIGNAL] = g_signal_new(
314         "update-client-side-ui", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0,
315         NULL, NULL,
316         fcitx_marshall_VOID__BOXED_INT_BOXED_BOXED_BOXED_INT_INT_BOOLEAN_BOOLEAN,
317         G_TYPE_NONE, 9, G_TYPE_PTR_ARRAY, G_TYPE_INT, G_TYPE_PTR_ARRAY,
318         G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY, G_TYPE_INT, G_TYPE_INT,
319         G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
320     /**
321      * FcitxGClient::current-im:
322      * @self: A #FcitxGClient
323      * @name: name of input method
324      * @unique_name: unique name of input method
325      * @lang_code: language code of input method
326      *
327      * Emit when input method used in input context changed
328      */
329     signals[CURRENT_IM_SIGNAL] = g_signal_new(
330         "current-im", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
331         fcitx_marshall_VOID__STRING_STRING_STRING, G_TYPE_NONE, 3,
332         G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
333 }
334 
fcitx_g_client_init(FcitxGClient * self)335 static void fcitx_g_client_init(FcitxGClient *self) {
336     self->priv = fcitx_g_client_get_instance_private(self);
337 
338     self->priv->watcher = NULL;
339     self->priv->cancellable = NULL;
340     self->priv->improxy = NULL;
341     self->priv->icproxy = NULL;
342     self->priv->icname = NULL;
343     self->priv->display = NULL;
344     self->priv->program = NULL;
345     self->priv->watch_id = 0;
346 }
347 
fcitx_g_client_constructed(GObject * object)348 static void fcitx_g_client_constructed(GObject *object) {
349     FcitxGClient *self = FCITX_G_CLIENT(object);
350     G_OBJECT_CLASS(fcitx_g_client_parent_class)->constructed(object);
351     if (!self->priv->watcher) {
352         self->priv->watcher = fcitx_g_watcher_new();
353         g_object_ref_sink(self->priv->watcher);
354         fcitx_g_watcher_watch(self->priv->watcher);
355     }
356     g_signal_connect(self->priv->watcher, "availability-changed",
357                      (GCallback)_fcitx_g_client_availability_changed, self);
358     _fcitx_g_client_availability_changed(
359         self->priv->watcher,
360         fcitx_g_watcher_is_service_available(self->priv->watcher), self);
361 }
362 
fcitx_g_client_finalize(GObject * object)363 static void fcitx_g_client_finalize(GObject *object) {
364     if (G_OBJECT_CLASS(fcitx_g_client_parent_class)->finalize != NULL)
365         G_OBJECT_CLASS(fcitx_g_client_parent_class)->finalize(object);
366 }
367 
fcitx_g_client_dispose(GObject * object)368 static void fcitx_g_client_dispose(GObject *object) {
369     FcitxGClient *self = FCITX_G_CLIENT(object);
370 
371     if (self->priv->icproxy) {
372         g_dbus_proxy_call(self->priv->icproxy, "DestroyIC", NULL,
373                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
374     }
375 
376     g_signal_handlers_disconnect_by_data(self->priv->watcher, self);
377     _fcitx_g_client_clean_up(self);
378 
379     g_clear_pointer(&self->priv->display, g_free);
380     g_clear_pointer(&self->priv->program, g_free);
381 
382     if (G_OBJECT_CLASS(fcitx_g_client_parent_class)->dispose != NULL) {
383         G_OBJECT_CLASS(fcitx_g_client_parent_class)->dispose(object);
384     }
385 }
386 
387 /**
388  * fcitx_g_client_get_uuid
389  * @self: a #FcitxGWatcher
390  *
391  * Returns: (transfer none): the current uuid of input context.
392  */
fcitx_g_client_get_uuid(FcitxGClient * self)393 const guint8 *fcitx_g_client_get_uuid(FcitxGClient *self) {
394     return self->priv->uuid;
395 }
396 
397 /**
398  * fcitx_g_client_focus_in:
399  * @self: A #FcitxGClient
400  *
401  * tell fcitx current client has focus
402  **/
fcitx_g_client_focus_in(FcitxGClient * self)403 void fcitx_g_client_focus_in(FcitxGClient *self) {
404     g_return_if_fail(fcitx_g_client_is_valid(self));
405     g_dbus_proxy_call(self->priv->icproxy, "FocusIn", NULL,
406                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
407 }
408 
409 /**
410  * fcitx_g_client_focus_out:
411  * @self: A #FcitxGClient
412  *
413  * tell fcitx current client has lost focus
414  **/
fcitx_g_client_focus_out(FcitxGClient * self)415 void fcitx_g_client_focus_out(FcitxGClient *self) {
416     g_return_if_fail(fcitx_g_client_is_valid(self));
417     g_dbus_proxy_call(self->priv->icproxy, "FocusOut", NULL,
418                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
419 }
420 
421 /**
422  * fcitx_g_client_reset:
423  * @self: A #FcitxGClient
424  *
425  * tell fcitx current client is reset from client side
426  **/
fcitx_g_client_reset(FcitxGClient * self)427 void fcitx_g_client_reset(FcitxGClient *self) {
428     g_return_if_fail(fcitx_g_client_is_valid(self));
429     g_dbus_proxy_call(self->priv->icproxy, "Reset", NULL,
430                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
431 }
432 
433 /**
434  * fcitx_g_client_set_capability:
435  * @self: A #FcitxGClient
436  * @flags: capability
437  *
438  * set client capability of input context.
439  **/
fcitx_g_client_set_capability(FcitxGClient * self,guint64 flags)440 void fcitx_g_client_set_capability(FcitxGClient *self, guint64 flags) {
441     g_return_if_fail(fcitx_g_client_is_valid(self));
442     g_dbus_proxy_call(self->priv->icproxy, "SetCapability",
443                       g_variant_new("(t)", flags), G_DBUS_CALL_FLAGS_NONE, -1,
444                       NULL, NULL, NULL);
445 }
446 
447 /**
448  * fcitx_g_client_set_cursor_rect:
449  * @self: A #FcitxGClient
450  * @x: x of cursor
451  * @y: y of cursor
452  * @w: width of cursor
453  * @h: height of cursor
454  *
455  * tell fcitx current client's cursor geometry info
456  **/
fcitx_g_client_set_cursor_rect(FcitxGClient * self,gint x,gint y,gint w,gint h)457 void fcitx_g_client_set_cursor_rect(FcitxGClient *self, gint x, gint y, gint w,
458                                     gint h) {
459 
460     g_return_if_fail(fcitx_g_client_is_valid(self));
461     g_dbus_proxy_call(self->priv->icproxy, "SetCursorRect",
462                       g_variant_new("(iiii)", x, y, w, h),
463                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
464 }
465 
466 /**
467  * fcitx_g_client_set_cursor_rect_with_scale_factor:
468  * @self: A #FcitxGClient
469  * @x: x of cursor
470  * @y: y of cursor
471  * @w: width of cursor
472  * @h: height of cursor
473  * @scale: scale factor of surface
474  *
475  * tell fcitx current client's cursor geometry info
476  **/
fcitx_g_client_set_cursor_rect_with_scale_factor(FcitxGClient * self,gint x,gint y,gint w,gint h,gdouble scale)477 void fcitx_g_client_set_cursor_rect_with_scale_factor(FcitxGClient *self,
478                                                       gint x, gint y, gint w,
479                                                       gint h, gdouble scale) {
480 
481     g_return_if_fail(fcitx_g_client_is_valid(self));
482     g_dbus_proxy_call(self->priv->icproxy, "SetCursorRectV2",
483                       g_variant_new("(iiiid)", x, y, w, h, scale),
484                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
485 }
486 
487 /**
488  * fcitx_g_client_prev:
489  * @self: A #FcitxGClient
490  *
491  * tell fcitx current client to go prev page.
492  **/
fcitx_g_client_prev_page(FcitxGClient * self)493 void fcitx_g_client_prev_page(FcitxGClient *self) {
494     g_return_if_fail(fcitx_g_client_is_valid(self));
495     g_dbus_proxy_call(self->priv->icproxy, "PrevPage", NULL,
496                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
497 }
498 
499 /**
500  * fcitx_g_client_next:
501  * @self: A #FcitxGClient
502  *
503  * tell fcitx current client to go next page.
504  **/
fcitx_g_client_next_page(FcitxGClient * self)505 void fcitx_g_client_next_page(FcitxGClient *self) {
506     g_return_if_fail(fcitx_g_client_is_valid(self));
507     g_dbus_proxy_call(self->priv->icproxy, "NextPage", NULL,
508                       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
509 }
510 
511 /**
512  * fcitx_g_client_select_candidate:
513  * @self: A #FcitxGClient
514  * @index: Candidate index
515  *
516  * tell fcitx current client to select candidate.
517  **/
fcitx_g_client_select_candidate(FcitxGClient * self,int index)518 void fcitx_g_client_select_candidate(FcitxGClient *self, int index) {
519     g_return_if_fail(fcitx_g_client_is_valid(self));
520     g_dbus_proxy_call(self->priv->icproxy, "SelectCandidate",
521                       g_variant_new("(i)", index), G_DBUS_CALL_FLAGS_NONE, -1,
522                       NULL, NULL, NULL);
523 }
524 
525 /**
526  * fcitx_g_client_set_surrounding_text:
527  * @self: A #FcitxGClient
528  * @text: (transfer none) (allow-none): surroundng text
529  * @cursor: cursor position coresponding to text
530  * @anchor: anchor position coresponding to text
531  **/
fcitx_g_client_set_surrounding_text(FcitxGClient * self,gchar * text,guint cursor,guint anchor)532 void fcitx_g_client_set_surrounding_text(FcitxGClient *self, gchar *text,
533                                          guint cursor, guint anchor) {
534     g_return_if_fail(fcitx_g_client_is_valid(self));
535     if (text) {
536         g_dbus_proxy_call(self->priv->icproxy, "SetSurroundingText",
537                           g_variant_new("(suu)", text, cursor, anchor),
538                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
539     } else {
540         g_dbus_proxy_call(self->priv->icproxy, "SetSurroundingTextPosition",
541                           g_variant_new("(uu)", cursor, anchor),
542                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
543     }
544 }
545 
546 /**
547  * fcitx_g_client_process_key_finish:
548  * @self: A #FcitxGClient
549  * @res: result
550  *
551  * use this function with #fcitx_g_client_process_key_async
552  *
553  * Returns: process key result
554  **/
fcitx_g_client_process_key_finish(FcitxGClient * self,GAsyncResult * res)555 gboolean fcitx_g_client_process_key_finish(FcitxGClient *self,
556                                            GAsyncResult *res) {
557     g_return_val_if_fail(fcitx_g_client_is_valid(self), FALSE);
558 
559     gboolean ret = FALSE;
560     GVariant *result = g_dbus_proxy_call_finish(self->priv->icproxy, res, NULL);
561     if (result) {
562         g_variant_get(result, "(b)", &ret);
563         g_variant_unref(result);
564     }
565     return ret;
566 }
567 
_process_key_data_free(ProcessKeyStruct * pk)568 static void _process_key_data_free(ProcessKeyStruct *pk) {
569     g_object_unref(pk->self);
570     g_free(pk);
571 }
572 
_fcitx_g_client_process_key_cb(G_GNUC_UNUSED GObject * source_object,GAsyncResult * res,gpointer user_data)573 static void _fcitx_g_client_process_key_cb(G_GNUC_UNUSED GObject *source_object,
574                                            GAsyncResult *res,
575                                            gpointer user_data) {
576     ProcessKeyStruct *pk = user_data;
577     pk->callback(G_OBJECT(pk->self), res, pk->user_data);
578     _process_key_data_free(pk);
579 }
580 
581 /**
582  * fcitx_g_client_process_key:
583  * @self: A #FcitxGClient
584  * @keyval: key value
585  * @keycode: hardware key code
586  * @state: key state
587  * @isRelease: event type is key release
588  * @t: timestamp
589  * @timeout_msec: timeout in millisecond
590  * @cancellable: cancellable
591  * @callback: (scope async) (closure user_data): callback
592  * @user_data: (closure): user data
593  *
594  * use this function with #fcitx_g_client_process_key_finish
595  **/
fcitx_g_client_process_key(FcitxGClient * self,guint32 keyval,guint32 keycode,guint32 state,gboolean isRelease,guint32 t,gint timeout_msec,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)596 void fcitx_g_client_process_key(FcitxGClient *self, guint32 keyval,
597                                 guint32 keycode, guint32 state,
598                                 gboolean isRelease, guint32 t,
599                                 gint timeout_msec, GCancellable *cancellable,
600                                 GAsyncReadyCallback callback,
601                                 gpointer user_data) {
602     g_return_if_fail(fcitx_g_client_is_valid(self));
603     ProcessKeyStruct *pk = g_new(ProcessKeyStruct, 1);
604     pk->self = g_object_ref(self);
605     pk->callback = callback;
606     pk->user_data = user_data;
607     g_dbus_proxy_call(
608         self->priv->icproxy, "ProcessKeyEvent",
609         g_variant_new("(uuubu)", keyval, keycode, state, isRelease, t),
610         G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable,
611         _fcitx_g_client_process_key_cb, pk);
612 }
613 
614 /**
615  * fcitx_g_client_process_key_sync:
616  * @self: A #FcitxGClient
617  * @keyval: key value
618  * @keycode: hardware key code
619  * @state: key state
620  * @isRelease: is key release
621  * @t: timestamp
622  *
623  * send a key event to fcitx synchronizely
624  *
625  * Returns: the key is processed or not
626  */
fcitx_g_client_process_key_sync(FcitxGClient * self,guint32 keyval,guint32 keycode,guint32 state,gboolean isRelease,guint32 t)627 gboolean fcitx_g_client_process_key_sync(FcitxGClient *self, guint32 keyval,
628                                          guint32 keycode, guint32 state,
629                                          gboolean isRelease, guint32 t) {
630     g_return_val_if_fail(fcitx_g_client_is_valid(self), FALSE);
631     gboolean ret = FALSE;
632     GVariant *result = g_dbus_proxy_call_sync(
633         self->priv->icproxy, "ProcessKeyEvent",
634         g_variant_new("(uuubu)", keyval, keycode, state, isRelease, t),
635         G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
636 
637     if (result) {
638         g_variant_get(result, "(b)", &ret);
639         g_variant_unref(result);
640     }
641 
642     return ret;
643 }
644 
645 static void
_fcitx_g_client_availability_changed(G_GNUC_UNUSED FcitxGWatcher * connection,G_GNUC_UNUSED gboolean avail,gpointer user_data)646 _fcitx_g_client_availability_changed(G_GNUC_UNUSED FcitxGWatcher *connection,
647                                      G_GNUC_UNUSED gboolean avail,
648                                      gpointer user_data) {
649     FcitxGClient *self = user_data;
650     _fcitx_g_client_update_availability(self);
651 }
652 
_fcitx_g_client_update_availability(FcitxGClient * self)653 static void _fcitx_g_client_update_availability(FcitxGClient *self) {
654     g_timeout_add_full(G_PRIORITY_DEFAULT, 100, _fcitx_g_client_recheck,
655                        g_object_ref(self), g_object_unref);
656 }
657 
_fcitx_g_client_recheck(gpointer user_data)658 static gboolean _fcitx_g_client_recheck(gpointer user_data) {
659     FcitxGClient *self = user_data;
660     // Check we are not valid or in the process of create ic.
661     if (!fcitx_g_client_is_valid(self) && self->priv->cancellable == NULL &&
662         fcitx_g_watcher_is_service_available(self->priv->watcher)) {
663         _fcitx_g_client_create_ic(self);
664     }
665     if (!fcitx_g_watcher_is_service_available(self->priv->watcher)) {
666         _fcitx_g_client_clean_up(self);
667     }
668     return FALSE;
669 }
670 
_fcitx_g_client_create_ic(FcitxGClient * self)671 static void _fcitx_g_client_create_ic(FcitxGClient *self) {
672     g_return_if_fail(fcitx_g_watcher_is_service_available(self->priv->watcher));
673 
674     _fcitx_g_client_clean_up(self);
675 
676     const gchar *service_name =
677         fcitx_g_watcher_get_service_name(self->priv->watcher);
678     GDBusConnection *connection =
679         fcitx_g_watcher_get_connection(self->priv->watcher);
680     self->priv->watch_id = g_bus_watch_name_on_connection(
681         connection, service_name, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
682         _fcitx_g_client_service_vanished, self, NULL);
683 
684     self->priv->cancellable = g_cancellable_new();
685     g_dbus_proxy_new(connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
686                      _fcitx_g_client_get_interface_info(), service_name,
687                      "/org/freedesktop/portal/inputmethod",
688                      "org.fcitx.Fcitx.InputMethod1", self->priv->cancellable,
689                      _fcitx_g_client_create_ic_phase1_finished,
690                      g_object_ref(self));
691 }
692 
693 static void
_fcitx_g_client_service_vanished(G_GNUC_UNUSED GDBusConnection * conn,G_GNUC_UNUSED const gchar * name,gpointer user_data)694 _fcitx_g_client_service_vanished(G_GNUC_UNUSED GDBusConnection *conn,
695                                  G_GNUC_UNUSED const gchar *name,
696                                  gpointer user_data) {
697     FcitxGClient *self = user_data;
698     _fcitx_g_client_clean_up(self);
699     _fcitx_g_client_update_availability(self);
700 }
701 
702 static void
_fcitx_g_client_create_ic_phase1_finished(G_GNUC_UNUSED GObject * source_object,GAsyncResult * res,gpointer user_data)703 _fcitx_g_client_create_ic_phase1_finished(G_GNUC_UNUSED GObject *source_object,
704                                           GAsyncResult *res,
705                                           gpointer user_data) {
706     FcitxGClient *self = user_data;
707     g_return_if_fail(user_data != NULL);
708     g_return_if_fail(FCITX_G_IS_CLIENT(user_data));
709 
710     g_clear_object(&self->priv->cancellable);
711     g_clear_object(&self->priv->improxy);
712 
713     self->priv->improxy = g_dbus_proxy_new_finish(res, NULL);
714     if (!self->priv->improxy) {
715         _fcitx_g_client_clean_up(self);
716         g_object_unref(self);
717         return;
718     }
719 
720     self->priv->cancellable = g_cancellable_new();
721 
722     GVariantBuilder builder;
723     g_variant_builder_init(&builder, G_VARIANT_TYPE("a(ss)"));
724     if (self->priv->display) {
725         g_variant_builder_add(&builder, "(ss)", "display", self->priv->display);
726     }
727     if (self->priv->program) {
728         g_variant_builder_add(&builder, "(ss)", "program", self->priv->program);
729     }
730     g_dbus_proxy_call(self->priv->improxy, "CreateInputContext",
731                       g_variant_new("(a(ss))", &builder),
732                       G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */
733                       self->priv->cancellable, _fcitx_g_client_create_ic_cb,
734                       self);
735 }
736 
_fcitx_g_client_create_ic_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)737 static void _fcitx_g_client_create_ic_cb(GObject *source_object,
738                                          GAsyncResult *res,
739                                          gpointer user_data) {
740     FcitxGClient *self = (FcitxGClient *)user_data;
741     g_clear_object(&self->priv->cancellable);
742 
743     g_autoptr(GVariant) result =
744         g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, NULL);
745 
746     if (!result) {
747         _fcitx_g_client_clean_up(self);
748         g_object_unref(self);
749         return;
750     }
751 
752     GVariantIter iter, inner;
753     g_variant_iter_init(&iter, result);
754     GVariant *pathVariant = g_variant_iter_next_value(&iter);
755     const gchar *path = g_variant_get_string(pathVariant, NULL);
756     GVariant *uuidVariant = g_variant_iter_next_value(&iter);
757     size_t size = g_variant_iter_init(&inner, uuidVariant);
758     if (size == 16) {
759         int i = 0;
760         GVariant *byte;
761         while ((byte = g_variant_iter_next_value(&inner))) {
762             self->priv->uuid[i] = g_variant_get_byte(byte);
763             i++;
764         }
765     }
766 
767     self->priv->icname = g_strdup(path);
768     self->priv->cancellable = g_cancellable_new();
769     g_dbus_proxy_new(
770         g_dbus_proxy_get_connection(self->priv->improxy),
771         G_DBUS_PROXY_FLAGS_NONE, _fcitx_g_client_get_clientic_info(),
772         g_dbus_proxy_get_name(self->priv->improxy), self->priv->icname,
773         "org.fcitx.Fcitx.InputContext1", self->priv->cancellable,
774         _fcitx_g_client_create_ic_phase2_finished, self);
775 }
776 
777 static void
_fcitx_g_client_create_ic_phase2_finished(G_GNUC_UNUSED GObject * source_object,GAsyncResult * res,gpointer user_data)778 _fcitx_g_client_create_ic_phase2_finished(G_GNUC_UNUSED GObject *source_object,
779                                           GAsyncResult *res,
780                                           gpointer user_data) {
781     g_return_if_fail(user_data != NULL);
782     g_return_if_fail(FCITX_G_IS_CLIENT(user_data));
783     FcitxGClient *self = (FcitxGClient *)user_data;
784     g_clear_object(&self->priv->cancellable);
785     g_clear_object(&self->priv->icproxy);
786     self->priv->icproxy = g_dbus_proxy_new_finish(res, NULL);
787     if (!self->priv->icproxy) {
788         _fcitx_g_client_clean_up(self);
789         g_object_unref(self);
790         return;
791     }
792 
793     g_signal_connect(self->priv->icproxy, "g-signal",
794                      G_CALLBACK(_fcitx_g_client_g_signal), self);
795     g_signal_emit(self, signals[CONNECTED_SIGNAL], 0);
796 
797     /* unref for _fcitx_g_client_create_ic_cb */
798     g_object_unref(self);
799 }
800 
_item_free(gpointer arg)801 static void _item_free(gpointer arg) {
802     FcitxGPreeditItem *item = arg;
803     g_free(item->string);
804     g_free(item);
805 }
806 
_candidate_free(gpointer arg)807 static void _candidate_free(gpointer arg) {
808     FcitxGCandidateItem *item = arg;
809     g_free(item->label);
810     g_free(item->candidate);
811     g_free(item);
812 }
813 
buildFormattedTextArray(GPtrArray * array,GVariantIter * iter)814 void buildFormattedTextArray(GPtrArray *array, GVariantIter *iter) {
815     gchar *string;
816     int type;
817     while (g_variant_iter_next(iter, "(si)", &string, &type)) {
818         FcitxGPreeditItem *item = g_malloc0(sizeof(FcitxGPreeditItem));
819         item->string = string;
820         item->type = type;
821         g_ptr_array_add(array, item);
822     }
823     g_variant_iter_free(iter);
824 }
825 
buildCandidateArray(GPtrArray * array,GVariantIter * iter)826 void buildCandidateArray(GPtrArray *array, GVariantIter *iter) {
827     gchar *label, *candidate;
828     while (g_variant_iter_next(iter, "(ss)", &label, &candidate)) {
829         FcitxGCandidateItem *item = g_malloc0(sizeof(FcitxGCandidateItem));
830         item->label = label;
831         item->candidate = candidate;
832         g_ptr_array_add(array, item);
833     }
834     g_variant_iter_free(iter);
835 }
836 
_fcitx_g_client_g_signal(G_GNUC_UNUSED GDBusProxy * proxy,G_GNUC_UNUSED gchar * sender_name,gchar * signal_name,GVariant * parameters,gpointer user_data)837 static void _fcitx_g_client_g_signal(G_GNUC_UNUSED GDBusProxy *proxy,
838                                      G_GNUC_UNUSED gchar *sender_name,
839                                      gchar *signal_name, GVariant *parameters,
840                                      gpointer user_data) {
841     if (g_strcmp0(signal_name, "CommitString") == 0) {
842         gchar *data = NULL;
843         g_variant_get(parameters, "(s)", &data);
844         if (data) {
845             g_signal_emit(user_data, signals[COMMIT_STRING_SIGNAL], 0, data);
846         }
847         g_free(data);
848     } else if (g_strcmp0(signal_name, "CurrentIM") == 0) {
849         gchar *name = NULL;
850         gchar *uniqueName = NULL;
851         gchar *langCode = NULL;
852         g_variant_get(parameters, "(sss)", &name, &uniqueName, &langCode);
853         if (name && uniqueName && langCode) {
854             g_signal_emit(user_data, signals[CURRENT_IM_SIGNAL], 0, name,
855                           uniqueName, langCode);
856         }
857         g_free(name);
858         g_free(uniqueName);
859         g_free(langCode);
860     } else if (g_strcmp0(signal_name, "ForwardKey") == 0) {
861         guint32 key, state;
862         gboolean isRelease;
863         g_variant_get(parameters, "(uub)", &key, &state, &isRelease);
864         g_signal_emit(user_data, signals[FORWARD_KEY_SIGNAL], 0, key, state,
865                       isRelease);
866     } else if (g_strcmp0(signal_name, "DeleteSurroundingText") == 0) {
867         guint32 nchar;
868         gint32 offset;
869         g_variant_get(parameters, "(iu)", &offset, &nchar);
870         g_signal_emit(user_data, signals[DELETE_SURROUNDING_TEXT_SIGNAL], 0,
871                       offset, nchar);
872     } else if (g_strcmp0(signal_name, "UpdateFormattedPreedit") == 0) {
873         int cursor_pos;
874         GPtrArray *array = g_ptr_array_new_with_free_func(_item_free);
875         GVariantIter *iter;
876         g_variant_get(parameters, "(a(si)i)", &iter, &cursor_pos);
877 
878         buildFormattedTextArray(array, iter);
879         g_signal_emit(user_data, signals[UPDATED_FORMATTED_PREEDIT_SIGNAL], 0,
880                       array, cursor_pos);
881         g_ptr_array_free(array, TRUE);
882     } else if (g_strcmp0(signal_name, "UpdateClientSideUI") == 0) {
883         int preedit_cursor_pos = -1, candidate_cursor_pos = -1, layout_hint = 0;
884         gboolean has_prev = FALSE, has_next = FALSE;
885         GPtrArray *preedit_strings = g_ptr_array_new_with_free_func(_item_free);
886         GPtrArray *aux_up_strings = g_ptr_array_new_with_free_func(_item_free);
887         GPtrArray *aux_down_strings =
888             g_ptr_array_new_with_free_func(_item_free);
889         GPtrArray *candidate_list =
890             g_ptr_array_new_with_free_func(_candidate_free);
891         GVariantIter *preedit_iter, *aux_up_iter, *aux_down_iter,
892             *candidate_iter;
893 
894         // Unpack the values
895         g_variant_get(parameters, "(a(si)ia(si)a(si)a(ss)iibb)", &preedit_iter,
896                       &preedit_cursor_pos, &aux_up_iter, &aux_down_iter,
897                       &candidate_iter, &candidate_cursor_pos, &layout_hint,
898                       &has_prev, &has_next);
899 
900         // Populate the (si) GPtrArrays
901         buildFormattedTextArray(preedit_strings, preedit_iter);
902         buildFormattedTextArray(aux_up_strings, aux_up_iter);
903         buildFormattedTextArray(aux_down_strings, aux_down_iter);
904 
905         // Populate the (ss) candidate GPtrArray
906         buildCandidateArray(candidate_list, candidate_iter);
907 
908         // Emit the signal
909         g_signal_emit(user_data, signals[UPDATE_CLIENT_SIDE_UI_SIGNAL], 0,
910                       preedit_strings, preedit_cursor_pos, aux_up_strings,
911                       aux_down_strings, candidate_list, candidate_cursor_pos,
912                       layout_hint, has_prev, has_next);
913 
914         // Free memory
915         g_ptr_array_free(preedit_strings, TRUE);
916         g_ptr_array_free(aux_up_strings, TRUE);
917         g_ptr_array_free(aux_down_strings, TRUE);
918         g_ptr_array_free(candidate_list, TRUE);
919     }
920 }
921 
922 /**
923  * fcitx_g_client_new:
924  *
925  * New a #FcitxGClient
926  *
927  * Returns: A newly allocated #FcitxGClient
928  **/
fcitx_g_client_new()929 FcitxGClient *fcitx_g_client_new() {
930     FcitxGClient *self = g_object_new(FCITX_G_TYPE_CLIENT, NULL);
931     return FCITX_G_CLIENT(self);
932 }
933 
934 /**
935  * fcitx_g_client_new_with_connection:
936  * @connection: the #FcitxConnection to be used with this client
937  *
938  * New a #FcitxGClient
939  *
940  * Returns: A newly allocated #FcitxGClient
941  **/
fcitx_g_client_new_with_watcher(FcitxGWatcher * watcher)942 FcitxGClient *fcitx_g_client_new_with_watcher(FcitxGWatcher *watcher) {
943     FcitxGClient *self =
944         g_object_new(FCITX_G_TYPE_CLIENT, "watcher", watcher, NULL);
945     return FCITX_G_CLIENT(self);
946 }
947 
fcitx_g_client_set_display(FcitxGClient * self,const gchar * display)948 void fcitx_g_client_set_display(FcitxGClient *self, const gchar *display) {
949     g_free(self->priv->display);
950     self->priv->display = g_strdup(display);
951 }
952 
fcitx_g_client_set_program(FcitxGClient * self,const gchar * program)953 void fcitx_g_client_set_program(FcitxGClient *self, const gchar *program) {
954     g_free(self->priv->program);
955     self->priv->program = g_strdup(program);
956 }
957 
958 /**
959  * fcitx_g_client_is_valid:
960  * @self: A #FcitxGClient
961  *
962  * Check #FcitxGClient is valid to communicate with Fcitx
963  *
964  * Returns: #FcitxGClient is valid or not
965  **/
fcitx_g_client_is_valid(FcitxGClient * self)966 gboolean fcitx_g_client_is_valid(FcitxGClient *self) {
967     return self->priv->icproxy != NULL;
968 }
969 
fcitx_g_client_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)970 static void fcitx_g_client_set_property(GObject *gobject, guint prop_id,
971                                         const GValue *value,
972                                         GParamSpec *pspec) {
973     FcitxGClient *self = FCITX_G_CLIENT(gobject);
974     FcitxGWatcher *watcher;
975     switch (prop_id) {
976     case PROP_WATCHER:
977         watcher = g_value_get_object(value);
978         if (watcher) {
979             self->priv->watcher = watcher;
980             g_object_ref_sink(self->priv->watcher);
981         }
982         break;
983     default:
984         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
985         break;
986     }
987 }
988 
_fcitx_g_client_clean_up(FcitxGClient * self)989 static void _fcitx_g_client_clean_up(FcitxGClient *self) {
990     if (self->priv->cancellable) {
991         g_cancellable_cancel(self->priv->cancellable);
992     }
993 
994     g_clear_object(&self->priv->cancellable);
995     g_clear_object(&self->priv->improxy);
996     g_clear_pointer(&self->priv->icname, g_free);
997 
998     if (self->priv->icproxy) {
999         g_signal_handlers_disconnect_by_func(
1000             self->priv->icproxy, G_CALLBACK(_fcitx_g_client_g_signal), self);
1001     }
1002     g_clear_object(&self->priv->icproxy);
1003     if (self->priv->watch_id) {
1004         g_bus_unwatch_name(self->priv->watch_id);
1005         self->priv->watch_id = 0;
1006     }
1007 }
1008 
1009 // kate: indent-mode cstyle; replace-tabs on;
1010