]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: do not explicitely set presentation options for 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     i_originalLevel = [self level];
714     b_windowShouldExitFullscreenWhenFinished = [[VLCMain sharedInstance] activeVideoPlayback];
715
716     // b_fullscreen and b_in_fullscreen_transition must not be true yet
717     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
718     [self setLevel:NSNormalWindowLevel];
719
720     b_in_fullscreen_transition = YES;
721
722     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
723
724     frameBeforeLionFullscreen = [self frame];
725
726     if ([self hasActiveVideo]) {
727         vout_thread_t *p_vout = getVoutForActiveWindow();
728         if (p_vout) {
729             var_SetBool(p_vout, "fullscreen", true);
730             vlc_object_release(p_vout);
731         }
732     }
733
734     if ([self hasActiveVideo])
735         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
736
737     if (b_dark_interface) {
738         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
739
740         NSRect winrect;
741         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
742         winrect = [self frame];
743
744         winrect.size.height = winrect.size.height - f_titleBarHeight;
745         [self setFrame: winrect display:NO animate:NO];
746     }
747
748     [o_video_view setFrame: [[self contentView] frame]];
749     if (![o_video_view isHidden]) {
750         [[o_controls_bar bottomBarView] setHidden: YES];
751     }
752
753     [self setMovableByWindowBackground: NO];
754 }
755
756 - (void)windowDidEnterFullScreen:(NSNotification *)notification
757 {
758     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
759     // But this creates some problems when leaving fs over remote intfs, so activate app here.
760     [NSApp activateIgnoringOtherApps:YES];
761
762     [self setFullscreen: YES];
763     b_in_fullscreen_transition = NO;
764
765     if ([self hasActiveVideo]) {
766         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
767         if (![o_video_view isHidden])
768             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
769     }
770
771     NSArray *subviews = [[self videoView] subviews];
772     NSUInteger count = [subviews count];
773
774     for (NSUInteger x = 0; x < count; x++) {
775         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
776             [[subviews objectAtIndex:x] reshape];
777     }
778 }
779
780 - (void)windowWillExitFullScreen:(NSNotification *)notification
781 {
782     b_in_fullscreen_transition = YES;
783     [self setFullscreen: NO];
784
785     if ([self hasActiveVideo]) {
786         var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
787
788         vout_thread_t *p_vout = getVoutForActiveWindow();
789         if (p_vout) {
790             var_SetBool(p_vout, "fullscreen", false);
791             vlc_object_release(p_vout);
792         }
793     }
794
795     [NSCursor setHiddenUntilMouseMoves: NO];
796     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
797
798
799     if (b_dark_interface) {
800         NSRect winrect;
801         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
802
803         winrect = [o_video_view frame];
804         winrect.size.height -= f_titleBarHeight;
805         [o_video_view setFrame: winrect];
806
807         winrect = [self frame];
808         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
809                                               winrect.size.width, f_titleBarHeight)];
810         [[self contentView] addSubview: o_titlebar_view];
811
812         winrect.size.height = winrect.size.height + f_titleBarHeight;
813         [self setFrame: winrect display:NO animate:NO];
814     }
815
816     NSRect videoViewFrame = [o_video_view frame];
817     videoViewFrame.origin.y += [o_controls_bar height];
818     videoViewFrame.size.height -= [o_controls_bar height];
819     [o_video_view setFrame: videoViewFrame];
820
821     if (![o_video_view isHidden]) {
822         [[o_controls_bar bottomBarView] setHidden: NO];
823     }
824
825     [self setMovableByWindowBackground: YES];
826 }
827
828 - (void)windowDidExitFullScreen:(NSNotification *)notification
829 {
830     b_in_fullscreen_transition = NO;
831
832     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
833     [self setLevel:i_originalLevel];
834 }
835
836 #pragma mark -
837 #pragma mark Fullscreen Logic
838
839 - (void)enterFullscreenWithAnimation:(BOOL)b_animation
840 {
841     NSMutableDictionary *dict1, *dict2;
842     NSScreen *screen;
843     NSRect screen_rect;
844     NSRect rect;
845     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
846
847     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
848
849     if (!screen) {
850         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
851         screen = [self screen];
852     }
853     if (!screen) {
854         msg_Dbg(VLCIntf, "Using deepest screen");
855         screen = [NSScreen deepestScreen];
856     }
857
858     screen_rect = [screen frame];
859
860     if (o_controls_bar)
861         [o_controls_bar setFullscreenState:YES];
862     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
863
864     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
865
866     if (blackout_other_displays)
867         [screen blackoutOtherScreens];
868
869     /* Make sure we don't see the window flashes in float-on-top mode */
870     i_originalLevel = [self level];
871     // b_fullscreen must not be true yet
872     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
873     [self setLevel:NSNormalWindowLevel];
874
875     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
876     if (!o_fullscreen_window) {
877         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
878
879         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
880         rect.origin.x += [self frame].origin.x;
881         rect.origin.y += [self frame].origin.y;
882         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
883         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
884         [o_fullscreen_window setCanBecomeKeyWindow: YES];
885         [o_fullscreen_window setCanBecomeMainWindow: YES];
886         [o_fullscreen_window setHasActiveVideo: YES];
887         [o_fullscreen_window setFullscreen: YES];
888
889         /* Make sure video view gets visible in case the playlist was visible before */
890         b_video_view_was_hidden = [o_video_view isHidden];
891         [o_video_view setHidden: NO];
892
893         if (!b_animation) {
894             /* We don't animate if we are not visible, instead we
895              * simply fade the display */
896             CGDisplayFadeReservationToken token;
897
898             if (blackout_other_displays) {
899                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
900                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
901             }
902
903             NSDisableScreenUpdates();
904             [o_video_view retain];
905             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
906             [o_temp_view setFrame:[o_video_view frame]];
907             [[o_fullscreen_window contentView] addSubview:o_video_view];
908             [o_video_view setFrame: [[o_fullscreen_window contentView] frame]];
909             [o_video_view release];
910             NSEnableScreenUpdates();
911
912             [screen setFullscreenPresentationOptions];
913
914             [o_fullscreen_window setFrame:screen_rect display:YES animate:NO];
915
916             [o_fullscreen_window orderFront:self animate:YES];
917
918             [o_fullscreen_window setLevel:NSNormalWindowLevel];
919
920             if (blackout_other_displays) {
921                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
922                 CGReleaseDisplayFadeReservation(token);
923             }
924
925             /* Will release the lock */
926             [self hasBecomeFullscreen];
927
928             return;
929         }
930
931         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
932         NSDisableScreenUpdates();
933         [o_video_view retain];
934         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
935         [o_temp_view setFrame:[o_video_view frame]];
936         [[o_fullscreen_window contentView] addSubview:o_video_view];
937         [o_video_view setFrame: [[o_fullscreen_window contentView] frame]];
938         [o_video_view release];
939         [o_fullscreen_window makeKeyAndOrderFront:self];
940         NSEnableScreenUpdates();
941     }
942
943     /* We are in fullscreen (and no animation is running) */
944     if ([self fullscreen]) {
945         /* Make sure we are hidden */
946         [self orderOut: self];
947
948         return;
949     }
950
951     if (o_fullscreen_anim1) {
952         [o_fullscreen_anim1 stopAnimation];
953         [o_fullscreen_anim1 release];
954     }
955     if (o_fullscreen_anim2) {
956         [o_fullscreen_anim2 stopAnimation];
957         [o_fullscreen_anim2 release];
958     }
959
960     [screen setFullscreenPresentationOptions];
961
962     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
963     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
964
965     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
966     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
967
968     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
969     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
970     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
971
972     /* Strategy with NSAnimation allocation:
973      - Keep at most 2 animation at a time
974      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
975      */
976     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
977     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
978
979     [dict1 release];
980     [dict2 release];
981
982     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
983     [o_fullscreen_anim1 setDuration: 0.3];
984     [o_fullscreen_anim1 setFrameRate: 30];
985     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
986     [o_fullscreen_anim2 setDuration: 0.2];
987     [o_fullscreen_anim2 setFrameRate: 30];
988
989     [o_fullscreen_anim2 setDelegate: self];
990     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
991
992     [o_fullscreen_anim1 startAnimation];
993     /* fullscreenAnimation will be unlocked when animation ends */
994
995     b_in_fullscreen_transition = YES;
996 }
997
998 - (void)hasBecomeFullscreen
999 {
1000     if ([[o_video_view subviews] count] > 0)
1001         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1002
1003     [o_fullscreen_window makeKeyWindow];
1004     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
1005
1006     /* tell the fspanel to move itself to front next time it's triggered */
1007     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
1008     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
1009
1010     if ([self isVisible])
1011         [self orderOut: self];
1012
1013     b_in_fullscreen_transition = NO;
1014     [self setFullscreen:YES];
1015 }
1016
1017 - (void)leaveFullscreenWithAnimation:(BOOL)b_animation
1018 {
1019     NSMutableDictionary *dict1, *dict2;
1020     NSRect frame;
1021     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
1022
1023     if (o_controls_bar)
1024         [o_controls_bar setFullscreenState:NO];
1025     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
1026
1027     /* We always try to do so */
1028     [NSScreen unblackoutScreens];
1029
1030     [[o_video_view window] makeKeyAndOrderFront: nil];
1031
1032     /* Don't do anything if o_fullscreen_window is already closed */
1033     if (!o_fullscreen_window) {
1034         return;
1035     }
1036
1037     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
1038     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
1039
1040     if (o_fullscreen_anim1) {
1041         [o_fullscreen_anim1 stopAnimation];
1042         [o_fullscreen_anim1 release];
1043         o_fullscreen_anim1 = nil;
1044     }
1045     if (o_fullscreen_anim2) {
1046         [o_fullscreen_anim2 stopAnimation];
1047         [o_fullscreen_anim2 release];
1048         o_fullscreen_anim2 = nil;
1049     }
1050
1051     b_in_fullscreen_transition = YES;
1052     [self setFullscreen:NO];
1053
1054     if (!b_animation) {
1055         /* We don't animate if we are not visible, instead we
1056          * simply fade the display */
1057         CGDisplayFadeReservationToken token;
1058
1059         if (blackout_other_displays) {
1060             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1061             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1062         }
1063
1064         [self setAlphaValue:1.0];
1065         [self orderFront: self];
1066
1067         /* Will release the lock */
1068         [self hasEndedFullscreen];
1069
1070         if (blackout_other_displays) {
1071             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1072             CGReleaseDisplayFadeReservation(token);
1073         }
1074
1075         return;
1076     }
1077
1078     [self setAlphaValue: 0.0];
1079     [self orderFront: self];
1080     [[o_video_view window] orderFront: self];
1081
1082     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1083     frame.origin.x += [self frame].origin.x;
1084     frame.origin.y += [self frame].origin.y;
1085
1086     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1087     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1088     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1089
1090     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1091     [dict2 release];
1092
1093     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1094     [o_fullscreen_anim2 setDuration: 0.3];
1095     [o_fullscreen_anim2 setFrameRate: 30];
1096
1097     [o_fullscreen_anim2 setDelegate: self];
1098
1099     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1100
1101     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1102     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1103     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1104
1105     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1106     [dict1 release];
1107
1108     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1109     [o_fullscreen_anim1 setDuration: 0.2];
1110     [o_fullscreen_anim1 setFrameRate: 30];
1111     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1112
1113     /* Make sure o_fullscreen_window is the frontmost window */
1114     [o_fullscreen_window orderFront: self];
1115
1116     [o_fullscreen_anim1 startAnimation];
1117     /* fullscreenAnimation will be unlocked when animation ends */
1118 }
1119
1120 - (void)hasEndedFullscreen
1121 {
1122     b_in_fullscreen_transition = NO;
1123
1124     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1125     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1126     NSDisableScreenUpdates();
1127     [o_video_view retain];
1128     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1129     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1130     [o_video_view release];
1131     [o_video_view setFrame:[o_temp_view frame]];
1132     if ([[o_video_view subviews] count] > 0)
1133         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1134
1135     [o_video_view setHidden: b_video_view_was_hidden];
1136
1137     [self makeKeyAndOrderFront:self];
1138
1139     [o_fullscreen_window orderOut: self];
1140     NSEnableScreenUpdates();
1141
1142     [o_fullscreen_window release];
1143     o_fullscreen_window = nil;
1144
1145     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1146     [self setLevel:i_originalLevel];
1147
1148     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1149 }
1150
1151 - (void)animationDidEnd:(NSAnimation*)animation
1152 {
1153     NSArray *viewAnimations;
1154     if ([animation currentValue] < 1.0)
1155         return;
1156
1157     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1158     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1159     if ([viewAnimations count] >=1 &&
1160         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1161         /* Fullscreen ended */
1162         [self hasEndedFullscreen];
1163     } else
1164     /* Fullscreen started */
1165         [self hasBecomeFullscreen];
1166 }
1167
1168 #pragma mark -
1169 #pragma mark Accessibility stuff
1170
1171 - (NSArray *)accessibilityAttributeNames
1172 {
1173     if (!b_dark_interface || !o_titlebar_view)
1174         return [super accessibilityAttributeNames];
1175
1176     static NSMutableArray *attributes = nil;
1177     if (attributes == nil) {
1178         attributes = [[super accessibilityAttributeNames] mutableCopy];
1179         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1180                                      NSAccessibilityCloseButtonAttribute,
1181                                      NSAccessibilityMinimizeButtonAttribute,
1182                                      NSAccessibilityZoomButtonAttribute, nil];
1183
1184         for(NSString *attribute in appendAttributes) {
1185             if (![attributes containsObject:attribute])
1186                 [attributes addObject:attribute];
1187         }
1188     }
1189     return attributes;
1190 }
1191
1192 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1193 {
1194     if (b_dark_interface && o_titlebar_view) {
1195         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1196
1197         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1198             return NSAccessibilityStandardWindowSubrole;
1199
1200         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1201             return [[o_tbv closeButton] cell];
1202
1203         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1204             return [[o_tbv minimizeButton] cell];
1205
1206         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1207             return [[o_tbv zoomButton] cell];
1208     }
1209
1210     return [super accessibilityAttributeValue: o_attribute_name];
1211 }
1212
1213 @end