1 /*
2 * Copyright (c) 2011 Tim van der Molen <tim@kariliq.nl>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #ifdef __OpenBSD__
18 #include <sys/tree.h>
19 #else
20 #include "compat/tree.h"
21 #endif
22
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <strings.h>
27
28 #include "siren.h"
29
30 /*
31 * The length of the longest key-name (including "\0"). Most key names are
32 * defined in the bind_keys array below. The other key names have a length of 1
33 * (e.g. "a") or 2 (e.g. "^A").
34 */
35 #define BIND_KEY_MAXLEN 10
36
37 struct bind_entry {
38 int key;
39 enum bind_scope scope;
40 struct command *command;
41 void *command_data;
42 char *command_string;
43 RB_ENTRY(bind_entry) entries;
44 };
45
46 RB_HEAD(bind_tree, bind_entry);
47
48 static int bind_cmp_entry(struct bind_entry *,
49 struct bind_entry *);
50 static struct bind_entry *bind_find(enum bind_scope, int);
51 static char *bind_key_to_string(int, char *, size_t);
52 static void bind_remove(struct bind_entry *);
53 static const char *bind_scope_to_string(enum bind_scope);
54
55 RB_PROTOTYPE(bind_tree, bind_entry, entries, bind_cmp_entry)
56
57 static struct bind_tree bind_tree = RB_INITIALIZER(bind_tree);
58
59 static const struct {
60 const enum bind_scope scope;
61 const char *name;
62 } bind_scopes[] = {
63 { BIND_SCOPE_BROWSER, "browser" },
64 { BIND_SCOPE_COMMON, "common" },
65 { BIND_SCOPE_LIBRARY, "library" },
66 { BIND_SCOPE_QUEUE, "queue" }
67 };
68
69 static const struct {
70 const int key;
71 const char *name;
72 } bind_keys[] = {
73 { ' ', "space" },
74 { K_BACKSPACE, "backspace" },
75 { K_BACKTAB, "backtab" },
76 { K_DELETE, "delete" },
77 { K_DOWN, "down" },
78 { K_END, "end" },
79 { K_ENTER, "enter" },
80 { K_ESCAPE, "escape" },
81 { K_HOME, "home" },
82 { K_INSERT, "insert" },
83 { K_LEFT, "left" },
84 { K_PAGEDOWN, "page-down" },
85 { K_PAGEUP, "page-up" },
86 { K_RIGHT, "right" },
87 { K_TAB, "tab" },
88 { K_UP, "up" },
89 { K_F1, "f1" },
90 { K_F2, "f2" },
91 { K_F3, "f3" },
92 { K_F4, "f4" },
93 { K_F5, "f5" },
94 { K_F6, "f6" },
95 { K_F7, "f7" },
96 { K_F8, "f8" },
97 { K_F9, "f9" },
98 { K_F10, "f10" },
99 { K_F11, "f11" },
100 { K_F12, "f12" },
101 { K_F13, "f13" },
102 { K_F14, "f14" },
103 { K_F15, "f15" },
104 { K_F16, "f16" },
105 { K_F17, "f17" },
106 { K_F18, "f18" },
107 { K_F19, "f19" },
108 { K_F20, "f20" }
109 };
110
RB_GENERATE(bind_tree,bind_entry,entries,bind_cmp_entry)111 RB_GENERATE(bind_tree, bind_entry, entries, bind_cmp_entry)
112
113 static void
114 bind_add(enum bind_scope scope, int key, const char *command)
115 {
116 struct bind_entry *b;
117 char *error, keyname[BIND_KEY_MAXLEN];
118
119 b = xmalloc(sizeof *b);
120
121 if (command_parse_string(command, &b->command, &b->command_data,
122 &error))
123 LOG_FATALX("scope %s, key %s: invalid command: \"%s\": %s",
124 bind_scope_to_string(scope),
125 bind_key_to_string(key, keyname, sizeof keyname), command,
126 error);
127
128 b->command_string = xstrdup(command);
129 b->scope = scope;
130 b->key = key;
131
132 if (RB_INSERT(bind_tree, &bind_tree, b) != NULL)
133 LOG_FATALX("scope %s, key %s: already bound",
134 bind_scope_to_string(scope),
135 bind_key_to_string(key, keyname, sizeof keyname));
136 }
137
138 static int
bind_cmp_entry(struct bind_entry * b1,struct bind_entry * b2)139 bind_cmp_entry(struct bind_entry *b1, struct bind_entry *b2)
140 {
141 if (b1->scope != b2->scope)
142 return b1->scope < b2->scope ? -1 : 1;
143 else
144 return b1->key < b2->key ? -1 : b1->key > b2->key;
145 }
146
147 void
bind_end(void)148 bind_end(void)
149 {
150 struct bind_entry *b;
151
152 while ((b = RB_ROOT(&bind_tree)) != NULL)
153 bind_remove(b);
154 }
155
156 int
bind_execute(enum bind_scope scope,int key)157 bind_execute(enum bind_scope scope, int key)
158 {
159 struct bind_entry *b;
160
161 if ((b = bind_find(scope, key)) == NULL)
162 return -1;
163
164 command_execute(b->command, b->command_data);
165 return 0;
166 }
167
168 static struct bind_entry *
bind_find(enum bind_scope scope,int key)169 bind_find(enum bind_scope scope, int key)
170 {
171 struct bind_entry b;
172
173 b.key = key;
174 b.scope = scope;
175 return RB_FIND(bind_tree, &bind_tree, &b);
176 }
177
178 const char *
bind_get_command(enum bind_scope scope,int key)179 bind_get_command(enum bind_scope scope, int key)
180 {
181 struct bind_entry *b;
182
183 if ((b = bind_find(scope, key)) == NULL)
184 return NULL;
185 else
186 return b->command_string;
187 }
188
189 void
bind_init(void)190 bind_init(void)
191 {
192 bind_add(BIND_SCOPE_COMMON, K_CTRL('B'), "scroll-up -p");
193 bind_add(BIND_SCOPE_COMMON, K_CTRL('D'), "scroll-down -h");
194 bind_add(BIND_SCOPE_COMMON, K_CTRL('E'), "scroll-down -l");
195 bind_add(BIND_SCOPE_COMMON, K_CTRL('F'), "scroll-down -p");
196 bind_add(BIND_SCOPE_COMMON, K_CTRL('L'), "refresh-screen");
197 bind_add(BIND_SCOPE_COMMON, K_CTRL('U'), "scroll-up -h");
198 bind_add(BIND_SCOPE_COMMON, K_CTRL('Y'), "scroll-up -l");
199 bind_add(BIND_SCOPE_COMMON, K_DOWN, "select-next-entry");
200 bind_add(BIND_SCOPE_COMMON, K_END, "select-last-entry");
201 bind_add(BIND_SCOPE_COMMON, K_ENTER, "activate-entry");
202 bind_add(BIND_SCOPE_COMMON, K_HOME, "select-first-entry");
203 bind_add(BIND_SCOPE_COMMON, K_LEFT, "seek -b 5");
204 bind_add(BIND_SCOPE_COMMON, K_PAGEDOWN, "scroll-down -p");
205 bind_add(BIND_SCOPE_COMMON, K_PAGEUP, "scroll-up -p");
206 bind_add(BIND_SCOPE_COMMON, K_RIGHT, "seek -f 5");
207 bind_add(BIND_SCOPE_COMMON, K_UP, "select-prev-entry");
208 bind_add(BIND_SCOPE_COMMON, '+', "set-volume -i 10");
209 bind_add(BIND_SCOPE_COMMON, ',', "seek -b 1:00");
210 bind_add(BIND_SCOPE_COMMON, '.', "seek -f 1:00");
211 bind_add(BIND_SCOPE_COMMON, '-', "set-volume -d 5");
212 bind_add(BIND_SCOPE_COMMON, '/', "search-prompt");
213 bind_add(BIND_SCOPE_COMMON, '<', "seek -b 5:00");
214 bind_add(BIND_SCOPE_COMMON, '>', "seek -f 5:00");
215 bind_add(BIND_SCOPE_COMMON, '?', "search-prompt -b");
216 bind_add(BIND_SCOPE_COMMON, '1', "select-view library");
217 bind_add(BIND_SCOPE_COMMON, '2', "select-view playlist");
218 bind_add(BIND_SCOPE_COMMON, '3', "select-view browser");
219 bind_add(BIND_SCOPE_COMMON, '4', "select-view queue");
220 bind_add(BIND_SCOPE_COMMON, ':', "command-prompt");
221 bind_add(BIND_SCOPE_COMMON, '=', "set-volume -i 5");
222 bind_add(BIND_SCOPE_COMMON, 'C', "set continue");
223 bind_add(BIND_SCOPE_COMMON, 'G', "select-last-entry");
224 bind_add(BIND_SCOPE_COMMON, 'N', "search-prev");
225 bind_add(BIND_SCOPE_COMMON, 'R', "set repeat-all");
226 bind_add(BIND_SCOPE_COMMON, '_', "set-volume -d 10");
227 bind_add(BIND_SCOPE_COMMON, 'b', "play-next");
228 bind_add(BIND_SCOPE_COMMON, 'c', "pause");
229 bind_add(BIND_SCOPE_COMMON, 'g', "select-first-entry");
230 bind_add(BIND_SCOPE_COMMON, 'i', "select-active-entry");
231 bind_add(BIND_SCOPE_COMMON, 'j', "select-next-entry");
232 bind_add(BIND_SCOPE_COMMON, 'k', "select-prev-entry");
233 bind_add(BIND_SCOPE_COMMON, 'n', "search-next");
234 bind_add(BIND_SCOPE_COMMON, 'p', "search-prev");
235 bind_add(BIND_SCOPE_COMMON, 'q', "quit");
236 bind_add(BIND_SCOPE_COMMON, 'r', "set repeat-track");
237 bind_add(BIND_SCOPE_COMMON, 'v', "stop");
238 bind_add(BIND_SCOPE_COMMON, 'x', "play");
239 bind_add(BIND_SCOPE_COMMON, 'z', "play-prev");
240
241 bind_add(BIND_SCOPE_LIBRARY, K_DELETE, "delete-entry");
242 bind_add(BIND_SCOPE_LIBRARY, 'd', "delete-entry");
243 bind_add(BIND_SCOPE_LIBRARY, 'a', "add-entry -q");
244 bind_add(BIND_SCOPE_LIBRARY, 'l', "delete-entry -a");
245
246 bind_add(BIND_SCOPE_PLAYLIST, 'a', "add-entry -q");
247
248 bind_add(BIND_SCOPE_QUEUE, K_DELETE, "delete-entry");
249 bind_add(BIND_SCOPE_QUEUE, 'J', "move-entry-down");
250 bind_add(BIND_SCOPE_QUEUE, 'K', "move-entry-up");
251 bind_add(BIND_SCOPE_QUEUE, 'd', "delete-entry");
252 bind_add(BIND_SCOPE_QUEUE, 'l', "delete-entry -a");
253
254 bind_add(BIND_SCOPE_BROWSER, K_CTRL('R'), "reread-directory");
255 bind_add(BIND_SCOPE_BROWSER, K_BACKSPACE, "cd ..");
256 bind_add(BIND_SCOPE_BROWSER, 'a', "add-entry -q");
257 bind_add(BIND_SCOPE_BROWSER, 'h', "set show-hidden-files");
258 }
259
260 static char *
bind_key_to_string(int key,char * name,size_t namelen)261 bind_key_to_string(int key, char *name, size_t namelen)
262 {
263 size_t i;
264
265 if (K_IS_CTRL(key)) {
266 xsnprintf(name, namelen, "^%c", K_UNCTRL(key));
267 return name;
268 }
269
270 for (i = 0; i < NELEMENTS(bind_keys); i++)
271 if (key == bind_keys[i].key) {
272 strlcpy(name, bind_keys[i].name, namelen);
273 return name;
274 }
275
276 xsnprintf(name, namelen, "%c", key);
277 return name;
278 }
279
280 static void
bind_remove(struct bind_entry * b)281 bind_remove(struct bind_entry *b)
282 {
283 RB_REMOVE(bind_tree, &bind_tree, b);
284 command_free_data(b->command, b->command_data);
285 free(b->command_string);
286 free(b);
287 }
288
289 static const char *
bind_scope_to_string(enum bind_scope scope)290 bind_scope_to_string(enum bind_scope scope)
291 {
292 size_t i;
293
294 for (i = 0; i < NELEMENTS(bind_scopes); i++)
295 if (scope == bind_scopes[i].scope)
296 return bind_scopes[i].name;
297
298 LOG_FATALX("unknown scope");
299 }
300
301 void
bind_set(enum bind_scope scope,int key,struct command * command,void * command_data,const char * command_string)302 bind_set(enum bind_scope scope, int key, struct command *command,
303 void *command_data, const char *command_string)
304 {
305 struct bind_entry *b;
306
307 if ((b = bind_find(scope, key)) != NULL)
308 bind_remove(b);
309
310 b = xmalloc(sizeof *b);
311 b->scope = scope;
312 b->key = key;
313 b->command = command;
314 b->command_data = command_data;
315 b->command_string = xstrdup(command_string);
316 RB_INSERT(bind_tree, &bind_tree, b);
317 }
318
319 int
bind_string_to_scope(const char * name,enum bind_scope * scope)320 bind_string_to_scope(const char *name, enum bind_scope *scope)
321 {
322 size_t i;
323
324 for (i = 0; i < NELEMENTS(bind_scopes); i++)
325 if (!strcasecmp(name, bind_scopes[i].name)) {
326 *scope = bind_scopes[i].scope;
327 return 0;
328 }
329
330 return -1;
331 }
332
333 int
bind_string_to_key(const char * str)334 bind_string_to_key(const char *str)
335 {
336 size_t i;
337
338 /* Printable characters (ASCII 32 to 126 decimal). */
339 if (str[0] >= ' ' && str[0] <= '~' && str[1] == '\0')
340 return str[0];
341
342 /* Control characters (ASCII 0 to 31 decimal, and 127 decimal). */
343 if (str[0] == '^' && K_IS_CTRL(K_CTRL(toupper((unsigned char)str[1])))
344 && str[2] == '\0')
345 return K_CTRL(toupper((unsigned char)str[1]));
346
347 /* Key names. */
348 for (i = 0; i < NELEMENTS(bind_keys); i++)
349 if (!strcasecmp(str, bind_keys[i].name))
350 return bind_keys[i].key;
351
352 return K_NONE;
353 }
354
355 int
bind_unset(enum bind_scope scope,int key)356 bind_unset(enum bind_scope scope, int key)
357 {
358 struct bind_entry *b;
359
360 if ((b = bind_find(scope, key)) == NULL)
361 return -1;
362
363 bind_remove(b);
364 return 0;
365 }
366