]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
* include/main.h
[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.48 2003/05/21 15:40:03 hartman 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  *          Derk-Jan Hartman <thedj@users.sourceforge.net>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  * 
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #include <errno.h>                                                 /* ENOMEM */
31 #include <stdlib.h>                                                /* free() */
32 #include <string.h>                                            /* strerror() */
33
34 #include <QuickTime/QuickTime.h>
35
36 #include "intf.h"
37 #include "vout.h"
38
39 #define QT_MAX_DIRECTBUFFERS 10
40 #define VL_MAX_DISPLAYS 16
41
42 struct picture_sys_t
43 {
44     void *p_info;
45     unsigned int i_size;
46
47     /* When using I420 output */
48     PlanarPixmapInfoYUV420 pixmap_i420;
49 };
50
51 /*****************************************************************************
52  * Local prototypes
53  *****************************************************************************/
54 static int  vout_Init      ( vout_thread_t * );
55 static void vout_End       ( vout_thread_t * );
56 static int  vout_Manage    ( vout_thread_t * );
57 static void vout_Display   ( vout_thread_t *, picture_t * );
58
59 static int  CoSendRequest      ( vout_thread_t *, SEL );
60 static int  CoCreateWindow     ( vout_thread_t * );
61 static int  CoDestroyWindow    ( vout_thread_t * );
62 static int  CoToggleFullscreen ( vout_thread_t * );
63
64 static void VLCHideMouse       ( vout_thread_t *, BOOL );
65
66 static void QTScaleMatrix      ( vout_thread_t * );
67 static int  QTCreateSequence   ( vout_thread_t * );
68 static void QTDestroySequence  ( vout_thread_t * );
69 static int  QTNewPicture       ( vout_thread_t *, picture_t * );
70 static void QTFreePicture      ( vout_thread_t *, picture_t * );
71
72 /*****************************************************************************
73  * OpenVideo: allocates MacOS X video thread output method
74  *****************************************************************************
75  * This function allocates and initializes a MacOS X vout method.
76  *****************************************************************************/
77 int E_(OpenVideo) ( vlc_object_t *p_this )
78 {   
79     vout_thread_t * p_vout = (vout_thread_t *)p_this;
80     OSErr err;
81     int i_timeout;
82
83     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
84     if( p_vout->p_sys == NULL )
85     {
86         msg_Err( p_vout, "out of memory" );
87         return( 1 );
88     }
89
90     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
91
92     /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
93     for( i_timeout = 20 ; i_timeout-- ; )
94     {
95         if( NSApp == NULL )
96         {
97             msleep( INTF_IDLE_SLEEP );
98         }
99     }
100
101     if( NSApp == NULL )
102     {
103         /* no MacOS X intf, unable to communicate with MT */
104         msg_Err( p_vout, "no MacOS X interface present" );
105         free( p_vout->p_sys );
106         return( 1 );
107     }
108
109     if( [NSApp respondsToSelector: @selector(getIntf)] )
110     {
111         intf_thread_t * p_intf;
112
113         for( i_timeout = 10 ; i_timeout-- ; )
114         {
115             if( ( p_intf = [NSApp getIntf] ) == NULL )
116             {
117                 msleep( INTF_IDLE_SLEEP );
118             }
119         }
120
121         if( p_intf == NULL )
122         {
123             msg_Err( p_vout, "MacOS X intf has getIntf, but is NULL" );
124             free( p_vout->p_sys );
125             return( 1 );
126         }
127     }
128
129     p_vout->p_sys->h_img_descr = 
130         (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
131     p_vout->p_sys->p_matrix = (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
132     p_vout->p_sys->p_fullscreen_state = NULL;
133
134     p_vout->p_sys->b_mouse_pointer_visible = YES;
135     p_vout->p_sys->b_mouse_moved = YES;
136     p_vout->p_sys->i_time_mouse_last_moved = mdate();
137
138     /* set window size */
139     p_vout->p_sys->s_rect.size.width = p_vout->i_window_width;
140     p_vout->p_sys->s_rect.size.height = p_vout->i_window_height;
141
142     if( ( err = EnterMovies() ) != noErr )
143     {
144         msg_Err( p_vout, "EnterMovies failed: %d", err );
145         free( p_vout->p_sys->p_matrix );
146         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
147         free( p_vout->p_sys );
148         return( 1 );
149     } 
150
151     if( vout_ChromaCmp( p_vout->render.i_chroma, VLC_FOURCC('I','4','2','0') ) )
152     {
153         /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
154         vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
155
156         err = FindCodec( kYUV420CodecType, bestSpeedCodec,
157                          nil, &p_vout->p_sys->img_dc );
158         
159         vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
160         if( err == noErr && p_vout->p_sys->img_dc != 0 )
161         {
162             p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
163             p_vout->p_sys->i_codec = kYUV420CodecType;
164         }
165         else
166         {
167             msg_Err( p_vout, "failed to find an appropriate codec" );
168         }
169     }
170     else
171     {
172         msg_Err( p_vout, "chroma 0x%08x not supported",
173                          p_vout->render.i_chroma );
174     }
175
176     if( p_vout->p_sys->img_dc == 0 )
177     {
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     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
185     NSArray * o_screens = [NSScreen screens];
186     if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
187     {
188         int i = 1;
189         vlc_value_t val, text;
190         NSScreen * o_screen;
191
192         int i_option = config_GetInt( p_vout, "macosx-vdev" );
193
194         var_Create( p_vout, "video-device", VLC_VAR_INTEGER |
195                                             VLC_VAR_HASCHOICE ); 
196         text.psz_string = _("Video device");
197         var_Change( p_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
198         
199         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
200
201         while( (o_screen = [o_enumerator nextObject]) != NULL )
202         {
203             char psz_temp[255];
204             NSRect s_rect = [o_screen frame];
205
206             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1, 
207                       "%s %d (%dx%d)", _("Screen"), i,
208                       (int)s_rect.size.width, (int)s_rect.size.height ); 
209
210             text.psz_string = psz_temp;
211             val.i_int = i;
212             var_Change( p_vout, "video-device",
213                         VLC_VAR_ADDCHOICE, &val, &text );
214
215             if( ( i - 1 ) == i_option )
216             {
217                 var_Set( p_vout, "video-device", val );
218             }
219             i++;
220         }
221
222         var_AddCallback( p_vout, "video-device", vout_VarCallback,
223                          NULL );
224
225         val.b_bool = VLC_TRUE;
226         var_Set( p_vout, "intf-change", val );
227     }
228     [o_pool release];
229
230     if( CoCreateWindow( p_vout ) )
231     {
232         msg_Err( p_vout, "unable to create window" );
233         free( p_vout->p_sys->p_matrix );
234         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
235         free( p_vout->p_sys ); 
236         return( 1 );
237     }
238
239     p_vout->pf_init = vout_Init;
240     p_vout->pf_end = vout_End;
241     p_vout->pf_manage = vout_Manage;
242     p_vout->pf_render = NULL;
243     p_vout->pf_display = vout_Display;
244
245     return( 0 );
246 }
247
248 /*****************************************************************************
249  * vout_Init: initialize video thread output method
250  *****************************************************************************/
251 static int vout_Init( vout_thread_t *p_vout )
252 {
253     int i_index;
254     picture_t *p_pic;
255
256     I_OUTPUTPICTURES = 0;
257
258     /* Initialize the output structure; we already found a codec,
259      * and the corresponding chroma we will be using. Since we can
260      * arbitrary scale, stick to the coordinates and aspect. */
261     p_vout->output.i_width  = p_vout->render.i_width;
262     p_vout->output.i_height = p_vout->render.i_height;
263     p_vout->output.i_aspect = p_vout->render.i_aspect;
264
265     SetPort( p_vout->p_sys->p_qdport );
266     QTScaleMatrix( p_vout );
267
268     if( QTCreateSequence( p_vout ) )
269     {
270         msg_Err( p_vout, "unable to create sequence" );
271         return( 1 );
272     }
273
274     /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
275     while( I_OUTPUTPICTURES < QT_MAX_DIRECTBUFFERS )
276     {
277         p_pic = NULL;
278
279         /* Find an empty picture slot */
280         for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
281         {
282             if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
283             {
284                 p_pic = p_vout->p_picture + i_index;
285                 break;
286             }
287         }
288
289         /* Allocate the picture */
290         if( p_pic == NULL || QTNewPicture( p_vout, p_pic ) )
291         {
292             break;
293         }
294
295         p_pic->i_status = DESTROYED_PICTURE;
296         p_pic->i_type   = DIRECT_PICTURE;
297
298         PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
299
300         I_OUTPUTPICTURES++;
301     }
302
303     return( 0 );
304 }
305
306 /*****************************************************************************
307  * vout_End: terminate video thread output method
308  *****************************************************************************/
309 static void vout_End( vout_thread_t *p_vout )
310 {
311     int i_index;
312
313     QTDestroySequence( p_vout );
314
315     /* Free the direct buffers we allocated */
316     for( i_index = I_OUTPUTPICTURES; i_index; )
317     {
318         i_index--;
319         QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
320     }
321 }
322
323 /*****************************************************************************
324  * CloseVideo: destroy video thread output method
325  *****************************************************************************/
326 void E_(CloseVideo) ( vlc_object_t *p_this )
327 {       
328     vout_thread_t * p_vout = (vout_thread_t *)p_this;     
329
330     if( CoDestroyWindow( p_vout ) )
331     {
332         msg_Err( p_vout, "unable to destroy window" );
333     }
334
335     if ( p_vout->p_sys->p_fullscreen_state != NULL )
336         EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
337
338     ExitMovies();
339
340     free( p_vout->p_sys->p_matrix );
341     DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
342
343     free( p_vout->p_sys );
344 }
345
346 /*****************************************************************************
347  * vout_Manage: handle events
348  *****************************************************************************
349  * This function should be called regularly by video output thread. It manages
350  * console events. It returns a non null value on error.
351  *****************************************************************************/
352 static int vout_Manage( vout_thread_t *p_vout )
353 {
354     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
355     {
356         if( CoToggleFullscreen( p_vout ) )  
357         {
358             return( 1 );
359         }
360
361         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
362     }
363
364     if( p_vout->i_changes & VOUT_SIZE_CHANGE ) 
365     {
366         QTScaleMatrix( p_vout );
367         SetDSequenceMatrix( p_vout->p_sys->i_seq, 
368                             p_vout->p_sys->p_matrix );
369  
370         p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
371     }
372
373     /* hide/show mouse cursor 
374      * this code looks unnecessarily complicated, but is necessary like this.
375      * it has to deal with multiple monitors and therefore checks a lot */
376     if( !p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
377     {
378         if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 2000000 && 
379                    p_vout->p_sys->b_mouse_pointer_visible )
380         {
381             VLCHideMouse( p_vout, YES );
382         }
383         else if ( !p_vout->p_sys->b_mouse_pointer_visible )
384         {
385             vlc_bool_t b_playing = NO;
386             playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
387                                                                     FIND_ANYWHERE );
388
389             if ( p_playlist != nil )
390             {
391                 vlc_mutex_lock( &p_playlist->object_lock );
392                 if( p_playlist->p_input != NULL )
393                 {
394                     vlc_mutex_lock( &p_playlist->p_input->stream.stream_lock );
395                     b_playing = p_playlist->p_input->stream.control.i_status != PAUSE_S;
396                     vlc_mutex_unlock( &p_playlist->p_input->stream.stream_lock );
397                 }
398                 vlc_mutex_unlock( &p_playlist->object_lock );
399                 vlc_object_release( p_playlist );
400             }
401             if ( !b_playing )
402             {
403                 VLCHideMouse( p_vout, NO );
404             }
405         }
406     }
407     else if ( p_vout->p_sys->b_mouse_moved && p_vout->b_fullscreen )
408     {
409         if( !p_vout->p_sys->b_mouse_pointer_visible )
410         {
411             VLCHideMouse( p_vout, NO );
412         }
413         else
414         {
415             p_vout->p_sys->b_mouse_moved = NO;
416         }
417     }
418     
419     return( 0 );
420 }
421
422 /*****************************************************************************
423  * vout_Display: displays previously rendered output
424  *****************************************************************************
425  * This function sends the currently rendered image to the display.
426  *****************************************************************************/
427 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
428 {
429     OSErr err;
430     CodecFlags flags;
431
432     if( ( err = DecompressSequenceFrameS( 
433                     p_vout->p_sys->i_seq,
434                     p_pic->p_sys->p_info,
435                     p_pic->p_sys->i_size,                    
436                     codecFlagUseImageBuffer, &flags, nil ) != noErr ) )
437     {
438         msg_Warn( p_vout, "DecompressSequenceFrameS failed: %d", err );
439     }
440     else
441     {
442         QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
443     }
444 }
445
446 /*****************************************************************************
447  * CoSendRequest: send request to interface thread
448  *****************************************************************************
449  * Returns 0 on success, 1 otherwise
450  *****************************************************************************/
451 static int CoSendRequest( vout_thread_t *p_vout, SEL sel )
452 {
453     int i_ret = 0;
454
455     VLCVout * o_vlv = [[VLCVout alloc] init];
456
457     if( ( i_ret = ExecuteOnMainThread( o_vlv, sel, (void *)p_vout ) ) )
458     {
459         msg_Err( p_vout, "SendRequest: no way to communicate with mt" );
460     }
461
462     [o_vlv release];
463
464     return( i_ret );
465 }
466
467 /*****************************************************************************
468  * CoCreateWindow: create new window 
469  *****************************************************************************
470  * Returns 0 on success, 1 otherwise
471  *****************************************************************************/
472 static int CoCreateWindow( vout_thread_t *p_vout )
473 {
474     if( CoSendRequest( p_vout, @selector(createWindow:) ) )
475     {
476         msg_Err( p_vout, "CoSendRequest (createWindow) failed" );
477         return( 1 );
478     }
479
480     return( 0 );
481 }
482
483 /*****************************************************************************
484  * CoDestroyWindow: destroy window 
485  *****************************************************************************
486  * Returns 0 on success, 1 otherwise
487  *****************************************************************************/
488 static int CoDestroyWindow( vout_thread_t *p_vout )
489 {
490     if( !p_vout->p_sys->b_mouse_pointer_visible )
491     {
492         VLCHideMouse( p_vout, NO );
493     }
494
495     if( CoSendRequest( p_vout, @selector(destroyWindow:) ) )
496     {
497         msg_Err( p_vout, "CoSendRequest (destroyWindow) failed" );
498         return( 1 );
499     }
500
501     return( 0 );
502 }
503
504 /*****************************************************************************
505  * CoToggleFullscreen: toggle fullscreen 
506  *****************************************************************************
507  * Returns 0 on success, 1 otherwise
508  *****************************************************************************/
509 static int CoToggleFullscreen( vout_thread_t *p_vout )
510 {
511     QTDestroySequence( p_vout );
512
513     if( CoDestroyWindow( p_vout ) )
514     {
515         msg_Err( p_vout, "unable to destroy window" );
516         return( 1 );
517     }
518     
519     p_vout->b_fullscreen = !p_vout->b_fullscreen;
520
521     config_PutInt( p_vout, "fullscreen", p_vout->b_fullscreen );
522
523     if( CoCreateWindow( p_vout ) )
524     {
525         msg_Err( p_vout, "unable to create window" );
526         return( 1 );
527     }
528
529     SetPort( p_vout->p_sys->p_qdport );
530     QTScaleMatrix( p_vout );
531
532     if( QTCreateSequence( p_vout ) )
533     {
534         msg_Err( p_vout, "unable to create sequence" );
535         return( 1 ); 
536     } 
537
538     return( 0 );
539 }
540
541 /*****************************************************************************
542  * VLCHideMouse: if b_hide then hide the cursor
543  *****************************************************************************/
544 static void VLCHideMouse ( vout_thread_t *p_vout, BOOL b_hide )
545 {
546     BOOL b_inside;
547     NSRect s_rect;
548     NSPoint ml;
549     NSWindow *o_window = p_vout->p_sys->o_window;
550     NSView *o_contents = [o_window contentView];
551     
552     s_rect = [o_contents bounds];
553     ml = [o_window convertScreenToBase:[NSEvent mouseLocation]];
554     ml = [o_contents convertPoint:ml fromView:nil];
555     b_inside = [o_contents mouse: ml inRect: s_rect];
556     
557     if ( b_hide && b_inside )
558     {
559         /* only hide if mouse over VLCView */
560         [NSCursor hide];
561         p_vout->p_sys->b_mouse_pointer_visible = 0;
562     }
563     else if ( !b_hide )
564     {
565         [NSCursor unhide];
566         p_vout->p_sys->b_mouse_pointer_visible = 1;
567     }
568     p_vout->p_sys->b_mouse_moved = NO;
569     p_vout->p_sys->i_time_mouse_last_moved = mdate();
570     return;
571 }
572
573 /*****************************************************************************
574  * QTScaleMatrix: scale matrix 
575  *****************************************************************************/
576 static void QTScaleMatrix( vout_thread_t *p_vout )
577 {
578     Rect s_rect;
579     unsigned int i_width, i_height;
580     Fixed factor_x, factor_y;
581     unsigned int i_offset_x = 0;
582     unsigned int i_offset_y = 0;
583
584     GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
585
586     i_width = s_rect.right - s_rect.left;
587     i_height = s_rect.bottom - s_rect.top;
588
589     if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
590     {
591         int i_adj_width = i_height * p_vout->output.i_aspect /
592                           VOUT_ASPECT_FACTOR;
593
594         factor_x = FixDiv( Long2Fix( i_adj_width ),
595                            Long2Fix( p_vout->output.i_width ) );
596         factor_y = FixDiv( Long2Fix( i_height ),
597                            Long2Fix( p_vout->output.i_height ) );
598
599         i_offset_x = (i_width - i_adj_width) / 2;
600     }
601     else
602     {
603         int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
604                            p_vout->output.i_aspect;
605
606         factor_x = FixDiv( Long2Fix( i_width ),
607                            Long2Fix( p_vout->output.i_width ) );
608         factor_y = FixDiv( Long2Fix( i_adj_height ),
609                            Long2Fix( p_vout->output.i_height ) );
610
611         i_offset_y = (i_height - i_adj_height) / 2;
612     }
613     
614     SetIdentityMatrix( p_vout->p_sys->p_matrix );
615
616     ScaleMatrix( p_vout->p_sys->p_matrix,
617                  factor_x, factor_y,
618                  Long2Fix(0), Long2Fix(0) );            
619
620     TranslateMatrix( p_vout->p_sys->p_matrix, 
621                      Long2Fix(i_offset_x), 
622                      Long2Fix(i_offset_y) );
623 }
624
625 /*****************************************************************************
626  * QTCreateSequence: create a new sequence 
627  *****************************************************************************
628  * Returns 0 on success, 1 otherwise
629  *****************************************************************************/
630 static int QTCreateSequence( vout_thread_t *p_vout )
631 {
632     OSErr err;
633     ImageDescriptionPtr p_descr;
634
635     HLock( (Handle)p_vout->p_sys->h_img_descr );
636     p_descr = *p_vout->p_sys->h_img_descr;
637
638     p_descr->idSize = sizeof(ImageDescription);
639     p_descr->cType = p_vout->p_sys->i_codec;
640     p_descr->version = 1;
641     p_descr->revisionLevel = 0;
642     p_descr->vendor = 'appl';
643     p_descr->width = p_vout->output.i_width;
644     p_descr->height = p_vout->output.i_height;
645     p_descr->hRes = Long2Fix(72);
646     p_descr->vRes = Long2Fix(72);
647     p_descr->spatialQuality = codecLosslessQuality;
648     p_descr->frameCount = 1;
649     p_descr->clutID = -1;
650     p_descr->dataSize = 0;
651     p_descr->depth = 24;
652
653     HUnlock( (Handle)p_vout->p_sys->h_img_descr );
654
655     if( ( err = DecompressSequenceBeginS( 
656                               &p_vout->p_sys->i_seq,
657                               p_vout->p_sys->h_img_descr,
658                               NULL, 0,
659                               p_vout->p_sys->p_qdport,
660                               NULL, NULL,
661                               p_vout->p_sys->p_matrix,
662                               0, NULL,
663                               codecFlagUseImageBuffer,
664                               codecLosslessQuality,
665                               p_vout->p_sys->img_dc ) ) )
666     {
667         msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
668         return( 1 );
669     }
670
671     return( 0 );
672 }
673
674 /*****************************************************************************
675  * QTDestroySequence: destroy sequence 
676  *****************************************************************************/
677 static void QTDestroySequence( vout_thread_t *p_vout )
678 {
679     CDSequenceEnd( p_vout->p_sys->i_seq );
680 }
681
682 /*****************************************************************************
683  * QTNewPicture: allocate a picture
684  *****************************************************************************
685  * Returns 0 on success, 1 otherwise
686  *****************************************************************************/
687 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
688 {
689     int i_width  = p_vout->output.i_width;
690     int i_height = p_vout->output.i_height;
691
692     /* We know the chroma, allocate a buffer which will be used
693      * directly by the decoder */
694     p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
695
696     if( p_pic->p_sys == NULL )
697     {
698         return( -1 );
699     }
700
701     switch( p_vout->output.i_chroma )
702     {
703         case VLC_FOURCC('I','4','2','0'):
704
705             p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
706             p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
707
708             /* Allocate the memory buffer */
709             p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
710                                           16, i_width * i_height * 3 / 2 );
711
712             /* Y buffer */
713             p_pic->Y_PIXELS = p_pic->p_data; 
714             p_pic->p[Y_PLANE].i_lines = i_height;
715             p_pic->p[Y_PLANE].i_pitch = i_width;
716             p_pic->p[Y_PLANE].i_pixel_pitch = 1;
717             p_pic->p[Y_PLANE].i_visible_pitch = i_width;
718
719             /* U buffer */
720             p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
721             p_pic->p[U_PLANE].i_lines = i_height / 2;
722             p_pic->p[U_PLANE].i_pitch = i_width / 2;
723             p_pic->p[U_PLANE].i_pixel_pitch = 1;
724             p_pic->p[U_PLANE].i_visible_pitch = i_width / 2;
725
726             /* V buffer */
727             p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
728             p_pic->p[V_PLANE].i_lines = i_height / 2;
729             p_pic->p[V_PLANE].i_pitch = i_width / 2;
730             p_pic->p[V_PLANE].i_pixel_pitch = 1;
731             p_pic->p[V_PLANE].i_visible_pitch = i_width / 2;
732
733             /* We allocated 3 planes */
734             p_pic->i_planes = 3;
735
736 #define P p_pic->p_sys->pixmap_i420
737             P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
738                                        - p_pic->p_sys->p_info;
739             P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
740                                         - p_pic->p_sys->p_info;
741             P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
742                                         - p_pic->p_sys->p_info;
743
744             P.componentInfoY.rowBytes = i_width;
745             P.componentInfoCb.rowBytes = i_width / 2;
746             P.componentInfoCr.rowBytes = i_width / 2;
747 #undef P
748
749             break;
750
751     default:
752         /* Unknown chroma, tell the guy to get lost */
753         free( p_pic->p_sys );
754         msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
755                  p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
756         p_pic->i_planes = 0;
757         return( -1 );
758     }
759
760     return( 0 );
761 }
762
763 /*****************************************************************************
764  * QTFreePicture: destroy a picture allocated with QTNewPicture
765  *****************************************************************************/
766 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
767 {
768     switch( p_vout->output.i_chroma )
769     {
770         case VLC_FOURCC('I','4','2','0'):
771             free( p_pic->p_data_orig );
772             break;
773     }
774
775     free( p_pic->p_sys );
776 }
777
778 /*****************************************************************************
779  * VLCWindow implementation
780  *****************************************************************************/
781 @implementation VLCWindow
782
783 - (void)setVout:(vout_thread_t *)_p_vout
784 {
785     p_vout = _p_vout;
786 }
787
788 - (vout_thread_t *)getVout
789 {
790     return( p_vout );
791 }
792
793 - (void)scaleWindowWithFactor: (float)factor
794 {
795     NSSize newsize;
796     int i_corrected_height, i_corrected_width;
797     NSPoint topleftbase;
798     NSPoint topleftscreen;
799     
800     if ( !p_vout->b_fullscreen )
801     {
802         topleftbase.x = 0;
803         topleftbase.y = [self frame].size.height;
804         topleftscreen = [self convertBaseToScreen: topleftbase];
805         
806         if( p_vout->output.i_height * p_vout->output.i_aspect > 
807                         p_vout->output.i_width * VOUT_ASPECT_FACTOR )
808         {
809             i_corrected_width = p_vout->output.i_height * p_vout->output.i_aspect /
810                                             VOUT_ASPECT_FACTOR;
811             newsize.width = (int) ( i_corrected_width * factor );
812             newsize.height = (int) ( p_vout->render.i_height * factor );
813         }
814         else
815         {
816             i_corrected_height = p_vout->output.i_width * VOUT_ASPECT_FACTOR /
817                                             p_vout->output.i_aspect;
818             newsize.width = (int) ( p_vout->render.i_width * factor );
819             newsize.height = (int) ( i_corrected_height * factor );
820         }
821     
822         [self setContentSize: newsize];
823         
824         [self setFrameTopLeftPoint: topleftscreen];
825         p_vout->i_changes |= VOUT_SIZE_CHANGE;
826     }
827 }
828
829 - (void)toggleFloatOnTop
830 {
831     if( config_GetInt( p_vout, "macosx-float" ) )
832     {
833         config_PutInt( p_vout, "macosx-float", 0 );
834         [p_vout->p_sys->o_window setLevel: NSNormalWindowLevel];
835     }
836     else
837     {
838         config_PutInt( p_vout, "macosx-float", 1 );
839         [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
840     }
841 }
842
843 - (void)toggleFullscreen
844 {
845     p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
846 }
847
848 - (BOOL)isFullscreen
849 {
850     return( p_vout->b_fullscreen );
851 }
852
853 - (BOOL)canBecomeKeyWindow
854 {
855     return( YES );
856 }
857
858 - (void)keyDown:(NSEvent *)o_event
859 {
860     unichar key = 0;
861
862     if( [[o_event characters] length] )
863     {
864         key = [[o_event characters] characterAtIndex: 0];
865     }
866
867     switch( key )
868     {
869         case 'f': case 'F':
870             [self toggleFullscreen];
871             break;
872
873         case (unichar)0x1b: /* escape */
874             if( [self isFullscreen] )
875             {
876                 [self toggleFullscreen];
877             }
878             break;
879
880         case 'q': case 'Q':
881             p_vout->p_vlc->b_die = VLC_TRUE;
882             break;
883
884         case ' ':
885             input_SetStatus( p_vout, INPUT_STATUS_PAUSE );
886             break;
887
888         default:
889             [super keyDown: o_event];
890             break;
891     }
892 }
893
894 - (void)updateTitle
895 {
896     NSMutableString * o_title;
897     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
898                                                        FIND_ANYWHERE );
899     
900     if( p_playlist == NULL )
901     {
902         return;
903     }
904
905     vlc_mutex_lock( &p_playlist->object_lock );
906     o_title = [NSMutableString stringWithUTF8String: 
907         p_playlist->pp_items[p_playlist->i_index]->psz_name]; 
908     vlc_mutex_unlock( &p_playlist->object_lock );
909
910     vlc_object_release( p_playlist );
911
912     if( o_title != nil )
913     {
914         NSRange prefix_range = [o_title rangeOfString: @"file:"];
915         if( prefix_range.location != NSNotFound )
916         {
917             [o_title deleteCharactersInRange: prefix_range];
918         }
919
920         [self setTitleWithRepresentedFilename: o_title];
921     }
922     else
923     {
924         [self setTitle:
925             [NSString stringWithCString: VOUT_TITLE " (QuickTime)"]];
926     }
927 }
928
929 /* This is actually the same as VLCControls::stop. */
930 - (BOOL)windowShouldClose:(id)sender
931 {
932     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
933                                                        FIND_ANYWHERE );
934     if( p_playlist == NULL )      
935     {
936         return NO;
937     }
938
939     playlist_Stop( p_playlist );
940     vlc_object_release( p_playlist );
941
942     /* The window will be closed by the intf later. */
943     return NO;
944 }
945
946 @end
947
948 /*****************************************************************************
949  * VLCView implementation
950  *****************************************************************************/
951 @implementation VLCView
952
953 - (void)drawRect:(NSRect)rect
954 {
955     vout_thread_t * p_vout;
956     id o_window = [self window];
957     p_vout = (vout_thread_t *)[o_window getVout];
958     
959     [[NSColor blackColor] set];
960     NSRectFill( rect );
961     [super drawRect: rect];
962
963     p_vout->i_changes |= VOUT_SIZE_CHANGE;
964 }
965
966 - (BOOL)acceptsFirstResponder
967 {
968     return( YES );
969 }
970
971 - (BOOL)becomeFirstResponder
972 {
973     vout_thread_t * p_vout;
974     id o_window = [self window];
975     p_vout = (vout_thread_t *)[o_window getVout];
976     
977     [o_window setAcceptsMouseMovedEvents: YES];
978     return( YES );
979 }
980
981 - (BOOL)resignFirstResponder
982 {
983     vout_thread_t * p_vout;
984     id o_window = [self window];
985     p_vout = (vout_thread_t *)[o_window getVout];
986     
987     [o_window setAcceptsMouseMovedEvents: NO];
988     VLCHideMouse( p_vout, NO );
989     return( YES );
990 }
991
992 - (void)mouseDown:(NSEvent *)o_event
993 {
994     vout_thread_t * p_vout;
995     id o_window = [self window];
996     p_vout = (vout_thread_t *)[o_window getVout];
997     vlc_value_t val;
998
999     switch( [o_event type] )
1000     {        
1001         case NSLeftMouseDown:
1002         {
1003             var_Get( p_vout, "mouse-button-down", &val );
1004             val.i_int |= 1;
1005             var_Set( p_vout, "mouse-button-down", val );
1006         }
1007         break;
1008         
1009         default:
1010             [super mouseDown: o_event];
1011         break;
1012     }
1013 }
1014
1015 - (void)otherMouseDown:(NSEvent *)o_event
1016 {
1017     /* This is not the the wheel button. you need to poll the
1018      * mouseWheel event for that. other is a third, forth or fifth button */
1019     vout_thread_t * p_vout;
1020     id o_window = [self window];
1021     p_vout = (vout_thread_t *)[o_window getVout];
1022     vlc_value_t val;
1023
1024     switch( [o_event type] )
1025     {
1026         case NSOtherMouseDown:
1027         {
1028             var_Get( p_vout, "mouse-button-down", &val );
1029             val.i_int |= 2;
1030             var_Set( p_vout, "mouse-button-down", val );
1031         }
1032         break;
1033         
1034         default:
1035             [super mouseDown: o_event];
1036         break;
1037     }
1038 }
1039
1040 - (void)rightMouseDown:(NSEvent *)o_event
1041 {
1042     vout_thread_t * p_vout;
1043     id o_window = [self window];
1044     p_vout = (vout_thread_t *)[o_window getVout];
1045     vlc_value_t val;
1046
1047     switch( [o_event type] )
1048     {
1049         case NSRightMouseDown:
1050         {
1051             var_Get( p_vout, "mouse-button-down", &val );
1052             val.i_int |= 4;
1053             var_Set( p_vout, "mouse-button-down", val );
1054         }
1055         break;
1056         
1057         default:
1058             [super mouseDown: o_event];
1059         break;
1060     }
1061 }
1062
1063 - (void)mouseUp:(NSEvent *)o_event
1064 {
1065     vout_thread_t * p_vout;
1066     id o_window = [self window];
1067     p_vout = (vout_thread_t *)[o_window getVout];
1068     vlc_value_t val;
1069
1070     switch( [o_event type] )
1071     {
1072         case NSLeftMouseUp:
1073         {
1074             vlc_value_t b_val;
1075             b_val.b_bool = VLC_TRUE;
1076             var_Set( p_vout, "mouse-clicked", b_val );
1077             
1078             var_Get( p_vout, "mouse-button-down", &val );
1079             val.i_int &= ~1;
1080             var_Set( p_vout, "mouse-button-down", val );
1081         }
1082         break;
1083                 
1084         default:
1085             [super mouseUp: o_event];
1086         break;
1087     }
1088 }
1089
1090 - (void)otherMouseUp:(NSEvent *)o_event
1091 {
1092     vout_thread_t * p_vout;
1093     id o_window = [self window];
1094     p_vout = (vout_thread_t *)[o_window getVout];
1095     vlc_value_t val;
1096
1097     switch( [o_event type] )
1098     {
1099         case NSOtherMouseUp:
1100         {
1101             var_Get( p_vout, "mouse-button-down", &val );
1102             val.i_int &= ~2;
1103             var_Set( p_vout, "mouse-button-down", val );
1104         }
1105         break;
1106                 
1107         default:
1108             [super mouseUp: o_event];
1109         break;
1110     }
1111 }
1112
1113 - (void)rightMouseUp:(NSEvent *)o_event
1114 {
1115     vout_thread_t * p_vout;
1116     id o_window = [self window];
1117     p_vout = (vout_thread_t *)[o_window getVout];
1118     vlc_value_t val;
1119
1120     switch( [o_event type] )
1121     {
1122         case NSRightMouseUp:
1123         {
1124             var_Get( p_vout, "mouse-button-down", &val );
1125             val.i_int &= ~4;
1126             var_Set( p_vout, "mouse-button-down", val );
1127         }
1128         break;
1129         
1130         default:
1131             [super mouseUp: o_event];
1132         break;
1133     }
1134 }
1135
1136 - (void)mouseDragged:(NSEvent *)o_event
1137 {
1138     [self mouseMoved:o_event];
1139 }
1140
1141 - (void)otherMouseDragged:(NSEvent *)o_event
1142 {
1143     [self mouseMoved:o_event];
1144 }
1145
1146 - (void)rightMouseDragged:(NSEvent *)o_event
1147 {
1148     [self mouseMoved:o_event];
1149 }
1150
1151 - (void)mouseMoved:(NSEvent *)o_event
1152 {
1153     NSPoint ml;
1154     NSRect s_rect;
1155     BOOL b_inside;
1156
1157     vout_thread_t * p_vout;
1158     id o_window = [self window];
1159     p_vout = (vout_thread_t *)[o_window getVout];
1160
1161     s_rect = [self bounds];
1162     ml = [self convertPoint: [o_event locationInWindow] fromView: nil];
1163     b_inside = [self mouse: ml inRect: s_rect];
1164
1165     if( b_inside )
1166     {
1167         vlc_value_t val;
1168         int i_width, i_height, i_x, i_y;
1169         
1170         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
1171                                    (unsigned int)s_rect.size.height,
1172                                    &i_x, &i_y, &i_width, &i_height );
1173
1174         val.i_int = ( ((int)ml.x) - i_x ) * 
1175                     p_vout->render.i_width / i_width;
1176         var_Set( p_vout, "mouse-x", val );
1177
1178         val.i_int = ( ((int)ml.y) - i_y ) * 
1179                     p_vout->render.i_height / i_height;
1180         var_Set( p_vout, "mouse-y", val );
1181             
1182         val.b_bool = VLC_TRUE;
1183         var_Set( p_vout, "mouse-moved", val );
1184         p_vout->p_sys->i_time_mouse_last_moved = mdate();
1185         p_vout->p_sys->b_mouse_moved = YES;
1186     }
1187     else if ( !b_inside && !p_vout->p_sys->b_mouse_pointer_visible )
1188     {
1189         /* people with multiple monitors need their mouse,
1190          * even if VLCView in fullscreen. */
1191         VLCHideMouse( p_vout, NO );
1192     }
1193     
1194     [super mouseMoved: o_event];
1195 }
1196
1197 @end
1198
1199 /*****************************************************************************
1200  * VLCVout implementation
1201  *****************************************************************************/
1202 @implementation VLCVout
1203
1204 - (void)createWindow:(NSValue *)o_value
1205 {
1206     vlc_value_t val;
1207     VLCView * o_view;
1208     NSScreen * o_screen;
1209     vout_thread_t * p_vout;
1210     vlc_bool_t b_main_screen;
1211     
1212     p_vout = (vout_thread_t *)[o_value pointerValue];
1213
1214     p_vout->p_sys->o_window = [VLCWindow alloc];
1215     [p_vout->p_sys->o_window setVout: p_vout];
1216     [p_vout->p_sys->o_window setReleasedWhenClosed: YES];
1217
1218     if( var_Get( p_vout, "video-device", &val ) < 0 )
1219     {
1220         o_screen = [NSScreen mainScreen];
1221         b_main_screen = 1;
1222     }
1223     else
1224     {
1225         NSArray *o_screens = [NSScreen screens];
1226         unsigned int i_index = val.i_int;
1227         
1228         if( [o_screens count] < i_index )
1229         {
1230             o_screen = [NSScreen mainScreen];
1231             b_main_screen = 1;
1232         }
1233         else
1234         {
1235             i_index--;
1236             o_screen = [o_screens objectAtIndex: i_index];
1237             config_PutInt( p_vout, "macosx-vdev", i_index );
1238             b_main_screen = (i_index == 0);
1239         }
1240     } 
1241
1242     if( p_vout->b_fullscreen )
1243     {
1244         NSRect screen_rect = [o_screen frame];
1245         screen_rect.origin.x = screen_rect.origin.y = 0;
1246
1247         if ( b_main_screen && p_vout->p_sys->p_fullscreen_state == NULL )
1248             BeginFullScreen( &p_vout->p_sys->p_fullscreen_state, NULL, 0, 0,
1249                              NULL, NULL, fullScreenAllowEvents );
1250
1251         [p_vout->p_sys->o_window 
1252             initWithContentRect: screen_rect
1253             styleMask: NSBorderlessWindowMask
1254             backing: NSBackingStoreBuffered
1255             defer: NO screen: o_screen];
1256
1257         [p_vout->p_sys->o_window setLevel: NSPopUpMenuWindowLevel - 1];
1258         p_vout->p_sys->b_mouse_moved = YES;
1259         p_vout->p_sys->i_time_mouse_last_moved = mdate();
1260     }
1261     else
1262     {
1263         unsigned int i_stylemask = NSTitledWindowMask |
1264                                    NSMiniaturizableWindowMask |
1265                                    NSClosableWindowMask |
1266                                    NSResizableWindowMask;
1267         
1268         if ( p_vout->p_sys->p_fullscreen_state != NULL )
1269             EndFullScreen ( p_vout->p_sys->p_fullscreen_state, NULL );
1270         p_vout->p_sys->p_fullscreen_state = NULL;
1271
1272         [p_vout->p_sys->o_window 
1273             initWithContentRect: p_vout->p_sys->s_rect
1274             styleMask: i_stylemask
1275             backing: NSBackingStoreBuffered
1276             defer: NO screen: o_screen];
1277
1278         [p_vout->p_sys->o_window setAlphaValue: config_GetFloat( p_vout, "macosx-opaqueness" )];
1279         
1280         if( config_GetInt( p_vout, "macosx-float" ) )
1281         {
1282             [p_vout->p_sys->o_window setLevel: NSStatusWindowLevel];
1283         }
1284         
1285         if( !p_vout->p_sys->b_pos_saved )   
1286         {
1287             [p_vout->p_sys->o_window center];
1288         }
1289     }
1290
1291     o_view = [[VLCView alloc] init];
1292     /* FIXME: [o_view setMenu:] */
1293     [p_vout->p_sys->o_window setContentView: o_view];
1294     [o_view autorelease];
1295
1296     [o_view lockFocus];
1297     p_vout->p_sys->p_qdport = [o_view qdPort];
1298     [o_view unlockFocus];
1299     
1300     [p_vout->p_sys->o_window updateTitle];
1301     [p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
1302 }
1303
1304 - (void)destroyWindow:(NSValue *)o_value
1305 {
1306     vout_thread_t * p_vout;
1307
1308     p_vout = (vout_thread_t *)[o_value pointerValue];
1309
1310     if( !p_vout->b_fullscreen )
1311     {
1312         NSRect s_rect;
1313
1314         s_rect = [[p_vout->p_sys->o_window contentView] frame];
1315         p_vout->p_sys->s_rect.size = s_rect.size;
1316
1317         s_rect = [p_vout->p_sys->o_window frame];
1318         p_vout->p_sys->s_rect.origin = s_rect.origin;
1319
1320         p_vout->p_sys->b_pos_saved = YES;
1321     }
1322     
1323     p_vout->p_sys->p_qdport = nil;
1324     [p_vout->p_sys->o_window close];
1325     p_vout->p_sys->o_window = nil;
1326 }
1327
1328 @end