1 /*****************************************************************************
2 * VLCFSPanelController.m: macOS fullscreen controls window controller
3 *****************************************************************************
4 * Copyright (C) 2006-2016 VLC authors and VideoLAN
7 * Authors: Jérôme Decoodt <djc at videolan dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
10 * Marvin Scholz <epirat07 at gmail dot com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 #import "VLCFSPanelController.h"
28 #import "VLCCoreInteraction.h"
29 #import "CompatibilityFixes.h"
32 @interface VLCFSPanelController () {
34 CGDirectDisplayID _displayID;
39 @implementation VLCFSPanelController
42 #pragma mark Initialization
46 self = [super initWithWindowNibName:@"VLCFullScreenPanel"];
52 [super windowDidLoad];
54 /* Do some window setup that is not possible in IB */
55 [self.window setOpaque:NO];
56 [self.window setAlphaValue:0.0f];
57 [self.window setMovableByWindowBackground:YES];
58 [self.window setLevel:NSModalPanelWindowLevel];
59 [self.window setStyleMask:self.window.styleMask | NSResizableWindowMask];
60 [self.window setBackgroundColor:[NSColor clearColor]];
62 #ifdef MAC_OS_X_VERSION_10_10
63 /* Inject correct background view depending on OS support */
64 if (OSX_YOSEMITE_OR_HIGHER) {
65 [self injectVisualEffectView];
67 [self injectBackgroundView];
70 /* Compiled with old SDK, always use legacy style */
71 [self injectBackgroundView];
74 [(NSButtonCell*)[_playPauseButton cell] setHighlightsBy:NSPushInCellMask];
75 [(NSButtonCell*)[_playPauseButton cell] setShowsStateBy:NSContentsCellMask];
77 [_volumeSlider setMaxValue:[[VLCCoreInteraction sharedInstance] maxVolume]];
78 [_volumeSlider setIntValue:AOUT_VOLUME_DEFAULT];
82 #pragma mark Control Actions
84 - (IBAction)togglePlayPause:(id)sender
86 [[VLCCoreInteraction sharedInstance] playOrPause];
89 - (IBAction)jumpForward:(id)sender
91 static NSTimeInterval last_event = 0;
92 if (([NSDate timeIntervalSinceReferenceDate] - last_event) > 0.16) {
93 /* We just skipped 4 "continous" events, otherwise we are too fast */
94 [[VLCCoreInteraction sharedInstance] forwardExtraShort];
95 last_event = [NSDate timeIntervalSinceReferenceDate];
99 - (IBAction)jumpBackward:(id)sender
101 static NSTimeInterval last_event = 0;
102 if (([NSDate timeIntervalSinceReferenceDate] - last_event) > 0.16) {
103 /* We just skipped 4 "continous" events, otherwise we are too fast */
104 [[VLCCoreInteraction sharedInstance] backwardExtraShort];
105 last_event = [NSDate timeIntervalSinceReferenceDate];
109 - (IBAction)gotoPrevious:(id)sender
111 [[VLCCoreInteraction sharedInstance] previous];
114 - (IBAction)gotoNext:(id)sender
116 [[VLCCoreInteraction sharedInstance] next];
119 - (IBAction)toggleFullscreen:(id)sender
121 [[VLCCoreInteraction sharedInstance] toggleFullscreen];
124 - (IBAction)timeSliderUpdate:(id)sender
126 input_thread_t *p_input;
127 p_input = pl_CurrentInput(getIntf());
131 pos.f_float = [_timeSlider floatValue] / 10000.;
132 var_Set(p_input, "position", pos);
133 vlc_object_release(p_input);
135 [[[VLCMain sharedInstance] mainWindow] updateTimeSlider];
138 - (IBAction)volumeSliderUpdate:(id)sender
140 [[VLCCoreInteraction sharedInstance] setVolume:[sender intValue]];
144 #pragma mark Metadata and state updates
148 [_playPauseButton setState:NSOffState];
153 [_playPauseButton setState:NSOnState];
156 - (void)setStreamTitle:(NSString *)title
158 [_mediaTitle setStringValue:title];
161 - (void)updatePositionAndTime
163 input_thread_t *p_input = pl_CurrentInput(getIntf());
165 /* If nothing is playing, reset times and slider */
167 [_timeSlider setFloatValue:0.0];
168 [_elapsedTime setStringValue:@""];
169 [_remainingOrTotalTime setHidden:YES];
174 char psz_time[MSTRTIME_MAX_SIZE];
176 var_Get(p_input, "position", &pos);
177 float f_updated = 10000. * pos.f_float;
178 [_timeSlider setFloatValue:f_updated];
181 int64_t t = var_GetInteger(p_input, "time");
182 mtime_t dur = input_item_GetDuration(input_GetItem(p_input));
184 /* Update total duration (right field) */
186 [_remainingOrTotalTime setHidden:YES];
188 [_remainingOrTotalTime setHidden:NO];
192 if ([_remainingOrTotalTime timeRemaining]) {
193 mtime_t remaining = 0;
196 totalTime = [NSString stringWithFormat:@"-%s", secstotimestr(psz_time, (remaining / 1000000))];
198 totalTime = toNSStr(secstotimestr(psz_time, (dur / 1000000)));
200 [_remainingOrTotalTime setStringValue:totalTime];
203 /* Update current position (left field) */
204 NSString *playbackPosition = toNSStr(secstotimestr(psz_time, t / CLOCK_FREQ));
206 [_elapsedTime setStringValue:playbackPosition];
207 vlc_object_release(p_input);
210 - (void)setSeekable:(BOOL)seekable
212 [_timeSlider setEnabled:seekable];
213 [_forwardButton setEnabled:seekable];
214 [_backwardButton setEnabled:seekable];
217 - (void)setVolumeLevel:(int)value
219 [_volumeSlider setIntValue:value];
223 #pragma mark Window interactions
227 [NSAnimationContext beginGrouping];
228 [[NSAnimationContext currentContext] setDuration:0.7f];
229 [[self.window animator] setAlphaValue:1.0f];
230 [NSAnimationContext endGrouping];
232 [self startAutohideTimer];
237 [NSAnimationContext beginGrouping];
238 [[NSAnimationContext currentContext] setDuration:0.7f];
239 [[self.window animator] setAlphaValue:0.0f];
240 [NSAnimationContext endGrouping];
243 - (void)centerWindowOnScreen:(CGDirectDisplayID)screenID
245 /* Find screen by its ID */
246 NSScreen *screen = [NSScreen screenWithDisplayID:screenID];
248 /* Check screen validity, fallback to mainScreen */
250 screen = [NSScreen mainScreen];
252 NSRect screenFrame = [screen frame];
253 NSRect windowFrame = [self.window frame];
255 /* Calculate coordinates for new NSWindow position */
257 coordinates.x = (screenFrame.size.width - windowFrame.size.width) / 2 + screenFrame.origin.x;
258 coordinates.y = (screenFrame.size.height / 3) - windowFrame.size.height + screenFrame.origin.y;
260 [self.window setFrameTopLeftPoint:coordinates];
265 [self centerWindowOnScreen:_displayID];
270 [self.window orderOut:self];
275 [self.window orderFront:self];
279 #pragma mark Misc interactions
283 [NSCursor setHiddenUntilMouseMoves:YES];
286 - (void)setVoutWasUpdated:(VLCWindow *)voutWindow
288 _voutWindow = voutWindow;
289 int newDisplayID = [[self.window screen] displayID];
291 if (_displayID != newDisplayID) {
292 _displayID = newDisplayID;
298 #pragma mark Autohide timer management
300 - (void)startAutohideTimer
302 /* Do nothing if timer is already in place */
306 int _timeToKeepVisibleInSec = var_CreateGetInteger(getIntf(), "mouse-hide-timeout") / 1000;
307 _hideTimer = [NSTimer scheduledTimerWithTimeInterval:_timeToKeepVisibleInSec
309 selector:@selector(autohideCallback:)
315 - (void)stopAutohideTimer
317 [_hideTimer invalidate];
321 - (void)autohideCallback:(NSTimer *)timer
323 if (!NSPointInRect([NSEvent mouseLocation], [self.window frame])) {
333 #ifdef MAC_OS_X_VERSION_10_10
335 Create an image mask for the NSVisualEffectView
336 with rounded corners in the given rect
338 This is necessary as clipping the VisualEffectView using the layers
339 rounded corners is not possible when using the NSColor clearColor
342 \note The returned image will have the necessary \c capInsets and
343 \c capResizingMode set.
345 \param bounds The rect for the image size
347 - (NSImage *)maskImageWithBounds:(NSRect)bounds
349 static const float radius = 8.0;
350 NSImage *img = [NSImage imageWithSize:bounds.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
351 NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:radius yRadius:radius];
352 [[NSColor blackColor] setFill];
356 [img setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
357 [img setResizingMode:NSImageResizingModeStretch];
362 Injects the visual effect view in the Windows view hierarchy
364 This is necessary as we can't use the NSVisualEffect view on
365 all macOS Versions and therefore need to dynamically insert it.
367 \warning Never call both, \c injectVisualEffectView and \c injectBackgroundView
369 - (void)injectVisualEffectView
372 NSVisualEffectView *view = [[NSVisualEffectView alloc] initWithFrame:self.window.contentView.frame];
373 [view setMaskImage:[self maskImageWithBounds:self.window.contentView.bounds]];
374 [view setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
375 [view setMaterial:NSVisualEffectMaterialDark];
376 [view setState:NSVisualEffectStateActive];
377 [view setAutoresizesSubviews:YES];
379 /* Inject view in view hierarchy */
380 [self.window setContentView:view];
381 [_controllsView setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
382 [self.window.contentView addSubview:_controllsView];
387 Injects the standard background view in the Windows view hierarchy
389 This is necessary on macOS versions that do not support the
390 NSVisualEffectView that usually is injected.
392 \warning Never call both, \c injectVisualEffectView and \c injectBackgroundView
394 - (void)injectBackgroundView
397 NSColor *color = [NSColor colorWithCalibratedWhite:0.0 alpha:0.8];
398 NSView *view = [[NSView alloc] initWithFrame:self.window.contentView.frame];
399 [view setWantsLayer:YES];
400 [view.layer setBackgroundColor:[color CGColor]];
401 [view.layer setCornerRadius:8.0];
402 [view setAutoresizesSubviews:YES];
404 /* Inject view in view hierarchy */
405 [self.window setContentView:view];
406 [self.window.contentView addSubview:_controllsView];
411 [self stopAutohideTimer];