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