1 /*
2 * Copyright (C) 2010 Intel, Inc
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU 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 * Author: Sergey Udaltsov <svu@gnome.org>
18 * Michael Wood <michael.g.wood@intel.com>
19 *
20 * Based on gnome-control-center cc-region-panel.c
21 */
22
23 #define PAGE_ID "keyboard"
24
25 #include "config.h"
26
27 #include <locale.h>
28 #include <glib/gi18n.h>
29 #include <gio/gio.h>
30 #include <gtk/gtk.h>
31 #include <polkit/polkit.h>
32
33 #define GNOME_DESKTOP_USE_UNSTABLE_API
34 #include <libgnome-desktop/gnome-languages.h>
35
36 #include "gis-keyboard-page.h"
37 #include "keyboard-resources.h"
38 #include "cc-input-chooser.h"
39
40 #include "cc-common-language.h"
41
42 #include "gis-page-header.h"
43
44 #define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
45 #define KEY_CURRENT_INPUT_SOURCE "current"
46 #define KEY_INPUT_SOURCES "sources"
47
48 struct _GisKeyboardPagePrivate {
49 GtkWidget *input_chooser;
50
51 GDBusProxy *localed;
52 GCancellable *cancellable;
53 GPermission *permission;
54 GSettings *input_settings;
55
56 GSList *system_sources;
57 };
58 typedef struct _GisKeyboardPagePrivate GisKeyboardPagePrivate;
59
60 G_DEFINE_TYPE_WITH_PRIVATE (GisKeyboardPage, gis_keyboard_page, GIS_TYPE_PAGE);
61
62 static void
gis_keyboard_page_finalize(GObject * object)63 gis_keyboard_page_finalize (GObject *object)
64 {
65 GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object);
66 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
67
68 if (priv->cancellable)
69 g_cancellable_cancel (priv->cancellable);
70 g_clear_object (&priv->cancellable);
71
72 g_clear_object (&priv->permission);
73 g_clear_object (&priv->localed);
74 g_clear_object (&priv->input_settings);
75
76 g_slist_free_full (priv->system_sources, g_free);
77
78 G_OBJECT_CLASS (gis_keyboard_page_parent_class)->finalize (object);
79 }
80
81 static void
set_input_settings(GisKeyboardPage * self)82 set_input_settings (GisKeyboardPage *self)
83 {
84 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
85 const gchar *type;
86 const gchar *id;
87 GVariantBuilder builder;
88 GSList *l;
89 gboolean is_xkb_source = FALSE;
90
91 type = cc_input_chooser_get_input_type (CC_INPUT_CHOOSER (priv->input_chooser));
92 id = cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser));
93
94 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
95
96 if (g_str_equal (type, "xkb")) {
97 g_variant_builder_add (&builder, "(ss)", type, id);
98 is_xkb_source = TRUE;
99 }
100
101 for (l = priv->system_sources; l; l = l->next) {
102 const gchar *sid = l->data;
103
104 if (g_str_equal (id, sid) && g_str_equal (type, "xkb"))
105 continue;
106
107 g_variant_builder_add (&builder, "(ss)", "xkb", sid);
108 }
109
110 if (!is_xkb_source)
111 g_variant_builder_add (&builder, "(ss)", type, id);
112
113 g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
114 g_settings_set_uint (priv->input_settings, KEY_CURRENT_INPUT_SOURCE, 0);
115
116 g_settings_apply (priv->input_settings);
117 }
118
119 static void
set_localed_input(GisKeyboardPage * self)120 set_localed_input (GisKeyboardPage *self)
121 {
122 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
123 const gchar *layout, *variant;
124 GString *layouts;
125 GString *variants;
126 GSList *l;
127
128 if (!priv->localed)
129 return;
130
131 cc_input_chooser_get_layout (CC_INPUT_CHOOSER (priv->input_chooser), &layout, &variant);
132 if (layout == NULL)
133 layout = "";
134 if (variant == NULL)
135 variant = "";
136
137 layouts = g_string_new (layout);
138 variants = g_string_new (variant);
139
140 #define LAYOUT(a) (a[0])
141 #define VARIANT(a) (a[1] ? a[1] : "")
142 for (l = priv->system_sources; l; l = l->next) {
143 const gchar *sid = l->data;
144 gchar **lv = g_strsplit (sid, "+", -1);
145
146 if (!g_str_equal (LAYOUT (lv), layout) ||
147 !g_str_equal (VARIANT (lv), variant)) {
148 if (layouts->str[0]) {
149 g_string_append_c (layouts, ',');
150 g_string_append_c (variants, ',');
151 }
152 g_string_append (layouts, LAYOUT (lv));
153 g_string_append (variants, VARIANT (lv));
154 }
155 g_strfreev (lv);
156 }
157 #undef LAYOUT
158 #undef VARIANT
159
160 g_dbus_proxy_call (priv->localed,
161 "SetX11Keyboard",
162 g_variant_new ("(ssssbb)", layouts->str, "", variants->str, "", TRUE, TRUE),
163 G_DBUS_CALL_FLAGS_NONE,
164 -1, NULL, NULL, NULL);
165 g_string_free (layouts, TRUE);
166 g_string_free (variants, TRUE);
167 }
168
169 static void
change_locale_permission_acquired(GObject * source,GAsyncResult * res,gpointer data)170 change_locale_permission_acquired (GObject *source,
171 GAsyncResult *res,
172 gpointer data)
173 {
174 GisKeyboardPage *page = GIS_KEYBOARD_PAGE (data);
175 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (page);
176 GError *error = NULL;
177 gboolean allowed;
178
179 allowed = g_permission_acquire_finish (priv->permission, res, &error);
180 if (error) {
181 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
182 g_warning ("Failed to acquire permission: %s", error->message);
183 g_error_free (error);
184 return;
185 }
186
187 if (allowed)
188 set_localed_input (GIS_KEYBOARD_PAGE (data));
189 }
190
191 static void
update_input(GisKeyboardPage * self)192 update_input (GisKeyboardPage *self)
193 {
194 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
195
196 set_input_settings (self);
197
198 if (priv->permission != NULL) {
199 if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) {
200 if (g_permission_get_allowed (priv->permission)) {
201 set_localed_input (self);
202 } else if (g_permission_get_can_acquire (priv->permission)) {
203 g_permission_acquire_async (priv->permission,
204 NULL,
205 change_locale_permission_acquired,
206 self);
207 }
208 }
209 }
210 }
211
212 static gboolean
gis_keyboard_page_apply(GisPage * page,GCancellable * cancellable)213 gis_keyboard_page_apply (GisPage *page,
214 GCancellable *cancellable)
215 {
216 update_input (GIS_KEYBOARD_PAGE (page));
217 return FALSE;
218 }
219
220 static GSList *
get_localed_input(GDBusProxy * proxy)221 get_localed_input (GDBusProxy *proxy)
222 {
223 GVariant *v;
224 const gchar *s;
225 gchar *id;
226 guint i, n;
227 gchar **layouts = NULL;
228 gchar **variants = NULL;
229 GSList *sources = NULL;
230
231 v = g_dbus_proxy_get_cached_property (proxy, "X11Layout");
232 if (v) {
233 s = g_variant_get_string (v, NULL);
234 layouts = g_strsplit (s, ",", -1);
235 g_variant_unref (v);
236 }
237
238 v = g_dbus_proxy_get_cached_property (proxy, "X11Variant");
239 if (v) {
240 s = g_variant_get_string (v, NULL);
241 if (s && *s)
242 variants = g_strsplit (s, ",", -1);
243 g_variant_unref (v);
244 }
245
246 if (variants && variants[0])
247 n = MIN (g_strv_length (layouts), g_strv_length (variants));
248 else if (layouts && layouts[0])
249 n = g_strv_length (layouts);
250 else
251 n = 0;
252
253 for (i = 0; i < n && layouts[i][0]; i++) {
254 if (variants && variants[i] && variants[i][0])
255 id = g_strdup_printf ("%s+%s", layouts[i], variants[i]);
256 else
257 id = g_strdup (layouts[i]);
258 sources = g_slist_prepend (sources, id);
259 }
260
261 g_strfreev (variants);
262 g_strfreev (layouts);
263
264 return sources;
265 }
266
267 static void
add_default_keyboard_layout(GDBusProxy * proxy,GVariantBuilder * builder)268 add_default_keyboard_layout (GDBusProxy *proxy,
269 GVariantBuilder *builder)
270 {
271 GSList *sources = get_localed_input (proxy);
272 sources = g_slist_reverse (sources);
273
274 for (; sources; sources = sources->next)
275 g_variant_builder_add (builder, "(ss)", "xkb",
276 (const gchar *) sources->data);
277
278 g_slist_free_full (sources, g_free);
279 }
280
281 static void
add_default_input_sources(GisKeyboardPage * self,GDBusProxy * proxy)282 add_default_input_sources (GisKeyboardPage *self,
283 GDBusProxy *proxy)
284 {
285 const gchar *type;
286 const gchar *id;
287 gchar *language;
288 GVariantBuilder builder;
289 GSettings *input_settings;
290
291 input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
292 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
293
294 add_default_keyboard_layout (proxy, &builder);
295
296 /* add other input sources */
297 language = cc_common_language_get_current_language ();
298 if (gnome_get_input_source_from_locale (language, &type, &id)) {
299 if (!g_str_equal (type, "xkb"))
300 g_variant_builder_add (&builder, "(ss)", type, id);
301 }
302 g_free (language);
303
304 g_settings_delay (input_settings);
305 g_settings_set_value (input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
306 g_settings_set_uint (input_settings, KEY_CURRENT_INPUT_SOURCE, 0);
307 g_settings_apply (input_settings);
308
309 g_object_unref (input_settings);
310 }
311
312 static void
skip_proxy_ready(GObject * source,GAsyncResult * res,gpointer data)313 skip_proxy_ready (GObject *source,
314 GAsyncResult *res,
315 gpointer data)
316 {
317 GisKeyboardPage *self = data;
318 GDBusProxy *proxy;
319 GError *error = NULL;
320
321 proxy = g_dbus_proxy_new_finish (res, &error);
322
323 if (!proxy) {
324 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
325 g_warning ("Failed to contact localed: %s", error->message);
326 g_error_free (error);
327 return;
328 }
329
330 add_default_input_sources (self, proxy);
331
332 g_object_unref (proxy);
333 }
334
335 static void
gis_keyboard_page_skip(GisPage * page)336 gis_keyboard_page_skip (GisPage *page)
337 {
338 GisKeyboardPage *self = GIS_KEYBOARD_PAGE (page);
339 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
340
341 g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
342 G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
343 NULL,
344 "org.freedesktop.locale1",
345 "/org/freedesktop/locale1",
346 "org.freedesktop.locale1",
347 priv->cancellable,
348 (GAsyncReadyCallback) skip_proxy_ready,
349 self);
350 }
351
352 static void
preselect_input_source(GisKeyboardPage * self)353 preselect_input_source (GisKeyboardPage *self)
354 {
355 const gchar *type;
356 const gchar *id;
357 gchar *language;
358 gboolean desktop_got_something;
359 gboolean desktop_got_input_method;
360
361 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
362 GSList *sources = get_localed_input (priv->localed);
363
364 /* These will be added silently after the user selection when
365 * writing out the settings. */
366 g_slist_free_full (priv->system_sources, g_free);
367 priv->system_sources = g_slist_reverse (sources);
368
369 /* We have two potential sources of information as to which
370 * source to pre-select here: the keyboard layout that is
371 * configured system-wide (read from priv->system_sources),
372 * and a gnome-desktop function that lets us look up a default
373 * input source for a given language.
374 *
375 * An important limitation here is that there is no system-wide
376 * configuration for input methods, so if the best choice for the
377 * language is an input method, we will only find it from the
378 * gnome-desktop lookup. But if both sources give us keyboard layouts,
379 * we want to prefer the one that's configured system-wide over the one
380 * from gnome-desktop.
381 *
382 * So we first do the gnome-desktop lookup, and keep track of what we
383 * got.
384 *
385 * - If we got an input method, we preselect that, and we're done.
386 * - If we got a keyboard layout, and there's no system-wide keyboard
387 * layout set, we preselect the layout we got from gnome-desktop.
388 * - If we didn't get an input method from gnome-desktop and there
389 * is a system-wide keyboard layout set, we preselect that.
390 * - If we got nothing from gnome-desktop and there's no system-wide
391 * keyboard layout set, we don't preselect anything.
392 *
393 * See:
394 * - https://bugzilla.gnome.org/show_bug.cgi?id=776189
395 * - https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/104
396 */
397 language = cc_common_language_get_current_language ();
398
399 desktop_got_something = gnome_get_input_source_from_locale (language, &type, &id);
400 desktop_got_input_method = (desktop_got_something && g_strcmp0 (type, "xkb") != 0);
401
402 if (desktop_got_something && (desktop_got_input_method || !priv->system_sources)) {
403 cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser),
404 id, type);
405 } else if (priv->system_sources) {
406 cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser),
407 (const gchar *) priv->system_sources->data,
408 "xkb");
409 }
410
411 g_free (language);
412 }
413
414 static void
update_page_complete(GisKeyboardPage * self)415 update_page_complete (GisKeyboardPage *self)
416 {
417 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
418 gboolean complete;
419
420 complete = (priv->localed != NULL &&
421 cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL);
422 gis_page_set_complete (GIS_PAGE (self), complete);
423 }
424
425 static void
localed_proxy_ready(GObject * source,GAsyncResult * res,gpointer data)426 localed_proxy_ready (GObject *source,
427 GAsyncResult *res,
428 gpointer data)
429 {
430 GisKeyboardPage *self = data;
431 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
432 GDBusProxy *proxy;
433 GError *error = NULL;
434
435 proxy = g_dbus_proxy_new_finish (res, &error);
436
437 if (!proxy) {
438 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
439 g_warning ("Failed to contact localed: %s", error->message);
440 g_error_free (error);
441 return;
442 }
443
444 priv->localed = proxy;
445
446 preselect_input_source (self);
447 update_page_complete (self);
448 }
449
450 static void
input_confirmed(CcInputChooser * chooser,GisKeyboardPage * self)451 input_confirmed (CcInputChooser *chooser,
452 GisKeyboardPage *self)
453 {
454 gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (self)->driver));
455 }
456
457 static void
input_changed(CcInputChooser * chooser,GisKeyboardPage * self)458 input_changed (CcInputChooser *chooser,
459 GisKeyboardPage *self)
460 {
461 update_page_complete (self);
462 }
463
464 static void
gis_keyboard_page_constructed(GObject * object)465 gis_keyboard_page_constructed (GObject *object)
466 {
467 GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object);
468 GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
469
470 g_type_ensure (CC_TYPE_INPUT_CHOOSER);
471
472 G_OBJECT_CLASS (gis_keyboard_page_parent_class)->constructed (object);
473
474 g_signal_connect (priv->input_chooser, "confirm",
475 G_CALLBACK (input_confirmed), self);
476 g_signal_connect (priv->input_chooser, "changed",
477 G_CALLBACK (input_changed), self);
478
479 priv->input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
480 g_settings_delay (priv->input_settings);
481
482 priv->cancellable = g_cancellable_new ();
483
484 g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
485 G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
486 NULL,
487 "org.freedesktop.locale1",
488 "/org/freedesktop/locale1",
489 "org.freedesktop.locale1",
490 priv->cancellable,
491 (GAsyncReadyCallback) localed_proxy_ready,
492 self);
493
494 /* If we're in new user mode then we're manipulating system settings */
495 if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER)
496 priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-keyboard", NULL, NULL, NULL);
497
498 update_page_complete (self);
499
500 gtk_widget_show (GTK_WIDGET (self));
501 }
502
503 static void
gis_keyboard_page_locale_changed(GisPage * page)504 gis_keyboard_page_locale_changed (GisPage *page)
505 {
506 gis_page_set_title (GIS_PAGE (page), _("Typing"));
507 }
508
509 static void
gis_keyboard_page_class_init(GisKeyboardPageClass * klass)510 gis_keyboard_page_class_init (GisKeyboardPageClass * klass)
511 {
512 GObjectClass *object_class = G_OBJECT_CLASS (klass);
513 GisPageClass * page_class = GIS_PAGE_CLASS (klass);
514
515 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-keyboard-page.ui");
516
517 gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisKeyboardPage, input_chooser);
518
519 page_class->page_id = PAGE_ID;
520 page_class->apply = gis_keyboard_page_apply;
521 page_class->skip = gis_keyboard_page_skip;
522 page_class->locale_changed = gis_keyboard_page_locale_changed;
523 object_class->constructed = gis_keyboard_page_constructed;
524 object_class->finalize = gis_keyboard_page_finalize;
525 }
526
527 static void
gis_keyboard_page_init(GisKeyboardPage * self)528 gis_keyboard_page_init (GisKeyboardPage *self)
529 {
530 g_resources_register (keyboard_get_resource ());
531 g_type_ensure (GIS_TYPE_PAGE_HEADER);
532 g_type_ensure (CC_TYPE_INPUT_CHOOSER);
533
534 gtk_widget_init_template (GTK_WIDGET (self));
535 }
536
537 GisPage *
gis_prepare_keyboard_page(GisDriver * driver)538 gis_prepare_keyboard_page (GisDriver *driver)
539 {
540 return g_object_new (GIS_TYPE_KEYBOARD_PAGE,
541 "driver", driver,
542 NULL);
543 }
544