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