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