]> git.sesse.net Git - vlc/blob - modules/gui/macosx/embeddedwindow.m
Mac OS X gui: Protect the fullscreen animation by a lock.
[vlc] / modules / gui / macosx / embeddedwindow.m
1 /*****************************************************************************
2  * embeddedwindow.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2005-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Benjamin Pracht <bigben at videolan dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 /* DisableScreenUpdates, SetSystemUIMode, ... */
29 #import <QuickTime/QuickTime.h>
30
31 #import "intf.h"
32 #import "controls.h"
33 #import "vout.h"
34 #import "embeddedwindow.h"
35 #import "fspanel.h"
36
37 /*****************************************************************************
38  * VLCEmbeddedWindow Implementation
39  *****************************************************************************/
40
41 @implementation VLCEmbeddedWindow
42
43 - (void)awakeFromNib
44 {
45     [self setDelegate: self];
46
47     [o_btn_backward setToolTip: _NS("Rewind")];
48     [o_btn_forward setToolTip: _NS("Fast Forward")];
49     [o_btn_fullscreen setToolTip: _NS("Fullscreen")];
50     [o_btn_play setToolTip: _NS("Play")];
51     [o_slider setToolTip: _NS("Position")];
52
53     o_img_play = [NSImage imageNamed: @"play_embedded"];
54     o_img_play_pressed = [NSImage imageNamed: @"play_embedded_blue"];
55     o_img_pause = [NSImage imageNamed: @"pause_embedded"];
56     o_img_pause_pressed = [NSImage imageNamed: @"pause_embedded_blue"];
57
58     o_saved_frame = NSMakeRect( 0.0f, 0.0f, 0.0f, 0.0f );
59
60     /* Useful to save o_view frame in fullscreen mode */
61     o_temp_view = [[NSView alloc] init];
62     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
63
64     o_fullscreen_window = nil;
65     o_fullscreen_anim1 = o_fullscreen_anim2 = nil;
66
67     /* Not fullscreen when we wake up */
68     [o_btn_fullscreen setState: NO];
69     b_fullscreen = NO;
70     o_animation_lock = [[NSLock alloc] init];
71 }
72
73 - (void)setTime:(NSString *)o_arg_time position:(float)f_position
74 {
75     [o_time setStringValue: o_arg_time];
76     [o_slider setFloatValue: f_position];
77 }
78
79 - (void)playStatusUpdated:(int)i_status
80 {
81     if( i_status == PLAYING_S )
82     {
83         [o_btn_play setImage: o_img_pause];
84         [o_btn_play setAlternateImage: o_img_pause_pressed];
85         [o_btn_play setToolTip: _NS("Pause")];
86     }
87     else
88     {
89         [o_btn_play setImage: o_img_play];
90         [o_btn_play setAlternateImage: o_img_play_pressed];
91         [o_btn_play setToolTip: _NS("Play")];
92     }
93 }
94
95 - (void)setSeekable:(BOOL)b_seekable
96 {
97     [o_btn_forward setEnabled: b_seekable];
98     [o_btn_backward setEnabled: b_seekable];
99     [o_slider setEnabled: b_seekable];
100 }
101
102 - (void)zoom:(id)sender
103 {
104     if( ![self isZoomed] )
105     {
106         NSRect zoomRect = [[self screen] frame];
107         o_saved_frame = [self frame];
108         /* we don't have to take care of the eventual menu bar and dock
109           as zoomRect will be cropped automatically by setFrame:display:
110           to the right rectangle */
111         [self setFrame: zoomRect display: YES animate: YES];
112     }
113     else
114     {
115         /* unzoom to the saved_frame if the o_saved_frame coords look sound
116            (just in case) */
117         if( o_saved_frame.size.width > 0 && o_saved_frame.size.height > 0 )
118             [self setFrame: o_saved_frame display: YES animate: YES];
119     }
120 }
121
122 - (BOOL)windowShouldClose:(id)sender
123 {
124     playlist_t * p_playlist = pl_Yield( VLCIntf );
125
126     playlist_Stop( p_playlist );
127     vlc_object_release( p_playlist );
128     return YES;
129 }
130
131 - (NSView *)mainView
132 {
133     if (o_fullscreen_window)
134         return o_temp_view;
135     else
136         return o_view;
137 }
138
139 /*****************************************************************************
140  * Fullscreen support
141  */
142
143 - (BOOL)isFullscreen
144 {
145     return b_fullscreen;
146 }
147
148 - (void)lockFullscreenAnimation
149 {
150     [o_animation_lock lock];
151 }
152
153 - (void)unlockFullscreenAnimation
154 {
155     [o_animation_lock unlock];
156 }
157
158 - (void)enterFullscreen
159 {
160     NSMutableDictionary *dict1, *dict2;
161     NSScreen *screen;
162     NSRect screen_rect;
163     NSRect rect;
164     vout_thread_t *p_vout = vlc_object_find( VLCIntf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
165     BOOL blackout_other_displays = var_GetBool( p_vout, "macosx-black" );
166
167     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_GetInteger( p_vout, "video-device" )];
168
169     vlc_object_release( p_vout );
170     
171     [self lockFullscreenAnimation];
172
173     if (!screen)
174         screen = [self screen];
175
176     screen_rect = [screen frame];
177
178     [o_btn_fullscreen setState: YES];
179
180     [NSCursor setHiddenUntilMouseMoves: YES];
181     
182     if (blackout_other_displays)
183         [screen blackoutOtherScreens]; /* We should do something like [screen blackoutOtherScreens]; */
184
185     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
186     if (!o_fullscreen_window)
187     {
188         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
189
190         rect = [[o_view superview] convertRect: [o_view frame] toView: nil]; /* Convert to Window base coord */
191         rect.origin.x += [self frame].origin.x;
192         rect.origin.y += [self frame].origin.y;
193         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
194         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
195         [o_fullscreen_window setCanBecomeKeyWindow: YES];
196
197         if (![self isVisible] || [self alphaValue] == 0.0 || MACOS_VERSION < 10.4f)
198         {
199             /* We don't animate if we are not visible or if we are running on
200              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
201              * simply fade the display */
202             CGDisplayFadeReservationToken token;
203             
204             [o_fullscreen_window setFrame:screen_rect display:NO];
205             
206             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
207             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
208             
209             if ([screen isMainScreen])
210                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
211             
212             [[self contentView] replaceSubview:o_view with:o_temp_view];
213             [o_temp_view setFrame:[o_view frame]];
214             [o_fullscreen_window setContentView:o_view];
215             [o_fullscreen_window makeKeyAndOrderFront:self];
216             [self orderOut: self];
217
218             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
219             CGReleaseDisplayFadeReservation( token);
220
221             /* Will release the lock */
222             [self hasBecomeFullscreen];
223
224             return;
225         }
226         
227         /* Make sure we don't see the o_view disappearing of the screen during this operation */
228         DisableScreenUpdates();
229         [[self contentView] replaceSubview:o_view with:o_temp_view];
230         [o_temp_view setFrame:[o_view frame]];
231         [o_fullscreen_window setContentView:o_view];
232         [o_fullscreen_window makeKeyAndOrderFront:self];
233         EnableScreenUpdates();
234     }
235
236     if (MACOS_VERSION < 10.4f)
237     {
238         /* We were already fullscreen nothing to do when NSAnimation
239          * is not supported */
240         [self unlockFullscreenAnimation];
241         return;
242     }
243
244     /* We are in fullscreen (and no animation is running) */
245     if (b_fullscreen)
246     {
247         [self unlockFullscreenAnimation];
248         return;
249     }
250
251     if (o_fullscreen_anim1)
252     {
253         [o_fullscreen_anim1 stopAnimation];
254         [o_fullscreen_anim1 release];
255     }
256     if (o_fullscreen_anim2)
257     {
258         [o_fullscreen_anim2 stopAnimation];
259         [o_fullscreen_anim2 release];
260     }
261  
262     if ([screen isMainScreen])
263         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
264
265     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
266     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
267
268     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
269     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
270
271     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
272     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
273     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
274
275     /* Strategy with NSAnimation allocation:
276         - Keep at most 2 animation at a time
277         - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
278     */
279     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
280     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
281
282     [dict1 release];
283     [dict2 release];
284
285     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
286     [o_fullscreen_anim1 setDuration: 0.3];
287     [o_fullscreen_anim1 setFrameRate: 30];
288     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
289     [o_fullscreen_anim2 setDuration: 0.2];
290     [o_fullscreen_anim2 setFrameRate: 30];
291
292     [o_fullscreen_anim2 setDelegate: self];
293     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
294
295     [o_fullscreen_anim1 startAnimation];
296     /* fullscreenAnimation will be unlocked when animation ends */
297 }
298
299 - (void)hasBecomeFullscreen
300 {
301     [o_fullscreen_window makeFirstResponder: [[[VLCMain sharedInstance] getControls] getVoutView]];
302
303     [o_fullscreen_window makeKeyWindow];
304     [o_fullscreen_window setAcceptsMouseMovedEvents: TRUE];
305
306     /* tell the fspanel to move itself to front next time it's triggered */
307     [[[[VLCMain sharedInstance] getControls] getFSPanel] setVoutWasUpdated: (int)[[o_fullscreen_window screen] displayID]];
308     
309     [[[[VLCMain sharedInstance] getControls] getFSPanel] setActive: nil];
310     b_fullscreen = YES;
311     [self unlockFullscreenAnimation];
312 }
313
314 - (void)leaveFullscreen
315 {
316     NSMutableDictionary *dict1, *dict2;
317     NSRect frame;
318
319     [self lockFullscreenAnimation];
320
321     b_fullscreen = NO;
322     [o_btn_fullscreen setState: NO];
323
324     /* We always try to do so */
325     [NSScreen unblackoutScreens];
326
327     /* Don't do anything if o_fullscreen_window is already closed */
328     if (!o_fullscreen_window)
329     {
330         [self lockFullscreenAnimation];
331         return;
332     }
333
334     if (![self isVisible] || MACOS_VERSION < 10.4f)
335     {
336         /* We don't animate if we are not visible or if we are running on
337         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
338         * simply fade the display */
339         CGDisplayFadeReservationToken token;
340
341         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
342         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
343
344         [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
345         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
346
347         /* Will release the lock */
348         [self hasEndedFullscreen];
349
350         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
351         CGReleaseDisplayFadeReservation( token);
352         return;
353     }
354
355     [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
356     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
357
358     if (o_fullscreen_anim1)
359     {
360         [o_fullscreen_anim1 stopAnimation];
361         [o_fullscreen_anim1 release];
362     }
363     if (o_fullscreen_anim2)
364     {
365         [o_fullscreen_anim2 stopAnimation];
366         [o_fullscreen_anim2 release];
367     }
368
369     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
370     frame.origin.x += [self frame].origin.x; 
371     frame.origin.y += [self frame].origin.y;
372
373     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
374     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
375     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
376
377     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
378     [dict2 release];
379
380     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
381     [o_fullscreen_anim2 setDuration: 0.3];
382     [o_fullscreen_anim2 setFrameRate: 30];
383
384     [o_fullscreen_anim2 setDelegate: self];
385
386     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
387
388     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
389     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
390     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
391
392     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
393     [dict1 release];
394
395     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
396     [o_fullscreen_anim1 setDuration: 0.2];
397     [o_fullscreen_anim1 setFrameRate: 30];
398     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
399     [o_fullscreen_anim1 startAnimation];
400     /* fullscreenAnimation will be unlocked when animation ends */
401 }
402
403 - (void)hasEndedFullscreen
404 {
405     /* This function is private and should be only triggered at the end of the fullscreen change animation */
406     /* Make sure we don't see the o_view disappearing of the screen during this operation */
407     DisableScreenUpdates();
408     [o_view retain];
409     [o_view removeFromSuperviewWithoutNeedingDisplay];
410     [[self contentView] replaceSubview:o_temp_view with:o_view];
411     [o_view release];
412     [o_view setFrame:[o_temp_view frame]];
413     if ([self isVisible])
414         [self makeKeyAndOrderFront:self];
415     [o_fullscreen_window orderOut: self];
416     EnableScreenUpdates();
417
418     [o_fullscreen_window release];
419     o_fullscreen_window = nil;
420     [self unlockFullscreenAnimation];
421 }
422
423 - (void)animationDidEnd:(NSAnimation*)animation
424 {
425     NSArray *viewAnimations;
426
427     if ([animation currentValue] < 1.0)
428         return;
429
430     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
431     viewAnimations = [o_fullscreen_anim2 viewAnimations];
432     if ([viewAnimations count] >=1 &&
433         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
434     {
435         /* Fullscreen ended */
436         [self hasEndedFullscreen];
437     }
438     else
439     {
440         /* Fullscreen started */
441         [self hasBecomeFullscreen];
442     }
443 }
444
445 - (void)orderOut: (id)sender
446 {
447     [super orderOut: sender];
448     /* Make sure we leave fullscreen */
449     [self leaveFullscreen];
450 }
451
452 /* Make sure setFrame gets executed on main thread especially if we are animating.
453  * (Thus we won't block the video output thread) */
454 - (void)setFrame:(NSRect)frame display:(BOOL)display animate:(BOOL)animate
455 {
456     struct { NSRect frame; BOOL display; BOOL animate;} args;
457     NSData *packedargs;
458
459     args.frame = frame;
460     args.display = display;
461     args.animate = animate;
462
463     packedargs = [NSData dataWithBytes:&args length:sizeof(args)];
464
465     [self performSelectorOnMainThread:@selector(setFrameOnMainThread:)
466                     withObject: packedargs waitUntilDone: YES];
467 }
468
469 - (void)setFrameOnMainThread:(NSData*)packedargs
470 {
471     struct args { NSRect frame; BOOL display; BOOL animate; } * args = (struct args*)[packedargs bytes];
472
473     [super setFrame: args->frame display: args->display animate:args->animate];
474 }
475 @end