1 /*****************************************************************************
2 * vout.m: MacOS X video output module
3 *****************************************************************************
4 * Copyright (C) 2001-2003 VideoLAN
5 * $Id: vout.m,v 1.77 2004/02/03 13:00:27 titer Exp $
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 <thedj@users.sourceforge.net>
11 * Eric Petit <titer@m0k.org>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
31 #include <errno.h> /* ENOMEM */
32 #include <stdlib.h> /* free() */
33 #include <string.h> /* strerror() */
35 #include <QuickTime/QuickTime.h>
37 #include <OpenGL/OpenGL.h>
38 #include <OpenGL/gl.h>
39 #include <OpenGL/glext.h>
46 #define QT_MAX_DIRECTBUFFERS 10
47 #define VL_MAX_DISPLAYS 16
54 /* When using I420 output */
55 PlanarPixmapInfoYUV420 pixmap_i420;
58 /*****************************************************************************
60 *****************************************************************************/
62 static int vout_Init ( vout_thread_t * );
63 static void vout_End ( vout_thread_t * );
64 static int vout_Manage ( vout_thread_t * );
65 static void vout_Display ( vout_thread_t *, picture_t * );
67 static int CoSendRequest ( vout_thread_t *, SEL );
68 static int CoCreateWindow ( vout_thread_t * );
69 static int CoDestroyWindow ( vout_thread_t * );
70 static int CoToggleFullscreen ( vout_thread_t * );
72 static void VLCHideMouse ( vout_thread_t *, BOOL );
74 static void QTScaleMatrix ( vout_thread_t * );
75 static int QTCreateSequence ( vout_thread_t * );
76 static void QTDestroySequence ( vout_thread_t * );
77 static int QTNewPicture ( vout_thread_t *, picture_t * );
78 static void QTFreePicture ( vout_thread_t *, picture_t * );
80 /*****************************************************************************
81 * OpenVideo: allocates MacOS X video thread output method
82 *****************************************************************************
83 * This function allocates and initializes a MacOS X vout method.
84 *****************************************************************************/
85 int E_(OpenVideo) ( vlc_object_t *p_this )
87 vout_thread_t * p_vout = (vout_thread_t *)p_this;
91 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
92 if( p_vout->p_sys == NULL )
94 msg_Err( p_vout, "out of memory" );
98 memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
100 /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
101 for( i_timeout = 20 ; i_timeout-- ; )
105 msleep( INTF_IDLE_SLEEP );
111 /* no MacOS X intf, unable to communicate with MT */
112 msg_Err( p_vout, "no MacOS X interface present" );
113 free( p_vout->p_sys );
117 if( [NSApp respondsToSelector: @selector(getIntf)] )
119 intf_thread_t * p_intf;
121 for( i_timeout = 10 ; i_timeout-- ; )
123 if( ( p_intf = [NSApp getIntf] ) == NULL )
125 msleep( INTF_IDLE_SLEEP );
131 msg_Err( p_vout, "MacOS X intf has getIntf, but is NULL" );
132 free( p_vout->p_sys );
137 p_vout->p_sys->b_mouse_moved = VLC_TRUE;
138 p_vout->p_sys->i_time_mouse_last_moved = mdate();
140 /* set window size */
141 p_vout->p_sys->s_rect.size.width = p_vout->i_window_width;
142 p_vout->p_sys->s_rect.size.height = p_vout->i_window_height;
144 /* Check if we should use QuickTime or OpenGL */
145 p_vout->p_sys->i_opengl = config_GetInt( p_vout, "macosx-opengl" );
147 if( !p_vout->p_sys->i_opengl )
149 /* Initialize QuickTime */
150 p_vout->p_sys->h_img_descr =
151 (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
152 p_vout->p_sys->p_matrix =
153 (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
154 p_vout->p_sys->p_fullscreen_state = NULL;
156 if( ( err = EnterMovies() ) != noErr )
158 msg_Err( p_vout, "EnterMovies failed: %d", err );
159 free( p_vout->p_sys->p_matrix );
160 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
161 free( p_vout->p_sys );
165 /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
166 vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
168 err = FindCodec( kYUV420CodecType, bestSpeedCodec,
169 nil, &p_vout->p_sys->img_dc );
171 vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
172 if( err == noErr && p_vout->p_sys->img_dc != 0 )
174 p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
175 p_vout->p_sys->i_codec = kYUV420CodecType;
179 msg_Err( p_vout, "failed to find an appropriate codec" );
182 if( p_vout->p_sys->img_dc == 0 )
184 free( p_vout->p_sys->p_matrix );
185 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
186 free( p_vout->p_sys );
191 NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
192 NSArray * o_screens = [NSScreen screens];
193 if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
196 vlc_value_t val, text;
199 int i_option = config_GetInt( p_vout, "macosx-vdev" );
201 var_Create( p_vout, "video-device", VLC_VAR_INTEGER |
203 text.psz_string = _("Video device");
204 var_Change( p_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
206 NSEnumerator * o_enumerator = [o_screens objectEnumerator];
208 while( (o_screen = [o_enumerator nextObject]) != NULL )
211 NSRect s_rect = [o_screen frame];
213 snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
214 "%s %d (%dx%d)", _("Screen"), i,
215 (int)s_rect.size.width, (int)s_rect.size.height );
217 text.psz_string = psz_temp;
219 var_Change( p_vout, "video-device",
220 VLC_VAR_ADDCHOICE, &val, &text );
222 if( ( i - 1 ) == i_option )
224 var_Set( p_vout, "video-device", val );
229 var_AddCallback( p_vout, "video-device", vout_VarCallback,
232 val.b_bool = VLC_TRUE;
233 var_Set( p_vout, "intf-change", val );
237 if( CoCreateWindow( p_vout ) )
239 msg_Err( p_vout, "unable to create window" );
240 if( !p_vout->p_sys->i_opengl )
242 free( p_vout->p_sys->p_matrix );
243 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
245 free( p_vout->p_sys );
249 p_vout->pf_init = vout_Init;
250 p_vout->pf_end = vout_End;
251 p_vout->pf_manage = vout_Manage;
252 p_vout->pf_render = NULL;
253 p_vout->pf_display = vout_Display;
258 /*****************************************************************************
259 * vout_Init: initialize video thread output method
260 *****************************************************************************/
261 static int vout_Init( vout_thread_t *p_vout )
266 I_OUTPUTPICTURES = 0;
268 /* Initialize the output structure; we already found a codec,
269 * and the corresponding chroma we will be using. Since we can
270 * arbitrary scale, stick to the coordinates and aspect. */
271 p_vout->output.i_width = p_vout->render.i_width;
272 p_vout->output.i_height = p_vout->render.i_height;
273 p_vout->output.i_aspect = p_vout->render.i_aspect;
275 if( !p_vout->p_sys->i_opengl )
277 SetPort( p_vout->p_sys->p_qdport );
278 QTScaleMatrix( p_vout );
280 if( QTCreateSequence( p_vout ) )
282 msg_Err( p_vout, "unable to create sequence" );
288 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
289 p_vout->output.i_rmask = 0xFF0000;
290 p_vout->output.i_gmask = 0x00FF00;
291 p_vout->output.i_bmask = 0x0000FF;
294 /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
295 while( I_OUTPUTPICTURES <
296 p_vout->p_sys->i_opengl ? 1 : QT_MAX_DIRECTBUFFERS )
300 /* Find an empty picture slot */
301 for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
303 if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
305 p_pic = p_vout->p_picture + i_index;
310 /* Allocate the picture */
316 if( !p_vout->p_sys->i_opengl )
318 if( QTNewPicture( p_vout, p_pic ) )
325 /* Nothing special to do, we just need a basic allocated
327 vout_AllocatePicture( p_vout, p_pic, p_vout->output.i_chroma,
328 p_vout->output.i_width, p_vout->output.i_height,
329 p_vout->output.i_aspect );
332 p_pic->i_status = DESTROYED_PICTURE;
333 p_pic->i_type = DIRECT_PICTURE;
335 PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
340 if( p_vout->p_sys->i_opengl )
342 [p_vout->p_sys->o_glview lockFocus];
343 [p_vout->p_sys->o_glview initTextures];
344 [p_vout->p_sys->o_glview unlockFocus];
350 /*****************************************************************************
351 * vout_End: terminate video thread output method
352 *****************************************************************************/
353 static void vout_End( vout_thread_t *p_vout )
357 if( !p_vout->p_sys->i_opengl )
359 QTDestroySequence( p_vout );
362 /* Free the direct buffers we allocated */
363 for( i_index = I_OUTPUTPICTURES; i_index; )
366 if( !p_vout->p_sys->i_opengl )
368 QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
372 free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
377 /*****************************************************************************
378 * CloseVideo: destroy video thread output method
379 *****************************************************************************/
380 void E_(CloseVideo) ( vlc_object_t *p_this )
382 vout_thread_t * p_vout = (vout_thread_t *)p_this;
384 if( CoDestroyWindow( p_vout ) )
386 msg_Err( p_vout, "unable to destroy window" );
389 if( !p_vout->p_sys->i_opengl )
391 if ( p_vout->p_sys->p_fullscreen_state != NULL )
392 EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
396 free( p_vout->p_sys->p_matrix );
397 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
400 free( p_vout->p_sys );
403 /*****************************************************************************
404 * vout_Manage: handle events
405 *****************************************************************************
406 * This function should be called regularly by video output thread. It manages
407 * console events. It returns a non null value on error.
408 *****************************************************************************/
409 static int vout_Manage( vout_thread_t *p_vout )
411 if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
413 if( CoToggleFullscreen( p_vout ) )
418 p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
421 if( p_vout->i_changes & VOUT_SIZE_CHANGE )
423 if( !p_vout->p_sys->i_opengl )
425 QTScaleMatrix( p_vout );
426 SetDSequenceMatrix( p_vout->p_sys->i_seq,
427 p_vout->p_sys->p_matrix );
430 p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
433 /* hide/show mouse cursor
434 * this code looks unnecessarily complicated, but is necessary like this.
435 * it has to deal with multiple monitors and therefore checks a lot */
436 if( !p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
438 if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 3000000 )
440 VLCHideMouse( p_vout, YES );
443 else if ( p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
445 VLCHideMouse( p_vout, NO );
448 /* disable screen saver */
449 UpdateSystemActivity( UsrActivity );
454 /*****************************************************************************
455 * vout_Display: displays previously rendered output
456 *****************************************************************************
457 * This function sends the currently rendered image to the display.
458 *****************************************************************************/
459 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
461 if( !p_vout->p_sys->i_opengl )
466 if( ( err = DecompressSequenceFrameS(
467 p_vout->p_sys->i_seq,
468 p_pic->p_sys->p_info,
469 p_pic->p_sys->i_size,
470 codecFlagUseImageBuffer, &flags, nil ) != noErr ) )
472 msg_Warn( p_vout, "DecompressSequenceFrameS failed: %d", err );
476 QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
481 if( [p_vout->p_sys->o_glview lockFocusIfCanDraw] )
483 /* Texture gotta be reload before the buffer is filled
484 (thanks to gcc from arstechnica forums) */
485 [p_vout->p_sys->o_glview drawRect:
486 [p_vout->p_sys->o_glview bounds]];
487 [p_vout->p_sys->o_glview reloadTexture];
488 [p_vout->p_sys->o_glview unlockFocus];
493 /*****************************************************************************
494 * CoSendRequest: send request to interface thread
495 *****************************************************************************
496 * Returns 0 on success, 1 otherwise
497 *****************************************************************************/
498 static int CoSendRequest( vout_thread_t *p_vout, SEL sel )
502 intf_thread_t * p_intf;
504 VLCVout * o_vlv = [[VLCVout alloc] init];
506 if( ( i_ret = ExecuteOnMainThread( o_vlv, sel, (void *)p_vout ) ) )
508 msg_Err( p_vout, "SendRequest: no way to communicate with mt" );
513 /*This makes this function dependant of the presence of a macosx
514 interface. We do not check if this interface exists, since it has
515 already been done before.*/
517 p_intf = [NSApp getIntf];
519 val.b_bool = VLC_TRUE;
520 var_Create(p_intf,"intf-change",VLC_VAR_BOOL);
521 var_Set(p_intf, "intf-change",val);
526 /*****************************************************************************
527 * CoCreateWindow: create new window
528 *****************************************************************************
529 * Returns 0 on success, 1 otherwise
530 *****************************************************************************/
531 static int CoCreateWindow( vout_thread_t *p_vout )
533 if( CoSendRequest( p_vout, @selector(createWindow:) ) )
535 msg_Err( p_vout, "CoSendRequest (createWindow) failed" );
542 /*****************************************************************************
543 * CoDestroyWindow: destroy window
544 *****************************************************************************
545 * Returns 0 on success, 1 otherwise
546 *****************************************************************************/
547 static int CoDestroyWindow( vout_thread_t *p_vout )
550 VLCHideMouse( p_vout, NO );
552 if( CoSendRequest( p_vout, @selector(destroyWindow:) ) )
554 msg_Err( p_vout, "CoSendRequest (destroyWindow) failed" );
561 /*****************************************************************************
562 * CoToggleFullscreen: toggle fullscreen
563 *****************************************************************************
564 * Returns 0 on success, 1 otherwise
565 *****************************************************************************/
566 static int CoToggleFullscreen( vout_thread_t *p_vout )
568 if( p_vout->p_sys->i_opengl )
574 QTDestroySequence( p_vout );
576 if( CoDestroyWindow( p_vout ) )
578 msg_Err( p_vout, "unable to destroy window" );
582 p_vout->b_fullscreen = !p_vout->b_fullscreen;
584 if( CoCreateWindow( p_vout ) )
586 msg_Err( p_vout, "unable to create window" );
590 SetPort( p_vout->p_sys->p_qdport );
591 QTScaleMatrix( p_vout );
593 if( QTCreateSequence( p_vout ) )
595 msg_Err( p_vout, "unable to create sequence" );
602 /*****************************************************************************
603 * VLCHideMouse: if b_hide then hide the cursor
604 *****************************************************************************/
605 static void VLCHideMouse ( vout_thread_t *p_vout, BOOL b_hide )
610 NSWindow *o_window = p_vout->p_sys->o_window;
611 NSView *o_contents = [o_window contentView];
613 s_rect = [o_contents bounds];
614 ml = [o_window convertScreenToBase:[NSEvent mouseLocation]];
615 ml = [o_contents convertPoint:ml fromView:nil];
616 b_inside = [o_contents mouse: ml inRect: s_rect];
618 if ( b_hide && b_inside )
620 /* only hide if mouse over VLCQTView */
621 [NSCursor setHiddenUntilMouseMoves: YES];
625 [NSCursor setHiddenUntilMouseMoves: NO];
627 p_vout->p_sys->b_mouse_moved = NO;
628 p_vout->p_sys->i_time_mouse_last_moved = mdate();
632 /*****************************************************************************
633 * QTScaleMatrix: scale matrix
634 *****************************************************************************/
635 static void QTScaleMatrix( vout_thread_t *p_vout )
638 unsigned int i_width, i_height;
639 Fixed factor_x, factor_y;
640 unsigned int i_offset_x = 0;
641 unsigned int i_offset_y = 0;
643 GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
645 i_width = s_rect.right - s_rect.left;
646 i_height = s_rect.bottom - s_rect.top;
648 if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
650 int i_adj_width = i_height * p_vout->output.i_aspect /
653 factor_x = FixDiv( Long2Fix( i_adj_width ),
654 Long2Fix( p_vout->output.i_width ) );
655 factor_y = FixDiv( Long2Fix( i_height ),
656 Long2Fix( p_vout->output.i_height ) );
658 i_offset_x = (i_width - i_adj_width) / 2;
662 int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
663 p_vout->output.i_aspect;
665 factor_x = FixDiv( Long2Fix( i_width ),
666 Long2Fix( p_vout->output.i_width ) );
667 factor_y = FixDiv( Long2Fix( i_adj_height ),
668 Long2Fix( p_vout->output.i_height ) );
670 i_offset_y = (i_height - i_adj_height) / 2;
673 SetIdentityMatrix( p_vout->p_sys->p_matrix );
675 ScaleMatrix( p_vout->p_sys->p_matrix,
677 Long2Fix(0), Long2Fix(0) );
679 TranslateMatrix( p_vout->p_sys->p_matrix,
680 Long2Fix(i_offset_x),
681 Long2Fix(i_offset_y) );
685 /*****************************************************************************
686 * QTCreateSequence: create a new sequence
687 *****************************************************************************
688 * Returns 0 on success, 1 otherwise
689 *****************************************************************************/
690 static int QTCreateSequence( vout_thread_t *p_vout )
693 ImageDescriptionPtr p_descr;
695 HLock( (Handle)p_vout->p_sys->h_img_descr );
696 p_descr = *p_vout->p_sys->h_img_descr;
698 p_descr->idSize = sizeof(ImageDescription);
699 p_descr->cType = p_vout->p_sys->i_codec;
700 p_descr->version = 1;
701 p_descr->revisionLevel = 0;
702 p_descr->vendor = 'appl';
703 p_descr->width = p_vout->output.i_width;
704 p_descr->height = p_vout->output.i_height;
705 p_descr->hRes = Long2Fix(72);
706 p_descr->vRes = Long2Fix(72);
707 p_descr->spatialQuality = codecLosslessQuality;
708 p_descr->frameCount = 1;
709 p_descr->clutID = -1;
710 p_descr->dataSize = 0;
713 HUnlock( (Handle)p_vout->p_sys->h_img_descr );
715 if( ( err = DecompressSequenceBeginS(
716 &p_vout->p_sys->i_seq,
717 p_vout->p_sys->h_img_descr,
719 p_vout->p_sys->p_qdport,
721 p_vout->p_sys->p_matrix,
723 codecFlagUseImageBuffer,
724 codecLosslessQuality,
725 p_vout->p_sys->img_dc ) ) )
727 msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
734 /*****************************************************************************
735 * QTDestroySequence: destroy sequence
736 *****************************************************************************/
737 static void QTDestroySequence( vout_thread_t *p_vout )
739 CDSequenceEnd( p_vout->p_sys->i_seq );
742 /*****************************************************************************
743 * QTNewPicture: allocate a picture
744 *****************************************************************************
745 * Returns 0 on success, 1 otherwise
746 *****************************************************************************/
747 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
749 int i_width = p_vout->output.i_width;
750 int i_height = p_vout->output.i_height;
752 /* We know the chroma, allocate a buffer which will be used
753 * directly by the decoder */
754 p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
756 if( p_pic->p_sys == NULL )
761 switch( p_vout->output.i_chroma )
763 case VLC_FOURCC('I','4','2','0'):
765 p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
766 p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
768 /* Allocate the memory buffer */
769 p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
770 16, i_width * i_height * 3 / 2 );
773 p_pic->Y_PIXELS = p_pic->p_data;
774 p_pic->p[Y_PLANE].i_lines = i_height;
775 p_pic->p[Y_PLANE].i_pitch = i_width;
776 p_pic->p[Y_PLANE].i_pixel_pitch = 1;
777 p_pic->p[Y_PLANE].i_visible_pitch = i_width;
780 p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
781 p_pic->p[U_PLANE].i_lines = i_height / 2;
782 p_pic->p[U_PLANE].i_pitch = i_width / 2;
783 p_pic->p[U_PLANE].i_pixel_pitch = 1;
784 p_pic->p[U_PLANE].i_visible_pitch = i_width / 2;
787 p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
788 p_pic->p[V_PLANE].i_lines = i_height / 2;
789 p_pic->p[V_PLANE].i_pitch = i_width / 2;
790 p_pic->p[V_PLANE].i_pixel_pitch = 1;
791 p_pic->p[V_PLANE].i_visible_pitch = i_width / 2;
793 /* We allocated 3 planes */
796 #define P p_pic->p_sys->pixmap_i420
797 P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
798 - p_pic->p_sys->p_info;
799 P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
800 - p_pic->p_sys->p_info;
801 P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
802 - p_pic->p_sys->p_info;
804 P.componentInfoY.rowBytes = i_width;
805 P.componentInfoCb.rowBytes = i_width / 2;
806 P.componentInfoCr.rowBytes = i_width / 2;
812 /* Unknown chroma, tell the guy to get lost */
813 free( p_pic->p_sys );
814 msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
815 p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
823 /*****************************************************************************
824 * QTFreePicture: destroy a picture allocated with QTNewPicture
825 *****************************************************************************/
826 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
828 switch( p_vout->output.i_chroma )
830 case VLC_FOURCC('I','4','2','0'):
831 free( p_pic->p_data_orig );
835 free( p_pic->p_sys );
838 /*****************************************************************************
839 * VLCWindow implementation
840 *****************************************************************************/
841 @implementation VLCWindow
843 - (void)setVout:(vout_thread_t *)_p_vout
848 - (vout_thread_t *)getVout
853 - (void)scaleWindowWithFactor: (float)factor
856 int i_corrected_height, i_corrected_width;
858 NSPoint topleftscreen;
860 if ( !p_vout->b_fullscreen )
863 topleftbase.y = [self frame].size.height;
864 topleftscreen = [self convertBaseToScreen: topleftbase];
866 if( p_vout->output.i_height * p_vout->output.i_aspect >
867 p_vout->output.i_width * VOUT_ASPECT_FACTOR )
869 i_corrected_width = p_vout->output.i_height * p_vout->output.i_aspect /
871 newsize.width = (int) ( i_corrected_width * factor );
872 newsize.height = (int) ( p_vout->render.i_height * factor );
876 i_corrected_height = p_vout->output.i_width * VOUT_ASPECT_FACTOR /
877 p_vout->output.i_aspect;
878 newsize.width = (int) ( p_vout->render.i_width * factor );
879 newsize.height = (int) ( i_corrected_height * factor );
882 [self setContentSize: newsize];
884 [self setFrameTopLeftPoint: topleftscreen];
885 p_vout->i_changes |= VOUT_SIZE_CHANGE;
889 - (void)toggleFloatOnTop
891 if( config_GetInt( p_vout, "video-on-top" ) )
893 config_PutInt( p_vout, "video-on-top", 0 );
894 [p_vout->p_sys->o_window setLevel: NSNormalWindowLevel];
898 config_PutInt( p_vout, "video-on-top", 1 );
899 [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
903 - (void)toggleFullscreen
905 config_PutInt(p_vout, "fullscreen", !p_vout->b_fullscreen);
906 p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
911 return( p_vout->b_fullscreen );
914 - (BOOL)canBecomeKeyWindow
919 - (void)keyDown:(NSEvent *)o_event
923 unsigned int i_pressed_modifiers = 0;
926 i_pressed_modifiers = [o_event modifierFlags];
928 if( i_pressed_modifiers & NSShiftKeyMask )
929 val.i_int |= KEY_MODIFIER_SHIFT;
930 if( i_pressed_modifiers & NSControlKeyMask )
931 val.i_int |= KEY_MODIFIER_CTRL;
932 if( i_pressed_modifiers & NSAlternateKeyMask )
933 val.i_int |= KEY_MODIFIER_ALT;
934 if( i_pressed_modifiers & NSCommandKeyMask )
935 val.i_int |= KEY_MODIFIER_COMMAND;
937 key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
941 /* Escape should always get you out of fullscreen */
942 if( key == (unichar) 0x1b )
944 if( [self isFullscreen] )
946 [self toggleFullscreen];
949 else if ( key == ' ' )
951 playlist_t *p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
953 if ( p_playlist != NULL )
955 playlist_Pause( p_playlist );
956 vlc_object_release( p_playlist);
961 val.i_int |= CocoaKeyToVLC( key );
962 var_Set( p_vout->p_vlc, "key-pressed", val );
967 [super keyDown: o_event];
973 NSMutableString * o_title;
974 playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
977 if( p_playlist == NULL )
982 vlc_mutex_lock( &p_playlist->object_lock );
983 o_title = [NSMutableString stringWithUTF8String:
984 p_playlist->pp_items[p_playlist->i_index]->psz_uri];
985 vlc_mutex_unlock( &p_playlist->object_lock );
987 vlc_object_release( p_playlist );
991 NSRange prefix_range = [o_title rangeOfString: @"file:"];
992 if( prefix_range.location != NSNotFound )
994 [o_title deleteCharactersInRange: prefix_range];
997 [self setTitleWithRepresentedFilename: o_title];
1002 [NSString stringWithCString: VOUT_TITLE " (QuickTime)"]];
1006 /* This is actually the same as VLCControls::stop. */
1007 - (BOOL)windowShouldClose:(id)sender
1009 playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1011 if( p_playlist == NULL )
1016 playlist_Stop( p_playlist );
1017 vlc_object_release( p_playlist );
1019 /* The window will be closed by the intf later. */
1025 /*****************************************************************************
1026 * VLCQTView implementation
1027 *****************************************************************************/
1028 @implementation VLCQTView
1030 - (void)drawRect:(NSRect)rect
1032 vout_thread_t * p_vout;
1033 id o_window = [self window];
1034 p_vout = (vout_thread_t *)[o_window getVout];
1036 [[NSColor blackColor] set];
1038 [super drawRect: rect];
1040 p_vout->i_changes |= VOUT_SIZE_CHANGE;
1043 - (BOOL)acceptsFirstResponder
1048 - (BOOL)becomeFirstResponder
1050 vout_thread_t * p_vout;
1051 id o_window = [self window];
1052 p_vout = (vout_thread_t *)[o_window getVout];
1054 [o_window setAcceptsMouseMovedEvents: YES];
1058 - (BOOL)resignFirstResponder
1060 vout_thread_t * p_vout;
1061 id o_window = [self window];
1062 p_vout = (vout_thread_t *)[o_window getVout];
1064 [o_window setAcceptsMouseMovedEvents: NO];
1065 VLCHideMouse( p_vout, NO );
1069 - (void)mouseDown:(NSEvent *)o_event
1071 vout_thread_t * p_vout;
1072 id o_window = [self window];
1073 p_vout = (vout_thread_t *)[o_window getVout];
1076 switch( [o_event type] )
1078 case NSLeftMouseDown:
1080 var_Get( p_vout, "mouse-button-down", &val );
1082 var_Set( p_vout, "mouse-button-down", val );
1087 [super mouseDown: o_event];
1092 - (void)otherMouseDown:(NSEvent *)o_event
1094 /* This is not the the wheel button. you need to poll the
1095 * mouseWheel event for that. other is a third, forth or fifth button */
1096 vout_thread_t * p_vout;
1097 id o_window = [self window];
1098 p_vout = (vout_thread_t *)[o_window getVout];
1101 switch( [o_event type] )
1103 case NSOtherMouseDown:
1105 var_Get( p_vout, "mouse-button-down", &val );
1107 var_Set( p_vout, "mouse-button-down", val );
1112 [super mouseDown: o_event];
1117 - (void)rightMouseDown:(NSEvent *)o_event
1119 vout_thread_t * p_vout;
1120 id o_window = [self window];
1121 p_vout = (vout_thread_t *)[o_window getVout];
1124 switch( [o_event type] )
1126 case NSRightMouseDown:
1128 var_Get( p_vout, "mouse-button-down", &val );
1130 var_Set( p_vout, "mouse-button-down", val );
1135 [super mouseDown: o_event];
1140 - (void)mouseUp:(NSEvent *)o_event
1142 vout_thread_t * p_vout;
1143 id o_window = [self window];
1144 p_vout = (vout_thread_t *)[o_window getVout];
1147 switch( [o_event type] )
1152 b_val.b_bool = VLC_TRUE;
1153 var_Set( p_vout, "mouse-clicked", b_val );
1155 var_Get( p_vout, "mouse-button-down", &val );
1157 var_Set( p_vout, "mouse-button-down", val );
1162 [super mouseUp: o_event];
1167 - (void)otherMouseUp:(NSEvent *)o_event
1169 vout_thread_t * p_vout;
1170 id o_window = [self window];
1171 p_vout = (vout_thread_t *)[o_window getVout];
1174 switch( [o_event type] )
1176 case NSOtherMouseUp:
1178 var_Get( p_vout, "mouse-button-down", &val );
1180 var_Set( p_vout, "mouse-button-down", val );
1185 [super mouseUp: o_event];
1190 - (void)rightMouseUp:(NSEvent *)o_event
1192 vout_thread_t * p_vout;
1193 id o_window = [self window];
1194 p_vout = (vout_thread_t *)[o_window getVout];
1197 switch( [o_event type] )
1199 case NSRightMouseUp:
1201 var_Get( p_vout, "mouse-button-down", &val );
1203 var_Set( p_vout, "mouse-button-down", val );
1208 [super mouseUp: o_event];
1213 - (void)mouseDragged:(NSEvent *)o_event
1215 [self mouseMoved:o_event];
1218 - (void)otherMouseDragged:(NSEvent *)o_event
1220 [self mouseMoved:o_event];
1223 - (void)rightMouseDragged:(NSEvent *)o_event
1225 [self mouseMoved:o_event];
1228 - (void)mouseMoved:(NSEvent *)o_event
1234 vout_thread_t * p_vout;
1235 id o_window = [self window];
1236 p_vout = (vout_thread_t *)[o_window getVout];
1238 s_rect = [self bounds];
1239 ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
1240 b_inside = [self mouse: ml inRect: s_rect];
1245 int i_width, i_height, i_x, i_y;
1247 vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
1248 (unsigned int)s_rect.size.height,
1249 &i_x, &i_y, &i_width, &i_height );
1251 val.i_int = ( ((int)ml.x) - i_x ) *
1252 p_vout->render.i_width / i_width;
1253 var_Set( p_vout, "mouse-x", val );
1255 val.i_int = ( ((int)ml.y) - i_y ) *
1256 p_vout->render.i_height / i_height;
1257 var_Set( p_vout, "mouse-y", val );
1259 val.b_bool = VLC_TRUE;
1260 var_Set( p_vout, "mouse-moved", val );
1261 p_vout->p_sys->i_time_mouse_last_moved = mdate();
1262 p_vout->p_sys->b_mouse_moved = YES;
1265 [super mouseMoved: o_event];
1270 /*****************************************************************************
1271 * VLCGLView implementation
1272 *****************************************************************************/
1273 @implementation VLCGLView
1276 - (id) initWithFrame: (NSRect) frame vout: (vout_thread_t*) _p_vout
1280 NSOpenGLPixelFormatAttribute attribs[] =
1282 NSOpenGLPFAAccelerated,
1283 NSOpenGLPFANoRecovery,
1284 NSOpenGLPFADoubleBuffer,
1289 NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
1290 initWithAttributes: attribs];
1294 fprintf( stderr, "Cannot create NSOpenGLPixelFormat\n" );
1298 self = [super initWithFrame:frame pixelFormat: fmt];
1300 [[self openGLContext] makeCurrentContext];
1301 [[self openGLContext] update];
1303 glClearColor( 0.0, 0.0, 0.0, 0.0 );
1312 [[self openGLContext] makeCurrentContext];
1314 NSRect bounds = [self bounds];
1316 glViewport( 0, 0, (GLint) bounds.size.width,
1317 (GLint) bounds.size.height );
1319 /* Quad size is set in order to preserve the aspect ratio */
1320 if( bounds.size.height * p_vout->output.i_aspect <
1321 bounds.size.width * VOUT_ASPECT_FACTOR )
1323 f_x = bounds.size.height * p_vout->render.i_aspect /
1324 VOUT_ASPECT_FACTOR / bounds.size.width;
1330 f_y = bounds.size.width * VOUT_ASPECT_FACTOR /
1331 p_vout->render.i_aspect / bounds.size.height;
1335 - (void) initTextures
1337 [[self openGLContext] makeCurrentContext];
1339 /* Create textures */
1340 glGenTextures( 1, &i_texture );
1342 glEnable( GL_TEXTURE_RECTANGLE_EXT );
1343 glEnable( GL_UNPACK_CLIENT_STORAGE_APPLE );
1345 glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1347 /* Use VRAM texturing */
1348 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1349 GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE );
1351 /* Tell the driver not to make a copy of the texture but to use
1353 glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE );
1355 /* Linear interpolation */
1356 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1357 GL_TEXTURE_MIN_FILTER, GL_LINEAR );
1358 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1359 GL_TEXTURE_MAG_FILTER, GL_LINEAR );
1361 /* I have no idea what this exactly does, but it seems to be
1362 necessary for scaling */
1363 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1364 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
1365 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1366 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1367 glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
1369 glTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
1370 p_vout->output.i_width, p_vout->output.i_height, 0,
1371 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1372 PP_OUTPUTPICTURE[0]->p_data );
1377 - (void) reloadTexture
1379 [[self openGLContext] makeCurrentContext];
1381 glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1383 /* glTexSubImage2D is faster than glTexImage2D
1384 http://developer.apple.com/samplecode/Sample_Code/Graphics_3D/
1385 TextureRange/MainOpenGLView.m.htm */
1386 glTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
1387 p_vout->output.i_width, p_vout->output.i_height,
1388 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1389 PP_OUTPUTPICTURE[0]->p_data );
1392 - (void) drawRect: (NSRect) rect
1394 [[self openGLContext] makeCurrentContext];
1396 /* Swap buffers only during the vertical retrace of the monitor.
1397 http://developer.apple.com/documentation/GraphicsImaging/
1398 Conceptual/OpenGL/chap5/chapter_5_section_44.html */
1399 long params[] = { 1 };
1400 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
1403 /* Black background */
1404 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1408 [[self openGLContext] flushBuffer];
1412 /* Draw a quad with our texture on it */
1413 glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1414 glBegin( GL_QUADS );
1416 glTexCoord2f( 0.0, 0.0 );
1417 glVertex2f( - f_x, f_y );
1419 glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1420 glVertex2f( - f_x, - f_y );
1422 glTexCoord2f( (float) p_vout->output.i_width,
1423 (float) p_vout->output.i_height );
1424 glVertex2f( f_x, - f_y );
1426 glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1427 glVertex2f( f_x, f_y );
1430 /* Wait for the job to be done */
1431 [[self openGLContext] flushBuffer];
1436 /*****************************************************************************
1437 * VLCVout implementation
1438 *****************************************************************************/
1439 @implementation VLCVout
1441 - (void)createWindow:(NSValue *)o_value
1445 NSScreen * o_screen;
1446 vout_thread_t * p_vout;
1447 vlc_bool_t b_main_screen;
1449 p_vout = (vout_thread_t *)[o_value pointerValue];
1451 p_vout->p_sys->o_window = [VLCWindow alloc];
1452 [p_vout->p_sys->o_window setVout: p_vout];
1453 [p_vout->p_sys->o_window setReleasedWhenClosed: YES];
1455 if( var_Get( p_vout, "video-device", &val ) < 0 )
1457 o_screen = [NSScreen mainScreen];
1462 NSArray *o_screens = [NSScreen screens];
1463 unsigned int i_index = val.i_int;
1465 if( [o_screens count] < i_index )
1467 o_screen = [NSScreen mainScreen];
1473 o_screen = [o_screens objectAtIndex: i_index];
1474 config_PutInt( p_vout, "macosx-vdev", i_index );
1475 b_main_screen = (i_index == 0);
1479 if( p_vout->p_sys->i_opengl )
1481 /* XXX Fix fullscreen mode */
1482 p_vout->b_fullscreen = 0;
1485 if( p_vout->b_fullscreen )
1487 NSRect screen_rect = [o_screen frame];
1488 screen_rect.origin.x = screen_rect.origin.y = 0;
1490 if ( b_main_screen && p_vout->p_sys->p_fullscreen_state == NULL )
1491 BeginFullScreen( &p_vout->p_sys->p_fullscreen_state, NULL, 0, 0,
1492 NULL, NULL, fullScreenAllowEvents );
1494 [p_vout->p_sys->o_window
1495 initWithContentRect: screen_rect
1496 styleMask: NSBorderlessWindowMask
1497 backing: NSBackingStoreBuffered
1498 defer: NO screen: o_screen];
1500 //[p_vout->p_sys->o_window setLevel: NSPopUpMenuWindowLevel - 1];
1501 p_vout->p_sys->b_mouse_moved = YES;
1502 p_vout->p_sys->i_time_mouse_last_moved = mdate();
1506 unsigned int i_stylemask = NSTitledWindowMask |
1507 NSMiniaturizableWindowMask |
1508 NSClosableWindowMask |
1509 NSResizableWindowMask;
1511 if( !p_vout->p_sys->i_opengl )
1513 if ( p_vout->p_sys->p_fullscreen_state != NULL )
1514 EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
1515 p_vout->p_sys->p_fullscreen_state = NULL;
1518 [p_vout->p_sys->o_window
1519 initWithContentRect: p_vout->p_sys->s_rect
1520 styleMask: i_stylemask
1521 backing: NSBackingStoreBuffered
1522 defer: NO screen: o_screen];
1524 [p_vout->p_sys->o_window setAlphaValue: config_GetFloat( p_vout, "macosx-opaqueness" )];
1526 if( config_GetInt( p_vout, "video-on-top" ) )
1528 [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
1531 if( !p_vout->p_sys->b_pos_saved )
1533 [p_vout->p_sys->o_window center];
1537 if( !p_vout->p_sys->i_opengl )
1539 o_view = [[VLCQTView alloc] init];
1540 /* FIXME: [o_view setMenu:] */
1541 [p_vout->p_sys->o_window setContentView: o_view];
1542 [o_view autorelease];
1545 p_vout->p_sys->p_qdport = [o_view qdPort];
1546 [o_view unlockFocus];
1550 #define o_glview p_vout->p_sys->o_glview
1551 o_glview = [[VLCGLView alloc] initWithFrame: p_vout->p_sys->s_rect vout: p_vout];
1552 [p_vout->p_sys->o_window setContentView: o_glview];
1553 [o_glview autorelease];
1557 [p_vout->p_sys->o_window updateTitle];
1558 [p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
1562 - (void)destroyWindow:(NSValue *)o_value
1564 vout_thread_t * p_vout;
1566 p_vout = (vout_thread_t *)[o_value pointerValue];
1568 if( !p_vout->b_fullscreen )
1572 s_rect = [[p_vout->p_sys->o_window contentView] frame];
1573 p_vout->p_sys->s_rect.size = s_rect.size;
1575 s_rect = [p_vout->p_sys->o_window frame];
1576 p_vout->p_sys->s_rect.origin = s_rect.origin;
1578 p_vout->p_sys->b_pos_saved = YES;
1581 p_vout->p_sys->p_qdport = nil;
1582 [p_vout->p_sys->o_window close];
1583 p_vout->p_sys->o_window = nil;