summaryrefslogtreecommitdiffstats
path: root/osdep
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--misc/io_utils.h (renamed from osdep/macosx_menubar_objc.h)12
-rw-r--r--osdep/io.c141
-rw-r--r--osdep/io.h23
-rw-r--r--osdep/language-mac.c (renamed from osdep/language-apple.c)2
-rw-r--r--osdep/mac/app_bridge.h (renamed from osdep/macosx_application.h)22
-rw-r--r--osdep/mac/app_bridge.m123
-rw-r--r--osdep/mac/app_bridge_objc.h (renamed from osdep/macOS_swift_bridge.h)25
-rw-r--r--osdep/mac/app_hub.swift130
-rw-r--r--osdep/mac/application.swift123
-rw-r--r--osdep/mac/event_helper.swift147
-rw-r--r--osdep/mac/input_helper.swift275
-rw-r--r--osdep/mac/libmpv_helper.swift (renamed from osdep/macos/libmpv_helper.swift)119
-rw-r--r--osdep/mac/log_helper.swift67
-rw-r--r--osdep/mac/menu_bar.swift397
-rw-r--r--osdep/mac/meson.build (renamed from osdep/meson.build)24
-rw-r--r--osdep/mac/option_helper.swift74
-rw-r--r--osdep/mac/precise_timer.swift (renamed from osdep/macos/precise_timer.swift)9
-rw-r--r--osdep/mac/presentation.swift56
-rw-r--r--osdep/mac/remote_command_center.swift202
-rw-r--r--osdep/mac/swift_compat.swift (renamed from osdep/macos/swift_compat.swift)0
-rw-r--r--osdep/mac/swift_extensions.swift93
-rw-r--r--osdep/mac/touch_bar.swift297
-rw-r--r--osdep/mac/type_helper.swift69
-rw-r--r--osdep/macos/log_helper.swift47
-rw-r--r--osdep/macos/mpv_helper.swift156
-rw-r--r--osdep/macos/remote_command_center.swift191
-rw-r--r--osdep/macos/swift_extensions.swift58
-rw-r--r--osdep/macosx_application.m375
-rw-r--r--osdep/macosx_application_objc.h40
-rw-r--r--osdep/macosx_events.h36
-rw-r--r--osdep/macosx_events.m408
-rw-r--r--osdep/macosx_events_objc.h45
-rw-r--r--osdep/macosx_menubar.m853
-rw-r--r--osdep/macosx_touchbar.h46
-rw-r--r--osdep/macosx_touchbar.m334
-rw-r--r--osdep/main-fn-cocoa.c10
-rw-r--r--osdep/main-fn-mac.c7
-rw-r--r--osdep/main-fn-win.c3
-rw-r--r--osdep/mpv.exe.manifest2
-rw-r--r--osdep/path-mac.m (renamed from osdep/path-macosx.m)2
-rw-r--r--osdep/path-uwp.c10
-rw-r--r--osdep/path-win.c11
-rw-r--r--osdep/path.h4
-rw-r--r--osdep/semaphore-mac.c (renamed from osdep/semaphore_osx.c)0
-rw-r--r--osdep/semaphore.h2
-rw-r--r--osdep/subprocess-posix.c4
-rw-r--r--osdep/subprocess-win.c6
-rw-r--r--osdep/terminal-dummy.c10
-rw-r--r--osdep/terminal-unix.c52
-rw-r--r--osdep/terminal-win.c201
-rw-r--r--osdep/terminal.h10
-rw-r--r--osdep/threads-posix.h9
-rw-r--r--osdep/threads-win32.h4
-rw-r--r--osdep/timer-darwin.c7
-rw-r--r--osdep/timer-win32.c51
-rw-r--r--osdep/timer.c7
-rw-r--r--osdep/timer.h17
-rw-r--r--osdep/utils-mac.c (renamed from osdep/apple_utils.c)2
-rw-r--r--osdep/utils-mac.h (renamed from osdep/apple_utils.h)6
-rw-r--r--osdep/w32_keyboard.c2
-rw-r--r--osdep/win32-console-wrapper.c45
-rw-r--r--osdep/windows_utils.c22
-rw-r--r--osdep/windows_utils.h4
-rw-r--r--video/out/win32/menu.h (renamed from osdep/macosx_menubar.h)24
64 files changed, 2611 insertions, 2942 deletions
diff --git a/osdep/macosx_menubar_objc.h b/misc/io_utils.h
index 072fef8..012f242 100644
--- a/osdep/macosx_menubar_objc.h
+++ b/misc/io_utils.h
@@ -1,4 +1,6 @@
/*
+ * I/O utility functions
+ *
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
@@ -15,11 +17,9 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#import <Cocoa/Cocoa.h>
-#include "osdep/macosx_menubar.h"
-
-@interface MenuBar : NSObject
+#pragma once
-- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key;
+#include <stddef.h>
-@end
+int mp_mkostemps(char *template, int suffixlen, int flags);
+bool mp_save_to_file(const char *filepath, const void *data, size_t size);
diff --git a/osdep/io.c b/osdep/io.c
index bdf79f8..21abe0e 100644
--- a/osdep/io.c
+++ b/osdep/io.c
@@ -189,7 +189,7 @@ static bool get_file_ids_win8(HANDLE h, dev_t *dev, ino_t *ino)
// SDK, but we can ignore that by just memcpying it. This will also
// truncate the file ID on 32-bit Windows, which doesn't support __int128.
// 128-bit file IDs are only used for ReFS, so that should be okay.
- assert(sizeof(*ino) <= sizeof(ii.FileId));
+ static_assert(sizeof(*ino) <= sizeof(ii.FileId), "");
memcpy(ino, &ii.FileId, sizeof(*ino));
return true;
}
@@ -298,62 +298,53 @@ int mp_fstat(int fd, struct mp_stat *buf)
return hstat(h, buf);
}
-#if HAVE_UWP
-static int mp_vfprintf(FILE *stream, const char *format, va_list args)
+static inline HANDLE get_handle(FILE *stream)
{
- return vfprintf(stream, format, args);
-}
-#else
-static int mp_check_console(HANDLE wstream)
-{
- if (wstream != INVALID_HANDLE_VALUE) {
- unsigned int filetype = GetFileType(wstream);
+ HANDLE wstream = INVALID_HANDLE_VALUE;
- if (!((filetype == FILE_TYPE_UNKNOWN) &&
- (GetLastError() != ERROR_SUCCESS)))
- {
- filetype &= ~(FILE_TYPE_REMOTE);
+ if (stream == stdout || stream == stderr) {
+ wstream = GetStdHandle(stream == stdout ?
+ STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
+ }
+ return wstream;
+}
- if (filetype == FILE_TYPE_CHAR) {
- DWORD ConsoleMode;
- int ret = GetConsoleMode(wstream, &ConsoleMode);
+size_t mp_fwrite(const void *restrict buffer, size_t size, size_t count,
+ FILE *restrict stream)
+{
+ if (!size || !count)
+ return 0;
- if (!(!ret && (GetLastError() == ERROR_INVALID_HANDLE))) {
- // This seems to be a console
- return 1;
- }
- }
+ HANDLE wstream = get_handle(stream);
+ if (mp_check_console(wstream)) {
+ unsigned char *start = (unsigned char *)buffer;
+ size_t c = 0;
+ for (; c < count; ++c) {
+ if (mp_console_write(wstream, (bstr){start, size}) <= 0)
+ break;
+ start += size;
}
+ return c;
}
- return 0;
+#undef fwrite
+ return fwrite(buffer, size, count, stream);
}
+#if HAVE_UWP
static int mp_vfprintf(FILE *stream, const char *format, va_list args)
{
- int done = 0;
-
- HANDLE wstream = INVALID_HANDLE_VALUE;
-
- if (stream == stdout || stream == stderr) {
- wstream = GetStdHandle(stream == stdout ?
- STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
- }
-
- if (mp_check_console(wstream)) {
- size_t len = vsnprintf(NULL, 0, format, args) + 1;
- char *buf = talloc_array(NULL, char, len);
+ return vfprintf(stream, format, args);
+}
+#else
- if (buf) {
- done = vsnprintf(buf, len, format, args);
- mp_write_console_ansi(wstream, buf);
- }
- talloc_free(buf);
- } else {
- done = vfprintf(stream, format, args);
- }
+static int mp_vfprintf(FILE *stream, const char *format, va_list args)
+{
+ HANDLE wstream = get_handle(stream);
+ if (mp_check_console(wstream))
+ return mp_console_vfprintf(wstream, format, args);
- return done;
+ return vfprintf(stream, format, args);
}
#endif
@@ -558,7 +549,9 @@ FILE *mp_fopen(const char *filename, const char *mode)
// Thus we need MP_PATH_MAX as the UTF-8/char version of PATH_MAX.
// Also make sure there's free space for the terminating \0.
// (For codepoints encoded as UTF-16 surrogate pairs, UTF-8 has the same length.)
-#define MP_PATH_MAX (FILENAME_MAX * 3 + 1)
+// Lastly, note that neither _wdirent nor WIN32_FIND_DATA can store filenames
+// longer than this, so long-path support for readdir() is impossible.
+#define MP_FILENAME_MAX (FILENAME_MAX * 3 + 1)
struct mp_dir {
DIR crap; // must be first member
@@ -568,9 +561,9 @@ struct mp_dir {
// dirent has space only for FILENAME_MAX bytes. _wdirent has space for
// FILENAME_MAX wchar_t, which might end up bigger as UTF-8 in some
// cases. Guarantee we can always hold _wdirent.d_name converted to
- // UTF-8 (see MP_PATH_MAX).
+ // UTF-8 (see above).
// This works because dirent.d_name is the last member of dirent.
- char space[MP_PATH_MAX];
+ char space[MP_FILENAME_MAX];
};
};
@@ -620,6 +613,14 @@ int mp_mkdir(const char *path, int mode)
return res;
}
+int mp_unlink(const char *path)
+{
+ wchar_t *wpath = mp_from_utf8(NULL, path);
+ int res = _wunlink(wpath);
+ talloc_free(wpath);
+ return res;
+}
+
char *mp_win32_getcwd(char *buf, size_t size)
{
if (size >= SIZE_MAX / 3 - 1) {
@@ -736,9 +737,23 @@ static void mp_dl_init(void)
void *mp_dlopen(const char *filename, int flag)
{
- wchar_t *wfilename = mp_from_utf8(NULL, filename);
- HMODULE lib = LoadLibraryW(wfilename);
- talloc_free(wfilename);
+ HMODULE lib = NULL;
+ void *ta_ctx = talloc_new(NULL);
+ wchar_t *wfilename = mp_from_utf8(ta_ctx, filename);
+
+ DWORD len = GetFullPathNameW(wfilename, 0, NULL, NULL);
+ if (!len)
+ goto err;
+
+ wchar_t *path = talloc_array(ta_ctx, wchar_t, len);
+ len = GetFullPathNameW(wfilename, len, path, NULL);
+ if (!len)
+ goto err;
+
+ lib = LoadLibraryW(path);
+
+err:
+ talloc_free(ta_ctx);
mp_dl_result.errcode = GetLastError();
return (void *)lib;
}
@@ -876,29 +891,3 @@ void freelocale(locale_t locobj)
}
#endif // __MINGW32__
-
-int mp_mkostemps(char *template, int suffixlen, int flags)
-{
- size_t len = strlen(template);
- char *t = len >= 6 + suffixlen ? &template[len - (6 + suffixlen)] : NULL;
- if (!t || strncmp(t, "XXXXXX", 6) != 0) {
- errno = EINVAL;
- return -1;
- }
-
- for (size_t fuckshit = 0; fuckshit < UINT32_MAX; fuckshit++) {
- // Using a random value may make it require fewer iterations (even if
- // not truly random; just a counter would be sufficient).
- size_t fuckmess = mp_rand_next();
- char crap[7] = "";
- snprintf(crap, sizeof(crap), "%06zx", fuckmess);
- memcpy(t, crap, 6);
-
- int res = open(template, O_RDWR | O_CREAT | O_EXCL | flags, 0600);
- if (res >= 0 || errno != EEXIST)
- return res;
- }
-
- errno = EEXIST;
- return -1;
-}
diff --git a/osdep/io.h b/osdep/io.h
index db711fb..3944eb9 100644
--- a/osdep/io.h
+++ b/osdep/io.h
@@ -29,6 +29,8 @@
#include <fcntl.h>
#include <locale.h>
+#include "compiler.h"
+
#if HAVE_GLOB_POSIX
#include <glob.h>
#endif
@@ -78,9 +80,19 @@ int mp_make_wakeup_pipe(int pipes[2]);
void mp_flush_wakeup_pipe(int pipe_end);
#ifdef _WIN32
+
#include <wchar.h>
wchar_t *mp_from_utf8(void *talloc_ctx, const char *s);
char *mp_to_utf8(void *talloc_ctx, const wchar_t *s);
+
+// Use this in win32-specific code rather than PATH_MAX or MAX_PATH.
+// This is necessary because we declare long-path aware support which raises
+// the effective limit without affecting any defines.
+// The actual limit is 32767 but there's a few edge cases that reduce
+// it. So pick this nice round number.
+// Note that this is wchars, not chars.
+#define MP_PATH_MAX (32000)
+
#endif
#ifdef __CYGWIN__
@@ -94,8 +106,10 @@ char *mp_to_utf8(void *talloc_ctx, const wchar_t *s);
#include <sys/stat.h>
#include <fcntl.h>
-int mp_printf(const char *format, ...);
-int mp_fprintf(FILE *stream, const char *format, ...);
+size_t mp_fwrite(const void *restrict buffer, size_t size, size_t count,
+ FILE *restrict stream);
+int mp_printf(const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
+int mp_fprintf(FILE *stream, const char *format, ...) PRINTF_ATTRIBUTE(2, 3);
int mp_open(const char *filename, int oflag, ...);
int mp_creat(const char *filename, int mode);
int mp_rename(const char *oldpath, const char *newpath);
@@ -104,6 +118,7 @@ DIR *mp_opendir(const char *path);
struct dirent *mp_readdir(DIR *dir);
int mp_closedir(DIR *dir);
int mp_mkdir(const char *path, int mode);
+int mp_unlink(const char *path);
char *mp_win32_getcwd(char *buf, size_t size);
char *mp_getenv(const char *name);
@@ -161,6 +176,7 @@ int mp_glob(const char *restrict pattern, int flags,
int (*errfunc)(const char*, int), mp_glob_t *restrict pglob);
void mp_globfree(mp_glob_t *pglob);
+#define fwrite(...) mp_fwrite(__VA_ARGS__)
#define printf(...) mp_printf(__VA_ARGS__)
#define fprintf(...) mp_fprintf(__VA_ARGS__)
#define open(...) mp_open(__VA_ARGS__)
@@ -171,6 +187,7 @@ void mp_globfree(mp_glob_t *pglob);
#define readdir(...) mp_readdir(__VA_ARGS__)
#define closedir(...) mp_closedir(__VA_ARGS__)
#define mkdir(...) mp_mkdir(__VA_ARGS__)
+#define unlink(...) mp_unlink(__VA_ARGS__)
#define getcwd(...) mp_win32_getcwd(__VA_ARGS__)
#define getenv(...) mp_getenv(__VA_ARGS__)
@@ -227,6 +244,4 @@ extern char **environ;
#endif /* __MINGW32__ */
-int mp_mkostemps(char *template, int suffixlen, int flags);
-
#endif
diff --git a/osdep/language-apple.c b/osdep/language-mac.c
index dc83fb5..32a94ae 100644
--- a/osdep/language-apple.c
+++ b/osdep/language-mac.c
@@ -19,7 +19,7 @@
#include "misc/language.h"
-#include "apple_utils.h"
+#include "utils-mac.h"
#include "mpv_talloc.h"
char **mp_get_user_langs(void)
diff --git a/osdep/macosx_application.h b/osdep/mac/app_bridge.h
index 753b9f0..db03c8e 100644
--- a/osdep/macosx_application.h
+++ b/osdep/mac/app_bridge.h
@@ -15,25 +15,27 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MPV_MACOSX_APPLICATION
-#define MPV_MACOSX_APPLICATION
+#pragma once
-#include "osdep/macosx_menubar.h"
+#include <stdbool.h>
#include "options/m_option.h"
+struct input_ctx;
+struct mpv_handle;
+
enum {
FRAME_VISIBLE = 0,
FRAME_WHOLE,
};
enum {
- RENDER_TIMER_CALLBACK = 0,
- RENDER_TIMER_PRECISE,
+ RENDER_TIMER_PRESENTATION_FEEDBACK = -1,
RENDER_TIMER_SYSTEM,
+ RENDER_TIMER_CALLBACK,
+ RENDER_TIMER_PRECISE,
};
struct macos_opts {
- int macos_title_bar_style;
int macos_title_bar_appearance;
int macos_title_bar_material;
struct m_color macos_title_bar_color;
@@ -46,10 +48,12 @@ struct macos_opts {
bool cocoa_cb_10bit_context;
};
+void cocoa_init_media_keys(void);
+void cocoa_uninit_media_keys(void);
+void cocoa_set_input_context(struct input_ctx *input_context);
+void cocoa_set_mpv_handle(struct mpv_handle *ctx);
+void cocoa_init_cocoa_cb(void);
// multithreaded wrapper for mpv_main
int cocoa_main(int argc, char *argv[]);
-void cocoa_register_menu_item_action(MPMenuKey key, void* action);
extern const struct m_sub_options macos_conf;
-
-#endif /* MPV_MACOSX_APPLICATION */
diff --git a/osdep/mac/app_bridge.m b/osdep/mac/app_bridge.m
new file mode 100644
index 0000000..bf39efe
--- /dev/null
+++ b/osdep/mac/app_bridge.m
@@ -0,0 +1,123 @@
+/*
+ * 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/>.
+ */
+
+#include "config.h"
+
+#import "osdep/mac/app_bridge_objc.h"
+
+#if HAVE_SWIFT
+#include "osdep/mac/swift.h"
+#endif
+
+#define OPT_BASE_STRUCT struct macos_opts
+const struct m_sub_options macos_conf = {
+ .opts = (const struct m_option[]) {
+ {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance,
+ {"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
+ {"vibrantLight", 3}, {"vibrantDark", 4},
+ {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
+ {"vibrantLightHighContrast", 7},
+ {"vibrantDarkHighContrast", 8})},
+ {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material,
+ {"titlebar", 0}, {"selection", 1}, {"menu", 2},
+ {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
+ {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
+ {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
+ {"underWindowBackground", 12}, {"underPageBackground", 13},
+ {"dark", 14}, {"light", 15}, {"mediumLight", 16},
+ {"ultraDark", 17})},
+ {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)},
+ {"macos-fs-animation-duration",
+ OPT_CHOICE(macos_fs_animation_duration, {"default", -1}),
+ M_RANGE(0, 1000)},
+ {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)},
+ {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy,
+ {"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
+ {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
+ {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
+ {"macos-render-timer", OPT_CHOICE(macos_render_timer,
+ {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
+ {"system", RENDER_TIMER_SYSTEM}, {"feedback", RENDER_TIMER_PRESENTATION_FEEDBACK})},
+ {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
+ {0}
+ },
+ .size = sizeof(struct macos_opts),
+ .defaults = &(const struct macos_opts){
+ .macos_title_bar_color = {0, 0, 0, 0},
+ .macos_fs_animation_duration = -1,
+ .macos_render_timer = RENDER_TIMER_CALLBACK,
+ .cocoa_cb_sw_renderer = -1,
+ .cocoa_cb_10bit_context = true
+ },
+};
+
+static const char app_icon[] =
+#include "TOOLS/osxbundle/icon.icns.inc"
+;
+
+NSData *app_bridge_icon(void)
+{
+ return [NSData dataWithBytesNoCopy:(void *)app_icon length:sizeof(app_icon) - 1 freeWhenDone:NO];
+}
+
+void app_bridge_tarray_append(void *t, char ***a, int *i, char *s)
+{
+ MP_TARRAY_APPEND(t, *a, *i, s);
+}
+
+const struct m_sub_options *app_bridge_mac_conf(void)
+{
+ return &macos_conf;
+}
+
+const struct m_sub_options *app_bridge_vo_conf(void)
+{
+ return &vo_sub_opts;
+}
+
+void cocoa_init_media_keys(void)
+{
+ [[AppHub shared] startRemote];
+}
+
+void cocoa_uninit_media_keys(void)
+{
+ [[AppHub shared] stopRemote];
+}
+
+void cocoa_set_input_context(struct input_ctx *input_context)
+{
+ [[AppHub shared] initInput:input_context];
+}
+
+void cocoa_set_mpv_handle(struct mpv_handle *ctx)
+{
+ [[AppHub shared] initMpv:ctx];
+}
+
+void cocoa_init_cocoa_cb(void)
+{
+ [[AppHub shared] initCocoaCb];
+}
+
+int cocoa_main(int argc, char *argv[])
+{
+ return [(Application *)[Application sharedApplication] main:argc :argv];
+}
+
diff --git a/osdep/macOS_swift_bridge.h b/osdep/mac/app_bridge_objc.h
index 9407b6f..6020198 100644
--- a/osdep/macOS_swift_bridge.h
+++ b/osdep/mac/app_bridge_objc.h
@@ -15,9 +15,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-// including frameworks here again doesn't make sense, but otherwise the swift
-// compiler doesn't include the needed headers in our generated header file
-#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#include "player/client.h"
@@ -27,13 +25,14 @@
#include "options/m_config.h"
#include "player/core.h"
#include "input/input.h"
+#include "input/event.h"
+#include "input/keycodes.h"
#include "video/out/win_state.h"
-#include "osdep/macosx_application_objc.h"
-#include "osdep/macosx_events_objc.h"
+#include "osdep/main-fn.h"
+#include "osdep/mac/app_bridge.h"
-
-// complex macros won't get imported to Swift so we have to reassign them
+// 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;
@@ -48,10 +47,10 @@ static int SWIFT_MBTN9 = MP_MBTN9;
static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE;
static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER;
-// only used from Swift files and therefore seen as unused by the c compiler
-static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) __attribute__ ((unused));
+static const char *swift_mpv_version = mpv_version;
+static const char *swift_mpv_copyright = mpv_copyright;
-static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s)
-{
- MP_TARRAY_APPEND(t, *a, *i, s);
-}
+NSData *app_bridge_icon(void);
+void app_bridge_tarray_append(void *t, char ***a, int *i, char *s);
+const struct m_sub_options *app_bridge_mac_conf(void);
+const struct m_sub_options *app_bridge_vo_conf(void);
diff --git a/osdep/mac/app_hub.swift b/osdep/mac/app_hub.swift
new file mode 100644
index 0000000..997cc33
--- /dev/null
+++ b/osdep/mac/app_hub.swift
@@ -0,0 +1,130 @@
+/*
+ * 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 AppHub: NSObject {
+ @objc static let shared = AppHub()
+
+ var mpv: OpaquePointer?
+ var input: InputHelper
+ var log: LogHelper
+ var option: OptionHelper?
+ var event: EventHelper?
+ var menu: MenuBar?
+#if HAVE_MACOS_MEDIA_PLAYER
+ var remote: RemoteCommandCenter?
+#endif
+#if HAVE_MACOS_TOUCHBAR
+ var touchBar: TouchBar?
+#endif
+#if HAVE_MACOS_COCOA_CB
+ var cocoaCb: CocoaCB?
+#endif
+
+ let MPV_PROTOCOL: String = "mpv://"
+ var isApplication: Bool { get { NSApp is Application } }
+ var openEvents: Int = 0
+
+ private override init() {
+ input = InputHelper()
+ log = LogHelper()
+ super.init()
+ if isApplication { menu = MenuBar(self) }
+#if HAVE_MACOS_MEDIA_PLAYER
+ remote = RemoteCommandCenter(self)
+#endif
+ log.verbose("AppHub initialised")
+ }
+
+ @objc func initMpv(_ mpv: OpaquePointer) {
+ event = EventHelper(self, mpv)
+ if let mpv = event?.mpv {
+ self.mpv = mpv
+ log.log = mp_log_new(nil, mp_client_get_log(mpv), "app")
+ option = OptionHelper(UnsafeMutablePointer(mpv), mp_client_get_global(mpv))
+ input.option = option
+ }
+
+#if HAVE_MACOS_MEDIA_PLAYER
+ remote?.registerEvents()
+#endif
+#if HAVE_MACOS_TOUCHBAR
+ touchBar = TouchBar(self)
+#endif
+ log.verbose("AppHub functionality initialised")
+ }
+
+ @objc func initInput(_ input: OpaquePointer?) {
+ log.verbose("Initialising Input")
+ self.input.signal(input: input)
+ }
+
+ @objc func initCocoaCb() {
+#if HAVE_MACOS_COCOA_CB
+ if !isApplication { return }
+ log.verbose("Initialising CocoaCB")
+ DispatchQueue.main.sync {
+ self.cocoaCb = self.cocoaCb ?? CocoaCB(mpv_create_client(mpv, "cocoacb"))
+ }
+#endif
+ }
+
+ @objc func startRemote() {
+#if HAVE_MACOS_MEDIA_PLAYER
+ log.verbose("Starting RemoteCommandCenter")
+ remote?.start()
+#endif
+ }
+
+ @objc func stopRemote() {
+#if HAVE_MACOS_MEDIA_PLAYER
+ log.verbose("Stoping RemoteCommandCenter")
+ remote?.stop()
+#endif
+ }
+
+ func open(urls: [URL]) {
+ let files = urls.map {
+ if $0.isFileURL { return $0.path }
+ var path = $0.absoluteString
+ if path.hasPrefix(MPV_PROTOCOL) { path.removeFirst(MPV_PROTOCOL.count) }
+ return path.removingPercentEncoding ?? path
+ }.sorted { (strL: String, strR: String) -> Bool in
+ return strL.localizedStandardCompare(strR) == .orderedAscending
+ }
+ log.verbose("\(openEvents > 0 ? "Appending" : "Opening") dropped files: \(files)")
+ input.open(files: files, append: openEvents > 0)
+ openEvents += 1
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.openEvents -= 1 }
+ }
+
+ func getIcon() -> NSImage {
+ guard let iconData = app_bridge_icon(), let icon = NSImage(data: iconData) else {
+ return NSImage(size: NSSize(width: 1, height: 1))
+ }
+ return icon
+ }
+
+ func getMacConf() -> UnsafePointer<m_sub_options>? {
+ return app_bridge_mac_conf()
+ }
+
+ func getVoConf() -> UnsafePointer<m_sub_options>? {
+ return app_bridge_vo_conf()
+ }
+}
diff --git a/osdep/mac/application.swift b/osdep/mac/application.swift
new file mode 100644
index 0000000..30f37a6
--- /dev/null
+++ b/osdep/mac/application.swift
@@ -0,0 +1,123 @@
+/*
+ * 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 Application: NSApplication, NSApplicationDelegate {
+ var appHub: AppHub { get { return AppHub.shared } }
+ var eventManager: NSAppleEventManager { get { return NSAppleEventManager.shared() } }
+ var isBundle: Bool { get { return ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" } }
+ var playbackThreadId: mp_thread!
+ var argc: Int32?
+ var argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
+
+ override init() {
+ super.init()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func sendEvent(_ event: NSEvent) {
+ if modalWindow != nil || !appHub.input.processKey(event: event) {
+ super.sendEvent(event)
+ }
+ appHub.input.wakeup()
+ }
+
+#if HAVE_MACOS_TOUCHBAR
+ override func makeTouchBar() -> NSTouchBar? {
+ return appHub.touchBar
+ }
+#endif
+
+ func application(_ application: NSApplication, open urls: [URL]) {
+ appHub.open(urls: urls)
+ }
+
+ func applicationWillFinishLaunching(_ notification: Notification) {
+ // register quit and exit events
+ eventManager.setEventHandler(
+ self,
+ andSelector: #selector(handleQuit(event:replyEvent:)),
+ forEventClass: AEEventClass(kCoreEventClass),
+ andEventID: kAEQuitApplication
+ )
+ atexit_b({
+ // clean up after exit() was called
+ DispatchQueue.main.async {
+ NSApp.hide(NSApp)
+ NSApp.setActivationPolicy(.prohibited)
+ self.eventManager.removeEventHandler(forEventClass: AEEventClass(kCoreEventClass), andEventID: kAEQuitApplication)
+ }
+ })
+ }
+
+ // quit from App icon, external quit from NSWorkspace
+ @objc func handleQuit(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
+ // send quit to core, terminates mpv_main called in playbackThread,
+ if !appHub.input.command("quit") {
+ appHub.log.warning("Could not properly shut down mpv")
+ exit(1)
+ }
+ }
+
+ func setupBundle() {
+ if !isBundle { return }
+
+ // started from finder the first argument after the binary may start with -psn_
+ if CommandLine.argc > 1 && CommandLine.arguments[1].hasPrefix("-psn_") {
+ argc? = 1
+ argv?[1] = nil
+ }
+
+ let path = (ProcessInfo.processInfo.environment["PATH"] ?? "") +
+ ":/usr/local/bin:/usr/local/sbin:/opt/local/bin:/opt/local/sbin"
+ appHub.log.verbose("Setting Bundle $PATH to: \(path)")
+ _ = path.withCString { setenv("PATH", $0, 1) }
+ }
+
+ let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
+ let application: Application = TypeHelper.bridge(ptr: ptr)
+ mp_thread_set_name("core/playback")
+ let exitCode: Int32 = mpv_main(application.argc ?? 1, application.argv)
+ // exit of any proper shut down
+ exit(exitCode)
+ }
+
+ @objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> Int {
+ self.argc = argc
+ self.argv = argv
+
+ NSApp = self
+ NSApp.delegate = self
+ NSApp.setActivationPolicy(isBundle ? .regular : .accessory)
+ setupBundle()
+ pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self))
+ appHub.input.wait()
+ NSApp.run()
+
+ // should never be reached
+ print("""
+ There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \
+ Please report this issues to a developer.\n
+ """)
+ pthread_join(playbackThreadId, nil)
+ return 1
+ }
+}
diff --git a/osdep/mac/event_helper.swift b/osdep/mac/event_helper.swift
new file mode 100644
index 0000000..277a9aa
--- /dev/null
+++ b/osdep/mac/event_helper.swift
@@ -0,0 +1,147 @@
+/*
+ * 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
+
+protocol EventSubscriber: AnyObject {
+ var uid: Int { get }
+ func handle(event: EventHelper.Event)
+}
+
+extension EventSubscriber {
+ var uid: Int { get { return Int(bitPattern: ObjectIdentifier(self)) }}
+}
+
+extension EventHelper {
+ typealias wakeup_cb = (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
+
+ struct Event {
+ var id: String {
+ get { name + (name.starts(with: "MPV_EVENT_") ? "" : String(format.rawValue)) }
+ }
+ var idReset: String {
+ get { name + (name.starts(with: "MPV_EVENT_") ? "" : String(MPV_FORMAT_NONE.rawValue)) }
+ }
+ let name: String
+ let format: mpv_format
+ let string: String?
+ let bool: Bool?
+ let double: Double?
+
+ init(
+ name: String = "",
+ format: mpv_format = MPV_FORMAT_NONE,
+ string: String? = nil,
+ bool: Bool? = nil,
+ double: Double? = nil
+
+ ) {
+ self.name = name
+ self.format = format
+ self.string = string
+ self.bool = bool
+ self.double = double
+ }
+ }
+}
+
+class EventHelper {
+ unowned let appHub: AppHub
+ var mpv: OpaquePointer?
+ var events: [String:[Int:EventSubscriber]] = [:]
+
+ init?(_ appHub: AppHub, _ mpv: OpaquePointer) {
+ if !appHub.isApplication {
+ mpv_destroy(mpv)
+ return nil
+ }
+
+ self.appHub = appHub
+ self.mpv = mpv
+ mpv_set_wakeup_callback(mpv, wakeup, TypeHelper.bridge(obj: self))
+ }
+
+ func subscribe(_ subscriber: any EventSubscriber, event: Event) {
+ guard let mpv = mpv else { return }
+
+ if !event.name.isEmpty {
+ if !events.keys.contains(event.idReset) {
+ events[event.idReset] = [:]
+ }
+ if !events.keys.contains(event.id) {
+ mpv_observe_property(mpv, 0, event.name, event.format)
+ events[event.id] = [:]
+ }
+ events[event.idReset]?[subscriber.uid] = subscriber
+ events[event.id]?[subscriber.uid] = subscriber
+ }
+ }
+
+ let wakeup: wakeup_cb = { ( ctx ) in
+ let event = unsafeBitCast(ctx, to: EventHelper.self)
+ DispatchQueue.main.async { event.eventLoop() }
+ }
+
+ func eventLoop() {
+ while let mpv = mpv, let event = mpv_wait_event(mpv, 0) {
+ if event.pointee.event_id == MPV_EVENT_NONE { break }
+ handle(event: event)
+ }
+ }
+
+ func handle(event: UnsafeMutablePointer<mpv_event>) {
+ switch event.pointee.event_id {
+ case MPV_EVENT_PROPERTY_CHANGE:
+ handle(property: event)
+ default:
+ for (_, subscriber) in events[String(describing: event.pointee.event_id)] ?? [:] {
+ subscriber.handle(event: .init(name: String(describing: event.pointee.event_id)))
+ }
+ }
+
+ if event.pointee.event_id == MPV_EVENT_SHUTDOWN {
+ mpv_destroy(mpv)
+ mpv = nil
+ }
+ }
+
+ func handle(property mpvEvent: UnsafeMutablePointer<mpv_event>) {
+ let pData = OpaquePointer(mpvEvent.pointee.data)
+ guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else {
+ return
+ }
+
+ let name = String(cString: property.name)
+ let format = property.format
+ for (_, subscriber) in events[name + String(format.rawValue)] ?? [:] {
+ var event: Event? = nil
+ switch format {
+ case MPV_FORMAT_STRING:
+ event = .init(name: name, format: format, string: TypeHelper.toString(property.data))
+ case MPV_FORMAT_FLAG:
+ event = .init(name: name, format: format, bool: TypeHelper.toBool(property.data))
+ case MPV_FORMAT_DOUBLE:
+ event = .init(name: name, format: format, double: TypeHelper.toDouble(property.data))
+ case MPV_FORMAT_NONE:
+ event = .init(name: name, format: format)
+ default: break
+ }
+
+ if let e = event { subscriber.handle(event: e) }
+ }
+ }
+}
diff --git a/osdep/mac/input_helper.swift b/osdep/mac/input_helper.swift
new file mode 100644
index 0000000..0e4ec1f
--- /dev/null
+++ b/osdep/mac/input_helper.swift
@@ -0,0 +1,275 @@
+/*
+ * 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 Carbon.HIToolbox
+
+class InputHelper: NSObject {
+ var option: OptionHelper?
+ var lock = NSCondition()
+ private var input: OpaquePointer?
+
+ let keymap: [mp_keymap] = [
+ // special keys
+ .init(kVK_Return, MP_KEY_ENTER), .init(kVK_Escape, MP_KEY_ESC),
+ .init(kVK_Delete, MP_KEY_BACKSPACE), .init(kVK_Tab, MP_KEY_TAB),
+ .init(kVK_VolumeUp, MP_KEY_VOLUME_UP), .init(kVK_VolumeDown, MP_KEY_VOLUME_DOWN),
+ .init(kVK_Mute, MP_KEY_MUTE),
+
+ // cursor keys
+ .init(kVK_UpArrow, MP_KEY_UP), .init(kVK_DownArrow, MP_KEY_DOWN),
+ .init(kVK_LeftArrow, MP_KEY_LEFT), .init(kVK_RightArrow, MP_KEY_RIGHT),
+
+ // navigation block
+ .init(kVK_Help, MP_KEY_INSERT), .init(kVK_ForwardDelete, MP_KEY_DELETE),
+ .init(kVK_Home, MP_KEY_HOME), .init(kVK_End, MP_KEY_END),
+ .init(kVK_PageUp, MP_KEY_PAGE_UP), .init(kVK_PageDown, MP_KEY_PAGE_DOWN),
+
+ // F-keys
+ .init(kVK_F1, MP_KEY_F + 1), .init(kVK_F2, MP_KEY_F + 2), .init(kVK_F3, MP_KEY_F + 3),
+ .init(kVK_F4, MP_KEY_F + 4), .init(kVK_F5, MP_KEY_F + 5), .init(kVK_F6, MP_KEY_F + 6),
+ .init(kVK_F7, MP_KEY_F + 7), .init(kVK_F8, MP_KEY_F + 8), .init(kVK_F9, MP_KEY_F + 9),
+ .init(kVK_F10, MP_KEY_F + 10), .init(kVK_F11, MP_KEY_F + 11), .init(kVK_F12, MP_KEY_F + 12),
+ .init(kVK_F13, MP_KEY_F + 13), .init(kVK_F14, MP_KEY_F + 14), .init(kVK_F15, MP_KEY_F + 15),
+ .init(kVK_F16, MP_KEY_F + 16), .init(kVK_F17, MP_KEY_F + 17), .init(kVK_F18, MP_KEY_F + 18),
+ .init(kVK_F19, MP_KEY_F + 19), .init(kVK_F20, MP_KEY_F + 20),
+
+ // numpad
+ .init(kVK_ANSI_KeypadPlus, Int32(Character("+").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadMinus, Int32(Character("-").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadMultiply, Int32(Character("*").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadDivide, Int32(Character("/").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadEnter, MP_KEY_KPENTER), .init(kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC),
+ .init(kVK_ANSI_Keypad0, MP_KEY_KP0), .init(kVK_ANSI_Keypad1, MP_KEY_KP1),
+ .init(kVK_ANSI_Keypad2, MP_KEY_KP2), .init(kVK_ANSI_Keypad3, MP_KEY_KP3),
+ .init(kVK_ANSI_Keypad4, MP_KEY_KP4), .init(kVK_ANSI_Keypad5, MP_KEY_KP5),
+ .init(kVK_ANSI_Keypad6, MP_KEY_KP6), .init(kVK_ANSI_Keypad7, MP_KEY_KP7),
+ .init(kVK_ANSI_Keypad8, MP_KEY_KP8), .init(kVK_ANSI_Keypad9, MP_KEY_KP9),
+
+ .init(0, 0)
+ ]
+
+ init(_ input: OpaquePointer? = nil, _ option: OptionHelper? = nil) {
+ super.init()
+ self.input = input
+ self.option = option
+ }
+
+ func put(
+ key: Int32,
+ modifiers: NSEvent.ModifierFlags = .init(rawValue: 0),
+ type: NSEvent.EventType = .applicationDefined
+ ) {
+ lock.withLock {
+ putKey(key, modifiers: modifiers, type: type)
+ }
+ }
+
+ private func putKey(
+ _ key: Int32,
+ modifiers: NSEvent.ModifierFlags = .init(rawValue: 0),
+ type: NSEvent.EventType = .applicationDefined
+ ) {
+ if key < 1 { return }
+
+ guard let input = input else { return }
+ let code = key | mapModifier(modifiers) | mapType(type)
+ mp_input_put_key(input, code)
+
+ if type == .keyUp {
+ mp_input_put_key(input, MP_INPUT_RELEASE_ALL)
+ }
+ }
+
+ @objc func processKey(event: NSEvent) -> Bool {
+ if event.type != .keyDown && event.type != .keyUp { return false }
+ if NSApp.mainMenu?.performKeyEquivalent(with: event) ?? false || event.isARepeat { return true }
+
+ return lock.withLock {
+ let mpkey = lookup_keymap_table(keymap, Int32(event.keyCode))
+ if mpkey > 0 {
+ putKey(mpkey, modifiers: event.modifierFlags, type: event.type)
+ return true
+ }
+
+ guard let chars = event.characters, let charsNoMod = event.charactersIgnoringModifiers else { return false }
+ let key = (useAltGr() && event.modifierFlags.contains(.optionRight)) ? chars : charsNoMod
+ key.withCString {
+ var bstr = bstr0($0)
+ putKey(bstr_decode_utf8(bstr, &bstr), modifiers: event.modifierFlags, type: event.type)
+ }
+ return true
+ }
+ }
+
+ func processMouse(event: NSEvent) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: event.type)
+ if event.clickCount > 1 {
+ putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: .keyUp)
+ }
+ }
+ }
+
+ func processWheel(event: NSEvent) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ guard let input = input else { return }
+ let modifiers = event.modifierFlags
+ let precise = event.hasPreciseScrollingDeltas
+ var deltaX = event.deltaX * 0.1
+ var deltaY = event.deltaY * 0.1
+
+ if !precise {
+ deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX
+ deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY
+ }
+
+ var key = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN
+ var delta = Double(deltaY)
+ if abs(deltaX) > abs(deltaY) {
+ key = deltaX > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT
+ delta = Double(deltaX)
+ }
+
+ mp_input_put_wheel(input, key | mapModifier(modifiers), precise ? abs(delta) : 1)
+ }
+ }
+
+ func draggable(at pos: NSPoint) -> Bool {
+ lock.withLock {
+ guard let input = input else { return false }
+ return !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y))
+ }
+ }
+
+ func mouseEnabled() -> Bool {
+ lock.withLock {
+ guard let input = input else { return true }
+ return mp_input_mouse_enabled(input)
+ }
+ }
+
+ func setMouse(position: NSPoint) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ guard let input = input else { return }
+ mp_input_set_mouse_pos(input, Int32(position.x), Int32(position.y))
+ }
+ }
+
+ @discardableResult @objc func command(_ cmd: String) -> Bool {
+ lock.withLock {
+ guard let input = input else { return false }
+ let cCmd = UnsafePointer<Int8>(strdup(cmd))
+ let mpvCmd = mp_input_parse_cmd(input, bstr0(cCmd), "")
+ mp_input_queue_cmd(input, mpvCmd)
+ free(UnsafeMutablePointer(mutating: cCmd))
+ return true
+ }
+ }
+
+ private func mapType(_ type: NSEvent.EventType) -> Int32 {
+ let typeMapping: [NSEvent.EventType:UInt32] = [
+ .keyDown: MP_KEY_STATE_DOWN,
+ .keyUp: MP_KEY_STATE_UP,
+ .leftMouseDown: MP_KEY_STATE_DOWN,
+ .leftMouseUp: MP_KEY_STATE_UP,
+ .rightMouseDown: MP_KEY_STATE_DOWN,
+ .rightMouseUp: MP_KEY_STATE_UP,
+ .otherMouseDown: MP_KEY_STATE_DOWN,
+ .otherMouseUp: MP_KEY_STATE_UP,
+ ]
+
+ return Int32(typeMapping[type] ?? 0);
+ }
+
+ private func mapModifier(_ modifiers: NSEvent.ModifierFlags) -> Int32 {
+ var mask: UInt32 = 0;
+
+ if modifiers.contains(.shift) {
+ mask |= MP_KEY_MODIFIER_SHIFT
+ }
+ if modifiers.contains(.control) {
+ mask |= MP_KEY_MODIFIER_CTRL
+ }
+ if modifiers.contains(.command) {
+ mask |= MP_KEY_MODIFIER_META
+ }
+ if modifiers.contains(.optionLeft) || modifiers.contains(.optionRight) && !useAltGr() {
+ mask |= MP_KEY_MODIFIER_ALT
+ }
+
+ return Int32(mask)
+ }
+
+ private func map(button: Int) -> Int32 {
+ let buttonMapping: [Int:Int32] = [
+ 0: SWIFT_MBTN_LEFT,
+ 1: SWIFT_MBTN_RIGHT,
+ 2: SWIFT_MBTN_MID,
+ 3: SWIFT_MBTN_FORWARD,
+ 4: SWIFT_MBTN_BACK,
+ ]
+
+ return Int32(buttonMapping[button] ?? SWIFT_MBTN9 + Int32(button - 5));
+ }
+
+ @objc func open(files: [String], append: Bool = false) {
+ lock.withLock {
+ guard let input = input else { return }
+ if (option?.vo.drag_and_drop ?? -1) == -2 { return }
+
+ var action = DND_APPEND
+ if !append {
+ action = NSEvent.modifierFlags.contains(.shift) ? DND_APPEND : DND_REPLACE
+ if (option?.vo.drag_and_drop ?? -1) >= 0 {
+ action = mp_dnd_action(UInt32(option?.vo.drag_and_drop ?? Int32(DND_REPLACE.rawValue)))
+ }
+ }
+
+ let filesClean = files.map{ $0.hasPrefix("file:///.file/id=") ? (URL(string: $0)?.path ?? $0) : $0 }
+ var filesPtr = filesClean.map { UnsafeMutablePointer<CChar>(strdup($0)) }
+ mp_event_drop_files(input, Int32(files.count), &filesPtr, action)
+ for charPtr in filesPtr { free(UnsafeMutablePointer(mutating: charPtr)) }
+ }
+ }
+
+ private func useAltGr() -> Bool {
+ guard let input = input else { return false }
+ return mp_input_use_alt_gr(input)
+ }
+
+ @objc func wakeup() {
+ lock.withLock {
+ guard let input = input else { return }
+ mp_input_wakeup(input)
+ }
+ }
+
+ func signal(input: OpaquePointer? = nil) {
+ lock.withLock {
+ self.input = input
+ if input != nil { lock.signal() }
+ }
+ }
+
+ @objc func wait() {
+ lock.withLock { while input == nil { lock.wait() } }
+ }
+}
diff --git a/osdep/macos/libmpv_helper.swift b/osdep/mac/libmpv_helper.swift
index 8b1c697..23953eb 100644
--- a/osdep/macos/libmpv_helper.swift
+++ b/osdep/mac/libmpv_helper.swift
@@ -23,27 +23,14 @@ let glDummy: @convention(c) () -> Void = {}
class LibmpvHelper {
var log: LogHelper
- var mpvHandle: OpaquePointer?
+ var mpv: OpaquePointer?
var mpvRenderContext: OpaquePointer?
- var macOptsPtr: UnsafeMutableRawPointer?
- var macOpts: macos_opts = macos_opts()
var fbo: GLint = 1
- let deinitLock = NSLock()
-
- init(_ mpv: OpaquePointer, _ mpLog: OpaquePointer?) {
- mpvHandle = mpv
- log = LogHelper(mpLog)
-
- guard let app = NSApp as? Application,
- let ptr = mp_get_config_group(nil,
- mp_client_get_global(mpvHandle),
- app.getMacOSConf()) else
- {
- log.sendError("macOS config group couldn't be retrieved'")
- exit(1)
- }
- macOptsPtr = ptr
- macOpts = UnsafeMutablePointer<macos_opts>(OpaquePointer(ptr)).pointee
+ let uninitLock = NSLock()
+
+ init(_ mpv: OpaquePointer, _ log: LogHelper) {
+ self.mpv = mpv
+ self.log = log
}
func initRender() {
@@ -52,7 +39,7 @@ class LibmpvHelper {
let pAddress = mpv_opengl_init_params(get_proc_address: getProcAddress,
get_proc_address_ctx: nil)
- MPVHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in
+ TypeHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in
var params: [mpv_render_param] = [
mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: pointers[0]),
@@ -60,12 +47,11 @@ class LibmpvHelper {
mpv_render_param()
]
- if (mpv_render_context_create(&mpvRenderContext, mpvHandle, &params) < 0) {
- log.sendError("Render context init has failed.")
+ if (mpv_render_context_create(&mpvRenderContext, mpv, &params) < 0) {
+ log.error("Render context init has failed.")
exit(1)
}
}
-
}
let getProcAddress: (@convention(c) (UnsafeMutableRawPointer?, UnsafePointer<Int8>?)
@@ -87,17 +73,17 @@ class LibmpvHelper {
func setRenderUpdateCallback(_ callback: @escaping mpv_render_update_fn, context object: AnyObject) {
if mpvRenderContext == nil {
- log.sendWarning("Init mpv render context first.")
+ log.warning("Init mpv render context first.")
} else {
- mpv_render_context_set_update_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object))
+ mpv_render_context_set_update_callback(mpvRenderContext, callback, TypeHelper.bridge(obj: object))
}
}
func setRenderControlCallback(_ callback: @escaping mp_render_cb_control_fn, context object: AnyObject) {
if mpvRenderContext == nil {
- log.sendWarning("Init mpv render context first.")
+ log.warning("Init mpv render context first.")
} else {
- mp_render_context_set_control_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object))
+ mp_render_context_set_control_callback(mpvRenderContext, callback, TypeHelper.bridge(obj: object))
}
}
@@ -107,18 +93,18 @@ class LibmpvHelper {
}
func isRenderUpdateFrame() -> Bool {
- deinitLock.lock()
+ uninitLock.lock()
if mpvRenderContext == nil {
- deinitLock.unlock()
+ uninitLock.unlock()
return false
}
let flags: UInt64 = mpv_render_context_update(mpvRenderContext)
- deinitLock.unlock()
+ uninitLock.unlock()
return flags & UInt64(MPV_RENDER_UPDATE_FRAME.rawValue) > 0
}
func drawRender(_ surface: NSSize, _ depth: GLint, _ ctx: CGLContextObj, skip: Bool = false) {
- deinitLock.lock()
+ uninitLock.lock()
if mpvRenderContext != nil {
var i: GLint = 0
let flip: CInt = 1
@@ -134,7 +120,7 @@ class LibmpvHelper {
h: Int32(surface.height),
internal_format: 0)
- MPVHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in
+ TypeHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in
var params: [mpv_render_param] = [
mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: pointers[0]),
mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: pointers[1]),
@@ -151,13 +137,13 @@ class LibmpvHelper {
if !skip { CGLFlushDrawable(ctx) }
- deinitLock.unlock()
+ uninitLock.unlock()
}
func setRenderICCProfile(_ profile: NSColorSpace) {
if mpvRenderContext == nil { return }
guard var iccData = profile.iccProfileData else {
- log.sendWarning("Invalid ICC profile data.")
+ log.warning("Invalid ICC profile data.")
return
}
iccData.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) in
@@ -182,69 +168,14 @@ class LibmpvHelper {
}
}
- 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)) }
- }
-
- // Unsafe function when called while using the render API
- func command(_ cmd: String) {
- if mpvHandle == nil { return }
- mpv_command_string(mpvHandle, cmd)
- }
-
- 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? {
- guard let mpv = mpvHandle else { return nil }
- guard let value = mpv_get_property_string(mpv, name) else { return nil }
- let str = String(cString: value)
- mpv_free(value)
- return str
- }
-
- func deinitRender() {
+ func uninit() {
mpv_render_context_set_update_callback(mpvRenderContext, nil, nil)
mp_render_context_set_control_callback(mpvRenderContext, nil, nil)
- deinitLock.lock()
+ uninitLock.lock()
mpv_render_context_free(mpvRenderContext)
mpvRenderContext = nil
- deinitLock.unlock()
- }
-
- func deinitMPV(_ destroy: Bool = false) {
- if destroy {
- mpv_destroy(mpvHandle)
- }
- ta_free(macOptsPtr)
- macOptsPtr = nil
- mpvHandle = nil
- }
-
- // *(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
+ mpv_destroy(mpv)
+ mpv = nil
+ uninitLock.unlock()
}
}
diff --git a/osdep/mac/log_helper.swift b/osdep/mac/log_helper.swift
new file mode 100644
index 0000000..3d349a4
--- /dev/null
+++ b/osdep/mac/log_helper.swift
@@ -0,0 +1,67 @@
+/*
+ * 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 os
+
+class LogHelper {
+ var log: OpaquePointer?
+ let logger = Logger(subsystem: "io.mpv", category: "mpv")
+
+ let loggerMapping: [Int:OSLogType] = [
+ MSGL_V: .debug,
+ MSGL_INFO: .info,
+ MSGL_WARN: .error,
+ MSGL_ERR: .fault,
+ ]
+
+ init(_ log: OpaquePointer? = nil) {
+ self.log = log
+ }
+
+ func verbose(_ message: String) {
+ send(message: message, type: MSGL_V)
+ }
+
+ func info(_ message: String) {
+ send(message: message, type: MSGL_INFO)
+ }
+
+ func warning(_ message: String) {
+ send(message: message, type: MSGL_WARN)
+ }
+
+ func error(_ message: String) {
+ send(message: message, type: MSGL_ERR)
+ }
+
+ func send(message: String, type: Int) {
+ guard let log = log else {
+ logger.log(level: loggerMapping[type] ?? .default, "\(message, privacy: .public)")
+ return
+ }
+
+ let args: [CVarArg] = [(message as NSString).utf8String ?? "NO MESSAGE"]
+ mp_msg_va(log, Int32(type), "%s\n", getVaList(args))
+ }
+
+ deinit {
+ // only a manual dereferencing will trigger this, cleanup properly in that case
+ ta_free(UnsafeMutablePointer(log))
+ log = nil
+ }
+}
diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift
new file mode 100644
index 0000000..b58367a
--- /dev/null
+++ b/osdep/mac/menu_bar.swift
@@ -0,0 +1,397 @@
+/*
+ * 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
+
+extension MenuBar {
+ class MenuItem: NSMenuItem {
+ var config: Config?
+ }
+
+ enum `Type`: Comparable {
+ case menu
+ case menuServices
+ case separator
+ case item
+ case itemNormalSize
+ case itemHalfSize
+ case itemDoubleSize
+ case itemMinimize
+ case itemZoom
+ }
+
+ struct Config {
+ let name: String
+ let key: String
+ let modifiers: NSEvent.ModifierFlags
+ let type: Type
+ let action: Selector?
+ let target: AnyObject?
+ let command: String
+ let url: String
+ var configs: [Config]
+
+ init(
+ name: String = "",
+ key: String = "",
+ modifiers: NSEvent.ModifierFlags = .command,
+ type: Type = .item,
+ action: Selector? = nil,
+ target: AnyObject? = nil,
+ command: String = "",
+ url: String = "",
+ configs: [Config] = []
+ ) {
+ self.name = name
+ self.key = key
+ self.modifiers = modifiers
+ self.type = configs.isEmpty ? type : .menu
+ self.action = action
+ self.target = target
+ self.command = command
+ self.url = url
+ self.configs = configs
+ }
+ }
+}
+
+class MenuBar: NSObject {
+ unowned let appHub: AppHub
+ let mainMenu = NSMenu(title: "Main")
+ let servicesMenu = NSMenu(title: "Services")
+ var menuConfigs: [Config] = []
+ var dynamicMenuItems: [Type:[MenuItem]] = [:]
+ let appIcon: NSImage
+
+ @objc init(_ appHub: AppHub) {
+ self.appHub = appHub
+ UserDefaults.standard.set(false, forKey: "NSFullScreenMenuItemEverywhere")
+ UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem")
+ UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem")
+ NSWindow.allowsAutomaticWindowTabbing = false
+ appIcon = appHub.getIcon()
+
+ super.init()
+
+ let appMenuConfigs = [
+ Config(name: "About mpv", action: #selector(about), target: self),
+ Config(type: .separator),
+ Config(
+ name: "Settings…",
+ key: ",",
+ action: #selector(settings(_:)),
+ target: self,
+ url: "mpv.conf"
+ ),
+ Config(
+ name: "Keyboard Shortcuts Config…",
+ action: #selector(settings(_:)),
+ target: self,
+ url: "input.conf"
+ ),
+ Config(type: .separator),
+ Config(name: "Services", type: .menuServices),
+ Config(type: .separator),
+ Config(name: "Hide mpv", key: "h", action: #selector(NSApp.hide(_:))),
+ Config(name: "Hide Others", key: "h", modifiers: [.command, .option], action: #selector(NSApp.hideOtherApplications(_:))),
+ Config(name: "Show All", action: #selector(NSApp.unhideAllApplications(_:))),
+ Config(type: .separator),
+ Config(name: "Quit and Remember Position", action: #selector(command(_:)), target: self, command: "quit-watch-later"),
+ Config(name: "Quit mpv", key: "q", action: #selector(command(_:)), target: self, command: "quit"),
+ ]
+
+ let fileMenuConfigs = [
+ Config(name: "Open File…", key: "o", action: #selector(openFiles), target: self),
+ Config(name: "Open URL…", key: "O", action: #selector(openUrl), target: self),
+ Config(name: "Open Playlist…", action: #selector(openPlaylist), target: self),
+ Config(type: .separator),
+ Config(name: "Close", key: "w", action: #selector(NSWindow.performClose(_:))),
+ Config(name: "Save Screenshot", action: #selector(command(_:)), target: self, command: "async screenshot"),
+ ]
+
+ let editMenuConfigs = [
+ Config(name: "Undo", key: "z", action: Selector(("undo:"))),
+ Config(name: "Redo", key: "Z", action: Selector(("redo:"))),
+ Config(type: .separator),
+ Config(name: "Cut", key: "x", action: #selector(NSText.cut(_:))),
+ Config(name: "Copy", key: "c", action: #selector(NSText.copy(_:))),
+ Config(name: "Paste", key: "v", action: #selector(NSText.paste(_:))),
+ Config(name: "Select All", key: "a", action: #selector(NSResponder.selectAll(_:))),
+ ]
+
+ var viewMenuConfigs = [
+ Config(name: "Toggle Fullscreen", action: #selector(command(_:)), target: self, command: "cycle fullscreen"),
+ Config(name: "Toggle Float on Top", action: #selector(command(_:)), target: self, command: "cycle ontop"),
+ Config(
+ name: "Toggle Visibility on All Workspaces",
+ action: #selector(command(_:)),
+ target: self,
+ command: "cycle on-all-workspaces"
+ ),
+ ]
+#if HAVE_MACOS_TOUCHBAR
+ viewMenuConfigs += [
+ Config(type: .separator),
+ Config(name: "Customize Touch Bar…", action: #selector(NSApp.toggleTouchBarCustomizationPalette(_:))),
+ ]
+#endif
+
+ let videoMenuConfigs = [
+ Config(name: "Zoom Out", action: #selector(command(_:)), target: self, command: "add panscan -0.1"),
+ Config(name: "Zoom In", action: #selector(command(_:)), target: self, command: "add panscan 0.1"),
+ Config(name: "Reset Zoom", action: #selector(command(_:)), target: self, command: "set panscan 0"),
+ Config(type: .separator),
+ Config(name: "Aspect Ratio 4:3", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"4:3\""),
+ Config(name: "Aspect Ratio 16:9", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"16:9\""),
+ Config(name: "Aspect Ratio 1.85:1", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"1.85:1\""),
+ Config(name: "Aspect Ratio 2.35:1", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"2.35:1\""),
+ Config(name: "Reset Aspect Ratio", action: #selector(command(_:)), target: self, command: "set video-aspect-override \"-1\""),
+ Config(type: .separator),
+ Config(name: "Rotate Left", action: #selector(command(_:)), target: self, command: "cycle-values video-rotate 0 270 180 90"),
+ Config(name: "Rotate Right", action: #selector(command(_:)), target: self, command: "cycle-values video-rotate 90 180 270 0"),
+ Config(name: "Reset Rotation", action: #selector(command(_:)), target: self, command: "set video-rotate 0"),
+ Config(type: .separator),
+ Config(name: "Half Size", key: "0", type: .itemHalfSize),
+ Config(name: "Normal Size", key: "1", type: .itemNormalSize),
+ Config(name: "Double Size", key: "2", type: .itemDoubleSize),
+ ]
+
+ let audioMenuConfigs = [
+ Config(name: "Next Audio Track", action: #selector(command(_:)), target: self, command: "cycle audio"),
+ Config(name: "Previous Audio Track", action: #selector(command(_:)), target: self, command: "cycle audio down"),
+ Config(type: .separator),
+ Config(name: "Toggle Mute", action: #selector(command(_:)), target: self, command: "cycle mute"),
+ Config(type: .separator),
+ Config(name: "Play Audio Later", action: #selector(command(_:)), target: self, command: "add audio-delay 0.1"),
+ Config(name: "Play Audio Earlier", action: #selector(command(_:)), target: self, command: "add audio-delay -0.1"),
+ Config(name: "Reset Audio Delay", action: #selector(command(_:)), target: self, command: "set audio-delay 0.0"),
+ ]
+
+ let subtitleMenuConfigs = [
+ Config(name: "Next Subtitle Track", action: #selector(command(_:)), target: self, command: "cycle sub"),
+ Config(name: "Previous Subtitle Track", action: #selector(command(_:)), target: self, command: "cycle sub down"),
+ Config(type: .separator),
+ Config(name: "Toggle Force Style", action: #selector(command(_:)), target: self, command: "cycle-values sub-ass-override \"force\" \"no\""),
+ Config(type: .separator),
+ Config(name: "Display Subtitles Later", action: #selector(command(_:)), target: self, command: "add sub-delay 0.1"),
+ Config(name: "Display Subtitles Earlier", action: #selector(command(_:)), target: self, command: "add sub-delay -0.1"),
+ Config(name: "Reset Subtitle Delay", action: #selector(command(_:)), target: self, command: "set sub-delay 0.0"),
+ ]
+
+ let playbackMenuConfigs = [
+ Config(name: "Toggle Pause", action: #selector(command(_:)), target: self, command: "cycle pause"),
+ Config(name: "Increase Speed", action: #selector(command(_:)), target: self, command: "add speed 0.1"),
+ Config(name: "Decrease Speed", action: #selector(command(_:)), target: self, command: "add speed -0.1"),
+ Config(name: "Reset Speed", action: #selector(command(_:)), target: self, command: "set speed 1.0"),
+ Config(type: .separator),
+ Config(name: "Show Playlist", action: #selector(command(_:)), target: self, command: "script-message osc-playlist"),
+ Config(name: "Show Chapters", action: #selector(command(_:)), target: self, command: "script-message osc-chapterlist"),
+ Config(name: "Show Tracks", action: #selector(command(_:)), target: self, command: "script-message osc-tracklist"),
+ Config(type: .separator),
+ Config(name: "Next File", action: #selector(command(_:)), target: self, command: "playlist-next"),
+ Config(name: "Previous File", action: #selector(command(_:)), target: self, command: "playlist-prev"),
+ Config(name: "Toggle Loop File", action: #selector(command(_:)), target: self, command: "cycle-values loop-file \"inf\" \"no\""),
+ Config(name: "Toggle Loop Playlist", action: #selector(command(_:)), target: self, command: "cycle-values loop-playlist \"inf\" \"no\""),
+ Config(name: "Shuffle", action: #selector(command(_:)), target: self, command: "playlist-shuffle"),
+ Config(type: .separator),
+ Config(name: "Next Chapter", action: #selector(command(_:)), target: self, command: "add chapter 1"),
+ Config(name: "Previous Chapter", action: #selector(command(_:)), target: self, command: "add chapter -1"),
+ Config(type: .separator),
+ Config(name: "Step Forward", action: #selector(command(_:)), target: self, command: "frame-step"),
+ Config(name: "Step Backward", action: #selector(command(_:)), target: self, command: "frame-back-step"),
+ ]
+
+ let windowMenuConfigs = [
+ Config(name: "Minimize", key: "m", type: .itemMinimize),
+ Config(name: "Zoom", type: .itemZoom),
+ ]
+
+ var helpMenuConfigs = [
+ Config(name: "mpv Website…", action: #selector(url(_:)), target: self, url: "https://mpv.io"),
+ Config(name: "mpv on GitHub…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv"),
+ Config(type: .separator),
+ Config(name: "Online Manual…", action: #selector(url(_:)), target: self, url: "https://mpv.io/manual/master/"),
+ Config(name: "Online Wiki…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/wiki"),
+ Config(name: "Release Notes…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES"),
+ Config(name: "Keyboard Shortcuts…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/blob/master/etc/input.conf"),
+ Config(type: .separator),
+ Config(name: "Report Issue…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/issues/new/choose"),
+ ]
+ if ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" {
+ helpMenuConfigs += [
+ Config(name: "Show log File…", action: #selector(showFile(_:)), target: self, url: NSHomeDirectory() + "/Library/Logs/mpv.log")
+ ]
+ }
+
+ menuConfigs = [
+ Config(name: "Apple", configs: appMenuConfigs),
+ Config(name: "File", configs: fileMenuConfigs),
+ Config(name: "Edit", configs: editMenuConfigs),
+ Config(name: "View", configs: viewMenuConfigs),
+ Config(name: "Video", configs: videoMenuConfigs),
+ Config(name: "Audio", configs: audioMenuConfigs),
+ Config(name: "Subtitle", configs: subtitleMenuConfigs),
+ Config(name: "Playback", configs: playbackMenuConfigs),
+ Config(name: "Window", configs: windowMenuConfigs),
+ Config(name: "Help", configs: helpMenuConfigs),
+ ]
+
+ createMenu(parentMenu: mainMenu, configs: menuConfigs)
+ NSApp.mainMenu = mainMenu
+ NSApp.servicesMenu = servicesMenu
+ }
+
+ func createMenu(parentMenu: NSMenu, configs: [Config]) {
+ for config in configs {
+ let item = createMenuItem(parentMenu: parentMenu, config: config)
+
+ if config.type <= .menuServices {
+ let menu = config.type == .menuServices ? servicesMenu : NSMenu(title: config.name)
+ item.submenu = menu
+ createMenu(parentMenu: menu, configs: config.configs)
+ }
+
+ if config.type > Type.item {
+ dynamicMenuItems[config.type] = (dynamicMenuItems[config.type] ?? []) + [item]
+ }
+ }
+ }
+
+ func createMenuItem(parentMenu: NSMenu, config: Config) -> MenuItem {
+ var item = MenuItem(title: config.name, action: config.action, keyEquivalent: config.key)
+ item.config = config
+ item.target = config.target
+ item.keyEquivalentModifierMask = config.modifiers
+
+ if config.type == .separator {
+ item = MenuItem.separator() as? MenuItem ?? item
+ }
+ parentMenu.addItem(item)
+
+ return item
+ }
+
+ @objc func about() {
+ NSApp.orderFrontStandardAboutPanel(options: [
+ .applicationName: "mpv",
+ .applicationIcon: appIcon,
+ .applicationVersion: String(cString: swift_mpv_version),
+ .init(rawValue: "Copyright"): String(cString: swift_mpv_copyright),
+ ])
+ }
+
+ @objc func settings(_ menuItem: MenuItem) {
+ guard let menuConfig = menuItem.config else { return }
+ let configPaths: [URL] = [
+ URL(fileURLWithPath: NSHomeDirectory() + "/.config/mpv/", isDirectory: true),
+ URL(fileURLWithPath: NSHomeDirectory() + "/.mpv/", isDirectory: true),
+ ]
+
+ for path in configPaths {
+ let configFile = path.appendingPathComponent(menuConfig.url, isDirectory: false)
+
+ if FileManager.default.fileExists(atPath: configFile.path) {
+ if NSWorkspace.shared.open(configFile) {
+ return
+ }
+ NSWorkspace.shared.open(path)
+ alert(title: "No Application found to open your config file.", text: "Please open the \(menuConfig.url) file with your preferred text editor in the now open folder to edit your config.")
+ return
+ }
+
+ if NSWorkspace.shared.open(path) {
+ alert(title: "No config file found.", text: "Please create a \(menuConfig.url) file with your preferred text editor in the now open folder.")
+ return
+ }
+ }
+ }
+
+ @objc func openFiles() {
+ let panel = NSOpenPanel()
+ panel.allowsMultipleSelection = true
+ panel.canChooseDirectories = true
+
+ if panel.runModal() == .OK {
+ appHub.input.open(files: panel.urls.map { $0.path })
+ }
+ }
+
+ @objc func openPlaylist() {
+ let panel = NSOpenPanel()
+
+ if panel.runModal() == .OK, let url = panel.urls.first {
+ appHub.input.command("loadlist \"\(url.path)\"")
+ }
+ }
+
+ @objc func openUrl() {
+ let alert = NSAlert()
+ alert.messageText = "Open URL"
+ alert.icon = appIcon
+ alert.addButton(withTitle: "Ok")
+ alert.addButton(withTitle: "Cancel")
+
+ let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
+ input.placeholderString = "URL"
+ alert.accessoryView = input
+
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
+ input.becomeFirstResponder()
+ }
+
+ if alert.runModal() == .alertFirstButtonReturn && input.stringValue.count > 0 {
+ appHub.input.open(files: [input.stringValue])
+ }
+ }
+
+ @objc func command(_ menuItem: MenuItem) {
+ guard let menuConfig = menuItem.config else { return }
+ appHub.input.command(menuConfig.command)
+ }
+
+ @objc func url(_ menuItem: MenuItem) {
+ guard let menuConfig = menuItem.config,
+ let url = URL(string: menuConfig.url) else { return }
+ NSWorkspace.shared.open(url)
+ }
+
+ @objc func showFile(_ menuItem: MenuItem) {
+ guard let menuConfig = menuItem.config else { return }
+ let url = URL(fileURLWithPath: menuConfig.url)
+ if FileManager.default.fileExists(atPath: url.path) {
+ NSWorkspace.shared.activateFileViewerSelecting([url])
+ return
+ }
+
+ alert(title: "No log File found.", text: "You deactivated logging for the Bundle.")
+ }
+
+ func alert(title: String, text: String) {
+ let alert = NSAlert()
+ alert.messageText = title
+ alert.informativeText = text
+ alert.icon = appIcon
+ alert.addButton(withTitle: "Ok")
+ alert.runModal()
+ }
+
+ func register(_ selector: Selector, key: Type) {
+ for menuItem in dynamicMenuItems[key] ?? [] {
+ menuItem.action = selector
+ }
+ }
+}
diff --git a/osdep/meson.build b/osdep/mac/meson.build
index 21baafc..8ddbdba 100644
--- a/osdep/meson.build
+++ b/osdep/mac/meson.build
@@ -1,8 +1,8 @@
# custom swift targets
-bridge = join_paths(source_root, 'osdep/macOS_swift_bridge.h')
-header = join_paths(build_root, 'osdep/macOS_swift.h')
-module = join_paths(build_root, 'osdep/macOS_swift.swiftmodule')
-target = join_paths(build_root, 'osdep/macOS_swift.o')
+bridge = join_paths(source_root, 'osdep/mac/app_bridge_objc.h')
+header = join_paths(build_root, 'osdep/mac/swift.h')
+module = join_paths(build_root, 'osdep/mac/swift.swiftmodule')
+target = join_paths(build_root, 'osdep/mac/swift.o')
swift_flags = ['-frontend', '-c', '-sdk', macos_sdk_path,
'-enable-objc-interop', '-emit-objc-header', '-parse-as-library']
@@ -19,10 +19,22 @@ if get_option('optimization') != '0'
swift_flags += '-O'
endif
+if macos_cocoa_cb.allowed()
+ swift_flags += ['-D', 'HAVE_MACOS_COCOA_CB']
+endif
+
+if macos_touchbar.allowed()
+ swift_flags += ['-D', 'HAVE_MACOS_TOUCHBAR']
+endif
+
+if macos_media_player.allowed()
+ swift_flags += ['-D', 'HAVE_MACOS_MEDIA_PLAYER']
+endif
+
extra_flags = get_option('swift-flags').split()
swift_flags += extra_flags
-swift_compile = [swift_prog, swift_flags, '-module-name', 'macOS_swift',
+swift_compile = [swift_prog, swift_flags, '-module-name', 'swift',
'-emit-module-path', '@OUTPUT0@', '-import-objc-header', bridge,
'-emit-objc-header-path', '@OUTPUT1@', '-o', '@OUTPUT2@',
'@INPUT@', '-I.', '-I' + source_root,
@@ -31,7 +43,7 @@ swift_compile = [swift_prog, swift_flags, '-module-name', 'macOS_swift',
swift_targets = custom_target('swift_targets',
input: swift_sources,
- output: ['macOS_swift.swiftmodule', 'macOS_swift.h', 'macOS_swift.o'],
+ output: ['swift.swiftmodule', 'swift.h', 'swift.o'],
command: swift_compile,
)
sources += swift_targets
diff --git a/osdep/mac/option_helper.swift b/osdep/mac/option_helper.swift
new file mode 100644
index 0000000..c44f090
--- /dev/null
+++ b/osdep/mac/option_helper.swift
@@ -0,0 +1,74 @@
+/*
+ * 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
+
+typealias swift_wakeup_cb_fn = (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
+
+class OptionHelper {
+ var voCachePtr: UnsafeMutablePointer<m_config_cache>
+ var macCachePtr: UnsafeMutablePointer<m_config_cache>
+
+ var voPtr: UnsafeMutablePointer<mp_vo_opts>
+ { get { return UnsafeMutablePointer<mp_vo_opts>(OpaquePointer(voCachePtr.pointee.opts)) } }
+ var macPtr: UnsafeMutablePointer<macos_opts>
+ { get { return UnsafeMutablePointer<macos_opts>(OpaquePointer(macCachePtr.pointee.opts)) } }
+
+ // these computed properties return a local copy of the struct accessed:
+ // - don't use if you rely on the pointers
+ // - only for reading
+ var vo: mp_vo_opts { get { return voPtr.pointee } }
+ var mac: macos_opts { get { return macPtr.pointee } }
+
+ init(_ taParent: UnsafeMutableRawPointer, _ global: OpaquePointer?) {
+ voCachePtr = m_config_cache_alloc(taParent, global, AppHub.shared.getVoConf())
+ macCachePtr = m_config_cache_alloc(taParent, global, AppHub.shared.getMacConf())
+ }
+
+ func nextChangedOption(property: inout UnsafeMutableRawPointer?) -> Bool {
+ return m_config_cache_get_next_changed(voCachePtr, &property)
+ }
+
+ func setOption(fullscreen: Bool) {
+ voPtr.pointee.fullscreen = fullscreen
+ _ = withUnsafeMutableBytes(of: &voPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(voCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setOption(minimized: Bool) {
+ voPtr.pointee.window_minimized = minimized
+ _ = withUnsafeMutableBytes(of: &voPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(voCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setOption(maximized: Bool) {
+ voPtr.pointee.window_maximized = maximized
+ _ = withUnsafeMutableBytes(of: &voPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(voCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) {
+ m_config_cache_set_wakeup_cb(macCachePtr, callback, TypeHelper.bridge(obj: object))
+ }
+
+ func nextChangedMacOption(property: inout UnsafeMutableRawPointer?) -> Bool {
+ return m_config_cache_get_next_changed(macCachePtr, &property)
+ }
+}
diff --git a/osdep/macos/precise_timer.swift b/osdep/mac/precise_timer.swift
index f4ad3bb..d4837f9 100644
--- a/osdep/macos/precise_timer.swift
+++ b/osdep/mac/precise_timer.swift
@@ -24,7 +24,6 @@ struct Timing {
class PreciseTimer {
unowned var common: Common
- var mpv: MPVHelper? { get { return common.mpv } }
let nanoPerSecond: Double = 1e+9
let machToNano: Double = {
@@ -59,9 +58,9 @@ class PreciseTimer {
init?(common com: Common) {
common = com
- pthread_create(&thread, &threadAttr, entryC, MPVHelper.bridge(obj: self))
+ pthread_create(&thread, &threadAttr, entryC, TypeHelper.bridge(obj: self))
if thread == nil {
- common.log.sendWarning("Couldn't create pthread for high precision timer")
+ common.log.warning("Couldn't create pthread for high precision timer")
return nil
}
@@ -85,7 +84,7 @@ class PreciseTimer {
isHighPrecision = success == KERN_SUCCESS
if !isHighPrecision {
- common.log.sendWarning("Couldn't create a high precision timer")
+ common.log.warning("Couldn't create a high precision timer")
}
}
@@ -119,7 +118,7 @@ class PreciseTimer {
let threadSignal: @convention(c) (Int32) -> () = { (sig: Int32) in }
let entryC: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
- let ptimer: PreciseTimer = MPVHelper.bridge(ptr: ptr)
+ let ptimer: PreciseTimer = TypeHelper.bridge(ptr: ptr)
ptimer.entry()
return nil
}
diff --git a/osdep/mac/presentation.swift b/osdep/mac/presentation.swift
new file mode 100644
index 0000000..c1d521a
--- /dev/null
+++ b/osdep/mac/presentation.swift
@@ -0,0 +1,56 @@
+/*
+ * 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/>.
+ */
+
+extension Presentation {
+ struct Time {
+ let cvTime: CVTimeStamp
+ var skipped: Int64 = 0
+ var time: Int64 { return mp_time_ns_from_raw_time(mp_raw_time_ns_from_mach(cvTime.hostTime)) }
+ var duration: Int64 {
+ let durationSeconds = Double(cvTime.videoRefreshPeriod) / Double(cvTime.videoTimeScale)
+ return Int64(durationSeconds * Presentation.nanoPerSecond * cvTime.rateScalar)
+ }
+
+ init(_ time: CVTimeStamp) {
+ cvTime = time
+ }
+ }
+}
+
+class Presentation {
+ unowned var common: Common
+ var times: [Time] = []
+ static let nanoPerSecond: Double = 1e+9
+
+ init(common com: Common) {
+ common = com
+ }
+
+ func add(time: CVTimeStamp) {
+ times.append(Time(time))
+ }
+
+ func next() -> Time? {
+ let now = mp_time_ns()
+ let count = times.count
+ times.removeAll(where: { $0.time <= now })
+ var time = times.first
+ time?.skipped = Int64(max(count - times.count - 1, 0))
+
+ return time
+ }
+}
diff --git a/osdep/mac/remote_command_center.swift b/osdep/mac/remote_command_center.swift
new file mode 100644
index 0000000..0e0eaeb
--- /dev/null
+++ b/osdep/mac/remote_command_center.swift
@@ -0,0 +1,202 @@
+/*
+ * 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 MediaPlayer
+
+extension RemoteCommandCenter {
+ typealias ConfigHandler = (MPRemoteCommandEvent) -> (MPRemoteCommandHandlerStatus)
+
+ enum KeyType {
+ case normal
+ case repeatable
+ }
+
+ struct Config {
+ let key: Int32
+ let type: KeyType
+ var state: NSEvent.EventType = .applicationDefined
+ let handler: ConfigHandler
+
+ init(key: Int32 = 0, type: KeyType = .normal, handler: @escaping ConfigHandler = { event in return .commandFailed }) {
+ self.key = key
+ self.type = type
+ self.handler = handler
+ }
+ }
+}
+
+class RemoteCommandCenter: EventSubscriber {
+ unowned let appHub: AppHub
+ var event: EventHelper? { get { return appHub.event } }
+ var input: InputHelper { get { return appHub.input } }
+ var configs: [MPRemoteCommand:Config] = [:]
+ var disabledCommands: [MPRemoteCommand] = []
+ var isPaused: Bool = false { didSet { updateInfoCenter() } }
+ var duration: Double = 0 { didSet { updateInfoCenter() } }
+ var position: Double = 0 { didSet { updateInfoCenter() } }
+ var rate: Double = 1 { didSet { updateInfoCenter() } }
+ var title: String = "" { didSet { updateInfoCenter() } }
+ var chapter: String? { didSet { updateInfoCenter() } }
+ var album: String? { didSet { updateInfoCenter() } }
+ var artist: String? { didSet { updateInfoCenter() } }
+ var cover: NSImage
+
+ var infoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } }
+ var commandCenter: MPRemoteCommandCenter { get { return MPRemoteCommandCenter.shared() } }
+
+ init(_ appHub: AppHub) {
+ self.appHub = appHub
+ cover = appHub.getIcon()
+
+ configs = [
+ commandCenter.pauseCommand: Config(key: MP_KEY_PAUSEONLY, handler: keyHandler),
+ commandCenter.playCommand: Config(key: MP_KEY_PLAYONLY, handler: keyHandler),
+ commandCenter.stopCommand: Config(key: MP_KEY_STOP, handler: keyHandler),
+ commandCenter.nextTrackCommand: Config(key: MP_KEY_NEXT, handler: keyHandler),
+ commandCenter.previousTrackCommand: Config(key: MP_KEY_PREV, handler: keyHandler),
+ commandCenter.togglePlayPauseCommand: Config(key: MP_KEY_PLAY, handler: keyHandler),
+ commandCenter.seekForwardCommand: Config(key: MP_KEY_FORWARD, type: .repeatable, handler: keyHandler),
+ commandCenter.seekBackwardCommand: Config(key: MP_KEY_REWIND, type: .repeatable, handler: keyHandler),
+ commandCenter.changePlaybackPositionCommand: Config(handler: seekHandler),
+ ]
+
+ disabledCommands = [
+ commandCenter.changePlaybackRateCommand,
+ commandCenter.changeRepeatModeCommand,
+ commandCenter.changeShuffleModeCommand,
+ commandCenter.skipForwardCommand,
+ commandCenter.skipBackwardCommand,
+ commandCenter.enableLanguageOptionCommand,
+ commandCenter.disableLanguageOptionCommand,
+ commandCenter.ratingCommand,
+ commandCenter.likeCommand,
+ commandCenter.dislikeCommand,
+ commandCenter.bookmarkCommand,
+ ]
+
+ for cmd in disabledCommands {
+ cmd.isEnabled = false
+ }
+ }
+
+ func registerEvents() {
+ event?.subscribe(self, event: .init(name: "duration", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "time-pos", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "speed", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "pause", format: MPV_FORMAT_FLAG))
+ event?.subscribe(self, event: .init(name: "media-title", format: MPV_FORMAT_STRING))
+ event?.subscribe(self, event: .init(name: "chapter-metadata/title", format: MPV_FORMAT_STRING))
+ event?.subscribe(self, event: .init(name: "metadata/by-key/album", format: MPV_FORMAT_STRING))
+ event?.subscribe(self, event: .init(name: "metadata/by-key/artist", format: MPV_FORMAT_STRING))
+ }
+
+ func start() {
+ for (cmd, config) in configs {
+ cmd.isEnabled = true
+ cmd.addTarget(handler: config.handler)
+ }
+
+ updateInfoCenter()
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.makeCurrent),
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
+ }
+
+ func stop() {
+ for (cmd, _) in configs {
+ cmd.isEnabled = false
+ cmd.removeTarget(nil)
+ }
+
+ infoCenter.nowPlayingInfo = nil
+ infoCenter.playbackState = .unknown
+
+ NotificationCenter.default.removeObserver(
+ self,
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
+ }
+
+ @objc func makeCurrent(notification: NSNotification) {
+ infoCenter.playbackState = .paused
+ infoCenter.playbackState = .playing
+ updateInfoCenter()
+ }
+
+ func updateInfoCenter() {
+ infoCenter.playbackState = isPaused ? .paused : .playing
+ infoCenter.nowPlayingInfo = (infoCenter.nowPlayingInfo ?? [:]).merging([
+ MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
+ MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0),
+ MPNowPlayingInfoPropertyPlaybackRate: NSNumber(value: isPaused ? 0 : rate),
+ MPNowPlayingInfoPropertyElapsedPlaybackTime: NSNumber(value: position),
+ MPMediaItemPropertyPlaybackDuration: NSNumber(value: duration),
+ MPMediaItemPropertyTitle: title,
+ MPMediaItemPropertyArtist: artist ?? chapter ?? "",
+ MPMediaItemPropertyAlbumTitle: album ?? "",
+ MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: cover.size) { _ in return self.cover }
+ ]) { (_, new) in new }
+ }
+
+ lazy var keyHandler: ConfigHandler = { event in
+ guard let config = self.configs[event.command] else {
+ return .commandFailed
+ }
+
+ var state = config.state
+ if config.type == .repeatable {
+ state = config.state == .keyDown ? .keyUp : .keyDown
+ self.configs[event.command]?.state = state
+ }
+ self.input.put(key: config.key, type: state)
+
+ return .success
+ }
+
+ lazy var seekHandler: ConfigHandler = { event in
+ guard let posEvent = event as? MPChangePlaybackPositionCommandEvent else {
+ return .commandFailed
+ }
+
+ let cmd = String(format: "seek %.02f absolute", posEvent.positionTime)
+ return self.input.command(cmd) ? .success : .commandFailed
+ }
+
+ func handle(event: EventHelper.Event) {
+ switch event.name {
+ case "time-pos":
+ let newPosition = max(event.double ?? 0, 0)
+ if Int((floor(newPosition) - floor(position)) / rate) != 0 {
+ position = newPosition
+ }
+ case "pause": isPaused = event.bool ?? false
+ case "duration": duration = event.double ?? 0
+ case "speed": rate = event.double ?? 1
+ case "media-title": title = event.string ?? ""
+ case "chapter-metadata/title": chapter = event.string
+ case "metadata/by-key/album": album = event.string
+ case "metadata/by-key/artist": artist = event.string
+ default: break
+ }
+ }
+}
diff --git a/osdep/macos/swift_compat.swift b/osdep/mac/swift_compat.swift
index 83059da..83059da 100644
--- a/osdep/macos/swift_compat.swift
+++ b/osdep/mac/swift_compat.swift
diff --git a/osdep/mac/swift_extensions.swift b/osdep/mac/swift_extensions.swift
new file mode 100644
index 0000000..ed6c86c
--- /dev/null
+++ b/osdep/mac/swift_extensions.swift
@@ -0,0 +1,93 @@
+/*
+ * 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.hidsystem
+
+extension NSDeviceDescriptionKey {
+ static let screenNumber = NSDeviceDescriptionKey("NSScreenNumber")
+}
+
+extension NSScreen {
+ public var displayID: CGDirectDisplayID {
+ get {
+ return deviceDescription[.screenNumber] as? CGDirectDisplayID ?? 0
+ }
+ }
+}
+
+extension NSColor {
+ convenience init(hex: String) {
+ let int = Int(hex.dropFirst(), radix: 16) ?? 0
+ let alpha = CGFloat((int >> 24) & 0x000000FF)/255
+ let red = CGFloat((int >> 16) & 0x000000FF)/255
+ let green = CGFloat((int >> 8) & 0x000000FF)/255
+ let blue = CGFloat((int) & 0x000000FF)/255
+
+ self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha)
+ }
+}
+
+extension NSEvent.ModifierFlags {
+ public static var optionLeft: NSEvent.ModifierFlags = .init(rawValue: UInt(NX_DEVICELALTKEYMASK))
+ public static var optionRight: NSEvent.ModifierFlags = .init(rawValue: UInt(NX_DEVICERALTKEYMASK))
+}
+
+extension mp_keymap {
+ init(_ f: Int, _ t: Int32) {
+ self.init(from: Int32(f), to: t)
+ }
+}
+
+extension mpv_event_id: CustomStringConvertible {
+ public var description: String {
+ switch self {
+ case MPV_EVENT_NONE: return "MPV_EVENT_NONE2"
+ case MPV_EVENT_SHUTDOWN: return "MPV_EVENT_SHUTDOWN"
+ case MPV_EVENT_LOG_MESSAGE: return "MPV_EVENT_LOG_MESSAGE"
+ case MPV_EVENT_GET_PROPERTY_REPLY: return "MPV_EVENT_GET_PROPERTY_REPLY"
+ case MPV_EVENT_SET_PROPERTY_REPLY: return "MPV_EVENT_SET_PROPERTY_REPLY"
+ case MPV_EVENT_COMMAND_REPLY: return "MPV_EVENT_COMMAND_REPLY"
+ case MPV_EVENT_START_FILE: return "MPV_EVENT_START_FILE"
+ case MPV_EVENT_END_FILE: return "MPV_EVENT_END_FILE"
+ case MPV_EVENT_FILE_LOADED: return "MPV_EVENT_FILE_LOADED"
+ case MPV_EVENT_IDLE: return "MPV_EVENT_IDLE"
+ case MPV_EVENT_TICK: return "MPV_EVENT_TICK"
+ case MPV_EVENT_CLIENT_MESSAGE: return "MPV_EVENT_CLIENT_MESSAGE"
+ case MPV_EVENT_VIDEO_RECONFIG: return "MPV_EVENT_VIDEO_RECONFIG"
+ case MPV_EVENT_AUDIO_RECONFIG: return "MPV_EVENT_AUDIO_RECONFIG"
+ case MPV_EVENT_SEEK: return "MPV_EVENT_SEEK"
+ case MPV_EVENT_PLAYBACK_RESTART: return "MPV_EVENT_PLAYBACK_RESTART"
+ case MPV_EVENT_PROPERTY_CHANGE: return "MPV_EVENT_PROPERTY_CHANGE"
+ case MPV_EVENT_QUEUE_OVERFLOW: return "MPV_EVENT_QUEUE_OVERFLOW"
+ case MPV_EVENT_HOOK: return "MPV_EVENT_HOOK"
+ default: return "MPV_EVENT_" + String(self.rawValue)
+ }
+ }
+}
+
+extension Bool {
+ init(_ int32: Int32) {
+ self.init(int32 != 0)
+ }
+}
+
+extension Int32 {
+ init(_ bool: Bool) {
+ self.init(bool ? 1 : 0)
+ }
+}
diff --git a/osdep/mac/touch_bar.swift b/osdep/mac/touch_bar.swift
new file mode 100644
index 0000000..8e64c51
--- /dev/null
+++ b/osdep/mac/touch_bar.swift
@@ -0,0 +1,297 @@
+/*
+ * 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
+
+extension NSTouchBar.CustomizationIdentifier {
+ public static let customId: NSTouchBar.CustomizationIdentifier = "io.mpv.touchbar"
+}
+
+extension NSTouchBarItem.Identifier {
+ public static let seekBar = NSTouchBarItem.Identifier(custom: ".seekbar")
+ public static let play = NSTouchBarItem.Identifier(custom: ".play")
+ public static let nextItem = NSTouchBarItem.Identifier(custom: ".nextItem")
+ public static let previousItem = NSTouchBarItem.Identifier(custom: ".previousItem")
+ public static let nextChapter = NSTouchBarItem.Identifier(custom: ".nextChapter")
+ public static let previousChapter = NSTouchBarItem.Identifier(custom: ".previousChapter")
+ public static let cycleAudio = NSTouchBarItem.Identifier(custom: ".cycleAudio")
+ public static let cycleSubtitle = NSTouchBarItem.Identifier(custom: ".cycleSubtitle")
+ public static let currentPosition = NSTouchBarItem.Identifier(custom: ".currentPosition")
+ public static let timeLeft = NSTouchBarItem.Identifier(custom: ".timeLeft")
+
+ init(custom: String) {
+ self.init(NSTouchBar.CustomizationIdentifier.customId + custom)
+ }
+}
+
+extension TouchBar {
+ typealias ViewHandler = (Config) -> (NSView)
+
+ struct Config {
+ let name: String
+ let command: String
+ var item: NSCustomTouchBarItem?
+ var constraint: NSLayoutConstraint?
+ let image: NSImage
+ let imageAlt: NSImage
+ let handler: ViewHandler
+
+ init(
+ name: String = "",
+ command: String = "",
+ item: NSCustomTouchBarItem? = nil,
+ constraint: NSLayoutConstraint? = nil,
+ image: NSImage? = nil,
+ imageAlt: NSImage? = nil,
+ handler: @escaping ViewHandler = { _ in return NSButton(title: "", target: nil, action: nil) }
+ ) {
+ self.name = name
+ self.command = command
+ self.item = item
+ self.constraint = constraint
+ self.image = image ?? NSImage(size: NSSize(width: 1, height: 1))
+ self.imageAlt = imageAlt ?? NSImage(size: NSSize(width: 1, height: 1))
+ self.handler = handler
+ }
+ }
+}
+
+class TouchBar: NSTouchBar, NSTouchBarDelegate, EventSubscriber {
+ unowned let appHub: AppHub
+ var event: EventHelper? { get { return appHub.event } }
+ var input: InputHelper { get { return appHub.input } }
+ var configs: [NSTouchBarItem.Identifier:Config] = [:]
+ var isPaused: Bool = false { didSet { updatePlayButton() } }
+ var position: Double = 0 { didSet { updateTouchBarTimeItems() } }
+ var duration: Double = 0 { didSet { updateTouchBarTimeItems() } }
+ var rate: Double = 1
+
+ init(_ appHub: AppHub) {
+ self.appHub = appHub
+ super.init()
+
+ configs = [
+ .seekBar: Config(name: "Seek Bar", command: "seek %f absolute-percent", handler: createSlider),
+ .currentPosition: Config(name: "Current Position", handler: createText),
+ .timeLeft: Config(name: "Time Left", handler: createText),
+ .play: Config(
+ name: "Play Button",
+ command: "cycle pause",
+ image: .init(named: NSImage.touchBarPauseTemplateName),
+ imageAlt: .init(named: NSImage.touchBarPlayTemplateName),
+ handler: createButton
+ ),
+ .previousItem: Config(
+ name: "Previous Playlist Item",
+ command: "playlist-prev",
+ image: .init(named: NSImage.touchBarGoBackTemplateName),
+ handler: createButton
+ ),
+ .nextItem: Config(
+ name: "Next Playlist Item",
+ command: "playlist-next",
+ image: .init(named: NSImage.touchBarGoForwardTemplateName),
+ handler: createButton
+ ),
+ .previousChapter: Config(
+ name: "Previous Chapter",
+ command: "add chapter -1",
+ image: .init(named: NSImage.touchBarSkipBackTemplateName),
+ handler: createButton
+ ),
+ .nextChapter: Config(
+ name: "Next Chapter",
+ command: "add chapter 1",
+ image: .init(named: NSImage.touchBarSkipAheadTemplateName),
+ handler: createButton
+ ),
+ .cycleAudio: Config(
+ name: "Cycle Audio",
+ command: "cycle audio",
+ image: .init(named: NSImage.touchBarAudioInputTemplateName),
+ handler: createButton
+ ),
+ .cycleSubtitle: Config(
+ name: "Cycle Subtitle",
+ command: "cycle sub",
+ image: .init(named: NSImage.touchBarComposeTemplateName),
+ handler: createButton
+ )
+ ]
+
+ delegate = self
+ customizationIdentifier = .customId;
+ defaultItemIdentifiers = [.play, .previousItem, .nextItem, .seekBar]
+ customizationAllowedItemIdentifiers = [.play, .seekBar, .previousItem, .nextItem,
+ .previousChapter, .nextChapter, .cycleAudio, .cycleSubtitle, .currentPosition, .timeLeft]
+ addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
+
+ event?.subscribe(self, event: .init(name: "duration", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "time-pos", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "speed", format: MPV_FORMAT_DOUBLE))
+ event?.subscribe(self, event: .init(name: "pause", format: MPV_FORMAT_FLAG))
+ event?.subscribe(self, event: .init(name: "MPV_EVENT_END_FILE"))
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
+ guard let config = configs[identifier] else { return nil }
+
+ let item = NSCustomTouchBarItem(identifier: identifier)
+ item.view = config.handler(config)
+ item.customizationLabel = config.name
+ configs[identifier]?.item = item
+ item.addObserver(self, forKeyPath: "visible", options: [.new], context: nil)
+ return item
+ }
+
+ lazy var createButton: ViewHandler = { config in
+ return NSButton(image: config.image, target: self, action: #selector(Self.buttonAction(_:)))
+ }
+
+ lazy var createText: ViewHandler = { config in
+ let text = NSTextField(labelWithString: "0:00")
+ text.alignment = .center
+ return text
+ }
+
+ lazy var createSlider: ViewHandler = { config in
+ let slider = NSSlider(target: self, action: #selector(Self.seekbarChanged(_:)))
+ slider.minValue = 0
+ slider.maxValue = 100
+ return slider
+ }
+
+ override func observeValue(
+ forKeyPath keyPath: String?,
+ of object: Any?,
+ change: [NSKeyValueChangeKey:Any]?,
+ context: UnsafeMutableRawPointer?
+ ) {
+ guard let visible = change?[.newKey] as? Bool else { return }
+ if keyPath == "isVisible" && visible {
+ updateTouchBarTimeItems()
+ updatePlayButton()
+ }
+ }
+
+ func updateTouchBarTimeItems() {
+ if !isVisible { return }
+ updateSlider()
+ updateTimeLeft()
+ updateCurrentPosition()
+ }
+
+ func updateSlider() {
+ guard let config = configs[.seekBar], let slider = config.item?.view as? NSSlider else { return }
+ if !(config.item?.isVisible ?? false) { return }
+
+ slider.isEnabled = duration > 0
+ if !slider.isHighlighted {
+ slider.doubleValue = slider.isEnabled ? (position / duration) * 100 : 0
+ }
+ }
+
+ func updateTimeLeft() {
+ guard let config = configs[.timeLeft], let text = config.item?.view as? NSTextField else { return }
+ if !(config.item?.isVisible ?? false) { return }
+
+ removeConstraintFor(identifier: .timeLeft)
+ text.stringValue = duration > 0 ? "-" + format(time: Int(floor(duration) - floor(position))) : ""
+ if !text.stringValue.isEmpty {
+ applyConstraintFrom(string: "-" + format(time: Int(duration)), identifier: .timeLeft)
+ }
+ }
+
+ func updateCurrentPosition() {
+ guard let config = configs[.currentPosition], let text = config.item?.view as? NSTextField else { return }
+ if !(config.item?.isVisible ?? false) { return }
+
+ text.stringValue = format(time: Int(floor(position)))
+ removeConstraintFor(identifier: .currentPosition)
+ applyConstraintFrom(string: format(time: Int(duration > 0 ? duration : position)), identifier: .currentPosition)
+ }
+
+ func updatePlayButton() {
+ guard let config = configs[.play], let button = config.item?.view as? NSButton else { return }
+ if !isVisible || !(config.item?.isVisible ?? false) { return }
+ button.image = isPaused ? configs[.play]?.imageAlt : configs[.play]?.image
+ }
+
+ @objc func buttonAction(_ button: NSButton) {
+ guard let identifier = getIdentifierFrom(view: button), let command = configs[identifier]?.command else { return }
+ input.command(command)
+ }
+
+ @objc func seekbarChanged(_ slider: NSSlider) {
+ guard let identifier = getIdentifierFrom(view: slider), let command = configs[identifier]?.command else { return }
+ input.command(String(format: command, slider.doubleValue))
+ }
+
+ func format(time: Int) -> String {
+ let formatter = DateComponentsFormatter()
+ formatter.unitsStyle = .positional
+ formatter.zeroFormattingBehavior = time >= (60 * 60) ? [.dropLeading] : []
+ formatter.allowedUnits = time >= (60 * 60) ? [.hour, .minute, .second] : [.minute, .second]
+ return formatter.string(from: TimeInterval(time)) ?? "0:00"
+ }
+
+ func removeConstraintFor(identifier: NSTouchBarItem.Identifier) {
+ guard let text = configs[identifier]?.item?.view as? NSTextField,
+ let constraint = configs[identifier]?.constraint as? NSLayoutConstraint else { return }
+ text.removeConstraint(constraint)
+ }
+
+ func applyConstraintFrom(string: String, identifier: NSTouchBarItem.Identifier) {
+ guard let text = configs[identifier]?.item?.view as? NSTextField else { return }
+ let fullString = string.components(separatedBy: .decimalDigits).joined(separator: "0")
+ let textField = NSTextField(labelWithString: fullString)
+ let con = NSLayoutConstraint(item: text, attribute: .width, relatedBy: .equal, toItem: nil,
+ attribute: .notAnAttribute, multiplier: 1.1, constant: ceil(textField.frame.size.width))
+ text.addConstraint(con)
+ configs[identifier]?.constraint = con
+ }
+
+ func getIdentifierFrom(view: NSView) -> NSTouchBarItem.Identifier? {
+ for (identifier, config) in configs {
+ if config.item?.view == view {
+ return identifier
+ }
+ }
+ return nil
+ }
+
+ func handle(event: EventHelper.Event) {
+ switch event.name {
+ case "MPV_EVENT_END_FILE":
+ position = 0
+ duration = 0
+ case "time-pos":
+ let newPosition = max(event.double ?? 0, 0)
+ if Int((floor(newPosition) - floor(position)) / rate) != 0 {
+ position = newPosition
+ }
+ case "pause": isPaused = event.bool ?? false
+ case "duration": duration = event.double ?? 0
+ case "speed": rate = event.double ?? 1
+ default: break
+ }
+ }
+}
diff --git a/osdep/mac/type_helper.swift b/osdep/mac/type_helper.swift
new file mode 100644
index 0000000..bd90d0a
--- /dev/null
+++ b/osdep/mac/type_helper.swift
@@ -0,0 +1,69 @@
+/*
+ * 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/>.
+ */
+
+class TypeHelper {
+ // (__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()
+ }
+
+ class func withUnsafeMutableRawPointers(_ arguments: [Any],
+ pointers: [UnsafeMutableRawPointer?] = [],
+ closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) {
+ if arguments.count > 0 {
+ let args = Array(arguments.dropFirst(1))
+ var newPtrs = pointers
+ var firstArg = arguments.first
+ withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in
+ newPtrs.append(ptr.baseAddress)
+ withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure)
+ }
+
+ return
+ }
+
+ closure(pointers)
+ }
+
+ class func toPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? {
+ return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in
+ ptr.baseAddress
+ }
+ }
+
+ // *(char **) MPV_FORMAT_STRING
+ class func toString(_ obj: UnsafeMutableRawPointer?) -> String? {
+ guard let str = obj else { return nil }
+ let cstr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>(OpaquePointer(str))
+ return String(cString: cstr[0])
+ }
+
+ // MPV_FORMAT_FLAG
+ class func toBool(_ obj: UnsafeMutableRawPointer) -> Bool? {
+ return UnsafePointer<Bool>(OpaquePointer(obj))?.pointee
+ }
+
+ // MPV_FORMAT_DOUBLE
+ class func toDouble(_ obj: UnsafeMutableRawPointer) -> Double? {
+ return UnsafePointer<Double>(OpaquePointer(obj))?.pointee
+ }
+}
diff --git a/osdep/macos/log_helper.swift b/osdep/macos/log_helper.swift
deleted file mode 100644
index 9464075..0000000
--- a/osdep/macos/log_helper.swift
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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 LogHelper: NSObject {
- var log: OpaquePointer?
-
- init(_ log: OpaquePointer?) {
- self.log = log
- }
-
- 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) {
- let args: [CVarArg] = [ (msg as NSString).utf8String ?? "NO MESSAGE"]
- mp_msg_va(log, Int32(t), "%s\n", getVaList(args))
- }
-}
diff --git a/osdep/macos/mpv_helper.swift b/osdep/macos/mpv_helper.swift
deleted file mode 100644
index 3b2a716..0000000
--- a/osdep/macos/mpv_helper.swift
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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
-
-typealias swift_wakeup_cb_fn = (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
-
-class MPVHelper {
- var log: LogHelper
- var vo: UnsafeMutablePointer<vo>
- var optsCachePtr: UnsafeMutablePointer<m_config_cache>
- var optsPtr: UnsafeMutablePointer<mp_vo_opts>
- var macOptsCachePtr: UnsafeMutablePointer<m_config_cache>
- var macOptsPtr: UnsafeMutablePointer<macos_opts>
-
- // these computed properties return a local copy of the struct accessed:
- // - don't use if you rely on the pointers
- // - only for reading
- var vout: vo { get { return vo.pointee } }
- var optsCache: m_config_cache { get { return optsCachePtr.pointee } }
- var opts: mp_vo_opts { get { return optsPtr.pointee } }
- var macOptsCache: m_config_cache { get { return macOptsCachePtr.pointee } }
- var macOpts: macos_opts { get { return macOptsPtr.pointee } }
-
- var input: OpaquePointer { get { return vout.input_ctx } }
-
- init(_ vo: UnsafeMutablePointer<vo>, _ log: LogHelper) {
- self.vo = vo
- self.log = log
-
- guard let app = NSApp as? Application,
- let cache = m_config_cache_alloc(vo, vo.pointee.global, app.getVoSubConf()) else
- {
- log.sendError("NSApp couldn't be retrieved")
- exit(1)
- }
-
- optsCachePtr = cache
- optsPtr = UnsafeMutablePointer<mp_vo_opts>(OpaquePointer(cache.pointee.opts))
-
- guard let macCache = m_config_cache_alloc(vo,
- vo.pointee.global,
- app.getMacOSConf()) else
- {
- // will never be hit, mp_get_config_group asserts for invalid groups
- exit(1)
- }
- macOptsCachePtr = macCache
- macOptsPtr = UnsafeMutablePointer<macos_opts>(OpaquePointer(macCache.pointee.opts))
- }
-
- func canBeDraggedAt(_ pos: NSPoint) -> Bool {
- let canDrag = !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y))
- return canDrag
- }
-
- func mouseEnabled() -> Bool {
- return mp_input_mouse_enabled(input)
- }
-
- func setMousePosition(_ pos: NSPoint) {
- mp_input_set_mouse_pos(input, Int32(pos.x), Int32(pos.y))
- }
-
- func putAxis(_ mpkey: Int32, delta: Double) {
- mp_input_put_wheel(input, mpkey, delta)
- }
-
- func nextChangedOption(property: inout UnsafeMutableRawPointer?) -> Bool {
- return m_config_cache_get_next_changed(optsCachePtr, &property)
- }
-
- func setOption(fullscreen: Bool) {
- optsPtr.pointee.fullscreen = fullscreen
- _ = withUnsafeMutableBytes(of: &optsPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in
- m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
- }
- }
-
- func setOption(minimized: Bool) {
- optsPtr.pointee.window_minimized = minimized
- _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in
- m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
- }
- }
-
- func setOption(maximized: Bool) {
- optsPtr.pointee.window_maximized = maximized
- _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in
- m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
- }
- }
-
- func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) {
- m_config_cache_set_wakeup_cb(macOptsCachePtr, callback, MPVHelper.bridge(obj: object))
- }
-
- func nextChangedMacOption(property: inout UnsafeMutableRawPointer?) -> Bool {
- return m_config_cache_get_next_changed(macOptsCachePtr, &property)
- }
-
- func command(_ cmd: String) {
- let cCmd = UnsafePointer<Int8>(strdup(cmd))
- let mpvCmd = mp_input_parse_cmd(input, bstr0(cCmd), "")
- mp_input_queue_cmd(input, mpvCmd)
- free(UnsafeMutablePointer(mutating: cCmd))
- }
-
- // (__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()
- }
-
- class func withUnsafeMutableRawPointers(_ arguments: [Any],
- pointers: [UnsafeMutableRawPointer?] = [],
- closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) {
- if arguments.count > 0 {
- let args = Array(arguments.dropFirst(1))
- var newPtrs = pointers
- var firstArg = arguments.first
- withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in
- newPtrs.append(ptr.baseAddress)
- withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure)
- }
-
- return
- }
-
- closure(pointers)
- }
-
- class func getPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? {
- return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in
- ptr.baseAddress
- }
- }
-}
diff --git a/osdep/macos/remote_command_center.swift b/osdep/macos/remote_command_center.swift
deleted file mode 100644
index 6fb2229..0000000
--- a/osdep/macos/remote_command_center.swift
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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 MediaPlayer
-
-class RemoteCommandCenter: NSObject {
- enum KeyType {
- case normal
- case repeatable
- }
-
- var config: [MPRemoteCommand:[String:Any]] = [
- MPRemoteCommandCenter.shared().pauseCommand: [
- "mpKey": MP_KEY_PAUSEONLY,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().playCommand: [
- "mpKey": MP_KEY_PLAYONLY,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().stopCommand: [
- "mpKey": MP_KEY_STOP,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().nextTrackCommand: [
- "mpKey": MP_KEY_NEXT,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().previousTrackCommand: [
- "mpKey": MP_KEY_PREV,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().togglePlayPauseCommand: [
- "mpKey": MP_KEY_PLAY,
- "keyType": KeyType.normal
- ],
- MPRemoteCommandCenter.shared().seekForwardCommand: [
- "mpKey": MP_KEY_FORWARD,
- "keyType": KeyType.repeatable,
- "state": MP_KEY_STATE_UP
- ],
- MPRemoteCommandCenter.shared().seekBackwardCommand: [
- "mpKey": MP_KEY_REWIND,
- "keyType": KeyType.repeatable,
- "state": MP_KEY_STATE_UP
- ],
- ]
-
- var nowPlayingInfo: [String: Any] = [
- MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
- MPNowPlayingInfoPropertyDefaultPlaybackRate: NSNumber(value: 1),
- MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0),
- MPMediaItemPropertyPlaybackDuration: NSNumber(value: 0),
- MPMediaItemPropertyTitle: "mpv",
- MPMediaItemPropertyAlbumTitle: "mpv",
- MPMediaItemPropertyArtist: "mpv",
- ]
-
- let disabledCommands: [MPRemoteCommand] = [
- MPRemoteCommandCenter.shared().changePlaybackRateCommand,
- MPRemoteCommandCenter.shared().changeRepeatModeCommand,
- MPRemoteCommandCenter.shared().changeShuffleModeCommand,
- MPRemoteCommandCenter.shared().skipForwardCommand,
- MPRemoteCommandCenter.shared().skipBackwardCommand,
- MPRemoteCommandCenter.shared().changePlaybackPositionCommand,
- MPRemoteCommandCenter.shared().enableLanguageOptionCommand,
- MPRemoteCommandCenter.shared().disableLanguageOptionCommand,
- MPRemoteCommandCenter.shared().ratingCommand,
- MPRemoteCommandCenter.shared().likeCommand,
- MPRemoteCommandCenter.shared().dislikeCommand,
- MPRemoteCommandCenter.shared().bookmarkCommand,
- ]
-
- var mpInfoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } }
- var isPaused: Bool = false { didSet { updatePlaybackState() } }
-
- @objc override init() {
- super.init()
-
- for cmd in disabledCommands {
- cmd.isEnabled = false
- }
- }
-
- @objc func start() {
- for (cmd, _) in config {
- cmd.isEnabled = true
- cmd.addTarget { [unowned self] event in
- return self.cmdHandler(event)
- }
- }
-
- if let app = NSApp as? Application, let icon = app.getMPVIcon() {
- let albumArt = MPMediaItemArtwork(boundsSize: icon.size) { _ in
- return icon
- }
- nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArt
- }
-
- mpInfoCenter.nowPlayingInfo = nowPlayingInfo
- mpInfoCenter.playbackState = .playing
-
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(self.makeCurrent),
- name: NSApplication.willBecomeActiveNotification,
- object: nil
- )
- }
-
- @objc func stop() {
- for (cmd, _) in config {
- cmd.isEnabled = false
- cmd.removeTarget(nil)
- }
-
- mpInfoCenter.nowPlayingInfo = nil
- mpInfoCenter.playbackState = .unknown
- }
-
- @objc func makeCurrent(notification: NSNotification) {
- mpInfoCenter.playbackState = .paused
- mpInfoCenter.playbackState = .playing
- updatePlaybackState()
- }
-
- func updatePlaybackState() {
- mpInfoCenter.playbackState = isPaused ? .paused : .playing
- }
-
- func cmdHandler(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- guard let cmdConfig = config[event.command],
- let mpKey = cmdConfig["mpKey"] as? Int32,
- let keyType = cmdConfig["keyType"] as? KeyType else
- {
- return .commandFailed
- }
-
- var state = cmdConfig["state"] as? UInt32 ?? 0
-
- if let currentState = cmdConfig["state"] as? UInt32, keyType == .repeatable {
- state = MP_KEY_STATE_DOWN
- config[event.command]?["state"] = MP_KEY_STATE_DOWN
- if currentState == MP_KEY_STATE_DOWN {
- state = MP_KEY_STATE_UP
- config[event.command]?["state"] = MP_KEY_STATE_UP
- }
- }
-
- EventsResponder.sharedInstance().handleMPKey(mpKey, withMask: Int32(state))
-
- return .success
- }
-
- @objc func processEvent(_ event: UnsafeMutablePointer<mpv_event>) {
- switch event.pointee.event_id {
- case MPV_EVENT_PROPERTY_CHANGE:
- handlePropertyChange(event)
- default:
- break
- }
- }
-
- func handlePropertyChange(_ event: UnsafeMutablePointer<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 "pause" where property.format == MPV_FORMAT_FLAG:
- isPaused = LibmpvHelper.mpvFlagToBool(property.data) ?? false
- default:
- break
- }
- }
-}
diff --git a/osdep/macos/swift_extensions.swift b/osdep/macos/swift_extensions.swift
deleted file mode 100644
index 127c568..0000000
--- a/osdep/macos/swift_extensions.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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
-
-extension NSDeviceDescriptionKey {
- static let screenNumber = NSDeviceDescriptionKey("NSScreenNumber")
-}
-
-extension NSScreen {
-
- public var displayID: CGDirectDisplayID {
- get {
- return deviceDescription[.screenNumber] as? CGDirectDisplayID ?? 0
- }
- }
-}
-
-extension NSColor {
-
- convenience init(hex: String) {
- let int = Int(hex.dropFirst(), radix: 16) ?? 0
- let alpha = CGFloat((int >> 24) & 0x000000FF)/255
- let red = CGFloat((int >> 16) & 0x000000FF)/255
- let green = CGFloat((int >> 8) & 0x000000FF)/255
- let blue = CGFloat((int) & 0x000000FF)/255
-
- self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha)
- }
-}
-
-extension Bool {
-
- init(_ int32: Int32) {
- self.init(int32 != 0)
- }
-}
-
-extension Int32 {
-
- init(_ bool: Bool) {
- self.init(bool ? 1 : 0)
- }
-}
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
deleted file mode 100644
index 73503ad..0000000
--- a/osdep/macosx_application.m
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * 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/>.
- */
-
-#include <stdio.h>
-#include "config.h"
-#include "mpv_talloc.h"
-
-#include "common/msg.h"
-#include "input/input.h"
-#include "player/client.h"
-#include "options/m_config.h"
-#include "options/options.h"
-
-#import "osdep/macosx_application_objc.h"
-#import "osdep/macosx_events_objc.h"
-#include "osdep/threads.h"
-#include "osdep/main-fn.h"
-
-#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://"
-
-#define OPT_BASE_STRUCT struct macos_opts
-const struct m_sub_options macos_conf = {
- .opts = (const struct m_option[]) {
- {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance,
- {"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
- {"vibrantLight", 3}, {"vibrantDark", 4},
- {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
- {"vibrantLightHighContrast", 7},
- {"vibrantDarkHighContrast", 8})},
- {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material,
- {"titlebar", 0}, {"selection", 1}, {"menu", 2},
- {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
- {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
- {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
- {"underWindowBackground", 12}, {"underPageBackground", 13},
- {"dark", 14}, {"light", 15}, {"mediumLight", 16},
- {"ultraDark", 17})},
- {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)},
- {"macos-fs-animation-duration",
- OPT_CHOICE(macos_fs_animation_duration, {"default", -1}),
- M_RANGE(0, 1000)},
- {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)},
- {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy,
- {"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
- {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
- {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
- {"macos-render-timer", OPT_CHOICE(macos_render_timer,
- {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
- {"system", RENDER_TIMER_SYSTEM})},
- {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
- {"auto", -1}, {"no", 0}, {"yes", 1})},
- {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
- {0}
- },
- .size = sizeof(struct macos_opts),
- .defaults = &(const struct macos_opts){
- .macos_title_bar_color = {0, 0, 0, 0},
- .macos_fs_animation_duration = -1,
- .cocoa_cb_sw_renderer = -1,
- .cocoa_cb_10bit_context = true
- },
-};
-
-// Whether the NSApplication singleton was created. If this is false, we are
-// running in libmpv mode, and cocoa_main() was never called.
-static bool application_instantiated;
-
-static mp_thread playback_thread_id;
-
-@interface Application ()
-{
- EventsResponder *_eventsResponder;
-}
-
-@end
-
-static Application *mpv_shared_app(void)
-{
- return (Application *)[Application sharedApplication];
-}
-
-static void terminate_cocoa_application(void)
-{
- dispatch_async(dispatch_get_main_queue(), ^{
- [NSApp hide:NSApp];
- [NSApp terminate:NSApp];
- });
-}
-
-@implementation Application
-@synthesize menuBar = _menu_bar;
-@synthesize openCount = _open_count;
-@synthesize cocoaCB = _cocoa_cb;
-
-- (void)sendEvent:(NSEvent *)event
-{
- if ([self modalWindow] || ![_eventsResponder processKeyEvent:event])
- [super sendEvent:event];
- [_eventsResponder wakeup];
-}
-
-- (id)init
-{
- if (self = [super init]) {
- _eventsResponder = [EventsResponder sharedInstance];
-
- NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
- [em setEventHandler:self
- andSelector:@selector(getUrl:withReplyEvent:)
- forEventClass:kInternetEventClass
- andEventID:kAEGetURL];
- }
-
- return self;
-}
-
-- (void)dealloc
-{
- NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
- [em removeEventHandlerForEventClass:kInternetEventClass
- andEventID:kAEGetURL];
- [em removeEventHandlerForEventClass:kCoreEventClass
- andEventID:kAEQuitApplication];
- [super dealloc];
-}
-
-static const char macosx_icon[] =
-#include "TOOLS/osxbundle/icon.icns.inc"
-;
-
-- (NSImage *)getMPVIcon
-{
- // The C string contains a trailing null, so we strip it away
- NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
- length:sizeof(macosx_icon) - 1
- freeWhenDone:NO];
- return [[NSImage alloc] initWithData:icon_data];
-}
-
-#if HAVE_MACOS_TOUCHBAR
-- (NSTouchBar *)makeTouchBar
-{
- TouchBar *tBar = [[TouchBar alloc] init];
- [tBar setApp:self];
- tBar.delegate = tBar;
- tBar.customizationIdentifier = customID;
- tBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar];
- tBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem,
- nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle,
- currentPosition, timeLeft];
- return tBar;
-}
-#endif
-
-- (void)processEvent:(struct mpv_event *)event
-{
-#if HAVE_MACOS_TOUCHBAR
- [(TouchBar *)self.touchBar processEvent:event];
-#endif
- if (_cocoa_cb) {
- [_cocoa_cb processEvent:event];
- }
-}
-
-- (void)setMpvHandle:(struct mpv_handle *)ctx
-{
-#if HAVE_MACOS_COCOA_CB
- [NSApp setCocoaCB:[[CocoaCB alloc] init:ctx]];
-#endif
-}
-
-- (const struct m_sub_options *)getMacOSConf
-{
- return &macos_conf;
-}
-
-- (const struct m_sub_options *)getVoSubConf
-{
- return &vo_sub_opts;
-}
-
-- (void)queueCommand:(char *)cmd
-{
- [_eventsResponder queueCommand:cmd];
-}
-
-- (void)stopMPV:(char *)cmd
-{
- if (![_eventsResponder queueCommand:cmd])
- terminate_cocoa_application();
-}
-
-- (void)applicationWillFinishLaunching:(NSNotification *)notification
-{
- NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
- [em setEventHandler:self
- andSelector:@selector(handleQuitEvent:withReplyEvent:)
- forEventClass:kCoreEventClass
- andEventID:kAEQuitApplication];
-}
-
-- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
- withReplyEvent:(NSAppleEventDescriptor *)replyEvent
-{
- [self stopMPV:"quit"];
-}
-
-- (void)getUrl:(NSAppleEventDescriptor *)event
- withReplyEvent:(NSAppleEventDescriptor *)replyEvent
-{
- NSString *url =
- [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
-
- url = [url stringByReplacingOccurrencesOfString:MPV_PROTOCOL
- withString:@""
- options:NSAnchoredSearch
- range:NSMakeRange(0, [MPV_PROTOCOL length])];
-
- url = [url stringByRemovingPercentEncoding];
- [_eventsResponder handleFilesArray:@[url]];
-}
-
-- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
-{
- if (mpv_shared_app().openCount > 0) {
- mpv_shared_app().openCount--;
- return;
- }
- [self openFiles:filenames];
-}
-
-- (void)openFiles:(NSArray *)filenames
-{
- SEL cmpsel = @selector(localizedStandardCompare:);
- NSArray *files = [filenames sortedArrayUsingSelector:cmpsel];
- [_eventsResponder handleFilesArray:files];
-}
-@end
-
-struct playback_thread_ctx {
- int *argc;
- char ***argv;
-};
-
-static void cocoa_run_runloop(void)
-{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [NSApp run];
- [pool drain];
-}
-
-static MP_THREAD_VOID playback_thread(void *ctx_obj)
-{
- mp_thread_set_name("core/playback");
- @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)
- [[NSApp menuBar] registerSelector:(SEL)action forKey:key];
-}
-
-static void init_cocoa_application(bool regular)
-{
- NSApp = mpv_shared_app();
- [NSApp setDelegate:NSApp];
- [NSApp setMenuBar:[[MenuBar alloc] init]];
-
- // 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 ?
- NSApplicationActivationPolicyRegular :
- NSApplicationActivationPolicyAccessory];
-
- atexit_b(^{
- // Because activation policy has just been set to behave like a real
- // application, that policy must be reset on exit to prevent, among
- // other things, the menubar created here from remaining on screen.
- dispatch_async(dispatch_get_main_queue(), ^{
- [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
- });
- });
-}
-
-static bool bundle_started_from_finder()
-{
- NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"];
- return [bundle isEqual:@"true"];
-}
-
-static bool is_psn_argument(char *arg_to_check)
-{
- NSString *arg = [NSString stringWithUTF8String:arg_to_check];
- return [arg hasPrefix:@"-psn_"];
-}
-
-static void setup_bundle(int *argc, char *argv[])
-{
- if (*argc > 1 && is_psn_argument(argv[1])) {
- *argc = 1;
- argv[1] = NULL;
- }
-
- NSDictionary *env = [[NSProcessInfo processInfo] environment];
- NSString *path_bundle = [env objectForKey:@"PATH"];
- NSString *path_new = [NSString stringWithFormat:@"%@:%@:%@:%@:%@",
- path_bundle,
- @"/usr/local/bin",
- @"/usr/local/sbin",
- @"/opt/local/bin",
- @"/opt/local/sbin"];
- setenv("PATH", [path_new UTF8String], 1);
-}
-
-int cocoa_main(int argc, char *argv[])
-{
- @autoreleasepool {
- application_instantiated = true;
- [[EventsResponder sharedInstance] setIsApplication:YES];
-
- struct playback_thread_ctx ctx = {0};
- ctx.argc = &argc;
- ctx.argv = &argv;
-
- if (bundle_started_from_finder()) {
- setup_bundle(&argc, argv);
- init_cocoa_application(true);
- } else {
- for (int i = 1; i < argc; i++)
- if (argv[i][0] != '-')
- mpv_shared_app().openCount++;
- init_cocoa_application(false);
- }
-
- mp_thread_create(&playback_thread_id, playback_thread, &ctx);
- [[EventsResponder sharedInstance] waitForInputContext];
- cocoa_run_runloop();
-
- // This should never be reached: cocoa_run_runloop blocks until the
- // process is quit
- fprintf(stderr, "There was either a problem "
- "initializing Cocoa or the Runloop was stopped unexpectedly. "
- "Please report this issues to a developer.\n");
- mp_thread_join(playback_thread_id);
- return 1;
- }
-}
diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h
deleted file mode 100644
index 11959a8..0000000
--- a/osdep/macosx_application_objc.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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/Cocoa.h>
-#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;
-- (const struct m_sub_options *)getMacOSConf;
-- (const struct m_sub_options *)getVoSubConf;
-
-@property(nonatomic, retain) MenuBar *menuBar;
-@property(nonatomic, assign) size_t openCount;
-@property(nonatomic, retain) CocoaCB *cocoaCB;
-@end
diff --git a/osdep/macosx_events.h b/osdep/macosx_events.h
deleted file mode 100644
index 9188c8b..0000000
--- a/osdep/macosx_events.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Cocoa Application Event Handling
- *
- * 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/>.
- */
-
-#ifndef MACOSX_EVENTS_H
-#define MACOSX_EVENTS_H
-#include "input/keycodes.h"
-
-struct input_ctx;
-struct mpv_handle;
-
-void cocoa_put_key(int keycode);
-void cocoa_put_key_with_modifiers(int keycode, int modifiers);
-
-void cocoa_init_media_keys(void);
-void cocoa_uninit_media_keys(void);
-
-void cocoa_set_input_context(struct input_ctx *input_context);
-void cocoa_set_mpv_handle(struct mpv_handle *ctx);
-
-#endif
diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m
deleted file mode 100644
index 627077a..0000000
--- a/osdep/macosx_events.m
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Cocoa Application Event Handling
- *
- * 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/>.
- */
-
-// Carbon header is included but Carbon is NOT linked to mpv's binary. This
-// file only needs this include to use the keycode definitions in keymap.
-#import <Carbon/Carbon.h>
-
-// Media keys definitions
-#import <IOKit/hidsystem/ev_keymap.h>
-#import <Cocoa/Cocoa.h>
-
-#include "mpv_talloc.h"
-#include "input/event.h"
-#include "input/input.h"
-#include "player/client.h"
-#include "input/keycodes.h"
-// doesn't make much sense, but needed to access keymap functionality
-#include "video/out/vo.h"
-
-#import "osdep/macosx_events_objc.h"
-#import "osdep/macosx_application_objc.h"
-
-#include "config.h"
-
-#if HAVE_MACOS_COCOA_CB
-#include "osdep/macOS_swift.h"
-#endif
-
-@interface EventsResponder ()
-{
- struct input_ctx *_inputContext;
- struct mpv_handle *_ctx;
- BOOL _is_application;
- NSCondition *_input_lock;
-}
-
-- (NSEvent *)handleKey:(NSEvent *)event;
-- (BOOL)setMpvHandle:(struct mpv_handle *)ctx;
-- (void)readEvents;
-- (void)startMediaKeys;
-- (void)stopMediaKeys;
-- (int)mapKeyModifiers:(int)cocoaModifiers;
-- (int)keyModifierMask:(NSEvent *)event;
-@end
-
-
-#define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption)
-#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption)
-
-static bool LeftAltPressed(int mask)
-{
- return (mask & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask;
-}
-
-static bool RightAltPressed(int mask)
-{
- return (mask & NSRightAlternateKeyMask) == NSRightAlternateKeyMask;
-}
-
-static const struct mp_keymap keymap[] = {
- // special keys
- {kVK_Return, MP_KEY_ENTER}, {kVK_Escape, MP_KEY_ESC},
- {kVK_Delete, MP_KEY_BACKSPACE}, {kVK_Option, MP_KEY_BACKSPACE},
- {kVK_Control, MP_KEY_BACKSPACE}, {kVK_Shift, MP_KEY_BACKSPACE},
- {kVK_Tab, MP_KEY_TAB},
-
- // cursor keys
- {kVK_UpArrow, MP_KEY_UP}, {kVK_DownArrow, MP_KEY_DOWN},
- {kVK_LeftArrow, MP_KEY_LEFT}, {kVK_RightArrow, MP_KEY_RIGHT},
-
- // navigation block
- {kVK_Help, MP_KEY_INSERT}, {kVK_ForwardDelete, MP_KEY_DELETE},
- {kVK_Home, MP_KEY_HOME}, {kVK_End, MP_KEY_END},
- {kVK_PageUp, MP_KEY_PAGE_UP}, {kVK_PageDown, MP_KEY_PAGE_DOWN},
-
- // F-keys
- {kVK_F1, MP_KEY_F + 1}, {kVK_F2, MP_KEY_F + 2}, {kVK_F3, MP_KEY_F + 3},
- {kVK_F4, MP_KEY_F + 4}, {kVK_F5, MP_KEY_F + 5}, {kVK_F6, MP_KEY_F + 6},
- {kVK_F7, MP_KEY_F + 7}, {kVK_F8, MP_KEY_F + 8}, {kVK_F9, MP_KEY_F + 9},
- {kVK_F10, MP_KEY_F + 10}, {kVK_F11, MP_KEY_F + 11}, {kVK_F12, MP_KEY_F + 12},
- {kVK_F13, MP_KEY_F + 13}, {kVK_F14, MP_KEY_F + 14}, {kVK_F15, MP_KEY_F + 15},
- {kVK_F16, MP_KEY_F + 16}, {kVK_F17, MP_KEY_F + 17}, {kVK_F18, MP_KEY_F + 18},
- {kVK_F19, MP_KEY_F + 19}, {kVK_F20, MP_KEY_F + 20},
-
- // numpad
- {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'},
- {kVK_ANSI_KeypadMultiply, '*'}, {kVK_ANSI_KeypadDivide, '/'},
- {kVK_ANSI_KeypadEnter, MP_KEY_KPENTER},
- {kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC},
- {kVK_ANSI_Keypad0, MP_KEY_KP0}, {kVK_ANSI_Keypad1, MP_KEY_KP1},
- {kVK_ANSI_Keypad2, MP_KEY_KP2}, {kVK_ANSI_Keypad3, MP_KEY_KP3},
- {kVK_ANSI_Keypad4, MP_KEY_KP4}, {kVK_ANSI_Keypad5, MP_KEY_KP5},
- {kVK_ANSI_Keypad6, MP_KEY_KP6}, {kVK_ANSI_Keypad7, MP_KEY_KP7},
- {kVK_ANSI_Keypad8, MP_KEY_KP8}, {kVK_ANSI_Keypad9, MP_KEY_KP9},
-
- {0, 0}
-};
-
-static int convert_key(unsigned key, unsigned charcode)
-{
- int mpkey = lookup_keymap_table(keymap, key);
- if (mpkey)
- return mpkey;
- return charcode;
-}
-
-void cocoa_init_media_keys(void)
-{
- [[EventsResponder sharedInstance] startMediaKeys];
-}
-
-void cocoa_uninit_media_keys(void)
-{
- [[EventsResponder sharedInstance] stopMediaKeys];
-}
-
-void cocoa_put_key(int keycode)
-{
- [[EventsResponder sharedInstance] putKey:keycode];
-}
-
-void cocoa_put_key_with_modifiers(int keycode, int modifiers)
-{
- keycode |= [[EventsResponder sharedInstance] mapKeyModifiers:modifiers];
- cocoa_put_key(keycode);
-}
-
-void cocoa_set_input_context(struct input_ctx *input_context)
-{
- [[EventsResponder sharedInstance] setInputContext:input_context];
-}
-
-static void wakeup(void *context)
-{
- [[EventsResponder sharedInstance] readEvents];
-}
-
-void cocoa_set_mpv_handle(struct mpv_handle *ctx)
-{
- if ([[EventsResponder sharedInstance] setMpvHandle:ctx]) {
- mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_DOUBLE);
- mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_DOUBLE);
- mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG);
- mpv_set_wakeup_callback(ctx, wakeup, NULL);
- }
-}
-
-@implementation EventsResponder
-
-@synthesize remoteCommandCenter = _remoteCommandCenter;
-
-+ (EventsResponder *)sharedInstance
-{
- static EventsResponder *responder = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- responder = [EventsResponder new];
- });
- return responder;
-}
-
-- (id)init
-{
- self = [super init];
- if (self) {
- _input_lock = [NSCondition new];
- }
- return self;
-}
-
-- (void)waitForInputContext
-{
- [_input_lock lock];
- while (!_inputContext)
- [_input_lock wait];
- [_input_lock unlock];
-}
-
-- (void)setInputContext:(struct input_ctx *)ctx
-{
- [_input_lock lock];
- _inputContext = ctx;
- [_input_lock signal];
- [_input_lock unlock];
-}
-
-- (void)wakeup
-{
- [_input_lock lock];
- if (_inputContext)
- mp_input_wakeup(_inputContext);
- [_input_lock unlock];
-}
-
-- (bool)queueCommand:(char *)cmd
-{
- bool r = false;
- [_input_lock lock];
- if (_inputContext) {
- mp_cmd_t *cmdt = mp_input_parse_cmd(_inputContext, bstr0(cmd), "");
- mp_input_queue_cmd(_inputContext, cmdt);
- r = true;
- }
- [_input_lock unlock];
- return r;
-}
-
-- (void)putKey:(int)keycode
-{
- [_input_lock lock];
- if (_inputContext)
- mp_input_put_key(_inputContext, keycode);
- [_input_lock unlock];
-}
-
-- (BOOL)useAltGr
-{
- BOOL r = YES;
- [_input_lock lock];
- if (_inputContext)
- r = mp_input_use_alt_gr(_inputContext);
- [_input_lock unlock];
- return r;
-}
-
-- (void)setIsApplication:(BOOL)isApplication
-{
- _is_application = isApplication;
-}
-
-- (BOOL)setMpvHandle:(struct mpv_handle *)ctx
-{
- if (_is_application) {
- dispatch_sync(dispatch_get_main_queue(), ^{
- _ctx = ctx;
- [NSApp setMpvHandle:ctx];
- });
- return YES;
- } else {
- mpv_destroy(ctx);
- return NO;
- }
-}
-
-- (void)readEvents
-{
- dispatch_async(dispatch_get_main_queue(), ^{
- while (_ctx) {
- mpv_event *event = mpv_wait_event(_ctx, 0);
- if (event->event_id == MPV_EVENT_NONE)
- break;
- [self processEvent:event];
- }
- });
-}
-
--(void)processEvent:(struct mpv_event *)event
-{
- if(_is_application) {
- [NSApp processEvent:event];
- }
-
- if (_remoteCommandCenter) {
- [_remoteCommandCenter processEvent:event];
- }
-
- switch (event->event_id) {
- case MPV_EVENT_SHUTDOWN: {
-#if HAVE_MACOS_COCOA_CB
- if ([(Application *)NSApp cocoaCB].isShuttingDown) {
- _ctx = nil;
- return;
- }
-#endif
- mpv_destroy(_ctx);
- _ctx = nil;
- break;
- }
- }
-}
-
-- (void)startMediaKeys
-{
-#if HAVE_MACOS_MEDIA_PLAYER
- if (_remoteCommandCenter == nil) {
- _remoteCommandCenter = [[RemoteCommandCenter alloc] init];
- }
-#endif
-
- [_remoteCommandCenter start];
-}
-
-- (void)stopMediaKeys
-{
- [_remoteCommandCenter stop];
-}
-
-- (int)mapKeyModifiers:(int)cocoaModifiers
-{
- int mask = 0;
- if (cocoaModifiers & NSEventModifierFlagShift)
- mask |= MP_KEY_MODIFIER_SHIFT;
- if (cocoaModifiers & NSEventModifierFlagControl)
- mask |= MP_KEY_MODIFIER_CTRL;
- if (LeftAltPressed(cocoaModifiers) ||
- (RightAltPressed(cocoaModifiers) && ![self useAltGr]))
- mask |= MP_KEY_MODIFIER_ALT;
- if (cocoaModifiers & NSEventModifierFlagCommand)
- mask |= MP_KEY_MODIFIER_META;
- return mask;
-}
-
-- (int)mapTypeModifiers:(NSEventType)type
-{
- NSDictionary *map = @{
- @(NSEventTypeKeyDown) : @(MP_KEY_STATE_DOWN),
- @(NSEventTypeKeyUp) : @(MP_KEY_STATE_UP),
- };
- return [map[@(type)] intValue];
-}
-
-- (int)keyModifierMask:(NSEvent *)event
-{
- return [self mapKeyModifiers:[event modifierFlags]] |
- [self mapTypeModifiers:[event type]];
-}
-
--(BOOL)handleMPKey:(int)key withMask:(int)mask
-{
- if (key > 0) {
- cocoa_put_key(key | mask);
- if (mask & MP_KEY_STATE_UP)
- cocoa_put_key(MP_INPUT_RELEASE_ALL);
- return YES;
- } else {
- return NO;
- }
-}
-
-- (NSEvent*)handleKey:(NSEvent *)event
-{
- if ([event isARepeat]) return nil;
-
- NSString *chars;
-
- if ([self useAltGr] && RightAltPressed([event modifierFlags])) {
- chars = [event characters];
- } else {
- chars = [event charactersIgnoringModifiers];
- }
-
- struct bstr t = bstr0([chars UTF8String]);
- int key = convert_key([event keyCode], bstr_decode_utf8(t, &t));
-
- if (key > -1)
- [self handleMPKey:key withMask:[self keyModifierMask:event]];
-
- return nil;
-}
-
-- (bool)processKeyEvent:(NSEvent *)event
-{
- if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp){
- if (![[NSApp mainMenu] performKeyEquivalent:event])
- [self handleKey:event];
- return true;
- }
- return false;
-}
-
-- (void)handleFilesArray:(NSArray *)files
-{
- enum mp_dnd_action action = [NSEvent modifierFlags] &
- NSEventModifierFlagShift ? DND_APPEND : DND_REPLACE;
-
- size_t num_files = [files count];
- char **files_utf8 = talloc_array(NULL, char*, num_files);
- [files enumerateObjectsUsingBlock:^(NSString *p, NSUInteger i, BOOL *_){
- if ([p hasPrefix:@"file:///.file/id="])
- p = [[NSURL URLWithString:p] path];
- char *filename = (char *)[p UTF8String];
- size_t bytes = [p lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
- files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1);
- }];
- [_input_lock lock];
- if (_inputContext)
- mp_event_drop_files(_inputContext, num_files, files_utf8, action);
- [_input_lock unlock];
- talloc_free(files_utf8);
-}
-
-@end
diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h
deleted file mode 100644
index 9394fe7..0000000
--- a/osdep/macosx_events_objc.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Cocoa Application Event Handling
- *
- * 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/Cocoa.h>
-#include "osdep/macosx_events.h"
-
-@class RemoteCommandCenter;
-struct input_ctx;
-
-@interface EventsResponder : NSObject
-
-+ (EventsResponder *)sharedInstance;
-- (void)setInputContext:(struct input_ctx *)ctx;
-- (void)setIsApplication:(BOOL)isApplication;
-
-/// Blocks until inputContext is present.
-- (void)waitForInputContext;
-- (void)wakeup;
-- (void)putKey:(int)keycode;
-- (void)handleFilesArray:(NSArray *)files;
-
-- (bool)queueCommand:(char *)cmd;
-- (bool)processKeyEvent:(NSEvent *)event;
-
-- (BOOL)handleMPKey:(int)key withMask:(int)mask;
-
-@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter;
-
-@end
diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m
deleted file mode 100644
index 5c6cd47..0000000
--- a/osdep/macosx_menubar.m
+++ /dev/null
@@ -1,853 +0,0 @@
-/*
- * 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/>.
- */
-
-#include "config.h"
-#include "common/common.h"
-
-#import "macosx_menubar_objc.h"
-#import "osdep/macosx_application_objc.h"
-
-@implementation MenuBar
-{
- NSArray *menuTree;
-}
-
-- (id)init
-{
- if (self = [super init]) {
- NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
- [userDefaults setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
- [userDefaults setBool:YES forKey:@"NSDisabledDictationMenuItem"];
- [userDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
- [NSWindow setAllowsAutomaticWindowTabbing: NO];
-
- menuTree = @[
- @{
- @"name": @"Apple",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"About mpv",
- @"action" : @"about",
- @"key" : @"",
- @"target" : self
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Preferences…",
- @"action" : @"preferences:",
- @"key" : @",",
- @"target" : self,
- @"file" : @"mpv.conf",
- @"alertTitle1": @"No Application found to open your config file.",
- @"alertText1" : @"Please open the mpv.conf file with "
- "your preferred text editor in the now "
- "open folder to edit your config.",
- @"alertTitle2": @"No config file found.",
- @"alertText2" : @"Please create a mpv.conf file with your "
- "preferred text editor in the now open folder.",
- @"alertTitle3": @"No config path or file found.",
- @"alertText3" : @"Please create the following path ~/.config/mpv/ "
- "and a mpv.conf file within with your preferred "
- "text editor."
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Keyboard Shortcuts Config…",
- @"action" : @"preferences:",
- @"key" : @"",
- @"target" : self,
- @"file" : @"input.conf",
- @"alertTitle1": @"No Application found to open your config file.",
- @"alertText1" : @"Please open the input.conf file with "
- "your preferred text editor in the now "
- "open folder to edit your config.",
- @"alertTitle2": @"No config file found.",
- @"alertText2" : @"Please create a input.conf file with your "
- "preferred text editor in the now open folder.",
- @"alertTitle3": @"No config path or file found.",
- @"alertText3" : @"Please create the following path ~/.config/mpv/ "
- "and a input.conf file within with your preferred "
- "text editor."
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Services",
- @"key" : @"",
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Hide mpv",
- @"action" : @"hide:",
- @"key" : @"h",
- @"target" : NSApp
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Hide Others",
- @"action" : @"hideOtherApplications:",
- @"key" : @"h",
- @"modifiers" : [NSNumber numberWithUnsignedInteger:
- NSEventModifierFlagCommand |
- NSEventModifierFlagOption],
- @"target" : NSApp
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Show All",
- @"action" : @"unhideAllApplications:",
- @"key" : @"",
- @"target" : NSApp
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Quit and Remember Position",
- @"action" : @"quit:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"quit-watch-later"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Quit mpv",
- @"action" : @"quit:",
- @"key" : @"q",
- @"target" : self,
- @"cmd" : @"quit"
- }]
- ]
- },
- @{
- @"name": @"File",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Open File…",
- @"action" : @"openFile",
- @"key" : @"o",
- @"target" : self
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Open URL…",
- @"action" : @"openURL",
- @"key" : @"O",
- @"target" : self
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Open Playlist…",
- @"action" : @"openPlaylist",
- @"key" : @"",
- @"target" : self
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Close",
- @"action" : @"performClose:",
- @"key" : @"w"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Save Screenshot",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"async screenshot"
- }]
- ]
- },
- @{
- @"name": @"Edit",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Undo",
- @"action" : @"undo:",
- @"key" : @"z"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Redo",
- @"action" : @"redo:",
- @"key" : @"Z"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Cut",
- @"action" : @"cut:",
- @"key" : @"x"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Copy",
- @"action" : @"copy:",
- @"key" : @"c"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Paste",
- @"action" : @"paste:",
- @"key" : @"v"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Select All",
- @"action" : @"selectAll:",
- @"key" : @"a"
- }]
- ]
- },
- @{
- @"name": @"View",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Fullscreen",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle fullscreen"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Float on Top",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle ontop"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Visibility on All Workspaces",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle on-all-workspaces"
- }],
-#if HAVE_MACOS_TOUCHBAR
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Customize Touch Bar…",
- @"action" : @"toggleTouchBarCustomizationPalette:",
- @"key" : @"",
- @"target" : NSApp
- }]
-#endif
- ]
- },
- @{
- @"name": @"Video",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Zoom Out",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add panscan -0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Zoom In",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add panscan 0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Zoom",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set panscan 0"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Aspect Ratio 4:3",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-aspect-override \"4:3\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Aspect Ratio 16:9",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-aspect-override \"16:9\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Aspect Ratio 1.85:1",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-aspect-override \"1.85:1\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Aspect Ratio 2.35:1",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-aspect-override \"2.35:1\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Aspect Ratio",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-aspect-override \"-1\""
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Rotate Left",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle-values video-rotate 0 270 180 90"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Rotate Right",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle-values video-rotate 90 180 270 0"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Rotation",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set video-rotate 0"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Half Size",
- @"key" : @"0",
- @"cmdSpecial" : [NSNumber numberWithInt:MPM_H_SIZE]
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Normal Size",
- @"key" : @"1",
- @"cmdSpecial" : [NSNumber numberWithInt:MPM_N_SIZE]
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Double Size",
- @"key" : @"2",
- @"cmdSpecial" : [NSNumber numberWithInt:MPM_D_SIZE]
- }]
- ]
- },
- @{
- @"name": @"Audio",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Next Audio Track",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle audio"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Previous Audio Track",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle audio down"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Mute",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle mute"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Play Audio Later",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add audio-delay 0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Play Audio Earlier",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add audio-delay -0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Audio Delay",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set audio-delay 0.0 "
- }]
- ]
- },
- @{
- @"name": @"Subtitle",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Next Subtitle Track",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle sub"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Previous Subtitle Track",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle sub down"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Force Style",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle-values sub-ass-override \"force\" \"no\""
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Display Subtitles Later",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add sub-delay 0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Display Subtitles Earlier",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add sub-delay -0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Subtitle Delay",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set sub-delay 0.0"
- }]
- ]
- },
- @{
- @"name": @"Playback",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Pause",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle pause"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Increase Speed",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add speed 0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Decrease Speed",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add speed -0.1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Reset Speed",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"set speed 1.0"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Show Playlist",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"script-message osc-playlist"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Show Chapters",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"script-message osc-chapterlist"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Show Tracks",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"script-message osc-tracklist"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Next File",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"playlist-next"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Previous File",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"playlist-prev"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Loop File",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle-values loop-file \"inf\" \"no\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Toggle Loop Playlist",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"cycle-values loop-playlist \"inf\" \"no\""
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Shuffle",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"playlist-shuffle"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Next Chapter",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add chapter 1"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Previous Chapter",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"add chapter -1"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Step Forward",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"frame-step"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Step Backward",
- @"action" : @"cmd:",
- @"key" : @"",
- @"target" : self,
- @"cmd" : @"frame-back-step"
- }]
- ]
- },
- @{
- @"name": @"Window",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Minimize",
- @"key" : @"m",
- @"cmdSpecial" : [NSNumber numberWithInt:MPM_MINIMIZE]
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Zoom",
- @"key" : @"z",
- @"cmdSpecial" : [NSNumber numberWithInt:MPM_ZOOM]
- }]
- ]
- },
- @{
- @"name": @"Help",
- @"menu": @[
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"mpv Website…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://mpv.io"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"mpv on github…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://github.com/mpv-player/mpv"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Online Manual…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://mpv.io/manual/master/"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Online Wiki…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://github.com/mpv-player/mpv/wiki"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Release Notes…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Keyboard Shortcuts…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://github.com/mpv-player/mpv/blob/master/etc/input.conf"
- }],
- @{ @"name": @"separator" },
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Report Issue…",
- @"action" : @"url:",
- @"key" : @"",
- @"target" : self,
- @"url" : @"https://github.com/mpv-player/mpv/issues/new/choose"
- }],
- [NSMutableDictionary dictionaryWithDictionary:@{
- @"name" : @"Show log File…",
- @"action" : @"showFile:",
- @"key" : @"",
- @"target" : self,
- @"file" : @"~/Library/Logs/mpv.log",
- @"alertTitle" : @"No log File found.",
- @"alertText" : @"You deactivated logging for the Bundle."
- }]
- ]
- }
- ];
-
- [NSApp setMainMenu:[self mainMenu]];
- }
-
- return self;
-}
-
-- (NSMenu *)mainMenu
-{
- NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
- [NSApp setServicesMenu:[[NSMenu alloc] init]];
- NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"];
-
- for(id mMenu in menuTree) {
- NSMenu *menu = [[NSMenu alloc] initWithTitle:mMenu[@"name"]];
- NSMenuItem *mItem = [mainMenu addItemWithTitle:mMenu[@"name"]
- action:nil
- keyEquivalent:@""];
- [mainMenu setSubmenu:menu forItem:mItem];
-
- for(id subMenu in mMenu[@"menu"]) {
- NSString *name = subMenu[@"name"];
- NSString *action = subMenu[@"action"];
-
-#if HAVE_MACOS_TOUCHBAR
- if ([action isEqual:@"toggleTouchBarCustomizationPalette:"]) {
- continue;
- }
-#endif
-
- if ([name isEqual:@"Show log File…"] && ![bundle isEqual:@"true"]) {
- continue;
- }
-
- if ([name isEqual:@"separator"]) {
- [menu addItem:[NSMenuItem separatorItem]];
- } else {
- NSMenuItem *iItem = [menu addItemWithTitle:name
- action:NSSelectorFromString(action)
- keyEquivalent:subMenu[@"key"]];
- [iItem setTarget:subMenu[@"target"]];
- [subMenu setObject:iItem forKey:@"menuItem"];
-
- NSNumber *m = subMenu[@"modifiers"];
- if (m) {
- [iItem setKeyEquivalentModifierMask:m.unsignedIntegerValue];
- }
-
- if ([subMenu[@"name"] isEqual:@"Services"]) {
- iItem.submenu = [NSApp servicesMenu];
- }
- }
- }
- }
-
- return mainMenu;
-}
-
-- (void)about
-{
- NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
- @"mpv", @"ApplicationName",
- [(Application *)NSApp getMPVIcon], @"ApplicationIcon",
- [NSString stringWithUTF8String:mpv_copyright], @"Copyright",
- [NSString stringWithUTF8String:mpv_version], @"ApplicationVersion",
- nil];
- [NSApp orderFrontStandardAboutPanelWithOptions:options];
-}
-
-- (void)preferences:(NSMenuItem *)menuItem
-{
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem];
- NSArray *configPaths = @[
- [NSString stringWithFormat:@"%@/.mpv/", NSHomeDirectory()],
- [NSString stringWithFormat:@"%@/.config/mpv/", NSHomeDirectory()]];
-
- for (id path in configPaths) {
- NSString *fileP = [path stringByAppendingString:mItemDict[@"file"]];
- if ([fileManager fileExistsAtPath:fileP]){
- if ([workspace openFile:fileP])
- return;
- [workspace openFile:path];
- [self alertWithTitle:mItemDict[@"alertTitle1"]
- andText:mItemDict[@"alertText1"]];
- return;
- }
- if ([workspace openFile:path]) {
- [self alertWithTitle:mItemDict[@"alertTitle2"]
- andText:mItemDict[@"alertText2"]];
- return;
- }
- }
-
- [self alertWithTitle:mItemDict[@"alertTitle3"]
- andText:mItemDict[@"alertText3"]];
-}
-
-- (void)quit:(NSMenuItem *)menuItem
-{
- NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
- [(Application *)NSApp stopMPV:(char *)[cmd UTF8String]];
-}
-
-- (void)openFile
-{
- NSOpenPanel *panel = [[NSOpenPanel alloc] init];
- [panel setCanChooseDirectories:YES];
- [panel setAllowsMultipleSelection:YES];
-
- if ([panel runModal] == NSModalResponseOK){
- NSMutableArray *fileArray = [[NSMutableArray alloc] init];
- for (id url in [panel URLs])
- [fileArray addObject:[url path]];
- [(Application *)NSApp openFiles:fileArray];
- }
-}
-
-- (void)openPlaylist
-{
- NSOpenPanel *panel = [[NSOpenPanel alloc] init];
-
- if ([panel runModal] == NSModalResponseOK){
- NSString *pl = [NSString stringWithFormat:@"loadlist \"%@\"",
- [panel URLs][0].path];
- [(Application *)NSApp queueCommand:(char *)[pl UTF8String]];
- }
-}
-
-- (void)openURL
-{
- NSAlert *alert = [[NSAlert alloc] init];
- [alert setMessageText:@"Open URL"];
- [alert addButtonWithTitle:@"Ok"];
- [alert addButtonWithTitle:@"Cancel"];
- [alert setIcon:[(Application *)NSApp getMPVIcon]];
-
- NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
- [input setPlaceholderString:@"URL"];
- [alert setAccessoryView:input];
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [input becomeFirstResponder];
- });
-
- if ([alert runModal] == NSAlertFirstButtonReturn && [input stringValue].length > 0) {
- NSArray *url = [NSArray arrayWithObjects:[input stringValue], nil];
- [(Application *)NSApp openFiles:url];
- }
-}
-
-- (void)cmd:(NSMenuItem *)menuItem
-{
- NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
- [(Application *)NSApp queueCommand:(char *)[cmd UTF8String]];
-}
-
-- (void)url:(NSMenuItem *)menuItem
-{
- NSString *url = [self getDictFromMenuItem:menuItem][@"url"];
- [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
-}
-
-- (void)showFile:(NSMenuItem *)menuItem
-{
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem];
- NSString *file = [mItemDict[@"file"] stringByExpandingTildeInPath];
-
- if ([fileManager fileExistsAtPath:file]){
- NSURL *url = [NSURL fileURLWithPath:file];
- NSArray *urlArray = [NSArray arrayWithObjects:url, nil];
-
- [workspace activateFileViewerSelectingURLs:urlArray];
- return;
- }
-
- [self alertWithTitle:mItemDict[@"alertTitle"]
- andText:mItemDict[@"alertText"]];
-}
-
-- (void)alertWithTitle:(NSString *)title andText:(NSString *)text
-{
- NSAlert *alert = [[NSAlert alloc] init];
- [alert setMessageText:title];
- [alert setInformativeText:text];
- [alert addButtonWithTitle:@"Ok"];
- [alert setIcon:[(Application *)NSApp getMPVIcon]];
- [alert runModal];
-}
-
-- (NSMutableDictionary *)getDictFromMenuItem:(NSMenuItem *)menuItem
-{
- for(id mMenu in menuTree) {
- for(id subMenu in mMenu[@"menu"]) {
- if([subMenu[@"menuItem"] isEqual:menuItem])
- return subMenu;
- }
- }
-
- return nil;
-}
-
-- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key
-{
- for(id mMenu in menuTree) {
- for(id subMenu in mMenu[@"menu"]) {
- if([subMenu[@"cmdSpecial"] isEqual:[NSNumber numberWithInt:key]]) {
- [subMenu[@"menuItem"] setAction:action];
- return;
- }
- }
- }
-}
-
-@end
diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h
deleted file mode 100644
index a03b68c..0000000
--- a/osdep/macosx_touchbar.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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/Cocoa.h>
-#import "osdep/macosx_application_objc.h"
-
-#define BASE_ID @"io.mpv.touchbar"
-static NSTouchBarCustomizationIdentifier customID = BASE_ID;
-static NSTouchBarItemIdentifier seekBar = BASE_ID ".seekbar";
-static NSTouchBarItemIdentifier play = BASE_ID ".play";
-static NSTouchBarItemIdentifier nextItem = BASE_ID ".nextItem";
-static NSTouchBarItemIdentifier previousItem = BASE_ID ".previousItem";
-static NSTouchBarItemIdentifier nextChapter = BASE_ID ".nextChapter";
-static NSTouchBarItemIdentifier previousChapter = BASE_ID ".previousChapter";
-static NSTouchBarItemIdentifier cycleAudio = BASE_ID ".cycleAudio";
-static NSTouchBarItemIdentifier cycleSubtitle = BASE_ID ".cycleSubtitle";
-static NSTouchBarItemIdentifier currentPosition = BASE_ID ".currentPosition";
-static NSTouchBarItemIdentifier timeLeft = BASE_ID ".timeLeft";
-
-struct mpv_event;
-
-@interface TouchBar : NSTouchBar <NSTouchBarDelegate>
-
--(void)processEvent:(struct mpv_event *)event;
-
-@property(nonatomic, retain) Application *app;
-@property(nonatomic, retain) NSDictionary *touchbarItems;
-@property(nonatomic, assign) double duration;
-@property(nonatomic, assign) double position;
-@property(nonatomic, assign) int pause;
-
-@end
diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m
deleted file mode 100644
index ccce8f7..0000000
--- a/osdep/macosx_touchbar.m
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * 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/>.
- */
-
-#include "player/client.h"
-#import "macosx_touchbar.h"
-
-@implementation TouchBar
-
-@synthesize app = _app;
-@synthesize touchbarItems = _touchbar_items;
-@synthesize duration = _duration;
-@synthesize position = _position;
-@synthesize pause = _pause;
-
-- (id)init
-{
- if (self = [super init]) {
- self.touchbarItems = @{
- seekBar: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"slider",
- @"name": @"Seek Bar",
- @"cmd": @"seek %f absolute-percent"
- }],
- play: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Play Button",
- @"cmd": @"cycle pause",
- @"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate],
- @"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]
- }],
- previousItem: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Previous Playlist Item",
- @"cmd": @"playlist-prev",
- @"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate]
- }],
- nextItem: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Next Playlist Item",
- @"cmd": @"playlist-next",
- @"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate]
- }],
- previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Previous Chapter",
- @"cmd": @"add chapter -1",
- @"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate]
- }],
- nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Next Chapter",
- @"cmd": @"add chapter 1",
- @"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate]
- }],
- cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Cycle Audio",
- @"cmd": @"cycle audio",
- @"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate]
- }],
- cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"button",
- @"name": @"Cycle Subtitle",
- @"cmd": @"cycle sub",
- @"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate]
- }],
- currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"text",
- @"name": @"Current Position"
- }],
- timeLeft: [NSMutableDictionary dictionaryWithDictionary:@{
- @"type": @"text",
- @"name": @"Time Left"
- }]
- };
-
- [self addObserver:self forKeyPath:@"visible" options:0 context:nil];
- }
- return self;
-}
-
-- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
- makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
-{
- if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
- NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
- NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)];
- slider.minValue = 0.0f;
- slider.maxValue = 100.0f;
- tbItem.view = slider;
- tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
- [self.touchbarItems[identifier] setObject:slider forKey:@"view"];
- [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
- [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
- return tbItem;
- } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) {
- NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
- NSImage *tbImage = self.touchbarItems[identifier][@"image"];
- NSButton *tbButton = [NSButton buttonWithImage:tbImage target:self action:@selector(buttonAction:)];
- tbItem.view = tbButton;
- tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
- [self.touchbarItems[identifier] setObject:tbButton forKey:@"view"];
- [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
- [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
- return tbItem;
- } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) {
- NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
- NSTextField *tbText = [NSTextField labelWithString:@"0:00"];
- tbText.alignment = NSTextAlignmentCenter;
- tbItem.view = tbText;
- tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
- [self.touchbarItems[identifier] setObject:tbText forKey:@"view"];
- [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
- [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
- return tbItem;
- }
-
- return nil;
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary<NSKeyValueChangeKey,id> *)change
- context:(void *)context {
- if ([keyPath isEqualToString:@"visible"]) {
- NSNumber *visible = [object valueForKey:@"visible"];
- if (visible.boolValue) {
- [self updateTouchBarTimeItems];
- [self updatePlayButton];
- }
- }
-}
-
-- (void)updateTouchBarTimeItems
-{
- if (!self.isVisible)
- return;
-
- [self updateSlider];
- [self updateTimeLeft];
- [self updateCurrentPosition];
-}
-
-- (void)updateSlider
-{
- NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"];
- if (!tbItem.visible)
- return;
-
- NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
-
- if (self.duration <= 0) {
- seekSlider.enabled = NO;
- seekSlider.doubleValue = 0;
- } else {
- seekSlider.enabled = YES;
- if (!seekSlider.highlighted)
- seekSlider.doubleValue = (self.position/self.duration)*100;
- }
-}
-
-- (void)updateTimeLeft
-{
- NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"];
- if (!tbItem.visible)
- return;
-
- NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
-
- [self removeConstraintForIdentifier:timeLeft];
- if (self.duration <= 0) {
- timeLeftItem.stringValue = @"";
- } else {
- int left = (int)(floor(self.duration)-floor(self.position));
- NSString *leftFormat = [self formatTime:left];
- NSString *durFormat = [self formatTime:self.duration];
- timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
- [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
- forIdentifier:timeLeft];
- }
-}
-
-- (void)updateCurrentPosition
-{
- NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"];
- if (!tbItem.visible)
- return;
-
- NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
- NSString *posFormat = [self formatTime:(int)floor(self.position)];
- curPosItem.stringValue = posFormat;
-
- [self removeConstraintForIdentifier:currentPosition];
- if (self.duration <= 0) {
- [self applyConstraintFromString:[self formatTime:self.position]
- forIdentifier:currentPosition];
- } else {
- NSString *durFormat = [self formatTime:self.duration];
- [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
- }
-}
-
-- (void)updatePlayButton
-{
- NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"];
- if (!self.isVisible || !tbItem.visible)
- return;
-
- NSButton *playButton = self.touchbarItems[play][@"view"];
- if (self.pause) {
- playButton.image = self.touchbarItems[play][@"imageAlt"];
- } else {
- playButton.image = self.touchbarItems[play][@"image"];
- }
-}
-
-- (void)buttonAction:(NSButton *)sender
-{
- NSString *identifier = [self getIdentifierFromView:sender];
- [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
-}
-
-- (void)seekbarChanged:(NSSlider *)slider
-{
- NSString *identifier = [self getIdentifierFromView:slider];
- NSString *seek = [NSString stringWithFormat:
- self.touchbarItems[identifier][@"cmd"], slider.doubleValue];
- [self.app queueCommand:(char *)[seek UTF8String]];
-}
-
-- (NSString *)formatTime:(int)time
-{
- int seconds = time % 60;
- int minutes = (time / 60) % 60;
- int hours = time / (60 * 60);
-
- NSString *stime = hours > 0 ? [NSString stringWithFormat:@"%d:", hours] : @"";
- stime = (stime.length > 0 || minutes > 9) ?
- [NSString stringWithFormat:@"%@%02d:", stime, minutes] :
- [NSString stringWithFormat:@"%d:", minutes];
- stime = [NSString stringWithFormat:@"%@%02d", stime, seconds];
-
- return stime;
-}
-
-- (void)removeConstraintForIdentifier:(NSTouchBarItemIdentifier)identifier
-{
- NSTextField *field = self.touchbarItems[identifier][@"view"];
- [field removeConstraint:self.touchbarItems[identifier][@"constrain"]];
-}
-
-- (void)applyConstraintFromString:(NSString *)string
- forIdentifier:(NSTouchBarItemIdentifier)identifier
-{
- NSTextField *field = self.touchbarItems[identifier][@"view"];
- if (field) {
- NSString *fString = [[string componentsSeparatedByCharactersInSet:
- [NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"];
- NSTextField *textField = [NSTextField labelWithString:fString];
- NSSize size = [textField frame].size;
-
- NSLayoutConstraint *con =
- [NSLayoutConstraint constraintWithItem:field
- attribute:NSLayoutAttributeWidth
- relatedBy:NSLayoutRelationEqual
- toItem:nil
- attribute:NSLayoutAttributeNotAnAttribute
- multiplier:1.0
- constant:(int)ceil(size.width*1.1)];
- [field addConstraint:con];
- [self.touchbarItems[identifier] setObject:con forKey:@"constrain"];
- }
-}
-
-- (NSString *)getIdentifierFromView:(id)view
-{
- NSString *identifier;
- for (identifier in self.touchbarItems)
- if([self.touchbarItems[identifier][@"view"] isEqual:view])
- break;
- return identifier;
-}
-
-- (void)processEvent:(struct mpv_event *)event
-{
- switch (event->event_id) {
- case MPV_EVENT_END_FILE: {
- self.position = 0;
- self.duration = 0;
- break;
- }
- case MPV_EVENT_PROPERTY_CHANGE: {
- [self handlePropertyChange:(mpv_event_property *)event->data];
- break;
- }
- }
-}
-
-- (void)handlePropertyChange:(struct mpv_event_property *)property
-{
- NSString *name = [NSString stringWithUTF8String:property->name];
- mpv_format format = property->format;
-
- if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
- double newPosition = *(double *)property->data;
- newPosition = newPosition < 0 ? 0 : newPosition;
- if ((int)(floor(newPosition) - floor(self.position)) != 0) {
- self.position = newPosition;
- [self updateTouchBarTimeItems];
- }
- } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
- self.duration = *(double *)property->data;
- [self updateTouchBarTimeItems];
- } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
- self.pause = *(int *)property->data;
- [self updatePlayButton];
- }
-}
-
-@end
diff --git a/osdep/main-fn-cocoa.c b/osdep/main-fn-cocoa.c
deleted file mode 100644
index eeed127..0000000
--- a/osdep/main-fn-cocoa.c
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "osdep/macosx_application.h"
-
-// This is needed because Cocoa absolutely requires creating the NSApplication
-// singleton and running it in the "main" thread. It is apparently not
-// possible to do this on a separate thread at all. It is not known how
-// Apple managed this colossal fuckup.
-int main(int argc, char *argv[])
-{
- return cocoa_main(argc, argv);
-}
diff --git a/osdep/main-fn-mac.c b/osdep/main-fn-mac.c
new file mode 100644
index 0000000..1406100
--- /dev/null
+++ b/osdep/main-fn-mac.c
@@ -0,0 +1,7 @@
+#include "osdep/mac/app_bridge.h"
+
+// Cocoa absolutely requires creating the NSApplication singleton and running it on the main thread.
+int main(int argc, char *argv[])
+{
+ return cocoa_main(argc, argv);
+}
diff --git a/osdep/main-fn-win.c b/osdep/main-fn-win.c
index 16ea80b..cf2df57 100644
--- a/osdep/main-fn-win.c
+++ b/osdep/main-fn-win.c
@@ -1,4 +1,5 @@
#include <windows.h>
+#include <shellapi.h>
#ifndef BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE
#define BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE (0x0001)
@@ -12,7 +13,7 @@
#ifndef HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION
#define HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION 1
-enum { HeapOptimizeResources = 3 };
+#define HeapOptimizeResources ((HEAP_INFORMATION_CLASS)3)
struct HEAP_OPTIMIZE_RESOURCES_INFORMATION {
DWORD Version;
diff --git a/osdep/mpv.exe.manifest b/osdep/mpv.exe.manifest
index 32dd80b..3cade40 100644
--- a/osdep/mpv.exe.manifest
+++ b/osdep/mpv.exe.manifest
@@ -12,6 +12,8 @@
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ <heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
</windowsSettings>
</application>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
diff --git a/osdep/path-macosx.m b/osdep/path-mac.m
index 8a5a704..2054269 100644
--- a/osdep/path-macosx.m
+++ b/osdep/path-mac.m
@@ -19,7 +19,7 @@
#include "options/path.h"
#include "osdep/path.h"
-const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type)
+const char *mp_get_platform_path_mac(void *talloc_ctx, const char *type)
{
if (strcmp(type, "osxbundle") == 0 && getenv("MPVBUNDLE")) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
diff --git a/osdep/path-uwp.c b/osdep/path-uwp.c
index 7eafb03..e21c9d1 100644
--- a/osdep/path-uwp.c
+++ b/osdep/path-uwp.c
@@ -27,9 +27,13 @@ WINBASEAPI DWORD WINAPI GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffe
const char *mp_get_platform_path_uwp(void *talloc_ctx, const char *type)
{
if (strcmp(type, "home") == 0) {
- wchar_t homeDir[_MAX_PATH];
- if (GetCurrentDirectoryW(_MAX_PATH, homeDir) != 0)
- return mp_to_utf8(talloc_ctx, homeDir);
+ DWORD count = GetCurrentDirectoryW(0, NULL);
+ wchar_t *home_dir = talloc_array(NULL, wchar_t, count);
+ if (GetCurrentDirectoryW(count, home_dir) != 0) {
+ char *ret = mp_to_utf8(talloc_ctx, home_dir);
+ talloc_free(home_dir);
+ return ret;
+ }
}
return NULL;
}
diff --git a/osdep/path-win.c b/osdep/path-win.c
index bddf5a5..3b104ca 100644
--- a/osdep/path-win.c
+++ b/osdep/path-win.c
@@ -24,17 +24,15 @@
#include "osdep/path.h"
#include "osdep/threads.h"
-// Warning: do not use PATH_MAX. Cygwin messed it up.
-
static mp_once path_init_once = MP_STATIC_ONCE_INITIALIZER;
static char *portable_path;
static char *mp_get_win_exe_dir(void *talloc_ctx)
{
- wchar_t w_exedir[MAX_PATH + 1] = {0};
+ wchar_t *w_exedir = talloc_array(NULL, wchar_t, MP_PATH_MAX);
- int len = (int)GetModuleFileNameW(NULL, w_exedir, MAX_PATH);
+ int len = (int)GetModuleFileNameW(NULL, w_exedir, MP_PATH_MAX);
int imax = 0;
for (int i = 0; i < len; i++) {
if (w_exedir[i] == '\\') {
@@ -42,10 +40,11 @@ static char *mp_get_win_exe_dir(void *talloc_ctx)
imax = i;
}
}
-
w_exedir[imax] = '\0';
- return mp_to_utf8(talloc_ctx, w_exedir);
+ char *ret = mp_to_utf8(talloc_ctx, w_exedir);
+ talloc_free(w_exedir);
+ return ret;
}
static char *mp_get_win_exe_subdir(void *ta_ctx, const char *name)
diff --git a/osdep/path.h b/osdep/path.h
index 2c00ea5..c5540e4 100644
--- a/osdep/path.h
+++ b/osdep/path.h
@@ -7,7 +7,7 @@
// The following type values are defined:
// "home" the native mpv-specific user config dir
// "old_home" same as "home", but lesser priority (compatibility)
-// "osxbundle" OSX bundle resource path
+// "osxbundle" macOS bundle resource path
// "global" the least priority, global config file location
// "desktop" path to desktop contents
//
@@ -26,7 +26,7 @@ typedef const char *(*mp_get_platform_path_cb)(void *talloc_ctx, const char *typ
const char *mp_get_platform_path_darwin(void *talloc_ctx, const char *type);
const char *mp_get_platform_path_uwp(void *talloc_ctx, const char *type);
const char *mp_get_platform_path_win(void *talloc_ctx, const char *type);
-const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type);
+const char *mp_get_platform_path_mac(void *talloc_ctx, const char *type);
const char *mp_get_platform_path_unix(void *talloc_ctx, const char *type);
#endif
diff --git a/osdep/semaphore_osx.c b/osdep/semaphore-mac.c
index bfb4d57..bfb4d57 100644
--- a/osdep/semaphore_osx.c
+++ b/osdep/semaphore-mac.c
diff --git a/osdep/semaphore.h b/osdep/semaphore.h
index 40cf383..81da12d 100644
--- a/osdep/semaphore.h
+++ b/osdep/semaphore.h
@@ -4,7 +4,7 @@
#include <sys/types.h>
#include <semaphore.h>
-// OSX provides non-working empty stubs, so we emulate them.
+// macOS provides non-working empty stubs, so we emulate them.
// This should be AS-safe, but cancellation issues were ignored.
// sem_getvalue() is not provided.
// sem_post() won't always correctly return an error on overflow.
diff --git a/osdep/subprocess-posix.c b/osdep/subprocess-posix.c
index 0656ec5..c75d267 100644
--- a/osdep/subprocess-posix.c
+++ b/osdep/subprocess-posix.c
@@ -281,7 +281,7 @@ void mp_subprocess2(struct mp_subprocess_opts *opts,
if (pid)
kill(pid, SIGKILL);
killed_by_us = true;
- break;
+ goto break_poll;
}
struct mp_subprocess_fd *fd = &opts->fds[n];
if (fd->on_read) {
@@ -316,6 +316,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts,
}
}
+break_poll:
+
// Note: it can happen that a child process closes the pipe, but does not
// terminate yet. In this case, we would have to run waitpid() in
// a separate thread and use pthread_cancel(), or use other weird
diff --git a/osdep/subprocess-win.c b/osdep/subprocess-win.c
index 5413b24..efeb650 100644
--- a/osdep/subprocess-win.c
+++ b/osdep/subprocess-win.c
@@ -377,7 +377,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts,
// Get pointers to the arrays in lpReserved2. This is an undocumented data
// structure used by MSVCRT (and other frameworks and runtimes) to emulate
// FD inheritance. The format is unofficially documented here:
- // https://www.catch22.net/tuts/undocumented-createprocess
+ // <https://web.archive.org/web/20221014190010/
+ // https://www.catch22.net/tuts/undocumented-createprocess>
si.StartupInfo.cbReserved2 = sizeof(int) + crt_fd_count * (1 + sizeof(intptr_t));
si.StartupInfo.lpReserved2 = talloc_size(tmp, si.StartupInfo.cbReserved2);
char *crt_buf_flags = si.StartupInfo.lpReserved2 + sizeof(int);
@@ -402,7 +403,8 @@ void mp_subprocess2(struct mp_subprocess_opts *opts,
// Specify which handles are inherited by the subprocess. If this isn't
// specified, the subprocess inherits all inheritable handles, which could
// include handles created by other threads. See:
- // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx
+ // <https://web.archive.org/web/20121127222200/
+ // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx>
si.lpAttributeList = create_handle_list(tmp, share_hndls, share_hndl_count);
// If we have a console, the subprocess will automatically attach to it so
diff --git a/osdep/terminal-dummy.c b/osdep/terminal-dummy.c
index 4b33d78..8ae6c64 100644
--- a/osdep/terminal-dummy.c
+++ b/osdep/terminal-dummy.c
@@ -1,5 +1,7 @@
#include "terminal.h"
+#include "misc/bstr.h"
+
void terminal_init(void)
{
}
@@ -25,8 +27,14 @@ void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height)
{
}
-void mp_write_console_ansi(void *wstream, char *buf)
+int mp_console_vfprintf(void *wstream, const char *format, va_list args)
+{
+ return 0;
+}
+
+int mp_console_write(void *wstream, bstr str)
{
+ return 0;
}
bool terminal_try_attach(void)
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index d5b8fe3..be0463b 100644
--- a/osdep/terminal-unix.c
+++ b/osdep/terminal-unix.c
@@ -324,7 +324,7 @@ static int setsigaction(int signo, void (*handler) (int),
struct sigaction sa;
sa.sa_handler = handler;
- if(do_mask)
+ if (do_mask)
sigfillset(&sa.sa_mask);
else
sigemptyset(&sa.sa_mask);
@@ -354,24 +354,11 @@ static int death_pipe[2] = {-1, -1};
enum { PIPE_STOP, PIPE_CONT };
static int stop_cont_pipe[2] = {-1, -1};
-static void stop_sighandler(int signum)
+static void stop_cont_sighandler(int signum)
{
int saved_errno = errno;
- (void)write(stop_cont_pipe[1], &(char){PIPE_STOP}, 1);
- errno = saved_errno;
-
- // note: for this signal, we use SA_RESETHAND but do NOT mask signals
- // so this will invoke the default handler
- raise(SIGTSTP);
-}
-
-static void continue_sighandler(int signum)
-{
- int saved_errno = errno;
- // SA_RESETHAND has reset SIGTSTP, so we need to restore it here
- setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
-
- (void)write(stop_cont_pipe[1], &(char){PIPE_CONT}, 1);
+ char sig = signum == SIGCONT ? PIPE_CONT : PIPE_STOP;
+ (void)write(stop_cont_pipe[1], &sig, 1);
errno = saved_errno;
}
@@ -435,10 +422,20 @@ static MP_THREAD_VOID terminal_thread(void *ptr)
if (fds[1].revents & POLLIN) {
int8_t c = -1;
(void)read(stop_cont_pipe[0], &c, 1);
- if (c == PIPE_STOP)
+ if (c == PIPE_STOP) {
do_deactivate_getch2();
- else if (c == PIPE_CONT)
+ if (isatty(STDERR_FILENO)) {
+ (void)write(STDERR_FILENO, TERM_ESC_RESTORE_CURSOR,
+ sizeof(TERM_ESC_RESTORE_CURSOR) - 1);
+ }
+ // trying to reset SIGTSTP handler to default and raise it will
+ // result in a race and there's no other way to invoke the
+ // default handler. so just invoke SIGSTOP since it's
+ // effectively the same thing.
+ raise(SIGSTOP);
+ } else if (c == PIPE_CONT) {
getch2_poll();
+ }
}
if (fds[2].revents) {
int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len);
@@ -470,10 +467,6 @@ void terminal_setup_getch(struct input_ctx *ictx)
if (mp_make_wakeup_pipe(death_pipe) < 0)
return;
- if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) {
- close_sig_pipes();
- return;
- }
// Disable reading from the terminal even if stdout is not a tty, to make
// mpv ... | less
@@ -490,8 +483,8 @@ void terminal_setup_getch(struct input_ctx *ictx)
}
setsigaction(SIGINT, quit_request_sighandler, SA_RESETHAND, false);
- setsigaction(SIGQUIT, quit_request_sighandler, SA_RESETHAND, false);
- setsigaction(SIGTERM, quit_request_sighandler, SA_RESETHAND, false);
+ setsigaction(SIGQUIT, quit_request_sighandler, 0, true);
+ setsigaction(SIGTERM, quit_request_sighandler, 0, true);
}
void terminal_uninit(void)
@@ -555,6 +548,11 @@ void terminal_init(void)
assert(!getch2_enabled);
getch2_enabled = 1;
+ if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) {
+ getch2_enabled = 0;
+ return;
+ }
+
tty_in = tty_out = open("/dev/tty", O_RDWR | O_CLOEXEC);
if (tty_in < 0) {
tty_in = STDIN_FILENO;
@@ -564,8 +562,8 @@ void terminal_init(void)
tcgetattr(tty_in, &tio_orig);
// handlers to fix terminal settings
- setsigaction(SIGCONT, continue_sighandler, 0, true);
- setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
+ setsigaction(SIGCONT, stop_cont_sighandler, 0, true);
+ setsigaction(SIGTSTP, stop_cont_sighandler, 0, true);
setsigaction(SIGTTIN, SIG_IGN, 0, true);
setsigaction(SIGTTOU, SIG_IGN, 0, true);
diff --git a/osdep/terminal-win.c b/osdep/terminal-win.c
index 8f3410c..dc75180 100644
--- a/osdep/terminal-win.c
+++ b/osdep/terminal-win.c
@@ -53,20 +53,17 @@ static void attempt_native_out_vt(HANDLE hOut, DWORD basemode)
SetConsoleMode(hOut, basemode);
}
-static bool is_native_out_vt(HANDLE hOut)
-{
- DWORD cmode;
- return GetConsoleMode(hOut, &cmode) &&
- (cmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) &&
- !(cmode & DISABLE_NEWLINE_AUTO_RETURN);
-}
+#define hSTDIN GetStdHandle(STD_INPUT_HANDLE)
#define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE)
#define hSTDERR GetStdHandle(STD_ERROR_HANDLE)
#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
+static bool is_console[STDERR_FILENO + 1];
+static bool is_vt[STDERR_FILENO + 1];
+static bool utf8_output;
static short stdoutAttrs = 0; // copied from the screen buffer on init
static const unsigned char ansi2win32[8] = {
0,
@@ -94,6 +91,23 @@ static HANDLE death;
static mp_thread input_thread;
static struct input_ctx *input_ctx;
+static bool is_native_out_vt_internal(HANDLE hOut)
+{
+ DWORD cmode;
+ return GetConsoleMode(hOut, &cmode) &&
+ (cmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) &&
+ !(cmode & DISABLE_NEWLINE_AUTO_RETURN);
+}
+
+static bool is_native_out_vt(HANDLE hOut)
+{
+ if (hOut == hSTDOUT)
+ return is_vt[STDOUT_FILENO];
+ if (hOut == hSTDERR)
+ return is_vt[STDERR_FILENO];
+ return is_native_out_vt_internal(hOut);
+}
+
void terminal_get_size(int *w, int *h)
{
CONSOLE_SCREEN_BUFFER_INFO cinfo;
@@ -191,6 +205,12 @@ void terminal_setup_getch(struct input_ctx *ictx)
}
}
+DWORD tmp_buffers_key = FLS_OUT_OF_INDEXES;
+struct tmp_buffers {
+ bstr write_console_buf;
+ wchar_t *write_console_wbuf;
+};
+
void terminal_uninit(void)
{
if (running) {
@@ -199,6 +219,7 @@ void terminal_uninit(void)
input_ctx = NULL;
running = false;
}
+ FlsFree(tmp_buffers_key);
}
bool terminal_in_background(void)
@@ -206,16 +227,53 @@ bool terminal_in_background(void)
return false;
}
-void mp_write_console_ansi(HANDLE wstream, char *buf)
+int mp_console_vfprintf(HANDLE wstream, const char *format, va_list args)
{
- wchar_t *wbuf = mp_from_utf8(NULL, buf);
- wchar_t *pos = wbuf;
+ struct tmp_buffers *buffers = FlsGetValue(tmp_buffers_key);
+ bool free_buf = false;
+ if (!buffers) {
+ buffers = talloc_zero(NULL, struct tmp_buffers);
+ free_buf = !FlsSetValue(tmp_buffers_key, buffers);
+ }
- while (*pos) {
- if (is_native_out_vt(wstream)) {
- WriteConsoleW(wstream, pos, wcslen(pos), NULL, NULL);
- break;
+ buffers->write_console_buf.len = 0;
+ bstr_xappend_vasprintf(buffers, &buffers->write_console_buf, format, args);
+
+ int ret = mp_console_write(wstream, buffers->write_console_buf);
+
+ if (free_buf)
+ talloc_free(buffers);
+
+ return ret;
+}
+
+int mp_console_write(HANDLE wstream, bstr str)
+{
+ struct tmp_buffers *buffers = FlsGetValue(tmp_buffers_key);
+ bool free_buf = false;
+ if (!buffers) {
+ buffers = talloc_zero(NULL, struct tmp_buffers);
+ free_buf = !FlsSetValue(tmp_buffers_key, buffers);
+ }
+
+ bool vt = is_native_out_vt(wstream);
+ int wlen = 0;
+ wchar_t *pos = NULL;
+ if (!utf8_output || !vt) {
+ wlen = bstr_to_wchar(buffers, str, &buffers->write_console_wbuf);
+ pos = buffers->write_console_wbuf;
+ }
+
+ if (vt) {
+ if (utf8_output) {
+ WriteConsoleA(wstream, str.start, str.len, NULL, NULL);
+ } else {
+ WriteConsoleW(wstream, pos, wlen, NULL, NULL);
}
+ goto done;
+ }
+
+ while (*pos) {
wchar_t *next = wcschr(pos, '\033');
if (!next) {
WriteConsoleW(wstream, pos, wcslen(pos), NULL, NULL);
@@ -227,6 +285,10 @@ void mp_write_console_ansi(HANDLE wstream, char *buf)
// CSI - Control Sequence Introducer
next += 2;
+ // private sequences
+ bool priv = next[0] == '?';
+ next += priv;
+
// CSI codes generally follow this syntax:
// "\033[" [ <i> (';' <i> )* ] <c>
// where <i> are integers, and <c> a single char command code.
@@ -250,18 +312,84 @@ void mp_write_console_ansi(HANDLE wstream, char *buf)
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(wstream, &info);
switch (code) {
- case 'K': { // erase to end of line
- COORD at = info.dwCursorPosition;
- int len = info.dwSize.X - at.X;
- FillConsoleOutputCharacterW(wstream, ' ', len, at, &(DWORD){0});
- SetConsoleCursorPosition(wstream, at);
+ case 'K': { // erase line
+ COORD cursor_pos = info.dwCursorPosition;
+ COORD at = cursor_pos;
+ int len;
+ switch (num_params ? params[0] : 0) {
+ case 1:
+ len = at.X;
+ at.X = 0;
+ break;
+ case 2:
+ len = info.dwSize.X;
+ at.X = 0;
+ break;
+ case 0:
+ default:
+ len = info.dwSize.X - at.X;
+ }
+ FillConsoleOutputCharacterW(wstream, L' ', len, at, &(DWORD){0});
+ SetConsoleCursorPosition(wstream, cursor_pos);
+ break;
+ }
+ case 'B': { // cursor down
+ info.dwCursorPosition.Y += !num_params ? 1 : params[0];
+ SetConsoleCursorPosition(wstream, info.dwCursorPosition);
break;
}
case 'A': { // cursor up
- info.dwCursorPosition.Y -= 1;
+ info.dwCursorPosition.Y -= !num_params ? 1 : params[0];
SetConsoleCursorPosition(wstream, info.dwCursorPosition);
break;
}
+ case 'J': {
+ // Only full screen clear is supported
+ if (!num_params || params[0] != 2)
+ break;
+
+ COORD top_left = {0, 0};
+ FillConsoleOutputCharacterW(wstream, L' ', info.dwSize.X * info.dwSize.Y,
+ top_left, &(DWORD){0});
+ SetConsoleCursorPosition(wstream, top_left);
+ break;
+ }
+ case 'f': {
+ if (num_params != 2)
+ break;
+ SetConsoleCursorPosition(wstream, (COORD){params[0], params[1]});
+ break;
+ }
+ case 'l': {
+ if (!priv || !num_params)
+ break;
+
+ switch (params[0]) {
+ case 25:; // hide the cursor
+ CONSOLE_CURSOR_INFO cursor_info;
+ if (!GetConsoleCursorInfo(wstream, &cursor_info))
+ break;
+ cursor_info.bVisible = FALSE;
+ SetConsoleCursorInfo(wstream, &cursor_info);
+ break;
+ }
+ break;
+ }
+ case 'h': {
+ if (!priv || !num_params)
+ break;
+
+ switch (params[0]) {
+ case 25:; // show the cursor
+ CONSOLE_CURSOR_INFO cursor_info;
+ if (!GetConsoleCursorInfo(wstream, &cursor_info))
+ break;
+ cursor_info.bVisible = TRUE;
+ SetConsoleCursorInfo(wstream, &cursor_info);
+ break;
+ }
+ break;
+ }
case 'm': { // "SGR"
short attr = info.wAttributes;
if (num_params == 0) // reset
@@ -352,7 +480,13 @@ void mp_write_console_ansi(HANDLE wstream, char *buf)
pos = next;
}
- talloc_free(wbuf);
+done:;
+ int ret = buffers->write_console_buf.len;
+
+ if (free_buf)
+ talloc_free(buffers);
+
+ return ret;
}
static bool is_a_console(HANDLE h)
@@ -360,6 +494,17 @@ static bool is_a_console(HANDLE h)
return GetConsoleMode(h, &(DWORD){0});
}
+bool mp_check_console(void *handle)
+{
+ if (handle == hSTDIN)
+ return is_console[STDIN_FILENO];
+ if (handle == hSTDOUT)
+ return is_console[STDOUT_FILENO];
+ if (handle == hSTDERR)
+ return is_console[STDERR_FILENO];
+ return is_a_console(handle);
+}
+
static void reopen_console_handle(DWORD std, int fd, FILE *stream)
{
HANDLE handle = GetStdHandle(std);
@@ -369,7 +514,6 @@ static void reopen_console_handle(DWORD std, int fd, FILE *stream)
} else {
freopen("CONOUT$", "wt", stream);
}
- setvbuf(stream, NULL, _IONBF, 0);
// Set the low-level FD to the new handle value, since mp_subprocess2
// callers might rely on low-level FDs being set. Note, with this
@@ -420,6 +564,19 @@ void terminal_init(void)
cmode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
attempt_native_out_vt(hSTDOUT, cmode);
attempt_native_out_vt(hSTDERR, cmode);
+
+ // Init for mp_check_console(), this never changes during runtime
+ is_console[STDIN_FILENO] = is_a_console(hSTDIN);
+ is_console[STDOUT_FILENO] = is_a_console(hSTDOUT);
+ is_console[STDERR_FILENO] = is_a_console(hSTDERR);
+
+ // Init for is_native_out_vt(), this is never disabled/changed during runtime
+ is_vt[STDOUT_FILENO] = is_native_out_vt_internal(hSTDOUT);
+ is_vt[STDERR_FILENO] = is_native_out_vt_internal(hSTDERR);
+
GetConsoleScreenBufferInfo(hSTDOUT, &cinfo);
stdoutAttrs = cinfo.wAttributes;
+
+ tmp_buffers_key = FlsAlloc((PFLS_CALLBACK_FUNCTION)talloc_free);
+ utf8_output = SetConsoleOutputCP(CP_UTF8);
}
diff --git a/osdep/terminal.h b/osdep/terminal.h
index 5383a17..c83b0a2 100644
--- a/osdep/terminal.h
+++ b/osdep/terminal.h
@@ -20,12 +20,16 @@
#ifndef MPLAYER_GETCH2_H
#define MPLAYER_GETCH2_H
+#include <stdarg.h>
#include <stdbool.h>
-#include <stdio.h>
+
+#include "misc/bstr.h"
#define TERM_ESC_GOTO_YX "\033[%d;%df"
#define TERM_ESC_HIDE_CURSOR "\033[?25l"
#define TERM_ESC_RESTORE_CURSOR "\033[?25h"
+#define TERM_ESC_SYNC_UPDATE_BEGIN "\033[?2026h"
+#define TERM_ESC_SYNC_UPDATE_END "\033[?2026l"
#define TERM_ESC_CLEAR_SCREEN "\033[2J"
#define TERM_ESC_ALT_SCREEN "\033[?1049h"
@@ -52,7 +56,9 @@ void terminal_get_size(int *w, int *h);
void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height);
// Windows only.
-void mp_write_console_ansi(void *wstream, char *buf);
+int mp_console_vfprintf(void *wstream, const char *format, va_list args);
+int mp_console_write(void *wstream, bstr str);
+bool mp_check_console(void *handle);
/* Windows-only function to attach to the parent process's console */
bool terminal_try_attach(void);
diff --git a/osdep/threads-posix.h b/osdep/threads-posix.h
index 482e4a8..4b3bb90 100644
--- a/osdep/threads-posix.h
+++ b/osdep/threads-posix.h
@@ -23,6 +23,11 @@
#include "common/common.h"
#include "config.h"
+// We make use of NON-POSIX pthreads functions and certain systems
+// require this header to build without issues. (ex: OpenBSD)
+#if HAVE_BSD_THREAD_NAME
+#include <pthread_np.h>
+#endif
#include "osdep/compiler.h"
#include "timer.h"
@@ -97,7 +102,7 @@ typedef pthread_once_t mp_once;
typedef pthread_t mp_thread_id;
typedef pthread_t mp_thread;
-#define MP_STATIC_COND_INITIALIZER (mp_cond){ .cond = PTHREAD_COND_INITIALIZER, .clk_id = CLOCK_REALTIME }
+#define MP_STATIC_COND_INITIALIZER { .cond = PTHREAD_COND_INITIALIZER, .clk_id = CLOCK_REALTIME }
#define MP_STATIC_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
#define MP_STATIC_ONCE_INITIALIZER PTHREAD_ONCE_INIT
@@ -230,7 +235,7 @@ static inline void mp_thread_set_name(const char *name)
}
#elif HAVE_BSD_THREAD_NAME
pthread_set_name_np(pthread_self(), name);
-#elif HAVE_OSX_THREAD_NAME
+#elif HAVE_MAC_THREAD_NAME
pthread_setname_np(name);
#endif
}
diff --git a/osdep/threads-win32.h b/osdep/threads-win32.h
index dbce353..ec90fe9 100644
--- a/osdep/threads-win32.h
+++ b/osdep/threads-win32.h
@@ -39,7 +39,7 @@ typedef HANDLE mp_thread;
typedef DWORD mp_thread_id;
#define MP_STATIC_COND_INITIALIZER CONDITION_VARIABLE_INIT
-#define MP_STATIC_MUTEX_INITIALIZER (mp_mutex){ .srw = SRWLOCK_INIT }
+#define MP_STATIC_MUTEX_INITIALIZER { .srw = SRWLOCK_INIT }
#define MP_STATIC_ONCE_INITIALIZER INIT_ONCE_STATIC_INIT
static inline int mp_mutex_init_type_internal(mp_mutex *mutex, enum mp_mutex_type mtype)
@@ -115,7 +115,7 @@ static inline int mp_cond_timedwait(mp_cond *cond, mp_mutex *mutex, int64_t time
timeout = MPCLAMP(timeout, 0, MP_TIME_MS_TO_NS(INFINITE)) / MP_TIME_MS_TO_NS(1);
int ret = 0;
- int hrt = mp_start_hires_timers(timeout);
+ int64_t hrt = mp_start_hires_timers(MP_TIME_MS_TO_NS(timeout));
BOOL bRet;
if (mutex->use_cs) {
diff --git a/osdep/timer-darwin.c b/osdep/timer-darwin.c
index bb8a9b4..36e7194 100644
--- a/osdep/timer-darwin.c
+++ b/osdep/timer-darwin.c
@@ -36,7 +36,12 @@ void mp_sleep_ns(int64_t ns)
uint64_t mp_raw_time_ns(void)
{
- return mach_absolute_time() * timebase_ratio_ns;
+ return mp_raw_time_ns_from_mach(mach_absolute_time());
+}
+
+uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time)
+{
+ return mach_time * timebase_ratio_ns;
}
void mp_raw_time_init(void)
diff --git a/osdep/timer-win32.c b/osdep/timer-win32.c
index 7867b5a..a2815ca 100644
--- a/osdep/timer-win32.c
+++ b/osdep/timer-win32.c
@@ -16,6 +16,8 @@
*/
#include <windows.h>
+#include <winternl.h>
+#include <ntstatus.h>
#include <sys/time.h>
#include <mmsystem.h>
#include <stdlib.h>
@@ -27,16 +29,23 @@
static LARGE_INTEGER perf_freq;
-// ms values
-static int hires_max = 50;
-static int hires_res = 1;
+static int64_t hires_max = MP_TIME_MS_TO_NS(50);
+static int64_t hires_res = MP_TIME_MS_TO_NS(1);
-int mp_start_hires_timers(int wait_ms)
+// NtSetTimerResolution allows setting the timer resolution to less than 1 ms.
+// Resolutions are specified in 100-ns units.
+// If Set is TRUE, set the RequestedResolution. Otherwise, return to the previous resolution.
+NTSTATUS NTAPI NtSetTimerResolution(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
+// Acquire the valid timer resolution range.
+NTSTATUS NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution, PULONG ActualResolution);
+
+int64_t mp_start_hires_timers(int64_t wait_ns)
{
#if !HAVE_UWP
- // policy: request hires_res ms resolution if wait < hires_max ms
- if (wait_ms > 0 && wait_ms <= hires_max &&
- timeBeginPeriod(hires_res) == TIMERR_NOERROR)
+ ULONG actual_res = 0;
+ // policy: request hires_res resolution if wait < hires_max ns
+ if (wait_ns > 0 && wait_ns <= hires_max &&
+ NtSetTimerResolution(hires_res / 100, TRUE, &actual_res) == STATUS_SUCCESS)
{
return hires_res;
}
@@ -44,11 +53,12 @@ int mp_start_hires_timers(int wait_ms)
return 0;
}
-void mp_end_hires_timers(int res_ms)
+void mp_end_hires_timers(int64_t res_ns)
{
#if !HAVE_UWP
- if (res_ms > 0)
- timeEndPeriod(res_ms);
+ ULONG actual_res = 0;
+ if (res_ns > 0)
+ NtSetTimerResolution(res_ns / 100, FALSE, &actual_res);
#endif
}
@@ -57,7 +67,7 @@ void mp_sleep_ns(int64_t ns)
if (ns < 0)
return;
- int hrt = mp_start_hires_timers(ns < 1e6 ? 1 : ns / 1e6);
+ int64_t hrt = mp_start_hires_timers(ns);
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x2
@@ -107,21 +117,28 @@ void mp_raw_time_init(void)
QueryPerformanceFrequency(&perf_freq);
#if !HAVE_UWP
+ ULONG min_res, max_res, actual_res;
+ if (NtQueryTimerResolution(&min_res, &max_res, &actual_res) != STATUS_SUCCESS) {
+ min_res = 156250;
+ max_res = 10000;
+ }
+
// allow (undocumented) control of all the High Res Timers parameters,
// for easier experimentation and diagnostic of bug reports.
const char *v;
+ char *end;
// 1..1000 ms max timetout for hires (used in "perwait" mode)
if ((v = getenv("MPV_HRT_MAX"))) {
- int hmax = atoi(v);
- if (hmax >= 1 && hmax <= 1000)
+ int64_t hmax = strtoll(v, &end, 10);
+ if (*end == '\0' && hmax >= MP_TIME_MS_TO_NS(1) && hmax <= MP_TIME_MS_TO_NS(1000))
hires_max = hmax;
}
- // 1..15 ms hires resolution (not used in "never" mode)
+ // hires resolution clamped by the available resolution range (not used in "never" mode)
if ((v = getenv("MPV_HRT_RES"))) {
- int res = atoi(v);
- if (res >= 1 && res <= 15)
+ int64_t res = strtoll(v, &end, 10);
+ if (*end == '\0' && res >= max_res * INT64_C(100) && res <= min_res * INT64_C(100))
hires_res = res;
}
@@ -134,8 +151,8 @@ void mp_raw_time_init(void)
} else if (!strcmp(v, "never")) {
hires_max = 0;
} else { // "always" or unknown value
+ mp_start_hires_timers(hires_res);
hires_max = 0;
- timeBeginPeriod(hires_res);
}
#endif
}
diff --git a/osdep/timer.c b/osdep/timer.c
index d0a8a92..907ba50 100644
--- a/osdep/timer.c
+++ b/osdep/timer.c
@@ -46,7 +46,12 @@ void mp_time_init(void)
int64_t mp_time_ns(void)
{
- return mp_raw_time_ns() - raw_time_offset;
+ return mp_time_ns_from_raw_time(mp_raw_time_ns());
+}
+
+int64_t mp_time_ns_from_raw_time(uint64_t raw_time)
+{
+ return raw_time - raw_time_offset;
}
double mp_time_sec(void)
diff --git a/osdep/timer.h b/osdep/timer.h
index 3a925ca..5fedbb6 100644
--- a/osdep/timer.h
+++ b/osdep/timer.h
@@ -19,13 +19,17 @@
#define MPLAYER_TIMER_H
#include <inttypes.h>
+#include "config.h"
// Initialize timer, must be called at least once at start.
void mp_time_init(void);
-// Return time in nanoseconds. Never wraps. Never returns 0 or negative values.
+// Return time in nanoseconds. Never wraps. Never returns negative values.
int64_t mp_time_ns(void);
+// Return time in nanoseconds. Coverts raw time in nanoseconds to mp time, subtracts init offset.
+int64_t mp_time_ns_from_raw_time(uint64_t raw_time);
+
// Return time in seconds. Can have down to 1 nanosecond resolution, but will
// be much worse when casted to float.
double mp_time_sec(void);
@@ -38,12 +42,17 @@ uint64_t mp_raw_time_ns(void);
// Sleep in nanoseconds.
void mp_sleep_ns(int64_t ns);
+#if HAVE_DARWIN
+// Coverts mach time to raw time in nanoseconds and returns it.
+uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time);
+#endif
+
#ifdef _WIN32
-// returns: timer resolution in ms if needed and started successfully, else 0
-int mp_start_hires_timers(int wait_ms);
+// returns: timer resolution in ns if needed and started successfully, else 0
+int64_t mp_start_hires_timers(int64_t wait_ns);
// call unconditionally with the return value of mp_start_hires_timers
-void mp_end_hires_timers(int resolution_ms);
+void mp_end_hires_timers(int64_t resolution_ns);
#endif /* _WIN32 */
// Converts time units to nanoseconds (int64_t)
diff --git a/osdep/apple_utils.c b/osdep/utils-mac.c
index 02cdfaa..6acddfb 100644
--- a/osdep/apple_utils.c
+++ b/osdep/utils-mac.c
@@ -17,7 +17,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "apple_utils.h"
+#include "utils-mac.h"
#include "mpv_talloc.h"
diff --git a/osdep/apple_utils.h b/osdep/utils-mac.h
index 166937e..b324825 100644
--- a/osdep/apple_utils.h
+++ b/osdep/utils-mac.h
@@ -17,12 +17,12 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MPV_APPLE_UTILS
-#define MPV_APPLE_UTILS
+#ifndef UTILS_MAC
+#define UTILS_MAC
#include <CoreFoundation/CoreFoundation.h>
CFStringRef cfstr_from_cstr(const char *str);
char *cfstr_get_cstr(const CFStringRef cfstr);
-#endif /* MPV_APPLE_UTILS */
+#endif /* UTILS_MAC */
diff --git a/osdep/w32_keyboard.c b/osdep/w32_keyboard.c
index 52221e6..57988ec 100644
--- a/osdep/w32_keyboard.c
+++ b/osdep/w32_keyboard.c
@@ -93,6 +93,8 @@ static const struct keymap appcmd_map[] = {
{APPCOMMAND_LAUNCH_MAIL, MP_KEY_MAIL},
{APPCOMMAND_BROWSER_FAVORITES, MP_KEY_FAVORITES},
{APPCOMMAND_BROWSER_SEARCH, MP_KEY_SEARCH},
+ {APPCOMMAND_BROWSER_BACKWARD, MP_KEY_GO_BACK},
+ {APPCOMMAND_BROWSER_FORWARD, MP_KEY_GO_FORWARD},
{0, 0}
};
diff --git a/osdep/win32-console-wrapper.c b/osdep/win32-console-wrapper.c
index 4e74dac..a579163 100644
--- a/osdep/win32-console-wrapper.c
+++ b/osdep/win32-console-wrapper.c
@@ -19,6 +19,9 @@
#include <stdio.h>
#include <windows.h>
+// copied from osdep/io.h since this file is standalone
+#define MP_PATH_MAX (32000)
+
int wmain(int argc, wchar_t **argv, wchar_t **envp);
static void cr_perror(const wchar_t *prefix)
@@ -32,32 +35,37 @@ static void cr_perror(const wchar_t *prefix)
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&error, 0, NULL);
- fwprintf(stderr, L"%s: %s", prefix, error);
+ fwprintf(stderr, L"%ls: %ls", prefix, error);
LocalFree(error);
}
static int cr_runproc(wchar_t *name, wchar_t *cmdline)
{
- STARTUPINFOW si;
- STARTUPINFOW our_si;
- PROCESS_INFORMATION pi;
DWORD retval = 1;
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
- si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
- si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
- si.dwFlags |= STARTF_USESTDHANDLES;
-
// Copy the list of inherited CRT file descriptors to the new process
- our_si.cb = sizeof(our_si);
+ STARTUPINFOW our_si = {sizeof(our_si)};
GetStartupInfoW(&our_si);
- si.lpReserved2 = our_si.lpReserved2;
- si.cbReserved2 = our_si.cbReserved2;
-
- ZeroMemory(&pi, sizeof(pi));
+ // Don't redirect std streams if they are attached to a console. Let mpv
+ // attach to the console directly in this case. In theory, it should work
+ // out of the box because "console-like" handles should be managed by Windows
+ // internally, which works for INPUT and OUTPUT, but in certain cases,
+ // not for ERROR.
+ DWORD mode;
+ HANDLE hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ HANDLE hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ STARTUPINFOW si = {
+ .cb = sizeof(si),
+ .lpReserved2 = our_si.lpReserved2,
+ .cbReserved2 = our_si.cbReserved2,
+ .hStdInput = GetConsoleMode(hStdInput, &mode) ? NULL : hStdInput,
+ .hStdOutput = GetConsoleMode(hStdOutput, &mode) ? NULL : hStdOutput,
+ .hStdError = GetConsoleMode(hStdError, &mode) ? NULL : hStdError,
+ };
+ si.dwFlags = (si.hStdInput || si.hStdOutput || si.hStdError) ? STARTF_USESTDHANDLES : 0;
+ PROCESS_INFORMATION pi = {0};
if (!CreateProcessW(name, cmdline, NULL, NULL, TRUE, 0,
NULL, NULL, &si, &pi)) {
@@ -75,10 +83,11 @@ static int cr_runproc(wchar_t *name, wchar_t *cmdline)
int wmain(int argc, wchar_t **argv, wchar_t **envp)
{
wchar_t *cmd;
- wchar_t exe[MAX_PATH];
+ wchar_t *exe;
cmd = GetCommandLineW();
- GetModuleFileNameW(NULL, exe, MAX_PATH);
+ exe = LocalAlloc(LPTR, MP_PATH_MAX * sizeof(wchar_t));
+ GetModuleFileNameW(NULL, exe, MP_PATH_MAX);
wcscpy(wcsrchr(exe, '.') + 1, L"exe");
// Set an environment variable so the child process can tell whether it
diff --git a/osdep/windows_utils.c b/osdep/windows_utils.c
index 8cedf93..91eef62 100644
--- a/osdep/windows_utils.c
+++ b/osdep/windows_utils.c
@@ -24,9 +24,12 @@
#include <audioclient.h>
#include <d3d9.h>
#include <dxgi1_2.h>
+#include <ole2.h>
+#include <shobjidl.h>
#include "common/common.h"
#include "windows_utils.h"
+#include "mpv_talloc.h"
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
{
@@ -227,3 +230,22 @@ error:
*server = *client = INVALID_HANDLE_VALUE;
return false;
}
+
+wchar_t *mp_w32_get_shell_link_target(wchar_t *path)
+{
+ IShellLink *psl = NULL;
+ IPersistFile *ppf = NULL;
+ wchar_t *buf = talloc_array(NULL, wchar_t, MAX_PATH + 1);
+
+ if (FAILED(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (void**)&psl)) ||
+ FAILED(IShellLinkW_QueryInterface(psl, &IID_IPersistFile, (void**)&ppf)) ||
+ FAILED(IPersistFile_Load(ppf, path, STGM_READ)) ||
+ FAILED(IShellLinkW_GetPath(psl, buf, MAX_PATH, NULL, 0)))
+ {
+ TA_FREEP(&buf);
+ }
+
+ SAFE_RELEASE(psl);
+ SAFE_RELEASE(ppf);
+ return buf;
+}
diff --git a/osdep/windows_utils.h b/osdep/windows_utils.h
index a8a5e94..c3fc72a 100644
--- a/osdep/windows_utils.h
+++ b/osdep/windows_utils.h
@@ -46,4 +46,8 @@ struct w32_create_anon_pipe_opts {
bool mp_w32_create_anon_pipe(HANDLE *server, HANDLE *client,
struct w32_create_anon_pipe_opts *opts);
+// Returns the target of the shell link in talloc memory if the path is a
+// resolvable shell link, otherwise returns NULL.
+wchar_t *mp_w32_get_shell_link_target(wchar_t *path);
+
#endif
diff --git a/osdep/macosx_menubar.h b/video/out/win32/menu.h
index 509083d..8b1fe72 100644
--- a/osdep/macosx_menubar.h
+++ b/video/out/win32/menu.h
@@ -15,16 +15,18 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MPV_MACOSX_MENU
-#define MPV_MACOSX_MENU
+#ifndef MP_WIN32_MENU_H
+#define MP_WIN32_MENU_H
-// Menu Keys identifying menu items
-typedef enum {
- MPM_H_SIZE,
- MPM_N_SIZE,
- MPM_D_SIZE,
- MPM_MINIMIZE,
- MPM_ZOOM,
-} MPMenuKey;
+#include <windows.h>
-#endif /* MPV_MACOSX_MENU */
+struct mpv_node;
+struct menu_ctx;
+
+struct menu_ctx *mp_win32_menu_init(void);
+void mp_win32_menu_uninit(struct menu_ctx *ctx);
+void mp_win32_menu_show(struct menu_ctx *ctx, HWND hwnd);
+void mp_win32_menu_update(struct menu_ctx *ctx, struct mpv_node *data);
+const char* mp_win32_menu_get_cmd(struct menu_ctx *ctx, UINT id);
+
+#endif