]> git.sesse.net Git - vlc/blob - plugins/text/ncurses.c
* ALL: new module API. Makes a few things a lot simpler, and we gain
[vlc] / plugins / text / ncurses.c
1 /*****************************************************************************
2  * ncurses.c : NCurses plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: ncurses.c,v 1.20 2002/07/31 20:56:52 sam Exp $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *      
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  * 
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29 #include <errno.h>                                                 /* ENOMEM */
30 #include <stdio.h>
31 #include <time.h>
32
33 #include <curses.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc/intf.h>
37 #include <vlc/vout.h>
38
39 /*****************************************************************************
40  * Local prototypes.
41  *****************************************************************************/
42 static int  Open           ( vlc_object_t * );
43 static void Close          ( vlc_object_t * );             
44
45 static void Run            ( intf_thread_t * );                  
46 static void FullScreen     ( intf_thread_t * );
47 static void Play           ( intf_thread_t * );
48 static void Stop           ( intf_thread_t * );
49 static void Next           ( intf_thread_t * );
50 static void Eject          ( intf_thread_t * );
51 static void Pause          ( intf_thread_t * );
52 static void PrevTitle      ( intf_thread_t * );
53 static void NextTitle      ( intf_thread_t * );
54 static void PrevChapter    ( intf_thread_t * );
55 static void NextChapter    ( intf_thread_t * );
56
57 static int  HandleKey      ( intf_thread_t *, int );
58 static void Redraw           ( intf_thread_t *, time_t * );
59 static int  PrintFullLine  ( const char *p_fmt, ... );
60 static void ManageSlider   ( intf_thread_t * );
61
62 /*****************************************************************************
63  * Module descriptor
64  *****************************************************************************/
65 vlc_module_begin();
66     set_description( _("ncurses interface module") );
67     set_capability( "interface", 10 );
68     set_callbacks( Open, Close );
69     add_shortcut( "curses" );
70 vlc_module_end();
71
72 /*****************************************************************************
73  * intf_sys_t: description and status of ncurses interface
74  *****************************************************************************/
75 struct intf_sys_t
76 {
77     input_thread_t *    p_input;
78
79     float               f_slider_state;
80     float               f_slider_state_old;
81 };
82
83 /*****************************************************************************
84  * Open: initialize and create window
85  *****************************************************************************/
86 static int Open( vlc_object_t *p_this )
87 {   
88     intf_thread_t *p_intf = (intf_thread_t *)p_this;
89
90     /* Allocate instance and initialize some members */
91     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
92     if( p_intf->p_sys == NULL )
93     {
94         msg_Err( p_intf, "out of memory" );
95         return( 1 );
96     }
97
98     p_intf->p_sys->p_input = NULL;
99
100     p_intf->pf_run = Run;
101
102     /* Initialize the curses library */
103     initscr();
104     /* Don't do NL -> CR/NL */
105     nonl();
106     /* Take input chars one at a time */
107     cbreak();
108     /* Don't echo */
109     noecho();
110
111     curs_set(0);
112     timeout(0);
113
114     clear();
115
116     return( 0 );
117 }
118
119 /*****************************************************************************
120  * Close: destroy interface window
121  *****************************************************************************/
122 static void Close( vlc_object_t *p_this )
123 {   
124     intf_thread_t *p_intf = (intf_thread_t *)p_this;
125
126     if( p_intf->p_sys->p_input )
127     {
128         vlc_object_release( p_intf->p_sys->p_input );
129     }
130
131     /* Close the ncurses interface */
132     endwin();
133
134     /* Destroy structure */
135     free( p_intf->p_sys );
136 }
137
138 /*****************************************************************************
139  * Run: ncurses thread
140  *****************************************************************************/
141 static void Run( intf_thread_t *p_intf )
142 {
143     signed char i_key;
144     time_t t_last_refresh;
145
146     /*
147      * force drawing the interface for the first time
148      */
149     t_last_refresh = ( time( 0 ) - 1);
150
151     while( !p_intf->b_die )
152     {
153         msleep( INTF_IDLE_SLEEP );
154
155         /* Update the input */ 
156         if( p_intf->p_sys->p_input == NULL )
157         {
158             p_intf->p_sys->p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
159                                                               FIND_ANYWHERE );
160         }
161         else if( p_intf->p_sys->p_input->b_dead )
162         {
163             vlc_object_release( p_intf->p_sys->p_input );
164             p_intf->p_sys->p_input = NULL;
165         }
166     
167         while( (i_key = getch()) != -1 )
168         {
169             /*
170              * HandleKey returns 1 if the screen needs to be redrawn
171              */
172             if ( HandleKey( p_intf, i_key ) )
173             {
174                 Redraw( p_intf, &t_last_refresh );
175             }
176         }
177
178         /*
179          * redraw the screen every second
180          */
181         if ( (time(0) - t_last_refresh) >= 1 )
182         {
183             ManageSlider ( p_intf );
184             Redraw( p_intf, &t_last_refresh );
185         }
186     }
187 }
188
189 /* following functions are local */
190
191 static int HandleKey( intf_thread_t *p_intf, int i_key )
192 {
193     switch( i_key )
194     {
195         case 'q':
196         case 'Q':
197             p_intf->b_die = 1;
198             return 0;
199
200         case 'f':
201             FullScreen( p_intf );
202             return 1;
203
204         case 'p':
205             Play( p_intf );
206             return 1;
207
208         case ' ':
209             Pause( p_intf );
210             return 1;
211
212         case 's':
213             Stop( p_intf );
214             return 1;
215
216         case 'n':
217             Next( p_intf );
218             return 1;
219
220         case 'e':
221             Eject( p_intf );
222             return 1;
223
224         case '[':
225             PrevTitle( p_intf );
226             break;
227
228         case ']':
229             NextTitle( p_intf );
230             break;
231
232         case '<':
233             PrevChapter( p_intf );
234             break;
235
236         case '>':
237             NextChapter( p_intf );
238             break;
239
240         case KEY_RIGHT:
241             p_intf->p_sys->f_slider_state += 100;
242             ManageSlider ( p_intf );
243             break;
244
245         case KEY_LEFT:
246             p_intf->p_sys->f_slider_state--;
247             ManageSlider ( p_intf );
248             break;
249
250         /*
251          * ^l should clear and redraw the screen
252          */
253         case 0x0c:
254             clear();
255             return 1;
256
257         default:
258             break;
259     }
260
261     return 0;
262 }
263
264 static int PrintFullLine ( const char *p_fmt, ... )
265 {
266     va_list  vl_args;
267     char *    p_buf        = NULL;
268     int       i_len;
269
270     va_start ( vl_args, p_fmt );
271     vasprintf ( &p_buf, p_fmt, vl_args );
272     va_end ( vl_args );
273
274     if ( p_buf == NULL )
275     {
276 //X        msg_Err( p_input, "intf error: %s", strerror ( ENOMEM ) );
277         return ( -1 );
278     }
279
280     i_len = strlen( p_buf );
281
282     /*
283      * make sure we don't exceed the border on the right side
284      */
285     if ( i_len > COLS )
286     {
287         p_buf[COLS] = '\0';
288         i_len = COLS;
289         printw( "%s", p_buf );
290     }
291     else
292     {
293         printw( "%s", p_buf );
294         hline( ' ', COLS - i_len );
295     }
296
297     free ( p_buf );
298
299     return i_len;
300 }
301
302 static void
303 Redraw ( intf_thread_t *p_intf, time_t *t_last_refresh )
304 {
305     int row = 0;
306
307     move ( row, 0 );
308
309     attrset ( A_REVERSE );
310     PrintFullLine( VOUT_TITLE " (ncurses interface)" );
311     attroff ( A_REVERSE );
312
313     row++;
314
315     row++;
316     move ( row, 0 );
317
318     if ( p_intf->p_sys->p_input != NULL )
319     {
320         PrintFullLine ( " DVD Chapter:%3d     DVD Title:%3d",
321             p_intf->p_sys->p_input->stream.p_selected_area->i_part,
322             p_intf->p_sys->p_input->stream.p_selected_area->i_id );
323     }
324
325     row++;
326     mvaddch ( row, 0, ACS_ULCORNER );
327     mvhline ( row, 1, ACS_HLINE, COLS-2 );
328     mvaddch ( row, COLS-1, ACS_URCORNER );
329
330     row++;
331     mvaddch ( row, 0, ACS_VLINE );
332     attrset ( A_REVERSE );
333     mvhline ( row, 1, ' ', ( (int) p_intf->p_sys->f_slider_state % COLS-2) );
334     attroff ( A_REVERSE );
335     mvaddch ( row, COLS-1, ACS_VLINE );
336
337     row++;
338     mvaddch ( row, 0, ACS_LLCORNER );
339     mvhline ( row, 1, ACS_HLINE, COLS-2 );
340     mvaddch ( row, COLS-1, ACS_LRCORNER );
341
342     refresh();
343
344     *t_last_refresh = time( 0 );
345 }
346
347 static void FullScreen( intf_thread_t *p_intf )
348 {
349     vout_thread_t *p_vout;
350
351     p_vout = vlc_object_find( p_intf->p_sys->p_input,
352                               VLC_OBJECT_VOUT, FIND_CHILD );
353     if( p_vout == NULL )
354     {
355         return;
356     }
357
358     p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
359     vlc_object_release( p_vout );
360 }
361
362 static void Eject ( intf_thread_t *p_intf )
363 {
364     char *psz_device = NULL, *psz_parser, *psz_name;
365
366     /*
367      * Get the active input
368      * Determine whether we can eject a media, ie it's a VCD or DVD
369      * If it's neither a VCD nor a DVD, then return
370      */
371
372     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
373                                                        FIND_ANYWHERE );
374
375     if( p_playlist == NULL )
376     {
377         return;
378     }
379
380     vlc_mutex_lock( &p_playlist->object_lock );
381
382     if( p_playlist->i_index < 0 )
383     {   
384         vlc_mutex_unlock( &p_playlist->object_lock );
385         vlc_object_release( p_playlist );
386         return; 
387     }
388
389     psz_name = p_playlist->pp_items[ p_playlist->i_index ]->psz_name;
390
391     if( psz_name )
392     {
393         if( !strncmp(psz_name, "dvd:", 4) )
394         {
395             switch( psz_name[4] )
396             {
397             case '\0':
398             case '@':
399                 psz_device = config_GetPsz( p_intf, "dvd_device" );
400                 break;
401             default:
402                 /* Omit the first 4 characters */
403                 psz_device = strdup( psz_name + 4 );
404                 break;
405             }
406         }
407         else if( !strncmp(psz_name, "vcd:", 4) )
408         {
409             switch( psz_name[4] )
410             {
411             case '\0':
412             case '@':
413                 psz_device = config_GetPsz( p_intf, "vcd_device" );
414                 break;
415             default:
416                 /* Omit the first 4 characters */
417                 psz_device = strdup( psz_name + 4 );
418                 break;
419             }
420         }
421         else
422         {
423             psz_device = strdup( psz_name );
424         }
425     }
426
427     vlc_mutex_unlock( &p_playlist->object_lock );
428     vlc_object_release( p_playlist );
429
430     if( psz_device == NULL )
431     {
432         return;
433     }
434
435     /* Remove what we have after @ */
436     psz_parser = psz_device;
437     for( psz_parser = psz_device ; *psz_parser ; psz_parser++ )
438     {
439         if( *psz_parser == '@' )
440         {
441             *psz_parser = '\0';
442             break;
443         }
444     }
445
446     /* If there's a stream playing, we aren't allowed to eject ! */
447     if( p_intf->p_sys->p_input == NULL )
448     {
449 //X        msg_Dbg( p_input, "ejecting %s", psz_device );
450
451         intf_Eject( p_intf, psz_device );
452     }
453
454     free(psz_device);
455     return;
456 }
457
458 static void Play ( intf_thread_t *p_intf )
459 {
460     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
461                                                        FIND_ANYWHERE );
462     if( p_playlist )
463     {
464         vlc_mutex_lock( &p_playlist->object_lock );
465
466         if( p_playlist->i_size )
467         {
468             vlc_mutex_unlock( &p_playlist->object_lock );
469             playlist_Play( p_playlist );
470             vlc_object_release( p_playlist );
471         }
472         else
473         {
474             vlc_mutex_unlock( &p_playlist->object_lock );
475             vlc_object_release( p_playlist );
476         }
477     }
478 }
479
480 static void Pause ( intf_thread_t *p_intf )
481 {
482     if( p_intf->p_sys->p_input == NULL )
483     {
484         return;
485     }
486
487     input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PAUSE );
488
489     return;
490 }
491
492 static void Stop ( intf_thread_t *p_intf )
493 {
494     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
495                                                        FIND_ANYWHERE );
496     if( p_playlist == NULL )
497     {
498         return;
499     }
500
501     playlist_Stop( p_playlist );
502     vlc_object_release( p_playlist );
503
504     return;
505 }
506
507 static void Next ( intf_thread_t *p_intf )
508 {
509     int i_id;
510     input_area_t * p_area;
511
512     i_id = p_intf->p_sys->p_input->stream.p_selected_area->i_id+1;
513
514     if ( i_id < p_intf->p_sys->p_input->stream.i_area_nb )
515     {
516         p_area = p_intf->p_sys->p_input->stream.pp_areas[i_id];
517
518         input_ChangeArea( p_intf->p_sys->p_input,
519                 (input_area_t *) p_area );
520
521         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PLAY );
522     }
523 }
524
525 static void ManageSlider ( intf_thread_t *p_intf )
526 {
527     if( p_intf->p_sys->p_input != NULL )
528     {
529         vlc_mutex_lock( &p_intf->p_sys->p_input->stream.stream_lock );
530
531         if( p_intf->p_sys->p_input->stream.b_seekable &&
532             p_intf->p_sys->p_input->stream.control.i_status == PLAYING_S )
533         {
534             float newvalue = p_intf->p_sys->f_slider_state;
535
536 #define p_area p_intf->p_sys->p_input->stream.p_selected_area
537
538             /* If the user hasn't touched the slider since the last time,
539              * then the input can safely change it */
540             if( newvalue == p_intf->p_sys->f_slider_state_old )
541             {
542                 /* Update the value */
543                 p_intf->p_sys->f_slider_state =
544                     p_intf->p_sys->f_slider_state_old =
545                     ( 100 * p_area->i_tell ) / p_area->i_size;
546             }
547             /* Otherwise, send message to the input if the user has
548              * finished dragging the slider */
549             else
550             {
551                 off_t i_seek = ( newvalue * p_area->i_size ) / 100;
552
553                 /* release the lock to be able to seek */
554                 vlc_mutex_unlock( &p_intf->p_sys->p_input->stream.stream_lock );
555                 input_Seek( p_intf, i_seek, INPUT_SEEK_SET );
556                 vlc_mutex_lock( &p_intf->p_sys->p_input->stream.stream_lock );
557
558                 /* Update the old value */
559                 p_intf->p_sys->f_slider_state_old = newvalue;
560             }
561 #    undef p_area
562         }
563
564         vlc_mutex_unlock( &p_intf->p_sys->p_input->stream.stream_lock );
565     }
566 }
567
568 static void PrevTitle ( intf_thread_t *p_intf )
569 {
570     input_area_t *  p_area;
571     int             i_id;
572
573     i_id = p_intf->p_sys->p_input->stream.p_selected_area->i_id - 1;
574
575     /* Disallow area 0 since it is used for video_ts.vob */
576     if ( i_id > 0 )
577     {
578         p_area = p_intf->p_sys->p_input->stream.pp_areas[i_id];
579         input_ChangeArea( p_intf->p_sys->p_input, (input_area_t*)p_area );
580
581         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PLAY );
582     }
583 }
584
585 static void NextTitle ( intf_thread_t *p_intf )
586 {
587     input_area_t *  p_area;
588     int             i_id;
589
590     i_id = p_intf->p_sys->p_input->stream.p_selected_area->i_id + 1;
591
592     if ( i_id < p_intf->p_sys->p_input->stream.i_area_nb )
593     {
594         p_area = p_intf->p_sys->p_input->stream.pp_areas[i_id];
595         input_ChangeArea( p_intf->p_sys->p_input, (input_area_t*)p_area );
596
597         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PLAY );
598     }
599 }
600
601 static void PrevChapter ( intf_thread_t *p_intf )
602 {
603     input_area_t *  p_area;
604
605     p_area = p_intf->p_sys->p_input->stream.p_selected_area;
606
607     if ( p_area->i_part > 0 )
608     {
609         p_area->i_part--;
610         input_ChangeArea( p_intf->p_sys->p_input, (input_area_t*)p_area );
611
612         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PLAY );
613     }
614 }
615
616 static void NextChapter( intf_thread_t *p_intf )
617 {
618     input_area_t *  p_area;
619
620     p_area = p_intf->p_sys->p_input->stream.p_selected_area;
621
622     if ( p_area->i_part < p_area->i_part_nb )
623     {
624         p_area->i_part++;
625         input_ChangeArea( p_intf->p_sys->p_input, (input_area_t*)p_area );
626
627         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PLAY );
628     }
629 }
630