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