1 /* cc-input-list-box.c
2  *
3  * Copyright (C) 2010 Intel, Inc
4  * Copyright (C) 2020 System76, Inc.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Author: Sergey Udaltsov   <svu@gnome.org>
20  *         Ian Douglas Scott <idscott@system76.com>
21  *
22  * SPDX-License-Identifier: GPL-2.0-or-later
23  */
24 
25 #define GNOME_DESKTOP_USE_UNSTABLE_API
26 #include <libgnome-desktop/gnome-xkb-info.h>
27 
28 #include "list-box-helper.h"
29 #include "cc-input-list-box.h"
30 #include "cc-input-chooser.h"
31 #include "cc-input-row.h"
32 #include "cc-input-source-ibus.h"
33 #include "cc-input-source-xkb.h"
34 
35 #ifdef HAVE_IBUS
36 #include <ibus.h>
37 #endif
38 
39 #define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
40 #define KEY_INPUT_SOURCES        "sources"
41 
42 struct _CcInputListBox {
43   GtkListBox       parent_instance;
44 
45   GtkListBoxRow   *add_input_row;
46   GtkSizeGroup    *input_size_group;
47   GtkListBoxRow   *no_inputs_row;
48 
49   GCancellable    *cancellable;
50 
51   gboolean     login;
52   gboolean     login_auto_apply;
53   GPermission *permission;
54   GDBusProxy  *localed;
55 
56   GSettings *input_settings;
57   GnomeXkbInfo *xkb_info;
58 #ifdef HAVE_IBUS
59   IBusBus *ibus;
60   GHashTable *ibus_engines;
61 #endif
62 };
63 
64 G_DEFINE_TYPE (CcInputListBox, cc_input_list_box, GTK_TYPE_LIST_BOX)
65 
66 typedef struct
67 {
68   CcInputListBox *panel;
69   CcInputRow    *source;
70   CcInputRow    *dest;
71 } RowData;
72 
73 static RowData *
row_data_new(CcInputListBox * panel,CcInputRow * source,CcInputRow * dest)74 row_data_new (CcInputListBox *panel, CcInputRow *source, CcInputRow *dest)
75 {
76   RowData *data = g_malloc0 (sizeof (RowData));
77   data->panel = panel;
78   data->source = g_object_ref (source);
79   if (dest != NULL)
80     data->dest = g_object_ref (dest);
81   return data;
82 }
83 
84 static void
row_data_free(RowData * data)85 row_data_free (RowData *data)
86 {
87   g_clear_object (&data->source);
88   g_clear_object (&data->dest);
89   g_free (data);
90 }
91 
92 G_DEFINE_AUTOPTR_CLEANUP_FUNC (RowData, row_data_free)
93 
94 static void show_input_chooser (CcInputListBox *self);
95 
96 #ifdef HAVE_IBUS
97 static void
update_ibus_active_sources(CcInputListBox * self)98 update_ibus_active_sources (CcInputListBox *self)
99 {
100   g_autoptr(GList) rows = NULL;
101   GList *l;
102 
103   rows = gtk_container_get_children (GTK_CONTAINER (self));
104   for (l = rows; l; l = l->next) {
105     CcInputRow *row;
106     CcInputSourceIBus *source;
107     IBusEngineDesc *engine_desc;
108 
109     if (!CC_IS_INPUT_ROW (l->data))
110       continue;
111     row = CC_INPUT_ROW (l->data);
112 
113     if (!CC_IS_INPUT_SOURCE_IBUS (cc_input_row_get_source (row)))
114       continue;
115     source = CC_INPUT_SOURCE_IBUS (cc_input_row_get_source (row));
116 
117     engine_desc = g_hash_table_lookup (self->ibus_engines, cc_input_source_ibus_get_engine_name (source));
118     if (engine_desc != NULL)
119       cc_input_source_ibus_set_engine_desc (source, engine_desc);
120   }
121 }
122 
123 static void
fetch_ibus_engines_result(GObject * object,GAsyncResult * result,CcInputListBox * self)124 fetch_ibus_engines_result (GObject       *object,
125                            GAsyncResult  *result,
126                            CcInputListBox *self)
127 {
128   g_autoptr(GList) list = NULL;
129   GList *l;
130   g_autoptr(GError) error = NULL;
131 
132   list = ibus_bus_list_engines_async_finish (IBUS_BUS (object), result, &error);
133   if (!list && error) {
134     if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
135 	g_warning ("Couldn't finish IBus request: %s", error->message);
136       return;
137   }
138 
139   /* Maps engine ids to engine description objects */
140   self->ibus_engines = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
141 
142   for (l = list; l; l = l->next) {
143     IBusEngineDesc *engine = l->data;
144     const gchar *engine_id = ibus_engine_desc_get_name (engine);
145 
146     if (g_str_has_prefix (engine_id, "xkb:"))
147       g_object_unref (engine);
148     else
149       g_hash_table_replace (self->ibus_engines, (gpointer)engine_id, engine);
150   }
151 
152   update_ibus_active_sources (self);
153 }
154 
155 static void
fetch_ibus_engines(CcInputListBox * self)156 fetch_ibus_engines (CcInputListBox *self)
157 {
158   ibus_bus_list_engines_async (self->ibus,
159 			       -1,
160 			       self->cancellable,
161 			       (GAsyncReadyCallback)fetch_ibus_engines_result,
162 			       self);
163 
164   /* We've got everything we needed, don't want to be called again. */
165   g_signal_handlers_disconnect_by_func (self->ibus, fetch_ibus_engines, self);
166 }
167 
168 static void
maybe_start_ibus(void)169 maybe_start_ibus (void)
170 {
171   /* IBus doesn't export API in the session bus. The only thing
172    * we have there is a well known name which we can use as a
173    * sure-fire way to activate it.
174    */
175   g_bus_unwatch_name (g_bus_watch_name (G_BUS_TYPE_SESSION,
176 					IBUS_SERVICE_IBUS,
177 					G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
178 					NULL,
179 					NULL,
180 					NULL,
181 					NULL));
182 }
183 
184 #endif
185 
186 static void
row_settings_cb(CcInputListBox * self,CcInputRow * row)187 row_settings_cb (CcInputListBox *self,
188                  CcInputRow    *row)
189 {
190   CcInputSourceIBus *source;
191   g_autoptr(GdkAppLaunchContext) ctx = NULL;
192   GDesktopAppInfo *app_info;
193   g_autoptr(GError) error = NULL;
194 
195   g_return_if_fail (CC_IS_INPUT_SOURCE_IBUS (cc_input_row_get_source (row)));
196   source = CC_INPUT_SOURCE_IBUS (cc_input_row_get_source (row));
197 
198   app_info = cc_input_source_ibus_get_app_info (source);
199   if  (app_info == NULL)
200     return;
201 
202   ctx = gdk_display_get_app_launch_context (gdk_display_get_default ());
203   gdk_app_launch_context_set_timestamp (ctx, gtk_get_current_event_time ());
204 
205   g_app_launch_context_setenv (G_APP_LAUNCH_CONTEXT (ctx),
206 			       "IBUS_ENGINE_NAME", cc_input_source_ibus_get_engine_name (source));
207 
208   if (!g_app_info_launch (G_APP_INFO (app_info), NULL, G_APP_LAUNCH_CONTEXT (ctx), &error))
209     g_warning ("Failed to launch input source setup: %s", error->message);
210 }
211 
212 static void
row_layout_cb(CcInputListBox * self,CcInputRow * row)213 row_layout_cb (CcInputListBox *self,
214                CcInputRow    *row)
215 {
216   CcInputSource *source;
217   const gchar *layout, *layout_variant;
218   g_autofree gchar *commandline = NULL;
219 
220   source = cc_input_row_get_source (row);
221 
222   layout = cc_input_source_get_layout (source);
223   layout_variant = cc_input_source_get_layout_variant (source);
224 
225   if (layout_variant && layout_variant[0])
226     commandline = g_strdup_printf ("gkbd-keyboard-display -l \"%s\t%s\"",
227 				   layout, layout_variant);
228   else
229     commandline = g_strdup_printf ("gkbd-keyboard-display -l %s",
230 				   layout);
231 
232   g_spawn_command_line_async (commandline, NULL);
233 }
234 
235 static void move_input (CcInputListBox *self, CcInputRow *source, CcInputRow *dest);
236 
237 static void
row_moved_cb(CcInputListBox * self,CcInputRow * dest_row,CcInputRow * row)238 row_moved_cb (CcInputListBox *self,
239               CcInputRow    *dest_row,
240               CcInputRow    *row)
241 {
242   move_input (self, row, dest_row);
243 }
244 
245 static void remove_input (CcInputListBox *self, CcInputRow *row);
246 
247 static void
row_removed_cb(CcInputListBox * self,CcInputRow * row)248 row_removed_cb (CcInputListBox *self,
249                 CcInputRow    *row)
250 {
251   remove_input (self, row);
252 }
253 
254 static void
update_input_rows(CcInputListBox * self)255 update_input_rows (CcInputListBox *self)
256 {
257   g_autoptr(GList) rows = NULL;
258   GList *l;
259   guint n_input_rows = 0;
260 
261   rows = gtk_container_get_children (GTK_CONTAINER (self));
262   for (l = rows; l; l = l->next)
263     if (CC_IS_INPUT_ROW (l->data))
264       n_input_rows++;
265   for (l = rows; l; l = l->next) {
266     CcInputRow *row;
267 
268     if (!CC_IS_INPUT_ROW (l->data))
269       continue;
270     row = CC_INPUT_ROW (l->data);
271 
272     cc_input_row_set_removable (row, n_input_rows > 1);
273     cc_input_row_set_draggable (row, n_input_rows > 1);
274   }
275 }
276 
277 static void
add_input_row(CcInputListBox * self,CcInputSource * source)278 add_input_row (CcInputListBox *self, CcInputSource *source)
279 {
280   CcInputRow *row;
281 
282   gtk_widget_set_visible (GTK_WIDGET (self->no_inputs_row), FALSE);
283 
284   row = cc_input_row_new (source);
285   gtk_widget_show (GTK_WIDGET (row));
286   gtk_size_group_add_widget (self->input_size_group, GTK_WIDGET (row));
287   g_signal_connect_object (row, "show-settings", G_CALLBACK (row_settings_cb), self, G_CONNECT_SWAPPED);
288   g_signal_connect_object (row, "show-layout", G_CALLBACK (row_layout_cb), self, G_CONNECT_SWAPPED);
289   g_signal_connect_object (row, "move-row", G_CALLBACK (row_moved_cb), self, G_CONNECT_SWAPPED);
290   g_signal_connect_object (row, "remove-row", G_CALLBACK (row_removed_cb), self, G_CONNECT_SWAPPED);
291   gtk_list_box_insert (GTK_LIST_BOX (self), GTK_WIDGET (row), gtk_list_box_row_get_index (self->add_input_row));
292   update_input_rows (self);
293 }
294 
295 static void
add_input_sources(CcInputListBox * self,GVariant * sources)296 add_input_sources (CcInputListBox *self,
297                    GVariant      *sources)
298 {
299   GVariantIter iter;
300   const gchar *type, *id;
301 
302   if (g_variant_n_children (sources) < 1) {
303     gtk_widget_set_visible (GTK_WIDGET (self->no_inputs_row), TRUE);
304     return;
305   }
306 
307   g_variant_iter_init (&iter, sources);
308   while (g_variant_iter_next (&iter, "(&s&s)", &type, &id)) {
309     g_autoptr(CcInputSource) source = NULL;
310 
311     if (g_str_equal (type, "xkb")) {
312       source = CC_INPUT_SOURCE (cc_input_source_xkb_new_from_id (self->xkb_info, id));
313     } else if (g_str_equal (type, "ibus")) {
314       source = CC_INPUT_SOURCE (cc_input_source_ibus_new (id));
315 #ifdef HAVE_IBUS
316       if (self->ibus_engines) {
317 	IBusEngineDesc *engine_desc = g_hash_table_lookup (self->ibus_engines, id);
318 	if (engine_desc != NULL)
319 	  cc_input_source_ibus_set_engine_desc (CC_INPUT_SOURCE_IBUS (source), engine_desc);
320 	}
321 #endif
322     } else {
323       g_warning ("Unhandled input source type '%s'", type);
324       continue;
325     }
326 
327     add_input_row (self, source);
328   }
329 }
330 
331 static void
add_input_sources_from_settings(CcInputListBox * self)332 add_input_sources_from_settings (CcInputListBox *self)
333 {
334   g_autoptr(GVariant) sources = NULL;
335   sources = g_settings_get_value (self->input_settings, "sources");
336   add_input_sources (self, sources);
337 }
338 
339 static void
clear_input_sources(CcInputListBox * self)340 clear_input_sources (CcInputListBox *self)
341 {
342   g_autoptr(GList) list = NULL;
343   GList *l;
344 
345   list = gtk_container_get_children (GTK_CONTAINER (self));
346   for (l = list; l; l = l->next) {
347     if (CC_IS_INPUT_ROW (l->data))
348       gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (l->data));
349   }
350 
351   cc_list_box_adjust_scrolling (GTK_LIST_BOX (self));
352 }
353 
354 static CcInputRow *
get_row_by_source(CcInputListBox * self,CcInputSource * source)355 get_row_by_source (CcInputListBox *self, CcInputSource *source)
356 {
357   g_autoptr(GList) list = NULL;
358   GList *l;
359 
360   list = gtk_container_get_children (GTK_CONTAINER (self));
361   for (l = list; l; l = l->next) {
362     CcInputRow *row;
363 
364     if (!CC_IS_INPUT_ROW (l->data))
365       continue;
366     row = CC_INPUT_ROW (l->data);
367 
368     if (cc_input_source_matches (source, cc_input_row_get_source (row)))
369       return row;
370   }
371 
372   return NULL;
373 }
374 
375 static void
input_sources_changed(CcInputListBox * self,const gchar * key)376 input_sources_changed (CcInputListBox *self,
377                        const gchar   *key)
378 {
379   CcInputRow *selected;
380   g_autoptr(CcInputSource) source = NULL;
381 
382   selected = CC_INPUT_ROW (gtk_list_box_get_selected_row (GTK_LIST_BOX (self)));
383   if (selected)
384     source = g_object_ref (cc_input_row_get_source (selected));
385   clear_input_sources (self);
386   add_input_sources_from_settings (self);
387   if (source != NULL) {
388     CcInputRow *row = get_row_by_source (self, source);
389     if (row != NULL)
390       gtk_list_box_select_row (GTK_LIST_BOX (self), GTK_LIST_BOX_ROW (row));
391   }
392 }
393 
394 static void
set_input_settings(CcInputListBox * self)395 set_input_settings (CcInputListBox *self)
396 {
397   GVariantBuilder builder;
398   g_autoptr(GList) list = NULL;
399   GList *l;
400 
401   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
402   list = gtk_container_get_children (GTK_CONTAINER (self));
403   for (l = list; l; l = l->next) {
404     CcInputRow *row;
405     CcInputSource *source;
406 
407     if (!CC_IS_INPUT_ROW (l->data))
408       continue;
409     row = CC_INPUT_ROW (l->data);
410     source = cc_input_row_get_source (row);
411 
412     if (CC_IS_INPUT_SOURCE_XKB (source)) {
413       g_autofree gchar *id = cc_input_source_xkb_get_id (CC_INPUT_SOURCE_XKB (source));
414       g_variant_builder_add (&builder, "(ss)", "xkb", id);
415     } else if (CC_IS_INPUT_SOURCE_IBUS (source)) {
416       g_variant_builder_add (&builder, "(ss)", "ibus",
417 			     cc_input_source_ibus_get_engine_name (CC_INPUT_SOURCE_IBUS (source)));
418     }
419   }
420 
421   g_settings_set_value (self->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
422 }
423 
424 static void set_localed_input (CcInputListBox *self);
425 
426 static void
update_input(CcInputListBox * self)427 update_input (CcInputListBox *self)
428 {
429   if (self->login) {
430     set_localed_input (self);
431   } else {
432     set_input_settings (self);
433     if (self->login_auto_apply)
434       set_localed_input (self);
435   }
436 }
437 
438 static void
show_input_chooser(CcInputListBox * self)439 show_input_chooser (CcInputListBox *self)
440 {
441   CcInputChooser *chooser;
442 
443   chooser = cc_input_chooser_new (self->login,
444 				  self->xkb_info,
445 #ifdef HAVE_IBUS
446 				  self->ibus_engines
447 #else
448 				  NULL
449 #endif
450 				  );
451   gtk_window_set_transient_for (GTK_WINDOW (chooser), GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
452 
453   if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_OK) {
454     CcInputSource *source;
455 
456     source = cc_input_chooser_get_source (chooser);
457     if (source != NULL && get_row_by_source (self, source) == NULL) {
458       add_input_row (self, source);
459       update_input (self);
460     }
461   }
462   gtk_widget_destroy (GTK_WIDGET (chooser));
463 }
464 
465 // Duplicated from cc-region-panel.c
466 static gboolean
permission_acquired(GPermission * permission,GAsyncResult * res,const gchar * action)467 permission_acquired (GPermission *permission, GAsyncResult *res, const gchar *action)
468 {
469   g_autoptr(GError) error = NULL;
470 
471   if (!g_permission_acquire_finish (permission, res, &error)) {
472     if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
473       g_warning ("Failed to acquire permission to %s: %s\n", error->message, action);
474     return FALSE;
475   }
476 
477   return FALSE;
478 }
479 
480 static void
add_input_permission_cb(GObject * source,GAsyncResult * res,gpointer user_data)481 add_input_permission_cb (GObject *source, GAsyncResult *res, gpointer user_data)
482 {
483   CcInputListBox *self = user_data;
484   if (permission_acquired (G_PERMISSION (source), res, "add input"))
485     show_input_chooser (self);
486 }
487 
488 static void
add_input(CcInputListBox * self)489 add_input (CcInputListBox *self)
490 {
491   if (!self->login) {
492     show_input_chooser (self);
493   } else if (g_permission_get_allowed (self->permission)) {
494     show_input_chooser (self);
495   } else if (g_permission_get_can_acquire (self->permission)) {
496     g_permission_acquire_async (self->permission,
497 				self->cancellable,
498 				add_input_permission_cb,
499 				self);
500   }
501 }
502 
503 static GtkWidget *
find_sibling(GtkContainer * container,GtkWidget * child)504 find_sibling (GtkContainer *container, GtkWidget *child)
505 {
506   g_autoptr(GList) list = NULL;
507   GList *c, *l;
508   GtkWidget *sibling;
509 
510   list = gtk_container_get_children (container);
511   c = g_list_find (list, child);
512 
513   for (l = c->next; l; l = l->next) {
514     sibling = l->data;
515     if (gtk_widget_get_visible (sibling) && gtk_widget_get_child_visible (sibling))
516       return sibling;
517   }
518 
519   for (l = c->prev; l; l = l->prev) {
520     sibling = l->data;
521     if (gtk_widget_get_visible (sibling) && gtk_widget_get_child_visible (sibling))
522       return sibling;
523   }
524 
525   return NULL;
526 }
527 
528 static void
do_remove_input(CcInputListBox * self,CcInputRow * row)529 do_remove_input (CcInputListBox *self, CcInputRow *row)
530 {
531         GtkWidget *sibling;
532 
533         sibling = find_sibling (GTK_CONTAINER (self), GTK_WIDGET (row));
534         gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (row));
535         gtk_list_box_select_row (GTK_LIST_BOX (self), GTK_LIST_BOX_ROW (sibling));
536 
537         cc_list_box_adjust_scrolling (GTK_LIST_BOX(self));
538 
539         update_input (self);
540         update_input_rows (self);
541 }
542 
543 static void
remove_input_permission_cb(GObject * source,GAsyncResult * res,gpointer user_data)544 remove_input_permission_cb (GObject *source, GAsyncResult *res, gpointer user_data)
545 {
546   RowData *data = user_data;
547   if (permission_acquired (G_PERMISSION (source), res, "remove input"))
548     do_remove_input (data->panel, data->source);
549 }
550 
551 static void
remove_input(CcInputListBox * self,CcInputRow * row)552 remove_input (CcInputListBox *self, CcInputRow *row)
553 {
554   if (!self->login) {
555     do_remove_input (self, row);
556   } else if (g_permission_get_allowed (self->permission)) {
557     do_remove_input (self, row);
558   } else if (g_permission_get_can_acquire (self->permission)) {
559     g_permission_acquire_async (self->permission,
560 				self->cancellable,
561 				remove_input_permission_cb,
562 				row_data_new (self, row, NULL));
563   }
564 }
565 
566 static void
do_move_input(CcInputListBox * self,CcInputRow * source,CcInputRow * dest)567 do_move_input (CcInputListBox *self, CcInputRow *source, CcInputRow *dest)
568 {
569   gint dest_index;
570 
571   dest_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (dest));
572 
573   g_object_ref (source);
574   gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (source));
575   gtk_list_box_insert (GTK_LIST_BOX (self), GTK_WIDGET (source), dest_index);
576   g_object_unref (source);
577 
578   cc_list_box_adjust_scrolling (GTK_LIST_BOX (self));
579 
580   update_input (self);
581 }
582 
583 static void
move_input_permission_cb(GObject * source,GAsyncResult * res,gpointer user_data)584 move_input_permission_cb (GObject *source, GAsyncResult *res, gpointer user_data)
585 {
586   RowData *data = user_data;
587   if (permission_acquired (G_PERMISSION (source), res, "move input"))
588     do_move_input (data->panel, data->source, data->dest);
589 }
590 
591 static void
move_input(CcInputListBox * self,CcInputRow * source,CcInputRow * dest)592 move_input (CcInputListBox *self,
593             CcInputRow    *source,
594             CcInputRow    *dest)
595 {
596   if (!self->login) {
597     do_move_input (self, source, dest);
598   } else if (g_permission_get_allowed (self->permission)) {
599     do_move_input (self, source, dest);
600   } else if (g_permission_get_can_acquire (self->permission)) {
601     g_permission_acquire_async (self->permission,
602 				self->cancellable,
603 				move_input_permission_cb,
604 				row_data_new (self, source, dest));
605   }
606 }
607 
608 static void
input_row_activated_cb(CcInputListBox * self,GtkListBoxRow * row)609 input_row_activated_cb (CcInputListBox *self, GtkListBoxRow *row)
610 {
611   if (row == self->add_input_row) {
612     add_input (self);
613   }
614 }
615 
616 static void
add_input_sources_from_localed(CcInputListBox * self)617 add_input_sources_from_localed (CcInputListBox *self)
618 {
619   g_autoptr(GVariant) layout_property = NULL;
620   g_autoptr(GVariant) variant_property = NULL;
621   const gchar *s;
622   g_auto(GStrv) layouts = NULL;
623   g_auto(GStrv) variants = NULL;
624   gint i, n;
625 
626   if (!self->localed)
627     return;
628 
629   layout_property = g_dbus_proxy_get_cached_property (self->localed, "X11Layout");
630   if (layout_property) {
631     s = g_variant_get_string (layout_property, NULL);
632     layouts = g_strsplit (s, ",", -1);
633   }
634 
635   variant_property = g_dbus_proxy_get_cached_property (self->localed, "X11Variant");
636   if (variant_property) {
637     s = g_variant_get_string (variant_property, NULL);
638     if (s && *s)
639       variants = g_strsplit (s, ",", -1);
640   }
641 
642   if (variants && variants[0])
643     n = MIN (g_strv_length (layouts), g_strv_length (variants));
644   else if (layouts && layouts[0])
645     n = g_strv_length (layouts);
646   else
647     n = 0;
648 
649   for (i = 0; i < n && layouts[i][0]; i++) {
650     const char *variant = variants ? variants[i] : NULL;
651     g_autoptr(CcInputSourceXkb) source = cc_input_source_xkb_new (self->xkb_info, layouts[i], variant);
652     add_input_row (self, CC_INPUT_SOURCE (source));
653   }
654   gtk_widget_set_visible (GTK_WIDGET (self->no_inputs_row), n == 0);
655 }
656 
657 static void
set_localed_input(CcInputListBox * self)658 set_localed_input (CcInputListBox *self)
659 {
660   g_autoptr(GString) layouts = NULL;
661   g_autoptr(GString) variants = NULL;
662   g_autoptr(GList) list = NULL;
663   GList *li;
664 
665   layouts = g_string_new ("");
666   variants = g_string_new ("");
667 
668   list = gtk_container_get_children (GTK_CONTAINER (self));
669   for (li = list; li; li = li->next) {
670     CcInputRow *row;
671     CcInputSourceXkb *source;
672     g_autofree gchar *id = NULL;
673     const gchar *l, *v;
674 
675     if (!CC_IS_INPUT_ROW (li->data))
676       continue;
677     row = CC_INPUT_ROW (li->data);
678 
679     if (!CC_IS_INPUT_SOURCE_XKB (cc_input_row_get_source (row)))
680       continue;
681     source = CC_INPUT_SOURCE_XKB (cc_input_row_get_source (row));
682 
683     id = cc_input_source_xkb_get_id (source);
684     if (gnome_xkb_info_get_layout_info (self->xkb_info, id, NULL, NULL, &l, &v)) {
685       if (layouts->str[0]) {
686         g_string_append_c (layouts, ',');
687         g_string_append_c (variants, ',');
688       }
689       g_string_append (layouts, l);
690       g_string_append (variants, v);
691     }
692   }
693 
694   g_dbus_proxy_call (self->localed,
695                      "SetX11Keyboard",
696                      g_variant_new ("(ssssbb)", layouts->str, "", variants->str, "", TRUE, TRUE),
697                      G_DBUS_CALL_FLAGS_NONE,
698                      -1, NULL, NULL, NULL);
699 }
700 
701 static void
cc_input_list_box_finalize(GObject * object)702 cc_input_list_box_finalize (GObject *object)
703 {
704   CcInputListBox *self = CC_INPUT_LIST_BOX (object);
705 
706   g_cancellable_cancel (self->cancellable);
707 
708   g_clear_object (&self->input_settings);
709   g_clear_object (&self->xkb_info);
710 #ifdef HAVE_IBUS
711   g_clear_object (&self->ibus);
712   g_clear_pointer (&self->ibus_engines, g_hash_table_destroy);
713 #endif
714 
715   G_OBJECT_CLASS (cc_input_list_box_parent_class)->finalize (object);
716 }
717 
718 static void
cc_input_list_box_class_init(CcInputListBoxClass * klass)719 cc_input_list_box_class_init (CcInputListBoxClass *klass)
720 {
721   GObjectClass *object_class = G_OBJECT_CLASS (klass);
722   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
723 
724   object_class->finalize = cc_input_list_box_finalize;
725 
726   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-input-list-box.ui");
727 
728   gtk_widget_class_bind_template_child (widget_class, CcInputListBox, add_input_row);
729   gtk_widget_class_bind_template_child (widget_class, CcInputListBox, input_size_group);
730   gtk_widget_class_bind_template_child (widget_class, CcInputListBox, no_inputs_row);
731 
732   gtk_widget_class_bind_template_callback (widget_class, input_row_activated_cb);
733 }
734 
735 static void
cc_input_list_box_init(CcInputListBox * self)736 cc_input_list_box_init (CcInputListBox *self)
737 {
738   gtk_widget_init_template (GTK_WIDGET (self));
739 
740   self->login = FALSE;
741   self->login_auto_apply = FALSE;
742   self->localed = NULL;
743   self->permission = NULL;
744 
745   self->cancellable = g_cancellable_new();
746 
747   self->input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
748 
749   self->xkb_info = gnome_xkb_info_new ();
750 
751 #ifdef HAVE_IBUS
752   ibus_init ();
753   if (!self->ibus) {
754     self->ibus = ibus_bus_new_async ();
755     if (ibus_bus_is_connected (self->ibus))
756       fetch_ibus_engines (self);
757     else
758       g_signal_connect_object (self->ibus, "connected",
759                                G_CALLBACK (fetch_ibus_engines), self,
760                                G_CONNECT_SWAPPED);
761   }
762   maybe_start_ibus ();
763 #endif
764 
765   g_signal_connect_object (self->input_settings, "changed::" KEY_INPUT_SOURCES,
766                            G_CALLBACK (input_sources_changed), self, G_CONNECT_SWAPPED);
767 
768   add_input_sources_from_settings (self);
769 }
770 
771 void
cc_input_list_box_set_login(CcInputListBox * self,gboolean login)772 cc_input_list_box_set_login (CcInputListBox *self, gboolean login)
773 {
774   self->login = login;
775   clear_input_sources (self);
776   if (login)
777     add_input_sources_from_localed (self);
778   else
779     add_input_sources_from_settings (self);
780 }
781 
782 void
cc_input_list_box_set_login_auto_apply(CcInputListBox * self,gboolean login_auto_apply)783 cc_input_list_box_set_login_auto_apply (CcInputListBox *self, gboolean login_auto_apply)
784 {
785   self->login_auto_apply = login_auto_apply;
786 }
787 
788 void
cc_input_list_box_set_localed(CcInputListBox * self,GDBusProxy * localed)789 cc_input_list_box_set_localed (CcInputListBox *self, GDBusProxy *localed)
790 {
791   self->localed = localed;
792 }
793 
794 void
cc_input_list_box_set_permission(CcInputListBox * self,GPermission * permission)795 cc_input_list_box_set_permission (CcInputListBox *self, GPermission *permission)
796 {
797   self->permission = permission;
798 }
799