/* -*- 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include "common/generated_server_marshallers.h"
#include "cursor-channel.h"
#include "cache-item.h"
#define CLIENT_CURSOR_CACHE_SIZE 256
#define CURSOR_CACHE_HASH_SHIFT 8
#define CURSOR_CACHE_HASH_SIZE (1 << CURSOR_CACHE_HASH_SHIFT)
#define CURSOR_CACHE_HASH_MASK (CURSOR_CACHE_HASH_SIZE - 1)
#define CURSOR_CACHE_HASH_KEY(id) ((id) & CURSOR_CACHE_HASH_MASK)
enum {
PIPE_ITEM_TYPE_CURSOR = PIPE_ITEM_TYPE_COMMON_LAST,
PIPE_ITEM_TYPE_CURSOR_INIT,
PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE,
};
typedef struct CursorItem {
QXLInstance *qxl;
int refs;
RedCursorCmd *red_cursor;
} CursorItem;
G_STATIC_ASSERT(sizeof(CursorItem) <= QXL_CURSUR_DEVICE_DATA_SIZE);
typedef struct CursorPipeItem {
PipeItem base;
CursorItem *cursor_item;
int refs;
} CursorPipeItem;
struct CursorChannel {
CommonGraphicsChannel common; // Must be the first thing
CursorItem *item;
int cursor_visible;
SpicePoint16 cursor_position;
uint16_t cursor_trail_length;
uint16_t cursor_trail_frequency;
uint32_t mouse_mode;
#ifdef RED_STATISTICS
StatNodeRef stat;
#endif
};
struct CursorChannelClient {
CommonGraphicsChannelClient common;
CacheItem *cursor_cache[CURSOR_CACHE_HASH_SIZE];
Ring cursor_cache_lru;
long cursor_cache_available;
uint32_t cursor_cache_items;
};
#define RCC_TO_CCC(rcc) SPICE_CONTAINEROF((rcc), CursorChannelClient, common.base)
#define CLIENT_CURSOR_CACHE
#include "cache-item.tmpl.c"
#undef CLIENT_CURSOR_CACHE
static CursorItem *cursor_item_new(QXLInstance *qxl, RedCursorCmd *cmd)
{
CursorItem *cursor_item;
spice_return_val_if_fail(cmd != NULL, NULL);
cursor_item = g_new0(CursorItem, 1);
cursor_item->qxl = qxl;
cursor_item->refs = 1;
cursor_item->red_cursor = cmd;
return cursor_item;
}
static CursorItem *cursor_item_ref(CursorItem *item)
{
spice_return_val_if_fail(item != NULL, NULL);
spice_return_val_if_fail(item->refs > 0, NULL);
item->refs++;
return item;
}
static void cursor_item_unref(CursorItem *item)
{
RedCursorCmd *cursor_cmd;
spice_return_if_fail(item != NULL);
if (--item->refs)
return;
cursor_cmd = item->red_cursor;
red_qxl_release_resource(item->qxl, cursor_cmd->release_info_ext);
red_put_cursor_cmd(cursor_cmd);
free(cursor_cmd);
g_free(item);
}
static void cursor_set_item(CursorChannel *cursor, CursorItem *item)
{
if (cursor->item)
cursor_item_unref(cursor->item);
cursor->item = item ? cursor_item_ref(item) : NULL;
}
static PipeItem *new_cursor_pipe_item(RedChannelClient *rcc, void *data, int num)
{
CursorPipeItem *item = spice_malloc0(sizeof(CursorPipeItem));
pipe_item_init(&item->base, PIPE_ITEM_TYPE_CURSOR);
item->refs = 1;
item->cursor_item = data;
item->cursor_item->refs++;
return &item->base;
}
typedef struct {
void *data;
uint32_t size;
} AddBufInfo;
static void add_buf_from_info(SpiceMarshaller *m, AddBufInfo *info)
{
if (info->data) {
spice_marshaller_add_ref(m, info->data, info->size);
}
}
static void cursor_fill(CursorChannelClient *ccc, SpiceCursor *red_cursor,
CursorItem *cursor, AddBufInfo *addbuf)
{
RedCursorCmd *cursor_cmd;
addbuf->data = NULL;
if (!cursor) {
red_cursor->flags = SPICE_CURSOR_FLAGS_NONE;
return;
}
cursor_cmd = cursor->red_cursor;
*red_cursor = cursor_cmd->u.set.shape;
if (red_cursor->header.unique) {
if (red_cursor_cache_find(ccc, red_cursor->header.unique)) {
red_cursor->flags |= SPICE_CURSOR_FLAGS_FROM_CACHE;
return;
}
if (red_cursor_cache_add(ccc, red_cursor->header.unique, 1)) {
red_cursor->flags |= SPICE_CURSOR_FLAGS_CACHE_ME;
}
}
if (red_cursor->data_size) {
addbuf->data = red_cursor->data;
addbuf->size = red_cursor->data_size;
}
}
static void red_reset_cursor_cache(RedChannelClient *rcc)
{
red_cursor_cache_reset(RCC_TO_CCC(rcc), CLIENT_CURSOR_CACHE_SIZE);
}
void cursor_channel_disconnect(CursorChannel *cursor_channel)
{
RedChannel *channel = (RedChannel *)cursor_channel;
if (!channel || !red_channel_is_connected(channel)) {
return;
}
red_channel_apply_clients(channel, red_reset_cursor_cache);
red_channel_disconnect(channel);
}
static void put_cursor_pipe_item(CursorChannelClient *ccc, CursorPipeItem *pipe_item)
{
spice_return_if_fail(pipe_item);
spice_return_if_fail(pipe_item->refs > 0);
if (--pipe_item->refs) {
return;
}
spice_assert(!pipe_item_is_linked(&pipe_item->base));
cursor_item_unref(pipe_item->cursor_item);
free(pipe_item);
}
static void cursor_channel_client_on_disconnect(RedChannelClient *rcc)
{
if (!rcc) {
return;
}
red_reset_cursor_cache(rcc);
}
// TODO: share code between before/after_push since most of the items need the same
// release
static void cursor_channel_client_release_item_before_push(CursorChannelClient *ccc,
PipeItem *item)
{
switch (item->type) {
case PIPE_ITEM_TYPE_CURSOR: {
CursorPipeItem *cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
put_cursor_pipe_item(ccc, cursor_pipe_item);
break;
}
case PIPE_ITEM_TYPE_INVAL_ONE:
case PIPE_ITEM_TYPE_VERB:
case PIPE_ITEM_TYPE_CURSOR_INIT:
case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
free(item);
break;
default:
spice_error("invalid pipe item type");
}
}
static void cursor_channel_client_release_item_after_push(CursorChannelClient *ccc,
PipeItem *item)
{
switch (item->type) {
case PIPE_ITEM_TYPE_CURSOR: {
CursorPipeItem *cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
put_cursor_pipe_item(ccc, cursor_pipe_item);
break;
}
default:
spice_critical("invalid item type");
}
}
static void red_marshall_cursor_init(RedChannelClient *rcc, SpiceMarshaller *base_marshaller,
PipeItem *pipe_item)
{
CursorChannel *cursor_channel;
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
SpiceMsgCursorInit msg;
AddBufInfo info;
spice_assert(rcc);
cursor_channel = SPICE_CONTAINEROF(rcc->channel, CursorChannel, common.base);
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_INIT, NULL);
msg.visible = cursor_channel->cursor_visible;
msg.position = cursor_channel->cursor_position;
msg.trail_length = cursor_channel->cursor_trail_length;
msg.trail_frequency = cursor_channel->cursor_trail_frequency;
cursor_fill(ccc, &msg.cursor, cursor_channel->item, &info);
spice_marshall_msg_cursor_init(base_marshaller, &msg);
add_buf_from_info(base_marshaller, &info);
}
static void cursor_marshall(RedChannelClient *rcc,
SpiceMarshaller *m, CursorPipeItem *cursor_pipe_item)
{
CursorChannel *cursor_channel = SPICE_CONTAINEROF(rcc->channel, CursorChannel, common.base);
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
CursorItem *item = cursor_pipe_item->cursor_item;
PipeItem *pipe_item = &cursor_pipe_item->base;
RedCursorCmd *cmd;
spice_return_if_fail(cursor_channel);
cmd = item->red_cursor;
switch (cmd->type) {
case QXL_CURSOR_MOVE:
{
SpiceMsgCursorMove cursor_move;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_MOVE, pipe_item);
cursor_move.position = cmd->u.position;
spice_marshall_msg_cursor_move(m, &cursor_move);
break;
}
case QXL_CURSOR_SET:
{
SpiceMsgCursorSet cursor_set;
AddBufInfo info;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_SET, pipe_item);
cursor_set.position = cmd->u.set.position;
cursor_set.visible = cursor_channel->cursor_visible;
cursor_fill(ccc, &cursor_set.cursor, item, &info);
spice_marshall_msg_cursor_set(m, &cursor_set);
add_buf_from_info(m, &info);
break;
}
case QXL_CURSOR_HIDE:
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_HIDE, pipe_item);
break;
case QXL_CURSOR_TRAIL:
{
SpiceMsgCursorTrail cursor_trail;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_TRAIL, pipe_item);
cursor_trail.length = cmd->u.trail.length;
cursor_trail.frequency = cmd->u.trail.frequency;
spice_marshall_msg_cursor_trail(m, &cursor_trail);
}
break;
default:
spice_error("bad cursor command %d", cmd->type);
}
}
static inline void red_marshall_inval(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, CacheItem *cach_item)
{
SpiceMsgDisplayInvalOne inval_one;
red_channel_client_init_send_data(rcc, cach_item->inval_type, NULL);
inval_one.id = *(uint64_t *)&cach_item->id;
spice_marshall_msg_cursor_inval_one(base_marshaller, &inval_one);
}
static void cursor_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item)
{
SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
switch (pipe_item->type) {
case PIPE_ITEM_TYPE_CURSOR:
cursor_marshall(rcc, m, SPICE_CONTAINEROF(pipe_item, CursorPipeItem, base));
break;
case PIPE_ITEM_TYPE_INVAL_ONE:
red_marshall_inval(rcc, m, (CacheItem *)pipe_item);
break;
case PIPE_ITEM_TYPE_VERB:
red_marshall_verb(rcc, (VerbItem*)pipe_item);
break;
case PIPE_ITEM_TYPE_CURSOR_INIT:
red_reset_cursor_cache(rcc);
red_marshall_cursor_init(rcc, m, pipe_item);
break;
case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
red_reset_cursor_cache(rcc);
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_INVAL_ALL, NULL);
break;
default:
spice_error("invalid pipe item type");
}
cursor_channel_client_release_item_before_push(ccc, pipe_item);
red_channel_client_begin_send_message(rcc);
}
static CursorPipeItem *cursor_pipe_item_ref(CursorPipeItem *item)
{
spice_return_val_if_fail(item, NULL);
spice_return_val_if_fail(item->refs > 0, NULL);
item->refs++;
return item;
}
static void cursor_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
{
CursorPipeItem *cursor_pipe_item;
spice_return_if_fail(item);
cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
cursor_pipe_item_ref(cursor_pipe_item);
}
static void cursor_channel_release_item(RedChannelClient *rcc, PipeItem *item, int item_pushed)
{
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
spice_assert(item);
if (item_pushed) {
cursor_channel_client_release_item_after_push(ccc, item);
} else {
spice_debug("not pushed (%d)", item->type);
cursor_channel_client_release_item_before_push(ccc, item);
}
}
CursorChannel* cursor_channel_new(RedWorker *worker)
{
CursorChannel *cursor_channel;
CommonGraphicsChannel *channel = NULL;
ChannelCbs cbs = {
.on_disconnect = cursor_channel_client_on_disconnect,
.send_item = cursor_channel_send_item,
.hold_item = cursor_channel_hold_pipe_item,
.release_item = cursor_channel_release_item
};
spice_info("create cursor channel");
channel = red_worker_new_channel(worker, sizeof(CursorChannel), "cursor_channel",
SPICE_CHANNEL_CURSOR, 0,
&cbs, red_channel_client_handle_message);
cursor_channel = (CursorChannel *)channel;
cursor_channel->cursor_visible = TRUE;
cursor_channel->mouse_mode = SPICE_MOUSE_MODE_SERVER;
return cursor_channel;
}
void cursor_channel_client_migrate(CursorChannelClient* client)
{
RedChannelClient *rcc;
spice_return_if_fail(client);
rcc = RED_CHANNEL_CLIENT(client);
red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
red_channel_client_default_migrate(rcc);
}
CursorChannelClient* cursor_channel_client_new(CursorChannel *cursor, RedClient *client, RedsStream *stream,
int mig_target,
uint32_t *common_caps, int num_common_caps,
uint32_t *caps, int num_caps)
{
spice_return_val_if_fail(cursor, NULL);
spice_return_val_if_fail(client, NULL);
spice_return_val_if_fail(stream, NULL);
spice_return_val_if_fail(!num_common_caps || common_caps, NULL);
spice_return_val_if_fail(!num_caps || caps, NULL);
CursorChannelClient *ccc =
(CursorChannelClient*)common_graphics_channel_new_client(&cursor->common,
sizeof(CursorChannelClient),
client, stream,
mig_target,
FALSE,
common_caps,
num_common_caps,
caps,
num_caps);
spice_return_val_if_fail(ccc != NULL, NULL);
ring_init(&ccc->cursor_cache_lru);
ccc->cursor_cache_available = CLIENT_CURSOR_CACHE_SIZE;
return ccc;
}
void cursor_channel_process_cmd(CursorChannel *cursor, RedCursorCmd *cursor_cmd)
{
CursorItem *cursor_item;
int cursor_show = FALSE;
spice_return_if_fail(cursor);
spice_return_if_fail(cursor_cmd);
cursor_item = cursor_item_new(cursor->common.qxl, cursor_cmd);
switch (cursor_cmd->type) {
case QXL_CURSOR_SET:
cursor->cursor_visible = cursor_cmd->u.set.visible;
cursor_set_item(cursor, cursor_item);
break;
case QXL_CURSOR_MOVE:
cursor_show = !cursor->cursor_visible;
cursor->cursor_visible = TRUE;
cursor->cursor_position = cursor_cmd->u.position;
break;
case QXL_CURSOR_HIDE:
cursor->cursor_visible = FALSE;
break;
case QXL_CURSOR_TRAIL:
cursor->cursor_trail_length = cursor_cmd->u.trail.length;
cursor->cursor_trail_frequency = cursor_cmd->u.trail.frequency;
break;
default:
spice_warning("invalid cursor command %u", cursor_cmd->type);
return;
}
if (red_channel_is_connected(&cursor->common.base) &&
(cursor->mouse_mode == SPICE_MOUSE_MODE_SERVER
|| cursor_cmd->type != QXL_CURSOR_MOVE
|| cursor_show)) {
red_channel_pipes_new_add(&cursor->common.base,
new_cursor_pipe_item, cursor_item);
}
cursor_item_unref(cursor_item);
}
void cursor_channel_reset(CursorChannel *cursor)
{
RedChannel *channel = &cursor->common.base;
spice_return_if_fail(cursor);
cursor_set_item(cursor, NULL);
cursor->cursor_visible = TRUE;
cursor->cursor_position.x = cursor->cursor_position.y = 0;
cursor->cursor_trail_length = cursor->cursor_trail_frequency = 0;
if (red_channel_is_connected(channel)) {
red_channel_pipes_add_type(channel, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
if (!cursor->common.during_target_migrate) {
red_pipes_add_verb(channel, SPICE_MSG_CURSOR_RESET);
}
if (!red_channel_wait_all_sent(&cursor->common.base,
COMMON_CLIENT_TIMEOUT)) {
red_channel_apply_clients(channel,
red_channel_client_disconnect_if_pending_send);
}
}
}
void cursor_channel_init(CursorChannel *cursor, CursorChannelClient *client)
{
spice_return_if_fail(cursor);
if (!red_channel_is_connected(&cursor->common.base)
|| COMMON_GRAPHICS_CHANNEL(cursor)->during_target_migrate) {
spice_debug("during_target_migrate: skip init");
return;
}
if (client)
red_channel_client_pipe_add_type(RED_CHANNEL_CLIENT(client),
PIPE_ITEM_TYPE_CURSOR_INIT);
else
red_channel_pipes_add_type(RED_CHANNEL(cursor), PIPE_ITEM_TYPE_CURSOR_INIT);
}
void cursor_channel_set_mouse_mode(CursorChannel *cursor, uint32_t mode)
{
spice_return_if_fail(cursor);
cursor->mouse_mode = mode;
}