diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2015-06-05 17:44:47 +0200 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2015-06-08 17:38:58 +0200 |
commit | caf28401cac9ece5e314360f8a3a54479df59727 (patch) | |
tree | 7ada0451442ffa1d9d056500ebd878ad80da3e06 /src/channel-inputs.c | |
parent | 39c315241350dde3741c99fee4fff17b22d2b1fa (diff) | |
download | spice-gtk-caf28401cac9ece5e314360f8a3a54479df59727.tar.gz spice-gtk-caf28401cac9ece5e314360f8a3a54479df59727.tar.xz spice-gtk-caf28401cac9ece5e314360f8a3a54479df59727.zip |
Move gtk/ -> src/
For historical reasons, the code was placed under gtk/ subdirectory.
If it was always bugging you, bug no more!
Diffstat (limited to 'src/channel-inputs.c')
-rw-r--r-- | src/channel-inputs.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/src/channel-inputs.c b/src/channel-inputs.c new file mode 100644 index 0000000..df1ffe1 --- /dev/null +++ b/src/channel-inputs.c @@ -0,0 +1,603 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2010 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 "config.h" + +#include "spice-client.h" +#include "spice-common.h" +#include "spice-channel-priv.h" + +/** + * SECTION:channel-inputs + * @short_description: control the server mouse and keyboard + * @title: Inputs Channel + * @section_id: + * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay + * @stability: Stable + * @include: channel-inputs.h + * + * Spice supports sending keyboard key events and keyboard leds + * synchronization. The key events are sent using + * spice_inputs_key_press() and spice_inputs_key_release() using + * a modified variant of PC XT scancodes. + * + * Guest keyboard leds state can be manipulated with + * spice_inputs_set_key_locks(). When key lock change, a notification + * is emitted with #SpiceInputsChannel::inputs-modifiers signal. + */ + +#define SPICE_INPUTS_CHANNEL_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelPrivate)) + +struct _SpiceInputsChannelPrivate { + int bs; + int dx, dy; + unsigned int x, y, dpy; + int motion_count; + int modifiers; + guint32 locks; +}; + +G_DEFINE_TYPE(SpiceInputsChannel, spice_inputs_channel, SPICE_TYPE_CHANNEL) + +/* Properties */ +enum { + PROP_0, + PROP_KEY_MODIFIERS, +}; + +/* Signals */ +enum { + SPICE_INPUTS_MODIFIERS, + + SPICE_INPUTS_LAST_SIGNAL, +}; + +static guint signals[SPICE_INPUTS_LAST_SIGNAL]; + +static void spice_inputs_channel_up(SpiceChannel *channel); +static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating); +static void channel_set_handlers(SpiceChannelClass *klass); + +/* ------------------------------------------------------------------ */ + +static void spice_inputs_channel_init(SpiceInputsChannel *channel) +{ + channel->priv = SPICE_INPUTS_CHANNEL_GET_PRIVATE(channel); +} + +static void spice_inputs_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(object)->priv; + + switch (prop_id) { + case PROP_KEY_MODIFIERS: + g_value_set_int(value, c->modifiers); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void spice_inputs_channel_finalize(GObject *obj) +{ + if (G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize) + G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize(obj); +} + +static void spice_inputs_channel_class_init(SpiceInputsChannelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass); + + gobject_class->finalize = spice_inputs_channel_finalize; + gobject_class->get_property = spice_inputs_get_property; + channel_class->channel_up = spice_inputs_channel_up; + channel_class->channel_reset = spice_inputs_channel_reset; + + g_object_class_install_property + (gobject_class, PROP_KEY_MODIFIERS, + g_param_spec_int("key-modifiers", + "Key modifiers", + "Guest keyboard lock/led state", + 0, INT_MAX, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + /** + * SpiceInputsChannel::inputs-modifier: + * @display: the #SpiceInputsChannel that emitted the signal + * + * The #SpiceInputsChannel::inputs-modifier signal is emitted when + * the guest keyboard locks are changed. You can read the current + * state from #SpiceInputsChannel:key-modifiers property. + **/ + /* TODO: use notify instead? */ + signals[SPICE_INPUTS_MODIFIERS] = + g_signal_new("inputs-modifiers", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SpiceInputsChannelClass, inputs_modifiers), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private(klass, sizeof(SpiceInputsChannelPrivate)); + channel_set_handlers(SPICE_CHANNEL_CLASS(klass)); +} + +/* ------------------------------------------------------------------ */ + +static SpiceMsgOut* mouse_motion(SpiceInputsChannel *channel) +{ + SpiceInputsChannelPrivate *c = channel->priv; + SpiceMsgcMouseMotion motion; + SpiceMsgOut *msg; + + if (!c->dx && !c->dy) + return NULL; + + motion.buttons_state = c->bs; + motion.dx = c->dx; + motion.dy = c->dy; + msg = spice_msg_out_new(SPICE_CHANNEL(channel), + SPICE_MSGC_INPUTS_MOUSE_MOTION); + msg->marshallers->msgc_inputs_mouse_motion(msg->marshaller, &motion); + + c->motion_count++; + c->dx = 0; + c->dy = 0; + + return msg; +} + +static SpiceMsgOut* mouse_position(SpiceInputsChannel *channel) +{ + SpiceInputsChannelPrivate *c = channel->priv; + SpiceMsgcMousePosition position; + SpiceMsgOut *msg; + + if (c->dpy == -1) + return NULL; + + /* CHANNEL_DEBUG(channel, "%s: +%d+%d", __FUNCTION__, c->x, c->y); */ + position.buttons_state = c->bs; + position.x = c->x; + position.y = c->y; + position.display_id = c->dpy; + msg = spice_msg_out_new(SPICE_CHANNEL(channel), + SPICE_MSGC_INPUTS_MOUSE_POSITION); + msg->marshallers->msgc_inputs_mouse_position(msg->marshaller, &position); + + c->motion_count++; + c->dpy = -1; + + return msg; +} + +/* main context */ +static void send_position(SpiceInputsChannel *channel) +{ + SpiceMsgOut *msg; + + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + msg = mouse_position(channel); + if (!msg) /* if no motion */ + return; + + spice_msg_out_send(msg); +} + +/* main context */ +static void send_motion(SpiceInputsChannel *channel) +{ + SpiceMsgOut *msg; + + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + msg = mouse_motion(channel); + if (!msg) /* if no motion */ + return; + + spice_msg_out_send(msg); +} + +/* coroutine context */ +static void inputs_handle_init(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv; + SpiceMsgInputsInit *init = spice_msg_in_parsed(in); + + c->modifiers = init->keyboard_modifiers; + g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0); +} + +/* coroutine context */ +static void inputs_handle_modifiers(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv; + SpiceMsgInputsKeyModifiers *modifiers = spice_msg_in_parsed(in); + + c->modifiers = modifiers->modifiers; + g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0); +} + +/* coroutine context */ +static void inputs_handle_ack(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv; + SpiceMsgOut *msg; + + c->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH; + + msg = mouse_motion(SPICE_INPUTS_CHANNEL(channel)); + if (msg) { /* if no motion, msg == NULL */ + spice_msg_out_send_internal(msg); + } + + msg = mouse_position(SPICE_INPUTS_CHANNEL(channel)); + if (msg) { + spice_msg_out_send_internal(msg); + } +} + +static void channel_set_handlers(SpiceChannelClass *klass) +{ + static const spice_msg_handler handlers[] = { + [ SPICE_MSG_INPUTS_INIT ] = inputs_handle_init, + [ SPICE_MSG_INPUTS_KEY_MODIFIERS ] = inputs_handle_modifiers, + [ SPICE_MSG_INPUTS_MOUSE_MOTION_ACK ] = inputs_handle_ack, + }; + + spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers)); +} + +/** + * spice_inputs_motion: + * @channel: + * @dx: delta X mouse coordinates + * @dy: delta Y mouse coordinates + * @button_state: SPICE_MOUSE_BUTTON_MASK flags + * + * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT). + **/ +void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy, + gint button_state) +{ + SpiceInputsChannelPrivate *c; + + g_return_if_fail(channel != NULL); + g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED); + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + + if (dx == 0 && dy == 0) + return; + + c = channel->priv; + c->bs = button_state; + c->dx += dx; + c->dy += dy; + + if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) { + send_motion(channel); + } +} + +/** + * spice_inputs_position: + * @channel: + * @x: X mouse coordinates + * @y: Y mouse coordinates + * @display: display channel id + * @button_state: SPICE_MOUSE_BUTTON_MASK flags + * + * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT). + **/ +void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y, + gint display, gint button_state) +{ + SpiceInputsChannelPrivate *c; + + g_return_if_fail(channel != NULL); + + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + + c = channel->priv; + c->bs = button_state; + c->x = x; + c->y = y; + c->dpy = display; + + if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) { + send_position(channel); + } else { + CHANNEL_DEBUG(channel, "over SPICE_INPUT_MOTION_ACK_BUNCH * 2, dropping"); + } +} + +/** + * spice_inputs_button_press: + * @channel: + * @button: a SPICE_MOUSE_BUTTON + * @button_state: SPICE_MOUSE_BUTTON_MASK flags + * + * Press a mouse button. + **/ +void spice_inputs_button_press(SpiceInputsChannel *channel, gint button, + gint button_state) +{ + SpiceInputsChannelPrivate *c; + SpiceMsgcMousePress press; + SpiceMsgOut *msg; + + g_return_if_fail(channel != NULL); + + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + c = channel->priv; + switch (button) { + case SPICE_MOUSE_BUTTON_LEFT: + button_state |= SPICE_MOUSE_BUTTON_MASK_LEFT; + break; + case SPICE_MOUSE_BUTTON_MIDDLE: + button_state |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; + break; + case SPICE_MOUSE_BUTTON_RIGHT: + button_state |= SPICE_MOUSE_BUTTON_MASK_RIGHT; + break; + } + + c->bs = button_state; + send_motion(channel); + send_position(channel); + + msg = spice_msg_out_new(SPICE_CHANNEL(channel), + SPICE_MSGC_INPUTS_MOUSE_PRESS); + press.button = button; + press.buttons_state = button_state; + msg->marshallers->msgc_inputs_mouse_press(msg->marshaller, &press); + spice_msg_out_send(msg); +} + +/** + * spice_inputs_button_release: + * @channel: + * @button: a SPICE_MOUSE_BUTTON + * @button_state: SPICE_MOUSE_BUTTON_MASK flags + * + * Release a button. + **/ +void spice_inputs_button_release(SpiceInputsChannel *channel, gint button, + gint button_state) +{ + SpiceInputsChannelPrivate *c; + SpiceMsgcMouseRelease release; + SpiceMsgOut *msg; + + g_return_if_fail(channel != NULL); + + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + c = channel->priv; + switch (button) { + case SPICE_MOUSE_BUTTON_LEFT: + button_state &= ~SPICE_MOUSE_BUTTON_MASK_LEFT; + break; + case SPICE_MOUSE_BUTTON_MIDDLE: + button_state &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; + break; + case SPICE_MOUSE_BUTTON_RIGHT: + button_state &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT; + break; + } + + c->bs = button_state; + send_motion(channel); + send_position(channel); + + msg = spice_msg_out_new(SPICE_CHANNEL(channel), + SPICE_MSGC_INPUTS_MOUSE_RELEASE); + release.button = button; + release.buttons_state = button_state; + msg->marshallers->msgc_inputs_mouse_release(msg->marshaller, &release); + spice_msg_out_send(msg); +} + +/** + * spice_inputs_key_press: + * @channel: + * @scancode: a PC XT (set 1) key scancode. For scancodes with an %0xe0 + * prefix, drop the prefix and OR the scancode with %0x100. + * + * Press a key. + **/ +void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode) +{ + SpiceMsgcKeyDown down; + SpiceMsgOut *msg; + + g_return_if_fail(channel != NULL); + g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED); + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + down.code = spice_make_scancode(scancode, FALSE); + msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_DOWN); + msg->marshallers->msgc_inputs_key_down(msg->marshaller, &down); + spice_msg_out_send(msg); +} + +/** + * spice_inputs_key_release: + * @channel: + * @scancode: a PC XT (set 1) key scancode. For scancodes with an %0xe0 + * prefix, drop the prefix and OR the scancode with %0x100. + * + * Release a key. + **/ +void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode) +{ + SpiceMsgcKeyUp up; + SpiceMsgOut *msg; + + g_return_if_fail(channel != NULL); + g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED); + if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY) + return; + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + up.code = spice_make_scancode(scancode, TRUE); + msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_UP); + msg->marshallers->msgc_inputs_key_up(msg->marshaller, &up); + spice_msg_out_send(msg); +} + +/** + * spice_inputs_key_press_and_release: + * @channel: + * @scancode: a PC XT (set 1) key scancode. For scancodes with an %0xe0 + * prefix, drop the prefix and OR the scancode with %0x100. + * + * Press and release a key event atomically (in the same message). + * + * Since: 0.13 + **/ +void spice_inputs_key_press_and_release(SpiceInputsChannel *input_channel, guint scancode) +{ + SpiceChannel *channel = SPICE_CHANNEL(input_channel); + + g_return_if_fail(channel != NULL); + g_return_if_fail(channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED); + + if (channel->priv->state != SPICE_CHANNEL_STATE_READY) + return; + if (spice_channel_get_read_only(channel)) + return; + + if (spice_channel_test_capability(channel, SPICE_INPUTS_CAP_KEY_SCANCODE)) { + SpiceMsgOut *msg; + guint16 code; + guint8 *buf; + + msg = spice_msg_out_new(channel, SPICE_MSGC_INPUTS_KEY_SCANCODE); + if (scancode < 0x100) { + buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 2); + buf[0] = spice_make_scancode(scancode, FALSE); + buf[1] = spice_make_scancode(scancode, TRUE); + } else { + buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 4); + code = spice_make_scancode(scancode, FALSE); + buf[0] = code & 0xff; + buf[1] = code >> 8; + code = spice_make_scancode(scancode, TRUE); + buf[2] = code & 0xff; + buf[3] = code >> 8; + } + spice_msg_out_send(msg); + } else { + CHANNEL_DEBUG(channel, "The server doesn't support atomic press and release"); + spice_inputs_key_press(input_channel, scancode); + spice_inputs_key_release(input_channel, scancode); + } +} + +/* main or coroutine context */ +static SpiceMsgOut* set_key_locks(SpiceInputsChannel *channel, guint locks) +{ + SpiceMsgcKeyModifiers modifiers; + SpiceMsgOut *msg; + SpiceInputsChannelPrivate *ic; + SpiceChannelPrivate *c; + + g_return_val_if_fail(SPICE_IS_INPUTS_CHANNEL(channel), NULL); + + ic = channel->priv; + c = SPICE_CHANNEL(channel)->priv; + + ic->locks = locks; + if (c->state != SPICE_CHANNEL_STATE_READY) + return NULL; + + msg = spice_msg_out_new(SPICE_CHANNEL(channel), + SPICE_MSGC_INPUTS_KEY_MODIFIERS); + modifiers.modifiers = locks; + msg->marshallers->msgc_inputs_key_modifiers(msg->marshaller, &modifiers); + return msg; +} + +/** + * spice_inputs_set_key_locks: + * @channel: + * @locks: #SpiceInputsLock modifiers flags + * + * Set the keyboard locks on the guest (Caps, Num, Scroll..) + **/ +void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks) +{ + SpiceMsgOut *msg; + + if (spice_channel_get_read_only(SPICE_CHANNEL(channel))) + return; + + msg = set_key_locks(channel, locks); + if (!msg) /* you can set_key_locks() even if the channel is not ready */ + return; + + spice_msg_out_send(msg); /* main -> coroutine */ +} + +/* coroutine context */ +static void spice_inputs_channel_up(SpiceChannel *channel) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv; + SpiceMsgOut *msg; + + if (spice_channel_get_read_only(channel)) + return; + + msg = set_key_locks(SPICE_INPUTS_CHANNEL(channel), c->locks); + spice_msg_out_send_internal(msg); +} + +static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating) +{ + SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv; + c->motion_count = 0; + + SPICE_CHANNEL_CLASS(spice_inputs_channel_parent_class)->channel_reset(channel, migrating); +} |