#ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include "conf.h" #include "dir.h" #include "err.h" #include "mem.h" #include "player.h" #include "queue.h" #include "tui.h" /* Call curses initialization routines. */ static void tui_setup_display (struct TUI *tui) { int status = 0; static struct TUI *my_tui; if (tui) my_tui = tui; initscr (); status |= halfdelay (3); status |= noecho (); status |= nonl (); status |= intrflush (stdscr, FALSE); keypad (stdscr, TRUE); curs_set (0); if (status == ERR) { endwin (); fprintf (stderr, "error: Could not setup wanted curses mode.\n"); exit (ERROR); } /* LINES & COLS are not updated after resize Linux, why? */ my_tui->lines = LINES; my_tui->cols = COLS; my_tui->redraw = 1; } /* static void tui_exit_by_signal (struct PLAYER *p) { struct PLAYER *my_p; if (p) { my_p = p; return; } my_p->kill = my_p->pid; player_kill (my_p); endwin (); exit (0); } */ static void tui_signal_handler (int signal) { if (signal == SIGWINCH) { endwin (); tui_setup_display (0); } /* if (signal == SIGINT) { tui_exit_by_signal (0); } */ } /* Ask for Yes or No */ static int tui_yes_no () { int r = 2; do { switch (getch ()) { case 'y': case 'Y': r = 1; break; case 'n': case 'N': r = 0; break; } } while (r == 2); return r; } /* Build index with items visible in the current directory. */ static void tui_update_list (struct TUI *tui) { int pos = 0, i, items; if (tui->view != -1) { items = dir_count_parent (tui->dinfo, tui->view); if (items) mem_resize ((void *)&tui->list, items * sizeof (int)); for (i=0; idinfo, tui->view, pos); tui->list[i] = pos; pos++; } } else { items = tui->dinfo->trees; mem_resize ((void *)&tui->list, items * sizeof (int)); pos = 0; for (i=0; idinfo, pos); tui->list[i] = pos; pos++; } } tui->disp[DM_ITEMS].items = items; } /* Search for key in item list */ static int tui_search_list (struct TUI *tui, int key) { int i; for (i=0; idisp[DM_ITEMS].items; i++) { if (tui->list[i] == key) return i; } return 0; } /* Print message on the status bar. */ static void tui_status_message (struct TUI *tui, char *message) { move (0,2); clrtoeol (); addnstr (message, tui->cols - 2); refresh (); } /* Redraw status bar */ static void tui_redraw_status (struct TUI *tui) { int strsize; char *str, buf[11]; move (0, 0); clrtoeol (); if (tui->player->paused) addch ('P'); move (0,2); switch (tui->dm) { case DM_ITEMS: addstr ("(items) "); if (tui->view != -1) { str = dir_build_path (tui->dinfo, tui->view); strsize = strlen (str); if (strsize <= tui->cols - 10) addnstr (str, strsize); else addch ('+'); free (str); } break; case DM_QUEUE: addstr ("(queue) "); buf[9] = 0; snprintf (buf, 11, "%d", tui->queue->items); if (!buf[9]) addstr (buf); else addch ('+'); } refresh (); } /* Redraw list */ static void tui_redraw_list (struct TUI *tui) { int line, pos, item, strsize; char *str; struct TUI_DISP *d = &tui->disp[tui->dm]; switch (tui->dm) { case DM_ITEMS: pos = d->base; for (line=2; linelines; line++) { move (line, 0); clrtoeol (); /* Screen is sometimes messed up if refresh() below is left out. * ncurses bug? */ refresh (); if (pos < d->items) { item = tui->list[pos]; if (tui->dinfo->item[item].queued) addch ('q'); if (!tui->dinfo->item[item].type) addch ('D'); strsize = tui->dinfo->item[item].size; if (strsize > (tui->cols - 2)) strsize = tui->cols - 2; str = tui->dinfo->strings; str += tui->dinfo->item[item].offset; mvaddnstr (line, 2, str, strsize); pos++; } } break; case DM_QUEUE: pos = d->base; for (line=2; linelines; line++) { move (line, 0); clrtoeol (); /* Screen is sometimes messed up if refresh() below is left out. * ncurses bug? */ refresh (); if (pos < d->items) { item = queue_get_item (tui->queue, pos); strsize = tui->dinfo->item[item].size; if (strsize > (tui->cols - 2)) strsize = tui->cols - 2; str = tui->dinfo->strings; str += tui->dinfo->item[item].offset; mvaddnstr (line, 2, str, strsize); pos++; } } } refresh (); } /* Update display variables to wanted cursor pos. */ static void tui_upd_y (struct TUI_DISP *td, struct TUI *tui, int offset, int redraw) { int lines = tui->lines - 2; td->cursor += offset; if (td->cursor < 0) td->cursor = 0; if (td->cursor >= td->items) td->cursor = td->items - 1; if (td->cursor < td->base) { td->base = td->cursor - (lines - 1); if (td->base < 0) td->base = 0; redraw = 1; } if (td->cursor >= (td->base + lines)) { td->base = td->cursor; if (td->base > (td->items - lines)) td->base = td->items - lines; redraw = 1; } if (redraw) tui_redraw_list (tui); } /* Move cursor and update screen as needed * mode is one of CURSOR_UP, CURSOR_DOWN, CURSOR_PG_UP, CURSOR_PG_DOWN * or CURSOR_REDRAW */ static void tui_move_cursor (struct TUI *tui, int mode) { int dot_mode = 0; struct TUI_DISP *d; d = &tui->disp[tui->dm]; switch (mode) { case CURSOR_UP: if (d->cursor > 0) { mvaddch (d->cursor - d->base + 2, 1, ' '); tui_upd_y (d, tui, -1, 0); mvaddch (d->cursor - d->base + 2, 1, '*'); refresh (); } break; case CURSOR_DOWN_ADD: dot_mode = 1; case CURSOR_DOWN_DELETE: if (!dot_mode) dot_mode = 2; case CURSOR_DOWN: if (dot_mode == 1) mvaddch (d->cursor - d->base + 2, 0, 'q'); if (dot_mode == 2) mvaddch (d->cursor - d->base + 2, 0, ' '); if (d->cursor < (d->items - 1)) { mvaddch (d->cursor - d->base + 2, 1, ' '); tui_upd_y (d, tui, 1, 0); mvaddch (d->cursor - d->base + 2, 1, '*'); refresh (); } break; case CURSOR_PG_UP: if (d->cursor > 0) { mvaddch (d->cursor - d->base + 2, 1, ' '); tui_upd_y (d, tui, -(tui->lines - 2), 0); mvaddch (d->cursor - d->base + 2, 1, '*'); refresh (); } break; case CURSOR_PG_DOWN: if (d->cursor < (d->items - 1)) { mvaddch (d->cursor - d->base + 2, 1, ' '); tui_upd_y (d, tui, tui->lines - 2, 0); mvaddch (d->cursor - d->base + 2, 1, '*'); refresh (); } break; case CURSOR_REDRAW: if (d->items) { if (d->cursor >= d->items) d->cursor = d->items - 1; tui_upd_y (d, tui, 0, 1); mvaddch (d->cursor - d->base + 2, 1, '*'); refresh (); } else { tui_redraw_list (tui); } } } /* Prints help message and waits for key press. */ static void tui_help_screen (struct TUI *tui) { static char help[] = {" (help)\n" "\n" "I a add tune\n" " b page up\n" " c clear queue\n" " d delete tune\n" " h help message\n" " i up\n" "I j left\n" " k down\n" "I l right\n" " m move to top\n" " p pause\n" " q quit\n" " r randomize\n" " s skip tune\n" " t toggle display\n" " sp page down\n" "\n" " "}; erase (); mvaddstr (0, 0, help); refresh (); while (getch () == -1) {} tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } /* Enter directory at cursor position and redraw screen */ static void tui_enter_dir (struct TUI *tui) { int item; struct DIR_ITEM *item_p; if ((tui->dm == DM_ITEMS) && tui->disp[DM_ITEMS].items) { item = tui->list[tui->disp[DM_ITEMS].cursor]; item_p = &tui->dinfo->item[item]; if (!item_p->type) { tui->view = item; tui_update_list (tui); tui->disp[DM_ITEMS].cursor = 0; tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } } } /* Leave directory and redraw screen */ static void tui_leave_dir (struct TUI *tui) { int old, cursor; struct DIR_ITEM *item_p = &tui->dinfo->item[tui->view]; if ((tui->dm == DM_ITEMS) && (tui->view != -1)) { old = tui->view; if (!item_p->level) tui->view = -1; else tui->view = tui->dinfo->item[tui->view].parent; tui_update_list (tui); cursor = tui_search_list (tui, old); tui->disp[DM_ITEMS].cursor = cursor; tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } } /* Add item at cursor position to queue. * Add dot to display as well. */ static void tui_add_to_queue (struct TUI *tui) { int item; if (tui->dm == DM_ITEMS) { item = tui->list[tui->disp[DM_ITEMS].cursor]; if (tui->dinfo->item[item].type) { if (!tui->dinfo->item[item].queued) { queue_append (tui->queue, tui->dinfo, item); tui->disp[DM_QUEUE].items = tui->queue->items; tui_move_cursor (tui, CURSOR_DOWN_ADD); } else { tui_move_cursor (tui, CURSOR_DOWN); } } else { queue_append_dir (tui->queue, tui->dinfo, item); tui->disp[DM_QUEUE].items = tui->queue->items; tui_move_cursor (tui, CURSOR_DOWN); } } } /* Delete item at cursor position to queue. * Delete dot from display as well. */ static void tui_delete_from_queue (struct TUI *tui) { int item, pos, playing_item; struct QUEUE *q = tui->queue; if (!tui->queue->items) return; switch (tui->dm) { case DM_ITEMS: item = tui->list[tui->disp[DM_ITEMS].cursor]; if (tui->dinfo->item[item].queued) { pos = queue_search_for_item (tui->queue, item); if (pos != -1) { if (!pos) { tui->player->cmd = PLAYER_SKIP; tui->disp[DM_ITEMS].cursor++; } else { queue_delete (tui->queue, tui->dinfo, pos); tui->disp[DM_QUEUE].items = tui->queue->items; tui_move_cursor (tui, CURSOR_DOWN_DELETE); } } } else { if (!tui->dinfo->item[item].type && q->items) { playing_item = q->base[q->pos]; queue_delete_dir (tui->queue, tui->dinfo, item); tui->disp[DM_QUEUE].items = tui->queue->items; if (!q->items) { tui->player->cmd = PLAYER_RELOAD; } else { if (playing_item != q->base[q->pos]) tui->player->cmd = PLAYER_RELOAD; } } tui_move_cursor (tui, CURSOR_DOWN); } break; case DM_QUEUE: if (!tui->disp[DM_QUEUE].cursor) { tui->player->cmd = PLAYER_SKIP; } else { queue_delete (tui->queue, tui->dinfo, tui->disp[DM_QUEUE].cursor); tui->disp[DM_QUEUE].items = tui->queue->items; tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } } } /* Clear queue */ static void tui_clear_queue (struct TUI *tui) { queue_clear (tui->queue, tui->dinfo); tui->disp[DM_QUEUE].items = tui->queue->items; tui->player->cmd = PLAYER_RELOAD; if (tui->dm == DM_QUEUE) tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } /* Move item at cursor position to position 0 in queue */ /* static void tui_move_item (struct TUI *tui) { int pos_x, item_x, item0; pos_x = tui->disp[tui->dm].cursor; item0 = tui->queue->base[tui->queue->pos]; if (tui->dm == DM_ITEMS) { item_x = tui->list[pos_x]; if (item_x == item0) return; if (!tui->dinfo->item[item_x].queued) return; pos_x = queue_search_for_item (tui->queue, item_x); } else { if (pos_x) item_x = queue_get_item (tui->queue, pos_x); else return; } queue_delete (tui->queue, tui->dinfo, pos_x); tui->dinfo->item[item0].queued = 0; tui->dinfo->item[item_x].queued = 1; tui->queue->base[tui->queue->pos] = item_x; tui->player->cmd = PLAYER_RELOAD; tui->disp[DM_QUEUE].items = tui->queue->items; if (tui->dm == DM_QUEUE) tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } */ static void tui_move_item (struct TUI *tui) { int del_item, del_pos, cursor; cursor = tui->disp[tui->dm].cursor; if (tui->dm == DM_ITEMS) { del_item = tui->list[cursor]; if (del_item == tui->queue->base[0]) return; del_pos = queue_search_for_item (tui->queue, del_item); if (del_pos == -1) return; } else { if (!cursor) return; del_pos = cursor; del_item = queue_get_item (tui->queue, del_pos); } queue_delete (tui->queue, tui->dinfo, del_pos); queue_prepend (tui->queue, tui->dinfo, del_item); tui->player->cmd = PLAYER_RELOAD; tui->disp[DM_QUEUE].items = tui->queue->items; tui_move_cursor (tui, CURSOR_REDRAW); } /* Randomize items in queue */ static void tui_randomize_queue (struct TUI *tui) { queue_shuffle (tui->queue, tui->dinfo); tui->player->cmd = PLAYER_RELOAD; if (tui->dm == DM_QUEUE) { tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } } /* Main player routine. * Note: case 1 ... 255. Player returned an error code, probably because * an audio device was busy. Restart. */ static void tui_player_control (struct TUI *tui) { int item; struct PLAYER *p = tui->player; switch (p->cmd) { case PLAYER_PAUSE: if (p->paused) { p->paused = 0; player_cont (p); } else { p->paused = 1; player_stop (p); } tui_redraw_status (tui); break; case PLAYER_RELOAD: if (p->pid) { p->kill = p->pid; p->pid = 0; } p->cmd = PLAYER_NO_CMD; return; case PLAYER_SKIP: if (tui->queue->items) { if (p->pid) { p->kill = p->pid; p->pid = 0; } queue_skip (tui->queue, tui->dinfo); tui->disp[DM_QUEUE].items = tui->queue->items; if (tui->dm == DM_QUEUE) tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); p->cmd = PLAYER_NO_CMD; return; } } p->cmd = PLAYER_NO_CMD; if (p->kill) player_kill (p); if (!p->paused) { switch (player_status (p)) { case 0: if (tui->queue->items && p->pid) { queue_skip (tui->queue, tui->dinfo); tui->disp[DM_QUEUE].items = tui->queue->items; if (tui->dm == DM_QUEUE) tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); if (!tui->queue->items) p->pid = 0; } case 1 ... 255: if (tui->queue->items) { item = queue_get_item (tui->queue, 0); p->pid = player_play (tui->cfg, tui->dinfo, item); } } } } static void tui_event_loop (struct TUI *tui) { int exit = 0, key = -1, resize; tui_update_list (tui); do { do { if (tui->lines < TUI_MIN_LINES || tui->cols < TUI_MIN_COLS) { resize = 1; } else { resize = 0; if (tui->redraw) { tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); tui->redraw = 0; } } tui_player_control (tui); key = getch (); } while (key == -1 || resize); switch (key) { case KEY_NPAGE: case ' ': tui_move_cursor (tui, CURSOR_PG_DOWN); break; case 'a': case 'A': tui_add_to_queue (tui); break; case KEY_PPAGE: case 'b': case 'B': tui_move_cursor (tui, CURSOR_PG_UP); break; case 'c': case 'C': tui_clear_queue (tui); break; case 'd': case 'D': tui_delete_from_queue (tui); break; case 'h': case 'H': tui_help_screen (tui); break; case KEY_UP: case 'i': case 'I': tui_move_cursor (tui, CURSOR_UP); break; case KEY_LEFT: case 'j': case 'J': tui_leave_dir (tui); break; case KEY_DOWN: case 'k': case 'K': tui_move_cursor (tui, CURSOR_DOWN); break; case KEY_RIGHT: case 'l': case 'L': tui_enter_dir (tui); break; case 'm': case 'M': tui_move_item (tui); break; case 'p': case 'P': tui->player->cmd = PLAYER_PAUSE; break; case 'q': case 'Q': tui_status_message (tui, "Quit? (y/n)"); exit = tui_yes_no (); if (!exit) { tui_redraw_status (tui); } else { tui->player->kill = tui->player->pid; player_kill (tui->player); } break; case 'r': case 'R': tui_randomize_queue (tui); break; case 's': case 'S': tui->player->cmd = PLAYER_SKIP; break; case 't': case 'T': tui->dm ^= 1; tui_redraw_status (tui); tui_move_cursor (tui, CURSOR_REDRAW); } } while (!exit); } void tui_start (struct TUI *tui) { struct sigaction sa, old_sa; struct PLAYER player = {0}; tui_setup_display (tui); tui->view = -1; tui->player = &player; /* tui_exit_by_signal (tui->player); */ sa.sa_handler = tui_signal_handler; if (sigemptyset (&sa.sa_mask) == -1) { endwin (); err_exit (ERROR); } sa.sa_flags = SA_RESTART; if (sigaction (SIGWINCH, &sa, &old_sa) == -1) { endwin (); err_exit (ERROR); } /* if (sigaction (SIGINT, &sa, &old_sa) == -1) { endwin (); err_exit (ERROR); } */ tui_event_loop (tui); endwin (); }