]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: fspanel: give up focus to the right fullscreen window
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "Windows.h"
26 #import "intf.h"
27 #import "CoreInteraction.h"
28 #import "ControlsBar.h"
29 #import "VideoView.h"
30
31 /*****************************************************************************
32  * VLCWindow
33  *
34  *  Missing extension to NSWindow
35  *****************************************************************************/
36
37 @implementation VLCWindow
38 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
39                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
40 {
41     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
42     if (self) {
43         /* we don't want this window to be restored on relaunch */
44         if (!OSX_SNOW_LEOPARD)
45             [self setRestorable:NO];
46     }
47     return self;
48 }
49
50 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
51 {
52     b_isset_canBecomeKeyWindow = YES;
53     b_canBecomeKeyWindow = canBecomeKey;
54 }
55
56 - (BOOL)canBecomeKeyWindow
57 {
58     if (b_isset_canBecomeKeyWindow)
59         return b_canBecomeKeyWindow;
60
61     return [super canBecomeKeyWindow];
62 }
63
64 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
65 {
66     b_isset_canBecomeMainWindow = YES;
67     b_canBecomeMainWindow = canBecomeMain;
68 }
69
70 - (BOOL)canBecomeMainWindow
71 {
72     if (b_isset_canBecomeMainWindow)
73         return b_canBecomeMainWindow;
74
75     return [super canBecomeMainWindow];
76 }
77
78 - (void)closeAndAnimate: (BOOL)animate
79 {
80     NSInvocation *invoc;
81
82     if (!animate) {
83         [super close];
84         return;
85     }
86
87     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
88     [invoc setTarget: self];
89
90     if (![self isVisible] || [self alphaValue] == 0.0) {
91         [super close];
92         return;
93     }
94
95     [self orderOut: self animate: YES callback: invoc];
96 }
97
98 - (void)orderOut: (id)sender animate: (BOOL)animate
99 {
100     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
101     [invoc setTarget: self];
102     [invoc setArgument: sender atIndex: 0];
103     [self orderOut: sender animate: animate callback: invoc];
104 }
105
106 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
107 {
108     NSViewAnimation *anim;
109     NSViewAnimation *current_anim;
110     NSMutableDictionary *dict;
111
112     if (!animate) {
113         [self orderOut: sender];
114         return;
115     }
116
117     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
118
119     [dict setObject:self forKey:NSViewAnimationTargetKey];
120
121     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
122     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
123     [dict release];
124
125     [anim setAnimationBlockingMode:NSAnimationNonblocking];
126     [anim setDuration:0.9];
127     [anim setFrameRate:30];
128     [anim setUserInfo: callback];
129
130     @synchronized(self) {
131         current_anim = self->o_current_animation;
132
133         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
134             [anim release];
135         } else {
136             if (current_anim) {
137                 [current_anim stopAnimation];
138                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
139                 [current_anim release];
140             }
141             else
142                 [anim setCurrentProgress:1.0 - [self alphaValue]];
143             self->o_current_animation = anim;
144             [anim startAnimation];
145         }
146     }
147 }
148
149 - (void)orderFront: (id)sender animate: (BOOL)animate
150 {
151     NSViewAnimation *anim;
152     NSViewAnimation *current_anim;
153     NSMutableDictionary *dict;
154
155     if (!animate) {
156         [super orderFront: sender];
157         [self setAlphaValue: 1.0];
158         return;
159     }
160
161     if (![self isVisible]) {
162         [self setAlphaValue: 0.0];
163         [super orderFront: sender];
164     }
165     else if ([self alphaValue] == 1.0) {
166         [super orderFront: self];
167         return;
168     }
169
170     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
171
172     [dict setObject:self forKey:NSViewAnimationTargetKey];
173
174     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
175     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
176     [dict release];
177
178     [anim setAnimationBlockingMode:NSAnimationNonblocking];
179     [anim setDuration:0.5];
180     [anim setFrameRate:30];
181
182     @synchronized(self) {
183         current_anim = self->o_current_animation;
184
185         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
186             [anim release];
187         } else {
188             if (current_anim) {
189                 [current_anim stopAnimation];
190                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
191                 [current_anim release];
192             }
193             else
194                 [anim setCurrentProgress:[self alphaValue]];
195             self->o_current_animation = anim;
196             [self orderFront: sender];
197             [anim startAnimation];
198         }
199     }
200 }
201
202 - (void)animationDidEnd:(NSAnimation*)anim
203 {
204     if ([self alphaValue] <= 0.0) {
205         NSInvocation * invoc;
206         [super orderOut: nil];
207         [self setAlphaValue: 1.0];
208         if ((invoc = [anim userInfo]))
209             [invoc invoke];
210     }
211 }
212
213 @end
214
215
216 /*****************************************************************************
217  * VLCVideoWindowCommon
218  *
219  *  Common code for main window, detached window and extra video window
220  *****************************************************************************/
221
222 @implementation VLCVideoWindowCommon
223
224 @synthesize videoView=o_video_view;
225 @synthesize controlsBar=o_controls_bar;
226
227 #pragma mark -
228 #pragma mark Init
229
230 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
231                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
232 {
233     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
234
235     if (b_dark_interface) {
236         styleMask = NSBorderlessWindowMask;
237 #ifdef MAC_OS_X_VERSION_10_7
238         if (!OSX_SNOW_LEOPARD)
239             styleMask |= NSResizableWindowMask;
240 #endif
241     }
242
243     self = [super initWithContentRect:contentRect styleMask:styleMask
244                               backing:backingType defer:flag];
245
246     /* we want to be moveable regardless of our style */
247     [self setMovableByWindowBackground: YES];
248     [self setCanBecomeKeyWindow:YES];
249
250     o_temp_view = [[NSView alloc] init];
251     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
252
253     return self;
254 }
255
256 - (void)dealloc
257 {
258     [o_temp_view release];
259     [super dealloc];
260 }
261
262 - (void)setTitle:(NSString *)title
263 {
264     if (b_dark_interface && o_titlebar_view)
265         [o_titlebar_view setWindowTitle: title];
266
267     [super setTitle: title];
268 }
269
270 #pragma mark -
271 #pragma mark zoom / minimize / close
272
273 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
274 {
275     SEL s_menuAction = [menuItem action];
276
277     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
278         return YES;
279
280     return [super validateMenuItem:menuItem];
281 }
282
283 - (BOOL)windowShouldClose:(id)sender
284 {
285     return YES;
286 }
287
288 - (void)performClose:(id)sender
289 {
290     if (!([self styleMask] & NSTitledWindowMask)) {
291         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
292
293         [self orderOut: sender];
294     } else
295         [super performClose: sender];
296 }
297
298 - (void)performMiniaturize:(id)sender
299 {
300     if (!([self styleMask] & NSTitledWindowMask))
301         [self miniaturize: sender];
302     else
303         [super performMiniaturize: sender];
304 }
305
306 - (void)performZoom:(id)sender
307 {
308     if (!([self styleMask] & NSTitledWindowMask))
309         [self customZoom: sender];
310     else
311         [super performZoom: sender];
312 }
313
314 - (void)zoom:(id)sender
315 {
316     if (!([self styleMask] & NSTitledWindowMask))
317         [self customZoom: sender];
318     else
319         [super zoom: sender];
320 }
321
322 /**
323  * Given a proposed frame rectangle, return a modified version
324  * which will fit inside the screen.
325  *
326  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
327  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
328  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
329  *    Copyright (C) 1996 Free Software Foundation, Inc.
330  */
331 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
332 {
333     NSRect screenRect = [screen visibleFrame];
334     float difference;
335
336     /* Move top edge of the window inside the screen */
337     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
338     if (difference > 0) {
339         frameRect.origin.y -= difference;
340     }
341
342     /* If the window is resizable, resize it (if needed) so that the
343      bottom edge is on the screen or can be on the screen when the user moves
344      the window */
345     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
346     if (_styleMask & NSResizableWindowMask) {
347         float difference2;
348
349         difference2 = screenRect.origin.y - frameRect.origin.y;
350         difference2 -= difference;
351         // Take in account the space between the top of window and the top of the
352         // screen which can be used to move the bottom of the window on the screen
353         if (difference2 > 0) {
354             frameRect.size.height -= difference2;
355             frameRect.origin.y += difference2;
356         }
357
358         /* Ensure that resizing doesn't makewindow smaller than minimum */
359         difference2 = [self minSize].height - frameRect.size.height;
360         if (difference2 > 0) {
361             frameRect.size.height += difference2;
362             frameRect.origin.y -= difference2;
363         }
364     }
365
366     return frameRect;
367 }
368
369 #define DIST 3
370
371 /**
372  Zooms the receiver.   This method calls the delegate method
373  windowShouldZoom:toFrame: to determine if the window should
374  be allowed to zoom to full screen.
375  *
376  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
377  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
378  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
379  *    Copyright (C) 1996 Free Software Foundation, Inc.
380  */
381 - (void) customZoom: (id)sender
382 {
383     NSRect maxRect = [[self screen] visibleFrame];
384     NSRect currentFrame = [self frame];
385
386     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
387         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
388     }
389
390     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
391
392     // Compare the new frame with the current one
393     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
394         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
395         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
396         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
397         // Already in zoomed mode, reset user frame, if stored
398         if ([self frameAutosaveName] != nil) {
399             [self setFrame: previousSavedFrame display: YES animate: YES];
400             [self saveFrameUsingName: [self frameAutosaveName]];
401         }
402         return;
403     }
404
405     if ([self frameAutosaveName] != nil) {
406         [self saveFrameUsingName: [self frameAutosaveName]];
407         previousSavedFrame = [self frame];
408     }
409
410     [self setFrame: maxRect display: YES animate: YES];
411 }
412
413 #pragma mark -
414 #pragma mark Video window resizing logic
415
416 - (void)resizeWindow
417 {
418     if ([[VLCMainWindow sharedInstance] fullscreen])
419         return;
420
421     NSSize windowMinSize = [self minSize];
422     NSRect screenFrame = [[self screen] visibleFrame];
423
424     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
425     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
426
427     unsigned int i_width = nativeVideoSize.width;
428     unsigned int i_height = nativeVideoSize.height;
429     if (i_width < windowMinSize.width)
430         i_width = windowMinSize.width;
431     if (i_height < f_min_video_height)
432         i_height = f_min_video_height;
433
434     /* Calculate the window's new size */
435     NSRect new_frame;
436     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
437     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
438     new_frame.origin.x = topleftscreen.x;
439     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
440
441     /* make sure the window doesn't exceed the screen size the window is on */
442     if (new_frame.size.width > screenFrame.size.width) {
443         new_frame.size.width = screenFrame.size.width;
444         new_frame.origin.x = screenFrame.origin.x;
445     }
446     if (new_frame.size.height > screenFrame.size.height) {
447         new_frame.size.height = screenFrame.size.height;
448         new_frame.origin.y = screenFrame.origin.y;
449     }
450     if (new_frame.origin.y < screenFrame.origin.y)
451         new_frame.origin.y = screenFrame.origin.y;
452
453     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
454     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
455     if (right_window_point > right_screen_point)
456         new_frame.origin.x -= (right_window_point - right_screen_point);
457
458     [[self animator] setFrame:new_frame display:YES];
459 }
460
461 - (void)setNativeVideoSize:(NSSize)size
462 {
463     nativeVideoSize = size;
464
465     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
466         [self resizeWindow];
467 }
468
469 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
470 {
471     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
472         return proposedFrameSize;
473
474     // needed when entering lion fullscreen mode
475     if ([[VLCMainWindow sharedInstance] fullscreen])
476         return proposedFrameSize;
477
478     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
479         NSRect videoWindowFrame = [self frame];
480         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
481         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
482         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
483         float marginx = contentRect.size.width - viewRect.size.width;
484         if (o_titlebar_view && b_dark_interface)
485             marginy += [o_titlebar_view frame].size.height;
486
487         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
488     }
489
490     return proposedFrameSize;
491 }
492
493 #pragma mark -
494 #pragma mark Fullscreen Logic
495
496 - (void)lockFullscreenAnimation
497 {
498     [o_animation_lock lock];
499 }
500
501 - (void)unlockFullscreenAnimation
502 {
503     [o_animation_lock unlock];
504 }
505
506 - (void)enterFullscreen
507 {
508     NSMutableDictionary *dict1, *dict2;
509     NSScreen *screen;
510     NSRect screen_rect;
511     NSRect rect;
512     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
513
514     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
515     [self lockFullscreenAnimation];
516
517     if (!screen) {
518         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
519         screen = [self screen];
520     }
521     if (!screen) {
522         msg_Dbg(VLCIntf, "Using deepest screen");
523         screen = [NSScreen deepestScreen];
524     }
525
526     screen_rect = [screen frame];
527
528     if (o_controls_bar)
529         [o_controls_bar setFullscreenState:YES];
530
531     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
532
533     if (blackout_other_displays)
534         [screen blackoutOtherScreens];
535
536     /* Make sure we don't see the window flashes in float-on-top mode */
537     i_originalLevel = [self level];
538     [self setLevel:NSNormalWindowLevel];
539
540     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
541     if (!o_fullscreen_window) {
542         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
543
544         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
545         rect.origin.x += [self frame].origin.x;
546         rect.origin.y += [self frame].origin.y;
547         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
548         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
549         [o_fullscreen_window setCanBecomeKeyWindow: YES];
550         [o_fullscreen_window setCanBecomeMainWindow: YES];
551
552         if (![self isVisible] || [self alphaValue] == 0.0) {
553             /* We don't animate if we are not visible, instead we
554              * simply fade the display */
555             CGDisplayFadeReservationToken token;
556
557             if (blackout_other_displays) {
558                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
559                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
560             }
561
562             if ([screen mainScreen])
563                 [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
564
565             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
566             [o_temp_view setFrame:[o_video_view frame]];
567             [o_fullscreen_window setContentView:o_video_view];
568
569             [o_fullscreen_window makeKeyAndOrderFront:self];
570             [o_fullscreen_window orderFront:self animate:YES];
571
572             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
573             [o_fullscreen_window setLevel:NSNormalWindowLevel];
574
575             if (blackout_other_displays) {
576                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
577                 CGReleaseDisplayFadeReservation(token);
578             }
579
580             /* Will release the lock */
581             [self hasBecomeFullscreen];
582
583             return;
584         }
585
586         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
587         NSDisableScreenUpdates();
588         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
589         [o_temp_view setFrame:[o_video_view frame]];
590         [o_fullscreen_window setContentView:o_video_view];
591         [o_fullscreen_window makeKeyAndOrderFront:self];
592         NSEnableScreenUpdates();
593     }
594
595     /* We are in fullscreen (and no animation is running) */
596     if ([[VLCMainWindow sharedInstance] fullscreen]) {
597         /* Make sure we are hidden */
598         [self orderOut: self];
599
600         [self unlockFullscreenAnimation];
601         return;
602     }
603
604     if (o_fullscreen_anim1) {
605         [o_fullscreen_anim1 stopAnimation];
606         [o_fullscreen_anim1 release];
607     }
608     if (o_fullscreen_anim2) {
609         [o_fullscreen_anim2 stopAnimation];
610         [o_fullscreen_anim2 release];
611     }
612
613     if ([screen mainScreen])
614         [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
615
616     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
617     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
618
619     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
620     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
621
622     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
623     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
624     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
625
626     /* Strategy with NSAnimation allocation:
627      - Keep at most 2 animation at a time
628      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
629      */
630     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
631     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
632
633     [dict1 release];
634     [dict2 release];
635
636     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
637     [o_fullscreen_anim1 setDuration: 0.3];
638     [o_fullscreen_anim1 setFrameRate: 30];
639     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
640     [o_fullscreen_anim2 setDuration: 0.2];
641     [o_fullscreen_anim2 setFrameRate: 30];
642
643     [o_fullscreen_anim2 setDelegate: self];
644     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
645
646     [o_fullscreen_anim1 startAnimation];
647     /* fullscreenAnimation will be unlocked when animation ends */
648 }
649
650 - (void)hasBecomeFullscreen
651 {
652     if ([[o_video_view subviews] count] > 0)
653         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
654
655     [o_fullscreen_window makeKeyWindow];
656     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
657
658     /* tell the fspanel to move itself to front next time it's triggered */
659     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
660     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
661
662     if ([self isVisible])
663         [self orderOut: self];
664
665     [[VLCMainWindow sharedInstance] setFullscreen:YES];
666     [self unlockFullscreenAnimation];
667 }
668
669 - (void)leaveFullscreen
670 {
671     [self leaveFullscreenAndFadeOut: NO];
672 }
673
674 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
675 {
676     NSMutableDictionary *dict1, *dict2;
677     NSRect frame;
678     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
679
680     [self lockFullscreenAnimation];
681
682     if (o_controls_bar)
683         [o_controls_bar setFullscreenState:NO];
684
685     /* We always try to do so */
686     [NSScreen unblackoutScreens];
687
688     vout_thread_t *p_vout = getVout();
689     if (p_vout) {
690         if (var_GetBool(p_vout, "video-on-top"))
691             [[o_video_view window] setLevel: NSStatusWindowLevel];
692         else
693             [[o_video_view window] setLevel: NSNormalWindowLevel];
694         vlc_object_release(p_vout);
695     }
696     [[o_video_view window] makeKeyAndOrderFront: nil];
697
698     /* Don't do anything if o_fullscreen_window is already closed */
699     if (!o_fullscreen_window) {
700         [self unlockFullscreenAnimation];
701         return;
702     }
703
704     if (fadeout) {
705         /* We don't animate if we are not visible, instead we
706          * simply fade the display */
707         CGDisplayFadeReservationToken token;
708
709         if (blackout_other_displays) {
710             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
711             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
712         }
713
714         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
715         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
716
717         /* Will release the lock */
718         [self hasEndedFullscreen];
719
720         /* Our window is hidden, and might be faded. We need to workaround that, so note it
721          * here */
722         b_window_is_invisible = YES;
723
724         if (blackout_other_displays) {
725             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
726             CGReleaseDisplayFadeReservation(token);
727         }
728
729         return;
730     }
731
732     [self setAlphaValue: 0.0];
733     [self orderFront: self];
734     [[o_video_view window] orderFront: self];
735
736     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
737     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
738
739     if (o_fullscreen_anim1) {
740         [o_fullscreen_anim1 stopAnimation];
741         [o_fullscreen_anim1 release];
742     }
743     if (o_fullscreen_anim2) {
744         [o_fullscreen_anim2 stopAnimation];
745         [o_fullscreen_anim2 release];
746     }
747
748     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
749     frame.origin.x += [self frame].origin.x;
750     frame.origin.y += [self frame].origin.y;
751
752     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
753     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
754     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
755
756     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
757     [dict2 release];
758
759     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
760     [o_fullscreen_anim2 setDuration: 0.3];
761     [o_fullscreen_anim2 setFrameRate: 30];
762
763     [o_fullscreen_anim2 setDelegate: self];
764
765     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
766
767     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
768     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
769     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
770
771     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
772     [dict1 release];
773
774     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
775     [o_fullscreen_anim1 setDuration: 0.2];
776     [o_fullscreen_anim1 setFrameRate: 30];
777     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
778
779     /* Make sure o_fullscreen_window is the frontmost window */
780     [o_fullscreen_window orderFront: self];
781
782     [o_fullscreen_anim1 startAnimation];
783     /* fullscreenAnimation will be unlocked when animation ends */
784 }
785
786 - (void)hasEndedFullscreen
787 {
788     [[VLCMainWindow sharedInstance] setFullscreen:NO];
789     
790     /* This function is private and should be only triggered at the end of the fullscreen change animation */
791     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
792     NSDisableScreenUpdates();
793     [o_video_view retain];
794     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
795     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
796     [o_video_view release];
797     [o_video_view setFrame:[o_temp_view frame]];
798     if ([[o_video_view subviews] count] > 0)
799         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
800
801     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
802
803     [o_fullscreen_window orderOut: self];
804     NSEnableScreenUpdates();
805
806     [o_fullscreen_window release];
807     o_fullscreen_window = nil;
808     [self setLevel:i_originalLevel];
809     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
810
811     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
812     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
813         [self orderOut: self];
814
815     [self unlockFullscreenAnimation];
816 }
817
818 - (void)animationDidEnd:(NSAnimation*)animation
819 {
820     NSArray *viewAnimations;
821     if (o_makekey_anim == animation) {
822         [o_makekey_anim release];
823         return;
824     }
825     if ([animation currentValue] < 1.0)
826         return;
827
828     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
829     viewAnimations = [o_fullscreen_anim2 viewAnimations];
830     if ([viewAnimations count] >=1 &&
831         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
832         /* Fullscreen ended */
833         [self hasEndedFullscreen];
834     } else
835     /* Fullscreen started */
836         [self hasBecomeFullscreen];
837 }
838
839 #pragma mark -
840 #pragma mark Accessibility stuff
841
842 - (NSArray *)accessibilityAttributeNames
843 {
844     if (!b_dark_interface || !o_titlebar_view)
845         return [super accessibilityAttributeNames];
846
847     static NSMutableArray *attributes = nil;
848     if (attributes == nil) {
849         attributes = [[super accessibilityAttributeNames] mutableCopy];
850         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
851                                      NSAccessibilityCloseButtonAttribute,
852                                      NSAccessibilityMinimizeButtonAttribute,
853                                      NSAccessibilityZoomButtonAttribute,
854                                      nil];
855
856         for(NSString *attribute in appendAttributes) {
857             if (![attributes containsObject:attribute])
858                 [attributes addObject:attribute];
859         }
860     }
861     return attributes;
862 }
863
864 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
865 {
866     if (b_dark_interface && o_titlebar_view) {
867         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
868
869         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
870             return NSAccessibilityStandardWindowSubrole;
871
872         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
873             return [[o_tbv closeButton] cell];
874
875         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
876             return [[o_tbv minimizeButton] cell];
877
878         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
879             return [[o_tbv zoomButton] cell];
880     }
881
882     return [super accessibilityAttributeValue: o_attribute_name];
883 }
884
885 @end