vlcplugin_mac.mm 42.8 KB
Newer Older
1 2 3
/*****************************************************************************
 * vlcplugin_mac.cpp: a VLC plugin for Mozilla (Mac interface)
 *****************************************************************************
4
 * Copyright (C) 2011-2015 VLC Authors and VideoLAN
5 6
 * $Id$
 *
7
 * Authors: Felix Paul Kühne <fkuehne # videolan # org>
8
 *          Cheng Sun <chengsun9@gmail.com>
9 10
 *          Jean-Baptiste Kempf <jb@videolan.org>
 *          James Bates <james.h.bates@gmail.com>
11
 *          Pierre d'Herbemont <pdherbemont # videolan.org>
12
 *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
13
 *
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

29
#pragma mark includes and fix-ups
30

31 32
#import "vlcplugin_mac.h"
#import <npapi.h>
33

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

37 38 39 40 41 42 43 44 45 46
/* compilation support for 10.6 */
#define OSX_LION NSAppKitVersionNumber >= 1115.2
#ifndef MAC_OS_X_VERSION_10_7

@interface NSView (IntroducedInLion)
- (NSRect)convertRectToBacking:(NSRect)aRect;
@end

#endif

47 48 49 50 51 52 53
#pragma mark - prototypes

CGImageRef createImageNamed(NSString *);

#pragma mark - objc class interfaces

@interface VLCNoMediaLayer : CALayer
54 55 56
{
    VlcPluginMac *_cppPlugin;
}
57

58
@property (readwrite) VlcPluginMac *cppPlugin;
59 60 61

@end

62
@interface VLCBrowserRootLayer : CALayer {
63
    NSTimer *_interfaceUpdateTimer;
64
    VlcPluginMac *_cppPlugin;
65
}
66

67
@property (readwrite) VlcPluginMac *cppPlugin;
68 69 70

- (void)startUIUpdateTimer;

71 72 73
- (void)addVoutLayer:(CALayer *)aLayer;
- (void)removeVoutLayer:(CALayer *)aLayer;
- (CGSize)currentOutputSize;
74 75
@end

76
@interface VLCControllerLayer : CALayer {
77 78 79 80 81 82 83
    CGImageRef _playImage;
    CGImageRef _pauseImage;

    CGImageRef _sliderTrackLeft;
    CGImageRef _sliderTrackRight;
    CGImageRef _sliderTrackCenter;

84 85 86
    CGImageRef _enterFullscreen;
    CGImageRef _leaveFullscreen;

87
    CGImageRef _knob;
88 89 90 91

    BOOL _wasPlayingBeforeMouseDown;
    BOOL _isScrubbing;
    CGFloat _mouseDownXDelta;
92 93 94 95 96

    double _mediaPosition;
    BOOL _isPlaying;
    BOOL _isFullscreen;
    VlcPluginMac *_cppPlugin;
97
}
98 99
@property (readwrite) double mediaPosition;
@property (readwrite) BOOL isPlaying;
100
@property (readwrite) BOOL isFullscreen;
101
@property (readwrite) VlcPluginMac *cppPlugin;
102

103 104 105
- (void)handleMouseDown:(CGPoint)point;
- (void)handleMouseUp:(CGPoint)point;
- (void)handleMouseDragged:(CGPoint)point;
106 107 108

@end

109 110 111 112 113 114
@interface VLCControllerLayer (Internal)
- (CGRect)_playPauseButtonRect;
- (CGRect)_fullscreenButtonRect;
- (CGRect)_sliderRect;
@end

115 116 117 118 119 120
@interface VLCPlaybackLayer : CALayer
- (void)mouseButtonDown:(int)buttonNumber;
- (void)mouseButtonUp:(int)buttonNumber;
- (void)mouseMovedToX:(double)xValue Y:(double)yValue;
@end

121 122
@interface VLCFullscreenContentView : NSView {
    VlcPluginMac *_cppPlugin;
123
    NSTimeInterval _timeSinceLastMouseMove;
124
}
125
@property (readwrite) VlcPluginMac *cppPlugin;
126

127 128
- (void)hideToolbar;

129 130 131
@end

@interface VLCFullscreenWindow : NSWindow {
132 133
    NSRect _initialFrame;
    VLCFullscreenContentView *_customContentView;
134
}
135
@property (readonly) VLCFullscreenContentView *customContentView;
136 137 138 139 140

- (id)initWithContentRect:(NSRect)contentRect;

@end

141 142 143 144 145 146
@interface NSScreen (VLCAdditions)
- (BOOL)hasMenuBar;
- (BOOL)hasDock;
- (CGDirectDisplayID)displayID;
@end

147
@interface VLCPerInstanceStorage : NSObject
148 149 150 151 152 153 154 155 156
{
    VlcPluginMac *_cppPlugin;
    VLCBrowserRootLayer *_browserRootLayer;
    VLCPlaybackLayer *_playbackLayer;
    VLCNoMediaLayer *_noMediaLayer;
    VLCControllerLayer *_controllerLayer;
    VLCFullscreenWindow *_fullscreenWindow;
    VLCFullscreenContentView *_fullscreenView;
}
157

158 159 160 161 162 163 164
@property (readwrite, assign) VlcPluginMac *cppPlugin;
@property (readwrite, retain) VLCBrowserRootLayer *browserRootLayer;
@property (readwrite, retain) VLCPlaybackLayer *playbackLayer;
@property (readwrite, retain) VLCNoMediaLayer *noMediaLayer;
@property (readwrite, retain) VLCControllerLayer *controllerLayer;
@property (readwrite, retain) VLCFullscreenWindow *fullscreenWindow;
@property (readwrite, retain) VLCFullscreenContentView *fullscreenView;
165

166
@end
167

168
@implementation VLCPerInstanceStorage
169

170 171
@synthesize cppPlugin = _cppPlugin, browserRootLayer = _browserRootLayer, playbackLayer = _playbackLayer, noMediaLayer = _noMediaLayer, controllerLayer = _controllerLayer, fullscreenWindow = _fullscreenWindow, fullscreenView = _fullscreenView;

172
@end
173

174
#pragma mark - handling of c++ bindings
175

176 177 178
VlcPluginMac::VlcPluginMac(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
179 180
    _perInstanceStorage = [[VLCPerInstanceStorage alloc] init];
    [(VLCPerInstanceStorage *)_perInstanceStorage setCppPlugin: this];
181 182 183 184
}

VlcPluginMac::~VlcPluginMac()
{
185
    [(VLCPerInstanceStorage *)_perInstanceStorage release];
186
    _perInstanceStorage = nil;
187 188 189 190
}

void VlcPluginMac::set_player_window()
{
191
    /* pass base layer to libvlc to pass it on to the vout */
192
    libvlc_media_player_set_nsobject(getMD(), [(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer]);
193 194 195 196
}

void VlcPluginMac::toggle_fullscreen()
{
197 198
    if (!get_options().get_enable_fs())
        return;
199
    libvlc_toggle_fullscreen(getMD());
200
    this->update_controls();
201

202
    if (get_fullscreen() != 0) {
203 204
        /* this window is kind of useless. however, we need to support 10.5, since enterFullScreenMode depends on the
         * existance of a parent window. This is solved in 10.6 and we should remove the window once we require it. */
205 206 207
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setFullscreenWindow:[[VLCFullscreenWindow alloc] initWithContentRect: NSMakeRect(0., 0., npwindow.width, npwindow.height)]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow] setLevel:CGShieldingWindowLevel()];
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setFullscreenView:[[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow] customContentView]];
208 209 210

        /* 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 */
211 212 213
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView] setLayer:[CALayer layer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView] setWantsLayer:YES];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView] setCppPlugin:this];
214

215 216 217
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer] removeFromSuperlayer];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] removeFromSuperlayer];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] removeFromSuperlayer];
218

219
        if ([(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView] == nil)
220
            return;
221
        if ([(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView].layer == nil)
222 223
            return;

224 225 226 227
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView].layer addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView].layer addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView].layer addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView].layer setNeedsDisplay];
228

229
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow].contentView enterFullScreenMode: [NSScreen mainScreen] withOptions: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 0], NSFullScreenModeAllScreens, nil]];
230 231

        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenView] performSelector:@selector(hideToolbar) withObject:nil afterDelay: 4.1];
232
    } else {
233
        if (![(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow])
234
            return;
235
        if (![(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow].contentView)
236 237
            return;

238 239 240 241
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow].contentView exitFullScreenModeWithOptions: nil];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer] removeFromSuperlayer];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] removeFromSuperlayer];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] removeFromSuperlayer];
242

243 244 245 246 247
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] addSublayer: [(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage fullscreenWindow] orderOut: nil];
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setFullscreenWindow: nil];
248
    }
249 250
}

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

259
bool  VlcPluginMac::get_fullscreen()
260
{
261
    return libvlc_get_fullscreen(getMD());
262 263
}

264 265
void VlcPluginMac::set_toolbar_visible(bool b_value)
{
266
    if (!get_options().get_show_toolbar()) {
267
        [(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer].hidden = YES;
268
        return;
269
    }
270
    [(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer].hidden = !b_value;
271 272 273 274
}

bool VlcPluginMac::get_toolbar_visible()
{
275
    return [(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer].isHidden;
276 277 278 279
}

void VlcPluginMac::update_controls()
{
280 281
    libvlc_state_t currentstate = libvlc_media_player_get_state(getMD());
    if (currentstate == libvlc_Playing || currentstate == libvlc_Paused || currentstate == libvlc_Opening) {
282 283
        [(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer].hidden = YES;
        [(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer].hidden = NO;
284
    } else {
285 286
        [(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer].hidden = NO;
        [(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer].hidden = YES;
287 288
    }

289
    if ([(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] != nil) {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
290 291
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setMediaPosition: m_player.get_mp().position()];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setIsPlaying: player().mlp().isPlaying()];
292 293
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setIsFullscreen:this->get_fullscreen()];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setNeedsDisplay];
294
    }
295 296
}

297 298 299 300 301 302 303
bool VlcPluginMac::create_windows()
{
    return true;
}

bool VlcPluginMac::resize_windows()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
304
    return true;
305 306 307 308
}

bool VlcPluginMac::destroy_windows()
{
309
    npwindow.window = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
310
    return true;
311
}
312

313 314
NPError VlcPluginMac::get_root_layer(void *value)
{
315 316 317 318 319
    if ([(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] == nil) {
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setBrowserRootLayer:[[VLCBrowserRootLayer alloc] init]];
        [(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer].cppPlugin = this;

        const char *userAgent = NPN_UserAgent(this->getBrowser());
320 321
        if (strstr(userAgent, "Safari")) {
            NSLog(@"Safari detected, deploying UI update timer");
322
            [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] performSelector:@selector(startUIUpdateTimer) withObject:nil afterDelay:1.];
323 324
        } else if (strstr(userAgent, "Firefox")) {
            NSLog(@"Firefox detected, deploying UI update timer");
325
            this->runningWithinFirefox = true;
326 327
            [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] performSelector:@selector(startUIUpdateTimer) withObject:nil afterDelay:1.];
        }
328

329 330
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setNoMediaLayer:[[VLCNoMediaLayer alloc] init]];
        [(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer].opaque = 1.;
331 332
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer] setCppPlugin:this];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] addSublayer:[(VLCPerInstanceStorage *)this->_perInstanceStorage noMediaLayer]];
333

334
        [(VLCPerInstanceStorage *)this->_perInstanceStorage setControllerLayer:[[VLCControllerLayer alloc] init]];
335 336
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] addSublayer:[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]];
        [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setCppPlugin:this];
337 338 339

        [[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] setNeedsDisplay];
    }
340

341
    *(CALayer **)value = [(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer];
342
    return NPERR_NO_ERROR;
343 344
}

345 346
bool VlcPluginMac::handle_event(void *event)
{
347
    NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event;
348

349 350 351 352 353 354 355
    if (!event)
        return false;

    NPCocoaEventType eventType = cocoaEvent->type;

    switch (eventType) {
        case NPCocoaEventMouseDown:
356
        {
357 358 359
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] != nil) {
                if ([[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseButtonDown:)])
                    [[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] mouseButtonDown:cocoaEvent->data.mouse.buttonNumber];
360
            }
361
            if (cocoaEvent->data.mouse.clickCount >= 2)
362
                this->toggle_fullscreen();
363

364 365 366
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);
367
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] != nil) {
368
                [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] handleMouseDown:[[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] convertPoint:point toLayer:[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]]];
369 370
                [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setNeedsDisplay];
            }
371

372 373
            return true;
        }
374
        case NPCocoaEventMouseUp:
375
        {
376 377 378
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] != nil) {
                if ([[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseButtonUp:)])
                    [[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] mouseButtonUp:cocoaEvent->data.mouse.buttonNumber];
379
            }
380 381 382 383
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

384
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] != nil) {
385
                [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] handleMouseUp:[[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] convertPoint:point toLayer:[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]]];
386 387
                [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] setNeedsDisplay];
            }
388 389 390

            return true;
        }
391 392
        case NPCocoaEventMouseMoved:
        {
393 394 395
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] != nil) {
                if ([[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseMovedToX:Y:)])
                    [[(VLCPerInstanceStorage *)this->_perInstanceStorage playbackLayer] mouseMovedToX:cocoaEvent->data.mouse.pluginX Y:cocoaEvent->data.mouse.pluginY];
396 397
            }
        }
398 399 400 401 402 403
        case NPCocoaEventMouseDragged:
        {
            CGPoint point = CGPointMake(cocoaEvent->data.mouse.pluginX,
                                        // Flip the y coordinate
                                        npwindow.height - cocoaEvent->data.mouse.pluginY);

404 405
            if ([(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] != nil)
                [[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer] handleMouseDragged:[[(VLCPerInstanceStorage *)this->_perInstanceStorage browserRootLayer] convertPoint:point toLayer:[(VLCPerInstanceStorage *)this->_perInstanceStorage controllerLayer]]];
406 407 408

            return true;
        }
409 410
        case NPCocoaEventMouseEntered:
        {
411
            this->set_toolbar_visible(true);
412 413 414 415
            return true;
        }
        case NPCocoaEventMouseExited:
        {
416
            this->set_toolbar_visible(false);
417 418
            return true;
        }
419
        case NPCocoaEventKeyDown:
420 421 422 423
        {
            if (cocoaEvent->data.key.keyCode == 53) {
                toggle_fullscreen();
                return true;
424
            } else if (cocoaEvent->data.key.keyCode == 49) {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
425
                m_player.mlp().pause();
426
                return true;
427 428 429
            }
        }
        case NPCocoaEventKeyUp:
430 431
        case NPCocoaEventFocusChanged:
        case NPCocoaEventScrollWheel:
432
            return true;
433 434 435 436 437

        default:
            break;
    }

438
    if (eventType == NPCocoaEventDrawRect) {
439 440 441 442 443 444 445 446
        /* 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. */
447 448 449
        CGContextRef cgContext = cocoaEvent->data.draw.context;
        if (!cgContext) {
            return false;
450
        }
451 452 453 454 455 456 457 458 459 460

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

        CGContextSaveGState(cgContext);

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

461
        // draw black rectancle
462
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
463
        CGContextSetGrayFillColor(cgContext, 0., 1.);
464 465 466 467 468
        CGContextDrawPath(cgContext, kCGPathFill);

        CGContextRestoreGState(cgContext);

        return true;
469
    }
470

471 472
    return VlcPluginBase::handle_event(event);
}
473

474 475
#pragma mark - objc class implementations

476
@implementation VLCBrowserRootLayer
477

478 479
@synthesize cppPlugin = _cppPlugin;

480 481 482 483 484 485 486 487 488 489
- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
    }

    return self;
}

490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
- (void)startUIUpdateTimer
{
    _interfaceUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_updateUI) userInfo:nil repeats:YES];
    [_interfaceUpdateTimer retain];
    [_interfaceUpdateTimer fire];
}

- (void)dealloc
{
    if (_interfaceUpdateTimer) {
        [_interfaceUpdateTimer invalidate];
        [_interfaceUpdateTimer release];
    }

    [super dealloc];
}

- (void)_updateUI
{
    if (_cppPlugin)
        _cppPlugin->update_controls();
}

513
- (void)addVoutLayer:(CALayer *)aLayer
514
{
515
    [CATransaction begin];
516
    VLCPlaybackLayer *playbackLayer = (VLCPlaybackLayer *)[aLayer retain];
517 518
    playbackLayer.opaque = 1.;
    playbackLayer.hidden = NO;
519 520 521 522

    if (libvlc_get_fullscreen(_cppPlugin->getMD()) != 0) {
        /* work-around a 32bit runtime limitation where we can't cast
         * NSRect to CGRect */
523
        NSRect fullscreenViewFrame = [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage fullscreenView].frame;
524 525 526 527 528
        playbackLayer.bounds = CGRectMake(fullscreenViewFrame.origin.x,
                                          fullscreenViewFrame.origin.y,
                                          fullscreenViewFrame.size.width,
                                          fullscreenViewFrame.size.height);
        playbackLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
529
        [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage setPlaybackLayer: playbackLayer];
530

531 532 533 534
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] removeFromSuperlayer];
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage fullscreenView].layer addSublayer: [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer]];
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage fullscreenView].layer addSublayer: [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer]];
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage fullscreenView].layer setNeedsDisplay];
535
    } else {
536 537 538
        [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage setPlaybackLayer: playbackLayer];
        [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer].bounds = [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage noMediaLayer].bounds;
        [self insertSublayer:[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] below:[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer]];
539
    }
540
    [self setNeedsDisplay];
541 542
    [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] setNeedsDisplay];
    CGRect frame = [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer].bounds;
543 544
    frame.origin.x = 0.;
    frame.origin.y = 0.;
545
    [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer].frame = frame;
546
    [CATransaction commit];
547 548
}

549
- (void)removeVoutLayer:(CALayer *)aLayer
550
{
551 552 553
    [CATransaction begin];
    [aLayer removeFromSuperlayer];
    [CATransaction commit];
554
    VLCPerInstanceStorage *storage = (VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage;
555

556 557 558 559 560
    if (storage != nil) {
        if ([storage respondsToSelector:@selector(setPlaybackLayer:)]) {
            [storage setPlaybackLayer:nil];
        }
    }
561
}
562

563 564
- (CGSize)currentOutputSize
{
565
    return [(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage browserRootLayer].visibleRect.size;
566 567 568 569
}

@end

570
@implementation VLCNoMediaLayer
571

572 573
@synthesize cppPlugin = _cppPlugin;

574 575 576 577
- (id)init
{
    if (self = [super init]) {
        self.needsDisplayOnBoundsChange = YES;
578
        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
579 580 581 582 583
    }

    return self;
}

584
- (void)drawInContext:(CGContextRef)cgContext
585 586 587 588 589 590
{
    float windowWidth = self.visibleRect.size.width;
    float windowHeight = self.visibleRect.size.height;

    CGContextSaveGState(cgContext);

591 592 593 594 595
    CGColorRef backgroundColor;
    unsigned r = 0, g = 0, b = 0;
    HTMLColor2RGB(self.cppPlugin->get_options().get_bg_color().c_str(), &r, &g, &b);
    backgroundColor = CGColorCreateGenericRGB(r, g, b, 1.);

596 597 598 599 600 601 602 603 604 605 606 607
    if (self.cppPlugin->get_options().get_enable_branding()) {
        // draw background
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
        CGContextSetFillColorWithColor(cgContext, backgroundColor);
        CGContextDrawPath(cgContext, kCGPathFill);

        // draw gradient
        CGImageRef gradient = createImageNamed(@"gradient");
        CGContextDrawImage(cgContext, CGRectMake(0., 0., windowWidth, 150.), gradient);
        CGImageRelease(gradient);

        // draw info text
608 609 610 611 612 613 614
        CGContextSetGrayStrokeColor(cgContext, .95, 1.);
        CGContextSetTextDrawingMode(cgContext, kCGTextFill);
        CGContextSetGrayFillColor(cgContext, 1., 1.);
        CFStringRef keys[2];
        keys[0] = kCTFontAttributeName;
        keys[1] = kCTForegroundColorFromContextAttributeName;
        CFTypeRef values[2];
615
        values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
616 617 618 619 620
        values[1] = kCFBooleanTrue;
        CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                        (const void **)&keys,
                                                        (const void **)&values,
                                                        2, NULL, NULL);
621
        CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("VLC Web Plugin"), stylesDict);
622
        CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
        CGContextSetTextPosition(cgContext, 25., 60.);
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);

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

        // draw version string
        CFStringRef arch;
    #ifdef __x86_64__
        arch = CFSTR("64-bit");
    #else
        arch = CFSTR("32-bit");
    #endif

        attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s — windowed mode — %@"), libvlc_get_version(), arch), stylesDict);
        textLine = CTLineCreateWithAttributedString(attRef);
        CGContextSetTextPosition(cgContext, 25., 40.);
647 648 649 650
        CTLineDraw(textLine, cgContext);
        CFRelease(textLine);
        CFRelease(attRef);
        CFRelease(stylesDict);
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694

        // draw cone
        CGImageRef cone = createImageNamed(@"cone");
        CGFloat coneWidth = CGImageGetWidth(cone);
        CGFloat coneHeight = CGImageGetHeight(cone);
        if (windowHeight <= 320.) {
            coneWidth = coneWidth / 2.;
            coneHeight = coneHeight / 2.;
        }
        CGContextDrawImage(cgContext, CGRectMake((windowWidth - coneWidth) / 2., (windowHeight - coneHeight) / 2., coneWidth, coneHeight), cone);
        CGImageRelease(cone);

        // draw custom text
        values[0] = CTFontCreateWithName(CFSTR("Helvetica"),14,NULL);
        stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                        (const void **)&keys,
                                        (const void **)&values,
                                        2, NULL, NULL);
        const char *text = self.cppPlugin->get_options().get_bg_text().c_str();
        if (text != NULL) {
            attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8), stylesDict);
            textLine = CTLineCreateWithAttributedString(attRef);
            CGRect textRect = CTLineGetImageBounds(textLine, cgContext);
            CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2.), (windowHeight / 2.) + (coneHeight / 2.) + textRect.size.height + 50.);
            CTLineDraw(textLine, cgContext);
            CFRelease(textLine);
            CFRelease(attRef);
        }
        CFRelease(stylesDict);
    } else {
        // draw a background colored rect
        CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight));
        CGContextSetFillColorWithColor(cgContext, backgroundColor);
        CGContextDrawPath(cgContext, kCGPathFill);

        const char *text = self.cppPlugin->get_options().get_bg_text().c_str();
        if (text != NULL) {
            CGContextSetGrayStrokeColor(cgContext, .95, 1.);
            CGContextSetTextDrawingMode(cgContext, kCGTextFill);
            CGContextSetGrayFillColor(cgContext, 1., 1.);
            CFStringRef keys[2];
            keys[0] = kCTFontAttributeName;
            keys[1] = kCTForegroundColorFromContextAttributeName;
            CFTypeRef values[2];
695
            values[0] = CTFontCreateWithName(CFSTR("HelveticaNeue-Light"),18,NULL);
696 697 698 699 700 701 702 703 704 705 706 707 708 709
            values[1] = kCFBooleanTrue;
            CFDictionaryRef stylesDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                            (const void **)&keys,
                                                            (const void **)&values,
                                                            2, NULL, NULL);
            CFAttributedStringRef attRef = CFAttributedStringCreate(kCFAllocatorDefault, CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8), stylesDict);
            CTLineRef textLine = CTLineCreateWithAttributedString(attRef);
            CGRect textRect = CTLineGetImageBounds(textLine, cgContext);
            CGContextSetTextPosition(cgContext, ((windowWidth - textRect.size.width) / 2.), (windowHeight / 2.));
            CTLineDraw(textLine, cgContext);
            CFRelease(textLine);
            CFRelease(attRef);
            CFRelease(stylesDict);
        }
710
    }
711
    CGColorRelease(backgroundColor);
712 713 714 715

    CGContextRestoreGState(cgContext);
}

716 717 718 719
@end

@implementation VLCControllerLayer

720 721
@synthesize cppPlugin = _cppPlugin, mediaPosition = _mediaPosition, isPlaying = _isPlaying, isFullscreen = _isFullscreen;

722 723 724 725 726 727 728 729 730 731 732 733 734
- (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");

735 736 737
        _enterFullscreen = createImageNamed(@"enter-fullscreen");
        _leaveFullscreen = createImageNamed(@"leave-fullscreen");

738
        _knob = createImageNamed(@"Knob");
739
    }
740 741 742 743 744 745 746 747 748 749 750 751 752

    return self;
}

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

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

753 754 755
    CGImageRelease(_enterFullscreen);
    CGImageRelease(_leaveFullscreen);

756 757 758 759 760 761 762
    CGImageRelease(_knob);

    [super dealloc];
}

- (CGRect)_playPauseButtonRect
{
763 764 765 766 767 768
    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));
769 770 771 772
}

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

777
    return CGRectMake(playPauseButtonWidth + 7, sliderYPosition,
778
                      self.bounds.size.width - playPauseButtonWidth - fullscreenButtonWidth - 15., CGImageGetHeight(_sliderTrackLeft));
779 780 781 782 783 784
}

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

785
    CGFloat x = self.mediaPosition * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_knob));
786

787
    return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) + 1,
788 789 790 791 792 793 794 795 796 797
                      CGImageGetWidth(_knob), CGImageGetHeight(_knob));
}

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

- (void)_drawPlayPauseButtonInContext:(CGContextRef)context
{
798
    CGContextDrawImage(context, [self _playPauseButtonRect], self.isPlaying ? _pauseImage : _playImage);
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
}

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

823
    // Draw fullscreen button
824 825 826 827 828
    if (self.cppPlugin->get_options().get_enable_fs()) {
        CGRect fullscreenButtonRect = [self _fullscreenButtonRect];
        fullscreenButtonRect.origin.x = CGRectGetMaxX(sliderRightTrackRect) + 5;
        CGContextDrawImage(context, fullscreenButtonRect, self.isFullscreen ? _leaveFullscreen : _enterFullscreen);
    }
829 830 831 832 833 834 835 836 837
}

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

    [self _drawPlayPauseButtonInContext:cgContext];
    [self _drawSliderInContext:cgContext];
838 839
}

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

844
    double fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect);
845 846 847 848 849
    if (fraction > 1.0)
        fraction = 1.0;
    else if (fraction < 0.0)
        fraction = 0.0;

850
    libvlc_media_player_set_position(self.cppPlugin->getMD(), fraction);
851 852 853 854 855 856 857

    [self setNeedsDisplay];
}

- (void)handleMouseDown:(CGPoint)point
{
    if (CGRectContainsPoint([self _sliderRect], point)) {
858
        _wasPlayingBeforeMouseDown = self.isPlaying;
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
        _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;

876
        return;
877 878 879
    }

    if (CGRectContainsPoint([self _playPauseButtonRect], point)) {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
880
        self.cppPlugin->player().mlp().pause();
881 882
        return;
    }
883 884 885 886
    if (CGRectContainsPoint([self _fullscreenButtonRect], point)) {
        self.cppPlugin->toggle_fullscreen();
        return;
    }
887 888 889 890 891 892 893 894 895 896 897 898
}

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

    point.x -= _mouseDownXDelta;

    [self _setNewTimeForThumbCenterX:point.x];
}

899
@end
900

901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
@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

930 931
@implementation VLCFullscreenWindow

932 933
@synthesize customContentView = _customContentView;

934 935
- (id)initWithContentRect:(NSRect)contentRect
{
936
    if (self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]) {
937
        _initialFrame = contentRect;
938
        [self setBackgroundColor:[NSColor blackColor]];
939
        [self setAcceptsMouseMovedEvents: YES];
940 941

        _customContentView = [[VLCFullscreenContentView alloc] initWithFrame:_initialFrame];
942
        [_customContentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
943
        [[self contentView] addSubview: _customContentView];
944
        [self setInitialFirstResponder:_customContentView];
945 946 947 948
    }
    return self;
}

949 950 951 952 953 954
- (void)dealloc
{
    [_customContentView release];
    [super dealloc];
}

955 956 957 958 959 960 961 962 963 964
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

- (BOOL)canBecomeMainWindow
{
    return YES;
}

965 966
@end

967
@implementation VLCFullscreenContentView
968

969 970
@synthesize cppPlugin = _cppPlugin;

971 972 973 974 975
- (BOOL)acceptsFirstResponder
{
    return YES;
}

976 977 978 979 980
- (BOOL)canBecomeKeyView
{
    return YES;
}

981 982 983 984 985 986 987 988 989 990
- (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) {
991
                _cppPlugin->toggle_fullscreen();
992
                return;
993
            } else if (key == ' ') {
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
994
                _cppPlugin->player().mlp().pause();
995
                return;
996 997 998 999 1000
            }
        }
    }
    [super keyDown: theEvent];
}
1001

1002 1003
- (void)mouseDown:(NSEvent *)theEvent
{
1004 1005 1006
    NSEventType eventType = [theEvent type];

    if (eventType == NSLeftMouseDown && !([theEvent modifierFlags] & NSControlKeyMask)) {
1007
        if ([theEvent clickCount] >= 2)
1008
            _cppPlugin->toggle_fullscreen();
1009 1010
        else {
            NSPoint point = [NSEvent mouseLocation];
1011 1012 1013 1014 1015 1016
            /* for Firefox, retina doesn't exist yet so it will return pixels instead of points when doing the conversation
             * so don't convert for Firefox */
            if (!_cppPlugin->runningWithinFirefox)
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] handleMouseDown:[[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage browserRootLayer] convertPoint:CGPointMake(point.x, point.y) toLayer:[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer]]];
            else
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] handleMouseDown:CGPointMake(point.x, point.y)];
1017
        }
1018
    }
1019 1020
    if ([(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] != nil) {
        if ([[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseButtonDown:)]) {
1021
            if (eventType == NSLeftMouseDown)
1022
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonDown:0];
1023
            else if (eventType == NSRightMouseDown)
1024
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonDown:1];
1025
            else
1026
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonDown:2];
1027 1028
        }
    }
1029 1030 1031 1032

    [super mouseDown: theEvent];
}

1033 1034 1035
- (void)mouseUp:(NSEvent *)theEvent
{
    NSPoint point = [NSEvent mouseLocation];
1036
    NSEventType eventType = [theEvent type];
1037

1038 1039 1040 1041 1042 1043
    /* for Firefox, retina doesn't exist yet so it will return pixels instead of points when doing the conversation
     * so don't convert for Firefox */
    if (!_cppPlugin->runningWithinFirefox)
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] handleMouseUp:[[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage browserRootLayer] convertPoint:CGPointMake(point.x, point.y) toLayer:[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer]]];
    else
        [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] handleMouseUp:CGPointMake(point.x, point.y)];
1044

1045 1046
    if ([(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] != nil) {
        if ([[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseButtonUp:)]) {
1047
            if (eventType == NSLeftMouseUp)
1048
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonUp:0];
1049
            else if (eventType == NSRightMouseUp)
1050
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonUp:1];
1051
            else
1052
                [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseButtonUp:2];
1053 1054 1055
        }
    }

1056 1057 1058 1059 1060 1061 1062
    [super mouseUp: theEvent];
}

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

1063
    [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer] handleMouseDragged:[[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage browserRootLayer] convertPoint:CGPointMake(point.x, point.y) toLayer:[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage controllerLayer]]];
1064 1065 1066 1067 1068 1069 1070

    [super mouseDragged: theEvent];
}

- (void)mouseMoved:(NSEvent *)theEvent
{
    self.cppPlugin->set_toolbar_visible(true);
1071
    _timeSinceLastMouseMove = [NSDate timeIntervalSinceReferenceDate];
1072
    [self performSelector:@selector(_hideToolbar) withObject:nil afterDelay: 4.1];
1073

1074 1075
    if ([(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] != nil) {
        if ([[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] respondsToSelector:@selector(mouseMovedToX:Y:)]) {
1076
            NSPoint ml = [theEvent locationInWindow];
1077
            [[(VLCPerInstanceStorage *)_cppPlugin->_perInstanceStorage playbackLayer] mouseMovedToX:ml.x Y:([self.window frame].size.height - ml.y)];
1078 1079 1080
        }
    }

1081 1082 1083
    [super mouseMoved: theEvent];
}

1084 1085 1086 1087 1088 1089
- (void)_hideToolbar
{
    if ([NSDate timeIntervalSinceReferenceDate] - _timeSinceLastMouseMove >= 4)
        [self hideToolbar];
}

1090 1091
- (void)hideToolbar
{
1092 1093
    self.cppPlugin->set_toolbar_visible(false);
    [NSCursor setHiddenUntilMouseMoves:YES];
1094 1095
}

1096 1097
@end

1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
#pragma mark - helpers

CGImageRef createImageNamed(NSString *name)
{
    CFURLRef url = CFBundleCopyResourceURL(CFBundleGetBundleWithIdentifier(CFSTR("org.videolan.vlc-npapi-plugin")), (CFStringRef)name, CFSTR("png"), NULL);

    if (!url)
        return NULL;

    CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL);
    if (!imageSource)
        return NULL;

    CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    CFRelease(imageSource);

    return image;
}