/* GTK hotkeys configuration for Deadbeef player Copyright (C) 2009-2013 Alexey Yakovenko and other contributors This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ // deadbeef core doesn't have any special hotkeys code, // but we need some common hotkey definition to share between plugins // so here is the example structure to use when implementing hotkeys support /* // corresponding line in the config file: // hotkey.keyX "key combination" CONTEXT IS_GLOBAL ACTION_ID // action contexts are defined in deadbeef.h // // example: // hotkey.key1 "Super+n" 0 1 playback_random // this would mean "execute playback_random action when Super+n is pressed globally" // // context can be main, selection, playlist or nowplaying // TODO: do we need anything else, like widget contexts?.. typedef struct { char *key_combination; int context; // NULL, selection, playlist, nowplaying DB_plugin_action_t *action; unsigned is_global : 1; } ddb_hotkey_t; */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "../../gettext.h" #include "support.h" #include "gtkui.h" #include "interface.h" #include "../libparser/parser.h" #include "../hotkeys/hotkeys.h" #ifndef __APPLE__ #include // only for the KeySym type #endif #include "hotkeys.h" #include "../../strdupa.h" int gtkui_hotkeys_changed = 0; void on_hotkeys_actions_cursor_changed (GtkTreeView *treeview, gpointer user_data); static GtkWidget *prefwin; static guint last_accel_key = 0; static guint last_accel_mask = 0; static const char *ctx_names[DDB_ACTION_CTX_COUNT]; static void unescape_forward_slash (const char *src, char *dst, int size) { char *start = dst; while (*src) { if (dst - start >= size - 1) { break; } if (*src == '\\' && *(src+1) == '/') { src++; } *dst++ = *src++; } *dst = 0; } static void prettify_forward_slash (const char *src, char *dst, int size) { char *start = dst; const char arrow[] = " → "; int larrow = strlen (arrow); while (*src && size > 1) { if (*src == '\\' && *(src+1) == '/') { src++; } else if (*src == '/' && size > larrow) { memcpy (dst, arrow, larrow); src++; dst += larrow; size -= larrow; continue; } *dst++ = *src++; size--; } *dst = 0; } static const char * get_display_action_title (const char *title) { const char *t = title + strlen (title) - 1; while (t > title) { if (*t != '/' || *(t-1) == '\\') { t--; continue; } t++; break; } return t; } DB_plugin_action_t * find_action_by_name (const char *command) { // find action with this name, and add to list DB_plugin_action_t *actions = NULL; DB_plugin_t **plugins = deadbeef->plug_get_list (); for (int i = 0; plugins[i]; i++) { DB_plugin_t *p = plugins[i]; if (p->get_actions) { actions = p->get_actions (NULL); while (actions) { if (actions->name && actions->title && !strcasecmp (actions->name, command)) { break; // found } actions = actions->next; } if (actions) { break; } } } return actions; } static int hotkeys_load (void) { GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys))); gtk_list_store_clear (hkstore); int n_items = 0; DB_conf_item_t *item = deadbeef->conf_find ("hotkey.", NULL); while (item) { char token[MAX_TOKEN]; char keycombo[MAX_TOKEN]; int ctx; int isglobal; DB_plugin_action_t *action; const char *script = item->value; if ((script = gettoken (script, keycombo)) == 0) { goto out; } if ((script = gettoken (script, token)) == 0) { goto out; } ctx = atoi (token); if (ctx < 0 || ctx >= DDB_ACTION_CTX_COUNT) { goto out; } if ((script = gettoken (script, token)) == 0) { goto out; } isglobal = atoi (token); if ((script = gettoken (script, token)) == 0) { goto out; } action = find_action_by_name (token); if (!action) { goto out; } GtkTreeIter iter; gtk_list_store_append (hkstore, &iter); const char *t = get_display_action_title (action->title); char title[100]; unescape_forward_slash (t, title, sizeof (title)); gtk_list_store_set (hkstore, &iter, 0, keycombo, 1, title, 2, ctx_names[ctx], 3, isglobal, 4, action->name, 5, ctx, -1); n_items++; out: item = deadbeef->conf_find ("hotkey.", item); } return n_items; } static void hotkeys_save (void) { GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys))); deadbeef->conf_remove_items ("hotkey.key"); GtkTreePath *path = gtk_tree_path_new_first (); GtkTreeIter iter; gboolean res = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (hkstore), &iter); int i = 1; while (res) { GValue keycombo = {0,}, action = {0,}, context = {0,}, global = {0,}; gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 0, &keycombo); gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 4, &action); gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 5, &context); gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 3, &global); char key[100]; snprintf (key, sizeof (key), "hotkey.key%02d", i); char value[1000]; snprintf (value, sizeof (value), "\"%s\" %d %d %s", g_value_get_string (&keycombo), g_value_get_int (&context), g_value_get_boolean (&global), g_value_get_string (&action)); deadbeef->conf_set_str (key, value); res = gtk_tree_model_iter_next (GTK_TREE_MODEL (hkstore), &iter); i++; } // FIXME: should be done in a more generic sendmessage DB_plugin_t *hkplug = deadbeef->plug_get_for_id ("hotkeys"); if (hkplug) { ((DB_hotkeys_plugin_t *)hkplug)->reset (); } } const char * action_tree_append (const char *title, GtkTreeStore *store, GtkTreeIter *root_iter, GtkTreeIter *iter) { char *t = strdupa (title); char *p = t; GtkTreeIter i; int got_iter = 0; for (;;) { char *s = strchr (p, '/'); // find unescaped forward slash if (s == p) { p++; continue; } if (s && s > p && *(s-1) == '\\') { p = s + 1; continue; } if (!s) { break; } *s = 0; // find iter in the current root with name==p gboolean res = gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &i, root_iter); if (!res) { gtk_tree_store_append (store, &i, root_iter); gtk_tree_store_set (store, &i, 0, p, 1, NULL, 2, -1, -1); root_iter = &i; } else { int found = 0; do { GValue val = {0,}; gtk_tree_model_get_value (GTK_TREE_MODEL (store), &i, 0, &val); const char *n = g_value_get_string (&val); if (n && !strcmp (n, p)) { root_iter = &i; found = 1; break; } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &i)); if (!found) { gtk_tree_store_append (store, &i, root_iter); gtk_tree_store_set (store, &i, 0, p, 1, NULL, 2, -1, -1); root_iter = &i; } } p = s+1; } gtk_tree_store_append (store, iter, root_iter); return get_display_action_title (title); } typedef struct { const char *name; int ctx; GtkWidget *treeview; } actionbinding_t; static gboolean set_current_action (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GValue val = {0,}, ctx_val = {0,}; gtk_tree_model_get_value (model, iter, 1, &val); gtk_tree_model_get_value (model, iter, 2, &ctx_val); actionbinding_t *binding = data; const char *name = g_value_get_string (&val); if (name && binding->name && !strcmp (binding->name, name) && binding->ctx == g_value_get_int (&ctx_val)) { gtk_tree_view_expand_to_path (GTK_TREE_VIEW (binding->treeview), path); gtk_tree_view_set_cursor (GTK_TREE_VIEW (binding->treeview), path, NULL, FALSE); return TRUE; } return FALSE; } void init_action_tree (GtkWidget *actions, const char *act, int ctx) { GtkTreeViewColumn *hk_act_col1 = gtk_tree_view_column_new_with_attributes (_("Action"), gtk_cell_renderer_text_new (), "text", 0, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (actions), hk_act_col1); // traverse all plugins and collect all exported actions to treeview // column0: title // column1: ID (invisible) // column2: ctx (invisible GtkTreeStore *actions_store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); GtkTreeIter action_main_iter; gtk_tree_store_append (actions_store, &action_main_iter, NULL); gtk_tree_store_set (actions_store, &action_main_iter, 0, _("Main"), -1); GtkTreeIter action_selection_iter; gtk_tree_store_append (actions_store, &action_selection_iter, NULL); gtk_tree_store_set (actions_store, &action_selection_iter, 0, _("Selected track(s)"), -1); GtkTreeIter action_playlist_iter; gtk_tree_store_append (actions_store, &action_playlist_iter, NULL); gtk_tree_store_set (actions_store, &action_playlist_iter, 0, _("Current playlist"), -1); GtkTreeIter action_nowplaying_iter; gtk_tree_store_append (actions_store, &action_nowplaying_iter, NULL); gtk_tree_store_set (actions_store, &action_nowplaying_iter, 0, _("Now playing"), -1); DB_plugin_t **plugins = deadbeef->plug_get_list (); for (int i = 0; plugins[i]; i++) { DB_plugin_t *p = plugins[i]; if (p->get_actions) { DB_plugin_action_t *actions = p->get_actions (NULL); while (actions) { if (actions->name && actions->title) { // only add actions with both the name and the title char title[100]; GtkTreeIter iter; const char *t; if (actions->flags & DB_ACTION_COMMON) { t = action_tree_append (actions->title, actions_store, &action_main_iter, &iter); unescape_forward_slash (t, title, sizeof (title)); gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_MAIN, -1); } if (actions->flags & (DB_ACTION_SINGLE_TRACK | DB_ACTION_MULTIPLE_TRACKS | DB_ACTION_CAN_MULTIPLE_TRACKS)) { t = action_tree_append (actions->title, actions_store, &action_selection_iter, &iter); unescape_forward_slash (t, title, sizeof (title)); gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_SELECTION, -1); t = action_tree_append (actions->title, actions_store, &action_playlist_iter, &iter); unescape_forward_slash (t, title, sizeof (title)); gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_PLAYLIST, -1); t = action_tree_append (actions->title, actions_store, &action_nowplaying_iter, &iter); unescape_forward_slash (t, title, sizeof (title)); gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_NOWPLAYING, -1); } } else { // fprintf (stderr, "WARNING: action %s/%s from plugin %s is missing name and/or title\n", actions->name, actions->title, p->name); } actions = actions->next; } } } gtk_tree_view_set_model (GTK_TREE_VIEW (actions), GTK_TREE_MODEL (actions_store)); if (act && ctx != -1) { actionbinding_t binding = { .name = act, .ctx = ctx, .treeview = actions }; gtk_tree_model_foreach (GTK_TREE_MODEL (actions_store), set_current_action, (void*)&binding); } } void on_hotkeys_actions_clicked (GtkButton *button, gpointer user_data) { GtkTreePath *path; GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)); GtkTreeIter iter; if (!path || !gtk_tree_model_get_iter (model, &iter, path)) { return; } // get action name from iter GValue val_name = {0,}, val_ctx = {0,}; gtk_tree_model_get_value (model, &iter, 4, &val_name); gtk_tree_model_get_value (model, &iter, 5, &val_ctx); const char *act = g_value_get_string (&val_name); int ctx = g_value_get_int (&val_ctx); GtkWidget *dlg = create_select_action (); GtkWidget *treeview = lookup_widget (dlg, "actions"); init_action_tree (treeview, act, ctx); int response = gtk_dialog_run (GTK_DIALOG (dlg)); if (response == GTK_RESPONSE_OK) { on_hotkeys_actions_cursor_changed (GTK_TREE_VIEW (treeview), NULL); GtkTreePath *path; gtk_tree_view_get_cursor (GTK_TREE_VIEW (treeview), &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); GtkTreeIter iter; const char *name = NULL; int ctx = -1; if (path && gtk_tree_model_get_iter (model, &iter, path)) { GValue val = {0,}; gtk_tree_model_get_value (model, &iter, 1, &val); name = g_value_get_string (&val); GValue val_ctx = {0,}; gtk_tree_model_get_value (model, &iter, 2, &val_ctx); ctx = g_value_get_int (&val_ctx); } set_button_action_label (name, ctx, lookup_widget (prefwin, "hotkeys_actions")); } gtk_widget_destroy (dlg); } void prefwin_init_hotkeys (GtkWidget *_prefwin) { DB_plugin_t *hkplug = deadbeef->plug_get_for_id ("hotkeys"); if (!hkplug) { // remove hotkeys tab gtk_notebook_remove_page (GTK_NOTEBOOK (lookup_widget (_prefwin, "notebook")), 6); return; } gtkui_hotkeys_changed = 0; ctx_names[DDB_ACTION_CTX_MAIN] = _("Main"); ctx_names[DDB_ACTION_CTX_SELECTION] = _("Selection"); ctx_names[DDB_ACTION_CTX_PLAYLIST] = _("Playlist"); ctx_names[DDB_ACTION_CTX_NOWPLAYING] = _("Now playing"); prefwin = _prefwin; GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); // setup hotkeys list GtkTreeViewColumn *hk_col1 = gtk_tree_view_column_new_with_attributes (_("Key combination"), gtk_cell_renderer_text_new (), "text", 0, NULL); gtk_tree_view_column_set_resizable (hk_col1, TRUE); GtkTreeViewColumn *hk_col2 = gtk_tree_view_column_new_with_attributes (_("Action"), gtk_cell_renderer_text_new (), "text", 1, NULL); gtk_tree_view_column_set_resizable (hk_col2, TRUE); GtkTreeViewColumn *hk_col3 = gtk_tree_view_column_new_with_attributes (_("Context"), gtk_cell_renderer_text_new (), "text", 2, NULL); gtk_tree_view_column_set_resizable (hk_col3, TRUE); GtkTreeViewColumn *hk_col4 = gtk_tree_view_column_new_with_attributes (_("Is global"), gtk_cell_renderer_text_new (), "text", 3, NULL); gtk_tree_view_column_set_resizable (hk_col4, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col1); gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col2); gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col3); gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col4); // column0: keycombo string // column1: action title // column2: context title // column3: is_global // column4: action title id (hidden) // column5: context id (hidden) GtkListStore *hkstore = gtk_list_store_new (6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_INT); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE); gtk_tree_view_set_model (GTK_TREE_VIEW (hotkeys), GTK_TREE_MODEL (hkstore)); int n_hotkeys = hotkeys_load (); } void set_button_action_label (const char *act, int action_ctx, GtkWidget *button) { if (act && action_ctx >= 0) { DB_plugin_action_t *action = find_action_by_name (act); if (action) { const char *ctx_str = NULL; switch (action_ctx) { case DDB_ACTION_CTX_MAIN: break; case DDB_ACTION_CTX_SELECTION: ctx_str = _("Selected tracks"); break; case DDB_ACTION_CTX_PLAYLIST: ctx_str = _("Tracks in current playlist"); break; case DDB_ACTION_CTX_NOWPLAYING: ctx_str = _("Currently playing track"); break; } char s[200]; snprintf (s, sizeof (s), "%s%s%s", ctx_str ? ctx_str : "", ctx_str ? " ⇒ ": "", action->title); char s_fixed[200]; prettify_forward_slash (s, s_fixed, sizeof (s_fixed)); gtk_button_set_label (GTK_BUTTON (button), s_fixed); return; } } gtk_button_set_label (GTK_BUTTON (button), _("")); } void on_hotkeys_list_cursor_changed (GtkTreeView *treeview, gpointer user_data) { GtkTreePath *path; gtk_tree_view_get_cursor (treeview, &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (treeview); GtkTreeIter iter; int changed = gtkui_hotkeys_changed; if (path && gtk_tree_model_get_iter (model, &iter, path)) { GtkWidget *actions = lookup_widget (prefwin, "hotkeys_actions"); gtk_widget_set_sensitive (actions, TRUE); // get action name from iter GValue val_name = {0,}, val_ctx = {0,}; gtk_tree_model_get_value (model, &iter, 4, &val_name); gtk_tree_model_get_value (model, &iter, 5, &val_ctx); const char *name = g_value_get_string (&val_name); set_button_action_label (name, g_value_get_int (&val_ctx), actions); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), TRUE); GValue val_isglobal = {0,}; gtk_tree_model_get_value (model, &iter, 3, &val_isglobal); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), g_value_get_boolean (&val_isglobal)); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), TRUE); GValue val_keycombo = {0,}; gtk_tree_model_get_value (model, &iter, 0, &val_keycombo); const char *keycombo = g_value_get_string (&val_keycombo); gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), keycombo ? keycombo : ""); } else { gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE); gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), _("")); } if (path) { gtk_tree_path_free (path); } gtkui_hotkeys_changed = changed; } void on_hotkey_add_clicked (GtkButton *button, gpointer user_data) { GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys))); GtkTreeIter iter; gtk_list_store_append (hkstore, &iter); gtk_list_store_set (hkstore, &iter, 0, _(""), 1, _(""), 2, _(""), 3, 0, 4, NULL, 5, -1, -1); GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (hkstore), &iter); gtk_tree_view_set_cursor (GTK_TREE_VIEW (hotkeys), path, NULL, FALSE); gtk_tree_path_free (path); gtk_widget_grab_focus (hotkeys); gtkui_hotkeys_changed = 1; } void on_hotkey_remove_clicked (GtkButton *button, gpointer user_data) { GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkTreePath *path; gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL); GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys))); GtkTreeIter iter; gtk_tree_model_get_iter (GTK_TREE_MODEL (hkstore), &iter, path); gtk_list_store_remove (hkstore, &iter); set_button_action_label (NULL, 0, lookup_widget (prefwin, "hotkeys_actions")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), FALSE); gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), _("")); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE); gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE); gtkui_hotkeys_changed = 1; } void on_hotkeys_actions_cursor_changed (GtkTreeView *treeview, gpointer user_data) { GtkTreePath *path; gtk_tree_view_get_cursor (treeview, &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (treeview); GtkTreeIter iter; if (path && gtk_tree_model_get_iter (model, &iter, path)) { GValue val = {0,}; gtk_tree_model_get_value (model, &iter, 1, &val); const gchar *name = g_value_get_string (&val); DB_plugin_action_t *action = NULL; int ctx = 0; if (name) { action = find_action_by_name (name); GValue val_ctx = {0,}; gtk_tree_model_get_value (model, &iter, 2, &val_ctx); ctx = g_value_get_int (&val_ctx); } // update the tree { GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkTreePath *path; gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)); GtkTreeIter iter; if (path && gtk_tree_model_get_iter (model, &iter, path)) { if (action) { const char *t = get_display_action_title (action->title); char title[100]; unescape_forward_slash (t, title, sizeof (title)); gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, title, 4, action->name, 5, ctx, 2, ctx_names[ctx], -1); } else { gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, _(""), 4, NULL, 2, _(""), -1); } } } } } void on_hotkey_is_global_toggled (GtkToggleButton *togglebutton, gpointer user_data) { // update the tree GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkTreePath *path; gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)); GtkTreeIter iter; if (path && gtk_tree_model_get_iter (model, &iter, path)) { gtk_list_store_set (GTK_LIST_STORE (model), &iter, 3, gtk_toggle_button_get_active (togglebutton), -1); } gtkui_hotkeys_changed = 1; } typedef struct { const char *name; int keysym; } xkey_t; #define KEY(kname, kcode) { .name=kname, .keysym=kcode }, static const xkey_t keys[] = { #include "../hotkeys/keysyms.inc" }; static const char * get_name_for_keycode (int keycode) { for (int i = 0; keys[i].name; i++) { if (keycode == keys[i].keysym) { return keys[i].name; } } return NULL; } static int grabbed = 0; static void get_keycombo_string (guint accel_key, GdkModifierType accel_mods, char *new_value) { // build value new_value[0] = 0; if (!accel_key) { strcpy (new_value, _("")); return; } if (accel_mods & GDK_SHIFT_MASK) { strcat (new_value, "Shift "); } if (accel_mods & GDK_CONTROL_MASK) { strcat (new_value, "Ctrl "); } if (accel_mods & GDK_SUPER_MASK) { strcat (new_value, "Super "); } if (accel_mods & GDK_MOD1_MASK) { strcat (new_value, "Alt "); } // translate numlock keycodes into non-numlock codes switch (accel_key) { case GDK_KP_0: accel_key = GDK_KP_Insert; break; case GDK_KP_1: accel_key = GDK_KP_End; break; case GDK_KP_2: accel_key = GDK_KP_Down; break; case GDK_KP_3: accel_key = GDK_KP_Page_Down; break; case GDK_KP_4: accel_key = GDK_KP_Left; break; case GDK_KP_6: accel_key = GDK_KP_Right; break; case GDK_KP_7: accel_key = GDK_KP_Home; break; case GDK_KP_8: accel_key = GDK_KP_Up; break; case GDK_KP_9: accel_key = GDK_KP_Page_Up; break; } const char *name = get_name_for_keycode (accel_key); if (!name) { strcpy (new_value, _("")); return; } strcat (new_value, name); } gboolean on_hotkeys_set_key_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { GdkModifierType accel_mods = 0; guint accel_key; gchar *path; gboolean edited; gboolean cleared; GdkModifierType consumed_modifiers; GdkDisplay *display; GtkTreePath *curpath; GtkTreeIter iter; if (!grabbed) { return FALSE; } display = gtk_widget_get_display (widget); if (event->is_modifier) return TRUE; edited = FALSE; cleared = FALSE; gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display), event->hardware_keycode, event->state, 0, &accel_key, NULL, NULL, &consumed_modifiers); if (accel_key == GDK_ISO_Left_Tab) accel_key = GDK_Tab; accel_mods = event->state & gtk_accelerator_get_default_mod_mask (); /* Filter consumed modifiers */ accel_mods &= ~(consumed_modifiers&~GDK_SHIFT_MASK); /* Put shift back if it changed the case of the key, not otherwise. */ int lower = gdk_keyval_to_lower (accel_key); if (lower != accel_key) { accel_key = lower; } char name[1000]; gtk_button_set_label (GTK_BUTTON (widget), _("")); GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list"); GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)); // check if this key already registered get_keycombo_string (accel_key, accel_mods, name); gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &curpath, NULL); gboolean res = gtk_tree_model_get_iter_first (model, &iter); while (res) { GtkTreePath *iterpath = gtk_tree_model_get_path (model, &iter); if (!curpath || gtk_tree_path_compare (iterpath, curpath)) { GValue keycombo = {0,}; gtk_tree_model_get_value (model, &iter, 0, &keycombo); const char *val = g_value_get_string (&keycombo); if (val && !strcmp (val, name)) { gtk_tree_path_free (iterpath); break; } } gtk_tree_path_free (iterpath); res = gtk_tree_model_iter_next (model, &iter); } if (res) { // duplicate gtk_button_set_label (GTK_BUTTON (widget), _("Duplicate key combination!")); gtk_widget_error_bell (widget); goto out; } last_accel_key = accel_key; last_accel_mask = accel_mods; get_keycombo_string (last_accel_key, last_accel_mask, name); gtk_button_set_label (GTK_BUTTON (widget), name); // update the tree if (curpath && gtk_tree_model_get_iter (model, &iter, curpath)) { gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, name, -1); } out: if (curpath) { gtk_tree_path_free (curpath); } gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); grabbed = 0; gtkui_hotkeys_changed = 1; return TRUE; } static void hotkey_grab_focus (GtkWidget *widget) { GdkDisplay *display = gtk_widget_get_display (widget); if (grabbed) { return; } grabbed = 0; if (GDK_GRAB_SUCCESS != gdk_keyboard_grab (gtk_widget_get_window (widget), FALSE, GDK_CURRENT_TIME)) { return; } if (gdk_pointer_grab (gtk_widget_get_window (widget), FALSE, GDK_BUTTON_PRESS_MASK, NULL, NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) { gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); return; } gtk_button_set_label (GTK_BUTTON (widget), _("New key combination...")); grabbed = 1; } void on_hotkeys_set_key_clicked (GtkButton *button, gpointer user_data) { hotkey_grab_focus (GTK_WIDGET (button)); } void on_hotkeys_apply_clicked (GtkButton *button, gpointer user_data) { hotkeys_save (); gtkui_hotkeys_changed = 0; } void on_hotkeys_revert_clicked (GtkButton *button, gpointer user_data) { hotkeys_load (); gtkui_hotkeys_changed = 0; } void on_hotkeys_defaults_clicked (GtkButton *button, gpointer user_data) { GtkWidget *dlg = gtk_message_dialog_new (GTK_WINDOW (prefwin), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, _("All your custom-defined hotkeys will be lost.")); gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (prefwin)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), _("This operation cannot be undone. Proceed?")); gtk_window_set_title (GTK_WINDOW (dlg), _("Warning")); int response = gtk_dialog_run (GTK_DIALOG (dlg)); gtk_widget_destroy (dlg); if (response != GTK_RESPONSE_YES) { return; } gtkui_set_default_hotkeys (); hotkeys_load (); gtkui_hotkeys_changed = 0; } void gtkui_set_default_hotkeys (void) { deadbeef->conf_remove_items ("hotkey.key"); deadbeef->conf_set_str ("hotkey.key01", "\"Ctrl f\" 0 0 find"); deadbeef->conf_set_str ("hotkey.key02", "\"Ctrl o\" 0 0 open_files"); deadbeef->conf_set_str ("hotkey.key03", "\"Ctrl q\" 0 0 quit"); deadbeef->conf_set_str ("hotkey.key04", "\"Ctrl n\" 0 0 new_playlist"); deadbeef->conf_set_str ("hotkey.key05", "\"Ctrl a\" 0 0 select_all"); deadbeef->conf_set_str ("hotkey.key06", "\"Escape\" 0 0 deselect_all"); deadbeef->conf_set_str ("hotkey.key07", "\"Ctrl m\" 0 0 toggle_stop_after_current"); deadbeef->conf_set_str ("hotkey.key08", "\"Ctrl j\" 0 0 jump_to_current_track"); deadbeef->conf_set_str ("hotkey.key09", "\"F1\" 0 0 help"); deadbeef->conf_set_str ("hotkey.key10", "\"Delete\" 1 0 remove_from_playlist"); deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist"); deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist"); deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist"); deadbeef->conf_set_str ("hotkey.key14", "\"Return\" 0 0 play"); deadbeef->conf_set_str ("hotkey.key15", "\"Ctrl p\" 0 0 toggle_pause"); deadbeef->conf_set_str ("hotkey.key16", "\"Alt 1\" 0 0 playlist1"); deadbeef->conf_set_str ("hotkey.key17", "\"Alt 2\" 0 0 playlist2"); deadbeef->conf_set_str ("hotkey.key18", "\"Alt 3\" 0 0 playlist3"); deadbeef->conf_set_str ("hotkey.key19", "\"Alt 4\" 0 0 playlist4"); deadbeef->conf_set_str ("hotkey.key20", "\"Alt 5\" 0 0 playlist5"); deadbeef->conf_set_str ("hotkey.key21", "\"Alt 6\" 0 0 playlist6"); deadbeef->conf_set_str ("hotkey.key22", "\"Alt 7\" 0 0 playlist7"); deadbeef->conf_set_str ("hotkey.key23", "\"Alt 8\" 0 0 playlist8"); deadbeef->conf_set_str ("hotkey.key24", "\"Alt 9\" 0 0 playlist9"); deadbeef->conf_set_str ("hotkey.key25", "\"Alt 0\" 0 0 playlist10"); deadbeef->conf_set_str ("hotkey.key26", "z 0 0 prev"); deadbeef->conf_set_str ("hotkey.key27", "x 0 0 play"); deadbeef->conf_set_str ("hotkey.key28", "c 0 0 toggle_pause"); deadbeef->conf_set_str ("hotkey.key29", "v 0 0 stop"); deadbeef->conf_set_str ("hotkey.key30", "b 0 0 next"); deadbeef->conf_set_str ("hotkey.key31", "n 0 0 playback_random"); deadbeef->conf_set_str ("hotkey.key32", "\"Ctrl k\" 0 0 toggle_stop_after_album"); deadbeef->conf_save (); } void gtkui_import_0_5_global_hotkeys (void) { int n = 40; deadbeef->conf_lock (); DB_conf_item_t *item = deadbeef->conf_find ("hotkeys.key", NULL); while (item) { char *val = strdupa (item->value); char *colon = strchr (val, ':'); if (colon) { *colon++ = 0; while (*colon && *colon == ' ') { colon++; } if (*colon) { char newkey[100]; char newval[100]; snprintf (newkey, sizeof (newkey), "hotkey.key%02d", n); snprintf (newval, sizeof (newval), "\"%s\" 0 1 %s", val, colon); deadbeef->conf_set_str (newkey, newval); n++; } } item = deadbeef->conf_find ("hotkeys.", item); } deadbeef->conf_unlock (); }