diff options
Diffstat (limited to 'src/lib/client')
| -rw-r--r-- | src/lib/client/CMakeLists.txt | 28 | ||||
| -rw-r--r-- | src/lib/client/Client.cpp | 836 | ||||
| -rw-r--r-- | src/lib/client/Client.h | 227 | ||||
| -rw-r--r-- | src/lib/client/ServerProxy.cpp | 908 | ||||
| -rw-r--r-- | src/lib/client/ServerProxy.h | 133 |
5 files changed, 2132 insertions, 0 deletions
diff --git a/src/lib/client/CMakeLists.txt b/src/lib/client/CMakeLists.txt new file mode 100644 index 0000000..97dc9db --- /dev/null +++ b/src/lib/client/CMakeLists.txt @@ -0,0 +1,28 @@ +# barrier -- mouse and keyboard sharing utility +# Copyright (C) 2012-2016 Symless Ltd. +# Copyright (C) 2009 Nick Bolton +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file LICENSE that should have accompanied this file. +# +# This package 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, see <http://www.gnu.org/licenses/>. + +file(GLOB headers "*.h") +file(GLOB sources "*.cpp") + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +add_library(client STATIC ${sources}) + +if (UNIX) + target_link_libraries(client synlib io) +endif() diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp new file mode 100644 index 0000000..2158ee2 --- /dev/null +++ b/src/lib/client/Client.cpp @@ -0,0 +1,836 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see <http://www.gnu.org/licenses/>. + */ + +#include "client/Client.h" + +#include "client/ServerProxy.h" +#include "barrier/Screen.h" +#include "barrier/FileChunk.h" +#include "barrier/DropHelper.h" +#include "barrier/PacketStreamFilter.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/protocol_types.h" +#include "barrier/XBarrier.h" +#include "barrier/StreamChunker.h" +#include "barrier/IPlatformScreen.h" +#include "mt/Thread.h" +#include "net/TCPSocket.h" +#include "net/IDataSocket.h" +#include "net/ISocketFactory.h" +#include "net/SecureSocket.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" +#include "common/stdexcept.h" + +#include <cstring> +#include <cstdlib> +#include <sstream> +#include <fstream> + +// +// Client +// + +Client::Client( + IEventQueue* events, + const String& name, const NetworkAddress& address, + ISocketFactory* socketFactory, + barrier::Screen* screen, + ClientArgs const& args) : + m_mock(false), + m_name(name), + m_serverAddress(address), + m_socketFactory(socketFactory), + m_screen(screen), + m_stream(NULL), + m_timer(NULL), + m_server(NULL), + m_ready(false), + m_active(false), + m_suspended(false), + m_connectOnResume(false), + m_events(events), + m_sendFileThread(NULL), + m_writeToDropDirThread(NULL), + m_socket(NULL), + m_useSecureNetwork(args.m_enableCrypto), + m_args(args), + m_enableClipboard(true) +{ + assert(m_socketFactory != NULL); + assert(m_screen != NULL); + + // register suspend/resume event handlers + m_events->adoptHandler(m_events->forIScreen().suspend(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleSuspend)); + m_events->adoptHandler(m_events->forIScreen().resume(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleResume)); + + if (m_args.m_enableDragDrop) { + m_events->adoptHandler(m_events->forFile().fileChunkSending(), + this, + new TMethodEventJob<Client>(this, + &Client::handleFileChunkSending)); + m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(), + this, + new TMethodEventJob<Client>(this, + &Client::handleFileRecieveCompleted)); + } +} + +Client::~Client() +{ + if (m_mock) { + return; + } + + m_events->removeHandler(m_events->forIScreen().suspend(), + getEventTarget()); + m_events->removeHandler(m_events->forIScreen().resume(), + getEventTarget()); + + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + delete m_socketFactory; +} + +void +Client::connect() +{ + if (m_stream != NULL) { + return; + } + if (m_suspended) { + m_connectOnResume = true; + return; + } + + try { + // resolve the server hostname. do this every time we connect + // in case we couldn't resolve the address earlier or the address + // has changed (which can happen frequently if this is a laptop + // being shuttled between various networks). patch by Brent + // Priddy. + m_serverAddress.resolve(); + + // m_serverAddress will be null if the hostname address is not reolved + if (m_serverAddress.getAddress() != NULL) { + // to help users troubleshoot, show server host name (issue: 60) + LOG((CLOG_NOTE "connecting to '%s': %s:%i", + m_serverAddress.getHostname().c_str(), + ARCH->addrToString(m_serverAddress.getAddress()).c_str(), + m_serverAddress.getPort())); + } + + // create the socket + IDataSocket* socket = m_socketFactory->create( + ARCH->getAddrFamily(m_serverAddress.getAddress()), + m_useSecureNetwork); + m_socket = dynamic_cast<TCPSocket*>(socket); + + // filter socket messages, including a packetizing filter + m_stream = socket; + m_stream = new PacketStreamFilter(m_events, m_stream, true); + + // connect + LOG((CLOG_DEBUG1 "connecting to server")); + setupConnecting(); + setupTimer(); + socket->connect(m_serverAddress); + } + catch (XBase& e) { + cleanupTimer(); + cleanupConnecting(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(e.what()); + return; + } +} + +void +Client::disconnect(const char* msg) +{ + m_connectOnResume = false; + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + if (msg != NULL) { + sendConnectionFailedEvent(msg); + } + else { + sendEvent(m_events->forClient().disconnected(), NULL); + } +} + +void +Client::handshakeComplete() +{ + m_ready = true; + m_screen->enable(); + sendEvent(m_events->forClient().connected(), NULL); +} + +bool +Client::isConnected() const +{ + return (m_server != NULL); +} + +bool +Client::isConnecting() const +{ + return (m_timer != NULL); +} + +NetworkAddress +Client::getServerAddress() const +{ + return m_serverAddress; +} + +void* +Client::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +Client::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +Client::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +Client::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +Client::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) +{ + m_active = true; + m_screen->mouseMove(xAbs, yAbs); + m_screen->enter(mask); + + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + m_sendFileThread = NULL; + } +} + +bool +Client::leave() +{ + m_active = false; + + m_screen->leave(); + + if (m_enableClipboard) { + // send clipboards that we own and that have changed + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_ownClipboard[id]) { + sendClipboard(id); + } + } + } + + return true; +} + +void +Client::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +Client::grabClipboard(ClipboardID id) +{ + m_screen->grabClipboard(id); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +Client::setClipboardDirty(ClipboardID, bool) +{ + assert(0 && "shouldn't be called"); +} + +void +Client::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyDown(id, mask, button); +} + +void +Client::keyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + m_screen->keyRepeat(id, mask, count, button); +} + +void +Client::keyUp(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyUp(id, mask, button); +} + +void +Client::mouseDown(ButtonID id) +{ + m_screen->mouseDown(id); +} + +void +Client::mouseUp(ButtonID id) +{ + m_screen->mouseUp(id); +} + +void +Client::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->mouseMove(x, y); +} + +void +Client::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + m_screen->mouseRelativeMove(dx, dy); +} + +void +Client::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + m_screen->mouseWheel(xDelta, yDelta); +} + +void +Client::screensaver(bool activate) +{ + m_screen->screensaver(activate); +} + +void +Client::resetOptions() +{ + m_screen->resetOptions(); +} + +void +Client::setOptions(const OptionsList& options) +{ + for (OptionsList::const_iterator index = options.begin(); + index != options.end(); ++index) { + const OptionID id = *index; + if (id == kOptionClipboardSharing) { + index++; + if (*index == static_cast<OptionValue>(false)) { + LOG((CLOG_NOTE "clipboard sharing is disabled")); + } + m_enableClipboard = *index; + + break; + } + } + + m_screen->setOptions(options); +} + +String +Client::getName() const +{ + return m_name; +} + +void +Client::sendClipboard(ClipboardID id) +{ + // note -- m_mutex must be locked on entry + assert(m_screen != NULL); + assert(m_server != NULL); + + // get clipboard data. set the clipboard time to the last + // clipboard time before getting the data from the screen + // as the screen may detect an unchanged clipboard and + // avoid copying the data. + Clipboard clipboard; + if (clipboard.open(m_timeClipboard[id])) { + clipboard.close(); + } + m_screen->getClipboard(id, &clipboard); + + // check time + if (m_timeClipboard[id] == 0 || + clipboard.getTime() != m_timeClipboard[id]) { + // save new time + m_timeClipboard[id] = clipboard.getTime(); + + // marshall the data + String data = clipboard.marshall(); + + // save and send data if different or not yet sent + if (!m_sentClipboard[id] || data != m_dataClipboard[id]) { + m_sentClipboard[id] = true; + m_dataClipboard[id] = data; + m_server->onClipboardChanged(id, &clipboard); + } + } +} + +void +Client::sendEvent(Event::Type type, void* data) +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +Client::sendConnectionFailedEvent(const char* msg) +{ + FailInfo* info = new FailInfo(msg); + info->m_retry = true; + Event event(m_events->forClient().connectionFailed(), getEventTarget(), info, Event::kDontFreeData); + m_events->addEvent(event); +} + +void +Client::sendFileChunk(const void* data) +{ + FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data)); + LOG((CLOG_DEBUG1 "send file chunk")); + assert(m_server != NULL); + + // relay + m_server->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); +} + +void +Client::setupConnecting() +{ + assert(m_stream != NULL); + + if (m_args.m_enableCrypto) { + m_events->adoptHandler(m_events->forIDataSocket().secureConnected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnected)); + } + else { + m_events->adoptHandler(m_events->forIDataSocket().connected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnected)); + } + + m_events->adoptHandler(m_events->forIDataSocket().connectionFailed(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnectionFailed)); +} + +void +Client::setupConnection() +{ + assert(m_stream != NULL); + + m_events->adoptHandler(m_events->forISocket().disconnected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + m_events->adoptHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleHello)); + m_events->adoptHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleOutputError)); + m_events->adoptHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + m_events->adoptHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + + m_events->adoptHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, &Client::handleStopRetry)); +} + +void +Client::setupScreen() +{ + assert(m_server == NULL); + + m_ready = false; + m_server = new ServerProxy(this, m_stream, m_events); + m_events->adoptHandler(m_events->forIScreen().shapeChanged(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleShapeChanged)); + m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleClipboardGrabbed)); +} + +void +Client::setupTimer() +{ + assert(m_timer == NULL); + + m_timer = m_events->newOneShotTimer(15.0, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<Client>(this, + &Client::handleConnectTimeout)); +} + +void +Client::cleanupConnecting() +{ + if (m_stream != NULL) { + m_events->removeHandler(m_events->forIDataSocket().connected(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIDataSocket().connectionFailed(), + m_stream->getEventTarget()); + } +} + +void +Client::cleanupConnection() +{ + if (m_stream != NULL) { + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forISocket().disconnected(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget()); + cleanupStream(); + } +} + +void +Client::cleanupScreen() +{ + if (m_server != NULL) { + if (m_ready) { + m_screen->disable(); + m_ready = false; + } + m_events->removeHandler(m_events->forIScreen().shapeChanged(), + getEventTarget()); + m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), + getEventTarget()); + delete m_server; + m_server = NULL; + } +} + +void +Client::cleanupTimer() +{ + if (m_timer != NULL) { + m_events->removeHandler(Event::kTimer, m_timer); + m_events->deleteTimer(m_timer); + m_timer = NULL; + } +} + +void +Client::cleanupStream() +{ + delete m_stream; + m_stream = NULL; +} + +void +Client::handleConnected(const Event&, void*) +{ + LOG((CLOG_DEBUG1 "connected; wait for hello")); + cleanupConnecting(); + setupConnection(); + + // reset clipboard state + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; + m_timeClipboard[id] = 0; + } +} + +void +Client::handleConnectionFailed(const Event& event, void*) +{ + IDataSocket::ConnectionFailedInfo* info = + static_cast<IDataSocket::ConnectionFailedInfo*>(event.getData()); + + cleanupTimer(); + cleanupConnecting(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(info->m_what.c_str()); + delete info; +} + +void +Client::handleConnectTimeout(const Event&, void*) +{ + cleanupTimer(); + cleanupConnecting(); + cleanupConnection(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection timed out")); + sendConnectionFailedEvent("Timed out"); +} + +void +Client::handleOutputError(const Event&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_WARN "error sending to server")); + sendEvent(m_events->forClient().disconnected(), NULL); +} + +void +Client::handleDisconnected(const Event&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_DEBUG1 "disconnected")); + sendEvent(m_events->forClient().disconnected(), NULL); +} + +void +Client::handleShapeChanged(const Event&, void*) +{ + LOG((CLOG_DEBUG "resolution changed")); + m_server->onInfoChanged(); +} + +void +Client::handleClipboardGrabbed(const Event& event, void*) +{ + if (!m_enableClipboard) { + return; + } + + const IScreen::ClipboardInfo* info = + static_cast<const IScreen::ClipboardInfo*>(event.getData()); + + // grab ownership + m_server->onGrabClipboard(info->m_id); + + // we now own the clipboard and it has not been sent to the server + m_ownClipboard[info->m_id] = true; + m_sentClipboard[info->m_id] = false; + m_timeClipboard[info->m_id] = 0; + + // if we're not the active screen then send the clipboard now, + // otherwise we'll wait until we leave. + if (!m_active) { + sendClipboard(info->m_id); + } +} + +void +Client::handleHello(const Event&, void*) +{ + SInt16 major, minor; + if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { + sendConnectionFailedEvent("Protocol error from server, check encryption settings"); + cleanupTimer(); + cleanupConnection(); + return; + } + + // check versions + LOG((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); + if (major < kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) { + sendConnectionFailedEvent(XIncompatibleClient(major, minor).what()); + cleanupTimer(); + cleanupConnection(); + return; + } + + // say hello back + LOG((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); + ProtocolUtil::writef(m_stream, kMsgHelloBack, + kProtocolMajorVersion, + kProtocolMinorVersion, &m_name); + + // now connected but waiting to complete handshake + setupScreen(); + cleanupTimer(); + + // make sure we process any remaining messages later. we won't + // receive another event for already pending messages so we fake + // one. + if (m_stream->isReady()) { + m_events->addEvent(Event(m_events->forIStream().inputReady(), + m_stream->getEventTarget())); + } +} + +void +Client::handleSuspend(const Event&, void*) +{ + LOG((CLOG_INFO "suspend")); + m_suspended = true; + bool wasConnected = isConnected(); + disconnect(NULL); + m_connectOnResume = wasConnected; +} + +void +Client::handleResume(const Event&, void*) +{ + LOG((CLOG_INFO "resume")); + m_suspended = false; + if (m_connectOnResume) { + m_connectOnResume = false; + connect(); + } +} + +void +Client::handleFileChunkSending(const Event& event, void*) +{ + sendFileChunk(event.getData()); +} + +void +Client::handleFileRecieveCompleted(const Event& event, void*) +{ + onFileRecieveCompleted(); +} + +void +Client::onFileRecieveCompleted() +{ + if (isReceivedFileSizeValid()) { + m_writeToDropDirThread = new Thread( + new TMethodJob<Client>( + this, &Client::writeToDropDirThread)); + } +} + +void +Client::handleStopRetry(const Event&, void*) +{ + m_args.m_restartable = false; +} + +void +Client::writeToDropDirThread(void*) +{ + LOG((CLOG_DEBUG "starting write to drop dir thread")); + + while (m_screen->isFakeDraggingStarted()) { + ARCH->sleep(.1f); + } + + DropHelper::writeToDir(m_screen->getDropTarget(), m_dragFileList, + m_receivedFileData); +} + +void +Client::dragInfoReceived(UInt32 fileNum, String data) +{ + // TODO: fix duplicate function from CServer + if (!m_args.m_enableDragDrop) { + LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info.")); + return; + } + + DragInformation::parseDragInfo(m_dragFileList, fileNum, data); + + m_screen->startDraggingFiles(m_dragFileList); +} + +bool +Client::isReceivedFileSizeValid() +{ + return m_expectedFileSize == m_receivedFileData.size(); +} + +void +Client::sendFileToServer(const char* filename) +{ + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + } + + m_sendFileThread = new Thread( + new TMethodJob<Client>( + this, &Client::sendFileThread, + static_cast<void*>(const_cast<char*>(filename)))); +} + +void +Client::sendFileThread(void* filename) +{ + try { + char* name = static_cast<char*>(filename); + StreamChunker::sendFile(name, m_events, this); + } + catch (std::runtime_error& error) { + LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); + } + + m_sendFileThread = NULL; +} + +void +Client::sendDragInfo(UInt32 fileCount, String& info, size_t size) +{ + m_server->sendDragInfo(fileCount, info.c_str(), size); +} diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h new file mode 100644 index 0000000..7ac515d --- /dev/null +++ b/src/lib/client/Client.h @@ -0,0 +1,227 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "barrier/IClient.h" + +#include "barrier/Clipboard.h" +#include "barrier/DragInformation.h" +#include "barrier/INode.h" +#include "barrier/ClientArgs.h" +#include "net/NetworkAddress.h" +#include "base/EventTypes.h" +#include "mt/CondVar.h" + +class EventQueueTimer; +namespace barrier { class Screen; } +class ServerProxy; +class IDataSocket; +class ISocketFactory; +namespace barrier { class IStream; } +class IEventQueue; +class Thread; +class TCPSocket; + +//! Barrier client +/*! +This class implements the top-level client algorithms for barrier. +*/ +class Client : public IClient, public INode { +public: + class FailInfo { + public: + FailInfo(const char* what) : m_retry(false), m_what(what) { } + bool m_retry; + String m_what; + }; + +public: + /*! + This client will attempt to connect to the server using \p name + as its name and \p address as the server's address and \p factory + to create the socket. \p screen is the local screen. + */ + Client(IEventQueue* events, const String& name, + const NetworkAddress& address, ISocketFactory* socketFactory, + barrier::Screen* screen, ClientArgs const& args); + + ~Client(); + + //! @name manipulators + //@{ + + //! Connect to server + /*! + Starts an attempt to connect to the server. This is ignored if + the client is trying to connect or is already connected. + */ + void connect(); + + //! Disconnect + /*! + Disconnects from the server with an optional error message. + */ + void disconnect(const char* msg); + + //! Notify of handshake complete + /*! + Notifies the client that the connection handshake has completed. + */ + virtual void handshakeComplete(); + + //! Received drag information + void dragInfoReceived(UInt32 fileNum, String data); + + //! Create a new thread and use it to send file to Server + void sendFileToServer(const char* filename); + + //! Send dragging file information back to server + void sendDragInfo(UInt32 fileCount, String& info, size_t size); + + + //@} + //! @name accessors + //@{ + + //! Test if connected + /*! + Returns true iff the client is successfully connected to the server. + */ + bool isConnected() const; + + //! Test if connecting + /*! + Returns true iff the client is currently attempting to connect to + the server. + */ + bool isConnecting() const; + + //! Get address of server + /*! + Returns the address of the server the client is connected (or wants + to connect) to. + */ + NetworkAddress getServerAddress() const; + + //! Return true if recieved file size is valid + bool isReceivedFileSizeValid(); + + //! Return expected file size + size_t& getExpectedFileSize() { return m_expectedFileSize; } + + //! Return received file data + String& getReceivedFileData() { return m_receivedFileData; } + + //! Return drag file list + DragFileList getDragFileList() { return m_dragFileList; } + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual String getName() const; + +private: + void sendClipboard(ClipboardID); + void sendEvent(Event::Type, void*); + void sendConnectionFailedEvent(const char* msg); + void sendFileChunk(const void* data); + void sendFileThread(void*); + void writeToDropDirThread(void*); + void setupConnecting(); + void setupConnection(); + void setupScreen(); + void setupTimer(); + void cleanupConnecting(); + void cleanupConnection(); + void cleanupScreen(); + void cleanupTimer(); + void cleanupStream(); + void handleConnected(const Event&, void*); + void handleConnectionFailed(const Event&, void*); + void handleConnectTimeout(const Event&, void*); + void handleOutputError(const Event&, void*); + void handleDisconnected(const Event&, void*); + void handleShapeChanged(const Event&, void*); + void handleClipboardGrabbed(const Event&, void*); + void handleHello(const Event&, void*); + void handleSuspend(const Event& event, void*); + void handleResume(const Event& event, void*); + void handleFileChunkSending(const Event&, void*); + void handleFileRecieveCompleted(const Event&, void*); + void handleStopRetry(const Event&, void*); + void onFileRecieveCompleted(); + void sendClipboardThread(void*); + +public: + bool m_mock; + +private: + String m_name; + NetworkAddress m_serverAddress; + ISocketFactory* m_socketFactory; + barrier::Screen* m_screen; + barrier::IStream* m_stream; + EventQueueTimer* m_timer; + ServerProxy* m_server; + bool m_ready; + bool m_active; + bool m_suspended; + bool m_connectOnResume; + bool m_ownClipboard[kClipboardEnd]; + bool m_sentClipboard[kClipboardEnd]; + IClipboard::Time m_timeClipboard[kClipboardEnd]; + String m_dataClipboard[kClipboardEnd]; + IEventQueue* m_events; + std::size_t m_expectedFileSize; + String m_receivedFileData; + DragFileList m_dragFileList; + String m_dragFileExt; + Thread* m_sendFileThread; + Thread* m_writeToDropDirThread; + TCPSocket* m_socket; + bool m_useSecureNetwork; + ClientArgs m_args; + bool m_enableClipboard; +}; diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp new file mode 100644 index 0000000..9224db6 --- /dev/null +++ b/src/lib/client/ServerProxy.cpp @@ -0,0 +1,908 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see <http://www.gnu.org/licenses/>. + */ + +#include "client/ServerProxy.h" + +#include "client/Client.h" +#include "barrier/FileChunk.h" +#include "barrier/ClipboardChunk.h" +#include "barrier/StreamChunker.h" +#include "barrier/Clipboard.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/option_types.h" +#include "barrier/protocol_types.h" +#include "io/IStream.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/XBase.h" + +#include <memory> + +// +// ServerProxy +// + +ServerProxy::ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events) : + m_client(client), + m_stream(stream), + m_seqNum(0), + m_compressMouse(false), + m_compressMouseRelative(false), + m_xMouse(0), + m_yMouse(0), + m_dxMouse(0), + m_dyMouse(0), + m_ignoreMouse(false), + m_keepAliveAlarm(0.0), + m_keepAliveAlarmTimer(NULL), + m_parser(&ServerProxy::parseHandshakeMessage), + m_events(events) +{ + assert(m_client != NULL); + assert(m_stream != NULL); + + // initialize modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) + m_modifierTranslationTable[id] = id; + + // handle data on stream + m_events->adoptHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget(), + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleData)); + + m_events->adoptHandler(m_events->forClipboard().clipboardSending(), + this, + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleClipboardSendingEvent)); + + // send heartbeat + setKeepAliveRate(kKeepAliveRate); +} + +ServerProxy::~ServerProxy() +{ + setKeepAliveRate(-1.0); + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget()); +} + +void +ServerProxy::resetKeepAliveAlarm() +{ + if (m_keepAliveAlarmTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_keepAliveAlarmTimer); + m_events->deleteTimer(m_keepAliveAlarmTimer); + m_keepAliveAlarmTimer = NULL; + } + if (m_keepAliveAlarm > 0.0) { + m_keepAliveAlarmTimer = + m_events->newOneShotTimer(m_keepAliveAlarm, NULL); + m_events->adoptHandler(Event::kTimer, m_keepAliveAlarmTimer, + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleKeepAliveAlarm)); + } +} + +void +ServerProxy::setKeepAliveRate(double rate) +{ + m_keepAliveAlarm = rate * kKeepAlivesUntilDeath; + resetKeepAliveAlarm(); +} + +void +ServerProxy::handleData(const Event&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = m_stream->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from server: %d bytes", n)); + m_client->disconnect("incomplete message from server"); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + switch ((this->*m_parser)(code)) { + case kOkay: + break; + + case kUnknown: + LOG((CLOG_ERR "invalid message from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + m_client->disconnect("invalid message from server"); + return; + + case kDisconnect: + return; + } + + // next message + n = m_stream->read(code, 4); + } + + flushCompressedMouse(); +} + +ServerProxy::EResult +ServerProxy::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + + // handshake is complete + m_parser = &ServerProxy::parseMessage; + m_client->handshakeComplete(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + ProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + + else if (memcmp(code, kMsgEIncompatible, 4) == 0) { + SInt32 major, minor; + ProtocolUtil::readf(m_stream, + kMsgEIncompatible + 4, &major, &minor); + LOG((CLOG_ERR "server has incompatible version %d.%d", major, minor)); + m_client->disconnect("server has incompatible version"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBusy, 4) == 0) { + LOG((CLOG_ERR "server already has a connected client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server already has a connected client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEUnknown, 4) == 0) { + LOG((CLOG_ERR "server refused client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server refused client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + return kOkay; +} + +ServerProxy::EResult +ServerProxy::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDMouseMove, 4) == 0) { + mouseMove(); + } + + else if (memcmp(code, kMsgDMouseRelMove, 4) == 0) { + mouseRelativeMove(); + } + + else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { + mouseWheel(); + } + + else if (memcmp(code, kMsgDKeyDown, 4) == 0) { + keyDown(); + } + + else if (memcmp(code, kMsgDKeyUp, 4) == 0) { + keyUp(); + } + + else if (memcmp(code, kMsgDMouseDown, 4) == 0) { + mouseDown(); + } + + else if (memcmp(code, kMsgDMouseUp, 4) == 0) { + mouseUp(); + } + + else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + keyRepeat(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + ProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCEnter, 4) == 0) { + enter(); + } + + else if (memcmp(code, kMsgCLeave, 4) == 0) { + leave(); + } + + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + grabClipboard(); + } + + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + screensaver(); + } + + else if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + setClipboard(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + } + + else if (memcmp(code, kMsgDFileTransfer, 4) == 0) { + fileChunkReceived(); + } + else if (memcmp(code, kMsgDDragInfo, 4) == 0) { + dragInfoReceived(); + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + // send a reply. this is intended to work around a delay when + // running a linux server and an OS X (any BSD?) client. the + // client waits to send an ACK (if the system control flag + // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it + // on a data packet. we provide that packet here. i don't + // know why a delayed ACK should cause the server to wait since + // TCP_NODELAY is enabled. + ProtocolUtil::writef(m_stream, kMsgCNoop); + + return kOkay; +} + +void +ServerProxy::handleKeepAliveAlarm(const Event&, void*) +{ + LOG((CLOG_NOTE "server is dead")); + m_client->disconnect("server is not responding"); +} + +void +ServerProxy::onInfoChanged() +{ + // ignore mouse motion until we receive acknowledgment of our info + // change message. + m_ignoreMouse = true; + + // send info update + queryInfo(); +} + +bool +ServerProxy::onGrabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG1 "sending clipboard %d changed", id)); + ProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum); + return true; +} + +void +ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) +{ + String data = IClipboard::marshall(clipboard); + LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum)); + + StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); +} + +void +ServerProxy::flushCompressedMouse() +{ + if (m_compressMouse) { + m_compressMouse = false; + m_client->mouseMove(m_xMouse, m_yMouse); + } + if (m_compressMouseRelative) { + m_compressMouseRelative = false; + m_client->mouseRelativeMove(m_dxMouse, m_dyMouse); + m_dxMouse = 0; + m_dyMouse = 0; + } +} + +void +ServerProxy::sendInfo(const ClientInfo& info) +{ + LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h)); + ProtocolUtil::writef(m_stream, kMsgDInfo, + info.m_x, info.m_y, + info.m_w, info.m_h, 0, + info.m_mx, info.m_my); +} + +KeyID +ServerProxy::translateKey(KeyID id) const +{ + static const KeyID s_translationTable[kKeyModifierIDLast][2] = { + { kKeyNone, kKeyNone }, + { kKeyShift_L, kKeyShift_R }, + { kKeyControl_L, kKeyControl_R }, + { kKeyAlt_L, kKeyAlt_R }, + { kKeyMeta_L, kKeyMeta_R }, + { kKeySuper_L, kKeySuper_R }, + { kKeyAltGr, kKeyAltGr} + }; + + KeyModifierID id2 = kKeyModifierIDNull; + UInt32 side = 0; + switch (id) { + case kKeyShift_L: + id2 = kKeyModifierIDShift; + side = 0; + break; + + case kKeyShift_R: + id2 = kKeyModifierIDShift; + side = 1; + break; + + case kKeyControl_L: + id2 = kKeyModifierIDControl; + side = 0; + break; + + case kKeyControl_R: + id2 = kKeyModifierIDControl; + side = 1; + break; + + case kKeyAlt_L: + id2 = kKeyModifierIDAlt; + side = 0; + break; + + case kKeyAlt_R: + id2 = kKeyModifierIDAlt; + side = 1; + break; + + case kKeyAltGr: + id2 = kKeyModifierIDAltGr; + side = 1; // there is only one alt gr key on the right side + break; + + case kKeyMeta_L: + id2 = kKeyModifierIDMeta; + side = 0; + break; + + case kKeyMeta_R: + id2 = kKeyModifierIDMeta; + side = 1; + break; + + case kKeySuper_L: + id2 = kKeyModifierIDSuper; + side = 0; + break; + + case kKeySuper_R: + id2 = kKeyModifierIDSuper; + side = 1; + break; + } + + if (id2 != kKeyModifierIDNull) { + return s_translationTable[m_modifierTranslationTable[id2]][side]; + } + else { + return id; + } +} + +KeyModifierMask +ServerProxy::translateModifierMask(KeyModifierMask mask) const +{ + static const KeyModifierMask s_masks[kKeyModifierIDLast] = { + 0x0000, + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper, + KeyModifierAltGr + }; + + KeyModifierMask newMask = mask & ~(KeyModifierShift | + KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper | + KeyModifierAltGr ); + if ((mask & KeyModifierShift) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDShift]]; + } + if ((mask & KeyModifierControl) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDControl]]; + } + if ((mask & KeyModifierAlt) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAlt]]; + } + if ((mask & KeyModifierAltGr) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAltGr]]; + } + if ((mask & KeyModifierMeta) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]]; + } + if ((mask & KeyModifierSuper) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; + } + return newMask; +} + +void +ServerProxy::enter() +{ + // parse + SInt16 x, y; + UInt16 mask; + UInt32 seqNum; + ProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); + LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); + + // discard old compressed mouse motion, if any + m_compressMouse = false; + m_compressMouseRelative = false; + m_dxMouse = 0; + m_dyMouse = 0; + m_seqNum = seqNum; + + // forward + m_client->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false); +} + +void +ServerProxy::leave() +{ + // parse + LOG((CLOG_DEBUG1 "recv leave")); + + // send last mouse motion + flushCompressedMouse(); + + // forward + m_client->leave(); +} + +void +ServerProxy::setClipboard() +{ + // parse + static String dataCached; + ClipboardID id; + UInt32 seq; + + int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); + + if (r == kStart) { + size_t size = ClipboardChunk::getExpectedSize(); + LOG((CLOG_DEBUG "receiving clipboard %d size=%d", id, size)); + } + else if (r == kFinish) { + LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size())); + + // forward + Clipboard clipboard; + clipboard.unmarshall(dataCached, 0); + m_client->setClipboard(id, &clipboard); + + LOG((CLOG_INFO "clipboard was updated")); + } +} + +void +ServerProxy::grabClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + ProtocolUtil::readf(m_stream, kMsgCClipboard + 4, &id, &seqNum); + LOG((CLOG_DEBUG "recv grab clipboard %d", id)); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + m_client->grabClipboard(id); +} + +void +ServerProxy::keyDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyDown(id2, mask2, button); +} + +void +ServerProxy::keyRepeat() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, count, button; + ProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, + &id, &mask, &count, &button); + LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyRepeat(id2, mask2, count, button); +} + +void +ServerProxy::keyUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + ProtocolUtil::readf(m_stream, kMsgDKeyUp + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key up translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyUp(id2, mask2, button); +} + +void +ServerProxy::mouseDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + ProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse down id=%d", id)); + + // forward + m_client->mouseDown(static_cast<ButtonID>(id)); +} + +void +ServerProxy::mouseUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + ProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse up id=%d", id)); + + // forward + m_client->mouseUp(static_cast<ButtonID>(id)); +} + +void +ServerProxy::mouseMove() +{ + // parse + bool ignore; + SInt16 x, y; + ProtocolUtil::readf(m_stream, kMsgDMouseMove + 4, &x, &y); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouse && m_stream->isReady()) { + m_compressMouse = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouse) { + m_compressMouseRelative = false; + ignore = true; + m_xMouse = x; + m_yMouse = y; + m_dxMouse = 0; + m_dyMouse = 0; + } + LOG((CLOG_DEBUG2 "recv mouse move %d,%d", x, y)); + + // forward + if (!ignore) { + m_client->mouseMove(x, y); + } +} + +void +ServerProxy::mouseRelativeMove() +{ + // parse + bool ignore; + SInt16 dx, dy; + ProtocolUtil::readf(m_stream, kMsgDMouseRelMove + 4, &dx, &dy); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouseRelative && m_stream->isReady()) { + m_compressMouseRelative = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouseRelative) { + ignore = true; + m_dxMouse += dx; + m_dyMouse += dy; + } + LOG((CLOG_DEBUG2 "recv mouse relative move %d,%d", dx, dy)); + + // forward + if (!ignore) { + m_client->mouseRelativeMove(dx, dy); + } +} + +void +ServerProxy::mouseWheel() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt16 xDelta, yDelta; + ProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta); + LOG((CLOG_DEBUG2 "recv mouse wheel %+d,%+d", xDelta, yDelta)); + + // forward + m_client->mouseWheel(xDelta, yDelta); +} + +void +ServerProxy::screensaver() +{ + // parse + SInt8 on; + ProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on); + LOG((CLOG_DEBUG1 "recv screen saver on=%d", on)); + + // forward + m_client->screensaver(on != 0); +} + +void +ServerProxy::resetOptions() +{ + // parse + LOG((CLOG_DEBUG1 "recv reset options")); + + // forward + m_client->resetOptions(); + + // reset keep alive + setKeepAliveRate(kKeepAliveRate); + + // reset modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { + m_modifierTranslationTable[id] = id; + } +} + +void +ServerProxy::setOptions() +{ + // parse + OptionsList options; + ProtocolUtil::readf(m_stream, kMsgDSetOptions + 4, &options); + LOG((CLOG_DEBUG1 "recv set options size=%d", options.size())); + + // forward + m_client->setOptions(options); + + // update modifier table + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + KeyModifierID id = kKeyModifierIDNull; + if (options[i] == kOptionModifierMapForShift) { + id = kKeyModifierIDShift; + } + else if (options[i] == kOptionModifierMapForControl) { + id = kKeyModifierIDControl; + } + else if (options[i] == kOptionModifierMapForAlt) { + id = kKeyModifierIDAlt; + } + else if (options[i] == kOptionModifierMapForAltGr) { + id = kKeyModifierIDAltGr; + } + else if (options[i] == kOptionModifierMapForMeta) { + id = kKeyModifierIDMeta; + } + else if (options[i] == kOptionModifierMapForSuper) { + id = kKeyModifierIDSuper; + } + else if (options[i] == kOptionHeartbeat) { + // update keep alive + setKeepAliveRate(1.0e-3 * static_cast<double>(options[i + 1])); + } + + if (id != kKeyModifierIDNull) { + m_modifierTranslationTable[id] = + static_cast<KeyModifierID>(options[i + 1]); + LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id])); + } + } +} + +void +ServerProxy::queryInfo() +{ + ClientInfo info; + m_client->getShape(info.m_x, info.m_y, info.m_w, info.m_h); + m_client->getCursorPos(info.m_mx, info.m_my); + sendInfo(info); +} + +void +ServerProxy::infoAcknowledgment() +{ + LOG((CLOG_DEBUG1 "recv info acknowledgment")); + m_ignoreMouse = false; +} + +void +ServerProxy::fileChunkReceived() +{ + int result = FileChunk::assemble( + m_stream, + m_client->getReceivedFileData(), + m_client->getExpectedFileSize()); + + if (result == kFinish) { + m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), m_client)); + } + else if (result == kStart) { + if (m_client->getDragFileList().size() > 0) { + String filename = m_client->getDragFileList().at(0).getFilename(); + LOG((CLOG_DEBUG "start receiving %s", filename.c_str())); + } + } +} + +void +ServerProxy::dragInfoReceived() +{ + // parse + UInt32 fileNum = 0; + String content; + ProtocolUtil::readf(m_stream, kMsgDDragInfo + 4, &fileNum, &content); + + m_client->dragInfoReceived(fileNum, content); +} + +void +ServerProxy::handleClipboardSendingEvent(const Event& event, void*) +{ + ClipboardChunk::send(m_stream, event.getData()); +} + +void +ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) +{ + FileChunk::send(m_stream, mark, data, dataSize); +} + +void +ServerProxy::sendDragInfo(UInt32 fileCount, const char* info, size_t size) +{ + String data(info, size); + ProtocolUtil::writef(m_stream, kMsgDDragInfo, fileCount, &data); +} diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h new file mode 100644 index 0000000..2ad711a --- /dev/null +++ b/src/lib/client/ServerProxy.h @@ -0,0 +1,133 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "barrier/clipboard_types.h" +#include "barrier/key_types.h" +#include "base/Event.h" +#include "base/Stopwatch.h" +#include "base/String.h" + +class Client; +class ClientInfo; +class EventQueueTimer; +class IClipboard; +namespace barrier { class IStream; } +class IEventQueue; + +//! Proxy for server +/*! +This class acts a proxy for the server, converting calls into messages +to the server and messages from the server to calls on the client. +*/ +class ServerProxy { +public: + /*! + Process messages from the server on \p stream and forward to + \p client. + */ + ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events); + ~ServerProxy(); + + //! @name manipulators + //@{ + + void onInfoChanged(); + bool onGrabClipboard(ClipboardID); + void onClipboardChanged(ClipboardID, const IClipboard*); + + //@} + + // sending file chunk to server + void fileChunkSending(UInt8 mark, char* data, size_t dataSize); + + // sending dragging information to server + void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + +#ifdef TEST_ENV + void handleDataForTest() { handleData(Event(), NULL); } +#endif + +protected: + enum EResult { kOkay, kUnknown, kDisconnect }; + EResult parseHandshakeMessage(const UInt8* code); + EResult parseMessage(const UInt8* code); + +private: + // if compressing mouse motion then send the last motion now + void flushCompressedMouse(); + + void sendInfo(const ClientInfo&); + + void resetKeepAliveAlarm(); + void setKeepAliveRate(double); + + // modifier key translation + KeyID translateKey(KeyID) const; + KeyModifierMask translateModifierMask(KeyModifierMask) const; + + // event handlers + void handleData(const Event&, void*); + void handleKeepAliveAlarm(const Event&, void*); + + // message handlers + void enter(); + void leave(); + void setClipboard(); + void grabClipboard(); + void keyDown(); + void keyRepeat(); + void keyUp(); + void mouseDown(); + void mouseUp(); + void mouseMove(); + void mouseRelativeMove(); + void mouseWheel(); + void screensaver(); + void resetOptions(); + void setOptions(); + void queryInfo(); + void infoAcknowledgment(); + void fileChunkReceived(); + void dragInfoReceived(); + void handleClipboardSendingEvent(const Event&, void*); + +private: + typedef EResult (ServerProxy::*MessageParser)(const UInt8*); + + Client* m_client; + barrier::IStream* m_stream; + + UInt32 m_seqNum; + + bool m_compressMouse; + bool m_compressMouseRelative; + SInt32 m_xMouse, m_yMouse; + SInt32 m_dxMouse, m_dyMouse; + + bool m_ignoreMouse; + + KeyModifierID m_modifierTranslationTable[kKeyModifierIDLast]; + + double m_keepAliveAlarm; + EventQueueTimer* m_keepAliveAlarmTimer; + + MessageParser m_parser; + IEventQueue* m_events; +}; |
