[Spice-devel] server: add monitors_config support

Submitted by Alon Levy on July 16, 2012, 3:39 p.m.

Details

Message ID 1342453183-15363-1-git-send-email-alevy@redhat.com
State Accepted
Commit 0acea04d71f28e98b15620e31902dd8e2196c637
Headers show

Not browsing as part of any series.

Commit Message

Alon Levy July 16, 2012, 3:39 p.m.
a SpiceMsgDisplayMonitorsConfig is sent on two occasions:
 * as a result of a spice_qxl_monitors_config_async
 * whenever a client connects and there is a previously set monitors
   config

Sending the new message is protected by a new cap,
SPICE_DISPLAY_CAP_MONITORS_CONFIG

More elaborately:
spice_qxl_monitors_config_async receives a QXLPHYSICAL address of a
QXLMonitorsConfig struct and reads it, caching it in the RedWorker, and
sending it to all clients. Whenever a new client connects it receives
a SpiceMsgDisplayMonitorsConfig message as well.
---
This patch on purpose doesn't change spice-common, it requires at least
spice-common 7eba850ad562f3c5a29621af09d93535a76ff99c .

 server/red_dispatcher.c  |   25 +++++
 server/red_dispatcher.h  |    5 +
 server/red_worker.c      |  247 +++++++++++++++++++++++++++++++++++++++++++++-
 server/red_worker.h      |    2 +
 server/spice-server.syms |    4 +
 server/spice.h           |    3 +
 6 files changed, 285 insertions(+), 1 deletion(-)

Patch hide | download patch | download mbox

diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c
index 6b96a2e..8d9c073 100644
--- a/server/red_dispatcher.c
+++ b/server/red_dispatcher.c
@@ -648,6 +648,21 @@  static void red_dispatcher_flush_surfaces_async(RedDispatcher *dispatcher, uint6
     dispatcher_send_message(&dispatcher->dispatcher, message, &payload);
 }
 
+static void red_dispatcher_monitors_config_async(RedDispatcher *dispatcher,
+                                                 QXLPHYSICAL monitors_config,
+                                                 int group_id,
+                                                 uint64_t cookie)
+{
+    RedWorkerMessageMonitorsConfigAsync payload;
+    RedWorkerMessage message = RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC;
+
+    payload.base.cmd = async_command_alloc(dispatcher, message, cookie);
+    payload.monitors_config = monitors_config;
+    payload.group_id = group_id;
+
+    dispatcher_send_message(&dispatcher->dispatcher, message, &payload);
+}
+
 static void red_dispatcher_stop(RedDispatcher *dispatcher)
 {
     RedWorkerMessageStop payload;
@@ -908,6 +923,13 @@  void spice_qxl_flush_surfaces_async(QXLInstance *instance, uint64_t cookie)
     red_dispatcher_flush_surfaces_async(instance->st->dispatcher, cookie);
 }
 
+SPICE_GNUC_VISIBLE
+void spice_qxl_monitors_config_async(QXLInstance *instance, QXLPHYSICAL monitors_config,
+                                     int group_id, uint64_t cookie)
+{
+    red_dispatcher_monitors_config_async(instance->st->dispatcher, monitors_config, group_id, cookie);
+}
+
 void red_dispatcher_async_complete(struct RedDispatcher *dispatcher,
                                    AsyncCommand *async_command)
 {
@@ -935,6 +957,8 @@  void red_dispatcher_async_complete(struct RedDispatcher *dispatcher,
         break;
     case RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC:
         break;
+    case RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC:
+        break;
     default:
         spice_warning("unexpected message %d", async_command->message);
     }
@@ -1054,6 +1078,7 @@  RedDispatcher *red_dispatcher_init(QXLInstance *qxl)
         client_cbs.migrate = red_dispatcher_display_migrate;
         red_channel_register_client_cbs(display_channel, &client_cbs);
         red_channel_set_data(display_channel, red_dispatcher);
+        red_channel_set_cap(display_channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG);
         reds_register_channel(display_channel);
     }
 
diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h
index 36db4e3..98f8cea 100644
--- a/server/red_dispatcher.h
+++ b/server/red_dispatcher.h
@@ -189,5 +189,10 @@  typedef struct RedWorkerMessageDestroySurfaceWaitAsync {
 typedef struct RedWorkerMessageResetMemslots {
 } RedWorkerMessageResetMemslots;
 
+typedef struct RedWorkerMessageMonitorsConfigAsync {
+    RedWorkerMessageAsync base;
+    QXLPHYSICAL monitors_config;
+    int group_id;
+} RedWorkerMessageMonitorsConfigAsync;
 
 #endif
diff --git a/server/red_worker.c b/server/red_worker.c
index 5634db5..2400c3a 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -45,6 +45,7 @@ 
 #include <netinet/tcp.h>
 #include <setjmp.h>
 #include <openssl/ssl.h>
+#include <inttypes.h>
 
 #include <spice/protocol.h>
 #include <spice/qxl_dev.h>
@@ -267,6 +268,7 @@  enum {
     PIPE_ITEM_TYPE_INVAL_PALLET_CACHE,
     PIPE_ITEM_TYPE_CREATE_SURFACE,
     PIPE_ITEM_TYPE_DESTROY_SURFACE,
+    PIPE_ITEM_TYPE_MONITORS_CONFIG,
 };
 
 typedef struct VerbItem {
@@ -313,6 +315,19 @@  typedef struct SurfaceDestroyItem {
     PipeItem pipe_item;
 } SurfaceDestroyItem;
 
+typedef struct MonitorsConfig {
+    int refs;
+    struct RedWorker *worker;
+    int count;
+    int max_allowed;
+    QXLHead heads[0];
+} MonitorsConfig;
+
+typedef struct MonitorsConfigItem {
+    PipeItem pipe_item;
+    MonitorsConfig *monitors_config;
+} MonitorsConfigItem;
+
 typedef struct CursorItem {
     uint32_t group_id;
     int refs;
@@ -899,6 +914,8 @@  typedef struct RedWorker {
     uint32_t n_surfaces;
     SpiceImageSurfaces image_surfaces;
 
+    MonitorsConfig *monitors_config;
+
     Ring current_list;
     uint32_t current_size;
     uint32_t drawable_count;
@@ -970,6 +987,8 @@  typedef struct RedWorker {
     uint64_t *wakeup_counter;
     uint64_t *command_counter;
 #endif
+
+    int driver_has_monitors_config;
 } RedWorker;
 
 typedef enum {
@@ -1031,6 +1050,8 @@  static void red_wait_pipe_item_sent(RedChannelClient *rcc, PipeItem *item);
 static void dump_bitmap(RedWorker *worker, SpiceBitmap *bitmap, uint32_t group_id);
 #endif
 
+static void red_push_monitors_config(DisplayChannelClient *dcc);
+
 /*
  * Macros to make iterating over stuff easier
  * The two collections we iterate over:
@@ -1194,6 +1215,24 @@  static void print_compress_stats(DisplayChannel *display_channel)
 
 #endif
 
+static MonitorsConfig *monitors_config_getref(MonitorsConfig *monitors_config)
+{
+    monitors_config->refs++;
+
+    return monitors_config;
+}
+
+static void monitors_config_decref(MonitorsConfig *monitors_config)
+{
+    if (--monitors_config->refs > 0) {
+        return;
+    }
+
+    spice_debug("removing worker monitors config");
+    monitors_config->worker->monitors_config = NULL;
+    free(monitors_config);
+}
+
 static inline int is_primary_surface(RedWorker *worker, uint32_t surface_id)
 {
     if (surface_id == 0) {
@@ -8697,6 +8736,33 @@  static void red_marshall_surface_destroy(RedChannelClient *rcc,
     spice_marshall_msg_display_surface_destroy(base_marshaller, &surface_destroy);
 }
 
+static void red_marshall_monitors_config(RedChannelClient *rcc, SpiceMarshaller *base_marshaller,
+                                         MonitorsConfig *monitors_config)
+{
+    int heads_size = sizeof(SpiceHead) * monitors_config->count;
+    int i;
+    SpiceMsgDisplayMonitorsConfig *msg = spice_malloc0(sizeof(*msg) + heads_size);
+    int count = 0; // ignore monitors_config->count, it may contain zero width monitors, remove them now
+
+    red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_MONITORS_CONFIG, NULL);
+    for (i = 0 ; i < monitors_config->count; ++i) {
+        if (monitors_config->heads[i].width == 0 || monitors_config->heads[i].height == 0) {
+            continue;
+        }
+        msg->heads[count].id = monitors_config->heads[i].id;
+        msg->heads[count].surface_id = monitors_config->heads[i].surface_id;
+        msg->heads[count].width = monitors_config->heads[i].width;
+        msg->heads[count].height = monitors_config->heads[i].height;
+        msg->heads[count].x = monitors_config->heads[i].x;
+        msg->heads[count].y = monitors_config->heads[i].y;
+        count++;
+    }
+    msg->count = count;
+    msg->max_allowed = monitors_config->max_allowed;
+    spice_marshall_msg_display_monitors_config(base_marshaller, msg);
+    free(msg);
+}
+
 static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item)
 {
     SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
@@ -8765,6 +8831,12 @@  static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item
         red_marshall_surface_destroy(rcc, m, surface_destroy->surface_destroy.surface_id);
         break;
     }
+    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
+        MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(pipe_item,
+                                                             MonitorsConfigItem, pipe_item);
+        red_marshall_monitors_config(rcc, m, monconf_item->monitors_config);
+        break;
+    }
     default:
         spice_error("invalid pipe item type");
     }
@@ -9321,6 +9393,7 @@  static void on_new_display_channel_client(DisplayChannelClient *dcc)
         red_current_flush(worker, 0);
         push_new_primary_surface(dcc);
         red_push_surface_image(dcc, 0);
+        red_push_monitors_config(dcc);
         red_pipe_add_verb(rcc, SPICE_MSG_DISPLAY_MARK);
         red_disply_start_streams(dcc);
     }
@@ -9932,6 +10005,13 @@  static void display_channel_client_release_item_after_push(DisplayChannelClient
     case PIPE_ITEM_TYPE_VERB:
         free(item);
         break;
+    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
+        MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item,
+                                                             MonitorsConfigItem, pipe_item);
+        monitors_config_decref(monconf_item->monitors_config);
+        free(item);
+        break;
+    }
     default:
         spice_critical("invalid item type");
     }
@@ -9982,6 +10062,13 @@  static void display_channel_client_release_item_before_push(DisplayChannelClient
         free(surface_destroy);
         break;
     }
+    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
+        MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item,
+                                                             MonitorsConfigItem, pipe_item);
+        monitors_config_decref(monconf_item->monitors_config);
+        free(item);
+        break;
+    }
     case PIPE_ITEM_TYPE_INVAL_ONE:
     case PIPE_ITEM_TYPE_VERB:
     case PIPE_ITEM_TYPE_MIGRATE:
@@ -10578,6 +10665,125 @@  void handle_dev_destroy_surfaces(void *opaque, void *payload)
     dev_destroy_surfaces(worker);
 }
 
+static MonitorsConfigItem *get_monitors_config_item(
+    RedChannel* channel, MonitorsConfig *monitors_config)
+{
+    MonitorsConfigItem *mci;
+
+    mci = (MonitorsConfigItem *)spice_malloc(sizeof(*mci));
+    mci->monitors_config = monitors_config;
+
+    red_channel_pipe_item_init(channel,
+            &mci->pipe_item, PIPE_ITEM_TYPE_MONITORS_CONFIG);
+    return mci;
+}
+
+static inline void red_monitors_config_item_add(DisplayChannelClient *dcc)
+{
+    MonitorsConfigItem *mci;
+    RedWorker *worker = dcc->common.worker;
+
+    mci = get_monitors_config_item(dcc->common.base.channel,
+                                   monitors_config_getref(worker->monitors_config));
+    red_channel_client_pipe_add(&dcc->common.base, &mci->pipe_item);
+}
+
+static void worker_update_monitors_config(RedWorker *worker,
+                                          QXLMonitorsConfig *dev_monitors_config)
+{
+    int heads_size;
+    MonitorsConfig *monitors_config;
+    int real_count = 0;
+    int i;
+
+    if (worker->monitors_config) {
+        monitors_config_decref(worker->monitors_config);
+    }
+
+    spice_debug("monitors config %d(%d)",
+                dev_monitors_config->count,
+                dev_monitors_config->max_allowed);
+    for (i = 0; i < dev_monitors_config->count; i++) {
+        spice_debug("+%d+%d %dx%d",
+                    dev_monitors_config->heads[i].x,
+                    dev_monitors_config->heads[i].y,
+                    dev_monitors_config->heads[i].width,
+                    dev_monitors_config->heads[i].height);
+    }
+
+    // Ignore any empty sized monitors at the end of the config.
+    // 4: {w1,h1},{w2,h2},{0,0},{0,0} -> 2: {w1,h1},{w2,h2}
+    for (i = dev_monitors_config->count ; i > 0 ; --i) {
+        if (dev_monitors_config->heads[i - 1].width > 0 &&
+            dev_monitors_config->heads[i - 1].height > 0) {
+            real_count = i;
+            break;
+        }
+    }
+    heads_size = real_count * sizeof(QXLHead);
+    spice_debug("new working monitor config (count: %d, real: %d)",
+                dev_monitors_config->count, real_count);
+    worker->monitors_config = monitors_config =
+        spice_malloc(sizeof(*monitors_config) + heads_size);
+    monitors_config->refs = 1;
+    monitors_config->worker = worker;
+    monitors_config->count = dev_monitors_config->count;
+    monitors_config->max_allowed = dev_monitors_config->max_allowed;
+    memcpy(monitors_config->heads, dev_monitors_config->heads, heads_size);
+}
+
+static void red_push_monitors_config(DisplayChannelClient *dcc)
+{
+    MonitorsConfig *monitors_config = DCC_TO_WORKER(dcc)->monitors_config;
+
+    spice_return_if_fail(monitors_config != NULL);
+
+    if (!red_channel_client_test_remote_cap(&dcc->common.base,
+                                            SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
+        return;
+    }
+    red_monitors_config_item_add(dcc);
+    red_channel_client_push(&dcc->common.base);
+}
+
+static void red_worker_push_monitors_config(RedWorker *worker)
+{
+    DisplayChannelClient *dcc;
+    RingItem *item;
+
+    WORKER_FOREACH_DCC(worker, item, dcc) {
+        red_push_monitors_config(dcc);
+    }
+}
+
+static void set_monitors_config_to_primary(RedWorker *worker)
+{
+    QXLHead *head;
+    DrawContext *context;
+
+    if (!worker->surfaces[0].context.canvas) {
+        spice_warning("%s: no primary surface", __FUNCTION__);
+        return;
+    }
+    if (worker->monitors_config) {
+        monitors_config_decref(worker->monitors_config);
+    }
+    context = &worker->surfaces[0].context;
+    worker->monitors_config =
+        spice_malloc(sizeof(*worker->monitors_config) + sizeof(QXLHead));
+    worker->monitors_config->refs = 1;
+    worker->monitors_config->worker = worker;
+    worker->monitors_config->count = 1;
+    worker->monitors_config->max_allowed = 1;
+    head = worker->monitors_config->heads;
+    head->id = 0;
+    head->surface_id = 0;
+    head->width = context->width;
+    head->height = context->height;
+    head->x = 0;
+    head->y = 0;
+}
+
 static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id,
                                        QXLDevSurfaceCreate surface)
 {
@@ -10601,7 +10807,10 @@  static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id,
 
     red_create_surface(worker, 0, surface.width, surface.height, surface.stride, surface.format,
                        line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE);
-
+    set_monitors_config_to_primary(worker);
+    if (!worker->driver_has_monitors_config) {
+        red_worker_push_monitors_config(worker);
+    }
     if (display_is_connected(worker)) {
         red_pipes_add_verb(&worker->display_channel->common.base,
                            SPICE_MSG_DISPLAY_MARK);
@@ -10849,6 +11058,36 @@  void handle_dev_display_migrate(void *opaque, void *payload)
     red_migrate_display(worker, rcc);
 }
 
+static void handle_dev_monitors_config_async(void *opaque, void *payload)
+{
+    RedWorkerMessageMonitorsConfigAsync *msg = payload;
+    RedWorker *worker = opaque;
+    int min_size = sizeof(QXLMonitorsConfig) + sizeof(QXLHead);
+    int error;
+    QXLMonitorsConfig *dev_monitors_config =
+        (QXLMonitorsConfig*)get_virt(&worker->mem_slots, msg->monitors_config,
+                                     min_size, msg->group_id, &error);
+
+    if (error) {
+        /* TODO: raise guest bug (requires added QXL interface) */
+        return;
+    }
+    worker->driver_has_monitors_config = 1;
+    if (dev_monitors_config->count == 0) {
+        spice_warning("ignoring an empty monitors config message from driver");
+        return;
+    }
+    if (dev_monitors_config->count > dev_monitors_config->max_allowed) {
+        spice_warning("ignoring malformed monitors_config from driver, "
+                      "count > max_allowed %d > %d",
+                      dev_monitors_config->count,
+                      dev_monitors_config->max_allowed);
+        return;
+    }
+    worker_update_monitors_config(worker, dev_monitors_config);
+    red_worker_push_monitors_config(worker);
+}
+
 /* TODO: special, perhaps use another dispatcher? */
 void handle_dev_cursor_channel_create(void *opaque, void *payload)
 {
@@ -11203,6 +11442,11 @@  static void register_callbacks(Dispatcher *dispatcher)
                                 handle_dev_reset_memslots,
                                 sizeof(RedWorkerMessageResetMemslots),
                                 DISPATCHER_NONE);
+    dispatcher_register_handler(dispatcher,
+                                RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,
+                                handle_dev_monitors_config_async,
+                                sizeof(RedWorkerMessageMonitorsConfigAsync),
+                                DISPATCHER_ASYNC);
 }
 
 
@@ -11241,6 +11485,7 @@  static void red_init(RedWorker *worker, WorkerInitData *init_data)
     worker->jpeg_state = init_data->jpeg_state;
     worker->zlib_glz_state = init_data->zlib_glz_state;
     worker->streaming_video = init_data->streaming_video;
+    worker->driver_has_monitors_config = 0;
     ring_init(&worker->current_list);
     image_cache_init(&worker->image_cache);
     image_surface_init(worker);
diff --git a/server/red_worker.h b/server/red_worker.h
index fd23ede..6c5b839 100644
--- a/server/red_worker.h
+++ b/server/red_worker.h
@@ -85,6 +85,8 @@  enum {
     RED_WORKER_MESSAGE_DISPLAY_CHANNEL_CREATE,
     RED_WORKER_MESSAGE_CURSOR_CHANNEL_CREATE,
 
+    RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,
+
     RED_WORKER_MESSAGE_COUNT // LAST
 };
 
diff --git a/server/spice-server.syms b/server/spice-server.syms
index 99a7271..a45951c 100644
--- a/server/spice-server.syms
+++ b/server/spice-server.syms
@@ -112,3 +112,7 @@  global:
 SPICE_SERVER_0.10.3 {
     spice_server_is_server_mouse;
 } SPICE_SERVER_0.10.2;
+
+SPICE_SERVER_0.10.4 {
+    spice_qxl_monitors_config_async;
+} SPICE_SERVER_0.10.3;
diff --git a/server/spice.h b/server/spice.h
index abaffbd..ec28f4a 100644
--- a/server/spice.h
+++ b/server/spice.h
@@ -161,6 +161,9 @@  void spice_qxl_create_primary_surface_async(QXLInstance *instance, uint32_t surf
 void spice_qxl_destroy_surface_async(QXLInstance *instance, uint32_t surface_id, uint64_t cookie);
 /* suspend and resolution change on windows drivers */
 void spice_qxl_flush_surfaces_async(QXLInstance *instance, uint64_t cookie);
+/* since spice 0.12.0 */
+void spice_qxl_monitors_config_async(QXLInstance *instance, QXLPHYSICAL monitors_config,
+                                     int group_id, uint64_t cookie);
 
 typedef struct QXLDrawArea {
     uint8_t *buf;

Comments

ack

----- Mensaje original -----
> a SpiceMsgDisplayMonitorsConfig is sent on two occasions:
>  * as a result of a spice_qxl_monitors_config_async
>  * whenever a client connects and there is a previously set monitors
>    config
> 
> Sending the new message is protected by a new cap,
> SPICE_DISPLAY_CAP_MONITORS_CONFIG
> 
> More elaborately:
> spice_qxl_monitors_config_async receives a QXLPHYSICAL address of a
> QXLMonitorsConfig struct and reads it, caching it in the RedWorker,
> and
> sending it to all clients. Whenever a new client connects it receives
> a SpiceMsgDisplayMonitorsConfig message as well.
> ---
> This patch on purpose doesn't change spice-common, it requires at
> least
> spice-common 7eba850ad562f3c5a29621af09d93535a76ff99c .
> 
>  server/red_dispatcher.c  |   25 +++++
>  server/red_dispatcher.h  |    5 +
>  server/red_worker.c      |  247
>  +++++++++++++++++++++++++++++++++++++++++++++-
>  server/red_worker.h      |    2 +
>  server/spice-server.syms |    4 +
>  server/spice.h           |    3 +
>  6 files changed, 285 insertions(+), 1 deletion(-)
> 
> diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c
> index 6b96a2e..8d9c073 100644
> --- a/server/red_dispatcher.c
> +++ b/server/red_dispatcher.c
> @@ -648,6 +648,21 @@ static void
> red_dispatcher_flush_surfaces_async(RedDispatcher *dispatcher, uint6
>      dispatcher_send_message(&dispatcher->dispatcher, message,
>      &payload);
>  }
>  
> +static void red_dispatcher_monitors_config_async(RedDispatcher
> *dispatcher,
> +                                                 QXLPHYSICAL
> monitors_config,
> +                                                 int group_id,
> +                                                 uint64_t cookie)
> +{
> +    RedWorkerMessageMonitorsConfigAsync payload;
> +    RedWorkerMessage message =
> RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC;
> +
> +    payload.base.cmd = async_command_alloc(dispatcher, message,
> cookie);
> +    payload.monitors_config = monitors_config;
> +    payload.group_id = group_id;
> +
> +    dispatcher_send_message(&dispatcher->dispatcher, message,
> &payload);
> +}
> +
>  static void red_dispatcher_stop(RedDispatcher *dispatcher)
>  {
>      RedWorkerMessageStop payload;
> @@ -908,6 +923,13 @@ void spice_qxl_flush_surfaces_async(QXLInstance
> *instance, uint64_t cookie)
>      red_dispatcher_flush_surfaces_async(instance->st->dispatcher,
>      cookie);
>  }
>  
> +SPICE_GNUC_VISIBLE
> +void spice_qxl_monitors_config_async(QXLInstance *instance,
> QXLPHYSICAL monitors_config,
> +                                     int group_id, uint64_t cookie)
> +{
> +    red_dispatcher_monitors_config_async(instance->st->dispatcher,
> monitors_config, group_id, cookie);
> +}
> +
>  void red_dispatcher_async_complete(struct RedDispatcher *dispatcher,
>                                     AsyncCommand *async_command)
>  {
> @@ -935,6 +957,8 @@ void red_dispatcher_async_complete(struct
> RedDispatcher *dispatcher,
>          break;
>      case RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC:
>          break;
> +    case RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC:
> +        break;
>      default:
>          spice_warning("unexpected message %d",
>          async_command->message);
>      }
> @@ -1054,6 +1078,7 @@ RedDispatcher *red_dispatcher_init(QXLInstance
> *qxl)
>          client_cbs.migrate = red_dispatcher_display_migrate;
>          red_channel_register_client_cbs(display_channel,
>          &client_cbs);
>          red_channel_set_data(display_channel, red_dispatcher);
> +        red_channel_set_cap(display_channel,
> SPICE_DISPLAY_CAP_MONITORS_CONFIG);
>          reds_register_channel(display_channel);
>      }
>  
> diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h
> index 36db4e3..98f8cea 100644
> --- a/server/red_dispatcher.h
> +++ b/server/red_dispatcher.h
> @@ -189,5 +189,10 @@ typedef struct
> RedWorkerMessageDestroySurfaceWaitAsync {
>  typedef struct RedWorkerMessageResetMemslots {
>  } RedWorkerMessageResetMemslots;
>  
> +typedef struct RedWorkerMessageMonitorsConfigAsync {
> +    RedWorkerMessageAsync base;
> +    QXLPHYSICAL monitors_config;
> +    int group_id;
> +} RedWorkerMessageMonitorsConfigAsync;
>  
>  #endif
> diff --git a/server/red_worker.c b/server/red_worker.c
> index 5634db5..2400c3a 100644
> --- a/server/red_worker.c
> +++ b/server/red_worker.c
> @@ -45,6 +45,7 @@
>  #include <netinet/tcp.h>
>  #include <setjmp.h>
>  #include <openssl/ssl.h>
> +#include <inttypes.h>
>  
>  #include <spice/protocol.h>
>  #include <spice/qxl_dev.h>
> @@ -267,6 +268,7 @@ enum {
>      PIPE_ITEM_TYPE_INVAL_PALLET_CACHE,
>      PIPE_ITEM_TYPE_CREATE_SURFACE,
>      PIPE_ITEM_TYPE_DESTROY_SURFACE,
> +    PIPE_ITEM_TYPE_MONITORS_CONFIG,
>  };
>  
>  typedef struct VerbItem {
> @@ -313,6 +315,19 @@ typedef struct SurfaceDestroyItem {
>      PipeItem pipe_item;
>  } SurfaceDestroyItem;
>  
> +typedef struct MonitorsConfig {
> +    int refs;
> +    struct RedWorker *worker;
> +    int count;
> +    int max_allowed;
> +    QXLHead heads[0];
> +} MonitorsConfig;
> +
> +typedef struct MonitorsConfigItem {
> +    PipeItem pipe_item;
> +    MonitorsConfig *monitors_config;
> +} MonitorsConfigItem;
> +
>  typedef struct CursorItem {
>      uint32_t group_id;
>      int refs;
> @@ -899,6 +914,8 @@ typedef struct RedWorker {
>      uint32_t n_surfaces;
>      SpiceImageSurfaces image_surfaces;
>  
> +    MonitorsConfig *monitors_config;
> +
>      Ring current_list;
>      uint32_t current_size;
>      uint32_t drawable_count;
> @@ -970,6 +987,8 @@ typedef struct RedWorker {
>      uint64_t *wakeup_counter;
>      uint64_t *command_counter;
>  #endif
> +
> +    int driver_has_monitors_config;
>  } RedWorker;
>  
>  typedef enum {
> @@ -1031,6 +1050,8 @@ static void
> red_wait_pipe_item_sent(RedChannelClient *rcc, PipeItem *item);
>  static void dump_bitmap(RedWorker *worker, SpiceBitmap *bitmap,
>  uint32_t group_id);
>  #endif
>  
> +static void red_push_monitors_config(DisplayChannelClient *dcc);
> +
>  /*
>   * Macros to make iterating over stuff easier
>   * The two collections we iterate over:
> @@ -1194,6 +1215,24 @@ static void
> print_compress_stats(DisplayChannel *display_channel)
>  
>  #endif
>  
> +static MonitorsConfig *monitors_config_getref(MonitorsConfig
> *monitors_config)
> +{
> +    monitors_config->refs++;
> +
> +    return monitors_config;
> +}
> +
> +static void monitors_config_decref(MonitorsConfig *monitors_config)
> +{
> +    if (--monitors_config->refs > 0) {
> +        return;
> +    }
> +
> +    spice_debug("removing worker monitors config");
> +    monitors_config->worker->monitors_config = NULL;
> +    free(monitors_config);
> +}
> +
>  static inline int is_primary_surface(RedWorker *worker, uint32_t
>  surface_id)
>  {
>      if (surface_id == 0) {
> @@ -8697,6 +8736,33 @@ static void
> red_marshall_surface_destroy(RedChannelClient *rcc,
>      spice_marshall_msg_display_surface_destroy(base_marshaller,
>      &surface_destroy);
>  }
>  
> +static void red_marshall_monitors_config(RedChannelClient *rcc,
> SpiceMarshaller *base_marshaller,
> +                                         MonitorsConfig
> *monitors_config)
> +{
> +    int heads_size = sizeof(SpiceHead) * monitors_config->count;
> +    int i;
> +    SpiceMsgDisplayMonitorsConfig *msg = spice_malloc0(sizeof(*msg)
> + heads_size);
> +    int count = 0; // ignore monitors_config->count, it may contain
> zero width monitors, remove them now
> +
> +    red_channel_client_init_send_data(rcc,
> SPICE_MSG_DISPLAY_MONITORS_CONFIG, NULL);
> +    for (i = 0 ; i < monitors_config->count; ++i) {
> +        if (monitors_config->heads[i].width == 0 ||
> monitors_config->heads[i].height == 0) {
> +            continue;
> +        }
> +        msg->heads[count].id = monitors_config->heads[i].id;
> +        msg->heads[count].surface_id =
> monitors_config->heads[i].surface_id;
> +        msg->heads[count].width = monitors_config->heads[i].width;
> +        msg->heads[count].height = monitors_config->heads[i].height;
> +        msg->heads[count].x = monitors_config->heads[i].x;
> +        msg->heads[count].y = monitors_config->heads[i].y;
> +        count++;
> +    }
> +    msg->count = count;
> +    msg->max_allowed = monitors_config->max_allowed;
> +    spice_marshall_msg_display_monitors_config(base_marshaller,
> msg);
> +    free(msg);
> +}
> +
>  static void display_channel_send_item(RedChannelClient *rcc,
>  PipeItem *pipe_item)
>  {
>      SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
> @@ -8765,6 +8831,12 @@ static void
> display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item
>          red_marshall_surface_destroy(rcc, m,
>          surface_destroy->surface_destroy.surface_id);
>          break;
>      }
> +    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
> +        MonitorsConfigItem *monconf_item =
> SPICE_CONTAINEROF(pipe_item,
> +
>                                                             MonitorsConfigItem,
> pipe_item);
> +        red_marshall_monitors_config(rcc, m,
> monconf_item->monitors_config);
> +        break;
> +    }
>      default:
>          spice_error("invalid pipe item type");
>      }
> @@ -9321,6 +9393,7 @@ static void
> on_new_display_channel_client(DisplayChannelClient *dcc)
>          red_current_flush(worker, 0);
>          push_new_primary_surface(dcc);
>          red_push_surface_image(dcc, 0);
> +        red_push_monitors_config(dcc);
>          red_pipe_add_verb(rcc, SPICE_MSG_DISPLAY_MARK);
>          red_disply_start_streams(dcc);
>      }
> @@ -9932,6 +10005,13 @@ static void
> display_channel_client_release_item_after_push(DisplayChannelClient
>      case PIPE_ITEM_TYPE_VERB:
>          free(item);
>          break;
> +    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
> +        MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item,
> +
>                                                             MonitorsConfigItem,
> pipe_item);
> +        monitors_config_decref(monconf_item->monitors_config);
> +        free(item);
> +        break;
> +    }
>      default:
>          spice_critical("invalid item type");
>      }
> @@ -9982,6 +10062,13 @@ static void
> display_channel_client_release_item_before_push(DisplayChannelClient
>          free(surface_destroy);
>          break;
>      }
> +    case PIPE_ITEM_TYPE_MONITORS_CONFIG: {
> +        MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item,
> +
>                                                             MonitorsConfigItem,
> pipe_item);
> +        monitors_config_decref(monconf_item->monitors_config);
> +        free(item);
> +        break;
> +    }
>      case PIPE_ITEM_TYPE_INVAL_ONE:
>      case PIPE_ITEM_TYPE_VERB:
>      case PIPE_ITEM_TYPE_MIGRATE:
> @@ -10578,6 +10665,125 @@ void handle_dev_destroy_surfaces(void
> *opaque, void *payload)
>      dev_destroy_surfaces(worker);
>  }
>  
> +static MonitorsConfigItem *get_monitors_config_item(
> +    RedChannel* channel, MonitorsConfig *monitors_config)
> +{
> +    MonitorsConfigItem *mci;
> +
> +    mci = (MonitorsConfigItem *)spice_malloc(sizeof(*mci));
> +    mci->monitors_config = monitors_config;
> +
> +    red_channel_pipe_item_init(channel,
> +            &mci->pipe_item, PIPE_ITEM_TYPE_MONITORS_CONFIG);
> +    return mci;
> +}
> +
> +static inline void red_monitors_config_item_add(DisplayChannelClient
> *dcc)
> +{
> +    MonitorsConfigItem *mci;
> +    RedWorker *worker = dcc->common.worker;
> +
> +    mci = get_monitors_config_item(dcc->common.base.channel,
> +
>                                   monitors_config_getref(worker->monitors_config));
> +    red_channel_client_pipe_add(&dcc->common.base, &mci->pipe_item);
> +}
> +
> +static void worker_update_monitors_config(RedWorker *worker,
> +                                          QXLMonitorsConfig
> *dev_monitors_config)
> +{
> +    int heads_size;
> +    MonitorsConfig *monitors_config;
> +    int real_count = 0;
> +    int i;
> +
> +    if (worker->monitors_config) {
> +        monitors_config_decref(worker->monitors_config);
> +    }
> +
> +    spice_debug("monitors config %d(%d)",
> +                dev_monitors_config->count,
> +                dev_monitors_config->max_allowed);
> +    for (i = 0; i < dev_monitors_config->count; i++) {
> +        spice_debug("+%d+%d %dx%d",
> +                    dev_monitors_config->heads[i].x,
> +                    dev_monitors_config->heads[i].y,
> +                    dev_monitors_config->heads[i].width,
> +                    dev_monitors_config->heads[i].height);
> +    }
> +
> +    // Ignore any empty sized monitors at the end of the config.
> +    // 4: {w1,h1},{w2,h2},{0,0},{0,0} -> 2: {w1,h1},{w2,h2}
> +    for (i = dev_monitors_config->count ; i > 0 ; --i) {
> +        if (dev_monitors_config->heads[i - 1].width > 0 &&
> +            dev_monitors_config->heads[i - 1].height > 0) {
> +            real_count = i;
> +            break;
> +        }
> +    }
> +    heads_size = real_count * sizeof(QXLHead);
> +    spice_debug("new working monitor config (count: %d, real: %d)",
> +                dev_monitors_config->count, real_count);
> +    worker->monitors_config = monitors_config =
> +        spice_malloc(sizeof(*monitors_config) + heads_size);
> +    monitors_config->refs = 1;
> +    monitors_config->worker = worker;
> +    monitors_config->count = dev_monitors_config->count;
> +    monitors_config->max_allowed = dev_monitors_config->max_allowed;
> +    memcpy(monitors_config->heads, dev_monitors_config->heads,
> heads_size);
> +}
> +
> +static void red_push_monitors_config(DisplayChannelClient *dcc)
> +{
> +    MonitorsConfig *monitors_config =
> DCC_TO_WORKER(dcc)->monitors_config;
> +
> +    spice_return_if_fail(monitors_config != NULL);
> +
> +    if (!red_channel_client_test_remote_cap(&dcc->common.base,
> +
>                                            SPICE_DISPLAY_CAP_MONITORS_CONFIG))
> {
> +        return;
> +    }
> +    red_monitors_config_item_add(dcc);
> +    red_channel_client_push(&dcc->common.base);
> +}
> +
> +static void red_worker_push_monitors_config(RedWorker *worker)
> +{
> +    DisplayChannelClient *dcc;
> +    RingItem *item;
> +
> +    WORKER_FOREACH_DCC(worker, item, dcc) {
> +        red_push_monitors_config(dcc);
> +    }
> +}
> +
> +static void set_monitors_config_to_primary(RedWorker *worker)
> +{
> +    QXLHead *head;
> +    DrawContext *context;
> +
> +    if (!worker->surfaces[0].context.canvas) {
> +        spice_warning("%s: no primary surface", __FUNCTION__);
> +        return;
> +    }
> +    if (worker->monitors_config) {
> +        monitors_config_decref(worker->monitors_config);
> +    }
> +    context = &worker->surfaces[0].context;
> +    worker->monitors_config =
> +        spice_malloc(sizeof(*worker->monitors_config) +
> sizeof(QXLHead));
> +    worker->monitors_config->refs = 1;
> +    worker->monitors_config->worker = worker;
> +    worker->monitors_config->count = 1;
> +    worker->monitors_config->max_allowed = 1;
> +    head = worker->monitors_config->heads;
> +    head->id = 0;
> +    head->surface_id = 0;
> +    head->width = context->width;
> +    head->height = context->height;
> +    head->x = 0;
> +    head->y = 0;
> +}
> +
>  static void dev_create_primary_surface(RedWorker *worker, uint32_t
>  surface_id,
>                                         QXLDevSurfaceCreate surface)
>  {
> @@ -10601,7 +10807,10 @@ static void
> dev_create_primary_surface(RedWorker *worker, uint32_t surface_id,
>  
>      red_create_surface(worker, 0, surface.width, surface.height,
>      surface.stride, surface.format,
>                         line_0, surface.flags &
>                         QXL_SURF_FLAG_KEEP_DATA, TRUE);
> -
> +    set_monitors_config_to_primary(worker);
> +    if (!worker->driver_has_monitors_config) {
> +        red_worker_push_monitors_config(worker);
> +    }
>      if (display_is_connected(worker)) {
>          red_pipes_add_verb(&worker->display_channel->common.base,
>                             SPICE_MSG_DISPLAY_MARK);
> @@ -10849,6 +11058,36 @@ void handle_dev_display_migrate(void
> *opaque, void *payload)
>      red_migrate_display(worker, rcc);
>  }
>  
> +static void handle_dev_monitors_config_async(void *opaque, void
> *payload)
> +{
> +    RedWorkerMessageMonitorsConfigAsync *msg = payload;
> +    RedWorker *worker = opaque;
> +    int min_size = sizeof(QXLMonitorsConfig) + sizeof(QXLHead);
> +    int error;
> +    QXLMonitorsConfig *dev_monitors_config =
> +        (QXLMonitorsConfig*)get_virt(&worker->mem_slots,
> msg->monitors_config,
> +                                     min_size, msg->group_id,
> &error);
> +
> +    if (error) {
> +        /* TODO: raise guest bug (requires added QXL interface) */
> +        return;
> +    }
> +    worker->driver_has_monitors_config = 1;
> +    if (dev_monitors_config->count == 0) {
> +        spice_warning("ignoring an empty monitors config message
> from driver");
> +        return;
> +    }
> +    if (dev_monitors_config->count >
> dev_monitors_config->max_allowed) {
> +        spice_warning("ignoring malformed monitors_config from
> driver, "
> +                      "count > max_allowed %d > %d",
> +                      dev_monitors_config->count,
> +                      dev_monitors_config->max_allowed);
> +        return;
> +    }
> +    worker_update_monitors_config(worker, dev_monitors_config);
> +    red_worker_push_monitors_config(worker);
> +}
> +
>  /* TODO: special, perhaps use another dispatcher? */
>  void handle_dev_cursor_channel_create(void *opaque, void *payload)
>  {
> @@ -11203,6 +11442,11 @@ static void register_callbacks(Dispatcher
> *dispatcher)
>                                  handle_dev_reset_memslots,
>                                  sizeof(RedWorkerMessageResetMemslots),
>                                  DISPATCHER_NONE);
> +    dispatcher_register_handler(dispatcher,
> +
>                                RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,
> +                                handle_dev_monitors_config_async,
> +
>                                sizeof(RedWorkerMessageMonitorsConfigAsync),
> +                                DISPATCHER_ASYNC);
>  }
>  
>  
> @@ -11241,6 +11485,7 @@ static void red_init(RedWorker *worker,
> WorkerInitData *init_data)
>      worker->jpeg_state = init_data->jpeg_state;
>      worker->zlib_glz_state = init_data->zlib_glz_state;
>      worker->streaming_video = init_data->streaming_video;
> +    worker->driver_has_monitors_config = 0;
>      ring_init(&worker->current_list);
>      image_cache_init(&worker->image_cache);
>      image_surface_init(worker);
> diff --git a/server/red_worker.h b/server/red_worker.h
> index fd23ede..6c5b839 100644
> --- a/server/red_worker.h
> +++ b/server/red_worker.h
> @@ -85,6 +85,8 @@ enum {
>      RED_WORKER_MESSAGE_DISPLAY_CHANNEL_CREATE,
>      RED_WORKER_MESSAGE_CURSOR_CHANNEL_CREATE,
>  
> +    RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,
> +
>      RED_WORKER_MESSAGE_COUNT // LAST
>  };
>  
> diff --git a/server/spice-server.syms b/server/spice-server.syms
> index 99a7271..a45951c 100644
> --- a/server/spice-server.syms
> +++ b/server/spice-server.syms
> @@ -112,3 +112,7 @@ global:
>  SPICE_SERVER_0.10.3 {
>      spice_server_is_server_mouse;
>  } SPICE_SERVER_0.10.2;
> +
> +SPICE_SERVER_0.10.4 {
> +    spice_qxl_monitors_config_async;
> +} SPICE_SERVER_0.10.3;
> diff --git a/server/spice.h b/server/spice.h
> index abaffbd..ec28f4a 100644
> --- a/server/spice.h
> +++ b/server/spice.h
> @@ -161,6 +161,9 @@ void
> spice_qxl_create_primary_surface_async(QXLInstance *instance,
> uint32_t surf
>  void spice_qxl_destroy_surface_async(QXLInstance *instance, uint32_t
>  surface_id, uint64_t cookie);
>  /* suspend and resolution change on windows drivers */
>  void spice_qxl_flush_surfaces_async(QXLInstance *instance, uint64_t
>  cookie);
> +/* since spice 0.12.0 */
> +void spice_qxl_monitors_config_async(QXLInstance *instance,
> QXLPHYSICAL monitors_config,
> +                                     int group_id, uint64_t cookie);
>  
>  typedef struct QXLDrawArea {
>      uint8_t *buf;
> --
> 1.7.10.1
> 
>