aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Akemi <der.richter@gmx.de>2018-02-12 12:28:19 +0100
committerGravatar Kevin Mitchell <kevmitch@gmail.com>2018-02-12 04:49:15 -0800
commitc5e4538bc4b63f02bf89f62aa25a37b9eb0c0316 (patch)
tree443f47ce1d584bb5fbf4fa30918e7c17cb800016
parent235eb60671c899d55b1174043940763b250fa3b8 (diff)
cocoa-cb: initial implementation via opengl-cb API
this is meant to replace the old and not properly working vo_gpu/opengl cocoa backend in the future. the problems are various shortcomings of Apple's opengl implementation and buggy behaviour in certain circumstances that couldn't be properly worked around. there are also certain regressions on newer macOS versions from 10.11 onwards. - awful opengl performance with a none layer backed context - huge amount of dropped frames with an early context flush - flickering of system elements like the dock or volume indicator - double buffering not properly working with a none layer backed context - bad performance in fullscreen because of system optimisations all the problems were caused by using a normal opengl context, that seems somewhat abandoned by apple, and are fixed by using a layer backed opengl context instead. problems that couldn't be fixed could be properly worked around. this has all features our old backend has sans the wid embedding, the possibility to disable the automatic GPU switching and taking screenshots of the window content. the first was deemed unnecessary by me for now, since i just use the libmpv API that others can use anyway. second is technically not possible atm because we have to pre-allocate our opengl context at a time the config isn't read yet, so we can't get the needed property. third one is a bit tricky because of deadlocking and it needed to be in sync, hopefully i can work around that in the future. this also has at least one additional feature or eye-candy. a properly working fullscreen animation with the native fs. also since this is a direct port of the old backend of the parts that could be used, though with adaptions and improvements, this looks a lot cleaner and easier to understand. some credit goes to @pigoz for the initial swift build support which i could improve upon. Fixes: #5478, #5393, #5152, #5151, #4615, #4476, #3978, #3746, #3739, #2392, #2217
-rw-r--r--DOCS/interface-changes.rst2
-rw-r--r--DOCS/man/options.rst2
-rw-r--r--DOCS/man/vo.rst4
-rw-r--r--osdep/macOS_mpv_helper.swift255
-rw-r--r--osdep/macOS_swift_bridge.h54
-rw-r--r--osdep/macosx_application.m80
-rw-r--r--osdep/macosx_application_objc.h6
-rw-r--r--osdep/macosx_events.m21
-rw-r--r--osdep/macosx_menubar.m18
-rw-r--r--player/client.c24
-rw-r--r--player/client.h10
-rw-r--r--video/out/cocoa-cb/events_view.swift266
-rw-r--r--video/out/cocoa-cb/video_layer.swift210
-rw-r--r--video/out/cocoa-cb/window.swift456
-rw-r--r--video/out/cocoa_cb_common.swift485
-rw-r--r--video/out/opengl/context_cocoa.c2
-rw-r--r--video/out/vo.h5
-rw-r--r--video/out/vo_opengl_cb.c88
-rw-r--r--waftools/checks/custom.py14
-rw-r--r--waftools/detections/compiler_swift.py71
-rw-r--r--waftools/generators/sources.py11
-rw-r--r--wscript15
-rw-r--r--wscript_build.py37
23 files changed, 2054 insertions, 82 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index da47ef6ba8..ba1a25c273 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -70,6 +70,8 @@ Interface changes
pad must be connected either to another filter, or to a video/audio track
or video/audio output). If they are disconnected at runtime, the stream
will probably stall.
+ - deprecate the OpenGL cocoa backend, option choice --gpu-context=cocoa
+ when used with --gpu-api=opengl (use --vo=opengl-cb)
--- mpv 0.28.0 ---
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
conventions followed by other hardware video decoding APIs
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 9f0c672919..c12453ae5f 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -4876,7 +4876,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
auto
auto-select (default)
cocoa
- Cocoa/OS X
+ Cocoa/OS X (deprecated, use --vo=opengl-cb instead)
win
Win32/WGL
winvk
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 20e406dd4e..d5a4ef9b26 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -429,7 +429,9 @@ Available video output drivers are:
Specify the directory to save the image files to (default: ``./``).
``opengl-cb``
- For use with libmpv direct OpenGL embedding; useless in any other contexts.
+ For use with libmpv direct OpenGL embedding. As a special case, on OS X it
+ is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
+ other contexts.
(See ``<mpv/opengl_cb.h>``.)
This also supports many of the options the ``gpu`` VO has.
diff --git a/osdep/macOS_mpv_helper.swift b/osdep/macOS_mpv_helper.swift
new file mode 100644
index 0000000000..39be9cc8c7
--- /dev/null
+++ b/osdep/macOS_mpv_helper.swift
@@ -0,0 +1,255 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Cocoa
+import OpenGL.GL
+import OpenGL.GL3
+
+class MPVHelper: NSObject {
+
+ var mpvHandle: OpaquePointer?
+ var mpvGLCBContext: OpaquePointer?
+ var mpvLog: OpaquePointer?
+ var inputContext: OpaquePointer?
+ var mpctx: UnsafeMutablePointer<MPContext>?
+
+ init(_ mpv: OpaquePointer) {
+ super.init()
+ mpvHandle = mpv
+ mpvLog = mp_log_new(UnsafeMutablePointer<MPContext>(mpvHandle),
+ mp_client_get_log(mpvHandle), "cocoacb")
+ mpctx = UnsafeMutablePointer<MPContext>(mp_client_get_core(mpvHandle))
+ inputContext = mpctx!.pointee.input
+
+ mpv_observe_property(mpvHandle, 0, "ontop", MPV_FORMAT_FLAG)
+ mpv_observe_property(mpvHandle, 0, "border", MPV_FORMAT_FLAG)
+ mpv_observe_property(mpvHandle, 0, "keepaspect-window", MPV_FORMAT_FLAG)
+ }
+
+ func setGLCB() {
+ if mpvHandle == nil {
+ sendError("No mpv handle available.")
+ exit(1)
+ }
+ mpvGLCBContext = OpaquePointer(mp_get_sub_api2(mpvHandle, MPV_SUB_API_OPENGL_CB, false))
+ if mpvGLCBContext == nil {
+ sendError("libmpv does not have the opengl-cb sub-API.")
+ exit(1)
+ }
+ }
+
+ func initGLCB() {
+ if mpvGLCBContext == nil {
+ setGLCB()
+ }
+ if mpv_opengl_cb_init_gl(mpvGLCBContext, nil, getProcAddress, nil) < 0 {
+ sendError("GL init has failed.")
+ exit(1)
+ }
+ }
+
+ let getProcAddress: mpv_opengl_cb_get_proc_address_fn = {
+ (ctx: UnsafeMutableRawPointer?, name: UnsafePointer<Int8>?) -> UnsafeMutableRawPointer? in
+ let symbol: CFString = CFStringCreateWithCString(
+ kCFAllocatorDefault, name, kCFStringEncodingASCII)
+ let indentifier = CFBundleGetBundleWithIdentifier("com.apple.opengl" as CFString)
+ let addr = CFBundleGetFunctionPointerForName(indentifier, symbol)
+
+ if symbol as String == "glFlush" {
+ return glDummyPtr()
+ }
+
+ return addr
+ }
+
+ func setGLCBUpdateCallback(_ callback: @escaping mpv_opengl_cb_update_fn, context object: AnyObject) {
+ if mpvGLCBContext == nil {
+ sendWarning("Init mpv opengl-cb first.")
+ } else {
+ mpv_opengl_cb_set_update_callback(mpvGLCBContext, callback, MPVHelper.bridge(obj: object))
+ }
+ }
+
+ func setGLCBControlCallback(_ callback: @escaping mpv_opengl_cb_control_fn, context object: AnyObject) {
+ if mpvGLCBContext == nil {
+ sendWarning("Init mpv opengl-cb first.")
+ } else {
+ mp_client_set_control_callback(mpvGLCBContext, callback, MPVHelper.bridge(obj: object))
+ }
+ }
+
+ func reportGLCBFlip() {
+ if mpvGLCBContext == nil { return }
+ mpv_opengl_cb_report_flip(mpvGLCBContext, 0)
+ }
+
+ func drawGLCB(_ surface: NSSize) {
+ if mpvGLCBContext != nil {
+ var i: GLint = 0
+ glGetIntegerv(GLenum(GL_DRAW_FRAMEBUFFER_BINDING), &i)
+
+ mpv_opengl_cb_draw(mpvGLCBContext, i, Int32(surface.width), Int32(-surface.height))
+ } else {
+ glClearColor(0, 0, 0, 1)
+ glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
+ }
+ }
+
+ func setGLCBICCProfile(_ profile: NSColorSpace) {
+ if mpvGLCBContext == nil { return }
+ var iccData = profile.iccProfileData
+ iccData!.withUnsafeMutableBytes { (u8Ptr: UnsafeMutablePointer<UInt8>) in
+ let icc = bstrdup(nil, bstr(start: u8Ptr, len: iccData!.count))
+ mp_client_set_icc_profile(mpvGLCBContext, icc)
+ }
+ }
+
+ func setGLCBLux(_ lux: Int) {
+ if mpvGLCBContext == nil { return }
+ mp_client_set_ambient_lux(mpvGLCBContext, Int32(lux))
+ }
+
+ func command(_ cmd: String) {
+ if mpvHandle == nil { return }
+ mpv_command_string(mpvHandle, cmd)
+ }
+
+ func commandAsync(_ cmd: [String?], id: UInt64 = 1) {
+ if mpvHandle == nil { return }
+ var mCmd = cmd
+ mCmd.append(nil)
+ var cargs = mCmd.map { $0.flatMap { UnsafePointer<Int8>(strdup($0)) } }
+ mpv_command_async(mpvHandle, id, &cargs)
+ for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }
+ }
+
+ func getBoolProperty(_ name: String) -> Bool {
+ if mpvHandle == nil { return false }
+ var value = Int32()
+ mpv_get_property(mpvHandle, name, MPV_FORMAT_FLAG, &value)
+ return value > 0
+ }
+
+ func getIntProperty(_ name: String) -> Int {
+ if mpvHandle == nil { return 0 }
+ var value = Int64()
+ mpv_get_property(mpvHandle, name, MPV_FORMAT_INT64, &value)
+ return Int(value)
+ }
+
+ func getStringProperty(_ name: String) -> String? {
+ if mpvHandle == nil { return nil }
+ let value = mpv_get_property_string(mpvHandle, name)
+ let str = value == nil ? nil : String(cString: value!)
+ mpv_free(value)
+ return str
+ }
+
+ func canBeDraggedAt(_ pos: NSPoint) -> Bool {
+ if inputContext == nil { return false }
+ let canDrag = !mp_input_test_dragging(inputContext!, Int32(pos.x), Int32(pos.y))
+ return canDrag
+ }
+
+ func setMousePosition(_ pos: NSPoint) {
+ if inputContext == nil { return }
+ mp_input_set_mouse_pos(inputContext!, Int32(pos.x), Int32(pos.y))
+ }
+
+ func putAxis(_ mpkey: Int32, delta: Double) {
+ if inputContext == nil { return }
+ mp_input_put_wheel(inputContext!, mpkey, delta)
+ }
+
+ func sendVerbose(_ msg: String) {
+ send(message: msg, type: MSGL_V)
+ }
+
+ func sendInfo(_ msg: String) {
+ send(message: msg, type: MSGL_INFO)
+ }
+
+ func sendWarning(_ msg: String) {
+ send(message: msg, type: MSGL_WARN)
+ }
+
+ func sendError(_ msg: String) {
+ send(message: msg, type: MSGL_ERR)
+ }
+
+ func send(message msg: String, type t: Int) {
+ if mpvLog == nil {
+ sendFallback(message: msg, type: t)
+ } else {
+ let args: [CVarArg] = [ (msg as NSString).utf8String! ]
+ mp_msg_va(mpvLog, Int32(t), "%s\n", getVaList(args))
+ }
+ }
+
+ func sendFallback(message msg: String, type t: Int) {
+ var level = "\u{001B}"
+ switch t {
+ case MSGL_V:
+ level += "[0;30m[VERBOSE]"
+ case MSGL_INFO:
+ level += "[0;30m[INFO]"
+ case MSGL_WARN:
+ level += "[0;33m"
+ case MSGL_ERR:
+ level += "[0;31m"
+ default:
+ level += "[0;30m"
+ }
+
+ print("\(level)[osx/cocoacb] \(msg)\u{001B}[0;30m")
+ }
+
+ func deinitGLCB() {
+ mpv_opengl_cb_set_update_callback(mpvGLCBContext, nil, nil)
+ mp_client_set_control_callback(mpvGLCBContext, nil, nil)
+ mpv_opengl_cb_uninit_gl(mpvGLCBContext)
+ mpvGLCBContext = nil
+ }
+
+ func deinitMPV() {
+ mpvHandle = nil
+ mpvLog = nil
+ inputContext = nil
+ mpctx = nil
+ }
+
+ // (__bridge void*)
+ class func bridge<T: AnyObject>(obj: T) -> UnsafeMutableRawPointer {
+ return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque())
+ }
+
+ // (__bridge T*)
+ class func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T {
+ return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
+ }
+
+ // *(char **) MPV_FORMAT_STRING on mpv_event_property
+ class func mpvStringArrayToString(_ obj: UnsafeMutableRawPointer) -> String? {
+ let cstr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>(OpaquePointer(obj))
+ return String(cString: cstr[0])
+ }
+
+ // MPV_FORMAT_FLAG
+ class func mpvFlagToBool(_ obj: UnsafeMutableRawPointer) -> Bool? {
+ return UnsafePointer<Bool>(OpaquePointer(obj))?.pointee
+ }
+}
diff --git a/osdep/macOS_swift_bridge.h b/osdep/macOS_swift_bridge.h
new file mode 100644
index 0000000000..d662dbcf4e
--- /dev/null
+++ b/osdep/macOS_swift_bridge.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// including IOKit here again doesn't make sense, but otherwise the swift
+// compiler doesn't include the needed header in our generated header file
+#import <IOKit/pwr_mgt/IOPMLib.h>
+
+#include "player/client.h"
+#include "libmpv/opengl_cb.h"
+
+#include "player/core.h"
+#include "input/input.h"
+#include "video/out/win_state.h"
+
+#include "osdep/macosx_application_objc.h"
+#include "osdep/macosx_events_objc.h"
+
+
+// complex macros won't get imported to Swift so we have to reassign them
+static int SWIFT_MBTN_LEFT = MP_MBTN_LEFT;
+static int SWIFT_MBTN_MID = MP_MBTN_MID;
+static int SWIFT_MBTN_RIGHT = MP_MBTN_RIGHT;
+static int SWIFT_WHEEL_UP = MP_WHEEL_UP;
+static int SWIFT_WHEEL_DOWN = MP_WHEEL_DOWN;
+static int SWIFT_WHEEL_LEFT = MP_WHEEL_LEFT;
+static int SWIFT_WHEEL_RIGHT = MP_WHEEL_RIGHT;
+static int SWIFT_MBTN_BACK = MP_MBTN_BACK;
+static int SWIFT_MBTN_FORWARD = MP_MBTN_FORWARD;
+static int SWIFT_MBTN9 = MP_MBTN9;
+
+static int SWIFT_KEY_CLOSE_WIN = MP_KEY_CLOSE_WIN;
+static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE;
+static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER;
+static int SWIFT_KEY_STATE_DOWN = MP_KEY_STATE_DOWN;
+static int SWIFT_KEY_STATE_UP = MP_KEY_STATE_UP;
+
+// dummy function to override glFlush()
+static void glDummy() {}
+static void *glDummyPtr(void) __attribute__((unused));
+static void *glDummyPtr() { return &glDummy; }
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index d070401621..a65910ec9e 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -33,6 +33,9 @@
#if HAVE_MACOS_TOUCHBAR
#import "osdep/macosx_touchbar.h"
#endif
+#if HAVE_MACOS_COCOA_CB
+#include "osdep/macOS_swift.h"
+#endif
#define MPV_PROTOCOL @"mpv://"
@@ -40,7 +43,13 @@
// running in libmpv mode, and cocoa_main() was never called.
static bool application_instantiated;
+struct playback_thread_ctx {
+ int *argc;
+ char ***argv;
+};
+
static pthread_t playback_thread_id;
+static struct playback_thread_ctx thread_ctx = {0};
@interface Application ()
{
@@ -60,9 +69,22 @@ static void terminate_cocoa_application(void)
[NSApp terminate:NSApp];
}
+static void *playback_thread(void *ctx_obj)
+{
+ mpthread_set_name("playback core (OSX)");
+ @autoreleasepool {
+ struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj;
+ int r = mpv_main(*ctx->argc, *ctx->argv);
+ terminate_cocoa_application();
+ // normally never reached - unless the cocoa mainloop hasn't started yet
+ exit(r);
+ }
+}
+
@implementation Application
@synthesize menuBar = _menu_bar;
@synthesize openCount = _open_count;
+@synthesize cocoaCB = _cocoa_cb;
- (void)sendEvent:(NSEvent *)event
{
@@ -96,6 +118,24 @@ static void terminate_cocoa_application(void)
[super dealloc];
}
+- (void)initMPVCore
+{
+ pthread_create(&playback_thread_id, NULL, playback_thread, &thread_ctx);
+ [[EventsResponder sharedInstance] waitForInputContext];
+}
+
+static const char macosx_icon[] =
+#include "osdep/macosx_icon.inc"
+;
+
+- (NSImage *)getMPVIcon
+{
+ NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
+ length:sizeof(macosx_icon)
+ freeWhenDone:NO];
+ return [[NSImage alloc] initWithData:icon_data];
+}
+
#if HAVE_MACOS_TOUCHBAR
- (NSTouchBar *)makeTouchBar
{
@@ -117,6 +157,16 @@ static void terminate_cocoa_application(void)
if ([self respondsToSelector:@selector(touchBar)])
[(TouchBar *)self.touchBar processEvent:event];
#endif
+ if (_cocoa_cb) {
+ [_cocoa_cb processEvent:event];
+ }
+}
+
+- (void)setMpvHandle:(struct mpv_handle *)ctx
+{
+ if (_cocoa_cb) {
+ [_cocoa_cb setMpvHandle:ctx];
+ }
}
- (void)queueCommand:(char *)cmd
@@ -182,11 +232,6 @@ static void terminate_cocoa_application(void)
}
@end
-struct playback_thread_ctx {
- int *argc;
- char ***argv;
-};
-
static void cocoa_run_runloop(void)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@@ -194,18 +239,6 @@ static void cocoa_run_runloop(void)
[pool drain];
}
-static void *playback_thread(void *ctx_obj)
-{
- mpthread_set_name("playback core (OSX)");
- @autoreleasepool {
- struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj;
- int r = mpv_main(*ctx->argc, *ctx->argv);
- terminate_cocoa_application();
- // normally never reached - unless the cocoa mainloop hasn't started yet
- exit(r);
- }
-}
-
void cocoa_register_menu_item_action(MPMenuKey key, void* action)
{
if (application_instantiated)
@@ -218,6 +251,10 @@ static void init_cocoa_application(bool regular)
[NSApp setDelegate:NSApp];
[NSApp setMenuBar:[[MenuBar alloc] init]];
+#if HAVE_MACOS_COCOA_CB
+ [NSApp setCocoaCB:[[CocoaCB alloc] init]];
+#endif
+
// Will be set to Regular from cocoa_common during UI creation so that we
// don't create an icon when playing audio only files.
[NSApp setActivationPolicy: regular ?
@@ -279,9 +316,8 @@ int cocoa_main(int argc, char *argv[])
application_instantiated = true;
[[EventsResponder sharedInstance] setIsApplication:YES];
- struct playback_thread_ctx ctx = {0};
- ctx.argc = &argc;
- ctx.argv = &argv;
+ thread_ctx.argc = &argc;
+ thread_ctx.argv = &argv;
if (bundle_started_from_finder(argv)) {
setup_bundle(&argc, argv);
@@ -294,8 +330,8 @@ int cocoa_main(int argc, char *argv[])
init_cocoa_application(false);
}
- pthread_create(&playback_thread_id, NULL, playback_thread, &ctx);
- [[EventsResponder sharedInstance] waitForInputContext];
+ if (![NSApp cocoaCB])
+ [NSApp initMPVCore];
cocoa_run_runloop();
// This should never be reached: cocoa_run_runloop blocks until the
diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h
index c12a8b8c64..22e6f7e525 100644
--- a/osdep/macosx_application_objc.h
+++ b/osdep/macosx_application_objc.h
@@ -19,15 +19,21 @@
#include "osdep/macosx_application.h"
#import "osdep/macosx_menubar_objc.h"
+@class CocoaCB;
struct mpv_event;
+struct mpv_handle;
@interface Application : NSApplication
+- (NSImage *)getMPVIcon;
- (void)processEvent:(struct mpv_event *)event;
- (void)queueCommand:(char *)cmd;
- (void)stopMPV:(char *)cmd;
- (void)openFiles:(NSArray *)filenames;
+- (void)setMpvHandle:(struct mpv_handle *)ctx;
+- (void)initMPVCore;
@property(nonatomic, retain) MenuBar *menuBar;
@property(nonatomic, assign) size_t openCount;
+@property(nonatomic, retain) CocoaCB *cocoaCB;
@end
diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m
index a23bd56278..fef7e95053 100644
--- a/osdep/macosx_events.m
+++ b/osdep/macosx_events.m
@@ -39,6 +39,10 @@
#include "config.h"
+#if HAVE_MACOS_COCOA_CB
+#include "osdep/macOS_swift.h"
+#endif
+
@interface EventsResponder ()
{
struct input_ctx *_inputContext;
@@ -304,7 +308,10 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
- (BOOL)setMpvHandle:(struct mpv_handle *)ctx
{
if (_is_application) {
- dispatch_sync(dispatch_get_main_queue(), ^{ _ctx = ctx; });
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ _ctx = ctx;
+ [NSApp setMpvHandle:ctx];
+ });
return YES;
} else {
mpv_detach_destroy(ctx);
@@ -326,17 +333,21 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
-(void)processEvent:(struct mpv_event *)event
{
+ if(_is_application) {
+ [NSApp processEvent:event];
+ }
+
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN: {
+ #if HAVE_MACOS_COCOA_CB
+ if ([(Application *)NSApp cocoaCB].isShuttingDown)
+ return;
+ #endif
mpv_detach_destroy(_ctx);
_ctx = nil;
break;
}
}
-
- if(_is_application) {
- [NSApp processEvent:event];
- }
}
- (void)startAppleRemote
diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m
index 92bd3fa991..931079a552 100644
--- a/osdep/macosx_menubar.m
+++ b/osdep/macosx_menubar.m
@@ -646,7 +646,7 @@
{
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
@"mpv", @"ApplicationName",
- [self getMPVIcon], @"ApplicationIcon",
+ [(Application *)NSApp getMPVIcon], @"ApplicationIcon",
[NSString stringWithUTF8String:mpv_copyright], @"Copyright",
[NSString stringWithUTF8String:mpv_version], @"ApplicationVersion",
nil];
@@ -709,7 +709,7 @@
[alert setMessageText:@"Open URL"];
[alert addButtonWithTitle:@"Ok"];
[alert addButtonWithTitle:@"Cancel"];
- [alert setIcon:[self getMPVIcon]];
+ [alert setIcon:[(Application *)NSApp getMPVIcon]];
NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
[input setPlaceholderString:@"URL"];
@@ -737,25 +737,13 @@
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}
-static const char macosx_icon[] =
-#include "osdep/macosx_icon.inc"
-;
-
-- (NSImage *)getMPVIcon
-{
- NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
- length:sizeof(macosx_icon)
- freeWhenDone:NO];
- return [[NSImage alloc] initWithData:icon_data];
-}
-
- (void)alertWithTitle:(NSString *)title andText:(NSString *)text
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:title];
[alert setInformativeText:text];
[alert addButtonWithTitle:@"Ok"];
- [alert setIcon:[self getMPVIcon]];
+ [alert setIcon:[(Application *)NSApp getMPVIcon]];
[alert runModal];
}
diff --git a/player/client.c b/player/client.c
index 618dbc4165..1df7e659d8 100644
--- a/player/client.c
+++ b/player/client.c
@@ -1726,6 +1726,17 @@ int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx)
{
return MPV_ERROR_NOT_IMPLEMENTED;
}
+void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx,
+ mpv_opengl_cb_control_fn callback,
+ void *callback_ctx)
+{
+}
+void mp_client_set_icc_profile(struct mpv_opengl_cb_context *ctx, bstr icc_data)
+{
+}
+void mp_client_set_ambient_lux(struct mpv_opengl_cb_context *ctx, int lux)
+{
+}
#endif
int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4])
@@ -1733,22 +1744,29 @@ int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4])
return mpv_opengl_cb_draw(ctx, fbo, vp[2], vp[3]);
}
-void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
+void *mp_get_sub_api2(mpv_handle *ctx, mpv_sub_api sub_api, bool lock)
{
if (!ctx->mpctx->initialized)
return NULL;
void *res = NULL;
- lock_core(ctx);
+ if (lock)
+ lock_core(ctx);
switch (sub_api) {
case MPV_SUB_API_OPENGL_CB:
res = opengl_cb_get_context(ctx);
break;
default:;
}
- unlock_core(ctx);
+ if (lock)
+ unlock_core(ctx);
return res;
}
+void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
+{
+ return mp_get_sub_api2(ctx, sub_api, true);
+}
+
struct mp_custom_protocol {
char *protocol;
void *user_data;
diff --git a/player/client.h b/player/client.h
index 67b287b67f..042934cde3 100644
--- a/player/client.h
+++ b/player/client.h
@@ -6,6 +6,7 @@
#include "libmpv/client.h"
#include "libmpv/stream_cb.h"
+#include "misc/bstr.h"
struct MPContext;
struct mpv_handle;
@@ -34,6 +35,7 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
struct mp_log *mp_client_get_log(struct mpv_handle *ctx);
struct MPContext *mp_client_get_core(struct mpv_handle *ctx);
struct MPContext *mp_client_api_get_core(struct mp_client_api *api);
+void *mp_get_sub_api2(mpv_handle *ctx, mpv_sub_api sub_api, bool lock);
// m_option.c
void *node_get_alloc(struct mpv_node *node);
@@ -49,4 +51,12 @@ void kill_video(struct mp_client_api *client_api);
bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn);
+typedef int (*mpv_opengl_cb_control_fn)(void *cb_ctx, int *events, uint32_t request, void *data);
+
+void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx,
+ mpv_opengl_cb_control_fn callback,
+ void *callback_ctx);
+void mp_client_set_icc_profile(struct mpv_opengl_cb_context *ctx, bstr icc_data);
+void mp_client_set_ambient_lux(struct mpv_opengl_cb_context *ctx, int lux);
+
#endif
diff --git a/video/out/cocoa-cb/events_view.swift b/video/out/cocoa-cb/events_view.swift
new file mode 100644
index 0000000000..4cb154c64a
--- /dev/null
+++ b/video/out/cocoa-cb/events_view.swift
@@ -0,0 +1,266 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Cocoa
+
+class EventsView: NSView {
+
+ weak var cocoaCB: CocoaCB!
+ var mpv: MPVHelper! {
+ get { return cocoaCB == nil ? nil : cocoaCB.mpv }
+ }
+
+ var tracker: NSTrackingArea?
+ var hasMouseDown: Bool = false
+
+ override var isFlipped: Bool { return true }
+ override var acceptsFirstResponder: Bool { return true }
+
+
+ init(frame frameRect: NSRect, cocoaCB ccb: CocoaCB) {
+ cocoaCB = ccb
+ super.init(frame: frameRect)
+ autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
+ wantsBestResolutionOpenGLSurface = true
+ register(forDraggedTypes: [NSFilenamesPboardType, NSURLPboardType])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func updateTrackingAreas() {
+ if tracker != nil {
+ removeTrackingArea(tracker!)
+ }
+
+ if mpv != nil && !mpv.getBoolProperty("input-cursor") {
+ return
+ }
+
+ tracker = NSTrackingArea(rect: self.bounds,
+ options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved, .enabledDuringMouseDrag],
+ owner: self, userInfo: nil)
+ addTrackingArea(tracker!)
+
+ if containsMouseLocation() {
+ cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0)
+ }
+ }
+
+ override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
+ guard let types = sender.draggingPasteboard().types else { return [] }
+ if types.contains(NSFilenamesPboardType) || types.contains(NSURLPboardType) {
+ return .copy
+ }
+ return []
+ }
+
+ override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
+ let pb = sender.draggingPasteboard()
+ guard let types = sender.draggingPasteboard().types else { return false }
+ if types.contains(NSFilenamesPboardType) {
+ if let files = pb.propertyList(forType: NSFilenamesPboardType) as? [Any] {
+ EventsResponder.sharedInstance().handleFilesArray(files)
+ return true
+ }
+ } else if types.contains(NSURLPboardType) {
+ if let url = pb.propertyList(forType: NSURLPboardType) as? [Any] {
+ EventsResponder.sharedInstance().handleFilesArray(url)
+ return true
+ }
+ }
+ return false
+ }
+
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+ return mpv.getBoolProperty("input-cursor")
+ }
+
+ override func becomeFirstResponder() -> Bool {
+ return mpv.getBoolProperty("input-cursor") ||
+ mpv.getBoolProperty("input-vo-keyboard")
+ }
+
+ override func resignFirstResponder() -> Bool {
+ return true
+ }
+
+ override func mouseEntered(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_ENTER, 0)
+ }
+ }
+
+ override func mouseExited(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0)
+ }
+ }
+
+ override func mouseMoved(with event: NSEvent) {
+ if mpv != nil && mpv.getBoolProperty("input-cursor") {
+ signalMouseMovement(event)
+ }
+ }
+
+ override func mouseDragged(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseMovement(event)
+ }
+ }
+
+ override func mouseDown(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseDown(event)
+ }
+ }
+
+ override func mouseUp(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseUp(event)
+ }
+ cocoaCB.window.isMoving = false
+ }
+
+ override func rightMouseDown(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseDown(event)
+ }
+ }
+
+ override func rightMouseUp(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseUp(event)
+ }
+ }
+
+ override func otherMouseDown(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseDown(event)
+ }
+ }
+
+ override func otherMouseUp(with event: NSEvent) {
+ if mpv.getBoolProperty("input-cursor") {
+ signalMouseUp(event)
+ }
+ }
+
+ func signalMouseDown(_ event: NSEvent) {
+ signalMouseEvent(event, SWIFT_KEY_STATE_DOWN)
+ if event.clickCount > 1 {
+ signalMouseEvent(event, SWIFT_KEY_STATE_UP)
+ }
+ }
+
+ func signalMouseUp(_ event: NSEvent) {
+ signalMouseEvent(event, SWIFT_KEY_STATE_UP)
+ }
+
+ func signalMouseEvent(_ event: NSEvent, _ state: Int32) {
+ hasMouseDown = state == SWIFT_KEY_STATE_DOWN
+ let mpkey = getMpvButton(event)
+ cocoa_put_key_with_modifiers((mpkey | state), Int32(event.modifierFlags.rawValue));
+ }
+
+ func signalMouseMovement(_ event: NSEvent) {
+ var point = convert(event.locationInWindow, from: nil)
+ point = convertToBacking(point)
+ point.y = -point.y
+
+ cocoaCB.window.updateMovableBackground(point)
+ if !cocoaCB.window.isMoving {
+ mpv.setMousePosition(point)
+ }
+ }
+
+ func preciseScroll(_ event: NSEvent) {
+ var delta: Double
+ var cmd: Int32
+
+ if fabs(event.deltaY) >= fabs(event.deltaX) {
+ delta = Double(event.deltaY) * 0.1;
+ cmd = delta > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN;
+ } else {
+ delta = Double(event.deltaX) * 0.1;
+ cmd = delta > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT;
+ }
+
+ mpv.putAxis(cmd, delta: fabs(delta))
+ }
+
+ override func scrollWheel(with event: NSEvent) {
+ if !mpv.getBoolProperty("input-cursor") {
+ return
+ }
+
+ if event.hasPreciseScrollingDeltas {
+ preciseScroll(event)
+ } else {
+ let modifiers = event.modifierFlags
+ let deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX
+ let deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY
+ var mpkey: Int32
+
+ if fabs(deltaY) >= fabs(deltaX) {
+ mpkey = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN;
+ } else {
+ mpkey = deltaX > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT;
+ }
+
+ cocoa_put_key_with_modifiers(mpkey, Int32(modifiers.rawValue))
+ }
+ }
+
+ func containsMouseLocation() -> Bool {
+ if cocoaCB == nil { return false }
+ var topMargin: CGFloat = 0.0
+ let menuBarHeight = NSApp.mainMenu!.menuBarHeight
+
+ if cocoaCB.window.isInFullscreen && (menuBarHeight > 0) {
+ let titleBar = NSWindow.frameRect(forContentRect: CGRect.zero, styleMask: .titled)
+ topMargin = titleBar.size.height + 1 + menuBarHeight
+ }
+
+ var vF = window!.screen!.frame
+ vF.size.height -= topMargin
+
+ let vFW = window!.convertFromScreen(vF)
+ let vFV = convert(vFW, from: nil)
+ let pt = convert(window!.mouseLocationOutsideOfEventStream, from: nil)
+
+ let clippedBounds = bounds.intersection(vFV)
+ return clippedBounds.contains(pt)
+ }
+
+ func canHideCursor() -> Bool {
+ return !hasMouseDown && containsMouseLocation() && window!.isKeyWindow
+ }
+
+ func getMpvButton(_ event: NSEvent) -> Int32 {
+ let buttonNumber = event.buttonNumber
+ switch (buttonNumber) {
+ case 0: return SWIFT_MBTN_LEFT;
+ case 1: return SWIFT_MBTN_RIGHT;
+ case 2: return SWIFT_MBTN_MID;
+ case 3: return SWIFT_MBTN_BACK;
+ case 4: return SWIFT_MBTN_FORWARD;
+ default: return SWIFT_MBTN9 + Int32(buttonNumber - 5);
+ }
+ }
+}
diff --git a/video/out/cocoa-cb/video_layer.swift b/video/out/cocoa-cb/video_layer.swift
new file mode 100644
index 0000000000..05f0894159
--- /dev/null
+++ b/video/out/cocoa-cb/video_layer.swift
@@ -0,0 +1,210 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Cocoa
+import OpenGL.GL
+import OpenGL.GL3
+
+class VideoLayer: CAOpenGLLayer {
+
+ weak var cocoaCB: CocoaCB!
+ var mpv: MPVHelper! {
+ get { return cocoaCB == nil ? nil : cocoaCB.mpv }
+ }
+
+ let videoLock = NSLock()
+ var hasVideo: Bool = false
+ var neededFlips: Int = 0
+ var cglContext: CGLContextObj? = nil
+
+ var canDrawOffScreen: Bool = false
+ var lastThread: Thread? = nil
+
+ var needsICCUpdate: Bool = false {
+ didSet {
+ if needsICCUpdate == true {
+ neededFlips += 1
+ }
+ }
+ }
+
+ let surfaceLock = NSLock()
+ var surfaceSize: NSSize?
+
+ var inLiveResize: Bool = false {
+ didSet {
+ if inLiveResize == false {
+ isAsynchronous = false
+ display()
+ } else {
+ surfaceLock.lock()
+ updateSurfaceSize()
+ surfaceLock.unlock()
+ isAsynchronous = true
+ }
+ }
+ }
+
+ init(cocoaCB ccb: CocoaCB) {
+ cocoaCB = ccb
+ super.init()
+ autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
+ backgroundColor = NSColor.black.cgColor
+ contentsScale = cocoaCB.window.backingScaleFactor
+ }
+
+ override init(layer: Any) {
+ let oldLayer = layer as! VideoLayer
+ cocoaCB = oldLayer.cocoaCB
+ super.init()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func setUpGLCB() {
+ self.mpv.initGLCB()
+ self.mpv.setGLCBUpdateCallback(self.updateCallback, context: self)
+ self.mpv.setGLCBControlCallback(self.cocoaCB.controlCallback, context: self.cocoaCB)
+ }
+
+ override func canDraw(inCGLContext ctx: CGLContextObj,
+ pixelFormat pf: CGLPixelFormatObj,
+ forLayerTime t: CFTimeInterval,
+ displayTime ts: UnsafePointer<CVTimeStamp>?) -> Bool {
+ return mpv != nil && cocoaCB.backendState == .init
+ }
+
+ override func draw(inCGLContext ctx: CGLContextObj,
+ pixelFormat pf: CGLPixelFormatObj,
+ forLayerTime t: CFTimeInterval,
+ displayTime ts: UnsafePointer<CVTimeStamp>?) {
+ neededFlips = 0
+ canDrawOffScreen = Thread.current == lastThread
+ lastThread = Thread.current
+ draw(ctx)
+ }
+
+ func draw(_ ctx: CGLContextObj) {
+ surfaceLock.lock()
+ if inLiveResize == false {
+ updateSurfaceSize()
+ }
+
+ mpv.drawGLCB(surfaceSize!)
+ surfaceLock.unlock()
+ CGLFlushDrawable(ctx)
+
+ if needsICCUpdate {
+ needsICCUpdate = false
+ cocoaCB.updateICCProfile()
+ }
+ }
+
+ func updateSurfaceSize() {
+ surfaceSize = bounds.size
+ surfaceSize!.width *= contentsScale
+ surfaceSize!.height *= contentsScale
+ }
+
+ override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj {
+ let glVersions: [CGLOpenGLProfile] = [
+ kCGLOGLPVersion_3_2_Core,
+ kCGLOGLPVersion_Legacy
+ ]
+
+ var pix: CGLPixelFormatObj?
+ var err: CGLError = CGLError(rawValue: 0)
+ var npix: GLint = 0
+
+ verLoop : for ver in glVersions {
+ var glAttributes: [CGLPixelFormatAttribute] = [
+ kCGLPFAOpenGLProfile, CGLPixelFormatAttribute(ver.rawValue),
+ kCGLPFAAccelerated,
+ kCGLPFADoubleBuffer,
+ kCGLPFABackingStore,
+ kCGLPFAAllowOfflineRenderers,
+ kCGLPFASupportsAutomaticGraphicsSwitching,
+ _CGLPixelFormatAttribute(rawValue: 0)
+ ]
+
+ for index in stride(from: glAttributes.count-2, through: 4, by: -1) {
+ err = CGLChoosePixelFormat(glAttributes, &pix, &npix)
+ if err == kCGLBadAttribute {
+ glAttributes.remove(at: index)
+ } else {
+ break verLoop
+ }
+ }
+ }
+
+ if err != kCGLNoError {
+ fatalError("Couldn't create CGL pixel format: \(CGLErrorString(err)) (\(err))")
+ }
+ return pix!
+ }
+
+ override func copyCGLContext(forPixelFormat pf: CGLPixelFormatObj) -> CGLContextObj {
+ let ctx = super.copyCGLContext(forPixelFormat: pf)
+ var i: GLint = 1
+ CGLSetParameter(ctx, kCGLCPSwapInterval, &i)
+ CGLSetCurrentContext(ctx)
+ cglContext = ctx
+
+ if let app = NSApp as? Application {
+ app.initMPVCore()
+ }
+ return ctx
+ }
+
+ let updateCallback: mpv_opengl_cb_update_fn = { (ctx) in
+ let layer: VideoLayer = MPVHelper.bridge(ptr: ctx!)
+ layer.neededFlips += 1
+ }
+
+ override func display() {
+ super.display()
+ if !isAsynchronous {
+ CATransaction.flush()
+ }
+ }
+
+ func setVideo(_ state: Bool) {
+ videoLock.lock()
+ hasVideo = state
+ neededFlips = 0
+ videoLock.unlock()
+ }
+
+ func reportFlip() {
+ mpv.reportGLCBFlip()
+ videoLock.lock()
+ if !isAsynchronous && neededFlips > 0 && hasVideo {
+ if !cocoaCB.window.occlusionState.contains(.visible) &&
+ neededFlips > 1 && canDrawOffScreen
+ {
+ draw(cglContext!)
+ display()
+ } else {
+ display()
+ }
+ }
+ videoLock.unlock()
+ }
+
+}
diff --git a/video/out/cocoa-cb/window.swift b/video/out/cocoa-cb/window.swift
new file mode 100644
index 0000000000..c93537c03a
--- /dev/null
+++ b/video/out/cocoa-cb/window.swift
@@ -0,0 +1,456 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Cocoa
+
+class Window: NSWindow, NSWindowDelegate {
+
+ weak var cocoaCB: CocoaCB! = nil
+ var mpv: MPVHelper! {
+ get { return cocoaCB == nil ? nil : cocoaCB.mpv }
+ }
+
+ var targetScreen: NSScreen?
+ var previousScreen: NSScreen?
+ var currentScreen: NSScreen?
+ var unfScreen: NSScreen?
+
+ var unfsContentFrame: NSRect?
+ var isInFullscreen: Bool = false
+ var isAnimating: Bool = false
+ var isMoving: Bool = false
+ var forceTargetScreen: Bool = false
+
+ var keepAspect: Bool = true {
+ didSet {
+ if !isInFullscreen {
+ unfsContentFrame = convertToScreen(contentView!.frame)
+ }
+
+ if keepAspect {
+ contentAspectRatio = unfsContentFrame!.size
+ } else {
+ resizeIncrements = NSSize(width: 1.0, height: 1.0)
+ }
+ }
+ }
+
+ override var canBecomeKey: Bool { return true }
+ override var canBecomeMain: Bool { return true }
+
+ override var styleMask: NSWindowStyleMask {
+ get { return super.styleMask }
+ set {
+ let responder = firstResponder
+ let windowTitle = title
+ super.styleMask = newValue
+ makeFirstResponder(responder)
+ title = windowTitle
+ }
+ }
+
+ convenience init(cocoaCB ccb: CocoaCB) {
+ self.init(contentRect: NSMakeRect(0, 0, 960, 480),
+ styleMask: [.titled, .closable, .miniaturizable, .resizable],
+ backing: .buffered, defer: false, screen: NSScreen.main())
+ cocoaCB = ccb
+ title = "mpv"
+ }
+
+ convenience init(contentRect: NSRect, styleMask style: NSWindowStyleMask,
+ screen: NSScreen?, cocoaCB ccb: CocoaCB)
+ {
+ self.init(contentRect: contentRect, styleMask: style,
+ backing: .buffered, defer: false, screen: screen)
+ cocoaCB = ccb
+ minSize = NSMakeSize(160, 90)
+ collectionBehavior = .fullScreenPrimary
+ delegate = self
+
+ unfsContentFrame = convertToScreen(contentView!.frame)
+ targetScreen = screen!
+ currentScreen = screen!
+ unfScreen = screen!
+
+ if let app = NSApp as? Application {
+ app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE)
+ app.menuBar.register(#selector(setNormalWindowSize), for: MPM_N_SIZE)
+ app.menuBar.register(#selector(setDoubleWindowSize), for: MPM_D_SIZE)
+ app.menuBar.register(#selector(performMiniaturize(_:)), for: MPM_MINIMIZE)
+ app.menuBar.register(#selector(performZoom(_:)), for: MPM_ZOOM)
+ }
+ }
+
+ override func toggleFullScreen(_ sender: Any?) {
+ if isAnimating {
+ return
+ }
+
+ isAnimating = true
+
+ targetScreen = cocoaCB.getTargetScreen(forFullscreen: !isInFullscreen)
+ if targetScreen == nil && previousScreen == nil {
+ targetScreen = screen
+ } else if targetScreen == nil {
+ targetScreen = previousScreen
+ previousScreen = nil
+ } else {
+ previousScreen = screen
+ }
+
+ if !isInFullscreen {
+ unfsContentFrame = convertToScreen(contentView!.frame)
+ unfScreen = screen
+ }
+ // move window to target screen when going to fullscreen
+ if !isInFullscreen && (targetScreen != screen) {
+ let frame = calculateWindowPosition(for: targetScreen!, withoutBounds: false)
+ setFrame(frame, display: true)
+ }
+
+ if mpv.getBoolProperty("native-fs") {
+ super.toggleFullScreen(sender)
+ } else {
+ if !isInFullscreen {
+ setToFullScreen()
+ }
+ else {
+ setToWindow()
+ }
+ }
+ }
+
+ func customWindowsToEnterFullScreen(for window: NSWindow) -> [NSWindow]? {
+ return [window]
+ }
+
+ func customWindowsToExitFullScreen(for window: NSWindow) -> [NSWindow]? {
+ return [window]
+ }
+
+ func window(_ window: NSWindow, startCustomAnimationToEnterFullScreenWithDuration duration: TimeInterval) {
+ var newFrame = targetScreen!.frame
+ let cRect = contentRect(forFrameRect: frame)
+ newFrame.size.height = newFrame.size.height - (frame.size.height - cRect.size.height)
+ let intermediateFrame = aspectFit(rect: cRect, in: newFrame)
+
+ NSAnimationContext.runAnimationGroup({ (context) -> Void in
+ context.duration = duration-0.1
+ window.animator().setFrame(intermediateFrame, display: true)
+ }, completionHandler: { })
+ }
+
+ func window(_ window: NSWindow, startCustomAnimationToExitFullScreenWithDuration duration: TimeInterval) {
+ let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen)
+ let intermediateFrame = aspectFit(rect: newFrame, in: screen!.frame)
+ setFrame(intermediateFrame, display: true)
+ cocoaCB.layer.display()
+
+ NSAnimationContext.runAnimationGroup({ (context) -> Void in
+ context.duration = duration-0.1
+ window.animator().setFrame(newFrame, display: true)
+ }, completionHandler: { })
+ }
+
+ func windowDidEnterFullScreen(_ notification: Notification) {
+ isInFullscreen = true
+ cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE)
+ cocoaCB.updateCusorVisibility()
+ endAnimation()
+ }
+
+ func windowDidExitFullScreen(_ notification: Notification) {
+ isInFullscreen = false
+ cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE)
+ endAnimation()
+ }
+
+ func windowDidFailToEnterFullScreen(_ window: NSWindow) {
+ let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen)
+ setFrame(newFrame, display: true)
+ endAnimation()
+ }
+
+ func windowDidFailToExitFullScreen(_ window: NSWindow) {
+ let newFrame = targetScreen!.frame
+ setFrame(newFrame, display: true)
+ endAnimation()
+ }
+
+ func endAnimation() {
+ isAnimating = false
+ cocoaCB.isShuttingDown = false
+ }
+
+ func setToFullScreen() {
+ styleMask.insert(.fullScreen)
+ NSApp.presentationOptions = [.autoHideMenuBar, .autoHideDock]
+ setFrame(targetScreen!.frame, display: true)
+ endAnimation()
+ isInFullscreen = true
+ cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE)
+ cocoaCB.layer.neededFlips += 1
+ }
+
+ func setToWindow() {
+ let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen)
+ NSApp.presentationOptions = []
+ setFrame(newFrame, display: true)
+ styleMask.remove(.fullScreen)
+ endAnimation()
+ isInFullscreen = false
+ cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE)
+ cocoaCB.layer.neededFlips += 1
+ }
+
+ func setBorder(_ state: Bool) {
+ if styleMask.contains(.titled) != state {
+ if state {
+ styleMask.remove(.borderless)
+ styleMask.insert(.titled)
+ } else {
+ styleMask.remove(.titled)
+ styleMask.insert(.borderless)
+ }
+ }
+ }
+
+ func setOnTop(_ state: Bool) {
+ if state {
+ let ontopLevel = mpv.getStringProperty("ontop-level") ?? "window"
+ switch ontopLevel {
+ case "window":
+ level = Int(CGWindowLevelForKey(.floatingWindow))
+ case "system":
+ level = Int(CGWindowLevelForKey(.statusWindow))+1
+ default:
+ level = Int(ontopLevel)!
+ }
+ collectionBehavior.remove(.transient)
+ collectionBehavior.insert(.managed)
+ } else {
+ level = Int(CGWindowLevelForKey(.normalWindow))
+ }
+ }
+
+ func updateMovableBackground(_ pos: NSPoint) {
+ if !isInFullscreen {
+ isMovableByWindowBackground = mpv.canBeDraggedAt(pos)
+ } else {
+ isMovableByWindowBackground = false
+ }
+ }
+
+ func updateFrame(_ rect: NSRect) {
+ if rect != frame {
+ let cRect = frameRect(forContentRect: rect)
+ setFrame(cRect, display: true)
+ unfsContentFrame = rect
+ }
+ }
+
+ func updateSize(_ size: NSSize) {
+ if size != contentView!.frame.size {
+ let newContentFrame = centeredContentSize(for: frame, size: size)
+ if !isInFullscreen {
+ updateFrame(newContentFrame)
+ } else {
+ unfsContentFrame = newContentFrame
+ }
+ }
+ }
+
+ override func setFrame(_ frameRect: NSRect, display flag: Bool) {
+ super.setFrame(frameRect, display: flag)
+ if keepAspect {
+ contentAspectRatio = unfsContentFrame!.size
+ }
+ }
+
+ func centeredContentSize(for rect: NSRect, size sz: NSSize) -> NSRect {
+ let cRect = contentRect(forFrameRect: rect)
+ let dx = (cRect.size.width - sz.width) / 2
+ let dy = (cRect.size.height - sz.height) / 2
+ return NSInsetRect(cRect, dx, dy)
+ }
+
+ func aspectFit(rect r: NSRect, in rTarget: NSRect) -> NSRect {
+ var s = rTarget.width / r.width;
+ if r.height*s > rTarget.height {
+ s = rTarget.height / r.height
+ }
+ let w = r.width * s
+ let h = r.height * s
+ return NSRect(x: rTarget.midX - w/2, y: rTarget.midY - h/2, width: w, height: h)
+ }
+
+ func calculateWindowPosition(for tScreen: NSScreen, withoutBounds: Bool) -> NSRect {
+ var newFrame = frameRect(forContentRect: unfsContentFrame!)
+ let targetFrame = tScreen.frame
+ let targetVisibleFrame = tScreen.visibleFrame
+ let unfsScreenFrame = unfScreen!.frame
+ let visibleWindow = NSIntersectionRect(unfsScreenFrame, newFrame)
+
+ // calculate visible area of every side
+ let left = newFrame.origin.x - unfsScreenFrame.origin.x
+ let right = unfsScreenFrame.size.width -
+ (newFrame.origin.x - unfsScreenFrame.origin.x + newFrame.size.width)
+ let bottom = newFrame.origin.y - unfsScreenFrame.origin.y
+ let top = unfsScreenFrame.size.height -
+ (newFrame.origin.y - unfsScreenFrame.origin.y + newFrame.size.height)
+
+ // normalize visible areas, decide which one to take horizontal/vertical
+ var xPer = (unfsScreenFrame.size.width - visibleWindow.size.width)
+ var yPer = (unfsScreenFrame.size.height - visibleWindow.size.height)
+ if xPer != 0 { xPer = (left >= 0 || right < 0 ? left : right) / xPer }
+ if yPer != 0 { yPer = (bottom >= 0 || top < 0 ? bottom : top) / yPer }
+
+ // calculate visible area for every side for target screen
+ let xNewLeft = targetFrame.origin.x +
+ (targetFrame.size.width - visibleWindow.size.width) * xPer
+ let xNewRight = targetFrame.origin.x + targetFrame.size.width -
+ (targetFrame.size.width - visibleWindow.size.width) * xPer - newFrame.size.width
+ let yNewBottom = targetFrame.origin.y +
+ (targetFrame.size.height - visibleWindow.size.height) * yPer
+ let yNewTop = targetFrame.origin.y + targetFrame.size.height -
+ (targetFrame.size.height - visibleWindow.size.height) * yPer - newFrame.size.height
+
+ // calculate new coordinates, decide which one to take horizontal/vertical
+ newFrame.origin.x = left >= 0 || right < 0 ? xNewLeft : xNewRight
+ newFrame.origin.y = bottom >= 0 || top < 0 ? yNewBottom : yNewTop
+
+ // don't place new window on top of a visible menubar
+ let topMar = targetFrame.size.height -
+ (newFrame.origin.y - targetFrame.origin.y + newFrame.size.height)
+ let menuBarHeight = targetFrame.size.height -
+ (targetVisibleFrame.size.height + targetVisibleFrame.origin.y)
+ if topMar < menuBarHeight {
+ newFrame.origin.y -= top - menuBarHeight
+ }
+
+ if withoutBounds {
+ return newFrame
+ }
+
+ // screen bounds right and left
+ if newFrame.origin.x + newFrame.size.width > targetFrame.origin.x + targetFrame.size.width {
+ newFrame.origin.x = targetFrame.origin.x + targetFrame.size.width - newFrame.size.width
+ }
+ if newFrame.origin.x < targetFrame.origin.x {
+ newFrame.origin.x = targetFrame.origin.x
+ }
+
+ // screen bounds top and bottom
+ if newFrame.origin.y + newFrame.size.height > targetFrame.origin.y + targetFrame.size.height {
+ newFrame.origin.y = targetFrame.origin.y + targetFrame.size.height - newFrame.size.height
+ }
+ if newFrame.origin.y < targetFrame.origin.y {
+ newFrame.origin.y = targetFrame.origin.y
+ }
+ return newFrame
+ }
+
+ override func constrainFrameRect(_ frameRect: NSRect, to tScreen: NSScreen?) -> NSRect {
+ if (isAnimating && !isInFullscreen) || (!isAnimating && isInFullscreen) {
+ return frameRect
+ }
+
+ var nf: NSRect = frameRect
+ let ts: NSScreen = tScreen ?? screen ?? NSScreen.main()!
+ let of: NSRect = frame
+ let vf: NSRect = (isAnimating ? targetScreen! : ts).visibleFrame
+ let ncf: NSRect = contentRect(forFrameRect: nf)
+
+ // screen bounds top and bottom
+ if NSMaxY(nf) > NSMaxY(vf) {
+ nf.origin.y = NSMaxY(vf) - NSHeight(nf)
+ }
+ if NSMaxY(ncf) < NSMinY(vf) {
+ nf.origin.y = NSMinY(vf) + NSMinY(ncf) - NSMaxY(ncf)
+ }
+
+ // screen bounds right and left
+ if NSMinX(nf) > NSMaxX(vf) {
+ nf.origin.x = NSMaxX(vf) - NSWidth(nf)
+ }
+ if NSMaxX(nf) < NSMinX(vf) {
+ nf.origin.x = NSMinX(vf)
+ }
+
+ if NSHeight(nf) < NSHeight(vf) && NSHeight(of) > NSHeight(vf) && !isInFullscreen {
+ // If the window height is smaller than the visible frame, but it was
+ // bigger previously recenter the smaller window vertically. This is
+ // needed to counter the 'snap to top' behaviour.
+ nf.origin.y = (NSHeight(vf) - NSHeight(nf)) / 2
+ }
+ return nf
+ }
+
+ func setNormalWindowSize() { setWindowScale(1.0) }
+ func setHalfWindowSize() { setWindowScale(0.5) }
+ func setDoubleWindowSize() { setWindowScale(2.0) }
+
+ func setWindowScale(_ scale: Double) {
+ mpv.commandAsync(["osd-auto", "set", "window-scale", "\(scale)"])
+ }
+
+ func windowDidChangeScreen(_ notification: Notification) {
+ if screen == nil {
+ return
+ }
+ if !isAnimating && (currentScreen != screen) {
+ previousScreen = screen
+ }
+ if currentScreen != screen {
+ cocoaCB.updateDisplaylink()
+ }
+ currentScreen = screen
+ }
+
+ func windowDidChangeScreenProfile(_ notification: Notification) {
+ cocoaCB.layer.needsICCUpdate = true
+ }
+
+ func windowDidChangeBackingProperties(_ notification: Notification) {
+ cocoaCB.layer.contentsScale = backingScaleFactor
+ }
+
+ func windowWillStartLiveResize(_ notification: Notification) {
+ cocoaCB.layer.inLiveResize = true
+ }
+
+ func windowDidEndLiveResize(_ notification: Notification) {
+ cocoaCB.layer.inLiveResize = false
+ }
+
+ func windowShouldClose(_ sender: Any) -> Bool {
+ cocoa_put_key(SWIFT_KEY_CLOSE_WIN)
+ return false
+ }
+
+ func windowDidResignKey(_ notification: Notification) {
+ cocoaCB.setCursorVisiblility(true)
+ }
+
+ func windowDidBecomeKey(_ notification: Notification) {
+ cocoaCB.updateCusorVisibility()
+ }
+
+ func windowWillMove(_ notification: Notification) {
+ isMoving = true
+ }
+}
diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift
new file mode 100644
index 0000000000..9a6da82ecf
--- /dev/null
+++ b/video/out/cocoa_cb_common.swift
@@ -0,0 +1,485 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Cocoa
+import IOKit.pwr_mgt
+
+class CocoaCB: NSObject {
+
+ var mpv: MPVHelper!
+ var window: Window!
+ var view: EventsView!
+ var layer: VideoLayer!
+ var link: CVDisplayLink?
+
+ var cursorHidden: Bool = false
+ var cursorVisibilityWanted: Bool = true
+ var isShuttingDown: Bool = false
+
+ enum State {
+ case uninit
+ case needsInit
+ case `init`
+ }
+ var backendState: State = .uninit
+
+ let eventsLock = NSLock()
+ var events: Int = 0
+
+ var lightSensor: io_connect_t = 0
+ var lastLmu: UInt64 = 0
+ var lightSensorIOPort: IONotificationPortRef?
+ var displaySleepAssertion: IOPMAssertionID = IOPMAssertionID(0)
+
+ let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue")
+
+ override init() {
+ super.init()
+ window = Window(cocoaCB: self)
+
+ view = EventsView(frame: window.contentView!.bounds, cocoaCB: self)
+ window.contentView!.addSubview(view)
+
+ layer = VideoLayer(cocoaCB: self)
+ view.layer = layer
+ view.wantsLayer = true
+ }
+
+ func setMpvHandle(_ ctx: OpaquePointer) {
+ mpv = MPVHelper(ctx)
+ layer.setUpGLCB()
+ }
+
+ func preinit() {
+ if backendState == .uninit {
+ backendState = .needsInit
+ DispatchQueue.main.async {
+ self.updateICCProfile()
+ }
+ startDisplayLink()
+ } else {
+ layer.setVideo(true)
+ }
+ }
+
+ func uninit() {
+ layer.setVideo(false)
+ window.orderOut(nil)
+ }
+
+ func reconfig() {
+ if backendState == .needsInit {
+ initBackend()
+ } else {
+ updateWindowSize()
+ layer.neededFlips += 1
+ }
+ }
+
+ func initBackend() {
+ NSApp.setActivationPolicy(.regular)
+
+ let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main()
+ let wr = getWindowGeometry(forScreen: targetScreen!, videoOut: mpv.mpctx!.pointee.video_out)
+ let win = Window(contentRect: wr, styleMask: window.styleMask,
+ screen: targetScreen, cocoaCB: self)
+ win.title = window.title
+ win.setOnTop(mpv.getBoolProperty("ontop"))
+ win.setBorder(mpv.getBoolProperty("border"))
+ win.keepAspect = mpv.getBoolProperty("keepaspect-window")
+ window.close()
+ window = win
+ window.contentView!.addSubview(view)
+ view.frame = window.contentView!.frame
+
+ setAppIcon()
+ window.isRestorable = false
+ window.makeMain()
+ window.makeKeyAndOrderFront(nil)
+ NSApp.activate(ignoringOtherApps: true)
+ layer.setVideo(true)
+
+ if self.mpv.getBoolProperty("fullscreen") {
+ window.toggleFullScreen(nil)
+ } else {
+ window.isMovableByWindowBackground = true
+ }
+
+ initLightSensor()
+ addDisplayReconfigureObserver()
+ backendState = .init
+ }
+
+ func updateWindowSize() {
+ if layer.hasVideo {
+ let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main()
+ let wr = getWindowGeometry(forScreen: targetScreen!, videoOut: mpv.mpctx!.pointee.video_out)
+ if !window.isVisible {
+ window.makeKeyAndOrderFront(nil)
+ }
+ window.updateSize(wr.size)
+ }
+ }
+
+ func setAppIcon() {
+ if let app = NSApp as? Application {
+ NSApp.applicationIconImage = app.getMPVIcon()
+ }
+ }
+
+ func startDisplayLink() {
+ let displayId = UInt32(window.screen!.deviceDescription["NSScreenNumber"] as! Int)
+ CVDisplayLinkCreateWithActiveCGDisplays(&link)
+ CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
+ CVDisplayLinkSetOutputHandler(link!) { link, now, out, inFlags, outFlags -> CVReturn in
+ self.layer.reportFlip()
+ return kCVReturnSuccess
+ }
+ CVDisplayLinkStart(link!)
+ }
+
+ func stopDisplaylink() {
+ if link != nil && CVDisplayLinkIsRunning(link!) {
+ CVDisplayLinkStop(link!)
+ }
+ }
+
+ func updateDisplaylink() {
+ let displayId = UInt32(window.screen!.deviceDescription["NSScreenNumber"] as! Int)
+ CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
+
+ queue.asyncAfter(deadline: DispatchTime.now() + 0.1) {
+ self.flagEvents(VO_EVENT_WIN_STATE)
+ }
+ }
+
+ func currentFps() -> Double {
+ var actualFps = CVDisplayLinkGetActualOutputVideoRefreshPeriod(link!)
+ let nominalData = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link!)
+
+ if (nominalData.flags & Int32(CVTimeFlags.isIndefinite.rawValue)) < 1 {
+ let nominalFps = Double(nominalData.timeScale) / Double(nominalData.timeValue)
+
+ if actualFps > 0 {
+ actualFps = 1/actualFps
+ }
+
+ if fabs(actualFps - nominalFps) > 0.1 {
+ mpv.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)")
+ return nominalFps
+ } else {
+ return actualFps
+ }
+ }
+ mpv.sendWarning("Falling back to standard display refresh rate: 60Hz")
+ return 60.0
+ }
+
+ func enableDisplaySleep() {
+ IOPMAssertionRelease(displaySleepAssertion)
+ displaySleepAssertion = IOPMAssertionID(0)
+ }
+
+ func disableDisplaySleep() {
+ if displaySleepAssertion != IOPMAssertionID(0) { return }
+ IOPMAssertionCreateWithName(
+ kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString,
+ IOPMAssertionLevel(kIOPMAssertionLevelOn),
+ "io.mpv.video_playing_back" as CFString,
+ &displaySleepAssertion)
+ }
+
+ func updateCusorVisibility() {
+ setCursorVisiblility(cursorVisibilityWanted)
+ }
+
+ func setCursorVisiblility(_ visible: Bool) {
+ let visibility = visible ? true : !view.canHideCursor()
+
+ if visibility && cursorHidden {
+ NSCursor.unhide()
+ cursorHidden = false;
+ } else if !visibility && !cursorHidden {
+ NSCursor.hide()
+ cursorHidden = true
+ }
+ }
+
+ func updateICCProfile() {
+ if mpv.getBoolProperty("icc-profile-auto") {
+ mpv.setGLCBICCProfile(window.screen!.colorSpace!)
+ }
+ layer.colorspace = window.screen!.colorSpace!.cgColorSpace!
+ }
+
+ func lmuToLux(_ v: UInt64) -> Int {
+ // the polinomial approximation for apple lmu value -> lux was empirically
+ // derived by firefox developers (Apple provides no documentation).
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=793728
+ let power_c4 = 1 / pow(10, 27)
+ let power_c3 = 1 / pow(10, 19)
+ let power_c2 = 1 / pow(10, 12)
+ let power_c1 = 1 / pow(10, 5)
+
+ let term4 = -3.0 * power_c4 * pow(Decimal(v), 4)
+ let term3 = 2.6 * power_c3 * pow(Decimal(v), 3)
+ let term2 = -3.4 * power_c2 * pow(Decimal(v), 2)
+ let term1 = 3.9 * power_c1 * Decimal(v)
+
+ let lux = Int(ceil( Double((term4 + term3 + term2 + term1 - 0.19) as NSNumber)))
+ return Int(lux > 0 ? lux : 0)
+ }
+
+ var lightSensorCallback: IOServiceInterestCallback = { (ctx, service, messageType, messageArgument) -> Void in
+ let ccb: CocoaCB = MPVHelper.bridge(ptr: ctx!)
+
+ var outputs: UInt32 = 2
+ var values: [UInt64] = [0, 0]
+
+ var kr = IOConnectCallMethod(ccb.lightSensor, 0, nil, 0, nil, 0, &values, &outputs, nil, nil)
+ if kr == KERN_SUCCESS {
+ var mean = (values[0] + values[1]) / 2
+ if ccb.lastLmu != mean {
+ ccb.lastLmu = mean
+ ccb.mpv.setGLCBLux(ccb.lmuToLux(ccb.lastLmu))
+ }
+ }
+ }
+
+ func initLightSensor() {
+ let srv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController"))
+ if srv == IO_OBJECT_NULL {
+ mpv.sendVerbose("Can't find an ambient light sensor")
+ return
+ }
+
+ lightSensorIOPort = IONotificationPortCreate(kIOMasterPortDefault)
+ IONotificationPortSetDispatchQueue(lightSensorIOPort, queue)
+ var n = io_object_t()
+ IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, MPVHelper.bridge(obj: self), &n)
+ let kr = IOServiceOpen(srv, mach_task_self_, 0, &lightSensor)
+ IOObjectRelease(srv)
+
+ if kr != KERN_SUCCESS {
+ mpv.sendVerbose("Can't start ambient light sensor connection")
+ return
+ }
+ lightSensorCallback(MPVHelper.bridge(obj: self), 0, 0, nil)
+ }
+
+ func uninitLightSensor() {
+ if lightSensorIOPort != nil {
+ IONotificationPortDestroy(lightSensorIOPort)
+ IOObjectRelease(lightSensor)
+ }
+ }
+
+ var reconfigureCallback: CGDisplayReconfigurationCallBack = { (display, flags, userInfo) in
+ if flags.contains(.setModeFlag) {
+ let ccb: CocoaCB = MPVHelper.bridge(ptr: userInfo!)
+ let displayID = (ccb.window.screen!.deviceDescription["NSScreenNumber"] as! NSNumber).intValue
+ if UInt32(displayID) == display {
+ ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate\n");
+ ccb.flagEvents(VO_EVENT_WIN_STATE)
+ }
+ }
+ }
+
+ func addDisplayReconfigureObserver() {
+ CGDisplayRegisterReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self))
+ }
+
+ func removeDisplayReconfigureObserver() {
+ CGDisplayRemoveReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self))
+ }
+
+ func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? {
+ let screenID = fs ? mpv.getStringProperty("fs-screen") ?? "current":
+ mpv.getStringProperty("screen") ?? "current"
+
+ switch screenID {
+ case "current", "default", "all":
+ return getScreenBy(id: -1)
+ default:
+ return getScreenBy(id: Int(screenID)!)
+ }
+ }
+
+ func getScreenBy(id screenID: Int) -> NSScreen? {
+ let screens = NSScreen.screens()
+ if screenID >= screens!.count {
+ mpv.sendInfo("Screen ID \(screenID) does not exist, falling back to current device")
+ return nil
+ } else if screenID < 0 {
+ return nil
+ }
+ return screens![screenID]
+ }
+
+ func getWindowGeometry(forScreen targetScreen: NSScreen,
+ videoOut vo: UnsafeMutablePointer<vo>) -> NSRect {
+ let r = targetScreen.convertRectToBacking(targetScreen.frame)
+ var screenRC: mp_rect = mp_rect(x0: Int32(0),
+ y0: Int32(0),
+ x1: Int32(r.size.width),
+ y1: Int32(r.size.height))
+
+ var geo: vo_win_geometry = vo_win_geometry()
+ vo_calc_window_geometry2(vo, &screenRC, Double(targetScreen.backingScaleFactor), &geo)
+
+ // flip y coordinates
+ geo.win.y1 = Int32(r.size.height) - geo.win.y1
+ geo.win.y0 = Int32(r.size.height) - geo.win.y0
+
+ let wr = NSMakeRect(CGFloat(geo.win.x0), CGFloat(geo.win.y1),
+ CGFloat(geo.win.x1 - geo.win.x0),
+ CGFloat(geo.win.y0 - geo.win.y1))
+ return targetScreen.convertRectFromBacking(wr)
+ }
+
+ func flagEvents(_ ev: Int) {
+ eventsLock.lock()
+ events |= ev
+ eventsLock.unlock()
+ }
+
+ func checkEvents() -> Int {
+ eventsLock.lock()
+ let ev = events
+ events = 0
+ eventsLock.unlock()
+ return ev
+ }
+
+ var controlCallback: mpv_opengl_cb_control_fn = { ( ctx, events, request, data ) -> Int32 in
+ let ccb: CocoaCB = MPVHelper.bridge(ptr: ctx!)
+
+ switch mp_voctrl(request) {
+ case VOCTRL_CHECK_EVENTS:
+ events!.pointee = Int32(ccb.checkEvents())
+ return VO_TRUE
+ case VOCTRL_FULLSCREEN:
+ DispatchQueue.main.async {
+ ccb.window.toggleFullScreen(nil)
+ }
+ return VO_TRUE
+ case VOCTRL_GET_FULLSCREEN:
+ let fsData = data!.assumingMemoryBound(to: Int32.self)
+ fsData.pointee = ccb.window.isInFullscreen ? 1 : 0
+ return VO_TRUE
+ case VOCTRL_GET_DISPLAY_FPS:
+ let fps = data!.assumingMemoryBound(to: CDouble.self)
+ fps.pointee = ccb.currentFps()
+ return VO_TRUE
+ case VOCTRL_RESTORE_SCREENSAVER:
+ ccb.enableDisplaySleep()
+ return VO_TRUE
+ case VOCTRL_KILL_SCREENSAVER:
+ ccb.disableDisplaySleep()
+ return VO_TRUE
+ case VOCTRL_SET_CURSOR_VISIBILITY:
+ ccb.cursorVisibilityWanted = data!.assumingMemoryBound(to: CBool.self).pointee
+ DispatchQueue.main.async {
+ ccb.setCursorVisiblility(ccb.cursorVisibilityWanted)
+ }
+ return VO_TRUE
+ case VOCTRL_SET_UNFS_WINDOW_SIZE:
+ let sizeData = data!.assumingMemoryBound(to: Int32.self)
+ let size = UnsafeBufferPointer(start: sizeData, count: 2)
+ var rect = NSMakeRect(0, 0, CGFloat(size[0]), CGFloat(size[1]))
+ DispatchQueue.main.async {
+ if !ccb.mpv.getBoolProperty("hidpi-window-scale") {
+ rect = ccb.window.currentScreen!.convertRectFromBacking(rect)
+ }
+ ccb.window.updateSize(rect.size)
+ }
+ return VO_TRUE
+ case VOCTRL_GET_WIN_STATE:
+ let minimized = data!.assumingMemoryBound(to: Int32.self)
+ minimized.pointee = ccb.window.isMiniaturized ? VO_WIN_STATE_MINIMIZED : Int32(0)
+ return VO_TRUE
+ case VOCTRL_UPDATE_WINDOW_TITLE:
+ let titleData = data!.assumingMemoryBound(to: Int8.self)
+ let title = String(cString: titleData)
+ DispatchQueue.main.async {
+ ccb.window.title = String(cString: titleData)
+ }
+ return VO_TRUE
+ case VOCTRL_PREINIT:
+ ccb.preinit()
+ return VO_TRUE
+ case VOCTRL_UNINIT:
+ DispatchQueue.main.async {
+ ccb.uninit()
+ }
+ return VO_TRUE
+ case VOCTRL_RECONFIG:
+ DispatchQueue.main.async {
+ ccb.reconfig()
+ }
+ return VO_TRUE
+ default:
+ return VO_NOTIMPL
+ }
+ }
+
+ func processEvent(_ event: UnsafePointer<mpv_event>) {
+ switch event.pointee.event_id {
+ case MPV_EVENT_SHUTDOWN:
+ if window.isAnimating {
+ isShuttingDown = true
+ return
+ }
+ setCursorVisiblility(true)
+ stopDisplaylink()
+ uninitLightSensor()
+ removeDisplayReconfigureObserver()
+ mpv.deinitGLCB()
+ mpv.deinitMPV()
+ case MPV_EVENT_PROPERTY_CHANGE:
+ if backendState == .init {
+ handlePropertyChange(event)
+ }
+ default:
+ break
+ }
+ }
+
+ func handlePropertyChange(_ event: UnsafePointer<mpv_event>) {
+ let pData = OpaquePointer(event.pointee.data)
+ guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else {
+ return
+ }
+
+ switch String(cString: property.name) {
+ case "border":
+ if let data = MPVHelper.mpvFlagToBool(property.data) {
+ window.setBorder(data)
+ }
+ case "ontop":
+ if let data = MPVHelper.mpvFlagToBool(property.data) {
+ window.setOnTop(data)
+ }
+ case "keepaspect-window":
+ if let data = MPVHelper.mpvFlagToBool(property.data) {
+ window.keepAspect = data
+ }
+ default:
+ break
+ }
+ }
+}
diff --git a/video/out/opengl/context_cocoa.c b/video/out/opengl/context_cocoa.c
index da20f400b2..367c6cd291 100644
--- a/video/out/opengl/context_cocoa.c
+++ b/video/out/opengl/context_cocoa.c
@@ -171,6 +171,8 @@ static bool cocoa_init(struct ra_ctx *ctx)
p->opts = mp_get_config_group(ctx, ctx->global, &cocoa_conf);
vo_cocoa_init(ctx->vo);
+ MP_WARN(ctx->vo, "opengl cocoa backend is deprecated, use opengl-cb instead\n");
+
if (!create_gl_context(ctx))
goto fail;
diff --git a/video/out/vo.h b/video/out/vo.h
index 4ce2cf1371..533eca6a2e 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -73,6 +73,11 @@ enum mp_voctrl {
// be updated and redrawn. Optional; emulated if not available.
VOCTRL_REDRAW_FRAME,
+ // Only used internally in vo_opengl_cb
+ VOCTRL_PREINIT,
+ VOCTRL_UNINIT,
+ VOCTRL_RECONFIG,
+
VOCTRL_FULLSCREEN,
VOCTRL_ONTOP,
VOCTRL_BORDER,
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index f839f0a197..4be36118a0 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -57,6 +57,10 @@ struct mpv_opengl_cb_context {
struct mpv_global *global;
struct mp_client_api *client_api;
+ pthread_mutex_t control_lock;
+ mpv_opengl_cb_control_fn control_cb;
+ void *control_cb_ctx;
+
pthread_mutex_t lock;
pthread_cond_t wakeup;
@@ -78,6 +82,7 @@ struct mpv_opengl_cb_context {
bool imgfmt_supported[IMGFMT_END - IMGFMT_START];
bool update_new_opts;
struct vo *active;
+ bool icc_was_set;
// --- This is only mutable while initialized=false, during which nothing
// except the OpenGL context manager is allowed to access it.
@@ -93,7 +98,7 @@ struct mpv_opengl_cb_context {
struct mp_vo_opts *vo_opts;
};
-static void update(struct vo_priv *p);
+static void update(struct mpv_opengl_cb_context *ctx);
static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all)
{
@@ -114,6 +119,7 @@ static void free_ctx(void *ptr)
pthread_cond_destroy(&ctx->wakeup);
pthread_mutex_destroy(&ctx->lock);
+ pthread_mutex_destroy(&ctx->control_lock);
}
struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
@@ -121,6 +127,7 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
{
mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context);
talloc_set_destructor(ctx, free_ctx);
+ pthread_mutex_init(&ctx->control_lock, NULL);
pthread_mutex_init(&ctx->lock, NULL);
pthread_cond_init(&ctx->wakeup, NULL);
@@ -144,6 +151,17 @@ void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx,
pthread_mutex_unlock(&ctx->lock);
}
+
+void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx,
+ mpv_opengl_cb_control_fn callback,
+ void *callback_ctx)
+{
+ pthread_mutex_lock(&ctx->control_lock);
+ ctx->control_cb = callback;
+ ctx->control_cb_ctx = callback_ctx;
+ pthread_mutex_unlock(&ctx->control_lock);
+}
+
// Reset some GL attributes the user might clobber. For mid-term compatibility
// only - we expect both user code and our code to do this correctly.
static void reset_gl_state(GL *gl)
@@ -223,9 +241,11 @@ int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx)
pthread_mutex_lock(&ctx->lock);
forget_frames(ctx, true);
ctx->initialized = false;
+ bool was_active = ctx->active ? true : false;
pthread_mutex_unlock(&ctx->lock);
- kill_video(ctx->client_api);
+ if (was_active)
+ kill_video(ctx->client_api);
pthread_mutex_lock(&ctx->lock);
assert(!ctx->active);
@@ -291,7 +311,7 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
&debug);
ctx->gl->debug_context = debug;
ra_gl_set_debug(ctx->ra_ctx->ra, debug);
- if (gl_video_icc_auto_enabled(ctx->renderer))
+ if (!ctx->icc_was_set && gl_video_icc_auto_enabled(ctx->renderer))
MP_ERR(ctx, "icc-profile-auto is not available with opengl-cb\n");
}
ctx->reconfigured = false;
@@ -360,10 +380,10 @@ int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
}
// Called locked.
-static void update(struct vo_priv *p)
+static void update(struct mpv_opengl_cb_context *ctx)
{
- if (p->ctx->update_cb)
- p->ctx->update_cb(p->ctx->update_cb_ctx);
+ if (ctx->update_cb)
+ ctx->update_cb(ctx->update_cb_ctx);
}
static void draw_frame(struct vo *vo, struct vo_frame *frame)
@@ -375,7 +395,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
p->ctx->next_frame = vo_frame_ref(frame);
p->ctx->expected_flip_count = p->ctx->flip_count + 1;
p->ctx->redrawing = frame->redraw || !frame->current;
- update(p);
+ update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
}
@@ -442,17 +462,20 @@ static int query_format(struct vo *vo, int format)
return ok;
}
-static int reconfig(struct vo *vo, struct mp_image_params *params)
+// Can only be called from the GL thread
+void mp_client_set_icc_profile(struct mpv_opengl_cb_context *ctx, bstr icc_data)
{
- struct vo_priv *p = vo->priv;
-
- pthread_mutex_lock(&p->ctx->lock);
- forget_frames(p->ctx, true);
- p->ctx->img_params = *params;
- p->ctx->reconfigured = true;
- pthread_mutex_unlock(&p->ctx->lock);
+ gl_video_set_icc_profile(ctx->renderer, icc_data);
+ pthread_mutex_lock(&ctx->lock);
+ ctx->icc_was_set = true;
+ if (ctx->active)
+ update(ctx);
+ pthread_mutex_unlock(&ctx->lock);
+}
- return 0;
+void mp_client_set_ambient_lux(struct mpv_opengl_cb_context *ctx, int lux)
+{
+ gl_video_set_ambient_lux(ctx->renderer, lux);
}
static int control(struct vo *vo, uint32_t request, void *data)
@@ -475,30 +498,54 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_SET_PANSCAN:
pthread_mutex_lock(&p->ctx->lock);
p->ctx->force_update = true;
- update(p);
+ update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
return VO_TRUE;
case VOCTRL_UPDATE_RENDER_OPTS:
pthread_mutex_lock(&p->ctx->lock);
p->ctx->update_new_opts = true;
- update(p);
+ update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
return VO_TRUE;
}
- return VO_NOTIMPL;
+ int r = VO_NOTIMPL;
+ pthread_mutex_lock(&p->ctx->control_lock);
+ if (p->ctx->control_cb) {
+ int events = 0;
+ r = p->ctx->control_cb(p->ctx->control_cb_ctx, &events, request, data);
+ vo_event(vo, events);
+ }
+ pthread_mutex_unlock(&p->ctx->control_lock);
+
+ return r;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct vo_priv *p = vo->priv;
+
+ pthread_mutex_lock(&p->ctx->lock);
+ forget_frames(p->ctx, true);
+ p->ctx->img_params = *params;
+ p->ctx->reconfigured = true;
+ pthread_mutex_unlock(&p->ctx->lock);
+ control(vo, VOCTRL_RECONFIG, NULL);
+
+ return 0;
}
static void uninit(struct vo *vo)
{
struct vo_priv *p = vo->priv;
+ control(vo, VOCTRL_UNINIT, NULL);
pthread_mutex_lock(&p->ctx->lock);
forget_frames(p->ctx, true);
p->ctx->img_params = (struct mp_image_params){0};
p->ctx->reconfigured = true;
p->ctx->active = NULL;
- update(p);
+ update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
}
@@ -523,6 +570,7 @@ static int preinit(struct vo *vo)
pthread_mutex_unlock(&p->ctx->lock);
vo->hwdec_devs = p->ctx->hwdec_devs;
+ control(vo, VOCTRL_PREINIT, NULL);
return 0;
}
diff --git a/waftools/checks/custom.py b/waftools/checks/custom.py
index 3af9bdb921..14e59f3f49 100644
--- a/waftools/checks/custom.py
+++ b/waftools/checks/custom.py
@@ -92,18 +92,6 @@ def check_wl_protocols(ctx, dependency_identifier):
return ret
return fn(ctx, dependency_identifier)
-def _run(cmd):
- from waflib import Utils
- try:
- cmd = Utils.subprocess.Popen(cmd,
- stdout=Utils.subprocess.PIPE,
- stderr=Utils.subprocess.PIPE,
- shell=True)
- output = cmd.stdout.read().strip()
- return output
- except Exception:
- return ""
-
def check_cocoa(ctx, dependency_identifier):
fn = check_cc(
fragment = load_fragment('cocoa.m'),
@@ -117,7 +105,7 @@ def check_cocoa(ctx, dependency_identifier):
# linking warnings or errors
if res:
ctx.env.append_value('LINKFLAGS', [
- '-isysroot', '%s' % _run('xcrun --sdk macosx --show-sdk-path')
+ '-isysroot', ctx.env.MACOS_SDK
])
return res
diff --git a/waftools/detections/compiler_swift.py b/waftools/detections/compiler_swift.py
new file mode 100644
index 0000000000..36f4f0b432
--- /dev/null
+++ b/waftools/detections/compiler_swift.py
@@ -0,0 +1,71 @@
+from waflib import Utils
+
+def __run(cmd):
+ try:
+ cmd = Utils.subprocess.Popen(cmd,
+ stdout=Utils.subprocess.PIPE,
+ stderr=Utils.subprocess.PIPE,
+ shell=True)
+ output = cmd.stdout.read().strip()
+ return output
+ except Exception:
+ return ""
+
+def __add_swift_flags(ctx):
+ ctx.env.SWIFT_FLAGS = ('-frontend -c -sdk %s -enable-objc-interop -emit-objc-header'
+ ' -emit-module -parse-as-library') % (ctx.env.MACOS_SDK)
+ swift_version = __run(ctx.env.SWIFT + ' -version').split(' ')[3].split('.')
+ major, minor, sub = [int(n) for n in swift_version]
+
+ # the -swift-version parameter is only supported on swift 3.1 and newer
+ if major >= 3 and minor >= 1 or major >= 4:
+ ctx.env.SWIFT_FLAGS += ' -swift-version 3'
+
+ if ctx.is_optimization():
+ ctx.env.SWIFT_FLAGS += ' -O'
+
+def __add_swift_library_linking_flags(ctx, swift_library):
+ ctx.env.append_value('LINKFLAGS', [
+ '-L%s' % swift_library,
+ '-Xlinker', '-force_load_swift_libs', '-lc++',
+ ])
+
+def __find_swift_library(ctx):
+ swift_library_paths = [
+ 'Toolchains/XcodeDefault.xctoolchain/usr/lib/swift_static/macosx',
+ 'usr/lib/swift_static/macosx'
+ ]
+ dev_path = __run('xcode-select -p')[1:]
+
+ ctx.start_msg('Checking for Swift Library')
+ for path in swift_library_paths:
+ swift_library = ctx.root.find_dir([dev_path, path])
+ if swift_library is not None:
+ ctx.end_msg(swift_library)
+ __add_swift_library_linking_flags(ctx, swift_library)
+ return
+ ctx.end_msg(None, "RED")
+
+def __find_macos_sdk(ctx):
+ ctx.start_msg('Checking for macOS SDK')
+ sdk = __run('xcrun --sdk macosx --show-sdk-path')
+ if sdk != "":
+ ctx.end_msg(sdk)
+ ctx.env.MACOS_SDK = sdk
+ else:
+ ctx.end_msg(None, "RED")
+
+def __find_swift_compiler(ctx):
+ ctx.start_msg('Checking for swift (Swift compiler)')
+ swift = __run('xcrun -find swift')
+ if swift != "":
+ ctx.end_msg(swift)
+ ctx.env.SWIFT = swift
+ __add_swift_flags(ctx)
+ __find_swift_library(ctx)
+ else:
+ ctx.end_msg(None, "RED")
+
+def configure(ctx):
+ __find_macos_sdk(ctx)
+ __find_swift_compiler(ctx)
diff --git a/waftools/generators/sources.py b/waftools/generators/sources.py
index b0b423edda..b7766cc1b6 100644
--- a/waftools/generators/sources.py
+++ b/waftools/generators/sources.py
@@ -1,5 +1,5 @@
from waflib.Build import BuildContext
-from waflib import TaskGen
+from waflib import TaskGen, Utils
from io import StringIO
from TOOLS.matroska import generate_C_header, generate_C_definitions
from TOOLS.file2string import file2string
@@ -71,6 +71,15 @@ def __wayland_protocol_header__(ctx, **kwargs):
**kwargs
)
+@TaskGen.feature('cprogram')
+@TaskGen.feature('apply_link')
+def handle_add_object(tgen):
+ if getattr(tgen, 'add_object', None):
+ for input in Utils.to_list(tgen.add_object):
+ input_node = tgen.path.find_resource(input)
+ if input_node is not None:
+ tgen.link_task.inputs.append(input_node)
+
BuildContext.file2string = __file2string__
BuildContext.wayland_protocol_code = __wayland_protocol_code__
BuildContext.wayland_protocol_header = __wayland_protocol_header__
diff --git a/wscript b/wscript
index 9512340b59..eb0a6df636 100644
--- a/wscript
+++ b/wscript
@@ -914,7 +914,12 @@ standalone_features = [
framework_name=['AppKit'],
compile_filename='test-touchbar.m',
linkflags='-fobjc-arc')
- }
+ }, {
+ 'name': '--macos-cocoa-cb',
+ 'desc': 'macOS opengl-cb backend',
+ 'deps': 'cocoa',
+ 'func': check_true
+ }
]
_INSTALL_DIRS_LIST = [
@@ -965,6 +970,10 @@ def options(opt):
type = 'string',
dest = 'LUA_VER',
help = "select Lua package which should be autodetected. Choices: 51 51deb 51obsd 51fbsd 52 52deb 52arch 52fbsd luajit")
+ group.add_option('--swift-flags',
+ type = 'string',
+ dest = 'SWIFT_FLAGS',
+ help = "Optional Swift compiler flags")
@conf
def is_optimization(ctx):
@@ -1000,6 +1009,7 @@ def configure(ctx):
ctx.load('compiler_c')
ctx.load('waf_customizations')
ctx.load('dependencies')
+ ctx.load('detections.compiler_swift')
ctx.load('detections.compiler')
ctx.load('detections.devices')
@@ -1022,6 +1032,9 @@ def configure(ctx):
if ctx.options.LUA_VER:
ctx.options.enable_lua = True
+ if ctx.options.SWIFT_FLAGS:
+ ctx.env.SWIFT_FLAGS += ' ' + ctx.options.SWIFT_FLAGS
+
ctx.parse_dependencies(standalone_features)
ctx.load('generators.headers')
diff --git a/wscript_build.py b/wscript_build.py
index b2d61cf0be..78db131beb 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -139,6 +139,42 @@ def build(ctx):
ctx(features = "ebml_header", target = "ebml_types.h")
ctx(features = "ebml_definitions", target = "ebml_defs.c")
+ def swift(task):
+ src = ' '.join([x.abspath() for x in task.inputs])
+ bridge = ctx.path.find_node("osdep/macOS_swift_bridge.h")
+ tgt = task.outputs[0].abspath()
+ header = task.outputs[1].abspath()
+ module = task.outputs[2].abspath()
+
+ cmd = ('%s %s -module-name macOS_swift -emit-module-path %s '
+ '-import-objc-header %s -emit-objc-header-path %s -o %s %s '
+ '-I. -I..') % (ctx.env.SWIFT, ctx.env.SWIFT_FLAGS, module,
+ bridge, header, tgt, src)
+ return task.exec_command(cmd)
+
+ if ctx.dependency_satisfied('macos-cocoa-cb'):
+ swift_source = [
+ ( "video/out/cocoa_cb_common.swift" ),
+ ( "video/out/cocoa-cb/window.swift" ),
+ ( "video/out/cocoa-cb/events_view.swift" ),
+ ( "video/out/cocoa-cb/video_layer.swift" ),
+ ( "osdep/macOS_mpv_helper.swift" )
+ ]
+
+ ctx(
+ rule = swift,
+ source = ctx.filtered_sources(swift_source),
+ target = ('osdep/macOS_swift.o '
+ 'osdep/macOS_swift.h '
+ 'osdep/macOS_swift.swiftmodule'),
+ before = 'c',
+ )
+
+ ctx.env.append_value('LINKFLAGS', [
+ '-Xlinker', '-add_ast_path',
+ '-Xlinker', '%s' % ctx.path.find_resource("osdep/macOS_swift.swiftmodule")
+ ])
+
if ctx.dependency_satisfied('cplayer'):
main_fn_c = ctx.pick_first_matching_dep([
( "osdep/main-fn-cocoa.c", "cocoa" ),
@@ -552,6 +588,7 @@ def build(ctx):
target = "mpv",
source = main_fn_c,
use = ctx.dependencies_use() + ['objects'],
+ add_object = "osdep/macOS_swift.o",
includes = _all_includes(ctx),
features = "c cprogram" + (" syms" if syms else ""),
export_symbols_def = "libmpv/mpv.def", # for syms=True