[vlc-devel] [PATCH] vout: macosx: add standalone window provider
Marvin Scholz
epirat07 at gmail.com
Fri Oct 23 11:11:45 CEST 2020
This has a incredibly high module priority that should be change
once the embedded window provider is fixed.
Additionally some things do not yet work:
- Fullscreen monitor selection
- Leaving fullscreen from a different space causes the window to get
hidden
- The interface does not seem to properly respect the fullscreen state,
so the
fullscreen button in the interface can get out of sync with the actual
fullscreen state.
- Wallpaper and always in front mode not implemented yet
- Mouse/keyboard handling currently still done in vout, should be moved
to window
This is the first step for proper vout/window separation, next a revised
layer based vout
will follow with the goal of replacing the now extremely broken macosx
vout.
On 23 Oct 2020, at 10:35, Marvin Scholz wrote:
> ---
> 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