summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--plugins/gtkui/hotkeys.c349
2 files changed, 323 insertions, 28 deletions
diff --git a/README b/README
index e4fc8c1a..af0fab4d 100644
--- a/README
+++ b/README
@@ -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);