]> git.sesse.net Git - vlc/blob - modules/lua/extension.c
lua: always use vlclua_dofile
[vlc] / modules / lua / extension.c
1 /*****************************************************************************
2  * extension.c: Lua Extensions (meta data, web information, ...)
3  *****************************************************************************
4  * Copyright (C) 2009-2010 VideoLAN and authors
5  * $Id$
6  *
7  * Authors: Jean-Philippe AndrĂ© < jpeg # videolan.org >
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifndef _GNU_SOURCE
25 # define _GNU_SOURCE
26 #endif
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include "vlc.h"
33 #include "libs.h"
34 #include "extension.h"
35 #include "assert.h"
36
37 #include <vlc_common.h>
38 #include <vlc_input.h>
39 #include <vlc_interface.h>
40 #include <vlc_events.h>
41 #include <vlc_dialog.h>
42
43 /* Functions to register */
44 static const luaL_Reg p_reg[] =
45 {
46     { NULL, NULL }
47 };
48
49 /*
50  * Extensions capabilities
51  * Note: #define and ppsz_capabilities must be in sync
52  */
53 static const char caps[][20] = {
54 #define EXT_HAS_MENU          (1 << 0)   ///< Hook: menu
55     "menu",
56 #define EXT_TRIGGER_ONLY      (1 << 1)   ///< Hook: trigger. Not activable
57     "trigger",
58 #define EXT_INPUT_LISTENER    (1 << 2)   ///< Hook: input_changed
59     "input-listener",
60 #define EXT_META_LISTENER     (1 << 3)   ///< Hook: meta_changed
61     "meta-listener",
62 #define EXT_PLAYING_LISTENER  (1 << 4)   ///< Hook: status_changed
63     "playing-listener",
64 };
65
66 #define WATCH_TIMER_PERIOD    (10 * CLOCK_FREQ) ///< 10s period for the timer
67
68 static int ScanExtensions( extensions_manager_t *p_this );
69 static int ScanLuaCallback( vlc_object_t *p_this, const char *psz_script,
70                             const struct luabatch_context_t * );
71 static int Control( extensions_manager_t *, int, va_list );
72 static int GetMenuEntries( extensions_manager_t *p_mgr, extension_t *p_ext,
73                     char ***pppsz_titles, uint16_t **ppi_ids );
74 static lua_State* GetLuaState( extensions_manager_t *p_mgr,
75                                extension_t *p_ext );
76 static int TriggerMenu( extension_t *p_ext, int id );
77 static int TriggerExtension( extensions_manager_t *p_mgr,
78                              extension_t *p_ext );
79 static void WatchTimerCallback( void* );
80
81 static int vlclua_extension_deactivate( lua_State *L );
82 static int vlclua_extension_keep_alive( lua_State *L );
83
84 /* Interactions */
85 static int vlclua_extension_dialog_callback( vlc_object_t *p_this,
86                                              char const *psz_var,
87                                              vlc_value_t oldval,
88                                              vlc_value_t newval,
89                                              void *p_data );
90
91 /* Input item callback: vlc_InputItemMetaChanged */
92 static void inputItemMetaChanged( const vlc_event_t *p_event,
93                                   void *data );
94
95
96 /**
97  * Module entry-point
98  **/
99 int Open_Extension( vlc_object_t *p_this )
100 {
101     msg_Dbg( p_this, "Opening Lua Extension module" );
102
103     extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
104
105     p_mgr->pf_control = Control;
106
107     extensions_manager_sys_t *p_sys = ( extensions_manager_sys_t* )
108                     calloc( 1, sizeof( extensions_manager_sys_t ) );
109     if( !p_sys ) return VLC_ENOMEM;
110
111     p_mgr->p_sys = p_sys;
112     ARRAY_INIT( p_sys->activated_extensions );
113     ARRAY_INIT( p_mgr->extensions );
114     vlc_mutex_init( &p_mgr->lock );
115     vlc_mutex_init( &p_mgr->p_sys->lock );
116
117     /* Scan available Lua Extensions */
118     if( ScanExtensions( p_mgr ) != VLC_SUCCESS )
119     {
120         msg_Err( p_mgr, "Can't load extensions modules" );
121         return VLC_EGENERIC;
122     }
123
124     // Create the dialog-event variable
125     var_Create( p_this, "dialog-event", VLC_VAR_ADDRESS );
126     var_AddCallback( p_this, "dialog-event",
127                      vlclua_extension_dialog_callback, NULL );
128
129     return VLC_SUCCESS;
130 }
131
132 /**
133  * Module unload function
134  **/
135 void Close_Extension( vlc_object_t *p_this )
136 {
137     extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
138     msg_Dbg( p_mgr, "Deactivating all loaded extensions" );
139
140     vlc_mutex_lock( &p_mgr->lock );
141     p_mgr->p_sys->b_killed = true;
142     vlc_mutex_unlock( &p_mgr->lock );
143
144     var_Destroy( p_mgr, "dialog-event" );
145
146     extension_t *p_ext = NULL;
147     FOREACH_ARRAY( p_ext, p_mgr->p_sys->activated_extensions )
148     {
149         if( !p_ext ) break;
150         msg_Dbg( p_mgr, "Deactivating '%s'", p_ext->psz_title );
151         Deactivate( p_mgr, p_ext );
152         vlc_join( p_ext->p_sys->thread, NULL );
153     }
154     FOREACH_END()
155
156     msg_Dbg( p_mgr, "All extensions are now deactivated" );
157     ARRAY_RESET( p_mgr->p_sys->activated_extensions );
158
159     vlc_mutex_destroy( &p_mgr->lock );
160     vlc_mutex_destroy( &p_mgr->p_sys->lock );
161     free( p_mgr->p_sys );
162     p_mgr->p_sys = NULL;
163
164     /* Free extensions' memory */
165     FOREACH_ARRAY( p_ext, p_mgr->extensions )
166     {
167         if( !p_ext )
168             break;
169         if( p_ext->p_sys->L )
170             lua_close( p_ext->p_sys->L );
171         free( p_ext->psz_name );
172         free( p_ext->psz_title );
173         free( p_ext->psz_author );
174         free( p_ext->psz_description );
175         free( p_ext->psz_shortdescription );
176         free( p_ext->psz_url );
177         free( p_ext->psz_version );
178         free( p_ext->p_icondata );
179
180         vlc_mutex_destroy( &p_ext->p_sys->running_lock );
181         vlc_mutex_destroy( &p_ext->p_sys->command_lock );
182         vlc_cond_destroy( &p_ext->p_sys->wait );
183         vlc_timer_destroy( p_ext->p_sys->timer );
184
185         free( p_ext->p_sys );
186         free( p_ext );
187     }
188     FOREACH_END()
189
190     ARRAY_RESET( p_mgr->extensions );
191
192     var_DelCallback( p_this, "dialog-event",
193                      vlclua_extension_dialog_callback, NULL );
194 }
195
196 /**
197  * Batch scan all Lua files in folder "extensions"
198  * @param p_mgr This extensions_manager_t object
199  **/
200 static int ScanExtensions( extensions_manager_t *p_mgr )
201 {
202     int i_ret =
203         vlclua_scripts_batch_execute( VLC_OBJECT( p_mgr ),
204                                       "extensions",
205                                       &ScanLuaCallback,
206                                       NULL );
207
208     if( !i_ret )
209         return VLC_EGENERIC;
210
211     return VLC_SUCCESS;
212 }
213
214 /**
215  * Dummy Lua function: does nothing
216  * @note This function can be used to replace "require" while scanning for
217  * extensions
218  * Even the built-in libraries are not loaded when calling descriptor()
219  **/
220 static int vlclua_dummy_require( lua_State *L )
221 {
222     (void) L;
223     return 0;
224 }
225
226 /**
227  * Replacement for "require", adding support for packaged extensions
228  * @note Loads modules in the modules/ folder of a package
229  * @note Try first with .luac and then with .lua
230  **/
231 static int vlclua_extension_require( lua_State *L )
232 {
233     const char *psz_module = luaL_checkstring( L, 1 );
234     vlc_object_t *p_this = vlclua_get_this( L );
235     extension_t *p_ext = vlclua_extension_get( L );
236     msg_Dbg( p_this, "loading module '%s' from extension package",
237              psz_module );
238     char *psz_fullpath, *psz_package, *sep;
239     psz_package = strdup( p_ext->psz_name );
240     sep = strrchr( psz_package, '/' );
241     if( !sep )
242     {
243         free( psz_package );
244         return luaL_error( L, "could not find package name" );
245     }
246     *sep = '\0';
247     if( -1 == asprintf( &psz_fullpath,
248                         "%s/modules/%s.luac", psz_package, psz_module ) )
249     {
250         free( psz_package );
251         return 1;
252     }
253     int i_ret = vlclua_dofile( p_this, L, psz_fullpath );
254     if( i_ret != 0 )
255     {
256         // Remove trailing 'c' --> try with .lua script
257         psz_fullpath[ strlen( psz_fullpath ) - 1 ] = '\0';
258         i_ret = vlclua_dofile( p_this, L, psz_fullpath );
259     }
260     free( psz_fullpath );
261     free( psz_package );
262     if( i_ret != 0 )
263     {
264         return luaL_error( L, "unable to load module '%s' from package",
265                            psz_module );
266     }
267     return 0;
268 }
269
270 /**
271  * Batch scan all Lua files in folder "extensions": callback
272  * @param p_this This extensions_manager_t object
273  * @param psz_filename Name of the script to run
274  * @param L Lua State, common to all scripts here
275  * @param dummy: unused
276  **/
277 int ScanLuaCallback( vlc_object_t *p_this, const char *psz_filename,
278                      const struct luabatch_context_t *dummy )
279 {
280     VLC_UNUSED(dummy);
281     extensions_manager_t *p_mgr = ( extensions_manager_t* ) p_this;
282     bool b_ok = false;
283
284     msg_Dbg( p_mgr, "Scanning Lua script %s", psz_filename );
285
286     /* Experimental: read .vle packages (Zip archives) */
287     char *psz_script = NULL;
288     int i_flen = strlen( psz_filename );
289     if( !strncasecmp( psz_filename + i_flen - 4, ".vle", 4 ) )
290     {
291         msg_Dbg( p_this, "reading Lua script in a zip archive" );
292         psz_script = calloc( 1, i_flen + 6 + 12 + 1 );
293         if( !psz_script )
294             return 0;
295         strcpy( psz_script, "zip://" );
296         strncat( psz_script, psz_filename, i_flen + 19 );
297         strncat( psz_script, "!/script.lua", i_flen + 19 );
298     }
299     else
300     {
301         psz_script = vlc_path2uri( psz_filename, "file" );
302         if( !psz_script )
303             return 0;
304     }
305
306     /* Create new script descriptor */
307     extension_t *p_ext = ( extension_t* ) calloc( 1, sizeof( extension_t ) );
308     if( !p_ext )
309     {
310         free( psz_script );
311         return 0;
312     }
313
314     p_ext->psz_name = psz_script;
315     p_ext->p_sys = (extension_sys_t*) calloc( 1, sizeof( extension_sys_t ) );
316     if( !p_ext->p_sys || !p_ext->psz_name )
317     {
318         free( p_ext->psz_name );
319         free( p_ext->p_sys );
320         free( p_ext );
321         return 0;
322     }
323     p_ext->p_sys->p_mgr = p_mgr;
324
325     /* Watch timer */
326     if( vlc_timer_create( &p_ext->p_sys->timer, WatchTimerCallback, p_ext ) )
327     {
328         free( p_ext->psz_name );
329         free( p_ext->p_sys );
330         free( p_ext );
331         return 0;
332     }
333
334     /* Mutexes and conditions */
335     vlc_mutex_init( &p_ext->p_sys->command_lock );
336     vlc_mutex_init( &p_ext->p_sys->running_lock );
337     vlc_cond_init( &p_ext->p_sys->wait );
338
339     /* Prepare Lua state */
340     lua_State *L = luaL_newstate();
341     lua_register( L, "require", &vlclua_dummy_require );
342
343     /* Let's run it */
344     if( vlclua_dofile( p_this, L, psz_script ) ) // luaL_dofile
345     {
346         msg_Warn( p_mgr, "Error loading script %s: %s", psz_script,
347                   lua_tostring( L, lua_gettop( L ) ) );
348         lua_pop( L, 1 );
349         goto exit;
350     }
351
352     /* Scan script for capabilities */
353     lua_getglobal( L, "descriptor" );
354
355     if( !lua_isfunction( L, -1 ) )
356     {
357         msg_Warn( p_mgr, "Error while running script %s, "
358                   "function descriptor() not found", psz_script );
359         goto exit;
360     }
361
362     if( lua_pcall( L, 0, 1, 0 ) )
363     {
364         msg_Warn( p_mgr, "Error while running script %s, "
365                   "function descriptor(): %s", psz_script,
366                   lua_tostring( L, lua_gettop( L ) ) );
367         goto exit;
368     }
369
370     if( lua_gettop( L ) )
371     {
372         if( lua_istable( L, -1 ) )
373         {
374             /* Get caps */
375             lua_getfield( L, -1, "capabilities" );
376             if( lua_istable( L, -1 ) )
377             {
378                 lua_pushnil( L );
379                 while( lua_next( L, -2 ) != 0 )
380                 {
381                     /* Key is at index -2 and value at index -1. Discard key */
382                     const char *psz_cap = luaL_checkstring( L, -1 );
383                     bool b_ok = false;
384                     /* Find this capability's flag */
385                     for( size_t i = 0; i < sizeof(caps)/sizeof(caps[0]); i++ )
386                     {
387                         if( !strcmp( caps[i], psz_cap ) )
388                         {
389                             /* Flag it! */
390                             p_ext->p_sys->i_capabilities |= 1 << i;
391                             b_ok = true;
392                             break;
393                         }
394                     }
395                     if( !b_ok )
396                     {
397                         msg_Warn( p_mgr, "Extension capability '%s' unknown in"
398                                   " script %s", psz_cap, psz_script );
399                     }
400                     /* Removes 'value'; keeps 'key' for next iteration */
401                     lua_pop( L, 1 );
402                 }
403             }
404             else
405             {
406                 msg_Warn( p_mgr, "In script %s, function descriptor() "
407                               "did not return a table of capabilities.",
408                               psz_script );
409             }
410             lua_pop( L, 1 );
411
412             /* Get title */
413             lua_getfield( L, -1, "title" );
414             if( lua_isstring( L, -1 ) )
415             {
416                 p_ext->psz_title = strdup( luaL_checkstring( L, -1 ) );
417             }
418             else
419             {
420                 msg_Dbg( p_mgr, "In script %s, function descriptor() "
421                                 "did not return a string as title.",
422                                 psz_script );
423                 p_ext->psz_title = strdup( psz_script );
424             }
425             lua_pop( L, 1 );
426
427             /* Get author */
428             lua_getfield( L, -1, "author" );
429             p_ext->psz_author = luaL_strdupornull( L, -1 );
430             lua_pop( L, 1 );
431
432             /* Get description */
433             lua_getfield( L, -1, "description" );
434             p_ext->psz_description = luaL_strdupornull( L, -1 );
435             lua_pop( L, 1 );
436
437             /* Get short description */
438             lua_getfield( L, -1, "shortdesc" );
439             p_ext->psz_shortdescription = luaL_strdupornull( L, -1 );
440             lua_pop( L, 1 );
441
442             /* Get URL */
443             lua_getfield( L, -1, "url" );
444             p_ext->psz_url = luaL_strdupornull( L, -1 );
445             lua_pop( L, 1 );
446
447             /* Get version */
448             lua_getfield( L, -1, "version" );
449             p_ext->psz_version = luaL_strdupornull( L, -1 );
450             lua_pop( L, 1 );
451
452             /* Get icon data */
453             lua_getfield( L, -1, "icon" );
454             if( !lua_isnil( L, -1 ) && lua_isstring( L, -1 ) )
455             {
456                 int len = lua_strlen( L, -1 );
457                 p_ext->p_icondata = malloc( len );
458                 if( p_ext->p_icondata )
459                 {
460                     p_ext->i_icondata_size = len;
461                     memcpy( p_ext->p_icondata, lua_tostring( L, -1 ), len );
462                 }
463             }
464             lua_pop( L, 1 );
465         }
466         else
467         {
468             msg_Warn( p_mgr, "In script %s, function descriptor() "
469                       "did not return a table!", psz_script );
470             goto exit;
471         }
472     }
473     else
474     {
475         msg_Err( p_mgr, "Script %s went completely foobar", psz_script );
476         goto exit;
477     }
478
479     msg_Dbg( p_mgr, "Script %s has the following capability flags: 0x%x",
480              psz_script, p_ext->p_sys->i_capabilities );
481
482     b_ok = true;
483 exit:
484     lua_close( L );
485     if( !b_ok )
486     {
487         free( p_ext->psz_name );
488         free( p_ext->psz_title );
489         free( p_ext->psz_url );
490         free( p_ext->psz_author );
491         free( p_ext->psz_description );
492         free( p_ext->psz_shortdescription );
493         free( p_ext->psz_version );
494         vlc_mutex_destroy( &p_ext->p_sys->command_lock );
495         vlc_mutex_destroy( &p_ext->p_sys->running_lock );
496         vlc_cond_destroy( &p_ext->p_sys->wait );
497         free( p_ext->p_sys );
498         free( p_ext );
499     }
500     else
501     {
502         /* Add the extension to the list of known extensions */
503         ARRAY_APPEND( p_mgr->extensions, p_ext );
504     }
505
506     /* Continue batch execution */
507     return VLC_EGENERIC;
508 }
509
510 static int Control( extensions_manager_t *p_mgr, int i_control, va_list args )
511 {
512     extension_t *p_ext = NULL;
513     bool *pb = NULL;
514     uint16_t **ppus = NULL;
515     char ***pppsz = NULL;
516     int i = 0;
517
518     switch( i_control )
519     {
520         case EXTENSION_ACTIVATE:
521             p_ext = ( extension_t* ) va_arg( args, extension_t* );
522             return Activate( p_mgr, p_ext );
523
524         case EXTENSION_DEACTIVATE:
525             p_ext = ( extension_t* ) va_arg( args, extension_t* );
526             return Deactivate( p_mgr, p_ext );
527
528         case EXTENSION_IS_ACTIVATED:
529             p_ext = ( extension_t* ) va_arg( args, extension_t* );
530             pb = ( bool* ) va_arg( args, bool* );
531             *pb = IsActivated( p_mgr, p_ext );
532             break;
533
534         case EXTENSION_HAS_MENU:
535             p_ext = ( extension_t* ) va_arg( args, extension_t* );
536             pb = ( bool* ) va_arg( args, bool* );
537             *pb = ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) ? 1 : 0;
538             break;
539
540         case EXTENSION_GET_MENU:
541             p_ext = ( extension_t* ) va_arg( args, extension_t* );
542             pppsz = ( char*** ) va_arg( args, char*** );
543             ppus = ( uint16_t** ) va_arg( args, uint16_t** );
544             return GetMenuEntries( p_mgr, p_ext, pppsz, ppus );
545
546         case EXTENSION_TRIGGER_ONLY:
547             p_ext = ( extension_t* ) va_arg( args, extension_t* );
548             pb = ( bool* ) va_arg( args, bool* );
549             *pb = ( p_ext->p_sys->i_capabilities & EXT_TRIGGER_ONLY ) ? 1 : 0;
550             break;
551
552         case EXTENSION_TRIGGER:
553             p_ext = ( extension_t* ) va_arg( args, extension_t* );
554             return TriggerExtension( p_mgr, p_ext );
555
556         case EXTENSION_TRIGGER_MENU:
557             p_ext = ( extension_t* ) va_arg( args, extension_t* );
558             // GCC: 'uint16_t' is promoted to 'int' when passed through '...'
559             i = ( int ) va_arg( args, int );
560             return TriggerMenu( p_ext, i );
561
562         case EXTENSION_SET_INPUT:
563         {
564             p_ext = ( extension_t* ) va_arg( args, extension_t* );
565             input_thread_t *p_input = va_arg( args, struct input_thread_t * );
566
567             if( !LockExtension( p_ext ) )
568                 return VLC_EGENERIC;
569
570             // Change input
571             input_thread_t *old = p_ext->p_sys->p_input;
572             input_item_t *p_item;
573             if( old )
574             {
575                 // Untrack meta fetched events
576                 if( p_ext->p_sys->i_capabilities & EXT_META_LISTENER )
577                 {
578                     p_item = input_GetItem( old );
579                     vlc_event_detach( &p_item->event_manager,
580                                       vlc_InputItemMetaChanged,
581                                       inputItemMetaChanged,
582                                       p_ext );
583                     vlc_gc_decref( p_item );
584                 }
585                 vlc_object_release( old );
586             }
587
588             p_ext->p_sys->p_input = p_input ? vlc_object_hold( p_input )
589                                             : p_input;
590
591             // Tell the script the input changed
592             if( p_ext->p_sys->i_capabilities & EXT_INPUT_LISTENER )
593             {
594                 PushCommandUnique( p_ext, CMD_SET_INPUT );
595             }
596
597             // Track meta fetched events
598             if( p_ext->p_sys->p_input &&
599                 p_ext->p_sys->i_capabilities & EXT_META_LISTENER )
600             {
601                 p_item = input_GetItem( p_ext->p_sys->p_input );
602                 vlc_gc_incref( p_item );
603                 vlc_event_attach( &p_item->event_manager,
604                                   vlc_InputItemMetaChanged,
605                                   inputItemMetaChanged,
606                                   p_ext );
607             }
608
609             UnlockExtension( p_ext );
610             break;
611         }
612         case EXTENSION_PLAYING_CHANGED:
613         {
614             extension_t *p_ext;
615             p_ext = ( extension_t* ) va_arg( args, extension_t* );
616             assert( p_ext->psz_name != NULL );
617             i = ( int ) va_arg( args, int );
618             if( p_ext->p_sys->i_capabilities & EXT_PLAYING_LISTENER )
619             {
620                 PushCommand( p_ext, CMD_PLAYING_CHANGED, i );
621             }
622             break;
623         }
624         case EXTENSION_META_CHANGED:
625         {
626             extension_t *p_ext;
627             p_ext = ( extension_t* ) va_arg( args, extension_t* );
628             PushCommand( p_ext, CMD_UPDATE_META );
629             break;
630         }
631         default:
632             msg_Warn( p_mgr, "Control '%d' not yet implemented in Extension",
633                       i_control );
634             return VLC_EGENERIC;
635     }
636
637     return VLC_SUCCESS;
638 }
639
640 int lua_ExtensionActivate( extensions_manager_t *p_mgr, extension_t *p_ext )
641 {
642     assert( p_mgr != NULL && p_ext != NULL );
643     return lua_ExecuteFunction( p_mgr, p_ext, "activate", LUA_END );
644 }
645
646 int lua_ExtensionDeactivate( extensions_manager_t *p_mgr, extension_t *p_ext )
647 {
648     assert( p_mgr != NULL && p_ext != NULL );
649
650     if( !p_ext->p_sys->L )
651         return VLC_SUCCESS;
652
653     vlclua_fd_interrupt( &p_ext->p_sys->dtable );
654
655     // Unset and release input objects
656     if( p_ext->p_sys->p_input )
657     {
658         if( p_ext->p_sys->i_capabilities & EXT_META_LISTENER )
659         {
660             // Release item
661             input_item_t *p_item = input_GetItem( p_ext->p_sys->p_input );
662             vlc_gc_decref( p_item );
663         }
664         vlc_object_release( p_ext->p_sys->p_input );
665         p_ext->p_sys->p_input = NULL;
666     }
667
668     int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "deactivate", LUA_END );
669
670     /* Clear Lua State */
671     vlclua_fd_cleanup( &p_ext->p_sys->dtable );
672     lua_close( p_ext->p_sys->L );
673     p_ext->p_sys->L = NULL;
674
675     return i_ret;
676 }
677
678 int lua_ExtensionWidgetClick( extensions_manager_t *p_mgr,
679                               extension_t *p_ext,
680                               extension_widget_t *p_widget )
681 {
682     if( !p_ext->p_sys->L )
683         return VLC_SUCCESS;
684
685     lua_State *L = GetLuaState( p_mgr, p_ext );
686     lua_pushlightuserdata( L, p_widget );
687     lua_gettable( L, LUA_REGISTRYINDEX );
688     return lua_ExecuteFunction( p_mgr, p_ext, NULL, LUA_END );
689 }
690
691
692 /**
693  * Get the list of menu entries from an extension script
694  * @param p_mgr
695  * @param p_ext
696  * @param pppsz_titles Pointer to NULL. All strings must be freed by the caller
697  * @param ppi_ids Pointer to NULL. Must be freed by the caller.
698  * @note This function is allowed to run in the UI thread. This means
699  *       that it MUST respond very fast.
700  * @todo Remove the menu() hook and provide a new function vlc.set_menu()
701  **/
702 static int GetMenuEntries( extensions_manager_t *p_mgr, extension_t *p_ext,
703                     char ***pppsz_titles, uint16_t **ppi_ids )
704 {
705     assert( *pppsz_titles == NULL );
706     assert( *ppi_ids == NULL );
707
708     if( !IsActivated( p_mgr, p_ext ) )
709     {
710         msg_Dbg( p_mgr, "Can't get menu before activating the extension!" );
711         return VLC_EGENERIC;
712     }
713
714     if( !LockExtension( p_ext ) )
715     {
716         /* Dying extension, fail. */
717         return VLC_EGENERIC;
718     }
719
720     int i_ret = VLC_EGENERIC;
721     lua_State *L = GetLuaState( p_mgr, p_ext );
722
723     if( ( p_ext->p_sys->i_capabilities & EXT_HAS_MENU ) == 0 )
724     {
725         msg_Dbg( p_mgr, "can't get a menu from an extension without menu!" );
726         goto exit;
727     }
728
729     lua_getglobal( L, "menu" );
730
731     if( !lua_isfunction( L, -1 ) )
732     {
733         msg_Warn( p_mgr, "Error while running script %s, "
734                   "function menu() not found", p_ext->psz_name );
735         goto exit;
736     }
737
738     if( lua_pcall( L, 0, 1, 0 ) )
739     {
740         msg_Warn( p_mgr, "Error while running script %s, "
741                   "function menu(): %s", p_ext->psz_name,
742                   lua_tostring( L, lua_gettop( L ) ) );
743         goto exit;
744     }
745
746     if( lua_gettop( L ) )
747     {
748         if( lua_istable( L, -1 ) )
749         {
750             /* Get table size */
751             size_t i_size = lua_objlen( L, -1 );
752             *pppsz_titles = ( char** ) calloc( i_size+1, sizeof( char* ) );
753             *ppi_ids = ( uint16_t* ) calloc( i_size+1, sizeof( uint16_t ) );
754
755             /* Walk table */
756             size_t i_idx = 0;
757             lua_pushnil( L );
758             while( lua_next( L, -2 ) != 0 )
759             {
760                 assert( i_idx < i_size );
761                 if( (!lua_isstring( L, -1 )) || (!lua_isnumber( L, -2 )) )
762                 {
763                     msg_Warn( p_mgr, "In script %s, an entry in "
764                               "the menu table is invalid!", p_ext->psz_name );
765                     goto exit;
766                 }
767                 (*pppsz_titles)[ i_idx ] = strdup( luaL_checkstring( L, -1 ) );
768                 (*ppi_ids)[ i_idx ] = (uint16_t) ( luaL_checkinteger( L, -2 ) & 0xFFFF );
769                 i_idx++;
770                 lua_pop( L, 1 );
771             }
772         }
773         else
774         {
775             msg_Warn( p_mgr, "Function menu() in script %s "
776                       "did not return a table", p_ext->psz_name );
777             goto exit;
778         }
779     }
780     else
781     {
782         msg_Warn( p_mgr, "Script %s went completely foobar", p_ext->psz_name );
783         goto exit;
784     }
785
786     i_ret = VLC_SUCCESS;
787
788 exit:
789     UnlockExtension( p_ext );
790     if( i_ret != VLC_SUCCESS )
791     {
792         msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
793                  __func__, __FILE__, __LINE__ );
794     }
795     return i_ret;
796 }
797
798 /* Must be entered with the Lock on Extension */
799 static lua_State* GetLuaState( extensions_manager_t *p_mgr,
800                                extension_t *p_ext )
801 {
802     lua_State *L = NULL;
803     if( p_ext )
804         L = p_ext->p_sys->L;
805
806     if( !L )
807     {
808         L = luaL_newstate();
809         if( !L )
810         {
811             msg_Err( p_mgr, "Could not create new Lua State" );
812             return NULL;
813         }
814         vlclua_set_this( L, p_mgr );
815         vlclua_set_playlist_internal( L,
816             pl_Get((intf_thread_t *)(p_mgr->p_parent)) );
817         vlclua_extension_set( L, p_ext );
818
819         luaL_openlibs( L );
820         luaL_register( L, "vlc", p_reg );
821         luaopen_msg( L );
822
823         if( p_ext )
824         {
825             /* Load more libraries */
826             luaopen_config( L );
827             luaopen_dialog( L, p_ext );
828             luaopen_input( L );
829             luaopen_msg( L );
830             if( vlclua_fd_init( L, &p_ext->p_sys->dtable ) )
831             {
832                 lua_close( L );
833                 return NULL;
834             }
835             luaopen_object( L );
836             luaopen_osd( L );
837             luaopen_playlist( L );
838             luaopen_sd_intf( L );
839             luaopen_stream( L );
840             luaopen_strings( L );
841             luaopen_variables( L );
842             luaopen_video( L );
843             luaopen_vlm( L );
844             luaopen_volume( L );
845             luaopen_xml( L );
846 #if defined(_WIN32) && !VLC_WINSTORE_APP
847             luaopen_win( L );
848 #endif
849
850             /* Register extension specific functions */
851             lua_getglobal( L, "vlc" );
852             lua_pushcfunction( L, vlclua_extension_deactivate );
853             lua_setfield( L, -2, "deactivate" );
854             lua_pushcfunction( L, vlclua_extension_keep_alive );
855             lua_setfield( L, -2, "keep_alive" );
856
857             /* Setup the module search path */
858             if( !strncmp( p_ext->psz_name, "zip://", 6 ) )
859             {
860                 /* Load all required modules manually */
861                 lua_register( L, "require", &vlclua_extension_require );
862             }
863             else
864             {
865                 if( vlclua_add_modules_path( L, p_ext->psz_name ) )
866                 {
867                     msg_Warn( p_mgr, "Error while setting the module "
868                               "search path for %s", p_ext->psz_name );
869                     vlclua_fd_cleanup( &p_ext->p_sys->dtable );
870                     lua_close( L );
871                     return NULL;
872                 }
873             }
874             /* Load and run the script(s) */
875             if( vlclua_dofile( VLC_OBJECT( p_mgr ), L, p_ext->psz_name ) )
876             {
877                 msg_Warn( p_mgr, "Error loading script %s: %s", p_ext->psz_name,
878                           lua_tostring( L, lua_gettop( L ) ) );
879                 vlclua_fd_cleanup( &p_ext->p_sys->dtable );
880                 lua_close( L );
881                 return NULL;
882             }
883
884             p_ext->p_sys->L = L;
885         }
886     }
887
888     return L;
889 }
890
891 int lua_ExecuteFunction( extensions_manager_t *p_mgr, extension_t *p_ext,
892                             const char *psz_function, ... )
893 {
894     va_list args;
895     va_start( args, psz_function );
896     int i_ret = lua_ExecuteFunctionVa( p_mgr, p_ext, psz_function, args );
897     va_end( args );
898     return i_ret;
899 }
900
901 /**
902  * Execute a function in a Lua script
903  * @param psz_function Name of global function to execute. If NULL, assume
904  *                     that the function object is already on top of the
905  *                     stack.
906  * @return < 0 in case of failure, >= 0 in case of success
907  * @note It's better to call this function from a dedicated thread
908  * (see extension_thread.c)
909  **/
910 int lua_ExecuteFunctionVa( extensions_manager_t *p_mgr, extension_t *p_ext,
911                            const char *psz_function, va_list args )
912 {
913     int i_ret = VLC_SUCCESS;
914     int i_args = 0;
915     assert( p_mgr != NULL );
916     assert( p_ext != NULL );
917
918     lua_State *L = GetLuaState( p_mgr, p_ext );
919     if( !L )
920         return -1;
921
922     if( psz_function )
923         lua_getglobal( L, psz_function );
924
925     if( !lua_isfunction( L, -1 ) )
926     {
927         msg_Warn( p_mgr, "Error while running script %s, "
928                   "function %s() not found", p_ext->psz_name, psz_function );
929         goto exit;
930     }
931
932     lua_datatype_e type = LUA_END;
933     while( ( type = va_arg( args, int ) ) != LUA_END )
934     {
935         if( type == LUA_NUM )
936         {
937             lua_pushnumber( L , ( int ) va_arg( args, int ) );
938         }
939         else if( type == LUA_TEXT )
940         {
941             lua_pushstring( L , ( char * ) va_arg( args, char* ) );
942         }
943         else
944         {
945             msg_Warn( p_mgr, "Undefined argument type %d to lua function %s"
946                    "from script %s", type, psz_function, p_ext->psz_name );
947             goto exit;
948         }
949         i_args ++;
950     }
951
952     // Create watch timer
953     vlc_mutex_lock( &p_ext->p_sys->command_lock );
954     vlc_timer_schedule( p_ext->p_sys->timer, false, WATCH_TIMER_PERIOD, 0 );
955     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
956
957     // Start actual call to Lua
958     if( lua_pcall( L, i_args, 1, 0 ) )
959     {
960         msg_Warn( p_mgr, "Error while running script %s, "
961                   "function %s(): %s", p_ext->psz_name, psz_function,
962                   lua_tostring( L, lua_gettop( L ) ) );
963         i_ret = VLC_EGENERIC;
964     }
965
966     // Reset watch timer and timestamp
967     vlc_mutex_lock( &p_ext->p_sys->command_lock );
968     if( p_ext->p_sys->progress )
969     {
970         dialog_ProgressDestroy( p_ext->p_sys->progress );
971         p_ext->p_sys->progress = NULL;
972     }
973     vlc_timer_schedule( p_ext->p_sys->timer, false, 0, 0 );
974     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
975
976     i_ret |= lua_DialogFlush( L );
977
978 exit:
979     return i_ret;
980
981 }
982
983 static inline int TriggerMenu( extension_t *p_ext, int i_id )
984 {
985     return PushCommand( p_ext, CMD_TRIGGERMENU, i_id );
986 }
987
988 int lua_ExtensionTriggerMenu( extensions_manager_t *p_mgr,
989                               extension_t *p_ext, int id )
990 {
991     int i_ret = VLC_SUCCESS;
992     lua_State *L = GetLuaState( p_mgr, p_ext );
993
994     if( !L )
995         return VLC_EGENERIC;
996
997     luaopen_dialog( L, p_ext );
998
999     lua_getglobal( L, "trigger_menu" );
1000     if( !lua_isfunction( L, -1 ) )
1001     {
1002         msg_Warn( p_mgr, "Error while running script %s, "
1003                   "function trigger_menu() not found", p_ext->psz_name );
1004         return VLC_EGENERIC;
1005     }
1006
1007     /* Pass id as unique argument to the function */
1008     lua_pushinteger( L, id );
1009
1010     // Create watch timer
1011     vlc_mutex_lock( &p_ext->p_sys->command_lock );
1012     vlc_timer_schedule( p_ext->p_sys->timer, false, WATCH_TIMER_PERIOD, 0 );
1013     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1014
1015     if( lua_pcall( L, 1, 1, 0 ) != 0 )
1016     {
1017         msg_Warn( p_mgr, "Error while running script %s, "
1018                   "function trigger_menu(): %s", p_ext->psz_name,
1019                   lua_tostring( L, lua_gettop( L ) ) );
1020         i_ret = VLC_EGENERIC;
1021     }
1022
1023     // Reset watch timer and timestamp
1024     vlc_mutex_lock( &p_ext->p_sys->command_lock );
1025     if( p_ext->p_sys->progress )
1026     {
1027         dialog_ProgressDestroy( p_ext->p_sys->progress );
1028         p_ext->p_sys->progress = NULL;
1029     }
1030     vlc_timer_schedule( p_ext->p_sys->timer, false, 0, 0 );
1031     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1032
1033     i_ret |= lua_DialogFlush( L );
1034     if( i_ret < VLC_SUCCESS )
1035     {
1036         msg_Dbg( p_mgr, "Something went wrong in %s (%s:%d)",
1037                  __func__, __FILE__, __LINE__ );
1038     }
1039
1040     return i_ret;
1041 }
1042
1043 /** Directly trigger an extension, without activating it
1044  * This is NOT multithreaded, and this code runs in the UI thread
1045  * @param p_mgr
1046  * @param p_ext Extension to trigger
1047  * @return Value returned by the lua function "trigger"
1048  **/
1049 static int TriggerExtension( extensions_manager_t *p_mgr,
1050                              extension_t *p_ext )
1051 {
1052     int i_ret = lua_ExecuteFunction( p_mgr, p_ext, "trigger", LUA_END );
1053
1054     /* Close lua state for trigger-only extensions */
1055     if( p_ext->p_sys->L )
1056     {
1057         vlclua_fd_cleanup( &p_ext->p_sys->dtable );
1058         lua_close( p_ext->p_sys->L );
1059     }
1060     p_ext->p_sys->L = NULL;
1061
1062     return i_ret;
1063 }
1064
1065 /** Set extension associated to the current script
1066  * @param L current lua_State
1067  * @param p_ext the extension
1068  */
1069 void vlclua_extension_set( lua_State *L, extension_t *p_ext )
1070 {
1071     lua_pushlightuserdata( L, vlclua_extension_set );
1072     lua_pushlightuserdata( L, p_ext );
1073     lua_rawset( L, LUA_REGISTRYINDEX );
1074 }
1075
1076 /** Retrieve extension associated to the current script
1077  * @param L current lua_State
1078  * @return Extension pointer
1079  **/
1080 extension_t *vlclua_extension_get( lua_State *L )
1081 {
1082     lua_pushlightuserdata( L, vlclua_extension_set );
1083     lua_rawget( L, LUA_REGISTRYINDEX );
1084     extension_t *p_ext = (extension_t*) lua_topointer( L, -1 );
1085     lua_pop( L, 1 );
1086     return p_ext;
1087 }
1088
1089 /** Deactivate an extension by order from the extension itself
1090  * @param L lua_State
1091  * @note This is an asynchronous call. A script calling vlc.deactivate() will
1092  * be executed to the end before the last call to deactivate() is done.
1093  **/
1094 int vlclua_extension_deactivate( lua_State *L )
1095 {
1096     extension_t *p_ext = vlclua_extension_get( L );
1097     int i_ret = Deactivate( p_ext->p_sys->p_mgr, p_ext );
1098     return ( i_ret == VLC_SUCCESS ) ? 1 : 0;
1099 }
1100
1101 /** Keep an extension alive. This resets the watch timer to 0
1102  * @param L lua_State
1103  * @note This is the "vlc.keep_alive()" function
1104  **/
1105 int vlclua_extension_keep_alive( lua_State *L )
1106 {
1107     extension_t *p_ext = vlclua_extension_get( L );
1108
1109     vlc_mutex_lock( &p_ext->p_sys->command_lock );
1110     if( p_ext->p_sys->progress )
1111     {
1112         dialog_ProgressDestroy( p_ext->p_sys->progress );
1113         p_ext->p_sys->progress = NULL;
1114     }
1115     vlc_timer_schedule( p_ext->p_sys->timer, false, WATCH_TIMER_PERIOD, 0 );
1116     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1117
1118     return 1;
1119 }
1120
1121 /** Callback for the variable "dialog-event"
1122  * @param p_this Current object owner of the extension and the dialog
1123  * @param psz_var "dialog-event"
1124  * @param oldval Unused
1125  * @param newval Address of the dialog
1126  * @param p_data Unused
1127  **/
1128 static int vlclua_extension_dialog_callback( vlc_object_t *p_this,
1129                                              char const *psz_var,
1130                                              vlc_value_t oldval,
1131                                              vlc_value_t newval,
1132                                              void *p_data )
1133 {
1134     /* psz_var == "dialog-event" */
1135     ( void ) psz_var;
1136     ( void ) oldval;
1137     ( void ) p_data;
1138
1139     extension_dialog_command_t *command = newval.p_address;
1140     assert( command != NULL );
1141     assert( command->p_dlg != NULL);
1142
1143     extension_t *p_ext = command->p_dlg->p_sys;
1144     assert( p_ext != NULL );
1145
1146     extension_widget_t *p_widget = command->p_data;
1147
1148     switch( command->event )
1149     {
1150         case EXTENSION_EVENT_CLICK:
1151             assert( p_widget != NULL );
1152             PushCommandUnique( p_ext, CMD_CLICK, p_widget );
1153             break;
1154         case EXTENSION_EVENT_CLOSE:
1155             PushCommandUnique( p_ext, CMD_CLOSE );
1156             break;
1157         default:
1158             msg_Dbg( p_this, "Received unknown UI event %d, discarded",
1159                      command->event );
1160             break;
1161     }
1162
1163     return VLC_SUCCESS;
1164 }
1165
1166 /** Callback on vlc_InputItemMetaChanged event
1167  **/
1168 static void inputItemMetaChanged( const vlc_event_t *p_event,
1169                                   void *data )
1170 {
1171     assert( p_event && p_event->type == vlc_InputItemMetaChanged );
1172
1173     extension_t *p_ext = ( extension_t* ) data;
1174     assert( p_ext != NULL );
1175
1176     PushCommandUnique( p_ext, CMD_UPDATE_META );
1177 }
1178
1179 /** Lock this extension. Can fail. */
1180 bool LockExtension( extension_t *p_ext )
1181 {
1182     vlc_mutex_lock( &p_ext->p_sys->command_lock );
1183     if( p_ext->p_sys->b_exiting )
1184     {
1185         vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1186         return false;
1187     }
1188
1189     vlc_mutex_lock( &p_ext->p_sys->running_lock );
1190     if( p_ext->p_sys->b_exiting )
1191     {
1192         vlc_mutex_unlock( &p_ext->p_sys->running_lock );
1193         vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1194         return false;
1195     }
1196
1197     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1198     return true;
1199 }
1200
1201 /** Unlock this extension. */
1202 void UnlockExtension( extension_t *p_ext )
1203 {
1204     vlc_mutex_unlock( &p_ext->p_sys->running_lock );
1205 }
1206
1207 /** Watch timer callback
1208  * The timer expired, Lua may be stuck, ask the user what to do now
1209  **/
1210 static void WatchTimerCallback( void *data )
1211 {
1212     extension_t *p_ext = data;
1213     extensions_manager_t *p_mgr = p_ext->p_sys->p_mgr;
1214
1215     char *message;
1216     if( asprintf( &message, _( "Extension '%s' does not respond.\n"
1217                                "Do you want to kill it now? " ),
1218                   p_ext->psz_title ) == -1 )
1219     {
1220         return;
1221     }
1222
1223     vlc_mutex_lock( &p_ext->p_sys->command_lock );
1224
1225     for( struct command_t *cmd = p_ext->p_sys->command;
1226          cmd != NULL;
1227          cmd = cmd->next )
1228         if( cmd->i_command == CMD_DEACTIVATE )
1229         {   /* We have a pending Deactivate command... */
1230             if( p_ext->p_sys->progress )
1231             {
1232                 dialog_ProgressDestroy( p_ext->p_sys->progress );
1233                 p_ext->p_sys->progress = NULL;
1234             }
1235             vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1236             KillExtension( p_mgr, p_ext );
1237             return;
1238         }
1239
1240     if( !p_ext->p_sys->progress )
1241     {
1242         p_ext->p_sys->progress =
1243                 dialog_ProgressCreate( p_mgr, _( "Extension not responding!" ),
1244                                        message,
1245                                        _( "Yes" ) );
1246         vlc_timer_schedule( p_ext->p_sys->timer, false, 100000, 0 );
1247     }
1248     else
1249     {
1250         if( dialog_ProgressCancelled( p_ext->p_sys->progress ) )
1251         {
1252             dialog_ProgressDestroy( p_ext->p_sys->progress );
1253             p_ext->p_sys->progress = NULL;
1254             vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1255             KillExtension( p_mgr, p_ext );
1256             return;
1257         }
1258         vlc_timer_schedule( p_ext->p_sys->timer, false, 100000, 0 );
1259     }
1260     vlc_mutex_unlock( &p_ext->p_sys->command_lock );
1261 }