]> git.sesse.net Git - vlc/blob - modules/gui/macosx/voutgl.m
- voutgl.m: misc cleanup, reworked context locking as well as enabling double bufferi...
[vlc] / modules / gui / macosx / voutgl.m
1 /*****************************************************************************
2  * voutgl.m: MacOS X OpenGL provider
3  *****************************************************************************
4  * Copyright (C) 2001-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Florian G. Pflug <fgp@phlo.org>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *          Derk-Jan Hartman <hartman at videolan dot org>
11  *          Eric Petit <titer@m0k.org>
12  *          Benjamin Pracht <bigben at videolan dot org>
13  *          Damien Fouilleul <damienf at videolan dot org>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33 #include <errno.h>                                                 /* ENOMEM */
34 #include <stdlib.h>                                                /* free() */
35 #include <string.h>                                            /* strerror() */
36
37 #include <vlc_keys.h>
38
39 #include "intf.h"
40 #include "vout.h"
41
42 #include <OpenGL/OpenGL.h>
43 #include <OpenGL/gl.h>
44
45 #include <AGL/agl.h>
46
47 /*****************************************************************************
48  * VLCGLView interface
49  *****************************************************************************/
50 @interface VLCGLView : NSOpenGLView <VLCVoutViewResetting>
51 {
52     vout_thread_t * p_vout;
53 }
54
55 + (void)resetVout: (vout_thread_t *)p_vout;
56 - (id) initWithVout: (vout_thread_t *) p_vout;
57 @end
58
59 struct vout_sys_t
60 {
61     NSAutoreleasePool * o_pool;
62     VLCGLView         * o_glview;
63     VLCVoutView       * o_vout_view;
64     vlc_bool_t          b_saved_frame;
65     NSRect              s_frame;
66     vlc_bool_t          b_got_frame;
67     /* Mozilla plugin-related variables */
68     vlc_bool_t          b_embedded;
69     AGLContext          agl_ctx;
70     AGLDrawable         agl_drawable;
71     int                 i_offx, i_offy;
72     int                 i_width, i_height;
73     WindowRef           theWindow;
74     WindowGroupRef      winGroup;
75 };
76
77 /*****************************************************************************
78  * Local prototypes
79  *****************************************************************************/
80
81 static int  Init   ( vout_thread_t * p_vout );
82 static void End    ( vout_thread_t * p_vout );
83 static int  Manage ( vout_thread_t * p_vout );
84 static int  Control( vout_thread_t *, int, va_list );
85 static void Swap   ( vout_thread_t * p_vout );
86 static int  Lock   ( vout_thread_t * p_vout );
87 static void Unlock ( vout_thread_t * p_vout );
88
89 static int  aglInit   ( vout_thread_t * p_vout );
90 static void aglEnd    ( vout_thread_t * p_vout );
91 static int  aglManage ( vout_thread_t * p_vout );
92 static int  aglControl( vout_thread_t *, int, va_list );
93 static void aglSwap   ( vout_thread_t * p_vout );
94 static int  aglLock   ( vout_thread_t * p_vout );
95 static void aglUnlock ( vout_thread_t * p_vout );
96
97 int E_(OpenVideoGL)  ( vlc_object_t * p_this )
98 {
99     vout_thread_t * p_vout = (vout_thread_t *) p_this;
100     vlc_value_t value_drawable;
101
102     if( !CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay ) )
103     {
104         msg_Warn( p_vout, "no OpenGL hardware acceleration found. "
105                           "Video display will be slow" );
106         return( 1 );
107     }
108     msg_Dbg( p_vout, "display is Quartz Extreme accelerated" );
109
110     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
111     if( p_vout->p_sys == NULL )
112     {
113         msg_Err( p_vout, "out of memory" );
114         return( 1 );
115     }
116
117     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
118
119     var_Get( p_vout->p_libvlc, "drawable", &value_drawable );
120     if( value_drawable.i_int != 0 )
121     {
122         static const GLint ATTRIBUTES[] = { 
123             AGL_WINDOW,
124             AGL_RGBA,
125             AGL_NO_RECOVERY,
126             AGL_ACCELERATED,
127             AGL_DOUBLEBUFFER,
128             AGL_RED_SIZE,   8,
129             AGL_GREEN_SIZE, 8,
130             AGL_BLUE_SIZE,  8,
131             AGL_ALPHA_SIZE, 8,
132             AGL_DEPTH_SIZE, 24,
133             AGL_NONE };
134
135         AGLPixelFormat pixFormat;
136
137         p_vout->p_sys->b_embedded = VLC_TRUE;
138
139         pixFormat = aglChoosePixelFormat(NULL, 0, ATTRIBUTES);
140         if( NULL == pixFormat )
141         {
142             msg_Err( p_vout, "no screen renderer available for required attributes." );
143             return VLC_EGENERIC;
144         }
145         
146         p_vout->p_sys->agl_ctx = aglCreateContext(pixFormat, NULL);
147         aglDestroyPixelFormat(pixFormat);
148         if( NULL == p_vout->p_sys->agl_ctx )
149         {
150             msg_Err( p_vout, "cannot create AGL context." );
151             return VLC_EGENERIC;
152         }
153         else {
154             // tell opengl not to sync buffer swap with vertical retrace (too inefficient)
155             GLint param = 0;
156             aglSetInteger(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL, &param);
157             aglEnable(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL);
158         }
159
160         p_vout->pf_init             = aglInit;
161         p_vout->pf_end              = aglEnd;
162         p_vout->pf_manage           = aglManage;
163         p_vout->pf_control          = aglControl;
164         p_vout->pf_swap             = aglSwap;
165         p_vout->pf_lock             = aglLock;
166         p_vout->pf_unlock           = aglUnlock;
167     }
168     else
169     {
170         p_vout->p_sys->b_embedded = VLC_FALSE;
171
172         p_vout->p_sys->o_pool = [[NSAutoreleasePool alloc] init];
173
174         /* Create the GL view */
175         p_vout->p_sys->o_glview = [[VLCGLView alloc] initWithVout: p_vout];
176         [p_vout->p_sys->o_glview autorelease];
177
178         /* Spawn the window */
179
180         if( !(p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
181                         subView: p_vout->p_sys->o_glview frame: nil]) )
182         {
183             return VLC_EGENERIC;
184         }
185         p_vout->pf_init   = Init;
186         p_vout->pf_end    = End;
187         p_vout->pf_manage = Manage;
188         p_vout->pf_control= Control;
189         p_vout->pf_swap   = Swap;
190         p_vout->pf_lock   = Lock;
191         p_vout->pf_unlock = Unlock;
192     }
193     p_vout->p_sys->b_got_frame = VLC_FALSE;
194
195     return VLC_SUCCESS;
196 }
197
198 void E_(CloseVideoGL) ( vlc_object_t * p_this )
199 {
200     vout_thread_t * p_vout = (vout_thread_t *) p_this;
201     if( p_vout->p_sys->b_embedded )
202     {
203         aglDestroyContext(p_vout->p_sys->agl_ctx);
204     }
205     else if(!VLCIntf->b_die)
206     {
207         NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
208
209         /* Close the window */
210         [p_vout->p_sys->o_vout_view closeVout];
211
212         [o_pool release];
213     }
214     /* Clean up */
215     free( p_vout->p_sys );
216 }
217
218 static int Init( vout_thread_t * p_vout )
219 {
220     [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
221     return VLC_SUCCESS;
222 }
223
224 static void End( vout_thread_t * p_vout )
225 {
226     [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
227 }
228
229 static int Manage( vout_thread_t * p_vout )
230 {
231     if( p_vout->i_changes & VOUT_ASPECT_CHANGE )
232     {
233         [p_vout->p_sys->o_glview reshape];
234         p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
235     }
236     if( p_vout->i_changes & VOUT_CROP_CHANGE )
237     {
238         [p_vout->p_sys->o_glview reshape];
239         p_vout->i_changes &= ~VOUT_CROP_CHANGE;
240     }
241
242     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
243     {
244         NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
245
246         p_vout->b_fullscreen = !p_vout->b_fullscreen;
247
248         if( p_vout->b_fullscreen )
249             [p_vout->p_sys->o_vout_view enterFullscreen];
250         else
251             [p_vout->p_sys->o_vout_view leaveFullscreen];
252
253         [o_pool release];
254
255         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
256     }
257
258     [p_vout->p_sys->o_vout_view manage];
259     return VLC_SUCCESS;
260 }
261
262 /*****************************************************************************
263  * Control: control facility for the vout
264  *****************************************************************************/
265 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
266 {
267     vlc_bool_t b_arg;
268
269     switch( i_query )
270     {
271         case VOUT_SET_STAY_ON_TOP:
272             b_arg = va_arg( args, vlc_bool_t );
273             [p_vout->p_sys->o_vout_view setOnTop: b_arg];
274             return VLC_SUCCESS;
275
276         case VOUT_CLOSE:
277         case VOUT_REPARENT:
278         default:
279             return vout_vaControlDefault( p_vout, i_query, args );
280     }
281 }
282
283 static void Swap( vout_thread_t * p_vout )
284 {
285     p_vout->p_sys->b_got_frame = VLC_TRUE;
286     [[p_vout->p_sys->o_glview openGLContext] flushBuffer];
287 }
288
289 static int Lock( vout_thread_t * p_vout )
290 {
291     if( kCGLNoError == CGLLockContext([[p_vout->p_sys->o_glview openGLContext] CGLContextObj]) )
292     {
293         [[p_vout->p_sys->o_glview openGLContext] makeCurrentContext];
294         return 0;
295     }
296     return 1;
297 }
298
299 static void Unlock( vout_thread_t * p_vout )
300 {
301     CGLUnlockContext([[p_vout->p_sys->o_glview openGLContext] CGLContextObj]);
302 }
303
304 /*****************************************************************************
305  * VLCGLView implementation
306  *****************************************************************************/
307 @implementation VLCGLView
308
309 /* This function will reset the o_vout_view. It's useful to go fullscreen. */
310 + (void)resetVout: (vout_thread_t *)p_vout
311 {
312     if( p_vout->b_fullscreen )
313     {
314         /* Save window size and position */
315         p_vout->p_sys->s_frame.size =
316             [p_vout->p_sys->o_vout_view frame].size;
317         p_vout->p_sys->s_frame.origin =
318             [[p_vout->p_sys->o_vout_view getWindow ]frame].origin;
319         p_vout->p_sys->b_saved_frame = VLC_TRUE;
320     }
321
322     [p_vout->p_sys->o_vout_view closeVout];
323
324 #define o_glview p_vout->p_sys->o_glview
325     o_glview = [[VLCGLView alloc] initWithVout: p_vout];
326     [o_glview autorelease];
327     
328     if( p_vout->p_sys->b_saved_frame )
329     {
330         p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
331                                                       subView: o_glview
332                                                         frame: &p_vout->p_sys->s_frame];
333     }
334     else
335     {
336         p_vout->p_sys->o_vout_view = [VLCVoutView getVoutView: p_vout
337                                                       subView: o_glview frame: nil];
338         
339     }
340 #undef o_glview
341 }
342
343 - (id) initWithVout: (vout_thread_t *) vout
344 {
345     p_vout = vout;
346
347     NSOpenGLPixelFormatAttribute attribs[] =
348     {
349         NSOpenGLPFADoubleBuffer,
350         NSOpenGLPFAAccelerated,
351         NSOpenGLPFANoRecovery,
352         NSOpenGLPFAColorSize, 24,
353         NSOpenGLPFAAlphaSize, 8,
354         NSOpenGLPFADepthSize, 24,
355         NSOpenGLPFAWindow,
356         0
357     };
358
359     NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
360         initWithAttributes: attribs];
361
362     if( !fmt )
363     {
364         msg_Warn( p_vout, "could not create OpenGL video output" );
365         return nil;
366     }
367
368     self = [super initWithFrame: NSMakeRect(0,0,10,10) pixelFormat: fmt];
369     [fmt release];
370
371     [[self openGLContext] makeCurrentContext];
372     [[self openGLContext] update];
373
374     /* Swap buffers only during the vertical retrace of the monitor.
375        http://developer.apple.com/documentation/GraphicsImaging/
376        Conceptual/OpenGL/chap5/chapter_5_section_44.html */
377     long params[] = { 1 };
378     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
379                      params );
380     return self;
381 }
382
383 - (void) reshape
384 {
385     int x, y;
386     vlc_value_t val;
387
388     Lock( p_vout );
389     NSRect bounds = [self bounds];
390
391     var_Get( p_vout, "macosx-stretch", &val );
392     if( val.b_bool )
393     {
394         x = bounds.size.width;
395         y = bounds.size.height;
396     }
397     else if( bounds.size.height * p_vout->fmt_in.i_visible_width *
398              p_vout->fmt_in.i_sar_num <
399              bounds.size.width * p_vout->fmt_in.i_visible_height *
400              p_vout->fmt_in.i_sar_den )
401     {
402         x = ( bounds.size.height * p_vout->fmt_in.i_visible_width *
403               p_vout->fmt_in.i_sar_num ) /
404             ( p_vout->fmt_in.i_visible_height * p_vout->fmt_in.i_sar_den);
405
406         y = bounds.size.height;
407     }
408     else
409     {
410         x = bounds.size.width;
411         y = ( bounds.size.width * p_vout->fmt_in.i_visible_height *
412               p_vout->fmt_in.i_sar_den) /
413             ( p_vout->fmt_in.i_visible_width * p_vout->fmt_in.i_sar_num  );
414     }
415
416     glViewport( ( bounds.size.width - x ) / 2,
417                 ( bounds.size.height - y ) / 2, x, y );
418
419     if( p_vout->p_sys->b_got_frame )
420     {
421         /* Ask the opengl module to redraw */
422         vout_thread_t * p_parent;
423         p_parent = (vout_thread_t *) p_vout->p_parent;
424         Unlock( p_vout );
425         if( p_parent && p_parent->pf_display )
426         {
427             p_parent->pf_display( p_parent, NULL );
428         }
429     }
430     else
431     {
432         glClear( GL_COLOR_BUFFER_BIT );
433         Unlock( p_vout );
434     }
435     [super reshape];
436 }
437
438 - (void) update
439 {
440     Lock( p_vout );
441     [super update];
442     Unlock( p_vout );
443 }
444
445 - (void) drawRect: (NSRect) rect
446 {
447     Lock( p_vout );
448     [[p_vout->p_sys->o_glview openGLContext] flushBuffer];
449     [super drawRect:rect];
450     Unlock( p_vout );
451 }
452
453 @end
454
455 /*****************************************************************************
456  * embedded AGL context implementation
457  *****************************************************************************/
458
459 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds );
460 static void aglReshape( vout_thread_t * p_vout );
461 static OSStatus WindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
462
463 static int aglInit( vout_thread_t * p_vout )
464 {
465     vlc_value_t val;
466
467     Rect viewBounds;    
468     Rect clipBounds;
469     
470     var_Get( p_vout->p_libvlc, "drawable", &val );
471     p_vout->p_sys->agl_drawable = (AGLDrawable)val.i_int;
472     aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
473
474     var_Get( p_vout->p_libvlc, "drawable-view-top", &val );
475     viewBounds.top = val.i_int;
476     var_Get( p_vout->p_libvlc, "drawable-view-left", &val );
477     viewBounds.left = val.i_int;
478     var_Get( p_vout->p_libvlc, "drawable-view-bottom", &val );
479     viewBounds.bottom = val.i_int;
480     var_Get( p_vout->p_libvlc, "drawable-view-right", &val );
481     viewBounds.right = val.i_int;
482     var_Get( p_vout->p_libvlc, "drawable-clip-top", &val );
483     clipBounds.top = val.i_int;
484     var_Get( p_vout->p_libvlc, "drawable-clip-left", &val );
485     clipBounds.left = val.i_int;
486     var_Get( p_vout->p_libvlc, "drawable-clip-bottom", &val );
487     clipBounds.bottom = val.i_int;
488     var_Get( p_vout->p_libvlc, "drawable-clip-right", &val );
489     clipBounds.right = val.i_int;
490
491     aglLock(p_vout);
492     aglSetViewport(p_vout, viewBounds, clipBounds);
493     aglReshape(p_vout);
494     aglUnlock(p_vout);
495     
496     return VLC_SUCCESS;
497 }
498
499 static void aglEnd( vout_thread_t * p_vout )
500 {
501     aglSetCurrentContext(NULL);
502     if( p_vout->p_sys->theWindow ) DisposeWindow( p_vout->p_sys->theWindow );
503 }
504
505 static void aglReshape( vout_thread_t * p_vout )
506 {
507     unsigned int x, y;
508     unsigned int i_height = p_vout->p_sys->i_height;
509     unsigned int i_width  = p_vout->p_sys->i_width;
510
511     vout_PlacePicture(p_vout, i_width, i_height, &x, &y, &i_width, &i_height); 
512
513     glViewport( p_vout->p_sys->i_offx + x, p_vout->p_sys->i_offy + y, i_width, i_height );
514
515     if( p_vout->p_sys->b_got_frame )
516     {
517         /* Ask the opengl module to redraw */
518         vout_thread_t * p_parent;
519         p_parent = (vout_thread_t *) p_vout->p_parent;
520         if( p_parent && p_parent->pf_display )
521         {
522             p_parent->pf_display( p_parent, NULL );
523         }
524     }
525     else
526     {
527         glClear( GL_COLOR_BUFFER_BIT );
528     }
529 }
530
531 /* private event class */
532 enum 
533 {
534     kEventClassVLCPlugin = 'vlcp',
535 };
536 /* private event kinds */
537 enum
538 {
539     kEventVLCPluginShowFullscreen = 32768,
540     kEventVLCPluginHideFullscreen,
541 };
542
543 static void sendEventToMainThread(EventTargetRef target, UInt32 class, UInt32 kind)
544 {
545     EventRef myEvent;
546     if( noErr == CreateEvent(NULL, class, kind, 0, kEventAttributeNone, &myEvent) )
547     {
548         if( noErr == SetEventParameter(myEvent, kEventParamPostTarget, typeEventTargetRef, sizeof(EventTargetRef), &target) )
549         {
550             PostEventToQueue(GetMainEventQueue(), myEvent, kEventPriorityStandard);
551         }
552         ReleaseEvent(myEvent);
553     }
554 }
555
556 static int aglManage( vout_thread_t * p_vout )
557 {
558     if( p_vout->i_changes & VOUT_ASPECT_CHANGE )
559     {
560         aglLock( p_vout );
561         aglReshape(p_vout);
562         aglUnlock( p_vout );
563         p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
564     }
565     if( p_vout->i_changes & VOUT_CROP_CHANGE )
566     {
567         aglLock( p_vout );
568         aglReshape(p_vout);
569         aglUnlock( p_vout );
570         p_vout->i_changes &= ~VOUT_CROP_CHANGE;
571     }
572     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
573     {
574         aglSetDrawable(p_vout->p_sys->agl_ctx, NULL);
575         aglLock( p_vout );
576         if( p_vout->b_fullscreen )
577         {
578             /* Close the fullscreen window and resume normal drawing */
579             vlc_value_t val;
580             Rect viewBounds;    
581             Rect clipBounds;
582
583             var_Get( p_vout->p_libvlc, "drawable", &val );
584             p_vout->p_sys->agl_drawable = (AGLDrawable)val.i_int;
585             aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
586
587             var_Get( p_vout->p_libvlc, "drawable-view-top", &val );
588             viewBounds.top = val.i_int;
589             var_Get( p_vout->p_libvlc, "drawable-view-left", &val );
590             viewBounds.left = val.i_int;
591             var_Get( p_vout->p_libvlc, "drawable-view-bottom", &val );
592             viewBounds.bottom = val.i_int;
593             var_Get( p_vout->p_libvlc, "drawable-view-right", &val );
594             viewBounds.right = val.i_int;
595             var_Get( p_vout->p_libvlc, "drawable-clip-top", &val );
596             clipBounds.top = val.i_int;
597             var_Get( p_vout->p_libvlc, "drawable-clip-left", &val );
598             clipBounds.left = val.i_int;
599             var_Get( p_vout->p_libvlc, "drawable-clip-bottom", &val );
600             clipBounds.bottom = val.i_int;
601             var_Get( p_vout->p_libvlc, "drawable-clip-right", &val );
602             clipBounds.right = val.i_int;
603
604             aglSetCurrentContext(p_vout->p_sys->agl_ctx);
605             aglSetViewport(p_vout, viewBounds, clipBounds);
606
607             /* Most Carbon APIs are not thread-safe, therefore delagate some GUI visibilty update to the main thread */
608             sendEventToMainThread(GetWindowEventTarget(p_vout->p_sys->theWindow), kEventClassVLCPlugin, kEventVLCPluginHideFullscreen);
609         }
610         else
611         {
612             Rect deviceRect;
613             
614             GDHandle deviceHdl = GetMainDevice();
615             deviceRect = (*deviceHdl)->gdRect;
616             
617             if( !p_vout->p_sys->theWindow )
618             {
619                 /* Create a window */
620                 WindowAttributes    windowAttrs;
621
622                 windowAttrs = kWindowStandardDocumentAttributes
623                             | kWindowStandardHandlerAttribute
624                             | kWindowLiveResizeAttribute
625                             | kWindowNoShadowAttribute;
626                                             
627                 windowAttrs &= (~kWindowResizableAttribute);
628
629                 CreateNewWindow(kDocumentWindowClass, windowAttrs, &deviceRect, &p_vout->p_sys->theWindow);
630                 if( !p_vout->p_sys->winGroup )
631                 {
632                     CreateWindowGroup(0, &p_vout->p_sys->winGroup);
633                     SetWindowGroup(p_vout->p_sys->theWindow, p_vout->p_sys->winGroup);
634                     SetWindowGroupParent( p_vout->p_sys->winGroup, GetWindowGroupOfClass(kDocumentWindowClass) ) ;
635                 }
636                 
637                 // Window title
638                 CFStringRef titleKey    = CFSTR("Fullscreen VLC media plugin");
639                 CFStringRef windowTitle = CFCopyLocalizedString(titleKey, NULL);
640                 SetWindowTitleWithCFString(p_vout->p_sys->theWindow, windowTitle);
641                 CFRelease(titleKey);
642                 CFRelease(windowTitle);
643                 
644                 //Install event handler
645                 static const EventTypeSpec win_events[] = {
646                     { kEventClassMouse, kEventMouseDown },
647                     { kEventClassMouse, kEventMouseMoved },
648                     { kEventClassMouse, kEventMouseUp },
649                     { kEventClassWindow, kEventWindowClosed },
650                     { kEventClassWindow, kEventWindowBoundsChanged },
651                     { kEventClassCommand, kEventCommandProcess },
652                     { kEventClassVLCPlugin, kEventVLCPluginShowFullscreen },
653                     { kEventClassVLCPlugin, kEventVLCPluginHideFullscreen },
654                 };
655                 InstallWindowEventHandler (p_vout->p_sys->theWindow, NewEventHandlerUPP (WindowEventHandler), GetEventTypeCount(win_events), win_events, p_vout, NULL);
656             }
657             else
658             {
659                 /* just in case device resolution changed */
660                 SetWindowBounds(p_vout->p_sys->theWindow, kWindowContentRgn, &deviceRect);
661             }
662             glClear( GL_COLOR_BUFFER_BIT );
663             p_vout->p_sys->agl_drawable = (AGLDrawable)GetWindowPort(p_vout->p_sys->theWindow);
664             aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
665             aglSetCurrentContext(p_vout->p_sys->agl_ctx);
666             aglSetViewport(p_vout, deviceRect, deviceRect);
667             //aglSetFullScreen(p_vout->p_sys->agl_ctx, device_width, device_height, 0, 0);
668
669             /* Most Carbon APIs are not thread-safe, therefore delagate some GUI visibilty update to the main thread */
670             sendEventToMainThread(GetWindowEventTarget(p_vout->p_sys->theWindow), kEventClassVLCPlugin, kEventVLCPluginShowFullscreen);
671         }
672         aglReshape(p_vout);
673         aglUnlock( p_vout );
674         p_vout->b_fullscreen = !p_vout->b_fullscreen;
675         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
676     }
677     return VLC_SUCCESS;
678 }
679
680 static int aglControl( vout_thread_t *p_vout, int i_query, va_list args )
681 {
682     switch( i_query )
683     {
684         case VOUT_SET_VIEWPORT:
685         {
686             Rect viewBounds, clipBounds;
687             viewBounds.top = va_arg( args, int);
688             viewBounds.left = va_arg( args, int);
689             viewBounds.bottom = va_arg( args, int);
690             viewBounds.right = va_arg( args, int);
691             clipBounds.top = va_arg( args, int);
692             clipBounds.left = va_arg( args, int);
693             clipBounds.bottom = va_arg( args, int);
694             clipBounds.right = va_arg( args, int);
695             
696             if( !p_vout->b_fullscreen ) 
697             {
698                 aglLock( p_vout );
699                 aglSetViewport(p_vout, viewBounds, clipBounds);
700                 aglReshape( p_vout );
701                 aglUnlock( p_vout );
702             }
703             return VLC_SUCCESS;
704         }
705
706         case VOUT_REPARENT:
707         {
708             AGLDrawable drawable = (AGLDrawable)va_arg( args, int);
709             if( !p_vout->b_fullscreen && drawable != p_vout->p_sys->agl_drawable )
710             {
711                 p_vout->p_sys->agl_drawable = drawable;
712                 aglSetDrawable(p_vout->p_sys->agl_ctx, drawable);
713             }
714             return VLC_SUCCESS;
715         }
716
717         default:
718             return vout_vaControlDefault( p_vout, i_query, args );
719     }
720 }
721
722 static void aglSwap( vout_thread_t * p_vout )
723 {
724     p_vout->p_sys->b_got_frame = VLC_TRUE;
725     aglSwapBuffers(p_vout->p_sys->agl_ctx);
726 }
727
728 /* Enter this function with the p_vout locked */
729 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds )
730 {
731     // mozilla plugin provides coordinates based on port bounds
732     // however AGL coordinates are based on window structure region
733     // and are vertically flipped
734     GLint rect[4];
735     CGrafPtr port = (CGrafPtr)p_vout->p_sys->agl_drawable;
736     Rect winBounds, clientBounds;
737
738     GetWindowBounds(GetWindowFromPort(port),
739         kWindowStructureRgn, &winBounds);
740     GetWindowBounds(GetWindowFromPort(port),
741         kWindowContentRgn, &clientBounds);
742
743     /* update video clipping bounds in drawable */
744     rect[0] = (clientBounds.left-winBounds.left)
745             + clipBounds.left;                  // from window left edge
746     rect[1] = (winBounds.bottom-winBounds.top)
747             - (clientBounds.top-winBounds.top)
748             - clipBounds.bottom;                // from window bottom edge
749     rect[2] = clipBounds.right-clipBounds.left; // width
750     rect[3] = clipBounds.bottom-clipBounds.top; // height
751     aglSetInteger(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT, rect);
752     aglEnable(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT);
753
754     /* update video internal bounds in drawable */
755     p_vout->p_sys->i_width  = viewBounds.right-viewBounds.left;
756     p_vout->p_sys->i_height = viewBounds.bottom-viewBounds.top;
757     p_vout->p_sys->i_offx   = -clipBounds.left - viewBounds.left;
758     p_vout->p_sys->i_offy   = clipBounds.bottom + viewBounds.top
759                             - p_vout->p_sys->i_height; 
760
761     aglUpdateContext(p_vout->p_sys->agl_ctx);
762 }
763
764 //default window event handler
765 static pascal OSStatus WindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
766 {
767     OSStatus result = noErr;
768     UInt32 class = GetEventClass (event);
769     UInt32 kind = GetEventKind (event); 
770     vout_thread_t *p_vout = (vout_thread_t *)userData;
771
772     result = CallNextEventHandler(nextHandler, event);
773     if(class == kEventClassCommand)
774     {
775         HICommand theHICommand;
776         GetEventParameter( event, kEventParamDirectObject, typeHICommand, NULL, sizeof( HICommand ), NULL, &theHICommand );
777         
778         switch ( theHICommand.commandID )
779         {
780             default:
781                 result = eventNotHandledErr;
782         }
783     }
784     else if(class == kEventClassWindow)
785     {
786         WindowRef     window;
787         Rect          rectPort = {0,0,0,0};
788         
789         GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &window);
790
791         if(window)
792         {
793             GetPortBounds(GetWindowPort(window), &rectPort);
794         }   
795
796         switch (kind)
797         {
798             case kEventWindowClosed:
799             case kEventWindowZoomed:
800             case kEventWindowBoundsChanged:
801                 break;
802             
803             default:
804                 result = eventNotHandledErr;
805         }
806     }
807     else if(class == kEventClassMouse)
808     {
809         switch (kind)
810         {
811             case kEventMouseDown:
812             {
813                 UInt16     button;
814         
815                 GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
816                 switch (button)
817                 {
818                     case kEventMouseButtonPrimary:
819                     {
820                         vlc_value_t val;
821
822                         var_Get( p_vout, "mouse-button-down", &val );
823                         val.i_int |= 1;
824                         var_Set( p_vout, "mouse-button-down", val );
825                         break;
826                     }
827                     case kEventMouseButtonSecondary:
828                     {
829                         vlc_value_t val;
830
831                         var_Get( p_vout, "mouse-button-down", &val );
832                         val.i_int |= 2;
833                         var_Set( p_vout, "mouse-button-down", val );
834                         break;
835                     }
836                     case kEventMouseButtonTertiary:
837                     {
838                         vlc_value_t val;
839
840                         var_Get( p_vout, "mouse-button-down", &val );
841                         val.i_int |= 4;
842                         var_Set( p_vout, "mouse-button-down", val );
843                         break;
844                     }
845                     default:
846                         result = eventNotHandledErr;
847                 }
848                 break;
849             }
850
851             case kEventMouseUp:
852             {
853                 UInt16     button;
854         
855                 GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
856                 switch (button)
857                 {
858                     case kEventMouseButtonPrimary:
859                     {
860                         UInt32 clickCount = 0;
861                         GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount);
862                         if( clickCount > 1 )
863                         {
864                             vlc_value_t val;
865
866                             val.b_bool = VLC_FALSE;
867                             var_Set((vout_thread_t *) p_vout->p_parent, "fullscreen", val);
868                         }
869                         else
870                         {
871                             vlc_value_t val;
872
873                             val.b_bool = VLC_TRUE;
874                             var_Set( p_vout, "mouse-clicked", val );
875
876                             var_Get( p_vout, "mouse-button-down", &val );
877                             val.i_int &= ~1;
878                             var_Set( p_vout, "mouse-button-down", val );
879                         }
880                         break;
881                     }
882                     case kEventMouseButtonSecondary:
883                     {
884                         vlc_value_t val;
885
886                         var_Get( p_vout, "mouse-button-down", &val );
887                         val.i_int &= ~2;
888                         var_Set( p_vout, "mouse-button-down", val );
889                         break;
890                     }
891                     case kEventMouseButtonTertiary:
892                     {
893                         vlc_value_t val;
894
895                         var_Get( p_vout, "mouse-button-down", &val );
896                         val.i_int &= ~2;
897                         var_Set( p_vout, "mouse-button-down", val );
898                         break;
899                     }
900                     default:
901                         result = eventNotHandledErr;
902                 }
903                 break;
904             }
905
906             case kEventMouseMoved:
907             {
908                 Point ml;
909                 vlc_value_t val;
910
911                 unsigned int i_x, i_y;
912                 unsigned int i_height = p_vout->p_sys->i_height;
913                 unsigned int i_width  = p_vout->p_sys->i_width;
914
915                 vout_PlacePicture(p_vout, i_width, i_height, &i_x, &i_y, &i_width, &i_height); 
916
917                 GetEventParameter(event, kEventParamWindowMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &ml);
918                 
919                 val.i_int = ( ((int)ml.h) - i_x ) *
920                             p_vout->render.i_width / i_width;
921                 var_Set( p_vout, "mouse-x", val );
922
923                 val.i_int = ( ((int)ml.v) - i_y ) *
924                             p_vout->render.i_height / i_height;
925
926                 var_Set( p_vout, "mouse-y", val );
927
928                 val.b_bool = VLC_TRUE;
929                 var_Set( p_vout, "mouse-moved", val );
930
931                 break;
932             }
933             
934             default:
935                 result = eventNotHandledErr;
936         }
937     }
938     else if(class == kEventClassTextInput)
939     {
940         switch (kind)
941         {
942             case kEventTextInputUnicodeForKeyEvent:
943             {
944                 break;
945             }
946             default:
947                 result = eventNotHandledErr;
948         }
949     }
950     else if(class == kEventClassVLCPlugin)
951     {
952         switch (kind)
953         {
954             case kEventVLCPluginShowFullscreen:
955                 ShowWindow (p_vout->p_sys->theWindow);
956                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
957                 //CGDisplayHideCursor(kCGDirectMainDisplay);
958                 break;
959             case kEventVLCPluginHideFullscreen:
960                 HideWindow (p_vout->p_sys->theWindow);
961                 SetSystemUIMode( kUIModeNormal, 0);
962                 CGDisplayShowCursor(kCGDirectMainDisplay);
963                 break;
964             default:
965                 result = eventNotHandledErr;
966                 break;
967         }
968     }
969     return result;
970 }
971
972 static int aglLock( vout_thread_t * p_vout )
973 {
974 #ifdef __ppc__
975     /*
976      * before 10.4, we set the AGL context as current and
977      * then we retrieve and use the matching CGL context 
978      */
979     aglSetCurrentContext(p_vout->p_sys->agl_ctx);
980     return kCGLNoError != CGLLockContext( CGLGetCurrentContext() );
981 #else
982     /* since 10.4, this is the safe way to get the underlying CGL context */
983     CGLContextObj cglContext;
984     if( aglGetCGLContext(p_vout->p_sys->agl_ctx, (void**)&cglContext) )
985     {
986         if( kCGLNoError == CGLLockContext( cglContext ) )
987         {
988             aglSetCurrentContext(p_vout->p_sys->agl_ctx);
989             return 0;
990         }
991     }
992     return 1;
993 #endif
994 }
995
996 static void aglUnlock( vout_thread_t * p_vout )
997 {
998 #ifdef __ppc__
999     /*
1000      * before 10.4, we assume that the AGL context is current.
1001      * therefore, we use the current CGL context 
1002      */
1003     CGLUnlockContext( CGLGetCurrentContext() );
1004 #else
1005     /* since 10.4, this is the safe way to get the underlying CGL context */
1006     CGLContextObj cglContext;
1007     if( aglGetCGLContext(p_vout->p_sys->agl_ctx, (void**)&cglContext) )
1008     {
1009         CGLUnlockContext( cglContext );
1010     }
1011 #endif
1012 }
1013
1014