Merge branch 'spice.v23.pull' of git://anongit.freedesktop.org/spice/qemu
* 'spice.v23.pull' of git://anongit.freedesktop.org/spice/qemu: vnc/spice: add set_passwd monitor command. vnc: support password expire vnc: auth reject cleanup spice: add qmp 'query-spice' and hmp 'info spice' commands. spice: connection events. spice: add qxl device spice: add qxl vgabios binary.
This commit is contained in:
commit
818c2e1b97
2
Makefile
2
Makefile
@ -205,7 +205,7 @@ common de-ch es fo fr-ca hu ja mk nl-be pt sl tr
|
|||||||
|
|
||||||
ifdef INSTALL_BLOBS
|
ifdef INSTALL_BLOBS
|
||||||
BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin \
|
BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin \
|
||||||
vgabios-stdvga.bin vgabios-vmware.bin \
|
vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin \
|
||||||
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc \
|
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc \
|
||||||
gpxe-eepro100-80861209.rom \
|
gpxe-eepro100-80861209.rom \
|
||||||
pxe-e1000.bin \
|
pxe-e1000.bin \
|
||||||
|
@ -217,6 +217,7 @@ obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o
|
|||||||
obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
|
obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
|
||||||
obj-i386-y += debugcon.o multiboot.o
|
obj-i386-y += debugcon.o multiboot.o
|
||||||
obj-i386-y += pc_piix.o
|
obj-i386-y += pc_piix.o
|
||||||
|
obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
|
||||||
|
|
||||||
# shared objects
|
# shared objects
|
||||||
obj-ppc-y = ppc.o
|
obj-ppc-y = ppc.o
|
||||||
|
@ -182,6 +182,70 @@ Example:
|
|||||||
"host": "127.0.0.1", "sasl_username": "luiz" } },
|
"host": "127.0.0.1", "sasl_username": "luiz" } },
|
||||||
"timestamp": { "seconds": 1263475302, "microseconds": 150772 } }
|
"timestamp": { "seconds": 1263475302, "microseconds": 150772 } }
|
||||||
|
|
||||||
|
SPICE_CONNECTED, SPICE_DISCONNECTED
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
Emitted when a SPICE client connects or disconnects.
|
||||||
|
|
||||||
|
Data:
|
||||||
|
|
||||||
|
- "server": Server information (json-object)
|
||||||
|
- "host": IP address (json-string)
|
||||||
|
- "port": port number (json-string)
|
||||||
|
- "family": address family (json-string, "ipv4" or "ipv6")
|
||||||
|
- "client": Client information (json-object)
|
||||||
|
- "host": IP address (json-string)
|
||||||
|
- "port": port number (json-string)
|
||||||
|
- "family": address family (json-string, "ipv4" or "ipv6")
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{ "timestamp": {"seconds": 1290688046, "microseconds": 388707},
|
||||||
|
"event": "SPICE_CONNECTED",
|
||||||
|
"data": {
|
||||||
|
"server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"},
|
||||||
|
"client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"}
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
SPICE_INITIALIZED
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Emitted after initial handshake and authentication takes place (if any)
|
||||||
|
and the SPICE channel is up'n'running
|
||||||
|
|
||||||
|
Data:
|
||||||
|
|
||||||
|
- "server": Server information (json-object)
|
||||||
|
- "host": IP address (json-string)
|
||||||
|
- "port": port number (json-string)
|
||||||
|
- "family": address family (json-string, "ipv4" or "ipv6")
|
||||||
|
- "auth": authentication method (json-string, optional)
|
||||||
|
- "client": Client information (json-object)
|
||||||
|
- "host": IP address (json-string)
|
||||||
|
- "port": port number (json-string)
|
||||||
|
- "family": address family (json-string, "ipv4" or "ipv6")
|
||||||
|
- "connection-id": spice connection id. All channels with the same id
|
||||||
|
belong to the same spice session (json-int)
|
||||||
|
- "channel-type": channel type. "1" is the main control channel, filter for
|
||||||
|
this one if you want track spice sessions only (json-int)
|
||||||
|
- "channel-id": channel id. Usually "0", might be different needed when
|
||||||
|
multiple channels of the same type exist, such as multiple
|
||||||
|
display channels in a multihead setup (json-int)
|
||||||
|
- "tls": whevener the channel is encrypted (json-bool)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{ "timestamp": {"seconds": 1290688046, "microseconds": 417172},
|
||||||
|
"event": "SPICE_INITIALIZED",
|
||||||
|
"data": {"server": {"auth": "spice", "port": "5921",
|
||||||
|
"family": "ipv4", "host": "127.0.0.1"},
|
||||||
|
"client": {"port": "49004", "family": "ipv4", "channel-type": 3,
|
||||||
|
"connection-id": 1804289383, "host": "127.0.0.1",
|
||||||
|
"channel-id": 0, "tls": true}
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
WATCHDOG
|
WATCHDOG
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -369,6 +369,7 @@ void vnc_display_init(DisplayState *ds);
|
|||||||
void vnc_display_close(DisplayState *ds);
|
void vnc_display_close(DisplayState *ds);
|
||||||
int vnc_display_open(DisplayState *ds, const char *display);
|
int vnc_display_open(DisplayState *ds, const char *display);
|
||||||
int vnc_display_password(DisplayState *ds, const char *password);
|
int vnc_display_password(DisplayState *ds, const char *password);
|
||||||
|
int vnc_display_pw_expire(DisplayState *ds, time_t expires);
|
||||||
void do_info_vnc_print(Monitor *mon, const QObject *data);
|
void do_info_vnc_print(Monitor *mon, const QObject *data);
|
||||||
void do_info_vnc(Monitor *mon, QObject **ret_data);
|
void do_info_vnc(Monitor *mon, QObject **ret_data);
|
||||||
char *vnc_display_local_addr(DisplayState *ds);
|
char *vnc_display_local_addr(DisplayState *ds);
|
||||||
|
@ -1151,6 +1151,60 @@ STEXI
|
|||||||
@item block_passwd @var{device} @var{password}
|
@item block_passwd @var{device} @var{password}
|
||||||
@findex block_passwd
|
@findex block_passwd
|
||||||
Set the encrypted device @var{device} password to @var{password}
|
Set the encrypted device @var{device} password to @var{password}
|
||||||
|
ETEXI
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "set_password",
|
||||||
|
.args_type = "protocol:s,password:s,connected:s?",
|
||||||
|
.params = "protocol password action-if-connected",
|
||||||
|
.help = "set spice/vnc password",
|
||||||
|
.user_print = monitor_user_noop,
|
||||||
|
.mhandler.cmd_new = set_password,
|
||||||
|
},
|
||||||
|
|
||||||
|
STEXI
|
||||||
|
@item set_password [ vnc | spice ] password [ action-if-connected ]
|
||||||
|
@findex set_password
|
||||||
|
|
||||||
|
Change spice/vnc password. Use zero to make the password stay valid
|
||||||
|
forever. @var{action-if-connected} specifies what should happen in
|
||||||
|
case a connection is established: @var{fail} makes the password change
|
||||||
|
fail. @var{disconnect} changes the password and disconnects the
|
||||||
|
client. @var{keep} changes the password and keeps the connection up.
|
||||||
|
@var{keep} is the default.
|
||||||
|
ETEXI
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "expire_password",
|
||||||
|
.args_type = "protocol:s,time:s",
|
||||||
|
.params = "protocol time",
|
||||||
|
.help = "set spice/vnc password expire-time",
|
||||||
|
.user_print = monitor_user_noop,
|
||||||
|
.mhandler.cmd_new = expire_password,
|
||||||
|
},
|
||||||
|
|
||||||
|
STEXI
|
||||||
|
@item expire_password [ vnc | spice ] expire-time
|
||||||
|
@findex expire_password
|
||||||
|
|
||||||
|
Specify when a password for spice/vnc becomes
|
||||||
|
invalid. @var{expire-time} accepts:
|
||||||
|
|
||||||
|
@table @var
|
||||||
|
@item now
|
||||||
|
Invalidate password instantly.
|
||||||
|
|
||||||
|
@item never
|
||||||
|
Password stays valid forever.
|
||||||
|
|
||||||
|
@item +nsec
|
||||||
|
Password stays valid for @var{nsec} seconds starting now.
|
||||||
|
|
||||||
|
@item nsec
|
||||||
|
Password is invalidated at the given time. @var{nsec} are the seconds
|
||||||
|
passed since 1970, i.e. unix epoch.
|
||||||
|
|
||||||
|
@end table
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
{
|
{
|
||||||
|
14
hw/hw.h
14
hw/hw.h
@ -528,6 +528,17 @@ extern const VMStateInfo vmstate_info_unused_buffer;
|
|||||||
.start = (_start), \
|
.start = (_start), \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \
|
||||||
|
.name = (stringify(_field)), \
|
||||||
|
.version_id = (_version), \
|
||||||
|
.field_exists = (_test), \
|
||||||
|
.size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\
|
||||||
|
.info = &vmstate_info_buffer, \
|
||||||
|
.flags = VMS_VBUFFER|VMS_POINTER, \
|
||||||
|
.offset = offsetof(_state, _field), \
|
||||||
|
.start = (_start), \
|
||||||
|
}
|
||||||
|
|
||||||
#define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \
|
#define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \
|
||||||
.name = (stringify(_field)), \
|
.name = (stringify(_field)), \
|
||||||
.version_id = (_version), \
|
.version_id = (_version), \
|
||||||
@ -745,6 +756,9 @@ extern const VMStateDescription vmstate_i2c_slave;
|
|||||||
#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \
|
#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \
|
||||||
VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size)
|
VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size)
|
||||||
|
|
||||||
|
#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \
|
||||||
|
VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size)
|
||||||
|
|
||||||
#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \
|
#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \
|
||||||
VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size)
|
VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size)
|
||||||
|
|
||||||
|
8
hw/pc.c
8
hw/pc.c
@ -40,6 +40,7 @@
|
|||||||
#include "sysbus.h"
|
#include "sysbus.h"
|
||||||
#include "sysemu.h"
|
#include "sysemu.h"
|
||||||
#include "blockdev.h"
|
#include "blockdev.h"
|
||||||
|
#include "ui/qemu-spice.h"
|
||||||
|
|
||||||
/* output Bochs bios info messages */
|
/* output Bochs bios info messages */
|
||||||
//#define DEBUG_BIOS
|
//#define DEBUG_BIOS
|
||||||
@ -992,6 +993,13 @@ void pc_vga_init(PCIBus *pci_bus)
|
|||||||
pci_vmsvga_init(pci_bus);
|
pci_vmsvga_init(pci_bus);
|
||||||
else
|
else
|
||||||
fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__);
|
fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__);
|
||||||
|
#ifdef CONFIG_SPICE
|
||||||
|
} else if (qxl_enabled) {
|
||||||
|
if (pci_bus)
|
||||||
|
pci_create_simple(pci_bus, -1, "qxl-vga");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s: qxl: no PCI bus\n", __FUNCTION__);
|
||||||
|
#endif
|
||||||
} else if (std_vga_enabled) {
|
} else if (std_vga_enabled) {
|
||||||
if (pci_bus) {
|
if (pci_bus) {
|
||||||
pci_vga_init(pci_bus);
|
pci_vga_init(pci_bus);
|
||||||
|
248
hw/qxl-logger.c
Normal file
248
hw/qxl-logger.c
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* qxl command logging -- for debug purposes
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* maintained by Gerd Hoffmann <kraxel@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 or
|
||||||
|
* (at your option) version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qxl.h"
|
||||||
|
|
||||||
|
static const char *qxl_type[] = {
|
||||||
|
[ QXL_CMD_NOP ] = "nop",
|
||||||
|
[ QXL_CMD_DRAW ] = "draw",
|
||||||
|
[ QXL_CMD_UPDATE ] = "update",
|
||||||
|
[ QXL_CMD_CURSOR ] = "cursor",
|
||||||
|
[ QXL_CMD_MESSAGE ] = "message",
|
||||||
|
[ QXL_CMD_SURFACE ] = "surface",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *qxl_draw_type[] = {
|
||||||
|
[ QXL_DRAW_NOP ] = "nop",
|
||||||
|
[ QXL_DRAW_FILL ] = "fill",
|
||||||
|
[ QXL_DRAW_OPAQUE ] = "opaque",
|
||||||
|
[ QXL_DRAW_COPY ] = "copy",
|
||||||
|
[ QXL_COPY_BITS ] = "copy-bits",
|
||||||
|
[ QXL_DRAW_BLEND ] = "blend",
|
||||||
|
[ QXL_DRAW_BLACKNESS ] = "blackness",
|
||||||
|
[ QXL_DRAW_WHITENESS ] = "whitemess",
|
||||||
|
[ QXL_DRAW_INVERS ] = "invers",
|
||||||
|
[ QXL_DRAW_ROP3 ] = "rop3",
|
||||||
|
[ QXL_DRAW_STROKE ] = "stroke",
|
||||||
|
[ QXL_DRAW_TEXT ] = "text",
|
||||||
|
[ QXL_DRAW_TRANSPARENT ] = "transparent",
|
||||||
|
[ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *qxl_draw_effect[] = {
|
||||||
|
[ QXL_EFFECT_BLEND ] = "blend",
|
||||||
|
[ QXL_EFFECT_OPAQUE ] = "opaque",
|
||||||
|
[ QXL_EFFECT_REVERT_ON_DUP ] = "revert-on-dup",
|
||||||
|
[ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup",
|
||||||
|
[ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup",
|
||||||
|
[ QXL_EFFECT_NOP_ON_DUP ] = "nop-on-dup",
|
||||||
|
[ QXL_EFFECT_NOP ] = "nop",
|
||||||
|
[ QXL_EFFECT_OPAQUE_BRUSH ] = "opaque-brush",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *qxl_surface_cmd[] = {
|
||||||
|
[ QXL_SURFACE_CMD_CREATE ] = "create",
|
||||||
|
[ QXL_SURFACE_CMD_DESTROY ] = "destroy",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *spice_surface_fmt[] = {
|
||||||
|
[ SPICE_SURFACE_FMT_INVALID ] = "invalid",
|
||||||
|
[ SPICE_SURFACE_FMT_1_A ] = "alpha/1",
|
||||||
|
[ SPICE_SURFACE_FMT_8_A ] = "alpha/8",
|
||||||
|
[ SPICE_SURFACE_FMT_16_555 ] = "555/16",
|
||||||
|
[ SPICE_SURFACE_FMT_16_565 ] = "565/16",
|
||||||
|
[ SPICE_SURFACE_FMT_32_xRGB ] = "xRGB/32",
|
||||||
|
[ SPICE_SURFACE_FMT_32_ARGB ] = "ARGB/32",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *qxl_cursor_cmd[] = {
|
||||||
|
[ QXL_CURSOR_SET ] = "set",
|
||||||
|
[ QXL_CURSOR_MOVE ] = "move",
|
||||||
|
[ QXL_CURSOR_HIDE ] = "hide",
|
||||||
|
[ QXL_CURSOR_TRAIL ] = "trail",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *spice_cursor_type[] = {
|
||||||
|
[ SPICE_CURSOR_TYPE_ALPHA ] = "alpha",
|
||||||
|
[ SPICE_CURSOR_TYPE_MONO ] = "mono",
|
||||||
|
[ SPICE_CURSOR_TYPE_COLOR4 ] = "color4",
|
||||||
|
[ SPICE_CURSOR_TYPE_COLOR8 ] = "color8",
|
||||||
|
[ SPICE_CURSOR_TYPE_COLOR16 ] = "color16",
|
||||||
|
[ SPICE_CURSOR_TYPE_COLOR24 ] = "color24",
|
||||||
|
[ SPICE_CURSOR_TYPE_COLOR32 ] = "color32",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *qxl_v2n(const char *n[], size_t l, int v)
|
||||||
|
{
|
||||||
|
if (v >= l || !n[v]) {
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
return n[v];
|
||||||
|
}
|
||||||
|
#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value)
|
||||||
|
|
||||||
|
static void qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id)
|
||||||
|
{
|
||||||
|
QXLImage *image;
|
||||||
|
QXLImageDescriptor *desc;
|
||||||
|
|
||||||
|
image = qxl_phys2virt(qxl, addr, group_id);
|
||||||
|
desc = &image->descriptor;
|
||||||
|
fprintf(stderr, " (id %" PRIx64 " type %d flags %d width %d height %d",
|
||||||
|
desc->id, desc->type, desc->flags, desc->width, desc->height);
|
||||||
|
switch (desc->type) {
|
||||||
|
case SPICE_IMAGE_TYPE_BITMAP:
|
||||||
|
fprintf(stderr, ", fmt %d flags %d x %d y %d stride %d"
|
||||||
|
" palette %" PRIx64 " data %" PRIx64,
|
||||||
|
image->bitmap.format, image->bitmap.flags,
|
||||||
|
image->bitmap.x, image->bitmap.y,
|
||||||
|
image->bitmap.stride,
|
||||||
|
image->bitmap.palette, image->bitmap.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fprintf(stderr, ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qxl_log_rect(QXLRect *rect)
|
||||||
|
{
|
||||||
|
fprintf(stderr, " %dx%d+%d+%d",
|
||||||
|
rect->right - rect->left,
|
||||||
|
rect->bottom - rect->top,
|
||||||
|
rect->left, rect->top);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy, int group_id)
|
||||||
|
{
|
||||||
|
fprintf(stderr, " src %" PRIx64,
|
||||||
|
copy->src_bitmap);
|
||||||
|
qxl_log_image(qxl, copy->src_bitmap, group_id);
|
||||||
|
fprintf(stderr, " area");
|
||||||
|
qxl_log_rect(©->src_area);
|
||||||
|
fprintf(stderr, " rop %d", copy->rop_descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw, int group_id)
|
||||||
|
{
|
||||||
|
fprintf(stderr, ": surface_id %d type %s effect %s",
|
||||||
|
draw->surface_id,
|
||||||
|
qxl_name(qxl_draw_type, draw->type),
|
||||||
|
qxl_name(qxl_draw_effect, draw->effect));
|
||||||
|
switch (draw->type) {
|
||||||
|
case QXL_DRAW_COPY:
|
||||||
|
qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw,
|
||||||
|
int group_id)
|
||||||
|
{
|
||||||
|
fprintf(stderr, ": type %s effect %s",
|
||||||
|
qxl_name(qxl_draw_type, draw->type),
|
||||||
|
qxl_name(qxl_draw_effect, draw->effect));
|
||||||
|
if (draw->bitmap_offset) {
|
||||||
|
fprintf(stderr, ": bitmap %d",
|
||||||
|
draw->bitmap_offset);
|
||||||
|
qxl_log_rect(&draw->bitmap_area);
|
||||||
|
}
|
||||||
|
switch (draw->type) {
|
||||||
|
case QXL_DRAW_COPY:
|
||||||
|
qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd)
|
||||||
|
{
|
||||||
|
fprintf(stderr, ": %s id %d",
|
||||||
|
qxl_name(qxl_surface_cmd, cmd->type),
|
||||||
|
cmd->surface_id);
|
||||||
|
if (cmd->type == QXL_SURFACE_CMD_CREATE) {
|
||||||
|
fprintf(stderr, " size %dx%d stride %d format %s (count %d, max %d)",
|
||||||
|
cmd->u.surface_create.width,
|
||||||
|
cmd->u.surface_create.height,
|
||||||
|
cmd->u.surface_create.stride,
|
||||||
|
qxl_name(spice_surface_fmt, cmd->u.surface_create.format),
|
||||||
|
qxl->guest_surfaces.count, qxl->guest_surfaces.max);
|
||||||
|
}
|
||||||
|
if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
|
||||||
|
fprintf(stderr, " (count %d)", qxl->guest_surfaces.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id)
|
||||||
|
{
|
||||||
|
QXLCursor *cursor;
|
||||||
|
|
||||||
|
fprintf(stderr, ": %s",
|
||||||
|
qxl_name(qxl_cursor_cmd, cmd->type));
|
||||||
|
switch (cmd->type) {
|
||||||
|
case QXL_CURSOR_SET:
|
||||||
|
fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64,
|
||||||
|
cmd->u.set.position.x,
|
||||||
|
cmd->u.set.position.y,
|
||||||
|
cmd->u.set.visible ? "yes" : "no",
|
||||||
|
cmd->u.set.shape);
|
||||||
|
cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id);
|
||||||
|
fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d"
|
||||||
|
" unique 0x%" PRIx64 " data-size %d",
|
||||||
|
qxl_name(spice_cursor_type, cursor->header.type),
|
||||||
|
cursor->header.width, cursor->header.height,
|
||||||
|
cursor->header.hot_spot_x, cursor->header.hot_spot_y,
|
||||||
|
cursor->header.unique, cursor->data_size);
|
||||||
|
break;
|
||||||
|
case QXL_CURSOR_MOVE:
|
||||||
|
fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext)
|
||||||
|
{
|
||||||
|
bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT;
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
if (!qxl->cmdlog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "qxl-%d/%s:", qxl->id, ring);
|
||||||
|
fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data,
|
||||||
|
qxl_name(qxl_type, ext->cmd.type),
|
||||||
|
compat ? "(compat)" : "");
|
||||||
|
|
||||||
|
data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
|
||||||
|
switch (ext->cmd.type) {
|
||||||
|
case QXL_CMD_DRAW:
|
||||||
|
if (!compat) {
|
||||||
|
qxl_log_cmd_draw(qxl, data, ext->group_id);
|
||||||
|
} else {
|
||||||
|
qxl_log_cmd_draw_compat(qxl, data, ext->group_id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QXL_CMD_SURFACE:
|
||||||
|
qxl_log_cmd_surface(qxl, data);
|
||||||
|
break;
|
||||||
|
case QXL_CMD_CURSOR:
|
||||||
|
qxl_log_cmd_cursor(qxl, data, ext->group_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
226
hw/qxl-render.c
Normal file
226
hw/qxl-render.c
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* qxl local rendering (aka display on sdl/vnc)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* maintained by Gerd Hoffmann <kraxel@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 or
|
||||||
|
* (at your option) version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qxl.h"
|
||||||
|
|
||||||
|
static void qxl_flip(PCIQXLDevice *qxl, QXLRect *rect)
|
||||||
|
{
|
||||||
|
uint8_t *src = qxl->guest_primary.data;
|
||||||
|
uint8_t *dst = qxl->guest_primary.flipped;
|
||||||
|
int len, i;
|
||||||
|
|
||||||
|
src += (qxl->guest_primary.surface.height - rect->top - 1) *
|
||||||
|
qxl->guest_primary.stride;
|
||||||
|
dst += rect->top * qxl->guest_primary.stride;
|
||||||
|
src += rect->left * qxl->guest_primary.bytes_pp;
|
||||||
|
dst += rect->left * qxl->guest_primary.bytes_pp;
|
||||||
|
len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp;
|
||||||
|
|
||||||
|
for (i = rect->top; i < rect->bottom; i++) {
|
||||||
|
memcpy(dst, src, len);
|
||||||
|
dst += qxl->guest_primary.stride;
|
||||||
|
src -= qxl->guest_primary.stride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qxl_render_resize(PCIQXLDevice *qxl)
|
||||||
|
{
|
||||||
|
QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
|
||||||
|
|
||||||
|
qxl->guest_primary.stride = sc->stride;
|
||||||
|
qxl->guest_primary.resized++;
|
||||||
|
switch (sc->format) {
|
||||||
|
case SPICE_SURFACE_FMT_16_555:
|
||||||
|
qxl->guest_primary.bytes_pp = 2;
|
||||||
|
qxl->guest_primary.bits_pp = 15;
|
||||||
|
break;
|
||||||
|
case SPICE_SURFACE_FMT_16_565:
|
||||||
|
qxl->guest_primary.bytes_pp = 2;
|
||||||
|
qxl->guest_primary.bits_pp = 16;
|
||||||
|
break;
|
||||||
|
case SPICE_SURFACE_FMT_32_xRGB:
|
||||||
|
case SPICE_SURFACE_FMT_32_ARGB:
|
||||||
|
qxl->guest_primary.bytes_pp = 4;
|
||||||
|
qxl->guest_primary.bits_pp = 32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: unhandled format: %x\n", __FUNCTION__,
|
||||||
|
qxl->guest_primary.surface.format);
|
||||||
|
qxl->guest_primary.bytes_pp = 4;
|
||||||
|
qxl->guest_primary.bits_pp = 32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qxl_render_update(PCIQXLDevice *qxl)
|
||||||
|
{
|
||||||
|
VGACommonState *vga = &qxl->vga;
|
||||||
|
QXLRect dirty[32], update;
|
||||||
|
void *ptr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (qxl->guest_primary.resized) {
|
||||||
|
qxl->guest_primary.resized = 0;
|
||||||
|
|
||||||
|
if (qxl->guest_primary.flipped) {
|
||||||
|
qemu_free(qxl->guest_primary.flipped);
|
||||||
|
qxl->guest_primary.flipped = NULL;
|
||||||
|
}
|
||||||
|
qemu_free_displaysurface(vga->ds);
|
||||||
|
|
||||||
|
qxl->guest_primary.data = qemu_get_ram_ptr(qxl->vga.vram_offset);
|
||||||
|
if (qxl->guest_primary.stride < 0) {
|
||||||
|
/* spice surface is upside down -> need extra buffer to flip */
|
||||||
|
qxl->guest_primary.stride = -qxl->guest_primary.stride;
|
||||||
|
qxl->guest_primary.flipped = qemu_malloc(qxl->guest_primary.surface.width *
|
||||||
|
qxl->guest_primary.stride);
|
||||||
|
ptr = qxl->guest_primary.flipped;
|
||||||
|
} else {
|
||||||
|
ptr = qxl->guest_primary.data;
|
||||||
|
}
|
||||||
|
dprint(qxl, 1, "%s: %dx%d, stride %d, bpp %d, depth %d, flip %s\n",
|
||||||
|
__FUNCTION__,
|
||||||
|
qxl->guest_primary.surface.width,
|
||||||
|
qxl->guest_primary.surface.height,
|
||||||
|
qxl->guest_primary.stride,
|
||||||
|
qxl->guest_primary.bytes_pp,
|
||||||
|
qxl->guest_primary.bits_pp,
|
||||||
|
qxl->guest_primary.flipped ? "yes" : "no");
|
||||||
|
vga->ds->surface =
|
||||||
|
qemu_create_displaysurface_from(qxl->guest_primary.surface.width,
|
||||||
|
qxl->guest_primary.surface.height,
|
||||||
|
qxl->guest_primary.bits_pp,
|
||||||
|
qxl->guest_primary.stride,
|
||||||
|
ptr);
|
||||||
|
dpy_resize(vga->ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qxl->guest_primary.commands) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qxl->guest_primary.commands = 0;
|
||||||
|
|
||||||
|
update.left = 0;
|
||||||
|
update.right = qxl->guest_primary.surface.width;
|
||||||
|
update.top = 0;
|
||||||
|
update.bottom = qxl->guest_primary.surface.height;
|
||||||
|
|
||||||
|
memset(dirty, 0, sizeof(dirty));
|
||||||
|
qxl->ssd.worker->update_area(qxl->ssd.worker, 0, &update,
|
||||||
|
dirty, ARRAY_SIZE(dirty), 1);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(dirty); i++) {
|
||||||
|
if (qemu_spice_rect_is_empty(dirty+i)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (qxl->guest_primary.flipped) {
|
||||||
|
qxl_flip(qxl, dirty+i);
|
||||||
|
}
|
||||||
|
dpy_update(vga->ds,
|
||||||
|
dirty[i].left, dirty[i].top,
|
||||||
|
dirty[i].right - dirty[i].left,
|
||||||
|
dirty[i].bottom - dirty[i].top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor)
|
||||||
|
{
|
||||||
|
QEMUCursor *c;
|
||||||
|
uint8_t *image, *mask;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
c = cursor_alloc(cursor->header.width, cursor->header.height);
|
||||||
|
c->hot_x = cursor->header.hot_spot_x;
|
||||||
|
c->hot_y = cursor->header.hot_spot_y;
|
||||||
|
switch (cursor->header.type) {
|
||||||
|
case SPICE_CURSOR_TYPE_ALPHA:
|
||||||
|
size = cursor->header.width * cursor->header.height * sizeof(uint32_t);
|
||||||
|
memcpy(c->data, cursor->chunk.data, size);
|
||||||
|
if (qxl->debug > 2) {
|
||||||
|
cursor_print_ascii_art(c, "qxl/alpha");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPICE_CURSOR_TYPE_MONO:
|
||||||
|
mask = cursor->chunk.data;
|
||||||
|
image = mask + cursor_get_mono_bpl(c) * c->width;
|
||||||
|
cursor_set_mono(c, 0xffffff, 0x000000, image, 1, mask);
|
||||||
|
if (qxl->debug > 2) {
|
||||||
|
cursor_print_ascii_art(c, "qxl/mono");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s: not implemented: type %d\n",
|
||||||
|
__FUNCTION__, cursor->header.type);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
cursor_put(c);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* called from spice server thread context only */
|
||||||
|
void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
|
||||||
|
{
|
||||||
|
QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
|
||||||
|
QXLCursor *cursor;
|
||||||
|
QEMUCursor *c;
|
||||||
|
int x = -1, y = -1;
|
||||||
|
|
||||||
|
if (!qxl->ssd.ds->mouse_set || !qxl->ssd.ds->cursor_define) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qxl->debug > 1 && cmd->type != QXL_CURSOR_MOVE) {
|
||||||
|
fprintf(stderr, "%s", __FUNCTION__);
|
||||||
|
qxl_log_cmd_cursor(qxl, cmd, ext->group_id);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
switch (cmd->type) {
|
||||||
|
case QXL_CURSOR_SET:
|
||||||
|
x = cmd->u.set.position.x;
|
||||||
|
y = cmd->u.set.position.y;
|
||||||
|
cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id);
|
||||||
|
if (cursor->chunk.data_size != cursor->data_size) {
|
||||||
|
fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c = qxl_cursor(qxl, cursor);
|
||||||
|
if (c == NULL) {
|
||||||
|
c = cursor_builtin_left_ptr();
|
||||||
|
}
|
||||||
|
qemu_mutex_lock_iothread();
|
||||||
|
qxl->ssd.ds->cursor_define(c);
|
||||||
|
qxl->ssd.ds->mouse_set(x, y, 1);
|
||||||
|
qemu_mutex_unlock_iothread();
|
||||||
|
cursor_put(c);
|
||||||
|
break;
|
||||||
|
case QXL_CURSOR_MOVE:
|
||||||
|
x = cmd->u.position.x;
|
||||||
|
y = cmd->u.position.y;
|
||||||
|
qemu_mutex_lock_iothread();
|
||||||
|
qxl->ssd.ds->mouse_set(x, y, 1);
|
||||||
|
qemu_mutex_unlock_iothread();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
112
hw/qxl.h
Normal file
112
hw/qxl.h
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include "qemu-common.h"
|
||||||
|
|
||||||
|
#include "console.h"
|
||||||
|
#include "hw.h"
|
||||||
|
#include "pci.h"
|
||||||
|
#include "vga_int.h"
|
||||||
|
|
||||||
|
#include "ui/qemu-spice.h"
|
||||||
|
#include "ui/spice-display.h"
|
||||||
|
|
||||||
|
enum qxl_mode {
|
||||||
|
QXL_MODE_UNDEFINED,
|
||||||
|
QXL_MODE_VGA,
|
||||||
|
QXL_MODE_COMPAT, /* spice 0.4.x */
|
||||||
|
QXL_MODE_NATIVE,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct PCIQXLDevice {
|
||||||
|
PCIDevice pci;
|
||||||
|
SimpleSpiceDisplay ssd;
|
||||||
|
int id;
|
||||||
|
uint32_t debug;
|
||||||
|
uint32_t guestdebug;
|
||||||
|
uint32_t cmdlog;
|
||||||
|
enum qxl_mode mode;
|
||||||
|
uint32_t cmdflags;
|
||||||
|
int generation;
|
||||||
|
uint32_t revision;
|
||||||
|
|
||||||
|
int32_t num_memslots;
|
||||||
|
int32_t num_surfaces;
|
||||||
|
|
||||||
|
struct guest_slots {
|
||||||
|
QXLMemSlot slot;
|
||||||
|
void *ptr;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t delta;
|
||||||
|
uint32_t active;
|
||||||
|
} guest_slots[NUM_MEMSLOTS];
|
||||||
|
|
||||||
|
struct guest_primary {
|
||||||
|
QXLSurfaceCreate surface;
|
||||||
|
uint32_t commands;
|
||||||
|
uint32_t resized;
|
||||||
|
int32_t stride;
|
||||||
|
uint32_t bits_pp;
|
||||||
|
uint32_t bytes_pp;
|
||||||
|
uint8_t *data, *flipped;
|
||||||
|
} guest_primary;
|
||||||
|
|
||||||
|
struct surfaces {
|
||||||
|
QXLPHYSICAL cmds[NUM_SURFACES];
|
||||||
|
uint32_t count;
|
||||||
|
uint32_t max;
|
||||||
|
} guest_surfaces;
|
||||||
|
QXLPHYSICAL guest_cursor;
|
||||||
|
|
||||||
|
/* thread signaling */
|
||||||
|
pthread_t main;
|
||||||
|
int pipe[2];
|
||||||
|
|
||||||
|
/* ram pci bar */
|
||||||
|
QXLRam *ram;
|
||||||
|
VGACommonState vga;
|
||||||
|
uint32_t num_free_res;
|
||||||
|
QXLReleaseInfo *last_release;
|
||||||
|
uint32_t last_release_offset;
|
||||||
|
uint32_t oom_running;
|
||||||
|
|
||||||
|
/* rom pci bar */
|
||||||
|
QXLRom shadow_rom;
|
||||||
|
QXLRom *rom;
|
||||||
|
QXLModes *modes;
|
||||||
|
uint32_t rom_size;
|
||||||
|
uint64_t rom_offset;
|
||||||
|
|
||||||
|
/* vram pci bar */
|
||||||
|
uint32_t vram_size;
|
||||||
|
uint64_t vram_offset;
|
||||||
|
|
||||||
|
/* io bar */
|
||||||
|
uint32_t io_base;
|
||||||
|
|
||||||
|
/* spice 0.4 loadvm compatibility */
|
||||||
|
void *worker_data;
|
||||||
|
uint32_t worker_data_size;
|
||||||
|
} PCIQXLDevice;
|
||||||
|
|
||||||
|
#define PANIC_ON(x) if ((x)) { \
|
||||||
|
printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \
|
||||||
|
exit(-1); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define dprint(_qxl, _level, _fmt, ...) \
|
||||||
|
do { \
|
||||||
|
if (_qxl->debug >= _level) { \
|
||||||
|
fprintf(stderr, "qxl-%d: ", _qxl->id); \
|
||||||
|
fprintf(stderr, _fmt, ## __VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* qxl.c */
|
||||||
|
void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id);
|
||||||
|
|
||||||
|
/* qxl-logger.c */
|
||||||
|
void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id);
|
||||||
|
void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext);
|
||||||
|
|
||||||
|
/* qxl-render.c */
|
||||||
|
void qxl_render_resize(PCIQXLDevice *qxl);
|
||||||
|
void qxl_render_update(PCIQXLDevice *qxl);
|
||||||
|
void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext);
|
@ -106,7 +106,7 @@ typedef void (* vga_update_retrace_info_fn)(struct VGACommonState *s);
|
|||||||
typedef struct VGACommonState {
|
typedef struct VGACommonState {
|
||||||
uint8_t *vram_ptr;
|
uint8_t *vram_ptr;
|
||||||
ram_addr_t vram_offset;
|
ram_addr_t vram_offset;
|
||||||
unsigned int vram_size;
|
uint32_t vram_size;
|
||||||
uint32_t lfb_addr;
|
uint32_t lfb_addr;
|
||||||
uint32_t lfb_end;
|
uint32_t lfb_end;
|
||||||
uint32_t map_addr;
|
uint32_t map_addr;
|
||||||
|
130
monitor.c
130
monitor.c
@ -34,6 +34,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "net/slirp.h"
|
#include "net/slirp.h"
|
||||||
#include "qemu-char.h"
|
#include "qemu-char.h"
|
||||||
|
#include "ui/qemu-spice.h"
|
||||||
#include "sysemu.h"
|
#include "sysemu.h"
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
#include "readline.h"
|
#include "readline.h"
|
||||||
@ -59,6 +60,7 @@
|
|||||||
#ifdef CONFIG_SIMPLE_TRACE
|
#ifdef CONFIG_SIMPLE_TRACE
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "ui/qemu-spice.h"
|
||||||
|
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
//#define DEBUG_COMPLETION
|
//#define DEBUG_COMPLETION
|
||||||
@ -457,6 +459,15 @@ void monitor_protocol_event(MonitorEvent event, QObject *data)
|
|||||||
case QEVENT_WATCHDOG:
|
case QEVENT_WATCHDOG:
|
||||||
event_name = "WATCHDOG";
|
event_name = "WATCHDOG";
|
||||||
break;
|
break;
|
||||||
|
case QEVENT_SPICE_CONNECTED:
|
||||||
|
event_name = "SPICE_CONNECTED";
|
||||||
|
break;
|
||||||
|
case QEVENT_SPICE_INITIALIZED:
|
||||||
|
event_name = "SPICE_INITIALIZED";
|
||||||
|
break;
|
||||||
|
case QEVENT_SPICE_DISCONNECTED:
|
||||||
|
event_name = "SPICE_DISCONNECTED";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
break;
|
break;
|
||||||
@ -1063,6 +1074,105 @@ static int do_change(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int set_password(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||||
|
{
|
||||||
|
const char *protocol = qdict_get_str(qdict, "protocol");
|
||||||
|
const char *password = qdict_get_str(qdict, "password");
|
||||||
|
const char *connected = qdict_get_try_str(qdict, "connected");
|
||||||
|
int disconnect_if_connected = 0;
|
||||||
|
int fail_if_connected = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
if (strcmp(connected, "fail") == 0) {
|
||||||
|
fail_if_connected = 1;
|
||||||
|
} else if (strcmp(connected, "disconnect") == 0) {
|
||||||
|
disconnect_if_connected = 1;
|
||||||
|
} else if (strcmp(connected, "keep") == 0) {
|
||||||
|
/* nothing */
|
||||||
|
} else {
|
||||||
|
qerror_report(QERR_INVALID_PARAMETER, "connected");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(protocol, "spice") == 0) {
|
||||||
|
if (!using_spice) {
|
||||||
|
/* correct one? spice isn't a device ,,, */
|
||||||
|
qerror_report(QERR_DEVICE_NOT_ACTIVE, "spice");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rc = qemu_spice_set_passwd(password, fail_if_connected,
|
||||||
|
disconnect_if_connected);
|
||||||
|
if (rc != 0) {
|
||||||
|
qerror_report(QERR_SET_PASSWD_FAILED);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(protocol, "vnc") == 0) {
|
||||||
|
if (fail_if_connected || disconnect_if_connected) {
|
||||||
|
/* vnc supports "connected=keep" only */
|
||||||
|
qerror_report(QERR_INVALID_PARAMETER, "connected");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rc = vnc_display_password(NULL, password);
|
||||||
|
if (rc != 0) {
|
||||||
|
qerror_report(QERR_SET_PASSWD_FAILED);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
qerror_report(QERR_INVALID_PARAMETER, "protocol");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int expire_password(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||||
|
{
|
||||||
|
const char *protocol = qdict_get_str(qdict, "protocol");
|
||||||
|
const char *whenstr = qdict_get_str(qdict, "time");
|
||||||
|
time_t when;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (strcmp(whenstr, "now")) {
|
||||||
|
when = 0;
|
||||||
|
} else if (strcmp(whenstr, "never")) {
|
||||||
|
when = TIME_MAX;
|
||||||
|
} else if (whenstr[0] == '+') {
|
||||||
|
when = time(NULL) + strtoull(whenstr+1, NULL, 10);
|
||||||
|
} else {
|
||||||
|
when = strtoull(whenstr, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(protocol, "spice") == 0) {
|
||||||
|
if (!using_spice) {
|
||||||
|
/* correct one? spice isn't a device ,,, */
|
||||||
|
qerror_report(QERR_DEVICE_NOT_ACTIVE, "spice");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rc = qemu_spice_set_pw_expire(when);
|
||||||
|
if (rc != 0) {
|
||||||
|
qerror_report(QERR_SET_PASSWD_FAILED);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(protocol, "vnc") == 0) {
|
||||||
|
rc = vnc_display_pw_expire(NULL, when);
|
||||||
|
if (rc != 0) {
|
||||||
|
qerror_report(QERR_SET_PASSWD_FAILED);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
qerror_report(QERR_INVALID_PARAMETER, "protocol");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int do_screen_dump(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
static int do_screen_dump(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||||
{
|
{
|
||||||
vga_hw_screen_dump(qdict_get_str(qdict, "filename"));
|
vga_hw_screen_dump(qdict_get_str(qdict, "filename"));
|
||||||
@ -2859,6 +2969,16 @@ static const mon_cmd_t info_cmds[] = {
|
|||||||
.user_print = do_info_vnc_print,
|
.user_print = do_info_vnc_print,
|
||||||
.mhandler.info_new = do_info_vnc,
|
.mhandler.info_new = do_info_vnc,
|
||||||
},
|
},
|
||||||
|
#if defined(CONFIG_SPICE)
|
||||||
|
{
|
||||||
|
.name = "spice",
|
||||||
|
.args_type = "",
|
||||||
|
.params = "",
|
||||||
|
.help = "show the spice server status",
|
||||||
|
.user_print = do_info_spice_print,
|
||||||
|
.mhandler.info_new = do_info_spice,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
.name = "name",
|
.name = "name",
|
||||||
.args_type = "",
|
.args_type = "",
|
||||||
@ -3046,6 +3166,16 @@ static const mon_cmd_t qmp_query_cmds[] = {
|
|||||||
.user_print = do_info_vnc_print,
|
.user_print = do_info_vnc_print,
|
||||||
.mhandler.info_new = do_info_vnc,
|
.mhandler.info_new = do_info_vnc,
|
||||||
},
|
},
|
||||||
|
#if defined(CONFIG_SPICE)
|
||||||
|
{
|
||||||
|
.name = "spice",
|
||||||
|
.args_type = "",
|
||||||
|
.params = "",
|
||||||
|
.help = "show the spice server status",
|
||||||
|
.user_print = do_info_spice_print,
|
||||||
|
.mhandler.info_new = do_info_spice,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
.name = "name",
|
.name = "name",
|
||||||
.args_type = "",
|
.args_type = "",
|
||||||
|
@ -32,6 +32,9 @@ typedef enum MonitorEvent {
|
|||||||
QEVENT_BLOCK_IO_ERROR,
|
QEVENT_BLOCK_IO_ERROR,
|
||||||
QEVENT_RTC_CHANGE,
|
QEVENT_RTC_CHANGE,
|
||||||
QEVENT_WATCHDOG,
|
QEVENT_WATCHDOG,
|
||||||
|
QEVENT_SPICE_CONNECTED,
|
||||||
|
QEVENT_SPICE_INITIALIZED,
|
||||||
|
QEVENT_SPICE_DISCONNECTED,
|
||||||
QEVENT_MAX,
|
QEVENT_MAX,
|
||||||
} MonitorEvent;
|
} MonitorEvent;
|
||||||
|
|
||||||
|
BIN
pc-bios/vgabios-qxl.bin
Normal file
BIN
pc-bios/vgabios-qxl.bin
Normal file
Binary file not shown.
@ -50,6 +50,9 @@ typedef struct DeviceState DeviceState;
|
|||||||
#if !defined(ENOTSUP)
|
#if !defined(ENOTSUP)
|
||||||
#define ENOTSUP 4096
|
#define ENOTSUP 4096
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef TIME_MAX
|
||||||
|
#define TIME_MAX LONG_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef CONFIG_IOVEC
|
#ifndef CONFIG_IOVEC
|
||||||
#define CONFIG_IOVEC
|
#define CONFIG_IOVEC
|
||||||
|
@ -751,7 +751,7 @@ Rotate graphical output 90 deg left (only PXA LCD).
|
|||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("vga", HAS_ARG, QEMU_OPTION_vga,
|
DEF("vga", HAS_ARG, QEMU_OPTION_vga,
|
||||||
"-vga [std|cirrus|vmware|xenfb|none]\n"
|
"-vga [std|cirrus|vmware|qxl|xenfb|none]\n"
|
||||||
" select video card type\n", QEMU_ARCH_ALL)
|
" select video card type\n", QEMU_ARCH_ALL)
|
||||||
STEXI
|
STEXI
|
||||||
@item -vga @var{type}
|
@item -vga @var{type}
|
||||||
@ -772,6 +772,10 @@ this option.
|
|||||||
VMWare SVGA-II compatible adapter. Use it if you have sufficiently
|
VMWare SVGA-II compatible adapter. Use it if you have sufficiently
|
||||||
recent XFree86/XOrg server or Windows guest with a driver for this
|
recent XFree86/XOrg server or Windows guest with a driver for this
|
||||||
card.
|
card.
|
||||||
|
@item qxl
|
||||||
|
QXL paravirtual graphic card. It is VGA compatible (including VESA
|
||||||
|
2.0 VBE support). Works best with qxl guest drivers installed though.
|
||||||
|
Recommended choice when using the spice protocol.
|
||||||
@item none
|
@item none
|
||||||
Disable VGA card.
|
Disable VGA card.
|
||||||
@end table
|
@end table
|
||||||
|
127
qmp-commands.hx
127
qmp-commands.hx
@ -735,6 +735,63 @@ Example:
|
|||||||
"password": "12345" } }
|
"password": "12345" } }
|
||||||
<- { "return": {} }
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "set_password",
|
||||||
|
.args_type = "protocol:s,password:s,connected:s?",
|
||||||
|
.params = "protocol password action-if-connected",
|
||||||
|
.help = "set spice/vnc password",
|
||||||
|
.user_print = monitor_user_noop,
|
||||||
|
.mhandler.cmd_new = set_password,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
set_password
|
||||||
|
------------
|
||||||
|
|
||||||
|
Set the password for vnc/spice protocols.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "protocol": protocol name (json-string)
|
||||||
|
- "password": password (json-string)
|
||||||
|
- "connected": [ keep | disconnect | fail ] (josn-string, optional)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "set_password", "arguments": { "protocol": "vnc",
|
||||||
|
"password": "secret" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "expire_password",
|
||||||
|
.args_type = "protocol:s,time:s",
|
||||||
|
.params = "protocol time",
|
||||||
|
.help = "set spice/vnc password expire-time",
|
||||||
|
.user_print = monitor_user_noop,
|
||||||
|
.mhandler.cmd_new = expire_password,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
expire_password
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Set the password expire time for vnc/spice protocols.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "protocol": protocol name (json-string)
|
||||||
|
- "time": [ now | never | +secs | secs ] (json-string)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "expire_password", "arguments": { "protocol": "vnc",
|
||||||
|
"time": "+60" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
EQMP
|
EQMP
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -1438,6 +1495,76 @@ Example:
|
|||||||
|
|
||||||
EQMP
|
EQMP
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
query-spice
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Show SPICE server information.
|
||||||
|
|
||||||
|
Return a json-object with server information. Connected clients are returned
|
||||||
|
as a json-array of json-objects.
|
||||||
|
|
||||||
|
The main json-object contains the following:
|
||||||
|
|
||||||
|
- "enabled": true or false (json-bool)
|
||||||
|
- "host": server's IP address (json-string)
|
||||||
|
- "port": server's port number (json-int, optional)
|
||||||
|
- "tls-port": server's port number (json-int, optional)
|
||||||
|
- "auth": authentication method (json-string)
|
||||||
|
- Possible values: "none", "spice"
|
||||||
|
- "channels": a json-array of all active channels clients
|
||||||
|
|
||||||
|
Channels are described by a json-object, each one contain the following:
|
||||||
|
|
||||||
|
- "host": client's IP address (json-string)
|
||||||
|
- "family": address family (json-string)
|
||||||
|
- Possible values: "ipv4", "ipv6", "unix", "unknown"
|
||||||
|
- "port": client's port number (json-string)
|
||||||
|
- "connection-id": spice connection id. All channels with the same id
|
||||||
|
belong to the same spice session (json-int)
|
||||||
|
- "channel-type": channel type. "1" is the main control channel, filter for
|
||||||
|
this one if you want track spice sessions only (json-int)
|
||||||
|
- "channel-id": channel id. Usually "0", might be different needed when
|
||||||
|
multiple channels of the same type exist, such as multiple
|
||||||
|
display channels in a multihead setup (json-int)
|
||||||
|
- "tls": whevener the channel is encrypted (json-bool)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
-> { "execute": "query-spice" }
|
||||||
|
<- {
|
||||||
|
"return": {
|
||||||
|
"enabled": true,
|
||||||
|
"auth": "spice",
|
||||||
|
"port": 5920,
|
||||||
|
"tls-port": 5921,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"port": "54924",
|
||||||
|
"family": "ipv4",
|
||||||
|
"channel-type": 1,
|
||||||
|
"connection-id": 1804289383,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"channel-id": 0,
|
||||||
|
"tls": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "36710",
|
||||||
|
"family": "ipv4",
|
||||||
|
"channel-type": 4,
|
||||||
|
"connection-id": 1804289383,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"channel-id": 0,
|
||||||
|
"tls": false
|
||||||
|
},
|
||||||
|
[ ... more channels follow ... ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
SQMP
|
SQMP
|
||||||
query-name
|
query-name
|
||||||
----------
|
----------
|
||||||
|
3
sysemu.h
3
sysemu.h
@ -104,7 +104,7 @@ extern int incoming_expected;
|
|||||||
extern int bios_size;
|
extern int bios_size;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VGA_NONE, VGA_STD, VGA_CIRRUS, VGA_VMWARE, VGA_XENFB
|
VGA_NONE, VGA_STD, VGA_CIRRUS, VGA_VMWARE, VGA_XENFB, VGA_QXL,
|
||||||
} VGAInterfaceType;
|
} VGAInterfaceType;
|
||||||
|
|
||||||
extern int vga_interface_type;
|
extern int vga_interface_type;
|
||||||
@ -112,6 +112,7 @@ extern int vga_interface_type;
|
|||||||
#define std_vga_enabled (vga_interface_type == VGA_STD)
|
#define std_vga_enabled (vga_interface_type == VGA_STD)
|
||||||
#define xenfb_enabled (vga_interface_type == VGA_XENFB)
|
#define xenfb_enabled (vga_interface_type == VGA_XENFB)
|
||||||
#define vmsvga_enabled (vga_interface_type == VGA_VMWARE)
|
#define vmsvga_enabled (vga_interface_type == VGA_VMWARE)
|
||||||
|
#define qxl_enabled (vga_interface_type == VGA_QXL)
|
||||||
|
|
||||||
extern int graphic_width;
|
extern int graphic_width;
|
||||||
extern int graphic_height;
|
extern int graphic_height;
|
||||||
|
@ -32,10 +32,18 @@ void qemu_spice_input_init(void);
|
|||||||
void qemu_spice_audio_init(void);
|
void qemu_spice_audio_init(void);
|
||||||
void qemu_spice_display_init(DisplayState *ds);
|
void qemu_spice_display_init(DisplayState *ds);
|
||||||
int qemu_spice_add_interface(SpiceBaseInstance *sin);
|
int qemu_spice_add_interface(SpiceBaseInstance *sin);
|
||||||
|
int qemu_spice_set_passwd(const char *passwd,
|
||||||
|
bool fail_if_connected, bool disconnect_if_connected);
|
||||||
|
int qemu_spice_set_pw_expire(time_t expires);
|
||||||
|
|
||||||
|
void do_info_spice_print(Monitor *mon, const QObject *data);
|
||||||
|
void do_info_spice(Monitor *mon, QObject **ret_data);
|
||||||
|
|
||||||
#else /* CONFIG_SPICE */
|
#else /* CONFIG_SPICE */
|
||||||
|
|
||||||
#define using_spice 0
|
#define using_spice 0
|
||||||
|
#define qemu_spice_set_passwd(_p, _f1, _f2) (-1)
|
||||||
|
#define qemu_spice_set_pw_expire(_e) (-1)
|
||||||
|
|
||||||
#endif /* CONFIG_SPICE */
|
#endif /* CONFIG_SPICE */
|
||||||
|
|
||||||
|
261
ui/spice-core.c
261
ui/spice-core.c
@ -18,16 +18,26 @@
|
|||||||
#include <spice.h>
|
#include <spice.h>
|
||||||
#include <spice-experimental.h>
|
#include <spice-experimental.h>
|
||||||
|
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu-spice.h"
|
#include "qemu-spice.h"
|
||||||
#include "qemu-timer.h"
|
#include "qemu-timer.h"
|
||||||
#include "qemu-queue.h"
|
#include "qemu-queue.h"
|
||||||
#include "qemu-x509.h"
|
#include "qemu-x509.h"
|
||||||
|
#include "qemu_socket.h"
|
||||||
|
#include "qint.h"
|
||||||
|
#include "qbool.h"
|
||||||
|
#include "qstring.h"
|
||||||
|
#include "qjson.h"
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
|
|
||||||
/* core bits */
|
/* core bits */
|
||||||
|
|
||||||
static SpiceServer *spice_server;
|
static SpiceServer *spice_server;
|
||||||
|
static const char *auth = "spice";
|
||||||
|
static char *auth_passwd;
|
||||||
|
static time_t auth_expires = TIME_MAX;
|
||||||
int using_spice = 0;
|
int using_spice = 0;
|
||||||
|
|
||||||
struct SpiceTimer {
|
struct SpiceTimer {
|
||||||
@ -121,6 +131,118 @@ static void watch_remove(SpiceWatch *watch)
|
|||||||
qemu_free(watch);
|
qemu_free(watch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SPICE_INTERFACE_CORE_MINOR >= 3
|
||||||
|
|
||||||
|
typedef struct ChannelList ChannelList;
|
||||||
|
struct ChannelList {
|
||||||
|
SpiceChannelEventInfo *info;
|
||||||
|
QTAILQ_ENTRY(ChannelList) link;
|
||||||
|
};
|
||||||
|
static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list);
|
||||||
|
|
||||||
|
static void channel_list_add(SpiceChannelEventInfo *info)
|
||||||
|
{
|
||||||
|
ChannelList *item;
|
||||||
|
|
||||||
|
item = qemu_mallocz(sizeof(*item));
|
||||||
|
item->info = info;
|
||||||
|
QTAILQ_INSERT_TAIL(&channel_list, item, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void channel_list_del(SpiceChannelEventInfo *info)
|
||||||
|
{
|
||||||
|
ChannelList *item;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(item, &channel_list, link) {
|
||||||
|
if (item->info != info) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QTAILQ_REMOVE(&channel_list, item, link);
|
||||||
|
qemu_free(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_addr_info(QDict *dict, struct sockaddr *addr, int len)
|
||||||
|
{
|
||||||
|
char host[NI_MAXHOST], port[NI_MAXSERV];
|
||||||
|
const char *family;
|
||||||
|
|
||||||
|
getnameinfo(addr, len, host, sizeof(host), port, sizeof(port),
|
||||||
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||||
|
family = inet_strfamily(addr->sa_family);
|
||||||
|
|
||||||
|
qdict_put(dict, "host", qstring_from_str(host));
|
||||||
|
qdict_put(dict, "port", qstring_from_str(port));
|
||||||
|
qdict_put(dict, "family", qstring_from_str(family));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info)
|
||||||
|
{
|
||||||
|
int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
|
||||||
|
|
||||||
|
qdict_put(dict, "connection-id", qint_from_int(info->connection_id));
|
||||||
|
qdict_put(dict, "channel-type", qint_from_int(info->type));
|
||||||
|
qdict_put(dict, "channel-id", qint_from_int(info->id));
|
||||||
|
qdict_put(dict, "tls", qbool_from_int(tls));
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList *channel_list_get(void)
|
||||||
|
{
|
||||||
|
ChannelList *item;
|
||||||
|
QList *list;
|
||||||
|
QDict *dict;
|
||||||
|
|
||||||
|
list = qlist_new();
|
||||||
|
QTAILQ_FOREACH(item, &channel_list, link) {
|
||||||
|
dict = qdict_new();
|
||||||
|
add_addr_info(dict, &item->info->paddr, item->info->plen);
|
||||||
|
add_channel_info(dict, item->info);
|
||||||
|
qlist_append(list, dict);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void channel_event(int event, SpiceChannelEventInfo *info)
|
||||||
|
{
|
||||||
|
static const int qevent[] = {
|
||||||
|
[ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED,
|
||||||
|
[ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED,
|
||||||
|
[ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED,
|
||||||
|
};
|
||||||
|
QDict *server, *client;
|
||||||
|
QObject *data;
|
||||||
|
|
||||||
|
client = qdict_new();
|
||||||
|
add_addr_info(client, &info->paddr, info->plen);
|
||||||
|
|
||||||
|
server = qdict_new();
|
||||||
|
add_addr_info(server, &info->laddr, info->llen);
|
||||||
|
|
||||||
|
if (event == SPICE_CHANNEL_EVENT_INITIALIZED) {
|
||||||
|
qdict_put(server, "auth", qstring_from_str(auth));
|
||||||
|
add_channel_info(client, info);
|
||||||
|
channel_list_add(info);
|
||||||
|
}
|
||||||
|
if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) {
|
||||||
|
channel_list_del(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
|
||||||
|
QOBJECT(client), QOBJECT(server));
|
||||||
|
monitor_protocol_event(qevent[event], data);
|
||||||
|
qobject_decref(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* SPICE_INTERFACE_CORE_MINOR >= 3 */
|
||||||
|
|
||||||
|
static QList *channel_list_get(void)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* SPICE_INTERFACE_CORE_MINOR >= 3 */
|
||||||
|
|
||||||
static SpiceCoreInterface core_interface = {
|
static SpiceCoreInterface core_interface = {
|
||||||
.base.type = SPICE_INTERFACE_CORE,
|
.base.type = SPICE_INTERFACE_CORE,
|
||||||
.base.description = "qemu core services",
|
.base.description = "qemu core services",
|
||||||
@ -135,6 +257,10 @@ static SpiceCoreInterface core_interface = {
|
|||||||
.watch_add = watch_add,
|
.watch_add = watch_add,
|
||||||
.watch_update_mask = watch_update_mask,
|
.watch_update_mask = watch_update_mask,
|
||||||
.watch_remove = watch_remove,
|
.watch_remove = watch_remove,
|
||||||
|
|
||||||
|
#if SPICE_INTERFACE_CORE_MINOR >= 3
|
||||||
|
.channel_event = channel_event,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* config string parsing */
|
/* config string parsing */
|
||||||
@ -204,6 +330,92 @@ static const char *wan_compression_names[] = {
|
|||||||
|
|
||||||
/* functions for the rest of qemu */
|
/* functions for the rest of qemu */
|
||||||
|
|
||||||
|
static void info_spice_iter(QObject *obj, void *opaque)
|
||||||
|
{
|
||||||
|
QDict *client;
|
||||||
|
Monitor *mon = opaque;
|
||||||
|
|
||||||
|
client = qobject_to_qdict(obj);
|
||||||
|
monitor_printf(mon, "Channel:\n");
|
||||||
|
monitor_printf(mon, " address: %s:%s%s\n",
|
||||||
|
qdict_get_str(client, "host"),
|
||||||
|
qdict_get_str(client, "port"),
|
||||||
|
qdict_get_bool(client, "tls") ? " [tls]" : "");
|
||||||
|
monitor_printf(mon, " session: %" PRId64 "\n",
|
||||||
|
qdict_get_int(client, "connection-id"));
|
||||||
|
monitor_printf(mon, " channel: %d:%d\n",
|
||||||
|
(int)qdict_get_int(client, "channel-type"),
|
||||||
|
(int)qdict_get_int(client, "channel-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_info_spice_print(Monitor *mon, const QObject *data)
|
||||||
|
{
|
||||||
|
QDict *server;
|
||||||
|
QList *channels;
|
||||||
|
const char *host;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
server = qobject_to_qdict(data);
|
||||||
|
if (qdict_get_bool(server, "enabled") == 0) {
|
||||||
|
monitor_printf(mon, "Server: disabled\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor_printf(mon, "Server:\n");
|
||||||
|
host = qdict_get_str(server, "host");
|
||||||
|
port = qdict_get_try_int(server, "port", -1);
|
||||||
|
if (port != -1) {
|
||||||
|
monitor_printf(mon, " address: %s:%d\n", host, port);
|
||||||
|
}
|
||||||
|
port = qdict_get_try_int(server, "tls-port", -1);
|
||||||
|
if (port != -1) {
|
||||||
|
monitor_printf(mon, " address: %s:%d [tls]\n", host, port);
|
||||||
|
}
|
||||||
|
monitor_printf(mon, " auth: %s\n", qdict_get_str(server, "auth"));
|
||||||
|
|
||||||
|
channels = qdict_get_qlist(server, "channels");
|
||||||
|
if (qlist_empty(channels)) {
|
||||||
|
monitor_printf(mon, "Channels: none\n");
|
||||||
|
} else {
|
||||||
|
qlist_iter(channels, info_spice_iter, mon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_info_spice(Monitor *mon, QObject **ret_data)
|
||||||
|
{
|
||||||
|
QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
|
||||||
|
QDict *server;
|
||||||
|
QList *clist;
|
||||||
|
const char *addr;
|
||||||
|
int port, tls_port;
|
||||||
|
|
||||||
|
if (!spice_server) {
|
||||||
|
*ret_data = qobject_from_jsonf("{ 'enabled': false }");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = qemu_opt_get(opts, "addr");
|
||||||
|
port = qemu_opt_get_number(opts, "port", 0);
|
||||||
|
tls_port = qemu_opt_get_number(opts, "tls-port", 0);
|
||||||
|
clist = channel_list_get();
|
||||||
|
|
||||||
|
server = qdict_new();
|
||||||
|
qdict_put(server, "enabled", qbool_from_int(true));
|
||||||
|
qdict_put(server, "auth", qstring_from_str(auth));
|
||||||
|
qdict_put(server, "host", qstring_from_str(addr ? addr : "0.0.0.0"));
|
||||||
|
if (port) {
|
||||||
|
qdict_put(server, "port", qint_from_int(port));
|
||||||
|
}
|
||||||
|
if (tls_port) {
|
||||||
|
qdict_put(server, "tls-port", qint_from_int(tls_port));
|
||||||
|
}
|
||||||
|
if (clist) {
|
||||||
|
qdict_put(server, "channels", clist);
|
||||||
|
}
|
||||||
|
|
||||||
|
*ret_data = QOBJECT(server);
|
||||||
|
}
|
||||||
|
|
||||||
static int add_channel(const char *name, const char *value, void *opaque)
|
static int add_channel(const char *name, const char *value, void *opaque)
|
||||||
{
|
{
|
||||||
int security = 0;
|
int security = 0;
|
||||||
@ -316,6 +528,7 @@ void qemu_spice_init(void)
|
|||||||
spice_server_set_ticket(spice_server, password, 0, 0, 0);
|
spice_server_set_ticket(spice_server, password, 0, 0, 0);
|
||||||
}
|
}
|
||||||
if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
|
if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
|
||||||
|
auth = "none";
|
||||||
spice_server_set_noauth(spice_server);
|
spice_server_set_noauth(spice_server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,9 +583,57 @@ void qemu_spice_init(void)
|
|||||||
|
|
||||||
int qemu_spice_add_interface(SpiceBaseInstance *sin)
|
int qemu_spice_add_interface(SpiceBaseInstance *sin)
|
||||||
{
|
{
|
||||||
|
if (!spice_server) {
|
||||||
|
if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) {
|
||||||
|
fprintf(stderr, "Oops: spice configured but not active\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Create a spice server instance.
|
||||||
|
* It does *not* listen on the network.
|
||||||
|
* It handles QXL local rendering only.
|
||||||
|
*
|
||||||
|
* With a command line like '-vnc :0 -vga qxl' you'll end up here.
|
||||||
|
*/
|
||||||
|
spice_server = spice_server_new();
|
||||||
|
spice_server_init(spice_server, &core_interface);
|
||||||
|
}
|
||||||
return spice_server_add_interface(spice_server, sin);
|
return spice_server_add_interface(spice_server, sin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn)
|
||||||
|
{
|
||||||
|
time_t lifetime, now = time(NULL);
|
||||||
|
char *passwd;
|
||||||
|
|
||||||
|
if (now < auth_expires) {
|
||||||
|
passwd = auth_passwd;
|
||||||
|
lifetime = (auth_expires - now);
|
||||||
|
if (lifetime > INT_MAX) {
|
||||||
|
lifetime = INT_MAX;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passwd = NULL;
|
||||||
|
lifetime = 1;
|
||||||
|
}
|
||||||
|
return spice_server_set_ticket(spice_server, passwd, lifetime,
|
||||||
|
fail_if_conn, disconnect_if_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qemu_spice_set_passwd(const char *passwd,
|
||||||
|
bool fail_if_conn, bool disconnect_if_conn)
|
||||||
|
{
|
||||||
|
free(auth_passwd);
|
||||||
|
auth_passwd = strdup(passwd);
|
||||||
|
return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qemu_spice_set_pw_expire(time_t expires)
|
||||||
|
{
|
||||||
|
auth_expires = expires;
|
||||||
|
return qemu_spice_set_ticket(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
static void spice_register_config(void)
|
static void spice_register_config(void)
|
||||||
{
|
{
|
||||||
qemu_add_opts(&qemu_spice_opts);
|
qemu_add_opts(&qemu_spice_opts);
|
||||||
|
44
ui/vnc.c
44
ui/vnc.c
@ -2082,18 +2082,15 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
|
|||||||
unsigned char response[VNC_AUTH_CHALLENGE_SIZE];
|
unsigned char response[VNC_AUTH_CHALLENGE_SIZE];
|
||||||
int i, j, pwlen;
|
int i, j, pwlen;
|
||||||
unsigned char key[8];
|
unsigned char key[8];
|
||||||
|
time_t now = time(NULL);
|
||||||
|
|
||||||
if (!vs->vd->password || !vs->vd->password[0]) {
|
if (!vs->vd->password || !vs->vd->password[0]) {
|
||||||
VNC_DEBUG("No password configured on server");
|
VNC_DEBUG("No password configured on server");
|
||||||
vnc_write_u32(vs, 1); /* Reject auth */
|
goto reject;
|
||||||
if (vs->minor >= 8) {
|
}
|
||||||
static const char err[] = "Authentication failed";
|
if (vs->vd->expires < now) {
|
||||||
vnc_write_u32(vs, sizeof(err));
|
VNC_DEBUG("Password is expired");
|
||||||
vnc_write(vs, err, sizeof(err));
|
goto reject;
|
||||||
}
|
|
||||||
vnc_flush(vs);
|
|
||||||
vnc_client_error(vs);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE);
|
memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE);
|
||||||
@ -2109,14 +2106,7 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
|
|||||||
/* Compare expected vs actual challenge response */
|
/* Compare expected vs actual challenge response */
|
||||||
if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
|
if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
|
||||||
VNC_DEBUG("Client challenge reponse did not match\n");
|
VNC_DEBUG("Client challenge reponse did not match\n");
|
||||||
vnc_write_u32(vs, 1); /* Reject auth */
|
goto reject;
|
||||||
if (vs->minor >= 8) {
|
|
||||||
static const char err[] = "Authentication failed";
|
|
||||||
vnc_write_u32(vs, sizeof(err));
|
|
||||||
vnc_write(vs, err, sizeof(err));
|
|
||||||
}
|
|
||||||
vnc_flush(vs);
|
|
||||||
vnc_client_error(vs);
|
|
||||||
} else {
|
} else {
|
||||||
VNC_DEBUG("Accepting VNC challenge response\n");
|
VNC_DEBUG("Accepting VNC challenge response\n");
|
||||||
vnc_write_u32(vs, 0); /* Accept auth */
|
vnc_write_u32(vs, 0); /* Accept auth */
|
||||||
@ -2125,6 +2115,17 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
|
|||||||
start_client_init(vs);
|
start_client_init(vs);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
reject:
|
||||||
|
vnc_write_u32(vs, 1); /* Reject auth */
|
||||||
|
if (vs->minor >= 8) {
|
||||||
|
static const char err[] = "Authentication failed";
|
||||||
|
vnc_write_u32(vs, sizeof(err));
|
||||||
|
vnc_write(vs, err, sizeof(err));
|
||||||
|
}
|
||||||
|
vnc_flush(vs);
|
||||||
|
vnc_client_error(vs);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_auth_vnc(VncState *vs)
|
void start_auth_vnc(VncState *vs)
|
||||||
@ -2436,6 +2437,7 @@ void vnc_display_init(DisplayState *ds)
|
|||||||
|
|
||||||
vs->ds = ds;
|
vs->ds = ds;
|
||||||
QTAILQ_INIT(&vs->clients);
|
QTAILQ_INIT(&vs->clients);
|
||||||
|
vs->expires = TIME_MAX;
|
||||||
|
|
||||||
if (keyboard_layout)
|
if (keyboard_layout)
|
||||||
vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout);
|
vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout);
|
||||||
@ -2507,6 +2509,14 @@ int vnc_display_password(DisplayState *ds, const char *password)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int vnc_display_pw_expire(DisplayState *ds, time_t expires)
|
||||||
|
{
|
||||||
|
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
|
||||||
|
|
||||||
|
vs->expires = expires;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
char *vnc_display_local_addr(DisplayState *ds)
|
char *vnc_display_local_addr(DisplayState *ds)
|
||||||
{
|
{
|
||||||
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
|
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
|
||||||
|
1
ui/vnc.h
1
ui/vnc.h
@ -120,6 +120,7 @@ struct VncDisplay
|
|||||||
|
|
||||||
char *display;
|
char *display;
|
||||||
char *password;
|
char *password;
|
||||||
|
time_t expires;
|
||||||
int auth;
|
int auth;
|
||||||
bool lossy;
|
bool lossy;
|
||||||
#ifdef CONFIG_VNC_TLS
|
#ifdef CONFIG_VNC_TLS
|
||||||
|
4
vl.c
4
vl.c
@ -1504,6 +1504,8 @@ static void select_vgahw (const char *p)
|
|||||||
vga_interface_type = VGA_VMWARE;
|
vga_interface_type = VGA_VMWARE;
|
||||||
} else if (strstart(p, "xenfb", &opts)) {
|
} else if (strstart(p, "xenfb", &opts)) {
|
||||||
vga_interface_type = VGA_XENFB;
|
vga_interface_type = VGA_XENFB;
|
||||||
|
} else if (strstart(p, "qxl", &opts)) {
|
||||||
|
vga_interface_type = VGA_QXL;
|
||||||
} else if (!strstart(p, "none", &opts)) {
|
} else if (!strstart(p, "none", &opts)) {
|
||||||
invalid_vga:
|
invalid_vga:
|
||||||
fprintf(stderr, "Unknown vga type: %s\n", p);
|
fprintf(stderr, "Unknown vga type: %s\n", p);
|
||||||
@ -3055,7 +3057,7 @@ int main(int argc, char **argv, char **envp)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef CONFIG_SPICE
|
#ifdef CONFIG_SPICE
|
||||||
if (using_spice) {
|
if (using_spice && !qxl_enabled) {
|
||||||
qemu_spice_display_init(ds);
|
qemu_spice_display_init(ds);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user