File: | commands.c |
Location: | line 115, column 16 |
Description: | Access to field 'commands' results in a dereference of a null pointer (loaded from variable 't') |
1 | /* | |||
2 | * Copyright (c) 2010, 2011, 2012 Ryan Flannery <ryan.flannery@gmail.com> | |||
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 | #include "commands.h" | |||
18 | ||||
19 | bool_Bool sorts_need_saving = false0; | |||
20 | ||||
21 | #define swap(type, x, y){ type temp = x; x = y; y = temp;} \ | |||
22 | { type temp = x; x = y; y = temp;} | |||
23 | ||||
24 | /* | |||
25 | * List of command-mode commands. Take note of the following: XXX | |||
26 | * 1. See 'match_cmd_name()' for the handling of abbreviations. | |||
27 | * 2. Commands that accept a '!' after their names are handled | |||
28 | * in 'match_cmd_name()'. | |||
29 | */ | |||
30 | const cmd CommandPath[] = { | |||
31 | { "bind", cmd_bind }, | |||
32 | { "color", cmd_color }, | |||
33 | { "display", cmd_display }, | |||
34 | { "filter", cmd_filter }, | |||
35 | { "mode", cmd_mode }, | |||
36 | { "new", cmd_new }, | |||
37 | { "playlist", cmd_playlist }, | |||
38 | { "q", cmd_quit }, | |||
39 | { "reload", cmd_reload }, | |||
40 | { "set", cmd_set }, | |||
41 | { "sort", cmd_sort }, | |||
42 | { "unbind", cmd_unbind }, | |||
43 | { "w", cmd_write }, | |||
44 | { "toggle", cmd_toggle } | |||
45 | }; | |||
46 | const int CommandPathSize = (sizeof(CommandPath) / sizeof(cmd)); | |||
47 | ||||
48 | ||||
49 | /**************************************************************************** | |||
50 | * Toggleset related stuff | |||
51 | ***************************************************************************/ | |||
52 | ||||
53 | toggle_list **toggleset; | |||
54 | size_t toggleset_size; | |||
55 | ||||
56 | void | |||
57 | toggleset_init() | |||
58 | { | |||
59 | const int max_size = 52; /* since we only have registers a-z and A-Z */ | |||
60 | if ((toggleset = calloc(max_size, sizeof(toggle_list*))) == NULL((void *)0)) | |||
61 | err(1, "%s: calloc(3) failed", __FUNCTION__); | |||
62 | ||||
63 | toggleset_size = 0; | |||
64 | } | |||
65 | ||||
66 | void | |||
67 | toggleset_free() | |||
68 | { | |||
69 | size_t i; | |||
70 | for (i = 0; i < toggleset_size; i++) | |||
71 | toggle_list_free(toggleset[i]); | |||
72 | ||||
73 | free(toggleset); | |||
74 | toggleset_size = 0; | |||
75 | } | |||
76 | ||||
77 | void | |||
78 | toggle_list_add_command(toggle_list *t, char *cmd) | |||
79 | { | |||
80 | char **new_cmds; | |||
81 | int idx; | |||
82 | ||||
83 | /* resize array */ | |||
84 | if (t->size == 0) { | |||
85 | if ((t->commands = malloc(sizeof(char*))) == NULL((void *)0)) | |||
86 | err(1, "%s: malloc(3) failed", __FUNCTION__); | |||
87 | ||||
88 | idx = 0; | |||
89 | t->size = 1; | |||
90 | } else { | |||
91 | int new_size = (t->size + 1) * sizeof(char*); | |||
92 | if ((new_cmds = realloc(t->commands, new_size)) == NULL((void *)0)) | |||
93 | err(1, "%s: realloc(3) failed", __FUNCTION__); | |||
94 | ||||
95 | idx = t->size; | |||
96 | t->commands = new_cmds; | |||
97 | t->size++; | |||
98 | } | |||
99 | ||||
100 | /* add command */ | |||
101 | if ((t->commands[idx] = strdup(cmd)) == NULL((void *)0)) | |||
102 | err(1, "%s: strdup(3) failed", __FUNCTION__); | |||
103 | } | |||
104 | ||||
105 | toggle_list* | |||
106 | toggle_list_create(int registr, int argc, char *argv[]) | |||
107 | { | |||
108 | toggle_list *t; | |||
109 | char *cmd = NULL((void *)0); | |||
110 | int i, j; | |||
111 | ||||
112 | if ((t = malloc(sizeof(toggle_list))) == NULL((void *)0)) | |||
113 | err(1, "%s: malloc(3) failed", __FUNCTION__); | |||
114 | ||||
115 | t->commands = NULL((void *)0); | |||
| ||||
116 | t->registr = registr; | |||
117 | t->size = 0; | |||
118 | ||||
119 | /* parse the argv into the toggle list */ | |||
120 | for (i = 0; i < argc; i++) { | |||
121 | if (!strcmp("/", argv[i])) | |||
122 | continue; | |||
123 | ||||
124 | /* count number strings in this command and determine length */ | |||
125 | for (j = i; j < argc && strcmp("/", argv[j]); j++); | |||
126 | ||||
127 | /* now collapse them into a single string */ | |||
128 | cmd = argv2str(j - i + 1, argv + i); | |||
129 | toggle_list_add_command(t, cmd); | |||
130 | free(cmd); | |||
131 | ||||
132 | i += (j - i) - 1; | |||
133 | } | |||
134 | ||||
135 | t->index = t->size - 1; | |||
136 | return t; | |||
137 | } | |||
138 | ||||
139 | void | |||
140 | toggle_list_free(toggle_list *t) | |||
141 | { | |||
142 | size_t i; | |||
143 | ||||
144 | for (i = 0; i < t->size; i++) | |||
145 | free(t->commands[i]); | |||
146 | ||||
147 | free(t); | |||
148 | } | |||
149 | ||||
150 | void | |||
151 | toggle_add(toggle_list *t) | |||
152 | { | |||
153 | if (toggle_get(t->registr) != NULL((void *)0)) | |||
154 | toggle_remove(t->registr); | |||
155 | ||||
156 | toggleset[toggleset_size++] = t; | |||
157 | } | |||
158 | ||||
159 | void | |||
160 | toggle_remove(int registr) | |||
161 | { | |||
162 | size_t i, idx; | |||
163 | bool_Bool found; | |||
164 | ||||
165 | found = false0; | |||
166 | idx = 0; | |||
167 | for (i = 0; i < toggleset_size; i++) { | |||
168 | if (toggleset[i]->registr == registr) { | |||
169 | idx = i; | |||
170 | found = true1; | |||
171 | } | |||
172 | } | |||
173 | ||||
174 | if (!found) return; | |||
175 | ||||
176 | for (i = idx; i < toggleset_size - 1; i++) | |||
177 | toggleset[i] = toggleset[i + 1]; | |||
178 | ||||
179 | toggleset_size--; | |||
180 | } | |||
181 | ||||
182 | toggle_list* | |||
183 | toggle_get(int registr) | |||
184 | { | |||
185 | size_t i; | |||
186 | ||||
187 | for (i = 0; i < toggleset_size; i++) { | |||
188 | if (toggleset[i]->registr == registr) | |||
189 | return toggleset[i]; | |||
190 | } | |||
191 | ||||
192 | return NULL((void *)0); | |||
193 | } | |||
194 | ||||
195 | ||||
196 | /**************************************************************************** | |||
197 | * Misc handy functions | |||
198 | ***************************************************************************/ | |||
199 | ||||
200 | void | |||
201 | setup_viewing_playlist(playlist *p) | |||
202 | { | |||
203 | viewing_playlist = p; | |||
204 | ||||
205 | ui.playlist->nrows = p->nfiles; | |||
206 | ui.playlist->crow = 0; | |||
207 | ui.playlist->voffset = 0; | |||
208 | ui.playlist->hoffset = 0; | |||
209 | } | |||
210 | ||||
211 | int | |||
212 | str2bool(const char *s, bool_Bool *b) | |||
213 | { | |||
214 | /* check true/t/yes/y */ | |||
215 | if (strcasecmp(s, "true") == 0 | |||
216 | || strcasecmp(s, "t") == 0 | |||
217 | || strcasecmp(s, "yes") == 0 | |||
218 | || strcasecmp(s, "y") == 0) { | |||
219 | *b = true1; | |||
220 | return 0; | |||
221 | } | |||
222 | ||||
223 | /* check false/f/no/n */ | |||
224 | if (strcasecmp(s, "false") == 0 | |||
225 | || strcasecmp(s, "f") == 0 | |||
226 | || strcasecmp(s, "no") == 0 | |||
227 | || strcasecmp(s, "n") == 0) { | |||
228 | *b = false0; | |||
229 | return 0; | |||
230 | } | |||
231 | ||||
232 | return -1; | |||
233 | } | |||
234 | ||||
235 | ||||
236 | /**************************************************************************** | |||
237 | * Command handlers | |||
238 | ***************************************************************************/ | |||
239 | ||||
240 | int | |||
241 | cmd_quit(int argc, char *argv[]) | |||
242 | { | |||
243 | bool_Bool forced; | |||
244 | ||||
245 | if (argc != 1) { | |||
246 | paint_error("usage: q[!]"); | |||
247 | return 1; | |||
248 | } | |||
249 | ||||
250 | /* is this a forced quit? */ | |||
251 | forced = (strcmp(argv[0], "q!") == 0); | |||
252 | ||||
253 | /* check if there are any unsaved changes if not forced */ | |||
254 | if (!forced) { | |||
255 | int i; | |||
256 | for (i = 0; i < mdb.nplaylists; i++) { | |||
257 | if (mdb.playlists[i]->needs_saving) { | |||
258 | paint_error("there are playlists with unsaved changes. use \"q!\" to force."); | |||
259 | return 2; | |||
260 | } | |||
261 | } | |||
262 | } | |||
263 | ||||
264 | VSIG_QUIT = 1; | |||
265 | return 0; | |||
266 | } | |||
267 | ||||
268 | int | |||
269 | cmd_write(int argc, char *argv[]) | |||
270 | { | |||
271 | char *filename; | |||
272 | bool_Bool forced; | |||
273 | ||||
274 | if (argc > 2) { | |||
275 | paint_error("usage: w[!] [name]"); | |||
276 | return 1; | |||
277 | } | |||
278 | ||||
279 | forced = (strcmp(argv[0], "w!") == 0); | |||
280 | ||||
281 | if (argc == 1) { /* "save" */ | |||
282 | ||||
283 | /* can't save library or filter results */ | |||
284 | if (viewing_playlist == mdb.library | |||
285 | || viewing_playlist == mdb.filter_results) { | |||
286 | paint_error("use \"w name\" when saving pseudo-playlists like library/filter"); | |||
287 | return 2; | |||
288 | } | |||
289 | ||||
290 | /* can't save a new playlist that has no name */ | |||
291 | if (viewing_playlist->filename == NULL((void *)0)) { | |||
292 | paint_error("use \"w name\" for new playlists"); | |||
293 | return 3; | |||
294 | } | |||
295 | ||||
296 | /* do the save... */ | |||
297 | playlist_save(viewing_playlist); | |||
298 | viewing_playlist->needs_saving = false0; | |||
299 | paint_library(); | |||
300 | paint_message("\"%s\" %d songs written", | |||
301 | viewing_playlist->filename, viewing_playlist->nfiles); | |||
302 | ||||
303 | } else { /* "save as" */ | |||
304 | int i, clobber_index; | |||
305 | bool_Bool will_clobber; | |||
306 | ||||
307 | /* build filename for playlist */ | |||
308 | if (asprintf(&filename, "%s/%s.playlist", mdb.playlist_dir, argv[1]) == -1) | |||
309 | err(1, "cmd_write: asprintf failed"); | |||
310 | ||||
311 | /* check to see if playlist with that name already exists */ | |||
312 | will_clobber = false0; | |||
313 | clobber_index = -1; | |||
314 | for (i = 0; i < mdb.nplaylists; i++) { | |||
315 | if (mdb.playlists[i]->filename != NULL((void *)0) | |||
316 | && strcmp(mdb.playlists[i]->filename, filename) == 0) { | |||
317 | will_clobber = true1; | |||
318 | clobber_index = i; | |||
319 | } | |||
320 | } | |||
321 | ||||
322 | if (will_clobber && !forced) { | |||
323 | paint_error("playlist with that name exists (use \"w!\" to overwrite)"); | |||
324 | free(filename); | |||
325 | return 4; | |||
326 | } | |||
327 | ||||
328 | /* if reached here, we're going to do the save-as... */ | |||
329 | ||||
330 | /* duplicate playlist */ | |||
331 | playlist *dup = playlist_dup(viewing_playlist, filename, argv[1]); | |||
332 | if (will_clobber) | |||
333 | medialib_playlist_remove(clobber_index); | |||
334 | else | |||
335 | ui.library->nrows++; | |||
336 | ||||
337 | /* | |||
338 | * TODO If the original playlist was a new one, with no name (i.e. if | |||
339 | * name == NULL) then we should remove that playlist from the medialib | |||
340 | * and display here. | |||
341 | */ | |||
342 | ||||
343 | /* do the save-as... */ | |||
344 | playlist_save(dup); | |||
345 | medialib_playlist_add(dup); | |||
346 | ||||
347 | dup->needs_saving = false0; | |||
348 | viewing_playlist->needs_saving = false0; | |||
349 | ||||
350 | paint_library(); | |||
351 | paint_message("\"%s\" %d songs written", | |||
352 | filename, viewing_playlist->nfiles); | |||
353 | ||||
354 | free(filename); | |||
355 | } | |||
356 | ||||
357 | return 0; | |||
358 | } | |||
359 | ||||
360 | int | |||
361 | cmd_mode(int argc, char *argv[]) | |||
362 | { | |||
363 | if (argc != 2) { | |||
364 | paint_error("usage: mode [ linear | loop | random ]"); | |||
365 | return 1; | |||
366 | } | |||
367 | ||||
368 | if (strcasecmp(argv[1], "linear") == 0) | |||
369 | player_info.mode = MODE_LINEAR; | |||
370 | else if (strcasecmp(argv[1], "loop") == 0) | |||
371 | player_info.mode = MODE_LOOP; | |||
372 | else if (strcasecmp(argv[1], "random") == 0) | |||
373 | player_info.mode = MODE_RANDOM; | |||
374 | else { | |||
375 | paint_error("invalid mode \"%s\". must be one of: linear, loop, or random", argv[1]); | |||
376 | return 2; | |||
377 | } | |||
378 | ||||
379 | paint_message("mode changed to: %s", argv[1]); | |||
380 | return 0; | |||
381 | } | |||
382 | ||||
383 | int | |||
384 | cmd_new(int argc, char *argv[]) | |||
385 | { | |||
386 | playlist *p; | |||
387 | char *name; | |||
388 | char *filename; | |||
389 | ||||
390 | if (argc > 2) { | |||
391 | paint_error("usage: new [name]"); | |||
392 | return 1; | |||
393 | } | |||
394 | ||||
395 | /* defaults */ | |||
396 | name = "untitled"; | |||
397 | filename = NULL((void *)0); | |||
398 | ||||
399 | /* was a name specified? */ | |||
400 | if (argc == 2) { | |||
401 | /* check for existing playlist with name */ | |||
402 | int i; | |||
403 | for (i = 0; i < mdb.nplaylists; i++) { | |||
404 | if (strcmp(mdb.playlists[i]->name, argv[1]) == 0) { | |||
405 | paint_error("playlist \"%s\" already exists.", argv[1]); | |||
406 | return 2; | |||
407 | } | |||
408 | } | |||
409 | ||||
410 | name = argv[1]; | |||
411 | if (asprintf(&filename, "%s/%s.playlist", mdb.playlist_dir, name) == -1) | |||
412 | err(1, "cmd_new: asprintf failed"); | |||
413 | } | |||
414 | ||||
415 | /* create & setup playlist */ | |||
416 | p = playlist_new(); | |||
417 | p->needs_saving = true1; | |||
418 | p->filename = filename; | |||
419 | if ((p->name = strdup(name)) == NULL((void *)0)) | |||
420 | err(1, "cmd_new: strdup(3) failed"); | |||
421 | ||||
422 | /* add playlist to media library and update ui */ | |||
423 | medialib_playlist_add(p); | |||
424 | ui.library->nrows++; | |||
425 | if (p->filename != NULL((void *)0)) | |||
426 | playlist_save(p); | |||
427 | ||||
428 | /* redraw */ | |||
429 | paint_library(); | |||
430 | paint_message("playlist \"%s\" added", name); | |||
431 | ||||
432 | return 0; | |||
433 | } | |||
434 | ||||
435 | int | |||
436 | cmd_filter(int argc, char *argv[]) | |||
437 | { | |||
438 | playlist *results; | |||
439 | char *search_phrase; | |||
440 | bool_Bool match; | |||
441 | int i; | |||
442 | ||||
443 | if (argc == 1) { | |||
444 | paint_error("usage: filter[!] token [token2 ...]"); | |||
445 | return 1; | |||
446 | } | |||
447 | ||||
448 | /* determine what kind of filter we're doing */ | |||
449 | match = argv[0][strlen(argv[0]) - 1] != '!'; | |||
450 | ||||
451 | /* set the raw query */ | |||
452 | search_phrase = argv2str(argc - 1, argv + 1); | |||
453 | mi_query_setraw(search_phrase); | |||
454 | free(search_phrase); | |||
455 | ||||
456 | /* clear existing global query & set new one */ | |||
457 | mi_query_clear(); | |||
458 | for (i = 1; i < argc; i++) | |||
459 | mi_query_add_token(argv[i]); | |||
460 | ||||
461 | /* do actual filter */ | |||
462 | results = playlist_filter(viewing_playlist, match); | |||
463 | ||||
464 | /* swap necessary bits of results with filter playlist */ | |||
465 | swap(meta_info **, results->files, mdb.filter_results->files){ meta_info ** temp = results->files; results->files = mdb .filter_results->files; mdb.filter_results->files = temp ;}; | |||
466 | swap(int, results->nfiles, mdb.filter_results->nfiles){ int temp = results->nfiles; results->nfiles = mdb.filter_results ->nfiles; mdb.filter_results->nfiles = temp;}; | |||
467 | swap(int, results->capacity, mdb.filter_results->capacity){ int temp = results->capacity; results->capacity = mdb .filter_results->capacity; mdb.filter_results->capacity = temp;}; | |||
468 | playlist_free(results); | |||
469 | ||||
470 | /* redraw */ | |||
471 | setup_viewing_playlist(mdb.filter_results); | |||
472 | paint_library(); | |||
473 | paint_playlist(); | |||
474 | ||||
475 | return 0; | |||
476 | } | |||
477 | ||||
478 | int | |||
479 | cmd_sort(int argc, char *argv[]) | |||
480 | { | |||
481 | const char *errmsg; | |||
482 | ||||
483 | if (argc != 2) { | |||
484 | paint_error("usage: sort <sort-description>"); | |||
485 | return 1; | |||
486 | } | |||
487 | ||||
488 | /* setup global sort description */ | |||
489 | if (mi_sort_set(argv[1], &errmsg) != 0) { | |||
490 | paint_error("%s: bad sort description: %s", argv[0], errmsg); | |||
491 | return 2; | |||
492 | } | |||
493 | ||||
494 | /* do the actual sort */ | |||
495 | qsort(viewing_playlist->files, viewing_playlist->nfiles, | |||
496 | sizeof(meta_info*), mi_compare); | |||
497 | ||||
498 | if (!ui_is_init()) | |||
499 | return 0; | |||
500 | ||||
501 | /* redraw */ | |||
502 | paint_playlist(); | |||
503 | ||||
504 | /* if we sorted a playlist other than library, and user wants to save sorts */ | |||
505 | if (viewing_playlist != mdb.library && sorts_need_saving) { | |||
506 | viewing_playlist->needs_saving = true1; | |||
507 | paint_library(); | |||
508 | } | |||
509 | ||||
510 | return 0; | |||
511 | } | |||
512 | ||||
513 | int | |||
514 | cmd_display(int argc, char *argv[]) | |||
515 | { | |||
516 | const char *errmsg; | |||
517 | ||||
518 | if (argc != 2) { | |||
519 | paint_error("usage: display [ reset | show | <display-description> ]"); | |||
520 | return 1; | |||
521 | } | |||
522 | ||||
523 | /* show existng display? */ | |||
524 | if (strcasecmp(argv[1], "show") == 0) { | |||
525 | paint_message(":display %s", mi_display_tostr()); | |||
526 | return 0; | |||
527 | } | |||
528 | ||||
529 | /* reset display to default? */ | |||
530 | if (strcasecmp(argv[1], "reset") == 0) { | |||
531 | mi_display_reset(); | |||
532 | if (ui_is_init()) | |||
533 | paint_playlist(); | |||
534 | ||||
535 | return 0; | |||
536 | } | |||
537 | ||||
538 | /* if reached here, setup global display description */ | |||
539 | ||||
540 | if (mi_display_set(argv[1], &errmsg) != 0) { | |||
541 | paint_error("%s: bad display description: %s", argv[0], errmsg); | |||
542 | return 1; | |||
543 | } | |||
544 | ||||
545 | if(ui_is_init()) | |||
546 | paint_playlist(); | |||
547 | ||||
548 | return 0; | |||
549 | } | |||
550 | ||||
551 | int | |||
552 | cmd_color(int argc, char *argv[]) | |||
553 | { | |||
554 | char *item; | |||
555 | char *fg, *bg; | |||
556 | int i_item, i_fg, i_bg, j; | |||
557 | ||||
558 | if (argc != 2) { | |||
559 | paint_error("usage: %s ITEM=FG,BG", argv[0]); | |||
560 | return 1; | |||
561 | } | |||
562 | ||||
563 | /* extract item and foreground/background colors */ | |||
564 | item = argv[1]; | |||
565 | if ((fg = strchr(item, '=')) == NULL((void *)0)) { | |||
566 | paint_error("usage: %s ITEM=FG,BG", argv[0]); | |||
567 | return 2; | |||
568 | } | |||
569 | *fg = '\0'; | |||
570 | fg++; | |||
571 | ||||
572 | if ((bg = strchr(fg, ',')) == NULL((void *)0)) { | |||
573 | paint_error("usage: %s ITEM=FG,BG", argv[0]); | |||
574 | return 3; | |||
575 | } | |||
576 | *bg = '\0'; | |||
577 | bg++; | |||
578 | ||||
579 | /* convert all */ | |||
580 | if ((i_item = paint_str2item(item)) < 0) { | |||
581 | paint_error("invalid item '%s'", item); | |||
582 | return 4; | |||
583 | } | |||
584 | ||||
585 | if ((i_fg = paint_str2color(fg)) == -2) { | |||
586 | paint_error("invalid foreground color '%s'", fg); | |||
587 | return 5; | |||
588 | } | |||
589 | ||||
590 | if ((i_bg = paint_str2color(bg)) == -2) { | |||
591 | paint_error("invalid background color '%s'", bg); | |||
592 | return 6; | |||
593 | } | |||
594 | ||||
595 | /* init color */ | |||
596 | init_pair(i_item, i_fg, i_bg); | |||
597 | ||||
598 | /* if this was a cinfo item, indicate that it was set */ | |||
599 | for (j = 0; j < MI_NUM_CINFO8; j++) { | |||
600 | if (i_item == colors.cinfos[j]) | |||
601 | colors.cinfos_set[j] = true1; | |||
602 | } | |||
603 | ||||
604 | /* redraw */ | |||
605 | if (ui_is_init()) { | |||
606 | ui_clear(); | |||
607 | paint_all(); | |||
608 | } | |||
609 | ||||
610 | return 0; | |||
611 | } | |||
612 | ||||
613 | int | |||
614 | cmd_set(int argc, char *argv[]) | |||
615 | { | |||
616 | const char *err; | |||
617 | char *property; | |||
618 | char *value; | |||
619 | bool_Bool tf; | |||
620 | bool_Bool player_is_setup; | |||
621 | ||||
622 | if (argc != 2) { | |||
623 | paint_error("usage: %s <property>=<value>", argv[0]); | |||
624 | return 1; | |||
625 | } | |||
626 | ||||
627 | /* determine if the player has been setup (needed for redraws below) */ | |||
628 | player_is_setup = (player.name != NULL((void *)0)) | |||
629 | && (*player.name != '\0'); | |||
630 | ||||
631 | /* extract property and value */ | |||
632 | property = argv[1]; | |||
633 | if ((value = strchr(property, '=')) == NULL((void *)0)) { | |||
634 | paint_error("usage: %s <property>=<value>", argv[0]); | |||
635 | return 2; | |||
636 | } | |||
637 | *value = '\0'; | |||
638 | value++; | |||
639 | ||||
640 | /* handle property */ | |||
641 | ||||
642 | if (strcasecmp(property, "lwidth") == 0) { | |||
643 | /* get max width and height */ | |||
644 | int max_w, new_width; | |||
645 | max_w = getmaxx(stdscr)((stdscr) ? ((stdscr)->_maxx + 1) : (-1)); | |||
646 | ||||
647 | /* validate and convert width user provided */ | |||
648 | new_width = (int)strtonum(value, 1, max_w, &err); | |||
649 | if (err != NULL((void *)0)) { | |||
650 | paint_error("%s %s: bad width: '%s' %s", | |||
651 | argv[0], property, value, err); | |||
652 | return 3; | |||
653 | } | |||
654 | ||||
655 | /* resize & redraw (if we're past setup & curses is running) */ | |||
656 | ui.lwidth = new_width; | |||
657 | ui_resize(); | |||
658 | if(player_is_setup && ui_is_init()) { | |||
659 | ui_clear(); | |||
660 | paint_all(); | |||
661 | } | |||
662 | ||||
663 | } else if (strcasecmp(property, "lhide") == 0) { | |||
664 | if (str2bool(value, &tf) < 0) { | |||
665 | paint_error("%s %s: value must be boolean", | |||
666 | argv[0], property); | |||
667 | return 4; | |||
668 | } | |||
669 | ui.lhide = tf; | |||
670 | if (ui.lhide) { | |||
671 | if (ui.active == ui.playlist) | |||
672 | ui_hide_library(); | |||
673 | if (player_is_setup && ui_is_init()) { | |||
674 | ui_clear(); | |||
675 | paint_all(); | |||
676 | paint_message("library window hidden"); | |||
677 | } | |||
678 | } else { | |||
679 | if (ui.library->cwin == NULL((void *)0)) ui_unhide_library(); | |||
680 | if (player_is_setup && ui_is_init()) paint_all(); | |||
681 | paint_message("library window un-hidden"); | |||
682 | } | |||
683 | ||||
684 | } else if (strcasecmp(property, "match-fname") == 0) { | |||
685 | if (str2bool(value, &tf) < 0) { | |||
686 | paint_error("%s %s: value must be boolean", | |||
687 | argv[0], property); | |||
688 | return 5; | |||
689 | } | |||
690 | mi_query_match_filename = tf; | |||
691 | if (mi_query_match_filename) | |||
692 | paint_message("filenames will be matched against"); | |||
693 | else | |||
694 | paint_message("filenames will NOT be matched against"); | |||
695 | ||||
696 | } else if (strcasecmp(property, "save-sorts") == 0) { | |||
697 | if (str2bool(value, &tf) < 0) { | |||
698 | paint_error("%s %s: value must be boolean", | |||
699 | argv[0], property); | |||
700 | return 6; | |||
701 | } | |||
702 | sorts_need_saving = tf; | |||
703 | if (sorts_need_saving) | |||
704 | paint_message("changing sort will be prompted for saving"); | |||
705 | else | |||
706 | paint_message("changing sort will NOT be prompted for saving"); | |||
707 | ||||
708 | } else { | |||
709 | paint_error("%s: unknown property '%s'", argv[0], property); | |||
710 | return 7; | |||
711 | } | |||
712 | ||||
713 | return 0; | |||
714 | } | |||
715 | ||||
716 | int | |||
717 | cmd_reload(int argc, char *argv[]) | |||
718 | { | |||
719 | if (argc != 2) { | |||
720 | paint_error("usage: %s [ db | conf ]", argv[0]); | |||
721 | return 1; | |||
722 | } | |||
723 | ||||
724 | /* reload database or config file */ | |||
725 | if (strcasecmp(argv[1], "db") == 0) { | |||
726 | ||||
727 | char *db_file = strdup(mdb.db_file); | |||
728 | char *playlist_dir = strdup(mdb.playlist_dir); | |||
729 | if (db_file == NULL((void *)0) || playlist_dir == NULL((void *)0)) | |||
730 | err(1, "cmd_reload: strdup(3) failed"); | |||
731 | ||||
732 | /* stop playback TODO investigate a nice way around this */ | |||
733 | player_stop(); | |||
734 | ||||
735 | /* reload db */ | |||
736 | medialib_destroy(); | |||
737 | medialib_load(db_file, playlist_dir); | |||
738 | ||||
739 | /* sort entries */ | |||
740 | qsort(mdb.library->files, mdb.library->nfiles, sizeof(meta_info*), mi_compare); | |||
741 | ||||
742 | free(db_file); | |||
743 | free(playlist_dir); | |||
744 | ||||
745 | /* re-setup ui basics */ | |||
746 | playing_playlist = NULL((void *)0); | |||
747 | setup_viewing_playlist(mdb.library); | |||
748 | ui.library->voffset = 0; | |||
749 | ui.library->nrows = mdb.nplaylists; | |||
750 | ui.library->crow = 0; | |||
751 | paint_all(); | |||
752 | ||||
753 | } else if (strcasecmp(argv[1], "conf") == 0) { | |||
754 | load_config(); | |||
755 | paint_message("configuration reloaded"); | |||
756 | } else { | |||
757 | paint_error("usage: %s [ db | conf ]", argv[0]); | |||
758 | return 2; | |||
759 | } | |||
760 | ||||
761 | return 0; | |||
762 | } | |||
763 | ||||
764 | int | |||
765 | cmd_bind(int argc, char *argv[]) | |||
766 | { | |||
767 | KeyAction action; | |||
768 | KeyCode code; | |||
769 | ||||
770 | if (argc < 3 || argc > 4) { | |||
771 | paint_error("usage: %s <action> <keycode>", argv[0]); | |||
772 | return 1; | |||
773 | } | |||
774 | ||||
775 | if (!kb_str2action(argv[1], &action)) { | |||
776 | paint_error("Unknown action '%s'", argv[1]); | |||
777 | return 1; | |||
778 | } | |||
779 | ||||
780 | if (argc == 3) { | |||
781 | if ((code = kb_str2keycode(argv[2])) < 0) { | |||
782 | paint_error("Invalid keycode '%s'", argv[2]); | |||
783 | return 1; | |||
784 | } | |||
785 | } else { | |||
786 | if ((code = kb_str2keycode2(argv[2], argv[3])) < 0) { | |||
787 | paint_error("Invalid keycode '%s'", argv[2]); | |||
788 | return 1; | |||
789 | } | |||
790 | } | |||
791 | ||||
792 | kb_bind(action, code); | |||
793 | return 0; | |||
794 | } | |||
795 | ||||
796 | int | |||
797 | cmd_unbind(int argc, char *argv[]) | |||
798 | { | |||
799 | KeyAction action; | |||
800 | KeyCode key; | |||
801 | ||||
802 | /* unbind all case ("unbind *") */ | |||
803 | if (argc == 2 && strcmp(argv[1], "*") == 0) { | |||
804 | kb_unbind_all(); | |||
805 | return 0; | |||
806 | } | |||
807 | ||||
808 | /* unbind action case ("unbind action <ACTION>") */ | |||
809 | if (argc == 3 && strcasecmp(argv[1], "action") == 0) { | |||
810 | if (kb_str2action(argv[2], &action)) { | |||
811 | kb_unbind_action(action); | |||
812 | return 0; | |||
813 | } else { | |||
814 | paint_error("Unknown action '%s'", argv[2]); | |||
815 | return 1; | |||
816 | } | |||
817 | } | |||
818 | ||||
819 | /* unbind key case, no control ("unbind key X") */ | |||
820 | if (argc == 3 && strcasecmp(argv[1], "key") == 0) { | |||
821 | if ((key = kb_str2keycode(argv[2])) < 0) { | |||
822 | paint_error("Invalid keycode '%s'", argv[2]); | |||
823 | return 1; | |||
824 | } | |||
825 | ||||
826 | kb_unbind_key(key); | |||
827 | return 0; | |||
828 | } | |||
829 | ||||
830 | /* unbind key case, with control ("unbind key control X") */ | |||
831 | if (argc == 4 && strcasecmp(argv[1], "key") == 0) { | |||
832 | if ((key = kb_str2keycode2(argv[2], argv[3])) < 0) { | |||
833 | paint_error("Invalid keycode '%s %s'", argv[2], argv[3]); | |||
834 | return 1; | |||
835 | } | |||
836 | ||||
837 | kb_unbind_key(key); | |||
838 | return 0; | |||
839 | } | |||
840 | ||||
841 | paint_error("usage: unbind [* | action <ACTION> | key <KEYCODE> ]"); | |||
842 | return 1; | |||
843 | } | |||
844 | ||||
845 | int | |||
846 | cmd_toggle(int argc, char *argv[]) | |||
847 | { | |||
848 | toggle_list *t; | |||
849 | char **cmd_argv; | |||
850 | int cmd_argc; | |||
851 | int registr; | |||
852 | ||||
853 | if (argc < 3) { | |||
| ||||
854 | paint_error("usage: %s <register> <action1> / ...", argv[0]); | |||
855 | return 1; | |||
856 | } | |||
857 | ||||
858 | if (strlen(argv[1]) != 1) { | |||
859 | paint_error("error: register name must be a single letter (in [a-zA-Z])"); | |||
860 | return 1; | |||
861 | } | |||
862 | ||||
863 | registr = *(argv[1]); | |||
864 | ||||
865 | if (!( ('a' <= registr && registr <= 'z') | |||
866 | || ('A' <= registr && registr <= 'Z'))) { | |||
867 | paint_error("error: invalid register name. Must be one of [a-zA-Z]"); | |||
868 | return 1; | |||
869 | } | |||
870 | ||||
871 | cmd_argc = argc - 2; | |||
872 | cmd_argv = argv + 2; | |||
873 | t = toggle_list_create(registr, cmd_argc, cmd_argv); | |||
874 | toggle_add(t); | |||
875 | return 0; | |||
876 | } | |||
877 | ||||
878 | int | |||
879 | cmd_playlist(int argc, char *argv[]) | |||
880 | { | |||
881 | int x; | |||
882 | int idx = -1; | |||
883 | ||||
884 | if (argc != 2) { | |||
885 | paint_error("usage: playlist <list-name>"); | |||
886 | return 1; | |||
887 | } | |||
888 | ||||
889 | for(x = 0; x < mdb.nplaylists; x++) { | |||
890 | if(!strncmp(argv[1], mdb.playlists[x]->name, strlen(argv[1]))) { | |||
891 | if(idx > -1) { | |||
892 | idx = -2; | |||
893 | break; | |||
894 | } | |||
895 | ||||
896 | if(idx == -1) | |||
897 | idx = x; | |||
898 | } | |||
899 | } | |||
900 | ||||
901 | if(idx > -1) { | |||
902 | setup_viewing_playlist(mdb.playlists[idx]); | |||
903 | ui.active = ui.playlist; | |||
904 | paint_all(); | |||
905 | paint_message("jumped to playlist: %s", mdb.playlists[idx]->name); | |||
906 | return 0; | |||
907 | } | |||
908 | ||||
909 | if(idx == -1) { | |||
910 | paint_error("no match for: %s", argv[1]); | |||
911 | return 0; | |||
912 | } | |||
913 | ||||
914 | if(idx == -2) | |||
915 | paint_error("no unique match for: %s", argv[1]); | |||
916 | ||||
917 | return 0; | |||
918 | } | |||
919 | ||||
920 | void | |||
921 | cmd_execute(char *cmd) | |||
922 | { | |||
923 | const char *errmsg = NULL((void *)0); | |||
924 | bool_Bool found; | |||
925 | char **argv; | |||
926 | int argc; | |||
927 | int found_idx = 0; | |||
928 | int num_matches; | |||
929 | int i; | |||
930 | ||||
931 | if (str2argv(cmd, &argc, &argv, &errmsg) != 0) { | |||
932 | paint_error("parse error: %s in '%s'", errmsg, cmd); | |||
933 | return; | |||
934 | } | |||
935 | ||||
936 | found = false0; | |||
937 | num_matches = 0; | |||
938 | for (i = 0; i < CommandPathSize; i++) { | |||
939 | if (match_command_name(argv[0], CommandPath[i].name)) { | |||
940 | found = true1; | |||
941 | found_idx = i; | |||
942 | num_matches++; | |||
943 | } | |||
944 | } | |||
945 | ||||
946 | if (found && num_matches == 1) | |||
947 | (CommandPath[found_idx].func)(argc, argv); | |||
948 | else if (num_matches > 1) | |||
949 | paint_error("Ambiguous abbreviation '%s'", argv[0]); | |||
950 | else | |||
951 | paint_error("Unknown commands '%s'", argv[0]); | |||
952 | ||||
953 | argv_free(&argc, &argv); | |||
954 | } | |||
955 | ||||
956 | ||||
957 | /***************************************************************************** | |||
958 | * command window input methods | |||
959 | ****************************************************************************/ | |||
960 | ||||
961 | /* | |||
962 | * Note: Both of these return 0 if input was successfull, and something else | |||
963 | * (1) if the user cancelled the input (such as, by hitting ESCAPE) | |||
964 | */ | |||
965 | ||||
966 | int | |||
967 | user_getstr(const char *prompt, char **response) | |||
968 | { | |||
969 | const int MAX_INPUT_SIZE = 1000; /* TODO remove this limit */ | |||
970 | char *input; | |||
971 | int pos, ch, ret; | |||
972 | ||||
973 | /* display the prompt */ | |||
974 | werase(ui.command); | |||
975 | mvwprintw(ui.command, 0, 0, "%s", prompt); | |||
976 | ||||
977 | /* position the cursor */ | |||
978 | curs_set(1); | |||
979 | wmove(ui.command, 0, strlen(prompt)); | |||
980 | wrefresh(ui.command); | |||
981 | ||||
982 | /* allocate input space and clear */ | |||
983 | if ((input = calloc(MAX_INPUT_SIZE, sizeof(char))) == NULL((void *)0)) | |||
984 | err(1, "user_getstr: calloc(3) failed for input string"); | |||
985 | ||||
986 | bzero(input, MAX_INPUT_SIZE); | |||
987 | ||||
988 | /* start getting input */ | |||
989 | ret = 0; | |||
990 | pos = 0; | |||
991 | while ((ch = getch()wgetch(stdscr)) && !VSIG_QUIT) { | |||
992 | ||||
993 | /* | |||
994 | * Handle any signals. Note that the use of curs_set, wmvoe, and | |||
995 | * wrefresh here are all necessary to ensure that the cursor does | |||
996 | * not show anywhere outside of the command window. | |||
997 | */ | |||
998 | curs_set(0); | |||
999 | process_signals(); | |||
1000 | curs_set(1); | |||
1001 | wmove(ui.command, 0, strlen(prompt) + pos); | |||
1002 | wrefresh(ui.command); | |||
1003 | ||||
1004 | if (ch == ERR(-1)) | |||
1005 | continue; | |||
1006 | ||||
1007 | if (ch == '\n' || ch == 13) | |||
1008 | break; | |||
1009 | ||||
1010 | /* handle 'escape' */ | |||
1011 | if (ch == 27) { | |||
1012 | ret = 1; | |||
1013 | goto end; | |||
1014 | } | |||
1015 | ||||
1016 | /* handle 'backspace' / left-arrow, etc. */ | |||
1017 | if (ch == 127 || ch == KEY_BACKSPACE0407 || ch == KEY_LEFT0404 | |||
1018 | || ch == KEY_DC0512 || ch == KEY_SDC0577) { | |||
1019 | if (pos == 0) { | |||
1020 | if (ch == KEY_BACKSPACE0407) { | |||
1021 | ret = 1; | |||
1022 | goto end; | |||
1023 | } | |||
1024 | beep(); | |||
1025 | } else { | |||
1026 | mvwaddch(ui.command, 0, strlen(prompt) + pos - 1, ' ')(wmove(ui.command,0,strlen(prompt) + pos - 1) == (-1) ? (-1) : waddch(ui.command,' ')); | |||
1027 | wmove(ui.command, 0, strlen(prompt) + pos - 1); | |||
1028 | wrefresh(ui.command); | |||
1029 | pos--; | |||
1030 | } | |||
1031 | continue; | |||
1032 | } | |||
1033 | ||||
1034 | /* got regular input. add to buffer. */ | |||
1035 | input[pos] = ch; | |||
1036 | mvwaddch(ui.command, 0, strlen(prompt) + pos, ch)(wmove(ui.command,0,strlen(prompt) + pos) == (-1) ? (-1) : waddch (ui.command,ch)); | |||
1037 | wrefresh(ui.command); | |||
1038 | pos++; | |||
1039 | ||||
1040 | /* see todo above - realloc input buffer here if position reaches max */ | |||
1041 | if (pos >= MAX_INPUT_SIZE) | |||
1042 | errx(1, "user_getstr: shamefull limit reached"); | |||
1043 | } | |||
1044 | ||||
1045 | /* For lack of input, bail out */ | |||
1046 | if (pos == 0) { | |||
1047 | ret = 1; | |||
1048 | goto end; | |||
1049 | } | |||
1050 | ||||
1051 | /* NULL-terminate and trim off trailing whitespace */ | |||
1052 | input[pos--] = '\0'; | |||
1053 | for (; input[pos] == ' ' && pos >= 0; pos--) | |||
1054 | input[pos] = '\0'; | |||
1055 | ||||
1056 | /* trim the fat */ | |||
1057 | if ((*response = calloc(strlen(input) + 1, sizeof(char))) == NULL((void *)0)) | |||
1058 | err(1, "user_getstr: calloc(3) failed for result"); | |||
1059 | ||||
1060 | snprintf(*response, strlen(input) + 1, "%s", input); | |||
1061 | ||||
1062 | end: | |||
1063 | free(input); | |||
1064 | curs_set(0); | |||
1065 | return ret; | |||
1066 | } | |||
1067 | ||||
1068 | int | |||
1069 | user_get_yesno(const char *msg, int *response) | |||
1070 | { | |||
1071 | char *answer; | |||
1072 | ||||
1073 | if (user_getstr(msg, &answer) != 0) | |||
1074 | return 1; | |||
1075 | ||||
1076 | if (strncasecmp(answer, "yes", 3) == 0 | |||
1077 | || strncasecmp(answer, "y", 1) == 0) | |||
1078 | *response = 1; | |||
1079 | else if (strncasecmp(answer, "no", 2) == 0 | |||
1080 | || strncasecmp(answer, "n", 1) == 0) | |||
1081 | *response = 0; | |||
1082 | else | |||
1083 | *response = -1; | |||
1084 | ||||
1085 | free(answer); | |||
1086 | return 0; | |||
1087 | } | |||
1088 |