1 /*****************************************************************************
2 * voutagl.c: MacOS X agl OpenGL provider (used by webbrowser.plugin)
3 *****************************************************************************
4 * Copyright (C) 2001-2007 the VideoLAN team
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>
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.
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.
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 *****************************************************************************/
34 /*****************************************************************************
35 * embedded AGL context implementation
36 *****************************************************************************/
38 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds );
39 static void aglReshape( vout_thread_t * p_vout );
40 static OSStatus WindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
42 int aglInit( vout_thread_t * p_vout )
49 static const GLint ATTRIBUTES[] = {
62 AGLPixelFormat pixFormat;
64 p_vout->p_sys->b_embedded = true;
66 pixFormat = aglChoosePixelFormat(NULL, 0, ATTRIBUTES);
67 if( NULL == pixFormat )
69 msg_Err( p_vout, "No screen renderer available for required attributes." );
73 p_vout->p_sys->agl_ctx = aglCreateContext(pixFormat, NULL);
74 aglDestroyPixelFormat(pixFormat);
75 if( NULL == p_vout->p_sys->agl_ctx )
77 msg_Err( p_vout, "Cannot create AGL context." );
81 // tell opengl not to sync buffer swap with vertical retrace (too inefficient)
83 aglSetInteger(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL, ¶m);
84 aglEnable(p_vout->p_sys->agl_ctx, AGL_SWAP_INTERVAL);
87 var_Get( p_vout->p_libvlc, "drawable", &val );
88 p_vout->p_sys->agl_drawable = (AGLDrawable)val.i_int;
89 aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
91 var_Get( p_vout->p_libvlc, "drawable-view-top", &val );
92 viewBounds.top = val.i_int;
93 var_Get( p_vout->p_libvlc, "drawable-view-left", &val );
94 viewBounds.left = val.i_int;
95 var_Get( p_vout->p_libvlc, "drawable-view-bottom", &val );
96 viewBounds.bottom = val.i_int;
97 var_Get( p_vout->p_libvlc, "drawable-view-right", &val );
98 viewBounds.right = val.i_int;
99 var_Get( p_vout->p_libvlc, "drawable-clip-top", &val );
100 clipBounds.top = val.i_int;
101 var_Get( p_vout->p_libvlc, "drawable-clip-left", &val );
102 clipBounds.left = val.i_int;
103 var_Get( p_vout->p_libvlc, "drawable-clip-bottom", &val );
104 clipBounds.bottom = val.i_int;
105 var_Get( p_vout->p_libvlc, "drawable-clip-right", &val );
106 clipBounds.right = val.i_int;
108 p_vout->p_sys->b_clipped_out = (clipBounds.top == clipBounds.bottom)
109 || (clipBounds.left == clipBounds.right);
110 if( ! p_vout->p_sys->b_clipped_out )
113 aglSetViewport(p_vout, viewBounds, clipBounds);
117 p_vout->p_sys->clipBounds = clipBounds;
118 p_vout->p_sys->viewBounds = viewBounds;
123 void aglEnd( vout_thread_t * p_vout )
125 aglSetCurrentContext(NULL);
126 if( p_vout->p_sys->theWindow ) DisposeWindow( p_vout->p_sys->theWindow );
127 aglDestroyContext(p_vout->p_sys->agl_ctx);
130 void aglReshape( vout_thread_t * p_vout )
133 unsigned int i_height = p_vout->p_sys->i_height;
134 unsigned int i_width = p_vout->p_sys->i_width;
136 vout_PlacePicture(p_vout, i_width, i_height, &x, &y, &i_width, &i_height);
138 glViewport( p_vout->p_sys->i_offx + x, p_vout->p_sys->i_offy + y, i_width, i_height );
140 if( p_vout->p_sys->b_got_frame )
142 /* Ask the opengl module to redraw */
143 vout_thread_t * p_parent;
144 p_parent = (vout_thread_t *) p_vout->p_parent;
145 if( p_parent && p_parent->pf_display )
147 p_parent->pf_display( p_parent, NULL );
152 glClear( GL_COLOR_BUFFER_BIT );
156 /* private event class */
159 kEventClassVLCPlugin = 'vlcp',
161 /* private event kinds */
164 kEventVLCPluginShowFullscreen = 32768,
165 kEventVLCPluginHideFullscreen,
168 static void sendEventToMainThread(EventTargetRef target, UInt32 class, UInt32 kind)
171 if( noErr == CreateEvent(NULL, class, kind, 0, kEventAttributeNone, &myEvent) )
173 if( noErr == SetEventParameter(myEvent, kEventParamPostTarget, typeEventTargetRef, sizeof(EventTargetRef), &target) )
175 PostEventToQueue(GetMainEventQueue(), myEvent, kEventPriorityStandard);
177 ReleaseEvent(myEvent);
181 int aglManage( vout_thread_t * p_vout )
183 if( p_vout->i_changes & VOUT_ASPECT_CHANGE )
188 p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
190 if( p_vout->i_changes & VOUT_CROP_CHANGE )
195 p_vout->i_changes &= ~VOUT_CROP_CHANGE;
197 if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
199 aglSetDrawable(p_vout->p_sys->agl_ctx, NULL);
201 if( p_vout->b_fullscreen )
203 /* Close the fullscreen window and resume normal drawing */
208 var_Get( p_vout->p_libvlc, "drawable", &val );
209 p_vout->p_sys->agl_drawable = (AGLDrawable)val.i_int;
210 aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
212 var_Get( p_vout->p_libvlc, "drawable-view-top", &val );
213 viewBounds.top = val.i_int;
214 var_Get( p_vout->p_libvlc, "drawable-view-left", &val );
215 viewBounds.left = val.i_int;
216 var_Get( p_vout->p_libvlc, "drawable-view-bottom", &val );
217 viewBounds.bottom = val.i_int;
218 var_Get( p_vout->p_libvlc, "drawable-view-right", &val );
219 viewBounds.right = val.i_int;
220 var_Get( p_vout->p_libvlc, "drawable-clip-top", &val );
221 clipBounds.top = val.i_int;
222 var_Get( p_vout->p_libvlc, "drawable-clip-left", &val );
223 clipBounds.left = val.i_int;
224 var_Get( p_vout->p_libvlc, "drawable-clip-bottom", &val );
225 clipBounds.bottom = val.i_int;
226 var_Get( p_vout->p_libvlc, "drawable-clip-right", &val );
227 clipBounds.right = val.i_int;
229 aglSetCurrentContext(p_vout->p_sys->agl_ctx);
230 aglSetViewport(p_vout, viewBounds, clipBounds);
232 /* Most Carbon APIs are not thread-safe, therefore delagate some GUI visibilty update to the main thread */
233 sendEventToMainThread(GetWindowEventTarget(p_vout->p_sys->theWindow), kEventClassVLCPlugin, kEventVLCPluginHideFullscreen);
239 GDHandle deviceHdl = GetMainDevice();
240 deviceRect = (*deviceHdl)->gdRect;
242 if( !p_vout->p_sys->theWindow )
244 /* Create a window */
245 WindowAttributes windowAttrs;
247 windowAttrs = kWindowStandardDocumentAttributes
248 | kWindowStandardHandlerAttribute
249 | kWindowLiveResizeAttribute
250 | kWindowNoShadowAttribute;
252 windowAttrs &= (~kWindowResizableAttribute);
254 CreateNewWindow(kDocumentWindowClass, windowAttrs, &deviceRect, &p_vout->p_sys->theWindow);
255 if( !p_vout->p_sys->winGroup )
257 CreateWindowGroup(0, &p_vout->p_sys->winGroup);
258 SetWindowGroup(p_vout->p_sys->theWindow, p_vout->p_sys->winGroup);
259 SetWindowGroupParent( p_vout->p_sys->winGroup, GetWindowGroupOfClass(kDocumentWindowClass) ) ;
263 CFStringRef titleKey = CFSTR("Fullscreen VLC media plugin");
264 CFStringRef windowTitle = CFCopyLocalizedString(titleKey, NULL);
265 SetWindowTitleWithCFString(p_vout->p_sys->theWindow, windowTitle);
267 CFRelease(windowTitle);
269 //Install event handler
270 static const EventTypeSpec win_events[] = {
271 { kEventClassMouse, kEventMouseDown },
272 { kEventClassMouse, kEventMouseMoved },
273 { kEventClassMouse, kEventMouseUp },
274 { kEventClassWindow, kEventWindowClosed },
275 { kEventClassWindow, kEventWindowBoundsChanged },
276 { kEventClassCommand, kEventCommandProcess },
277 { kEventClassVLCPlugin, kEventVLCPluginShowFullscreen },
278 { kEventClassVLCPlugin, kEventVLCPluginHideFullscreen },
280 InstallWindowEventHandler (p_vout->p_sys->theWindow, NewEventHandlerUPP (WindowEventHandler), GetEventTypeCount(win_events), win_events, p_vout, NULL);
284 /* just in case device resolution changed */
285 SetWindowBounds(p_vout->p_sys->theWindow, kWindowContentRgn, &deviceRect);
287 glClear( GL_COLOR_BUFFER_BIT );
288 p_vout->p_sys->agl_drawable = (AGLDrawable)GetWindowPort(p_vout->p_sys->theWindow);
289 aglSetDrawable(p_vout->p_sys->agl_ctx, p_vout->p_sys->agl_drawable);
290 aglSetCurrentContext(p_vout->p_sys->agl_ctx);
291 aglSetViewport(p_vout, deviceRect, deviceRect);
292 //aglSetFullScreen(p_vout->p_sys->agl_ctx, device_width, device_height, 0, 0);
294 /* Most Carbon APIs are not thread-safe, therefore delagate some GUI visibilty update to the main thread */
295 sendEventToMainThread(GetWindowEventTarget(p_vout->p_sys->theWindow), kEventClassVLCPlugin, kEventVLCPluginShowFullscreen);
299 p_vout->b_fullscreen = !p_vout->b_fullscreen;
300 p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
305 int aglControl( vout_thread_t *p_vout, int i_query, va_list args )
309 case VOUT_SET_VIEWPORT:
311 Rect viewBounds, clipBounds;
312 viewBounds.top = va_arg( args, int);
313 viewBounds.left = va_arg( args, int);
314 viewBounds.bottom = va_arg( args, int);
315 viewBounds.right = va_arg( args, int);
316 clipBounds.top = va_arg( args, int);
317 clipBounds.left = va_arg( args, int);
318 clipBounds.bottom = va_arg( args, int);
319 clipBounds.right = va_arg( args, int);
321 if( !p_vout->b_fullscreen )
324 ** check that the clip rect is not empty, as this is used
325 ** by Firefox to prevent a plugin from displaying during
326 ** a scrolling event. In this case we just prevent buffers
327 ** from being swapped and ignore clipping as this is less
328 ** disruptive than a GL geometry change
331 p_vout->p_sys->b_clipped_out = (clipBounds.top == clipBounds.bottom)
332 || (clipBounds.left == clipBounds.right);
333 if( ! p_vout->p_sys->b_clipped_out )
335 /* ignore consecutive viewport update with identical parameters */
336 if( memcmp(&clipBounds, &(p_vout->p_sys->clipBounds), sizeof(clipBounds) )
337 && memcmp(&viewBounds, &(p_vout->p_sys->viewBounds), sizeof(viewBounds)) )
340 aglSetViewport(p_vout, viewBounds, clipBounds);
341 aglReshape( p_vout );
343 p_vout->p_sys->clipBounds = clipBounds;
344 p_vout->p_sys->viewBounds = viewBounds;
351 case VOUT_REDRAW_RECT:
353 vout_thread_t * p_parent;
356 areaBounds.top = va_arg( args, int);
357 areaBounds.left = va_arg( args, int);
358 areaBounds.bottom = va_arg( args, int);
359 areaBounds.right = va_arg( args, int);
361 /* Ask the opengl module to redraw */
362 p_parent = (vout_thread_t *) p_vout->p_parent;
363 if( p_parent && p_parent->pf_display )
365 p_parent->pf_display( p_parent, NULL );
372 AGLDrawable drawable = (AGLDrawable)va_arg( args, int);
373 if( !p_vout->b_fullscreen && drawable != p_vout->p_sys->agl_drawable )
375 p_vout->p_sys->agl_drawable = drawable;
376 aglSetDrawable(p_vout->p_sys->agl_ctx, drawable);
386 void aglSwap( vout_thread_t * p_vout )
388 if( ! p_vout->p_sys->b_clipped_out )
390 p_vout->p_sys->b_got_frame = true;
391 aglSwapBuffers(p_vout->p_sys->agl_ctx);
400 /* Enter this function with the p_vout locked */
401 static void aglSetViewport( vout_thread_t *p_vout, Rect viewBounds, Rect clipBounds )
403 // mozilla plugin provides coordinates based on port bounds
404 // however AGL coordinates are based on window structure region
405 // and are vertically flipped
407 CGrafPtr port = (CGrafPtr)p_vout->p_sys->agl_drawable;
408 Rect winBounds, clientBounds;
410 GetWindowBounds(GetWindowFromPort(port),
411 kWindowStructureRgn, &winBounds);
412 GetWindowBounds(GetWindowFromPort(port),
413 kWindowContentRgn, &clientBounds);
415 /* update video clipping bounds in drawable */
416 rect[0] = (clientBounds.left-winBounds.left)
417 + clipBounds.left; // from window left edge
418 rect[1] = (winBounds.bottom-winBounds.top)
419 - (clientBounds.top-winBounds.top)
420 - clipBounds.bottom; // from window bottom edge
421 rect[2] = clipBounds.right-clipBounds.left; // width
422 rect[3] = clipBounds.bottom-clipBounds.top; // height
423 aglSetInteger(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT, rect);
424 aglEnable(p_vout->p_sys->agl_ctx, AGL_BUFFER_RECT);
426 /* update video internal bounds in drawable */
427 p_vout->p_sys->i_width = viewBounds.right-viewBounds.left;
428 p_vout->p_sys->i_height = viewBounds.bottom-viewBounds.top;
429 p_vout->p_sys->i_offx = -clipBounds.left - viewBounds.left;
430 p_vout->p_sys->i_offy = clipBounds.bottom + viewBounds.top
431 - p_vout->p_sys->i_height;
433 aglUpdateContext(p_vout->p_sys->agl_ctx);
436 //default window event handler
437 static pascal OSStatus WindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
439 OSStatus result = noErr;
440 UInt32 class = GetEventClass (event);
441 UInt32 kind = GetEventKind (event);
442 vout_thread_t *p_vout = (vout_thread_t *)userData;
444 result = CallNextEventHandler(nextHandler, event);
445 if(class == kEventClassCommand)
447 HICommand theHICommand;
448 GetEventParameter( event, kEventParamDirectObject, typeHICommand, NULL, sizeof( HICommand ), NULL, &theHICommand );
450 switch ( theHICommand.commandID )
453 result = eventNotHandledErr;
456 else if(class == kEventClassWindow)
459 Rect rectPort = {0,0,0,0};
461 GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &window);
465 GetPortBounds(GetWindowPort(window), &rectPort);
470 case kEventWindowClosed:
471 case kEventWindowZoomed:
472 case kEventWindowBoundsChanged:
476 result = eventNotHandledErr;
479 else if(class == kEventClassMouse)
483 case kEventMouseDown:
487 GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
490 case kEventMouseButtonPrimary:
494 var_Get( p_vout, "mouse-button-down", &val );
496 var_Set( p_vout, "mouse-button-down", val );
499 case kEventMouseButtonSecondary:
503 var_Get( p_vout, "mouse-button-down", &val );
505 var_Set( p_vout, "mouse-button-down", val );
508 case kEventMouseButtonTertiary:
512 var_Get( p_vout, "mouse-button-down", &val );
514 var_Set( p_vout, "mouse-button-down", val );
518 result = eventNotHandledErr;
527 GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
530 case kEventMouseButtonPrimary:
532 UInt32 clickCount = 0;
533 GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount);
539 var_Set((vout_thread_t *) p_vout->p_parent, "fullscreen", val);
545 var_SetBool( p_vout, "mouse-clicked", true );
547 var_Get( p_vout, "mouse-button-down", &val );
549 var_Set( p_vout, "mouse-button-down", val );
553 case kEventMouseButtonSecondary:
557 var_Get( p_vout, "mouse-button-down", &val );
559 var_Set( p_vout, "mouse-button-down", val );
562 case kEventMouseButtonTertiary:
566 var_Get( p_vout, "mouse-button-down", &val );
568 var_Set( p_vout, "mouse-button-down", val );
572 result = eventNotHandledErr;
577 case kEventMouseMoved:
582 unsigned int i_x, i_y;
583 unsigned int i_height = p_vout->p_sys->i_height;
584 unsigned int i_width = p_vout->p_sys->i_width;
586 vout_PlacePicture(p_vout, i_width, i_height, &i_x, &i_y, &i_width, &i_height);
588 GetEventParameter(event, kEventParamWindowMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &ml);
590 val.i_int = ( ((int)ml.h) - i_x ) *
591 p_vout->render.i_width / i_width;
592 var_Set( p_vout, "mouse-x", val );
594 val.i_int = ( ((int)ml.v) - i_y ) *
595 p_vout->render.i_height / i_height;
597 var_Set( p_vout, "mouse-y", val );
600 var_Set( p_vout, "mouse-moved", val );
606 result = eventNotHandledErr;
609 else if(class == kEventClassTextInput)
613 case kEventTextInputUnicodeForKeyEvent:
618 result = eventNotHandledErr;
621 else if(class == kEventClassVLCPlugin)
625 case kEventVLCPluginShowFullscreen:
626 ShowWindow (p_vout->p_sys->theWindow);
627 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
628 //CGDisplayHideCursor(kCGDirectMainDisplay);
630 case kEventVLCPluginHideFullscreen:
631 HideWindow (p_vout->p_sys->theWindow);
632 SetSystemUIMode( kUIModeNormal, 0);
633 CGDisplayShowCursor(kCGDirectMainDisplay);
636 result = eventNotHandledErr;
643 int aglLock( vout_thread_t * p_vout )
647 * before 10.4, we set the AGL context as current and
648 * then we retrieve and use the matching CGL context
650 aglSetCurrentContext(p_vout->p_sys->agl_ctx);
651 return kCGLNoError != CGLLockContext( CGLGetCurrentContext() );
653 /* since 10.4, this is the safe way to get the underlying CGL context */
654 CGLContextObj cglContext;
655 if( aglGetCGLContext(p_vout->p_sys->agl_ctx, (void**)&cglContext) )
657 if( kCGLNoError == CGLLockContext( cglContext ) )
659 aglSetCurrentContext(p_vout->p_sys->agl_ctx);
667 void aglUnlock( vout_thread_t * p_vout )
671 * before 10.4, we assume that the AGL context is current.
672 * therefore, we use the current CGL context
674 CGLUnlockContext( CGLGetCurrentContext() );
676 /* since 10.4, this is the safe way to get the underlying CGL context */
677 CGLContextObj cglContext;
678 if( aglGetCGLContext(p_vout->p_sys->agl_ctx, (void**)&cglContext) )
680 CGLUnlockContext( cglContext );