From nobody Tue Feb 10 04:08:09 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1621602126; cv=none; d=zohomail.com; s=zohoarc; b=lpCSSIUmkxmYAt6J1rLZsv9BM1pp4b0LIfEPtMR/tSIfD4C76tHGaT46/FyDvFmQcHdbWInIyt57WCg51zlFQfAhkwrPPf2cP3rLGEJB2OkAbWQqQO01B/6ZO2tuinw5hn8c+oyG2ZEY3dMXOUV93hIzZ/y6eSPj0Wm6FInX2hQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1621602126; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=e/GobeqPWMnoSn9jVN4AltsxbfmBRCBosjxe6Js/HvA=; b=EtxnJK6l/twUqlFz+tbdtnptOI/lanAUEHkxurrfZG76KMnF3umgG/DlvgOAeSRYy7hKIqJNP3rdqYmZN4QpH4ogoeuXI0U1vo9u0HDeVIXM2JZ1yxVCWixKlTdL6OknH008OC+2sYlevY91ZVrNeq96geuUF+XOmwznyvt+vpQ= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1621602126018263.4719385656489; Fri, 21 May 2021 06:02:06 -0700 (PDT) Received: from localhost ([::1]:33102 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lk4my-00058s-KI for importer@patchew.org; Fri, 21 May 2021 09:02:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:52834) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lk4f1-0007Qy-VV for qemu-devel@nongnu.org; Fri, 21 May 2021 08:53:52 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:45951) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lk4ez-0007zG-0u for qemu-devel@nongnu.org; Fri, 21 May 2021 08:53:51 -0400 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-104-sWdxyxKfPyugqf_NmAjVCA-1; Fri, 21 May 2021 08:53:46 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A67EB8049D6 for ; Fri, 21 May 2021 12:53:45 +0000 (UTC) Received: from sirius.home.kraxel.org (ovpn-112-84.ams2.redhat.com [10.36.112.84]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 346685D9D5; Fri, 21 May 2021 12:53:41 +0000 (UTC) Received: by sirius.home.kraxel.org (Postfix, from userid 1000) id B421C180087F; Fri, 21 May 2021 14:51:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1621601628; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=e/GobeqPWMnoSn9jVN4AltsxbfmBRCBosjxe6Js/HvA=; b=bKTRqyO6XJmrviIWoo1aDj4gUVpPWocluLpX6yaIPeqjUbZk0iAMnGi1pbjfZgXDHg70/X fZjfRYc7/s4RdYvzEdvH38HAuomRdURLWSDTbBOJSwcXdcOr5vqVYrUQ9dU7doY+mTrm8D Ph7M8nDdBhqx79Jxoi1lKqi9xCp08wU= X-MC-Unique: sWdxyxKfPyugqf_NmAjVCA-1 From: Gerd Hoffmann To: qemu-devel@nongnu.org Subject: [PULL 08/11] ui/vdagent: add clipboard support Date: Fri, 21 May 2021 14:51:16 +0200 Message-Id: <20210521125119.3173309-9-kraxel@redhat.com> In-Reply-To: <20210521125119.3173309-1-kraxel@redhat.com> References: <20210521125119.3173309-1-kraxel@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kraxel@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=kraxel@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -31 X-Spam_score: -3.2 X-Spam_bar: --- X-Spam_report: (-3.2 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.374, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Paolo Bonzini , Markus Armbruster , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , Gerd Hoffmann Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) This patch adds support for clipboard messages to the qemu vdagent implementation, which allows the guest exchange clipboard data with qemu. Clipboard support can be enabled/disabled using the new 'clipboard' parameter for the vdagent chardev. Default is off. Signed-off-by: Gerd Hoffmann Reviewed-by: Marc-Andr=C3=A9 Lureau Message-id: 20210519053940.1888907-1-kraxel@redhat.com Message-Id: <20210519053940.1888907-7-kraxel@redhat.com> --- chardev/char.c | 3 + ui/vdagent.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++ qapi/char.json | 4 +- ui/trace-events | 2 + 4 files changed, 301 insertions(+), 1 deletion(-) diff --git a/chardev/char.c b/chardev/char.c index 52c567e8ff00..d959eec5229c 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -934,6 +934,9 @@ QemuOptsList qemu_chardev_opts =3D { },{ .name =3D "mouse", .type =3D QEMU_OPT_BOOL, + },{ + .name =3D "clipboard", + .type =3D QEMU_OPT_BOOL, #ifdef CONFIG_LINUX },{ .name =3D "tight", diff --git a/ui/vdagent.c b/ui/vdagent.c index cf81ab6beb68..a253a8fe63a6 100644 --- a/ui/vdagent.c +++ b/ui/vdagent.c @@ -6,6 +6,7 @@ #include "qemu/option.h" #include "qemu/units.h" #include "hw/qdev-core.h" +#include "ui/clipboard.h" #include "ui/console.h" #include "ui/input.h" #include "trace.h" @@ -17,12 +18,14 @@ =20 #define VDAGENT_BUFFER_LIMIT (1 * MiB) #define VDAGENT_MOUSE_DEFAULT true +#define VDAGENT_CLIPBOARD_DEFAULT false =20 struct VDAgentChardev { Chardev parent; =20 /* config */ bool mouse; + bool clipboard; =20 /* guest vdagent */ uint32_t caps; @@ -41,6 +44,11 @@ struct VDAgentChardev { uint32_t mouse_btn; uint32_t mouse_display; QemuInputHandlerState *mouse_hs; + + /* clipboard */ + QemuClipboardPeer cbpeer; + QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; + uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT]; }; typedef struct VDAgentChardev VDAgentChardev; =20 @@ -96,6 +104,24 @@ static const char *msg_name[] =3D { #endif }; =20 +static const char *sel_name[] =3D { + [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] =3D "clipboard", + [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY] =3D "primary", + [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] =3D "secondary", +}; + +static const char *type_name[] =3D { + [VD_AGENT_CLIPBOARD_NONE] =3D "none", + [VD_AGENT_CLIPBOARD_UTF8_TEXT] =3D "text", + [VD_AGENT_CLIPBOARD_IMAGE_PNG] =3D "png", + [VD_AGENT_CLIPBOARD_IMAGE_BMP] =3D "bmp", + [VD_AGENT_CLIPBOARD_IMAGE_TIFF] =3D "tiff", + [VD_AGENT_CLIPBOARD_IMAGE_JPG] =3D "jpg", +#if 0 + [VD_AGENT_CLIPBOARD_FILE_LIST] =3D "files", +#endif +}; + #define GET_NAME(_m, _v) \ (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???") =20 @@ -161,6 +187,10 @@ static void vdagent_send_caps(VDAgentChardev *vd) if (vd->mouse) { caps->caps[0] |=3D (1 << VD_AGENT_CAP_MOUSE_STATE); } + if (vd->clipboard) { + caps->caps[0] |=3D (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); + caps->caps[0] |=3D (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); + } =20 vdagent_send_msg(vd, msg); } @@ -261,6 +291,244 @@ static QemuInputHandler vdagent_mouse_handler =3D { .sync =3D vdagent_pointer_sync, }; =20 +/* ------------------------------------------------------------------ */ +/* clipboard */ + +static bool have_clipboard(VDAgentChardev *vd) +{ + return vd->clipboard && + (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)); +} + +static bool have_selection(VDAgentChardev *vd) +{ + return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); +} + +static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type) +{ + switch (type) { + case QEMU_CLIPBOARD_TYPE_TEXT: + return VD_AGENT_CLIPBOARD_UTF8_TEXT; + default: + return VD_AGENT_CLIPBOARD_NONE; + } +} + +static void vdagent_send_clipboard_grab(VDAgentChardev *vd, + QemuClipboardInfo *info) +{ + g_autofree VDAgentMessage *msg =3D + g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1)); + uint8_t *s =3D msg->data; + uint32_t *data =3D (uint32_t *)msg->data; + uint32_t q, type; + + if (have_selection(vd)) { + *s =3D info->selection; + data++; + msg->size +=3D sizeof(uint32_t); + } else if (info->selection !=3D QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { + return; + } + + for (q =3D 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) { + type =3D type_qemu_to_vdagent(q); + if (type !=3D VD_AGENT_CLIPBOARD_NONE && info->types[q].available)= { + *data =3D type; + data++; + msg->size +=3D sizeof(uint32_t); + } + } + + msg->type =3D VD_AGENT_CLIPBOARD_GRAB; + vdagent_send_msg(vd, msg); +} + +static void vdagent_send_clipboard_data(VDAgentChardev *vd, + QemuClipboardInfo *info, + QemuClipboardType type) +{ + g_autofree VDAgentMessage *msg =3D g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * 2 + + info->types[type].size); + + uint8_t *s =3D msg->data; + uint32_t *data =3D (uint32_t *)msg->data; + + if (have_selection(vd)) { + *s =3D info->selection; + data++; + msg->size +=3D sizeof(uint32_t); + } else if (info->selection !=3D QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { + return; + } + + *data =3D type_qemu_to_vdagent(type); + data++; + msg->size +=3D sizeof(uint32_t); + + memcpy(data, info->types[type].data, info->types[type].size); + msg->size +=3D info->types[type].size; + + msg->type =3D VD_AGENT_CLIPBOARD; + vdagent_send_msg(vd, msg); +} + +static void vdagent_clipboard_notify(Notifier *notifier, void *data) +{ + VDAgentChardev *vd =3D container_of(notifier, VDAgentChardev, cbpeer.u= pdate); + QemuClipboardInfo *info =3D data; + QemuClipboardSelection s =3D info->selection; + QemuClipboardType type; + bool self_update =3D info->owner =3D=3D &vd->cbpeer; + + if (info !=3D vd->cbinfo[s]) { + qemu_clipboard_info_unref(vd->cbinfo[s]); + vd->cbinfo[s] =3D qemu_clipboard_info_ref(info); + vd->cbpending[s] =3D 0; + if (!self_update) { + vdagent_send_clipboard_grab(vd, info); + } + return; + } + + if (self_update) { + return; + } + + for (type =3D 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) { + if (vd->cbpending[s] & (1 << type)) { + vd->cbpending[s] &=3D ~(1 << type); + vdagent_send_clipboard_data(vd, info, type); + } + } +} + +static void vdagent_clipboard_request(QemuClipboardInfo *info, + QemuClipboardType qtype) +{ + VDAgentChardev *vd =3D container_of(info->owner, VDAgentChardev, cbpee= r); + g_autofree VDAgentMessage *msg =3D g_malloc0(sizeof(VDAgentMessage) + + sizeof(uint32_t) * 2); + uint32_t type =3D type_qemu_to_vdagent(qtype); + uint8_t *s =3D msg->data; + uint32_t *data =3D (uint32_t *)msg->data; + + if (type =3D=3D VD_AGENT_CLIPBOARD_NONE) { + return; + } + + if (have_selection(vd)) { + *s =3D info->selection; + data++; + msg->size +=3D sizeof(uint32_t); + } + + *data =3D type; + msg->size +=3D sizeof(uint32_t); + + msg->type =3D VD_AGENT_CLIPBOARD_REQUEST; + vdagent_send_msg(vd, msg); +} + +static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage = *msg) +{ + uint8_t s =3D VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; + uint32_t size =3D msg->size; + void *data =3D msg->data; + QemuClipboardInfo *info; + QemuClipboardType type; + + if (have_selection(vd)) { + if (size < 4) { + return; + } + s =3D *(uint8_t *)data; + if (s >=3D QEMU_CLIPBOARD_SELECTION__COUNT) { + return; + } + data +=3D 4; + size -=3D 4; + } + + switch (msg->type) { + case VD_AGENT_CLIPBOARD_GRAB: + trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s)); + info =3D qemu_clipboard_info_new(&vd->cbpeer, s); + if (size > sizeof(uint32_t) * 10) { + /* + * spice has 6 types as of 2021. Limiting to 10 entries + * so we we have some wiggle room. + */ + return; + } + while (size >=3D sizeof(uint32_t)) { + trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)da= ta)); + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + info->types[QEMU_CLIPBOARD_TYPE_TEXT].available =3D true; + break; + default: + break; + } + data +=3D sizeof(uint32_t); + size -=3D sizeof(uint32_t); + } + qemu_clipboard_update(info); + qemu_clipboard_info_unref(info); + break; + case VD_AGENT_CLIPBOARD_REQUEST: + if (size < sizeof(uint32_t)) { + return; + } + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + type =3D QEMU_CLIPBOARD_TYPE_TEXT; + break; + default: + return; + } + if (vd->cbinfo[s] && + vd->cbinfo[s]->types[type].available && + vd->cbinfo[s]->owner !=3D &vd->cbpeer) { + if (vd->cbinfo[s]->types[type].data) { + vdagent_send_clipboard_data(vd, vd->cbinfo[s], type); + } else { + vd->cbpending[s] |=3D (1 << type); + qemu_clipboard_request(vd->cbinfo[s], type); + } + } + break; + case VD_AGENT_CLIPBOARD: /* data */ + if (size < sizeof(uint32_t)) { + return; + } + switch (*(uint32_t *)data) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + type =3D QEMU_CLIPBOARD_TYPE_TEXT; + break; + default: + return; + } + data +=3D 4; + size -=3D 4; + qemu_clipboard_set_data(&vd->cbpeer, vd->cbinfo[s], type, + size, data, true); + break; + case VD_AGENT_CLIPBOARD_RELEASE: /* data */ + if (vd->cbinfo[s] && + vd->cbinfo[s]->owner =3D=3D &vd->cbpeer) { + /* set empty clipboard info */ + info =3D qemu_clipboard_info_new(NULL, s); + qemu_clipboard_update(info); + qemu_clipboard_info_unref(info); + } + break; + } +} + /* ------------------------------------------------------------------ */ /* chardev backend */ =20 @@ -286,6 +554,11 @@ static void vdagent_chr_open(Chardev *chr, vd->mouse =3D cfg->mouse; } =20 + vd->clipboard =3D VDAGENT_CLIPBOARD_DEFAULT; + if (cfg->has_clipboard) { + vd->clipboard =3D cfg->clipboard; + } + if (vd->mouse) { vd->mouse_hs =3D qemu_input_handler_register(&vd->mouse_dev, &vdagent_mouse_handler); @@ -317,6 +590,12 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, = VDAgentMessage *msg) if (have_mouse(vd) && vd->mouse_hs) { qemu_input_handler_activate(vd->mouse_hs); } + if (have_clipboard(vd) && vd->cbpeer.update.notify =3D=3D NULL) { + vd->cbpeer.name =3D "vdagent"; + vd->cbpeer.update.notify =3D vdagent_clipboard_notify; + vd->cbpeer.request =3D vdagent_clipboard_request; + qemu_clipboard_peer_register(&vd->cbpeer); + } } =20 static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg) @@ -327,6 +606,14 @@ static void vdagent_chr_recv_msg(VDAgentChardev *vd, V= DAgentMessage *msg) case VD_AGENT_ANNOUNCE_CAPABILITIES: vdagent_chr_recv_caps(vd, msg); break; + case VD_AGENT_CLIPBOARD: + case VD_AGENT_CLIPBOARD_GRAB: + case VD_AGENT_CLIPBOARD_REQUEST: + case VD_AGENT_CLIPBOARD_RELEASE: + if (have_clipboard(vd)) { + vdagent_chr_recv_clipboard(vd, msg); + } + break; default: break; } @@ -448,6 +735,10 @@ static void vdagent_chr_set_fe_open(struct Chardev *ch= r, int fe_open) if (vd->mouse_hs) { qemu_input_handler_deactivate(vd->mouse_hs); } + if (vd->cbpeer.update.notify) { + qemu_clipboard_peer_unregister(&vd->cbpeer); + memset(&vd->cbpeer, 0, sizeof(vd->cbpeer)); + } return; } =20 @@ -464,6 +755,8 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBa= ckend *backend, qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg)); cfg->has_mouse =3D true; cfg->mouse =3D qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT); + cfg->has_clipboard =3D true; + cfg->clipboard =3D qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBO= ARD_DEFAULT); } =20 /* ------------------------------------------------------------------ */ diff --git a/qapi/char.json b/qapi/char.json index 5711e8c60aeb..adf2685f6889 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -396,12 +396,14 @@ # Configuration info for qemu vdagent implementation. # # @mouse: enable/disable mouse, default is enabled. +# @clipboard: enable/disable clipboard, default is disabled. # # Since: 6.1 # ## { 'struct': 'ChardevQemuVDAgent', - 'data': { '*mouse': 'bool' }, + 'data': { '*mouse': 'bool', + '*clipboard': 'bool' }, 'base': 'ChardevCommon', 'if': 'defined(CONFIG_SPICE_PROTOCOL)' } =20 diff --git a/ui/trace-events b/ui/trace-events index c34cffb0452b..c86542e2b69b 100644 --- a/ui/trace-events +++ b/ui/trace-events @@ -132,3 +132,5 @@ vdagent_send(const char *name) "msg %s" vdagent_recv_chunk(uint32_t size) "size %d" vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d" vdagent_peer_cap(const char *name) "cap %s" +vdagent_cb_grab_selection(const char *name) "selection %s" +vdagent_cb_grab_type(const char *name) "type %s" --=20 2.31.1