1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <stdarg.h>
4 #include <string.h>
5 #include <time.h>
6 #include <glib.h>
7 #include <ncurses.h>
9 #include "libmpdclient.h"
10 #include "mpc.h"
11 #include "command.h"
12 #include "options.h"
13 #include "screen.h"
14 #include "screen_play.h"
15 #include "screen_file.h"
16 #include "screen_help.h"
17 #include "screen_search.h"
18 #include "screen_utils.h"
20 #define STATUS_MESSAGE_TIMEOUT 3
21 #define STATUS_LINE_MAX_SIZE 512
23 static screen_t *screen = NULL;
25 static void
26 switch_screen_mode(screen_mode_t new_mode, mpd_client_t *c)
27 {
28 if( new_mode == screen->mode )
29 return;
31 switch(screen->mode)
32 {
33 case SCREEN_PLAY_WINDOW:
34 play_close(screen, c);
35 break;
36 case SCREEN_FILE_WINDOW:
37 file_close(screen, c);
38 break;
39 case SCREEN_SEARCH_WINDOW:
40 search_close(screen, c);
41 break;
42 case SCREEN_HELP_WINDOW:
43 help_close(screen, c);
44 break;
45 }
47 screen->mode = new_mode;
48 screen->painted = 0;
50 switch(screen->mode)
51 {
52 case SCREEN_PLAY_WINDOW:
53 play_open(screen, c);
54 break;
55 case SCREEN_FILE_WINDOW:
56 file_open(screen, c);
57 break;
58 case SCREEN_SEARCH_WINDOW:
59 search_open(screen, c);
60 break;
61 case SCREEN_HELP_WINDOW:
62 help_open(screen, c);
63 break;
64 }
65 }
67 static void
68 paint_top_window(char *header, int volume, int clear)
69 {
70 static int prev_volume = -1;
71 WINDOW *w = screen->top_window.w;
73 if(clear)
74 {
75 wclear(w);
76 }
78 if(prev_volume!=volume || clear)
79 {
80 char buf[12];
82 wattron(w, A_BOLD);
83 mvwaddstr(w, 0, 0, header);
84 wattroff(w, A_BOLD);
85 if( volume==MPD_STATUS_NO_VOLUME )
86 {
87 snprintf(buf, 12, "Volume n/a ");
88 }
89 else
90 {
91 snprintf(buf, 12, "Volume %3d%%", volume);
92 }
93 mvwaddstr(w, 0, screen->top_window.cols-12, buf);
95 if( options.enable_colors )
96 wattron(w, LINE_COLORS);
97 mvwhline(w, 1, 0, ACS_HLINE, screen->top_window.cols);
98 if( options.enable_colors )
99 wattroff(w, LINE_COLORS);
101 wnoutrefresh(w);
102 }
103 }
105 static void
106 paint_progress_window(mpd_client_t *c)
107 {
108 double p;
109 int width;
111 if( c->status==NULL || !IS_PLAYING(c->status->state) )
112 {
113 mvwhline(screen->progress_window.w, 0, 0, ACS_HLINE,
114 screen->progress_window.cols);
115 wnoutrefresh(screen->progress_window.w);
116 return;
117 }
119 p = ((double) c->status->elapsedTime) / ((double) c->status->totalTime);
121 width = (int) (p * (double) screen->progress_window.cols);
122 mvwhline(screen->progress_window.w,
123 0, 0,
124 ACS_HLINE,
125 screen->progress_window.cols);
126 whline(screen->progress_window.w, '=', width-1);
127 mvwaddch(screen->progress_window.w, 0, width-1, 'O');
128 wnoutrefresh(screen->progress_window.w);
129 }
131 static void
132 paint_status_window(mpd_client_t *c)
133 {
134 WINDOW *w = screen->status_window.w;
135 mpd_Status *status = c->status;
136 mpd_Song *song = c->song;
137 int x = 0;
139 if( time(NULL) - screen->status_timestamp <= STATUS_MESSAGE_TIMEOUT )
140 return;
142 wmove(w, 0, 0);
143 wclrtoeol(w);
145 switch(status->state)
146 {
147 case MPD_STATUS_STATE_STOP:
148 wattron(w, A_BOLD);
149 waddstr(w, "Stopped! ");
150 wattroff(w, A_BOLD);
151 break;
152 case MPD_STATUS_STATE_PLAY:
153 wattron(w, A_BOLD);
154 waddstr(w, "Playing:");
155 wattroff(w, A_BOLD);
156 break;
157 case MPD_STATUS_STATE_PAUSE:
158 wattron(w, A_BOLD);
159 waddstr(w, "[Paused]");
160 wattroff(w, A_BOLD);
161 break;
162 default:
163 my_waddstr(w,
164 "Warning: Music Player Daemon in unknown state!",
165 ALERT_COLORS);
166 break;
167 }
168 x += 10;
170 if( (IS_PLAYING(status->state) || IS_PAUSED(status->state)) && song )
171 {
172 mvwaddstr(w, 0, x, mpc_get_song_name(song));
173 }
176 /* time */
177 if( IS_PLAYING(status->state) || IS_PAUSED(status->state) )
178 {
179 x = screen->status_window.cols - strlen(screen->buf);
181 snprintf(screen->buf, screen->buf_size,
182 " [%i:%02i/%i:%02i] ",
183 status->elapsedTime/60, status->elapsedTime%60,
184 status->totalTime/60, status->totalTime%60 );
185 mvwaddstr(w, 0, x, screen->buf);
187 }
188 #if 1
189 else if( c->status->state == MPD_STATUS_STATE_STOP )
190 {
191 time_t timep;
192 x = screen->status_window.cols - strlen(screen->buf);
194 time(&timep);
195 //strftime(screen->buf, screen->buf_size, "%X ", localtime(&timep));
196 strftime(screen->buf, screen->buf_size, "%R ", localtime(&timep));
197 mvwaddstr(w, 0, x, screen->buf);
198 }
199 #endif
202 wnoutrefresh(w);
203 }
207 int
208 screen_exit(void)
209 {
210 endwin();
211 if( screen )
212 {
213 screen->playlist = list_window_free(screen->playlist);
214 screen->filelist = list_window_free(screen->filelist);
215 screen->helplist = list_window_free(screen->helplist);
216 free(screen->buf);
217 free(screen->findbuf);
218 free(screen);
219 screen = NULL;
220 }
221 return 0;
222 }
224 void
225 screen_resized(int sig)
226 {
227 screen_exit();
228 if( COLS<SCREEN_MIN_COLS || LINES<SCREEN_MIN_ROWS )
229 {
230 fprintf(stderr, "Error: Screen to small!\n");
231 exit(EXIT_FAILURE);
232 }
233 screen_init();
234 }
236 void
237 screen_status_message(char *msg)
238 {
239 WINDOW *w = screen->status_window.w;
241 wmove(w, 0, 0);
242 wclrtoeol(w);
243 wattron(w, A_BOLD);
244 my_waddstr(w, msg, ALERT_COLORS);
245 wattroff(w, A_BOLD);
246 wnoutrefresh(w);
247 screen->status_timestamp = time(NULL);
248 }
250 void
251 screen_status_printf(char *format, ...)
252 {
253 char buffer[STATUS_LINE_MAX_SIZE];
254 va_list ap;
256 va_start(ap,format);
257 vsnprintf(buffer,sizeof(buffer),format,ap);
258 va_end(ap);
259 screen_status_message(buffer);
260 }
262 int
263 screen_init(void)
264 {
265 /* initialize the curses library */
266 initscr();
267 if( has_colors() )
268 {
269 start_color();
270 if( options.enable_colors )
271 {
272 init_pair(1, options.title_color, options.bg_color);
273 init_pair(2, options.line_color, options.bg_color);
274 init_pair(3, options.list_color, options.bg_color);
275 init_pair(4, options.progress_color, options.bg_color);
276 init_pair(5, options.status_color, options.bg_color);
277 init_pair(6, options.alert_color, options.bg_color);
278 }
279 else
280 use_default_colors();
281 }
282 else if( options.enable_colors )
283 {
284 fprintf(stderr, "Terminal lacks color capabilities.\n");
285 options.enable_colors = 0;
286 }
288 /* tell curses not to do NL->CR/NL on output */
289 nonl();
290 /* take input chars one at a time, no wait for \n */
291 cbreak();
292 /* don't echo input */
293 noecho();
294 /* set cursor invisible */
295 curs_set(0);
296 /* return from getch() without blocking */
297 // nodelay(stdscr, TRUE);
298 keypad(stdscr, TRUE);
299 timeout(SCREEN_TIMEOUT);
301 if( COLS<SCREEN_MIN_COLS || LINES<SCREEN_MIN_ROWS )
302 {
303 fprintf(stderr, "Error: Screen to small!\n");
304 exit(EXIT_FAILURE);
305 }
307 screen = malloc(sizeof(screen_t));
308 memset(screen, 0, sizeof(screen_t));
309 screen->mode = SCREEN_PLAY_WINDOW;
310 screen->cols = COLS;
311 screen->rows = LINES;
312 screen->buf = malloc(screen->cols);
313 screen->buf_size = screen->cols;
314 screen->findbuf = NULL;
315 screen->painted = 0;
317 /* create top window */
318 screen->top_window.rows = 2;
319 screen->top_window.cols = screen->cols;
320 screen->top_window.w = newwin(screen->top_window.rows,
321 screen->top_window.cols,
322 0, 0);
323 leaveok(screen->top_window.w, TRUE);
324 keypad(screen->top_window.w, TRUE);
326 /* create main window */
327 screen->main_window.rows = screen->rows-4;
328 screen->main_window.cols = screen->cols;
329 screen->main_window.w = newwin(screen->main_window.rows,
330 screen->main_window.cols,
331 2,
332 0);
333 screen->playlist = list_window_init( screen->main_window.w,
334 screen->main_window.cols,
335 screen->main_window.rows );
336 screen->filelist = list_window_init( screen->main_window.w,
337 screen->main_window.cols,
338 screen->main_window.rows );
339 screen->helplist = list_window_init( screen->main_window.w,
340 screen->main_window.cols,
341 screen->main_window.rows );
343 leaveok(screen->main_window.w, TRUE);
344 keypad(screen->main_window.w, TRUE);
346 /* create progress window */
347 screen->progress_window.rows = 1;
348 screen->progress_window.cols = screen->cols;
349 screen->progress_window.w = newwin(screen->progress_window.rows,
350 screen->progress_window.cols,
351 screen->rows-2,
352 0);
353 leaveok(screen->progress_window.w, TRUE);
355 /* create status window */
356 screen->status_window.rows = 1;
357 screen->status_window.cols = screen->cols;
358 screen->status_window.w = newwin(screen->status_window.rows,
359 screen->status_window.cols,
360 screen->rows-1,
361 0);
363 leaveok(screen->status_window.w, FALSE);
364 keypad(screen->status_window.w, TRUE);
366 if( options.enable_colors )
367 {
368 /* set background attributes */
369 wbkgd(screen->main_window.w, LIST_COLORS);
370 wbkgd(screen->top_window.w, TITLE_COLORS);
371 wbkgd(screen->progress_window.w, PROGRESS_COLORS);
372 wbkgd(screen->status_window.w, STATUS_COLORS);
373 }
375 return 0;
376 }
378 void
379 screen_paint(mpd_client_t *c)
380 {
381 switch(screen->mode)
382 {
383 case SCREEN_PLAY_WINDOW:
384 paint_top_window(TOP_HEADER_PLAY, c->status->volume, 1);
385 play_paint(screen, c);
386 break;
387 case SCREEN_FILE_WINDOW:
388 paint_top_window(file_get_header(c), c->status->volume, 1);
389 file_paint(screen, c);
390 break;
391 case SCREEN_SEARCH_WINDOW:
392 paint_top_window(TOP_HEADER_SEARCH, c->status->volume, 1);
393 search_paint(screen, c);
394 break;
395 case SCREEN_HELP_WINDOW:
396 paint_top_window(TOP_HEADER_PLAY, c->status->volume, 1);
397 help_paint(screen, c);
398 break;
399 }
401 paint_progress_window(c);
402 paint_status_window(c);
403 screen->painted = 1;
404 doupdate();
405 }
407 void
408 screen_update(mpd_client_t *c)
409 {
410 if( !screen->painted )
411 return screen_paint(c);
413 switch(screen->mode)
414 {
415 case SCREEN_PLAY_WINDOW:
416 paint_top_window(TOP_HEADER_PLAY, c->status->volume, 0);
417 play_update(screen, c);
418 break;
419 case SCREEN_FILE_WINDOW:
420 paint_top_window(file_get_header(c), c->status->volume, 0);
421 file_update(screen, c);
422 break;
423 case SCREEN_SEARCH_WINDOW:
424 paint_top_window(TOP_HEADER_SEARCH, c->status->volume, 0);
425 search_update(screen, c);
426 break;
427 case SCREEN_HELP_WINDOW:
428 paint_top_window(TOP_HEADER_HELP, c->status->volume, 0);
429 help_update(screen, c);
430 break;
431 }
432 paint_progress_window(c);
433 paint_status_window(c);
434 doupdate();
435 }
437 void
438 screen_cmd(mpd_client_t *c, command_t cmd)
439 {
440 int n;
441 screen_mode_t new_mode = screen->mode;
443 switch(screen->mode)
444 {
445 case SCREEN_PLAY_WINDOW:
446 if( play_cmd(screen, c, cmd) )
447 return;
448 break;
449 case SCREEN_FILE_WINDOW:
450 if( file_cmd(screen, c, cmd) )
451 return;
452 break;
453 case SCREEN_SEARCH_WINDOW:
454 if( search_cmd(screen, c, cmd) )
455 return;
456 break;
457 case SCREEN_HELP_WINDOW:
458 if( help_cmd(screen, c, cmd) )
459 return;
460 break;
461 }
463 switch(cmd)
464 {
465 case CMD_PLAY:
466 mpd_sendPlayCommand(c->connection, screen->playlist->selected);
467 mpd_finishCommand(c->connection);
468 break;
469 case CMD_PAUSE:
470 mpd_sendPauseCommand(c->connection);
471 mpd_finishCommand(c->connection);
472 break;
473 case CMD_STOP:
474 mpd_sendStopCommand(c->connection);
475 mpd_finishCommand(c->connection);
476 break;
477 case CMD_TRACK_NEXT:
478 if( IS_PLAYING(c->status->state) )
479 {
480 mpd_sendNextCommand(c->connection);
481 mpd_finishCommand(c->connection);
482 }
483 break;
484 case CMD_TRACK_PREVIOUS:
485 if( IS_PLAYING(c->status->state) )
486 {
487 mpd_sendPrevCommand(c->connection);
488 mpd_finishCommand(c->connection);
489 }
490 break;
491 case CMD_SHUFFLE:
492 mpd_sendShuffleCommand(c->connection);
493 mpd_finishCommand(c->connection);
494 screen_status_message("Shuffled playlist!");
495 break;
496 case CMD_CLEAR:
497 mpd_sendClearCommand(c->connection);
498 mpd_finishCommand(c->connection);
499 file_clear_highlights(c);
500 screen_status_message("Cleared playlist!");
501 break;
502 case CMD_REPEAT:
503 n = !c->status->repeat;
504 mpd_sendRepeatCommand(c->connection, n);
505 mpd_finishCommand(c->connection);
506 break;
507 case CMD_RANDOM:
508 n = !c->status->random;
509 mpd_sendRandomCommand(c->connection, n);
510 mpd_finishCommand(c->connection);
511 break;
512 case CMD_VOLUME_UP:
513 if( c->status->volume!=MPD_STATUS_NO_VOLUME && c->status->volume<100 )
514 {
515 c->status->volume=c->status->volume+1;
516 mpd_sendSetvolCommand(c->connection, c->status->volume );
517 mpd_finishCommand(c->connection);
518 screen_status_printf("Volume %d%%", c->status->volume);
519 }
520 break;
521 case CMD_VOLUME_DOWN:
522 if( c->status->volume!=MPD_STATUS_NO_VOLUME && c->status->volume>0 )
523 {
524 c->status->volume=c->status->volume-1;
525 mpd_sendSetvolCommand(c->connection, c->status->volume );
526 mpd_finishCommand(c->connection);
527 screen_status_printf("Volume %d%%", c->status->volume);
528 }
529 break;
530 case CMD_TOGGLE_FIND_WRAP:
531 options.find_wrap = !options.find_wrap;
532 screen_status_printf("Find mode: %s",
533 options.find_wrap ? "Wrapped" : "Normal");
534 break;
535 case CMD_SCREEN_PREVIOUS:
536 if( screen->mode > SCREEN_PLAY_WINDOW )
537 new_mode = screen->mode - 1;
538 else
539 new_mode = SCREEN_HELP_WINDOW-1;
540 switch_screen_mode(new_mode, c);
541 break;
542 case CMD_SCREEN_NEXT:
543 new_mode = screen->mode + 1;
544 if( new_mode >= SCREEN_HELP_WINDOW )
545 new_mode = SCREEN_PLAY_WINDOW;
546 switch_screen_mode(new_mode, c);
547 break;
548 case CMD_SCREEN_PLAY:
549 switch_screen_mode(SCREEN_PLAY_WINDOW, c);
550 break;
551 case CMD_SCREEN_FILE:
552 switch_screen_mode(SCREEN_FILE_WINDOW, c);
553 break;
554 case CMD_SCREEN_SEARCH:
555 switch_screen_mode(SCREEN_SEARCH_WINDOW, c);
556 break;
557 case CMD_SCREEN_HELP:
558 switch_screen_mode(SCREEN_HELP_WINDOW, c);
559 break;
560 case CMD_QUIT:
561 exit(EXIT_SUCCESS);
562 default:
563 break;
564 }
566 }