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
|
||||
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 \
|
||||
gpxe-eepro100-80861209.rom \
|
||||
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 += debugcon.o multiboot.o
|
||||
obj-i386-y += pc_piix.o
|
||||
obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
|
||||
|
||||
# shared objects
|
||||
obj-ppc-y = ppc.o
|
||||
|
@ -182,6 +182,70 @@ Example:
|
||||
"host": "127.0.0.1", "sasl_username": "luiz" } },
|
||||
"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
|
||||
--------
|
||||
|
||||
|
@ -369,6 +369,7 @@ void vnc_display_init(DisplayState *ds);
|
||||
void vnc_display_close(DisplayState *ds);
|
||||
int vnc_display_open(DisplayState *ds, const char *display);
|
||||
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(Monitor *mon, QObject **ret_data);
|
||||
char *vnc_display_local_addr(DisplayState *ds);
|
||||
|
@ -1151,6 +1151,60 @@ STEXI
|
||||
@item block_passwd @var{device} @var{password}
|
||||
@findex block_passwd
|
||||
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
|
||||
|
||||
{
|
||||
|
14
hw/hw.h
14
hw/hw.h
@ -528,6 +528,17 @@ extern const VMStateInfo vmstate_info_unused_buffer;
|
||||
.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) { \
|
||||
.name = (stringify(_field)), \
|
||||
.version_id = (_version), \
|
||||
@ -745,6 +756,9 @@ extern const VMStateDescription vmstate_i2c_slave;
|
||||
#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _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) \
|
||||
VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size)
|
||||
|
||||
|
8
hw/pc.c
8
hw/pc.c
@ -40,6 +40,7 @@
|
||||
#include "sysbus.h"
|
||||
#include "sysemu.h"
|
||||
#include "blockdev.h"
|
||||
#include "ui/qemu-spice.h"
|
||||
|
||||
/* output Bochs bios info messages */
|
||||
//#define DEBUG_BIOS
|
||||
@ -992,6 +993,13 @@ void pc_vga_init(PCIBus *pci_bus)
|
||||
pci_vmsvga_init(pci_bus);
|
||||
else
|
||||
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) {
|
||||
if (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 {
|
||||
uint8_t *vram_ptr;
|
||||
ram_addr_t vram_offset;
|
||||
unsigned int vram_size;
|
||||
uint32_t vram_size;
|
||||
uint32_t lfb_addr;
|
||||
uint32_t lfb_end;
|
||||
uint32_t map_addr;
|
||||
|
130
monitor.c
130
monitor.c
@ -34,6 +34,7 @@
|
||||
#include "net.h"
|
||||
#include "net/slirp.h"
|
||||
#include "qemu-char.h"
|
||||
#include "ui/qemu-spice.h"
|
||||
#include "sysemu.h"
|
||||
#include "monitor.h"
|
||||
#include "readline.h"
|
||||
@ -59,6 +60,7 @@
|
||||
#ifdef CONFIG_SIMPLE_TRACE
|
||||
#include "trace.h"
|
||||
#endif
|
||||
#include "ui/qemu-spice.h"
|
||||
|
||||
//#define DEBUG
|
||||
//#define DEBUG_COMPLETION
|
||||
@ -457,6 +459,15 @@ void monitor_protocol_event(MonitorEvent event, QObject *data)
|
||||
case QEVENT_WATCHDOG:
|
||||
event_name = "WATCHDOG";
|
||||
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:
|
||||
abort();
|
||||
break;
|
||||
@ -1063,6 +1074,105 @@ static int do_change(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
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)
|
||||
{
|
||||
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,
|
||||
.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",
|
||||
.args_type = "",
|
||||
@ -3046,6 +3166,16 @@ static const mon_cmd_t qmp_query_cmds[] = {
|
||||
.user_print = do_info_vnc_print,
|
||||
.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",
|
||||
.args_type = "",
|
||||
|
@ -32,6 +32,9 @@ typedef enum MonitorEvent {
|
||||
QEVENT_BLOCK_IO_ERROR,
|
||||
QEVENT_RTC_CHANGE,
|
||||
QEVENT_WATCHDOG,
|
||||
QEVENT_SPICE_CONNECTED,
|
||||
QEVENT_SPICE_INITIALIZED,
|
||||
QEVENT_SPICE_DISCONNECTED,
|
||||
QEVENT_MAX,
|
||||
} 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)
|
||||
#define ENOTSUP 4096
|
||||
#endif
|
||||
#ifndef TIME_MAX
|
||||
#define TIME_MAX LONG_MAX
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_IOVEC
|
||||
#define CONFIG_IOVEC
|
||||
|
@ -751,7 +751,7 @@ Rotate graphical output 90 deg left (only PXA LCD).
|
||||
ETEXI
|
||||
|
||||
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)
|
||||
STEXI
|
||||
@item -vga @var{type}
|
||||
@ -772,6 +772,10 @@ this option.
|
||||
VMWare SVGA-II compatible adapter. Use it if you have sufficiently
|
||||
recent XFree86/XOrg server or Windows guest with a driver for this
|
||||
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
|
||||
Disable VGA card.
|
||||
@end table
|
||||
|
127
qmp-commands.hx
127
qmp-commands.hx
@ -735,6 +735,63 @@ Example:
|
||||
"password": "12345" } }
|
||||
<- { "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
|
||||
|
||||
{
|
||||
@ -1438,6 +1495,76 @@ Example:
|
||||
|
||||
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
|
||||
query-name
|
||||
----------
|
||||
|
3
sysemu.h
3
sysemu.h
@ -104,7 +104,7 @@ extern int incoming_expected;
|
||||
extern int bios_size;
|
||||
|
||||
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;
|
||||
|
||||
extern int vga_interface_type;
|
||||
@ -112,6 +112,7 @@ extern int vga_interface_type;
|
||||
#define std_vga_enabled (vga_interface_type == VGA_STD)
|
||||
#define xenfb_enabled (vga_interface_type == VGA_XENFB)
|
||||
#define vmsvga_enabled (vga_interface_type == VGA_VMWARE)
|
||||
#define qxl_enabled (vga_interface_type == VGA_QXL)
|
||||
|
||||
extern int graphic_width;
|
||||
extern int graphic_height;
|
||||
|
@ -32,10 +32,18 @@ void qemu_spice_input_init(void);
|
||||
void qemu_spice_audio_init(void);
|
||||
void qemu_spice_display_init(DisplayState *ds);
|
||||
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 */
|
||||
|
||||
#define using_spice 0
|
||||
#define qemu_spice_set_passwd(_p, _f1, _f2) (-1)
|
||||
#define qemu_spice_set_pw_expire(_e) (-1)
|
||||
|
||||
#endif /* CONFIG_SPICE */
|
||||
|
||||
|
261
ui/spice-core.c
261
ui/spice-core.c
@ -18,16 +18,26 @@
|
||||
#include <spice.h>
|
||||
#include <spice-experimental.h>
|
||||
|
||||
#include <netdb.h>
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qemu-spice.h"
|
||||
#include "qemu-timer.h"
|
||||
#include "qemu-queue.h"
|
||||
#include "qemu-x509.h"
|
||||
#include "qemu_socket.h"
|
||||
#include "qint.h"
|
||||
#include "qbool.h"
|
||||
#include "qstring.h"
|
||||
#include "qjson.h"
|
||||
#include "monitor.h"
|
||||
|
||||
/* core bits */
|
||||
|
||||
static SpiceServer *spice_server;
|
||||
static const char *auth = "spice";
|
||||
static char *auth_passwd;
|
||||
static time_t auth_expires = TIME_MAX;
|
||||
int using_spice = 0;
|
||||
|
||||
struct SpiceTimer {
|
||||
@ -121,6 +131,118 @@ static void watch_remove(SpiceWatch *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 = {
|
||||
.base.type = SPICE_INTERFACE_CORE,
|
||||
.base.description = "qemu core services",
|
||||
@ -135,6 +257,10 @@ static SpiceCoreInterface core_interface = {
|
||||
.watch_add = watch_add,
|
||||
.watch_update_mask = watch_update_mask,
|
||||
.watch_remove = watch_remove,
|
||||
|
||||
#if SPICE_INTERFACE_CORE_MINOR >= 3
|
||||
.channel_event = channel_event,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* config string parsing */
|
||||
@ -204,6 +330,92 @@ static const char *wan_compression_names[] = {
|
||||
|
||||
/* 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)
|
||||
{
|
||||
int security = 0;
|
||||
@ -316,6 +528,7 @@ void qemu_spice_init(void)
|
||||
spice_server_set_ticket(spice_server, password, 0, 0, 0);
|
||||
}
|
||||
if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
|
||||
auth = "none";
|
||||
spice_server_set_noauth(spice_server);
|
||||
}
|
||||
|
||||
@ -370,9 +583,57 @@ void qemu_spice_init(void)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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];
|
||||
int i, j, pwlen;
|
||||
unsigned char key[8];
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (!vs->vd->password || !vs->vd->password[0]) {
|
||||
VNC_DEBUG("No password configured on server");
|
||||
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;
|
||||
goto reject;
|
||||
}
|
||||
if (vs->vd->expires < now) {
|
||||
VNC_DEBUG("Password is expired");
|
||||
goto reject;
|
||||
}
|
||||
|
||||
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 */
|
||||
if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
|
||||
VNC_DEBUG("Client challenge reponse did not match\n");
|
||||
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);
|
||||
goto reject;
|
||||
} else {
|
||||
VNC_DEBUG("Accepting VNC challenge response\n");
|
||||
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);
|
||||
}
|
||||
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)
|
||||
@ -2436,6 +2437,7 @@ void vnc_display_init(DisplayState *ds)
|
||||
|
||||
vs->ds = ds;
|
||||
QTAILQ_INIT(&vs->clients);
|
||||
vs->expires = TIME_MAX;
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 *password;
|
||||
time_t expires;
|
||||
int auth;
|
||||
bool lossy;
|
||||
#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;
|
||||
} else if (strstart(p, "xenfb", &opts)) {
|
||||
vga_interface_type = VGA_XENFB;
|
||||
} else if (strstart(p, "qxl", &opts)) {
|
||||
vga_interface_type = VGA_QXL;
|
||||
} else if (!strstart(p, "none", &opts)) {
|
||||
invalid_vga:
|
||||
fprintf(stderr, "Unknown vga type: %s\n", p);
|
||||
@ -3055,7 +3057,7 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
}
|
||||
#ifdef CONFIG_SPICE
|
||||
if (using_spice) {
|
||||
if (using_spice && !qxl_enabled) {
|
||||
qemu_spice_display_init(ds);
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user