X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fmisc%2Flua%2Fextension.c;h=498f50c9641b0b7b8a10366422f6869f644f37cb;hb=e20a107d1aae63efe536c863e9e9d4692da86e45;hp=2e69f4c46dffd869aab63c408f8212f1bf4d2d06;hpb=9f49f1112e688ca366b6881172eedb604851f416;p=vlc diff --git a/modules/misc/lua/extension.c b/modules/misc/lua/extension.c index 2e69f4c46d..498f50c964 100644 --- a/modules/misc/lua/extension.c +++ b/modules/misc/lua/extension.c @@ -26,6 +26,9 @@ #include "extension.h" #include "assert.h" +#include +#include + /* Functions to register */ static const luaL_Reg p_reg[] = { @@ -36,18 +39,24 @@ static const luaL_Reg p_reg[] = * Extensions capabilities * Note: #define and ppsz_capabilities must be in sync */ -#define EXT_HAS_MENU (1 << 0) -#define EXT_TRIGGER_ONLY (1 << 1) +#define EXT_HAS_MENU (1 << 0) ///< Hook: menu +#define EXT_TRIGGER_ONLY (1 << 1) ///< Hook: trigger. Not activable +#define EXT_INPUT_LISTENER (1 << 2) ///< Hook: input_changed +#define EXT_META_LISTENER (1 << 3) ///< Hook: meta_changed +#define EXT_PLAYING_LISTENER (1 << 4) ///< Hook: status_changed const char* const ppsz_capabilities[] = { "menu", "trigger", + "input-listener", + "meta-listener", + "playing-listener", 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 ); + void *pb_continue ); static int Control( extensions_manager_t *, int, va_list ); static int GetMenuEntries( extensions_manager_t *p_mgr, extension_t *p_ext, char ***pppsz_titles, uint16_t **ppi_ids ); @@ -66,6 +75,10 @@ static int vlclua_extension_dialog_callback( vlc_object_t *p_this, vlc_value_t newval, void *p_data ); +/* Input item callback: vlc_InputItemMetaChanged */ +static void inputItemMetaChanged( const vlc_event_t *p_event, + void *data ); + /** * Module entry-point @@ -88,15 +101,6 @@ int Open_Extension( vlc_object_t *p_this ) vlc_mutex_init( &p_mgr->lock ); vlc_mutex_init( &p_mgr->p_sys->lock ); - /* 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 ) { @@ -104,9 +108,6 @@ int Open_Extension( vlc_object_t *p_this ) return VLC_EGENERIC; } - lua_close( L ); - p_sys->L = NULL; - // Create the dialog-event variable var_Create( p_this, "dialog-event", VLC_VAR_ADDRESS ); var_AddCallback( p_this, "dialog-event", @@ -133,6 +134,7 @@ void Close_Extension( vlc_object_t *p_this ) FOREACH_ARRAY( p_ext, p_mgr->p_sys->activated_extensions ) { if( !p_ext ) break; + msg_Dbg( p_mgr, "Deactivating '%s'", p_ext->psz_title ); Deactivate( p_mgr, p_ext ); WaitForDeactivation( p_ext ); } @@ -141,9 +143,6 @@ void Close_Extension( vlc_object_t *p_this ) msg_Dbg( p_mgr, "All extensions are now deactivated" ); ARRAY_RESET( p_mgr->p_sys->activated_extensions ); - if( p_mgr->p_sys && p_mgr->p_sys->L ) - lua_close( p_mgr->p_sys->L ); - vlc_mutex_destroy( &p_mgr->lock ); vlc_mutex_destroy( &p_mgr->p_sys->lock ); free( p_mgr->p_sys ); @@ -158,6 +157,11 @@ void Close_Extension( vlc_object_t *p_this ) lua_close( p_ext->p_sys->L ); free( p_ext->psz_name ); free( p_ext->psz_title ); + free( p_ext->psz_author ); + free( p_ext->psz_description ); + free( p_ext->psz_shortdescription ); + free( p_ext->psz_url ); + free( p_ext->psz_version ); vlc_mutex_destroy( &p_ext->p_sys->running_lock ); vlc_mutex_destroy( &p_ext->p_sys->command_lock ); @@ -182,7 +186,7 @@ static int ScanExtensions( extensions_manager_t *p_mgr ) vlclua_scripts_batch_execute( VLC_OBJECT( p_mgr ), "extensions", &ScanLuaCallback, - p_mgr->p_sys->L, &b_true ); + &b_true ); if( !i_ret ) return VLC_EGENERIC; @@ -190,6 +194,18 @@ static int ScanExtensions( extensions_manager_t *p_mgr ) return VLC_SUCCESS; } +/** + * Dummy Lua function: does nothing + * @note This function can be used to replace "require" while scanning for + * extensions + * Even the built-in libraries are not loaded when calling descriptor() + **/ +static int vlclua_dummy_require( lua_State *L ) +{ + (void) L; + return 0; +} + /** * Batch scan all Lua files in folder "extensions": callback * @param p_this This extensions_manager_t object @@ -198,7 +214,7 @@ static int ScanExtensions( extensions_manager_t *p_mgr ) * @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 ) + void *pb_continue ) { extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this; bool b_ok = false; @@ -232,7 +248,11 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, vlc_mutex_init( &p_ext->p_sys->running_lock ); vlc_cond_init( &p_ext->p_sys->wait ); - /* Load and run the script(s) */ + /* Prepare Lua state */ + lua_State *L = luaL_newstate(); + lua_register( L, "require", &vlclua_dummy_require ); + + /* Let's run it */ if( luaL_dofile( L, psz_script ) ) { msg_Warn( p_mgr, "Error loading script %s: %s", psz_script, @@ -246,14 +266,14 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, if( !lua_isfunction( L, -1 ) ) { - msg_Warn( p_mgr, "Error while runing script %s, " + msg_Warn( p_mgr, "Error while running 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, " + msg_Warn( p_mgr, "Error while running script %s, " "function descriptor(): %s", psz_script, lua_tostring( L, lua_gettop( L ) ) ); goto exit; @@ -263,6 +283,7 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, { if( lua_istable( L, -1 ) ) { + /* Get caps */ lua_getfield( L, -1, "capabilities" ); if( lua_istable( L, -1 ) ) { @@ -301,8 +322,9 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, "did not return a table of capabilities.", psz_script ); } - lua_pop( L, 1 ); + + /* Get title */ lua_getfield( L, -1, "title" ); if( lua_isstring( L, -1 ) ) { @@ -315,6 +337,37 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, psz_script ); p_ext->psz_title = strdup( psz_script ); } + lua_pop( L, 1 ); + + /* Get author */ + lua_getfield( L, -1, "author" ); + if( lua_isstring( L, -1 ) ) + p_ext->psz_author = strdup( luaL_optstring( L, -1, NULL ) ); + lua_pop( L, 1 ); + + /* Get description */ + lua_getfield( L, -1, "description" ); + if( lua_isstring( L, -1 ) ) + p_ext->psz_description = strdup( luaL_optstring( L, -1, NULL ) ); + lua_pop( L, 1 ); + + /* Get short description */ + lua_getfield( L, -1, "shortdesc" ); + if( lua_isstring( L, -1 ) ) + p_ext->psz_shortdescription = strdup( luaL_optstring( L, -1, NULL ) ); + lua_pop( L, 1 ); + + /* Get URL */ + lua_getfield( L, -1, "url" ); + if( lua_isstring( L, -1 ) ) + p_ext->psz_url = strdup( luaL_optstring( L, -1, NULL ) ); + lua_pop( L, 1 ); + + /* Get version */ + lua_getfield( L, -1, "version" ); + if( lua_isstring( L, -1 ) ) + p_ext->psz_version = strdup( luaL_optstring( L, -1, NULL ) ); + lua_pop( L, 1 ); } else { @@ -334,10 +387,16 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script, b_ok = true; exit: + lua_close( L ); if( !b_ok ) { free( p_ext->psz_name ); free( p_ext->psz_title ); + free( p_ext->psz_url ); + free( p_ext->psz_author ); + free( p_ext->psz_description ); + free( p_ext->psz_shortdescription ); + free( p_ext->psz_version ); vlc_mutex_destroy( &p_ext->p_sys->command_lock ); vlc_mutex_destroy( &p_ext->p_sys->running_lock ); vlc_cond_destroy( &p_ext->p_sys->wait ); @@ -407,9 +466,71 @@ static int Control( extensions_manager_t *p_mgr, int i_control, va_list args ) i = ( int ) va_arg( args, int ); return TriggerMenu( p_ext, i ); + case EXTENSION_SET_INPUT: + { + p_ext = ( extension_t* ) va_arg( args, extension_t* ); + input_thread_t *p_input = va_arg( args, struct input_thread_t * ); + + if( !LockExtension( p_ext ) ) + return VLC_EGENERIC; + + // Change input + input_thread_t *old = p_ext->p_sys->p_input; + input_item_t *p_item; + if( old ) + { + // Untrack meta fetched events + if( p_ext->p_sys->i_capabilities & EXT_META_LISTENER ) + { + p_item = input_GetItem( old ); + vlc_event_detach( &p_item->event_manager, + vlc_InputItemMetaChanged, + inputItemMetaChanged, + p_ext ); + vlc_gc_decref( p_item ); + } + vlc_object_release( old ); + } + + p_ext->p_sys->p_input = p_input ? vlc_object_hold( p_input ) + : p_input; + + // Tell the script the input changed + if( p_ext->p_sys->i_capabilities & EXT_INPUT_LISTENER ) + { + PushCommandUnique( p_ext, CMD_SET_INPUT ); + } + + // Track meta fetched events + if( p_ext->p_sys->p_input && + p_ext->p_sys->i_capabilities & EXT_META_LISTENER ) + { + p_item = input_GetItem( p_ext->p_sys->p_input ); + vlc_gc_incref( p_item ); + vlc_event_attach( &p_item->event_manager, + vlc_InputItemMetaChanged, + inputItemMetaChanged, + p_ext ); + } + + UnlockExtension( p_ext ); + break; + } + case EXTENSION_PLAYING_CHANGED: + { + extension_t *p_ext; + p_ext = ( extension_t* ) va_arg( args, extension_t* ); + assert( p_ext->psz_name != NULL ); + i = ( int ) va_arg( args, int ); + if( p_ext->p_sys->i_capabilities & EXT_PLAYING_LISTENER ) + { + PushCommand( p_ext, CMD_PLAYING_CHANGED, i ); + } + break; + } default: - msg_Err( p_mgr, "Control '%d' not yet implemented in Extension", - i_control ); + msg_Warn( p_mgr, "Control '%d' not yet implemented in Extension", + i_control ); return VLC_EGENERIC; } @@ -419,7 +540,7 @@ static int Control( extensions_manager_t *p_mgr, int i_control, va_list args ) 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" ); + return lua_ExecuteFunction( p_mgr, p_ext, "activate", LUA_END ); } int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext ) @@ -429,7 +550,19 @@ int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext ) if( !p_ext->p_sys->L ) return VLC_SUCCESS; - int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate" ); + // Unset and release input objects + if( p_ext->p_sys->p_input ) + { + if( p_ext->p_sys->i_capabilities & EXT_META_LISTENER ) + { + // Release item + input_item_t *p_item = input_GetItem( p_ext->p_sys->p_input ); + vlc_gc_decref( p_item ); + } + vlc_object_release( p_ext->p_sys->p_input ); + } + + int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate", LUA_END ); /* Clear Lua State */ lua_close( p_ext->p_sys->L ); @@ -445,7 +578,10 @@ int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr, if( !p_ext->p_sys->L ) return VLC_SUCCESS; - return lua_ExecuteFunction( p_mgr, p_ext, (const char*) p_widget->p_sys ); + lua_State *L = GetLuaState( p_mgr, p_ext ); + lua_pushlightuserdata( L, p_widget ); + lua_gettable( L, LUA_REGISTRYINDEX ); + return lua_ExecuteFunction( p_mgr, p_ext, NULL, LUA_END ); } @@ -454,7 +590,7 @@ int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr, * @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. + * @param ppi_ids Pointer to NULL. Must be freed by the caller. * @note This function is allowed to run in the UI thread. This means * that it MUST respond very fast. * @todo Remove the menu() hook and provide a new function vlc.set_menu() @@ -490,14 +626,14 @@ static int GetMenuEntries( extensions_manager_t *p_mgr, extension_t *p_ext, if( !lua_isfunction( L, -1 ) ) { - msg_Warn( p_mgr, "Error while runing script %s, " + msg_Warn( p_mgr, "Error while running 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, " + msg_Warn( p_mgr, "Error while running script %s, " "function menu(): %s", p_ext->psz_name, lua_tostring( L, lua_gettop( L ) ) ); goto exit; @@ -571,16 +707,13 @@ static lua_State* GetLuaState( extensions_manager_t *p_mgr, msg_Err( p_mgr, "Could not create new Lua State" ); return NULL; } + vlclua_set_this( L, p_mgr ); + vlclua_extension_set( L, p_ext ); + 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 */ @@ -588,6 +721,7 @@ static lua_State* GetLuaState( extensions_manager_t *p_mgr, luaopen_config( L ); luaopen_dialog( L, p_ext ); luaopen_input( L ); + luaopen_md5( L ); luaopen_msg( L ); luaopen_misc( L ); luaopen_net( L ); @@ -601,12 +735,20 @@ static lua_State* GetLuaState( extensions_manager_t *p_mgr, luaopen_video( L ); luaopen_vlm( L ); luaopen_volume( L ); + luaopen_xml( L ); /* Register extension specific functions */ lua_getglobal( L, "vlc" ); lua_pushcfunction( L, vlclua_extension_deactivate ); lua_setfield( L, -2, "deactivate" ); + /* Setup the module search path */ + if( vlclua_add_modules_path( p_mgr, L, p_ext->psz_name ) ) + { + msg_Warn( p_mgr, "Error while setting the module search path for %s", p_ext->psz_name ); + return NULL; + } + /* Load and run the script(s) */ if( luaL_dofile( L, p_ext->psz_name ) != 0 ) { @@ -619,52 +761,79 @@ static lua_State* GetLuaState( extensions_manager_t *p_mgr, 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; } +int lua_ExecuteFunction( extensions_manager_t *p_mgr, extension_t *p_ext, + const char *psz_function, ... ) +{ + va_list args; + va_start( args, psz_function ); + int i_ret = lua_ExecuteFunctionVa( p_mgr, p_ext, psz_function, args ); + va_end( args ); + return i_ret; +} + /** * Execute a function in a Lua script + * @param psz_function Name of global function to execute. If NULL, assume + * that the function object is already on top of the + * stack. * @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 lua_ExecuteFunctionVa( extensions_manager_t *p_mgr, extension_t *p_ext, + const char *psz_function, va_list args ) { int i_ret = VLC_EGENERIC; + int i_args = 0; assert( p_mgr != NULL ); assert( p_ext != NULL ); lua_State *L = GetLuaState( p_mgr, p_ext ); - lua_getglobal( L, psz_function ); + if( psz_function ) + lua_getglobal( L, psz_function ); if( !lua_isfunction( L, -1 ) ) { - msg_Warn( p_mgr, "Error while runing script %s, " + msg_Warn( p_mgr, "Error while running script %s, " "function %s() not found", p_ext->psz_name, psz_function ); goto exit; } - if( lua_pcall( L, 0, 1, 0 ) ) + lua_datatype_e type = LUA_END; + while( ( type = va_arg( args, int ) ) != LUA_END ) { - msg_Warn( p_mgr, "Error while runing script %s, " + if( type == LUA_NUM ) + { + lua_pushnumber( L , ( int ) va_arg( args, int ) ); + } + else if( type == LUA_TEXT ) + { + lua_pushstring( L , ( char * ) va_arg( args, char* ) ); + } + else + { + msg_Warn( p_mgr, "Undefined argument type %d to lua function %s" + "from script %s", type, psz_function, p_ext->psz_name ); + goto exit; + } + i_args ++; + } + if( lua_pcall( L, i_args, 1, 0 ) ) + { + msg_Warn( p_mgr, "Error while running script %s, " "function %s(): %s", p_ext->psz_name, psz_function, lua_tostring( L, lua_gettop( L ) ) ); goto exit; } - i_ret = VLC_SUCCESS; + i_ret = lua_DialogFlush( L ); exit: return i_ret; + } static inline int TriggerMenu( extension_t *p_ext, int i_id ) @@ -686,7 +855,7 @@ int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr, lua_getglobal( L, "trigger_menu" ); if( !lua_isfunction( L, -1 ) ) { - msg_Warn( p_mgr, "Error while runing script %s, " + msg_Warn( p_mgr, "Error while running script %s, " "function trigger_menu() not found", p_ext->psz_name ); return VLC_EGENERIC; } @@ -696,12 +865,13 @@ int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr, if( lua_pcall( L, 1, 1, 0 ) != 0 ) { - msg_Warn( p_mgr, "Error while runing script %s, " + msg_Warn( p_mgr, "Error while running script %s, " "function trigger_menu(): %s", p_ext->psz_name, lua_tostring( L, lua_gettop( L ) ) ); return VLC_EGENERIC; } + i_ret = lua_DialogFlush( L ); if( i_ret < VLC_SUCCESS ) { msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)", @@ -719,7 +889,7 @@ int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr, static int TriggerExtension( extensions_manager_t *p_mgr, extension_t *p_ext ) { - int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "trigger" ); + int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "trigger", LUA_END ); /* Close lua state for trigger-only extensions */ if( p_ext->p_sys->L ) @@ -729,17 +899,27 @@ static int TriggerExtension( extensions_manager_t *p_mgr, return i_ret; } +/** Set extension associated to the current script + * @param L current lua_State + * @param p_ext the extension + */ +void vlclua_extension_set( lua_State *L, extension_t *p_ext ) +{ + lua_pushlightuserdata( L, vlclua_extension_set ); + lua_pushlightuserdata( L, p_ext ); + lua_rawset( L, LUA_REGISTRYINDEX ); +} + /** Retrieve extension associated to the current script * @param L current lua_State - * @return Lua userdata "vlc.extension" + * @return Extension pointer **/ 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 ); + lua_pushlightuserdata( L, vlclua_extension_set ); + lua_rawget( L, LUA_REGISTRYINDEX ); + extension_t *p_ext = (extension_t*) lua_topointer( L, -1 ); + lua_pop( L, 1 ); return p_ext; } @@ -786,10 +966,10 @@ static int vlclua_extension_dialog_callback( vlc_object_t *p_this, { case EXTENSION_EVENT_CLICK: assert( p_widget != NULL ); - PushCommand( p_ext, CMD_CLICK, p_widget ); + PushCommandUnique( p_ext, CMD_CLICK, p_widget ); break; case EXTENSION_EVENT_CLOSE: - PushCommand( p_ext, CMD_CLOSE ); + PushCommandUnique( p_ext, CMD_CLOSE ); break; default: msg_Dbg( p_this, "Received unknown UI event %d, discarded", @@ -800,6 +980,19 @@ static int vlclua_extension_dialog_callback( vlc_object_t *p_this, return VLC_SUCCESS; } +/** Callback on vlc_InputItemMetaChanged event + **/ +static void inputItemMetaChanged( const vlc_event_t *p_event, + void *data ) +{ + assert( p_event && p_event->type == vlc_InputItemMetaChanged ); + + extension_t *p_ext = ( extension_t* ) data; + assert( p_ext != NULL ); + + PushCommandUnique( p_ext, CMD_UPDATE_META ); +} + /* Lock this extension. Can fail. */ bool LockExtension( extension_t *p_ext ) {