[vlc-devel] [PATCH] vout: macosx: add standalone window provider

Marvin Scholz epirat07 at gmail.com
Fri Oct 23 10:35:45 CEST 2020


---
 modules/video_output/Makefile.am     |   8 +-
 modules/video_output/window_macosx.m | 480 +++++++++++++++++++++++++++
 2 files changed, 487 insertions(+), 1 deletion(-)
 create mode 100644 modules/video_output/window_macosx.m

diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
index c42fdc474a6..43464d8f8b1 100644
--- a/modules/video_output/Makefile.am
+++ b/modules/video_output/Makefile.am
@@ -27,6 +27,12 @@ libglinterop_cvpx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 	-Wl,-framework,Foundation,-framework,CoreVideo
 
 if HAVE_OSX
+libwindow_macosx_plugin_la_SOURCES = video_output/window_macosx.m
+libwindow_macosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) \
+	-Wl,-framework,Cocoa
+libwindow_macosx_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) \
+	-fobjc-arc -fobjc-exceptions
+
 libvout_macosx_plugin_la_SOURCES = video_output/macosx.m
 libvout_macosx_plugin_la_CFLAGS = $(AM_CFLAGS) -DHAVE_GL_CORE_SYMBOLS
 libvout_macosx_plugin_la_LIBADD = libvlc_opengl.la
@@ -41,7 +47,7 @@ libcaopengllayer_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 
 libglinterop_cvpx_plugin_la_LDFLAGS += -Wl,-framework,IOSurface,-framework,OpenGL
 vout_LTLIBRARIES += libvout_macosx_plugin.la libcaopengllayer_plugin.la \
-	libglinterop_cvpx_plugin.la
+	libglinterop_cvpx_plugin.la libwindow_macosx_plugin.la
 endif
 if HAVE_IOS
 libglinterop_cvpx_plugin_la_CFLAGS = $(AM_CFLAGS) -DUSE_OPENGL_ES2
diff --git a/modules/video_output/window_macosx.m b/modules/video_output/window_macosx.m
new file mode 100644
index 00000000000..6a242f30df8
--- /dev/null
+++ b/modules/video_output/window_macosx.m
@@ -0,0 +1,480 @@
+/**
+ * @file window_macosx.m
+ * @brief macOS Window and View output provider
+ */
+
+/* Copyright (C) 2020 VLC authors and VideoLAN
+ *
+ * Authors: Marvin Scholz <epirat 07 at gmail dot com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#import <Cocoa/Cocoa.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_window.h>
+
+#define VLC_ASSERT_MAINTHREAD NSAssert([[NSThread currentThread] isMainThread], \
+    @"Must be called from the main thread!")
+
+/**
+ * Style mask for a decorated window
+ */
+static const NSWindowStyleMask decoratedWindowStyleMask =
+    NSWindowStyleMaskTitled
+    | NSWindowStyleMaskClosable
+    | NSWindowStyleMaskMiniaturizable
+    | NSWindowStyleMaskResizable;
+
+/**
+ * Style mask for a non-decorated window
+ */
+static const NSWindowStyleMask undecoratedWindowStyleMask =
+    NSWindowStyleMaskBorderless
+    | NSWindowStyleMaskResizable;
+
+
+#pragma mark -
+#pragma mark Obj-C Interfaces
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Video output window class
+ *
+ * Custom NSWindow subclass, mostly to overwrite that the window
+ * can become the key window even if its using the borderless
+ * (undecorated) style.
+ */
+ at interface VLCVideoWindow : NSWindow
+ at end
+
+
+/**
+ * Video view class
+ *
+ * Custom NSWindow subclass, used to track resizes so that
+ * the core cen be notified about the new sizes in a timely manner.
+ */
+ at interface VLCVideoWindowContentView : NSView {
+    @private
+    vout_window_t*     vlc_vout_window;
+}
+
+- (instancetype)initWithVLCVoutWindow:(vout_window_t *)vout_wnd;
+ at end
+
+
+/**
+ * Video output window controller class
+ *
+ * Controller for the VLC standalone video window (independent of the interface)
+ *
+ * Implements all interactions between the display module and the NSWindow
+ * class, except for resizes (which is handled by VLCVideoWindowContentView).
+ */
+ at interface VLCVideoStandaloneWindowController : NSWindowController <NSWindowDelegate> {
+    @private
+    vout_window_t*     vlc_vout_window;
+}
+
+- (instancetype)initWithVLCVoutWindow:(vout_window_t *)vout_wnd;
+- (void)showWindowWithConfig:(const vout_window_cfg_t *restrict)config;
+
+/* Methods called by the callbacks to change properties of the Window */
+- (void)setWindowDecorated:(BOOL)decorated;
+- (void)setWindowFullscreen:(BOOL)fullscreen;
+
+ at end
+
+
+#pragma mark -
+#pragma mark Obj-C Implementations
+
+ at implementation VLCVideoStandaloneWindowController
+
+/**
+ * Initializes the window controller with the content view in the
+ * given vout_window_t.
+ */
+- (instancetype)initWithVLCVoutWindow:(vout_window_t *)vout_wnd
+{
+    VLC_ASSERT_MAINTHREAD;
+
+    NSWindow *window = [[NSWindow alloc] initWithContentRect:NSZeroRect
+                                                   styleMask:decoratedWindowStyleMask
+                                                     backing:NSBackingStoreBuffered
+                                                       defer:YES];
+
+    self = [super initWithWindow:window];
+    if (self) {
+        vlc_vout_window = vout_wnd;
+
+        // Set the initial vout title
+        [window setTitle:[NSString stringWithUTF8String:VOUT_TITLE " (VLC Video Output)"]];
+
+        // The content always changes during live resize
+        [window setPreservesContentDuringLiveResize:NO];
+
+        // Do not release on close (we might want to re-open the window later)
+        [window setReleasedWhenClosed:NO];
+
+        // Hint that the window should become a primary fullscreen window
+        [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+
+        // Create and set custom content view for the window
+        VLCVideoWindowContentView *view =
+            [[VLCVideoWindowContentView alloc] initWithVLCVoutWindow:vout_wnd];
+        [window setContentView:view];
+
+        [window setDelegate:self];
+
+        // Position the window in the center
+        [window center];
+
+        if (view == nil)
+            return nil;
+
+        [self setWindowFrameAutosaveName:@"VLCVideoStandaloneWindow"];
+
+        vout_wnd->type = VOUT_WINDOW_TYPE_NSOBJECT;
+        vout_wnd->handle.nsobject = (__bridge void*)view;
+    }
+
+    return self;
+}
+
+/**
+ * Applies the given config to the window and shows it.
+ */
+- (void)showWindowWithConfig:(const vout_window_cfg_t *restrict)config
+{
+    VLC_ASSERT_MAINTHREAD;
+
+    // Convert from backing to window coordinates
+    NSRect backingRect = NSMakeRect(0, 0, config->width, config->height);
+    NSRect windowRect = [self.window convertRectFromBacking:backingRect];
+    [self.window setContentSize:windowRect.size];
+
+    // Set decoration
+    [self setWindowDecorated:config->is_decorated];
+
+    // This should always be called last, to ensure we only show the
+    // window once its fully configured. Else there could be visible
+    // changes or animations when the config is applied.
+    [self showWindow:nil];
+    [self.window makeKeyAndOrderFront:nil];
+}
+
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+    vout_window_ReportClose(vlc_vout_window);
+    return YES;
+}
+
+#pragma mark Helper methods
+
+- (BOOL)isWindowFullscreen
+{
+    return ((self.window.styleMask & NSFullScreenWindowMask) == NSFullScreenWindowMask);
+}
+
+#pragma mark Module interactions
+
+- (void)setWindowDecorated:(BOOL)decorated
+{
+    NSWindowStyleMask mask =
+        (decorated) ? decoratedWindowStyleMask : undecoratedWindowStyleMask;
+
+    [self.window setStyleMask:mask];
+}
+
+- (void)setWindowFullscreen:(BOOL)fullscreen
+{
+    if (!!fullscreen == !![self isWindowFullscreen]) {
+        // Nothing to do, just report the state to core
+        if (fullscreen) {
+            vout_window_ReportFullscreen(vlc_vout_window, NULL);
+        } else {
+            vout_window_ReportWindowed(vlc_vout_window);
+        }
+        return;
+    }
+
+    [self.window toggleFullScreen:nil];
+}
+
+#pragma mark Window delegate
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+    vout_window_ReportFullscreen(vlc_vout_window, NULL);
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+    vout_window_ReportWindowed(vlc_vout_window);
+}
+
+ at end
+
+
+
+ at implementation VLCVideoWindowContentView
+
+- (instancetype)initWithVLCVoutWindow:(vout_window_t *)vout_wnd
+{
+    self = [super init];
+    if (self) {
+        NSAssert(vout_wnd != NULL, @"Invalid vout_window_t passed.");
+        vlc_vout_window = vout_wnd;
+    }
+    return self;
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+    [[NSColor blackColor] setFill];
+    NSRectFill(dirtyRect);
+}
+
+/**
+ * Report the view size in the backing size dimensions to VLC core
+ */
+- (void)reportBackingSize
+{
+    NSRect bounds = [self convertRectToBacking:self.bounds];
+    vout_window_ReportSize(vlc_vout_window, NSWidth(bounds), NSHeight(bounds));
+}
+
+/**
+ * Handle view size changes
+ */
+- (void)resizeSubviewsWithOldSize:(NSSize)oldSize
+{
+    [self reportBackingSize];
+    [super resizeSubviewsWithOldSize:oldSize];
+}
+
+/**
+ * Handle view backing property changes
+ */
+- (void)viewDidChangeBackingProperties
+{
+    // When the view backing size changes, it means the view effectively
+    // resizes from VLC core perspective, as it operates on the real
+    // backing dimensions, not the view point size.
+    [self reportBackingSize];
+    [super viewDidChangeBackingProperties];
+}
+
+ at end
+
+ at implementation VLCVideoWindow
+
+- (BOOL)canBecomeKeyWindow
+{
+    // A window with NSWindowStyleMaskBorderless can usually not become key
+    // window, unless we return YES here.
+    return YES;
+}
+
+ at end
+
+NS_ASSUME_NONNULL_END
+
+
+#pragma mark -
+#pragma mark VLC module
+
+typedef struct
+{
+    VLCVideoStandaloneWindowController *vc;
+} vout_window_sys_t;
+
+/* Enable Window
+ */
+static int WindowEnable(vout_window_t *wnd, const vout_window_cfg_t *restrict cfg)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+
+        VLCVideoStandaloneWindowController *vc = sys->vc;
+        dispatch_sync(dispatch_get_main_queue(), ^{
+            [vc showWindowWithConfig:cfg];
+        });
+    }
+
+    return VLC_SUCCESS;
+}
+
+/* Request to close the window */
+static void WindowDisable(vout_window_t *wnd)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [weakVc close];
+        });
+    }
+}
+
+/* Request to resize the window */
+static void WindowResize(vout_window_t *wnd, unsigned width, unsigned height)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            VLCVideoStandaloneWindowController *vc = weakVc;
+            // Convert from backing to window coordinates
+            NSRect backingRect = NSMakeRect(0, 0, width, height);
+            NSRect windowRect = [vc.window convertRectFromBacking:backingRect];
+            [vc.window setContentSize:windowRect.size];
+
+            // Size is reported by resizeSubviewsWithOldSize:, do not
+            // report it here, else it would get reported twice.
+        });
+    }
+}
+
+/* Request to enable/disable Window decorations */
+static void SetDecoration(vout_window_t *wnd, bool decorated)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [weakVc setWindowDecorated:decorated];
+        });
+    }
+}
+
+/* Request to enter fullscreen */
+static void WindowSetFullscreen(vout_window_t *wnd, const char *idstr)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [weakVc setWindowFullscreen:YES];
+        });
+    }
+}
+
+/* Request to exit fullscreen */
+static void WindowUnsetFullscreen(vout_window_t *wnd)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [weakVc setWindowFullscreen:NO];
+        });
+    }
+}
+
+static void WindowSetTitle(struct vout_window_t *wnd, const char *title)
+{
+    vout_window_sys_t *sys = wnd->sys;
+    @autoreleasepool {
+        __weak VLCVideoStandaloneWindowController *weakVc = sys->vc;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [weakVc.window setTitle:[NSString stringWithUTF8String:title]];
+        });
+    }
+}
+
+/*
+ * Module destruction
+ */
+void Close(vout_window_t *wnd)
+{
+    vout_window_sys_t *sys = wnd->sys;
+
+    // ARC can not know when to release an object in a heap-allocated
+    // struct, so we need to explicitly set it to nil here.
+    sys->vc = nil;
+}
+
+/*
+ * Callbacks
+ */
+static const struct vout_window_operations ops = {
+    .enable = WindowEnable,
+    .disable = WindowDisable,
+    .resize = WindowResize,
+    .set_state = NULL,
+    .unset_fullscreen = WindowUnsetFullscreen,
+    .set_fullscreen = WindowSetFullscreen,
+    .set_title = WindowSetTitle,
+    .destroy = Close,
+};
+
+/*
+ * Module initialization
+ */
+int Open(vout_window_t *wnd)
+{
+    @autoreleasepool {
+        msg_Info(wnd, "using the macOS new video output window module");
+
+        // Check if there is an NSApplication, needed for the connection
+        // to the Window Server so we can use NSWindows, NSViews, etc.
+        if (NSApp == nil) {
+            msg_Err(wnd, "cannot create video output window without NSApplication");
+            return VLC_EGENERIC;
+        }
+
+        vout_window_sys_t *sys = vlc_obj_calloc(VLC_OBJECT(wnd), 1, sizeof(*sys));
+        if (unlikely(sys == NULL))
+            return VLC_ENOMEM;
+
+        __block VLCVideoStandaloneWindowController *vc;
+        dispatch_sync(dispatch_get_main_queue(), ^{
+            vc = [[VLCVideoStandaloneWindowController alloc] initWithVLCVoutWindow:wnd];
+        });
+        if (unlikely(vc == nil))
+            return VLC_ENOMEM;
+        sys->vc = vc;
+
+        wnd->ops = &ops;
+        wnd->sys = sys;
+
+        return VLC_SUCCESS;
+    }
+}
+
+/*
+ * Module declaration
+ */
+vlc_module_begin()
+    set_description("macOS Video Output Window")
+    set_capability("vout window", 1000)
+    set_callback(Open)
+vlc_module_end()
-- 
2.24.3 (Apple Git-128)



More information about the vlc-devel mailing list