diff options
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | plugins/gtkui/hotkeys.c | 349 |
2 files changed, 323 insertions, 28 deletions
@@ -41,7 +41,7 @@ most of them are optional, which means deadbeef will build and run without them, libsndfile: for sndfile plugin libcdio + libcddb: for cd audio plugin ffmpeg < 0.11: for ffmpeg plugin (versions >= 0.11 are not supported) - xlib: for global hotkeys and for gtkui opengl support + xlib: for global hotkeys and gtkui dbus: for notification daemon support (OSD current song notifications) pulseaudio: for PulseAudio output plugin faad2: for AAC plugin diff --git a/plugins/gtkui/hotkeys.c b/plugins/gtkui/hotkeys.c index be67f9e3..ec852710 100644 --- a/plugins/gtkui/hotkeys.c +++ b/plugins/gtkui/hotkeys.c @@ -20,13 +20,48 @@ 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 <config.h> #endif #include <gtk/gtk.h> +#include <string.h> +#include <stdlib.h> #include "../../gettext.h" #include "support.h" #include "gtkui.h" +#include "parser.h" +#include "../hotkeys/hotkeys.h" +#include <X11/Xlib.h> // only for the KeySym type + +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) { @@ -43,8 +78,37 @@ unescape_forward_slash (const char *src, char *dst, int size) { *dst = 0; } +static 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; +} + void -prefwin_init_hotkeys (GtkWidget *prefwin) { +prefwin_init_hotkeys (GtkWidget *_prefwin) { + 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"); GtkWidget *actions = lookup_widget (prefwin, "hotkeys_actions"); @@ -61,14 +125,57 @@ prefwin_init_hotkeys (GtkWidget *prefwin) { 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); - GtkListStore *hkstore = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); - - DB_conf_item_t *item = deadbeef->conf_find ("hotkeys.", NULL); + // 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, "hotkey_keycombo"), FALSE); + + int has_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); - gtk_list_store_set (hkstore, &iter, 0, "key", 1, "action", 2, "context", 3, "global", -1); - item = deadbeef->conf_find ("hotkeys.", item); + + gtk_list_store_set (hkstore, &iter, 0, keycombo, 1, action->title, 2, ctx_names[ctx], 3, isglobal, 4, action->name, 5, ctx, -1); + has_items = 1; + +out: + item = deadbeef->conf_find ("hotkey.", item); } gtk_tree_view_set_model (GTK_TREE_VIEW (hotkeys), GTK_TREE_MODEL (hkstore)); @@ -80,19 +187,20 @@ prefwin_init_hotkeys (GtkWidget *prefwin) { // traverse all plugins and collect all exported actions to dropdown // column0: title // column1: ID (invisible) - GtkTreeStore *actions_store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + // 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, NULL, -1); + 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, NULL, -1); + 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, NULL, -1); + 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, NULL, -1); + 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++) { @@ -107,15 +215,15 @@ prefwin_init_hotkeys (GtkWidget *prefwin) { GtkTreeIter iter; if (actions->flags & DB_ACTION_COMMON) { gtk_tree_store_append (actions_store, &iter, &action_main_iter); - gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, -1); + 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_ALLOW_MULTIPLE_TRACKS | DB_ACTION_CAN_MULTIPLE_TRACKS)) { gtk_tree_store_append (actions_store, &iter, &action_selection_iter); - gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, -1); - gtk_tree_store_append (actions_store, &iter, &action_nowplaying_iter); - gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, -1); + gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_SELECTION, -1); gtk_tree_store_append (actions_store, &iter, &action_playlist_iter); - gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, -1); + gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_PLAYLIST, -1); + gtk_tree_store_append (actions_store, &iter, &action_nowplaying_iter); + gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_NOWPLAYING, -1); } } else { @@ -127,13 +235,77 @@ prefwin_init_hotkeys (GtkWidget *prefwin) { } gtk_tree_view_set_model (GTK_TREE_VIEW (actions), GTK_TREE_MODEL (actions_store)); + + GtkTreePath *path = gtk_tree_path_new_first (); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (hotkeys), path, NULL, FALSE); + gtk_tree_path_free (path); +} + +typedef struct { + const char *name; + int ctx; +} 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 && !strcmp (binding->name, name) && binding->ctx == g_value_get_int (&ctx_val)) { + printf ("set cursor to %s\n", name); + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (lookup_widget (prefwin, "hotkeys_actions")), path); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (lookup_widget (prefwin, "hotkeys_actions")), path, NULL, FALSE); + return TRUE; + } + return FALSE; } 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; + 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); + // find in the action list and set as current + GtkTreeModel *actmodel = gtk_tree_view_get_model (GTK_TREE_VIEW (actions)); + actionbinding_t binding = { + .name = name, + .ctx = g_value_get_int (&val_ctx) + }; + gtk_tree_model_foreach (actmodel, set_current_action, (void*)&binding); + + 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, "hotkey_keycombo"), TRUE); + GValue val_keycombo = {0,}; + gtk_tree_model_get_value (model, &iter, 0, &val_keycombo); + gtk_entry_set_text (GTK_ENTRY (lookup_widget (prefwin, "hotkey_keycombo")), g_value_get_string (&val_keycombo)); + } + else { + gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (lookup_widget (prefwin, "hotkeys_actions")), NULL, NULL, 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, "hotkey_keycombo"), FALSE); + gtk_entry_set_text (GTK_ENTRY (lookup_widget (prefwin, "hotkey_keycombo")), ""); + } + if (path) { + gtk_tree_path_free (path); + } } @@ -157,7 +329,39 @@ 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) { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, action->title, 4, action->name, 5, ctx, 2, ctx_names[ctx], -1); + } + else { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, NULL, 4, NULL, 2, 0, -1); + } + } + } + } } @@ -165,12 +369,94 @@ 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); + } + } +} + +typedef struct { + const char *name; + KeySym 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; } -int grabbed = 0; -guint last_accel_key = 0; -guint last_accel_mask = 0; + +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_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); + strcat (new_value, name); +} gboolean on_hotkey_keycombo_key_press_event (GtkWidget *widget, @@ -218,17 +504,15 @@ on_hotkey_keycombo_key_press_event (GtkWidget *widget, if (accel_key != event->keyval) accel_mods |= GDK_SHIFT_MASK; + char name[1000]; gtk_entry_set_text (GTK_ENTRY (widget), _("")); if (accel_mods == 0) { switch (event->keyval) { case GDK_Escape: - { - gchar *name = gtk_accelerator_get_label (last_accel_key, last_accel_mask); + get_keycombo_string (last_accel_key, last_accel_mask, name); gtk_entry_set_text (GTK_ENTRY (widget), name); - g_free (name); - } goto out; /* cancel */ case GDK_BackSpace: gtk_entry_set_text (GTK_ENTRY (widget), ""); @@ -250,9 +534,20 @@ on_hotkey_keycombo_key_press_event (GtkWidget *widget, } last_accel_key = accel_key; last_accel_mask = accel_mods; - gchar *name = gtk_accelerator_get_label (accel_key, accel_mods); + get_keycombo_string (last_accel_key, last_accel_mask, name); gtk_entry_set_text (GTK_ENTRY (widget), name); - g_free (name); + + // 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, 0, name, -1); + } + } out: gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); |