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