/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mate-settings-profile.h" #include "msd-keybindings-manager.h" #include "dconf-util.h" #include "msd-keygrab.h" #include "eggaccelerators.h" #define GSETTINGS_KEYBINDINGS_DIR "/org/mate/desktop/keybindings/" #define CUSTOM_KEYBINDING_SCHEMA "org.mate.control-center.keybinding" #define MSD_KEYBINDINGS_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MSD_TYPE_KEYBINDINGS_MANAGER, MsdKeybindingsManagerPrivate)) typedef struct { char *binding_str; char *action; char *settings_path; Key key; Key previous_key; } Binding; struct MsdKeybindingsManagerPrivate { DConfClient *client; GSList *binding_list; GSList *screens; }; static void msd_keybindings_manager_class_init (MsdKeybindingsManagerClass *klass); static void msd_keybindings_manager_init (MsdKeybindingsManager *keybindings_manager); static void msd_keybindings_manager_finalize (GObject *object); G_DEFINE_TYPE (MsdKeybindingsManager, msd_keybindings_manager, G_TYPE_OBJECT) static gpointer manager_object = NULL; static GSList * get_screens_list (void) { GdkDisplay *display = gdk_display_get_default(); int n_screens; GSList *list = NULL; int i; n_screens = gdk_display_get_n_screens (display); if (n_screens == 1) { list = g_slist_append (list, gdk_screen_get_default ()); } else { for (i = 0; i < n_screens; i++) { GdkScreen *screen; screen = gdk_display_get_screen (display, i); if (screen != NULL) { list = g_slist_prepend (list, screen); } } list = g_slist_reverse (list); } return list; } static gboolean parse_binding (Binding *binding) { gboolean success; g_return_val_if_fail (binding != NULL, FALSE); binding->key.keysym = 0; binding->key.state = 0; g_free (binding->key.keycodes); binding->key.keycodes = NULL; if (binding->binding_str == NULL || binding->binding_str[0] == '\0' || g_strcmp0 (binding->binding_str, "Disabled") == 0 || g_strcmp0 (binding->binding_str, "disabled") == 0 ) { return FALSE; } success = egg_accelerator_parse_virtual (binding->binding_str, &binding->key.keysym, &binding->key.keycodes, &binding->key.state); if (!success) g_warning (_("Key binding (%s) is invalid"), binding->settings_path); return success; } static gint compare_bindings (gconstpointer a, gconstpointer b) { Binding *key_a = (Binding *) a; char *key_b = (char *) b; return g_strcmp0 (key_b, key_a->settings_path); } static gboolean bindings_get_entry (MsdKeybindingsManager *manager, const char *settings_path) { GSettings *settings; Binding *new_binding; GSList *tmp_elem; char *action = NULL; char *key = NULL; if (!settings_path) { return FALSE; } /* Get entries for this binding */ settings = g_settings_new_with_path (CUSTOM_KEYBINDING_SCHEMA, settings_path); action = g_settings_get_string (settings, "action"); key = g_settings_get_string (settings, "binding"); g_object_unref (settings); if (!action || !key) { g_warning (_("Key binding (%s) is incomplete"), settings_path); g_free (action); g_free (key); return FALSE; } g_debug ("keybindings: get entries from '%s' (action: '%s', key: '%s')", settings_path, action, key); tmp_elem = g_slist_find_custom (manager->priv->binding_list, settings_path, compare_bindings); if (!tmp_elem) { new_binding = g_new0 (Binding, 1); } else { new_binding = (Binding *) tmp_elem->data; g_free (new_binding->binding_str); g_free (new_binding->action); g_free (new_binding->settings_path); new_binding->previous_key.keysym = new_binding->key.keysym; new_binding->previous_key.state = new_binding->key.state; new_binding->previous_key.keycodes = new_binding->key.keycodes; new_binding->key.keycodes = NULL; } new_binding->binding_str = key; new_binding->action = action; new_binding->settings_path = g_strdup (settings_path); if (parse_binding (new_binding)) { if (!tmp_elem) manager->priv->binding_list = g_slist_prepend (manager->priv->binding_list, new_binding); } else { g_free (new_binding->binding_str); g_free (new_binding->action); g_free (new_binding->settings_path); g_free (new_binding->previous_key.keycodes); g_free (new_binding); if (tmp_elem) manager->priv->binding_list = g_slist_delete_link (manager->priv->binding_list, tmp_elem); return FALSE; } return TRUE; } static void bindings_clear (MsdKeybindingsManager *manager) { MsdKeybindingsManagerPrivate *p = manager->priv; GSList *l; if (p->binding_list != NULL) { for (l = p->binding_list; l; l = l->next) { Binding *b = l->data; g_free (b->binding_str); g_free (b->action); g_free (b->settings_path); g_free (b->previous_key.keycodes); g_free (b->key.keycodes); g_free (b); } g_slist_free (p->binding_list); p->binding_list = NULL; } } static void bindings_get_entries (MsdKeybindingsManager *manager) { gchar **custom_list = NULL; gint i; bindings_clear (manager); custom_list = dconf_util_list_subdirs (GSETTINGS_KEYBINDINGS_DIR, FALSE); if (custom_list != NULL) { for (i = 0; custom_list[i] != NULL; i++) { gchar *settings_path; settings_path = g_strdup_printf("%s%s", GSETTINGS_KEYBINDINGS_DIR, custom_list[i]); bindings_get_entry (manager, settings_path); g_free (settings_path); } g_strfreev (custom_list); } } static gboolean same_keycode (const Key *key, const Key *other) { if (key->keycodes != NULL && other->keycodes != NULL) { guint *c; for (c = key->keycodes; *c; ++c) { if (key_uses_keycode (other, *c)) return TRUE; } } return FALSE; } static gboolean same_key (const Key *key, const Key *other) { if (key->state == other->state) { if (key->keycodes != NULL && other->keycodes != NULL) { guint *c1, *c2; for (c1 = key->keycodes, c2 = other->keycodes; *c1 || *c2; ++c1, ++c2) { if (*c1 != *c2) return FALSE; } } else if (key->keycodes != NULL || other->keycodes != NULL) return FALSE; return TRUE; } return FALSE; } static gboolean key_already_used (MsdKeybindingsManager *manager, Binding *binding) { GSList *li; for (li = manager->priv->binding_list; li != NULL; li = li->next) { Binding *tmp_binding = (Binding*) li->data; if (tmp_binding != binding && same_keycode (&tmp_binding->key, &binding->key) && tmp_binding->key.state == binding->key.state) { return TRUE; } } return FALSE; } static void binding_unregister_keys (MsdKeybindingsManager *manager) { GSList *li; gboolean need_flush = FALSE; gdk_error_trap_push (); for (li = manager->priv->binding_list; li != NULL; li = li->next) { Binding *binding = (Binding *) li->data; if (binding->key.keycodes) { need_flush = TRUE; grab_key_unsafe (&binding->key, FALSE, manager->priv->screens); } } if (need_flush) gdk_flush (); #if GTK_CHECK_VERSION (3, 0, 0) gdk_error_trap_pop_ignored (); #else gdk_error_trap_pop (); #endif } static void binding_register_keys (MsdKeybindingsManager *manager) { GSList *li; gboolean need_flush = FALSE; gdk_error_trap_push (); /* Now check for changes and grab new key if not already used */ for (li = manager->priv->binding_list; li != NULL; li = li->next) { Binding *binding = (Binding *) li->data; if (!same_key (&binding->previous_key, &binding->key)) { /* Ungrab key if it changed and not clashing with previously set binding */ if (!key_already_used (manager, binding)) { gint i; need_flush = TRUE; if (binding->previous_key.keycodes) { grab_key_unsafe (&binding->previous_key, FALSE, manager->priv->screens); } grab_key_unsafe (&binding->key, TRUE, manager->priv->screens); binding->previous_key.keysym = binding->key.keysym; binding->previous_key.state = binding->key.state; g_free (binding->previous_key.keycodes); for (i = 0; binding->key.keycodes[i]; ++i); binding->previous_key.keycodes = g_new0 (guint, i); for (i = 0; binding->key.keycodes[i]; ++i) binding->previous_key.keycodes[i] = binding->key.keycodes[i]; } else g_warning ("Key binding (%s) is already in use", binding->binding_str); } } if (need_flush) gdk_flush (); if (gdk_error_trap_pop ()) g_warning ("Grab failed for some keys, another application may already have access the them."); } extern char **environ; static char * screen_exec_display_string (GdkScreen *screen) { GString *str; const char *old_display; char *p; g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); old_display = gdk_display_get_name (gdk_screen_get_display (screen)); str = g_string_new ("DISPLAY="); g_string_append (str, old_display); p = strrchr (str->str, '.'); if (p && p > strchr (str->str, ':')) { g_string_truncate (str, p - str->str); } g_string_append_printf (str, ".%d", gdk_screen_get_number (screen)); return g_string_free (str, FALSE); } /** * get_exec_environment: * * Description: Modifies the current program environment to * ensure that $DISPLAY is set such that a launched application * inheriting this environment would appear on screen. * * Returns: a newly-allocated %NULL-terminated array of strings or * %NULL on error. Use g_strfreev() to free it. * * mainly ripped from egg_screen_exec_display_string in * mate-panel/egg-screen-exec.c **/ static char ** get_exec_environment (XEvent *xevent) { char **retval = NULL; int i; int display_index = -1; GdkScreen *screen = NULL; GdkWindow *window = gdk_x11_window_lookup_for_display (gdk_display_get_default (), xevent->xkey.root); if (window) { screen = gdk_window_get_screen (window); } g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); for (i = 0; environ [i]; i++) { if (!strncmp (environ [i], "DISPLAY", 7)) { display_index = i; } } if (display_index == -1) { display_index = i++; } retval = g_new (char *, i + 1); for (i = 0; environ [i]; i++) { if (i == display_index) { retval [i] = screen_exec_display_string (screen); } else { retval [i] = g_strdup (environ [i]); } } retval [i] = NULL; return retval; } static GdkFilterReturn keybindings_filter (GdkXEvent *gdk_xevent, GdkEvent *event, MsdKeybindingsManager *manager) { XEvent *xevent = (XEvent *) gdk_xevent; GSList *li; if (xevent->type != KeyPress) { return GDK_FILTER_CONTINUE; } for (li = manager->priv->binding_list; li != NULL; li = li->next) { Binding *binding = (Binding *) li->data; if (match_key (&binding->key, xevent)) { GError *error = NULL; gboolean retval; gchar **argv = NULL; gchar **envp = NULL; g_return_val_if_fail (binding->action != NULL, GDK_FILTER_CONTINUE); if (!g_shell_parse_argv (binding->action, NULL, &argv, &error)) { return GDK_FILTER_CONTINUE; } envp = get_exec_environment (xevent); retval = g_spawn_async (NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); g_strfreev (argv); g_strfreev (envp); if (!retval) { GtkWidget *dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, _("Error while trying to run (%s)\n"\ "which is linked to the key (%s)"), binding->action, binding->binding_str); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_widget_show (dialog); } return GDK_FILTER_REMOVE; } } return GDK_FILTER_CONTINUE; } static void bindings_callback (DConfClient *client, #ifdef HAVE_DCONF_0_13 gchar *prefix, GStrv changes, #else const gchar *path, const gchar * const *items, gint n_items, #endif gchar *tag, MsdKeybindingsManager *manager) { g_debug ("keybindings: received 'changed' signal from dconf"); binding_unregister_keys (manager); bindings_get_entries (manager); binding_register_keys (manager); } gboolean msd_keybindings_manager_start (MsdKeybindingsManager *manager, GError **error) { GdkDisplay *dpy; GdkScreen *screen; int screen_num; int i; g_debug ("Starting keybindings manager"); mate_settings_profile_start (NULL); dpy = gdk_display_get_default (); screen_num = gdk_display_get_n_screens (dpy); for (i = 0; i < screen_num; i++) { screen = gdk_display_get_screen (dpy, i); gdk_window_add_filter (gdk_screen_get_root_window (screen), (GdkFilterFunc) keybindings_filter, manager); } manager->priv->screens = get_screens_list (); manager->priv->binding_list = NULL; bindings_get_entries (manager); binding_register_keys (manager); /* DConf has different API between versions: * http://developer.gnome.org/dconf/0.12/DConfClient.html * http://developer.gnome.org/dconf/0.14/DConfClient.html */ #ifdef HAVE_DCONF_0_13 manager->priv->client = dconf_client_new (); dconf_client_watch_fast (manager->priv->client, GSETTINGS_KEYBINDINGS_DIR); g_signal_connect (manager->priv->client, "changed", G_CALLBACK (bindings_callback), manager); #else manager->priv->client = dconf_client_new (NULL, (DConfWatchFunc) bindings_callback, manager, NULL); dconf_client_watch (manager->priv->client, GSETTINGS_KEYBINDINGS_DIR, NULL, NULL); #endif mate_settings_profile_end (NULL); return TRUE; } void msd_keybindings_manager_stop (MsdKeybindingsManager *manager) { MsdKeybindingsManagerPrivate *p = manager->priv; GSList *l; g_debug ("Stopping keybindings manager"); if (p->client != NULL) { g_object_unref (p->client); p->client = NULL; } for (l = p->screens; l; l = l->next) { GdkScreen *screen = l->data; gdk_window_remove_filter (gdk_screen_get_root_window (screen), (GdkFilterFunc) keybindings_filter, manager); } binding_unregister_keys (manager); bindings_clear (manager); g_slist_free (p->screens); p->screens = NULL; } static void msd_keybindings_manager_class_init (MsdKeybindingsManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = msd_keybindings_manager_finalize; g_type_class_add_private (klass, sizeof (MsdKeybindingsManagerPrivate)); } static void msd_keybindings_manager_init (MsdKeybindingsManager *manager) { manager->priv = MSD_KEYBINDINGS_MANAGER_GET_PRIVATE (manager); } static void msd_keybindings_manager_finalize (GObject *object) { MsdKeybindingsManager *keybindings_manager; g_return_if_fail (object != NULL); g_return_if_fail (MSD_IS_KEYBINDINGS_MANAGER (object)); keybindings_manager = MSD_KEYBINDINGS_MANAGER (object); g_return_if_fail (keybindings_manager->priv != NULL); G_OBJECT_CLASS (msd_keybindings_manager_parent_class)->finalize (object); } MsdKeybindingsManager * msd_keybindings_manager_new (void) { if (manager_object != NULL) { g_object_ref (manager_object); } else { manager_object = g_object_new (MSD_TYPE_KEYBINDINGS_MANAGER, NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); } return MSD_KEYBINDINGS_MANAGER (manager_object); }