diff --git a/.gitmodules b/.gitmodules index 2d2157c2..b82c6579 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "LGMP"] - path = LGMP + path = repos/LGMP url = https://github.com/gnif/LGMP.git +[submodule "repos/PureSpice"] + path = repos/PureSpice + url = https://github.com/gnif/PureSpice diff --git a/VERSION b/VERSION index 31c8b6fc..0a20d91c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -B1-140-gcc2c49644d+1 \ No newline at end of file +B1-147-g0ca760fad6+1 \ No newline at end of file diff --git a/c-host/CMakeLists.txt b/c-host/CMakeLists.txt index 2b76ad81..47d9f6fa 100644 --- a/c-host/CMakeLists.txt +++ b/c-host/CMakeLists.txt @@ -52,8 +52,8 @@ set(SOURCES src/app.c ) -add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") -add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" ) +add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") +add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" ) add_subdirectory(platform) if(WIN32) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ef504379..ddae7b91 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -88,10 +88,10 @@ set(SOURCES src/utils.c ) -add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") -add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" ) +add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) +add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) +add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice") -add_subdirectory(spice) add_subdirectory(renderers) add_subdirectory(clipboards) add_subdirectory(fonts) @@ -103,7 +103,7 @@ target_link_libraries(looking-glass-client ${EXE_FLAGS} lg_common lgmp - spice + purespice renderers clipboards fonts diff --git a/client/spice/CMakeLists.txt b/client/spice/CMakeLists.txt deleted file mode 100644 index 405b64cc..00000000 --- a/client/spice/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(spice LANGUAGES C) -set(CMAKE_C_STANDARD 11) - -find_package(PkgConfig) -pkg_check_modules(SPICE_PKGCONFIG REQUIRED - spice-protocol - nettle - hogweed -) - -add_definitions(-D USE_NETTLE) - -add_library(spice STATIC - src/spice.c - src/rsa.c -) - -target_link_libraries(spice - lg_common - ${SPICE_PKGCONFIG_LIBRARIES} - gmp -) - -target_include_directories(spice - PUBLIC - $ - $ - PRIVATE - src - ${SPICE_PKGCONFIG_INCLUDE_DIRS} -) diff --git a/client/spice/include/spice/spice.h b/client/spice/include/spice/spice.h deleted file mode 100644 index 07b5f732..00000000 --- a/client/spice/include/spice/spice.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2019 Geoffrey McRae -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 -#include -#include - -typedef enum SpiceDataType -{ - SPICE_DATA_TEXT, - SPICE_DATA_PNG, - SPICE_DATA_BMP, - SPICE_DATA_TIFF, - SPICE_DATA_JPEG, - - SPICE_DATA_NONE -} -SpiceDataType; - -typedef void (*SpiceClipboardNotice )(const SpiceDataType type); -typedef void (*SpiceClipboardData )(const SpiceDataType type, uint8_t * buffer, uint32_t size); -typedef void (*SpiceClipboardRelease)(); -typedef void (*SpiceClipboardRequest)(const SpiceDataType type); - -bool spice_connect(const char * host, const unsigned short port, const char * password); -void spice_disconnect(); -bool spice_process(int timeout); -bool spice_ready(); - -bool spice_key_down (uint32_t code); -bool spice_key_up (uint32_t code); -bool spice_mouse_mode (bool server); -bool spice_mouse_position(uint32_t x, uint32_t y); -bool spice_mouse_motion ( int32_t x, int32_t y); -bool spice_mouse_press (uint32_t button); -bool spice_mouse_release (uint32_t button); - -bool spice_clipboard_request(SpiceDataType type); -bool spice_clipboard_grab (SpiceDataType type); -bool spice_clipboard_release(); -bool spice_clipboard_data (SpiceDataType type, uint8_t * data, size_t size); - -/* events */ -bool spice_set_clipboard_cb( - SpiceClipboardNotice cbNoticeFn, - SpiceClipboardData cbDataFn, - SpiceClipboardRelease cbReleaseFn, - SpiceClipboardRequest cbRequestFn); diff --git a/client/spice/src/messages.h b/client/spice/src/messages.h deleted file mode 100644 index 3a3a6210..00000000 --- a/client/spice/src/messages.h +++ /dev/null @@ -1,146 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2019 Geoffrey McRae -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 - -#pragma pack(push,1) - -typedef struct SpicePoint16 -{ - int16_t x, y; -} -SpicePoint16; - -typedef struct SpiceMsgMainInit -{ - uint32_t session_id; - uint32_t display_channels_hint; - uint32_t supported_mouse_modes; - uint32_t current_mouse_mode; - uint32_t agent_connected; - uint32_t agent_tokens; - uint32_t multi_media_time; - uint32_t ram_hint; -} -SpiceMsgMainInit; - -typedef struct SpiceChannelID -{ - uint8_t type; - uint8_t channel_id; -} -SpiceChannelID; - -typedef struct SpiceMsgMainChannelsList -{ - uint32_t num_of_channels; - //SpiceChannelID channels[num_of_channels] -} -SpiceMainChannelsList; - -typedef struct SpiceMsgcMainMouseModeRequest -{ - uint16_t mouse_mode; -} -SpiceMsgcMainMouseModeRequest; - -typedef struct SpiceMsgPing -{ - uint32_t id; - uint64_t timestamp; -} -SpiceMsgPing, -SpiceMsgcPong; - -typedef struct SpiceMsgSetAck -{ - uint32_t generation; - uint32_t window; -} -SpiceMsgSetAck; - -typedef struct SpiceMsgcAckSync -{ - uint32_t generation; -} -SpiceMsgcAckSync; - -typedef struct SpiceMsgNotify -{ - uint64_t time_stamp; - uint32_t severity; - uint32_t visibility; - uint32_t what; - uint32_t message_len; - //char message[message_len+1] -} -SpiceMsgNotify; - -typedef struct SpiceMsgInputsInit -{ - uint16_t modifiers; -} -SpiceMsgInputsInit, -SpiceMsgInputsKeyModifiers, -SpiceMsgcInputsKeyModifiers; - -typedef struct SpiceMsgcKeyDown -{ - uint32_t code; -} -SpiceMsgcKeyDown, -SpiceMsgcKeyUp; - -typedef struct SpiceMsgcMousePosition -{ - uint32_t x; - uint32_t y; - uint16_t button_state; - uint8_t display_id; -} -SpiceMsgcMousePosition; - -typedef struct SpiceMsgcMouseMotion -{ - int32_t x; - int32_t y; - uint16_t button_state; -} -SpiceMsgcMouseMotion; - -typedef struct SpiceMsgcMousePress -{ - uint8_t button; - uint16_t button_state; -} -SpiceMsgcMousePress, -SpiceMsgcMouseRelease; - - -// spice is missing these defines, the offical reference library incorrectly uses the VD defines -#define COMMON_CAPS_BYTES (((SPICE_COMMON_CAP_MINI_HEADER + 32) / 8) & ~3) -#define COMMON_SET_CAPABILITY(caps, index) \ - { (caps)[(index) / 32] |= (1 << ((index) % 32)); } - -#define MAIN_CAPS_BYTES (((SPICE_MAIN_CAP_SEAMLESS_MIGRATE + 32) / 8) & ~3) -#define MAIN_SET_CAPABILITY(caps, index) \ - { (caps)[(index) / 32] |= (1 << ((index) % 32)); } - - -#pragma pack(pop) \ No newline at end of file diff --git a/client/spice/src/rsa.c b/client/spice/src/rsa.c deleted file mode 100644 index 7ee02d4c..00000000 --- a/client/spice/src/rsa.c +++ /dev/null @@ -1,227 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2019 Geoffrey McRae -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 "rsa.h" -#include "common/debug.h" - -#include -#include -#include - -#if defined(USE_OPENSSL) && defined(USE_NETTLE) - #error "USE_OPENSSL and USE_NETTLE are both defined" -#elif !defined(USE_OPENSSL) && !defined(USE_NETTLE) - #error "One of USE_OPENSSL or USE_NETTLE must be defined" -#endif - -#if defined(USE_OPENSSL) -#include -#include -#include -#endif - -#if defined(USE_NETTLE) -#include -#include -#include -#include -#include -#include - -#define SHA1_HASH_LEN 20 -#endif - -#if defined(USE_NETTLE) -/* the below OAEP implementation is derived from the FreeTDS project */ -static void memxor(uint8_t * a, const uint8_t * b, const unsigned int len) -{ - for(unsigned int i = 0; i < len; ++i) - a[i] = a[i] ^ b[i]; -} - -static void sha1(uint8_t * hash, const uint8_t *data, unsigned int len) -{ - struct sha1_ctx ctx; - - sha1_init(&ctx); - sha1_update(&ctx, len, data); - sha1_digest(&ctx, SHA1_HASH_LEN, hash); -} - -static void oaep_mask(uint8_t * dest, size_t dest_len, const uint8_t * mask, size_t mask_len) -{ - uint8_t hash[SHA1_HASH_LEN]; - uint8_t seed[mask_len + 4 ]; - memcpy(seed, mask, mask_len); - - for(unsigned int n = 0;; ++n) - { - (seed+mask_len)[0] = n >> 24; - (seed+mask_len)[1] = n >> 16; - (seed+mask_len)[2] = n >> 8; - (seed+mask_len)[3] = n >> 0; - - sha1(hash, seed, sizeof(seed)); - if (dest_len <= SHA1_HASH_LEN) - { - memxor(dest, hash, dest_len); - break; - } - - memxor(dest, hash, SHA1_HASH_LEN); - dest += SHA1_HASH_LEN; - dest_len -= SHA1_HASH_LEN; - } -} - -static bool oaep_pad(mpz_t m, unsigned int key_size, const uint8_t * message, unsigned int len) -{ - if (len + SHA1_HASH_LEN * 2 + 2 > key_size) - { - DEBUG_ERROR("Message too long"); - return false; - } - - struct - { - uint8_t all[1]; - uint8_t ros[SHA1_HASH_LEN]; - uint8_t db [key_size - SHA1_HASH_LEN - 1]; - } em; - - memset(&em, 0, sizeof(em)); - sha1(em.db, (uint8_t *)"", 0); - em.all[key_size - len - 1] = 0x1; - memcpy(em.all + (key_size - len), message, len); - - /* we are not too worried about randomness since we are just making a local - * connection, should anyone use this code outside of LookingGlass please be - * sure to use something better such as `gnutls_rnd` */ - for(int i = 0; i < SHA1_HASH_LEN; ++i) - em.ros[i] = rand() % 255; - - const int db_len = key_size - SHA1_HASH_LEN - 1; - oaep_mask(em.db , db_len , em.ros, SHA1_HASH_LEN); - oaep_mask(em.ros, SHA1_HASH_LEN, em.db , db_len ); - - nettle_mpz_set_str_256_u(m, key_size, em.all); - return true; -} -#endif - -bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result) -{ - result->size = 0; - result->data = NULL; - -#if defined(USE_OPENSSL) - BIO *bioKey = BIO_new(BIO_s_mem()); - if (!bioKey) - { - DEBUG_ERROR("failed to allocate bioKey"); - return false; - } - - BIO_write(bioKey, pub_key, SPICE_TICKET_PUBKEY_BYTES); - EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL); - RSA *rsa = EVP_PKEY_get1_RSA(rsaKey); - - result->size = RSA_size(rsa); - result->data = (char *)malloc(result->size); - - if (RSA_public_encrypt( - strlen(password) + 1, - (uint8_t*)password, - (uint8_t*)result->data, - rsa, - RSA_PKCS1_OAEP_PADDING - ) <= 0) - { - free(result->data); - result->size = 0; - result->data = NULL; - - DEBUG_ERROR("rsa public encrypt failed"); - EVP_PKEY_free(rsaKey); - BIO_free(bioKey); - return false; - } - - EVP_PKEY_free(rsaKey); - BIO_free(bioKey); - return true; -#endif - -#if defined(USE_NETTLE) - struct asn1_der_iterator der; - struct asn1_der_iterator j; - struct rsa_public_key pub; - - if (asn1_der_iterator_first(&der, SPICE_TICKET_PUBKEY_BYTES, pub_key) == ASN1_ITERATOR_CONSTRUCTED - && der.type == ASN1_SEQUENCE - && asn1_der_decode_constructed_last(&der) == ASN1_ITERATOR_CONSTRUCTED - && der.type == ASN1_SEQUENCE - && asn1_der_decode_constructed(&der, &j) == ASN1_ITERATOR_PRIMITIVE - && j.type == ASN1_IDENTIFIER - && asn1_der_iterator_next(&der) == ASN1_ITERATOR_PRIMITIVE - && der.type == ASN1_BITSTRING - && asn1_der_decode_bitstring_last(&der)) - { - if (j.length != 9) - { - DEBUG_ERROR("Invalid key, not RSA"); - return false; - } - - if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE - && j.type == ASN1_NULL - && j.length == 0 - && asn1_der_iterator_next(&j) == ASN1_ITERATOR_END) - { - rsa_public_key_init(&pub); - if (!rsa_public_key_from_der_iterator(&pub, 0, &der)) - { - DEBUG_ERROR("Unable to load public key from DER iterator"); - rsa_public_key_clear(&pub); - return false; - } - } - } - - mpz_t p; - mpz_init(p); - oaep_pad(p, pub.size, (uint8_t *)password, strlen(password)+1); - mpz_powm(p, p, pub.e, pub.n); - - result->size = pub.size; - result->data = malloc(pub.size); - nettle_mpz_get_str_256(pub.size, (uint8_t *)result->data, p); - - rsa_public_key_clear(&pub); - mpz_clear(p); - return true; -#endif -} - -void spice_rsa_free_password(struct spice_password * pass) -{ - free(pass->data); - pass->size = 0; - pass->data = NULL; -} \ No newline at end of file diff --git a/client/spice/src/rsa.h b/client/spice/src/rsa.h deleted file mode 100644 index 8e8a2ec9..00000000 --- a/client/spice/src/rsa.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2019 Geoffrey McRae -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 -#include - -struct spice_password -{ - char * data; - unsigned int size; -}; - -bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result); -void spice_rsa_free_password(struct spice_password * pass); \ No newline at end of file diff --git a/client/spice/src/spice.c b/client/spice/src/spice.c deleted file mode 100644 index 0f189da5..00000000 --- a/client/spice/src/spice.c +++ /dev/null @@ -1,1665 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2019 Geoffrey McRae -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/spice.h" -#include "common/debug.h" -#include "common/locking.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "messages.h" -#include "rsa.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 - -#ifdef DEBUG_SPICE_CLIPBOARD - #define DEBUG_CLIPBOARD(fmt, args...) DEBUG_PRINT("[C]", fmt, ##args) -#else - #define DEBUG_CLIPBOARD(fmt, args...) do {} while(0) -#endif - -// we don't really need flow control because we are all local -// instead do what the spice-gtk library does and provide the largest -// possible number -#define SPICE_AGENT_TOKENS_MAX ~0 - -#define SPICE_PACKET(htype, payloadType, extraData) \ -({ \ - uint8_t packet[sizeof(ssize_t) + sizeof(SpiceMiniDataHeader) + sizeof(payloadType)]; \ - ssize_t * sz = (ssize_t*)packet; \ - SpiceMiniDataHeader * header = (SpiceMiniDataHeader *)(packet + sizeof(ssize_t)); \ - *sz = sizeof(SpiceMiniDataHeader) + sizeof(payloadType); \ - header->type = (htype); \ - header->size = sizeof(payloadType) + extraData; \ - (payloadType *)(header + 1); \ -}) - -/* only the main channel requires locking due to the agent needing to send - * multiple packets, all other channels are atomic */ -#define SPICE_SEND_PACKET_LOCKED(channel, packet) \ -({ \ - SpiceMiniDataHeader * header = (SpiceMiniDataHeader *)(((uint8_t *)packet) - \ - sizeof(SpiceMiniDataHeader)); \ - ssize_t *sz = (ssize_t *)(((uint8_t *)header) - sizeof(ssize_t)); \ - LG_LOCK((channel)->lock); \ - const ssize_t wrote = spice_write_nl((channel), header, *sz); \ - LG_UNLOCK((channel)->lock); \ - wrote == *sz; \ -}) - -#define SPICE_SEND_PACKET(channel, packet) \ -({ \ - SpiceMiniDataHeader * header = (SpiceMiniDataHeader *)(((uint8_t *)packet) - \ - sizeof(SpiceMiniDataHeader)); \ - ssize_t *sz = (ssize_t *)(((uint8_t *)header) - sizeof(ssize_t)); \ - const ssize_t wrote = spice_write_nl((channel), header, *sz); \ - wrote == *sz; \ -}) - -// ============================================================================ - -// internal structures -struct SpiceChannel -{ - bool connected; - bool ready; - bool initDone; - uint8_t channelType; - int socket; - uint32_t ackFrequency; - uint32_t ackCount; - LG_Lock lock; -}; - -struct SpiceKeyboard -{ - uint32_t modifiers; -}; - -struct SpiceMouse -{ - uint32_t buttonState; - - int sentCount; - int rpos, wpos; -}; - -union SpiceAddr -{ - struct sockaddr addr; - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_un un; -}; - -struct Spice -{ - char password[32]; - short family; - union SpiceAddr addr; - - bool hasAgent; - uint32_t serverTokens; - uint32_t sessionID; - uint32_t channelID; - struct SpiceChannel scMain; - struct SpiceChannel scInputs; - - struct SpiceKeyboard kb; - struct SpiceMouse mouse; - - bool cbSupported; - bool cbSelection; - - // clipboard variables - bool cbAgentGrabbed; - bool cbClientGrabbed; - SpiceDataType cbType; - uint8_t * cbBuffer; - uint32_t cbRemain; - uint32_t cbSize; - SpiceClipboardNotice cbNoticeFn; - SpiceClipboardData cbDataFn; - SpiceClipboardRelease cbReleaseFn; - SpiceClipboardRequest cbRequestFn; -}; - -// 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, SpiceMiniDataHeader * header, bool * handled); -bool spice_on_main_channel_read (); -bool spice_on_inputs_channel_read(); - -bool spice_agent_process (uint32_t dataSize); -bool spice_agent_connect (); -bool spice_agent_send_caps(bool request); -void spice_agent_on_clipboard(); - -// utility functions -static uint32_t spice_type_to_agent_type(SpiceDataType type); -static SpiceDataType agent_type_to_spice_type(uint32_t type); - -// thread safe read/write methods -bool spice_agent_write_msg (uint32_t type, const void * buffer, ssize_t size); - -// non thread safe read/write methods (nl = non-locking) -bool spice_read_nl (const struct SpiceChannel * channel, void * buffer, const ssize_t size); -ssize_t spice_write_nl (const struct SpiceChannel * channel, const void * buffer, const ssize_t size); -bool spice_discard_nl(const struct SpiceChannel * channel, ssize_t size); - -// ============================================================================ - -bool spice_connect(const char * host, const unsigned short port, const char * password) -{ - strncpy(spice.password, password, sizeof(spice.password) - 1); - memset(&spice.addr, 0, sizeof(spice.addr)); - - if (port == 0) - { - spice.family = AF_UNIX; - spice.addr.un.sun_family = spice.family; - strncpy(spice.addr.un.sun_path, host, sizeof(spice.addr.un.sun_path) - 1); - DEBUG_INFO("Remote: %s", host); - } - else - { - spice.family = AF_INET; - inet_pton(spice.family, host, &spice.addr.in.sin_addr); - spice.addr.in.sin_family = spice.family; - spice.addr.in.sin_port = htons(port); - DEBUG_INFO("Remote: %s:%u", host, port); - } - - 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); - - spice.sessionID = 0; - - if (spice.cbBuffer) - free(spice.cbBuffer); - spice.cbBuffer = NULL; - spice.cbRemain = 0; - spice.cbSize = 0; - - spice.cbAgentGrabbed = false; - spice.cbClientGrabbed = false; -} - -// ============================================================================ - -bool spice_ready() -{ - return spice.scMain.connected && - spice.scInputs.connected; -} - -// ============================================================================ - -bool spice_process(int timeout) -{ - fd_set readSet; - FD_ZERO(&readSet); - FD_SET(spice.scMain.socket , &readSet); - FD_SET(spice.scInputs.socket, &readSet); - - struct timeval tv; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - - int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &tv); - 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; - - char * ack = SPICE_PACKET(SPICE_MSGC_ACK, char, 0); - *ack = 0; - return SPICE_SEND_PACKET(channel, ack); -} - -// ============================================================================ - -bool spice_on_common_read(struct SpiceChannel * channel, SpiceMiniDataHeader * header, bool * handled) -{ - *handled = false; - if (!spice_read_nl(channel, header, sizeof(SpiceMiniDataHeader))) - { - DEBUG_ERROR("read failure"); - return false; - } - -//#if 0 - DEBUG_PROTO("socket: %d, type: %2u, size %6u", - channel->socket, header->type, header->size); -//#endif - - if (!channel->initDone) - 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_nl(channel, &in, sizeof(in))) - return false; - - channel->ackFrequency = in.window; - - SpiceMsgcAckSync * out = - SPICE_PACKET(SPICE_MSGC_ACK_SYNC, SpiceMsgcAckSync, 0); - - out->generation = in.generation; - return SPICE_SEND_PACKET_LOCKED(channel, out); - } - - case SPICE_MSG_PING: - { - DEBUG_PROTO("SPICE_MSG_PING"); - *handled = true; - - SpiceMsgPing in; - if (!spice_read_nl(channel, &in, sizeof(in))) - return false; - - const int discard = header->size - sizeof(in); - if (!spice_discard_nl(channel, discard)) - { - DEBUG_ERROR("failed discarding enough bytes (%d) from the ping packet", discard); - return false; - } - else - DEBUG_PROTO("discard %d", discard); - - SpiceMsgcPong * out = - SPICE_PACKET(SPICE_MSGC_PONG, SpiceMsgcPong, 0); - - out->id = in.id; - out->timestamp = in.timestamp; - return SPICE_SEND_PACKET_LOCKED(channel, out); - } - - 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_nl(channel, &in, sizeof(in))) - return false; - - char msg[in.message_len+1]; - if (!spice_read_nl(channel, msg, in.message_len+1)) - return false; - - DEBUG_INFO("notify message: %s", msg); - *handled = true; - return true; - } - } - - return true; -} - -// ============================================================================ - -bool spice_on_main_channel_read() -{ - struct SpiceChannel *channel = &spice.scMain; - - SpiceMiniDataHeader 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_nl(channel, &msg, sizeof(msg))) - { - spice_disconnect(); - return false; - } - - spice.sessionID = msg.session_id; - - spice.serverTokens = msg.agent_tokens; - spice.hasAgent = msg.agent_connected; - if (spice.hasAgent && !spice_agent_connect()) - { - spice_disconnect(); - DEBUG_ERROR("failed to connect to spice agent"); - return false; - } - - if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false)) - { - DEBUG_ERROR("failed to set mouse mode"); - return false; - } - - void * packet = SPICE_PACKET(SPICE_MSGC_MAIN_ATTACH_CHANNELS, void, 0); - if (!SPICE_SEND_PACKET_LOCKED(channel, packet)) - { - spice_disconnect(); - DEBUG_ERROR("failed to ask for channel list"); - return false; - } - - return true; - } - - if (header.type == SPICE_MSG_MAIN_CHANNELS_LIST) - { - DEBUG_PROTO("SPICE_MSG_MAIN_CHANNELS_LIST"); - - SpiceMainChannelsList msg; - if (!spice_read_nl(channel, &msg, sizeof(msg))) - { - DEBUG_ERROR("Failed to read channel list msg"); - spice_disconnect(); - return false; - } - - // documentation doesn't state that the array is null terminated but it seems that it is - SpiceChannelID channels[msg.num_of_channels]; - if (!spice_read_nl(channel, &channels, msg.num_of_channels * sizeof(SpiceChannelID))) - { - DEBUG_ERROR("Failed to read channel list vector"); - spice_disconnect(); - return false; - } - - for(int i = 0; i < msg.num_of_channels; ++i) - { - DEBUG_PROTO("channel %d = %u", i, channels[i].type); - if (channels[i].type == SPICE_CHANNEL_INPUTS) - { - if (spice.scInputs.connected) - { - DEBUG_ERROR("inputs channel already connected"); - spice_disconnect(); - return false; - } - - if (!spice_connect_channel(&spice.scInputs)) - { - DEBUG_ERROR("failed to connect inputs channel"); - spice_disconnect(); - return false; - } - } - } - - return true; - } - - if (header.type == SPICE_MSG_MAIN_AGENT_CONNECTED) - { - DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_CONNECTED"); - - spice.hasAgent = true; - if (!spice_agent_connect()) - { - DEBUG_ERROR("failed to connect to spice agent"); - spice_disconnect(); - return false; - } - return true; - } - - if (header.type == SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS) - { - DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS"); - - uint32_t num_tokens; - if (!spice_read_nl(channel, &num_tokens, sizeof(num_tokens))) - { - DEBUG_ERROR("failed to read agent tokens"); - spice_disconnect(); - return false; - } - - spice.hasAgent = true; - spice.serverTokens = num_tokens; - if (!spice_agent_connect()) - { - DEBUG_ERROR("failed to connect to spice agent"); - spice_disconnect(); - return false; - } - return true; - } - - if (header.type == SPICE_MSG_MAIN_AGENT_DISCONNECTED) - { - DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_DISCONNECTED"); - - uint32_t error; - if (!spice_read_nl(channel, &error, sizeof(error))) - { - DEBUG_ERROR("failed to read agent disconnect error code"); - spice_disconnect(); - return false; - } - - DEBUG_INFO("Spice agent disconnected, error: %u", error); - spice.hasAgent = false; - - if (spice.cbBuffer) - { - free(spice.cbBuffer); - spice.cbBuffer = NULL; - spice.cbSize = 0; - spice.cbRemain = 0; - } - - return true; - } - - if (header.type == SPICE_MSG_MAIN_AGENT_DATA) - { - DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_DATA"); - - if (!spice.hasAgent) - { - DEBUG_WARN("recieved agent data when the agent is yet to be started"); - spice_discard_nl(channel, header.size); - return true; - } - - if (!spice_agent_process(header.size)) - { - DEBUG_ERROR("failed to process spice agent message"); - spice_disconnect(); - return false; - } - return true; - } - - if (header.type == SPICE_MSG_MAIN_AGENT_TOKEN) - { - DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_TOKEN"); - - uint32_t num_tokens; - if (!spice_read_nl(channel, &num_tokens, sizeof(num_tokens))) - { - DEBUG_ERROR("failed to read agent tokens"); - spice_disconnect(); - return false; - } - - spice.serverTokens = num_tokens; - return true; - } - - DEBUG_WARN("main channel unhandled message type %u", header.type); - spice_discard_nl(channel, header.size); - return true; -} - -// ============================================================================ - -bool spice_on_inputs_channel_read() -{ - struct SpiceChannel *channel = &spice.scInputs; - - SpiceMiniDataHeader 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_nl(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_nl(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"); - const int count = __sync_add_and_fetch(&spice.mouse.sentCount, SPICE_INPUT_MOTION_ACK_BUNCH); - if (count < 0) - { - DEBUG_ERROR("comms failure, too many mouse motion ACKs recieved"); - return false; - } - return true; - } - } - - DEBUG_WARN("inputs channel unhandled message type %u", header.type); - spice_discard_nl(channel, header.size); - return true; -} - -// ============================================================================ - -bool spice_connect_channel(struct SpiceChannel * channel) -{ - channel->initDone = false; - channel->ackFrequency = 0; - channel->ackCount = 0; - - LG_LOCK_INIT(channel->lock); - - size_t addrSize; - switch(spice.family) - { - case AF_UNIX: - addrSize = sizeof(spice.addr.un); - break; - - case AF_INET: - addrSize = sizeof(spice.addr.in); - break; - - case AF_INET6: - addrSize = sizeof(spice.addr.in6); - break; - - default: - DEBUG_ERROR("Unsupported socket family"); - return false; - } - - channel->socket = socket(spice.family, SOCK_STREAM, 0); - if (channel->socket == -1) - return false; - - if (spice.family != AF_UNIX) - { - const int flag = 1; - setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY , &flag, sizeof(int)); - setsockopt(channel->socket, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(int)); - } - - if (connect(channel->socket, &spice.addr.addr, addrSize) == -1) - { - DEBUG_ERROR("socket connect failure"); - close(channel->socket); - return false; - } - channel->connected = true; - - typedef struct - { - SpiceLinkHeader header; - SpiceLinkMess message; - uint32_t supportCaps[COMMON_CAPS_BYTES / sizeof(uint32_t)]; - uint32_t channelCaps[MAIN_CAPS_BYTES / sizeof(uint32_t)]; - } - __attribute__((packed)) ConnectPacket; - - ConnectPacket p = - { - .header = { - .magic = SPICE_MAGIC , - .major_version = SPICE_VERSION_MAJOR, - .minor_version = SPICE_VERSION_MINOR, - .size = sizeof(ConnectPacket) - sizeof(SpiceLinkHeader) - }, - .message = { - .connection_id = spice.sessionID, - .channel_type = channel->channelType, - .channel_id = spice.channelID, - .num_common_caps = COMMON_CAPS_BYTES / sizeof(uint32_t), - .num_channel_caps = MAIN_CAPS_BYTES / sizeof(uint32_t), - .caps_offset = sizeof(SpiceLinkMess) - } - }; - - COMMON_SET_CAPABILITY(p.supportCaps, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION); - COMMON_SET_CAPABILITY(p.supportCaps, SPICE_COMMON_CAP_AUTH_SPICE ); - COMMON_SET_CAPABILITY(p.supportCaps, SPICE_COMMON_CAP_MINI_HEADER ); - - if (channel == &spice.scMain) - MAIN_SET_CAPABILITY(p.channelCaps, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS); - - if (!spice_write_nl(channel, &p, sizeof(p))) - { - DEBUG_ERROR("failed to write the initial payload"); - spice_disconnect_channel(channel); - return false; - } - - if (!spice_read_nl(channel, &p.header, sizeof(p.header))) - { - DEBUG_ERROR("failed to read SpiceLinkHeader"); - spice_disconnect_channel(channel); - return false; - } - - if (p.header.magic != SPICE_MAGIC || - p.header.major_version != SPICE_VERSION_MAJOR) - { - DEBUG_ERROR("invalid or unsupported protocol version"); - spice_disconnect_channel(channel); - return false; - } - - if (p.header.size < sizeof(SpiceLinkReply)) - { - DEBUG_ERROR("reported data size too small"); - spice_disconnect_channel(channel); - return false; - } - - SpiceLinkReply reply; - if (!spice_read_nl(channel, &reply, sizeof(reply))) - { - DEBUG_ERROR("failed to read SpiceLinkReply"); - spice_disconnect_channel(channel); - return false; - } - - if (reply.error != SPICE_LINK_ERR_OK) - { - 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]; - if ( - !spice_read_nl(channel, &capsCommon , sizeof(capsCommon)) || - !spice_read_nl(channel, &capsChannel, sizeof(capsChannel)) - ) - { - DEBUG_ERROR("failed to read the capabilities"); - spice_disconnect_channel(channel); - return false; - } - - SpiceLinkAuthMechanism auth; - auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE; - if (!spice_write_nl(channel, &auth, sizeof(auth))) - { - DEBUG_ERROR("failed to write the auth mechanism"); - spice_disconnect_channel(channel); - return false; - } - - struct spice_password pass; - if (!spice_rsa_encrypt_password(reply.pub_key, spice.password, &pass)) - { - DEBUG_ERROR("failed to encrypt the password"); - spice_disconnect_channel(channel); - return false; - } - - if (!spice_write_nl(channel, pass.data, pass.size)) - { - spice_rsa_free_password(&pass); - DEBUG_ERROR("failed to write encrypted data"); - spice_disconnect_channel(channel); - return false; - } - - spice_rsa_free_password(&pass); - - uint32_t linkResult; - if (!spice_read_nl(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; - } - - channel->ready = true; - return true; -} - -// ============================================================================ - -void spice_disconnect_channel(struct SpiceChannel * channel) -{ - if (channel->connected) - { - shutdown(channel->socket, SHUT_WR); - - char buffer[1024]; - ssize_t len = 0; - do - len = read(channel->socket, buffer, sizeof(buffer)); - while(len > 0); - - close(channel->socket); - } - channel->connected = false; - LG_LOCK_FREE(channel->lock); -} - -// ============================================================================ - -bool spice_agent_connect() -{ - DEBUG_INFO("Spice agent available, sending start"); - - uint32_t * packet = SPICE_PACKET(SPICE_MSGC_MAIN_AGENT_START, uint32_t, 0); - *packet = SPICE_AGENT_TOKENS_MAX; - if (!SPICE_SEND_PACKET_LOCKED(&spice.scMain, packet)) - { - DEBUG_ERROR("failed to send agent start message"); - return false; - } - - if (!spice_agent_send_caps(true)) - return false; - - return true; -} - -// ============================================================================ - -bool spice_agent_process(uint32_t dataSize) -{ - if (spice.cbRemain) - { - const uint32_t r = spice.cbRemain > dataSize ? dataSize : spice.cbRemain; - if (!spice_read_nl(&spice.scMain, spice.cbBuffer + spice.cbSize, r)) - { - DEBUG_ERROR("failed to read the clipboard data"); - free(spice.cbBuffer); - spice.cbBuffer = NULL; - spice.cbRemain = 0; - spice.cbSize = 0; - return false; - } - - spice.cbRemain -= r; - spice.cbSize += r; - - if (spice.cbRemain == 0) - spice_agent_on_clipboard(); - - return true; - } - - VDAgentMessage msg; - - #pragma pack(push,1) - struct Selection - { - uint8_t selection; - uint8_t reserved[3]; - }; - #pragma pack(pop) - - if (!spice_read_nl(&spice.scMain, &msg, sizeof(msg))) - { - DEBUG_ERROR("failed to read spice agent message"); - return false; - } - dataSize -= sizeof(msg); - - if (msg.protocol != VD_AGENT_PROTOCOL) - { - DEBUG_ERROR("invalid or unknown spice agent protocol"); - return false; - } - - switch(msg.type) - { - case VD_AGENT_ANNOUNCE_CAPABILITIES: - { - VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)malloc(msg.size); - memset(caps, 0, msg.size); - - if (!spice_read_nl(&spice.scMain, caps, msg.size)) - { - DEBUG_ERROR("failed to read agent message payload"); - free(caps); - return false; - } - - const int capsSize = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg.size); - spice.cbSupported = VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND) || - VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_SELECTION); - spice.cbSelection = VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_SELECTION); - - if (spice.cbSupported) - DEBUG_INFO("clipboard capability detected"); - - if (caps->request && !spice_agent_send_caps(false)) - { - free(caps); - return false; - } - - free(caps); - return true; - } - - case VD_AGENT_CLIPBOARD: - case VD_AGENT_CLIPBOARD_REQUEST: - case VD_AGENT_CLIPBOARD_GRAB: - case VD_AGENT_CLIPBOARD_RELEASE: - { - uint32_t remaining = msg.size; - if (spice.cbSelection) - { - struct Selection selection; - if (!spice_read_nl(&spice.scMain, &selection, sizeof(selection))) - { - DEBUG_ERROR("failed to read the clipboard selection"); - return false; - } - remaining -= sizeof(selection); - dataSize -= sizeof(selection); - } - - if (msg.type == VD_AGENT_CLIPBOARD_RELEASE) - { - DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_RELEASE"); - spice.cbAgentGrabbed = false; - if (spice.cbReleaseFn) - spice.cbReleaseFn(); - return true; - } - - if (msg.type == VD_AGENT_CLIPBOARD || msg.type == VD_AGENT_CLIPBOARD_REQUEST) - { - uint32_t type; - if (!spice_read_nl(&spice.scMain, &type, sizeof(type))) - { - DEBUG_ERROR("failed to read the clipboard data type"); - return false; - } - remaining -= sizeof(type); - dataSize -= sizeof(type); - - if (msg.type == VD_AGENT_CLIPBOARD) - { - DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD"); - if (spice.cbBuffer) - { - DEBUG_ERROR("cbBuffer was never freed"); - return false; - } - - spice.cbSize = 0; - spice.cbRemain = remaining; - spice.cbBuffer = (uint8_t *)malloc(remaining); - const uint32_t r = remaining > dataSize ? dataSize : remaining; - - if (!spice_read_nl(&spice.scMain, spice.cbBuffer, r)) - { - DEBUG_ERROR("failed to read the clipboard data"); - free(spice.cbBuffer); - spice.cbBuffer = NULL; - spice.cbRemain = 0; - spice.cbSize = 0; - return false; - } - - spice.cbRemain -= r; - spice.cbSize += r; - - if (spice.cbRemain == 0) - spice_agent_on_clipboard(); - - return true; - } - else - { - DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_REQUEST"); - if (spice.cbRequestFn) - spice.cbRequestFn(agent_type_to_spice_type(type)); - return true; - } - } - else - { - DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_GRAB"); - if (remaining == 0) - return true; - - uint32_t *types = malloc(remaining); - if (!spice_read_nl(&spice.scMain, types, remaining)) - { - DEBUG_ERROR("failed to read the clipboard grab types"); - return false; - } - - // there is zero documentation on the types field, it might be a bitfield - // but for now we are going to assume it's not. - - spice.cbType = agent_type_to_spice_type(types[0]); - spice.cbAgentGrabbed = true; - spice.cbClientGrabbed = false; - if (spice.cbSelection) - { - // Windows doesnt support this, so until it's needed there is no point messing with it - DEBUG_ERROR("Fixme!"); - return false; - } - - if (spice.cbNoticeFn) - spice.cbNoticeFn(spice.cbType); - - free(types); - return true; - } - } - } - - DEBUG_WARN("unknown agent message type %d", msg.type); - spice_discard_nl(&spice.scMain, msg.size); - return true; -} - - -// ============================================================================ - -void spice_agent_on_clipboard() -{ - if (spice.cbDataFn) - spice.cbDataFn(spice.cbType, spice.cbBuffer, spice.cbSize); - - free(spice.cbBuffer); - spice.cbBuffer = NULL; - spice.cbSize = 0; - spice.cbRemain = 0; -} - -// ============================================================================ - -bool spice_agent_send_caps(bool request) -{ - const ssize_t capsSize = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES; - VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)malloc(capsSize); - memset(caps, 0, capsSize); - - caps->request = request ? 1 : 0; - VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); - VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION); - - if (!spice_agent_write_msg(VD_AGENT_ANNOUNCE_CAPABILITIES, caps, capsSize)) - { - DEBUG_ERROR("failed to send agent capabilities"); - free(caps); - return false; - } - free(caps); - - return true; -} - -// ============================================================================ - -bool spice_agent_write_msg(uint32_t type, const void * buffer, ssize_t size) -{ - uint8_t * buf = (uint8_t *)buffer; - ssize_t toWrite = size > VD_AGENT_MAX_DATA_SIZE - sizeof(VDAgentMessage) ? - VD_AGENT_MAX_DATA_SIZE - sizeof(VDAgentMessage) : size; - - VDAgentMessage * msg = - SPICE_PACKET(SPICE_MSGC_MAIN_AGENT_DATA, VDAgentMessage, toWrite); - - msg->protocol = VD_AGENT_PROTOCOL; - msg->type = type; - msg->opaque = 0; - msg->size = size; - - LG_LOCK(spice.scMain.lock); - - if (!SPICE_SEND_PACKET(&spice.scMain, msg)) - { - LG_UNLOCK(spice.scMain.lock); - DEBUG_ERROR("failed to write agent data header"); - return false; - } - - bool first = true; - while(toWrite) - { - bool ok = false; - if (first) - { - ok = spice_write_nl(&spice.scMain, buf, toWrite) == toWrite; - first = false; - } - else - { - void * cont = SPICE_PACKET(SPICE_MSGC_MAIN_AGENT_DATA, void, toWrite); - ok = SPICE_SEND_PACKET(&spice.scMain, cont); - } - - if (!ok) - { - LG_UNLOCK(spice.scMain.lock); - DEBUG_ERROR("failed to write agent data payload"); - return false; - } - - size -= toWrite; - buf += toWrite; - toWrite = size > VD_AGENT_MAX_DATA_SIZE ? VD_AGENT_MAX_DATA_SIZE : size; - } - - LG_UNLOCK(spice.scMain.lock); - return true; -} - -// ============================================================================ - -ssize_t spice_write_nl(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_read_nl(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; - } - - size_t left = size; - uint8_t * buf = (uint8_t *)buffer; - while(left) - { - ssize_t len = read(channel->socket, buf, left); - if (len <= 0) - { - if (len == 0) - DEBUG_ERROR("remote end closd connection after %ld byte(s)", size - left); - return false; - } - left -= len; - buf += len; - } - - return true; -} - -// ============================================================================ - -bool spice_discard_nl(const struct SpiceChannel * channel, ssize_t size) -{ - void *c = malloc(8192); - ssize_t left = size; - while(left) - { - size_t len = read(channel->socket, c, left > 8192 ? 8192 : left); - if (len <= 0) - { - if (len == 0) - DEBUG_ERROR("remote end closed connection after %ld byte(s)", size - left); - free(c); - return false; - } - left -= len; - } - - free(c); - 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 = - SPICE_PACKET(SPICE_MSGC_INPUTS_KEY_DOWN, SpiceMsgcKeyDown, 0); - msg->code = code; - return SPICE_SEND_PACKET(&spice.scInputs, 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); - - SpiceMsgcKeyUp * msg = - SPICE_PACKET(SPICE_MSGC_INPUTS_KEY_UP, SpiceMsgcKeyUp, 0); - msg->code = code; - return SPICE_SEND_PACKET(&spice.scInputs, msg); -} - -// ============================================================================ - -bool spice_mouse_mode(bool server) -{ - DEBUG_MOUSE("%s", server ? "server" : "client"); - if (!spice.scMain.connected) - { - DEBUG_ERROR("not connected"); - return false; - } - - SpiceMsgcMainMouseModeRequest * msg = SPICE_PACKET( - SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, - SpiceMsgcMainMouseModeRequest, 0); - - msg->mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT; - return SPICE_SEND_PACKET_LOCKED(&spice.scMain, 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 = - SPICE_PACKET(SPICE_MSGC_INPUTS_MOUSE_POSITION, SpiceMsgcMousePosition, 0); - - msg->x = x; - msg->y = y; - msg->button_state = spice.mouse.buttonState; - msg->display_id = 0; - - __sync_fetch_and_add(&spice.mouse.sentCount, 1); - return SPICE_SEND_PACKET(&spice.scInputs, 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; - } - - SpiceMsgcMouseMotion * msg = - SPICE_PACKET(SPICE_MSGC_INPUTS_MOUSE_MOTION, SpiceMsgcMouseMotion, 0); - - msg->x = x; - msg->y = y; - msg->button_state = spice.mouse.buttonState; - - __sync_fetch_and_add(&spice.mouse.sentCount, 1); - return SPICE_SEND_PACKET(&spice.scInputs, 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 = - SPICE_PACKET(SPICE_MSGC_INPUTS_MOUSE_PRESS, SpiceMsgcMousePress, 0); - - msg->button = button; - msg->button_state = spice.mouse.buttonState; - - return SPICE_SEND_PACKET(&spice.scInputs, 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 = - SPICE_PACKET(SPICE_MSGC_INPUTS_MOUSE_RELEASE, SpiceMsgcMouseRelease, 0); - - msg->button = button; - msg->button_state = spice.mouse.buttonState; - - return SPICE_SEND_PACKET(&spice.scInputs, msg); -} - -// ============================================================================ - -static uint32_t spice_type_to_agent_type(SpiceDataType type) -{ - switch(type) - { - case SPICE_DATA_TEXT: return VD_AGENT_CLIPBOARD_UTF8_TEXT ; break; - case SPICE_DATA_PNG : return VD_AGENT_CLIPBOARD_IMAGE_PNG ; break; - case SPICE_DATA_BMP : return VD_AGENT_CLIPBOARD_IMAGE_BMP ; break; - case SPICE_DATA_TIFF: return VD_AGENT_CLIPBOARD_IMAGE_TIFF; break; - case SPICE_DATA_JPEG: return VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; - default: - DEBUG_ERROR("unsupported spice data type specified"); - return VD_AGENT_CLIPBOARD_NONE; - } -} - -static SpiceDataType agent_type_to_spice_type(uint32_t type) -{ - switch(type) - { - case VD_AGENT_CLIPBOARD_UTF8_TEXT : return SPICE_DATA_TEXT; break; - case VD_AGENT_CLIPBOARD_IMAGE_PNG : return SPICE_DATA_PNG ; break; - case VD_AGENT_CLIPBOARD_IMAGE_BMP : return SPICE_DATA_BMP ; break; - case VD_AGENT_CLIPBOARD_IMAGE_TIFF: return SPICE_DATA_TIFF; break; - case VD_AGENT_CLIPBOARD_IMAGE_JPG : return SPICE_DATA_JPEG; break; - default: - DEBUG_ERROR("unsupported agent data type specified"); - return SPICE_DATA_NONE; - } -} - -// ============================================================================ - -bool spice_clipboard_request(SpiceDataType type) -{ - VDAgentClipboardRequest req; - - if (!spice.cbAgentGrabbed) - { - DEBUG_ERROR("the agent has not grabbed any data yet"); - return false; - } - - if (type != spice.cbType) - { - DEBUG_ERROR("data type requested doesn't match reported data type"); - return false; - } - - req.type = spice_type_to_agent_type(type); - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_REQUEST, &req, sizeof(req))) - { - DEBUG_ERROR("failed to request clipboard data"); - return false; - } - - return true; -} - -// ============================================================================ - -bool spice_set_clipboard_cb(SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn, SpiceClipboardRelease cbReleaseFn, SpiceClipboardRequest cbRequestFn) -{ - if ((cbNoticeFn && !cbDataFn) || (cbDataFn && !cbNoticeFn)) - { - DEBUG_ERROR("clipboard callback notice and data callbacks must be specified"); - return false; - } - - spice.cbNoticeFn = cbNoticeFn; - spice.cbDataFn = cbDataFn; - spice.cbReleaseFn = cbReleaseFn; - spice.cbRequestFn = cbRequestFn; - - return true; -} - -// ============================================================================ - -bool spice_clipboard_grab(SpiceDataType type) -{ - if (type == SPICE_DATA_NONE) - { - DEBUG_ERROR("grab type is invalid"); - return false; - } - - if (spice.cbSelection) - { - uint8_t req[8] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; - ((uint32_t*)req)[1] = spice_type_to_agent_type(type); - - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, req, sizeof(req))) - { - DEBUG_ERROR("failed to grab the clipboard"); - return false; - } - - spice.cbClientGrabbed = true; - return true; - } - - uint32_t req = spice_type_to_agent_type(type); - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, &req, sizeof(req))) - { - DEBUG_ERROR("failed to grab the clipboard"); - return false; - } - - spice.cbClientGrabbed = true; - return true; -} - -// ============================================================================ - -bool spice_clipboard_release() -{ - // check if if there is anything to release first - if (!spice.cbClientGrabbed) - return true; - - if (spice.cbSelection) - { - uint8_t req[4] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, req, sizeof(req))) - { - DEBUG_ERROR("failed to release the clipboard"); - return false; - } - - spice.cbClientGrabbed = false; - return true; - } - - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, NULL, 0)) - { - DEBUG_ERROR("failed to release the clipboard"); - return false; - } - - spice.cbClientGrabbed = false; - return true; -} - -// ============================================================================ - -bool spice_clipboard_data(SpiceDataType type, uint8_t * data, size_t size) -{ - uint8_t * buffer; - size_t bufSize; - - if (spice.cbSelection) - { - bufSize = 8 + size; - buffer = (uint8_t *)malloc(bufSize); - buffer[0] = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; - buffer[1] = buffer[2] = buffer[3] = 0; - ((uint32_t*)buffer)[1] = spice_type_to_agent_type(type); - memcpy(buffer + 8, data, size); - } - else - { - bufSize = 4 + size; - buffer = (uint8_t *)malloc(bufSize); - ((uint32_t*)buffer)[0] = spice_type_to_agent_type(type); - memcpy(buffer + 4, data, size); - } - - if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD, buffer, bufSize)) - { - DEBUG_ERROR("failed to write the clipboard data"); - free(buffer); - return false; - } - - free(buffer); - return true; -} diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 71ee9f4c..e5858b57 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -59,8 +59,8 @@ set(SOURCES lg.c ) -add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") -add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp") +add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") +add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" ) add_library(looking-glass-obs SHARED ${SOURCES}) target_link_libraries(looking-glass-obs diff --git a/LGMP b/repos/LGMP similarity index 100% rename from LGMP rename to repos/LGMP diff --git a/repos/PureSpice b/repos/PureSpice new file mode 160000 index 00000000..ca4d61aa --- /dev/null +++ b/repos/PureSpice @@ -0,0 +1 @@ +Subproject commit ca4d61aa15bfc3781f25f364fba9b4fd0bf3fbf4