[libvirt PATCH v2 3/4] conf: add qemu-vdagent channel

Jonathon Jongsma posted 4 patches 3 years, 10 months ago
[libvirt PATCH v2 3/4] conf: add qemu-vdagent channel
Posted by Jonathon Jongsma 3 years, 10 months ago
Add the ability to configure a qemu-vdagent in guest domains. This
device is similar to the spice vdagent channel except that qemu handles
the spice-vdagent protocol messages itself rather than routing them over
a spice protocol channel.

The qemu-vdagent device has two notable configuration options which
determine whether qemu will handle particular vdagent features:
'clipboard' and 'mouse'.

The 'clipboard' option allows qemu to synchronize its internal clipboard
manager with the guest clipboard, which enables client<->guest clipboard
synchronization for non-spice guests such as vnc.

The 'mouse' option allows absolute mouse positioning to be sent over the
vdagent channel rather than using a usb or virtio tablet device.

Sample configuration:
  <channel type='qemu-vdagent'>
    <target type='virtio' name='com.redhat.spice.0'/>
    <source>
      <clipboard copypaste='yes'/>
      <mouse mode='client'/>
    </source>
  </channel>

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
---
 docs/formatdomain.rst             | 23 ++++++++++++
 src/conf/domain_conf.c            | 62 ++++++++++++++++++++++++++++++-
 src/conf/domain_conf.h            | 20 ++++++----
 src/conf/domain_validate.c        |  1 +
 src/conf/schemas/domaincommon.rng | 51 ++++++++++++++++---------
 src/qemu/qemu_command.c           |  3 ++
 src/qemu/qemu_monitor_json.c      |  1 +
 src/qemu/qemu_process.c           |  1 +
 src/qemu/qemu_validate.c          |  1 +
 src/security/security_apparmor.c  |  2 +
 src/security/security_dac.c       |  2 +
 tests/testutilsqemu.c             |  1 +
 12 files changed, 140 insertions(+), 28 deletions(-)

diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index e492532004..75339c2fda 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -6662,6 +6662,29 @@ types have different ``target`` attributes.
    ``name='com.redhat.spice.0'``. The optional ``address`` element can tie the
    channel to a particular ``type='virtio-serial'`` controller. :since:`Since
    0.8.8`
+``qemu-vdagent``
+   Paravirtualized qemu vdagent channel. This channel implements the SPICE
+   vdagent protocol, but is handled internally by qemu and therefore does not
+   require a SPICE graphics device. Like the spicevmc channel, the ``target``
+   element must be present, with attribute ``type='virtio'``; an optional
+   attribute ``name`` controls how the guest will have access to the channel,
+   and defaults to ``name='com.redhat.spice.0'``. The optional ``address``
+   element can tie the channel to a particular ``type='virtio-serial'``
+   controller. Certain vdagent protocol features can by enabled or disabled
+   using the ``source`` element.
+
+   Copy & Paste functionality is set by the ``clipboard`` element. It is
+   disabled by default, and can be enabled by setting the ``copypaste``
+   property to ``yes``. This allows the guest's clipboard to be synchronized
+   with the qemu clipboard manager. This can enable copy and paste between a
+   guest and a client when using a VNC `graphics device <#elementsGraphics>`__
+   (when using a VNC client that supports the copy/paste feature) or other
+   graphics types that support the qemu clipboard manager.
+
+   Mouse mode is set by the ``mouse`` element, setting its ``mode`` attribute
+   to one of ``server`` or ``client``. If no mode is specified, the qemu
+   default will be used (client mode).
+   :since:`Since 8.2.0`
 
 :anchor:`<a id="elementsCharHostInterface"/>`
 
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 6f9954638c..3b97de89f4 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -713,6 +713,7 @@ VIR_ENUM_IMPL(virDomainChr,
               "spicevmc",
               "spiceport",
               "nmdm",
+              "qemu-vdagent",
 );
 
 VIR_ENUM_IMPL(virDomainChrTcpProtocol,
@@ -2698,6 +2699,7 @@ virDomainChrSourceDefGetPath(virDomainChrSourceDef *chr)
     case VIR_DOMAIN_CHR_TYPE_STDIO:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         return NULL;
     }
@@ -2807,6 +2809,11 @@ virDomainChrSourceDefCopy(virDomainChrSourceDef *dest,
         dest->data.spiceport.channel = g_strdup(src->data.spiceport.channel);
         break;
 
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
+        dest->data.qemuVdagent.clipboard = src->data.qemuVdagent.clipboard;
+        dest->data.qemuVdagent.mouse = src->data.qemuVdagent.mouse;
+        break;
+
     case VIR_DOMAIN_CHR_TYPE_NULL:
     case VIR_DOMAIN_CHR_TYPE_VC:
     case VIR_DOMAIN_CHR_TYPE_STDIO:
@@ -2888,6 +2895,10 @@ virDomainChrSourceDefIsEqual(const virDomainChrSourceDef *src,
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
         return src->data.spicevmc == tgt->data.spicevmc;
 
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
+        return src->data.qemuVdagent.clipboard == tgt->data.qemuVdagent.clipboard &&
+            src->data.qemuVdagent.mouse == tgt->data.qemuVdagent.mouse;
+
     case VIR_DOMAIN_CHR_TYPE_NULL:
     case VIR_DOMAIN_CHR_TYPE_VC:
     case VIR_DOMAIN_CHR_TYPE_STDIO:
@@ -11244,6 +11255,33 @@ virDomainChrSourceDefParseLog(virDomainChrSourceDef *def,
 }
 
 
+static int
+virDomainChrSourceDefParseQemuVdagent(virDomainChrSourceDef *def,
+                                      xmlNodePtr source,
+                                      xmlXPathContextPtr ctxt)
+{
+    xmlNodePtr cur;
+    VIR_XPATH_NODE_AUTORESTORE(ctxt)
+
+    ctxt->node = source;
+    if ((cur = virXPathNode("./clipboard", ctxt))) {
+        if (virXMLPropTristateBool(cur, "copypaste",
+                                   VIR_XML_PROP_REQUIRED,
+                                   &def->data.qemuVdagent.clipboard) < 0)
+            return -1;
+    }
+    if ((cur = virXPathNode("./mouse", ctxt))) {
+        if (virXMLPropEnum(cur, "mode",
+                           virDomainMouseModeTypeFromString,
+                           VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
+                           &def->data.qemuVdagent.mouse) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
 /* Parse the source half of the XML definition for a character device,
  * where node is the first element of node->children of the parent
  * element.  def->type must already be valid.
@@ -11325,6 +11363,12 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDef *def,
             def->data.nmdm.slave = virXMLPropString(sources[0], "slave");
             break;
 
+        case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
+            if (virDomainChrSourceDefParseQemuVdagent(def, sources[0], ctxt) < 0)
+                goto error;
+
+            break;
+
         case VIR_DOMAIN_CHR_TYPE_LAST:
         case VIR_DOMAIN_CHR_TYPE_NULL:
         case VIR_DOMAIN_CHR_TYPE_VC:
@@ -24996,6 +25040,22 @@ virDomainChrSourceDefFormat(virBuffer *buf,
         /* nada */
         break;
 
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
+        if (def->data.qemuVdagent.mouse != VIR_DOMAIN_MOUSE_MODE_DEFAULT ||
+            def->data.qemuVdagent.clipboard != VIR_TRISTATE_BOOL_ABSENT) {
+            virBufferAddLit(buf, "<source>\n");
+            virBufferAdjustIndent(buf, 2);
+            if (def->data.qemuVdagent.clipboard != VIR_TRISTATE_BOOL_ABSENT)
+                virBufferEscapeString(buf, "<clipboard copypaste='%s'/>\n",
+                                      virTristateBoolTypeToString(def->data.qemuVdagent.clipboard));
+            if (def->data.qemuVdagent.mouse != VIR_DOMAIN_MOUSE_MODE_DEFAULT)
+                virBufferEscapeString(buf, "<mouse mode='%s'/>\n",
+                                      virDomainMouseModeTypeToString(def->data.qemuVdagent.mouse));
+            virBufferAdjustIndent(buf, -2);
+            virBufferAddLit(buf, "</source>\n");
+        }
+        break;
+
     case VIR_DOMAIN_CHR_TYPE_PTY:
     case VIR_DOMAIN_CHR_TYPE_DEV:
     case VIR_DOMAIN_CHR_TYPE_FILE:
@@ -25081,7 +25141,6 @@ virDomainChrSourceDefFormat(virBuffer *buf,
         virBufferEscapeString(buf, "<source channel='%s'/>\n",
                               def->data.spiceport.channel);
         break;
-
     }
 
     if (def->logfile) {
@@ -25211,7 +25270,6 @@ virDomainChrTargetDefFormat(virBuffer *buf,
     return 0;
 }
 
-
 static int
 virDomainChrDefFormat(virBuffer *buf,
                       virDomainChrDef *def,
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 2b00099431..484fc08f81 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1239,6 +1239,7 @@ typedef enum {
     VIR_DOMAIN_CHR_TYPE_SPICEVMC,
     VIR_DOMAIN_CHR_TYPE_SPICEPORT,
     VIR_DOMAIN_CHR_TYPE_NMDM,
+    VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT,
 
     VIR_DOMAIN_CHR_TYPE_LAST
 } virDomainChrType;
@@ -1266,6 +1267,13 @@ struct _virDomainChrSourceReconnectDef {
     unsigned int timeout;
 };
 
+typedef enum {
+    VIR_DOMAIN_MOUSE_MODE_DEFAULT = 0,
+    VIR_DOMAIN_MOUSE_MODE_SERVER,
+    VIR_DOMAIN_MOUSE_MODE_CLIENT,
+
+    VIR_DOMAIN_MOUSE_MODE_LAST
+} virDomainMouseMode;
 
 /* The host side information for a character device.  */
 struct _virDomainChrSourceDef {
@@ -1307,6 +1315,10 @@ struct _virDomainChrSourceDef {
         struct {
             char *channel;
         } spiceport;
+        struct {
+            virDomainMouseMode mouse;
+            virTristateBool clipboard;
+        } qemuVdagent;
     } data;
     char *logfile;
     virTristateSwitch logappend;
@@ -1843,14 +1855,6 @@ typedef enum {
     VIR_DOMAIN_GRAPHICS_SPICE_ZLIB_COMPRESSION_LAST
 } virDomainGraphicsSpiceZlibCompression;
 
-typedef enum {
-    VIR_DOMAIN_MOUSE_MODE_DEFAULT = 0,
-    VIR_DOMAIN_MOUSE_MODE_SERVER,
-    VIR_DOMAIN_MOUSE_MODE_CLIENT,
-
-    VIR_DOMAIN_MOUSE_MODE_LAST
-} virDomainMouseMode;
-
 typedef enum {
     VIR_DOMAIN_GRAPHICS_SPICE_STREAMING_MODE_DEFAULT = 0,
     VIR_DOMAIN_GRAPHICS_SPICE_STREAMING_MODE_FILTER,
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index d6869e8fd8..c0eb2490a8 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -849,6 +849,7 @@ virDomainChrSourceDefValidate(const virDomainChrSourceDef *src_def,
     case VIR_DOMAIN_CHR_TYPE_VC:
     case VIR_DOMAIN_CHR_TYPE_STDIO:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         break;
 
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 34bccee2f5..60aad8cabe 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -3965,23 +3965,10 @@
               </element>
             </optional>
             <optional>
-              <element name="clipboard">
-                <attribute name="copypaste">
-                  <ref name="virYesNo"/>
-                </attribute>
-                <empty/>
-              </element>
+              <ref name="clipboard"/>
             </optional>
             <optional>
-              <element name="mouse">
-                <attribute name="mode">
-                  <choice>
-                    <value>server</value>
-                    <value>client</value>
-                  </choice>
-                </attribute>
-                <empty/>
-              </element>
+              <ref name="mousemode"/>
             </optional>
             <optional>
               <element name="filetransfer">
@@ -4070,6 +4057,25 @@
     </element>
   </define>
 
+  <define name="clipboard">
+    <element name="clipboard">
+      <attribute name="copypaste">
+        <ref name="virYesNo"/>
+      </attribute>
+      <empty/>
+    </element>
+  </define>
+  <define name="mousemode">
+    <element name="mouse">
+      <attribute name="mode">
+        <choice>
+          <value>server</value>
+          <value>client</value>
+        </choice>
+      </attribute>
+      <empty/>
+    </element>
+  </define>
   <define name="listenElements">
     <zeroOrMore>
       <element name="listen">
@@ -4470,6 +4476,7 @@
       <value>spicevmc</value>
       <value>spiceport</value>
       <value>nmdm</value>
+      <value>qemu-vdagent</value>
     </choice>
   </define>
 
@@ -4555,9 +4562,17 @@
         <optional>
           <ref name="reconnect"/>
         </optional>
-        <zeroOrMore>
-          <ref name="devSeclabel"/>
-        </zeroOrMore>
+        <interleave>
+          <zeroOrMore>
+            <ref name="devSeclabel"/>
+          </zeroOrMore>
+          <optional>
+            <ref name="clipboard"/>
+          </optional>
+          <optional>
+            <ref name="mousemode"/>
+          </optional>
+        </interleave>
       </element>
     </zeroOrMore>
     <optional>
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 7834aece35..1eef9fb6d0 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -1390,6 +1390,7 @@ qemuBuildChardevStr(const virDomainChrSourceDef *dev,
         break;
 
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
     default:
         break;
@@ -1473,6 +1474,7 @@ qemuBuildChardevCommand(virCommand *cmd,
     case VIR_DOMAIN_CHR_TYPE_UDP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
         break;
 
     case VIR_DOMAIN_CHR_TYPE_NMDM:
@@ -8613,6 +8615,7 @@ qemuInterfaceVhostuserConnect(virCommand *cmd,
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         virReportError(VIR_ERR_INTERNAL_ERROR,
                        _("vhost-user type '%s' not supported"),
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index d5622bd6d9..1ac5377449 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -6818,6 +6818,7 @@ qemuMonitorJSONAttachCharDevGetProps(const char *chrID,
     case VIR_DOMAIN_CHR_TYPE_PIPE:
     case VIR_DOMAIN_CHR_TYPE_STDIO:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
         virReportError(VIR_ERR_OPERATION_FAILED,
                        _("Hotplug unsupported for char device type '%s'"),
                        virDomainChrTypeToString(chr->type));
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 1ed60917ea..423a10738d 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -6834,6 +6834,7 @@ qemuProcessPrepareHostBackendChardevOne(virDomainDeviceDef *dev,
     case VIR_DOMAIN_CHR_TYPE_TCP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
         break;
 
     case VIR_DOMAIN_CHR_TYPE_FILE: {
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index e0708b8a76..5ad3d914dc 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -1991,6 +1991,7 @@ qemuValidateDomainChrSourceDef(const virDomainChrSourceDef *def,
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         break;
     }
diff --git a/src/security/security_apparmor.c b/src/security/security_apparmor.c
index 8f7acba980..55c0193940 100644
--- a/src/security/security_apparmor.c
+++ b/src/security/security_apparmor.c
@@ -1021,6 +1021,7 @@ AppArmorSetChardevLabel(virSecurityManager *mgr,
     case VIR_DOMAIN_CHR_TYPE_TCP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         ret = 0;
         break;
@@ -1083,6 +1084,7 @@ AppArmorSetNetdevLabel(virSecurityManager *mgr,
     case VIR_DOMAIN_CHR_TYPE_TCP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         ret = 0;
         break;
diff --git a/src/security/security_dac.c b/src/security/security_dac.c
index e9e316551e..5b840f4225 100644
--- a/src/security/security_dac.c
+++ b/src/security/security_dac.c
@@ -1555,6 +1555,7 @@ virSecurityDACSetChardevLabelHelper(virSecurityManager *mgr,
     case VIR_DOMAIN_CHR_TYPE_TCP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         break;
     }
@@ -1639,6 +1640,7 @@ virSecurityDACRestoreChardevLabelHelper(virSecurityManager *mgr,
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
     case VIR_DOMAIN_CHR_TYPE_NMDM:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
     case VIR_DOMAIN_CHR_TYPE_LAST:
         break;
     }
diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c
index fe8908f533..10fdeacaa2 100644
--- a/tests/testutilsqemu.c
+++ b/tests/testutilsqemu.c
@@ -1042,6 +1042,7 @@ testQemuPrepareHostBackendChardevOne(virDomainDeviceDef *dev,
     case VIR_DOMAIN_CHR_TYPE_TCP:
     case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
     case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
+    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
         break;
 
     case VIR_DOMAIN_CHR_TYPE_FILE:
-- 
2.35.1
Re: [libvirt PATCH v2 3/4] conf: add qemu-vdagent channel
Posted by Marc-André Lureau 3 years, 10 months ago
Hi


On Thu, Mar 24, 2022 at 11:26 PM Jonathon Jongsma <jjongsma@redhat.com> wrote:
>
> Add the ability to configure a qemu-vdagent in guest domains. This
> device is similar to the spice vdagent channel except that qemu handles
> the spice-vdagent protocol messages itself rather than routing them over
> a spice protocol channel.
>
> The qemu-vdagent device has two notable configuration options which
> determine whether qemu will handle particular vdagent features:
> 'clipboard' and 'mouse'.
>
> The 'clipboard' option allows qemu to synchronize its internal clipboard
> manager with the guest clipboard, which enables client<->guest clipboard
> synchronization for non-spice guests such as vnc.
>
> The 'mouse' option allows absolute mouse positioning to be sent over the
> vdagent channel rather than using a usb or virtio tablet device.
>
> Sample configuration:
>   <channel type='qemu-vdagent'>
>     <target type='virtio' name='com.redhat.spice.0'/>
>     <source>
>       <clipboard copypaste='yes'/>
>       <mouse mode='client'/>
>     </source>
>   </channel>
>
> Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>

I guess you could have added some domain XML tests here, but the next
patch covers it, so

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  docs/formatdomain.rst             | 23 ++++++++++++
>  src/conf/domain_conf.c            | 62 ++++++++++++++++++++++++++++++-
>  src/conf/domain_conf.h            | 20 ++++++----
>  src/conf/domain_validate.c        |  1 +
>  src/conf/schemas/domaincommon.rng | 51 ++++++++++++++++---------
>  src/qemu/qemu_command.c           |  3 ++
>  src/qemu/qemu_monitor_json.c      |  1 +
>  src/qemu/qemu_process.c           |  1 +
>  src/qemu/qemu_validate.c          |  1 +
>  src/security/security_apparmor.c  |  2 +
>  src/security/security_dac.c       |  2 +
>  tests/testutilsqemu.c             |  1 +
>  12 files changed, 140 insertions(+), 28 deletions(-)
>
> diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
> index e492532004..75339c2fda 100644
> --- a/docs/formatdomain.rst
> +++ b/docs/formatdomain.rst
> @@ -6662,6 +6662,29 @@ types have different ``target`` attributes.
>     ``name='com.redhat.spice.0'``. The optional ``address`` element can tie the
>     channel to a particular ``type='virtio-serial'`` controller. :since:`Since
>     0.8.8`
> +``qemu-vdagent``
> +   Paravirtualized qemu vdagent channel. This channel implements the SPICE
> +   vdagent protocol, but is handled internally by qemu and therefore does not
> +   require a SPICE graphics device. Like the spicevmc channel, the ``target``
> +   element must be present, with attribute ``type='virtio'``; an optional
> +   attribute ``name`` controls how the guest will have access to the channel,
> +   and defaults to ``name='com.redhat.spice.0'``. The optional ``address``
> +   element can tie the channel to a particular ``type='virtio-serial'``
> +   controller. Certain vdagent protocol features can by enabled or disabled
> +   using the ``source`` element.
> +
> +   Copy & Paste functionality is set by the ``clipboard`` element. It is
> +   disabled by default, and can be enabled by setting the ``copypaste``
> +   property to ``yes``. This allows the guest's clipboard to be synchronized
> +   with the qemu clipboard manager. This can enable copy and paste between a
> +   guest and a client when using a VNC `graphics device <#elementsGraphics>`__
> +   (when using a VNC client that supports the copy/paste feature) or other
> +   graphics types that support the qemu clipboard manager.
> +
> +   Mouse mode is set by the ``mouse`` element, setting its ``mode`` attribute
> +   to one of ``server`` or ``client``. If no mode is specified, the qemu
> +   default will be used (client mode).
> +   :since:`Since 8.2.0`
>
>  :anchor:`<a id="elementsCharHostInterface"/>`
>
> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
> index 6f9954638c..3b97de89f4 100644
> --- a/src/conf/domain_conf.c
> +++ b/src/conf/domain_conf.c
> @@ -713,6 +713,7 @@ VIR_ENUM_IMPL(virDomainChr,
>                "spicevmc",
>                "spiceport",
>                "nmdm",
> +              "qemu-vdagent",
>  );
>
>  VIR_ENUM_IMPL(virDomainChrTcpProtocol,
> @@ -2698,6 +2699,7 @@ virDomainChrSourceDefGetPath(virDomainChrSourceDef *chr)
>      case VIR_DOMAIN_CHR_TYPE_STDIO:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          return NULL;
>      }
> @@ -2807,6 +2809,11 @@ virDomainChrSourceDefCopy(virDomainChrSourceDef *dest,
>          dest->data.spiceport.channel = g_strdup(src->data.spiceport.channel);
>          break;
>
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
> +        dest->data.qemuVdagent.clipboard = src->data.qemuVdagent.clipboard;
> +        dest->data.qemuVdagent.mouse = src->data.qemuVdagent.mouse;
> +        break;
> +
>      case VIR_DOMAIN_CHR_TYPE_NULL:
>      case VIR_DOMAIN_CHR_TYPE_VC:
>      case VIR_DOMAIN_CHR_TYPE_STDIO:
> @@ -2888,6 +2895,10 @@ virDomainChrSourceDefIsEqual(const virDomainChrSourceDef *src,
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>          return src->data.spicevmc == tgt->data.spicevmc;
>
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
> +        return src->data.qemuVdagent.clipboard == tgt->data.qemuVdagent.clipboard &&
> +            src->data.qemuVdagent.mouse == tgt->data.qemuVdagent.mouse;
> +
>      case VIR_DOMAIN_CHR_TYPE_NULL:
>      case VIR_DOMAIN_CHR_TYPE_VC:
>      case VIR_DOMAIN_CHR_TYPE_STDIO:
> @@ -11244,6 +11255,33 @@ virDomainChrSourceDefParseLog(virDomainChrSourceDef *def,
>  }
>
>
> +static int
> +virDomainChrSourceDefParseQemuVdagent(virDomainChrSourceDef *def,
> +                                      xmlNodePtr source,
> +                                      xmlXPathContextPtr ctxt)
> +{
> +    xmlNodePtr cur;
> +    VIR_XPATH_NODE_AUTORESTORE(ctxt)
> +
> +    ctxt->node = source;
> +    if ((cur = virXPathNode("./clipboard", ctxt))) {
> +        if (virXMLPropTristateBool(cur, "copypaste",
> +                                   VIR_XML_PROP_REQUIRED,
> +                                   &def->data.qemuVdagent.clipboard) < 0)
> +            return -1;
> +    }
> +    if ((cur = virXPathNode("./mouse", ctxt))) {
> +        if (virXMLPropEnum(cur, "mode",
> +                           virDomainMouseModeTypeFromString,
> +                           VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
> +                           &def->data.qemuVdagent.mouse) < 0)
> +            return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +
>  /* Parse the source half of the XML definition for a character device,
>   * where node is the first element of node->children of the parent
>   * element.  def->type must already be valid.
> @@ -11325,6 +11363,12 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDef *def,
>              def->data.nmdm.slave = virXMLPropString(sources[0], "slave");
>              break;
>
> +        case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
> +            if (virDomainChrSourceDefParseQemuVdagent(def, sources[0], ctxt) < 0)
> +                goto error;
> +
> +            break;
> +
>          case VIR_DOMAIN_CHR_TYPE_LAST:
>          case VIR_DOMAIN_CHR_TYPE_NULL:
>          case VIR_DOMAIN_CHR_TYPE_VC:
> @@ -24996,6 +25040,22 @@ virDomainChrSourceDefFormat(virBuffer *buf,
>          /* nada */
>          break;
>
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
> +        if (def->data.qemuVdagent.mouse != VIR_DOMAIN_MOUSE_MODE_DEFAULT ||
> +            def->data.qemuVdagent.clipboard != VIR_TRISTATE_BOOL_ABSENT) {
> +            virBufferAddLit(buf, "<source>\n");
> +            virBufferAdjustIndent(buf, 2);
> +            if (def->data.qemuVdagent.clipboard != VIR_TRISTATE_BOOL_ABSENT)
> +                virBufferEscapeString(buf, "<clipboard copypaste='%s'/>\n",
> +                                      virTristateBoolTypeToString(def->data.qemuVdagent.clipboard));
> +            if (def->data.qemuVdagent.mouse != VIR_DOMAIN_MOUSE_MODE_DEFAULT)
> +                virBufferEscapeString(buf, "<mouse mode='%s'/>\n",
> +                                      virDomainMouseModeTypeToString(def->data.qemuVdagent.mouse));
> +            virBufferAdjustIndent(buf, -2);
> +            virBufferAddLit(buf, "</source>\n");
> +        }
> +        break;
> +
>      case VIR_DOMAIN_CHR_TYPE_PTY:
>      case VIR_DOMAIN_CHR_TYPE_DEV:
>      case VIR_DOMAIN_CHR_TYPE_FILE:
> @@ -25081,7 +25141,6 @@ virDomainChrSourceDefFormat(virBuffer *buf,
>          virBufferEscapeString(buf, "<source channel='%s'/>\n",
>                                def->data.spiceport.channel);
>          break;
> -
>      }
>
>      if (def->logfile) {
> @@ -25211,7 +25270,6 @@ virDomainChrTargetDefFormat(virBuffer *buf,
>      return 0;
>  }
>
> -
>  static int
>  virDomainChrDefFormat(virBuffer *buf,
>                        virDomainChrDef *def,
> diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
> index 2b00099431..484fc08f81 100644
> --- a/src/conf/domain_conf.h
> +++ b/src/conf/domain_conf.h
> @@ -1239,6 +1239,7 @@ typedef enum {
>      VIR_DOMAIN_CHR_TYPE_SPICEVMC,
>      VIR_DOMAIN_CHR_TYPE_SPICEPORT,
>      VIR_DOMAIN_CHR_TYPE_NMDM,
> +    VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT,
>
>      VIR_DOMAIN_CHR_TYPE_LAST
>  } virDomainChrType;
> @@ -1266,6 +1267,13 @@ struct _virDomainChrSourceReconnectDef {
>      unsigned int timeout;
>  };
>
> +typedef enum {
> +    VIR_DOMAIN_MOUSE_MODE_DEFAULT = 0,
> +    VIR_DOMAIN_MOUSE_MODE_SERVER,
> +    VIR_DOMAIN_MOUSE_MODE_CLIENT,
> +
> +    VIR_DOMAIN_MOUSE_MODE_LAST
> +} virDomainMouseMode;
>
>  /* The host side information for a character device.  */
>  struct _virDomainChrSourceDef {
> @@ -1307,6 +1315,10 @@ struct _virDomainChrSourceDef {
>          struct {
>              char *channel;
>          } spiceport;
> +        struct {
> +            virDomainMouseMode mouse;
> +            virTristateBool clipboard;
> +        } qemuVdagent;
>      } data;
>      char *logfile;
>      virTristateSwitch logappend;
> @@ -1843,14 +1855,6 @@ typedef enum {
>      VIR_DOMAIN_GRAPHICS_SPICE_ZLIB_COMPRESSION_LAST
>  } virDomainGraphicsSpiceZlibCompression;
>
> -typedef enum {
> -    VIR_DOMAIN_MOUSE_MODE_DEFAULT = 0,
> -    VIR_DOMAIN_MOUSE_MODE_SERVER,
> -    VIR_DOMAIN_MOUSE_MODE_CLIENT,
> -
> -    VIR_DOMAIN_MOUSE_MODE_LAST
> -} virDomainMouseMode;
> -
>  typedef enum {
>      VIR_DOMAIN_GRAPHICS_SPICE_STREAMING_MODE_DEFAULT = 0,
>      VIR_DOMAIN_GRAPHICS_SPICE_STREAMING_MODE_FILTER,
> diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
> index d6869e8fd8..c0eb2490a8 100644
> --- a/src/conf/domain_validate.c
> +++ b/src/conf/domain_validate.c
> @@ -849,6 +849,7 @@ virDomainChrSourceDefValidate(const virDomainChrSourceDef *src_def,
>      case VIR_DOMAIN_CHR_TYPE_VC:
>      case VIR_DOMAIN_CHR_TYPE_STDIO:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          break;
>
> diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
> index 34bccee2f5..60aad8cabe 100644
> --- a/src/conf/schemas/domaincommon.rng
> +++ b/src/conf/schemas/domaincommon.rng
> @@ -3965,23 +3965,10 @@
>                </element>
>              </optional>
>              <optional>
> -              <element name="clipboard">
> -                <attribute name="copypaste">
> -                  <ref name="virYesNo"/>
> -                </attribute>
> -                <empty/>
> -              </element>
> +              <ref name="clipboard"/>
>              </optional>
>              <optional>
> -              <element name="mouse">
> -                <attribute name="mode">
> -                  <choice>
> -                    <value>server</value>
> -                    <value>client</value>
> -                  </choice>
> -                </attribute>
> -                <empty/>
> -              </element>
> +              <ref name="mousemode"/>
>              </optional>
>              <optional>
>                <element name="filetransfer">
> @@ -4070,6 +4057,25 @@
>      </element>
>    </define>
>
> +  <define name="clipboard">
> +    <element name="clipboard">
> +      <attribute name="copypaste">
> +        <ref name="virYesNo"/>
> +      </attribute>
> +      <empty/>
> +    </element>
> +  </define>
> +  <define name="mousemode">
> +    <element name="mouse">
> +      <attribute name="mode">
> +        <choice>
> +          <value>server</value>
> +          <value>client</value>
> +        </choice>
> +      </attribute>
> +      <empty/>
> +    </element>
> +  </define>
>    <define name="listenElements">
>      <zeroOrMore>
>        <element name="listen">
> @@ -4470,6 +4476,7 @@
>        <value>spicevmc</value>
>        <value>spiceport</value>
>        <value>nmdm</value>
> +      <value>qemu-vdagent</value>
>      </choice>
>    </define>
>
> @@ -4555,9 +4562,17 @@
>          <optional>
>            <ref name="reconnect"/>
>          </optional>
> -        <zeroOrMore>
> -          <ref name="devSeclabel"/>
> -        </zeroOrMore>
> +        <interleave>
> +          <zeroOrMore>
> +            <ref name="devSeclabel"/>
> +          </zeroOrMore>
> +          <optional>
> +            <ref name="clipboard"/>
> +          </optional>
> +          <optional>
> +            <ref name="mousemode"/>
> +          </optional>
> +        </interleave>
>        </element>
>      </zeroOrMore>
>      <optional>
> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
> index 7834aece35..1eef9fb6d0 100644
> --- a/src/qemu/qemu_command.c
> +++ b/src/qemu/qemu_command.c
> @@ -1390,6 +1390,7 @@ qemuBuildChardevStr(const virDomainChrSourceDef *dev,
>          break;
>
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>      default:
>          break;
> @@ -1473,6 +1474,7 @@ qemuBuildChardevCommand(virCommand *cmd,
>      case VIR_DOMAIN_CHR_TYPE_UDP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>          break;
>
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> @@ -8613,6 +8615,7 @@ qemuInterfaceVhostuserConnect(virCommand *cmd,
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          virReportError(VIR_ERR_INTERNAL_ERROR,
>                         _("vhost-user type '%s' not supported"),
> diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
> index d5622bd6d9..1ac5377449 100644
> --- a/src/qemu/qemu_monitor_json.c
> +++ b/src/qemu/qemu_monitor_json.c
> @@ -6818,6 +6818,7 @@ qemuMonitorJSONAttachCharDevGetProps(const char *chrID,
>      case VIR_DOMAIN_CHR_TYPE_PIPE:
>      case VIR_DOMAIN_CHR_TYPE_STDIO:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>          virReportError(VIR_ERR_OPERATION_FAILED,
>                         _("Hotplug unsupported for char device type '%s'"),
>                         virDomainChrTypeToString(chr->type));
> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
> index 1ed60917ea..423a10738d 100644
> --- a/src/qemu/qemu_process.c
> +++ b/src/qemu/qemu_process.c
> @@ -6834,6 +6834,7 @@ qemuProcessPrepareHostBackendChardevOne(virDomainDeviceDef *dev,
>      case VIR_DOMAIN_CHR_TYPE_TCP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>          break;
>
>      case VIR_DOMAIN_CHR_TYPE_FILE: {
> diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
> index e0708b8a76..5ad3d914dc 100644
> --- a/src/qemu/qemu_validate.c
> +++ b/src/qemu/qemu_validate.c
> @@ -1991,6 +1991,7 @@ qemuValidateDomainChrSourceDef(const virDomainChrSourceDef *def,
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          break;
>      }
> diff --git a/src/security/security_apparmor.c b/src/security/security_apparmor.c
> index 8f7acba980..55c0193940 100644
> --- a/src/security/security_apparmor.c
> +++ b/src/security/security_apparmor.c
> @@ -1021,6 +1021,7 @@ AppArmorSetChardevLabel(virSecurityManager *mgr,
>      case VIR_DOMAIN_CHR_TYPE_TCP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          ret = 0;
>          break;
> @@ -1083,6 +1084,7 @@ AppArmorSetNetdevLabel(virSecurityManager *mgr,
>      case VIR_DOMAIN_CHR_TYPE_TCP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          ret = 0;
>          break;
> diff --git a/src/security/security_dac.c b/src/security/security_dac.c
> index e9e316551e..5b840f4225 100644
> --- a/src/security/security_dac.c
> +++ b/src/security/security_dac.c
> @@ -1555,6 +1555,7 @@ virSecurityDACSetChardevLabelHelper(virSecurityManager *mgr,
>      case VIR_DOMAIN_CHR_TYPE_TCP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          break;
>      }
> @@ -1639,6 +1640,7 @@ virSecurityDACRestoreChardevLabelHelper(virSecurityManager *mgr,
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
>      case VIR_DOMAIN_CHR_TYPE_NMDM:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>      case VIR_DOMAIN_CHR_TYPE_LAST:
>          break;
>      }
> diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c
> index fe8908f533..10fdeacaa2 100644
> --- a/tests/testutilsqemu.c
> +++ b/tests/testutilsqemu.c
> @@ -1042,6 +1042,7 @@ testQemuPrepareHostBackendChardevOne(virDomainDeviceDef *dev,
>      case VIR_DOMAIN_CHR_TYPE_TCP:
>      case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
>      case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
> +    case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
>          break;
>
>      case VIR_DOMAIN_CHR_TYPE_FILE:
> --
> 2.35.1
>