1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "screen.h"
21 #include "screen_list.h"
22 #include "screen_utils.h"
23 #include "config.h"
24 #include "i18n.h"
25 #include "charset.h"
26 #include "mpdclient.h"
27 #include "utils.h"
28 #include "options.h"
29 #include "colors.h"
30 #include "strfsong.h"
31 #include "player_command.h"
33 #ifndef NCMPC_MINI
34 #include "hscroll.h"
35 #endif
37 #include <mpd/client.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <stdarg.h>
42 #include <string.h>
43 #include <time.h>
44 #include <locale.h>
46 #ifndef NCMPC_MINI
47 /** welcome message time [s] */
48 static const GTime SCREEN_WELCOME_TIME = 10;
49 #endif
51 /* minimum window size */
52 static const int SCREEN_MIN_COLS = 14;
53 static const int SCREEN_MIN_ROWS = 5;
55 /* screens */
57 #ifndef NCMPC_MINI
58 static gboolean welcome = TRUE;
59 #endif
61 struct screen screen;
62 static const struct screen_functions *mode_fn = &screen_playlist;
63 static const struct screen_functions *mode_fn_prev = &screen_playlist;
65 gboolean
66 screen_is_visible(const struct screen_functions *sf)
67 {
68 return sf == mode_fn;
69 }
71 void
72 screen_switch(const struct screen_functions *sf, struct mpdclient *c)
73 {
74 assert(sf != NULL);
76 if (sf == mode_fn)
77 return;
79 mode_fn_prev = mode_fn;
81 /* close the old mode */
82 if (mode_fn->close != NULL)
83 mode_fn->close();
85 /* get functions for the new mode */
86 mode_fn = sf;
88 /* open the new mode */
89 if (mode_fn->open != NULL)
90 mode_fn->open(c);
92 screen_paint(c);
93 }
95 void
96 screen_swap(struct mpdclient *c, const struct mpd_song *song)
97 {
98 if (song != NULL)
99 {
100 if (false)
101 { /* just a hack to make the ifdefs less ugly */ }
102 #ifdef ENABLE_SONG_SCREEN
103 if (mode_fn_prev == &screen_song)
104 screen_song_switch(c, song);
105 #endif
106 #ifdef ENABLE_LYRICS_SCREEN
107 else if (mode_fn_prev == &screen_lyrics)
108 screen_lyrics_switch(c, song, true);
109 #endif
110 else
111 screen_switch(mode_fn_prev, c);
112 }
113 else
114 screen_switch(mode_fn_prev, c);
115 }
117 static int
118 find_configured_screen(const char *name)
119 {
120 unsigned i;
122 for (i = 0; options.screen_list[i] != NULL; ++i)
123 if (strcmp(options.screen_list[i], name) == 0)
124 return i;
126 return -1;
127 }
129 static void
130 screen_next_mode(struct mpdclient *c, int offset)
131 {
132 int max = g_strv_length(options.screen_list);
133 int current, next;
134 const struct screen_functions *sf;
136 /* find current screen */
137 current = find_configured_screen(screen_get_name(mode_fn));
138 next = current + offset;
139 if (next<0)
140 next = max-1;
141 else if (next>=max)
142 next = 0;
144 sf = screen_lookup_name(options.screen_list[next]);
145 if (sf != NULL)
146 screen_switch(sf, c);
147 }
149 static inline int
150 volume_length(int volume)
151 {
152 if (volume == 100)
153 return 3;
154 if (volume >= 10 && volume < 100)
155 return 2;
156 if (volume >= 0 && volume < 10)
157 return 1;
158 return -1;
159 }
161 static void
162 paint_top_window(const char *header, const struct mpdclient *c)
163 {
164 title_bar_paint(&screen.title_bar, header, c->status);
165 }
167 static void
168 paint_progress_window(struct mpdclient *c)
169 {
170 unsigned elapsed, duration;
172 if (c->song != NULL && seek_id == (int)mpd_song_get_id(c->song))
173 elapsed = seek_target_time;
174 else if (c->status != NULL)
175 elapsed = mpd_status_get_elapsed_time(c->status);
176 else
177 elapsed = 0;
179 duration = c->status != NULL &&
180 !IS_STOPPED(mpd_status_get_state(c->status))
181 ? mpd_status_get_total_time(c->status)
182 : 0;
184 if (progress_bar_set(&screen.progress_bar, elapsed, duration))
185 progress_bar_paint(&screen.progress_bar);
186 }
188 static void
189 paint_status_window(struct mpdclient *c)
190 {
191 WINDOW *w = screen.status_window.w;
192 const struct mpd_status *status = c->status;
193 enum mpd_state state;
194 const struct mpd_song *song = c->song;
195 int elapsedTime = 0;
196 #ifdef NCMPC_MINI
197 static char bitrate[1];
198 #else
199 char bitrate[16];
200 #endif
201 const char *str = NULL;
202 int x = 0;
204 if( time(NULL) - screen.status_timestamp <= options.status_message_time )
205 return;
207 wmove(w, 0, 0);
208 wclrtoeol(w);
209 colors_use(w, COLOR_STATUS_BOLD);
211 state = status == NULL ? MPD_STATE_UNKNOWN
212 : mpd_status_get_state(status);
214 switch (state) {
215 case MPD_STATE_PLAY:
216 str = _("Playing:");
217 break;
218 case MPD_STATE_PAUSE:
219 str = _("[Paused]");
220 break;
221 case MPD_STATE_STOP:
222 default:
223 break;
224 }
226 if (str) {
227 waddstr(w, str);
228 x += utf8_width(str) + 1;
229 }
231 /* create time string */
232 memset(screen.buf, 0, screen.buf_size);
233 if (IS_PLAYING(state) || IS_PAUSED(state)) {
234 int total_time = mpd_status_get_total_time(status);
235 if (total_time > 0) {
236 /*checks the conf to see whether to display elapsed or remaining time */
237 if(!strcmp(options.timedisplay_type,"elapsed"))
238 elapsedTime = mpd_status_get_elapsed_time(c->status);
239 else if(!strcmp(options.timedisplay_type,"remaining"))
240 elapsedTime = total_time -
241 mpd_status_get_elapsed_time(c->status);
243 if (c->song != NULL &&
244 seek_id == (int)mpd_song_get_id(c->song))
245 elapsedTime = seek_target_time;
247 /* display bitrate if visible-bitrate is true */
248 #ifndef NCMPC_MINI
249 if (options.visible_bitrate) {
250 g_snprintf(bitrate, 16,
251 " [%d kbps]",
252 mpd_status_get_kbit_rate(status));
253 } else {
254 bitrate[0] = '\0';
255 }
256 #endif
258 /*write out the time, using hours if time over 60 minutes*/
259 if (total_time > 3600) {
260 g_snprintf(screen.buf, screen.buf_size,
261 "%s [%i:%02i:%02i/%i:%02i:%02i]",
262 bitrate, elapsedTime/3600, (elapsedTime%3600)/60, elapsedTime%60,
263 total_time / 3600,
264 (total_time % 3600)/60,
265 total_time % 60);
266 } else {
267 g_snprintf(screen.buf, screen.buf_size,
268 "%s [%i:%02i/%i:%02i]",
269 bitrate, elapsedTime/60, elapsedTime%60,
270 total_time / 60, total_time % 60);
271 }
272 #ifndef NCMPC_MINI
273 } else {
274 g_snprintf(screen.buf, screen.buf_size,
275 " [%d kbps]",
276 mpd_status_get_kbit_rate(status));
277 #endif
278 }
279 #ifndef NCMPC_MINI
280 } else {
281 if (options.display_time) {
282 time_t timep;
284 time(&timep);
285 strftime(screen.buf, screen.buf_size, "%X ",localtime(&timep));
286 }
287 #endif
288 }
290 /* display song */
291 if (IS_PLAYING(state) || IS_PAUSED(state)) {
292 char songname[MAX_SONGNAME_LENGTH];
293 #ifndef NCMPC_MINI
294 int width = COLS - x - utf8_width(screen.buf);
295 #endif
297 if (song)
298 strfsong(songname, MAX_SONGNAME_LENGTH,
299 options.status_format, song);
300 else
301 songname[0] = '\0';
303 colors_use(w, COLOR_STATUS);
304 /* scroll if the song name is to long */
305 #ifndef NCMPC_MINI
306 if (options.scroll && utf8_width(songname) > (unsigned)width) {
307 static scroll_state_t st = { 0, 0 };
308 char *tmp = strscroll(songname, options.scroll_sep, width, &st);
310 g_strlcpy(songname, tmp, MAX_SONGNAME_LENGTH);
311 g_free(tmp);
312 }
313 #endif
314 //mvwaddnstr(w, 0, x, songname, width);
315 mvwaddstr(w, 0, x, songname);
316 }
318 /* display time string */
319 if (screen.buf[0]) {
320 x = screen.status_window.cols - strlen(screen.buf);
321 colors_use(w, COLOR_STATUS_TIME);
322 mvwaddstr(w, 0, x, screen.buf);
323 }
325 wnoutrefresh(w);
326 }
328 void
329 screen_exit(void)
330 {
331 if (mode_fn->close != NULL)
332 mode_fn->close();
334 screen_list_exit();
336 string_list_free(screen.find_history);
337 g_free(screen.buf);
338 g_free(screen.findbuf);
340 title_bar_deinit(&screen.title_bar);
341 delwin(screen.main_window.w);
342 progress_bar_deinit(&screen.progress_bar);
343 delwin(screen.status_window.w);
344 }
346 void
347 screen_resize(struct mpdclient *c)
348 {
349 if (COLS<SCREEN_MIN_COLS || LINES<SCREEN_MIN_ROWS) {
350 screen_exit();
351 fprintf(stderr, "%s", _("Error: Screen too small"));
352 exit(EXIT_FAILURE);
353 }
355 resizeterm(LINES, COLS);
357 screen.cols = COLS;
358 screen.rows = LINES;
360 title_bar_resize(&screen.title_bar, screen.cols);
362 /* main window */
363 screen.main_window.cols = screen.cols;
364 screen.main_window.rows = screen.rows-4;
365 wresize(screen.main_window.w, screen.main_window.rows, screen.cols);
366 wclear(screen.main_window.w);
368 /* progress window */
369 progress_bar_resize(&screen.progress_bar, screen.cols,
370 screen.rows - 2, 0);
371 progress_bar_paint(&screen.progress_bar);
373 /* status window */
374 screen.status_window.cols = screen.cols;
375 wresize(screen.status_window.w, 1, screen.cols);
376 mvwin(screen.status_window.w, screen.rows-1, 0);
378 screen.buf_size = screen.cols;
379 g_free(screen.buf);
380 screen.buf = g_malloc(screen.cols);
382 /* resize all screens */
383 screen_list_resize(screen.main_window.cols, screen.main_window.rows);
385 /* ? - without this the cursor becomes visible with aterm & Eterm */
386 curs_set(1);
387 curs_set(0);
389 screen_paint(c);
390 }
392 void
393 screen_status_message(const char *msg)
394 {
395 WINDOW *w = screen.status_window.w;
397 wmove(w, 0, 0);
398 wclrtoeol(w);
399 colors_use(w, COLOR_STATUS_ALERT);
400 waddstr(w, msg);
401 wnoutrefresh(w);
402 screen.status_timestamp = time(NULL);
403 }
405 void
406 screen_status_printf(const char *format, ...)
407 {
408 char *msg;
409 va_list ap;
411 va_start(ap,format);
412 msg = g_strdup_vprintf(format,ap);
413 va_end(ap);
414 screen_status_message(msg);
415 g_free(msg);
416 }
418 void
419 screen_init(struct mpdclient *c)
420 {
421 if (COLS < SCREEN_MIN_COLS || LINES < SCREEN_MIN_ROWS) {
422 fprintf(stderr, "%s\n", _("Error: Screen too small"));
423 exit(EXIT_FAILURE);
424 }
426 screen.cols = COLS;
427 screen.rows = LINES;
429 screen.buf = g_malloc(screen.cols);
430 screen.buf_size = screen.cols;
431 screen.findbuf = NULL;
432 screen.start_timestamp = time(NULL);
434 /* create top window */
435 title_bar_init(&screen.title_bar, screen.cols, 0, 0);
437 /* create main window */
438 window_init(&screen.main_window, screen.rows - 4, screen.cols, 2, 0);
440 // leaveok(screen.main_window.w, TRUE); temporary disabled
441 keypad(screen.main_window.w, TRUE);
443 /* create progress window */
444 progress_bar_init(&screen.progress_bar, screen.cols,
445 screen.rows - 2, 0);
446 progress_bar_paint(&screen.progress_bar);
448 /* create status window */
449 window_init(&screen.status_window, 1, screen.cols,
450 screen.rows - 1, 0);
452 leaveok(screen.status_window.w, FALSE);
453 keypad(screen.status_window.w, TRUE);
455 #ifdef ENABLE_COLORS
456 if (options.enable_colors) {
457 /* set background attributes */
458 wbkgd(stdscr, COLOR_PAIR(COLOR_LIST));
459 wbkgd(screen.main_window.w, COLOR_PAIR(COLOR_LIST));
460 wbkgd(screen.title_bar.window.w, COLOR_PAIR(COLOR_TITLE));
461 wbkgd(screen.progress_bar.window.w,
462 COLOR_PAIR(COLOR_PROGRESSBAR));
463 wbkgd(screen.status_window.w, COLOR_PAIR(COLOR_STATUS));
464 colors_use(screen.progress_bar.window.w, COLOR_PROGRESSBAR);
465 }
466 #endif
468 doupdate();
470 /* initialize screens */
471 screen_list_init(screen.main_window.w,
472 screen.main_window.cols, screen.main_window.rows);
474 if (mode_fn->open != NULL)
475 mode_fn->open(c);
476 }
478 void
479 screen_paint(struct mpdclient *c)
480 {
481 const char *title = NULL;
483 if (mode_fn->get_title != NULL)
484 title = mode_fn->get_title(screen.buf, screen.buf_size);
486 /* paint the title/header window */
487 if( title )
488 paint_top_window(title, c);
489 else
490 paint_top_window("", c);
492 /* paint the bottom window */
494 paint_progress_window(c);
495 paint_status_window(c);
497 /* paint the main window */
499 wclear(screen.main_window.w);
500 if (mode_fn->paint != NULL)
501 mode_fn->paint();
503 /* move the cursor to the origin */
505 if (!options.hardware_cursor)
506 wmove(screen.main_window.w, 0, 0);
508 wnoutrefresh(screen.main_window.w);
510 /* tell curses to update */
511 doupdate();
512 }
514 void
515 screen_update(struct mpdclient *c)
516 {
517 #ifndef NCMPC_MINI
518 static bool initialized = false;
519 static bool repeat;
520 static bool random_enabled;
521 static bool single;
522 static bool consume;
523 static unsigned crossfade;
524 static unsigned dbupdate;
526 /* print a message if mpd status has changed */
527 if (c->status != NULL) {
528 if (!initialized) {
529 repeat = mpd_status_get_repeat(c->status);
530 random_enabled = mpd_status_get_random(c->status);
531 single = mpd_status_get_single(c->status);
532 consume = mpd_status_get_consume(c->status);
533 crossfade = mpd_status_get_crossfade(c->status);
534 dbupdate = mpd_status_get_update_id(c->status);
535 initialized = true;
536 }
538 if (repeat != mpd_status_get_repeat(c->status))
539 screen_status_printf(mpd_status_get_repeat(c->status) ?
540 _("Repeat mode is on") :
541 _("Repeat mode is off"));
543 if (random_enabled != mpd_status_get_random(c->status))
544 screen_status_printf(mpd_status_get_random(c->status) ?
545 _("Random mode is on") :
546 _("Random mode is off"));
548 if (single != mpd_status_get_single(c->status))
549 screen_status_printf(mpd_status_get_single(c->status) ?
550 /* "single" mode means
551 that MPD will
552 automatically stop
553 after playing one
554 single song */
555 _("Single mode is on") :
556 _("Single mode is off"));
558 if (consume != mpd_status_get_consume(c->status))
559 screen_status_printf(mpd_status_get_consume(c->status) ?
560 /* "consume" mode means
561 that MPD removes each
562 song which has
563 finished playing */
564 _("Consume mode is on") :
565 _("Consume mode is off"));
567 if (crossfade != mpd_status_get_crossfade(c->status))
568 screen_status_printf(_("Crossfade %d seconds"),
569 mpd_status_get_crossfade(c->status));
571 if (dbupdate != 0 &&
572 dbupdate != mpd_status_get_update_id(c->status)) {
573 screen_status_printf(_("Database updated"));
574 }
576 repeat = mpd_status_get_repeat(c->status);
577 random_enabled = mpd_status_get_random(c->status);
578 single = mpd_status_get_single(c->status);
579 consume = mpd_status_get_consume(c->status);
580 crossfade = mpd_status_get_crossfade(c->status);
581 dbupdate = mpd_status_get_update_id(c->status);
582 }
584 /* update title/header window */
585 if (welcome && options.welcome_screen_list &&
586 time(NULL)-screen.start_timestamp <= SCREEN_WELCOME_TIME)
587 paint_top_window("", c);
588 else
589 #endif
590 if (mode_fn->get_title != NULL) {
591 paint_top_window(mode_fn->get_title(screen.buf,screen.buf_size), c);
592 #ifndef NCMPC_MINI
593 welcome = FALSE;
594 #endif
595 } else
596 paint_top_window("", c);
598 /* update progress window */
599 paint_progress_window(c);
601 /* update status window */
602 paint_status_window(c);
604 /* update the main window */
605 if (mode_fn->update != NULL)
606 mode_fn->update(c);
608 /* move the cursor to the origin */
610 if (!options.hardware_cursor)
611 wmove(screen.main_window.w, 0, 0);
613 wnoutrefresh(screen.main_window.w);
615 /* tell curses to update */
616 doupdate();
617 }
619 #ifdef HAVE_GETMOUSE
620 int
621 screen_get_mouse_event(struct mpdclient *c, unsigned long *bstate, int *row)
622 {
623 MEVENT event;
625 /* retrieve the mouse event from ncurses */
626 getmouse(&event);
627 /* calculate the selected row in the list window */
628 *row = event.y - screen.title_bar.window.rows;
629 /* copy button state bits */
630 *bstate = event.bstate;
631 /* if button 2 was pressed switch screen */
632 if (event.bstate & BUTTON2_CLICKED) {
633 screen_cmd(c, CMD_SCREEN_NEXT);
634 return 1;
635 }
637 return 0;
638 }
639 #endif
641 void
642 screen_cmd(struct mpdclient *c, command_t cmd)
643 {
644 #ifndef NCMPC_MINI
645 welcome = FALSE;
646 #endif
648 if (mode_fn->cmd != NULL && mode_fn->cmd(c, cmd))
649 return;
651 if (handle_player_command(c, cmd))
652 return;
654 switch(cmd) {
655 case CMD_TOGGLE_FIND_WRAP:
656 options.find_wrap = !options.find_wrap;
657 screen_status_printf(options.find_wrap ?
658 _("Find mode: Wrapped") :
659 _("Find mode: Normal"));
660 break;
661 case CMD_TOGGLE_AUTOCENTER:
662 options.auto_center = !options.auto_center;
663 screen_status_printf(options.auto_center ?
664 _("Auto center mode: On") :
665 _("Auto center mode: Off"));
666 break;
667 case CMD_SCREEN_UPDATE:
668 screen_paint(c);
669 break;
670 case CMD_SCREEN_PREVIOUS:
671 screen_next_mode(c, -1);
672 break;
673 case CMD_SCREEN_NEXT:
674 screen_next_mode(c, 1);
675 break;
676 case CMD_SCREEN_PLAY:
677 screen_switch(&screen_playlist, c);
678 break;
679 case CMD_SCREEN_FILE:
680 screen_switch(&screen_browse, c);
681 break;
682 #ifdef ENABLE_HELP_SCREEN
683 case CMD_SCREEN_HELP:
684 screen_switch(&screen_help, c);
685 break;
686 #endif
687 #ifdef ENABLE_SEARCH_SCREEN
688 case CMD_SCREEN_SEARCH:
689 screen_switch(&screen_search, c);
690 break;
691 #endif
692 #ifdef ENABLE_ARTIST_SCREEN
693 case CMD_SCREEN_ARTIST:
694 screen_switch(&screen_artist, c);
695 break;
696 #endif
697 #ifdef ENABLE_SONG_SCREEN
698 case CMD_SCREEN_SONG:
699 screen_switch(&screen_song, c);
700 break;
701 #endif
702 #ifdef ENABLE_KEYDEF_SCREEN
703 case CMD_SCREEN_KEYDEF:
704 screen_switch(&screen_keydef, c);
705 break;
706 #endif
707 #ifdef ENABLE_LYRICS_SCREEN
708 case CMD_SCREEN_LYRICS:
709 screen_switch(&screen_lyrics, c);
710 break;
711 #endif
712 #ifdef ENABLE_OUTPUTS_SCREEN
713 case CMD_SCREEN_OUTPUTS:
714 screen_switch(&screen_outputs, c);
715 break;
716 case CMD_SCREEN_SWAP:
717 screen_swap(c, NULL);
718 break;
719 #endif
721 default:
722 break;
723 }
724 }