diff options
Diffstat (limited to '')
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); @@ -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; -} @@ -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, ¶ms) < 0) { - log.sendError("Render context init has failed.") + if (mpv_render_context_create(&mpvRenderContext, mpv, ¶ms) < 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 |