]> git.sesse.net Git - vlc/blob - modules/gui/macosx/embeddedwindow.m
Mac OS X gui: Use a recursive lock to be able to trigger enter/leaveFullscreen in...
[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     /* Use a recursive lock to be able to trigger enter/leavefullscreen
71      * in middle of an animation, providing that the enter/leave functions
72      * are called from the same thread */
73     o_animation_lock = [[NSRecursiveLock alloc] init];
74     b_animation_lock_alreadylocked = NO;
75 }
76
77 - (void)setTime:(NSString *)o_arg_time position:(float)f_position
78 {
79     [o_time setStringValue: o_arg_time];
80     [o_slider setFloatValue: f_position];
81 }
82
83 - (void)playStatusUpdated:(int)i_status
84 {
85     if( i_status == PLAYING_S )
86     {
87         [o_btn_play setImage: o_img_pause];
88         [o_btn_play setAlternateImage: o_img_pause_pressed];
89         [o_btn_play setToolTip: _NS("Pause")];
90     }
91     else
92     {
93         [o_btn_play setImage: o_img_play];
94         [o_btn_play setAlternateImage: o_img_play_pressed];
95         [o_btn_play setToolTip: _NS("Play")];
96     }
97 }
98
99 - (void)setSeekable:(BOOL)b_seekable
100 {
101     [o_btn_forward setEnabled: b_seekable];
102     [o_btn_backward setEnabled: b_seekable];
103     [o_slider setEnabled: b_seekable];
104 }
105
106 - (void)zoom:(id)sender
107 {
108     if( ![self isZoomed] )
109     {
110         NSRect zoomRect = [[self screen] frame];
111         o_saved_frame = [self frame];
112         /* we don't have to take care of the eventual menu bar and dock
113           as zoomRect will be cropped automatically by setFrame:display:
114           to the right rectangle */
115         [self setFrame: zoomRect display: YES animate: YES];
116     }
117     else
118     {
119         /* unzoom to the saved_frame if the o_saved_frame coords look sound
120            (just in case) */
121         if( o_saved_frame.size.width > 0 && o_saved_frame.size.height > 0 )
122             [self setFrame: o_saved_frame display: YES animate: YES];
123     }
124 }
125
126 - (BOOL)windowShouldClose:(id)sender
127 {
128     playlist_t * p_playlist = pl_Yield( VLCIntf );
129
130     playlist_Stop( p_playlist );
131     vlc_object_release( p_playlist );
132     return YES;
133 }
134
135 - (NSView *)mainView
136 {
137     if (o_fullscreen_window)
138         return o_temp_view;
139     else
140         return o_view;
141 }
142
143 /*****************************************************************************
144  * Fullscreen support
145  */
146
147 - (BOOL)isFullscreen
148 {
149     return b_fullscreen;
150 }
151
152 - (void)lockFullscreenAnimation
153 {
154     [o_animation_lock lock];
155 }
156
157 - (void)unlockFullscreenAnimation
158 {
159     [o_animation_lock unlock];
160 }
161
162 - (void)enterFullscreen
163 {
164     NSMutableDictionary *dict1, *dict2;
165     NSScreen *screen;
166     NSRect screen_rect;
167     NSRect rect;
168     vout_thread_t *p_vout = vlc_object_find( VLCIntf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
169     BOOL blackout_other_displays = var_GetBool( p_vout, "macosx-black" );
170
171     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_GetInteger( p_vout, "video-device" )];
172
173     vlc_object_release( p_vout );
174     
175     [self lockFullscreenAnimation];
176
177     /* This is a recursive lock. If we are already in the middle of an animation we
178      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
179      * are executed always in the same thread */ 
180     if (b_animation_lock_alreadylocked)
181         [self unlockFullscreenAnimation];
182     b_animation_lock_alreadylocked = YES;
183
184     if (!screen)
185         screen = [self screen];
186
187     screen_rect = [screen frame];
188
189     [o_btn_fullscreen setState: YES];
190
191     [NSCursor setHiddenUntilMouseMoves: YES];
192     
193     if (blackout_other_displays)
194         [screen blackoutOtherScreens]; /* We should do something like [screen blackoutOtherScreens]; */
195
196     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
197     if (!o_fullscreen_window)
198     {
199         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
200
201         rect = [[o_view superview] convertRect: [o_view frame] toView: nil]; /* Convert to Window base coord */
202         rect.origin.x += [self frame].origin.x;
203         rect.origin.y += [self frame].origin.y;
204         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
205         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
206         [o_fullscreen_window setCanBecomeKeyWindow: YES];
207
208         if (![self isVisible] || [self alphaValue] == 0.0 || MACOS_VERSION < 10.4f)
209         {
210             /* We don't animate if we are not visible or if we are running on
211              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
212              * simply fade the display */
213             CGDisplayFadeReservationToken token;
214             
215             [o_fullscreen_window setFrame:screen_rect display:NO];
216             
217             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
218             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
219             
220             if ([screen isMainScreen])
221                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
222             
223             [[self contentView] replaceSubview:o_view with:o_temp_view];
224             [o_temp_view setFrame:[o_view frame]];
225             [o_fullscreen_window setContentView:o_view];
226             [o_fullscreen_window makeKeyAndOrderFront:self];
227             [self orderOut: self];
228
229             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
230             CGReleaseDisplayFadeReservation( token);
231
232             /* Will release the lock */
233             [self hasBecomeFullscreen];
234
235             return;
236         }
237         
238         /* Make sure we don't see the o_view disappearing of the screen during this operation */
239         DisableScreenUpdates();
240         [[self contentView] replaceSubview:o_view with:o_temp_view];
241         [o_temp_view setFrame:[o_view frame]];
242         [o_fullscreen_window setContentView:o_view];
243         [o_fullscreen_window makeKeyAndOrderFront:self];
244         EnableScreenUpdates();
245     }
246
247     if (MACOS_VERSION < 10.4f)
248     {
249         /* We were already fullscreen nothing to do when NSAnimation
250          * is not supported */
251         [self unlockFullscreenAnimation];
252         b_animation_lock_alreadylocked = NO;
253         return;
254     }
255
256     /* We are in fullscreen (and no animation is running) */
257     if (b_fullscreen)
258     {
259         [self unlockFullscreenAnimation];
260         b_animation_lock_alreadylocked = NO;
261         return;
262     }
263
264     if (o_fullscreen_anim1)
265     {
266         [o_fullscreen_anim1 stopAnimation];
267         [o_fullscreen_anim1 release];
268     }
269     if (o_fullscreen_anim2)
270     {
271         [o_fullscreen_anim2 stopAnimation];
272         [o_fullscreen_anim2 release];
273     }
274  
275     if ([screen isMainScreen])
276         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
277
278     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
279     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
280
281     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
282     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
283
284     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
285     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
286     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
287
288     /* Strategy with NSAnimation allocation:
289         - Keep at most 2 animation at a time
290         - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
291     */
292     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
293     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
294
295     [dict1 release];
296     [dict2 release];
297
298     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
299     [o_fullscreen_anim1 setDuration: 0.3];
300     [o_fullscreen_anim1 setFrameRate: 30];
301     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
302     [o_fullscreen_anim2 setDuration: 0.2];
303     [o_fullscreen_anim2 setFrameRate: 30];
304
305     [o_fullscreen_anim2 setDelegate: self];
306     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
307
308     [o_fullscreen_anim1 startAnimation];
309     /* fullscreenAnimation will be unlocked when animation ends */
310 }
311
312 - (void)hasBecomeFullscreen
313 {
314     [o_fullscreen_window makeFirstResponder: [[[VLCMain sharedInstance] getControls] getVoutView]];
315
316     [o_fullscreen_window makeKeyWindow];
317     [o_fullscreen_window setAcceptsMouseMovedEvents: TRUE];
318
319     /* tell the fspanel to move itself to front next time it's triggered */
320     [[[[VLCMain sharedInstance] getControls] getFSPanel] setVoutWasUpdated: (int)[[o_fullscreen_window screen] displayID]];
321     
322     [[[[VLCMain sharedInstance] getControls] getFSPanel] setActive: nil];
323     b_fullscreen = YES;
324     [self unlockFullscreenAnimation];
325 }
326
327 - (void)leaveFullscreen
328 {
329     NSMutableDictionary *dict1, *dict2;
330     NSRect frame;
331
332     [self lockFullscreenAnimation];
333
334     /* This is a recursive lock. If we are already in the middle of an animation we
335      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
336      * are executed always in the same thread */ 
337     if (b_animation_lock_alreadylocked)
338         [self unlockFullscreenAnimation];
339     b_animation_lock_alreadylocked = YES;
340
341     b_fullscreen = NO;
342     [o_btn_fullscreen setState: NO];
343
344     /* We always try to do so */
345     [NSScreen unblackoutScreens];
346
347     /* Don't do anything if o_fullscreen_window is already closed */
348     if (!o_fullscreen_window)
349     {
350         [self unlockFullscreenAnimation];
351         b_animation_lock_alreadylocked = NO;
352         return;
353     }
354
355     if (![self isVisible] || MACOS_VERSION < 10.4f)
356     {
357         /* We don't animate if we are not visible or if we are running on
358         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
359         * simply fade the display */
360         CGDisplayFadeReservationToken token;
361
362         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
363         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
364
365         [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
366         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
367
368         /* Will release the lock */
369         [self hasEndedFullscreen];
370
371         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
372         CGReleaseDisplayFadeReservation( token);
373         return;
374     }
375
376     [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
377     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
378
379     if (o_fullscreen_anim1)
380     {
381         [o_fullscreen_anim1 stopAnimation];
382         [o_fullscreen_anim1 release];
383     }
384     if (o_fullscreen_anim2)
385     {
386         [o_fullscreen_anim2 stopAnimation];
387         [o_fullscreen_anim2 release];
388     }
389
390     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
391     frame.origin.x += [self frame].origin.x; 
392     frame.origin.y += [self frame].origin.y;
393
394     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
395     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
396     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
397
398     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
399     [dict2 release];
400
401     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
402     [o_fullscreen_anim2 setDuration: 0.3];
403     [o_fullscreen_anim2 setFrameRate: 30];
404
405     [o_fullscreen_anim2 setDelegate: self];
406
407     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
408
409     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
410     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
411     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
412
413     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
414     [dict1 release];
415
416     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
417     [o_fullscreen_anim1 setDuration: 0.2];
418     [o_fullscreen_anim1 setFrameRate: 30];
419     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
420     [o_fullscreen_anim1 startAnimation];
421     /* fullscreenAnimation will be unlocked when animation ends */
422 }
423
424 - (void)hasEndedFullscreen
425 {
426     /* This function is private and should be only triggered at the end of the fullscreen change animation */
427     /* Make sure we don't see the o_view disappearing of the screen during this operation */
428     DisableScreenUpdates();
429     [o_view retain];
430     [o_view removeFromSuperviewWithoutNeedingDisplay];
431     [[self contentView] replaceSubview:o_temp_view with:o_view];
432     [o_view release];
433     [o_view setFrame:[o_temp_view frame]];
434     if ([self isVisible])
435         [self makeKeyAndOrderFront:self];
436     [o_fullscreen_window orderOut: self];
437     EnableScreenUpdates();
438
439     [o_fullscreen_window release];
440     o_fullscreen_window = nil;
441     [self unlockFullscreenAnimation];
442     b_animation_lock_alreadylocked = NO;
443 }
444
445 - (void)animationDidEnd:(NSAnimation*)animation
446 {
447     NSArray *viewAnimations;
448
449     if ([animation currentValue] < 1.0)
450         return;
451
452     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
453     viewAnimations = [o_fullscreen_anim2 viewAnimations];
454     if ([viewAnimations count] >=1 &&
455         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
456     {
457         /* Fullscreen ended */
458         [self hasEndedFullscreen];
459     }
460     else
461     {
462         /* Fullscreen started */
463         [self hasBecomeFullscreen];
464     }
465 }
466
467 - (void)orderOut: (id)sender
468 {
469     [super orderOut: sender];
470     /* Make sure we leave fullscreen */
471     [self leaveFullscreen];
472 }
473
474 /* Make sure setFrame gets executed on main thread especially if we are animating.
475  * (Thus we won't block the video output thread) */
476 - (void)setFrame:(NSRect)frame display:(BOOL)display animate:(BOOL)animate
477 {
478     struct { NSRect frame; BOOL display; BOOL animate;} args;
479     NSData *packedargs;
480
481     args.frame = frame;
482     args.display = display;
483     args.animate = animate;
484
485     packedargs = [NSData dataWithBytes:&args length:sizeof(args)];
486
487     [self performSelectorOnMainThread:@selector(setFrameOnMainThread:)
488                     withObject: packedargs waitUntilDone: YES];
489 }
490
491 - (void)setFrameOnMainThread:(NSData*)packedargs
492 {
493     struct args { NSRect frame; BOOL display; BOOL animate; } * args = (struct args*)[packedargs bytes];
494
495     [super setFrame: args->frame display: args->display animate:args->animate];
496 }
497 @end