vlcplugin_mac.mm 32 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
 * 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
#define SHOW_BRANDING 1

@interface VLCNoMediaLayer : CALayer {
    VlcPluginMac *_cppPlugin;
}
@property (readwrite) VlcPluginMac * cppPlugin;
42 43 44

@end

45
@interface VLCPlaybackLayer : CALayer {
46
    CGColorSpaceRef _colorspace;
47
    VlcPluginMac *_cppPlugin;
48 49 50
    CGImageRef _lastFrame;
    int _cached_width;
    int _cached_height;
51 52 53 54 55
}
@property (readwrite) VlcPluginMac * cppPlugin;

@end

56
@interface VLCControllerLayer : CALayer {
57 58 59 60 61 62 63
    CGImageRef _playImage;
    CGImageRef _pauseImage;

    CGImageRef _sliderTrackLeft;
    CGImageRef _sliderTrackRight;
    CGImageRef _sliderTrackCenter;

64 65 66
    CGImageRef _enterFullscreen;
    CGImageRef _leaveFullscreen;

67
    CGImageRef _knob;
68 69 70 71

    BOOL _wasPlayingBeforeMouseDown;
    BOOL _isScrubbing;
    CGFloat _mouseDownXDelta;
72 73 74

    double _position;
    BOOL _isPlaying;
75
    BOOL _isFullscreen;
76 77

    VlcPluginMac *_cppPlugin;
78
}
79 80
@property (readwrite) double mediaPosition;
@property (readwrite) BOOL isPlaying;
81
@property (readwrite) BOOL isFullscreen;
82 83
@property (readwrite) VlcPluginMac * cppPlugin;

84 85 86
- (void)handleMouseDown:(CGPoint)point;
- (void)handleMouseUp:(CGPoint)point;
- (void)handleMouseDragged:(CGPoint)point;
87 88 89

@end

90 91 92 93 94 95
@interface VLCControllerLayer (Internal)
- (CGRect)_playPauseButtonRect;
- (CGRect)_fullscreenButtonRect;
- (CGRect)_sliderRect;
@end

96 97
@interface VLCFullscreenContentView : NSView {
    VlcPluginMac *_cppPlugin;
98
    NSTimeInterval _timeSinceLastMouseMove;
99 100
}
@property (readwrite) VlcPluginMac * cppPlugin;
101 102 103 104

@end

@interface VLCFullscreenWindow : NSWindow {
105 106
    NSRect _initialFrame;
    VLCFullscreenContentView *_customContentView;
107
}
108
@property (readonly) VLCFullscreenContentView* customContentView;
109 110 111 112 113

- (id)initWithContentRect:(NSRect)contentRect;

@end

114 115 116 117 118 119
@interface NSScreen (VLCAdditions)
- (BOOL)hasMenuBar;
- (BOOL)hasDock;
- (CGDirectDisplayID)displayID;
@end

120
static CALayer * browserRootLayer;
121
static VLCPlaybackLayer * playbackLayer;
122
static VLCNoMediaLayer * noMediaLayer;
123
static VLCControllerLayer * controllerLayer;
124
static VLCFullscreenWindow * fullscreenWindow;
125
static VLCFullscreenContentView * fullscreenView;
126

127 128 129
VlcPluginMac::VlcPluginMac(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
130
    browserRootLayer = [[CALayer alloc] init];
131 132 133 134
}

VlcPluginMac::~VlcPluginMac()
{
135
    [fullscreenWindow release];
136 137
    [playbackLayer release];
    [noMediaLayer release];
138
    [controllerLayer release];
139
    [browserRootLayer release];
140 141 142 143
}

void VlcPluginMac::set_player_window()
{
144 145 146 147 148 149 150 151 152 153 154 155 156 157
    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)
{
158 159 160 161
    /* store native video resolution and use it
     * scaling will be performed by CA, which is more efficient */
    m_media_width = (float)(*width);
    m_media_height = (float)(*height);
162 163

    memcpy(chroma, "RGBA", sizeof("RGBA")-1);
164 165 166

    (*pitches) = (*width) * 4;
    (*lines) = (*height);
167 168 169 170 171 172 173 174 175 176 177

    //+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);
178 179
    m_media_width = 0.;
    m_media_height = 0.;
180
    [fullscreenWindow orderOut: nil];
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
}

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
    libvlc_toggle_fullscreen(getMD());
203
    this->update_controls();
204 205 206

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

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

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

229
        [[fullscreenWindow contentView] enterFullScreenMode: [NSScreen mainScreen] withOptions: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 0], NSFullScreenModeAllScreens, nil]];
230
    } else {
231
        [[fullscreenWindow contentView] exitFullScreenModeWithOptions: nil];
232 233 234 235 236 237 238 239 240
        [fullscreenWindow orderOut: nil];
        [noMediaLayer removeFromSuperlayer];
        [playbackLayer removeFromSuperlayer];
        [controllerLayer removeFromSuperlayer];

        [browserRootLayer addSublayer: noMediaLayer];
        [browserRootLayer addSublayer: playbackLayer];
        [browserRootLayer addSublayer: controllerLayer];
    }
241 242
}

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

int  VlcPluginMac::get_fullscreen()
{
253
    return libvlc_get_fullscreen(getMD());
254 255
}

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

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

void VlcPluginMac::update_controls()
{
270 271
    [controllerLayer setMediaPosition: libvlc_media_player_get_position(getMD())];
    [controllerLayer setIsPlaying: playlist_isplaying()];
272
    [controllerLayer setIsFullscreen:this->get_fullscreen()];
273

274 275 276

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

284 285 286
    [controllerLayer setNeedsDisplay];
}

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

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

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

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

310 311
    playbackLayer = [[VLCPlaybackLayer alloc] init];
    playbackLayer.opaque = 1.;
312
    [browserRootLayer addSublayer: playbackLayer];
313 314 315
    [playbackLayer setCppPlugin: this];
    [playbackLayer setHidden: YES];

316 317
    controllerLayer = [[VLCControllerLayer alloc] init];
    controllerLayer.opaque = 1.;
318
    [browserRootLayer addSublayer: controllerLayer];
319
    [controllerLayer setCppPlugin: this];
320

321 322 323
    [browserRootLayer setNeedsDisplay];

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

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

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

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
338
        {
339 340 341
            if (cocoaEvent->data.mouse.clickCount >= 2)
                VlcPluginMac::toggle_fullscreen();

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

347 348
            return true;
        }
349
        case NPCocoaEventMouseUp:
350 351 352 353 354
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

355
            [controllerLayer handleMouseUp:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
356 357 358 359 360 361 362 363 364

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

365
            [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:point toLayer:controllerLayer]];
366 367 368

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

        default:
            break;
    }

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

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

        CGContextSaveGState(cgContext);

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

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

        CGContextRestoreGState(cgContext);

        return true;
429
    }
430

431 432
    return VlcPluginBase::handle_event(event);
}
433

434 435 436 437 438 439 440 441
@implementation VLCPlaybackLayer
@synthesize cppPlugin = _cppPlugin;

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

        _colorspace = CGColorSpaceCreateDeviceRGB();
444 445 446 447 448
    }

    return self;
}

449 450 451
- (void)dealloc
{
    CGColorSpaceRelease(_colorspace);
452 453
    if (_lastFrame)
        CGImageRelease(_lastFrame);
454 455 456
    [super dealloc];
}

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

462 463 464 465
    BOOL b_paused = !([self cppPlugin]->playlist_isplaying());

    if ((!_lastFrame && b_paused) || ![self cppPlugin]->get_player().is_open()) {
        NSLog(@"no last frame or no open player");
466
        return;
467
    }
468

469 470
    float media_width = [self cppPlugin]->m_media_width;
    float media_height = [self cppPlugin]->m_media_height;
471

472 473 474 475 476 477 478 479 480 481
    if (media_width == 0. || media_height == 0.) {
        if (_cached_height != 0 && _cached_width != 0) {
            media_width = _cached_width;
            media_height = _cached_height;
        } else
            return;
    } else {
        _cached_width = media_width;
        _cached_height = media_height;
    }
482

483
    CGRect layerRect = self.bounds;
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
    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;
        }
    }
506 507

    /* Compute the position of the video */
508 509 510 511
    float left = (layerRect.size.width  - display_width)  / 2.;
    float top  = (layerRect.size.height - display_height) / 2.;

    CGContextSaveGState(cgContext);
512

513 514 515
    static const size_t kComponentsPerPixel = 4;
    static const size_t kBitsPerComponent = sizeof(unsigned char) * 8;

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
    if (!b_paused) {
        /* fetch 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);

        if (_lastFrame)
            CGImageRelease(_lastFrame);

        _lastFrame = CGImageCreate(media_width,
                                         media_height,
                                         kBitsPerComponent,
                                         kBitsPerComponent * kComponentsPerPixel,
                                         kComponentsPerPixel * media_width,
                                         _colorspace,
                                         kCGBitmapByteOrder16Big,
                                         dataProvider,
                                         NULL,
                                         true,
                                         kCGRenderingIntentPerceptual);

539
        CGDataProviderRelease(dataProvider);
540 541 542 543 544 545

        if (!_lastFrame) {
            CGImageRelease(_lastFrame);
            CGContextRestoreGState(cgContext);
            return;
        }
546
    }
547
    CGRect rect = CGRectMake(left, top, display_width, display_height);
548
    CGContextDrawImage(cgContext, rect, _lastFrame);
549 550 551 552 553 554

    CGContextRestoreGState(cgContext);
}

@end

555
@implementation VLCNoMediaLayer
556
@synthesize cppPlugin = _cppPlugin;
557 558 559 560 561

- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
562
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
563 564 565 566 567
    }

    return self;
}

568
- (void)drawInContext:(CGContextRef)cgContext
569 570 571 572 573 574
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

575
#if SHOW_BRANDING
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 616 617 618 619 620 621 622 623 624 625 626 627 628 629
    // 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);
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
#else
    // draw a black rect
    CGRect rect;
    float media_width = [self cppPlugin]->m_media_width;
    float media_height = [self cppPlugin]->m_media_height;

    if (media_width == 0. || media_height == 0.)
        CGRectMake(0, 0, windowWidth, windowHeight);
    else {
        CGRect 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;
            }
        }

        float left = (layerRect.size.width  - display_width)  / 2.;
        float top  = (layerRect.size.height - display_height) / 2.;
        CGRect rect = CGRectMake(left, top, display_width, display_height);
    }

    CGContextAddRect(cgContext, rect);
    CGContextSetGrayFillColor(cgContext, 0., 1.);
    CGContextDrawPath(cgContext, kCGPathFill);
#endif
671 672 673 674

    CGContextRestoreGState(cgContext);
}

675 676 677 678
@end

@implementation VLCControllerLayer

679 680
@synthesize mediaPosition = _position;
@synthesize isPlaying = _isPlaying;
681
@synthesize isFullscreen = _isFullscreen;
682 683
@synthesize cppPlugin = _cppPlugin;

684
static CGImageRef createImageNamed(NSString *name)
685
{
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
    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");

714 715 716
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

717
        _knob = createImageNamed(@"Knob");
718
    }
719 720 721 722 723 724 725 726 727 728 729 730 731

    return self;
}

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

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

732 733 734
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

735 736 737 738 739
    CGImageRelease(_knob);

    [super dealloc];
}

740 741 742
#pragma mark -
#pragma mark drawing

743 744
- (CGRect)_playPauseButtonRect
{
745 746 747 748 749 750
    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));
751 752 753 754
}

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

758
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
759
                      self.bounds.size.width - playPauseButtonWidth - 15 - CGImageGetWidth(_enterFullscreen), CGImageGetHeight(_sliderTrackLeft));
760 761 762 763 764 765
}

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

766
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
767

768
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
769 770 771 772 773 774 775 776 777 778
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

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

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

804 805 806 807
    // Draw fullscreen button
    CGRect fullscreenButtonRect = [self _fullscreenButtonRect];
    fullscreenButtonRect.origin.x = CGRectGetMaxX(sliderRightTrackRect) + 5;
    CGContextDrawImage(context, fullscreenButtonRect, self.isFullscreen ? _leaveFullscreen : _enterFullscreen);
808 809 810 811 812 813 814 815 816 817 818
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
819 820
}

821 822 823 824 825 826 827
#pragma mark -
#pragma mark event handling

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

828
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
829 830 831 832 833
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

834
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
835 836 837 838 839 840 841

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
842
        _wasPlayingBeforeMouseDown = self.isPlaying;
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
        _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;

860
        return;
861 862 863
    }

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

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

883
@end
884

885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
@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

914 915
@implementation VLCFullscreenWindow

916 917
@synthesize customContentView = _customContentView;

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

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
926
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
927
        [[self contentView] addSubview: _customContentView];
928
        [self setInitialFirstResponder:_customContentView];
929 930 931 932
    }
    return self;
}

933 934 935 936 937 938
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

939 940 941 942 943 944 945 946 947 948
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

949 950
@end

951
@implementation VLCFullscreenContentView
952 953 954 955 956 957 958
@synthesize cppPlugin = _cppPlugin;

- (BOOL)acceptsFirstResponder
{
    return YES;
}

959 960 961 962 963
- (BOOL)canBecomeKeyView
{
    return YES;
}

964 965 966 967 968 969 970 971 972 973 974 975
- (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;
976 977 978
            } else if (key == ' ') {
                self.cppPlugin->playlist_togglePause();
                return;
979 980 981 982 983
            }
        }
    }
    [super keyDown: theEvent];
}
984

985 986 987
- (void)mouseDown:(NSEvent *)theEvent
{
    if ([theEvent type] == NSLeftMouseDown && !([theEvent modifierFlags] & NSControlKeyMask)) {
988
        if ([theEvent clickCount] >= 2)
989
            self.cppPlugin->toggle_fullscreen();
990 991 992
        else {
            NSPoint point = [NSEvent mouseLocation];

993
            [controllerLayer handleMouseDown:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
994
        }
995 996 997 998 999
    }

    [super mouseDown: theEvent];
}

1000 1001 1002 1003
- (void)mouseUp:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];

1004
    [controllerLayer handleMouseUp:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1005 1006 1007 1008 1009 1010 1011 1012

    [super mouseUp: theEvent];
}

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

1013
    [controllerLayer handleMouseDragged:[browserRootLayer convertPoint:CGPointMake(point.x, point.y) toLayer:controllerLayer]];
1014 1015 1016 1017 1018 1019 1020

    [super mouseDragged: theEvent];
}

- (void)mouseMoved:(NSEvent *)theEvent
{
    self.cppPlugin->set_toolbar_visible(true);
1021 1022
    _timeSinceLastMouseMove = [NSDate timeIntervalSinceReferenceDate];
    [self performSelector:@selector(hideToolbar) withObject:nil afterDelay: 4.1];
1023 1024 1025 1026

    [super mouseMoved: theEvent];
}

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

1035 1036
@end