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