1 /*****************************************************************************
2 * vout.m: MacOS X video output module
3 *****************************************************************************
4 * Copyright (C) 2001-2003 VideoLAN
5 * $Id: vout.m,v 1.74 2004/01/28 21:31:15 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>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
30 #include <errno.h> /* ENOMEM */
31 #include <stdlib.h> /* free() */
32 #include <string.h> /* strerror() */
34 #include <QuickTime/QuickTime.h>
36 #include <OpenGL/OpenGL.h>
37 #include <OpenGL/gl.h>
38 #include <OpenGL/glext.h>
45 #define QT_MAX_DIRECTBUFFERS 10
46 #define VL_MAX_DISPLAYS 16
53 /* When using I420 output */
54 PlanarPixmapInfoYUV420 pixmap_i420;
57 /*****************************************************************************
59 *****************************************************************************/
61 static int vout_Init ( vout_thread_t * );
62 static void vout_End ( vout_thread_t * );
63 static int vout_Manage ( vout_thread_t * );
64 static void vout_Display ( vout_thread_t *, picture_t * );
66 static int CoSendRequest ( vout_thread_t *, SEL );
67 static int CoCreateWindow ( vout_thread_t * );
68 static int CoDestroyWindow ( vout_thread_t * );
69 static int CoToggleFullscreen ( vout_thread_t * );
71 static void VLCHideMouse ( vout_thread_t *, BOOL );
73 static void QTScaleMatrix ( vout_thread_t * );
74 static int QTCreateSequence ( vout_thread_t * );
75 static void QTDestroySequence ( vout_thread_t * );
76 static int QTNewPicture ( vout_thread_t *, picture_t * );
77 static void QTFreePicture ( vout_thread_t *, picture_t * );
79 /*****************************************************************************
80 * OpenVideo: allocates MacOS X video thread output method
81 *****************************************************************************
82 * This function allocates and initializes a MacOS X vout method.
83 *****************************************************************************/
84 int E_(OpenVideo) ( vlc_object_t *p_this )
86 vout_thread_t * p_vout = (vout_thread_t *)p_this;
90 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
91 if( p_vout->p_sys == NULL )
93 msg_Err( p_vout, "out of memory" );
97 memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
99 /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
100 for( i_timeout = 20 ; i_timeout-- ; )
104 msleep( INTF_IDLE_SLEEP );
110 /* no MacOS X intf, unable to communicate with MT */
111 msg_Err( p_vout, "no MacOS X interface present" );
112 free( p_vout->p_sys );
116 if( [NSApp respondsToSelector: @selector(getIntf)] )
118 intf_thread_t * p_intf;
120 for( i_timeout = 10 ; i_timeout-- ; )
122 if( ( p_intf = [NSApp getIntf] ) == NULL )
124 msleep( INTF_IDLE_SLEEP );
130 msg_Err( p_vout, "MacOS X intf has getIntf, but is NULL" );
131 free( p_vout->p_sys );
136 p_vout->p_sys->b_mouse_moved = VLC_TRUE;
137 p_vout->p_sys->i_time_mouse_last_moved = mdate();
139 /* set window size */
140 p_vout->p_sys->s_rect.size.width = p_vout->i_window_width;
141 p_vout->p_sys->s_rect.size.height = p_vout->i_window_height;
143 /* Check if we should use QuickTime or OpenGL */
144 p_vout->p_sys->i_opengl = config_GetInt( p_vout, "macosx-opengl" );
146 if( !p_vout->p_sys->i_opengl )
148 /* Initialize QuickTime */
149 p_vout->p_sys->h_img_descr =
150 (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
151 p_vout->p_sys->p_matrix =
152 (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
153 p_vout->p_sys->p_fullscreen_state = NULL;
155 if( ( err = EnterMovies() ) != noErr )
157 msg_Err( p_vout, "EnterMovies failed: %d", err );
158 free( p_vout->p_sys->p_matrix );
159 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
160 free( p_vout->p_sys );
164 /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
165 vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
167 err = FindCodec( kYUV420CodecType, bestSpeedCodec,
168 nil, &p_vout->p_sys->img_dc );
170 vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
171 if( err == noErr && p_vout->p_sys->img_dc != 0 )
173 p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
174 p_vout->p_sys->i_codec = kYUV420CodecType;
178 msg_Err( p_vout, "failed to find an appropriate codec" );
181 if( p_vout->p_sys->img_dc == 0 )
183 free( p_vout->p_sys->p_matrix );
184 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
185 free( p_vout->p_sys );
190 NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
191 NSArray * o_screens = [NSScreen screens];
192 if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
195 vlc_value_t val, text;
198 int i_option = config_GetInt( p_vout, "macosx-vdev" );
200 var_Create( p_vout, "video-device", VLC_VAR_INTEGER |
202 text.psz_string = _("Video device");
203 var_Change( p_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
205 NSEnumerator * o_enumerator = [o_screens objectEnumerator];
207 while( (o_screen = [o_enumerator nextObject]) != NULL )
210 NSRect s_rect = [o_screen frame];
212 snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
213 "%s %d (%dx%d)", _("Screen"), i,
214 (int)s_rect.size.width, (int)s_rect.size.height );
216 text.psz_string = psz_temp;
218 var_Change( p_vout, "video-device",
219 VLC_VAR_ADDCHOICE, &val, &text );
221 if( ( i - 1 ) == i_option )
223 var_Set( p_vout, "video-device", val );
228 var_AddCallback( p_vout, "video-device", vout_VarCallback,
231 val.b_bool = VLC_TRUE;
232 var_Set( p_vout, "intf-change", val );
236 if( CoCreateWindow( p_vout ) )
238 msg_Err( p_vout, "unable to create window" );
239 if( !p_vout->p_sys->i_opengl )
241 free( p_vout->p_sys->p_matrix );
242 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
244 free( p_vout->p_sys );
248 p_vout->pf_init = vout_Init;
249 p_vout->pf_end = vout_End;
250 p_vout->pf_manage = vout_Manage;
251 p_vout->pf_render = NULL;
252 p_vout->pf_display = vout_Display;
257 /*****************************************************************************
258 * vout_Init: initialize video thread output method
259 *****************************************************************************/
260 static int vout_Init( vout_thread_t *p_vout )
265 I_OUTPUTPICTURES = 0;
267 /* Initialize the output structure; we already found a codec,
268 * and the corresponding chroma we will be using. Since we can
269 * arbitrary scale, stick to the coordinates and aspect. */
270 p_vout->output.i_width = p_vout->render.i_width;
271 p_vout->output.i_height = p_vout->render.i_height;
272 p_vout->output.i_aspect = p_vout->render.i_aspect;
274 if( !p_vout->p_sys->i_opengl )
276 SetPort( p_vout->p_sys->p_qdport );
277 QTScaleMatrix( p_vout );
279 if( QTCreateSequence( p_vout ) )
281 msg_Err( p_vout, "unable to create sequence" );
287 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
288 p_vout->output.i_rmask = 0xFF0000;
289 p_vout->output.i_gmask = 0x00FF00;
290 p_vout->output.i_bmask = 0x0000FF;
293 /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
294 while( I_OUTPUTPICTURES <
295 ( p_vout->p_sys->i_opengl ? 3 : QT_MAX_DIRECTBUFFERS ) )
299 /* Find an empty picture slot */
300 for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
302 if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
304 p_pic = p_vout->p_picture + i_index;
309 /* Allocate the picture */
315 if( !p_vout->p_sys->i_opengl )
317 if( QTNewPicture( p_vout, p_pic ) )
324 vout_AllocatePicture( p_vout, p_pic, p_vout->output.i_chroma,
325 p_vout->output.i_width, p_vout->output.i_height,
326 p_vout->output.i_aspect );
329 p_pic->i_status = DESTROYED_PICTURE;
330 p_pic->i_type = DIRECT_PICTURE;
332 PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
337 if( p_vout->p_sys->i_opengl )
339 p_vout->p_sys->o_glview->i_index = -1;
340 p_vout->p_sys->o_glview->i_init_done = 1;
346 /*****************************************************************************
347 * vout_End: terminate video thread output method
348 *****************************************************************************/
349 static void vout_End( vout_thread_t *p_vout )
353 if( !p_vout->p_sys->i_opengl )
355 QTDestroySequence( p_vout );
358 /* Free the direct buffers we allocated */
359 for( i_index = I_OUTPUTPICTURES; i_index; )
362 if( !p_vout->p_sys->i_opengl )
364 QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
368 free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
373 /*****************************************************************************
374 * CloseVideo: destroy video thread output method
375 *****************************************************************************/
376 void E_(CloseVideo) ( vlc_object_t *p_this )
378 vout_thread_t * p_vout = (vout_thread_t *)p_this;
380 if( CoDestroyWindow( p_vout ) )
382 msg_Err( p_vout, "unable to destroy window" );
385 if( !p_vout->p_sys->i_opengl )
387 if ( p_vout->p_sys->p_fullscreen_state != NULL )
388 EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
392 free( p_vout->p_sys->p_matrix );
393 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
396 free( p_vout->p_sys );
399 /*****************************************************************************
400 * vout_Manage: handle events
401 *****************************************************************************
402 * This function should be called regularly by video output thread. It manages
403 * console events. It returns a non null value on error.
404 *****************************************************************************/
405 static int vout_Manage( vout_thread_t *p_vout )
407 if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
409 if( CoToggleFullscreen( p_vout ) )
414 p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
417 if( p_vout->i_changes & VOUT_SIZE_CHANGE )
419 if( !p_vout->p_sys->i_opengl )
421 QTScaleMatrix( p_vout );
422 SetDSequenceMatrix( p_vout->p_sys->i_seq,
423 p_vout->p_sys->p_matrix );
426 p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
429 /* hide/show mouse cursor
430 * this code looks unnecessarily complicated, but is necessary like this.
431 * it has to deal with multiple monitors and therefore checks a lot */
432 if( !p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
434 if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 3000000 )
436 VLCHideMouse( p_vout, YES );
439 else if ( p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
441 VLCHideMouse( p_vout, NO );
444 /* disable screen saver */
445 UpdateSystemActivity( UsrActivity );
450 /*****************************************************************************
451 * vout_Display: displays previously rendered output
452 *****************************************************************************
453 * This function sends the currently rendered image to the display.
454 *****************************************************************************/
455 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
457 if( !p_vout->p_sys->i_opengl )
462 if( ( err = DecompressSequenceFrameS(
463 p_vout->p_sys->i_seq,
464 p_pic->p_sys->p_info,
465 p_pic->p_sys->i_size,
466 codecFlagUseImageBuffer, &flags, nil ) != noErr ) )
468 msg_Warn( p_vout, "DecompressSequenceFrameS failed: %d", err );
472 QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
477 p_vout->p_sys->o_glview->i_index++;
478 p_vout->p_sys->o_glview->i_index %= 3;
479 [p_vout->p_sys->o_glview setNeedsDisplay: YES];
481 PP_OUTPUTPICTURE[(p_vout->p_sys->o_glview->i_index+1)%3]->p_data;
485 /*****************************************************************************
486 * CoSendRequest: send request to interface thread
487 *****************************************************************************
488 * Returns 0 on success, 1 otherwise
489 *****************************************************************************/
490 static int CoSendRequest( vout_thread_t *p_vout, SEL sel )
494 intf_thread_t * p_intf;
496 VLCVout * o_vlv = [[VLCVout alloc] init];
498 if( ( i_ret = ExecuteOnMainThread( o_vlv, sel, (void *)p_vout ) ) )
500 msg_Err( p_vout, "SendRequest: no way to communicate with mt" );
505 /*This makes this function dependant of the presence of a macosx
506 interface. We do not check if this interface exists, since it has
507 already been done before.*/
509 p_intf = [NSApp getIntf];
511 val.b_bool = VLC_TRUE;
512 var_Create(p_intf,"intf-change",VLC_VAR_BOOL);
513 var_Set(p_intf, "intf-change",val);
518 /*****************************************************************************
519 * CoCreateWindow: create new window
520 *****************************************************************************
521 * Returns 0 on success, 1 otherwise
522 *****************************************************************************/
523 static int CoCreateWindow( vout_thread_t *p_vout )
525 if( CoSendRequest( p_vout, @selector(createWindow:) ) )
527 msg_Err( p_vout, "CoSendRequest (createWindow) failed" );
534 /*****************************************************************************
535 * CoDestroyWindow: destroy window
536 *****************************************************************************
537 * Returns 0 on success, 1 otherwise
538 *****************************************************************************/
539 static int CoDestroyWindow( vout_thread_t *p_vout )
542 VLCHideMouse( p_vout, NO );
544 if( CoSendRequest( p_vout, @selector(destroyWindow:) ) )
546 msg_Err( p_vout, "CoSendRequest (destroyWindow) failed" );
553 /*****************************************************************************
554 * CoToggleFullscreen: toggle fullscreen
555 *****************************************************************************
556 * Returns 0 on success, 1 otherwise
557 *****************************************************************************/
558 static int CoToggleFullscreen( vout_thread_t *p_vout )
560 if( p_vout->p_sys->i_opengl )
566 QTDestroySequence( p_vout );
568 if( CoDestroyWindow( p_vout ) )
570 msg_Err( p_vout, "unable to destroy window" );
574 p_vout->b_fullscreen = !p_vout->b_fullscreen;
576 if( CoCreateWindow( p_vout ) )
578 msg_Err( p_vout, "unable to create window" );
582 SetPort( p_vout->p_sys->p_qdport );
583 QTScaleMatrix( p_vout );
585 if( QTCreateSequence( p_vout ) )
587 msg_Err( p_vout, "unable to create sequence" );
594 /*****************************************************************************
595 * VLCHideMouse: if b_hide then hide the cursor
596 *****************************************************************************/
597 static void VLCHideMouse ( vout_thread_t *p_vout, BOOL b_hide )
602 NSWindow *o_window = p_vout->p_sys->o_window;
603 NSView *o_contents = [o_window contentView];
605 s_rect = [o_contents bounds];
606 ml = [o_window convertScreenToBase:[NSEvent mouseLocation]];
607 ml = [o_contents convertPoint:ml fromView:nil];
608 b_inside = [o_contents mouse: ml inRect: s_rect];
610 if ( b_hide && b_inside )
612 /* only hide if mouse over VLCQTView */
613 [NSCursor setHiddenUntilMouseMoves: YES];
617 [NSCursor setHiddenUntilMouseMoves: NO];
619 p_vout->p_sys->b_mouse_moved = NO;
620 p_vout->p_sys->i_time_mouse_last_moved = mdate();
624 /*****************************************************************************
625 * QTScaleMatrix: scale matrix
626 *****************************************************************************/
627 static void QTScaleMatrix( vout_thread_t *p_vout )
630 unsigned int i_width, i_height;
631 Fixed factor_x, factor_y;
632 unsigned int i_offset_x = 0;
633 unsigned int i_offset_y = 0;
635 GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
637 i_width = s_rect.right - s_rect.left;
638 i_height = s_rect.bottom - s_rect.top;
640 if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
642 int i_adj_width = i_height * p_vout->output.i_aspect /
645 factor_x = FixDiv( Long2Fix( i_adj_width ),
646 Long2Fix( p_vout->output.i_width ) );
647 factor_y = FixDiv( Long2Fix( i_height ),
648 Long2Fix( p_vout->output.i_height ) );
650 i_offset_x = (i_width - i_adj_width) / 2;
654 int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
655 p_vout->output.i_aspect;
657 factor_x = FixDiv( Long2Fix( i_width ),
658 Long2Fix( p_vout->output.i_width ) );
659 factor_y = FixDiv( Long2Fix( i_adj_height ),
660 Long2Fix( p_vout->output.i_height ) );
662 i_offset_y = (i_height - i_adj_height) / 2;
665 SetIdentityMatrix( p_vout->p_sys->p_matrix );
667 ScaleMatrix( p_vout->p_sys->p_matrix,
669 Long2Fix(0), Long2Fix(0) );
671 TranslateMatrix( p_vout->p_sys->p_matrix,
672 Long2Fix(i_offset_x),
673 Long2Fix(i_offset_y) );
677 /*****************************************************************************
678 * QTCreateSequence: create a new sequence
679 *****************************************************************************
680 * Returns 0 on success, 1 otherwise
681 *****************************************************************************/
682 static int QTCreateSequence( vout_thread_t *p_vout )
685 ImageDescriptionPtr p_descr;
687 HLock( (Handle)p_vout->p_sys->h_img_descr );
688 p_descr = *p_vout->p_sys->h_img_descr;
690 p_descr->idSize = sizeof(ImageDescription);
691 p_descr->cType = p_vout->p_sys->i_codec;
692 p_descr->version = 1;
693 p_descr->revisionLevel = 0;
694 p_descr->vendor = 'appl';
695 p_descr->width = p_vout->output.i_width;
696 p_descr->height = p_vout->output.i_height;
697 p_descr->hRes = Long2Fix(72);
698 p_descr->vRes = Long2Fix(72);
699 p_descr->spatialQuality = codecLosslessQuality;
700 p_descr->frameCount = 1;
701 p_descr->clutID = -1;
702 p_descr->dataSize = 0;
705 HUnlock( (Handle)p_vout->p_sys->h_img_descr );
707 if( ( err = DecompressSequenceBeginS(
708 &p_vout->p_sys->i_seq,
709 p_vout->p_sys->h_img_descr,
711 p_vout->p_sys->p_qdport,
713 p_vout->p_sys->p_matrix,
715 codecFlagUseImageBuffer,
716 codecLosslessQuality,
717 p_vout->p_sys->img_dc ) ) )
719 msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
726 /*****************************************************************************
727 * QTDestroySequence: destroy sequence
728 *****************************************************************************/
729 static void QTDestroySequence( vout_thread_t *p_vout )
731 CDSequenceEnd( p_vout->p_sys->i_seq );
734 /*****************************************************************************
735 * QTNewPicture: allocate a picture
736 *****************************************************************************
737 * Returns 0 on success, 1 otherwise
738 *****************************************************************************/
739 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
741 int i_width = p_vout->output.i_width;
742 int i_height = p_vout->output.i_height;
744 /* We know the chroma, allocate a buffer which will be used
745 * directly by the decoder */
746 p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
748 if( p_pic->p_sys == NULL )
753 switch( p_vout->output.i_chroma )
755 case VLC_FOURCC('I','4','2','0'):
757 p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
758 p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
760 /* Allocate the memory buffer */
761 p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
762 16, i_width * i_height * 3 / 2 );
765 p_pic->Y_PIXELS = p_pic->p_data;
766 p_pic->p[Y_PLANE].i_lines = i_height;
767 p_pic->p[Y_PLANE].i_pitch = i_width;
768 p_pic->p[Y_PLANE].i_pixel_pitch = 1;
769 p_pic->p[Y_PLANE].i_visible_pitch = i_width;
772 p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
773 p_pic->p[U_PLANE].i_lines = i_height / 2;
774 p_pic->p[U_PLANE].i_pitch = i_width / 2;
775 p_pic->p[U_PLANE].i_pixel_pitch = 1;
776 p_pic->p[U_PLANE].i_visible_pitch = i_width / 2;
779 p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
780 p_pic->p[V_PLANE].i_lines = i_height / 2;
781 p_pic->p[V_PLANE].i_pitch = i_width / 2;
782 p_pic->p[V_PLANE].i_pixel_pitch = 1;
783 p_pic->p[V_PLANE].i_visible_pitch = i_width / 2;
785 /* We allocated 3 planes */
788 #define P p_pic->p_sys->pixmap_i420
789 P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
790 - p_pic->p_sys->p_info;
791 P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
792 - p_pic->p_sys->p_info;
793 P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
794 - p_pic->p_sys->p_info;
796 P.componentInfoY.rowBytes = i_width;
797 P.componentInfoCb.rowBytes = i_width / 2;
798 P.componentInfoCr.rowBytes = i_width / 2;
804 /* Unknown chroma, tell the guy to get lost */
805 free( p_pic->p_sys );
806 msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
807 p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
815 /*****************************************************************************
816 * QTFreePicture: destroy a picture allocated with QTNewPicture
817 *****************************************************************************/
818 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
820 switch( p_vout->output.i_chroma )
822 case VLC_FOURCC('I','4','2','0'):
823 free( p_pic->p_data_orig );
827 free( p_pic->p_sys );
830 /*****************************************************************************
831 * VLCWindow implementation
832 *****************************************************************************/
833 @implementation VLCWindow
835 - (void)setVout:(vout_thread_t *)_p_vout
840 - (vout_thread_t *)getVout
845 - (void)scaleWindowWithFactor: (float)factor
848 int i_corrected_height, i_corrected_width;
850 NSPoint topleftscreen;
852 if ( !p_vout->b_fullscreen )
855 topleftbase.y = [self frame].size.height;
856 topleftscreen = [self convertBaseToScreen: topleftbase];
858 if( p_vout->output.i_height * p_vout->output.i_aspect >
859 p_vout->output.i_width * VOUT_ASPECT_FACTOR )
861 i_corrected_width = p_vout->output.i_height * p_vout->output.i_aspect /
863 newsize.width = (int) ( i_corrected_width * factor );
864 newsize.height = (int) ( p_vout->render.i_height * factor );
868 i_corrected_height = p_vout->output.i_width * VOUT_ASPECT_FACTOR /
869 p_vout->output.i_aspect;
870 newsize.width = (int) ( p_vout->render.i_width * factor );
871 newsize.height = (int) ( i_corrected_height * factor );
874 [self setContentSize: newsize];
876 [self setFrameTopLeftPoint: topleftscreen];
877 p_vout->i_changes |= VOUT_SIZE_CHANGE;
881 - (void)toggleFloatOnTop
883 if( config_GetInt( p_vout, "video-on-top" ) )
885 config_PutInt( p_vout, "video-on-top", 0 );
886 [p_vout->p_sys->o_window setLevel: NSNormalWindowLevel];
890 config_PutInt( p_vout, "video-on-top", 1 );
891 [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
895 - (void)toggleFullscreen
897 config_PutInt(p_vout, "fullscreen", !p_vout->b_fullscreen);
898 p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
903 return( p_vout->b_fullscreen );
906 - (BOOL)canBecomeKeyWindow
911 - (void)keyDown:(NSEvent *)o_event
915 unsigned int i_pressed_modifiers = 0;
918 i_pressed_modifiers = [o_event modifierFlags];
920 if( i_pressed_modifiers & NSShiftKeyMask )
921 val.i_int |= KEY_MODIFIER_SHIFT;
922 if( i_pressed_modifiers & NSControlKeyMask )
923 val.i_int |= KEY_MODIFIER_CTRL;
924 if( i_pressed_modifiers & NSAlternateKeyMask )
925 val.i_int |= KEY_MODIFIER_ALT;
926 if( i_pressed_modifiers & NSCommandKeyMask )
927 val.i_int |= KEY_MODIFIER_COMMAND;
929 key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
933 /* Escape should always get you out of fullscreen */
934 if( key == (unichar) 0x1b )
936 if( [self isFullscreen] )
938 [self toggleFullscreen];
941 else if ( key == ' ' )
943 playlist_t *p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
945 if ( p_playlist != NULL )
947 playlist_Pause( p_playlist );
948 vlc_object_release( p_playlist);
953 val.i_int |= CocoaKeyToVLC( key );
954 var_Set( p_vout->p_vlc, "key-pressed", val );
959 [super keyDown: o_event];
965 NSMutableString * o_title;
966 playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
969 if( p_playlist == NULL )
974 vlc_mutex_lock( &p_playlist->object_lock );
975 o_title = [NSMutableString stringWithUTF8String:
976 p_playlist->pp_items[p_playlist->i_index]->psz_uri];
977 vlc_mutex_unlock( &p_playlist->object_lock );
979 vlc_object_release( p_playlist );
983 NSRange prefix_range = [o_title rangeOfString: @"file:"];
984 if( prefix_range.location != NSNotFound )
986 [o_title deleteCharactersInRange: prefix_range];
989 [self setTitleWithRepresentedFilename: o_title];
994 [NSString stringWithCString: VOUT_TITLE " (QuickTime)"]];
998 /* This is actually the same as VLCControls::stop. */
999 - (BOOL)windowShouldClose:(id)sender
1001 playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1003 if( p_playlist == NULL )
1008 playlist_Stop( p_playlist );
1009 vlc_object_release( p_playlist );
1011 /* The window will be closed by the intf later. */
1017 /*****************************************************************************
1018 * VLCQTView implementation
1019 *****************************************************************************/
1020 @implementation VLCQTView
1022 - (void)drawRect:(NSRect)rect
1024 vout_thread_t * p_vout;
1025 id o_window = [self window];
1026 p_vout = (vout_thread_t *)[o_window getVout];
1028 [[NSColor blackColor] set];
1030 [super drawRect: rect];
1032 p_vout->i_changes |= VOUT_SIZE_CHANGE;
1035 - (BOOL)acceptsFirstResponder
1040 - (BOOL)becomeFirstResponder
1042 vout_thread_t * p_vout;
1043 id o_window = [self window];
1044 p_vout = (vout_thread_t *)[o_window getVout];
1046 [o_window setAcceptsMouseMovedEvents: YES];
1050 - (BOOL)resignFirstResponder
1052 vout_thread_t * p_vout;
1053 id o_window = [self window];
1054 p_vout = (vout_thread_t *)[o_window getVout];
1056 [o_window setAcceptsMouseMovedEvents: NO];
1057 VLCHideMouse( p_vout, NO );
1061 - (void)mouseDown:(NSEvent *)o_event
1063 vout_thread_t * p_vout;
1064 id o_window = [self window];
1065 p_vout = (vout_thread_t *)[o_window getVout];
1068 switch( [o_event type] )
1070 case NSLeftMouseDown:
1072 var_Get( p_vout, "mouse-button-down", &val );
1074 var_Set( p_vout, "mouse-button-down", val );
1079 [super mouseDown: o_event];
1084 - (void)otherMouseDown:(NSEvent *)o_event
1086 /* This is not the the wheel button. you need to poll the
1087 * mouseWheel event for that. other is a third, forth or fifth button */
1088 vout_thread_t * p_vout;
1089 id o_window = [self window];
1090 p_vout = (vout_thread_t *)[o_window getVout];
1093 switch( [o_event type] )
1095 case NSOtherMouseDown:
1097 var_Get( p_vout, "mouse-button-down", &val );
1099 var_Set( p_vout, "mouse-button-down", val );
1104 [super mouseDown: o_event];
1109 - (void)rightMouseDown:(NSEvent *)o_event
1111 vout_thread_t * p_vout;
1112 id o_window = [self window];
1113 p_vout = (vout_thread_t *)[o_window getVout];
1116 switch( [o_event type] )
1118 case NSRightMouseDown:
1120 var_Get( p_vout, "mouse-button-down", &val );
1122 var_Set( p_vout, "mouse-button-down", val );
1127 [super mouseDown: o_event];
1132 - (void)mouseUp:(NSEvent *)o_event
1134 vout_thread_t * p_vout;
1135 id o_window = [self window];
1136 p_vout = (vout_thread_t *)[o_window getVout];
1139 switch( [o_event type] )
1144 b_val.b_bool = VLC_TRUE;
1145 var_Set( p_vout, "mouse-clicked", b_val );
1147 var_Get( p_vout, "mouse-button-down", &val );
1149 var_Set( p_vout, "mouse-button-down", val );
1154 [super mouseUp: o_event];
1159 - (void)otherMouseUp:(NSEvent *)o_event
1161 vout_thread_t * p_vout;
1162 id o_window = [self window];
1163 p_vout = (vout_thread_t *)[o_window getVout];
1166 switch( [o_event type] )
1168 case NSOtherMouseUp:
1170 var_Get( p_vout, "mouse-button-down", &val );
1172 var_Set( p_vout, "mouse-button-down", val );
1177 [super mouseUp: o_event];
1182 - (void)rightMouseUp:(NSEvent *)o_event
1184 vout_thread_t * p_vout;
1185 id o_window = [self window];
1186 p_vout = (vout_thread_t *)[o_window getVout];
1189 switch( [o_event type] )
1191 case NSRightMouseUp:
1193 var_Get( p_vout, "mouse-button-down", &val );
1195 var_Set( p_vout, "mouse-button-down", val );
1200 [super mouseUp: o_event];
1205 - (void)mouseDragged:(NSEvent *)o_event
1207 [self mouseMoved:o_event];
1210 - (void)otherMouseDragged:(NSEvent *)o_event
1212 [self mouseMoved:o_event];
1215 - (void)rightMouseDragged:(NSEvent *)o_event
1217 [self mouseMoved:o_event];
1220 - (void)mouseMoved:(NSEvent *)o_event
1226 vout_thread_t * p_vout;
1227 id o_window = [self window];
1228 p_vout = (vout_thread_t *)[o_window getVout];
1230 s_rect = [self bounds];
1231 ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
1232 b_inside = [self mouse: ml inRect: s_rect];
1237 int i_width, i_height, i_x, i_y;
1239 vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
1240 (unsigned int)s_rect.size.height,
1241 &i_x, &i_y, &i_width, &i_height );
1243 val.i_int = ( ((int)ml.x) - i_x ) *
1244 p_vout->render.i_width / i_width;
1245 var_Set( p_vout, "mouse-x", val );
1247 val.i_int = ( ((int)ml.y) - i_y ) *
1248 p_vout->render.i_height / i_height;
1249 var_Set( p_vout, "mouse-y", val );
1251 val.b_bool = VLC_TRUE;
1252 var_Set( p_vout, "mouse-moved", val );
1253 p_vout->p_sys->i_time_mouse_last_moved = mdate();
1254 p_vout->p_sys->b_mouse_moved = YES;
1257 [super mouseMoved: o_event];
1262 /*****************************************************************************
1263 * VLCGLView implementation
1264 *****************************************************************************/
1265 @implementation VLCGLView
1268 - (id) initWithFrame: (NSRect) frame
1270 NSOpenGLPixelFormatAttribute attribs[] =
1272 NSOpenGLPFAAccelerated,
1273 NSOpenGLPFANoRecovery,
1274 NSOpenGLPFADoubleBuffer,
1279 NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
1280 initWithAttributes: attribs];
1284 fprintf( stderr, "Cannot create NSOpenGLPixelFormat\n" );
1288 self = [super initWithFrame:frame pixelFormat: fmt];
1290 [[self openGLContext] makeCurrentContext];
1291 [[self openGLContext] update];
1293 glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
1294 glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
1295 glClearColor( 0.0, 0.0, 0.0, 0.0 );
1298 i_textures_loaded = 0;
1305 [[self openGLContext] update];
1306 NSRect bounds = [self bounds];
1307 glViewport( 0, 0, (GLint) bounds.size.width,
1308 (GLint) bounds.size.height );
1311 - (void)drawRect:(NSRect)rect
1313 vout_thread_t * p_vout;
1314 id o_window = [self window];
1315 p_vout = (vout_thread_t *)[o_window getVout];
1317 /* Make this current context */
1318 [[self openGLContext] makeCurrentContext];
1320 /* http://developer.apple.com/documentation/GraphicsImaging/
1321 Conceptual/OpenGL/chap5/chapter_5_section_44.html */
1322 long params[] = { 1 };
1323 CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
1326 /* Black background */
1327 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1334 if( !i_textures_loaded )
1336 glGenTextures( 1, &i_texture );
1337 glEnable( GL_TEXTURE_RECTANGLE_EXT );
1338 glBindTexture(GL_TEXTURE_RECTANGLE_EXT, i_texture);
1339 glTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
1340 p_vout->output.i_width, p_vout->output.i_height, 0,
1341 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1342 PP_OUTPUTPICTURE[i_index]->p_data );
1344 /* Turn on AGP transferts */
1345 glTexParameterf( GL_TEXTURE_RECTANGLE_EXT,
1346 GL_TEXTURE_PRIORITY, 0.0 );
1348 /* Use AGP texturing */
1349 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1350 GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE );
1352 /* Tell the driver not to make a copy of the texture but to use
1354 glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE );
1356 /* Linear interpolation */
1357 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1358 GL_TEXTURE_MIN_FILTER, GL_LINEAR );
1359 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1360 GL_TEXTURE_MAG_FILTER, GL_LINEAR );
1362 /* I have no idea what this exactly does, but it seems to be
1363 necessary for scaling */
1364 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1365 GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE );
1366 glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1367 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1368 glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
1370 i_textures_loaded = 1;
1374 /* glTexSubImage2D is supposed to be faster than glTexImage2D,
1375 so we use it starting from the second frame */
1376 glBindTexture(GL_TEXTURE_RECTANGLE_EXT, i_texture);
1377 glTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
1378 p_vout->output.i_width, p_vout->output.i_height,
1379 GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1380 PP_OUTPUTPICTURE[i_index]->p_data );
1383 /* Draw a quad with our texture on it. Quad size is set in order
1384 to preserve the aspect ratio */
1385 NSRect bounds = [self bounds];
1387 if( bounds.size.height * p_vout->output.i_aspect <
1388 bounds.size.width * VOUT_ASPECT_FACTOR )
1390 f_x = bounds.size.height * p_vout->output.i_aspect /
1391 VOUT_ASPECT_FACTOR / bounds.size.width;
1397 f_y = bounds.size.width * VOUT_ASPECT_FACTOR /
1398 p_vout->output.i_aspect / bounds.size.height;
1401 glBegin( GL_QUADS );
1403 glTexCoord2f( 0.0f, 0.0f );
1404 glVertex2f( - f_x, f_y );
1406 glTexCoord2f( 0.0f, (float) p_vout->output.i_height );
1407 glVertex2f( - f_x, - f_y );
1409 glTexCoord2f( (float) p_vout->output.i_width,
1410 (float) p_vout->output.i_height );
1411 glVertex2f( f_x, - f_y );
1413 glTexCoord2f( (float) p_vout->output.i_width, 0.0f );
1414 glVertex2f( f_x, f_y );
1417 /* Wait for the job to be done */
1418 [[self openGLContext] flushBuffer];
1423 /*****************************************************************************
1424 * VLCVout implementation
1425 *****************************************************************************/
1426 @implementation VLCVout
1428 - (void)createWindow:(NSValue *)o_value
1432 NSScreen * o_screen;
1433 vout_thread_t * p_vout;
1434 vlc_bool_t b_main_screen;
1436 p_vout = (vout_thread_t *)[o_value pointerValue];
1438 p_vout->p_sys->o_window = [VLCWindow alloc];
1439 [p_vout->p_sys->o_window setVout: p_vout];
1440 [p_vout->p_sys->o_window setReleasedWhenClosed: YES];
1442 if( var_Get( p_vout, "video-device", &val ) < 0 )
1444 o_screen = [NSScreen mainScreen];
1449 NSArray *o_screens = [NSScreen screens];
1450 unsigned int i_index = val.i_int;
1452 if( [o_screens count] < i_index )
1454 o_screen = [NSScreen mainScreen];
1460 o_screen = [o_screens objectAtIndex: i_index];
1461 config_PutInt( p_vout, "macosx-vdev", i_index );
1462 b_main_screen = (i_index == 0);
1466 if( p_vout->p_sys->i_opengl )
1468 /* XXX Fix fullscreen mode */
1469 p_vout->b_fullscreen = 0;
1472 if( p_vout->b_fullscreen )
1474 NSRect screen_rect = [o_screen frame];
1475 screen_rect.origin.x = screen_rect.origin.y = 0;
1477 if ( b_main_screen && p_vout->p_sys->p_fullscreen_state == NULL )
1478 BeginFullScreen( &p_vout->p_sys->p_fullscreen_state, NULL, 0, 0,
1479 NULL, NULL, fullScreenAllowEvents );
1481 [p_vout->p_sys->o_window
1482 initWithContentRect: screen_rect
1483 styleMask: NSBorderlessWindowMask
1484 backing: NSBackingStoreBuffered
1485 defer: NO screen: o_screen];
1487 //[p_vout->p_sys->o_window setLevel: NSPopUpMenuWindowLevel - 1];
1488 p_vout->p_sys->b_mouse_moved = YES;
1489 p_vout->p_sys->i_time_mouse_last_moved = mdate();
1493 unsigned int i_stylemask = NSTitledWindowMask |
1494 NSMiniaturizableWindowMask |
1495 NSClosableWindowMask |
1496 NSResizableWindowMask;
1498 if( !p_vout->p_sys->i_opengl )
1500 if ( p_vout->p_sys->p_fullscreen_state != NULL )
1501 EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
1502 p_vout->p_sys->p_fullscreen_state = NULL;
1505 [p_vout->p_sys->o_window
1506 initWithContentRect: p_vout->p_sys->s_rect
1507 styleMask: i_stylemask
1508 backing: NSBackingStoreBuffered
1509 defer: NO screen: o_screen];
1511 [p_vout->p_sys->o_window setAlphaValue: config_GetFloat( p_vout, "macosx-opaqueness" )];
1513 if( config_GetInt( p_vout, "video-on-top" ) )
1515 [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
1518 if( !p_vout->p_sys->b_pos_saved )
1520 [p_vout->p_sys->o_window center];
1524 if( !p_vout->p_sys->i_opengl )
1526 o_view = [[VLCQTView alloc] init];
1527 /* FIXME: [o_view setMenu:] */
1528 [p_vout->p_sys->o_window setContentView: o_view];
1529 [o_view autorelease];
1532 p_vout->p_sys->p_qdport = [o_view qdPort];
1533 [o_view unlockFocus];
1537 #define o_glview p_vout->p_sys->o_glview
1538 o_glview = [[VLCGLView alloc] initWithFrame: p_vout->p_sys->s_rect];
1539 [p_vout->p_sys->o_window setContentView: o_glview];
1540 [o_glview autorelease];
1544 [p_vout->p_sys->o_window updateTitle];
1545 [p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
1549 - (void)destroyWindow:(NSValue *)o_value
1551 vout_thread_t * p_vout;
1553 p_vout = (vout_thread_t *)[o_value pointerValue];
1555 if( !p_vout->b_fullscreen )
1559 s_rect = [[p_vout->p_sys->o_window contentView] frame];
1560 p_vout->p_sys->s_rect.size = s_rect.size;
1562 s_rect = [p_vout->p_sys->o_window frame];
1563 p_vout->p_sys->s_rect.origin = s_rect.origin;
1565 p_vout->p_sys->b_pos_saved = YES;
1568 p_vout->p_sys->p_qdport = nil;
1569 [p_vout->p_sys->o_window close];
1570 p_vout->p_sys->o_window = nil;