]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: reactivate fast path for exiting fullscreen window when video finished
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012-2014 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 #import "CompatibilityFixes.h"
31
32 /*****************************************************************************
33  * VLCWindow
34  *
35  *  Missing extension to NSWindow
36  *****************************************************************************/
37
38 @implementation VLCWindow
39
40 @synthesize hasActiveVideo=b_has_active_video;
41 @synthesize fullscreen=b_fullscreen;
42
43 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
44                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
45 {
46     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
47     if (self) {
48         /* we don't want this window to be restored on relaunch */
49         if (!OSX_SNOW_LEOPARD)
50             [self setRestorable:NO];
51     }
52     return self;
53 }
54
55 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
56 {
57     b_isset_canBecomeKeyWindow = YES;
58     b_canBecomeKeyWindow = canBecomeKey;
59 }
60
61 - (BOOL)canBecomeKeyWindow
62 {
63     if (b_isset_canBecomeKeyWindow)
64         return b_canBecomeKeyWindow;
65
66     return [super canBecomeKeyWindow];
67 }
68
69 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
70 {
71     b_isset_canBecomeMainWindow = YES;
72     b_canBecomeMainWindow = canBecomeMain;
73 }
74
75 - (BOOL)canBecomeMainWindow
76 {
77     if (b_isset_canBecomeMainWindow)
78         return b_canBecomeMainWindow;
79
80     return [super canBecomeMainWindow];
81 }
82
83 - (void)closeAndAnimate: (BOOL)animate
84 {
85     NSInvocation *invoc;
86
87     if (!animate) {
88         [super close];
89         return;
90     }
91
92     // TODO this callback stuff does not work and is not needed
93     invoc = [[[NSInvocation alloc] init] autorelease];
94     [invoc setSelector:@selector(close)];
95     [invoc setTarget: self];
96
97     if (![self isVisible] || [self alphaValue] == 0.0) {
98         [super close];
99         return;
100     }
101
102     [self orderOut: self animate: YES callback: invoc];
103 }
104
105 - (void)orderOut: (id)sender animate: (BOOL)animate
106 {
107     NSInvocation *invoc = [[[NSInvocation alloc] init] autorelease];
108     [invoc setSelector:@selector(orderOut:)];
109     [invoc setTarget: self];
110     [invoc setArgument: sender atIndex: 2];
111     [self orderOut: sender animate: animate callback: invoc];
112 }
113
114 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
115 {
116     NSViewAnimation *anim;
117     NSViewAnimation *current_anim;
118     NSMutableDictionary *dict;
119
120     if (!animate) {
121         [self orderOut: sender];
122         return;
123     }
124
125     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
126
127     [dict setObject:self forKey:NSViewAnimationTargetKey];
128
129     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
130     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
131     [dict release];
132
133     [anim setAnimationBlockingMode:NSAnimationNonblocking];
134     [anim setDuration:0.9];
135     [anim setFrameRate:30];
136     [anim setUserInfo:callback];
137     [anim setDelegate:self];
138
139     @synchronized(self) {
140         current_anim = self->o_current_animation;
141
142         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
143             [anim release];
144         } else {
145             if (current_anim) {
146                 [current_anim stopAnimation];
147                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
148                 [current_anim release];
149             }
150             else
151                 [anim setCurrentProgress:1.0 - [self alphaValue]];
152             self->o_current_animation = anim;
153             [anim startAnimation];
154         }
155     }
156 }
157
158 - (void)orderFront: (id)sender animate: (BOOL)animate
159 {
160     NSViewAnimation *anim;
161     NSViewAnimation *current_anim;
162     NSMutableDictionary *dict;
163
164     if (!animate) {
165         [super orderFront: sender];
166         [self setAlphaValue: 1.0];
167         return;
168     }
169
170     if (![self isVisible]) {
171         [self setAlphaValue: 0.0];
172         [super orderFront: sender];
173     }
174     else if ([self alphaValue] == 1.0) {
175         [super orderFront: self];
176         return;
177     }
178
179     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
180
181     [dict setObject:self forKey:NSViewAnimationTargetKey];
182
183     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
184     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
185     [dict release];
186
187     [anim setAnimationBlockingMode:NSAnimationNonblocking];
188     [anim setDuration:0.5];
189     [anim setFrameRate:30];
190     [anim setDelegate:self];
191
192     @synchronized(self) {
193         current_anim = self->o_current_animation;
194
195         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
196             [anim release];
197         } else {
198             if (current_anim) {
199                 [current_anim stopAnimation];
200                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
201                 [current_anim release];
202             }
203             else
204                 [anim setCurrentProgress:[self alphaValue]];
205             self->o_current_animation = anim;
206             [self orderFront: sender];
207             [anim startAnimation];
208         }
209     }
210 }
211
212 - (void)animationDidEnd:(NSAnimation*)anim
213 {
214     if ([self alphaValue] <= 0.0) {
215         NSInvocation * invoc;
216         [super orderOut: nil];
217         [self setAlphaValue: 1.0];
218         if ((invoc = [anim userInfo])) {
219             [invoc invoke];
220         }
221     }
222 }
223
224 - (VLCVoutView *)videoView
225 {
226     if ([[self contentView] class] == [VLCVoutView class])
227         return (VLCVoutView *)[self contentView];
228
229     return nil;
230 }
231
232 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
233 {
234     if (!screen)
235         screen = [self screen];
236     NSRect screenRect = [screen frame];
237     NSRect constrainedRect = [super constrainFrameRect:frameRect toScreen:screen];
238
239     /*
240      * Ugly workaround!
241      * With Mavericks, there is a nasty bug resulting in grey bars on top in fullscreen mode.
242      * It looks like this is enforced by the os because the window is in the way for the menu bar.
243      *
244      * According to the documentation, this constraining can be changed by overwriting this
245      * method. But in this situation, even the received frameRect is already contrained with the
246      * menu bars height substracted. This case is detected here, and the full height is
247      * enforced again.
248      *
249      * See #9469 and radar://15583566
250      */
251
252     BOOL b_inFullscreen = [self fullscreen] || ([self respondsToSelector:@selector(inFullscreenTransition)] && [(VLCVideoWindowCommon *)self inFullscreenTransition]);
253
254     if(OSX_MAVERICKS && b_inFullscreen && constrainedRect.size.width == screenRect.size.width
255           && constrainedRect.size.height != screenRect.size.height
256           && abs(screenRect.size.height - constrainedRect.size.height) <= 25.) {
257
258         msg_Dbg(VLCIntf, "Contrain window height %.1f to screen height %.1f",
259                 constrainedRect.size.height, screenRect.size.height);
260         constrainedRect.size.height = screenRect.size.height;
261     }
262
263     return constrainedRect;
264 }
265
266 @end
267
268
269 /*****************************************************************************
270  * VLCVideoWindowCommon
271  *
272  *  Common code for main window, detached window and extra video window
273  *****************************************************************************/
274
275 @interface VLCVideoWindowCommon (Internal)
276 - (void)customZoom:(id)sender;
277 - (void)hasBecomeFullscreen;
278 - (void)hasEndedFullscreen;
279 @end
280
281 @implementation VLCVideoWindowCommon
282
283 @synthesize videoView=o_video_view;
284 @synthesize controlsBar=o_controls_bar;
285 @synthesize inFullscreenTransition=b_in_fullscreen_transition;
286
287 #pragma mark -
288 #pragma mark Init
289
290 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
291                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
292 {
293     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
294
295     if (b_dark_interface) {
296         styleMask = NSBorderlessWindowMask;
297 #ifdef MAC_OS_X_VERSION_10_7
298         if (!OSX_SNOW_LEOPARD)
299             styleMask |= NSResizableWindowMask;
300 #endif
301     }
302
303     self = [super initWithContentRect:contentRect styleMask:styleMask
304                               backing:backingType defer:flag];
305
306     /* we want to be moveable regardless of our style */
307     [self setMovableByWindowBackground: YES];
308     [self setCanBecomeKeyWindow:YES];
309
310     o_temp_view = [[NSView alloc] init];
311     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
312
313     return self;
314 }
315
316 - (void)dealloc
317 {
318     [o_temp_view release];
319     [super dealloc];
320 }
321
322 - (void)awakeFromNib
323 {
324     BOOL b_nativeFullscreenMode = NO;
325 #ifdef MAC_OS_X_VERSION_10_7
326     if (!OSX_SNOW_LEOPARD)
327         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
328 #endif
329
330     if (b_nativeFullscreenMode) {
331         [self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
332     } else {
333         [o_titlebar_view setFullscreenButtonHidden: YES];
334     }
335
336     [super awakeFromNib];
337 }
338
339 - (void)setTitle:(NSString *)title
340 {
341     if (!title || [title length] < 1)
342         return;
343
344     if (b_dark_interface && o_titlebar_view)
345         [o_titlebar_view setWindowTitle: title];
346
347     [super setTitle: title];
348 }
349
350 #pragma mark -
351 #pragma mark zoom / minimize / close
352
353 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
354 {
355     SEL s_menuAction = [menuItem action];
356
357     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
358         return YES;
359
360     return [super validateMenuItem:menuItem];
361 }
362
363 - (BOOL)windowShouldClose:(id)sender
364 {
365     return YES;
366 }
367
368 - (void)performClose:(id)sender
369 {
370     if (!([self styleMask] & NSTitledWindowMask)) {
371         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
372
373         [self close];
374     } else
375         [super performClose: sender];
376 }
377
378 - (void)performMiniaturize:(id)sender
379 {
380     if (!([self styleMask] & NSTitledWindowMask))
381         [self miniaturize: sender];
382     else
383         [super performMiniaturize: sender];
384 }
385
386 - (void)performZoom:(id)sender
387 {
388     if (!([self styleMask] & NSTitledWindowMask))
389         [self customZoom: sender];
390     else
391         [super performZoom: sender];
392 }
393
394 - (void)zoom:(id)sender
395 {
396     if (!([self styleMask] & NSTitledWindowMask))
397         [self customZoom: sender];
398     else
399         [super zoom: sender];
400 }
401
402 /**
403  * Given a proposed frame rectangle, return a modified version
404  * which will fit inside the screen.
405  *
406  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
407  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
408  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
409  *    Copyright (C) 1996 Free Software Foundation, Inc.
410  */
411 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
412 {
413     NSRect screenRect = [screen visibleFrame];
414     CGFloat difference;
415
416     /* Move top edge of the window inside the screen */
417     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
418     if (difference > 0) {
419         frameRect.origin.y -= difference;
420     }
421
422     /* If the window is resizable, resize it (if needed) so that the
423      bottom edge is on the screen or can be on the screen when the user moves
424      the window */
425     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
426     if (_styleMask & NSResizableWindowMask) {
427         CGFloat difference2;
428
429         difference2 = screenRect.origin.y - frameRect.origin.y;
430         difference2 -= difference;
431         // Take in account the space between the top of window and the top of the
432         // screen which can be used to move the bottom of the window on the screen
433         if (difference2 > 0) {
434             frameRect.size.height -= difference2;
435             frameRect.origin.y += difference2;
436         }
437
438         /* Ensure that resizing doesn't makewindow smaller than minimum */
439         difference2 = [self minSize].height - frameRect.size.height;
440         if (difference2 > 0) {
441             frameRect.size.height += difference2;
442             frameRect.origin.y -= difference2;
443         }
444     }
445
446     return frameRect;
447 }
448
449 #define DIST 3
450
451 /**
452  Zooms the receiver.   This method calls the delegate method
453  windowShouldZoom:toFrame: to determine if the window should
454  be allowed to zoom to full screen.
455  *
456  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
457  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
458  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
459  *    Copyright (C) 1996 Free Software Foundation, Inc.
460  */
461 - (void) customZoom: (id)sender
462 {
463     NSRect maxRect = [[self screen] visibleFrame];
464     NSRect currentFrame = [self frame];
465
466     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
467         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
468     }
469
470     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
471
472     // Compare the new frame with the current one
473     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
474         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
475         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
476         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
477         // Already in zoomed mode, reset user frame, if stored
478         if ([self frameAutosaveName] != nil) {
479             [self setFrame: previousSavedFrame display: YES animate: YES];
480             [self saveFrameUsingName: [self frameAutosaveName]];
481         }
482         return;
483     }
484
485     if ([self frameAutosaveName] != nil) {
486         [self saveFrameUsingName: [self frameAutosaveName]];
487         previousSavedFrame = [self frame];
488     }
489
490     [self setFrame: maxRect display: YES animate: YES];
491 }
492
493 #pragma mark -
494 #pragma mark Video window resizing logic
495
496 - (void)setWindowLevel:(NSInteger)i_state
497 {
498     if (var_InheritBool(VLCIntf, "video-wallpaper") || [self level] < NSNormalWindowLevel)
499         return;
500
501     if (!b_fullscreen && !b_in_fullscreen_transition)
502         [self setLevel: i_state];
503
504     // save it for restore if window is currently minimized or in fullscreen
505     i_originalLevel = i_state;
506 }
507
508 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
509 {
510     NSSize windowMinSize = [self minSize];
511     NSRect screenFrame = [[self screen] visibleFrame];
512
513     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
514     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
515
516     CGFloat f_width = size.width;
517     CGFloat f_height = size.height;
518     if (f_width < windowMinSize.width)
519         f_width = windowMinSize.width;
520     if (f_height < f_min_video_height)
521         f_height = f_min_video_height;
522
523     /* Calculate the window's new size */
524     NSRect new_frame;
525     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + f_width;
526     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + f_height;
527     new_frame.origin.x = topleftscreen.x;
528     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
529
530     /* make sure the window doesn't exceed the screen size the window is on */
531     if (new_frame.size.width > screenFrame.size.width) {
532         new_frame.size.width = screenFrame.size.width;
533         new_frame.origin.x = screenFrame.origin.x;
534     }
535     if (new_frame.size.height > screenFrame.size.height) {
536         new_frame.size.height = screenFrame.size.height;
537         new_frame.origin.y = screenFrame.origin.y;
538     }
539     if (new_frame.origin.y < screenFrame.origin.y)
540         new_frame.origin.y = screenFrame.origin.y;
541
542     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
543     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
544     if (right_window_point > right_screen_point)
545         new_frame.origin.x -= (right_window_point - right_screen_point);
546
547     return new_frame;
548 }
549
550 - (void)resizeWindow
551 {
552     // VOUT_WINDOW_SET_SIZE is triggered when exiting fullscreen. This event is ignored here
553     // to avoid interference with the animation.
554     if ([self fullscreen] || b_in_fullscreen_transition)
555         return;
556
557     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
558     [[self animator] setFrame:window_rect display:YES];
559 }
560
561 - (void)setNativeVideoSize:(NSSize)size
562 {
563     nativeVideoSize = size;
564
565     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
566         [self resizeWindow];
567 }
568
569 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
570 {
571     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
572         return proposedFrameSize;
573
574     // needed when entering lion fullscreen mode
575     if (b_in_fullscreen_transition || [self fullscreen])
576         return proposedFrameSize;
577
578     if ([o_video_view isHidden])
579         return proposedFrameSize;
580
581     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
582         NSRect videoWindowFrame = [self frame];
583         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
584         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
585         CGFloat marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
586         CGFloat marginx = contentRect.size.width - viewRect.size.width;
587         if (o_titlebar_view && b_dark_interface)
588             marginy += [o_titlebar_view frame].size.height;
589
590         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
591     }
592
593     return proposedFrameSize;
594 }
595
596 - (void)windowWillMiniaturize:(NSNotification *)notification
597 {
598     // Set level to normal as a workaround for Mavericks bug causing window
599     // to vanish from screen, see radar://15473716
600     i_originalLevel = [self level];
601     [self setLevel: NSNormalWindowLevel];
602 }
603
604 - (void)windowDidDeminiaturize:(NSNotification *)notification
605 {
606     [self setLevel: i_originalLevel];
607 }
608
609 #pragma mark -
610 #pragma mark Mouse cursor handling
611
612 //  NSTimer selectors require this function signature as per Apple's docs
613 - (void)hideMouseCursor:(NSTimer *)timer
614 {
615     [NSCursor setHiddenUntilMouseMoves: YES];
616 }
617
618 - (void)recreateHideMouseTimer
619 {
620     if (t_hide_mouse_timer != nil) {
621         [t_hide_mouse_timer invalidate];
622         [t_hide_mouse_timer release];
623     }
624
625     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
626                                                           target:self
627                                                         selector:@selector(hideMouseCursor:)
628                                                         userInfo:nil
629                                                          repeats:NO];
630     [t_hide_mouse_timer retain];
631 }
632
633 //  Called automatically if window's acceptsMouseMovedEvents property is true
634 - (void)mouseMoved:(NSEvent *)theEvent
635 {
636     if (b_fullscreen)
637         [self recreateHideMouseTimer];
638
639     [super mouseMoved: theEvent];
640 }
641
642 #pragma mark -
643 #pragma mark Lion native fullscreen handling
644
645 - (void)becomeKeyWindow
646 {
647     [super becomeKeyWindow];
648
649     // change fspanel state for the case when multiple windows are in fullscreen
650     if ([self hasActiveVideo] && [self fullscreen])
651         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
652     else
653         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
654 }
655
656 - (void)resignKeyWindow
657 {
658     [super resignKeyWindow];
659
660     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
661 }
662
663 -(NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
664 {
665     if (window == self) {
666         return [NSArray arrayWithObject:window];
667     }
668
669     return nil;
670 }
671
672 - (NSArray*)customWindowsToExitFullScreenForWindow:(NSWindow*)window
673 {
674     if (window == self) {
675         return [NSArray arrayWithObject:window];
676     }
677
678     return nil;
679 }
680
681 - (void)window:window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
682 {
683     [window setStyleMask:([window styleMask] | NSFullScreenWindowMask)];
684
685     NSScreen *screen = [window screen];
686     NSRect screenFrame = [screen frame];
687
688     [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
689         [context setDuration:0.5 * duration];
690         [[window animator] setFrame:screenFrame display:YES];
691     } completionHandler:nil];
692 }
693
694 - (void)window:window startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration
695 {
696     [window setStyleMask:([window styleMask] & ~NSFullScreenWindowMask)];
697     [[window animator] setFrame:frameBeforeLionFullscreen display:YES animate:YES];
698
699     [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
700         [context setDuration:0.5 * duration];
701         [[window animator] setFrame:frameBeforeLionFullscreen display:YES animate:YES];
702     } completionHandler:nil];
703 }
704
705 - (void)windowWillEnterFullScreen:(NSNotification *)notification
706 {
707     // workaround, see #6668
708     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
709
710     i_originalLevel = [self level];
711     // b_fullscreen and b_in_fullscreen_transition must not be true yet
712     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
713     [self setLevel:NSNormalWindowLevel];
714
715     b_in_fullscreen_transition = YES;
716
717     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
718
719     frameBeforeLionFullscreen = [self frame];
720
721     if ([self hasActiveVideo]) {
722         vout_thread_t *p_vout = getVoutForActiveWindow();
723         if (p_vout) {
724             var_SetBool(p_vout, "fullscreen", true);
725             vlc_object_release(p_vout);
726         }
727     }
728
729     if ([self hasActiveVideo])
730         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
731
732     if (b_dark_interface) {
733         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
734
735         NSRect winrect;
736         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
737         winrect = [self frame];
738
739         winrect.size.height = winrect.size.height - f_titleBarHeight;
740         [self setFrame: winrect display:NO animate:NO];
741     }
742
743     [o_video_view setFrame: [[self contentView] frame]];
744     if (![o_video_view isHidden]) {
745         [[o_controls_bar bottomBarView] setHidden: YES];
746     }
747
748     [self setMovableByWindowBackground: NO];
749 }
750
751 - (void)windowDidEnterFullScreen:(NSNotification *)notification
752 {
753     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
754     // But this creates some problems when leaving fs over remote intfs, so activate app here.
755     [NSApp activateIgnoringOtherApps:YES];
756
757     [self setFullscreen: YES];
758     b_in_fullscreen_transition = NO;
759
760     if ([self hasActiveVideo]) {
761         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
762         if (![o_video_view isHidden])
763             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
764     }
765
766     NSArray *subviews = [[self videoView] subviews];
767     NSUInteger count = [subviews count];
768
769     for (NSUInteger x = 0; x < count; x++) {
770         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
771             [[subviews objectAtIndex:x] reshape];
772     }
773
774 }
775
776 - (void)windowWillExitFullScreen:(NSNotification *)notification
777 {
778     b_in_fullscreen_transition = YES;
779     [self setFullscreen: NO];
780
781     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
782
783     if ([self hasActiveVideo]) {
784         vout_thread_t *p_vout = getVoutForActiveWindow();
785         if (p_vout) {
786             var_SetBool(p_vout, "fullscreen", false);
787             vlc_object_release(p_vout);
788         }
789     }
790
791     [NSCursor setHiddenUntilMouseMoves: NO];
792     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
793
794
795     if (b_dark_interface) {
796         NSRect winrect;
797         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
798
799         winrect = [o_video_view frame];
800         winrect.size.height -= f_titleBarHeight;
801         [o_video_view setFrame: winrect];
802
803         winrect = [self frame];
804         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
805                                               winrect.size.width, f_titleBarHeight)];
806         [[self contentView] addSubview: o_titlebar_view];
807
808         winrect.size.height = winrect.size.height + f_titleBarHeight;
809         [self setFrame: winrect display:NO animate:NO];
810     }
811
812     NSRect videoViewFrame = [o_video_view frame];
813     videoViewFrame.origin.y += [o_controls_bar height];
814     videoViewFrame.size.height -= [o_controls_bar height];
815     [o_video_view setFrame: videoViewFrame];
816
817     if (![o_video_view isHidden]) {
818         [[o_controls_bar bottomBarView] setHidden: NO];
819     }
820
821     [self setMovableByWindowBackground: YES];
822 }
823
824 - (void)windowDidExitFullScreen:(NSNotification *)notification
825 {
826     b_in_fullscreen_transition = NO;
827
828     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
829     [self setLevel:i_originalLevel];
830 }
831
832 #pragma mark -
833 #pragma mark Fullscreen Logic
834
835 - (void)enterFullscreen
836 {
837     NSMutableDictionary *dict1, *dict2;
838     NSScreen *screen;
839     NSRect screen_rect;
840     NSRect rect;
841     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
842
843     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
844
845     if (!screen) {
846         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
847         screen = [self screen];
848     }
849     if (!screen) {
850         msg_Dbg(VLCIntf, "Using deepest screen");
851         screen = [NSScreen deepestScreen];
852     }
853
854     screen_rect = [screen frame];
855
856     if (o_controls_bar)
857         [o_controls_bar setFullscreenState:YES];
858     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
859
860     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
861
862     if (blackout_other_displays)
863         [screen blackoutOtherScreens];
864
865     /* Make sure we don't see the window flashes in float-on-top mode */
866     i_originalLevel = [self level];
867     // b_fullscreen must not be true yet
868     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
869     [self setLevel:NSNormalWindowLevel];
870
871     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
872     if (!o_fullscreen_window) {
873         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
874
875         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
876         rect.origin.x += [self frame].origin.x;
877         rect.origin.y += [self frame].origin.y;
878         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
879         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
880         [o_fullscreen_window setCanBecomeKeyWindow: YES];
881         [o_fullscreen_window setCanBecomeMainWindow: YES];
882         [o_fullscreen_window setHasActiveVideo: YES];
883         [o_fullscreen_window setFullscreen: YES];
884
885         if (![self isVisible] || [self alphaValue] == 0.0) {
886             /* We don't animate if we are not visible, instead we
887              * simply fade the display */
888             CGDisplayFadeReservationToken token;
889
890             if (blackout_other_displays) {
891                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
892                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
893             }
894
895             [screen setFullscreenPresentationOptions];
896
897             [o_video_view retain];
898             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
899             [o_temp_view setFrame:[o_video_view frame]];
900             [o_fullscreen_window setContentView:o_video_view];
901             [o_video_view release];
902
903             [o_fullscreen_window makeKeyAndOrderFront:self];
904             [o_fullscreen_window orderFront:self animate:YES];
905
906             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
907             [o_fullscreen_window setLevel:NSNormalWindowLevel];
908
909             if (blackout_other_displays) {
910                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
911                 CGReleaseDisplayFadeReservation(token);
912             }
913
914             /* Will release the lock */
915             [self hasBecomeFullscreen];
916
917             return;
918         }
919
920         /* Make sure video view gets visible in case the playlist was visible before */
921         b_video_view_was_hidden = [o_video_view isHidden];
922         [o_video_view setHidden: NO];
923
924         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
925         NSDisableScreenUpdates();
926         [o_video_view retain];
927         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
928         [o_temp_view setFrame:[o_video_view frame]];
929         [o_fullscreen_window setContentView:o_video_view];
930         [o_video_view release];
931         [o_fullscreen_window makeKeyAndOrderFront:self];
932         NSEnableScreenUpdates();
933     }
934
935     /* We are in fullscreen (and no animation is running) */
936     if ([self fullscreen]) {
937         /* Make sure we are hidden */
938         [self orderOut: self];
939
940         return;
941     }
942
943     if (o_fullscreen_anim1) {
944         [o_fullscreen_anim1 stopAnimation];
945         [o_fullscreen_anim1 release];
946     }
947     if (o_fullscreen_anim2) {
948         [o_fullscreen_anim2 stopAnimation];
949         [o_fullscreen_anim2 release];
950     }
951
952     [screen setFullscreenPresentationOptions];
953
954     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
955     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
956
957     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
958     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
959
960     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
961     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
962     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
963
964     /* Strategy with NSAnimation allocation:
965      - Keep at most 2 animation at a time
966      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
967      */
968     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
969     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
970
971     [dict1 release];
972     [dict2 release];
973
974     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
975     [o_fullscreen_anim1 setDuration: 0.3];
976     [o_fullscreen_anim1 setFrameRate: 30];
977     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
978     [o_fullscreen_anim2 setDuration: 0.2];
979     [o_fullscreen_anim2 setFrameRate: 30];
980
981     [o_fullscreen_anim2 setDelegate: self];
982     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
983
984     [o_fullscreen_anim1 startAnimation];
985     /* fullscreenAnimation will be unlocked when animation ends */
986
987     b_in_fullscreen_transition = YES;
988 }
989
990 - (void)hasBecomeFullscreen
991 {
992     if ([[o_video_view subviews] count] > 0)
993         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
994
995     [o_fullscreen_window makeKeyWindow];
996     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
997
998     /* tell the fspanel to move itself to front next time it's triggered */
999     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
1000     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
1001
1002     if ([self isVisible])
1003         [self orderOut: self];
1004
1005     b_in_fullscreen_transition = NO;
1006     [self setFullscreen:YES];
1007 }
1008
1009 - (void)leaveFullscreenWithAnimation:(BOOL)b_animation
1010 {
1011     NSMutableDictionary *dict1, *dict2;
1012     NSRect frame;
1013     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
1014
1015     if (o_controls_bar)
1016         [o_controls_bar setFullscreenState:NO];
1017     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
1018
1019     /* We always try to do so */
1020     [NSScreen unblackoutScreens];
1021
1022     [[o_video_view window] makeKeyAndOrderFront: nil];
1023
1024     /* Don't do anything if o_fullscreen_window is already closed */
1025     if (!o_fullscreen_window) {
1026         return;
1027     }
1028
1029     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
1030     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
1031
1032     if (o_fullscreen_anim1) {
1033         [o_fullscreen_anim1 stopAnimation];
1034         [o_fullscreen_anim1 release];
1035         o_fullscreen_anim1 = nil;
1036     }
1037     if (o_fullscreen_anim2) {
1038         [o_fullscreen_anim2 stopAnimation];
1039         [o_fullscreen_anim2 release];
1040         o_fullscreen_anim2 = nil;
1041     }
1042
1043     if (!b_animation) {
1044         /* We don't animate if we are not visible, instead we
1045          * simply fade the display */
1046         CGDisplayFadeReservationToken token;
1047
1048         if (blackout_other_displays) {
1049             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1050             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1051         }
1052
1053         [self setAlphaValue:1.0];
1054         [self orderFront: self];
1055
1056         /* Will release the lock */
1057         [self hasEndedFullscreen];
1058
1059         if (blackout_other_displays) {
1060             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1061             CGReleaseDisplayFadeReservation(token);
1062         }
1063
1064         return;
1065     }
1066
1067     b_in_fullscreen_transition = YES;
1068
1069     [self setAlphaValue: 0.0];
1070     [self orderFront: self];
1071     [[o_video_view window] orderFront: self];
1072
1073     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1074     frame.origin.x += [self frame].origin.x;
1075     frame.origin.y += [self frame].origin.y;
1076
1077     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1078     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1079     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1080
1081     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1082     [dict2 release];
1083
1084     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1085     [o_fullscreen_anim2 setDuration: 0.3];
1086     [o_fullscreen_anim2 setFrameRate: 30];
1087
1088     [o_fullscreen_anim2 setDelegate: self];
1089
1090     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1091
1092     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1093     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1094     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1095
1096     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1097     [dict1 release];
1098
1099     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1100     [o_fullscreen_anim1 setDuration: 0.2];
1101     [o_fullscreen_anim1 setFrameRate: 30];
1102     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1103
1104     /* Make sure o_fullscreen_window is the frontmost window */
1105     [o_fullscreen_window orderFront: self];
1106
1107     [o_fullscreen_anim1 startAnimation];
1108     /* fullscreenAnimation will be unlocked when animation ends */
1109 }
1110
1111 - (void)hasEndedFullscreen
1112 {
1113     [self setFullscreen:NO];
1114     b_in_fullscreen_transition = NO;
1115
1116     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1117     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1118     NSDisableScreenUpdates();
1119     [o_video_view retain];
1120     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1121     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1122     [o_video_view release];
1123     [o_video_view setFrame:[o_temp_view frame]];
1124     if ([[o_video_view subviews] count] > 0)
1125         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1126
1127     [o_video_view setHidden: b_video_view_was_hidden];
1128
1129     [self makeKeyAndOrderFront:self];
1130
1131     [o_fullscreen_window orderOut: self];
1132     NSEnableScreenUpdates();
1133
1134     [o_fullscreen_window release];
1135     o_fullscreen_window = nil;
1136
1137     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1138     [self setLevel:i_originalLevel];
1139
1140     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1141 }
1142
1143 - (void)animationDidEnd:(NSAnimation*)animation
1144 {
1145     NSArray *viewAnimations;
1146     if ([animation currentValue] < 1.0)
1147         return;
1148
1149     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1150     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1151     if ([viewAnimations count] >=1 &&
1152         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1153         /* Fullscreen ended */
1154         [self hasEndedFullscreen];
1155     } else
1156     /* Fullscreen started */
1157         [self hasBecomeFullscreen];
1158 }
1159
1160 #pragma mark -
1161 #pragma mark Accessibility stuff
1162
1163 - (NSArray *)accessibilityAttributeNames
1164 {
1165     if (!b_dark_interface || !o_titlebar_view)
1166         return [super accessibilityAttributeNames];
1167
1168     static NSMutableArray *attributes = nil;
1169     if (attributes == nil) {
1170         attributes = [[super accessibilityAttributeNames] mutableCopy];
1171         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1172                                      NSAccessibilityCloseButtonAttribute,
1173                                      NSAccessibilityMinimizeButtonAttribute,
1174                                      NSAccessibilityZoomButtonAttribute, nil];
1175
1176         for(NSString *attribute in appendAttributes) {
1177             if (![attributes containsObject:attribute])
1178                 [attributes addObject:attribute];
1179         }
1180     }
1181     return attributes;
1182 }
1183
1184 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1185 {
1186     if (b_dark_interface && o_titlebar_view) {
1187         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1188
1189         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1190             return NSAccessibilityStandardWindowSubrole;
1191
1192         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1193             return [[o_tbv closeButton] cell];
1194
1195         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1196             return [[o_tbv minimizeButton] cell];
1197
1198         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1199             return [[o_tbv zoomButton] cell];
1200     }
1201
1202     return [super accessibilityAttributeValue: o_attribute_name];
1203 }
1204
1205 @end