misc: ensure vlc_object_t is correctly aligned
[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: NSMainMenuWindowLevel + 1];
665     [self setLevel:NSMainMenuWindowLevel + 1];
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: NSMainMenuWindowLevel + 1];
810     [self setLevel:NSMainMenuWindowLevel + 1];
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         [o_fullscreen_window setLevel:NSMainMenuWindowLevel + 1];
827
828         /* Make sure video view gets visible in case the playlist was visible before */
829         b_video_view_was_hidden = [_videoView isHidden];
830         [_videoView setHidden: NO];
831         _videoView.translatesAutoresizingMaskIntoConstraints = YES;
832
833         if (!b_animation) {
834             /* We don't animate if we are not visible, instead we
835              * simply fade the display */
836             CGDisplayFadeReservationToken token;
837
838             if (blackout_other_displays) {
839                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
840                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
841             }
842
843             NSDisableScreenUpdates();
844             [[_videoView superview] replaceSubview:_videoView with:o_temp_view];
845             [o_temp_view setFrame:[_videoView frame]];
846             [[o_fullscreen_window contentView] addSubview:_videoView];
847             [_videoView setFrame: [[o_fullscreen_window contentView] frame]];
848             [_videoView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
849             NSEnableScreenUpdates();
850
851             [screen setFullscreenPresentationOptions];
852
853             [o_fullscreen_window setFrame:screen_rect display:YES animate:NO];
854
855             [o_fullscreen_window orderFront:self animate:YES];
856
857             if (blackout_other_displays) {
858                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
859                 CGReleaseDisplayFadeReservation(token);
860             }
861
862             /* Will release the lock */
863             [self hasBecomeFullscreen];
864
865             return;
866         }
867
868         /* Make sure we don't see the _videoView disappearing of the screen during this operation */
869         NSDisableScreenUpdates();
870         [[_videoView superview] replaceSubview:_videoView with:o_temp_view];
871         [o_temp_view setFrame:[_videoView frame]];
872         [[o_fullscreen_window contentView] addSubview:_videoView];
873         [_videoView setFrame: [[o_fullscreen_window contentView] frame]];
874         [_videoView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
875
876         [o_fullscreen_window makeKeyAndOrderFront:self];
877         NSEnableScreenUpdates();
878     }
879
880     /* We are in fullscreen (and no animation is running) */
881     if ([self fullscreen]) {
882         /* Make sure we are hidden */
883         [self orderOut: self];
884
885         return;
886     }
887
888     if (o_fullscreen_anim1) {
889         [o_fullscreen_anim1 stopAnimation];
890     }
891     if (o_fullscreen_anim2) {
892         [o_fullscreen_anim2 stopAnimation];
893     }
894
895     [screen setFullscreenPresentationOptions];
896
897     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
898     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
899
900     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
901     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
902
903     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
904     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
905     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
906
907     /* Strategy with NSAnimation allocation:
908      - Keep at most 2 animation at a time
909      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
910      */
911     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
912     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
913
914     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
915     [o_fullscreen_anim1 setDuration: 0.3];
916     [o_fullscreen_anim1 setFrameRate: 30];
917     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
918     [o_fullscreen_anim2 setDuration: 0.2];
919     [o_fullscreen_anim2 setFrameRate: 30];
920
921     [o_fullscreen_anim2 setDelegate: self];
922     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
923
924     [o_fullscreen_anim1 startAnimation];
925     /* fullscreenAnimation will be unlocked when animation ends */
926
927     _inFullscreenTransition = YES;
928 }
929
930 - (void)hasBecomeFullscreen
931 {
932     if ([[_videoView subviews] count] > 0)
933         [o_fullscreen_window makeFirstResponder: [[_videoView subviews] firstObject]];
934
935     [o_fullscreen_window makeKeyWindow];
936     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
937
938     /* tell the fspanel to move itself to front next time it's triggered */
939     [[[[VLCMain sharedInstance] mainWindow] fspanel] setVoutWasUpdated:o_fullscreen_window];
940     [[[[VLCMain sharedInstance] mainWindow] fspanel] setActive];
941
942     if ([self isVisible])
943         [self orderOut: self];
944
945     _inFullscreenTransition = NO;
946     [self setFullscreen:YES];
947 }
948
949 - (void)leaveFullscreenWithAnimation:(BOOL)b_animation
950 {
951     NSMutableDictionary *dict1, *dict2;
952     NSRect frame;
953     BOOL blackout_other_displays = var_InheritBool(getIntf(), "macosx-black");
954
955     if (self.controlsBar)
956         [self.controlsBar setFullscreenState:NO];
957     [[[[VLCMain sharedInstance] mainWindow] controlsBar] setFullscreenState:NO];
958
959     /* We always try to do so */
960     [NSScreen unblackoutScreens];
961
962     [[_videoView window] makeKeyAndOrderFront: nil];
963
964     /* Don't do anything if o_fullscreen_window is already closed */
965     if (!o_fullscreen_window) {
966         return;
967     }
968
969     [[[[VLCMain sharedInstance] mainWindow] fspanel] setNonActive];
970     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
971
972     if (o_fullscreen_anim1) {
973         [o_fullscreen_anim1 stopAnimation];
974         o_fullscreen_anim1 = nil;
975     }
976     if (o_fullscreen_anim2) {
977         [o_fullscreen_anim2 stopAnimation];
978         o_fullscreen_anim2 = nil;
979     }
980
981     _inFullscreenTransition = YES;
982     [self setFullscreen:NO];
983
984     if (!b_animation) {
985         /* We don't animate if we are not visible, instead we
986          * simply fade the display */
987         CGDisplayFadeReservationToken token;
988
989         if (blackout_other_displays) {
990             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
991             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
992         }
993
994         [self setAlphaValue:1.0];
995         [self orderFront: self];
996
997         /* Will release the lock */
998         [self hasEndedFullscreen];
999
1000         if (blackout_other_displays) {
1001             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1002             CGReleaseDisplayFadeReservation(token);
1003         }
1004
1005         return;
1006     }
1007
1008     [self setAlphaValue: 0.0];
1009     [self orderFront: self];
1010     [[_videoView window] orderFront: self];
1011
1012     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1013     frame.origin.x += [self frame].origin.x;
1014     frame.origin.y += [self frame].origin.y;
1015
1016     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1017     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1018     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1019
1020     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1021
1022     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1023     [o_fullscreen_anim2 setDuration: 0.3];
1024     [o_fullscreen_anim2 setFrameRate: 30];
1025
1026     [o_fullscreen_anim2 setDelegate: self];
1027
1028     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1029
1030     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1031     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1032     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1033
1034     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1035
1036     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1037     [o_fullscreen_anim1 setDuration: 0.2];
1038     [o_fullscreen_anim1 setFrameRate: 30];
1039     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1040
1041     /* Make sure o_fullscreen_window is the frontmost window */
1042     [o_fullscreen_window orderFront: self];
1043
1044     [o_fullscreen_anim1 startAnimation];
1045     /* fullscreenAnimation will be unlocked when animation ends */
1046 }
1047
1048 - (void)hasEndedFullscreen
1049 {
1050     _inFullscreenTransition = NO;
1051
1052     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1053     /* Make sure we don't see the _videoView disappearing of the screen during this operation */
1054     NSDisableScreenUpdates();
1055     [_videoView removeFromSuperviewWithoutNeedingDisplay];
1056     [[o_temp_view superview] replaceSubview:o_temp_view with:_videoView];
1057     // TODO Replace tmpView by an existing view (e.g. middle view)
1058     // TODO Use constraints for fullscreen window, reinstate constraints once the video view is added to the main window again
1059     [_videoView setFrame:[o_temp_view frame]];
1060     if ([[_videoView subviews] count] > 0)
1061         [self makeFirstResponder: [[_videoView subviews] firstObject]];
1062
1063     [_videoView setHidden: b_video_view_was_hidden];
1064
1065     [self makeKeyAndOrderFront:self];
1066
1067     [o_fullscreen_window orderOut: self];
1068     NSEnableScreenUpdates();
1069
1070     o_fullscreen_window = nil;
1071
1072     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1073     [self setLevel:i_originalLevel];
1074
1075     [self setAlphaValue: config_GetFloat(getIntf(), "macosx-opaqueness")];
1076 }
1077
1078 - (void)animationDidEnd:(NSAnimation*)animation
1079 {
1080     NSArray *viewAnimations;
1081     if ([animation currentValue] < 1.0)
1082         return;
1083
1084     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1085     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1086     if ([viewAnimations count] >=1 &&
1087         [[[viewAnimations firstObject] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1088         /* Fullscreen ended */
1089         [self hasEndedFullscreen];
1090     } else
1091     /* Fullscreen started */
1092         [self hasBecomeFullscreen];
1093 }
1094
1095 #pragma mark -
1096 #pragma mark Accessibility stuff
1097
1098 - (NSArray *)accessibilityAttributeNames
1099 {
1100     if (!_darkInterface || !self.titlebarView)
1101         return [super accessibilityAttributeNames];
1102
1103     static NSMutableArray *attributes = nil;
1104     if (attributes == nil) {
1105         attributes = [[super accessibilityAttributeNames] mutableCopy];
1106         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1107                                      NSAccessibilityCloseButtonAttribute,
1108                                      NSAccessibilityMinimizeButtonAttribute,
1109                                      NSAccessibilityZoomButtonAttribute, nil];
1110
1111         for(NSString *attribute in appendAttributes) {
1112             if (![attributes containsObject:attribute])
1113                 [attributes addObject:attribute];
1114         }
1115     }
1116     return attributes;
1117 }
1118
1119 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1120 {
1121     if (_darkInterface && self.titlebarView) {
1122         VLCMainWindowTitleView *o_tbv = self.titlebarView;
1123
1124         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1125             return NSAccessibilityStandardWindowSubrole;
1126
1127         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1128             return [[o_tbv closeButton] cell];
1129
1130         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1131             return [[o_tbv minimizeButton] cell];
1132
1133         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1134             return [[o_tbv zoomButton] cell];
1135     }
1136
1137     return [super accessibilityAttributeValue: o_attribute_name];
1138 }
1139
1140 @end