1 /*****************************************************************************
2 * vout.m: MacOS X video output module
3 *****************************************************************************
4 * Copyright (C) 2001-2005 the VideoLAN team
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 * Benjamin Pracht <bigben at videolan dot org>
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
27 *****************************************************************************/
29 /*****************************************************************************
31 *****************************************************************************/
32 #include <errno.h> /* ENOMEM */
33 #include <stdlib.h> /* free() */
34 #include <string.h> /* strerror() */
36 /* BeginFullScreen, EndFullScreen */
37 #include <QuickTime/QuickTime.h>
45 /*****************************************************************************
46 * DeviceCallback: Callback triggered when the video-device variable is changed
47 *****************************************************************************/
48 int DeviceCallback( vlc_object_t *p_this, const char *psz_variable,
49 vlc_value_t old_val, vlc_value_t new_val, void *param )
52 vout_thread_t *p_vout = (vout_thread_t *)p_this;
54 msg_Dbg( p_vout, "set %d", new_val.i_int );
55 var_Create( p_vout->p_vlc, "video-device", VLC_VAR_INTEGER );
56 var_Set( p_vout->p_vlc, "video-device", new_val );
58 val.b_bool = VLC_TRUE;
59 var_Set( p_vout, "intf-change", val );
64 /*****************************************************************************
65 * VLCEmbeddedList implementation
66 *****************************************************************************/
67 @implementation VLCEmbeddedList
72 o_embedded_array = [NSMutableArray array];
80 for( i = 0; i < [o_embedded_array count]; i++ )
82 id o_vout_view = [o_embedded_array objectAtIndex: i];
83 if( ![o_vout_view isUsed] )
85 [o_vout_view setUsed: YES];
92 - (void)releaseEmbeddedVout: (id)o_vout_view
94 if( [o_embedded_array containsObject: o_vout_view] )
96 [o_vout_view setUsed: NO];
100 msg_Warn( VLCIntf, "Cannot find Video Output");
104 - (void)addEmbeddedVout: (id)o_vout_view
106 if( ![o_embedded_array containsObject: o_vout_view] )
108 [o_embedded_array addObject: o_vout_view];
112 - (BOOL)windowContainsEmbedded: (id)o_window
114 return ([self getViewForWindow: o_window] == nil ? NO : YES);
117 - (id)getViewForWindow: (id)o_window
119 id o_enumerator = [o_embedded_array objectEnumerator];
120 id o_current_embedded;
122 while( (o_current_embedded = [o_enumerator nextObject]) )
124 if( [o_current_embedded getWindow] == o_window )
126 return o_current_embedded;
134 /*****************************************************************************
135 * VLCVoutView implementation
136 *****************************************************************************/
137 @implementation VLCVoutView
139 - (id)initWithFrame:(NSRect)frameRect
141 [super initWithFrame: frameRect];
144 s_frame = &frameRect;
151 - (BOOL)setVout: (vout_thread_t *) vout subView: (NSView *) view
152 frame: (NSRect *) frame
155 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
156 NSArray *o_screens = [NSScreen screens];
162 if( [o_screens count] <= 0 )
164 msg_Err( p_vout, "no OSX screens available" );
168 p_real_vout = [VLCVoutView getRealVout: p_vout];
170 /* Get the pref value when this is the first time, otherwise retrieve the device from the top level video-device var */
171 if( var_Type( p_real_vout->p_vlc, "video-device" ) == 0 )
173 i_device = var_GetInteger( p_vout, "macosx-vdev" );
177 i_device = var_GetInteger( p_real_vout->p_vlc, "video-device" );
180 /* Setup the menuitem for the multiple displays. */
181 if( var_Type( p_real_vout, "video-device" ) == 0 )
184 vlc_value_t val2, text;
187 var_Create( p_real_vout, "video-device", VLC_VAR_INTEGER |
189 text.psz_string = _("Video Device");
190 var_Change( p_real_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
192 NSEnumerator * o_enumerator = [o_screens objectEnumerator];
195 text.psz_string = _("Default");
196 var_Change( p_real_vout, "video-device",
197 VLC_VAR_ADDCHOICE, &val2, &text );
198 var_Set( p_real_vout, "video-device", val2 );
200 while( (o_screen = [o_enumerator nextObject]) != NULL )
203 NSRect s_rect = [o_screen frame];
205 snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
206 "%s %d (%dx%d)", _("Screen"), i,
207 (int)s_rect.size.width, (int)s_rect.size.height );
209 text.psz_string = psz_temp;
211 var_Change( p_real_vout, "video-device",
212 VLC_VAR_ADDCHOICE, &val2, &text );
215 var_Set( p_real_vout, "video-device", val2 );
220 var_AddCallback( p_real_vout, "video-device", DeviceCallback,
223 val2.b_bool = VLC_TRUE;
224 var_Set( p_real_vout, "intf-change", val2 );
227 /* Add the view. It's automatically resized to fit the window */
228 [self addSubview: o_view];
229 [self setAutoresizesSubviews: YES];
235 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize
237 [super resizeSubviewsWithOldSize: oldBoundsSize];
238 [o_view setFrameSize: [self frame].size];
243 [o_view removeFromSuperview];
251 - (void)setOnTop:(BOOL)b_on_top
255 [o_window setLevel: NSStatusWindowLevel];
259 [o_window setLevel: NSNormalWindowLevel];
263 - (void)scaleWindowWithFactor: (float)factor
266 int i_corrected_height, i_corrected_width;
268 NSPoint topleftscreen;
270 if ( !p_vout->b_fullscreen )
274 topleftbase.y = [o_window frame].size.height;
275 topleftscreen = [o_window convertBaseToScreen: topleftbase];
277 if( p_vout->render.i_height * p_vout->render.i_aspect >
278 p_vout->render.i_width * VOUT_ASPECT_FACTOR )
280 i_corrected_width = p_vout->render.i_height * p_vout->render.i_aspect /
282 newsize.width = (int) ( i_corrected_width * factor );
283 newsize.height = (int) ( p_vout->render.i_height * factor );
287 i_corrected_height = p_vout->render.i_width * VOUT_ASPECT_FACTOR /
288 p_vout->render.i_aspect;
289 newsize.width = (int) ( p_vout->render.i_width * factor );
290 newsize.height = (int) ( i_corrected_height * factor );
293 /* Calculate the window's new size */
294 new_frame.size.width = [o_window frame].size.width -
295 [self frame].size.width + newsize.width;
296 new_frame.size.height = [o_window frame].size.height -
297 [self frame].size.height + newsize.height;
299 new_frame.origin.x = topleftscreen.x;
300 new_frame.origin.y = topleftscreen.y - new_frame.size.height;
302 [o_window setFrame: new_frame display: YES];
304 p_vout->i_changes |= VOUT_SIZE_CHANGE;
308 - (void)toggleFloatOnTop
312 if( var_Get( p_real_vout, "video-on-top", &val )>=0 && val.b_bool)
314 val.b_bool = VLC_FALSE;
318 val.b_bool = VLC_TRUE;
320 var_Set( p_real_vout, "video-on-top", val );
323 - (void)toggleFullscreen
326 var_Get( p_real_vout, "fullscreen", &val );
327 val.b_bool = !val.b_bool;
328 var_Set( p_real_vout, "fullscreen", val );
334 var_Get( p_real_vout, "fullscreen", &val );
335 return( val.b_bool );
340 vout_Control( p_real_vout, VOUT_SNAPSHOT );
345 /* Disable Screensaver */
346 UpdateSystemActivity( UsrActivity );
354 - (void)keyDown:(NSEvent *)o_event
358 unsigned int i_pressed_modifiers = 0;
361 i_pressed_modifiers = [o_event modifierFlags];
363 if( i_pressed_modifiers & NSShiftKeyMask )
364 val.i_int |= KEY_MODIFIER_SHIFT;
365 if( i_pressed_modifiers & NSControlKeyMask )
366 val.i_int |= KEY_MODIFIER_CTRL;
367 if( i_pressed_modifiers & NSAlternateKeyMask )
368 val.i_int |= KEY_MODIFIER_ALT;
369 if( i_pressed_modifiers & NSCommandKeyMask )
370 val.i_int |= KEY_MODIFIER_COMMAND;
372 key = [[o_event charactersIgnoringModifiers] characterAtIndex: 0];
376 /* Escape should always get you out of fullscreen */
377 if( key == (unichar) 0x1b )
379 if( [self isFullscreen] )
381 [self toggleFullscreen];
384 else if ( key == ' ' )
387 val.i_int = config_GetInt( p_vout, "key-play-pause" );
388 var_Set( p_vout->p_vlc, "key-pressed", val );
392 val.i_int |= CocoaKeyToVLC( key );
393 var_Set( p_vout->p_vlc, "key-pressed", val );
398 [super keyDown: o_event];
402 - (void)mouseDown:(NSEvent *)o_event
408 switch( [o_event type] )
410 case NSLeftMouseDown:
412 var_Get( p_vout, "mouse-button-down", &val );
414 var_Set( p_vout, "mouse-button-down", val );
419 [super mouseDown: o_event];
425 - (void)otherMouseDown:(NSEvent *)o_event
431 switch( [o_event type] )
433 case NSOtherMouseDown:
435 var_Get( p_vout, "mouse-button-down", &val );
437 var_Set( p_vout, "mouse-button-down", val );
442 [super mouseDown: o_event];
448 - (void)rightMouseDown:(NSEvent *)o_event
454 switch( [o_event type] )
456 case NSRightMouseDown:
458 var_Get( p_vout, "mouse-button-down", &val );
460 var_Set( p_vout, "mouse-button-down", val );
465 [super mouseDown: o_event];
471 - (void)mouseUp:(NSEvent *)o_event
477 switch( [o_event type] )
482 b_val.b_bool = VLC_TRUE;
483 var_Set( p_vout, "mouse-clicked", b_val );
485 var_Get( p_vout, "mouse-button-down", &val );
487 var_Set( p_vout, "mouse-button-down", val );
492 [super mouseUp: o_event];
498 - (void)otherMouseUp:(NSEvent *)o_event
504 switch( [o_event type] )
508 var_Get( p_vout, "mouse-button-down", &val );
510 var_Set( p_vout, "mouse-button-down", val );
515 [super mouseUp: o_event];
521 - (void)rightMouseUp:(NSEvent *)o_event
527 switch( [o_event type] )
531 var_Get( p_vout, "mouse-button-down", &val );
533 var_Set( p_vout, "mouse-button-down", val );
538 [super mouseUp: o_event];
544 - (void)mouseDragged:(NSEvent *)o_event
546 [self mouseMoved: o_event];
549 - (void)otherMouseDragged:(NSEvent *)o_event
551 [self mouseMoved: o_event];
554 - (void)rightMouseDragged:(NSEvent *)o_event
556 [self mouseMoved: o_event];
559 - (void)mouseMoved:(NSEvent *)o_event
567 s_rect = [o_view bounds];
568 ml = [o_view convertPoint: [o_event locationInWindow] fromView: nil];
569 b_inside = [o_view mouse: ml inRect: s_rect];
574 unsigned int i_width, i_height, i_x, i_y;
576 vout_PlacePicture( p_vout, (unsigned int)s_rect.size.width,
577 (unsigned int)s_rect.size.height,
578 &i_x, &i_y, &i_width, &i_height );
580 val.i_int = ( ((int)ml.x) - i_x ) *
581 p_vout->render.i_width / i_width;
582 var_Set( p_vout, "mouse-x", val );
584 if( [[o_view className] isEqualToString: @"VLCGLView"] )
586 val.i_int = ( ((int)(s_rect.size.height - ml.y)) - i_y ) *
587 p_vout->render.i_height / i_height;
591 val.i_int = ( ((int)ml.y) - i_y ) *
592 p_vout->render.i_height / i_height;
594 var_Set( p_vout, "mouse-y", val );
596 val.b_bool = VLC_TRUE;
597 var_Set( p_vout, "mouse-moved", val );
600 [super mouseMoved: o_event];
603 - (BOOL)acceptsFirstResponder
608 - (BOOL)becomeFirstResponder
613 - (BOOL)resignFirstResponder
615 /* We need to stay the first responder or we'll miss some
620 /* Class methods used by the different vout modules */
622 + (vout_thread_t *)getRealVout: (vout_thread_t *)p_vout
624 /* p_real_vout: the vout we have to use to check for video-on-top
625 and a few other things. If we are the QuickTime output, it's us.
626 It we are the OpenGL provider, it is our parent. */
627 if( p_vout->i_object_type == VLC_OBJECT_OPENGL )
629 return (vout_thread_t *) p_vout->p_parent;
638 + (id)getVoutView: (vout_thread_t *)p_vout subView: (NSView *)view
639 frame: (NSRect *)s_frame
641 vlc_value_t value_drawable;
644 vout_thread_t * p_real_vout = [VLCVoutView getRealVout: p_vout];
646 var_Get( p_vout->p_vlc, "drawable", &value_drawable );
648 var_Create( p_vout, "macosx-vdev", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
649 var_Create( p_vout, "macosx-fill", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
650 var_Create( p_vout, "macosx-stretch", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
651 var_Create( p_vout, "macosx-opaqueness", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
652 var_Create( p_vout, "macosx-background", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
655 /* We only wait for NSApp to initialise if we're not embedded (as in the
656 * case of the Mozilla plugin). We can tell whether we're embedded or not
657 * by examining the "drawable" value: if it's zero, we're running in the
658 * main Mac intf; if it's non-zero, we're embedded. */
659 if( value_drawable.i_int == 0 )
661 /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
662 for( i_timeout = 20 ; i_timeout-- ; )
666 msleep( INTF_IDLE_SLEEP );
672 /* No MacOS X intf, unable to communicate with MT */
673 msg_Err( p_vout, "no MacOS X interface present" );
678 if ( VLCIntf && !(p_vout->b_fullscreen) &&
679 !(var_GetBool( p_real_vout, "macosx-background" )) )
681 o_return = [[[VLCMain sharedInstance] getEmbeddedList]
687 /* No embedded vout is available */
688 if( o_return == nil )
691 bzero( &null_rect, sizeof( NSRect ) );
692 o_return = [[VLCDetachedVoutView alloc] initWithFrame: null_rect ];
694 [o_return setVout: p_vout subView: view frame: s_frame];
700 /*****************************************************************************
701 * VLCDetachedVoutView implementation
702 *****************************************************************************/
703 @implementation VLCDetachedVoutView
705 - (id)initWithFrame: (NSRect)frameRect
707 [super initWithFrame: frameRect];
708 i_time_mouse_last_moved = 0;
712 - (bool)setVout: (vout_thread_t *) p_arg_vout subView: (NSView *) view
713 frame: (NSRect *) s_arg_frame
715 BOOL b_return = [super setVout: p_arg_vout subView: view frame:s_arg_frame];
716 i_time_mouse_last_moved = mdate();
717 o_window = [[VLCWindow alloc] initWithVout: p_arg_vout view: self
719 [view setFrame: [self frame]];
720 [o_window setAcceptsMouseMovedEvents: TRUE];
726 [o_window closeWindow];
727 [o_window setAcceptsMouseMovedEvents: NO];
728 i_time_mouse_last_moved = 0;
732 - (void)mouseMoved:(NSEvent *)o_event
734 i_time_mouse_last_moved = mdate();
735 [super mouseMoved: o_event];
738 - (void)hideMouse:(BOOL)b_hide
742 NSView *o_contents = [o_window contentView];
744 ml = [o_window convertScreenToBase:[NSEvent mouseLocation]];
745 ml = [o_contents convertPoint:ml fromView:nil];
746 b_inside = [o_contents mouse: ml inRect: [o_contents bounds]];
748 if( b_hide && b_inside )
750 [NSCursor setHiddenUntilMouseMoves: YES];
754 [NSCursor setHiddenUntilMouseMoves: NO];
761 if( p_vout->b_fullscreen )
763 if( mdate() - i_time_mouse_last_moved > 3000000 )
765 [self hideMouse: YES];
770 [self hideMouse: NO];
776 /*****************************************************************************
777 * VLCEmbeddedVoutView implementation
778 *****************************************************************************/
780 @implementation VLCEmbeddedVoutView
782 - (id)initWithFrame: (NSRect)frameRect
784 [super initWithFrame: frameRect];
786 [[[VLCMain sharedInstance] getEmbeddedList] addEmbeddedVout: self];
790 - (BOOL)setVout: (vout_thread_t *) p_arg_vout subView: (NSView *) view
791 frame: (NSRect *) s_arg_frame
795 b_return = [super setVout: p_arg_vout subView: view frame: s_arg_frame];
798 o_window = [self window];
799 [o_window makeKeyAndOrderFront: self];
800 [o_window setAcceptsMouseMovedEvents: TRUE];
801 [view setFrameSize: [self frame].size];
806 - (void)setUsed: (BOOL)b_new_used
819 [o_window setAcceptsMouseMovedEvents: NO];
820 [[[VLCMain sharedInstance] getEmbeddedList] releaseEmbeddedVout: self];
827 /*****************************************************************************
828 * VLCWindow implementation
829 *****************************************************************************/
830 @implementation VLCWindow
832 - (id) initWithVout: (vout_thread_t *) vout view: (VLCVoutView *) view
833 frame: (NSRect *) frame
839 [self performSelectorOnMainThread: @selector(initReal:)
840 withObject: NULL waitUntilDone: YES];
850 - (id) initReal: (id) sender
852 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
853 NSArray *o_screens = [NSScreen screens];
855 vlc_bool_t b_menubar_screen = VLC_FALSE;
858 b_init_ok = VLC_FALSE;
860 p_fullscreen_state = NULL;
862 p_real_vout = [VLCVoutView getRealVout: p_vout];
863 i_device = var_GetInteger( p_real_vout->p_vlc, "video-device" );
865 /* Find out on which screen to open the window */
866 if( i_device <= 0 || i_device > (int)[o_screens count] )
868 /* No preference specified. Use the main screen */
869 o_screen = [NSScreen mainScreen];
870 if( o_screen == [o_screens objectAtIndex: 0] )
871 b_menubar_screen = VLC_TRUE;
876 o_screen = [o_screens objectAtIndex: i_device];
877 b_menubar_screen = ( i_device == 0 );
880 if( p_vout->b_fullscreen )
882 NSRect screen_rect = [o_screen frame];
883 screen_rect.origin.x = screen_rect.origin.y = 0;
885 /* Creates a window with size: screen_rect on o_screen */
886 [self initWithContentRect: screen_rect
887 styleMask: NSBorderlessWindowMask
888 backing: NSBackingStoreBuffered
889 defer: YES screen: o_screen];
891 if( b_menubar_screen )
893 BeginFullScreen( &p_fullscreen_state, NULL, 0, 0,
894 NULL, NULL, fullScreenAllowEvents );
897 else if( var_GetBool( p_real_vout, "macosx-background" ) )
899 NSRect screen_rect = [o_screen frame];
900 screen_rect.origin.x = screen_rect.origin.y = 0;
902 /* Creates a window with size: screen_rect on o_screen */
903 [self initWithContentRect: screen_rect
904 styleMask: NSBorderlessWindowMask
905 backing: NSBackingStoreBuffered
906 defer: YES screen: o_screen];
908 [self setLevel: CGWindowLevelForKey(kCGDesktopWindowLevelKey)];
912 unsigned int i_stylemask = NSTitledWindowMask |
913 NSMiniaturizableWindowMask |
914 NSClosableWindowMask |
915 NSResizableWindowMask;
920 s_rect.size.width = p_vout->i_window_width;
921 s_rect.size.height = p_vout->i_window_height;
928 [self initWithContentRect: s_rect
929 styleMask: i_stylemask
930 backing: NSBackingStoreBuffered
931 defer: YES screen: o_screen];
933 [self setAlphaValue: var_GetFloat( p_vout, "macosx-opaqueness" )];
935 if( var_GetBool( p_real_vout, "video-on-top" ) )
937 [self setLevel: NSStatusWindowLevel];
947 [self makeKeyAndOrderFront: nil];
948 [self setReleasedWhenClosed: YES];
950 /* We'll catch mouse events */
951 [self makeFirstResponder: o_view];
953 /* Add the view. It's automatically resized to fit the window */
954 [self setContentView: o_view];
958 b_init_ok = VLC_TRUE;
969 /* XXX waitUntilDone = NO to avoid a possible deadlock when hitting
971 [self setContentView: NULL];
972 [self performSelectorOnMainThread: @selector(closeReal:)
973 withObject: NULL waitUntilDone: NO];
976 - (id) closeReal: (id) sender
979 if( p_fullscreen_state )
981 EndFullScreen( p_fullscreen_state, 0 );
991 - (BOOL)canBecomeKeyWindow
996 /* Sometimes crashes VLC....
997 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
999 return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event];
1002 /* This is actually the same as VLCControls::stop. */
1004 - (void)updateTitle /*not modified yey ! */
1006 NSMutableString * o_title = NULL, * o_mrl = NULL;
1007 input_thread_t * p_input;
1009 if( p_vout == NULL )
1014 p_input = vlc_object_find( p_vout, VLC_OBJECT_INPUT, FIND_PARENT );
1016 if( p_input == NULL )
1021 if( p_input->input.p_item->psz_name != NULL )
1022 o_title = [NSMutableString stringWithUTF8String:
1023 p_input->input.p_item->psz_name];
1024 if( p_input->input.p_item->psz_uri != NULL )
1025 o_mrl = [NSMutableString stringWithUTF8String:
1026 p_input->input.p_item->psz_uri];
1027 if( o_title == nil )
1032 if( p_input->input.p_access && !strcmp( p_input->input.p_access->p_module->psz_shortname, "File" ) )
1034 NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
1035 if( prefix_range.location != NSNotFound )
1036 [o_mrl deleteCharactersInRange: prefix_range];
1037 [self setRepresentedFilename: o_mrl];
1039 [self setTitle: o_title];
1043 [self setTitle: [NSString stringWithCString: VOUT_TITLE]];
1045 vlc_object_release( p_input );
1048 - (BOOL)windowShouldClose:(id)sender
1050 playlist_t * p_playlist = vlc_object_find( p_vout, VLC_OBJECT_PLAYLIST,
1052 if( p_playlist == NULL )
1057 playlist_Stop( p_playlist );
1058 vlc_object_release( p_playlist );
1060 /* The window will be closed by the intf later. */