* ncurses.c : NCurses plugin for vlc
*****************************************************************************
* Copyright (C) 2001 VideoLAN
- * $Id: ncurses.c,v 1.4 2001/04/28 03:36:25 sam Exp $
+ * $Id: ncurses.c,v 1.17 2002/06/01 18:04:49 sam Exp $
*
* Authors: Samuel Hocevar <sam@zoy.org>
*
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
-#define MODULE_NAME ncurses
-#include "modules_inner.h"
-
/*****************************************************************************
* Preamble
*****************************************************************************/
-#include "defs.h"
-
#include <stdlib.h> /* malloc(), free() */
+#include <string.h>
+#include <errno.h> /* ENOMEM */
+#include <stdio.h>
+#include <time.h>
-#include "config.h"
-#include "common.h" /* boolean_t, byte_t */
-#include "threads.h"
-#include "mtime.h"
+#include <curses.h>
-#include "modules.h"
+#include <vlc/vlc.h>
+#include <vlc/intf.h>
+#include <vlc/vout.h>
/*****************************************************************************
- * Building configuration tree
+ * Local prototypes.
*****************************************************************************/
-MODULE_CONFIG_START
-ADD_WINDOW( "Configuration for NCurses module" )
- ADD_COMMENT( "For now, the NCurses module cannot be configured" )
-MODULE_CONFIG_END
+static void intf_getfunctions ( function_list_t * );
+static int intf_Open ( intf_thread_t * );
+static void intf_Close ( intf_thread_t * );
+static void intf_Run ( intf_thread_t * );
+
+static void FullScreen ( intf_thread_t * );
+static void Play ( intf_thread_t * );
+static void Stop ( intf_thread_t * );
+static void Next ( intf_thread_t * );
+static void Eject ( intf_thread_t * );
+static void Pause ( intf_thread_t * );
+static void PrevTitle ( intf_thread_t * );
+static void NextTitle ( intf_thread_t * );
+static void PrevChapter ( intf_thread_t * );
+static void NextChapter ( intf_thread_t * );
+
+static int HandleKey ( intf_thread_t *, int );
+static void Redraw ( intf_thread_t *, time_t * );
+static int PrintFullLine ( const char *p_fmt, ... );
+static void ManageSlider ( intf_thread_t * );
/*****************************************************************************
- * Capabilities defined in the other files.
+ * Building configuration tree
*****************************************************************************/
-void _M( intf_getfunctions )( function_list_t * p_function_list );
+MODULE_CONFIG_START
+MODULE_CONFIG_STOP
+
+MODULE_INIT_START
+ SET_DESCRIPTION( _("ncurses interface module") )
+ ADD_CAPABILITY( INTF, 10 )
+ ADD_SHORTCUT( "curses" )
+MODULE_INIT_STOP
+
+MODULE_ACTIVATE_START
+ intf_getfunctions( &p_module->p_functions->intf );
+MODULE_ACTIVATE_STOP
+
+MODULE_DEACTIVATE_START
+MODULE_DEACTIVATE_STOP
/*****************************************************************************
- * InitModule: get the module structure and configuration.
- *****************************************************************************
- * We have to fill psz_name, psz_longname and psz_version. These variables
- * will be strdup()ed later by the main application because the module can
- * be unloaded later to save memory, and we want to be able to access this
- * data even after the module has been unloaded.
+ * intf_sys_t: description and status of ncurses interface
*****************************************************************************/
-MODULE_INIT
+struct intf_sys_s
{
- p_module->psz_name = MODULE_STRING;
- p_module->psz_longname = "ncurses interface module";
- p_module->psz_version = VERSION;
+ /* special actions */
+ vlc_mutex_t change_lock; /* the change lock */
- p_module->i_capabilities = MODULE_CAPABILITY_NULL
- | MODULE_CAPABILITY_INTF;
+ float f_slider_state;
+ float f_slider_state_old;
+};
- return( 0 );
+/*****************************************************************************
+ * Functions exported as capabilities. They are declared as static so that
+ * we don't pollute the namespace too much.
+ *****************************************************************************/
+static void intf_getfunctions( function_list_t * p_function_list )
+{
+ p_function_list->functions.intf.pf_open = intf_Open;
+ p_function_list->functions.intf.pf_close = intf_Close;
+ p_function_list->functions.intf.pf_run = intf_Run;
}
/*****************************************************************************
- * ActivateModule: set the module to an usable state.
- *****************************************************************************
- * This function fills the capability functions and the configuration
- * structure. Once ActivateModule() has been called, the i_usage can
- * be set to 0 and calls to NeedModule() be made to increment it. To unload
- * the module, one has to wait until i_usage == 0 and call DeactivateModule().
+ * intf_Open: initialize and create window
*****************************************************************************/
-MODULE_ACTIVATE
+static int intf_Open( intf_thread_t *p_intf )
{
- p_module->p_functions = malloc( sizeof( module_functions_t ) );
- if( p_module->p_functions == NULL )
+ /* Allocate instance and initialize some members */
+ p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
+ if( p_intf->p_sys == NULL )
{
- return( -1 );
+ msg_Err( p_intf, "out of memory" );
+ return( 1 );
}
- _M( intf_getfunctions )( &p_module->p_functions->intf );
+ memset ( p_intf->p_sys, 0, sizeof ( intf_sys_t ) );
+
+ /* Initialize the curses library */
+ initscr();
+ /* Don't do NL -> CR/NL */
+ nonl();
+ /* Take input chars one at a time */
+ cbreak();
+ /* Don't echo */
+ noecho();
- p_module->p_config = p_config;
+ curs_set(0);
+ timeout(0);
+
+ clear();
return( 0 );
}
/*****************************************************************************
- * DeactivateModule: make sure the module can be unloaded.
- *****************************************************************************
- * This function must only be called when i_usage == 0. If it successfully
- * returns, i_usage can be set to -1 and the module unloaded. Be careful to
- * lock usage_lock during the whole process.
+ * intf_Close: destroy interface window
*****************************************************************************/
-MODULE_DEACTIVATE
+static void intf_Close( intf_thread_t *p_intf )
{
- free( p_module->p_functions );
+ /* Close the ncurses interface */
+ endwin();
- return( 0 );
+ /* Destroy structure */
+ free( p_intf->p_sys );
+}
+
+/*****************************************************************************
+ * intf_Run: ncurses thread
+ *****************************************************************************/
+static void intf_Run( intf_thread_t *p_intf )
+{
+ signed char i_key;
+ time_t t_last_refresh;
+
+ /*
+ * force drawing the interface for the first time
+ */
+ t_last_refresh = ( time( 0 ) - 1);
+
+ while( !p_intf->p_vlc->b_die )
+ {
+ p_intf->pf_manage( p_intf );
+
+ msleep( INTF_IDLE_SLEEP );
+
+ while( (i_key = getch()) != -1 )
+ {
+ /*
+ * HandleKey returns 1 if the screen needs to be redrawn
+ */
+ if ( HandleKey( p_intf, i_key ) )
+ {
+ Redraw( p_intf, &t_last_refresh );
+ }
+ }
+
+ /*
+ * redraw the screen every second
+ */
+ if ( (time(0) - t_last_refresh) >= 1 )
+ {
+ ManageSlider ( p_intf );
+ Redraw( p_intf, &t_last_refresh );
+ }
+ }
+}
+
+/* following functions are local */
+
+static int HandleKey( intf_thread_t *p_intf, int i_key )
+{
+ switch( i_key )
+ {
+ case 'q':
+ case 'Q':
+ p_intf->b_die = 1;
+ return 0;
+
+ case 'f':
+ FullScreen( p_intf );
+ return 1;
+
+ case 'p':
+ Play( p_intf );
+ return 1;
+
+ case ' ':
+ Pause( p_intf );
+ return 1;
+
+ case 's':
+ Stop( p_intf );
+ return 1;
+
+ case 'n':
+ Next( p_intf );
+ return 1;
+
+ case 'e':
+ Eject( p_intf );
+ return 1;
+
+ case '[':
+ PrevTitle( p_intf );
+ break;
+
+ case ']':
+ NextTitle( p_intf );
+ break;
+
+ case '<':
+ PrevChapter( p_intf );
+ break;
+
+ case '>':
+ NextChapter( p_intf );
+ break;
+
+ case KEY_RIGHT:
+ p_intf->p_sys->f_slider_state += 100;
+ ManageSlider ( p_intf );
+ break;
+
+ case KEY_LEFT:
+ p_intf->p_sys->f_slider_state--;
+ ManageSlider ( p_intf );
+ break;
+
+ /*
+ * ^l should clear and redraw the screen
+ */
+ case 0x0c:
+ clear();
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int PrintFullLine ( const char *p_fmt, ... )
+{
+ va_list vl_args;
+ char * p_buf = NULL;
+ int i_len;
+
+ va_start ( vl_args, p_fmt );
+ vasprintf ( &p_buf, p_fmt, vl_args );
+ va_end ( vl_args );
+
+ if ( p_buf == NULL )
+ {
+//X msg_Err( p_input, "intf error: %s", strerror ( ENOMEM ) );
+ return ( -1 );
+ }
+
+ i_len = strlen( p_buf );
+
+ /*
+ * make sure we don't exceed the border on the right side
+ */
+ if ( i_len > COLS )
+ {
+ p_buf[COLS] = '\0';
+ i_len = COLS;
+ printw( "%s", p_buf );
+ }
+ else
+ {
+ printw( "%s", p_buf );
+ hline( ' ', COLS - i_len );
+ }
+
+ free ( p_buf );
+
+ return i_len;
+}
+
+static void
+Redraw ( intf_thread_t *p_intf, time_t *t_last_refresh )
+{
+ int row = 0;
+
+ move ( row, 0 );
+
+ attrset ( A_REVERSE );
+ PrintFullLine( VOUT_TITLE " (ncurses interface)" );
+ attroff ( A_REVERSE );
+
+ row++;
+
+ row++;
+ move ( row, 0 );
+
+ if ( p_intf->p_vlc->p_input_bank->pp_input[0] != NULL )
+ {
+ PrintFullLine ( " DVD Chapter:%3d DVD Title:%3d",
+ p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area->i_part,
+ p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area->i_id );
+ }
+
+ row++;
+ mvaddch ( row, 0, ACS_ULCORNER );
+ mvhline ( row, 1, ACS_HLINE, COLS-2 );
+ mvaddch ( row, COLS-1, ACS_URCORNER );
+
+ row++;
+ mvaddch ( row, 0, ACS_VLINE );
+ attrset ( A_REVERSE );
+ mvhline ( row, 1, ' ', ( (int) p_intf->p_sys->f_slider_state % COLS-2) );
+ attroff ( A_REVERSE );
+ mvaddch ( row, COLS-1, ACS_VLINE );
+
+ row++;
+ mvaddch ( row, 0, ACS_LLCORNER );
+ mvhline ( row, 1, ACS_HLINE, COLS-2 );
+ mvaddch ( row, COLS-1, ACS_LRCORNER );
+
+ refresh();
+
+ *t_last_refresh = time( 0 );
+}
+
+static void FullScreen( intf_thread_t *p_intf )
+{
+ vlc_mutex_lock( &p_intf->p_vlc->p_vout_bank->pp_vout[0]->change_lock );
+
+ p_intf->p_vlc->p_vout_bank->pp_vout[0]->i_changes |= VOUT_FULLSCREEN_CHANGE;
+
+ vlc_mutex_unlock( &p_intf->p_vlc->p_vout_bank->pp_vout[0]->change_lock );
+}
+
+static void Eject ( intf_thread_t *p_intf )
+{
+ char *psz_device = NULL;
+ char *psz_parser;
+
+ /*
+ * Get the active input
+ * Determine whether we can eject a media, ie it's a VCD or DVD
+ * If it's neither a VCD nor a DVD, then return
+ */
+
+ /*
+ * Don't really know if I must lock the stuff here, we're using it read-only
+ */
+
+ if( p_intf->p_vlc->p_playlist->current.psz_name != NULL)
+ {
+ if( !strncmp(p_intf->p_vlc->p_playlist->current.psz_name, "dvd:", 4) )
+ {
+ switch( p_intf->p_vlc->p_playlist->current.psz_name[4] )
+ {
+ case '\0':
+ case '@':
+ psz_device = config_GetPsz( p_intf, "dvd_device" );
+ break;
+ default:
+ /* Omit the first 4 characters */
+ psz_device = strdup( p_intf->p_vlc->p_playlist->current.psz_name + 4 );
+ break;
+ }
+ }
+ else if( !strncmp(p_intf->p_vlc->p_playlist->current.psz_name, "vcd:", 4) )
+ {
+ switch( p_intf->p_vlc->p_playlist->current.psz_name[4] )
+ {
+ case '\0':
+ case '@':
+ psz_device = config_GetPsz( p_intf, "vcd_device" );
+ break;
+ default:
+ /* Omit the first 4 characters */
+ psz_device = strdup( p_intf->p_vlc->p_playlist->current.psz_name + 4 );
+ break;
+ }
+ }
+ else
+ {
+ psz_device = strdup( p_intf->p_vlc->p_playlist->current.psz_name );
+ }
+ }
+
+ if( psz_device == NULL )
+ {
+ return;
+ }
+
+ /* Remove what we have after @ */
+ psz_parser = psz_device;
+ for( psz_parser = psz_device ; *psz_parser ; psz_parser++ )
+ {
+ if( *psz_parser == '@' )
+ {
+ *psz_parser = '\0';
+ break;
+ }
+ }
+
+ /* If there's a stream playing, we aren't allowed to eject ! */
+ if( p_intf->p_vlc->p_input_bank->pp_input[0] == NULL )
+ {
+//X msg_Dbg( p_input, "ejecting %s", psz_device );
+
+ intf_Eject( p_intf, psz_device );
+ }
+
+ free(psz_device);
+ return;
+}
+
+static void Play ( intf_thread_t *p_intf )
+{
+ if( p_intf->p_vlc->p_input_bank->pp_input[0] != NULL )
+ {
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ p_intf->p_vlc->p_playlist->b_stopped = 0;
+ }
+ else
+ {
+ vlc_mutex_lock( &p_intf->p_vlc->p_playlist->change_lock );
+
+ if( p_intf->p_vlc->p_playlist->b_stopped )
+ {
+ if( p_intf->p_vlc->p_playlist->i_size )
+ {
+ vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
+ intf_PlaylistJumpto( p_intf->p_vlc->p_playlist,
+ p_intf->p_vlc->p_playlist->i_index );
+ }
+ else
+ {
+ vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
+ }
+ }
+ else
+ {
+
+ vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
+ }
+
+ }
+}
+
+static void Pause ( intf_thread_t *p_intf )
+{
+ if( p_intf->p_vlc->p_input_bank->pp_input[0] != NULL )
+ {
+ if ( p_intf->p_vlc->p_input_bank->pp_input[0]->i_status & INPUT_STATUS_PLAY )
+ {
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PAUSE );
+
+ vlc_mutex_lock( &p_intf->p_vlc->p_playlist->change_lock );
+ p_intf->p_vlc->p_playlist->b_stopped = 0;
+ vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
+ }
+ else
+ {
+ Play ( p_intf );
+ }
+ }
+}
+
+static void Stop ( intf_thread_t *p_intf )
+{
+ if( p_intf->p_vlc->p_input_bank->pp_input[0] != NULL )
+ {
+ /* end playing item */
+ p_intf->p_vlc->p_input_bank->pp_input[0]->b_eof = 1;
+
+ /* update playlist */
+ vlc_mutex_lock( &p_intf->p_vlc->p_playlist->change_lock );
+
+ p_intf->p_vlc->p_playlist->i_index--;
+ p_intf->p_vlc->p_playlist->b_stopped = 1;
+
+ vlc_mutex_unlock( &p_intf->p_vlc->p_playlist->change_lock );
+
+ }
+}
+
+static void Next ( intf_thread_t *p_intf )
+{
+ int i_id;
+ input_area_t * p_area;
+
+ i_id = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area->i_id+1;
+
+ if ( i_id < p_intf->p_vlc->p_input_bank->pp_input[0]->stream.i_area_nb )
+ {
+ p_area = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.pp_areas[i_id];
+
+ input_ChangeArea( p_intf->p_vlc->p_input_bank->pp_input[0],
+ (input_area_t *) p_area );
+
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ }
+}
+
+static void ManageSlider ( intf_thread_t *p_intf )
+{
+ if( p_intf->p_vlc->p_input_bank->pp_input[0] != NULL )
+ {
+ vlc_mutex_lock( &p_intf->p_vlc->p_input_bank->pp_input[0]->stream.stream_lock );
+
+ if( p_intf->p_vlc->p_input_bank->pp_input[0]->stream.b_seekable &&
+ p_intf->p_vlc->p_input_bank->pp_input[0]->i_status & INPUT_STATUS_PLAY )
+ {
+ float newvalue = p_intf->p_sys->f_slider_state;
+
+#define p_area p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area
+
+ /* If the user hasn't touched the slider since the last time,
+ * then the input can safely change it */
+ if( newvalue == p_intf->p_sys->f_slider_state_old )
+ {
+ /* Update the value */
+ p_intf->p_sys->f_slider_state =
+ p_intf->p_sys->f_slider_state_old =
+ ( 100 * p_area->i_tell ) / p_area->i_size;
+ }
+ /* Otherwise, send message to the input if the user has
+ * finished dragging the slider */
+ else
+ {
+ off_t i_seek = ( newvalue * p_area->i_size ) / 100;
+
+ /* release the lock to be able to seek */
+ vlc_mutex_unlock( &p_intf->p_vlc->p_input_bank->pp_input[0]->stream.stream_lock );
+ input_Seek( p_intf->p_vlc->p_input_bank->pp_input[0]->p_this, i_seek, INPUT_SEEK_SET );
+ vlc_mutex_lock( &p_intf->p_vlc->p_input_bank->pp_input[0]->stream.stream_lock );
+
+ /* Update the old value */
+ p_intf->p_sys->f_slider_state_old = newvalue;
+ }
+# undef p_area
+ }
+
+ vlc_mutex_unlock( &p_intf->p_vlc->p_input_bank->pp_input[0]->stream.stream_lock );
+ }
+}
+
+static void PrevTitle ( intf_thread_t *p_intf )
+{
+ input_area_t * p_area;
+ int i_id;
+
+ i_id = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area->i_id - 1;
+
+ /* Disallow area 0 since it is used for video_ts.vob */
+ if ( i_id > 0 )
+ {
+ p_area = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.pp_areas[i_id];
+ input_ChangeArea( p_intf->p_vlc->p_input_bank->pp_input[0], (input_area_t*)p_area );
+
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ }
+}
+
+static void NextTitle ( intf_thread_t *p_intf )
+{
+ input_area_t * p_area;
+ int i_id;
+
+ i_id = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area->i_id + 1;
+
+ if ( i_id < p_intf->p_vlc->p_input_bank->pp_input[0]->stream.i_area_nb )
+ {
+ p_area = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.pp_areas[i_id];
+ input_ChangeArea( p_intf->p_vlc->p_input_bank->pp_input[0], (input_area_t*)p_area );
+
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ }
+}
+
+static void PrevChapter ( intf_thread_t *p_intf )
+{
+ input_area_t * p_area;
+
+ p_area = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area;
+
+ if ( p_area->i_part > 0 )
+ {
+ p_area->i_part--;
+ input_ChangeArea( p_intf->p_vlc->p_input_bank->pp_input[0], (input_area_t*)p_area );
+
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ }
+}
+
+static void NextChapter( intf_thread_t *p_intf )
+{
+ input_area_t * p_area;
+
+ p_area = p_intf->p_vlc->p_input_bank->pp_input[0]->stream.p_selected_area;
+
+ if ( p_area->i_part < p_area->i_part_nb )
+ {
+ p_area->i_part++;
+ input_ChangeArea( p_intf->p_vlc->p_input_bank->pp_input[0], (input_area_t*)p_area );
+
+ input_SetStatus( p_intf->p_vlc->p_input_bank->pp_input[0], INPUT_STATUS_PLAY );
+ }
}