]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
01e270ffcd35ba448175161b575092b3cff8297e
[vlc] / modules / gui / macosx / vout.m
1 /*****************************************************************************
2  * vout.m: MacOS X video output plugin
3  *****************************************************************************
4  * Copyright (C) 2001-2003 VideoLAN
5  * $Id: vout.m,v 1.12 2003/01/15 00:49:49 jlj Exp $
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Florian G. Pflug <fgp@phlo.org>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include <errno.h>                                                 /* ENOMEM */
30 #include <stdlib.h>                                                /* free() */
31 #include <string.h>                                            /* strerror() */
32
33 #include <vlc/vlc.h>
34 #include <vlc/vout.h>
35 #include <vlc/aout.h>
36 #include <vlc/intf.h>
37
38 #include <Cocoa/Cocoa.h>
39 #include <QuickTime/QuickTime.h>
40
41 #include "intf.h"
42 #include "vout.h"
43
44 #define QT_MAX_DIRECTBUFFERS 10
45
46 struct picture_sys_t
47 {
48     void *p_info;
49     unsigned int i_size;
50
51     /* When using I420 output */
52     PlanarPixmapInfoYUV420 pixmap_i420;
53 };
54
55 /*****************************************************************************
56  * Local prototypes
57  *****************************************************************************/
58 static int  vout_Init      ( vout_thread_t * );
59 static void vout_End       ( vout_thread_t * );
60 static int  vout_Manage    ( vout_thread_t * );
61 static void vout_Display   ( vout_thread_t *, picture_t * );
62
63 static int  CoSendRequest      ( vout_thread_t *, SEL );
64 static int  CoCreateWindow     ( vout_thread_t * );
65 static int  CoDestroyWindow    ( vout_thread_t * );
66 static int  CoToggleFullscreen ( vout_thread_t * );
67
68 static void QTScaleMatrix      ( vout_thread_t * );
69 static int  QTCreateSequence   ( vout_thread_t * );
70 static void QTDestroySequence  ( vout_thread_t * );
71 static int  QTNewPicture       ( vout_thread_t *, picture_t * );
72 static void QTFreePicture      ( vout_thread_t *, picture_t * );
73
74 /*****************************************************************************
75  * OpenVideo: allocates MacOS X video thread output method
76  *****************************************************************************
77  * This function allocates and initializes a MacOS X vout method.
78  *****************************************************************************/
79 int E_(OpenVideo) ( vlc_object_t *p_this )
80 {   
81     vout_thread_t * p_vout = (vout_thread_t *)p_this;
82     OSErr err;
83     int i_timeout;
84
85     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
86     if( p_vout->p_sys == NULL )
87     {
88         msg_Err( p_vout, "out of memory" );
89         return( 1 );
90     }
91
92     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
93
94     /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
95     for( i_timeout = 20 ; i_timeout-- ; )
96     {
97         if( NSApp == NULL )
98         {
99             msleep( INTF_IDLE_SLEEP );
100         }
101     }
102
103     if( NSApp == NULL )
104     {
105         msg_Err( p_vout, "no MacOS X interface present" );
106         free( p_vout->p_sys );
107         return( 1 );
108     }
109
110     if( [NSApp respondsToSelector: @selector(getIntf)] )
111     {
112         intf_thread_t * p_intf;
113
114         for( i_timeout = 10 ; i_timeout-- ; )
115         {
116             if( ( p_intf = [NSApp getIntf] ) == NULL )
117             {
118                 msleep( INTF_IDLE_SLEEP );
119             }
120         }
121
122         if( p_intf == NULL )
123         {
124             msg_Err( p_vout, "MacOS X intf has getIntf, but is NULL" );
125             free( p_vout->p_sys );
126             return( 1 );
127         }
128     }
129
130     p_vout->p_sys->h_img_descr = 
131         (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
132     p_vout->p_sys->p_matrix = (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
133     p_vout->p_sys->p_fullscreen_state = NULL;
134
135     p_vout->p_sys->b_mouse_pointer_visible = 1;
136
137     /* set window size */
138     p_vout->p_sys->s_rect.size.width = p_vout->i_window_width;
139     p_vout->p_sys->s_rect.size.height = p_vout->i_window_height;
140
141     if( ( err = EnterMovies() ) != noErr )
142     {
143         msg_Err( p_vout, "EnterMovies failed: %d", err );
144         free( p_vout->p_sys->p_matrix );
145         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
146         free( p_vout->p_sys );
147         return( 1 );
148     } 
149
150     if( vout_ChromaCmp( p_vout->render.i_chroma, VLC_FOURCC('I','4','2','0') ) )
151     {
152         err = FindCodec( kYUV420CodecType, bestSpeedCodec,
153                          nil, &p_vout->p_sys->img_dc );
154         if( err == noErr && p_vout->p_sys->img_dc != 0 )
155         {
156             p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
157             p_vout->p_sys->i_codec = kYUV420CodecType;
158         }
159         else
160         {
161             msg_Err( p_vout, "failed to find an appropriate codec" );
162         }
163     }
164     else
165     {
166         msg_Err( p_vout, "chroma 0x%08x not supported",
167                          p_vout->render.i_chroma );
168     }
169
170     if( p_vout->p_sys->img_dc == 0 )
171     {
172         free( p_vout->p_sys->p_matrix );
173         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
174         free( p_vout->p_sys );
175         return( 1 );        
176     }
177
178     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
179     NSArray * o_screens = [NSScreen screens];
180     if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
181     {
182         int i = 1;
183         vlc_value_t val;
184         NSScreen * o_screen;
185
186         int i_option = config_GetInt( p_vout, "macosx-vdev" );
187
188         var_Create( p_vout, "video-device", VLC_VAR_STRING |
189                                             VLC_VAR_HASCHOICE ); 
190
191         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
192
193         while( (o_screen = [o_enumerator nextObject]) != NULL )
194         {
195             char psz_temp[255];
196             NSRect s_rect = [o_screen frame];
197
198             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1, 
199                       "%s %d (%dx%d)", _("Screen"), i,
200                       (int)s_rect.size.width, (int)s_rect.size.height ); 
201
202             val.psz_string = psz_temp;
203             var_Change( p_vout, "video-device", VLC_VAR_ADDCHOICE, &val );
204
205             if( ( i - 1 ) == i_option )
206             {
207                 var_Set( p_vout, "video-device", val );
208             }
209
210             i++;
211         }
212
213         var_AddCallback( p_vout, "video-device", vout_VarCallback,
214                          NULL );
215
216         val.b_bool = VLC_TRUE;
217         var_Set( p_vout, "intf-change", val );
218     }
219     [o_pool release];
220
221     if( CoCreateWindow( p_vout ) )
222     {
223         msg_Err( p_vout, "unable to create window" );
224         free( p_vout->p_sys->p_matrix );
225         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
226         free( p_vout->p_sys ); 
227         return( 1 );
228     }
229
230     p_vout->pf_init = vout_Init;
231     p_vout->pf_end = vout_End;
232     p_vout->pf_manage = vout_Manage;
233     p_vout->pf_render = NULL;
234     p_vout->pf_display = vout_Display;
235
236     return( 0 );
237 }
238
239 /*****************************************************************************
240  * vout_Init: initialize video thread output method
241  *****************************************************************************/
242 static int vout_Init( vout_thread_t *p_vout )
243 {
244     int i_index;
245     picture_t *p_pic;
246
247     I_OUTPUTPICTURES = 0;
248
249     /* Initialize the output structure; we already found a codec,
250      * and the corresponding chroma we will be using. Since we can
251      * arbitrary scale, stick to the coordinates and aspect. */
252     p_vout->output.i_width  = p_vout->render.i_width;
253     p_vout->output.i_height = p_vout->render.i_height;
254     p_vout->output.i_aspect = p_vout->render.i_aspect;
255
256     SetPort( p_vout->p_sys->p_qdport );
257     QTScaleMatrix( p_vout );
258
259     if( QTCreateSequence( p_vout ) )
260     {
261         msg_Err( p_vout, "unable to create sequence" );
262         return( 1 );
263     }
264
265     /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
266     while( I_OUTPUTPICTURES < QT_MAX_DIRECTBUFFERS )
267     {
268         p_pic = NULL;
269
270         /* Find an empty picture slot */
271         for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
272         {
273             if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
274             {
275                 p_pic = p_vout->p_picture + i_index;
276                 break;
277             }
278         }
279
280         /* Allocate the picture */
281         if( p_pic == NULL || QTNewPicture( p_vout, p_pic ) )
282         {
283             break;
284         }
285
286         p_pic->i_status = DESTROYED_PICTURE;
287         p_pic->i_type   = DIRECT_PICTURE;
288
289         PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
290
291         I_OUTPUTPICTURES++;
292     }
293
294     return( 0 );
295 }
296
297 /*****************************************************************************
298  * vout_End: terminate video thread output method
299  *****************************************************************************/
300 static void vout_End( vout_thread_t *p_vout )
301 {
302     int i_index;
303
304     QTDestroySequence( p_vout );
305
306     /* Free the direct buffers we allocated */
307     for( i_index = I_OUTPUTPICTURES; i_index; )
308     {
309         i_index--;
310         QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
311     }
312 }
313
314 /*****************************************************************************
315  * CloseVideo: destroy video thread output method
316  *****************************************************************************/
317 void E_(CloseVideo) ( vlc_object_t *p_this )
318 {       
319     vout_thread_t * p_vout = (vout_thread_t *)p_this;     
320
321     if( CoDestroyWindow( p_vout ) )
322     {
323         msg_Err( p_vout, "unable to destroy window" );
324     }
325
326     if ( p_vout->p_sys->p_fullscreen_state != NULL )
327         EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
328
329     ExitMovies();
330
331     free( p_vout->p_sys->p_matrix );
332     DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
333
334     free( p_vout->p_sys );
335 }
336
337 /*****************************************************************************
338  * vout_Manage: handle events
339  *****************************************************************************
340  * This function should be called regularly by video output thread. It manages
341  * console events. It returns a non null value on error.
342  *****************************************************************************/
343 static int vout_Manage( vout_thread_t *p_vout )
344 {    
345     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
346     {
347         if( CoToggleFullscreen( p_vout ) )  
348         {
349             return( 1 );
350         }
351
352         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
353     }
354
355     if( p_vout->i_changes & VOUT_SIZE_CHANGE ) 
356     {
357         QTScaleMatrix( p_vout );
358         SetDSequenceMatrix( p_vout->p_sys->i_seq, 
359                             p_vout->p_sys->p_matrix );
360  
361         p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
362     }
363
364     /* hide/show mouse cursor */
365     if( p_vout->p_sys->b_mouse_moved ||
366         p_vout->p_sys->i_time_mouse_last_moved )
367     {
368         vlc_bool_t b_change = 0;
369
370         if( !p_vout->p_sys->b_mouse_pointer_visible )
371         {
372             CGDisplayShowCursor( kCGDirectMainDisplay );
373             b_change = 1;
374         }
375 #if 0
376         else if( !p_vout->p_sys->b_mouse_moved && 
377             mdate() - p_vout->p_sys->i_time_mouse_last_moved > 2000000 &&
378             p_vout->p_sys->b_mouse_pointer_visible )
379         {
380             CGDisplayHideCursor( kCGDirectMainDisplay );
381             b_change = 1;
382         }
383 #endif
384
385         if( b_change )
386         {
387             p_vout->p_sys->i_time_mouse_last_moved = 0;
388             p_vout->p_sys->b_mouse_moved = 0;
389             p_vout->p_sys->b_mouse_pointer_visible =
390                 !p_vout->p_sys->b_mouse_pointer_visible;
391         }
392     }
393
394     return( 0 );
395 }
396
397 /*****************************************************************************
398  * vout_Display: displays previously rendered output
399  *****************************************************************************
400  * This function sends the currently rendered image to the display.
401  *****************************************************************************/
402 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
403 {
404     OSErr err;
405     CodecFlags flags;
406
407     if( ( err = DecompressSequenceFrameS( 
408                     p_vout->p_sys->i_seq,
409                     p_pic->p_sys->p_info,
410                     p_pic->p_sys->i_size,                    
411                     codecFlagUseImageBuffer, &flags, nil ) != noErr ) )
412     {
413         msg_Err( p_vout, "DecompressSequenceFrameS failed: %d", err );
414     }
415     else
416     {
417         QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
418     }
419 }
420
421 /*****************************************************************************
422  * CoSendRequest: send request to interface thread
423  *****************************************************************************
424  * Returns 0 on success, 1 otherwise
425  *****************************************************************************/
426 static int CoSendRequest( vout_thread_t *p_vout, SEL sel )
427 {
428     int i_ret = 0;
429
430     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
431     VLCVout * o_vlv = [[[VLCVout alloc] init] autorelease];
432
433     if( [o_vlv respondsToSelector: @selector(performSelectorOnMainThread:
434                                              withObject:waitUntilDone:)] )
435     {
436         [o_vlv performSelectorOnMainThread: sel
437             withObject: [NSValue valueWithPointer: p_vout]
438             waitUntilDone: YES];
439     }
440     else if( [NSApp respondsToSelector: @selector(getIntf)] )
441     {
442         NSArray * o_array;
443         NSValue * o_value;
444         NSPort * o_recv_port;
445         NSInvocation * o_inv;
446         NSPortMessage * o_msg;
447         intf_thread_t * p_intf;
448         NSMethodSignature * o_sig;
449
450         p_intf = (intf_thread_t *)[NSApp getIntf];
451
452         o_recv_port = [[NSPort port] retain];
453         o_value = [NSValue valueWithPointer: p_vout];
454
455         o_sig = [VLCVout instanceMethodSignatureForSelector: sel];
456         o_inv = [NSInvocation invocationWithMethodSignature: o_sig];
457         [o_inv setArgument: &o_value atIndex: 2];
458         [o_inv setTarget: o_vlv];
459         [o_inv setSelector: sel];
460
461         o_array = [NSArray arrayWithObject:
462             [NSData dataWithBytes: &o_inv length: sizeof(o_inv)]];
463         o_msg = [[NSPortMessage alloc]
464             initWithSendPort: p_intf->p_sys->o_sendport
465             receivePort: o_recv_port components: o_array];
466
467         p_vout->p_sys->o_lock =
468             [[NSConditionLock alloc] initWithCondition: 0];
469         [o_msg sendBeforeDate: [NSDate distantPast]];
470         [p_vout->p_sys->o_lock lockWhenCondition: 1];
471         [p_vout->p_sys->o_lock unlock];
472         [p_vout->p_sys->o_lock release];
473         p_vout->p_sys->o_lock = nil;
474
475         [o_msg release];
476         [o_recv_port release];
477     }
478     else
479     {
480         msg_Err( p_vout, "SendRequest: no way to communicate with mt" );
481         i_ret = 1;
482     }
483
484     [o_pool release];
485
486     return( i_ret );
487 }
488
489 /*****************************************************************************
490  * CoCreateWindow: create new window 
491  *****************************************************************************
492  * Returns 0 on success, 1 otherwise
493  *****************************************************************************/
494 static int CoCreateWindow( vout_thread_t *p_vout )
495 {
496     if( CoSendRequest( p_vout, @selector(createWindow:) ) )
497     {
498         msg_Err( p_vout, "CoSendRequest (createWindow) failed" );
499         return( 1 );
500     }
501
502     return( 0 );
503 }
504
505 /*****************************************************************************
506  * CoDestroyWindow: destroy window 
507  *****************************************************************************
508  * Returns 0 on success, 1 otherwise
509  *****************************************************************************/
510 static int CoDestroyWindow( vout_thread_t *p_vout )
511 {
512     if( !p_vout->p_sys->b_mouse_pointer_visible )
513     {
514         CGDisplayShowCursor( kCGDirectMainDisplay );
515         p_vout->p_sys->b_mouse_pointer_visible = 1;
516     }
517
518     if( CoSendRequest( p_vout, @selector(destroyWindow:) ) )
519     {
520         msg_Err( p_vout, "CoSendRequest (destroyWindow) failed" );
521         return( 1 );
522     }
523
524     return( 0 );
525 }
526
527 /*****************************************************************************
528  * CoToggleFullscreen: toggle fullscreen 
529  *****************************************************************************
530  * Returns 0 on success, 1 otherwise
531  *****************************************************************************/
532 static int CoToggleFullscreen( vout_thread_t *p_vout )
533 {
534     QTDestroySequence( p_vout );
535
536     if( CoDestroyWindow( p_vout ) )
537     {
538         msg_Err( p_vout, "unable to destroy window" );
539         return( 1 );
540     }
541     
542     p_vout->b_fullscreen = !p_vout->b_fullscreen;
543
544     if( p_vout->b_fullscreen )
545     {
546         if ( p_vout->p_sys->p_fullscreen_state == NULL )
547             BeginFullScreen( &p_vout->p_sys->p_fullscreen_state, NULL, 0, 0,
548                              NULL, NULL, fullScreenHideCursor | fullScreenAllowEvents );
549     }
550     else
551     {
552         if ( p_vout->p_sys->p_fullscreen_state != NULL )
553             EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
554         p_vout->p_sys->p_fullscreen_state = NULL;
555     }
556     config_PutInt( p_vout, "fullscreen", p_vout->b_fullscreen );
557
558     if( CoCreateWindow( p_vout ) )
559     {
560         msg_Err( p_vout, "unable to create window" );
561         return( 1 );
562     }
563
564     SetPort( p_vout->p_sys->p_qdport );
565     QTScaleMatrix( p_vout );
566
567     if( QTCreateSequence( p_vout ) )
568     {
569         msg_Err( p_vout, "unable to create sequence" );
570         return( 1 ); 
571     } 
572
573     return( 0 );
574 }
575
576 /*****************************************************************************
577  * QTScaleMatrix: scale matrix 
578  *****************************************************************************/
579 static void QTScaleMatrix( vout_thread_t *p_vout )
580 {
581     Rect s_rect;
582     unsigned int i_width, i_height;
583     Fixed factor_x, factor_y;
584     unsigned int i_offset_x = 0;
585     unsigned int i_offset_y = 0;
586
587     GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
588
589     i_width = s_rect.right - s_rect.left;
590     i_height = s_rect.bottom - s_rect.top;
591
592     if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
593     {
594         int i_adj_width = i_height * p_vout->output.i_aspect /
595                           VOUT_ASPECT_FACTOR;
596
597         factor_x = FixDiv( Long2Fix( i_adj_width ),
598                            Long2Fix( p_vout->output.i_width ) );
599         factor_y = FixDiv( Long2Fix( i_height ),
600                            Long2Fix( p_vout->output.i_height ) );
601
602         i_offset_x = (i_width - i_adj_width) / 2;
603     }
604     else
605     {
606         int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
607                            p_vout->output.i_aspect;
608
609         factor_x = FixDiv( Long2Fix( i_width ),
610                            Long2Fix( p_vout->output.i_width ) );
611         factor_y = FixDiv( Long2Fix( i_adj_height ),
612                            Long2Fix( p_vout->output.i_height ) );
613
614         i_offset_y = (i_height - i_adj_height) / 2;
615     }
616
617     SetIdentityMatrix( p_vout->p_sys->p_matrix );
618
619     ScaleMatrix( p_vout->p_sys->p_matrix,
620                  factor_x, factor_y,
621                  Long2Fix(0), Long2Fix(0) );            
622
623     TranslateMatrix( p_vout->p_sys->p_matrix, 
624                      Long2Fix(i_offset_x), 
625                      Long2Fix(i_offset_y) );
626 }
627
628 /*****************************************************************************
629  * QTCreateSequence: create a new sequence 
630  *****************************************************************************
631  * Returns 0 on success, 1 otherwise
632  *****************************************************************************/
633 static int QTCreateSequence( vout_thread_t *p_vout )
634 {
635     OSErr err;
636     ImageDescriptionPtr p_descr;
637
638     HLock( (Handle)p_vout->p_sys->h_img_descr );
639     p_descr = *p_vout->p_sys->h_img_descr;
640
641     p_descr->idSize = sizeof(ImageDescription);
642     p_descr->cType = p_vout->p_sys->i_codec;
643     p_descr->version = 1;
644     p_descr->revisionLevel = 0;
645     p_descr->vendor = 'appl';
646     p_descr->width = p_vout->output.i_width;
647     p_descr->height = p_vout->output.i_height;
648     p_descr->hRes = Long2Fix(72);
649     p_descr->vRes = Long2Fix(72);
650     p_descr->spatialQuality = codecLosslessQuality;
651     p_descr->frameCount = 1;
652     p_descr->clutID = -1;
653     p_descr->dataSize = 0;
654     p_descr->depth = 24;
655
656     HUnlock( (Handle)p_vout->p_sys->h_img_descr );
657
658     if( ( err = DecompressSequenceBeginS( 
659                               &p_vout->p_sys->i_seq,
660                               p_vout->p_sys->h_img_descr,
661                               NULL, 0,
662                               p_vout->p_sys->p_qdport,
663                               NULL, NULL,
664                               p_vout->p_sys->p_matrix,
665                               0, NULL,
666                               codecFlagUseImageBuffer,
667                               codecLosslessQuality,
668                               p_vout->p_sys->img_dc ) ) )
669     {
670         msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
671         return( 1 );
672     }
673
674     return( 0 );
675 }
676
677 /*****************************************************************************
678  * QTDestroySequence: destroy sequence 
679  *****************************************************************************/
680 static void QTDestroySequence( vout_thread_t *p_vout )
681 {
682     CDSequenceEnd( p_vout->p_sys->i_seq );
683 }
684
685 /*****************************************************************************
686  * QTNewPicture: allocate a picture
687  *****************************************************************************
688  * Returns 0 on success, 1 otherwise
689  *****************************************************************************/
690 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
691 {
692     int i_width  = p_vout->output.i_width;
693     int i_height = p_vout->output.i_height;
694
695     /* We know the chroma, allocate a buffer which will be used
696      * directly by the decoder */
697     p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
698
699     if( p_pic->p_sys == NULL )
700     {
701         return( -1 );
702     }
703
704     switch( p_vout->output.i_chroma )
705     {
706         case VLC_FOURCC('I','4','2','0'):
707
708             p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
709             p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
710
711             /* Allocate the memory buffer */
712             p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
713                                           16, i_width * i_height * 3 / 2 );
714
715             /* Y buffer */
716             p_pic->Y_PIXELS = p_pic->p_data; 
717             p_pic->p[Y_PLANE].i_lines = i_height;
718             p_pic->p[Y_PLANE].i_pitch = i_width;
719             p_pic->p[Y_PLANE].i_pixel_pitch = 1;
720             p_pic->p[Y_PLANE].i_visible_pitch = i_width;
721
722             /* U buffer */
723             p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
724             p_pic->p[U_PLANE].i_lines = i_height / 2;
725             p_pic->p[U_PLANE].i_pitch = i_width / 2;
726             p_pic->p[U_PLANE].i_pixel_pitch = 1;
727             p_pic->p[U_PLANE].i_visible_pitch = i_width / 2;
728
729             /* V buffer */
730             p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
731             p_pic->p[V_PLANE].i_lines = i_height / 2;
732             p_pic->p[V_PLANE].i_pitch = i_width / 2;
733             p_pic->p[V_PLANE].i_pixel_pitch = 1;
734             p_pic->p[V_PLANE].i_visible_pitch = i_width / 2;
735
736             /* We allocated 3 planes */
737             p_pic->i_planes = 3;
738
739 #define P p_pic->p_sys->pixmap_i420
740             P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
741                                        - p_pic->p_sys->p_info;
742             P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
743                                         - p_pic->p_sys->p_info;
744             P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
745                                         - p_pic->p_sys->p_info;
746
747             P.componentInfoY.rowBytes = i_width;
748             P.componentInfoCb.rowBytes = i_width / 2;
749             P.componentInfoCr.rowBytes = i_width / 2;
750 #undef P
751
752             break;
753
754     default:
755         /* Unknown chroma, tell the guy to get lost */
756         free( p_pic->p_sys );
757         msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
758                  p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
759         p_pic->i_planes = 0;
760         return( -1 );
761     }
762
763     return( 0 );
764 }
765
766 /*****************************************************************************
767  * QTFreePicture: destroy a picture allocated with QTNewPicture
768  *****************************************************************************/
769 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
770 {
771     switch( p_vout->output.i_chroma )
772     {
773         case VLC_FOURCC('I','4','2','0'):
774             free( p_pic->p_data_orig );
775             break;
776     }
777
778     free( p_pic->p_sys );
779 }
780
781 /*****************************************************************************
782  * VLCWindow implementation
783  *****************************************************************************/
784 @implementation VLCWindow
785
786 - (void)setVout:(vout_thread_t *)_p_vout
787 {
788     p_vout = _p_vout;
789 }
790
791 - (vout_thread_t *)getVout
792 {
793     return( p_vout );
794 }
795
796 - (void)toggleFullscreen
797 {
798     p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
799 }
800
801 - (BOOL)isFullscreen
802 {
803     return( p_vout->b_fullscreen );
804 }
805
806 - (BOOL)canBecomeKeyWindow
807 {
808     return( YES );
809 }
810
811 - (void)keyDown:(NSEvent *)o_event
812 {
813     unichar key = 0;
814
815     if( [[o_event characters] length] )
816     {
817         key = [[o_event characters] characterAtIndex: 0];
818     }
819
820     switch( key )
821     {
822         case (unichar)0xf700: /* up-arrow */
823         { 
824             aout_instance_t * p_aout = vlc_object_find( p_vout, VLC_OBJECT_AOUT,
825                                                         FIND_ANYWHERE );
826             if ( p_aout != NULL ) 
827             {
828                 aout_VolumeUp( p_aout, 1, NULL );
829                 vlc_object_release( (vlc_object_t *)p_aout );
830             }
831         } 
832         break;
833
834         case (unichar)0xf701: /* down-arrow */
835         {
836             aout_instance_t * p_aout = vlc_object_find( p_vout, VLC_OBJECT_AOUT,
837                                                         FIND_ANYWHERE );
838             if ( p_aout != NULL ) 
839             {
840                 aout_VolumeDown( p_aout, 1, NULL );
841                 vlc_object_release( (vlc_object_t *)p_aout );
842             }
843         }
844         break;
845
846         case 'f': case 'F':
847             [self toggleFullscreen];
848             break;
849
850         case (unichar)0x1b: /* escape */
851             if( [self isFullscreen] )
852             {
853                 [self toggleFullscreen];
854             }
855             break;
856
857         case 'q': case 'Q':
858             p_vout->p_vlc->b_die = VLC_TRUE;
859             break;
860
861         case ' ':
862             input_SetStatus( p_vout, INPUT_STATUS_PAUSE );
863             break;
864
865         default:
866             [super keyDown: o_event];
867             break;
868     }
869 }
870
871 /* This is actually the same as VLCControls::stop. */
872 - (BOOL)windowShouldClose:(id)sender
873 {
874     intf_thread_t * p_intf = [NSApp getIntf];
875     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
876                                                        FIND_ANYWHERE );
877     if( p_playlist == NULL )      
878     {
879         return NO;
880     }
881
882     playlist_Stop( p_playlist );
883     vlc_object_release( p_playlist );
884     p_intf->p_sys->b_stopping = 1;
885
886     /* The window will be closed by the intf later. */
887     return NO;
888 }
889
890 @end
891
892 /*****************************************************************************
893  * VLCView implementation
894  *****************************************************************************/
895 @implementation VLCView
896
897 - (void)drawRect:(NSRect)rect
898 {
899     vout_thread_t * p_vout;
900     id o_window = [self window];
901     p_vout = (vout_thread_t *)[o_window getVout];
902
903     [[NSColor blackColor] set];
904     NSRectFill( rect );
905     [super drawRect: rect];
906
907     p_vout->i_changes |= VOUT_SIZE_CHANGE;
908 }
909
910 - (BOOL)acceptsFirstResponder
911 {
912     return( YES );
913 }
914
915 - (BOOL)becomeFirstResponder
916 {
917     [[self window] setAcceptsMouseMovedEvents: YES];
918     return( YES );
919 }
920
921 - (BOOL)resignFirstResponder
922 {
923     [[self window] setAcceptsMouseMovedEvents: NO];
924     return( YES );
925 }
926
927 - (void)mouseUp:(NSEvent *)o_event
928 {
929     vout_thread_t * p_vout;
930     id o_window = [self window];
931     p_vout = (vout_thread_t *)[o_window getVout];
932
933     switch( [o_event type] )
934     {
935         case NSLeftMouseUp:
936         {
937             vlc_value_t val;
938             val.b_bool = VLC_TRUE;
939             var_Set( p_vout, "mouse-clicked", val );        
940         }
941         break;
942
943         default:
944             [super mouseUp: o_event];
945         break;
946     }
947 }
948
949 - (void)mouseMoved:(NSEvent *)o_event
950 {
951     NSPoint ml;
952     NSRect s_rect;
953     BOOL b_inside;
954
955     vout_thread_t * p_vout;
956     id o_window = [self window];
957     p_vout = (vout_thread_t *)[o_window getVout];
958
959     s_rect = [self bounds];
960     ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
961     b_inside = [self mouse: ml inRect: s_rect];
962
963     if( b_inside )
964     {
965         vlc_value_t val;
966         int i_width, i_height, i_x, i_y;
967
968         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
969                                    (unsigned int)s_rect.size.height,
970                                    &i_x, &i_y, &i_width, &i_height );
971
972         val.i_int = ( ((int)ml.x) - i_x ) * 
973                     p_vout->render.i_width / i_width;
974         var_Set( p_vout, "mouse-x", val );
975
976         val.i_int = ( ((int)ml.y) - i_y ) * 
977                     p_vout->render.i_height / i_height;
978         var_Set( p_vout, "mouse-y", val );
979
980         val.b_bool = VLC_TRUE;
981         var_Set( p_vout, "mouse-moved", val );
982     }
983     else
984     {
985         [super mouseMoved: o_event];
986     }
987 }
988
989 @end
990
991 /*****************************************************************************
992  * VLCVout implementation
993  *****************************************************************************/
994 @implementation VLCVout
995
996 - (void)createWindow:(NSValue *)o_value
997 {
998     vlc_value_t val;
999     VLCView * o_view;
1000     NSScreen * o_screen;
1001     vout_thread_t * p_vout;
1002
1003     p_vout = (vout_thread_t *)[o_value pointerValue];
1004
1005     p_vout->p_sys->o_window = [VLCWindow alloc];
1006     [p_vout->p_sys->o_window setVout: p_vout];
1007     [p_vout->p_sys->o_window setReleasedWhenClosed: YES];
1008
1009     if( var_Get( p_vout, "video-device", &val ) < 0 )
1010     {
1011         o_screen = [NSScreen mainScreen];
1012     }
1013     else
1014     {
1015         unsigned int i_index = 0;
1016         NSArray *o_screens = [NSScreen screens];
1017
1018         if( !sscanf( val.psz_string, "Screen %d", &i_index ) ||
1019             [o_screens count] < i_index )
1020         {
1021             o_screen = [NSScreen mainScreen];
1022         }
1023         else
1024         {
1025             i_index--;
1026             o_screen = [o_screens objectAtIndex: i_index];
1027             config_PutInt( p_vout, "macosx-vdev", i_index );
1028         } 
1029
1030         free( val.psz_string );
1031     } 
1032
1033     if( p_vout->b_fullscreen )
1034     {
1035         NSRect screen_rect = [o_screen frame];
1036         screen_rect.origin.x = screen_rect.origin.y = 0;
1037
1038         [p_vout->p_sys->o_window 
1039             initWithContentRect: screen_rect
1040             styleMask: NSBorderlessWindowMask
1041             backing: NSBackingStoreBuffered
1042             defer: NO screen: o_screen];
1043
1044         [p_vout->p_sys->o_window setLevel: NSModalPanelWindowLevel];
1045     }
1046     else
1047     {
1048         unsigned int i_stylemask = NSTitledWindowMask |
1049                                    NSMiniaturizableWindowMask |
1050                                    NSClosableWindowMask |
1051                                    NSResizableWindowMask;
1052
1053         [p_vout->p_sys->o_window 
1054             initWithContentRect: p_vout->p_sys->s_rect
1055             styleMask: i_stylemask
1056             backing: NSBackingStoreBuffered
1057             defer: NO screen: o_screen];
1058
1059         if( !p_vout->p_sys->b_pos_saved )   
1060         {
1061             [p_vout->p_sys->o_window center];
1062         }
1063     }
1064
1065     o_view = [[VLCView alloc] init];
1066     /* FIXME: [o_view setMenu:] */
1067     [p_vout->p_sys->o_window setContentView: o_view];
1068     [o_view autorelease];
1069
1070     [o_view lockFocus];
1071     p_vout->p_sys->p_qdport = [o_view qdPort];
1072     [o_view unlockFocus];
1073
1074     [p_vout->p_sys->o_window setTitle:
1075         [NSString stringWithCString: VOUT_TITLE " (QuickTime)"]];
1076     [p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
1077 }
1078
1079 - (void)destroyWindow:(NSValue *)o_value
1080 {
1081     vout_thread_t * p_vout;
1082
1083     p_vout = (vout_thread_t *)[o_value pointerValue];
1084
1085     if( !p_vout->b_fullscreen )
1086     {
1087         NSRect s_rect;
1088
1089         s_rect = [[p_vout->p_sys->o_window contentView] frame];
1090         p_vout->p_sys->s_rect.size = s_rect.size;
1091
1092         s_rect = [p_vout->p_sys->o_window frame];
1093         p_vout->p_sys->s_rect.origin = s_rect.origin;
1094
1095         p_vout->p_sys->b_pos_saved = 1;
1096     }
1097
1098     p_vout->p_sys->p_qdport = nil;
1099     [p_vout->p_sys->o_window close];
1100     p_vout->p_sys->o_window = nil;
1101 }
1102
1103 @end