[vlc-devel] [PATCH 03/10] Extensions: core library
Jean-Philippe André
jpeg at videolan.org
Thu Nov 12 00:08:03 CET 2009
This contains:
- Extensions manager
- Extensions activation / deactivation functions
- Separate threads for each extension
- Lua specific functions for calling the script
---
modules/misc/lua/extension.c | 773 +++++++++++++++++++++++++++++++++++
modules/misc/lua/extension.h | 120 ++++++
modules/misc/lua/extension_thread.c | 387 ++++++++++++++++++
3 files changed, 1280 insertions(+), 0 deletions(-)
create mode 100644 modules/misc/lua/extension.c
create mode 100644 modules/misc/lua/extension.h
create mode 100644 modules/misc/lua/extension_thread.c
diff --git a/modules/misc/lua/extension.c b/modules/misc/lua/extension.c
new file mode 100644
index 0000000..5238199
--- /dev/null
+++ b/modules/misc/lua/extension.c
@@ -0,0 +1,773 @@
+/*****************************************************************************
+ * extension.c: Lua Extensions (meta data, web information, ...)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.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.
+ *****************************************************************************/
+
+#include "vlc.h"
+#include "libs.h"
+#include "extension.h"
+#include "assert.h"
+
+/* Functions to register */
+static const luaL_Reg p_reg[] =
+{
+ { NULL, NULL }
+};
+
+/*
+ * Extensions capabilities
+ * Note: #define and ppsz_capabilities must be in sync
+ */
+#define EXT_HAS_MENU (1 << 0)
+#define EXT_TRIGGER_ONLY (1 << 1)
+
+const char* const ppsz_capabilities[] = {
+ "menu",
+ "trigger",
+ NULL
+};
+
+static int ScanExtensions( extensions_manager_t *p_this );
+static int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
+ lua_State *L, void *pb_continue );
+static int Control( extensions_manager_t *, int, va_list );
+static int GetMenu( extensions_manager_t *p_mgr, extension_t *p_ext,
+ char ***pppsz_titles, uint16_t **ppi_ids );
+static lua_State* GetLuaState( extensions_manager_t *p_mgr,
+ extension_t *p_ext );
+static int TriggerMenu( extension_t *p_ext, int id );
+static int TriggerExtension( extensions_manager_t *p_mgr,
+ extension_t *p_ext );
+
+int vlclua_extension_deactivate( lua_State *L );
+
+
+/**
+ * Module entry-point
+ **/
+int Open_Extension( vlc_object_t *p_this )
+{
+ msg_Dbg( p_this, "Opening EXPERIMENTAL Lua Extension module" );
+
+ extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+
+ p_mgr->pf_control = Control;
+
+ extensions_manager_sys_t *p_sys = ( extensions_manager_sys_t* )
+ calloc( 1, sizeof( extensions_manager_sys_t ) );
+ if( !p_sys ) return VLC_ENOMEM;
+
+ p_mgr->p_sys = p_sys;
+ p_sys->pp_activated_extensions = NULL;
+ p_sys->i_num_activated = 0;
+ p_sys->p_dialog_provider = NULL;
+ vlc_mutex_init( &p_sys->lock );
+
+ p_mgr->i_num_extensions = 0;
+ p_mgr->pp_extensions = NULL;
+
+ /* Initialise Lua state structure */
+ lua_State *L = GetLuaState( p_mgr, NULL );
+ if( !L )
+ {
+ free( p_sys );
+ return VLC_EGENERIC;
+ }
+ p_sys->L = L;
+
+ /* Scan available Lua Extensions */
+ if( ScanExtensions( p_mgr ) != VLC_SUCCESS )
+ {
+ msg_Err( p_mgr, "Can't load extensions modules" );
+ return VLC_EGENERIC;
+ }
+
+ lua_close( L );
+ p_sys->L = NULL;
+
+ return VLC_SUCCESS;
+}
+
+/**
+ * Module unload function
+ **/
+void Close_Extension( vlc_object_t *p_this )
+{
+ extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+ msg_Dbg( p_mgr, "Deactivating all loaded extensions" );
+
+ vlc_mutex_lock( &p_mgr->p_sys->lock );
+ p_mgr->p_sys->b_killed = true;
+
+ /* Reverse walk so that memmove is a no-op */
+ for( int i = p_mgr->p_sys->i_num_activated - 1; i >= 0; i-- )
+ {
+ extension_t *p_ext = p_mgr->p_sys->pp_activated_extensions[i];
+ if( !p_ext ) break;
+ Deactivate( p_mgr, p_ext );
+ WaitForDeactivation( p_ext );
+ }
+
+ /* Release dialog provider, if any */
+ if( p_mgr->p_sys->p_dialog_provider )
+ vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+
+ free( p_mgr->p_sys->pp_activated_extensions );
+ if( p_mgr->p_sys && p_mgr->p_sys->L )
+ lua_close( p_mgr->p_sys->L );
+
+ p_mgr->p_sys->pp_activated_extensions = NULL;
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+ // FIXME: this looks bad
+ vlc_mutex_destroy( &p_mgr->p_sys->lock );
+ free( p_mgr->p_sys );
+ p_mgr->p_sys = NULL;
+
+ /* Free extensions' memory */
+ for( int j = 0; j < p_mgr->i_num_extensions; ++j )
+ {
+ extension_t *p_ext = p_mgr->pp_extensions[j];
+ if( !p_ext ) break;
+ if( p_ext->p_sys->L )
+ lua_close( p_ext->p_sys->L );
+ free( p_ext->psz_name );
+ free( p_ext->psz_title );
+
+ vlc_mutex_destroy( &p_ext->p_sys->running_lock );
+ vlc_mutex_destroy( &p_ext->p_sys->command_lock );
+ vlc_cond_destroy( &p_ext->p_sys->wait );
+
+ free( p_ext->p_sys );
+ free( p_ext );
+ }
+ free( p_mgr->pp_extensions );
+ p_mgr->pp_extensions = NULL;
+}
+
+/**
+ * Batch scan all Lua files in folder "extensions"
+ * @param p_mgr This extensions_manager_t object
+ **/
+static int ScanExtensions( extensions_manager_t *p_mgr )
+{
+ bool b_true = true;
+ int i_ret =
+ vlclua_scripts_batch_execute( VLC_OBJECT( p_mgr ),
+ "extensions",
+ &ScanLuaCallback,
+ p_mgr->p_sys->L, &b_true );
+
+ if( !i_ret )
+ return VLC_EGENERIC;
+
+ return VLC_SUCCESS;
+}
+
+/**
+ * Batch scan all Lua files in folder "extensions": callback
+ * @param p_this This extensions_manager_t object
+ * @param psz_script Name of the script to run
+ * @param L Lua State, common to all scripts here
+ * @param pb_continue bool* that indicates whether to continue batch or not
+ **/
+int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
+ lua_State *L, void *pb_continue )
+{
+ extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
+ bool b_ok = false;
+
+ msg_Dbg( p_mgr, "Scanning Lua script %s", psz_script );
+
+ vlc_mutex_lock( &p_mgr->p_sys->lock );
+
+ /* Add new script descriptor */
+ extension_t **tmp = realloc( p_mgr->pp_extensions,
+ sizeof( extension_t* ) * (p_mgr->i_num_extensions + 1) );
+ if( !tmp )
+ {
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return 0; /* Stop batch */
+ }
+
+ p_mgr->pp_extensions = tmp;
+ p_mgr->i_num_extensions++;
+
+ extension_t *p_ext = tmp[ p_mgr->i_num_extensions - 1 ] =
+ ( extension_t* ) calloc( 1, sizeof( extension_t ) );
+ if( !p_ext )
+ {
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return 0;
+ }
+
+ p_ext->psz_name = strdup( psz_script );
+ p_ext->p_sys = ( extension_sys_t* ) calloc( 1, sizeof( extension_sys_t ) );
+ if( !p_ext->p_sys )
+ {
+ p_mgr->i_num_extensions--; /* Note: leak */
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return VLC_ENOMEM;
+ }
+ p_ext->p_sys->p_mgr = p_mgr;
+
+ /* Mutexes and conditions */
+ vlc_mutex_init( &p_ext->p_sys->command_lock );
+ vlc_mutex_init( &p_ext->p_sys->running_lock );
+ vlc_cond_init( &p_ext->p_sys->wait );
+
+ /* Load and run the script(s) */
+ if( luaL_dofile( L, psz_script ) )
+ {
+ msg_Warn( p_mgr, "Error loading script %s: %s", psz_script,
+ lua_tostring( L, lua_gettop( L ) ) );
+ lua_pop( L, 1 );
+ goto exit;
+ }
+
+ /* Scan script for capabilities */
+ lua_getglobal( L, "descriptor" );
+
+ if( !lua_isfunction( L, -1 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function descriptor() not found", psz_script );
+ goto exit;
+ }
+
+ if( lua_pcall( L, 0, 1, 0 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function descriptor(): %s", psz_script,
+ lua_tostring( L, lua_gettop( L ) ) );
+ goto exit;
+ }
+
+ if( lua_gettop( L ) )
+ {
+ if( lua_istable( L, -1 ) )
+ {
+ lua_getfield( L, -1, "capabilities" );
+ if( lua_istable( L, -1 ) )
+ {
+ lua_pushnil( L );
+ while( lua_next( L, -2 ) != 0 )
+ {
+ /* Key is at index -2 and value at index -1. Discard key */
+ const char *psz_cap = luaL_checkstring( L, -1 );
+ int i_cap = 0;
+ bool b_ok = false;
+ /* Find this capability's flag */
+ for( const char *iter = *ppsz_capabilities;
+ iter != NULL;
+ iter = ppsz_capabilities[ ++i_cap ])
+ {
+ if( !strcmp( iter, psz_cap ) )
+ {
+ /* Flag it! */
+ p_ext->p_sys->i_capabilities |= 1 << i_cap;
+ b_ok = true;
+ break;
+ }
+ }
+ if( !b_ok )
+ {
+ msg_Warn( p_mgr, "Extension capability '%s' unknown in"
+ " script %s", psz_cap, psz_script );
+ }
+ /* Removes 'value'; keeps 'key' for next iteration */
+ lua_pop( L, 1 );
+ }
+ }
+ else
+ {
+ msg_Warn( p_mgr, "In script %s, function descriptor() "
+ "did not return a table of capabilities.",
+ psz_script );
+ }
+
+ lua_pop( L, 1 );
+ lua_getfield( L, -1, "title" );
+ if( lua_isstring( L, -1 ) )
+ {
+ p_ext->psz_title = strdup( luaL_checkstring( L, -1 ) );
+ }
+ else
+ {
+ msg_Dbg( p_mgr, "In script %s, function descriptor() "
+ "did not return a string as title.",
+ psz_script );
+ p_ext->psz_title = strdup( psz_script );
+ }
+ }
+ else
+ {
+ msg_Warn( p_mgr, "In script %s, function descriptor() "
+ "did not return a table!", psz_script );
+ goto exit;
+ }
+ }
+ else
+ {
+ msg_Err( p_mgr, "Script %s went completely foobar", psz_script );
+ goto exit;
+ }
+
+ msg_Dbg( p_mgr, "Script %s has the following capability flags: 0x%x",
+ psz_script, p_ext->p_sys->i_capabilities );
+
+ b_ok = true;
+exit:
+ if( !b_ok )
+ p_mgr->i_num_extensions--; /* Note: leak */
+
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ /* Continue batch execution */
+ return pb_continue ? ( (* (bool*)pb_continue) ? -1 : 0 ) : -1;
+}
+
+static int Control( extensions_manager_t *p_mgr, int i_control, va_list args )
+{
+ vlc_object_t *p_object = NULL, **pp_object = NULL;
+ extension_t *p_ext = NULL;
+ bool *pb = NULL;
+ uint16_t **ppus = NULL;
+ char ***pppsz = NULL;
+ int i = 0;
+
+ switch( i_control )
+ {
+ case EXTENSION_ACTIVATE:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ return Activate( p_mgr, p_ext );
+
+ case EXTENSION_DEACTIVATE:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ return Deactivate( p_mgr, p_ext );
+
+ case EXTENSION_IS_ACTIVATED:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ pb = ( bool* ) va_arg( args, bool* );
+ *pb = IsActivated( p_mgr, p_ext );
+ break;
+
+ case EXTENSION_HAS_MENU:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ pb = ( bool* ) va_arg( args, bool* );
+ *pb = ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) ? 1 : 0;
+ break;
+
+ case EXTENSION_GET_MENU:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ pppsz = ( char*** ) va_arg( args, char*** );
+ ppus = ( uint16_t** ) va_arg( args, uint16_t** );
+ return GetMenu( p_mgr, p_ext, pppsz, ppus );
+
+ case EXTENSION_TRIGGER_ONLY:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ pb = ( bool* ) va_arg( args, bool* );
+ *pb = ( p_ext->p_sys->i_capabilities & EXT_TRIGGER_ONLY ) ? 1 : 0;
+ break;
+
+ case EXTENSION_TRIGGER:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ return TriggerExtension( p_mgr, p_ext );
+
+ case EXTENSION_TRIGGER_MENU:
+ p_ext = ( extension_t* ) va_arg( args, extension_t* );
+ // GCC: 'uint16_t' is promoted to 'int' when passed through '...'
+ i = ( int ) va_arg( args, int );
+ return TriggerMenu( p_ext, i );
+
+ /* Dialogs */
+
+ case EXTENSION_SET_DIALOG_PROVIDER:
+ p_object = ( vlc_object_t* ) va_arg( args, vlc_object_t* );
+ if( p_object )
+ {
+ if( p_mgr->p_sys->p_dialog_provider )
+ vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+ vlc_object_hold( p_mgr->p_sys->p_dialog_provider = p_object );
+ }
+ else if( p_mgr->p_sys->p_dialog_provider )
+ {
+ vlc_object_release( p_mgr->p_sys->p_dialog_provider );
+ p_mgr->p_sys->p_dialog_provider = NULL;
+ }
+ break;
+
+ case EXTENSION_GET_DIALOG_PROVIDER:
+ pp_object = ( vlc_object_t** ) va_arg( args, vlc_object_t** );
+ if( !pp_object )
+ return VLC_EGENERIC;
+ *pp_object = p_mgr->p_sys->p_dialog_provider;
+ if( p_mgr->p_sys->p_dialog_provider )
+ vlc_object_hold( p_mgr->p_sys->p_dialog_provider );
+ break;
+
+ default:
+ msg_Err( p_mgr, "Control '%d' not yet implemented in Extension",
+ i_control );
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+int lua_ExtensionActivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ assert( p_mgr != NULL && p_ext != NULL );
+ return lua_ExecuteFunction( p_mgr, p_ext, "activate" );
+}
+
+int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ assert( p_mgr != NULL && p_ext != NULL );
+
+ if( !p_ext->p_sys->L )
+ return VLC_SUCCESS;
+
+ int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate" );
+
+ /* Clear Lua State */
+ lua_close( p_ext->p_sys->L );
+ p_ext->p_sys->L = NULL;
+
+ return i_ret;
+}
+
+int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr,
+ extension_t *p_ext,
+ extension_widget_t *p_widget )
+{
+ if( !p_ext->p_sys->L )
+ return VLC_SUCCESS;
+
+ widget_sys_t *p_sys = p_widget->p_sys;
+
+ return lua_ExecuteFunction( p_mgr, p_ext, p_sys->psz_function );
+}
+
+
+/**
+ * Get the list of menu entries from an extension script
+ * @param p_mgr
+ * @param p_ext
+ * @param pppsz_titles Pointer to NULL. All strings must be freed by the caller
+ * @param ppi_ids Pointer to NULL. Must be feed by the caller.
+ * @note This function is allowed to run in the UI thread. This means
+ * that it MUST respond very fast. FIXME: forbid access to libs
+ **/
+static int GetMenu( extensions_manager_t *p_mgr, extension_t *p_ext,
+ char ***pppsz_titles, uint16_t **ppi_ids )
+{
+ assert( *pppsz_titles == NULL );
+ assert( *ppi_ids == NULL );
+
+ if( !IsActivated( p_mgr, p_ext ) )
+ {
+ msg_Dbg( p_mgr, "Can't get menu before activating the extension!" );
+ return VLC_EGENERIC;
+ }
+
+ if( !LockExtension( p_ext ) )
+ {
+ /* Dying extension, fail. */
+ return VLC_EGENERIC;
+ }
+
+ int i_ret = VLC_EGENERIC;
+ lua_State *L = GetLuaState( p_mgr, p_ext );
+
+ if( ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) == 0 )
+ {
+ msg_Dbg( p_mgr, "can't get a menu from an extension without menu!" );
+ goto exit;
+ }
+
+ lua_getglobal( L, "menu" );
+
+ if( !lua_isfunction( L, -1 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function menu() not found", p_ext->psz_name );
+ goto exit;
+ }
+
+ if( lua_pcall( L, 0, 1, 0 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function menu(): %s", p_ext->psz_name,
+ lua_tostring( L, lua_gettop( L ) ) );
+ goto exit;
+ }
+
+ if( lua_gettop( L ) )
+ {
+ if( lua_istable( L, -1 ) )
+ {
+ /* Get table size */
+ size_t i_size = lua_objlen( L, -1 );
+ *pppsz_titles = ( char** ) calloc( i_size+1, sizeof( char* ) );
+ *ppi_ids = ( uint16_t* ) calloc( i_size+1, sizeof( uint16_t ) );
+
+ /* Walk table */
+ size_t i_idx = 0;
+ lua_pushnil( L );
+ while( lua_next( L, -2 ) != 0 )
+ {
+ assert( i_idx < i_size );
+ if( (!lua_isstring( L, -1 )) || (!lua_isnumber( L, -2 )) )
+ {
+ msg_Warn( p_mgr, "In script %s, an entry in "
+ "the menu table is invalid!", p_ext->psz_name );
+ goto exit;
+ }
+ (*pppsz_titles)[ i_idx ] = strdup( luaL_checkstring( L, -1 ) );
+ (*ppi_ids)[ i_idx ] = (uint16_t) ( luaL_checkinteger( L, -2 ) & 0xFFFF );
+ i_idx++;
+ lua_pop( L, 1 );
+ }
+ }
+ else
+ {
+ msg_Warn( p_mgr, "Function menu() in script %s "
+ "did not return a table", p_ext->psz_name );
+ goto exit;
+ }
+ }
+ else
+ {
+ msg_Warn( p_mgr, "Script %s went completely foobar", p_ext->psz_name );
+ goto exit;
+ }
+
+ i_ret = VLC_SUCCESS;
+
+exit:
+ UnlockExtension( p_ext );
+ if( i_ret != VLC_SUCCESS )
+ {
+ msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
+ __func__, __FILE__, __LINE__ );
+ }
+ return i_ret;
+}
+
+/* Must be entered with the Lock on Extension */
+static lua_State* GetLuaState( extensions_manager_t *p_mgr,
+ extension_t *p_ext )
+{
+ lua_State *L = NULL;
+ if( p_ext )
+ L = p_ext->p_sys->L;
+
+ if( !L )
+ {
+ L = luaL_newstate();
+ if( !L )
+ {
+ msg_Err( p_mgr, "Could not create new Lua State" );
+ return NULL;
+ }
+ luaL_openlibs( L );
+ luaL_register( L, "vlc", p_reg );
+ luaopen_msg( L );
+
+ lua_pushlightuserdata( L, p_mgr );
+ lua_setfield( L, -2, "private" );
+
+ lua_pushlightuserdata( L, p_ext );
+ lua_setfield( L, -2, "extension" );
+
+ if( p_ext )
+ {
+ /* Load more libraries */
+ luaopen_acl( L );
+ luaopen_config( L );
+ luaopen_dialog( L );
+ luaopen_input( L );
+ luaopen_msg( L );
+ luaopen_misc( L );
+ luaopen_net( L );
+ luaopen_object( L );
+ luaopen_osd( L );
+ luaopen_playlist( L );
+ luaopen_sd( L );
+ luaopen_stream( L );
+ luaopen_strings( L );
+ luaopen_variables( L );
+ luaopen_video( L );
+ luaopen_vlm( L );
+ luaopen_volume( L );
+
+ /* Register extension specific functions */
+ lua_getglobal( L, "vlc" );
+ lua_pushcfunction( L, vlclua_extension_deactivate );
+ lua_setfield( L, -2, "deactivate" );
+
+ /* Load and run the script(s) */
+ if( luaL_dofile( L, p_ext->psz_name ) != 0 )
+ {
+ msg_Warn( p_mgr, "Error loading script %s: %s", p_ext->psz_name,
+ lua_tostring( L, lua_gettop( L ) ) );
+ lua_pop( L, 1 );
+ return NULL;
+ }
+
+ p_ext->p_sys->L = L;
+ }
+ }
+#ifndef NDEBUG
+ else
+ {
+ msg_Dbg( p_mgr, "Reusing old Lua state for extension '%s'",
+ p_ext->psz_name );
+ }
+#endif
+
+ return L;
+}
+
+/**
+ * Execute a function in a Lua script
+ * @return < 0 in case of failure, >= 0 in case of success
+ * @note It's better to call this function from a dedicated thread
+ * (see extension_thread.c)
+ **/
+int lua_ExecuteFunction( extensions_manager_t *p_mgr,
+ extension_t *p_ext,
+ const char *psz_function )
+{
+ int i_ret = VLC_EGENERIC;
+ assert( p_mgr != NULL );
+ assert( p_ext != NULL );
+
+ lua_State *L = GetLuaState( p_mgr, p_ext );
+ lua_getglobal( L, psz_function );
+
+ if( !lua_isfunction( L, -1 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function %s() not found", p_ext->psz_name, psz_function );
+ goto exit;
+ }
+
+ if( lua_pcall( L, 0, 1, 0 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function %s(): %s", p_ext->psz_name, psz_function,
+ lua_tostring( L, lua_gettop( L ) ) );
+ goto exit;
+ }
+
+ i_ret = VLC_SUCCESS;
+exit:
+ return i_ret;
+}
+
+static inline int TriggerMenu( extension_t *p_ext, int i_id )
+{
+ return PushCommand( p_ext, CMD_TRIGGERMENU, i_id );
+}
+
+int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr,
+ extension_t *p_ext, int id )
+{
+ int i_ret = VLC_EGENERIC;
+ lua_State *L = GetLuaState( p_mgr, p_ext );
+
+ if( !L )
+ return VLC_EGENERIC;
+
+ luaopen_dialog( L );
+
+ lua_getglobal( L, "trigger_menu" );
+ if( !lua_isfunction( L, -1 ) )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function trigger_menu() not found", p_ext->psz_name );
+ return VLC_EGENERIC;
+ }
+
+ /* Pass id as unique argument to the function */
+ lua_pushinteger( L, id );
+
+ if( lua_pcall( L, 1, 1, 0 ) != 0 )
+ {
+ msg_Warn( p_mgr, "Error while runing script %s, "
+ "function trigger_menu(): %s", p_ext->psz_name,
+ lua_tostring( L, lua_gettop( L ) ) );
+ return VLC_EGENERIC;
+ }
+
+ if( i_ret < VLC_SUCCESS )
+ {
+ msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
+ __func__, __FILE__, __LINE__ );
+ }
+ return i_ret;
+}
+
+/** Directly trigger an extension, without activating it
+ * This is NOT multithreaded, and this code runs in the UI thread
+ * @param p_mgr
+ * @param p_ext Extension to trigger
+ * @return Value returned by the lua function "trigger"
+ **/
+static int TriggerExtension( extensions_manager_t *p_mgr,
+ extension_t *p_ext )
+{
+ int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "trigger" );
+
+ /* Close lua state for trigger-only extensions */
+ if( p_ext->p_sys->L )
+ lua_close( p_ext->p_sys->L );
+ p_ext->p_sys->L = NULL;
+
+ return i_ret;
+}
+
+/** Retrieve extension associated to the current script
+ * @param L current lua_State
+ * @return Lua userdata "vlc.extension"
+ **/
+extension_t *vlclua_extension_get( lua_State *L )
+{
+ extension_t *p_ext = NULL;
+ lua_getglobal( L, "vlc" );
+ lua_getfield( L, -1, "extension" );
+ p_ext = (extension_t*) lua_topointer( L, lua_gettop( L ) );
+ lua_pop( L, 2 );
+ return p_ext;
+}
+
+/** Deactivate an extension by order from the extension itself
+ * @param L lua_State
+ * @note This is an asynchronous call. A script calling vlc.deactivate() will
+ * be executed to the end before the last call to deactivate() is done.
+ **/
+int vlclua_extension_deactivate( lua_State *L )
+{
+ extension_t *p_ext = vlclua_extension_get( L );
+ int i_ret = Deactivate( p_ext->p_sys->p_mgr, p_ext );
+ return ( i_ret == VLC_SUCCESS ) ? 1 : 0;
+}
diff --git a/modules/misc/lua/extension.h b/modules/misc/lua/extension.h
new file mode 100644
index 0000000..395aeb7
--- /dev/null
+++ b/modules/misc/lua/extension.h
@@ -0,0 +1,120 @@
+/*****************************************************************************
+ * extension.h: Lua Extensions (meta data, web information, ...)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.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.
+ *****************************************************************************/
+
+#ifndef LUA_EXTENSION_H
+#define LUA_EXTENSION_H
+
+#include <vlc_extensions.h>
+
+///< Array of extension_t
+TYPEDEF_ARRAY( extension_t, array_extension_t );
+
+/* List of available commands */
+#define CMD_ACTIVATE 1
+#define CMD_DEACTIVATE 2
+#define CMD_TRIGGERMENU 3 /* Arg1 = int*, pointing to id to trigger. free */
+#define CMD_CLICK 4 /* Arg1 = extension_widget_t* */
+#define CMD_CLOSE 5
+
+struct extensions_manager_sys_t
+{
+ /* List of activated extensions */
+ int i_num_activated;
+ extension_t **pp_activated_extensions;
+
+ /* Lua specific */
+ lua_State *L;
+
+ /* Communication with the UI */
+ vlc_object_t *p_dialog_provider;
+
+ vlc_mutex_t lock;
+ bool b_killed;
+};
+
+struct extension_sys_t
+{
+ /* Extension general */
+ int i_capabilities;
+
+ /* Lua specific */
+ lua_State *L;
+
+ /* Thread data */
+ vlc_thread_t thread;
+ vlc_mutex_t command_lock;
+ vlc_mutex_t running_lock;
+ vlc_cond_t wait;
+ bool b_exiting;
+ extensions_manager_t *p_mgr; ///< Parent
+ /* Queue of commands to execute */
+ struct command_t
+ {
+ int i_command;
+ void *data[10]; ///< Optional void* arguments
+ struct command_t *next; ///< Next command
+ } *command;
+};
+
+typedef struct
+{
+ char *psz_function; ///< Function to call in case of click
+} widget_sys_t;
+
+/* Extensions: manager functions */
+int Activate( extensions_manager_t *p_mgr, extension_t * );
+bool IsActivated( extensions_manager_t *p_mgr, extension_t * );
+int Deactivate( extensions_manager_t *p_mgr, extension_t * );
+void WaitForDeactivation( extension_t *p_ext );
+int PushCommand( extension_t *p_ext, int i_command, ... );
+
+/* Lua specific functions */
+extension_t *vlclua_extension_get( lua_State *L );
+int lua_ExtensionActivate( extensions_manager_t *, extension_t * );
+int lua_ExtensionDeactivate( extensions_manager_t *, extension_t * );
+int lua_ExecuteFunction( extensions_manager_t *p_mgr, extension_t *p_ext,
+ const char *psz_function );
+int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr,
+ extension_t *p_ext,
+ extension_widget_t *p_widget );
+int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr,
+ extension_t *p_ext, int id );
+
+/* Lock this extension. Can fail. */
+static inline bool LockExtension( extension_t *p_ext )
+{
+ vlc_mutex_lock( &p_ext->p_sys->running_lock );
+ if( p_ext->p_sys->b_exiting )
+ {
+ vlc_mutex_unlock( &p_ext->p_sys->running_lock );
+ return false;
+ }
+ return true;
+}
+
+static inline void UnlockExtension( extension_t *p_ext )
+{
+ vlc_mutex_unlock( &p_ext->p_sys->running_lock );
+}
+
+#endif // LUA_EXTENSION_H
diff --git a/modules/misc/lua/extension_thread.c b/modules/misc/lua/extension_thread.c
new file mode 100644
index 0000000..7f04a5f
--- /dev/null
+++ b/modules/misc/lua/extension_thread.c
@@ -0,0 +1,387 @@
+/*****************************************************************************
+ * extension_thread.c: Extensions Manager, Threads manager (no Lua here)
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Jean-Philippe André < jpeg # videolan.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.
+ *****************************************************************************/
+
+/* I don't want to include lua headers here */
+typedef struct lua_State lua_State;
+
+#include "extension.h"
+#include "assert.h"
+
+struct thread_sys_t
+{
+ extensions_manager_t *p_mgr;
+ extension_t *p_ext;
+};
+
+/** Thread Run */
+static void* Run( void *data );
+static void FreeCommands( struct command_t *command );
+static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext );
+
+/**
+ * Activate an extension
+ * @param p_mgr This manager
+ * @param p_ext Extension to activate
+ * @return The usual VLC return codes
+ **/
+int Activate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ assert( p_ext != NULL );
+
+ struct extension_sys_t *p_sys = p_ext->p_sys;
+ assert( p_sys != NULL );
+
+ msg_Dbg( p_mgr, "Activating extension '%s'", p_ext->psz_title );
+
+ if( IsActivated( p_mgr, p_ext ) )
+ {
+ msg_Warn( p_mgr, "Extension is already activated!" );
+ return VLC_EGENERIC;
+ }
+
+ /* Add this script to the activated extensions list */
+ if( !p_mgr->p_sys->pp_activated_extensions )
+ {
+ p_mgr->p_sys->pp_activated_extensions =
+ malloc( sizeof( extension_t* ) );
+ p_mgr->p_sys->pp_activated_extensions[0] = p_ext;
+ p_mgr->p_sys->i_num_activated = 1;
+ }
+ else
+ {
+ int i_num = p_mgr->p_sys->i_num_activated + 1;
+ extension_t **tmp = ( extension_t** )
+ realloc( p_mgr->p_sys->pp_activated_extensions,
+ sizeof( extension_t* ) * i_num );
+ if( !tmp )
+ return VLC_ENOMEM;
+ tmp[ p_mgr->p_sys->i_num_activated ] = p_ext;
+ p_mgr->p_sys->pp_activated_extensions = tmp;
+ p_mgr->p_sys->i_num_activated = i_num;
+ }
+
+ /* Prepare first command */
+ p_sys->command = calloc( 1, sizeof( struct command_t ) );
+ if( !p_sys->command )
+ return VLC_ENOMEM;
+ p_sys->command->i_command = CMD_ACTIVATE; /* No params */
+
+ /* Start thread */
+ p_sys->b_exiting = false;
+
+ if( vlc_clone( &p_sys->thread, Run, p_ext, VLC_THREAD_PRIORITY_LOW )
+ != VLC_SUCCESS )
+ {
+ p_sys->b_exiting = true;
+ // Note: Automatically deactivating the extension...
+ Deactivate( p_mgr, p_ext );
+ return VLC_ENOMEM;
+ }
+
+ return VLC_SUCCESS;
+}
+
+/** Look for an extension in the activated extensions list */
+bool IsActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ vlc_mutex_lock( &p_mgr->p_sys->lock );
+ if( !p_mgr->p_sys->pp_activated_extensions )
+ {
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return false;
+ }
+ for( int i = 0; i < p_mgr->p_sys->i_num_activated; i++ )
+ {
+ extension_t *p_iter = p_mgr->p_sys->pp_activated_extensions[i];
+ if( !p_iter )
+ break;
+ if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
+ {
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return true;
+ }
+ }
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+ return false;
+}
+
+/** Recursively drop and free commands starting from "command" */
+static void FreeCommands( struct command_t *command )
+{
+ if( !command ) return;
+ struct command_t *next = command->next;
+ switch( command->i_command )
+ {
+ case CMD_ACTIVATE:
+ case CMD_DEACTIVATE:
+ case CMD_CLICK: // Arg1 must not be freed
+ break;
+
+ case CMD_TRIGGERMENU:
+ free( command->data[0] ); // Arg1 is int*, to free
+ break;
+ }
+ free( command );
+ FreeCommands( next );
+}
+
+/** Deactivate this extension: pushes immediate command and drops queued */
+int Deactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ (void) p_mgr;
+ vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+ if( p_ext->p_sys->b_exiting )
+ {
+ vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+ return VLC_EGENERIC;
+ }
+
+ /* Free the list of commands */
+ FreeCommands( p_ext->p_sys->command );
+
+ /* Push command */
+ struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
+ cmd->i_command = CMD_DEACTIVATE;
+ p_ext->p_sys->command = cmd;
+
+ vlc_cond_signal( &p_ext->p_sys->wait );
+ vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+
+ return VLC_SUCCESS;
+}
+
+/** Remove an extension from the activated list */
+static int RemoveActivated( extensions_manager_t *p_mgr, extension_t *p_ext )
+{
+ if( p_mgr->p_sys->b_killed )
+ return VLC_SUCCESS;
+ vlc_mutex_lock( &p_mgr->p_sys->lock );
+ int i_idx = -1;
+ for( int i = 0; i < p_mgr->p_sys->i_num_activated; ++i )
+ {
+ extension_t *p_iter = p_mgr->p_sys->pp_activated_extensions[i];
+ if( !p_iter )
+ break;
+ if( !strcmp( p_iter->psz_name, p_ext->psz_name ) )
+ {
+ i_idx = i;
+ break;
+ }
+ }
+ if( i_idx < 0 )
+ {
+ msg_Dbg( p_mgr, "Can't find extension '%s' in the activated list",
+ p_ext->psz_title );
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+ return VLC_EGENERIC;
+ }
+
+ /* Move memory. Do not realloc.
+ Yeah, it leaks a bit... until next extension is loaded. Who cares? */
+ memmove( &( p_mgr->p_sys->pp_activated_extensions[ i_idx ] ),
+ &( p_mgr->p_sys->pp_activated_extensions[ i_idx + 1 ] ),
+ p_mgr->p_sys->i_num_activated - i_idx - 1 );
+ p_mgr->p_sys->i_num_activated--;
+
+ if( !p_mgr->p_sys->i_num_activated )
+ {
+ free( p_mgr->p_sys->pp_activated_extensions );
+ p_mgr->p_sys->pp_activated_extensions = NULL;
+ }
+
+ vlc_mutex_unlock( &p_mgr->p_sys->lock );
+
+ return VLC_SUCCESS;
+}
+
+/** Wait for an extension to finish */
+void WaitForDeactivation( extension_t *p_ext )
+{
+ void *pointer = NULL;
+ vlc_cond_signal( &p_ext->p_sys->wait );
+ vlc_join( p_ext->p_sys->thread, &pointer );
+}
+
+/** Push a UI command */
+int PushCommand( extension_t *p_ext,
+ int i_command,
+ ... )
+{
+ vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+ va_list args;
+ va_start( args, i_command );
+
+ /* Create command */
+ struct command_t *cmd = calloc( 1, sizeof( struct command_t ) );
+ cmd->i_command = i_command;
+ switch( i_command )
+ {
+ case CMD_CLICK:
+ cmd->data[0] = va_arg( args, void* );
+ break;
+ case CMD_CLOSE:
+ // Nothing to do here
+ break;
+ case CMD_TRIGGERMENU:
+ {
+ int *pi = malloc( sizeof( int ) );
+ if( !pi ) return VLC_ENOMEM;
+ *pi = va_arg( args, int );
+ cmd->data[0] = pi;
+ }
+ break;
+ default:
+ msg_Dbg( p_ext->p_sys->p_mgr,
+ "Unknown command send to extension: %d", i_command );
+ break;
+ }
+
+ va_end( args );
+
+ /* Push command to the end of the queue */
+ struct command_t *last = p_ext->p_sys->command;
+ if( !last )
+ {
+ p_ext->p_sys->command = cmd;
+ }
+ else
+ {
+ while( last->next != NULL )
+ {
+ last = last->next;
+ }
+ last->next = cmd;
+ }
+
+ vlc_cond_signal( &p_ext->p_sys->wait );
+ vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+ return VLC_SUCCESS;
+}
+
+/* Thread loop */
+static void* Run( void *data )
+{
+ extension_t *p_ext = data;
+ extensions_manager_t *p_mgr = p_ext->p_sys->p_mgr;
+
+ vlc_mutex_lock( &p_ext->p_sys->command_lock );
+
+ while( !p_ext->p_sys->b_exiting )
+ {
+ /* Pop command in front */
+ struct command_t *cmd = p_ext->p_sys->command;
+ if( cmd )
+ {
+ p_ext->p_sys->command = cmd->next;
+ }
+
+ vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+
+ /* Run command */
+ if( cmd )
+ {
+ if( LockExtension( p_ext ) )
+ {
+ switch( cmd->i_command )
+ {
+ case CMD_ACTIVATE:
+ {
+ if( lua_ExecuteFunction( p_mgr, p_ext, "activate" ) < 0 )
+ {
+ msg_Dbg( p_mgr, "Could not activate extension!" );
+ Deactivate( p_mgr, p_ext );
+ }
+ break;
+ }
+
+ case CMD_DEACTIVATE:
+ {
+ msg_Dbg( p_mgr, "Deactivating '%s'", p_ext->psz_title );
+ if( lua_ExtensionDeactivate( p_mgr, p_ext ) < 0 )
+ {
+ msg_Warn( p_mgr, "Extension '%s' did not deactivate properly",
+ p_ext->psz_title );
+ }
+ p_ext->p_sys->b_exiting = true;
+ RemoveActivated( p_mgr, p_ext );
+ break;
+ }
+
+ case CMD_CLOSE:
+ {
+ lua_ExecuteFunction( p_mgr, p_ext, "close" );
+ break;
+ }
+
+ case CMD_CLICK:
+ {
+ extension_widget_t *p_widget = cmd->data[0];
+ assert( p_widget );
+ msg_Dbg( p_mgr, "Clicking '%s': '%s'",
+ p_ext->psz_name, p_widget->psz_text );
+ if( !lua_ExtensionWidgetClick( p_mgr, p_ext, p_widget )
+ < 0 )
+ {
+ msg_Warn( p_mgr, "Could not translate click" );
+ }
+ break;
+ }
+
+ case CMD_TRIGGERMENU:
+ {
+ int *pi_id = cmd->data[0];
+ assert( pi_id );
+ msg_Dbg( p_mgr, "Trigger menu %d of '%s'",
+ *pi_id, p_ext->psz_name );
+ lua_ExtensionTriggerMenu( p_mgr, p_ext, *pi_id );
+ free( pi_id );
+ break;
+ }
+
+ default:
+ {
+ msg_Dbg( p_mgr, "Unknown command in extension command queue: %d",
+ cmd->i_command );
+ break;
+ }
+ }
+ UnlockExtension( p_ext );
+ }
+ }
+
+ vlc_mutex_lock( &p_ext->p_sys->command_lock );
+ if( !p_ext->p_sys->b_exiting && !p_ext->p_sys->command )
+ {
+ vlc_cond_wait( &p_ext->p_sys->wait, &p_ext->p_sys->command_lock );
+ }
+ }
+
+ vlc_mutex_unlock( &p_ext->p_sys->command_lock );
+ msg_Dbg( p_mgr, "Extension thread ending..." );
+
+ // Note: At this point, the extension should be deactivated
+ return NULL;
+}
--
1.6.5.2
More information about the vlc-devel
mailing list