#include #include #include #include #include #include #include #include "gnome-settings-daemon.h" #include "gnome-settings-keybindings.h" #include "eggaccelerators.h" /* we exclude shift, GDK_CONTROL_MASK and GDK_MOD1_MASK since we know what these modifiers mean these are the mods whose combinations are bound by the keygrabbing code */ #define IGNORED_MODS (0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | \ GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK) /* these are the ones we actually use for global keys, we always only check * for these set */ #define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK) #define GCONF_BINDING_DIR "/desktop/gnome/keybindings" typedef struct { guint keysym; guint state; guint keycode; } Key; typedef struct { char *binding_str; char *action; char *gconf_key; Key key; Key previous_key; } Binding; static GSList *binding_list = NULL; static GSList *screens = NULL; static GSList * get_screens_list (void) { GdkDisplay *display = gdk_display_get_default(); GSList *list = NULL; int i; if (gdk_display_get_n_screens (display) == 1) { list = g_slist_append (list, gdk_screen_get_default ()); } else { for (i = 0; i < gdk_display_get_n_screens (display); i++) { GdkScreen *screen; screen = gdk_display_get_screen (display, i); if (screen != NULL) { list = g_slist_append (list, screen); } } } return list; } extern char **environ; static char * screen_exec_display_string (GdkScreen *screen) { GString *str; const char *old_display; char *retval; 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)); retval = str->str; g_string_free (str, FALSE); return retval; } /** * 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 * gnome-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_xid_table_lookup (xevent->xkey.root); if (window) screen = gdk_drawable_get_screen (GDK_DRAWABLE (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 gint compare_bindings (gconstpointer a, gconstpointer b) { Binding *key_a = (Binding*) a; char *key_b = (char*) b; return strcmp (key_b, key_a->gconf_key); } static gboolean parse_binding (Binding *binding) { g_return_val_if_fail (binding != NULL, FALSE); binding->key.keysym = 0; binding->key.state = 0; if (binding->binding_str == NULL || binding->binding_str[0] == '\0' || strcmp (binding->binding_str, "Disabled") == 0) return FALSE; if (egg_accelerator_parse_virtual (binding->binding_str, &binding->key.keysym, &binding->key.keycode, &binding->key.state) == FALSE) return FALSE; return TRUE; } static gboolean bindings_get_entry (char *subdir) { GConfValue *value; Binding *new_binding; GSList *tmp_elem = NULL, *list = NULL, *li; char *gconf_key; char *action = NULL; char *key = NULL; GConfClient *client = gnome_settings_daemon_get_conf_client (); g_return_val_if_fail (subdir != NULL, FALSE); /* value = gconf_entry_get_value (entry); */ gconf_key = g_path_get_basename (subdir); if (!gconf_key) return FALSE; /* Get entries for this binding */ list = gconf_client_all_entries (client, subdir, NULL); for (li = list; li != NULL; li = li->next) { GConfEntry *entry = li->data; char *key_name = g_path_get_basename (gconf_entry_get_key (entry)); if (strcmp (key_name, "action") == 0) { if (!action) { value = gconf_entry_get_value (entry); if (value->type != GCONF_VALUE_STRING) return FALSE; action = g_strdup (gconf_value_get_string (value)); } else g_warning (_("Key Binding (%s) has its action defined multiple times\n"), gconf_key); } if (strcmp (key_name, "binding") == 0) { if (!key) { value = gconf_entry_get_value (entry); if (value->type != GCONF_VALUE_STRING) return FALSE; key = g_strdup (gconf_value_get_string (value)); } else g_warning (_("Key Binding (%s) has its binding defined multiple times\n"), gconf_key); } } if (!action || !key) { g_warning (_("Key Binding (%s) is incomplete\n"), gconf_key); return FALSE; } tmp_elem = g_slist_find_custom (binding_list, gconf_key, 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); } new_binding->binding_str = key; new_binding->action = action; new_binding->gconf_key = gconf_key; new_binding->previous_key.keysym = new_binding->key.keysym; new_binding->previous_key.state = new_binding->key.state; new_binding->previous_key.keycode = new_binding->key.keycode; if (parse_binding (new_binding)) binding_list = g_slist_append (binding_list, new_binding); else { g_warning (_("Key Binding (%s) is invalid\n"), gconf_key); g_free (new_binding->binding_str); g_free (new_binding->action); return FALSE; } return TRUE; } static gboolean key_already_used (Binding *binding) { GSList *li; for (li = binding_list; li != NULL; li = li->next) { Binding *tmp_binding = (Binding*) li->data; if (tmp_binding != binding && tmp_binding->key.keycode == binding->key.keycode && tmp_binding->key.state == binding->key.state) return TRUE; } return FALSE; } static void grab_key (GdkWindow *root, Key *key, int result, gboolean grab) { gdk_error_trap_push (); if (grab) XGrabKey (GDK_DISPLAY(), key->keycode, (result | key->state), GDK_WINDOW_XID (root), True, GrabModeAsync, GrabModeAsync); else XUngrabKey(GDK_DISPLAY(), key->keycode, (result | key->state), GDK_WINDOW_XID (root)); gdk_flush (); if (gdk_error_trap_pop ()) { g_warning (_("It seems that another application already has" " access to key '%u'."), key->keycode); } } /* inspired from all_combinations from gnome-panel/gnome-panel/global-keys.c */ #define N_BITS 32 static void do_grab (gboolean grab, Key *key) { int indexes[N_BITS];/*indexes of bits we need to flip*/ int i, bit, bits_set_cnt; int uppervalue; guint mask_to_traverse = IGNORED_MODS & ~ key->state; bit = 0; for (i = 0; i < N_BITS; i++) { if (mask_to_traverse & (1<next) { GdkScreen *screen = l->data; grab_key (gdk_screen_get_root_window (screen), key, result, grab); } } } static void binding_register_keys (void) { GSList *li; gdk_error_trap_push(); /* Now check for changes and grab new key if not already used */ for (li = binding_list ; li != NULL; li = li->next) { Binding *binding = (Binding *) li->data; if (binding->previous_key.keycode != binding->key.keycode || binding->previous_key.state != binding->key.state) { /* Ungrab key if it changed and not clashing with previously set binding */ if (!key_already_used (binding)) { if (binding->previous_key.keycode) do_grab (FALSE, &binding->previous_key); do_grab (TRUE, &binding->key); binding->previous_key.keysym = binding->key.keysym; binding->previous_key.state = binding->key.state; binding->previous_key.keycode = binding->key.keycode; } else g_warning (_("Key Binding (%s) is already in use\n"), binding->binding_str); } } gdk_flush (); gdk_error_trap_pop(); } static void bindings_callback (GConfEntry *entry) { /* ensure we get binding dir not a sub component */ gchar** key_elems = g_strsplit (gconf_entry_get_key (entry), "/", 15); gchar* binding_entry = g_strdup_printf ("/%s/%s/%s/%s", key_elems[1], key_elems[2], key_elems[3], key_elems[4]); g_strfreev (key_elems); bindings_get_entry (binding_entry); g_free (binding_entry); binding_register_keys (); } static GdkFilterReturn keybindings_filter (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) { XEvent *xevent = (XEvent *)gdk_xevent; guint keycode, state; GSList *li; if(xevent->type != KeyPress) return GDK_FILTER_CONTINUE; keycode = xevent->xkey.keycode; state = xevent->xkey.state; for (li = binding_list; li != NULL; li = li->next) { Binding *binding = (Binding*) li->data; if (keycode == binding->key.keycode && (state & USED_MODS) == binding->key.state) { 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; } void gnome_settings_keybindings_init (GConfClient *client) { GdkDisplay *dpy = gdk_display_get_default (); GdkScreen *screen; int screen_num = gdk_display_get_n_screens (dpy); int i; gnome_settings_daemon_register_callback (GCONF_BINDING_DIR, bindings_callback); gdk_window_add_filter (gdk_get_default_root_window (), keybindings_filter, NULL); for (i = 0; i < screen_num; i++) { screen = gdk_display_get_screen (dpy, i); gdk_window_add_filter (gdk_screen_get_root_window (screen), keybindings_filter, NULL); } } void gnome_settings_keybindings_load (GConfClient *client) { GSList *list, *li; list = gconf_client_all_dirs (client, GCONF_BINDING_DIR, NULL); screens = get_screens_list (); for (li = list; li != NULL; li = li->next) { char *subdir = li->data; li->data = NULL; bindings_get_entry (subdir); } binding_register_keys (); }