Files
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
init
2026-02-16 15:50:16 +03:00

1475 lines
44 KiB
C

/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
/*
* nimf-settings.c
* This file is part of Nimf.
*
* Copyright (C) 2016-2019 Hodong Kim <cogniti@gmail.com>
*
* Nimf is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nimf 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libxklavier/xklavier.h>
#include <gdk/gdkx.h>
#include "config.h"
#include "nimf.h"
#include "nimf-enum-types-private.h"
#include "nimf-utils-private.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define EXPORT_ENVIRONMENT "export $(/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)"
#define INPUT_CONF "50-input.conf"
#define NIMF_TYPE_SETTINGS (nimf_settings_get_type ())
#define NIMF_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NIMF_TYPE_SETTINGS, NimfSettings))
#define NIMF_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NIMF_TYPE_SETTINGS, NimfSettingsClass))
#define NIMF_IS_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NIMF_TYPE_SETTINGS))
#define NIMF_IS_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NIMF_TYPE_SETTINGS))
#define NIMF_SETTINGS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NIMF_TYPE_SETTINGS, NimfSettingsClass))
typedef struct _NimfXkb
{
XklEngine *engine;
GtkWidget *options_box;
GtkWidget *vbox;
gchar **options;
gint options_len;
GSList *toggle_buttons;
GSList *radio_group;
} NimfXkb;
typedef struct _NimfSettings NimfSettings;
typedef struct _NimfSettingsClass NimfSettingsClass;
struct _NimfSettings
{
GObject parent_instance;
GApplication *app;
};
struct _NimfSettingsClass
{
GObjectClass parent_class;
};
GType nimf_settings_get_type (void) G_GNUC_CONST;
enum {
SCHEMA_COLUMN = 0
};
typedef struct _NimfSettingsPage
{
GSettings *gsettings;
GtkWidget *box;
GPtrArray *page_keys;
} NimfSettingsPage;
typedef struct _NimfSettingsPageKey {
GSettings *gsettings;
gchar *key;
GtkWidget *label;
GtkWidget *treeview;
} NimfSettingsPageKey;
static GtkWidget *nimf_settings_window = NULL;
G_DEFINE_TYPE (NimfSettings, nimf_settings, G_TYPE_OBJECT);
static gboolean
on_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer user_data)
{
gchar *id;
GtkWidget *widget = user_data;
gint retval;
gtk_tree_model_get (model, iter, 1, &id, -1);
retval = g_strcmp0 (id, gtk_widget_get_name (widget));
g_free (id);
if (!retval)
{
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), iter);
return TRUE;
}
return FALSE;
}
static void
on_gsettings_changed (GSettings *settings,
gchar *key,
GtkWidget *widget)
{
if (GTK_IS_SWITCH (widget))
{
gboolean active1 = g_settings_get_boolean (settings, key);
gboolean active2 = gtk_switch_get_active (GTK_SWITCH (widget));
if (active1 != active2)
gtk_switch_set_active (GTK_SWITCH (widget), active1);
}
else if (GTK_IS_COMBO_BOX (widget))
{
gchar *id;
gboolean retval;
id = g_settings_get_string (settings, key);
retval = gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), id);
GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
if (!retval)
{
gtk_widget_set_name (widget, id);
gtk_tree_model_foreach (model, on_foreach, widget);
}
if (retval == FALSE && g_strcmp0 (key, "default-engine") == 0)
g_settings_set_string (settings, key, "nimf-system-keyboard");
g_free (id);
}
else if (GTK_IS_TREE_VIEW (widget))
{
GtkTreeModel *model;
gchar **vals;
GtkTreeIter iter;
gint i;
vals = g_settings_get_strv (settings, key);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
gtk_list_store_clear (GTK_LIST_STORE (model));
for (i = 0; vals[i] != NULL; i++)
{
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, vals[i], -1);
}
g_strfreev (vals);
}
}
static gint
on_comparison (const char *a,
const char *b)
{
if (g_str_has_prefix (a, "org.nimf.engines.") &&
g_str_has_prefix (b, "org.nimf.engines."))
{
GSettingsSchemaSource *source;
GSettingsSchemaKey *key_a, *key_b;
GSettingsSchema *schema_a, *schema_b;
GVariant *variant_a, *variant_b;
gchar *value_a, *value_b;
gint retval;
source = g_settings_schema_source_get_default ();
schema_a = g_settings_schema_source_lookup (source, a, FALSE);
if (schema_a == NULL)
{
g_warning (G_STRLOC ": %s: %s is not found.", G_STRFUNC, a);
return g_strcmp0 (a, b);
}
schema_b = g_settings_schema_source_lookup (source, b, FALSE);
if (schema_b == NULL)
{
g_warning (G_STRLOC ": %s: %s is not found.", G_STRFUNC, b);
return g_strcmp0 (a, b);
}
key_a = g_settings_schema_get_key (schema_a, "hidden-schema-name");
key_b = g_settings_schema_get_key (schema_b, "hidden-schema-name");
variant_a = g_settings_schema_key_get_default_value (key_a);
variant_b = g_settings_schema_key_get_default_value (key_b);
value_a = g_strdup (g_variant_get_string (variant_a, NULL));
value_b = g_strdup (g_variant_get_string (variant_b, NULL));
retval = g_utf8_collate (value_a, value_b);
g_free (value_a);
g_free (value_b);
g_variant_unref (variant_a);
g_variant_unref (variant_b);
g_settings_schema_key_unref (key_a);
g_settings_schema_key_unref (key_b);
g_settings_schema_unref (schema_a);
g_settings_schema_unref (schema_b);
return retval;
}
return g_strcmp0 (a, b);
}
static void
on_combo_box_changed (GtkComboBox *widget,
NimfSettingsPageKey *page_key)
{
const gchar *id1;
gchar *id2;
id1 = gtk_combo_box_get_active_id (GTK_COMBO_BOX (widget));
id2 = g_settings_get_string (page_key->gsettings, page_key->key);
if (id1 && g_strcmp0 (id1, id2) != 0)
g_settings_set_string (page_key->gsettings, page_key->key, id1);
g_free (id2);
}
static gboolean
on_finding (gconstpointer a,
gconstpointer b)
{
return !g_strcmp0 (a, b);
}
static gboolean
on_active_engine_state_set (GtkSwitch *widget,
gboolean state,
NimfSettingsPageKey *page_key)
{
if (state != g_settings_get_boolean (page_key->gsettings, page_key->key))
{
const gchar *engine_id;
GPtrArray *engine_ids;
GSettings *settings;
gchar **strv;
gint i;
engine_ids = g_ptr_array_new ();
settings = g_settings_new ("org.nimf");
engine_id = gtk_widget_get_name (GTK_WIDGET (widget));
strv = g_settings_get_strv (settings, "hidden-active-engines");
for (i = 0; strv[i]; i++)
g_ptr_array_add (engine_ids, strv[i]);
if (state)
{
if (!g_ptr_array_find_with_equal_func (engine_ids, engine_id, (GEqualFunc) on_finding, NULL))
g_ptr_array_add (engine_ids, g_strdup (engine_id));
}
else
{
guint index;
if (g_ptr_array_find_with_equal_func (engine_ids, engine_id, (GEqualFunc) on_finding, &index))
g_ptr_array_remove_index_fast (engine_ids, index);
}
g_free (strv);
g_ptr_array_add (engine_ids, NULL);
strv = (gchar **) g_ptr_array_free (engine_ids, FALSE);
g_settings_set_strv (settings, "hidden-active-engines", (const gchar *const *) strv);
g_settings_set_boolean (page_key->gsettings, page_key->key, state);
g_strfreev (strv);
g_object_unref (settings);
}
return FALSE;
}
static void
on_notify_active (GtkSwitch *widget,
GParamSpec *pspec,
NimfSettingsPageKey *page_key)
{
gboolean active1;
gboolean active2;
active1 = gtk_switch_get_active (GTK_SWITCH (widget));
active2 = g_settings_get_boolean (page_key->gsettings, page_key->key);
if (active1 != active2)
g_settings_set_boolean (page_key->gsettings, page_key->key, active1);
}
static NimfSettingsPageKey *
nimf_settings_page_key_new (GSettings *gsettings,
const gchar *key,
const gchar *summary,
const gchar *desc)
{
NimfSettingsPageKey *page_key;
page_key = g_slice_new (NimfSettingsPageKey);
page_key->gsettings = gsettings;
page_key->key = g_strdup (key);
page_key->label = gtk_label_new (summary);
gtk_widget_set_halign (page_key->label, GTK_ALIGN_START);
gtk_widget_set_tooltip_text (page_key->label, desc);
return page_key;
}
static void
nimf_settings_page_key_free (NimfSettingsPageKey *page_key)
{
g_free (page_key->key);
g_slice_free (NimfSettingsPageKey, page_key);
}
static GtkWidget *
nimf_settings_page_key_build_boolean (NimfSettingsPageKey *page_key,
const gchar *schema_id)
{
GtkWidget *gswitch;
GtkWidget *hbox;
gchar *detailed_signal;
gboolean is_active;
gswitch = gtk_switch_new ();
if (g_str_has_prefix (schema_id, "org.nimf.engines."))
gtk_widget_set_name (gswitch, schema_id + strlen ("org.nimf.engines."));
is_active = g_settings_get_boolean (page_key->gsettings, page_key->key);
gtk_switch_set_active (GTK_SWITCH (gswitch), is_active);
gtk_widget_set_halign (gswitch, GTK_ALIGN_END);
detailed_signal = g_strdup_printf ("changed::%s", page_key->key);
g_signal_connect (gswitch, "notify::active",
G_CALLBACK (on_notify_active), page_key);
if (!g_strcmp0 (page_key->key, "active-engine"))
g_signal_connect (gswitch, "state-set",
G_CALLBACK (on_active_engine_state_set), page_key);
g_signal_connect (page_key->gsettings, detailed_signal,
G_CALLBACK (on_gsettings_changed), gswitch);
g_free (detailed_signal);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 15);
gtk_box_pack_start (GTK_BOX (hbox), page_key->label, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), gswitch, FALSE, FALSE, 0);
return hbox;
}
static GtkWidget *
nimf_settings_page_key_build_string (NimfSettingsPageKey *page_key,
const gchar *schema_id,
GList *key_list)
{
GtkTreeStore *store;
GtkWidget *combo;
GtkWidget *hbox;
gchar *detailed_signal;
GtkTreeIter iter;
store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
combo = gtk_combo_box_text_new ();
gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store));
g_object_unref (store);
gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), 1);
gtk_tree_model_get_iter_first ((GtkTreeModel *) store, &iter);
if (!g_strcmp0 (schema_id, "org.nimf.engines") &&
!g_strcmp0 (page_key->key, "default-engine"))
{
GSettingsSchemaSource *schema_source;
GList *schema_list = NULL;
gchar **schemas;
gchar *id1;
gint i;
id1 = g_settings_get_string (page_key->gsettings, page_key->key);
schema_source = g_settings_schema_source_get_default ();
g_settings_schema_source_list_schemas (schema_source, TRUE,
&schemas, NULL);
for (i = 0; schemas[i] != NULL; i++)
if (g_str_has_prefix (schemas[i], "org.nimf.engines."))
schema_list = g_list_prepend (schema_list, schemas[i]);
for (schema_list = g_list_sort (schema_list, (GCompareFunc) on_comparison);
schema_list != NULL;
schema_list = schema_list->next)
{
GSettingsSchema *schema;
GSettings *gsettings;
gchar *name;
const gchar *id2;
schema = g_settings_schema_source_lookup (schema_source,
schema_list->data, TRUE);
gsettings = g_settings_new (schema_list->data);
name = g_settings_get_string (gsettings, "hidden-schema-name");
id2 = schema_list->data + strlen ("org.nimf.engines.");
if (g_settings_schema_has_key (schema, "active-engine") == FALSE ||
g_settings_get_boolean (gsettings, "active-engine"))
{
gtk_tree_store_append (store, &iter, NULL);
gtk_tree_store_set (store, &iter, 0, name, 1, id2, -1);
}
g_settings_schema_unref (schema);
g_free (name);
g_object_unref (gsettings);
}
if (gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), id1) == FALSE)
{
g_settings_set_string (page_key->gsettings, "default-engine",
"nimf-system-keyboard");
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo),
"nimf-system-keyboard");
}
g_strfreev (schemas);
g_list_free (schema_list);
g_free (id1);
}
else if (g_str_has_prefix (page_key->key, "hidden-") == FALSE)
{
gchar *id1;
id1 = g_settings_get_string (page_key->gsettings, page_key->key);
if (g_str_has_prefix (page_key->key, "get-"))
{
const gchar *engine_id;
GModule *module;
NimfMethodInfo ** (* function) ();
gchar *path;
gchar *symbol_name;
gchar *p;
engine_id = schema_id + strlen ("org.nimf.engines.");
path = g_module_build_path (NIMF_MODULE_DIR, engine_id);
module = g_module_open (path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
symbol_name = g_strdup_printf ("%s_%s", engine_id, page_key->key);
for (p = symbol_name; *p; p++)
if (*p == '-')
*p = '_';
if (g_module_symbol (module, symbol_name, (gpointer *) &function))
{
GtkTreeIter parent;
NimfMethodInfo **infos = function ();
const gchar *prev_group = NULL;
gint i;
for (i = 0; infos[i]; i++)
{
if (infos[i]->group && g_strcmp0 (infos[i]->group, prev_group))
{
gtk_tree_store_append (store, &parent, NULL);
gtk_tree_store_set (store, &parent, 0, infos[i]->group, -1);
}
if (infos[i]->group)
gtk_tree_store_append (store, &iter, &parent);
else
gtk_tree_store_append (store, &iter, NULL);
gtk_tree_store_set (store, &iter, 0, infos[i]->label,
1, infos[i]->method_id, -1);
if (g_strcmp0 (id1, infos[i]->method_id) == 0)
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
prev_group = infos[i]->group;
}
nimf_method_info_freev (infos);
}
else
{
g_warning (G_STRLOC ": %s", g_module_error ());
}
g_free (symbol_name);
g_module_close (module);
g_free (path);
}
g_free (id1);
}
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 15);
gtk_box_pack_start (GTK_BOX (hbox), page_key->label, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
detailed_signal = g_strdup_printf ("changed::%s", page_key->key);
g_signal_connect (combo, "changed",
G_CALLBACK (on_combo_box_changed), page_key);
g_signal_connect (page_key->gsettings, detailed_signal,
G_CALLBACK (on_gsettings_changed), combo);
g_free (detailed_signal);
return hbox;
}
static gboolean
on_key_press_event (GtkWidget *widget,
GdkEvent *event,
GtkWidget *dialog)
{
const gchar *keystr;
GString *combination;
GFlagsClass *flags_class; /* do not free */
guint mod;
guint i;
combination = g_string_new ("");
mod = event->key.state & NIMF_MODIFIER_MASK;
flags_class = (GFlagsClass *) g_type_class_ref (NIMF_TYPE_MODIFIER_TYPE);
/* does not use virtual modifiers */
for (i = 0; i <= 12; i++)
{
GFlagsValue *flags_value = g_flags_get_first_value (flags_class,
mod & (1 << i));
if (flags_value)
g_string_append_printf (combination, "%s ", flags_value->value_nick);
}
g_type_class_unref (flags_class);
keystr = nimf_keyval_to_keysym_name (event->key.keyval);
if (keystr == NULL)
{
gchar *text;
g_string_free (combination, TRUE);
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
GTK_RESPONSE_OK, FALSE);
text = g_strdup_printf (_("Please report a bug. (keyval: %d)"),
event->key.keyval);
gtk_entry_set_text (GTK_ENTRY (widget), text);
g_free (text);
g_return_val_if_fail (keystr, TRUE);
}
g_string_append_printf (combination, "%s", keystr);
gtk_entry_set_text (GTK_ENTRY (widget), combination->str);
gtk_editable_set_position (GTK_EDITABLE (widget), -1);
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
GTK_RESPONSE_OK, TRUE);
g_string_free (combination, TRUE);
return TRUE;
}
static void
nimf_settings_page_key_update_gsettings_strv (NimfSettingsPageKey *page_key,
GtkTreeModel *model)
{
gchar **vals;
GtkTreeIter iter;
gint i;
vals = g_malloc0_n (1, sizeof (gchar *));
if (gtk_tree_model_get_iter_first (model, &iter))
{
i = 0;
do {
gtk_tree_model_get (model, &iter, 0, &vals[i], -1);
vals = g_realloc_n (vals, sizeof (gchar *), i + 2);
vals[i + 1] = NULL;
i++;
} while (gtk_tree_model_iter_next (model, &iter));
}
g_settings_set_strv (page_key->gsettings,
page_key->key, (const gchar **) vals);
g_strfreev (vals);
}
static void
on_button_clicked_add (GtkButton *button,
NimfSettingsPageKey *page_key)
{
GtkWidget *dialog;
GtkWidget *entry;
GtkWidget *content_area;
GtkDialogFlags flags;
#if GTK_CHECK_VERSION (3, 12, 0)
flags = GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR;
#else
flags = GTK_DIALOG_MODAL;
#endif
dialog = gtk_dialog_new_with_buttons (_("Press key combination"),
GTK_WINDOW (nimf_settings_window),
flags,
_("_OK"), GTK_RESPONSE_OK,
_("_Cancel"), GTK_RESPONSE_CANCEL,
NULL);
gtk_window_set_icon_name (GTK_WINDOW (dialog), "nimf-logo");
gtk_widget_set_size_request (GTK_WIDGET (dialog), 400, -1);
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
GTK_RESPONSE_OK, FALSE);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
entry = gtk_entry_new ();
gtk_entry_set_placeholder_text (GTK_ENTRY (entry),
_("Click here and then press key combination"));
gtk_box_pack_start (GTK_BOX (content_area), entry, TRUE, TRUE, 0);
g_signal_connect (entry, "key-press-event",
G_CALLBACK (on_key_press_event), dialog);
gtk_widget_show_all (content_area);
switch (gtk_dialog_run (GTK_DIALOG (dialog)))
{
case GTK_RESPONSE_OK:
{
GtkTreeModel *model;
const gchar *text;
GtkTreeIter iter;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (page_key->treeview));
text = gtk_entry_get_text (GTK_ENTRY (entry));
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, text, -1);
nimf_settings_page_key_update_gsettings_strv (page_key, model);
}
break;
default:
break;
}
gtk_widget_destroy (dialog);
}
static void
on_button_clicked_remove (GtkButton *button,
NimfSettingsPageKey *page_key)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (page_key->treeview));
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
nimf_settings_page_key_update_gsettings_strv (page_key, model);
}
}
static void
on_tree_view_realize (GtkWidget *tree_view,
GtkScrolledWindow *scrolled_w)
{
GtkTreeViewColumn *column;
gint height;
column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0);
gtk_tree_view_column_cell_get_size (column, NULL, NULL, NULL, NULL, &height);
gtk_widget_set_size_request (GTK_WIDGET (scrolled_w), -1, height * 3 );
}
static GtkWidget *
nimf_settings_page_key_build_string_array (NimfSettingsPageKey *page_key)
{
GtkListStore *store;
GtkWidget *treeview;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
GtkWidget *scrolled_w;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *button1;
GtkWidget *button2;
gchar **strv;
gchar *detailed_signal;
GtkTreeIter iter;
gint j;
button1 = gtk_button_new_from_icon_name ("list-add", GTK_ICON_SIZE_SMALL_TOOLBAR);
button2 = gtk_button_new_from_icon_name ("list-remove", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_button_set_relief (GTK_BUTTON (button1), GTK_RELIEF_NONE);
gtk_button_set_relief (GTK_BUTTON (button2), GTK_RELIEF_NONE);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
#if GTK_CHECK_VERSION (3, 12, 0)
gtk_widget_set_margin_end (page_key->label, 15);
#else
gtk_widget_set_margin_right (page_key->label, 15);
#endif
gtk_box_pack_start (GTK_BOX (hbox), page_key->label, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), button2, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), button1, FALSE, FALSE, 0);
store = gtk_list_store_new (1, G_TYPE_STRING);
strv = g_settings_get_strv (page_key->gsettings, page_key->key);
for (j = 0; strv[j] != NULL; j++)
{
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, strv[j], -1);
}
treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
g_object_unref (store);
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Hotkeys", renderer,
"text", 0, NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
scrolled_w = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_w),
GTK_SHADOW_ETCHED_IN);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_w),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
gtk_container_add (GTK_CONTAINER (scrolled_w), treeview);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (vbox), scrolled_w, FALSE, FALSE, 0);
page_key->treeview = treeview;
detailed_signal = g_strdup_printf ("changed::%s", page_key->key);
g_signal_connect (treeview, "realize",
G_CALLBACK (on_tree_view_realize), scrolled_w);
g_signal_connect (button1, "clicked", G_CALLBACK (on_button_clicked_add), page_key);
g_signal_connect (button2, "clicked", G_CALLBACK (on_button_clicked_remove), page_key);
g_signal_connect (page_key->gsettings, detailed_signal,
G_CALLBACK (on_gsettings_changed), page_key->treeview);
g_strfreev (strv);
g_free (detailed_signal);
return vbox;
}
static gboolean
xprofile_contains_generator (GFile *file)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
GFileInputStream *fis;
GDataInputStream *dis;
gboolean found = FALSE;
if (!(fis = g_file_read (file, NULL, NULL)))
return FALSE;
dis = g_data_input_stream_new (G_INPUT_STREAM (fis));
do {
char *line;
line = g_data_input_stream_read_line (dis, NULL, NULL, NULL);
if (!line)
break;
g_strstrip (line);
found = !g_strcmp0 (line, EXPORT_ENVIRONMENT);
g_free (line);
} while (found == FALSE);
g_input_stream_close (G_INPUT_STREAM (dis), NULL, NULL);
g_input_stream_close (G_INPUT_STREAM (fis), NULL, NULL);
g_object_unref (dis);
g_object_unref (fis);
return found;
}
static void
on_setup_environment (GSettings *settings,
gchar *key,
gpointer user_data)
{
g_debug (G_STRLOC ": %s", G_STRFUNC);
if (g_settings_get_boolean (settings, key))
{
GFile *file;
gchar *filename;
gchar *xprofile;
gboolean success = TRUE;
filename = g_build_filename (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
"environment.d", INPUT_CONF, NULL);
if (g_access (filename, F_OK))
{
gchar *path;
path = g_build_filename (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
"environment.d", NULL);
g_unlink (filename);
success = !g_mkdir_with_parents (path, 0700) &&
!symlink ("/etc/input.d/nimf.conf", filename);
g_free (path);
}
g_free (filename);
if (!success)
{
g_settings_set_boolean (settings, key, FALSE);
return;
}
xprofile = g_build_filename (G_DIR_SEPARATOR_S, g_get_home_dir (),
".xprofile", NULL);
file = g_file_new_for_path (xprofile);
if (xprofile_contains_generator (file) == FALSE)
{
GFileOutputStream *fos;
gboolean success = FALSE;
fos = g_file_append_to (file, G_FILE_CREATE_NONE, NULL, NULL);
if (fos)
{
gsize count = strlen (EXPORT_ENVIRONMENT) + 1;
if (g_output_stream_write (G_OUTPUT_STREAM (fos),
EXPORT_ENVIRONMENT"\n", count,
NULL, NULL) == count)
success = TRUE;
g_output_stream_close (G_OUTPUT_STREAM (fos), NULL, NULL);
g_object_unref (fos);
}
if (success == FALSE)
g_settings_set_boolean (settings, key, FALSE);
}
g_object_unref (file);
g_free (xprofile);
}
else
{
gchar *filename;
filename = g_build_filename (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
"environment.d", INPUT_CONF, NULL);
if (!g_access (filename, F_OK))
g_unlink (filename);
g_free (filename);
}
}
static void
nimf_settings_page_free (NimfSettingsPage *page)
{
g_object_unref (page->gsettings);
g_ptr_array_free (page->page_keys, TRUE);
g_slice_free (NimfSettingsPage, page);
}
static NimfSettingsPage *
nimf_settings_page_new (const gchar *schema_id)
{
NimfSettingsPage *page;
GSettingsSchema *schema;
GList *key_list = NULL;
gchar **keys;
GList *l;
gint i;
page = g_slice_new0 (NimfSettingsPage);
page->gsettings = g_settings_new (schema_id);
page->box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 15);
page->page_keys = g_ptr_array_new_with_free_func ((GDestroyNotify) nimf_settings_page_key_free);
g_object_set_data_full (G_OBJECT (page->box), "free", page, (GDestroyNotify) nimf_settings_page_free);
#if GTK_CHECK_VERSION (3, 12, 0)
gtk_widget_set_margin_start (page->box, 15);
gtk_widget_set_margin_end (page->box, 15);
#else
gtk_widget_set_margin_left (page->box, 15);
gtk_widget_set_margin_right (page->box, 15);
#endif
gtk_widget_set_margin_top (page->box, 15);
gtk_widget_set_margin_bottom (page->box, 15);
schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (),
schema_id, TRUE);
#if GLIB_CHECK_VERSION (2, 46, 0)
keys = g_settings_schema_list_keys (schema);
#else
keys = g_settings_list_keys (page->gsettings);
#endif
for (i = 0; keys[i] != NULL; i++)
key_list = g_list_prepend (key_list, keys[i]);
key_list = g_list_sort (key_list, (GCompareFunc) g_strcmp0);
for (i = 0, l = key_list; l != NULL; l = l->next, i++)
{
GSettingsSchemaKey *schema_key = NULL;
NimfSettingsPageKey *page_key;
const GVariantType *type;
const gchar *key;
const gchar *summary;
const gchar *desc;
key = l->data;
if (g_str_has_prefix (key, "hidden-"))
continue;
schema_key = g_settings_schema_get_key (schema, key);
type = g_settings_schema_key_get_value_type (schema_key);
summary = g_settings_schema_key_get_summary (schema_key);
desc = g_settings_schema_key_get_description (schema_key);
page_key = nimf_settings_page_key_new (page->gsettings, key, summary, desc);
g_ptr_array_add (page->page_keys, page_key);
if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
{
GtkWidget *item;
item = nimf_settings_page_key_build_boolean (page_key, schema_id);
gtk_box_pack_start (GTK_BOX (page->box), item, FALSE, FALSE, 0);
}
else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
{
GtkWidget *item;
item = nimf_settings_page_key_build_string (page_key, schema_id, key_list);
gtk_box_pack_start (GTK_BOX (page->box), item, FALSE, FALSE, 0);
}
else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING_ARRAY))
{
GtkWidget *item;
item = nimf_settings_page_key_build_string_array (page_key);
gtk_box_pack_start (GTK_BOX (page->box), item, FALSE, FALSE, 0);
}
else
g_error (G_STRLOC ": %s: not supported variant type: \"%s\"",
G_STRFUNC, (gchar *) type);
g_settings_schema_key_unref (schema_key);
}
if (!g_strcmp0 (schema_id, "org.nimf"))
{
GFile *file;
gchar *xprofile;
gchar *conf;
xprofile = g_build_filename (G_DIR_SEPARATOR_S, g_get_home_dir (),
".xprofile", NULL);
file = g_file_new_for_path (xprofile);
conf = g_build_filename (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
"environment.d", INPUT_CONF, NULL);
g_settings_set_boolean (page->gsettings, "setup-environment-variables",
!g_access (conf, F_OK) &&
xprofile_contains_generator (file));
g_signal_connect (page->gsettings, "changed::setup-environment-variables",
G_CALLBACK (on_setup_environment), NULL);
g_object_unref (file);
g_free (xprofile);
g_free (conf);
}
g_strfreev (keys);
g_list_free (key_list);
g_settings_schema_unref (schema);
return page;
}
static void
on_destroy (GtkWidget *widget, GApplication *app)
{
g_application_release (app);
}
static void
build_xkb_options (GtkToggleButton *toggle_button,
NimfXkb *xkb)
{
if (gtk_toggle_button_get_active (toggle_button))
{
gchar *name = g_strdup (gtk_widget_get_name (GTK_WIDGET (toggle_button)));
xkb->options_len += 1;
xkb->options = g_realloc_n (xkb->options,
xkb->options_len + 1,
sizeof (gchar *));
xkb->options[xkb->options_len - 1] = name;
xkb->options[xkb->options_len] = NULL;
}
}
static void
on_toggled (GtkToggleButton *toggle_button,
NimfXkb *xkb)
{
if (GTK_IS_RADIO_BUTTON (toggle_button) &&
!gtk_toggle_button_get_active (toggle_button))
return;
if (xkb->options_len > 0)
{
xkb->options_len = 0;
xkb->options = g_realloc_n (xkb->options, 1, sizeof (gchar *));
xkb->options[0] = NULL;
}
g_slist_foreach (xkb->toggle_buttons, (GFunc) build_xkb_options, xkb);
if (gnome_xkb_is_available () && gnome_is_running ())
{
GSettings *settings;
settings = g_settings_new ("org.gnome.desktop.input-sources");
g_settings_set_strv (settings, "xkb-options",
(const gchar *const *) xkb->options);
g_object_unref (settings);
}
else if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "x11") == 0)
{
XklConfigRec *rec;
GSettings *settings;
rec = xkl_config_rec_new ();
xkl_config_rec_get_from_server (rec, xkb->engine);
g_strfreev (rec->options);
rec->options = g_strdupv (xkb->options);
xkl_config_rec_activate (rec, xkb->engine);
settings = g_settings_new ("org.nimf.settings");
g_settings_set_strv (settings, "xkb-options",
(const gchar *const *) xkb->options);
g_object_unref (settings);
g_object_unref (rec);
}
}
static void
configure_button (GtkWidget *button, const XklConfigItem *item, NimfXkb *xkb)
{
gtk_widget_set_name (button, item->name);
if (g_strv_contains ((const gchar * const *) xkb->options, item->name))
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
g_signal_connect (button, "toggled", G_CALLBACK (on_toggled), xkb);
xkb->toggle_buttons = g_slist_append (xkb->toggle_buttons, button);
gtk_box_pack_start (GTK_BOX (xkb->vbox), button, FALSE, FALSE, 0);
}
static void
build_check_button_option (XklConfigRegistry *config,
const XklConfigItem *item,
NimfXkb *xkb)
{
configure_button (gtk_check_button_new_with_label (item->description),
item, xkb);
}
static void
build_radio_button_option (XklConfigRegistry *config,
const XklConfigItem *item,
NimfXkb *xkb)
{
GtkWidget *radio_button;
radio_button = gtk_radio_button_new_with_label (xkb->radio_group,
item->description);
xkb->radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_button));
configure_button (radio_button, item, xkb);
}
static void
build_option_group (XklConfigRegistry *config,
const XklConfigItem *item,
NimfXkb *xkb)
{
xkb->radio_group = NULL;
GtkWidget *expander = gtk_expander_new (item->description);
xkb->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start (xkb->vbox, 15);
gtk_container_add (GTK_CONTAINER (expander), xkb->vbox);
gtk_box_pack_start (GTK_BOX (xkb->options_box), expander, FALSE, FALSE, 0);
if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), XCI_PROP_ALLOW_MULTIPLE_SELECTION)))
{
xkl_config_registry_foreach_option (config, item->name,
(XklConfigItemProcessFunc) build_check_button_option,
xkb);
}
else
{
GtkWidget *radio_button;
radio_button = gtk_radio_button_new_with_label (xkb->radio_group,
_("Default"));
g_signal_connect (radio_button, "toggled", G_CALLBACK (on_toggled), xkb);
xkb->radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_button));
gtk_box_pack_start (GTK_BOX (xkb->vbox), radio_button, FALSE, FALSE, 0);
xkl_config_registry_foreach_option (config, item->name,
(XklConfigItemProcessFunc) build_radio_button_option,
xkb);
}
}
static void
nimf_xkb_free (NimfXkb *xkb)
{
g_strfreev (xkb->options);
/*
if (xkb->engine)
g_object_unref (xkb->engine);
*/
g_slist_free (xkb->toggle_buttons);
g_slist_free (xkb->radio_group);
g_slice_free (NimfXkb, xkb);
}
static GtkWidget *
nimf_settings_build_xkb_options_ui ()
{
NimfXkb *xkb;
XklConfigRegistry *config_registry;
GSettings *settings;
if (gnome_xkb_is_available () && gnome_is_running ())
settings = g_settings_new ("org.gnome.desktop.input-sources");
else if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "x11") == 0)
settings = g_settings_new ("org.nimf.settings");
else
return NULL;
xkb = g_slice_new0 (NimfXkb);
xkb->options = g_settings_get_strv (settings, "xkb-options");
xkb->options_len = g_strv_length (xkb->options);
xkb->options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set_data_full (G_OBJECT (xkb->options_box), "xkb", xkb,
(GDestroyNotify) nimf_xkb_free);
gtk_widget_set_margin_start (xkb->options_box, 15);
gtk_widget_set_margin_end (xkb->options_box, 15);
gtk_widget_set_margin_top (xkb->options_box, 15);
gtk_widget_set_margin_bottom (xkb->options_box, 15);
xkb->engine =
xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
config_registry = xkl_config_registry_get_instance (xkb->engine);
xkl_config_registry_load (config_registry, TRUE);
xkl_config_registry_foreach_option_group (config_registry,
(ConfigItemProcessFunc) build_option_group,
xkb);
g_object_unref (settings);
g_object_unref (config_registry);
return xkb->options_box;
}
static void
on_row_selected (GtkListBox *box,
GtkListBoxRow *row,
gpointer user_data)
{
NimfSettingsPage *page;
const gchar *schema_id;
GtkWidget *content = user_data;
GtkWidget *child;
if ((child = gtk_bin_get_child (GTK_BIN (content))))
gtk_container_remove (GTK_CONTAINER (content), child);
schema_id = gtk_widget_get_name (GTK_WIDGET (row));
if (g_strcmp0 (schema_id, "xkb-options"))
{
page = nimf_settings_page_new (schema_id);
gtk_container_add (GTK_CONTAINER (content), page->box);
}
else
{
gtk_container_add (GTK_CONTAINER (content),
nimf_settings_build_xkb_options_ui ());
}
gtk_widget_show_all (content);
}
static void
append_xkb_menu_after_nimf_menu (GtkWidget *listbox)
{
GtkWidget *label;
GtkWidget *row;
label = gtk_label_new (_("XKB Options"));
row = gtk_list_box_row_new ();
gtk_widget_set_name (row, "xkb-options");
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
gtk_container_add (GTK_CONTAINER (row), label);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_widget_set_margin_start (label, 15);
gtk_widget_set_margin_end (label, 15);
gtk_widget_set_margin_top (label, 5);
gtk_widget_set_margin_bottom (label, 5);
gtk_list_box_insert (GTK_LIST_BOX (listbox), row, 1);
}
static GtkWidget *
nimf_settings_build_main_window (NimfSettings *nsettings)
{
GSettingsSchemaSource *source;
GtkWidget *window;
GtkWidget *sidebar;
GtkWidget *content;
GtkWidget *listbox;
GtkWidget *box;
GSList *schema_list = NULL;
gchar **schemas;
gint i;
source = g_settings_schema_source_get_default ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
gtk_window_set_title (GTK_WINDOW (window), _("Nimf Settings"));
gtk_window_set_icon_name (GTK_WINDOW (window), "nimf-logo");
listbox = gtk_list_box_new ();
gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox), GTK_SELECTION_BROWSE);
g_settings_schema_source_list_schemas (source, TRUE, &schemas, NULL);
for (i = 0; schemas[i] != NULL; i++)
if (g_str_has_prefix (schemas[i], "org.nimf") &&
g_strcmp0 (schemas[i], "org.nimf.settings"))
schema_list = g_slist_prepend (schema_list, schemas[i]);
for (schema_list = g_slist_sort (schema_list, (GCompareFunc) on_comparison);
schema_list != NULL;
schema_list = schema_list->next)
{
GSettingsSchemaKey *key;
GSettingsSchema *schema;
GVariant *variant;
GtkWidget *row;
GtkWidget *label;
gchar *title;
gchar *p;
gint level = 0;
gchar *schema_id = schema_list->data;
schema = g_settings_schema_source_lookup (source, schema_id, FALSE);
if (schema == NULL)
{
g_warning (G_STRLOC ": %s: %s is not found.", G_STRFUNC, schema_id);
continue;
}
key = g_settings_schema_get_key (schema, "hidden-schema-name");
variant = g_settings_schema_key_get_default_value (key);
title = g_strdup (g_variant_get_string (variant, NULL));
for (p = schema_id; *p != 0; p++)
if (*p == '.')
level++;
if (level < 3)
{
gchar *markup;
label = gtk_label_new (NULL);
markup = g_strdup_printf ("<span weight=\"bold\""
"size=\"large\">\%s</span>", title);
gtk_label_set_markup (GTK_LABEL (label), markup);
g_free (markup);
}
else
{
label = gtk_label_new (title);
}
row = gtk_list_box_row_new ();
gtk_widget_set_name (row, schema_id);
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
gtk_container_add (GTK_CONTAINER (row), label);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_widget_set_margin_start (label, 15);
gtk_widget_set_margin_end (label, 15);
gtk_widget_set_margin_top (label, 5);
gtk_widget_set_margin_bottom (label, 5);
gtk_list_box_insert (GTK_LIST_BOX (listbox), row, -1);
g_free (title);
g_variant_unref (variant);
g_settings_schema_key_unref (key);
g_settings_schema_unref (schema);
}
if ((gnome_xkb_is_available () && gnome_is_running ()) ||
!g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "x11"))
append_xkb_menu_after_nimf_menu (listbox);
sidebar = gtk_scrolled_window_new (NULL, NULL);
content = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_container_add (GTK_CONTAINER (sidebar), listbox);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (box), sidebar, FALSE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box), content, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (window), box);
g_signal_connect (listbox, "row-selected", G_CALLBACK (on_row_selected), content);
g_strfreev (schemas);
g_slist_free (schema_list);
g_signal_connect (window, "destroy",
G_CALLBACK (on_destroy), nsettings->app);
return window;
}
static void
on_activate (GApplication *app, NimfSettings *nsettings)
{
g_application_hold (app);
if (nimf_settings_window)
{
gtk_window_present (GTK_WINDOW (nimf_settings_window));
g_application_release (app);
return;
}
nimf_settings_window = nimf_settings_build_main_window (nsettings);
gtk_widget_show_all (nimf_settings_window);
}
static int
nimf_settings_run (NimfSettings *nsettings,
int argc,
char **argv)
{
return g_application_run (G_APPLICATION (nsettings->app), argc, argv);
}
static NimfSettings *
nimf_settings_new ()
{
return g_object_new (NIMF_TYPE_SETTINGS, NULL);
}
static void
nimf_settings_init (NimfSettings *nsettings)
{
nsettings->app = g_application_new ("org.nimf.settings",
G_APPLICATION_FLAGS_NONE);
g_signal_connect (nsettings->app, "activate",
G_CALLBACK (on_activate), nsettings);
}
static void
nimf_settings_finalize (GObject *object)
{
g_object_unref (NIMF_SETTINGS (object)->app);
G_OBJECT_CLASS (nimf_settings_parent_class)->finalize (object);
}
static void
nimf_settings_class_init (NimfSettingsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = nimf_settings_finalize;
}
/**
* SECTION:nimf-settings
* @title: nimf-settings
* @section_id: nimf-settings
*/
/**
* PROGRAM:nimf-settings
* @short_description: Tool for setting up Nimf
* @synopsis: nimf-settings [*OPTIONS*...]
* @see_also: nimf(1)
* @-h, --help: Show help messages
* @--help-all: Show all options
* @--help-gapplication: Show GApplication options
* @--gapplication-service: Enter GApplication service mode. Currently this
* option is used to trigger the nimf indicator.
*
* nimf-settings is a GUI tool for configuring nimf.
*
* Returns: 0 on success, non-zero on failure
*/
int main (int argc, char **argv)
{
NimfSettings *nsettings;
int status;
g_setenv ("GTK_IM_MODULE", "gtk-im-context-simple", TRUE);
g_setenv ("GDK_BACKEND", "x11", TRUE);
#ifdef ENABLE_NLS
bindtextdomain (GETTEXT_PACKAGE, NIMF_LOCALE_DIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
#endif
gtk_init (&argc, &argv);
nsettings = nimf_settings_new ();
status = nimf_settings_run (nsettings, argc, argv);
g_object_unref (nsettings);
return status;
}