/* -*- 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 // IPPROTO_TCP
#include // TCP_NODELAY
#include
#include // NULL
#include
#include
#include
#include
#include
#include
#include
#include
#include "demarshallers.h"
#include "spice.h"
#include "red-common.h"
#include "reds.h"
#include "reds-stream.h"
#include "red-channel.h"
#include "inputs-channel-client.h"
#include "main-channel-client.h"
#include "inputs-channel.h"
#include "migration-protocol.h"
#include "utils.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 {
uint8_t push_ext_type;
/* track key press state */
bool key[0x80];
bool key_ext[0x80];
RedsState *reds;
};
static SpiceKbdInstance* inputs_channel_get_keyboard(InputsChannel *inputs);
static SpiceMouseInstance* inputs_channel_get_mouse(InputsChannel *inputs);
static SpiceTabletInstance* inputs_channel_get_tablet(InputsChannel *inputs);
static SpiceKbdState* spice_kbd_state_new(RedsState *reds)
{
SpiceKbdState *st = spice_new0(SpiceKbdState, 1);
st->reds = reds;
return st;
}
RedsState* spice_kbd_state_get_server(SpiceKbdState *dev)
{
return dev->reds;
}
struct SpiceMouseState {
int dummy;
};
static SpiceMouseState* spice_mouse_state_new(void)
{
return spice_new0(SpiceMouseState, 1);
}
struct SpiceTabletState {
RedsState *reds;
};
static SpiceTabletState* spice_tablet_state_new(void)
{
return spice_new0(SpiceTabletState, 1);
}
RedsState* spice_tablet_state_get_server(SpiceTabletState *st)
{
return st->reds;
}
struct InputsChannel {
RedChannel base;
uint8_t recv_buf[RECEIVE_BUF_SIZE];
VDAgentMouseState mouse_state;
int src_during_migrate;
SpiceKbdInstance *keyboard;
SpiceMouseInstance *mouse;
SpiceTabletInstance *tablet;
};
typedef struct RedInputsPipeItem {
RedPipeItem base;
} RedInputsPipeItem;
typedef struct RedKeyModifiersPipeItem {
RedPipeItem base;
uint8_t modifiers;
} RedKeyModifiersPipeItem;
typedef struct RedInputsInitPipeItem {
RedPipeItem base;
uint8_t modifiers;
} RedInputsInitPipeItem;
static SpiceTimer *key_modifiers_timer;
#define KEY_MODIFIERS_TTL (MSEC_PER_SEC * 2)
#define SCROLL_LOCK_SCAN_CODE 0x46
#define NUM_LOCK_SCAN_CODE 0x45
#define CAPS_LOCK_SCAN_CODE 0x3a
void inputs_channel_set_tablet_logical_size(InputsChannel *inputs, int x_res, int y_res)
{
SpiceTabletInterface *sif;
sif = SPICE_UPCAST(SpiceTabletInterface, inputs->tablet->base.sif);
sif->set_logical_size(inputs->tablet, x_res, y_res);
}
const VDAgentMouseState *inputs_channel_get_mouse_state(InputsChannel *inputs)
{
return &inputs->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(RedsState *reds)
{
reds_core_timer_start(reds, 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 && scan <= 0xe2) {
sin->st->push_ext_type = scan;
} else {
if (sin->st->push_ext_type == 0 || sin->st->push_ext_type == 0xe0) {
bool *state = sin->st->push_ext_type ? sin->st->key_ext : sin->st->key;
state[scan & 0x7f] = !(scan & 0x80);
}
sin->st->push_ext_type = 0;
}
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 RedPipeItem *red_inputs_key_modifiers_item_new(
RedChannelClient *rcc, void *data, int num)
{
RedKeyModifiersPipeItem *item = spice_malloc(sizeof(RedKeyModifiersPipeItem));
red_pipe_item_init(&item->base, RED_PIPE_ITEM_KEY_MODIFIERS);
item->modifiers = *(uint8_t *)data;
return &item->base;
}
static void inputs_channel_send_item(RedChannelClient *rcc, RedPipeItem *base)
{
SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
switch (base->type) {
case RED_PIPE_ITEM_KEY_MODIFIERS:
{
SpiceMsgInputsKeyModifiers key_modifiers;
red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_KEY_MODIFIERS, base);
key_modifiers.modifiers =
SPICE_UPCAST(RedKeyModifiersPipeItem, base)->modifiers;
spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers);
break;
}
case RED_PIPE_ITEM_INPUTS_INIT:
{
SpiceMsgInputsInit inputs_init;
red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_INIT, base);
inputs_init.keyboard_modifiers =
SPICE_UPCAST(RedInputsInitPipeItem, base)->modifiers;
spice_marshall_msg_inputs_init(m, &inputs_init);
break;
}
case RED_PIPE_ITEM_MOUSE_MOTION_ACK:
red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK, base);
break;
case RED_PIPE_ITEM_MIGRATE_DATA:
((InputsChannel*)rcc->channel)->src_during_migrate = FALSE;
inputs_channel_client_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;
RedsState *reds = red_channel_get_server(&inputs_channel->base);
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(reds);
}
}
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(inputs_channel_get_keyboard(inputs_channel), code);
}
break;
}
case SPICE_MSGC_INPUTS_KEY_SCANCODE: {
uint8_t *code = message;
for (i = 0; i < size; i++) {
kbd_push_scan(inputs_channel_get_keyboard(inputs_channel), code[i]);
}
break;
}
case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
SpiceMouseInstance *mouse = inputs_channel_get_mouse(inputs_channel);
SpiceMsgcMouseMotion *mouse_motion = message;
inputs_channel_client_on_mouse_motion(icc);
if (mouse && reds_get_mouse_mode(reds) == 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;
SpiceTabletInstance *tablet = inputs_channel_get_tablet(inputs_channel);
inputs_channel_client_on_mouse_motion(icc);
if (reds_get_mouse_mode(reds) != SPICE_MOUSE_MODE_CLIENT) {
break;
}
spice_assert((reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) || tablet);
if (!reds_config_get_agent_mouse(reds) || !reds_has_vdagent(reds)) {
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(reds, 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(reds) == SPICE_MOUSE_MODE_CLIENT) {
if (reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) {
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(reds, &inputs_channel->mouse_state);
} else if (inputs_channel_get_tablet(inputs_channel)) {
SpiceTabletInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel_get_tablet(inputs_channel)->base.sif, SpiceTabletInterface, base);
sif->wheel(inputs_channel_get_tablet(inputs_channel), dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
}
} else if (inputs_channel_get_mouse(inputs_channel)) {
SpiceMouseInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel_get_mouse(inputs_channel)->base.sif, SpiceMouseInterface, base);
sif->motion(inputs_channel_get_mouse(inputs_channel), 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(reds) == SPICE_MOUSE_MODE_CLIENT) {
if (reds_config_get_agent_mouse(reds) && reds_has_vdagent(reds)) {
inputs_channel->mouse_state.buttons =
RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
reds_handle_agent_mouse_event(reds, &inputs_channel->mouse_state);
} else if (inputs_channel_get_tablet(inputs_channel)) {
SpiceTabletInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel_get_tablet(inputs_channel)->base.sif, SpiceTabletInterface, base);
sif->buttons(inputs_channel_get_tablet(inputs_channel), RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
}
} else if (inputs_channel_get_mouse(inputs_channel)) {
SpiceMouseInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel_get_mouse(inputs_channel)->base.sif, SpiceMouseInterface, base);
sif->buttons(inputs_channel_get_mouse(inputs_channel),
RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
}
break;
}
case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
SpiceMsgcKeyModifiers *modifiers = message;
uint8_t leds;
SpiceKbdInstance *keyboard = inputs_channel_get_keyboard(inputs_channel);
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(reds);
break;
}
case SPICE_MSGC_DISCONNECTING:
break;
default:
return red_channel_client_handle_message(rcc, size, type, message);
}
return TRUE;
}
static void inputs_release_keys(InputsChannel *inputs)
{
int i;
SpiceKbdState *st;
SpiceKbdInstance *keyboard = inputs_channel_get_keyboard(inputs);
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(SPICE_CONTAINEROF(rcc->channel, InputsChannel, base));
}
static void inputs_pipe_add_init(RedChannelClient *rcc)
{
RedInputsInitPipeItem *item = spice_malloc(sizeof(RedInputsInitPipeItem));
InputsChannel *inputs = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base);
red_pipe_item_init(&item->base, RED_PIPE_ITEM_INPUTS_INIT);
item->modifiers = kbd_get_leds(inputs_channel_get_keyboard(inputs));
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_connect(RedChannel *channel, RedClient *client,
RedsStream *stream, int migration,
int num_common_caps, uint32_t *common_caps,
int num_caps, uint32_t *caps)
{
RedChannelClient *rcc;
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");
rcc = inputs_channel_client_create(channel, client, stream, FALSE,
num_common_caps, common_caps,
num_caps, caps);
if (!rcc) {
return;
}
inputs_pipe_add_init(rcc);
}
static void inputs_migrate(RedChannelClient *rcc)
{
InputsChannel *inputs = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base);
inputs->src_during_migrate = TRUE;
red_channel_client_default_migrate(rcc);
}
static void inputs_channel_push_keyboard_modifiers(InputsChannel *inputs, uint8_t modifiers)
{
if (!inputs || !red_channel_is_connected(&inputs->base) ||
inputs->src_during_migrate) {
return;
}
red_channel_pipes_new_add_push(&inputs->base,
red_inputs_key_modifiers_item_new, (void*)&modifiers);
}
void inputs_channel_on_keyboard_leds_change(InputsChannel *inputs, uint8_t leds)
{
inputs_channel_push_keyboard_modifiers(inputs, leds);
}
static void key_modifiers_sender(void *opaque)
{
InputsChannel *inputs = opaque;
inputs_channel_push_keyboard_modifiers(inputs, kbd_get_leds(inputs_channel_get_keyboard(inputs)));
}
static int inputs_channel_handle_migrate_flush_mark(RedChannelClient *rcc)
{
red_channel_client_pipe_add_type(rcc, RED_PIPE_ITEM_MIGRATE_DATA);
return TRUE;
}
static int inputs_channel_handle_migrate_data(RedChannelClient *rcc,
uint32_t size,
void *message)
{
InputsChannelClient *icc = (InputsChannelClient*)rcc;
InputsChannel *inputs = SPICE_CONTAINEROF(rcc->channel, InputsChannel, 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(inputs);
inputs_channel_client_handle_migrate_data(icc, mig_data->motion_count);
return TRUE;
}
InputsChannel* inputs_channel_new(RedsState *reds)
{
ChannelCbs channel_cbs = { NULL, };
ClientCbs client_cbs = { NULL, };
InputsChannel *inputs;
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.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;
inputs = (InputsChannel *)red_channel_create_parser(
sizeof(InputsChannel),
reds,
reds_get_core_interface(reds),
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 (!inputs) {
spice_error("failed to allocate Inputs Channel");
}
client_cbs.connect = inputs_connect;
client_cbs.migrate = inputs_migrate;
red_channel_register_client_cbs(&inputs->base, &client_cbs, NULL);
red_channel_set_cap(&inputs->base, SPICE_INPUTS_CAP_KEY_SCANCODE);
reds_register_channel(reds, &inputs->base);
if (!(key_modifiers_timer = reds_core_timer_add(reds, key_modifiers_sender, inputs))) {
spice_error("key modifiers timer create failed");
}
return inputs;
}
static SpiceKbdInstance* inputs_channel_get_keyboard(InputsChannel *inputs)
{
return inputs->keyboard;
}
int inputs_channel_set_keyboard(InputsChannel *inputs, SpiceKbdInstance *keyboard)
{
if (inputs->keyboard) {
spice_printerr("already have keyboard");
return -1;
}
inputs->keyboard = keyboard;
inputs->keyboard->st = spice_kbd_state_new(red_channel_get_server(&inputs->base));
return 0;
}
static SpiceMouseInstance* inputs_channel_get_mouse(InputsChannel *inputs)
{
return inputs->mouse;
}
int inputs_channel_set_mouse(InputsChannel *inputs, SpiceMouseInstance *mouse)
{
if (inputs->mouse) {
spice_printerr("already have mouse");
return -1;
}
inputs->mouse = mouse;
inputs->mouse->st = spice_mouse_state_new();
return 0;
}
static SpiceTabletInstance* inputs_channel_get_tablet(InputsChannel *inputs)
{
return inputs->tablet;
}
int inputs_channel_set_tablet(InputsChannel *inputs, SpiceTabletInstance *tablet, RedsState *reds)
{
if (inputs->tablet) {
spice_printerr("already have tablet");
return -1;
}
inputs->tablet = tablet;
inputs->tablet->st = spice_tablet_state_new();
inputs->tablet->st->reds = reds;
return 0;
}
int inputs_channel_has_tablet(InputsChannel *inputs)
{
return inputs != NULL && inputs->tablet != NULL;
}
void inputs_channel_detach_tablet(InputsChannel *inputs, SpiceTabletInstance *tablet)
{
spice_printerr("");
inputs->tablet = NULL;
}
gboolean inputs_channel_is_src_during_migrate(InputsChannel *inputs)
{
return inputs->src_during_migrate;
}