aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/server
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/server
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/server')
-rw-r--r--src/lib/server/BaseClientProxy.cpp56
-rw-r--r--src/lib/server/BaseClientProxy.h99
-rw-r--r--src/lib/server/CMakeLists.txt30
-rw-r--r--src/lib/server/ClientListener.cpp256
-rw-r--r--src/lib/server/ClientListener.h91
-rw-r--r--src/lib/server/ClientProxy.cpp61
-rw-r--r--src/lib/server/ClientProxy.h91
-rw-r--r--src/lib/server/ClientProxy1_0.cpp484
-rw-r--r--src/lib/server/ClientProxy1_0.h107
-rw-r--r--src/lib/server/ClientProxy1_1.cpp61
-rw-r--r--src/lib/server/ClientProxy1_1.h34
-rw-r--r--src/lib/server/ClientProxy1_2.cpp44
-rw-r--r--src/lib/server/ClientProxy1_2.h33
-rw-r--r--src/lib/server/ClientProxy1_3.cpp129
-rw-r--r--src/lib/server/ClientProxy1_3.h48
-rw-r--r--src/lib/server/ClientProxy1_4.cpp66
-rw-r--r--src/lib/server/ClientProxy1_4.h46
-rw-r--r--src/lib/server/ClientProxy1_5.cpp110
-rw-r--r--src/lib/server/ClientProxy1_5.h41
-rw-r--r--src/lib/server/ClientProxy1_6.cpp100
-rw-r--r--src/lib/server/ClientProxy1_6.h39
-rw-r--r--src/lib/server/ClientProxyUnknown.cpp295
-rw-r--r--src/lib/server/ClientProxyUnknown.h71
-rw-r--r--src/lib/server/Config.cpp2335
-rw-r--r--src/lib/server/Config.h549
-rw-r--r--src/lib/server/InputFilter.cpp1090
-rw-r--r--src/lib/server/InputFilter.h361
-rw-r--r--src/lib/server/PrimaryClient.cpp274
-rw-r--r--src/lib/server/PrimaryClient.h156
-rw-r--r--src/lib/server/Server.cpp2405
-rw-r--r--src/lib/server/Server.h483
31 files changed, 10045 insertions, 0 deletions
diff --git a/src/lib/server/BaseClientProxy.cpp b/src/lib/server/BaseClientProxy.cpp
new file mode 100644
index 0000000..b9c5339
--- /dev/null
+++ b/src/lib/server/BaseClientProxy.cpp
@@ -0,0 +1,56 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2006 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 "server/BaseClientProxy.h"
+
+//
+// BaseClientProxy
+//
+
+BaseClientProxy::BaseClientProxy(const String& name) :
+ m_name(name),
+ m_x(0),
+ m_y(0)
+{
+ // do nothing
+}
+
+BaseClientProxy::~BaseClientProxy()
+{
+ // do nothing
+}
+
+void
+BaseClientProxy::setJumpCursorPos(SInt32 x, SInt32 y)
+{
+ m_x = x;
+ m_y = y;
+}
+
+void
+BaseClientProxy::getJumpCursorPos(SInt32& x, SInt32& y) const
+{
+ x = m_x;
+ y = m_y;
+}
+
+String
+BaseClientProxy::getName() const
+{
+ return m_name;
+}
diff --git a/src/lib/server/BaseClientProxy.h b/src/lib/server/BaseClientProxy.h
new file mode 100644
index 0000000..c7c23ff
--- /dev/null
+++ b/src/lib/server/BaseClientProxy.h
@@ -0,0 +1,99 @@
+/*
+ * 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 "base/String.h"
+
+namespace barrier { class IStream; }
+
+//! Generic proxy for client or primary
+class BaseClientProxy : public IClient {
+public:
+ /*!
+ \c name is the name of the client.
+ */
+ BaseClientProxy(const String& name);
+ ~BaseClientProxy();
+
+ //! @name manipulators
+ //@{
+
+ //! Save cursor position
+ /*!
+ Save the position of the cursor when jumping from client.
+ */
+ void setJumpCursorPos(SInt32 x, SInt32 y);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get cursor position
+ /*!
+ Get the position of the cursor when last jumping from client.
+ */
+ void getJumpCursorPos(SInt32& x, SInt32& y) const;
+
+ //! Get cursor position
+ /*!
+ Return if this proxy is for client or primary.
+ */
+ virtual bool isPrimary() const { return false; }
+
+ //@}
+
+ // IScreen
+ virtual void* getEventTarget() const = 0;
+ virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0;
+ virtual void getShape(SInt32& x, SInt32& y,
+ SInt32& width, SInt32& height) const = 0;
+ virtual void getCursorPos(SInt32& x, SInt32& y) const = 0;
+
+ // IClient overrides
+ virtual void enter(SInt32 xAbs, SInt32 yAbs,
+ UInt32 seqNum, KeyModifierMask mask,
+ bool forScreensaver) = 0;
+ virtual bool leave() = 0;
+ virtual void setClipboard(ClipboardID, const IClipboard*) = 0;
+ virtual void grabClipboard(ClipboardID) = 0;
+ virtual void setClipboardDirty(ClipboardID, bool) = 0;
+ virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0;
+ virtual void keyRepeat(KeyID, KeyModifierMask,
+ SInt32 count, KeyButton) = 0;
+ virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0;
+ virtual void mouseDown(ButtonID) = 0;
+ virtual void mouseUp(ButtonID) = 0;
+ virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0;
+ virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0;
+ virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0;
+ virtual void screensaver(bool activate) = 0;
+ virtual void resetOptions() = 0;
+ virtual void setOptions(const OptionsList& options) = 0;
+ virtual void sendDragInfo(UInt32 fileCount, const char* info,
+ size_t size) = 0;
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0;
+ virtual String getName() const;
+ virtual barrier::IStream*
+ getStream() const = 0;
+
+private:
+ String m_name;
+ SInt32 m_x, m_y;
+};
diff --git a/src/lib/server/CMakeLists.txt b/src/lib/server/CMakeLists.txt
new file mode 100644
index 0000000..5242d6d
--- /dev/null
+++ b/src/lib/server/CMakeLists.txt
@@ -0,0 +1,30 @@
+# 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(server STATIC ${sources})
+
+target_link_libraries(server)
+
+if (UNIX)
+ target_link_libraries(server synlib)
+endif()
diff --git a/src/lib/server/ClientListener.cpp b/src/lib/server/ClientListener.cpp
new file mode 100644
index 0000000..00067ba
--- /dev/null
+++ b/src/lib/server/ClientListener.cpp
@@ -0,0 +1,256 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 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 "server/ClientListener.h"
+
+#include "server/ClientProxy.h"
+#include "server/ClientProxyUnknown.h"
+#include "barrier/PacketStreamFilter.h"
+#include "net/IDataSocket.h"
+#include "net/IListenSocket.h"
+#include "net/ISocketFactory.h"
+#include "net/XSocket.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+//
+// ClientListener
+//
+
+ClientListener::ClientListener(const NetworkAddress& address,
+ ISocketFactory* socketFactory,
+ IEventQueue* events,
+ bool enableCrypto) :
+ m_socketFactory(socketFactory),
+ m_server(NULL),
+ m_events(events),
+ m_useSecureNetwork(enableCrypto)
+{
+ assert(m_socketFactory != NULL);
+
+ try {
+ m_listen = m_socketFactory->createListen(
+ ARCH->getAddrFamily(address.getAddress()),
+ m_useSecureNetwork);
+
+ // setup event handler
+ m_events->adoptHandler(m_events->forIListenSocket().connecting(),
+ m_listen,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientConnecting));
+
+ // bind listen address
+ LOG((CLOG_DEBUG1 "binding listen socket"));
+ m_listen->bind(address);
+ }
+ catch (XSocketAddressInUse&) {
+ cleanupListenSocket();
+ delete m_socketFactory;
+ throw;
+ }
+ catch (XBase&) {
+ cleanupListenSocket();
+ delete m_socketFactory;
+ throw;
+ }
+ LOG((CLOG_DEBUG1 "listening for clients"));
+}
+
+ClientListener::~ClientListener()
+{
+ LOG((CLOG_DEBUG1 "stop listening for clients"));
+
+ // discard already connected clients
+ for (NewClients::iterator index = m_newClients.begin();
+ index != m_newClients.end(); ++index) {
+ ClientProxyUnknown* client = *index;
+ m_events->removeHandler(
+ m_events->forClientProxyUnknown().success(), client);
+ m_events->removeHandler(
+ m_events->forClientProxyUnknown().failure(), client);
+ m_events->removeHandler(
+ m_events->forClientProxy().disconnected(), client);
+ delete client;
+ }
+
+ // discard waiting clients
+ ClientProxy* client = getNextClient();
+ while (client != NULL) {
+ delete client;
+ client = getNextClient();
+ }
+
+ m_events->removeHandler(m_events->forIListenSocket().connecting(), m_listen);
+ cleanupListenSocket();
+ cleanupClientSockets();
+ delete m_socketFactory;
+}
+
+void
+ClientListener::setServer(Server* server)
+{
+ assert(server != NULL);
+ m_server = server;
+}
+
+ClientProxy*
+ClientListener::getNextClient()
+{
+ ClientProxy* client = NULL;
+ if (!m_waitingClients.empty()) {
+ client = m_waitingClients.front();
+ m_waitingClients.pop_front();
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ }
+ return client;
+}
+
+void
+ClientListener::handleClientConnecting(const Event&, void*)
+{
+ // accept client connection
+ IDataSocket* socket = m_listen->accept();
+
+ if (socket == NULL) {
+ return;
+ }
+
+ m_clientSockets.insert(socket);
+
+ m_events->adoptHandler(m_events->forClientListener().accepted(),
+ socket->getEventTarget(),
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientAccepted, socket));
+
+ // When using non SSL, server accepts clients immediately, while SSL
+ // has to call secure accept which may require retry
+ if (!m_useSecureNetwork) {
+ m_events->addEvent(Event(m_events->forClientListener().accepted(),
+ socket->getEventTarget()));
+ }
+}
+
+void
+ClientListener::handleClientAccepted(const Event&, void* vsocket)
+{
+ LOG((CLOG_NOTE "accepted client connection"));
+
+ IDataSocket* socket = static_cast<IDataSocket*>(vsocket);
+
+ // filter socket messages, including a packetizing filter
+ barrier::IStream* stream = new PacketStreamFilter(m_events, socket, false);
+ assert(m_server != NULL);
+
+ // create proxy for unknown client
+ ClientProxyUnknown* client = new ClientProxyUnknown(stream, 30.0, m_server, m_events);
+
+ m_newClients.insert(client);
+
+ // watch for events from unknown client
+ m_events->adoptHandler(m_events->forClientProxyUnknown().success(),
+ client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleUnknownClient, client));
+ m_events->adoptHandler(m_events->forClientProxyUnknown().failure(),
+ client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleUnknownClient, client));
+}
+
+void
+ClientListener::handleUnknownClient(const Event&, void* vclient)
+{
+ ClientProxyUnknown* unknownClient =
+ static_cast<ClientProxyUnknown*>(vclient);
+
+ // we should have the client in our new client list
+ assert(m_newClients.count(unknownClient) == 1);
+
+ // get the real client proxy and install it
+ ClientProxy* client = unknownClient->orphanClientProxy();
+ bool handshakeOk = true;
+ if (client != NULL) {
+ // handshake was successful
+ m_waitingClients.push_back(client);
+ m_events->addEvent(Event(m_events->forClientListener().connected(),
+ this));
+
+ // watch for client to disconnect while it's in our queue
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(), client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientDisconnected,
+ client));
+ }
+ else {
+ handshakeOk = false;
+ }
+
+ // now finished with unknown client
+ m_events->removeHandler(m_events->forClientProxyUnknown().success(), client);
+ m_events->removeHandler(m_events->forClientProxyUnknown().failure(), client);
+ m_newClients.erase(unknownClient);
+ PacketStreamFilter* streamFileter = dynamic_cast<PacketStreamFilter*>(unknownClient->getStream());
+ IDataSocket* socket = NULL;
+ if (streamFileter != NULL) {
+ socket = dynamic_cast<IDataSocket*>(streamFileter->getStream());
+ }
+
+ delete unknownClient;
+}
+
+void
+ClientListener::handleClientDisconnected(const Event&, void* vclient)
+{
+ ClientProxy* client = static_cast<ClientProxy*>(vclient);
+
+ // find client in waiting clients queue
+ for (WaitingClients::iterator i = m_waitingClients.begin(),
+ n = m_waitingClients.end(); i != n; ++i) {
+ if (*i == client) {
+ m_waitingClients.erase(i);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(),
+ client);
+
+ // pull out the socket before deleting the client so
+ // we know which socket we no longer need
+ IDataSocket* socket = static_cast<IDataSocket*>(client->getStream());
+ delete client;
+ m_clientSockets.erase(socket);
+ delete socket;
+
+ break;
+ }
+ }
+}
+
+void
+ClientListener::cleanupListenSocket()
+{
+ delete m_listen;
+}
+
+void
+ClientListener::cleanupClientSockets()
+{
+ ClientSockets::iterator it;
+ for (it = m_clientSockets.begin(); it != m_clientSockets.end(); it++) {
+ delete *it;
+ }
+ m_clientSockets.clear();
+}
diff --git a/src/lib/server/ClientListener.h b/src/lib/server/ClientListener.h
new file mode 100644
index 0000000..b02cbb1
--- /dev/null
+++ b/src/lib/server/ClientListener.h
@@ -0,0 +1,91 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 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 "server/Config.h"
+#include "base/EventTypes.h"
+#include "base/Event.h"
+#include "common/stddeque.h"
+#include "common/stdset.h"
+
+class ClientProxy;
+class ClientProxyUnknown;
+class NetworkAddress;
+class IListenSocket;
+class ISocketFactory;
+class Server;
+class IEventQueue;
+class IDataSocket;
+
+class ClientListener {
+public:
+ // The factories are adopted.
+ ClientListener(const NetworkAddress&,
+ ISocketFactory*,
+ IEventQueue* events,
+ bool enableCrypto);
+ ~ClientListener();
+
+ //! @name manipulators
+ //@{
+
+ void setServer(Server* server);
+
+ //@}
+
+ //! @name accessors
+ //@{
+
+ //! Get next connected client
+ /*!
+ Returns the next connected client and removes it from the internal
+ list. The client is responsible for deleting the returned client.
+ Returns NULL if no clients are available.
+ */
+ ClientProxy* getNextClient();
+
+ //! Get server which owns this listener
+ Server* getServer() { return m_server; }
+
+ //@}
+
+private:
+ // client connection event handlers
+ void handleClientConnecting(const Event&, void*);
+ void handleClientAccepted(const Event&, void*);
+ void handleUnknownClient(const Event&, void*);
+ void handleClientDisconnected(const Event&, void*);
+
+ void cleanupListenSocket();
+ void cleanupClientSockets();
+
+private:
+ typedef std::set<ClientProxyUnknown*> NewClients;
+ typedef std::deque<ClientProxy*> WaitingClients;
+ typedef std::set<IDataSocket*> ClientSockets;
+
+ IListenSocket* m_listen;
+ ISocketFactory* m_socketFactory;
+ NewClients m_newClients;
+ WaitingClients m_waitingClients;
+ Server* m_server;
+ IEventQueue* m_events;
+ bool m_useSecureNetwork;
+ ClientSockets m_clientSockets;
+};
diff --git a/src/lib/server/ClientProxy.cpp b/src/lib/server/ClientProxy.cpp
new file mode 100644
index 0000000..5a28248
--- /dev/null
+++ b/src/lib/server/ClientProxy.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "server/ClientProxy.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "base/EventQueue.h"
+
+//
+// ClientProxy
+//
+
+ClientProxy::ClientProxy(const String& name, barrier::IStream* stream) :
+ BaseClientProxy(name),
+ m_stream(stream)
+{
+}
+
+ClientProxy::~ClientProxy()
+{
+ delete m_stream;
+}
+
+void
+ClientProxy::close(const char* msg)
+{
+ LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str()));
+ ProtocolUtil::writef(getStream(), msg);
+
+ // force the close to be sent before we return
+ getStream()->flush();
+}
+
+barrier::IStream*
+ClientProxy::getStream() const
+{
+ return m_stream;
+}
+
+void*
+ClientProxy::getEventTarget() const
+{
+ return static_cast<IScreen*>(const_cast<ClientProxy*>(this));
+}
diff --git a/src/lib/server/ClientProxy.h b/src/lib/server/ClientProxy.h
new file mode 100644
index 0000000..726ded4
--- /dev/null
+++ b/src/lib/server/ClientProxy.h
@@ -0,0 +1,91 @@
+/*
+ * 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 "server/BaseClientProxy.h"
+#include "base/Event.h"
+#include "base/String.h"
+#include "base/EventTypes.h"
+
+namespace barrier { class IStream; }
+
+//! Generic proxy for client
+class ClientProxy : public BaseClientProxy {
+public:
+ /*!
+ \c name is the name of the client.
+ */
+ ClientProxy(const String& name, barrier::IStream* adoptedStream);
+ ~ClientProxy();
+
+ //! @name manipulators
+ //@{
+
+ //! Disconnect
+ /*!
+ Ask the client to disconnect, using \p msg as the reason.
+ */
+ void close(const char* msg);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get stream
+ /*!
+ Returns the original stream passed to the c'tor.
+ */
+ barrier::IStream* getStream() const;
+
+ //@}
+
+ // IScreen
+ virtual void* getEventTarget() const;
+ virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0;
+ virtual void getShape(SInt32& x, SInt32& y,
+ SInt32& width, SInt32& height) const = 0;
+ virtual void getCursorPos(SInt32& x, SInt32& y) const = 0;
+
+ // IClient overrides
+ virtual void enter(SInt32 xAbs, SInt32 yAbs,
+ UInt32 seqNum, KeyModifierMask mask,
+ bool forScreensaver) = 0;
+ virtual bool leave() = 0;
+ virtual void setClipboard(ClipboardID, const IClipboard*) = 0;
+ virtual void grabClipboard(ClipboardID) = 0;
+ virtual void setClipboardDirty(ClipboardID, bool) = 0;
+ virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0;
+ virtual void keyRepeat(KeyID, KeyModifierMask,
+ SInt32 count, KeyButton) = 0;
+ virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0;
+ virtual void mouseDown(ButtonID) = 0;
+ virtual void mouseUp(ButtonID) = 0;
+ virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0;
+ virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0;
+ virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0;
+ virtual void screensaver(bool activate) = 0;
+ virtual void resetOptions() = 0;
+ virtual void setOptions(const OptionsList& options) = 0;
+ virtual void sendDragInfo(UInt32 fileCount, const char* info,
+ size_t size) = 0;
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0;
+
+private:
+ barrier::IStream* m_stream;
+};
diff --git a/src/lib/server/ClientProxy1_0.cpp b/src/lib/server/ClientProxy1_0.cpp
new file mode 100644
index 0000000..ee805c6
--- /dev/null
+++ b/src/lib/server/ClientProxy1_0.cpp
@@ -0,0 +1,484 @@
+/*
+ * 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 "server/ClientProxy1_0.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "barrier/XBarrier.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+
+//
+// ClientProxy1_0
+//
+
+ClientProxy1_0::ClientProxy1_0(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy(name, stream),
+ m_heartbeatTimer(NULL),
+ m_parser(&ClientProxy1_0::parseHandshakeMessage),
+ m_events(events)
+{
+ // install event handlers
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleData, NULL));
+ m_events->adoptHandler(m_events->forIStream().outputError(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleWriteError, NULL));
+ m_events->adoptHandler(m_events->forIStream().inputShutdown(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleDisconnect, NULL));
+ m_events->adoptHandler(m_events->forIStream().outputShutdown(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleWriteError, NULL));
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleFlatline, NULL));
+
+ setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath);
+
+ LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgQInfo);
+}
+
+ClientProxy1_0::~ClientProxy1_0()
+{
+ removeHandlers();
+}
+
+void
+ClientProxy1_0::disconnect()
+{
+ removeHandlers();
+ getStream()->close();
+ m_events->addEvent(Event(m_events->forClientProxy().disconnected(), getEventTarget()));
+}
+
+void
+ClientProxy1_0::removeHandlers()
+{
+ // uninstall event handlers
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputError(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().inputShutdown(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputShutdown(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(Event::kTimer, this);
+
+ // remove timer
+ removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::addHeartbeatTimer()
+{
+ if (m_heartbeatAlarm > 0.0) {
+ m_heartbeatTimer = m_events->newOneShotTimer(m_heartbeatAlarm, this);
+ }
+}
+
+void
+ClientProxy1_0::removeHeartbeatTimer()
+{
+ if (m_heartbeatTimer != NULL) {
+ m_events->deleteTimer(m_heartbeatTimer);
+ m_heartbeatTimer = NULL;
+ }
+}
+
+void
+ClientProxy1_0::resetHeartbeatTimer()
+{
+ // reset the alarm
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::resetHeartbeatRate()
+{
+ setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath);
+}
+
+void
+ClientProxy1_0::setHeartbeatRate(double, double alarm)
+{
+ m_heartbeatAlarm = alarm;
+}
+
+void
+ClientProxy1_0::handleData(const Event&, void*)
+{
+ // handle messages until there are no more. first read message code.
+ UInt8 code[4];
+ UInt32 n = getStream()->read(code, 4);
+ while (n != 0) {
+ // verify we got an entire code
+ if (n != 4) {
+ LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n));
+ disconnect();
+ return;
+ }
+
+ // parse message
+ LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
+ if (!(this->*m_parser)(code)) {
+ LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
+ disconnect();
+ return;
+ }
+
+ // next message
+ n = getStream()->read(code, 4);
+ }
+
+ // restart heartbeat timer
+ resetHeartbeatTimer();
+}
+
+bool
+ClientProxy1_0::parseHandshakeMessage(const UInt8* code)
+{
+ if (memcmp(code, kMsgCNoop, 4) == 0) {
+ // discard no-ops
+ LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
+ return true;
+ }
+ else if (memcmp(code, kMsgDInfo, 4) == 0) {
+ // future messages get parsed by parseMessage
+ m_parser = &ClientProxy1_0::parseMessage;
+ if (recvInfo()) {
+ m_events->addEvent(Event(m_events->forClientProxy().ready(), getEventTarget()));
+ addHeartbeatTimer();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ClientProxy1_0::parseMessage(const UInt8* code)
+{
+ if (memcmp(code, kMsgDInfo, 4) == 0) {
+ if (recvInfo()) {
+ m_events->addEvent(
+ Event(m_events->forIScreen().shapeChanged(), getEventTarget()));
+ return true;
+ }
+ return false;
+ }
+ else if (memcmp(code, kMsgCNoop, 4) == 0) {
+ // discard no-ops
+ LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
+ return true;
+ }
+ else if (memcmp(code, kMsgCClipboard, 4) == 0) {
+ return recvGrabClipboard();
+ }
+ else if (memcmp(code, kMsgDClipboard, 4) == 0) {
+ return recvClipboard();
+ }
+ return false;
+}
+
+void
+ClientProxy1_0::handleDisconnect(const Event&, void*)
+{
+ LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str()));
+ disconnect();
+}
+
+void
+ClientProxy1_0::handleWriteError(const Event&, void*)
+{
+ LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str()));
+ disconnect();
+}
+
+void
+ClientProxy1_0::handleFlatline(const Event&, void*)
+{
+ // didn't get a heartbeat fast enough. assume client is dead.
+ LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str()));
+ disconnect();
+}
+
+bool
+ClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ Clipboard::copy(clipboard, &m_clipboard[id].m_clipboard);
+ return true;
+}
+
+void
+ClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ x = m_info.m_x;
+ y = m_info.m_y;
+ w = m_info.m_w;
+ h = m_info.m_h;
+}
+
+void
+ClientProxy1_0::getCursorPos(SInt32& x, SInt32& y) const
+{
+ // note -- this returns the cursor pos from when we last got client info
+ x = m_info.m_mx;
+ y = m_info.m_my;
+}
+
+void
+ClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs,
+ UInt32 seqNum, KeyModifierMask mask, bool)
+{
+ LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask));
+ ProtocolUtil::writef(getStream(), kMsgCEnter,
+ xAbs, yAbs, seqNum, mask);
+}
+
+bool
+ClientProxy1_0::leave()
+{
+ LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCLeave);
+
+ // we can never prevent the user from leaving
+ return true;
+}
+
+void
+ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ // ignore -- deprecated in protocol 1.0
+}
+
+void
+ClientProxy1_0::grabClipboard(ClipboardID id)
+{
+ LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0);
+
+ // this clipboard is now dirty
+ m_clipboard[id].m_dirty = true;
+}
+
+void
+ClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty)
+{
+ m_clipboard[id].m_dirty = dirty;
+}
+
+void
+ClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton)
+{
+ LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
+ ProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask);
+}
+
+void
+ClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask,
+ SInt32 count, KeyButton)
+{
+ LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count));
+ ProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count);
+}
+
+void
+ClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton)
+{
+ LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
+ ProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask);
+}
+
+void
+ClientProxy1_0::mouseDown(ButtonID button)
+{
+ LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button));
+ ProtocolUtil::writef(getStream(), kMsgDMouseDown, button);
+}
+
+void
+ClientProxy1_0::mouseUp(ButtonID button)
+{
+ LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button));
+ ProtocolUtil::writef(getStream(), kMsgDMouseUp, button);
+}
+
+void
+ClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs)
+{
+ LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs));
+ ProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs);
+}
+
+void
+ClientProxy1_0::mouseRelativeMove(SInt32, SInt32)
+{
+ // ignore -- not supported in protocol 1.0
+}
+
+void
+ClientProxy1_0::mouseWheel(SInt32, SInt32 yDelta)
+{
+ // clients prior to 1.3 only support the y axis
+ LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), yDelta));
+ ProtocolUtil::writef(getStream(), kMsgDMouseWheel1_0, yDelta);
+}
+
+void
+ClientProxy1_0::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ // ignore -- not supported in protocol 1.0
+ LOG((CLOG_DEBUG "draggingInfoSending not supported"));
+}
+
+void
+ClientProxy1_0::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ // ignore -- not supported in protocol 1.0
+ LOG((CLOG_DEBUG "fileChunkSending not supported"));
+}
+
+void
+ClientProxy1_0::screensaver(bool on)
+{
+ LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0));
+ ProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0);
+}
+
+void
+ClientProxy1_0::resetOptions()
+{
+ LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCResetOptions);
+
+ // reset heart rate and death
+ resetHeartbeatRate();
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::setOptions(const OptionsList& options)
+{
+ LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size()));
+ ProtocolUtil::writef(getStream(), kMsgDSetOptions, &options);
+
+ // check options
+ for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) {
+ if (options[i] == kOptionHeartbeat) {
+ double rate = 1.0e-3 * static_cast<double>(options[i + 1]);
+ if (rate <= 0.0) {
+ rate = -1.0;
+ }
+ setHeartbeatRate(rate, rate * kHeartBeatsUntilDeath);
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+ }
+ }
+}
+
+bool
+ClientProxy1_0::recvInfo()
+{
+ // parse the message
+ SInt16 x, y, w, h, dummy1, mx, my;
+ if (!ProtocolUtil::readf(getStream(), kMsgDInfo + 4,
+ &x, &y, &w, &h, &dummy1, &mx, &my)) {
+ return false;
+ }
+ LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d at %d,%d", getName().c_str(), x, y, w, h, mx, my));
+
+ // validate
+ if (w <= 0 || h <= 0) {
+ return false;
+ }
+ if (mx < x || mx >= x + w || my < y || my >= y + h) {
+ mx = x + w / 2;
+ my = y + h / 2;
+ }
+
+ // save
+ m_info.m_x = x;
+ m_info.m_y = y;
+ m_info.m_w = w;
+ m_info.m_h = h;
+ m_info.m_mx = mx;
+ m_info.m_my = my;
+
+ // acknowledge receipt
+ LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCInfoAck);
+ return true;
+}
+
+bool
+ClientProxy1_0::recvClipboard()
+{
+ // deprecated in protocol 1.0
+ return false;
+}
+
+bool
+ClientProxy1_0::recvGrabClipboard()
+{
+ // parse message
+ ClipboardID id;
+ UInt32 seqNum;
+ if (!ProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) {
+ return false;
+ }
+ LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum));
+
+ // validate
+ if (id >= kClipboardEnd) {
+ return false;
+ }
+
+ // notify
+ ClipboardInfo* info = new ClipboardInfo;
+ info->m_id = id;
+ info->m_sequenceNumber = seqNum;
+ m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(),
+ getEventTarget(), info));
+
+ return true;
+}
+
+//
+// ClientProxy1_0::ClientClipboard
+//
+
+ClientProxy1_0::ClientClipboard::ClientClipboard() :
+ m_clipboard(),
+ m_sequenceNumber(0),
+ m_dirty(true)
+{
+ // do nothing
+}
diff --git a/src/lib/server/ClientProxy1_0.h b/src/lib/server/ClientProxy1_0.h
new file mode 100644
index 0000000..0720232
--- /dev/null
+++ b/src/lib/server/ClientProxy1_0.h
@@ -0,0 +1,107 @@
+/*
+ * 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 "server/ClientProxy.h"
+#include "barrier/Clipboard.h"
+#include "barrier/protocol_types.h"
+
+class Event;
+class EventQueueTimer;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.0
+class ClientProxy1_0 : public ClientProxy {
+public:
+ ClientProxy1_0(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_0();
+
+ // IScreen
+ 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 void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+
+protected:
+ virtual bool parseHandshakeMessage(const UInt8* code);
+ virtual bool parseMessage(const UInt8* code);
+
+ virtual void resetHeartbeatRate();
+ virtual void setHeartbeatRate(double rate, double alarm);
+ virtual void resetHeartbeatTimer();
+ virtual void addHeartbeatTimer();
+ virtual void removeHeartbeatTimer();
+ virtual bool recvClipboard();
+private:
+ void disconnect();
+ void removeHandlers();
+
+ void handleData(const Event&, void*);
+ void handleDisconnect(const Event&, void*);
+ void handleWriteError(const Event&, void*);
+ void handleFlatline(const Event&, void*);
+
+ bool recvInfo();
+ bool recvGrabClipboard();
+
+protected:
+ struct ClientClipboard {
+ public:
+ ClientClipboard();
+
+ public:
+ Clipboard m_clipboard;
+ UInt32 m_sequenceNumber;
+ bool m_dirty;
+ };
+
+ ClientClipboard m_clipboard[kClipboardEnd];
+
+private:
+ typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*);
+
+ ClientInfo m_info;
+ double m_heartbeatAlarm;
+ EventQueueTimer* m_heartbeatTimer;
+ MessageParser m_parser;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_1.cpp b/src/lib/server/ClientProxy1_1.cpp
new file mode 100644
index 0000000..b7eb4c4
--- /dev/null
+++ b/src/lib/server/ClientProxy1_1.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "server/ClientProxy1_1.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+
+#include <cstring>
+
+//
+// ClientProxy1_1
+//
+
+ClientProxy1_1::ClientProxy1_1(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_0(name, stream, events)
+{
+ // do nothing
+}
+
+ClientProxy1_1::~ClientProxy1_1()
+{
+ // do nothing
+}
+
+void
+ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button));
+ ProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button);
+}
+
+void
+ClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button));
+ ProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button);
+}
+
+void
+ClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button));
+ ProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button);
+}
diff --git a/src/lib/server/ClientProxy1_1.h b/src/lib/server/ClientProxy1_1.h
new file mode 100644
index 0000000..cdb674d
--- /dev/null
+++ b/src/lib/server/ClientProxy1_1.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "server/ClientProxy1_0.h"
+
+//! Proxy for client implementing protocol version 1.1
+class ClientProxy1_1 : public ClientProxy1_0 {
+public:
+ ClientProxy1_1(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_1();
+
+ // IClient overrides
+ virtual void keyDown(KeyID, KeyModifierMask, KeyButton);
+ virtual void keyRepeat(KeyID, KeyModifierMask,
+ SInt32 count, KeyButton);
+ virtual void keyUp(KeyID, KeyModifierMask, KeyButton);
+};
diff --git a/src/lib/server/ClientProxy1_2.cpp b/src/lib/server/ClientProxy1_2.cpp
new file mode 100644
index 0000000..2dd13cf
--- /dev/null
+++ b/src/lib/server/ClientProxy1_2.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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 "server/ClientProxy1_2.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+
+//
+// ClientProxy1_1
+//
+
+ClientProxy1_2::ClientProxy1_2(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_1(name, stream, events)
+{
+ // do nothing
+}
+
+ClientProxy1_2::~ClientProxy1_2()
+{
+ // do nothing
+}
+
+void
+ClientProxy1_2::mouseRelativeMove(SInt32 xRel, SInt32 yRel)
+{
+ LOG((CLOG_DEBUG2 "send mouse relative move to \"%s\" %d,%d", getName().c_str(), xRel, yRel));
+ ProtocolUtil::writef(getStream(), kMsgDMouseRelMove, xRel, yRel);
+}
diff --git a/src/lib/server/ClientProxy1_2.h b/src/lib/server/ClientProxy1_2.h
new file mode 100644
index 0000000..f6ffe94
--- /dev/null
+++ b/src/lib/server/ClientProxy1_2.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 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 "server/ClientProxy1_1.h"
+
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.2
+class ClientProxy1_2 : public ClientProxy1_1 {
+public:
+ ClientProxy1_2(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_2();
+
+ // IClient overrides
+ virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel);
+};
diff --git a/src/lib/server/ClientProxy1_3.cpp b/src/lib/server/ClientProxy1_3.cpp
new file mode 100644
index 0000000..34ea0c8
--- /dev/null
+++ b/src/lib/server/ClientProxy1_3.cpp
@@ -0,0 +1,129 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2006 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 "server/ClientProxy1_3.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <memory>
+
+//
+// ClientProxy1_3
+//
+
+ClientProxy1_3::ClientProxy1_3(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_2(name, stream, events),
+ m_keepAliveRate(kKeepAliveRate),
+ m_keepAliveTimer(NULL),
+ m_events(events)
+{
+ setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath);
+}
+
+ClientProxy1_3::~ClientProxy1_3()
+{
+ // cannot do this in superclass or our override wouldn't get called
+ removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::mouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d,%+d", getName().c_str(), xDelta, yDelta));
+ ProtocolUtil::writef(getStream(), kMsgDMouseWheel, xDelta, yDelta);
+}
+
+bool
+ClientProxy1_3::parseMessage(const UInt8* code)
+{
+ // process message
+ if (memcmp(code, kMsgCKeepAlive, 4) == 0) {
+ // reset alarm
+ resetHeartbeatTimer();
+ return true;
+ }
+ else {
+ return ClientProxy1_2::parseMessage(code);
+ }
+}
+
+void
+ClientProxy1_3::resetHeartbeatRate()
+{
+ setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath);
+}
+
+void
+ClientProxy1_3::setHeartbeatRate(double rate, double)
+{
+ m_keepAliveRate = rate;
+ ClientProxy1_2::setHeartbeatRate(rate, rate * kKeepAlivesUntilDeath);
+}
+
+void
+ClientProxy1_3::resetHeartbeatTimer()
+{
+ // reset the alarm but not the keep alive timer
+ ClientProxy1_2::removeHeartbeatTimer();
+ ClientProxy1_2::addHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::addHeartbeatTimer()
+{
+ // create and install a timer to periodically send keep alives
+ if (m_keepAliveRate > 0.0) {
+ m_keepAliveTimer = m_events->newTimer(m_keepAliveRate, NULL);
+ m_events->adoptHandler(Event::kTimer, m_keepAliveTimer,
+ new TMethodEventJob<ClientProxy1_3>(this,
+ &ClientProxy1_3::handleKeepAlive, NULL));
+ }
+
+ // superclass does the alarm
+ ClientProxy1_2::addHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::removeHeartbeatTimer()
+{
+ // remove the timer that sends keep alives periodically
+ if (m_keepAliveTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_keepAliveTimer);
+ m_events->deleteTimer(m_keepAliveTimer);
+ m_keepAliveTimer = NULL;
+ }
+
+ // superclass does the alarm
+ ClientProxy1_2::removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::handleKeepAlive(const Event&, void*)
+{
+ keepAlive();
+}
+
+void
+ClientProxy1_3::keepAlive()
+{
+ ProtocolUtil::writef(getStream(), kMsgCKeepAlive);
+}
diff --git a/src/lib/server/ClientProxy1_3.h b/src/lib/server/ClientProxy1_3.h
new file mode 100644
index 0000000..ff2ed0a
--- /dev/null
+++ b/src/lib/server/ClientProxy1_3.h
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2006 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 "server/ClientProxy1_2.h"
+
+//! Proxy for client implementing protocol version 1.3
+class ClientProxy1_3 : public ClientProxy1_2 {
+public:
+ ClientProxy1_3(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_3();
+
+ // IClient overrides
+ virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta);
+
+ void handleKeepAlive(const Event&, void*);
+
+protected:
+ // ClientProxy overrides
+ virtual bool parseMessage(const UInt8* code);
+ virtual void resetHeartbeatRate();
+ virtual void setHeartbeatRate(double rate, double alarm);
+ virtual void resetHeartbeatTimer();
+ virtual void addHeartbeatTimer();
+ virtual void removeHeartbeatTimer();
+ virtual void keepAlive();
+
+private:
+ double m_keepAliveRate;
+ EventQueueTimer* m_keepAliveTimer;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_4.cpp b/src/lib/server/ClientProxy1_4.cpp
new file mode 100644
index 0000000..43c708d
--- /dev/null
+++ b/src/lib/server/ClientProxy1_4.cpp
@@ -0,0 +1,66 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 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 "server/ClientProxy1_4.h"
+
+#include "server/Server.h"
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <memory>
+
+//
+// ClientProxy1_4
+//
+
+ClientProxy1_4::ClientProxy1_4(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_3(name, stream, events), m_server(server)
+{
+ assert(m_server != NULL);
+}
+
+ClientProxy1_4::~ClientProxy1_4()
+{
+}
+
+void
+ClientProxy1_4::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ ClientProxy1_3::keyDown(key, mask, button);
+}
+
+void
+ClientProxy1_4::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button)
+{
+ ClientProxy1_3::keyRepeat(key, mask, count, button);
+}
+
+void
+ClientProxy1_4::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ ClientProxy1_3::keyUp(key, mask, button);
+}
+
+void
+ClientProxy1_4::keepAlive()
+{
+ ClientProxy1_3::keepAlive();
+}
diff --git a/src/lib/server/ClientProxy1_4.h b/src/lib/server/ClientProxy1_4.h
new file mode 100644
index 0000000..6c55965
--- /dev/null
+++ b/src/lib/server/ClientProxy1_4.h
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 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 "server/ClientProxy1_3.h"
+
+class Server;
+
+//! Proxy for client implementing protocol version 1.4
+class ClientProxy1_4 : public ClientProxy1_3 {
+public:
+ ClientProxy1_4(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_4();
+
+ //! @name accessors
+ //@{
+
+ //! get server pointer
+ Server* getServer() { return m_server; }
+
+ //@}
+
+ // IClient overrides
+ virtual void keyDown(KeyID key, KeyModifierMask mask, KeyButton button);
+ virtual void keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button);
+ virtual void keyUp(KeyID key, KeyModifierMask mask, KeyButton button);
+ virtual void keepAlive();
+
+ Server* m_server;
+};
diff --git a/src/lib/server/ClientProxy1_5.cpp b/src/lib/server/ClientProxy1_5.cpp
new file mode 100644
index 0000000..43fd0b7
--- /dev/null
+++ b/src/lib/server/ClientProxy1_5.cpp
@@ -0,0 +1,110 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * 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 "server/ClientProxy1_5.h"
+
+#include "server/Server.h"
+#include "barrier/FileChunk.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+#include <sstream>
+
+//
+// ClientProxy1_5
+//
+
+ClientProxy1_5::ClientProxy1_5(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_4(name, stream, server, events),
+ m_events(events)
+{
+
+ m_events->adoptHandler(m_events->forFile().keepAlive(),
+ this,
+ new TMethodEventJob<ClientProxy1_3>(this,
+ &ClientProxy1_3::handleKeepAlive, NULL));
+}
+
+ClientProxy1_5::~ClientProxy1_5()
+{
+ m_events->removeHandler(m_events->forFile().keepAlive(), this);
+}
+
+void
+ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ String data(info, size);
+
+ ProtocolUtil::writef(getStream(), kMsgDDragInfo, fileCount, &data);
+}
+
+void
+ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ FileChunk::send(getStream(), mark, data, dataSize);
+}
+
+bool
+ClientProxy1_5::parseMessage(const UInt8* code)
+{
+ if (memcmp(code, kMsgDFileTransfer, 4) == 0) {
+ fileChunkReceived();
+ }
+ else if (memcmp(code, kMsgDDragInfo, 4) == 0) {
+ dragInfoReceived();
+ }
+ else {
+ return ClientProxy1_4::parseMessage(code);
+ }
+
+ return true;
+}
+
+void
+ClientProxy1_5::fileChunkReceived()
+{
+ Server* server = getServer();
+ int result = FileChunk::assemble(
+ getStream(),
+ server->getReceivedFileData(),
+ server->getExpectedFileSize());
+
+
+ if (result == kFinish) {
+ m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), server));
+ }
+ else if (result == kStart) {
+ if (server->getFakeDragFileList().size() > 0) {
+ String filename = server->getFakeDragFileList().at(0).getFilename();
+ LOG((CLOG_DEBUG "start receiving %s", filename.c_str()));
+ }
+ }
+}
+
+void
+ClientProxy1_5::dragInfoReceived()
+{
+ // parse
+ UInt32 fileNum = 0;
+ String content;
+ ProtocolUtil::readf(getStream(), kMsgDDragInfo + 4, &fileNum, &content);
+
+ m_server->dragInfoReceived(fileNum, content);
+}
diff --git a/src/lib/server/ClientProxy1_5.h b/src/lib/server/ClientProxy1_5.h
new file mode 100644
index 0000000..776de03
--- /dev/null
+++ b/src/lib/server/ClientProxy1_5.h
@@ -0,0 +1,41 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * 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 "server/ClientProxy1_4.h"
+#include "base/Stopwatch.h"
+#include "common/stdvector.h"
+
+class Server;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.5
+class ClientProxy1_5 : public ClientProxy1_4 {
+public:
+ ClientProxy1_5(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_5();
+
+ virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+ virtual bool parseMessage(const UInt8* code);
+ void fileChunkReceived();
+ void dragInfoReceived();
+
+private:
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_6.cpp b/src/lib/server/ClientProxy1_6.cpp
new file mode 100644
index 0000000..a0d2621
--- /dev/null
+++ b/src/lib/server/ClientProxy1_6.cpp
@@ -0,0 +1,100 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * 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 "server/ClientProxy1_6.h"
+
+#include "server/Server.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/ClipboardChunk.h"
+#include "io/IStream.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+//
+// ClientProxy1_6
+//
+
+ClientProxy1_6::ClientProxy1_6(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_5(name, stream, server, events),
+ m_events(events)
+{
+ m_events->adoptHandler(m_events->forClipboard().clipboardSending(),
+ this,
+ new TMethodEventJob<ClientProxy1_6>(this,
+ &ClientProxy1_6::handleClipboardSendingEvent));
+}
+
+ClientProxy1_6::~ClientProxy1_6()
+{
+}
+
+void
+ClientProxy1_6::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ // ignore if this clipboard is already clean
+ if (m_clipboard[id].m_dirty) {
+ // this clipboard is now clean
+ m_clipboard[id].m_dirty = false;
+ Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard);
+
+ String data = m_clipboard[id].m_clipboard.marshall();
+
+ size_t size = data.size();
+ LOG((CLOG_DEBUG "sending clipboard %d to \"%s\"", id, getName().c_str()));
+
+ StreamChunker::sendClipboard(data, size, id, 0, m_events, this);
+ }
+}
+
+void
+ClientProxy1_6::handleClipboardSendingEvent(const Event& event, void*)
+{
+ ClipboardChunk::send(getStream(), event.getData());
+}
+
+bool
+ClientProxy1_6::recvClipboard()
+{
+ // parse message
+ static String dataCached;
+ ClipboardID id;
+ UInt32 seq;
+
+ int r = ClipboardChunk::assemble(getStream(), 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 client \"%s\" clipboard %d seqnum=%d, size=%d",
+ getName().c_str(), id, seq, dataCached.size()));
+ // save clipboard
+ m_clipboard[id].m_clipboard.unmarshall(dataCached, 0);
+ m_clipboard[id].m_sequenceNumber = seq;
+
+ // notify
+ ClipboardInfo* info = new ClipboardInfo;
+ info->m_id = id;
+ info->m_sequenceNumber = seq;
+ m_events->addEvent(Event(m_events->forClipboard().clipboardChanged(),
+ getEventTarget(), info));
+ }
+
+ return true;
+}
diff --git a/src/lib/server/ClientProxy1_6.h b/src/lib/server/ClientProxy1_6.h
new file mode 100644
index 0000000..838cb02
--- /dev/null
+++ b/src/lib/server/ClientProxy1_6.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * 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 "server/ClientProxy1_5.h"
+
+class Server;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.6
+class ClientProxy1_6 : public ClientProxy1_5 {
+public:
+ ClientProxy1_6(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_6();
+
+ virtual void setClipboard(ClipboardID id, const IClipboard* clipboard);
+ virtual bool recvClipboard();
+
+private:
+ void handleClipboardSendingEvent(const Event&, void*);
+
+private:
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp
new file mode 100644
index 0000000..f929108
--- /dev/null
+++ b/src/lib/server/ClientProxyUnknown.cpp
@@ -0,0 +1,295 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 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 "server/ClientProxyUnknown.h"
+
+#include "server/Server.h"
+#include "server/ClientProxy1_0.h"
+#include "server/ClientProxy1_1.h"
+#include "server/ClientProxy1_2.h"
+#include "server/ClientProxy1_3.h"
+#include "server/ClientProxy1_4.h"
+#include "server/ClientProxy1_5.h"
+#include "server/ClientProxy1_6.h"
+#include "barrier/protocol_types.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/XBarrier.h"
+#include "io/IStream.h"
+#include "io/XIO.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+//
+// ClientProxyUnknown
+//
+
+ClientProxyUnknown::ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events) :
+ m_stream(stream),
+ m_proxy(NULL),
+ m_ready(false),
+ m_server(server),
+ m_events(events)
+{
+ assert(m_server != NULL);
+
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleTimeout, NULL));
+ m_timer = m_events->newOneShotTimer(timeout, this);
+ addStreamHandlers();
+
+ LOG((CLOG_DEBUG1 "saying hello"));
+ ProtocolUtil::writef(m_stream, kMsgHello,
+ kProtocolMajorVersion,
+ kProtocolMinorVersion);
+}
+
+ClientProxyUnknown::~ClientProxyUnknown()
+{
+ removeHandlers();
+ removeTimer();
+ delete m_stream;
+ delete m_proxy;
+}
+
+ClientProxy*
+ClientProxyUnknown::orphanClientProxy()
+{
+ if (m_ready) {
+ removeHandlers();
+ ClientProxy* proxy = m_proxy;
+ m_proxy = NULL;
+ return proxy;
+ }
+ else {
+ return NULL;
+ }
+}
+
+void
+ClientProxyUnknown::sendSuccess()
+{
+ m_ready = true;
+ removeTimer();
+ m_events->addEvent(Event(m_events->forClientProxyUnknown().success(), this));
+}
+
+void
+ClientProxyUnknown::sendFailure()
+{
+ delete m_proxy;
+ m_proxy = NULL;
+ m_ready = false;
+ removeHandlers();
+ removeTimer();
+ m_events->addEvent(Event(m_events->forClientProxyUnknown().failure(), this));
+}
+
+void
+ClientProxyUnknown::addStreamHandlers()
+{
+ assert(m_stream != NULL);
+
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleData));
+ m_events->adoptHandler(m_events->forIStream().outputError(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleWriteError));
+ m_events->adoptHandler(m_events->forIStream().inputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleDisconnect));
+ m_events->adoptHandler(m_events->forIStream().outputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleWriteError));
+}
+
+void
+ClientProxyUnknown::addProxyHandlers()
+{
+ assert(m_proxy != NULL);
+
+ m_events->adoptHandler(m_events->forClientProxy().ready(),
+ m_proxy,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleReady));
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(),
+ m_proxy,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleDisconnect));
+}
+
+void
+ClientProxyUnknown::removeHandlers()
+{
+ 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());
+ }
+ if (m_proxy != NULL) {
+ m_events->removeHandler(m_events->forClientProxy().ready(),
+ m_proxy);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(),
+ m_proxy);
+ }
+}
+
+void
+ClientProxyUnknown::removeTimer()
+{
+ if (m_timer != NULL) {
+ m_events->deleteTimer(m_timer);
+ m_events->removeHandler(Event::kTimer, this);
+ m_timer = NULL;
+ }
+}
+
+void
+ClientProxyUnknown::handleData(const Event&, void*)
+{
+ LOG((CLOG_DEBUG1 "parsing hello reply"));
+
+ String name("<unknown>");
+ try {
+ // limit the maximum length of the hello
+ UInt32 n = m_stream->getSize();
+ if (n > kMaxHelloLength) {
+ LOG((CLOG_DEBUG1 "hello reply too long"));
+ throw XBadClient();
+ }
+
+ // parse the reply to hello
+ SInt16 major, minor;
+ if (!ProtocolUtil::readf(m_stream, kMsgHelloBack,
+ &major, &minor, &name)) {
+ throw XBadClient();
+ }
+
+ // disallow invalid version numbers
+ if (major <= 0 || minor < 0) {
+ throw XIncompatibleClient(major, minor);
+ }
+
+ // remove stream event handlers. the proxy we're about to create
+ // may install its own handlers and we don't want to accidentally
+ // remove those later.
+ removeHandlers();
+
+ // create client proxy for highest version supported by the client
+ if (major == 1) {
+ switch (minor) {
+ case 0:
+ m_proxy = new ClientProxy1_0(name, m_stream, m_events);
+ break;
+
+ case 1:
+ m_proxy = new ClientProxy1_1(name, m_stream, m_events);
+ break;
+
+ case 2:
+ m_proxy = new ClientProxy1_2(name, m_stream, m_events);
+ break;
+
+ case 3:
+ m_proxy = new ClientProxy1_3(name, m_stream, m_events);
+ break;
+
+ case 4:
+ m_proxy = new ClientProxy1_4(name, m_stream, m_server, m_events);
+ break;
+
+ case 5:
+ m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events);
+ break;
+
+ case 6:
+ m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events);
+ break;
+ }
+ }
+
+ // hangup (with error) if version isn't supported
+ if (m_proxy == NULL) {
+ throw XIncompatibleClient(major, minor);
+ }
+
+ // the proxy is created and now proxy now owns the stream
+ LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor));
+ m_stream = NULL;
+
+ // wait until the proxy signals that it's ready or has disconnected
+ addProxyHandlers();
+ return;
+ }
+ catch (XIncompatibleClient& e) {
+ // client is incompatible
+ LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor()));
+ ProtocolUtil::writef(m_stream,
+ kMsgEIncompatible,
+ kProtocolMajorVersion, kProtocolMinorVersion);
+ }
+ catch (XBadClient&) {
+ // client not behaving
+ LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
+ ProtocolUtil::writef(m_stream, kMsgEBad);
+ }
+ catch (XBase& e) {
+ // misc error
+ LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what()));
+ }
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleWriteError(const Event&, void*)
+{
+ LOG((CLOG_NOTE "error communicating with new client"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleTimeout(const Event&, void*)
+{
+ LOG((CLOG_NOTE "new client is unresponsive"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleDisconnect(const Event&, void*)
+{
+ LOG((CLOG_NOTE "new client disconnected"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleReady(const Event&, void*)
+{
+ sendSuccess();
+}
diff --git a/src/lib/server/ClientProxyUnknown.h b/src/lib/server/ClientProxyUnknown.h
new file mode 100644
index 0000000..5d59402
--- /dev/null
+++ b/src/lib/server/ClientProxyUnknown.h
@@ -0,0 +1,71 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 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 "base/Event.h"
+#include "base/EventTypes.h"
+
+class ClientProxy;
+class EventQueueTimer;
+namespace barrier { class IStream; }
+class Server;
+class IEventQueue;
+
+class ClientProxyUnknown {
+public:
+ ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events);
+ ~ClientProxyUnknown();
+
+ //! @name manipulators
+ //@{
+
+ //! Get the client proxy
+ /*!
+ Returns the client proxy created after a successful handshake
+ (i.e. when this object sends a success event). Returns NULL
+ if the handshake is unsuccessful or incomplete.
+ */
+ ClientProxy* orphanClientProxy();
+
+ //! Get the stream
+ barrier::IStream* getStream() { return m_stream; }
+
+ //@}
+
+private:
+ void sendSuccess();
+ void sendFailure();
+ void addStreamHandlers();
+ void addProxyHandlers();
+ void removeHandlers();
+ void removeTimer();
+ void handleData(const Event&, void*);
+ void handleWriteError(const Event&, void*);
+ void handleTimeout(const Event&, void*);
+ void handleDisconnect(const Event&, void*);
+ void handleReady(const Event&, void*);
+
+private:
+ barrier::IStream* m_stream;
+ EventQueueTimer* m_timer;
+ ClientProxy* m_proxy;
+ bool m_ready;
+ Server* m_server;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp
new file mode 100644
index 0000000..3cf60a5
--- /dev/null
+++ b/src/lib/server/Config.cpp
@@ -0,0 +1,2335 @@
+/*
+ * 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 "server/Config.h"
+
+#include "server/Server.h"
+#include "barrier/KeyMap.h"
+#include "barrier/key_types.h"
+#include "net/XSocket.h"
+#include "base/IEventQueue.h"
+#include "common/stdistream.h"
+#include "common/stdostream.h"
+
+#include <cstdlib>
+
+using namespace barrier::string;
+
+//
+// Config
+//
+
+Config::Config(IEventQueue* events) :
+ m_inputFilter(events),
+ m_hasLockToScreenAction(false),
+ m_events(events)
+{
+ // do nothing
+}
+
+Config::~Config()
+{
+ // do nothing
+}
+
+bool
+Config::addScreen(const String& name)
+{
+ // alias name must not exist
+ if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // add cell
+ m_map.insert(std::make_pair(name, Cell()));
+
+ // add name
+ m_nameToCanonicalName.insert(std::make_pair(name, name));
+
+ return true;
+}
+
+bool
+Config::renameScreen(const String& oldName,
+ const String& newName)
+{
+ // get canonical name and find cell
+ String oldCanonical = getCanonicalName(oldName);
+ CellMap::iterator index = m_map.find(oldCanonical);
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // accept if names are equal but replace with new name to maintain
+ // case. otherwise, the new name must not exist.
+ if (!CaselessCmp::equal(oldName, newName) &&
+ m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // update cell
+ Cell tmpCell = index->second;
+ m_map.erase(index);
+ m_map.insert(std::make_pair(newName, tmpCell));
+
+ // update name
+ m_nameToCanonicalName.erase(oldCanonical);
+ m_nameToCanonicalName.insert(std::make_pair(newName, newName));
+
+ // update connections
+ Name oldNameObj(this, oldName);
+ for (index = m_map.begin(); index != m_map.end(); ++index) {
+ index->second.rename(oldNameObj, newName);
+ }
+
+ // update alias targets
+ if (CaselessCmp::equal(oldName, oldCanonical)) {
+ for (NameMap::iterator iter = m_nameToCanonicalName.begin();
+ iter != m_nameToCanonicalName.end(); ++iter) {
+ if (CaselessCmp::equal(
+ iter->second, oldCanonical)) {
+ iter->second = newName;
+ }
+ }
+ }
+
+ return true;
+}
+
+void
+Config::removeScreen(const String& name)
+{
+ // get canonical name and find cell
+ String canonical = getCanonicalName(name);
+ CellMap::iterator index = m_map.find(canonical);
+ if (index == m_map.end()) {
+ return;
+ }
+
+ // remove from map
+ m_map.erase(index);
+
+ // disconnect
+ Name nameObj(this, name);
+ for (index = m_map.begin(); index != m_map.end(); ++index) {
+ index->second.remove(nameObj);
+ }
+
+ // remove aliases (and canonical name)
+ for (NameMap::iterator iter = m_nameToCanonicalName.begin();
+ iter != m_nameToCanonicalName.end(); ) {
+ if (iter->second == canonical) {
+ m_nameToCanonicalName.erase(iter++);
+ }
+ else {
+ ++index;
+ }
+ }
+}
+
+void
+Config::removeAllScreens()
+{
+ m_map.clear();
+ m_nameToCanonicalName.clear();
+}
+
+bool
+Config::addAlias(const String& canonical, const String& alias)
+{
+ // alias name must not exist
+ if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // canonical name must be known
+ if (m_map.find(canonical) == m_map.end()) {
+ return false;
+ }
+
+ // insert alias
+ m_nameToCanonicalName.insert(std::make_pair(alias, canonical));
+
+ return true;
+}
+
+bool
+Config::removeAlias(const String& alias)
+{
+ // must not be a canonical name
+ if (m_map.find(alias) != m_map.end()) {
+ return false;
+ }
+
+ // find alias
+ NameMap::iterator index = m_nameToCanonicalName.find(alias);
+ if (index == m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // remove alias
+ m_nameToCanonicalName.erase(index);
+
+ return true;
+}
+
+bool
+Config::removeAliases(const String& canonical)
+{
+ // must be a canonical name
+ if (m_map.find(canonical) == m_map.end()) {
+ return false;
+ }
+
+ // find and removing matching aliases
+ for (NameMap::iterator index = m_nameToCanonicalName.begin();
+ index != m_nameToCanonicalName.end(); ) {
+ if (index->second == canonical && index->first != canonical) {
+ m_nameToCanonicalName.erase(index++);
+ }
+ else {
+ ++index;
+ }
+ }
+
+ return true;
+}
+
+void
+Config::removeAllAliases()
+{
+ // remove all names
+ m_nameToCanonicalName.clear();
+
+ // put the canonical names back in
+ for (CellMap::iterator index = m_map.begin();
+ index != m_map.end(); ++index) {
+ m_nameToCanonicalName.insert(
+ std::make_pair(index->first, index->first));
+ }
+}
+
+bool
+Config::connect(const String& srcName,
+ EDirection srcSide,
+ float srcStart, float srcEnd,
+ const String& dstName,
+ float dstStart, float dstEnd)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // add link
+ CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd));
+ CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd));
+ return index->second.add(srcEdge, dstEdge);
+}
+
+bool
+Config::disconnect(const String& srcName, EDirection srcSide)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(srcName);
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // disconnect side
+ index->second.remove(srcSide);
+
+ return true;
+}
+
+bool
+Config::disconnect(const String& srcName, EDirection srcSide, float position)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(srcName);
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // disconnect side
+ index->second.remove(srcSide, position);
+
+ return true;
+}
+
+void
+Config::setBarrierAddress(const NetworkAddress& addr)
+{
+ m_barrierAddress = addr;
+}
+
+bool
+Config::addOption(const String& name, OptionID option, OptionValue value)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::iterator index = m_map.find(name);
+ if (index != m_map.end()) {
+ options = &index->second.m_options;
+ }
+ }
+ if (options == NULL) {
+ return false;
+ }
+
+ // add option
+ options->insert(std::make_pair(option, value));
+ return true;
+}
+
+bool
+Config::removeOption(const String& name, OptionID option)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::iterator index = m_map.find(name);
+ if (index != m_map.end()) {
+ options = &index->second.m_options;
+ }
+ }
+ if (options == NULL) {
+ return false;
+ }
+
+ // remove option
+ options->erase(option);
+ return true;
+}
+
+bool
+Config::removeOptions(const String& name)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::iterator index = m_map.find(name);
+ if (index != m_map.end()) {
+ options = &index->second.m_options;
+ }
+ }
+ if (options == NULL) {
+ return false;
+ }
+
+ // remove options
+ options->clear();
+ return true;
+}
+
+bool
+Config::isValidScreenName(const String& name) const
+{
+ // name is valid if matches validname
+ // name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9]
+ // domain ::= . name
+ // validname ::= name domain*
+ // we also accept names ending in . because many OS X users have
+ // so misconfigured their systems.
+
+ // empty name is invalid
+ if (name.empty()) {
+ return false;
+ }
+
+ // check each dot separated part
+ String::size_type b = 0;
+ for (;;) {
+ // accept trailing .
+ if (b == name.size()) {
+ break;
+ }
+
+ // find end of part
+ String::size_type e = name.find('.', b);
+ if (e == String::npos) {
+ e = name.size();
+ }
+
+ // part may not be empty
+ if (e - b < 1) {
+ return false;
+ }
+
+ // check first and last characters
+ if (!(isalnum(name[b]) || name[b] == '_') ||
+ !(isalnum(name[e - 1]) || name[e - 1] == '_')) {
+ return false;
+ }
+
+ // check interior characters
+ for (String::size_type i = b; i < e; ++i) {
+ if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') {
+ return false;
+ }
+ }
+
+ // next part
+ if (e == name.size()) {
+ // no more parts
+ break;
+ }
+ b = e + 1;
+ }
+
+ return true;
+}
+
+Config::const_iterator
+Config::begin() const
+{
+ return const_iterator(m_map.begin());
+}
+
+Config::const_iterator
+Config::end() const
+{
+ return const_iterator(m_map.end());
+}
+
+Config::all_const_iterator
+Config::beginAll() const
+{
+ return m_nameToCanonicalName.begin();
+}
+
+Config::all_const_iterator
+Config::endAll() const
+{
+ return m_nameToCanonicalName.end();
+}
+
+bool
+Config::isScreen(const String& name) const
+{
+ return (m_nameToCanonicalName.count(name) > 0);
+}
+
+bool
+Config::isCanonicalName(const String& name) const
+{
+ return (!name.empty() &&
+ CaselessCmp::equal(getCanonicalName(name), name));
+}
+
+String
+Config::getCanonicalName(const String& name) const
+{
+ NameMap::const_iterator index = m_nameToCanonicalName.find(name);
+ if (index == m_nameToCanonicalName.end()) {
+ return String();
+ }
+ else {
+ return index->second;
+ }
+}
+
+String
+Config::getNeighbor(const String& srcName, EDirection srcSide,
+ float position, float* positionOut) const
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return String();
+ }
+
+ // find edge
+ const CellEdge* srcEdge, *dstEdge;
+ if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) {
+ // no neighbor
+ return "";
+ }
+ else {
+ // compute position on neighbor
+ if (positionOut != NULL) {
+ *positionOut =
+ dstEdge->inverseTransform(srcEdge->transform(position));
+ }
+
+ // return neighbor's name
+ return getCanonicalName(dstEdge->getName());
+ }
+}
+
+bool
+Config::hasNeighbor(const String& srcName, EDirection srcSide) const
+{
+ return hasNeighbor(srcName, srcSide, 0.0f, 1.0f);
+}
+
+bool
+Config::hasNeighbor(const String& srcName, EDirection srcSide,
+ float start, float end) const
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ return index->second.overlaps(CellEdge(srcSide, Interval(start, end)));
+}
+
+Config::link_const_iterator
+Config::beginNeighbor(const String& srcName) const
+{
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ assert(index != m_map.end());
+ return index->second.begin();
+}
+
+Config::link_const_iterator
+Config::endNeighbor(const String& srcName) const
+{
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ assert(index != m_map.end());
+ return index->second.end();
+}
+
+const NetworkAddress&
+Config::getBarrierAddress() const
+{
+ return m_barrierAddress;
+}
+
+const Config::ScreenOptions*
+Config::getOptions(const String& name) const
+{
+ // find options
+ const ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::const_iterator index = m_map.find(name);
+ if (index != m_map.end()) {
+ options = &index->second.m_options;
+ }
+ }
+
+ // return options
+ return options;
+}
+
+bool
+Config::hasLockToScreenAction() const
+{
+ return m_hasLockToScreenAction;
+}
+
+bool
+Config::operator==(const Config& x) const
+{
+ if (m_barrierAddress != x.m_barrierAddress) {
+ return false;
+ }
+ if (m_map.size() != x.m_map.size()) {
+ return false;
+ }
+ if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) {
+ return false;
+ }
+
+ // compare global options
+ if (m_globalOptions != x.m_globalOptions) {
+ return false;
+ }
+
+ for (CellMap::const_iterator index1 = m_map.begin(),
+ index2 = x.m_map.begin();
+ index1 != m_map.end(); ++index1, ++index2) {
+ // compare names
+ if (!CaselessCmp::equal(index1->first, index2->first)) {
+ return false;
+ }
+
+ // compare cells
+ if (index1->second != index2->second) {
+ return false;
+ }
+ }
+
+ for (NameMap::const_iterator index1 = m_nameToCanonicalName.begin(),
+ index2 = x.m_nameToCanonicalName.begin();
+ index1 != m_nameToCanonicalName.end();
+ ++index1, ++index2) {
+ if (!CaselessCmp::equal(index1->first, index2->first) ||
+ !CaselessCmp::equal(index1->second, index2->second)) {
+ return false;
+ }
+ }
+
+ // compare input filters
+ if (m_inputFilter != x.m_inputFilter) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+Config::operator!=(const Config& x) const
+{
+ return !operator==(x);
+}
+
+void
+Config::read(ConfigReadContext& context)
+{
+ Config tmp(m_events);
+ while (context.getStream()) {
+ tmp.readSection(context);
+ }
+ *this = tmp;
+}
+
+const char*
+Config::dirName(EDirection dir)
+{
+ static const char* s_name[] = { "left", "right", "up", "down" };
+
+ assert(dir >= kFirstDirection && dir <= kLastDirection);
+
+ return s_name[dir - kFirstDirection];
+}
+
+InputFilter*
+Config::getInputFilter()
+{
+ return &m_inputFilter;
+}
+
+String
+Config::formatInterval(const Interval& x)
+{
+ if (x.first == 0.0f && x.second == 1.0f) {
+ return "";
+ }
+ return barrier::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f),
+ (int)(x.second * 100.0f + 0.5f));
+}
+
+void
+Config::readSection(ConfigReadContext& s)
+{
+ static const char s_section[] = "section:";
+ static const char s_options[] = "options";
+ static const char s_screens[] = "screens";
+ static const char s_links[] = "links";
+ static const char s_aliases[] = "aliases";
+
+ String line;
+ if (!s.readLine(line)) {
+ // no more sections
+ return;
+ }
+
+ // should be a section header
+ if (line.find(s_section) != 0) {
+ throw XConfigRead(s, "found data outside section");
+ }
+
+ // get section name
+ String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1);
+ if (i == String::npos) {
+ throw XConfigRead(s, "section name is missing");
+ }
+ String name = line.substr(i);
+ i = name.find_first_of(" \t");
+ if (i != String::npos) {
+ throw XConfigRead(s, "unexpected data after section name");
+ }
+
+ // read section
+ if (name == s_options) {
+ readSectionOptions(s);
+ }
+ else if (name == s_screens) {
+ readSectionScreens(s);
+ }
+ else if (name == s_links) {
+ readSectionLinks(s);
+ }
+ else if (name == s_aliases) {
+ readSectionAliases(s);
+ }
+ else {
+ throw XConfigRead(s, "unknown section name \"%{1}\"", name);
+ }
+}
+
+void
+Config::readSectionOptions(ConfigReadContext& s)
+{
+ String line;
+ while (s.readLine(line)) {
+ // check for end of section
+ if (line == "end") {
+ return;
+ }
+
+ // parse argument: `nameAndArgs = [values][;[values]]'
+ // nameAndArgs := <name>[(arg[,...])]
+ // values := valueAndArgs[,valueAndArgs]...
+ // valueAndArgs := <value>[(arg[,...])]
+ String::size_type i = 0;
+ String name, value;
+ ConfigReadContext::ArgList nameArgs, valueArgs;
+ s.parseNameWithArgs("name", line, "=", i, name, nameArgs);
+ ++i;
+ s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);
+
+ bool handled = true;
+ if (name == "address") {
+ try {
+ m_barrierAddress = NetworkAddress(value, kDefaultPort);
+ m_barrierAddress.resolve();
+ }
+ catch (XSocketAddress& e) {
+ throw XConfigRead(s,
+ String("invalid address argument ") + e.what());
+ }
+ }
+ else if (name == "heartbeat") {
+ addOption("", kOptionHeartbeat, s.parseInt(value));
+ }
+ else if (name == "switchCorners") {
+ addOption("", kOptionScreenSwitchCorners, s.parseCorners(value));
+ }
+ else if (name == "switchCornerSize") {
+ addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value));
+ }
+ else if (name == "switchDelay") {
+ addOption("", kOptionScreenSwitchDelay, s.parseInt(value));
+ }
+ else if (name == "switchDoubleTap") {
+ addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value));
+ }
+ else if (name == "switchNeedsShift") {
+ addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value));
+ }
+ else if (name == "switchNeedsControl") {
+ addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value));
+ }
+ else if (name == "switchNeedsAlt") {
+ addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(value));
+ }
+ else if (name == "screenSaverSync") {
+ addOption("", kOptionScreenSaverSync, s.parseBoolean(value));
+ }
+ else if (name == "relativeMouseMoves") {
+ addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value));
+ }
+ else if (name == "win32KeepForeground") {
+ addOption("", kOptionWin32KeepForeground, s.parseBoolean(value));
+ }
+ else if (name == "clipboardSharing") {
+ addOption("", kOptionClipboardSharing, s.parseBoolean(value));
+ }
+
+ else {
+ handled = false;
+ }
+
+ if (handled) {
+ // make sure handled options aren't followed by more values
+ if (i < line.size() && (line[i] == ',' || line[i] == ';')) {
+ throw XConfigRead(s, "to many arguments to %s", name.c_str());
+ }
+ }
+ else {
+ // make filter rule
+ InputFilter::Rule rule(parseCondition(s, name, nameArgs));
+
+ // save first action (if any)
+ if (!value.empty() || line[i] != ';') {
+ parseAction(s, value, valueArgs, rule, true);
+ }
+
+ // get remaining activate actions
+ while (i < line.length() && line[i] != ';') {
+ ++i;
+ s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);
+ parseAction(s, value, valueArgs, rule, true);
+ }
+
+ // get deactivate actions
+ if (i < line.length() && line[i] == ';') {
+ // allow trailing ';'
+ i = line.find_first_not_of(" \t", i + 1);
+ if (i == String::npos) {
+ i = line.length();
+ }
+ else {
+ --i;
+ }
+
+ // get actions
+ while (i < line.length()) {
+ ++i;
+ s.parseNameWithArgs("value", line, ",\n",
+ i, value, valueArgs);
+ parseAction(s, value, valueArgs, rule, false);
+ }
+ }
+
+ // add rule
+ m_inputFilter.addFilterRule(rule);
+ }
+ }
+ throw XConfigRead(s, "unexpected end of options section");
+}
+
+void
+Config::readSectionScreens(ConfigReadContext& s)
+{
+ String line;
+ String screen;
+ while (s.readLine(line)) {
+ // check for end of section
+ if (line == "end") {
+ return;
+ }
+
+ // see if it's the next screen
+ if (line[line.size() - 1] == ':') {
+ // strip :
+ screen = line.substr(0, line.size() - 1);
+
+ // verify validity of screen name
+ if (!isValidScreenName(screen)) {
+ throw XConfigRead(s, "invalid screen name \"%{1}\"", screen);
+ }
+
+ // add the screen to the configuration
+ if (!addScreen(screen)) {
+ throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen);
+ }
+ }
+ else if (screen.empty()) {
+ throw XConfigRead(s, "argument before first screen");
+ }
+ else {
+ // parse argument: `<name>=<value>'
+ String::size_type i = line.find_first_of(" \t=");
+ if (i == 0) {
+ throw XConfigRead(s, "missing argument name");
+ }
+ if (i == String::npos) {
+ throw XConfigRead(s, "missing =");
+ }
+ String name = line.substr(0, i);
+ i = line.find_first_not_of(" \t", i);
+ if (i == String::npos || line[i] != '=') {
+ throw XConfigRead(s, "missing =");
+ }
+ i = line.find_first_not_of(" \t", i + 1);
+ String value;
+ if (i != String::npos) {
+ value = line.substr(i);
+ }
+
+ // handle argument
+ if (name == "halfDuplexCapsLock") {
+ addOption(screen, kOptionHalfDuplexCapsLock,
+ s.parseBoolean(value));
+ }
+ else if (name == "halfDuplexNumLock") {
+ addOption(screen, kOptionHalfDuplexNumLock,
+ s.parseBoolean(value));
+ }
+ else if (name == "halfDuplexScrollLock") {
+ addOption(screen, kOptionHalfDuplexScrollLock,
+ s.parseBoolean(value));
+ }
+ else if (name == "shift") {
+ addOption(screen, kOptionModifierMapForShift,
+ s.parseModifierKey(value));
+ }
+ else if (name == "ctrl") {
+ addOption(screen, kOptionModifierMapForControl,
+ s.parseModifierKey(value));
+ }
+ else if (name == "alt") {
+ addOption(screen, kOptionModifierMapForAlt,
+ s.parseModifierKey(value));
+ }
+ else if (name == "altgr") {
+ addOption(screen, kOptionModifierMapForAltGr,
+ s.parseModifierKey(value));
+ }
+ else if (name == "meta") {
+ addOption(screen, kOptionModifierMapForMeta,
+ s.parseModifierKey(value));
+ }
+ else if (name == "super") {
+ addOption(screen, kOptionModifierMapForSuper,
+ s.parseModifierKey(value));
+ }
+ else if (name == "xtestIsXineramaUnaware") {
+ addOption(screen, kOptionXTestXineramaUnaware,
+ s.parseBoolean(value));
+ }
+ else if (name == "switchCorners") {
+ addOption(screen, kOptionScreenSwitchCorners,
+ s.parseCorners(value));
+ }
+ else if (name == "switchCornerSize") {
+ addOption(screen, kOptionScreenSwitchCornerSize,
+ s.parseInt(value));
+ }
+ else if (name == "preserveFocus") {
+ addOption(screen, kOptionScreenPreserveFocus,
+ s.parseBoolean(value));
+ }
+ else {
+ // unknown argument
+ throw XConfigRead(s, "unknown argument \"%{1}\"", name);
+ }
+ }
+ }
+ throw XConfigRead(s, "unexpected end of screens section");
+}
+
+void
+Config::readSectionLinks(ConfigReadContext& s)
+{
+ String line;
+ String screen;
+ while (s.readLine(line)) {
+ // check for end of section
+ if (line == "end") {
+ return;
+ }
+
+ // see if it's the next screen
+ if (line[line.size() - 1] == ':') {
+ // strip :
+ screen = line.substr(0, line.size() - 1);
+
+ // verify we know about the screen
+ if (!isScreen(screen)) {
+ throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
+ }
+ if (!isCanonicalName(screen)) {
+ throw XConfigRead(s, "cannot use screen name alias here");
+ }
+ }
+ else if (screen.empty()) {
+ throw XConfigRead(s, "argument before first screen");
+ }
+ else {
+ // parse argument: `<name>[(<s0>,<e0>)]=<value>[(<s1>,<e1>)]'
+ // the stuff in brackets is optional. interval values must be
+ // in the range [0,100] and start < end. if not given the
+ // interval is taken to be (0,100).
+ String::size_type i = 0;
+ String side, dstScreen, srcArgString, dstArgString;
+ ConfigReadContext::ArgList srcArgs, dstArgs;
+ s.parseNameWithArgs("link", line, "=", i, side, srcArgs);
+ ++i;
+ s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs);
+ Interval srcInterval(s.parseInterval(srcArgs));
+ Interval dstInterval(s.parseInterval(dstArgs));
+
+ // handle argument
+ EDirection dir;
+ if (side == "left") {
+ dir = kLeft;
+ }
+ else if (side == "right") {
+ dir = kRight;
+ }
+ else if (side == "up") {
+ dir = kTop;
+ }
+ else if (side == "down") {
+ dir = kBottom;
+ }
+ else {
+ // unknown argument
+ throw XConfigRead(s, "unknown side \"%{1}\" in link", side);
+ }
+ if (!isScreen(dstScreen)) {
+ throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen);
+ }
+ if (!connect(screen, dir,
+ srcInterval.first, srcInterval.second,
+ dstScreen,
+ dstInterval.first, dstInterval.second)) {
+ throw XConfigRead(s, "overlapping range");
+ }
+ }
+ }
+ throw XConfigRead(s, "unexpected end of links section");
+}
+
+void
+Config::readSectionAliases(ConfigReadContext& s)
+{
+ String line;
+ String screen;
+ while (s.readLine(line)) {
+ // check for end of section
+ if (line == "end") {
+ return;
+ }
+
+ // see if it's the next screen
+ if (line[line.size() - 1] == ':') {
+ // strip :
+ screen = line.substr(0, line.size() - 1);
+
+ // verify we know about the screen
+ if (!isScreen(screen)) {
+ throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
+ }
+ if (!isCanonicalName(screen)) {
+ throw XConfigRead(s, "cannot use screen name alias here");
+ }
+ }
+ else if (screen.empty()) {
+ throw XConfigRead(s, "argument before first screen");
+ }
+ else {
+ // verify validity of screen name
+ if (!isValidScreenName(line)) {
+ throw XConfigRead(s, "invalid screen alias \"%{1}\"", line);
+ }
+
+ // add alias
+ if (!addAlias(screen, line)) {
+ throw XConfigRead(s, "alias \"%{1}\" is already used", line);
+ }
+ }
+ }
+ throw XConfigRead(s, "unexpected end of aliases section");
+}
+
+
+InputFilter::Condition*
+Config::parseCondition(ConfigReadContext& s,
+ const String& name, const std::vector<String>& args)
+{
+ if (name == "keystroke") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)");
+ }
+
+ IPlatformScreen::KeyInfo* keyInfo = s.parseKeystroke(args[0]);
+
+ return new InputFilter::KeystrokeCondition(m_events, keyInfo);
+ }
+
+ if (name == "mousebutton") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)");
+ }
+
+ IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
+
+ return new InputFilter::MouseButtonCondition(m_events, mouseInfo);
+ }
+
+ if (name == "connect") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: connect([screen])");
+ }
+
+ String screen = args[0];
+ if (isScreen(screen)) {
+ screen = getCanonicalName(screen);
+ }
+ else if (!screen.empty()) {
+ throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen);
+ }
+
+ return new InputFilter::ScreenConnectedCondition(m_events, screen);
+ }
+
+ throw XConfigRead(s, "unknown argument \"%{1}\"", name);
+}
+
+void
+Config::parseAction(ConfigReadContext& s,
+ const String& name, const std::vector<String>& args,
+ InputFilter::Rule& rule, bool activate)
+{
+ InputFilter::Action* action;
+
+ if (name == "keystroke" || name == "keyDown" || name == "keyUp") {
+ if (args.size() < 1 || args.size() > 2) {
+ throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])");
+ }
+
+ IPlatformScreen::KeyInfo* keyInfo;
+ if (args.size() == 1) {
+ keyInfo = s.parseKeystroke(args[0]);
+ }
+ else {
+ std::set<String> screens;
+ parseScreens(s, args[1], screens);
+ keyInfo = s.parseKeystroke(args[0], screens);
+ }
+
+ if (name == "keystroke") {
+ IPlatformScreen::KeyInfo* keyInfo2 =
+ IKeyState::KeyInfo::alloc(*keyInfo);
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo2, true);
+ rule.adoptAction(action, true);
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
+ activate = false;
+ }
+ else if (name == "keyDown") {
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, true);
+ }
+ else {
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
+ }
+ }
+
+ else if (name == "mousebutton" ||
+ name == "mouseDown" || name == "mouseUp") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)");
+ }
+
+ IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
+
+ if (name == "mousebutton") {
+ IPlatformScreen::ButtonInfo* mouseInfo2 =
+ IPlatformScreen::ButtonInfo::alloc(*mouseInfo);
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo2, true);
+ rule.adoptAction(action, true);
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
+ activate = false;
+ }
+ else if (name == "mouseDown") {
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo, true);
+ }
+ else {
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
+ }
+ }
+
+/* XXX -- not supported
+ else if (name == "modifier") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: modifier(modifiers)");
+ }
+
+ KeyModifierMask mask = s.parseModifier(args[0]);
+
+ action = new InputFilter::ModifierAction(mask, ~mask);
+ }
+*/
+
+ else if (name == "switchToScreen") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: switchToScreen(name)");
+ }
+
+ String screen = args[0];
+ if (isScreen(screen)) {
+ screen = getCanonicalName(screen);
+ }
+ else if (!screen.empty()) {
+ throw XConfigRead(s, "unknown screen name in switchToScreen");
+ }
+
+ action = new InputFilter::SwitchToScreenAction(m_events, screen);
+ }
+
+ else if (name == "switchInDirection") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: switchInDirection(<left|right|up|down>)");
+ }
+
+ EDirection direction;
+ if (args[0] == "left") {
+ direction = kLeft;
+ }
+ else if (args[0] == "right") {
+ direction = kRight;
+ }
+ else if (args[0] == "up") {
+ direction = kTop;
+ }
+ else if (args[0] == "down") {
+ direction = kBottom;
+ }
+ else {
+ throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]);
+ }
+
+ action = new InputFilter::SwitchInDirectionAction(m_events, direction);
+ }
+
+ else if (name == "lockCursorToScreen") {
+ if (args.size() > 1) {
+ throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
+ }
+
+ InputFilter::LockCursorToScreenAction::Mode mode =
+ InputFilter::LockCursorToScreenAction::kToggle;
+ if (args.size() == 1) {
+ if (args[0] == "off") {
+ mode = InputFilter::LockCursorToScreenAction::kOff;
+ }
+ else if (args[0] == "on") {
+ mode = InputFilter::LockCursorToScreenAction::kOn;
+ }
+ else if (args[0] == "toggle") {
+ mode = InputFilter::LockCursorToScreenAction::kToggle;
+ }
+ else {
+ throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
+ }
+ }
+
+ if (mode != InputFilter::LockCursorToScreenAction::kOff) {
+ m_hasLockToScreenAction = true;
+ }
+
+ action = new InputFilter::LockCursorToScreenAction(m_events, mode);
+ }
+
+ else if (name == "keyboardBroadcast") {
+ if (args.size() > 2) {
+ throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
+ }
+
+ InputFilter::KeyboardBroadcastAction::Mode mode =
+ InputFilter::KeyboardBroadcastAction::kToggle;
+ if (args.size() >= 1) {
+ if (args[0] == "off") {
+ mode = InputFilter::KeyboardBroadcastAction::kOff;
+ }
+ else if (args[0] == "on") {
+ mode = InputFilter::KeyboardBroadcastAction::kOn;
+ }
+ else if (args[0] == "toggle") {
+ mode = InputFilter::KeyboardBroadcastAction::kToggle;
+ }
+ else {
+ throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
+ }
+ }
+
+ std::set<String> screens;
+ if (args.size() >= 2) {
+ parseScreens(s, args[1], screens);
+ }
+
+ action = new InputFilter::KeyboardBroadcastAction(m_events, mode, screens);
+ }
+
+ else {
+ throw XConfigRead(s, "unknown action argument \"%{1}\"", name);
+ }
+
+ rule.adoptAction(action, activate);
+}
+
+void
+Config::parseScreens(ConfigReadContext& c,
+ const String& s, std::set<String>& screens) const
+{
+ screens.clear();
+
+ String::size_type i = 0;
+ while (i < s.size()) {
+ // find end of next screen name
+ String::size_type j = s.find(':', i);
+ if (j == String::npos) {
+ j = s.size();
+ }
+
+ // extract name
+ String rawName;
+ i = s.find_first_not_of(" \t", i);
+ if (i < j) {
+ rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1);
+ }
+
+ // add name
+ if (rawName == "*") {
+ screens.insert("*");
+ }
+ else if (!rawName.empty()) {
+ String name = getCanonicalName(rawName);
+ if (name.empty()) {
+ throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName);
+ }
+ screens.insert(name);
+ }
+
+ // next
+ i = j + 1;
+ }
+}
+
+const char*
+Config::getOptionName(OptionID id)
+{
+ if (id == kOptionHalfDuplexCapsLock) {
+ return "halfDuplexCapsLock";
+ }
+ if (id == kOptionHalfDuplexNumLock) {
+ return "halfDuplexNumLock";
+ }
+ if (id == kOptionHalfDuplexScrollLock) {
+ return "halfDuplexScrollLock";
+ }
+ if (id == kOptionModifierMapForShift) {
+ return "shift";
+ }
+ if (id == kOptionModifierMapForControl) {
+ return "ctrl";
+ }
+ if (id == kOptionModifierMapForAlt) {
+ return "alt";
+ }
+ if (id == kOptionModifierMapForAltGr) {
+ return "altgr";
+ }
+ if (id == kOptionModifierMapForMeta) {
+ return "meta";
+ }
+ if (id == kOptionModifierMapForSuper) {
+ return "super";
+ }
+ if (id == kOptionHeartbeat) {
+ return "heartbeat";
+ }
+ if (id == kOptionScreenSwitchCorners) {
+ return "switchCorners";
+ }
+ if (id == kOptionScreenSwitchCornerSize) {
+ return "switchCornerSize";
+ }
+ if (id == kOptionScreenSwitchDelay) {
+ return "switchDelay";
+ }
+ if (id == kOptionScreenSwitchTwoTap) {
+ return "switchDoubleTap";
+ }
+ if (id == kOptionScreenSwitchNeedsShift) {
+ return "switchNeedsShift";
+ }
+ if (id == kOptionScreenSwitchNeedsControl) {
+ return "switchNeedsControl";
+ }
+ if (id == kOptionScreenSwitchNeedsAlt) {
+ return "switchNeedsAlt";
+ }
+ if (id == kOptionScreenSaverSync) {
+ return "screenSaverSync";
+ }
+ if (id == kOptionXTestXineramaUnaware) {
+ return "xtestIsXineramaUnaware";
+ }
+ if (id == kOptionRelativeMouseMoves) {
+ return "relativeMouseMoves";
+ }
+ if (id == kOptionWin32KeepForeground) {
+ return "win32KeepForeground";
+ }
+ if (id == kOptionScreenPreserveFocus) {
+ return "preserveFocus";
+ }
+ if (id == kOptionClipboardSharing) {
+ return "clipboardSharing";
+ }
+ return NULL;
+}
+
+String
+Config::getOptionValue(OptionID id, OptionValue value)
+{
+ if (id == kOptionHalfDuplexCapsLock ||
+ id == kOptionHalfDuplexNumLock ||
+ id == kOptionHalfDuplexScrollLock ||
+ id == kOptionScreenSwitchNeedsShift ||
+ id == kOptionScreenSwitchNeedsControl ||
+ id == kOptionScreenSwitchNeedsAlt ||
+ id == kOptionScreenSaverSync ||
+ id == kOptionXTestXineramaUnaware ||
+ id == kOptionRelativeMouseMoves ||
+ id == kOptionWin32KeepForeground ||
+ id == kOptionScreenPreserveFocus ||
+ id == kOptionClipboardSharing) {
+ return (value != 0) ? "true" : "false";
+ }
+ if (id == kOptionModifierMapForShift ||
+ id == kOptionModifierMapForControl ||
+ id == kOptionModifierMapForAlt ||
+ id == kOptionModifierMapForAltGr ||
+ id == kOptionModifierMapForMeta ||
+ id == kOptionModifierMapForSuper) {
+ switch (value) {
+ case kKeyModifierIDShift:
+ return "shift";
+
+ case kKeyModifierIDControl:
+ return "ctrl";
+
+ case kKeyModifierIDAlt:
+ return "alt";
+
+ case kKeyModifierIDAltGr:
+ return "altgr";
+
+ case kKeyModifierIDMeta:
+ return "meta";
+
+ case kKeyModifierIDSuper:
+ return "super";
+
+ default:
+ return "none";
+ }
+ }
+ if (id == kOptionHeartbeat ||
+ id == kOptionScreenSwitchCornerSize ||
+ id == kOptionScreenSwitchDelay ||
+ id == kOptionScreenSwitchTwoTap) {
+ return barrier::string::sprintf("%d", value);
+ }
+ if (id == kOptionScreenSwitchCorners) {
+ std::string result("none");
+ if ((value & kTopLeftMask) != 0) {
+ result += " +top-left";
+ }
+ if ((value & kTopRightMask) != 0) {
+ result += " +top-right";
+ }
+ if ((value & kBottomLeftMask) != 0) {
+ result += " +bottom-left";
+ }
+ if ((value & kBottomRightMask) != 0) {
+ result += " +bottom-right";
+ }
+ return result;
+ }
+
+ return "";
+}
+
+
+//
+// Config::Name
+//
+
+Config::Name::Name(Config* config, const String& name) :
+ m_config(config),
+ m_name(config->getCanonicalName(name))
+{
+ // do nothing
+}
+
+bool
+Config::Name::operator==(const String& name) const
+{
+ String canonical = m_config->getCanonicalName(name);
+ return CaselessCmp::equal(canonical, m_name);
+}
+
+
+//
+// Config::CellEdge
+//
+
+Config::CellEdge::CellEdge(EDirection side, float position)
+{
+ init("", side, Interval(position, position));
+}
+
+Config::CellEdge::CellEdge(EDirection side, const Interval& interval)
+{
+ assert(interval.first >= 0.0f);
+ assert(interval.second <= 1.0f);
+ assert(interval.first < interval.second);
+
+ init("", side, interval);
+}
+
+Config::CellEdge::CellEdge(const String& name,
+ EDirection side, const Interval& interval)
+{
+ assert(interval.first >= 0.0f);
+ assert(interval.second <= 1.0f);
+ assert(interval.first < interval.second);
+
+ init(name, side, interval);
+}
+
+Config::CellEdge::~CellEdge()
+{
+ // do nothing
+}
+
+void
+Config::CellEdge::init(const String& name, EDirection side,
+ const Interval& interval)
+{
+ assert(side != kNoDirection);
+
+ m_name = name;
+ m_side = side;
+ m_interval = interval;
+}
+
+Config::Interval
+Config::CellEdge::getInterval() const
+{
+ return m_interval;
+}
+
+void
+Config::CellEdge::setName(const String& newName)
+{
+ m_name = newName;
+}
+
+String
+Config::CellEdge::getName() const
+{
+ return m_name;
+}
+
+EDirection
+Config::CellEdge::getSide() const
+{
+ return m_side;
+}
+
+bool
+Config::CellEdge::overlaps(const CellEdge& edge) const
+{
+ const Interval& x = m_interval;
+ const Interval& y = edge.m_interval;
+ if (m_side != edge.m_side) {
+ return false;
+ }
+ return (x.first >= y.first && x.first < y.second) ||
+ (x.second > y.first && x.second <= y.second) ||
+ (y.first >= x.first && y.first < x.second) ||
+ (y.second > x.first && y.second <= x.second);
+}
+
+bool
+Config::CellEdge::isInside(float x) const
+{
+ return (x >= m_interval.first && x < m_interval.second);
+}
+
+float
+Config::CellEdge::transform(float x) const
+{
+ return (x - m_interval.first) / (m_interval.second - m_interval.first);
+}
+
+
+float
+Config::CellEdge::inverseTransform(float x) const
+{
+ return x * (m_interval.second - m_interval.first) + m_interval.first;
+}
+
+bool
+Config::CellEdge::operator<(const CellEdge& o) const
+{
+ if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) {
+ return true;
+ }
+ else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) {
+ return false;
+ }
+
+ return (m_interval.first < o.m_interval.first);
+}
+
+bool
+Config::CellEdge::operator==(const CellEdge& x) const
+{
+ return (m_side == x.m_side && m_interval == x.m_interval);
+}
+
+bool
+Config::CellEdge::operator!=(const CellEdge& x) const
+{
+ return !operator==(x);
+}
+
+
+//
+// Config::Cell
+//
+
+bool
+Config::Cell::add(const CellEdge& src, const CellEdge& dst)
+{
+ // cannot add an edge that overlaps other existing edges but we
+ // can exactly replace an edge.
+ if (!hasEdge(src) && overlaps(src)) {
+ return false;
+ }
+
+ m_neighbors.erase(src);
+ m_neighbors.insert(std::make_pair(src, dst));
+ return true;
+}
+
+void
+Config::Cell::remove(EDirection side)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ) {
+ if (j->first.getSide() == side) {
+ m_neighbors.erase(j++);
+ }
+ else {
+ ++j;
+ }
+ }
+}
+
+void
+Config::Cell::remove(EDirection side, float position)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ++j) {
+ if (j->first.getSide() == side && j->first.isInside(position)) {
+ m_neighbors.erase(j);
+ break;
+ }
+ }
+}
+void
+Config::Cell::remove(const Name& name)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ) {
+ if (name == j->second.getName()) {
+ m_neighbors.erase(j++);
+ }
+ else {
+ ++j;
+ }
+ }
+}
+
+void
+Config::Cell::rename(const Name& oldName, const String& newName)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ++j) {
+ if (oldName == j->second.getName()) {
+ j->second.setName(newName);
+ }
+ }
+}
+
+bool
+Config::Cell::hasEdge(const CellEdge& edge) const
+{
+ EdgeLinks::const_iterator i = m_neighbors.find(edge);
+ return (i != m_neighbors.end() && i->first == edge);
+}
+
+bool
+Config::Cell::overlaps(const CellEdge& edge) const
+{
+ EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
+ if (i != m_neighbors.end() && i->first.overlaps(edge)) {
+ return true;
+ }
+ if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) {
+ return true;
+ }
+ return false;
+}
+
+bool
+Config::Cell::getLink(EDirection side, float position,
+ const CellEdge*& src, const CellEdge*& dst) const
+{
+ CellEdge edge(side, position);
+ EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
+ if (i == m_neighbors.begin()) {
+ return false;
+ }
+ --i;
+ if (i->first.getSide() == side && i->first.isInside(position)) {
+ src = &i->first;
+ dst = &i->second;
+ return true;
+ }
+ return false;
+}
+
+bool
+Config::Cell::operator==(const Cell& x) const
+{
+ // compare options
+ if (m_options != x.m_options) {
+ return false;
+ }
+
+ // compare links
+ if (m_neighbors.size() != x.m_neighbors.size()) {
+ return false;
+ }
+ for (EdgeLinks::const_iterator index1 = m_neighbors.begin(),
+ index2 = x.m_neighbors.begin();
+ index1 != m_neighbors.end();
+ ++index1, ++index2) {
+ if (index1->first != index2->first) {
+ return false;
+ }
+ if (index1->second != index2->second) {
+ return false;
+ }
+
+ // operator== doesn't compare names. only compare destination
+ // names.
+ if (!CaselessCmp::equal(index1->second.getName(),
+ index2->second.getName())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+Config::Cell::operator!=(const Cell& x) const
+{
+ return !operator==(x);
+}
+
+Config::Cell::const_iterator
+Config::Cell::begin() const
+{
+ return m_neighbors.begin();
+}
+
+Config::Cell::const_iterator
+Config::Cell::end() const
+{
+ return m_neighbors.end();
+}
+
+
+//
+// Config I/O
+//
+
+std::istream&
+operator>>(std::istream& s, Config& config)
+{
+ ConfigReadContext context(s);
+ config.read(context);
+ return s;
+}
+
+std::ostream&
+operator<<(std::ostream& s, const Config& config)
+{
+ // screens section
+ s << "section: screens" << std::endl;
+ for (Config::const_iterator screen = config.begin();
+ screen != config.end(); ++screen) {
+ s << "\t" << screen->c_str() << ":" << std::endl;
+ const Config::ScreenOptions* options = config.getOptions(*screen);
+ if (options != NULL && options->size() > 0) {
+ for (Config::ScreenOptions::const_iterator
+ option = options->begin();
+ option != options->end(); ++option) {
+ const char* name = Config::getOptionName(option->first);
+ String value = Config::getOptionValue(option->first,
+ option->second);
+ if (name != NULL && !value.empty()) {
+ s << "\t\t" << name << " = " << value << std::endl;
+ }
+ }
+ }
+ }
+ s << "end" << std::endl;
+
+ // links section
+ String neighbor;
+ s << "section: links" << std::endl;
+ for (Config::const_iterator screen = config.begin();
+ screen != config.end(); ++screen) {
+ s << "\t" << screen->c_str() << ":" << std::endl;
+
+ for (Config::link_const_iterator
+ link = config.beginNeighbor(*screen),
+ nend = config.endNeighbor(*screen); link != nend; ++link) {
+ s << "\t\t" << Config::dirName(link->first.getSide()) <<
+ Config::formatInterval(link->first.getInterval()) <<
+ " = " << link->second.getName().c_str() <<
+ Config::formatInterval(link->second.getInterval()) <<
+ std::endl;
+ }
+ }
+ s << "end" << std::endl;
+
+ // aliases section (if there are any)
+ if (config.m_map.size() != config.m_nameToCanonicalName.size()) {
+ // map canonical to alias
+ typedef std::multimap<String, String,
+ CaselessCmp> CMNameMap;
+ CMNameMap aliases;
+ for (Config::NameMap::const_iterator
+ index = config.m_nameToCanonicalName.begin();
+ index != config.m_nameToCanonicalName.end();
+ ++index) {
+ if (index->first != index->second) {
+ aliases.insert(std::make_pair(index->second, index->first));
+ }
+ }
+
+ // dump it
+ String screen;
+ s << "section: aliases" << std::endl;
+ for (CMNameMap::const_iterator index = aliases.begin();
+ index != aliases.end(); ++index) {
+ if (index->first != screen) {
+ screen = index->first;
+ s << "\t" << screen.c_str() << ":" << std::endl;
+ }
+ s << "\t\t" << index->second.c_str() << std::endl;
+ }
+ s << "end" << std::endl;
+ }
+
+ // options section
+ s << "section: options" << std::endl;
+ const Config::ScreenOptions* options = config.getOptions("");
+ if (options != NULL && options->size() > 0) {
+ for (Config::ScreenOptions::const_iterator
+ option = options->begin();
+ option != options->end(); ++option) {
+ const char* name = Config::getOptionName(option->first);
+ String value = Config::getOptionValue(option->first,
+ option->second);
+ if (name != NULL && !value.empty()) {
+ s << "\t" << name << " = " << value << std::endl;
+ }
+ }
+ }
+ if (config.m_barrierAddress.isValid()) {
+ s << "\taddress = " <<
+ config.m_barrierAddress.getHostname().c_str() << std::endl;
+ }
+ s << config.m_inputFilter.format("\t");
+ s << "end" << std::endl;
+
+ return s;
+}
+
+
+//
+// ConfigReadContext
+//
+
+ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) :
+ m_stream(s),
+ m_line(firstLine - 1)
+{
+ // do nothing
+}
+
+ConfigReadContext::~ConfigReadContext()
+{
+ // do nothing
+}
+
+bool
+ConfigReadContext::readLine(String& line)
+{
+ ++m_line;
+ while (std::getline(m_stream, line)) {
+ // strip leading whitespace
+ String::size_type i = line.find_first_not_of(" \t");
+ if (i != String::npos) {
+ line.erase(0, i);
+ }
+
+ // strip comments and then trailing whitespace
+ i = line.find('#');
+ if (i != String::npos) {
+ line.erase(i);
+ }
+ i = line.find_last_not_of(" \r\t");
+ if (i != String::npos) {
+ line.erase(i + 1);
+ }
+
+ // return non empty line
+ if (!line.empty()) {
+ // make sure there are no invalid characters
+ for (i = 0; i < line.length(); ++i) {
+ if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') {
+ throw XConfigRead(*this,
+ "invalid character %{1}",
+ barrier::string::sprintf("%#2x", line[i]));
+ }
+ }
+
+ return true;
+ }
+
+ // next line
+ ++m_line;
+ }
+ return false;
+}
+
+UInt32
+ConfigReadContext::getLineNumber() const
+{
+ return m_line;
+}
+
+bool
+ConfigReadContext::operator!() const
+{
+ return !m_stream;
+}
+
+OptionValue
+ConfigReadContext::parseBoolean(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "true")) {
+ return static_cast<OptionValue>(true);
+ }
+ if (CaselessCmp::equal(arg, "false")) {
+ return static_cast<OptionValue>(false);
+ }
+ throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseInt(const String& arg) const
+{
+ const char* s = arg.c_str();
+ char* end;
+ long tmp = strtol(s, &end, 10);
+ if (*end != '\0') {
+ // invalid characters
+ throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg);
+ }
+ OptionValue value = static_cast<OptionValue>(tmp);
+ if (value != tmp) {
+ // out of range
+ throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg);
+ }
+ return value;
+}
+
+OptionValue
+ConfigReadContext::parseModifierKey(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "shift")) {
+ return static_cast<OptionValue>(kKeyModifierIDShift);
+ }
+ if (CaselessCmp::equal(arg, "ctrl")) {
+ return static_cast<OptionValue>(kKeyModifierIDControl);
+ }
+ if (CaselessCmp::equal(arg, "alt")) {
+ return static_cast<OptionValue>(kKeyModifierIDAlt);
+ }
+ if (CaselessCmp::equal(arg, "altgr")) {
+ return static_cast<OptionValue>(kKeyModifierIDAltGr);
+ }
+ if (CaselessCmp::equal(arg, "meta")) {
+ return static_cast<OptionValue>(kKeyModifierIDMeta);
+ }
+ if (CaselessCmp::equal(arg, "super")) {
+ return static_cast<OptionValue>(kKeyModifierIDSuper);
+ }
+ if (CaselessCmp::equal(arg, "none")) {
+ return static_cast<OptionValue>(kKeyModifierIDNull);
+ }
+ throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseCorner(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "left")) {
+ return kTopLeftMask | kBottomLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "right")) {
+ return kTopRightMask | kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "top")) {
+ return kTopLeftMask | kTopRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom")) {
+ return kBottomLeftMask | kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "top-left")) {
+ return kTopLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "top-right")) {
+ return kTopRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom-left")) {
+ return kBottomLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom-right")) {
+ return kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "none")) {
+ return kNoCornerMask;
+ }
+ else if (CaselessCmp::equal(arg, "all")) {
+ return kAllCornersMask;
+ }
+ throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseCorners(const String& args) const
+{
+ // find first token
+ String::size_type i = args.find_first_not_of(" \t", 0);
+ if (i == String::npos) {
+ throw XConfigRead(*this, "missing corner argument");
+ }
+ String::size_type j = args.find_first_of(" \t", i);
+
+ // parse first corner token
+ OptionValue corners = parseCorner(args.substr(i, j - i));
+
+ // get +/-
+ i = args.find_first_not_of(" \t", j);
+ while (i != String::npos) {
+ // parse +/-
+ bool add;
+ if (args[i] == '-') {
+ add = false;
+ }
+ else if (args[i] == '+') {
+ add = true;
+ }
+ else {
+ throw XConfigRead(*this,
+ "invalid corner operator \"%{1}\"",
+ String(args.c_str() + i, 1));
+ }
+
+ // get next corner token
+ i = args.find_first_not_of(" \t", i + 1);
+ j = args.find_first_of(" \t", i);
+ if (i == String::npos) {
+ throw XConfigRead(*this, "missing corner argument");
+ }
+
+ // parse next corner token
+ if (add) {
+ corners |= parseCorner(args.substr(i, j - i));
+ }
+ else {
+ corners &= ~parseCorner(args.substr(i, j - i));
+ }
+ i = args.find_first_not_of(" \t", j);
+ }
+
+ return corners;
+}
+
+Config::Interval
+ConfigReadContext::parseInterval(const ArgList& args) const
+{
+ if (args.size() == 0) {
+ return Config::Interval(0.0f, 1.0f);
+ }
+ if (args.size() != 2 || args[0].empty() || args[1].empty()) {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+
+ char* end;
+ double startValue = strtod(args[0].c_str(), &end);
+ if (end[0] != '\0') {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+ double endValue = strtod(args[1].c_str(), &end);
+ if (end[0] != '\0') {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+
+ if (startValue < 0 || startValue > 100 ||
+ endValue < 0 || endValue > 100 ||
+ startValue >= endValue) {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+
+ return Config::Interval(startValue / 100.0f, endValue / 100.0f);
+}
+
+void
+ConfigReadContext::parseNameWithArgs(
+ const String& type, const String& line,
+ const String& delim, String::size_type& index,
+ String& name, ArgList& args) const
+{
+ // skip leading whitespace
+ String::size_type i = line.find_first_not_of(" \t", index);
+ if (i == String::npos) {
+ throw XConfigRead(*this, String("missing ") + type);
+ }
+
+ // find end of name
+ String::size_type j = line.find_first_of(" \t(" + delim, i);
+ if (j == String::npos) {
+ j = line.length();
+ }
+
+ // save name
+ name = line.substr(i, j - i);
+ args.clear();
+
+ // is it okay to not find a delimiter?
+ bool needDelim = (!delim.empty() && delim.find('\n') == String::npos);
+
+ // skip whitespace
+ i = line.find_first_not_of(" \t", j);
+ if (i == String::npos && needDelim) {
+ // expected delimiter but didn't find it
+ throw XConfigRead(*this, String("missing ") + delim[0]);
+ }
+ if (i == String::npos) {
+ // no arguments
+ index = line.length();
+ return;
+ }
+ if (line[i] != '(') {
+ // no arguments
+ index = i;
+ return;
+ }
+
+ // eat '('
+ ++i;
+
+ // parse arguments
+ j = line.find_first_of(",)", i);
+ while (j != String::npos) {
+ // extract arg
+ String arg(line.substr(i, j - i));
+ i = j;
+
+ // trim whitespace
+ j = arg.find_first_not_of(" \t");
+ if (j != String::npos) {
+ arg.erase(0, j);
+ }
+ j = arg.find_last_not_of(" \t");
+ if (j != String::npos) {
+ arg.erase(j + 1);
+ }
+
+ // save arg
+ args.push_back(arg);
+
+ // exit loop at end of arguments
+ if (line[i] == ')') {
+ break;
+ }
+
+ // eat ','
+ ++i;
+
+ // next
+ j = line.find_first_of(",)", i);
+ }
+
+ // verify ')'
+ if (j == String::npos) {
+ // expected )
+ throw XConfigRead(*this, "missing )");
+ }
+
+ // eat ')'
+ ++i;
+
+ // skip whitespace
+ j = line.find_first_not_of(" \t", i);
+ if (j == String::npos && needDelim) {
+ // expected delimiter but didn't find it
+ throw XConfigRead(*this, String("missing ") + delim[0]);
+ }
+
+ // verify delimiter
+ if (needDelim && delim.find(line[j]) == String::npos) {
+ throw XConfigRead(*this, String("expected ") + delim[0]);
+ }
+
+ if (j == String::npos) {
+ j = line.length();
+ }
+
+ index = j;
+ return;
+}
+
+IPlatformScreen::KeyInfo*
+ConfigReadContext::parseKeystroke(const String& keystroke) const
+{
+ return parseKeystroke(keystroke, std::set<String>());
+}
+
+IPlatformScreen::KeyInfo*
+ConfigReadContext::parseKeystroke(const String& keystroke,
+ const std::set<String>& screens) const
+{
+ String s = keystroke;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse key modifiers");
+ }
+
+ KeyID key;
+ if (!barrier::KeyMap::parseKey(s, key)) {
+ throw XConfigRead(*this, "unable to parse key");
+ }
+
+ if (key == kKeyNone && mask == 0) {
+ throw XConfigRead(*this, "missing key and/or modifiers in keystroke");
+ }
+
+ return IPlatformScreen::KeyInfo::alloc(key, mask, 0, 0, screens);
+}
+
+IPlatformScreen::ButtonInfo*
+ConfigReadContext::parseMouse(const String& mouse) const
+{
+ String s = mouse;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse button modifiers");
+ }
+
+ char* end;
+ ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10);
+ if (*end != '\0') {
+ throw XConfigRead(*this, "unable to parse button");
+ }
+ if (s.empty() || button <= 0) {
+ throw XConfigRead(*this, "invalid button");
+ }
+
+ return IPlatformScreen::ButtonInfo::alloc(button, mask);
+}
+
+KeyModifierMask
+ConfigReadContext::parseModifier(const String& modifiers) const
+{
+ String s = modifiers;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse modifiers");
+ }
+
+ if (mask == 0) {
+ throw XConfigRead(*this, "no modifiers specified");
+ }
+
+ return mask;
+}
+
+String
+ConfigReadContext::concatArgs(const ArgList& args)
+{
+ String s("(");
+ for (size_t i = 0; i < args.size(); ++i) {
+ if (i != 0) {
+ s += ",";
+ }
+ s += args[i];
+ }
+ s += ")";
+ return s;
+}
+
+
+//
+// Config I/O exceptions
+//
+
+XConfigRead::XConfigRead(const ConfigReadContext& context,
+ const String& error) :
+ m_error(barrier::string::sprintf("line %d: %s",
+ context.getLineNumber(), error.c_str()))
+{
+ // do nothing
+}
+
+XConfigRead::XConfigRead(const ConfigReadContext& context,
+ const char* errorFmt, const String& arg) :
+ m_error(barrier::string::sprintf("line %d: ", context.getLineNumber()) +
+ barrier::string::format(errorFmt, arg.c_str()))
+{
+ // do nothing
+}
+
+XConfigRead::~XConfigRead() _NOEXCEPT
+{
+ // do nothing
+}
+
+String
+XConfigRead::getWhat() const throw()
+{
+ return format("XConfigRead", "read error: %{1}", m_error.c_str());
+}
diff --git a/src/lib/server/Config.h b/src/lib/server/Config.h
new file mode 100644
index 0000000..69b01c4
--- /dev/null
+++ b/src/lib/server/Config.h
@@ -0,0 +1,549 @@
+/*
+ * 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 "server/InputFilter.h"
+#include "barrier/option_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/IPlatformScreen.h"
+#include "net/NetworkAddress.h"
+#include "base/String.h"
+#include "base/XBase.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+
+#include <iosfwd>
+
+class Config;
+class ConfigReadContext;
+class IEventQueue;
+
+namespace std {
+template <>
+struct iterator_traits<Config> {
+ typedef String value_type;
+ typedef ptrdiff_t difference_type;
+ typedef bidirectional_iterator_tag iterator_category;
+ typedef String* pointer;
+ typedef String& reference;
+};
+};
+
+//! Server configuration
+/*!
+This class holds server configuration information. That includes
+the names of screens and their aliases, the links between them,
+and network addresses.
+
+Note that case is preserved in screen names but is ignored when
+comparing names. Screen names and their aliases share a
+namespace and must be unique.
+*/
+class Config {
+public:
+ typedef std::map<OptionID, OptionValue> ScreenOptions;
+ typedef std::pair<float, float> Interval;
+
+ class CellEdge {
+ public:
+ CellEdge(EDirection side, float position);
+ CellEdge(EDirection side, const Interval&);
+ CellEdge(const String& name, EDirection side, const Interval&);
+ ~CellEdge();
+
+ Interval getInterval() const;
+ void setName(const String& newName);
+ String getName() const;
+ EDirection getSide() const;
+ bool overlaps(const CellEdge&) const;
+ bool isInside(float x) const;
+
+ // transform position to [0,1]
+ float transform(float x) const;
+
+ // transform [0,1] to position
+ float inverseTransform(float x) const;
+
+ // compares side and start of interval
+ bool operator<(const CellEdge&) const;
+
+ // compares side and interval
+ bool operator==(const CellEdge&) const;
+ bool operator!=(const CellEdge&) const;
+
+ private:
+ void init(const String& name, EDirection side,
+ const Interval&);
+
+ private:
+ String m_name;
+ EDirection m_side;
+ Interval m_interval;
+ };
+
+private:
+ class Name {
+ public:
+ Name(Config*, const String& name);
+
+ bool operator==(const String& name) const;
+
+ private:
+ Config* m_config;
+ String m_name;
+ };
+
+ class Cell {
+ private:
+ typedef std::map<CellEdge, CellEdge> EdgeLinks;
+
+ public:
+ typedef EdgeLinks::const_iterator const_iterator;
+
+ bool add(const CellEdge& src, const CellEdge& dst);
+ void remove(EDirection side);
+ void remove(EDirection side, float position);
+ void remove(const Name& destinationName);
+ void rename(const Name& oldName, const String& newName);
+
+ bool hasEdge(const CellEdge&) const;
+ bool overlaps(const CellEdge&) const;
+
+ bool getLink(EDirection side, float position,
+ const CellEdge*& src, const CellEdge*& dst) const;
+
+ bool operator==(const Cell&) const;
+ bool operator!=(const Cell&) const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ EdgeLinks m_neighbors;
+
+ public:
+ ScreenOptions m_options;
+ };
+ typedef std::map<String, Cell, barrier::string::CaselessCmp> CellMap;
+ typedef std::map<String, String, barrier::string::CaselessCmp> NameMap;
+
+public:
+ typedef Cell::const_iterator link_const_iterator;
+ typedef CellMap::const_iterator internal_const_iterator;
+ typedef NameMap::const_iterator all_const_iterator;
+ class const_iterator : std::iterator_traits<Config> {
+ public:
+ explicit const_iterator() : m_i() { }
+ explicit const_iterator(const internal_const_iterator& i) : m_i(i) { }
+
+ const_iterator& operator=(const const_iterator& i) {
+ m_i = i.m_i;
+ return *this;
+ }
+ String operator*() { return m_i->first; }
+ const String* operator->() { return &(m_i->first); }
+ const_iterator& operator++() { ++m_i; return *this; }
+ const_iterator operator++(int) { return const_iterator(m_i++); }
+ const_iterator& operator--() { --m_i; return *this; }
+ const_iterator operator--(int) { return const_iterator(m_i--); }
+ bool operator==(const const_iterator& i) const {
+ return (m_i == i.m_i);
+ }
+ bool operator!=(const const_iterator& i) const {
+ return (m_i != i.m_i);
+ }
+
+ private:
+ internal_const_iterator m_i;
+ };
+
+ Config(IEventQueue* events);
+ virtual ~Config();
+
+#ifdef TEST_ENV
+ Config() : m_inputFilter(NULL) { }
+#endif
+
+ //! @name manipulators
+ //@{
+
+ //! Add screen
+ /*!
+ Adds a screen, returning true iff successful. If a screen or
+ alias with the given name exists then it fails.
+ */
+ bool addScreen(const String& name);
+
+ //! Rename screen
+ /*!
+ Renames a screen. All references to the name are updated.
+ Returns true iff successful.
+ */
+ bool renameScreen(const String& oldName,
+ const String& newName);
+
+ //! Remove screen
+ /*!
+ Removes a screen. This also removes aliases for the screen and
+ disconnects any connections to the screen. \c name may be an
+ alias.
+ */
+ void removeScreen(const String& name);
+
+ //! Remove all screens
+ /*!
+ Removes all screens, aliases, and connections.
+ */
+ void removeAllScreens();
+
+ //! Add alias
+ /*!
+ Adds an alias for a screen name. An alias can be used
+ any place the canonical screen name can (except addScreen()).
+ Returns false if the alias name already exists or the canonical
+ name is unknown, otherwise returns true.
+ */
+ bool addAlias(const String& canonical,
+ const String& alias);
+
+ //! Remove alias
+ /*!
+ Removes an alias for a screen name. It returns false if the
+ alias is unknown or a canonical name, otherwise returns true.
+ */
+ bool removeAlias(const String& alias);
+
+ //! Remove aliases
+ /*!
+ Removes all aliases for a canonical screen name. It returns false
+ if the canonical name is unknown, otherwise returns true.
+ */
+ bool removeAliases(const String& canonical);
+
+ //! Remove all aliases
+ /*!
+ This removes all aliases but not the screens.
+ */
+ void removeAllAliases();
+
+ //! Connect screens
+ /*!
+ Establishes a one-way connection between portions of opposite edges
+ of two screens. Each portion is described by an interval defined
+ by two numbers, the start and end of the interval half-open on the
+ end. The numbers range from 0 to 1, inclusive, for the left/top
+ to the right/bottom. The user will be able to jump from the
+ \c srcStart to \c srcSend interval of \c srcSide of screen
+ \c srcName to the opposite side of screen \c dstName in the interval
+ \c dstStart and \c dstEnd when both screens are connected to the
+ server and the user isn't locked to a screen. Returns false if
+ \c srcName is unknown. \c srcStart must be less than or equal to
+ \c srcEnd and \c dstStart must be less then or equal to \c dstEnd
+ and all of \c srcStart, \c srcEnd, \c dstStart, or \c dstEnd must
+ be inside the range [0,1].
+ */
+ bool connect(const String& srcName,
+ EDirection srcSide,
+ float srcStart, float srcEnd,
+ const String& dstName,
+ float dstStart, float dstEnd);
+
+ //! Disconnect screens
+ /*!
+ Removes all connections created by connect() on side \c srcSide.
+ Returns false if \c srcName is unknown.
+ */
+ bool disconnect(const String& srcName,
+ EDirection srcSide);
+
+ //! Disconnect screens
+ /*!
+ Removes the connections created by connect() on side \c srcSide
+ covering position \c position. Returns false if \c srcName is
+ unknown.
+ */
+ bool disconnect(const String& srcName,
+ EDirection srcSide, float position);
+
+ //! Set server address
+ /*!
+ Set the barrier listen addresses. There is no default address so
+ this must be called to run a server using this configuration.
+ */
+ void setBarrierAddress(const NetworkAddress&);
+
+ //! Add a screen option
+ /*!
+ Adds an option and its value to the named screen. Replaces the
+ existing option's value if there is one. Returns true iff \c name
+ is a known screen.
+ */
+ bool addOption(const String& name,
+ OptionID option, OptionValue value);
+
+ //! Remove a screen option
+ /*!
+ Removes an option and its value from the named screen. Does
+ nothing if the option doesn't exist on the screen. Returns true
+ iff \c name is a known screen.
+ */
+ bool removeOption(const String& name, OptionID option);
+
+ //! Remove a screen options
+ /*!
+ Removes all options and values from the named screen. Returns true
+ iff \c name is a known screen.
+ */
+ bool removeOptions(const String& name);
+
+ //! Get the hot key input filter
+ /*!
+ Returns the hot key input filter. Clients can modify hotkeys using
+ that object.
+ */
+ virtual InputFilter*
+ getInputFilter();
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Test screen name validity
+ /*!
+ Returns true iff \c name is a valid screen name.
+ */
+ bool isValidScreenName(const String& name) const;
+
+ //! Get beginning (canonical) screen name iterator
+ const_iterator begin() const;
+ //! Get ending (canonical) screen name iterator
+ const_iterator end() const;
+
+ //! Get beginning screen name iterator
+ all_const_iterator beginAll() const;
+ //! Get ending screen name iterator
+ all_const_iterator endAll() const;
+
+ //! Test for screen name
+ /*!
+ Returns true iff \c name names a screen.
+ */
+ virtual bool isScreen(const String& name) const;
+
+ //! Test for canonical screen name
+ /*!
+ Returns true iff \c name is the canonical name of a screen.
+ */
+ bool isCanonicalName(const String& name) const;
+
+ //! Get canonical name
+ /*!
+ Returns the canonical name of a screen or the empty string if
+ the name is unknown. Returns the canonical name if one is given.
+ */
+ String getCanonicalName(const String& name) const;
+
+ //! Get neighbor
+ /*!
+ Returns the canonical screen name of the neighbor in the given
+ direction (set through connect()) at position \c position. Returns
+ the empty string if there is no neighbor in that direction, otherwise
+ saves the position on the neighbor in \c positionOut if it's not
+ \c NULL.
+ */
+ String getNeighbor(const String&, EDirection,
+ float position, float* positionOut) const;
+
+ //! Check for neighbor
+ /*!
+ Returns \c true if the screen has a neighbor anywhere along the edge
+ given by the direction.
+ */
+ bool hasNeighbor(const String&, EDirection) const;
+
+ //! Check for neighbor
+ /*!
+ Returns \c true if the screen has a neighbor in the given range along
+ the edge given by the direction.
+ */
+ bool hasNeighbor(const String&, EDirection,
+ float start, float end) const;
+
+ //! Get beginning neighbor iterator
+ link_const_iterator beginNeighbor(const String&) const;
+ //! Get ending neighbor iterator
+ link_const_iterator endNeighbor(const String&) const;
+
+ //! Get the server address
+ const NetworkAddress&
+ getBarrierAddress() const;
+
+ //! Get the screen options
+ /*!
+ Returns all the added options for the named screen. Returns NULL
+ if the screen is unknown and an empty collection if there are no
+ options.
+ */
+ const ScreenOptions*
+ getOptions(const String& name) const;
+
+ //! Check for lock to screen action
+ /*!
+ Returns \c true if this configuration has a lock to screen action.
+ This is for backwards compatible support of ScrollLock locking.
+ */
+ bool hasLockToScreenAction() const;
+
+ //! Compare configurations
+ bool operator==(const Config&) const;
+ //! Compare configurations
+ bool operator!=(const Config&) const;
+
+ //! Read configuration
+ /*!
+ Reads a configuration from a context. Throws XConfigRead on error
+ and context is unchanged.
+ */
+ void read(ConfigReadContext& context);
+
+ //! Read configuration
+ /*!
+ Reads a configuration from a stream. Throws XConfigRead on error.
+ */
+ friend std::istream&
+ operator>>(std::istream&, Config&);
+
+ //! Write configuration
+ /*!
+ Writes a configuration to a stream.
+ */
+ friend std::ostream&
+ operator<<(std::ostream&, const Config&);
+
+ //! Get direction name
+ /*!
+ Returns the name of a direction (for debugging).
+ */
+ static const char* dirName(EDirection);
+
+ //! Get interval as string
+ /*!
+ Returns an interval as a parseable string.
+ */
+ static String formatInterval(const Interval&);
+
+ //@}
+
+private:
+ void readSection(ConfigReadContext&);
+ void readSectionOptions(ConfigReadContext&);
+ void readSectionScreens(ConfigReadContext&);
+ void readSectionLinks(ConfigReadContext&);
+ void readSectionAliases(ConfigReadContext&);
+
+ InputFilter::Condition*
+ parseCondition(ConfigReadContext&,
+ const String& condition,
+ const std::vector<String>& args);
+ void parseAction(ConfigReadContext&,
+ const String& action,
+ const std::vector<String>& args,
+ InputFilter::Rule&, bool activate);
+
+ void parseScreens(ConfigReadContext&, const String&,
+ std::set<String>& screens) const;
+ static const char* getOptionName(OptionID);
+ static String getOptionValue(OptionID, OptionValue);
+
+private:
+ CellMap m_map;
+ NameMap m_nameToCanonicalName;
+ NetworkAddress m_barrierAddress;
+ ScreenOptions m_globalOptions;
+ InputFilter m_inputFilter;
+ bool m_hasLockToScreenAction;
+ IEventQueue* m_events;
+};
+
+//! Configuration read context
+/*!
+Maintains a context when reading a configuration from a stream.
+*/
+class ConfigReadContext {
+public:
+ typedef std::vector<String> ArgList;
+
+ ConfigReadContext(std::istream&, SInt32 firstLine = 1);
+ ~ConfigReadContext();
+
+ bool readLine(String&);
+ UInt32 getLineNumber() const;
+
+ bool operator!() const;
+
+ OptionValue parseBoolean(const String&) const;
+ OptionValue parseInt(const String&) const;
+ OptionValue parseModifierKey(const String&) const;
+ OptionValue parseCorner(const String&) const;
+ OptionValue parseCorners(const String&) const;
+ Config::Interval
+ parseInterval(const ArgList& args) const;
+ void parseNameWithArgs(
+ const String& type, const String& line,
+ const String& delim, String::size_type& index,
+ String& name, ArgList& args) const;
+ IPlatformScreen::KeyInfo*
+ parseKeystroke(const String& keystroke) const;
+ IPlatformScreen::KeyInfo*
+ parseKeystroke(const String& keystroke,
+ const std::set<String>& screens) const;
+ IPlatformScreen::ButtonInfo*
+ parseMouse(const String& mouse) const;
+ KeyModifierMask parseModifier(const String& modifiers) const;
+ std::istream& getStream() const { return m_stream; };
+
+private:
+ // not implemented
+ ConfigReadContext& operator=(const ConfigReadContext&);
+
+ static String concatArgs(const ArgList& args);
+
+private:
+ std::istream& m_stream;
+ SInt32 m_line;
+};
+
+//! Configuration stream read exception
+/*!
+Thrown when a configuration stream cannot be parsed.
+*/
+class XConfigRead : public XBase {
+public:
+ XConfigRead(const ConfigReadContext& context, const String&);
+ XConfigRead(const ConfigReadContext& context,
+ const char* errorFmt, const String& arg);
+ virtual ~XConfigRead() _NOEXCEPT;
+
+protected:
+ // XBase overrides
+ virtual String getWhat() const throw();
+
+private:
+ String m_error;
+};
diff --git a/src/lib/server/InputFilter.cpp b/src/lib/server/InputFilter.cpp
new file mode 100644
index 0000000..9e73f45
--- /dev/null
+++ b/src/lib/server/InputFilter.cpp
@@ -0,0 +1,1090 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2005 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 "server/InputFilter.h"
+#include "server/Server.h"
+#include "server/PrimaryClient.h"
+#include "barrier/KeyMap.h"
+#include "base/EventQueue.h"
+#include "base/Log.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstdlib>
+#include <cstring>
+
+// -----------------------------------------------------------------------------
+// Input Filter Condition Classes
+// -----------------------------------------------------------------------------
+InputFilter::Condition::Condition()
+{
+ // do nothing
+}
+
+InputFilter::Condition::~Condition()
+{
+ // do nothing
+}
+
+void
+InputFilter::Condition::enablePrimary(PrimaryClient*)
+{
+ // do nothing
+}
+
+void
+InputFilter::Condition::disablePrimary(PrimaryClient*)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeCondition::KeystrokeCondition(
+ IEventQueue* events, IPlatformScreen::KeyInfo* info) :
+ m_id(0),
+ m_key(info->m_key),
+ m_mask(info->m_mask),
+ m_events(events)
+{
+ free(info);
+}
+
+InputFilter::KeystrokeCondition::KeystrokeCondition(
+ IEventQueue* events, KeyID key, KeyModifierMask mask) :
+ m_id(0),
+ m_key(key),
+ m_mask(mask),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeCondition::~KeystrokeCondition()
+{
+ // do nothing
+}
+
+KeyID
+InputFilter::KeystrokeCondition::getKey() const
+{
+ return m_key;
+}
+
+KeyModifierMask
+InputFilter::KeystrokeCondition::getMask() const
+{
+ return m_mask;
+}
+
+InputFilter::Condition*
+InputFilter::KeystrokeCondition::clone() const
+{
+ return new KeystrokeCondition(m_events, m_key, m_mask);
+}
+
+String
+InputFilter::KeystrokeCondition::format() const
+{
+ return barrier::string::sprintf("keystroke(%s)",
+ barrier::KeyMap::formatKey(m_key, m_mask).c_str());
+}
+
+InputFilter::EFilterStatus
+InputFilter::KeystrokeCondition::match(const Event& event)
+{
+ EFilterStatus status;
+
+ // check for hotkey events
+ Event::Type type = event.getType();
+ if (type == m_events->forIPrimaryScreen().hotKeyDown()) {
+ status = kActivate;
+ }
+ else if (type == m_events->forIPrimaryScreen().hotKeyUp()) {
+ status = kDeactivate;
+ }
+ else {
+ return kNoMatch;
+ }
+
+ // check if it's our hotkey
+ IPrimaryScreen::HotKeyInfo* kinfo =
+ static_cast<IPlatformScreen::HotKeyInfo*>(event.getData());
+ if (kinfo->m_id != m_id) {
+ return kNoMatch;
+ }
+
+ return status;
+}
+
+void
+InputFilter::KeystrokeCondition::enablePrimary(PrimaryClient* primary)
+{
+ m_id = primary->registerHotKey(m_key, m_mask);
+}
+
+void
+InputFilter::KeystrokeCondition::disablePrimary(PrimaryClient* primary)
+{
+ primary->unregisterHotKey(m_id);
+ m_id = 0;
+}
+
+InputFilter::MouseButtonCondition::MouseButtonCondition(
+ IEventQueue* events, IPlatformScreen::ButtonInfo* info) :
+ m_button(info->m_button),
+ m_mask(info->m_mask),
+ m_events(events)
+{
+ free(info);
+}
+
+InputFilter::MouseButtonCondition::MouseButtonCondition(
+ IEventQueue* events, ButtonID button, KeyModifierMask mask) :
+ m_button(button),
+ m_mask(mask),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::MouseButtonCondition::~MouseButtonCondition()
+{
+ // do nothing
+}
+
+ButtonID
+InputFilter::MouseButtonCondition::getButton() const
+{
+ return m_button;
+}
+
+KeyModifierMask
+InputFilter::MouseButtonCondition::getMask() const
+{
+ return m_mask;
+}
+
+InputFilter::Condition*
+InputFilter::MouseButtonCondition::clone() const
+{
+ return new MouseButtonCondition(m_events, m_button, m_mask);
+}
+
+String
+InputFilter::MouseButtonCondition::format() const
+{
+ String key = barrier::KeyMap::formatKey(kKeyNone, m_mask);
+ if (!key.empty()) {
+ key += "+";
+ }
+ return barrier::string::sprintf("mousebutton(%s%d)", key.c_str(), m_button);
+}
+
+InputFilter::EFilterStatus
+InputFilter::MouseButtonCondition::match(const Event& event)
+{
+ static const KeyModifierMask s_ignoreMask =
+ KeyModifierAltGr | KeyModifierCapsLock |
+ KeyModifierNumLock | KeyModifierScrollLock;
+
+ EFilterStatus status;
+
+ // check for hotkey events
+ Event::Type type = event.getType();
+ if (type == m_events->forIPrimaryScreen().buttonDown()) {
+ status = kActivate;
+ }
+ else if (type == m_events->forIPrimaryScreen().buttonUp()) {
+ status = kDeactivate;
+ }
+ else {
+ return kNoMatch;
+ }
+
+ // check if it's the right button and modifiers. ignore modifiers
+ // that cannot be combined with a mouse button.
+ IPlatformScreen::ButtonInfo* minfo =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ if (minfo->m_button != m_button ||
+ (minfo->m_mask & ~s_ignoreMask) != m_mask) {
+ return kNoMatch;
+ }
+
+ return status;
+}
+
+InputFilter::ScreenConnectedCondition::ScreenConnectedCondition(
+ IEventQueue* events, const String& screen) :
+ m_screen(screen),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::ScreenConnectedCondition::~ScreenConnectedCondition()
+{
+ // do nothing
+}
+
+InputFilter::Condition*
+InputFilter::ScreenConnectedCondition::clone() const
+{
+ return new ScreenConnectedCondition(m_events, m_screen);
+}
+
+String
+InputFilter::ScreenConnectedCondition::format() const
+{
+ return barrier::string::sprintf("connect(%s)", m_screen.c_str());
+}
+
+InputFilter::EFilterStatus
+InputFilter::ScreenConnectedCondition::match(const Event& event)
+{
+ if (event.getType() == m_events->forServer().connected()) {
+ Server::ScreenConnectedInfo* info =
+ static_cast<Server::ScreenConnectedInfo*>(event.getData());
+ if (m_screen == info->m_screen || m_screen.empty()) {
+ return kActivate;
+ }
+ }
+
+ return kNoMatch;
+}
+
+// -----------------------------------------------------------------------------
+// Input Filter Action Classes
+// -----------------------------------------------------------------------------
+InputFilter::Action::Action()
+{
+ // do nothing
+}
+
+InputFilter::Action::~Action()
+{
+ // do nothing
+}
+
+InputFilter::LockCursorToScreenAction::LockCursorToScreenAction(
+ IEventQueue* events, Mode mode) :
+ m_mode(mode),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::LockCursorToScreenAction::Mode
+InputFilter::LockCursorToScreenAction::getMode() const
+{
+ return m_mode;
+}
+
+InputFilter::Action*
+InputFilter::LockCursorToScreenAction::clone() const
+{
+ return new LockCursorToScreenAction(*this);
+}
+
+String
+InputFilter::LockCursorToScreenAction::format() const
+{
+ static const char* s_mode[] = { "off", "on", "toggle" };
+
+ return barrier::string::sprintf("lockCursorToScreen(%s)", s_mode[m_mode]);
+}
+
+void
+InputFilter::LockCursorToScreenAction::perform(const Event& event)
+{
+ static const Server::LockCursorToScreenInfo::State s_state[] = {
+ Server::LockCursorToScreenInfo::kOff,
+ Server::LockCursorToScreenInfo::kOn,
+ Server::LockCursorToScreenInfo::kToggle
+ };
+
+ // send event
+ Server::LockCursorToScreenInfo* info =
+ Server::LockCursorToScreenInfo::alloc(s_state[m_mode]);
+ m_events->addEvent(Event(m_events->forServer().lockCursorToScreen(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::SwitchToScreenAction::SwitchToScreenAction(
+ IEventQueue* events, const String& screen) :
+ m_screen(screen),
+ m_events(events)
+{
+ // do nothing
+}
+
+String
+InputFilter::SwitchToScreenAction::getScreen() const
+{
+ return m_screen;
+}
+
+InputFilter::Action*
+InputFilter::SwitchToScreenAction::clone() const
+{
+ return new SwitchToScreenAction(*this);
+}
+
+String
+InputFilter::SwitchToScreenAction::format() const
+{
+ return barrier::string::sprintf("switchToScreen(%s)", m_screen.c_str());
+}
+
+void
+InputFilter::SwitchToScreenAction::perform(const Event& event)
+{
+ // pick screen name. if m_screen is empty then use the screen from
+ // event if it has one.
+ String screen = m_screen;
+ if (screen.empty() && event.getType() == m_events->forServer().connected()) {
+ Server::ScreenConnectedInfo* info =
+ static_cast<Server::ScreenConnectedInfo*>(event.getData());
+ screen = info->m_screen;
+ }
+
+ // send event
+ Server::SwitchToScreenInfo* info =
+ Server::SwitchToScreenInfo::alloc(screen);
+ m_events->addEvent(Event(m_events->forServer().switchToScreen(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::SwitchInDirectionAction::SwitchInDirectionAction(
+ IEventQueue* events, EDirection direction) :
+ m_direction(direction),
+ m_events(events)
+{
+ // do nothing
+}
+
+EDirection
+InputFilter::SwitchInDirectionAction::getDirection() const
+{
+ return m_direction;
+}
+
+InputFilter::Action*
+InputFilter::SwitchInDirectionAction::clone() const
+{
+ return new SwitchInDirectionAction(*this);
+}
+
+String
+InputFilter::SwitchInDirectionAction::format() const
+{
+ static const char* s_names[] = {
+ "",
+ "left",
+ "right",
+ "up",
+ "down"
+ };
+
+ return barrier::string::sprintf("switchInDirection(%s)", s_names[m_direction]);
+}
+
+void
+InputFilter::SwitchInDirectionAction::perform(const Event& event)
+{
+ Server::SwitchInDirectionInfo* info =
+ Server::SwitchInDirectionInfo::alloc(m_direction);
+ m_events->addEvent(Event(m_events->forServer().switchInDirection(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction(
+ IEventQueue* events, Mode mode) :
+ m_mode(mode),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction(
+ IEventQueue* events,
+ Mode mode,
+ const std::set<String>& screens) :
+ m_mode(mode),
+ m_screens(IKeyState::KeyInfo::join(screens)),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeyboardBroadcastAction::Mode
+InputFilter::KeyboardBroadcastAction::getMode() const
+{
+ return m_mode;
+}
+
+std::set<String>
+InputFilter::KeyboardBroadcastAction::getScreens() const
+{
+ std::set<String> screens;
+ IKeyState::KeyInfo::split(m_screens.c_str(), screens);
+ return screens;
+}
+
+InputFilter::Action*
+InputFilter::KeyboardBroadcastAction::clone() const
+{
+ return new KeyboardBroadcastAction(*this);
+}
+
+String
+InputFilter::KeyboardBroadcastAction::format() const
+{
+ static const char* s_mode[] = { "off", "on", "toggle" };
+ static const char* s_name = "keyboardBroadcast";
+
+ if (m_screens.empty() || m_screens[0] == '*') {
+ return barrier::string::sprintf("%s(%s)", s_name, s_mode[m_mode]);
+ }
+ else {
+ return barrier::string::sprintf("%s(%s,%.*s)", s_name, s_mode[m_mode],
+ m_screens.size() - 2,
+ m_screens.c_str() + 1);
+ }
+}
+
+void
+InputFilter::KeyboardBroadcastAction::perform(const Event& event)
+{
+ static const Server::KeyboardBroadcastInfo::State s_state[] = {
+ Server::KeyboardBroadcastInfo::kOff,
+ Server::KeyboardBroadcastInfo::kOn,
+ Server::KeyboardBroadcastInfo::kToggle
+ };
+
+ // send event
+ Server::KeyboardBroadcastInfo* info =
+ Server::KeyboardBroadcastInfo::alloc(s_state[m_mode], m_screens);
+ m_events->addEvent(Event(m_events->forServer().keyboardBroadcast(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::KeystrokeAction::KeystrokeAction(
+ IEventQueue* events, IPlatformScreen::KeyInfo* info, bool press) :
+ m_keyInfo(info),
+ m_press(press),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeAction::~KeystrokeAction()
+{
+ free(m_keyInfo);
+}
+
+void
+InputFilter::KeystrokeAction::adoptInfo(IPlatformScreen::KeyInfo* info)
+{
+ free(m_keyInfo);
+ m_keyInfo = info;
+}
+
+const IPlatformScreen::KeyInfo*
+InputFilter::KeystrokeAction::getInfo() const
+{
+ return m_keyInfo;
+}
+
+bool
+InputFilter::KeystrokeAction::isOnPress() const
+{
+ return m_press;
+}
+
+InputFilter::Action*
+InputFilter::KeystrokeAction::clone() const
+{
+ IKeyState::KeyInfo* info = IKeyState::KeyInfo::alloc(*m_keyInfo);
+ return new KeystrokeAction(m_events, info, m_press);
+}
+
+String
+InputFilter::KeystrokeAction::format() const
+{
+ const char* type = formatName();
+
+ if (m_keyInfo->m_screens[0] == '\0') {
+ return barrier::string::sprintf("%s(%s)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str());
+ }
+ else if (m_keyInfo->m_screens[0] == '*') {
+ return barrier::string::sprintf("%s(%s,*)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str());
+ }
+ else {
+ return barrier::string::sprintf("%s(%s,%.*s)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str(),
+ strlen(m_keyInfo->m_screens + 1) - 1,
+ m_keyInfo->m_screens + 1);
+ }
+}
+
+void
+InputFilter::KeystrokeAction::perform(const Event& event)
+{
+ Event::Type type = m_press ?
+ m_events->forIKeyState().keyDown() :
+ m_events->forIKeyState().keyUp();
+
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputBegin(),
+ event.getTarget(), NULL,
+ Event::kDeliverImmediately));
+ m_events->addEvent(Event(type, event.getTarget(), m_keyInfo,
+ Event::kDeliverImmediately |
+ Event::kDontFreeData));
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputEnd(),
+ event.getTarget(), NULL,
+ Event::kDeliverImmediately));
+}
+
+const char*
+InputFilter::KeystrokeAction::formatName() const
+{
+ return (m_press ? "keyDown" : "keyUp");
+}
+
+InputFilter::MouseButtonAction::MouseButtonAction(
+ IEventQueue* events, IPlatformScreen::ButtonInfo* info, bool press) :
+ m_buttonInfo(info),
+ m_press(press),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::MouseButtonAction::~MouseButtonAction()
+{
+ free(m_buttonInfo);
+}
+
+const IPlatformScreen::ButtonInfo*
+InputFilter::MouseButtonAction::getInfo() const
+{
+ return m_buttonInfo;
+}
+
+bool
+InputFilter::MouseButtonAction::isOnPress() const
+{
+ return m_press;
+}
+
+InputFilter::Action*
+InputFilter::MouseButtonAction::clone() const
+{
+ IPlatformScreen::ButtonInfo* info =
+ IPrimaryScreen::ButtonInfo::alloc(*m_buttonInfo);
+ return new MouseButtonAction(m_events, info, m_press);
+}
+
+String
+InputFilter::MouseButtonAction::format() const
+{
+ const char* type = formatName();
+
+ String key = barrier::KeyMap::formatKey(kKeyNone, m_buttonInfo->m_mask);
+ return barrier::string::sprintf("%s(%s%s%d)", type,
+ key.c_str(), key.empty() ? "" : "+",
+ m_buttonInfo->m_button);
+}
+
+void
+InputFilter::MouseButtonAction::perform(const Event& event)
+
+{
+ // send modifiers
+ IPlatformScreen::KeyInfo* modifierInfo = NULL;
+ if (m_buttonInfo->m_mask != 0) {
+ KeyID key = m_press ? kKeySetModifiers : kKeyClearModifiers;
+ modifierInfo =
+ IKeyState::KeyInfo::alloc(key, m_buttonInfo->m_mask, 0, 1);
+ m_events->addEvent(Event(m_events->forIKeyState().keyDown(),
+ event.getTarget(), modifierInfo,
+ Event::kDeliverImmediately));
+ }
+
+ // send button
+ Event::Type type = m_press ? m_events->forIPrimaryScreen().buttonDown() :
+ m_events->forIPrimaryScreen().buttonUp();
+ m_events->addEvent(Event(type, event.getTarget(), m_buttonInfo,
+ Event::kDeliverImmediately |
+ Event::kDontFreeData));
+}
+
+const char*
+InputFilter::MouseButtonAction::formatName() const
+{
+ return (m_press ? "mouseDown" : "mouseUp");
+}
+
+//
+// InputFilter::Rule
+//
+
+InputFilter::Rule::Rule() :
+ m_condition(NULL)
+{
+ // do nothing
+}
+
+InputFilter::Rule::Rule(Condition* adoptedCondition) :
+ m_condition(adoptedCondition)
+{
+ // do nothing
+}
+
+InputFilter::Rule::Rule(const Rule& rule) :
+ m_condition(NULL)
+{
+ copy(rule);
+}
+
+InputFilter::Rule::~Rule()
+{
+ clear();
+}
+
+InputFilter::Rule&
+InputFilter::Rule::operator=(const Rule& rule)
+{
+ if (&rule != this) {
+ copy(rule);
+ }
+ return *this;
+}
+
+void
+InputFilter::Rule::clear()
+{
+ delete m_condition;
+ for (ActionList::iterator i = m_activateActions.begin();
+ i != m_activateActions.end(); ++i) {
+ delete *i;
+ }
+ for (ActionList::iterator i = m_deactivateActions.begin();
+ i != m_deactivateActions.end(); ++i) {
+ delete *i;
+ }
+
+ m_condition = NULL;
+ m_activateActions.clear();
+ m_deactivateActions.clear();
+}
+
+void
+InputFilter::Rule::copy(const Rule& rule)
+{
+ clear();
+ if (rule.m_condition != NULL) {
+ m_condition = rule.m_condition->clone();
+ }
+ for (ActionList::const_iterator i = rule.m_activateActions.begin();
+ i != rule.m_activateActions.end(); ++i) {
+ m_activateActions.push_back((*i)->clone());
+ }
+ for (ActionList::const_iterator i = rule.m_deactivateActions.begin();
+ i != rule.m_deactivateActions.end(); ++i) {
+ m_deactivateActions.push_back((*i)->clone());
+ }
+}
+
+void
+InputFilter::Rule::setCondition(Condition* adopted)
+{
+ delete m_condition;
+ m_condition = adopted;
+}
+
+void
+InputFilter::Rule::adoptAction(Action* action, bool onActivation)
+{
+ if (action != NULL) {
+ if (onActivation) {
+ m_activateActions.push_back(action);
+ }
+ else {
+ m_deactivateActions.push_back(action);
+ }
+ }
+}
+
+void
+InputFilter::Rule::removeAction(bool onActivation, UInt32 index)
+{
+ if (onActivation) {
+ delete m_activateActions[index];
+ m_activateActions.erase(m_activateActions.begin() + index);
+ }
+ else {
+ delete m_deactivateActions[index];
+ m_deactivateActions.erase(m_deactivateActions.begin() + index);
+ }
+}
+
+void
+InputFilter::Rule::replaceAction(Action* adopted,
+ bool onActivation, UInt32 index)
+{
+ if (adopted == NULL) {
+ removeAction(onActivation, index);
+ }
+ else if (onActivation) {
+ delete m_activateActions[index];
+ m_activateActions[index] = adopted;
+ }
+ else {
+ delete m_deactivateActions[index];
+ m_deactivateActions[index] = adopted;
+ }
+}
+
+void
+InputFilter::Rule::enable(PrimaryClient* primaryClient)
+{
+ if (m_condition != NULL) {
+ m_condition->enablePrimary(primaryClient);
+ }
+}
+
+void
+InputFilter::Rule::disable(PrimaryClient* primaryClient)
+{
+ if (m_condition != NULL) {
+ m_condition->disablePrimary(primaryClient);
+ }
+}
+
+bool
+InputFilter::Rule::handleEvent(const Event& event)
+{
+ // NULL condition never matches
+ if (m_condition == NULL) {
+ return false;
+ }
+
+ // match
+ const ActionList* actions;
+ switch (m_condition->match(event)) {
+ default:
+ // not handled
+ return false;
+
+ case kActivate:
+ actions = &m_activateActions;
+ LOG((CLOG_DEBUG1 "activate actions"));
+ break;
+
+ case kDeactivate:
+ actions = &m_deactivateActions;
+ LOG((CLOG_DEBUG1 "deactivate actions"));
+ break;
+ }
+
+ // perform actions
+ for (ActionList::const_iterator i = actions->begin();
+ i != actions->end(); ++i) {
+ LOG((CLOG_DEBUG1 "hotkey: %s", (*i)->format().c_str()));
+ (*i)->perform(event);
+ }
+
+ return true;
+}
+
+String
+InputFilter::Rule::format() const
+{
+ String s;
+ if (m_condition != NULL) {
+ // condition
+ s += m_condition->format();
+ s += " = ";
+
+ // activate actions
+ ActionList::const_iterator i = m_activateActions.begin();
+ if (i != m_activateActions.end()) {
+ s += (*i)->format();
+ while (++i != m_activateActions.end()) {
+ s += ", ";
+ s += (*i)->format();
+ }
+ }
+
+ // deactivate actions
+ if (!m_deactivateActions.empty()) {
+ s += "; ";
+ i = m_deactivateActions.begin();
+ if (i != m_deactivateActions.end()) {
+ s += (*i)->format();
+ while (++i != m_deactivateActions.end()) {
+ s += ", ";
+ s += (*i)->format();
+ }
+ }
+ }
+ }
+ return s;
+}
+
+const InputFilter::Condition*
+InputFilter::Rule::getCondition() const
+{
+ return m_condition;
+}
+
+UInt32
+InputFilter::Rule::getNumActions(bool onActivation) const
+{
+ if (onActivation) {
+ return static_cast<UInt32>(m_activateActions.size());
+ }
+ else {
+ return static_cast<UInt32>(m_deactivateActions.size());
+ }
+}
+
+const InputFilter::Action&
+InputFilter::Rule::getAction(bool onActivation, UInt32 index) const
+{
+ if (onActivation) {
+ return *m_activateActions[index];
+ }
+ else {
+ return *m_deactivateActions[index];
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// Input Filter Class
+// -----------------------------------------------------------------------------
+InputFilter::InputFilter(IEventQueue* events) :
+ m_primaryClient(NULL),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::InputFilter(const InputFilter& x) :
+ m_ruleList(x.m_ruleList),
+ m_primaryClient(NULL),
+ m_events(x.m_events)
+{
+ setPrimaryClient(x.m_primaryClient);
+}
+
+InputFilter::~InputFilter()
+{
+ setPrimaryClient(NULL);
+}
+
+InputFilter&
+InputFilter::operator=(const InputFilter& x)
+{
+ if (&x != this) {
+ PrimaryClient* oldClient = m_primaryClient;
+ setPrimaryClient(NULL);
+
+ m_ruleList = x.m_ruleList;
+
+ setPrimaryClient(oldClient);
+ }
+ return *this;
+}
+
+void
+InputFilter::addFilterRule(const Rule& rule)
+{
+ m_ruleList.push_back(rule);
+ if (m_primaryClient != NULL) {
+ m_ruleList.back().enable(m_primaryClient);
+ }
+}
+
+void
+InputFilter::removeFilterRule(UInt32 index)
+{
+ if (m_primaryClient != NULL) {
+ m_ruleList[index].disable(m_primaryClient);
+ }
+ m_ruleList.erase(m_ruleList.begin() + index);
+}
+
+InputFilter::Rule&
+InputFilter::getRule(UInt32 index)
+{
+ return m_ruleList[index];
+}
+
+void
+InputFilter::setPrimaryClient(PrimaryClient* client)
+{
+ if (m_primaryClient == client) {
+ return;
+ }
+
+ if (m_primaryClient != NULL) {
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ rule->disable(m_primaryClient);
+ }
+
+ m_events->removeHandler(m_events->forIKeyState().keyDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIKeyState().keyUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIKeyState().keyRepeat(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget());
+ }
+
+ m_primaryClient = client;
+
+ if (m_primaryClient != NULL) {
+ m_events->adoptHandler(m_events->forIKeyState().keyDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyRepeat(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ rule->enable(m_primaryClient);
+ }
+ }
+}
+
+String
+InputFilter::format(const String& linePrefix) const
+{
+ String s;
+ for (RuleList::const_iterator i = m_ruleList.begin();
+ i != m_ruleList.end(); ++i) {
+ s += linePrefix;
+ s += i->format();
+ s += "\n";
+ }
+ return s;
+}
+
+UInt32
+InputFilter::getNumRules() const
+{
+ return static_cast<UInt32>(m_ruleList.size());
+}
+
+bool
+InputFilter::operator==(const InputFilter& x) const
+{
+ // if there are different numbers of rules then we can't be equal
+ if (m_ruleList.size() != x.m_ruleList.size()) {
+ return false;
+ }
+
+ // compare rule lists. the easiest way to do that is to format each
+ // rule into a string, sort the strings, then compare the results.
+ std::vector<String> aList, bList;
+ for (RuleList::const_iterator i = m_ruleList.begin();
+ i != m_ruleList.end(); ++i) {
+ aList.push_back(i->format());
+ }
+ for (RuleList::const_iterator i = x.m_ruleList.begin();
+ i != x.m_ruleList.end(); ++i) {
+ bList.push_back(i->format());
+ }
+ std::partial_sort(aList.begin(), aList.end(), aList.end());
+ std::partial_sort(bList.begin(), bList.end(), bList.end());
+ return (aList == bList);
+}
+
+bool
+InputFilter::operator!=(const InputFilter& x) const
+{
+ return !operator==(x);
+}
+
+void
+InputFilter::handleEvent(const Event& event, void*)
+{
+ // copy event and adjust target
+ Event myEvent(event.getType(), this, event.getData(),
+ event.getFlags() | Event::kDontFreeData |
+ Event::kDeliverImmediately);
+
+ // let each rule try to match the event until one does
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ if (rule->handleEvent(myEvent)) {
+ // handled
+ return;
+ }
+ }
+
+ // not handled so pass through
+ m_events->addEvent(myEvent);
+}
diff --git a/src/lib/server/InputFilter.h b/src/lib/server/InputFilter.h
new file mode 100644
index 0000000..73afe97
--- /dev/null
+++ b/src/lib/server/InputFilter.h
@@ -0,0 +1,361 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2005 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/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/IPlatformScreen.h"
+#include "base/String.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+
+class PrimaryClient;
+class Event;
+class IEventQueue;
+
+class InputFilter {
+public:
+ // -------------------------------------------------------------------------
+ // Input Filter Condition Classes
+ // -------------------------------------------------------------------------
+ enum EFilterStatus {
+ kNoMatch,
+ kActivate,
+ kDeactivate
+ };
+
+ class Condition {
+ public:
+ Condition();
+ virtual ~Condition();
+
+ virtual Condition* clone() const = 0;
+ virtual String format() const = 0;
+
+ virtual EFilterStatus match(const Event&) = 0;
+
+ virtual void enablePrimary(PrimaryClient*);
+ virtual void disablePrimary(PrimaryClient*);
+ };
+
+ // KeystrokeCondition
+ class KeystrokeCondition : public Condition {
+ public:
+ KeystrokeCondition(IEventQueue* events, IPlatformScreen::KeyInfo*);
+ KeystrokeCondition(IEventQueue* events, KeyID key, KeyModifierMask mask);
+ virtual ~KeystrokeCondition();
+
+ KeyID getKey() const;
+ KeyModifierMask getMask() const;
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+ virtual void enablePrimary(PrimaryClient*);
+ virtual void disablePrimary(PrimaryClient*);
+
+ private:
+ UInt32 m_id;
+ KeyID m_key;
+ KeyModifierMask m_mask;
+ IEventQueue* m_events;
+ };
+
+ // MouseButtonCondition
+ class MouseButtonCondition : public Condition {
+ public:
+ MouseButtonCondition(IEventQueue* events, IPlatformScreen::ButtonInfo*);
+ MouseButtonCondition(IEventQueue* events, ButtonID, KeyModifierMask mask);
+ virtual ~MouseButtonCondition();
+
+ ButtonID getButton() const;
+ KeyModifierMask getMask() const;
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+
+ private:
+ ButtonID m_button;
+ KeyModifierMask m_mask;
+ IEventQueue* m_events;
+ };
+
+ // ScreenConnectedCondition
+ class ScreenConnectedCondition : public Condition {
+ public:
+ ScreenConnectedCondition(IEventQueue* events, const String& screen);
+ virtual ~ScreenConnectedCondition();
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+
+ private:
+ String m_screen;
+ IEventQueue* m_events;
+ };
+
+ // -------------------------------------------------------------------------
+ // Input Filter Action Classes
+ // -------------------------------------------------------------------------
+
+ class Action {
+ public:
+ Action();
+ virtual ~Action();
+
+ virtual Action* clone() const = 0;
+ virtual String format() const = 0;
+
+ virtual void perform(const Event&) = 0;
+ };
+
+ // LockCursorToScreenAction
+ class LockCursorToScreenAction : public Action {
+ public:
+ enum Mode { kOff, kOn, kToggle };
+
+ LockCursorToScreenAction(IEventQueue* events, Mode = kToggle);
+
+ Mode getMode() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ Mode m_mode;
+ IEventQueue* m_events;
+ };
+
+ // SwitchToScreenAction
+ class SwitchToScreenAction : public Action {
+ public:
+ SwitchToScreenAction(IEventQueue* events, const String& screen);
+
+ String getScreen() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ String m_screen;
+ IEventQueue* m_events;
+ };
+
+ // SwitchInDirectionAction
+ class SwitchInDirectionAction : public Action {
+ public:
+ SwitchInDirectionAction(IEventQueue* events, EDirection);
+
+ EDirection getDirection() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ EDirection m_direction;
+ IEventQueue* m_events;
+ };
+
+ // KeyboardBroadcastAction
+ class KeyboardBroadcastAction : public Action {
+ public:
+ enum Mode { kOff, kOn, kToggle };
+
+ KeyboardBroadcastAction(IEventQueue* events, Mode = kToggle);
+ KeyboardBroadcastAction(IEventQueue* events, Mode, const std::set<String>& screens);
+
+ Mode getMode() const;
+ std::set<String> getScreens() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ Mode m_mode;
+ String m_screens;
+ IEventQueue* m_events;
+ };
+
+ // KeystrokeAction
+ class KeystrokeAction : public Action {
+ public:
+ KeystrokeAction(IEventQueue* events, IPlatformScreen::KeyInfo* adoptedInfo, bool press);
+ ~KeystrokeAction();
+
+ void adoptInfo(IPlatformScreen::KeyInfo*);
+ const IPlatformScreen::KeyInfo*
+ getInfo() const;
+ bool isOnPress() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ protected:
+ virtual const char* formatName() const;
+
+ private:
+ IPlatformScreen::KeyInfo* m_keyInfo;
+ bool m_press;
+ IEventQueue* m_events;
+ };
+
+ // MouseButtonAction -- modifier combinations not implemented yet
+ class MouseButtonAction : public Action {
+ public:
+ MouseButtonAction(IEventQueue* events,
+ IPlatformScreen::ButtonInfo* adoptedInfo,
+ bool press);
+ ~MouseButtonAction();
+
+ const IPlatformScreen::ButtonInfo*
+ getInfo() const;
+ bool isOnPress() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ protected:
+ virtual const char* formatName() const;
+
+ private:
+ IPlatformScreen::ButtonInfo* m_buttonInfo;
+ bool m_press;
+ IEventQueue* m_events;
+ };
+
+ class Rule {
+ public:
+ Rule();
+ Rule(Condition* adopted);
+ Rule(const Rule&);
+ ~Rule();
+
+ Rule& operator=(const Rule&);
+
+ // replace the condition
+ void setCondition(Condition* adopted);
+
+ // add an action to the rule
+ void adoptAction(Action*, bool onActivation);
+
+ // remove an action from the rule
+ void removeAction(bool onActivation, UInt32 index);
+
+ // replace an action in the rule
+ void replaceAction(Action* adopted,
+ bool onActivation, UInt32 index);
+
+ // enable/disable
+ void enable(PrimaryClient*);
+ void disable(PrimaryClient*);
+
+ // event handling
+ bool handleEvent(const Event&);
+
+ // convert rule to a string
+ String format() const;
+
+ // get the rule's condition
+ const Condition*
+ getCondition() const;
+
+ // get number of actions
+ UInt32 getNumActions(bool onActivation) const;
+
+ // get action by index
+ const Action& getAction(bool onActivation, UInt32 index) const;
+
+ private:
+ void clear();
+ void copy(const Rule&);
+
+ private:
+ typedef std::vector<Action*> ActionList;
+
+ Condition* m_condition;
+ ActionList m_activateActions;
+ ActionList m_deactivateActions;
+ };
+
+ // -------------------------------------------------------------------------
+ // Input Filter Class
+ // -------------------------------------------------------------------------
+ typedef std::vector<Rule> RuleList;
+
+ InputFilter(IEventQueue* events);
+ InputFilter(const InputFilter&);
+ virtual ~InputFilter();
+
+#ifdef TEST_ENV
+ InputFilter() : m_primaryClient(NULL) { }
+#endif
+
+ InputFilter& operator=(const InputFilter&);
+
+ // add rule, adopting the condition and the actions
+ void addFilterRule(const Rule& rule);
+
+ // remove a rule
+ void removeFilterRule(UInt32 index);
+
+ // get rule by index
+ Rule& getRule(UInt32 index);
+
+ // enable event filtering using the given primary client. disable
+ // if client is NULL.
+ virtual void setPrimaryClient(PrimaryClient* client);
+
+ // convert rules to a string
+ String format(const String& linePrefix) const;
+
+ // get number of rules
+ UInt32 getNumRules() const;
+
+ //! Compare filters
+ bool operator==(const InputFilter&) const;
+ //! Compare filters
+ bool operator!=(const InputFilter&) const;
+
+private:
+ // event handling
+ void handleEvent(const Event&, void*);
+
+private:
+ RuleList m_ruleList;
+ PrimaryClient* m_primaryClient;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp
new file mode 100644
index 0000000..4c9fe50
--- /dev/null
+++ b/src/lib/server/PrimaryClient.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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 "server/PrimaryClient.h"
+
+#include "barrier/Screen.h"
+#include "barrier/Clipboard.h"
+#include "base/Log.h"
+
+//
+// PrimaryClient
+//
+
+PrimaryClient::PrimaryClient(const String& name, barrier::Screen* screen) :
+ BaseClientProxy(name),
+ m_screen(screen),
+ m_fakeInputCount(0)
+{
+ // all clipboards are clean
+ for (UInt32 i = 0; i < kClipboardEnd; ++i) {
+ m_clipboardDirty[i] = false;
+ }
+}
+
+PrimaryClient::~PrimaryClient()
+{
+ // do nothing
+}
+
+void
+PrimaryClient::reconfigure(UInt32 activeSides)
+{
+ m_screen->reconfigure(activeSides);
+}
+
+UInt32
+PrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ return m_screen->registerHotKey(key, mask);
+}
+
+void
+PrimaryClient::unregisterHotKey(UInt32 id)
+{
+ m_screen->unregisterHotKey(id);
+}
+
+void
+PrimaryClient::fakeInputBegin()
+{
+ if (++m_fakeInputCount == 1) {
+ m_screen->fakeInputBegin();
+ }
+}
+
+void
+PrimaryClient::fakeInputEnd()
+{
+ if (--m_fakeInputCount == 0) {
+ m_screen->fakeInputEnd();
+ }
+}
+
+SInt32
+PrimaryClient::getJumpZoneSize() const
+{
+ return m_screen->getJumpZoneSize();
+}
+
+void
+PrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorCenter(x, y);
+}
+
+KeyModifierMask
+PrimaryClient::getToggleMask() const
+{
+ return m_screen->pollActiveModifiers();
+}
+
+bool
+PrimaryClient::isLockedToScreen() const
+{
+ return m_screen->isLockedToScreen();
+}
+
+void*
+PrimaryClient::getEventTarget() const
+{
+ return m_screen->getEventTarget();
+}
+
+bool
+PrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ return m_screen->getClipboard(id, clipboard);
+}
+
+void
+PrimaryClient::getShape(SInt32& x, SInt32& y,
+ SInt32& width, SInt32& height) const
+{
+ m_screen->getShape(x, y, width, height);
+}
+
+void
+PrimaryClient::getCursorPos(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorPos(x, y);
+}
+
+void
+PrimaryClient::enable()
+{
+ m_screen->enable();
+}
+
+void
+PrimaryClient::disable()
+{
+ m_screen->disable();
+}
+
+void
+PrimaryClient::enter(SInt32 xAbs, SInt32 yAbs,
+ UInt32 seqNum, KeyModifierMask mask, bool screensaver)
+{
+ m_screen->setSequenceNumber(seqNum);
+ if (!screensaver) {
+ m_screen->warpCursor(xAbs, yAbs);
+ }
+ m_screen->enter(mask);
+}
+
+bool
+PrimaryClient::leave()
+{
+ return m_screen->leave();
+}
+
+void
+PrimaryClient::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ // ignore if this clipboard is already clean
+ if (m_clipboardDirty[id]) {
+ // this clipboard is now clean
+ m_clipboardDirty[id] = false;
+
+ // set clipboard
+ m_screen->setClipboard(id, clipboard);
+ }
+}
+
+void
+PrimaryClient::grabClipboard(ClipboardID id)
+{
+ // grab clipboard
+ m_screen->grabClipboard(id);
+
+ // clipboard is dirty (because someone else owns it now)
+ m_clipboardDirty[id] = true;
+}
+
+void
+PrimaryClient::setClipboardDirty(ClipboardID id, bool dirty)
+{
+ m_clipboardDirty[id] = dirty;
+}
+
+void
+PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ if (m_fakeInputCount > 0) {
+// XXX -- don't forward keystrokes to primary screen for now
+ (void)key;
+ (void)mask;
+ (void)button;
+// m_screen->keyDown(key, mask, button);
+ }
+}
+
+void
+PrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton)
+{
+ // ignore
+}
+
+void
+PrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ if (m_fakeInputCount > 0) {
+// XXX -- don't forward keystrokes to primary screen for now
+ (void)key;
+ (void)mask;
+ (void)button;
+// m_screen->keyUp(key, mask, button);
+ }
+}
+
+void
+PrimaryClient::mouseDown(ButtonID)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseUp(ButtonID)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseMove(SInt32 x, SInt32 y)
+{
+ m_screen->warpCursor(x, y);
+}
+
+void
+PrimaryClient::mouseRelativeMove(SInt32, SInt32)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseWheel(SInt32, SInt32)
+{
+ // ignore
+}
+
+void
+PrimaryClient::screensaver(bool)
+{
+ // ignore
+}
+
+void
+PrimaryClient::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ // ignore
+}
+
+void
+PrimaryClient::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ // ignore
+}
+
+void
+PrimaryClient::resetOptions()
+{
+ m_screen->resetOptions();
+}
+
+void
+PrimaryClient::setOptions(const OptionsList& options)
+{
+ m_screen->setOptions(options);
+}
diff --git a/src/lib/server/PrimaryClient.h b/src/lib/server/PrimaryClient.h
new file mode 100644
index 0000000..4296aaa
--- /dev/null
+++ b/src/lib/server/PrimaryClient.h
@@ -0,0 +1,156 @@
+/*
+ * 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 "server/BaseClientProxy.h"
+#include "barrier/protocol_types.h"
+
+namespace barrier { class Screen; }
+
+//! Primary screen as pseudo-client
+/*!
+The primary screen does not have a client associated with it. This
+class provides a pseudo-client to allow the primary screen to be
+treated as if it was a client.
+*/
+class PrimaryClient : public BaseClientProxy {
+public:
+ /*!
+ \c name is the name of the server and \p screen is primary screen.
+ */
+ PrimaryClient(const String& name, barrier::Screen* screen);
+ ~PrimaryClient();
+
+#ifdef TEST_ENV
+ PrimaryClient() : BaseClientProxy("") { }
+#endif
+
+ //! @name manipulators
+ //@{
+
+ //! Update configuration
+ /*!
+ Handles reconfiguration of jump zones.
+ */
+ virtual void reconfigure(UInt32 activeSides);
+
+ //! Register a system hotkey
+ /*!
+ Registers a system-wide hotkey for key \p key with modifiers \p mask.
+ Returns an id used to unregister the hotkey.
+ */
+ virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask);
+
+ //! Unregister a system hotkey
+ /*!
+ Unregisters a previously registered hot key.
+ */
+ virtual void unregisterHotKey(UInt32 id);
+
+ //! Prepare to synthesize input on primary screen
+ /*!
+ Prepares the primary screen to receive synthesized input. We do not
+ want to receive this synthesized input as user input so this method
+ ensures that we ignore it. Calls to \c fakeInputBegin() and
+ \c fakeInputEnd() may be nested; only the outermost have an effect.
+ */
+ void fakeInputBegin();
+
+ //! Done synthesizing input on primary screen
+ /*!
+ Undoes whatever \c fakeInputBegin() did.
+ */
+ void fakeInputEnd();
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get jump zone size
+ /*!
+ Return the jump zone size, the size of the regions on the edges of
+ the screen that cause the cursor to jump to another screen.
+ */
+ SInt32 getJumpZoneSize() const;
+
+ //! Get cursor center position
+ /*!
+ Return the cursor center position which is where we park the
+ cursor to compute cursor motion deltas and should be far from
+ the edges of the screen, typically the center.
+ */
+ void getCursorCenter(SInt32& x, SInt32& y) const;
+
+ //! Get toggle key state
+ /*!
+ Returns the primary screen's current toggle modifier key state.
+ */
+ virtual KeyModifierMask
+ getToggleMask() const;
+
+ //! Get screen lock state
+ /*!
+ Returns true if the user is locked to the screen.
+ */
+ bool isLockedToScreen() const;
+
+ //@}
+
+ // FIXME -- these probably belong on IScreen
+ virtual void enable();
+ virtual void disable();
+
+ // 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 void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+
+ virtual barrier::IStream*
+ getStream() const { return NULL; }
+ bool isPrimary() const { return true; }
+private:
+ barrier::Screen* m_screen;
+ bool m_clipboardDirty[kClipboardEnd];
+ SInt32 m_fakeInputCount;
+};
diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp
new file mode 100644
index 0000000..32153a6
--- /dev/null
+++ b/src/lib/server/Server.cpp
@@ -0,0 +1,2405 @@
+/*
+ * 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 "server/Server.h"
+
+#include "server/ClientProxy.h"
+#include "server/ClientProxyUnknown.h"
+#include "server/PrimaryClient.h"
+#include "server/ClientListener.h"
+#include "barrier/FileChunk.h"
+#include "barrier/IPlatformScreen.h"
+#include "barrier/DropHelper.h"
+#include "barrier/option_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/XScreen.h"
+#include "barrier/XBarrier.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/KeyState.h"
+#include "barrier/Screen.h"
+#include "barrier/PacketStreamFilter.h"
+#include "net/TCPSocket.h"
+#include "net/IDataSocket.h"
+#include "net/IListenSocket.h"
+#include "net/XSocket.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "base/TMethodJob.h"
+#include "base/IEventQueue.h"
+#include "base/Log.h"
+#include "base/TMethodEventJob.h"
+#include "common/stdexcept.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+#include <ctime>
+
+//
+// Server
+//
+
+Server::Server(
+ Config& config,
+ PrimaryClient* primaryClient,
+ barrier::Screen* screen,
+ IEventQueue* events,
+ ServerArgs const& args) :
+ m_mock(false),
+ m_primaryClient(primaryClient),
+ m_active(primaryClient),
+ m_seqNum(0),
+ m_xDelta(0),
+ m_yDelta(0),
+ m_xDelta2(0),
+ m_yDelta2(0),
+ m_config(&config),
+ m_inputFilter(config.getInputFilter()),
+ m_activeSaver(NULL),
+ m_switchDir(kNoDirection),
+ m_switchScreen(NULL),
+ m_switchWaitDelay(0.0),
+ m_switchWaitTimer(NULL),
+ m_switchTwoTapDelay(0.0),
+ m_switchTwoTapEngaged(false),
+ m_switchTwoTapArmed(false),
+ m_switchTwoTapZone(3),
+ m_switchNeedsShift(false),
+ m_switchNeedsControl(false),
+ m_switchNeedsAlt(false),
+ m_relativeMoves(false),
+ m_keyboardBroadcasting(false),
+ m_lockedToScreen(false),
+ m_screen(screen),
+ m_events(events),
+ m_sendFileThread(NULL),
+ m_writeToDropDirThread(NULL),
+ m_ignoreFileTransfer(false),
+ m_enableClipboard(true),
+ m_sendDragInfoThread(NULL),
+ m_waitDragInfoThread(true),
+ m_args(args)
+{
+ // must have a primary client and it must have a canonical name
+ assert(m_primaryClient != NULL);
+ assert(config.isScreen(primaryClient->getName()));
+ assert(m_screen != NULL);
+
+ String primaryName = getName(primaryClient);
+
+ // clear clipboards
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ ClipboardInfo& clipboard = m_clipboards[id];
+ clipboard.m_clipboardOwner = primaryName;
+ clipboard.m_clipboardSeqNum = m_seqNum;
+ if (clipboard.m_clipboard.open(0)) {
+ clipboard.m_clipboard.empty();
+ clipboard.m_clipboard.close();
+ }
+ clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
+ }
+
+ // install event handlers
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchWaitTimeout));
+ m_events->adoptHandler(m_events->forIKeyState().keyDown(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyDownEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyUp(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyUpEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyRepeat(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyRepeatEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleButtonDownEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleButtonUpEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleMotionPrimaryEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleMotionSecondaryEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().wheel(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleWheelEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverActivated(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleScreensaverActivatedEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleScreensaverDeactivatedEvent));
+ m_events->adoptHandler(m_events->forServer().switchToScreen(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchToScreenEvent));
+ m_events->adoptHandler(m_events->forServer().switchInDirection(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchInDirectionEvent));
+ m_events->adoptHandler(m_events->forServer().keyboardBroadcast(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyboardBroadcastEvent));
+ m_events->adoptHandler(m_events->forServer().lockCursorToScreen(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleLockCursorToScreenEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFakeInputBeginEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFakeInputEndEvent));
+
+ if (m_args.m_enableDragDrop) {
+ m_events->adoptHandler(m_events->forFile().fileChunkSending(),
+ this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFileChunkSendingEvent));
+ m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(),
+ this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFileRecieveCompletedEvent));
+ }
+
+ // add connection
+ addClient(m_primaryClient);
+
+ // set initial configuration
+ setConfig(config);
+
+ // enable primary client
+ m_primaryClient->enable();
+ m_inputFilter->setPrimaryClient(m_primaryClient);
+
+ // Determine if scroll lock is already set. If so, lock the cursor to the primary screen
+ if (m_primaryClient->getToggleMask() & KeyModifierScrollLock) {
+ LOG((CLOG_NOTE "Scroll Lock is on, locking cursor to screen"));
+ m_lockedToScreen = true;
+ }
+
+}
+
+Server::~Server()
+{
+ if (m_mock) {
+ return;
+ }
+
+ // remove event handlers and timers
+ m_events->removeHandler(m_events->forIKeyState().keyDown(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIKeyState().keyUp(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIKeyState().keyRepeat(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().wheel(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().screensaverActivated(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
+ m_inputFilter);
+ m_events->removeHandler(Event::kTimer, this);
+ stopSwitch();
+
+ // force immediate disconnection of secondary clients
+ disconnect();
+ for (OldClients::iterator index = m_oldClients.begin();
+ index != m_oldClients.end(); ++index) {
+ BaseClientProxy* client = index->first;
+ m_events->deleteTimer(index->second);
+ m_events->removeHandler(Event::kTimer, client);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ delete client;
+ }
+
+ // remove input filter
+ m_inputFilter->setPrimaryClient(NULL);
+
+ // disable and disconnect primary client
+ m_primaryClient->disable();
+ removeClient(m_primaryClient);
+}
+
+bool
+Server::setConfig(const Config& config)
+{
+ // refuse configuration if it doesn't include the primary screen
+ if (!config.isScreen(m_primaryClient->getName())) {
+ return false;
+ }
+
+ // close clients that are connected but being dropped from the
+ // configuration.
+ closeClients(config);
+
+ // cut over
+ processOptions();
+
+ // add ScrollLock as a hotkey to lock to the screen. this was a
+ // built-in feature in earlier releases and is now supported via
+ // the user configurable hotkey mechanism. if the user has already
+ // registered ScrollLock for something else then that will win but
+ // we will unfortunately generate a warning. if the user has
+ // configured a LockCursorToScreenAction then we don't add
+ // ScrollLock as a hotkey.
+ if (!m_config->hasLockToScreenAction()) {
+ IPlatformScreen::KeyInfo* key =
+ IPlatformScreen::KeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
+ InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key));
+ rule.adoptAction(new InputFilter::LockCursorToScreenAction(m_events), true);
+ m_inputFilter->addFilterRule(rule);
+ }
+
+ // tell primary screen about reconfiguration
+ m_primaryClient->reconfigure(getActivePrimarySides());
+
+ // tell all (connected) clients about current options
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ sendOptions(client);
+ }
+
+ return true;
+}
+
+void
+Server::adoptClient(BaseClientProxy* client)
+{
+ assert(client != NULL);
+
+ // watch for client disconnection
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(), client,
+ new TMethodEventJob<Server>(this,
+ &Server::handleClientDisconnected, client));
+
+ // name must be in our configuration
+ if (!m_config->isScreen(client->getName())) {
+ LOG((CLOG_WARN "unrecognised client name \"%s\", check server config", client->getName().c_str()));
+ closeClient(client, kMsgEUnknown);
+ return;
+ }
+
+ // add client to client list
+ if (!addClient(client)) {
+ // can only have one screen with a given name at any given time
+ LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str()));
+ closeClient(client, kMsgEBusy);
+ return;
+ }
+ LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str()));
+
+ // send configuration options to client
+ sendOptions(client);
+
+ // activate screen saver on new client if active on the primary screen
+ if (m_activeSaver != NULL) {
+ client->screensaver(true);
+ }
+
+ // send notification
+ Server::ScreenConnectedInfo* info =
+ new Server::ScreenConnectedInfo(getName(client));
+ m_events->addEvent(Event(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget(), info));
+}
+
+void
+Server::disconnect()
+{
+ // close all secondary clients
+ if (m_clients.size() > 1 || !m_oldClients.empty()) {
+ Config emptyConfig(m_events);
+ closeClients(emptyConfig);
+ }
+ else {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+}
+
+UInt32
+Server::getNumClients() const
+{
+ return (SInt32)m_clients.size();
+}
+
+void
+Server::getClients(std::vector<String>& list) const
+{
+ list.clear();
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ list.push_back(index->first);
+ }
+}
+
+String
+Server::getName(const BaseClientProxy* client) const
+{
+ String name = m_config->getCanonicalName(client->getName());
+ if (name.empty()) {
+ name = client->getName();
+ }
+ return name;
+}
+
+UInt32
+Server::getActivePrimarySides() const
+{
+ UInt32 sides = 0;
+ if (!isLockedToScreenServer()) {
+ if (hasAnyNeighbor(m_primaryClient, kLeft)) {
+ sides |= kLeftMask;
+ }
+ if (hasAnyNeighbor(m_primaryClient, kRight)) {
+ sides |= kRightMask;
+ }
+ if (hasAnyNeighbor(m_primaryClient, kTop)) {
+ sides |= kTopMask;
+ }
+ if (hasAnyNeighbor(m_primaryClient, kBottom)) {
+ sides |= kBottomMask;
+ }
+ }
+ return sides;
+}
+
+bool
+Server::isLockedToScreenServer() const
+{
+ // locked if scroll-lock is toggled on
+ return m_lockedToScreen;
+}
+
+bool
+Server::isLockedToScreen() const
+{
+ // locked if we say we're locked
+ if (isLockedToScreenServer()) {
+ return true;
+ }
+
+ // locked if primary says we're locked
+ if (m_primaryClient->isLockedToScreen()) {
+ return true;
+ }
+
+ // not locked
+ return false;
+}
+
+SInt32
+Server::getJumpZoneSize(BaseClientProxy* client) const
+{
+ if (client == m_primaryClient) {
+ return m_primaryClient->getJumpZoneSize();
+ }
+ else {
+ return 0;
+ }
+}
+
+void
+Server::switchScreen(BaseClientProxy* dst,
+ SInt32 x, SInt32 y, bool forScreensaver)
+{
+ assert(dst != NULL);
+
+#ifndef NDEBUG
+ {
+ SInt32 dx, dy, dw, dh;
+ dst->getShape(dx, dy, dw, dh);
+ assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
+ }
+#endif
+ assert(m_active != NULL);
+
+ LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y));
+
+ // stop waiting to switch
+ stopSwitch();
+
+ // record new position
+ m_x = x;
+ m_y = y;
+ m_xDelta = 0;
+ m_yDelta = 0;
+ m_xDelta2 = 0;
+ m_yDelta2 = 0;
+
+ // wrapping means leaving the active screen and entering it again.
+ // since that's a waste of time we skip that and just warp the
+ // mouse.
+ if (m_active != dst) {
+ // leave active screen
+ if (!m_active->leave()) {
+ // cannot leave screen
+ LOG((CLOG_WARN "can't leave screen"));
+ return;
+ }
+
+ // update the primary client's clipboards if we're leaving the
+ // primary screen.
+ if (m_active == m_primaryClient && m_enableClipboard) {
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ ClipboardInfo& clipboard = m_clipboards[id];
+ if (clipboard.m_clipboardOwner == getName(m_primaryClient)) {
+ onClipboardChanged(m_primaryClient,
+ id, clipboard.m_clipboardSeqNum);
+ }
+ }
+ }
+
+ // cut over
+ m_active = dst;
+
+ // increment enter sequence number
+ ++m_seqNum;
+
+ // enter new screen
+ m_active->enter(x, y, m_seqNum,
+ m_primaryClient->getToggleMask(),
+ forScreensaver);
+
+ if (m_enableClipboard) {
+ // send the clipboard data to new active screen
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
+ }
+ }
+
+ Server::SwitchToScreenInfo* info =
+ Server::SwitchToScreenInfo::alloc(m_active->getName());
+ m_events->addEvent(Event(m_events->forServer().screenSwitched(), this, info));
+ }
+ else {
+ m_active->mouseMove(x, y);
+ }
+}
+
+void
+Server::jumpToScreen(BaseClientProxy* newScreen)
+{
+ assert(newScreen != NULL);
+
+ // record the current cursor position on the active screen
+ m_active->setJumpCursorPos(m_x, m_y);
+
+ // get the last cursor position on the target screen
+ SInt32 x, y;
+ newScreen->getJumpCursorPos(x, y);
+
+ switchScreen(newScreen, x, y, false);
+}
+
+float
+Server::mapToFraction(BaseClientProxy* client,
+ EDirection dir, SInt32 x, SInt32 y) const
+{
+ SInt32 sx, sy, sw, sh;
+ client->getShape(sx, sy, sw, sh);
+ switch (dir) {
+ case kLeft:
+ case kRight:
+ return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh);
+
+ case kTop:
+ case kBottom:
+ return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ break;
+ }
+ return 0.0f;
+}
+
+void
+Server::mapToPixel(BaseClientProxy* client,
+ EDirection dir, float f, SInt32& x, SInt32& y) const
+{
+ SInt32 sx, sy, sw, sh;
+ client->getShape(sx, sy, sw, sh);
+ switch (dir) {
+ case kLeft:
+ case kRight:
+ y = static_cast<SInt32>(f * sh) + sy;
+ break;
+
+ case kTop:
+ case kBottom:
+ x = static_cast<SInt32>(f * sw) + sx;
+ break;
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ break;
+ }
+}
+
+bool
+Server::hasAnyNeighbor(BaseClientProxy* client, EDirection dir) const
+{
+ assert(client != NULL);
+
+ return m_config->hasNeighbor(getName(client), dir);
+}
+
+BaseClientProxy*
+Server::getNeighbor(BaseClientProxy* src,
+ EDirection dir, SInt32& x, SInt32& y) const
+{
+ // note -- must be locked on entry
+
+ assert(src != NULL);
+
+ // get source screen name
+ String srcName = getName(src);
+ assert(!srcName.empty());
+ LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str()));
+
+ // convert position to fraction
+ float t = mapToFraction(src, dir, x, y);
+
+ // search for the closest neighbor that exists in direction dir
+ float tTmp;
+ for (;;) {
+ String dstName(m_config->getNeighbor(srcName, dir, t, &tTmp));
+
+ // if nothing in that direction then return NULL. if the
+ // destination is the source then we can make no more
+ // progress in this direction. since we haven't found a
+ // connected neighbor we return NULL.
+ if (dstName.empty()) {
+ LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str()));
+ return NULL;
+ }
+
+ // look up neighbor cell. if the screen is connected and
+ // ready then we can stop.
+ ClientList::const_iterator index = m_clients.find(dstName);
+ if (index != m_clients.end()) {
+ LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), Config::dirName(dir), srcName.c_str(), t));
+ mapToPixel(index->second, dir, tTmp, x, y);
+ return index->second;
+ }
+
+ // skip over unconnected screen
+ LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), Config::dirName(dir), srcName.c_str()));
+ srcName = dstName;
+
+ // use position on skipped screen
+ t = tTmp;
+ }
+}
+
+BaseClientProxy*
+Server::mapToNeighbor(BaseClientProxy* src,
+ EDirection srcSide, SInt32& x, SInt32& y) const
+{
+ // note -- must be locked on entry
+
+ assert(src != NULL);
+
+ // get the first neighbor
+ BaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ // get the source screen's size
+ SInt32 dx, dy, dw, dh;
+ BaseClientProxy* lastGoodScreen = src;
+ lastGoodScreen->getShape(dx, dy, dw, dh);
+
+ // find destination screen, adjusting x or y (but not both). the
+ // searches are done in a sort of canonical screen space where
+ // the upper-left corner is 0,0 for each screen. we adjust from
+ // actual to canonical position on entry to and from canonical to
+ // actual on exit from the search.
+ switch (srcSide) {
+ case kLeft:
+ x -= dx;
+ while (dst != NULL) {
+ lastGoodScreen = dst;
+ lastGoodScreen->getShape(dx, dy, dw, dh);
+ x += dw;
+ if (x >= 0) {
+ break;
+ }
+ LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+ dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+ }
+ assert(lastGoodScreen != NULL);
+ x += dx;
+ break;
+
+ case kRight:
+ x -= dx;
+ while (dst != NULL) {
+ x -= dw;
+ lastGoodScreen = dst;
+ lastGoodScreen->getShape(dx, dy, dw, dh);
+ if (x < dw) {
+ break;
+ }
+ LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+ dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+ }
+ assert(lastGoodScreen != NULL);
+ x += dx;
+ break;
+
+ case kTop:
+ y -= dy;
+ while (dst != NULL) {
+ lastGoodScreen = dst;
+ lastGoodScreen->getShape(dx, dy, dw, dh);
+ y += dh;
+ if (y >= 0) {
+ break;
+ }
+ LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+ dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+ }
+ assert(lastGoodScreen != NULL);
+ y += dy;
+ break;
+
+ case kBottom:
+ y -= dy;
+ while (dst != NULL) {
+ y -= dh;
+ lastGoodScreen = dst;
+ lastGoodScreen->getShape(dx, dy, dw, dh);
+ if (y < dh) {
+ break;
+ }
+ LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
+ dst = getNeighbor(lastGoodScreen, srcSide, x, y);
+ }
+ assert(lastGoodScreen != NULL);
+ y += dy;
+ break;
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ return NULL;
+ }
+
+ // save destination screen
+ assert(lastGoodScreen != NULL);
+ dst = lastGoodScreen;
+
+ // if entering primary screen then be sure to move in far enough
+ // to avoid the jump zone. if entering a side that doesn't have
+ // a neighbor (i.e. an asymmetrical side) then we don't need to
+ // move inwards because that side can't provoke a jump.
+ avoidJumpZone(dst, srcSide, x, y);
+
+ return dst;
+}
+
+void
+Server::avoidJumpZone(BaseClientProxy* dst,
+ EDirection dir, SInt32& x, SInt32& y) const
+{
+ // we only need to avoid jump zones on the primary screen
+ if (dst != m_primaryClient) {
+ return;
+ }
+
+ const String dstName(getName(dst));
+ SInt32 dx, dy, dw, dh;
+ dst->getShape(dx, dy, dw, dh);
+ float t = mapToFraction(dst, dir, x, y);
+ SInt32 z = getJumpZoneSize(dst);
+
+ // move in far enough to avoid the jump zone. if entering a side
+ // that doesn't have a neighbor (i.e. an asymmetrical side) then we
+ // don't need to move inwards because that side can't provoke a jump.
+ switch (dir) {
+ case kLeft:
+ if (!m_config->getNeighbor(dstName, kRight, t, NULL).empty() &&
+ x > dx + dw - 1 - z)
+ x = dx + dw - 1 - z;
+ break;
+
+ case kRight:
+ if (!m_config->getNeighbor(dstName, kLeft, t, NULL).empty() &&
+ x < dx + z)
+ x = dx + z;
+ break;
+
+ case kTop:
+ if (!m_config->getNeighbor(dstName, kBottom, t, NULL).empty() &&
+ y > dy + dh - 1 - z)
+ y = dy + dh - 1 - z;
+ break;
+
+ case kBottom:
+ if (!m_config->getNeighbor(dstName, kTop, t, NULL).empty() &&
+ y < dy + z)
+ y = dy + z;
+ break;
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ }
+}
+
+bool
+Server::isSwitchOkay(BaseClientProxy* newScreen,
+ EDirection dir, SInt32 x, SInt32 y,
+ SInt32 xActive, SInt32 yActive)
+{
+ LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), Config::dirName(dir)));
+
+ // is there a neighbor?
+ if (newScreen == NULL) {
+ // there's no neighbor. we don't want to switch and we don't
+ // want to try to switch later.
+ LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(dir)));
+ stopSwitch();
+ return false;
+ }
+
+ // should we switch or not?
+ bool preventSwitch = false;
+ bool allowSwitch = false;
+
+ // note if the switch direction has changed. save the new
+ // direction and screen if so.
+ bool isNewDirection = (dir != m_switchDir);
+ if (isNewDirection || m_switchScreen == NULL) {
+ m_switchDir = dir;
+ m_switchScreen = newScreen;
+ }
+
+ // is this a double tap and do we care?
+ if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
+ if (isNewDirection ||
+ !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) {
+ // tapping a different or new edge or second tap not
+ // fast enough. prepare for second tap.
+ preventSwitch = true;
+ startSwitchTwoTap();
+ }
+ else {
+ // got second tap
+ allowSwitch = true;
+ }
+ }
+
+ // if waiting before a switch then prepare to switch later
+ if (!allowSwitch && m_switchWaitDelay > 0.0) {
+ if (isNewDirection || !isSwitchWaitStarted()) {
+ startSwitchWait(x, y);
+ }
+ preventSwitch = true;
+ }
+
+ // are we in a locked corner? first check if screen has the option set
+ // and, if not, check the global options.
+ const Config::ScreenOptions* options =
+ m_config->getOptions(getName(m_active));
+ if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) {
+ options = m_config->getOptions("");
+ }
+ if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) {
+ // get corner mask and size
+ Config::ScreenOptions::const_iterator i =
+ options->find(kOptionScreenSwitchCorners);
+ UInt32 corners = static_cast<UInt32>(i->second);
+ i = options->find(kOptionScreenSwitchCornerSize);
+ SInt32 size = 0;
+ if (i != options->end()) {
+ size = i->second;
+ }
+
+ // see if we're in a locked corner
+ if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) {
+ // yep, no switching
+ LOG((CLOG_DEBUG1 "locked in corner"));
+ preventSwitch = true;
+ stopSwitch();
+ }
+ }
+
+ // ignore if mouse is locked to screen and don't try to switch later
+ if (!preventSwitch && isLockedToScreen()) {
+ LOG((CLOG_DEBUG1 "locked to screen"));
+ preventSwitch = true;
+ stopSwitch();
+ }
+
+ // check for optional needed modifiers
+ KeyModifierMask mods = this->m_primaryClient->getToggleMask();
+
+ if (!preventSwitch && (
+ (this->m_switchNeedsShift && ((mods & KeyModifierShift) != KeyModifierShift)) ||
+ (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) ||
+ (this->m_switchNeedsAlt && ((mods & KeyModifierAlt) != KeyModifierAlt))
+ )) {
+ LOG((CLOG_DEBUG1 "need modifiers to switch"));
+ preventSwitch = true;
+ stopSwitch();
+ }
+
+ return !preventSwitch;
+}
+
+void
+Server::noSwitch(SInt32 x, SInt32 y)
+{
+ armSwitchTwoTap(x, y);
+ stopSwitchWait();
+}
+
+void
+Server::stopSwitch()
+{
+ if (m_switchScreen != NULL) {
+ m_switchScreen = NULL;
+ m_switchDir = kNoDirection;
+ stopSwitchTwoTap();
+ stopSwitchWait();
+ }
+}
+
+void
+Server::startSwitchTwoTap()
+{
+ m_switchTwoTapEngaged = true;
+ m_switchTwoTapArmed = false;
+ m_switchTwoTapTimer.reset();
+ LOG((CLOG_DEBUG1 "waiting for second tap"));
+}
+
+void
+Server::armSwitchTwoTap(SInt32 x, SInt32 y)
+{
+ if (m_switchTwoTapEngaged) {
+ if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
+ // second tap took too long. disengage.
+ stopSwitchTwoTap();
+ }
+ else if (!m_switchTwoTapArmed) {
+ // still time for a double tap. see if we left the tap
+ // zone and, if so, arm the two tap.
+ SInt32 ax, ay, aw, ah;
+ m_active->getShape(ax, ay, aw, ah);
+ SInt32 tapZone = m_primaryClient->getJumpZoneSize();
+ if (tapZone < m_switchTwoTapZone) {
+ tapZone = m_switchTwoTapZone;
+ }
+ if (x >= ax + tapZone && x < ax + aw - tapZone &&
+ y >= ay + tapZone && y < ay + ah - tapZone) {
+ // win32 can generate bogus mouse events that appear to
+ // move in the opposite direction that the mouse actually
+ // moved. try to ignore that crap here.
+ switch (m_switchDir) {
+ case kLeft:
+ m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0);
+ break;
+
+ case kRight:
+ m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0);
+ break;
+
+ case kTop:
+ m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0);
+ break;
+
+ case kBottom:
+ m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+Server::stopSwitchTwoTap()
+{
+ m_switchTwoTapEngaged = false;
+ m_switchTwoTapArmed = false;
+}
+
+bool
+Server::isSwitchTwoTapStarted() const
+{
+ return m_switchTwoTapEngaged;
+}
+
+bool
+Server::shouldSwitchTwoTap() const
+{
+ // this is the second tap if two-tap is armed and this tap
+ // came fast enough
+ return (m_switchTwoTapArmed &&
+ m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay);
+}
+
+void
+Server::startSwitchWait(SInt32 x, SInt32 y)
+{
+ stopSwitchWait();
+ m_switchWaitX = x;
+ m_switchWaitY = y;
+ m_switchWaitTimer = m_events->newOneShotTimer(m_switchWaitDelay, this);
+ LOG((CLOG_DEBUG1 "waiting to switch"));
+}
+
+void
+Server::stopSwitchWait()
+{
+ if (m_switchWaitTimer != NULL) {
+ m_events->deleteTimer(m_switchWaitTimer);
+ m_switchWaitTimer = NULL;
+ }
+}
+
+bool
+Server::isSwitchWaitStarted() const
+{
+ return (m_switchWaitTimer != NULL);
+}
+
+UInt32
+Server::getCorner(BaseClientProxy* client,
+ SInt32 x, SInt32 y, SInt32 size) const
+{
+ assert(client != NULL);
+
+ // get client screen shape
+ SInt32 ax, ay, aw, ah;
+ client->getShape(ax, ay, aw, ah);
+
+ // check for x,y on the left or right
+ SInt32 xSide;
+ if (x <= ax) {
+ xSide = -1;
+ }
+ else if (x >= ax + aw - 1) {
+ xSide = 1;
+ }
+ else {
+ xSide = 0;
+ }
+
+ // check for x,y on the top or bottom
+ SInt32 ySide;
+ if (y <= ay) {
+ ySide = -1;
+ }
+ else if (y >= ay + ah - 1) {
+ ySide = 1;
+ }
+ else {
+ ySide = 0;
+ }
+
+ // if against the left or right then check if y is within size
+ if (xSide != 0) {
+ if (y < ay + size) {
+ return (xSide < 0) ? kTopLeftMask : kTopRightMask;
+ }
+ else if (y >= ay + ah - size) {
+ return (xSide < 0) ? kBottomLeftMask : kBottomRightMask;
+ }
+ }
+
+ // if against the left or right then check if y is within size
+ if (ySide != 0) {
+ if (x < ax + size) {
+ return (ySide < 0) ? kTopLeftMask : kBottomLeftMask;
+ }
+ else if (x >= ax + aw - size) {
+ return (ySide < 0) ? kTopRightMask : kBottomRightMask;
+ }
+ }
+
+ return kNoCornerMask;
+}
+
+void
+Server::stopRelativeMoves()
+{
+ if (m_relativeMoves && m_active != m_primaryClient) {
+ // warp to the center of the active client so we know where we are
+ SInt32 ax, ay, aw, ah;
+ m_active->getShape(ax, ay, aw, ah);
+ m_x = ax + (aw >> 1);
+ m_y = ay + (ah >> 1);
+ m_xDelta = 0;
+ m_yDelta = 0;
+ m_xDelta2 = 0;
+ m_yDelta2 = 0;
+ LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y));
+ m_active->mouseMove(m_x, m_y);
+ }
+}
+
+void
+Server::sendOptions(BaseClientProxy* client) const
+{
+ OptionsList optionsList;
+
+ // look up options for client
+ const Config::ScreenOptions* options =
+ m_config->getOptions(getName(client));
+ if (options != NULL) {
+ // convert options to a more convenient form for sending
+ optionsList.reserve(2 * options->size());
+ for (Config::ScreenOptions::const_iterator index = options->begin();
+ index != options->end(); ++index) {
+ optionsList.push_back(index->first);
+ optionsList.push_back(static_cast<UInt32>(index->second));
+ }
+ }
+
+ // look up global options
+ options = m_config->getOptions("");
+ if (options != NULL) {
+ // convert options to a more convenient form for sending
+ optionsList.reserve(optionsList.size() + 2 * options->size());
+ for (Config::ScreenOptions::const_iterator index = options->begin();
+ index != options->end(); ++index) {
+ optionsList.push_back(index->first);
+ optionsList.push_back(static_cast<UInt32>(index->second));
+ }
+ }
+
+ // send the options
+ client->resetOptions();
+ client->setOptions(optionsList);
+}
+
+void
+Server::processOptions()
+{
+ const Config::ScreenOptions* options = m_config->getOptions("");
+ if (options == NULL) {
+ return;
+ }
+
+ m_switchNeedsShift = false; // it seems if i don't add these
+ m_switchNeedsControl = false; // lines, the 'reload config' option
+ m_switchNeedsAlt = false; // doesnt' work correct.
+
+ bool newRelativeMoves = m_relativeMoves;
+ for (Config::ScreenOptions::const_iterator index = options->begin();
+ index != options->end(); ++index) {
+ const OptionID id = index->first;
+ const OptionValue value = index->second;
+ if (id == kOptionScreenSwitchDelay) {
+ m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
+ if (m_switchWaitDelay < 0.0) {
+ m_switchWaitDelay = 0.0;
+ }
+ stopSwitchWait();
+ }
+ else if (id == kOptionScreenSwitchTwoTap) {
+ m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
+ if (m_switchTwoTapDelay < 0.0) {
+ m_switchTwoTapDelay = 0.0;
+ }
+ stopSwitchTwoTap();
+ }
+ else if (id == kOptionScreenSwitchNeedsControl) {
+ m_switchNeedsControl = (value != 0);
+ }
+ else if (id == kOptionScreenSwitchNeedsShift) {
+ m_switchNeedsShift = (value != 0);
+ }
+ else if (id == kOptionScreenSwitchNeedsAlt) {
+ m_switchNeedsAlt = (value != 0);
+ }
+ else if (id == kOptionRelativeMouseMoves) {
+ newRelativeMoves = (value != 0);
+ }
+ else if (id == kOptionClipboardSharing) {
+ m_enableClipboard = (value != 0);
+
+ if (m_enableClipboard == false) {
+ LOG((CLOG_NOTE "clipboard sharing is disabled"));
+ }
+ }
+ }
+ if (m_relativeMoves && !newRelativeMoves) {
+ stopRelativeMoves();
+ }
+ m_relativeMoves = newRelativeMoves;
+}
+
+void
+Server::handleShapeChanged(const Event&, void* vclient)
+{
+ // ignore events from unknown clients
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(client) == 0) {
+ return;
+ }
+
+ LOG((CLOG_DEBUG "screen \"%s\" shape changed", getName(client).c_str()));
+
+ // update jump coordinate
+ SInt32 x, y;
+ client->getCursorPos(x, y);
+ client->setJumpCursorPos(x, y);
+
+ // update the mouse coordinates
+ if (client == m_active) {
+ m_x = x;
+ m_y = y;
+ }
+
+ // handle resolution change to primary screen
+ if (client == m_primaryClient) {
+ if (client == m_active) {
+ onMouseMovePrimary(m_x, m_y);
+ }
+ else {
+ onMouseMoveSecondary(0, 0);
+ }
+ }
+}
+
+void
+Server::handleClipboardGrabbed(const Event& event, void* vclient)
+{
+ if (!m_enableClipboard) {
+ return;
+ }
+
+ // ignore events from unknown clients
+ BaseClientProxy* grabber = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(grabber) == 0) {
+ return;
+ }
+ const IScreen::ClipboardInfo* info =
+ static_cast<const IScreen::ClipboardInfo*>(event.getData());
+
+ // ignore grab if sequence number is old. always allow primary
+ // screen to grab.
+ ClipboardInfo& clipboard = m_clipboards[info->m_id];
+ if (grabber != m_primaryClient &&
+ info->m_sequenceNumber < clipboard.m_clipboardSeqNum) {
+ LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id));
+ return;
+ }
+
+ // mark screen as owning clipboard
+ LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str()));
+ clipboard.m_clipboardOwner = getName(grabber);
+ clipboard.m_clipboardSeqNum = info->m_sequenceNumber;
+
+ // clear the clipboard data (since it's not known at this point)
+ if (clipboard.m_clipboard.open(0)) {
+ clipboard.m_clipboard.empty();
+ clipboard.m_clipboard.close();
+ }
+ clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
+
+ // tell all other screens to take ownership of clipboard. tell the
+ // grabber that it's clipboard isn't dirty.
+ for (ClientList::iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ if (client == grabber) {
+ client->setClipboardDirty(info->m_id, false);
+ }
+ else {
+ client->grabClipboard(info->m_id);
+ }
+ }
+}
+
+void
+Server::handleClipboardChanged(const Event& event, void* vclient)
+{
+ // ignore events from unknown clients
+ BaseClientProxy* sender = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(sender) == 0) {
+ return;
+ }
+ const IScreen::ClipboardInfo* info =
+ static_cast<const IScreen::ClipboardInfo*>(event.getData());
+ onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
+}
+
+void
+Server::handleKeyDownEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+Server::handleKeyUpEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+Server::handleKeyRepeatEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
+}
+
+void
+Server::handleButtonDownEvent(const Event& event, void*)
+{
+ IPlatformScreen::ButtonInfo* info =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ onMouseDown(info->m_button);
+}
+
+void
+Server::handleButtonUpEvent(const Event& event, void*)
+{
+ IPlatformScreen::ButtonInfo* info =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ onMouseUp(info->m_button);
+}
+
+void
+Server::handleMotionPrimaryEvent(const Event& event, void*)
+{
+ IPlatformScreen::MotionInfo* info =
+ static_cast<IPlatformScreen::MotionInfo*>(event.getData());
+ onMouseMovePrimary(info->m_x, info->m_y);
+}
+
+void
+Server::handleMotionSecondaryEvent(const Event& event, void*)
+{
+ IPlatformScreen::MotionInfo* info =
+ static_cast<IPlatformScreen::MotionInfo*>(event.getData());
+ onMouseMoveSecondary(info->m_x, info->m_y);
+}
+
+void
+Server::handleWheelEvent(const Event& event, void*)
+{
+ IPlatformScreen::WheelInfo* info =
+ static_cast<IPlatformScreen::WheelInfo*>(event.getData());
+ onMouseWheel(info->m_xDelta, info->m_yDelta);
+}
+
+void
+Server::handleScreensaverActivatedEvent(const Event&, void*)
+{
+ onScreensaver(true);
+}
+
+void
+Server::handleScreensaverDeactivatedEvent(const Event&, void*)
+{
+ onScreensaver(false);
+}
+
+void
+Server::handleSwitchWaitTimeout(const Event&, void*)
+{
+ // ignore if mouse is locked to screen
+ if (isLockedToScreen()) {
+ LOG((CLOG_DEBUG1 "locked to screen"));
+ stopSwitch();
+ return;
+ }
+
+ // switch screen
+ switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
+}
+
+void
+Server::handleClientDisconnected(const Event&, void* vclient)
+{
+ // client has disconnected. it might be an old client or an
+ // active client. we don't care so just handle it both ways.
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ removeActiveClient(client);
+ removeOldClient(client);
+
+ delete client;
+}
+
+void
+Server::handleClientCloseTimeout(const Event&, void* vclient)
+{
+ // client took too long to disconnect. just dump it.
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
+ removeOldClient(client);
+
+ delete client;
+}
+
+void
+Server::handleSwitchToScreenEvent(const Event& event, void*)
+{
+ SwitchToScreenInfo* info =
+ static_cast<SwitchToScreenInfo*>(event.getData());
+
+ ClientList::const_iterator index = m_clients.find(info->m_screen);
+ if (index == m_clients.end()) {
+ LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen));
+ }
+ else {
+ jumpToScreen(index->second);
+ }
+}
+
+void
+Server::handleSwitchInDirectionEvent(const Event& event, void*)
+{
+ SwitchInDirectionInfo* info =
+ static_cast<SwitchInDirectionInfo*>(event.getData());
+
+ // jump to screen in chosen direction from center of this screen
+ SInt32 x = m_x, y = m_y;
+ BaseClientProxy* newScreen =
+ getNeighbor(m_active, info->m_direction, x, y);
+ if (newScreen == NULL) {
+ LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(info->m_direction)));
+ }
+ else {
+ jumpToScreen(newScreen);
+ }
+}
+
+void
+Server::handleKeyboardBroadcastEvent(const Event& event, void*)
+{
+ KeyboardBroadcastInfo* info = (KeyboardBroadcastInfo*)event.getData();
+
+ // choose new state
+ bool newState;
+ switch (info->m_state) {
+ case KeyboardBroadcastInfo::kOff:
+ newState = false;
+ break;
+
+ default:
+ case KeyboardBroadcastInfo::kOn:
+ newState = true;
+ break;
+
+ case KeyboardBroadcastInfo::kToggle:
+ newState = !m_keyboardBroadcasting;
+ break;
+ }
+
+ // enter new state
+ if (newState != m_keyboardBroadcasting ||
+ info->m_screens != m_keyboardBroadcastingScreens) {
+ m_keyboardBroadcasting = newState;
+ m_keyboardBroadcastingScreens = info->m_screens;
+ LOG((CLOG_DEBUG "keyboard broadcasting %s: %s", m_keyboardBroadcasting ? "on" : "off", m_keyboardBroadcastingScreens.c_str()));
+ }
+}
+
+void
+Server::handleLockCursorToScreenEvent(const Event& event, void*)
+{
+ LockCursorToScreenInfo* info = (LockCursorToScreenInfo*)event.getData();
+
+ // choose new state
+ bool newState;
+ switch (info->m_state) {
+ case LockCursorToScreenInfo::kOff:
+ newState = false;
+ break;
+
+ default:
+ case LockCursorToScreenInfo::kOn:
+ newState = true;
+ break;
+
+ case LockCursorToScreenInfo::kToggle:
+ newState = !m_lockedToScreen;
+ break;
+ }
+
+ // enter new state
+ if (newState != m_lockedToScreen) {
+ m_lockedToScreen = newState;
+ LOG((CLOG_NOTE "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
+
+ m_primaryClient->reconfigure(getActivePrimarySides());
+ if (!isLockedToScreenServer()) {
+ stopRelativeMoves();
+ }
+ }
+}
+
+void
+Server::handleFakeInputBeginEvent(const Event&, void*)
+{
+ m_primaryClient->fakeInputBegin();
+}
+
+void
+Server::handleFakeInputEndEvent(const Event&, void*)
+{
+ m_primaryClient->fakeInputEnd();
+}
+
+void
+Server::handleFileChunkSendingEvent(const Event& event, void*)
+{
+ onFileChunkSending(event.getData());
+}
+
+void
+Server::handleFileRecieveCompletedEvent(const Event& event, void*)
+{
+ onFileRecieveCompleted();
+}
+
+void
+Server::onClipboardChanged(BaseClientProxy* sender,
+ ClipboardID id, UInt32 seqNum)
+{
+ ClipboardInfo& clipboard = m_clipboards[id];
+
+ // ignore update if sequence number is old
+ if (seqNum < clipboard.m_clipboardSeqNum) {
+ LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id));
+ return;
+ }
+
+ // should be the expected client
+ assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second);
+
+ // get data
+ sender->getClipboard(id, &clipboard.m_clipboard);
+
+ // ignore if data hasn't changed
+ String data = clipboard.m_clipboard.marshall();
+ if (data == clipboard.m_clipboardData) {
+ LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
+ return;
+ }
+
+ // got new data
+ LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
+ clipboard.m_clipboardData = data;
+
+ // tell all clients except the sender that the clipboard is dirty
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ client->setClipboardDirty(id, client != sender);
+ }
+
+ // send the new clipboard to the active screen
+ m_active->setClipboard(id, &clipboard.m_clipboard);
+}
+
+void
+Server::onScreensaver(bool activated)
+{
+ LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
+
+ if (activated) {
+ // save current screen and position
+ m_activeSaver = m_active;
+ m_xSaver = m_x;
+ m_ySaver = m_y;
+
+ // jump to primary screen
+ if (m_active != m_primaryClient) {
+ switchScreen(m_primaryClient, 0, 0, true);
+ }
+ }
+ else {
+ // jump back to previous screen and position. we must check
+ // that the position is still valid since the screen may have
+ // changed resolutions while the screen saver was running.
+ if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
+ // check position
+ BaseClientProxy* screen = m_activeSaver;
+ SInt32 x, y, w, h;
+ screen->getShape(x, y, w, h);
+ SInt32 zoneSize = getJumpZoneSize(screen);
+ if (m_xSaver < x + zoneSize) {
+ m_xSaver = x + zoneSize;
+ }
+ else if (m_xSaver >= x + w - zoneSize) {
+ m_xSaver = x + w - zoneSize - 1;
+ }
+ if (m_ySaver < y + zoneSize) {
+ m_ySaver = y + zoneSize;
+ }
+ else if (m_ySaver >= y + h - zoneSize) {
+ m_ySaver = y + h - zoneSize - 1;
+ }
+
+ // jump
+ switchScreen(screen, m_xSaver, m_ySaver, false);
+ }
+
+ // reset state
+ m_activeSaver = NULL;
+ }
+
+ // send message to all clients
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ client->screensaver(activated);
+ }
+}
+
+void
+Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button,
+ const char* screens)
+{
+ LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
+ assert(m_active != NULL);
+
+ // relay
+ if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) {
+ m_active->keyDown(id, mask, button);
+ }
+ else {
+ if (!screens && m_keyboardBroadcasting) {
+ screens = m_keyboardBroadcastingScreens.c_str();
+ if (IKeyState::KeyInfo::isDefault(screens)) {
+ screens = "*";
+ }
+ }
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ if (IKeyState::KeyInfo::contains(screens, index->first)) {
+ index->second->keyDown(id, mask, button);
+ }
+ }
+ }
+}
+
+void
+Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button,
+ const char* screens)
+{
+ LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
+ assert(m_active != NULL);
+
+ // relay
+ if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) {
+ m_active->keyUp(id, mask, button);
+ }
+ else {
+ if (!screens && m_keyboardBroadcasting) {
+ screens = m_keyboardBroadcastingScreens.c_str();
+ if (IKeyState::KeyInfo::isDefault(screens)) {
+ screens = "*";
+ }
+ }
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ if (IKeyState::KeyInfo::contains(screens, index->first)) {
+ index->second->keyUp(id, mask, button);
+ }
+ }
+ }
+}
+
+void
+Server::onKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->keyRepeat(id, mask, count, button);
+}
+
+void
+Server::onMouseDown(ButtonID id)
+{
+ LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseDown(id);
+
+ // reset this variable back to default value true
+ m_waitDragInfoThread = true;
+}
+
+void
+Server::onMouseUp(ButtonID id)
+{
+ LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseUp(id);
+
+ if (m_ignoreFileTransfer) {
+ m_ignoreFileTransfer = false;
+ return;
+ }
+
+ if (m_args.m_enableDragDrop) {
+ if (!m_screen->isOnScreen()) {
+ String& file = m_screen->getDraggingFilename();
+ if (!file.empty()) {
+ sendFileToClient(file.c_str());
+ }
+ }
+
+ // always clear dragging filename
+ m_screen->clearDraggingFilename();
+ }
+}
+
+bool
+Server::onMouseMovePrimary(SInt32 x, SInt32 y)
+{
+ LOG((CLOG_DEBUG4 "onMouseMovePrimary %d,%d", x, y));
+
+ // mouse move on primary (server's) screen
+ if (m_active != m_primaryClient) {
+ // stale event -- we're actually on a secondary screen
+ return false;
+ }
+
+ // save last delta
+ m_xDelta2 = m_xDelta;
+ m_yDelta2 = m_yDelta;
+
+ // save current delta
+ m_xDelta = x - m_x;
+ m_yDelta = y - m_y;
+
+ // save position
+ m_x = x;
+ m_y = y;
+
+ // get screen shape
+ SInt32 ax, ay, aw, ah;
+ m_active->getShape(ax, ay, aw, ah);
+ SInt32 zoneSize = getJumpZoneSize(m_active);
+
+ // clamp position to screen
+ SInt32 xc = x, yc = y;
+ if (xc < ax + zoneSize) {
+ xc = ax;
+ }
+ else if (xc >= ax + aw - zoneSize) {
+ xc = ax + aw - 1;
+ }
+ if (yc < ay + zoneSize) {
+ yc = ay;
+ }
+ else if (yc >= ay + ah - zoneSize) {
+ yc = ay + ah - 1;
+ }
+
+ // see if we should change screens
+ // when the cursor is in a corner, there may be a screen either
+ // horizontally or vertically. check both directions.
+ EDirection dirh = kNoDirection, dirv = kNoDirection;
+ SInt32 xh = x, yv = y;
+ if (x < ax + zoneSize) {
+ xh -= zoneSize;
+ dirh = kLeft;
+ }
+ else if (x >= ax + aw - zoneSize) {
+ xh += zoneSize;
+ dirh = kRight;
+ }
+ if (y < ay + zoneSize) {
+ yv -= zoneSize;
+ dirv = kTop;
+ }
+ else if (y >= ay + ah - zoneSize) {
+ yv += zoneSize;
+ dirv = kBottom;
+ }
+ if (dirh == kNoDirection && dirv == kNoDirection) {
+ // still on local screen
+ noSwitch(x, y);
+ return false;
+ }
+
+ // check both horizontally and vertically
+ EDirection dirs[] = {dirh, dirv};
+ SInt32 xs[] = {xh, x}, ys[] = {y, yv};
+ for (int i = 0; i < 2; ++i) {
+ EDirection dir = dirs[i];
+ if (dir == kNoDirection) {
+ continue;
+ }
+ x = xs[i], y = ys[i];
+
+ // get jump destination
+ BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
+
+ // should we switch or not?
+ if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
+ if (m_args.m_enableDragDrop
+ && m_screen->isDraggingStarted()
+ && m_active != newScreen
+ && m_waitDragInfoThread) {
+ if (m_sendDragInfoThread == NULL) {
+ m_sendDragInfoThread = new Thread(
+ new TMethodJob<Server>(
+ this,
+ &Server::sendDragInfoThread, newScreen));
+ }
+
+ return false;
+ }
+
+ // switch screen
+ switchScreen(newScreen, x, y, false);
+ m_waitDragInfoThread = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+Server::sendDragInfoThread(void* arg)
+{
+ BaseClientProxy* newScreen = static_cast<BaseClientProxy*>(arg);
+
+ m_dragFileList.clear();
+ String& dragFileList = m_screen->getDraggingFilename();
+ if (!dragFileList.empty()) {
+ DragInformation di;
+ di.setFilename(dragFileList);
+ m_dragFileList.push_back(di);
+ }
+
+#if defined(__APPLE__)
+ // on mac it seems that after faking a LMB up, system would signal back
+ // to barrier a mouse up event, which doesn't happen on windows. as a
+ // result, barrier would send dragging file to client twice. This variable
+ // is used to ignore the first file sending.
+ m_ignoreFileTransfer = true;
+#endif
+
+ // send drag file info to client if there is any
+ if (m_dragFileList.size() > 0) {
+ sendDragInfo(newScreen);
+ m_dragFileList.clear();
+ }
+ m_waitDragInfoThread = false;
+ m_sendDragInfoThread = NULL;
+}
+
+void
+Server::sendDragInfo(BaseClientProxy* newScreen)
+{
+ String infoString;
+ UInt32 fileCount = DragInformation::setupDragInfo(m_dragFileList, infoString);
+
+ if (fileCount > 0) {
+ char* info = NULL;
+ size_t size = infoString.size();
+ info = new char[size];
+ memcpy(info, infoString.c_str(), size);
+
+ LOG((CLOG_DEBUG2 "sending drag information to client"));
+ LOG((CLOG_DEBUG3 "dragging file list: %s", info));
+ LOG((CLOG_DEBUG3 "dragging file list string size: %i", size));
+ newScreen->sendDragInfo(fileCount, info, size);
+ }
+}
+
+void
+Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
+{
+ LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
+
+ // mouse move on secondary (client's) screen
+ assert(m_active != NULL);
+ if (m_active == m_primaryClient) {
+ // stale event -- we're actually on the primary screen
+ return;
+ }
+
+ // if doing relative motion on secondary screens and we're locked
+ // to the screen (which activates relative moves) then send a
+ // relative mouse motion. when we're doing this we pretend as if
+ // the mouse isn't actually moving because we're expecting some
+ // program on the secondary screen to warp the mouse on us, so we
+ // have no idea where it really is.
+ if (m_relativeMoves && isLockedToScreenServer()) {
+ LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy));
+ m_active->mouseRelativeMove(dx, dy);
+ return;
+ }
+
+ // save old position
+ const SInt32 xOld = m_x;
+ const SInt32 yOld = m_y;
+
+ // save last delta
+ m_xDelta2 = m_xDelta;
+ m_yDelta2 = m_yDelta;
+
+ // save current delta
+ m_xDelta = dx;
+ m_yDelta = dy;
+
+ // accumulate motion
+ m_x += dx;
+ m_y += dy;
+
+ // get screen shape
+ SInt32 ax, ay, aw, ah;
+ m_active->getShape(ax, ay, aw, ah);
+
+ // find direction of neighbor and get the neighbor
+ bool jump = true;
+ BaseClientProxy* newScreen;
+ do {
+ // clamp position to screen
+ SInt32 xc = m_x, yc = m_y;
+ if (xc < ax) {
+ xc = ax;
+ }
+ else if (xc >= ax + aw) {
+ xc = ax + aw - 1;
+ }
+ if (yc < ay) {
+ yc = ay;
+ }
+ else if (yc >= ay + ah) {
+ yc = ay + ah - 1;
+ }
+
+ EDirection dir;
+ if (m_x < ax) {
+ dir = kLeft;
+ }
+ else if (m_x > ax + aw - 1) {
+ dir = kRight;
+ }
+ else if (m_y < ay) {
+ dir = kTop;
+ }
+ else if (m_y > ay + ah - 1) {
+ dir = kBottom;
+ }
+ else {
+ // we haven't left the screen
+ newScreen = m_active;
+ jump = false;
+
+ // if waiting and mouse is not on the border we're waiting
+ // on then stop waiting. also if it's not on the border
+ // then arm the double tap.
+ if (m_switchScreen != NULL) {
+ bool clearWait;
+ SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
+ switch (m_switchDir) {
+ case kLeft:
+ clearWait = (m_x >= ax + zoneSize);
+ break;
+
+ case kRight:
+ clearWait = (m_x <= ax + aw - 1 - zoneSize);
+ break;
+
+ case kTop:
+ clearWait = (m_y >= ay + zoneSize);
+ break;
+
+ case kBottom:
+ clearWait = (m_y <= ay + ah - 1 + zoneSize);
+ break;
+
+ default:
+ clearWait = false;
+ break;
+ }
+ if (clearWait) {
+ // still on local screen
+ noSwitch(m_x, m_y);
+ }
+ }
+
+ // skip rest of block
+ break;
+ }
+
+ // try to switch screen. get the neighbor.
+ newScreen = mapToNeighbor(m_active, dir, m_x, m_y);
+
+ // see if we should switch
+ if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {
+ newScreen = m_active;
+ jump = false;
+ }
+ } while (false);
+
+ if (jump) {
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ m_sendFileThread = NULL;
+ }
+
+ SInt32 newX = m_x;
+ SInt32 newY = m_y;
+
+ // switch screens
+ switchScreen(newScreen, newX, newY, false);
+ }
+ else {
+ // same screen. clamp mouse to edge.
+ m_x = xOld + dx;
+ m_y = yOld + dy;
+ if (m_x < ax) {
+ m_x = ax;
+ LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str()));
+ }
+ else if (m_x > ax + aw - 1) {
+ m_x = ax + aw - 1;
+ LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str()));
+ }
+ if (m_y < ay) {
+ m_y = ay;
+ LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str()));
+ }
+ else if (m_y > ay + ah - 1) {
+ m_y = ay + ah - 1;
+ LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str()));
+ }
+
+ // warp cursor if it moved.
+ if (m_x != xOld || m_y != yOld) {
+ LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y));
+ m_active->mouseMove(m_x, m_y);
+ }
+ }
+}
+
+void
+Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseWheel(xDelta, yDelta);
+}
+
+void
+Server::onFileChunkSending(const void* data)
+{
+ FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data));
+
+ LOG((CLOG_DEBUG1 "sending file chunk"));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
+}
+
+void
+Server::onFileRecieveCompleted()
+{
+ if (isReceivedFileSizeValid()) {
+ m_writeToDropDirThread = new Thread(
+ new TMethodJob<Server>(
+ this, &Server::writeToDropDirThread));
+ }
+}
+
+void
+Server::writeToDropDirThread(void*)
+{
+ LOG((CLOG_DEBUG "starting write to drop dir thread"));
+
+ while (m_screen->isFakeDraggingStarted()) {
+ ARCH->sleep(.1f);
+ }
+
+ DropHelper::writeToDir(m_screen->getDropTarget(), m_fakeDragFileList,
+ m_receivedFileData);
+}
+
+bool
+Server::addClient(BaseClientProxy* client)
+{
+ String name = getName(client);
+ if (m_clients.count(name) != 0) {
+ return false;
+ }
+
+ // add event handlers
+ m_events->adoptHandler(m_events->forIScreen().shapeChanged(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleShapeChanged, client));
+ m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleClipboardGrabbed, client));
+ m_events->adoptHandler(m_events->forClipboard().clipboardChanged(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleClipboardChanged, client));
+
+ // add to list
+ m_clientSet.insert(client);
+ m_clients.insert(std::make_pair(name, client));
+
+ // initialize client data
+ SInt32 x, y;
+ client->getCursorPos(x, y);
+ client->setJumpCursorPos(x, y);
+
+ // tell primary client about the active sides
+ m_primaryClient->reconfigure(getActivePrimarySides());
+
+ return true;
+}
+
+bool
+Server::removeClient(BaseClientProxy* client)
+{
+ // return false if not in list
+ ClientSet::iterator i = m_clientSet.find(client);
+ if (i == m_clientSet.end()) {
+ return false;
+ }
+
+ // remove event handlers
+ m_events->removeHandler(m_events->forIScreen().shapeChanged(),
+ client->getEventTarget());
+ m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
+ client->getEventTarget());
+ m_events->removeHandler(m_events->forClipboard().clipboardChanged(),
+ client->getEventTarget());
+
+ // remove from list
+ m_clients.erase(getName(client));
+ m_clientSet.erase(i);
+
+ return true;
+}
+
+void
+Server::closeClient(BaseClientProxy* client, const char* msg)
+{
+ assert(client != m_primaryClient);
+ assert(msg != NULL);
+
+ // send message to client. this message should cause the client
+ // to disconnect. we add this client to the closed client list
+ // and install a timer to remove the client if it doesn't respond
+ // quickly enough. we also remove the client from the active
+ // client list since we're not going to listen to it anymore.
+ // note that this method also works on clients that are not in
+ // the m_clients list. adoptClient() may call us with such a
+ // client.
+ LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str()));
+
+ // send message
+ // FIXME -- avoid type cast (kinda hard, though)
+ ((ClientProxy*)client)->close(msg);
+
+ // install timer. wait timeout seconds for client to close.
+ double timeout = 5.0;
+ EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL);
+ m_events->adoptHandler(Event::kTimer, timer,
+ new TMethodEventJob<Server>(this,
+ &Server::handleClientCloseTimeout, client));
+
+ // move client to closing list
+ removeClient(client);
+ m_oldClients.insert(std::make_pair(client, timer));
+
+ // if this client is the active screen then we have to
+ // jump off of it
+ forceLeaveClient(client);
+}
+
+void
+Server::closeClients(const Config& config)
+{
+ // collect the clients that are connected but are being dropped
+ // from the configuration (or who's canonical name is changing).
+ typedef std::set<BaseClientProxy*> RemovedClients;
+ RemovedClients removed;
+ for (ClientList::iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ if (!config.isCanonicalName(index->first)) {
+ removed.insert(index->second);
+ }
+ }
+
+ // don't close the primary client
+ removed.erase(m_primaryClient);
+
+ // now close them. we collect the list then close in two steps
+ // because closeClient() modifies the collection we iterate over.
+ for (RemovedClients::iterator index = removed.begin();
+ index != removed.end(); ++index) {
+ closeClient(*index, kMsgCClose);
+ }
+}
+
+void
+Server::removeActiveClient(BaseClientProxy* client)
+{
+ if (removeClient(client)) {
+ forceLeaveClient(client);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ if (m_clients.size() == 1 && m_oldClients.empty()) {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+ }
+}
+
+void
+Server::removeOldClient(BaseClientProxy* client)
+{
+ OldClients::iterator i = m_oldClients.find(client);
+ if (i != m_oldClients.end()) {
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ m_events->removeHandler(Event::kTimer, i->second);
+ m_events->deleteTimer(i->second);
+ m_oldClients.erase(i);
+ if (m_clients.size() == 1 && m_oldClients.empty()) {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+ }
+}
+
+void
+Server::forceLeaveClient(BaseClientProxy* client)
+{
+ BaseClientProxy* active =
+ (m_activeSaver != NULL) ? m_activeSaver : m_active;
+ if (active == client) {
+ // record new position (center of primary screen)
+ m_primaryClient->getCursorCenter(m_x, m_y);
+
+ // stop waiting to switch to this client
+ if (active == m_switchScreen) {
+ stopSwitch();
+ }
+
+ // don't notify active screen since it has probably already
+ // disconnected.
+ LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y));
+
+ // cut over
+ m_active = m_primaryClient;
+
+ // enter new screen (unless we already have because of the
+ // screen saver)
+ if (m_activeSaver == NULL) {
+ m_primaryClient->enter(m_x, m_y, m_seqNum,
+ m_primaryClient->getToggleMask(), false);
+ }
+ }
+
+ // if this screen had the cursor when the screen saver activated
+ // then we can't switch back to it when the screen saver
+ // deactivates.
+ if (m_activeSaver == client) {
+ m_activeSaver = NULL;
+ }
+
+ // tell primary client about the active sides
+ m_primaryClient->reconfigure(getActivePrimarySides());
+}
+
+
+//
+// Server::ClipboardInfo
+//
+
+Server::ClipboardInfo::ClipboardInfo() :
+ m_clipboard(),
+ m_clipboardData(),
+ m_clipboardOwner(),
+ m_clipboardSeqNum(0)
+{
+ // do nothing
+}
+
+
+//
+// Server::LockCursorToScreenInfo
+//
+
+Server::LockCursorToScreenInfo*
+Server::LockCursorToScreenInfo::alloc(State state)
+{
+ LockCursorToScreenInfo* info =
+ (LockCursorToScreenInfo*)malloc(sizeof(LockCursorToScreenInfo));
+ info->m_state = state;
+ return info;
+}
+
+
+//
+// Server::SwitchToScreenInfo
+//
+
+Server::SwitchToScreenInfo*
+Server::SwitchToScreenInfo::alloc(const String& screen)
+{
+ SwitchToScreenInfo* info =
+ (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) +
+ screen.size());
+ strcpy(info->m_screen, screen.c_str());
+ return info;
+}
+
+
+//
+// Server::SwitchInDirectionInfo
+//
+
+Server::SwitchInDirectionInfo*
+Server::SwitchInDirectionInfo::alloc(EDirection direction)
+{
+ SwitchInDirectionInfo* info =
+ (SwitchInDirectionInfo*)malloc(sizeof(SwitchInDirectionInfo));
+ info->m_direction = direction;
+ return info;
+}
+
+//
+// Server::KeyboardBroadcastInfo
+//
+
+Server::KeyboardBroadcastInfo*
+Server::KeyboardBroadcastInfo::alloc(State state)
+{
+ KeyboardBroadcastInfo* info =
+ (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo));
+ info->m_state = state;
+ info->m_screens[0] = '\0';
+ return info;
+}
+
+Server::KeyboardBroadcastInfo*
+Server::KeyboardBroadcastInfo::alloc(State state, const String& screens)
+{
+ KeyboardBroadcastInfo* info =
+ (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo) +
+ screens.size());
+ info->m_state = state;
+ strcpy(info->m_screens, screens.c_str());
+ return info;
+}
+
+bool
+Server::isReceivedFileSizeValid()
+{
+ return m_expectedFileSize == m_receivedFileData.size();
+}
+
+void
+Server::sendFileToClient(const char* filename)
+{
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ }
+
+ m_sendFileThread = new Thread(
+ new TMethodJob<Server>(
+ this, &Server::sendFileThread,
+ static_cast<void*>(const_cast<char*>(filename))));
+}
+
+void
+Server::sendFileThread(void* data)
+{
+ try {
+ char* filename = static_cast<char*>(data);
+ LOG((CLOG_DEBUG "sending file to client, filename=%s", filename));
+ StreamChunker::sendFile(filename, m_events, this);
+ }
+ catch (std::runtime_error &error) {
+ LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what()));
+ }
+
+ m_sendFileThread = NULL;
+}
+
+void
+Server::dragInfoReceived(UInt32 fileNum, String content)
+{
+ if (!m_args.m_enableDragDrop) {
+ LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info."));
+ return;
+ }
+
+ DragInformation::parseDragInfo(m_fakeDragFileList, fileNum, content);
+
+ m_screen->startDraggingFiles(m_fakeDragFileList);
+}
diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h
new file mode 100644
index 0000000..609af21
--- /dev/null
+++ b/src/lib/server/Server.h
@@ -0,0 +1,483 @@
+/*
+ * 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 "server/Config.h"
+#include "barrier/clipboard_types.h"
+#include "barrier/Clipboard.h"
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/INode.h"
+#include "barrier/DragInformation.h"
+#include "barrier/ServerArgs.h"
+#include "base/Event.h"
+#include "base/Stopwatch.h"
+#include "base/EventTypes.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+#include "common/stdvector.h"
+
+class BaseClientProxy;
+class EventQueueTimer;
+class PrimaryClient;
+class InputFilter;
+namespace barrier { class Screen; }
+class IEventQueue;
+class Thread;
+class ClientListener;
+
+//! Barrier server
+/*!
+This class implements the top-level server algorithms for barrier.
+*/
+class Server : public INode {
+public:
+ //! Lock cursor to screen data
+ class LockCursorToScreenInfo {
+ public:
+ enum State { kOff, kOn, kToggle };
+
+ static LockCursorToScreenInfo* alloc(State state = kToggle);
+
+ public:
+ State m_state;
+ };
+
+ //! Switch to screen data
+ class SwitchToScreenInfo {
+ public:
+ static SwitchToScreenInfo* alloc(const String& screen);
+
+ public:
+ // this is a C-string; this type is a variable size structure
+ char m_screen[1];
+ };
+
+ //! Switch in direction data
+ class SwitchInDirectionInfo {
+ public:
+ static SwitchInDirectionInfo* alloc(EDirection direction);
+
+ public:
+ EDirection m_direction;
+ };
+
+ //! Screen connected data
+ class ScreenConnectedInfo {
+ public:
+ ScreenConnectedInfo(String screen) : m_screen(screen) { }
+
+ public:
+ String m_screen; // was char[1]
+ };
+
+ //! Keyboard broadcast data
+ class KeyboardBroadcastInfo {
+ public:
+ enum State { kOff, kOn, kToggle };
+
+ static KeyboardBroadcastInfo* alloc(State state = kToggle);
+ static KeyboardBroadcastInfo* alloc(State state,
+ const String& screens);
+
+ public:
+ State m_state;
+ char m_screens[1];
+ };
+
+ /*!
+ Start the server with the configuration \p config and the primary
+ client (local screen) \p primaryClient. The client retains
+ ownership of \p primaryClient.
+ */
+ Server(Config& config, PrimaryClient* primaryClient,
+ barrier::Screen* screen, IEventQueue* events, ServerArgs const& args);
+ ~Server();
+
+#ifdef TEST_ENV
+ Server() : m_mock(true), m_config(NULL) { }
+ void setActive(BaseClientProxy* active) { m_active = active; }
+#endif
+
+ //! @name manipulators
+ //@{
+
+ //! Set configuration
+ /*!
+ Change the server's configuration. Returns true iff the new
+ configuration was accepted (it must include the server's name).
+ This will disconnect any clients no longer in the configuration.
+ */
+ bool setConfig(const Config&);
+
+ //! Add a client
+ /*!
+ Adds \p client to the server. The client is adopted and will be
+ destroyed when the client disconnects or is disconnected.
+ */
+ void adoptClient(BaseClientProxy* client);
+
+ //! Disconnect clients
+ /*!
+ Disconnect clients. This tells them to disconnect but does not wait
+ for them to actually do so. The server sends the disconnected event
+ when they're all disconnected (or immediately if none are connected).
+ The caller can also just destroy this object to force the disconnection.
+ */
+ void disconnect();
+
+ //! Create a new thread and use it to send file to client
+ void sendFileToClient(const char* filename);
+
+ //! Received dragging information from client
+ void dragInfoReceived(UInt32 fileNum, String content);
+
+ //! Store ClientListener pointer
+ void setListener(ClientListener* p) { m_clientListener = p; }
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get number of connected clients
+ /*!
+ Returns the number of connected clients, including the server itself.
+ */
+ UInt32 getNumClients() const;
+
+ //! Get the list of connected clients
+ /*!
+ Set the \c list to the names of the currently connected clients.
+ */
+ void getClients(std::vector<String>& list) const;
+
+ //! Return true if recieved file size is valid
+ bool isReceivedFileSizeValid();
+
+ //! Return expected file data size
+ size_t& getExpectedFileSize() { return m_expectedFileSize; }
+
+ //! Return received file data
+ String& getReceivedFileData() { return m_receivedFileData; }
+
+ //! Return fake drag file list
+ DragFileList getFakeDragFileList() { return m_fakeDragFileList; }
+
+ //@}
+
+private:
+ // get canonical name of client
+ String getName(const BaseClientProxy*) const;
+
+ // get the sides of the primary screen that have neighbors
+ UInt32 getActivePrimarySides() const;
+
+ // returns true iff mouse should be locked to the current screen
+ // according to this object only, ignoring what the primary client
+ // says.
+ bool isLockedToScreenServer() const;
+
+ // returns true iff mouse should be locked to the current screen
+ // according to this object or the primary client.
+ bool isLockedToScreen() const;
+
+ // returns the jump zone of the client
+ SInt32 getJumpZoneSize(BaseClientProxy*) const;
+
+ // change the active screen
+ void switchScreen(BaseClientProxy*,
+ SInt32 x, SInt32 y, bool forScreenSaver);
+
+ // jump to screen
+ void jumpToScreen(BaseClientProxy*);
+
+ // convert pixel position to fraction, using x or y depending on the
+ // direction.
+ float mapToFraction(BaseClientProxy*, EDirection,
+ SInt32 x, SInt32 y) const;
+
+ // convert fraction to pixel position, writing only x or y depending
+ // on the direction.
+ void mapToPixel(BaseClientProxy*, EDirection, float f,
+ SInt32& x, SInt32& y) const;
+
+ // returns true if the client has a neighbor anywhere along the edge
+ // indicated by the direction.
+ bool hasAnyNeighbor(BaseClientProxy*, EDirection) const;
+
+ // lookup neighboring screen, mapping the coordinate independent of
+ // the direction to the neighbor's coordinate space.
+ BaseClientProxy* getNeighbor(BaseClientProxy*, EDirection,
+ SInt32& x, SInt32& y) const;
+
+ // lookup neighboring screen. given a position relative to the
+ // source screen, find the screen we should move onto and where.
+ // if the position is sufficiently far from the source then we
+ // cross multiple screens. if there is no suitable screen then
+ // return NULL and x,y are not modified.
+ BaseClientProxy* mapToNeighbor(BaseClientProxy*, EDirection,
+ SInt32& x, SInt32& y) const;
+
+ // adjusts x and y or neither to avoid ending up in a jump zone
+ // after entering the client in the given direction.
+ void avoidJumpZone(BaseClientProxy*, EDirection,
+ SInt32& x, SInt32& y) const;
+
+ // test if a switch is permitted. this includes testing user
+ // options like switch delay and tracking any state required to
+ // implement them. returns true iff a switch is permitted.
+ bool isSwitchOkay(BaseClientProxy* dst, EDirection,
+ SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive);
+
+ // update switch state due to a mouse move at \p x, \p y that
+ // doesn't switch screens.
+ void noSwitch(SInt32 x, SInt32 y);
+
+ // stop switch timers
+ void stopSwitch();
+
+ // start two tap switch timer
+ void startSwitchTwoTap();
+
+ // arm the two tap switch timer if \p x, \p y is outside the tap zone
+ void armSwitchTwoTap(SInt32 x, SInt32 y);
+
+ // stop the two tap switch timer
+ void stopSwitchTwoTap();
+
+ // returns true iff the two tap switch timer is started
+ bool isSwitchTwoTapStarted() const;
+
+ // returns true iff should switch because of two tap
+ bool shouldSwitchTwoTap() const;
+
+ // start delay switch timer
+ void startSwitchWait(SInt32 x, SInt32 y);
+
+ // stop delay switch timer
+ void stopSwitchWait();
+
+ // returns true iff the delay switch timer is started
+ bool isSwitchWaitStarted() const;
+
+ // returns the corner (EScreenSwitchCornerMasks) where x,y is on the
+ // given client. corners have the given size.
+ UInt32 getCorner(BaseClientProxy*,
+ SInt32 x, SInt32 y, SInt32 size) const;
+
+ // stop relative mouse moves
+ void stopRelativeMoves();
+
+ // send screen options to \c client
+ void sendOptions(BaseClientProxy* client) const;
+
+ // process options from configuration
+ void processOptions();
+
+ // event handlers
+ void handleShapeChanged(const Event&, void*);
+ void handleClipboardGrabbed(const Event&, void*);
+ void handleClipboardChanged(const Event&, void*);
+ void handleKeyDownEvent(const Event&, void*);
+ void handleKeyUpEvent(const Event&, void*);
+ void handleKeyRepeatEvent(const Event&, void*);
+ void handleButtonDownEvent(const Event&, void*);
+ void handleButtonUpEvent(const Event&, void*);
+ void handleMotionPrimaryEvent(const Event&, void*);
+ void handleMotionSecondaryEvent(const Event&, void*);
+ void handleWheelEvent(const Event&, void*);
+ void handleScreensaverActivatedEvent(const Event&, void*);
+ void handleScreensaverDeactivatedEvent(const Event&, void*);
+ void handleSwitchWaitTimeout(const Event&, void*);
+ void handleClientDisconnected(const Event&, void*);
+ void handleClientCloseTimeout(const Event&, void*);
+ void handleSwitchToScreenEvent(const Event&, void*);
+ void handleSwitchInDirectionEvent(const Event&, void*);
+ void handleKeyboardBroadcastEvent(const Event&,void*);
+ void handleLockCursorToScreenEvent(const Event&, void*);
+ void handleFakeInputBeginEvent(const Event&, void*);
+ void handleFakeInputEndEvent(const Event&, void*);
+ void handleFileChunkSendingEvent(const Event&, void*);
+ void handleFileRecieveCompletedEvent(const Event&, void*);
+
+ // event processing
+ void onClipboardChanged(BaseClientProxy* sender,
+ ClipboardID id, UInt32 seqNum);
+ void onScreensaver(bool activated);
+ void onKeyDown(KeyID, KeyModifierMask, KeyButton,
+ const char* screens);
+ void onKeyUp(KeyID, KeyModifierMask, KeyButton,
+ const char* screens);
+ void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton);
+ void onMouseDown(ButtonID);
+ void onMouseUp(ButtonID);
+ bool onMouseMovePrimary(SInt32 x, SInt32 y);
+ void onMouseMoveSecondary(SInt32 dx, SInt32 dy);
+ void onMouseWheel(SInt32 xDelta, SInt32 yDelta);
+ void onFileChunkSending(const void* data);
+ void onFileRecieveCompleted();
+
+ // add client to list and attach event handlers for client
+ bool addClient(BaseClientProxy*);
+
+ // remove client from list and detach event handlers for client
+ bool removeClient(BaseClientProxy*);
+
+ // close a client
+ void closeClient(BaseClientProxy*, const char* msg);
+
+ // close clients not in \p config
+ void closeClients(const Config& config);
+
+ // close all clients whether they've completed the handshake or not,
+ // except the primary client
+ void closeAllClients();
+
+ // remove clients from internal state
+ void removeActiveClient(BaseClientProxy*);
+ void removeOldClient(BaseClientProxy*);
+
+ // force the cursor off of \p client
+ void forceLeaveClient(BaseClientProxy* client);
+
+ // thread funciton for sending file
+ void sendFileThread(void*);
+
+ // thread function for writing file to drop directory
+ void writeToDropDirThread(void*);
+
+ // thread function for sending drag information
+ void sendDragInfoThread(void*);
+
+ // send drag info to new client screen
+ void sendDragInfo(BaseClientProxy* newScreen);
+
+public:
+ bool m_mock;
+
+private:
+ class ClipboardInfo {
+ public:
+ ClipboardInfo();
+
+ public:
+ Clipboard m_clipboard;
+ String m_clipboardData;
+ String m_clipboardOwner;
+ UInt32 m_clipboardSeqNum;
+ };
+
+ // the primary screen client
+ PrimaryClient* m_primaryClient;
+
+ // all clients (including the primary client) indexed by name
+ typedef std::map<String, BaseClientProxy*> ClientList;
+ typedef std::set<BaseClientProxy*> ClientSet;
+ ClientList m_clients;
+ ClientSet m_clientSet;
+
+ // all old connections that we're waiting to hangup
+ typedef std::map<BaseClientProxy*, EventQueueTimer*> OldClients;
+ OldClients m_oldClients;
+
+ // the client with focus
+ BaseClientProxy* m_active;
+
+ // the sequence number of enter messages
+ UInt32 m_seqNum;
+
+ // current mouse position (in absolute screen coordinates) on
+ // whichever screen is active
+ SInt32 m_x, m_y;
+
+ // last mouse deltas. this is needed to smooth out double tap
+ // on win32 which reports bogus mouse motion at the edge of
+ // the screen when using low level hooks, synthesizing motion
+ // in the opposite direction the mouse actually moved.
+ SInt32 m_xDelta, m_yDelta;
+ SInt32 m_xDelta2, m_yDelta2;
+
+ // current configuration
+ Config* m_config;
+
+ // input filter (from m_config);
+ InputFilter* m_inputFilter;
+
+ // clipboard cache
+ ClipboardInfo m_clipboards[kClipboardEnd];
+
+ // state saved when screen saver activates
+ BaseClientProxy* m_activeSaver;
+ SInt32 m_xSaver, m_ySaver;
+
+ // common state for screen switch tests. all tests are always
+ // trying to reach the same screen in the same direction.
+ EDirection m_switchDir;
+ BaseClientProxy* m_switchScreen;
+
+ // state for delayed screen switching
+ double m_switchWaitDelay;
+ EventQueueTimer* m_switchWaitTimer;
+ SInt32 m_switchWaitX, m_switchWaitY;
+
+ // state for double-tap screen switching
+ double m_switchTwoTapDelay;
+ Stopwatch m_switchTwoTapTimer;
+ bool m_switchTwoTapEngaged;
+ bool m_switchTwoTapArmed;
+ SInt32 m_switchTwoTapZone;
+
+ // modifiers needed before switching
+ bool m_switchNeedsShift;
+ bool m_switchNeedsControl;
+ bool m_switchNeedsAlt;
+
+ // relative mouse move option
+ bool m_relativeMoves;
+
+ // flag whether or not we have broadcasting enabled and the screens to
+ // which we should send broadcasted keys.
+ bool m_keyboardBroadcasting;
+ String m_keyboardBroadcastingScreens;
+
+ // screen locking (former scroll lock)
+ bool m_lockedToScreen;
+
+ // server screen
+ barrier::Screen* m_screen;
+
+ IEventQueue* m_events;
+
+ // file transfer
+ size_t m_expectedFileSize;
+ String m_receivedFileData;
+ DragFileList m_dragFileList;
+ DragFileList m_fakeDragFileList;
+ Thread* m_sendFileThread;
+ Thread* m_writeToDropDirThread;
+ String m_dragFileExt;
+ bool m_ignoreFileTransfer;
+ bool m_enableClipboard;
+
+ Thread* m_sendDragInfoThread;
+ bool m_waitDragInfoThread;
+
+ ClientListener* m_clientListener;
+ ServerArgs m_args;
+};