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 |
|
|
|