When the going from the region panel to the overview and back, the input methods in the list loose their display names. What is happening is that we recreate the ibus object, but the second time around it is already connected since ibus uses a shared singleton connection internally. Thus, the ::connected signal is never emitted, and we don't fetch the engines. Work around this by checking if the newly created ibus object is already connected, and directly calling fetch_ibus_engines if it is. To make this work, we have to set up the treeview with its model earlier.
1189 lines
33 KiB
C
1189 lines
33 KiB
C
/*
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
*
|
|
* Written by: Matthias Clasen <mclasen@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gio/gdesktopappinfo.h>
|
|
|
|
#define GNOME_DESKTOP_USE_UNSTABLE_API
|
|
#include <libgnome-desktop/gnome-xkb-info.h>
|
|
|
|
#ifdef HAVE_IBUS
|
|
#include <ibus.h>
|
|
#endif
|
|
|
|
#include "gdm-languages.h"
|
|
#include "gnome-region-panel-input.h"
|
|
|
|
#define WID(s) GTK_WIDGET(gtk_builder_get_object (builder, s))
|
|
|
|
#define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
|
|
|
|
#define KEY_CURRENT_INPUT_SOURCE "current"
|
|
#define KEY_INPUT_SOURCES "sources"
|
|
|
|
#define INPUT_SOURCE_TYPE_XKB "xkb"
|
|
#define INPUT_SOURCE_TYPE_IBUS "ibus"
|
|
|
|
enum {
|
|
NAME_COLUMN,
|
|
TYPE_COLUMN,
|
|
ID_COLUMN,
|
|
SETUP_COLUMN,
|
|
N_COLUMNS
|
|
};
|
|
|
|
static GSettings *input_sources_settings = NULL;
|
|
static GnomeXkbInfo *xkb_info = NULL;
|
|
|
|
#ifdef HAVE_IBUS
|
|
static IBusBus *ibus = NULL;
|
|
static GHashTable *ibus_engines = NULL;
|
|
|
|
static const gchar *supported_ibus_engines[] = {
|
|
"bopomofo",
|
|
"pinyin",
|
|
"chewing",
|
|
"anthy",
|
|
"hangul",
|
|
NULL
|
|
};
|
|
#endif /* HAVE_IBUS */
|
|
|
|
static GtkWidget *input_chooser_new (GtkWindow *main_window,
|
|
GtkListStore *active_sources);
|
|
static gboolean input_chooser_get_selected (GtkWidget *chooser,
|
|
GtkTreeModel **model,
|
|
GtkTreeIter *iter);
|
|
|
|
#ifdef HAVE_IBUS
|
|
static void
|
|
clear_ibus (void)
|
|
{
|
|
g_clear_pointer (&ibus_engines, g_hash_table_destroy);
|
|
g_clear_object (&ibus);
|
|
}
|
|
|
|
static gchar *
|
|
engine_get_display_name (IBusEngineDesc *engine_desc)
|
|
{
|
|
const gchar *name;
|
|
const gchar *language_code;
|
|
gchar *language;
|
|
gchar *display_name;
|
|
|
|
name = ibus_engine_desc_get_longname (engine_desc);
|
|
language_code = ibus_engine_desc_get_language (engine_desc);
|
|
|
|
language = gdm_get_language_from_name (language_code, NULL);
|
|
|
|
display_name = g_strdup_printf ("%s (%s)", language, name);
|
|
|
|
g_free (language);
|
|
|
|
return display_name;
|
|
}
|
|
|
|
static GDesktopAppInfo *
|
|
setup_app_info_for_id (const gchar *id)
|
|
{
|
|
GDesktopAppInfo *app_info;
|
|
gchar *desktop_file_name;
|
|
|
|
desktop_file_name = g_strdup_printf ("ibus-setup-%s.desktop", id);
|
|
app_info = g_desktop_app_info_new (desktop_file_name);
|
|
g_free (desktop_file_name);
|
|
|
|
return app_info;
|
|
}
|
|
|
|
static void
|
|
update_ibus_active_sources (GtkBuilder *builder)
|
|
{
|
|
GtkTreeView *tv;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gchar *type, *id;
|
|
gboolean ret;
|
|
|
|
tv = GTK_TREE_VIEW (WID ("active_input_sources"));
|
|
model = gtk_tree_view_get_model (tv);
|
|
|
|
ret = gtk_tree_model_get_iter_first (model, &iter);
|
|
while (ret)
|
|
{
|
|
gtk_tree_model_get (model, &iter,
|
|
TYPE_COLUMN, &type,
|
|
ID_COLUMN, &id,
|
|
-1);
|
|
|
|
if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
|
|
{
|
|
IBusEngineDesc *engine_desc = NULL;
|
|
gchar *display_name = NULL;
|
|
|
|
engine_desc = g_hash_table_lookup (ibus_engines, id);
|
|
if (engine_desc)
|
|
{
|
|
display_name = engine_get_display_name (engine_desc);
|
|
|
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
|
NAME_COLUMN, display_name,
|
|
-1);
|
|
g_free (display_name);
|
|
}
|
|
}
|
|
|
|
g_free (type);
|
|
g_free (id);
|
|
|
|
ret = gtk_tree_model_iter_next (model, &iter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fetch_ibus_engines (GtkBuilder *builder)
|
|
{
|
|
IBusEngineDesc **engines, **iter;
|
|
const gchar *name;
|
|
|
|
ibus_engines = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
|
|
|
|
engines = ibus_bus_get_engines_by_names (ibus, supported_ibus_engines);
|
|
for (iter = engines; *iter; ++iter)
|
|
{
|
|
name = ibus_engine_desc_get_name (*iter);
|
|
g_hash_table_replace (ibus_engines, (gpointer)name, *iter);
|
|
}
|
|
|
|
g_free (engines);
|
|
|
|
update_ibus_active_sources (builder);
|
|
|
|
/* We've got everything we needed, don't want to be called again. */
|
|
g_signal_handlers_disconnect_by_func (ibus, fetch_ibus_engines, builder);
|
|
}
|
|
|
|
static void
|
|
maybe_start_ibus (void)
|
|
{
|
|
/* IBus doesn't export API in the session bus. The only thing
|
|
* we have there is a well known name which we can use as a
|
|
* sure-fire way to activate it. */
|
|
g_bus_unwatch_name (g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
IBUS_SERVICE_IBUS,
|
|
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL));
|
|
}
|
|
#endif /* HAVE_IBUS */
|
|
|
|
static gboolean
|
|
add_source_to_table (GtkTreeModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer data)
|
|
{
|
|
GHashTable *hash = data;
|
|
gchar *type;
|
|
gchar *id;
|
|
|
|
gtk_tree_model_get (model, iter,
|
|
TYPE_COLUMN, &type,
|
|
ID_COLUMN, &id,
|
|
-1);
|
|
|
|
g_hash_table_add (hash, g_strconcat (type, id, NULL));
|
|
|
|
g_free (type);
|
|
g_free (id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
populate_model (GtkListStore *store,
|
|
GtkListStore *active_sources_store)
|
|
{
|
|
GHashTable *active_sources_table;
|
|
GtkTreeIter iter;
|
|
const gchar *name;
|
|
GList *sources, *tmp;
|
|
gchar *source_id = NULL;
|
|
|
|
active_sources_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
gtk_tree_model_foreach (GTK_TREE_MODEL (active_sources_store),
|
|
add_source_to_table,
|
|
active_sources_table);
|
|
|
|
sources = gnome_xkb_info_get_all_layouts (xkb_info);
|
|
|
|
for (tmp = sources; tmp; tmp = tmp->next)
|
|
{
|
|
g_free (source_id);
|
|
source_id = g_strconcat (INPUT_SOURCE_TYPE_XKB, tmp->data, NULL);
|
|
|
|
if (g_hash_table_contains (active_sources_table, source_id))
|
|
continue;
|
|
|
|
gnome_xkb_info_get_layout_info (xkb_info, (const gchar *)tmp->data,
|
|
&name, NULL, NULL, NULL);
|
|
|
|
gtk_list_store_append (store, &iter);
|
|
gtk_list_store_set (store, &iter,
|
|
NAME_COLUMN, name,
|
|
TYPE_COLUMN, INPUT_SOURCE_TYPE_XKB,
|
|
ID_COLUMN, tmp->data,
|
|
-1);
|
|
}
|
|
g_free (source_id);
|
|
|
|
g_list_free (sources);
|
|
|
|
#ifdef HAVE_IBUS
|
|
if (ibus_engines)
|
|
{
|
|
gchar *display_name;
|
|
|
|
sources = g_hash_table_get_keys (ibus_engines);
|
|
|
|
source_id = NULL;
|
|
for (tmp = sources; tmp; tmp = tmp->next)
|
|
{
|
|
g_free (source_id);
|
|
source_id = g_strconcat (INPUT_SOURCE_TYPE_IBUS, tmp->data, NULL);
|
|
|
|
if (g_hash_table_contains (active_sources_table, source_id))
|
|
continue;
|
|
|
|
display_name = engine_get_display_name (g_hash_table_lookup (ibus_engines, tmp->data));
|
|
|
|
gtk_list_store_append (store, &iter);
|
|
gtk_list_store_set (store, &iter,
|
|
NAME_COLUMN, display_name,
|
|
TYPE_COLUMN, INPUT_SOURCE_TYPE_IBUS,
|
|
ID_COLUMN, tmp->data,
|
|
-1);
|
|
g_free (display_name);
|
|
}
|
|
g_free (source_id);
|
|
|
|
g_list_free (sources);
|
|
}
|
|
#endif
|
|
|
|
g_hash_table_destroy (active_sources_table);
|
|
}
|
|
|
|
static void
|
|
populate_with_active_sources (GtkListStore *store)
|
|
{
|
|
GVariant *sources;
|
|
GVariantIter iter;
|
|
const gchar *name;
|
|
const gchar *type;
|
|
const gchar *id;
|
|
gchar *display_name;
|
|
GDesktopAppInfo *app_info;
|
|
GtkTreeIter tree_iter;
|
|
|
|
sources = g_settings_get_value (input_sources_settings, KEY_INPUT_SOURCES);
|
|
|
|
g_variant_iter_init (&iter, sources);
|
|
while (g_variant_iter_next (&iter, "(&s&s)", &type, &id))
|
|
{
|
|
display_name = NULL;
|
|
app_info = NULL;
|
|
|
|
if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
|
|
{
|
|
gnome_xkb_info_get_layout_info (xkb_info, id, &name, NULL, NULL, NULL);
|
|
if (!name)
|
|
{
|
|
g_warning ("Couldn't find XKB input source '%s'", id);
|
|
continue;
|
|
}
|
|
display_name = g_strdup (name);
|
|
}
|
|
else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
|
|
{
|
|
#ifdef HAVE_IBUS
|
|
IBusEngineDesc *engine_desc = NULL;
|
|
|
|
if (ibus_engines)
|
|
engine_desc = g_hash_table_lookup (ibus_engines, id);
|
|
|
|
if (engine_desc)
|
|
display_name = engine_get_display_name (engine_desc);
|
|
|
|
app_info = setup_app_info_for_id (id);
|
|
#else
|
|
g_warning ("IBus input source type specified but IBus support was not compiled");
|
|
continue;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Unknown input source type '%s'", type);
|
|
continue;
|
|
}
|
|
|
|
gtk_list_store_append (store, &tree_iter);
|
|
gtk_list_store_set (store, &tree_iter,
|
|
NAME_COLUMN, display_name ? display_name : id,
|
|
TYPE_COLUMN, type,
|
|
ID_COLUMN, id,
|
|
SETUP_COLUMN, app_info,
|
|
-1);
|
|
g_free (display_name);
|
|
if (app_info)
|
|
g_object_unref (app_info);
|
|
}
|
|
|
|
g_variant_unref (sources);
|
|
}
|
|
|
|
static void
|
|
update_configuration (GtkTreeModel *model)
|
|
{
|
|
GtkTreeIter iter;
|
|
gchar *type;
|
|
gchar *id;
|
|
GVariantBuilder builder;
|
|
GVariant *old_sources;
|
|
const gchar *old_current_type;
|
|
const gchar *old_current_id;
|
|
guint old_current_index;
|
|
guint index;
|
|
|
|
old_sources = g_settings_get_value (input_sources_settings, KEY_INPUT_SOURCES);
|
|
old_current_index = g_settings_get_uint (input_sources_settings, KEY_CURRENT_INPUT_SOURCE);
|
|
g_variant_get_child (old_sources,
|
|
old_current_index,
|
|
"(&s&s)",
|
|
&old_current_type,
|
|
&old_current_id);
|
|
if (g_variant_n_children (old_sources) < 1)
|
|
{
|
|
g_warning ("No input source configured, resetting");
|
|
g_settings_reset (input_sources_settings, KEY_INPUT_SOURCES);
|
|
goto exit;
|
|
}
|
|
if (old_current_index >= g_variant_n_children (old_sources))
|
|
{
|
|
g_settings_set_uint (input_sources_settings,
|
|
KEY_CURRENT_INPUT_SOURCE,
|
|
g_variant_n_children (old_sources) - 1);
|
|
}
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
|
|
index = 0;
|
|
gtk_tree_model_get_iter_first (model, &iter);
|
|
do
|
|
{
|
|
gtk_tree_model_get (model, &iter,
|
|
TYPE_COLUMN, &type,
|
|
ID_COLUMN, &id,
|
|
-1);
|
|
if (index != old_current_index &&
|
|
g_str_equal (type, old_current_type) &&
|
|
g_str_equal (id, old_current_id))
|
|
{
|
|
g_settings_set_uint (input_sources_settings, KEY_CURRENT_INPUT_SOURCE, index);
|
|
}
|
|
g_variant_builder_add (&builder, "(ss)", type, id);
|
|
g_free (type);
|
|
g_free (id);
|
|
index += 1;
|
|
}
|
|
while (gtk_tree_model_iter_next (model, &iter));
|
|
|
|
g_settings_set_value (input_sources_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
|
|
|
|
exit:
|
|
g_settings_apply (input_sources_settings);
|
|
g_variant_unref (old_sources);
|
|
}
|
|
|
|
static gboolean
|
|
get_selected_iter (GtkBuilder *builder,
|
|
GtkTreeModel **model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkTreeSelection *selection;
|
|
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID ("active_input_sources")));
|
|
|
|
return gtk_tree_selection_get_selected (selection, model, iter);
|
|
}
|
|
|
|
static gint
|
|
idx_from_model_iter (GtkTreeModel *model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkTreePath *path;
|
|
gint idx;
|
|
|
|
path = gtk_tree_model_get_path (model, iter);
|
|
if (path == NULL)
|
|
return -1;
|
|
|
|
idx = gtk_tree_path_get_indices (path)[0];
|
|
gtk_tree_path_free (path);
|
|
|
|
return idx;
|
|
}
|
|
|
|
static void
|
|
update_button_sensitivity (GtkBuilder *builder)
|
|
{
|
|
GtkWidget *remove_button;
|
|
GtkWidget *up_button;
|
|
GtkWidget *down_button;
|
|
GtkWidget *show_button;
|
|
GtkWidget *settings_button;
|
|
GtkTreeView *tv;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gint n_active;
|
|
gint index;
|
|
gboolean settings_sensitive;
|
|
GDesktopAppInfo *app_info;
|
|
|
|
remove_button = WID("input_source_remove");
|
|
show_button = WID("input_source_show");
|
|
up_button = WID("input_source_move_up");
|
|
down_button = WID("input_source_move_down");
|
|
settings_button = WID("input_source_settings");
|
|
|
|
tv = GTK_TREE_VIEW (WID ("active_input_sources"));
|
|
n_active = gtk_tree_model_iter_n_children (gtk_tree_view_get_model (tv), NULL);
|
|
|
|
if (get_selected_iter (builder, &model, &iter))
|
|
{
|
|
index = idx_from_model_iter (model, &iter);
|
|
gtk_tree_model_get (model, &iter, SETUP_COLUMN, &app_info, -1);
|
|
}
|
|
else
|
|
{
|
|
index = -1;
|
|
app_info = NULL;
|
|
}
|
|
|
|
settings_sensitive = (index >= 0 && app_info != NULL);
|
|
|
|
if (app_info)
|
|
g_object_unref (app_info);
|
|
|
|
gtk_widget_set_sensitive (remove_button, index >= 0 && n_active > 1);
|
|
gtk_widget_set_sensitive (show_button, index >= 0);
|
|
gtk_widget_set_sensitive (up_button, index > 0);
|
|
gtk_widget_set_sensitive (down_button, index >= 0 && index < n_active - 1);
|
|
gtk_widget_set_sensitive (settings_button, settings_sensitive);
|
|
}
|
|
|
|
static void
|
|
set_selected_path (GtkBuilder *builder,
|
|
GtkTreePath *path)
|
|
{
|
|
GtkTreeSelection *selection;
|
|
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID ("active_input_sources")));
|
|
|
|
gtk_tree_selection_select_path (selection, path);
|
|
}
|
|
|
|
static void
|
|
chooser_response (GtkWidget *chooser, gint response_id, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
|
|
if (response_id == GTK_RESPONSE_OK)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
if (input_chooser_get_selected (chooser, &model, &iter))
|
|
{
|
|
GtkTreeView *tv;
|
|
GtkListStore *my_model;
|
|
GtkTreeIter child_iter;
|
|
gchar *name;
|
|
gchar *type;
|
|
gchar *id;
|
|
GDesktopAppInfo *app_info = NULL;
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
NAME_COLUMN, &name,
|
|
TYPE_COLUMN, &type,
|
|
ID_COLUMN, &id,
|
|
-1);
|
|
|
|
#ifdef HAVE_IBUS
|
|
if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
|
|
app_info = setup_app_info_for_id (id);
|
|
#endif
|
|
|
|
tv = GTK_TREE_VIEW (WID ("active_input_sources"));
|
|
my_model = GTK_LIST_STORE (gtk_tree_view_get_model (tv));
|
|
|
|
gtk_list_store_insert_with_values (my_model, &child_iter, -1,
|
|
NAME_COLUMN, name,
|
|
TYPE_COLUMN, type,
|
|
ID_COLUMN, id,
|
|
SETUP_COLUMN, app_info,
|
|
-1);
|
|
g_free (name);
|
|
g_free (type);
|
|
g_free (id);
|
|
if (app_info)
|
|
g_object_unref (app_info);
|
|
|
|
gtk_tree_selection_select_iter (gtk_tree_view_get_selection (tv), &child_iter);
|
|
|
|
update_button_sensitivity (builder);
|
|
update_configuration (GTK_TREE_MODEL (my_model));
|
|
}
|
|
else
|
|
{
|
|
g_debug ("nothing selected, nothing added");
|
|
}
|
|
}
|
|
|
|
gtk_widget_destroy (GTK_WIDGET (chooser));
|
|
}
|
|
|
|
static void
|
|
add_input (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkWidget *chooser;
|
|
GtkWidget *toplevel;
|
|
GtkWidget *treeview;
|
|
GtkListStore *active_sources;
|
|
|
|
g_debug ("add an input source");
|
|
|
|
toplevel = gtk_widget_get_toplevel (WID ("region_notebook"));
|
|
treeview = WID ("active_input_sources");
|
|
active_sources = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
|
|
|
|
chooser = input_chooser_new (GTK_WINDOW (toplevel), active_sources);
|
|
g_signal_connect (chooser, "response",
|
|
G_CALLBACK (chooser_response), builder);
|
|
}
|
|
|
|
static void
|
|
remove_selected_input (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
g_debug ("remove selected input source");
|
|
|
|
if (get_selected_iter (builder, &model, &iter) == FALSE)
|
|
return;
|
|
|
|
path = gtk_tree_model_get_path (model, &iter);
|
|
|
|
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
|
|
|
|
if (!gtk_tree_model_get_iter (model, &iter, path))
|
|
gtk_tree_path_prev (path);
|
|
|
|
set_selected_path (builder, path);
|
|
|
|
gtk_tree_path_free (path);
|
|
|
|
update_button_sensitivity (builder);
|
|
update_configuration (model);
|
|
}
|
|
|
|
static void
|
|
move_selected_input_up (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter, prev;
|
|
GtkTreePath *path;
|
|
|
|
g_debug ("move selected input source up");
|
|
|
|
if (!get_selected_iter (builder, &model, &iter))
|
|
return;
|
|
|
|
prev = iter;
|
|
if (!gtk_tree_model_iter_previous (model, &prev))
|
|
return;
|
|
|
|
path = gtk_tree_model_get_path (model, &prev);
|
|
|
|
gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &prev);
|
|
|
|
set_selected_path (builder, path);
|
|
gtk_tree_path_free (path);
|
|
|
|
update_button_sensitivity (builder);
|
|
update_configuration (model);
|
|
}
|
|
|
|
static void
|
|
move_selected_input_down (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter, next;
|
|
GtkTreePath *path;
|
|
|
|
g_debug ("move selected input source down");
|
|
|
|
if (!get_selected_iter (builder, &model, &iter))
|
|
return;
|
|
|
|
next = iter;
|
|
if (!gtk_tree_model_iter_next (model, &next))
|
|
return;
|
|
|
|
path = gtk_tree_model_get_path (model, &next);
|
|
|
|
gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &next);
|
|
|
|
set_selected_path (builder, path);
|
|
gtk_tree_path_free (path);
|
|
|
|
update_button_sensitivity (builder);
|
|
update_configuration (model);
|
|
}
|
|
|
|
static void
|
|
show_selected_layout (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gchar *type;
|
|
gchar *id;
|
|
gchar *kbd_viewer_args;
|
|
const gchar *xkb_layout;
|
|
const gchar *xkb_variant;
|
|
|
|
g_debug ("show selected layout");
|
|
|
|
if (!get_selected_iter (builder, &model, &iter))
|
|
return;
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
TYPE_COLUMN, &type,
|
|
ID_COLUMN, &id,
|
|
-1);
|
|
|
|
if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
|
|
{
|
|
gnome_xkb_info_get_layout_info (xkb_info, id, NULL, NULL, &xkb_layout, &xkb_variant);
|
|
|
|
if (!xkb_layout || !xkb_layout[0])
|
|
{
|
|
g_warning ("Couldn't find XKB input source '%s'", id);
|
|
goto exit;
|
|
}
|
|
}
|
|
else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
|
|
{
|
|
#ifdef HAVE_IBUS
|
|
IBusEngineDesc *engine_desc = NULL;
|
|
|
|
if (ibus_engines)
|
|
engine_desc = g_hash_table_lookup (ibus_engines, id);
|
|
|
|
if (engine_desc)
|
|
{
|
|
xkb_layout = ibus_engine_desc_get_layout (engine_desc);
|
|
xkb_variant = "";
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Couldn't find IBus input source '%s'", id);
|
|
goto exit;
|
|
}
|
|
#else
|
|
g_warning ("IBus input source type specified but IBus support was not compiled");
|
|
goto exit;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Unknown input source type '%s'", type);
|
|
goto exit;
|
|
}
|
|
|
|
if (xkb_variant[0])
|
|
kbd_viewer_args = g_strdup_printf ("gkbd-keyboard-display -l \"%s\t%s\"",
|
|
xkb_layout, xkb_variant);
|
|
else
|
|
kbd_viewer_args = g_strdup_printf ("gkbd-keyboard-display -l %s",
|
|
xkb_layout);
|
|
|
|
g_spawn_command_line_async (kbd_viewer_args, NULL);
|
|
|
|
g_free (kbd_viewer_args);
|
|
exit:
|
|
g_free (type);
|
|
g_free (id);
|
|
}
|
|
|
|
static void
|
|
show_selected_settings (GtkButton *button, gpointer data)
|
|
{
|
|
GtkBuilder *builder = data;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
GdkAppLaunchContext *ctx;
|
|
GDesktopAppInfo *app_info;
|
|
GError *error = NULL;
|
|
|
|
g_debug ("show selected layout");
|
|
|
|
if (!get_selected_iter (builder, &model, &iter))
|
|
return;
|
|
|
|
gtk_tree_model_get (model, &iter, SETUP_COLUMN, &app_info, -1);
|
|
|
|
if (!app_info)
|
|
return;
|
|
|
|
ctx = gdk_display_get_app_launch_context (gdk_display_get_default ());
|
|
gdk_app_launch_context_set_timestamp (ctx, gtk_get_current_event_time ());
|
|
|
|
if (!g_app_info_launch (G_APP_INFO (app_info), NULL, G_APP_LAUNCH_CONTEXT (ctx), &error))
|
|
{
|
|
g_warning ("Failed to launch input source setup: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_object_unref (ctx);
|
|
g_object_unref (app_info);
|
|
}
|
|
|
|
static gboolean
|
|
go_to_shortcuts (GtkLinkButton *button,
|
|
CcRegionPanel *panel)
|
|
{
|
|
CcShell *shell;
|
|
const gchar *argv[] = { "shortcuts", NULL };
|
|
GError *error = NULL;
|
|
|
|
shell = cc_panel_get_shell (CC_PANEL (panel));
|
|
if (!cc_shell_set_active_panel_from_id (shell, "keyboard", argv, &error))
|
|
{
|
|
g_warning ("Failed to activate Keyboard panel: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
input_sources_changed (GSettings *settings,
|
|
gchar *key,
|
|
GtkBuilder *builder)
|
|
{
|
|
GtkWidget *treeview;
|
|
GtkTreeModel *store;
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
GtkTreeModel *model;
|
|
|
|
treeview = WID("active_input_sources");
|
|
store = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
|
|
|
|
if (get_selected_iter (builder, &model, &iter))
|
|
path = gtk_tree_model_get_path (model, &iter);
|
|
else
|
|
path = NULL;
|
|
|
|
gtk_list_store_clear (GTK_LIST_STORE (store));
|
|
populate_with_active_sources (GTK_LIST_STORE (store));
|
|
|
|
if (path)
|
|
{
|
|
set_selected_path (builder, path);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_shortcut_label (GtkWidget *widget,
|
|
const char *value)
|
|
{
|
|
char *text;
|
|
guint accel_key, *keycode;
|
|
GdkModifierType mods;
|
|
|
|
if (value == NULL || *value == '\0')
|
|
{
|
|
gtk_label_set_text (GTK_LABEL (widget), "\342\200\224");
|
|
return;
|
|
}
|
|
gtk_accelerator_parse_with_keycode (value, &accel_key, &keycode, &mods);
|
|
if (accel_key == 0 && keycode == NULL && mods == 0)
|
|
{
|
|
gtk_label_set_text (GTK_LABEL (widget), "\342\200\224");
|
|
g_warning ("Failed to parse keyboard shortcut: '%s'", value);
|
|
return;
|
|
}
|
|
|
|
text = gtk_accelerator_get_label_with_keycode (gtk_widget_get_display (widget), accel_key, *keycode, mods);
|
|
g_free (keycode);
|
|
gtk_label_set_text (GTK_LABEL (widget), text);
|
|
g_free (text);
|
|
}
|
|
|
|
static void
|
|
update_shortcuts (GtkBuilder *builder)
|
|
{
|
|
char *previous, *next;
|
|
GSettings *settings;
|
|
|
|
settings = g_settings_new ("org.gnome.settings-daemon.plugins.media-keys");
|
|
|
|
previous = g_settings_get_string (settings, "switch-input-source-backward");
|
|
next = g_settings_get_string (settings, "switch-input-source");
|
|
|
|
update_shortcut_label (WID ("prev-source-shortcut-label"), previous);
|
|
update_shortcut_label (WID ("next-source-shortcut-label"), next);
|
|
|
|
g_free (previous);
|
|
g_free (next);
|
|
}
|
|
|
|
void
|
|
setup_input_tabs (GtkBuilder *builder,
|
|
CcRegionPanel *panel)
|
|
{
|
|
GtkWidget *treeview;
|
|
GtkTreeViewColumn *column;
|
|
GtkCellRenderer *cell;
|
|
GtkListStore *store;
|
|
GtkTreeSelection *selection;
|
|
|
|
/* set up the list of active inputs */
|
|
treeview = WID("active_input_sources");
|
|
column = gtk_tree_view_column_new ();
|
|
cell = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_column_pack_start (column, cell, TRUE);
|
|
gtk_tree_view_column_add_attribute (column, cell, "text", NAME_COLUMN);
|
|
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
|
|
|
|
store = gtk_list_store_new (N_COLUMNS,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_DESKTOP_APP_INFO);
|
|
|
|
gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
|
|
|
|
input_sources_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
|
|
g_settings_delay (input_sources_settings);
|
|
g_object_weak_ref (G_OBJECT (builder), (GWeakNotify) g_object_unref, input_sources_settings);
|
|
|
|
if (!xkb_info)
|
|
xkb_info = gnome_xkb_info_new ();
|
|
|
|
#ifdef HAVE_IBUS
|
|
ibus_init ();
|
|
if (!ibus)
|
|
{
|
|
ibus = ibus_bus_new_async ();
|
|
if (ibus_bus_is_connected (ibus))
|
|
fetch_ibus_engines (builder);
|
|
else
|
|
g_signal_connect_swapped (ibus, "connected",
|
|
G_CALLBACK (fetch_ibus_engines), builder);
|
|
g_object_weak_ref (G_OBJECT (builder), (GWeakNotify) clear_ibus, NULL);
|
|
}
|
|
maybe_start_ibus ();
|
|
#endif
|
|
|
|
populate_with_active_sources (store);
|
|
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
|
|
g_signal_connect_swapped (selection, "changed",
|
|
G_CALLBACK (update_button_sensitivity), builder);
|
|
|
|
/* set up the buttons */
|
|
g_signal_connect (WID("input_source_add"), "clicked",
|
|
G_CALLBACK (add_input), builder);
|
|
g_signal_connect (WID("input_source_remove"), "clicked",
|
|
G_CALLBACK (remove_selected_input), builder);
|
|
g_signal_connect (WID("input_source_move_up"), "clicked",
|
|
G_CALLBACK (move_selected_input_up), builder);
|
|
g_signal_connect (WID("input_source_move_down"), "clicked",
|
|
G_CALLBACK (move_selected_input_down), builder);
|
|
g_signal_connect (WID("input_source_show"), "clicked",
|
|
G_CALLBACK (show_selected_layout), builder);
|
|
g_signal_connect (WID("input_source_settings"), "clicked",
|
|
G_CALLBACK (show_selected_settings), builder);
|
|
|
|
/* use an em dash is no shortcut */
|
|
update_shortcuts (builder);
|
|
|
|
g_signal_connect (WID("jump-to-shortcuts"), "activate-link",
|
|
G_CALLBACK (go_to_shortcuts), panel);
|
|
|
|
g_signal_connect (G_OBJECT (input_sources_settings),
|
|
"changed::" KEY_INPUT_SOURCES,
|
|
G_CALLBACK (input_sources_changed),
|
|
builder);
|
|
}
|
|
|
|
static void
|
|
filter_clear (GtkEntry *entry,
|
|
GtkEntryIconPosition icon_pos,
|
|
GdkEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
gtk_entry_set_text (entry, "");
|
|
}
|
|
|
|
static gchar **search_pattern_list;
|
|
|
|
static void
|
|
filter_changed (GtkBuilder *builder)
|
|
{
|
|
GtkTreeModelFilter *filtered_model;
|
|
GtkTreeView *tree_view;
|
|
GtkTreeSelection *selection;
|
|
GtkTreeIter selected_iter;
|
|
GtkWidget *filter_entry;
|
|
const gchar *pattern;
|
|
gchar *upattern;
|
|
|
|
filter_entry = WID ("input_source_filter");
|
|
pattern = gtk_entry_get_text (GTK_ENTRY (filter_entry));
|
|
upattern = g_utf8_strup (pattern, -1);
|
|
if (!g_strcmp0 (pattern, ""))
|
|
g_object_set (G_OBJECT (filter_entry),
|
|
"secondary-icon-name", "edit-find-symbolic",
|
|
"secondary-icon-activatable", FALSE,
|
|
"secondary-icon-sensitive", FALSE,
|
|
NULL);
|
|
else
|
|
g_object_set (G_OBJECT (filter_entry),
|
|
"secondary-icon-name", "edit-clear-symbolic",
|
|
"secondary-icon-activatable", TRUE,
|
|
"secondary-icon-sensitive", TRUE,
|
|
NULL);
|
|
|
|
if (search_pattern_list != NULL)
|
|
g_strfreev (search_pattern_list);
|
|
|
|
search_pattern_list = g_strsplit (upattern, " ", -1);
|
|
g_free (upattern);
|
|
|
|
filtered_model = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "filtered_input_source_model"));
|
|
gtk_tree_model_filter_refilter (filtered_model);
|
|
|
|
tree_view = GTK_TREE_VIEW (WID ("filtered_input_source_list"));
|
|
selection = gtk_tree_view_get_selection (tree_view);
|
|
if (gtk_tree_selection_get_selected (selection, NULL, &selected_iter))
|
|
{
|
|
GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (filtered_model),
|
|
&selected_iter);
|
|
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.5);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
else
|
|
{
|
|
GtkTreeIter iter;
|
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filtered_model), &iter))
|
|
gtk_tree_selection_select_iter (selection, &iter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
selection_changed (GtkTreeSelection *selection,
|
|
GtkBuilder *builder)
|
|
{
|
|
gtk_widget_set_sensitive (WID ("ok-button"),
|
|
gtk_tree_selection_get_selected (selection, NULL, NULL));
|
|
}
|
|
|
|
static void
|
|
row_activated (GtkTreeView *tree_view,
|
|
GtkTreePath *path,
|
|
GtkTreeViewColumn *column,
|
|
GtkBuilder *builder)
|
|
{
|
|
GtkWidget *add_button;
|
|
GtkWidget *dialog;
|
|
|
|
add_button = WID ("ok-button");
|
|
dialog = WID ("input_source_chooser");
|
|
if (gtk_widget_is_sensitive (add_button))
|
|
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
|
|
}
|
|
|
|
static void
|
|
entry_activated (GtkBuilder *builder,
|
|
gpointer data)
|
|
{
|
|
row_activated (NULL, NULL, NULL, builder);
|
|
}
|
|
|
|
static gboolean
|
|
filter_func (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data)
|
|
{
|
|
gchar *name = NULL;
|
|
gchar **pattern;
|
|
gboolean rv = TRUE;
|
|
|
|
if (search_pattern_list == NULL || search_pattern_list[0] == NULL)
|
|
return TRUE;
|
|
|
|
gtk_tree_model_get (model, iter,
|
|
NAME_COLUMN, &name,
|
|
-1);
|
|
|
|
pattern = search_pattern_list;
|
|
do {
|
|
gboolean is_pattern_found = FALSE;
|
|
gchar *udesc = g_utf8_strup (name, -1);
|
|
if (udesc != NULL && g_strstr_len (udesc, -1, *pattern))
|
|
{
|
|
is_pattern_found = TRUE;
|
|
}
|
|
g_free (udesc);
|
|
|
|
if (!is_pattern_found)
|
|
{
|
|
rv = FALSE;
|
|
break;
|
|
}
|
|
|
|
} while (*++pattern != NULL);
|
|
|
|
g_free (name);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static GtkWidget *
|
|
input_chooser_new (GtkWindow *main_window,
|
|
GtkListStore *active_sources)
|
|
{
|
|
GtkBuilder *builder;
|
|
GtkWidget *chooser;
|
|
GtkWidget *filtered_list;
|
|
GtkWidget *filter_entry;
|
|
GtkTreeViewColumn *visible_column;
|
|
GtkTreeSelection *selection;
|
|
GtkListStore *model;
|
|
GtkTreeModelFilter *filtered_model;
|
|
GtkTreeIter iter;
|
|
|
|
builder = gtk_builder_new ();
|
|
gtk_builder_add_from_file (builder,
|
|
GNOMECC_UI_DIR "/gnome-region-panel-input-chooser.ui",
|
|
NULL);
|
|
chooser = WID ("input_source_chooser");
|
|
g_object_set_data_full (G_OBJECT (chooser), "builder", builder, g_object_unref);
|
|
|
|
filtered_list = WID ("filtered_input_source_list");
|
|
filter_entry = WID ("input_source_filter");
|
|
|
|
g_object_set_data (G_OBJECT (chooser),
|
|
"filtered_input_source_list", filtered_list);
|
|
visible_column =
|
|
gtk_tree_view_column_new_with_attributes ("Input Sources",
|
|
gtk_cell_renderer_text_new (),
|
|
"text", NAME_COLUMN,
|
|
NULL);
|
|
|
|
gtk_window_set_transient_for (GTK_WINDOW (chooser), main_window);
|
|
|
|
gtk_tree_view_append_column (GTK_TREE_VIEW (filtered_list),
|
|
visible_column);
|
|
/* We handle searching ourselves, thank you. */
|
|
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (filtered_list), FALSE);
|
|
gtk_tree_view_set_search_column (GTK_TREE_VIEW (filtered_list), -1);
|
|
|
|
g_signal_connect_swapped (G_OBJECT (filter_entry), "activate",
|
|
G_CALLBACK (entry_activated), builder);
|
|
g_signal_connect_swapped (G_OBJECT (filter_entry), "notify::text",
|
|
G_CALLBACK (filter_changed), builder);
|
|
|
|
g_signal_connect (G_OBJECT (filter_entry), "icon-release",
|
|
G_CALLBACK (filter_clear), NULL);
|
|
|
|
filtered_model = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "filtered_input_source_model"));
|
|
model = GTK_LIST_STORE (gtk_builder_get_object (builder, "input_source_model"));
|
|
|
|
populate_model (model, active_sources);
|
|
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
|
|
NAME_COLUMN, GTK_SORT_ASCENDING);
|
|
|
|
gtk_tree_model_filter_set_visible_func (filtered_model,
|
|
filter_func,
|
|
NULL, NULL);
|
|
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (filtered_list));
|
|
|
|
g_signal_connect (G_OBJECT (selection), "changed",
|
|
G_CALLBACK (selection_changed), builder);
|
|
|
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filtered_model), &iter))
|
|
gtk_tree_selection_select_iter (selection, &iter);
|
|
|
|
g_signal_connect (G_OBJECT (filtered_list), "row-activated",
|
|
G_CALLBACK (row_activated), builder);
|
|
|
|
gtk_widget_grab_focus (filter_entry);
|
|
|
|
gtk_widget_show (chooser);
|
|
|
|
return chooser;
|
|
}
|
|
|
|
static gboolean
|
|
input_chooser_get_selected (GtkWidget *dialog,
|
|
GtkTreeModel **model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkWidget *tv;
|
|
GtkTreeSelection *selection;
|
|
|
|
tv = g_object_get_data (G_OBJECT (dialog), "filtered_input_source_list");
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
|
|
|
|
return gtk_tree_selection_get_selected (selection, model, iter);
|
|
}
|