1 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /* vim:set et sts=4: */
3 /* bus - The Input Bus
4 * Copyright (C) 2017-2019 Takao Fujiwara <takao.fujiwara1@gmail.com>
5 * Copyright (C) 2017-2019 Red Hat, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include "ibusemoji.h"
29 #include "ibusinternal.h"
30
31 #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
32 #define IBUS_EMOJI_DATA_VERSION (5)
33
34 enum {
35 PROP_0 = 0,
36 PROP_EMOJI,
37 PROP_ANNOTATIONS,
38 PROP_DESCRIPTION,
39 PROP_CATEGORY,
40 };
41
42 struct _IBusEmojiDataPrivate {
43 gchar *emoji;
44 GSList *annotations;
45 gchar *description;
46 gchar *category;
47 };
48
49 #define IBUS_EMOJI_DATA_GET_PRIVATE(o) \
50 ((IBusEmojiDataPrivate *)ibus_emoji_data_get_instance_private (o))
51
52 /* functions prototype */
53 static void ibus_emoji_data_set_property (IBusEmojiData *emoji,
54 guint prop_id,
55 const GValue *value,
56 GParamSpec *pspec);
57 static void ibus_emoji_data_get_property (IBusEmojiData *emoji,
58 guint prop_id,
59 GValue *value,
60 GParamSpec *pspec);
61 static void ibus_emoji_data_destroy (IBusEmojiData *emoji);
62 static gboolean ibus_emoji_data_serialize (IBusEmojiData *emoji,
63 GVariantBuilder *builder);
64 static gint ibus_emoji_data_deserialize (IBusEmojiData *emoji,
65 GVariant *variant);
66 static gboolean ibus_emoji_data_copy (IBusEmojiData *emoji,
67 const IBusEmojiData *src);
68
G_DEFINE_TYPE_WITH_PRIVATE(IBusEmojiData,ibus_emoji_data,IBUS_TYPE_SERIALIZABLE)69 G_DEFINE_TYPE_WITH_PRIVATE (IBusEmojiData,
70 ibus_emoji_data,
71 IBUS_TYPE_SERIALIZABLE)
72
73 static void
74 ibus_emoji_data_class_init (IBusEmojiDataClass *class)
75 {
76 IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
77 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
78 IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
79
80 object_class->destroy = (IBusObjectDestroyFunc) ibus_emoji_data_destroy;
81 gobject_class->set_property =
82 (GObjectSetPropertyFunc) ibus_emoji_data_set_property;
83 gobject_class->get_property =
84 (GObjectGetPropertyFunc) ibus_emoji_data_get_property;
85 serializable_class->serialize =
86 (IBusSerializableSerializeFunc) ibus_emoji_data_serialize;
87 serializable_class->deserialize =
88 (IBusSerializableDeserializeFunc) ibus_emoji_data_deserialize;
89 serializable_class->copy =
90 (IBusSerializableCopyFunc) ibus_emoji_data_copy;
91
92 /* install properties */
93 /**
94 * IBusEmojiData:emoji:
95 *
96 * The emoji character
97 */
98 g_object_class_install_property (gobject_class,
99 PROP_EMOJI,
100 g_param_spec_string ("emoji",
101 "emoji character",
102 "The emoji character UTF-8",
103 NULL,
104 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
105
106 /**
107 * IBusEmojiData:annotations: (transfer container) (element-type utf8):
108 *
109 * The emoji annotations
110 */
111 g_object_class_install_property (gobject_class,
112 PROP_ANNOTATIONS,
113 g_param_spec_pointer ("annotations",
114 "emoji annotations",
115 "The emoji annotation list",
116 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
117
118 /**
119 * IBusEmojiData:description:
120 *
121 * The emoji description
122 */
123 g_object_class_install_property (gobject_class,
124 PROP_DESCRIPTION,
125 g_param_spec_string ("description",
126 "emoji description",
127 "The emoji description",
128 "",
129 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
130
131 /**
132 * IBusEmojiData:category:
133 *
134 * The emoji category
135 */
136 g_object_class_install_property (gobject_class,
137 PROP_CATEGORY,
138 g_param_spec_string ("category",
139 "emoji category",
140 "The emoji category",
141 "",
142 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
143 }
144
145 static void
ibus_emoji_data_init(IBusEmojiData * emoji)146 ibus_emoji_data_init (IBusEmojiData *emoji)
147 {
148 emoji->priv = IBUS_EMOJI_DATA_GET_PRIVATE (emoji);
149 }
150
151 static void
free_dict_words(gpointer list)152 free_dict_words (gpointer list)
153 {
154 g_slist_free_full (list, g_free);
155 }
156
157 static void
ibus_emoji_data_destroy(IBusEmojiData * emoji)158 ibus_emoji_data_destroy (IBusEmojiData *emoji)
159 {
160 g_clear_pointer (&emoji->priv->emoji, g_free);
161 g_clear_pointer (&emoji->priv->annotations, free_dict_words);
162 g_clear_pointer (&emoji->priv->description, g_free);
163 g_clear_pointer (&emoji->priv->category, g_free);
164
165 IBUS_OBJECT_CLASS (ibus_emoji_data_parent_class)->
166 destroy (IBUS_OBJECT (emoji));
167 }
168
169 static void
ibus_emoji_data_set_property(IBusEmojiData * emoji,guint prop_id,const GValue * value,GParamSpec * pspec)170 ibus_emoji_data_set_property (IBusEmojiData *emoji,
171 guint prop_id,
172 const GValue *value,
173 GParamSpec *pspec)
174 {
175 switch (prop_id) {
176 case PROP_EMOJI:
177 g_assert (emoji->priv->emoji == NULL);
178 emoji->priv->emoji = g_value_dup_string (value);
179 break;
180 case PROP_ANNOTATIONS:
181 if (emoji->priv->annotations)
182 g_slist_free_full (emoji->priv->annotations, g_free);
183 emoji->priv->annotations =
184 g_slist_copy_deep (g_value_get_pointer (value),
185 (GCopyFunc) g_strdup, NULL);
186 break;
187 case PROP_DESCRIPTION:
188 g_free (emoji->priv->description);
189 emoji->priv->description = g_value_dup_string (value);
190 break;
191 case PROP_CATEGORY:
192 g_assert (emoji->priv->category == NULL);
193 emoji->priv->category = g_value_dup_string (value);
194 break;
195 default:
196 G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
197 }
198 }
199
200 static void
ibus_emoji_data_get_property(IBusEmojiData * emoji,guint prop_id,GValue * value,GParamSpec * pspec)201 ibus_emoji_data_get_property (IBusEmojiData *emoji,
202 guint prop_id,
203 GValue *value,
204 GParamSpec *pspec)
205 {
206 switch (prop_id) {
207 case PROP_EMOJI:
208 g_value_set_string (value, ibus_emoji_data_get_emoji (emoji));
209 break;
210 case PROP_ANNOTATIONS:
211 g_value_set_pointer (
212 value,
213 g_slist_copy_deep (ibus_emoji_data_get_annotations (emoji),
214 (GCopyFunc) g_strdup, NULL));
215 break;
216 case PROP_DESCRIPTION:
217 g_value_set_string (value, ibus_emoji_data_get_description (emoji));
218 break;
219 case PROP_CATEGORY:
220 g_value_set_string (value, ibus_emoji_data_get_category (emoji));
221 break;
222 default:
223 G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
224 }
225 }
226
227 static gboolean
ibus_emoji_data_serialize(IBusEmojiData * emoji,GVariantBuilder * builder)228 ibus_emoji_data_serialize (IBusEmojiData *emoji,
229 GVariantBuilder *builder)
230 {
231 GSList *l;
232 gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
233 serialize ((IBusSerializable *)emoji, builder);
234 g_return_val_if_fail (retval, FALSE);
235
236 #define NOTNULL(s) ((s) != NULL ? (s) : "")
237 /* If you will add a new property, you can append it at the end and
238 * you should not change the serialized order of name, longname,
239 * description, ... because the order is also used in other applications
240 * likes ibus-qt. */
241 g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->emoji));
242 g_variant_builder_add (builder, "u",
243 g_slist_length (emoji->priv->annotations));
244 for (l = emoji->priv->annotations; l != NULL; l = l->next) {
245 g_variant_builder_add (builder, "s", NOTNULL (l->data));
246 }
247 g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description));
248 g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category));
249 #undef NOTNULL
250 return TRUE;
251 }
252
253 static gint
ibus_emoji_data_deserialize(IBusEmojiData * emoji,GVariant * variant)254 ibus_emoji_data_deserialize (IBusEmojiData *emoji,
255 GVariant *variant)
256 {
257 guint length, i;
258 GSList *annotations = NULL;
259 gint retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
260 deserialize ((IBusSerializable *)emoji, variant);
261 g_return_val_if_fail (retval, 0);
262
263 /* If you will add a new property, you can append it at the end and
264 * you should not change the serialized order of name, longname,
265 * description, ... because the order is also used in other applications
266 * likes ibus-qt. */
267 ibus_g_variant_get_child_string (variant, retval++,
268 &emoji->priv->emoji);
269 g_variant_get_child (variant, retval++, "u", &length);
270 for (i = 0; i < length; i++) {
271 gchar *s = NULL;
272 g_variant_get_child (variant, retval++, "s", &s);
273 annotations = g_slist_append (annotations, s);
274 }
275 emoji->priv->annotations = annotations;
276 ibus_g_variant_get_child_string (variant, retval++,
277 &emoji->priv->description);
278 ibus_g_variant_get_child_string (variant, retval++,
279 &emoji->priv->category);
280 return retval;
281 }
282
283 static gboolean
ibus_emoji_data_copy(IBusEmojiData * dest,const IBusEmojiData * src)284 ibus_emoji_data_copy (IBusEmojiData *dest,
285 const IBusEmojiData *src)
286 {
287 gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
288 copy ((IBusSerializable *)dest,
289 (IBusSerializable *)src);
290 g_return_val_if_fail (retval, FALSE);
291
292 dest->priv->emoji = g_strdup (src->priv->emoji);
293 dest->priv->annotations = g_slist_copy_deep (src->priv->annotations,
294 (GCopyFunc) g_strdup,
295 NULL);
296 dest->priv->description = g_strdup (src->priv->description);
297 dest->priv->category = g_strdup (src->priv->category);
298 return TRUE;
299 }
300
301 IBusEmojiData *
ibus_emoji_data_new(const gchar * first_property_name,...)302 ibus_emoji_data_new (const gchar *first_property_name, ...)
303 {
304 va_list var_args;
305 IBusEmojiData *emoji;
306
307 g_assert (first_property_name != NULL);
308 va_start (var_args, first_property_name);
309 emoji = (IBusEmojiData *) g_object_new_valist (IBUS_TYPE_EMOJI_DATA,
310 first_property_name,
311 var_args);
312 va_end (var_args);
313 /* emoji is required. Other properties are set in class_init by default. */
314 g_assert (emoji->priv->emoji != NULL);
315 g_assert (emoji->priv->description != NULL);
316 g_assert (emoji->priv->category != NULL);
317 return emoji;
318 }
319
320 const gchar *
ibus_emoji_data_get_emoji(IBusEmojiData * emoji)321 ibus_emoji_data_get_emoji (IBusEmojiData *emoji)
322 {
323 g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
324
325 return emoji->priv->emoji;
326 }
327
328 GSList *
ibus_emoji_data_get_annotations(IBusEmojiData * emoji)329 ibus_emoji_data_get_annotations (IBusEmojiData *emoji)
330 {
331 g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
332
333 return emoji->priv->annotations;
334 }
335
336 void
ibus_emoji_data_set_annotations(IBusEmojiData * emoji,GSList * annotations)337 ibus_emoji_data_set_annotations (IBusEmojiData *emoji,
338 GSList *annotations)
339 {
340 g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
341
342 if (emoji->priv->annotations)
343 g_slist_free_full (emoji->priv->annotations, g_free);
344 emoji->priv->annotations = annotations;
345 }
346
347 const gchar *
ibus_emoji_data_get_description(IBusEmojiData * emoji)348 ibus_emoji_data_get_description (IBusEmojiData *emoji)
349 {
350 g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
351
352 return emoji->priv->description;
353 }
354
355 void
ibus_emoji_data_set_description(IBusEmojiData * emoji,const gchar * description)356 ibus_emoji_data_set_description (IBusEmojiData *emoji,
357 const gchar *description)
358 {
359 g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
360
361 g_free (emoji->priv->description);
362 emoji->priv->description = g_strdup (description);
363 }
364
365 const gchar *
ibus_emoji_data_get_category(IBusEmojiData * emoji)366 ibus_emoji_data_get_category (IBusEmojiData *emoji)
367 {
368 g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
369
370 return emoji->priv->category;
371 }
372
373 static void
variant_foreach_add_emoji(IBusEmojiData * emoji,GVariantBuilder * builder)374 variant_foreach_add_emoji (IBusEmojiData *emoji,
375 GVariantBuilder *builder)
376 {
377 g_variant_builder_add (
378 builder, "v",
379 ibus_serializable_serialize (IBUS_SERIALIZABLE (emoji)));
380 }
381
382 static GVariant *
ibus_emoji_data_list_serialize(GSList * list)383 ibus_emoji_data_list_serialize (GSList *list)
384 {
385 GVariantBuilder builder;
386
387 g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
388 g_slist_foreach (list, (GFunc) variant_foreach_add_emoji, &builder);
389 return g_variant_builder_end (&builder);
390 }
391
392 static GSList *
ibus_emoji_data_list_deserialize(GVariant * variant)393 ibus_emoji_data_list_deserialize (GVariant *variant)
394 {
395 GSList *list = NULL;
396 GVariantIter iter;
397 GVariant *emoji_variant = NULL;
398
399 g_variant_iter_init (&iter, variant);
400 while (g_variant_iter_loop (&iter, "v", &emoji_variant)) {
401 IBusEmojiData *data =
402 IBUS_EMOJI_DATA (ibus_serializable_deserialize (emoji_variant));
403 list = g_slist_append (list, data);
404 g_clear_pointer (&emoji_variant, g_variant_unref);
405 }
406
407 return list;
408 }
409
410 void
ibus_emoji_dict_save(const gchar * path,GHashTable * dict)411 ibus_emoji_dict_save (const gchar *path,
412 GHashTable *dict)
413 {
414 GList *values, *v;
415 GSList *list_for_save = NULL;
416
417 g_return_if_fail (path != NULL);
418 g_return_if_fail (dict != NULL);
419
420 values = g_hash_table_get_values (dict);
421 for (v = values; v; v = v->next) {
422 IBusEmojiData *data = v->data;
423 if (!IBUS_IS_EMOJI_DATA (data)) {
424 g_warning ("Your dict format of { annotation char, emoji GSList "
425 "} is no longer supported.\n"
426 "{ emoji char, IBusEmojiData GSList } is expected.");
427 return;
428 }
429 list_for_save = g_slist_append (list_for_save, data);
430 }
431
432 ibus_emoji_data_save (path, list_for_save);
433 }
434
435 GHashTable *
ibus_emoji_dict_load(const gchar * path)436 ibus_emoji_dict_load (const gchar *path)
437 {
438 GSList *list = ibus_emoji_data_load (path);
439 GSList *l;
440 GHashTable *dict = g_hash_table_new_full (g_str_hash,
441 g_str_equal,
442 g_free,
443 g_object_unref);
444
445 for (l = list; l; l = l->next) {
446 IBusEmojiData *data = l->data;
447 if (!IBUS_IS_EMOJI_DATA (data)) {
448 g_warning ("Your dict format is no longer supported.\n"
449 "Need to create the dictionaries again.");
450 return NULL;
451 }
452 g_hash_table_insert (dict,
453 g_strdup (ibus_emoji_data_get_emoji (data)),
454 g_object_ref_sink (data));
455 }
456
457 g_slist_free (list);
458
459 return dict;
460 }
461
462
463 IBusEmojiData *
ibus_emoji_dict_lookup(GHashTable * dict,const gchar * emoji)464 ibus_emoji_dict_lookup (GHashTable *dict,
465 const gchar *emoji)
466 {
467 return (IBusEmojiData *) g_hash_table_lookup (dict, emoji);
468 }
469
470 void
ibus_emoji_data_save(const gchar * path,GSList * list)471 ibus_emoji_data_save (const gchar *path,
472 GSList *list)
473 {
474 GVariant *variant;
475 const gchar *header = IBUS_EMOJI_DATA_MAGIC;
476 const guint16 version = IBUS_EMOJI_DATA_VERSION;
477 const gchar *contents;
478 gsize length;
479 gchar *dir;
480 GStatBuf buf = { 0, };
481 GError *error = NULL;
482
483 g_return_if_fail (path != NULL);
484 g_return_if_fail (list != NULL);
485 if (list->data == NULL) {
486 g_warning ("Failed to save IBus emoji data: Need a list data.");
487 return;
488 }
489
490 variant = g_variant_new ("(sqv)",
491 header,
492 version,
493 ibus_emoji_data_list_serialize (list));
494
495 contents = g_variant_get_data (variant);
496 length = g_variant_get_size (variant);
497
498 dir = g_path_get_dirname (path);
499 if (g_strcmp0 (dir, ".") != 0 && g_stat (dir, &buf) != 0) {
500 g_mkdir_with_parents (dir, 0777);
501 }
502 g_free (dir);
503 if (!g_file_set_contents (path, contents, length, &error)) {
504 g_warning ("Failed to save emoji dict %s: %s", path, error->message);
505 g_error_free (error);
506 }
507
508 g_variant_unref (variant);
509 }
510
511 GSList *
ibus_emoji_data_load(const gchar * path)512 ibus_emoji_data_load (const gchar *path)
513 {
514 gchar *contents = NULL;
515 gsize length = 0;
516 GError *error = NULL;
517 GVariant *variant_table = NULL;
518 GVariant *variant = NULL;
519 const gchar *header = NULL;
520 guint16 version = 0;
521 GSList *retval = NULL;
522
523 if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
524 g_warning ("Emoji dict does not exist: %s", path);
525 goto out_load_cache;
526 }
527
528 if (!g_file_get_contents (path, &contents, &length, &error)) {
529 g_warning ("Failed to get dict content %s: %s", path, error->message);
530 g_error_free (error);
531 goto out_load_cache;
532 }
533
534 variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
535 contents,
536 length,
537 FALSE,
538 NULL,
539 NULL);
540
541 if (variant_table == NULL) {
542 g_warning ("cache table is broken.");
543 goto out_load_cache;
544 }
545
546 g_variant_get (variant_table, "(&sq)", &header, &version);
547
548 if (g_strcmp0 (header, IBUS_EMOJI_DATA_MAGIC) != 0) {
549 g_warning ("cache is not IBusEmojiData.");
550 goto out_load_cache;
551 }
552
553 if (version > IBUS_EMOJI_DATA_VERSION) {
554 g_warning ("cache version is different: %u != %u",
555 version, IBUS_EMOJI_DATA_VERSION);
556 goto out_load_cache;
557 }
558
559 version = 0;
560 header = NULL;
561 g_variant_unref (variant_table);
562
563 variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
564 contents,
565 length,
566 FALSE,
567 NULL,
568 NULL);
569
570 if (variant_table == NULL) {
571 g_warning ("cache table is broken.");
572 goto out_load_cache;
573 }
574
575 g_variant_get (variant_table, "(&sqv)",
576 NULL,
577 NULL,
578 &variant);
579
580 if (variant == NULL) {
581 g_warning ("cache dict is broken.");
582 goto out_load_cache;
583 }
584
585 retval = ibus_emoji_data_list_deserialize (variant);
586
587 out_load_cache:
588 if (variant)
589 g_variant_unref (variant);
590 if (variant_table)
591 g_variant_unref (variant_table);
592 g_free (contents);
593
594 return retval;
595 }
596