/* -*- 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() { 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; } struct RedKeyModifiersPipeItem: public RedPipeItemNum { explicit RedKeyModifiersPipeItem(uint8_t modifiers); uint8_t modifiers; }; struct RedInputsInitPipeItem: public RedPipeItemNum { explicit RedInputsInitPipeItem(uint8_t modifiers); uint8_t modifiers; }; #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; } // middle and right states are inverted // all buttons from SPICE_MOUSE_BUTTON_MASK_SIDE are mapped a bit higher // to avoid conflicting with some internal Qemu bit #define RED_MOUSE_STATE_TO_LOCAL(state) \ ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \ ((state & (SPICE_MOUSE_BUTTON_MASK_MIDDLE|0xffe0)) << 1) | \ ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1)) // mouse button constants are defined to be off-one between agent and SPICE protocol #define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) ((state) << 1) 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); } RedKeyModifiersPipeItem::RedKeyModifiersPipeItem(uint8_t init_modifiers): modifiers(init_modifiers) { } 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 = static_cast(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 = static_cast(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); } } RedInputsInitPipeItem::RedInputsInitPipeItem(uint8_t init_modifiers): modifiers(init_modifiers) { } void InputsChannelClient::pipe_add_init() { auto modifiers = kbd_get_leds(get_channel()->keyboard); pipe_add_push(red::make_shared(modifiers)); } 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() { if (!is_connected() || src_during_migrate) { return; } pipes_add(red::make_shared(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(); } return 0; } void InputsChannel::key_modifiers_sender(InputsChannel *inputs) { inputs->push_keyboard_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 *old_tablet) { if (old_tablet != NULL && old_tablet == tablet) { spice_tablet_state_free(old_tablet->st); old_tablet->st = NULL; } tablet = NULL; } bool InputsChannel::is_src_during_migrate() const { return src_during_migrate; }