/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2011, 2014 Red Hat, Inc. * * 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 of the License, 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, see . * */ #include "config.h" #include #include #include #include #include #include "cc-keyboard-item.h" #define CC_KEYBOARD_ITEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_KEYBOARD_ITEM, CcKeyboardItemPrivate)) #define CUSTOM_KEYS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys.custom-keybinding" struct CcKeyboardItemPrivate { /* properties */ /* common */ char *binding; /* internal */ CcKeyboardItem *reverse_item; gboolean is_reversed; gboolean hidden; }; enum { PROP_0, PROP_DESCRIPTION, PROP_BINDING, PROP_EDITABLE, PROP_TYPE, PROP_IS_VALUE_DEFAULT, PROP_COMMAND }; static void cc_keyboard_item_class_init (CcKeyboardItemClass *klass); static void cc_keyboard_item_init (CcKeyboardItem *keyboard_item); static void cc_keyboard_item_finalize (GObject *object); G_DEFINE_TYPE (CcKeyboardItem, cc_keyboard_item, G_TYPE_OBJECT) static const gchar * get_binding_from_variant (GVariant *variant) { const char *str, **strv; if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) return g_variant_get_string (variant, NULL); else if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY)) return ""; strv = g_variant_get_strv (variant, NULL); str = strv[0]; g_free (strv); return str; } static gboolean binding_from_string (const char *str, CcKeyCombo *combo) { g_return_val_if_fail (combo != NULL, FALSE); guint *keycodes; if (str == NULL || strcmp (str, "disabled") == 0) { memset (combo, 0, sizeof(CcKeyCombo)); return TRUE; } gtk_accelerator_parse_with_keycode (str, &combo->keyval, &keycodes, &combo->mask); combo->keycode = (keycodes ? keycodes[0] : 0); g_free (keycodes); if (combo->keyval == 0) return FALSE; else return TRUE; } static void _set_description (CcKeyboardItem *item, const char *value) { g_free (item->description); item->description = g_strdup (value); } const char * cc_keyboard_item_get_description (CcKeyboardItem *item) { g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL); return item->description; } /* wrapper around g_settings_set_str[ing|v] */ static void settings_set_binding (GSettings *settings, const char *key, const char *value) { GVariant *variant; variant = g_settings_get_value (settings, key); if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) g_settings_set_string (settings, key, value ? value : ""); else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY)) { if (value == NULL || *value == '\0') g_settings_set_strv (settings, key, NULL); else { char **str_array = g_new0 (char *, 2); /* clear any additional bindings by only setting the first one */ *str_array = g_strdup (value); g_settings_set_strv (settings, key, (const char * const *)str_array); g_strfreev (str_array); } } g_variant_unref (variant); } static void _set_binding (CcKeyboardItem *item, const char *value, gboolean set_backend) { CcKeyboardItem *reverse; gboolean enabled; reverse = item->priv->reverse_item; enabled = value && strlen (value) > 0; g_clear_pointer (&item->priv->binding, g_free); item->priv->binding = enabled ? g_strdup (value) : g_strdup (""); binding_from_string (item->priv->binding, item->primary_combo); /* * Always treat the pair (item, reverse) as a unit: setting one also * disables the other, disabling one up also sets the other. */ if (reverse) { GdkModifierType reverse_mask; reverse_mask = enabled ? item->primary_combo->mask ^ GDK_SHIFT_MASK : item->primary_combo->mask; g_clear_pointer (&reverse->priv->binding, g_free); if (enabled) reverse->priv->binding = gtk_accelerator_name_with_keycode (NULL, item->primary_combo->keyval, item->primary_combo->keycode, reverse_mask); binding_from_string (reverse->priv->binding, reverse->primary_combo); } if (set_backend == FALSE) return; settings_set_binding (item->settings, item->key, item->priv->binding); g_object_notify (G_OBJECT (item), "is-value-default"); if (reverse) { settings_set_binding (reverse->settings, reverse->key, reverse->priv->binding); g_object_notify (G_OBJECT (reverse), "is-value-default"); } } static void _set_type (CcKeyboardItem *item, gint value) { item->type = value; } static void _set_command (CcKeyboardItem *item, const char *value) { g_free (item->command); item->command = g_strdup (value); } const char * cc_keyboard_item_get_command (CcKeyboardItem *item) { g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL); return item->command; } static void cc_keyboard_item_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CcKeyboardItem *self; self = CC_KEYBOARD_ITEM (object); switch (prop_id) { case PROP_DESCRIPTION: _set_description (self, g_value_get_string (value)); break; case PROP_BINDING: _set_binding (self, g_value_get_string (value), TRUE); break; case PROP_COMMAND: _set_command (self, g_value_get_string (value)); break; case PROP_TYPE: _set_type (self, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cc_keyboard_item_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CcKeyboardItem *self; self = CC_KEYBOARD_ITEM (object); switch (prop_id) { case PROP_DESCRIPTION: g_value_set_string (value, self->description); break; case PROP_BINDING: g_value_set_string (value, self->priv->binding); break; case PROP_EDITABLE: g_value_set_boolean (value, self->editable); break; case PROP_COMMAND: g_value_set_string (value, self->command); break; case PROP_IS_VALUE_DEFAULT: g_value_set_boolean (value, cc_keyboard_item_is_value_default (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cc_keyboard_item_class_init (CcKeyboardItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = cc_keyboard_item_get_property; object_class->set_property = cc_keyboard_item_set_property; object_class->finalize = cc_keyboard_item_finalize; g_object_class_install_property (object_class, PROP_DESCRIPTION, g_param_spec_string ("description", "description", "description", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BINDING, g_param_spec_string ("binding", "binding", "binding", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_EDITABLE, g_param_spec_boolean ("editable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_TYPE, g_param_spec_int ("type", NULL, NULL, CC_KEYBOARD_ITEM_TYPE_NONE, CC_KEYBOARD_ITEM_TYPE_GSETTINGS, CC_KEYBOARD_ITEM_TYPE_NONE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); g_object_class_install_property (object_class, PROP_COMMAND, g_param_spec_string ("command", "command", "command", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_IS_VALUE_DEFAULT, g_param_spec_boolean ("is-value-default", "is value default", "is value default", TRUE, G_PARAM_READABLE)); g_type_class_add_private (klass, sizeof (CcKeyboardItemPrivate)); } static void cc_keyboard_item_init (CcKeyboardItem *item) { item->priv = CC_KEYBOARD_ITEM_GET_PRIVATE (item); item->primary_combo = g_new0 (CcKeyCombo, 1); } static void cc_keyboard_item_finalize (GObject *object) { CcKeyboardItem *item; g_return_if_fail (object != NULL); g_return_if_fail (CC_IS_KEYBOARD_ITEM (object)); item = CC_KEYBOARD_ITEM (object); g_return_if_fail (item->priv != NULL); if (item->settings != NULL) g_object_unref (item->settings); /* Free memory */ g_free (item->priv->binding); g_free (item->primary_combo); g_free (item->gsettings_path); g_free (item->description); g_free (item->command); g_free (item->schema); g_free (item->key); g_list_free_full (item->key_combos, g_free); g_list_free_full (item->default_combos, g_free); G_OBJECT_CLASS (cc_keyboard_item_parent_class)->finalize (object); } CcKeyboardItem * cc_keyboard_item_new (CcKeyboardItemType type) { GObject *object; object = g_object_new (CC_TYPE_KEYBOARD_ITEM, "type", type, NULL); return CC_KEYBOARD_ITEM (object); } static GList * variant_get_key_combos (GVariant *variant) { GList *combos = NULL; char **bindings = NULL, **str; if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) { bindings = g_malloc0_n (2, sizeof(char *)); bindings[0] = g_variant_dup_string (variant, NULL); } else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY)) { bindings = g_variant_dup_strv (variant, NULL); } for (str = bindings; *str; str++) { CcKeyCombo *combo = g_new (CcKeyCombo, 1); binding_from_string (*str, combo); combos = g_list_prepend (combos, combo); } g_strfreev (bindings); return g_list_reverse (combos); } static GList * settings_get_key_combos (GSettings *settings, const char *key, gboolean use_default) { GList *key_combos; GVariant *variant; if (use_default) variant = g_settings_get_default_value (settings, key); else variant = g_settings_get_value (settings, key); key_combos = variant_get_key_combos (variant); g_variant_unref (variant); return key_combos; } /* wrapper around g_settings_get_str[ing|v] */ static char * settings_get_binding (GSettings *settings, const char *key) { GVariant *variant; char *value = NULL; variant = g_settings_get_value (settings, key); if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) value = g_variant_dup_string (variant, NULL); else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY)) { const char **str_array; str_array = g_variant_get_strv (variant, NULL); value = g_strdup (str_array[0]); g_free (str_array); } g_variant_unref (variant); return value; } static void binding_changed (GSettings *settings, const char *key, CcKeyboardItem *item) { char *value; g_list_free_full (item->key_combos, g_free); item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE); value = settings_get_binding (item->settings, item->key); item->editable = g_settings_is_writable (item->settings, item->key); _set_binding (item, value, FALSE); g_free (value); g_object_notify (G_OBJECT (item), "binding"); } gboolean cc_keyboard_item_load_from_gsettings_path (CcKeyboardItem *item, const char *path, gboolean reset) { item->schema = g_strdup (CUSTOM_KEYS_SCHEMA); item->gsettings_path = g_strdup (path); item->key = g_strdup ("binding"); item->settings = g_settings_new_with_path (item->schema, path); item->editable = g_settings_is_writable (item->settings, item->key); item->desc_editable = g_settings_is_writable (item->settings, "name"); item->cmd_editable = g_settings_is_writable (item->settings, "command"); if (reset) { g_settings_reset (item->settings, "name"); g_settings_reset (item->settings, "command"); g_settings_reset (item->settings, "binding"); } g_settings_bind (item->settings, "name", G_OBJECT (item), "description", G_SETTINGS_BIND_DEFAULT); g_settings_bind (item->settings, "command", G_OBJECT (item), "command", G_SETTINGS_BIND_DEFAULT); g_list_free_full (item->key_combos, g_free); item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE); g_free (item->priv->binding); item->priv->binding = settings_get_binding (item->settings, item->key); binding_from_string (item->priv->binding, item->primary_combo); g_signal_connect (G_OBJECT (item->settings), "changed::binding", G_CALLBACK (binding_changed), item); return TRUE; } gboolean cc_keyboard_item_load_from_gsettings (CcKeyboardItem *item, const char *description, const char *schema, const char *key) { char *signal_name; item->schema = g_strdup (schema); item->key = g_strdup (key); item->description = g_strdup (description); item->settings = g_settings_new (item->schema); g_free (item->priv->binding); item->priv->binding = settings_get_binding (item->settings, item->key); item->editable = g_settings_is_writable (item->settings, item->key); binding_from_string (item->priv->binding, item->primary_combo); g_list_free_full (item->key_combos, g_free); item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE); g_list_free_full (item->default_combos, g_free); item->default_combos = settings_get_key_combos (item->settings, item->key, TRUE); signal_name = g_strdup_printf ("changed::%s", item->key); g_signal_connect (G_OBJECT (item->settings), signal_name, G_CALLBACK (binding_changed), item); g_free (signal_name); return TRUE; } gboolean cc_keyboard_item_equal (CcKeyboardItem *a, CcKeyboardItem *b) { if (a->type != b->type) return FALSE; switch (a->type) { case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH: return g_str_equal (a->gsettings_path, b->gsettings_path); case CC_KEYBOARD_ITEM_TYPE_GSETTINGS: return (g_str_equal (a->schema, b->schema) && g_str_equal (a->key, b->key)); default: g_assert_not_reached (); } } void cc_keyboard_item_add_reverse_item (CcKeyboardItem *item, CcKeyboardItem *reverse_item, gboolean is_reversed) { g_return_if_fail (item->key != NULL); item->priv->reverse_item = reverse_item; if (reverse_item->priv->reverse_item == NULL) { reverse_item->priv->reverse_item = item; reverse_item->priv->is_reversed = !is_reversed; } else g_warn_if_fail (reverse_item->priv->is_reversed == !!is_reversed); item->priv->is_reversed = !!is_reversed; } CcKeyboardItem * cc_keyboard_item_get_reverse_item (CcKeyboardItem *item) { return item->priv->reverse_item; } void cc_keyboard_item_set_hidden (CcKeyboardItem *item, gboolean hidden) { item->priv->hidden = !!hidden; } gboolean cc_keyboard_item_is_hidden (CcKeyboardItem *item) { return item->priv->hidden; } /** * cc_keyboard_item_is_value_default: * @self: a #CcKeyboardItem * * Retrieves whether the shortcut is the default value or not. * * Returns: %TRUE if the shortcut is the default value, %FALSE otherwise. */ gboolean cc_keyboard_item_is_value_default (CcKeyboardItem *self) { GVariant *user_value; gboolean is_value_default; g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (self), FALSE); /* * When the shortcut is custom, we don't treat it as modified * since we don't know what would be its default value. */ if (self->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH) return TRUE; user_value = g_settings_get_user_value (self->settings, self->key); is_value_default = TRUE; if (user_value) { GVariant *default_value; const gchar *default_binding, *user_binding; default_value = g_settings_get_default_value (self->settings, self->key); default_binding = get_binding_from_variant (default_value); user_binding = get_binding_from_variant (user_value); is_value_default = (g_strcmp0 (default_binding, user_binding) == 0); g_clear_pointer (&default_value, g_variant_unref); } g_clear_pointer (&user_value, g_variant_unref); return is_value_default; } /** * cc_keyboard_item_reset: * @self: a #CcKeyboardItem * * Reset the keyboard binding to the default value. */ void cc_keyboard_item_reset (CcKeyboardItem *self) { CcKeyboardItem *reverse; g_return_if_fail (CC_IS_KEYBOARD_ITEM (self)); reverse = self->priv->reverse_item; g_settings_reset (self->settings, self->key); g_object_notify (G_OBJECT (self), "is-value-default"); /* Also reset the reverse item */ if (reverse) { g_settings_reset (reverse->settings, reverse->key); g_object_notify (G_OBJECT (reverse), "is-value-default"); } } /* * vim: sw=2 ts=8 cindent noai bs=2 */