@ -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 @@
# define VDAGENT_BUFFER_LIMIT (1 * MiB)
# define VDAGENT_MOUSE_DEFAULT true
# define VDAGENT_CLIPBOARD_DEFAULT false
struct VDAgentChardev {
Chardev parent ;
/* config */
bool mouse ;
bool clipboard ;
/* 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 ;
@ -96,6 +104,24 @@ static const char *msg_name[] = {
# endif
} ;
static const char * sel_name [ ] = {
[ VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD ] = " clipboard " ,
[ VD_AGENT_CLIPBOARD_SELECTION_PRIMARY ] = " primary " ,
[ VD_AGENT_CLIPBOARD_SELECTION_SECONDARY ] = " secondary " ,
} ;
static const char * type_name [ ] = {
[ VD_AGENT_CLIPBOARD_NONE ] = " none " ,
[ VD_AGENT_CLIPBOARD_UTF8_TEXT ] = " text " ,
[ VD_AGENT_CLIPBOARD_IMAGE_PNG ] = " png " ,
[ VD_AGENT_CLIPBOARD_IMAGE_BMP ] = " bmp " ,
[ VD_AGENT_CLIPBOARD_IMAGE_TIFF ] = " tiff " ,
[ VD_AGENT_CLIPBOARD_IMAGE_JPG ] = " jpg " ,
#if 0
[ VD_AGENT_CLIPBOARD_FILE_LIST ] = " files " ,
# endif
} ;
# define GET_NAME(_m, _v) \
( ( ( _v ) < ARRAY_SIZE ( _m ) & & ( _m [ _v ] ) ) ? ( _m [ _v ] ) : " ??? " )
@ -161,6 +187,10 @@ static void vdagent_send_caps(VDAgentChardev *vd)
if ( vd - > mouse ) {
caps - > caps [ 0 ] | = ( 1 < < VD_AGENT_CAP_MOUSE_STATE ) ;
}
if ( vd - > clipboard ) {
caps - > caps [ 0 ] | = ( 1 < < VD_AGENT_CAP_CLIPBOARD_BY_DEMAND ) ;
caps - > caps [ 0 ] | = ( 1 < < VD_AGENT_CAP_CLIPBOARD_SELECTION ) ;
}
vdagent_send_msg ( vd , msg ) ;
}
@ -261,6 +291,244 @@ static QemuInputHandler vdagent_mouse_handler = {
. sync = vdagent_pointer_sync ,
} ;
/* ------------------------------------------------------------------ */
/* 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 =
g_malloc0 ( sizeof ( VDAgentMessage ) +
sizeof ( uint32_t ) * ( QEMU_CLIPBOARD_TYPE__COUNT + 1 ) ) ;
uint8_t * s = msg - > data ;
uint32_t * data = ( uint32_t * ) msg - > data ;
uint32_t q , type ;
if ( have_selection ( vd ) ) {
* s = info - > selection ;
data + + ;
msg - > size + = sizeof ( uint32_t ) ;
} else if ( info - > selection ! = QEMU_CLIPBOARD_SELECTION_CLIPBOARD ) {
return ;
}
for ( q = 0 ; q < QEMU_CLIPBOARD_TYPE__COUNT ; q + + ) {
type = type_qemu_to_vdagent ( q ) ;
if ( type ! = VD_AGENT_CLIPBOARD_NONE & & info - > types [ q ] . available ) {
* data = type ;
data + + ;
msg - > size + = sizeof ( uint32_t ) ;
}
}
msg - > type = 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 = g_malloc0 ( sizeof ( VDAgentMessage ) +
sizeof ( uint32_t ) * 2 +
info - > types [ type ] . size ) ;
uint8_t * s = msg - > data ;
uint32_t * data = ( uint32_t * ) msg - > data ;
if ( have_selection ( vd ) ) {
* s = info - > selection ;
data + + ;
msg - > size + = sizeof ( uint32_t ) ;
} else if ( info - > selection ! = QEMU_CLIPBOARD_SELECTION_CLIPBOARD ) {
return ;
}
* data = type_qemu_to_vdagent ( type ) ;
data + + ;
msg - > size + = sizeof ( uint32_t ) ;
memcpy ( data , info - > types [ type ] . data , info - > types [ type ] . size ) ;
msg - > size + = info - > types [ type ] . size ;
msg - > type = VD_AGENT_CLIPBOARD ;
vdagent_send_msg ( vd , msg ) ;
}
static void vdagent_clipboard_notify ( Notifier * notifier , void * data )
{
VDAgentChardev * vd = container_of ( notifier , VDAgentChardev , cbpeer . update ) ;
QemuClipboardInfo * info = data ;
QemuClipboardSelection s = info - > selection ;
QemuClipboardType type ;
bool self_update = info - > owner = = & vd - > cbpeer ;
if ( info ! = vd - > cbinfo [ s ] ) {
qemu_clipboard_info_unref ( vd - > cbinfo [ s ] ) ;
vd - > cbinfo [ s ] = qemu_clipboard_info_ref ( info ) ;
vd - > cbpending [ s ] = 0 ;
if ( ! self_update ) {
vdagent_send_clipboard_grab ( vd , info ) ;
}
return ;
}
if ( self_update ) {
return ;
}
for ( type = 0 ; type < QEMU_CLIPBOARD_TYPE__COUNT ; type + + ) {
if ( vd - > cbpending [ s ] & ( 1 < < type ) ) {
vd - > cbpending [ s ] & = ~ ( 1 < < type ) ;
vdagent_send_clipboard_data ( vd , info , type ) ;
}
}
}
static void vdagent_clipboard_request ( QemuClipboardInfo * info ,
QemuClipboardType qtype )
{
VDAgentChardev * vd = container_of ( info - > owner , VDAgentChardev , cbpeer ) ;
g_autofree VDAgentMessage * msg = g_malloc0 ( sizeof ( VDAgentMessage ) +
sizeof ( uint32_t ) * 2 ) ;
uint32_t type = type_qemu_to_vdagent ( qtype ) ;
uint8_t * s = msg - > data ;
uint32_t * data = ( uint32_t * ) msg - > data ;
if ( type = = VD_AGENT_CLIPBOARD_NONE ) {
return ;
}
if ( have_selection ( vd ) ) {
* s = info - > selection ;
data + + ;
msg - > size + = sizeof ( uint32_t ) ;
}
* data = type ;
msg - > size + = sizeof ( uint32_t ) ;
msg - > type = VD_AGENT_CLIPBOARD_REQUEST ;
vdagent_send_msg ( vd , msg ) ;
}
static void vdagent_chr_recv_clipboard ( VDAgentChardev * vd , VDAgentMessage * msg )
{
uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD ;
uint32_t size = msg - > size ;
void * data = msg - > data ;
QemuClipboardInfo * info ;
QemuClipboardType type ;
if ( have_selection ( vd ) ) {
if ( size < 4 ) {
return ;
}
s = * ( uint8_t * ) data ;
if ( s > = QEMU_CLIPBOARD_SELECTION__COUNT ) {
return ;
}
data + = 4 ;
size - = 4 ;
}
switch ( msg - > type ) {
case VD_AGENT_CLIPBOARD_GRAB :
trace_vdagent_cb_grab_selection ( GET_NAME ( sel_name , s ) ) ;
info = 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 > = sizeof ( uint32_t ) ) {
trace_vdagent_cb_grab_type ( GET_NAME ( type_name , * ( uint32_t * ) data ) ) ;
switch ( * ( uint32_t * ) data ) {
case VD_AGENT_CLIPBOARD_UTF8_TEXT :
info - > types [ QEMU_CLIPBOARD_TYPE_TEXT ] . available = true ;
break ;
default :
break ;
}
data + = sizeof ( uint32_t ) ;
size - = 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 = QEMU_CLIPBOARD_TYPE_TEXT ;
break ;
default :
return ;
}
if ( vd - > cbinfo [ s ] & &
vd - > cbinfo [ s ] - > types [ type ] . available & &
vd - > cbinfo [ s ] - > owner ! = & vd - > cbpeer ) {
if ( vd - > cbinfo [ s ] - > types [ type ] . data ) {
vdagent_send_clipboard_data ( vd , vd - > cbinfo [ s ] , type ) ;
} else {
vd - > cbpending [ s ] | = ( 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 = QEMU_CLIPBOARD_TYPE_TEXT ;
break ;
default :
return ;
}
data + = 4 ;
size - = 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 = = & vd - > cbpeer ) {
/* set empty clipboard info */
info = qemu_clipboard_info_new ( NULL , s ) ;
qemu_clipboard_update ( info ) ;
qemu_clipboard_info_unref ( info ) ;
}
break ;
}
}
/* ------------------------------------------------------------------ */
/* chardev backend */
@ -286,6 +554,11 @@ static void vdagent_chr_open(Chardev *chr,
vd - > mouse = cfg - > mouse ;
}
vd - > clipboard = VDAGENT_CLIPBOARD_DEFAULT ;
if ( cfg - > has_clipboard ) {
vd - > clipboard = cfg - > clipboard ;
}
if ( vd - > mouse ) {
vd - > mouse_hs = 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 = = NULL ) {
vd - > cbpeer . name = " vdagent " ;
vd - > cbpeer . update . notify = vdagent_clipboard_notify ;
vd - > cbpeer . request = vdagent_clipboard_request ;
qemu_clipboard_peer_register ( & vd - > cbpeer ) ;
}
}
static void vdagent_chr_recv_msg ( VDAgentChardev * vd , VDAgentMessage * msg )
@ -327,6 +606,14 @@ static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *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 *chr, 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 ;
}
@ -464,6 +755,8 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
qemu_chr_parse_common ( opts , qapi_ChardevQemuVDAgent_base ( cfg ) ) ;
cfg - > has_mouse = true ;
cfg - > mouse = qemu_opt_get_bool ( opts , " mouse " , VDAGENT_MOUSE_DEFAULT ) ;
cfg - > has_clipboard = true ;
cfg - > clipboard = qemu_opt_get_bool ( opts , " clipboard " , VDAGENT_CLIPBOARD_DEFAULT ) ;
}
/* ------------------------------------------------------------------ */