]> git.sesse.net Git - vlc/blob - projects/mozilla/vlcplugin.cpp
Mozilla: more fixes
[vlc] / projects / mozilla / vlcplugin.cpp
1 /*****************************************************************************
2  * vlcplugin.cpp: a VLC plugin for Mozilla
3  *****************************************************************************
4  * Copyright (C) 2002-2010 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *          Damien Fouilleul <damienf.fouilleul@laposte.net>
9  *          Jean-Paul Saman <jpsaman@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include "config.h"
30
31 #ifdef HAVE_MOZILLA_CONFIG_H
32 #   include <mozilla-config.h>
33 #endif
34
35 #include "vlcplugin.h"
36 #include "control/npolibvlc.h"
37
38 #include <ctype.h>
39 #if defined(XP_UNIX)
40 #   include <pthread.h>
41 #elif defined(XP_WIN)
42     /* windows headers */
43 #   include <winbase.h>
44 #else
45 #warning "locking not implemented for this platform"
46 #endif
47
48 #include <stdio.h>
49 #include <assert.h>
50 #include <stdlib.h>
51
52 /*****************************************************************************
53  * utilitiy functions
54  *****************************************************************************/
55 static void plugin_lock_init(plugin_lock_t *lock)
56 {
57     assert(lock);
58
59 #if defined(XP_UNIX)
60     pthread_mutex_init(&lock->mutex, NULL);
61 #elif defined(XP_WIN)
62     InitializeCriticalSection(&lock->cs);
63 #else
64 #warning "locking not implemented in this platform"
65 #endif
66 }
67
68 static void plugin_lock_destroy(plugin_lock_t *lock)
69 {
70     assert(lock);
71
72 #if defined(XP_UNIX)
73     pthread_mutex_destroy(&lock->mutex);
74 #elif defined(XP_WIN)
75     DeleteCriticalSection(&lock->cs);
76 #else
77 #warning "locking not implemented in this platform"
78 #endif
79 }
80
81 static void plugin_lock(plugin_lock_t *lock)
82 {
83     assert(lock);
84
85 #if defined(XP_UNIX)
86     pthread_mutex_lock(&lock->mutex);
87 #elif defined(XP_WIN)
88     EnterCriticalSection(&lock->cs);
89 #else
90 #warning "locking not implemented in this platform"
91 #endif
92 }
93
94 static void plugin_unlock(plugin_lock_t *lock)
95 {
96     assert(lock);
97
98 #if defined(XP_UNIX)
99     pthread_mutex_unlock(&lock->mutex);
100 #elif defined(XP_WIN)
101     LeaveCriticalSection(&lock->cs);
102 #else
103 #warning "locking not implemented in this platform"
104 #endif
105 }
106
107 /*****************************************************************************
108  * VlcPlugin constructor and destructor
109  *****************************************************************************/
110 #if (((NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR) < 20)
111 VlcPlugin::VlcPlugin( NPP instance, uint16 mode ) :
112 #else
113 VlcPlugin::VlcPlugin( NPP instance, uint16_t mode ) :
114 #endif
115     i_npmode(mode),
116     b_stream(0),
117     b_autoplay(1),
118     b_toolbar(0),
119     psz_text(NULL),
120     psz_target(NULL),
121     playlist_index(-1),
122     libvlc_instance(NULL),
123     libvlc_media_list(NULL),
124     libvlc_media_player(NULL),
125     p_scriptClass(NULL),
126     p_browser(instance),
127     psz_baseURL(NULL)
128 #if defined(XP_WIN)
129     ,pf_wndproc(NULL)
130 #endif
131 #if defined(XP_UNIX)
132     ,i_width((unsigned)-1)
133     ,i_height((unsigned)-1)
134     ,i_tb_width(0)
135     ,i_tb_height(0)
136     ,i_last_position(0)
137     ,p_btnPlay(NULL)
138     ,p_btnPause(NULL)
139     ,p_btnStop(NULL)
140     ,p_btnMute(NULL)
141     ,p_btnUnmute(NULL)
142     ,p_btnFullscreen(NULL)
143     ,p_btnTime(NULL)
144     ,p_timeline(NULL)
145 #endif
146 {
147     memset(&npwindow, 0, sizeof(NPWindow));
148 #if defined(XP_UNIX)
149     memset(&npvideo, 0, sizeof(Window));
150     memset(&npcontrol, 0, sizeof(Window));
151 #endif
152 }
153
154 static bool boolValue(const char *value) {
155     return ( !strcmp(value, "1") ||
156              !strcasecmp(value, "true") ||
157              !strcasecmp(value, "yes") );
158 }
159
160 bool EventObj::init()
161 {
162     plugin_lock_init(&lock);
163     return true;
164 }
165
166 EventObj::~EventObj()
167 {
168     plugin_lock_destroy(&lock);
169 }
170
171 void EventObj::deliver(NPP browser)
172 {
173     NPVariant result;
174     NPVariant params[1];
175
176     plugin_lock(&lock);
177
178     for( ev_l::iterator i=_elist.begin();i!=_elist.end();++i )
179     {
180         libvlc_event_type_t event = *i;
181         STRINGZ_TO_NPVARIANT(libvlc_event_type_name(event), params[0]);
182
183         // Invalid events aren't supposed to be queued up.
184         // if( !have_event(event) ) continue;
185
186         for( lr_l::iterator j=_llist.begin();j!=_llist.end();++j )
187         {
188             if (j->get(event))
189             {
190                 NPN_InvokeDefault(browser, j->listener(), params, 1, &result);
191                 NPN_ReleaseVariantValue(&result);
192             }
193         }
194     }
195     _elist.clear();
196
197     plugin_unlock(&lock);
198 }
199
200 void VlcPlugin::eventAsync(void *param)
201 {
202     VlcPlugin *plugin = (VlcPlugin*)param;
203     plugin->events.deliver(plugin->getBrowser());
204 }
205
206 void EventObj::callback(const libvlc_event_t* event)
207 {
208     plugin_lock(&lock);
209
210     if( have_event(event->type) )
211         _elist.push_back(event->type);
212
213     plugin_unlock(&lock);
214 }
215
216 void VlcPlugin::event_callback(const libvlc_event_t* event, void *param)
217 {
218     VlcPlugin *plugin = (VlcPlugin*)param;
219 #if defined(XP_UNIX)
220     plugin->events.callback(event);
221     NPN_PluginThreadAsyncCall(plugin->getBrowser(), eventAsync, plugin);
222 #else
223 #warning NPN_PluginThreadAsyncCall not implemented yet.
224     printf("No NPN_PluginThreadAsyncCall(), doing nothing.");
225 #endif
226 }
227
228 inline EventObj::event_t EventObj::find_event(const char *s) const
229 {
230     event_t i;
231     for(i=0;i<maxbit();++i)
232         if(!strcmp(s,libvlc_event_type_name(i)))
233             break;
234     return i;
235 }
236
237 bool EventObj::insert(const NPString &s, NPObject *l, bool b)
238 {
239     event_t e = find_event(s.utf8characters);
240     if( e>=maxbit() )
241         return false;
242
243     if( !have_event(e) && !ask_for_event(e) )
244         return false;
245
246     lr_l::iterator i;
247     for(i=_llist.begin();i!=_llist.end();++i)
248         if(i->listener()==l && i->bubble()==b)
249             break;
250
251     if( i == _llist.end() ) {
252         _llist.push_back(Listener(e,l,b));
253     } else {
254         if( i->get(e) )
255             return false;
256         i->get(e);
257     }
258     return true;
259 }
260
261
262 bool EventObj::remove(const NPString &s, NPObject *l, bool b)
263 {
264     event_t e = find_event(s.utf8characters);
265     if( e>=maxbit() || !get(e) )
266         return false;
267
268     bool any=false;
269     for(lr_l::iterator i=_llist.begin();i!=_llist.end();)
270     {
271         if(i->listener()!=l || i->bubble()!=b)
272             any|=i->get(e);
273         else
274         {
275             i->reset(e);
276             if(i->empty())
277             {
278                 i=_llist.erase(i);
279                 continue;
280             }
281         }
282         ++i;
283     }
284     if(!any)
285         unask_for_event(e);
286
287     return true;
288 }
289
290
291 void EventObj::hook_manager(libvlc_event_manager_t *em,
292                             libvlc_callback_t cb, void *udata)
293 {
294     _em = em; _cb = cb; _ud = udata;
295     if( !_em )
296         return;
297     for(size_t i=0;i<maxbit();++i)
298         if(get(i))
299             libvlc_event_attach(_em, i, _cb, _ud);
300 }
301
302 void EventObj::unhook_manager()
303 {
304     if( !_em )
305     return;
306     for(size_t i=0;i<maxbit();++i)
307         if(get(i))
308             libvlc_event_detach(_em, i, _cb, _ud);
309 }
310
311
312 bool EventObj::ask_for_event(event_t e)
313 {
314     return _em?0==libvlc_event_attach(_em, e, _cb, _ud):false;
315 }
316
317
318 void EventObj::unask_for_event(event_t e)
319 {
320     if(_em) libvlc_event_detach(_em, e, _cb, _ud);
321 }
322
323
324 NPError VlcPlugin::init(int argc, char* const argn[], char* const argv[])
325 {
326     /* prepare VLC command line */
327     const char *ppsz_argv[32];
328     int ppsz_argc = 0;
329
330 #ifndef NDEBUG
331     ppsz_argv[ppsz_argc++] = "--no-plugins-cache";
332 #endif
333
334     /* locate VLC module path */
335 #ifdef XP_MACOSX
336     ppsz_argv[ppsz_argc++] = "--plugin-path=/Library/Internet\\ Plug-Ins/VLC\\ Plugin.plugin/Contents/MacOS/modules";
337     ppsz_argv[ppsz_argc++] = "--vout=minimal_macosx";
338 #elif defined(XP_WIN)
339     HKEY h_key;
340     DWORD i_type, i_data = MAX_PATH + 1;
341     char p_data[MAX_PATH + 1];
342     if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Software\\VideoLAN\\VLC",
343                       0, KEY_READ, &h_key ) == ERROR_SUCCESS )
344     {
345          if( RegQueryValueEx( h_key, "InstallDir", 0, &i_type,
346                               (LPBYTE)p_data, &i_data ) == ERROR_SUCCESS )
347          {
348              if( i_type == REG_SZ )
349              {
350                  strcat( p_data, "\\plugins" );
351                  ppsz_argv[ppsz_argc++] = "--plugin-path";
352                  ppsz_argv[ppsz_argc++] = p_data;
353              }
354          }
355          RegCloseKey( h_key );
356     }
357     ppsz_argv[ppsz_argc++] = "--no-one-instance";
358
359 #endif /* XP_MACOSX */
360
361     /* common settings */
362     ppsz_argv[ppsz_argc++] = "-vv";
363     ppsz_argv[ppsz_argc++] = "--no-stats";
364     ppsz_argv[ppsz_argc++] = "--no-media-library";
365     ppsz_argv[ppsz_argc++] = "--intf=dummy";
366     ppsz_argv[ppsz_argc++] = "--no-video-title-show";
367
368     const char *progid = NULL;
369
370     /* parse plugin arguments */
371     for( int i = 0; (i < argc) && (ppsz_argc < 32); i++ )
372     {
373        /* fprintf(stderr, "argn=%s, argv=%s\n", argn[i], argv[i]); */
374
375         if( !strcmp( argn[i], "target" )
376          || !strcmp( argn[i], "mrl")
377          || !strcmp( argn[i], "filename")
378          || !strcmp( argn[i], "src") )
379         {
380             psz_target = argv[i];
381         }
382         else if( !strcmp( argn[i], "text" ) )
383         {
384             free( psz_text );
385             psz_text = strdup( argv[i] );
386         }
387         else if( !strcmp( argn[i], "autoplay")
388               || !strcmp( argn[i], "autostart") )
389         {
390             b_autoplay = boolValue(argv[i]);
391         }
392         else if( !strcmp( argn[i], "fullscreen" ) )
393         {
394             if( boolValue(argv[i]) )
395             {
396                 ppsz_argv[ppsz_argc++] = "--fullscreen";
397             }
398             else
399             {
400                 ppsz_argv[ppsz_argc++] = "--no-fullscreen";
401             }
402         }
403         else if( !strcmp( argn[i], "mute" ) )
404         {
405             if( boolValue(argv[i]) )
406             {
407                 ppsz_argv[ppsz_argc++] = "--volume=0";
408             }
409         }
410         else if( !strcmp( argn[i], "loop")
411               || !strcmp( argn[i], "autoloop") )
412         {
413             if( boolValue(argv[i]) )
414             {
415                 ppsz_argv[ppsz_argc++] = "--loop";
416             }
417             else
418             {
419                 ppsz_argv[ppsz_argc++] = "--no-loop";
420             }
421         }
422         else if( !strcmp( argn[i], "version")
423               || !strcmp( argn[i], "progid") )
424         {
425             progid = argv[i];
426         }
427         else if( !strcmp( argn[i], "toolbar" ) )
428         {
429 /* FIXME: Remove this when toolbar functionality has been implemented on
430  * MacOS X and Win32 for Firefox/Mozilla/Safari. */
431 #ifdef XP_UNIX
432             b_toolbar = boolValue(argv[i]);
433 #endif
434         }
435     }
436
437     libvlc_instance = libvlc_new(ppsz_argc, ppsz_argv);
438     if( !libvlc_instance )
439         return NPERR_GENERIC_ERROR;
440     libvlc_media_list = libvlc_media_list_new(libvlc_instance);
441
442     /*
443     ** fetch plugin base URL, which is the URL of the page containing the plugin
444     ** this URL is used for making absolute URL from relative URL that may be
445     ** passed as an MRL argument
446     */
447     NPObject *plugin = NULL;
448
449     if( NPERR_NO_ERROR == NPN_GetValue(p_browser, NPNVWindowNPObject, &plugin) )
450     {
451         /*
452         ** is there a better way to get that info ?
453         */
454         static const char docLocHref[] = "document.location.href";
455         NPString script;
456         NPVariant result;
457
458         script.UTF8Characters = docLocHref;
459         script.UTF8Length = sizeof(docLocHref)-1;
460
461         if( NPN_Evaluate(p_browser, plugin, &script, &result) )
462         {
463             if( NPVARIANT_IS_STRING(result) )
464             {
465                 NPString &location = NPVARIANT_TO_STRING(result);
466
467                 psz_baseURL = (char *) malloc(location.UTF8Length+1);
468                 if( psz_baseURL )
469                 {
470                     strncpy(psz_baseURL, location.UTF8Characters, location.UTF8Length);
471                     psz_baseURL[location.UTF8Length] = '\0';
472                 }
473             }
474             NPN_ReleaseVariantValue(&result);
475         }
476         NPN_ReleaseObject(plugin);
477     }
478
479     if( psz_target )
480     {
481         // get absolute URL from src
482         char *psz_absurl = getAbsoluteURL(psz_target);
483         psz_target = psz_absurl ? psz_absurl : strdup(psz_target);
484     }
485
486     /* assign plugin script root class */
487     /* new APIs */
488     p_scriptClass = RuntimeNPClass<LibvlcRootNPObject>::getClass();
489
490     if( !events.init() )
491         return NPERR_GENERIC_ERROR;
492
493     return NPERR_NO_ERROR;
494 }
495
496 VlcPlugin::~VlcPlugin()
497 {
498     free(psz_baseURL);
499     free(psz_target);
500     free(psz_text);
501
502     if( libvlc_media_player )
503     {
504         if( playlist_isplaying() )
505             playlist_stop();
506         events.unhook_manager();
507         libvlc_media_player_release( libvlc_media_player );
508     }
509     if( libvlc_media_list )
510         libvlc_media_list_release( libvlc_media_list );
511     if( libvlc_instance )
512         libvlc_release(libvlc_instance);
513 }
514
515 /*****************************************************************************
516  * VlcPlugin playlist replacement methods
517  *****************************************************************************/
518 void VlcPlugin::set_player_window()
519 {
520 #ifdef XP_UNIX
521     libvlc_media_player_set_xwindow(libvlc_media_player,
522                                     (uint32_t)getVideoWindow());
523 #endif
524 #ifdef XP_MACOSX
525     // XXX FIXME insert appropriate call here
526 #endif
527 #ifdef XP_WIN
528     libvlc_media_player_set_hwnd(libvlc_media_player,
529                                  getWindow().window);
530 #endif
531 }
532
533 int VlcPlugin::playlist_add( const char *mrl )
534 {
535     int item = -1;
536     libvlc_media_t *p_m = libvlc_media_new_location(libvlc_instance,mrl);
537     if( !p_m )
538         return -1;
539     assert( libvlc_media_list );
540     libvlc_media_list_lock(libvlc_media_list);
541     if( !libvlc_media_list_add_media(libvlc_media_list,p_m) )
542         item = libvlc_media_list_count(libvlc_media_list)-1;
543     libvlc_media_list_unlock(libvlc_media_list);
544
545     libvlc_media_release(p_m);
546
547     return item;
548 }
549
550 int VlcPlugin::playlist_add_extended_untrusted( const char *mrl, const char *name,
551                     int optc, const char **optv )
552 {
553     libvlc_media_t *p_m;
554     int item = -1;
555
556     assert( libvlc_media_list );
557
558     p_m = libvlc_media_new_location(libvlc_instance, mrl);
559     if( !p_m )
560         return -1;
561
562     for( int i = 0; i < optc; ++i )
563         libvlc_media_add_option_flag(p_m, optv[i], libvlc_media_option_unique);
564
565     libvlc_media_list_lock(libvlc_media_list);
566     if( !libvlc_media_list_add_media(libvlc_media_list,p_m) )
567         item = libvlc_media_list_count(libvlc_media_list)-1;
568     libvlc_media_list_unlock(libvlc_media_list);
569     libvlc_media_release(p_m);
570
571     return item;
572 }
573
574 bool VlcPlugin::playlist_select( int idx )
575 {
576     libvlc_media_t *p_m = NULL;
577
578     assert( libvlc_media_list );
579
580     libvlc_media_list_lock(libvlc_media_list);
581
582     int count = libvlc_media_list_count(libvlc_media_list);
583     if( idx<0||idx>=count )
584         goto bad_unlock;
585
586     playlist_index = idx;
587
588     p_m = libvlc_media_list_item_at_index(libvlc_media_list,playlist_index);
589     libvlc_media_list_unlock(libvlc_media_list);
590
591     if( !p_m )
592         return false;
593
594     if( libvlc_media_player )
595     {
596         if( playlist_isplaying() )
597             playlist_stop();
598         events.unhook_manager();
599         libvlc_media_player_release( libvlc_media_player );
600         libvlc_media_player = NULL;
601     }
602
603     libvlc_media_player = libvlc_media_player_new_from_media(p_m);
604     if( libvlc_media_player )
605     {
606         set_player_window();
607         events.hook_manager(
608                       libvlc_media_player_event_manager(libvlc_media_player),
609                       event_callback, this);
610     }
611
612     libvlc_media_release( p_m );
613     return true;
614
615 bad_unlock:
616     libvlc_media_list_unlock(libvlc_media_list);
617     return false;
618 }
619
620 int VlcPlugin::playlist_delete_item( int idx )
621 {
622     if( !libvlc_media_list )
623         return -1;
624     libvlc_media_list_lock(libvlc_media_list);
625     int ret = libvlc_media_list_remove_index(libvlc_media_list,idx);
626     libvlc_media_list_unlock(libvlc_media_list);
627     return ret;
628 }
629
630 void VlcPlugin::playlist_clear()
631 {
632     if( libvlc_media_list )
633         libvlc_media_list_release(libvlc_media_list);
634     libvlc_media_list = libvlc_media_list_new(getVLC());
635 }
636
637 int VlcPlugin::playlist_count()
638 {
639     int items_count = 0;
640     if( !libvlc_media_list )
641         return items_count;
642     libvlc_media_list_lock(libvlc_media_list);
643     items_count = libvlc_media_list_count(libvlc_media_list);
644     libvlc_media_list_unlock(libvlc_media_list);
645     return items_count;
646 }
647
648 void VlcPlugin::toggle_fullscreen()
649 {
650     if( playlist_isplaying() )
651         libvlc_toggle_fullscreen(libvlc_media_player);
652 }
653
654 void VlcPlugin::set_fullscreen( int yes )
655 {
656     if( playlist_isplaying() )
657         libvlc_set_fullscreen(libvlc_media_player,yes);
658 }
659
660 int  VlcPlugin::get_fullscreen()
661 {
662     int r = 0;
663     if( playlist_isplaying() )
664         r = libvlc_get_fullscreen(libvlc_media_player);
665     return r;
666 }
667
668 bool  VlcPlugin::player_has_vout()
669 {
670     bool r = false;
671     if( playlist_isplaying() )
672         r = libvlc_media_player_has_vout(libvlc_media_player);
673     return r;
674 }
675
676 /*****************************************************************************
677  * VlcPlugin methods
678  *****************************************************************************/
679
680 char *VlcPlugin::getAbsoluteURL(const char *url)
681 {
682     if( NULL != url )
683     {
684         // check whether URL is already absolute
685         const char *end=strchr(url, ':');
686         if( (NULL != end) && (end != url) )
687         {
688             // validate protocol header
689             const char *start = url;
690             char c = *start;
691             if( isalpha(c) )
692             {
693                 ++start;
694                 while( start != end )
695                 {
696                     c  = *start;
697                     if( ! (isalnum(c)
698                        || ('-' == c)
699                        || ('+' == c)
700                        || ('.' == c)
701                        || ('/' == c)) ) /* VLC uses / to allow user to specify a demuxer */
702                         // not valid protocol header, assume relative URL
703                         goto relativeurl;
704                     ++start;
705                 }
706                 /* we have a protocol header, therefore URL is absolute */
707                 return strdup(url);
708             }
709             // not a valid protocol header, assume relative URL
710         }
711
712 relativeurl:
713
714         if( psz_baseURL )
715         {
716             size_t baseLen = strlen(psz_baseURL);
717             char *href = (char *) malloc(baseLen+strlen(url)+1);
718             if( href )
719             {
720                 /* prepend base URL */
721                 memcpy(href, psz_baseURL, baseLen+1);
722
723                 /*
724                 ** relative url could be empty,
725                 ** in which case return base URL
726                 */
727                 if( '\0' == *url )
728                     return href;
729
730                 /*
731                 ** locate pathname part of base URL
732                 */
733
734                 /* skip over protocol part  */
735                 char *pathstart = strchr(href, ':');
736                 char *pathend = href+baseLen;
737                 if( pathstart )
738                 {
739                     if( '/' == *(++pathstart) )
740                     {
741                         if( '/' == *(++pathstart) )
742                         {
743                             ++pathstart;
744                         }
745                     }
746                     /* skip over host part */
747                     pathstart = strchr(pathstart, '/');
748                     if( ! pathstart )
749                     {
750                         // no path, add a / past end of url (over '\0')
751                         pathstart = pathend;
752                         *pathstart = '/';
753                     }
754                 }
755                 else
756                 {
757                     /* baseURL is just a UNIX path */
758                     if( '/' != *href )
759                     {
760                         /* baseURL is not an absolute path */
761                         free(href);
762                         return NULL;
763                     }
764                     pathstart = href;
765                 }
766
767                 /* relative URL made of an absolute path ? */
768                 if( '/' == *url )
769                 {
770                     /* replace path completely */
771                     strcpy(pathstart, url);
772                     return href;
773                 }
774
775                 /* find last path component and replace it */
776                 while( '/' != *pathend)
777                     --pathend;
778
779                 /*
780                 ** if relative url path starts with one or more '../',
781                 ** factor them out of href so that we return a
782                 ** normalized URL
783                 */
784                 while( pathend != pathstart )
785                 {
786                     const char *p = url;
787                     if( '.' != *p )
788                         break;
789                     ++p;
790                     if( '\0' == *p  )
791                     {
792                         /* relative url is just '.' */
793                         url = p;
794                         break;
795                     }
796                     if( '/' == *p  )
797                     {
798                         /* relative url starts with './' */
799                         url = ++p;
800                         continue;
801                     }
802                     if( '.' != *p )
803                         break;
804                     ++p;
805                     if( '\0' == *p )
806                     {
807                         /* relative url is '..' */
808                     }
809                     else
810                     {
811                         if( '/' != *p )
812                             break;
813                         /* relative url starts with '../' */
814                         ++p;
815                     }
816                     url = p;
817                     do
818                     {
819                         --pathend;
820                     }
821                     while( '/' != *pathend );
822                 }
823                 /* skip over '/' separator */
824                 ++pathend;
825                 /* concatenate remaining base URL and relative URL */
826                 strcpy(pathend, url);
827             }
828             return href;
829         }
830     }
831     return NULL;
832 }
833
834 #if defined(XP_UNIX)
835 int  VlcPlugin::setSize(unsigned width, unsigned height)
836 {
837     int diff = (width != i_width) || (height != i_height);
838
839     i_width = width;
840     i_height = height;
841
842     /* return size */
843     return diff;
844 }
845
846 #define BTN_SPACE ((unsigned int)4)
847 void VlcPlugin::showToolbar()
848 {
849     const NPWindow& window = getWindow();
850     Window control = getControlWindow();
851     Window video = getVideoWindow();
852     Display *p_display = ((NPSetWindowCallbackStruct *)window.ws_info)->display;
853     unsigned int i_height = 0, i_width = BTN_SPACE;
854
855     /* load icons */
856     if( !p_btnPlay )
857         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/play.xpm",
858                             &p_btnPlay, NULL, NULL);
859     if( p_btnPlay )
860     {
861         i_height = __MAX( i_height, p_btnPlay->height );
862     }
863     if( !p_btnPause )
864         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/pause.xpm",
865                             &p_btnPause, NULL, NULL);
866     if( p_btnPause )
867     {
868         i_height = __MAX( i_height, p_btnPause->height );
869     }
870     i_width += __MAX( p_btnPause->width, p_btnPlay->width );
871
872     if( !p_btnStop )
873         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/stop.xpm",
874                             &p_btnStop, NULL, NULL );
875     if( p_btnStop )
876     {
877         i_height = __MAX( i_height, p_btnStop->height );
878         i_width += BTN_SPACE + p_btnStop->width;
879     }
880     if( !p_timeline )
881         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/time_line.xpm",
882                             &p_timeline, NULL, NULL);
883     if( p_timeline )
884     {
885         i_height = __MAX( i_height, p_timeline->height );
886         i_width += BTN_SPACE + p_timeline->width;
887     }
888     if( !p_btnTime )
889         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/time_icon.xpm",
890                             &p_btnTime, NULL, NULL);
891     if( p_btnTime )
892     {
893         i_height = __MAX( i_height, p_btnTime->height );
894         i_width += BTN_SPACE + p_btnTime->width;
895     }
896     if( !p_btnFullscreen )
897         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/fullscreen.xpm",
898                             &p_btnFullscreen, NULL, NULL);
899     if( p_btnFullscreen )
900     {
901         i_height = __MAX( i_height, p_btnFullscreen->height );
902         i_width += BTN_SPACE + p_btnFullscreen->width;
903     }
904     if( !p_btnMute )
905         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/volume_max.xpm",
906                             &p_btnMute, NULL, NULL);
907     if( p_btnMute )
908     {
909         i_height = __MAX( i_height, p_btnMute->height );
910     }
911     if( !p_btnUnmute )
912         XpmReadFileToImage( p_display, DATA_PATH "/mozilla/volume_mute.xpm",
913                             &p_btnUnmute, NULL, NULL);
914     if( p_btnUnmute )
915     {
916         i_height = __MAX( i_height, p_btnUnmute->height );
917     }
918     i_width += BTN_SPACE + __MAX( p_btnUnmute->width, p_btnMute->width );
919
920     setToolbarSize( i_width, i_height );
921
922     if( !p_btnPlay || !p_btnPause || !p_btnStop || !p_timeline ||
923         !p_btnTime || !p_btnFullscreen || !p_btnMute || !p_btnUnmute )
924         fprintf(stderr, "Error: some button images not found in %s\n", DATA_PATH );
925
926     /* reset panels position and size */
927     /* XXX  use i_width */
928     XResizeWindow( p_display, video, window.width, window.height - i_height);
929     XMoveWindow( p_display, control, 0, window.height - i_height );
930     XResizeWindow( p_display, control, window.width, i_height -1);
931
932     b_toolbar = 1; /* says toolbar is now shown */
933     redrawToolbar();
934 }
935
936 void VlcPlugin::hideToolbar()
937 {
938     const NPWindow& window = getWindow();
939     Display *p_display = ((NPSetWindowCallbackStruct *)window.ws_info)->display;
940     Window control = getControlWindow();
941     Window video = getVideoWindow();
942
943     i_tb_width = i_tb_height = 0;
944
945     if( p_btnPlay )  XDestroyImage( p_btnPlay );
946     if( p_btnPause ) XDestroyImage( p_btnPause );
947     if( p_btnStop )  XDestroyImage( p_btnStop );
948     if( p_timeline ) XDestroyImage( p_timeline );
949     if( p_btnTime )  XDestroyImage( p_btnTime );
950     if( p_btnFullscreen ) XDestroyImage( p_btnFullscreen );
951     if( p_btnMute )  XDestroyImage( p_btnMute );
952     if( p_btnUnmute ) XDestroyImage( p_btnUnmute );
953
954     p_btnPlay = NULL;
955     p_btnPause = NULL;
956     p_btnStop = NULL;
957     p_timeline = NULL;
958     p_btnTime = NULL;
959     p_btnFullscreen = NULL;
960     p_btnMute = NULL;
961     p_btnUnmute = NULL;
962
963     /* reset panels position and size */
964     /* XXX  use i_width */
965     XResizeWindow( p_display, video, window.width, window.height );
966     XMoveWindow( p_display, control, 0, window.height-1 );
967     XResizeWindow( p_display, control, window.width, 1 );
968
969     b_toolbar = 0; /* says toolbar is now hidden */
970     redrawToolbar();
971 }
972
973 void VlcPlugin::redrawToolbar()
974 {
975     int is_playing = 0;
976     bool b_mute = false;
977     unsigned int dst_x, dst_y;
978     GC gc;
979     XGCValues gcv;
980     unsigned int i_tb_width, i_tb_height;
981
982     /* This method does nothing if toolbar is hidden. */
983     if( !b_toolbar || !libvlc_media_player )
984         return;
985
986     const NPWindow& window = getWindow();
987     Window control = getControlWindow();
988     Display *p_display = ((NPSetWindowCallbackStruct *)window.ws_info)->display;
989
990     getToolbarSize( &i_tb_width, &i_tb_height );
991
992     /* get mute info */
993     b_mute = libvlc_audio_get_mute( libvlc_media_player );
994
995     gcv.foreground = BlackPixel( p_display, 0 );
996     gc = XCreateGC( p_display, control, GCForeground, &gcv );
997
998     XFillRectangle( p_display, control, gc,
999                     0, 0, window.width, i_tb_height );
1000     gcv.foreground = WhitePixel( p_display, 0 );
1001     XChangeGC( p_display, gc, GCForeground, &gcv );
1002
1003     /* position icons */
1004     dst_x = BTN_SPACE;
1005     dst_y = i_tb_height >> 1; /* baseline = vertical middle */
1006
1007     if( p_btnPause && (is_playing == 1) )
1008     {
1009         XPutImage( p_display, control, gc, p_btnPause, 0, 0, dst_x,
1010                    dst_y - (p_btnPause->height >> 1),
1011                    p_btnPause->width, p_btnPause->height );
1012         dst_x += BTN_SPACE + p_btnPause->width;
1013     }
1014     else if( p_btnPlay )
1015     {
1016         XPutImage( p_display, control, gc, p_btnPlay, 0, 0, dst_x,
1017                    dst_y - (p_btnPlay->height >> 1),
1018                    p_btnPlay->width, p_btnPlay->height );
1019         dst_x += BTN_SPACE + p_btnPlay->width;
1020     }
1021
1022     if( p_btnStop )
1023         XPutImage( p_display, control, gc, p_btnStop, 0, 0, dst_x,
1024                    dst_y - (p_btnStop->height >> 1),
1025                    p_btnStop->width, p_btnStop->height );
1026
1027     dst_x += BTN_SPACE + ( p_btnStop ? p_btnStop->width : 0 );
1028
1029     if( p_btnFullscreen )
1030         XPutImage( p_display, control, gc, p_btnFullscreen, 0, 0, dst_x,
1031                    dst_y - (p_btnFullscreen->height >> 1),
1032                    p_btnFullscreen->width, p_btnFullscreen->height );
1033
1034     dst_x += BTN_SPACE + ( p_btnFullscreen ? p_btnFullscreen->width : 0 );
1035
1036     if( p_btnUnmute && b_mute )
1037     {
1038         XPutImage( p_display, control, gc, p_btnUnmute, 0, 0, dst_x,
1039                    dst_y - (p_btnUnmute->height >> 1),
1040                    p_btnUnmute->width, p_btnUnmute->height );
1041
1042         dst_x += BTN_SPACE + ( p_btnUnmute ? p_btnUnmute->width : 0 );
1043     }
1044     else if( p_btnMute )
1045     {
1046         XPutImage( p_display, control, gc, p_btnMute, 0, 0, dst_x,
1047                    dst_y - (p_btnMute->height >> 1),
1048                    p_btnMute->width, p_btnMute->height );
1049
1050         dst_x += BTN_SPACE + ( p_btnMute ? p_btnMute->width : 0 );
1051     }
1052
1053     if( p_timeline )
1054         XPutImage( p_display, control, gc, p_timeline, 0, 0, dst_x,
1055                    dst_y - (p_timeline->height >> 1),
1056                    (window.width-(dst_x+BTN_SPACE)), p_timeline->height );
1057
1058     /* get movie position in % */
1059     if( playlist_isplaying() )
1060     {
1061         i_last_position = (int)((window.width-(dst_x+BTN_SPACE))*
1062                    libvlc_media_player_get_position(libvlc_media_player));
1063     }
1064
1065     if( p_btnTime )
1066         XPutImage( p_display, control, gc, p_btnTime,
1067                    0, 0, (dst_x+i_last_position),
1068                    dst_y - (p_btnTime->height >> 1),
1069                    p_btnTime->width, p_btnTime->height );
1070
1071     XFreeGC( p_display, gc );
1072 }
1073
1074 vlc_toolbar_clicked_t VlcPlugin::getToolbarButtonClicked( int i_xpos, int i_ypos )
1075 {
1076     unsigned int i_dest = BTN_SPACE;
1077     int is_playing = 0;
1078     bool b_mute = false;
1079
1080 #ifndef NDEBUG
1081     fprintf( stderr, "ToolbarButtonClicked:: "
1082                      "trying to match (%d,%d) (%d,%d)\n",
1083              i_xpos, i_ypos, i_tb_height, i_tb_width );
1084 #endif
1085     if( i_ypos >= i_tb_width )
1086         return clicked_Unknown;
1087
1088     /* Note: the order of testing is dependend on the original
1089      * drawing positions of the icon buttons. Buttons are tested
1090      * left to right.
1091      */
1092
1093     /* get isplaying */
1094     is_playing = playlist_isplaying();
1095
1096     /* get mute info */
1097     if( libvlc_media_player )
1098         b_mute = libvlc_audio_get_mute( libvlc_media_player );
1099
1100     /* is Pause of Play button clicked */
1101     if( (is_playing != 1) &&
1102         (i_xpos >= (BTN_SPACE>>1)) &&
1103         (i_xpos <= i_dest + p_btnPlay->width + (BTN_SPACE>>1)) )
1104         return clicked_Play;
1105     else if( (i_xpos >= (BTN_SPACE>>1))  &&
1106              (i_xpos <= i_dest + p_btnPause->width) )
1107         return clicked_Pause;
1108
1109     /* is Stop button clicked */
1110     if( is_playing != 1 )
1111         i_dest += (p_btnPlay->width + (BTN_SPACE>>1));
1112     else
1113         i_dest += (p_btnPause->width + (BTN_SPACE>>1));
1114
1115     if( (i_xpos >= i_dest) &&
1116         (i_xpos <= i_dest + p_btnStop->width + (BTN_SPACE>>1)) )
1117         return clicked_Stop;
1118
1119     /* is Fullscreen button clicked */
1120     i_dest += (p_btnStop->width + (BTN_SPACE>>1));
1121     if( (i_xpos >= i_dest) &&
1122         (i_xpos <= i_dest + p_btnFullscreen->width + (BTN_SPACE>>1)) )
1123         return clicked_Fullscreen;
1124
1125     /* is Mute or Unmute button clicked */
1126     i_dest += (p_btnFullscreen->width + (BTN_SPACE>>1));
1127     if( !b_mute && (i_xpos >= i_dest) &&
1128         (i_xpos <= i_dest + p_btnMute->width + (BTN_SPACE>>1)) )
1129         return clicked_Mute;
1130     else if( (i_xpos >= i_dest) &&
1131              (i_xpos <= i_dest + p_btnUnmute->width + (BTN_SPACE>>1)) )
1132         return clicked_Unmute;
1133
1134     /* is timeline clicked */
1135     if( !b_mute )
1136         i_dest += (p_btnMute->width + (BTN_SPACE>>1));
1137     else
1138         i_dest += (p_btnUnmute->width + (BTN_SPACE>>1));
1139     if( (i_xpos >= i_dest) &&
1140         (i_xpos <= i_dest + p_timeline->width + (BTN_SPACE>>1)) )
1141         return clicked_timeline;
1142
1143     /* is time button clicked */
1144     i_dest += (p_timeline->width + (BTN_SPACE>>1));
1145     if( (i_xpos >= i_dest) &&
1146         (i_xpos <= i_dest + p_btnTime->width + (BTN_SPACE>>1)) )
1147         return clicked_Time;
1148
1149     return clicked_Unknown;
1150 }
1151 #undef BTN_SPACE
1152 #endif
1153
1154 // Verifies the version of the NPAPI.
1155 // The eventListeners use a NPAPI function available
1156 // since Gecko 1.9.
1157 bool VlcPlugin::canUseEventListener()
1158 {
1159     int plugin_major, plugin_minor;
1160     int browser_major, browser_minor;
1161
1162     NPN_Version(&plugin_major, &plugin_minor,
1163                 &browser_major, &browser_minor);
1164
1165     if (browser_minor >= 19 || browser_major > 0)
1166         return true;
1167     return false;
1168 }