diff --git a/DOCS/interface-changes/update-clipboard.txt b/DOCS/interface-changes/update-clipboard.txt new file mode 100644 index 0000000000..9322ea7d4f --- /dev/null +++ b/DOCS/interface-changes/update-clipboard.txt @@ -0,0 +1,2 @@ +add `update-clipboard` command +change `clipboard` property to require `update-clipboard` command when clipboard monitoring is disabled diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 7eea15fbcb..cc00ee0d43 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -1758,6 +1758,22 @@ Miscellaneous Commands ``context-menu`` Show context menu on the video window. See `Context Menu`_ section for details. +``update-clipboard [timeout]`` + Update the clipboard content so that the ``clipboard`` property reflects + up-to-date value. This command is required to update the ``clipboard`` + property value if ``--clipboard-monitor=no``. ```` must be ``text`` + or ``text-primary`` to indicate whether to update clipboard or primary + selection. ``[timeout]`` specifies the maximum timeout in ms to wait for + the clipboard content to be updated. A negative timeout means infinite. + The default timeout is 10 ms. + + This command returns as soon as the clipboard content is updated or the + timeout has reached. On some platforms, this command does nothing and + returns immediately. + + This command can be asynchronously aborted via API. See `Asynchronous + command details`_. + Undocumented commands: ``ao-reload`` (experimental/internal). List of events @@ -4285,6 +4301,13 @@ Property list does not have this limitation. See ``current-clipboard-backend`` property for more details. + .. warning:: + + Since mpv 0.42.0, this property does not return the current clipboard + content on certain platforms if ``--clipboard-monitor=no``. + When reading clipboard content, you must run ``update-clipboard`` + command to update the clipboard content manually in this case. + :: MPV_FORMAT_NODE_MAP diff --git a/player/clipboard/clipboard.c b/player/clipboard/clipboard.c index ebb71feba2..7b2f9058a5 100644 --- a/player/clipboard/clipboard.c +++ b/player/clipboard/clipboard.c @@ -19,6 +19,7 @@ #include "common/common.h" #include "common/global.h" +#include "misc/thread_tools.h" #include "options/m_config.h" #include "player/core.h" @@ -107,7 +108,9 @@ static void mp_clipboard_create(struct clipboard_ctx *cl, continue; if (backend->init(cl, params) != CLIPBOARD_SUCCESS) break; + mp_mutex_lock(&cl->lock); cl->backend = backend; + mp_mutex_unlock(&cl->lock); MP_VERBOSE(cl, "Initialized %s clipboard backend.\n", cl->backend->name); return; } @@ -116,16 +119,26 @@ static void mp_clipboard_create(struct clipboard_ctx *cl, MP_WARN(cl, "Failed to initialize any clipboard backend.\n"); } +void mp_clipboard_notify_update_data(struct clipboard_ctx *cl) +{ + mp_mutex_lock(&cl->lock); + for (int i = 0; i < cl->num_cancels; ++i) + mp_cancel_trigger(cl->cancels[i]); + mp_mutex_unlock(&cl->lock); +} + void mp_clipboard_destroy(struct clipboard_ctx *cl) { + mp_mutex_lock(&cl->lock); if (cl->backend && cl->backend->uninit) cl->backend->uninit(cl); - *cl = (struct clipboard_ctx){.log = cl->log}; + cl->backend = cl->priv = NULL; + mp_mutex_unlock(&cl->lock); } bool mp_clipboard_data_changed(struct clipboard_ctx *cl) { - if (cl && cl->backend->data_changed && cl->monitor) + if (cl->backend && cl->backend->data_changed && cl->monitor) return cl->backend->data_changed(cl); return false; } @@ -146,6 +159,32 @@ int mp_clipboard_set_data(struct clipboard_ctx *cl, struct clipboard_access_para return CLIPBOARD_UNAVAILABLE; } +bool mp_clipboard_update_data(struct clipboard_ctx *cl, struct clipboard_access_params *params, + struct mp_cancel *cancel, double timeout) +{ + mp_mutex_lock(&cl->lock); + if (cl->backend && cl->backend->update_data) { + cl->backend->update_data(cl, params); + } else { + mp_mutex_unlock(&cl->lock); + return true; + } + MP_TARRAY_APPEND(cl, cl->cancels, cl->num_cancels, cancel); + mp_mutex_unlock(&cl->lock); + + bool success = mp_cancel_wait(cancel, timeout); + + mp_mutex_lock(&cl->lock); + for (int i = 0; i < cl->num_cancels; ++i) { + if (cl->cancels[i] == cancel) { + MP_TARRAY_REMOVE_AT(cl->cancels, cl->num_cancels, i); + break; + } + } + mp_mutex_unlock(&cl->lock); + return success; +} + const char *mp_clipboard_get_backend_name(struct clipboard_ctx *cl) { return cl->backend ? cl->backend->name : NULL; @@ -168,8 +207,17 @@ void reinit_clipboard(struct MPContext *mpctx) talloc_free(opts); } +static void clipboard_destructor(void *ctx) +{ + struct clipboard_ctx *cl = ctx; + mp_mutex_destroy(&cl->lock); +} + void clipboard_init(struct MPContext *mpctx) { - mpctx->clipboard = talloc_zero_ptrtype(mpctx, mpctx->clipboard); - mpctx->clipboard->log = mp_log_new(mpctx->clipboard, mpctx->global->log, "clipboard"); + struct clipboard_ctx *cl = talloc_zero_ptrtype(mpctx, cl); + talloc_set_destructor(cl, clipboard_destructor); + cl->log = mp_log_new(cl, mpctx->global->log, "clipboard"); + mp_mutex_init(&cl->lock); + mpctx->clipboard = cl; } diff --git a/player/clipboard/clipboard.h b/player/clipboard/clipboard.h index ef991f0826..7a30cb9ac6 100644 --- a/player/clipboard/clipboard.h +++ b/player/clipboard/clipboard.h @@ -18,6 +18,7 @@ #pragma once #include "common/common.h" +#include "osdep/threads.h" struct clipboard_ctx; struct mp_image; @@ -75,6 +76,7 @@ struct clipboard_backend { struct clipboard_data *out, void *talloc_ctx); int (*set_data)(struct clipboard_ctx *cl, struct clipboard_access_params *params, struct clipboard_data *data); + void (*update_data)(struct clipboard_ctx *cl, struct clipboard_access_params *params); }; struct clipboard_ctx { @@ -82,6 +84,12 @@ struct clipboard_ctx { struct mp_log *log; void *priv; // backend-specific internal data bool monitor; + // For notifying mp_clipboard_update_data waiters. + // Lock protects backend and cancel access. + // Must outlive the pending commands that use worker threads. + mp_mutex lock; + struct mp_cancel **cancels; + int num_cancels; }; void mp_clipboard_destroy(struct clipboard_ctx *cl); @@ -90,7 +98,10 @@ int mp_clipboard_get_data(struct clipboard_ctx *cl, struct clipboard_access_para struct clipboard_data *out, void *talloc_ctx); int mp_clipboard_set_data(struct clipboard_ctx *cl, struct clipboard_access_params *params, struct clipboard_data *data); +bool mp_clipboard_update_data(struct clipboard_ctx *cl, struct clipboard_access_params *params, + struct mp_cancel *cancel, double timeout); const char *mp_clipboard_get_backend_name(struct clipboard_ctx *cl); +void mp_clipboard_notify_update_data(struct clipboard_ctx *cl); void reinit_clipboard(struct MPContext *mpctx); void clipboard_init(struct MPContext *mpctx); diff --git a/player/command.c b/player/command.c index 751f676921..946cdfbe86 100644 --- a/player/command.c +++ b/player/command.c @@ -7151,6 +7151,20 @@ static void cmd_begin_vo_dragging(void *p) vo_control(vo, VOCTRL_BEGIN_DRAGGING, NULL); } +static void cmd_update_clipboard(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct clipboard_access_params params = {.type = cmd->args[0].v.i}; + double timeout = cmd->args[1].v.i / 1000.0; + bool success = false; + + mp_core_unlock(mpctx); + success = mp_clipboard_update_data(mpctx->clipboard, ¶ms, cmd->abort->cancel, timeout); + mp_core_lock(mpctx); + cmd->success = success; +} + static void cmd_context_menu(void *p) { struct mp_cmd_ctx *cmd = p; @@ -7705,6 +7719,16 @@ const struct mp_cmd_def mp_cmds[] = { { "begin-vo-dragging", cmd_begin_vo_dragging }, + { "update-clipboard", cmd_update_clipboard, + { {"type", OPT_CHOICE(v.i, + {"text", CLIPBOARD_TARGET_CLIPBOARD}, + {"text-primary", CLIPBOARD_TARGET_PRIMARY_SELECTION})}, + {"timeout", OPT_INT(v.i), OPTDEF_INT(10)}, + }, + .spawn_thread = true, + .can_abort = true, + }, + { "context-menu", cmd_context_menu }, { "flush-status-line", cmd_flush_status_line, { {"clear", OPT_BOOL(v.b)} } },