[Spice-devel] [PATCH 10/22] server: rename files
Fabiano FidĂȘncio
fidencio at redhat.com
Wed Dec 2 12:31:39 PST 2015
On Wed, Dec 2, 2015 at 5:19 PM, Frediano Ziglio <fziglio at redhat.com> wrote:
> ---
> server/Makefile.am | 56 +-
> server/char-device.c | 1035 ++++++++++++++++++
> server/char-device.h | 216 ++++
> server/char_device.c | 1035 ------------------
> server/char_device.h | 216 ----
> server/dcc-encoders.h | 10 +-
> server/display-channel.h | 14 +-
> server/glz-encoder-dict.c | 633 +++++++++++
> server/glz-encoder-dict.h | 69 ++
> server/glz-encoder-priv.h | 186 ++++
> server/glz-encoder.c | 311 ++++++
> server/glz-encoder.h | 55 +
> server/glz_encoder.c | 311 ------
> server/glz_encoder.h | 55 -
> server/glz_encoder_dictionary.c | 633 -----------
> server/glz_encoder_dictionary.h | 69 --
> server/glz_encoder_dictionary_protected.h | 186 ----
> server/image-cache.c | 214 ++++
> server/image-cache.h | 65 ++
> server/inputs-channel.c | 679 ++++++++++++
> server/inputs-channel.h | 38 +
> server/inputs_channel.c | 679 ------------
> server/inputs_channel.h | 38 -
> server/jpeg-encoder.c | 248 +++++
> server/jpeg-encoder.h | 61 ++
> server/jpeg_encoder.c | 248 -----
> server/jpeg_encoder.h | 61 --
> server/main-channel.c | 1345 ++++++++++++++++++++++++
> server/main-channel.h | 103 ++
> server/main-dispatcher.c | 217 ++++
> server/main-dispatcher.h | 36 +
> server/main_channel.c | 1345 ------------------------
> server/main_channel.h | 103 --
> server/main_dispatcher.c | 217 ----
> server/main_dispatcher.h | 36 -
> server/memslot.c | 184 ++++
> server/memslot.h | 72 ++
> server/migration-protocol.h | 213 ++++
> server/migration_protocol.h | 213 ----
> server/mjpeg-encoder.c | 1375 ++++++++++++++++++++++++
> server/mjpeg-encoder.h | 100 ++
> server/mjpeg_encoder.c | 1375 ------------------------
> server/mjpeg_encoder.h | 100 --
> server/red_channel.c | 2 +-
> server/red_dispatcher.c | 2 +-
> server/red_memslots.c | 184 ----
> server/red_memslots.h | 72 --
> server/red_parse_qxl.c | 2 +-
> server/red_parse_qxl.h | 2 +-
> server/red_record_qxl.c | 4 +-
> server/red_record_qxl.h | 2 +-
> server/red_replay_qxl.c | 2 +-
> server/reds.c | 12 +-
> server/reds.h | 4 +-
> server/reds_stream.c | 2 +-
> server/reds_sw_canvas.c | 26 -
> server/reds_sw_canvas.h | 24 -
> server/smartcard.c | 4 +-
> server/snd_worker.c | 1625 -----------------------------
> server/snd_worker.h | 33 -
> server/sound.c | 1625 +++++++++++++++++++++++++++++
> server/sound.h | 33 +
> server/spice_image_cache.c | 214 ----
> server/spice_image_cache.h | 65 --
> server/spicevmc.c | 4 +-
> server/stream.h | 4 +-
> server/sw-canvas.c | 26 +
> server/sw-canvas.h | 24 +
> server/zlib-encoder.c | 125 +++
> server/zlib-encoder.h | 47 +
> server/zlib_encoder.c | 125 ---
> server/zlib_encoder.h | 47 -
> 72 files changed, 9398 insertions(+), 9398 deletions(-)
> create mode 100644 server/char-device.c
> create mode 100644 server/char-device.h
> delete mode 100644 server/char_device.c
> delete mode 100644 server/char_device.h
> create mode 100644 server/glz-encoder-dict.c
> create mode 100644 server/glz-encoder-dict.h
> create mode 100644 server/glz-encoder-priv.h
> create mode 100644 server/glz-encoder.c
> create mode 100644 server/glz-encoder.h
> delete mode 100644 server/glz_encoder.c
> delete mode 100644 server/glz_encoder.h
> delete mode 100644 server/glz_encoder_dictionary.c
> delete mode 100644 server/glz_encoder_dictionary.h
> delete mode 100644 server/glz_encoder_dictionary_protected.h
> create mode 100644 server/image-cache.c
> create mode 100644 server/image-cache.h
> create mode 100644 server/inputs-channel.c
> create mode 100644 server/inputs-channel.h
> delete mode 100644 server/inputs_channel.c
> delete mode 100644 server/inputs_channel.h
> create mode 100644 server/jpeg-encoder.c
> create mode 100644 server/jpeg-encoder.h
> delete mode 100644 server/jpeg_encoder.c
> delete mode 100644 server/jpeg_encoder.h
> create mode 100644 server/main-channel.c
> create mode 100644 server/main-channel.h
> create mode 100644 server/main-dispatcher.c
> create mode 100644 server/main-dispatcher.h
> delete mode 100644 server/main_channel.c
> delete mode 100644 server/main_channel.h
> delete mode 100644 server/main_dispatcher.c
> delete mode 100644 server/main_dispatcher.h
> create mode 100644 server/memslot.c
> create mode 100644 server/memslot.h
> create mode 100644 server/migration-protocol.h
> delete mode 100644 server/migration_protocol.h
> create mode 100644 server/mjpeg-encoder.c
> create mode 100644 server/mjpeg-encoder.h
> delete mode 100644 server/mjpeg_encoder.c
> delete mode 100644 server/mjpeg_encoder.h
> delete mode 100644 server/red_memslots.c
> delete mode 100644 server/red_memslots.h
> delete mode 100644 server/reds_sw_canvas.c
> delete mode 100644 server/reds_sw_canvas.h
> delete mode 100644 server/snd_worker.c
> delete mode 100644 server/snd_worker.h
> create mode 100644 server/sound.c
> create mode 100644 server/sound.h
> delete mode 100644 server/spice_image_cache.c
> delete mode 100644 server/spice_image_cache.h
> create mode 100644 server/sw-canvas.c
> create mode 100644 server/sw-canvas.h
> create mode 100644 server/zlib-encoder.c
> create mode 100644 server/zlib-encoder.h
> delete mode 100644 server/zlib_encoder.c
> delete mode 100644 server/zlib_encoder.h
>
> diff --git a/server/Makefile.am b/server/Makefile.am
> index 6b45a42..e964fb1 100644
> --- a/server/Makefile.am
> +++ b/server/Makefile.am
> @@ -66,25 +66,25 @@ libspice_server_la_SOURCES = \
> agent-msg-filter.c \
> agent-msg-filter.h \
> cache-item.h \
> - char_device.c \
> - char_device.h \
> + char-device.c \
> + char-device.h \
> demarshallers.h \
> - glz_encoder.c \
> - glz_encoder.h \
> + glz-encoder.c \
> + glz-encoder.h \
> glz_encoder_config.h \
> - glz_encoder_dictionary.c \
> - glz_encoder_dictionary.h \
> - glz_encoder_dictionary_protected.h \
> - inputs_channel.c \
> - inputs_channel.h \
> - jpeg_encoder.c \
> - jpeg_encoder.h \
> + glz-encoder-dict.c \
> + glz-encoder-dict.h \
> + glz-encoder-priv.h \
> + inputs-channel.c \
> + inputs-channel.h \
> + jpeg-encoder.c \
> + jpeg-encoder.h \
> lz4_encoder.c \
> lz4_encoder.h \
> - main_channel.c \
> - main_channel.h \
> - mjpeg_encoder.c \
> - mjpeg_encoder.h \
> + main-channel.c \
> + main-channel.h \
> + mjpeg-encoder.c \
> + mjpeg-encoder.h \
> red_channel.c \
> red_channel.h \
> red_common.h \
> @@ -92,11 +92,11 @@ libspice_server_la_SOURCES = \
> dispatcher.h \
> red_dispatcher.c \
> red_dispatcher.h \
> - main_dispatcher.c \
> - main_dispatcher.h \
> - migration_protocol.h \
> - red_memslots.c \
> - red_memslots.h \
> + main-dispatcher.c \
> + main-dispatcher.h \
> + migration-protocol.h \
> + memslot.c \
> + memslot.h \
> red_parse_qxl.c \
> red_record_qxl.c \
> red_record_qxl.h \
> @@ -114,20 +114,20 @@ libspice_server_la_SOURCES = \
> reds-private.h \
> reds_stream.c \
> reds_stream.h \
> - reds_sw_canvas.c \
> - reds_sw_canvas.h \
> - snd_worker.c \
> - snd_worker.h \
> + sw-canvas.c \
> + sw-canvas.h \
> + sound.c \
> + sound.h \
> stat.h \
> spicevmc.c \
> spice_timer_queue.c \
> spice_timer_queue.h \
> - zlib_encoder.c \
> - zlib_encoder.h \
> + zlib-encoder.c \
> + zlib-encoder.h \
> spice_bitmap_utils.h \
> spice_bitmap_utils.c \
> - spice_image_cache.h \
> - spice_image_cache.c \
> + image-cache.h \
> + image-cache.c \
> pixmap-cache.h \
> pixmap-cache.c \
> tree.h \
> diff --git a/server/char-device.c b/server/char-device.c
> new file mode 100644
> index 0000000..3790fab
> --- /dev/null
> +++ b/server/char-device.c
> @@ -0,0 +1,1035 @@
> +/* spice-server char device flow control code
> +
> + Copyright (C) 2012 Red Hat, Inc.
> +
> + Red Hat Authors:
> + Yonit Halperin <yhalperi at redhat.com>
> +
> + 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/>.
> +*/
> +
> +
> +#include <config.h>
> +#include "char-device.h"
> +#include "red_channel.h"
> +#include "reds.h"
> +
> +#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100
> +#define SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000
> +#define MAX_POOL_SIZE (10 * 64 * 1024)
> +
> +typedef struct SpiceCharDeviceClientState SpiceCharDeviceClientState;
> +struct SpiceCharDeviceClientState {
> + RingItem link;
> + SpiceCharDeviceState *dev;
> + RedClient *client;
> + int do_flow_control;
> + uint64_t num_client_tokens;
> + uint64_t num_client_tokens_free; /* client messages that were consumed by the device */
> + uint64_t num_send_tokens; /* send to client */
> + SpiceTimer *wait_for_tokens_timer;
> + int wait_for_tokens_started;
> + Ring send_queue;
> + uint32_t send_queue_size;
> + uint32_t max_send_queue_size;
> +};
> +
> +struct SpiceCharDeviceState {
> + int running;
> + int active; /* has read/write been performed since the device was started */
> + int wait_for_migrate_data;
> + uint32_t refs;
> +
> + Ring write_queue;
> + Ring write_bufs_pool;
> + uint64_t cur_pool_size;
> + SpiceCharDeviceWriteBuffer *cur_write_buf;
> + uint8_t *cur_write_buf_pos;
> + SpiceTimer *write_to_dev_timer;
> + uint64_t num_self_tokens;
> +
> + Ring clients; /* list of SpiceCharDeviceClientState */
> + uint32_t num_clients;
> +
> + uint64_t client_tokens_interval; /* frequency of returning tokens to the client */
> + SpiceCharDeviceInstance *sin;
> +
> + int during_read_from_device;
> + int during_write_to_device;
> +
> + SpiceCharDeviceCallbacks cbs;
> + void *opaque;
> +};
> +
> +enum {
> + WRITE_BUFFER_ORIGIN_NONE,
> + WRITE_BUFFER_ORIGIN_CLIENT,
> + WRITE_BUFFER_ORIGIN_SERVER,
> + WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN,
> +};
> +
> +/* Holding references for avoiding access violation if the char device was
> + * destroyed during a callback */
> +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev);
> +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev);
> +static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf);
> +
> +static void spice_char_dev_write_retry(void *opaque);
> +
> +typedef struct SpiceCharDeviceMsgToClientItem {
> + RingItem link;
> + SpiceCharDeviceMsgToClient *msg;
> +} SpiceCharDeviceMsgToClientItem;
> +
> +static void spice_char_device_write_buffer_free(SpiceCharDeviceWriteBuffer *buf)
> +{
> + if (buf == NULL)
> + return;
> +
> + free(buf->buf);
> + free(buf);
> +}
> +
> +static void write_buffers_queue_free(Ring *write_queue)
> +{
> + while (!ring_is_empty(write_queue)) {
> + RingItem *item = ring_get_tail(write_queue);
> + SpiceCharDeviceWriteBuffer *buf;
> +
> + ring_remove(item);
> + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + spice_char_device_write_buffer_free(buf);
> + }
> +}
> +
> +static void spice_char_device_write_buffer_pool_add(SpiceCharDeviceState *dev,
> + SpiceCharDeviceWriteBuffer *buf)
> +{
> + if (buf->refs == 1 &&
> + dev->cur_pool_size < MAX_POOL_SIZE) {
> + buf->buf_used = 0;
> + buf->origin = WRITE_BUFFER_ORIGIN_NONE;
> + buf->client = NULL;
> + dev->cur_pool_size += buf->buf_size;
> + ring_add(&dev->write_bufs_pool, &buf->link);
> + return;
> + }
> +
> + /* Buffer still being used - just unref for the caller */
> + spice_char_device_write_buffer_unref(buf);
> +}
> +
> +static void spice_char_device_client_send_queue_free(SpiceCharDeviceState *dev,
> + SpiceCharDeviceClientState *dev_client)
> +{
> + spice_debug("send_queue_empty %d", ring_is_empty(&dev_client->send_queue));
> + while (!ring_is_empty(&dev_client->send_queue)) {
> + RingItem *item = ring_get_tail(&dev_client->send_queue);
> + SpiceCharDeviceMsgToClientItem *msg_item = SPICE_CONTAINEROF(item,
> + SpiceCharDeviceMsgToClientItem,
> + link);
> +
> + ring_remove(item);
> + dev->cbs.unref_msg_to_client(msg_item->msg, dev->opaque);
> + free(msg_item);
> + }
> + dev_client->num_send_tokens += dev_client->send_queue_size;
> + dev_client->send_queue_size = 0;
> +}
> +
> +static void spice_char_device_client_free(SpiceCharDeviceState *dev,
> + SpiceCharDeviceClientState *dev_client)
> +{
> + RingItem *item, *next;
> +
> + if (dev_client->wait_for_tokens_timer) {
> + core->timer_remove(dev_client->wait_for_tokens_timer);
> + }
> +
> + spice_char_device_client_send_queue_free(dev, dev_client);
> +
> + /* remove write buffers that are associated with the client */
> + spice_debug("write_queue_is_empty %d", ring_is_empty(&dev->write_queue) && !dev->cur_write_buf);
> + RING_FOREACH_SAFE(item, next, &dev->write_queue) {
> + SpiceCharDeviceWriteBuffer *write_buf;
> +
> + write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> + write_buf->client == dev_client->client) {
> + ring_remove(item);
> + spice_char_device_write_buffer_pool_add(dev, write_buf);
> + }
> + }
> +
> + if (dev->cur_write_buf && dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> + dev->cur_write_buf->client == dev_client->client) {
> + dev->cur_write_buf->origin = WRITE_BUFFER_ORIGIN_NONE;
> + dev->cur_write_buf->client = NULL;
> + }
> +
> + dev->num_clients--;
> + ring_remove(&dev_client->link);
> + free(dev_client);
> +}
> +
> +static void spice_char_device_handle_client_overflow(SpiceCharDeviceClientState *dev_client)
> +{
> + SpiceCharDeviceState *dev = dev_client->dev;
> + spice_printerr("dev %p client %p ", dev, dev_client);
> + dev->cbs.remove_client(dev_client->client, dev->opaque);
> +}
> +
> +static SpiceCharDeviceClientState *spice_char_device_client_find(SpiceCharDeviceState *dev,
> + RedClient *client)
> +{
> + RingItem *item;
> +
> + RING_FOREACH(item, &dev->clients) {
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> + if (dev_client->client == client) {
> + return dev_client;
> + }
> + }
> + return NULL;
> +}
> +
> +/***************************
> + * Reading from the device *
> + **************************/
> +
> +static void device_client_wait_for_tokens_timeout(void *opaque)
> +{
> + SpiceCharDeviceClientState *dev_client = opaque;
> +
> + spice_char_device_handle_client_overflow(dev_client);
> +}
> +
> +static int spice_char_device_can_send_to_client(SpiceCharDeviceClientState *dev_client)
> +{
> + return !dev_client->do_flow_control || dev_client->num_send_tokens;
> +}
> +
> +static uint64_t spice_char_device_max_send_tokens(SpiceCharDeviceState *dev)
> +{
> + RingItem *item;
> + uint64_t max = 0;
> +
> + RING_FOREACH(item, &dev->clients) {
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> +
> + if (!dev_client->do_flow_control) {
> + max = ~0;
> + break;
> + }
> +
> + if (dev_client->num_send_tokens > max) {
> + max = dev_client->num_send_tokens;
> + }
> + }
> + return max;
> +}
> +
> +static void spice_char_device_add_msg_to_client_queue(SpiceCharDeviceClientState *dev_client,
> + SpiceCharDeviceMsgToClient *msg)
> +{
> + SpiceCharDeviceState *dev = dev_client->dev;
> + SpiceCharDeviceMsgToClientItem *msg_item;
> +
> + if (dev_client->send_queue_size >= dev_client->max_send_queue_size) {
> + spice_char_device_handle_client_overflow(dev_client);
> + return;
> + }
> +
> + msg_item = spice_new0(SpiceCharDeviceMsgToClientItem, 1);
> + msg_item->msg = dev->cbs.ref_msg_to_client(msg, dev->opaque);
> + ring_add(&dev_client->send_queue, &msg_item->link);
> + dev_client->send_queue_size++;
> + if (!dev_client->wait_for_tokens_started) {
> + core->timer_start(dev_client->wait_for_tokens_timer,
> + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
> + dev_client->wait_for_tokens_started = TRUE;
> + }
> +}
> +
> +static void spice_char_device_send_msg_to_clients(SpiceCharDeviceState *dev,
> + SpiceCharDeviceMsgToClient *msg)
> +{
> + RingItem *item, *next;
> +
> + RING_FOREACH_SAFE(item, next, &dev->clients) {
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> + if (spice_char_device_can_send_to_client(dev_client)) {
> + dev_client->num_send_tokens--;
> + spice_assert(ring_is_empty(&dev_client->send_queue));
> + dev->cbs.send_msg_to_client(msg, dev_client->client, dev->opaque);
> +
> + /* don't refer to dev_client anymore, it may have been released */
> + } else {
> + spice_char_device_add_msg_to_client_queue(dev_client, msg);
> + }
> + }
> +}
> +
> +static int spice_char_device_read_from_device(SpiceCharDeviceState *dev)
> +{
> + uint64_t max_send_tokens;
> + int did_read = FALSE;
> +
> + if (!dev->running || dev->wait_for_migrate_data || !dev->sin) {
> + return FALSE;
> + }
> +
> + /* There are 2 scenarios where we can get called recursively:
> + * 1) spice-vmc vmc_read triggering flush of throttled data, recalling wakeup
> + * (virtio)
> + * 2) in case of sending messages to the client, and unreferencing the
> + * msg, we trigger another read.
> + */
> + if (dev->during_read_from_device++ > 0) {
> + return FALSE;
> + }
> +
> + max_send_tokens = spice_char_device_max_send_tokens(dev);
> + spice_char_device_state_ref(dev);
> + /*
> + * Reading from the device only in case at least one of the clients have a free token.
> + * All messages will be discarded if no client is attached to the device
> + */
> + while ((max_send_tokens || ring_is_empty(&dev->clients)) && dev->running) {
> + SpiceCharDeviceMsgToClient *msg;
> +
> + msg = dev->cbs.read_one_msg_from_device(dev->sin, dev->opaque);
> + if (!msg) {
> + if (dev->during_read_from_device > 1) {
> + dev->during_read_from_device = 1;
> + continue; /* a wakeup might have been called during the read -
> + make sure it doesn't get lost */
> + }
> + break;
> + }
> + did_read = TRUE;
> + spice_char_device_send_msg_to_clients(dev, msg);
> + dev->cbs.unref_msg_to_client(msg, dev->opaque);
> + max_send_tokens--;
> + }
> + dev->during_read_from_device = 0;
> + if (dev->running) {
> + dev->active = dev->active || did_read;
> + }
> + spice_char_device_state_unref(dev);
> + return did_read;
> +}
> +
> +static void spice_char_device_client_send_queue_push(SpiceCharDeviceClientState *dev_client)
> +{
> + RingItem *item;
> + while ((item = ring_get_tail(&dev_client->send_queue)) &&
> + spice_char_device_can_send_to_client(dev_client)) {
> + SpiceCharDeviceMsgToClientItem *msg_item;
> +
> + msg_item = SPICE_CONTAINEROF(item, SpiceCharDeviceMsgToClientItem, link);
> + ring_remove(item);
> +
> + dev_client->num_send_tokens--;
> + dev_client->dev->cbs.send_msg_to_client(msg_item->msg,
> + dev_client->client,
> + dev_client->dev->opaque);
> + dev_client->dev->cbs.unref_msg_to_client(msg_item->msg, dev_client->dev->opaque);
> + dev_client->send_queue_size--;
> + free(msg_item);
> + }
> +}
> +
> +static void spice_char_device_send_to_client_tokens_absorb(SpiceCharDeviceClientState *dev_client,
> + uint32_t tokens)
> +{
> + dev_client->num_send_tokens += tokens;
> +
> + if (dev_client->send_queue_size) {
> + spice_assert(dev_client->num_send_tokens == tokens);
> + spice_char_device_client_send_queue_push(dev_client);
> + }
> +
> + if (spice_char_device_can_send_to_client(dev_client)) {
> + core->timer_cancel(dev_client->wait_for_tokens_timer);
> + dev_client->wait_for_tokens_started = FALSE;
> + spice_char_device_read_from_device(dev_client->dev);
> + } else if (dev_client->send_queue_size) {
> + core->timer_start(dev_client->wait_for_tokens_timer,
> + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
> + dev_client->wait_for_tokens_started = TRUE;
> + }
> +}
> +
> +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev,
> + RedClient *client,
> + uint32_t tokens)
> +{
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = spice_char_device_client_find(dev, client);
> +
> + if (!dev_client) {
> + spice_error("client wasn't found dev %p client %p", dev, client);
> + return;
> + }
> + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens);
> +}
> +
> +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev,
> + RedClient *client,
> + uint32_t tokens)
> +{
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = spice_char_device_client_find(dev, client);
> +
> + if (!dev_client) {
> + spice_error("client wasn't found dev %p client %p", dev, client);
> + return;
> + }
> +
> + dev_client->num_send_tokens = 0;
> + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens);
> +}
> +
> +/**************************
> + * Writing to the device *
> +***************************/
> +
> +static void spice_char_device_client_tokens_add(SpiceCharDeviceState *dev,
> + SpiceCharDeviceClientState *dev_client,
> + uint32_t num_tokens)
> +{
> + if (!dev_client->do_flow_control) {
> + return;
> + }
> + if (num_tokens > 1) {
> + spice_debug("#tokens > 1 (=%u)", num_tokens);
> + }
> + dev_client->num_client_tokens_free += num_tokens;
> + if (dev_client->num_client_tokens_free >= dev->client_tokens_interval) {
> + uint32_t tokens = dev_client->num_client_tokens_free;
> +
> + dev_client->num_client_tokens += dev_client->num_client_tokens_free;
> + dev_client->num_client_tokens_free = 0;
> + dev->cbs.send_tokens_to_client(dev_client->client,
> + tokens,
> + dev->opaque);
> + }
> +}
> +
> +static int spice_char_device_write_to_device(SpiceCharDeviceState *dev)
> +{
> + SpiceCharDeviceInterface *sif;
> + int total = 0;
> + int n;
> +
> + if (!dev->running || dev->wait_for_migrate_data || !dev->sin) {
> + return 0;
> + }
> +
> + /* protect against recursion with spice_char_device_wakeup */
> + if (dev->during_write_to_device++ > 0) {
> + return 0;
> + }
> +
> + spice_char_device_state_ref(dev);
> +
> + if (dev->write_to_dev_timer) {
> + core->timer_cancel(dev->write_to_dev_timer);
> + }
> +
> + sif = SPICE_CONTAINEROF(dev->sin->base.sif, SpiceCharDeviceInterface, base);
> + while (dev->running) {
> + uint32_t write_len;
> +
> + if (!dev->cur_write_buf) {
> + RingItem *item = ring_get_tail(&dev->write_queue);
> + if (!item) {
> + break;
> + }
> + dev->cur_write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + dev->cur_write_buf_pos = dev->cur_write_buf->buf;
> + ring_remove(item);
> + }
> +
> + write_len = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used -
> + dev->cur_write_buf_pos;
> + n = sif->write(dev->sin, dev->cur_write_buf_pos, write_len);
> + if (n <= 0) {
> + if (dev->during_write_to_device > 1) {
> + dev->during_write_to_device = 1;
> + continue; /* a wakeup might have been called during the write -
> + make sure it doesn't get lost */
> + }
> + break;
> + }
> + total += n;
> + write_len -= n;
> + if (!write_len) {
> + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf;
> + dev->cur_write_buf = NULL;
> + spice_char_device_write_buffer_release(dev, release_buf);
> + continue;
> + }
> + dev->cur_write_buf_pos += n;
> + }
> + /* retry writing as long as the write queue is not empty */
> + if (dev->running) {
> + if (dev->cur_write_buf) {
> + if (dev->write_to_dev_timer) {
> + core->timer_start(dev->write_to_dev_timer,
> + CHAR_DEVICE_WRITE_TO_TIMEOUT);
> + }
> + } else {
> + spice_assert(ring_is_empty(&dev->write_queue));
> + }
> + dev->active = dev->active || total;
> + }
> + dev->during_write_to_device = 0;
> + spice_char_device_state_unref(dev);
> + return total;
> +}
> +
> +static void spice_char_dev_write_retry(void *opaque)
> +{
> + SpiceCharDeviceState *dev = opaque;
> +
> + if (dev->write_to_dev_timer) {
> + core->timer_cancel(dev->write_to_dev_timer);
> + }
> + spice_char_device_write_to_device(dev);
> +}
> +
> +static SpiceCharDeviceWriteBuffer *__spice_char_device_write_buffer_get(
> + SpiceCharDeviceState *dev, RedClient *client,
> + int size, int origin, int migrated_data_tokens)
> +{
> + RingItem *item;
> + SpiceCharDeviceWriteBuffer *ret;
> +
> + if (origin == WRITE_BUFFER_ORIGIN_SERVER && !dev->num_self_tokens) {
> + return NULL;
> + }
> +
> + if ((item = ring_get_tail(&dev->write_bufs_pool))) {
> + ret = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + ring_remove(item);
> + dev->cur_pool_size -= ret->buf_size;
> + } else {
> + ret = spice_new0(SpiceCharDeviceWriteBuffer, 1);
> + }
> +
> + spice_assert(!ret->buf_used);
> +
> + if (ret->buf_size < size) {
> + ret->buf = spice_realloc(ret->buf, size);
> + ret->buf_size = size;
> + }
> + ret->origin = origin;
> +
> + if (origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> + spice_assert(client);
> + SpiceCharDeviceClientState *dev_client = spice_char_device_client_find(dev, client);
> + if (dev_client) {
> + if (!migrated_data_tokens &&
> + dev_client->do_flow_control && !dev_client->num_client_tokens) {
> + spice_printerr("token violation: dev %p client %p", dev, client);
> + spice_char_device_handle_client_overflow(dev_client);
> + goto error;
> + }
> + ret->client = client;
> + if (!migrated_data_tokens && dev_client->do_flow_control) {
> + dev_client->num_client_tokens--;
> + }
> + } else {
> + /* it is possible that the client was removed due to send tokens underflow, but
> + * the caller still receive messages from the client */
> + spice_printerr("client not found: dev %p client %p", dev, client);
> + goto error;
> + }
> + } else if (origin == WRITE_BUFFER_ORIGIN_SERVER) {
> + dev->num_self_tokens--;
> + }
> +
> + ret->token_price = migrated_data_tokens ? migrated_data_tokens : 1;
> + ret->refs = 1;
> + return ret;
> +error:
> + dev->cur_pool_size += ret->buf_size;
> + ring_add(&dev->write_bufs_pool, &ret->link);
> + return NULL;
> +}
> +
> +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev,
> + RedClient *client,
> + int size)
> +{
> + return __spice_char_device_write_buffer_get(dev, client, size,
> + client ? WRITE_BUFFER_ORIGIN_CLIENT : WRITE_BUFFER_ORIGIN_SERVER,
> + 0);
> +}
> +
> +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token(
> + SpiceCharDeviceState *dev, int size)
> +{
> + return __spice_char_device_write_buffer_get(dev, NULL, size,
> + WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, 0);
> +}
> +
> +static SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_ref(SpiceCharDeviceWriteBuffer *write_buf)
> +{
> + spice_assert(write_buf);
> +
> + write_buf->refs++;
> + return write_buf;
> +}
> +
> +static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf)
> +{
> + spice_assert(write_buf);
> +
> + write_buf->refs--;
> + if (write_buf->refs == 0)
> + spice_char_device_write_buffer_free(write_buf);
> +}
> +
> +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev,
> + SpiceCharDeviceWriteBuffer *write_buf)
> +{
> + spice_assert(dev);
> + /* caller shouldn't add buffers for client that was removed */
> + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> + !spice_char_device_client_find(dev, write_buf->client)) {
> + spice_printerr("client not found: dev %p client %p", dev, write_buf->client);
> + spice_char_device_write_buffer_pool_add(dev, write_buf);
> + return;
> + }
> +
> + ring_add(&dev->write_queue, &write_buf->link);
> + spice_char_device_write_to_device(dev);
> +}
> +
> +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev,
> + SpiceCharDeviceWriteBuffer *write_buf)
> +{
> + int buf_origin = write_buf->origin;
> + uint32_t buf_token_price = write_buf->token_price;
> + RedClient *client = write_buf->client;
> +
> + spice_assert(!ring_item_is_linked(&write_buf->link));
> + if (!dev) {
> + spice_printerr("no device. write buffer is freed");
> + spice_char_device_write_buffer_free(write_buf);
> + return;
> + }
> +
> + spice_assert(dev->cur_write_buf != write_buf);
> +
> + spice_char_device_write_buffer_pool_add(dev, write_buf);
> + if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> + SpiceCharDeviceClientState *dev_client;
> +
> + spice_assert(client);
> + dev_client = spice_char_device_client_find(dev, client);
> + /* when a client is removed, we remove all the buffers that are associated with it */
> + spice_assert(dev_client);
> + spice_char_device_client_tokens_add(dev, dev_client, buf_token_price);
> + } else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) {
> + dev->num_self_tokens++;
> + if (dev->cbs.on_free_self_token) {
> + dev->cbs.on_free_self_token(dev->opaque);
> + }
> + }
> +}
> +
> +/********************************
> + * char_device_state management *
> + ********************************/
> +
> +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin,
> + uint32_t client_tokens_interval,
> + uint32_t self_tokens,
> + SpiceCharDeviceCallbacks *cbs,
> + void *opaque)
> +{
> + SpiceCharDeviceState *char_dev;
> + SpiceCharDeviceInterface *sif;
> +
> + spice_assert(sin);
> + spice_assert(cbs->read_one_msg_from_device && cbs->ref_msg_to_client &&
> + cbs->unref_msg_to_client && cbs->send_msg_to_client &&
> + cbs->send_tokens_to_client && cbs->remove_client);
> +
> + char_dev = spice_new0(SpiceCharDeviceState, 1);
> + char_dev->sin = sin;
> + char_dev->cbs = *cbs;
> + char_dev->opaque = opaque;
> + char_dev->client_tokens_interval = client_tokens_interval;
> + char_dev->num_self_tokens = self_tokens;
> +
> + ring_init(&char_dev->write_queue);
> + ring_init(&char_dev->write_bufs_pool);
> + ring_init(&char_dev->clients);
> +
> + sif = SPICE_CONTAINEROF(char_dev->sin->base.sif, SpiceCharDeviceInterface, base);
> + if (sif->base.minor_version <= 2 ||
> + !(sif->flags & SPICE_CHAR_DEVICE_NOTIFY_WRITABLE)) {
> + char_dev->write_to_dev_timer = core->timer_add(spice_char_dev_write_retry, char_dev);
> + if (!char_dev->write_to_dev_timer) {
> + spice_error("failed creating char dev write timer");
> + }
> + }
> +
> + char_dev->refs = 1;
> + sin->st = char_dev;
> + spice_debug("sin %p dev_state %p", sin, char_dev);
> + return char_dev;
> +}
> +
> +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *state,
> + SpiceCharDeviceInstance *sin)
> +{
> + spice_debug("sin %p dev_state %p", sin, state);
> + state->sin = sin;
> + sin->st = state;
> +}
> +
> +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev)
> +{
> + return dev->opaque;
> +}
> +
> +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev)
> +{
> + char_dev->refs++;
> +}
> +
> +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev)
> +{
> + /* The refs field protects the char_dev from being deallocated in
> + * case spice_char_device_state_destroy has been called
> + * during a callabck, and we might still access the char_dev afterwards.
> + * spice_char_device_state_unref is always coupled with a preceding
> + * spice_char_device_state_ref. Here, refs can turn 0
> + * only when spice_char_device_state_destroy is called in between
> + * the calls to spice_char_device_state_ref and spice_char_device_state_unref.*/
> + if (!--char_dev->refs) {
> + free(char_dev);
> + }
> +}
> +
> +void spice_char_device_state_destroy(SpiceCharDeviceState *char_dev)
> +{
> + reds_on_char_device_state_destroy(char_dev);
> + if (char_dev->write_to_dev_timer) {
> + core->timer_remove(char_dev->write_to_dev_timer);
> + char_dev->write_to_dev_timer = NULL;
> + }
> + write_buffers_queue_free(&char_dev->write_queue);
> + write_buffers_queue_free(&char_dev->write_bufs_pool);
> + char_dev->cur_pool_size = 0;
> + spice_char_device_write_buffer_free(char_dev->cur_write_buf);
> + char_dev->cur_write_buf = NULL;
> +
> + while (!ring_is_empty(&char_dev->clients)) {
> + RingItem *item = ring_get_tail(&char_dev->clients);
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> + spice_char_device_client_free(char_dev, dev_client);
> + }
> + char_dev->running = FALSE;
> +
> + spice_char_device_state_unref(char_dev);
> +}
> +
> +int spice_char_device_client_add(SpiceCharDeviceState *dev,
> + RedClient *client,
> + int do_flow_control,
> + uint32_t max_send_queue_size,
> + uint32_t num_client_tokens,
> + uint32_t num_send_tokens,
> + int wait_for_migrate_data)
> +{
> + SpiceCharDeviceClientState *dev_client;
> +
> + spice_assert(dev);
> + spice_assert(client);
> +
> + if (wait_for_migrate_data && (dev->num_clients > 0 || dev->active)) {
> + spice_warning("can't restore device %p from migration data. The device "
> + "has already been active", dev);
> + return FALSE;
> + }
> +
> + dev->wait_for_migrate_data = wait_for_migrate_data;
> +
> + spice_debug("dev_state %p client %p", dev, client);
> + dev_client = spice_new0(SpiceCharDeviceClientState, 1);
> + dev_client->dev = dev;
> + dev_client->client = client;
> + ring_init(&dev_client->send_queue);
> + dev_client->send_queue_size = 0;
> + dev_client->max_send_queue_size = max_send_queue_size;
> + dev_client->do_flow_control = do_flow_control;
> + if (do_flow_control) {
> + dev_client->wait_for_tokens_timer = core->timer_add(device_client_wait_for_tokens_timeout,
> + dev_client);
> + if (!dev_client->wait_for_tokens_timer) {
> + spice_error("failed to create wait for tokens timer");
> + }
> + dev_client->num_client_tokens = num_client_tokens;
> + dev_client->num_send_tokens = num_send_tokens;
> + } else {
> + dev_client->num_client_tokens = ~0;
> + dev_client->num_send_tokens = ~0;
> + }
> + ring_add(&dev->clients, &dev_client->link);
> + dev->num_clients++;
> + /* Now that we have a client, forward any pending device data */
> + spice_char_device_wakeup(dev);
> + return TRUE;
> +}
> +
> +void spice_char_device_client_remove(SpiceCharDeviceState *dev,
> + RedClient *client)
> +{
> + SpiceCharDeviceClientState *dev_client;
> +
> + spice_debug("dev_state %p client %p", dev, client);
> + dev_client = spice_char_device_client_find(dev, client);
> +
> + if (!dev_client) {
> + spice_error("client wasn't found");
> + return;
> + }
> + spice_char_device_client_free(dev, dev_client);
> + if (dev->wait_for_migrate_data) {
> + spice_assert(dev->num_clients == 0);
> + dev->wait_for_migrate_data = FALSE;
> + spice_char_device_read_from_device(dev);
> + }
> +
> + if (dev->num_clients == 0) {
> + spice_debug("client removed, memory pool will be freed (%lu bytes)", dev->cur_pool_size);
> + write_buffers_queue_free(&dev->write_bufs_pool);
> + dev->cur_pool_size = 0;
> + }
> +}
> +
> +int spice_char_device_client_exists(SpiceCharDeviceState *dev,
> + RedClient *client)
> +{
> + return (spice_char_device_client_find(dev, client) != NULL);
> +}
> +
> +void spice_char_device_start(SpiceCharDeviceState *dev)
> +{
> + spice_debug("dev_state %p", dev);
> + dev->running = TRUE;
> + spice_char_device_state_ref(dev);
> + while (spice_char_device_write_to_device(dev) ||
> + spice_char_device_read_from_device(dev));
> + spice_char_device_state_unref(dev);
> +}
> +
> +void spice_char_device_stop(SpiceCharDeviceState *dev)
> +{
> + spice_debug("dev_state %p", dev);
> + dev->running = FALSE;
> + dev->active = FALSE;
> + if (dev->write_to_dev_timer) {
> + core->timer_cancel(dev->write_to_dev_timer);
> + }
> +}
> +
> +void spice_char_device_reset(SpiceCharDeviceState *dev)
> +{
> + RingItem *client_item;
> +
> + spice_char_device_stop(dev);
> + dev->wait_for_migrate_data = FALSE;
> + spice_debug("dev_state %p", dev);
> + while (!ring_is_empty(&dev->write_queue)) {
> + RingItem *item = ring_get_tail(&dev->write_queue);
> + SpiceCharDeviceWriteBuffer *buf;
> +
> + ring_remove(item);
> + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + /* tracking the tokens */
> + spice_char_device_write_buffer_release(dev, buf);
> + }
> + if (dev->cur_write_buf) {
> + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf;
> +
> + dev->cur_write_buf = NULL;
> + spice_char_device_write_buffer_release(dev, release_buf);
> + }
> +
> + RING_FOREACH(client_item, &dev->clients) {
> + SpiceCharDeviceClientState *dev_client;
> +
> + dev_client = SPICE_CONTAINEROF(client_item, SpiceCharDeviceClientState, link);
> + spice_char_device_client_send_queue_free(dev, dev_client);
> + }
> + dev->sin = NULL;
> +}
> +
> +void spice_char_device_wakeup(SpiceCharDeviceState *dev)
> +{
> + spice_char_device_write_to_device(dev);
> + spice_char_device_read_from_device(dev);
> +}
> +
> +/*************
> + * Migration *
> + * **********/
> +
> +void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m)
> +{
> + SpiceMigrateDataCharDevice *mig_data;
> +
> + spice_debug(NULL);
> + mig_data = (SpiceMigrateDataCharDevice *)spice_marshaller_reserve_space(m,
> + sizeof(*mig_data));
> + memset(mig_data, 0, sizeof(*mig_data));
> + mig_data->version = SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION;
> + mig_data->connected = FALSE;
> +}
> +
> +static void migrate_data_marshaller_write_buffer_free(uint8_t *data, void *opaque)
> +{
> + SpiceCharDeviceWriteBuffer *write_buf = (SpiceCharDeviceWriteBuffer *)opaque;
> +
> + spice_char_device_write_buffer_unref(write_buf);
> +}
> +
> +void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev,
> + SpiceMarshaller *m)
> +{
> + SpiceCharDeviceClientState *client_state;
> + RingItem *item;
> + uint32_t *write_to_dev_size_ptr;
> + uint32_t *write_to_dev_tokens_ptr;
> + SpiceMarshaller *m2;
> +
> + /* multi-clients are not supported */
> + spice_assert(dev->num_clients == 1);
> + client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients),
> + SpiceCharDeviceClientState,
> + link);
> + /* FIXME: if there were more than one client before the marshalling,
> + * it is possible that the send_queue_size > 0, and the send data
> + * should be migrated as well */
> + spice_assert(client_state->send_queue_size == 0);
> + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
> + spice_marshaller_add_uint8(m, 1); /* connected */
> + spice_marshaller_add_uint32(m, client_state->num_client_tokens);
> + spice_marshaller_add_uint32(m, client_state->num_send_tokens);
> + write_to_dev_size_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t));
> + write_to_dev_tokens_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t));
> + *write_to_dev_size_ptr = 0;
> + *write_to_dev_tokens_ptr = 0;
> +
> + m2 = spice_marshaller_get_ptr_submarshaller(m, 0);
> + if (dev->cur_write_buf) {
> + uint32_t buf_remaining = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used -
> + dev->cur_write_buf_pos;
> + spice_marshaller_add_ref_full(m2, dev->cur_write_buf_pos, buf_remaining,
> + migrate_data_marshaller_write_buffer_free,
> + spice_char_device_write_buffer_ref(dev->cur_write_buf)
> + );
> + *write_to_dev_size_ptr += buf_remaining;
> + if (dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> + spice_assert(dev->cur_write_buf->client == client_state->client);
> + (*write_to_dev_tokens_ptr) += dev->cur_write_buf->token_price;
> + }
> + }
> +
> + RING_FOREACH_REVERSED(item, &dev->write_queue) {
> + SpiceCharDeviceWriteBuffer *write_buf;
> +
> + write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> + spice_marshaller_add_ref_full(m2, write_buf->buf, write_buf->buf_used,
> + migrate_data_marshaller_write_buffer_free,
> + spice_char_device_write_buffer_ref(write_buf)
> + );
> + *write_to_dev_size_ptr += write_buf->buf_used;
> + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> + spice_assert(write_buf->client == client_state->client);
> + (*write_to_dev_tokens_ptr) += write_buf->token_price;
> + }
> + }
> + spice_debug("migration data dev %p: write_queue size %u tokens %u",
> + dev, *write_to_dev_size_ptr, *write_to_dev_tokens_ptr);
> +}
> +
> +int spice_char_device_state_restore(SpiceCharDeviceState *dev,
> + SpiceMigrateDataCharDevice *mig_data)
> +{
> + SpiceCharDeviceClientState *client_state;
> + uint32_t client_tokens_window;
> +
> + spice_assert(dev->num_clients == 1 && dev->wait_for_migrate_data);
> +
> + client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients),
> + SpiceCharDeviceClientState,
> + link);
> + if (mig_data->version > SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION) {
> + spice_error("dev %p error: migration data version %u is bigger than self %u",
> + dev, mig_data->version, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
> + return FALSE;
> + }
> + spice_assert(!dev->cur_write_buf && ring_is_empty(&dev->write_queue));
> + spice_assert(mig_data->connected);
> +
> + client_tokens_window = client_state->num_client_tokens; /* initial state of tokens */
> + client_state->num_client_tokens = mig_data->num_client_tokens;
> + /* assumption: client_tokens_window stays the same across severs */
> + client_state->num_client_tokens_free = client_tokens_window -
> + mig_data->num_client_tokens -
> + mig_data->write_num_client_tokens;
> + client_state->num_send_tokens = mig_data->num_send_tokens;
> +
> + if (mig_data->write_size > 0) {
> + if (mig_data->write_num_client_tokens) {
> + dev->cur_write_buf =
> + __spice_char_device_write_buffer_get(dev, client_state->client,
> + mig_data->write_size, WRITE_BUFFER_ORIGIN_CLIENT,
> + mig_data->write_num_client_tokens);
> + } else {
> + dev->cur_write_buf =
> + __spice_char_device_write_buffer_get(dev, NULL,
> + mig_data->write_size, WRITE_BUFFER_ORIGIN_SERVER, 0);
> + }
> + /* the first write buffer contains all the data that was saved for migration */
> + memcpy(dev->cur_write_buf->buf,
> + ((uint8_t *)mig_data) + mig_data->write_data_ptr - sizeof(SpiceMigrateDataHeader),
> + mig_data->write_size);
> + dev->cur_write_buf->buf_used = mig_data->write_size;
> + dev->cur_write_buf_pos = dev->cur_write_buf->buf;
> + }
> + dev->wait_for_migrate_data = FALSE;
> + spice_char_device_write_to_device(dev);
> + spice_char_device_read_from_device(dev);
> + return TRUE;
> +}
> diff --git a/server/char-device.h b/server/char-device.h
> new file mode 100644
> index 0000000..7449c6c
> --- /dev/null
> +++ b/server/char-device.h
> @@ -0,0 +1,216 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009-2015 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/>.
> +*/
> +#ifndef CHAR_DEVICE_H_
> +#define CHAR_DEVICE_H_
> +
> +#include "spice.h"
> +#include "red_channel.h"
> +#include "migration-protocol.h"
> +
> +/*
> + * Shared code for char devices, mainly for flow control.
> + *
> + * How to use the api:
> + * ==================
> + * device attached: call spice_char_device_state_create
> + * device detached: call spice_char_device_state_destroy/reset
> + *
> + * client connected and associated with a device: spice_char_device_client_add
> + * client disconnected: spice_char_device_client_remove
> + *
> + * Writing to the device
> + * ---------------------
> + * Write the data into SpiceCharDeviceWriteBuffer:
> + * call spice_char_device_write_buffer_get in order to get an appropriate buffer.
> + * call spice_char_device_write_buffer_add in order to push the buffer to the write queue.
> + * If you choose not to push the buffer to the device, call
> + * spice_char_device_write_buffer_release
> + *
> + * reading from the device
> + * -----------------------
> + * The callback read_one_msg_from_device (see below) should be implemented
> + * (using sif->read).
> + * When the device is ready, this callback is called, and is expected to
> + * return one message which is addressed to the client, or NULL if the read
> + * hasn't completed.
> + *
> + * calls triggered from the device (qemu):
> + * --------------------------------------
> + * spice_char_device_start
> + * spice_char_device_stop
> + * spice_char_device_wakeup (for reading from the device)
> + */
> +
> +/*
> + * Note about multiple-clients:
> + * Multiclients are currently not supported in any of the character devices:
> + * spicevmc does not allow more than one client (and at least for usb, it should stay this way).
> + * smartcard code is not compatible with more than one reader.
> + * The server and guest agent code doesn't distinguish messages from different clients.
> + * In addition, its current flow control code (e.g., tokens handling) is wrong and doesn't
> + * take into account the different clients.
> + *
> + * Nonetheless, the following code introduces some support for multiple-clients:
> + * We track the number of tokens for all the clients, and we read from the device
> + * if one of the clients have enough tokens. For the clients that don't have tokens,
> + * we queue the messages, till they receive tokens, or till a timeout.
> + *
> + * TODO:
> + * At least for the agent, not all the messages from the device will be directed to all
> + * the clients (e.g., copy from guest to a specific client). Thus, support for
> + * client-specific-messages should be added.
> + * In addition, we should have support for clients that are being connected
> + * in the middle of a message transfer from the agent to the clients.
> + *
> + * */
> +
> +/* buffer that is used for writing to the device */
> +typedef struct SpiceCharDeviceWriteBuffer {
> + RingItem link;
> + int origin;
> + RedClient *client; /* The client that sent the message to the device.
> + NULL if the server created the message */
> +
> + uint8_t *buf;
> + uint32_t buf_size;
> + uint32_t buf_used;
> + uint32_t token_price;
> + uint32_t refs;
> +} SpiceCharDeviceWriteBuffer;
> +
> +typedef void SpiceCharDeviceMsgToClient;
> +
> +typedef struct SpiceCharDeviceCallbacks {
> + /*
> + * Messages that are addressed to the client can be queued in case we have
> + * multiple clients and some of them don't have enough tokens.
> + */
> +
> + /* reads from the device till reaching a msg that should be sent to the client,
> + * or till the reading fails */
> + SpiceCharDeviceMsgToClient* (*read_one_msg_from_device)(SpiceCharDeviceInstance *sin,
> + void *opaque);
> + SpiceCharDeviceMsgToClient* (*ref_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> + void *opaque);
> + void (*unref_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> + void *opaque);
> + void (*send_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> + RedClient *client,
> + void *opaque); /* after this call, the message is unreferenced */
> +
> + /* The cb is called when a predefined number of write buffers were consumed by the
> + * device */
> + void (*send_tokens_to_client)(RedClient *client, uint32_t tokens, void *opaque);
> +
> + /* The cb is called when a server (self) message that was addressed to the device,
> + * has been completely written to it */
> + void (*on_free_self_token)(void *opaque);
> +
> + /* This cb is called if it is recommanded that a client will be removed
> + * due to slow flow or due to some other error.
> + * The called instance should disconnect the client, or at least the corresponding channel */
> + void (*remove_client)(RedClient *client, void *opaque);
> +} SpiceCharDeviceCallbacks;
> +
> +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin,
> + uint32_t client_tokens_interval,
> + uint32_t self_tokens,
> + SpiceCharDeviceCallbacks *cbs,
> + void *opaque);
> +
> +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *dev,
> + SpiceCharDeviceInstance *sin);
> +void spice_char_device_state_destroy(SpiceCharDeviceState *dev);
> +
> +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev);
> +
> +/* only one client is supported */
> +void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev,
> + SpiceMarshaller *m);
> +void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m);
> +
> +int spice_char_device_state_restore(SpiceCharDeviceState *dev,
> + SpiceMigrateDataCharDevice *mig_data);
> +
> +/*
> + * Resets write/read queues, and moves that state to being stopped.
> + * This routine is a workaround for a bad tokens management in the vdagent
> + * protocol:
> + * The client tokens' are set only once, when the main channel is initialized.
> + * Instead, it would have been more appropriate to reset them upon AGEN_CONNECT.
> + * The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus,
> + * in order to be backwartd compatible with the client, we need to track the tokens
> + * event when the agent is detached. We don't destroy the char_device state, and
> + * instead we just reset it.
> + * In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it
> + * overrides the amount of tokens, instead of adding the given amount.
> + *
> + * todo: change AGENT_CONNECT msg to contain tokens count.
> + */
> +void spice_char_device_reset(SpiceCharDeviceState *dev);
> +
> +/* max_send_queue_size = how many messages we can read from the device and enqueue for this client,
> + * when we have tokens for other clients and no tokens for this one */
> +int spice_char_device_client_add(SpiceCharDeviceState *dev,
> + RedClient *client,
> + int do_flow_control,
> + uint32_t max_send_queue_size,
> + uint32_t num_client_tokens,
> + uint32_t num_send_tokens,
> + int wait_for_migrate_data);
> +
> +void spice_char_device_client_remove(SpiceCharDeviceState *dev,
> + RedClient *client);
> +int spice_char_device_client_exists(SpiceCharDeviceState *dev,
> + RedClient *client);
> +
> +void spice_char_device_start(SpiceCharDeviceState *dev);
> +void spice_char_device_stop(SpiceCharDeviceState *dev);
> +
> +/** Read from device **/
> +
> +void spice_char_device_wakeup(SpiceCharDeviceState *dev);
> +
> +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev,
> + RedClient *client,
> + uint32_t tokens);
> +
> +
> +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev,
> + RedClient *client,
> + uint32_t tokens);
> +/** Write to device **/
> +
> +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev,
> + RedClient *client, int size);
> +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token(
> + SpiceCharDeviceState *dev, int size);
> +
> +/* Either add the buffer to the write queue or release it */
> +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev,
> + SpiceCharDeviceWriteBuffer *write_buf);
> +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev,
> + SpiceCharDeviceWriteBuffer *write_buf);
> +
> +/* api for specific char devices */
> +
> +SpiceCharDeviceState *spicevmc_device_connect(SpiceCharDeviceInstance *sin,
> + uint8_t channel_type);
> +void spicevmc_device_disconnect(SpiceCharDeviceInstance *char_device);
> +
> +#endif // CHAR_DEVICE_H_
> diff --git a/server/char_device.c b/server/char_device.c
> deleted file mode 100644
> index ae7cb98..0000000
> --- a/server/char_device.c
> +++ /dev/null
> @@ -1,1035 +0,0 @@
> -/* spice-server char device flow control code
> -
> - Copyright (C) 2012 Red Hat, Inc.
> -
> - Red Hat Authors:
> - Yonit Halperin <yhalperi at redhat.com>
> -
> - 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/>.
> -*/
> -
> -
> -#include <config.h>
> -#include "char_device.h"
> -#include "red_channel.h"
> -#include "reds.h"
> -
> -#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100
> -#define SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000
> -#define MAX_POOL_SIZE (10 * 64 * 1024)
> -
> -typedef struct SpiceCharDeviceClientState SpiceCharDeviceClientState;
> -struct SpiceCharDeviceClientState {
> - RingItem link;
> - SpiceCharDeviceState *dev;
> - RedClient *client;
> - int do_flow_control;
> - uint64_t num_client_tokens;
> - uint64_t num_client_tokens_free; /* client messages that were consumed by the device */
> - uint64_t num_send_tokens; /* send to client */
> - SpiceTimer *wait_for_tokens_timer;
> - int wait_for_tokens_started;
> - Ring send_queue;
> - uint32_t send_queue_size;
> - uint32_t max_send_queue_size;
> -};
> -
> -struct SpiceCharDeviceState {
> - int running;
> - int active; /* has read/write been performed since the device was started */
> - int wait_for_migrate_data;
> - uint32_t refs;
> -
> - Ring write_queue;
> - Ring write_bufs_pool;
> - uint64_t cur_pool_size;
> - SpiceCharDeviceWriteBuffer *cur_write_buf;
> - uint8_t *cur_write_buf_pos;
> - SpiceTimer *write_to_dev_timer;
> - uint64_t num_self_tokens;
> -
> - Ring clients; /* list of SpiceCharDeviceClientState */
> - uint32_t num_clients;
> -
> - uint64_t client_tokens_interval; /* frequency of returning tokens to the client */
> - SpiceCharDeviceInstance *sin;
> -
> - int during_read_from_device;
> - int during_write_to_device;
> -
> - SpiceCharDeviceCallbacks cbs;
> - void *opaque;
> -};
> -
> -enum {
> - WRITE_BUFFER_ORIGIN_NONE,
> - WRITE_BUFFER_ORIGIN_CLIENT,
> - WRITE_BUFFER_ORIGIN_SERVER,
> - WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN,
> -};
> -
> -/* Holding references for avoiding access violation if the char device was
> - * destroyed during a callback */
> -static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev);
> -static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev);
> -static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf);
> -
> -static void spice_char_dev_write_retry(void *opaque);
> -
> -typedef struct SpiceCharDeviceMsgToClientItem {
> - RingItem link;
> - SpiceCharDeviceMsgToClient *msg;
> -} SpiceCharDeviceMsgToClientItem;
> -
> -static void spice_char_device_write_buffer_free(SpiceCharDeviceWriteBuffer *buf)
> -{
> - if (buf == NULL)
> - return;
> -
> - free(buf->buf);
> - free(buf);
> -}
> -
> -static void write_buffers_queue_free(Ring *write_queue)
> -{
> - while (!ring_is_empty(write_queue)) {
> - RingItem *item = ring_get_tail(write_queue);
> - SpiceCharDeviceWriteBuffer *buf;
> -
> - ring_remove(item);
> - buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - spice_char_device_write_buffer_free(buf);
> - }
> -}
> -
> -static void spice_char_device_write_buffer_pool_add(SpiceCharDeviceState *dev,
> - SpiceCharDeviceWriteBuffer *buf)
> -{
> - if (buf->refs == 1 &&
> - dev->cur_pool_size < MAX_POOL_SIZE) {
> - buf->buf_used = 0;
> - buf->origin = WRITE_BUFFER_ORIGIN_NONE;
> - buf->client = NULL;
> - dev->cur_pool_size += buf->buf_size;
> - ring_add(&dev->write_bufs_pool, &buf->link);
> - return;
> - }
> -
> - /* Buffer still being used - just unref for the caller */
> - spice_char_device_write_buffer_unref(buf);
> -}
> -
> -static void spice_char_device_client_send_queue_free(SpiceCharDeviceState *dev,
> - SpiceCharDeviceClientState *dev_client)
> -{
> - spice_debug("send_queue_empty %d", ring_is_empty(&dev_client->send_queue));
> - while (!ring_is_empty(&dev_client->send_queue)) {
> - RingItem *item = ring_get_tail(&dev_client->send_queue);
> - SpiceCharDeviceMsgToClientItem *msg_item = SPICE_CONTAINEROF(item,
> - SpiceCharDeviceMsgToClientItem,
> - link);
> -
> - ring_remove(item);
> - dev->cbs.unref_msg_to_client(msg_item->msg, dev->opaque);
> - free(msg_item);
> - }
> - dev_client->num_send_tokens += dev_client->send_queue_size;
> - dev_client->send_queue_size = 0;
> -}
> -
> -static void spice_char_device_client_free(SpiceCharDeviceState *dev,
> - SpiceCharDeviceClientState *dev_client)
> -{
> - RingItem *item, *next;
> -
> - if (dev_client->wait_for_tokens_timer) {
> - core->timer_remove(dev_client->wait_for_tokens_timer);
> - }
> -
> - spice_char_device_client_send_queue_free(dev, dev_client);
> -
> - /* remove write buffers that are associated with the client */
> - spice_debug("write_queue_is_empty %d", ring_is_empty(&dev->write_queue) && !dev->cur_write_buf);
> - RING_FOREACH_SAFE(item, next, &dev->write_queue) {
> - SpiceCharDeviceWriteBuffer *write_buf;
> -
> - write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> - write_buf->client == dev_client->client) {
> - ring_remove(item);
> - spice_char_device_write_buffer_pool_add(dev, write_buf);
> - }
> - }
> -
> - if (dev->cur_write_buf && dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> - dev->cur_write_buf->client == dev_client->client) {
> - dev->cur_write_buf->origin = WRITE_BUFFER_ORIGIN_NONE;
> - dev->cur_write_buf->client = NULL;
> - }
> -
> - dev->num_clients--;
> - ring_remove(&dev_client->link);
> - free(dev_client);
> -}
> -
> -static void spice_char_device_handle_client_overflow(SpiceCharDeviceClientState *dev_client)
> -{
> - SpiceCharDeviceState *dev = dev_client->dev;
> - spice_printerr("dev %p client %p ", dev, dev_client);
> - dev->cbs.remove_client(dev_client->client, dev->opaque);
> -}
> -
> -static SpiceCharDeviceClientState *spice_char_device_client_find(SpiceCharDeviceState *dev,
> - RedClient *client)
> -{
> - RingItem *item;
> -
> - RING_FOREACH(item, &dev->clients) {
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> - if (dev_client->client == client) {
> - return dev_client;
> - }
> - }
> - return NULL;
> -}
> -
> -/***************************
> - * Reading from the device *
> - **************************/
> -
> -static void device_client_wait_for_tokens_timeout(void *opaque)
> -{
> - SpiceCharDeviceClientState *dev_client = opaque;
> -
> - spice_char_device_handle_client_overflow(dev_client);
> -}
> -
> -static int spice_char_device_can_send_to_client(SpiceCharDeviceClientState *dev_client)
> -{
> - return !dev_client->do_flow_control || dev_client->num_send_tokens;
> -}
> -
> -static uint64_t spice_char_device_max_send_tokens(SpiceCharDeviceState *dev)
> -{
> - RingItem *item;
> - uint64_t max = 0;
> -
> - RING_FOREACH(item, &dev->clients) {
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> -
> - if (!dev_client->do_flow_control) {
> - max = ~0;
> - break;
> - }
> -
> - if (dev_client->num_send_tokens > max) {
> - max = dev_client->num_send_tokens;
> - }
> - }
> - return max;
> -}
> -
> -static void spice_char_device_add_msg_to_client_queue(SpiceCharDeviceClientState *dev_client,
> - SpiceCharDeviceMsgToClient *msg)
> -{
> - SpiceCharDeviceState *dev = dev_client->dev;
> - SpiceCharDeviceMsgToClientItem *msg_item;
> -
> - if (dev_client->send_queue_size >= dev_client->max_send_queue_size) {
> - spice_char_device_handle_client_overflow(dev_client);
> - return;
> - }
> -
> - msg_item = spice_new0(SpiceCharDeviceMsgToClientItem, 1);
> - msg_item->msg = dev->cbs.ref_msg_to_client(msg, dev->opaque);
> - ring_add(&dev_client->send_queue, &msg_item->link);
> - dev_client->send_queue_size++;
> - if (!dev_client->wait_for_tokens_started) {
> - core->timer_start(dev_client->wait_for_tokens_timer,
> - SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
> - dev_client->wait_for_tokens_started = TRUE;
> - }
> -}
> -
> -static void spice_char_device_send_msg_to_clients(SpiceCharDeviceState *dev,
> - SpiceCharDeviceMsgToClient *msg)
> -{
> - RingItem *item, *next;
> -
> - RING_FOREACH_SAFE(item, next, &dev->clients) {
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> - if (spice_char_device_can_send_to_client(dev_client)) {
> - dev_client->num_send_tokens--;
> - spice_assert(ring_is_empty(&dev_client->send_queue));
> - dev->cbs.send_msg_to_client(msg, dev_client->client, dev->opaque);
> -
> - /* don't refer to dev_client anymore, it may have been released */
> - } else {
> - spice_char_device_add_msg_to_client_queue(dev_client, msg);
> - }
> - }
> -}
> -
> -static int spice_char_device_read_from_device(SpiceCharDeviceState *dev)
> -{
> - uint64_t max_send_tokens;
> - int did_read = FALSE;
> -
> - if (!dev->running || dev->wait_for_migrate_data || !dev->sin) {
> - return FALSE;
> - }
> -
> - /* There are 2 scenarios where we can get called recursively:
> - * 1) spice-vmc vmc_read triggering flush of throttled data, recalling wakeup
> - * (virtio)
> - * 2) in case of sending messages to the client, and unreferencing the
> - * msg, we trigger another read.
> - */
> - if (dev->during_read_from_device++ > 0) {
> - return FALSE;
> - }
> -
> - max_send_tokens = spice_char_device_max_send_tokens(dev);
> - spice_char_device_state_ref(dev);
> - /*
> - * Reading from the device only in case at least one of the clients have a free token.
> - * All messages will be discarded if no client is attached to the device
> - */
> - while ((max_send_tokens || ring_is_empty(&dev->clients)) && dev->running) {
> - SpiceCharDeviceMsgToClient *msg;
> -
> - msg = dev->cbs.read_one_msg_from_device(dev->sin, dev->opaque);
> - if (!msg) {
> - if (dev->during_read_from_device > 1) {
> - dev->during_read_from_device = 1;
> - continue; /* a wakeup might have been called during the read -
> - make sure it doesn't get lost */
> - }
> - break;
> - }
> - did_read = TRUE;
> - spice_char_device_send_msg_to_clients(dev, msg);
> - dev->cbs.unref_msg_to_client(msg, dev->opaque);
> - max_send_tokens--;
> - }
> - dev->during_read_from_device = 0;
> - if (dev->running) {
> - dev->active = dev->active || did_read;
> - }
> - spice_char_device_state_unref(dev);
> - return did_read;
> -}
> -
> -static void spice_char_device_client_send_queue_push(SpiceCharDeviceClientState *dev_client)
> -{
> - RingItem *item;
> - while ((item = ring_get_tail(&dev_client->send_queue)) &&
> - spice_char_device_can_send_to_client(dev_client)) {
> - SpiceCharDeviceMsgToClientItem *msg_item;
> -
> - msg_item = SPICE_CONTAINEROF(item, SpiceCharDeviceMsgToClientItem, link);
> - ring_remove(item);
> -
> - dev_client->num_send_tokens--;
> - dev_client->dev->cbs.send_msg_to_client(msg_item->msg,
> - dev_client->client,
> - dev_client->dev->opaque);
> - dev_client->dev->cbs.unref_msg_to_client(msg_item->msg, dev_client->dev->opaque);
> - dev_client->send_queue_size--;
> - free(msg_item);
> - }
> -}
> -
> -static void spice_char_device_send_to_client_tokens_absorb(SpiceCharDeviceClientState *dev_client,
> - uint32_t tokens)
> -{
> - dev_client->num_send_tokens += tokens;
> -
> - if (dev_client->send_queue_size) {
> - spice_assert(dev_client->num_send_tokens == tokens);
> - spice_char_device_client_send_queue_push(dev_client);
> - }
> -
> - if (spice_char_device_can_send_to_client(dev_client)) {
> - core->timer_cancel(dev_client->wait_for_tokens_timer);
> - dev_client->wait_for_tokens_started = FALSE;
> - spice_char_device_read_from_device(dev_client->dev);
> - } else if (dev_client->send_queue_size) {
> - core->timer_start(dev_client->wait_for_tokens_timer,
> - SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
> - dev_client->wait_for_tokens_started = TRUE;
> - }
> -}
> -
> -void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev,
> - RedClient *client,
> - uint32_t tokens)
> -{
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = spice_char_device_client_find(dev, client);
> -
> - if (!dev_client) {
> - spice_error("client wasn't found dev %p client %p", dev, client);
> - return;
> - }
> - spice_char_device_send_to_client_tokens_absorb(dev_client, tokens);
> -}
> -
> -void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev,
> - RedClient *client,
> - uint32_t tokens)
> -{
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = spice_char_device_client_find(dev, client);
> -
> - if (!dev_client) {
> - spice_error("client wasn't found dev %p client %p", dev, client);
> - return;
> - }
> -
> - dev_client->num_send_tokens = 0;
> - spice_char_device_send_to_client_tokens_absorb(dev_client, tokens);
> -}
> -
> -/**************************
> - * Writing to the device *
> -***************************/
> -
> -static void spice_char_device_client_tokens_add(SpiceCharDeviceState *dev,
> - SpiceCharDeviceClientState *dev_client,
> - uint32_t num_tokens)
> -{
> - if (!dev_client->do_flow_control) {
> - return;
> - }
> - if (num_tokens > 1) {
> - spice_debug("#tokens > 1 (=%u)", num_tokens);
> - }
> - dev_client->num_client_tokens_free += num_tokens;
> - if (dev_client->num_client_tokens_free >= dev->client_tokens_interval) {
> - uint32_t tokens = dev_client->num_client_tokens_free;
> -
> - dev_client->num_client_tokens += dev_client->num_client_tokens_free;
> - dev_client->num_client_tokens_free = 0;
> - dev->cbs.send_tokens_to_client(dev_client->client,
> - tokens,
> - dev->opaque);
> - }
> -}
> -
> -static int spice_char_device_write_to_device(SpiceCharDeviceState *dev)
> -{
> - SpiceCharDeviceInterface *sif;
> - int total = 0;
> - int n;
> -
> - if (!dev->running || dev->wait_for_migrate_data || !dev->sin) {
> - return 0;
> - }
> -
> - /* protect against recursion with spice_char_device_wakeup */
> - if (dev->during_write_to_device++ > 0) {
> - return 0;
> - }
> -
> - spice_char_device_state_ref(dev);
> -
> - if (dev->write_to_dev_timer) {
> - core->timer_cancel(dev->write_to_dev_timer);
> - }
> -
> - sif = SPICE_CONTAINEROF(dev->sin->base.sif, SpiceCharDeviceInterface, base);
> - while (dev->running) {
> - uint32_t write_len;
> -
> - if (!dev->cur_write_buf) {
> - RingItem *item = ring_get_tail(&dev->write_queue);
> - if (!item) {
> - break;
> - }
> - dev->cur_write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - dev->cur_write_buf_pos = dev->cur_write_buf->buf;
> - ring_remove(item);
> - }
> -
> - write_len = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used -
> - dev->cur_write_buf_pos;
> - n = sif->write(dev->sin, dev->cur_write_buf_pos, write_len);
> - if (n <= 0) {
> - if (dev->during_write_to_device > 1) {
> - dev->during_write_to_device = 1;
> - continue; /* a wakeup might have been called during the write -
> - make sure it doesn't get lost */
> - }
> - break;
> - }
> - total += n;
> - write_len -= n;
> - if (!write_len) {
> - SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf;
> - dev->cur_write_buf = NULL;
> - spice_char_device_write_buffer_release(dev, release_buf);
> - continue;
> - }
> - dev->cur_write_buf_pos += n;
> - }
> - /* retry writing as long as the write queue is not empty */
> - if (dev->running) {
> - if (dev->cur_write_buf) {
> - if (dev->write_to_dev_timer) {
> - core->timer_start(dev->write_to_dev_timer,
> - CHAR_DEVICE_WRITE_TO_TIMEOUT);
> - }
> - } else {
> - spice_assert(ring_is_empty(&dev->write_queue));
> - }
> - dev->active = dev->active || total;
> - }
> - dev->during_write_to_device = 0;
> - spice_char_device_state_unref(dev);
> - return total;
> -}
> -
> -static void spice_char_dev_write_retry(void *opaque)
> -{
> - SpiceCharDeviceState *dev = opaque;
> -
> - if (dev->write_to_dev_timer) {
> - core->timer_cancel(dev->write_to_dev_timer);
> - }
> - spice_char_device_write_to_device(dev);
> -}
> -
> -static SpiceCharDeviceWriteBuffer *__spice_char_device_write_buffer_get(
> - SpiceCharDeviceState *dev, RedClient *client,
> - int size, int origin, int migrated_data_tokens)
> -{
> - RingItem *item;
> - SpiceCharDeviceWriteBuffer *ret;
> -
> - if (origin == WRITE_BUFFER_ORIGIN_SERVER && !dev->num_self_tokens) {
> - return NULL;
> - }
> -
> - if ((item = ring_get_tail(&dev->write_bufs_pool))) {
> - ret = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - ring_remove(item);
> - dev->cur_pool_size -= ret->buf_size;
> - } else {
> - ret = spice_new0(SpiceCharDeviceWriteBuffer, 1);
> - }
> -
> - spice_assert(!ret->buf_used);
> -
> - if (ret->buf_size < size) {
> - ret->buf = spice_realloc(ret->buf, size);
> - ret->buf_size = size;
> - }
> - ret->origin = origin;
> -
> - if (origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> - spice_assert(client);
> - SpiceCharDeviceClientState *dev_client = spice_char_device_client_find(dev, client);
> - if (dev_client) {
> - if (!migrated_data_tokens &&
> - dev_client->do_flow_control && !dev_client->num_client_tokens) {
> - spice_printerr("token violation: dev %p client %p", dev, client);
> - spice_char_device_handle_client_overflow(dev_client);
> - goto error;
> - }
> - ret->client = client;
> - if (!migrated_data_tokens && dev_client->do_flow_control) {
> - dev_client->num_client_tokens--;
> - }
> - } else {
> - /* it is possible that the client was removed due to send tokens underflow, but
> - * the caller still receive messages from the client */
> - spice_printerr("client not found: dev %p client %p", dev, client);
> - goto error;
> - }
> - } else if (origin == WRITE_BUFFER_ORIGIN_SERVER) {
> - dev->num_self_tokens--;
> - }
> -
> - ret->token_price = migrated_data_tokens ? migrated_data_tokens : 1;
> - ret->refs = 1;
> - return ret;
> -error:
> - dev->cur_pool_size += ret->buf_size;
> - ring_add(&dev->write_bufs_pool, &ret->link);
> - return NULL;
> -}
> -
> -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev,
> - RedClient *client,
> - int size)
> -{
> - return __spice_char_device_write_buffer_get(dev, client, size,
> - client ? WRITE_BUFFER_ORIGIN_CLIENT : WRITE_BUFFER_ORIGIN_SERVER,
> - 0);
> -}
> -
> -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token(
> - SpiceCharDeviceState *dev, int size)
> -{
> - return __spice_char_device_write_buffer_get(dev, NULL, size,
> - WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, 0);
> -}
> -
> -static SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_ref(SpiceCharDeviceWriteBuffer *write_buf)
> -{
> - spice_assert(write_buf);
> -
> - write_buf->refs++;
> - return write_buf;
> -}
> -
> -static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf)
> -{
> - spice_assert(write_buf);
> -
> - write_buf->refs--;
> - if (write_buf->refs == 0)
> - spice_char_device_write_buffer_free(write_buf);
> -}
> -
> -void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev,
> - SpiceCharDeviceWriteBuffer *write_buf)
> -{
> - spice_assert(dev);
> - /* caller shouldn't add buffers for client that was removed */
> - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
> - !spice_char_device_client_find(dev, write_buf->client)) {
> - spice_printerr("client not found: dev %p client %p", dev, write_buf->client);
> - spice_char_device_write_buffer_pool_add(dev, write_buf);
> - return;
> - }
> -
> - ring_add(&dev->write_queue, &write_buf->link);
> - spice_char_device_write_to_device(dev);
> -}
> -
> -void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev,
> - SpiceCharDeviceWriteBuffer *write_buf)
> -{
> - int buf_origin = write_buf->origin;
> - uint32_t buf_token_price = write_buf->token_price;
> - RedClient *client = write_buf->client;
> -
> - spice_assert(!ring_item_is_linked(&write_buf->link));
> - if (!dev) {
> - spice_printerr("no device. write buffer is freed");
> - spice_char_device_write_buffer_free(write_buf);
> - return;
> - }
> -
> - spice_assert(dev->cur_write_buf != write_buf);
> -
> - spice_char_device_write_buffer_pool_add(dev, write_buf);
> - if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> - SpiceCharDeviceClientState *dev_client;
> -
> - spice_assert(client);
> - dev_client = spice_char_device_client_find(dev, client);
> - /* when a client is removed, we remove all the buffers that are associated with it */
> - spice_assert(dev_client);
> - spice_char_device_client_tokens_add(dev, dev_client, buf_token_price);
> - } else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) {
> - dev->num_self_tokens++;
> - if (dev->cbs.on_free_self_token) {
> - dev->cbs.on_free_self_token(dev->opaque);
> - }
> - }
> -}
> -
> -/********************************
> - * char_device_state management *
> - ********************************/
> -
> -SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin,
> - uint32_t client_tokens_interval,
> - uint32_t self_tokens,
> - SpiceCharDeviceCallbacks *cbs,
> - void *opaque)
> -{
> - SpiceCharDeviceState *char_dev;
> - SpiceCharDeviceInterface *sif;
> -
> - spice_assert(sin);
> - spice_assert(cbs->read_one_msg_from_device && cbs->ref_msg_to_client &&
> - cbs->unref_msg_to_client && cbs->send_msg_to_client &&
> - cbs->send_tokens_to_client && cbs->remove_client);
> -
> - char_dev = spice_new0(SpiceCharDeviceState, 1);
> - char_dev->sin = sin;
> - char_dev->cbs = *cbs;
> - char_dev->opaque = opaque;
> - char_dev->client_tokens_interval = client_tokens_interval;
> - char_dev->num_self_tokens = self_tokens;
> -
> - ring_init(&char_dev->write_queue);
> - ring_init(&char_dev->write_bufs_pool);
> - ring_init(&char_dev->clients);
> -
> - sif = SPICE_CONTAINEROF(char_dev->sin->base.sif, SpiceCharDeviceInterface, base);
> - if (sif->base.minor_version <= 2 ||
> - !(sif->flags & SPICE_CHAR_DEVICE_NOTIFY_WRITABLE)) {
> - char_dev->write_to_dev_timer = core->timer_add(spice_char_dev_write_retry, char_dev);
> - if (!char_dev->write_to_dev_timer) {
> - spice_error("failed creating char dev write timer");
> - }
> - }
> -
> - char_dev->refs = 1;
> - sin->st = char_dev;
> - spice_debug("sin %p dev_state %p", sin, char_dev);
> - return char_dev;
> -}
> -
> -void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *state,
> - SpiceCharDeviceInstance *sin)
> -{
> - spice_debug("sin %p dev_state %p", sin, state);
> - state->sin = sin;
> - sin->st = state;
> -}
> -
> -void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev)
> -{
> - return dev->opaque;
> -}
> -
> -static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev)
> -{
> - char_dev->refs++;
> -}
> -
> -static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev)
> -{
> - /* The refs field protects the char_dev from being deallocated in
> - * case spice_char_device_state_destroy has been called
> - * during a callabck, and we might still access the char_dev afterwards.
> - * spice_char_device_state_unref is always coupled with a preceding
> - * spice_char_device_state_ref. Here, refs can turn 0
> - * only when spice_char_device_state_destroy is called in between
> - * the calls to spice_char_device_state_ref and spice_char_device_state_unref.*/
> - if (!--char_dev->refs) {
> - free(char_dev);
> - }
> -}
> -
> -void spice_char_device_state_destroy(SpiceCharDeviceState *char_dev)
> -{
> - reds_on_char_device_state_destroy(char_dev);
> - if (char_dev->write_to_dev_timer) {
> - core->timer_remove(char_dev->write_to_dev_timer);
> - char_dev->write_to_dev_timer = NULL;
> - }
> - write_buffers_queue_free(&char_dev->write_queue);
> - write_buffers_queue_free(&char_dev->write_bufs_pool);
> - char_dev->cur_pool_size = 0;
> - spice_char_device_write_buffer_free(char_dev->cur_write_buf);
> - char_dev->cur_write_buf = NULL;
> -
> - while (!ring_is_empty(&char_dev->clients)) {
> - RingItem *item = ring_get_tail(&char_dev->clients);
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link);
> - spice_char_device_client_free(char_dev, dev_client);
> - }
> - char_dev->running = FALSE;
> -
> - spice_char_device_state_unref(char_dev);
> -}
> -
> -int spice_char_device_client_add(SpiceCharDeviceState *dev,
> - RedClient *client,
> - int do_flow_control,
> - uint32_t max_send_queue_size,
> - uint32_t num_client_tokens,
> - uint32_t num_send_tokens,
> - int wait_for_migrate_data)
> -{
> - SpiceCharDeviceClientState *dev_client;
> -
> - spice_assert(dev);
> - spice_assert(client);
> -
> - if (wait_for_migrate_data && (dev->num_clients > 0 || dev->active)) {
> - spice_warning("can't restore device %p from migration data. The device "
> - "has already been active", dev);
> - return FALSE;
> - }
> -
> - dev->wait_for_migrate_data = wait_for_migrate_data;
> -
> - spice_debug("dev_state %p client %p", dev, client);
> - dev_client = spice_new0(SpiceCharDeviceClientState, 1);
> - dev_client->dev = dev;
> - dev_client->client = client;
> - ring_init(&dev_client->send_queue);
> - dev_client->send_queue_size = 0;
> - dev_client->max_send_queue_size = max_send_queue_size;
> - dev_client->do_flow_control = do_flow_control;
> - if (do_flow_control) {
> - dev_client->wait_for_tokens_timer = core->timer_add(device_client_wait_for_tokens_timeout,
> - dev_client);
> - if (!dev_client->wait_for_tokens_timer) {
> - spice_error("failed to create wait for tokens timer");
> - }
> - dev_client->num_client_tokens = num_client_tokens;
> - dev_client->num_send_tokens = num_send_tokens;
> - } else {
> - dev_client->num_client_tokens = ~0;
> - dev_client->num_send_tokens = ~0;
> - }
> - ring_add(&dev->clients, &dev_client->link);
> - dev->num_clients++;
> - /* Now that we have a client, forward any pending device data */
> - spice_char_device_wakeup(dev);
> - return TRUE;
> -}
> -
> -void spice_char_device_client_remove(SpiceCharDeviceState *dev,
> - RedClient *client)
> -{
> - SpiceCharDeviceClientState *dev_client;
> -
> - spice_debug("dev_state %p client %p", dev, client);
> - dev_client = spice_char_device_client_find(dev, client);
> -
> - if (!dev_client) {
> - spice_error("client wasn't found");
> - return;
> - }
> - spice_char_device_client_free(dev, dev_client);
> - if (dev->wait_for_migrate_data) {
> - spice_assert(dev->num_clients == 0);
> - dev->wait_for_migrate_data = FALSE;
> - spice_char_device_read_from_device(dev);
> - }
> -
> - if (dev->num_clients == 0) {
> - spice_debug("client removed, memory pool will be freed (%lu bytes)", dev->cur_pool_size);
> - write_buffers_queue_free(&dev->write_bufs_pool);
> - dev->cur_pool_size = 0;
> - }
> -}
> -
> -int spice_char_device_client_exists(SpiceCharDeviceState *dev,
> - RedClient *client)
> -{
> - return (spice_char_device_client_find(dev, client) != NULL);
> -}
> -
> -void spice_char_device_start(SpiceCharDeviceState *dev)
> -{
> - spice_debug("dev_state %p", dev);
> - dev->running = TRUE;
> - spice_char_device_state_ref(dev);
> - while (spice_char_device_write_to_device(dev) ||
> - spice_char_device_read_from_device(dev));
> - spice_char_device_state_unref(dev);
> -}
> -
> -void spice_char_device_stop(SpiceCharDeviceState *dev)
> -{
> - spice_debug("dev_state %p", dev);
> - dev->running = FALSE;
> - dev->active = FALSE;
> - if (dev->write_to_dev_timer) {
> - core->timer_cancel(dev->write_to_dev_timer);
> - }
> -}
> -
> -void spice_char_device_reset(SpiceCharDeviceState *dev)
> -{
> - RingItem *client_item;
> -
> - spice_char_device_stop(dev);
> - dev->wait_for_migrate_data = FALSE;
> - spice_debug("dev_state %p", dev);
> - while (!ring_is_empty(&dev->write_queue)) {
> - RingItem *item = ring_get_tail(&dev->write_queue);
> - SpiceCharDeviceWriteBuffer *buf;
> -
> - ring_remove(item);
> - buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - /* tracking the tokens */
> - spice_char_device_write_buffer_release(dev, buf);
> - }
> - if (dev->cur_write_buf) {
> - SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf;
> -
> - dev->cur_write_buf = NULL;
> - spice_char_device_write_buffer_release(dev, release_buf);
> - }
> -
> - RING_FOREACH(client_item, &dev->clients) {
> - SpiceCharDeviceClientState *dev_client;
> -
> - dev_client = SPICE_CONTAINEROF(client_item, SpiceCharDeviceClientState, link);
> - spice_char_device_client_send_queue_free(dev, dev_client);
> - }
> - dev->sin = NULL;
> -}
> -
> -void spice_char_device_wakeup(SpiceCharDeviceState *dev)
> -{
> - spice_char_device_write_to_device(dev);
> - spice_char_device_read_from_device(dev);
> -}
> -
> -/*************
> - * Migration *
> - * **********/
> -
> -void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m)
> -{
> - SpiceMigrateDataCharDevice *mig_data;
> -
> - spice_debug(NULL);
> - mig_data = (SpiceMigrateDataCharDevice *)spice_marshaller_reserve_space(m,
> - sizeof(*mig_data));
> - memset(mig_data, 0, sizeof(*mig_data));
> - mig_data->version = SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION;
> - mig_data->connected = FALSE;
> -}
> -
> -static void migrate_data_marshaller_write_buffer_free(uint8_t *data, void *opaque)
> -{
> - SpiceCharDeviceWriteBuffer *write_buf = (SpiceCharDeviceWriteBuffer *)opaque;
> -
> - spice_char_device_write_buffer_unref(write_buf);
> -}
> -
> -void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev,
> - SpiceMarshaller *m)
> -{
> - SpiceCharDeviceClientState *client_state;
> - RingItem *item;
> - uint32_t *write_to_dev_size_ptr;
> - uint32_t *write_to_dev_tokens_ptr;
> - SpiceMarshaller *m2;
> -
> - /* multi-clients are not supported */
> - spice_assert(dev->num_clients == 1);
> - client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients),
> - SpiceCharDeviceClientState,
> - link);
> - /* FIXME: if there were more than one client before the marshalling,
> - * it is possible that the send_queue_size > 0, and the send data
> - * should be migrated as well */
> - spice_assert(client_state->send_queue_size == 0);
> - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
> - spice_marshaller_add_uint8(m, 1); /* connected */
> - spice_marshaller_add_uint32(m, client_state->num_client_tokens);
> - spice_marshaller_add_uint32(m, client_state->num_send_tokens);
> - write_to_dev_size_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t));
> - write_to_dev_tokens_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t));
> - *write_to_dev_size_ptr = 0;
> - *write_to_dev_tokens_ptr = 0;
> -
> - m2 = spice_marshaller_get_ptr_submarshaller(m, 0);
> - if (dev->cur_write_buf) {
> - uint32_t buf_remaining = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used -
> - dev->cur_write_buf_pos;
> - spice_marshaller_add_ref_full(m2, dev->cur_write_buf_pos, buf_remaining,
> - migrate_data_marshaller_write_buffer_free,
> - spice_char_device_write_buffer_ref(dev->cur_write_buf)
> - );
> - *write_to_dev_size_ptr += buf_remaining;
> - if (dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> - spice_assert(dev->cur_write_buf->client == client_state->client);
> - (*write_to_dev_tokens_ptr) += dev->cur_write_buf->token_price;
> - }
> - }
> -
> - RING_FOREACH_REVERSED(item, &dev->write_queue) {
> - SpiceCharDeviceWriteBuffer *write_buf;
> -
> - write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link);
> - spice_marshaller_add_ref_full(m2, write_buf->buf, write_buf->buf_used,
> - migrate_data_marshaller_write_buffer_free,
> - spice_char_device_write_buffer_ref(write_buf)
> - );
> - *write_to_dev_size_ptr += write_buf->buf_used;
> - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
> - spice_assert(write_buf->client == client_state->client);
> - (*write_to_dev_tokens_ptr) += write_buf->token_price;
> - }
> - }
> - spice_debug("migration data dev %p: write_queue size %u tokens %u",
> - dev, *write_to_dev_size_ptr, *write_to_dev_tokens_ptr);
> -}
> -
> -int spice_char_device_state_restore(SpiceCharDeviceState *dev,
> - SpiceMigrateDataCharDevice *mig_data)
> -{
> - SpiceCharDeviceClientState *client_state;
> - uint32_t client_tokens_window;
> -
> - spice_assert(dev->num_clients == 1 && dev->wait_for_migrate_data);
> -
> - client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients),
> - SpiceCharDeviceClientState,
> - link);
> - if (mig_data->version > SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION) {
> - spice_error("dev %p error: migration data version %u is bigger than self %u",
> - dev, mig_data->version, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
> - return FALSE;
> - }
> - spice_assert(!dev->cur_write_buf && ring_is_empty(&dev->write_queue));
> - spice_assert(mig_data->connected);
> -
> - client_tokens_window = client_state->num_client_tokens; /* initial state of tokens */
> - client_state->num_client_tokens = mig_data->num_client_tokens;
> - /* assumption: client_tokens_window stays the same across severs */
> - client_state->num_client_tokens_free = client_tokens_window -
> - mig_data->num_client_tokens -
> - mig_data->write_num_client_tokens;
> - client_state->num_send_tokens = mig_data->num_send_tokens;
> -
> - if (mig_data->write_size > 0) {
> - if (mig_data->write_num_client_tokens) {
> - dev->cur_write_buf =
> - __spice_char_device_write_buffer_get(dev, client_state->client,
> - mig_data->write_size, WRITE_BUFFER_ORIGIN_CLIENT,
> - mig_data->write_num_client_tokens);
> - } else {
> - dev->cur_write_buf =
> - __spice_char_device_write_buffer_get(dev, NULL,
> - mig_data->write_size, WRITE_BUFFER_ORIGIN_SERVER, 0);
> - }
> - /* the first write buffer contains all the data that was saved for migration */
> - memcpy(dev->cur_write_buf->buf,
> - ((uint8_t *)mig_data) + mig_data->write_data_ptr - sizeof(SpiceMigrateDataHeader),
> - mig_data->write_size);
> - dev->cur_write_buf->buf_used = mig_data->write_size;
> - dev->cur_write_buf_pos = dev->cur_write_buf->buf;
> - }
> - dev->wait_for_migrate_data = FALSE;
> - spice_char_device_write_to_device(dev);
> - spice_char_device_read_from_device(dev);
> - return TRUE;
> -}
> diff --git a/server/char_device.h b/server/char_device.h
> deleted file mode 100644
> index 55d1ee6..0000000
> --- a/server/char_device.h
> +++ /dev/null
> @@ -1,216 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009-2015 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/>.
> -*/
> -#ifndef __CHAR_DEVICE_H__
> -#define __CHAR_DEVICE_H__
> -
> -#include "spice.h"
> -#include "red_channel.h"
> -#include "migration_protocol.h"
> -
> -/*
> - * Shared code for char devices, mainly for flow control.
> - *
> - * How to use the api:
> - * ==================
> - * device attached: call spice_char_device_state_create
> - * device detached: call spice_char_device_state_destroy/reset
> - *
> - * client connected and associated with a device: spice_char_device_client_add
> - * client disconnected: spice_char_device_client_remove
> - *
> - * Writing to the device
> - * ---------------------
> - * Write the data into SpiceCharDeviceWriteBuffer:
> - * call spice_char_device_write_buffer_get in order to get an appropriate buffer.
> - * call spice_char_device_write_buffer_add in order to push the buffer to the write queue.
> - * If you choose not to push the buffer to the device, call
> - * spice_char_device_write_buffer_release
> - *
> - * reading from the device
> - * -----------------------
> - * The callback read_one_msg_from_device (see below) should be implemented
> - * (using sif->read).
> - * When the device is ready, this callback is called, and is expected to
> - * return one message which is addressed to the client, or NULL if the read
> - * hasn't completed.
> - *
> - * calls triggered from the device (qemu):
> - * --------------------------------------
> - * spice_char_device_start
> - * spice_char_device_stop
> - * spice_char_device_wakeup (for reading from the device)
> - */
> -
> -/*
> - * Note about multiple-clients:
> - * Multiclients are currently not supported in any of the character devices:
> - * spicevmc does not allow more than one client (and at least for usb, it should stay this way).
> - * smartcard code is not compatible with more than one reader.
> - * The server and guest agent code doesn't distinguish messages from different clients.
> - * In addition, its current flow control code (e.g., tokens handling) is wrong and doesn't
> - * take into account the different clients.
> - *
> - * Nonetheless, the following code introduces some support for multiple-clients:
> - * We track the number of tokens for all the clients, and we read from the device
> - * if one of the clients have enough tokens. For the clients that don't have tokens,
> - * we queue the messages, till they receive tokens, or till a timeout.
> - *
> - * TODO:
> - * At least for the agent, not all the messages from the device will be directed to all
> - * the clients (e.g., copy from guest to a specific client). Thus, support for
> - * client-specific-messages should be added.
> - * In addition, we should have support for clients that are being connected
> - * in the middle of a message transfer from the agent to the clients.
> - *
> - * */
> -
> -/* buffer that is used for writing to the device */
> -typedef struct SpiceCharDeviceWriteBuffer {
> - RingItem link;
> - int origin;
> - RedClient *client; /* The client that sent the message to the device.
> - NULL if the server created the message */
> -
> - uint8_t *buf;
> - uint32_t buf_size;
> - uint32_t buf_used;
> - uint32_t token_price;
> - uint32_t refs;
> -} SpiceCharDeviceWriteBuffer;
> -
> -typedef void SpiceCharDeviceMsgToClient;
> -
> -typedef struct SpiceCharDeviceCallbacks {
> - /*
> - * Messages that are addressed to the client can be queued in case we have
> - * multiple clients and some of them don't have enough tokens.
> - */
> -
> - /* reads from the device till reaching a msg that should be sent to the client,
> - * or till the reading fails */
> - SpiceCharDeviceMsgToClient* (*read_one_msg_from_device)(SpiceCharDeviceInstance *sin,
> - void *opaque);
> - SpiceCharDeviceMsgToClient* (*ref_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> - void *opaque);
> - void (*unref_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> - void *opaque);
> - void (*send_msg_to_client)(SpiceCharDeviceMsgToClient *msg,
> - RedClient *client,
> - void *opaque); /* after this call, the message is unreferenced */
> -
> - /* The cb is called when a predefined number of write buffers were consumed by the
> - * device */
> - void (*send_tokens_to_client)(RedClient *client, uint32_t tokens, void *opaque);
> -
> - /* The cb is called when a server (self) message that was addressed to the device,
> - * has been completely written to it */
> - void (*on_free_self_token)(void *opaque);
> -
> - /* This cb is called if it is recommanded that a client will be removed
> - * due to slow flow or due to some other error.
> - * The called instance should disconnect the client, or at least the corresponding channel */
> - void (*remove_client)(RedClient *client, void *opaque);
> -} SpiceCharDeviceCallbacks;
> -
> -SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin,
> - uint32_t client_tokens_interval,
> - uint32_t self_tokens,
> - SpiceCharDeviceCallbacks *cbs,
> - void *opaque);
> -
> -void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *dev,
> - SpiceCharDeviceInstance *sin);
> -void spice_char_device_state_destroy(SpiceCharDeviceState *dev);
> -
> -void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev);
> -
> -/* only one client is supported */
> -void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev,
> - SpiceMarshaller *m);
> -void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m);
> -
> -int spice_char_device_state_restore(SpiceCharDeviceState *dev,
> - SpiceMigrateDataCharDevice *mig_data);
> -
> -/*
> - * Resets write/read queues, and moves that state to being stopped.
> - * This routine is a workaround for a bad tokens management in the vdagent
> - * protocol:
> - * The client tokens' are set only once, when the main channel is initialized.
> - * Instead, it would have been more appropriate to reset them upon AGEN_CONNECT.
> - * The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus,
> - * in order to be backwartd compatible with the client, we need to track the tokens
> - * event when the agent is detached. We don't destroy the char_device state, and
> - * instead we just reset it.
> - * In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it
> - * overrides the amount of tokens, instead of adding the given amount.
> - *
> - * todo: change AGENT_CONNECT msg to contain tokens count.
> - */
> -void spice_char_device_reset(SpiceCharDeviceState *dev);
> -
> -/* max_send_queue_size = how many messages we can read from the device and enqueue for this client,
> - * when we have tokens for other clients and no tokens for this one */
> -int spice_char_device_client_add(SpiceCharDeviceState *dev,
> - RedClient *client,
> - int do_flow_control,
> - uint32_t max_send_queue_size,
> - uint32_t num_client_tokens,
> - uint32_t num_send_tokens,
> - int wait_for_migrate_data);
> -
> -void spice_char_device_client_remove(SpiceCharDeviceState *dev,
> - RedClient *client);
> -int spice_char_device_client_exists(SpiceCharDeviceState *dev,
> - RedClient *client);
> -
> -void spice_char_device_start(SpiceCharDeviceState *dev);
> -void spice_char_device_stop(SpiceCharDeviceState *dev);
> -
> -/** Read from device **/
> -
> -void spice_char_device_wakeup(SpiceCharDeviceState *dev);
> -
> -void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev,
> - RedClient *client,
> - uint32_t tokens);
> -
> -
> -void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev,
> - RedClient *client,
> - uint32_t tokens);
> -/** Write to device **/
> -
> -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev,
> - RedClient *client, int size);
> -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token(
> - SpiceCharDeviceState *dev, int size);
> -
> -/* Either add the buffer to the write queue or release it */
> -void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev,
> - SpiceCharDeviceWriteBuffer *write_buf);
> -void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev,
> - SpiceCharDeviceWriteBuffer *write_buf);
> -
> -/* api for specific char devices */
> -
> -SpiceCharDeviceState *spicevmc_device_connect(SpiceCharDeviceInstance *sin,
> - uint8_t channel_type);
> -void spicevmc_device_disconnect(SpiceCharDeviceInstance *char_device);
> -
> -#endif // __CHAR_DEVICE_H__
> diff --git a/server/dcc-encoders.h b/server/dcc-encoders.h
> index 5de66f7..a0a5c64 100644
> --- a/server/dcc-encoders.h
> +++ b/server/dcc-encoders.h
> @@ -23,14 +23,14 @@
> #include "common/quic.h"
> #include "red_channel.h"
> #include "red_parse_qxl.h"
> -#include "spice_image_cache.h"
> -#include "glz_encoder_dictionary.h"
> -#include "glz_encoder.h"
> -#include "jpeg_encoder.h"
> +#include "image-cache.h"
> +#include "glz-encoder-dict.h"
> +#include "glz-encoder.h"
> +#include "jpeg-encoder.h"
> #ifdef USE_LZ4
> #include "lz4_encoder.h"
> #endif
> -#include "zlib_encoder.h"
> +#include "zlib-encoder.h"
>
> typedef struct RedCompressBuf RedCompressBuf;
> typedef struct GlzDrawableInstanceItem GlzDrawableInstanceItem;
> diff --git a/server/display-channel.h b/server/display-channel.h
> index a990e09..50f6c5e 100644
> --- a/server/display-channel.h
> +++ b/server/display-channel.h
> @@ -25,22 +25,22 @@
> #include "reds_stream.h"
> #include "cache-item.h"
> #include "pixmap-cache.h"
> -#include "reds_sw_canvas.h"
> +#include "sw-canvas.h"
> #include "stat.h"
> #include "reds.h"
> -#include "mjpeg_encoder.h"
> -#include "red_memslots.h"
> +#include "mjpeg-encoder.h"
> +#include "memslot.h"
> #include "red_parse_qxl.h"
> #include "red_record_qxl.h"
> #include "demarshallers.h"
> #include "red_channel.h"
> #include "red_dispatcher.h"
> #include "dispatcher.h"
> -#include "main_channel.h"
> -#include "migration_protocol.h"
> -#include "main_dispatcher.h"
> +#include "main-channel.h"
> +#include "migration-protocol.h"
> +#include "main-dispatcher.h"
> #include "spice_bitmap_utils.h"
> -#include "spice_image_cache.h"
> +#include "image-cache.h"
> #include "utils.h"
> #include "tree.h"
> #include "stream.h"
> diff --git a/server/glz-encoder-dict.c b/server/glz-encoder-dict.c
> new file mode 100644
> index 0000000..1fd6753
> --- /dev/null
> +++ b/server/glz-encoder-dict.c
> @@ -0,0 +1,633 @@
> +/*
> + Copyright (C) 2009 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 <pthread.h>
> +#include <string.h>
> +#include <stdio.h>
> +
> +#include "glz-encoder-dict.h"
> +#include "glz-encoder-priv.h"
> +
> +/* turning all used images to free ones. If they are alive, calling the free_image callback for
> + each one */
> +static inline void __glz_dictionary_window_reset_images(SharedDictionary *dict)
> +{
> + WindowImage *tmp;
> +
> + while (dict->window.used_images_head) {
> + tmp = dict->window.used_images_head;
> + dict->window.used_images_head = dict->window.used_images_head->next;
> + if (tmp->is_alive) {
> + dict->cur_usr->free_image(dict->cur_usr, tmp->usr_context);
> + }
> + tmp->next = dict->window.free_images;
> + tmp->is_alive = FALSE;
> + dict->window.free_images = tmp;
> + }
> + dict->window.used_images_tail = NULL;
> +}
> +
> +/* allocate window fields (no reset)*/
> +static int glz_dictionary_window_create(SharedDictionary *dict, uint32_t size)
> +{
> + if (size > LZ_MAX_WINDOW_SIZE) {
> + return FALSE;
> + }
> +
> + dict->window.size_limit = size;
> + dict->window.segs = (WindowImageSegment *)(
> + dict->cur_usr->malloc(dict->cur_usr, sizeof(WindowImageSegment) * INIT_IMAGE_SEGS_NUM));
> +
> + if (!dict->window.segs) {
> + return FALSE;
> + }
> +
> + dict->window.segs_quota = INIT_IMAGE_SEGS_NUM;
> +
> + dict->window.encoders_heads = (uint32_t *)dict->cur_usr->malloc(dict->cur_usr,
> + sizeof(uint32_t) * dict->max_encoders);
> +
> + if (!dict->window.encoders_heads) {
> + dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> + return FALSE;
> + }
> +
> + dict->window.used_images_head = NULL;
> + dict->window.used_images_tail = NULL;
> + dict->window.free_images = NULL;
> + dict->window.pixels_so_far = 0;
> +
> + return TRUE;
> +}
> +
> +/* initializes an empty window (segs and encoder_heads should be pre allocated.
> + resets the image infos, and calls the free_image usr callback*/
> +static void glz_dictionary_window_reset(SharedDictionary *dict)
> +{
> + uint32_t i;
> + WindowImageSegment *seg, *last_seg;
> +
> + last_seg = dict->window.segs + dict->window.segs_quota;
> + /* reset free segs list */
> + dict->window.free_segs_head = 0;
> + for (seg = dict->window.segs, i = 0; seg < last_seg; seg++, i++) {
> + seg->next = i + 1;
> + seg->image = NULL;
> + seg->lines = NULL;
> + seg->lines_end = NULL;
> + seg->pixels_num = 0;
> + seg->pixels_so_far = 0;
> + }
> + dict->window.segs[dict->window.segs_quota - 1].next = NULL_IMAGE_SEG_ID;
> +
> + dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
> + dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
> +
> + // reset encoders heads
> + for (i = 0; i < dict->max_encoders; i++) {
> + dict->window.encoders_heads[i] = NULL_IMAGE_SEG_ID;
> + }
> +
> + __glz_dictionary_window_reset_images(dict);
> +}
> +
> +static inline void glz_dictionary_reset_hash(SharedDictionary *dict)
> +{
> + memset(dict->htab, 0, sizeof(HashEntry) * HASH_SIZE * HASH_CHAIN_SIZE);
> +#ifdef CHAINED_HASH
> + memset(dict->htab_counter, 0, HASH_SIZE * sizeof(uint8_t));
> +#endif
> +}
> +
> +static inline void glz_dictionary_window_destroy(SharedDictionary *dict)
> +{
> + __glz_dictionary_window_reset_images(dict);
> +
> + if (dict->window.segs) {
> + dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> + dict->window.segs = NULL;
> + }
> +
> + while (dict->window.free_images) {
> + WindowImage *tmp = dict->window.free_images;
> + dict->window.free_images = tmp->next;
> +
> + dict->cur_usr->free(dict->cur_usr, tmp);
> + }
> +
> + if (dict->window.encoders_heads) {
> + dict->cur_usr->free(dict->cur_usr, dict->window.encoders_heads);
> + dict->window.encoders_heads = NULL;
> + }
> +}
> +
> +/* logic removal only */
> +static inline void glz_dictionary_window_kill_image(SharedDictionary *dict, WindowImage *image)
> +{
> + image->is_alive = FALSE;
> +}
> +
> +GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
> + GlzEncoderUsrContext *usr)
> +{
> + SharedDictionary *dict;
> +
> + if (!(dict = (SharedDictionary *)usr->malloc(usr,
> + sizeof(SharedDictionary)))) {
> + return NULL;
> + }
> +
> + dict->cur_usr = usr;
> + dict->last_image_id = 0;
> + dict->max_encoders = max_encoders;
> +
> + pthread_mutex_init(&dict->lock, NULL);
> + pthread_rwlock_init(&dict->rw_alloc_lock, NULL);
> +
> + dict->window.encoders_heads = NULL;
> +
> + // alloc window fields and reset
> + if (!glz_dictionary_window_create(dict, size)) {
> + dict->cur_usr->free(usr, dict);
> + return NULL;
> + }
> +
> + // reset window and hash
> + glz_enc_dictionary_reset((GlzEncDictContext *)dict, usr);
> +
> + return (GlzEncDictContext *)dict;
> +}
> +
> +void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
> + GlzEncDictRestoreData *out_data, GlzEncoderUsrContext *usr)
> +{
> + SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> + dict->cur_usr = usr;
> + GLZ_ASSERT(dict->cur_usr, opaque_dict);
> + GLZ_ASSERT(dict->cur_usr, out_data);
> +
> + out_data->last_image_id = dict->last_image_id;
> + out_data->max_encoders = dict->max_encoders;
> + out_data->size = dict->window.size_limit;
> +}
> +
> +GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
> + GlzEncoderUsrContext *usr)
> +{
> + if (!restore_data) {
> + return NULL;
> + }
> + SharedDictionary *ret = (SharedDictionary *)glz_enc_dictionary_create(
> + restore_data->size, restore_data->max_encoders, usr);
> + ret->last_image_id = restore_data->last_image_id;
> + return ((GlzEncDictContext *)ret);
> +}
> +
> +void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
> +{
> + SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> + dict->cur_usr = usr;
> + GLZ_ASSERT(dict->cur_usr, opaque_dict);
> +
> + dict->last_image_id = 0;
> + glz_dictionary_window_reset(dict);
> + glz_dictionary_reset_hash(dict);
> +}
> +
> +void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
> +{
> + SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> +
> + if (!opaque_dict) {
> + return;
> + }
> +
> + dict->cur_usr = usr;
> + glz_dictionary_window_destroy(dict);
> +
> + pthread_mutex_destroy(&dict->lock);
> + pthread_rwlock_destroy(&dict->rw_alloc_lock);
> +
> + dict->cur_usr->free(dict->cur_usr, dict);
> +}
> +
> +uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *opaque_dict)
> +{
> + SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> +
> + if (!opaque_dict) {
> + return 0;
> + }
> + return dict->window.size_limit;
> +}
> +
> +/* doesn't call the remove image callback */
> +void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
> + GlzEncDictImageContext *opaque_image,
> + GlzEncoderUsrContext *usr)
> +{
> + SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> + WindowImage *image = (WindowImage *)opaque_image;
> + dict->cur_usr = usr;
> + GLZ_ASSERT(dict->cur_usr, opaque_image && opaque_dict);
> +
> + glz_dictionary_window_kill_image(dict, image);
> +}
> +
> +/***********************************************************************************
> + Mutators of the window. Should be called by the encoder before and after encoding.
> + ***********************************************************************************/
> +
> +static inline int __get_pixels_num(LzImageType image_type, unsigned int num_lines, int stride)
> +{
> + if (IS_IMAGE_TYPE_RGB[image_type]) {
> + return num_lines * stride / RGB_BYTES_PER_PIXEL[image_type];
> + } else {
> + return num_lines * stride * PLT_PIXELS_PER_BYTE[image_type];
> + }
> +}
> +
> +static void __glz_dictionary_window_segs_realloc(SharedDictionary *dict)
> +{
> + WindowImageSegment *new_segs;
> + uint32_t new_quota = (MAX_IMAGE_SEGS_NUM < (dict->window.segs_quota * 2)) ?
> + MAX_IMAGE_SEGS_NUM : (dict->window.segs_quota * 2);
> + WindowImageSegment *seg;
> + uint32_t i;
> +
> + pthread_rwlock_wrlock(&dict->rw_alloc_lock);
> +
> + if (dict->window.segs_quota == MAX_IMAGE_SEGS_NUM) {
> + dict->cur_usr->error(dict->cur_usr, "overflow in image segments window\n");
> + }
> +
> + new_segs = (WindowImageSegment*)dict->cur_usr->malloc(
> + dict->cur_usr, sizeof(WindowImageSegment) * new_quota);
> +
> + if (!new_segs) {
> + dict->cur_usr->error(dict->cur_usr,
> + "realloc of dictionary window failed\n");
> + }
> +
> + memcpy(new_segs, dict->window.segs,
> + sizeof(WindowImageSegment) * dict->window.segs_quota);
> +
> + // resetting the new elements
> + for (i = dict->window.segs_quota, seg = new_segs + i; i < new_quota; i++, seg++) {
> + seg->image = NULL;
> + seg->lines = NULL;
> + seg->lines_end = NULL;
> + seg->pixels_num = 0;
> + seg->pixels_so_far = 0;
> + seg->next = i + 1;
> + }
> + new_segs[new_quota - 1].next = dict->window.free_segs_head;
> + dict->window.free_segs_head = dict->window.segs_quota;
> +
> + dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> + dict->window.segs = new_segs;
> + dict->window.segs_quota = new_quota;
> +
> + pthread_rwlock_unlock(&dict->rw_alloc_lock);
> +}
> +
> +/* NOTE - it also updates the used_images_list*/
> +static WindowImage *__glz_dictionary_window_alloc_image(SharedDictionary *dict)
> +{
> + WindowImage *ret;
> +
> + if (dict->window.free_images) {
> + ret = dict->window.free_images;
> + dict->window.free_images = ret->next;
> + } else {
> + if (!(ret = (WindowImage *)dict->cur_usr->malloc(dict->cur_usr,
> + sizeof(*ret)))) {
> + return NULL;
> + }
> + }
> +
> + ret->next = NULL;
> + if (dict->window.used_images_tail) {
> + dict->window.used_images_tail->next = ret;
> + }
> + dict->window.used_images_tail = ret;
> +
> + if (!dict->window.used_images_head) {
> + dict->window.used_images_head = ret;
> + }
> + return ret;
> +}
> +
> +/* NOTE - it doesn't update the used_segs list*/
> +static uint32_t __glz_dictionary_window_alloc_image_seg(SharedDictionary *dict)
> +{
> + uint32_t seg_id;
> + WindowImageSegment *seg;
> +
> + // TODO: when is it best to realloc? when full or when half full?
> + if (dict->window.free_segs_head == NULL_IMAGE_SEG_ID) {
> + __glz_dictionary_window_segs_realloc(dict);
> + }
> +
> + GLZ_ASSERT(dict->cur_usr, dict->window.free_segs_head != NULL_IMAGE_SEG_ID);
> +
> + seg_id = dict->window.free_segs_head;
> + seg = dict->window.segs + seg_id;
> + dict->window.free_segs_head = seg->next;
> +
> + return seg_id;
> +}
> +
> +/* moves image to free list and "kill" it. Calls the free_image callback if was alive. */
> +static inline void __glz_dictionary_window_free_image(SharedDictionary *dict, WindowImage *image)
> +{
> + if (image->is_alive) {
> + dict->cur_usr->free_image(dict->cur_usr, image->usr_context);
> + }
> + image->is_alive = FALSE;
> + image->next = dict->window.free_images;
> + dict->window.free_images = image;
> +}
> +
> +/* moves all the segments that were associated with the images to the free segments */
> +static inline void __glz_dictionary_window_free_image_segs(SharedDictionary *dict,
> + WindowImage *image)
> +{
> + uint32_t old_free_head = dict->window.free_segs_head;
> + uint32_t seg_id, next_seg_id;
> +
> + GLZ_ASSERT(dict->cur_usr, image->first_seg != NULL_IMAGE_SEG_ID);
> + dict->window.free_segs_head = image->first_seg;
> +
> + // retrieving the last segment of the image
> + for (seg_id = image->first_seg, next_seg_id = dict->window.segs[seg_id].next;
> + (next_seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[next_seg_id].image == image);
> + seg_id = next_seg_id, next_seg_id = dict->window.segs[seg_id].next) {
> + }
> +
> + // concatenate the free list
> + dict->window.segs[seg_id].next = old_free_head;
> +}
> +
> +/* Returns the logical head of the window after we add an image with the give size to its tail.
> + Returns NULL when the window is empty, of when we have to empty the window in order
> + to insert the new image. */
> +static WindowImage *glz_dictionary_window_get_new_head(SharedDictionary *dict, int new_image_size)
> +{
> + uint32_t cur_win_size;
> + WindowImage *cur_head;
> +
> + if ((uint32_t)new_image_size > dict->window.size_limit) {
> + dict->cur_usr->error(dict->cur_usr, "image is bigger than window\n");
> + }
> +
> + GLZ_ASSERT(dict->cur_usr, new_image_size < dict->window.size_limit)
> +
> + // the window is empty
> + if (!dict->window.used_images_head) {
> + return NULL;
> + }
> +
> + GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_head != NULL_IMAGE_SEG_ID);
> + GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_tail != NULL_IMAGE_SEG_ID);
> +
> + // used_segs_head is the latest logical head (the physical head may preceed it)
> + cur_head = dict->window.segs[dict->window.used_segs_head].image;
> + cur_win_size = dict->window.segs[dict->window.used_segs_tail].pixels_num +
> + dict->window.segs[dict->window.used_segs_tail].pixels_so_far -
> + dict->window.segs[dict->window.used_segs_head].pixels_so_far;
> +
> + while ((cur_win_size + new_image_size) > dict->window.size_limit) {
> + GLZ_ASSERT(dict->cur_usr, cur_head);
> + cur_win_size -= cur_head->size;
> + cur_head = cur_head->next;
> + }
> +
> + return cur_head;
> +}
> +
> +static inline int glz_dictionary_is_in_use(SharedDictionary *dict)
> +{
> + uint32_t i = 0;
> + for (i = 0; i < dict->max_encoders; i++) {
> + if (dict->window.encoders_heads[i] != NULL_IMAGE_SEG_ID) {
> + return TRUE;
> + }
> + }
> + return FALSE;
> +}
> +
> +/* remove from the window (and free relevant data) the images between the oldest physical head
> + (inclusive) and the end_image (exclusive). If end_image is NULL, empties the window*/
> +static void glz_dictionary_window_remove_head(SharedDictionary *dict, uint32_t encoder_id,
> + WindowImage *end_image)
> +{
> + // note that the segs list heads (one per encoder) may be different than the
> + // used_segs_head and it is updated somewhere else
> + while (dict->window.used_images_head != end_image) {
> + WindowImage *image = dict->window.used_images_head;
> +
> + __glz_dictionary_window_free_image_segs(dict, image);
> + dict->window.used_images_head = image->next;
> + __glz_dictionary_window_free_image(dict, image);
> + }
> +
> + if (!dict->window.used_images_head) {
> + dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
> + dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
> + dict->window.used_images_tail = NULL;
> + } else {
> + dict->window.used_segs_head = end_image->first_seg;
> + }
> +}
> +
> +static uint32_t glz_dictionary_window_alloc_image_seg(SharedDictionary *dict, WindowImage* image,
> + int size, int stride,
> + uint8_t *lines, unsigned int num_lines)
> +{
> + uint32_t seg_id = __glz_dictionary_window_alloc_image_seg(dict);
> + WindowImageSegment *seg = &dict->window.segs[seg_id];
> +
> + seg->image = image;
> + seg->lines = lines;
> + seg->lines_end = lines + num_lines * stride;
> + seg->pixels_num = size;
> + seg->pixels_so_far = dict->window.pixels_so_far;
> + dict->window.pixels_so_far += seg->pixels_num;
> +
> + seg->next = NULL_IMAGE_SEG_ID;
> +
> + return seg_id;
> +}
> +
> +static WindowImage *glz_dictionary_window_add_image(SharedDictionary *dict, LzImageType image_type,
> + int image_size, int image_height,
> + int image_stride, uint8_t *first_lines,
> + unsigned int num_first_lines,
> + GlzUsrImageContext *usr_image_context)
> +{
> + unsigned int num_lines = num_first_lines;
> + unsigned int row;
> + uint32_t seg_id, prev_seg_id;
> + uint8_t* lines = first_lines;
> + // alloc image info,update used head tail, if used_head null - update head
> + WindowImage *image = __glz_dictionary_window_alloc_image(dict);
> + image->id = dict->last_image_id++;
> + image->size = image_size;
> + image->type = image_type;
> + image->usr_context = usr_image_context;
> +
> + if (num_lines <= 0) {
> + num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
> + if (num_lines <= 0) {
> + dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
> + }
> + }
> +
> + for (row = 0;;) {
> + seg_id = glz_dictionary_window_alloc_image_seg(dict, image,
> + image_size * num_lines / image_height,
> + image_stride,
> + lines, num_lines);
> + if (row == 0) {
> + image->first_seg = seg_id;
> + } else {
> + dict->window.segs[prev_seg_id].next = seg_id;
> + }
> +
> + row += num_lines;
> + if (row < (uint32_t)image_height) {
> + num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
> + if (num_lines <= 0) {
> + dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
> + }
> + } else {
> + break;
> + }
> + prev_seg_id = seg_id;
> + }
> +
> + if (dict->window.used_segs_tail == NULL_IMAGE_SEG_ID) {
> + dict->window.used_segs_head = image->first_seg;
> + dict->window.used_segs_tail = seg_id;
> + } else {
> + int prev_tail = dict->window.used_segs_tail;
> +
> + // The used segs may be in use by another thread which is during encoding
> + // (read-only use - when going over the segs of an image,
> + // see glz_encode_tmpl::compress).
> + // Thus, the 'next' field of the list's tail can be accessed only
> + // after all the new tail's data was set. Note that we are relying on
> + // an atomic assignment (32 bit variable).
> + // For the other thread that may read 'next' of the old tail, NULL_IMAGE_SEG_ID
> + // is equivalent to a segment with an image id that is different
> + // from the image id of the tail, so we don't need to further protect this field.
> + dict->window.segs[prev_tail].next = image->first_seg;
> + dict->window.used_segs_tail = seg_id;
> + }
> + image->is_alive = TRUE;
> +
> + return image;
> +}
> +
> +WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> + SharedDictionary *dict, LzImageType image_type,
> + int image_width, int image_height, int image_stride,
> + uint8_t *first_lines, unsigned int num_first_lines,
> + GlzUsrImageContext *usr_image_context,
> + uint32_t *image_head_dist)
> +{
> + WindowImage *new_win_head, *ret;
> + int image_size;
> +
> +
> + pthread_mutex_lock(&dict->lock);
> +
> + dict->cur_usr = usr;
> + GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] == NULL_IMAGE_SEG_ID);
> +
> + image_size = __get_pixels_num(image_type, image_height, image_stride);
> + new_win_head = glz_dictionary_window_get_new_head(dict, image_size);
> +
> + if (!glz_dictionary_is_in_use(dict)) {
> + glz_dictionary_window_remove_head(dict, encoder_id, new_win_head);
> + }
> +
> + ret = glz_dictionary_window_add_image(dict, image_type, image_size, image_height, image_stride,
> + first_lines, num_first_lines, usr_image_context);
> +
> + if (new_win_head) {
> + dict->window.encoders_heads[encoder_id] = new_win_head->first_seg;
> + *image_head_dist = (uint32_t)(ret->id - new_win_head->id); // shouldn't be greater than 32
> + // bit because the window size is
> + // limited to 2^25
> + } else {
> + dict->window.encoders_heads[encoder_id] = ret->first_seg;
> + *image_head_dist = 0;
> + }
> +
> +
> + // update encoders head (the other heads were already updated)
> + pthread_mutex_unlock(&dict->lock);
> + pthread_rwlock_rdlock(&dict->rw_alloc_lock);
> + return ret;
> +}
> +
> +void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> + SharedDictionary *dict)
> +{
> + uint32_t i;
> + uint32_t early_head_seg = NULL_IMAGE_SEG_ID;
> + uint32_t this_encoder_head_seg;
> +
> + pthread_rwlock_unlock(&dict->rw_alloc_lock);
> + pthread_mutex_lock(&dict->lock);
> + dict->cur_usr = usr;
> +
> + GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] != NULL_IMAGE_SEG_ID);
> + // get the earliest head in use (not including this encoder head)
> + for (i = 0; i < dict->max_encoders; i++) {
> + if (i != encoder_id) {
> + if (IMAGE_SEG_IS_EARLIER(dict, dict->window.encoders_heads[i], early_head_seg)) {
> + early_head_seg = dict->window.encoders_heads[i];
> + }
> + }
> + }
> +
> + // possible only if early_head_seg == NULL
> + if (IMAGE_SEG_IS_EARLIER(dict, dict->window.used_segs_head, early_head_seg)) {
> + early_head_seg = dict->window.used_segs_head;
> + }
> +
> + this_encoder_head_seg = dict->window.encoders_heads[encoder_id];
> +
> + GLZ_ASSERT(dict->cur_usr, early_head_seg != NULL_IMAGE_SEG_ID);
> +
> + if (IMAGE_SEG_IS_EARLIER(dict, this_encoder_head_seg, early_head_seg)) {
> + GLZ_ASSERT(dict->cur_usr,
> + this_encoder_head_seg == dict->window.used_images_head->first_seg);
> + glz_dictionary_window_remove_head(dict, encoder_id,
> + dict->window.segs[early_head_seg].image);
> + }
> +
> +
> + dict->window.encoders_heads[encoder_id] = NULL_IMAGE_SEG_ID;
> + pthread_mutex_unlock(&dict->lock);
> +}
> diff --git a/server/glz-encoder-dict.h b/server/glz-encoder-dict.h
> new file mode 100644
> index 0000000..960f165
> --- /dev/null
> +++ b/server/glz-encoder-dict.h
> @@ -0,0 +1,69 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef GLZ_ENCODER_DICT_H_
> +#define GLZ_ENCODER_DICT_H_
> +
> +#include <stdint.h>
> +#include "glz_encoder_config.h"
> +
> +/*
> + Interface for maintaining lz dictionary that is shared among several encoders.
> + The interface for accessing the dictionary for encoding purposes is located in
> + glz-encoder-priv.h
> +*/
> +
> +typedef void GlzEncDictContext;
> +typedef void GlzEncDictImageContext;
> +
> +/* NOTE: DISPLAY_MIGRATE_DATA_VERSION should change in case GlzEncDictRestoreData changes*/
> +typedef struct GlzEncDictRestoreData {
> + uint32_t size;
> + uint32_t max_encoders;
> + uint64_t last_image_id;
> +} GlzEncDictRestoreData;
> +
> +/* size : maximal number of pixels occupying the window
> + max_encoders: maximal number of encoders that use the dictionary
> + usr : callbacks */
> +GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
> + GlzEncoderUsrContext *usr);
> +
> +void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
> +
> +/* returns the window capacity in pixels */
> +uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *);
> +
> +/* returns the current state of the dictionary.
> + NOTE - you should use it only when no encoder uses the dictionary. */
> +void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
> + GlzEncDictRestoreData *out_data,
> + GlzEncoderUsrContext *usr);
> +
> +/* creates a dictionary and initialized it by use the given info */
> +GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
> + GlzEncoderUsrContext *usr);
> +
> +/* NOTE - you should use this routine only when no encoder uses the dictionary. */
> +void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
> +
> +/* image: the context returned by the encoder when the image was encoded.
> + NOTE - you should use this routine only when no encoder uses the dictionary.*/
> +void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
> + GlzEncDictImageContext *image, GlzEncoderUsrContext *usr);
> +
> +#endif // GLZ_ENCODER_DICT_H_
> diff --git a/server/glz-encoder-priv.h b/server/glz-encoder-priv.h
> new file mode 100644
> index 0000000..a408966
> --- /dev/null
> +++ b/server/glz-encoder-priv.h
> @@ -0,0 +1,186 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef GLZ_ENCODER_PRIV_H_
> +#define GLZ_ENCODER_PRIV_H_
> +
> +/* Interface for using the dictionary for encoding.
> + Data structures are exposed for the encoder for efficiency
> + purposes. */
> +typedef struct WindowImage WindowImage;
> +typedef struct WindowImageSegment WindowImageSegment;
> +
> +
> +//#define CHAINED_HASH
> +
> +#ifdef CHAINED_HASH
> +#define HASH_SIZE_LOG 16
> +#define HASH_CHAIN_SIZE 4
> +#else
> +#define HASH_SIZE_LOG 20
> +#define HASH_CHAIN_SIZE 1
> +#endif
> +
> +#define HASH_SIZE (1 << HASH_SIZE_LOG)
> +#define HASH_MASK (HASH_SIZE - 1)
> +
> +typedef struct HashEntry HashEntry;
> +
> +typedef struct SharedDictionary SharedDictionary;
> +
> +struct WindowImage {
> + uint64_t id;
> + LzImageType type;
> + int size; // in pixels
> + uint32_t first_seg;
> + GlzUsrImageContext *usr_context;
> + WindowImage* next;
> + uint8_t is_alive;
> +};
> +
> +#define MAX_IMAGE_SEGS_NUM (0xffffffff)
> +#define NULL_IMAGE_SEG_ID MAX_IMAGE_SEGS_NUM
> +#define INIT_IMAGE_SEGS_NUM 1000
> +
> +/* Images can be separated into several chunks. The basic unit of the
> + dictionary window is one image segment. Each segment is encoded separately.
> + An encoded match can refer to only one segment.*/
> +struct WindowImageSegment {
> + WindowImage *image;
> + void *lines;
> + void *lines_end;
> + uint32_t pixels_num; // Number of pixels in the segment
> + uint64_t pixels_so_far; // Total no. pixels passed through the window till this segment.
> + // NOTE - never use size delta independently. It should
> + // always be used with respect to a previous size delta
> + uint32_t next;
> +};
> +
> +
> +struct __attribute__ ((__packed__)) HashEntry {
> + uint32_t image_seg_idx;
> + uint32_t ref_pix_idx;
> +};
> +
> +
> +struct SharedDictionary {
> + struct {
> + /* The segments storage. A dynamic array.
> + By referring to a segment by its index, instead of address,
> + we save space in the hash entries (32bit instead of 64bit) */
> + WindowImageSegment *segs;
> + uint32_t segs_quota;
> +
> + /* The window is manged as a linked list rather than as a cyclic
> + array in order to keep the indices of the segments consistent
> + after reallocation */
> +
> + /* the window in a resolution of image segments */
> + uint32_t used_segs_head; // the latest head
> + uint32_t used_segs_tail;
> + uint32_t free_segs_head;
> +
> + uint32_t *encoders_heads; // Holds for each encoder (by id), the window head when
> + // it started the encoding.
> + // The head is NULL_IMAGE_SEG_ID when the encoder is
> + // not encoding.
> +
> + /* the window in a resolution of images. But here the head contains the oldest head*/
> + WindowImage* used_images_tail;
> + WindowImage* used_images_head;
> + WindowImage* free_images;
> +
> + uint64_t pixels_so_far;
> + uint32_t size_limit; // max number of pixels in a window (per encoder)
> + } window;
> +
> + /* Concurrency issues: the reading/writing of each entry field should be atomic.
> + It is allowed that the reading/writing of the whole entry won't be atomic,
> + since before we access a reference we check its validity*/
> +#ifdef CHAINED_HASH
> + HashEntry htab[HASH_SIZE][HASH_CHAIN_SIZE];
> + uint8_t htab_counter[HASH_SIZE]; //cyclic counter for the next entry in a chain to be assigned
> +#else
> + HashEntry htab[HASH_SIZE];
> +#endif
> +
> + uint64_t last_image_id;
> + uint32_t max_encoders;
> + pthread_mutex_t lock;
> + pthread_rwlock_t rw_alloc_lock;
> + GlzEncoderUsrContext *cur_usr; // each encoder has other context.
> +};
> +
> +/*
> + Add the image to the tail of the window.
> + If possible, release images from the head of the window.
> + Also perform concurrency related operations.
> +
> + usr_image_context: when an image is released from the window due to capacity overflow,
> + usr_image_context is given as a parameter to the free_image callback.
> +
> + image_head_dist : the number of images between the current image and the head of the
> + window that is associated with the encoder.
> +*/
> +WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> + SharedDictionary *dict, LzImageType image_type,
> + int image_width, int image_height, int image_stride,
> + uint8_t *first_lines, unsigned int num_first_lines,
> + GlzUsrImageContext *usr_image_context,
> + uint32_t *image_head_dist);
> +
> +/*
> + Performs concurrency related operations.
> + If possible, release images from the head of the window.
> +*/
> +void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> + SharedDictionary *dict);
> +
> +#define IMAGE_SEG_IS_EARLIER(dict, dst_seg, src_seg) ( \
> + ((src_seg) == NULL_IMAGE_SEG_ID) || (((dst_seg) != NULL_IMAGE_SEG_ID) \
> + && ((dict)->window.segs[(dst_seg)].pixels_so_far < \
> + (dict)->window.segs[(src_seg)].pixels_so_far)))
> +
> +
> +#ifdef CHAINED_HASH
> +#define UPDATE_HASH(dict, hval, seg, pix) { \
> + uint8_t tmp_count = (dict)->htab_counter[hval]; \
> + (dict)->htab[hval][tmp_count].image_seg_idx = seg; \
> + (dict)->htab[hval][tmp_count].ref_pix_idx = pix; \
> + tmp_count = ((tmp_count) + 1) & (HASH_CHAIN_SIZE - 1); \
> + dict->htab_counter[hval] = tmp_count; \
> +}
> +#else
> +#define UPDATE_HASH(dict, hval, seg, pix) { \
> + (dict)->htab[hval].image_seg_idx = seg; \
> + (dict)->htab[hval].ref_pix_idx = pix; \
> +}
> +#endif
> +
> +/* checks if the reference segment is located in the range of the window
> + of the current encoder */
> +#define REF_SEG_IS_VALID(dict, enc_id, ref_seg, src_seg) ( \
> + ((ref_seg) == (src_seg)) || \
> + ((ref_seg)->image && \
> + (ref_seg)->image->is_alive && \
> + (src_seg->image->type == ref_seg->image->type) && \
> + (ref_seg->pixels_so_far <= src_seg->pixels_so_far) && \
> + ((dict)->window.segs[ \
> + (dict)->window.encoders_heads[enc_id]].pixels_so_far <= \
> + ref_seg->pixels_so_far)))
> +
> +#endif // GLZ_ENCODER_PRIV_H_
> diff --git a/server/glz-encoder.c b/server/glz-encoder.c
> new file mode 100644
> index 0000000..f761330
> --- /dev/null
> +++ b/server/glz-encoder.c
> @@ -0,0 +1,311 @@
> +/*
> + Copyright (C) 2009 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 <glib.h>
> +#include <pthread.h>
> +#include <stdio.h>
> +#include "glz-encoder.h"
> +#include "glz-encoder-priv.h"
> +
> +
> +/* Holds a specific data for one encoder, and data that is relevant for the current image encoded */
> +typedef struct Encoder {
> + GlzEncoderUsrContext *usr;
> + uint8_t id;
> + SharedDictionary *dict;
> +
> + struct {
> + LzImageType type;
> + uint32_t id;
> + uint32_t first_win_seg;
> + } cur_image;
> +
> + struct {
> + uint8_t *start;
> + uint8_t *now;
> + uint8_t *end;
> + size_t bytes_count;
> + uint8_t *last_copy; // pointer to the last byte in which copy count was written
> + } io;
> +} Encoder;
> +
> +
> +/**************************************************************************
> +* Handling writing the encoded image to the output buffer
> +***************************************************************************/
> +static inline int more_io_bytes(Encoder *encoder)
> +{
> + uint8_t *io_ptr;
> + int num_io_bytes = encoder->usr->more_space(encoder->usr, &io_ptr);
> + encoder->io.bytes_count += num_io_bytes;
> + encoder->io.now = io_ptr;
> + encoder->io.end = encoder->io.now + num_io_bytes;
> + return num_io_bytes;
> +}
> +
> +static inline void encode(Encoder *encoder, uint8_t byte)
> +{
> + if (encoder->io.now == encoder->io.end) {
> + if (more_io_bytes(encoder) <= 0) {
> + encoder->usr->error(encoder->usr, "%s: no more bytes\n", __FUNCTION__);
> + }
> + GLZ_ASSERT(encoder->usr, encoder->io.now);
> + }
> +
> + GLZ_ASSERT(encoder->usr, encoder->io.now < encoder->io.end);
> + *(encoder->io.now++) = byte;
> +}
> +
> +static inline void encode_32(Encoder *encoder, unsigned int word)
> +{
> + encode(encoder, (uint8_t)(word >> 24));
> + encode(encoder, (uint8_t)(word >> 16) & 0x0000ff);
> + encode(encoder, (uint8_t)(word >> 8) & 0x0000ff);
> + encode(encoder, (uint8_t)(word & 0x0000ff));
> +}
> +
> +static inline void encode_64(Encoder *encoder, uint64_t word)
> +{
> + encode_32(encoder, (uint32_t)(word >> 32));
> + encode_32(encoder, (uint32_t)(word & 0xffffff));
> +}
> +
> +static inline void encode_copy_count(Encoder *encoder, uint8_t copy_count)
> +{
> + encode(encoder, copy_count);
> + encoder->io.last_copy = encoder->io.now - 1; // io_now cannot be the first byte of the buffer
> +}
> +
> +static inline void update_copy_count(Encoder *encoder, uint8_t copy_count)
> +{
> + GLZ_ASSERT(encoder->usr, encoder->io.last_copy);
> + *(encoder->io.last_copy) = copy_count;
> +}
> +
> +// decrease the io ptr by 1
> +static inline void compress_output_prev(Encoder *encoder)
> +{
> + // io_now cannot be the first byte of the buffer
> + encoder->io.now--;
> + // the function should be called only when copy count is written unnecessarily by glz_compress
> + GLZ_ASSERT(encoder->usr, encoder->io.now == encoder->io.last_copy)
> +}
> +
> +static int encoder_reset(Encoder *encoder, uint8_t *io_ptr, uint8_t *io_ptr_end)
> +{
> + GLZ_ASSERT(encoder->usr, io_ptr <= io_ptr_end);
> + encoder->io.bytes_count = io_ptr_end - io_ptr;
> + encoder->io.start = io_ptr;
> + encoder->io.now = io_ptr;
> + encoder->io.end = io_ptr_end;
> + encoder->io.last_copy = NULL;
> +
> + return TRUE;
> +}
> +
> +/**********************************************************
> +* Encoding
> +***********************************************************/
> +
> +GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
> + GlzEncoderUsrContext *usr)
> +{
> + Encoder *encoder;
> +
> + if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc ||
> + !usr->free || !usr->more_space) {
> + return NULL;
> + }
> +
> + if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) {
> + return NULL;
> + }
> +
> + encoder->id = id;
> + encoder->usr = usr;
> + encoder->dict = (SharedDictionary *)dictionary;
> +
> + return (GlzEncoderContext *)encoder;
> +}
> +
> +void glz_encoder_destroy(GlzEncoderContext *opaque_encoder)
> +{
> + Encoder *encoder = (Encoder *)opaque_encoder;
> +
> + if (!opaque_encoder) {
> + return;
> + }
> +
> + encoder->usr->free(encoder->usr, encoder);
> +}
> +
> +/*
> + * Give hints to the compiler for branch prediction optimization.
> + */
> +#if defined(__GNUC__) && (__GNUC__ > 2)
> +#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
> +#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
> +#else
> +#define LZ_EXPECT_CONDITIONAL(c) (c)
> +#define LZ_UNEXPECT_CONDITIONAL(c) (c)
> +#endif
> +
> +
> +typedef uint8_t BYTE;
> +
> +typedef struct __attribute__ ((__packed__)) one_byte_pixel_t {
> + BYTE a;
> +} one_byte_pixel_t;
> +
> +typedef struct __attribute__ ((__packed__)) rgb32_pixel_t {
> + BYTE b;
> + BYTE g;
> + BYTE r;
> + BYTE pad;
> +} rgb32_pixel_t;
> +
> +typedef struct __attribute__ ((__packed__)) rgb24_pixel_t {
> + BYTE b;
> + BYTE g;
> + BYTE r;
> +} rgb24_pixel_t;
> +
> +typedef uint16_t rgb16_pixel_t;
> +
> +#define BOUND_OFFSET 2
> +#define LIMIT_OFFSET 6
> +#define MIN_FILE_SIZE 4
> +
> +#define MAX_PIXEL_SHORT_DISTANCE 4096 // (1 << 12)
> +#define MAX_PIXEL_MEDIUM_DISTANCE 131072 // (1 << 17) 2 ^ (12 + 5)
> +#define MAX_PIXEL_LONG_DISTANCE 33554432 // (1 << 25) 2 ^ (12 + 5 + 8)
> +#define MAX_IMAGE_DIST 16777215 // (1 << 24 - 1)
> +
> +
> +//#define DEBUG_ENCODE
> +
> +
> +#define GLZ_ENCODE_SIZE
> +#include "glz-encode-match.tmpl.c"
> +#define GLZ_ENCODE_MATCH
> +#include "glz-encode-match.tmpl.c"
> +
> +#define LZ_PLT
> +#include "glz-encode.tmpl.c"
> +
> +#define LZ_RGB16
> +#include "glz-encode.tmpl.c"
> +
> +#define LZ_RGB24
> +#include "glz-encode.tmpl.c"
> +
> +#define LZ_RGB32
> +#include "glz-encode.tmpl.c"
> +
> +#define LZ_RGB_ALPHA
> +#include "glz-encode.tmpl.c"
> +
> +
> +int glz_encode(GlzEncoderContext *opaque_encoder,
> + LzImageType type, int width, int height, int top_down,
> + uint8_t *lines, unsigned int num_lines, int stride,
> + uint8_t *io_ptr, unsigned int num_io_bytes,
> + GlzUsrImageContext *usr_context, GlzEncDictImageContext **o_enc_dict_context)
> +{
> + Encoder *encoder = (Encoder *)opaque_encoder;
> + WindowImage *dict_image;
> + uint8_t *io_ptr_end = io_ptr + num_io_bytes;
> + uint32_t win_head_image_dist;
> +
> + if (IS_IMAGE_TYPE_PLT[type]) {
> + if (stride > (width / PLT_PIXELS_PER_BYTE[type])) {
> + if (((width % PLT_PIXELS_PER_BYTE[type]) == 0) || (
> + (stride - (width / PLT_PIXELS_PER_BYTE[type])) > 1)) {
> + encoder->usr->error(encoder->usr, "stride overflows (plt)\n");
> + }
> + }
> + } else {
> + if (stride != width * RGB_BYTES_PER_PIXEL[type]) {
> + encoder->usr->error(encoder->usr, "stride != width*bytes_per_pixel (rgb)\n");
> + }
> + }
> +
> + // assign the output buffer
> + if (!encoder_reset(encoder, io_ptr, io_ptr_end)) {
> + encoder->usr->error(encoder->usr, "lz encoder io reset failed\n");
> + }
> +
> + // first read the list of the image segments into the dictionary window
> + dict_image = glz_dictionary_pre_encode(encoder->id, encoder->usr,
> + encoder->dict, type, width, height, stride,
> + lines, num_lines, usr_context, &win_head_image_dist);
> + *o_enc_dict_context = (GlzEncDictImageContext *)dict_image;
> +
> + encoder->cur_image.type = type;
> + encoder->cur_image.id = dict_image->id;
> + encoder->cur_image.first_win_seg = dict_image->first_seg;
> +
> + encode_32(encoder, GUINT32_TO_LE(LZ_MAGIC));
> + encode_32(encoder, LZ_VERSION);
> + if (top_down) {
> + encode(encoder, (type & LZ_IMAGE_TYPE_MASK) | (1 << LZ_IMAGE_TYPE_LOG));
> + } else {
> + encode(encoder, (type & LZ_IMAGE_TYPE_MASK));
> + }
> +
> + encode_32(encoder, width);
> + encode_32(encoder, height);
> + encode_32(encoder, stride);
> + encode_64(encoder, dict_image->id);
> + encode_32(encoder, win_head_image_dist);
> +
> + switch (encoder->cur_image.type) {
> + case LZ_IMAGE_TYPE_PLT1_BE:
> + case LZ_IMAGE_TYPE_PLT1_LE:
> + case LZ_IMAGE_TYPE_PLT4_BE:
> + case LZ_IMAGE_TYPE_PLT4_LE:
> + case LZ_IMAGE_TYPE_PLT8:
> + glz_plt_compress(encoder);
> + break;
> + case LZ_IMAGE_TYPE_RGB16:
> + glz_rgb16_compress(encoder);
> + break;
> + case LZ_IMAGE_TYPE_RGB24:
> + glz_rgb24_compress(encoder);
> + break;
> + case LZ_IMAGE_TYPE_RGB32:
> + glz_rgb32_compress(encoder);
> + break;
> + case LZ_IMAGE_TYPE_RGBA:
> + glz_rgb32_compress(encoder);
> + glz_rgb_alpha_compress(encoder);
> + break;
> + case LZ_IMAGE_TYPE_INVALID:
> + default:
> + encoder->usr->error(encoder->usr, "bad image type\n");
> + }
> +
> + glz_dictionary_post_encode(encoder->id, encoder->usr, encoder->dict);
> +
> + // move all the used segments to the free ones
> + encoder->io.bytes_count -= (encoder->io.end - encoder->io.now);
> +
> + return encoder->io.bytes_count;
> +}
> diff --git a/server/glz-encoder.h b/server/glz-encoder.h
> new file mode 100644
> index 0000000..93164ed
> --- /dev/null
> +++ b/server/glz-encoder.h
> @@ -0,0 +1,55 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef _H_GLZ_ENCODER
> +#define _H_GLZ_ENCODER
> +
> +/* Manging the lz encoding using a dictionary that is shared among encoders */
> +
> +#include <stdint.h>
> +#include "common/lz_common.h"
> +#include "glz-encoder-dict.h"
> +#include "glz_encoder_config.h"
> +
> +typedef void GlzEncoderContext;
> +
> +GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
> + GlzEncoderUsrContext *usr);
> +
> +void glz_encoder_destroy(GlzEncoderContext *opaque_encoder);
> +
> +/*
> + assumes width is in pixels and stride is in bytes
> + usr_context : when an image is released from the window due to capacity overflow,
> + usr_context is given as a parameter to the free_image callback.
> + o_enc_dict_context: if glz_enc_dictionary_remove_image is called, it should be
> + called with the o_enc_dict_context that is associated with
> + the image.
> +
> + return: the number of bytes in the compressed data and sets o_enc_dict_context
> +
> + NOTE : currently supports only rgb images in which width*bytes_per_pixel = stride OR
> + palette images in which stride equals the min number of bytes to hold a line.
> + The stride should be > 0
> +*/
> +int glz_encode(GlzEncoderContext *opaque_encoder, LzImageType type, int width, int height,
> + int top_down, uint8_t *lines, unsigned int num_lines, int stride,
> + uint8_t *io_ptr, unsigned int num_io_bytes, GlzUsrImageContext *usr_context,
> + GlzEncDictImageContext **o_enc_dict_context);
> +
> +
> +#endif // _H_GLZ_ENCODER
> diff --git a/server/glz_encoder.c b/server/glz_encoder.c
> deleted file mode 100644
> index 65f4478..0000000
> --- a/server/glz_encoder.c
> +++ /dev/null
> @@ -1,311 +0,0 @@
> -/*
> - Copyright (C) 2009 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 <glib.h>
> -#include <pthread.h>
> -#include <stdio.h>
> -#include "glz_encoder.h"
> -#include "glz_encoder_dictionary_protected.h"
> -
> -
> -/* Holds a specific data for one encoder, and data that is relevant for the current image encoded */
> -typedef struct Encoder {
> - GlzEncoderUsrContext *usr;
> - uint8_t id;
> - SharedDictionary *dict;
> -
> - struct {
> - LzImageType type;
> - uint32_t id;
> - uint32_t first_win_seg;
> - } cur_image;
> -
> - struct {
> - uint8_t *start;
> - uint8_t *now;
> - uint8_t *end;
> - size_t bytes_count;
> - uint8_t *last_copy; // pointer to the last byte in which copy count was written
> - } io;
> -} Encoder;
> -
> -
> -/**************************************************************************
> -* Handling writing the encoded image to the output buffer
> -***************************************************************************/
> -static inline int more_io_bytes(Encoder *encoder)
> -{
> - uint8_t *io_ptr;
> - int num_io_bytes = encoder->usr->more_space(encoder->usr, &io_ptr);
> - encoder->io.bytes_count += num_io_bytes;
> - encoder->io.now = io_ptr;
> - encoder->io.end = encoder->io.now + num_io_bytes;
> - return num_io_bytes;
> -}
> -
> -static inline void encode(Encoder *encoder, uint8_t byte)
> -{
> - if (encoder->io.now == encoder->io.end) {
> - if (more_io_bytes(encoder) <= 0) {
> - encoder->usr->error(encoder->usr, "%s: no more bytes\n", __FUNCTION__);
> - }
> - GLZ_ASSERT(encoder->usr, encoder->io.now);
> - }
> -
> - GLZ_ASSERT(encoder->usr, encoder->io.now < encoder->io.end);
> - *(encoder->io.now++) = byte;
> -}
> -
> -static inline void encode_32(Encoder *encoder, unsigned int word)
> -{
> - encode(encoder, (uint8_t)(word >> 24));
> - encode(encoder, (uint8_t)(word >> 16) & 0x0000ff);
> - encode(encoder, (uint8_t)(word >> 8) & 0x0000ff);
> - encode(encoder, (uint8_t)(word & 0x0000ff));
> -}
> -
> -static inline void encode_64(Encoder *encoder, uint64_t word)
> -{
> - encode_32(encoder, (uint32_t)(word >> 32));
> - encode_32(encoder, (uint32_t)(word & 0xffffff));
> -}
> -
> -static inline void encode_copy_count(Encoder *encoder, uint8_t copy_count)
> -{
> - encode(encoder, copy_count);
> - encoder->io.last_copy = encoder->io.now - 1; // io_now cannot be the first byte of the buffer
> -}
> -
> -static inline void update_copy_count(Encoder *encoder, uint8_t copy_count)
> -{
> - GLZ_ASSERT(encoder->usr, encoder->io.last_copy);
> - *(encoder->io.last_copy) = copy_count;
> -}
> -
> -// decrease the io ptr by 1
> -static inline void compress_output_prev(Encoder *encoder)
> -{
> - // io_now cannot be the first byte of the buffer
> - encoder->io.now--;
> - // the function should be called only when copy count is written unnecessarily by glz_compress
> - GLZ_ASSERT(encoder->usr, encoder->io.now == encoder->io.last_copy)
> -}
> -
> -static int encoder_reset(Encoder *encoder, uint8_t *io_ptr, uint8_t *io_ptr_end)
> -{
> - GLZ_ASSERT(encoder->usr, io_ptr <= io_ptr_end);
> - encoder->io.bytes_count = io_ptr_end - io_ptr;
> - encoder->io.start = io_ptr;
> - encoder->io.now = io_ptr;
> - encoder->io.end = io_ptr_end;
> - encoder->io.last_copy = NULL;
> -
> - return TRUE;
> -}
> -
> -/**********************************************************
> -* Encoding
> -***********************************************************/
> -
> -GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
> - GlzEncoderUsrContext *usr)
> -{
> - Encoder *encoder;
> -
> - if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc ||
> - !usr->free || !usr->more_space) {
> - return NULL;
> - }
> -
> - if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) {
> - return NULL;
> - }
> -
> - encoder->id = id;
> - encoder->usr = usr;
> - encoder->dict = (SharedDictionary *)dictionary;
> -
> - return (GlzEncoderContext *)encoder;
> -}
> -
> -void glz_encoder_destroy(GlzEncoderContext *opaque_encoder)
> -{
> - Encoder *encoder = (Encoder *)opaque_encoder;
> -
> - if (!opaque_encoder) {
> - return;
> - }
> -
> - encoder->usr->free(encoder->usr, encoder);
> -}
> -
> -/*
> - * Give hints to the compiler for branch prediction optimization.
> - */
> -#if defined(__GNUC__) && (__GNUC__ > 2)
> -#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
> -#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
> -#else
> -#define LZ_EXPECT_CONDITIONAL(c) (c)
> -#define LZ_UNEXPECT_CONDITIONAL(c) (c)
> -#endif
> -
> -
> -typedef uint8_t BYTE;
> -
> -typedef struct __attribute__ ((__packed__)) one_byte_pixel_t {
> - BYTE a;
> -} one_byte_pixel_t;
> -
> -typedef struct __attribute__ ((__packed__)) rgb32_pixel_t {
> - BYTE b;
> - BYTE g;
> - BYTE r;
> - BYTE pad;
> -} rgb32_pixel_t;
> -
> -typedef struct __attribute__ ((__packed__)) rgb24_pixel_t {
> - BYTE b;
> - BYTE g;
> - BYTE r;
> -} rgb24_pixel_t;
> -
> -typedef uint16_t rgb16_pixel_t;
> -
> -#define BOUND_OFFSET 2
> -#define LIMIT_OFFSET 6
> -#define MIN_FILE_SIZE 4
> -
> -#define MAX_PIXEL_SHORT_DISTANCE 4096 // (1 << 12)
> -#define MAX_PIXEL_MEDIUM_DISTANCE 131072 // (1 << 17) 2 ^ (12 + 5)
> -#define MAX_PIXEL_LONG_DISTANCE 33554432 // (1 << 25) 2 ^ (12 + 5 + 8)
> -#define MAX_IMAGE_DIST 16777215 // (1 << 24 - 1)
> -
> -
> -//#define DEBUG_ENCODE
> -
> -
> -#define GLZ_ENCODE_SIZE
> -#include "glz-encode-match.tmpl.c"
> -#define GLZ_ENCODE_MATCH
> -#include "glz-encode-match.tmpl.c"
> -
> -#define LZ_PLT
> -#include "glz-encode.tmpl.c"
> -
> -#define LZ_RGB16
> -#include "glz-encode.tmpl.c"
> -
> -#define LZ_RGB24
> -#include "glz-encode.tmpl.c"
> -
> -#define LZ_RGB32
> -#include "glz-encode.tmpl.c"
> -
> -#define LZ_RGB_ALPHA
> -#include "glz-encode.tmpl.c"
> -
> -
> -int glz_encode(GlzEncoderContext *opaque_encoder,
> - LzImageType type, int width, int height, int top_down,
> - uint8_t *lines, unsigned int num_lines, int stride,
> - uint8_t *io_ptr, unsigned int num_io_bytes,
> - GlzUsrImageContext *usr_context, GlzEncDictImageContext **o_enc_dict_context)
> -{
> - Encoder *encoder = (Encoder *)opaque_encoder;
> - WindowImage *dict_image;
> - uint8_t *io_ptr_end = io_ptr + num_io_bytes;
> - uint32_t win_head_image_dist;
> -
> - if (IS_IMAGE_TYPE_PLT[type]) {
> - if (stride > (width / PLT_PIXELS_PER_BYTE[type])) {
> - if (((width % PLT_PIXELS_PER_BYTE[type]) == 0) || (
> - (stride - (width / PLT_PIXELS_PER_BYTE[type])) > 1)) {
> - encoder->usr->error(encoder->usr, "stride overflows (plt)\n");
> - }
> - }
> - } else {
> - if (stride != width * RGB_BYTES_PER_PIXEL[type]) {
> - encoder->usr->error(encoder->usr, "stride != width*bytes_per_pixel (rgb)\n");
> - }
> - }
> -
> - // assign the output buffer
> - if (!encoder_reset(encoder, io_ptr, io_ptr_end)) {
> - encoder->usr->error(encoder->usr, "lz encoder io reset failed\n");
> - }
> -
> - // first read the list of the image segments into the dictionary window
> - dict_image = glz_dictionary_pre_encode(encoder->id, encoder->usr,
> - encoder->dict, type, width, height, stride,
> - lines, num_lines, usr_context, &win_head_image_dist);
> - *o_enc_dict_context = (GlzEncDictImageContext *)dict_image;
> -
> - encoder->cur_image.type = type;
> - encoder->cur_image.id = dict_image->id;
> - encoder->cur_image.first_win_seg = dict_image->first_seg;
> -
> - encode_32(encoder, GUINT32_TO_LE(LZ_MAGIC));
> - encode_32(encoder, LZ_VERSION);
> - if (top_down) {
> - encode(encoder, (type & LZ_IMAGE_TYPE_MASK) | (1 << LZ_IMAGE_TYPE_LOG));
> - } else {
> - encode(encoder, (type & LZ_IMAGE_TYPE_MASK));
> - }
> -
> - encode_32(encoder, width);
> - encode_32(encoder, height);
> - encode_32(encoder, stride);
> - encode_64(encoder, dict_image->id);
> - encode_32(encoder, win_head_image_dist);
> -
> - switch (encoder->cur_image.type) {
> - case LZ_IMAGE_TYPE_PLT1_BE:
> - case LZ_IMAGE_TYPE_PLT1_LE:
> - case LZ_IMAGE_TYPE_PLT4_BE:
> - case LZ_IMAGE_TYPE_PLT4_LE:
> - case LZ_IMAGE_TYPE_PLT8:
> - glz_plt_compress(encoder);
> - break;
> - case LZ_IMAGE_TYPE_RGB16:
> - glz_rgb16_compress(encoder);
> - break;
> - case LZ_IMAGE_TYPE_RGB24:
> - glz_rgb24_compress(encoder);
> - break;
> - case LZ_IMAGE_TYPE_RGB32:
> - glz_rgb32_compress(encoder);
> - break;
> - case LZ_IMAGE_TYPE_RGBA:
> - glz_rgb32_compress(encoder);
> - glz_rgb_alpha_compress(encoder);
> - break;
> - case LZ_IMAGE_TYPE_INVALID:
> - default:
> - encoder->usr->error(encoder->usr, "bad image type\n");
> - }
> -
> - glz_dictionary_post_encode(encoder->id, encoder->usr, encoder->dict);
> -
> - // move all the used segments to the free ones
> - encoder->io.bytes_count -= (encoder->io.end - encoder->io.now);
> -
> - return encoder->io.bytes_count;
> -}
> diff --git a/server/glz_encoder.h b/server/glz_encoder.h
> deleted file mode 100644
> index e91f515..0000000
> --- a/server/glz_encoder.h
> +++ /dev/null
> @@ -1,55 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _H_GLZ_ENCODER
> -#define _H_GLZ_ENCODER
> -
> -/* Manging the lz encoding using a dictionary that is shared among encoders */
> -
> -#include <stdint.h>
> -#include "common/lz_common.h"
> -#include "glz_encoder_dictionary.h"
> -#include "glz_encoder_config.h"
> -
> -typedef void GlzEncoderContext;
> -
> -GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
> - GlzEncoderUsrContext *usr);
> -
> -void glz_encoder_destroy(GlzEncoderContext *opaque_encoder);
> -
> -/*
> - assumes width is in pixels and stride is in bytes
> - usr_context : when an image is released from the window due to capacity overflow,
> - usr_context is given as a parameter to the free_image callback.
> - o_enc_dict_context: if glz_enc_dictionary_remove_image is called, it should be
> - called with the o_enc_dict_context that is associated with
> - the image.
> -
> - return: the number of bytes in the compressed data and sets o_enc_dict_context
> -
> - NOTE : currently supports only rgb images in which width*bytes_per_pixel = stride OR
> - palette images in which stride equals the min number of bytes to hold a line.
> - The stride should be > 0
> -*/
> -int glz_encode(GlzEncoderContext *opaque_encoder, LzImageType type, int width, int height,
> - int top_down, uint8_t *lines, unsigned int num_lines, int stride,
> - uint8_t *io_ptr, unsigned int num_io_bytes, GlzUsrImageContext *usr_context,
> - GlzEncDictImageContext **o_enc_dict_context);
> -
> -
> -#endif // _H_GLZ_ENCODER
> diff --git a/server/glz_encoder_dictionary.c b/server/glz_encoder_dictionary.c
> deleted file mode 100644
> index 70226e1..0000000
> --- a/server/glz_encoder_dictionary.c
> +++ /dev/null
> @@ -1,633 +0,0 @@
> -/*
> - Copyright (C) 2009 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 <pthread.h>
> -#include <string.h>
> -#include <stdio.h>
> -
> -#include "glz_encoder_dictionary.h"
> -#include "glz_encoder_dictionary_protected.h"
> -
> -/* turning all used images to free ones. If they are alive, calling the free_image callback for
> - each one */
> -static inline void __glz_dictionary_window_reset_images(SharedDictionary *dict)
> -{
> - WindowImage *tmp;
> -
> - while (dict->window.used_images_head) {
> - tmp = dict->window.used_images_head;
> - dict->window.used_images_head = dict->window.used_images_head->next;
> - if (tmp->is_alive) {
> - dict->cur_usr->free_image(dict->cur_usr, tmp->usr_context);
> - }
> - tmp->next = dict->window.free_images;
> - tmp->is_alive = FALSE;
> - dict->window.free_images = tmp;
> - }
> - dict->window.used_images_tail = NULL;
> -}
> -
> -/* allocate window fields (no reset)*/
> -static int glz_dictionary_window_create(SharedDictionary *dict, uint32_t size)
> -{
> - if (size > LZ_MAX_WINDOW_SIZE) {
> - return FALSE;
> - }
> -
> - dict->window.size_limit = size;
> - dict->window.segs = (WindowImageSegment *)(
> - dict->cur_usr->malloc(dict->cur_usr, sizeof(WindowImageSegment) * INIT_IMAGE_SEGS_NUM));
> -
> - if (!dict->window.segs) {
> - return FALSE;
> - }
> -
> - dict->window.segs_quota = INIT_IMAGE_SEGS_NUM;
> -
> - dict->window.encoders_heads = (uint32_t *)dict->cur_usr->malloc(dict->cur_usr,
> - sizeof(uint32_t) * dict->max_encoders);
> -
> - if (!dict->window.encoders_heads) {
> - dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> - return FALSE;
> - }
> -
> - dict->window.used_images_head = NULL;
> - dict->window.used_images_tail = NULL;
> - dict->window.free_images = NULL;
> - dict->window.pixels_so_far = 0;
> -
> - return TRUE;
> -}
> -
> -/* initializes an empty window (segs and encoder_heads should be pre allocated.
> - resets the image infos, and calls the free_image usr callback*/
> -static void glz_dictionary_window_reset(SharedDictionary *dict)
> -{
> - uint32_t i;
> - WindowImageSegment *seg, *last_seg;
> -
> - last_seg = dict->window.segs + dict->window.segs_quota;
> - /* reset free segs list */
> - dict->window.free_segs_head = 0;
> - for (seg = dict->window.segs, i = 0; seg < last_seg; seg++, i++) {
> - seg->next = i + 1;
> - seg->image = NULL;
> - seg->lines = NULL;
> - seg->lines_end = NULL;
> - seg->pixels_num = 0;
> - seg->pixels_so_far = 0;
> - }
> - dict->window.segs[dict->window.segs_quota - 1].next = NULL_IMAGE_SEG_ID;
> -
> - dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
> - dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
> -
> - // reset encoders heads
> - for (i = 0; i < dict->max_encoders; i++) {
> - dict->window.encoders_heads[i] = NULL_IMAGE_SEG_ID;
> - }
> -
> - __glz_dictionary_window_reset_images(dict);
> -}
> -
> -static inline void glz_dictionary_reset_hash(SharedDictionary *dict)
> -{
> - memset(dict->htab, 0, sizeof(HashEntry) * HASH_SIZE * HASH_CHAIN_SIZE);
> -#ifdef CHAINED_HASH
> - memset(dict->htab_counter, 0, HASH_SIZE * sizeof(uint8_t));
> -#endif
> -}
> -
> -static inline void glz_dictionary_window_destroy(SharedDictionary *dict)
> -{
> - __glz_dictionary_window_reset_images(dict);
> -
> - if (dict->window.segs) {
> - dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> - dict->window.segs = NULL;
> - }
> -
> - while (dict->window.free_images) {
> - WindowImage *tmp = dict->window.free_images;
> - dict->window.free_images = tmp->next;
> -
> - dict->cur_usr->free(dict->cur_usr, tmp);
> - }
> -
> - if (dict->window.encoders_heads) {
> - dict->cur_usr->free(dict->cur_usr, dict->window.encoders_heads);
> - dict->window.encoders_heads = NULL;
> - }
> -}
> -
> -/* logic removal only */
> -static inline void glz_dictionary_window_kill_image(SharedDictionary *dict, WindowImage *image)
> -{
> - image->is_alive = FALSE;
> -}
> -
> -GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
> - GlzEncoderUsrContext *usr)
> -{
> - SharedDictionary *dict;
> -
> - if (!(dict = (SharedDictionary *)usr->malloc(usr,
> - sizeof(SharedDictionary)))) {
> - return NULL;
> - }
> -
> - dict->cur_usr = usr;
> - dict->last_image_id = 0;
> - dict->max_encoders = max_encoders;
> -
> - pthread_mutex_init(&dict->lock, NULL);
> - pthread_rwlock_init(&dict->rw_alloc_lock, NULL);
> -
> - dict->window.encoders_heads = NULL;
> -
> - // alloc window fields and reset
> - if (!glz_dictionary_window_create(dict, size)) {
> - dict->cur_usr->free(usr, dict);
> - return NULL;
> - }
> -
> - // reset window and hash
> - glz_enc_dictionary_reset((GlzEncDictContext *)dict, usr);
> -
> - return (GlzEncDictContext *)dict;
> -}
> -
> -void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
> - GlzEncDictRestoreData *out_data, GlzEncoderUsrContext *usr)
> -{
> - SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> - dict->cur_usr = usr;
> - GLZ_ASSERT(dict->cur_usr, opaque_dict);
> - GLZ_ASSERT(dict->cur_usr, out_data);
> -
> - out_data->last_image_id = dict->last_image_id;
> - out_data->max_encoders = dict->max_encoders;
> - out_data->size = dict->window.size_limit;
> -}
> -
> -GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
> - GlzEncoderUsrContext *usr)
> -{
> - if (!restore_data) {
> - return NULL;
> - }
> - SharedDictionary *ret = (SharedDictionary *)glz_enc_dictionary_create(
> - restore_data->size, restore_data->max_encoders, usr);
> - ret->last_image_id = restore_data->last_image_id;
> - return ((GlzEncDictContext *)ret);
> -}
> -
> -void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
> -{
> - SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> - dict->cur_usr = usr;
> - GLZ_ASSERT(dict->cur_usr, opaque_dict);
> -
> - dict->last_image_id = 0;
> - glz_dictionary_window_reset(dict);
> - glz_dictionary_reset_hash(dict);
> -}
> -
> -void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
> -{
> - SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> -
> - if (!opaque_dict) {
> - return;
> - }
> -
> - dict->cur_usr = usr;
> - glz_dictionary_window_destroy(dict);
> -
> - pthread_mutex_destroy(&dict->lock);
> - pthread_rwlock_destroy(&dict->rw_alloc_lock);
> -
> - dict->cur_usr->free(dict->cur_usr, dict);
> -}
> -
> -uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *opaque_dict)
> -{
> - SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> -
> - if (!opaque_dict) {
> - return 0;
> - }
> - return dict->window.size_limit;
> -}
> -
> -/* doesn't call the remove image callback */
> -void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
> - GlzEncDictImageContext *opaque_image,
> - GlzEncoderUsrContext *usr)
> -{
> - SharedDictionary *dict = (SharedDictionary *)opaque_dict;
> - WindowImage *image = (WindowImage *)opaque_image;
> - dict->cur_usr = usr;
> - GLZ_ASSERT(dict->cur_usr, opaque_image && opaque_dict);
> -
> - glz_dictionary_window_kill_image(dict, image);
> -}
> -
> -/***********************************************************************************
> - Mutators of the window. Should be called by the encoder before and after encoding.
> - ***********************************************************************************/
> -
> -static inline int __get_pixels_num(LzImageType image_type, unsigned int num_lines, int stride)
> -{
> - if (IS_IMAGE_TYPE_RGB[image_type]) {
> - return num_lines * stride / RGB_BYTES_PER_PIXEL[image_type];
> - } else {
> - return num_lines * stride * PLT_PIXELS_PER_BYTE[image_type];
> - }
> -}
> -
> -static void __glz_dictionary_window_segs_realloc(SharedDictionary *dict)
> -{
> - WindowImageSegment *new_segs;
> - uint32_t new_quota = (MAX_IMAGE_SEGS_NUM < (dict->window.segs_quota * 2)) ?
> - MAX_IMAGE_SEGS_NUM : (dict->window.segs_quota * 2);
> - WindowImageSegment *seg;
> - uint32_t i;
> -
> - pthread_rwlock_wrlock(&dict->rw_alloc_lock);
> -
> - if (dict->window.segs_quota == MAX_IMAGE_SEGS_NUM) {
> - dict->cur_usr->error(dict->cur_usr, "overflow in image segments window\n");
> - }
> -
> - new_segs = (WindowImageSegment*)dict->cur_usr->malloc(
> - dict->cur_usr, sizeof(WindowImageSegment) * new_quota);
> -
> - if (!new_segs) {
> - dict->cur_usr->error(dict->cur_usr,
> - "realloc of dictionary window failed\n");
> - }
> -
> - memcpy(new_segs, dict->window.segs,
> - sizeof(WindowImageSegment) * dict->window.segs_quota);
> -
> - // resetting the new elements
> - for (i = dict->window.segs_quota, seg = new_segs + i; i < new_quota; i++, seg++) {
> - seg->image = NULL;
> - seg->lines = NULL;
> - seg->lines_end = NULL;
> - seg->pixels_num = 0;
> - seg->pixels_so_far = 0;
> - seg->next = i + 1;
> - }
> - new_segs[new_quota - 1].next = dict->window.free_segs_head;
> - dict->window.free_segs_head = dict->window.segs_quota;
> -
> - dict->cur_usr->free(dict->cur_usr, dict->window.segs);
> - dict->window.segs = new_segs;
> - dict->window.segs_quota = new_quota;
> -
> - pthread_rwlock_unlock(&dict->rw_alloc_lock);
> -}
> -
> -/* NOTE - it also updates the used_images_list*/
> -static WindowImage *__glz_dictionary_window_alloc_image(SharedDictionary *dict)
> -{
> - WindowImage *ret;
> -
> - if (dict->window.free_images) {
> - ret = dict->window.free_images;
> - dict->window.free_images = ret->next;
> - } else {
> - if (!(ret = (WindowImage *)dict->cur_usr->malloc(dict->cur_usr,
> - sizeof(*ret)))) {
> - return NULL;
> - }
> - }
> -
> - ret->next = NULL;
> - if (dict->window.used_images_tail) {
> - dict->window.used_images_tail->next = ret;
> - }
> - dict->window.used_images_tail = ret;
> -
> - if (!dict->window.used_images_head) {
> - dict->window.used_images_head = ret;
> - }
> - return ret;
> -}
> -
> -/* NOTE - it doesn't update the used_segs list*/
> -static uint32_t __glz_dictionary_window_alloc_image_seg(SharedDictionary *dict)
> -{
> - uint32_t seg_id;
> - WindowImageSegment *seg;
> -
> - // TODO: when is it best to realloc? when full or when half full?
> - if (dict->window.free_segs_head == NULL_IMAGE_SEG_ID) {
> - __glz_dictionary_window_segs_realloc(dict);
> - }
> -
> - GLZ_ASSERT(dict->cur_usr, dict->window.free_segs_head != NULL_IMAGE_SEG_ID);
> -
> - seg_id = dict->window.free_segs_head;
> - seg = dict->window.segs + seg_id;
> - dict->window.free_segs_head = seg->next;
> -
> - return seg_id;
> -}
> -
> -/* moves image to free list and "kill" it. Calls the free_image callback if was alive. */
> -static inline void __glz_dictionary_window_free_image(SharedDictionary *dict, WindowImage *image)
> -{
> - if (image->is_alive) {
> - dict->cur_usr->free_image(dict->cur_usr, image->usr_context);
> - }
> - image->is_alive = FALSE;
> - image->next = dict->window.free_images;
> - dict->window.free_images = image;
> -}
> -
> -/* moves all the segments that were associated with the images to the free segments */
> -static inline void __glz_dictionary_window_free_image_segs(SharedDictionary *dict,
> - WindowImage *image)
> -{
> - uint32_t old_free_head = dict->window.free_segs_head;
> - uint32_t seg_id, next_seg_id;
> -
> - GLZ_ASSERT(dict->cur_usr, image->first_seg != NULL_IMAGE_SEG_ID);
> - dict->window.free_segs_head = image->first_seg;
> -
> - // retrieving the last segment of the image
> - for (seg_id = image->first_seg, next_seg_id = dict->window.segs[seg_id].next;
> - (next_seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[next_seg_id].image == image);
> - seg_id = next_seg_id, next_seg_id = dict->window.segs[seg_id].next) {
> - }
> -
> - // concatenate the free list
> - dict->window.segs[seg_id].next = old_free_head;
> -}
> -
> -/* Returns the logical head of the window after we add an image with the give size to its tail.
> - Returns NULL when the window is empty, of when we have to empty the window in order
> - to insert the new image. */
> -static WindowImage *glz_dictionary_window_get_new_head(SharedDictionary *dict, int new_image_size)
> -{
> - uint32_t cur_win_size;
> - WindowImage *cur_head;
> -
> - if ((uint32_t)new_image_size > dict->window.size_limit) {
> - dict->cur_usr->error(dict->cur_usr, "image is bigger than window\n");
> - }
> -
> - GLZ_ASSERT(dict->cur_usr, new_image_size < dict->window.size_limit)
> -
> - // the window is empty
> - if (!dict->window.used_images_head) {
> - return NULL;
> - }
> -
> - GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_head != NULL_IMAGE_SEG_ID);
> - GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_tail != NULL_IMAGE_SEG_ID);
> -
> - // used_segs_head is the latest logical head (the physical head may preceed it)
> - cur_head = dict->window.segs[dict->window.used_segs_head].image;
> - cur_win_size = dict->window.segs[dict->window.used_segs_tail].pixels_num +
> - dict->window.segs[dict->window.used_segs_tail].pixels_so_far -
> - dict->window.segs[dict->window.used_segs_head].pixels_so_far;
> -
> - while ((cur_win_size + new_image_size) > dict->window.size_limit) {
> - GLZ_ASSERT(dict->cur_usr, cur_head);
> - cur_win_size -= cur_head->size;
> - cur_head = cur_head->next;
> - }
> -
> - return cur_head;
> -}
> -
> -static inline int glz_dictionary_is_in_use(SharedDictionary *dict)
> -{
> - uint32_t i = 0;
> - for (i = 0; i < dict->max_encoders; i++) {
> - if (dict->window.encoders_heads[i] != NULL_IMAGE_SEG_ID) {
> - return TRUE;
> - }
> - }
> - return FALSE;
> -}
> -
> -/* remove from the window (and free relevant data) the images between the oldest physical head
> - (inclusive) and the end_image (exclusive). If end_image is NULL, empties the window*/
> -static void glz_dictionary_window_remove_head(SharedDictionary *dict, uint32_t encoder_id,
> - WindowImage *end_image)
> -{
> - // note that the segs list heads (one per encoder) may be different than the
> - // used_segs_head and it is updated somewhere else
> - while (dict->window.used_images_head != end_image) {
> - WindowImage *image = dict->window.used_images_head;
> -
> - __glz_dictionary_window_free_image_segs(dict, image);
> - dict->window.used_images_head = image->next;
> - __glz_dictionary_window_free_image(dict, image);
> - }
> -
> - if (!dict->window.used_images_head) {
> - dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
> - dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
> - dict->window.used_images_tail = NULL;
> - } else {
> - dict->window.used_segs_head = end_image->first_seg;
> - }
> -}
> -
> -static uint32_t glz_dictionary_window_alloc_image_seg(SharedDictionary *dict, WindowImage* image,
> - int size, int stride,
> - uint8_t *lines, unsigned int num_lines)
> -{
> - uint32_t seg_id = __glz_dictionary_window_alloc_image_seg(dict);
> - WindowImageSegment *seg = &dict->window.segs[seg_id];
> -
> - seg->image = image;
> - seg->lines = lines;
> - seg->lines_end = lines + num_lines * stride;
> - seg->pixels_num = size;
> - seg->pixels_so_far = dict->window.pixels_so_far;
> - dict->window.pixels_so_far += seg->pixels_num;
> -
> - seg->next = NULL_IMAGE_SEG_ID;
> -
> - return seg_id;
> -}
> -
> -static WindowImage *glz_dictionary_window_add_image(SharedDictionary *dict, LzImageType image_type,
> - int image_size, int image_height,
> - int image_stride, uint8_t *first_lines,
> - unsigned int num_first_lines,
> - GlzUsrImageContext *usr_image_context)
> -{
> - unsigned int num_lines = num_first_lines;
> - unsigned int row;
> - uint32_t seg_id, prev_seg_id;
> - uint8_t* lines = first_lines;
> - // alloc image info,update used head tail, if used_head null - update head
> - WindowImage *image = __glz_dictionary_window_alloc_image(dict);
> - image->id = dict->last_image_id++;
> - image->size = image_size;
> - image->type = image_type;
> - image->usr_context = usr_image_context;
> -
> - if (num_lines <= 0) {
> - num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
> - if (num_lines <= 0) {
> - dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
> - }
> - }
> -
> - for (row = 0;;) {
> - seg_id = glz_dictionary_window_alloc_image_seg(dict, image,
> - image_size * num_lines / image_height,
> - image_stride,
> - lines, num_lines);
> - if (row == 0) {
> - image->first_seg = seg_id;
> - } else {
> - dict->window.segs[prev_seg_id].next = seg_id;
> - }
> -
> - row += num_lines;
> - if (row < (uint32_t)image_height) {
> - num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
> - if (num_lines <= 0) {
> - dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
> - }
> - } else {
> - break;
> - }
> - prev_seg_id = seg_id;
> - }
> -
> - if (dict->window.used_segs_tail == NULL_IMAGE_SEG_ID) {
> - dict->window.used_segs_head = image->first_seg;
> - dict->window.used_segs_tail = seg_id;
> - } else {
> - int prev_tail = dict->window.used_segs_tail;
> -
> - // The used segs may be in use by another thread which is during encoding
> - // (read-only use - when going over the segs of an image,
> - // see glz_encode_tmpl::compress).
> - // Thus, the 'next' field of the list's tail can be accessed only
> - // after all the new tail's data was set. Note that we are relying on
> - // an atomic assignment (32 bit variable).
> - // For the other thread that may read 'next' of the old tail, NULL_IMAGE_SEG_ID
> - // is equivalent to a segment with an image id that is different
> - // from the image id of the tail, so we don't need to further protect this field.
> - dict->window.segs[prev_tail].next = image->first_seg;
> - dict->window.used_segs_tail = seg_id;
> - }
> - image->is_alive = TRUE;
> -
> - return image;
> -}
> -
> -WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> - SharedDictionary *dict, LzImageType image_type,
> - int image_width, int image_height, int image_stride,
> - uint8_t *first_lines, unsigned int num_first_lines,
> - GlzUsrImageContext *usr_image_context,
> - uint32_t *image_head_dist)
> -{
> - WindowImage *new_win_head, *ret;
> - int image_size;
> -
> -
> - pthread_mutex_lock(&dict->lock);
> -
> - dict->cur_usr = usr;
> - GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] == NULL_IMAGE_SEG_ID);
> -
> - image_size = __get_pixels_num(image_type, image_height, image_stride);
> - new_win_head = glz_dictionary_window_get_new_head(dict, image_size);
> -
> - if (!glz_dictionary_is_in_use(dict)) {
> - glz_dictionary_window_remove_head(dict, encoder_id, new_win_head);
> - }
> -
> - ret = glz_dictionary_window_add_image(dict, image_type, image_size, image_height, image_stride,
> - first_lines, num_first_lines, usr_image_context);
> -
> - if (new_win_head) {
> - dict->window.encoders_heads[encoder_id] = new_win_head->first_seg;
> - *image_head_dist = (uint32_t)(ret->id - new_win_head->id); // shouldn't be greater than 32
> - // bit because the window size is
> - // limited to 2^25
> - } else {
> - dict->window.encoders_heads[encoder_id] = ret->first_seg;
> - *image_head_dist = 0;
> - }
> -
> -
> - // update encoders head (the other heads were already updated)
> - pthread_mutex_unlock(&dict->lock);
> - pthread_rwlock_rdlock(&dict->rw_alloc_lock);
> - return ret;
> -}
> -
> -void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> - SharedDictionary *dict)
> -{
> - uint32_t i;
> - uint32_t early_head_seg = NULL_IMAGE_SEG_ID;
> - uint32_t this_encoder_head_seg;
> -
> - pthread_rwlock_unlock(&dict->rw_alloc_lock);
> - pthread_mutex_lock(&dict->lock);
> - dict->cur_usr = usr;
> -
> - GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] != NULL_IMAGE_SEG_ID);
> - // get the earliest head in use (not including this encoder head)
> - for (i = 0; i < dict->max_encoders; i++) {
> - if (i != encoder_id) {
> - if (IMAGE_SEG_IS_EARLIER(dict, dict->window.encoders_heads[i], early_head_seg)) {
> - early_head_seg = dict->window.encoders_heads[i];
> - }
> - }
> - }
> -
> - // possible only if early_head_seg == NULL
> - if (IMAGE_SEG_IS_EARLIER(dict, dict->window.used_segs_head, early_head_seg)) {
> - early_head_seg = dict->window.used_segs_head;
> - }
> -
> - this_encoder_head_seg = dict->window.encoders_heads[encoder_id];
> -
> - GLZ_ASSERT(dict->cur_usr, early_head_seg != NULL_IMAGE_SEG_ID);
> -
> - if (IMAGE_SEG_IS_EARLIER(dict, this_encoder_head_seg, early_head_seg)) {
> - GLZ_ASSERT(dict->cur_usr,
> - this_encoder_head_seg == dict->window.used_images_head->first_seg);
> - glz_dictionary_window_remove_head(dict, encoder_id,
> - dict->window.segs[early_head_seg].image);
> - }
> -
> -
> - dict->window.encoders_heads[encoder_id] = NULL_IMAGE_SEG_ID;
> - pthread_mutex_unlock(&dict->lock);
> -}
> diff --git a/server/glz_encoder_dictionary.h b/server/glz_encoder_dictionary.h
> deleted file mode 100644
> index eb57aa5..0000000
> --- a/server/glz_encoder_dictionary.h
> +++ /dev/null
> @@ -1,69 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _H_GLZ_ENCODER_DICTIONARY
> -#define _H_GLZ_ENCODER_DICTIONARY
> -
> -#include <stdint.h>
> -#include "glz_encoder_config.h"
> -
> -/*
> - Interface for maintaining lz dictionary that is shared among several encoders.
> - The interface for accessing the dictionary for encoding purposes is located in
> - glz_encoder_dictionary_protected.h
> -*/
> -
> -typedef void GlzEncDictContext;
> -typedef void GlzEncDictImageContext;
> -
> -/* NOTE: DISPLAY_MIGRATE_DATA_VERSION should change in case GlzEncDictRestoreData changes*/
> -typedef struct GlzEncDictRestoreData {
> - uint32_t size;
> - uint32_t max_encoders;
> - uint64_t last_image_id;
> -} GlzEncDictRestoreData;
> -
> -/* size : maximal number of pixels occupying the window
> - max_encoders: maximal number of encoders that use the dictionary
> - usr : callbacks */
> -GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
> - GlzEncoderUsrContext *usr);
> -
> -void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
> -
> -/* returns the window capacity in pixels */
> -uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *);
> -
> -/* returns the current state of the dictionary.
> - NOTE - you should use it only when no encoder uses the dictionary. */
> -void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
> - GlzEncDictRestoreData *out_data,
> - GlzEncoderUsrContext *usr);
> -
> -/* creates a dictionary and initialized it by use the given info */
> -GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
> - GlzEncoderUsrContext *usr);
> -
> -/* NOTE - you should use this routine only when no encoder uses the dictionary. */
> -void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
> -
> -/* image: the context returned by the encoder when the image was encoded.
> - NOTE - you should use this routine only when no encoder uses the dictionary.*/
> -void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
> - GlzEncDictImageContext *image, GlzEncoderUsrContext *usr);
> -
> -#endif // _H_GLZ_ENCODER_DICTIONARY
> diff --git a/server/glz_encoder_dictionary_protected.h b/server/glz_encoder_dictionary_protected.h
> deleted file mode 100644
> index 098684f..0000000
> --- a/server/glz_encoder_dictionary_protected.h
> +++ /dev/null
> @@ -1,186 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _H_GLZ_ENCODER_DICTIONARY_PROTECTED
> -#define _H_GLZ_ENCODER_DICTIONARY_PROTECTED
> -
> -/* Interface for using the dictionary for encoding.
> - Data structures are exposed for the encoder for efficiency
> - purposes. */
> -typedef struct WindowImage WindowImage;
> -typedef struct WindowImageSegment WindowImageSegment;
> -
> -
> -//#define CHAINED_HASH
> -
> -#ifdef CHAINED_HASH
> -#define HASH_SIZE_LOG 16
> -#define HASH_CHAIN_SIZE 4
> -#else
> -#define HASH_SIZE_LOG 20
> -#define HASH_CHAIN_SIZE 1
> -#endif
> -
> -#define HASH_SIZE (1 << HASH_SIZE_LOG)
> -#define HASH_MASK (HASH_SIZE - 1)
> -
> -typedef struct HashEntry HashEntry;
> -
> -typedef struct SharedDictionary SharedDictionary;
> -
> -struct WindowImage {
> - uint64_t id;
> - LzImageType type;
> - int size; // in pixels
> - uint32_t first_seg;
> - GlzUsrImageContext *usr_context;
> - WindowImage* next;
> - uint8_t is_alive;
> -};
> -
> -#define MAX_IMAGE_SEGS_NUM (0xffffffff)
> -#define NULL_IMAGE_SEG_ID MAX_IMAGE_SEGS_NUM
> -#define INIT_IMAGE_SEGS_NUM 1000
> -
> -/* Images can be separated into several chunks. The basic unit of the
> - dictionary window is one image segment. Each segment is encoded separately.
> - An encoded match can refer to only one segment.*/
> -struct WindowImageSegment {
> - WindowImage *image;
> - void *lines;
> - void *lines_end;
> - uint32_t pixels_num; // Number of pixels in the segment
> - uint64_t pixels_so_far; // Total no. pixels passed through the window till this segment.
> - // NOTE - never use size delta independently. It should
> - // always be used with respect to a previous size delta
> - uint32_t next;
> -};
> -
> -
> -struct __attribute__ ((__packed__)) HashEntry {
> - uint32_t image_seg_idx;
> - uint32_t ref_pix_idx;
> -};
> -
> -
> -struct SharedDictionary {
> - struct {
> - /* The segments storage. A dynamic array.
> - By referring to a segment by its index, instead of address,
> - we save space in the hash entries (32bit instead of 64bit) */
> - WindowImageSegment *segs;
> - uint32_t segs_quota;
> -
> - /* The window is manged as a linked list rather than as a cyclic
> - array in order to keep the indices of the segments consistent
> - after reallocation */
> -
> - /* the window in a resolution of image segments */
> - uint32_t used_segs_head; // the latest head
> - uint32_t used_segs_tail;
> - uint32_t free_segs_head;
> -
> - uint32_t *encoders_heads; // Holds for each encoder (by id), the window head when
> - // it started the encoding.
> - // The head is NULL_IMAGE_SEG_ID when the encoder is
> - // not encoding.
> -
> - /* the window in a resolution of images. But here the head contains the oldest head*/
> - WindowImage* used_images_tail;
> - WindowImage* used_images_head;
> - WindowImage* free_images;
> -
> - uint64_t pixels_so_far;
> - uint32_t size_limit; // max number of pixels in a window (per encoder)
> - } window;
> -
> - /* Concurrency issues: the reading/writing of each entry field should be atomic.
> - It is allowed that the reading/writing of the whole entry won't be atomic,
> - since before we access a reference we check its validity*/
> -#ifdef CHAINED_HASH
> - HashEntry htab[HASH_SIZE][HASH_CHAIN_SIZE];
> - uint8_t htab_counter[HASH_SIZE]; //cyclic counter for the next entry in a chain to be assigned
> -#else
> - HashEntry htab[HASH_SIZE];
> -#endif
> -
> - uint64_t last_image_id;
> - uint32_t max_encoders;
> - pthread_mutex_t lock;
> - pthread_rwlock_t rw_alloc_lock;
> - GlzEncoderUsrContext *cur_usr; // each encoder has other context.
> -};
> -
> -/*
> - Add the image to the tail of the window.
> - If possible, release images from the head of the window.
> - Also perform concurrency related operations.
> -
> - usr_image_context: when an image is released from the window due to capacity overflow,
> - usr_image_context is given as a parameter to the free_image callback.
> -
> - image_head_dist : the number of images between the current image and the head of the
> - window that is associated with the encoder.
> -*/
> -WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> - SharedDictionary *dict, LzImageType image_type,
> - int image_width, int image_height, int image_stride,
> - uint8_t *first_lines, unsigned int num_first_lines,
> - GlzUsrImageContext *usr_image_context,
> - uint32_t *image_head_dist);
> -
> -/*
> - Performs concurrency related operations.
> - If possible, release images from the head of the window.
> -*/
> -void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
> - SharedDictionary *dict);
> -
> -#define IMAGE_SEG_IS_EARLIER(dict, dst_seg, src_seg) ( \
> - ((src_seg) == NULL_IMAGE_SEG_ID) || (((dst_seg) != NULL_IMAGE_SEG_ID) \
> - && ((dict)->window.segs[(dst_seg)].pixels_so_far < \
> - (dict)->window.segs[(src_seg)].pixels_so_far)))
> -
> -
> -#ifdef CHAINED_HASH
> -#define UPDATE_HASH(dict, hval, seg, pix) { \
> - uint8_t tmp_count = (dict)->htab_counter[hval]; \
> - (dict)->htab[hval][tmp_count].image_seg_idx = seg; \
> - (dict)->htab[hval][tmp_count].ref_pix_idx = pix; \
> - tmp_count = ((tmp_count) + 1) & (HASH_CHAIN_SIZE - 1); \
> - dict->htab_counter[hval] = tmp_count; \
> -}
> -#else
> -#define UPDATE_HASH(dict, hval, seg, pix) { \
> - (dict)->htab[hval].image_seg_idx = seg; \
> - (dict)->htab[hval].ref_pix_idx = pix; \
> -}
> -#endif
> -
> -/* checks if the reference segment is located in the range of the window
> - of the current encoder */
> -#define REF_SEG_IS_VALID(dict, enc_id, ref_seg, src_seg) ( \
> - ((ref_seg) == (src_seg)) || \
> - ((ref_seg)->image && \
> - (ref_seg)->image->is_alive && \
> - (src_seg->image->type == ref_seg->image->type) && \
> - (ref_seg->pixels_so_far <= src_seg->pixels_so_far) && \
> - ((dict)->window.segs[ \
> - (dict)->window.encoders_heads[enc_id]].pixels_so_far <= \
> - ref_seg->pixels_so_far)))
> -
> -#endif // _H_GLZ_ENCODER_DICTIONARY_PROTECTED
> diff --git a/server/image-cache.c b/server/image-cache.c
> new file mode 100644
> index 0000000..f4d2ee9
> --- /dev/null
> +++ b/server/image-cache.c
> @@ -0,0 +1,214 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009-2015 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 "image-cache.h"
> +#include "red_parse_qxl.h"
> +#include "display-channel.h"
> +
> +static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t id)
> +{
> + ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE];
> +
> + while (item) {
> + if (item->id == id) {
> + return item;
> + }
> + item = item->next;
> + }
> + return NULL;
> +}
> +
> +int image_cache_hit(ImageCache *cache, uint64_t id)
> +{
> + ImageCacheItem *item;
> + if (!(item = image_cache_find(cache, id))) {
> + return FALSE;
> + }
> +#ifdef IMAGE_CACHE_AGE
> + item->age = cache->age;
> +#endif
> + ring_remove(&item->lru_link);
> + ring_add(&cache->lru, &item->lru_link);
> + return TRUE;
> +}
> +
> +static void image_cache_remove(ImageCache *cache, ImageCacheItem *item)
> +{
> + ImageCacheItem **now;
> +
> + now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
> + for (;;) {
> + spice_assert(*now);
> + if (*now == item) {
> + *now = item->next;
> + break;
> + }
> + now = &(*now)->next;
> + }
> + ring_remove(&item->lru_link);
> + pixman_image_unref(item->image);
> + free(item);
> +#ifndef IMAGE_CACHE_AGE
> + cache->num_items--;
> +#endif
> +}
> +
> +#define IMAGE_CACHE_MAX_ITEMS 2
> +
> +static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image)
> +{
> + ImageCache *cache = (ImageCache *)spice_cache;
> + ImageCacheItem *item;
> +
> +#ifndef IMAGE_CACHE_AGE
> + if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) {
> + ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru);
> + spice_assert(tail);
> + image_cache_remove(cache, tail);
> + }
> +#endif
> +
> + item = spice_new(ImageCacheItem, 1);
> + item->id = id;
> +#ifdef IMAGE_CACHE_AGE
> + item->age = cache->age;
> +#else
> + cache->num_items++;
> +#endif
> + item->image = pixman_image_ref(image);
> + ring_item_init(&item->lru_link);
> +
> + item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
> + cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item;
> +
> + ring_add(&cache->lru, &item->lru_link);
> +}
> +
> +static pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id)
> +{
> + ImageCache *cache = (ImageCache *)spice_cache;
> +
> + ImageCacheItem *item = image_cache_find(cache, id);
> + if (!item) {
> + spice_error("not found");
> + }
> + return pixman_image_ref(item->image);
> +}
> +
> +void image_cache_init(ImageCache *cache)
> +{
> + static SpiceImageCacheOps image_cache_ops = {
> + image_cache_put,
> + image_cache_get,
> + };
> +
> + cache->base.ops = &image_cache_ops;
> + memset(cache->hash_table, 0, sizeof(cache->hash_table));
> + ring_init(&cache->lru);
> +#ifdef IMAGE_CACHE_AGE
> + cache->age = 0;
> +#else
> + cache->num_items = 0;
> +#endif
> +}
> +
> +void image_cache_reset(ImageCache *cache)
> +{
> + ImageCacheItem *item;
> +
> + while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) {
> + image_cache_remove(cache, item);
> + }
> +#ifdef IMAGE_CACHE_AGE
> + cache->age = 0;
> +#endif
> +}
> +
> +#define IMAGE_CACHE_DEPTH 4
> +
> +void image_cache_aging(ImageCache *cache)
> +{
> +#ifdef IMAGE_CACHE_AGE
> + ImageCacheItem *item;
> +
> + cache->age++;
> + while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) &&
> + cache->age - item->age > IMAGE_CACHE_DEPTH) {
> + image_cache_remove(cache, item);
> + }
> +#endif
> +}
> +
> +void image_cache_localize(ImageCache *cache, SpiceImage **image_ptr,
> + SpiceImage *image_store, Drawable *drawable)
> +{
> + SpiceImage *image = *image_ptr;
> +
> + if (image == NULL) {
> + spice_assert(drawable != NULL);
> + spice_assert(drawable->red_drawable->self_bitmap_image != NULL);
> + *image_ptr = drawable->red_drawable->self_bitmap_image;
> + return;
> + }
> +
> + if (image_cache_hit(cache, image->descriptor.id)) {
> + image_store->descriptor = image->descriptor;
> + image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
> + image_store->descriptor.flags = 0;
> + *image_ptr = image_store;
> + return;
> + }
> +
> + switch (image->descriptor.type) {
> + case SPICE_IMAGE_TYPE_QUIC: {
> + image_store->descriptor = image->descriptor;
> + image_store->u.quic = image->u.quic;
> + *image_ptr = image_store;
> +#ifdef IMAGE_CACHE_AGE
> + image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
> +#else
> + if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) {
> + image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
> + }
> +#endif
> + break;
> + }
> + case SPICE_IMAGE_TYPE_BITMAP:
> + case SPICE_IMAGE_TYPE_SURFACE:
> + /* nothing */
> + break;
> + default:
> + spice_error("invalid image type");
> + }
> +}
> +
> +void image_cache_localize_brush(ImageCache *cache, SpiceBrush *brush, SpiceImage *image_store)
> +{
> + if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
> + image_cache_localize(cache, &brush->u.pattern.pat, image_store, NULL);
> + }
> +}
> +
> +void image_cache_localize_mask(ImageCache *cache, SpiceQMask *mask, SpiceImage *image_store)
> +{
> + if (mask->bitmap) {
> + image_cache_localize(cache, &mask->bitmap, image_store, NULL);
> + }
> +}
> diff --git a/server/image-cache.h b/server/image-cache.h
> new file mode 100644
> index 0000000..d66c7ff
> --- /dev/null
> +++ b/server/image-cache.h
> @@ -0,0 +1,65 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009-2015 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/>.
> +*/
> +#ifndef IMAGE_CACHE_H_
> +#define IMAGE_CACHE_H_
> +
> +#include <inttypes.h>
> +
> +#include "common/pixman_utils.h"
> +#include "common/canvas_base.h"
> +#include "common/ring.h"
> +
> +/* FIXME: move back to display_channel.h (once structs are private) */
> +typedef struct Drawable Drawable;
> +typedef struct DisplayChannelClient DisplayChannelClient;
> +
> +typedef struct ImageCacheItem {
> + RingItem lru_link;
> + uint64_t id;
> +#ifdef IMAGE_CACHE_AGE
> + uint32_t age;
> +#endif
> + struct ImageCacheItem *next;
> + pixman_image_t *image;
> +} ImageCacheItem;
> +
> +#define IMAGE_CACHE_HASH_SIZE 1024
> +
> +typedef struct ImageCache {
> + SpiceImageCache base;
> + ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE];
> + Ring lru;
> +#ifdef IMAGE_CACHE_AGE
> + uint32_t age;
> +#else
> + uint32_t num_items;
> +#endif
> +} ImageCache;
> +
> +int image_cache_hit (ImageCache *cache, uint64_t id);
> +void image_cache_init (ImageCache *cache);
> +void image_cache_reset (ImageCache *cache);
> +void image_cache_aging (ImageCache *cache);
> +void image_cache_localize (ImageCache *cache, SpiceImage **image_ptr,
> + SpiceImage *image_store, Drawable *drawable);
> +void image_cache_localize_brush (ImageCache *cache, SpiceBrush *brush,
> + SpiceImage *image_store);
> +void image_cache_localize_mask (ImageCache *cache, SpiceQMask *mask,
> + SpiceImage *image_store);
> +
> +#endif
> diff --git a/server/inputs-channel.c b/server/inputs-channel.c
> new file mode 100644
> index 0000000..3e8fccd
> --- /dev/null
> +++ b/server/inputs-channel.c
> @@ -0,0 +1,679 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009 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 <netinet/in.h> // IPPROTO_TCP
> +#include <netinet/tcp.h> // TCP_NODELAY
> +#include <fcntl.h>
> +#include <stddef.h> // NULL
> +#include <errno.h>
> +#include <spice/macros.h>
> +#include <spice/vd_agent.h>
> +#include <spice/protocol.h>
> +#include <stdbool.h>
> +
> +#include "common/marshaller.h"
> +#include "common/messages.h"
> +#include "common/generated_server_marshallers.h"
> +
> +#include "demarshallers.h"
> +#include "spice.h"
> +#include "red_common.h"
> +#include "reds.h"
> +#include "reds_stream.h"
> +#include "red_channel.h"
> +#include "main-channel.h"
> +#include "inputs-channel.h"
> +#include "migration-protocol.h"
> +
> +// TODO: RECEIVE_BUF_SIZE used to be the same for inputs_channel and main_channel
> +// since it was defined once in reds.c which contained both.
> +// Now that they are split we can give a more fitting value for inputs - what
> +// should it be?
> +#define REDS_AGENT_WINDOW_SIZE 10
> +#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
> +
> +// approximate max receive message size
> +#define RECEIVE_BUF_SIZE \
> + (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE)
> +
> +struct SpiceKbdState {
> + bool push_ext;
> +
> + /* track key press state */
> + bool key[0x7f];
> + bool key_ext[0x7f];
> +};
> +
> +struct SpiceMouseState {
> + int dummy;
> +};
> +
> +struct SpiceTabletState {
> + int dummy;
> +};
> +
> +typedef struct InputsChannelClient {
> + RedChannelClient base;
> + uint16_t motion_count;
> +} InputsChannelClient;
> +
> +typedef struct InputsChannel {
> + RedChannel base;
> + uint8_t recv_buf[RECEIVE_BUF_SIZE];
> + VDAgentMouseState mouse_state;
> + int src_during_migrate;
> +} InputsChannel;
> +
> +enum {
> + PIPE_ITEM_INPUTS_INIT = PIPE_ITEM_TYPE_CHANNEL_BASE,
> + PIPE_ITEM_MOUSE_MOTION_ACK,
> + PIPE_ITEM_KEY_MODIFIERS,
> + PIPE_ITEM_MIGRATE_DATA,
> +};
> +
> +typedef struct InputsPipeItem {
> + PipeItem base;
> +} InputsPipeItem;
> +
> +typedef struct KeyModifiersPipeItem {
> + PipeItem base;
> + uint8_t modifiers;
> +} KeyModifiersPipeItem;
> +
> +typedef struct InputsInitPipeItem {
> + PipeItem base;
> + uint8_t modifiers;
> +} InputsInitPipeItem;
> +
> +static SpiceKbdInstance *keyboard = NULL;
> +static SpiceMouseInstance *mouse = NULL;
> +static SpiceTabletInstance *tablet = NULL;
> +
> +static SpiceTimer *key_modifiers_timer;
> +
> +static InputsChannel *g_inputs_channel = NULL;
> +
> +#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/
> +
> +#define SCROLL_LOCK_SCAN_CODE 0x46
> +#define NUM_LOCK_SCAN_CODE 0x45
> +#define CAPS_LOCK_SCAN_CODE 0x3a
> +
> +int inputs_inited(void)
> +{
> + return !!g_inputs_channel;
> +}
> +
> +int inputs_set_keyboard(SpiceKbdInstance *_keyboard)
> +{
> + if (keyboard) {
> + spice_printerr("already have keyboard");
> + return -1;
> + }
> + keyboard = _keyboard;
> + keyboard->st = spice_new0(SpiceKbdState, 1);
> + return 0;
> +}
> +
> +int inputs_set_mouse(SpiceMouseInstance *_mouse)
> +{
> + if (mouse) {
> + spice_printerr("already have mouse");
> + return -1;
> + }
> + mouse = _mouse;
> + mouse->st = spice_new0(SpiceMouseState, 1);
> + return 0;
> +}
> +
> +int inputs_set_tablet(SpiceTabletInstance *_tablet)
> +{
> + if (tablet) {
> + spice_printerr("already have tablet");
> + return -1;
> + }
> + tablet = _tablet;
> + tablet->st = spice_new0(SpiceTabletState, 1);
> + return 0;
> +}
> +
> +int inputs_has_tablet(void)
> +{
> + return !!tablet;
> +}
> +
> +void inputs_detach_tablet(SpiceTabletInstance *_tablet)
> +{
> + spice_printerr("");
> + tablet = NULL;
> +}
> +
> +void inputs_set_tablet_logical_size(int x_res, int y_res)
> +{
> + SpiceTabletInterface *sif;
> +
> + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> + sif->set_logical_size(tablet, x_res, y_res);
> +}
> +
> +const VDAgentMouseState *inputs_get_mouse_state(void)
> +{
> + spice_assert(g_inputs_channel);
> + return &g_inputs_channel->mouse_state;
> +}
> +
> +static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size)
> +{
> + InputsChannel *inputs_channel = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base);
> +
> + if (size > RECEIVE_BUF_SIZE) {
> + spice_printerr("error: too large incoming message");
> + return NULL;
> + }
> + return inputs_channel->recv_buf;
> +}
> +
> +static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> +{
> +}
> +
> +#define OUTGOING_OK 0
> +#define OUTGOING_FAILED -1
> +#define OUTGOING_BLOCKED 1
> +
> +#define RED_MOUSE_STATE_TO_LOCAL(state) \
> + ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \
> + ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) << 1) | \
> + ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1))
> +
> +#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \
> + (((state & SPICE_MOUSE_BUTTON_MASK_LEFT) ? VD_AGENT_LBUTTON_MASK : 0) | \
> + ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) ? VD_AGENT_MBUTTON_MASK : 0) | \
> + ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) ? VD_AGENT_RBUTTON_MASK : 0))
> +
> +static void activate_modifiers_watch(void)
> +{
> + core->timer_start(key_modifiers_timer, KEY_MODIFIERS_TTL);
> +}
> +
> +static void kbd_push_scan(SpiceKbdInstance *sin, uint8_t scan)
> +{
> + SpiceKbdInterface *sif;
> +
> + if (!sin) {
> + return;
> + }
> + sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base);
> +
> + /* track XT scan code set 1 key state */
> + if (scan == 0xe0) {
> + sin->st->push_ext = TRUE;
> + } else {
> + bool *state = sin->st->push_ext ? sin->st->key : sin->st->key_ext;
> + sin->st->push_ext = FALSE;
> + state[scan & 0x7f] = !(scan & 0x80);
> + }
> +
> + sif->push_scan_freg(sin, scan);
> +}
> +
> +static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
> +{
> + SpiceKbdInterface *sif;
> +
> + if (!sin) {
> + return 0;
> + }
> + sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base);
> + return sif->get_leds(sin);
> +}
> +
> +static PipeItem *inputs_key_modifiers_item_new(
> + RedChannelClient *rcc, void *data, int num)
> +{
> + KeyModifiersPipeItem *item = spice_malloc(sizeof(KeyModifiersPipeItem));
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_KEY_MODIFIERS);
> + item->modifiers = *(uint8_t *)data;
> + return &item->base;
> +}
> +
> +static void inputs_channel_send_migrate_data(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + PipeItem *item)
> +{
> + InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base);
> +
> + g_inputs_channel->src_during_migrate = FALSE;
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item);
> +
> + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_MAGIC);
> + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_VERSION);
> + spice_marshaller_add_uint16(m, icc->motion_count);
> +}
> +
> +static void inputs_channel_release_pipe_item(RedChannelClient *rcc,
> + PipeItem *base, int item_pushed)
> +{
> + free(base);
> +}
> +
> +static void inputs_channel_send_item(RedChannelClient *rcc, PipeItem *base)
> +{
> + SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
> +
> + switch (base->type) {
> + case PIPE_ITEM_KEY_MODIFIERS:
> + {
> + SpiceMsgInputsKeyModifiers key_modifiers;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_KEY_MODIFIERS, base);
> + key_modifiers.modifiers =
> + SPICE_CONTAINEROF(base, KeyModifiersPipeItem, base)->modifiers;
> + spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers);
> + break;
> + }
> + case PIPE_ITEM_INPUTS_INIT:
> + {
> + SpiceMsgInputsInit inputs_init;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_INIT, base);
> + inputs_init.keyboard_modifiers =
> + SPICE_CONTAINEROF(base, InputsInitPipeItem, base)->modifiers;
> + spice_marshall_msg_inputs_init(m, &inputs_init);
> + break;
> + }
> + case PIPE_ITEM_MOUSE_MOTION_ACK:
> + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK, base);
> + break;
> + case PIPE_ITEM_MIGRATE_DATA:
> + inputs_channel_send_migrate_data(rcc, m, base);
> + break;
> + default:
> + spice_warning("invalid pipe iten %d", base->type);
> + break;
> + }
> + red_channel_client_begin_send_message(rcc);
> +}
> +
> +static int inputs_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type,
> + void *message)
> +{
> + InputsChannel *inputs_channel = (InputsChannel *)rcc->channel;
> + InputsChannelClient *icc = (InputsChannelClient *)rcc;
> + uint32_t i;
> +
> + spice_assert(g_inputs_channel == inputs_channel);
> + switch (type) {
> + case SPICE_MSGC_INPUTS_KEY_DOWN: {
> + SpiceMsgcKeyDown *key_down = message;
> + if (key_down->code == CAPS_LOCK_SCAN_CODE ||
> + key_down->code == NUM_LOCK_SCAN_CODE ||
> + key_down->code == SCROLL_LOCK_SCAN_CODE) {
> + activate_modifiers_watch();
> + }
> + }
> + case SPICE_MSGC_INPUTS_KEY_UP: {
> + SpiceMsgcKeyUp *key_up = message;
> + for (i = 0; i < 4; i++) {
> + uint8_t code = (key_up->code >> (i * 8)) & 0xff;
> + if (code == 0) {
> + break;
> + }
> + kbd_push_scan(keyboard, code);
> + }
> + break;
> + }
> + case SPICE_MSGC_INPUTS_KEY_SCANCODE: {
> + uint8_t *code = message;
> + for (i = 0; i < size; i++) {
> + kbd_push_scan(keyboard, code[i]);
> + }
> + break;
> + }
> + case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
> + SpiceMsgcMouseMotion *mouse_motion = message;
> +
> + if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 &&
> + !g_inputs_channel->src_during_migrate) {
> + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> + icc->motion_count = 0;
> + }
> + if (mouse && reds_get_mouse_mode() == SPICE_MOUSE_MODE_SERVER) {
> + SpiceMouseInterface *sif;
> + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> + sif->motion(mouse,
> + mouse_motion->dx, mouse_motion->dy, 0,
> + RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state));
> + }
> + break;
> + }
> + case SPICE_MSGC_INPUTS_MOUSE_POSITION: {
> + SpiceMsgcMousePosition *pos = message;
> +
> + if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 &&
> + !g_inputs_channel->src_during_migrate) {
> + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> + icc->motion_count = 0;
> + }
> + if (reds_get_mouse_mode() != SPICE_MOUSE_MODE_CLIENT) {
> + break;
> + }
> + spice_assert((reds_get_agent_mouse() && reds_has_vdagent()) || tablet);
> + if (!reds_get_agent_mouse() || !reds_has_vdagent()) {
> + SpiceTabletInterface *sif;
> + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> + sif->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state));
> + break;
> + }
> + VDAgentMouseState *mouse_state = &inputs_channel->mouse_state;
> + mouse_state->x = pos->x;
> + mouse_state->y = pos->y;
> + mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state);
> + mouse_state->display_id = pos->display_id;
> + reds_handle_agent_mouse_event(mouse_state);
> + break;
> + }
> + case SPICE_MSGC_INPUTS_MOUSE_PRESS: {
> + SpiceMsgcMousePress *mouse_press = message;
> + int dz = 0;
> + if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) {
> + dz = -1;
> + } else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) {
> + dz = 1;
> + }
> + if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) {
> + if (reds_get_agent_mouse() && reds_has_vdagent()) {
> + inputs_channel->mouse_state.buttons =
> + RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) |
> + (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) |
> + (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0);
> + reds_handle_agent_mouse_event(&inputs_channel->mouse_state);
> + } else if (tablet) {
> + SpiceTabletInterface *sif;
> + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> + sif->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
> + }
> + } else if (mouse) {
> + SpiceMouseInterface *sif;
> + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> + sif->motion(mouse, 0, 0, dz,
> + RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
> + }
> + break;
> + }
> + case SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
> + SpiceMsgcMouseRelease *mouse_release = message;
> + if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) {
> + if (reds_get_agent_mouse() && reds_has_vdagent()) {
> + inputs_channel->mouse_state.buttons =
> + RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
> + reds_handle_agent_mouse_event(&inputs_channel->mouse_state);
> + } else if (tablet) {
> + SpiceTabletInterface *sif;
> + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> + sif->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
> + }
> + } else if (mouse) {
> + SpiceMouseInterface *sif;
> + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> + sif->buttons(mouse,
> + RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
> + }
> + break;
> + }
> + case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
> + SpiceMsgcKeyModifiers *modifiers = message;
> + uint8_t leds;
> +
> + if (!keyboard) {
> + break;
> + }
> + leds = kbd_get_leds(keyboard);
> + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) !=
> + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK)) {
> + kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE);
> + kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE | 0x80);
> + }
> + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) !=
> + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK)) {
> + kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE);
> + kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE | 0x80);
> + }
> + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) !=
> + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK)) {
> + kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE);
> + kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE | 0x80);
> + }
> + activate_modifiers_watch();
> + break;
> + }
> + case SPICE_MSGC_DISCONNECTING:
> + break;
> + default:
> + return red_channel_client_handle_message(rcc, size, type, message);
> + }
> + return TRUE;
> +}
> +
> +static void inputs_release_keys(void)
> +{
> + int i;
> + SpiceKbdState *st;
> +
> + if (!keyboard) {
> + return;
> + }
> + st = keyboard->st;
> +
> + for (i = 0; i < SPICE_N_ELEMENTS(st->key); i++) {
> + if (!st->key[i])
> + continue;
> +
> + st->key[i] = FALSE;
> + kbd_push_scan(keyboard, i | 0x80);
> + }
> +
> + for (i = 0; i < SPICE_N_ELEMENTS(st->key_ext); i++) {
> + if (!st->key_ext[i])
> + continue;
> +
> + st->key_ext[i] = FALSE;
> + kbd_push_scan(keyboard, 0xe0);
> + kbd_push_scan(keyboard, i | 0x80);
> + }
> +}
> +
> +static void inputs_channel_on_disconnect(RedChannelClient *rcc)
> +{
> + if (!rcc) {
> + return;
> + }
> + inputs_release_keys();
> +}
> +
> +static void inputs_pipe_add_init(RedChannelClient *rcc)
> +{
> + InputsInitPipeItem *item = spice_malloc(sizeof(InputsInitPipeItem));
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_INPUTS_INIT);
> + item->modifiers = kbd_get_leds(keyboard);
> + red_channel_client_pipe_add_push(rcc, &item->base);
> +}
> +
> +static int inputs_channel_config_socket(RedChannelClient *rcc)
> +{
> + int delay_val = 1;
> + RedsStream *stream = red_channel_client_get_stream(rcc);
> +
> + if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY,
> + &delay_val, sizeof(delay_val)) == -1) {
> + if (errno != ENOTSUP && errno != ENOPROTOOPT) {
> + spice_printerr("setsockopt failed, %s", strerror(errno));
> + return FALSE;
> + }
> + }
> +
> + return TRUE;
> +}
> +
> +static void inputs_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
> +{
> +}
> +
> +static void inputs_connect(RedChannel *channel, RedClient *client,
> + RedsStream *stream, int migration,
> + int num_common_caps, uint32_t *common_caps,
> + int num_caps, uint32_t *caps)
> +{
> + InputsChannelClient *icc;
> +
> + spice_assert(g_inputs_channel);
> + spice_assert(channel == &g_inputs_channel->base);
> +
> + if (!reds_stream_is_ssl(stream) && !red_client_during_migrate_at_target(client)) {
> + main_channel_client_push_notify(red_client_get_main(client),
> + "keyboard channel is insecure");
> + }
> +
> + spice_printerr("inputs channel client create");
> + icc = (InputsChannelClient*)red_channel_client_create(sizeof(InputsChannelClient),
> + channel,
> + client,
> + stream,
> + FALSE,
> + num_common_caps, common_caps,
> + num_caps, caps);
> + if (!icc) {
> + return;
> + }
> + icc->motion_count = 0;
> + inputs_pipe_add_init(&icc->base);
> +}
> +
> +static void inputs_migrate(RedChannelClient *rcc)
> +{
> + g_inputs_channel->src_during_migrate = TRUE;
> + red_channel_client_default_migrate(rcc);
> +}
> +
> +static void inputs_push_keyboard_modifiers(uint8_t modifiers)
> +{
> + if (!g_inputs_channel || !red_channel_is_connected(&g_inputs_channel->base) ||
> + g_inputs_channel->src_during_migrate) {
> + return;
> + }
> + red_channel_pipes_new_add_push(&g_inputs_channel->base,
> + inputs_key_modifiers_item_new, (void*)&modifiers);
> +}
> +
> +void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds)
> +{
> + inputs_push_keyboard_modifiers(leds);
> +}
> +
> +static void key_modifiers_sender(void *opaque)
> +{
> + inputs_push_keyboard_modifiers(kbd_get_leds(keyboard));
> +}
> +
> +static int inputs_channel_handle_migrate_flush_mark(RedChannelClient *rcc)
> +{
> + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MIGRATE_DATA);
> + return TRUE;
> +}
> +
> +static int inputs_channel_handle_migrate_data(RedChannelClient *rcc,
> + uint32_t size,
> + void *message)
> +{
> + InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base);
> + SpiceMigrateDataHeader *header;
> + SpiceMigrateDataInputs *mig_data;
> +
> + header = (SpiceMigrateDataHeader *)message;
> + mig_data = (SpiceMigrateDataInputs *)(header + 1);
> +
> + if (!migration_protocol_validate_header(header,
> + SPICE_MIGRATE_DATA_INPUTS_MAGIC,
> + SPICE_MIGRATE_DATA_INPUTS_VERSION)) {
> + spice_error("bad header");
> + return FALSE;
> + }
> + key_modifiers_sender(NULL);
> + icc->motion_count = mig_data->motion_count;
> +
> + for (; icc->motion_count >= SPICE_INPUT_MOTION_ACK_BUNCH;
> + icc->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH) {
> + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> + }
> + return TRUE;
> +}
> +
> +void inputs_init(void)
> +{
> + ChannelCbs channel_cbs = { NULL, };
> + ClientCbs client_cbs = { NULL, };
> +
> + spice_assert(!g_inputs_channel);
> +
> + channel_cbs.config_socket = inputs_channel_config_socket;
> + channel_cbs.on_disconnect = inputs_channel_on_disconnect;
> + channel_cbs.send_item = inputs_channel_send_item;
> + channel_cbs.hold_item = inputs_channel_hold_pipe_item;
> + channel_cbs.release_item = inputs_channel_release_pipe_item;
> + channel_cbs.alloc_recv_buf = inputs_channel_alloc_msg_rcv_buf;
> + channel_cbs.release_recv_buf = inputs_channel_release_msg_rcv_buf;
> + channel_cbs.handle_migrate_data = inputs_channel_handle_migrate_data;
> + channel_cbs.handle_migrate_flush_mark = inputs_channel_handle_migrate_flush_mark;
> +
> + g_inputs_channel = (InputsChannel *)red_channel_create_parser(
> + sizeof(InputsChannel),
> + core,
> + SPICE_CHANNEL_INPUTS, 0,
> + FALSE, /* handle_acks */
> + spice_get_client_channel_parser(SPICE_CHANNEL_INPUTS, NULL),
> + inputs_channel_handle_parsed,
> + &channel_cbs,
> + SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER);
> +
> + if (!g_inputs_channel) {
> + spice_error("failed to allocate Inputs Channel");
> + }
> +
> + client_cbs.connect = inputs_connect;
> + client_cbs.migrate = inputs_migrate;
> + red_channel_register_client_cbs(&g_inputs_channel->base, &client_cbs);
> +
> + red_channel_set_cap(&g_inputs_channel->base, SPICE_INPUTS_CAP_KEY_SCANCODE);
> + reds_register_channel(&g_inputs_channel->base);
> +
> + if (!(key_modifiers_timer = core->timer_add(key_modifiers_sender, NULL))) {
> + spice_error("key modifiers timer create failed");
> + }
> +}
> diff --git a/server/inputs-channel.h b/server/inputs-channel.h
> new file mode 100644
> index 0000000..7f7ace0
> --- /dev/null
> +++ b/server/inputs-channel.h
> @@ -0,0 +1,38 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef _INPUTS_CHANNEL_H_
> +#define _INPUTS_CHANNEL_H_
> +
> +// Inputs channel, dealing with keyboard, mouse, tablet.
> +// This include should only be used by reds.c and inputs-channel.c
> +
> +#include <stdint.h>
> +#include <spice/vd_agent.h>
> +
> +void inputs_init(void);
> +int inputs_inited(void);
> +int inputs_has_tablet(void);
> +const VDAgentMouseState *inputs_get_mouse_state(void);
> +void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds);
> +int inputs_set_keyboard(SpiceKbdInstance *_keyboard);
> +int inputs_set_mouse(SpiceMouseInstance *_mouse);
> +int inputs_set_tablet(SpiceTabletInstance *_tablet);
> +void inputs_detach_tablet(SpiceTabletInstance *_tablet);
> +void inputs_set_tablet_logical_size(int x_res, int y_res);
> +
> +#endif
> diff --git a/server/inputs_channel.c b/server/inputs_channel.c
> deleted file mode 100644
> index 2934572..0000000
> --- a/server/inputs_channel.c
> +++ /dev/null
> @@ -1,679 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009 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 <netinet/in.h> // IPPROTO_TCP
> -#include <netinet/tcp.h> // TCP_NODELAY
> -#include <fcntl.h>
> -#include <stddef.h> // NULL
> -#include <errno.h>
> -#include <spice/macros.h>
> -#include <spice/vd_agent.h>
> -#include <spice/protocol.h>
> -#include <stdbool.h>
> -
> -#include "common/marshaller.h"
> -#include "common/messages.h"
> -#include "common/generated_server_marshallers.h"
> -
> -#include "demarshallers.h"
> -#include "spice.h"
> -#include "red_common.h"
> -#include "reds.h"
> -#include "reds_stream.h"
> -#include "red_channel.h"
> -#include "main_channel.h"
> -#include "inputs_channel.h"
> -#include "migration_protocol.h"
> -
> -// TODO: RECEIVE_BUF_SIZE used to be the same for inputs_channel and main_channel
> -// since it was defined once in reds.c which contained both.
> -// Now that they are split we can give a more fitting value for inputs - what
> -// should it be?
> -#define REDS_AGENT_WINDOW_SIZE 10
> -#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
> -
> -// approximate max receive message size
> -#define RECEIVE_BUF_SIZE \
> - (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE)
> -
> -struct SpiceKbdState {
> - bool push_ext;
> -
> - /* track key press state */
> - bool key[0x7f];
> - bool key_ext[0x7f];
> -};
> -
> -struct SpiceMouseState {
> - int dummy;
> -};
> -
> -struct SpiceTabletState {
> - int dummy;
> -};
> -
> -typedef struct InputsChannelClient {
> - RedChannelClient base;
> - uint16_t motion_count;
> -} InputsChannelClient;
> -
> -typedef struct InputsChannel {
> - RedChannel base;
> - uint8_t recv_buf[RECEIVE_BUF_SIZE];
> - VDAgentMouseState mouse_state;
> - int src_during_migrate;
> -} InputsChannel;
> -
> -enum {
> - PIPE_ITEM_INPUTS_INIT = PIPE_ITEM_TYPE_CHANNEL_BASE,
> - PIPE_ITEM_MOUSE_MOTION_ACK,
> - PIPE_ITEM_KEY_MODIFIERS,
> - PIPE_ITEM_MIGRATE_DATA,
> -};
> -
> -typedef struct InputsPipeItem {
> - PipeItem base;
> -} InputsPipeItem;
> -
> -typedef struct KeyModifiersPipeItem {
> - PipeItem base;
> - uint8_t modifiers;
> -} KeyModifiersPipeItem;
> -
> -typedef struct InputsInitPipeItem {
> - PipeItem base;
> - uint8_t modifiers;
> -} InputsInitPipeItem;
> -
> -static SpiceKbdInstance *keyboard = NULL;
> -static SpiceMouseInstance *mouse = NULL;
> -static SpiceTabletInstance *tablet = NULL;
> -
> -static SpiceTimer *key_modifiers_timer;
> -
> -static InputsChannel *g_inputs_channel = NULL;
> -
> -#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/
> -
> -#define SCROLL_LOCK_SCAN_CODE 0x46
> -#define NUM_LOCK_SCAN_CODE 0x45
> -#define CAPS_LOCK_SCAN_CODE 0x3a
> -
> -int inputs_inited(void)
> -{
> - return !!g_inputs_channel;
> -}
> -
> -int inputs_set_keyboard(SpiceKbdInstance *_keyboard)
> -{
> - if (keyboard) {
> - spice_printerr("already have keyboard");
> - return -1;
> - }
> - keyboard = _keyboard;
> - keyboard->st = spice_new0(SpiceKbdState, 1);
> - return 0;
> -}
> -
> -int inputs_set_mouse(SpiceMouseInstance *_mouse)
> -{
> - if (mouse) {
> - spice_printerr("already have mouse");
> - return -1;
> - }
> - mouse = _mouse;
> - mouse->st = spice_new0(SpiceMouseState, 1);
> - return 0;
> -}
> -
> -int inputs_set_tablet(SpiceTabletInstance *_tablet)
> -{
> - if (tablet) {
> - spice_printerr("already have tablet");
> - return -1;
> - }
> - tablet = _tablet;
> - tablet->st = spice_new0(SpiceTabletState, 1);
> - return 0;
> -}
> -
> -int inputs_has_tablet(void)
> -{
> - return !!tablet;
> -}
> -
> -void inputs_detach_tablet(SpiceTabletInstance *_tablet)
> -{
> - spice_printerr("");
> - tablet = NULL;
> -}
> -
> -void inputs_set_tablet_logical_size(int x_res, int y_res)
> -{
> - SpiceTabletInterface *sif;
> -
> - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> - sif->set_logical_size(tablet, x_res, y_res);
> -}
> -
> -const VDAgentMouseState *inputs_get_mouse_state(void)
> -{
> - spice_assert(g_inputs_channel);
> - return &g_inputs_channel->mouse_state;
> -}
> -
> -static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> - uint16_t type,
> - uint32_t size)
> -{
> - InputsChannel *inputs_channel = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base);
> -
> - if (size > RECEIVE_BUF_SIZE) {
> - spice_printerr("error: too large incoming message");
> - return NULL;
> - }
> - return inputs_channel->recv_buf;
> -}
> -
> -static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> - uint16_t type,
> - uint32_t size,
> - uint8_t *msg)
> -{
> -}
> -
> -#define OUTGOING_OK 0
> -#define OUTGOING_FAILED -1
> -#define OUTGOING_BLOCKED 1
> -
> -#define RED_MOUSE_STATE_TO_LOCAL(state) \
> - ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \
> - ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) << 1) | \
> - ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1))
> -
> -#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \
> - (((state & SPICE_MOUSE_BUTTON_MASK_LEFT) ? VD_AGENT_LBUTTON_MASK : 0) | \
> - ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) ? VD_AGENT_MBUTTON_MASK : 0) | \
> - ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) ? VD_AGENT_RBUTTON_MASK : 0))
> -
> -static void activate_modifiers_watch(void)
> -{
> - core->timer_start(key_modifiers_timer, KEY_MODIFIERS_TTL);
> -}
> -
> -static void kbd_push_scan(SpiceKbdInstance *sin, uint8_t scan)
> -{
> - SpiceKbdInterface *sif;
> -
> - if (!sin) {
> - return;
> - }
> - sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base);
> -
> - /* track XT scan code set 1 key state */
> - if (scan == 0xe0) {
> - sin->st->push_ext = TRUE;
> - } else {
> - bool *state = sin->st->push_ext ? sin->st->key : sin->st->key_ext;
> - sin->st->push_ext = FALSE;
> - state[scan & 0x7f] = !(scan & 0x80);
> - }
> -
> - sif->push_scan_freg(sin, scan);
> -}
> -
> -static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
> -{
> - SpiceKbdInterface *sif;
> -
> - if (!sin) {
> - return 0;
> - }
> - sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base);
> - return sif->get_leds(sin);
> -}
> -
> -static PipeItem *inputs_key_modifiers_item_new(
> - RedChannelClient *rcc, void *data, int num)
> -{
> - KeyModifiersPipeItem *item = spice_malloc(sizeof(KeyModifiersPipeItem));
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_KEY_MODIFIERS);
> - item->modifiers = *(uint8_t *)data;
> - return &item->base;
> -}
> -
> -static void inputs_channel_send_migrate_data(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - PipeItem *item)
> -{
> - InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base);
> -
> - g_inputs_channel->src_during_migrate = FALSE;
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item);
> -
> - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_MAGIC);
> - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_VERSION);
> - spice_marshaller_add_uint16(m, icc->motion_count);
> -}
> -
> -static void inputs_channel_release_pipe_item(RedChannelClient *rcc,
> - PipeItem *base, int item_pushed)
> -{
> - free(base);
> -}
> -
> -static void inputs_channel_send_item(RedChannelClient *rcc, PipeItem *base)
> -{
> - SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
> -
> - switch (base->type) {
> - case PIPE_ITEM_KEY_MODIFIERS:
> - {
> - SpiceMsgInputsKeyModifiers key_modifiers;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_KEY_MODIFIERS, base);
> - key_modifiers.modifiers =
> - SPICE_CONTAINEROF(base, KeyModifiersPipeItem, base)->modifiers;
> - spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers);
> - break;
> - }
> - case PIPE_ITEM_INPUTS_INIT:
> - {
> - SpiceMsgInputsInit inputs_init;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_INIT, base);
> - inputs_init.keyboard_modifiers =
> - SPICE_CONTAINEROF(base, InputsInitPipeItem, base)->modifiers;
> - spice_marshall_msg_inputs_init(m, &inputs_init);
> - break;
> - }
> - case PIPE_ITEM_MOUSE_MOTION_ACK:
> - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK, base);
> - break;
> - case PIPE_ITEM_MIGRATE_DATA:
> - inputs_channel_send_migrate_data(rcc, m, base);
> - break;
> - default:
> - spice_warning("invalid pipe iten %d", base->type);
> - break;
> - }
> - red_channel_client_begin_send_message(rcc);
> -}
> -
> -static int inputs_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type,
> - void *message)
> -{
> - InputsChannel *inputs_channel = (InputsChannel *)rcc->channel;
> - InputsChannelClient *icc = (InputsChannelClient *)rcc;
> - uint32_t i;
> -
> - spice_assert(g_inputs_channel == inputs_channel);
> - switch (type) {
> - case SPICE_MSGC_INPUTS_KEY_DOWN: {
> - SpiceMsgcKeyDown *key_down = message;
> - if (key_down->code == CAPS_LOCK_SCAN_CODE ||
> - key_down->code == NUM_LOCK_SCAN_CODE ||
> - key_down->code == SCROLL_LOCK_SCAN_CODE) {
> - activate_modifiers_watch();
> - }
> - }
> - case SPICE_MSGC_INPUTS_KEY_UP: {
> - SpiceMsgcKeyUp *key_up = message;
> - for (i = 0; i < 4; i++) {
> - uint8_t code = (key_up->code >> (i * 8)) & 0xff;
> - if (code == 0) {
> - break;
> - }
> - kbd_push_scan(keyboard, code);
> - }
> - break;
> - }
> - case SPICE_MSGC_INPUTS_KEY_SCANCODE: {
> - uint8_t *code = message;
> - for (i = 0; i < size; i++) {
> - kbd_push_scan(keyboard, code[i]);
> - }
> - break;
> - }
> - case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
> - SpiceMsgcMouseMotion *mouse_motion = message;
> -
> - if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 &&
> - !g_inputs_channel->src_during_migrate) {
> - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> - icc->motion_count = 0;
> - }
> - if (mouse && reds_get_mouse_mode() == SPICE_MOUSE_MODE_SERVER) {
> - SpiceMouseInterface *sif;
> - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> - sif->motion(mouse,
> - mouse_motion->dx, mouse_motion->dy, 0,
> - RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state));
> - }
> - break;
> - }
> - case SPICE_MSGC_INPUTS_MOUSE_POSITION: {
> - SpiceMsgcMousePosition *pos = message;
> -
> - if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 &&
> - !g_inputs_channel->src_during_migrate) {
> - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> - icc->motion_count = 0;
> - }
> - if (reds_get_mouse_mode() != SPICE_MOUSE_MODE_CLIENT) {
> - break;
> - }
> - spice_assert((reds_get_agent_mouse() && reds_has_vdagent()) || tablet);
> - if (!reds_get_agent_mouse() || !reds_has_vdagent()) {
> - SpiceTabletInterface *sif;
> - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> - sif->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state));
> - break;
> - }
> - VDAgentMouseState *mouse_state = &inputs_channel->mouse_state;
> - mouse_state->x = pos->x;
> - mouse_state->y = pos->y;
> - mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state);
> - mouse_state->display_id = pos->display_id;
> - reds_handle_agent_mouse_event(mouse_state);
> - break;
> - }
> - case SPICE_MSGC_INPUTS_MOUSE_PRESS: {
> - SpiceMsgcMousePress *mouse_press = message;
> - int dz = 0;
> - if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) {
> - dz = -1;
> - } else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) {
> - dz = 1;
> - }
> - if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) {
> - if (reds_get_agent_mouse() && reds_has_vdagent()) {
> - inputs_channel->mouse_state.buttons =
> - RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) |
> - (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) |
> - (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0);
> - reds_handle_agent_mouse_event(&inputs_channel->mouse_state);
> - } else if (tablet) {
> - SpiceTabletInterface *sif;
> - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> - sif->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
> - }
> - } else if (mouse) {
> - SpiceMouseInterface *sif;
> - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> - sif->motion(mouse, 0, 0, dz,
> - RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
> - }
> - break;
> - }
> - case SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
> - SpiceMsgcMouseRelease *mouse_release = message;
> - if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) {
> - if (reds_get_agent_mouse() && reds_has_vdagent()) {
> - inputs_channel->mouse_state.buttons =
> - RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
> - reds_handle_agent_mouse_event(&inputs_channel->mouse_state);
> - } else if (tablet) {
> - SpiceTabletInterface *sif;
> - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
> - sif->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
> - }
> - } else if (mouse) {
> - SpiceMouseInterface *sif;
> - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
> - sif->buttons(mouse,
> - RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
> - }
> - break;
> - }
> - case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
> - SpiceMsgcKeyModifiers *modifiers = message;
> - uint8_t leds;
> -
> - if (!keyboard) {
> - break;
> - }
> - leds = kbd_get_leds(keyboard);
> - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) !=
> - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK)) {
> - kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE);
> - kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE | 0x80);
> - }
> - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) !=
> - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK)) {
> - kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE);
> - kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE | 0x80);
> - }
> - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) !=
> - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK)) {
> - kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE);
> - kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE | 0x80);
> - }
> - activate_modifiers_watch();
> - break;
> - }
> - case SPICE_MSGC_DISCONNECTING:
> - break;
> - default:
> - return red_channel_client_handle_message(rcc, size, type, message);
> - }
> - return TRUE;
> -}
> -
> -static void inputs_release_keys(void)
> -{
> - int i;
> - SpiceKbdState *st;
> -
> - if (!keyboard) {
> - return;
> - }
> - st = keyboard->st;
> -
> - for (i = 0; i < SPICE_N_ELEMENTS(st->key); i++) {
> - if (!st->key[i])
> - continue;
> -
> - st->key[i] = FALSE;
> - kbd_push_scan(keyboard, i | 0x80);
> - }
> -
> - for (i = 0; i < SPICE_N_ELEMENTS(st->key_ext); i++) {
> - if (!st->key_ext[i])
> - continue;
> -
> - st->key_ext[i] = FALSE;
> - kbd_push_scan(keyboard, 0xe0);
> - kbd_push_scan(keyboard, i | 0x80);
> - }
> -}
> -
> -static void inputs_channel_on_disconnect(RedChannelClient *rcc)
> -{
> - if (!rcc) {
> - return;
> - }
> - inputs_release_keys();
> -}
> -
> -static void inputs_pipe_add_init(RedChannelClient *rcc)
> -{
> - InputsInitPipeItem *item = spice_malloc(sizeof(InputsInitPipeItem));
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_INPUTS_INIT);
> - item->modifiers = kbd_get_leds(keyboard);
> - red_channel_client_pipe_add_push(rcc, &item->base);
> -}
> -
> -static int inputs_channel_config_socket(RedChannelClient *rcc)
> -{
> - int delay_val = 1;
> - RedsStream *stream = red_channel_client_get_stream(rcc);
> -
> - if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY,
> - &delay_val, sizeof(delay_val)) == -1) {
> - if (errno != ENOTSUP && errno != ENOPROTOOPT) {
> - spice_printerr("setsockopt failed, %s", strerror(errno));
> - return FALSE;
> - }
> - }
> -
> - return TRUE;
> -}
> -
> -static void inputs_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
> -{
> -}
> -
> -static void inputs_connect(RedChannel *channel, RedClient *client,
> - RedsStream *stream, int migration,
> - int num_common_caps, uint32_t *common_caps,
> - int num_caps, uint32_t *caps)
> -{
> - InputsChannelClient *icc;
> -
> - spice_assert(g_inputs_channel);
> - spice_assert(channel == &g_inputs_channel->base);
> -
> - if (!reds_stream_is_ssl(stream) && !red_client_during_migrate_at_target(client)) {
> - main_channel_client_push_notify(red_client_get_main(client),
> - "keyboard channel is insecure");
> - }
> -
> - spice_printerr("inputs channel client create");
> - icc = (InputsChannelClient*)red_channel_client_create(sizeof(InputsChannelClient),
> - channel,
> - client,
> - stream,
> - FALSE,
> - num_common_caps, common_caps,
> - num_caps, caps);
> - if (!icc) {
> - return;
> - }
> - icc->motion_count = 0;
> - inputs_pipe_add_init(&icc->base);
> -}
> -
> -static void inputs_migrate(RedChannelClient *rcc)
> -{
> - g_inputs_channel->src_during_migrate = TRUE;
> - red_channel_client_default_migrate(rcc);
> -}
> -
> -static void inputs_push_keyboard_modifiers(uint8_t modifiers)
> -{
> - if (!g_inputs_channel || !red_channel_is_connected(&g_inputs_channel->base) ||
> - g_inputs_channel->src_during_migrate) {
> - return;
> - }
> - red_channel_pipes_new_add_push(&g_inputs_channel->base,
> - inputs_key_modifiers_item_new, (void*)&modifiers);
> -}
> -
> -void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds)
> -{
> - inputs_push_keyboard_modifiers(leds);
> -}
> -
> -static void key_modifiers_sender(void *opaque)
> -{
> - inputs_push_keyboard_modifiers(kbd_get_leds(keyboard));
> -}
> -
> -static int inputs_channel_handle_migrate_flush_mark(RedChannelClient *rcc)
> -{
> - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MIGRATE_DATA);
> - return TRUE;
> -}
> -
> -static int inputs_channel_handle_migrate_data(RedChannelClient *rcc,
> - uint32_t size,
> - void *message)
> -{
> - InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base);
> - SpiceMigrateDataHeader *header;
> - SpiceMigrateDataInputs *mig_data;
> -
> - header = (SpiceMigrateDataHeader *)message;
> - mig_data = (SpiceMigrateDataInputs *)(header + 1);
> -
> - if (!migration_protocol_validate_header(header,
> - SPICE_MIGRATE_DATA_INPUTS_MAGIC,
> - SPICE_MIGRATE_DATA_INPUTS_VERSION)) {
> - spice_error("bad header");
> - return FALSE;
> - }
> - key_modifiers_sender(NULL);
> - icc->motion_count = mig_data->motion_count;
> -
> - for (; icc->motion_count >= SPICE_INPUT_MOTION_ACK_BUNCH;
> - icc->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH) {
> - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK);
> - }
> - return TRUE;
> -}
> -
> -void inputs_init(void)
> -{
> - ChannelCbs channel_cbs = { NULL, };
> - ClientCbs client_cbs = { NULL, };
> -
> - spice_assert(!g_inputs_channel);
> -
> - channel_cbs.config_socket = inputs_channel_config_socket;
> - channel_cbs.on_disconnect = inputs_channel_on_disconnect;
> - channel_cbs.send_item = inputs_channel_send_item;
> - channel_cbs.hold_item = inputs_channel_hold_pipe_item;
> - channel_cbs.release_item = inputs_channel_release_pipe_item;
> - channel_cbs.alloc_recv_buf = inputs_channel_alloc_msg_rcv_buf;
> - channel_cbs.release_recv_buf = inputs_channel_release_msg_rcv_buf;
> - channel_cbs.handle_migrate_data = inputs_channel_handle_migrate_data;
> - channel_cbs.handle_migrate_flush_mark = inputs_channel_handle_migrate_flush_mark;
> -
> - g_inputs_channel = (InputsChannel *)red_channel_create_parser(
> - sizeof(InputsChannel),
> - core,
> - SPICE_CHANNEL_INPUTS, 0,
> - FALSE, /* handle_acks */
> - spice_get_client_channel_parser(SPICE_CHANNEL_INPUTS, NULL),
> - inputs_channel_handle_parsed,
> - &channel_cbs,
> - SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER);
> -
> - if (!g_inputs_channel) {
> - spice_error("failed to allocate Inputs Channel");
> - }
> -
> - client_cbs.connect = inputs_connect;
> - client_cbs.migrate = inputs_migrate;
> - red_channel_register_client_cbs(&g_inputs_channel->base, &client_cbs);
> -
> - red_channel_set_cap(&g_inputs_channel->base, SPICE_INPUTS_CAP_KEY_SCANCODE);
> - reds_register_channel(&g_inputs_channel->base);
> -
> - if (!(key_modifiers_timer = core->timer_add(key_modifiers_sender, NULL))) {
> - spice_error("key modifiers timer create failed");
> - }
> -}
> diff --git a/server/inputs_channel.h b/server/inputs_channel.h
> deleted file mode 100644
> index 672ca83..0000000
> --- a/server/inputs_channel.h
> +++ /dev/null
> @@ -1,38 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _INPUTS_CHANNEL_H_
> -#define _INPUTS_CHANNEL_H_
> -
> -// Inputs channel, dealing with keyboard, mouse, tablet.
> -// This include should only be used by reds.c and inputs_channel.c
> -
> -#include <stdint.h>
> -#include <spice/vd_agent.h>
> -
> -void inputs_init(void);
> -int inputs_inited(void);
> -int inputs_has_tablet(void);
> -const VDAgentMouseState *inputs_get_mouse_state(void);
> -void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds);
> -int inputs_set_keyboard(SpiceKbdInstance *_keyboard);
> -int inputs_set_mouse(SpiceMouseInstance *_mouse);
> -int inputs_set_tablet(SpiceTabletInstance *_tablet);
> -void inputs_detach_tablet(SpiceTabletInstance *_tablet);
> -void inputs_set_tablet_logical_size(int x_res, int y_res);
> -
> -#endif
> diff --git a/server/jpeg-encoder.c b/server/jpeg-encoder.c
> new file mode 100644
> index 0000000..8e54dd2
> --- /dev/null
> +++ b/server/jpeg-encoder.c
> @@ -0,0 +1,248 @@
> +/*
> + Copyright (C) 2009 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 "red_common.h"
> +#include "jpeg-encoder.h"
> +#include <jpeglib.h>
> +
> +typedef struct JpegEncoder {
> + JpegEncoderUsrContext *usr;
> +
> + struct jpeg_destination_mgr dest_mgr;
> + struct jpeg_compress_struct cinfo;
> + struct jpeg_error_mgr jerr;
> +
> + struct {
> + JpegEncoderImageType type;
> + int width;
> + int height;
> + int stride;
> + unsigned int out_size;
> + void (*convert_line_to_RGB24) (void *line, int width, uint8_t **out_line);
> + } cur_image;
> +} JpegEncoder;
> +
> +/* jpeg destination manager callbacks */
> +
> +static void dest_mgr_init_destination(j_compress_ptr cinfo)
> +{
> + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> + if (enc->dest_mgr.free_in_buffer == 0) {
> + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
> + &enc->dest_mgr.next_output_byte);
> +
> + if (enc->dest_mgr.free_in_buffer == 0) {
> + spice_error("not enough space");
> + }
> + }
> +
> + enc->cur_image.out_size = enc->dest_mgr.free_in_buffer;
> +}
> +
> +static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo)
> +{
> + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
> + &enc->dest_mgr.next_output_byte);
> +
> + if (enc->dest_mgr.free_in_buffer == 0) {
> + spice_error("not enough space");
> + }
> + enc->cur_image.out_size += enc->dest_mgr.free_in_buffer;
> + return TRUE;
> +}
> +
> +static void dest_mgr_term_destination(j_compress_ptr cinfo)
> +{
> + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> + enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer;
> +}
> +
> +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr)
> +{
> + JpegEncoder *enc;
> + if (!usr->more_space || !usr->more_lines) {
> + return NULL;
> + }
> +
> + enc = spice_new0(JpegEncoder, 1);
> +
> + enc->usr = usr;
> +
> + enc->dest_mgr.init_destination = dest_mgr_init_destination;
> + enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer;
> + enc->dest_mgr.term_destination = dest_mgr_term_destination;
> +
> + enc->cinfo.err = jpeg_std_error(&enc->jerr);
> +
> + jpeg_create_compress(&enc->cinfo);
> + enc->cinfo.client_data = enc;
> + enc->cinfo.dest = &enc->dest_mgr;
> + return (JpegEncoderContext*)enc;
> +}
> +
> +void jpeg_encoder_destroy(JpegEncoderContext* encoder)
> +{
> + jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo);
> + free(encoder);
> +}
> +
> +static void convert_RGB16_to_RGB24(void *line, int width, uint8_t **out_line)
> +{
> + uint16_t *src_line = line;
> + uint8_t *out_pix;
> + int x;
> +
> + spice_assert(out_line && *out_line);
> +
> + out_pix = *out_line;
> +
> + for (x = 0; x < width; x++) {
> + uint16_t pixel = *src_line++;
> + *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
> + *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
> + *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
> + }
> +}
> +
> +static void convert_BGR24_to_RGB24(void *in_line, int width, uint8_t **out_line)
> +{
> + int x;
> + uint8_t *out_pix;
> + uint8_t *line = in_line;
> + spice_assert(out_line && *out_line);
> +
> + out_pix = *out_line;
> +
> + for (x = 0; x < width; x++) {
> + *out_pix++ = line[2];
> + *out_pix++ = line[1];
> + *out_pix++ = line[0];
> + line += 3;
> + }
> +}
> +
> +static void convert_BGRX32_to_RGB24(void *line, int width, uint8_t **out_line)
> +{
> + uint32_t *src_line = line;
> + uint8_t *out_pix;
> + int x;
> +
> + spice_assert(out_line && *out_line);
> +
> + out_pix = *out_line;
> +
> + for (x = 0; x < width; x++) {
> + uint32_t pixel = *src_line++;
> + *out_pix++ = (pixel >> 16) & 0xff;
> + *out_pix++ = (pixel >> 8) & 0xff;
> + *out_pix++ = pixel & 0xff;
> + }
> +}
> +
> +static void convert_RGB24_to_RGB24(void *line, int width, uint8_t **out_line)
> +{
> + *out_line = line;
> +}
> +
> +
> +#define FILL_LINES() { \
> + if (lines == lines_end) { \
> + int n = jpeg->usr->more_lines(jpeg->usr, &lines); \
> + if (n <= 0) { \
> + spice_error("more lines failed"); \
> + } \
> + lines_end = lines + n * stride; \
> + } \
> +}
> +
> +static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines)
> +{
> + uint8_t *lines_end;
> + uint8_t *RGB24_line;
> + int stride, width;
> + JSAMPROW row_pointer[1];
> + width = jpeg->cur_image.width;
> + stride = jpeg->cur_image.stride;
> +
> + if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
> + RGB24_line = (uint8_t *)spice_malloc(width*3);
> + }
> +
> + lines_end = lines + (stride * num_lines);
> +
> + for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) {
> + FILL_LINES();
> + jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line);
> + row_pointer[0] = RGB24_line;
> + jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1);
> + }
> +
> + if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
> + free(RGB24_line);
> + }
> +}
> +
> +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
> + int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
> + uint8_t *io_ptr, unsigned int num_io_bytes)
> +{
> + JpegEncoder *enc = (JpegEncoder *)jpeg;
> +
> + enc->cur_image.type = type;
> + enc->cur_image.width = width;
> + enc->cur_image.height = height;
> + enc->cur_image.stride = stride;
> + enc->cur_image.out_size = 0;
> +
> + switch (type) {
> + case JPEG_IMAGE_TYPE_RGB16:
> + enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24;
> + break;
> + case JPEG_IMAGE_TYPE_RGB24:
> + enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24;
> + break;
> + case JPEG_IMAGE_TYPE_BGR24:
> + enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24;
> + break;
> + case JPEG_IMAGE_TYPE_BGRX32:
> + enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24;
> + break;
> + default:
> + spice_error("bad image type");
> + }
> +
> + enc->cinfo.image_width = width;
> + enc->cinfo.image_height = height;
> + enc->cinfo.input_components = 3;
> + enc->cinfo.in_color_space = JCS_RGB;
> + jpeg_set_defaults(&enc->cinfo);
> + jpeg_set_quality(&enc->cinfo, quality, TRUE);
> +
> + enc->dest_mgr.next_output_byte = io_ptr;
> + enc->dest_mgr.free_in_buffer = num_io_bytes;
> +
> + jpeg_start_compress(&enc->cinfo, TRUE);
> +
> + do_jpeg_encode(enc, lines, num_lines);
> +
> + jpeg_finish_compress(&enc->cinfo);
> + return enc->cur_image.out_size;
> +}
> diff --git a/server/jpeg-encoder.h b/server/jpeg-encoder.h
> new file mode 100644
> index 0000000..690a029
> --- /dev/null
> +++ b/server/jpeg-encoder.h
> @@ -0,0 +1,61 @@
> +/*
> + Copyright (C) 2009 Red Hat, Inc.
> +
> + Redistribution and use in source and binary forms, with or without
> + modification, are permitted provided that the following conditions are
> + met:
> +
> + * Redistributions of source code must retain the above copyright
> + notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + notice, this list of conditions and the following disclaimer in
> + the documentation and/or other materials provided with the
> + distribution.
> + * Neither the name of the copyright holder nor the names of its
> + contributors may be used to endorse or promote products derived
> + from this software without specific prior written permission.
> +
> + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
> + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
> + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> +*/
> +#ifndef _H_JPEG_ENCODER
> +#define _H_JPEG_ENCODER
> +
> +#include <spice/types.h>
> +
> +typedef enum {
> + JPEG_IMAGE_TYPE_INVALID,
> + JPEG_IMAGE_TYPE_RGB16,
> + /* in byte per color types, the notation is according to the order of the
> + colors in the memory */
> + JPEG_IMAGE_TYPE_RGB24,
> + JPEG_IMAGE_TYPE_BGR24,
> + JPEG_IMAGE_TYPE_BGRX32,
> +} JpegEncoderImageType;
> +
> +typedef void* JpegEncoderContext;
> +typedef struct JpegEncoderUsrContext JpegEncoderUsrContext;
> +
> +struct JpegEncoderUsrContext {
> + int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr);
> + int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines);
> +};
> +
> +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr);
> +void jpeg_encoder_destroy(JpegEncoderContext *encoder);
> +
> +/* returns the total size of the encoded data. Images must be supplied from the
> + top line to the bottom */
> +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
> + int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
> + uint8_t *io_ptr, unsigned int num_io_bytes);
> +#endif
> diff --git a/server/jpeg_encoder.c b/server/jpeg_encoder.c
> deleted file mode 100644
> index 0296e9b..0000000
> --- a/server/jpeg_encoder.c
> +++ /dev/null
> @@ -1,248 +0,0 @@
> -/*
> - Copyright (C) 2009 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 "red_common.h"
> -#include "jpeg_encoder.h"
> -#include <jpeglib.h>
> -
> -typedef struct JpegEncoder {
> - JpegEncoderUsrContext *usr;
> -
> - struct jpeg_destination_mgr dest_mgr;
> - struct jpeg_compress_struct cinfo;
> - struct jpeg_error_mgr jerr;
> -
> - struct {
> - JpegEncoderImageType type;
> - int width;
> - int height;
> - int stride;
> - unsigned int out_size;
> - void (*convert_line_to_RGB24) (void *line, int width, uint8_t **out_line);
> - } cur_image;
> -} JpegEncoder;
> -
> -/* jpeg destination manager callbacks */
> -
> -static void dest_mgr_init_destination(j_compress_ptr cinfo)
> -{
> - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> - if (enc->dest_mgr.free_in_buffer == 0) {
> - enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
> - &enc->dest_mgr.next_output_byte);
> -
> - if (enc->dest_mgr.free_in_buffer == 0) {
> - spice_error("not enough space");
> - }
> - }
> -
> - enc->cur_image.out_size = enc->dest_mgr.free_in_buffer;
> -}
> -
> -static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo)
> -{
> - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> - enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
> - &enc->dest_mgr.next_output_byte);
> -
> - if (enc->dest_mgr.free_in_buffer == 0) {
> - spice_error("not enough space");
> - }
> - enc->cur_image.out_size += enc->dest_mgr.free_in_buffer;
> - return TRUE;
> -}
> -
> -static void dest_mgr_term_destination(j_compress_ptr cinfo)
> -{
> - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
> - enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer;
> -}
> -
> -JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr)
> -{
> - JpegEncoder *enc;
> - if (!usr->more_space || !usr->more_lines) {
> - return NULL;
> - }
> -
> - enc = spice_new0(JpegEncoder, 1);
> -
> - enc->usr = usr;
> -
> - enc->dest_mgr.init_destination = dest_mgr_init_destination;
> - enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer;
> - enc->dest_mgr.term_destination = dest_mgr_term_destination;
> -
> - enc->cinfo.err = jpeg_std_error(&enc->jerr);
> -
> - jpeg_create_compress(&enc->cinfo);
> - enc->cinfo.client_data = enc;
> - enc->cinfo.dest = &enc->dest_mgr;
> - return (JpegEncoderContext*)enc;
> -}
> -
> -void jpeg_encoder_destroy(JpegEncoderContext* encoder)
> -{
> - jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo);
> - free(encoder);
> -}
> -
> -static void convert_RGB16_to_RGB24(void *line, int width, uint8_t **out_line)
> -{
> - uint16_t *src_line = line;
> - uint8_t *out_pix;
> - int x;
> -
> - spice_assert(out_line && *out_line);
> -
> - out_pix = *out_line;
> -
> - for (x = 0; x < width; x++) {
> - uint16_t pixel = *src_line++;
> - *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
> - *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
> - *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
> - }
> -}
> -
> -static void convert_BGR24_to_RGB24(void *in_line, int width, uint8_t **out_line)
> -{
> - int x;
> - uint8_t *out_pix;
> - uint8_t *line = in_line;
> - spice_assert(out_line && *out_line);
> -
> - out_pix = *out_line;
> -
> - for (x = 0; x < width; x++) {
> - *out_pix++ = line[2];
> - *out_pix++ = line[1];
> - *out_pix++ = line[0];
> - line += 3;
> - }
> -}
> -
> -static void convert_BGRX32_to_RGB24(void *line, int width, uint8_t **out_line)
> -{
> - uint32_t *src_line = line;
> - uint8_t *out_pix;
> - int x;
> -
> - spice_assert(out_line && *out_line);
> -
> - out_pix = *out_line;
> -
> - for (x = 0; x < width; x++) {
> - uint32_t pixel = *src_line++;
> - *out_pix++ = (pixel >> 16) & 0xff;
> - *out_pix++ = (pixel >> 8) & 0xff;
> - *out_pix++ = pixel & 0xff;
> - }
> -}
> -
> -static void convert_RGB24_to_RGB24(void *line, int width, uint8_t **out_line)
> -{
> - *out_line = line;
> -}
> -
> -
> -#define FILL_LINES() { \
> - if (lines == lines_end) { \
> - int n = jpeg->usr->more_lines(jpeg->usr, &lines); \
> - if (n <= 0) { \
> - spice_error("more lines failed"); \
> - } \
> - lines_end = lines + n * stride; \
> - } \
> -}
> -
> -static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines)
> -{
> - uint8_t *lines_end;
> - uint8_t *RGB24_line;
> - int stride, width;
> - JSAMPROW row_pointer[1];
> - width = jpeg->cur_image.width;
> - stride = jpeg->cur_image.stride;
> -
> - if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
> - RGB24_line = (uint8_t *)spice_malloc(width*3);
> - }
> -
> - lines_end = lines + (stride * num_lines);
> -
> - for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) {
> - FILL_LINES();
> - jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line);
> - row_pointer[0] = RGB24_line;
> - jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1);
> - }
> -
> - if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
> - free(RGB24_line);
> - }
> -}
> -
> -int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
> - int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
> - uint8_t *io_ptr, unsigned int num_io_bytes)
> -{
> - JpegEncoder *enc = (JpegEncoder *)jpeg;
> -
> - enc->cur_image.type = type;
> - enc->cur_image.width = width;
> - enc->cur_image.height = height;
> - enc->cur_image.stride = stride;
> - enc->cur_image.out_size = 0;
> -
> - switch (type) {
> - case JPEG_IMAGE_TYPE_RGB16:
> - enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24;
> - break;
> - case JPEG_IMAGE_TYPE_RGB24:
> - enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24;
> - break;
> - case JPEG_IMAGE_TYPE_BGR24:
> - enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24;
> - break;
> - case JPEG_IMAGE_TYPE_BGRX32:
> - enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24;
> - break;
> - default:
> - spice_error("bad image type");
> - }
> -
> - enc->cinfo.image_width = width;
> - enc->cinfo.image_height = height;
> - enc->cinfo.input_components = 3;
> - enc->cinfo.in_color_space = JCS_RGB;
> - jpeg_set_defaults(&enc->cinfo);
> - jpeg_set_quality(&enc->cinfo, quality, TRUE);
> -
> - enc->dest_mgr.next_output_byte = io_ptr;
> - enc->dest_mgr.free_in_buffer = num_io_bytes;
> -
> - jpeg_start_compress(&enc->cinfo, TRUE);
> -
> - do_jpeg_encode(enc, lines, num_lines);
> -
> - jpeg_finish_compress(&enc->cinfo);
> - return enc->cur_image.out_size;
> -}
> diff --git a/server/jpeg_encoder.h b/server/jpeg_encoder.h
> deleted file mode 100644
> index 690a029..0000000
> --- a/server/jpeg_encoder.h
> +++ /dev/null
> @@ -1,61 +0,0 @@
> -/*
> - Copyright (C) 2009 Red Hat, Inc.
> -
> - Redistribution and use in source and binary forms, with or without
> - modification, are permitted provided that the following conditions are
> - met:
> -
> - * Redistributions of source code must retain the above copyright
> - notice, this list of conditions and the following disclaimer.
> - * Redistributions in binary form must reproduce the above copyright
> - notice, this list of conditions and the following disclaimer in
> - the documentation and/or other materials provided with the
> - distribution.
> - * Neither the name of the copyright holder nor the names of its
> - contributors may be used to endorse or promote products derived
> - from this software without specific prior written permission.
> -
> - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
> - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
> - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> -*/
> -#ifndef _H_JPEG_ENCODER
> -#define _H_JPEG_ENCODER
> -
> -#include <spice/types.h>
> -
> -typedef enum {
> - JPEG_IMAGE_TYPE_INVALID,
> - JPEG_IMAGE_TYPE_RGB16,
> - /* in byte per color types, the notation is according to the order of the
> - colors in the memory */
> - JPEG_IMAGE_TYPE_RGB24,
> - JPEG_IMAGE_TYPE_BGR24,
> - JPEG_IMAGE_TYPE_BGRX32,
> -} JpegEncoderImageType;
> -
> -typedef void* JpegEncoderContext;
> -typedef struct JpegEncoderUsrContext JpegEncoderUsrContext;
> -
> -struct JpegEncoderUsrContext {
> - int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr);
> - int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines);
> -};
> -
> -JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr);
> -void jpeg_encoder_destroy(JpegEncoderContext *encoder);
> -
> -/* returns the total size of the encoded data. Images must be supplied from the
> - top line to the bottom */
> -int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
> - int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
> - uint8_t *io_ptr, unsigned int num_io_bytes);
> -#endif
> diff --git a/server/main-channel.c b/server/main-channel.c
> new file mode 100644
> index 0000000..5ca5bba
> --- /dev/null
> +++ b/server/main-channel.c
> @@ -0,0 +1,1345 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009 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 <inttypes.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <netinet/tcp.h>
> +#include <arpa/inet.h>
> +#include <netdb.h>
> +#include <limits.h>
> +#include <time.h>
> +#include <pthread.h>
> +#include <sys/mman.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <ctype.h>
> +
> +#include "common/generated_server_marshallers.h"
> +#include "common/messages.h"
> +#include "common/ring.h"
> +
> +#include "demarshallers.h"
> +#include "main-channel.h"
> +#include "red_channel.h"
> +#include "red_common.h"
> +#include "reds.h"
> +#include "migration-protocol.h"
> +#include "main-dispatcher.h"
> +#include "utils.h"
> +
> +#define ZERO_BUF_SIZE 4096
> +
> +#define NET_TEST_WARMUP_BYTES 0
> +#define NET_TEST_BYTES (1024 * 250)
> +
> +#define PING_INTERVAL (1000 * 10)
> +
> +#define CLIENT_CONNECTIVITY_TIMEOUT (30*1000) // 30 seconds
> +
> +static uint8_t zero_page[ZERO_BUF_SIZE] = {0};
> +
> +enum {
> + PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST = PIPE_ITEM_TYPE_CHANNEL_BASE,
> + PIPE_ITEM_TYPE_MAIN_PING,
> + PIPE_ITEM_TYPE_MAIN_MOUSE_MODE,
> + PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED,
> + PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN,
> + PIPE_ITEM_TYPE_MAIN_AGENT_DATA,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA,
> + PIPE_ITEM_TYPE_MAIN_INIT,
> + PIPE_ITEM_TYPE_MAIN_NOTIFY,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST,
> + PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME,
> + PIPE_ITEM_TYPE_MAIN_NAME,
> + PIPE_ITEM_TYPE_MAIN_UUID,
> + PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS,
> +};
> +
> +typedef struct RefsPipeItem {
> + PipeItem base;
> + int *refs;
> +} RefsPipeItem;
> +
> +typedef struct PingPipeItem {
> + PipeItem base;
> + int size;
> +} PingPipeItem;
> +
> +typedef struct MouseModePipeItem {
> + PipeItem base;
> + int current_mode;
> + int is_client_mouse_allowed;
> +} MouseModePipeItem;
> +
> +typedef struct TokensPipeItem {
> + PipeItem base;
> + int tokens;
> +} TokensPipeItem;
> +
> +typedef struct AgentDataPipeItem {
> + PipeItem base;
> + uint8_t* data;
> + size_t len;
> + spice_marshaller_item_free_func free_data;
> + void *opaque;
> +} AgentDataPipeItem;
> +
> +typedef struct InitPipeItem {
> + PipeItem base;
> + int connection_id;
> + int display_channels_hint;
> + int current_mouse_mode;
> + int is_client_mouse_allowed;
> + int multi_media_time;
> + int ram_hint;
> +} InitPipeItem;
> +
> +typedef struct NamePipeItem {
> + PipeItem base;
> + SpiceMsgMainName msg;
> +} NamePipeItem;
> +
> +typedef struct UuidPipeItem {
> + PipeItem base;
> + SpiceMsgMainUuid msg;
> +} UuidPipeItem;
> +
> +typedef struct NotifyPipeItem {
> + PipeItem base;
> + char *msg;
> +} NotifyPipeItem;
> +
> +typedef struct MultiMediaTimePipeItem {
> + PipeItem base;
> + int time;
> +} MultiMediaTimePipeItem;
> +
> +struct MainChannelClient {
> + RedChannelClient base;
> + uint32_t connection_id;
> + uint32_t ping_id;
> + uint32_t net_test_id;
> + int net_test_stage;
> + uint64_t latency;
> + uint64_t bitrate_per_sec;
> +#ifdef RED_STATISTICS
> + SpiceTimer *ping_timer;
> + int ping_interval;
> +#endif
> + int mig_wait_connect;
> + int mig_connect_ok;
> + int mig_wait_prev_complete;
> + int mig_wait_prev_try_seamless;
> + int init_sent;
> + int seamless_mig_dst;
> +};
> +
> +enum NetTestStage {
> + NET_TEST_STAGE_INVALID,
> + NET_TEST_STAGE_WARMUP,
> + NET_TEST_STAGE_LATENCY,
> + NET_TEST_STAGE_RATE,
> + NET_TEST_STAGE_COMPLETE,
> +};
> +
> +static void main_channel_release_pipe_item(RedChannelClient *rcc,
> + PipeItem *base, int item_pushed);
> +
> +int main_channel_is_connected(MainChannel *main_chan)
> +{
> + return red_channel_is_connected(&main_chan->base);
> +}
> +
> +/*
> + * When the main channel is disconnected, disconnect the entire client.
> + */
> +static void main_channel_client_on_disconnect(RedChannelClient *rcc)
> +{
> + spice_printerr("rcc=%p", rcc);
> + main_dispatcher_client_disconnect(rcc->client);
> +}
> +
> +RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id)
> +{
> + RingItem *link;
> + MainChannelClient *mcc;
> +
> + RING_FOREACH(link, &main_chan->base.clients) {
> + mcc = SPICE_CONTAINEROF(link, MainChannelClient, base.channel_link);
> + if (mcc->connection_id == connection_id) {
> + return mcc->base.client;
> + }
> + }
> + return NULL;
> +}
> +
> +static int main_channel_client_push_ping(MainChannelClient *mcc, int size);
> +
> +void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate)
> +{
> + if (!mcc || mcc->net_test_id) {
> + return;
> + }
> + if (test_rate) {
> + if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES)
> + && main_channel_client_push_ping(mcc, 0)
> + && main_channel_client_push_ping(mcc, NET_TEST_BYTES)) {
> + mcc->net_test_id = mcc->ping_id - 2;
> + mcc->net_test_stage = NET_TEST_STAGE_WARMUP;
> + }
> + } else {
> + red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT);
> + }
> +}
> +
> +typedef struct MainMouseModeItemInfo {
> + int current_mode;
> + int is_client_mouse_allowed;
> +} MainMouseModeItemInfo;
> +
> +static PipeItem *main_mouse_mode_item_new(RedChannelClient *rcc, void *data, int num)
> +{
> + MouseModePipeItem *item = spice_malloc(sizeof(MouseModePipeItem));
> + MainMouseModeItemInfo *info = data;
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_MOUSE_MODE);
> + item->current_mode = info->current_mode;
> + item->is_client_mouse_allowed = info->is_client_mouse_allowed;
> + return &item->base;
> +}
> +
> +static PipeItem *main_ping_item_new(MainChannelClient *mcc, int size)
> +{
> + PingPipeItem *item = spice_malloc(sizeof(PingPipeItem));
> +
> + red_channel_pipe_item_init(mcc->base.channel, &item->base, PIPE_ITEM_TYPE_MAIN_PING);
> + item->size = size;
> + return &item->base;
> +}
> +
> +static PipeItem *main_agent_tokens_item_new(RedChannelClient *rcc, uint32_t num_tokens)
> +{
> + TokensPipeItem *item = spice_malloc(sizeof(TokensPipeItem));
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN);
> + item->tokens = num_tokens;
> + return &item->base;
> +}
> +
> +static PipeItem *main_agent_data_item_new(RedChannelClient *rcc, uint8_t* data, size_t len,
> + spice_marshaller_item_free_func free_data,
> + void *opaque)
> +{
> + AgentDataPipeItem *item = spice_malloc(sizeof(AgentDataPipeItem));
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_AGENT_DATA);
> + item->data = data;
> + item->len = len;
> + item->free_data = free_data;
> + item->opaque = opaque;
> + return &item->base;
> +}
> +
> +static PipeItem *main_init_item_new(MainChannelClient *mcc,
> + int connection_id, int display_channels_hint, int current_mouse_mode,
> + int is_client_mouse_allowed, int multi_media_time,
> + int ram_hint)
> +{
> + InitPipeItem *item = spice_malloc(sizeof(InitPipeItem));
> +
> + red_channel_pipe_item_init(mcc->base.channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_INIT);
> + item->connection_id = connection_id;
> + item->display_channels_hint = display_channels_hint;
> + item->current_mouse_mode = current_mouse_mode;
> + item->is_client_mouse_allowed = is_client_mouse_allowed;
> + item->multi_media_time = multi_media_time;
> + item->ram_hint = ram_hint;
> + return &item->base;
> +}
> +
> +static PipeItem *main_name_item_new(MainChannelClient *mcc, const char *name)
> +{
> + NamePipeItem *item = spice_malloc(sizeof(NamePipeItem) + strlen(name) + 1);
> +
> + red_channel_pipe_item_init(mcc->base.channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_NAME);
> + item->msg.name_len = strlen(name) + 1;
> + memcpy(&item->msg.name, name, item->msg.name_len);
> +
> + return &item->base;
> +}
> +
> +static PipeItem *main_uuid_item_new(MainChannelClient *mcc, const uint8_t uuid[16])
> +{
> + UuidPipeItem *item = spice_malloc(sizeof(UuidPipeItem));
> +
> + red_channel_pipe_item_init(mcc->base.channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_UUID);
> + memcpy(item->msg.uuid, uuid, sizeof(item->msg.uuid));
> +
> + return &item->base;
> +}
> +
> +static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num)
> +{
> + NotifyPipeItem *item = spice_malloc(sizeof(NotifyPipeItem));
> + const char *msg = data;
> +
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_NOTIFY);
> + item->msg = spice_strdup(msg);
> + return &item->base;
> +}
> +
> +static PipeItem *main_multi_media_time_item_new(
> + RedChannelClient *rcc, void *data, int num)
> +{
> + MultiMediaTimePipeItem *item, *info = data;
> +
> + item = spice_malloc(sizeof(MultiMediaTimePipeItem));
> + red_channel_pipe_item_init(rcc->channel, &item->base,
> + PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME);
> + item->time = info->time;
> + return &item->base;
> +}
> +
> +static void main_channel_push_channels(MainChannelClient *mcc)
> +{
> + if (red_client_during_migrate_at_target(mcc->base.client)) {
> + spice_printerr("warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS"
> + "during migration");
> + return;
> + }
> + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST);
> +}
> +
> +static void main_channel_marshall_channels(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + PipeItem *item)
> +{
> + SpiceMsgChannels* channels_info;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_CHANNELS_LIST, item);
> + channels_info = (SpiceMsgChannels *)spice_malloc(sizeof(SpiceMsgChannels)
> + + reds_num_of_channels() * sizeof(SpiceChannelId));
> + reds_fill_channels(channels_info);
> + spice_marshall_msg_main_channels_list(m, channels_info);
> + free(channels_info);
> +}
> +
> +int main_channel_client_push_ping(MainChannelClient *mcc, int size)
> +{
> + PipeItem *item;
> +
> + if (mcc == NULL) {
> + return FALSE;
> + }
> + item = main_ping_item_new(mcc, size);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> + return TRUE;
> +}
> +
> +static void main_channel_marshall_ping(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + PingPipeItem *item)
> +{
> + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> + struct timespec time_space;
> + SpiceMsgPing ping;
> + int size_left = item->size;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_PING, &item->base);
> + ping.id = ++(mcc->ping_id);
> + clock_gettime(CLOCK_MONOTONIC, &time_space);
> + ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL;
> + spice_marshall_msg_ping(m, &ping);
> +
> + while (size_left > 0) {
> + int now = MIN(ZERO_BUF_SIZE, size_left);
> + size_left -= now;
> + spice_marshaller_add_ref(m, zero_page, now);
> + }
> +}
> +
> +void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode,
> + int is_client_mouse_allowed)
> +{
> + MainMouseModeItemInfo info = {
> + .current_mode=current_mode,
> + .is_client_mouse_allowed=is_client_mouse_allowed,
> + };
> +
> + red_channel_pipes_new_add_push(&main_chan->base,
> + main_mouse_mode_item_new, &info);
> +}
> +
> +static void main_channel_marshall_mouse_mode(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + MouseModePipeItem *item)
> +{
> + SpiceMsgMainMouseMode mouse_mode;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MOUSE_MODE, &item->base);
> + mouse_mode.supported_modes = SPICE_MOUSE_MODE_SERVER;
> + if (item->is_client_mouse_allowed) {
> + mouse_mode.supported_modes |= SPICE_MOUSE_MODE_CLIENT;
> + }
> + mouse_mode.current_mode = item->current_mode;
> + spice_marshall_msg_main_mouse_mode(m, &mouse_mode);
> +}
> +
> +void main_channel_push_agent_connected(MainChannel *main_chan)
> +{
> + if (red_channel_test_remote_cap(&main_chan->base, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) {
> + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS);
> + } else {
> + red_channel_pipes_add_empty_msg(&main_chan->base, SPICE_MSG_MAIN_AGENT_CONNECTED);
> + }
> +}
> +
> +static void main_channel_marshall_agent_connected(SpiceMarshaller *m,
> + RedChannelClient *rcc,
> + PipeItem *item)
> +{
> + SpiceMsgMainAgentConnectedTokens connected;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS, item);
> + connected.num_tokens = REDS_AGENT_WINDOW_SIZE;
> + spice_marshall_msg_main_agent_connected_tokens(m, &connected);
> +}
> +
> +void main_channel_push_agent_disconnected(MainChannel *main_chan)
> +{
> + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED);
> +}
> +
> +static void main_channel_marshall_agent_disconnected(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + PipeItem *item)
> +{
> + SpiceMsgMainAgentDisconnect disconnect;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DISCONNECTED, item);
> + disconnect.error_code = SPICE_LINK_ERR_OK;
> + spice_marshall_msg_main_agent_disconnected(m, &disconnect);
> +}
> +
> +void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens)
> +{
> + PipeItem *item = main_agent_tokens_item_new(&mcc->base, num_tokens);
> +
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +static void main_channel_marshall_tokens(RedChannelClient *rcc,
> + SpiceMarshaller *m, TokensPipeItem *item)
> +{
> + SpiceMsgMainAgentTokens tokens;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_TOKEN, &item->base);
> + tokens.num_tokens = item->tokens;
> + spice_marshall_msg_main_agent_token(m, &tokens);
> +}
> +
> +void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len,
> + spice_marshaller_item_free_func free_data, void *opaque)
> +{
> + PipeItem *item;
> +
> + item = main_agent_data_item_new(&mcc->base, data, len, free_data, opaque);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +static void main_channel_marshall_agent_data(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + AgentDataPipeItem *item)
> +{
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DATA, &item->base);
> + spice_marshaller_add_ref(m, item->data, item->len);
> +}
> +
> +static void main_channel_push_migrate_data_item(MainChannel *main_chan)
> +{
> + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA);
> +}
> +
> +static void main_channel_marshall_migrate_data_item(RedChannelClient *rcc,
> + SpiceMarshaller *m, PipeItem *item)
> +{
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item);
> + reds_marshall_migrate_data(m); // TODO: from reds split. ugly separation.
> +}
> +
> +static int main_channel_handle_migrate_data(RedChannelClient *rcc,
> + uint32_t size, void *message)
> +{
> + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> + SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message;
> +
> + /* not supported with multi-clients */
> + spice_assert(rcc->channel->clients_num == 1);
> +
> + if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) {
> + spice_printerr("bad message size %u", size);
> + return FALSE;
> + }
> + if (!migration_protocol_validate_header(header,
> + SPICE_MIGRATE_DATA_MAIN_MAGIC,
> + SPICE_MIGRATE_DATA_MAIN_VERSION)) {
> + spice_error("bad header");
> + return FALSE;
> + }
> + return reds_handle_migrate_data(mcc, (SpiceMigrateDataMain *)(header + 1), size);
> +}
> +
> +void main_channel_push_init(MainChannelClient *mcc,
> + int display_channels_hint, int current_mouse_mode,
> + int is_client_mouse_allowed, int multi_media_time,
> + int ram_hint)
> +{
> + PipeItem *item;
> +
> + item = main_init_item_new(mcc,
> + mcc->connection_id, display_channels_hint, current_mouse_mode,
> + is_client_mouse_allowed, multi_media_time, ram_hint);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +static void main_channel_marshall_init(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + InitPipeItem *item)
> +{
> + SpiceMsgMainInit init; // TODO - remove this copy, make InitPipeItem reuse SpiceMsgMainInit
> +
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_INIT, &item->base);
> + init.session_id = item->connection_id;
> + init.display_channels_hint = item->display_channels_hint;
> + init.current_mouse_mode = item->current_mouse_mode;
> + init.supported_mouse_modes = SPICE_MOUSE_MODE_SERVER;
> + if (item->is_client_mouse_allowed) {
> + init.supported_mouse_modes |= SPICE_MOUSE_MODE_CLIENT;
> + }
> + init.agent_connected = reds_has_vdagent();
> + init.agent_tokens = REDS_AGENT_WINDOW_SIZE;
> + init.multi_media_time = item->multi_media_time;
> + init.ram_hint = item->ram_hint;
> + spice_marshall_msg_main_init(m, &init);
> +}
> +
> +void main_channel_push_name(MainChannelClient *mcc, const char *name)
> +{
> + PipeItem *item;
> +
> + if (!red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_NAME_AND_UUID))
> + return;
> +
> + item = main_name_item_new(mcc, name);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16])
> +{
> + PipeItem *item;
> +
> + if (!red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_NAME_AND_UUID))
> + return;
> +
> + item = main_uuid_item_new(mcc, uuid);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg)
> +{
> + PipeItem *item = main_notify_item_new(&mcc->base, (void *)msg, 1);
> + red_channel_client_pipe_add_push(&mcc->base, item);
> +}
> +
> +static void main_channel_marshall_notify(RedChannelClient *rcc,
> + SpiceMarshaller *m, NotifyPipeItem *item)
> +{
> + SpiceMsgNotify notify;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_NOTIFY, &item->base);
> + notify.time_stamp = red_get_monotonic_time(); // TODO - move to main_new_notify_item
> + notify.severity = SPICE_NOTIFY_SEVERITY_WARN;
> + notify.visibilty = SPICE_NOTIFY_VISIBILITY_HIGH;
> + notify.what = SPICE_WARN_GENERAL;
> + notify.message_len = strlen(item->msg);
> + spice_marshall_msg_notify(m, ¬ify);
> + spice_marshaller_add(m, (uint8_t *)item->msg, notify.message_len + 1);
> +}
> +
> +static void main_channel_fill_migrate_dst_info(MainChannel *main_channel,
> + SpiceMigrationDstInfo *dst_info)
> +{
> + RedsMigSpice *mig_dst = &main_channel->mig_target;
> + dst_info->port = mig_dst->port;
> + dst_info->sport = mig_dst->sport;
> + dst_info->host_size = strlen(mig_dst->host) + 1;
> + dst_info->host_data = (uint8_t *)mig_dst->host;
> + if (mig_dst->cert_subject) {
> + dst_info->cert_subject_size = strlen(mig_dst->cert_subject) + 1;
> + dst_info->cert_subject_data = (uint8_t *)mig_dst->cert_subject;
> + } else {
> + dst_info->cert_subject_size = 0;
> + dst_info->cert_subject_data = NULL;
> + }
> +}
> +
> +static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc,
> + PipeItem *item)
> +{
> + SpiceMsgMainMigrationBegin migrate;
> + MainChannel *main_ch;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN, item);
> + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + main_channel_fill_migrate_dst_info(main_ch, &migrate.dst_info);
> + spice_marshall_msg_main_migrate_begin(m, &migrate);
> +}
> +
> +static void main_channel_marshall_migrate_begin_seamless(SpiceMarshaller *m,
> + RedChannelClient *rcc,
> + PipeItem *item)
> +{
> + SpiceMsgMainMigrateBeginSeamless migrate_seamless;
> + MainChannel *main_ch;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS, item);
> + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + main_channel_fill_migrate_dst_info(main_ch, &migrate_seamless.dst_info);
> + migrate_seamless.src_mig_version = SPICE_MIGRATION_PROTOCOL_VERSION;
> + spice_marshall_msg_main_migrate_begin_seamless(m, &migrate_seamless);
> +}
> +
> +void main_channel_push_multi_media_time(MainChannel *main_chan, int time)
> +{
> + MultiMediaTimePipeItem info = {
> + .time = time,
> + };
> +
> + red_channel_pipes_new_add_push(&main_chan->base,
> + main_multi_media_time_item_new, &info);
> +}
> +
> +static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target)
> +{
> + spice_assert(mig_target);
> + free(main_channel->mig_target.host);
> + main_channel->mig_target.host = spice_strdup(mig_target->host);
> + free(main_channel->mig_target.cert_subject);
> + if (mig_target->cert_subject) {
> + main_channel->mig_target.cert_subject = spice_strdup(mig_target->cert_subject);
> + } else {
> + main_channel->mig_target.cert_subject = NULL;
> + }
> + main_channel->mig_target.port = mig_target->port;
> + main_channel->mig_target.sport = mig_target->sport;
> +}
> +
> +void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target)
> +{
> + main_channel_fill_mig_target(main_chan, mig_target);
> + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST);
> +}
> +
> +static void main_channel_marshall_migrate_switch(SpiceMarshaller *m, RedChannelClient *rcc,
> + PipeItem *item)
> +{
> + SpiceMsgMainMigrationSwitchHost migrate;
> + MainChannel *main_ch;
> +
> + spice_printerr("");
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, item);
> + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + migrate.port = main_ch->mig_target.port;
> + migrate.sport = main_ch->mig_target.sport;
> + migrate.host_size = strlen(main_ch->mig_target.host) + 1;
> + migrate.host_data = (uint8_t *)main_ch->mig_target.host;
> + if (main_ch->mig_target.cert_subject) {
> + migrate.cert_subject_size = strlen(main_ch->mig_target.cert_subject) + 1;
> + migrate.cert_subject_data = (uint8_t *)main_ch->mig_target.cert_subject;
> + } else {
> + migrate.cert_subject_size = 0;
> + migrate.cert_subject_data = NULL;
> + }
> + spice_marshall_msg_main_migrate_switch_host(m, &migrate);
> +}
> +
> +static void main_channel_marshall_multi_media_time(RedChannelClient *rcc,
> + SpiceMarshaller *m,
> + MultiMediaTimePipeItem *item)
> +{
> + SpiceMsgMainMultiMediaTime time_mes;
> +
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &item->base);
> + time_mes.time = item->time;
> + spice_marshall_msg_main_multi_media_time(m, &time_mes);
> +}
> +
> +static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base)
> +{
> + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> + SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
> +
> + /* In semi-seamless migration (dest side), the connection is started from scratch, and
> + * we ignore any pipe item that arrives before the INIT msg is sent.
> + * For seamless we don't send INIT, and the connection continues from the same place
> + * it stopped on the src side. */
> + if (!mcc->init_sent && !mcc->seamless_mig_dst && base->type != PIPE_ITEM_TYPE_MAIN_INIT) {
> + spice_printerr("Init msg for client %p was not sent yet "
> + "(client is probably during semi-seamless migration). Ignoring msg type %d",
> + rcc->client, base->type);
> + main_channel_release_pipe_item(rcc, base, FALSE);
> + return;
> + }
> + switch (base->type) {
> + case PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST:
> + main_channel_marshall_channels(rcc, m, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_PING:
> + main_channel_marshall_ping(rcc, m,
> + SPICE_CONTAINEROF(base, PingPipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MOUSE_MODE:
> + {
> + MouseModePipeItem *item =
> + SPICE_CONTAINEROF(base, MouseModePipeItem, base);
> + main_channel_marshall_mouse_mode(rcc, m, item);
> + break;
> + }
> + case PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED:
> + main_channel_marshall_agent_disconnected(rcc, m, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN:
> + main_channel_marshall_tokens(rcc, m,
> + SPICE_CONTAINEROF(base, TokensPipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_AGENT_DATA:
> + main_channel_marshall_agent_data(rcc, m,
> + SPICE_CONTAINEROF(base, AgentDataPipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA:
> + main_channel_marshall_migrate_data_item(rcc, m, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_INIT:
> + mcc->init_sent = TRUE;
> + main_channel_marshall_init(rcc, m,
> + SPICE_CONTAINEROF(base, InitPipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_NOTIFY:
> + main_channel_marshall_notify(rcc, m,
> + SPICE_CONTAINEROF(base, NotifyPipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN:
> + main_channel_marshall_migrate_begin(m, rcc, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS:
> + main_channel_marshall_migrate_begin_seamless(m, rcc, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME:
> + main_channel_marshall_multi_media_time(rcc, m,
> + SPICE_CONTAINEROF(base, MultiMediaTimePipeItem, base));
> + break;
> + case PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST:
> + main_channel_marshall_migrate_switch(m, rcc, base);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_NAME:
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_NAME, base);
> + spice_marshall_msg_main_name(m, &SPICE_CONTAINEROF(base, NamePipeItem, base)->msg);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_UUID:
> + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_UUID, base);
> + spice_marshall_msg_main_uuid(m, &SPICE_CONTAINEROF(base, UuidPipeItem, base)->msg);
> + break;
> + case PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS:
> + main_channel_marshall_agent_connected(m, rcc, base);
> + break;
> + default:
> + break;
> + };
> + red_channel_client_begin_send_message(rcc);
> +}
> +
> +static void main_channel_release_pipe_item(RedChannelClient *rcc,
> + PipeItem *base, int item_pushed)
> +{
> + switch (base->type) {
> + case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: {
> + AgentDataPipeItem *data = (AgentDataPipeItem *)base;
> +
> + data->free_data(data->data, data->opaque);
> + break;
> + }
> + case PIPE_ITEM_TYPE_MAIN_NOTIFY: {
> + NotifyPipeItem *data = (NotifyPipeItem *)base;
> + free(data->msg);
> + break;
> + }
> + default:
> + break;
> + }
> + free(base);
> +}
> +
> +static void main_channel_client_handle_migrate_connected(MainChannelClient *mcc,
> + int success,
> + int seamless)
> +{
> + spice_printerr("client %p connected: %d seamless %d", mcc->base.client, success, seamless);
> + if (mcc->mig_wait_connect) {
> + MainChannel *main_channel = SPICE_CONTAINEROF(mcc->base.channel, MainChannel, base);
> +
> + mcc->mig_wait_connect = FALSE;
> + mcc->mig_connect_ok = success;
> + spice_assert(main_channel->num_clients_mig_wait);
> + spice_assert(!seamless || main_channel->num_clients_mig_wait == 1);
> + if (!--main_channel->num_clients_mig_wait) {
> + reds_on_main_migrate_connected(seamless && success);
> + }
> + } else {
> + if (success) {
> + spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client);
> + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL);
> + }
> + }
> +}
> +
> +void main_channel_client_handle_migrate_dst_do_seamless(MainChannelClient *mcc,
> + uint32_t src_version)
> +{
> + if (reds_on_migrate_dst_set_seamless(mcc, src_version)) {
> + mcc->seamless_mig_dst = TRUE;
> + red_channel_client_pipe_add_empty_msg(&mcc->base,
> + SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK);
> + } else {
> + red_channel_client_pipe_add_empty_msg(&mcc->base,
> + SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK);
> + }
> +}
> +
> +void main_channel_client_handle_migrate_end(MainChannelClient *mcc)
> +{
> + if (!red_client_during_migrate_at_target(mcc->base.client)) {
> + spice_printerr("unexpected SPICE_MSGC_MIGRATE_END");
> + return;
> + }
> + if (!red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) {
> + spice_printerr("unexpected SPICE_MSGC_MIGRATE_END, "
> + "client does not support semi-seamless migration");
> + return;
> + }
> + red_client_semi_seamless_migrate_complete(mcc->base.client);
> +}
> +
> +void main_channel_migrate_dst_complete(MainChannelClient *mcc)
> +{
> + if (mcc->mig_wait_prev_complete) {
> + if (mcc->mig_wait_prev_try_seamless) {
> + spice_assert(mcc->base.channel->clients_num == 1);
> + red_channel_client_pipe_add_type(&mcc->base,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS);
> + } else {
> + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN);
> + }
> + mcc->mig_wait_connect = TRUE;
> + mcc->mig_wait_prev_complete = FALSE;
> + }
> +}
> +
> +static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type,
> + void *message)
> +{
> + MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> +
> + switch (type) {
> + case SPICE_MSGC_MAIN_AGENT_START: {
> + SpiceMsgcMainAgentStart *tokens;
> +
> + spice_printerr("agent start");
> + if (!main_chan) {
> + return FALSE;
> + }
> + tokens = (SpiceMsgcMainAgentStart *)message;
> + reds_on_main_agent_start(mcc, tokens->num_tokens);
> + break;
> + }
> + case SPICE_MSGC_MAIN_AGENT_DATA: {
> + reds_on_main_agent_data(mcc, message, size);
> + break;
> + }
> + case SPICE_MSGC_MAIN_AGENT_TOKEN: {
> + SpiceMsgcMainAgentTokens *tokens;
> +
> + tokens = (SpiceMsgcMainAgentTokens *)message;
> + reds_on_main_agent_tokens(mcc, tokens->num_tokens);
> + break;
> + }
> + case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
> + main_channel_push_channels(mcc);
> + break;
> + case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
> + main_channel_client_handle_migrate_connected(mcc,
> + TRUE /* success */,
> + FALSE /* seamless */);
> + break;
> + case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS:
> + main_channel_client_handle_migrate_connected(mcc,
> + TRUE /* success */,
> + TRUE /* seamless */);
> + break;
> + case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
> + main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE);
> + break;
> + case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS:
> + main_channel_client_handle_migrate_dst_do_seamless(mcc,
> + ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version);
> + break;
> + case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST:
> + reds_on_main_mouse_mode_request(message, size);
> + break;
> + case SPICE_MSGC_PONG: {
> + SpiceMsgPing *ping = (SpiceMsgPing *)message;
> + uint64_t roundtrip;
> + struct timespec ts;
> +
> + clock_gettime(CLOCK_MONOTONIC, &ts);
> + roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp;
> +
> + if (ping->id == mcc->net_test_id) {
> + switch (mcc->net_test_stage) {
> + case NET_TEST_STAGE_WARMUP:
> + mcc->net_test_id++;
> + mcc->net_test_stage = NET_TEST_STAGE_LATENCY;
> + mcc->latency = roundtrip;
> + break;
> + case NET_TEST_STAGE_LATENCY:
> + mcc->net_test_id++;
> + mcc->net_test_stage = NET_TEST_STAGE_RATE;
> + mcc->latency = MIN(mcc->latency, roundtrip);
> + break;
> + case NET_TEST_STAGE_RATE:
> + mcc->net_test_id = 0;
> + if (roundtrip <= mcc->latency) {
> + // probably high load on client or server result with incorrect values
> + spice_printerr("net test: invalid values, latency %" PRIu64
> + " roundtrip %" PRIu64 ". assuming high"
> + " bandwidth", mcc->latency, roundtrip);
> + mcc->latency = 0;
> + mcc->net_test_stage = NET_TEST_STAGE_INVALID;
> + red_channel_client_start_connectivity_monitoring(&mcc->base,
> + CLIENT_CONNECTIVITY_TIMEOUT);
> + break;
> + }
> + mcc->bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000
> + / (roundtrip - mcc->latency);
> + mcc->net_test_stage = NET_TEST_STAGE_COMPLETE;
> + spice_printerr("net test: latency %f ms, bitrate %"PRIu64" bps (%f Mbps)%s",
> + (double)mcc->latency / 1000,
> + mcc->bitrate_per_sec,
> + (double)mcc->bitrate_per_sec / 1024 / 1024,
> + main_channel_client_is_low_bandwidth(mcc) ? " LOW BANDWIDTH" : "");
> + red_channel_client_start_connectivity_monitoring(&mcc->base,
> + CLIENT_CONNECTIVITY_TIMEOUT);
> + break;
> + default:
> + spice_printerr("invalid net test stage, ping id %d test id %d stage %d",
> + ping->id,
> + mcc->net_test_id,
> + mcc->net_test_stage);
> + mcc->net_test_stage = NET_TEST_STAGE_INVALID;
> + }
> + break;
> + } else {
> + /*
> + * channel client monitors the connectivity using ping-pong messages
> + */
> + red_channel_client_handle_message(rcc, size, type, message);
> + }
> +#ifdef RED_STATISTICS
> + reds_update_stat_value(roundtrip);
> +#endif
> + break;
> + }
> + case SPICE_MSGC_DISCONNECTING:
> + break;
> + case SPICE_MSGC_MAIN_MIGRATE_END:
> + main_channel_client_handle_migrate_end(mcc);
> + break;
> + default:
> + return red_channel_client_handle_message(rcc, size, type, message);
> + }
> + return TRUE;
> +}
> +
> +static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size)
> +{
> + MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> +
> + if (type == SPICE_MSGC_MAIN_AGENT_DATA) {
> + return reds_get_agent_data_buffer(mcc, size);
> + } else {
> + return main_chan->recv_buf;
> + }
> +}
> +
> +static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> +{
> + if (type == SPICE_MSGC_MAIN_AGENT_DATA) {
> + reds_release_agent_data_buffer(msg);
> + }
> +}
> +
> +static int main_channel_config_socket(RedChannelClient *rcc)
> +{
> + return TRUE;
> +}
> +
> +static void main_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
> +{
> +}
> +
> +static int main_channel_handle_migrate_flush_mark(RedChannelClient *rcc)
> +{
> + spice_debug(NULL);
> + main_channel_push_migrate_data_item(SPICE_CONTAINEROF(rcc->channel,
> + MainChannel, base));
> + return TRUE;
> +}
> +
> +#ifdef RED_STATISTICS
> +static void do_ping_client(MainChannelClient *mcc,
> + const char *opt, int has_interval, int interval)
> +{
> + spice_printerr("");
> + if (!opt) {
> + main_channel_client_push_ping(mcc, 0);
> + } else if (!strcmp(opt, "on")) {
> + if (has_interval && interval > 0) {
> + mcc->ping_interval = interval * 1000;
> + }
> + core->timer_start(mcc->ping_timer, mcc->ping_interval);
> + } else if (!strcmp(opt, "off")) {
> + core->timer_cancel(mcc->ping_timer);
> + } else {
> + return;
> + }
> +}
> +
> +static void ping_timer_cb(void *opaque)
> +{
> + MainChannelClient *mcc = opaque;
> +
> + if (!red_channel_client_is_connected(&mcc->base)) {
> + spice_printerr("not connected to peer, ping off");
> + core->timer_cancel(mcc->ping_timer);
> + return;
> + }
> + do_ping_client(mcc, NULL, 0, 0);
> + core->timer_start(mcc->ping_timer, mcc->ping_interval);
> +}
> +#endif /* RED_STATISTICS */
> +
> +static MainChannelClient *main_channel_client_create(MainChannel *main_chan, RedClient *client,
> + RedsStream *stream, uint32_t connection_id,
> + int num_common_caps, uint32_t *common_caps,
> + int num_caps, uint32_t *caps)
> +{
> + MainChannelClient *mcc = (MainChannelClient*)
> + red_channel_client_create(sizeof(MainChannelClient), &main_chan->base,
> + client, stream, FALSE, num_common_caps,
> + common_caps, num_caps, caps);
> + spice_assert(mcc != NULL);
> + mcc->connection_id = connection_id;
> + mcc->bitrate_per_sec = ~0;
> +#ifdef RED_STATISTICS
> + if (!(mcc->ping_timer = core->timer_add(ping_timer_cb, NULL))) {
> + spice_error("ping timer create failed");
> + }
> + mcc->ping_interval = PING_INTERVAL;
> +#endif
> + return mcc;
> +}
> +
> +MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client,
> + RedsStream *stream, uint32_t connection_id, int migration,
> + int num_common_caps, uint32_t *common_caps, int num_caps,
> + uint32_t *caps)
> +{
> + MainChannelClient *mcc;
> +
> + spice_assert(channel);
> +
> + // TODO - migration - I removed it from channel creation, now put it
> + // into usage somewhere (not an issue until we return migration to it's
> + // former glory)
> + spice_printerr("add main channel client");
> + mcc = main_channel_client_create(channel, client, stream, connection_id,
> + num_common_caps, common_caps,
> + num_caps, caps);
> + return mcc;
> +}
> +
> +int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen)
> +{
> + return main_chan ? getsockname(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1;
> +}
> +
> +int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen)
> +{
> + return main_chan ? getpeername(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1;
> +}
> +
> +// TODO: ? shouldn't it disonnect all clients? or shutdown all main_channels?
> +void main_channel_close(MainChannel *main_chan)
> +{
> + int socketfd;
> +
> + if (main_chan && (socketfd = red_channel_get_first_socket(&main_chan->base)) != -1) {
> + close(socketfd);
> + }
> +}
> +
> +int main_channel_client_is_network_info_initialized(MainChannelClient *mcc)
> +{
> + return mcc->net_test_stage == NET_TEST_STAGE_COMPLETE;
> +}
> +
> +int main_channel_client_is_low_bandwidth(MainChannelClient *mcc)
> +{
> + // TODO: configurable?
> + return mcc->bitrate_per_sec < 10 * 1024 * 1024;
> +}
> +
> +uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc)
> +{
> + return mcc->bitrate_per_sec;
> +}
> +
> +uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc)
> +{
> + return mcc->latency / 1000;
> +}
> +
> +static void main_channel_client_migrate(RedChannelClient *rcc)
> +{
> + reds_on_main_channel_migrate(SPICE_CONTAINEROF(rcc, MainChannelClient, base));
> + red_channel_client_default_migrate(rcc);
> +}
> +
> +MainChannel* main_channel_init(void)
> +{
> + RedChannel *channel;
> + ChannelCbs channel_cbs = { NULL, };
> + ClientCbs client_cbs = {NULL, };
> +
> + channel_cbs.config_socket = main_channel_config_socket;
> + channel_cbs.on_disconnect = main_channel_client_on_disconnect;
> + channel_cbs.send_item = main_channel_send_item;
> + channel_cbs.hold_item = main_channel_hold_pipe_item;
> + channel_cbs.release_item = main_channel_release_pipe_item;
> + channel_cbs.alloc_recv_buf = main_channel_alloc_msg_rcv_buf;
> + channel_cbs.release_recv_buf = main_channel_release_msg_rcv_buf;
> + channel_cbs.handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark;
> + channel_cbs.handle_migrate_data = main_channel_handle_migrate_data;
> +
> + // TODO: set the migration flag of the channel
> + channel = red_channel_create_parser(sizeof(MainChannel), core,
> + SPICE_CHANNEL_MAIN, 0,
> + FALSE, /* handle_acks */
> + spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL),
> + main_channel_handle_parsed,
> + &channel_cbs,
> + SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER);
> + spice_assert(channel);
> + red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
> + red_channel_set_cap(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
> +
> + client_cbs.migrate = main_channel_client_migrate;
> + red_channel_register_client_cbs(channel, &client_cbs);
> +
> + return (MainChannel *)channel;
> +}
> +
> +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc)
> +{
> + spice_assert(mcc);
> + return &mcc->base;
> +}
> +
> +static int main_channel_connect_semi_seamless(MainChannel *main_channel)
> +{
> + RingItem *client_link;
> +
> + RING_FOREACH(client_link, &main_channel->base.clients) {
> + MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient,
> + base.channel_link);
> + if (red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) {
> + if (red_client_during_migrate_at_target(mcc->base.client)) {
> + spice_printerr("client %p: wait till previous migration completes", mcc->base.client);
> + mcc->mig_wait_prev_complete = TRUE;
> + mcc->mig_wait_prev_try_seamless = FALSE;
> + } else {
> + red_channel_client_pipe_add_type(&mcc->base,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN);
> + mcc->mig_wait_connect = TRUE;
> + }
> + mcc->mig_connect_ok = FALSE;
> + main_channel->num_clients_mig_wait++;
> + }
> + }
> + return main_channel->num_clients_mig_wait;
> +}
> +
> +static int main_channel_connect_seamless(MainChannel *main_channel)
> +{
> + RingItem *client_link;
> +
> + spice_assert(main_channel->base.clients_num == 1);
> +
> + RING_FOREACH(client_link, &main_channel->base.clients) {
> + MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient,
> + base.channel_link);
> + spice_assert(red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEAMLESS_MIGRATE));
> + if (red_client_during_migrate_at_target(mcc->base.client)) {
> + spice_printerr("client %p: wait till previous migration completes", mcc->base.client);
> + mcc->mig_wait_prev_complete = TRUE;
> + mcc->mig_wait_prev_try_seamless = TRUE;
> + } else {
> + red_channel_client_pipe_add_type(&mcc->base,
> + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS);
> + mcc->mig_wait_connect = TRUE;
> + }
> + mcc->mig_connect_ok = FALSE;
> + main_channel->num_clients_mig_wait++;
> + }
> + return main_channel->num_clients_mig_wait;
> +}
> +
> +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target,
> + int try_seamless)
> +{
> + main_channel_fill_mig_target(main_channel, mig_target);
> + main_channel->num_clients_mig_wait = 0;
> +
> + if (!main_channel_is_connected(main_channel)) {
> + return 0;
> + }
> +
> + if (!try_seamless) {
> + return main_channel_connect_semi_seamless(main_channel);
> + } else {
> + RingItem *client_item;
> + MainChannelClient *mcc;
> +
> + client_item = ring_get_head(&main_channel->base.clients);
> + mcc = SPICE_CONTAINEROF(client_item, MainChannelClient, base.channel_link);
> +
> + if (!red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
> + return main_channel_connect_semi_seamless(main_channel);
> + } else {
> + return main_channel_connect_seamless(main_channel);
> + }
> + }
> +
> +}
> +
> +void main_channel_migrate_cancel_wait(MainChannel *main_chan)
> +{
> + RingItem *client_link;
> +
> + RING_FOREACH(client_link, &main_chan->base.clients) {
> + MainChannelClient *mcc;
> +
> + mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> + if (mcc->mig_wait_connect) {
> + spice_printerr("client %p cancel wait connect", mcc->base.client);
> + mcc->mig_wait_connect = FALSE;
> + mcc->mig_connect_ok = FALSE;
> + }
> + mcc->mig_wait_prev_complete = FALSE;
> + }
> + main_chan->num_clients_mig_wait = 0;
> +}
> +
> +int main_channel_migrate_src_complete(MainChannel *main_chan, int success)
> +{
> + RingItem *client_link;
> + int semi_seamless_count = 0;
> +
> + spice_printerr("");
> +
> + if (ring_is_empty(&main_chan->base.clients)) {
> + spice_printerr("no peer connected");
> + return 0;
> + }
> +
> + RING_FOREACH(client_link, &main_chan->base.clients) {
> + MainChannelClient *mcc;
> + int semi_seamless_support;
> +
> + mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> + semi_seamless_support = red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
> + if (semi_seamless_support && mcc->mig_connect_ok) {
> + if (success) {
> + spice_printerr("client %p MIGRATE_END", mcc->base.client);
> + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_END);
> + semi_seamless_count++;
> + } else {
> + spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client);
> + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL);
> + }
> + } else {
> + if (success) {
> + spice_printerr("client %p SWITCH_HOST", mcc->base.client);
> + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST);
> + }
> + }
> + mcc->mig_connect_ok = FALSE;
> + mcc->mig_wait_connect = FALSE;
> + }
> + return semi_seamless_count;
> +}
> diff --git a/server/main-channel.h b/server/main-channel.h
> new file mode 100644
> index 0000000..9bd20f1
> --- /dev/null
> +++ b/server/main-channel.h
> @@ -0,0 +1,103 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef __MAIN_CHANNEL_H__
> +#define __MAIN_CHANNEL_H__
> +
> +#include <stdint.h>
> +#include <spice/vd_agent.h>
> +#include "common/marshaller.h"
> +#include "red_channel.h"
> +
> +// TODO: Defines used to calculate receive buffer size, and also by reds.c
> +// other options: is to make a reds_main_consts.h, to duplicate defines.
> +#define REDS_AGENT_WINDOW_SIZE 10
> +#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
> +
> +// approximate max receive message size for main channel
> +#define MAIN_CHANNEL_RECEIVE_BUF_SIZE \
> + (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE)
> +
> +struct RedsMigSpice {
> + char *host;
> + char *cert_subject;
> + int port;
> + int sport;
> +};
> +typedef struct RedsMigSpice RedsMigSpice;
> +
> +typedef struct MainChannel {
> + RedChannel base;
> + uint8_t recv_buf[MAIN_CHANNEL_RECEIVE_BUF_SIZE];
> + RedsMigSpice mig_target; // TODO: add refs and release (afrer all clients completed migration in one way or the other?)
> + int num_clients_mig_wait;
> +} MainChannel;
> +
> +
> +MainChannel *main_channel_init(void);
> +RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t link_id);
> +/* This is a 'clone' from the reds.h Channel.link callback to allow passing link_id */
> +MainChannelClient *main_channel_link(MainChannel *, RedClient *client,
> + RedsStream *stream, uint32_t link_id, int migration, int num_common_caps,
> + uint32_t *common_caps, int num_caps, uint32_t *caps);
> +void main_channel_close(MainChannel *main_chan); // not destroy, just socket close
> +void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, int is_client_mouse_allowed);
> +void main_channel_push_agent_connected(MainChannel *main_chan);
> +void main_channel_push_agent_disconnected(MainChannel *main_chan);
> +void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens);
> +void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len,
> + spice_marshaller_item_free_func free_data, void *opaque);
> +void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate);
> +// TODO: huge. Consider making a reds_* interface for these functions
> +// and calling from main.
> +void main_channel_push_init(MainChannelClient *mcc, int display_channels_hint,
> + int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time,
> + int ram_hint);
> +void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg);
> +void main_channel_push_multi_media_time(MainChannel *main_chan, int time);
> +int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen);
> +int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen);
> +
> +/*
> + * return TRUE if network test had been completed successfully.
> + * If FALSE, bitrate_per_sec is set to MAX_UINT64 and the roundtrip is set to 0
> + */
> +int main_channel_client_is_network_info_initialized(MainChannelClient *mcc);
> +int main_channel_client_is_low_bandwidth(MainChannelClient *mcc);
> +uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc);
> +uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc);
> +
> +int main_channel_is_connected(MainChannel *main_chan);
> +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc);
> +
> +/* switch host migration */
> +void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target);
> +
> +/* semi seamless migration */
> +
> +/* returns the number of clients that we are waiting for their connection.
> + * try_seamless = 'true' when the seamless-migration=on in qemu command line */
> +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target,
> + int try_seamless);
> +void main_channel_migrate_cancel_wait(MainChannel *main_chan);
> +/* returns the number of clients for which SPICE_MSG_MAIN_MIGRATE_END was sent*/
> +int main_channel_migrate_src_complete(MainChannel *main_chan, int success);
> +void main_channel_migrate_dst_complete(MainChannelClient *mcc);
> +void main_channel_push_name(MainChannelClient *mcc, const char *name);
> +void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]);
> +
> +#endif
> diff --git a/server/main-dispatcher.c b/server/main-dispatcher.c
> new file mode 100644
> index 0000000..eb7cee6
> --- /dev/null
> +++ b/server/main-dispatcher.c
> @@ -0,0 +1,217 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009-2015 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/>.
> +*/
> +#include <config.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <pthread.h>
> +
> +#include "red_common.h"
> +#include "dispatcher.h"
> +#include "main-dispatcher.h"
> +#include "red_channel.h"
> +#include "reds.h"
> +
> +/*
> + * Main Dispatcher
> + * ===============
> + *
> + * Communication channel between any non main thread and the main thread.
> + *
> + * The main thread is that from which spice_server_init is called.
> + *
> + * Messages are single sized, sent from the non-main thread to the main-thread.
> + * No acknowledge is sent back. This prevents a possible deadlock with the main
> + * thread already waiting on a response for the existing red_dispatcher used
> + * by the worker thread.
> + *
> + * All events have three functions:
> + * main_dispatcher_<event_name> - non static, public function
> + * main_dispatcher_self_<event_name> - handler for main thread
> + * main_dispatcher_handle_<event_name> - handler for callback from main thread
> + * seperate from self because it may send an ack or do other work in the future.
> + */
> +
> +typedef struct {
> + Dispatcher base;
> + SpiceCoreInterface *core;
> +} MainDispatcher;
> +
> +MainDispatcher main_dispatcher;
> +
> +enum {
> + MAIN_DISPATCHER_CHANNEL_EVENT = 0,
> + MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> + MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> + MAIN_DISPATCHER_CLIENT_DISCONNECT,
> +
> + MAIN_DISPATCHER_NUM_MESSAGES
> +};
> +
> +typedef struct MainDispatcherChannelEventMessage {
> + int event;
> + SpiceChannelEventInfo *info;
> +} MainDispatcherChannelEventMessage;
> +
> +typedef struct MainDispatcherMigrateSeamlessDstCompleteMessage {
> + RedClient *client;
> +} MainDispatcherMigrateSeamlessDstCompleteMessage;
> +
> +typedef struct MainDispatcherMmTimeLatencyMessage {
> + RedClient *client;
> + uint32_t latency;
> +} MainDispatcherMmTimeLatencyMessage;
> +
> +typedef struct MainDispatcherClientDisconnectMessage {
> + RedClient *client;
> +} MainDispatcherClientDisconnectMessage;
> +
> +/* channel_event - calls core->channel_event, must be done in main thread */
> +static void main_dispatcher_self_handle_channel_event(
> + int event,
> + SpiceChannelEventInfo *info)
> +{
> + reds_handle_channel_event(event, info);
> +}
> +
> +static void main_dispatcher_handle_channel_event(void *opaque,
> + void *payload)
> +{
> + MainDispatcherChannelEventMessage *channel_event = payload;
> +
> + main_dispatcher_self_handle_channel_event(channel_event->event,
> + channel_event->info);
> +}
> +
> +void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info)
> +{
> + MainDispatcherChannelEventMessage msg = {0,};
> +
> + if (pthread_self() == main_dispatcher.base.self) {
> + main_dispatcher_self_handle_channel_event(event, info);
> + return;
> + }
> + msg.event = event;
> + msg.info = info;
> + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT,
> + &msg);
> +}
> +
> +
> +static void main_dispatcher_handle_migrate_complete(void *opaque,
> + void *payload)
> +{
> + MainDispatcherMigrateSeamlessDstCompleteMessage *mig_complete = payload;
> +
> + reds_on_client_seamless_migrate_complete(mig_complete->client);
> + red_client_unref(mig_complete->client);
> +}
> +
> +static void main_dispatcher_handle_mm_time_latency(void *opaque,
> + void *payload)
> +{
> + MainDispatcherMmTimeLatencyMessage *msg = payload;
> + reds_set_client_mm_time_latency(msg->client, msg->latency);
> + red_client_unref(msg->client);
> +}
> +
> +static void main_dispatcher_handle_client_disconnect(void *opaque,
> + void *payload)
> +{
> + MainDispatcherClientDisconnectMessage *msg = payload;
> +
> + spice_debug("client=%p", msg->client);
> + reds_client_disconnect(msg->client);
> + red_client_unref(msg->client);
> +}
> +
> +void main_dispatcher_seamless_migrate_dst_complete(RedClient *client)
> +{
> + MainDispatcherMigrateSeamlessDstCompleteMessage msg;
> +
> + if (pthread_self() == main_dispatcher.base.self) {
> + reds_on_client_seamless_migrate_complete(client);
> + return;
> + }
> +
> + msg.client = red_client_ref(client);
> + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> + &msg);
> +}
> +
> +void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency)
> +{
> + MainDispatcherMmTimeLatencyMessage msg;
> +
> + if (pthread_self() == main_dispatcher.base.self) {
> + reds_set_client_mm_time_latency(client, latency);
> + return;
> + }
> +
> + msg.client = red_client_ref(client);
> + msg.latency = latency;
> + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> + &msg);
> +}
> +
> +void main_dispatcher_client_disconnect(RedClient *client)
> +{
> + MainDispatcherClientDisconnectMessage msg;
> +
> + if (!client->disconnecting) {
> + spice_debug("client %p", client);
> + msg.client = red_client_ref(client);
> + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT,
> + &msg);
> + } else {
> + spice_debug("client %p already during disconnection", client);
> + }
> +}
> +
> +static void dispatcher_handle_read(int fd, int event, void *opaque)
> +{
> + Dispatcher *dispatcher = opaque;
> +
> + dispatcher_handle_recv_read(dispatcher);
> +}
> +
> +/*
> + * FIXME:
> + * Reds routines shouldn't be exposed. Instead reds.c should register the callbacks,
> + * and the corresponding operations should be made only via main_dispatcher.
> + */
> +void main_dispatcher_init(SpiceCoreInterface *core)
> +{
> + memset(&main_dispatcher, 0, sizeof(main_dispatcher));
> + main_dispatcher.core = core;
> + dispatcher_init(&main_dispatcher.base, MAIN_DISPATCHER_NUM_MESSAGES, &main_dispatcher.base);
> + core->watch_add(main_dispatcher.base.recv_fd, SPICE_WATCH_EVENT_READ,
> + dispatcher_handle_read, &main_dispatcher.base);
> + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT,
> + main_dispatcher_handle_channel_event,
> + sizeof(MainDispatcherChannelEventMessage), 0 /* no ack */);
> + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> + main_dispatcher_handle_migrate_complete,
> + sizeof(MainDispatcherMigrateSeamlessDstCompleteMessage), 0 /* no ack */);
> + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> + main_dispatcher_handle_mm_time_latency,
> + sizeof(MainDispatcherMmTimeLatencyMessage), 0 /* no ack */);
> + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT,
> + main_dispatcher_handle_client_disconnect,
> + sizeof(MainDispatcherClientDisconnectMessage), 0 /* no ack */);
> +}
> diff --git a/server/main-dispatcher.h b/server/main-dispatcher.h
> new file mode 100644
> index 0000000..af40093
> --- /dev/null
> +++ b/server/main-dispatcher.h
> @@ -0,0 +1,36 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009-2015 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/>.
> +*/
> +#ifndef MAIN_DISPATCHER_H
> +#define MAIN_DISPATCHER_H
> +
> +#include <spice.h>
> +#include "red_channel.h"
> +
> +void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info);
> +void main_dispatcher_seamless_migrate_dst_complete(RedClient *client);
> +void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency);
> +/*
> + * Disconnecting the client is always executed asynchronously,
> + * in order to protect from expired references in the routines
> + * that triggered the client destruction.
> + */
> +void main_dispatcher_client_disconnect(RedClient *client);
> +
> +void main_dispatcher_init(SpiceCoreInterface *core);
> +
> +#endif //MAIN_DISPATCHER_H
> diff --git a/server/main_channel.c b/server/main_channel.c
> deleted file mode 100644
> index 1af6baa..0000000
> --- a/server/main_channel.c
> +++ /dev/null
> @@ -1,1345 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009 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 <inttypes.h>
> -#include <stdint.h>
> -#include <stdio.h>
> -#include <unistd.h>
> -#include <sys/socket.h>
> -#include <netinet/in.h>
> -#include <netinet/tcp.h>
> -#include <arpa/inet.h>
> -#include <netdb.h>
> -#include <limits.h>
> -#include <time.h>
> -#include <pthread.h>
> -#include <sys/mman.h>
> -#include <fcntl.h>
> -#include <errno.h>
> -#include <ctype.h>
> -
> -#include "common/generated_server_marshallers.h"
> -#include "common/messages.h"
> -#include "common/ring.h"
> -
> -#include "demarshallers.h"
> -#include "main_channel.h"
> -#include "red_channel.h"
> -#include "red_common.h"
> -#include "reds.h"
> -#include "migration_protocol.h"
> -#include "main_dispatcher.h"
> -#include "utils.h"
> -
> -#define ZERO_BUF_SIZE 4096
> -
> -#define NET_TEST_WARMUP_BYTES 0
> -#define NET_TEST_BYTES (1024 * 250)
> -
> -#define PING_INTERVAL (1000 * 10)
> -
> -#define CLIENT_CONNECTIVITY_TIMEOUT (30*1000) // 30 seconds
> -
> -static uint8_t zero_page[ZERO_BUF_SIZE] = {0};
> -
> -enum {
> - PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST = PIPE_ITEM_TYPE_CHANNEL_BASE,
> - PIPE_ITEM_TYPE_MAIN_PING,
> - PIPE_ITEM_TYPE_MAIN_MOUSE_MODE,
> - PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED,
> - PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN,
> - PIPE_ITEM_TYPE_MAIN_AGENT_DATA,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA,
> - PIPE_ITEM_TYPE_MAIN_INIT,
> - PIPE_ITEM_TYPE_MAIN_NOTIFY,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST,
> - PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME,
> - PIPE_ITEM_TYPE_MAIN_NAME,
> - PIPE_ITEM_TYPE_MAIN_UUID,
> - PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS,
> -};
> -
> -typedef struct RefsPipeItem {
> - PipeItem base;
> - int *refs;
> -} RefsPipeItem;
> -
> -typedef struct PingPipeItem {
> - PipeItem base;
> - int size;
> -} PingPipeItem;
> -
> -typedef struct MouseModePipeItem {
> - PipeItem base;
> - int current_mode;
> - int is_client_mouse_allowed;
> -} MouseModePipeItem;
> -
> -typedef struct TokensPipeItem {
> - PipeItem base;
> - int tokens;
> -} TokensPipeItem;
> -
> -typedef struct AgentDataPipeItem {
> - PipeItem base;
> - uint8_t* data;
> - size_t len;
> - spice_marshaller_item_free_func free_data;
> - void *opaque;
> -} AgentDataPipeItem;
> -
> -typedef struct InitPipeItem {
> - PipeItem base;
> - int connection_id;
> - int display_channels_hint;
> - int current_mouse_mode;
> - int is_client_mouse_allowed;
> - int multi_media_time;
> - int ram_hint;
> -} InitPipeItem;
> -
> -typedef struct NamePipeItem {
> - PipeItem base;
> - SpiceMsgMainName msg;
> -} NamePipeItem;
> -
> -typedef struct UuidPipeItem {
> - PipeItem base;
> - SpiceMsgMainUuid msg;
> -} UuidPipeItem;
> -
> -typedef struct NotifyPipeItem {
> - PipeItem base;
> - char *msg;
> -} NotifyPipeItem;
> -
> -typedef struct MultiMediaTimePipeItem {
> - PipeItem base;
> - int time;
> -} MultiMediaTimePipeItem;
> -
> -struct MainChannelClient {
> - RedChannelClient base;
> - uint32_t connection_id;
> - uint32_t ping_id;
> - uint32_t net_test_id;
> - int net_test_stage;
> - uint64_t latency;
> - uint64_t bitrate_per_sec;
> -#ifdef RED_STATISTICS
> - SpiceTimer *ping_timer;
> - int ping_interval;
> -#endif
> - int mig_wait_connect;
> - int mig_connect_ok;
> - int mig_wait_prev_complete;
> - int mig_wait_prev_try_seamless;
> - int init_sent;
> - int seamless_mig_dst;
> -};
> -
> -enum NetTestStage {
> - NET_TEST_STAGE_INVALID,
> - NET_TEST_STAGE_WARMUP,
> - NET_TEST_STAGE_LATENCY,
> - NET_TEST_STAGE_RATE,
> - NET_TEST_STAGE_COMPLETE,
> -};
> -
> -static void main_channel_release_pipe_item(RedChannelClient *rcc,
> - PipeItem *base, int item_pushed);
> -
> -int main_channel_is_connected(MainChannel *main_chan)
> -{
> - return red_channel_is_connected(&main_chan->base);
> -}
> -
> -/*
> - * When the main channel is disconnected, disconnect the entire client.
> - */
> -static void main_channel_client_on_disconnect(RedChannelClient *rcc)
> -{
> - spice_printerr("rcc=%p", rcc);
> - main_dispatcher_client_disconnect(rcc->client);
> -}
> -
> -RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id)
> -{
> - RingItem *link;
> - MainChannelClient *mcc;
> -
> - RING_FOREACH(link, &main_chan->base.clients) {
> - mcc = SPICE_CONTAINEROF(link, MainChannelClient, base.channel_link);
> - if (mcc->connection_id == connection_id) {
> - return mcc->base.client;
> - }
> - }
> - return NULL;
> -}
> -
> -static int main_channel_client_push_ping(MainChannelClient *mcc, int size);
> -
> -void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate)
> -{
> - if (!mcc || mcc->net_test_id) {
> - return;
> - }
> - if (test_rate) {
> - if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES)
> - && main_channel_client_push_ping(mcc, 0)
> - && main_channel_client_push_ping(mcc, NET_TEST_BYTES)) {
> - mcc->net_test_id = mcc->ping_id - 2;
> - mcc->net_test_stage = NET_TEST_STAGE_WARMUP;
> - }
> - } else {
> - red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT);
> - }
> -}
> -
> -typedef struct MainMouseModeItemInfo {
> - int current_mode;
> - int is_client_mouse_allowed;
> -} MainMouseModeItemInfo;
> -
> -static PipeItem *main_mouse_mode_item_new(RedChannelClient *rcc, void *data, int num)
> -{
> - MouseModePipeItem *item = spice_malloc(sizeof(MouseModePipeItem));
> - MainMouseModeItemInfo *info = data;
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_MOUSE_MODE);
> - item->current_mode = info->current_mode;
> - item->is_client_mouse_allowed = info->is_client_mouse_allowed;
> - return &item->base;
> -}
> -
> -static PipeItem *main_ping_item_new(MainChannelClient *mcc, int size)
> -{
> - PingPipeItem *item = spice_malloc(sizeof(PingPipeItem));
> -
> - red_channel_pipe_item_init(mcc->base.channel, &item->base, PIPE_ITEM_TYPE_MAIN_PING);
> - item->size = size;
> - return &item->base;
> -}
> -
> -static PipeItem *main_agent_tokens_item_new(RedChannelClient *rcc, uint32_t num_tokens)
> -{
> - TokensPipeItem *item = spice_malloc(sizeof(TokensPipeItem));
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN);
> - item->tokens = num_tokens;
> - return &item->base;
> -}
> -
> -static PipeItem *main_agent_data_item_new(RedChannelClient *rcc, uint8_t* data, size_t len,
> - spice_marshaller_item_free_func free_data,
> - void *opaque)
> -{
> - AgentDataPipeItem *item = spice_malloc(sizeof(AgentDataPipeItem));
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_AGENT_DATA);
> - item->data = data;
> - item->len = len;
> - item->free_data = free_data;
> - item->opaque = opaque;
> - return &item->base;
> -}
> -
> -static PipeItem *main_init_item_new(MainChannelClient *mcc,
> - int connection_id, int display_channels_hint, int current_mouse_mode,
> - int is_client_mouse_allowed, int multi_media_time,
> - int ram_hint)
> -{
> - InitPipeItem *item = spice_malloc(sizeof(InitPipeItem));
> -
> - red_channel_pipe_item_init(mcc->base.channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_INIT);
> - item->connection_id = connection_id;
> - item->display_channels_hint = display_channels_hint;
> - item->current_mouse_mode = current_mouse_mode;
> - item->is_client_mouse_allowed = is_client_mouse_allowed;
> - item->multi_media_time = multi_media_time;
> - item->ram_hint = ram_hint;
> - return &item->base;
> -}
> -
> -static PipeItem *main_name_item_new(MainChannelClient *mcc, const char *name)
> -{
> - NamePipeItem *item = spice_malloc(sizeof(NamePipeItem) + strlen(name) + 1);
> -
> - red_channel_pipe_item_init(mcc->base.channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_NAME);
> - item->msg.name_len = strlen(name) + 1;
> - memcpy(&item->msg.name, name, item->msg.name_len);
> -
> - return &item->base;
> -}
> -
> -static PipeItem *main_uuid_item_new(MainChannelClient *mcc, const uint8_t uuid[16])
> -{
> - UuidPipeItem *item = spice_malloc(sizeof(UuidPipeItem));
> -
> - red_channel_pipe_item_init(mcc->base.channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_UUID);
> - memcpy(item->msg.uuid, uuid, sizeof(item->msg.uuid));
> -
> - return &item->base;
> -}
> -
> -static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num)
> -{
> - NotifyPipeItem *item = spice_malloc(sizeof(NotifyPipeItem));
> - const char *msg = data;
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_NOTIFY);
> - item->msg = spice_strdup(msg);
> - return &item->base;
> -}
> -
> -static PipeItem *main_multi_media_time_item_new(
> - RedChannelClient *rcc, void *data, int num)
> -{
> - MultiMediaTimePipeItem *item, *info = data;
> -
> - item = spice_malloc(sizeof(MultiMediaTimePipeItem));
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME);
> - item->time = info->time;
> - return &item->base;
> -}
> -
> -static void main_channel_push_channels(MainChannelClient *mcc)
> -{
> - if (red_client_during_migrate_at_target(mcc->base.client)) {
> - spice_printerr("warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS"
> - "during migration");
> - return;
> - }
> - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST);
> -}
> -
> -static void main_channel_marshall_channels(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - PipeItem *item)
> -{
> - SpiceMsgChannels* channels_info;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_CHANNELS_LIST, item);
> - channels_info = (SpiceMsgChannels *)spice_malloc(sizeof(SpiceMsgChannels)
> - + reds_num_of_channels() * sizeof(SpiceChannelId));
> - reds_fill_channels(channels_info);
> - spice_marshall_msg_main_channels_list(m, channels_info);
> - free(channels_info);
> -}
> -
> -int main_channel_client_push_ping(MainChannelClient *mcc, int size)
> -{
> - PipeItem *item;
> -
> - if (mcc == NULL) {
> - return FALSE;
> - }
> - item = main_ping_item_new(mcc, size);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> - return TRUE;
> -}
> -
> -static void main_channel_marshall_ping(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - PingPipeItem *item)
> -{
> - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> - struct timespec time_space;
> - SpiceMsgPing ping;
> - int size_left = item->size;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_PING, &item->base);
> - ping.id = ++(mcc->ping_id);
> - clock_gettime(CLOCK_MONOTONIC, &time_space);
> - ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL;
> - spice_marshall_msg_ping(m, &ping);
> -
> - while (size_left > 0) {
> - int now = MIN(ZERO_BUF_SIZE, size_left);
> - size_left -= now;
> - spice_marshaller_add_ref(m, zero_page, now);
> - }
> -}
> -
> -void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode,
> - int is_client_mouse_allowed)
> -{
> - MainMouseModeItemInfo info = {
> - .current_mode=current_mode,
> - .is_client_mouse_allowed=is_client_mouse_allowed,
> - };
> -
> - red_channel_pipes_new_add_push(&main_chan->base,
> - main_mouse_mode_item_new, &info);
> -}
> -
> -static void main_channel_marshall_mouse_mode(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - MouseModePipeItem *item)
> -{
> - SpiceMsgMainMouseMode mouse_mode;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MOUSE_MODE, &item->base);
> - mouse_mode.supported_modes = SPICE_MOUSE_MODE_SERVER;
> - if (item->is_client_mouse_allowed) {
> - mouse_mode.supported_modes |= SPICE_MOUSE_MODE_CLIENT;
> - }
> - mouse_mode.current_mode = item->current_mode;
> - spice_marshall_msg_main_mouse_mode(m, &mouse_mode);
> -}
> -
> -void main_channel_push_agent_connected(MainChannel *main_chan)
> -{
> - if (red_channel_test_remote_cap(&main_chan->base, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) {
> - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS);
> - } else {
> - red_channel_pipes_add_empty_msg(&main_chan->base, SPICE_MSG_MAIN_AGENT_CONNECTED);
> - }
> -}
> -
> -static void main_channel_marshall_agent_connected(SpiceMarshaller *m,
> - RedChannelClient *rcc,
> - PipeItem *item)
> -{
> - SpiceMsgMainAgentConnectedTokens connected;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS, item);
> - connected.num_tokens = REDS_AGENT_WINDOW_SIZE;
> - spice_marshall_msg_main_agent_connected_tokens(m, &connected);
> -}
> -
> -void main_channel_push_agent_disconnected(MainChannel *main_chan)
> -{
> - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED);
> -}
> -
> -static void main_channel_marshall_agent_disconnected(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - PipeItem *item)
> -{
> - SpiceMsgMainAgentDisconnect disconnect;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DISCONNECTED, item);
> - disconnect.error_code = SPICE_LINK_ERR_OK;
> - spice_marshall_msg_main_agent_disconnected(m, &disconnect);
> -}
> -
> -void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens)
> -{
> - PipeItem *item = main_agent_tokens_item_new(&mcc->base, num_tokens);
> -
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -static void main_channel_marshall_tokens(RedChannelClient *rcc,
> - SpiceMarshaller *m, TokensPipeItem *item)
> -{
> - SpiceMsgMainAgentTokens tokens;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_TOKEN, &item->base);
> - tokens.num_tokens = item->tokens;
> - spice_marshall_msg_main_agent_token(m, &tokens);
> -}
> -
> -void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len,
> - spice_marshaller_item_free_func free_data, void *opaque)
> -{
> - PipeItem *item;
> -
> - item = main_agent_data_item_new(&mcc->base, data, len, free_data, opaque);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -static void main_channel_marshall_agent_data(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - AgentDataPipeItem *item)
> -{
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DATA, &item->base);
> - spice_marshaller_add_ref(m, item->data, item->len);
> -}
> -
> -static void main_channel_push_migrate_data_item(MainChannel *main_chan)
> -{
> - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA);
> -}
> -
> -static void main_channel_marshall_migrate_data_item(RedChannelClient *rcc,
> - SpiceMarshaller *m, PipeItem *item)
> -{
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item);
> - reds_marshall_migrate_data(m); // TODO: from reds split. ugly separation.
> -}
> -
> -static int main_channel_handle_migrate_data(RedChannelClient *rcc,
> - uint32_t size, void *message)
> -{
> - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> - SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message;
> -
> - /* not supported with multi-clients */
> - spice_assert(rcc->channel->clients_num == 1);
> -
> - if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) {
> - spice_printerr("bad message size %u", size);
> - return FALSE;
> - }
> - if (!migration_protocol_validate_header(header,
> - SPICE_MIGRATE_DATA_MAIN_MAGIC,
> - SPICE_MIGRATE_DATA_MAIN_VERSION)) {
> - spice_error("bad header");
> - return FALSE;
> - }
> - return reds_handle_migrate_data(mcc, (SpiceMigrateDataMain *)(header + 1), size);
> -}
> -
> -void main_channel_push_init(MainChannelClient *mcc,
> - int display_channels_hint, int current_mouse_mode,
> - int is_client_mouse_allowed, int multi_media_time,
> - int ram_hint)
> -{
> - PipeItem *item;
> -
> - item = main_init_item_new(mcc,
> - mcc->connection_id, display_channels_hint, current_mouse_mode,
> - is_client_mouse_allowed, multi_media_time, ram_hint);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -static void main_channel_marshall_init(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - InitPipeItem *item)
> -{
> - SpiceMsgMainInit init; // TODO - remove this copy, make InitPipeItem reuse SpiceMsgMainInit
> -
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_INIT, &item->base);
> - init.session_id = item->connection_id;
> - init.display_channels_hint = item->display_channels_hint;
> - init.current_mouse_mode = item->current_mouse_mode;
> - init.supported_mouse_modes = SPICE_MOUSE_MODE_SERVER;
> - if (item->is_client_mouse_allowed) {
> - init.supported_mouse_modes |= SPICE_MOUSE_MODE_CLIENT;
> - }
> - init.agent_connected = reds_has_vdagent();
> - init.agent_tokens = REDS_AGENT_WINDOW_SIZE;
> - init.multi_media_time = item->multi_media_time;
> - init.ram_hint = item->ram_hint;
> - spice_marshall_msg_main_init(m, &init);
> -}
> -
> -void main_channel_push_name(MainChannelClient *mcc, const char *name)
> -{
> - PipeItem *item;
> -
> - if (!red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_NAME_AND_UUID))
> - return;
> -
> - item = main_name_item_new(mcc, name);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16])
> -{
> - PipeItem *item;
> -
> - if (!red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_NAME_AND_UUID))
> - return;
> -
> - item = main_uuid_item_new(mcc, uuid);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg)
> -{
> - PipeItem *item = main_notify_item_new(&mcc->base, (void *)msg, 1);
> - red_channel_client_pipe_add_push(&mcc->base, item);
> -}
> -
> -static void main_channel_marshall_notify(RedChannelClient *rcc,
> - SpiceMarshaller *m, NotifyPipeItem *item)
> -{
> - SpiceMsgNotify notify;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_NOTIFY, &item->base);
> - notify.time_stamp = red_get_monotonic_time(); // TODO - move to main_new_notify_item
> - notify.severity = SPICE_NOTIFY_SEVERITY_WARN;
> - notify.visibilty = SPICE_NOTIFY_VISIBILITY_HIGH;
> - notify.what = SPICE_WARN_GENERAL;
> - notify.message_len = strlen(item->msg);
> - spice_marshall_msg_notify(m, ¬ify);
> - spice_marshaller_add(m, (uint8_t *)item->msg, notify.message_len + 1);
> -}
> -
> -static void main_channel_fill_migrate_dst_info(MainChannel *main_channel,
> - SpiceMigrationDstInfo *dst_info)
> -{
> - RedsMigSpice *mig_dst = &main_channel->mig_target;
> - dst_info->port = mig_dst->port;
> - dst_info->sport = mig_dst->sport;
> - dst_info->host_size = strlen(mig_dst->host) + 1;
> - dst_info->host_data = (uint8_t *)mig_dst->host;
> - if (mig_dst->cert_subject) {
> - dst_info->cert_subject_size = strlen(mig_dst->cert_subject) + 1;
> - dst_info->cert_subject_data = (uint8_t *)mig_dst->cert_subject;
> - } else {
> - dst_info->cert_subject_size = 0;
> - dst_info->cert_subject_data = NULL;
> - }
> -}
> -
> -static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc,
> - PipeItem *item)
> -{
> - SpiceMsgMainMigrationBegin migrate;
> - MainChannel *main_ch;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN, item);
> - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> - main_channel_fill_migrate_dst_info(main_ch, &migrate.dst_info);
> - spice_marshall_msg_main_migrate_begin(m, &migrate);
> -}
> -
> -static void main_channel_marshall_migrate_begin_seamless(SpiceMarshaller *m,
> - RedChannelClient *rcc,
> - PipeItem *item)
> -{
> - SpiceMsgMainMigrateBeginSeamless migrate_seamless;
> - MainChannel *main_ch;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS, item);
> - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> - main_channel_fill_migrate_dst_info(main_ch, &migrate_seamless.dst_info);
> - migrate_seamless.src_mig_version = SPICE_MIGRATION_PROTOCOL_VERSION;
> - spice_marshall_msg_main_migrate_begin_seamless(m, &migrate_seamless);
> -}
> -
> -void main_channel_push_multi_media_time(MainChannel *main_chan, int time)
> -{
> - MultiMediaTimePipeItem info = {
> - .time = time,
> - };
> -
> - red_channel_pipes_new_add_push(&main_chan->base,
> - main_multi_media_time_item_new, &info);
> -}
> -
> -static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target)
> -{
> - spice_assert(mig_target);
> - free(main_channel->mig_target.host);
> - main_channel->mig_target.host = spice_strdup(mig_target->host);
> - free(main_channel->mig_target.cert_subject);
> - if (mig_target->cert_subject) {
> - main_channel->mig_target.cert_subject = spice_strdup(mig_target->cert_subject);
> - } else {
> - main_channel->mig_target.cert_subject = NULL;
> - }
> - main_channel->mig_target.port = mig_target->port;
> - main_channel->mig_target.sport = mig_target->sport;
> -}
> -
> -void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target)
> -{
> - main_channel_fill_mig_target(main_chan, mig_target);
> - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST);
> -}
> -
> -static void main_channel_marshall_migrate_switch(SpiceMarshaller *m, RedChannelClient *rcc,
> - PipeItem *item)
> -{
> - SpiceMsgMainMigrationSwitchHost migrate;
> - MainChannel *main_ch;
> -
> - spice_printerr("");
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, item);
> - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> - migrate.port = main_ch->mig_target.port;
> - migrate.sport = main_ch->mig_target.sport;
> - migrate.host_size = strlen(main_ch->mig_target.host) + 1;
> - migrate.host_data = (uint8_t *)main_ch->mig_target.host;
> - if (main_ch->mig_target.cert_subject) {
> - migrate.cert_subject_size = strlen(main_ch->mig_target.cert_subject) + 1;
> - migrate.cert_subject_data = (uint8_t *)main_ch->mig_target.cert_subject;
> - } else {
> - migrate.cert_subject_size = 0;
> - migrate.cert_subject_data = NULL;
> - }
> - spice_marshall_msg_main_migrate_switch_host(m, &migrate);
> -}
> -
> -static void main_channel_marshall_multi_media_time(RedChannelClient *rcc,
> - SpiceMarshaller *m,
> - MultiMediaTimePipeItem *item)
> -{
> - SpiceMsgMainMultiMediaTime time_mes;
> -
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &item->base);
> - time_mes.time = item->time;
> - spice_marshall_msg_main_multi_media_time(m, &time_mes);
> -}
> -
> -static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base)
> -{
> - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> - SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
> -
> - /* In semi-seamless migration (dest side), the connection is started from scratch, and
> - * we ignore any pipe item that arrives before the INIT msg is sent.
> - * For seamless we don't send INIT, and the connection continues from the same place
> - * it stopped on the src side. */
> - if (!mcc->init_sent && !mcc->seamless_mig_dst && base->type != PIPE_ITEM_TYPE_MAIN_INIT) {
> - spice_printerr("Init msg for client %p was not sent yet "
> - "(client is probably during semi-seamless migration). Ignoring msg type %d",
> - rcc->client, base->type);
> - main_channel_release_pipe_item(rcc, base, FALSE);
> - return;
> - }
> - switch (base->type) {
> - case PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST:
> - main_channel_marshall_channels(rcc, m, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_PING:
> - main_channel_marshall_ping(rcc, m,
> - SPICE_CONTAINEROF(base, PingPipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MOUSE_MODE:
> - {
> - MouseModePipeItem *item =
> - SPICE_CONTAINEROF(base, MouseModePipeItem, base);
> - main_channel_marshall_mouse_mode(rcc, m, item);
> - break;
> - }
> - case PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED:
> - main_channel_marshall_agent_disconnected(rcc, m, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN:
> - main_channel_marshall_tokens(rcc, m,
> - SPICE_CONTAINEROF(base, TokensPipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_AGENT_DATA:
> - main_channel_marshall_agent_data(rcc, m,
> - SPICE_CONTAINEROF(base, AgentDataPipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA:
> - main_channel_marshall_migrate_data_item(rcc, m, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_INIT:
> - mcc->init_sent = TRUE;
> - main_channel_marshall_init(rcc, m,
> - SPICE_CONTAINEROF(base, InitPipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_NOTIFY:
> - main_channel_marshall_notify(rcc, m,
> - SPICE_CONTAINEROF(base, NotifyPipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN:
> - main_channel_marshall_migrate_begin(m, rcc, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS:
> - main_channel_marshall_migrate_begin_seamless(m, rcc, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME:
> - main_channel_marshall_multi_media_time(rcc, m,
> - SPICE_CONTAINEROF(base, MultiMediaTimePipeItem, base));
> - break;
> - case PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST:
> - main_channel_marshall_migrate_switch(m, rcc, base);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_NAME:
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_NAME, base);
> - spice_marshall_msg_main_name(m, &SPICE_CONTAINEROF(base, NamePipeItem, base)->msg);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_UUID:
> - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_UUID, base);
> - spice_marshall_msg_main_uuid(m, &SPICE_CONTAINEROF(base, UuidPipeItem, base)->msg);
> - break;
> - case PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS:
> - main_channel_marshall_agent_connected(m, rcc, base);
> - break;
> - default:
> - break;
> - };
> - red_channel_client_begin_send_message(rcc);
> -}
> -
> -static void main_channel_release_pipe_item(RedChannelClient *rcc,
> - PipeItem *base, int item_pushed)
> -{
> - switch (base->type) {
> - case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: {
> - AgentDataPipeItem *data = (AgentDataPipeItem *)base;
> -
> - data->free_data(data->data, data->opaque);
> - break;
> - }
> - case PIPE_ITEM_TYPE_MAIN_NOTIFY: {
> - NotifyPipeItem *data = (NotifyPipeItem *)base;
> - free(data->msg);
> - break;
> - }
> - default:
> - break;
> - }
> - free(base);
> -}
> -
> -static void main_channel_client_handle_migrate_connected(MainChannelClient *mcc,
> - int success,
> - int seamless)
> -{
> - spice_printerr("client %p connected: %d seamless %d", mcc->base.client, success, seamless);
> - if (mcc->mig_wait_connect) {
> - MainChannel *main_channel = SPICE_CONTAINEROF(mcc->base.channel, MainChannel, base);
> -
> - mcc->mig_wait_connect = FALSE;
> - mcc->mig_connect_ok = success;
> - spice_assert(main_channel->num_clients_mig_wait);
> - spice_assert(!seamless || main_channel->num_clients_mig_wait == 1);
> - if (!--main_channel->num_clients_mig_wait) {
> - reds_on_main_migrate_connected(seamless && success);
> - }
> - } else {
> - if (success) {
> - spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client);
> - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL);
> - }
> - }
> -}
> -
> -void main_channel_client_handle_migrate_dst_do_seamless(MainChannelClient *mcc,
> - uint32_t src_version)
> -{
> - if (reds_on_migrate_dst_set_seamless(mcc, src_version)) {
> - mcc->seamless_mig_dst = TRUE;
> - red_channel_client_pipe_add_empty_msg(&mcc->base,
> - SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK);
> - } else {
> - red_channel_client_pipe_add_empty_msg(&mcc->base,
> - SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK);
> - }
> -}
> -
> -void main_channel_client_handle_migrate_end(MainChannelClient *mcc)
> -{
> - if (!red_client_during_migrate_at_target(mcc->base.client)) {
> - spice_printerr("unexpected SPICE_MSGC_MIGRATE_END");
> - return;
> - }
> - if (!red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) {
> - spice_printerr("unexpected SPICE_MSGC_MIGRATE_END, "
> - "client does not support semi-seamless migration");
> - return;
> - }
> - red_client_semi_seamless_migrate_complete(mcc->base.client);
> -}
> -
> -void main_channel_migrate_dst_complete(MainChannelClient *mcc)
> -{
> - if (mcc->mig_wait_prev_complete) {
> - if (mcc->mig_wait_prev_try_seamless) {
> - spice_assert(mcc->base.channel->clients_num == 1);
> - red_channel_client_pipe_add_type(&mcc->base,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS);
> - } else {
> - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN);
> - }
> - mcc->mig_wait_connect = TRUE;
> - mcc->mig_wait_prev_complete = FALSE;
> - }
> -}
> -
> -static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type,
> - void *message)
> -{
> - MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> -
> - switch (type) {
> - case SPICE_MSGC_MAIN_AGENT_START: {
> - SpiceMsgcMainAgentStart *tokens;
> -
> - spice_printerr("agent start");
> - if (!main_chan) {
> - return FALSE;
> - }
> - tokens = (SpiceMsgcMainAgentStart *)message;
> - reds_on_main_agent_start(mcc, tokens->num_tokens);
> - break;
> - }
> - case SPICE_MSGC_MAIN_AGENT_DATA: {
> - reds_on_main_agent_data(mcc, message, size);
> - break;
> - }
> - case SPICE_MSGC_MAIN_AGENT_TOKEN: {
> - SpiceMsgcMainAgentTokens *tokens;
> -
> - tokens = (SpiceMsgcMainAgentTokens *)message;
> - reds_on_main_agent_tokens(mcc, tokens->num_tokens);
> - break;
> - }
> - case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
> - main_channel_push_channels(mcc);
> - break;
> - case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
> - main_channel_client_handle_migrate_connected(mcc,
> - TRUE /* success */,
> - FALSE /* seamless */);
> - break;
> - case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS:
> - main_channel_client_handle_migrate_connected(mcc,
> - TRUE /* success */,
> - TRUE /* seamless */);
> - break;
> - case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
> - main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE);
> - break;
> - case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS:
> - main_channel_client_handle_migrate_dst_do_seamless(mcc,
> - ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version);
> - break;
> - case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST:
> - reds_on_main_mouse_mode_request(message, size);
> - break;
> - case SPICE_MSGC_PONG: {
> - SpiceMsgPing *ping = (SpiceMsgPing *)message;
> - uint64_t roundtrip;
> - struct timespec ts;
> -
> - clock_gettime(CLOCK_MONOTONIC, &ts);
> - roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp;
> -
> - if (ping->id == mcc->net_test_id) {
> - switch (mcc->net_test_stage) {
> - case NET_TEST_STAGE_WARMUP:
> - mcc->net_test_id++;
> - mcc->net_test_stage = NET_TEST_STAGE_LATENCY;
> - mcc->latency = roundtrip;
> - break;
> - case NET_TEST_STAGE_LATENCY:
> - mcc->net_test_id++;
> - mcc->net_test_stage = NET_TEST_STAGE_RATE;
> - mcc->latency = MIN(mcc->latency, roundtrip);
> - break;
> - case NET_TEST_STAGE_RATE:
> - mcc->net_test_id = 0;
> - if (roundtrip <= mcc->latency) {
> - // probably high load on client or server result with incorrect values
> - spice_printerr("net test: invalid values, latency %" PRIu64
> - " roundtrip %" PRIu64 ". assuming high"
> - " bandwidth", mcc->latency, roundtrip);
> - mcc->latency = 0;
> - mcc->net_test_stage = NET_TEST_STAGE_INVALID;
> - red_channel_client_start_connectivity_monitoring(&mcc->base,
> - CLIENT_CONNECTIVITY_TIMEOUT);
> - break;
> - }
> - mcc->bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000
> - / (roundtrip - mcc->latency);
> - mcc->net_test_stage = NET_TEST_STAGE_COMPLETE;
> - spice_printerr("net test: latency %f ms, bitrate %"PRIu64" bps (%f Mbps)%s",
> - (double)mcc->latency / 1000,
> - mcc->bitrate_per_sec,
> - (double)mcc->bitrate_per_sec / 1024 / 1024,
> - main_channel_client_is_low_bandwidth(mcc) ? " LOW BANDWIDTH" : "");
> - red_channel_client_start_connectivity_monitoring(&mcc->base,
> - CLIENT_CONNECTIVITY_TIMEOUT);
> - break;
> - default:
> - spice_printerr("invalid net test stage, ping id %d test id %d stage %d",
> - ping->id,
> - mcc->net_test_id,
> - mcc->net_test_stage);
> - mcc->net_test_stage = NET_TEST_STAGE_INVALID;
> - }
> - break;
> - } else {
> - /*
> - * channel client monitors the connectivity using ping-pong messages
> - */
> - red_channel_client_handle_message(rcc, size, type, message);
> - }
> -#ifdef RED_STATISTICS
> - reds_update_stat_value(roundtrip);
> -#endif
> - break;
> - }
> - case SPICE_MSGC_DISCONNECTING:
> - break;
> - case SPICE_MSGC_MAIN_MIGRATE_END:
> - main_channel_client_handle_migrate_end(mcc);
> - break;
> - default:
> - return red_channel_client_handle_message(rcc, size, type, message);
> - }
> - return TRUE;
> -}
> -
> -static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> - uint16_t type,
> - uint32_t size)
> -{
> - MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base);
> -
> - if (type == SPICE_MSGC_MAIN_AGENT_DATA) {
> - return reds_get_agent_data_buffer(mcc, size);
> - } else {
> - return main_chan->recv_buf;
> - }
> -}
> -
> -static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> - uint16_t type,
> - uint32_t size,
> - uint8_t *msg)
> -{
> - if (type == SPICE_MSGC_MAIN_AGENT_DATA) {
> - reds_release_agent_data_buffer(msg);
> - }
> -}
> -
> -static int main_channel_config_socket(RedChannelClient *rcc)
> -{
> - return TRUE;
> -}
> -
> -static void main_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
> -{
> -}
> -
> -static int main_channel_handle_migrate_flush_mark(RedChannelClient *rcc)
> -{
> - spice_debug(NULL);
> - main_channel_push_migrate_data_item(SPICE_CONTAINEROF(rcc->channel,
> - MainChannel, base));
> - return TRUE;
> -}
> -
> -#ifdef RED_STATISTICS
> -static void do_ping_client(MainChannelClient *mcc,
> - const char *opt, int has_interval, int interval)
> -{
> - spice_printerr("");
> - if (!opt) {
> - main_channel_client_push_ping(mcc, 0);
> - } else if (!strcmp(opt, "on")) {
> - if (has_interval && interval > 0) {
> - mcc->ping_interval = interval * 1000;
> - }
> - core->timer_start(mcc->ping_timer, mcc->ping_interval);
> - } else if (!strcmp(opt, "off")) {
> - core->timer_cancel(mcc->ping_timer);
> - } else {
> - return;
> - }
> -}
> -
> -static void ping_timer_cb(void *opaque)
> -{
> - MainChannelClient *mcc = opaque;
> -
> - if (!red_channel_client_is_connected(&mcc->base)) {
> - spice_printerr("not connected to peer, ping off");
> - core->timer_cancel(mcc->ping_timer);
> - return;
> - }
> - do_ping_client(mcc, NULL, 0, 0);
> - core->timer_start(mcc->ping_timer, mcc->ping_interval);
> -}
> -#endif /* RED_STATISTICS */
> -
> -static MainChannelClient *main_channel_client_create(MainChannel *main_chan, RedClient *client,
> - RedsStream *stream, uint32_t connection_id,
> - int num_common_caps, uint32_t *common_caps,
> - int num_caps, uint32_t *caps)
> -{
> - MainChannelClient *mcc = (MainChannelClient*)
> - red_channel_client_create(sizeof(MainChannelClient), &main_chan->base,
> - client, stream, FALSE, num_common_caps,
> - common_caps, num_caps, caps);
> - spice_assert(mcc != NULL);
> - mcc->connection_id = connection_id;
> - mcc->bitrate_per_sec = ~0;
> -#ifdef RED_STATISTICS
> - if (!(mcc->ping_timer = core->timer_add(ping_timer_cb, NULL))) {
> - spice_error("ping timer create failed");
> - }
> - mcc->ping_interval = PING_INTERVAL;
> -#endif
> - return mcc;
> -}
> -
> -MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client,
> - RedsStream *stream, uint32_t connection_id, int migration,
> - int num_common_caps, uint32_t *common_caps, int num_caps,
> - uint32_t *caps)
> -{
> - MainChannelClient *mcc;
> -
> - spice_assert(channel);
> -
> - // TODO - migration - I removed it from channel creation, now put it
> - // into usage somewhere (not an issue until we return migration to it's
> - // former glory)
> - spice_printerr("add main channel client");
> - mcc = main_channel_client_create(channel, client, stream, connection_id,
> - num_common_caps, common_caps,
> - num_caps, caps);
> - return mcc;
> -}
> -
> -int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen)
> -{
> - return main_chan ? getsockname(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1;
> -}
> -
> -int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen)
> -{
> - return main_chan ? getpeername(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1;
> -}
> -
> -// TODO: ? shouldn't it disonnect all clients? or shutdown all main_channels?
> -void main_channel_close(MainChannel *main_chan)
> -{
> - int socketfd;
> -
> - if (main_chan && (socketfd = red_channel_get_first_socket(&main_chan->base)) != -1) {
> - close(socketfd);
> - }
> -}
> -
> -int main_channel_client_is_network_info_initialized(MainChannelClient *mcc)
> -{
> - return mcc->net_test_stage == NET_TEST_STAGE_COMPLETE;
> -}
> -
> -int main_channel_client_is_low_bandwidth(MainChannelClient *mcc)
> -{
> - // TODO: configurable?
> - return mcc->bitrate_per_sec < 10 * 1024 * 1024;
> -}
> -
> -uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc)
> -{
> - return mcc->bitrate_per_sec;
> -}
> -
> -uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc)
> -{
> - return mcc->latency / 1000;
> -}
> -
> -static void main_channel_client_migrate(RedChannelClient *rcc)
> -{
> - reds_on_main_channel_migrate(SPICE_CONTAINEROF(rcc, MainChannelClient, base));
> - red_channel_client_default_migrate(rcc);
> -}
> -
> -MainChannel* main_channel_init(void)
> -{
> - RedChannel *channel;
> - ChannelCbs channel_cbs = { NULL, };
> - ClientCbs client_cbs = {NULL, };
> -
> - channel_cbs.config_socket = main_channel_config_socket;
> - channel_cbs.on_disconnect = main_channel_client_on_disconnect;
> - channel_cbs.send_item = main_channel_send_item;
> - channel_cbs.hold_item = main_channel_hold_pipe_item;
> - channel_cbs.release_item = main_channel_release_pipe_item;
> - channel_cbs.alloc_recv_buf = main_channel_alloc_msg_rcv_buf;
> - channel_cbs.release_recv_buf = main_channel_release_msg_rcv_buf;
> - channel_cbs.handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark;
> - channel_cbs.handle_migrate_data = main_channel_handle_migrate_data;
> -
> - // TODO: set the migration flag of the channel
> - channel = red_channel_create_parser(sizeof(MainChannel), core,
> - SPICE_CHANNEL_MAIN, 0,
> - FALSE, /* handle_acks */
> - spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL),
> - main_channel_handle_parsed,
> - &channel_cbs,
> - SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER);
> - spice_assert(channel);
> - red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
> - red_channel_set_cap(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
> -
> - client_cbs.migrate = main_channel_client_migrate;
> - red_channel_register_client_cbs(channel, &client_cbs);
> -
> - return (MainChannel *)channel;
> -}
> -
> -RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc)
> -{
> - spice_assert(mcc);
> - return &mcc->base;
> -}
> -
> -static int main_channel_connect_semi_seamless(MainChannel *main_channel)
> -{
> - RingItem *client_link;
> -
> - RING_FOREACH(client_link, &main_channel->base.clients) {
> - MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient,
> - base.channel_link);
> - if (red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) {
> - if (red_client_during_migrate_at_target(mcc->base.client)) {
> - spice_printerr("client %p: wait till previous migration completes", mcc->base.client);
> - mcc->mig_wait_prev_complete = TRUE;
> - mcc->mig_wait_prev_try_seamless = FALSE;
> - } else {
> - red_channel_client_pipe_add_type(&mcc->base,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN);
> - mcc->mig_wait_connect = TRUE;
> - }
> - mcc->mig_connect_ok = FALSE;
> - main_channel->num_clients_mig_wait++;
> - }
> - }
> - return main_channel->num_clients_mig_wait;
> -}
> -
> -static int main_channel_connect_seamless(MainChannel *main_channel)
> -{
> - RingItem *client_link;
> -
> - spice_assert(main_channel->base.clients_num == 1);
> -
> - RING_FOREACH(client_link, &main_channel->base.clients) {
> - MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient,
> - base.channel_link);
> - spice_assert(red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_SEAMLESS_MIGRATE));
> - if (red_client_during_migrate_at_target(mcc->base.client)) {
> - spice_printerr("client %p: wait till previous migration completes", mcc->base.client);
> - mcc->mig_wait_prev_complete = TRUE;
> - mcc->mig_wait_prev_try_seamless = TRUE;
> - } else {
> - red_channel_client_pipe_add_type(&mcc->base,
> - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS);
> - mcc->mig_wait_connect = TRUE;
> - }
> - mcc->mig_connect_ok = FALSE;
> - main_channel->num_clients_mig_wait++;
> - }
> - return main_channel->num_clients_mig_wait;
> -}
> -
> -int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target,
> - int try_seamless)
> -{
> - main_channel_fill_mig_target(main_channel, mig_target);
> - main_channel->num_clients_mig_wait = 0;
> -
> - if (!main_channel_is_connected(main_channel)) {
> - return 0;
> - }
> -
> - if (!try_seamless) {
> - return main_channel_connect_semi_seamless(main_channel);
> - } else {
> - RingItem *client_item;
> - MainChannelClient *mcc;
> -
> - client_item = ring_get_head(&main_channel->base.clients);
> - mcc = SPICE_CONTAINEROF(client_item, MainChannelClient, base.channel_link);
> -
> - if (!red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
> - return main_channel_connect_semi_seamless(main_channel);
> - } else {
> - return main_channel_connect_seamless(main_channel);
> - }
> - }
> -
> -}
> -
> -void main_channel_migrate_cancel_wait(MainChannel *main_chan)
> -{
> - RingItem *client_link;
> -
> - RING_FOREACH(client_link, &main_chan->base.clients) {
> - MainChannelClient *mcc;
> -
> - mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> - if (mcc->mig_wait_connect) {
> - spice_printerr("client %p cancel wait connect", mcc->base.client);
> - mcc->mig_wait_connect = FALSE;
> - mcc->mig_connect_ok = FALSE;
> - }
> - mcc->mig_wait_prev_complete = FALSE;
> - }
> - main_chan->num_clients_mig_wait = 0;
> -}
> -
> -int main_channel_migrate_src_complete(MainChannel *main_chan, int success)
> -{
> - RingItem *client_link;
> - int semi_seamless_count = 0;
> -
> - spice_printerr("");
> -
> - if (ring_is_empty(&main_chan->base.clients)) {
> - spice_printerr("no peer connected");
> - return 0;
> - }
> -
> - RING_FOREACH(client_link, &main_chan->base.clients) {
> - MainChannelClient *mcc;
> - int semi_seamless_support;
> -
> - mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> - semi_seamless_support = red_channel_client_test_remote_cap(&mcc->base,
> - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
> - if (semi_seamless_support && mcc->mig_connect_ok) {
> - if (success) {
> - spice_printerr("client %p MIGRATE_END", mcc->base.client);
> - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_END);
> - semi_seamless_count++;
> - } else {
> - spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client);
> - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL);
> - }
> - } else {
> - if (success) {
> - spice_printerr("client %p SWITCH_HOST", mcc->base.client);
> - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST);
> - }
> - }
> - mcc->mig_connect_ok = FALSE;
> - mcc->mig_wait_connect = FALSE;
> - }
> - return semi_seamless_count;
> -}
> diff --git a/server/main_channel.h b/server/main_channel.h
> deleted file mode 100644
> index 9bd20f1..0000000
> --- a/server/main_channel.h
> +++ /dev/null
> @@ -1,103 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef __MAIN_CHANNEL_H__
> -#define __MAIN_CHANNEL_H__
> -
> -#include <stdint.h>
> -#include <spice/vd_agent.h>
> -#include "common/marshaller.h"
> -#include "red_channel.h"
> -
> -// TODO: Defines used to calculate receive buffer size, and also by reds.c
> -// other options: is to make a reds_main_consts.h, to duplicate defines.
> -#define REDS_AGENT_WINDOW_SIZE 10
> -#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
> -
> -// approximate max receive message size for main channel
> -#define MAIN_CHANNEL_RECEIVE_BUF_SIZE \
> - (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE)
> -
> -struct RedsMigSpice {
> - char *host;
> - char *cert_subject;
> - int port;
> - int sport;
> -};
> -typedef struct RedsMigSpice RedsMigSpice;
> -
> -typedef struct MainChannel {
> - RedChannel base;
> - uint8_t recv_buf[MAIN_CHANNEL_RECEIVE_BUF_SIZE];
> - RedsMigSpice mig_target; // TODO: add refs and release (afrer all clients completed migration in one way or the other?)
> - int num_clients_mig_wait;
> -} MainChannel;
> -
> -
> -MainChannel *main_channel_init(void);
> -RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t link_id);
> -/* This is a 'clone' from the reds.h Channel.link callback to allow passing link_id */
> -MainChannelClient *main_channel_link(MainChannel *, RedClient *client,
> - RedsStream *stream, uint32_t link_id, int migration, int num_common_caps,
> - uint32_t *common_caps, int num_caps, uint32_t *caps);
> -void main_channel_close(MainChannel *main_chan); // not destroy, just socket close
> -void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, int is_client_mouse_allowed);
> -void main_channel_push_agent_connected(MainChannel *main_chan);
> -void main_channel_push_agent_disconnected(MainChannel *main_chan);
> -void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens);
> -void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len,
> - spice_marshaller_item_free_func free_data, void *opaque);
> -void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate);
> -// TODO: huge. Consider making a reds_* interface for these functions
> -// and calling from main.
> -void main_channel_push_init(MainChannelClient *mcc, int display_channels_hint,
> - int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time,
> - int ram_hint);
> -void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg);
> -void main_channel_push_multi_media_time(MainChannel *main_chan, int time);
> -int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen);
> -int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen);
> -
> -/*
> - * return TRUE if network test had been completed successfully.
> - * If FALSE, bitrate_per_sec is set to MAX_UINT64 and the roundtrip is set to 0
> - */
> -int main_channel_client_is_network_info_initialized(MainChannelClient *mcc);
> -int main_channel_client_is_low_bandwidth(MainChannelClient *mcc);
> -uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc);
> -uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc);
> -
> -int main_channel_is_connected(MainChannel *main_chan);
> -RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc);
> -
> -/* switch host migration */
> -void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target);
> -
> -/* semi seamless migration */
> -
> -/* returns the number of clients that we are waiting for their connection.
> - * try_seamless = 'true' when the seamless-migration=on in qemu command line */
> -int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target,
> - int try_seamless);
> -void main_channel_migrate_cancel_wait(MainChannel *main_chan);
> -/* returns the number of clients for which SPICE_MSG_MAIN_MIGRATE_END was sent*/
> -int main_channel_migrate_src_complete(MainChannel *main_chan, int success);
> -void main_channel_migrate_dst_complete(MainChannelClient *mcc);
> -void main_channel_push_name(MainChannelClient *mcc, const char *name);
> -void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]);
> -
> -#endif
> diff --git a/server/main_dispatcher.c b/server/main_dispatcher.c
> deleted file mode 100644
> index 6ad9d89..0000000
> --- a/server/main_dispatcher.c
> +++ /dev/null
> @@ -1,217 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009-2015 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/>.
> -*/
> -#include <config.h>
> -#include <unistd.h>
> -#include <string.h>
> -#include <errno.h>
> -#include <pthread.h>
> -
> -#include "red_common.h"
> -#include "dispatcher.h"
> -#include "main_dispatcher.h"
> -#include "red_channel.h"
> -#include "reds.h"
> -
> -/*
> - * Main Dispatcher
> - * ===============
> - *
> - * Communication channel between any non main thread and the main thread.
> - *
> - * The main thread is that from which spice_server_init is called.
> - *
> - * Messages are single sized, sent from the non-main thread to the main-thread.
> - * No acknowledge is sent back. This prevents a possible deadlock with the main
> - * thread already waiting on a response for the existing red_dispatcher used
> - * by the worker thread.
> - *
> - * All events have three functions:
> - * main_dispatcher_<event_name> - non static, public function
> - * main_dispatcher_self_<event_name> - handler for main thread
> - * main_dispatcher_handle_<event_name> - handler for callback from main thread
> - * seperate from self because it may send an ack or do other work in the future.
> - */
> -
> -typedef struct {
> - Dispatcher base;
> - SpiceCoreInterface *core;
> -} MainDispatcher;
> -
> -MainDispatcher main_dispatcher;
> -
> -enum {
> - MAIN_DISPATCHER_CHANNEL_EVENT = 0,
> - MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> - MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> - MAIN_DISPATCHER_CLIENT_DISCONNECT,
> -
> - MAIN_DISPATCHER_NUM_MESSAGES
> -};
> -
> -typedef struct MainDispatcherChannelEventMessage {
> - int event;
> - SpiceChannelEventInfo *info;
> -} MainDispatcherChannelEventMessage;
> -
> -typedef struct MainDispatcherMigrateSeamlessDstCompleteMessage {
> - RedClient *client;
> -} MainDispatcherMigrateSeamlessDstCompleteMessage;
> -
> -typedef struct MainDispatcherMmTimeLatencyMessage {
> - RedClient *client;
> - uint32_t latency;
> -} MainDispatcherMmTimeLatencyMessage;
> -
> -typedef struct MainDispatcherClientDisconnectMessage {
> - RedClient *client;
> -} MainDispatcherClientDisconnectMessage;
> -
> -/* channel_event - calls core->channel_event, must be done in main thread */
> -static void main_dispatcher_self_handle_channel_event(
> - int event,
> - SpiceChannelEventInfo *info)
> -{
> - reds_handle_channel_event(event, info);
> -}
> -
> -static void main_dispatcher_handle_channel_event(void *opaque,
> - void *payload)
> -{
> - MainDispatcherChannelEventMessage *channel_event = payload;
> -
> - main_dispatcher_self_handle_channel_event(channel_event->event,
> - channel_event->info);
> -}
> -
> -void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info)
> -{
> - MainDispatcherChannelEventMessage msg = {0,};
> -
> - if (pthread_self() == main_dispatcher.base.self) {
> - main_dispatcher_self_handle_channel_event(event, info);
> - return;
> - }
> - msg.event = event;
> - msg.info = info;
> - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT,
> - &msg);
> -}
> -
> -
> -static void main_dispatcher_handle_migrate_complete(void *opaque,
> - void *payload)
> -{
> - MainDispatcherMigrateSeamlessDstCompleteMessage *mig_complete = payload;
> -
> - reds_on_client_seamless_migrate_complete(mig_complete->client);
> - red_client_unref(mig_complete->client);
> -}
> -
> -static void main_dispatcher_handle_mm_time_latency(void *opaque,
> - void *payload)
> -{
> - MainDispatcherMmTimeLatencyMessage *msg = payload;
> - reds_set_client_mm_time_latency(msg->client, msg->latency);
> - red_client_unref(msg->client);
> -}
> -
> -static void main_dispatcher_handle_client_disconnect(void *opaque,
> - void *payload)
> -{
> - MainDispatcherClientDisconnectMessage *msg = payload;
> -
> - spice_debug("client=%p", msg->client);
> - reds_client_disconnect(msg->client);
> - red_client_unref(msg->client);
> -}
> -
> -void main_dispatcher_seamless_migrate_dst_complete(RedClient *client)
> -{
> - MainDispatcherMigrateSeamlessDstCompleteMessage msg;
> -
> - if (pthread_self() == main_dispatcher.base.self) {
> - reds_on_client_seamless_migrate_complete(client);
> - return;
> - }
> -
> - msg.client = red_client_ref(client);
> - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> - &msg);
> -}
> -
> -void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency)
> -{
> - MainDispatcherMmTimeLatencyMessage msg;
> -
> - if (pthread_self() == main_dispatcher.base.self) {
> - reds_set_client_mm_time_latency(client, latency);
> - return;
> - }
> -
> - msg.client = red_client_ref(client);
> - msg.latency = latency;
> - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> - &msg);
> -}
> -
> -void main_dispatcher_client_disconnect(RedClient *client)
> -{
> - MainDispatcherClientDisconnectMessage msg;
> -
> - if (!client->disconnecting) {
> - spice_debug("client %p", client);
> - msg.client = red_client_ref(client);
> - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT,
> - &msg);
> - } else {
> - spice_debug("client %p already during disconnection", client);
> - }
> -}
> -
> -static void dispatcher_handle_read(int fd, int event, void *opaque)
> -{
> - Dispatcher *dispatcher = opaque;
> -
> - dispatcher_handle_recv_read(dispatcher);
> -}
> -
> -/*
> - * FIXME:
> - * Reds routines shouldn't be exposed. Instead reds.c should register the callbacks,
> - * and the corresponding operations should be made only via main_dispatcher.
> - */
> -void main_dispatcher_init(SpiceCoreInterface *core)
> -{
> - memset(&main_dispatcher, 0, sizeof(main_dispatcher));
> - main_dispatcher.core = core;
> - dispatcher_init(&main_dispatcher.base, MAIN_DISPATCHER_NUM_MESSAGES, &main_dispatcher.base);
> - core->watch_add(main_dispatcher.base.recv_fd, SPICE_WATCH_EVENT_READ,
> - dispatcher_handle_read, &main_dispatcher.base);
> - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT,
> - main_dispatcher_handle_channel_event,
> - sizeof(MainDispatcherChannelEventMessage), 0 /* no ack */);
> - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE,
> - main_dispatcher_handle_migrate_complete,
> - sizeof(MainDispatcherMigrateSeamlessDstCompleteMessage), 0 /* no ack */);
> - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY,
> - main_dispatcher_handle_mm_time_latency,
> - sizeof(MainDispatcherMmTimeLatencyMessage), 0 /* no ack */);
> - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT,
> - main_dispatcher_handle_client_disconnect,
> - sizeof(MainDispatcherClientDisconnectMessage), 0 /* no ack */);
> -}
> diff --git a/server/main_dispatcher.h b/server/main_dispatcher.h
> deleted file mode 100644
> index af40093..0000000
> --- a/server/main_dispatcher.h
> +++ /dev/null
> @@ -1,36 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009-2015 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/>.
> -*/
> -#ifndef MAIN_DISPATCHER_H
> -#define MAIN_DISPATCHER_H
> -
> -#include <spice.h>
> -#include "red_channel.h"
> -
> -void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info);
> -void main_dispatcher_seamless_migrate_dst_complete(RedClient *client);
> -void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency);
> -/*
> - * Disconnecting the client is always executed asynchronously,
> - * in order to protect from expired references in the routines
> - * that triggered the client destruction.
> - */
> -void main_dispatcher_client_disconnect(RedClient *client);
> -
> -void main_dispatcher_init(SpiceCoreInterface *core);
> -
> -#endif //MAIN_DISPATCHER_H
> diff --git a/server/memslot.c b/server/memslot.c
> new file mode 100644
> index 0000000..e7ee04c
> --- /dev/null
> +++ b/server/memslot.c
> @@ -0,0 +1,184 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009,2010 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 <inttypes.h>
> +
> +#include "red_common.h"
> +#include "memslot.h"
> +
> +static unsigned long __get_clean_virt(RedMemSlotInfo *info, QXLPHYSICAL addr)
> +{
> + return addr & info->memslot_clean_virt_mask;
> +}
> +
> +static void print_memslots(RedMemSlotInfo *info)
> +{
> + int i;
> + int x;
> +
> + for (i = 0; i < info->num_memslots_groups; ++i) {
> + for (x = 0; x < info->num_memslots; ++x) {
> + if (!info->mem_slots[i][x].virt_start_addr &&
> + !info->mem_slots[i][x].virt_end_addr) {
> + continue;
> + }
> + printf("id %d, group %d, virt start %lx, virt end %lx, generation %u, delta %lx\n",
> + x, i, info->mem_slots[i][x].virt_start_addr,
> + info->mem_slots[i][x].virt_end_addr, info->mem_slots[i][x].generation,
> + info->mem_slots[i][x].address_delta);
> + }
> + }
> +}
> +
> +/* return 1 if validation successfull, 0 otherwise */
> +int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id,
> + uint32_t add_size, uint32_t group_id)
> +{
> + MemSlot *slot;
> +
> + slot = &info->mem_slots[group_id][slot_id];
> + if ((virt + add_size) < virt) {
> + spice_critical("virtual address overlap");
> + return 0;
> + }
> +
> + if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) {
> + print_memslots(info);
> + spice_critical("virtual address out of range\n"
> + " virt=0x%lx+0x%x slot_id=%d group_id=%d\n"
> + " slot=0x%lx-0x%lx delta=0x%lx",
> + virt, add_size, slot_id, group_id,
> + slot->virt_start_addr, slot->virt_end_addr, slot->address_delta);
> + return 0;
> + }
> + return 1;
> +}
> +
> +/*
> + * return virtual address if successful, which may be 0.
> + * returns 0 and sets error to 1 if an error condition occurs.
> + */
> +unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size,
> + int group_id, int *error)
> +{
> + int slot_id;
> + int generation;
> + unsigned long h_virt;
> +
> + MemSlot *slot;
> +
> + *error = 0;
> + if (group_id > info->num_memslots_groups) {
> + spice_critical("group_id too big");
> + *error = 1;
> + return 0;
> + }
> +
> + slot_id = memslot_get_id(info, addr);
> + if (slot_id > info->num_memslots) {
> + print_memslots(info);
> + spice_critical("slot_id %d too big, addr=%" PRIx64, slot_id, addr);
> + *error = 1;
> + return 0;
> + }
> +
> + slot = &info->mem_slots[group_id][slot_id];
> +
> + generation = memslot_get_generation(info, addr);
> + if (generation != slot->generation) {
> + print_memslots(info);
> + spice_critical("address generation is not valid, group_id %d, slot_id %d, gen %d, slot_gen %d\n",
> + group_id, slot_id, generation, slot->generation);
> + *error = 1;
> + return 0;
> + }
> +
> + h_virt = __get_clean_virt(info, addr);
> + h_virt += slot->address_delta;
> +
> + if (!memslot_validate_virt(info, h_virt, slot_id, add_size, group_id)) {
> + *error = 1;
> + return 0;
> + }
> +
> + return h_virt;
> +}
> +
> +void memslot_info_init(RedMemSlotInfo *info,
> + uint32_t num_groups, uint32_t num_slots,
> + uint8_t generation_bits,
> + uint8_t id_bits,
> + uint8_t internal_groupslot_id)
> +{
> + uint32_t i;
> +
> + spice_return_if_fail(num_slots > 0);
> + spice_return_if_fail(num_groups > 0);
> +
> + info->num_memslots_groups = num_groups;
> + info->num_memslots = num_slots;
> + info->generation_bits = generation_bits;
> + info->mem_slot_bits = id_bits;
> + info->internal_groupslot_id = internal_groupslot_id;
> +
> + info->mem_slots = spice_new(MemSlot *, num_groups);
> +
> + for (i = 0; i < num_groups; ++i) {
> + info->mem_slots[i] = spice_new0(MemSlot, num_slots);
> + }
> +
> + /* TODO: use QXLPHYSICAL_BITS */
> + info->memslot_id_shift = 64 - info->mem_slot_bits;
> + info->memslot_gen_shift = 64 - (info->mem_slot_bits + info->generation_bits);
> + info->memslot_gen_mask = ~((QXLPHYSICAL)-1 << info->generation_bits);
> + info->memslot_clean_virt_mask = (((QXLPHYSICAL)(-1)) >>
> + (info->mem_slot_bits + info->generation_bits));
> +}
> +
> +void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id,
> + uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end,
> + uint32_t generation)
> +{
> + spice_return_if_fail(info->num_memslots_groups > slot_group_id);
> + spice_return_if_fail(info->num_memslots > slot_id);
> +
> + info->mem_slots[slot_group_id][slot_id].address_delta = addr_delta;
> + info->mem_slots[slot_group_id][slot_id].virt_start_addr = virt_start;
> + info->mem_slots[slot_group_id][slot_id].virt_end_addr = virt_end;
> + info->mem_slots[slot_group_id][slot_id].generation = generation;
> +}
> +
> +void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id)
> +{
> + spice_return_if_fail(info->num_memslots_groups > slot_group_id);
> + spice_return_if_fail(info->num_memslots > slot_id);
> +
> + info->mem_slots[slot_group_id][slot_id].virt_start_addr = 0;
> + info->mem_slots[slot_group_id][slot_id].virt_end_addr = 0;
> +}
> +
> +void memslot_info_reset(RedMemSlotInfo *info)
> +{
> + uint32_t i;
> + for (i = 0; i < info->num_memslots_groups; ++i) {
> + memset(info->mem_slots[i], 0, sizeof(MemSlot) * info->num_memslots);
> + }
> +}
> diff --git a/server/memslot.h b/server/memslot.h
> new file mode 100644
> index 0000000..1fba4b8
> --- /dev/null
> +++ b/server/memslot.h
> @@ -0,0 +1,72 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009,2010 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/>.
> +*/
> +
> +#ifndef MEMSLOT_H_
> +#define MEMSLOT_H_
> +
> +#include "red_common.h"
> +
> +#include <spice/qxl_dev.h>
> +
> +typedef struct MemSlot {
> + int generation;
> + unsigned long virt_start_addr;
> + unsigned long virt_end_addr;
> + long address_delta;
> +} MemSlot;
> +
> +typedef struct RedMemSlotInfo {
> + MemSlot **mem_slots;
> + uint32_t num_memslots_groups;
> + uint32_t num_memslots;
> + uint8_t mem_slot_bits;
> + uint8_t generation_bits;
> + uint8_t memslot_id_shift;
> + uint8_t memslot_gen_shift;
> + uint8_t internal_groupslot_id;
> + unsigned long memslot_gen_mask;
> + unsigned long memslot_clean_virt_mask;
> +} RedMemSlotInfo;
> +
> +static inline int memslot_get_id(RedMemSlotInfo *info, uint64_t addr)
> +{
> + return addr >> info->memslot_id_shift;
> +}
> +
> +static inline int memslot_get_generation(RedMemSlotInfo *info, uint64_t addr)
> +{
> + return (addr >> info->memslot_gen_shift) & info->memslot_gen_mask;
> +}
> +
> +int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id,
> + uint32_t add_size, uint32_t group_id);
> +unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size,
> + int group_id, int *error);
> +
> +void memslot_info_init(RedMemSlotInfo *info,
> + uint32_t num_groups, uint32_t num_slots,
> + uint8_t generation_bits,
> + uint8_t id_bits,
> + uint8_t internal_groupslot_id);
> +void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id,
> + uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end,
> + uint32_t generation);
> +void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id);
> +void memslot_info_reset(RedMemSlotInfo *info);
> +
> +#endif
> diff --git a/server/migration-protocol.h b/server/migration-protocol.h
> new file mode 100644
> index 0000000..c1d97ef
> --- /dev/null
> +++ b/server/migration-protocol.h
> @@ -0,0 +1,213 @@
> +/*
> + Copyright (C) 2012 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/>.
> +*/
> +
> +#ifndef _H_MIGRATION_PROTOCOL
> +#define _H_MIGRATION_PROTOCOL
> +
> +#include <spice/macros.h>
> +#include <spice/vd_agent.h>
> +#include "glz-encoder-dict.h"
> +
> +/* ************************************************
> + * src-server to dst-server migration data messages
> + * ************************************************/
> +
> +/* increase the version when the version of any
> + * of the migration data messages is increased */
> +#define SPICE_MIGRATION_PROTOCOL_VERSION 1
> +
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataHeader {
> + uint32_t magic;
> + uint32_t version;
> +} SpiceMigrateDataHeader;
> +
> +/* ********************
> + * Char device base
> + * *******************/
> +
> +/* increase the version of descendent char devices when this
> + * version is increased */
> +#define SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION 1
> +
> +/* Should be the first field of any of the char_devices migration data (see write_data_ptr) */
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataCharDevice {
> + uint32_t version;
> + uint8_t connected;
> + uint32_t num_client_tokens;
> + uint32_t num_send_tokens;
> + uint32_t write_size; /* write to dev */
> + uint32_t write_num_client_tokens; /* how many messages from the client are part of the write_data */
> + uint32_t write_data_ptr; /* offset from
> + SpiceMigrateDataCharDevice - sizeof(SpiceMigrateDataHeader) */
> +} SpiceMigrateDataCharDevice;
> +
> +/* ********
> + * spicevmc
> + * ********/
> +
> +#define SPICE_MIGRATE_DATA_SPICEVMC_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> + is increased */
> +#define SPICE_MIGRATE_DATA_SPICEVMC_MAGIC SPICE_MAGIC_CONST("SVMD")
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSpiceVmc {
> + SpiceMigrateDataCharDevice base;
> +} SpiceMigrateDataSpiceVmc;
> +
> +/* *********
> + * smartcard
> + * *********/
> +
> +#define SPICE_MIGRATE_DATA_SMARTCARD_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> + is increased */
> +#define SPICE_MIGRATE_DATA_SMARTCARD_MAGIC SPICE_MAGIC_CONST("SCMD")
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSmartcard {
> + SpiceMigrateDataCharDevice base;
> + uint8_t reader_added;
> + uint32_t read_size; /* partial data read from dev */
> + uint32_t read_data_ptr;
> +} SpiceMigrateDataSmartcard;
> +
> +/* *********************************
> + * main channel (mainly guest agent)
> + * *********************************/
> +#define SPICE_MIGRATE_DATA_MAIN_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> + is increased */
> +#define SPICE_MIGRATE_DATA_MAIN_MAGIC SPICE_MAGIC_CONST("MNMD")
> +
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataMain {
> + SpiceMigrateDataCharDevice agent_base;
> + uint8_t client_agent_started; /* for discarding messages */
> +
> + struct __attribute__ ((__packed__)) {
> + /* partial data read from device. Such data is stored only
> + * if the chunk header or the entire msg header haven't yet been read completely.
> + * Once the headers are read, partial reads of chunks can be sent as
> + * smaller chunks to the client, without the roundtrip overhead of migration data */
> + uint32_t chunk_header_size;
> + VDIChunkHeader chunk_header;
> + uint8_t msg_header_done;
> + uint32_t msg_header_partial_len;
> + uint32_t msg_header_ptr;
> + uint32_t msg_remaining;
> + uint8_t msg_filter_result;
> + } agent2client;
> +
> + struct __attribute__ ((__packed__)) {
> + uint32_t msg_remaining;
> + uint8_t msg_filter_result;
> + } client2agent;
> +} SpiceMigrateDataMain;
> +
> +/* ****************
> + * display channel
> + * ***************/
> +
> +#define SPICE_MIGRATE_DATA_DISPLAY_VERSION 1
> +#define SPICE_MIGRATE_DATA_DISPLAY_MAGIC SPICE_MAGIC_CONST("DCMD")
> +
> +/*
> + * TODO: store the cache and dictionary data only in one channel (the
> + * freezer).
> + * TODO: optimizations: don't send surfaces information if it will be faster
> + * to resend the surfaces on-demand.
> + * */
> +#define MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS 4
> +
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataDisplay {
> + uint64_t message_serial;
> + uint8_t low_bandwidth_setting;
> +
> + /*
> + * Synchronizing the shared pixmap cache.
> + * For now, the cache is not migrated, and instead, we reset it and send
> + * SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS to the client.
> + * In order to keep the client and server caches consistent:
> + * The channel which freezed the cache on the src side, unfreezes it
> + * on the dest side, and increases its generation (see 'reset' in red_client_shared_cach.h).
> + * In order to enforce that images that are added to the cache by other channels
> + * will reach the client only after SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS,
> + * we send SPICE_MSG_WAIT_FOR_CHANNELS
> + * (see the generation mismatch handling in 'add' in red_client_shared_cach.h).
> + */
> + uint8_t pixmap_cache_id;
> + int64_t pixmap_cache_size;
> + uint8_t pixmap_cache_freezer;
> + uint64_t pixmap_cache_clients[MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS];
> +
> + uint8_t glz_dict_id;
> + GlzEncDictRestoreData glz_dict_data;
> +
> + uint32_t surfaces_at_client_ptr; /* reference to MigrateDisplaySurfacesAtClientLossless/Lossy.
> + Lossy: when jpeg-wan-compression(qemu cmd line)=always
> + or when jpeg-wan-compression=auto,
> + and low_bandwidth_setting=TRUE */
> +
> +} SpiceMigrateDataDisplay;
> +
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataRect {
> + int32_t left;
> + int32_t top;
> + int32_t right;
> + int32_t bottom;
> +} SpiceMigrateDataRect;
> +
> +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossless {
> + uint32_t id;
> +} MigrateDisplaySurfaceLossless;
> +
> +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossy {
> + uint32_t id;
> + SpiceMigrateDataRect lossy_rect;
> +} MigrateDisplaySurfaceLossy;
> +
> +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossless {
> + uint32_t num_surfaces;
> + MigrateDisplaySurfaceLossless surfaces[0];
> +} MigrateDisplaySurfacesAtClientLossless;
> +
> +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossy {
> + uint32_t num_surfaces;
> + MigrateDisplaySurfaceLossy surfaces[0];
> +} MigrateDisplaySurfacesAtClientLossy;
> +
> +/* ****************
> + * inputs channel
> + * ***************/
> +
> +#define SPICE_MIGRATE_DATA_INPUTS_VERSION 1
> +#define SPICE_MIGRATE_DATA_INPUTS_MAGIC SPICE_MAGIC_CONST("ICMD")
> +
> +
> +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataInputs {
> + uint16_t motion_count;
> +} SpiceMigrateDataInputs;
> +
> +static inline int migration_protocol_validate_header(SpiceMigrateDataHeader *header,
> + uint32_t magic,
> + uint32_t version)
> +{
> + if (header->magic != magic) {
> + spice_error("bad magic %u (!= %u)", header->magic, magic);
> + return FALSE;
> + }
> + if (header->version > version) {
> + spice_error("unsupported version %u (> %u)", header->version, version);
> + return FALSE;
> + }
> + return TRUE;
> +}
> +
> +#endif
> diff --git a/server/migration_protocol.h b/server/migration_protocol.h
> deleted file mode 100644
> index 21d3ec8..0000000
> --- a/server/migration_protocol.h
> +++ /dev/null
> @@ -1,213 +0,0 @@
> -/*
> - Copyright (C) 2012 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/>.
> -*/
> -
> -#ifndef _H_MIGRATION_PROTOCOL
> -#define _H_MIGRATION_PROTOCOL
> -
> -#include <spice/macros.h>
> -#include <spice/vd_agent.h>
> -#include "glz_encoder_dictionary.h"
> -
> -/* ************************************************
> - * src-server to dst-server migration data messages
> - * ************************************************/
> -
> -/* increase the version when the version of any
> - * of the migration data messages is increased */
> -#define SPICE_MIGRATION_PROTOCOL_VERSION 1
> -
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataHeader {
> - uint32_t magic;
> - uint32_t version;
> -} SpiceMigrateDataHeader;
> -
> -/* ********************
> - * Char device base
> - * *******************/
> -
> -/* increase the version of descendent char devices when this
> - * version is increased */
> -#define SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION 1
> -
> -/* Should be the first field of any of the char_devices migration data (see write_data_ptr) */
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataCharDevice {
> - uint32_t version;
> - uint8_t connected;
> - uint32_t num_client_tokens;
> - uint32_t num_send_tokens;
> - uint32_t write_size; /* write to dev */
> - uint32_t write_num_client_tokens; /* how many messages from the client are part of the write_data */
> - uint32_t write_data_ptr; /* offset from
> - SpiceMigrateDataCharDevice - sizeof(SpiceMigrateDataHeader) */
> -} SpiceMigrateDataCharDevice;
> -
> -/* ********
> - * spicevmc
> - * ********/
> -
> -#define SPICE_MIGRATE_DATA_SPICEVMC_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> - is increased */
> -#define SPICE_MIGRATE_DATA_SPICEVMC_MAGIC SPICE_MAGIC_CONST("SVMD")
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSpiceVmc {
> - SpiceMigrateDataCharDevice base;
> -} SpiceMigrateDataSpiceVmc;
> -
> -/* *********
> - * smartcard
> - * *********/
> -
> -#define SPICE_MIGRATE_DATA_SMARTCARD_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> - is increased */
> -#define SPICE_MIGRATE_DATA_SMARTCARD_MAGIC SPICE_MAGIC_CONST("SCMD")
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSmartcard {
> - SpiceMigrateDataCharDevice base;
> - uint8_t reader_added;
> - uint32_t read_size; /* partial data read from dev */
> - uint32_t read_data_ptr;
> -} SpiceMigrateDataSmartcard;
> -
> -/* *********************************
> - * main channel (mainly guest agent)
> - * *********************************/
> -#define SPICE_MIGRATE_DATA_MAIN_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION
> - is increased */
> -#define SPICE_MIGRATE_DATA_MAIN_MAGIC SPICE_MAGIC_CONST("MNMD")
> -
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataMain {
> - SpiceMigrateDataCharDevice agent_base;
> - uint8_t client_agent_started; /* for discarding messages */
> -
> - struct __attribute__ ((__packed__)) {
> - /* partial data read from device. Such data is stored only
> - * if the chunk header or the entire msg header haven't yet been read completely.
> - * Once the headers are read, partial reads of chunks can be sent as
> - * smaller chunks to the client, without the roundtrip overhead of migration data */
> - uint32_t chunk_header_size;
> - VDIChunkHeader chunk_header;
> - uint8_t msg_header_done;
> - uint32_t msg_header_partial_len;
> - uint32_t msg_header_ptr;
> - uint32_t msg_remaining;
> - uint8_t msg_filter_result;
> - } agent2client;
> -
> - struct __attribute__ ((__packed__)) {
> - uint32_t msg_remaining;
> - uint8_t msg_filter_result;
> - } client2agent;
> -} SpiceMigrateDataMain;
> -
> -/* ****************
> - * display channel
> - * ***************/
> -
> -#define SPICE_MIGRATE_DATA_DISPLAY_VERSION 1
> -#define SPICE_MIGRATE_DATA_DISPLAY_MAGIC SPICE_MAGIC_CONST("DCMD")
> -
> -/*
> - * TODO: store the cache and dictionary data only in one channel (the
> - * freezer).
> - * TODO: optimizations: don't send surfaces information if it will be faster
> - * to resend the surfaces on-demand.
> - * */
> -#define MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS 4
> -
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataDisplay {
> - uint64_t message_serial;
> - uint8_t low_bandwidth_setting;
> -
> - /*
> - * Synchronizing the shared pixmap cache.
> - * For now, the cache is not migrated, and instead, we reset it and send
> - * SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS to the client.
> - * In order to keep the client and server caches consistent:
> - * The channel which freezed the cache on the src side, unfreezes it
> - * on the dest side, and increases its generation (see 'reset' in red_client_shared_cach.h).
> - * In order to enforce that images that are added to the cache by other channels
> - * will reach the client only after SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS,
> - * we send SPICE_MSG_WAIT_FOR_CHANNELS
> - * (see the generation mismatch handling in 'add' in red_client_shared_cach.h).
> - */
> - uint8_t pixmap_cache_id;
> - int64_t pixmap_cache_size;
> - uint8_t pixmap_cache_freezer;
> - uint64_t pixmap_cache_clients[MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS];
> -
> - uint8_t glz_dict_id;
> - GlzEncDictRestoreData glz_dict_data;
> -
> - uint32_t surfaces_at_client_ptr; /* reference to MigrateDisplaySurfacesAtClientLossless/Lossy.
> - Lossy: when jpeg-wan-compression(qemu cmd line)=always
> - or when jpeg-wan-compression=auto,
> - and low_bandwidth_setting=TRUE */
> -
> -} SpiceMigrateDataDisplay;
> -
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataRect {
> - int32_t left;
> - int32_t top;
> - int32_t right;
> - int32_t bottom;
> -} SpiceMigrateDataRect;
> -
> -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossless {
> - uint32_t id;
> -} MigrateDisplaySurfaceLossless;
> -
> -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossy {
> - uint32_t id;
> - SpiceMigrateDataRect lossy_rect;
> -} MigrateDisplaySurfaceLossy;
> -
> -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossless {
> - uint32_t num_surfaces;
> - MigrateDisplaySurfaceLossless surfaces[0];
> -} MigrateDisplaySurfacesAtClientLossless;
> -
> -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossy {
> - uint32_t num_surfaces;
> - MigrateDisplaySurfaceLossy surfaces[0];
> -} MigrateDisplaySurfacesAtClientLossy;
> -
> -/* ****************
> - * inputs channel
> - * ***************/
> -
> -#define SPICE_MIGRATE_DATA_INPUTS_VERSION 1
> -#define SPICE_MIGRATE_DATA_INPUTS_MAGIC SPICE_MAGIC_CONST("ICMD")
> -
> -
> -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataInputs {
> - uint16_t motion_count;
> -} SpiceMigrateDataInputs;
> -
> -static inline int migration_protocol_validate_header(SpiceMigrateDataHeader *header,
> - uint32_t magic,
> - uint32_t version)
> -{
> - if (header->magic != magic) {
> - spice_error("bad magic %u (!= %u)", header->magic, magic);
> - return FALSE;
> - }
> - if (header->version > version) {
> - spice_error("unsupported version %u (> %u)", header->version, version);
> - return FALSE;
> - }
> - return TRUE;
> -}
> -
> -#endif
> diff --git a/server/mjpeg-encoder.c b/server/mjpeg-encoder.c
> new file mode 100644
> index 0000000..04c95a6
> --- /dev/null
> +++ b/server/mjpeg-encoder.c
> @@ -0,0 +1,1375 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009 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 "red_common.h"
> +#include "mjpeg-encoder.h"
> +#include "utils.h"
> +#include <jerror.h>
> +#include <jpeglib.h>
> +#include <inttypes.h>
> +
> +#define MJPEG_MAX_FPS 25
> +#define MJPEG_MIN_FPS 1
> +
> +#define MJPEG_QUALITY_SAMPLE_NUM 7
> +static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40, 50, 60, 70, 80};
> +
> +#define MJPEG_LEGACY_STATIC_QUALITY_ID 5 // jpeg quality 70
> +
> +#define MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH 10
> +#define MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH 5
> +
> +#define MJPEG_AVERAGE_SIZE_WINDOW 3
> +
> +#define MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES 3
> +#define MJPEG_LOW_FPS_RATE_TH 3
> +
> +#define MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL 1
> +#define MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH 0.1
> +
> +/*
> + * acting on positive client reports only if enough frame mm time
> + * has passed since the last bit rate change and the report.
> + * time
> + */
> +#define MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT 2000
> +#define MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT 3000
> +
> +#define MJPEG_ADJUST_FPS_TIMEOUT 500
> +
> +/*
> + * avoid interrupting the playback when there are temporary
> + * incidents of instability (with respect to server and client drops)
> + */
> +#define MJPEG_MAX_CLIENT_PLAYBACK_DELAY 5000 // 5 sec
> +
> +/*
> + * The stream starts after lossless frames were sent to the client,
> + * and without rate control (except for pipe congestion). Thus, on the beginning
> + * of the stream, we might observe frame drops on the client and server side which
> + * are not necessarily related to mis-estimation of the bit rate, and we would
> + * like to wait till the stream stabilizes.
> + */
> +#define MJPEG_WARMUP_TIME 3000LL // 3 sec
> +
> +enum {
> + MJPEG_QUALITY_EVAL_TYPE_SET,
> + MJPEG_QUALITY_EVAL_TYPE_UPGRADE,
> + MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE,
> +};
> +
> +enum {
> + MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> +};
> +
> +typedef struct MJpegEncoderQualityEval {
> + int type;
> + int reason;
> +
> + uint64_t encoded_size_by_quality[MJPEG_QUALITY_SAMPLE_NUM];
> + /* lower limit for the current evaluation round */
> + int min_quality_id;
> + int min_quality_fps; // min fps for the given quality
> + /* upper limit for the current evaluation round */
> + int max_quality_id;
> + int max_quality_fps; // max fps for the given quality
> + /* tracking the best sampled fps so far */
> + int max_sampled_fps;
> + int max_sampled_fps_quality_id;
> +} MJpegEncoderQualityEval;
> +
> +typedef struct MJpegEncoderClientState {
> + int max_video_latency;
> + uint32_t max_audio_latency;
> +} MJpegEncoderClientState;
> +
> +typedef struct MJpegEncoderServerState {
> + uint32_t num_frames_encoded;
> + uint32_t num_frames_dropped;
> +} MJpegEncoderServerState;
> +
> +typedef struct MJpegEncoderBitRateInfo {
> + uint64_t change_start_time;
> + uint64_t last_frame_time;
> + uint32_t change_start_mm_time;
> + int was_upgraded;
> +
> + /* gathering data about the frames that
> + * were encoded since the last bit rate change*/
> + uint32_t num_enc_frames;
> + uint64_t sum_enc_size;
> +} MJpegEncoderBitRateInfo;
> +
> +/*
> + * Adjusting the stream jpeg quality and frame rate (fps):
> + * When during_quality_eval=TRUE, we compress different frames with different
> + * jpeg quality. By considering (1) the resulting compression ratio, and (2) the available
> + * bit rate, we evaluate the max frame frequency for the stream with the given quality,
> + * and we choose the highest quality that will allow a reasonable frame rate.
> + * during_quality_eval is set for new streams and can also be set any time we want
> + * to re-evaluate the stream parameters (e.g., when the bit rate and/or
> + * compressed frame size significantly change).
> + */
> +typedef struct MJpegEncoderRateControl {
> + int during_quality_eval;
> + MJpegEncoderQualityEval quality_eval_data;
> + MJpegEncoderBitRateInfo bit_rate_info;
> + MJpegEncoderClientState client_state;
> + MJpegEncoderServerState server_state;
> +
> + uint64_t byte_rate;
> + int quality_id;
> + uint32_t fps;
> + double adjusted_fps;
> + uint64_t adjusted_fps_start_time;
> + uint64_t adjusted_fps_num_frames;
> +
> + /* the encoded frame size which the quality and the fps evaluation was based upon */
> + uint64_t base_enc_size;
> +
> + uint64_t last_enc_size;
> +
> + uint64_t sum_recent_enc_size;
> + uint32_t num_recent_enc_frames;
> +
> + uint64_t warmup_start_time;
> +} MJpegEncoderRateControl;
> +
> +struct MJpegEncoder {
> + uint8_t *row;
> + uint32_t row_size;
> + int first_frame;
> +
> + struct jpeg_compress_struct cinfo;
> + struct jpeg_error_mgr jerr;
> +
> + unsigned int bytes_per_pixel; /* bytes per pixel of the input buffer */
> + void (*pixel_converter)(void *src, uint8_t *dest);
> +
> + MJpegEncoderRateControl rate_control;
> + MJpegEncoderRateControlCbs cbs;
> + void *cbs_opaque;
> +
> + /* stats */
> + uint64_t starting_bit_rate;
> + uint64_t avg_quality;
> + uint32_t num_frames;
> +};
> +
> +static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder);
> +static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
> + uint64_t byte_rate,
> + uint32_t latency);
> +
> +static inline int rate_control_is_active(MJpegEncoder* encoder)
> +{
> + return encoder->cbs.get_roundtrip_ms != NULL;
> +}
> +
> +void mjpeg_encoder_destroy(MJpegEncoder *encoder)
> +{
> + free(encoder->cinfo.dest);
> + jpeg_destroy_compress(&encoder->cinfo);
> + free(encoder->row);
> + free(encoder);
> +}
> +
> +static uint8_t mjpeg_encoder_get_bytes_per_pixel(MJpegEncoder *encoder)
> +{
> + return encoder->bytes_per_pixel;
> +}
> +
> +#ifndef JCS_EXTENSIONS
> +/* Pixel conversion routines */
> +static void pixel_rgb24bpp_to_24(void *src_ptr, uint8_t *dest)
> +{
> + uint8_t *src = src_ptr;
> + /* libjpegs stores rgb, spice/win32 stores bgr */
> + *dest++ = src[2]; /* red */
> + *dest++ = src[1]; /* green */
> + *dest++ = src[0]; /* blue */
> +}
> +
> +static void pixel_rgb32bpp_to_24(void *src, uint8_t *dest)
> +{
> + uint32_t pixel = *(uint32_t *)src;
> + *dest++ = (pixel >> 16) & 0xff;
> + *dest++ = (pixel >> 8) & 0xff;
> + *dest++ = (pixel >> 0) & 0xff;
> +}
> +#endif
> +
> +static void pixel_rgb16bpp_to_24(void *src, uint8_t *dest)
> +{
> + uint16_t pixel = *(uint16_t *)src;
> + *dest++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
> + *dest++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
> + *dest++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
> +}
> +
> +
> +/* code from libjpeg 8 to handle compression to a memory buffer
> + *
> + * Copyright (C) 1994-1996, Thomas G. Lane.
> + * Modified 2009 by Guido Vollbeding.
> + * This file is part of the Independent JPEG Group's software.
> + */
> +typedef struct {
> + struct jpeg_destination_mgr pub; /* public fields */
> +
> + unsigned char ** outbuffer; /* target buffer */
> + size_t * outsize;
> + uint8_t * buffer; /* start of buffer */
> + size_t bufsize;
> +} mem_destination_mgr;
> +
> +static void init_mem_destination(j_compress_ptr cinfo)
> +{
> +}
> +
> +static boolean empty_mem_output_buffer(j_compress_ptr cinfo)
> +{
> + size_t nextsize;
> + uint8_t * nextbuffer;
> + mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest;
> +
> + /* Try to allocate new buffer with double size */
> + nextsize = dest->bufsize * 2;
> + nextbuffer = malloc(nextsize);
> +
> + if (nextbuffer == NULL)
> + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
> +
> + memcpy(nextbuffer, dest->buffer, dest->bufsize);
> +
> + free(dest->buffer);
> +
> + dest->pub.next_output_byte = nextbuffer + dest->bufsize;
> + dest->pub.free_in_buffer = dest->bufsize;
> +
> + dest->buffer = nextbuffer;
> + dest->bufsize = nextsize;
> +
> + return TRUE;
> +}
> +
> +static void term_mem_destination(j_compress_ptr cinfo)
> +{
> + mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest;
> +
> + *dest->outbuffer = dest->buffer;
> + *dest->outsize = dest->bufsize;
> +}
> +
> +/*
> + * Prepare for output to a memory buffer.
> + * The caller may supply an own initial buffer with appropriate size.
> + * Otherwise, or when the actual data output exceeds the given size,
> + * the library adapts the buffer size as necessary.
> + * The standard library functions malloc/free are used for allocating
> + * larger memory, so the buffer is available to the application after
> + * finishing compression, and then the application is responsible for
> + * freeing the requested memory.
> + */
> +
> +static void
> +spice_jpeg_mem_dest(j_compress_ptr cinfo,
> + unsigned char ** outbuffer, size_t * outsize)
> +{
> + mem_destination_mgr *dest;
> +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */
> +
> + if (outbuffer == NULL || outsize == NULL) /* sanity check */
> + ERREXIT(cinfo, JERR_BUFFER_SIZE);
> +
> + /* The destination object is made permanent so that multiple JPEG images
> + * can be written to the same buffer without re-executing jpeg_mem_dest.
> + */
> + if (cinfo->dest == NULL) { /* first time for this JPEG object? */
> + cinfo->dest = spice_malloc(sizeof(mem_destination_mgr));
> + }
> +
> + dest = (mem_destination_mgr *) cinfo->dest;
> + dest->pub.init_destination = init_mem_destination;
> + dest->pub.empty_output_buffer = empty_mem_output_buffer;
> + dest->pub.term_destination = term_mem_destination;
> + dest->outbuffer = outbuffer;
> + dest->outsize = outsize;
> + if (*outbuffer == NULL || *outsize == 0) {
> + /* Allocate initial buffer */
> + *outbuffer = malloc(OUTPUT_BUF_SIZE);
> + if (*outbuffer == NULL)
> + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
> + *outsize = OUTPUT_BUF_SIZE;
> + }
> +
> + dest->pub.next_output_byte = dest->buffer = *outbuffer;
> + dest->pub.free_in_buffer = dest->bufsize = *outsize;
> +}
> +/* end of code from libjpeg */
> +
> +static inline uint32_t mjpeg_encoder_get_source_fps(MJpegEncoder *encoder)
> +{
> + return encoder->cbs.get_source_fps ?
> + encoder->cbs.get_source_fps(encoder->cbs_opaque) : MJPEG_MAX_FPS;
> +}
> +
> +static inline uint32_t mjpeg_encoder_get_latency(MJpegEncoder *encoder)
> +{
> + return encoder->cbs.get_roundtrip_ms ?
> + encoder->cbs.get_roundtrip_ms(encoder->cbs_opaque) / 2 : 0;
> +}
> +
> +static uint32_t get_max_fps(uint64_t frame_size, uint64_t bytes_per_sec)
> +{
> + double fps;
> + double send_time_ms;
> +
> + if (!bytes_per_sec) {
> + return 0;
> + }
> + send_time_ms = frame_size * 1000.0 / bytes_per_sec;
> + fps = send_time_ms ? 1000 / send_time_ms : MJPEG_MAX_FPS;
> + return fps;
> +}
> +
> +static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder,
> + int quality_id,
> + uint32_t fps,
> + uint64_t frame_enc_size)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + double fps_ratio;
> +
> + rate_control->during_quality_eval = FALSE;
> +
> + if (rate_control->quality_id != quality_id) {
> + rate_control->last_enc_size = 0;
> + }
> +
> + if (rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> + memset(&rate_control->server_state, 0, sizeof(MJpegEncoderServerState));
> + }
> + rate_control->quality_id = quality_id;
> + memset(&rate_control->quality_eval_data, 0, sizeof(MJpegEncoderQualityEval));
> + rate_control->quality_eval_data.max_quality_id = MJPEG_QUALITY_SAMPLE_NUM - 1;
> + rate_control->quality_eval_data.max_quality_fps = MJPEG_MAX_FPS;
> +
> + if (rate_control->adjusted_fps) {
> + fps_ratio = rate_control->adjusted_fps / rate_control->fps;
> + } else {
> + fps_ratio = 1.5;
> + }
> + rate_control->fps = MAX(MJPEG_MIN_FPS, fps);
> + rate_control->fps = MIN(MJPEG_MAX_FPS, rate_control->fps);
> + rate_control->adjusted_fps = rate_control->fps*fps_ratio;
> + spice_debug("adjusted-fps-ratio=%.2f adjusted-fps=%.2f", fps_ratio, rate_control->adjusted_fps);
> + rate_control->adjusted_fps_start_time = 0;
> + rate_control->adjusted_fps_num_frames = 0;
> + rate_control->base_enc_size = frame_enc_size;
> +
> + rate_control->sum_recent_enc_size = 0;
> + rate_control->num_recent_enc_frames = 0;
> +}
> +
> +#define QUALITY_WAS_EVALUATED(encoder, quality) \
> + ((encoder)->rate_control.quality_eval_data.encoded_size_by_quality[(quality)] != 0)
> +
> +/*
> + * Adjust the stream's jpeg quality and frame rate.
> + * We evaluate the compression ratio of different jpeg qualities;
> + * We compress successive frames with different qualities,
> + * and then we estimate the stream frame rate according to the currently
> + * evaluated jpeg quality and available bit rate.
> + *
> + * During quality evaluation, mjpeg_encoder_eval_quality is called before a new
> + * frame is encoded. mjpeg_encoder_eval_quality examines the encoding size of
> + * the previously encoded frame, and determines whether to continue evaluation
> + * (and chnages the quality for the frame that is going to be encoded),
> + * or stop evaluation (and sets the quality and frame rate for the stream).
> + * When qualities are scanned, we assume monotonicity of compression ratio
> + * as a function of jpeg quality. When we reach a quality with too small, or
> + * big enough compression ratio, we stop the evaluation and set the stream parameters.
> +*/
> +static inline void mjpeg_encoder_eval_quality(MJpegEncoder *encoder)
> +{
> + MJpegEncoderRateControl *rate_control;
> + MJpegEncoderQualityEval *quality_eval;
> + uint32_t fps, src_fps;
> + uint64_t enc_size;
> + uint32_t final_quality_id;
> + uint32_t final_fps;
> + uint64_t final_quality_enc_size;
> +
> + rate_control = &encoder->rate_control;
> + quality_eval = &rate_control->quality_eval_data;
> +
> + spice_assert(rate_control->during_quality_eval);
> +
> + /* retrieving the encoded size of the last encoded frame */
> + enc_size = quality_eval->encoded_size_by_quality[rate_control->quality_id];
> + if (enc_size == 0) {
> + spice_debug("size info missing");
> + return;
> + }
> +
> + src_fps = mjpeg_encoder_get_source_fps(encoder);
> +
> + fps = get_max_fps(enc_size, rate_control->byte_rate);
> + spice_debug("mjpeg %p: jpeg %d: %.2f (KB) fps %d src-fps %u",
> + encoder,
> + mjpeg_quality_samples[rate_control->quality_id],
> + enc_size / 1024.0,
> + fps,
> + src_fps);
> +
> + if (fps > quality_eval->max_sampled_fps ||
> + ((fps == quality_eval->max_sampled_fps || fps >= src_fps) &&
> + rate_control->quality_id > quality_eval->max_sampled_fps_quality_id)) {
> + quality_eval->max_sampled_fps = fps;
> + quality_eval->max_sampled_fps_quality_id = rate_control->quality_id;
> + }
> +
> + /*
> + * Choosing whether to evaluate another quality, or to complete evaluation
> + * and set the stream parameters according to one of the qualities that
> + * were already sampled.
> + */
> +
> + if (rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2 &&
> + fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH &&
> + fps < src_fps) {
> + /*
> + * When the jpeg quality is bigger than the median quality, prefer a reasonable
> + * frame rate over improving the quality
> + */
> + spice_debug("fps < %d && (fps < src_fps), quality %d",
> + MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH,
> + mjpeg_quality_samples[rate_control->quality_id]);
> + if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) {
> + /* the next worse quality was already evaluated and it passed the frame
> + * rate thresholds (we know that, because we continued evaluating a better
> + * quality) */
> + rate_control->quality_id--;
> + goto complete_sample;
> + } else {
> + /* evaluate the next worse quality */
> + rate_control->quality_id--;
> + }
> + } else if ((fps > MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH &&
> + fps >= 0.66 * quality_eval->min_quality_fps) || fps >= src_fps) {
> + /* When the jpeg quality is worse than the median one (see first condition), we allow a less
> + strict threshold for fps, in order to improve the jpeg quality */
> + if (rate_control->quality_id + 1 == MJPEG_QUALITY_SAMPLE_NUM ||
> + rate_control->quality_id >= quality_eval->max_quality_id ||
> + QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id + 1)) {
> + /* best quality has been reached, or the next (better) quality was
> + * already evaluated and didn't pass the fps thresholds */
> + goto complete_sample;
> + } else {
> + if (rate_control->quality_id == MJPEG_QUALITY_SAMPLE_NUM / 2 &&
> + fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH &&
> + fps < src_fps) {
> + goto complete_sample;
> + }
> + /* evaluate the next quality as well*/
> + rate_control->quality_id++;
> + }
> + } else { // very small frame rate, try to improve by downgrading the quality
> + if (rate_control->quality_id == 0 ||
> + rate_control->quality_id <= quality_eval->min_quality_id) {
> + goto complete_sample;
> + } else if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) {
> + rate_control->quality_id--;
> + goto complete_sample;
> + } else {
> + /* evaluate the next worse quality */
> + rate_control->quality_id--;
> + }
> + }
> + return;
> +
> +complete_sample:
> + if (quality_eval->max_sampled_fps != 0) {
> + /* covering a case were monotonicity was violated and we sampled
> + a better jepg quality, with better frame rate. */
> + final_quality_id = MAX(rate_control->quality_id,
> + quality_eval->max_sampled_fps_quality_id);
> + } else {
> + final_quality_id = rate_control->quality_id;
> + }
> + final_quality_enc_size = quality_eval->encoded_size_by_quality[final_quality_id];
> + final_fps = get_max_fps(final_quality_enc_size,
> + rate_control->byte_rate);
> +
> + if (final_quality_id == quality_eval->min_quality_id) {
> + final_fps = MAX(final_fps, quality_eval->min_quality_fps);
> + }
> + if (final_quality_id == quality_eval->max_quality_id) {
> + final_fps = MIN(final_fps, quality_eval->max_quality_fps);
> + }
> + mjpeg_encoder_reset_quality(encoder, final_quality_id, final_fps, final_quality_enc_size);
> + rate_control->sum_recent_enc_size = final_quality_enc_size;
> + rate_control->num_recent_enc_frames = 1;
> +
> + spice_debug("MJpeg quality sample end %p: quality %d fps %d",
> + encoder, mjpeg_quality_samples[rate_control->quality_id], rate_control->fps);
> + if (encoder->cbs.update_client_playback_delay) {
> + uint32_t latency = mjpeg_encoder_get_latency(encoder);
> + uint32_t min_delay = get_min_required_playback_delay(final_quality_enc_size,
> + rate_control->byte_rate,
> + latency);
> +
> + encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, min_delay);
> + }
> +}
> +
> +static void mjpeg_encoder_quality_eval_set_upgrade(MJpegEncoder *encoder,
> + int reason,
> + uint32_t min_quality_id,
> + uint32_t min_quality_fps)
> +{
> + MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data;
> +
> + encoder->rate_control.during_quality_eval = TRUE;
> + quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_UPGRADE;
> + quality_eval->reason = reason;
> + quality_eval->min_quality_id = min_quality_id;
> + quality_eval->min_quality_fps = min_quality_fps;
> +}
> +
> +static void mjpeg_encoder_quality_eval_set_downgrade(MJpegEncoder *encoder,
> + int reason,
> + uint32_t max_quality_id,
> + uint32_t max_quality_fps)
> +{
> + MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data;
> +
> + encoder->rate_control.during_quality_eval = TRUE;
> + quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE;
> + quality_eval->reason = reason;
> + quality_eval->max_quality_id = max_quality_id;
> + quality_eval->max_quality_fps = max_quality_fps;
> +}
> +
> +static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder)
> +{
> + MJpegEncoderRateControl *rate_control;
> + MJpegEncoderQualityEval *quality_eval;
> + uint64_t new_avg_enc_size = 0;
> + uint32_t new_fps;
> + uint32_t latency = 0;
> + uint32_t src_fps;
> +
> + spice_assert(rate_control_is_active(encoder));
> +
> + rate_control = &encoder->rate_control;
> + quality_eval = &rate_control->quality_eval_data;
> +
> + if (!rate_control->last_enc_size) {
> + spice_debug("missing sample size");
> + return;
> + }
> +
> + if (rate_control->during_quality_eval) {
> + quality_eval->encoded_size_by_quality[rate_control->quality_id] = rate_control->last_enc_size;
> + mjpeg_encoder_eval_quality(encoder);
> + return;
> + }
> +
> + if (!rate_control->num_recent_enc_frames) {
> + spice_debug("No recent encoded frames");
> + return;
> + }
> +
> + if (rate_control->num_recent_enc_frames < MJPEG_AVERAGE_SIZE_WINDOW &&
> + rate_control->num_recent_enc_frames < rate_control->fps) {
> + goto end;
> + }
> +
> + latency = mjpeg_encoder_get_latency(encoder);
> + new_avg_enc_size = rate_control->sum_recent_enc_size /
> + rate_control->num_recent_enc_frames;
> + new_fps = get_max_fps(new_avg_enc_size, rate_control->byte_rate);
> +
> + spice_debug("cur-fps=%u new-fps=%u (new/old=%.2f) |"
> + "bit-rate=%.2f (Mbps) latency=%u (ms) quality=%d |"
> + " new-size-avg %"PRIu64" , base-size %"PRIu64", (new/old=%.2f) ",
> + rate_control->fps, new_fps, ((double)new_fps)/rate_control->fps,
> + ((double)rate_control->byte_rate*8)/1024/1024,
> + latency,
> + mjpeg_quality_samples[rate_control->quality_id],
> + new_avg_enc_size, rate_control->base_enc_size,
> + rate_control->base_enc_size ?
> + ((double)new_avg_enc_size) / rate_control->base_enc_size :
> + 1);
> +
> + src_fps = mjpeg_encoder_get_source_fps(encoder);
> +
> + /*
> + * The ratio between the new_fps and the current fps reflects the changes
> + * in latency and frame size. When the change passes a threshold,
> + * we re-evaluate the quality and frame rate.
> + */
> + if (new_fps > rate_control->fps &&
> + (rate_control->fps < src_fps || rate_control->quality_id < MJPEG_QUALITY_SAMPLE_NUM - 1)) {
> + spice_debug("mjpeg %p FPS CHANGE >> : re-evaluating params", encoder);
> + mjpeg_encoder_quality_eval_set_upgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> + rate_control->quality_id, /* fps has improved -->
> + don't allow stream quality
> + to deteriorate */
> + rate_control->fps);
> + } else if (new_fps < rate_control->fps && new_fps < src_fps) {
> + spice_debug("mjpeg %p FPS CHANGE << : re-evaluating params", encoder);
> + mjpeg_encoder_quality_eval_set_downgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> + rate_control->quality_id,
> + rate_control->fps);
> + }
> +end:
> + if (rate_control->during_quality_eval) {
> + quality_eval->encoded_size_by_quality[rate_control->quality_id] = new_avg_enc_size;
> + mjpeg_encoder_eval_quality(encoder);
> + } else {
> + mjpeg_encoder_process_server_drops(encoder);
> + }
> +}
> +
> +/*
> + * The actual frames distribution does not necessarily fit the condition "at least
> + * one frame every (1000/rate_contorl->fps) milliseconds".
> + * For keeping the average fps close to the defined fps, we periodically
> + * measure the current average fps, and modify rate_control->adjusted_fps accordingly.
> + * Then, we use (1000/rate_control->adjusted_fps) as the interval between frames.
> + */
> +static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + uint64_t adjusted_fps_time_passed;
> +
> + spice_assert(rate_control_is_active(encoder));
> +
> + adjusted_fps_time_passed = (now - rate_control->adjusted_fps_start_time) / 1000 / 1000;
> +
> + if (!rate_control->during_quality_eval &&
> + adjusted_fps_time_passed > MJPEG_ADJUST_FPS_TIMEOUT &&
> + adjusted_fps_time_passed > 1000 / rate_control->adjusted_fps) {
> + double avg_fps;
> + double fps_ratio;
> +
> + avg_fps = ((double)rate_control->adjusted_fps_num_frames*1000) /
> + adjusted_fps_time_passed;
> + spice_debug("#frames-adjust=%"PRIu64" #adjust-time=%"PRIu64" avg-fps=%.2f",
> + rate_control->adjusted_fps_num_frames, adjusted_fps_time_passed, avg_fps);
> + spice_debug("defined=%u old-adjusted=%.2f", rate_control->fps, rate_control->adjusted_fps);
> + fps_ratio = avg_fps / rate_control->fps;
> + if (avg_fps + 0.5 < rate_control->fps &&
> + mjpeg_encoder_get_source_fps(encoder) > avg_fps) {
> + double new_adjusted_fps = avg_fps ?
> + (rate_control->adjusted_fps/fps_ratio) :
> + rate_control->adjusted_fps * 2;
> +
> + rate_control->adjusted_fps = MIN(rate_control->fps*2, new_adjusted_fps);
> + spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps);
> + } else if (rate_control->fps + 0.5 < avg_fps) {
> + double new_adjusted_fps = rate_control->adjusted_fps / fps_ratio;
> +
> + rate_control->adjusted_fps = MAX(rate_control->fps, new_adjusted_fps);
> + spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps);
> + }
> + rate_control->adjusted_fps_start_time = now;
> + rate_control->adjusted_fps_num_frames = 0;
> + }
> +}
> +
> +/*
> + * dest must be either NULL or allocated by malloc, since it might be freed
> + * during the encoding, if its size is too small.
> + *
> + * return:
> + * MJPEG_ENCODER_FRAME_UNSUPPORTED : frame cannot be encoded
> + * MJPEG_ENCODER_FRAME_DROP : frame should be dropped. This value can only be returned
> + * if mjpeg rate control is active.
> + * MJPEG_ENCODER_FRAME_ENCODE_DONE : frame encoding started. Continue with
> + * mjpeg_encoder_encode_scanline.
> + */
> +static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
> + SpiceBitmapFmt format,
> + int width, int height,
> + uint8_t **dest, size_t *dest_len,
> + uint32_t frame_mm_time)
> +{
> + uint32_t quality;
> +
> + if (rate_control_is_active(encoder)) {
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + uint64_t now;
> + uint64_t interval;
> +
> + now = red_get_monotonic_time();
> +
> + if (!rate_control->adjusted_fps_start_time) {
> + rate_control->adjusted_fps_start_time = now;
> + }
> + mjpeg_encoder_adjust_fps(encoder, now);
> + interval = (now - rate_control->bit_rate_info.last_frame_time);
> +
> + if (interval < (1000*1000*1000) / rate_control->adjusted_fps) {
> + return MJPEG_ENCODER_FRAME_DROP;
> + }
> +
> + mjpeg_encoder_adjust_params_to_bit_rate(encoder);
> +
> + if (!rate_control->during_quality_eval ||
> + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
> + MJpegEncoderBitRateInfo *bit_rate_info;
> +
> + bit_rate_info = &encoder->rate_control.bit_rate_info;
> +
> + if (!bit_rate_info->change_start_time) {
> + bit_rate_info->change_start_time = now;
> + bit_rate_info->change_start_mm_time = frame_mm_time;
> + }
> + bit_rate_info->last_frame_time = now;
> + }
> + }
> +
> + encoder->cinfo.in_color_space = JCS_RGB;
> + encoder->cinfo.input_components = 3;
> + encoder->pixel_converter = NULL;
> +
> + switch (format) {
> + case SPICE_BITMAP_FMT_32BIT:
> + case SPICE_BITMAP_FMT_RGBA:
> + encoder->bytes_per_pixel = 4;
> +#ifdef JCS_EXTENSIONS
> + encoder->cinfo.in_color_space = JCS_EXT_BGRX;
> + encoder->cinfo.input_components = 4;
> +#else
> + encoder->pixel_converter = pixel_rgb32bpp_to_24;
> +#endif
> + break;
> + case SPICE_BITMAP_FMT_16BIT:
> + encoder->bytes_per_pixel = 2;
> + encoder->pixel_converter = pixel_rgb16bpp_to_24;
> + break;
> + case SPICE_BITMAP_FMT_24BIT:
> + encoder->bytes_per_pixel = 3;
> +#ifdef JCS_EXTENSIONS
> + encoder->cinfo.in_color_space = JCS_EXT_BGR;
> +#else
> + encoder->pixel_converter = pixel_rgb24bpp_to_24;
> +#endif
> + break;
> + default:
> + spice_debug("unsupported format %d", format);
> + return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> + }
> +
> + if (encoder->pixel_converter != NULL) {
> + unsigned int stride = width * 3;
> + /* check for integer overflow */
> + if (stride < width) {
> + return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> + }
> + if (encoder->row_size < stride) {
> + encoder->row = spice_realloc(encoder->row, stride);
> + encoder->row_size = stride;
> + }
> + }
> +
> + spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len);
> +
> + encoder->cinfo.image_width = width;
> + encoder->cinfo.image_height = height;
> + jpeg_set_defaults(&encoder->cinfo);
> + encoder->cinfo.dct_method = JDCT_IFAST;
> + quality = mjpeg_quality_samples[encoder->rate_control.quality_id];
> + jpeg_set_quality(&encoder->cinfo, quality, TRUE);
> + jpeg_start_compress(&encoder->cinfo, encoder->first_frame);
> +
> + encoder->num_frames++;
> + encoder->avg_quality += quality;
> + return MJPEG_ENCODER_FRAME_ENCODE_DONE;
> +}
> +
> +static int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder,
> + uint8_t *src_pixels,
> + size_t image_width)
> +{
> + unsigned int scanlines_written;
> + uint8_t *row;
> +
> + row = encoder->row;
> + if (encoder->pixel_converter) {
> + unsigned int x;
> + for (x = 0; x < image_width; x++) {
> + /* src_pixels is expected to be 4 bytes aligned */
> + encoder->pixel_converter(src_pixels, row);
> + row += 3;
> + src_pixels += encoder->bytes_per_pixel;
> + }
> + scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &encoder->row, 1);
> + } else {
> + scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &src_pixels, 1);
> + }
> + if (scanlines_written == 0) { /* Not enough space */
> + jpeg_abort_compress(&encoder->cinfo);
> + encoder->rate_control.last_enc_size = 0;
> + return 0;
> + }
> +
> + return scanlines_written;
> +}
> +
> +static size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder)
> +{
> + mem_destination_mgr *dest = (mem_destination_mgr *) encoder->cinfo.dest;
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> +
> + jpeg_finish_compress(&encoder->cinfo);
> +
> + encoder->first_frame = FALSE;
> + rate_control->last_enc_size = dest->pub.next_output_byte - dest->buffer;
> + rate_control->server_state.num_frames_encoded++;
> +
> + if (!rate_control->during_quality_eval ||
> + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
> +
> + if (!rate_control->during_quality_eval) {
> + if (rate_control->num_recent_enc_frames >= MJPEG_AVERAGE_SIZE_WINDOW) {
> + rate_control->num_recent_enc_frames = 0;
> + rate_control->sum_recent_enc_size = 0;
> + }
> + rate_control->sum_recent_enc_size += rate_control->last_enc_size;
> + rate_control->num_recent_enc_frames++;
> + rate_control->adjusted_fps_num_frames++;
> + }
> + rate_control->bit_rate_info.sum_enc_size += encoder->rate_control.last_enc_size;
> + rate_control->bit_rate_info.num_enc_frames++;
> + }
> + return encoder->rate_control.last_enc_size;
> +}
> +
> +static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset,
> + int *chunk_nr, int stride)
> +{
> + uint8_t *ret;
> + SpiceChunk *chunk;
> +
> + chunk = &chunks->chunk[*chunk_nr];
> +
> + if (*offset == chunk->len) {
> + if (*chunk_nr == chunks->num_chunks - 1) {
> + return NULL; /* Last chunk */
> + }
> + *offset = 0;
> + (*chunk_nr)++;
> + chunk = &chunks->chunk[*chunk_nr];
> + }
> +
> + if (chunk->len - *offset < stride) {
> + spice_warning("bad chunk alignment");
> + return NULL;
> + }
> + ret = chunk->data + *offset;
> + *offset += stride;
> + return ret;
> +}
> +
> +static int encode_frame(MJpegEncoder *encoder, const SpiceRect *src,
> + const SpiceBitmap *image, int top_down)
> +{
> + SpiceChunks *chunks;
> + uint32_t image_stride;
> + size_t offset;
> + int i, chunk;
> +
> + chunks = image->data;
> + offset = 0;
> + chunk = 0;
> + image_stride = image->stride;
> +
> + const int skip_lines = top_down ? src->top : image->y - (src->bottom - 0);
> + for (i = 0; i < skip_lines; i++) {
> + get_image_line(chunks, &offset, &chunk, image_stride);
> + }
> +
> + const unsigned int stream_height = src->bottom - src->top;
> + const unsigned int stream_width = src->right - src->left;
> +
> + for (i = 0; i < stream_height; i++) {
> + uint8_t *src_line = get_image_line(chunks, &offset, &chunk, image_stride);
> +
> + if (!src_line) {
> + return FALSE;
> + }
> +
> + src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(encoder);
> + if (mjpeg_encoder_encode_scanline(encoder, src_line, stream_width) == 0) {
> + return FALSE;
> + }
> + }
> +
> + return TRUE;
> +}
> +
> +int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
> + const SpiceBitmap *bitmap, int width, int height,
> + const SpiceRect *src,
> + int top_down, uint32_t frame_mm_time,
> + uint8_t **outbuf, size_t *outbuf_size,
> + int *data_size)
> +{
> + int ret = mjpeg_encoder_start_frame(encoder, bitmap->format,
> + width, height, outbuf, outbuf_size,
> + frame_mm_time);
> + if (ret != MJPEG_ENCODER_FRAME_ENCODE_DONE) {
> + return ret;
> + }
> +
> + if (!encode_frame(encoder, src, bitmap, top_down)) {
> + return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> + }
> +
> + *data_size = mjpeg_encoder_end_frame(encoder);
> +
> + return MJPEG_ENCODER_FRAME_ENCODE_DONE;
> +}
> +
> +
> +static void mjpeg_encoder_quality_eval_stop(MJpegEncoder *encoder)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + uint32_t quality_id;
> + uint32_t fps;
> +
> + if (!rate_control->during_quality_eval) {
> + return;
> + }
> + switch (rate_control->quality_eval_data.type) {
> + case MJPEG_QUALITY_EVAL_TYPE_UPGRADE:
> + quality_id = rate_control->quality_eval_data.min_quality_id;
> + fps = rate_control->quality_eval_data.min_quality_fps;
> + break;
> + case MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE:
> + quality_id = rate_control->quality_eval_data.max_quality_id;
> + fps = rate_control->quality_eval_data.max_quality_fps;
> + break;
> + case MJPEG_QUALITY_EVAL_TYPE_SET:
> + quality_id = MJPEG_QUALITY_SAMPLE_NUM / 2;
> + fps = MJPEG_MAX_FPS / 2;
> + break;
> + default:
> + spice_warning("unexected");
> + return;
> + }
> + mjpeg_encoder_reset_quality(encoder, quality_id, fps, 0);
> + spice_debug("during quality evaluation: canceling."
> + "reset quality to %d fps %d",
> + mjpeg_quality_samples[rate_control->quality_id], rate_control->fps);
> +}
> +
> +static void mjpeg_encoder_decrease_bit_rate(MJpegEncoder *encoder)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> + uint64_t measured_byte_rate;
> + uint32_t measured_fps;
> + uint64_t decrease_size;
> +
> + mjpeg_encoder_quality_eval_stop(encoder);
> +
> + rate_control->client_state.max_video_latency = 0;
> + rate_control->client_state.max_audio_latency = 0;
> + if (rate_control->warmup_start_time) {
> + uint64_t now;
> +
> + now = red_get_monotonic_time();
> + if (now - rate_control->warmup_start_time < MJPEG_WARMUP_TIME*1000*1000) {
> + spice_debug("during warmup. ignoring");
> + return;
> + } else {
> + rate_control->warmup_start_time = 0;
> + }
> + }
> +
> + if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES ||
> + bit_rate_info->num_enc_frames > rate_control->fps) {
> + double duration_sec;
> +
> + duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time);
> + duration_sec /= (1000.0 * 1000.0 * 1000.0);
> + measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec;
> + measured_fps = bit_rate_info->num_enc_frames / duration_sec;
> + decrease_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames;
> + spice_debug("bit rate esitimation %.2f (Mbps) fps %u",
> + measured_byte_rate*8/1024.0/1024,
> + measured_fps);
> + } else {
> + measured_byte_rate = rate_control->byte_rate;
> + measured_fps = rate_control->fps;
> + decrease_size = measured_byte_rate/measured_fps;
> + spice_debug("bit rate not re-estimated %.2f (Mbps) fps %u",
> + measured_byte_rate*8/1024.0/1024,
> + measured_fps);
> + }
> +
> + measured_byte_rate = MIN(rate_control->byte_rate, measured_byte_rate);
> +
> + if (decrease_size >= measured_byte_rate) {
> + decrease_size = measured_byte_rate / 2;
> + }
> +
> + rate_control->byte_rate = measured_byte_rate - decrease_size;
> + bit_rate_info->change_start_time = 0;
> + bit_rate_info->change_start_mm_time = 0;
> + bit_rate_info->last_frame_time = 0;
> + bit_rate_info->num_enc_frames = 0;
> + bit_rate_info->sum_enc_size = 0;
> + bit_rate_info->was_upgraded = FALSE;
> +
> + spice_debug("decrease bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0);
> + mjpeg_encoder_quality_eval_set_downgrade(encoder,
> + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> + rate_control->quality_id,
> + rate_control->fps);
> +}
> +
> +static void mjpeg_encoder_handle_negative_client_stream_report(MJpegEncoder *encoder,
> + uint32_t report_end_frame_mm_time)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> +
> + spice_debug(NULL);
> +
> + if ((rate_control->bit_rate_info.change_start_mm_time > report_end_frame_mm_time ||
> + !rate_control->bit_rate_info.change_start_mm_time) &&
> + !rate_control->bit_rate_info.was_upgraded) {
> + spice_debug("ignoring, a downgrade has already occurred later to the report time");
> + return;
> + }
> +
> + mjpeg_encoder_decrease_bit_rate(encoder);
> +}
> +
> +static void mjpeg_encoder_increase_bit_rate(MJpegEncoder *encoder)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> + uint64_t measured_byte_rate;
> + uint32_t measured_fps;
> + uint64_t increase_size;
> +
> +
> + if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES ||
> + bit_rate_info->num_enc_frames > rate_control->fps) {
> + uint64_t avg_frame_size;
> + double duration_sec;
> +
> + duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time);
> + duration_sec /= (1000.0 * 1000.0 * 1000.0);
> + measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec;
> + measured_fps = bit_rate_info->num_enc_frames / duration_sec;
> + avg_frame_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames;
> + spice_debug("bit rate esitimation %.2f (Mbps) defined %.2f"
> + " fps %u avg-frame-size=%.2f (KB)",
> + measured_byte_rate*8/1024.0/1024,
> + rate_control->byte_rate*8/1024.0/1024,
> + measured_fps,
> + avg_frame_size/1024.0);
> + increase_size = avg_frame_size;
> + } else {
> + spice_debug("not enough samples for measuring the bit rate. no change");
> + return;
> + }
> +
> +
> + mjpeg_encoder_quality_eval_stop(encoder);
> +
> + if (measured_byte_rate + increase_size < rate_control->byte_rate) {
> + spice_debug("measured byte rate is small: not upgrading, just re-evaluating");
> + } else {
> + rate_control->byte_rate = MIN(measured_byte_rate, rate_control->byte_rate) + increase_size;
> + }
> +
> + bit_rate_info->change_start_time = 0;
> + bit_rate_info->change_start_mm_time = 0;
> + bit_rate_info->last_frame_time = 0;
> + bit_rate_info->num_enc_frames = 0;
> + bit_rate_info->sum_enc_size = 0;
> + bit_rate_info->was_upgraded = TRUE;
> +
> + spice_debug("increase bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0);
> + mjpeg_encoder_quality_eval_set_upgrade(encoder,
> + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> + rate_control->quality_id,
> + rate_control->fps);
> +}
> +
> +static void mjpeg_encoder_handle_positive_client_stream_report(MJpegEncoder *encoder,
> + uint32_t report_start_frame_mm_time)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> + int stable_client_mm_time;
> + int timeout;
> +
> + if (rate_control->during_quality_eval &&
> + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> + spice_debug("during quality evaluation (rate change). ignoring report");
> + return;
> + }
> +
> + if ((rate_control->fps > MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH ||
> + rate_control->fps >= mjpeg_encoder_get_source_fps(encoder)) &&
> + rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2) {
> + timeout = MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT;
> + } else {
> + timeout = MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT;
> + }
> +
> + stable_client_mm_time = (int)report_start_frame_mm_time - bit_rate_info->change_start_mm_time;
> +
> + if (!bit_rate_info->change_start_mm_time || stable_client_mm_time < timeout) {
> + /* assessing the stability of the current setting and only then
> + * respond to the report */
> + spice_debug("no drops, but not enough time has passed for assessing"
> + "the playback stability since the last bit rate change");
> + return;
> + }
> + mjpeg_encoder_increase_bit_rate(encoder);
> +}
> +
> +/*
> + * the video playback jitter buffer should be at least (send_time*2 + net_latency) for
> + * preventing underflow
> + */
> +static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
> + uint64_t byte_rate,
> + uint32_t latency)
> +{
> + uint32_t one_frame_time;
> + uint32_t min_delay;
> +
> + if (!frame_enc_size || !byte_rate) {
> + return latency;
> + }
> + one_frame_time = (frame_enc_size*1000)/byte_rate;
> +
> + min_delay = MIN(one_frame_time*2 + latency, MJPEG_MAX_CLIENT_PLAYBACK_DELAY);
> + return min_delay;
> +}
> +
> +#define MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR 0.5
> +#define MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR 1.25
> +#define MJPEG_VIDEO_DELAY_TH -15
> +
> +void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
> + uint32_t num_frames,
> + uint32_t num_drops,
> + uint32_t start_frame_mm_time,
> + uint32_t end_frame_mm_time,
> + int32_t end_frame_delay,
> + uint32_t audio_delay)
> +{
> + MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> + MJpegEncoderClientState *client_state = &rate_control->client_state;
> + uint64_t avg_enc_size = 0;
> + uint32_t min_playback_delay;
> + int is_video_delay_small = FALSE;
> +
> + spice_debug("client report: #frames %u, #drops %d, duration %u video-delay %d audio-delay %u",
> + num_frames, num_drops,
> + end_frame_mm_time - start_frame_mm_time,
> + end_frame_delay, audio_delay);
> +
> + if (!rate_control_is_active(encoder)) {
> + spice_debug("rate control was not activated: ignoring");
> + return;
> + }
> + if (rate_control->during_quality_eval) {
> + if (rate_control->quality_eval_data.type == MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE &&
> + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> + spice_debug("during rate downgrade evaluation");
> + return;
> + }
> + }
> +
> + if (rate_control->num_recent_enc_frames) {
> + avg_enc_size = rate_control->sum_recent_enc_size /
> + rate_control->num_recent_enc_frames;
> + }
> + spice_debug("recent size avg %.2f (KB)", avg_enc_size / 1024.0);
> + min_playback_delay = get_min_required_playback_delay(avg_enc_size, rate_control->byte_rate,
> + mjpeg_encoder_get_latency(encoder));
> + spice_debug("min-delay %u client-delay %d", min_playback_delay, end_frame_delay);
> +
> + if (min_playback_delay > end_frame_delay) {
> + uint32_t src_fps = mjpeg_encoder_get_source_fps(encoder);
> + /*
> + * if the stream is at its highest rate, we can't estimate the "real"
> + * network bit rate and the min_playback_delay
> + */
> + if (rate_control->quality_id != MJPEG_QUALITY_SAMPLE_NUM - 1 ||
> + rate_control->fps < MIN(src_fps, MJPEG_MAX_FPS) || end_frame_delay < 0) {
> + is_video_delay_small = TRUE;
> + if (encoder->cbs.update_client_playback_delay) {
> + encoder->cbs.update_client_playback_delay(encoder->cbs_opaque,
> + min_playback_delay);
> + }
> + }
> + }
> +
> +
> + /*
> + * If the audio latency has decreased (since the start of the current
> + * sequence of positive reports), and the video latency is bigger, slow down
> + * the video rate
> + */
> + if (end_frame_delay > 0 &&
> + audio_delay < MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR*client_state->max_audio_latency &&
> + end_frame_delay > MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR*audio_delay) {
> + spice_debug("video_latency >> audio_latency && audio_latency << max (%u)",
> + client_state->max_audio_latency);
> + mjpeg_encoder_handle_negative_client_stream_report(encoder,
> + end_frame_mm_time);
> + return;
> + }
> +
> + if (end_frame_delay < MJPEG_VIDEO_DELAY_TH) {
> + mjpeg_encoder_handle_negative_client_stream_report(encoder,
> + end_frame_mm_time);
> + } else {
> + double major_delay_decrease_thresh;
> + double medium_delay_decrease_thresh;
> +
> + client_state->max_video_latency = MAX(end_frame_delay, client_state->max_video_latency);
> + client_state->max_audio_latency = MAX(audio_delay, client_state->max_audio_latency);
> +
> + medium_delay_decrease_thresh = client_state->max_video_latency;
> + medium_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR;
> +
> + major_delay_decrease_thresh = medium_delay_decrease_thresh;
> + major_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR;
> + /*
> + * since the bit rate and the required latency are only evaluation based on the
> + * reports we got till now, we assume that the latency is too low only if it
> + * was higher during the time that passed since the last report that resulted
> + * in a bit rate decrement. If we find that the latency has decreased, it might
> + * suggest that the stream bit rate is too high.
> + */
> + if ((end_frame_delay < medium_delay_decrease_thresh &&
> + is_video_delay_small) || end_frame_delay < major_delay_decrease_thresh) {
> + spice_debug("downgrade due to short video delay (last=%u, past-max=%u",
> + end_frame_delay, client_state->max_video_latency);
> + mjpeg_encoder_handle_negative_client_stream_report(encoder,
> + end_frame_mm_time);
> + } else if (!num_drops) {
> + mjpeg_encoder_handle_positive_client_stream_report(encoder,
> + start_frame_mm_time);
> +
> + }
> + }
> +}
> +
> +void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder)
> +{
> + encoder->rate_control.server_state.num_frames_dropped++;
> + mjpeg_encoder_process_server_drops(encoder);
> +}
> +
> +/*
> + * decrease the bit rate if the drop rate on the sever side exceeds a pre defined
> + * threshold.
> + */
> +static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder)
> +{
> + MJpegEncoderServerState *server_state = &encoder->rate_control.server_state;
> + uint32_t num_frames_total;
> + double drop_factor;
> + uint32_t fps;
> +
> + fps = MIN(encoder->rate_control.fps, mjpeg_encoder_get_source_fps(encoder));
> + if (server_state->num_frames_encoded < fps * MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL) {
> + return;
> + }
> +
> + num_frames_total = server_state->num_frames_dropped + server_state->num_frames_encoded;
> + drop_factor = ((double)server_state->num_frames_dropped) / num_frames_total;
> +
> + spice_debug("#drops %u total %u fps %u src-fps %u",
> + server_state->num_frames_dropped,
> + num_frames_total,
> + encoder->rate_control.fps,
> + mjpeg_encoder_get_source_fps(encoder));
> +
> + if (drop_factor > MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH) {
> + mjpeg_encoder_decrease_bit_rate(encoder);
> + }
> + server_state->num_frames_encoded = 0;
> + server_state->num_frames_dropped = 0;
> +}
> +
> +uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder)
> +{
> + return encoder->rate_control.byte_rate * 8;
> +}
> +
> +void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats)
> +{
> + spice_assert(encoder != NULL && stats != NULL);
> + stats->starting_bit_rate = encoder->starting_bit_rate;
> + stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(encoder);
> + stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames;
> +}
> +
> +MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
> + MJpegEncoderRateControlCbs *cbs,
> + void *cbs_opaque)
> +{
> + MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1);
> +
> + encoder->first_frame = TRUE;
> + encoder->rate_control.byte_rate = starting_bit_rate / 8;
> + encoder->starting_bit_rate = starting_bit_rate;
> +
> + if (cbs) {
> + struct timespec time;
> +
> + clock_gettime(CLOCK_MONOTONIC, &time);
> + encoder->cbs = *cbs;
> + encoder->cbs_opaque = cbs_opaque;
> + mjpeg_encoder_reset_quality(encoder, MJPEG_QUALITY_SAMPLE_NUM / 2, 5, 0);
> + encoder->rate_control.during_quality_eval = TRUE;
> + encoder->rate_control.quality_eval_data.type = MJPEG_QUALITY_EVAL_TYPE_SET;
> + encoder->rate_control.quality_eval_data.reason = MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE;
> + encoder->rate_control.warmup_start_time = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec;
> + } else {
> + encoder->cbs.get_roundtrip_ms = NULL;
> + mjpeg_encoder_reset_quality(encoder, MJPEG_LEGACY_STATIC_QUALITY_ID, MJPEG_MAX_FPS, 0);
> + }
> +
> + encoder->cinfo.err = jpeg_std_error(&encoder->jerr);
> + jpeg_create_compress(&encoder->cinfo);
> +
> + return encoder;
> +}
> diff --git a/server/mjpeg-encoder.h b/server/mjpeg-encoder.h
> new file mode 100644
> index 0000000..d070e70
> --- /dev/null
> +++ b/server/mjpeg-encoder.h
> @@ -0,0 +1,100 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef _H_MJPEG_ENCODER
> +#define _H_MJPEG_ENCODER
> +
> +#include "red_common.h"
> +
> +enum {
> + MJPEG_ENCODER_FRAME_UNSUPPORTED = -1,
> + MJPEG_ENCODER_FRAME_DROP,
> + MJPEG_ENCODER_FRAME_ENCODE_DONE,
> +};
> +
> +typedef struct MJpegEncoder MJpegEncoder;
> +
> +/*
> + * Callbacks required for controling and adjusting
> + * the stream bit rate:
> + * get_roundtrip_ms: roundtrip time in milliseconds
> + * get_source_fps: the input frame rate (#frames per second), i.e.,
> + * the rate of frames arriving from the guest to spice-server,
> + * before any drops.
> + */
> +typedef struct MJpegEncoderRateControlCbs {
> + uint32_t (*get_roundtrip_ms)(void *opaque);
> + uint32_t (*get_source_fps)(void *opaque);
> + void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);
> +} MJpegEncoderRateControlCbs;
> +
> +typedef struct MJpegEncoderStats {
> + uint64_t starting_bit_rate;
> + uint64_t cur_bit_rate;
> + double avg_quality;
> +} MJpegEncoderStats;
> +
> +MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
> + MJpegEncoderRateControlCbs *cbs, void *opaque);
> +void mjpeg_encoder_destroy(MJpegEncoder *encoder);
> +
> +int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
> + const SpiceBitmap *bitmap, int width, int height,
> + const SpiceRect *src,
> + int top_down, uint32_t frame_mm_time,
> + uint8_t **outbuf, size_t *outbuf_size,
> + int *data_size);
> +
> +/*
> + * bit rate control
> + */
> +
> +/*
> + * Data that should be periodically obtained from the client. The report contains:
> + * num_frames : the number of frames that reached the client during the time
> + * the report is referring to.
> + * num_drops : the part of the above frames that was dropped by the client due to
> + * late arrival time.
> + * start_frame_mm_time: the mm_time of the first frame included in the report
> + * end_frame_mm_time : the mm_time of the last_frame included in the report
> + * end_frame_delay : (end_frame_mm_time - client_mm_time)
> + * audio delay : the latency of the audio playback.
> + * If there is no audio playback, set it to MAX_UINT.
> + *
> + */
> +void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
> + uint32_t num_frames,
> + uint32_t num_drops,
> + uint32_t start_frame_mm_time,
> + uint32_t end_frame_mm_time,
> + int32_t end_frame_delay,
> + uint32_t audio_delay);
> +
> +/*
> + * Notify the encoder each time a frame is dropped due to pipe
> + * congestion.
> + * We can deduce the client state by the frame dropping rate in the server.
> + * Monitoring the frame drops can help in fine tuning the playback parameters
> + * when the client reports are delayed.
> + */
> +void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);
> +
> +uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder);
> +void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats);
> +
> +#endif
> diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c
> deleted file mode 100644
> index 9b331c1..0000000
> --- a/server/mjpeg_encoder.c
> +++ /dev/null
> @@ -1,1375 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009 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 "red_common.h"
> -#include "mjpeg_encoder.h"
> -#include "utils.h"
> -#include <jerror.h>
> -#include <jpeglib.h>
> -#include <inttypes.h>
> -
> -#define MJPEG_MAX_FPS 25
> -#define MJPEG_MIN_FPS 1
> -
> -#define MJPEG_QUALITY_SAMPLE_NUM 7
> -static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40, 50, 60, 70, 80};
> -
> -#define MJPEG_LEGACY_STATIC_QUALITY_ID 5 // jpeg quality 70
> -
> -#define MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH 10
> -#define MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH 5
> -
> -#define MJPEG_AVERAGE_SIZE_WINDOW 3
> -
> -#define MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES 3
> -#define MJPEG_LOW_FPS_RATE_TH 3
> -
> -#define MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL 1
> -#define MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH 0.1
> -
> -/*
> - * acting on positive client reports only if enough frame mm time
> - * has passed since the last bit rate change and the report.
> - * time
> - */
> -#define MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT 2000
> -#define MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT 3000
> -
> -#define MJPEG_ADJUST_FPS_TIMEOUT 500
> -
> -/*
> - * avoid interrupting the playback when there are temporary
> - * incidents of instability (with respect to server and client drops)
> - */
> -#define MJPEG_MAX_CLIENT_PLAYBACK_DELAY 5000 // 5 sec
> -
> -/*
> - * The stream starts after lossless frames were sent to the client,
> - * and without rate control (except for pipe congestion). Thus, on the beginning
> - * of the stream, we might observe frame drops on the client and server side which
> - * are not necessarily related to mis-estimation of the bit rate, and we would
> - * like to wait till the stream stabilizes.
> - */
> -#define MJPEG_WARMUP_TIME 3000LL // 3 sec
> -
> -enum {
> - MJPEG_QUALITY_EVAL_TYPE_SET,
> - MJPEG_QUALITY_EVAL_TYPE_UPGRADE,
> - MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE,
> -};
> -
> -enum {
> - MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> -};
> -
> -typedef struct MJpegEncoderQualityEval {
> - int type;
> - int reason;
> -
> - uint64_t encoded_size_by_quality[MJPEG_QUALITY_SAMPLE_NUM];
> - /* lower limit for the current evaluation round */
> - int min_quality_id;
> - int min_quality_fps; // min fps for the given quality
> - /* upper limit for the current evaluation round */
> - int max_quality_id;
> - int max_quality_fps; // max fps for the given quality
> - /* tracking the best sampled fps so far */
> - int max_sampled_fps;
> - int max_sampled_fps_quality_id;
> -} MJpegEncoderQualityEval;
> -
> -typedef struct MJpegEncoderClientState {
> - int max_video_latency;
> - uint32_t max_audio_latency;
> -} MJpegEncoderClientState;
> -
> -typedef struct MJpegEncoderServerState {
> - uint32_t num_frames_encoded;
> - uint32_t num_frames_dropped;
> -} MJpegEncoderServerState;
> -
> -typedef struct MJpegEncoderBitRateInfo {
> - uint64_t change_start_time;
> - uint64_t last_frame_time;
> - uint32_t change_start_mm_time;
> - int was_upgraded;
> -
> - /* gathering data about the frames that
> - * were encoded since the last bit rate change*/
> - uint32_t num_enc_frames;
> - uint64_t sum_enc_size;
> -} MJpegEncoderBitRateInfo;
> -
> -/*
> - * Adjusting the stream jpeg quality and frame rate (fps):
> - * When during_quality_eval=TRUE, we compress different frames with different
> - * jpeg quality. By considering (1) the resulting compression ratio, and (2) the available
> - * bit rate, we evaluate the max frame frequency for the stream with the given quality,
> - * and we choose the highest quality that will allow a reasonable frame rate.
> - * during_quality_eval is set for new streams and can also be set any time we want
> - * to re-evaluate the stream parameters (e.g., when the bit rate and/or
> - * compressed frame size significantly change).
> - */
> -typedef struct MJpegEncoderRateControl {
> - int during_quality_eval;
> - MJpegEncoderQualityEval quality_eval_data;
> - MJpegEncoderBitRateInfo bit_rate_info;
> - MJpegEncoderClientState client_state;
> - MJpegEncoderServerState server_state;
> -
> - uint64_t byte_rate;
> - int quality_id;
> - uint32_t fps;
> - double adjusted_fps;
> - uint64_t adjusted_fps_start_time;
> - uint64_t adjusted_fps_num_frames;
> -
> - /* the encoded frame size which the quality and the fps evaluation was based upon */
> - uint64_t base_enc_size;
> -
> - uint64_t last_enc_size;
> -
> - uint64_t sum_recent_enc_size;
> - uint32_t num_recent_enc_frames;
> -
> - uint64_t warmup_start_time;
> -} MJpegEncoderRateControl;
> -
> -struct MJpegEncoder {
> - uint8_t *row;
> - uint32_t row_size;
> - int first_frame;
> -
> - struct jpeg_compress_struct cinfo;
> - struct jpeg_error_mgr jerr;
> -
> - unsigned int bytes_per_pixel; /* bytes per pixel of the input buffer */
> - void (*pixel_converter)(void *src, uint8_t *dest);
> -
> - MJpegEncoderRateControl rate_control;
> - MJpegEncoderRateControlCbs cbs;
> - void *cbs_opaque;
> -
> - /* stats */
> - uint64_t starting_bit_rate;
> - uint64_t avg_quality;
> - uint32_t num_frames;
> -};
> -
> -static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder);
> -static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
> - uint64_t byte_rate,
> - uint32_t latency);
> -
> -static inline int rate_control_is_active(MJpegEncoder* encoder)
> -{
> - return encoder->cbs.get_roundtrip_ms != NULL;
> -}
> -
> -void mjpeg_encoder_destroy(MJpegEncoder *encoder)
> -{
> - free(encoder->cinfo.dest);
> - jpeg_destroy_compress(&encoder->cinfo);
> - free(encoder->row);
> - free(encoder);
> -}
> -
> -static uint8_t mjpeg_encoder_get_bytes_per_pixel(MJpegEncoder *encoder)
> -{
> - return encoder->bytes_per_pixel;
> -}
> -
> -#ifndef JCS_EXTENSIONS
> -/* Pixel conversion routines */
> -static void pixel_rgb24bpp_to_24(void *src_ptr, uint8_t *dest)
> -{
> - uint8_t *src = src_ptr;
> - /* libjpegs stores rgb, spice/win32 stores bgr */
> - *dest++ = src[2]; /* red */
> - *dest++ = src[1]; /* green */
> - *dest++ = src[0]; /* blue */
> -}
> -
> -static void pixel_rgb32bpp_to_24(void *src, uint8_t *dest)
> -{
> - uint32_t pixel = *(uint32_t *)src;
> - *dest++ = (pixel >> 16) & 0xff;
> - *dest++ = (pixel >> 8) & 0xff;
> - *dest++ = (pixel >> 0) & 0xff;
> -}
> -#endif
> -
> -static void pixel_rgb16bpp_to_24(void *src, uint8_t *dest)
> -{
> - uint16_t pixel = *(uint16_t *)src;
> - *dest++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
> - *dest++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
> - *dest++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
> -}
> -
> -
> -/* code from libjpeg 8 to handle compression to a memory buffer
> - *
> - * Copyright (C) 1994-1996, Thomas G. Lane.
> - * Modified 2009 by Guido Vollbeding.
> - * This file is part of the Independent JPEG Group's software.
> - */
> -typedef struct {
> - struct jpeg_destination_mgr pub; /* public fields */
> -
> - unsigned char ** outbuffer; /* target buffer */
> - size_t * outsize;
> - uint8_t * buffer; /* start of buffer */
> - size_t bufsize;
> -} mem_destination_mgr;
> -
> -static void init_mem_destination(j_compress_ptr cinfo)
> -{
> -}
> -
> -static boolean empty_mem_output_buffer(j_compress_ptr cinfo)
> -{
> - size_t nextsize;
> - uint8_t * nextbuffer;
> - mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest;
> -
> - /* Try to allocate new buffer with double size */
> - nextsize = dest->bufsize * 2;
> - nextbuffer = malloc(nextsize);
> -
> - if (nextbuffer == NULL)
> - ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
> -
> - memcpy(nextbuffer, dest->buffer, dest->bufsize);
> -
> - free(dest->buffer);
> -
> - dest->pub.next_output_byte = nextbuffer + dest->bufsize;
> - dest->pub.free_in_buffer = dest->bufsize;
> -
> - dest->buffer = nextbuffer;
> - dest->bufsize = nextsize;
> -
> - return TRUE;
> -}
> -
> -static void term_mem_destination(j_compress_ptr cinfo)
> -{
> - mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest;
> -
> - *dest->outbuffer = dest->buffer;
> - *dest->outsize = dest->bufsize;
> -}
> -
> -/*
> - * Prepare for output to a memory buffer.
> - * The caller may supply an own initial buffer with appropriate size.
> - * Otherwise, or when the actual data output exceeds the given size,
> - * the library adapts the buffer size as necessary.
> - * The standard library functions malloc/free are used for allocating
> - * larger memory, so the buffer is available to the application after
> - * finishing compression, and then the application is responsible for
> - * freeing the requested memory.
> - */
> -
> -static void
> -spice_jpeg_mem_dest(j_compress_ptr cinfo,
> - unsigned char ** outbuffer, size_t * outsize)
> -{
> - mem_destination_mgr *dest;
> -#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */
> -
> - if (outbuffer == NULL || outsize == NULL) /* sanity check */
> - ERREXIT(cinfo, JERR_BUFFER_SIZE);
> -
> - /* The destination object is made permanent so that multiple JPEG images
> - * can be written to the same buffer without re-executing jpeg_mem_dest.
> - */
> - if (cinfo->dest == NULL) { /* first time for this JPEG object? */
> - cinfo->dest = spice_malloc(sizeof(mem_destination_mgr));
> - }
> -
> - dest = (mem_destination_mgr *) cinfo->dest;
> - dest->pub.init_destination = init_mem_destination;
> - dest->pub.empty_output_buffer = empty_mem_output_buffer;
> - dest->pub.term_destination = term_mem_destination;
> - dest->outbuffer = outbuffer;
> - dest->outsize = outsize;
> - if (*outbuffer == NULL || *outsize == 0) {
> - /* Allocate initial buffer */
> - *outbuffer = malloc(OUTPUT_BUF_SIZE);
> - if (*outbuffer == NULL)
> - ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
> - *outsize = OUTPUT_BUF_SIZE;
> - }
> -
> - dest->pub.next_output_byte = dest->buffer = *outbuffer;
> - dest->pub.free_in_buffer = dest->bufsize = *outsize;
> -}
> -/* end of code from libjpeg */
> -
> -static inline uint32_t mjpeg_encoder_get_source_fps(MJpegEncoder *encoder)
> -{
> - return encoder->cbs.get_source_fps ?
> - encoder->cbs.get_source_fps(encoder->cbs_opaque) : MJPEG_MAX_FPS;
> -}
> -
> -static inline uint32_t mjpeg_encoder_get_latency(MJpegEncoder *encoder)
> -{
> - return encoder->cbs.get_roundtrip_ms ?
> - encoder->cbs.get_roundtrip_ms(encoder->cbs_opaque) / 2 : 0;
> -}
> -
> -static uint32_t get_max_fps(uint64_t frame_size, uint64_t bytes_per_sec)
> -{
> - double fps;
> - double send_time_ms;
> -
> - if (!bytes_per_sec) {
> - return 0;
> - }
> - send_time_ms = frame_size * 1000.0 / bytes_per_sec;
> - fps = send_time_ms ? 1000 / send_time_ms : MJPEG_MAX_FPS;
> - return fps;
> -}
> -
> -static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder,
> - int quality_id,
> - uint32_t fps,
> - uint64_t frame_enc_size)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - double fps_ratio;
> -
> - rate_control->during_quality_eval = FALSE;
> -
> - if (rate_control->quality_id != quality_id) {
> - rate_control->last_enc_size = 0;
> - }
> -
> - if (rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> - memset(&rate_control->server_state, 0, sizeof(MJpegEncoderServerState));
> - }
> - rate_control->quality_id = quality_id;
> - memset(&rate_control->quality_eval_data, 0, sizeof(MJpegEncoderQualityEval));
> - rate_control->quality_eval_data.max_quality_id = MJPEG_QUALITY_SAMPLE_NUM - 1;
> - rate_control->quality_eval_data.max_quality_fps = MJPEG_MAX_FPS;
> -
> - if (rate_control->adjusted_fps) {
> - fps_ratio = rate_control->adjusted_fps / rate_control->fps;
> - } else {
> - fps_ratio = 1.5;
> - }
> - rate_control->fps = MAX(MJPEG_MIN_FPS, fps);
> - rate_control->fps = MIN(MJPEG_MAX_FPS, rate_control->fps);
> - rate_control->adjusted_fps = rate_control->fps*fps_ratio;
> - spice_debug("adjusted-fps-ratio=%.2f adjusted-fps=%.2f", fps_ratio, rate_control->adjusted_fps);
> - rate_control->adjusted_fps_start_time = 0;
> - rate_control->adjusted_fps_num_frames = 0;
> - rate_control->base_enc_size = frame_enc_size;
> -
> - rate_control->sum_recent_enc_size = 0;
> - rate_control->num_recent_enc_frames = 0;
> -}
> -
> -#define QUALITY_WAS_EVALUATED(encoder, quality) \
> - ((encoder)->rate_control.quality_eval_data.encoded_size_by_quality[(quality)] != 0)
> -
> -/*
> - * Adjust the stream's jpeg quality and frame rate.
> - * We evaluate the compression ratio of different jpeg qualities;
> - * We compress successive frames with different qualities,
> - * and then we estimate the stream frame rate according to the currently
> - * evaluated jpeg quality and available bit rate.
> - *
> - * During quality evaluation, mjpeg_encoder_eval_quality is called before a new
> - * frame is encoded. mjpeg_encoder_eval_quality examines the encoding size of
> - * the previously encoded frame, and determines whether to continue evaluation
> - * (and chnages the quality for the frame that is going to be encoded),
> - * or stop evaluation (and sets the quality and frame rate for the stream).
> - * When qualities are scanned, we assume monotonicity of compression ratio
> - * as a function of jpeg quality. When we reach a quality with too small, or
> - * big enough compression ratio, we stop the evaluation and set the stream parameters.
> -*/
> -static inline void mjpeg_encoder_eval_quality(MJpegEncoder *encoder)
> -{
> - MJpegEncoderRateControl *rate_control;
> - MJpegEncoderQualityEval *quality_eval;
> - uint32_t fps, src_fps;
> - uint64_t enc_size;
> - uint32_t final_quality_id;
> - uint32_t final_fps;
> - uint64_t final_quality_enc_size;
> -
> - rate_control = &encoder->rate_control;
> - quality_eval = &rate_control->quality_eval_data;
> -
> - spice_assert(rate_control->during_quality_eval);
> -
> - /* retrieving the encoded size of the last encoded frame */
> - enc_size = quality_eval->encoded_size_by_quality[rate_control->quality_id];
> - if (enc_size == 0) {
> - spice_debug("size info missing");
> - return;
> - }
> -
> - src_fps = mjpeg_encoder_get_source_fps(encoder);
> -
> - fps = get_max_fps(enc_size, rate_control->byte_rate);
> - spice_debug("mjpeg %p: jpeg %d: %.2f (KB) fps %d src-fps %u",
> - encoder,
> - mjpeg_quality_samples[rate_control->quality_id],
> - enc_size / 1024.0,
> - fps,
> - src_fps);
> -
> - if (fps > quality_eval->max_sampled_fps ||
> - ((fps == quality_eval->max_sampled_fps || fps >= src_fps) &&
> - rate_control->quality_id > quality_eval->max_sampled_fps_quality_id)) {
> - quality_eval->max_sampled_fps = fps;
> - quality_eval->max_sampled_fps_quality_id = rate_control->quality_id;
> - }
> -
> - /*
> - * Choosing whether to evaluate another quality, or to complete evaluation
> - * and set the stream parameters according to one of the qualities that
> - * were already sampled.
> - */
> -
> - if (rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2 &&
> - fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH &&
> - fps < src_fps) {
> - /*
> - * When the jpeg quality is bigger than the median quality, prefer a reasonable
> - * frame rate over improving the quality
> - */
> - spice_debug("fps < %d && (fps < src_fps), quality %d",
> - MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH,
> - mjpeg_quality_samples[rate_control->quality_id]);
> - if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) {
> - /* the next worse quality was already evaluated and it passed the frame
> - * rate thresholds (we know that, because we continued evaluating a better
> - * quality) */
> - rate_control->quality_id--;
> - goto complete_sample;
> - } else {
> - /* evaluate the next worse quality */
> - rate_control->quality_id--;
> - }
> - } else if ((fps > MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH &&
> - fps >= 0.66 * quality_eval->min_quality_fps) || fps >= src_fps) {
> - /* When the jpeg quality is worse than the median one (see first condition), we allow a less
> - strict threshold for fps, in order to improve the jpeg quality */
> - if (rate_control->quality_id + 1 == MJPEG_QUALITY_SAMPLE_NUM ||
> - rate_control->quality_id >= quality_eval->max_quality_id ||
> - QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id + 1)) {
> - /* best quality has been reached, or the next (better) quality was
> - * already evaluated and didn't pass the fps thresholds */
> - goto complete_sample;
> - } else {
> - if (rate_control->quality_id == MJPEG_QUALITY_SAMPLE_NUM / 2 &&
> - fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH &&
> - fps < src_fps) {
> - goto complete_sample;
> - }
> - /* evaluate the next quality as well*/
> - rate_control->quality_id++;
> - }
> - } else { // very small frame rate, try to improve by downgrading the quality
> - if (rate_control->quality_id == 0 ||
> - rate_control->quality_id <= quality_eval->min_quality_id) {
> - goto complete_sample;
> - } else if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) {
> - rate_control->quality_id--;
> - goto complete_sample;
> - } else {
> - /* evaluate the next worse quality */
> - rate_control->quality_id--;
> - }
> - }
> - return;
> -
> -complete_sample:
> - if (quality_eval->max_sampled_fps != 0) {
> - /* covering a case were monotonicity was violated and we sampled
> - a better jepg quality, with better frame rate. */
> - final_quality_id = MAX(rate_control->quality_id,
> - quality_eval->max_sampled_fps_quality_id);
> - } else {
> - final_quality_id = rate_control->quality_id;
> - }
> - final_quality_enc_size = quality_eval->encoded_size_by_quality[final_quality_id];
> - final_fps = get_max_fps(final_quality_enc_size,
> - rate_control->byte_rate);
> -
> - if (final_quality_id == quality_eval->min_quality_id) {
> - final_fps = MAX(final_fps, quality_eval->min_quality_fps);
> - }
> - if (final_quality_id == quality_eval->max_quality_id) {
> - final_fps = MIN(final_fps, quality_eval->max_quality_fps);
> - }
> - mjpeg_encoder_reset_quality(encoder, final_quality_id, final_fps, final_quality_enc_size);
> - rate_control->sum_recent_enc_size = final_quality_enc_size;
> - rate_control->num_recent_enc_frames = 1;
> -
> - spice_debug("MJpeg quality sample end %p: quality %d fps %d",
> - encoder, mjpeg_quality_samples[rate_control->quality_id], rate_control->fps);
> - if (encoder->cbs.update_client_playback_delay) {
> - uint32_t latency = mjpeg_encoder_get_latency(encoder);
> - uint32_t min_delay = get_min_required_playback_delay(final_quality_enc_size,
> - rate_control->byte_rate,
> - latency);
> -
> - encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, min_delay);
> - }
> -}
> -
> -static void mjpeg_encoder_quality_eval_set_upgrade(MJpegEncoder *encoder,
> - int reason,
> - uint32_t min_quality_id,
> - uint32_t min_quality_fps)
> -{
> - MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data;
> -
> - encoder->rate_control.during_quality_eval = TRUE;
> - quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_UPGRADE;
> - quality_eval->reason = reason;
> - quality_eval->min_quality_id = min_quality_id;
> - quality_eval->min_quality_fps = min_quality_fps;
> -}
> -
> -static void mjpeg_encoder_quality_eval_set_downgrade(MJpegEncoder *encoder,
> - int reason,
> - uint32_t max_quality_id,
> - uint32_t max_quality_fps)
> -{
> - MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data;
> -
> - encoder->rate_control.during_quality_eval = TRUE;
> - quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE;
> - quality_eval->reason = reason;
> - quality_eval->max_quality_id = max_quality_id;
> - quality_eval->max_quality_fps = max_quality_fps;
> -}
> -
> -static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder)
> -{
> - MJpegEncoderRateControl *rate_control;
> - MJpegEncoderQualityEval *quality_eval;
> - uint64_t new_avg_enc_size = 0;
> - uint32_t new_fps;
> - uint32_t latency = 0;
> - uint32_t src_fps;
> -
> - spice_assert(rate_control_is_active(encoder));
> -
> - rate_control = &encoder->rate_control;
> - quality_eval = &rate_control->quality_eval_data;
> -
> - if (!rate_control->last_enc_size) {
> - spice_debug("missing sample size");
> - return;
> - }
> -
> - if (rate_control->during_quality_eval) {
> - quality_eval->encoded_size_by_quality[rate_control->quality_id] = rate_control->last_enc_size;
> - mjpeg_encoder_eval_quality(encoder);
> - return;
> - }
> -
> - if (!rate_control->num_recent_enc_frames) {
> - spice_debug("No recent encoded frames");
> - return;
> - }
> -
> - if (rate_control->num_recent_enc_frames < MJPEG_AVERAGE_SIZE_WINDOW &&
> - rate_control->num_recent_enc_frames < rate_control->fps) {
> - goto end;
> - }
> -
> - latency = mjpeg_encoder_get_latency(encoder);
> - new_avg_enc_size = rate_control->sum_recent_enc_size /
> - rate_control->num_recent_enc_frames;
> - new_fps = get_max_fps(new_avg_enc_size, rate_control->byte_rate);
> -
> - spice_debug("cur-fps=%u new-fps=%u (new/old=%.2f) |"
> - "bit-rate=%.2f (Mbps) latency=%u (ms) quality=%d |"
> - " new-size-avg %"PRIu64" , base-size %"PRIu64", (new/old=%.2f) ",
> - rate_control->fps, new_fps, ((double)new_fps)/rate_control->fps,
> - ((double)rate_control->byte_rate*8)/1024/1024,
> - latency,
> - mjpeg_quality_samples[rate_control->quality_id],
> - new_avg_enc_size, rate_control->base_enc_size,
> - rate_control->base_enc_size ?
> - ((double)new_avg_enc_size) / rate_control->base_enc_size :
> - 1);
> -
> - src_fps = mjpeg_encoder_get_source_fps(encoder);
> -
> - /*
> - * The ratio between the new_fps and the current fps reflects the changes
> - * in latency and frame size. When the change passes a threshold,
> - * we re-evaluate the quality and frame rate.
> - */
> - if (new_fps > rate_control->fps &&
> - (rate_control->fps < src_fps || rate_control->quality_id < MJPEG_QUALITY_SAMPLE_NUM - 1)) {
> - spice_debug("mjpeg %p FPS CHANGE >> : re-evaluating params", encoder);
> - mjpeg_encoder_quality_eval_set_upgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> - rate_control->quality_id, /* fps has improved -->
> - don't allow stream quality
> - to deteriorate */
> - rate_control->fps);
> - } else if (new_fps < rate_control->fps && new_fps < src_fps) {
> - spice_debug("mjpeg %p FPS CHANGE << : re-evaluating params", encoder);
> - mjpeg_encoder_quality_eval_set_downgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE,
> - rate_control->quality_id,
> - rate_control->fps);
> - }
> -end:
> - if (rate_control->during_quality_eval) {
> - quality_eval->encoded_size_by_quality[rate_control->quality_id] = new_avg_enc_size;
> - mjpeg_encoder_eval_quality(encoder);
> - } else {
> - mjpeg_encoder_process_server_drops(encoder);
> - }
> -}
> -
> -/*
> - * The actual frames distribution does not necessarily fit the condition "at least
> - * one frame every (1000/rate_contorl->fps) milliseconds".
> - * For keeping the average fps close to the defined fps, we periodically
> - * measure the current average fps, and modify rate_control->adjusted_fps accordingly.
> - * Then, we use (1000/rate_control->adjusted_fps) as the interval between frames.
> - */
> -static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - uint64_t adjusted_fps_time_passed;
> -
> - spice_assert(rate_control_is_active(encoder));
> -
> - adjusted_fps_time_passed = (now - rate_control->adjusted_fps_start_time) / 1000 / 1000;
> -
> - if (!rate_control->during_quality_eval &&
> - adjusted_fps_time_passed > MJPEG_ADJUST_FPS_TIMEOUT &&
> - adjusted_fps_time_passed > 1000 / rate_control->adjusted_fps) {
> - double avg_fps;
> - double fps_ratio;
> -
> - avg_fps = ((double)rate_control->adjusted_fps_num_frames*1000) /
> - adjusted_fps_time_passed;
> - spice_debug("#frames-adjust=%"PRIu64" #adjust-time=%"PRIu64" avg-fps=%.2f",
> - rate_control->adjusted_fps_num_frames, adjusted_fps_time_passed, avg_fps);
> - spice_debug("defined=%u old-adjusted=%.2f", rate_control->fps, rate_control->adjusted_fps);
> - fps_ratio = avg_fps / rate_control->fps;
> - if (avg_fps + 0.5 < rate_control->fps &&
> - mjpeg_encoder_get_source_fps(encoder) > avg_fps) {
> - double new_adjusted_fps = avg_fps ?
> - (rate_control->adjusted_fps/fps_ratio) :
> - rate_control->adjusted_fps * 2;
> -
> - rate_control->adjusted_fps = MIN(rate_control->fps*2, new_adjusted_fps);
> - spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps);
> - } else if (rate_control->fps + 0.5 < avg_fps) {
> - double new_adjusted_fps = rate_control->adjusted_fps / fps_ratio;
> -
> - rate_control->adjusted_fps = MAX(rate_control->fps, new_adjusted_fps);
> - spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps);
> - }
> - rate_control->adjusted_fps_start_time = now;
> - rate_control->adjusted_fps_num_frames = 0;
> - }
> -}
> -
> -/*
> - * dest must be either NULL or allocated by malloc, since it might be freed
> - * during the encoding, if its size is too small.
> - *
> - * return:
> - * MJPEG_ENCODER_FRAME_UNSUPPORTED : frame cannot be encoded
> - * MJPEG_ENCODER_FRAME_DROP : frame should be dropped. This value can only be returned
> - * if mjpeg rate control is active.
> - * MJPEG_ENCODER_FRAME_ENCODE_DONE : frame encoding started. Continue with
> - * mjpeg_encoder_encode_scanline.
> - */
> -static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
> - SpiceBitmapFmt format,
> - int width, int height,
> - uint8_t **dest, size_t *dest_len,
> - uint32_t frame_mm_time)
> -{
> - uint32_t quality;
> -
> - if (rate_control_is_active(encoder)) {
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - uint64_t now;
> - uint64_t interval;
> -
> - now = red_get_monotonic_time();
> -
> - if (!rate_control->adjusted_fps_start_time) {
> - rate_control->adjusted_fps_start_time = now;
> - }
> - mjpeg_encoder_adjust_fps(encoder, now);
> - interval = (now - rate_control->bit_rate_info.last_frame_time);
> -
> - if (interval < (1000*1000*1000) / rate_control->adjusted_fps) {
> - return MJPEG_ENCODER_FRAME_DROP;
> - }
> -
> - mjpeg_encoder_adjust_params_to_bit_rate(encoder);
> -
> - if (!rate_control->during_quality_eval ||
> - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
> - MJpegEncoderBitRateInfo *bit_rate_info;
> -
> - bit_rate_info = &encoder->rate_control.bit_rate_info;
> -
> - if (!bit_rate_info->change_start_time) {
> - bit_rate_info->change_start_time = now;
> - bit_rate_info->change_start_mm_time = frame_mm_time;
> - }
> - bit_rate_info->last_frame_time = now;
> - }
> - }
> -
> - encoder->cinfo.in_color_space = JCS_RGB;
> - encoder->cinfo.input_components = 3;
> - encoder->pixel_converter = NULL;
> -
> - switch (format) {
> - case SPICE_BITMAP_FMT_32BIT:
> - case SPICE_BITMAP_FMT_RGBA:
> - encoder->bytes_per_pixel = 4;
> -#ifdef JCS_EXTENSIONS
> - encoder->cinfo.in_color_space = JCS_EXT_BGRX;
> - encoder->cinfo.input_components = 4;
> -#else
> - encoder->pixel_converter = pixel_rgb32bpp_to_24;
> -#endif
> - break;
> - case SPICE_BITMAP_FMT_16BIT:
> - encoder->bytes_per_pixel = 2;
> - encoder->pixel_converter = pixel_rgb16bpp_to_24;
> - break;
> - case SPICE_BITMAP_FMT_24BIT:
> - encoder->bytes_per_pixel = 3;
> -#ifdef JCS_EXTENSIONS
> - encoder->cinfo.in_color_space = JCS_EXT_BGR;
> -#else
> - encoder->pixel_converter = pixel_rgb24bpp_to_24;
> -#endif
> - break;
> - default:
> - spice_debug("unsupported format %d", format);
> - return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> - }
> -
> - if (encoder->pixel_converter != NULL) {
> - unsigned int stride = width * 3;
> - /* check for integer overflow */
> - if (stride < width) {
> - return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> - }
> - if (encoder->row_size < stride) {
> - encoder->row = spice_realloc(encoder->row, stride);
> - encoder->row_size = stride;
> - }
> - }
> -
> - spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len);
> -
> - encoder->cinfo.image_width = width;
> - encoder->cinfo.image_height = height;
> - jpeg_set_defaults(&encoder->cinfo);
> - encoder->cinfo.dct_method = JDCT_IFAST;
> - quality = mjpeg_quality_samples[encoder->rate_control.quality_id];
> - jpeg_set_quality(&encoder->cinfo, quality, TRUE);
> - jpeg_start_compress(&encoder->cinfo, encoder->first_frame);
> -
> - encoder->num_frames++;
> - encoder->avg_quality += quality;
> - return MJPEG_ENCODER_FRAME_ENCODE_DONE;
> -}
> -
> -static int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder,
> - uint8_t *src_pixels,
> - size_t image_width)
> -{
> - unsigned int scanlines_written;
> - uint8_t *row;
> -
> - row = encoder->row;
> - if (encoder->pixel_converter) {
> - unsigned int x;
> - for (x = 0; x < image_width; x++) {
> - /* src_pixels is expected to be 4 bytes aligned */
> - encoder->pixel_converter(src_pixels, row);
> - row += 3;
> - src_pixels += encoder->bytes_per_pixel;
> - }
> - scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &encoder->row, 1);
> - } else {
> - scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &src_pixels, 1);
> - }
> - if (scanlines_written == 0) { /* Not enough space */
> - jpeg_abort_compress(&encoder->cinfo);
> - encoder->rate_control.last_enc_size = 0;
> - return 0;
> - }
> -
> - return scanlines_written;
> -}
> -
> -static size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder)
> -{
> - mem_destination_mgr *dest = (mem_destination_mgr *) encoder->cinfo.dest;
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> -
> - jpeg_finish_compress(&encoder->cinfo);
> -
> - encoder->first_frame = FALSE;
> - rate_control->last_enc_size = dest->pub.next_output_byte - dest->buffer;
> - rate_control->server_state.num_frames_encoded++;
> -
> - if (!rate_control->during_quality_eval ||
> - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
> -
> - if (!rate_control->during_quality_eval) {
> - if (rate_control->num_recent_enc_frames >= MJPEG_AVERAGE_SIZE_WINDOW) {
> - rate_control->num_recent_enc_frames = 0;
> - rate_control->sum_recent_enc_size = 0;
> - }
> - rate_control->sum_recent_enc_size += rate_control->last_enc_size;
> - rate_control->num_recent_enc_frames++;
> - rate_control->adjusted_fps_num_frames++;
> - }
> - rate_control->bit_rate_info.sum_enc_size += encoder->rate_control.last_enc_size;
> - rate_control->bit_rate_info.num_enc_frames++;
> - }
> - return encoder->rate_control.last_enc_size;
> -}
> -
> -static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset,
> - int *chunk_nr, int stride)
> -{
> - uint8_t *ret;
> - SpiceChunk *chunk;
> -
> - chunk = &chunks->chunk[*chunk_nr];
> -
> - if (*offset == chunk->len) {
> - if (*chunk_nr == chunks->num_chunks - 1) {
> - return NULL; /* Last chunk */
> - }
> - *offset = 0;
> - (*chunk_nr)++;
> - chunk = &chunks->chunk[*chunk_nr];
> - }
> -
> - if (chunk->len - *offset < stride) {
> - spice_warning("bad chunk alignment");
> - return NULL;
> - }
> - ret = chunk->data + *offset;
> - *offset += stride;
> - return ret;
> -}
> -
> -static int encode_frame(MJpegEncoder *encoder, const SpiceRect *src,
> - const SpiceBitmap *image, int top_down)
> -{
> - SpiceChunks *chunks;
> - uint32_t image_stride;
> - size_t offset;
> - int i, chunk;
> -
> - chunks = image->data;
> - offset = 0;
> - chunk = 0;
> - image_stride = image->stride;
> -
> - const int skip_lines = top_down ? src->top : image->y - (src->bottom - 0);
> - for (i = 0; i < skip_lines; i++) {
> - get_image_line(chunks, &offset, &chunk, image_stride);
> - }
> -
> - const unsigned int stream_height = src->bottom - src->top;
> - const unsigned int stream_width = src->right - src->left;
> -
> - for (i = 0; i < stream_height; i++) {
> - uint8_t *src_line = get_image_line(chunks, &offset, &chunk, image_stride);
> -
> - if (!src_line) {
> - return FALSE;
> - }
> -
> - src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(encoder);
> - if (mjpeg_encoder_encode_scanline(encoder, src_line, stream_width) == 0) {
> - return FALSE;
> - }
> - }
> -
> - return TRUE;
> -}
> -
> -int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
> - const SpiceBitmap *bitmap, int width, int height,
> - const SpiceRect *src,
> - int top_down, uint32_t frame_mm_time,
> - uint8_t **outbuf, size_t *outbuf_size,
> - int *data_size)
> -{
> - int ret = mjpeg_encoder_start_frame(encoder, bitmap->format,
> - width, height, outbuf, outbuf_size,
> - frame_mm_time);
> - if (ret != MJPEG_ENCODER_FRAME_ENCODE_DONE) {
> - return ret;
> - }
> -
> - if (!encode_frame(encoder, src, bitmap, top_down)) {
> - return MJPEG_ENCODER_FRAME_UNSUPPORTED;
> - }
> -
> - *data_size = mjpeg_encoder_end_frame(encoder);
> -
> - return MJPEG_ENCODER_FRAME_ENCODE_DONE;
> -}
> -
> -
> -static void mjpeg_encoder_quality_eval_stop(MJpegEncoder *encoder)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - uint32_t quality_id;
> - uint32_t fps;
> -
> - if (!rate_control->during_quality_eval) {
> - return;
> - }
> - switch (rate_control->quality_eval_data.type) {
> - case MJPEG_QUALITY_EVAL_TYPE_UPGRADE:
> - quality_id = rate_control->quality_eval_data.min_quality_id;
> - fps = rate_control->quality_eval_data.min_quality_fps;
> - break;
> - case MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE:
> - quality_id = rate_control->quality_eval_data.max_quality_id;
> - fps = rate_control->quality_eval_data.max_quality_fps;
> - break;
> - case MJPEG_QUALITY_EVAL_TYPE_SET:
> - quality_id = MJPEG_QUALITY_SAMPLE_NUM / 2;
> - fps = MJPEG_MAX_FPS / 2;
> - break;
> - default:
> - spice_warning("unexected");
> - return;
> - }
> - mjpeg_encoder_reset_quality(encoder, quality_id, fps, 0);
> - spice_debug("during quality evaluation: canceling."
> - "reset quality to %d fps %d",
> - mjpeg_quality_samples[rate_control->quality_id], rate_control->fps);
> -}
> -
> -static void mjpeg_encoder_decrease_bit_rate(MJpegEncoder *encoder)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> - uint64_t measured_byte_rate;
> - uint32_t measured_fps;
> - uint64_t decrease_size;
> -
> - mjpeg_encoder_quality_eval_stop(encoder);
> -
> - rate_control->client_state.max_video_latency = 0;
> - rate_control->client_state.max_audio_latency = 0;
> - if (rate_control->warmup_start_time) {
> - uint64_t now;
> -
> - now = red_get_monotonic_time();
> - if (now - rate_control->warmup_start_time < MJPEG_WARMUP_TIME*1000*1000) {
> - spice_debug("during warmup. ignoring");
> - return;
> - } else {
> - rate_control->warmup_start_time = 0;
> - }
> - }
> -
> - if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES ||
> - bit_rate_info->num_enc_frames > rate_control->fps) {
> - double duration_sec;
> -
> - duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time);
> - duration_sec /= (1000.0 * 1000.0 * 1000.0);
> - measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec;
> - measured_fps = bit_rate_info->num_enc_frames / duration_sec;
> - decrease_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames;
> - spice_debug("bit rate esitimation %.2f (Mbps) fps %u",
> - measured_byte_rate*8/1024.0/1024,
> - measured_fps);
> - } else {
> - measured_byte_rate = rate_control->byte_rate;
> - measured_fps = rate_control->fps;
> - decrease_size = measured_byte_rate/measured_fps;
> - spice_debug("bit rate not re-estimated %.2f (Mbps) fps %u",
> - measured_byte_rate*8/1024.0/1024,
> - measured_fps);
> - }
> -
> - measured_byte_rate = MIN(rate_control->byte_rate, measured_byte_rate);
> -
> - if (decrease_size >= measured_byte_rate) {
> - decrease_size = measured_byte_rate / 2;
> - }
> -
> - rate_control->byte_rate = measured_byte_rate - decrease_size;
> - bit_rate_info->change_start_time = 0;
> - bit_rate_info->change_start_mm_time = 0;
> - bit_rate_info->last_frame_time = 0;
> - bit_rate_info->num_enc_frames = 0;
> - bit_rate_info->sum_enc_size = 0;
> - bit_rate_info->was_upgraded = FALSE;
> -
> - spice_debug("decrease bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0);
> - mjpeg_encoder_quality_eval_set_downgrade(encoder,
> - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> - rate_control->quality_id,
> - rate_control->fps);
> -}
> -
> -static void mjpeg_encoder_handle_negative_client_stream_report(MJpegEncoder *encoder,
> - uint32_t report_end_frame_mm_time)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> -
> - spice_debug(NULL);
> -
> - if ((rate_control->bit_rate_info.change_start_mm_time > report_end_frame_mm_time ||
> - !rate_control->bit_rate_info.change_start_mm_time) &&
> - !rate_control->bit_rate_info.was_upgraded) {
> - spice_debug("ignoring, a downgrade has already occurred later to the report time");
> - return;
> - }
> -
> - mjpeg_encoder_decrease_bit_rate(encoder);
> -}
> -
> -static void mjpeg_encoder_increase_bit_rate(MJpegEncoder *encoder)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> - uint64_t measured_byte_rate;
> - uint32_t measured_fps;
> - uint64_t increase_size;
> -
> -
> - if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES ||
> - bit_rate_info->num_enc_frames > rate_control->fps) {
> - uint64_t avg_frame_size;
> - double duration_sec;
> -
> - duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time);
> - duration_sec /= (1000.0 * 1000.0 * 1000.0);
> - measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec;
> - measured_fps = bit_rate_info->num_enc_frames / duration_sec;
> - avg_frame_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames;
> - spice_debug("bit rate esitimation %.2f (Mbps) defined %.2f"
> - " fps %u avg-frame-size=%.2f (KB)",
> - measured_byte_rate*8/1024.0/1024,
> - rate_control->byte_rate*8/1024.0/1024,
> - measured_fps,
> - avg_frame_size/1024.0);
> - increase_size = avg_frame_size;
> - } else {
> - spice_debug("not enough samples for measuring the bit rate. no change");
> - return;
> - }
> -
> -
> - mjpeg_encoder_quality_eval_stop(encoder);
> -
> - if (measured_byte_rate + increase_size < rate_control->byte_rate) {
> - spice_debug("measured byte rate is small: not upgrading, just re-evaluating");
> - } else {
> - rate_control->byte_rate = MIN(measured_byte_rate, rate_control->byte_rate) + increase_size;
> - }
> -
> - bit_rate_info->change_start_time = 0;
> - bit_rate_info->change_start_mm_time = 0;
> - bit_rate_info->last_frame_time = 0;
> - bit_rate_info->num_enc_frames = 0;
> - bit_rate_info->sum_enc_size = 0;
> - bit_rate_info->was_upgraded = TRUE;
> -
> - spice_debug("increase bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0);
> - mjpeg_encoder_quality_eval_set_upgrade(encoder,
> - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE,
> - rate_control->quality_id,
> - rate_control->fps);
> -}
> -
> -static void mjpeg_encoder_handle_positive_client_stream_report(MJpegEncoder *encoder,
> - uint32_t report_start_frame_mm_time)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info;
> - int stable_client_mm_time;
> - int timeout;
> -
> - if (rate_control->during_quality_eval &&
> - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> - spice_debug("during quality evaluation (rate change). ignoring report");
> - return;
> - }
> -
> - if ((rate_control->fps > MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH ||
> - rate_control->fps >= mjpeg_encoder_get_source_fps(encoder)) &&
> - rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2) {
> - timeout = MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT;
> - } else {
> - timeout = MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT;
> - }
> -
> - stable_client_mm_time = (int)report_start_frame_mm_time - bit_rate_info->change_start_mm_time;
> -
> - if (!bit_rate_info->change_start_mm_time || stable_client_mm_time < timeout) {
> - /* assessing the stability of the current setting and only then
> - * respond to the report */
> - spice_debug("no drops, but not enough time has passed for assessing"
> - "the playback stability since the last bit rate change");
> - return;
> - }
> - mjpeg_encoder_increase_bit_rate(encoder);
> -}
> -
> -/*
> - * the video playback jitter buffer should be at least (send_time*2 + net_latency) for
> - * preventing underflow
> - */
> -static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
> - uint64_t byte_rate,
> - uint32_t latency)
> -{
> - uint32_t one_frame_time;
> - uint32_t min_delay;
> -
> - if (!frame_enc_size || !byte_rate) {
> - return latency;
> - }
> - one_frame_time = (frame_enc_size*1000)/byte_rate;
> -
> - min_delay = MIN(one_frame_time*2 + latency, MJPEG_MAX_CLIENT_PLAYBACK_DELAY);
> - return min_delay;
> -}
> -
> -#define MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR 0.5
> -#define MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR 1.25
> -#define MJPEG_VIDEO_DELAY_TH -15
> -
> -void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
> - uint32_t num_frames,
> - uint32_t num_drops,
> - uint32_t start_frame_mm_time,
> - uint32_t end_frame_mm_time,
> - int32_t end_frame_delay,
> - uint32_t audio_delay)
> -{
> - MJpegEncoderRateControl *rate_control = &encoder->rate_control;
> - MJpegEncoderClientState *client_state = &rate_control->client_state;
> - uint64_t avg_enc_size = 0;
> - uint32_t min_playback_delay;
> - int is_video_delay_small = FALSE;
> -
> - spice_debug("client report: #frames %u, #drops %d, duration %u video-delay %d audio-delay %u",
> - num_frames, num_drops,
> - end_frame_mm_time - start_frame_mm_time,
> - end_frame_delay, audio_delay);
> -
> - if (!rate_control_is_active(encoder)) {
> - spice_debug("rate control was not activated: ignoring");
> - return;
> - }
> - if (rate_control->during_quality_eval) {
> - if (rate_control->quality_eval_data.type == MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE &&
> - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
> - spice_debug("during rate downgrade evaluation");
> - return;
> - }
> - }
> -
> - if (rate_control->num_recent_enc_frames) {
> - avg_enc_size = rate_control->sum_recent_enc_size /
> - rate_control->num_recent_enc_frames;
> - }
> - spice_debug("recent size avg %.2f (KB)", avg_enc_size / 1024.0);
> - min_playback_delay = get_min_required_playback_delay(avg_enc_size, rate_control->byte_rate,
> - mjpeg_encoder_get_latency(encoder));
> - spice_debug("min-delay %u client-delay %d", min_playback_delay, end_frame_delay);
> -
> - if (min_playback_delay > end_frame_delay) {
> - uint32_t src_fps = mjpeg_encoder_get_source_fps(encoder);
> - /*
> - * if the stream is at its highest rate, we can't estimate the "real"
> - * network bit rate and the min_playback_delay
> - */
> - if (rate_control->quality_id != MJPEG_QUALITY_SAMPLE_NUM - 1 ||
> - rate_control->fps < MIN(src_fps, MJPEG_MAX_FPS) || end_frame_delay < 0) {
> - is_video_delay_small = TRUE;
> - if (encoder->cbs.update_client_playback_delay) {
> - encoder->cbs.update_client_playback_delay(encoder->cbs_opaque,
> - min_playback_delay);
> - }
> - }
> - }
> -
> -
> - /*
> - * If the audio latency has decreased (since the start of the current
> - * sequence of positive reports), and the video latency is bigger, slow down
> - * the video rate
> - */
> - if (end_frame_delay > 0 &&
> - audio_delay < MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR*client_state->max_audio_latency &&
> - end_frame_delay > MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR*audio_delay) {
> - spice_debug("video_latency >> audio_latency && audio_latency << max (%u)",
> - client_state->max_audio_latency);
> - mjpeg_encoder_handle_negative_client_stream_report(encoder,
> - end_frame_mm_time);
> - return;
> - }
> -
> - if (end_frame_delay < MJPEG_VIDEO_DELAY_TH) {
> - mjpeg_encoder_handle_negative_client_stream_report(encoder,
> - end_frame_mm_time);
> - } else {
> - double major_delay_decrease_thresh;
> - double medium_delay_decrease_thresh;
> -
> - client_state->max_video_latency = MAX(end_frame_delay, client_state->max_video_latency);
> - client_state->max_audio_latency = MAX(audio_delay, client_state->max_audio_latency);
> -
> - medium_delay_decrease_thresh = client_state->max_video_latency;
> - medium_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR;
> -
> - major_delay_decrease_thresh = medium_delay_decrease_thresh;
> - major_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR;
> - /*
> - * since the bit rate and the required latency are only evaluation based on the
> - * reports we got till now, we assume that the latency is too low only if it
> - * was higher during the time that passed since the last report that resulted
> - * in a bit rate decrement. If we find that the latency has decreased, it might
> - * suggest that the stream bit rate is too high.
> - */
> - if ((end_frame_delay < medium_delay_decrease_thresh &&
> - is_video_delay_small) || end_frame_delay < major_delay_decrease_thresh) {
> - spice_debug("downgrade due to short video delay (last=%u, past-max=%u",
> - end_frame_delay, client_state->max_video_latency);
> - mjpeg_encoder_handle_negative_client_stream_report(encoder,
> - end_frame_mm_time);
> - } else if (!num_drops) {
> - mjpeg_encoder_handle_positive_client_stream_report(encoder,
> - start_frame_mm_time);
> -
> - }
> - }
> -}
> -
> -void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder)
> -{
> - encoder->rate_control.server_state.num_frames_dropped++;
> - mjpeg_encoder_process_server_drops(encoder);
> -}
> -
> -/*
> - * decrease the bit rate if the drop rate on the sever side exceeds a pre defined
> - * threshold.
> - */
> -static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder)
> -{
> - MJpegEncoderServerState *server_state = &encoder->rate_control.server_state;
> - uint32_t num_frames_total;
> - double drop_factor;
> - uint32_t fps;
> -
> - fps = MIN(encoder->rate_control.fps, mjpeg_encoder_get_source_fps(encoder));
> - if (server_state->num_frames_encoded < fps * MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL) {
> - return;
> - }
> -
> - num_frames_total = server_state->num_frames_dropped + server_state->num_frames_encoded;
> - drop_factor = ((double)server_state->num_frames_dropped) / num_frames_total;
> -
> - spice_debug("#drops %u total %u fps %u src-fps %u",
> - server_state->num_frames_dropped,
> - num_frames_total,
> - encoder->rate_control.fps,
> - mjpeg_encoder_get_source_fps(encoder));
> -
> - if (drop_factor > MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH) {
> - mjpeg_encoder_decrease_bit_rate(encoder);
> - }
> - server_state->num_frames_encoded = 0;
> - server_state->num_frames_dropped = 0;
> -}
> -
> -uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder)
> -{
> - return encoder->rate_control.byte_rate * 8;
> -}
> -
> -void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats)
> -{
> - spice_assert(encoder != NULL && stats != NULL);
> - stats->starting_bit_rate = encoder->starting_bit_rate;
> - stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(encoder);
> - stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames;
> -}
> -
> -MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
> - MJpegEncoderRateControlCbs *cbs,
> - void *cbs_opaque)
> -{
> - MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1);
> -
> - encoder->first_frame = TRUE;
> - encoder->rate_control.byte_rate = starting_bit_rate / 8;
> - encoder->starting_bit_rate = starting_bit_rate;
> -
> - if (cbs) {
> - struct timespec time;
> -
> - clock_gettime(CLOCK_MONOTONIC, &time);
> - encoder->cbs = *cbs;
> - encoder->cbs_opaque = cbs_opaque;
> - mjpeg_encoder_reset_quality(encoder, MJPEG_QUALITY_SAMPLE_NUM / 2, 5, 0);
> - encoder->rate_control.during_quality_eval = TRUE;
> - encoder->rate_control.quality_eval_data.type = MJPEG_QUALITY_EVAL_TYPE_SET;
> - encoder->rate_control.quality_eval_data.reason = MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE;
> - encoder->rate_control.warmup_start_time = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec;
> - } else {
> - encoder->cbs.get_roundtrip_ms = NULL;
> - mjpeg_encoder_reset_quality(encoder, MJPEG_LEGACY_STATIC_QUALITY_ID, MJPEG_MAX_FPS, 0);
> - }
> -
> - encoder->cinfo.err = jpeg_std_error(&encoder->jerr);
> - jpeg_create_compress(&encoder->cinfo);
> -
> - return encoder;
> -}
> diff --git a/server/mjpeg_encoder.h b/server/mjpeg_encoder.h
> deleted file mode 100644
> index d070e70..0000000
> --- a/server/mjpeg_encoder.h
> +++ /dev/null
> @@ -1,100 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _H_MJPEG_ENCODER
> -#define _H_MJPEG_ENCODER
> -
> -#include "red_common.h"
> -
> -enum {
> - MJPEG_ENCODER_FRAME_UNSUPPORTED = -1,
> - MJPEG_ENCODER_FRAME_DROP,
> - MJPEG_ENCODER_FRAME_ENCODE_DONE,
> -};
> -
> -typedef struct MJpegEncoder MJpegEncoder;
> -
> -/*
> - * Callbacks required for controling and adjusting
> - * the stream bit rate:
> - * get_roundtrip_ms: roundtrip time in milliseconds
> - * get_source_fps: the input frame rate (#frames per second), i.e.,
> - * the rate of frames arriving from the guest to spice-server,
> - * before any drops.
> - */
> -typedef struct MJpegEncoderRateControlCbs {
> - uint32_t (*get_roundtrip_ms)(void *opaque);
> - uint32_t (*get_source_fps)(void *opaque);
> - void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);
> -} MJpegEncoderRateControlCbs;
> -
> -typedef struct MJpegEncoderStats {
> - uint64_t starting_bit_rate;
> - uint64_t cur_bit_rate;
> - double avg_quality;
> -} MJpegEncoderStats;
> -
> -MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
> - MJpegEncoderRateControlCbs *cbs, void *opaque);
> -void mjpeg_encoder_destroy(MJpegEncoder *encoder);
> -
> -int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
> - const SpiceBitmap *bitmap, int width, int height,
> - const SpiceRect *src,
> - int top_down, uint32_t frame_mm_time,
> - uint8_t **outbuf, size_t *outbuf_size,
> - int *data_size);
> -
> -/*
> - * bit rate control
> - */
> -
> -/*
> - * Data that should be periodically obtained from the client. The report contains:
> - * num_frames : the number of frames that reached the client during the time
> - * the report is referring to.
> - * num_drops : the part of the above frames that was dropped by the client due to
> - * late arrival time.
> - * start_frame_mm_time: the mm_time of the first frame included in the report
> - * end_frame_mm_time : the mm_time of the last_frame included in the report
> - * end_frame_delay : (end_frame_mm_time - client_mm_time)
> - * audio delay : the latency of the audio playback.
> - * If there is no audio playback, set it to MAX_UINT.
> - *
> - */
> -void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
> - uint32_t num_frames,
> - uint32_t num_drops,
> - uint32_t start_frame_mm_time,
> - uint32_t end_frame_mm_time,
> - int32_t end_frame_delay,
> - uint32_t audio_delay);
> -
> -/*
> - * Notify the encoder each time a frame is dropped due to pipe
> - * congestion.
> - * We can deduce the client state by the frame dropping rate in the server.
> - * Monitoring the frame drops can help in fine tuning the playback parameters
> - * when the client reports are delayed.
> - */
> -void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);
> -
> -uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder);
> -void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats);
> -
> -#endif
> diff --git a/server/red_channel.c b/server/red_channel.c
> index 609c83f..948d354 100644
> --- a/server/red_channel.c
> +++ b/server/red_channel.c
> @@ -42,7 +42,7 @@
> #include "red_channel.h"
> #include "reds.h"
> #include "reds_stream.h"
> -#include "main_dispatcher.h"
> +#include "main-dispatcher.h"
> #include "utils.h"
>
> typedef struct EmptyMsgPipeItem {
> diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c
> index a7825f5..b8b77d7 100644
> --- a/server/red_dispatcher.c
> +++ b/server/red_dispatcher.c
> @@ -32,7 +32,7 @@
>
> #include "spice.h"
> #include "red_worker.h"
> -#include "reds_sw_canvas.h"
> +#include "sw-canvas.h"
> #include "reds.h"
> #include "dispatcher.h"
> #include "red_parse_qxl.h"
> diff --git a/server/red_memslots.c b/server/red_memslots.c
> deleted file mode 100644
> index 0d2d963..0000000
> --- a/server/red_memslots.c
> +++ /dev/null
> @@ -1,184 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009,2010 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 <inttypes.h>
> -
> -#include "red_common.h"
> -#include "red_memslots.h"
> -
> -static unsigned long __get_clean_virt(RedMemSlotInfo *info, QXLPHYSICAL addr)
> -{
> - return addr & info->memslot_clean_virt_mask;
> -}
> -
> -static void print_memslots(RedMemSlotInfo *info)
> -{
> - int i;
> - int x;
> -
> - for (i = 0; i < info->num_memslots_groups; ++i) {
> - for (x = 0; x < info->num_memslots; ++x) {
> - if (!info->mem_slots[i][x].virt_start_addr &&
> - !info->mem_slots[i][x].virt_end_addr) {
> - continue;
> - }
> - printf("id %d, group %d, virt start %lx, virt end %lx, generation %u, delta %lx\n",
> - x, i, info->mem_slots[i][x].virt_start_addr,
> - info->mem_slots[i][x].virt_end_addr, info->mem_slots[i][x].generation,
> - info->mem_slots[i][x].address_delta);
> - }
> - }
> -}
> -
> -/* return 1 if validation successfull, 0 otherwise */
> -int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id,
> - uint32_t add_size, uint32_t group_id)
> -{
> - MemSlot *slot;
> -
> - slot = &info->mem_slots[group_id][slot_id];
> - if ((virt + add_size) < virt) {
> - spice_critical("virtual address overlap");
> - return 0;
> - }
> -
> - if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) {
> - print_memslots(info);
> - spice_critical("virtual address out of range\n"
> - " virt=0x%lx+0x%x slot_id=%d group_id=%d\n"
> - " slot=0x%lx-0x%lx delta=0x%lx",
> - virt, add_size, slot_id, group_id,
> - slot->virt_start_addr, slot->virt_end_addr, slot->address_delta);
> - return 0;
> - }
> - return 1;
> -}
> -
> -/*
> - * return virtual address if successful, which may be 0.
> - * returns 0 and sets error to 1 if an error condition occurs.
> - */
> -unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size,
> - int group_id, int *error)
> -{
> - int slot_id;
> - int generation;
> - unsigned long h_virt;
> -
> - MemSlot *slot;
> -
> - *error = 0;
> - if (group_id > info->num_memslots_groups) {
> - spice_critical("group_id too big");
> - *error = 1;
> - return 0;
> - }
> -
> - slot_id = memslot_get_id(info, addr);
> - if (slot_id > info->num_memslots) {
> - print_memslots(info);
> - spice_critical("slot_id %d too big, addr=%" PRIx64, slot_id, addr);
> - *error = 1;
> - return 0;
> - }
> -
> - slot = &info->mem_slots[group_id][slot_id];
> -
> - generation = memslot_get_generation(info, addr);
> - if (generation != slot->generation) {
> - print_memslots(info);
> - spice_critical("address generation is not valid, group_id %d, slot_id %d, gen %d, slot_gen %d\n",
> - group_id, slot_id, generation, slot->generation);
> - *error = 1;
> - return 0;
> - }
> -
> - h_virt = __get_clean_virt(info, addr);
> - h_virt += slot->address_delta;
> -
> - if (!memslot_validate_virt(info, h_virt, slot_id, add_size, group_id)) {
> - *error = 1;
> - return 0;
> - }
> -
> - return h_virt;
> -}
> -
> -void memslot_info_init(RedMemSlotInfo *info,
> - uint32_t num_groups, uint32_t num_slots,
> - uint8_t generation_bits,
> - uint8_t id_bits,
> - uint8_t internal_groupslot_id)
> -{
> - uint32_t i;
> -
> - spice_return_if_fail(num_slots > 0);
> - spice_return_if_fail(num_groups > 0);
> -
> - info->num_memslots_groups = num_groups;
> - info->num_memslots = num_slots;
> - info->generation_bits = generation_bits;
> - info->mem_slot_bits = id_bits;
> - info->internal_groupslot_id = internal_groupslot_id;
> -
> - info->mem_slots = spice_new(MemSlot *, num_groups);
> -
> - for (i = 0; i < num_groups; ++i) {
> - info->mem_slots[i] = spice_new0(MemSlot, num_slots);
> - }
> -
> - /* TODO: use QXLPHYSICAL_BITS */
> - info->memslot_id_shift = 64 - info->mem_slot_bits;
> - info->memslot_gen_shift = 64 - (info->mem_slot_bits + info->generation_bits);
> - info->memslot_gen_mask = ~((QXLPHYSICAL)-1 << info->generation_bits);
> - info->memslot_clean_virt_mask = (((QXLPHYSICAL)(-1)) >>
> - (info->mem_slot_bits + info->generation_bits));
> -}
> -
> -void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id,
> - uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end,
> - uint32_t generation)
> -{
> - spice_return_if_fail(info->num_memslots_groups > slot_group_id);
> - spice_return_if_fail(info->num_memslots > slot_id);
> -
> - info->mem_slots[slot_group_id][slot_id].address_delta = addr_delta;
> - info->mem_slots[slot_group_id][slot_id].virt_start_addr = virt_start;
> - info->mem_slots[slot_group_id][slot_id].virt_end_addr = virt_end;
> - info->mem_slots[slot_group_id][slot_id].generation = generation;
> -}
> -
> -void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id)
> -{
> - spice_return_if_fail(info->num_memslots_groups > slot_group_id);
> - spice_return_if_fail(info->num_memslots > slot_id);
> -
> - info->mem_slots[slot_group_id][slot_id].virt_start_addr = 0;
> - info->mem_slots[slot_group_id][slot_id].virt_end_addr = 0;
> -}
> -
> -void memslot_info_reset(RedMemSlotInfo *info)
> -{
> - uint32_t i;
> - for (i = 0; i < info->num_memslots_groups; ++i) {
> - memset(info->mem_slots[i], 0, sizeof(MemSlot) * info->num_memslots);
> - }
> -}
> diff --git a/server/red_memslots.h b/server/red_memslots.h
> deleted file mode 100644
> index a40050c..0000000
> --- a/server/red_memslots.h
> +++ /dev/null
> @@ -1,72 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009,2010 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/>.
> -*/
> -
> -#ifndef _H_REDMEMSLOTS
> -#define _H_REDMEMSLOTS
> -
> -#include "red_common.h"
> -
> -#include <spice/qxl_dev.h>
> -
> -typedef struct MemSlot {
> - int generation;
> - unsigned long virt_start_addr;
> - unsigned long virt_end_addr;
> - long address_delta;
> -} MemSlot;
> -
> -typedef struct RedMemSlotInfo {
> - MemSlot **mem_slots;
> - uint32_t num_memslots_groups;
> - uint32_t num_memslots;
> - uint8_t mem_slot_bits;
> - uint8_t generation_bits;
> - uint8_t memslot_id_shift;
> - uint8_t memslot_gen_shift;
> - uint8_t internal_groupslot_id;
> - unsigned long memslot_gen_mask;
> - unsigned long memslot_clean_virt_mask;
> -} RedMemSlotInfo;
> -
> -static inline int memslot_get_id(RedMemSlotInfo *info, uint64_t addr)
> -{
> - return addr >> info->memslot_id_shift;
> -}
> -
> -static inline int memslot_get_generation(RedMemSlotInfo *info, uint64_t addr)
> -{
> - return (addr >> info->memslot_gen_shift) & info->memslot_gen_mask;
> -}
> -
> -int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id,
> - uint32_t add_size, uint32_t group_id);
> -unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size,
> - int group_id, int *error);
> -
> -void memslot_info_init(RedMemSlotInfo *info,
> - uint32_t num_groups, uint32_t num_slots,
> - uint8_t generation_bits,
> - uint8_t id_bits,
> - uint8_t internal_groupslot_id);
> -void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id,
> - uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end,
> - uint32_t generation);
> -void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id);
> -void memslot_info_reset(RedMemSlotInfo *info);
> -
> -#endif
> diff --git a/server/red_parse_qxl.c b/server/red_parse_qxl.c
> index 6e2ca99..522a915 100644
> --- a/server/red_parse_qxl.c
> +++ b/server/red_parse_qxl.c
> @@ -25,7 +25,7 @@
> #include "common/lz_common.h"
> #include "spice-bitmap-utils.h"
> #include "red_common.h"
> -#include "red_memslots.h"
> +#include "memslot.h"
> #include "red_parse_qxl.h"
>
> /* Max size in bytes for any data field used in a QXL command.
> diff --git a/server/red_parse_qxl.h b/server/red_parse_qxl.h
> index b3b28e1..09059f5 100644
> --- a/server/red_parse_qxl.h
> +++ b/server/red_parse_qxl.h
> @@ -21,7 +21,7 @@
>
> #include <spice/qxl_dev.h>
> #include "red_common.h"
> -#include "red_memslots.h"
> +#include "memslot.h"
>
> typedef struct RedDrawable {
> int refs;
> diff --git a/server/red_record_qxl.c b/server/red_record_qxl.c
> index 17f17bd..52c0e81 100644
> --- a/server/red_record_qxl.c
> +++ b/server/red_record_qxl.c
> @@ -23,9 +23,9 @@
> #include <inttypes.h>
> #include "red_worker.h"
> #include "red_common.h"
> -#include "red_memslots.h"
> +#include "memslot.h"
> #include "red_parse_qxl.h"
> -#include "zlib_encoder.h"
> +#include "zlib-encoder.h"
>
> #if 0
> static void hexdump_qxl(RedMemSlotInfo *slots, int group_id,
> diff --git a/server/red_record_qxl.h b/server/red_record_qxl.h
> index b737db8..6fcbec9 100644
> --- a/server/red_record_qxl.h
> +++ b/server/red_record_qxl.h
> @@ -21,7 +21,7 @@
>
> #include <spice/qxl_dev.h>
> #include "red_common.h"
> -#include "red_memslots.h"
> +#include "memslot.h"
>
> void red_record_dev_input_primary_surface_create(
> FILE *fd, QXLDevSurfaceCreate *surface, uint8_t *line_0);
> diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c
> index ad1a8fd..6e32588 100644
> --- a/server/red_replay_qxl.c
> +++ b/server/red_replay_qxl.c
> @@ -26,7 +26,7 @@
> #include "reds.h"
> #include "red_worker.h"
> #include "red_common.h"
> -#include "red_memslots.h"
> +#include "memslot.h"
> #include "red_parse_qxl.h"
> #include "red_replay_qxl.h"
> #include <glib.h>
> diff --git a/server/reds.c b/server/reds.c
> index 8b3c3cb..5891034 100644
> --- a/server/reds.c
> +++ b/server/reds.c
> @@ -56,16 +56,16 @@
> #include "spice.h"
> #include "reds.h"
> #include "agent-msg-filter.h"
> -#include "inputs_channel.h"
> -#include "main_channel.h"
> +#include "inputs-channel.h"
> +#include "main-channel.h"
> #include "red_common.h"
> #include "red_dispatcher.h"
> -#include "main_dispatcher.h"
> -#include "snd_worker.h"
> +#include "main-dispatcher.h"
> +#include "sound.h"
> #include "stat.h"
> #include "demarshallers.h"
> -#include "char_device.h"
> -#include "migration_protocol.h"
> +#include "char-device.h"
> +#include "migration-protocol.h"
> #ifdef USE_SMARTCARD
> #include "smartcard.h"
> #endif
> diff --git a/server/reds.h b/server/reds.h
> index fcdc5eb..0584694 100644
> --- a/server/reds.h
> +++ b/server/reds.h
> @@ -28,7 +28,7 @@
> #include "common/messages.h"
> #include "spice.h"
> #include "red_channel.h"
> -#include "migration_protocol.h"
> +#include "migration-protocol.h"
>
> struct QXLState {
> QXLInterface *qif;
> @@ -75,7 +75,7 @@ extern SpiceImageCompression image_compression;
> extern spice_wan_compression_t jpeg_state;
> extern spice_wan_compression_t zlib_glz_state;
>
> -// Temporary measures to make splitting reds.c to inputs_channel.c easier
> +// Temporary measures to make splitting reds.c to inputs-channel.c easier
>
> /* should be called only from main_dispatcher */
> void reds_client_disconnect(RedClient *client);
> diff --git a/server/reds_stream.c b/server/reds_stream.c
> index 3b47391..6f5c43f 100644
> --- a/server/reds_stream.c
> +++ b/server/reds_stream.c
> @@ -19,7 +19,7 @@
> #include <config.h>
> #endif
>
> -#include "main_dispatcher.h"
> +#include "main-dispatcher.h"
> #include "red_common.h"
> #include "reds_stream.h"
> #include "common/log.h"
> diff --git a/server/reds_sw_canvas.c b/server/reds_sw_canvas.c
> deleted file mode 100644
> index 297df37..0000000
> --- a/server/reds_sw_canvas.c
> +++ /dev/null
> @@ -1,26 +0,0 @@
> -/*
> - Copyright (C) 2011 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/spice_common.h"
> -
> -#include "reds_sw_canvas.h"
> -#define SW_CANVAS_IMAGE_CACHE
> -#include "common/sw_canvas.c"
> -#undef SW_CANVAS_IMAGE_CACHE
> diff --git a/server/reds_sw_canvas.h b/server/reds_sw_canvas.h
> deleted file mode 100644
> index 96a4c0c..0000000
> --- a/server/reds_sw_canvas.h
> +++ /dev/null
> @@ -1,24 +0,0 @@
> -/*
> - Copyright (C) 2011 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/>.
> -*/
> -#ifndef _H_REDS_SW_CANVAS
> -#define _H_REDS_SW_CANVAS
> -
> -#define SW_CANVAS_IMAGE_CACHE
> -#include "common/sw_canvas.h"
> -#undef SW_CANVAS_IMAGE_CACHE
> -
> -#endif
> diff --git a/server/smartcard.c b/server/smartcard.c
> index aad22aa..928e27b8 100644
> --- a/server/smartcard.c
> +++ b/server/smartcard.c
> @@ -23,10 +23,10 @@
> #include <vscard_common.h>
>
> #include "reds.h"
> -#include "char_device.h"
> +#include "char-device.h"
> #include "red_channel.h"
> #include "smartcard.h"
> -#include "migration_protocol.h"
> +#include "migration-protocol.h"
>
> /*
> * TODO: the code doesn't really support multiple readers.
> diff --git a/server/snd_worker.c b/server/snd_worker.c
> deleted file mode 100644
> index b039939..0000000
> --- a/server/snd_worker.c
> +++ /dev/null
> @@ -1,1625 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009 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 <fcntl.h>
> -#include <errno.h>
> -#include <limits.h>
> -#include <sys/socket.h>
> -#include <netinet/ip.h>
> -#include <netinet/tcp.h>
> -
> -#include "common/marshaller.h"
> -#include "common/generated_server_marshallers.h"
> -
> -#include "spice.h"
> -#include "red_common.h"
> -#include "main_channel.h"
> -#include "reds.h"
> -#include "red_dispatcher.h"
> -#include "snd_worker.h"
> -#include "common/snd_codec.h"
> -#include "demarshallers.h"
> -
> -#ifndef IOV_MAX
> -#define IOV_MAX 1024
> -#endif
> -
> -#define SND_RECEIVE_BUF_SIZE (16 * 1024 * 2)
> -#define RECORD_SAMPLES_SIZE (SND_RECEIVE_BUF_SIZE >> 2)
> -
> -enum PlaybackCommand {
> - SND_PLAYBACK_MIGRATE,
> - SND_PLAYBACK_MODE,
> - SND_PLAYBACK_CTRL,
> - SND_PLAYBACK_PCM,
> - SND_PLAYBACK_VOLUME,
> - SND_PLAYBACK_LATENCY,
> -};
> -
> -enum RecordCommand {
> - SND_RECORD_MIGRATE,
> - SND_RECORD_CTRL,
> - SND_RECORD_VOLUME,
> -};
> -
> -#define SND_PLAYBACK_MIGRATE_MASK (1 << SND_PLAYBACK_MIGRATE)
> -#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE)
> -#define SND_PLAYBACK_CTRL_MASK (1 << SND_PLAYBACK_CTRL)
> -#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM)
> -#define SND_PLAYBACK_VOLUME_MASK (1 << SND_PLAYBACK_VOLUME)
> -#define SND_PLAYBACK_LATENCY_MASK ( 1 << SND_PLAYBACK_LATENCY)
> -
> -#define SND_RECORD_MIGRATE_MASK (1 << SND_RECORD_MIGRATE)
> -#define SND_RECORD_CTRL_MASK (1 << SND_RECORD_CTRL)
> -#define SND_RECORD_VOLUME_MASK (1 << SND_RECORD_VOLUME)
> -
> -typedef struct SndChannel SndChannel;
> -typedef void (*snd_channel_send_messages_proc)(void *in_channel);
> -typedef int (*snd_channel_handle_message_proc)(SndChannel *channel, size_t size, uint32_t type, void *message);
> -typedef void (*snd_channel_on_message_done_proc)(SndChannel *channel);
> -typedef void (*snd_channel_cleanup_channel_proc)(SndChannel *channel);
> -
> -typedef struct SndWorker SndWorker;
> -
> -struct SndChannel {
> - RedsStream *stream;
> - SndWorker *worker;
> - spice_parse_channel_func_t parser;
> - int refs;
> -
> - RedChannelClient *channel_client;
> -
> - int active;
> - int client_active;
> - int blocked;
> -
> - uint32_t command;
> - uint32_t ack_generation;
> - uint32_t client_ack_generation;
> - uint32_t out_messages;
> - uint32_t ack_messages;
> -
> - struct {
> - uint64_t serial;
> - SpiceMarshaller *marshaller;
> - uint32_t size;
> - uint32_t pos;
> - } send_data;
> -
> - struct {
> - uint8_t buf[SND_RECEIVE_BUF_SIZE];
> - uint8_t *message_start;
> - uint8_t *now;
> - uint8_t *end;
> - } receive_data;
> -
> - snd_channel_send_messages_proc send_messages;
> - snd_channel_handle_message_proc handle_message;
> - snd_channel_on_message_done_proc on_message_done;
> - snd_channel_cleanup_channel_proc cleanup;
> -};
> -
> -typedef struct PlaybackChannel PlaybackChannel;
> -
> -typedef struct AudioFrame AudioFrame;
> -struct AudioFrame {
> - uint32_t time;
> - uint32_t samples[SND_CODEC_MAX_FRAME_SIZE];
> - PlaybackChannel *channel;
> - AudioFrame *next;
> -};
> -
> -struct PlaybackChannel {
> - SndChannel base;
> - AudioFrame frames[3];
> - AudioFrame *free_frames;
> - AudioFrame *in_progress;
> - AudioFrame *pending_frame;
> - uint32_t mode;
> - uint32_t latency;
> - SndCodec codec;
> - uint8_t encode_buf[SND_CODEC_MAX_COMPRESSED_BYTES];
> -};
> -
> -struct SndWorker {
> - RedChannel *base_channel;
> - SndChannel *connection;
> - SndWorker *next;
> - int active;
> -};
> -
> -typedef struct SpiceVolumeState {
> - uint8_t volume_nchannels;
> - uint16_t *volume;
> - int mute;
> -} SpiceVolumeState;
> -
> -struct SpicePlaybackState {
> - struct SndWorker worker;
> - SpicePlaybackInstance *sin;
> - SpiceVolumeState volume;
> - uint32_t frequency;
> -};
> -
> -struct SpiceRecordState {
> - struct SndWorker worker;
> - SpiceRecordInstance *sin;
> - SpiceVolumeState volume;
> - uint32_t frequency;
> -};
> -
> -typedef struct RecordChannel {
> - SndChannel base;
> - uint32_t samples[RECORD_SAMPLES_SIZE];
> - uint32_t write_pos;
> - uint32_t read_pos;
> - uint32_t mode;
> - uint32_t mode_time;
> - uint32_t start_time;
> - SndCodec codec;
> - uint8_t decode_buf[SND_CODEC_MAX_FRAME_BYTES];
> -} RecordChannel;
> -
> -static SndWorker *workers;
> -static uint32_t playback_compression = TRUE;
> -
> -static void snd_receive(void* data);
> -
> -static SndChannel *snd_channel_get(SndChannel *channel)
> -{
> - channel->refs++;
> - return channel;
> -}
> -
> -static SndChannel *snd_channel_put(SndChannel *channel)
> -{
> - if (!--channel->refs) {
> - spice_printerr("SndChannel=%p freed", channel);
> - free(channel);
> - return NULL;
> - }
> - return channel;
> -}
> -
> -static void snd_disconnect_channel(SndChannel *channel)
> -{
> - SndWorker *worker;
> -
> - if (!channel || !channel->stream) {
> - spice_debug("not connected");
> - return;
> - }
> - spice_debug("SndChannel=%p rcc=%p type=%d",
> - channel, channel->channel_client, channel->channel_client->channel->type);
> - worker = channel->worker;
> - channel->cleanup(channel);
> - red_channel_client_disconnect(worker->connection->channel_client);
> - worker->connection->channel_client = NULL;
> - core->watch_remove(channel->stream->watch);
> - channel->stream->watch = NULL;
> - reds_stream_free(channel->stream);
> - channel->stream = NULL;
> - spice_marshaller_destroy(channel->send_data.marshaller);
> - snd_channel_put(channel);
> - worker->connection = NULL;
> -}
> -
> -static void snd_playback_free_frame(PlaybackChannel *playback_channel, AudioFrame *frame)
> -{
> - frame->channel = playback_channel;
> - frame->next = playback_channel->free_frames;
> - playback_channel->free_frames = frame;
> -}
> -
> -static void snd_playback_on_message_done(SndChannel *channel)
> -{
> - PlaybackChannel *playback_channel = (PlaybackChannel *)channel;
> - if (playback_channel->in_progress) {
> - snd_playback_free_frame(playback_channel, playback_channel->in_progress);
> - playback_channel->in_progress = NULL;
> - if (playback_channel->pending_frame) {
> - channel->command |= SND_PLAYBACK_PCM_MASK;
> - }
> - }
> -}
> -
> -static void snd_record_on_message_done(SndChannel *channel)
> -{
> -}
> -
> -static int snd_send_data(SndChannel *channel)
> -{
> - uint32_t n;
> -
> - if (!channel) {
> - return FALSE;
> - }
> -
> - if (!(n = channel->send_data.size - channel->send_data.pos)) {
> - return TRUE;
> - }
> -
> - for (;;) {
> - struct iovec vec[IOV_MAX];
> - int vec_size;
> -
> - if (!n) {
> - channel->on_message_done(channel);
> -
> - if (channel->blocked) {
> - channel->blocked = FALSE;
> - core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ);
> - }
> - break;
> - }
> -
> - vec_size = spice_marshaller_fill_iovec(channel->send_data.marshaller,
> - vec, IOV_MAX, channel->send_data.pos);
> - n = reds_stream_writev(channel->stream, vec, vec_size);
> - if (n == -1) {
> - switch (errno) {
> - case EAGAIN:
> - channel->blocked = TRUE;
> - core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ |
> - SPICE_WATCH_EVENT_WRITE);
> - return FALSE;
> - case EINTR:
> - break;
> - case EPIPE:
> - snd_disconnect_channel(channel);
> - return FALSE;
> - default:
> - spice_printerr("%s", strerror(errno));
> - snd_disconnect_channel(channel);
> - return FALSE;
> - }
> - } else {
> - channel->send_data.pos += n;
> - }
> - n = channel->send_data.size - channel->send_data.pos;
> - }
> - return TRUE;
> -}
> -
> -static int snd_record_handle_write(RecordChannel *record_channel, size_t size, void *message)
> -{
> - SpiceMsgcRecordPacket *packet;
> - uint32_t write_pos;
> - uint32_t* data;
> - uint32_t len;
> - uint32_t now;
> -
> - if (!record_channel) {
> - return FALSE;
> - }
> -
> - packet = (SpiceMsgcRecordPacket *)message;
> -
> - if (record_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) {
> - data = (uint32_t *)packet->data;
> - size = packet->data_size >> 2;
> - size = MIN(size, RECORD_SAMPLES_SIZE);
> - } else {
> - int decode_size;
> - decode_size = sizeof(record_channel->decode_buf);
> - if (snd_codec_decode(record_channel->codec, packet->data, packet->data_size,
> - record_channel->decode_buf, &decode_size) != SND_CODEC_OK)
> - return FALSE;
> - data = (uint32_t *) record_channel->decode_buf;
> - size = decode_size >> 2;
> - }
> -
> - write_pos = record_channel->write_pos % RECORD_SAMPLES_SIZE;
> - record_channel->write_pos += size;
> - len = RECORD_SAMPLES_SIZE - write_pos;
> - now = MIN(len, size);
> - size -= now;
> - memcpy(record_channel->samples + write_pos, data, now << 2);
> -
> - if (size) {
> - memcpy(record_channel->samples, data + now, size << 2);
> - }
> -
> - if (record_channel->write_pos - record_channel->read_pos > RECORD_SAMPLES_SIZE) {
> - record_channel->read_pos = record_channel->write_pos - RECORD_SAMPLES_SIZE;
> - }
> - return TRUE;
> -}
> -
> -static int snd_playback_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message)
> -{
> - if (!channel) {
> - return FALSE;
> - }
> -
> - switch (type) {
> - case SPICE_MSGC_DISCONNECTING:
> - break;
> - default:
> - spice_printerr("invalid message type %u", type);
> - return FALSE;
> - }
> - return TRUE;
> -}
> -
> -static int snd_record_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message)
> -{
> - RecordChannel *record_channel = (RecordChannel *)channel;
> -
> - if (!channel) {
> - return FALSE;
> - }
> - switch (type) {
> - case SPICE_MSGC_RECORD_DATA:
> - return snd_record_handle_write((RecordChannel *)channel, size, message);
> - case SPICE_MSGC_RECORD_MODE: {
> - SpiceMsgcRecordMode *mode = (SpiceMsgcRecordMode *)message;
> - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> - record_channel->mode_time = mode->time;
> - if (mode->mode != SPICE_AUDIO_DATA_MODE_RAW) {
> - if (snd_codec_is_capable(mode->mode, st->frequency)) {
> - if (snd_codec_create(&record_channel->codec, mode->mode, st->frequency, SND_CODEC_DECODE) == SND_CODEC_OK) {
> - record_channel->mode = mode->mode;
> - } else {
> - spice_printerr("create decoder failed");
> - return FALSE;
> - }
> - }
> - else {
> - spice_printerr("unsupported mode %d", record_channel->mode);
> - return FALSE;
> - }
> - }
> - else
> - record_channel->mode = mode->mode;
> - break;
> - }
> -
> - case SPICE_MSGC_RECORD_START_MARK: {
> - SpiceMsgcRecordStartMark *mark = (SpiceMsgcRecordStartMark *)message;
> - record_channel->start_time = mark->time;
> - break;
> - }
> - case SPICE_MSGC_DISCONNECTING:
> - break;
> - default:
> - spice_printerr("invalid message type %u", type);
> - return FALSE;
> - }
> - return TRUE;
> -}
> -
> -static void snd_receive(void* data)
> -{
> - SndChannel *channel = (SndChannel*)data;
> - SpiceDataHeaderOpaque *header;
> -
> - if (!channel) {
> - return;
> - }
> -
> - header = &channel->channel_client->incoming.header;
> -
> - for (;;) {
> - ssize_t n;
> - n = channel->receive_data.end - channel->receive_data.now;
> - spice_warn_if(n <= 0);
> - n = reds_stream_read(channel->stream, channel->receive_data.now, n);
> - if (n <= 0) {
> - if (n == 0) {
> - snd_disconnect_channel(channel);
> - return;
> - }
> - spice_assert(n == -1);
> - switch (errno) {
> - case EAGAIN:
> - return;
> - case EINTR:
> - break;
> - case EPIPE:
> - snd_disconnect_channel(channel);
> - return;
> - default:
> - spice_printerr("%s", strerror(errno));
> - snd_disconnect_channel(channel);
> - return;
> - }
> - } else {
> - channel->receive_data.now += n;
> - for (;;) {
> - uint8_t *msg_start = channel->receive_data.message_start;
> - uint8_t *data = msg_start + header->header_size;
> - size_t parsed_size;
> - uint8_t *parsed;
> - message_destructor_t parsed_free;
> -
> - header->data = msg_start;
> - n = channel->receive_data.now - msg_start;
> -
> - if (n < header->header_size ||
> - n < header->header_size + header->get_msg_size(header)) {
> - break;
> - }
> - parsed = channel->parser((void *)data, data + header->get_msg_size(header),
> - header->get_msg_type(header),
> - SPICE_VERSION_MINOR, &parsed_size, &parsed_free);
> - if (parsed == NULL) {
> - spice_printerr("failed to parse message type %d", header->get_msg_type(header));
> - snd_disconnect_channel(channel);
> - return;
> - }
> - if (!channel->handle_message(channel, parsed_size,
> - header->get_msg_type(header), parsed)) {
> - free(parsed);
> - snd_disconnect_channel(channel);
> - return;
> - }
> - parsed_free(parsed);
> - channel->receive_data.message_start = msg_start + header->header_size +
> - header->get_msg_size(header);
> - }
> - if (channel->receive_data.now == channel->receive_data.message_start) {
> - channel->receive_data.now = channel->receive_data.buf;
> - channel->receive_data.message_start = channel->receive_data.buf;
> - } else if (channel->receive_data.now == channel->receive_data.end) {
> - memcpy(channel->receive_data.buf, channel->receive_data.message_start, n);
> - channel->receive_data.now = channel->receive_data.buf + n;
> - channel->receive_data.message_start = channel->receive_data.buf;
> - }
> - }
> - }
> -}
> -
> -static void snd_event(int fd, int event, void *data)
> -{
> - SndChannel *channel = data;
> -
> - if (event & SPICE_WATCH_EVENT_READ) {
> - snd_receive(channel);
> - }
> - if (event & SPICE_WATCH_EVENT_WRITE) {
> - channel->send_messages(channel);
> - }
> -}
> -
> -static inline int snd_reset_send_data(SndChannel *channel, uint16_t verb)
> -{
> - SpiceDataHeaderOpaque *header;
> -
> - if (!channel) {
> - return FALSE;
> - }
> -
> - header = &channel->channel_client->send_data.header;
> - spice_marshaller_reset(channel->send_data.marshaller);
> - header->data = spice_marshaller_reserve_space(channel->send_data.marshaller,
> - header->header_size);
> - spice_marshaller_set_base(channel->send_data.marshaller,
> - header->header_size);
> - channel->send_data.pos = 0;
> - header->set_msg_size(header, 0);
> - header->set_msg_type(header, verb);
> - channel->send_data.serial++;
> - if (!channel->channel_client->is_mini_header) {
> - header->set_msg_serial(header, channel->send_data.serial);
> - header->set_msg_sub_list(header, 0);
> - }
> -
> - return TRUE;
> -}
> -
> -static int snd_begin_send_message(SndChannel *channel)
> -{
> - SpiceDataHeaderOpaque *header = &channel->channel_client->send_data.header;
> -
> - spice_marshaller_flush(channel->send_data.marshaller);
> - channel->send_data.size = spice_marshaller_get_total_size(channel->send_data.marshaller);
> - header->set_msg_size(header, channel->send_data.size - header->header_size);
> - return snd_send_data(channel);
> -}
> -
> -static int snd_channel_send_migrate(SndChannel *channel)
> -{
> - SpiceMsgMigrate migrate;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_MIGRATE)) {
> - return FALSE;
> - }
> - spice_debug(NULL);
> - migrate.flags = 0;
> - spice_marshall_msg_migrate(channel->send_data.marshaller, &migrate);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_playback_send_migrate(PlaybackChannel *channel)
> -{
> - return snd_channel_send_migrate(&channel->base);
> -}
> -
> -static int snd_send_volume(SndChannel *channel, SpiceVolumeState *st, int msg)
> -{
> - SpiceMsgAudioVolume *vol;
> - uint8_t c;
> -
> - vol = alloca(sizeof (SpiceMsgAudioVolume) +
> - st->volume_nchannels * sizeof (uint16_t));
> - if (!snd_reset_send_data(channel, msg)) {
> - return FALSE;
> - }
> - vol->nchannels = st->volume_nchannels;
> - for (c = 0; c < st->volume_nchannels; ++c) {
> - vol->volume[c] = st->volume[c];
> - }
> - spice_marshall_SpiceMsgAudioVolume(channel->send_data.marshaller, vol);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_playback_send_volume(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = &playback_channel->base;
> - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> -
> - if (!red_channel_client_test_remote_cap(channel->channel_client,
> - SPICE_PLAYBACK_CAP_VOLUME)) {
> - return TRUE;
> - }
> -
> - return snd_send_volume(channel, &st->volume, SPICE_MSG_PLAYBACK_VOLUME);
> -}
> -
> -static int snd_send_mute(SndChannel *channel, SpiceVolumeState *st, int msg)
> -{
> - SpiceMsgAudioMute mute;
> -
> - if (!snd_reset_send_data(channel, msg)) {
> - return FALSE;
> - }
> - mute.mute = st->mute;
> - spice_marshall_SpiceMsgAudioMute(channel->send_data.marshaller, &mute);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_playback_send_mute(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = &playback_channel->base;
> - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> -
> - if (!red_channel_client_test_remote_cap(channel->channel_client,
> - SPICE_PLAYBACK_CAP_VOLUME)) {
> - return TRUE;
> - }
> -
> - return snd_send_mute(channel, &st->volume, SPICE_MSG_PLAYBACK_MUTE);
> -}
> -
> -static int snd_playback_send_latency(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = &playback_channel->base;
> - SpiceMsgPlaybackLatency latency_msg;
> -
> - spice_debug("latency %u", playback_channel->latency);
> - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_LATENCY)) {
> - return FALSE;
> - }
> - latency_msg.latency_ms = playback_channel->latency;
> - spice_marshall_msg_playback_latency(channel->send_data.marshaller, &latency_msg);
> -
> - return snd_begin_send_message(channel);
> -}
> -static int snd_playback_send_start(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = (SndChannel *)playback_channel;
> - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> - SpiceMsgPlaybackStart start;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_START)) {
> - return FALSE;
> - }
> -
> - start.channels = SPICE_INTERFACE_PLAYBACK_CHAN;
> - start.frequency = st->frequency;
> - spice_assert(SPICE_INTERFACE_PLAYBACK_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
> - start.format = SPICE_AUDIO_FMT_S16;
> - start.time = reds_get_mm_time();
> - spice_marshall_msg_playback_start(channel->send_data.marshaller, &start);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_playback_send_stop(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = (SndChannel *)playback_channel;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_STOP)) {
> - return FALSE;
> - }
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_playback_send_ctl(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = (SndChannel *)playback_channel;
> -
> - if ((channel->client_active = channel->active)) {
> - return snd_playback_send_start(playback_channel);
> - } else {
> - return snd_playback_send_stop(playback_channel);
> - }
> -}
> -
> -static int snd_record_send_start(RecordChannel *record_channel)
> -{
> - SndChannel *channel = (SndChannel *)record_channel;
> - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> - SpiceMsgRecordStart start;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_START)) {
> - return FALSE;
> - }
> -
> - start.channels = SPICE_INTERFACE_RECORD_CHAN;
> - start.frequency = st->frequency;
> - spice_assert(SPICE_INTERFACE_RECORD_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
> - start.format = SPICE_AUDIO_FMT_S16;
> - spice_marshall_msg_record_start(channel->send_data.marshaller, &start);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_record_send_stop(RecordChannel *record_channel)
> -{
> - SndChannel *channel = (SndChannel *)record_channel;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_STOP)) {
> - return FALSE;
> - }
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int snd_record_send_ctl(RecordChannel *record_channel)
> -{
> - SndChannel *channel = (SndChannel *)record_channel;
> -
> - if ((channel->client_active = channel->active)) {
> - return snd_record_send_start(record_channel);
> - } else {
> - return snd_record_send_stop(record_channel);
> - }
> -}
> -
> -static int snd_record_send_volume(RecordChannel *record_channel)
> -{
> - SndChannel *channel = &record_channel->base;
> - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> -
> - if (!red_channel_client_test_remote_cap(channel->channel_client,
> - SPICE_RECORD_CAP_VOLUME)) {
> - return TRUE;
> - }
> -
> - return snd_send_volume(channel, &st->volume, SPICE_MSG_RECORD_VOLUME);
> -}
> -
> -static int snd_record_send_mute(RecordChannel *record_channel)
> -{
> - SndChannel *channel = &record_channel->base;
> - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> -
> - if (!red_channel_client_test_remote_cap(channel->channel_client,
> - SPICE_RECORD_CAP_VOLUME)) {
> - return TRUE;
> - }
> -
> - return snd_send_mute(channel, &st->volume, SPICE_MSG_RECORD_MUTE);
> -}
> -
> -static int snd_record_send_migrate(RecordChannel *record_channel)
> -{
> - /* No need for migration data: if recording has started before migration,
> - * the client receives RECORD_STOP from the src before the migration completion
> - * notification (when the vm is stopped).
> - * Afterwards, when the vm starts on the dest, the client receives RECORD_START. */
> - return snd_channel_send_migrate(&record_channel->base);
> -}
> -
> -static int snd_playback_send_write(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = (SndChannel *)playback_channel;
> - AudioFrame *frame;
> - SpiceMsgPlaybackPacket msg;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_DATA)) {
> - return FALSE;
> - }
> -
> - frame = playback_channel->in_progress;
> - msg.time = frame->time;
> -
> - spice_marshall_msg_playback_data(channel->send_data.marshaller, &msg);
> -
> - if (playback_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) {
> - spice_marshaller_add_ref(channel->send_data.marshaller,
> - (uint8_t *)frame->samples,
> - snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]));
> - }
> - else {
> - int n = sizeof(playback_channel->encode_buf);
> - if (snd_codec_encode(playback_channel->codec, (uint8_t *) frame->samples,
> - snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]),
> - playback_channel->encode_buf, &n) != SND_CODEC_OK) {
> - spice_printerr("encode failed");
> - snd_disconnect_channel(channel);
> - return FALSE;
> - }
> - spice_marshaller_add_ref(channel->send_data.marshaller, playback_channel->encode_buf, n);
> - }
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static int playback_send_mode(PlaybackChannel *playback_channel)
> -{
> - SndChannel *channel = (SndChannel *)playback_channel;
> - SpiceMsgPlaybackMode mode;
> -
> - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_MODE)) {
> - return FALSE;
> - }
> - mode.time = reds_get_mm_time();
> - mode.mode = playback_channel->mode;
> - spice_marshall_msg_playback_mode(channel->send_data.marshaller, &mode);
> -
> - return snd_begin_send_message(channel);
> -}
> -
> -static void snd_playback_send(void* data)
> -{
> - PlaybackChannel *playback_channel = (PlaybackChannel*)data;
> - SndChannel *channel = (SndChannel*)playback_channel;
> -
> - if (!playback_channel || !snd_send_data(data)) {
> - return;
> - }
> -
> - while (channel->command) {
> - if (channel->command & SND_PLAYBACK_MODE_MASK) {
> - if (!playback_send_mode(playback_channel)) {
> - return;
> - }
> - channel->command &= ~SND_PLAYBACK_MODE_MASK;
> - }
> - if (channel->command & SND_PLAYBACK_PCM_MASK) {
> - spice_assert(!playback_channel->in_progress && playback_channel->pending_frame);
> - playback_channel->in_progress = playback_channel->pending_frame;
> - playback_channel->pending_frame = NULL;
> - channel->command &= ~SND_PLAYBACK_PCM_MASK;
> - if (!snd_playback_send_write(playback_channel)) {
> - spice_printerr("snd_send_playback_write failed");
> - return;
> - }
> - }
> - if (channel->command & SND_PLAYBACK_CTRL_MASK) {
> - if (!snd_playback_send_ctl(playback_channel)) {
> - return;
> - }
> - channel->command &= ~SND_PLAYBACK_CTRL_MASK;
> - }
> - if (channel->command & SND_PLAYBACK_VOLUME_MASK) {
> - if (!snd_playback_send_volume(playback_channel) ||
> - !snd_playback_send_mute(playback_channel)) {
> - return;
> - }
> - channel->command &= ~SND_PLAYBACK_VOLUME_MASK;
> - }
> - if (channel->command & SND_PLAYBACK_MIGRATE_MASK) {
> - if (!snd_playback_send_migrate(playback_channel)) {
> - return;
> - }
> - channel->command &= ~SND_PLAYBACK_MIGRATE_MASK;
> - }
> - if (channel->command & SND_PLAYBACK_LATENCY_MASK) {
> - if (!snd_playback_send_latency(playback_channel)) {
> - return;
> - }
> - channel->command &= ~SND_PLAYBACK_LATENCY_MASK;
> - }
> - }
> -}
> -
> -static void snd_record_send(void* data)
> -{
> - RecordChannel *record_channel = (RecordChannel*)data;
> - SndChannel *channel = (SndChannel*)record_channel;
> -
> - if (!record_channel || !snd_send_data(data)) {
> - return;
> - }
> -
> - while (channel->command) {
> - if (channel->command & SND_RECORD_CTRL_MASK) {
> - if (!snd_record_send_ctl(record_channel)) {
> - return;
> - }
> - channel->command &= ~SND_RECORD_CTRL_MASK;
> - }
> - if (channel->command & SND_RECORD_VOLUME_MASK) {
> - if (!snd_record_send_volume(record_channel) ||
> - !snd_record_send_mute(record_channel)) {
> - return;
> - }
> - channel->command &= ~SND_RECORD_VOLUME_MASK;
> - }
> - if (channel->command & SND_RECORD_MIGRATE_MASK) {
> - if (!snd_record_send_migrate(record_channel)) {
> - return;
> - }
> - channel->command &= ~SND_RECORD_MIGRATE_MASK;
> - }
> - }
> -}
> -
> -static SndChannel *__new_channel(SndWorker *worker, int size, uint32_t channel_id,
> - RedClient *client,
> - RedsStream *stream,
> - int migrate,
> - snd_channel_send_messages_proc send_messages,
> - snd_channel_handle_message_proc handle_message,
> - snd_channel_on_message_done_proc on_message_done,
> - snd_channel_cleanup_channel_proc cleanup,
> - uint32_t *common_caps, int num_common_caps,
> - uint32_t *caps, int num_caps)
> -{
> - SndChannel *channel;
> - int delay_val;
> - int flags;
> -#ifdef SO_PRIORITY
> - int priority;
> -#endif
> - int tos;
> - MainChannelClient *mcc = red_client_get_main(client);
> -
> - spice_assert(stream);
> - if ((flags = fcntl(stream->socket, F_GETFL)) == -1) {
> - spice_printerr("accept failed, %s", strerror(errno));
> - goto error1;
> - }
> -
> -#ifdef SO_PRIORITY
> - priority = 6;
> - if (setsockopt(stream->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority,
> - sizeof(priority)) == -1) {
> - if (errno != ENOTSUP) {
> - spice_printerr("setsockopt failed, %s", strerror(errno));
> - }
> - }
> -#endif
> -
> - tos = IPTOS_LOWDELAY;
> - if (setsockopt(stream->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) {
> - if (errno != ENOTSUP) {
> - spice_printerr("setsockopt failed, %s", strerror(errno));
> - }
> - }
> -
> - delay_val = main_channel_client_is_low_bandwidth(mcc) ? 0 : 1;
> - if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
> - if (errno != ENOTSUP) {
> - spice_printerr("setsockopt failed, %s", strerror(errno));
> - }
> - }
> -
> - if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) {
> - spice_printerr("accept failed, %s", strerror(errno));
> - goto error1;
> - }
> -
> - spice_assert(size >= sizeof(*channel));
> - channel = spice_malloc0(size);
> - channel->refs = 1;
> - channel->parser = spice_get_client_channel_parser(channel_id, NULL);
> - channel->stream = stream;
> - channel->worker = worker;
> - channel->receive_data.message_start = channel->receive_data.buf;
> - channel->receive_data.now = channel->receive_data.buf;
> - channel->receive_data.end = channel->receive_data.buf + sizeof(channel->receive_data.buf);
> - channel->send_data.marshaller = spice_marshaller_new();
> -
> - stream->watch = core->watch_add(stream->socket, SPICE_WATCH_EVENT_READ,
> - snd_event, channel);
> - if (stream->watch == NULL) {
> - spice_printerr("watch_add failed, %s", strerror(errno));
> - goto error2;
> - }
> -
> - channel->send_messages = send_messages;
> - channel->handle_message = handle_message;
> - channel->on_message_done = on_message_done;
> - channel->cleanup = cleanup;
> -
> - channel->channel_client = red_channel_client_create_dummy(sizeof(RedChannelClient),
> - worker->base_channel,
> - client,
> - num_common_caps, common_caps,
> - num_caps, caps);
> - if (!channel->channel_client) {
> - goto error2;
> - }
> - return channel;
> -
> -error2:
> - free(channel);
> -
> -error1:
> - reds_stream_free(stream);
> - return NULL;
> -}
> -
> -static void snd_disconnect_channel_client(RedChannelClient *rcc)
> -{
> - SndWorker *worker;
> -
> - spice_assert(rcc->channel);
> - spice_assert(rcc->channel->data);
> - worker = (SndWorker *)rcc->channel->data;
> -
> - spice_debug("channel-type=%d", rcc->channel->type);
> - if (worker->connection) {
> - spice_assert(worker->connection->channel_client == rcc);
> - snd_disconnect_channel(worker->connection);
> - }
> -}
> -
> -static void snd_set_command(SndChannel *channel, uint32_t command)
> -{
> - if (!channel) {
> - return;
> - }
> - channel->command |= command;
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_set_volume(SpicePlaybackInstance *sin,
> - uint8_t nchannels,
> - uint16_t *volume)
> -{
> - SpiceVolumeState *st = &sin->st->volume;
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - st->volume_nchannels = nchannels;
> - free(st->volume);
> - st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels);
> -
> - if (!channel || nchannels == 0)
> - return;
> -
> - snd_playback_send_volume(playback_channel);
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_set_mute(SpicePlaybackInstance *sin, uint8_t mute)
> -{
> - SpiceVolumeState *st = &sin->st->volume;
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - st->mute = mute;
> -
> - if (!channel)
> - return;
> -
> - snd_playback_send_mute(playback_channel);
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_start(SpicePlaybackInstance *sin)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - sin->st->worker.active = 1;
> - if (!channel)
> - return;
> - spice_assert(!playback_channel->base.active);
> - reds_disable_mm_time();
> - playback_channel->base.active = TRUE;
> - if (!playback_channel->base.client_active) {
> - snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
> - snd_playback_send(&playback_channel->base);
> - } else {
> - playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
> - }
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_stop(SpicePlaybackInstance *sin)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - sin->st->worker.active = 0;
> - if (!channel)
> - return;
> - spice_assert(playback_channel->base.active);
> - reds_enable_mm_time();
> - playback_channel->base.active = FALSE;
> - if (playback_channel->base.client_active) {
> - snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
> - snd_playback_send(&playback_channel->base);
> - } else {
> - playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
> - playback_channel->base.command &= ~SND_PLAYBACK_PCM_MASK;
> -
> - if (playback_channel->pending_frame) {
> - spice_assert(!playback_channel->in_progress);
> - snd_playback_free_frame(playback_channel,
> - playback_channel->pending_frame);
> - playback_channel->pending_frame = NULL;
> - }
> - }
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_get_buffer(SpicePlaybackInstance *sin,
> - uint32_t **frame, uint32_t *num_samples)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - if (!channel || !playback_channel->free_frames) {
> - *frame = NULL;
> - *num_samples = 0;
> - return;
> - }
> - spice_assert(playback_channel->base.active);
> - snd_channel_get(channel);
> -
> - *frame = playback_channel->free_frames->samples;
> - playback_channel->free_frames = playback_channel->free_frames->next;
> - *num_samples = snd_codec_frame_size(playback_channel->codec);
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples)
> -{
> - PlaybackChannel *playback_channel;
> - AudioFrame *frame;
> -
> - frame = SPICE_CONTAINEROF(samples, AudioFrame, samples);
> - playback_channel = frame->channel;
> - spice_assert(playback_channel);
> - if (!snd_channel_put(&playback_channel->base) ||
> - sin->st->worker.connection != &playback_channel->base) {
> - /* lost last reference, channel has been destroyed previously */
> - spice_info("audio samples belong to a disconnected channel");
> - return;
> - }
> - spice_assert(playback_channel->base.active);
> -
> - if (playback_channel->pending_frame) {
> - snd_playback_free_frame(playback_channel, playback_channel->pending_frame);
> - }
> - frame->time = reds_get_mm_time();
> - playback_channel->pending_frame = frame;
> - snd_set_command(&playback_channel->base, SND_PLAYBACK_PCM_MASK);
> - snd_playback_send(&playback_channel->base);
> -}
> -
> -void snd_set_playback_latency(RedClient *client, uint32_t latency)
> -{
> - SndWorker *now = workers;
> -
> - for (; now; now = now->next) {
> - if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection &&
> - now->connection->channel_client->client == client) {
> -
> - if (red_channel_client_test_remote_cap(now->connection->channel_client,
> - SPICE_PLAYBACK_CAP_LATENCY)) {
> - PlaybackChannel* playback = (PlaybackChannel*)now->connection;
> -
> - playback->latency = latency;
> - snd_set_command(now->connection, SND_PLAYBACK_LATENCY_MASK);
> - snd_playback_send(now->connection);
> - } else {
> - spice_debug("client doesn't not support SPICE_PLAYBACK_CAP_LATENCY");
> - }
> - }
> - }
> -}
> -
> -static int snd_desired_audio_mode(int frequency, int client_can_celt, int client_can_opus)
> -{
> - if (! playback_compression)
> - return SPICE_AUDIO_DATA_MODE_RAW;
> -
> - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> - return SPICE_AUDIO_DATA_MODE_OPUS;
> -
> - if (client_can_celt && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency))
> - return SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
> -
> - return SPICE_AUDIO_DATA_MODE_RAW;
> -}
> -
> -static void on_new_playback_channel(SndWorker *worker)
> -{
> - PlaybackChannel *playback_channel =
> - SPICE_CONTAINEROF(worker->connection, PlaybackChannel, base);
> - SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker);
> -
> - spice_assert(playback_channel);
> -
> - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_MODE_MASK);
> - if (playback_channel->base.active) {
> - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_CTRL_MASK);
> - }
> - if (st->volume.volume_nchannels) {
> - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_VOLUME_MASK);
> - }
> - if (playback_channel->base.active) {
> - reds_disable_mm_time();
> - }
> -}
> -
> -static void snd_playback_cleanup(SndChannel *channel)
> -{
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> -
> - if (playback_channel->base.active) {
> - reds_enable_mm_time();
> - }
> -
> - snd_codec_destroy(&playback_channel->codec);
> -}
> -
> -static void snd_set_playback_peer(RedChannel *channel, RedClient *client, RedsStream *stream,
> - int migration, int num_common_caps, uint32_t *common_caps,
> - int num_caps, uint32_t *caps)
> -{
> - SndWorker *worker = channel->data;
> - PlaybackChannel *playback_channel;
> - SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker);
> -
> - snd_disconnect_channel(worker->connection);
> -
> - if (!(playback_channel = (PlaybackChannel *)__new_channel(worker,
> - sizeof(*playback_channel),
> - SPICE_CHANNEL_PLAYBACK,
> - client,
> - stream,
> - migration,
> - snd_playback_send,
> - snd_playback_handle_message,
> - snd_playback_on_message_done,
> - snd_playback_cleanup,
> - common_caps, num_common_caps,
> - caps, num_caps))) {
> - return;
> - }
> - worker->connection = &playback_channel->base;
> - snd_playback_free_frame(playback_channel, &playback_channel->frames[0]);
> - snd_playback_free_frame(playback_channel, &playback_channel->frames[1]);
> - snd_playback_free_frame(playback_channel, &playback_channel->frames[2]);
> -
> - int client_can_celt = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> - SPICE_PLAYBACK_CAP_CELT_0_5_1);
> - int client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> - SPICE_PLAYBACK_CAP_OPUS);
> - int desired_mode = snd_desired_audio_mode(st->frequency, client_can_celt, client_can_opus);
> - playback_channel->mode = SPICE_AUDIO_DATA_MODE_RAW;
> - if (desired_mode != SPICE_AUDIO_DATA_MODE_RAW) {
> - if (snd_codec_create(&playback_channel->codec, desired_mode, st->frequency, SND_CODEC_ENCODE) == SND_CODEC_OK) {
> - playback_channel->mode = desired_mode;
> - } else {
> - spice_printerr("create encoder failed");
> - }
> - }
> -
> - if (!red_client_during_migrate_at_target(client)) {
> - on_new_playback_channel(worker);
> - }
> -
> - if (worker->active) {
> - spice_server_playback_start(st->sin);
> - }
> - snd_playback_send(worker->connection);
> -}
> -
> -static void snd_record_migrate_channel_client(RedChannelClient *rcc)
> -{
> - SndWorker *worker;
> -
> - spice_debug(NULL);
> - spice_assert(rcc->channel);
> - spice_assert(rcc->channel->data);
> - worker = (SndWorker *)rcc->channel->data;
> -
> - if (worker->connection) {
> - spice_assert(worker->connection->channel_client == rcc);
> - snd_set_command(worker->connection, SND_RECORD_MIGRATE_MASK);
> - snd_record_send(worker->connection);
> - }
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_record_set_volume(SpiceRecordInstance *sin,
> - uint8_t nchannels,
> - uint16_t *volume)
> -{
> - SpiceVolumeState *st = &sin->st->volume;
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> -
> - st->volume_nchannels = nchannels;
> - free(st->volume);
> - st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels);
> -
> - if (!channel || nchannels == 0)
> - return;
> -
> - snd_record_send_volume(record_channel);
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_record_set_mute(SpiceRecordInstance *sin, uint8_t mute)
> -{
> - SpiceVolumeState *st = &sin->st->volume;
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> -
> - st->mute = mute;
> -
> - if (!channel)
> - return;
> -
> - snd_record_send_mute(record_channel);
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_record_start(SpiceRecordInstance *sin)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> -
> - sin->st->worker.active = 1;
> - if (!channel)
> - return;
> - spice_assert(!record_channel->base.active);
> - record_channel->base.active = TRUE;
> - record_channel->read_pos = record_channel->write_pos = 0; //todo: improve by
> - //stream generation
> - if (!record_channel->base.client_active) {
> - snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
> - snd_record_send(&record_channel->base);
> - } else {
> - record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
> - }
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_record_stop(SpiceRecordInstance *sin)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> -
> - sin->st->worker.active = 0;
> - if (!channel)
> - return;
> - spice_assert(record_channel->base.active);
> - record_channel->base.active = FALSE;
> - if (record_channel->base.client_active) {
> - snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
> - snd_record_send(&record_channel->base);
> - } else {
> - record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
> - }
> -}
> -
> -SPICE_GNUC_VISIBLE uint32_t spice_server_record_get_samples(SpiceRecordInstance *sin,
> - uint32_t *samples, uint32_t bufsize)
> -{
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> - uint32_t read_pos;
> - uint32_t now;
> - uint32_t len;
> -
> - if (!channel)
> - return 0;
> - spice_assert(record_channel->base.active);
> -
> - if (record_channel->write_pos < RECORD_SAMPLES_SIZE / 2) {
> - return 0;
> - }
> -
> - len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize);
> -
> - if (len < bufsize) {
> - SndWorker *worker = record_channel->base.worker;
> - snd_receive(record_channel);
> - if (!worker->connection) {
> - return 0;
> - }
> - len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize);
> - }
> -
> - read_pos = record_channel->read_pos % RECORD_SAMPLES_SIZE;
> - record_channel->read_pos += len;
> - now = MIN(len, RECORD_SAMPLES_SIZE - read_pos);
> - memcpy(samples, &record_channel->samples[read_pos], now * 4);
> - if (now < len) {
> - memcpy(samples + now, record_channel->samples, (len - now) * 4);
> - }
> - return len;
> -}
> -
> -SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_playback_rate(SpicePlaybackInstance *sin)
> -{
> - int client_can_opus = TRUE;
> - if (sin && sin->st->worker.connection) {
> - SndChannel *channel = sin->st->worker.connection;
> - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> - client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> - SPICE_PLAYBACK_CAP_OPUS);
> - }
> -
> - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
> - return SND_CODEC_OPUS_PLAYBACK_FREQ;
> -
> - return SND_CODEC_CELT_PLAYBACK_FREQ;
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_set_playback_rate(SpicePlaybackInstance *sin, uint32_t frequency)
> -{
> - RedChannel *channel = sin->st->worker.base_channel;
> - sin->st->frequency = frequency;
> - if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_OPUS);
> -}
> -
> -SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_record_rate(SpiceRecordInstance *sin)
> -{
> - int client_can_opus = TRUE;
> - if (sin && sin->st->worker.connection) {
> - SndChannel *channel = sin->st->worker.connection;
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> - client_can_opus = red_channel_client_test_remote_cap(record_channel->base.channel_client,
> - SPICE_RECORD_CAP_OPUS);
> - }
> -
> - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
> - return SND_CODEC_OPUS_PLAYBACK_FREQ;
> -
> - return SND_CODEC_CELT_PLAYBACK_FREQ;
> -}
> -
> -SPICE_GNUC_VISIBLE void spice_server_set_record_rate(SpiceRecordInstance *sin, uint32_t frequency)
> -{
> - RedChannel *channel = sin->st->worker.base_channel;
> - sin->st->frequency = frequency;
> - if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> - red_channel_set_cap(channel, SPICE_RECORD_CAP_OPUS);
> -}
> -
> -static void on_new_record_channel(SndWorker *worker)
> -{
> - RecordChannel *record_channel = (RecordChannel *)worker->connection;
> - SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker);
> -
> - spice_assert(record_channel);
> -
> - if (st->volume.volume_nchannels) {
> - snd_set_command((SndChannel *)record_channel, SND_RECORD_VOLUME_MASK);
> - }
> - if (record_channel->base.active) {
> - snd_set_command((SndChannel *)record_channel, SND_RECORD_CTRL_MASK);
> - }
> -}
> -
> -static void snd_record_cleanup(SndChannel *channel)
> -{
> - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> - snd_codec_destroy(&record_channel->codec);
> -}
> -
> -static void snd_set_record_peer(RedChannel *channel, RedClient *client, RedsStream *stream,
> - int migration, int num_common_caps, uint32_t *common_caps,
> - int num_caps, uint32_t *caps)
> -{
> - SndWorker *worker = channel->data;
> - RecordChannel *record_channel;
> - SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker);
> -
> - snd_disconnect_channel(worker->connection);
> -
> - if (!(record_channel = (RecordChannel *)__new_channel(worker,
> - sizeof(*record_channel),
> - SPICE_CHANNEL_RECORD,
> - client,
> - stream,
> - migration,
> - snd_record_send,
> - snd_record_handle_message,
> - snd_record_on_message_done,
> - snd_record_cleanup,
> - common_caps, num_common_caps,
> - caps, num_caps))) {
> - return;
> - }
> -
> - record_channel->mode = SPICE_AUDIO_DATA_MODE_RAW;
> -
> - worker->connection = &record_channel->base;
> -
> - on_new_record_channel(worker);
> - if (worker->active) {
> - spice_server_record_start(st->sin);
> - }
> - snd_record_send(worker->connection);
> -}
> -
> -static void snd_playback_migrate_channel_client(RedChannelClient *rcc)
> -{
> - SndWorker *worker;
> -
> - spice_assert(rcc->channel);
> - spice_assert(rcc->channel->data);
> - worker = (SndWorker *)rcc->channel->data;
> - spice_debug(NULL);
> -
> - if (worker->connection) {
> - spice_assert(worker->connection->channel_client == rcc);
> - snd_set_command(worker->connection, SND_PLAYBACK_MIGRATE_MASK);
> - snd_playback_send(worker->connection);
> - }
> -}
> -
> -static void add_worker(SndWorker *worker)
> -{
> - worker->next = workers;
> - workers = worker;
> -}
> -
> -static void remove_worker(SndWorker *worker)
> -{
> - SndWorker **now = &workers;
> - while (*now) {
> - if (*now == worker) {
> - *now = worker->next;
> - return;
> - }
> - now = &(*now)->next;
> - }
> - spice_printerr("not found");
> -}
> -
> -void snd_attach_playback(SpicePlaybackInstance *sin)
> -{
> - SndWorker *playback_worker;
> - RedChannel *channel;
> - ClientCbs client_cbs = { NULL, };
> -
> - sin->st = spice_new0(SpicePlaybackState, 1);
> - sin->st->sin = sin;
> - playback_worker = &sin->st->worker;
> - sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */
> -
> - // TODO: Make RedChannel base of worker? instead of assigning it to channel->data
> - channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_PLAYBACK, 0);
> -
> - channel->data = playback_worker;
> - client_cbs.connect = snd_set_playback_peer;
> - client_cbs.disconnect = snd_disconnect_channel_client;
> - client_cbs.migrate = snd_playback_migrate_channel_client;
> - red_channel_register_client_cbs(channel, &client_cbs);
> - red_channel_set_data(channel, playback_worker);
> -
> - if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
> - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_CELT_0_5_1);
> -
> - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_VOLUME);
> -
> - playback_worker->base_channel = channel;
> - add_worker(playback_worker);
> - reds_register_channel(playback_worker->base_channel);
> -}
> -
> -void snd_attach_record(SpiceRecordInstance *sin)
> -{
> - SndWorker *record_worker;
> - RedChannel *channel;
> - ClientCbs client_cbs = { NULL, };
> -
> - sin->st = spice_new0(SpiceRecordState, 1);
> - sin->st->sin = sin;
> - record_worker = &sin->st->worker;
> - sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */
> -
> - // TODO: Make RedChannel base of worker? instead of assigning it to channel->data
> - channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_RECORD, 0);
> -
> - channel->data = record_worker;
> - client_cbs.connect = snd_set_record_peer;
> - client_cbs.disconnect = snd_disconnect_channel_client;
> - client_cbs.migrate = snd_record_migrate_channel_client;
> - red_channel_register_client_cbs(channel, &client_cbs);
> - red_channel_set_data(channel, record_worker);
> - if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
> - red_channel_set_cap(channel, SPICE_RECORD_CAP_CELT_0_5_1);
> - red_channel_set_cap(channel, SPICE_RECORD_CAP_VOLUME);
> -
> - record_worker->base_channel = channel;
> - add_worker(record_worker);
> - reds_register_channel(record_worker->base_channel);
> -}
> -
> -static void snd_detach_common(SndWorker *worker)
> -{
> - if (!worker) {
> - return;
> - }
> - remove_worker(worker);
> - snd_disconnect_channel(worker->connection);
> - reds_unregister_channel(worker->base_channel);
> - red_channel_destroy(worker->base_channel);
> -}
> -
> -static void spice_playback_state_free(SpicePlaybackState *st)
> -{
> - free(st->volume.volume);
> - free(st);
> -}
> -
> -void snd_detach_playback(SpicePlaybackInstance *sin)
> -{
> - snd_detach_common(&sin->st->worker);
> - spice_playback_state_free(sin->st);
> -}
> -
> -static void spice_record_state_free(SpiceRecordState *st)
> -{
> - free(st->volume.volume);
> - free(st);
> -}
> -
> -void snd_detach_record(SpiceRecordInstance *sin)
> -{
> - snd_detach_common(&sin->st->worker);
> - spice_record_state_free(sin->st);
> -}
> -
> -void snd_set_playback_compression(int on)
> -{
> - SndWorker *now = workers;
> -
> - playback_compression = !!on;
> -
> - for (; now; now = now->next) {
> - if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection) {
> - PlaybackChannel* playback = (PlaybackChannel*)now->connection;
> - SpicePlaybackState *st = SPICE_CONTAINEROF(now, SpicePlaybackState, worker);
> - int client_can_celt = red_channel_client_test_remote_cap(playback->base.channel_client,
> - SPICE_PLAYBACK_CAP_CELT_0_5_1);
> - int client_can_opus = red_channel_client_test_remote_cap(playback->base.channel_client,
> - SPICE_PLAYBACK_CAP_OPUS);
> - int desired_mode = snd_desired_audio_mode(st->frequency, client_can_opus, client_can_celt);
> - if (playback->mode != desired_mode) {
> - playback->mode = desired_mode;
> - snd_set_command(now->connection, SND_PLAYBACK_MODE_MASK);
> - }
> - }
> - }
> -}
> diff --git a/server/snd_worker.h b/server/snd_worker.h
> deleted file mode 100644
> index 7cc4db5..0000000
> --- a/server/snd_worker.h
> +++ /dev/null
> @@ -1,33 +0,0 @@
> -/*
> - Copyright (C) 2009 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/>.
> -*/
> -
> -#ifndef _H_SND_WORKER
> -#define _H_SND_WORKER
> -
> -#include "spice.h"
> -
> -void snd_attach_playback(SpicePlaybackInstance *sin);
> -void snd_detach_playback(SpicePlaybackInstance *sin);
> -
> -void snd_attach_record(SpiceRecordInstance *sin);
> -void snd_detach_record(SpiceRecordInstance *sin);
> -
> -void snd_set_playback_compression(int on);
> -
> -void snd_set_playback_latency(RedClient *client, uint32_t latency);
> -
> -#endif
> diff --git a/server/sound.c b/server/sound.c
> new file mode 100644
> index 0000000..a91d56e
> --- /dev/null
> +++ b/server/sound.c
> @@ -0,0 +1,1625 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2009 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 <fcntl.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <sys/socket.h>
> +#include <netinet/ip.h>
> +#include <netinet/tcp.h>
> +
> +#include "common/marshaller.h"
> +#include "common/generated_server_marshallers.h"
> +
> +#include "spice.h"
> +#include "red_common.h"
> +#include "main-channel.h"
> +#include "reds.h"
> +#include "red_dispatcher.h"
> +#include "sound.h"
> +#include "common/snd_codec.h"
> +#include "demarshallers.h"
> +
> +#ifndef IOV_MAX
> +#define IOV_MAX 1024
> +#endif
> +
> +#define SND_RECEIVE_BUF_SIZE (16 * 1024 * 2)
> +#define RECORD_SAMPLES_SIZE (SND_RECEIVE_BUF_SIZE >> 2)
> +
> +enum PlaybackCommand {
> + SND_PLAYBACK_MIGRATE,
> + SND_PLAYBACK_MODE,
> + SND_PLAYBACK_CTRL,
> + SND_PLAYBACK_PCM,
> + SND_PLAYBACK_VOLUME,
> + SND_PLAYBACK_LATENCY,
> +};
> +
> +enum RecordCommand {
> + SND_RECORD_MIGRATE,
> + SND_RECORD_CTRL,
> + SND_RECORD_VOLUME,
> +};
> +
> +#define SND_PLAYBACK_MIGRATE_MASK (1 << SND_PLAYBACK_MIGRATE)
> +#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE)
> +#define SND_PLAYBACK_CTRL_MASK (1 << SND_PLAYBACK_CTRL)
> +#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM)
> +#define SND_PLAYBACK_VOLUME_MASK (1 << SND_PLAYBACK_VOLUME)
> +#define SND_PLAYBACK_LATENCY_MASK ( 1 << SND_PLAYBACK_LATENCY)
> +
> +#define SND_RECORD_MIGRATE_MASK (1 << SND_RECORD_MIGRATE)
> +#define SND_RECORD_CTRL_MASK (1 << SND_RECORD_CTRL)
> +#define SND_RECORD_VOLUME_MASK (1 << SND_RECORD_VOLUME)
> +
> +typedef struct SndChannel SndChannel;
> +typedef void (*snd_channel_send_messages_proc)(void *in_channel);
> +typedef int (*snd_channel_handle_message_proc)(SndChannel *channel, size_t size, uint32_t type, void *message);
> +typedef void (*snd_channel_on_message_done_proc)(SndChannel *channel);
> +typedef void (*snd_channel_cleanup_channel_proc)(SndChannel *channel);
> +
> +typedef struct SndWorker SndWorker;
> +
> +struct SndChannel {
> + RedsStream *stream;
> + SndWorker *worker;
> + spice_parse_channel_func_t parser;
> + int refs;
> +
> + RedChannelClient *channel_client;
> +
> + int active;
> + int client_active;
> + int blocked;
> +
> + uint32_t command;
> + uint32_t ack_generation;
> + uint32_t client_ack_generation;
> + uint32_t out_messages;
> + uint32_t ack_messages;
> +
> + struct {
> + uint64_t serial;
> + SpiceMarshaller *marshaller;
> + uint32_t size;
> + uint32_t pos;
> + } send_data;
> +
> + struct {
> + uint8_t buf[SND_RECEIVE_BUF_SIZE];
> + uint8_t *message_start;
> + uint8_t *now;
> + uint8_t *end;
> + } receive_data;
> +
> + snd_channel_send_messages_proc send_messages;
> + snd_channel_handle_message_proc handle_message;
> + snd_channel_on_message_done_proc on_message_done;
> + snd_channel_cleanup_channel_proc cleanup;
> +};
> +
> +typedef struct PlaybackChannel PlaybackChannel;
> +
> +typedef struct AudioFrame AudioFrame;
> +struct AudioFrame {
> + uint32_t time;
> + uint32_t samples[SND_CODEC_MAX_FRAME_SIZE];
> + PlaybackChannel *channel;
> + AudioFrame *next;
> +};
> +
> +struct PlaybackChannel {
> + SndChannel base;
> + AudioFrame frames[3];
> + AudioFrame *free_frames;
> + AudioFrame *in_progress;
> + AudioFrame *pending_frame;
> + uint32_t mode;
> + uint32_t latency;
> + SndCodec codec;
> + uint8_t encode_buf[SND_CODEC_MAX_COMPRESSED_BYTES];
> +};
> +
> +struct SndWorker {
> + RedChannel *base_channel;
> + SndChannel *connection;
> + SndWorker *next;
> + int active;
> +};
> +
> +typedef struct SpiceVolumeState {
> + uint8_t volume_nchannels;
> + uint16_t *volume;
> + int mute;
> +} SpiceVolumeState;
> +
> +struct SpicePlaybackState {
> + struct SndWorker worker;
> + SpicePlaybackInstance *sin;
> + SpiceVolumeState volume;
> + uint32_t frequency;
> +};
> +
> +struct SpiceRecordState {
> + struct SndWorker worker;
> + SpiceRecordInstance *sin;
> + SpiceVolumeState volume;
> + uint32_t frequency;
> +};
> +
> +typedef struct RecordChannel {
> + SndChannel base;
> + uint32_t samples[RECORD_SAMPLES_SIZE];
> + uint32_t write_pos;
> + uint32_t read_pos;
> + uint32_t mode;
> + uint32_t mode_time;
> + uint32_t start_time;
> + SndCodec codec;
> + uint8_t decode_buf[SND_CODEC_MAX_FRAME_BYTES];
> +} RecordChannel;
> +
> +static SndWorker *workers;
> +static uint32_t playback_compression = TRUE;
> +
> +static void snd_receive(void* data);
> +
> +static SndChannel *snd_channel_get(SndChannel *channel)
> +{
> + channel->refs++;
> + return channel;
> +}
> +
> +static SndChannel *snd_channel_put(SndChannel *channel)
> +{
> + if (!--channel->refs) {
> + spice_printerr("SndChannel=%p freed", channel);
> + free(channel);
> + return NULL;
> + }
> + return channel;
> +}
> +
> +static void snd_disconnect_channel(SndChannel *channel)
> +{
> + SndWorker *worker;
> +
> + if (!channel || !channel->stream) {
> + spice_debug("not connected");
> + return;
> + }
> + spice_debug("SndChannel=%p rcc=%p type=%d",
> + channel, channel->channel_client, channel->channel_client->channel->type);
> + worker = channel->worker;
> + channel->cleanup(channel);
> + red_channel_client_disconnect(worker->connection->channel_client);
> + worker->connection->channel_client = NULL;
> + core->watch_remove(channel->stream->watch);
> + channel->stream->watch = NULL;
> + reds_stream_free(channel->stream);
> + channel->stream = NULL;
> + spice_marshaller_destroy(channel->send_data.marshaller);
> + snd_channel_put(channel);
> + worker->connection = NULL;
> +}
> +
> +static void snd_playback_free_frame(PlaybackChannel *playback_channel, AudioFrame *frame)
> +{
> + frame->channel = playback_channel;
> + frame->next = playback_channel->free_frames;
> + playback_channel->free_frames = frame;
> +}
> +
> +static void snd_playback_on_message_done(SndChannel *channel)
> +{
> + PlaybackChannel *playback_channel = (PlaybackChannel *)channel;
> + if (playback_channel->in_progress) {
> + snd_playback_free_frame(playback_channel, playback_channel->in_progress);
> + playback_channel->in_progress = NULL;
> + if (playback_channel->pending_frame) {
> + channel->command |= SND_PLAYBACK_PCM_MASK;
> + }
> + }
> +}
> +
> +static void snd_record_on_message_done(SndChannel *channel)
> +{
> +}
> +
> +static int snd_send_data(SndChannel *channel)
> +{
> + uint32_t n;
> +
> + if (!channel) {
> + return FALSE;
> + }
> +
> + if (!(n = channel->send_data.size - channel->send_data.pos)) {
> + return TRUE;
> + }
> +
> + for (;;) {
> + struct iovec vec[IOV_MAX];
> + int vec_size;
> +
> + if (!n) {
> + channel->on_message_done(channel);
> +
> + if (channel->blocked) {
> + channel->blocked = FALSE;
> + core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ);
> + }
> + break;
> + }
> +
> + vec_size = spice_marshaller_fill_iovec(channel->send_data.marshaller,
> + vec, IOV_MAX, channel->send_data.pos);
> + n = reds_stream_writev(channel->stream, vec, vec_size);
> + if (n == -1) {
> + switch (errno) {
> + case EAGAIN:
> + channel->blocked = TRUE;
> + core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ |
> + SPICE_WATCH_EVENT_WRITE);
> + return FALSE;
> + case EINTR:
> + break;
> + case EPIPE:
> + snd_disconnect_channel(channel);
> + return FALSE;
> + default:
> + spice_printerr("%s", strerror(errno));
> + snd_disconnect_channel(channel);
> + return FALSE;
> + }
> + } else {
> + channel->send_data.pos += n;
> + }
> + n = channel->send_data.size - channel->send_data.pos;
> + }
> + return TRUE;
> +}
> +
> +static int snd_record_handle_write(RecordChannel *record_channel, size_t size, void *message)
> +{
> + SpiceMsgcRecordPacket *packet;
> + uint32_t write_pos;
> + uint32_t* data;
> + uint32_t len;
> + uint32_t now;
> +
> + if (!record_channel) {
> + return FALSE;
> + }
> +
> + packet = (SpiceMsgcRecordPacket *)message;
> +
> + if (record_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) {
> + data = (uint32_t *)packet->data;
> + size = packet->data_size >> 2;
> + size = MIN(size, RECORD_SAMPLES_SIZE);
> + } else {
> + int decode_size;
> + decode_size = sizeof(record_channel->decode_buf);
> + if (snd_codec_decode(record_channel->codec, packet->data, packet->data_size,
> + record_channel->decode_buf, &decode_size) != SND_CODEC_OK)
> + return FALSE;
> + data = (uint32_t *) record_channel->decode_buf;
> + size = decode_size >> 2;
> + }
> +
> + write_pos = record_channel->write_pos % RECORD_SAMPLES_SIZE;
> + record_channel->write_pos += size;
> + len = RECORD_SAMPLES_SIZE - write_pos;
> + now = MIN(len, size);
> + size -= now;
> + memcpy(record_channel->samples + write_pos, data, now << 2);
> +
> + if (size) {
> + memcpy(record_channel->samples, data + now, size << 2);
> + }
> +
> + if (record_channel->write_pos - record_channel->read_pos > RECORD_SAMPLES_SIZE) {
> + record_channel->read_pos = record_channel->write_pos - RECORD_SAMPLES_SIZE;
> + }
> + return TRUE;
> +}
> +
> +static int snd_playback_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message)
> +{
> + if (!channel) {
> + return FALSE;
> + }
> +
> + switch (type) {
> + case SPICE_MSGC_DISCONNECTING:
> + break;
> + default:
> + spice_printerr("invalid message type %u", type);
> + return FALSE;
> + }
> + return TRUE;
> +}
> +
> +static int snd_record_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message)
> +{
> + RecordChannel *record_channel = (RecordChannel *)channel;
> +
> + if (!channel) {
> + return FALSE;
> + }
> + switch (type) {
> + case SPICE_MSGC_RECORD_DATA:
> + return snd_record_handle_write((RecordChannel *)channel, size, message);
> + case SPICE_MSGC_RECORD_MODE: {
> + SpiceMsgcRecordMode *mode = (SpiceMsgcRecordMode *)message;
> + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> + record_channel->mode_time = mode->time;
> + if (mode->mode != SPICE_AUDIO_DATA_MODE_RAW) {
> + if (snd_codec_is_capable(mode->mode, st->frequency)) {
> + if (snd_codec_create(&record_channel->codec, mode->mode, st->frequency, SND_CODEC_DECODE) == SND_CODEC_OK) {
> + record_channel->mode = mode->mode;
> + } else {
> + spice_printerr("create decoder failed");
> + return FALSE;
> + }
> + }
> + else {
> + spice_printerr("unsupported mode %d", record_channel->mode);
> + return FALSE;
> + }
> + }
> + else
> + record_channel->mode = mode->mode;
> + break;
> + }
> +
> + case SPICE_MSGC_RECORD_START_MARK: {
> + SpiceMsgcRecordStartMark *mark = (SpiceMsgcRecordStartMark *)message;
> + record_channel->start_time = mark->time;
> + break;
> + }
> + case SPICE_MSGC_DISCONNECTING:
> + break;
> + default:
> + spice_printerr("invalid message type %u", type);
> + return FALSE;
> + }
> + return TRUE;
> +}
> +
> +static void snd_receive(void* data)
> +{
> + SndChannel *channel = (SndChannel*)data;
> + SpiceDataHeaderOpaque *header;
> +
> + if (!channel) {
> + return;
> + }
> +
> + header = &channel->channel_client->incoming.header;
> +
> + for (;;) {
> + ssize_t n;
> + n = channel->receive_data.end - channel->receive_data.now;
> + spice_warn_if(n <= 0);
> + n = reds_stream_read(channel->stream, channel->receive_data.now, n);
> + if (n <= 0) {
> + if (n == 0) {
> + snd_disconnect_channel(channel);
> + return;
> + }
> + spice_assert(n == -1);
> + switch (errno) {
> + case EAGAIN:
> + return;
> + case EINTR:
> + break;
> + case EPIPE:
> + snd_disconnect_channel(channel);
> + return;
> + default:
> + spice_printerr("%s", strerror(errno));
> + snd_disconnect_channel(channel);
> + return;
> + }
> + } else {
> + channel->receive_data.now += n;
> + for (;;) {
> + uint8_t *msg_start = channel->receive_data.message_start;
> + uint8_t *data = msg_start + header->header_size;
> + size_t parsed_size;
> + uint8_t *parsed;
> + message_destructor_t parsed_free;
> +
> + header->data = msg_start;
> + n = channel->receive_data.now - msg_start;
> +
> + if (n < header->header_size ||
> + n < header->header_size + header->get_msg_size(header)) {
> + break;
> + }
> + parsed = channel->parser((void *)data, data + header->get_msg_size(header),
> + header->get_msg_type(header),
> + SPICE_VERSION_MINOR, &parsed_size, &parsed_free);
> + if (parsed == NULL) {
> + spice_printerr("failed to parse message type %d", header->get_msg_type(header));
> + snd_disconnect_channel(channel);
> + return;
> + }
> + if (!channel->handle_message(channel, parsed_size,
> + header->get_msg_type(header), parsed)) {
> + free(parsed);
> + snd_disconnect_channel(channel);
> + return;
> + }
> + parsed_free(parsed);
> + channel->receive_data.message_start = msg_start + header->header_size +
> + header->get_msg_size(header);
> + }
> + if (channel->receive_data.now == channel->receive_data.message_start) {
> + channel->receive_data.now = channel->receive_data.buf;
> + channel->receive_data.message_start = channel->receive_data.buf;
> + } else if (channel->receive_data.now == channel->receive_data.end) {
> + memcpy(channel->receive_data.buf, channel->receive_data.message_start, n);
> + channel->receive_data.now = channel->receive_data.buf + n;
> + channel->receive_data.message_start = channel->receive_data.buf;
> + }
> + }
> + }
> +}
> +
> +static void snd_event(int fd, int event, void *data)
> +{
> + SndChannel *channel = data;
> +
> + if (event & SPICE_WATCH_EVENT_READ) {
> + snd_receive(channel);
> + }
> + if (event & SPICE_WATCH_EVENT_WRITE) {
> + channel->send_messages(channel);
> + }
> +}
> +
> +static inline int snd_reset_send_data(SndChannel *channel, uint16_t verb)
> +{
> + SpiceDataHeaderOpaque *header;
> +
> + if (!channel) {
> + return FALSE;
> + }
> +
> + header = &channel->channel_client->send_data.header;
> + spice_marshaller_reset(channel->send_data.marshaller);
> + header->data = spice_marshaller_reserve_space(channel->send_data.marshaller,
> + header->header_size);
> + spice_marshaller_set_base(channel->send_data.marshaller,
> + header->header_size);
> + channel->send_data.pos = 0;
> + header->set_msg_size(header, 0);
> + header->set_msg_type(header, verb);
> + channel->send_data.serial++;
> + if (!channel->channel_client->is_mini_header) {
> + header->set_msg_serial(header, channel->send_data.serial);
> + header->set_msg_sub_list(header, 0);
> + }
> +
> + return TRUE;
> +}
> +
> +static int snd_begin_send_message(SndChannel *channel)
> +{
> + SpiceDataHeaderOpaque *header = &channel->channel_client->send_data.header;
> +
> + spice_marshaller_flush(channel->send_data.marshaller);
> + channel->send_data.size = spice_marshaller_get_total_size(channel->send_data.marshaller);
> + header->set_msg_size(header, channel->send_data.size - header->header_size);
> + return snd_send_data(channel);
> +}
> +
> +static int snd_channel_send_migrate(SndChannel *channel)
> +{
> + SpiceMsgMigrate migrate;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_MIGRATE)) {
> + return FALSE;
> + }
> + spice_debug(NULL);
> + migrate.flags = 0;
> + spice_marshall_msg_migrate(channel->send_data.marshaller, &migrate);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_playback_send_migrate(PlaybackChannel *channel)
> +{
> + return snd_channel_send_migrate(&channel->base);
> +}
> +
> +static int snd_send_volume(SndChannel *channel, SpiceVolumeState *st, int msg)
> +{
> + SpiceMsgAudioVolume *vol;
> + uint8_t c;
> +
> + vol = alloca(sizeof (SpiceMsgAudioVolume) +
> + st->volume_nchannels * sizeof (uint16_t));
> + if (!snd_reset_send_data(channel, msg)) {
> + return FALSE;
> + }
> + vol->nchannels = st->volume_nchannels;
> + for (c = 0; c < st->volume_nchannels; ++c) {
> + vol->volume[c] = st->volume[c];
> + }
> + spice_marshall_SpiceMsgAudioVolume(channel->send_data.marshaller, vol);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_playback_send_volume(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = &playback_channel->base;
> + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> +
> + if (!red_channel_client_test_remote_cap(channel->channel_client,
> + SPICE_PLAYBACK_CAP_VOLUME)) {
> + return TRUE;
> + }
> +
> + return snd_send_volume(channel, &st->volume, SPICE_MSG_PLAYBACK_VOLUME);
> +}
> +
> +static int snd_send_mute(SndChannel *channel, SpiceVolumeState *st, int msg)
> +{
> + SpiceMsgAudioMute mute;
> +
> + if (!snd_reset_send_data(channel, msg)) {
> + return FALSE;
> + }
> + mute.mute = st->mute;
> + spice_marshall_SpiceMsgAudioMute(channel->send_data.marshaller, &mute);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_playback_send_mute(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = &playback_channel->base;
> + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> +
> + if (!red_channel_client_test_remote_cap(channel->channel_client,
> + SPICE_PLAYBACK_CAP_VOLUME)) {
> + return TRUE;
> + }
> +
> + return snd_send_mute(channel, &st->volume, SPICE_MSG_PLAYBACK_MUTE);
> +}
> +
> +static int snd_playback_send_latency(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = &playback_channel->base;
> + SpiceMsgPlaybackLatency latency_msg;
> +
> + spice_debug("latency %u", playback_channel->latency);
> + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_LATENCY)) {
> + return FALSE;
> + }
> + latency_msg.latency_ms = playback_channel->latency;
> + spice_marshall_msg_playback_latency(channel->send_data.marshaller, &latency_msg);
> +
> + return snd_begin_send_message(channel);
> +}
> +static int snd_playback_send_start(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = (SndChannel *)playback_channel;
> + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker);
> + SpiceMsgPlaybackStart start;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_START)) {
> + return FALSE;
> + }
> +
> + start.channels = SPICE_INTERFACE_PLAYBACK_CHAN;
> + start.frequency = st->frequency;
> + spice_assert(SPICE_INTERFACE_PLAYBACK_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
> + start.format = SPICE_AUDIO_FMT_S16;
> + start.time = reds_get_mm_time();
> + spice_marshall_msg_playback_start(channel->send_data.marshaller, &start);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_playback_send_stop(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = (SndChannel *)playback_channel;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_STOP)) {
> + return FALSE;
> + }
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_playback_send_ctl(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = (SndChannel *)playback_channel;
> +
> + if ((channel->client_active = channel->active)) {
> + return snd_playback_send_start(playback_channel);
> + } else {
> + return snd_playback_send_stop(playback_channel);
> + }
> +}
> +
> +static int snd_record_send_start(RecordChannel *record_channel)
> +{
> + SndChannel *channel = (SndChannel *)record_channel;
> + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> + SpiceMsgRecordStart start;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_START)) {
> + return FALSE;
> + }
> +
> + start.channels = SPICE_INTERFACE_RECORD_CHAN;
> + start.frequency = st->frequency;
> + spice_assert(SPICE_INTERFACE_RECORD_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
> + start.format = SPICE_AUDIO_FMT_S16;
> + spice_marshall_msg_record_start(channel->send_data.marshaller, &start);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_record_send_stop(RecordChannel *record_channel)
> +{
> + SndChannel *channel = (SndChannel *)record_channel;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_STOP)) {
> + return FALSE;
> + }
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int snd_record_send_ctl(RecordChannel *record_channel)
> +{
> + SndChannel *channel = (SndChannel *)record_channel;
> +
> + if ((channel->client_active = channel->active)) {
> + return snd_record_send_start(record_channel);
> + } else {
> + return snd_record_send_stop(record_channel);
> + }
> +}
> +
> +static int snd_record_send_volume(RecordChannel *record_channel)
> +{
> + SndChannel *channel = &record_channel->base;
> + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> +
> + if (!red_channel_client_test_remote_cap(channel->channel_client,
> + SPICE_RECORD_CAP_VOLUME)) {
> + return TRUE;
> + }
> +
> + return snd_send_volume(channel, &st->volume, SPICE_MSG_RECORD_VOLUME);
> +}
> +
> +static int snd_record_send_mute(RecordChannel *record_channel)
> +{
> + SndChannel *channel = &record_channel->base;
> + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker);
> +
> + if (!red_channel_client_test_remote_cap(channel->channel_client,
> + SPICE_RECORD_CAP_VOLUME)) {
> + return TRUE;
> + }
> +
> + return snd_send_mute(channel, &st->volume, SPICE_MSG_RECORD_MUTE);
> +}
> +
> +static int snd_record_send_migrate(RecordChannel *record_channel)
> +{
> + /* No need for migration data: if recording has started before migration,
> + * the client receives RECORD_STOP from the src before the migration completion
> + * notification (when the vm is stopped).
> + * Afterwards, when the vm starts on the dest, the client receives RECORD_START. */
> + return snd_channel_send_migrate(&record_channel->base);
> +}
> +
> +static int snd_playback_send_write(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = (SndChannel *)playback_channel;
> + AudioFrame *frame;
> + SpiceMsgPlaybackPacket msg;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_DATA)) {
> + return FALSE;
> + }
> +
> + frame = playback_channel->in_progress;
> + msg.time = frame->time;
> +
> + spice_marshall_msg_playback_data(channel->send_data.marshaller, &msg);
> +
> + if (playback_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) {
> + spice_marshaller_add_ref(channel->send_data.marshaller,
> + (uint8_t *)frame->samples,
> + snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]));
> + }
> + else {
> + int n = sizeof(playback_channel->encode_buf);
> + if (snd_codec_encode(playback_channel->codec, (uint8_t *) frame->samples,
> + snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]),
> + playback_channel->encode_buf, &n) != SND_CODEC_OK) {
> + spice_printerr("encode failed");
> + snd_disconnect_channel(channel);
> + return FALSE;
> + }
> + spice_marshaller_add_ref(channel->send_data.marshaller, playback_channel->encode_buf, n);
> + }
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static int playback_send_mode(PlaybackChannel *playback_channel)
> +{
> + SndChannel *channel = (SndChannel *)playback_channel;
> + SpiceMsgPlaybackMode mode;
> +
> + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_MODE)) {
> + return FALSE;
> + }
> + mode.time = reds_get_mm_time();
> + mode.mode = playback_channel->mode;
> + spice_marshall_msg_playback_mode(channel->send_data.marshaller, &mode);
> +
> + return snd_begin_send_message(channel);
> +}
> +
> +static void snd_playback_send(void* data)
> +{
> + PlaybackChannel *playback_channel = (PlaybackChannel*)data;
> + SndChannel *channel = (SndChannel*)playback_channel;
> +
> + if (!playback_channel || !snd_send_data(data)) {
> + return;
> + }
> +
> + while (channel->command) {
> + if (channel->command & SND_PLAYBACK_MODE_MASK) {
> + if (!playback_send_mode(playback_channel)) {
> + return;
> + }
> + channel->command &= ~SND_PLAYBACK_MODE_MASK;
> + }
> + if (channel->command & SND_PLAYBACK_PCM_MASK) {
> + spice_assert(!playback_channel->in_progress && playback_channel->pending_frame);
> + playback_channel->in_progress = playback_channel->pending_frame;
> + playback_channel->pending_frame = NULL;
> + channel->command &= ~SND_PLAYBACK_PCM_MASK;
> + if (!snd_playback_send_write(playback_channel)) {
> + spice_printerr("snd_send_playback_write failed");
> + return;
> + }
> + }
> + if (channel->command & SND_PLAYBACK_CTRL_MASK) {
> + if (!snd_playback_send_ctl(playback_channel)) {
> + return;
> + }
> + channel->command &= ~SND_PLAYBACK_CTRL_MASK;
> + }
> + if (channel->command & SND_PLAYBACK_VOLUME_MASK) {
> + if (!snd_playback_send_volume(playback_channel) ||
> + !snd_playback_send_mute(playback_channel)) {
> + return;
> + }
> + channel->command &= ~SND_PLAYBACK_VOLUME_MASK;
> + }
> + if (channel->command & SND_PLAYBACK_MIGRATE_MASK) {
> + if (!snd_playback_send_migrate(playback_channel)) {
> + return;
> + }
> + channel->command &= ~SND_PLAYBACK_MIGRATE_MASK;
> + }
> + if (channel->command & SND_PLAYBACK_LATENCY_MASK) {
> + if (!snd_playback_send_latency(playback_channel)) {
> + return;
> + }
> + channel->command &= ~SND_PLAYBACK_LATENCY_MASK;
> + }
> + }
> +}
> +
> +static void snd_record_send(void* data)
> +{
> + RecordChannel *record_channel = (RecordChannel*)data;
> + SndChannel *channel = (SndChannel*)record_channel;
> +
> + if (!record_channel || !snd_send_data(data)) {
> + return;
> + }
> +
> + while (channel->command) {
> + if (channel->command & SND_RECORD_CTRL_MASK) {
> + if (!snd_record_send_ctl(record_channel)) {
> + return;
> + }
> + channel->command &= ~SND_RECORD_CTRL_MASK;
> + }
> + if (channel->command & SND_RECORD_VOLUME_MASK) {
> + if (!snd_record_send_volume(record_channel) ||
> + !snd_record_send_mute(record_channel)) {
> + return;
> + }
> + channel->command &= ~SND_RECORD_VOLUME_MASK;
> + }
> + if (channel->command & SND_RECORD_MIGRATE_MASK) {
> + if (!snd_record_send_migrate(record_channel)) {
> + return;
> + }
> + channel->command &= ~SND_RECORD_MIGRATE_MASK;
> + }
> + }
> +}
> +
> +static SndChannel *__new_channel(SndWorker *worker, int size, uint32_t channel_id,
> + RedClient *client,
> + RedsStream *stream,
> + int migrate,
> + snd_channel_send_messages_proc send_messages,
> + snd_channel_handle_message_proc handle_message,
> + snd_channel_on_message_done_proc on_message_done,
> + snd_channel_cleanup_channel_proc cleanup,
> + uint32_t *common_caps, int num_common_caps,
> + uint32_t *caps, int num_caps)
> +{
> + SndChannel *channel;
> + int delay_val;
> + int flags;
> +#ifdef SO_PRIORITY
> + int priority;
> +#endif
> + int tos;
> + MainChannelClient *mcc = red_client_get_main(client);
> +
> + spice_assert(stream);
> + if ((flags = fcntl(stream->socket, F_GETFL)) == -1) {
> + spice_printerr("accept failed, %s", strerror(errno));
> + goto error1;
> + }
> +
> +#ifdef SO_PRIORITY
> + priority = 6;
> + if (setsockopt(stream->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority,
> + sizeof(priority)) == -1) {
> + if (errno != ENOTSUP) {
> + spice_printerr("setsockopt failed, %s", strerror(errno));
> + }
> + }
> +#endif
> +
> + tos = IPTOS_LOWDELAY;
> + if (setsockopt(stream->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) {
> + if (errno != ENOTSUP) {
> + spice_printerr("setsockopt failed, %s", strerror(errno));
> + }
> + }
> +
> + delay_val = main_channel_client_is_low_bandwidth(mcc) ? 0 : 1;
> + if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
> + if (errno != ENOTSUP) {
> + spice_printerr("setsockopt failed, %s", strerror(errno));
> + }
> + }
> +
> + if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) {
> + spice_printerr("accept failed, %s", strerror(errno));
> + goto error1;
> + }
> +
> + spice_assert(size >= sizeof(*channel));
> + channel = spice_malloc0(size);
> + channel->refs = 1;
> + channel->parser = spice_get_client_channel_parser(channel_id, NULL);
> + channel->stream = stream;
> + channel->worker = worker;
> + channel->receive_data.message_start = channel->receive_data.buf;
> + channel->receive_data.now = channel->receive_data.buf;
> + channel->receive_data.end = channel->receive_data.buf + sizeof(channel->receive_data.buf);
> + channel->send_data.marshaller = spice_marshaller_new();
> +
> + stream->watch = core->watch_add(stream->socket, SPICE_WATCH_EVENT_READ,
> + snd_event, channel);
> + if (stream->watch == NULL) {
> + spice_printerr("watch_add failed, %s", strerror(errno));
> + goto error2;
> + }
> +
> + channel->send_messages = send_messages;
> + channel->handle_message = handle_message;
> + channel->on_message_done = on_message_done;
> + channel->cleanup = cleanup;
> +
> + channel->channel_client = red_channel_client_create_dummy(sizeof(RedChannelClient),
> + worker->base_channel,
> + client,
> + num_common_caps, common_caps,
> + num_caps, caps);
> + if (!channel->channel_client) {
> + goto error2;
> + }
> + return channel;
> +
> +error2:
> + free(channel);
> +
> +error1:
> + reds_stream_free(stream);
> + return NULL;
> +}
> +
> +static void snd_disconnect_channel_client(RedChannelClient *rcc)
> +{
> + SndWorker *worker;
> +
> + spice_assert(rcc->channel);
> + spice_assert(rcc->channel->data);
> + worker = (SndWorker *)rcc->channel->data;
> +
> + spice_debug("channel-type=%d", rcc->channel->type);
> + if (worker->connection) {
> + spice_assert(worker->connection->channel_client == rcc);
> + snd_disconnect_channel(worker->connection);
> + }
> +}
> +
> +static void snd_set_command(SndChannel *channel, uint32_t command)
> +{
> + if (!channel) {
> + return;
> + }
> + channel->command |= command;
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_set_volume(SpicePlaybackInstance *sin,
> + uint8_t nchannels,
> + uint16_t *volume)
> +{
> + SpiceVolumeState *st = &sin->st->volume;
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + st->volume_nchannels = nchannels;
> + free(st->volume);
> + st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels);
> +
> + if (!channel || nchannels == 0)
> + return;
> +
> + snd_playback_send_volume(playback_channel);
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_set_mute(SpicePlaybackInstance *sin, uint8_t mute)
> +{
> + SpiceVolumeState *st = &sin->st->volume;
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + st->mute = mute;
> +
> + if (!channel)
> + return;
> +
> + snd_playback_send_mute(playback_channel);
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_start(SpicePlaybackInstance *sin)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + sin->st->worker.active = 1;
> + if (!channel)
> + return;
> + spice_assert(!playback_channel->base.active);
> + reds_disable_mm_time();
> + playback_channel->base.active = TRUE;
> + if (!playback_channel->base.client_active) {
> + snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
> + snd_playback_send(&playback_channel->base);
> + } else {
> + playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
> + }
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_stop(SpicePlaybackInstance *sin)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + sin->st->worker.active = 0;
> + if (!channel)
> + return;
> + spice_assert(playback_channel->base.active);
> + reds_enable_mm_time();
> + playback_channel->base.active = FALSE;
> + if (playback_channel->base.client_active) {
> + snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
> + snd_playback_send(&playback_channel->base);
> + } else {
> + playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
> + playback_channel->base.command &= ~SND_PLAYBACK_PCM_MASK;
> +
> + if (playback_channel->pending_frame) {
> + spice_assert(!playback_channel->in_progress);
> + snd_playback_free_frame(playback_channel,
> + playback_channel->pending_frame);
> + playback_channel->pending_frame = NULL;
> + }
> + }
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_get_buffer(SpicePlaybackInstance *sin,
> + uint32_t **frame, uint32_t *num_samples)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + if (!channel || !playback_channel->free_frames) {
> + *frame = NULL;
> + *num_samples = 0;
> + return;
> + }
> + spice_assert(playback_channel->base.active);
> + snd_channel_get(channel);
> +
> + *frame = playback_channel->free_frames->samples;
> + playback_channel->free_frames = playback_channel->free_frames->next;
> + *num_samples = snd_codec_frame_size(playback_channel->codec);
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples)
> +{
> + PlaybackChannel *playback_channel;
> + AudioFrame *frame;
> +
> + frame = SPICE_CONTAINEROF(samples, AudioFrame, samples);
> + playback_channel = frame->channel;
> + spice_assert(playback_channel);
> + if (!snd_channel_put(&playback_channel->base) ||
> + sin->st->worker.connection != &playback_channel->base) {
> + /* lost last reference, channel has been destroyed previously */
> + spice_info("audio samples belong to a disconnected channel");
> + return;
> + }
> + spice_assert(playback_channel->base.active);
> +
> + if (playback_channel->pending_frame) {
> + snd_playback_free_frame(playback_channel, playback_channel->pending_frame);
> + }
> + frame->time = reds_get_mm_time();
> + playback_channel->pending_frame = frame;
> + snd_set_command(&playback_channel->base, SND_PLAYBACK_PCM_MASK);
> + snd_playback_send(&playback_channel->base);
> +}
> +
> +void snd_set_playback_latency(RedClient *client, uint32_t latency)
> +{
> + SndWorker *now = workers;
> +
> + for (; now; now = now->next) {
> + if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection &&
> + now->connection->channel_client->client == client) {
> +
> + if (red_channel_client_test_remote_cap(now->connection->channel_client,
> + SPICE_PLAYBACK_CAP_LATENCY)) {
> + PlaybackChannel* playback = (PlaybackChannel*)now->connection;
> +
> + playback->latency = latency;
> + snd_set_command(now->connection, SND_PLAYBACK_LATENCY_MASK);
> + snd_playback_send(now->connection);
> + } else {
> + spice_debug("client doesn't not support SPICE_PLAYBACK_CAP_LATENCY");
> + }
> + }
> + }
> +}
> +
> +static int snd_desired_audio_mode(int frequency, int client_can_celt, int client_can_opus)
> +{
> + if (! playback_compression)
> + return SPICE_AUDIO_DATA_MODE_RAW;
> +
> + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> + return SPICE_AUDIO_DATA_MODE_OPUS;
> +
> + if (client_can_celt && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency))
> + return SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
> +
> + return SPICE_AUDIO_DATA_MODE_RAW;
> +}
> +
> +static void on_new_playback_channel(SndWorker *worker)
> +{
> + PlaybackChannel *playback_channel =
> + SPICE_CONTAINEROF(worker->connection, PlaybackChannel, base);
> + SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker);
> +
> + spice_assert(playback_channel);
> +
> + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_MODE_MASK);
> + if (playback_channel->base.active) {
> + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_CTRL_MASK);
> + }
> + if (st->volume.volume_nchannels) {
> + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_VOLUME_MASK);
> + }
> + if (playback_channel->base.active) {
> + reds_disable_mm_time();
> + }
> +}
> +
> +static void snd_playback_cleanup(SndChannel *channel)
> +{
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> +
> + if (playback_channel->base.active) {
> + reds_enable_mm_time();
> + }
> +
> + snd_codec_destroy(&playback_channel->codec);
> +}
> +
> +static void snd_set_playback_peer(RedChannel *channel, RedClient *client, RedsStream *stream,
> + int migration, int num_common_caps, uint32_t *common_caps,
> + int num_caps, uint32_t *caps)
> +{
> + SndWorker *worker = channel->data;
> + PlaybackChannel *playback_channel;
> + SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker);
> +
> + snd_disconnect_channel(worker->connection);
> +
> + if (!(playback_channel = (PlaybackChannel *)__new_channel(worker,
> + sizeof(*playback_channel),
> + SPICE_CHANNEL_PLAYBACK,
> + client,
> + stream,
> + migration,
> + snd_playback_send,
> + snd_playback_handle_message,
> + snd_playback_on_message_done,
> + snd_playback_cleanup,
> + common_caps, num_common_caps,
> + caps, num_caps))) {
> + return;
> + }
> + worker->connection = &playback_channel->base;
> + snd_playback_free_frame(playback_channel, &playback_channel->frames[0]);
> + snd_playback_free_frame(playback_channel, &playback_channel->frames[1]);
> + snd_playback_free_frame(playback_channel, &playback_channel->frames[2]);
> +
> + int client_can_celt = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> + SPICE_PLAYBACK_CAP_CELT_0_5_1);
> + int client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> + SPICE_PLAYBACK_CAP_OPUS);
> + int desired_mode = snd_desired_audio_mode(st->frequency, client_can_celt, client_can_opus);
> + playback_channel->mode = SPICE_AUDIO_DATA_MODE_RAW;
> + if (desired_mode != SPICE_AUDIO_DATA_MODE_RAW) {
> + if (snd_codec_create(&playback_channel->codec, desired_mode, st->frequency, SND_CODEC_ENCODE) == SND_CODEC_OK) {
> + playback_channel->mode = desired_mode;
> + } else {
> + spice_printerr("create encoder failed");
> + }
> + }
> +
> + if (!red_client_during_migrate_at_target(client)) {
> + on_new_playback_channel(worker);
> + }
> +
> + if (worker->active) {
> + spice_server_playback_start(st->sin);
> + }
> + snd_playback_send(worker->connection);
> +}
> +
> +static void snd_record_migrate_channel_client(RedChannelClient *rcc)
> +{
> + SndWorker *worker;
> +
> + spice_debug(NULL);
> + spice_assert(rcc->channel);
> + spice_assert(rcc->channel->data);
> + worker = (SndWorker *)rcc->channel->data;
> +
> + if (worker->connection) {
> + spice_assert(worker->connection->channel_client == rcc);
> + snd_set_command(worker->connection, SND_RECORD_MIGRATE_MASK);
> + snd_record_send(worker->connection);
> + }
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_record_set_volume(SpiceRecordInstance *sin,
> + uint8_t nchannels,
> + uint16_t *volume)
> +{
> + SpiceVolumeState *st = &sin->st->volume;
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> +
> + st->volume_nchannels = nchannels;
> + free(st->volume);
> + st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels);
> +
> + if (!channel || nchannels == 0)
> + return;
> +
> + snd_record_send_volume(record_channel);
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_record_set_mute(SpiceRecordInstance *sin, uint8_t mute)
> +{
> + SpiceVolumeState *st = &sin->st->volume;
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> +
> + st->mute = mute;
> +
> + if (!channel)
> + return;
> +
> + snd_record_send_mute(record_channel);
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_record_start(SpiceRecordInstance *sin)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> +
> + sin->st->worker.active = 1;
> + if (!channel)
> + return;
> + spice_assert(!record_channel->base.active);
> + record_channel->base.active = TRUE;
> + record_channel->read_pos = record_channel->write_pos = 0; //todo: improve by
> + //stream generation
> + if (!record_channel->base.client_active) {
> + snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
> + snd_record_send(&record_channel->base);
> + } else {
> + record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
> + }
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_record_stop(SpiceRecordInstance *sin)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> +
> + sin->st->worker.active = 0;
> + if (!channel)
> + return;
> + spice_assert(record_channel->base.active);
> + record_channel->base.active = FALSE;
> + if (record_channel->base.client_active) {
> + snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
> + snd_record_send(&record_channel->base);
> + } else {
> + record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
> + }
> +}
> +
> +SPICE_GNUC_VISIBLE uint32_t spice_server_record_get_samples(SpiceRecordInstance *sin,
> + uint32_t *samples, uint32_t bufsize)
> +{
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> + uint32_t read_pos;
> + uint32_t now;
> + uint32_t len;
> +
> + if (!channel)
> + return 0;
> + spice_assert(record_channel->base.active);
> +
> + if (record_channel->write_pos < RECORD_SAMPLES_SIZE / 2) {
> + return 0;
> + }
> +
> + len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize);
> +
> + if (len < bufsize) {
> + SndWorker *worker = record_channel->base.worker;
> + snd_receive(record_channel);
> + if (!worker->connection) {
> + return 0;
> + }
> + len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize);
> + }
> +
> + read_pos = record_channel->read_pos % RECORD_SAMPLES_SIZE;
> + record_channel->read_pos += len;
> + now = MIN(len, RECORD_SAMPLES_SIZE - read_pos);
> + memcpy(samples, &record_channel->samples[read_pos], now * 4);
> + if (now < len) {
> + memcpy(samples + now, record_channel->samples, (len - now) * 4);
> + }
> + return len;
> +}
> +
> +SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_playback_rate(SpicePlaybackInstance *sin)
> +{
> + int client_can_opus = TRUE;
> + if (sin && sin->st->worker.connection) {
> + SndChannel *channel = sin->st->worker.connection;
> + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base);
> + client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client,
> + SPICE_PLAYBACK_CAP_OPUS);
> + }
> +
> + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
> + return SND_CODEC_OPUS_PLAYBACK_FREQ;
> +
> + return SND_CODEC_CELT_PLAYBACK_FREQ;
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_set_playback_rate(SpicePlaybackInstance *sin, uint32_t frequency)
> +{
> + RedChannel *channel = sin->st->worker.base_channel;
> + sin->st->frequency = frequency;
> + if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_OPUS);
> +}
> +
> +SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_record_rate(SpiceRecordInstance *sin)
> +{
> + int client_can_opus = TRUE;
> + if (sin && sin->st->worker.connection) {
> + SndChannel *channel = sin->st->worker.connection;
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> + client_can_opus = red_channel_client_test_remote_cap(record_channel->base.channel_client,
> + SPICE_RECORD_CAP_OPUS);
> + }
> +
> + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
> + return SND_CODEC_OPUS_PLAYBACK_FREQ;
> +
> + return SND_CODEC_CELT_PLAYBACK_FREQ;
> +}
> +
> +SPICE_GNUC_VISIBLE void spice_server_set_record_rate(SpiceRecordInstance *sin, uint32_t frequency)
> +{
> + RedChannel *channel = sin->st->worker.base_channel;
> + sin->st->frequency = frequency;
> + if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
> + red_channel_set_cap(channel, SPICE_RECORD_CAP_OPUS);
> +}
> +
> +static void on_new_record_channel(SndWorker *worker)
> +{
> + RecordChannel *record_channel = (RecordChannel *)worker->connection;
> + SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker);
> +
> + spice_assert(record_channel);
> +
> + if (st->volume.volume_nchannels) {
> + snd_set_command((SndChannel *)record_channel, SND_RECORD_VOLUME_MASK);
> + }
> + if (record_channel->base.active) {
> + snd_set_command((SndChannel *)record_channel, SND_RECORD_CTRL_MASK);
> + }
> +}
> +
> +static void snd_record_cleanup(SndChannel *channel)
> +{
> + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base);
> + snd_codec_destroy(&record_channel->codec);
> +}
> +
> +static void snd_set_record_peer(RedChannel *channel, RedClient *client, RedsStream *stream,
> + int migration, int num_common_caps, uint32_t *common_caps,
> + int num_caps, uint32_t *caps)
> +{
> + SndWorker *worker = channel->data;
> + RecordChannel *record_channel;
> + SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker);
> +
> + snd_disconnect_channel(worker->connection);
> +
> + if (!(record_channel = (RecordChannel *)__new_channel(worker,
> + sizeof(*record_channel),
> + SPICE_CHANNEL_RECORD,
> + client,
> + stream,
> + migration,
> + snd_record_send,
> + snd_record_handle_message,
> + snd_record_on_message_done,
> + snd_record_cleanup,
> + common_caps, num_common_caps,
> + caps, num_caps))) {
> + return;
> + }
> +
> + record_channel->mode = SPICE_AUDIO_DATA_MODE_RAW;
> +
> + worker->connection = &record_channel->base;
> +
> + on_new_record_channel(worker);
> + if (worker->active) {
> + spice_server_record_start(st->sin);
> + }
> + snd_record_send(worker->connection);
> +}
> +
> +static void snd_playback_migrate_channel_client(RedChannelClient *rcc)
> +{
> + SndWorker *worker;
> +
> + spice_assert(rcc->channel);
> + spice_assert(rcc->channel->data);
> + worker = (SndWorker *)rcc->channel->data;
> + spice_debug(NULL);
> +
> + if (worker->connection) {
> + spice_assert(worker->connection->channel_client == rcc);
> + snd_set_command(worker->connection, SND_PLAYBACK_MIGRATE_MASK);
> + snd_playback_send(worker->connection);
> + }
> +}
> +
> +static void add_worker(SndWorker *worker)
> +{
> + worker->next = workers;
> + workers = worker;
> +}
> +
> +static void remove_worker(SndWorker *worker)
> +{
> + SndWorker **now = &workers;
> + while (*now) {
> + if (*now == worker) {
> + *now = worker->next;
> + return;
> + }
> + now = &(*now)->next;
> + }
> + spice_printerr("not found");
> +}
> +
> +void snd_attach_playback(SpicePlaybackInstance *sin)
> +{
> + SndWorker *playback_worker;
> + RedChannel *channel;
> + ClientCbs client_cbs = { NULL, };
> +
> + sin->st = spice_new0(SpicePlaybackState, 1);
> + sin->st->sin = sin;
> + playback_worker = &sin->st->worker;
> + sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */
> +
> + // TODO: Make RedChannel base of worker? instead of assigning it to channel->data
> + channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_PLAYBACK, 0);
> +
> + channel->data = playback_worker;
> + client_cbs.connect = snd_set_playback_peer;
> + client_cbs.disconnect = snd_disconnect_channel_client;
> + client_cbs.migrate = snd_playback_migrate_channel_client;
> + red_channel_register_client_cbs(channel, &client_cbs);
> + red_channel_set_data(channel, playback_worker);
> +
> + if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
> + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_CELT_0_5_1);
> +
> + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_VOLUME);
> +
> + playback_worker->base_channel = channel;
> + add_worker(playback_worker);
> + reds_register_channel(playback_worker->base_channel);
> +}
> +
> +void snd_attach_record(SpiceRecordInstance *sin)
> +{
> + SndWorker *record_worker;
> + RedChannel *channel;
> + ClientCbs client_cbs = { NULL, };
> +
> + sin->st = spice_new0(SpiceRecordState, 1);
> + sin->st->sin = sin;
> + record_worker = &sin->st->worker;
> + sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */
> +
> + // TODO: Make RedChannel base of worker? instead of assigning it to channel->data
> + channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_RECORD, 0);
> +
> + channel->data = record_worker;
> + client_cbs.connect = snd_set_record_peer;
> + client_cbs.disconnect = snd_disconnect_channel_client;
> + client_cbs.migrate = snd_record_migrate_channel_client;
> + red_channel_register_client_cbs(channel, &client_cbs);
> + red_channel_set_data(channel, record_worker);
> + if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
> + red_channel_set_cap(channel, SPICE_RECORD_CAP_CELT_0_5_1);
> + red_channel_set_cap(channel, SPICE_RECORD_CAP_VOLUME);
> +
> + record_worker->base_channel = channel;
> + add_worker(record_worker);
> + reds_register_channel(record_worker->base_channel);
> +}
> +
> +static void snd_detach_common(SndWorker *worker)
> +{
> + if (!worker) {
> + return;
> + }
> + remove_worker(worker);
> + snd_disconnect_channel(worker->connection);
> + reds_unregister_channel(worker->base_channel);
> + red_channel_destroy(worker->base_channel);
> +}
> +
> +static void spice_playback_state_free(SpicePlaybackState *st)
> +{
> + free(st->volume.volume);
> + free(st);
> +}
> +
> +void snd_detach_playback(SpicePlaybackInstance *sin)
> +{
> + snd_detach_common(&sin->st->worker);
> + spice_playback_state_free(sin->st);
> +}
> +
> +static void spice_record_state_free(SpiceRecordState *st)
> +{
> + free(st->volume.volume);
> + free(st);
> +}
> +
> +void snd_detach_record(SpiceRecordInstance *sin)
> +{
> + snd_detach_common(&sin->st->worker);
> + spice_record_state_free(sin->st);
> +}
> +
> +void snd_set_playback_compression(int on)
> +{
> + SndWorker *now = workers;
> +
> + playback_compression = !!on;
> +
> + for (; now; now = now->next) {
> + if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection) {
> + PlaybackChannel* playback = (PlaybackChannel*)now->connection;
> + SpicePlaybackState *st = SPICE_CONTAINEROF(now, SpicePlaybackState, worker);
> + int client_can_celt = red_channel_client_test_remote_cap(playback->base.channel_client,
> + SPICE_PLAYBACK_CAP_CELT_0_5_1);
> + int client_can_opus = red_channel_client_test_remote_cap(playback->base.channel_client,
> + SPICE_PLAYBACK_CAP_OPUS);
> + int desired_mode = snd_desired_audio_mode(st->frequency, client_can_opus, client_can_celt);
> + if (playback->mode != desired_mode) {
> + playback->mode = desired_mode;
> + snd_set_command(now->connection, SND_PLAYBACK_MODE_MASK);
> + }
> + }
> + }
> +}
> diff --git a/server/sound.h b/server/sound.h
> new file mode 100644
> index 0000000..97f8410
> --- /dev/null
> +++ b/server/sound.h
> @@ -0,0 +1,33 @@
> +/*
> + Copyright (C) 2009 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/>.
> +*/
> +
> +#ifndef SOUND_H_
> +#define SOUND_H_
> +
> +#include "spice.h"
> +
> +void snd_attach_playback(SpicePlaybackInstance *sin);
> +void snd_detach_playback(SpicePlaybackInstance *sin);
> +
> +void snd_attach_record(SpiceRecordInstance *sin);
> +void snd_detach_record(SpiceRecordInstance *sin);
> +
> +void snd_set_playback_compression(int on);
> +
> +void snd_set_playback_latency(RedClient *client, uint32_t latency);
> +
> +#endif
> diff --git a/server/spice_image_cache.c b/server/spice_image_cache.c
> deleted file mode 100644
> index 1c5de24..0000000
> --- a/server/spice_image_cache.c
> +++ /dev/null
> @@ -1,214 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009-2015 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 "spice_image_cache.h"
> -#include "red_parse_qxl.h"
> -#include "display-channel.h"
> -
> -static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t id)
> -{
> - ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE];
> -
> - while (item) {
> - if (item->id == id) {
> - return item;
> - }
> - item = item->next;
> - }
> - return NULL;
> -}
> -
> -int image_cache_hit(ImageCache *cache, uint64_t id)
> -{
> - ImageCacheItem *item;
> - if (!(item = image_cache_find(cache, id))) {
> - return FALSE;
> - }
> -#ifdef IMAGE_CACHE_AGE
> - item->age = cache->age;
> -#endif
> - ring_remove(&item->lru_link);
> - ring_add(&cache->lru, &item->lru_link);
> - return TRUE;
> -}
> -
> -static void image_cache_remove(ImageCache *cache, ImageCacheItem *item)
> -{
> - ImageCacheItem **now;
> -
> - now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
> - for (;;) {
> - spice_assert(*now);
> - if (*now == item) {
> - *now = item->next;
> - break;
> - }
> - now = &(*now)->next;
> - }
> - ring_remove(&item->lru_link);
> - pixman_image_unref(item->image);
> - free(item);
> -#ifndef IMAGE_CACHE_AGE
> - cache->num_items--;
> -#endif
> -}
> -
> -#define IMAGE_CACHE_MAX_ITEMS 2
> -
> -static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image)
> -{
> - ImageCache *cache = (ImageCache *)spice_cache;
> - ImageCacheItem *item;
> -
> -#ifndef IMAGE_CACHE_AGE
> - if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) {
> - ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru);
> - spice_assert(tail);
> - image_cache_remove(cache, tail);
> - }
> -#endif
> -
> - item = spice_new(ImageCacheItem, 1);
> - item->id = id;
> -#ifdef IMAGE_CACHE_AGE
> - item->age = cache->age;
> -#else
> - cache->num_items++;
> -#endif
> - item->image = pixman_image_ref(image);
> - ring_item_init(&item->lru_link);
> -
> - item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
> - cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item;
> -
> - ring_add(&cache->lru, &item->lru_link);
> -}
> -
> -static pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id)
> -{
> - ImageCache *cache = (ImageCache *)spice_cache;
> -
> - ImageCacheItem *item = image_cache_find(cache, id);
> - if (!item) {
> - spice_error("not found");
> - }
> - return pixman_image_ref(item->image);
> -}
> -
> -void image_cache_init(ImageCache *cache)
> -{
> - static SpiceImageCacheOps image_cache_ops = {
> - image_cache_put,
> - image_cache_get,
> - };
> -
> - cache->base.ops = &image_cache_ops;
> - memset(cache->hash_table, 0, sizeof(cache->hash_table));
> - ring_init(&cache->lru);
> -#ifdef IMAGE_CACHE_AGE
> - cache->age = 0;
> -#else
> - cache->num_items = 0;
> -#endif
> -}
> -
> -void image_cache_reset(ImageCache *cache)
> -{
> - ImageCacheItem *item;
> -
> - while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) {
> - image_cache_remove(cache, item);
> - }
> -#ifdef IMAGE_CACHE_AGE
> - cache->age = 0;
> -#endif
> -}
> -
> -#define IMAGE_CACHE_DEPTH 4
> -
> -void image_cache_aging(ImageCache *cache)
> -{
> -#ifdef IMAGE_CACHE_AGE
> - ImageCacheItem *item;
> -
> - cache->age++;
> - while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) &&
> - cache->age - item->age > IMAGE_CACHE_DEPTH) {
> - image_cache_remove(cache, item);
> - }
> -#endif
> -}
> -
> -void image_cache_localize(ImageCache *cache, SpiceImage **image_ptr,
> - SpiceImage *image_store, Drawable *drawable)
> -{
> - SpiceImage *image = *image_ptr;
> -
> - if (image == NULL) {
> - spice_assert(drawable != NULL);
> - spice_assert(drawable->red_drawable->self_bitmap_image != NULL);
> - *image_ptr = drawable->red_drawable->self_bitmap_image;
> - return;
> - }
> -
> - if (image_cache_hit(cache, image->descriptor.id)) {
> - image_store->descriptor = image->descriptor;
> - image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
> - image_store->descriptor.flags = 0;
> - *image_ptr = image_store;
> - return;
> - }
> -
> - switch (image->descriptor.type) {
> - case SPICE_IMAGE_TYPE_QUIC: {
> - image_store->descriptor = image->descriptor;
> - image_store->u.quic = image->u.quic;
> - *image_ptr = image_store;
> -#ifdef IMAGE_CACHE_AGE
> - image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
> -#else
> - if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) {
> - image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
> - }
> -#endif
> - break;
> - }
> - case SPICE_IMAGE_TYPE_BITMAP:
> - case SPICE_IMAGE_TYPE_SURFACE:
> - /* nothing */
> - break;
> - default:
> - spice_error("invalid image type");
> - }
> -}
> -
> -void image_cache_localize_brush(ImageCache *cache, SpiceBrush *brush, SpiceImage *image_store)
> -{
> - if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
> - image_cache_localize(cache, &brush->u.pattern.pat, image_store, NULL);
> - }
> -}
> -
> -void image_cache_localize_mask(ImageCache *cache, SpiceQMask *mask, SpiceImage *image_store)
> -{
> - if (mask->bitmap) {
> - image_cache_localize(cache, &mask->bitmap, image_store, NULL);
> - }
> -}
> diff --git a/server/spice_image_cache.h b/server/spice_image_cache.h
> deleted file mode 100644
> index 6d6b32d..0000000
> --- a/server/spice_image_cache.h
> +++ /dev/null
> @@ -1,65 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2009-2015 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/>.
> -*/
> -#ifndef H_SPICE_IMAGE_CACHE
> -#define H_SPICE_IMAGE_CACHE
> -
> -#include <inttypes.h>
> -
> -#include "common/pixman_utils.h"
> -#include "common/canvas_base.h"
> -#include "common/ring.h"
> -
> -/* FIXME: move back to display_channel.h (once structs are private) */
> -typedef struct Drawable Drawable;
> -typedef struct DisplayChannelClient DisplayChannelClient;
> -
> -typedef struct ImageCacheItem {
> - RingItem lru_link;
> - uint64_t id;
> -#ifdef IMAGE_CACHE_AGE
> - uint32_t age;
> -#endif
> - struct ImageCacheItem *next;
> - pixman_image_t *image;
> -} ImageCacheItem;
> -
> -#define IMAGE_CACHE_HASH_SIZE 1024
> -
> -typedef struct ImageCache {
> - SpiceImageCache base;
> - ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE];
> - Ring lru;
> -#ifdef IMAGE_CACHE_AGE
> - uint32_t age;
> -#else
> - uint32_t num_items;
> -#endif
> -} ImageCache;
> -
> -int image_cache_hit (ImageCache *cache, uint64_t id);
> -void image_cache_init (ImageCache *cache);
> -void image_cache_reset (ImageCache *cache);
> -void image_cache_aging (ImageCache *cache);
> -void image_cache_localize (ImageCache *cache, SpiceImage **image_ptr,
> - SpiceImage *image_store, Drawable *drawable);
> -void image_cache_localize_brush (ImageCache *cache, SpiceBrush *brush,
> - SpiceImage *image_store);
> -void image_cache_localize_mask (ImageCache *cache, SpiceQMask *mask,
> - SpiceImage *image_store);
> -
> -#endif
> diff --git a/server/spicevmc.c b/server/spicevmc.c
> index d37b1ec..52a29a4 100644
> --- a/server/spicevmc.c
> +++ b/server/spicevmc.c
> @@ -30,10 +30,10 @@
>
> #include "common/generated_server_marshallers.h"
>
> -#include "char_device.h"
> +#include "char-device.h"
> #include "red_channel.h"
> #include "reds.h"
> -#include "migration_protocol.h"
> +#include "migration-protocol.h"
>
> /* todo: add flow control. i.e.,
> * (a) limit the tokens available for the client
> diff --git a/server/stream.h b/server/stream.h
> index 214d1df..cb2b844 100644
> --- a/server/stream.h
> +++ b/server/stream.h
> @@ -20,10 +20,10 @@
>
> #include <glib.h>
> #include "utils.h"
> -#include "mjpeg_encoder.h"
> +#include "mjpeg-encoder.h"
> #include "common/region.h"
> #include "red_channel.h"
> -#include "spice_image_cache.h"
> +#include "image-cache.h"
>
> #define RED_STREAM_DETACTION_MAX_DELTA ((1000 * 1000 * 1000) / 5) // 1/5 sec
> #define RED_STREAM_CONTINUS_MAX_DELTA (1000 * 1000 * 1000)
> diff --git a/server/sw-canvas.c b/server/sw-canvas.c
> new file mode 100644
> index 0000000..0ef050e
> --- /dev/null
> +++ b/server/sw-canvas.c
> @@ -0,0 +1,26 @@
> +/*
> + Copyright (C) 2011 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/spice_common.h"
> +
> +#include "sw-canvas.h"
> +#define SW_CANVAS_IMAGE_CACHE
> +#include "common/sw_canvas.c"
> +#undef SW_CANVAS_IMAGE_CACHE
> diff --git a/server/sw-canvas.h b/server/sw-canvas.h
> new file mode 100644
> index 0000000..5ffba3d
> --- /dev/null
> +++ b/server/sw-canvas.h
> @@ -0,0 +1,24 @@
> +/*
> + Copyright (C) 2011 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/>.
> +*/
> +#ifndef SW_CANVAS_H_
> +#define SW_CANVAS_H_
> +
> +#define SW_CANVAS_IMAGE_CACHE
> +#include "common/sw_canvas.h"
> +#undef SW_CANVAS_IMAGE_CACHE
> +
> +#endif
> diff --git a/server/zlib-encoder.c b/server/zlib-encoder.c
> new file mode 100644
> index 0000000..069a448
> --- /dev/null
> +++ b/server/zlib-encoder.c
> @@ -0,0 +1,125 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2010 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 "red_common.h"
> +#include "zlib-encoder.h"
> +#include <zlib.h>
> +
> +struct ZlibEncoder {
> + ZlibEncoderUsrContext *usr;
> +
> + z_stream strm;
> + int last_level;
> +};
> +
> +ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level)
> +{
> + ZlibEncoder *enc;
> + int z_ret;
> +
> + if (!usr->more_space || !usr->more_input) {
> + return NULL;
> + }
> +
> + enc = spice_new0(ZlibEncoder, 1);
> +
> + enc->usr = usr;
> +
> + enc->strm.zalloc = Z_NULL;
> + enc->strm.zfree = Z_NULL;
> + enc->strm.opaque = Z_NULL;
> +
> + z_ret = deflateInit(&enc->strm, level);
> + enc->last_level = level;
> + if (z_ret != Z_OK) {
> + spice_printerr("zlib error");
> + free(enc);
> + return NULL;
> + }
> +
> + return enc;
> +}
> +
> +void zlib_encoder_destroy(ZlibEncoder *encoder)
> +{
> + deflateEnd(&encoder->strm);
> + free(encoder);
> +}
> +
> +/* returns the total size of the encoded data */
> +int zlib_encode(ZlibEncoder *zlib, int level, int input_size,
> + uint8_t *io_ptr, unsigned int num_io_bytes)
> +{
> + int flush;
> + int enc_size = 0;
> + int out_size = 0;
> + int z_ret;
> +
> + z_ret = deflateReset(&zlib->strm);
> +
> + if (z_ret != Z_OK) {
> + spice_error("deflateReset failed");
> + }
> +
> + zlib->strm.next_out = io_ptr;
> + zlib->strm.avail_out = num_io_bytes;
> +
> + if (level != zlib->last_level) {
> + if (zlib->strm.avail_out == 0) {
> + zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out);
> + if (zlib->strm.avail_out == 0) {
> + spice_error("not enough space");
> + }
> + }
> + z_ret = deflateParams(&zlib->strm, level, Z_DEFAULT_STRATEGY);
> + if (z_ret != Z_OK) {
> + spice_error("deflateParams failed");
> + }
> + zlib->last_level = level;
> + }
> +
> +
> + do {
> + zlib->strm.avail_in = zlib->usr->more_input(zlib->usr, &zlib->strm.next_in);
> + if (zlib->strm.avail_in <= 0) {
> + spice_error("more input failed");
> + }
> + enc_size += zlib->strm.avail_in;
> + flush = (enc_size == input_size) ? Z_FINISH : Z_NO_FLUSH;
> + while (1) {
> + int deflate_size = zlib->strm.avail_out;
> + z_ret = deflate(&zlib->strm, flush);
> + spice_assert(z_ret != Z_STREAM_ERROR);
> + out_size += deflate_size - zlib->strm.avail_out;
> + if (zlib->strm.avail_out) {
> + break;
> + }
> +
> + zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out);
> + if (zlib->strm.avail_out == 0) {
> + spice_error("not enough space");
> + }
> + }
> + } while (flush != Z_FINISH);
> +
> + spice_assert(z_ret == Z_STREAM_END);
> + return out_size;
> +}
> diff --git a/server/zlib-encoder.h b/server/zlib-encoder.h
> new file mode 100644
> index 0000000..0620fc7
> --- /dev/null
> +++ b/server/zlib-encoder.h
> @@ -0,0 +1,47 @@
> +/*
> + Copyright (C) 2009 Red Hat, Inc.
> +
> + Redistribution and use in source and binary forms, with or without
> + modification, are permitted provided that the following conditions are
> + met:
> +
> + * Redistributions of source code must retain the above copyright
> + notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + notice, this list of conditions and the following disclaimer in
> + the documentation and/or other materials provided with the
> + distribution.
> + * Neither the name of the copyright holder nor the names of its
> + contributors may be used to endorse or promote products derived
> + from this software without specific prior written permission.
> +
> + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
> + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
> + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> +*/
> +#ifndef _H_ZLIB_ENCODER
> +#define _H_ZLIB_ENCODER
> +
> +typedef struct ZlibEncoder ZlibEncoder;
> +typedef struct ZlibEncoderUsrContext ZlibEncoderUsrContext;
> +
> +struct ZlibEncoderUsrContext {
> + int (*more_space)(ZlibEncoderUsrContext *usr, uint8_t **io_ptr);
> + int (*more_input)(ZlibEncoderUsrContext *usr, uint8_t **input);
> +};
> +
> +ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level);
> +void zlib_encoder_destroy(ZlibEncoder *encoder);
> +
> +/* returns the total size of the encoded data */
> +int zlib_encode(ZlibEncoder *zlib, int level, int input_size,
> + uint8_t *io_ptr, unsigned int num_io_bytes);
> +#endif
> diff --git a/server/zlib_encoder.c b/server/zlib_encoder.c
> deleted file mode 100644
> index a3d2aa6..0000000
> --- a/server/zlib_encoder.c
> +++ /dev/null
> @@ -1,125 +0,0 @@
> -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> -/*
> - Copyright (C) 2010 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 "red_common.h"
> -#include "zlib_encoder.h"
> -#include <zlib.h>
> -
> -struct ZlibEncoder {
> - ZlibEncoderUsrContext *usr;
> -
> - z_stream strm;
> - int last_level;
> -};
> -
> -ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level)
> -{
> - ZlibEncoder *enc;
> - int z_ret;
> -
> - if (!usr->more_space || !usr->more_input) {
> - return NULL;
> - }
> -
> - enc = spice_new0(ZlibEncoder, 1);
> -
> - enc->usr = usr;
> -
> - enc->strm.zalloc = Z_NULL;
> - enc->strm.zfree = Z_NULL;
> - enc->strm.opaque = Z_NULL;
> -
> - z_ret = deflateInit(&enc->strm, level);
> - enc->last_level = level;
> - if (z_ret != Z_OK) {
> - spice_printerr("zlib error");
> - free(enc);
> - return NULL;
> - }
> -
> - return enc;
> -}
> -
> -void zlib_encoder_destroy(ZlibEncoder *encoder)
> -{
> - deflateEnd(&encoder->strm);
> - free(encoder);
> -}
> -
> -/* returns the total size of the encoded data */
> -int zlib_encode(ZlibEncoder *zlib, int level, int input_size,
> - uint8_t *io_ptr, unsigned int num_io_bytes)
> -{
> - int flush;
> - int enc_size = 0;
> - int out_size = 0;
> - int z_ret;
> -
> - z_ret = deflateReset(&zlib->strm);
> -
> - if (z_ret != Z_OK) {
> - spice_error("deflateReset failed");
> - }
> -
> - zlib->strm.next_out = io_ptr;
> - zlib->strm.avail_out = num_io_bytes;
> -
> - if (level != zlib->last_level) {
> - if (zlib->strm.avail_out == 0) {
> - zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out);
> - if (zlib->strm.avail_out == 0) {
> - spice_error("not enough space");
> - }
> - }
> - z_ret = deflateParams(&zlib->strm, level, Z_DEFAULT_STRATEGY);
> - if (z_ret != Z_OK) {
> - spice_error("deflateParams failed");
> - }
> - zlib->last_level = level;
> - }
> -
> -
> - do {
> - zlib->strm.avail_in = zlib->usr->more_input(zlib->usr, &zlib->strm.next_in);
> - if (zlib->strm.avail_in <= 0) {
> - spice_error("more input failed");
> - }
> - enc_size += zlib->strm.avail_in;
> - flush = (enc_size == input_size) ? Z_FINISH : Z_NO_FLUSH;
> - while (1) {
> - int deflate_size = zlib->strm.avail_out;
> - z_ret = deflate(&zlib->strm, flush);
> - spice_assert(z_ret != Z_STREAM_ERROR);
> - out_size += deflate_size - zlib->strm.avail_out;
> - if (zlib->strm.avail_out) {
> - break;
> - }
> -
> - zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out);
> - if (zlib->strm.avail_out == 0) {
> - spice_error("not enough space");
> - }
> - }
> - } while (flush != Z_FINISH);
> -
> - spice_assert(z_ret == Z_STREAM_END);
> - return out_size;
> -}
> diff --git a/server/zlib_encoder.h b/server/zlib_encoder.h
> deleted file mode 100644
> index 0620fc7..0000000
> --- a/server/zlib_encoder.h
> +++ /dev/null
> @@ -1,47 +0,0 @@
> -/*
> - Copyright (C) 2009 Red Hat, Inc.
> -
> - Redistribution and use in source and binary forms, with or without
> - modification, are permitted provided that the following conditions are
> - met:
> -
> - * Redistributions of source code must retain the above copyright
> - notice, this list of conditions and the following disclaimer.
> - * Redistributions in binary form must reproduce the above copyright
> - notice, this list of conditions and the following disclaimer in
> - the documentation and/or other materials provided with the
> - distribution.
> - * Neither the name of the copyright holder nor the names of its
> - contributors may be used to endorse or promote products derived
> - from this software without specific prior written permission.
> -
> - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
> - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
> - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> -*/
> -#ifndef _H_ZLIB_ENCODER
> -#define _H_ZLIB_ENCODER
> -
> -typedef struct ZlibEncoder ZlibEncoder;
> -typedef struct ZlibEncoderUsrContext ZlibEncoderUsrContext;
> -
> -struct ZlibEncoderUsrContext {
> - int (*more_space)(ZlibEncoderUsrContext *usr, uint8_t **io_ptr);
> - int (*more_input)(ZlibEncoderUsrContext *usr, uint8_t **input);
> -};
> -
> -ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level);
> -void zlib_encoder_destroy(ZlibEncoder *encoder);
> -
> -/* returns the total size of the encoded data */
> -int zlib_encode(ZlibEncoder *zlib, int level, int input_size,
> - uint8_t *io_ptr, unsigned int num_io_bytes);
> -#endif
> --
> 2.4.3
>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel
Acked-by: Fabiano FidĂȘncio <fidencio at redhat.com>
More information about the Spice-devel
mailing list