vlcplugin_mac.mm 24.5 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 35
#include <QuartzCore/QuartzCore.h>

36 37 38 39 40
@interface VLCNoMediaLayer : CALayer {
}

@end

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

@end

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

    CGImageRef _sliderTrackLeft;
    CGImageRef _sliderTrackRight;
    CGImageRef _sliderTrackCenter;

56 57 58
    CGImageRef _enterFullscreen;
    CGImageRef _leaveFullscreen;

59
    CGImageRef _knob;
60 61 62 63

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

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

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

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

@end

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

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
@interface VLCFullscreenContentView : NSView

@end

@interface VLCFullscreenWindow : NSWindow {
    NSRect initialFrame;
}

- (id)initWithContentRect:(NSRect)contentRect;

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

@end

103 104 105 106 107 108
@interface NSScreen (VLCAdditions)
- (BOOL)hasMenuBar;
- (BOOL)hasDock;
- (CGDirectDisplayID)displayID;
@end

109
static CALayer * rootLayer;
110
static VLCPlaybackLayer * playbackLayer;
111
static VLCNoMediaLayer * noMediaLayer;
112
static VLCControllerLayer * controllerLayer;
113
static VLCFullscreenWindow * fullscreenWindow;
114

115 116 117
VlcPluginMac::VlcPluginMac(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
118 119 120 121 122
    rootLayer = [[CALayer alloc] init];
}

VlcPluginMac::~VlcPluginMac()
{
123 124
    [playbackLayer release];
    [noMediaLayer release];
125 126
    [controllerLayer release];
    [rootLayer release];
127 128 129 130
}

void VlcPluginMac::set_player_window()
{
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    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 ) {
        float src_aspect = (float)(*width) / (*height);
        float dst_aspect = (float)npwindow.width/npwindow.height;
        if ( src_aspect > dst_aspect ) {
            if( npwindow.width != (*width) ) { //don't scale if size equal
                (*width) = npwindow.width;
                (*height) = static_cast<unsigned>( (*width) / src_aspect + 0.5);
            }
        }
        else {
            if( npwindow.height != (*height) ) { //don't scale if size equal
                (*height) = npwindow.height;
                (*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;
}

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];
196 197 198 199
}

void VlcPluginMac::toggle_fullscreen()
{
200 201
    if (!get_options().get_enable_fs())
        return;
202
    if (playlist_isplaying())
James Bates's avatar
James Bates committed
203
        libvlc_toggle_fullscreen(getMD());
204
    this->update_controls();
205 206
}

207
void VlcPluginMac::set_fullscreen(int i_value)
208
{
209 210
    if (!get_options().get_enable_fs())
        return;
211
    if (playlist_isplaying())
212 213
        libvlc_set_fullscreen(getMD(), i_value);
    this->update_controls();
214 215 216 217 218 219
}

int  VlcPluginMac::get_fullscreen()
{
    int r = 0;
    if (playlist_isplaying())
James Bates's avatar
James Bates committed
220
        r = libvlc_get_fullscreen(getMD());
221 222 223
    return r;
}

224 225
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
226
    [controllerLayer setHidden: !b_value];
227 228 229 230 231 232 233 234 235
}

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

void VlcPluginMac::update_controls()
{
236 237
    [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
    [controllerLayer setIsPlaying: playlist_isplaying()];
238
    [controllerLayer setIsFullscreen:this->get_fullscreen()];
239

240 241 242 243 244 245 246 247
    if (player_has_vout()) {
        [noMediaLayer setHidden: YES];
        [playbackLayer setHidden: NO];
    } else {
        [noMediaLayer setHidden: NO];
        [playbackLayer setHidden: YES];
    }

248 249 250
    [controllerLayer setNeedsDisplay];
}

251 252 253 254 255 256 257
bool VlcPluginMac::create_windows()
{
    return true;
}

bool VlcPluginMac::resize_windows()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
258
    return true;
259 260 261 262
}

bool VlcPluginMac::destroy_windows()
{
James Bates's avatar
James Bates committed
263
    npwindow.window = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
264
    return true;
265
}
266

267 268
NPError VlcPluginMac::get_root_layer(void *value)
{
269 270 271 272
    noMediaLayer = [[VLCNoMediaLayer alloc] init];
    noMediaLayer.opaque = 1.;
    [rootLayer addSublayer: noMediaLayer];

273 274 275 276 277 278
    playbackLayer = [[VLCPlaybackLayer alloc] init];
    playbackLayer.opaque = 1.;
    [rootLayer addSublayer: playbackLayer];
    [playbackLayer setCppPlugin: this];
    [playbackLayer setHidden: YES];

279 280 281
    controllerLayer = [[VLCControllerLayer alloc] init];
    controllerLayer.opaque = 1.;
    [rootLayer addSublayer: controllerLayer];
282
    [controllerLayer setCppPlugin: this];
283 284 285

    *(CALayer **)value = rootLayer;
    return NPERR_NO_ERROR;
286 287
}

288 289
bool VlcPluginMac::handle_event(void *event)
{
290
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
291

292 293 294 295 296 297 298
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
299
        {
300 301 302
            if (cocoaEvent->data.mouse.clickCount >= 2)
                VlcPluginMac::toggle_fullscreen();

303 304 305 306 307
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);
            [controllerLayer handleMouseDown:[rootLayer convertPoint:point toLayer:controllerLayer]];

308 309
            return true;
        }
310
        case NPCocoaEventMouseUp:
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

            [controllerLayer handleMouseUp:[rootLayer convertPoint:point toLayer:controllerLayer]];

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

            [controllerLayer handleMouseDragged:[rootLayer convertPoint:point toLayer:controllerLayer]];

            return true;
        }
330 331 332 333 334 335 336 337 338 339
        case NPCocoaEventMouseEntered:
        {
            set_toolbar_visible(true);
            return true;
        }
        case NPCocoaEventMouseExited:
        {
            set_toolbar_visible(false);
            return true;
        }
340 341 342 343
        case NPCocoaEventKeyUp:
        case NPCocoaEventKeyDown:
        case NPCocoaEventFocusChanged:
        case NPCocoaEventScrollWheel:
344
            return true;
345 346 347 348 349

        default:
            break;
    }

350
    if (eventType == NPCocoaEventDrawRect) {
351 352 353 354 355 356 357 358
        /* 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. */
359 360 361
        CGContextRef cgContext = cocoaEvent->data.draw.context;
        if (!cgContext) {
            return false;
362
        }
363 364 365 366 367 368 369 370 371 372

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

        CGContextSaveGState(cgContext);

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

373
        // draw black rectancle
374
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
375
        CGContextSetGrayFillColor(cgContext, 0., 1.);
376 377 378 379 380
        CGContextDrawPath(cgContext, kCGPathFill);

        CGContextRestoreGState(cgContext);

        return true;
381
    }
382

383 384
    return VlcPluginBase::handle_event(event);
}
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
@implementation VLCPlaybackLayer
@synthesize cppPlugin = _cppPlugin;

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

    return self;
}

- (void)drawInContext:(CGContextRef)cgContext
{
    if (!cgContext)
        return;

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

    unsigned int media_width = [self cppPlugin]->m_media_width;
    unsigned int media_height = [self cppPlugin]->m_media_height;

    if (media_width == 0 || media_height == 0)
        return;

    CGContextSaveGState(cgContext);

    /* Compute the position of the video */
    CGSize layerSize = [self preferredFrameSize];
    float left = (layerSize.width  - media_width)  / 2.;
    float top  = (layerSize.height - media_height) / 2.;
    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);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGImageRef image = CGImageCreate(media_width,
                                     media_height,
                                     kBitsPerComponent,
                                     kBitsPerComponent * kComponentsPerPixel,
                                     kComponentsPerPixel * media_width,
                                     colorspace,
                                     kCGBitmapByteOrder16Big,
                                     dataProvider,
                                     NULL,
                                     true,
                                     kCGRenderingIntentPerceptual);
    if (!image) {
        CGColorSpaceRelease(colorspace);
        CGImageRelease(image);
        CGDataProviderRelease(dataProvider);
        CGContextRestoreGState(cgContext);
        return;
    }
    CGRect rect = CGRectMake(left, top, media_width, media_height);
    CGContextDrawImage(cgContext, rect, image);

    CGColorSpaceRelease(colorspace);
    CGImageRelease(image);
    CGDataProviderRelease(dataProvider);

    CGContextRestoreGState(cgContext);
}

@end

459
@implementation VLCNoMediaLayer
460 461 462 463 464

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
465
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
466 467 468 469 470
    }

    return self;
}

471
- (void)drawInContext:(CGContextRef)cgContext
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
{
    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);
}

536 537 538 539
@end

@implementation VLCControllerLayer

540 541
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
542
@synthesize isFullscreen = _isFullscreen;
543 544
@synthesize cppPlugin = _cppPlugin;

545
static CGImageRef createImageNamed(NSString *name)
546
{
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
    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");

575 576 577
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

578
        _knob = createImageNamed(@"Knob");
579
    }
580 581 582 583 584 585 586 587 588 589 590 591 592

    return self;
}

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

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

593 594 595
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

596 597 598 599 600
    CGImageRelease(_knob);

    [super dealloc];
}

601 602 603
#pragma mark -
#pragma mark drawing

604 605
- (CGRect)_playPauseButtonRect
{
606 607 608 609 610 611
    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));
612 613 614 615
}

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

619
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
620
                      self.bounds.size.width - playPauseButtonWidth - 15 - CGImageGetWidth(_enterFullscreen), CGImageGetHeight(_sliderTrackLeft));
621 622 623 624 625 626
}

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

627
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
628

629
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
630 631 632 633 634 635 636 637 638 639
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
640
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
}

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

665 666 667 668
    // Draw fullscreen button
    CGRect fullscreenButtonRect = [self _fullscreenButtonRect];
    fullscreenButtonRect.origin.x = CGRectGetMaxX(sliderRightTrackRect) + 5;
    CGContextDrawImage(context, fullscreenButtonRect, self.isFullscreen ? _leaveFullscreen : _enterFullscreen);
669 670 671 672 673 674 675 676 677 678 679
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
680 681
}

682 683 684 685 686 687 688
#pragma mark -
#pragma mark event handling

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

689
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
690 691 692 693 694
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

695
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
696 697 698 699 700 701 702

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
703
        _wasPlayingBeforeMouseDown = self.isPlaying;
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
        _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;

721
        return;
722 723 724
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
725
        self.cppPlugin->playlist_togglePause();
726 727
        return;
    }
728 729 730 731
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
732 733 734 735 736 737 738 739 740 741 742 743
}

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

744
@end
745

746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
@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

775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
@implementation VLCFullscreenWindow

- (id)initWithContentRect:(NSRect)contentRect
{
    if( self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
        initialFrame = contentRect;
        [self setBackgroundColor:[NSColor blackColor]];
        [self setHasShadow:YES];
        [self setMovableByWindowBackground: YES];
        [self center];
    }
    return self;
}

- (void)enterFullscreen
{
    NSScreen *screen = [self screen];

    initialFrame = [self frame];
    [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];
    [self setFrame:initialFrame display:YES animate:YES];
}

@end