]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
* Mac OS X: intercept and respond to user-configured VLC hotkeys, rather
[vlc] / modules / gui / macosx / vout.m
1 /*****************************************************************************
2  * vout.m: MacOS X video output module
3  *****************************************************************************
4  * Copyright (C) 2001-2003 VideoLAN
5  * $Id$
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Florian G. Pflug <fgp@phlo.org>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *          Derk-Jan Hartman <thedj@users.sourceforge.net>
11  *          Eric Petit <titer@m0k.org>
12  *
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.
17  * 
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.
22  *
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  *****************************************************************************/
27
28 /*****************************************************************************
29  * Preamble
30  *****************************************************************************/
31 #include <errno.h>                                                 /* ENOMEM */
32 #include <stdlib.h>                                                /* free() */
33 #include <string.h>                                            /* strerror() */
34
35 #include <QuickTime/QuickTime.h>
36
37 #include <OpenGL/OpenGL.h>
38 #include <OpenGL/gl.h>
39 #include <OpenGL/glext.h>
40
41 #include <vlc_keys.h>
42
43 #include "intf.h"
44 #include "vout.h"
45
46 #define QT_MAX_DIRECTBUFFERS 10
47 #define VL_MAX_DISPLAYS 16
48
49 #define OPENGL_EFFECT_NONE             1
50 #define OPENGL_EFFECT_CUBE             2
51 #define OPENGL_EFFECT_TRANSPARENT_CUBE 4
52
53 struct picture_sys_t
54 {
55     void *p_info;
56     unsigned int i_size;
57
58     /* When using I420 output */
59     PlanarPixmapInfoYUV420 pixmap_i420;
60 };
61
62 /*****************************************************************************
63  * Local prototypes
64  *****************************************************************************/
65
66 static int  vout_Init      ( vout_thread_t * );
67 static void vout_End       ( vout_thread_t * );
68 static int  vout_Manage    ( vout_thread_t * );
69 static void vout_Display   ( vout_thread_t *, picture_t * );
70
71 static int  CoSendRequest      ( vout_thread_t *, SEL );
72 static int  CoCreateWindow     ( vout_thread_t * );
73 static int  CoDestroyWindow    ( vout_thread_t * );
74 static int  CoToggleFullscreen ( vout_thread_t * );
75
76 static void VLCHideMouse       ( vout_thread_t *, BOOL );
77
78 static void QTScaleMatrix      ( vout_thread_t * );
79 static int  QTCreateSequence   ( vout_thread_t * );
80 static void QTDestroySequence  ( vout_thread_t * );
81 static int  QTNewPicture       ( vout_thread_t *, picture_t * );
82 static void QTFreePicture      ( vout_thread_t *, picture_t * );
83
84 /*****************************************************************************
85  * OpenVideo: allocates MacOS X video thread output method
86  *****************************************************************************
87  * This function allocates and initializes a MacOS X vout method.
88  *****************************************************************************/
89 int E_(OpenVideo) ( vlc_object_t *p_this )
90 {   
91     vout_thread_t * p_vout = (vout_thread_t *)p_this;
92     OSErr err;
93     int i_timeout;
94     char *psz_vout_type;
95
96     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
97     if( p_vout->p_sys == NULL )
98     {
99         msg_Err( p_vout, "out of memory" );
100         return( 1 );
101     }
102
103     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
104
105     /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
106     for( i_timeout = 20 ; i_timeout-- ; )
107     {
108         if( NSApp == NULL )
109         {
110             msleep( INTF_IDLE_SLEEP );
111         }
112     }
113
114     if( NSApp == NULL )
115     {
116         /* no MacOS X intf, unable to communicate with MT */
117         msg_Err( p_vout, "no MacOS X interface present" );
118         free( p_vout->p_sys );
119         return( 1 );
120     }
121
122     if( [NSApp respondsToSelector: @selector(getIntf)] )
123     {
124         intf_thread_t * p_intf;
125
126         for( i_timeout = 10 ; i_timeout-- ; )
127         {
128             if( ( p_intf = [NSApp getIntf] ) == NULL )
129             {
130                 msleep( INTF_IDLE_SLEEP );
131             }
132         }
133
134         if( p_intf == NULL )
135         {
136             msg_Err( p_vout, "MacOS X intf has getIntf, but is NULL" );
137             free( p_vout->p_sys );
138             return( 1 );
139         }
140     }
141
142     p_vout->p_sys->b_mouse_moved = VLC_TRUE;
143     p_vout->p_sys->i_time_mouse_last_moved = mdate();
144
145     /* set window size */
146     p_vout->p_sys->s_rect.size.width = p_vout->i_window_width;
147     p_vout->p_sys->s_rect.size.height = p_vout->i_window_height;
148
149     /* Check if we should use QuickTime or OpenGL */
150     psz_vout_type = config_GetPsz( p_vout, "macosx-vout" );
151
152     if( !strncmp( psz_vout_type, "auto", 4 ) )
153     {
154         p_vout->p_sys->i_opengl = CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay );
155     }
156     else if( !strncmp( psz_vout_type, "opengl", 6 ) )
157     {
158         p_vout->p_sys->i_opengl = VLC_TRUE;
159     }
160     else
161     {
162         p_vout->p_sys->i_opengl = VLC_FALSE;
163     }
164     free( psz_vout_type );
165     
166     if( !p_vout->p_sys->i_opengl )
167     {
168         /* Initialize QuickTime */
169         p_vout->p_sys->h_img_descr = 
170             (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
171         p_vout->p_sys->p_matrix =
172             (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
173         p_vout->p_sys->p_fullscreen_state = NULL;
174
175         if( ( err = EnterMovies() ) != noErr )
176         {
177             msg_Err( p_vout, "EnterMovies failed: %d", err );
178             free( p_vout->p_sys->p_matrix );
179             DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
180             free( p_vout->p_sys );
181             return( 1 );
182         } 
183
184         /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
185         vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
186
187         err = FindCodec( kYUV420CodecType, bestSpeedCodec,
188                             nil, &p_vout->p_sys->img_dc );
189         
190         vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
191         if( err == noErr && p_vout->p_sys->img_dc != 0 )
192         {
193             p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
194             p_vout->p_sys->i_codec = kYUV420CodecType;
195         }
196         else
197         {
198             msg_Err( p_vout, "failed to find an appropriate codec" );
199         }
200
201         if( p_vout->p_sys->img_dc == 0 )
202         {
203             free( p_vout->p_sys->p_matrix );
204             DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
205             free( p_vout->p_sys );
206             return VLC_EGENERIC;        
207         }
208         msg_Dbg( p_vout, "using Quartz mode" );
209     }
210     else
211     {
212         msg_Dbg( p_vout, "using OpenGL mode" );
213     }
214
215     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
216     NSArray * o_screens = [NSScreen screens];
217     if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
218     {
219         int i = 1;
220         vlc_value_t val, text;
221         NSScreen * o_screen;
222
223         int i_option = config_GetInt( p_vout, "macosx-vdev" );
224
225         var_Create( p_vout, "video-device", VLC_VAR_INTEGER |
226                                             VLC_VAR_HASCHOICE ); 
227         text.psz_string = _("Video device");
228         var_Change( p_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
229         
230         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
231
232         while( (o_screen = [o_enumerator nextObject]) != NULL )
233         {
234             char psz_temp[255];
235             NSRect s_rect = [o_screen frame];
236
237             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1, 
238                       "%s %d (%dx%d)", _("Screen"), i,
239                       (int)s_rect.size.width, (int)s_rect.size.height ); 
240
241             text.psz_string = psz_temp;
242             val.i_int = i;
243             var_Change( p_vout, "video-device",
244                         VLC_VAR_ADDCHOICE, &val, &text );
245
246             if( ( i - 1 ) == i_option )
247             {
248                 var_Set( p_vout, "video-device", val );
249             }
250             i++;
251         }
252
253         var_AddCallback( p_vout, "video-device", vout_VarCallback,
254                          NULL );
255
256         val.b_bool = VLC_TRUE;
257         var_Set( p_vout, "intf-change", val );
258     }
259     [o_pool release];
260
261     if( CoCreateWindow( p_vout ) )
262     {
263         msg_Err( p_vout, "unable to create window" );
264         if( !p_vout->p_sys->i_opengl )
265         {
266             free( p_vout->p_sys->p_matrix );
267             DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
268         }
269         free( p_vout->p_sys ); 
270         return( 1 );
271     }
272
273     p_vout->pf_init = vout_Init;
274     p_vout->pf_end = vout_End;
275     p_vout->pf_manage = vout_Manage;
276     p_vout->pf_render = NULL;
277     p_vout->pf_display = vout_Display;
278
279     return( 0 );
280 }
281
282 /*****************************************************************************
283  * vout_Init: initialize video thread output method
284  *****************************************************************************/
285 static int vout_Init( vout_thread_t *p_vout )
286 {
287     int i_index;
288     picture_t *p_pic;
289
290     I_OUTPUTPICTURES = 0;
291
292     /* Initialize the output structure; we already found a codec,
293      * and the corresponding chroma we will be using. Since we can
294      * arbitrary scale, stick to the coordinates and aspect. */
295     p_vout->output.i_width  = p_vout->render.i_width;
296     p_vout->output.i_height = p_vout->render.i_height;
297     p_vout->output.i_aspect = p_vout->render.i_aspect;
298
299     if( !p_vout->p_sys->i_opengl )
300     {
301         SetPort( p_vout->p_sys->p_qdport );
302         QTScaleMatrix( p_vout );
303
304         if( QTCreateSequence( p_vout ) )
305         {
306             msg_Err( p_vout, "unable to create sequence" );
307             return( 1 );
308         }
309     }
310     else
311     {
312         p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
313         p_vout->output.i_rmask  = 0xFF0000;
314         p_vout->output.i_gmask  = 0x00FF00;
315         p_vout->output.i_bmask  = 0x0000FF;
316     }
317
318     /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
319     while( I_OUTPUTPICTURES <
320            p_vout->p_sys->i_opengl ? 1 : QT_MAX_DIRECTBUFFERS )
321     {
322         p_pic = NULL;
323
324         /* Find an empty picture slot */
325         for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
326         {
327             if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
328             {
329                 p_pic = p_vout->p_picture + i_index;
330                 break;
331             }
332         }
333
334         /* Allocate the picture */
335         if( p_pic == NULL )
336         {
337             break;
338         }
339
340         if( !p_vout->p_sys->i_opengl )
341         {
342             if( QTNewPicture( p_vout, p_pic ) )
343             {
344                 break;
345             }
346         }
347         else
348         {
349             /* Nothing special to do, we just need a basic allocated
350                picture_t */
351             vout_AllocatePicture( VLC_OBJECT( p_vout ), p_pic,
352                     p_vout->output.i_chroma, p_vout->output.i_width,
353                     p_vout->output.i_height, p_vout->output.i_aspect );
354         }
355
356         p_pic->i_status = DESTROYED_PICTURE;
357         p_pic->i_type   = DIRECT_PICTURE;
358
359         PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
360
361         I_OUTPUTPICTURES++;
362     }
363
364     if( p_vout->p_sys->i_opengl )
365     {
366         [p_vout->p_sys->o_glview lockFocus];
367         [p_vout->p_sys->o_glview initTextures];
368         [p_vout->p_sys->o_glview reshape];
369         [p_vout->p_sys->o_glview unlockFocus];
370     }
371
372     return( 0 );
373 }
374
375 /*****************************************************************************
376  * vout_End: terminate video thread output method
377  *****************************************************************************/
378 static void vout_End( vout_thread_t *p_vout )
379 {
380     int i_index;
381
382     if( !p_vout->p_sys->i_opengl )
383     {
384         QTDestroySequence( p_vout );
385     }
386     else
387     {
388         [p_vout->p_sys->o_glview cleanUp];
389     }
390
391     /* Free the direct buffers we allocated */
392     for( i_index = I_OUTPUTPICTURES; i_index; )
393     {
394         i_index--;
395         if( !p_vout->p_sys->i_opengl )
396         {
397             QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
398         }
399         else
400         {
401             free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
402         }
403     }
404 }
405
406 /*****************************************************************************
407  * CloseVideo: destroy video thread output method
408  *****************************************************************************/
409 void E_(CloseVideo) ( vlc_object_t *p_this )
410 {       
411     vout_thread_t * p_vout = (vout_thread_t *)p_this;     
412
413     if( CoDestroyWindow( p_vout ) )
414     {
415         msg_Err( p_vout, "unable to destroy window" );
416     }
417
418     if( !p_vout->p_sys->i_opengl )
419     {
420         if ( p_vout->p_sys->p_fullscreen_state != NULL )
421             EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
422
423         ExitMovies();
424
425         free( p_vout->p_sys->p_matrix );
426         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
427     }
428
429     free( p_vout->p_sys );
430 }
431
432 /*****************************************************************************
433  * vout_Manage: handle events
434  *****************************************************************************
435  * This function should be called regularly by video output thread. It manages
436  * console events. It returns a non null value on error.
437  *****************************************************************************/
438 static int vout_Manage( vout_thread_t *p_vout )
439 {
440     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
441     {
442         if( CoToggleFullscreen( p_vout ) )  
443         {
444             return( 1 );
445         }
446
447         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
448     }
449
450     if( p_vout->i_changes & VOUT_SIZE_CHANGE ) 
451     {
452         if( !p_vout->p_sys->i_opengl )
453         {
454             QTScaleMatrix( p_vout );
455             SetDSequenceMatrix( p_vout->p_sys->i_seq, 
456                                 p_vout->p_sys->p_matrix );
457         }
458  
459         p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
460     }
461
462     /* hide/show mouse cursor 
463      * this code looks unnecessarily complicated, but is necessary like this.
464      * it has to deal with multiple monitors and therefore checks a lot */
465     if( !p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
466     {
467         if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 3000000 )
468         {
469             VLCHideMouse( p_vout, YES );
470         }
471     }
472     else if ( p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
473     {
474         VLCHideMouse( p_vout, NO );
475     }
476     
477     /* disable screen saver */
478     UpdateSystemActivity( UsrActivity );
479     
480     return( 0 );
481 }
482
483 /*****************************************************************************
484  * vout_Display: displays previously rendered output
485  *****************************************************************************
486  * This function sends the currently rendered image to the display.
487  *****************************************************************************/
488 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
489 {
490     if( !p_vout->p_sys->i_opengl )
491     {
492         OSErr err;
493         CodecFlags flags;
494
495         if( ( err = DecompressSequenceFrameS( 
496                         p_vout->p_sys->i_seq,
497                         p_pic->p_sys->p_info,
498                         p_pic->p_sys->i_size,                    
499                         codecFlagUseImageBuffer, &flags, nil ) != noErr ) )
500         {
501             msg_Warn( p_vout, "DecompressSequenceFrameS failed: %d", err );
502         }
503         else
504         {
505             QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
506         }
507     }
508     else
509     {
510         if( [p_vout->p_sys->o_glview lockFocusIfCanDraw] )
511         {
512             /* Texture gotta be reload before the buffer is filled
513                (thanks to gcc from arstechnica forums) */
514             [p_vout->p_sys->o_glview drawRect:
515                 [p_vout->p_sys->o_glview bounds]];
516             [p_vout->p_sys->o_glview reloadTexture];
517             [p_vout->p_sys->o_glview unlockFocus];
518         }
519     }
520 }
521
522 /*****************************************************************************
523  * CoSendRequest: send request to interface thread
524  *****************************************************************************
525  * Returns 0 on success, 1 otherwise
526  *****************************************************************************/
527 static int CoSendRequest( vout_thread_t *p_vout, SEL sel )
528 {
529     int i_ret = 0;
530     vlc_value_t val;
531     intf_thread_t * p_intf;
532
533     VLCVout * o_vlv = [[VLCVout alloc] init];
534
535     if( ( i_ret = ExecuteOnMainThread( o_vlv, sel, (void *)p_vout ) ) )
536     {
537         msg_Err( p_vout, "SendRequest: no way to communicate with mt" );
538     }
539
540     [o_vlv release];
541
542     /*This makes this function dependant of the presence of a macosx 
543     interface. We do not check if this interface exists, since it has 
544     already been done before.*/
545
546     p_intf = [NSApp getIntf];
547
548     val.b_bool = VLC_TRUE;
549     var_Create(p_intf,"intf-change",VLC_VAR_BOOL);
550     var_Set(p_intf, "intf-change",val);
551
552     return( i_ret );
553 }
554
555 /*****************************************************************************
556  * CoCreateWindow: create new window 
557  *****************************************************************************
558  * Returns 0 on success, 1 otherwise
559  *****************************************************************************/
560 static int CoCreateWindow( vout_thread_t *p_vout )
561 {
562     if( CoSendRequest( p_vout, @selector(createWindow:) ) )
563     {
564         msg_Err( p_vout, "CoSendRequest (createWindow) failed" );
565         return( 1 );
566     }
567     
568     return( 0 );
569 }
570
571 /*****************************************************************************
572  * CoDestroyWindow: destroy window 
573  *****************************************************************************
574  * Returns 0 on success, 1 otherwise
575  *****************************************************************************/
576 static int CoDestroyWindow( vout_thread_t *p_vout )
577 {
578
579     VLCHideMouse( p_vout, NO );
580
581     if( CoSendRequest( p_vout, @selector(destroyWindow:) ) )
582     {
583         msg_Err( p_vout, "CoSendRequest (destroyWindow) failed" );
584         return( 1 );
585     }
586
587     return( 0 );
588 }
589
590 /*****************************************************************************
591  * CoToggleFullscreen: toggle fullscreen 
592  *****************************************************************************
593  * Returns 0 on success, 1 otherwise
594  *****************************************************************************/
595 static int CoToggleFullscreen( vout_thread_t *p_vout )
596 {
597
598     vlc_value_t val;
599     intf_thread_t * p_intf;
600
601     if( p_vout->p_sys->i_opengl )
602     {
603         p_vout->b_fullscreen = !p_vout->b_fullscreen;
604         if( p_vout->b_fullscreen )
605         {
606             [p_vout->p_sys->o_glview goFullScreen];
607         }
608         else
609         {
610             [p_vout->p_sys->o_glview exitFullScreen];
611         }
612         /*This makes this function dependant of the presence of a macosx 
613         interface. We do not check if this interface exists, since it has 
614         already been done before.*/
615
616         p_intf = [NSApp getIntf];
617
618         val.b_bool = VLC_TRUE;
619         var_Create(p_intf,"intf-change",VLC_VAR_BOOL);
620         var_Set(p_intf, "intf-change",val);
621
622         return 0;
623     }
624     
625     QTDestroySequence( p_vout );
626
627     if( CoDestroyWindow( p_vout ) )
628     {
629         msg_Err( p_vout, "unable to destroy window" );
630         return( 1 );
631     }
632     
633     p_vout->b_fullscreen = !p_vout->b_fullscreen;
634
635     if( CoCreateWindow( p_vout ) )
636     {
637         msg_Err( p_vout, "unable to create window" );
638         return( 1 );
639     }
640
641     SetPort( p_vout->p_sys->p_qdport );
642     QTScaleMatrix( p_vout );
643
644     if( QTCreateSequence( p_vout ) )
645     {
646         msg_Err( p_vout, "unable to create sequence" );
647         return( 1 ); 
648     } 
649
650     return( 0 );
651 }
652
653 /*****************************************************************************
654  * VLCHideMouse: if b_hide then hide the cursor
655  *****************************************************************************/
656 static void VLCHideMouse ( vout_thread_t *p_vout, BOOL b_hide )
657 {
658     BOOL b_inside;
659     NSRect s_rect;
660     NSPoint ml;
661     NSWindow *o_window = p_vout->p_sys->o_window;
662     NSView *o_contents = [o_window contentView];
663     
664     s_rect = [o_contents bounds];
665     ml = [o_window convertScreenToBase:[NSEvent mouseLocation]];
666     ml = [o_contents convertPoint:ml fromView:nil];
667     b_inside = [o_contents mouse: ml inRect: s_rect];
668     
669     if ( b_hide && b_inside )
670     {
671         /* only hide if mouse over VLCQTView */
672         [NSCursor setHiddenUntilMouseMoves: YES];
673     }
674     else if ( !b_hide )
675     {
676         [NSCursor setHiddenUntilMouseMoves: NO];
677     }
678     p_vout->p_sys->b_mouse_moved = NO;
679     p_vout->p_sys->i_time_mouse_last_moved = mdate();
680     return;
681 }
682
683 /*****************************************************************************
684  * QTScaleMatrix: scale matrix 
685  *****************************************************************************/
686 static void QTScaleMatrix( vout_thread_t *p_vout )
687 {
688     Rect s_rect;
689     unsigned int i_width, i_height;
690     Fixed factor_x, factor_y;
691     unsigned int i_offset_x = 0;
692     unsigned int i_offset_y = 0;
693
694     GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
695
696     i_width = s_rect.right - s_rect.left;
697     i_height = s_rect.bottom - s_rect.top;
698
699     if( config_GetInt( p_vout, "macosx-stretch" ) )
700     {
701         factor_x = FixDiv( Long2Fix( i_width ),
702                            Long2Fix( p_vout->output.i_width ) );
703         factor_y = FixDiv( Long2Fix( i_height ),
704                            Long2Fix( p_vout->output.i_height ) );
705                            
706     }
707     else if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
708     {
709         int i_adj_width = i_height * p_vout->output.i_aspect /
710                           VOUT_ASPECT_FACTOR;
711
712         factor_x = FixDiv( Long2Fix( i_adj_width ),
713                            Long2Fix( p_vout->output.i_width ) );
714         factor_y = FixDiv( Long2Fix( i_height ),
715                            Long2Fix( p_vout->output.i_height ) );
716
717         i_offset_x = (i_width - i_adj_width) / 2;
718     }
719     else
720     {
721         int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
722                            p_vout->output.i_aspect;
723
724         factor_x = FixDiv( Long2Fix( i_width ),
725                            Long2Fix( p_vout->output.i_width ) );
726         factor_y = FixDiv( Long2Fix( i_adj_height ),
727                            Long2Fix( p_vout->output.i_height ) );
728
729         i_offset_y = (i_height - i_adj_height) / 2;
730     }
731     
732     SetIdentityMatrix( p_vout->p_sys->p_matrix );
733
734     ScaleMatrix( p_vout->p_sys->p_matrix,
735                  factor_x, factor_y,
736                  Long2Fix(0), Long2Fix(0) );
737                  
738     TranslateMatrix( p_vout->p_sys->p_matrix,
739                  Long2Fix(i_offset_x), Long2Fix(i_offset_y) );
740 }
741
742 /*****************************************************************************
743  * QTCreateSequence: create a new sequence 
744  *****************************************************************************
745  * Returns 0 on success, 1 otherwise
746  *****************************************************************************/
747 static int QTCreateSequence( vout_thread_t *p_vout )
748 {
749     OSErr err;
750     ImageDescriptionPtr p_descr;
751
752     HLock( (Handle)p_vout->p_sys->h_img_descr );
753     p_descr = *p_vout->p_sys->h_img_descr;
754
755     p_descr->idSize = sizeof(ImageDescription);
756     p_descr->cType = p_vout->p_sys->i_codec;
757     p_descr->version = 1;
758     p_descr->revisionLevel = 0;
759     p_descr->vendor = 'appl';
760     p_descr->width = p_vout->output.i_width;
761     p_descr->height = p_vout->output.i_height;
762     p_descr->hRes = Long2Fix(72);
763     p_descr->vRes = Long2Fix(72);
764     p_descr->spatialQuality = codecLosslessQuality;
765     p_descr->frameCount = 1;
766     p_descr->clutID = -1;
767     p_descr->dataSize = 0;
768     p_descr->depth = 24;
769
770     HUnlock( (Handle)p_vout->p_sys->h_img_descr );
771
772     if( ( err = DecompressSequenceBeginS( 
773                               &p_vout->p_sys->i_seq,
774                               p_vout->p_sys->h_img_descr,
775                               NULL, 0,
776                               p_vout->p_sys->p_qdport,
777                               NULL, NULL,
778                               p_vout->p_sys->p_matrix,
779                               0, NULL,
780                               codecFlagUseImageBuffer,
781                               codecLosslessQuality,
782                               p_vout->p_sys->img_dc ) ) )
783     {
784         msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
785         return( 1 );
786     }
787
788     return( 0 );
789 }
790
791 /*****************************************************************************
792  * QTDestroySequence: destroy sequence 
793  *****************************************************************************/
794 static void QTDestroySequence( vout_thread_t *p_vout )
795 {
796     CDSequenceEnd( p_vout->p_sys->i_seq );
797 }
798
799 /*****************************************************************************
800  * QTNewPicture: allocate a picture
801  *****************************************************************************
802  * Returns 0 on success, 1 otherwise
803  *****************************************************************************/
804 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
805 {
806     int i_width  = p_vout->output.i_width;
807     int i_height = p_vout->output.i_height;
808
809     /* We know the chroma, allocate a buffer which will be used
810      * directly by the decoder */
811     p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
812
813     if( p_pic->p_sys == NULL )
814     {
815         return( -1 );
816     }
817
818     switch( p_vout->output.i_chroma )
819     {
820         case VLC_FOURCC('I','4','2','0'):
821
822             p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
823             p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
824
825             /* Allocate the memory buffer */
826             p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
827                                           16, i_width * i_height * 3 / 2 );
828
829             /* Y buffer */
830             p_pic->Y_PIXELS = p_pic->p_data; 
831             p_pic->p[Y_PLANE].i_lines = i_height;
832             p_pic->p[Y_PLANE].i_pitch = i_width;
833             p_pic->p[Y_PLANE].i_pixel_pitch = 1;
834             p_pic->p[Y_PLANE].i_visible_pitch = i_width;
835
836             /* U buffer */
837             p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
838             p_pic->p[U_PLANE].i_lines = i_height / 2;
839             p_pic->p[U_PLANE].i_pitch = i_width / 2;
840             p_pic->p[U_PLANE].i_pixel_pitch = 1;
841             p_pic->p[U_PLANE].i_visible_pitch = i_width / 2;
842
843             /* V buffer */
844             p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
845             p_pic->p[V_PLANE].i_lines = i_height / 2;
846             p_pic->p[V_PLANE].i_pitch = i_width / 2;
847             p_pic->p[V_PLANE].i_pixel_pitch = 1;
848             p_pic->p[V_PLANE].i_visible_pitch = i_width / 2;
849
850             /* We allocated 3 planes */
851             p_pic->i_planes = 3;
852
853 #define P p_pic->p_sys->pixmap_i420
854             P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
855                                        - p_pic->p_sys->p_info;
856             P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
857                                         - p_pic->p_sys->p_info;
858             P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
859                                         - p_pic->p_sys->p_info;
860
861             P.componentInfoY.rowBytes = i_width;
862             P.componentInfoCb.rowBytes = i_width / 2;
863             P.componentInfoCr.rowBytes = i_width / 2;
864 #undef P
865
866             break;
867
868     default:
869         /* Unknown chroma, tell the guy to get lost */
870         free( p_pic->p_sys );
871         msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
872                  p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
873         p_pic->i_planes = 0;
874         return( -1 );
875     }
876
877     return( 0 );
878 }
879
880 /*****************************************************************************
881  * QTFreePicture: destroy a picture allocated with QTNewPicture
882  *****************************************************************************/
883 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
884 {
885     switch( p_vout->output.i_chroma )
886     {
887         case VLC_FOURCC('I','4','2','0'):
888             free( p_pic->p_data_orig );
889             break;
890     }
891
892     free( p_pic->p_sys );
893 }
894
895 /*****************************************************************************
896  * VLCWindow implementation
897  *****************************************************************************/
898 @implementation VLCWindow
899
900 - (void)setVout:(vout_thread_t *)_p_vout
901 {
902     p_vout = _p_vout;
903 }
904
905 - (vout_thread_t *)getVout
906 {
907     return( p_vout );
908 }
909
910 - (void)scaleWindowWithFactor: (float)factor
911 {
912     NSSize newsize;
913     int i_corrected_height, i_corrected_width;
914     NSPoint topleftbase;
915     NSPoint topleftscreen;
916     
917     if ( !p_vout->b_fullscreen )
918     {
919         topleftbase.x = 0;
920         topleftbase.y = [self frame].size.height;
921         topleftscreen = [self convertBaseToScreen: topleftbase];
922         
923         if( p_vout->output.i_height * p_vout->output.i_aspect > 
924                         p_vout->output.i_width * VOUT_ASPECT_FACTOR )
925         {
926             i_corrected_width = p_vout->output.i_height * p_vout->output.i_aspect /
927                                             VOUT_ASPECT_FACTOR;
928             newsize.width = (int) ( i_corrected_width * factor );
929             newsize.height = (int) ( p_vout->render.i_height * factor );
930         }
931         else
932         {
933             i_corrected_height = p_vout->output.i_width * VOUT_ASPECT_FACTOR /
934                                             p_vout->output.i_aspect;
935             newsize.width = (int) ( p_vout->render.i_width * factor );
936             newsize.height = (int) ( i_corrected_height * factor );
937         }
938     
939         [self setContentSize: newsize];
940         
941         [self setFrameTopLeftPoint: topleftscreen];
942         p_vout->i_changes |= VOUT_SIZE_CHANGE;
943     }
944 }
945
946 - (void)toggleFloatOnTop
947 {
948     if( config_GetInt( p_vout, "video-on-top" ) )
949     {
950         config_PutInt( p_vout, "video-on-top", 0 );
951         [p_vout->p_sys->o_window setLevel: NSNormalWindowLevel];
952     }
953     else
954     {
955         config_PutInt( p_vout, "video-on-top", 1 );
956         [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
957     }
958 }
959
960 - (void)toggleFullscreen
961 {
962     config_PutInt(p_vout, "fullscreen", !p_vout->b_fullscreen);
963     p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
964 }
965
966 - (BOOL)isFullscreen
967 {
968     return( p_vout->b_fullscreen );
969 }
970
971 - (BOOL)canBecomeKeyWindow
972 {
973     return( YES );
974 }
975
976 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
977 {
978     return [(VLCApplication *) [VLCApplication sharedApplication]
979             hasDefinedShortcutKey:o_event];
980 }
981
982 - (void)keyDown:(NSEvent *)o_event
983 {
984     unichar key = 0;
985     vlc_value_t val;
986     unsigned int i_pressed_modifiers = 0;
987     val.i_int = 0;
988     
989     i_pressed_modifiers = [o_event modifierFlags];
990
991     if( i_pressed_modifiers & NSShiftKeyMask )
992         val.i_int |= KEY_MODIFIER_SHIFT;
993     if( i_pressed_modifiers & NSControlKeyMask )
994         val.i_int |= KEY_MODIFIER_CTRL;
995     if( i_pressed_modifiers & NSAlternateKeyMask )
996         val.i_int |= KEY_MODIFIER_ALT;
997     if( i_pressed_modifiers & NSCommandKeyMask )
998         val.i_int |= KEY_MODIFIER_COMMAND;
999
1000     key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
1001
1002     if( key )
1003     {
1004         /* Escape should always get you out of fullscreen */
1005         if( key == (unichar) 0x1b )
1006         {
1007              if( [self isFullscreen] )
1008              {
1009                  [self toggleFullscreen];
1010              }
1011         }
1012         else if ( key == ' ' )
1013         {
1014              playlist_t *p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1015                                                      FIND_ANYWHERE );
1016              if ( p_playlist != NULL )
1017              {
1018                  playlist_Pause( p_playlist );
1019                  vlc_object_release( p_playlist);
1020              }
1021         }
1022         else
1023         {
1024             val.i_int |= CocoaKeyToVLC( key );
1025             var_Set( p_vout->p_vlc, "key-pressed", val );
1026         }
1027     }
1028     else
1029     {
1030         [super keyDown: o_event];
1031     }
1032 }
1033
1034 - (void)updateTitle
1035 {
1036     NSMutableString * o_title;
1037     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1038                                                        FIND_ANYWHERE );
1039     
1040     if( p_playlist == NULL )
1041     {
1042         return;
1043     }
1044
1045     vlc_mutex_lock( &p_playlist->object_lock );
1046     o_title = [NSMutableString stringWithUTF8String: 
1047         p_playlist->pp_items[p_playlist->i_index]->input.psz_uri]; 
1048     vlc_mutex_unlock( &p_playlist->object_lock );
1049
1050     vlc_object_release( p_playlist );
1051
1052     if( o_title != nil )
1053     {
1054         NSRange prefix_range = [o_title rangeOfString: @"file:"];
1055         if( prefix_range.location != NSNotFound )
1056         {
1057             [o_title deleteCharactersInRange: prefix_range];
1058         }
1059
1060         [self setTitleWithRepresentedFilename: o_title];
1061     }
1062     else
1063     {
1064         [self setTitle:
1065             [NSString stringWithCString: VOUT_TITLE " (QuickTime)"]];
1066     }
1067 }
1068
1069 /* This is actually the same as VLCControls::stop. */
1070 - (BOOL)windowShouldClose:(id)sender
1071 {
1072     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1073                                                        FIND_ANYWHERE );
1074     if( p_playlist == NULL )      
1075     {
1076         return NO;
1077     }
1078
1079     playlist_Stop( p_playlist );
1080     vlc_object_release( p_playlist );
1081
1082     /* The window will be closed by the intf later. */
1083     return NO;
1084 }
1085
1086 @end
1087
1088 /*****************************************************************************
1089  * VLCQTView implementation
1090  *****************************************************************************/
1091 @implementation VLCQTView
1092
1093 - (void)drawRect:(NSRect)rect
1094 {
1095     vout_thread_t * p_vout;
1096     id o_window = [self window];
1097     p_vout = (vout_thread_t *)[o_window getVout];
1098     
1099     [[NSColor blackColor] set];
1100     NSRectFill( rect );
1101     [super drawRect: rect];
1102
1103     p_vout->i_changes |= VOUT_SIZE_CHANGE;
1104 }
1105
1106 - (BOOL)acceptsFirstResponder
1107 {
1108     return( YES );
1109 }
1110
1111 - (BOOL)becomeFirstResponder
1112 {
1113     vout_thread_t * p_vout;
1114     id o_window = [self window];
1115     p_vout = (vout_thread_t *)[o_window getVout];
1116     
1117     [o_window setAcceptsMouseMovedEvents: YES];
1118     return( YES );
1119 }
1120
1121 - (BOOL)resignFirstResponder
1122 {
1123     vout_thread_t * p_vout;
1124     id o_window = [self window];
1125     p_vout = (vout_thread_t *)[o_window getVout];
1126     
1127     [o_window setAcceptsMouseMovedEvents: NO];
1128     VLCHideMouse( p_vout, NO );
1129     return( YES );
1130 }
1131
1132 - (void)mouseDown:(NSEvent *)o_event
1133 {
1134     vout_thread_t * p_vout;
1135     id o_window = [self window];
1136     p_vout = (vout_thread_t *)[o_window getVout];
1137     vlc_value_t val;
1138
1139     switch( [o_event type] )
1140     {        
1141         case NSLeftMouseDown:
1142         {
1143             var_Get( p_vout, "mouse-button-down", &val );
1144             val.i_int |= 1;
1145             var_Set( p_vout, "mouse-button-down", val );
1146         }
1147         break;
1148         
1149         default:
1150             [super mouseDown: o_event];
1151         break;
1152     }
1153 }
1154
1155 - (void)otherMouseDown:(NSEvent *)o_event
1156 {
1157     /* This is not the the wheel button. you need to poll the
1158      * mouseWheel event for that. other is a third, forth or fifth button */
1159     vout_thread_t * p_vout;
1160     id o_window = [self window];
1161     p_vout = (vout_thread_t *)[o_window getVout];
1162     vlc_value_t val;
1163
1164     switch( [o_event type] )
1165     {
1166         case NSOtherMouseDown:
1167         {
1168             var_Get( p_vout, "mouse-button-down", &val );
1169             val.i_int |= 2;
1170             var_Set( p_vout, "mouse-button-down", val );
1171         }
1172         break;
1173         
1174         default:
1175             [super mouseDown: o_event];
1176         break;
1177     }
1178 }
1179
1180 - (void)rightMouseDown:(NSEvent *)o_event
1181 {
1182     vout_thread_t * p_vout;
1183     id o_window = [self window];
1184     p_vout = (vout_thread_t *)[o_window getVout];
1185     vlc_value_t val;
1186
1187     switch( [o_event type] )
1188     {
1189         case NSRightMouseDown:
1190         {
1191             var_Get( p_vout, "mouse-button-down", &val );
1192             val.i_int |= 4;
1193             var_Set( p_vout, "mouse-button-down", val );
1194         }
1195         break;
1196         
1197         default:
1198             [super mouseDown: o_event];
1199         break;
1200     }
1201 }
1202
1203 - (void)mouseUp:(NSEvent *)o_event
1204 {
1205     vout_thread_t * p_vout;
1206     id o_window = [self window];
1207     p_vout = (vout_thread_t *)[o_window getVout];
1208     vlc_value_t val;
1209
1210     switch( [o_event type] )
1211     {
1212         case NSLeftMouseUp:
1213         {
1214             vlc_value_t b_val;
1215             b_val.b_bool = VLC_TRUE;
1216             var_Set( p_vout, "mouse-clicked", b_val );
1217             
1218             var_Get( p_vout, "mouse-button-down", &val );
1219             val.i_int &= ~1;
1220             var_Set( p_vout, "mouse-button-down", val );
1221         }
1222         break;
1223                 
1224         default:
1225             [super mouseUp: o_event];
1226         break;
1227     }
1228 }
1229
1230 - (void)otherMouseUp:(NSEvent *)o_event
1231 {
1232     vout_thread_t * p_vout;
1233     id o_window = [self window];
1234     p_vout = (vout_thread_t *)[o_window getVout];
1235     vlc_value_t val;
1236
1237     switch( [o_event type] )
1238     {
1239         case NSOtherMouseUp:
1240         {
1241             var_Get( p_vout, "mouse-button-down", &val );
1242             val.i_int &= ~2;
1243             var_Set( p_vout, "mouse-button-down", val );
1244         }
1245         break;
1246                 
1247         default:
1248             [super mouseUp: o_event];
1249         break;
1250     }
1251 }
1252
1253 - (void)rightMouseUp:(NSEvent *)o_event
1254 {
1255     vout_thread_t * p_vout;
1256     id o_window = [self window];
1257     p_vout = (vout_thread_t *)[o_window getVout];
1258     vlc_value_t val;
1259
1260     switch( [o_event type] )
1261     {
1262         case NSRightMouseUp:
1263         {
1264             var_Get( p_vout, "mouse-button-down", &val );
1265             val.i_int &= ~4;
1266             var_Set( p_vout, "mouse-button-down", val );
1267         }
1268         break;
1269         
1270         default:
1271             [super mouseUp: o_event];
1272         break;
1273     }
1274 }
1275
1276 - (void)mouseDragged:(NSEvent *)o_event
1277 {
1278     [self mouseMoved:o_event];
1279 }
1280
1281 - (void)otherMouseDragged:(NSEvent *)o_event
1282 {
1283     [self mouseMoved:o_event];
1284 }
1285
1286 - (void)rightMouseDragged:(NSEvent *)o_event
1287 {
1288     [self mouseMoved:o_event];
1289 }
1290
1291 - (void)mouseMoved:(NSEvent *)o_event
1292 {
1293     NSPoint ml;
1294     NSRect s_rect;
1295     BOOL b_inside;
1296
1297     vout_thread_t * p_vout;
1298     id o_window = [self window];
1299     p_vout = (vout_thread_t *)[o_window getVout];
1300
1301     s_rect = [self bounds];
1302     ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
1303     b_inside = [self mouse: ml inRect: s_rect];
1304
1305     if( b_inside )
1306     {
1307         vlc_value_t val;
1308         int i_width, i_height, i_x, i_y;
1309         
1310         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
1311                                    (unsigned int)s_rect.size.height,
1312                                    &i_x, &i_y, &i_width, &i_height );
1313
1314         val.i_int = ( ((int)ml.x) - i_x ) * 
1315                     p_vout->render.i_width / i_width;
1316         var_Set( p_vout, "mouse-x", val );
1317
1318         val.i_int = ( ((int)ml.y) - i_y ) * 
1319                     p_vout->render.i_height / i_height;
1320         var_Set( p_vout, "mouse-y", val );
1321             
1322         val.b_bool = VLC_TRUE;
1323         var_Set( p_vout, "mouse-moved", val );
1324         p_vout->p_sys->i_time_mouse_last_moved = mdate();
1325         p_vout->p_sys->b_mouse_moved = YES;
1326     }
1327
1328     [super mouseMoved: o_event];
1329 }
1330
1331 @end
1332
1333 /*****************************************************************************
1334  * VLCGLView implementation
1335  *****************************************************************************/
1336 @implementation VLCGLView
1337
1338
1339 - (id) initWithFrame: (NSRect) frame vout: (vout_thread_t*) _p_vout
1340 {
1341     char * psz_effect;
1342     p_vout = _p_vout;
1343     
1344     NSOpenGLPixelFormatAttribute attribs[] =
1345     {
1346         NSOpenGLPFAAccelerated,
1347         NSOpenGLPFANoRecovery,
1348         NSOpenGLPFADoubleBuffer,
1349         NSOpenGLPFAColorSize, 24,
1350         NSOpenGLPFAAlphaSize, 8,
1351         NSOpenGLPFADepthSize, 24,
1352         NSOpenGLPFAWindow,
1353         0
1354     };
1355
1356     NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
1357         initWithAttributes: attribs];
1358
1359     if( !fmt )
1360     {
1361         msg_Warn( p_vout, "Cannot create NSOpenGLPixelFormat" );
1362         return nil;
1363     }
1364
1365     self = [super initWithFrame:frame pixelFormat: fmt];
1366
1367     currentContext = [self openGLContext];
1368     [currentContext makeCurrentContext];
1369     [currentContext update];
1370
1371     /* Black background */
1372     glClearColor( 0.0, 0.0, 0.0, 0.0 );
1373
1374     /* Check if the user asked for useless visual effects */
1375     psz_effect = config_GetPsz( p_vout, "macosx-opengl-effect" );
1376     if( !psz_effect || !strcmp( psz_effect, "none" ))
1377     {
1378         i_effect = OPENGL_EFFECT_NONE;
1379     }
1380     else if( !strcmp( psz_effect, "cube" ) )
1381     {
1382         i_effect = OPENGL_EFFECT_CUBE;
1383
1384         glEnable( GL_DEPTH_TEST );
1385     }
1386     else if( !strcmp( psz_effect, "transparent-cube" ) )
1387     {
1388         i_effect = OPENGL_EFFECT_TRANSPARENT_CUBE;
1389
1390         glDisable( GL_DEPTH_TEST );
1391         glEnable( GL_BLEND );
1392         glBlendFunc( GL_SRC_ALPHA, GL_ONE );
1393     }
1394     else
1395     {
1396         msg_Warn( p_vout, "no valid opengl effect provided, using "
1397                   "\"none\"" );
1398         i_effect = OPENGL_EFFECT_NONE;
1399     }
1400
1401     if( i_effect & ( OPENGL_EFFECT_CUBE |
1402                 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
1403     {
1404         /* Set the perpective */
1405         glMatrixMode( GL_PROJECTION );
1406         glLoadIdentity();
1407         glFrustum( -1.0, 1.0, -1.0, 1.0, 3.0, 20.0 );
1408         glMatrixMode( GL_MODELVIEW );
1409         glLoadIdentity();
1410         glTranslatef( 0.0, 0.0, - 5.0 );
1411     }
1412
1413     i_texture    = 0;
1414     initDone     = 0;
1415     isFullScreen = 0;
1416
1417     return self;
1418 }
1419
1420 - (void) reshape
1421 {
1422     if( !initDone )
1423     {
1424         return;
1425     }
1426     
1427     [currentContext makeCurrentContext];
1428
1429     NSRect bounds = [self bounds];
1430     glViewport( 0, 0, (GLint) bounds.size.width,
1431                 (GLint) bounds.size.height );
1432
1433     /* Quad size is set in order to preserve the aspect ratio */
1434     if( config_GetInt( p_vout, "macosx-stretch" ) )
1435     {
1436         f_x = 1.0;
1437         f_y = 1.0;
1438     }
1439     else if( bounds.size.height * p_vout->output.i_aspect <
1440         bounds.size.width * VOUT_ASPECT_FACTOR )
1441     {
1442         f_x = bounds.size.height * p_vout->output.i_aspect /
1443             VOUT_ASPECT_FACTOR / bounds.size.width;
1444         f_y = 1.0;
1445     }
1446     else
1447     {
1448         f_x = 1.0;
1449         f_y = bounds.size.width * VOUT_ASPECT_FACTOR /
1450             p_vout->output.i_aspect / bounds.size.height;
1451     }
1452 }
1453
1454 - (void) initTextures
1455 {
1456     [currentContext makeCurrentContext];
1457
1458     /* Free previous texture if any */
1459     if( i_texture )
1460     {
1461         glDeleteTextures( 1, &i_texture );
1462     }
1463     
1464     /* Create textures */
1465     glGenTextures( 1, &i_texture );
1466
1467     glEnable( GL_TEXTURE_RECTANGLE_EXT );
1468     glEnable( GL_UNPACK_CLIENT_STORAGE_APPLE );
1469
1470     glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1471
1472     /* Use VRAM texturing */
1473     glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1474             GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE );
1475
1476     /* Tell the driver not to make a copy of the texture but to use
1477        our buffer */
1478     glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE );
1479
1480     /* Linear interpolation */
1481     glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1482             GL_TEXTURE_MIN_FILTER, GL_LINEAR );
1483     glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1484             GL_TEXTURE_MAG_FILTER, GL_LINEAR );
1485
1486     /* I have no idea what this exactly does, but it seems to be
1487        necessary for scaling */
1488     glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1489             GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
1490     glTexParameteri( GL_TEXTURE_RECTANGLE_EXT,
1491             GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1492     glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
1493
1494     glTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA,
1495             p_vout->output.i_width, p_vout->output.i_height, 0,
1496             GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1497             PP_OUTPUTPICTURE[0]->p_data );
1498
1499     initDone = 1;
1500 }
1501
1502 - (void) reloadTexture
1503 {
1504     if( !initDone )
1505     {
1506         return;
1507     }
1508     
1509     [currentContext makeCurrentContext];
1510
1511     glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1512
1513     /* glTexSubImage2D is faster than glTexImage2D
1514        http://developer.apple.com/samplecode/Sample_Code/Graphics_3D/
1515        TextureRange/MainOpenGLView.m.htm */
1516     glTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0,
1517             p_vout->output.i_width, p_vout->output.i_height,
1518             GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE,
1519             PP_OUTPUTPICTURE[0]->p_data );
1520 }
1521
1522 - (void) goFullScreen
1523 {
1524     /* Create the new pixel format */
1525     NSOpenGLPixelFormatAttribute attribs[] =
1526     {
1527         NSOpenGLPFAAccelerated,
1528         NSOpenGLPFANoRecovery,
1529         NSOpenGLPFADoubleBuffer,
1530         NSOpenGLPFAColorSize, 24,
1531         NSOpenGLPFAAlphaSize, 8,
1532         NSOpenGLPFADepthSize, 24,
1533         NSOpenGLPFAFullScreen,
1534         NSOpenGLPFAScreenMask,
1535         /* TODO handle macosx-vdev */
1536         CGDisplayIDToOpenGLDisplayMask( kCGDirectMainDisplay ),
1537         0
1538     };
1539     NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
1540         initWithAttributes: attribs];
1541     if( !fmt )
1542     {
1543         msg_Warn( p_vout, "Cannot create NSOpenGLPixelFormat" );
1544         return;
1545     }
1546
1547     /* Create the new OpenGL context */
1548     fullScreenContext = [[NSOpenGLContext alloc]
1549         initWithFormat: fmt shareContext: nil];
1550     if( !fullScreenContext )
1551     {
1552         msg_Warn( p_vout, "Failed to create new NSOpenGLContext" );
1553         return;
1554     }
1555     currentContext = fullScreenContext;
1556
1557     /* Capture display, switch to fullscreen */
1558     if( CGCaptureAllDisplays() != CGDisplayNoErr )
1559     {
1560         msg_Warn( p_vout, "CGCaptureAllDisplays() failed" );
1561         return;
1562     }
1563     [fullScreenContext setFullScreen];
1564     [fullScreenContext makeCurrentContext];
1565
1566     /* Ratio */
1567     unsigned width    = CGDisplayPixelsWide( kCGDirectMainDisplay );
1568     unsigned height   = CGDisplayPixelsHigh( kCGDirectMainDisplay );
1569     int      stretch  = config_GetInt( p_vout, "macosx-stretch" );
1570     int      fill     = config_GetInt( p_vout, "macosx-fill" );
1571     int      bigRatio = ( height * p_vout->output.i_aspect <
1572                           width * VOUT_ASPECT_FACTOR );
1573     if( stretch )
1574     {
1575         f_x = 1.0;
1576         f_y = 1.0;
1577     }
1578     else if( ( bigRatio && !fill ) || ( !bigRatio && fill ) )
1579     {
1580         f_x = (float) height * p_vout->output.i_aspect /
1581                   width / VOUT_ASPECT_FACTOR;
1582         f_y = 1.0;
1583     }
1584     else
1585     {
1586         f_x = 1.0;
1587         f_y = (float) width * VOUT_ASPECT_FACTOR /
1588                   p_vout->output.i_aspect / height;
1589     }
1590
1591     /* Update viewport, re-init textures */
1592     glViewport( 0, 0, width, height );
1593     [self initTextures];
1594
1595     /* Redraw the last picture */
1596     [self setNeedsDisplay: YES];
1597
1598     isFullScreen = 1;
1599 }
1600
1601 - (void) exitFullScreen
1602 {
1603     /* Free current OpenGL context */
1604     [NSOpenGLContext clearCurrentContext];
1605     [fullScreenContext clearDrawable];
1606     [fullScreenContext release];
1607     CGReleaseAllDisplays();
1608
1609     currentContext = [self openGLContext];
1610     [self initTextures];
1611     [self reshape];
1612
1613     /* Redraw the last picture */
1614     [self setNeedsDisplay: YES];
1615
1616     isFullScreen = 0;
1617 }
1618
1619 - (void) cleanUp
1620 {
1621     if( isFullScreen )
1622     {
1623         [self exitFullScreen];
1624     }
1625     initDone = 0;
1626 }
1627
1628 - (void) drawQuad
1629 {
1630     glBegin( GL_QUADS );
1631         /* Top left */
1632         glTexCoord2f( 0.0, 0.0 );
1633         glVertex2f( - f_x, f_y );
1634         /* Bottom left */
1635         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1636         glVertex2f( - f_x, - f_y );
1637         /* Bottom right */
1638         glTexCoord2f( (float) p_vout->output.i_width,
1639                       (float) p_vout->output.i_height );
1640         glVertex2f( f_x, - f_y );
1641         /* Top right */
1642         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1643         glVertex2f( f_x, f_y );
1644     glEnd();
1645 }
1646
1647 - (void) drawCube
1648 {
1649     glBegin( GL_QUADS );
1650         /* Front */
1651         glTexCoord2f( 0.0, 0.0 );
1652         glVertex3f( - 1.0, 1.0, 1.0 );
1653         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1654         glVertex3f( - 1.0, - 1.0, 1.0 );
1655         glTexCoord2f( (float) p_vout->output.i_width,
1656                       (float) p_vout->output.i_height );
1657         glVertex3f( 1.0, - 1.0, 1.0 );
1658         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1659         glVertex3f( 1.0, 1.0, 1.0 );
1660
1661         /* Left */
1662         glTexCoord2f( 0.0, 0.0 );
1663         glVertex3f( - 1.0, 1.0, - 1.0 );
1664         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1665         glVertex3f( - 1.0, - 1.0, - 1.0 );
1666         glTexCoord2f( (float) p_vout->output.i_width,
1667                       (float) p_vout->output.i_height );
1668         glVertex3f( - 1.0, - 1.0, 1.0 );
1669         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1670         glVertex3f( - 1.0, 1.0, 1.0 );
1671
1672         /* Back */
1673         glTexCoord2f( 0.0, 0.0 );
1674         glVertex3f( 1.0, 1.0, - 1.0 );
1675         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1676         glVertex3f( 1.0, - 1.0, - 1.0 );
1677         glTexCoord2f( (float) p_vout->output.i_width,
1678                       (float) p_vout->output.i_height );
1679         glVertex3f( - 1.0, - 1.0, - 1.0 );
1680         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1681         glVertex3f( - 1.0, 1.0, - 1.0 );
1682
1683         /* Right */
1684         glTexCoord2f( 0.0, 0.0 );
1685         glVertex3f( 1.0, 1.0, 1.0 );
1686         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1687         glVertex3f( 1.0, - 1.0, 1.0 );
1688         glTexCoord2f( (float) p_vout->output.i_width,
1689                       (float) p_vout->output.i_height );
1690         glVertex3f( 1.0, - 1.0, - 1.0 );
1691         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1692         glVertex3f( 1.0, 1.0, - 1.0 );
1693
1694         /* Top */
1695         glTexCoord2f( 0.0, 0.0 );
1696         glVertex3f( - 1.0, 1.0, - 1.0 );
1697         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1698         glVertex3f( - 1.0, 1.0, 1.0 );
1699         glTexCoord2f( (float) p_vout->output.i_width,
1700                       (float) p_vout->output.i_height );
1701         glVertex3f( 1.0, 1.0, 1.0 );
1702         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1703         glVertex3f( 1.0, 1.0, - 1.0 );
1704
1705         /* Bottom */
1706         glTexCoord2f( 0.0, 0.0 );
1707         glVertex3f( - 1.0, - 1.0, 1.0 );
1708         glTexCoord2f( 0.0, (float) p_vout->output.i_height );
1709         glVertex3f( - 1.0, - 1.0, - 1.0 );
1710         glTexCoord2f( (float) p_vout->output.i_width,
1711                       (float) p_vout->output.i_height );
1712         glVertex3f( 1.0, - 1.0, - 1.0 );
1713         glTexCoord2f( (float) p_vout->output.i_width, 0.0 );
1714         glVertex3f( 1.0, - 1.0, 1.0 );
1715     glEnd();
1716 }
1717
1718 - (void) drawRect: (NSRect) rect
1719 {
1720     [currentContext makeCurrentContext];
1721
1722     /* Swap buffers only during the vertical retrace of the monitor.
1723        http://developer.apple.com/documentation/GraphicsImaging/
1724        Conceptual/OpenGL/chap5/chapter_5_section_44.html */
1725     long params[] = { 1 };
1726     CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
1727                      params );
1728     
1729     /* Black background */
1730     glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
1731
1732     if( !initDone )
1733     {
1734         [currentContext flushBuffer];
1735         return;
1736     }
1737
1738     /* Draw */
1739     glBindTexture( GL_TEXTURE_RECTANGLE_EXT, i_texture );
1740     if( i_effect & ( OPENGL_EFFECT_CUBE |
1741                 OPENGL_EFFECT_TRANSPARENT_CUBE ) )
1742     {
1743         glRotatef( 1.0, 0.3, 0.5, 0.7 );
1744         [self drawCube];
1745     }
1746     else
1747     {
1748         [self drawQuad];
1749     }
1750
1751     /* Wait for the job to be done */
1752     [currentContext flushBuffer];
1753 }
1754
1755 @end
1756
1757 /*****************************************************************************
1758  * VLCVout implementation
1759  *****************************************************************************/
1760 @implementation VLCVout
1761
1762 - (void)createWindow:(NSValue *)o_value
1763 {
1764     vlc_value_t val;
1765     VLCQTView * o_view;
1766     NSScreen * o_screen;
1767     vout_thread_t * p_vout;
1768     vlc_bool_t b_main_screen;
1769     
1770     p_vout = (vout_thread_t *)[o_value pointerValue];
1771
1772     p_vout->p_sys->o_window = [VLCWindow alloc];
1773     [p_vout->p_sys->o_window setVout: p_vout];
1774     [p_vout->p_sys->o_window setReleasedWhenClosed: YES];
1775
1776     if( var_Get( p_vout, "video-device", &val ) < 0 )
1777     {
1778         o_screen = [NSScreen mainScreen];
1779         b_main_screen = 1;
1780     }
1781     else
1782     {
1783         NSArray *o_screens = [NSScreen screens];
1784         unsigned int i_index = val.i_int;
1785         
1786         if( [o_screens count] < i_index )
1787         {
1788             o_screen = [NSScreen mainScreen];
1789             b_main_screen = 1;
1790         }
1791         else
1792         {
1793             i_index--;
1794             o_screen = [o_screens objectAtIndex: i_index];
1795             config_PutInt( p_vout, "macosx-vdev", i_index );
1796             b_main_screen = (i_index == 0);
1797         }
1798     } 
1799
1800     if( p_vout->b_fullscreen && !p_vout->p_sys->i_opengl )
1801     {
1802         NSRect screen_rect = [o_screen frame];
1803         screen_rect.origin.x = screen_rect.origin.y = 0;
1804
1805         if ( b_main_screen && p_vout->p_sys->p_fullscreen_state == NULL )
1806             BeginFullScreen( &p_vout->p_sys->p_fullscreen_state, NULL, 0, 0,
1807                              NULL, NULL, fullScreenAllowEvents );
1808
1809         [p_vout->p_sys->o_window 
1810             initWithContentRect: screen_rect
1811             styleMask: NSBorderlessWindowMask
1812             backing: NSBackingStoreBuffered
1813             defer: NO screen: o_screen];
1814
1815         //[p_vout->p_sys->o_window setLevel: NSPopUpMenuWindowLevel - 1];
1816         p_vout->p_sys->b_mouse_moved = YES;
1817         p_vout->p_sys->i_time_mouse_last_moved = mdate();
1818     }
1819     else
1820     {
1821         unsigned int i_stylemask = NSTitledWindowMask |
1822                                    NSMiniaturizableWindowMask |
1823                                    NSClosableWindowMask |
1824                                    NSResizableWindowMask;
1825         
1826         if( !p_vout->p_sys->i_opengl )
1827         {
1828             if ( p_vout->p_sys->p_fullscreen_state != NULL )
1829                 EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
1830             p_vout->p_sys->p_fullscreen_state = NULL;
1831         }
1832
1833         [p_vout->p_sys->o_window 
1834             initWithContentRect: p_vout->p_sys->s_rect
1835             styleMask: i_stylemask
1836             backing: NSBackingStoreBuffered
1837             defer: NO screen: o_screen];
1838
1839         [p_vout->p_sys->o_window setAlphaValue: config_GetFloat( p_vout, "macosx-opaqueness" )];
1840         
1841         if( config_GetInt( p_vout, "video-on-top" ) )
1842         {
1843             [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
1844         }
1845         
1846         if( !p_vout->p_sys->b_pos_saved )   
1847         {
1848             [p_vout->p_sys->o_window center];
1849         }
1850     }
1851
1852     if( !p_vout->p_sys->i_opengl )
1853     {
1854         o_view = [[VLCQTView alloc] init];
1855         /* FIXME: [o_view setMenu:] */
1856         [p_vout->p_sys->o_window setContentView: o_view];
1857         [o_view autorelease];
1858
1859         [o_view lockFocus];
1860         p_vout->p_sys->p_qdport = [o_view qdPort];
1861         [o_view unlockFocus];
1862     }
1863     else
1864     {
1865 #define o_glview p_vout->p_sys->o_glview
1866         o_glview = [[VLCGLView alloc] initWithFrame: p_vout->p_sys->s_rect vout: p_vout];
1867         [p_vout->p_sys->o_window setContentView: o_glview];
1868         [o_glview autorelease];
1869 #undef o_glview
1870     }
1871     
1872     [p_vout->p_sys->o_window updateTitle];
1873     [p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
1874
1875 }
1876
1877 - (void)destroyWindow:(NSValue *)o_value
1878 {
1879     vout_thread_t * p_vout;
1880
1881     p_vout = (vout_thread_t *)[o_value pointerValue];
1882
1883     if( !p_vout->b_fullscreen )
1884     {
1885         NSRect s_rect;
1886
1887         s_rect = [[p_vout->p_sys->o_window contentView] frame];
1888         p_vout->p_sys->s_rect.size = s_rect.size;
1889
1890         s_rect = [p_vout->p_sys->o_window frame];
1891         p_vout->p_sys->s_rect.origin = s_rect.origin;
1892
1893         p_vout->p_sys->b_pos_saved = YES;
1894     }
1895     
1896     p_vout->p_sys->p_qdport = nil;
1897     [p_vout->p_sys->o_window close];
1898     p_vout->p_sys->o_window = nil;
1899 }
1900
1901 @end