mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
Rather than having an API to register client callbacks for each channel type, make them vfuncs. Since the client callbacks are registered identically for each channel of the same type, it doesn't make sense for to require these to be registered separately for each object. It's cleaner to have these be per-class properties, so they've been converted to virtual functions. Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com> Acked-by: Frediano Ziglio <fziglio@redhat.com>
823 lines
25 KiB
C
823 lines
25 KiB
C
/* spice-server character device to handle a video stream
|
|
|
|
Copyright (C) 2017-2018 Red Hat, Inc.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <common/recorder.h>
|
|
|
|
#include "red-stream-device.h"
|
|
|
|
#include "stream-channel.h"
|
|
#include "cursor-channel.h"
|
|
#include "reds.h"
|
|
|
|
#define MAX_GUEST_CAPABILITIES_BYTES ((STREAM_CAP_END+7)/8)
|
|
|
|
struct StreamDevice {
|
|
RedCharDevice parent;
|
|
|
|
StreamDevHeader hdr;
|
|
uint8_t hdr_pos;
|
|
union {
|
|
StreamMsgFormat format;
|
|
StreamMsgCapabilities capabilities;
|
|
StreamMsgCursorSet cursor_set;
|
|
StreamMsgCursorMove cursor_move;
|
|
StreamMsgDeviceDisplayInfo device_display_info;
|
|
uint8_t buf[STREAM_MSG_CAPABILITIES_MAX_BYTES];
|
|
} *msg;
|
|
uint32_t msg_pos;
|
|
uint32_t msg_len;
|
|
bool has_error;
|
|
bool opened;
|
|
bool flow_stopped;
|
|
uint8_t guest_capabilities[MAX_GUEST_CAPABILITIES_BYTES];
|
|
StreamChannel *stream_channel;
|
|
CursorChannel *cursor_channel;
|
|
SpiceTimer *close_timer;
|
|
uint32_t frame_mmtime;
|
|
StreamDeviceDisplayInfo device_display_info;
|
|
};
|
|
|
|
struct StreamDeviceClass {
|
|
RedCharDeviceClass parent_class;
|
|
};
|
|
|
|
static StreamDevice *stream_device_new(SpiceCharDeviceInstance *sin, RedsState *reds);
|
|
|
|
G_DEFINE_TYPE(StreamDevice, stream_device, RED_TYPE_CHAR_DEVICE)
|
|
|
|
static void char_device_set_state(RedCharDevice *char_dev, int state);
|
|
|
|
typedef bool StreamMsgHandler(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
SPICE_GNUC_WARN_UNUSED_RESULT;
|
|
|
|
static StreamMsgHandler handle_msg_format, handle_msg_device_display_info, handle_msg_data,
|
|
handle_msg_cursor_set, handle_msg_cursor_move, handle_msg_capabilities;
|
|
|
|
static bool handle_msg_invalid(StreamDevice *dev, SpiceCharDeviceInstance *sin,
|
|
const char *error_msg) SPICE_GNUC_WARN_UNUSED_RESULT;
|
|
|
|
RECORDER(stream_device_data, 32, "Stream device data packet");
|
|
|
|
static void
|
|
close_timer_func(void *opaque)
|
|
{
|
|
StreamDevice *dev = opaque;
|
|
|
|
if (dev->opened && dev->has_error) {
|
|
char_device_set_state(RED_CHAR_DEVICE(dev), 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fill_dev_hdr(StreamDevHeader *hdr, StreamMsgType msg_type, uint32_t msg_size)
|
|
{
|
|
hdr->protocol_version = STREAM_DEVICE_PROTOCOL;
|
|
hdr->padding = 0;
|
|
hdr->type = GUINT16_TO_LE(msg_type);
|
|
hdr->size = GUINT32_TO_LE(msg_size);
|
|
}
|
|
|
|
static bool
|
|
stream_device_partial_read(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif;
|
|
int n;
|
|
bool handled = false;
|
|
|
|
sif = spice_char_device_get_interface(sin);
|
|
|
|
// in order to get in sync every time we open the device we need to discard data here.
|
|
// Qemu keeps a buffer of data which is used only during spice_server_char_device_wakeup
|
|
// from Qemu
|
|
if (G_UNLIKELY(dev->has_error)) {
|
|
uint8_t buf[16 * 1024];
|
|
while (sif->read(sin, buf, sizeof(buf)) > 0) {
|
|
continue;
|
|
}
|
|
|
|
// This code is a workaround for a Qemu bug, see patch
|
|
// "stream-device: Workaround Qemu bug closing device".
|
|
// As calling sif->state here can cause a crash, schedule
|
|
// a timer and do the call in it. Remove this code when
|
|
// we are sure all Qemu versions have been patched.
|
|
RedsState *reds = red_char_device_get_server(RED_CHAR_DEVICE(dev));
|
|
if (!dev->close_timer) {
|
|
dev->close_timer = reds_core_timer_add(reds, close_timer_func, dev);
|
|
}
|
|
reds_core_timer_start(reds, dev->close_timer, 0);
|
|
return false;
|
|
}
|
|
|
|
if (dev->flow_stopped || !dev->stream_channel) {
|
|
return false;
|
|
}
|
|
|
|
/* read header */
|
|
while (dev->hdr_pos < sizeof(dev->hdr)) {
|
|
n = sif->read(sin, (uint8_t *) &dev->hdr + dev->hdr_pos, sizeof(dev->hdr) - dev->hdr_pos);
|
|
if (n <= 0) {
|
|
return false;
|
|
}
|
|
dev->hdr_pos += n;
|
|
if (dev->hdr_pos >= sizeof(dev->hdr)) {
|
|
dev->hdr.type = GUINT16_FROM_LE(dev->hdr.type);
|
|
dev->hdr.size = GUINT32_FROM_LE(dev->hdr.size);
|
|
dev->msg_pos = 0;
|
|
}
|
|
}
|
|
|
|
switch ((StreamMsgType) dev->hdr.type) {
|
|
case STREAM_TYPE_FORMAT:
|
|
if (dev->hdr.size != sizeof(StreamMsgFormat)) {
|
|
handled = handle_msg_invalid(dev, sin, "Wrong size for StreamMsgFormat");
|
|
} else {
|
|
handled = handle_msg_format(dev, sin);
|
|
}
|
|
break;
|
|
case STREAM_TYPE_DEVICE_DISPLAY_INFO:
|
|
if (dev->hdr.size > sizeof(StreamMsgDeviceDisplayInfo) + MAX_DEVICE_ADDRESS_LEN) {
|
|
handled = handle_msg_invalid(dev, sin, "StreamMsgDeviceDisplayInfo too large");
|
|
} else {
|
|
handled = handle_msg_device_display_info(dev, sin);
|
|
}
|
|
break;
|
|
case STREAM_TYPE_DATA:
|
|
if (dev->hdr.size > 32*1024*1024) {
|
|
handled = handle_msg_invalid(dev, sin, "STREAM_DATA too large");
|
|
} else {
|
|
handled = handle_msg_data(dev, sin);
|
|
}
|
|
break;
|
|
case STREAM_TYPE_CURSOR_SET:
|
|
handled = handle_msg_cursor_set(dev, sin);
|
|
break;
|
|
case STREAM_TYPE_CURSOR_MOVE:
|
|
if (dev->hdr.size != sizeof(StreamMsgCursorMove)) {
|
|
handled = handle_msg_invalid(dev, sin, "Wrong size for StreamMsgCursorMove");
|
|
} else {
|
|
handled = handle_msg_cursor_move(dev, sin);
|
|
}
|
|
break;
|
|
case STREAM_TYPE_CAPABILITIES:
|
|
handled = handle_msg_capabilities(dev, sin);
|
|
break;
|
|
default:
|
|
handled = handle_msg_invalid(dev, sin, "Invalid message type");
|
|
break;
|
|
}
|
|
|
|
/* current message has been handled, so reset state and get ready to parse
|
|
* the next message */
|
|
if (handled) {
|
|
dev->hdr_pos = 0;
|
|
|
|
// Reallocate message buffer to the minimum.
|
|
// Currently the only message that requires resizing is the cursor shape,
|
|
// which is not expected to be sent so often.
|
|
if (dev->msg_len > sizeof(*dev->msg)) {
|
|
dev->msg = g_realloc(dev->msg, sizeof(*dev->msg));
|
|
dev->msg_len = sizeof(*dev->msg);
|
|
}
|
|
}
|
|
|
|
if (handled || dev->has_error) {
|
|
// Qemu put the device on blocking state if we don't read all data
|
|
// so schedule another read.
|
|
// We arrive here if we processed that entire message or we
|
|
// got an error, try to read another message or discard the
|
|
// wrong data
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static RedPipeItem *
|
|
stream_device_read_msg_from_dev(RedCharDevice *self, SpiceCharDeviceInstance *sin)
|
|
{
|
|
StreamDevice *dev = STREAM_DEVICE(self);
|
|
|
|
while (stream_device_partial_read(dev, sin)) {
|
|
continue;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_invalid(StreamDevice *dev, SpiceCharDeviceInstance *sin, const char *error_msg)
|
|
{
|
|
static const char default_error_msg[] = "Protocol error";
|
|
|
|
if (spice_extra_checks) {
|
|
spice_assert(dev->hdr_pos >= sizeof(StreamDevHeader));
|
|
}
|
|
|
|
if (!error_msg) {
|
|
error_msg = default_error_msg;
|
|
}
|
|
|
|
g_warning("Stream device received invalid message: %s", error_msg);
|
|
|
|
int msg_size = sizeof(StreamMsgNotifyError) + strlen(error_msg) + 1;
|
|
int total_size = sizeof(StreamDevHeader) + msg_size;
|
|
|
|
RedCharDevice *char_dev = RED_CHAR_DEVICE(dev);
|
|
RedCharDeviceWriteBuffer *buf =
|
|
red_char_device_write_buffer_get_server(char_dev, total_size, false);
|
|
buf->buf_used = total_size;
|
|
|
|
StreamDevHeader *const hdr = (StreamDevHeader *)buf->buf;
|
|
fill_dev_hdr(hdr, STREAM_TYPE_NOTIFY_ERROR, msg_size);
|
|
|
|
StreamMsgNotifyError *const error = (StreamMsgNotifyError *)(hdr+1);
|
|
error->error_code = GUINT32_TO_LE(0);
|
|
strcpy((char *) error->msg, error_msg);
|
|
|
|
red_char_device_write_buffer_add(char_dev, buf);
|
|
|
|
dev->has_error = true;
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_format(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
|
|
if (spice_extra_checks) {
|
|
spice_assert(dev->hdr_pos >= sizeof(StreamDevHeader));
|
|
spice_assert(dev->hdr.type == STREAM_TYPE_FORMAT);
|
|
}
|
|
|
|
int n = sif->read(sin, dev->msg->buf + dev->msg_pos, sizeof(StreamMsgFormat) - dev->msg_pos);
|
|
if (n < 0) {
|
|
return handle_msg_invalid(dev, sin, NULL);
|
|
}
|
|
|
|
dev->msg_pos += n;
|
|
|
|
if (dev->msg_pos < sizeof(StreamMsgFormat)) {
|
|
return false;
|
|
}
|
|
|
|
dev->msg->format.width = GUINT32_FROM_LE(dev->msg->format.width);
|
|
dev->msg->format.height = GUINT32_FROM_LE(dev->msg->format.height);
|
|
stream_channel_change_format(dev->stream_channel, &dev->msg->format);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_device_display_info(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
|
|
if (spice_extra_checks) {
|
|
spice_assert(dev->hdr_pos >= sizeof(StreamDevHeader));
|
|
spice_assert(dev->hdr.type == STREAM_TYPE_DEVICE_DISPLAY_INFO);
|
|
}
|
|
|
|
if (dev->msg_len < dev->hdr.size) {
|
|
dev->msg = g_realloc(dev->msg, dev->hdr.size);
|
|
dev->msg_len = dev->hdr.size;
|
|
}
|
|
|
|
/* read from device */
|
|
ssize_t n = sif->read(sin, dev->msg->buf + dev->msg_pos, dev->hdr.size - dev->msg_pos);
|
|
if (n <= 0) {
|
|
return dev->msg_pos == dev->hdr.size;
|
|
}
|
|
|
|
dev->msg_pos += n;
|
|
if (dev->msg_pos != dev->hdr.size) { /* some bytes are still missing */
|
|
return false;
|
|
}
|
|
|
|
StreamMsgDeviceDisplayInfo *display_info_msg = &dev->msg->device_display_info;
|
|
|
|
size_t device_address_len = GUINT32_FROM_LE(display_info_msg->device_address_len);
|
|
if (device_address_len > MAX_DEVICE_ADDRESS_LEN) {
|
|
g_warning("Received a device address longer than %u (%zu), "
|
|
"will be truncated!", MAX_DEVICE_ADDRESS_LEN, device_address_len);
|
|
device_address_len = sizeof(dev->device_display_info.device_address);
|
|
}
|
|
|
|
if (device_address_len == 0) {
|
|
g_warning("Zero length device_address in DeviceDisplayInfo message, ignoring.");
|
|
return true;
|
|
}
|
|
|
|
if (display_info_msg->device_address + device_address_len > (uint8_t*) dev->msg + dev->hdr.size) {
|
|
g_warning("Malformed DeviceDisplayInfo message, device_address length (%zu) "
|
|
"goes beyond the end of the message, ignoring.", device_address_len);
|
|
return true;
|
|
}
|
|
|
|
memcpy(dev->device_display_info.device_address,
|
|
(char*) display_info_msg->device_address,
|
|
device_address_len);
|
|
|
|
// make sure the string is terminated
|
|
dev->device_display_info.device_address[device_address_len - 1] = '\0';
|
|
|
|
dev->device_display_info.stream_id = GUINT32_FROM_LE(display_info_msg->stream_id);
|
|
dev->device_display_info.device_display_id = GUINT32_FROM_LE(display_info_msg->device_display_id);
|
|
|
|
g_debug("Received DeviceDisplayInfo from the streaming agent: stream_id %u, "
|
|
"device_address %s, device_display_id %u",
|
|
dev->device_display_info.stream_id,
|
|
dev->device_display_info.device_address,
|
|
dev->device_display_info.device_display_id);
|
|
|
|
reds_send_device_display_info(red_char_device_get_server(RED_CHAR_DEVICE(dev)));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_capabilities(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
|
|
if (spice_extra_checks) {
|
|
spice_assert(dev->hdr_pos >= sizeof(StreamDevHeader));
|
|
spice_assert(dev->hdr.type == STREAM_TYPE_CAPABILITIES);
|
|
}
|
|
|
|
if (dev->hdr.size > STREAM_MSG_CAPABILITIES_MAX_BYTES) {
|
|
return handle_msg_invalid(dev, sin, "Wrong size for StreamMsgCapabilities");
|
|
}
|
|
|
|
int n = sif->read(sin, dev->msg->buf + dev->msg_pos, dev->hdr.size - dev->msg_pos);
|
|
if (n < 0) {
|
|
return handle_msg_invalid(dev, sin, NULL);
|
|
}
|
|
|
|
dev->msg_pos += n;
|
|
|
|
if (dev->msg_pos < dev->hdr.size) {
|
|
return false;
|
|
}
|
|
|
|
// copy only capabilities we know about
|
|
memset(dev->guest_capabilities, 0, sizeof(dev->guest_capabilities));
|
|
memcpy(dev->guest_capabilities, dev->msg->capabilities.capabilities,
|
|
MIN(sizeof(dev->guest_capabilities), dev->hdr.size));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_data(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
int n;
|
|
|
|
if (spice_extra_checks) {
|
|
spice_assert(dev->hdr_pos >= sizeof(StreamDevHeader));
|
|
spice_assert(dev->hdr.type == STREAM_TYPE_DATA);
|
|
}
|
|
|
|
/* make sure we have a large enough buffer for the whole frame */
|
|
/* ---
|
|
* TODO: Now that we copy partial data into the buffer, for each frame
|
|
* the buffer is allocated and freed (look for g_realloc in
|
|
* stream_device_partial_read).
|
|
* Probably better to just keep the larger buffer.
|
|
*/
|
|
if (dev->msg_pos == 0) {
|
|
dev->frame_mmtime = reds_get_mm_time();
|
|
record(stream_device_data, "Stream data packet size %u mm_time %u",
|
|
dev->hdr.size, dev->frame_mmtime);
|
|
if (dev->msg_len < dev->hdr.size) {
|
|
g_free(dev->msg);
|
|
dev->msg = g_malloc(dev->hdr.size);
|
|
dev->msg_len = dev->hdr.size;
|
|
}
|
|
}
|
|
|
|
/* read from device */
|
|
n = sif->read(sin, dev->msg->buf + dev->msg_pos, dev->hdr.size - dev->msg_pos);
|
|
if (n <= 0) {
|
|
return dev->msg_pos == dev->hdr.size;
|
|
}
|
|
|
|
dev->msg_pos += n;
|
|
if (dev->msg_pos != dev->hdr.size) { /* some bytes are still missing */
|
|
return false;
|
|
}
|
|
|
|
/* The whole frame was read from the device, send it */
|
|
stream_channel_send_data(dev->stream_channel, dev->msg->buf, dev->hdr.size,
|
|
dev->frame_mmtime);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Returns number of bits required for a pixel of a given cursor type.
|
|
*
|
|
* Take into account mask bits.
|
|
* Returns 0 on error.
|
|
*/
|
|
static unsigned int
|
|
get_cursor_type_bits(unsigned int cursor_type)
|
|
{
|
|
switch (cursor_type) {
|
|
case SPICE_CURSOR_TYPE_ALPHA:
|
|
// RGBA
|
|
return 32;
|
|
case SPICE_CURSOR_TYPE_COLOR24:
|
|
// RGB + bitmask
|
|
return 24 + 1;
|
|
case SPICE_CURSOR_TYPE_COLOR32:
|
|
// RGBx + bitmask
|
|
return 32 + 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static RedCursorCmd *
|
|
stream_msg_cursor_set_to_cursor_cmd(const StreamMsgCursorSet *msg, size_t msg_size)
|
|
{
|
|
RedCursorCmd *cmd = g_new0(RedCursorCmd, 1);
|
|
cmd->type = QXL_CURSOR_SET;
|
|
cmd->u.set.position.x = 0; // TODO
|
|
cmd->u.set.position.y = 0; // TODO
|
|
cmd->u.set.visible = 1; // TODO
|
|
SpiceCursor *cursor = &cmd->u.set.shape;
|
|
cursor->header.unique = 0;
|
|
cursor->header.type = msg->type;
|
|
cursor->header.width = GUINT16_FROM_LE(msg->width);
|
|
cursor->header.height = GUINT16_FROM_LE(msg->height);
|
|
cursor->header.hot_spot_x = GUINT16_FROM_LE(msg->hot_spot_x);
|
|
cursor->header.hot_spot_y = GUINT16_FROM_LE(msg->hot_spot_y);
|
|
|
|
/* Limit cursor size to prevent DoS */
|
|
if (cursor->header.width > STREAM_MSG_CURSOR_SET_MAX_WIDTH ||
|
|
cursor->header.height > STREAM_MSG_CURSOR_SET_MAX_HEIGHT) {
|
|
g_free(cmd);
|
|
return NULL;
|
|
}
|
|
|
|
const unsigned int cursor_bits = get_cursor_type_bits(cursor->header.type);
|
|
if (cursor_bits == 0) {
|
|
g_free(cmd);
|
|
return NULL;
|
|
}
|
|
|
|
/* Check that enough data has been sent for the cursor.
|
|
* Note that these computations can't overflow due to sizes checks
|
|
* above. */
|
|
size_t size_required = cursor->header.width * cursor->header.height;
|
|
size_required = SPICE_ALIGN(size_required * cursor_bits, 8) / 8u;
|
|
if (msg_size < sizeof(StreamMsgCursorSet) + size_required) {
|
|
g_free(cmd);
|
|
return NULL;
|
|
}
|
|
cursor->data_size = size_required;
|
|
cursor->data = g_memdup(msg->data, size_required);
|
|
return cmd;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_cursor_set(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
// Calculate the maximum size required to send the pixel data for a cursor that is the
|
|
// maximum size using the format that requires the largest number of bits per pixel
|
|
// (SPICE_CURSOR_TYPE_COLOR32 requires 33 bits per pixel, see get_cursor_type_bits())
|
|
const unsigned int max_cursor_set_size =
|
|
sizeof(StreamMsgCursorSet) +
|
|
(STREAM_MSG_CURSOR_SET_MAX_WIDTH * 4 + (STREAM_MSG_CURSOR_SET_MAX_WIDTH + 7)/8)
|
|
* STREAM_MSG_CURSOR_SET_MAX_HEIGHT;
|
|
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
|
|
if (dev->hdr.size < sizeof(StreamMsgCursorSet) || dev->hdr.size > max_cursor_set_size) {
|
|
// we could skip the message but on the other hand the guest
|
|
// is buggy in any case
|
|
return handle_msg_invalid(dev, sin, "Cursor size is invalid");
|
|
}
|
|
|
|
// read part of the message till we get all
|
|
if (dev->msg_len < dev->hdr.size) {
|
|
dev->msg = g_realloc(dev->msg, dev->hdr.size);
|
|
dev->msg_len = dev->hdr.size;
|
|
}
|
|
int n = sif->read(sin, dev->msg->buf + dev->msg_pos, dev->hdr.size - dev->msg_pos);
|
|
if (n <= 0) {
|
|
return false;
|
|
}
|
|
dev->msg_pos += n;
|
|
if (dev->msg_pos != dev->hdr.size) {
|
|
return false;
|
|
}
|
|
|
|
// transform the message to a cursor command and process it
|
|
RedCursorCmd *cmd = stream_msg_cursor_set_to_cursor_cmd(&dev->msg->cursor_set, dev->msg_pos);
|
|
if (!cmd) {
|
|
return handle_msg_invalid(dev, sin, NULL);
|
|
}
|
|
cursor_channel_process_cmd(dev->cursor_channel, cmd);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
handle_msg_cursor_move(StreamDevice *dev, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
int n = sif->read(sin, dev->msg->buf + dev->msg_pos, dev->hdr.size - dev->msg_pos);
|
|
if (n <= 0) {
|
|
return false;
|
|
}
|
|
dev->msg_pos += n;
|
|
if (dev->msg_pos != dev->hdr.size) {
|
|
return false;
|
|
}
|
|
|
|
StreamMsgCursorMove *move = &dev->msg->cursor_move;
|
|
move->x = GINT32_FROM_LE(move->x);
|
|
move->y = GINT32_FROM_LE(move->y);
|
|
|
|
RedCursorCmd *cmd = g_new0(RedCursorCmd, 1);
|
|
cmd->type = QXL_CURSOR_MOVE;
|
|
cmd->u.position.x = move->x;
|
|
cmd->u.position.y = move->y;
|
|
|
|
cursor_channel_process_cmd(dev->cursor_channel, cmd);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
stream_device_send_msg_to_client(RedCharDevice *self, RedPipeItem *msg, RedClient *client)
|
|
{
|
|
}
|
|
|
|
static void
|
|
stream_device_remove_client(RedCharDevice *self, RedClient *client)
|
|
{
|
|
}
|
|
|
|
static void
|
|
stream_device_stream_start(void *opaque, StreamMsgStartStop *start,
|
|
StreamChannel *stream_channel G_GNUC_UNUSED)
|
|
{
|
|
StreamDevice *dev = (StreamDevice *) opaque;
|
|
|
|
if (!dev->opened) {
|
|
return;
|
|
}
|
|
|
|
int msg_size = sizeof(*start) + sizeof(start->codecs[0]) * start->num_codecs;
|
|
int total_size = sizeof(StreamDevHeader) + msg_size;
|
|
|
|
RedCharDevice *char_dev = RED_CHAR_DEVICE(dev);
|
|
RedCharDeviceWriteBuffer *buf =
|
|
red_char_device_write_buffer_get_server(char_dev, total_size, false);
|
|
buf->buf_used = total_size;
|
|
|
|
StreamDevHeader *hdr = (StreamDevHeader *)buf->buf;
|
|
fill_dev_hdr(hdr, STREAM_TYPE_START_STOP, msg_size);
|
|
|
|
memcpy(&hdr[1], start, msg_size);
|
|
|
|
red_char_device_write_buffer_add(char_dev, buf);
|
|
}
|
|
|
|
static void
|
|
stream_device_stream_queue_stat(void *opaque, const StreamQueueStat *stats G_GNUC_UNUSED,
|
|
StreamChannel *stream_channel G_GNUC_UNUSED)
|
|
{
|
|
StreamDevice *dev = (StreamDevice *) opaque;
|
|
|
|
if (!dev->opened) {
|
|
return;
|
|
}
|
|
|
|
// very easy control flow... if any data stop
|
|
// this seems a very small queue but as we use tcp
|
|
// there's already that queue
|
|
if (stats->num_items) {
|
|
dev->flow_stopped = true;
|
|
return;
|
|
}
|
|
|
|
if (dev->flow_stopped) {
|
|
dev->flow_stopped = false;
|
|
// TODO resume flow...
|
|
// avoid recursion if we need to call get data from data handling from
|
|
// data handling
|
|
red_char_device_wakeup(&dev->parent);
|
|
}
|
|
}
|
|
|
|
StreamDevice *
|
|
stream_device_connect(RedsState *reds, SpiceCharDeviceInstance *sin)
|
|
{
|
|
SpiceCharDeviceInterface *sif;
|
|
|
|
StreamDevice *dev = stream_device_new(sin, reds);
|
|
|
|
sif = spice_char_device_get_interface(sin);
|
|
if (sif->state) {
|
|
sif->state(sin, 1);
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void
|
|
stream_device_dispose(GObject *object)
|
|
{
|
|
StreamDevice *dev = STREAM_DEVICE(object);
|
|
|
|
RedsState *reds = red_char_device_get_server(RED_CHAR_DEVICE(dev));
|
|
reds_core_timer_remove(reds, dev->close_timer);
|
|
|
|
if (dev->stream_channel) {
|
|
// close all current connections and drop the reference
|
|
red_channel_destroy(RED_CHANNEL(dev->stream_channel));
|
|
dev->stream_channel = NULL;
|
|
}
|
|
if (dev->cursor_channel) {
|
|
// close all current connections and drop the reference
|
|
red_channel_destroy(RED_CHANNEL(dev->cursor_channel));
|
|
dev->cursor_channel = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS(stream_device_parent_class)->dispose(object);
|
|
}
|
|
|
|
static void
|
|
stream_device_finalize(GObject *object)
|
|
{
|
|
StreamDevice *dev = STREAM_DEVICE(object);
|
|
|
|
g_free(dev->msg);
|
|
dev->msg = NULL;
|
|
dev->msg_len = 0;
|
|
dev->msg_pos = 0;
|
|
|
|
G_OBJECT_CLASS(stream_device_parent_class)->finalize(object);
|
|
}
|
|
|
|
void
|
|
stream_device_create_channel(StreamDevice *dev)
|
|
{
|
|
if (dev->stream_channel) {
|
|
return;
|
|
}
|
|
|
|
SpiceServer* reds = red_char_device_get_server(RED_CHAR_DEVICE(dev));
|
|
SpiceCoreInterfaceInternal* core = reds_get_core_interface(reds);
|
|
|
|
int id = reds_get_free_channel_id(reds, SPICE_CHANNEL_DISPLAY);
|
|
g_return_if_fail(id >= 0);
|
|
|
|
StreamChannel *stream_channel = stream_channel_new(reds, id);
|
|
CursorChannel *cursor_channel = cursor_channel_new(reds, id, core, NULL);
|
|
|
|
dev->stream_channel = stream_channel;
|
|
dev->cursor_channel = cursor_channel;
|
|
|
|
stream_channel_register_start_cb(stream_channel, stream_device_stream_start, dev);
|
|
stream_channel_register_queue_stat_cb(stream_channel, stream_device_stream_queue_stat, dev);
|
|
}
|
|
|
|
static void
|
|
reset_channels(StreamDevice *dev)
|
|
{
|
|
if (dev->stream_channel) {
|
|
stream_channel_reset(dev->stream_channel);
|
|
}
|
|
}
|
|
|
|
static void
|
|
char_device_set_state(RedCharDevice *char_dev, int state)
|
|
{
|
|
SpiceCharDeviceInstance *sin = NULL;
|
|
g_object_get(char_dev, "sin", &sin, NULL);
|
|
spice_assert(sin != NULL);
|
|
|
|
SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
|
|
if (sif->state) {
|
|
sif->state(sin, state);
|
|
}
|
|
}
|
|
|
|
static void
|
|
send_capabilities(RedCharDevice *char_dev)
|
|
{
|
|
int msg_size = MAX_GUEST_CAPABILITIES_BYTES;
|
|
int total_size = sizeof(StreamDevHeader) + msg_size;
|
|
|
|
RedCharDeviceWriteBuffer *buf =
|
|
red_char_device_write_buffer_get_server(char_dev, total_size, false);
|
|
buf->buf_used = total_size;
|
|
|
|
StreamDevHeader *const hdr = (StreamDevHeader *)buf->buf;
|
|
fill_dev_hdr(hdr, STREAM_TYPE_CAPABILITIES, msg_size);
|
|
|
|
StreamMsgCapabilities *const caps = (StreamMsgCapabilities *)(hdr+1);
|
|
memset(caps, 0, msg_size);
|
|
|
|
red_char_device_write_buffer_add(char_dev, buf);
|
|
}
|
|
|
|
static void
|
|
stream_device_port_event(RedCharDevice *char_dev, uint8_t event)
|
|
{
|
|
if (event != SPICE_PORT_EVENT_OPENED && event != SPICE_PORT_EVENT_CLOSED) {
|
|
return;
|
|
}
|
|
|
|
StreamDevice *dev = STREAM_DEVICE(char_dev);
|
|
|
|
// reset device and channel on close/open
|
|
dev->opened = (event == SPICE_PORT_EVENT_OPENED);
|
|
if (dev->opened) {
|
|
stream_device_create_channel(dev);
|
|
|
|
send_capabilities(char_dev);
|
|
}
|
|
dev->hdr_pos = 0;
|
|
dev->msg_pos = 0;
|
|
dev->has_error = false;
|
|
dev->flow_stopped = false;
|
|
red_char_device_reset(char_dev);
|
|
reset_channels(dev);
|
|
|
|
// enable the device again. We re-enable it on close as otherwise we don't want to get a
|
|
// failure when we try to re-open the device as would happen if we keep it disabled
|
|
char_device_set_state(char_dev, 1);
|
|
}
|
|
|
|
static void
|
|
stream_device_class_init(StreamDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
RedCharDeviceClass *char_dev_class = RED_CHAR_DEVICE_CLASS(klass);
|
|
|
|
object_class->dispose = stream_device_dispose;
|
|
object_class->finalize = stream_device_finalize;
|
|
|
|
char_dev_class->read_one_msg_from_device = stream_device_read_msg_from_dev;
|
|
char_dev_class->send_msg_to_client = stream_device_send_msg_to_client;
|
|
char_dev_class->remove_client = stream_device_remove_client;
|
|
char_dev_class->port_event = stream_device_port_event;
|
|
}
|
|
|
|
static void
|
|
stream_device_init(StreamDevice *dev)
|
|
{
|
|
dev->msg = g_malloc(sizeof(*dev->msg));
|
|
dev->msg_len = sizeof(*dev->msg);
|
|
}
|
|
|
|
const StreamDeviceDisplayInfo *stream_device_get_device_display_info(StreamDevice *dev)
|
|
{
|
|
return &dev->device_display_info;
|
|
}
|
|
|
|
int32_t stream_device_get_stream_channel_id(StreamDevice *dev)
|
|
{
|
|
if (dev->stream_channel == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t channel_id;
|
|
g_object_get(dev->stream_channel, "id", &channel_id, NULL);
|
|
return channel_id;
|
|
}
|
|
|
|
static StreamDevice *
|
|
stream_device_new(SpiceCharDeviceInstance *sin, RedsState *reds)
|
|
{
|
|
return g_object_new(TYPE_STREAM_DEVICE,
|
|
"sin", sin,
|
|
"spice-server", reds,
|
|
"client-tokens-interval", 0ULL,
|
|
"self-tokens", ~0ULL,
|
|
NULL);
|
|
}
|