diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/decode/vd_lavc.c | 62 | ||||
-rw-r--r-- | video/out/gpu/context.h | 3 | ||||
-rw-r--r-- | video/out/gpu/video.c | 4 | ||||
-rw-r--r-- | video/out/opengl/context.c | 9 | ||||
-rw-r--r-- | video/out/opengl/context.h | 3 | ||||
-rw-r--r-- | video/out/opengl/context_glx.c | 129 | ||||
-rw-r--r-- | video/out/vo.c | 45 | ||||
-rw-r--r-- | video/out/vo.h | 38 | ||||
-rw-r--r-- | video/out/vo_gpu.c | 9 | ||||
-rw-r--r-- | video/out/x11_common.c | 49 | ||||
-rw-r--r-- | video/out/x11_common.h | 4 |
11 files changed, 297 insertions, 58 deletions
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index cce3b4510b..97b3fa8b7d 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -28,9 +28,12 @@ #include <libavutil/intreadwrite.h> #include <libavutil/pixdesc.h> +#include "config.h" + #include "mpv_talloc.h" #include "common/global.h" #include "common/msg.h" +#include "options/m_config.h" #include "options/options.h" #include "misc/bstr.h" #include "common/av_common.h" @@ -59,6 +62,8 @@ static void uninit_avctx(struct mp_filter *vd); static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags); static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx, const enum AVPixelFormat *pix_fmt); +static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param); #define HWDEC_DELAY_QUEUE_COUNT 2 @@ -84,6 +89,9 @@ struct vd_lavc_params { int software_fallback; char **avopts; int dr; + char *hwdec_api; + char *hwdec_codecs; + int hwdec_image_format; }; static const struct m_opt_choice_alternatives discard_names[] = { @@ -101,20 +109,24 @@ static const struct m_opt_choice_alternatives discard_names[] = { const struct m_sub_options vd_lavc_conf = { .opts = (const m_option_t[]){ - OPT_FLAG("fast", fast, 0), - OPT_FLAG("show-all", show_all, 0), - OPT_DISCARD("skiploopfilter", skip_loop_filter, 0), - OPT_DISCARD("skipidct", skip_idct, 0), - OPT_DISCARD("skipframe", skip_frame, 0), - OPT_DISCARD("framedrop", framedrop, 0), - OPT_INT("threads", threads, M_OPT_MIN, .min = 0), - OPT_FLAG("bitexact", bitexact, 0), - OPT_FLAG("assume-old-x264", old_x264, 0), - OPT_FLAG("check-hw-profile", check_hw_profile, 0), - OPT_CHOICE_OR_INT("software-fallback", software_fallback, 0, 1, INT_MAX, - ({"no", INT_MAX}, {"yes", 1})), - OPT_KEYVALUELIST("o", avopts, 0), - OPT_FLAG("dr", dr, 0), + OPT_FLAG("vd-lavc-fast", fast, 0), + OPT_FLAG("vd-lavc-show-all", show_all, 0), + OPT_DISCARD("vd-lavc-skiploopfilter", skip_loop_filter, 0), + OPT_DISCARD("vd-lavc-skipidct", skip_idct, 0), + OPT_DISCARD("vd-lavc-skipframe", skip_frame, 0), + OPT_DISCARD("vd-lavc-framedrop", framedrop, 0), + OPT_INT("vd-lavc-threads", threads, M_OPT_MIN, .min = 0), + OPT_FLAG("vd-lavc-bitexact", bitexact, 0), + OPT_FLAG("vd-lavc-assume-old-x264", old_x264, 0), + OPT_FLAG("vd-lavc-check-hw-profile", check_hw_profile, 0), + OPT_CHOICE_OR_INT("vd-lavc-software-fallback", software_fallback, + 0, 1, INT_MAX, ({"no", INT_MAX}, {"yes", 1})), + OPT_KEYVALUELIST("vd-lavc-o", avopts, 0), + OPT_FLAG("vd-lavc-dr", dr, 0), + OPT_STRING_VALIDATE("hwdec", hwdec_api, M_OPT_OPTIONAL_PARAM, + hwdec_validate_opt), + OPT_STRING("hwdec-codecs", hwdec_codecs, 0), + OPT_IMAGEFORMAT("hwdec-image-format", hwdec_image_format, 0, .min = -1), {0} }, .size = sizeof(struct vd_lavc_params), @@ -127,6 +139,8 @@ const struct m_sub_options vd_lavc_conf = { .skip_frame = AVDISCARD_DEFAULT, .framedrop = AVDISCARD_NONREF, .dr = 1, + .hwdec_api = HAVE_RPI ? "mmal" : "no", + .hwdec_codecs = "h264,vc1,hevc,vp9", }, }; @@ -147,7 +161,8 @@ struct hwdec_info { typedef struct lavc_ctx { struct mp_log *log; - struct MPOpts *opts; + struct m_config_cache *opts_cache; + struct vd_lavc_params *opts; struct mp_codec_params *codec; AVCodecContext *avctx; AVFrame *pic; @@ -409,6 +424,8 @@ static void select_and_set_hwdec(struct mp_filter *vd) vd_ffmpeg_ctx *ctx = vd->priv; const char *codec = ctx->codec->codec; + m_config_cache_update(ctx->opts_cache); + bstr opt = bstr0(ctx->opts->hwdec_api); bool hwdec_requested = !bstr_equals0(opt, "no"); @@ -493,8 +510,8 @@ static void select_and_set_hwdec(struct mp_filter *vd) } } -int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) +static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param) { if (bstr_equals0(param, "help")) { struct hwdec_info *hwdecs = NULL; @@ -543,9 +560,11 @@ static void reinit(struct mp_filter *vd) static void init_avctx(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; - struct vd_lavc_params *lavc_param = ctx->opts->vd_lavc_params; + struct vd_lavc_params *lavc_param = ctx->opts; struct mp_codec_params *c = ctx->codec; + m_config_cache_update(ctx->opts_cache); + assert(!ctx->avctx); const AVCodec *lavc_codec = NULL; @@ -911,7 +930,7 @@ static bool prepare_decoding(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; AVCodecContext *avctx = ctx->avctx; - struct vd_lavc_params *opts = ctx->opts->vd_lavc_params; + struct vd_lavc_params *opts = ctx->opts; if (!avctx || ctx->hwdec_failed) return false; @@ -937,7 +956,7 @@ static bool prepare_decoding(struct mp_filter *vd) static void handle_err(struct mp_filter *vd) { vd_ffmpeg_ctx *ctx = vd->priv; - struct vd_lavc_params *opts = ctx->opts->vd_lavc_params; + struct vd_lavc_params *opts = ctx->opts; MP_WARN(vd, "Error while decoding frame!\n"); @@ -1194,7 +1213,8 @@ static struct mp_decoder *create(struct mp_filter *parent, vd_ffmpeg_ctx *ctx = vd->priv; ctx->log = vd->log; - ctx->opts = vd->global->opts; + ctx->opts_cache = m_config_cache_alloc(ctx, vd->global, &vd_lavc_conf); + ctx->opts = ctx->opts_cache->opts; ctx->codec = codec; ctx->decoder = talloc_strdup(ctx, decoder); ctx->hwdec_swpool = mp_image_pool_new(ctx); diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index a2fcb3711a..ef48e6053f 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -83,6 +83,9 @@ struct ra_swapchain_fns { // Performs a buffer swap. This blocks for as long as necessary to meet // params.swapchain_depth, or until the next vblank (for vsynced contexts) void (*swap_buffers)(struct ra_swapchain *sw); + + // See vo. Usually called after swap_buffers(). + void (*get_vsync)(struct ra_swapchain *sw, struct vo_vsync_info *info); }; // Create and destroy a ra_ctx. This also takes care of creating and destroying diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 416ba928d1..6004a0ab60 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -3889,7 +3889,9 @@ static void reinit_from_options(struct gl_video *p) gl_video_setup_hooks(p); reinit_osd(p); - if (p->opts.interpolation && !p->global->opts->video_sync && !p->dsi_warned) { + int vs; + mp_read_option_raw(p->global, "video-sync", &m_option_type_choice, &vs); + if (p->opts.interpolation && !vs && !p->dsi_warned) { MP_WARN(p, "Interpolation now requires enabling display-sync mode.\n" "E.g.: --video-sync=display-resample\n"); p->dsi_warned = true; diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c index 43b57aa4ed..9b3dd6a827 100644 --- a/video/out/opengl/context.c +++ b/video/out/opengl/context.c @@ -313,9 +313,18 @@ void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw) } } +static void ra_gl_ctx_get_vsync(struct ra_swapchain *sw, + struct vo_vsync_info *info) +{ + struct priv *p = sw->priv; + if (p->params.get_vsync) + p->params.get_vsync(sw->ctx, info); +} + static const struct ra_swapchain_fns ra_gl_swapchain_fns = { .color_depth = ra_gl_ctx_color_depth, .start_frame = ra_gl_ctx_start_frame, .submit_frame = ra_gl_ctx_submit_frame, .swap_buffers = ra_gl_ctx_swap_buffers, + .get_vsync = ra_gl_ctx_get_vsync, }; diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h index 5fccc70033..2dd2517ef5 100644 --- a/video/out/opengl/context.h +++ b/video/out/opengl/context.h @@ -23,6 +23,9 @@ struct ra_gl_ctx_params { // function or if you override it yourself. void (*swap_buffers)(struct ra_ctx *ctx); + // See ra_swapchain_fns.get_vsync. + void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info); + // Set to false if the implementation follows normal GL semantics, which is // upside down. Set to true if it does *not*, i.e. if rendering is right // side up diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c index 462f2cf592..1f3225a9ac 100644 --- a/video/out/opengl/context_glx.c +++ b/video/out/opengl/context_glx.c @@ -37,6 +37,7 @@ #define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 #endif +#include "osdep/timer.h" #include "video/out/x11_common.h" #include "context.h" #include "utils.h" @@ -46,6 +47,16 @@ struct priv { XVisualInfo *vinfo; GLXContext context; GLXFBConfig fbc; + + Bool (*XGetSyncValues)(Display*, GLXDrawable, int64_t*, int64_t*, int64_t*); + int64_t last_ust; + int64_t last_msc; + int64_t last_sbc; + int64_t last_sbc_mp_time; + int64_t user_sbc; + int64_t vsync_duration; + int64_t last_skipped_vsyncs; + int64_t last_queue_display_time; }; static void glx_uninit(struct ra_ctx *ctx) @@ -161,6 +172,13 @@ static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version, mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log); + if (gl_check_extension(glxstr, "GLX_OML_sync_control")) { + p->XGetSyncValues = + (void *)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML"); + } + if (p->XGetSyncValues) + MP_VERBOSE(vo, "Using GLX_OML_sync_control.\n"); + return true; } @@ -208,9 +226,115 @@ static void set_glx_attrib(int *attribs, int name, int value) } } +static void update_vsync_oml(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + assert(p->XGetSyncValues); + + p->last_skipped_vsyncs = 0; + p->user_sbc += 1; + + // This extension returns two unrelated values: + // (ust, msc): clock time and incrementing counter of last vsync (this is + // reported continuously, even if we don't swap) + // sbc: swap counter of frame that was last displayed (every swap + // increments the user_sbc, and the reported sbc is the sbc + // of the frame that was just displayed) + // Invariants: + // - ust and msc change in lockstep (no value can change with the other) + // - msc is incremented; if you query it in a loop, and your thread isn't + // frozen or starved by the scheduler, it will usually not change or + // be incremented by 1 (while the ust will be incremented by vsync + // duration) + // - sbc is never higher than the user_sbc + // - (ust, msc) are equal to or higher by vsync increments than the display + // time of the frame referenced by the sbc + // Note that (ust, msc) and sbc are not locked to each other. The following + // can easily happen if vsync skips occur: + // - you draw a frame, in the meantime hardware swaps sbc_1 + // - another display vsync happens during drawing + // - you call swap() + // - query (ust, msc) and sbc + // - sbc contains sbc_1, but (ust, msc) contains the vsync after it + // As a consequence, it's hard to detect the latency or vsync skips. + int64_t ust, msc, sbc; + if (!p->XGetSyncValues(ctx->vo->x11->display, ctx->vo->x11->window, + &ust, &msc, &sbc)) + { + // Assume the extension is effectively unsupported. + p->vsync_duration = -1; + p->last_skipped_vsyncs = -1; + p->last_queue_display_time = -1; + return; + } + + int64_t ust_passed = p->last_ust ? ust - p->last_ust : 0; + p->last_ust = ust; + + int64_t msc_passed = p->last_msc ? msc - p->last_msc : 0; + p->last_msc = msc; + + int64_t sbc_passed = sbc - p->last_sbc; + p->last_sbc = sbc; + + // Display frame duration. This makes assumptions about UST (see below). + if (msc_passed && ust_passed) + p->vsync_duration = ust_passed / msc_passed; + + // Only if a new frame was displayed (sbc increased) we have sort-of a + // chance that the current (ust, msc) is for the sbc. But this is racy, + // because skipped frames drops could have increased the msc right after the + // display event and before we queried the values. This code hopes for the + // best and ignores this. + if (sbc_passed) { + // The extension spec doesn't define what the UST is (not even its unit). + // Simply assume UST is a simple CLOCK_MONOTONIC usec value. The swap + // buffer call happened "some" but very small time ago, so we can get + // away with querying the current time. There is also the implicit + // assumption that mpv's timer and the UST use the same clock (which it + // does on POSIX). + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + return; + uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000; + uint64_t ust_mp_time = mp_time_us() - (now_monotonic - ust); + + // Assume this is exactly when the actual display event for this sbc + // happened. This is subject to the race mentioned above. + p->last_sbc_mp_time = ust_mp_time; + } + + // At least one frame needs to be actually displayed before + // p->last_sbc_mp_time is set. + if (!sbc) + return; + + // Extrapolate from the last sbc time (when a frame was actually displayed), + // and by adding the number of frames that were queued since to it. + // For every unit the sbc is smaller than user_sbc, the actual display + // is one frame ahead (assumes glx_swap_buffers() is called for every + // vsync). + p->last_queue_display_time = + p->last_sbc_mp_time + (p->user_sbc - sbc) * p->vsync_duration; +} + static void glx_swap_buffers(struct ra_ctx *ctx) { + struct priv *p = ctx->priv; + glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window); + + if (p->XGetSyncValues) + update_vsync_oml(ctx); +} + +static void glx_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) +{ + struct priv *p = ctx->priv; + info->vsync_duration = p->vsync_duration; + info->skipped_vsyncs = p->last_skipped_vsyncs; + info->last_queue_display_time = p->last_queue_display_time; } static bool glx_init(struct ra_ctx *ctx) @@ -296,11 +420,16 @@ static bool glx_init(struct ra_ctx *ctx) struct ra_gl_ctx_params params = { .swap_buffers = glx_swap_buffers, + .get_vsync = glx_get_vsync, }; if (!ra_gl_ctx_init(ctx, gl, params)) goto uninit; + p->vsync_duration = -1; + p->last_skipped_vsyncs = -1; + p->last_queue_display_time = -1; + return true; uninit: diff --git a/video/out/vo.c b/video/out/vo.c index 9ecfd76a1f..0d4ed140ae 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -300,11 +300,9 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, m_config_cache_set_dispatch_change_cb(vo->opts_cache, vo->in->dispatch, update_opts, vo); -#if HAVE_GL vo->gl_opts_cache = m_config_cache_alloc(NULL, global, &gl_video_conf); m_config_cache_set_dispatch_change_cb(vo->gl_opts_cache, vo->in->dispatch, update_opts, vo); -#endif vo->eq_opts_cache = m_config_cache_alloc(NULL, global, &mp_csp_equalizer_conf); m_config_cache_set_dispatch_change_cb(vo->eq_opts_cache, vo->in->dispatch, @@ -332,7 +330,9 @@ error: struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex) { - struct m_obj_settings *vo_list = global->opts->vo->video_driver_list; + struct mp_vo_opts *opts = mp_get_config_group(NULL, global, &vo_sub_opts); + struct m_obj_settings *vo_list = opts->video_driver_list; + struct vo *vo = NULL; // first try the preferred drivers, with their optional subdevice param: if (vo_list && vo_list[0].name) { for (int n = 0; vo_list[n].name; n++) { @@ -340,11 +340,11 @@ struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex) if (strlen(vo_list[n].name) == 0) goto autoprobe; bool p = !!vo_list[n + 1].name; - struct vo *vo = vo_create(p, global, ex, vo_list[n].name); + vo = vo_create(p, global, ex, vo_list[n].name); if (vo) - return vo; + goto done; } - return NULL; + goto done; } autoprobe: // now try the rest... @@ -352,11 +352,13 @@ autoprobe: const struct vo_driver *driver = video_out_drivers[i]; if (driver == &video_out_null) break; - struct vo *vo = vo_create(true, global, ex, (char *)driver->name); + vo = vo_create(true, global, ex, (char *)driver->name); if (vo) - return vo; + goto done; } - return NULL; +done: + talloc_free(opts); + return vo; } static void terminate_vo(void *p) @@ -472,14 +474,14 @@ static void vsync_skip_detection(struct vo *vo) } // Always called locked. -static void update_vsync_timing_after_swap(struct vo *vo) +static void update_vsync_timing_after_swap(struct vo *vo, + struct vo_vsync_info *vsync) { struct vo_internal *in = vo->in; - int64_t now = mp_time_us(); + int64_t vsync_time = vsync->last_queue_display_time; int64_t prev_vsync = in->prev_vsync; - - in->prev_vsync = now; + in->prev_vsync = vsync_time; if (!in->expecting_vsync) { reset_vsync_timings(vo); @@ -493,13 +495,13 @@ static void update_vsync_timing_after_swap(struct vo *vo) if (in->num_vsync_samples >= MAX_VSYNC_SAMPLES) in->num_vsync_samples -= 1; MP_TARRAY_INSERT_AT(in, in->vsync_samples, in->num_vsync_samples, 0, - now - prev_vsync); + vsync_time - prev_vsync); in->drop_point = MPMIN(in->drop_point + 1, in->num_vsync_samples); in->num_total_vsync_samples += 1; if (in->base_vsync) { in->base_vsync += in->vsync_interval; } else { - in->base_vsync = now; + in->base_vsync = vsync_time; } double avg = 0; @@ -908,13 +910,24 @@ bool vo_render_frame_external(struct vo *vo) vo->driver->flip_page(vo); + struct vo_vsync_info vsync = { + .last_queue_display_time = -1, + .skipped_vsyncs = -1, + }; + if (vo->driver->get_vsync) + vo->driver->get_vsync(vo, &vsync); + + // Make up some crap if presentation feedback is missing. + if (vsync.last_queue_display_time < 0) + vsync.last_queue_display_time = mp_time_us(); + MP_STATS(vo, "end video-flip"); pthread_mutex_lock(&in->lock); in->dropped_frame = prev_drop_count < vo->in->drop_count; in->rendering = false; - update_vsync_timing_after_swap(vo); + update_vsync_timing_after_swap(vo, &vsync); } if (vo->driver->caps & VO_CAP_NORETAIN) { diff --git a/video/out/vo.h b/video/out/vo.h index 3c00bb988e..3514b6df5c 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -263,6 +263,35 @@ struct vo_frame { uint64_t frame_id; }; +// Presentation feedback. See get_vsync() for how backends should fill this +// struct. +struct vo_vsync_info { + // mp_time_us() timestamp at which the last queued frame will likely be + // displayed (this is in the future, unless the frame is instantly output). + // -1 if unset or unsupported. + // This implies the latency of the output. + int64_t last_queue_display_time; + + // Time between 2 vsync events in microseconds. The difference should be the + // from 2 times sampled from the same reference point (it should not be the + // difference between e.g. the end of scanout and the start of the next one; + // it must be continuous). + // -1 if unsupported. + // 0 if supported, but no value available yet. It is assumed that the value + // becomes available after enough swap_buffers() calls were done. + // >0 values are taken for granted. Very bad things will happen if it's + // inaccurate. + int64_t vsync_duration; + + // Number of skipped physical vsyncs at some point in time. Typically, this + // value is some time in the past by an offset that equals to the latency. + // This value is reset and newly sampled at every swap_buffers() call. + // This can be used to detect delayed frames iff you try to call + // swap_buffers() for every physical vsync. + // -1 if unset or unsupported. + int64_t skipped_vsyncs; +}; + struct vo_driver { // Encoding functionality, which can be invoked via --o only. bool encode; @@ -374,6 +403,15 @@ struct vo_driver { */ void (*flip_page)(struct vo *vo); + /* + * Return presentation feedback. The implementation should not touch fields + * it doesn't support; the info fields are preinitialized to neutral values. + * Usually called once after flip_page(), but can be called any time. + * The values returned by this are always relative to the last flip_page() + * call. + */ + void (*get_vsync)(struct vo *vo, struct vo_vsync_info *info); + /* These optional callbacks can be provided if the GUI framework used by * the VO requires entering a message loop for receiving events and does * not call vo_wakeup() from a separate thread when there are new events. diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c index a80ba233c2..0162e420e8 100644 --- a/video/out/vo_gpu.c +++ b/video/out/vo_gpu.c @@ -98,6 +98,14 @@ static void flip_page(struct vo *vo) sw->fns->swap_buffers(sw); } +static void get_vsync(struct vo *vo, struct vo_vsync_info *info) +{ + struct gpu_priv *p = vo->priv; + struct ra_swapchain *sw = p->ctx->swapchain; + if (sw->fns->get_vsync) + sw->fns->get_vsync(sw, info); +} + static int query_format(struct vo *vo, int format) { struct gpu_priv *p = vo->priv; @@ -326,6 +334,7 @@ const struct vo_driver video_out_gpu = { .get_image = get_image, .draw_frame = draw_frame, .flip_page = flip_page, + .get_vsync = get_vsync, .wait_events = wait_events, .wakeup = wakeup, .uninit = uninit, diff --git a/video/out/x11_common.c b/video/out/x11_common.c index 77ab6bbb45..f01d7e2610 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -248,7 +248,7 @@ static void x11_set_ewmh_state(struct vo_x11_state *x11, char *state, bool set) x11_send_ewmh_msg(x11, "_NET_WM_STATE", params); } -static void vo_set_cursor_hidden(struct vo *vo, bool cursor_hidden) +static void vo_update_cursor(struct vo *vo) { Cursor no_ptr; Pixmap bm_no; @@ -258,16 +258,17 @@ static void vo_set_cursor_hidden(struct vo *vo, bool cursor_hidden) struct vo_x11_state *x11 = vo->x11; Display *disp = x11->display; Window win = x11->window; + bool should_hide = x11->has_focus && !x11->mouse_cursor_visible; - if (cursor_hidden == x11->mouse_cursor_hidden) + if (should_hide == x11->mouse_cursor_set) return; - x11->mouse_cursor_hidden = cursor_hidden; + x11->mouse_cursor_set = should_hide; if (x11->parent == x11->rootwin || !win) return; // do not hide if playing on the root window - if (x11->mouse_cursor_hidden) { + if (x11->mouse_cursor_set) { colormap = DefaultColormap(disp, DefaultScreen(disp)); if (!XAllocNamedColor(disp, colormap, "black", &black, &dummy)) return; // color alloc failed, give up @@ -753,9 +754,6 @@ void vo_x11_uninit(struct vo *vo) set_screensaver(x11, true); - if (x11->window != None) - vo_set_cursor_hidden(vo, false); - if (x11->window != None && x11->window != x11->rootwin) { XUnmapWindow(x11->display, x11->window); XDestroyWindow(x11->display, x11->window); @@ -1040,6 +1038,17 @@ static void vo_x11_check_net_wm_state_fullscreen_change(struct vo *vo) } } +// Releasing all keys on key-up or defocus is simpler and ensures no keys can +// get "stuck". +static void release_all_keys(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + + if (x11->no_autorepeat) + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); + x11->win_drag_button1_down = false; +} + void vo_x11_check_events(struct vo *vo) { struct vo_x11_state *x11 = vo->x11; @@ -1091,16 +1100,18 @@ void vo_x11_check_events(struct vo *vo) } break; } - // Releasing all keys in these situations is simpler and ensures no - // keys can be get "stuck". + case FocusIn: + x11->has_focus = true; + vo_update_cursor(vo); + break; case FocusOut: + release_all_keys(vo); + x11->has_focus = false; + vo_update_cursor(vo); + break; case KeyRelease: - { - if (x11->no_autorepeat) - mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - x11->win_drag_button1_down = false; + release_all_keys(vo); break; - } case MotionNotify: if (x11->win_drag_button1_down && !x11->fs && !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, @@ -1413,10 +1424,9 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, Atom protos[1] = {XA(x11, WM_DELETE_WINDOW)}; XSetWMProtocols(x11->display, x11->window, protos, 1); - if (x11->mouse_cursor_hidden) { - x11->mouse_cursor_hidden = false; - vo_set_cursor_hidden(vo, true); - } + x11->mouse_cursor_set = false; + vo_update_cursor(vo); + if (x11->xim) { x11->xic = XCreateIC(x11->xim, XNInputStyle, XIMPreeditNone | XIMStatusNone, @@ -1858,7 +1868,8 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) return VO_TRUE; } case VOCTRL_SET_CURSOR_VISIBILITY: - vo_set_cursor_hidden(vo, !(*(bool *)arg)); + x11->mouse_cursor_visible = *(bool *)arg; + vo_update_cursor(vo); return VO_TRUE; case VOCTRL_KILL_SCREENSAVER: set_screensaver(x11, false); diff --git a/video/out/x11_common.h b/video/out/x11_common.h index 1c0096329c..608534052c 100644 --- a/video/out/x11_common.h +++ b/video/out/x11_common.h @@ -88,7 +88,9 @@ struct vo_x11_state { bool pseudo_mapped; // not necessarily mapped, but known window size int fs; // whether we assume the window is in fullscreen mode - bool mouse_cursor_hidden; + bool mouse_cursor_visible; + bool mouse_cursor_set; + bool has_focus; long orig_layer; // Current actual window position (updated on window move/resize events). |