]> git.sesse.net Git - vlc/blob - modules/gui/macosx/embeddedwindow.m
Mac OS X gui: Fix libvlc key handling when leaving fullscreen.
[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     if (!screen)
178         screen = [self screen];
179
180     screen_rect = [screen frame];
181
182     [o_btn_fullscreen setState: YES];
183
184     [NSCursor setHiddenUntilMouseMoves: YES];
185     
186     if (blackout_other_displays)
187         [screen blackoutOtherScreens]; /* We should do something like [screen blackoutOtherScreens]; */
188
189     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
190     if (!o_fullscreen_window)
191     {
192         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
193
194         rect = [[o_view superview] convertRect: [o_view frame] toView: nil]; /* Convert to Window base coord */
195         rect.origin.x += [self frame].origin.x;
196         rect.origin.y += [self frame].origin.y;
197         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
198         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
199         [o_fullscreen_window setCanBecomeKeyWindow: YES];
200
201         if (![self isVisible] || [self alphaValue] == 0.0 || MACOS_VERSION < 10.4f)
202         {
203             /* We don't animate if we are not visible or if we are running on
204              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
205              * simply fade the display */
206             CGDisplayFadeReservationToken token;
207             
208             [o_fullscreen_window setFrame:screen_rect display:NO];
209             
210             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
211             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
212             
213             if ([screen isMainScreen])
214                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
215             
216             [[self contentView] replaceSubview:o_view with:o_temp_view];
217             [o_temp_view setFrame:[o_view frame]];
218             [o_fullscreen_window setContentView:o_view];
219             [o_fullscreen_window makeKeyAndOrderFront:self];
220             [self orderOut: self];
221
222             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
223             CGReleaseDisplayFadeReservation( token);
224
225             /* Will release the lock */
226             [self hasBecomeFullscreen];
227
228             return;
229         }
230         
231         /* Make sure we don't see the o_view disappearing of the screen during this operation */
232         DisableScreenUpdates();
233         [[self contentView] replaceSubview:o_view with:o_temp_view];
234         [o_temp_view setFrame:[o_view frame]];
235         [o_fullscreen_window setContentView:o_view];
236         [o_fullscreen_window makeKeyAndOrderFront:self];
237         EnableScreenUpdates();
238     }
239
240     if (MACOS_VERSION < 10.4f)
241     {
242         /* We were already fullscreen nothing to do when NSAnimation
243          * is not supported */
244         b_animation_lock_alreadylocked = NO;
245         [self unlockFullscreenAnimation];
246         return;
247     }
248
249     /* We are in fullscreen (and no animation is running) */
250     if (b_fullscreen)
251     {
252         b_animation_lock_alreadylocked = NO;
253         [self unlockFullscreenAnimation];
254         return;
255     }
256
257     if (o_fullscreen_anim1)
258     {
259         [o_fullscreen_anim1 stopAnimation];
260         [o_fullscreen_anim1 release];
261     }
262     if (o_fullscreen_anim2)
263     {
264         [o_fullscreen_anim2 stopAnimation];
265         [o_fullscreen_anim2 release];
266     }
267  
268      /* This is a recursive lock. If we are already in the middle of an animation we
269      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
270      * are executed always in the same thread */ 
271     if (b_animation_lock_alreadylocked)
272         [self unlockFullscreenAnimation];
273     b_animation_lock_alreadylocked = YES;
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     b_fullscreen = NO;
335     [o_btn_fullscreen setState: NO];
336
337     /* We always try to do so */
338     [NSScreen unblackoutScreens];
339
340     /* Don't do anything if o_fullscreen_window is already closed */
341     if (!o_fullscreen_window)
342     {
343         b_animation_lock_alreadylocked = NO;
344         [self unlockFullscreenAnimation];
345         return;
346     }
347
348     if (![self isVisible] || MACOS_VERSION < 10.4f)
349     {
350         /* We don't animate if we are not visible or if we are running on
351         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
352         * simply fade the display */
353         CGDisplayFadeReservationToken token;
354
355         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
356         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
357
358         [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
359         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
360
361         /* Will release the lock */
362         [self hasEndedFullscreen];
363
364         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
365         CGReleaseDisplayFadeReservation( token);
366         return;
367     }
368
369     [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
370     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
371
372     if (o_fullscreen_anim1)
373     {
374         [o_fullscreen_anim1 stopAnimation];
375         [o_fullscreen_anim1 release];
376     }
377     if (o_fullscreen_anim2)
378     {
379         [o_fullscreen_anim2 stopAnimation];
380         [o_fullscreen_anim2 release];
381     }
382
383     /* This is a recursive lock. If we are already in the middle of an animation we
384      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
385      * are executed always in the same thread */ 
386     if (b_animation_lock_alreadylocked)
387         [self unlockFullscreenAnimation];
388     b_animation_lock_alreadylocked = YES;
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     [self makeFirstResponder: o_view];
435     if ([self isVisible])
436         [self makeKeyAndOrderFront:self];
437     [o_fullscreen_window orderOut: self];
438     EnableScreenUpdates();
439
440     [o_fullscreen_window release];
441     o_fullscreen_window = nil;
442     b_animation_lock_alreadylocked = NO;
443     [self unlockFullscreenAnimation];
444 }
445
446 - (void)animationDidEnd:(NSAnimation*)animation
447 {
448     NSArray *viewAnimations;
449
450     if ([animation currentValue] < 1.0)
451         return;
452
453     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
454     viewAnimations = [o_fullscreen_anim2 viewAnimations];
455     if ([viewAnimations count] >=1 &&
456         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
457     {
458         /* Fullscreen ended */
459         [self hasEndedFullscreen];
460     }
461     else
462     {
463         /* Fullscreen started */
464         [self hasBecomeFullscreen];
465     }
466 }
467
468 - (void)orderOut: (id)sender
469 {
470     [super orderOut: sender];
471     /* Make sure we leave fullscreen */
472     [self leaveFullscreen];
473 }
474
475 /* Make sure setFrame gets executed on main thread especially if we are animating.
476  * (Thus we won't block the video output thread) */
477 - (void)setFrame:(NSRect)frame display:(BOOL)display animate:(BOOL)animate
478 {
479     struct { NSRect frame; BOOL display; BOOL animate;} args;
480     NSData *packedargs;
481
482     args.frame = frame;
483     args.display = display;
484     args.animate = animate;
485
486     packedargs = [NSData dataWithBytes:&args length:sizeof(args)];
487
488     [self performSelectorOnMainThread:@selector(setFrameOnMainThread:)
489                     withObject: packedargs waitUntilDone: YES];
490 }
491
492 - (void)setFrameOnMainThread:(NSData*)packedargs
493 {
494     struct args { NSRect frame; BOOL display; BOOL animate; } * args = (struct args*)[packedargs bytes];
495
496     [super setFrame: args->frame display: args->display animate:args->animate];
497 }
498 @end