]> git.sesse.net Git - vlc/blob - modules/gui/macosx/vout.m
video_output/opengl.c: fixed a (OS X only) segfault
[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 <hartman at videolan dot org>
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 /* BeginFullScreen, EndFullScreen */
36 #include <QuickTime/QuickTime.h>
37
38 #include <vlc_keys.h>
39
40 #include "intf.h"
41 #include "vout.h"
42
43 /*****************************************************************************
44  * VLCWindow implementation
45  *****************************************************************************/
46 @implementation VLCWindow
47
48 - (id)initWithVout:(vout_thread_t *)_p_vout frame:(NSRect *)s_frame
49 {
50     int i_timeout;
51
52     p_vout = _p_vout;
53
54     /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
55     for( i_timeout = 20 ; i_timeout-- ; )
56     {
57         if( NSApp == NULL )
58         {
59             msleep( INTF_IDLE_SLEEP );
60         }
61     }
62
63     if( NSApp == NULL )
64     {
65         /* No MacOS X intf, unable to communicate with MT */
66         msg_Err( p_vout, "no MacOS X interface present" );
67         return NULL;
68     }                                                                           
69
70     /* p_real_vout: the vout we have to use to check for video-on-top
71        and a few other things. If we are the QuickTime output, it's us.
72        It we are the OpenGL provider, it is our parent. */
73     if( p_vout->i_object_type == VLC_OBJECT_OPENGL )
74     {
75         p_real_vout = (vout_thread_t *) p_vout->p_parent;
76     }
77     else
78     {
79         p_real_vout = p_vout;
80     }
81
82     p_fullscreen_state = NULL;
83     i_time_mouse_last_moved = mdate();
84
85     NSScreen * o_screen;
86     vlc_bool_t b_main_screen;
87
88     var_Create( p_vout, "macosx-vdev", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
89     var_Create( p_vout, "macosx-fill", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
90     var_Create( p_vout, "macosx-stretch", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
91     var_Create( p_vout, "macosx-opaqueness", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
92
93     /* Setup the menuitem for the multiple displays. Read the vlc preference (macosx-vdev) for the primary display */
94     NSArray * o_screens = [NSScreen screens];
95     if( [o_screens count] > 0 && var_Type( p_real_vout, "video-device" ) == 0 )
96     {
97         int i = 1;
98         vlc_value_t val, val2, text;
99         NSScreen * o_screen;
100
101         var_Get( p_real_vout, "macosx-vdev", &val );
102
103         var_Create( p_real_vout, "video-device", VLC_VAR_INTEGER |
104                                             VLC_VAR_HASCHOICE );
105         text.psz_string = _("Video device");
106         var_Change( p_real_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
107
108         NSEnumerator * o_enumerator = [o_screens objectEnumerator];
109
110         while( (o_screen = [o_enumerator nextObject]) != NULL )
111         {
112             char psz_temp[255];
113             NSRect s_rect = [o_screen frame];
114
115             snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
116                       "%s %d (%dx%d)", _("Screen"), i,
117                       (int)s_rect.size.width, (int)s_rect.size.height );
118
119             text.psz_string = psz_temp;
120             val2.i_int = i;
121             var_Change( p_real_vout, "video-device",
122                         VLC_VAR_ADDCHOICE, &val2, &text );
123
124             if( ( i - 1 ) == val.i_int )
125             {
126                 var_Set( p_real_vout, "video-device", val2 );
127             }
128             i++;
129         }
130
131         var_AddCallback( p_real_vout, "video-device", vout_VarCallback,
132                          NULL );
133
134         val2.b_bool = VLC_TRUE;
135         var_Set( p_real_vout, "intf-change", val2 );
136     }
137
138     /* Find out on which screen to open the window */
139     int i_device = var_GetInteger( p_real_vout, "video-device" );
140     if( i_device < 0 )
141     {
142          /* No preference specified. Use the main screen */
143         o_screen = [NSScreen mainScreen];
144         b_main_screen = 1;
145     }
146     else
147     {
148         NSArray *o_screens = [NSScreen screens];
149         
150         if( [o_screens count] < (unsigned) i_device )
151         {
152             o_screen = [NSScreen mainScreen];
153             b_main_screen = 1;
154         }
155         else
156         {
157             i_device--;
158             o_screen = [o_screens objectAtIndex: i_device];
159             var_SetInteger( p_real_vout, "macosx-vdev", i_device );
160             b_main_screen = ( i_device == 0 );
161         }
162     }
163
164     NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
165
166     if( p_vout->b_fullscreen )
167     {
168         NSRect screen_rect = [o_screen frame];
169         screen_rect.origin.x = screen_rect.origin.y = 0;
170
171         /* Creates a window with size: screen_rect on o_screen */
172         [self initWithContentRect: screen_rect
173               styleMask: NSBorderlessWindowMask
174               backing: NSBackingStoreBuffered
175               defer: YES screen: o_screen];
176
177         if( b_main_screen )
178         {
179             BeginFullScreen( &p_fullscreen_state, NULL, 0, 0,
180                              NULL, NULL, fullScreenAllowEvents );
181         }
182     }
183     else
184     {
185         unsigned int i_stylemask = NSTitledWindowMask |
186                                    NSMiniaturizableWindowMask |
187                                    NSClosableWindowMask |
188                                    NSResizableWindowMask;
189
190         NSRect s_rect;
191         if( !s_frame )
192         {
193             s_rect.size.width  = p_vout->i_window_width;
194             s_rect.size.height = p_vout->i_window_height;
195         }
196         else
197         {
198             s_rect = *s_frame;
199         }
200        
201         [self initWithContentRect: s_rect
202               styleMask: i_stylemask
203               backing: NSBackingStoreBuffered
204               defer: YES screen: o_screen];
205
206         [self setAlphaValue: var_GetFloat( p_vout, "macosx-opaqueness" )];
207
208         if( var_GetBool( p_real_vout, "video-on-top" ) )
209         {
210             [self setLevel: NSStatusWindowLevel];
211         }
212
213         if( !s_frame )
214         {
215             [self center];
216         }
217     }
218
219     [self updateTitle];
220     [self makeKeyAndOrderFront: nil];
221     [self setReleasedWhenClosed: YES];
222
223     /* We'll catch mouse events */
224     [self setAcceptsMouseMovedEvents: YES];
225     [self makeFirstResponder: self];
226     
227     [o_pool release];
228     return self;
229 }
230
231 - (void)close
232 {
233     if( p_fullscreen_state )
234     {
235         EndFullScreen( p_fullscreen_state, 0 );
236     }
237     [super close];
238 }
239
240 - (void)setOnTop:(BOOL)b_on_top
241 {
242     if( b_on_top )
243     {
244         [self setLevel: NSStatusWindowLevel];
245     }
246     else
247     {
248         [self setLevel: NSNormalWindowLevel];
249     }
250 }
251
252 - (void)hideMouse:(BOOL)b_hide
253 {
254     BOOL b_inside;
255     NSPoint ml;
256     NSView *o_contents = [self contentView];
257     
258     ml = [self convertScreenToBase:[NSEvent mouseLocation]];
259     ml = [o_contents convertPoint:ml fromView:nil];
260     b_inside = [o_contents mouse: ml inRect: [o_contents bounds]];
261     
262     if( b_hide && b_inside )
263     {
264         [NSCursor setHiddenUntilMouseMoves: YES];
265     }
266     else if( !b_hide )
267     {
268         [NSCursor setHiddenUntilMouseMoves: NO];
269     }
270 }
271
272 - (void)manage
273 {
274     if( p_fullscreen_state )
275     {
276         if( mdate() - i_time_mouse_last_moved > 3000000 )
277         {
278             [self hideMouse: YES];
279         }
280     }
281     else
282     {
283         [self hideMouse: NO];
284     }
285
286     /* Disable screensaver */
287     UpdateSystemActivity( UsrActivity );
288 }
289
290 - (void)scaleWindowWithFactor: (float)factor
291 {
292     NSSize newsize;
293     int i_corrected_height, i_corrected_width;
294     NSPoint topleftbase;
295     NSPoint topleftscreen;
296     
297     if ( !p_vout->b_fullscreen )
298     {
299         topleftbase.x = 0;
300         topleftbase.y = [self frame].size.height;
301         topleftscreen = [self convertBaseToScreen: topleftbase];
302         
303         if( p_vout->render.i_height * p_vout->render.i_aspect > 
304                         p_vout->render.i_width * VOUT_ASPECT_FACTOR )
305         {
306             i_corrected_width = p_vout->render.i_height * p_vout->render.i_aspect /
307                                             VOUT_ASPECT_FACTOR;
308             newsize.width = (int) ( i_corrected_width * factor );
309             newsize.height = (int) ( p_vout->render.i_height * factor );
310         }
311         else
312         {
313             i_corrected_height = p_vout->render.i_width * VOUT_ASPECT_FACTOR /
314                                             p_vout->render.i_aspect;
315             newsize.width = (int) ( p_vout->render.i_width * factor );
316             newsize.height = (int) ( i_corrected_height * factor );
317         }
318     
319         [self setContentSize: newsize];
320         
321         [self setFrameTopLeftPoint: topleftscreen];
322         p_vout->i_changes |= VOUT_SIZE_CHANGE;
323     }
324 }
325
326 - (void)toggleFloatOnTop
327 {
328     vlc_value_t val;
329     if( var_Get( p_vout, "video-on-top", &val )>=0 && val.b_bool)
330     {
331         val.b_bool = VLC_FALSE;
332         var_Set( p_vout, "video-on-top", val );
333     }
334     else
335     {
336         val.b_bool = VLC_TRUE;
337         var_Set( p_vout, "video-on-top", val );
338     }
339 }
340
341 - (void)toggleFullscreen
342 {
343     vlc_value_t val;
344     val.b_bool = !p_real_vout->b_fullscreen;
345     var_Set( p_real_vout, "fullscreen", val );
346 }
347
348 - (BOOL)isFullscreen
349 {
350     return( p_vout->b_fullscreen );
351 }
352
353 - (void)snapshot
354 {
355     vout_Control( p_vout, VOUT_SNAPSHOT );
356 }
357
358 - (BOOL)canBecomeKeyWindow
359 {
360     return YES;
361 }
362
363 /* Sometimes crashes VLC....
364 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
365 {
366         return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event];
367 }*/
368
369 - (void)keyDown:(NSEvent *)o_event
370 {
371     unichar key = 0;
372     vlc_value_t val;
373     unsigned int i_pressed_modifiers = 0;
374     val.i_int = 0;
375     
376     i_pressed_modifiers = [o_event modifierFlags];
377
378     if( i_pressed_modifiers & NSShiftKeyMask )
379         val.i_int |= KEY_MODIFIER_SHIFT;
380     if( i_pressed_modifiers & NSControlKeyMask )
381         val.i_int |= KEY_MODIFIER_CTRL;
382     if( i_pressed_modifiers & NSAlternateKeyMask )
383         val.i_int |= KEY_MODIFIER_ALT;
384     if( i_pressed_modifiers & NSCommandKeyMask )
385         val.i_int |= KEY_MODIFIER_COMMAND;
386
387     key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
388
389     if( key )
390     {
391         /* Escape should always get you out of fullscreen */
392         if( key == (unichar) 0x1b )
393         {
394              if( [self isFullscreen] )
395              {
396                  [self toggleFullscreen];
397              }
398         }
399         else if ( key == ' ' )
400         {
401             vlc_value_t val;
402             val.i_int = config_GetInt( p_vout, "key-play-pause" );
403             var_Set( p_vout->p_vlc, "key-pressed", val );
404         }
405         else
406         {
407             val.i_int |= CocoaKeyToVLC( key );
408             var_Set( p_vout->p_vlc, "key-pressed", val );
409         }
410     }
411     else
412     {
413         [super keyDown: o_event];
414     }
415 }
416
417 - (void)updateTitle
418 {
419     NSMutableString * o_title;
420     playlist_t * p_playlist;
421     
422     if( p_vout == NULL )
423     {
424         return;
425     }
426     
427     p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
428                                                 FIND_ANYWHERE );
429     
430     if( p_playlist == NULL )
431     {
432         return;
433     }
434
435     vlc_mutex_lock( &p_playlist->object_lock );
436     o_title = [NSMutableString stringWithUTF8String: 
437         p_playlist->status.p_item->input.psz_uri]; 
438     vlc_mutex_unlock( &p_playlist->object_lock );
439     vlc_object_release( p_playlist );
440
441     if( o_title != nil )
442     {
443         NSRange prefix_range = [o_title rangeOfString: @"file:"];
444         if( prefix_range.location != NSNotFound )
445         {
446             [o_title deleteCharactersInRange: prefix_range];
447         }
448
449         [self setTitleWithRepresentedFilename: o_title];
450     }
451     else
452     {
453         [self setTitle: [NSString stringWithCString: VOUT_TITLE]];
454     }
455 }
456
457 /* This is actually the same as VLCControls::stop. */
458 - (BOOL)windowShouldClose:(id)sender
459 {
460     playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
461                                                        FIND_ANYWHERE );
462     if( p_playlist == NULL )      
463     {
464         return NO;
465     }
466
467     playlist_Stop( p_playlist );
468     vlc_object_release( p_playlist );
469
470     /* The window will be closed by the intf later. */
471     return NO;
472 }
473
474 - (BOOL)acceptsFirstResponder
475 {
476     return YES;
477 }
478
479 - (BOOL)becomeFirstResponder
480 {
481     return YES;
482 }
483
484 - (BOOL)resignFirstResponder
485 {
486     /* We need to stay the first responder or we'll miss some
487        events */
488     return NO;
489 }
490
491 - (void)mouseDown:(NSEvent *)o_event
492 {
493     vlc_value_t val;
494
495     switch( [o_event type] )
496     {
497         case NSLeftMouseDown:
498         {
499             var_Get( p_vout, "mouse-button-down", &val );
500             val.i_int |= 1;
501             var_Set( p_vout, "mouse-button-down", val );
502         }
503         break;
504
505         default:
506             [super mouseDown: o_event];
507         break;
508     }
509 }
510
511 - (void)otherMouseDown:(NSEvent *)o_event
512 {
513     vlc_value_t val;
514
515     switch( [o_event type] )
516     {
517         case NSOtherMouseDown:
518         {
519             var_Get( p_vout, "mouse-button-down", &val );
520             val.i_int |= 2;
521             var_Set( p_vout, "mouse-button-down", val );
522         }
523         break;
524
525         default:
526             [super mouseDown: o_event];
527         break;
528     }
529 }
530
531 - (void)rightMouseDown:(NSEvent *)o_event
532 {
533     vlc_value_t val;
534
535     switch( [o_event type] )
536     {
537         case NSRightMouseDown:
538         {
539             var_Get( p_vout, "mouse-button-down", &val );
540             val.i_int |= 4;
541             var_Set( p_vout, "mouse-button-down", val );
542         }
543         break;
544
545         default:
546             [super mouseDown: o_event];
547         break;
548     }
549 }
550
551 - (void)mouseUp:(NSEvent *)o_event
552 {
553     vlc_value_t val;
554
555     switch( [o_event type] )
556     {
557         case NSLeftMouseUp:
558         {
559             vlc_value_t b_val;
560             b_val.b_bool = VLC_TRUE;
561             var_Set( p_vout, "mouse-clicked", b_val );
562
563             var_Get( p_vout, "mouse-button-down", &val );
564             val.i_int &= ~1;
565             var_Set( p_vout, "mouse-button-down", val );
566         }
567         break;
568
569         default:
570             [super mouseUp: o_event];
571         break;
572     }
573 }
574
575 - (void)otherMouseUp:(NSEvent *)o_event
576 {
577     vlc_value_t val;
578
579     switch( [o_event type] )
580     {
581         case NSOtherMouseUp:
582         {
583             var_Get( p_vout, "mouse-button-down", &val );
584             val.i_int &= ~2;
585             var_Set( p_vout, "mouse-button-down", val );
586         }
587         break;
588
589         default:
590             [super mouseUp: o_event];
591         break;
592     }
593 }
594
595 - (void)rightMouseUp:(NSEvent *)o_event
596 {
597     vlc_value_t val;
598
599     switch( [o_event type] )
600     {
601         case NSRightMouseUp:
602         {
603             var_Get( p_vout, "mouse-button-down", &val );
604             val.i_int &= ~4;
605             var_Set( p_vout, "mouse-button-down", val );
606         }
607         break;
608
609         default:
610             [super mouseUp: o_event];
611         break;
612     }
613 }
614
615 - (void)mouseDragged:(NSEvent *)o_event
616 {
617     [self mouseMoved: o_event];
618 }
619
620 - (void)otherMouseDragged:(NSEvent *)o_event
621 {
622     [self mouseMoved: o_event];
623 }
624
625 - (void)rightMouseDragged:(NSEvent *)o_event
626 {
627     [self mouseMoved: o_event];
628 }
629
630 - (void)mouseMoved:(NSEvent *)o_event
631 {   
632     NSPoint ml;
633     NSRect s_rect;
634     BOOL b_inside;
635     NSView * o_view;
636
637     i_time_mouse_last_moved = mdate();
638
639     o_view = [self contentView];
640     s_rect = [o_view bounds];
641     ml = [o_view convertPoint: [o_event locationInWindow] fromView: nil];
642     b_inside = [o_view mouse: ml inRect: s_rect];
643
644     if( b_inside )
645     {
646         vlc_value_t val;
647         int i_width, i_height, i_x, i_y;
648
649         vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
650                                    (unsigned int)s_rect.size.height,
651                                    &i_x, &i_y, &i_width, &i_height );
652
653         val.i_int = ( ((int)ml.x) - i_x ) *  
654                     p_vout->render.i_width / i_width;
655         var_Set( p_vout, "mouse-x", val );
656
657         if( [[o_view className] isEqualToString: @"VLCGLView"] )
658         {
659             val.i_int = ( ((int)(s_rect.size.height - ml.y)) - i_y ) *
660                         p_vout->render.i_height / i_height;
661         }
662         else
663         {
664             val.i_int = ( ((int)ml.y) - i_y ) * 
665                         p_vout->render.i_height / i_height;
666         }
667         var_Set( p_vout, "mouse-y", val );
668             
669         val.b_bool = VLC_TRUE;
670         var_Set( p_vout, "mouse-moved", val ); 
671     }
672
673     [super mouseMoved: o_event];
674 }
675
676 @end