diff options
-rw-r--r-- | player/playloop.c | 23 | ||||
-rw-r--r-- | video/out/vo.c | 34 | ||||
-rw-r--r-- | video/out/vo.h | 22 | ||||
-rw-r--r-- | video/out/win_state.c | 180 | ||||
-rw-r--r-- | video/out/win_state.h | 57 |
5 files changed, 308 insertions, 8 deletions
diff --git a/player/playloop.c b/player/playloop.c index 5c83615a86..585cad93c9 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -45,6 +45,7 @@ #include "sub/dec_sub.h" #include "sub/osd.h" #include "video/out/vo.h" +#include "video/out/win_state.h" #include "core.h" #include "client.h" @@ -832,10 +833,12 @@ static void handle_cursor_autohide(struct MPContext *mpctx) static void handle_vo_events(struct MPContext *mpctx) { struct vo *vo = mpctx->video_out; - int events = vo ? vo_query_and_reset_events(vo, VO_EVENTS_USER) : 0; + if (!vo) + return; + int events = vo_query_and_reset_events(vo, VO_EVENTS_USER); if (events & VO_EVENT_RESIZE) mp_notify(mpctx, MP_EVENT_WIN_RESIZE, NULL); - if (events & VO_EVENT_WIN_STATE) + if (events & (VO_EVENT_WIN_STATE | VO_EVENT_WIN_STATE2)) mp_notify(mpctx, MP_EVENT_WIN_STATE, NULL); if (events & VO_EVENT_FULLSCREEN_STATE) { // The only purpose of this is to update the fullscreen flag on the @@ -848,6 +851,22 @@ static void handle_vo_events(struct MPContext *mpctx) m_config_get_co(mpctx->mconfig, bstr0("fullscreen")), &fs, 0); } } + if (events & VO_EVENT_WIN_STATE2) { + // The only purpose of this is to update the option values on the + // playloop side if it changes "from outside" on the VO. This will be + // unnecessary once the core can deal with asynchronous option changes. + while (1) { + union m_option_value val; + int st = vo_win_state_fetch_ext_wrap(vo, -1, &val); + if (st < 0) + break; + char *optname = vo_win_state_opt(st); + if (optname) { + m_config_set_option_raw(mpctx->mconfig, + m_config_get_co(mpctx->mconfig, bstr0(optname)), &val, 0); + } + } + } } static void handle_sstep(struct MPContext *mpctx) diff --git a/video/out/vo.c b/video/out/vo.c index e28026cab5..90f33e60e4 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -45,6 +45,7 @@ #include "sub/osd.h" #include "osdep/io.h" #include "osdep/threads.h" +#include "win_state.h" extern const struct vo_driver video_out_mediacodec_embed; extern const struct vo_driver video_out_x11; @@ -162,6 +163,8 @@ struct vo_internal { double display_fps; double reported_display_fps; + + struct vo_win_state *win_state; }; extern const struct m_sub_options gl_video_conf; @@ -229,8 +232,10 @@ static void update_opts(void *p) read_opts(vo); // "Legacy" update of video position related options. - if (vo->driver->control) + if (vo->driver->control) { vo->driver->control(vo, VOCTRL_SET_PANSCAN, NULL); + vo->driver->control(vo, VOCTRL_VO_WIN_STATE_UPDATE, NULL); + } } if (vo->gl_opts_cache && m_config_cache_update(vo->gl_opts_cache)) { @@ -1069,6 +1074,7 @@ static void *vo_thread(void *ptr) vo->driver->uninit(vo); done: TA_FREEP(&in->dr_helper); + assert(!in->win_state); return NULL; } @@ -1352,6 +1358,32 @@ struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, return NULL; } +void vo_set_internal_win_state(struct vo *vo, struct vo_win_state *st) +{ + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + assert(!!in->win_state != !!st); // can either set or unset it + in->win_state = st; + pthread_mutex_unlock(&in->lock); +} + +int vo_win_state_fetch_ext_wrap(struct vo *vo, int state, + union m_option_value *val) +{ + struct vo_internal *in = vo->in; + int res = -1; + pthread_mutex_lock(&in->lock); + + if (in->win_state) { + res = vo_win_state_fetch_ext(in->win_state, state, val); + } else { + *val = (union m_option_value){0}; + } + + pthread_mutex_unlock(&in->lock); + return res; +} + static void destroy_frame(void *p) { struct vo_frame *frame = p; diff --git a/video/out/vo.h b/video/out/vo.h index 08ca1219a1..a68912ec84 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -37,7 +37,8 @@ enum { VO_EVENT_RESIZE = 1 << 1, // The ICC profile needs to be reloaded VO_EVENT_ICC_PROFILE_CHANGED = 1 << 2, - // Some other window state changed (position, window state, fps) + // When display fps or window position changes. + // Legacy: some other window state changed (fullscreen etc.) VO_EVENT_WIN_STATE = 1 << 3, // The ambient light conditions changed and need to be reloaded VO_EVENT_AMBIENT_LIGHTING_CHANGED = 1 << 4, @@ -48,10 +49,13 @@ enum { // Special thing for encode mode (vo_driver.initially_blocked). // Part of VO_EVENTS_USER to make vo_is_ready_for_frame() work properly. VO_EVENT_INITIAL_UNBLOCK = 1 << 7, + // Triggered by vo_win_state. Other code does not mess with this. + VO_EVENT_WIN_STATE2 = 1 << 8, // Set of events the player core may be interested in. VO_EVENTS_USER = VO_EVENT_RESIZE | VO_EVENT_WIN_STATE | - VO_EVENT_FULLSCREEN_STATE | VO_EVENT_INITIAL_UNBLOCK, + VO_EVENT_FULLSCREEN_STATE | VO_EVENT_INITIAL_UNBLOCK | + VO_EVENT_WIN_STATE2, }; enum mp_voctrl { @@ -80,12 +84,16 @@ enum mp_voctrl { VOCTRL_UNINIT, VOCTRL_RECONFIG, + // Legacy, do not use. VOCTRL_FULLSCREEN, + VOCTRL_GET_FULLSCREEN, VOCTRL_ONTOP, VOCTRL_BORDER, VOCTRL_ALL_WORKSPACES, + VOCTRL_GET_WIN_STATE, // int* (VO_WIN_STATE_* flags) - VOCTRL_GET_FULLSCREEN, + // If you use vo_win_state, call vo_win_state_update(). + VOCTRL_VO_WIN_STATE_UPDATE, VOCTRL_UPDATE_WINDOW_TITLE, // char* VOCTRL_UPDATE_PLAYBACK_STATE, // struct voctrl_playback_state* @@ -102,8 +110,6 @@ enum mp_voctrl { VOCTRL_GET_UNFS_WINDOW_SIZE, // int[2] (w/h) VOCTRL_SET_UNFS_WINDOW_SIZE, // int[2] (w/h) - VOCTRL_GET_WIN_STATE, // int* (VO_WIN_STATE_* flags) - // char *** (NULL terminated array compatible with CONF_TYPE_STRING_LIST) // names for displays the window is on VOCTRL_GET_DISPLAY_NAMES, @@ -520,6 +526,12 @@ struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, void vo_wakeup(struct vo *vo); void vo_wait_default(struct vo *vo, int64_t until_time); +// Internal for VO common code. VO backends do not call this. +struct vo_win_state; +void vo_set_internal_win_state(struct vo *vo, struct vo_win_state *st); +int vo_win_state_fetch_ext_wrap(struct vo *vo, int state, + union m_option_value *val); + struct mp_keymap { int from; int to; diff --git a/video/out/win_state.c b/video/out/win_state.c index 96857160cf..473cc38d62 100644 --- a/video/out/win_state.c +++ b/video/out/win_state.c @@ -15,6 +15,11 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include <pthread.h> + +#include "common/common.h" +#include "options/m_config.h" +#include "options/m_option.h" #include "win_state.h" #include "vo.h" @@ -140,3 +145,178 @@ void vo_apply_window_geometry(struct vo *vo, const struct vo_win_geometry *geo) vo->dheight = geo->win.y1 - geo->win.y0; vo->monitor_par = geo->monitor_par; } + +struct vo_win_state { + pthread_mutex_t lock; + + struct vo *vo; + + struct m_config_cache *opts_cache; + struct mp_vo_opts *opts; + + struct m_option types[VO_WIN_STATE_COUNT]; + void *opt_map[VO_WIN_STATE_COUNT]; + uint64_t external_changed; // VO_WIN_STATE_* bit field + // If external_changed bit set, this is the external "fixed" value. + // Otherwise, this is the current/previous value. + union m_option_value fixed[VO_WIN_STATE_COUNT]; + + // These are not options. + int minimize, maximize; +}; + +static void win_state_destroy(void *p) +{ + struct vo_win_state *st = p; + + vo_set_internal_win_state(st->vo, NULL); + + pthread_mutex_destroy(&st->lock); +} + +struct vo_win_state *vo_win_state_create(struct vo *vo) +{ + struct vo_win_state *st = talloc_zero(NULL, struct vo_win_state); + talloc_set_destructor(st, win_state_destroy); + + pthread_mutex_init(&st->lock, NULL); + + st->vo = vo; + + st->opts_cache = m_config_cache_alloc(st, vo->global, &vo_sub_opts); + st->opts = st->opts_cache->opts; + + st->opt_map[VO_WIN_STATE_FULLSCREEN] = &st->opts->fullscreen; + st->opt_map[VO_WIN_STATE_MINIMIZE] = &st->minimize; + st->opt_map[VO_WIN_STATE_MAXIMIZE] = &st->maximize; + st->opt_map[VO_WIN_STATE_ON_TOP] = &st->opts->ontop; + st->opt_map[VO_WIN_STATE_BORDER] = &st->opts->border; + st->opt_map[VO_WIN_STATE_ALL_WS] = &st->opts->all_workspaces; + + for (int n = 0; n < MP_ARRAY_SIZE(st->opt_map); n++) { + // Currently all the same. + st->types[n] = (struct m_option){ + .type = &m_option_type_flag, + }; + + // Copy initial value. + m_option_copy(&st->types[n], &st->fixed[n], st->opt_map[n]); + } + + vo_set_internal_win_state(vo, st); + return st; +} + +char *vo_win_state_opt(enum vo_win_states state) +{ + switch (state) { + case VO_WIN_STATE_FULLSCREEN: return "fullscreen"; + case VO_WIN_STATE_ON_TOP: return "ontop"; + case VO_WIN_STATE_BORDER: return "border"; + case VO_WIN_STATE_ALL_WS: return "on-all-workspaces"; + default: return NULL; // not managed as option + } +} + +struct mp_vo_opts *vo_win_state_opts(struct vo_win_state *st) +{ + return st->opts; +} + +// (The generic option code does not have this because it's too complex to +// support for _all_ option types.) +static bool opt_equals(struct m_option *t, void *v1, void *v2) +{ + if (t->type == &m_option_type_flag) { + return *(int *)v1 == *(int *)v2; + } else { + assert(0); // well, add it + } +} + +uint64_t vo_win_state_update(struct vo_win_state *st) +{ + uint64_t changed = 0; + + pthread_mutex_lock(&st->lock); + + if (m_config_cache_update(st->opts_cache)) { + // Ignore changes to any "fixed" fields, but return other changed fields. + for (int n = 0; n < MP_ARRAY_SIZE(st->opt_map); n++) { + if (st->external_changed & (1ull << n)) + m_option_copy(&st->types[n], st->opt_map[n], &st->fixed[n]); + + if (!opt_equals(&st->types[n], st->opt_map[n], &st->fixed[n])) { + changed |= (1ull << n); + + m_option_copy(&st->types[n], &st->fixed[n], st->opt_map[n]); + } + } + } + + pthread_mutex_unlock(&st->lock); + + return changed; +} + +bool vo_win_state_get_bool(struct vo_win_state *st, enum vo_win_states state) +{ + assert(st->types[state].type == &m_option_type_flag); + return *(int *)st->opt_map[state]; +} + +void vo_win_state_report_bool(struct vo_win_state *st, enum vo_win_states state, + bool val) +{ + assert(st->types[state].type == &m_option_type_flag); + *(int *)st->opt_map[state] = val; + vo_win_state_report_external_changed(st, st->opt_map[state]); +} + +void vo_win_state_report_external_changed(struct vo_win_state *st, void *field) +{ + int state = -1; + for (int n = 0; n < MP_ARRAY_SIZE(st->opt_map); n++) { + if (st->opt_map[n] == field) { + state = n; + break; + } + } + + assert(state >= 0); // user passed non-managed field, or uses wrong opt. struct + + // "Fix" the option to avoid that concurrent or recursive option updates + // clobber it (urgh). + pthread_mutex_lock(&st->lock); + st->external_changed |= 1ull << state; + m_option_copy(&st->types[state], &st->fixed[state], field); + pthread_mutex_unlock(&st->lock); + + // Causes some magic code to call vo_win_state_fetch_ext() to reset the + // fixed option. + vo_event(st->vo, VO_EVENT_WIN_STATE2); +} + +int vo_win_state_fetch_ext(struct vo_win_state *st, int state, + union m_option_value *val) +{ + pthread_mutex_lock(&st->lock); + + if (state < 0) { + for (int n = 0; n < MP_ARRAY_SIZE(st->opt_map); n++) { + uint64_t mask = 1ull << n; + if (st->external_changed & mask) { + st->external_changed &= ~mask; + state = n; + break; + } + } + } + + if (state >= 0) + m_option_copy(&st->types[state], val, st->opt_map[state]); + + pthread_mutex_unlock(&st->lock); + + return state; +} diff --git a/video/out/win_state.h b/video/out/win_state.h index d495377bac..a5de5691fb 100644 --- a/video/out/win_state.h +++ b/video/out/win_state.h @@ -29,4 +29,61 @@ void vo_calc_window_geometry2(struct vo *vo, const struct mp_rect *screen, double dpi_scale, struct vo_win_geometry *out_geo); void vo_apply_window_geometry(struct vo *vo, const struct vo_win_geometry *geo); +// Currently manages some user options. +struct vo_win_state; + +enum vo_win_states { + VO_WIN_STATE_FULLSCREEN, // bool + VO_WIN_STATE_MINIMIZE, // bool + VO_WIN_STATE_MAXIMIZE, // bool + VO_WIN_STATE_ON_TOP, // bool + VO_WIN_STATE_BORDER, // bool + VO_WIN_STATE_ALL_WS, // bool + + VO_WIN_STATE_COUNT +}; + +// Destroy with talloc_free(). +// Note: this must be strictly destroyed before vo. +// This hooks itself into vo (in a thread-safe way), and you can have only one +// per vo. +struct vo_win_state *vo_win_state_create(struct vo *vo); + +// Note: it's _not_ OK to use vo->opts instead (e.g. vo->opts->fullscreen +// instead of this opt struct. This is because vo->opts is managed for the +// VO thread, so this breaks with backends that do windowing on a foreign +// thread. You may also use vo_get_win_opts(). +struct mp_vo_opts *vo_win_state_opts(struct vo_win_state *st); + +// Update state in reaction to other events. Normally, you want to call this +// when receiving VOCTRL_VO_STATE_UPDATE. +// This returns a bit-field of externally changed event, using vo_win_states as +// bit position. E.g. if fullscreen and on-top changes, this would return +// (1 << VO_WIN_STATE_FULLSCREEN) | (1 << VO_WIN_STATE_ON_TOP). +uint64_t vo_win_state_update(struct vo_win_state *st); + +// Query the current user-desired state (basically, return the option value). +// This is equivalent to using vo_win_state_opts()->[field mapping to state]. +bool vo_win_state_get_bool(struct vo_win_state *st, enum vo_win_states state); + +// Update the current state, usually in reaction to external events. +// This is equivalent to setting vo_win_state_opts()->[field mapping to state] +// to the new value, and calling vo_win_state_mark_external_changed(). +void vo_win_state_report_bool(struct vo_win_state *st, enum vo_win_states state, + bool val); + +// Notify that the current state was updated, usually in reaction to external +// events. "field" must be a pointer to a field in vo_win_state_opts() (that +// is an option, and a direct member of struct mp_subtitle_opts). +void vo_win_state_report_external_changed(struct vo_win_state *st, void *field); + +// Internal: get and reset next changed state (option-managed fields only). +// Returns state value if it was externally changed, or -1 if not. +// If state<0, then get next changed state, otherwise use fixed state. +int vo_win_state_fetch_ext(struct vo_win_state *st, int state, + union m_option_value *val); + +// Internal, a hack. +char *vo_win_state_opt(enum vo_win_states state); + #endif |