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