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