aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/client
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
committerLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
commit9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch)
treece8840148d8445055ba9e4f12263b2208f234c16 /src/lib/client
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/client')
-rw-r--r--src/lib/client/CMakeLists.txt28
-rw-r--r--src/lib/client/Client.cpp836
-rw-r--r--src/lib/client/Client.h227
-rw-r--r--src/lib/client/ServerProxy.cpp908
-rw-r--r--src/lib/client/ServerProxy.h133
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;
+};