vlcplugin_mac.mm 32.4 KB
Newer Older
Cheng Sun's avatar
Cheng Sun committed
1 2 3
/*****************************************************************************
 * vlcplugin_mac.cpp: a VLC plugin for Mozilla (Mac interface)
 *****************************************************************************
4
 * Copyright (C) 2011-2014 VLC Authors and VideoLAN
Cheng Sun's avatar
Cheng Sun committed
5 6
 * $Id$
 *
7
 * Authors: Felix Paul Kühne <fkuehne # videolan # org>
Cheng Sun's avatar
Cheng Sun committed
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
 *
Cheng Sun's avatar
Cheng Sun committed
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"

James Bates's avatar
James Bates committed
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
    [fullscreenWindow release];
178 179
    [playbackLayer release];
    [noMediaLayer release];
180
    [controllerLayer release];
181
    [browserRootLayer release];
182 183 184 185
}

void VlcPluginMac::set_player_window()
{
186 187
    /* pass base layer to libvlc to pass it on to the vout */
    libvlc_media_player_set_nsobject(getMD(), browserRootLayer);
188 189 190 191
}

void VlcPluginMac::toggle_fullscreen()
{
192 193
    if (!get_options().get_enable_fs())
        return;
194
    libvlc_toggle_fullscreen(getMD());
195
    this->update_controls();
196

197
    if (get_fullscreen() != 0) {
198
        if (!fullscreenWindow) {
199
            /* this window is kind of useless. however, we need to support 10.5, since enterFullScreenMode depends on the
200
             * existance of a parent window. This is solved in 10.6 and we should remove the window once we require it. */
201
            fullscreenWindow = [[VLCFullscreenWindow alloc] initWithContentRect: NSMakeRect(0., 0., npwindow.width, npwindow.height)];
202
            [fullscreenWindow setLevel: CGShieldingWindowLevel()];
203 204 205 206 207 208
            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];
209
            [fullscreenView setCppPlugin: this];
210 211 212 213 214 215 216 217 218 219 220
        }

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

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

221
        [[fullscreenWindow contentView] enterFullScreenMode: [NSScreen mainScreen] withOptions: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 0], NSFullScreenModeAllScreens, nil]];
222
    } else {
223
        [[fullscreenWindow contentView] exitFullScreenModeWithOptions: nil];
224 225 226 227 228 229 230
        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

        [browserRootLayer addSublayer: noMediaLayer];
        [browserRootLayer addSublayer: playbackLayer];
        [browserRootLayer addSublayer: controllerLayer];
231
        [fullscreenWindow orderOut: nil];
232
    }
233 234
}

235
void VlcPluginMac::set_fullscreen(int i_value)
236
{
237 238
    if (!get_options().get_enable_fs())
        return;
239
    libvlc_set_fullscreen(getMD(), i_value);
240
    this->update_controls();
241 242 243 244
}

int  VlcPluginMac::get_fullscreen()
{
245
    return libvlc_get_fullscreen(getMD());
246 247
}

248 249
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
250 251
    if (!get_options().get_show_toolbar()) {
        [controllerLayer setHidden: YES];
252
        return;
253
    }
254
    [controllerLayer setHidden: !b_value];
255 256 257 258
}

bool VlcPluginMac::get_toolbar_visible()
{
259
    return controllerLayer.isHidden;
260 261 262 263
}

void VlcPluginMac::update_controls()
{
264 265
    libvlc_state_t currentstate = libvlc_media_player_get_state(getMD());
    if (currentstate == libvlc_Playing || currentstate == libvlc_Paused || currentstate == libvlc_Opening) {
266 267 268 269 270 271 272
        [noMediaLayer setHidden: YES];
        [playbackLayer setHidden: NO];
    } else {
        [noMediaLayer setHidden: NO];
        [playbackLayer setHidden: YES];
    }

273 274 275 276 277 278
    if (controllerLayer) {
        [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
        [controllerLayer setIsPlaying: playlist_isplaying()];
        [controllerLayer setIsFullscreen:this->get_fullscreen()];
        [controllerLayer setNeedsDisplay];
    }
279 280
}

281 282 283 284 285 286 287
bool VlcPluginMac::create_windows()
{
    return true;
}

bool VlcPluginMac::resize_windows()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
288
    return true;
289 290 291 292
}

bool VlcPluginMac::destroy_windows()
{
James Bates's avatar
James Bates committed
293
    npwindow.window = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
294
    return true;
295
}
296

297 298
NPError VlcPluginMac::get_root_layer(void *value)
{
299 300
    noMediaLayer = [[VLCNoMediaLayer alloc] init];
    noMediaLayer.opaque = 1.;
301
    [noMediaLayer setCppPlugin: this];
302
    [browserRootLayer addSublayer: noMediaLayer];
303

304
    controllerLayer = [[VLCControllerLayer alloc] init];
305
    [browserRootLayer addSublayer: controllerLayer];
306
    [controllerLayer setCppPlugin: this];
307

308 309 310
    [browserRootLayer setNeedsDisplay];

    *(CALayer **)value = browserRootLayer;
311
    return NPERR_NO_ERROR;
312 313
}

314 315
bool VlcPluginMac::handle_event(void *event)
{
316
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
317

318 319 320 321 322 323 324
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
325
        {
326 327 328 329
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)])
                    [playbackLayer mouseButtonDown:cocoaEvent->data.mouse.buttonNumber];
            }
330 331 332
            if (cocoaEvent->data.mouse.clickCount >= 2)
                VlcPluginMac::toggle_fullscreen();

333 334 335
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);
336
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
337

338 339
            return true;
        }
340
        case NPCocoaEventMouseUp:
341
        {
342 343 344 345
            if (playbackLayer) {
                if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)])
                    [playbackLayer mouseButtonUp:cocoaEvent->data.mouse.buttonNumber];
            }
346 347 348 349
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

350
            [controllerLayer handleMouseUp:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
351 352 353

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

367
            [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
368 369 370

            return true;
        }
371 372 373 374 375 376 377 378 379 380
        case NPCocoaEventMouseEntered:
        {
            set_toolbar_visible(true);
            return true;
        }
        case NPCocoaEventMouseExited:
        {
            set_toolbar_visible(false);
            return true;
        }
381
        case NPCocoaEventKeyDown:
382 383 384 385
        {
            if (cocoaEvent->data.key.keyCode == 53) {
                toggle_fullscreen();
                return true;
386 387 388
            } else if (cocoaEvent->data.key.keyCode == 49) {
                playlist_togglePause();
                return true;
389 390 391
            }
        }
        case NPCocoaEventKeyUp:
392 393
        case NPCocoaEventFocusChanged:
        case NPCocoaEventScrollWheel:
394
            return true;
395 396 397 398 399

        default:
            break;
    }

400
    if (eventType == NPCocoaEventDrawRect) {
401 402 403 404 405 406 407 408
        /* 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. */
409 410 411
        CGContextRef cgContext = cocoaEvent->data.draw.context;
        if (!cgContext) {
            return false;
412
        }
413 414 415 416 417 418 419 420 421 422

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

        CGContextSaveGState(cgContext);

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

423
        // draw black rectancle
424
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
425
        CGContextSetGrayFillColor(cgContext, 0., 1.);
426 427 428 429 430
        CGContextDrawPath(cgContext, kCGPathFill);

        CGContextRestoreGState(cgContext);

        return true;
431
    }
432

433 434
    return VlcPluginBase::handle_event(event);
}
435

436
@implementation VLCBrowserRootLayer
437
@synthesize cppPlugin = _cppPlugin;
438 439 440 441 442 443 444 445 446 447 448

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

    return self;
}

449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
- (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();
}

472
- (void)addVoutLayer:(CALayer *)aLayer
473
{
474
    [CATransaction begin];
475
    playbackLayer = (VLCPlaybackLayer *)[aLayer retain];
476 477 478 479 480 481 482 483 484 485 486
    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];
487 488
}

489
- (void)removeVoutLayer:(CALayer *)aLayer
490
{
491 492 493
    [CATransaction begin];
    [aLayer removeFromSuperlayer];
    [CATransaction commit];
494

495
    if (playbackLayer == aLayer) {
496
        [playbackLayer release];
497 498
        playbackLayer = nil;
    }
499
}
500

501 502 503
- (CGSize)currentOutputSize
{
    return [browserRootLayer visibleRect].size;
504 505 506 507
}

@end

508
@implementation VLCNoMediaLayer
509
@synthesize cppPlugin = _cppPlugin;
510 511 512 513 514

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
515
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
516 517 518 519 520
    }

    return self;
}

521
- (void)drawInContext:(CGContextRef)cgContext
522 523 524 525 526 527
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

528 529 530 531 532
    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.);

533 534 535 536 537 538 539 540 541 542 543 544
    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
545 546 547 548 549 550 551 552 553 554 555 556 557
        CGContextSetGrayStrokeColor(cgContext, .95, 1.);
        CGContextSetTextDrawingMode(cgContext, kCGTextFill);
        CGContextSetGrayFillColor(cgContext, 1., 1.);
        CFStringRef keys[2];
        keys[0] = kCTFontAttributeName;
        keys[1] = kCTForegroundColorFromContextAttributeName;
        CFTypeRef values[2];
        values[0] = CTFontCreateWithName(CFSTR("Helvetica Neue Light"),18,NULL);
        values[1] = kCFBooleanTrue;
        CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                        (const void **)&keys,
                                                        (const void **)&values,
                                                        2, NULL, NULL);
558
        CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("VLC Web Plugin"), stylesDict);
559
        CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
        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.);
584 585 586 587
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);
        CFRelease(stylesDict);
588 589 590 591 592 593 594 595 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 640 641 642 643 644 645 646

        // 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];
            values[0] = CTFontCreateWithName(CFSTR("Helvetica Neue Light"),18,NULL);
            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);
        }
647
    }
648
    CGColorRelease(backgroundColor);
649 650 651 652

    CGContextRestoreGState(cgContext);
}

653 654 655 656
@end

@implementation VLCControllerLayer

657 658
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
659
@synthesize isFullscreen = _isFullscreen;
660 661
@synthesize cppPlugin = _cppPlugin;

662 663 664 665 666 667 668 669 670 671 672 673 674
- (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");

675 676 677
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

678
        _knob = createImageNamed(@"Knob");
679
    }
680 681 682 683 684 685 686 687 688 689 690 691 692

    return self;
}

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

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

693 694 695
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

696 697 698 699 700
    CGImageRelease(_knob);

    [super dealloc];
}

701 702 703
#pragma mark -
#pragma mark drawing

704 705
- (CGRect)_playPauseButtonRect
{
706 707 708 709 710 711
    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));
712 713 714 715
}

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

720
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
721
                      self.bounds.size.width - playPauseButtonWidth - fullscreenButtonWidth - 15., CGImageGetHeight(_sliderTrackLeft));
722 723 724 725 726 727
}

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

728
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
729

730
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
731 732 733 734 735 736 737 738 739 740
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
741
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
}

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

766
    // Draw fullscreen button
767 768 769 770 771
    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);
    }
772 773 774 775 776 777 778 779 780
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
781 782
}

783 784 785 786 787 788 789
#pragma mark -
#pragma mark event handling

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

790
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
791 792 793 794 795
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

796
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
797 798 799 800 801 802 803

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
804
        _wasPlayingBeforeMouseDown = self.isPlaying;
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
        _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;

822
        return;
823 824 825
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
826
        self.cppPlugin->playlist_togglePause();
827 828
        return;
    }
829 830 831 832
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
833 834 835 836 837 838 839 840 841 842 843 844
}

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

845
@end
846

847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
@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

876 877
@implementation VLCFullscreenWindow

878 879
@synthesize customContentView = _customContentView;

880 881 882
- (id)initWithContentRect:(NSRect)contentRect
{
    if( self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
883
        _initialFrame = contentRect;
884
        [self setBackgroundColor:[NSColor blackColor]];
885
        [self setAcceptsMouseMovedEvents: YES];
886 887

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
888
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
889
        [[self contentView] addSubview: _customContentView];
890
        [self setInitialFirstResponder:_customContentView];
891 892 893 894
    }
    return self;
}

895 896 897 898 899 900
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

901 902 903 904 905 906 907 908 909 910
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

911 912
@end

913
@implementation VLCFullscreenContentView
914 915 916 917 918 919 920
@synthesize cppPlugin = _cppPlugin;

- (BOOL)acceptsFirstResponder
{
    return YES;
}

921 922 923 924 925
- (BOOL)canBecomeKeyView
{
    return YES;
}

926 927 928 929 930 931 932 933 934 935 936 937
- (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;
938 939 940
            } else if (key == ' ') {
                self.cppPlugin->playlist_togglePause();
                return;
941 942 943 944 945
            }
        }
    }
    [super keyDown: theEvent];
}
946

947 948
- (void)mouseDown:(NSEvent *)theEvent
{
949 950 951
    NSEventType eventType = [theEvent type];

    if (eventType == NSLeftMouseDown && !([theEvent modifierFlags] & NSControlKeyMask)) {
952
        if ([theEvent clickCount] >= 2)
953
            self.cppPlugin->toggle_fullscreen();
954 955 956
        else {
            NSPoint point = [NSEvent mouseLocation];

957
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
958
        }
959
    }
960 961 962 963 964 965 966 967 968 969
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonDown:)]) {
            if (eventType == NSLeftMouseDown)
                [playbackLayer mouseButtonDown:0];
            else if (eventType == NSRightMouseDown)
                [playbackLayer mouseButtonDown:1];
            else
                [playbackLayer mouseButtonDown:2];
        }
    }
970 971 972 973

    [super mouseDown: theEvent];
}

974 975 976
- (void)mouseUp:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];
977
    NSEventType eventType = [theEvent type];
978

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

981 982 983 984 985 986 987 988 989 990 991
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseButtonUp:)]) {
            if (eventType == NSLeftMouseUp)
                [playbackLayer mouseButtonUp:0];
            else if (eventType == NSRightMouseUp)
                [playbackLayer mouseButtonUp:1];
            else
                [playbackLayer mouseButtonUp:2];
        }
    }

992 993 994 995 996 997 998
    [super mouseUp: theEvent];
}

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

999
    [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1000 1001 1002 1003 1004 1005 1006

    [super mouseDragged: theEvent];
}

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

1010 1011 1012 1013 1014 1015 1016
    if (playbackLayer) {
        if ([playbackLayer respondsToSelector:@selector(mouseMovedToX:Y:)]) {
            NSPoint ml = [theEvent locationInWindow];
            [playbackLayer mouseMovedToX:ml.x Y:([self.window frame].size.height - ml.y)];
        }
    }

1017 1018 1019
    [super mouseMoved: theEvent];
}

1020 1021 1022 1023 1024 1025 1026 1027
- (void)hideToolbar
{
    if ([NSDate timeIntervalSinceReferenceDate] - _timeSinceLastMouseMove >= 4) {
        self.cppPlugin->set_toolbar_visible(false);
        [NSCursor setHiddenUntilMouseMoves:YES];
    }
}

1028 1029
@end