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