mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-15 05:23:02 +00:00
c61d97b0ac
This fixes a race condition which causes the mouse ringbuffer to overflow. It also corrects out of order message index IDs due to multiple threads sending messages asyncronously.
921 lines
No EOL
22 KiB
C
921 lines
No EOL
22 KiB
C
/*
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
|
https://looking-glass.hostfission.com
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free Software
|
|
Foundation; either version 2 of the License, or (at your option) any later
|
|
version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|
Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "spice.h"
|
|
#include "utils.h"
|
|
#include "debug.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/select.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <spice/protocol.h>
|
|
#include <spice/error_codes.h>
|
|
|
|
#include "messages.h"
|
|
|
|
#ifdef DEBUG_SPICE_MOUSE
|
|
#define DEBUG_MOUSE(fmt, args...) DEBUG_PRINT("[M]", fmt, ##args)
|
|
#else
|
|
#define DEBUG_MOUSE(fmt, args...) do {} while(0)
|
|
#endif
|
|
|
|
#ifdef DEBUG_SPICE_KEYBOARD
|
|
#define DEBUG_KEYBOARD(fmt, args...) DEBUG_PRINT("[K]", fmt, ##args)
|
|
#else
|
|
#define DEBUG_KEYBOARD(fmt, args...) do {} while(0)
|
|
#endif
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// internal structures
|
|
struct SpiceChannel
|
|
{
|
|
bool connected;
|
|
bool initDone;
|
|
uint8_t channelType;
|
|
int socket;
|
|
uint32_t ackFrequency;
|
|
uint32_t ackCount;
|
|
uint32_t serial;
|
|
LG_Lock lock;
|
|
};
|
|
|
|
struct SpiceKeyboard
|
|
{
|
|
uint32_t modifiers;
|
|
};
|
|
|
|
#define SPICE_MOUSE_QUEUE_SIZE 64
|
|
|
|
struct SpiceMouse
|
|
{
|
|
uint32_t buttonState;
|
|
|
|
int sentCount;
|
|
SpiceMsgcMouseMotion queue[SPICE_MOUSE_QUEUE_SIZE];
|
|
int rpos, wpos;
|
|
int queueLen;
|
|
LG_Lock lock;
|
|
};
|
|
|
|
struct Spice
|
|
{
|
|
char password[32];
|
|
struct sockaddr_in addr;
|
|
|
|
uint32_t sessionID;
|
|
uint32_t channelID;
|
|
struct SpiceChannel scMain;
|
|
struct SpiceChannel scInputs;
|
|
|
|
struct SpiceKeyboard kb;
|
|
struct SpiceMouse mouse;
|
|
};
|
|
|
|
// globals
|
|
struct Spice spice =
|
|
{
|
|
.sessionID = 0,
|
|
.scMain .connected = false,
|
|
.scMain .channelType = SPICE_CHANNEL_MAIN,
|
|
.scInputs.connected = false,
|
|
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
|
|
};
|
|
|
|
// internal forward decls
|
|
bool spice_connect_channel (struct SpiceChannel * channel);
|
|
void spice_disconnect_channel(struct SpiceChannel * channel);
|
|
|
|
bool spice_process_ack(struct SpiceChannel * channel);
|
|
|
|
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
|
|
bool spice_on_main_channel_read ();
|
|
bool spice_on_inputs_channel_read();
|
|
|
|
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
|
|
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
|
|
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
|
|
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
|
|
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_connect(const char * host, const short port, const char * password)
|
|
{
|
|
strncpy(spice.password, password, sizeof(spice.password));
|
|
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
|
|
inet_pton(AF_INET, host, &spice.addr.sin_addr);
|
|
spice.addr.sin_family = AF_INET;
|
|
spice.addr.sin_port = htons(port);
|
|
|
|
LG_LOCK_INIT(spice.mouse.lock);
|
|
|
|
spice.channelID = 0;
|
|
if (!spice_connect_channel(&spice.scMain))
|
|
{
|
|
DEBUG_ERROR("connect main channel failed");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
void spice_disconnect()
|
|
{
|
|
spice_disconnect_channel(&spice.scMain );
|
|
spice_disconnect_channel(&spice.scInputs);
|
|
|
|
LG_LOCK_FREE(spice.mouse.lock);
|
|
|
|
spice.sessionID = 0;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_ready()
|
|
{
|
|
return spice.scMain.connected &&
|
|
spice.scInputs.connected;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_process()
|
|
{
|
|
fd_set readSet;
|
|
FD_ZERO(&readSet);
|
|
FD_SET(spice.scMain.socket , &readSet);
|
|
FD_SET(spice.scInputs.socket, &readSet);
|
|
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 1;
|
|
timeout.tv_usec = 0;
|
|
|
|
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
|
|
if (rc < 0)
|
|
{
|
|
DEBUG_ERROR("select failure");
|
|
return false;
|
|
}
|
|
|
|
for(int i = 0; i < FD_SETSIZE; ++i)
|
|
if (FD_ISSET(i, &readSet))
|
|
{
|
|
if (i == spice.scMain.socket)
|
|
{
|
|
if (spice_on_main_channel_read())
|
|
{
|
|
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
|
|
{
|
|
DEBUG_ERROR("failed to process ack on main channel");
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_ERROR("failed to perform read on main channel");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (spice.scInputs.connected && i == spice.scInputs.socket)
|
|
{
|
|
if (!spice_process_ack(&spice.scInputs))
|
|
{
|
|
DEBUG_ERROR("failed to process ack on inputs channel");
|
|
return false;
|
|
}
|
|
|
|
if (spice_on_inputs_channel_read())
|
|
continue;
|
|
else
|
|
{
|
|
DEBUG_ERROR("failed to perform read on inputs channel");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_process_ack(struct SpiceChannel * channel)
|
|
{
|
|
if (channel->ackFrequency == 0)
|
|
return true;
|
|
|
|
if (channel->ackCount++ != channel->ackFrequency)
|
|
return true;
|
|
|
|
channel->ackCount = 0;
|
|
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
|
|
{
|
|
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
|
|
{
|
|
DEBUG_ERROR("read failure");
|
|
*handled = false;
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
|
|
channel->socket,
|
|
header->serial, header->type, header->size, header->sub_list);
|
|
#endif
|
|
|
|
if (!channel->initDone)
|
|
{
|
|
*handled = false;
|
|
|
|
return true;
|
|
}
|
|
switch(header->type)
|
|
{
|
|
case SPICE_MSG_MIGRATE:
|
|
case SPICE_MSG_MIGRATE_DATA:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
|
|
|
|
*handled = true;
|
|
DEBUG_WARN("migration is not supported");
|
|
return false;
|
|
}
|
|
|
|
case SPICE_MSG_SET_ACK:
|
|
{
|
|
DEBUG_INFO("SPICE_MSG_SET_ACK");
|
|
*handled = true;
|
|
|
|
SpiceMsgSetAck in;
|
|
if (!spice_read(channel, &in, sizeof(in)))
|
|
return false;
|
|
|
|
channel->ackFrequency = in.window;
|
|
|
|
SpiceMsgcAckSync out;
|
|
out.generation = in.generation;
|
|
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
case SPICE_MSG_PING:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_PING");
|
|
*handled = true;
|
|
|
|
SpiceMsgPing in;
|
|
if (!spice_read(channel, &in, sizeof(in)))
|
|
return false;
|
|
|
|
if (!spice_discard(channel, header->size - sizeof(in)))
|
|
{
|
|
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
|
|
return false;
|
|
}
|
|
|
|
SpiceMsgcPong out;
|
|
out.id = in.id;
|
|
out.timestamp = in.timestamp;
|
|
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
case SPICE_MSG_WAIT_FOR_CHANNELS:
|
|
case SPICE_MSG_DISCONNECTING :
|
|
{
|
|
*handled = true;
|
|
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
|
|
return false;
|
|
}
|
|
|
|
case SPICE_MSG_NOTIFY:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_NOTIFY");
|
|
SpiceMsgNotify in;
|
|
if (!spice_read(channel, &in, sizeof(in)))
|
|
return false;
|
|
|
|
char msg[in.message_len+1];
|
|
if (!spice_read(channel, msg, in.message_len+1))
|
|
return false;
|
|
|
|
DEBUG_INFO("notify message: %s", msg);
|
|
*handled = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
*handled = false;
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_on_main_channel_read()
|
|
{
|
|
struct SpiceChannel *channel = &spice.scMain;
|
|
|
|
SpiceDataHeader header;
|
|
bool handled;
|
|
|
|
if (!spice_on_common_read(channel, &header, &handled))
|
|
{
|
|
DEBUG_ERROR("read failure");
|
|
return false;
|
|
}
|
|
|
|
if (handled)
|
|
return true;
|
|
|
|
if (!channel->initDone)
|
|
{
|
|
if (header.type != SPICE_MSG_MAIN_INIT)
|
|
{
|
|
spice_disconnect();
|
|
DEBUG_ERROR("expected main init message but got type %u", header.type);
|
|
return false;
|
|
}
|
|
|
|
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
|
|
|
|
channel->initDone = true;
|
|
SpiceMsgMainInit msg;
|
|
if (!spice_read(channel, &msg, sizeof(msg)))
|
|
{
|
|
spice_disconnect();
|
|
return false;
|
|
}
|
|
|
|
spice.sessionID = msg.session_id;
|
|
if (!spice_connect_channel(&spice.scInputs))
|
|
{
|
|
DEBUG_ERROR("failed to connect inputs channel");
|
|
return false;
|
|
}
|
|
|
|
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
|
|
{
|
|
DEBUG_ERROR("failed to set mouse mode");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DEBUG_WARN("main channel unhandled message type %u", header.type);
|
|
spice_discard(channel, header.size);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_on_inputs_channel_read()
|
|
{
|
|
struct SpiceChannel *channel = &spice.scInputs;
|
|
|
|
SpiceDataHeader header;
|
|
bool handled;
|
|
|
|
if (!spice_on_common_read(channel, &header, &handled))
|
|
{
|
|
DEBUG_ERROR("read failure");
|
|
return false;
|
|
}
|
|
|
|
if (handled)
|
|
return true;
|
|
|
|
switch(header.type)
|
|
{
|
|
case SPICE_MSG_INPUTS_INIT:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
|
|
|
|
if (channel->initDone)
|
|
{
|
|
DEBUG_ERROR("input init message already done");
|
|
return false;
|
|
}
|
|
|
|
channel->initDone = true;
|
|
|
|
SpiceMsgInputsInit in;
|
|
if (!spice_read(channel, &in, sizeof(in)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
|
|
SpiceMsgInputsInit in;
|
|
if (!spice_read(channel, &in, sizeof(in)))
|
|
return false;
|
|
|
|
spice.kb.modifiers = in.modifiers;
|
|
return true;
|
|
}
|
|
|
|
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
|
|
{
|
|
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
|
|
int sent = 0;
|
|
LG_LOCK(spice.mouse.lock);
|
|
while(spice.mouse.queueLen && sent < 4)
|
|
{
|
|
SpiceMsgcMouseMotion *msg = &spice.mouse.queue[spice.mouse.rpos];
|
|
if (!spice_write_msg(channel, SPICE_MSGC_INPUTS_MOUSE_MOTION, msg, sizeof(SpiceMsgcMouseMotion)))
|
|
{
|
|
DEBUG_ERROR("failed to send post ack");
|
|
spice.mouse.sentCount = sent;
|
|
LG_UNLOCK(spice.mouse.lock);
|
|
return false;
|
|
}
|
|
|
|
if (++spice.mouse.rpos == SPICE_MOUSE_QUEUE_SIZE)
|
|
spice.mouse.rpos = 0;
|
|
|
|
++sent;
|
|
--spice.mouse.queueLen;
|
|
}
|
|
|
|
spice.mouse.sentCount = sent;
|
|
LG_UNLOCK(spice.mouse.lock);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
|
|
spice_discard(channel, header.size);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_connect_channel(struct SpiceChannel * channel)
|
|
{
|
|
channel->initDone = false;
|
|
channel->ackFrequency = 0;
|
|
channel->ackCount = 0;
|
|
channel->serial = 0;
|
|
|
|
LG_LOCK_INIT(channel->lock);
|
|
|
|
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (channel->socket == -1)
|
|
return false;
|
|
|
|
int flag = 1;
|
|
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
|
|
|
|
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
|
|
{
|
|
DEBUG_ERROR("socket connect failure");
|
|
close(channel->socket);
|
|
return false;
|
|
}
|
|
channel->connected = true;
|
|
|
|
SpiceLinkHeader header =
|
|
{
|
|
.magic = SPICE_MAGIC ,
|
|
.major_version = SPICE_VERSION_MAJOR,
|
|
.minor_version = SPICE_VERSION_MINOR,
|
|
.size = sizeof(SpiceLinkMess)
|
|
};
|
|
|
|
SpiceLinkMess message =
|
|
{
|
|
.connection_id = spice.sessionID,
|
|
.channel_type = channel->channelType,
|
|
.channel_id = spice.channelID,
|
|
.num_common_caps = 0,
|
|
.num_channel_caps = 0,
|
|
.caps_offset = sizeof(SpiceLinkMess)
|
|
};
|
|
|
|
spice_write(channel, &header , sizeof(header ));
|
|
spice_write(channel, &message, sizeof(message));
|
|
|
|
if (!spice_read(channel, &header, sizeof(header)))
|
|
{
|
|
DEBUG_ERROR("failed to read SpiceLinkHeader");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
|
|
{
|
|
DEBUG_ERROR("invalid or unsupported protocol version");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
if (header.size < sizeof(SpiceLinkReply))
|
|
{
|
|
DEBUG_ERROR("reported data size too small");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
SpiceLinkReply reply;
|
|
if (!spice_read(channel, &reply, sizeof(reply)))
|
|
{
|
|
DEBUG_ERROR("failed to read SpiceLinkReply");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
|
|
{
|
|
DEBUG_ERROR("server replied with error %u", reply.error);
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
uint32_t capsCommon [reply.num_common_caps ];
|
|
uint32_t capsChannel[reply.num_channel_caps];
|
|
spice_read(channel, &capsCommon , sizeof(capsCommon ));
|
|
spice_read(channel, &capsChannel, sizeof(capsChannel));
|
|
|
|
BIO *bioKey = BIO_new(BIO_s_mem());
|
|
if (!bioKey)
|
|
{
|
|
DEBUG_ERROR("failed to allocate bioKey");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
|
|
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
|
|
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
|
|
|
|
char enc[RSA_size(rsa)];
|
|
if (RSA_public_encrypt(
|
|
strlen(spice.password) + 1,
|
|
(uint8_t*)spice.password,
|
|
(uint8_t*)enc,
|
|
rsa,
|
|
RSA_PKCS1_OAEP_PADDING
|
|
) <= 0)
|
|
{
|
|
DEBUG_ERROR("rsa public encrypt failed");
|
|
spice_disconnect_channel(channel);
|
|
EVP_PKEY_free(rsaKey);
|
|
BIO_free(bioKey);
|
|
return false;
|
|
}
|
|
|
|
ssize_t rsaSize = RSA_size(rsa);
|
|
EVP_PKEY_free(rsaKey);
|
|
BIO_free(bioKey);
|
|
|
|
if (!spice_write(channel, enc, rsaSize))
|
|
{
|
|
DEBUG_ERROR("failed to write encrypted data");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
uint32_t linkResult;
|
|
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
|
|
{
|
|
DEBUG_ERROR("failed to read SpiceLinkResult");
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
if (linkResult != SPICE_LINK_ERR_OK)
|
|
{
|
|
DEBUG_ERROR("connect code error %u", linkResult);
|
|
spice_disconnect_channel(channel);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
void spice_disconnect_channel(struct SpiceChannel * channel)
|
|
{
|
|
if (channel->connected)
|
|
close(channel->socket);
|
|
channel->connected = false;
|
|
LG_LOCK_FREE(channel->lock);
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
|
|
{
|
|
if (!channel->connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return -1;
|
|
}
|
|
|
|
if (!buffer)
|
|
{
|
|
DEBUG_ERROR("invalid buffer argument supplied");
|
|
return -1;
|
|
}
|
|
|
|
ssize_t len = send(channel->socket, buffer, size, 0);
|
|
if (len != size)
|
|
DEBUG_WARN("incomplete write");
|
|
|
|
return len;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
|
|
{
|
|
LG_LOCK(channel->lock);
|
|
|
|
SpiceDataHeader header;
|
|
header.serial = channel->serial++;
|
|
header.type = type;
|
|
header.size = size;
|
|
header.sub_list = 0;
|
|
|
|
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
|
|
{
|
|
DEBUG_ERROR("failed to write message header");
|
|
LG_UNLOCK(channel->lock);
|
|
return false;
|
|
}
|
|
|
|
if (spice_write(channel, buffer, size) != size)
|
|
{
|
|
DEBUG_ERROR("failed to write message body");
|
|
LG_UNLOCK(channel->lock);
|
|
return false;
|
|
}
|
|
|
|
LG_UNLOCK(channel->lock);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
|
|
{
|
|
if (!channel->connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
if (!buffer)
|
|
{
|
|
DEBUG_ERROR("invalid buffer argument supplied");
|
|
return false;
|
|
}
|
|
|
|
ssize_t len = read(channel->socket, buffer, size);
|
|
if (len != size)
|
|
{
|
|
DEBUG_ERROR("incomplete write");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
|
|
{
|
|
while(size)
|
|
{
|
|
char c[8192];
|
|
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
|
|
if (len <= 0)
|
|
return false;
|
|
|
|
size -= len;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_key_down(uint32_t code)
|
|
{
|
|
DEBUG_KEYBOARD("%u", code);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
if (code > 0x100)
|
|
code = 0xe0 | ((code - 0x100) << 8);
|
|
|
|
SpiceMsgcKeyDown msg;
|
|
msg.code = code;
|
|
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_key_up(uint32_t code)
|
|
{
|
|
DEBUG_KEYBOARD("%u", code);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
if (code < 0x100)
|
|
code |= 0x80;
|
|
else
|
|
code = 0x80e0 | ((code - 0x100) << 8);
|
|
|
|
SpiceMsgcKeyDown msg;
|
|
msg.code = code;
|
|
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_mouse_mode(bool server)
|
|
{
|
|
DEBUG_MOUSE("%s", server ? "server" : "client");
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
SpiceMsgcMainMouseModeRequest msg;
|
|
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
|
|
|
|
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_mouse_position(uint32_t x, uint32_t y)
|
|
{
|
|
DEBUG_MOUSE("x=%u, y=%u", x, y);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
SpiceMsgcMousePosition msg;
|
|
msg.x = x;
|
|
msg.y = y;
|
|
msg.button_state = spice.mouse.buttonState;
|
|
msg.display_id = 0;
|
|
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_mouse_motion(int32_t x, int32_t y)
|
|
{
|
|
DEBUG_MOUSE("x=%d, y=%d", x, y);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
LG_LOCK(spice.mouse.lock);
|
|
if (spice.mouse.sentCount == 4)
|
|
{
|
|
if (spice.mouse.queueLen == SPICE_MOUSE_QUEUE_SIZE)
|
|
{
|
|
DEBUG_ERROR("mouse motion ringbuffer full!");
|
|
LG_UNLOCK(spice.mouse.lock);
|
|
return false;
|
|
}
|
|
|
|
SpiceMsgcMouseMotion *msg =
|
|
&spice.mouse.queue[spice.mouse.wpos++];
|
|
msg->x = x;
|
|
msg->y = y;
|
|
msg->button_state = spice.mouse.buttonState;
|
|
|
|
if (spice.mouse.wpos == SPICE_MOUSE_QUEUE_SIZE)
|
|
spice.mouse.wpos = 0;
|
|
|
|
++spice.mouse.queueLen;
|
|
LG_UNLOCK(spice.mouse.lock);
|
|
return true;
|
|
}
|
|
|
|
SpiceMsgcMouseMotion msg;
|
|
msg.x = x;
|
|
msg.y = y;
|
|
msg.button_state = spice.mouse.buttonState;
|
|
|
|
++spice.mouse.sentCount;
|
|
LG_UNLOCK(spice.mouse.lock);
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_mouse_press(uint32_t button)
|
|
{
|
|
DEBUG_MOUSE("%u", button);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
switch(button)
|
|
{
|
|
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
|
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
|
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
|
}
|
|
|
|
SpiceMsgcMousePress msg;
|
|
msg.button = button;
|
|
msg.button_state = spice.mouse.buttonState;
|
|
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
bool spice_mouse_release(uint32_t button)
|
|
{
|
|
DEBUG_MOUSE("%u", button);
|
|
if (!spice.scInputs.connected)
|
|
{
|
|
DEBUG_ERROR("not connected");
|
|
return false;
|
|
}
|
|
|
|
switch(button)
|
|
{
|
|
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
|
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
|
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
|
}
|
|
|
|
SpiceMsgcMouseRelease msg;
|
|
msg.button = button;
|
|
msg.button_state = spice.mouse.buttonState;
|
|
|
|
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
|
|
} |