Commit de31813f authored by Antoine Cellerier's avatar Antoine Cellerier

Add a new type of VLC Lua module: Interfaces.

Two things led me to add this new type of Lua modules:
 * Most interfaces duplicate code from one another (rc and telnet both deal
   with sockets, rc and hotkeys duplicate VLC interfacing code)
 * It's easier to code in Lua than in C (for high level stuff)
(* Users can code their own module easily, all they need is a text editor.)

Most of the changes in modules/misc/ are Lua wrappers for different C APIs
in VLC. I'd say that 90% of those wrappers won't change, the other 10% might
 need small changes (mostly to simplify the C code).

The VLC Lua "interface" module will look for lua scripts in the
*share*/luaintf/ directory. Files in *share*/luaintf/modules/ are lua modules
which can be used by multiple VLC Lua modules (using require "modulename").

This commit includes 4 Lua interface modules:
 * dummy.lua: the default module. Prints error messages and quits.
 * rc.lua: equivalent to the legacy rc.c module. Also includes a bunch of
   new features (+ multiple socket connections at a time work!). See file for
   more details.
 * telnet.lua: equivalent to the legacy telnet.c module. See file for more
   details.
 * hotkeys.lua: a rewrite of the hotkey handling module. This module is still
   experimental. I'll need to change VLC core hotkeys stuff to make it work
   like I want (ie: 1 hotkey triggers 1 action instead of the current 1 action
   can have 1 hotkey). This version executes 3 dummy actions when keys 'q',
   'w' or 'x' are pressed.

What's left to do:
 * Port the http interface plugin to this framework (at least for the
   macros/rpn part ... using <?vlc some lua code ?> à la PHP would be way
   easier than what we currently have).
 * Finish work on the hotkeys module.
 * Code a real telnet/rc module with autocompletion and all the cool features
   usually found in a telnet/terminal interface.
 * Trash the legacy C modules.

Stuff to test (which I can't test myself):
 * Win32 and Mac OS X specific changes to Makefile.am
 * Console interface under Win32. I expect it not to work.

Other stuff included in this changeset are:
 * Code cleanup (I'm sure that it's still possible to simplify some of the old lua bindings code).
 * That's pretty much it in fact :/
parent 154d9e74
......@@ -433,6 +433,14 @@ VLC-release.app: vlc
for i in $(srcdir)/share/luameta/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/VLC-release.app/Contents/MacOS/share/luameta/`basename $${i}` ; \
done ; \
$(INSTALL) -d $(top_builddir)/VLC-release.app/Contents/MacOS/share/luaintf
for i in $(srcdir)/share/luaintf/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/VLC-release.app/Contents/MacOS/share/luaintf/`basename $${i}` ; \
done ; \
$(INSTALL) -d $(top_builddir)/VLC-release.app/Contents/MacOS/share/luaintf/modules
for i in $(srcdir)/share/luaintf/modules/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/VLC-release.app/Contents/MacOS/share/luaintf/modules/`basename $${i}` ; \
done ; \
$(INSTALL) -d $(top_builddir)/VLC-release.app/Contents/MacOS/share/http/dialogs
$(INSTALL) -d $(top_builddir)/VLC-release.app/Contents/MacOS/share/http/js
$(INSTALL) -d $(top_builddir)/VLC-release.app/Contents/MacOS/share/http/old
......@@ -733,6 +741,14 @@ package-win32-base-debug: package-win-common
for i in $(srcdir)/share/luameta/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/vlc-${VERSION}/share/luameta/`basename $${i}` ; \
done ;
$(INSTALL) -d $(top_builddir)/vlc-${VERSION}/share/luaintf
for i in $(srcdir)/share/luaintf/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/vlc-${VERSION}/share/luaintf/`basename $${i}` ; \
done ;
$(INSTALL) -d $(top_builddir)/vlc-${VERSION}/share/luaintf/modules
for i in $(srcdir)/share/luaintf/modules/*.* ; do \
$(INSTALL) -m 644 $${i} $(top_builddir)/vlc-${VERSION}/share/luaintf/modules/`basename $${i}` ; \
done ;
mkdir -p "$(top_builddir)/vlc-${VERSION}/osdmenu"
cp $(srcdir)/share/osdmenu/*.* "$(top_builddir)/vlc-${VERSION}/osdmenu"
......
SOURCES_lua = luaplaylist.c luameta.c vlclua.c vlclua.h
SOURCES_lua = playlist.c meta.c intf.c vlc.c vlc.h callbacks.c objects.c variables.c configuration.c net.c vlm.c
/*****************************************************************************
* callbacks.c: Generic lua<->vlc callbacks interface
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Antoine Cellerier <dionoea at videolan tod org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <vlc/vlc.h>
#include <lua.h> /* Low level lua C API */
#include <lauxlib.h> /* Higher level C API */
#include <lualib.h> /* Lua libs */
#include "vlc.h"
typedef struct
{
int i_index;
int i_type;
lua_State *L;
} vlclua_callback_t;
static int vlclua_callback( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *p_data )
{
vlclua_callback_t *p_callback = (vlclua_callback_t*)p_data;
lua_State *L = p_callback->L;
/* <empty stack> */
lua_getglobal( L, "vlc" );
/* vlc */
lua_getfield( L, -1, "callbacks" );
/* vlc callbacks */
lua_remove( L, -2 );
/* callbacks */
lua_pushinteger( L, p_callback->i_index );
/* callbacks index */
lua_gettable( L, -2 );
/* callbacks callbacks[index] */
lua_remove( L, -2 );
/* callbacks[index] */
lua_getfield( L, -1, "callback" );
/* callbacks[index] callback */
lua_pushstring( L, psz_var );
/* callbacks[index] callback var */
vlclua_pushvalue( L, p_callback->i_type, oldval );
/* callbacks[index] callback var oldval */
vlclua_pushvalue( L, p_callback->i_type, newval );
/* callbacks[index] callback var oldval newval */
lua_getfield( L, -5, "data" );
/* callbacks[index] callback var oldval newval data */
lua_remove( L, -6 );
/* callback var oldval newval data */
if( lua_pcall( L, 4, 0, 0 ) )
{
/* errormessage */
const char *psz_err = lua_tostring( L, -1 );
msg_Err( p_this, "Error while runing lua interface callback: %s",
psz_err );
/* empty the stack (should only contain the error message) */
lua_settop( L, 0 );
return VLC_EGENERIC;
}
/* empty the stack (should already be empty) */
lua_settop( L, 0 );
return VLC_SUCCESS;
}
int vlclua_add_callback( lua_State *L )
{
vlclua_callback_t *p_callback;
static int i_index = 0;
vlc_object_t *p_obj = vlclua_checkobject( L, 1, 0 );
const char *psz_var = luaL_checkstring( L, 2 );
lua_settop( L, 4 ); /* makes sure that optional data arg is set */
if( !lua_isfunction( L, 3 ) )
return vlclua_error( L );
i_index++;
p_callback = (vlclua_callback_t*)malloc( sizeof( vlclua_callback_t ) );
if( !p_callback )
return vlclua_error( L );
/* obj var func data */
lua_getglobal( L, "vlc" );
/* obj var func data vlc */
lua_getfield( L, -1, "callbacks" );
if( lua_isnil( L, -1 ) )
{
lua_pop( L, 1 );
lua_newtable( L );
lua_setfield( L, -2, "callbacks" );
lua_getfield( L, -1, "callbacks" );
}
/* obj var func data vlc callbacks */
lua_remove( L, -2 );
/* obj var func data callbacks */
lua_pushinteger( L, i_index );
/* obj var func data callbacks index */
lua_insert( L, -4 );
/* obj var index func data callbacks */
lua_insert( L, -4 );
/* obj var callbacks index func data */
lua_createtable( L, 0, 0 );
/* obj var callbacks index func data cbtable */
lua_insert( L, -2 );
/* obj var callbacks index func cbtable data */
lua_setfield( L, -2, "data" );
/* obj var callbacks index func cbtable */
lua_insert( L, -2 );
/* obj var callbacks index cbtable func */
lua_setfield( L, -2, "callback" );
/* obj var callbacks index cbtable */
lua_pushlightuserdata( L, p_obj ); /* will be needed in vlclua_del_callback */
/* obj var callbacks index cbtable p_obj */
lua_setfield( L, -2, "private1" );
/* obj var callbacks index cbtable */
lua_pushvalue( L, 2 ); /* will be needed in vlclua_del_callback */
/* obj var callbacks index cbtable var */
lua_setfield( L, -2, "private2" );
/* obj var callbacks index cbtable */
lua_pushlightuserdata( L, p_callback ); /* will be needed in vlclua_del_callback */
/* obj var callbacks index cbtable p_callback */
lua_setfield( L, -2, "private3" );
/* obj var callbacks index cbtable */
lua_settable( L, -3 );
/* obj var callbacks */
lua_pop( L, 3 );
/* <empty stack> */
/* Do not move this before the lua specific code (it somehow changes
* the function in the stack to nil) */
p_callback->i_index = i_index;
p_callback->i_type = var_Type( p_obj, psz_var );
p_callback->L = lua_newthread( L );
var_AddCallback( p_obj, psz_var, vlclua_callback, p_callback );
return 0;
}
int vlclua_del_callback( lua_State *L )
{
vlclua_callback_t *p_callback;
vlc_bool_t b_found = VLC_FALSE;
vlc_object_t *p_obj = vlclua_checkobject( L, 1, 0 );
const char *psz_var = luaL_checkstring( L, 2 );
lua_settop( L, 4 ); /* makes sure that optional data arg is set */
if( !lua_isfunction( L, 3 ) )
return vlclua_error( L );
/* obj var func data */
lua_getglobal( L, "vlc" );
/* obj var func data vlc */
lua_getfield( L, -1, "callbacks" );
if( lua_isnil( L, -1 ) )
return luaL_error( L, "Couldn't find matching callback." );
/* obj var func data vlc callbacks */
lua_remove( L, -2 );
/* obj var func data callbacks */
lua_pushnil( L );
/* obj var func data callbacks index */
while( lua_next( L, -2 ) )
{
/* obj var func data callbacks index value */
if( lua_isnumber( L, -2 ) )
{
lua_getfield( L, -1, "private2" );
/* obj var func data callbacks index value private2 */
if( lua_equal( L, 2, -1 ) ) /* var name is equal */
{
lua_pop( L, 1 );
/* obj var func data callbacks index value */
lua_getfield( L, -1, "callback" );
/* obj var func data callbacks index value callback */
if( lua_equal( L, 3, -1 ) ) /* callback function is equal */
{
lua_pop( L, 1 );
/* obj var func data callbacks index value */
lua_getfield( L, -1, "data" ); /* callback data is equal */
/* obj var func data callbacks index value data */
if( lua_equal( L, 4, -1 ) )
{
vlc_object_t *p_obj2;
lua_pop( L, 1 );
/* obj var func data callbacks index value */
lua_getfield( L, -1, "private1" );
/* obj var func data callbacks index value private1 */
p_obj2 = (vlc_object_t*)luaL_checklightuserdata( L, -1 );
if( p_obj2 == p_obj ) /* object is equal */
{
lua_pop( L, 1 );
/* obj var func data callbacks index value */
lua_getfield( L, -1, "private3" );
/* obj var func data callbacks index value private3 */
p_callback = (vlclua_callback_t*)luaL_checklightuserdata( L, -1 );
lua_pop( L, 2 );
/* obj var func data callbacks index */
b_found = VLC_TRUE;
break;
}
else
{
/* obj var func data callbacks index value private1 */
lua_pop( L, 1 );
/* obj var func data callbacks index value */
}
}
else
{
/* obj var func data callbacks index value data */
lua_pop( L, 1 );
/* obj var func data callbacks index value */
}
}
else
{
/* obj var func data callbacks index value callback */
lua_pop( L, 1 );
/* obj var func data callbacks index value */
}
}
else
{
/* obj var func data callbacks index value private2 */
lua_pop( L, 1 );
/* obj var func data callbacks index value */
}
}
/* obj var func data callbacks index value */
lua_pop( L, 1 );
/* obj var func data callbacks index */
}
if( b_found == VLC_FALSE )
/* obj var func data callbacks */
return luaL_error( L, "Couldn't find matching callback." );
/* else */
/* obj var func data callbacks index*/
var_DelCallback( p_obj, psz_var, vlclua_callback, p_callback );
free( p_callback );
/* obj var func data callbacks index */
lua_pushnil( L );
/* obj var func data callbacks index nil */
lua_settable( L, -3 ); /* delete the callback table entry */
/* obj var func data callbacks */
lua_pop( L, 5 );
/* <empty stack> */
return 0;
}
/*****************************************************************************
* configuration.c: Generic lua<->vlc config inteface
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Antoine Cellerier <dionoea at videolan tod org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <vlc/vlc.h>
#include <lua.h> /* Low level lua C API */
#include <lauxlib.h> /* Higher level C API */
#include <lualib.h> /* Lua libs */
#include "vlc.h"
/*****************************************************************************
* Config handling
*****************************************************************************/
int vlclua_config_get( lua_State *L )
{
vlc_object_t * p_this = vlclua_get_this( L );
const char *psz_name;
psz_name = luaL_checkstring( L, 1 );
switch( config_GetType( p_this, psz_name ) )
{
case VLC_VAR_MODULE:
case VLC_VAR_STRING:
case VLC_VAR_FILE:
case VLC_VAR_DIRECTORY:
lua_pushstring( L, config_GetPsz( p_this, psz_name ) );
break;
case VLC_VAR_INTEGER:
lua_pushinteger( L, config_GetInt( p_this, psz_name ) );
break;
case VLC_VAR_BOOL:
lua_pushboolean( L, config_GetInt( p_this, psz_name ) );
break;
case VLC_VAR_FLOAT:
lua_pushnumber( L, config_GetFloat( p_this, psz_name ) );
break;
default:
return vlclua_error( L );
}
return 1;
}
int vlclua_config_set( lua_State *L )
{
vlc_object_t *p_this = vlclua_get_this( L );
const char *psz_name;
psz_name = luaL_checkstring( L, 1 );
switch( config_GetType( p_this, psz_name ) )
{
case VLC_VAR_MODULE:
case VLC_VAR_STRING:
case VLC_VAR_FILE:
case VLC_VAR_DIRECTORY:
config_PutPsz( p_this, psz_name, luaL_checkstring( L, 2 ) );
break;
case VLC_VAR_INTEGER:
config_PutInt( p_this, psz_name, luaL_checkint( L, 2 ) );
break;
case VLC_VAR_BOOL:
config_PutInt( p_this, psz_name, luaL_checkboolean( L, 2 ) );
break;
case VLC_VAR_FLOAT:
config_PutFloat( p_this, psz_name,
luaL_checknumber( L, 2 ) );
break;
default:
return vlclua_error( L );
}
return 0;
}
/*****************************************************************************
* intf.c: Generic lua inteface functions
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Antoine Cellerier <dionoea at videolan tod org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <vlc/vlc.h>
#include <vlc_meta.h>
#include <vlc_charset.h>
#include <vlc_interface.h>
#include <vlc_playlist.h>
#include <vlc_aout.h>
#include <vlc_vout.h>
#include <vlc_osd.h>
#include <lua.h> /* Low level lua C API */
#include <lauxlib.h> /* Higher level C API */
#include <lualib.h> /* Lua libs */
#include "vlc.h"
struct intf_sys_t
{
char *psz_filename;
lua_State *L;
};
/*****************************************************************************
* Internal lua<->vlc utils
*****************************************************************************/
static inline playlist_t *vlclua_get_playlist_internal( lua_State *L )
{
vlc_object_t *p_this = vlclua_get_this( L );
return pl_Yield( p_this );
}
static input_thread_t * vlclua_get_input_internal( lua_State *L )
{
playlist_t *p_playlist = vlclua_get_playlist_internal( L );
input_thread_t *p_input = p_playlist->p_input;
if( p_input ) vlc_object_yield( p_input );
vlc_object_release( p_playlist );
return p_input;
}
/* FIXME: This is high level logic. Should be implemented in lua */
#define vlclua_var_toggle_or_set(a,b,c) \
__vlclua_var_toggle_or_set(a,VLC_OBJECT(b),c)
static int __vlclua_var_toggle_or_set( lua_State *L, vlc_object_t *p_obj,
const char *psz_name )
{
vlc_bool_t b_bool;
if( lua_gettop( L ) > 1 ) return vlclua_error( L );
if( lua_gettop( L ) == 0 )
b_bool = !var_GetBool( p_obj, psz_name );
else /* lua_gettop( L ) == 1 */
{
b_bool = luaL_checkboolean( L, -1 )?VLC_TRUE:VLC_FALSE;
lua_pop( L, 1 );
}
if( b_bool != var_GetBool( p_obj, psz_name ) )
var_SetBool( p_obj, psz_name, b_bool );
lua_pushboolean( L, b_bool );
return 1;
}
/*****************************************************************************
* Libvlc TODO: move to vlc.c
*****************************************************************************/
static int vlclua_get_libvlc( lua_State *L )
{
vlclua_push_vlc_object( L, vlclua_get_this( L )->p_libvlc,
NULL );
return 1;
}
/*****************************************************************************
* Input handling
*****************************************************************************/
static int vlclua_get_input( lua_State *L )
{
input_thread_t *p_input = vlclua_get_input_internal( L );
if( p_input )
{
vlclua_push_vlc_object( L, p_input, vlclua_gc_release );
}
else lua_pushnil( L );
return 1;
}
static int vlclua_input_info( lua_State *L )
{
input_thread_t * p_input = vlclua_get_input_internal( L );
int i_cat;
int i;
if( !p_input ) return vlclua_error( L );
vlc_mutex_lock( &input_GetItem(p_input)->lock );
i_cat = input_GetItem(p_input)->i_categories;
lua_createtable( L, 0, i_cat );
for( i = 0; i < i_cat; i++ )
{
info_category_t *p_category = input_GetItem(p_input)->pp_categories[i];
int i_infos = p_category->i_infos;
int j;
lua_pushstring( L, p_category->psz_name );
lua_createtable( L, 0, i_infos );
for( j = 0; j < i_infos; j++ )
{
info_t *p_info = p_category->pp_infos[j];
lua_pushstring( L, p_info->psz_name );
lua_pushstring( L, p_info->psz_value );
lua_settable( L, -3 );
}
lua_settable( L, -3 );
}
vlc_object_release( p_input );
return 1;
}
static int vlclua_is_playing( lua_State *L )
{
input_thread_t * p_input = vlclua_get_input_internal( L );
lua_pushboolean( L, !!p_input );
return 1;
}
static int vlclua_get_title( lua_State *L )
{
input_thread_t *p_input = vlclua_get_input_internal( L );
if( !p_input )
lua_pushnil( L );
else
{
lua_pushstring( L, input_GetItem(p_input)->psz_name );
vlc_object_release( p_input );
}
return 1;
}
/*****************************************************************************
* Vout control
*****************************************************************************/
static int vlclua_fullscreen( lua_State *L )
{
vout_thread_t *p_vout;
int i_ret;
input_thread_t * p_input = vlclua_get_input_internal( L );
if( !p_input ) return vlclua_error( L );
p_vout = vlc_object_find( p_input, VLC_OBJECT_VOUT, FIND_CHILD );
if( !p_vout ) return vlclua_error( L );
i_ret = vlclua_var_toggle_or_set( L, p_vout, "fullscreen" );
vlc_object_release( p_vout );
vlc_object_release( p_input );
return i_ret;
}
static int vlc_osd_icon_from_string( const char *psz_name )
{
static const struct
{
int i_icon;
const char *psz_name;
} pp_icons[] =
{ { OSD_PAUSE_ICON, "pause" },
{ OSD_PLAY_ICON, "play" },
{ OSD_SPEAKER_ICON, "speaker" },
{ OSD_MUTE_ICON, "mute" },
{ 0, NULL } };
int i;
for( i = 0; pp_icons[i].psz_name; i++ )
{
if( !strcmp( psz_name, pp_icons[i].psz_name ) )
return pp_icons[i].i_icon;
}
return 0;
}
static int vlclua_osd_icon( lua_State *L )
{
const char *psz_icon = luaL_checkstring( L, 1 );
int i_icon = vlc_osd_icon_from_string( psz_icon );
int i_chan = luaL_optint( L, 2, DEFAULT_CHAN );
if( !i_icon )
return luaL_error( L, "\"%s\" is not a valid osd icon.", psz_icon );
else
{
vlc_object_t *p_this = vlclua_get_this( L );
vout_OSDIcon( p_this, i_chan, i_icon );
return 0;
}
}