GCC Code Coverage Report


Directory: ./
File: panels/keyboard/cc-input-list-box.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 389 0.0%
Functions: 0 45 0.0%
Branches: 0 227 0.0%

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