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