vlcplugin_mac.mm 28.7 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-2013 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 29
 *
 * 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.
 *****************************************************************************/

30 31
#include "vlcplugin_mac.h"

James Bates's avatar
James Bates committed
32 33
#include <npapi.h>

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

37
@interface VLCNoMediaLayer : CALayer
38 39 40

@end

41
@interface VLCPlaybackLayer : CALayer {
42
    CGColorSpaceRef _colorspace;
43 44 45 46 47 48
    VlcPluginMac *_cppPlugin;
}
@property (readwrite) VlcPluginMac * cppPlugin;

@end

49
@interface VLCControllerLayer : CALayer {
50 51 52 53 54 55 56
    CGImageRef _playImage;
    CGImageRef _pauseImage;

    CGImageRef _sliderTrackLeft;
    CGImageRef _sliderTrackRight;
    CGImageRef _sliderTrackCenter;

57 58 59
    CGImageRef _enterFullscreen;
    CGImageRef _leaveFullscreen;

60
    CGImageRef _knob;
61 62 63 64

    BOOL _wasPlayingBeforeMouseDown;
    BOOL _isScrubbing;
    CGFloat _mouseDownXDelta;
65 66 67

    double _position;
    BOOL _isPlaying;
68
    BOOL _isFullscreen;
69 70

    VlcPluginMac *_cppPlugin;
71
}
72 73
@property (readwrite) double mediaPosition;
@property (readwrite) BOOL isPlaying;
74
@property (readwrite) BOOL isFullscreen;
75 76
@property (readwrite) VlcPluginMac * cppPlugin;

77 78 79
- (void)handleMouseDown:(CGPoint)point;
- (void)handleMouseUp:(CGPoint)point;
- (void)handleMouseDragged:(CGPoint)point;
80 81 82

@end

83 84 85 86 87 88
@interface VLCControllerLayer (Internal)
- (CGRect)_playPauseButtonRect;
- (CGRect)_fullscreenButtonRect;
- (CGRect)_sliderRect;
@end

89 90 91 92
@interface VLCFullscreenContentView : NSView {
    VlcPluginMac *_cppPlugin;
}
@property (readwrite) VlcPluginMac * cppPlugin;
93 94 95 96

@end

@interface VLCFullscreenWindow : NSWindow {
97 98
    NSRect _initialFrame;
    VLCFullscreenContentView *_customContentView;
99
}
100
@property (readonly) VLCFullscreenContentView* customContentView;
101 102 103 104 105 106 107 108

- (id)initWithContentRect:(NSRect)contentRect;

- (void)enterFullscreen;
- (void)leaveFullscreen;

@end

109 110 111 112 113 114
@interface NSScreen (VLCAdditions)
- (BOOL)hasMenuBar;
- (BOOL)hasDock;
- (CGDirectDisplayID)displayID;
@end

115
static CALayer * browserRootLayer;
116
static VLCPlaybackLayer * playbackLayer;
117
static VLCNoMediaLayer * noMediaLayer;
118
static VLCControllerLayer * controllerLayer;
119
static VLCFullscreenWindow * fullscreenWindow;
120
static VLCFullscreenContentView * fullscreenView;
121

122 123 124
VlcPluginMac::VlcPluginMac(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
125
    browserRootLayer = [[CALayer alloc] init];
126 127 128 129
}

VlcPluginMac::~VlcPluginMac()
{
130
    [fullscreenWindow release];
131 132
    [playbackLayer release];
    [noMediaLayer release];
133
    [controllerLayer release];
134
    [browserRootLayer release];
135 136 137 138
}

void VlcPluginMac::set_player_window()
{
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    libvlc_video_set_format_callbacks(getMD(),
                                      video_format_proxy,
                                      video_cleanup_proxy);
    libvlc_video_set_callbacks(getMD(),
                               video_lock_proxy,
                               video_unlock_proxy,
                               video_display_proxy,
                               this);
}

unsigned VlcPluginMac::video_format_cb(char *chroma,
                                       unsigned *width, unsigned *height,
                                       unsigned *pitches, unsigned *lines)
{
    if ( p_browser ) {
154 155
        /* request video in fullscreen size. scaling will be done by CA */
        NSSize screenSize = [[NSScreen mainScreen] visibleFrame].size;
156
        float src_aspect = (float)(*width) / (*height);
157
        float dst_aspect = (float)screenSize.width/screenSize.height;
158
        if ( src_aspect > dst_aspect ) {
159 160
            if( screenSize.width != (*width) ) { //don't scale if size equal
                (*width) = screenSize.width;
161 162
                (*height) = static_cast<unsigned>( (*width) / src_aspect + 0.5);
            }
163 164 165
        } else {
            if( screenSize.height != (*height) ) { //don't scale if size equal
                (*height) = screenSize.height;
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
                (*width) = static_cast<unsigned>( (*height) * src_aspect + 0.5);
            }
        }
    }

    m_media_width = (*width);
    m_media_height = (*height);

    memcpy(chroma, "RGBA", sizeof("RGBA")-1);
    (*pitches) = m_media_width * 4;
    (*lines) = m_media_height;

    //+1 for vlc 2.0.3/2.1 bug workaround.
    //They writes after buffer end boundary by some reason unknown to me...
    m_frame_buf.resize( (*pitches) * ((*lines)+1) );

    return 1;
}

void VlcPluginMac::video_cleanup_cb()
{
    m_frame_buf.resize(0);
    m_media_width = 0;
    m_media_height = 0;
190
    [fullscreenWindow orderOut: nil];
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
}

void* VlcPluginMac::video_lock_cb(void **planes)
{
    (*planes) = m_frame_buf.empty()? 0 : &m_frame_buf[0];
    return 0;
}

void VlcPluginMac::video_unlock_cb(void* /*picture*/, void *const * /*planes*/)
{
}

void VlcPluginMac::video_display_cb(void * /*picture*/)
{
    [playbackLayer performSelectorOnMainThread:@selector(setNeedsDisplay) withObject: nil waitUntilDone:NO];
206 207 208 209
}

void VlcPluginMac::toggle_fullscreen()
{
210 211
    if (!get_options().get_enable_fs())
        return;
212
    libvlc_toggle_fullscreen(getMD());
213
    this->update_controls();
214 215 216 217 218 219 220 221 222 223 224

    if (get_fullscreen() == 0) {
        if (!fullscreenWindow) {
            fullscreenWindow = [[VLCFullscreenWindow alloc] initWithContentRect: NSMakeRect(npwindow.x, npwindow.y, npwindow.width, npwindow.height)];
            [fullscreenWindow setLevel: kCGFloatingWindowLevel];
            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];
225
            [fullscreenView setCppPlugin: this];
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        }

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

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

        [[fullscreenView layer] setNeedsDisplay];

        [fullscreenWindow makeKeyAndOrderFront:nil];
        [fullscreenWindow enterFullscreen];
    } else {
        [fullscreenWindow leaveFullscreen];
        [fullscreenWindow orderOut: nil];
        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

        [browserRootLayer addSublayer: noMediaLayer];
        [browserRootLayer addSublayer: playbackLayer];
        [browserRootLayer addSublayer: controllerLayer];
    }
251 252
}

253
void VlcPluginMac::set_fullscreen(int i_value)
254
{
255 256
    if (!get_options().get_enable_fs())
        return;
257
    libvlc_set_fullscreen(getMD(), i_value);
258
    this->update_controls();
259 260 261 262
}

int  VlcPluginMac::get_fullscreen()
{
263
    return libvlc_get_fullscreen(getMD());
264 265
}

266 267
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
268
    [controllerLayer setHidden: !b_value];
269 270 271 272 273 274 275 276 277
}

bool VlcPluginMac::get_toolbar_visible()
{
    return (bool)controllerLayer.opaque;
}

void VlcPluginMac::update_controls()
{
278 279
    [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
    [controllerLayer setIsPlaying: playlist_isplaying()];
280
    [controllerLayer setIsFullscreen:this->get_fullscreen()];
281

282 283 284 285 286 287 288 289
    if (player_has_vout()) {
        [noMediaLayer setHidden: YES];
        [playbackLayer setHidden: NO];
    } else {
        [noMediaLayer setHidden: NO];
        [playbackLayer setHidden: YES];
    }

290 291 292
    [controllerLayer setNeedsDisplay];
}

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

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

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

309 310
NPError VlcPluginMac::get_root_layer(void *value)
{
311 312
    noMediaLayer = [[VLCNoMediaLayer alloc] init];
    noMediaLayer.opaque = 1.;
313
    [browserRootLayer addSublayer: noMediaLayer];
314

315 316
    playbackLayer = [[VLCPlaybackLayer alloc] init];
    playbackLayer.opaque = 1.;
317
    [browserRootLayer addSublayer: playbackLayer];
318 319 320
    [playbackLayer setCppPlugin: this];
    [playbackLayer setHidden: YES];

321 322
    controllerLayer = [[VLCControllerLayer alloc] init];
    controllerLayer.opaque = 1.;
323
    [browserRootLayer addSublayer: controllerLayer];
324
    [controllerLayer setCppPlugin: this];
325

326 327 328
    [browserRootLayer setNeedsDisplay];

    *(CALayer **)value = browserRootLayer;
329
    return NPERR_NO_ERROR;
330 331
}

332 333
bool VlcPluginMac::handle_event(void *event)
{
334
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
335

336 337 338 339 340 341 342
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
343
        {
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
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
351

352 353
            return true;
        }
354
        case NPCocoaEventMouseUp:
355 356 357 358 359
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

360
            [controllerLayer handleMouseUp:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
361 362 363 364 365 366 367 368 369

            return true;
        }
        case NPCocoaEventMouseDragged:
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

370
            [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
371 372 373

            return true;
        }
374 375 376 377 378 379 380 381 382 383
        case NPCocoaEventMouseEntered:
        {
            set_toolbar_visible(true);
            return true;
        }
        case NPCocoaEventMouseExited:
        {
            set_toolbar_visible(false);
            return true;
        }
384
        case NPCocoaEventKeyDown:
385 386 387 388 389 390 391
        {
            if (cocoaEvent->data.key.keyCode == 53) {
                toggle_fullscreen();
                return true;
            }
        }
        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 437 438 439 440 441 442 443
@implementation VLCPlaybackLayer
@synthesize cppPlugin = _cppPlugin;

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

        _colorspace = CGColorSpaceCreateDeviceRGB();
446 447 448 449 450
    }

    return self;
}

451 452 453 454 455 456
- (void)dealloc
{
    CGColorSpaceRelease(_colorspace);
    [super dealloc];
}

457 458 459 460 461 462 463 464
- (void)drawInContext:(CGContextRef)cgContext
{
    if (!cgContext)
        return;

    if (![self cppPlugin]->playlist_isplaying() || ![self cppPlugin]->player_has_vout())
        return;

465 466
    float media_width = (float)[self cppPlugin]->m_media_width;
    float media_height = (float)[self cppPlugin]->m_media_height;
467

468
    if (media_width == 0. || media_height == 0.)
469 470
        return;

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    NSRect layerRect = self.bounds;
    float display_width = 0.;
    float display_height = 0.;

    float src_aspect = (float)media_width / media_height;
    float dst_aspect = (float)layerRect.size.width/layerRect.size.height;
    if ( src_aspect > dst_aspect ) {
        if( layerRect.size.width != media_width ) { //don't scale if size equal
            display_width = layerRect.size.width;
            display_height = display_width / src_aspect; // + 0.5);
        } else {
            display_width = media_width;
            display_height = media_height;
        }
    } else {
        if( layerRect.size.height != media_height ) { //don't scale if size equal
            display_height = layerRect.size.height;
            display_width = display_height * src_aspect; // + 0.5);
        } else {
            display_width = media_width;
            display_height = media_height;
        }
    }
494 495

    /* Compute the position of the video */
496 497 498 499
    float left = (layerRect.size.width  - display_width)  / 2.;
    float top  = (layerRect.size.height - display_height) / 2.;

    CGContextSaveGState(cgContext);
500

501 502 503 504 505 506 507 508 509
    static const size_t kComponentsPerPixel = 4;
    static const size_t kBitsPerComponent = sizeof(unsigned char) * 8;

    /* render frame */
    CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                                                    (const uint8_t *)&[self cppPlugin]->m_frame_buf[0],
                                                    sizeof([self cppPlugin]->m_frame_buf[0]),
                                                    kCFAllocatorNull);
    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);
510

511 512 513 514 515
    CGImageRef image = CGImageCreate(media_width,
                                     media_height,
                                     kBitsPerComponent,
                                     kBitsPerComponent * kComponentsPerPixel,
                                     kComponentsPerPixel * media_width,
516
                                     _colorspace,
517 518 519 520 521 522 523 524 525 526 527
                                     kCGBitmapByteOrder16Big,
                                     dataProvider,
                                     NULL,
                                     true,
                                     kCGRenderingIntentPerceptual);
    if (!image) {
        CGImageRelease(image);
        CGDataProviderRelease(dataProvider);
        CGContextRestoreGState(cgContext);
        return;
    }
528
    CGRect rect = CGRectMake(left, top, display_width, display_height);
529 530 531 532 533 534 535 536 537 538
    CGContextDrawImage(cgContext, rect, image);

    CGImageRelease(image);
    CGDataProviderRelease(dataProvider);

    CGContextRestoreGState(cgContext);
}

@end

539
@implementation VLCNoMediaLayer
540 541 542 543 544

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
545
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
546 547 548 549 550
    }

    return self;
}

551
- (void)drawInContext:(CGContextRef)cgContext
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 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
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

    // draw a gray background
    CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
    CGContextSetGrayFillColor(cgContext, .5, 1.);
    CGContextDrawPath(cgContext, kCGPathFill);

    // draw info text
    CGContextSetGrayStrokeColor(cgContext, .7, 1.);
    CGContextSetTextDrawingMode(cgContext, kCGTextFillStroke);
    CGContextSetGrayFillColor(cgContext, 1., 1.);
    CFStringRef keys[2];
    keys[0] = kCTFontAttributeName;
    keys[1] = kCTForegroundColorFromContextAttributeName;
    CFTypeRef values[2];
    values[0] = CTFontCreateWithName(CFSTR("Helvetica"),18,NULL);
    values[1] = kCFBooleanTrue;
    CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                    (const void **)&keys,
                                                    (const void **)&values,
                                                    2, NULL, NULL);
    CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("VLC Multimedia Plug-in"), stylesDict);
    CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
    CGRect textRect = CTLineGetImageBounds(textLine, cgContext);
    CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2), ((windowHeight - textRect.size.height) / 2));
    CTLineDraw(textLine, cgContext);
    CFRelease(textLine);
    CFRelease(attRef);

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

    // draw version string
    attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, libvlc_get_version(), kCFStringEncodingUTF8), stylesDict);
    textLine = CTLineCreateWithAttributedString(attRef);
    textRect = CTLineGetImageBounds(textLine, cgContext);
    CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2), ((windowHeight - textRect.size.height) / 2) - 25.);
    CTLineDraw(textLine, cgContext);
    CFRelease(textLine);
    CFRelease(attRef);

    // expose drawing model
    attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("windowed output mode using CoreAnimation"), stylesDict);
    textLine = CTLineCreateWithAttributedString(attRef);
    textRect = CTLineGetImageBounds(textLine, cgContext);
    CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2), ((windowHeight - textRect.size.height) / 2) - 45.);
    CTLineDraw(textLine, cgContext);
    CFRelease(textLine);
    CFRelease(attRef);
    CFRelease(stylesDict);

    CGContextRestoreGState(cgContext);
}

616 617 618 619
@end

@implementation VLCControllerLayer

620 621
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
622
@synthesize isFullscreen = _isFullscreen;
623 624
@synthesize cppPlugin = _cppPlugin;

625
static CGImageRef createImageNamed(NSString *name)
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
    CFURLRef url = CFBundleCopyResourceURL(CFBundleGetBundleWithIdentifier(CFSTR("com.netscape.vlc")), (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;
}

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

655 656 657
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

658
        _knob = createImageNamed(@"Knob");
659
    }
660 661 662 663 664 665 666 667 668 669 670 671 672

    return self;
}

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

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

673 674 675
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

676 677 678 679 680
    CGImageRelease(_knob);

    [super dealloc];
}

681 682 683
#pragma mark -
#pragma mark drawing

684 685
- (CGRect)_playPauseButtonRect
{
686 687 688 689 690 691
    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));
692 693 694 695
}

- (CGRect)_sliderRect
{
696
    CGFloat sliderYPosition = (self.bounds.size.height - CGImageGetHeight(_sliderTrackLeft)) / 2.;
697 698
    CGFloat playPauseButtonWidth = [self _playPauseButtonRect].size.width;

699
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
700
                      self.bounds.size.width - playPauseButtonWidth - 15 - CGImageGetWidth(_enterFullscreen), CGImageGetHeight(_sliderTrackLeft));
701 702 703 704 705 706
}

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

707
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
708

709
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
710 711 712 713 714 715 716 717 718 719
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
720
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
}

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

745 746 747 748
    // Draw fullscreen button
    CGRect fullscreenButtonRect = [self _fullscreenButtonRect];
    fullscreenButtonRect.origin.x = CGRectGetMaxX(sliderRightTrackRect) + 5;
    CGContextDrawImage(context, fullscreenButtonRect, self.isFullscreen ? _leaveFullscreen : _enterFullscreen);
749 750 751 752 753 754 755 756 757 758 759
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
760 761
}

762 763 764 765 766 767 768
#pragma mark -
#pragma mark event handling

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

769
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
770 771 772 773 774
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

775
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
776 777 778 779 780 781 782

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
783
        _wasPlayingBeforeMouseDown = self.isPlaying;
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
        _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;

801
        return;
802 803 804
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
805
        self.cppPlugin->playlist_togglePause();
806 807
        return;
    }
808 809 810 811
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
812 813 814 815 816 817 818 819 820 821 822 823
}

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

824
@end
825

826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
@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

855 856
@implementation VLCFullscreenWindow

857 858
@synthesize customContentView = _customContentView;

859 860 861
- (id)initWithContentRect:(NSRect)contentRect
{
    if( self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
862
        _initialFrame = contentRect;
863 864 865
        [self setBackgroundColor:[NSColor blackColor]];
        [self setHasShadow:YES];
        [self setMovableByWindowBackground: YES];
866
        [self setAcceptsMouseMovedEvents: YES];
867
        [self center];
868 869

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
870
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
871
        [[self contentView] addSubview: _customContentView];
872
        [self setInitialFirstResponder:_customContentView];
873 874 875 876
    }
    return self;
}

877 878 879 880 881 882
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

883 884 885 886
- (void)enterFullscreen
{
    NSScreen *screen = [self screen];

887
    _initialFrame = [self frame];
888 889 890 891 892 893 894 895 896 897 898 899 900
    [self setFrame:[[self screen] frame] display:YES animate:YES];

    NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
    if ([screen hasMenuBar])
        presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
    if ([screen hasMenuBar] || [screen hasDock])
        presentationOpts |= NSApplicationPresentationAutoHideDock;
    [NSApp setPresentationOptions:presentationOpts];
}

- (void)leaveFullscreen
{
    [NSApp setPresentationOptions: NSApplicationPresentationDefault];
901
    [self setFrame:_initialFrame display:YES animate:YES];
902 903
}

904 905 906 907 908 909 910 911 912 913
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

914 915
@end

916
@implementation VLCFullscreenContentView
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
@synthesize cppPlugin = _cppPlugin;

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (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;
            }
        }
    }
    [super keyDown: theEvent];
}
941 942 943

@end