vlcplugin_mac.mm 33.6 KB
Newer Older
1 2 3
/*****************************************************************************
 * vlcplugin_mac.cpp: a VLC plugin for Mozilla (Mac interface)
 *****************************************************************************
4
 * Copyright (C) 2011-2014 VLC Authors and VideoLAN
5 6
 * $Id$
 *
7
 * Authors: Felix Paul Kühne <fkuehne # videolan # org>
8
 *          Cheng Sun <chengsun9@gmail.com>
9 10
 *          Jean-Baptiste Kempf <jb@videolan.org>
 *          James Bates <james.h.bates@gmail.com>
11
 *          Pierre d'Herbemont <pdherbemont # videolan.org>
12
 *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
13
 *
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

29 30
#include "vlcplugin_mac.h"

31 32
#include <npapi.h>

33
#include <QuartzCore/QuartzCore.h>
34
#include <AppKit/AppKit.h>
35

36 37 38 39 40 41 42 43 44 45
/* compilation support for 10.6 */
#define OSX_LION NSAppKitVersionNumber >= 1115.2
#ifndef MAC_OS_X_VERSION_10_7

@interface NSView (IntroducedInLion)
- (NSRect)convertRectToBacking:(NSRect)aRect;
@end

#endif

46 47 48 49
@interface VLCNoMediaLayer : CALayer {
    VlcPluginMac *_cppPlugin;
}
@property (readwrite) VlcPluginMac * cppPlugin;
50 51 52

@end

53
@interface VLCBrowserRootLayer : CALayer
54 55 56 57 58 59 60 61
{
    NSTimer *_interfaceUpdateTimer;
    VlcPluginMac *_cppPlugin;
}
@property (readwrite) VlcPluginMac * cppPlugin;

- (void)startUIUpdateTimer;

62 63 64
- (void)addVoutLayer:(CALayer *)aLayer;
- (void)removeVoutLayer:(CALayer *)aLayer;
- (CGSize)currentOutputSize;
65 66
@end

67
@interface VLCControllerLayer : CALayer {
68 69 70 71 72 73 74
    CGImageRef _playImage;
    CGImageRef _pauseImage;

    CGImageRef _sliderTrackLeft;
    CGImageRef _sliderTrackRight;
    CGImageRef _sliderTrackCenter;

75 76 77
    CGImageRef _enterFullscreen;
    CGImageRef _leaveFullscreen;

78
    CGImageRef _knob;
79 80 81 82

    BOOL _wasPlayingBeforeMouseDown;
    BOOL _isScrubbing;
    CGFloat _mouseDownXDelta;
83 84 85

    double _position;
    BOOL _isPlaying;
86
    BOOL _isFullscreen;
87 88

    VlcPluginMac *_cppPlugin;
89
}
90 91
@property (readwrite) double mediaPosition;
@property (readwrite) BOOL isPlaying;
92
@property (readwrite) BOOL isFullscreen;
93 94
@property (readwrite) VlcPluginMac * cppPlugin;

95 96 97
- (void)handleMouseDown:(CGPoint)point;
- (void)handleMouseUp:(CGPoint)point;
- (void)handleMouseDragged:(CGPoint)point;
98 99 100

@end

101 102 103 104 105 106
@interface VLCControllerLayer (Internal)
- (CGRect)_playPauseButtonRect;
- (CGRect)_fullscreenButtonRect;
- (CGRect)_sliderRect;
@end

107 108 109 110 111 112
@interface VLCPlaybackLayer : CALayer
- (void)mouseButtonDown:(int)buttonNumber;
- (void)mouseButtonUp:(int)buttonNumber;
- (void)mouseMovedToX:(double)xValue Y:(double)yValue;
@end

113 114
@interface VLCFullscreenContentView : NSView {
    VlcPluginMac *_cppPlugin;
115
    NSTimeInterval _timeSinceLastMouseMove;
116 117
}
@property (readwrite) VlcPluginMac * cppPlugin;
118 119 120 121

@end

@interface VLCFullscreenWindow : NSWindow {
122 123
    NSRect _initialFrame;
    VLCFullscreenContentView *_customContentView;
124
}
125
@property (readonly) VLCFullscreenContentView* customContentView;
126 127 128 129 130

- (id)initWithContentRect:(NSRect)contentRect;

@end

131 132 133 134 135 136
@interface NSScreen (VLCAdditions)
- (BOOL)hasMenuBar;
- (BOOL)hasDock;
- (CGDirectDisplayID)displayID;
@end

137 138 139 140 141 142 143 144 145
VLCBrowserRootLayer * browserRootLayer;
VLCPlaybackLayer * playbackLayer;
VLCNoMediaLayer * noMediaLayer;
VLCControllerLayer * controllerLayer;
VLCFullscreenWindow * fullscreenWindow;
VLCFullscreenContentView * fullscreenView;
CGImageRef createImageNamed(NSString *);

CGImageRef createImageNamed(NSString *name)
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
{
    CFURLRef url = CFBundleCopyResourceURL(CFBundleGetBundleWithIdentifier(CFSTR("org.videolan.vlc-npapi-plugin")), (CFStringRef)name, CFSTR("png"), NULL);

    if (!url)
        return NULL;

    CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL);
    if (!imageSource)
        return NULL;

    CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    CFRelease(imageSource);

    return image;
}
161

162 163 164
VlcPluginMac::VlcPluginMac(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
165
    browserRootLayer = [[VLCBrowserRootLayer alloc] init];
166 167 168 169 170 171 172
    [browserRootLayer setCppPlugin:this];

    const char *userAgent = NPN_UserAgent(this->getBrowser());
    if (strstr(userAgent, "Safari") && strstr(userAgent, "Version/5")) {
        NSLog(@"Safari 5 detected, deploying UI update timer");
        [browserRootLayer performSelector:@selector(startUIUpdateTimer) withObject:nil afterDelay:1.];
    }
173 174 175 176
}

VlcPluginMac::~VlcPluginMac()
{
177
    if (fullscreenWindow != NULL)
178
        [fullscreenWindow release];
179
    if (playbackLayer != NULL)
180
        [playbackLayer release];
181
    if (noMediaLayer != NULL)
182
        [noMediaLayer release];
183
    if (controllerLayer != NULL)
184
        [controllerLayer release];
185
    if (browserRootLayer != NULL)
186
        [browserRootLayer release];
187 188 189 190
}

void VlcPluginMac::set_player_window()
{
191 192
    /* pass base layer to libvlc to pass it on to the vout */
    libvlc_media_player_set_nsobject(getMD(), browserRootLayer);
193 194 195 196
}

void VlcPluginMac::toggle_fullscreen()
{
197 198
    if (!get_options().get_enable_fs())
        return;
199
    libvlc_toggle_fullscreen(getMD());
200
    this->update_controls();
201

202
    if (get_fullscreen() != 0) {
203 204 205 206 207 208 209 210 211 212 213
        /* this window is kind of useless. however, we need to support 10.5, since enterFullScreenMode depends on the
         * existance of a parent window. This is solved in 10.6 and we should remove the window once we require it. */
        fullscreenWindow = [[VLCFullscreenWindow alloc] initWithContentRect: NSMakeRect(0., 0., npwindow.width, npwindow.height)];
        [fullscreenWindow setLevel: CGShieldingWindowLevel()];
        fullscreenView = [fullscreenWindow customContentView];

        /* CAVE: the order of these methods is important, since we want a layer-hosting view instead of
         * a layer-backed view, which you'd get if you do it the other way around */
        [fullscreenView setLayer: [CALayer layer]];
        [fullscreenView setWantsLayer:YES];
        [fullscreenView setCppPlugin: this];
214 215 216 217 218

        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

219 220 221 222 223
        if (!fullscreenView)
            return;
        if (![fullscreenView layer])
            return;

224 225 226 227 228
        [[fullscreenView layer] addSublayer: noMediaLayer];
        [[fullscreenView layer] addSublayer: playbackLayer];
        [[fullscreenView layer] addSublayer: controllerLayer];
        [[fullscreenView layer] setNeedsDisplay];

229
        [[fullscreenWindow contentView] enterFullScreenMode: [NSScreen mainScreen] withOptions: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 0], NSFullScreenModeAllScreens, nil]];
230
    } else {
231 232 233 234 235
        if (!fullscreenWindow)
            return;
        if (!fullscreenWindow.contentView)
            return;

236
        [[fullscreenWindow contentView] exitFullScreenModeWithOptions: nil];
237 238 239 240 241 242 243
        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

        [browserRootLayer addSublayer: noMediaLayer];
        [browserRootLayer addSublayer: playbackLayer];
        [browserRootLayer addSublayer: controllerLayer];
244
        [fullscreenWindow orderOut: nil];
245
        [fullscreenWindow release];
246
    }
247 248
}

249
void VlcPluginMac::set_fullscreen(int i_value)
250
{
251 252
    if (!get_options().get_enable_fs())
        return;
253
    libvlc_set_fullscreen(getMD(), i_value);
254
    this->update_controls();
255 256 257 258
}

int  VlcPluginMac::get_fullscreen()
{
259
    return libvlc_get_fullscreen(getMD());
260 261
}

262 263
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
264 265
    if (!get_options().get_show_toolbar()) {
        [controllerLayer setHidden: YES];
266
        return;
267
    }
268
    [controllerLayer setHidden: !b_value];
269 270 271 272
}

bool VlcPluginMac::get_toolbar_visible()
{
273
    return controllerLayer.isHidden;
274 275 276 277
}

void VlcPluginMac::update_controls()
{
278 279
    libvlc_state_t currentstate = libvlc_media_player_get_state(getMD());
    if (currentstate == libvlc_Playing || currentstate == libvlc_Paused || currentstate == libvlc_Opening) {
280 281 282 283 284 285 286
        [noMediaLayer setHidden: YES];
        [playbackLayer setHidden: NO];
    } else {
        [noMediaLayer setHidden: NO];
        [playbackLayer setHidden: YES];
    }

287 288 289 290 291 292
    if (controllerLayer) {
        [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
        [controllerLayer setIsPlaying: playlist_isplaying()];
        [controllerLayer setIsFullscreen:this->get_fullscreen()];
        [controllerLayer setNeedsDisplay];
    }
293 294
}

295 296 297 298 299 300 301
bool VlcPluginMac::create_windows()
{
    return true;
}

bool VlcPluginMac::resize_windows()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
302
    return true;
303 304 305 306
}

bool VlcPluginMac::destroy_windows()
{
307
    npwindow.window = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
308
    return true;
309
}
310

311 312
NPError VlcPluginMac::get_root_layer(void *value)
{
313 314
    noMediaLayer = [[VLCNoMediaLayer alloc] init];
    noMediaLayer.opaque = 1.;
315
    [noMediaLayer setCppPlugin: this];
316
    [browserRootLayer addSublayer: noMediaLayer];
317

318
    controllerLayer = [[VLCControllerLayer alloc] init];
319
    [browserRootLayer addSublayer: controllerLayer];
320
    [controllerLayer setCppPlugin: this];
321

322 323 324
    [browserRootLayer setNeedsDisplay];

    *(CALayer **)value = browserRootLayer;
325
    return NPERR_NO_ERROR;
326 327
}

328 329
bool VlcPluginMac::handle_event(void *event)
{
330
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
331

332 333 334 335 336 337 338
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
339
        {
340 341 342 343
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)])
                    [playbackLayer mouseButtonDown:cocoaEvent->data.mouse.buttonNumber];
            }
344 345 346
            if (cocoaEvent->data.mouse.clickCount >= 2)
                VlcPluginMac::toggle_fullscreen();

347 348 349
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);
350 351
            if (controllerLayer)
                [controllerLayer handleMouseDown:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
352

353 354
            return true;
        }
355
        case NPCocoaEventMouseUp:
356
        {
357 358 359 360
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)])
                    [playbackLayer mouseButtonUp:cocoaEvent->data.mouse.buttonNumber];
            }
361 362 363 364
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

365 366
            if (controllerLayer)
                [controllerLayer handleMouseUp:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
367 368 369

            return true;
        }
370 371 372 373 374 375 376
        case NPCocoaEventMouseMoved:
        {
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseMovedToX:Y:)])
                    [playbackLayer mouseMovedToX:cocoaEvent->data.mouse.pluginX Y:cocoaEvent->data.mouse.pluginY];
            }
        }
377 378 379 380 381 382
        case NPCocoaEventMouseDragged:
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

383 384
            if (controllerLayer)
                [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
385 386 387

            return true;
        }
388 389 390 391 392 393 394 395 396 397
        case NPCocoaEventMouseEntered:
        {
            set_toolbar_visible(true);
            return true;
        }
        case NPCocoaEventMouseExited:
        {
            set_toolbar_visible(false);
            return true;
        }
398
        case NPCocoaEventKeyDown:
399 400 401 402
        {
            if (cocoaEvent->data.key.keyCode == 53) {
                toggle_fullscreen();
                return true;
403 404 405
            } else if (cocoaEvent->data.key.keyCode == 49) {
                playlist_togglePause();
                return true;
406 407 408
            }
        }
        case NPCocoaEventKeyUp:
409 410
        case NPCocoaEventFocusChanged:
        case NPCocoaEventScrollWheel:
411
            return true;
412 413 414 415 416

        default:
            break;
    }

417
    if (eventType == NPCocoaEventDrawRect) {
418 419 420 421 422 423 424 425
        /* even though we are using the CoreAnimation drawing model
         * this can be called by the browser, especially when doing
         * screenshots.
         * Since speed isn't important in this case, we could fetch
         * fetch the current frame from libvlc and render it as an
         * image.
         * However, for sakes of simplicity, just show a black
         * rectancle for now. */
426 427 428
        CGContextRef cgContext = cocoaEvent->data.draw.context;
        if (!cgContext) {
            return false;
429
        }
430 431 432 433 434 435 436 437 438 439

        float windowWidth = npwindow.width;
        float windowHeight = npwindow.height;

        CGContextSaveGState(cgContext);

        // this context is flipped..
        CGContextTranslateCTM(cgContext, 0.0, windowHeight);
        CGContextScaleCTM(cgContext, 1., -1.);

440
        // draw black rectancle
441
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
442
        CGContextSetGrayFillColor(cgContext, 0., 1.);
443 444 445 446 447
        CGContextDrawPath(cgContext, kCGPathFill);

        CGContextRestoreGState(cgContext);

        return true;
448
    }
449

450 451
    return VlcPluginBase::handle_event(event);
}
452

453
@implementation VLCBrowserRootLayer
454
@synthesize cppPlugin = _cppPlugin;
455 456 457 458 459 460 461 462 463 464 465

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
    }

    return self;
}

466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
- (void)startUIUpdateTimer
{
    _interfaceUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_updateUI) userInfo:nil repeats:YES];
    [_interfaceUpdateTimer retain];
    [_interfaceUpdateTimer fire];
}

- (void)dealloc
{
    if (_interfaceUpdateTimer) {
        [_interfaceUpdateTimer invalidate];
        [_interfaceUpdateTimer release];
    }

    [super dealloc];
}

- (void)_updateUI
{
    if (_cppPlugin)
        _cppPlugin->update_controls();
}

489
- (void)addVoutLayer:(CALayer *)aLayer
490
{
491
    [CATransaction begin];
492
    playbackLayer = (VLCPlaybackLayer *)[aLayer retain];
493 494
    playbackLayer.opaque = 1.;
    playbackLayer.hidden = NO;
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513

    if (libvlc_get_fullscreen(_cppPlugin->getMD()) != 0) {
        /* work-around a 32bit runtime limitation where we can't cast
         * NSRect to CGRect */
        NSRect fullscreenViewFrame = fullscreenView.frame;
        playbackLayer.bounds = CGRectMake(fullscreenViewFrame.origin.x,
                                          fullscreenViewFrame.origin.y,
                                          fullscreenViewFrame.size.width,
                                          fullscreenViewFrame.size.height);
        playbackLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;

        [controllerLayer removeFromSuperlayer];
        [[fullscreenView layer] addSublayer: playbackLayer];
        [[fullscreenView layer] addSublayer: controllerLayer];
        [[fullscreenView layer] setNeedsDisplay];
    } else {
        playbackLayer.bounds = noMediaLayer.bounds;
        [self insertSublayer:playbackLayer below:controllerLayer];
    }
514 515 516 517 518 519 520
    [self setNeedsDisplay];
    [playbackLayer setNeedsDisplay];
    CGRect frame = playbackLayer.bounds;
    frame.origin.x = 0.;
    frame.origin.y = 0.;
    playbackLayer.frame = frame;
    [CATransaction commit];
521 522
}

523
- (void)removeVoutLayer:(CALayer *)aLayer
524
{
525 526 527
    [CATransaction begin];
    [aLayer removeFromSuperlayer];
    [CATransaction commit];
528

529
    if (playbackLayer == aLayer) {
530
        [playbackLayer release];
531 532
        playbackLayer = nil;
    }
533
}
534

535 536 537
- (CGSize)currentOutputSize
{
    return [browserRootLayer visibleRect].size;
538 539 540 541
}

@end

542
@implementation VLCNoMediaLayer
543
@synthesize cppPlugin = _cppPlugin;
544 545 546 547 548

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
549
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
550 551 552 553 554
    }

    return self;
}

555
- (void)drawInContext:(CGContextRef)cgContext
556 557 558 559 560 561
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

562 563 564 565 566
    CGColorRef backgroundColor;
    unsigned r = 0, g = 0, b = 0;
    HTMLColor2RGB(self.cppPlugin->get_options().get_bg_color().c_str(), &r, &g, &b);
    backgroundColor = CGColorCreateGenericRGB(r, g, b, 1.);

567 568 569 570 571 572 573 574 575 576 577 578
    if (self.cppPlugin->get_options().get_enable_branding()) {
        // draw background
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
        CGContextSetFillColorWithColor(cgContext, backgroundColor);
        CGContextDrawPath(cgContext, kCGPathFill);

        // draw gradient
        CGImageRef gradient = createImageNamed(@"gradient");
        CGContextDrawImage(cgContext, CGRectMake(0., 0., windowWidth, 150.), gradient);
        CGImageRelease(gradient);

        // draw info text
579 580 581 582 583 584 585
        CGContextSetGrayStrokeColor(cgContext, .95, 1.);
        CGContextSetTextDrawingMode(cgContext, kCGTextFill);
        CGContextSetGrayFillColor(cgContext, 1., 1.);
        CFStringRef keys[2];
        keys[0] = kCTFontAttributeName;
        keys[1] = kCTForegroundColorFromContextAttributeName;
        CFTypeRef values[2];
586
        values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
587 588 589 590 591
        values[1] = kCFBooleanTrue;
        CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                        (const void **)&keys,
                                                        (const void **)&values,
                                                        2, NULL, NULL);
592
        CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("VLC Web Plugin"), stylesDict);
593
        CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
        CGContextSetTextPosition(cgContext, 25., 60.);
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);

        // print smaller text from here
        CFRelease(stylesDict);
        values[0] = CTFontCreateWithName(CFSTR("Helvetica"),12,NULL);
        stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                        (const void **)&keys,
                                        (const void **)&values,
                                        2, NULL, NULL);

        // draw version string
        CFStringRef arch;
    #ifdef __x86_64__
        arch = CFSTR("64-bit");
    #else
        arch = CFSTR("32-bit");
    #endif

        attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s — windowed mode — %@"), libvlc_get_version(), arch), stylesDict);
        textLine = CTLineCreateWithAttributedString(attRef);
        CGContextSetTextPosition(cgContext, 25., 40.);
618 619 620 621
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);
        CFRelease(stylesDict);
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665

        // draw cone
        CGImageRef cone = createImageNamed(@"cone");
        CGFloat coneWidth = CGImageGetWidth(cone);
        CGFloat coneHeight = CGImageGetHeight(cone);
        if (windowHeight <= 320.) {
            coneWidth = coneWidth / 2.;
            coneHeight = coneHeight / 2.;
        }
        CGContextDrawImage(cgContext, CGRectMake((windowWidth - coneWidth) / 2., (windowHeight - coneHeight) / 2., coneWidth, coneHeight), cone);
        CGImageRelease(cone);

        // draw custom text
        values[0] = CTFontCreateWithName(CFSTR("Helvetica"),14,NULL);
        stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                        (const void **)&keys,
                                        (const void **)&values,
                                        2, NULL, NULL);
        const char *text = self.cppPlugin->get_options().get_bg_text().c_str();
        if (text != NULL) {
            attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8), stylesDict);
            textLine = CTLineCreateWithAttributedString(attRef);
            CGRect textRect = CTLineGetImageBounds(textLine, cgContext);
            CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2.), (windowHeight / 2.) + (coneHeight / 2.) + textRect.size.height + 50.);
            CTLineDraw(textLine, cgContext);
            CFRelease(textLine);
            CFRelease(attRef);
        }
        CFRelease(stylesDict);
    } else {
        // draw a background colored rect
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
        CGContextSetFillColorWithColor(cgContext, backgroundColor);
        CGContextDrawPath(cgContext, kCGPathFill);

        const char *text = self.cppPlugin->get_options().get_bg_text().c_str();
        if (text != NULL) {
            CGContextSetGrayStrokeColor(cgContext, .95, 1.);
            CGContextSetTextDrawingMode(cgContext, kCGTextFill);
            CGContextSetGrayFillColor(cgContext, 1., 1.);
            CFStringRef keys[2];
            keys[0] = kCTFontAttributeName;
            keys[1] = kCTForegroundColorFromContextAttributeName;
            CFTypeRef values[2];
666
            values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
667 668 669 670 671 672 673 674 675 676 677 678 679 680
            values[1] = kCFBooleanTrue;
            CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                            (const void **)&keys,
                                                            (const void **)&values,
                                                            2, NULL, NULL);
            CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8), stylesDict);
            CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
            CGRect textRect = CTLineGetImageBounds(textLine, cgContext);
            CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2.), (windowHeight / 2.));
            CTLineDraw(textLine, cgContext);
            CFRelease(textLine);
            CFRelease(attRef);
            CFRelease(stylesDict);
        }
681
    }
682
    CGColorRelease(backgroundColor);
683 684 685 686

    CGContextRestoreGState(cgContext);
}

687 688 689 690
@end

@implementation VLCControllerLayer

691 692
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
693
@synthesize isFullscreen = _isFullscreen;
694 695
@synthesize cppPlugin = _cppPlugin;

696 697 698 699 700 701 702 703 704 705 706 707 708
- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
        self.frame = CGRectMake(0, 0, 0, 25);
        self.autoresizingMask = kCALayerWidthSizable;

        _playImage = createImageNamed(@"Play");
        _pauseImage = createImageNamed(@"Pause");
        _sliderTrackLeft = createImageNamed(@"SliderTrackLeft");
        _sliderTrackRight = createImageNamed(@"SliderTrackRight");
        _sliderTrackCenter = createImageNamed(@"SliderTrackCenter");

709 710 711
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

712
        _knob = createImageNamed(@"Knob");
713
    }
714 715 716 717 718 719 720 721 722 723 724 725 726

    return self;
}

- (void)dealloc
{
    CGImageRelease(_playImage);
    CGImageRelease(_pauseImage);

    CGImageRelease(_sliderTrackLeft);
    CGImageRelease(_sliderTrackRight);
    CGImageRelease(_sliderTrackCenter);

727 728 729
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

730 731 732 733 734
    CGImageRelease(_knob);

    [super dealloc];
}

735 736 737
#pragma mark -
#pragma mark drawing

738 739
- (CGRect)_playPauseButtonRect
{
740 741 742 743 744 745
    return CGRectMake(4., (25. - CGImageGetHeight(_playImage)) / 2., CGImageGetWidth(_playImage), CGImageGetHeight(_playImage));
}

- (CGRect)_fullscreenButtonRect
{
    return CGRectMake( CGRectGetMaxX([self _sliderRect]), (25. - CGImageGetHeight(_enterFullscreen)) / 2., CGImageGetWidth(_enterFullscreen), CGImageGetHeight(_enterFullscreen));
746 747 748 749
}

- (CGRect)_sliderRect
{
750
    CGFloat sliderYPosition = (self.bounds.size.height - CGImageGetHeight(_sliderTrackLeft)) / 2.;
751
    CGFloat playPauseButtonWidth = [self _playPauseButtonRect].size.width;
752
    CGFloat fullscreenButtonWidth = self.cppPlugin->get_options().get_enable_fs() ? CGImageGetWidth(_enterFullscreen) : 0.;
753

754
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
755
                      self.bounds.size.width - playPauseButtonWidth - fullscreenButtonWidth - 15., CGImageGetHeight(_sliderTrackLeft));
756 757 758 759 760 761
}

- (CGRect)_sliderThumbRect
{
    CGRect sliderRect = [self _sliderRect];

762
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
763

764
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
765 766 767 768 769 770 771 772 773 774
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

- (CGRect)_innerSliderRect
{
    return CGRectInset([self _sliderRect], CGRectGetWidth([self _sliderThumbRect]) / 2, 0);
}

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
775
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
}

- (void)_drawSliderInContext:(CGContextRef)context
{
    // Draw the thumb
    CGRect sliderThumbRect = [self _sliderThumbRect];
    CGContextDrawImage(context, sliderThumbRect, _knob);

    CGRect sliderRect = [self _sliderRect];

    // Draw left part
    CGRect sliderLeftTrackRect = CGRectMake(CGRectGetMinX(sliderRect), CGRectGetMinY(sliderRect),
                                            CGImageGetWidth(_sliderTrackLeft), CGImageGetHeight(_sliderTrackLeft));
    CGContextDrawImage(context, sliderLeftTrackRect, _sliderTrackLeft);

    // Draw center part
    CGRect sliderCenterTrackRect = CGRectInset(sliderRect, CGImageGetWidth(_sliderTrackLeft), 0);
    CGContextDrawImage(context, sliderCenterTrackRect, _sliderTrackCenter);

    // Draw right part
    CGRect sliderRightTrackRect = CGRectMake(CGRectGetMaxX(sliderCenterTrackRect), CGRectGetMinY(sliderRect),
                                             CGImageGetWidth(_sliderTrackRight), CGImageGetHeight(_sliderTrackRight));
    CGContextDrawImage(context, sliderRightTrackRect, _sliderTrackRight);

800
    // Draw fullscreen button
801 802 803 804 805
    if (self.cppPlugin->get_options().get_enable_fs()) {
        CGRect fullscreenButtonRect = [self _fullscreenButtonRect];
        fullscreenButtonRect.origin.x = CGRectGetMaxX(sliderRightTrackRect) + 5;
        CGContextDrawImage(context, fullscreenButtonRect, self.isFullscreen ? _leaveFullscreen : _enterFullscreen);
    }
806 807 808 809 810 811 812 813 814
}

- (void)drawInContext:(CGContextRef)cgContext
{
    CGContextSetFillColorWithColor(cgContext, CGColorGetConstantColor(kCGColorBlack));
    CGContextFillRect(cgContext, self.bounds);

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
815 816
}

817 818 819 820 821 822 823
#pragma mark -
#pragma mark event handling

- (void)_setNewTimeForThumbCenterX:(CGFloat)centerX
{
    CGRect innerRect = [self _innerSliderRect];

824
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
825 826 827 828 829
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

830
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
831 832 833 834 835 836 837

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
838
        _wasPlayingBeforeMouseDown = self.isPlaying;
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
        _isScrubbing = YES;

        if (CGRectContainsPoint([self _sliderThumbRect], point))
            _mouseDownXDelta = point.x - CGRectGetMidX([self _sliderThumbRect]);
        else {
            [self _setNewTimeForThumbCenterX:point.x];
            _mouseDownXDelta = 0;
        }
    }
}

- (void)handleMouseUp:(CGPoint)point
{
    if (_isScrubbing) {
        _isScrubbing = NO;
        _mouseDownXDelta = 0;

856
        return;
857 858 859
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
860
        self.cppPlugin->playlist_togglePause();
861 862
        return;
    }
863 864 865 866
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
867 868 869 870 871 872 873 874 875 876 877 878
}

- (void)handleMouseDragged:(CGPoint)point
{
    if (!_isScrubbing)
        return;

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

879
@end
880

881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
@implementation NSScreen (VLCAdditions)

- (BOOL)hasMenuBar
{
    return ([self displayID] == [[[NSScreen screens] objectAtIndex:0] displayID]);
}

- (BOOL)hasDock
{
    NSRect screen_frame = [self frame];
    NSRect screen_visible_frame = [self visibleFrame];
    CGFloat f_menu_bar_thickness = [self hasMenuBar] ? [[NSStatusBar systemStatusBar] thickness] : 0.0;

    BOOL b_found_dock = NO;
    if (screen_visible_frame.size.width < screen_frame.size.width)
        b_found_dock = YES;
    else if (screen_visible_frame.size.height + f_menu_bar_thickness < screen_frame.size.height)
        b_found_dock = YES;

    return b_found_dock;
}

- (CGDirectDisplayID)displayID
{
    return (CGDirectDisplayID)[[[self deviceDescription] objectForKey: @"NSScreenNumber"] intValue];
}

@end

910 911
@implementation VLCFullscreenWindow

912 913
@synthesize customContentView = _customContentView;

914 915 916
- (id)initWithContentRect:(NSRect)contentRect
{
    if( self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
917
        _initialFrame = contentRect;
918
        [self setBackgroundColor:[NSColor blackColor]];
919
        [self setAcceptsMouseMovedEvents: YES];
920 921

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
922
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
923
        [[self contentView] addSubview: _customContentView];
924
        [self setInitialFirstResponder:_customContentView];
925 926 927 928
    }
    return self;
}

929 930 931 932 933 934
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

935 936 937 938 939 940 941 942 943 944
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

945 946
@end

947
@implementation VLCFullscreenContentView
948 949 950 951 952 953 954
@synthesize cppPlugin = _cppPlugin;

- (BOOL)acceptsFirstResponder
{
    return YES;
}

955 956 957 958 959
- (BOOL)canBecomeKeyView
{
    return YES;
}

960 961 962 963 964 965 966 967 968 969 970 971
- (void)keyDown:(NSEvent *)theEvent
{
    NSString * characters = [theEvent charactersIgnoringModifiers];
    unichar key = 0;

    if ([characters length] > 0) {
        key = [[characters lowercaseString] characterAtIndex: 0];
        if (key) {
            /* Escape should always get you out of fullscreen */
            if (key == (unichar) 0x1b) {
                self.cppPlugin->toggle_fullscreen();
                return;
972 973 974
            } else if (key == ' ') {
                self.cppPlugin->playlist_togglePause();
                return;
975 976 977 978 979
            }
        }
    }
    [super keyDown: theEvent];
}
980

981 982
- (void)mouseDown:(NSEvent *)theEvent
{
983 984 985
    NSEventType eventType = [theEvent type];

    if (eventType == NSLeftMouseDown && !([theEvent modifierFlags] & NSControlKeyMask)) {
986
        if ([theEvent clickCount] >= 2)
987
            self.cppPlugin->toggle_fullscreen();
988 989 990
        else {
            NSPoint point = [NSEvent mouseLocation];

991
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
992
        }
993
    }
994 995 996 997 998 999 1000 1001 1002 1003
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)]) {
            if (eventType == NSLeftMouseDown)
                [playbackLayer mouseButtonDown:0];
            else if (eventType == NSRightMouseDown)
                [playbackLayer mouseButtonDown:1];
            else
                [playbackLayer mouseButtonDown:2];
        }
    }
1004 1005 1006 1007

    [super mouseDown: theEvent];
}

1008 1009 1010
- (void)mouseUp:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];
1011
    NSEventType eventType = [theEvent type];
1012

1013
    [controllerLayer handleMouseUp:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1014

1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)]) {
            if (eventType == NSLeftMouseUp)
                [playbackLayer mouseButtonUp:0];
            else if (eventType == NSRightMouseUp)
                [playbackLayer mouseButtonUp:1];
            else
                [playbackLayer mouseButtonUp:2];
        }
    }

1026 1027 1028 1029 1030 1031 1032
    [super mouseUp: theEvent];
}

- (void)mouseDragged:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];

1033
    [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1034 1035 1036 1037 1038 1039 1040

    [super mouseDragged: theEvent];
}

- (void)mouseMoved:(NSEvent *)theEvent
{
    self.cppPlugin->set_toolbar_visible(true);
1041 1042
    _timeSinceLastMouseMove = [NSDate timeIntervalSinceReferenceDate];
    [self performSelector:@selector(hideToolbar) withObject:nil afterDelay: 4.1];
1043

1044 1045 1046 1047 1048 1049 1050
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseMovedToX:Y:)]) {
            NSPoint ml = [theEvent locationInWindow];
            [playbackLayer mouseMovedToX:ml.x Y:([self.window frame].size.height - ml.y)];
        }
    }

1051 1052 1053
    [super mouseMoved: theEvent];
}

1054 1055 1056 1057 1058 1059 1060 1061
- (void)hideToolbar
{
    if ([NSDate timeIntervalSinceReferenceDate] - _timeSinceLastMouseMove >= 4) {
        self.cppPlugin->set_toolbar_visible(false);
        [NSCursor setHiddenUntilMouseMoves:YES];
    }
}

1062 1063
@end