1 /*
2 * Copyright (C) 2013 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 *
17 * Written by:
18 * Jasper St. Pierre <jstpierre@mecheye.net>
19 * Matthias Clasen <mclasen@redhat.com>
20 */
21
22 #include "config.h"
23 #include "cc-input-chooser.h"
24
25 #include <locale.h>
26 #include <glib/gi18n.h>
27 #include <gio/gio.h>
28
29 #include <gtk/gtk.h>
30
31 #define GNOME_DESKTOP_USE_UNSTABLE_API
32 #include <libgnome-desktop/gnome-languages.h>
33 #include <libgnome-desktop/gnome-xkb-info.h>
34
35 #ifdef HAVE_IBUS
36 #include <ibus.h>
37 #include "cc-ibus-utils.h"
38 #endif
39
40 #include "cc-common-language.h"
41
42 #include <glib-object.h>
43
44 #define INPUT_SOURCE_TYPE_XKB "xkb"
45 #define INPUT_SOURCE_TYPE_IBUS "ibus"
46
47 #define MIN_ROWS 5
48
49 struct _CcInputChooserPrivate
50 {
51 GtkWidget *filter_entry;
52 GtkWidget *input_list;
53 GHashTable *inputs;
54
55 GtkWidget *scrolled_window;
56 GtkWidget *no_results;
57 GtkWidget *more_item;
58
59 gboolean showing_extra;
60 gchar *locale;
61 gchar *id;
62 gchar *type;
63 GnomeXkbInfo *xkb_info;
64 #ifdef HAVE_IBUS
65 IBusBus *ibus;
66 GHashTable *ibus_engines;
67 GCancellable *ibus_cancellable;
68 #endif
69 };
70 typedef struct _CcInputChooserPrivate CcInputChooserPrivate;
71 G_DEFINE_TYPE_WITH_PRIVATE (CcInputChooser, cc_input_chooser, GTK_TYPE_BOX);
72
73 enum {
74 PROP_0,
75 PROP_SHOWING_EXTRA,
76 PROP_LAST
77 };
78
79 static GParamSpec *obj_props[PROP_LAST];
80
81 enum {
82 CHANGED,
83 CONFIRM,
84 LAST_SIGNAL
85 };
86
87 static guint signals[LAST_SIGNAL] = { 0 };
88
89 typedef struct {
90 GtkWidget *box;
91 GtkWidget *label;
92 GtkWidget *checkmark;
93
94 gchar *id;
95 gchar *type;
96 gchar *name;
97 gboolean is_extra;
98 } InputWidget;
99
100 static InputWidget *
get_input_widget(GtkWidget * widget)101 get_input_widget (GtkWidget *widget)
102 {
103 return g_object_get_data (G_OBJECT (widget), "input-widget");
104 }
105
106 static GtkWidget *
padded_label_new(char * text)107 padded_label_new (char *text)
108 {
109 GtkWidget *widget;
110 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
111 gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
112 gtk_widget_set_margin_top (widget, 10);
113 gtk_widget_set_margin_bottom (widget, 10);
114 gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0);
115 return widget;
116 }
117
118 static void
input_widget_free(gpointer data)119 input_widget_free (gpointer data)
120 {
121 InputWidget *widget = data;
122
123 g_free (widget->id);
124 g_free (widget->type);
125 g_free (widget->name);
126 g_free (widget);
127 }
128
129 static gboolean
get_layout(CcInputChooser * chooser,const gchar * type,const gchar * id,const gchar ** layout,const gchar ** variant)130 get_layout (CcInputChooser *chooser,
131 const gchar *type,
132 const gchar *id,
133 const gchar **layout,
134 const gchar **variant)
135 {
136 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
137
138 if (g_strcmp0 (type, INPUT_SOURCE_TYPE_XKB) == 0) {
139 gnome_xkb_info_get_layout_info (priv->xkb_info,
140 id, NULL, NULL,
141 layout, variant);
142 return TRUE;
143 }
144 #ifdef HAVE_IBUS
145 if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) == 0) {
146 IBusEngineDesc *engine_desc = NULL;
147
148 if (priv->ibus_engines)
149 engine_desc = g_hash_table_lookup (priv->ibus_engines, id);
150
151 if (!engine_desc)
152 return FALSE;
153
154 *layout = ibus_engine_desc_get_layout (engine_desc);
155 *variant = "";
156 return TRUE;
157 }
158 #endif
159 return FALSE;
160 }
161
162 static gboolean
preview_cb(GtkLabel * label,const gchar * uri,CcInputChooser * chooser)163 preview_cb (GtkLabel *label,
164 const gchar *uri,
165 CcInputChooser *chooser)
166 {
167 GtkWidget *row;
168 InputWidget *widget;
169 const gchar *layout;
170 const gchar *variant;
171 gchar *commandline;
172
173 row = gtk_widget_get_parent (GTK_WIDGET (label));
174 widget = get_input_widget (row);
175
176 if (!get_layout (chooser, widget->type, widget->id, &layout, &variant))
177 return TRUE;
178
179 if (variant[0])
180 commandline = g_strdup_printf ("gkbd-keyboard-display -l \"%s\t%s\"", layout, variant);
181 else
182 commandline = g_strdup_printf ("gkbd-keyboard-display -l %s", layout);
183 g_spawn_command_line_async (commandline, NULL);
184 g_free (commandline);
185
186 return TRUE;
187 }
188
189 static GtkWidget *
input_widget_new(CcInputChooser * chooser,const char * type,const char * id,gboolean is_extra)190 input_widget_new (CcInputChooser *chooser,
191 const char *type,
192 const char *id,
193 gboolean is_extra)
194 {
195 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
196 GtkWidget *label;
197 InputWidget *widget = g_new0 (InputWidget, 1);
198 const gchar *name;
199 gchar *text;
200
201 if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB)) {
202 gnome_xkb_info_get_layout_info (priv->xkb_info, id, &name, NULL, NULL, NULL);
203 }
204 #ifdef HAVE_IBUS
205 else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS)) {
206 if (priv->ibus_engines)
207 name = engine_get_display_name (g_hash_table_lookup (priv->ibus_engines, id));
208 else
209 name = id;
210 }
211 #endif
212 else {
213 name = "ERROR";
214 }
215
216 widget->id = g_strdup (id);
217 widget->type = g_strdup (type);
218 widget->name = g_strdup (name);
219 widget->is_extra = is_extra;
220
221 widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
222 gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL);
223 gtk_widget_set_margin_top (widget->box, 10);
224 gtk_widget_set_margin_bottom (widget->box, 10);
225 gtk_widget_set_margin_start (widget->box, 10);
226 gtk_widget_set_margin_end (widget->box, 10);
227 widget->label = gtk_label_new (name);
228 gtk_label_set_xalign (GTK_LABEL (widget->label), 0);
229 gtk_label_set_yalign (GTK_LABEL (widget->label), 0.5);
230 gtk_label_set_ellipsize (GTK_LABEL (widget->label), PANGO_ELLIPSIZE_END);
231 gtk_label_set_max_width_chars (GTK_LABEL (widget->label), 40);
232 gtk_label_set_width_chars (GTK_LABEL (widget->label), 40);
233 gtk_box_pack_start (GTK_BOX (widget->box), widget->label, FALSE, FALSE, 0);
234 widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU);
235 gtk_box_pack_start (GTK_BOX (widget->box), widget->checkmark, TRUE, TRUE, 0);
236 gtk_widget_set_margin_start (widget->checkmark, 10);
237 gtk_widget_set_margin_end (widget->checkmark, 10);
238 gtk_widget_set_halign (widget->box, GTK_ALIGN_START);
239
240 text = g_strdup_printf ("<a href='preview'>%s</a>", _("Preview"));
241 label = gtk_label_new ("");
242 gtk_label_set_markup (GTK_LABEL (label), text);
243 g_free (text);
244 g_signal_connect (label, "activate-link",
245 G_CALLBACK (preview_cb), chooser);
246 gtk_box_pack_start (GTK_BOX (widget->box), label, TRUE, TRUE, 0);
247
248 gtk_widget_show_all (widget->box);
249
250 g_object_set_data_full (G_OBJECT (widget->box), "input-widget", widget,
251 input_widget_free);
252
253 return widget->box;
254 }
255
256 static void
sync_checkmark(GtkWidget * row,gpointer user_data)257 sync_checkmark (GtkWidget *row,
258 gpointer user_data)
259 {
260 CcInputChooser *chooser = user_data;
261 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
262 GtkWidget *child;
263 InputWidget *widget;
264 gboolean should_be_visible;
265
266 child = gtk_bin_get_child (GTK_BIN (row));
267 widget = get_input_widget (child);
268
269 if (widget == NULL)
270 return;
271
272 if (priv->id == NULL || priv->type == NULL)
273 should_be_visible = FALSE;
274 else
275 should_be_visible = g_strcmp0 (widget->id, priv->id) == 0 && g_strcmp0 (widget->type, priv->type) == 0;
276 gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0);
277
278 if (widget->is_extra && should_be_visible)
279 widget->is_extra = FALSE;
280 }
281
282 static void
sync_all_checkmarks(CcInputChooser * chooser)283 sync_all_checkmarks (CcInputChooser *chooser)
284 {
285 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
286
287 gtk_container_foreach (GTK_CONTAINER (priv->input_list),
288 sync_checkmark, chooser);
289 gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
290 }
291
292 static GtkWidget *
more_widget_new(void)293 more_widget_new (void)
294 {
295 GtkWidget *widget;
296 GtkWidget *arrow;
297
298 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
299 gtk_widget_set_tooltip_text (widget, _("More…"));
300
301 arrow = gtk_image_new_from_icon_name ("view-more-symbolic", GTK_ICON_SIZE_MENU);
302 gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label");
303 gtk_widget_set_margin_top (widget, 10);
304 gtk_widget_set_margin_bottom (widget, 10);
305 gtk_widget_set_halign (arrow, GTK_ALIGN_CENTER);
306 gtk_widget_set_valign (arrow, GTK_ALIGN_CENTER);
307 gtk_box_pack_start (GTK_BOX (widget), arrow, TRUE, TRUE, 0);
308 gtk_widget_show_all (widget);
309
310 return widget;
311 }
312
313 static GtkWidget *
no_results_widget_new(void)314 no_results_widget_new (void)
315 {
316 GtkWidget *widget;
317
318 /* Translators: a search for input methods or keyboard layouts
319 * did not yield any results
320 */
321 widget = padded_label_new (_("No inputs found"));
322 gtk_widget_set_sensitive (widget, FALSE);
323 gtk_widget_show_all (widget);
324 return widget;
325 }
326
327 static void
choose_non_extras_foreach(GtkWidget * row,gpointer user_data)328 choose_non_extras_foreach (GtkWidget *row,
329 gpointer user_data)
330 {
331 GtkWidget *child;
332 InputWidget *widget;
333 guint *count = user_data;
334
335 *count += 1;
336 if (*count > MIN_ROWS)
337 return;
338
339 child = gtk_bin_get_child (GTK_BIN (row));
340 widget = get_input_widget (child);
341 if (widget == NULL)
342 return;
343
344 widget->is_extra = FALSE;
345 }
346
347 static void
choose_non_extras(CcInputChooser * chooser)348 choose_non_extras (CcInputChooser *chooser)
349 {
350 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
351 guint count = 0;
352
353 gtk_container_foreach (GTK_CONTAINER (priv->input_list),
354 choose_non_extras_foreach, &count);
355 }
356
357 static void
add_rows_to_list(CcInputChooser * chooser,GList * list,const gchar * type,const gchar * default_id)358 add_rows_to_list (CcInputChooser *chooser,
359 GList *list,
360 const gchar *type,
361 const gchar *default_id)
362 {
363 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
364 const gchar *id;
365 GtkWidget *widget;
366 gchar *key;
367
368 for (; list; list = list->next) {
369 id = (const gchar *) list->data;
370
371 if (g_strcmp0 (id, default_id) == 0)
372 continue;
373
374 key = g_strdup_printf ("%s::%s", type, id);
375 if (g_hash_table_contains (priv->inputs, key)) {
376 g_free (key);
377 continue;
378 }
379 g_hash_table_add (priv->inputs, key);
380
381 widget = input_widget_new (chooser, type, id, TRUE);
382 gtk_container_add (GTK_CONTAINER (priv->input_list), widget);
383 }
384 }
385
386 static void
add_row_to_list(CcInputChooser * chooser,const gchar * type,const gchar * id)387 add_row_to_list (CcInputChooser *chooser,
388 const gchar *type,
389 const gchar *id)
390 {
391 GList tmp = { 0 };
392 tmp.data = (gpointer)id;
393 add_rows_to_list (chooser, &tmp, type, NULL);
394 }
395
396 static void
get_locale_infos(CcInputChooser * chooser)397 get_locale_infos (CcInputChooser *chooser)
398 {
399 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
400 const gchar *type = NULL;
401 const gchar *id = NULL;
402 gchar *lang, *country;
403 GList *list;
404
405 if (gnome_get_input_source_from_locale (priv->locale, &type, &id)) {
406 add_row_to_list (chooser, type, id);
407 if (!priv->id) {
408 priv->id = g_strdup (id);
409 priv->type = g_strdup (type);
410 }
411 }
412
413 if (!gnome_parse_locale (priv->locale, &lang, &country, NULL, NULL))
414 goto out;
415
416 list = gnome_xkb_info_get_layouts_for_language (priv->xkb_info, lang);
417 add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
418 g_list_free (list);
419
420 if (country != NULL) {
421 list = gnome_xkb_info_get_layouts_for_country (priv->xkb_info, country);
422 add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
423 g_list_free (list);
424 }
425
426 choose_non_extras (chooser);
427
428 list = gnome_xkb_info_get_all_layouts (priv->xkb_info);
429 add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
430 g_list_free (list);
431
432 gtk_widget_show_all (priv->input_list);
433
434 out:
435 g_free (lang);
436 g_free (country);
437 }
438
439 static gboolean
input_visible(GtkListBoxRow * row,gpointer user_data)440 input_visible (GtkListBoxRow *row,
441 gpointer user_data)
442 {
443 CcInputChooser *chooser = user_data;
444 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
445 InputWidget *widget;
446 gboolean visible;
447 GtkWidget *child;
448 const char *search_term;
449
450 child = gtk_bin_get_child (GTK_BIN (row));
451 if (child == priv->more_item)
452 return !priv->showing_extra && g_hash_table_size (priv->inputs) > MIN_ROWS;
453
454 widget = get_input_widget (child);
455
456 if (!priv->showing_extra && widget->is_extra)
457 return FALSE;
458
459 search_term = gtk_entry_get_text (GTK_ENTRY (priv->filter_entry));
460 if (!search_term || !*search_term)
461 return TRUE;
462
463 visible = g_str_match_string (search_term, widget->name, TRUE);
464 return visible;
465 }
466
467 static gint
sort_inputs(GtkListBoxRow * a,GtkListBoxRow * b,gpointer data)468 sort_inputs (GtkListBoxRow *a,
469 GtkListBoxRow *b,
470 gpointer data)
471 {
472 InputWidget *la, *lb;
473
474 la = get_input_widget (gtk_bin_get_child (GTK_BIN (a)));
475 lb = get_input_widget (gtk_bin_get_child (GTK_BIN (b)));
476
477 if (la == NULL)
478 return 1;
479
480 if (lb == NULL)
481 return -1;
482
483 if (la->is_extra && !lb->is_extra)
484 return 1;
485
486 if (!la->is_extra && lb->is_extra)
487 return -1;
488
489 return strcmp (la->name, lb->name);
490 }
491
492 static void
filter_changed(GtkEntry * entry,CcInputChooser * chooser)493 filter_changed (GtkEntry *entry,
494 CcInputChooser *chooser)
495 {
496 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
497 gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
498 }
499
500 static void
show_more(CcInputChooser * chooser)501 show_more (CcInputChooser *chooser)
502 {
503 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
504
505 if (g_hash_table_size (priv->inputs) <= MIN_ROWS)
506 return;
507
508 gtk_widget_show (priv->filter_entry);
509 gtk_widget_grab_focus (priv->filter_entry);
510
511 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
512 GTK_POLICY_NEVER,
513 GTK_POLICY_AUTOMATIC);
514 gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL);
515
516 priv->showing_extra = TRUE;
517 gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
518 g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]);
519 }
520
521 static void
set_input(CcInputChooser * chooser,const gchar * id,const gchar * type)522 set_input (CcInputChooser *chooser,
523 const gchar *id,
524 const gchar *type)
525 {
526 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
527
528 if (g_strcmp0 (priv->id, id) == 0 &&
529 g_strcmp0 (priv->type, type) == 0)
530 return;
531
532 g_free (priv->id);
533 g_free (priv->type);
534 priv->id = g_strdup (id);
535 priv->type = g_strdup (type);
536
537 sync_all_checkmarks (chooser);
538
539 g_signal_emit (chooser, signals[CHANGED], 0);
540 }
541
542 static gboolean
confirm_choice(gpointer data)543 confirm_choice (gpointer data)
544 {
545 GtkWidget *widget = data;
546
547 g_signal_emit (widget, signals[CONFIRM], 0);
548
549 return G_SOURCE_REMOVE;
550 }
551
552 static void
row_activated(GtkListBox * box,GtkListBoxRow * row,CcInputChooser * chooser)553 row_activated (GtkListBox *box,
554 GtkListBoxRow *row,
555 CcInputChooser *chooser)
556 {
557 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
558 GtkWidget *child;
559 InputWidget *widget;
560
561 if (row == NULL)
562 return;
563
564 child = gtk_bin_get_child (GTK_BIN (row));
565 if (child == priv->more_item) {
566 show_more (chooser);
567 } else {
568 widget = get_input_widget (child);
569 if (widget == NULL)
570 return;
571 if (g_strcmp0 (priv->id, widget->id) == 0 &&
572 g_strcmp0 (priv->type, widget->type) == 0)
573 confirm_choice (chooser);
574 else
575 set_input (chooser, widget->id, widget->type);
576 }
577 }
578
579 static void
update_header_func(GtkListBoxRow * child,GtkListBoxRow * before,gpointer user_data)580 update_header_func (GtkListBoxRow *child,
581 GtkListBoxRow *before,
582 gpointer user_data)
583 {
584 GtkWidget *header;
585
586 if (before == NULL) {
587 gtk_list_box_row_set_header (child, NULL);
588 return;
589 }
590
591 header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
592 gtk_list_box_row_set_header (child, header);
593 gtk_widget_show (header);
594 }
595
596 #ifdef HAVE_IBUS
597 static void
update_ibus_active_sources(CcInputChooser * chooser)598 update_ibus_active_sources (CcInputChooser *chooser)
599 {
600 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
601 GList *rows, *l;
602 InputWidget *row;
603 const gchar *type;
604 const gchar *id;
605 IBusEngineDesc *engine_desc;
606 gchar *name;
607
608 rows = gtk_container_get_children (GTK_CONTAINER (priv->input_list));
609 for (l = rows; l; l = l->next) {
610 row = get_input_widget (gtk_bin_get_child (GTK_BIN (l->data)));
611 if (row == NULL)
612 continue;
613
614 type = row->type;
615 id = row->id;
616 if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) != 0)
617 continue;
618
619 engine_desc = g_hash_table_lookup (priv->ibus_engines, id);
620 if (engine_desc) {
621 name = engine_get_display_name (engine_desc);
622 gtk_label_set_text (GTK_LABEL (row->label), name);
623 g_free (name);
624 }
625 }
626 g_list_free (rows);
627 }
628
629 static void
get_ibus_locale_infos(CcInputChooser * chooser)630 get_ibus_locale_infos (CcInputChooser *chooser)
631 {
632 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
633 GHashTableIter iter;
634 const gchar *engine_id;
635 IBusEngineDesc *engine;
636
637 if (!priv->ibus_engines)
638 return;
639
640 g_hash_table_iter_init (&iter, priv->ibus_engines);
641 while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine))
642 add_row_to_list (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id);
643 }
644
645 static void
fetch_ibus_engines_result(GObject * object,GAsyncResult * result,CcInputChooser * chooser)646 fetch_ibus_engines_result (GObject *object,
647 GAsyncResult *result,
648 CcInputChooser *chooser)
649 {
650 CcInputChooserPrivate *priv;
651 GList *list, *l;
652 GError *error;
653
654 error = NULL;
655 list = ibus_bus_list_engines_async_finish (IBUS_BUS (object), result, &error);
656 if (!list && error) {
657 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
658 g_warning ("Couldn't finish IBus request: %s", error->message);
659 g_error_free (error);
660 return;
661 }
662
663 priv = cc_input_chooser_get_instance_private (chooser);
664 g_clear_object (&priv->ibus_cancellable);
665
666 /* Maps engine ids to engine description objects */
667 priv->ibus_engines = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
668
669 for (l = list; l; l = l->next) {
670 IBusEngineDesc *engine = l->data;
671 const gchar *engine_id;
672
673 engine_id = ibus_engine_desc_get_name (engine);
674 if (g_str_has_prefix (engine_id, "xkb:"))
675 g_object_unref (engine);
676 else
677 g_hash_table_replace (priv->ibus_engines, (gpointer)engine_id, engine);
678 }
679 g_list_free (list);
680
681 update_ibus_active_sources (chooser);
682 get_ibus_locale_infos (chooser);
683
684 sync_all_checkmarks (chooser);
685 }
686
687 static void
fetch_ibus_engines(CcInputChooser * chooser)688 fetch_ibus_engines (CcInputChooser *chooser)
689 {
690 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
691
692 priv->ibus_cancellable = g_cancellable_new ();
693
694 ibus_bus_list_engines_async (priv->ibus,
695 -1,
696 priv->ibus_cancellable,
697 (GAsyncReadyCallback)fetch_ibus_engines_result,
698 chooser);
699
700 /* We've got everything we needed, don't want to be called again. */
701 g_signal_handlers_disconnect_by_func (priv->ibus, fetch_ibus_engines, chooser);
702 }
703
704 static void
maybe_start_ibus(void)705 maybe_start_ibus (void)
706 {
707 /* IBus doesn't export API in the session bus. The only thing
708 * we have there is a well known name which we can use as a
709 * sure-fire way to activate it.
710 */
711 g_bus_unwatch_name (g_bus_watch_name (G_BUS_TYPE_SESSION,
712 IBUS_SERVICE_IBUS,
713 G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
714 NULL,
715 NULL,
716 NULL,
717 NULL));
718 }
719 #endif
720
721 static void
cc_input_chooser_constructed(GObject * object)722 cc_input_chooser_constructed (GObject *object)
723 {
724 CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
725 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
726
727 G_OBJECT_CLASS (cc_input_chooser_parent_class)->constructed (object);
728
729 priv->xkb_info = gnome_xkb_info_new ();
730
731 #ifdef HAVE_IBUS
732 ibus_init ();
733 if (!priv->ibus) {
734 priv->ibus = ibus_bus_new_async ();
735 if (ibus_bus_is_connected (priv->ibus))
736 fetch_ibus_engines (chooser);
737 else
738 g_signal_connect_swapped (priv->ibus, "connected",
739 G_CALLBACK (fetch_ibus_engines), chooser);
740 }
741 maybe_start_ibus ();
742 #endif
743
744 priv->inputs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
745 priv->more_item = more_widget_new ();
746 priv->no_results = no_results_widget_new ();
747
748 gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->input_list),
749 sort_inputs, chooser, NULL);
750 gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->input_list),
751 input_visible, chooser, NULL);
752 gtk_list_box_set_header_func (GTK_LIST_BOX (priv->input_list),
753 update_header_func, chooser, NULL);
754 gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->input_list),
755 GTK_SELECTION_NONE);
756
757 if (priv->locale == NULL) {
758 priv->locale = cc_common_language_get_current_language ();
759 }
760
761 get_locale_infos (chooser);
762 #ifdef HAVE_IBUS
763 get_ibus_locale_infos (chooser);
764 #endif
765
766 gtk_container_add (GTK_CONTAINER (priv->input_list), priv->more_item);
767 gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->input_list), priv->no_results);
768
769 g_signal_connect (priv->filter_entry, "changed",
770 G_CALLBACK (filter_changed),
771 chooser);
772
773 g_signal_connect (priv->input_list, "row-activated",
774 G_CALLBACK (row_activated), chooser);
775
776 sync_all_checkmarks (chooser);
777 }
778
779 static void
cc_input_chooser_finalize(GObject * object)780 cc_input_chooser_finalize (GObject *object)
781 {
782 CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
783 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
784
785 g_clear_object (&priv->xkb_info);
786 g_hash_table_unref (priv->inputs);
787 #ifdef HAVE_IBUS
788 g_clear_object (&priv->ibus);
789 if (priv->ibus_cancellable)
790 g_cancellable_cancel (priv->ibus_cancellable);
791 g_clear_object (&priv->ibus_cancellable);
792 g_clear_pointer (&priv->ibus_engines, g_hash_table_destroy);
793 #endif
794
795 G_OBJECT_CLASS (cc_input_chooser_parent_class)->finalize (object);
796 }
797
798 static void
cc_input_chooser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)799 cc_input_chooser_get_property (GObject *object,
800 guint prop_id,
801 GValue *value,
802 GParamSpec *pspec)
803 {
804 CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
805 switch (prop_id) {
806 case PROP_SHOWING_EXTRA:
807 g_value_set_boolean (value, cc_input_chooser_get_showing_extra (chooser));
808 break;
809 default:
810 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
811 break;
812 }
813 }
814
815 static void
cc_input_chooser_class_init(CcInputChooserClass * klass)816 cc_input_chooser_class_init (CcInputChooserClass *klass)
817 {
818 GObjectClass *object_class = G_OBJECT_CLASS (klass);
819
820 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/input-chooser.ui");
821
822 gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, filter_entry);
823 gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, input_list);
824 gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, scrolled_window);
825
826 object_class->finalize = cc_input_chooser_finalize;
827 object_class->get_property = cc_input_chooser_get_property;
828 object_class->constructed = cc_input_chooser_constructed;
829
830 obj_props[PROP_SHOWING_EXTRA] =
831 g_param_spec_string ("showing-extra", "", "", "",
832 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
833
834 signals[CHANGED] =
835 g_signal_new ("changed",
836 G_TYPE_FROM_CLASS (object_class),
837 G_SIGNAL_RUN_FIRST,
838 0,
839 NULL, NULL,
840 g_cclosure_marshal_VOID__VOID,
841 G_TYPE_NONE, 0);
842
843 signals[CONFIRM] =
844 g_signal_new ("confirm",
845 G_TYPE_FROM_CLASS (object_class),
846 G_SIGNAL_RUN_FIRST,
847 0,
848 NULL, NULL,
849 g_cclosure_marshal_VOID__VOID,
850 G_TYPE_NONE, 0);
851
852 g_object_class_install_properties (object_class, PROP_LAST, obj_props);
853 }
854
855 static void
cc_input_chooser_init(CcInputChooser * chooser)856 cc_input_chooser_init (CcInputChooser *chooser)
857 {
858 gtk_widget_init_template (GTK_WIDGET (chooser));
859 }
860
861 void
cc_input_chooser_clear_filter(CcInputChooser * chooser)862 cc_input_chooser_clear_filter (CcInputChooser *chooser)
863 {
864 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
865 gtk_entry_set_text (GTK_ENTRY (priv->filter_entry), "");
866 }
867
868 const gchar *
cc_input_chooser_get_input_id(CcInputChooser * chooser)869 cc_input_chooser_get_input_id (CcInputChooser *chooser)
870 {
871 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
872 return priv->id;
873 }
874
875 const gchar *
cc_input_chooser_get_input_type(CcInputChooser * chooser)876 cc_input_chooser_get_input_type (CcInputChooser *chooser)
877 {
878 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
879 return priv->type;
880 }
881
882 void
cc_input_chooser_get_layout(CcInputChooser * chooser,const gchar ** layout,const gchar ** variant)883 cc_input_chooser_get_layout (CcInputChooser *chooser,
884 const gchar **layout,
885 const gchar **variant)
886 {
887 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
888
889 if (!get_layout (chooser, priv->type, priv->id, layout, variant)) {
890 if (layout != NULL)
891 *layout = NULL;
892 if (variant != NULL)
893 *variant = NULL;
894 }
895 }
896
897 void
cc_input_chooser_set_input(CcInputChooser * chooser,const gchar * id,const gchar * type)898 cc_input_chooser_set_input (CcInputChooser *chooser,
899 const gchar *id,
900 const gchar *type)
901 {
902 set_input (chooser, id, type);
903 }
904
905 gboolean
cc_input_chooser_get_showing_extra(CcInputChooser * chooser)906 cc_input_chooser_get_showing_extra (CcInputChooser *chooser)
907 {
908 CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
909 return priv->showing_extra;
910 }
911