[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