| File: | commands.c |
| Location: | line 101, column 26 |
| Description: | Array access (via field 'commands') results in a null pointer dereference |
| 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 |