diff options
author | Alon Levy <alevy@redhat.com> | 2010-10-31 16:31:14 +0200 |
---|---|---|
committer | Alon Levy <alevy@redhat.com> | 2010-12-07 21:32:33 +0200 |
commit | 347e32177cd7a563054ac903e1efb193f6ec7b57 (patch) | |
tree | 325974ac49ac33cdf9e127a852cdfe78da31e1e3 /server/inputs_channel.c | |
parent | 26c40c4196a6c1b12dc59a794d64fb217b5a7259 (diff) | |
download | spice-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.c | 694 |
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"); + } +} + |