From a81212836dc1211a41eb5e2f76de6fbe214c4f6d Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:21:32 -0500 Subject: [PATCH 01/53] ci/mingw: enable nvdec Install the nv-codec-headers to enable nvdec for ffmpeg. This allows the CI build to enable hwdec on opengl and vulkan contexts. --- ci/build-mingw64.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh index 797c48358c894..350aac535cc67 100755 --- a/ci/build-mingw64.sh +++ b/ci/build-mingw64.sh @@ -161,6 +161,14 @@ _spirv_cross () { } _spirv_cross_mark=lib/libspirv-cross-c-shared.dll.a +_nv_headers () { + [ -d nv-codec-headers ] || $gitclone https://github.com/FFmpeg/nv-codec-headers + pushd nv-codec-headers + makeplusinstall + popd +} +_nv_headers_mark=include/ffnvcodec/dynlink_loader.h + _vulkan_headers () { [ -d Vulkan-Headers ] || $gitclone https://github.com/KhronosGroup/Vulkan-Headers builddir Vulkan-Headers @@ -246,7 +254,7 @@ _luajit () { } _luajit_mark=lib/libluajit-5.1.a -for x in iconv zlib shaderc spirv-cross; do +for x in iconv zlib shaderc spirv-cross nv-headers; do build_if_missing $x done if [[ "$TARGET" != "i686-"* ]]; then From 7d31db9e456560f24d831a173516bac46b341828 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:22:16 -0500 Subject: [PATCH 02/53] ci/mingw: add dav1d decoder ffmpeg has built-in decoders for almost all common formats, except AV1. dav1d allows the CI build to play AV1 videos without a hwdec-capable GPU. --- ci/build-mingw64.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh index 350aac535cc67..77449990e916e 100755 --- a/ci/build-mingw64.sh +++ b/ci/build-mingw64.sh @@ -121,6 +121,16 @@ _zlib () { } _zlib_mark=lib/libz.dll.a +_dav1d () { + [ -d dav1d ] || $gitclone https://code.videolan.org/videolan/dav1d.git + builddir dav1d + meson setup .. --cross-file "$prefix_dir/crossfile" \ + -Denable_{tools,tests}=false + makeplusinstall + popd +} +_dav1d_mark=lib/libdav1d.dll.a + _ffmpeg () { [ -d ffmpeg ] || $gitclone https://github.com/FFmpeg/FFmpeg.git ffmpeg builddir ffmpeg @@ -129,7 +139,7 @@ _ffmpeg () { --enable-cross-compile --cross-prefix=$TARGET- --arch=${TARGET%%-*} --cc="$CC" --cxx="$CXX" $commonflags --disable-{doc,programs,muxers,encoders} - --enable-encoder=mjpeg,png + --enable-encoder=mjpeg,png --enable-libdav1d ) pkg-config vulkan && args+=(--enable-vulkan --enable-libshaderc) ../configure "${args[@]}" @@ -254,7 +264,7 @@ _luajit () { } _luajit_mark=lib/libluajit-5.1.a -for x in iconv zlib shaderc spirv-cross nv-headers; do +for x in iconv zlib shaderc spirv-cross nv-headers dav1d; do build_if_missing $x done if [[ "$TARGET" != "i686-"* ]]; then @@ -299,7 +309,7 @@ if [ "$2" = pack ]; then dlls=( libgcc_*.dll lib{ssp,stdc++,winpthread}-[0-9]*.dll # compiler runtime av*.dll sw*.dll lib{ass,freetype,fribidi,harfbuzz,iconv,placebo}-[0-9]*.dll - lib{shaderc_shared,spirv-cross-c-shared}.dll zlib1.dll + lib{shaderc_shared,spirv-cross-c-shared,dav1d}.dll zlib1.dll # note: vulkan-1.dll is not here since drivers provide it ) mv -v "${dlls[@]}" .. From 385031ae2d525ccf8c2a6fb71268e197b6d26a07 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:44:53 -0500 Subject: [PATCH 03/53] ci/mingw: update dependency versions --- ci/build-mingw64.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh index 77449990e916e..d8449a9b0bc44 100755 --- a/ci/build-mingw64.sh +++ b/ci/build-mingw64.sh @@ -110,7 +110,7 @@ _iconv () { _iconv_mark=lib/libiconv.dll.a _zlib () { - local ver=1.3 + local ver=1.3.1 gettar "https://zlib.net/fossils/zlib-${ver}.tar.gz" pushd zlib-${ver} make -fwin32/Makefile.gcc clean @@ -209,7 +209,7 @@ _libplacebo () { _libplacebo_mark=lib/libplacebo.dll.a _freetype () { - local ver=2.13.1 + local ver=2.13.2 gettar "https://mirror.netcologne.de/savannah/freetype/freetype-${ver}.tar.xz" builddir freetype-${ver} meson setup .. --cross-file "$prefix_dir/crossfile" @@ -230,7 +230,7 @@ _fribidi () { _fribidi_mark=lib/libfribidi.dll.a _harfbuzz () { - local ver=8.1.1 + local ver=8.3.0 gettar "https://github.com/harfbuzz/harfbuzz/releases/download/${ver}/harfbuzz-${ver}.tar.xz" builddir harfbuzz-${ver} meson setup .. --cross-file "$prefix_dir/crossfile" \ From b08822b8ce829856261d826c199e94a7fdab72ab Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sun, 3 Mar 2024 15:15:51 -0600 Subject: [PATCH 04/53] test/test_utils: add mp_msg_set_max_level stub Needed since the previous commit. --- test/test_utils.c | 1 + test/test_utils.h | 1 + 2 files changed, 2 insertions(+) diff --git a/test/test_utils.c b/test/test_utils.c index b80caf8f710df..98597c9847491 100644 --- a/test/test_utils.c +++ b/test/test_utils.c @@ -105,6 +105,7 @@ const char *mp_help_text; void mp_msg(struct mp_log *log, int lev, const char *format, ...) {}; int mp_msg_find_level(const char *s) {return 0;}; int mp_msg_level(struct mp_log *log) {return 0;}; +void mp_msg_set_max_level(struct mp_log *log, int lev) {}; void mp_write_console_ansi(void) {}; void mp_set_avdict(AVDictionary **dict, char **kv) {}; struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, diff --git a/test/test_utils.h b/test/test_utils.h index 66615d3710857..df8c567ce90aa 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -51,6 +51,7 @@ void mp_msg(struct mp_log *log, int lev, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); int mp_msg_find_level(const char *s); int mp_msg_level(struct mp_log *log); +void mp_msg_set_max_level(struct mp_log *log, int lev); void mp_write_console_ansi(void); typedef struct AVDictionary AVDictionary; void mp_set_avdict(AVDictionary **dict, char **kv); From 60abbb424bef6873e7bfcbae0f43c4b972fe4274 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Fri, 1 Mar 2024 23:47:09 -0600 Subject: [PATCH 05/53] player/audio: also adjust apts by audio speed in audio_start_ao Fixes 7051e94e4bacd00e53e88835d28e9d9082de3bb3 --- player/audio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/player/audio.c b/player/audio.c index 7a37c0d2f289b..05a39cd81922f 100644 --- a/player/audio.c +++ b/player/audio.c @@ -829,7 +829,8 @@ void audio_start_ao(struct MPContext *mpctx) double pts = MP_NOPTS_VALUE; if (!get_sync_pts(mpctx, &pts)) return; - double apts = playing_audio_pts(mpctx); + double apts = written_audio_pts(mpctx); + apts -= apts != MP_NOPTS_VALUE ? mpctx->audio_speed * ao_get_delay(mpctx->ao) : 0; if (pts != MP_NOPTS_VALUE && apts != MP_NOPTS_VALUE && pts < apts && mpctx->video_status != STATUS_EOF) { From d10cebec1317d1fd9006f3bd3bdc6d75e399ebaa Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sat, 2 Mar 2024 09:50:23 -0600 Subject: [PATCH 06/53] player/video: subtract frame_time from delay when ao_chain starts audio This seems more robust than relying on the audio status to actually be playing. For files where there is no audio or the audio start is delayed, this guards against that but it allows the subtraction to always occur otherwise on normal files with audio. --- player/audio.c | 1 + player/core.h | 2 ++ player/video.c | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/player/audio.c b/player/audio.c index 05a39cd81922f..da91dd4340c9c 100644 --- a/player/audio.c +++ b/player/audio.c @@ -846,6 +846,7 @@ void audio_start_ao(struct MPContext *mpctx) } MP_VERBOSE(mpctx, "starting audio playback\n"); + ao_c->audio_started = true; ao_start(ao_c->ao); mpctx->audio_status = STATUS_PLAYING; if (ao_c->out_eof) { diff --git a/player/core.h b/player/core.h index dcb3c27e12056..fc5e2e3937494 100644 --- a/player/core.h +++ b/player/core.h @@ -192,6 +192,8 @@ struct ao_chain { double start_pts; bool start_pts_known; + bool audio_started; + struct track *track; struct mp_pin *filter_src; struct mp_pin *dec_src; diff --git a/player/video.c b/player/video.c index 6b63cc7b0edc8..c33f947659af8 100644 --- a/player/video.c +++ b/player/video.c @@ -359,7 +359,6 @@ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time if (mpctx->audio_status != STATUS_PLAYING) return; - mpctx->delay -= frame_time; double a_pts = written_audio_pts(mpctx) + opts->audio_delay - mpctx->delay; double av_delay = a_pts - v_pts; @@ -401,6 +400,8 @@ static void handle_new_frame(struct MPContext *mpctx) } } mpctx->time_frame += frame_time / mpctx->video_speed; + if (mpctx->ao_chain && mpctx->ao_chain->audio_started) + mpctx->delay -= frame_time; if (mpctx->video_status >= STATUS_PLAYING) adjust_sync(mpctx, pts, frame_time); MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); From 9ac791c329aa967d6034da1543e03c777b09f4ef Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sat, 2 Mar 2024 15:27:19 -0600 Subject: [PATCH 07/53] Revert "player: reset av state on speed changes" Ended up being too flawed and caused trouble in other areas. There's other approaches to trying to solve the issue this meant to address in the works that should be better, so let's wait on that. Fixes #13613 and fixes #13622. This reverts commit e3af545421322e357eb9355395923710ea93f83b. --- player/command.c | 1 - player/core.h | 1 - player/video.c | 22 ++-------------------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/player/command.c b/player/command.c index e1de32be25527..5b61ae3498797 100644 --- a/player/command.c +++ b/player/command.c @@ -7246,7 +7246,6 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (opt_ptr == &opts->playback_speed) { update_playback_speed(mpctx); - reset_av_state(mpctx); mp_wakeup_core(mpctx); } diff --git a/player/core.h b/player/core.h index fc5e2e3937494..5e97b6d8687c6 100644 --- a/player/core.h +++ b/player/core.h @@ -637,7 +637,6 @@ void update_osd_msg(struct MPContext *mpctx); bool update_subtitles(struct MPContext *mpctx, double video_pts); // video.c -void reset_av_state(struct MPContext *mpctx); void reset_video_state(struct MPContext *mpctx); int init_video_decoder(struct MPContext *mpctx, struct track *track); void reinit_video_chain(struct MPContext *mpctx); diff --git a/player/video.c b/player/video.c index c33f947659af8..908299baa10af 100644 --- a/player/video.c +++ b/player/video.c @@ -45,8 +45,6 @@ #include "command.h" #include "screenshot.h" -#define MIN_PAST_FRAMES 10 - enum { // update_video() - code also uses: <0 error, 0 eof, >0 progress VD_ERROR = -1, @@ -97,17 +95,6 @@ static void vo_chain_reset_state(struct vo_chain *vo_c) vo_c->underrun_signaled = false; } -void reset_av_state(struct MPContext *mpctx) -{ - mpctx->delay = 0; - mpctx->display_sync_drift_dir = 0; - mpctx->display_sync_error = 0; - mpctx->last_av_difference = 0; - mpctx->logged_async_diff = -1; - mpctx->num_past_frames = 0; - mpctx->total_avsync_change = 0; -} - void reset_video_state(struct MPContext *mpctx) { if (mpctx->vo_chain) { @@ -606,9 +593,7 @@ static void update_avsync_before_frame(struct MPContext *mpctx) if (mpctx->video_status < STATUS_READY) { mpctx->time_frame = 0; - } else if (mpctx->display_sync_active || vo->opts->video_sync == VS_NONE || - mpctx->num_past_frames <= MIN_PAST_FRAMES) - { + } else if (mpctx->display_sync_active || vo->opts->video_sync == VS_NONE) { // don't touch the timing } else if (mpctx->audio_status == STATUS_PLAYING && mpctx->video_status == STATUS_PLAYING && @@ -742,7 +727,7 @@ static double compute_audio_drift(struct MPContext *mpctx, double vsync) // audio desync for y. Assume speed didn't change for the frames we're // looking at for simplicity. This also should actually use the realtime // (minus paused time) for x, but use vsync scheduling points instead. - if (mpctx->num_past_frames <= MIN_PAST_FRAMES) + if (mpctx->num_past_frames <= 10) return NAN; int num = mpctx->num_past_frames - 1; double sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0; @@ -847,9 +832,6 @@ static void handle_display_sync_frame(struct MPContext *mpctx, if (resample && using_spdif_passthrough(mpctx)) return; - if (mpctx->num_past_frames <= MIN_PAST_FRAMES) - return; - double vsync = vo_get_vsync_interval(vo) / 1e9; if (vsync <= 0) return; From 781f78fb3a0457aea8084f94feeeb69978a02a71 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Mon, 4 Mar 2024 09:42:15 -0600 Subject: [PATCH 08/53] wayland: guess the first hidpi frame better It's been a longstanding issue in wayland* that the first frame on a hidpi screen will have wrong scaling. A well behaved client immediately corrects this, but it's noticeable and also can affect window placement due to the way resizng works. Preferred scale from the fractional protocol and preferred buffer scale can actually solve this problem. It depends on compositors mostly, but one could simply send the event before the client maps its surface so it knows what the correct scale is in the first place. I'm not sure if any compositors currently behave like this (sway seems to still require the client to render before sending any scaling information at least), but it makes to sense to account for this possibility. *: https://gitlab.freedesktop.org/wayland/wayland/-/issues/133 --- video/out/wayland_common.c | 8 +++++++- video/out/wayland_common.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index c7314cb144539..52d5e90f886cd 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -985,6 +985,7 @@ static void surface_handle_preferred_buffer_scale(void *data, return; wl->scaling = scale; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->scaling); wl->pending_vo_events |= VO_EVENT_DPI; @@ -1209,6 +1210,7 @@ static void preferred_scale(void *data, double old_scale = wl->scaling; wl->scaling = (double)scale / 120; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->scaling); wl->pending_vo_events |= VO_EVENT_DPI; @@ -2071,8 +2073,11 @@ static int set_screensaver_inhibitor(struct vo_wayland_state *wl, int state) static void set_surface_scaling(struct vo_wayland_state *wl) { - if (wl->fractional_scale_manager || wl_surface_get_version(wl->surface) >= 6) + if (wl->scale_configured && (wl->fractional_scale_manager || + wl_surface_get_version(wl->surface) >= 6)) + { return; + } double old_scale = wl->scaling; wl->scaling = wl->current_output->scale; @@ -2587,6 +2592,7 @@ bool vo_wayland_reconfig(struct vo *vo) if (!wl->current_output) return false; set_surface_scaling(wl); + wl->scale_configured = true; wl->pending_vo_events |= VO_EVENT_DPI; } diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 721d4fb2eb4ee..3e8530eaffc3b 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -75,6 +75,7 @@ struct vo_wayland_state { bool hidden; bool initial_size_hint; bool locked_size; + bool scale_configured; bool state_change; bool tiled; bool toplevel_configured; From c1029aaa820de8193e2a466039d2acccca610fd6 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Mon, 4 Mar 2024 10:00:53 -0600 Subject: [PATCH 09/53] wayland: fix missing lround in cursor surface Missed in f0a6578259f508a8863afcf9a1487872d7ae1878. --- video/out/wayland_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 52d5e90f886cd..6fa5da1ca0449 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -1984,7 +1984,7 @@ static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) wl_pointer_set_cursor(s->pointer, s->pointer_serial, wl->cursor_surface, img->hotspot_x / scale, img->hotspot_y / scale); wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale), - img->height / scale); + lround(img->height / scale)); wl_surface_attach(wl->cursor_surface, buffer, 0, 0); wl_surface_damage_buffer(wl->cursor_surface, 0, 0, img->width, img->height); } From 38a8e9bcba3bd31ecf5efbeee07c2169f82192d8 Mon Sep 17 00:00:00 2001 From: 1nsane000 <33362569+1nsane000@users.noreply.github.com> Date: Tue, 5 Mar 2024 02:20:01 +0100 Subject: [PATCH 10/53] options: add --deinterlace-field-parity option Previously there was no way to specify the field order of interlaced videos when deinterlacing with inbuilt filters. Lavfi deinterlacers seemed to prefer top field order while inbuilt ones (vdpaupp, vavpp, d3d11vpp) seemed to prefer bottom field order. The default "auto" option should work exactly as before while specifying either "tff" or "bff" should force the specified field order --- DOCS/interface-changes.rst | 1 + DOCS/man/options.rst | 11 +++++++++++ options/options.c | 8 ++++++++ options/options.h | 1 + video/filter/refqueue.h | 4 ++++ 5 files changed, 25 insertions(+) diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 2abf99ed938e4..13f8cf20efbc0 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -28,6 +28,7 @@ Interface changes --- mpv 0.38.0 --- - add `begin-vo-dragging` command + - add `--deinterlace-field-parity` option - add `--volume-gain`, `--volume-gain-min`, and `--volume-gain-max` options - add `current-gpu-context` property - add `--secondary-sub-ass-override` option diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 178b739c8da3f..6a09cf388484e 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1676,6 +1676,17 @@ Video inserted deinterlacing filters, and that this will make video look worse if it's not actually interlaced. +``--deinterlace-field-parity=`` + Specify the field parity/order when deinterlacing(default: auto) + Each frame of an interlaced video is divided into two fields, which are + then separately transmitted. Top field represents even lines while bottom + field represents odd lines. When deinterlacing the deinterlacer needs to + know the correct temporal order of the fields else the video will appear + jittery. + + ``auto`` will automatically try to detect the field order of the video, + ``tff`` forces top field first while ``bff`` forces bottom field first. + ``--frames=`` Play/convert only first ```` video frames, then quit. diff --git a/options/options.c b/options/options.c index 3675d5734d551..796a6c668bb6f 100644 --- a/options/options.c +++ b/options/options.c @@ -42,6 +42,7 @@ #include "input/event.h" #include "stream/stream.h" #include "video/csputils.h" +#include "video/filter/refqueue.h" #include "video/hwdec.h" #include "video/image_writer.h" #include "sub/osd.h" @@ -440,9 +441,16 @@ const struct m_sub_options filter_conf = { .opts = (const struct m_option[]){ {"deinterlace", OPT_CHOICE(deinterlace, {"no", 0}, {"yes", 1}, {"auto", -1})}, + {"deinterlace-field-parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }, .size = sizeof(OPT_BASE_STRUCT), + .defaults = &(const struct filter_opts){ + .field_parity = MP_FIELD_PARITY_AUTO, + }, .change_flags = UPDATE_IMGPAR, }; diff --git a/options/options.h b/options/options.h index 9dce3f609ca6e..1b7c3e358351f 100644 --- a/options/options.h +++ b/options/options.h @@ -399,6 +399,7 @@ struct dvd_opts { struct filter_opts { int deinterlace; + int field_parity; }; extern const struct m_sub_options vo_sub_opts; diff --git a/video/filter/refqueue.h b/video/filter/refqueue.h index 0a8ace003153e..9d47ee87fbbf1 100644 --- a/video/filter/refqueue.h +++ b/video/filter/refqueue.h @@ -29,6 +29,10 @@ enum { MP_MODE_INTERLACED_ONLY = (1 << 2), // only deinterlace marked frames }; +#define MP_FIELD_PARITY_AUTO -1 +#define MP_FIELD_PARITY_TFF 0 +#define MP_FIELD_PARITY_BFF 1 + void mp_refqueue_set_mode(struct mp_refqueue *q, int flags); bool mp_refqueue_should_deint(struct mp_refqueue *q); bool mp_refqueue_is_top_field(struct mp_refqueue *q); From 5b52d4497206f9d977f0f1bbf08c396e76dd619f Mon Sep 17 00:00:00 2001 From: 1nsane000 <33362569+1nsane000@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:43:50 +0100 Subject: [PATCH 11/53] f_auto_filters: pass field parity to lavfi bwdif deinterlacers Since all of them(software, vulkan, cuda) already have a built in parity parameter --- filters/f_auto_filters.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c index 43791ed13a707..b37fd9d5f4c24 100644 --- a/filters/f_auto_filters.c +++ b/filters/f_auto_filters.c @@ -6,6 +6,7 @@ #include "common/msg.h" #include "options/m_config.h" #include "options/options.h" +#include "video/filter/refqueue.h" #include "video/mp_image.h" #include "video/mp_image_pool.h" @@ -73,6 +74,18 @@ static void deint_process(struct mp_filter *f) return; } + char *field_parity; + switch (opts->field_parity) { + case MP_FIELD_PARITY_TFF: + field_parity = "tff"; + break; + case MP_FIELD_PARITY_BFF: + field_parity = "bff"; + break; + default: + field_parity = "auto"; + } + bool has_filter = true; if (img->imgfmt == IMGFMT_VDPAU) { char *args[] = {"deint", "yes", NULL}; @@ -82,11 +95,13 @@ static void deint_process(struct mp_filter *f) p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", NULL); } else if (img->imgfmt == IMGFMT_CUDA) { - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_cuda", args); } else if (img->imgfmt == IMGFMT_VULKAN) { - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_vulkan", args); } else if (img->imgfmt == IMGFMT_VAAPI) { @@ -120,7 +135,8 @@ static void deint_process(struct mp_filter *f) } } - char *args[] = {"mode", "send_field", NULL}; + char *args[] = {"mode", "send_field", + "parity", field_parity, NULL}; filters[1] = mp_create_user_filter(subf, MP_OUTPUT_CHAIN_VIDEO, "bwdif", args); From 03bfd797f6d8d703601aad1065194c760d8927dd Mon Sep 17 00:00:00 2001 From: 1nsane000 <33362569+1nsane000@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:55:36 +0100 Subject: [PATCH 12/53] video/filter: add field order support for built in deinterlacers refqueue gets the field of the frame from mp_image which almost always(if not always) assumes bottom field order first. By default this behavior should not change but specifying the field order should bypass this. --- filters/f_auto_filters.c | 9 ++++++--- video/filter/refqueue.c | 20 +++++++++++++++++--- video/filter/refqueue.h | 1 + video/filter/vf_d3d11vpp.c | 7 +++++++ video/filter/vf_vavpp.c | 8 ++++++++ video/filter/vf_vdpaupp.c | 10 ++++++++++ 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c index b37fd9d5f4c24..6fa38b96c2c48 100644 --- a/filters/f_auto_filters.c +++ b/filters/f_auto_filters.c @@ -88,12 +88,14 @@ static void deint_process(struct mp_filter *f) bool has_filter = true; if (img->imgfmt == IMGFMT_VDPAU) { - char *args[] = {"deint", "yes", NULL}; + char *args[] = {"deint", "yes", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args); } else if (img->imgfmt == IMGFMT_D3D11) { + char *args[] = {"parity", field_parity, NULL}; p->sub.filter = - mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", NULL); + mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", args); } else if (img->imgfmt == IMGFMT_CUDA) { char *args[] = {"mode", "send_field", "parity", field_parity, NULL}; @@ -106,7 +108,8 @@ static void deint_process(struct mp_filter *f) mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_vulkan", args); } else if (img->imgfmt == IMGFMT_VAAPI) { char *args[] = {"deint", "motion-adaptive", - "interlaced-only", "yes", NULL}; + "interlaced-only", "yes", + "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args); } else { diff --git a/video/filter/refqueue.c b/video/filter/refqueue.c index d018e38c00871..3cfe3c61ffa5c 100644 --- a/video/filter/refqueue.c +++ b/video/filter/refqueue.c @@ -39,6 +39,7 @@ struct mp_refqueue { int needed_past_frames; int needed_future_frames; int flags; + int field_parity; bool second_field; // current frame has to output a second field yet bool eof; @@ -97,6 +98,11 @@ void mp_refqueue_set_mode(struct mp_refqueue *q, int flags) q->flags = flags; } +void mp_refqueue_set_parity(struct mp_refqueue *q, int parity) +{ + q->field_parity = parity; +} + // Whether the current frame should be deinterlaced. bool mp_refqueue_should_deint(struct mp_refqueue *q) { @@ -113,8 +119,14 @@ bool mp_refqueue_is_top_field(struct mp_refqueue *q) { if (!mp_refqueue_has_output(q)) return false; - - return !!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field; + + bool tff = q->field_parity == MP_FIELD_PARITY_TFF; + bool bff = q->field_parity == MP_FIELD_PARITY_BFF; + bool ret = (!!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field + && !tff && !bff); // Default parity + ret = ret || (tff && !q->second_field); // Check if top field is forced + ret = ret || (bff && q->second_field); // Check if bottom field is forced + return ret; } // Whether top-field-first mode is enabled. @@ -123,7 +135,9 @@ bool mp_refqueue_top_field_first(struct mp_refqueue *q) if (!mp_refqueue_has_output(q)) return false; - return q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST; + bool tff = q->field_parity == MP_FIELD_PARITY_TFF; + bool bff = q->field_parity == MP_FIELD_PARITY_BFF; + return ((q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) || tff) && !bff; } // Discard all state. diff --git a/video/filter/refqueue.h b/video/filter/refqueue.h index 9d47ee87fbbf1..d14058d609570 100644 --- a/video/filter/refqueue.h +++ b/video/filter/refqueue.h @@ -34,6 +34,7 @@ enum { #define MP_FIELD_PARITY_BFF 1 void mp_refqueue_set_mode(struct mp_refqueue *q, int flags); +void mp_refqueue_set_parity(struct mp_refqueue *q, int parity); bool mp_refqueue_should_deint(struct mp_refqueue *q); bool mp_refqueue_is_top_field(struct mp_refqueue *q); bool mp_refqueue_top_field_first(struct mp_refqueue *q); diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c index d63acd11ce0a6..cedb91d8574c4 100644 --- a/video/filter/vf_d3d11vpp.c +++ b/video/filter/vf_d3d11vpp.c @@ -46,6 +46,7 @@ struct opts { bool deint_enabled; bool interlaced_only; int mode; + int field_parity; }; struct priv { @@ -469,6 +470,7 @@ static struct mp_filter *vf_d3d11vpp_create(struct mp_filter *parent, (p->opts->deint_enabled ? MP_MODE_DEINT : 0) | MP_MODE_OUTPUT_FIELDS | (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); return f; @@ -488,6 +490,10 @@ static const m_option_t vf_opts_fields[] = { {"mocomp", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_MOTION_COMPENSATION}, {"ivctc", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_INVERSE_TELECINE}, {"none", 0})}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -499,6 +505,7 @@ const struct mp_user_filter_entry vf_d3d11vpp = { .priv_defaults = &(const OPT_BASE_STRUCT) { .deint_enabled = true, .mode = D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB, + .field_parity = MP_FIELD_PARITY_AUTO, }, .options = vf_opts_fields, }, diff --git a/video/filter/vf_vavpp.c b/video/filter/vf_vavpp.c index f70fe1f4a6d33..960c74587e5f1 100644 --- a/video/filter/vf_vavpp.c +++ b/video/filter/vf_vavpp.c @@ -51,6 +51,7 @@ struct pipeline { struct opts { int deint_type; + int field_parity; bool interlaced_only; bool reversal_bug; }; @@ -143,11 +144,13 @@ static void update_pipeline(struct mp_filter *vf) (p->do_deint ? MP_MODE_DEINT : 0) | (p->opts->deint_type >= 2 ? MP_MODE_OUTPUT_FIELDS : 0) | (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); return; nodeint: mp_refqueue_set_refs(p->queue, 0, 0); mp_refqueue_set_mode(p->queue, 0); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); } static struct mp_image *alloc_out(struct mp_filter *vf) @@ -485,6 +488,10 @@ static const m_option_t vf_opts_fields[] = { {"motion-compensated", 5})}, {"interlaced-only", OPT_BOOL(interlaced_only)}, {"reversal-bug", OPT_BOOL(reversal_bug)}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -496,6 +503,7 @@ const struct mp_user_filter_entry vf_vavpp = { .priv_defaults = &(const OPT_BASE_STRUCT){ .deint_type = -1, .reversal_bug = true, + .field_parity = MP_FIELD_PARITY_AUTO, }, .options = vf_opts_fields, }, diff --git a/video/filter/vf_vdpaupp.c b/video/filter/vf_vdpaupp.c index b8a5d4194f75c..b5434cdfbc607 100644 --- a/video/filter/vf_vdpaupp.c +++ b/video/filter/vf_vdpaupp.c @@ -43,6 +43,7 @@ struct opts { bool deint_enabled; bool interlaced_only; + int field_parity; struct mp_vdpau_mixer_opts opts; }; @@ -156,6 +157,8 @@ static struct mp_filter *vf_vdpaupp_create(struct mp_filter *parent, void *optio (p->opts->interlaced_only ? MP_MODE_INTERLACED_ONLY : 0) | (p->opts->opts.deint >= 2 ? MP_MODE_OUTPUT_FIELDS : 0)); + mp_refqueue_set_parity(p->queue, p->opts->field_parity); + mp_refqueue_add_in_format(p->queue, IMGFMT_VDPAU, 0); return f; @@ -180,6 +183,10 @@ static const m_option_t vf_opts_fields[] = { {"sharpen", OPT_FLOAT(opts.sharpen), M_RANGE(-1, 1)}, {"hqscaling", OPT_INT(opts.hqscaling), M_RANGE(0, 9)}, {"interlaced-only", OPT_BOOL(interlaced_only)}, + {"parity", OPT_CHOICE(field_parity, + {"tff", MP_FIELD_PARITY_TFF}, + {"bff", MP_FIELD_PARITY_BFF}, + {"auto", MP_FIELD_PARITY_AUTO})}, {0} }; @@ -188,6 +195,9 @@ const struct mp_user_filter_entry vf_vdpaupp = { .description = "vdpau postprocessing", .name = "vdpaupp", .priv_size = sizeof(OPT_BASE_STRUCT), + .priv_defaults = &(const OPT_BASE_STRUCT){ + .field_parity = MP_FIELD_PARITY_AUTO, + }, .options = vf_opts_fields, }, .create = vf_vdpaupp_create, From 9c03b7569b73561d6679cbd2fd1fb8b556d02820 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:57:27 -0500 Subject: [PATCH 13/53] wayland_common: fix pointer serial conflict c2129c18f8ecac4133aec59526434ad10f7f6896 saves the button down serial to pointer_serial of the seat so that it can be used for window dragging later. However, this overwrites the serial saved at the enter event. Since the serial in wl_pointer_set_cursor must be the latest wl_pointer_enter serial number sent to the client, if a button down serial overwrites that, setting cursor no longer works until the cursor enters the window next time. Fix this by using separate serials for these two types of events. --- video/out/wayland_common.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 6fa5da1ca0449..f6c4741a3814a 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -197,7 +197,8 @@ struct vo_wayland_seat { struct wl_data_device *dnd_ddev; /* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */ void *cursor_shape_device; - uint32_t pointer_serial; + uint32_t pointer_enter_serial; + uint32_t pointer_button_serial; struct xkb_keymap *xkb_keymap; struct xkb_state *xkb_state; uint32_t keyboard_code; @@ -242,7 +243,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; - s->pointer_serial = serial; + s->pointer_enter_serial = serial; set_cursor_visibility(s, wl->cursor_visible); mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER); } @@ -317,7 +318,7 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, mp_input_put_key(wl->vo->input_ctx, button | MP_KEY_STATE_UP); } else if (state == MP_KEY_STATE_DOWN) { // Save the serial and seat for voctrl-initialized dragging requests. - s->pointer_serial = serial; + s->pointer_button_serial = serial; wl->last_button_seat = s; } else { wl->last_button_seat = NULL; @@ -441,7 +442,7 @@ static void touch_handle_down(void *data, struct wl_touch *wl_touch, mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); } else { // Save the serial and seat for voctrl-initialized dragging requests. - s->pointer_serial = serial; + s->pointer_button_serial = serial; wl->last_button_seat = s; } } @@ -1959,7 +1960,7 @@ static void set_content_type(struct vo_wayland_state *wl) static void set_cursor_shape(struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 - wp_cursor_shape_device_v1_set_shape(s->cursor_shape_device, s->pointer_serial, + wp_cursor_shape_device_v1_set_shape(s->cursor_shape_device, s->pointer_enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); #endif } @@ -1981,7 +1982,7 @@ static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) if (!buffer) return VO_FALSE; int scale = MPMAX(wl->scaling, 1); - wl_pointer_set_cursor(s->pointer, s->pointer_serial, wl->cursor_surface, + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, wl->cursor_surface, img->hotspot_x / scale, img->hotspot_y / scale); wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale), lround(img->height / scale)); @@ -1990,7 +1991,7 @@ static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) } wl_surface_commit(wl->cursor_surface); } else { - wl_pointer_set_cursor(s->pointer, s->pointer_serial, NULL, 0, 0); + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, NULL, 0, 0); } return VO_TRUE; } @@ -2220,7 +2221,7 @@ static void begin_dragging(struct vo_wayland_state *wl) if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && !wl->locked_size && s) { - xdg_toplevel_move(wl->xdg_toplevel, s->seat, s->pointer_serial); + xdg_toplevel_move(wl->xdg_toplevel, s->seat, s->pointer_button_serial); wl->last_button_seat = NULL; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); } From 83bad548d2a87fa77176b2a16fb0b257b1c113ec Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Mon, 4 Mar 2024 11:48:20 -0600 Subject: [PATCH 14/53] ad_spdif: handle deprecated FF_PROFILE_* definitions See: https://github.com/FFmpeg/FFmpeg/commit/8238bc0b5e3dba271217b1223a901b3f9713dc6e --- audio/decode/ad_spdif.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c index bb110cbed4814..f3dca7d1753ed 100644 --- a/audio/decode/ad_spdif.c +++ b/audio/decode/ad_spdif.c @@ -37,6 +37,12 @@ #define OUTBUF_SIZE 65536 +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 26, 100) +#define AV_PROFILE_UNKNOWN FF_PROFILE_UNKNOWN +#define AV_PROFILE_DTS_HD_HRA FF_PROFILE_DTS_HD_HRA +#define AV_PROFILE_DTS_HD_MA FF_PROFILE_DTS_HD_MA +#endif + struct spdifContext { struct mp_log *log; enum AVCodecID codec_id; @@ -90,7 +96,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt, int *out_profile, int *out_rate) { struct spdifContext *spdif_ctx = da->priv; - int profile = FF_PROFILE_UNKNOWN; + int profile = AV_PROFILE_UNKNOWN; AVCodecContext *ctx = NULL; AVFrame *frame = NULL; @@ -115,7 +121,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt, av_parser_close(parser); } - if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS) + if (profile != AV_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS) return; const AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id); @@ -145,7 +151,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt, av_frame_free(&frame); avcodec_free_context(&ctx); - if (profile == FF_PROFILE_UNKNOWN) + if (profile == AV_PROFILE_UNKNOWN) MP_WARN(da, "Failed to parse codec profile.\n"); } @@ -155,7 +161,7 @@ static int init_filter(struct mp_filter *da) AVPacket *pkt = spdif_ctx->avpkt; - int profile = FF_PROFILE_UNKNOWN; + int profile = AV_PROFILE_UNKNOWN; int c_rate = 0; determine_codec_params(da, pkt, &profile, &c_rate); MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate); @@ -208,9 +214,9 @@ static int init_filter(struct mp_filter *da) num_channels = 2; break; case AV_CODEC_ID_DTS: { - bool is_hd = profile == FF_PROFILE_DTS_HD_HRA || - profile == FF_PROFILE_DTS_HD_MA || - profile == FF_PROFILE_UNKNOWN; + bool is_hd = profile == AV_PROFILE_DTS_HD_HRA || + profile == AV_PROFILE_DTS_HD_MA || + profile == AV_PROFILE_UNKNOWN; // Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels) // for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit From 665a47209869d7a0c4ea860b28910fcd6ca874c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 25 Feb 2024 22:53:22 +0100 Subject: [PATCH 15/53] github/workflows: update actions/upload-artifact to v4 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58781439df77f..626bf1e7093bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: env: WINEDEBUG: '+loaddll' - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: mpv-${{ matrix.target }} path: mpv-git-*.zip From 6016423427ffdad29841093a588c57374c2ad108 Mon Sep 17 00:00:00 2001 From: der richter Date: Wed, 6 Mar 2024 21:24:13 +0100 Subject: [PATCH 16/53] mac/vulkan: workaround for MoltenVK problem that causes flicker MoltenVK itself tries to work around a supposedly Metal problem that itself causes flicker, black screens or broken rendering. it sets the drawableSize to 1x1 to forcefully complete the presentation. though if 1x1 resolution frame is presented it causes a visual flicker or rather a solid coloured frame. it causes even more problems since sometimes it does not reset the drawableSize to the proper resolution and keeps rendering everything in 1x1. work around this workaround by discarding drawableSize that are <=1 in any direction. Fixes #13505 --- video/out/mac/metal_layer.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/video/out/mac/metal_layer.swift b/video/out/mac/metal_layer.swift index 7cea87c0b4df8..9d2b5bd353d07 100644 --- a/video/out/mac/metal_layer.swift +++ b/video/out/mac/metal_layer.swift @@ -20,6 +20,17 @@ import Cocoa class MetalLayer: CAMetalLayer { unowned var common: MacCommon + // workaround for a MoltenVK workaround that sets the drawableSize to 1x1 to forcefully complete + // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 + override var drawableSize: CGSize { + get { return super.drawableSize } + set { + if Int(newValue.width) > 1 && Int(newValue.height) > 1 { + super.drawableSize = newValue + } + } + } + init(common com: MacCommon) { common = com super.init() From 68c61fd89f4c825ac559589458f406b4c92d04c6 Mon Sep 17 00:00:00 2001 From: der richter Date: Wed, 6 Mar 2024 22:02:43 +0100 Subject: [PATCH 17/53] mac/vulkan: directly retrieve current render size without caching the render size cached in ctx->vo->dwidth/dheight can be outdated in some circumstances at the time the context needs resizing. instead use the current render size. --- video/out/mac/common.swift | 2 +- video/out/mac/window.swift | 2 +- video/out/mac_common.swift | 13 +------------ video/out/vulkan/context_mac.m | 9 ++++++++- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift index 8e7aab75ec9e5..7b6fa48b5db64 100644 --- a/video/out/mac/common.swift +++ b/video/out/mac/common.swift @@ -23,7 +23,7 @@ class Common: NSObject { var log: LogHelper let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue") - var window: Window? + @objc var window: Window? var view: View? var titleBar: TitleBar? diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index 8a8c56c76a8a9..0829205da47e8 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -35,7 +35,7 @@ class Window: NSWindow, NSWindowDelegate { let animationLock: NSCondition = NSCondition() var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame ?? NSRect(x: 0, y: 0, width: 160, height: 90)) } } - var framePixel: NSRect { get { return convertToBacking(frame) } } + @objc var framePixel: NSRect { get { return convertToBacking(frame) } } var keepAspect: Bool = true { didSet { diff --git a/video/out/mac_common.swift b/video/out/mac_common.swift index 211c535811d6a..71659de8e578f 100644 --- a/video/out/mac_common.swift +++ b/video/out/mac_common.swift @@ -93,12 +93,6 @@ class MacCommon: Common { } } - func updateRenderSize(_ size: NSSize) { - mpv?.vo.pointee.dwidth = Int32(size.width) - mpv?.vo.pointee.dheight = Int32(size.height) - flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) - } - override func displayLinkCallback(_ displayLink: CVDisplayLink, _ inNow: UnsafePointer, _ inOutputTime: UnsafePointer, @@ -144,12 +138,7 @@ class MacCommon: Common { } override func windowDidResize() { - guard let window = window else { - log.sendWarning("No window available on window resize event") - return - } - - updateRenderSize(window.framePixel.size) + flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) } override func windowDidChangeScreenProfile() { diff --git a/video/out/vulkan/context_mac.m b/video/out/vulkan/context_mac.m index 38b34ff02944b..5621e6dca3c1f 100644 --- a/video/out/vulkan/context_mac.m +++ b/video/out/vulkan/context_mac.m @@ -85,7 +85,14 @@ static bool mac_vk_init(struct ra_ctx *ctx) static bool resize(struct ra_ctx *ctx) { - return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight); + struct priv *p = ctx->priv; + + if (!p->vo_mac.window) { + return false; + } + CGSize size = p->vo_mac.window.framePixel.size; + + return ra_vk_ctx_resize(ctx, (int)size.width, (int)size.height); } static bool mac_vk_reconfig(struct ra_ctx *ctx) From f532f3f38f1c430ae273d01acec8d2be2605b219 Mon Sep 17 00:00:00 2001 From: savoury1 <43487468+savoury1@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:35:35 -0800 Subject: [PATCH 18/53] DOCS/man/input.rst: fix incorrect syntax Some incorrect syntax is used in DOCS/man/input.rst with https://github.com/mpv-player/mpv/commit/c678033c1d60b48ae02fbbe4815869b9504a17f6 causing failure of PDF manual generation with rst2pdf. There are single rather than double back-ticks for insert-at and insert-at-play so this is fixed and a couple of typos are also corrected with this commit. --- DOCS/man/input.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index f3e222d8c149b..aa9f9af35aa1f 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -483,11 +483,11 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). is currently playing, start playback. (Always starts with the added file, even if the playlist was not empty before running this command.) - The third argument is an insertion index, used only by the `insert-at` and - `insert-at-play` actions. When used with those actions, the new item will be - insert at the position in the playlist, or appended to the end if - is less than 0 or greater than the size of the playlist. This - argument will be ignored for all other actions. + The third argument is an insertion index, used only by the ``insert-at`` and + ``insert-at-play`` actions. When used with those actions, the new item will + be inserted at the index position in the playlist, or appended to the end if + index is less than 0 or greater than the size of the playlist. This argument + will be ignored for all other actions. The fourth argument is a list of options and values which should be set while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``. @@ -524,11 +524,11 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). new playlist, even if the internal playlist was not empty before running this command.) - The third argument is an insertion index, used only by the `insert-at` and - `insert-at-play` actions. When used with those actions, the new playlist - will be insert at the position in the internal playlist, or appended - to the end if is less than 0 or greater than the size of the - internal playlist. This argument will be ignored for all other actions. + The third argument is an insertion index, used only by the ``insert-at`` and + ``insert-at-play`` actions. When used with those actions, the new playlist + will be inserted at the index position in the internal playlist, or appended + to the end if index is less than 0 or greater than the size of the internal + playlist. This argument will be ignored for all other actions. ``playlist-clear`` Clear the playlist, except the currently played file. From 4179d58389412a2664670543cc424e6b31734e57 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Wed, 6 Mar 2024 16:03:42 -0600 Subject: [PATCH 19/53] github/workflows: generate html and pdf in docs job as well Not sure if this is particularly useful, but we might as well generate these as well. Also use the docutils wrapper since that's what the meson build does. --- .github/workflows/docs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5c2317b8c1019..3936769854221 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,5 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: Docs run: | - sudo apt-get install python3-docutils - rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1 + sudo apt-get install python3-docutils rst2pdf + ./TOOLS/docutils-wrapper.py rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1 + ./TOOLS/docutils-wrapper.py rst2html --halt=2 ./DOCS/man/mpv.rst mpv.html + ./TOOLS/docutils-wrapper.py rst2pdf -c -b 1 --repeat-table-rows ./DOCS/man/mpv.rst -o mpv.pdf From bbbd6e272ce9bc7b87cff117d270728602df40b5 Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 01:42:01 +0100 Subject: [PATCH 20/53] mac/app: remove unused function --- osdep/mac/application.h | 1 - osdep/mac/application.m | 6 ------ 2 files changed, 7 deletions(-) diff --git a/osdep/mac/application.h b/osdep/mac/application.h index 9d86f9e3127fd..6b83abd248af5 100644 --- a/osdep/mac/application.h +++ b/osdep/mac/application.h @@ -47,7 +47,6 @@ struct macos_opts { // 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; diff --git a/osdep/mac/application.m b/osdep/mac/application.m index 5938180a8534a..0c62cf724bf93 100644 --- a/osdep/mac/application.m +++ b/osdep/mac/application.m @@ -284,12 +284,6 @@ static MP_THREAD_VOID playback_thread(void *ctx_obj) } } -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(); From fe35baa88e128f54d766fcb8b76857b036c91088 Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 02:49:11 +0100 Subject: [PATCH 21/53] mac/menu: rewrite menu bar in swift --- meson.build | 12 +- osdep/mac/application.h | 1 - osdep/mac/application_objc.h | 2 +- osdep/mac/menu_bar.swift | 464 +++++++++++++++++++ osdep/mac/menubar.h | 30 -- osdep/mac/menubar.m | 853 ----------------------------------- osdep/mac/menubar_objc.h | 25 - osdep/mac/meson.build | 4 + osdep/mac/swift_bridge.h | 3 + video/out/mac/window.swift | 10 +- 10 files changed, 483 insertions(+), 921 deletions(-) create mode 100644 osdep/mac/menu_bar.swift delete mode 100644 osdep/mac/menubar.h delete mode 100644 osdep/mac/menubar.m delete mode 100644 osdep/mac/menubar_objc.h diff --git a/meson.build b/meson.build index c58de3e175037..dd07caeb1ef6d 100644 --- a/meson.build +++ b/meson.build @@ -396,8 +396,7 @@ if features['cocoa'] 'osdep/path-mac.m', 'osdep/utils-mac.c', 'osdep/mac/application.m', - 'osdep/mac/events.m', - 'osdep/mac/menubar.m') + 'osdep/mac/events.m') endif if posix @@ -1516,6 +1515,7 @@ if features['cocoa'] and features['swift'] swift_sources += files('osdep/mac/libmpv_helper.swift', 'osdep/mac/log_helper.swift', 'osdep/mac/mpv_helper.swift', + 'osdep/mac/menu_bar.swift', 'osdep/mac/precise_timer.swift', 'osdep/mac/swift_compat.swift', 'osdep/mac/swift_extensions.swift', @@ -1549,10 +1549,6 @@ if features['macos-media-player'] swift_sources += files('osdep/mac/remote_command_center.swift') endif -if features['swift'] and swift_sources.length() > 0 - subdir('osdep/mac') -endif - macos_touchbar = get_option('macos-touchbar').require( features['cocoa'] and cc.has_header('AppKit/NSTouchBar.h'), error_message: 'Either cocoa could not be found or AppKit/NSTouchBar.h could not be found!', @@ -1562,6 +1558,10 @@ if features['macos-touchbar'] sources += files('osdep/mac/touchbar.m') endif +if features['swift'] and swift_sources.length() > 0 + subdir('osdep/mac') +endif + # manpages manpage = 'DOCS/man/mpv.rst' diff --git a/osdep/mac/application.h b/osdep/mac/application.h index 6b83abd248af5..06e5f1dcb53e2 100644 --- a/osdep/mac/application.h +++ b/osdep/mac/application.h @@ -18,7 +18,6 @@ #ifndef MAC_APPLICATION #define MAC_APPLICATION -#include "osdep/mac/menubar.h" #include "options/m_option.h" enum { diff --git a/osdep/mac/application_objc.h b/osdep/mac/application_objc.h index ad4f13ec5d0b3..6e492eef0e928 100644 --- a/osdep/mac/application_objc.h +++ b/osdep/mac/application_objc.h @@ -17,9 +17,9 @@ #import #include "osdep/mac/application.h" -#import "osdep/mac/menubar_objc.h" @class CocoaCB; +@class MenuBar; struct mpv_event; struct mpv_handle; diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift new file mode 100644 index 0000000000000..c5e39d86708d6 --- /dev/null +++ b/osdep/mac/menu_bar.swift @@ -0,0 +1,464 @@ +/* + * 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 . + */ + +extension MenuBar { + enum MenuKey { + case normalSize + case halfSize + case doubleSize + case minimize + case zoom + } + + struct Config { + let name: String + let key: String + let modifiers: NSEvent.ModifierFlags + let action: Selector? + let target: AnyObject? + let command: String + let url: String? + let file: String? + let alertTitle1: String? + let alertText1: String? + let alertTitle2: String? + let alertText2: String? + let alertTitle3: String? + let alertText3: String? + let commandSpecial: MenuKey? + var menuItem: NSMenuItem? + var configs: [Config]? + + init( + name: String = "", + key: String = "", + modifiers: NSEvent.ModifierFlags = .command, + action: Selector? = nil, + target: AnyObject? = nil, + command: String = "", + url: String? = nil, + file: String? = nil, + alertTitle1: String? = nil, + alertText1: String? = nil, + alertTitle2: String? = nil, + alertText2: String? = nil, + alertTitle3: String? = nil, + alertText3: String? = nil, + commandSpecial: MenuKey? = nil, + menuItem: NSMenuItem? = nil, + configs: [Config]? = nil + ) { + self.name = name + self.key = key + self.modifiers = modifiers + self.action = action + self.target = target + self.command = command + self.url = url + self.file = file + self.alertTitle1 = alertTitle1 + self.alertText1 = alertText1 + self.alertTitle2 = alertTitle2 + self.alertText2 = alertText2 + self.alertTitle3 = alertTitle3 + self.alertText3 = alertText3 + self.commandSpecial = commandSpecial + self.menuItem = menuItem + self.configs = configs + } + } +} + +class MenuBar: NSObject { + var menuConfigs: [Config] = [] + let appIcon: NSImage + + @objc override init() { + UserDefaults.standard.set(false, forKey: "NSFullScreenMenuItemEverywhere") + UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem") + UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem") + NSWindow.allowsAutomaticWindowTabbing = false + appIcon = (NSApp as? Application)?.getMPVIcon() ?? NSImage(size: NSSize(width: 1, height: 1)) + + super.init() + + let appMenuConfigs = [ + Config(name: "About mpv", action: #selector(about), target: self), + Config(name: "separator"), + Config( + name: "Preferences…", + key: ",", + action: #selector(preferences(_:)), + 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." + ), + Config( + name: "Keyboard Shortcuts Config…", + action: #selector(preferences(_:)), + 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." + ), + Config(name: "separator"), + Config(name: "Services"), + Config(name: "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(name: "separator"), + Config(name: "Quit and Remember Position", action: #selector(quit(_:)), target: self, command: "quit-watch-later"), + Config(name: "Quit mpv", key: "q", action: #selector(quit(_:)), target: self, command: "quit"), + ] + + let fileMenuConfigs = [ + Config(name: "Open File…", key: "o", action: #selector(openFile), target: self), + Config(name: "Open URL…", key: "O", action: #selector(openUrl), target: self), + Config(name: "Open Playlist…", action: #selector(openPlaylist), target: self), + Config(name: "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(name: "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(name: "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(name: "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(name: "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(name: "separator"), + Config(name: "Half Size", key: "0", commandSpecial: .halfSize), + Config(name: "Normal Size", key: "1", commandSpecial: .normalSize), + Config(name: "Double Size", key: "2", commandSpecial: .doubleSize), + ] + + 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(name: "separator"), + Config(name: "Toggle Mute", action: #selector(command(_:)), target: self, command: "cycle mute"), + Config(name: "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(name: "separator"), + Config(name: "Toggle Force Style", action: #selector(command(_:)), target: self, command: "cycle-values sub-ass-override \"force\" \"no\""), + Config(name: "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(name: "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(name: "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(name: "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(name: "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", commandSpecial: .minimize), + Config(name: "Zoom", key: "z", commandSpecial: .zoom), + ] + + let 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(name: "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(name: "separator"), + Config(name: "Report Issue…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/issues/new/choose"), + Config( + name: "Show log File…", + action: #selector(showFile(_:)), + target: self, + file: NSHomeDirectory() + "/Library/Logs/mpv.log", + alertTitle1: "No log File found.", + alertText1: "You deactivated logging for the Bundle." + ), + ] + + 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), + ] + + NSApp.mainMenu = generateMainMenu() + } + + func generateMainMenu() -> NSMenu { + let mainMenu = NSMenu(title: "MainMenu") + NSApp.servicesMenu = NSMenu() + + for (menuConfigIndex, menuConfig) in menuConfigs.enumerated() { + let menu = NSMenu(title: menuConfig.name) + let item = NSMenuItem(title: menuConfig.name, action: nil, keyEquivalent: menuConfig.key) + mainMenu.addItem(item) + mainMenu.setSubmenu(menu, for: item) + menuConfigs[menuConfigIndex].menuItem = item + + for (subConfigIndex, subConfig) in (menuConfig.configs ?? []).enumerated() { +#if HAVE_MACOS_TOUCHBAR + if subConfig.action == "toggleTouchBarCustomizationPalette:" { + continue + } +#endif + + if subConfig.name == "Show log File…" && ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { + continue + } + + if subConfig.name == "separator" { + menu.addItem(NSMenuItem.separator()) + } else { + let subItem = NSMenuItem(title: subConfig.name, action: subConfig.action, keyEquivalent: subConfig.key) + subItem.target = subConfig.target + subItem.keyEquivalentModifierMask = subConfig.modifiers + menu.addItem(subItem) + menuConfigs[menuConfigIndex].configs?[subConfigIndex].menuItem = subItem + + if subConfig.name == "Services" { + subItem.submenu = NSApp.servicesMenu + } + } + } + } + + return mainMenu + } + + @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 preferences(_ menuItem: NSMenuItem) { + guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + let configPaths: [String] = [ + NSHomeDirectory() + "/.mpv/", + NSHomeDirectory() + "/.config/mpv/", + ] + + for path in configPaths { + let configFile = path + (menuConfig.file ?? "") + + if FileManager.default.fileExists(atPath: configFile) { + if NSWorkspace.shared.openFile(configFile) { + return + } + NSWorkspace.shared.openFile(path) + alert(title: menuConfig.alertTitle1 ?? "", text: menuConfig.alertText1 ?? "") + return + } + + if NSWorkspace.shared.openFile(path) { + alert(title: menuConfig.alertTitle2 ?? "", text: menuConfig.alertText2 ?? "") + return + } + } + + alert(title: menuConfig.alertTitle3 ?? "", text: menuConfig.alertText3 ?? "") + } + + @objc func quit(_ menuItem: NSMenuItem) { + guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + menuConfig.command.withCString { + (NSApp as? Application)?.stopMPV(UnsafeMutablePointer(mutating: $0)) + } + } + + @objc func openFile() { + let panel = NSOpenPanel() + panel.allowsMultipleSelection = true + panel.canChooseDirectories = true + + if panel.runModal() == .OK { + var files: [String] = [] + for url in panel.urls { + files += [url.path] + } + (NSApp as? Application)?.openFiles(files) + } + } + + @objc func openPlaylist() { + let panel = NSOpenPanel() + + if panel.runModal() == .OK { + "loadlist \"\(panel.urls[0].path)\"".withCString { + (NSApp as? Application)?.queueCommand(UnsafeMutablePointer(mutating: $0)) + } + } + } + + @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 { + (NSApp as? Application)?.openFiles([input.stringValue]) + } + } + + @objc func command(_ menuItem: NSMenuItem) { + guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + menuConfig.command.withCString { + (NSApp as? Application)?.queueCommand(UnsafeMutablePointer(mutating: $0)) + } + } + + @objc func url(_ menuItem: NSMenuItem) { + guard let menuConfig = getConfigFromMenu(menuItem: menuItem), + let url = URL(string: menuConfig.url ?? "") else { return } + NSWorkspace.shared.open(url) + } + + @objc func showFile(_ menuItem: NSMenuItem) { + guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + let url = URL(fileURLWithPath: menuConfig.file ?? "") + if FileManager.default.fileExists(atPath: url.path) { + NSWorkspace.shared.activateFileViewerSelecting([url]) + return + } + + alert(title: menuConfig.alertTitle1 ?? "", text: menuConfig.alertText1 ?? "") + } + + 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 getConfigFromMenu(menuItem: NSMenuItem) -> Config? { + for menuConfig in menuConfigs { + for subConfig in menuConfig.configs ?? [] { + if subConfig.menuItem == menuItem { + return subConfig + } + } + } + + return nil + } + + func register(_ selector: Selector, key: MenuKey) { + for menuConfig in menuConfigs { + for subConfig in menuConfig.configs ?? [] { + if subConfig.commandSpecial == key { + subConfig.menuItem?.action = selector + return + } + } + } + } +} diff --git a/osdep/mac/menubar.h b/osdep/mac/menubar.h deleted file mode 100644 index 65e3dda34ae13..0000000000000 --- a/osdep/mac/menubar.h +++ /dev/null @@ -1,30 +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 . - */ - -#ifndef MAC_MENUBAR -#define MAC_MENUBAR - -// Menu Keys identifying menu items -typedef enum { - MPM_H_SIZE, - MPM_N_SIZE, - MPM_D_SIZE, - MPM_MINIMIZE, - MPM_ZOOM, -} MPMenuKey; - -#endif /* MAC_MENUBAR */ diff --git a/osdep/mac/menubar.m b/osdep/mac/menubar.m deleted file mode 100644 index 6a4ea96afeb58..0000000000000 --- a/osdep/mac/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 . - */ - -#include "config.h" -#include "common/common.h" - -#import "osdep/mac/menubar_objc.h" -#import "osdep/mac/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/mac/menubar_objc.h b/osdep/mac/menubar_objc.h deleted file mode 100644 index 6278916c0256d..0000000000000 --- a/osdep/mac/menubar_objc.h +++ /dev/null @@ -1,25 +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 . - */ - -#import -#include "osdep/mac/menubar.h" - -@interface MenuBar : NSObject - -- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key; - -@end diff --git a/osdep/mac/meson.build b/osdep/mac/meson.build index fca38e5282c40..faebf7e1d6832 100644 --- a/osdep/mac/meson.build +++ b/osdep/mac/meson.build @@ -19,6 +19,10 @@ if get_option('optimization') != '0' swift_flags += '-O' endif +if macos_touchbar.allowed() + swift_flags += ['-D', 'HAVE_MACOS_TOUCHBAR'] +endif + extra_flags = get_option('swift-flags').split() swift_flags += extra_flags diff --git a/osdep/mac/swift_bridge.h b/osdep/mac/swift_bridge.h index a32d046d9e34c..2c0c9a9225e95 100644 --- a/osdep/mac/swift_bridge.h +++ b/osdep/mac/swift_bridge.h @@ -49,6 +49,9 @@ 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; +static const char *swift_mpv_version = mpv_version; +static const char *swift_mpv_copyright = mpv_copyright; + // 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)); diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index 0829205da47e8..3084df2376dc2 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -105,11 +105,11 @@ class Window: NSWindow, NSWindowDelegate { unfScreen = screen if let app = NSApp as? Application { - app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE) - app.menuBar.register(#selector(setNormalWindowSize), for: MPM_N_SIZE) - app.menuBar.register(#selector(setDoubleWindowSize), for: MPM_D_SIZE) - app.menuBar.register(#selector(performMiniaturize(_:)), for: MPM_MINIMIZE) - app.menuBar.register(#selector(performZoom(_:)), for: MPM_ZOOM) + app.menuBar.register(#selector(setHalfWindowSize), key: .halfSize) + app.menuBar.register(#selector(setNormalWindowSize), key: .normalSize) + app.menuBar.register(#selector(setDoubleWindowSize), key: .doubleSize) + app.menuBar.register(#selector(performMiniaturize(_:)), key: .minimize) + app.menuBar.register(#selector(performZoom(_:)), key: .zoom) } } From 996ec6fecaedc96cc6bd6076c05f07628f82b4ad Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 14:40:40 +0100 Subject: [PATCH 22/53] mac/menu: remove unnecessary alert config fields and alert the config folder is created by mpv if it does not exist, so the last alert is unnecessary. also change config path priority. --- osdep/mac/menu_bar.swift | 53 ++++++++-------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index c5e39d86708d6..c2de6ac319ed6 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -33,12 +33,6 @@ extension MenuBar { let command: String let url: String? let file: String? - let alertTitle1: String? - let alertText1: String? - let alertTitle2: String? - let alertText2: String? - let alertTitle3: String? - let alertText3: String? let commandSpecial: MenuKey? var menuItem: NSMenuItem? var configs: [Config]? @@ -52,12 +46,6 @@ extension MenuBar { command: String = "", url: String? = nil, file: String? = nil, - alertTitle1: String? = nil, - alertText1: String? = nil, - alertTitle2: String? = nil, - alertText2: String? = nil, - alertTitle3: String? = nil, - alertText3: String? = nil, commandSpecial: MenuKey? = nil, menuItem: NSMenuItem? = nil, configs: [Config]? = nil @@ -70,12 +58,6 @@ extension MenuBar { self.command = command self.url = url self.file = file - self.alertTitle1 = alertTitle1 - self.alertText1 = alertText1 - self.alertTitle2 = alertTitle2 - self.alertText2 = alertText2 - self.alertTitle3 = alertTitle3 - self.alertText3 = alertText3 self.commandSpecial = commandSpecial self.menuItem = menuItem self.configs = configs @@ -104,25 +86,13 @@ class MenuBar: NSObject { key: ",", action: #selector(preferences(_:)), 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." + file: "mpv.conf" ), Config( name: "Keyboard Shortcuts Config…", action: #selector(preferences(_:)), 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." + file: "input.conf" ), Config(name: "separator"), Config(name: "Services"), @@ -255,9 +225,7 @@ class MenuBar: NSObject { name: "Show log File…", action: #selector(showFile(_:)), target: self, - file: NSHomeDirectory() + "/Library/Logs/mpv.log", - alertTitle1: "No log File found.", - alertText1: "You deactivated logging for the Bundle." + file: NSHomeDirectory() + "/Library/Logs/mpv.log" ), ] @@ -328,31 +296,30 @@ class MenuBar: NSObject { } @objc func preferences(_ menuItem: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + guard let menuConfig = getConfigFromMenu(menuItem: menuItem), + let fileName = menuConfig.file else { return } let configPaths: [String] = [ - NSHomeDirectory() + "/.mpv/", NSHomeDirectory() + "/.config/mpv/", + NSHomeDirectory() + "/.mpv/", ] for path in configPaths { - let configFile = path + (menuConfig.file ?? "") + let configFile = path + fileName if FileManager.default.fileExists(atPath: configFile) { if NSWorkspace.shared.openFile(configFile) { return } NSWorkspace.shared.openFile(path) - alert(title: menuConfig.alertTitle1 ?? "", text: menuConfig.alertText1 ?? "") + alert(title: "No Application found to open your config file.", text: "Please open the \(fileName) file with your preferred text editor in the now open folder to edit your config.") return } if NSWorkspace.shared.openFile(path) { - alert(title: menuConfig.alertTitle2 ?? "", text: menuConfig.alertText2 ?? "") + alert(title: "No config file found.", text: "Please create a \(fileName) file with your preferred text editor in the now open folder.") return } } - - alert(title: menuConfig.alertTitle3 ?? "", text: menuConfig.alertText3 ?? "") } @objc func quit(_ menuItem: NSMenuItem) { @@ -427,7 +394,7 @@ class MenuBar: NSObject { return } - alert(title: menuConfig.alertTitle1 ?? "", text: menuConfig.alertText1 ?? "") + alert(title: "No log File found.", text: "You deactivated logging for the Bundle.") } func alert(title: String, text: String) { From cb807ff063739fbbdc2a0f702916d16c9ca713e5 Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 15:30:20 +0100 Subject: [PATCH 23/53] mac/menu: replace deprecated openFile() usage --- osdep/mac/menu_bar.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index c2de6ac319ed6..d8158598a1799 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -298,24 +298,24 @@ class MenuBar: NSObject { @objc func preferences(_ menuItem: NSMenuItem) { guard let menuConfig = getConfigFromMenu(menuItem: menuItem), let fileName = menuConfig.file else { return } - let configPaths: [String] = [ - NSHomeDirectory() + "/.config/mpv/", - NSHomeDirectory() + "/.mpv/", + let configPaths: [URL] = [ + URL(fileURLWithPath: NSHomeDirectory() + "/.config/mpv/", isDirectory: true), + URL(fileURLWithPath: NSHomeDirectory() + "/.mpv/", isDirectory: true), ] for path in configPaths { - let configFile = path + fileName + let configFile = path.appendingPathComponent(fileName, isDirectory: false) - if FileManager.default.fileExists(atPath: configFile) { - if NSWorkspace.shared.openFile(configFile) { + if FileManager.default.fileExists(atPath: configFile.path) { + if NSWorkspace.shared.open(configFile) { return } - NSWorkspace.shared.openFile(path) + NSWorkspace.shared.open(path) alert(title: "No Application found to open your config file.", text: "Please open the \(fileName) file with your preferred text editor in the now open folder to edit your config.") return } - if NSWorkspace.shared.openFile(path) { + if NSWorkspace.shared.open(path) { alert(title: "No config file found.", text: "Please create a \(fileName) file with your preferred text editor in the now open folder.") return } From eadd85a6ac11dd824226a43765fad2cfb42b8cb3 Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 15:39:07 +0100 Subject: [PATCH 24/53] mac/menu: optimise loading files function don't save files in a temporary array and use an in place mapping. --- osdep/mac/menu_bar.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index d8158598a1799..c236995d54528 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -106,7 +106,7 @@ class MenuBar: NSObject { ] let fileMenuConfigs = [ - Config(name: "Open File…", key: "o", action: #selector(openFile), target: self), + 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(name: "separator"), @@ -329,17 +329,13 @@ class MenuBar: NSObject { } } - @objc func openFile() { + @objc func openFiles() { let panel = NSOpenPanel() panel.allowsMultipleSelection = true panel.canChooseDirectories = true if panel.runModal() == .OK { - var files: [String] = [] - for url in panel.urls { - files += [url.path] - } - (NSApp as? Application)?.openFiles(files) + (NSApp as? Application)?.openFiles(panel.urls.map { $0.path }) } } From ad0573ff8ed9fc36312fc0110cabc7a04a8f36da Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 15:43:15 +0100 Subject: [PATCH 25/53] mac/menu: remove duplicate key assignment Undo/Zoom this key is already assigned to Undo. --- osdep/mac/menu_bar.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index c236995d54528..e57ea62be817f 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -208,7 +208,7 @@ class MenuBar: NSObject { let windowMenuConfigs = [ Config(name: "Minimize", key: "m", commandSpecial: .minimize), - Config(name: "Zoom", key: "z", commandSpecial: .zoom), + Config(name: "Zoom", commandSpecial: .zoom), ] let helpMenuConfigs = [ From 035906dad52c47239cfcdc8f82e7fb56c9f41e3b Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 15:46:34 +0100 Subject: [PATCH 26/53] mac/menu: rename Preferences to Settings for consistency with macOS --- osdep/mac/menu_bar.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index e57ea62be817f..590c306ec434a 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -82,15 +82,15 @@ class MenuBar: NSObject { Config(name: "About mpv", action: #selector(about), target: self), Config(name: "separator"), Config( - name: "Preferences…", + name: "Settings…", key: ",", - action: #selector(preferences(_:)), + action: #selector(settings(_:)), target: self, file: "mpv.conf" ), Config( name: "Keyboard Shortcuts Config…", - action: #selector(preferences(_:)), + action: #selector(settings(_:)), target: self, file: "input.conf" ), @@ -295,7 +295,7 @@ class MenuBar: NSObject { ]) } - @objc func preferences(_ menuItem: NSMenuItem) { + @objc func settings(_ menuItem: NSMenuItem) { guard let menuConfig = getConfigFromMenu(menuItem: menuItem), let fileName = menuConfig.file else { return } let configPaths: [URL] = [ From 37990597c734910f170fc021bbadb78adb14fd1a Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 16:31:31 +0100 Subject: [PATCH 27/53] mac/menu: fix touch bar menu item the check broke when the runtime check was removed, eg the menu item was never added to the menu. since we only add the menu item to the config when touch bar support is available the check is completely unnecessary. --- osdep/mac/menu_bar.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 590c306ec434a..a15d6da713a8f 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -257,12 +257,6 @@ class MenuBar: NSObject { menuConfigs[menuConfigIndex].menuItem = item for (subConfigIndex, subConfig) in (menuConfig.configs ?? []).enumerated() { -#if HAVE_MACOS_TOUCHBAR - if subConfig.action == "toggleTouchBarCustomizationPalette:" { - continue - } -#endif - if subConfig.name == "Show log File…" && ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { continue } From 95d08df7a7ea9dde5f4f6ecf32da97d29d9a7fed Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 16:53:05 +0100 Subject: [PATCH 28/53] mac/menu: replace app.command usage with event.command --- osdep/mac/menu_bar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index a15d6da713a8f..6e322f346ab1a 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -338,7 +338,7 @@ class MenuBar: NSObject { if panel.runModal() == .OK { "loadlist \"\(panel.urls[0].path)\"".withCString { - (NSApp as? Application)?.queueCommand(UnsafeMutablePointer(mutating: $0)) + EventsResponder.sharedInstance().queueCommand(UnsafeMutablePointer(mutating: $0)) } } } @@ -366,7 +366,7 @@ class MenuBar: NSObject { @objc func command(_ menuItem: NSMenuItem) { guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } menuConfig.command.withCString { - (NSApp as? Application)?.queueCommand(UnsafeMutablePointer(mutating: $0)) + EventsResponder.sharedInstance().queueCommand(UnsafeMutablePointer(mutating: $0)) } } From 30eab4b9334720e0fccc44a486984b34fe06a3db Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 22:28:48 +0100 Subject: [PATCH 29/53] mac/menu: attach menu config to menu item for direct access add a config property to the menu items, so we don't need to search in the config array for the right config. --- osdep/mac/menu_bar.swift | 48 ++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 6e322f346ab1a..37e6a7783cb96 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -16,6 +16,10 @@ */ extension MenuBar { + class MenuItem: NSMenuItem { + var config: Config? + } + enum MenuKey { case normalSize case halfSize @@ -34,7 +38,7 @@ extension MenuBar { let url: String? let file: String? let commandSpecial: MenuKey? - var menuItem: NSMenuItem? + var menuItem: MenuItem? var configs: [Config]? init( @@ -47,7 +51,7 @@ extension MenuBar { url: String? = nil, file: String? = nil, commandSpecial: MenuKey? = nil, - menuItem: NSMenuItem? = nil, + menuItem: MenuItem? = nil, configs: [Config]? = nil ) { self.name = name @@ -251,7 +255,8 @@ class MenuBar: NSObject { for (menuConfigIndex, menuConfig) in menuConfigs.enumerated() { let menu = NSMenu(title: menuConfig.name) - let item = NSMenuItem(title: menuConfig.name, action: nil, keyEquivalent: menuConfig.key) + let item = MenuItem(title: menuConfig.name, action: nil, keyEquivalent: menuConfig.key) + item.config = menuConfig mainMenu.addItem(item) mainMenu.setSubmenu(menu, for: item) menuConfigs[menuConfigIndex].menuItem = item @@ -262,11 +267,12 @@ class MenuBar: NSObject { } if subConfig.name == "separator" { - menu.addItem(NSMenuItem.separator()) + menu.addItem(MenuItem.separator()) } else { - let subItem = NSMenuItem(title: subConfig.name, action: subConfig.action, keyEquivalent: subConfig.key) + let subItem = MenuItem(title: subConfig.name, action: subConfig.action, keyEquivalent: subConfig.key) subItem.target = subConfig.target subItem.keyEquivalentModifierMask = subConfig.modifiers + subItem.config = subConfig menu.addItem(subItem) menuConfigs[menuConfigIndex].configs?[subConfigIndex].menuItem = subItem @@ -289,8 +295,8 @@ class MenuBar: NSObject { ]) } - @objc func settings(_ menuItem: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem), + @objc func settings(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config, let fileName = menuConfig.file else { return } let configPaths: [URL] = [ URL(fileURLWithPath: NSHomeDirectory() + "/.config/mpv/", isDirectory: true), @@ -316,8 +322,8 @@ class MenuBar: NSObject { } } - @objc func quit(_ menuItem: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + @objc func quit(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } menuConfig.command.withCString { (NSApp as? Application)?.stopMPV(UnsafeMutablePointer(mutating: $0)) } @@ -363,21 +369,21 @@ class MenuBar: NSObject { } } - @objc func command(_ menuItem: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + @objc func command(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } menuConfig.command.withCString { EventsResponder.sharedInstance().queueCommand(UnsafeMutablePointer(mutating: $0)) } } - @objc func url(_ menuItem: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem), + @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: NSMenuItem) { - guard let menuConfig = getConfigFromMenu(menuItem: menuItem) else { return } + @objc func showFile(_ menuItem: MenuItem) { + guard let menuConfig = menuItem.config else { return } let url = URL(fileURLWithPath: menuConfig.file ?? "") if FileManager.default.fileExists(atPath: url.path) { NSWorkspace.shared.activateFileViewerSelecting([url]) @@ -396,18 +402,6 @@ class MenuBar: NSObject { alert.runModal() } - func getConfigFromMenu(menuItem: NSMenuItem) -> Config? { - for menuConfig in menuConfigs { - for subConfig in menuConfig.configs ?? [] { - if subConfig.menuItem == menuItem { - return subConfig - } - } - } - - return nil - } - func register(_ selector: Selector, key: MenuKey) { for menuConfig in menuConfigs { for subConfig in menuConfig.configs ?? [] { From 46a9e8c130117f1c6e0a4de831e82c6e7044b4f6 Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 22:53:00 +0100 Subject: [PATCH 30/53] mac/menu: properly guard playlist array access --- osdep/mac/menu_bar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 37e6a7783cb96..862b29e61cffc 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -342,8 +342,8 @@ class MenuBar: NSObject { @objc func openPlaylist() { let panel = NSOpenPanel() - if panel.runModal() == .OK { - "loadlist \"\(panel.urls[0].path)\"".withCString { + if panel.runModal() == .OK, let url = panel.urls.first { + "loadlist \"\(url.path)\"".withCString { EventsResponder.sharedInstance().queueCommand(UnsafeMutablePointer(mutating: $0)) } } From 8d4329a7101a8128edec62eede452c1aa61b37bf Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 3 Mar 2024 23:21:18 +0100 Subject: [PATCH 31/53] mac/menu: merge file and url config properties a file path is basically an URL. both are also handled as URL objects in our code. --- osdep/mac/menu_bar.swift | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 862b29e61cffc..2ba04b0f33f2e 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -35,8 +35,7 @@ extension MenuBar { let action: Selector? let target: AnyObject? let command: String - let url: String? - let file: String? + let url: String let commandSpecial: MenuKey? var menuItem: MenuItem? var configs: [Config]? @@ -48,8 +47,7 @@ extension MenuBar { action: Selector? = nil, target: AnyObject? = nil, command: String = "", - url: String? = nil, - file: String? = nil, + url: String = "", commandSpecial: MenuKey? = nil, menuItem: MenuItem? = nil, configs: [Config]? = nil @@ -61,7 +59,6 @@ extension MenuBar { self.target = target self.command = command self.url = url - self.file = file self.commandSpecial = commandSpecial self.menuItem = menuItem self.configs = configs @@ -90,13 +87,13 @@ class MenuBar: NSObject { key: ",", action: #selector(settings(_:)), target: self, - file: "mpv.conf" + url: "mpv.conf" ), Config( name: "Keyboard Shortcuts Config…", action: #selector(settings(_:)), target: self, - file: "input.conf" + url: "input.conf" ), Config(name: "separator"), Config(name: "Services"), @@ -229,7 +226,7 @@ class MenuBar: NSObject { name: "Show log File…", action: #selector(showFile(_:)), target: self, - file: NSHomeDirectory() + "/Library/Logs/mpv.log" + url: NSHomeDirectory() + "/Library/Logs/mpv.log" ), ] @@ -296,27 +293,26 @@ class MenuBar: NSObject { } @objc func settings(_ menuItem: MenuItem) { - guard let menuConfig = menuItem.config, - let fileName = menuConfig.file else { return } + 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(fileName, isDirectory: false) + 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 \(fileName) file with your preferred text editor in the now open folder to edit your config.") + 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 \(fileName) file with your preferred text editor in the now open folder.") + alert(title: "No config file found.", text: "Please create a \(menuConfig.url) file with your preferred text editor in the now open folder.") return } } @@ -378,13 +374,13 @@ class MenuBar: NSObject { @objc func url(_ menuItem: MenuItem) { guard let menuConfig = menuItem.config, - let url = URL(string: menuConfig.url ?? "") else { return } + 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.file ?? "") + let url = URL(fileURLWithPath: menuConfig.url) if FileManager.default.fileExists(atPath: url.path) { NSWorkspace.shared.activateFileViewerSelecting([url]) return From 73e494a237939bc71632834f2d237034a10a665d Mon Sep 17 00:00:00 2001 From: der richter Date: Mon, 4 Mar 2024 00:00:42 +0100 Subject: [PATCH 32/53] mac/menu: keep track of menu items with dynamic actions keeping track of the dynamic menu items lets us directly access them by key. we don't need to search in the config array for the right config and menu item. --- osdep/mac/menu_bar.swift | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 2ba04b0f33f2e..6fa64b43cab8c 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -37,7 +37,6 @@ extension MenuBar { let command: String let url: String let commandSpecial: MenuKey? - var menuItem: MenuItem? var configs: [Config]? init( @@ -49,7 +48,6 @@ extension MenuBar { command: String = "", url: String = "", commandSpecial: MenuKey? = nil, - menuItem: MenuItem? = nil, configs: [Config]? = nil ) { self.name = name @@ -60,7 +58,6 @@ extension MenuBar { self.command = command self.url = url self.commandSpecial = commandSpecial - self.menuItem = menuItem self.configs = configs } } @@ -68,6 +65,7 @@ extension MenuBar { class MenuBar: NSObject { var menuConfigs: [Config] = [] + var dynamicMenuItems: [MenuKey:[MenuItem]] = [:] let appIcon: NSImage @objc override init() { @@ -250,15 +248,14 @@ class MenuBar: NSObject { let mainMenu = NSMenu(title: "MainMenu") NSApp.servicesMenu = NSMenu() - for (menuConfigIndex, menuConfig) in menuConfigs.enumerated() { + for menuConfig in menuConfigs { let menu = NSMenu(title: menuConfig.name) let item = MenuItem(title: menuConfig.name, action: nil, keyEquivalent: menuConfig.key) item.config = menuConfig mainMenu.addItem(item) mainMenu.setSubmenu(menu, for: item) - menuConfigs[menuConfigIndex].menuItem = item - for (subConfigIndex, subConfig) in (menuConfig.configs ?? []).enumerated() { + for subConfig in menuConfig.configs ?? [] { if subConfig.name == "Show log File…" && ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { continue } @@ -271,11 +268,13 @@ class MenuBar: NSObject { subItem.keyEquivalentModifierMask = subConfig.modifiers subItem.config = subConfig menu.addItem(subItem) - menuConfigs[menuConfigIndex].configs?[subConfigIndex].menuItem = subItem if subConfig.name == "Services" { subItem.submenu = NSApp.servicesMenu } + if let cmd = subConfig.commandSpecial { + dynamicMenuItems[cmd] = (dynamicMenuItems[cmd] ?? []) + [subItem] + } } } } @@ -399,13 +398,8 @@ class MenuBar: NSObject { } func register(_ selector: Selector, key: MenuKey) { - for menuConfig in menuConfigs { - for subConfig in menuConfig.configs ?? [] { - if subConfig.commandSpecial == key { - subConfig.menuItem?.action = selector - return - } - } + for menuItem in dynamicMenuItems[key] ?? [] { + menuItem.action = selector } } } From 4eb58f6ea71ed9ee6454707d605cd0713c1baa4c Mon Sep 17 00:00:00 2001 From: der richter Date: Mon, 4 Mar 2024 00:29:07 +0100 Subject: [PATCH 33/53] mac/menu: move conditional Bundle menu items into config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit only add the "Show log File…" menu item config when invoked from the bundle, instead of testing on menu item creation. this is similar to the touch bar menu items now. --- osdep/mac/menu_bar.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 6fa64b43cab8c..75b1f35aba64c 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -210,7 +210,7 @@ class MenuBar: NSObject { Config(name: "Zoom", commandSpecial: .zoom), ] - let helpMenuConfigs = [ + 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(name: "separator"), @@ -220,13 +220,12 @@ class MenuBar: NSObject { Config(name: "Keyboard Shortcuts…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/blob/master/etc/input.conf"), Config(name: "separator"), Config(name: "Report Issue…", action: #selector(url(_:)), target: self, url: "https://github.com/mpv-player/mpv/issues/new/choose"), - Config( - name: "Show log File…", - action: #selector(showFile(_:)), - target: self, - url: NSHomeDirectory() + "/Library/Logs/mpv.log" - ), ] + 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), @@ -256,10 +255,6 @@ class MenuBar: NSObject { mainMenu.setSubmenu(menu, for: item) for subConfig in menuConfig.configs ?? [] { - if subConfig.name == "Show log File…" && ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { - continue - } - if subConfig.name == "separator" { menu.addItem(MenuItem.separator()) } else { From 16ea688115b83f83fe81834e290632468b7634c8 Mon Sep 17 00:00:00 2001 From: der richter Date: Mon, 4 Mar 2024 23:59:54 +0100 Subject: [PATCH 34/53] mac/menu: make menu creation recursive to allow nested submenus also makes menu creation cleaner and more obvious. --- osdep/mac/menu_bar.swift | 62 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index 75b1f35aba64c..e57d92eb19cad 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -64,6 +64,8 @@ extension MenuBar { } class MenuBar: NSObject { + let mainMenu = NSMenu(title: "Main") + let servicesMenu = NSMenu(title: "Services") var menuConfigs: [Config] = [] var dynamicMenuItems: [MenuKey:[MenuItem]] = [:] let appIcon: NSImage @@ -94,7 +96,7 @@ class MenuBar: NSObject { url: "input.conf" ), Config(name: "separator"), - Config(name: "Services"), + Config(name: "Services", configs: []), Config(name: "separator"), Config(name: "Hide mpv", key: "h", action: #selector(NSApp.hide(_:))), Config(name: "Hide Others", key: "h", modifiers: [.command, .option], action: #selector(NSApp.hideOtherApplications(_:))), @@ -240,41 +242,39 @@ class MenuBar: NSObject { Config(name: "Help", configs: helpMenuConfigs), ] - NSApp.mainMenu = generateMainMenu() + createMenu(parentMenu: mainMenu, configs: menuConfigs) + NSApp.mainMenu = mainMenu + NSApp.servicesMenu = servicesMenu } - func generateMainMenu() -> NSMenu { - let mainMenu = NSMenu(title: "MainMenu") - NSApp.servicesMenu = NSMenu() - - for menuConfig in menuConfigs { - let menu = NSMenu(title: menuConfig.name) - let item = MenuItem(title: menuConfig.name, action: nil, keyEquivalent: menuConfig.key) - item.config = menuConfig - mainMenu.addItem(item) - mainMenu.setSubmenu(menu, for: item) - - for subConfig in menuConfig.configs ?? [] { - if subConfig.name == "separator" { - menu.addItem(MenuItem.separator()) - } else { - let subItem = MenuItem(title: subConfig.name, action: subConfig.action, keyEquivalent: subConfig.key) - subItem.target = subConfig.target - subItem.keyEquivalentModifierMask = subConfig.modifiers - subItem.config = subConfig - menu.addItem(subItem) - - if subConfig.name == "Services" { - subItem.submenu = NSApp.servicesMenu - } - if let cmd = subConfig.commandSpecial { - dynamicMenuItems[cmd] = (dynamicMenuItems[cmd] ?? []) + [subItem] - } - } + func createMenu(parentMenu: NSMenu, configs: [Config]) { + for config in configs { + let item = createMenuItem(parentMenu: parentMenu, config: config) + + if config.configs != nil { + let menu = config.name == "Services" ? servicesMenu : NSMenu(title: config.name) + item.submenu = menu + createMenu(parentMenu: menu, configs: config.configs ?? []) + } + + if let cmd = config.commandSpecial { + dynamicMenuItems[cmd] = (dynamicMenuItems[cmd] ?? []) + [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.name == "separator" { + item = MenuItem.separator() as? MenuItem ?? item + } + parentMenu.addItem(item) - return mainMenu + return item } @objc func about() { From 8a37f0f693bc70252acbbe3a1e68ed4b11da197f Mon Sep 17 00:00:00 2001 From: der richter Date: Tue, 5 Mar 2024 01:18:32 +0100 Subject: [PATCH 35/53] mac/menu: add explicit menu type instead of an inferred type with the use of an explicit type we can removed inferred type checks like separators/services menu by name or menus by sub configs. --- osdep/mac/menu_bar.swift | 94 ++++++++++++++++++++------------------ video/out/mac/window.swift | 10 ++-- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/osdep/mac/menu_bar.swift b/osdep/mac/menu_bar.swift index e57d92eb19cad..2a906eb95ace8 100644 --- a/osdep/mac/menu_bar.swift +++ b/osdep/mac/menu_bar.swift @@ -20,44 +20,48 @@ extension MenuBar { var config: Config? } - enum MenuKey { - case normalSize - case halfSize - case doubleSize - case minimize - case zoom + 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 - let commandSpecial: MenuKey? - var configs: [Config]? + 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 = "", - commandSpecial: MenuKey? = nil, - configs: [Config]? = nil + 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.commandSpecial = commandSpecial self.configs = configs } } @@ -67,7 +71,7 @@ class MenuBar: NSObject { let mainMenu = NSMenu(title: "Main") let servicesMenu = NSMenu(title: "Services") var menuConfigs: [Config] = [] - var dynamicMenuItems: [MenuKey:[MenuItem]] = [:] + var dynamicMenuItems: [Type:[MenuItem]] = [:] let appIcon: NSImage @objc override init() { @@ -81,7 +85,7 @@ class MenuBar: NSObject { let appMenuConfigs = [ Config(name: "About mpv", action: #selector(about), target: self), - Config(name: "separator"), + Config(type: .separator), Config( name: "Settings…", key: ",", @@ -95,13 +99,13 @@ class MenuBar: NSObject { target: self, url: "input.conf" ), - Config(name: "separator"), - Config(name: "Services", configs: []), - Config(name: "separator"), + 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(name: "separator"), + Config(type: .separator), Config(name: "Quit and Remember Position", action: #selector(quit(_:)), target: self, command: "quit-watch-later"), Config(name: "Quit mpv", key: "q", action: #selector(quit(_:)), target: self, command: "quit"), ] @@ -110,7 +114,7 @@ class MenuBar: NSObject { 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(name: "separator"), + Config(type: .separator), Config(name: "Close", key: "w", action: #selector(NSWindow.performClose(_:))), Config(name: "Save Screenshot", action: #selector(command(_:)), target: self, command: "async screenshot"), ] @@ -118,7 +122,7 @@ class MenuBar: NSObject { let editMenuConfigs = [ Config(name: "Undo", key: "z", action: Selector(("undo:"))), Config(name: "Redo", key: "Z", action: Selector(("redo:"))), - Config(name: "separator"), + 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(_:))), @@ -137,7 +141,7 @@ class MenuBar: NSObject { ] #if HAVE_MACOS_TOUCHBAR viewMenuConfigs += [ - Config(name: "separator"), + Config(type: .separator), Config(name: "Customize Touch Bar…", action: #selector(NSApp.toggleTouchBarCustomizationPalette(_:))), ] #endif @@ -146,28 +150,28 @@ class MenuBar: NSObject { 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(name: "separator"), + 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(name: "separator"), + 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(name: "separator"), - Config(name: "Half Size", key: "0", commandSpecial: .halfSize), - Config(name: "Normal Size", key: "1", commandSpecial: .normalSize), - Config(name: "Double Size", key: "2", commandSpecial: .doubleSize), + 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(name: "separator"), + Config(type: .separator), Config(name: "Toggle Mute", action: #selector(command(_:)), target: self, command: "cycle mute"), - Config(name: "separator"), + 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"), @@ -176,9 +180,9 @@ class MenuBar: NSObject { 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(name: "separator"), + Config(type: .separator), Config(name: "Toggle Force Style", action: #selector(command(_:)), target: self, command: "cycle-values sub-ass-override \"force\" \"no\""), - Config(name: "separator"), + 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"), @@ -189,38 +193,38 @@ class MenuBar: NSObject { 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(name: "separator"), + 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(name: "separator"), + 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(name: "separator"), + 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(name: "separator"), + 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", commandSpecial: .minimize), - Config(name: "Zoom", commandSpecial: .zoom), + 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(name: "separator"), + 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(name: "separator"), + 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" { @@ -251,14 +255,14 @@ class MenuBar: NSObject { for config in configs { let item = createMenuItem(parentMenu: parentMenu, config: config) - if config.configs != nil { - let menu = config.name == "Services" ? servicesMenu : NSMenu(title: config.name) + if config.type <= .menuServices { + let menu = config.type == .menuServices ? servicesMenu : NSMenu(title: config.name) item.submenu = menu - createMenu(parentMenu: menu, configs: config.configs ?? []) + createMenu(parentMenu: menu, configs: config.configs) } - if let cmd = config.commandSpecial { - dynamicMenuItems[cmd] = (dynamicMenuItems[cmd] ?? []) + [item] + if config.type > Type.item { + dynamicMenuItems[config.type] = (dynamicMenuItems[config.type] ?? []) + [item] } } } @@ -269,7 +273,7 @@ class MenuBar: NSObject { item.target = config.target item.keyEquivalentModifierMask = config.modifiers - if config.name == "separator" { + if config.type == .separator { item = MenuItem.separator() as? MenuItem ?? item } parentMenu.addItem(item) @@ -392,7 +396,7 @@ class MenuBar: NSObject { alert.runModal() } - func register(_ selector: Selector, key: MenuKey) { + func register(_ selector: Selector, key: Type) { for menuItem in dynamicMenuItems[key] ?? [] { menuItem.action = selector } diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index 3084df2376dc2..1fbb2374ec276 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -105,11 +105,11 @@ class Window: NSWindow, NSWindowDelegate { unfScreen = screen if let app = NSApp as? Application { - app.menuBar.register(#selector(setHalfWindowSize), key: .halfSize) - app.menuBar.register(#selector(setNormalWindowSize), key: .normalSize) - app.menuBar.register(#selector(setDoubleWindowSize), key: .doubleSize) - app.menuBar.register(#selector(performMiniaturize(_:)), key: .minimize) - app.menuBar.register(#selector(performZoom(_:)), key: .zoom) + app.menuBar.register(#selector(setHalfWindowSize), key: .itemHalfSize) + app.menuBar.register(#selector(setNormalWindowSize), key: .itemNormalSize) + app.menuBar.register(#selector(setDoubleWindowSize), key: .itemDoubleSize) + app.menuBar.register(#selector(performMiniaturize(_:)), key: .itemMinimize) + app.menuBar.register(#selector(performZoom(_:)), key: .itemZoom) } } From a13a6743633d9e0eddb2ea7befbef89f1e19788a Mon Sep 17 00:00:00 2001 From: der richter Date: Sun, 25 Feb 2024 16:17:26 +0100 Subject: [PATCH 36/53] osxbundle: activate Game Mode with App bundle mpv isn't really a game and categorising our App bundle as game seems counterintuitive. though as a video player it does have some similarities, possibly needing considerably CPU and GPU resources and low presentation latencies. the macOS Game Mode promises exactly that, it gives App the highest priority access to CPU and GPU resources and lowers usage for background tasks. sadly the Game Mode can only be activated by categorising the App as a game and that is only possible with an App bundle and not as a command line tool. --- TOOLS/osxbundle/mpv.app/Contents/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TOOLS/osxbundle/mpv.app/Contents/Info.plist b/TOOLS/osxbundle/mpv.app/Contents/Info.plist index e239dc74f950b..eaa83ad0d8f0b 100644 --- a/TOOLS/osxbundle/mpv.app/Contents/Info.plist +++ b/TOOLS/osxbundle/mpv.app/Contents/Info.plist @@ -188,6 +188,8 @@ ${VERSION} NSHighResolutionCapable + LSApplicationCategoryType + public.app-category.games LSEnvironment MallocNanoZone From 0d33394955cb9d90905e8ade8776a3574d694544 Mon Sep 17 00:00:00 2001 From: owl0w1 <17188713+owl0w1@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:45:27 +0800 Subject: [PATCH 37/53] meson: remove main function from libmpv build Only include a main function in cplayer build, not libmpv build. This shaves off 40 bytes from libmpv build. --- meson.build | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index dd07caeb1ef6d..608cd10a09e2c 100644 --- a/meson.build +++ b/meson.build @@ -392,11 +392,11 @@ features += {'cocoa': cocoa.found()} if features['cocoa'] dependencies += cocoa sources += files('osdep/language-mac.c', - 'osdep/main-fn-mac.c', 'osdep/path-mac.m', 'osdep/utils-mac.c', 'osdep/mac/application.m', 'osdep/mac/events.m') + main_fn_source = files('osdep/main-fn-mac.c') endif if posix @@ -413,8 +413,8 @@ if posix endif if posix and not features['cocoa'] - sources += files('osdep/main-fn-unix.c', - 'osdep/language-posix.c') + sources += files('osdep/language-posix.c') + main_fn_source = files('osdep/main-fn-unix.c') endif if darwin @@ -502,11 +502,11 @@ if features['win32-desktop'] subprocess_source = files('osdep/subprocess-win.c') sources += files('input/ipc-win.c', 'osdep/language-win.c', - 'osdep/main-fn-win.c', 'osdep/terminal-win.c', 'video/out/w32_common.c', 'video/out/win32/displayconfig.c', 'video/out/win32/droptarget.c') + main_fn_source = files('osdep/main-fn-win.c') endif if not posix and not features['win32-desktop'] @@ -1747,7 +1747,7 @@ if get_option('cplayer') rename: 'mpv.svg') install_data('etc/mpv-symbolic.svg', install_dir: join_paths(hicolor_dir, 'symbolic', 'apps')) - mpv = executable('mpv', objects: libmpv.extract_all_objects(recursive: true), dependencies: dependencies, + mpv = executable('mpv', main_fn_source, objects: libmpv.extract_all_objects(recursive: true), dependencies: dependencies, win_subsystem: 'windows,6.0', install: true) # Older meson versions generate this in the player subdirectory. From d471f2902eb4f824fb5b62c3a30b26581d1e22b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Thu, 31 Aug 2023 16:52:28 +0200 Subject: [PATCH 38/53] mp_image: add imgfmt_name to mp_image_params Convenience to override name if imgfmt is not set. Allows to create mp_image_params without setting imgfmt. Will be useful for the next change where mp_imgfmt is not available. This is workaround that will be remved once all codebase switches to pl_fmt. --- player/command.c | 24 ++++++++++++++---------- video/mp_image.h | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/player/command.c b/player/command.c index 5b61ae3498797..75debf781d98d 100644 --- a/player/command.c +++ b/player/command.c @@ -2321,22 +2321,24 @@ static const char *get_aspect_ratio_name(double ratio) static int property_imgparams(const struct mp_image_params *p, int action, void *arg) { - if (!p->imgfmt) + if (!p->imgfmt && !p->imgfmt_name) return M_PROPERTY_UNAVAILABLE; int d_w, d_h; mp_image_params_get_dsize(p, &d_w, &d_h); - struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->imgfmt); int bpp = 0; - for (int i = 0; i < desc.num_planes; i++) - bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); - enum pl_alpha_mode alpha = p->repr.alpha; - // Alpha type is not supported by FFmpeg, so PL_ALPHA_UNKNOWN may mean alpha - // is of an unknown type, or simply not present. Normalize to AUTO=no alpha. - if (!!(desc.flags & MP_IMGFLAG_ALPHA) != (alpha != PL_ALPHA_UNKNOWN)) - alpha = (desc.flags & MP_IMGFLAG_ALPHA) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_UNKNOWN; + if (p->imgfmt) { + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->imgfmt); + for (int i = 0; i < desc.num_planes; i++) + bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); + + // Alpha type is not supported by FFmpeg, so PL_ALPHA_UNKNOWN may mean alpha + // is of an unknown type, or simply not present. Normalize to AUTO=no alpha. + if (!!(desc.flags & MP_IMGFLAG_ALPHA) != (alpha != PL_ALPHA_UNKNOWN)) + alpha = (desc.flags & MP_IMGFLAG_ALPHA) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_UNKNOWN; + } const struct pl_hdr_metadata *hdr = &p->color.hdr; bool has_cie_y = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_CIE_Y); @@ -2346,8 +2348,10 @@ static int property_imgparams(const struct mp_image_params *p, int action, void bool has_crop = mp_rect_w(p->crop) > 0 && mp_rect_h(p->crop) > 0; const char *aspect_name = get_aspect_ratio_name(d_w / (double)d_h); const char *sar_name = get_aspect_ratio_name(p->w / (double)p->h); + const char *pixelformat_name = p->imgfmt_name ? p->imgfmt_name : + mp_imgfmt_to_name(p->imgfmt); struct m_sub_property props[] = { - {"pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p->imgfmt))}, + {"pixelformat", SUB_PROP_STR(pixelformat_name)}, {"hw-pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p->hw_subfmt)), .unavailable = !p->hw_subfmt}, {"average-bpp", SUB_PROP_INT(bpp), diff --git a/video/mp_image.h b/video/mp_image.h index 149b63ead14ed..9658efd433f5f 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -43,6 +43,7 @@ // usually copy the whole struct, so that fields added later will be preserved. struct mp_image_params { enum mp_imgfmt imgfmt; // pixel format + const char *imgfmt_name; // pixel format name enum mp_imgfmt hw_subfmt; // underlying format for some hwaccel pixfmts int w, h; // image dimensions int p_w, p_h; // define pixel aspect ratio (undefined: 0/0) From a6f661b5c7620225a86b8d687433b229ce6f2132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Thu, 31 Aug 2023 18:56:02 +0200 Subject: [PATCH 39/53] player/command: fix video-params/[average-bpp, alpha] when hw decoding Need to check hw_subfmt for real values. --- player/command.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/player/command.c b/player/command.c index 75debf781d98d..29ece5b7fb42f 100644 --- a/player/command.c +++ b/player/command.c @@ -2329,8 +2329,9 @@ static int property_imgparams(const struct mp_image_params *p, int action, void int bpp = 0; enum pl_alpha_mode alpha = p->repr.alpha; - if (p->imgfmt) { - struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->imgfmt); + int fmt = p->hw_subfmt ? p->hw_subfmt : p->imgfmt; + if (fmt) { + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(fmt); for (int i = 0; i < desc.num_planes; i++) bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); From da3bfc96e90001175c59aeed442b1eff05bfc18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Thu, 31 Aug 2023 16:59:14 +0200 Subject: [PATCH 40/53] vo: add video-target-params property --- DOCS/interface-changes.rst | 1 + DOCS/man/input.rst | 5 +++++ player/command.c | 19 ++++++++++++++++++- video/out/vo.c | 14 ++++++++++++++ video/out/vo.h | 2 ++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 13f8cf20efbc0..146ed0f6b0df6 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -61,6 +61,7 @@ Interface changes - remove `--alpha` and reintroduce `--background` option for better control over blending alpha components into specific background types - add `--border-background` option + - add `video-target-params` property --- mpv 0.37.0 --- - `--save-position-on-quit` and its associated commands now store state files in %LOCALAPPDATA% instead of %APPDATA% directory by default on Windows. diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index aa9f9af35aa1f..1a829c0d5eec7 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -2647,6 +2647,11 @@ Property list Has the same sub-properties as ``video-params``. +``video-target-params`` + Same as ``video-params``, but with the target properties that VO outputs to. + + Has the same sub-properties as ``video-params``. + ``video-frame-info`` Approximate information of the current frame. Note that if any of these are used on OSD, the information might be off by a few frames due to OSD diff --git a/player/command.c b/player/command.c index 29ece5b7fb42f..2f479a5a147a5 100644 --- a/player/command.c +++ b/player/command.c @@ -2439,6 +2439,22 @@ static int mp_property_vo_imgparams(void *ctx, struct m_property *prop, return property_imgparams(&p, action, arg); } +static int mp_property_tgt_imgparams(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct vo *vo = mpctx->video_out; + if (!mpctx->video_out) + return M_PROPERTY_UNAVAILABLE; + + int valid = m_property_read_sub_validate(ctx, prop, action, arg); + if (valid != M_PROPERTY_VALID) + return valid; + + struct mp_image_params p = vo_get_target_params(vo); + return property_imgparams(&p, action, arg); +} + static int mp_property_dec_imgparams(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3965,6 +3981,7 @@ static const struct m_property mp_properties_base[] = { {"current-ao", mp_property_ao}, // Video + {"video-target-params", mp_property_tgt_imgparams}, {"video-out-params", mp_property_vo_imgparams}, {"video-dec-params", mp_property_dec_imgparams}, {"video-params", mp_property_vd_imgparams}, @@ -4107,7 +4124,7 @@ static const char *const *const mp_event_property_change[] = { "decoder-frame-drop-count", "frame-drop-count", "video-frame-info", "vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start", "secondary-sub-end", "video-out-params", "video-dec-params", "video-params", - "deinterlace-active"), + "deinterlace-active", "video-target-params"), E(MP_EVENT_DURATION_UPDATE, "duration"), E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params", "video-format", "video-codec", "video-bitrate", "dwidth", "dheight", diff --git a/video/out/vo.c b/video/out/vo.c index 08a0a9f88b125..319e2c07fbec4 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -614,6 +614,10 @@ static void run_reconfig(void *p) mp_mutex_unlock(&vo->params_mutex); } + mp_mutex_lock(&vo->params_mutex); + talloc_free(vo->target_params); + vo->target_params = NULL; + mp_mutex_unlock(&vo->params_mutex); mp_mutex_lock(&in->lock); talloc_free(in->current_frame); in->current_frame = NULL; @@ -1480,3 +1484,13 @@ struct mp_image_params vo_get_current_params(struct vo *vo) mp_mutex_unlock(&vo->params_mutex); return p; } + +struct mp_image_params vo_get_target_params(struct vo *vo) +{ + struct mp_image_params p = {0}; + mp_mutex_lock(&vo->params_mutex); + if (vo->target_params) + p = *vo->target_params; + mp_mutex_unlock(&vo->params_mutex); + return p; +} diff --git a/video/out/vo.h b/video/out/vo.h index 3879d024e7133..3deee0d3a71b8 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -481,6 +481,7 @@ struct vo { // generic getter is protected by params_mutex. mp_mutex params_mutex; struct mp_image_params *params; // Configured parameters (changed in vo_reconfig) + struct mp_image_params *target_params; // Target display parameters // --- The following fields can be accessed only by the VO thread, or from // anywhere _if_ the VO thread is suspended (use vo->dispatch). @@ -554,5 +555,6 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, struct vo_frame *vo_frame_ref(struct vo_frame *frame); struct mp_image_params vo_get_current_params(struct vo *vo); +struct mp_image_params vo_get_target_params(struct vo *vo); #endif /* MPLAYER_VIDEO_OUT_H */ From fc5d533a66df72b79e35aa35dfd028562601f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Thu, 31 Aug 2023 17:00:25 +0200 Subject: [PATCH 41/53] vo_gpu_next: add support for video-target-params --- video/out/vo_gpu_next.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 0e42e190858b4..55a6fd083ffc9 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -1114,8 +1114,20 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) goto done; } - const struct pl_frame *cur_frame = pl_frame_mix_nearest(&mix); mp_mutex_lock(&vo->params_mutex); + if (!vo->target_params) + vo->target_params = talloc(vo, struct mp_image_params); + *vo->target_params = (struct mp_image_params){ + .imgfmt_name = swframe.fbo->params.format + ? swframe.fbo->params.format->name : NULL, + .w = swframe.fbo->params.w, + .h = swframe.fbo->params.h, + .color = target.color, + .repr = target.repr, + .rotate = target.rotation, + }; + + const struct pl_frame *cur_frame = pl_frame_mix_nearest(&mix); if (cur_frame && vo->params) { vo->params->color.hdr = cur_frame->color.hdr; // Augment metadata with peak detection max_pq_y / avg_pq_y From 580bc69d0c4e99394a5490d060ebba9ab54ef056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Mon, 18 Sep 2023 03:16:24 +0200 Subject: [PATCH 42/53] vo_gpu_next: infer target parameters This allows us to read them back. --- video/out/vo_gpu_next.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 55a6fd083ffc9..8fb24c39b175f 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -1114,6 +1114,9 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) goto done; } + struct pl_frame ref_frame; + pl_frames_infer_mix(p->rr, &mix, &target, &ref_frame); + mp_mutex_lock(&vo->params_mutex); if (!vo->target_params) vo->target_params = talloc(vo, struct mp_image_params); @@ -1127,9 +1130,8 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) .rotate = target.rotation, }; - const struct pl_frame *cur_frame = pl_frame_mix_nearest(&mix); - if (cur_frame && vo->params) { - vo->params->color.hdr = cur_frame->color.hdr; + if (vo->params) { + vo->params->color.hdr = ref_frame.color.hdr; // Augment metadata with peak detection max_pq_y / avg_pq_y pl_renderer_get_hdr_metadata(p->rr, &vo->params->color.hdr); } From 68fbdc88d27c81e1bc6c7a68647fd8a970283423 Mon Sep 17 00:00:00 2001 From: David Knaack Date: Sat, 2 Mar 2024 14:03:48 +0100 Subject: [PATCH 43/53] osxbundle: avoid running `codesign` with deprecated `--deep` argument `--deep` is deprecated as of macos 13.0. It is also not supported by alternative `codesign` implementations like [sigtool](https://github.com/thefloweringash/sigtool). Related: https://github.com/NixOS/nixpkgs/pull/270691 --- TOOLS/osxbundle.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py index 1a9cfb4a6e2e8..fc8379c748401 100755 --- a/TOOLS/osxbundle.py +++ b/TOOLS/osxbundle.py @@ -40,7 +40,13 @@ def apply_plist_template(plist_file, version): print(line.rstrip().replace('${VERSION}', version)) def sign_bundle(binary_name): - sh('codesign --force --deep -s - ' + bundle_path(binary_name)) + sign_directories = ['Contents/Frameworks', 'Contents/MacOS'] + for dir in sign_directories: + resolved_dir = os.path.join(bundle_path(binary_name), dir) + for root, _dirs, files in os.walk(resolved_dir): + for f in files: + sh('codesign --force -s - ' + os.path.join(root, f)) + sh('codesign --force -s - ' + bundle_path(binary_name)) def bundle_version(src_path): version = 'UNKNOWN' From 62b1bad755bb6141c5a704741bda8a4da6dfcde5 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Thu, 7 Mar 2024 13:42:25 -0600 Subject: [PATCH 44/53] ad_spdif: handle const buf pointee in avio_alloc_context ffmpeg recently changed this field to be const which causes our CI to fail on newer versions. See: https://github.com/FFmpeg/FFmpeg/commit/2a68d945cd74265bb71c3d38b7a2e7f7d7e87be5 --- audio/decode/ad_spdif.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c index f3dca7d1753ed..ac6e0123cab4b 100644 --- a/audio/decode/ad_spdif.c +++ b/audio/decode/ad_spdif.c @@ -59,7 +59,11 @@ struct spdifContext { struct mp_decoder public; }; +#if LIBAVCODEC_VERSION_MAJOR < 61 static int write_packet(void *p, uint8_t *buf, int buf_size) +#else +static int write_packet(void *p, const uint8_t *buf, int buf_size) +#endif { struct spdifContext *ctx = p; From 78447c4b91634aa91dcace1cc6a9805fb93b9252 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Thu, 7 Mar 2024 14:12:15 -0600 Subject: [PATCH 45/53] filters/f_lavfi: handle removed AV_OPT_TYPE_CHANNEL_LAYOUT See: https://github.com/FFmpeg/FFmpeg/commit/65ddc74988245a01421a63c5cffa4d900c47117c --- filters/f_lavfi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c index dd8cd4826a2ab..de3cd52a2a08f 100644 --- a/filters/f_lavfi.c +++ b/filters/f_lavfi.c @@ -1034,7 +1034,11 @@ static const char *get_avopt_type_name(enum AVOptionType type) case AV_OPT_TYPE_VIDEO_RATE: return "fps"; case AV_OPT_TYPE_DURATION: return "duration"; case AV_OPT_TYPE_COLOR: return "color"; +#if LIBAVUTIL_VERSION_MAJOR < 59 case AV_OPT_TYPE_CHANNEL_LAYOUT: return "channellayout"; +#else + case AV_OPT_TYPE_CHLAYOUT: return "channellayout"; +#endif case AV_OPT_TYPE_BOOL: return "bool"; case AV_OPT_TYPE_CONST: // fallthrough default: From 9ef614d6a3b5e56474ec91626b06247ac60ed746 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Thu, 7 Mar 2024 18:12:38 -0600 Subject: [PATCH 46/53] swresample: stop using deprecated {in,out}_channel_layout options These options were deprecated with the addition of the channel layout API about a couple of years ago*. Unfortunately, we never saw the deprecation messages so it went unnoticed until they were completely removed with the recent major version bump. Fix this by setting in_chlayout and out_chlayout instead if we have AV_CHANNEL_LAYOUT. Fixes #13662. *: https://github.com/FFmpeg/FFmpeg/commit/8a5896ec1f635ccf0d726f7ba7a06649ebeebf25 --- filters/f_swresample.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/filters/f_swresample.c b/filters/f_swresample.c index 8cb687def0b5c..424a62bcf5757 100644 --- a/filters/f_swresample.c +++ b/filters/f_swresample.c @@ -23,6 +23,7 @@ #include #include "audio/aframe.h" +#include "audio/chmap_avchannel.h" #include "audio/fmt-conversion.h" #include "audio/format.h" #include "common/common.h" @@ -269,14 +270,28 @@ static bool configure_lavrr(struct priv *p, bool verbose) out_ch_layout = fudge_layout_conversion(p, in_ch_layout, out_ch_layout); +#if HAVE_AV_CHANNEL_LAYOUT // Real conversion; output is input to avrctx_out. + AVChannelLayout in_layout, out_layout; + mp_chmap_to_av_layout(&in_layout, &in_lavc); + mp_chmap_to_av_layout(&out_layout, &out_lavc); + av_opt_set_chlayout(p->avrctx, "in_chlayout", &in_layout, 0); + av_opt_set_chlayout(p->avrctx, "out_chlayout", &out_layout, 0); +#else av_opt_set_int(p->avrctx, "in_channel_layout", in_ch_layout, 0); av_opt_set_int(p->avrctx, "out_channel_layout", out_ch_layout, 0); +#endif av_opt_set_int(p->avrctx, "in_sample_rate", p->in_rate, 0); av_opt_set_int(p->avrctx, "out_sample_rate", p->out_rate, 0); av_opt_set_int(p->avrctx, "in_sample_fmt", in_samplefmt, 0); av_opt_set_int(p->avrctx, "out_sample_fmt", out_samplefmtp, 0); +#if HAVE_AV_CHANNEL_LAYOUT + AVChannelLayout fake_layout; + av_channel_layout_default(&fake_layout, map_out.num); + av_opt_set_chlayout(p->avrctx_out, "in_chlayout", &fake_layout, 0); + av_opt_set_chlayout(p->avrctx_out, "out_chlayout", &fake_layout, 0); +#else // Just needs the correct number of channels for deplanarization. struct mp_chmap fake_chmap; mp_chmap_set_unknown(&fake_chmap, map_out.num); @@ -285,6 +300,7 @@ static bool configure_lavrr(struct priv *p, bool verbose) goto error; av_opt_set_int(p->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0); av_opt_set_int(p->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0); +#endif av_opt_set_int(p->avrctx_out, "in_sample_fmt", out_samplefmtp, 0); av_opt_set_int(p->avrctx_out, "out_sample_fmt", out_samplefmt, 0); From 414ddbd628724df3afc1e15f5e415dbb2c76a0b5 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Fri, 8 Mar 2024 08:52:31 -0600 Subject: [PATCH 47/53] filters/f_lavfi: rename channellayout to ch_layout To better match upstream naming. --- filters/f_lavfi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c index de3cd52a2a08f..afc9f2d445cc5 100644 --- a/filters/f_lavfi.c +++ b/filters/f_lavfi.c @@ -1035,9 +1035,9 @@ static const char *get_avopt_type_name(enum AVOptionType type) case AV_OPT_TYPE_DURATION: return "duration"; case AV_OPT_TYPE_COLOR: return "color"; #if LIBAVUTIL_VERSION_MAJOR < 59 - case AV_OPT_TYPE_CHANNEL_LAYOUT: return "channellayout"; + case AV_OPT_TYPE_CHANNEL_LAYOUT: return "ch_layout"; #else - case AV_OPT_TYPE_CHLAYOUT: return "channellayout"; + case AV_OPT_TYPE_CHLAYOUT: return "ch_layout"; #endif case AV_OPT_TYPE_BOOL: return "bool"; case AV_OPT_TYPE_CONST: // fallthrough From 120b0ac4125859fc5bfb555e73ffd4f8905fe881 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Mon, 4 Mar 2024 23:06:52 -0600 Subject: [PATCH 48/53] wayland: always rescale geometry if in a fullscreen/maximized state This should only be a problem during initialization. If in a multi-monitor setup, mpv guesses the wrong scale value and the user passes --fs, the scaled size will be wrong and you have to unfullscreen and fullscreen again to fix it. This is because rescale geometry won't do anything if the value of hidpi-window-scale is false (the default) so the geometry is never rescaled to the correct value thus the wrong size. Normally, mpv will just correct itself after subsequent events occur but because it is considered a locked size (as it should be), we avoid doing any other resizing events thus it never gets corrected. Fix this by just always rescaling the geometry in the locked size case. It shouldn't matter elsewhere because mpv will always have the correct scale value and the possibility of having the wrong one is only possible on startup. Fixes ded181f642bc8915e3569d718d404e915cb62c27 --- video/out/wayland_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index f6c4741a3814a..6b6b3dc9b117f 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -1865,7 +1865,7 @@ static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode) static void rescale_geometry(struct vo_wayland_state *wl, double old_scale) { - if (!wl->vo_opts->hidpi_window_scale) + if (!wl->vo_opts->hidpi_window_scale && !wl->locked_size) return; double factor = old_scale / wl->scaling; From 391261f7576ff2abc738cf8d566bdc8aad267f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sat, 10 Feb 2024 23:46:04 +0100 Subject: [PATCH 49/53] mp_image: add mp_image_params_static_equal for finer comparision In case of dynamic HDR metadata is present. --- filters/f_autoconvert.c | 2 +- filters/f_decoder_wrapper.c | 21 ++++++++++++++------- filters/f_output_chain.c | 32 +++++++++++++++++--------------- player/video.c | 10 +--------- video/filter/refqueue.c | 2 +- video/filter/vf_gpu.c | 6 ++++-- video/mp_image.c | 11 +++++++++++ video/mp_image.h | 2 ++ video/out/gpu/video.c | 4 ++-- video/out/vo_gpu_next.c | 6 +++++- video/vdpau_mixer.c | 2 +- 11 files changed, 59 insertions(+), 39 deletions(-) diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c index dcd5ea24852b7..aaea7fe4f1eb3 100644 --- a/filters/f_autoconvert.c +++ b/filters/f_autoconvert.c @@ -157,7 +157,7 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, */ if (samefmt && samesubffmt) { if (p->imgparams_set) { - if (!mp_image_params_equal(&p->imgparams, &img->params)) + if (!mp_image_params_static_equal(&p->imgparams, &img->params)) break; } return true; diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c index e85463ce9061e..ba867621c5c6c 100644 --- a/filters/f_decoder_wrapper.c +++ b/filters/f_decoder_wrapper.c @@ -551,31 +551,36 @@ void mp_decoder_wrapper_set_play_dir(struct mp_decoder_wrapper *d, int dir) } static void fix_image_params(struct priv *p, - struct mp_image_params *params) + struct mp_image_params *params, + bool quiet) { struct mp_image_params m = *params; struct mp_codec_params *c = p->codec; struct dec_wrapper_opts *opts = p->opts; - MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params)); + if (!quiet) + MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params)); p->dec_format = *params; // While mp_image_params normally always have to have d_w/d_h set, the // decoder signals unknown bitstream aspect ratio with both set to 0. bool use_container = true; if (opts->aspect_method == 1 && m.p_w > 0 && m.p_h > 0) { - MP_VERBOSE(p, "Using bitstream aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Using bitstream aspect ratio.\n"); use_container = false; } if (use_container && c->par_w > 0 && c->par_h) { - MP_VERBOSE(p, "Using container aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Using container aspect ratio.\n"); m.p_w = c->par_w; m.p_h = c->par_h; } if (opts->movie_aspect >= 0) { - MP_VERBOSE(p, "Forcing user-set aspect ratio.\n"); + if (!quiet) + MP_VERBOSE(p, "Forcing user-set aspect ratio.\n"); if (opts->movie_aspect == 0) { m.p_w = m.p_h = 1; } else { @@ -819,8 +824,10 @@ static void process_output_frame(struct priv *p, struct mp_frame frame) correct_video_pts(p, mpi); - if (!mp_image_params_equal(&p->last_format, &mpi->params)) - fix_image_params(p, &mpi->params); + if (!mp_image_params_equal(&p->last_format, &mpi->params)) { + fix_image_params(p, &mpi->params, + mp_image_params_static_equal(&p->last_format, &mpi->params)); + } mpi->params = p->fixed_format; mpi->nominal_fps = p->fps; diff --git a/filters/f_output_chain.c b/filters/f_output_chain.c index ffad932a578a5..6c14ebc12baf0 100644 --- a/filters/f_output_chain.c +++ b/filters/f_output_chain.c @@ -100,27 +100,29 @@ static void check_in_format_change(struct mp_user_filter *u, struct mp_image *img = frame.data; if (!mp_image_params_equal(&img->params, &u->last_in_vformat)) { - MP_VERBOSE(p, "[%s] %s\n", u->name, - mp_image_params_to_str(&img->params)); - u->last_in_vformat = img->params; - if (u == p->input) { p->public.input_params = img->params; } else if (u == p->output) { p->public.output_params = img->params; } - // Unfortunately there's no good place to update these. - // But a common case is enabling HW decoding, which - // might init some support of them in the VO, and update - // the VO's format list. - // - // But as this is only relevant to the "convert" filter, don't - // do this for the other filters as it is wasted work. - if (strcmp(u->name, "convert") == 0) - update_output_caps(p); - - p->public.reconfig_happened = true; + if (!mp_image_params_static_equal(&img->params, &u->last_in_vformat)) { + MP_VERBOSE(p, "[%s] %s\n", u->name, + mp_image_params_to_str(&img->params)); + + // Unfortunately there's no good place to update these. + // But a common case is enabling HW decoding, which + // might init some support of them in the VO, and update + // the VO's format list. + // + // But as this is only relevant to the "convert" filter, don't + // do this for the other filters as it is wasted work. + if (strcmp(u->name, "convert") == 0) + update_output_caps(p); + + p->public.reconfig_happened = true; + } + u->last_in_vformat = img->params; } } diff --git a/player/video.c b/player/video.c index 908299baa10af..777460dceae2c 100644 --- a/player/video.c +++ b/player/video.c @@ -1043,14 +1043,6 @@ static void apply_video_crop(struct MPContext *mpctx, struct vo *vo) } } -static bool video_reconfig_needed(struct mp_image_params a, - struct mp_image_params b) -{ - a.color.hdr = (struct pl_hdr_metadata){0}; - b.color.hdr = (struct pl_hdr_metadata){0}; - return !mp_image_params_equal(&a, &b); -} - void write_video(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -1173,7 +1165,7 @@ void write_video(struct MPContext *mpctx) // Filter output is different from VO input? struct mp_image_params *p = &mpctx->next_frames[0]->params; - if (!vo->params || video_reconfig_needed(*p, *vo->params)) { + if (!vo->params || !mp_image_params_static_equal(p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. if (vo_still_displaying(vo)) { vo_request_wakeup_on_done(vo); diff --git a/video/filter/refqueue.c b/video/filter/refqueue.c index 3cfe3c61ffa5c..f5124087c6fdf 100644 --- a/video/filter/refqueue.c +++ b/video/filter/refqueue.c @@ -333,7 +333,7 @@ bool mp_refqueue_can_output(struct mp_refqueue *q) if (!q->in_format || !!q->in_format->hwctx != !!img->hwctx || (img->hwctx && img->hwctx->data != q->in_format->hwctx->data) || - !mp_image_params_equal(&q->in_format->params, &img->params)) + !mp_image_params_static_equal(&q->in_format->params, &img->params)) { q->next = img; q->eof = true; diff --git a/video/filter/vf_gpu.c b/video/filter/vf_gpu.c index dba4b3204ffe9..e19faaef42eec 100644 --- a/video/filter/vf_gpu.c +++ b/video/filter/vf_gpu.c @@ -166,12 +166,14 @@ static struct mp_image *gpu_render_frame(struct mp_filter *f, struct mp_image *i bool need_reconfig = m_config_cache_update(priv->vo_opts_cache); - if (!mp_image_params_equal(&priv->img_params, &in->params)) { - priv->img_params = in->params; + if (!mp_image_params_static_equal(&priv->img_params, &in->params)) { gl_video_config(priv->renderer, &in->params); need_reconfig = true; } + if (!mp_image_params_equal(&priv->img_params, &in->params)) + priv->img_params = in->params; + if (need_reconfig) { struct mp_rect src, dst; struct mp_osd_res osd; diff --git a/video/mp_image.c b/video/mp_image.c index f9decfa57911c..33a784741f6e7 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -844,6 +844,17 @@ bool mp_image_params_equal(const struct mp_image_params *p1, mp_rect_equals(&p1->crop, &p2->crop); } +bool mp_image_params_static_equal(const struct mp_image_params *p1, + const struct mp_image_params *p2) +{ + // Compare only static video parameters, excluding dynamic metadata. + struct mp_image_params a = *p1; + struct mp_image_params b = *p2; + a.repr.dovi = b.repr.dovi = NULL; + a.color.hdr = b.color.hdr = (struct pl_hdr_metadata){0}; + return mp_image_params_equal(&a, &b); +} + // Set most image parameters, but not image format or size. // Display size is used to set the PAR. void mp_image_set_attributes(struct mp_image *image, diff --git a/video/mp_image.h b/video/mp_image.h index 9658efd433f5f..3a6130a24d6fe 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -177,6 +177,8 @@ bool mp_image_crop_valid(const struct mp_image_params *p); bool mp_image_params_valid(const struct mp_image_params *p); bool mp_image_params_equal(const struct mp_image_params *p1, const struct mp_image_params *p2); +bool mp_image_params_static_equal(const struct mp_image_params *p1, + const struct mp_image_params *p2); void mp_image_params_get_dsize(const struct mp_image_params *p, int *d_w, int *d_h); diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 1de9ae8cd5dac..fe6ec1cdde564 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -3193,7 +3193,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, struct mp_image *f = t->frames[i]; uint64_t f_id = t->frame_id + i; - if (!mp_image_params_equal(&f->params, &p->real_image_params)) + if (!mp_image_params_static_equal(&f->params, &p->real_image_params)) continue; if (f_id > p->surfaces[p->surface_idx].id) { @@ -4016,7 +4016,7 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params) unmap_overlay(p); unref_current_image(p); - if (!mp_image_params_equal(&p->real_image_params, params)) { + if (!mp_image_params_static_equal(&p->real_image_params, params)) { uninit_video(p); p->real_image_params = *params; p->image_params = *params; diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 8fb24c39b175f..0139cfd3e1eae 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -494,7 +494,11 @@ static bool hwdec_reconfig(struct priv *p, struct ra_hwdec *hwdec, const struct mp_image_params *par) { if (p->hwdec_mapper) { - if (mp_image_params_equal(par, &p->hwdec_mapper->src_params)) { + if (mp_image_params_static_equal(par, &p->hwdec_mapper->src_params)) { + p->hwdec_mapper->src_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->dst_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->src_params.color.hdr = par->color.hdr; + p->hwdec_mapper->dst_params.color.hdr = par->color.hdr; return p->hwdec_mapper; } else { ra_hwdec_mapper_free(&p->hwdec_mapper); diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c index 5adb3b4ec3c36..e062dcc06c1f5 100644 --- a/video/vdpau_mixer.c +++ b/video/vdpau_mixer.c @@ -277,7 +277,7 @@ int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer, CHECK_VDP_ERROR(mixer, "Error when calling vdp_video_surface_get_parameters"); if (!mixer->initialized || !opts_equal(opts, &mixer->opts) || - !mp_image_params_equal(&video->params, &mixer->image_params) || + !mp_image_params_static_equal(&video->params, &mixer->image_params) || mixer->current_w != s_w || mixer->current_h != s_h || mixer->current_chroma_type != s_chroma_type) { From 05c8d5a93a743cb735ca751c2a6343dba4dcf511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 11 Feb 2024 00:51:58 +0100 Subject: [PATCH 50/53] csputils: add missing PL_COLOR_SYSTEM names --- DOCS/man/vf.rst | 11 +++++++---- video/csputils.c | 9 +++++++++ video/mp_image.c | 3 +++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 1423e4c3923a9..1d067e31f4874 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -204,10 +204,13 @@ Available mpv-only filters are: Available color spaces are: :auto: automatic selection (default) - :bt.601: ITU-R BT.601 (SD) - :bt.709: ITU-R BT.709 (HD) - :bt.2020-ncl: ITU-R BT.2020 non-constant luminance system - :bt.2020-cl: ITU-R BT.2020 constant luminance system + :bt.601: ITU-R Rec. BT.601 (SD) + :bt.709: ITU-R Rec. BT.709 (HD) + :bt.2020-ncl: ITU-R Rec. BT.2020 (non-constant luminance) + :bt.2020-cl: ITU-R Rec. BT.2020 (constant luminance) + :bt.2100-pq: ITU-R Rec. BT.2100 ICtCp PQ variant + :bt.2100-hlg: ITU-R Rec. BT.2100 ICtCp HLG variant + :dolbyvision: Dolby Vision :smpte-240m: SMPTE-240M ```` diff --git a/video/csputils.c b/video/csputils.c index 0587c57f4ecbf..555f0c045e262 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -37,6 +37,9 @@ const struct m_opt_choice_alternatives pl_csp_names[] = { {"smpte-240m", PL_COLOR_SYSTEM_SMPTE_240M}, {"bt.2020-ncl", PL_COLOR_SYSTEM_BT_2020_NC}, {"bt.2020-cl", PL_COLOR_SYSTEM_BT_2020_C}, + {"bt.2100-pq", PL_COLOR_SYSTEM_BT_2100_PQ}, + {"bt.2100-hlg", PL_COLOR_SYSTEM_BT_2100_HLG}, + {"dolbyvision", PL_COLOR_SYSTEM_DOLBYVISION}, {"rgb", PL_COLOR_SYSTEM_RGB}, {"xyz", PL_COLOR_SYSTEM_XYZ}, {"ycgco", PL_COLOR_SYSTEM_YCGCO}, @@ -315,6 +318,12 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *m) enum pl_color_system colorspace = params->repr.sys; if (colorspace <= PL_COLOR_SYSTEM_UNKNOWN || colorspace >= PL_COLOR_SYSTEM_COUNT) colorspace = PL_COLOR_SYSTEM_BT_601; + // Not supported. TODO: replace with pl_color_repr_decode + if (colorspace == PL_COLOR_SYSTEM_BT_2100_PQ || + colorspace == PL_COLOR_SYSTEM_BT_2100_HLG || + colorspace == PL_COLOR_SYSTEM_DOLBYVISION) { + colorspace = PL_COLOR_SYSTEM_BT_2020_NC; + } enum pl_color_levels levels_in = params->repr.levels; if (levels_in <= PL_COLOR_LEVELS_UNKNOWN || levels_in >= PL_COLOR_LEVELS_COUNT) levels_in = PL_COLOR_LEVELS_LIMITED; diff --git a/video/mp_image.c b/video/mp_image.c index 33a784741f6e7..23b1ab8af0c5f 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -910,6 +910,9 @@ void mp_image_params_guess_csp(struct mp_image_params *params) params->repr.sys != PL_COLOR_SYSTEM_BT_709 && params->repr.sys != PL_COLOR_SYSTEM_BT_2020_NC && params->repr.sys != PL_COLOR_SYSTEM_BT_2020_C && + params->repr.sys != PL_COLOR_SYSTEM_BT_2100_PQ && + params->repr.sys != PL_COLOR_SYSTEM_BT_2100_HLG && + params->repr.sys != PL_COLOR_SYSTEM_DOLBYVISION && params->repr.sys != PL_COLOR_SYSTEM_SMPTE_240M && params->repr.sys != PL_COLOR_SYSTEM_YCGCO) { From d9c1e9bc5c8ca5e4a75b197789b77890375cb8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 11 Feb 2024 00:52:49 +0100 Subject: [PATCH 51/53] mp_image: add Dolby Vision metadata mapping Remove side-loading metadata in vo_gpu_next.c and remove unneded side-data duplication. --- video/filter/vf_format.c | 23 +++++++++++++++++++++-- video/mp_image.c | 37 +++++++++++++++++++++++++++++-------- video/mp_image.h | 2 -- video/out/placebo/utils.c | 24 ------------------------ video/out/placebo/utils.h | 3 --- video/out/vo_gpu_next.c | 3 --- 6 files changed, 50 insertions(+), 42 deletions(-) diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index 2063045864151..4a2916864c3d9 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -23,6 +23,8 @@ #include #include +#include +#include #include "common/msg.h" #include "common/common.h" @@ -109,6 +111,16 @@ static void set_params(struct vf_format_opts *p, struct mp_image_params *out, mp_image_params_set_dsize(out, dsize.num, dsize.den); } +static inline void *get_side_data(const struct mp_image *mpi, + enum AVFrameSideDataType type) +{ + for (int i = 0; i < mpi->num_ff_side_data; i++) { + if (mpi->ff_side_data[i].type == type) + return (void *)mpi->ff_side_data[i].buf->data; + } + return NULL; +} + static void vf_format_process(struct mp_filter *f) { struct priv *priv = f->priv; @@ -155,8 +167,15 @@ static void vf_format_process(struct mp_filter *f) } if (!priv->opts->dovi) { - av_buffer_unref(&img->dovi); - av_buffer_unref(&img->dovi_buf); + if (img->params.repr.sys == PL_COLOR_SYSTEM_DOLBYVISION) + img->params.repr.sys = PL_COLOR_SYSTEM_BT_2020_NC; + // Map again to strip any DV metadata set to common fields. + img->params.color.hdr = (struct pl_hdr_metadata){0}; + pl_map_hdr_metadata(&img->params.color.hdr, &(struct pl_av_hdr_metadata) { + .mdm = get_side_data(img, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA), + .clm = get_side_data(img, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL), + .dhp = get_side_data(img, AV_FRAME_DATA_DYNAMIC_HDR_PLUS), + }); } if (!priv->opts->film_grain) diff --git a/video/mp_image.c b/video/mp_image.c index 23b1ab8af0c5f..a8c0262aa75a2 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -209,7 +209,6 @@ static void mp_image_destructor(void *ptr) av_buffer_unref(&mpi->a53_cc); av_buffer_unref(&mpi->dovi); av_buffer_unref(&mpi->film_grain); - av_buffer_unref(&mpi->dovi_buf); for (int n = 0; n < mpi->num_ff_side_data; n++) av_buffer_unref(&mpi->ff_side_data[n].buf); talloc_free(mpi->ff_side_data); @@ -344,7 +343,6 @@ struct mp_image *mp_image_new_ref(struct mp_image *img) ref_buffer(&new->a53_cc); ref_buffer(&new->dovi); ref_buffer(&new->film_grain); - ref_buffer(&new->dovi_buf); new->ff_side_data = talloc_memdup(NULL, new->ff_side_data, new->num_ff_side_data * sizeof(new->ff_side_data[0])); @@ -382,7 +380,6 @@ struct mp_image *mp_image_new_dummy_ref(struct mp_image *img) new->a53_cc = NULL; new->dovi = NULL; new->film_grain = NULL; - new->dovi_buf = NULL; new->num_ff_side_data = 0; new->ff_side_data = NULL; return new; @@ -542,7 +539,6 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src) } assign_bufref(&dst->icc_profile, src->icc_profile); assign_bufref(&dst->dovi, src->dovi); - assign_bufref(&dst->dovi_buf, src->dovi_buf); assign_bufref(&dst->film_grain, src->film_grain); assign_bufref(&dst->a53_cc, src->a53_cc); @@ -1093,14 +1089,38 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) if (sd) dst->a53_cc = sd->buf; + AVBufferRef *dovi = NULL; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 16, 100) sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_METADATA); - if (sd) - dst->dovi = sd->buf; + if (sd) { +#ifdef PL_HAVE_LAV_DOLBY_VISION + const AVDOVIMetadata *metadata = (const AVDOVIMetadata *)sd->buf->data; + const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata); + if (header->disable_residual_flag) { + dst->dovi = dovi = av_buffer_alloc(sizeof(struct pl_dovi_metadata)); + MP_HANDLE_OOM(dovi); +#if PL_API_VER >= 343 + pl_map_avdovi_metadata(&dst->params.color, &dst->params.repr, + (void *)dst->dovi->data, metadata); +#else + struct pl_frame frame; + frame.repr = dst->params.repr; + frame.color = dst->params.color; + pl_frame_map_avdovi_metadata(&frame, (void *)dst->dovi->data, metadata); + dst->params.repr = frame.repr; + dst->params.color = frame.color; +#endif + } +#endif + } sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_RPU_BUFFER); - if (sd) - dst->dovi_buf = sd->buf; + if (sd) { +#ifdef PL_HAVE_LIBDOVI + pl_hdr_metadata_from_dovi_rpu(&dst->params.color.hdr, sd->buf->data, + sd->buf->size); +#endif + } #endif sd = av_frame_get_side_data(src, AV_FRAME_DATA_FILM_GRAIN_PARAMS); @@ -1125,6 +1145,7 @@ struct mp_image *mp_image_from_av_frame(struct AVFrame *src) // Allocated, but non-refcounted data. talloc_free(dst->ff_side_data); + av_buffer_unref(&dovi); return res; } diff --git a/video/mp_image.h b/video/mp_image.h index 3a6130a24d6fe..af0d9fd58dfa5 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -116,8 +116,6 @@ typedef struct mp_image { struct AVBufferRef *dovi; // Film grain data, if any struct AVBufferRef *film_grain; - // Dolby Vision RPU buffer, if any - struct AVBufferRef *dovi_buf; // Other side data we don't care about. struct mp_ff_side_data *ff_side_data; int num_ff_side_data; diff --git a/video/out/placebo/utils.c b/video/out/placebo/utils.c index e01c37103911f..86b0bfd38d146 100644 --- a/video/out/placebo/utils.c +++ b/video/out/placebo/utils.c @@ -71,27 +71,3 @@ void mppl_log_set_probing(pl_log log, bool probing) params.log_cb = probing ? log_cb_probing : log_cb; pl_log_update(log, ¶ms); } - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame) -{ -#ifdef PL_HAVE_LAV_DOLBY_VISION - if (mpi->dovi) { - const AVDOVIMetadata *metadata = (AVDOVIMetadata *) mpi->dovi->data; - const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata); - - if (header->disable_residual_flag) { - // Only automatically map DoVi RPUs that don't require an EL - struct pl_dovi_metadata *dovi = talloc_ptrtype(mpi, dovi); - pl_frame_map_avdovi_metadata(frame, dovi, metadata); - } - } - -#if defined(PL_HAVE_LIBDOVI) - if (mpi->dovi_buf) - pl_hdr_metadata_from_dovi_rpu(&frame->color.hdr, mpi->dovi_buf->data, - mpi->dovi_buf->size); -#endif - -#endif // PL_HAVE_LAV_DOLBY_VISION -} diff --git a/video/out/placebo/utils.h b/video/out/placebo/utils.h index d2fa619b4a918..3f61d8b5046a5 100644 --- a/video/out/placebo/utils.h +++ b/video/out/placebo/utils.h @@ -26,6 +26,3 @@ static inline struct pl_rect2d mp_rect2d_to_pl(struct mp_rect rc) .y1 = rc.y1, }; } - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame); diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 0139cfd3e1eae..84facb9e445bb 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -705,9 +705,6 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src // Update chroma location, must be done after initializing planes pl_frame_set_chroma_location(frame, par->chroma_location); - // Set the frame DOVI metadata - mp_map_dovi_metadata_to_pl(mpi, frame); - if (mpi->film_grain) pl_film_grain_from_av(&frame->film_grain, (AVFilmGrainParams *) mpi->film_grain->data); From 024edb29913cbef676ebdc234afe91193849de20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 11 Feb 2024 00:50:02 +0100 Subject: [PATCH 52/53] vf_format: add hdr10plus sub-parameter to format video filter --- DOCS/interface-changes.rst | 1 + DOCS/man/vf.rst | 4 ++++ video/filter/vf_format.c | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 146ed0f6b0df6..14557de1b5ab3 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -62,6 +62,7 @@ Interface changes over blending alpha components into specific background types - add `--border-background` option - add `video-target-params` property + - add `hdr10plus` sub-parameter to `format` video filter --- mpv 0.37.0 --- - `--save-position-on-quit` and its associated commands now store state files in %LOCALAPPDATA% instead of %APPDATA% directory by default on Windows. diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 1d067e31f4874..59175e79eff6a 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -316,6 +316,10 @@ Available mpv-only filters are: Whether or not to include Dolby Vision metadata (default: yes). If disabled, any Dolby Vision metadata will be stripped from frames. + ```` + Whether or not to include HDR10+ metadata (default: yes). If + disabled, any HDR10+ metadata will be stripped from frames. + ```` Whether or not to include film grain metadata (default: yes). If disabled, any film grain metadata will be stripped from frames. diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index 4a2916864c3d9..f226bf22a7ad0 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -60,6 +60,7 @@ struct vf_format_opts { bool convert; int force_scaler; bool dovi; + bool hdr10plus; bool film_grain; }; @@ -178,6 +179,13 @@ static void vf_format_process(struct mp_filter *f) }); } + if (!priv->opts->hdr10plus) { + memset(img->params.color.hdr.scene_max, 0, + sizeof(img->params.color.hdr.scene_max)); + img->params.color.hdr.scene_avg = 0; + img->params.color.hdr.ootf = (struct pl_hdr_bezier){0}; + } + if (!priv->opts->film_grain) av_buffer_unref(&img->film_grain); @@ -240,6 +248,7 @@ static const m_option_t vf_opts_fields[] = { {"dar", OPT_DOUBLE(dar)}, {"convert", OPT_BOOL(convert)}, {"dolbyvision", OPT_BOOL(dovi)}, + {"hdr10plus", OPT_BOOL(hdr10plus)}, {"film-grain", OPT_BOOL(film_grain)}, {"force-scaler", OPT_CHOICE(force_scaler, {"auto", MP_SWS_AUTO}, @@ -256,6 +265,7 @@ const struct mp_user_filter_entry vf_format = { .priv_defaults = &(const OPT_BASE_STRUCT){ .rotate = -1, .dovi = true, + .hdr10plus = true, .film_grain = true, }, .options = vf_opts_fields, From a56d8ff18470e4612cbb23e35e7849b8e522ba7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 11 Feb 2024 03:44:07 +0100 Subject: [PATCH 53/53] stats.lua: display video parameters after filtering --- player/lua/stats.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 82b7c118ce0ae..75c3e35032935 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -806,6 +806,7 @@ local function append_img_params(s, r, ro) end local indent = o.prefix_sep .. o.prefix_sep + r = ro or r local pixel_format = r["hw-pixelformat"] or r["pixelformat"] append(s, pixel_format, {prefix="Format:"})