/* -*- 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 .
*/
#include
#include // NULL
#include
#include
#include
#include
#include
#include
#include
#include "spice-wrapped.h"
#include "red-common.h"
#include "reds.h"
#include "red-stream.h"
#include "red-channel.h"
#include "red-channel-client.h"
#include "red-client.h"
#include "inputs-channel-client.h"
#include "main-channel-client.h"
#include "inputs-channel.h"
#include "migration-protocol.h"
#include "utils.h"
struct SpiceKbdState {
uint8_t push_ext_type;
/* track key press state */
bool key[0x80];
bool key_ext[0x80];
InputsChannel *inputs;
};
static SpiceKbdState* spice_kbd_state_new(InputsChannel *inputs)
{
SpiceKbdState *st = g_new0(SpiceKbdState, 1);
st->inputs = inputs;
return st;
}
struct SpiceMouseState {
int dummy;
};
static SpiceMouseState* spice_mouse_state_new(void)
{
return g_new0(SpiceMouseState, 1);
}
struct SpiceTabletState {
RedsState *reds;
};
static SpiceTabletState* spice_tablet_state_new(RedsState* reds)
{
SpiceTabletState *st = g_new0(SpiceTabletState, 1);
st->reds = reds;
return st;
}
static void spice_tablet_state_free(SpiceTabletState* st)
{
g_free(st);
}
RedsState* spice_tablet_state_get_server(SpiceTabletState *st)
{
return st->reds;
}
typedef struct RedKeyModifiersPipeItem {
RedPipeItem base;
uint8_t modifiers;
} RedKeyModifiersPipeItem;
typedef struct RedInputsInitPipeItem {
RedPipeItem base;
uint8_t modifiers;
} RedInputsInitPipeItem;
#define KEY_MODIFIERS_TTL (MSEC_PER_SEC * 2)
#define SCAN_CODE_RELEASE 0x80
#define SCROLL_LOCK_SCAN_CODE 0x46
#define NUM_LOCK_SCAN_CODE 0x45
#define CAPS_LOCK_SCAN_CODE 0x3a
void InputsChannel::set_tablet_logical_size(int x_res, int y_res)
{
SpiceTabletInterface *sif;
sif = SPICE_UPCAST(SpiceTabletInterface, tablet->base.sif);
sif->set_logical_size(tablet, x_res, y_res);
}
const VDAgentMouseState *InputsChannel::get_mouse_state()
{
return &mouse_state;
}
#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))
void InputsChannel::activate_modifiers_watch()
{
red_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_UPCAST(SpiceKbdInterface, sin->base.sif);
/* 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 & SCAN_CODE_RELEASE);
}
sin->st->push_ext_type = 0;
}
sif->push_scan_freg(sin, scan);
}
static uint8_t scancode_to_modifier_flag(uint8_t scancode)
{
switch (scancode & ~SCAN_CODE_RELEASE) {
case CAPS_LOCK_SCAN_CODE:
return SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
case NUM_LOCK_SCAN_CODE:
return SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK;
case SCROLL_LOCK_SCAN_CODE:
return SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK;
}
return 0;
}
void InputsChannel::sync_locks(uint8_t scan)
{
uint8_t change_modifier = scancode_to_modifier_flag(scan);
if (scan & SCAN_CODE_RELEASE) { /* KEY_UP */
modifiers_pressed &= ~change_modifier;
} else { /* KEY_DOWN */
if (change_modifier && !(modifiers_pressed & change_modifier)) {
modifiers ^= change_modifier;
modifiers_pressed |= change_modifier;
activate_modifiers_watch();
}
}
}
static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
{
SpiceKbdInterface *sif;
if (!sin) {
return 0;
}
sif = SPICE_UPCAST(SpiceKbdInterface, sin->base.sif);
return sif->get_leds(sin);
}
static RedPipeItem *red_inputs_key_modifiers_item_new(uint8_t modifiers)
{
RedKeyModifiersPipeItem *item = g_new(RedKeyModifiersPipeItem, 1);
red_pipe_item_init(&item->base, RED_PIPE_ITEM_KEY_MODIFIERS);
item->modifiers = modifiers;
return &item->base;
}
void InputsChannelClient::send_item(RedPipeItem *base)
{
SpiceMarshaller *m = get_marshaller();
switch (base->type) {
case RED_PIPE_ITEM_KEY_MODIFIERS:
{
SpiceMsgInputsKeyModifiers key_modifiers;
init_send_data(SPICE_MSG_INPUTS_KEY_MODIFIERS);
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;
init_send_data(SPICE_MSG_INPUTS_INIT);
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:
init_send_data(SPICE_MSG_INPUTS_MOUSE_MOTION_ACK);
break;
case RED_PIPE_ITEM_MIGRATE_DATA:
get_channel()->src_during_migrate = FALSE;
send_migrate_data(m, base);
break;
default:
spice_warning("invalid pipe iten %d", base->type);
break;
}
begin_send_message();
}
bool InputsChannelClient::handle_message(uint16_t type, uint32_t size, void *message)
{
InputsChannel *inputs_channel = get_channel();
uint32_t i;
RedsState *reds = inputs_channel->get_server();
switch (type) {
case SPICE_MSGC_INPUTS_KEY_DOWN: {
SpiceMsgcKeyDown *key_down = (SpiceMsgcKeyDown *) message;
inputs_channel->sync_locks(key_down->code);
}
/* fallthrough */
case SPICE_MSGC_INPUTS_KEY_UP: {
SpiceMsgcKeyUp *key_up = (SpiceMsgcKeyUp *) 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->keyboard, code);
inputs_channel->sync_locks(code);
}
break;
}
case SPICE_MSGC_INPUTS_KEY_SCANCODE: {
uint8_t *code = (uint8_t *) message;
for (i = 0; i < size; i++) {
kbd_push_scan(inputs_channel->keyboard, code[i]);
inputs_channel->sync_locks(code[i]);
}
break;
}
case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
SpiceMouseInstance *mouse = inputs_channel->mouse;
SpiceMsgcMouseMotion *mouse_motion = (SpiceMsgcMouseMotion *) message;
on_mouse_motion();
if (mouse && reds_get_mouse_mode(reds) == SPICE_MOUSE_MODE_SERVER) {
SpiceMouseInterface *sif;
sif = SPICE_UPCAST(SpiceMouseInterface, mouse->base.sif);
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 = (SpiceMsgcMousePosition *) message;
SpiceTabletInstance *tablet = inputs_channel->tablet;
on_mouse_motion();
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_UPCAST(SpiceTabletInterface, tablet->base.sif);
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 = (SpiceMsgcMousePress *) 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->tablet) {
SpiceTabletInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel->tablet->base.sif,
SpiceTabletInterface, base);
sif->wheel(inputs_channel->tablet, dz,
RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
}
} else if (inputs_channel->mouse) {
SpiceMouseInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel->mouse->base.sif,
SpiceMouseInterface, base);
sif->motion(inputs_channel->mouse, 0, 0, dz,
RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
}
break;
}
case SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
SpiceMsgcMouseRelease *mouse_release = (SpiceMsgcMouseRelease *) 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->tablet) {
SpiceTabletInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel->tablet->base.sif,
SpiceTabletInterface, base);
sif->buttons(inputs_channel->tablet,
RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
}
} else if (inputs_channel->mouse) {
SpiceMouseInterface *sif;
sif = SPICE_CONTAINEROF(inputs_channel->mouse->base.sif,
SpiceMouseInterface, base);
sif->buttons(inputs_channel->mouse,
RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
}
break;
}
case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
SpiceMsgcKeyModifiers *modifiers = (SpiceMsgcKeyModifiers *) message;
uint8_t leds;
SpiceKbdInstance *keyboard = inputs_channel->keyboard;
if (!keyboard) {
break;
}
leds = inputs_channel->modifiers;
if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) &&
((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 | SCAN_CODE_RELEASE);
inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK;
}
if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) &&
((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 | SCAN_CODE_RELEASE);
inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK;
}
if (!(inputs_channel->modifiers_pressed & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) &&
((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 | SCAN_CODE_RELEASE);
inputs_channel->modifiers ^= SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
}
inputs_channel->activate_modifiers_watch();
break;
}
default:
return RedChannelClient::handle_message(type, size, message);
}
return TRUE;
}
void InputsChannel::release_keys()
{
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 | SCAN_CODE_RELEASE);
}
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 | SCAN_CODE_RELEASE);
}
}
void InputsChannelClient::pipe_add_init()
{
RedInputsInitPipeItem *item = g_new(RedInputsInitPipeItem, 1);
InputsChannel *inputs = get_channel();
red_pipe_item_init(&item->base, RED_PIPE_ITEM_INPUTS_INIT);
item->modifiers = kbd_get_leds(inputs->keyboard);
pipe_add_push(&item->base);
}
void InputsChannel::on_connect(RedClient *client, RedStream *stream, int migration,
RedChannelCapabilities *caps)
{
if (!red_stream_is_ssl(stream) && !client->during_migrate_at_target()) {
client->get_main()->push_notify("keyboard channel is insecure");
}
inputs_channel_client_create(this, client, stream, caps);
}
void InputsChannelClient::migrate()
{
InputsChannel *inputs = get_channel();
inputs->src_during_migrate = true;
RedChannelClient::migrate();
}
void InputsChannel::push_keyboard_modifiers(uint8_t modifiers)
{
if (!is_connected() || src_during_migrate) {
return;
}
pipes_add(red_inputs_key_modifiers_item_new(modifiers));
}
SPICE_GNUC_VISIBLE int spice_server_kbd_leds(SpiceKbdInstance *sin, int leds)
{
InputsChannel *inputs_channel = sin->st->inputs;
if (inputs_channel) {
inputs_channel->modifiers = leds;
inputs_channel->push_keyboard_modifiers(leds);
}
return 0;
}
void InputsChannel::key_modifiers_sender(InputsChannel *inputs)
{
inputs->push_keyboard_modifiers(inputs->modifiers);
}
void InputsChannelClient::handle_migrate_flush_mark()
{
pipe_add_type(RED_PIPE_ITEM_MIGRATE_DATA);
}
bool InputsChannelClient::handle_migrate_data(uint32_t size, void *message)
{
InputsChannel *inputs = get_channel();
SpiceMigrateDataHeader *header;
SpiceMigrateDataInputs *mig_data;
if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataInputs)) {
spice_warning("bad message size %u", size);
return FALSE;
}
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;
}
InputsChannel::key_modifiers_sender(inputs);
handle_migrate_data(mig_data->motion_count);
return TRUE;
}
red::shared_ptr inputs_channel_new(RedsState *reds)
{
return red::make_shared(reds);
}
InputsChannel::InputsChannel(RedsState *reds):
RedChannel(reds, SPICE_CHANNEL_INPUTS, 0, RedChannel::MigrateAll)
{
SpiceCoreInterfaceInternal *core = get_core_interface();
set_cap(SPICE_INPUTS_CAP_KEY_SCANCODE);
reds_register_channel(reds, this);
key_modifiers_timer = core->timer_new(key_modifiers_sender, this);
if (!key_modifiers_timer) {
spice_error("key modifiers timer create failed");
}
}
InputsChannel::~InputsChannel()
{
detach_tablet(tablet);
red_timer_remove(key_modifiers_timer);
}
int InputsChannel::set_keyboard(SpiceKbdInstance *new_keyboard)
{
if (keyboard) {
red_channel_warning(this, "already have keyboard");
return -1;
}
keyboard = new_keyboard;
keyboard->st = spice_kbd_state_new(this);
return 0;
}
int InputsChannel::set_mouse(SpiceMouseInstance *new_mouse)
{
if (mouse) {
red_channel_warning(this, "already have mouse");
return -1;
}
mouse = new_mouse;
mouse->st = spice_mouse_state_new();
return 0;
}
int InputsChannel::set_tablet(SpiceTabletInstance *new_tablet)
{
if (tablet) {
red_channel_warning(this, "already have tablet");
return -1;
}
tablet = new_tablet;
tablet->st = spice_tablet_state_new(get_server());
return 0;
}
bool InputsChannel::has_tablet() const
{
return tablet != NULL;
}
void InputsChannel::detach_tablet(SpiceTabletInstance *tablet)
{
if (tablet != NULL && tablet == this->tablet) {
spice_tablet_state_free(tablet->st);
tablet->st = NULL;
}
this->tablet = NULL;
}
bool InputsChannel::is_src_during_migrate() const
{
return src_during_migrate;
}