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