command: add update-clipboard command

This uses the update_data request for clipboard backends that require it.
When clipboard monitoring is disabled, the backends will no longer
fetch data when it detects data change, and will now only do that when
receiving the update_data request.

The client now needs to run this command to update the clipboard content,
otherwise the property value is outdated.

This makes sure that applications that depend on "paste once" behavior
work correctly.

This command runs in a separate worker thread so that it can be used
as a "blocking" way to update clipboard until the update notification
arrives without blocking the playback core. It accepts a maximum timeout
parameter. This uses the existing mp_cancel mechanism to make sure this
command can be aborted and the worker threads will not stall the player
too much when quitting mpv.

For backends that do not support update_data, this command does nothing.
This commit is contained in:
nanahi
2026-03-15 08:09:52 -04:00
committed by Kacper Michajłow
parent 49b2cf66d6
commit f7f7cf18f3
5 changed files with 112 additions and 4 deletions
@@ -0,0 +1,2 @@
add `update-clipboard` command
change `clipboard` property to require `update-clipboard` command when clipboard monitoring is disabled
+23
View File
@@ -1758,6 +1758,22 @@ Miscellaneous Commands
``context-menu``
Show context menu on the video window. See `Context Menu`_ section for details.
``update-clipboard <type> [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``. ``<type>`` 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
+52 -4
View File
@@ -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;
}
+11
View File
@@ -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);
+24
View File
@@ -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, &params, 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)} } },