summaryrefslogtreecommitdiffstats
path: root/server/inputs_channel.c
diff options
context:
space:
mode:
authorAlon Levy <alevy@redhat.com>2010-10-31 16:31:14 +0200
committerAlon Levy <alevy@redhat.com>2010-12-07 21:32:33 +0200
commit347e32177cd7a563054ac903e1efb193f6ec7b57 (patch)
tree325974ac49ac33cdf9e127a852cdfe78da31e1e3 /server/inputs_channel.c
parent26c40c4196a6c1b12dc59a794d64fb217b5a7259 (diff)
downloadspice-347e32177cd7a563054ac903e1efb193f6ec7b57.tar.gz
spice-347e32177cd7a563054ac903e1efb193f6ec7b57.tar.xz
spice-347e32177cd7a563054ac903e1efb193f6ec7b57.zip
server: introduce inputs_channel, split from reds.c
Diffstat (limited to 'server/inputs_channel.c')
-rw-r--r--server/inputs_channel.c694
1 files changed, 694 insertions, 0 deletions
diff --git a/server/inputs_channel.c b/server/inputs_channel.c
new file mode 100644
index 00000000..288724e6
--- /dev/null
+++ b/server/inputs_channel.c
@@ -0,0 +1,694 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <netinet/in.h> // IPPROTO_TCP
+#include <netinet/tcp.h> // TCP_NODELAY
+#include <fcntl.h>
+#include <stddef.h> // NULL
+#include <errno.h>
+#include <spice/macros.h>
+#include <spice/vd_agent.h>
+#include "common/marshaller.h"
+#include "common/messages.h"
+#include "server/demarshallers.h"
+#include "server/generated_marshallers.h"
+#include "spice.h"
+#include "inputs_channel.h"
+#include "red_common.h"
+#include "reds.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)
+
+#define SEND_BUF_SIZE 4096
+
+typedef struct IncomingHandler {
+ spice_parse_channel_func_t parser;
+ void *opaque;
+ int shut;
+ uint8_t buf[RECEIVE_BUF_SIZE];
+ uint32_t end_pos;
+ void (*handle_message)(void *opaque, size_t size, uint32_t type, void *message);
+} IncomingHandler;
+
+typedef struct OutgoingHandler {
+ void *opaque;
+ uint8_t buf[SEND_BUF_SIZE];
+ uint8_t *now;
+ uint32_t length;
+ void (*select)(void *opaque, int select);
+ void (*may_write)(void *opaque);
+} OutgoingHandler;
+
+
+// Temporarily here to make splitting reds.c to inputs_channel.c easier,
+// TODO - remove from here, leave private to inputs_channel.c
+typedef struct InputsState {
+ Channel *channel;
+ RedsStreamContext *peer;
+ IncomingHandler in_handler;
+ OutgoingHandler out_handler;
+ VDAgentMouseState mouse_state;
+ uint32_t motion_count;
+ uint64_t serial; //migrate me
+} InputsState;
+
+
+// TODO: move to InputsState after InputsState lands here
+// from reds_inputs.h
+
+static SpiceKbdInstance *keyboard = NULL;
+static SpiceMouseInstance *mouse = NULL;
+static SpiceTabletInstance *tablet = NULL;
+
+static SpiceTimer *key_modifiers_timer;
+
+static InputsState *inputs_state;
+
+#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/
+
+#define SCROLL_LOCK_SCAN_CODE 0x46
+#define NUM_LOCK_SCAN_CODE 0x45
+#define CAPS_LOCK_SCAN_CODE 0x3a
+
+int inputs_inited(void)
+{
+ return !!inputs_state;
+}
+
+int inputs_set_keyboard(SpiceKbdInstance *_keyboard)
+{
+ if (keyboard) {
+ red_printf("already have keyboard");
+ return -1;
+ }
+ keyboard = _keyboard;
+ keyboard->st = spice_new0(SpiceKbdState, 1);
+ return 0;
+}
+
+int inputs_set_mouse(SpiceMouseInstance *_mouse)
+{
+ if (mouse) {
+ red_printf("already have mouse");
+ return -1;
+ }
+ mouse = _mouse;
+ mouse->st = spice_new0(SpiceMouseState, 1);
+ return 0;
+}
+
+int inputs_set_tablet(SpiceTabletInstance *_tablet)
+{
+ if (tablet) {
+ red_printf("already have tablet");
+ return -1;
+ }
+ tablet = _tablet;
+ tablet->st = spice_new0(SpiceTabletState, 1);
+ return 0;
+}
+
+int inputs_has_tablet(void)
+{
+ return !!tablet;
+}
+
+void inputs_detach_tablet(SpiceTabletInstance *_tablet)
+{
+ red_printf("");
+ tablet = NULL;
+}
+
+void inputs_set_tablet_logical_size(int x_res, int y_res)
+{
+ SpiceTabletInterface *sif;
+
+ sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
+ sif->set_logical_size(tablet, x_res, y_res);
+}
+
+const VDAgentMouseState *inputs_get_mouse_state(void)
+{
+ ASSERT(inputs_state);
+ return &inputs_state->mouse_state;
+}
+
+static int handle_incoming(RedsStreamContext *peer, IncomingHandler *handler)
+{
+ for (;;) {
+ uint8_t *buf = handler->buf;
+ uint32_t pos = handler->end_pos;
+ uint8_t *end = buf + pos;
+ SpiceDataHeader *header;
+ int n;
+ n = peer->cb_read(peer->ctx, buf + pos, RECEIVE_BUF_SIZE - pos);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ pos += n;
+ end = buf + pos;
+ while (buf + sizeof(SpiceDataHeader) <= end &&
+ buf + sizeof(SpiceDataHeader) + (header = (SpiceDataHeader *)buf)->size <= end) {
+ uint8_t *data = (uint8_t *)(header+1);
+ size_t parsed_size;
+ uint8_t *parsed;
+ message_destructor_t parsed_free;
+
+
+ buf += sizeof(SpiceDataHeader) + header->size;
+ parsed = handler->parser(data, data + header->size, header->type,
+ SPICE_VERSION_MINOR, &parsed_size, &parsed_free);
+ if (parsed == NULL) {
+ red_printf("failed to parse message type %d", header->type);
+ return -1;
+ }
+ handler->handle_message(handler->opaque, parsed_size, header->type, parsed);
+ parsed_free(parsed);
+ if (handler->shut) {
+ return -1;
+ }
+ }
+ memmove(handler->buf, buf, (handler->end_pos = end - buf));
+ }
+ }
+}
+
+static int handle_outgoing(RedsStreamContext *peer, OutgoingHandler *handler)
+{
+ if (!handler->length) {
+ return 0;
+ }
+
+ while (handler->length) {
+ int n;
+
+ n = peer->cb_write(peer->ctx, handler->now, handler->length);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ handler->now += n;
+ handler->length -= n;
+ }
+ }
+ handler->select(handler->opaque, FALSE);
+ handler->may_write(handler->opaque);
+ return 0;
+}
+
+#define OUTGOING_OK 0
+#define OUTGOING_FAILED -1
+#define OUTGOING_BLOCKED 1
+
+static int outgoing_write(RedsStreamContext *peer, OutgoingHandler *handler, void *in_data,
+ int length)
+{
+ uint8_t *data = in_data;
+ ASSERT(length <= SEND_BUF_SIZE);
+ if (handler->length) {
+ return OUTGOING_BLOCKED;
+ }
+
+ while (length) {
+ int n = peer->cb_write(peer->ctx, data, length);
+ if (n < 0) {
+ switch (errno) {
+ case EAGAIN:
+ handler->length = length;
+ memcpy(handler->buf, data, length);
+ handler->select(handler->opaque, TRUE);
+ return OUTGOING_OK;
+ case EINTR:
+ break;
+ case EPIPE:
+ return OUTGOING_FAILED;
+ default:
+ red_printf("%s", strerror(errno));
+ return OUTGOING_FAILED;
+ }
+ } else {
+ data += n;
+ length -= n;
+ }
+ }
+ return OUTGOING_OK;
+}
+
+#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()
+{
+ core->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_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base);
+ 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 SpiceMarshaller *marshaller_new_for_outgoing(InputsState *state, int type)
+{
+ SpiceMarshaller *m;
+ SpiceDataHeader *header;
+
+ m = spice_marshaller_new();
+ header = (SpiceDataHeader *)
+ spice_marshaller_reserve_space(m, sizeof(SpiceDataHeader));
+ header->serial = ++state->serial;
+ header->type = type;
+ header->sub_list = 0;
+
+ return m;
+}
+
+static int marshaller_outgoing_write(SpiceMarshaller *m,
+ InputsState *state)
+{
+ SpiceDataHeader *header = (SpiceDataHeader *)spice_marshaller_get_ptr(m);
+ uint8_t *data;
+ size_t len;
+ int free_data;
+
+ spice_marshaller_flush(m);
+ header->size = spice_marshaller_get_total_size(m) - sizeof(SpiceDataHeader);
+
+ data = spice_marshaller_linearize(m, 0, &len, &free_data);
+
+ if (outgoing_write(state->peer, &state->out_handler, data, len) != OUTGOING_OK) {
+ return FALSE;
+ }
+
+ if (free_data) {
+ free(data);
+ }
+
+ return TRUE;
+}
+
+
+static void inputs_handle_input(void *opaque, size_t size, uint32_t type, void *message)
+{
+ InputsState *state = (InputsState *)opaque;
+ uint8_t *buf = (uint8_t *)message;
+ SpiceMarshaller *m;
+
+ switch (type) {
+ case SPICE_MSGC_INPUTS_KEY_DOWN: {
+ SpiceMsgcKeyDown *key_up = (SpiceMsgcKeyDown *)buf;
+ if (key_up->code == CAPS_LOCK_SCAN_CODE || key_up->code == NUM_LOCK_SCAN_CODE ||
+ key_up->code == SCROLL_LOCK_SCAN_CODE) {
+ activate_modifiers_watch();
+ }
+ }
+ case SPICE_MSGC_INPUTS_KEY_UP: {
+ SpiceMsgcKeyDown *key_down = (SpiceMsgcKeyDown *)buf;
+ uint8_t *now = (uint8_t *)&key_down->code;
+ uint8_t *end = now + sizeof(key_down->code);
+ for (; now < end && *now; now++) {
+ kbd_push_scan(keyboard, *now);
+ }
+ break;
+ }
+ case SPICE_MSGC_INPUTS_MOUSE_MOTION: {
+ SpiceMsgcMouseMotion *mouse_motion = (SpiceMsgcMouseMotion *)buf;
+
+ if (++state->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0) {
+ m = marshaller_new_for_outgoing(state, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK);
+ if (!marshaller_outgoing_write(m, state)) {
+ red_printf("motion ack failed");
+ reds_disconnect();
+ }
+ spice_marshaller_destroy(m);
+ }
+ if (mouse && reds_get_mouse_mode() == 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 = (SpiceMsgcMousePosition *)buf;
+
+ if (++state->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0) {
+ m = marshaller_new_for_outgoing(state, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK);
+ if (!marshaller_outgoing_write(m, state)) {
+ red_printf("position ack failed");
+ reds_disconnect();
+ }
+ spice_marshaller_destroy(m);
+ }
+ if (reds_get_mouse_mode() != SPICE_MOUSE_MODE_CLIENT) {
+ break;
+ }
+ ASSERT((reds_get_agent_mouse() && reds_has_vdagent()) || tablet);
+ if (!reds_get_agent_mouse() || !reds_has_vdagent()) {
+ 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 = &state->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(mouse_state);
+ break;
+ }
+ case SPICE_MSGC_INPUTS_MOUSE_PRESS: {
+ SpiceMsgcMousePress *mouse_press = (SpiceMsgcMousePress *)buf;
+ 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() == SPICE_MOUSE_MODE_CLIENT) {
+ if (reds_get_agent_mouse() && reds_has_vdagent()) {
+ inputs_state->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(&inputs_state->mouse_state);
+ } else if (tablet) {
+ SpiceTabletInterface *sif;
+ sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
+ sif->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
+ }
+ } else if (mouse) {
+ SpiceMouseInterface *sif;
+ sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
+ sif->motion(mouse, 0, 0, dz,
+ RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
+ }
+ break;
+ }
+ case SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
+ SpiceMsgcMouseRelease *mouse_release = (SpiceMsgcMouseRelease *)buf;
+ if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) {
+ if (reds_get_agent_mouse() && reds_has_vdagent()) {
+ inputs_state->mouse_state.buttons =
+ RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
+ reds_handle_agent_mouse_event(&inputs_state->mouse_state);
+ } else if (tablet) {
+ SpiceTabletInterface *sif;
+ sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base);
+ sif->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
+ }
+ } else if (mouse) {
+ SpiceMouseInterface *sif;
+ sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base);
+ sif->buttons(mouse,
+ RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
+ }
+ break;
+ }
+ case SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
+ SpiceMsgcKeyModifiers *modifiers = (SpiceMsgcKeyModifiers *)buf;
+ uint8_t leds;
+
+ 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();
+ break;
+ }
+ case SPICE_MSGC_DISCONNECTING:
+ break;
+ default:
+ red_printf("unexpected type %d", type);
+ }
+}
+
+static void inputs_relase_keys(void)
+{
+ kbd_push_scan(keyboard, 0x2a | 0x80); //LSHIFT
+ kbd_push_scan(keyboard, 0x36 | 0x80); //RSHIFT
+ kbd_push_scan(keyboard, 0xe0); kbd_push_scan(keyboard, 0x1d | 0x80); //RCTRL
+ kbd_push_scan(keyboard, 0x1d | 0x80); //LCTRL
+ kbd_push_scan(keyboard, 0xe0); kbd_push_scan(keyboard, 0x38 | 0x80); //RALT
+ kbd_push_scan(keyboard, 0x38 | 0x80); //LALT
+}
+
+static void inputs_event(int fd, int event, void *data)
+{
+ if (data != inputs_state) {
+ return; // shutdown already happened
+ }
+
+ if (event & SPICE_WATCH_EVENT_READ) {
+ if (handle_incoming(inputs_state->peer, &inputs_state->in_handler)) {
+ inputs_relase_keys();
+ core->watch_remove(inputs_state->peer->watch);
+ inputs_state->peer->watch = NULL;
+ if (inputs_state->channel) {
+ inputs_state->channel->data = NULL;
+ }
+ inputs_state->peer->cb_free(inputs_state->peer);
+ free(inputs_state);
+ inputs_state = NULL;
+ }
+ }
+ if (event & SPICE_WATCH_EVENT_WRITE) {
+ if (handle_outgoing(inputs_state->peer, &inputs_state->out_handler)) {
+ reds_disconnect();
+ }
+ }
+}
+
+
+static void inputs_shutdown(Channel *channel)
+{
+ InputsState *state = (InputsState *)channel->data;
+ if (state) {
+ state->in_handler.shut = TRUE;
+ shutdown(state->peer->socket, SHUT_RDWR);
+ channel->data = NULL;
+ state->channel = NULL;
+ inputs_state = NULL;
+ }
+}
+
+static void inputs_migrate(Channel *channel)
+{
+ InputsState *state = (InputsState *)channel->data;
+ SpiceMarshaller *m;
+ SpiceMsgMigrate migrate;
+
+ m = marshaller_new_for_outgoing(state, SPICE_MSG_MIGRATE);
+
+ migrate.flags = 0;
+ spice_marshall_msg_migrate(m, &migrate);
+
+ if (!marshaller_outgoing_write(m, state)) {
+ red_printf("write failed");
+ }
+ spice_marshaller_destroy(m);
+}
+
+static void inputs_select(void *opaque, int select)
+{
+ int eventmask = SPICE_WATCH_EVENT_READ;
+ red_printf("");
+
+ ASSERT(opaque == inputs_state);
+ if (select) {
+ eventmask |= SPICE_WATCH_EVENT_WRITE;
+ }
+ core->watch_update_mask(inputs_state->peer->watch, eventmask);
+}
+
+static void inputs_may_write(void *opaque)
+{
+ red_printf("");
+}
+
+static void inputs_link(Channel *channel, RedsStreamContext *peer, int migration,
+ int num_common_caps, uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ int delay_val;
+ int flags;
+
+ red_printf("");
+ ASSERT(channel->data == NULL);
+
+ inputs_state = spice_new0(InputsState, 1);
+
+ delay_val = 1;
+ if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ if ((flags = fcntl(peer->socket, F_GETFL)) == -1 ||
+ fcntl(peer->socket, F_SETFL, flags | O_ASYNC) == -1) {
+ red_printf("fcntl failed, %s", strerror(errno));
+ }
+
+ inputs_state->peer = peer;
+ inputs_state->channel = channel;
+ inputs_state->in_handler.parser = spice_get_client_channel_parser(SPICE_CHANNEL_INPUTS, NULL);
+ inputs_state->in_handler.opaque = inputs_state;
+ inputs_state->in_handler.handle_message = inputs_handle_input;
+ inputs_state->out_handler.length = 0;
+ inputs_state->out_handler.opaque = inputs_state;
+ inputs_state->out_handler.select = inputs_select;
+ inputs_state->out_handler.may_write = inputs_may_write;
+ channel->data = inputs_state;
+ peer->watch = core->watch_add(peer->socket, SPICE_WATCH_EVENT_READ,
+ inputs_event, inputs_state);
+
+ SpiceMarshaller *m;
+ SpiceMsgInputsInit inputs_init;
+ m = marshaller_new_for_outgoing(inputs_state, SPICE_MSG_INPUTS_INIT);
+ inputs_init.keyboard_modifiers = kbd_get_leds(keyboard);
+ spice_marshall_msg_inputs_init(m, &inputs_init);
+ if (!marshaller_outgoing_write(m, inputs_state)) {
+ red_printf("failed to send modifiers state");
+ reds_disconnect();
+ }
+ spice_marshaller_destroy(m);
+}
+
+void inputs_send_keyboard_modifiers(uint8_t modifiers)
+{
+ SpiceMsgInputsKeyModifiers key_modifiers;
+ SpiceMarshaller *m;
+
+ if (!inputs_state) {
+ return;
+ }
+ ASSERT(inputs_state->peer);
+
+ m = marshaller_new_for_outgoing(inputs_state,
+ SPICE_MSG_INPUTS_KEY_MODIFIERS);
+
+ key_modifiers.modifiers = modifiers;
+ spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers);
+
+ if (!marshaller_outgoing_write(m, inputs_state)) {
+ red_printf("failed to send modifiers state");
+ reds_disconnect();
+ }
+ spice_marshaller_destroy(m);
+}
+
+void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds)
+{
+ inputs_send_keyboard_modifiers(leds);
+}
+
+static void key_modifiers_sender(void *opaque)
+{
+ inputs_send_keyboard_modifiers(kbd_get_leds(keyboard));
+}
+
+void inputs_init(void)
+{
+ Channel *channel;
+
+ channel = spice_new0(Channel, 1);
+ channel->type = SPICE_CHANNEL_INPUTS;
+ channel->link = inputs_link;
+ channel->shutdown = inputs_shutdown;
+ channel->migrate = inputs_migrate;
+ reds_register_channel(channel);
+
+ if (!(key_modifiers_timer = core->timer_add(key_modifiers_sender, NULL))) {
+ red_error("key modifiers timer create failed");
+ }
+}
+