vlcplugin_mac.mm 32.7 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
static VLCBrowserRootLayer * browserRootLayer;
138
static VLCPlaybackLayer * playbackLayer;
139
static VLCNoMediaLayer * noMediaLayer;
140
static VLCControllerLayer * controllerLayer;
141
static VLCFullscreenWindow * fullscreenWindow;
142
static VLCFullscreenContentView * fullscreenView;
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
static CGImageRef createImageNamed(NSString *);

static CGImageRef createImageNamed(NSString *name)
{
    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 178 179 180 181 182 183 184 185 186
    if (fullscreenWindow)
        [fullscreenWindow release];
    if (playbackLayer)
        [playbackLayer release];
    if (noMediaLayer)
        [noMediaLayer release];
    if (controllerLayer)
        [controllerLayer release];
    if (browserRootLayer)
        [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
        if (!fullscreenWindow) {
204
            /* this window is kind of useless. however, we need to support 10.5, since enterFullScreenMode depends on the
205
             * existance of a parent window. This is solved in 10.6 and we should remove the window once we require it. */
206
            fullscreenWindow = [[VLCFullscreenWindow alloc] initWithContentRect: NSMakeRect(0., 0., npwindow.width, npwindow.height)];
207
            [fullscreenWindow setLevel: CGShieldingWindowLevel()];
208 209 210 211 212 213
            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];
214
            [fullscreenView setCppPlugin: this];
215 216 217 218 219 220 221 222 223 224 225
        }

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

        [[fullscreenView layer] addSublayer: noMediaLayer];
        [[fullscreenView layer] addSublayer: playbackLayer];
        [[fullscreenView layer] addSublayer: controllerLayer];
        [[fullscreenView layer] setNeedsDisplay];

226
        [[fullscreenWindow contentView] enterFullScreenMode: [NSScreen mainScreen] withOptions: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 0], NSFullScreenModeAllScreens, nil]];
227
    } else {
228
        [[fullscreenWindow contentView] exitFullScreenModeWithOptions: nil];
229 230 231 232 233 234 235
        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

        [browserRootLayer addSublayer: noMediaLayer];
        [browserRootLayer addSublayer: playbackLayer];
        [browserRootLayer addSublayer: controllerLayer];
236
        [fullscreenWindow orderOut: nil];
237
    }
238 239
}

240
void VlcPluginMac::set_fullscreen(int i_value)
241
{
242 243
    if (!get_options().get_enable_fs())
        return;
244
    libvlc_set_fullscreen(getMD(), i_value);
245
    this->update_controls();
246 247 248 249
}

int  VlcPluginMac::get_fullscreen()
{
250
    return libvlc_get_fullscreen(getMD());
251 252
}

253 254
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
255 256
    if (!get_options().get_show_toolbar()) {
        [controllerLayer setHidden: YES];
257
        return;
258
    }
259
    [controllerLayer setHidden: !b_value];
260 261 262 263
}

bool VlcPluginMac::get_toolbar_visible()
{
264
    return controllerLayer.isHidden;
265 266 267 268
}

void VlcPluginMac::update_controls()
{
269 270
    libvlc_state_t currentstate = libvlc_media_player_get_state(getMD());
    if (currentstate == libvlc_Playing || currentstate == libvlc_Paused || currentstate == libvlc_Opening) {
271 272 273 274 275 276 277
        [noMediaLayer setHidden: YES];
        [playbackLayer setHidden: NO];
    } else {
        [noMediaLayer setHidden: NO];
        [playbackLayer setHidden: YES];
    }

278 279 280 281 282 283
    if (controllerLayer) {
        [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
        [controllerLayer setIsPlaying: playlist_isplaying()];
        [controllerLayer setIsFullscreen:this->get_fullscreen()];
        [controllerLayer setNeedsDisplay];
    }
284 285
}

286 287 288 289 290 291 292
bool VlcPluginMac::create_windows()
{
    return true;
}

bool VlcPluginMac::resize_windows()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
293
    return true;
294 295 296 297
}

bool VlcPluginMac::destroy_windows()
{
298
    npwindow.window = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
299
    return true;
300
}
301

302 303
NPError VlcPluginMac::get_root_layer(void *value)
{
304 305
    noMediaLayer = [[VLCNoMediaLayer alloc] init];
    noMediaLayer.opaque = 1.;
306
    [noMediaLayer setCppPlugin: this];
307
    [browserRootLayer addSublayer: noMediaLayer];
308

309
    controllerLayer = [[VLCControllerLayer alloc] init];
310
    [browserRootLayer addSublayer: controllerLayer];
311
    [controllerLayer setCppPlugin: this];
312

313 314 315
    [browserRootLayer setNeedsDisplay];

    *(CALayer **)value = browserRootLayer;
316
    return NPERR_NO_ERROR;
317 318
}

319 320
bool VlcPluginMac::handle_event(void *event)
{
321
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
322

323 324 325 326 327 328 329
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
330
        {
331 332 333 334
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)])
                    [playbackLayer mouseButtonDown:cocoaEvent->data.mouse.buttonNumber];
            }
335 336 337
            if (cocoaEvent->data.mouse.clickCount >= 2)
                VlcPluginMac::toggle_fullscreen();

338 339 340
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);
341 342
            if (controllerLayer)
                [controllerLayer handleMouseDown:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
343

344 345
            return true;
        }
346
        case NPCocoaEventMouseUp:
347
        {
348 349 350 351
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)])
                    [playbackLayer mouseButtonUp:cocoaEvent->data.mouse.buttonNumber];
            }
352 353 354 355
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

356 357
            if (controllerLayer)
                [controllerLayer handleMouseUp:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
358 359 360

            return true;
        }
361 362 363 364 365 366 367
        case NPCocoaEventMouseMoved:
        {
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseMovedToX:Y:)])
                    [playbackLayer mouseMovedToX:cocoaEvent->data.mouse.pluginX Y:cocoaEvent->data.mouse.pluginY];
            }
        }
368 369 370 371 372 373
        case NPCocoaEventMouseDragged:
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

374 375
            if (controllerLayer)
                [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
376 377 378

            return true;
        }
379 380 381 382 383 384 385 386 387 388
        case NPCocoaEventMouseEntered:
        {
            set_toolbar_visible(true);
            return true;
        }
        case NPCocoaEventMouseExited:
        {
            set_toolbar_visible(false);
            return true;
        }
389
        case NPCocoaEventKeyDown:
390 391 392 393
        {
            if (cocoaEvent->data.key.keyCode == 53) {
                toggle_fullscreen();
                return true;
394 395 396
            } else if (cocoaEvent->data.key.keyCode == 49) {
                playlist_togglePause();
                return true;
397 398 399
            }
        }
        case NPCocoaEventKeyUp:
400 401
        case NPCocoaEventFocusChanged:
        case NPCocoaEventScrollWheel:
402
            return true;
403 404 405 406 407

        default:
            break;
    }

408
    if (eventType == NPCocoaEventDrawRect) {
409 410 411 412 413 414 415 416
        /* 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. */
417 418 419
        CGContextRef cgContext = cocoaEvent->data.draw.context;
        if (!cgContext) {
            return false;
420
        }
421 422 423 424 425 426 427 428 429 430

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

        CGContextSaveGState(cgContext);

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

431
        // draw black rectancle
432
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
433
        CGContextSetGrayFillColor(cgContext, 0., 1.);
434 435 436 437 438
        CGContextDrawPath(cgContext, kCGPathFill);

        CGContextRestoreGState(cgContext);

        return true;
439
    }
440

441 442
    return VlcPluginBase::handle_event(event);
}
443

444
@implementation VLCBrowserRootLayer
445
@synthesize cppPlugin = _cppPlugin;
446 447 448 449 450 451 452 453 454 455 456

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

    return self;
}

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
- (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();
}

480
- (void)addVoutLayer:(CALayer *)aLayer
481
{
482
    [CATransaction begin];
483
    playbackLayer = (VLCPlaybackLayer *)[aLayer retain];
484 485 486 487 488 489 490 491 492 493 494
    playbackLayer.opaque = 1.;
    playbackLayer.hidden = NO;
    playbackLayer.bounds = noMediaLayer.bounds;
    [self insertSublayer:playbackLayer below:controllerLayer];
    [self setNeedsDisplay];
    [playbackLayer setNeedsDisplay];
    CGRect frame = playbackLayer.bounds;
    frame.origin.x = 0.;
    frame.origin.y = 0.;
    playbackLayer.frame = frame;
    [CATransaction commit];
495 496
}

497
- (void)removeVoutLayer:(CALayer *)aLayer
498
{
499 500 501
    [CATransaction begin];
    [aLayer removeFromSuperlayer];
    [CATransaction commit];
502

503
    if (playbackLayer == aLayer) {
504
        [playbackLayer release];
505 506
        playbackLayer = nil;
    }
507
}
508

509 510 511
- (CGSize)currentOutputSize
{
    return [browserRootLayer visibleRect].size;
512 513 514 515
}

@end

516
@implementation VLCNoMediaLayer
517
@synthesize cppPlugin = _cppPlugin;
518 519 520 521 522

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
523
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
524 525 526 527 528
    }

    return self;
}

529
- (void)drawInContext:(CGContextRef)cgContext
530 531 532 533 534 535
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

536 537 538 539 540
    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.);

541 542 543 544 545 546 547 548 549 550 551 552
    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
553 554 555 556 557 558 559
        CGContextSetGrayStrokeColor(cgContext, .95, 1.);
        CGContextSetTextDrawingMode(cgContext, kCGTextFill);
        CGContextSetGrayFillColor(cgContext, 1., 1.);
        CFStringRef keys[2];
        keys[0] = kCTFontAttributeName;
        keys[1] = kCTForegroundColorFromContextAttributeName;
        CFTypeRef values[2];
560
        values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
561 562 563 564 565
        values[1] = kCFBooleanTrue;
        CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                        (const void **)&keys,
                                                        (const void **)&values,
                                                        2, NULL, NULL);
566
        CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("VLC Web Plugin"), stylesDict);
567
        CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
        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.);
592 593 594 595
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);
        CFRelease(stylesDict);
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639

        // 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];
640
            values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
641 642 643 644 645 646 647 648 649 650 651 652 653 654
            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);
        }
655
    }
656
    CGColorRelease(backgroundColor);
657 658 659 660

    CGContextRestoreGState(cgContext);
}

661 662 663 664
@end

@implementation VLCControllerLayer

665 666
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
667
@synthesize isFullscreen = _isFullscreen;
668 669
@synthesize cppPlugin = _cppPlugin;

670 671 672 673 674 675 676 677 678 679 680 681 682
- (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");

683 684 685
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

686
        _knob = createImageNamed(@"Knob");
687
    }
688 689 690 691 692 693 694 695 696 697 698 699 700

    return self;
}

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

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

701 702 703
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

704 705 706 707 708
    CGImageRelease(_knob);

    [super dealloc];
}

709 710 711
#pragma mark -
#pragma mark drawing

712 713
- (CGRect)_playPauseButtonRect
{
714 715 716 717 718 719
    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));
720 721 722 723
}

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

728
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
729
                      self.bounds.size.width - playPauseButtonWidth - fullscreenButtonWidth - 15., CGImageGetHeight(_sliderTrackLeft));
730 731 732 733 734 735
}

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

736
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
737

738
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
739 740 741 742 743 744 745 746 747 748
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
749
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
}

- (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);

774
    // Draw fullscreen button
775 776 777 778 779
    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);
    }
780 781 782 783 784 785 786 787 788
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
789 790
}

791 792 793 794 795 796 797
#pragma mark -
#pragma mark event handling

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

798
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
799 800 801 802 803
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

804
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
805 806 807 808 809 810 811

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
812
        _wasPlayingBeforeMouseDown = self.isPlaying;
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
        _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;

830
        return;
831 832 833
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
834
        self.cppPlugin->playlist_togglePause();
835 836
        return;
    }
837 838 839 840
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
841 842 843 844 845 846 847 848 849 850 851 852
}

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

853
@end
854

855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
@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

884 885
@implementation VLCFullscreenWindow

886 887
@synthesize customContentView = _customContentView;

888 889 890
- (id)initWithContentRect:(NSRect)contentRect
{
    if( self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
891
        _initialFrame = contentRect;
892
        [self setBackgroundColor:[NSColor blackColor]];
893
        [self setAcceptsMouseMovedEvents: YES];
894 895

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
896
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
897
        [[self contentView] addSubview: _customContentView];
898
        [self setInitialFirstResponder:_customContentView];
899 900 901 902
    }
    return self;
}

903 904 905 906 907 908
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

909 910 911 912 913 914 915 916 917 918
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

919 920
@end

921
@implementation VLCFullscreenContentView
922 923 924 925 926 927 928
@synthesize cppPlugin = _cppPlugin;

- (BOOL)acceptsFirstResponder
{
    return YES;
}

929 930 931 932 933
- (BOOL)canBecomeKeyView
{
    return YES;
}

934 935 936 937 938 939 940 941 942 943 944 945
- (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;
946 947 948
            } else if (key == ' ') {
                self.cppPlugin->playlist_togglePause();
                return;
949 950 951 952 953
            }
        }
    }
    [super keyDown: theEvent];
}
954

955 956
- (void)mouseDown:(NSEvent *)theEvent
{
957 958 959
    NSEventType eventType = [theEvent type];

    if (eventType == NSLeftMouseDown && !([theEvent modifierFlags] & NSControlKeyMask)) {
960
        if ([theEvent clickCount] >= 2)
961
            self.cppPlugin->toggle_fullscreen();
962 963 964
        else {
            NSPoint point = [NSEvent mouseLocation];

965
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
966
        }
967
    }
968 969 970 971 972 973 974 975 976 977
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)]) {
            if (eventType == NSLeftMouseDown)
                [playbackLayer mouseButtonDown:0];
            else if (eventType == NSRightMouseDown)
                [playbackLayer mouseButtonDown:1];
            else
                [playbackLayer mouseButtonDown:2];
        }
    }
978 979 980 981

    [super mouseDown: theEvent];
}

982 983 984
- (void)mouseUp:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];
985
    NSEventType eventType = [theEvent type];
986

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

989 990 991 992 993 994 995 996 997 998 999
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)]) {
            if (eventType == NSLeftMouseUp)
                [playbackLayer mouseButtonUp:0];
            else if (eventType == NSRightMouseUp)
                [playbackLayer mouseButtonUp:1];
            else
                [playbackLayer mouseButtonUp:2];
        }
    }

1000 1001 1002 1003 1004 1005 1006
    [super mouseUp: theEvent];
}

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

1007
    [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1008 1009 1010 1011 1012 1013 1014

    [super mouseDragged: theEvent];
}

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

1018 1019 1020 1021 1022 1023 1024
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseMovedToX:Y:)]) {
            NSPoint ml = [theEvent locationInWindow];
            [playbackLayer mouseMovedToX:ml.x Y:([self.window frame].size.height - ml.y)];
        }
    }

1025 1026 1027
    [super mouseMoved: theEvent];
}

1028 1029 1030 1031 1032 1033 1034 1035
- (void)hideToolbar
{
    if ([NSDate timeIntervalSinceReferenceDate] - _timeSinceLastMouseMove >= 4) {
        self.cppPlugin->set_toolbar_visible(false);
        [NSCursor setHiddenUntilMouseMoves:YES];
    }
}

1036 1037
@end