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