]> git.sesse.net Git - vlc/blobdiff - modules/misc/lua/extension.c
lua: factorize the right way.
[vlc] / modules / misc / lua / extension.c
index 5d377a38ecfea1afbbd0eb53057be73c42418a54..745350fdeeb7b2d6363c8c784f1a3e244e6bab6f 100644 (file)
@@ -43,18 +43,20 @@ static const luaL_Reg p_reg[] =
 #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 *dummy );
 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 );
@@ -99,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 )
     {
@@ -115,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",
@@ -153,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 );
@@ -170,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 );
@@ -189,12 +181,11 @@ void Close_Extension( vlc_object_t *p_this )
  **/
 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 );
+                                      NULL );
 
     if( !i_ret )
         return VLC_EGENERIC;
@@ -202,16 +193,29 @@ 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
  * @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
+ * @param dummy: unused
  **/
 int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
-                     lua_State *L, void *pb_continue )
+                     void *dummy )
 {
+    VLC_UNUSED(dummy);
     extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
     bool b_ok = false;
 
@@ -244,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,
@@ -258,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;
@@ -333,50 +341,27 @@ int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
 
             /* Get author */
             lua_getfield( L, -1, "author" );
-            if( lua_isstring( L, -1 ) )
-            {
-                p_ext->psz_author = strdup( luaL_checkstring( L, -1 ) );
-            }
-            else
-            {
-                p_ext->psz_author = NULL;
-            }
+            p_ext->psz_author = luaL_strdupornull( L, -1 );
             lua_pop( L, 1 );
 
             /* Get description */
             lua_getfield( L, -1, "description" );
-            if( lua_isstring( L, -1 ) )
-            {
-                p_ext->psz_description = strdup( luaL_checkstring( L, -1 ) );
-            }
-            else
-            {
-                p_ext->psz_description = NULL;
-            }
+            p_ext->psz_description = luaL_strdupornull( L, -1 );
+            lua_pop( L, 1 );
+
+            /* Get short description */
+            lua_getfield( L, -1, "shortdesc" );
+            p_ext->psz_shortdescription = luaL_strdupornull( L, -1 );
             lua_pop( L, 1 );
 
             /* Get URL */
             lua_getfield( L, -1, "url" );
-            if( lua_isstring( L, -1 ) )
-            {
-                p_ext->psz_url = strdup( luaL_checkstring( L, -1 ) );
-            }
-            else
-            {
-                p_ext->psz_url = NULL;
-            }
+            p_ext->psz_url = luaL_strdupornull( L, -1 );
             lua_pop( L, 1 );
 
             /* Get version */
             lua_getfield( L, -1, "version" );
-            if( lua_isstring( L, -1 ) )
-            {
-                p_ext->psz_version = strdup( luaL_checkstring( L, -1 ) );
-            }
-            else
-            {
-                p_ext->psz_version = NULL;
-            }
+            p_ext->psz_version = luaL_strdupornull( L, -1 );
             lua_pop( L, 1 );
         }
         else
@@ -397,6 +382,7 @@ 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 );
@@ -404,6 +390,7 @@ exit:
         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 );
@@ -419,7 +406,7 @@ exit:
 
     vlc_mutex_unlock( &p_mgr->lock );
     /* Continue batch execution */
-    return pb_continue ? ( (* (bool*)pb_continue) ? -1 : 0 ) : -1;
+    return VLC_EGENERIC;
 }
 
 static int Control( extensions_manager_t *p_mgr, int i_control, va_list args )
@@ -524,7 +511,18 @@ static int Control( extensions_manager_t *p_mgr, int i_control, va_list args )
             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_Warn( p_mgr, "Control '%d' not yet implemented in Extension",
                       i_control );
@@ -537,7 +535,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 )
@@ -559,7 +557,7 @@ int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
         vlc_object_release( p_ext->p_sys->p_input );
     }
 
-    int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate" );
+    int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate", LUA_END );
 
     /* Clear Lua State */
     lua_close( p_ext->p_sys->L );
@@ -575,7 +573,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 );
 }
 
 
@@ -584,7 +585,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()
@@ -620,14 +621,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;
@@ -701,16 +702,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 */
@@ -718,6 +716,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 );
@@ -731,12 +730,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 )
             {
@@ -749,44 +756,70 @@ 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;
@@ -795,6 +828,7 @@ int lua_ExecuteFunction( extensions_manager_t *p_mgr,
     i_ret = lua_DialogFlush( L );
 exit:
     return i_ret;
+
 }
 
 static inline int TriggerMenu( extension_t *p_ext, int i_id )
@@ -816,7 +850,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;
     }
@@ -826,7 +860,7 @@ 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;
@@ -850,7 +884,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 )
@@ -860,17 +894,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;
 }
 
@@ -917,7 +961,7 @@ 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:
             PushCommandUnique( p_ext, CMD_CLOSE );