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