1 /*
2 GTK hotkeys configuration for Deadbeef player
3 Copyright (C) 2009-2013 Alexey Yakovenko and other contributors
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source distribution.
22 */
23
24
25 // deadbeef core doesn't have any special hotkeys code,
26 // but we need some common hotkey definition to share between plugins
27 // so here is the example structure to use when implementing hotkeys support
28 /*
29 // corresponding line in the config file:
30 // hotkey.keyX "key combination" CONTEXT IS_GLOBAL ACTION_ID
31 // action contexts are defined in deadbeef.h
32 //
33 // example:
34 // hotkey.key1 "Super+n" 0 1 playback_random
35 // this would mean "execute playback_random action when Super+n is pressed globally"
36 //
37 // context can be main, selection, playlist or nowplaying
38 // TODO: do we need anything else, like widget contexts?..
39 typedef struct
40 {
41 char *key_combination;
42 int context; // NULL, selection, playlist, nowplaying
43 DB_plugin_action_t *action;
44 unsigned is_global : 1;
45 } ddb_hotkey_t;
46 */
47
48 #ifdef HAVE_CONFIG_H
49 # include <config.h>
50 #endif
51 #include <gtk/gtk.h>
52 #include <string.h>
53 #include <stdlib.h>
54 #include "../../gettext.h"
55 #include "support.h"
56 #include "gtkui.h"
57 #include "interface.h"
58 #include "../libparser/parser.h"
59 #include "../hotkeys/hotkeys.h"
60 #ifndef __APPLE__
61 #include <X11/Xlib.h> // only for the KeySym type
62 #endif
63 #include "hotkeys.h"
64 #include "../../strdupa.h"
65
66 int gtkui_hotkeys_changed = 0;
67
68 void
69 on_hotkeys_actions_cursor_changed (GtkTreeView *treeview,
70 gpointer user_data);
71
72 static GtkWidget *prefwin;
73 static guint last_accel_key = 0;
74 static guint last_accel_mask = 0;
75 static const char *ctx_names[DDB_ACTION_CTX_COUNT];
76
77 static void
unescape_forward_slash(const char * src,char * dst,int size)78 unescape_forward_slash (const char *src, char *dst, int size) {
79 char *start = dst;
80 while (*src) {
81 if (dst - start >= size - 1) {
82 break;
83 }
84 if (*src == '\\' && *(src+1) == '/') {
85 src++;
86 }
87 *dst++ = *src++;
88 }
89 *dst = 0;
90 }
91
92 static void
prettify_forward_slash(const char * src,char * dst,int size)93 prettify_forward_slash (const char *src, char *dst, int size) {
94 char *start = dst;
95 const char arrow[] = " → ";
96 int larrow = strlen (arrow);
97 while (*src && size > 1) {
98 if (*src == '\\' && *(src+1) == '/') {
99 src++;
100 }
101 else if (*src == '/' && size > larrow) {
102 memcpy (dst, arrow, larrow);
103 src++;
104 dst += larrow;
105 size -= larrow;
106 continue;
107 }
108 *dst++ = *src++;
109 size--;
110 }
111 *dst = 0;
112 }
113
114 static const char *
get_display_action_title(const char * title)115 get_display_action_title (const char *title) {
116 const char *t = title + strlen (title) - 1;
117 while (t > title) {
118 if (*t != '/' || *(t-1) == '\\') {
119 t--;
120 continue;
121 }
122 t++;
123 break;
124 }
125 return t;
126 }
127
128 DB_plugin_action_t *
find_action_by_name(const char * command)129 find_action_by_name (const char *command) {
130 // find action with this name, and add to list
131 DB_plugin_action_t *actions = NULL;
132 DB_plugin_t **plugins = deadbeef->plug_get_list ();
133 for (int i = 0; plugins[i]; i++) {
134 DB_plugin_t *p = plugins[i];
135 if (p->get_actions) {
136 actions = p->get_actions (NULL);
137 while (actions) {
138 if (actions->name && actions->title && !strcasecmp (actions->name, command)) {
139 break; // found
140 }
141 actions = actions->next;
142 }
143 if (actions) {
144 break;
145 }
146 }
147 }
148 return actions;
149 }
150
151 static int
hotkeys_load(void)152 hotkeys_load (void) {
153 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
154 GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)));
155 gtk_list_store_clear (hkstore);
156 int n_items = 0;
157 DB_conf_item_t *item = deadbeef->conf_find ("hotkey.", NULL);
158 while (item) {
159 char token[MAX_TOKEN];
160 char keycombo[MAX_TOKEN];
161 int ctx;
162 int isglobal;
163 DB_plugin_action_t *action;
164 const char *script = item->value;
165 if ((script = gettoken (script, keycombo)) == 0) {
166 goto out;
167 }
168 if ((script = gettoken (script, token)) == 0) {
169 goto out;
170 }
171 ctx = atoi (token);
172 if (ctx < 0 || ctx >= DDB_ACTION_CTX_COUNT) {
173 goto out;
174 }
175 if ((script = gettoken (script, token)) == 0) {
176 goto out;
177 }
178 isglobal = atoi (token);
179 if ((script = gettoken (script, token)) == 0) {
180 goto out;
181 }
182 action = find_action_by_name (token);
183 if (!action) {
184 goto out;
185 }
186
187 GtkTreeIter iter;
188 gtk_list_store_append (hkstore, &iter);
189
190 const char *t = get_display_action_title (action->title);
191 char title[100];
192 unescape_forward_slash (t, title, sizeof (title));
193 gtk_list_store_set (hkstore, &iter, 0, keycombo, 1, title, 2, ctx_names[ctx], 3, isglobal, 4, action->name, 5, ctx, -1);
194 n_items++;
195
196 out:
197 item = deadbeef->conf_find ("hotkey.", item);
198 }
199 return n_items;
200 }
201
202 static void
hotkeys_save(void)203 hotkeys_save (void) {
204 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
205 GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)));
206 deadbeef->conf_remove_items ("hotkey.key");
207
208 GtkTreePath *path = gtk_tree_path_new_first ();
209 GtkTreeIter iter;
210 gboolean res = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (hkstore), &iter);
211 int i = 1;
212 while (res) {
213 GValue keycombo = {0,}, action = {0,}, context = {0,}, global = {0,};
214 gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 0, &keycombo);
215 gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 4, &action);
216 gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 5, &context);
217 gtk_tree_model_get_value (GTK_TREE_MODEL (hkstore), &iter, 3, &global);
218 char key[100];
219 snprintf (key, sizeof (key), "hotkey.key%02d", i);
220 char value[1000];
221 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));
222 deadbeef->conf_set_str (key, value);
223
224 res = gtk_tree_model_iter_next (GTK_TREE_MODEL (hkstore), &iter);
225 i++;
226 }
227 // FIXME: should be done in a more generic sendmessage
228 DB_plugin_t *hkplug = deadbeef->plug_get_for_id ("hotkeys");
229 if (hkplug) {
230 ((DB_hotkeys_plugin_t *)hkplug)->reset ();
231 }
232 }
233
234 const char *
action_tree_append(const char * title,GtkTreeStore * store,GtkTreeIter * root_iter,GtkTreeIter * iter)235 action_tree_append (const char *title, GtkTreeStore *store, GtkTreeIter *root_iter, GtkTreeIter *iter) {
236 char *t = strdupa (title);
237 char *p = t;
238 GtkTreeIter i;
239 GtkTreeIter newroot;
240 int got_iter = 0;
241 for (;;) {
242 char *s = strchr (p, '/');
243 // find unescaped forward slash
244 if (s == p) {
245 p++;
246 continue;
247 }
248 if (s && s > p && *(s-1) == '\\') {
249 p = s + 1;
250 continue;
251 }
252 if (!s) {
253 break;
254 }
255 *s = 0;
256 // find iter in the current root with name==p
257 gboolean res = gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &i, root_iter);
258 if (!res) {
259 gtk_tree_store_append (store, &i, root_iter);
260 gtk_tree_store_set (store, &i, 0, p, 1, NULL, 2, -1, -1);
261 memcpy (&newroot, &i, sizeof (GtkTreeIter));
262 root_iter = &newroot;
263 }
264 else {
265 int found = 0;
266 do {
267 GValue val = {0,};
268 gtk_tree_model_get_value (GTK_TREE_MODEL (store), &i, 0, &val);
269 const char *n = g_value_get_string (&val);
270 if (n && !strcmp (n, p)) {
271 memcpy (&newroot, &i, sizeof (GtkTreeIter));
272 root_iter = &newroot;
273 found = 1;
274 break;
275 }
276 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &i));
277 if (!found) {
278 gtk_tree_store_append (store, &i, root_iter);
279 gtk_tree_store_set (store, &i, 0, p, 1, NULL, 2, -1, -1);
280 root_iter = &i;
281 }
282 }
283
284 p = s+1;
285 }
286 gtk_tree_store_append (store, iter, root_iter);
287 return get_display_action_title (title);
288 }
289
290 typedef struct {
291 const char *name;
292 int ctx;
293 GtkWidget *treeview;
294 } actionbinding_t;
295
296 static gboolean
set_current_action(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)297 set_current_action (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
298 GValue val = {0,}, ctx_val = {0,};
299 gtk_tree_model_get_value (model, iter, 1, &val);
300 gtk_tree_model_get_value (model, iter, 2, &ctx_val);
301 actionbinding_t *binding = data;
302 const char *name = g_value_get_string (&val);
303 if (name && binding->name && !strcmp (binding->name, name) && binding->ctx == g_value_get_int (&ctx_val)) {
304 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (binding->treeview), path);
305 gtk_tree_view_set_cursor (GTK_TREE_VIEW (binding->treeview), path, NULL, FALSE);
306 return TRUE;
307 }
308 return FALSE;
309 }
310
311 void
init_action_tree(GtkWidget * actions,const char * act,int ctx)312 init_action_tree (GtkWidget *actions, const char *act, int ctx) {
313 GtkTreeViewColumn *hk_act_col1 = gtk_tree_view_column_new_with_attributes (_("Action"), gtk_cell_renderer_text_new (), "text", 0, NULL);
314 gtk_tree_view_append_column (GTK_TREE_VIEW (actions), hk_act_col1);
315
316 // traverse all plugins and collect all exported actions to treeview
317 // column0: title
318 // column1: ID (invisible)
319 // column2: ctx (invisible
320 GtkTreeStore *actions_store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
321 GtkTreeIter action_main_iter;
322 gtk_tree_store_append (actions_store, &action_main_iter, NULL);
323 gtk_tree_store_set (actions_store, &action_main_iter, 0, _("Main"), -1);
324
325 GtkTreeIter action_selection_iter;
326 gtk_tree_store_append (actions_store, &action_selection_iter, NULL);
327 gtk_tree_store_set (actions_store, &action_selection_iter, 0, _("Selected track(s)"), -1);
328 GtkTreeIter action_playlist_iter;
329 gtk_tree_store_append (actions_store, &action_playlist_iter, NULL);
330 gtk_tree_store_set (actions_store, &action_playlist_iter, 0, _("Current playlist"), -1);
331 GtkTreeIter action_nowplaying_iter;
332 gtk_tree_store_append (actions_store, &action_nowplaying_iter, NULL);
333 gtk_tree_store_set (actions_store, &action_nowplaying_iter, 0, _("Now playing"), -1);
334
335 DB_plugin_t **plugins = deadbeef->plug_get_list ();
336 for (int i = 0; plugins[i]; i++) {
337 DB_plugin_t *p = plugins[i];
338 if (p->get_actions) {
339 DB_plugin_action_t *actions = p->get_actions (NULL);
340 while (actions) {
341 if (actions->name && actions->title) { // only add actions with both the name and the title
342 char title[100];
343
344 GtkTreeIter iter;
345 const char *t;
346 if (actions->flags & DB_ACTION_COMMON) {
347 t = action_tree_append (actions->title, actions_store, &action_main_iter, &iter);
348 unescape_forward_slash (t, title, sizeof (title));
349 gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_MAIN, -1);
350 }
351 if (actions->flags & (DB_ACTION_SINGLE_TRACK | DB_ACTION_MULTIPLE_TRACKS | DB_ACTION_CAN_MULTIPLE_TRACKS)) {
352 t = action_tree_append (actions->title, actions_store, &action_selection_iter, &iter);
353 unescape_forward_slash (t, title, sizeof (title));
354 gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_SELECTION, -1);
355 t = action_tree_append (actions->title, actions_store, &action_playlist_iter, &iter);
356 unescape_forward_slash (t, title, sizeof (title));
357 gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_PLAYLIST, -1);
358 t = action_tree_append (actions->title, actions_store, &action_nowplaying_iter, &iter);
359 unescape_forward_slash (t, title, sizeof (title));
360 gtk_tree_store_set (actions_store, &iter, 0, title, 1, actions->name, 2, DDB_ACTION_CTX_NOWPLAYING, -1);
361 }
362 }
363 else {
364 // fprintf (stderr, "WARNING: action %s/%s from plugin %s is missing name and/or title\n", actions->name, actions->title, p->name);
365 }
366 actions = actions->next;
367 }
368 }
369 }
370
371 gtk_tree_view_set_model (GTK_TREE_VIEW (actions), GTK_TREE_MODEL (actions_store));
372
373 if (act && ctx != -1) {
374 actionbinding_t binding = {
375 .name = act,
376 .ctx = ctx,
377 .treeview = actions
378 };
379 gtk_tree_model_foreach (GTK_TREE_MODEL (actions_store), set_current_action, (void*)&binding);
380 }
381 }
382
383 void
on_hotkeys_actions_clicked(GtkButton * button,gpointer user_data)384 on_hotkeys_actions_clicked (GtkButton *button,
385 gpointer user_data)
386 {
387 GtkTreePath *path;
388 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
389 gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL);
390 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys));
391 GtkTreeIter iter;
392 if (!path || !gtk_tree_model_get_iter (model, &iter, path)) {
393 return;
394 }
395 // get action name from iter
396 GValue val_name = {0,}, val_ctx = {0,};
397 gtk_tree_model_get_value (model, &iter, 4, &val_name);
398 gtk_tree_model_get_value (model, &iter, 5, &val_ctx);
399 const char *act = g_value_get_string (&val_name);
400 int ctx = g_value_get_int (&val_ctx);
401
402 GtkWidget *dlg = create_select_action ();
403 GtkWidget *treeview = lookup_widget (dlg, "actions");
404 init_action_tree (treeview, act, ctx);
405 int response = gtk_dialog_run (GTK_DIALOG (dlg));
406 if (response == GTK_RESPONSE_OK) {
407 on_hotkeys_actions_cursor_changed (GTK_TREE_VIEW (treeview), NULL);
408
409 GtkTreePath *path;
410 gtk_tree_view_get_cursor (GTK_TREE_VIEW (treeview), &path, NULL);
411 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
412 GtkTreeIter iter;
413 const char *name = NULL;
414 int ctx = -1;
415 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
416 GValue val = {0,};
417 gtk_tree_model_get_value (model, &iter, 1, &val);
418 name = g_value_get_string (&val);
419 GValue val_ctx = {0,};
420 gtk_tree_model_get_value (model, &iter, 2, &val_ctx);
421 ctx = g_value_get_int (&val_ctx);
422 }
423 set_button_action_label (name, ctx, lookup_widget (prefwin, "hotkeys_actions"));
424 }
425 gtk_widget_destroy (dlg);
426 }
427
428 void
prefwin_init_hotkeys(GtkWidget * _prefwin)429 prefwin_init_hotkeys (GtkWidget *_prefwin) {
430 DB_plugin_t *hkplug = deadbeef->plug_get_for_id ("hotkeys");
431 if (!hkplug) {
432 // remove hotkeys tab
433 gtk_notebook_remove_page (GTK_NOTEBOOK (lookup_widget (_prefwin, "notebook")), 6);
434 return;
435 }
436
437 gtkui_hotkeys_changed = 0;
438 ctx_names[DDB_ACTION_CTX_MAIN] = _("Main");
439 ctx_names[DDB_ACTION_CTX_SELECTION] = _("Selection");
440 ctx_names[DDB_ACTION_CTX_PLAYLIST] = _("Playlist");
441 ctx_names[DDB_ACTION_CTX_NOWPLAYING] = _("Now playing");
442
443 prefwin = _prefwin;
444 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
445
446 // setup hotkeys list
447 GtkTreeViewColumn *hk_col1 = gtk_tree_view_column_new_with_attributes (_("Key combination"), gtk_cell_renderer_text_new (), "text", 0, NULL);
448 gtk_tree_view_column_set_resizable (hk_col1, TRUE);
449 GtkTreeViewColumn *hk_col2 = gtk_tree_view_column_new_with_attributes (_("Action"), gtk_cell_renderer_text_new (), "text", 1, NULL);
450 gtk_tree_view_column_set_resizable (hk_col2, TRUE);
451 GtkTreeViewColumn *hk_col3 = gtk_tree_view_column_new_with_attributes (_("Context"), gtk_cell_renderer_text_new (), "text", 2, NULL);
452 gtk_tree_view_column_set_resizable (hk_col3, TRUE);
453 GtkTreeViewColumn *hk_col4 = gtk_tree_view_column_new_with_attributes (_("Is global"), gtk_cell_renderer_text_new (), "text", 3, NULL);
454 gtk_tree_view_column_set_resizable (hk_col4, TRUE);
455 gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col1);
456 gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col2);
457 gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col3);
458 gtk_tree_view_append_column (GTK_TREE_VIEW (hotkeys), hk_col4);
459 // column0: keycombo string
460 // column1: action title
461 // column2: context title
462 // column3: is_global
463 // column4: action title id (hidden)
464 // column5: context id (hidden)
465 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);
466
467 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE);
468 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE);
469 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE);
470
471 gtk_tree_view_set_model (GTK_TREE_VIEW (hotkeys), GTK_TREE_MODEL (hkstore));
472
473 int n_hotkeys = hotkeys_load ();
474 }
475
476 void
set_button_action_label(const char * act,int action_ctx,GtkWidget * button)477 set_button_action_label (const char *act, int action_ctx, GtkWidget *button) {
478 if (act && action_ctx >= 0) {
479 DB_plugin_action_t *action = find_action_by_name (act);
480 if (action) {
481 const char *ctx_str = NULL;
482 switch (action_ctx) {
483 case DDB_ACTION_CTX_MAIN:
484 break;
485 case DDB_ACTION_CTX_SELECTION:
486 ctx_str = _("Selected tracks");
487 break;
488 case DDB_ACTION_CTX_PLAYLIST:
489 ctx_str = _("Tracks in current playlist");
490 break;
491 case DDB_ACTION_CTX_NOWPLAYING:
492 ctx_str = _("Currently playing track");
493 break;
494 }
495 char s[200];
496 snprintf (s, sizeof (s), "%s%s%s", ctx_str ? ctx_str : "", ctx_str ? " ⇒ ": "", action->title);
497 char s_fixed[200];
498 prettify_forward_slash (s, s_fixed, sizeof (s_fixed));
499
500 gtk_button_set_label (GTK_BUTTON (button), s_fixed);
501 return;
502 }
503 }
504
505 gtk_button_set_label (GTK_BUTTON (button), _("<Not set>"));
506 }
507
508
509 void
on_hotkeys_list_cursor_changed(GtkTreeView * treeview,gpointer user_data)510 on_hotkeys_list_cursor_changed (GtkTreeView *treeview,
511 gpointer user_data)
512 {
513 GtkTreePath *path;
514 gtk_tree_view_get_cursor (treeview, &path, NULL);
515 GtkTreeModel *model = gtk_tree_view_get_model (treeview);
516 GtkTreeIter iter;
517 int changed = gtkui_hotkeys_changed;
518 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
519 GtkWidget *actions = lookup_widget (prefwin, "hotkeys_actions");
520 gtk_widget_set_sensitive (actions, TRUE);
521 // get action name from iter
522 GValue val_name = {0,}, val_ctx = {0,};
523 gtk_tree_model_get_value (model, &iter, 4, &val_name);
524 gtk_tree_model_get_value (model, &iter, 5, &val_ctx);
525 const char *name = g_value_get_string (&val_name);
526
527 set_button_action_label (name, g_value_get_int (&val_ctx), actions);
528
529 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), TRUE);
530 GValue val_isglobal = {0,};
531 gtk_tree_model_get_value (model, &iter, 3, &val_isglobal);
532 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), g_value_get_boolean (&val_isglobal));
533 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), TRUE);
534 GValue val_keycombo = {0,};
535 gtk_tree_model_get_value (model, &iter, 0, &val_keycombo);
536 const char *keycombo = g_value_get_string (&val_keycombo);
537 gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), keycombo ? keycombo : "");
538 }
539 else {
540 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE);
541 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE);
542 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), FALSE);
543 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE);
544 gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), _("<Not set>"));
545 }
546 if (path) {
547 gtk_tree_path_free (path);
548 }
549 gtkui_hotkeys_changed = changed;
550 }
551
552
553 void
on_hotkey_add_clicked(GtkButton * button,gpointer user_data)554 on_hotkey_add_clicked (GtkButton *button,
555 gpointer user_data)
556 {
557 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
558 GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)));
559 GtkTreeIter iter;
560 gtk_list_store_append (hkstore, &iter);
561 gtk_list_store_set (hkstore, &iter, 0, _("<Not set>"), 1, _("<Not set>"), 2, _("<Not set>"), 3, 0, 4, NULL, 5, -1, -1);
562 GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (hkstore), &iter);
563 gtk_tree_view_set_cursor (GTK_TREE_VIEW (hotkeys), path, NULL, FALSE);
564 gtk_tree_path_free (path);
565 gtk_widget_grab_focus (hotkeys);
566 gtkui_hotkeys_changed = 1;
567 }
568
569
570 void
on_hotkey_remove_clicked(GtkButton * button,gpointer user_data)571 on_hotkey_remove_clicked (GtkButton *button,
572 gpointer user_data)
573 {
574 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
575 GtkTreePath *path;
576 gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL);
577 GtkListStore *hkstore = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys)));
578 GtkTreeIter iter;
579 gtk_tree_model_get_iter (GTK_TREE_MODEL (hkstore), &iter, path);
580 gtk_list_store_remove (hkstore, &iter);
581 set_button_action_label (NULL, 0, lookup_widget (prefwin, "hotkeys_actions"));
582 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (prefwin, "hotkey_is_global")), FALSE);
583 gtk_button_set_label (GTK_BUTTON (lookup_widget (prefwin, "hotkeys_set_key")), _("<Not set>"));
584 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_actions"), FALSE);
585 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkey_is_global"), FALSE);
586 gtk_widget_set_sensitive (lookup_widget (prefwin, "hotkeys_set_key"), FALSE);
587 gtkui_hotkeys_changed = 1;
588 }
589
590
591 void
on_hotkeys_actions_cursor_changed(GtkTreeView * treeview,gpointer user_data)592 on_hotkeys_actions_cursor_changed (GtkTreeView *treeview,
593 gpointer user_data)
594 {
595 GtkTreePath *path;
596 gtk_tree_view_get_cursor (treeview, &path, NULL);
597 GtkTreeModel *model = gtk_tree_view_get_model (treeview);
598 GtkTreeIter iter;
599 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
600 GValue val = {0,};
601 gtk_tree_model_get_value (model, &iter, 1, &val);
602 const gchar *name = g_value_get_string (&val);
603 DB_plugin_action_t *action = NULL;
604 int ctx = 0;
605 if (name) {
606 action = find_action_by_name (name);
607 GValue val_ctx = {0,};
608 gtk_tree_model_get_value (model, &iter, 2, &val_ctx);
609 ctx = g_value_get_int (&val_ctx);
610 }
611 // update the tree
612 {
613 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
614 GtkTreePath *path;
615 gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL);
616 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys));
617 GtkTreeIter iter;
618 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
619 if (action) {
620 const char *t = get_display_action_title (action->title);
621 char title[100];
622 unescape_forward_slash (t, title, sizeof (title));
623 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, title, 4, action->name, 5, ctx, 2, ctx_names[ctx], -1);
624 }
625 else {
626 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, _("<Not set>"), 4, NULL, 2, _("<Not set>"), -1);
627 }
628 }
629 }
630 }
631 }
632
633
634 void
on_hotkey_is_global_toggled(GtkToggleButton * togglebutton,gpointer user_data)635 on_hotkey_is_global_toggled (GtkToggleButton *togglebutton,
636 gpointer user_data)
637 {
638 // update the tree
639 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
640 GtkTreePath *path;
641 gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &path, NULL);
642 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys));
643 GtkTreeIter iter;
644 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
645 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 3, gtk_toggle_button_get_active (togglebutton), -1);
646 }
647 gtkui_hotkeys_changed = 1;
648 }
649
650 typedef struct {
651 const char *name;
652 int keysym;
653 } xkey_t;
654
655 #define KEY(kname, kcode) { .name=kname, .keysym=kcode },
656
657 static const xkey_t keys[] = {
658 #include "../hotkeys/keysyms.inc"
659 };
660
661 static const char *
get_name_for_keycode(int keycode)662 get_name_for_keycode (int keycode) {
663 for (int i = 0; keys[i].name; i++) {
664 if (keycode == keys[i].keysym) {
665 return keys[i].name;
666 }
667 }
668 return NULL;
669 }
670
671
672 int gtkui_hotkey_grabbing = 0;
673
674 static void
get_keycombo_string(guint accel_key,GdkModifierType accel_mods,char * new_value)675 get_keycombo_string (guint accel_key, GdkModifierType accel_mods, char *new_value) {
676 // build value
677 new_value[0] = 0;
678 if (!accel_key) {
679 strcpy (new_value, _("<Not set>"));
680 return;
681 }
682 if (accel_mods & GDK_SHIFT_MASK) {
683 strcat (new_value, "Shift ");
684 }
685 if (accel_mods & GDK_CONTROL_MASK) {
686 strcat (new_value, "Ctrl ");
687 }
688 if (accel_mods & GDK_SUPER_MASK) {
689 strcat (new_value, "Super ");
690 }
691 if (accel_mods & GDK_MOD1_MASK) {
692 strcat (new_value, "Alt ");
693 }
694
695 // translate numlock keycodes into non-numlock codes
696 switch (accel_key) {
697 case GDK_KP_0:
698 accel_key = GDK_KP_Insert;
699 break;
700 case GDK_KP_1:
701 accel_key = GDK_KP_End;
702 break;
703 case GDK_KP_2:
704 accel_key = GDK_KP_Down;
705 break;
706 case GDK_KP_3:
707 accel_key = GDK_KP_Page_Down;
708 break;
709 case GDK_KP_4:
710 accel_key = GDK_KP_Left;
711 break;
712 case GDK_KP_6:
713 accel_key = GDK_KP_Right;
714 break;
715 case GDK_KP_7:
716 accel_key = GDK_KP_Home;
717 break;
718 case GDK_KP_8:
719 accel_key = GDK_KP_Up;
720 break;
721 case GDK_KP_9:
722 accel_key = GDK_KP_Page_Up;
723 break;
724 }
725
726 const char *name = get_name_for_keycode (accel_key);
727 if (!name) {
728 strcpy (new_value, _("<Not set>"));
729 return;
730 }
731 strcat (new_value, name);
732 }
733
734 static GtkWidget *hotkey_grabber_button;
735 gboolean
on_hotkeys_set_key_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)736 on_hotkeys_set_key_key_press_event (GtkWidget *widget,
737 GdkEventKey *event,
738 gpointer user_data)
739 {
740 widget = hotkey_grabber_button;
741 GdkModifierType accel_mods = 0;
742 guint accel_key;
743 gchar *path;
744 gboolean edited;
745 gboolean cleared;
746 GdkModifierType consumed_modifiers;
747 GdkDisplay *display;
748 GtkTreePath *curpath;
749 GtkTreeIter iter;
750
751 if (!gtkui_hotkey_grabbing) {
752 return FALSE;
753 }
754
755 display = gtk_widget_get_display (widget);
756
757 if (event->is_modifier)
758 return TRUE;
759
760 edited = FALSE;
761 cleared = FALSE;
762
763 gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
764 event->hardware_keycode, event->state,
765 0, &accel_key, NULL, NULL, &consumed_modifiers);
766
767 if (accel_key == GDK_ISO_Left_Tab)
768 accel_key = GDK_Tab;
769
770 accel_mods = event->state & gtk_accelerator_get_default_mod_mask ();
771
772 /* Filter consumed modifiers
773 */
774 accel_mods &= ~(consumed_modifiers&~GDK_SHIFT_MASK);
775
776 /* Put shift back if it changed the case of the key, not otherwise.
777 */
778 int lower = gdk_keyval_to_lower (accel_key);
779 if (lower != accel_key) {
780 accel_key = lower;
781 }
782
783 char name[1000];
784 gtk_button_set_label (GTK_BUTTON (widget), _(""));
785
786 GtkWidget *hotkeys = lookup_widget (prefwin, "hotkeys_list");
787 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (hotkeys));
788
789 // check if this key already registered
790 get_keycombo_string (accel_key, accel_mods, name);
791
792 gtk_tree_view_get_cursor (GTK_TREE_VIEW (hotkeys), &curpath, NULL);
793 gboolean res = gtk_tree_model_get_iter_first (model, &iter);
794 while (res) {
795 GtkTreePath *iterpath = gtk_tree_model_get_path (model, &iter);
796
797 if (!curpath || gtk_tree_path_compare (iterpath, curpath)) {
798 GValue keycombo = {0,};
799 gtk_tree_model_get_value (model, &iter, 0, &keycombo);
800 const char *val = g_value_get_string (&keycombo);
801 if (val && !strcmp (val, name)) {
802 gtk_tree_path_free (iterpath);
803 break;
804 }
805 }
806 gtk_tree_path_free (iterpath);
807
808 res = gtk_tree_model_iter_next (model, &iter);
809 }
810
811 if (res) {
812 // duplicate
813 gtk_button_set_label (GTK_BUTTON (widget), _("Duplicate key combination!"));
814 gtk_widget_error_bell (widget);
815 goto out;
816 }
817
818 last_accel_key = accel_key;
819 last_accel_mask = accel_mods;
820 get_keycombo_string (last_accel_key, last_accel_mask, name);
821 gtk_button_set_label (GTK_BUTTON (widget), name);
822
823 // update the tree
824 if (curpath && gtk_tree_model_get_iter (model, &iter, curpath)) {
825 gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, name, -1);
826 }
827
828 out:
829 if (curpath) {
830 gtk_tree_path_free (curpath);
831 }
832 gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
833 gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
834 gtkui_hotkey_grabbing = 0;
835 gtkui_hotkeys_changed = 1;
836 return TRUE;
837 }
838
839 static void
hotkey_grab_focus(GtkWidget * widget)840 hotkey_grab_focus (GtkWidget *widget) {
841 GdkDisplay *display = gtk_widget_get_display (widget);
842 if (gtkui_hotkey_grabbing) {
843 return;
844 }
845 gtkui_hotkey_grabbing = 0;
846 if (GDK_GRAB_SUCCESS != gdk_keyboard_grab (gtk_widget_get_window (widget), FALSE, GDK_CURRENT_TIME)) {
847 return;
848 }
849
850 if (gdk_pointer_grab (gtk_widget_get_window (widget), FALSE,
851 GDK_BUTTON_PRESS_MASK,
852 NULL, NULL,
853 GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
854 {
855 gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
856 return;
857 }
858 gtk_button_set_label (GTK_BUTTON (widget), _("New key combination..."));
859 gtkui_hotkey_grabbing = 1;
860
861 // disable the window accelerators temporarily
862 hotkey_grabber_button = widget;
863 }
864
865 void
on_hotkeys_set_key_clicked(GtkButton * button,gpointer user_data)866 on_hotkeys_set_key_clicked (GtkButton *button,
867 gpointer user_data)
868 {
869 hotkey_grab_focus (GTK_WIDGET (button));
870 }
871
872 void
on_hotkeys_apply_clicked(GtkButton * button,gpointer user_data)873 on_hotkeys_apply_clicked (GtkButton *button,
874 gpointer user_data)
875 {
876 hotkeys_save ();
877 gtkui_hotkeys_changed = 0;
878 }
879
880
881 void
on_hotkeys_revert_clicked(GtkButton * button,gpointer user_data)882 on_hotkeys_revert_clicked (GtkButton *button,
883 gpointer user_data)
884 {
885 hotkeys_load ();
886 gtkui_hotkeys_changed = 0;
887 }
888
889 void
on_hotkeys_defaults_clicked(GtkButton * button,gpointer user_data)890 on_hotkeys_defaults_clicked (GtkButton *button,
891 gpointer user_data)
892 {
893 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."));
894 gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (prefwin));
895 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), _("This operation cannot be undone. Proceed?"));
896 gtk_window_set_title (GTK_WINDOW (dlg), _("Warning"));
897 int response = gtk_dialog_run (GTK_DIALOG (dlg));
898 gtk_widget_destroy (dlg);
899 if (response != GTK_RESPONSE_YES) {
900 return;
901 }
902 gtkui_set_default_hotkeys ();
903 hotkeys_load ();
904 gtkui_hotkeys_changed = 0;
905 }
906
907 void
gtkui_set_default_hotkeys(void)908 gtkui_set_default_hotkeys (void) {
909 deadbeef->conf_remove_items ("hotkey.key");
910 deadbeef->conf_set_str ("hotkey.key01", "\"Ctrl f\" 0 0 find");
911 deadbeef->conf_set_str ("hotkey.key02", "\"Ctrl o\" 0 0 open_files");
912 deadbeef->conf_set_str ("hotkey.key03", "\"Ctrl q\" 0 0 quit");
913 deadbeef->conf_set_str ("hotkey.key04", "\"Ctrl n\" 0 0 new_playlist");
914 deadbeef->conf_set_str ("hotkey.key05", "\"Ctrl a\" 0 0 select_all");
915 deadbeef->conf_set_str ("hotkey.key06", "\"Escape\" 0 0 deselect_all");
916 deadbeef->conf_set_str ("hotkey.key07", "\"Ctrl m\" 0 0 toggle_stop_after_current");
917 deadbeef->conf_set_str ("hotkey.key08", "\"Ctrl j\" 0 0 jump_to_current_track");
918 deadbeef->conf_set_str ("hotkey.key09", "\"F1\" 0 0 help");
919 deadbeef->conf_set_str ("hotkey.key10", "\"Delete\" 1 0 remove_from_playlist");
920 deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist");
921 deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist");
922 deadbeef->conf_set_str ("hotkey.key11", "\"Ctrl w\" 0 0 remove_current_playlist");
923 deadbeef->conf_set_str ("hotkey.key14", "\"Return\" 0 0 play");
924 deadbeef->conf_set_str ("hotkey.key15", "\"Ctrl p\" 0 0 toggle_pause");
925 deadbeef->conf_set_str ("hotkey.key16", "\"Alt 1\" 0 0 playlist1");
926 deadbeef->conf_set_str ("hotkey.key17", "\"Alt 2\" 0 0 playlist2");
927 deadbeef->conf_set_str ("hotkey.key18", "\"Alt 3\" 0 0 playlist3");
928 deadbeef->conf_set_str ("hotkey.key19", "\"Alt 4\" 0 0 playlist4");
929 deadbeef->conf_set_str ("hotkey.key20", "\"Alt 5\" 0 0 playlist5");
930 deadbeef->conf_set_str ("hotkey.key21", "\"Alt 6\" 0 0 playlist6");
931 deadbeef->conf_set_str ("hotkey.key22", "\"Alt 7\" 0 0 playlist7");
932 deadbeef->conf_set_str ("hotkey.key23", "\"Alt 8\" 0 0 playlist8");
933 deadbeef->conf_set_str ("hotkey.key24", "\"Alt 9\" 0 0 playlist9");
934 deadbeef->conf_set_str ("hotkey.key25", "\"Alt 0\" 0 0 playlist10");
935 deadbeef->conf_set_str ("hotkey.key26", "z 0 0 prev");
936 deadbeef->conf_set_str ("hotkey.key27", "x 0 0 play");
937 deadbeef->conf_set_str ("hotkey.key28", "c 0 0 toggle_pause");
938 deadbeef->conf_set_str ("hotkey.key29", "v 0 0 stop");
939 deadbeef->conf_set_str ("hotkey.key30", "b 0 0 next");
940 deadbeef->conf_set_str ("hotkey.key31", "n 0 0 playback_random");
941 deadbeef->conf_set_str ("hotkey.key32", "\"Ctrl k\" 0 0 toggle_stop_after_album");
942 deadbeef->conf_save ();
943 }
944
945 void
gtkui_import_0_5_global_hotkeys(void)946 gtkui_import_0_5_global_hotkeys (void) {
947 int n = 40;
948 deadbeef->conf_lock ();
949 DB_conf_item_t *item = deadbeef->conf_find ("hotkeys.key", NULL);
950 while (item) {
951 char *val = strdupa (item->value);
952 char *colon = strchr (val, ':');
953 if (colon) {
954 *colon++ = 0;
955 while (*colon && *colon == ' ') {
956 colon++;
957 }
958 if (*colon) {
959 char newkey[100];
960 char newval[100];
961 snprintf (newkey, sizeof (newkey), "hotkey.key%02d", n);
962 snprintf (newval, sizeof (newval), "\"%s\" 0 1 %s", val, colon);
963 deadbeef->conf_set_str (newkey, newval);
964 n++;
965 }
966 }
967 item = deadbeef->conf_find ("hotkeys.", item);
968 }
969 deadbeef->conf_unlock ();
970 }
971