[Spice-devel] [PATCH 10/22] server: rename files
Frediano Ziglio
fziglio at redhat.com
Wed Dec 2 08:19:53 PST 2015
---
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
More information about the Spice-devel
mailing list