]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: reactivate no animation path for start in fullscreen
[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)enterFullscreenWithAnimation:(BOOL)b_animation
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         /* Make sure video view gets visible in case the playlist was visible before */
886         b_video_view_was_hidden = [o_video_view isHidden];
887         [o_video_view setHidden: NO];
888
889         if (!b_animation) {
890             /* We don't animate if we are not visible, instead we
891              * simply fade the display */
892             CGDisplayFadeReservationToken token;
893
894             if (blackout_other_displays) {
895                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
896                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
897             }
898
899             NSDisableScreenUpdates();
900             [o_video_view retain];
901             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
902             [o_temp_view setFrame:[o_video_view frame]];
903             [o_fullscreen_window setContentView:o_video_view];
904             [o_video_view release];
905             NSEnableScreenUpdates();
906
907             [screen setFullscreenPresentationOptions];
908
909             [o_fullscreen_window setFrame:screen_rect display:YES animate:NO];
910
911             [o_fullscreen_window orderFront:self animate:YES];
912
913             [o_fullscreen_window setLevel:NSNormalWindowLevel];
914
915             if (blackout_other_displays) {
916                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
917                 CGReleaseDisplayFadeReservation(token);
918             }
919
920             /* Will release the lock */
921             [self hasBecomeFullscreen];
922
923             return;
924         }
925
926         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
927         NSDisableScreenUpdates();
928         [o_video_view retain];
929         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
930         [o_temp_view setFrame:[o_video_view frame]];
931         [o_fullscreen_window setContentView:o_video_view];
932         [o_video_view release];
933         [o_fullscreen_window makeKeyAndOrderFront:self];
934         NSEnableScreenUpdates();
935     }
936
937     /* We are in fullscreen (and no animation is running) */
938     if ([self fullscreen]) {
939         /* Make sure we are hidden */
940         [self orderOut: self];
941
942         return;
943     }
944
945     if (o_fullscreen_anim1) {
946         [o_fullscreen_anim1 stopAnimation];
947         [o_fullscreen_anim1 release];
948     }
949     if (o_fullscreen_anim2) {
950         [o_fullscreen_anim2 stopAnimation];
951         [o_fullscreen_anim2 release];
952     }
953
954     [screen setFullscreenPresentationOptions];
955
956     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
957     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
958
959     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
960     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
961
962     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
963     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
964     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
965
966     /* Strategy with NSAnimation allocation:
967      - Keep at most 2 animation at a time
968      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
969      */
970     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
971     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
972
973     [dict1 release];
974     [dict2 release];
975
976     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
977     [o_fullscreen_anim1 setDuration: 0.3];
978     [o_fullscreen_anim1 setFrameRate: 30];
979     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
980     [o_fullscreen_anim2 setDuration: 0.2];
981     [o_fullscreen_anim2 setFrameRate: 30];
982
983     [o_fullscreen_anim2 setDelegate: self];
984     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
985
986     [o_fullscreen_anim1 startAnimation];
987     /* fullscreenAnimation will be unlocked when animation ends */
988
989     b_in_fullscreen_transition = YES;
990 }
991
992 - (void)hasBecomeFullscreen
993 {
994     if ([[o_video_view subviews] count] > 0)
995         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
996
997     [o_fullscreen_window makeKeyWindow];
998     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
999
1000     /* tell the fspanel to move itself to front next time it's triggered */
1001     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
1002     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
1003
1004     if ([self isVisible])
1005         [self orderOut: self];
1006
1007     b_in_fullscreen_transition = NO;
1008     [self setFullscreen:YES];
1009 }
1010
1011 - (void)leaveFullscreenWithAnimation:(BOOL)b_animation
1012 {
1013     NSMutableDictionary *dict1, *dict2;
1014     NSRect frame;
1015     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
1016
1017     if (o_controls_bar)
1018         [o_controls_bar setFullscreenState:NO];
1019     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
1020
1021     /* We always try to do so */
1022     [NSScreen unblackoutScreens];
1023
1024     [[o_video_view window] makeKeyAndOrderFront: nil];
1025
1026     /* Don't do anything if o_fullscreen_window is already closed */
1027     if (!o_fullscreen_window) {
1028         return;
1029     }
1030
1031     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
1032     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
1033
1034     if (o_fullscreen_anim1) {
1035         [o_fullscreen_anim1 stopAnimation];
1036         [o_fullscreen_anim1 release];
1037         o_fullscreen_anim1 = nil;
1038     }
1039     if (o_fullscreen_anim2) {
1040         [o_fullscreen_anim2 stopAnimation];
1041         [o_fullscreen_anim2 release];
1042         o_fullscreen_anim2 = nil;
1043     }
1044
1045     if (!b_animation) {
1046         /* We don't animate if we are not visible, instead we
1047          * simply fade the display */
1048         CGDisplayFadeReservationToken token;
1049
1050         if (blackout_other_displays) {
1051             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1052             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1053         }
1054
1055         [self setAlphaValue:1.0];
1056         [self orderFront: self];
1057
1058         /* Will release the lock */
1059         [self hasEndedFullscreen];
1060
1061         if (blackout_other_displays) {
1062             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1063             CGReleaseDisplayFadeReservation(token);
1064         }
1065
1066         return;
1067     }
1068
1069     b_in_fullscreen_transition = YES;
1070
1071     [self setAlphaValue: 0.0];
1072     [self orderFront: self];
1073     [[o_video_view window] orderFront: self];
1074
1075     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1076     frame.origin.x += [self frame].origin.x;
1077     frame.origin.y += [self frame].origin.y;
1078
1079     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1080     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1081     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1082
1083     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1084     [dict2 release];
1085
1086     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1087     [o_fullscreen_anim2 setDuration: 0.3];
1088     [o_fullscreen_anim2 setFrameRate: 30];
1089
1090     [o_fullscreen_anim2 setDelegate: self];
1091
1092     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1093
1094     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1095     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1096     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1097
1098     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1099     [dict1 release];
1100
1101     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1102     [o_fullscreen_anim1 setDuration: 0.2];
1103     [o_fullscreen_anim1 setFrameRate: 30];
1104     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1105
1106     /* Make sure o_fullscreen_window is the frontmost window */
1107     [o_fullscreen_window orderFront: self];
1108
1109     [o_fullscreen_anim1 startAnimation];
1110     /* fullscreenAnimation will be unlocked when animation ends */
1111 }
1112
1113 - (void)hasEndedFullscreen
1114 {
1115     [self setFullscreen:NO];
1116     b_in_fullscreen_transition = NO;
1117
1118     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1119     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1120     NSDisableScreenUpdates();
1121     [o_video_view retain];
1122     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1123     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1124     [o_video_view release];
1125     [o_video_view setFrame:[o_temp_view frame]];
1126     if ([[o_video_view subviews] count] > 0)
1127         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1128
1129     [o_video_view setHidden: b_video_view_was_hidden];
1130
1131     [self makeKeyAndOrderFront:self];
1132
1133     [o_fullscreen_window orderOut: self];
1134     NSEnableScreenUpdates();
1135
1136     [o_fullscreen_window release];
1137     o_fullscreen_window = nil;
1138
1139     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1140     [self setLevel:i_originalLevel];
1141
1142     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1143 }
1144
1145 - (void)animationDidEnd:(NSAnimation*)animation
1146 {
1147     NSArray *viewAnimations;
1148     if ([animation currentValue] < 1.0)
1149         return;
1150
1151     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1152     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1153     if ([viewAnimations count] >=1 &&
1154         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1155         /* Fullscreen ended */
1156         [self hasEndedFullscreen];
1157     } else
1158     /* Fullscreen started */
1159         [self hasBecomeFullscreen];
1160 }
1161
1162 #pragma mark -
1163 #pragma mark Accessibility stuff
1164
1165 - (NSArray *)accessibilityAttributeNames
1166 {
1167     if (!b_dark_interface || !o_titlebar_view)
1168         return [super accessibilityAttributeNames];
1169
1170     static NSMutableArray *attributes = nil;
1171     if (attributes == nil) {
1172         attributes = [[super accessibilityAttributeNames] mutableCopy];
1173         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1174                                      NSAccessibilityCloseButtonAttribute,
1175                                      NSAccessibilityMinimizeButtonAttribute,
1176                                      NSAccessibilityZoomButtonAttribute, nil];
1177
1178         for(NSString *attribute in appendAttributes) {
1179             if (![attributes containsObject:attribute])
1180                 [attributes addObject:attribute];
1181         }
1182     }
1183     return attributes;
1184 }
1185
1186 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1187 {
1188     if (b_dark_interface && o_titlebar_view) {
1189         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1190
1191         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1192             return NSAccessibilityStandardWindowSubrole;
1193
1194         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1195             return [[o_tbv closeButton] cell];
1196
1197         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1198             return [[o_tbv minimizeButton] cell];
1199
1200         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1201             return [[o_tbv zoomButton] cell];
1202     }
1203
1204     return [super accessibilityAttributeValue: o_attribute_name];
1205 }
1206
1207 @end