diff options
Diffstat (limited to 'src/lib/ipc')
| -rw-r--r-- | src/lib/ipc/CMakeLists.txt | 28 | ||||
| -rw-r--r-- | src/lib/ipc/Ipc.cpp | 24 | ||||
| -rw-r--r-- | src/lib/ipc/Ipc.h | 52 | ||||
| -rw-r--r-- | src/lib/ipc/IpcClient.cpp | 108 | ||||
| -rw-r--r-- | src/lib/ipc/IpcClient.h | 64 | ||||
| -rw-r--r-- | src/lib/ipc/IpcClientProxy.cpp | 194 | ||||
| -rw-r--r-- | src/lib/ipc/IpcClientProxy.h | 55 | ||||
| -rw-r--r-- | src/lib/ipc/IpcLogOutputter.cpp | 228 | ||||
| -rw-r--r-- | src/lib/ipc/IpcLogOutputter.h | 119 | ||||
| -rw-r--r-- | src/lib/ipc/IpcMessage.cpp | 69 | ||||
| -rw-r--r-- | src/lib/ipc/IpcMessage.h | 85 | ||||
| -rw-r--r-- | src/lib/ipc/IpcServer.cpp | 187 | ||||
| -rw-r--r-- | src/lib/ipc/IpcServer.h | 92 | ||||
| -rw-r--r-- | src/lib/ipc/IpcServerProxy.cpp | 123 | ||||
| -rw-r--r-- | src/lib/ipc/IpcServerProxy.h | 46 |
15 files changed, 1474 insertions, 0 deletions
diff --git a/src/lib/ipc/CMakeLists.txt b/src/lib/ipc/CMakeLists.txt new file mode 100644 index 0000000..3c7302a --- /dev/null +++ b/src/lib/ipc/CMakeLists.txt @@ -0,0 +1,28 @@ +# barrier -- mouse and keyboard sharing utility +# Copyright (C) 2012-2016 Symless Ltd. +# Copyright (C) 2009 Nick Bolton +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file LICENSE that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +file(GLOB headers "*.h") +file(GLOB sources "*.cpp") + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +add_library(ipc STATIC ${sources}) + +if (UNIX) + target_link_libraries(ipc arch base common mt io net synlib) +endif() diff --git a/src/lib/ipc/Ipc.cpp b/src/lib/ipc/Ipc.cpp new file mode 100644 index 0000000..78b8407 --- /dev/null +++ b/src/lib/ipc/Ipc.cpp @@ -0,0 +1,24 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/Ipc.h" + +const char* kIpcMsgHello = "IHEL%1i"; +const char* kIpcMsgLogLine = "ILOG%s"; +const char* kIpcMsgCommand = "ICMD%s%1i"; +const char* kIpcMsgShutdown = "ISDN"; diff --git a/src/lib/ipc/Ipc.h b/src/lib/ipc/Ipc.h new file mode 100644 index 0000000..bc69c08 --- /dev/null +++ b/src/lib/ipc/Ipc.h @@ -0,0 +1,52 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#define IPC_HOST "127.0.0.1" +#define IPC_PORT 24801 + +enum EIpcMessage { + kIpcHello, + kIpcLogLine, + kIpcCommand, + kIpcShutdown, +}; + +enum EIpcClientType { + kIpcClientUnknown, + kIpcClientGui, + kIpcClientNode, +}; + +// handshake: node/gui -> daemon +// $1 = type, the client identifies it's self as gui or node (barrierc/s). +extern const char* kIpcMsgHello; + +// log line: daemon -> gui +// $1 = aggregate log lines collected from barriers/c or the daemon itself. +extern const char* kIpcMsgLogLine; + +// command: gui -> daemon +// $1 = command; the command for the daemon to launch, typically the full +// path to barriers/c. $2 = true when process must be elevated on ms windows. +extern const char* kIpcMsgCommand; + +// shutdown: daemon -> node +// the daemon tells barriers/c to shut down gracefully. +extern const char* kIpcMsgShutdown; diff --git a/src/lib/ipc/IpcClient.cpp b/src/lib/ipc/IpcClient.cpp new file mode 100644 index 0000000..4eeae5b --- /dev/null +++ b/src/lib/ipc/IpcClient.cpp @@ -0,0 +1,108 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcClient.h" +#include "ipc/Ipc.h" +#include "ipc/IpcServerProxy.h" +#include "ipc/IpcMessage.h" +#include "base/TMethodEventJob.h" + +// +// IpcClient +// + +IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_serverAddress(NetworkAddress(IPC_HOST, IPC_PORT)), + m_socket(events, socketMultiplexer, IArchNetwork::kINET), + m_server(nullptr), + m_events(events) +{ + init(); +} + +IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : + m_serverAddress(NetworkAddress(IPC_HOST, port)), + m_socket(events, socketMultiplexer, IArchNetwork::kINET), + m_server(nullptr), + m_events(events) +{ + init(); +} + +void +IpcClient::init() +{ + m_serverAddress.resolve(); +} + +IpcClient::~IpcClient() +{ +} + +void +IpcClient::connect() +{ + m_events->adoptHandler( + m_events->forIDataSocket().connected(), m_socket.getEventTarget(), + new TMethodEventJob<IpcClient>( + this, &IpcClient::handleConnected)); + + m_socket.connect(m_serverAddress); + m_server = new IpcServerProxy(m_socket, m_events); + + m_events->adoptHandler( + m_events->forIpcServerProxy().messageReceived(), m_server, + new TMethodEventJob<IpcClient>( + this, &IpcClient::handleMessageReceived)); +} + +void +IpcClient::disconnect() +{ + m_events->removeHandler(m_events->forIDataSocket().connected(), m_socket.getEventTarget()); + m_events->removeHandler(m_events->forIpcServerProxy().messageReceived(), m_server); + + m_server->disconnect(); + delete m_server; + m_server = nullptr; +} + +void +IpcClient::send(const IpcMessage& message) +{ + assert(m_server != nullptr); + m_server->send(message); +} + +void +IpcClient::handleConnected(const Event&, void*) +{ + m_events->addEvent(Event( + m_events->forIpcClient().connected(), this, m_server, Event::kDontFreeData)); + + IpcHelloMessage message(kIpcClientNode); + send(message); +} + +void +IpcClient::handleMessageReceived(const Event& e, void*) +{ + Event event(m_events->forIpcClient().messageReceived(), this); + event.setDataObject(e.getDataObject()); + m_events->addEvent(event); +} diff --git a/src/lib/ipc/IpcClient.h b/src/lib/ipc/IpcClient.h new file mode 100644 index 0000000..1e9bca6 --- /dev/null +++ b/src/lib/ipc/IpcClient.h @@ -0,0 +1,64 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "net/NetworkAddress.h" +#include "net/TCPSocket.h" +#include "base/EventTypes.h" + +class IpcServerProxy; +class IpcMessage; +class IEventQueue; +class SocketMultiplexer; + +//! IPC client for communication between daemon and GUI. +/*! + * See \ref IpcServer description. + */ +class IpcClient { +public: + IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port); + virtual ~IpcClient(); + + //! @name manipulators + //@{ + + //! Connects to the IPC server at localhost. + void connect(); + + //! Disconnects from the IPC server. + void disconnect(); + + //! Sends a message to the server. + void send(const IpcMessage& message); + + //@} + +private: + void init(); + void handleConnected(const Event&, void*); + void handleMessageReceived(const Event&, void*); + +private: + NetworkAddress m_serverAddress; + TCPSocket m_socket; + IpcServerProxy* m_server; + IEventQueue* m_events; +}; diff --git a/src/lib/ipc/IpcClientProxy.cpp b/src/lib/ipc/IpcClientProxy.cpp new file mode 100644 index 0000000..af85eca --- /dev/null +++ b/src/lib/ipc/IpcClientProxy.cpp @@ -0,0 +1,194 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcClientProxy.h" + +#include "ipc/Ipc.h" +#include "ipc/IpcMessage.h" +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "arch/Arch.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// IpcClientProxy +// + +IpcClientProxy::IpcClientProxy(barrier::IStream& stream, IEventQueue* events) : + m_stream(stream), + m_clientType(kIpcClientUnknown), + m_disconnecting(false), + m_readMutex(ARCH->newMutex()), + m_writeMutex(ARCH->newMutex()), + m_events(events) +{ + m_events->adoptHandler( + m_events->forIStream().inputReady(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleData)); + + m_events->adoptHandler( + m_events->forIStream().outputError(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleWriteError)); + + m_events->adoptHandler( + m_events->forIStream().inputShutdown(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleDisconnect)); + + m_events->adoptHandler( + m_events->forIStream().outputShutdown(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleWriteError)); +} + +IpcClientProxy::~IpcClientProxy() +{ + 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()); + + // don't delete the stream while it's being used. + ARCH->lockMutex(m_readMutex); + ARCH->lockMutex(m_writeMutex); + delete &m_stream; + ARCH->unlockMutex(m_readMutex); + ARCH->unlockMutex(m_writeMutex); + + ARCH->closeMutex(m_readMutex); + ARCH->closeMutex(m_writeMutex); +} + +void +IpcClientProxy::handleDisconnect(const Event&, void*) +{ + disconnect(); + LOG((CLOG_DEBUG "ipc client disconnected")); +} + +void +IpcClientProxy::handleWriteError(const Event&, void*) +{ + disconnect(); + LOG((CLOG_DEBUG "ipc client write error")); +} + +void +IpcClientProxy::handleData(const Event&, void*) +{ + // don't allow the dtor to destroy the stream while we're using it. + ArchMutexLock lock(m_readMutex); + + LOG((CLOG_DEBUG "start ipc handle data")); + + UInt8 code[4]; + UInt32 n = m_stream.read(code, 4); + while (n != 0) { + + LOG((CLOG_DEBUG "ipc read: %c%c%c%c", + code[0], code[1], code[2], code[3])); + + IpcMessage* m = nullptr; + if (memcmp(code, kIpcMsgHello, 4) == 0) { + m = parseHello(); + } + else if (memcmp(code, kIpcMsgCommand, 4) == 0) { + m = parseCommand(); + } + else { + LOG((CLOG_ERR "invalid ipc message")); + disconnect(); + } + + // don't delete with this event; the data is passed to a new event. + Event e(m_events->forIpcClientProxy().messageReceived(), this, NULL, Event::kDontFreeData); + e.setDataObject(m); + m_events->addEvent(e); + + n = m_stream.read(code, 4); + } + + LOG((CLOG_DEBUG "finished ipc handle data")); +} + +void +IpcClientProxy::send(const IpcMessage& message) +{ + // don't allow other threads to write until we've finished the entire + // message. stream write is locked, but only for that single write. + // also, don't allow the dtor to destroy the stream while we're using it. + ArchMutexLock lock(m_writeMutex); + + LOG((CLOG_DEBUG4 "ipc write: %d", message.type())); + + switch (message.type()) { + case kIpcLogLine: { + const IpcLogLineMessage& llm = static_cast<const IpcLogLineMessage&>(message); + const String logLine = llm.logLine(); + ProtocolUtil::writef(&m_stream, kIpcMsgLogLine, &logLine); + break; + } + + case kIpcShutdown: + ProtocolUtil::writef(&m_stream, kIpcMsgShutdown); + break; + + default: + LOG((CLOG_ERR "ipc message not supported: %d", message.type())); + break; + } +} + +IpcHelloMessage* +IpcClientProxy::parseHello() +{ + UInt8 type; + ProtocolUtil::readf(&m_stream, kIpcMsgHello + 4, &type); + + m_clientType = static_cast<EIpcClientType>(type); + + // must be deleted by event handler. + return new IpcHelloMessage(m_clientType); +} + +IpcCommandMessage* +IpcClientProxy::parseCommand() +{ + String command; + UInt8 elevate; + ProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command, &elevate); + + // must be deleted by event handler. + return new IpcCommandMessage(command, elevate != 0); +} + +void +IpcClientProxy::disconnect() +{ + LOG((CLOG_DEBUG "ipc disconnect, closing stream")); + m_disconnecting = true; + m_stream.close(); + m_events->addEvent(Event(m_events->forIpcClientProxy().disconnected(), this)); +} diff --git a/src/lib/ipc/IpcClientProxy.h b/src/lib/ipc/IpcClientProxy.h new file mode 100644 index 0000000..eaa12c7 --- /dev/null +++ b/src/lib/ipc/IpcClientProxy.h @@ -0,0 +1,55 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "arch/IArchMultithread.h" +#include "base/EventTypes.h" +#include "base/Event.h" + +namespace barrier { class IStream; } +class IpcMessage; +class IpcCommandMessage; +class IpcHelloMessage; +class IEventQueue; + +class IpcClientProxy { + friend class IpcServer; + +public: + IpcClientProxy(barrier::IStream& stream, IEventQueue* events); + virtual ~IpcClientProxy(); + +private: + void send(const IpcMessage& message); + void handleData(const Event&, void*); + void handleDisconnect(const Event&, void*); + void handleWriteError(const Event&, void*); + IpcHelloMessage* parseHello(); + IpcCommandMessage* parseCommand(); + void disconnect(); + +private: + barrier::IStream& m_stream; + EIpcClientType m_clientType; + bool m_disconnecting; + ArchMutex m_readMutex; + ArchMutex m_writeMutex; + IEventQueue* m_events; +}; diff --git a/src/lib/ipc/IpcLogOutputter.cpp b/src/lib/ipc/IpcLogOutputter.cpp new file mode 100644 index 0000000..984793e --- /dev/null +++ b/src/lib/ipc/IpcLogOutputter.cpp @@ -0,0 +1,228 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcLogOutputter.h" + +#include "ipc/IpcServer.h" +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "ipc/IpcClientProxy.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Event.h" +#include "base/EventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" + +enum EIpcLogOutputter { + kBufferMaxSize = 1000, + kMaxSendLines = 100, + kBufferRateWriteLimit = 1000, // writes per kBufferRateTime + kBufferRateTimeLimit = 1 // seconds +}; + +IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread) : + m_ipcServer(ipcServer), + m_bufferMutex(ARCH->newMutex()), + m_sending(false), + m_bufferThread(nullptr), + m_running(false), + m_notifyCond(ARCH->newCondVar()), + m_notifyMutex(ARCH->newMutex()), + m_bufferThreadId(0), + m_bufferWaiting(false), + m_bufferMaxSize(kBufferMaxSize), + m_bufferRateWriteLimit(kBufferRateWriteLimit), + m_bufferRateTimeLimit(kBufferRateTimeLimit), + m_bufferWriteCount(0), + m_bufferRateStart(ARCH->time()), + m_clientType(clientType), + m_runningMutex(ARCH->newMutex()) +{ + if (useThread) { + m_bufferThread = new Thread(new TMethodJob<IpcLogOutputter>( + this, &IpcLogOutputter::bufferThread)); + } +} + +IpcLogOutputter::~IpcLogOutputter() +{ + close(); + + ARCH->closeMutex(m_bufferMutex); + + if (m_bufferThread != nullptr) { + m_bufferThread->cancel(); + m_bufferThread->wait(); + delete m_bufferThread; + } + + ARCH->closeCondVar(m_notifyCond); + ARCH->closeMutex(m_notifyMutex); +} + +void +IpcLogOutputter::open(const char* title) +{ +} + +void +IpcLogOutputter::close() +{ + if (m_bufferThread != nullptr) { + ArchMutexLock lock(m_runningMutex); + m_running = false; + notifyBuffer(); + m_bufferThread->wait(5); + } +} + +void +IpcLogOutputter::show(bool showIfEmpty) +{ +} + +bool +IpcLogOutputter::write(ELevel, const char* text) +{ + // ignore events from the buffer thread (would cause recursion). + if (m_bufferThread != nullptr && + Thread::getCurrentThread().getID() == m_bufferThreadId) { + return true; + } + + appendBuffer(text); + notifyBuffer(); + + return true; +} + +void +IpcLogOutputter::appendBuffer(const String& text) +{ + ArchMutexLock lock(m_bufferMutex); + + double elapsed = ARCH->time() - m_bufferRateStart; + if (elapsed < m_bufferRateTimeLimit) { + if (m_bufferWriteCount >= m_bufferRateWriteLimit) { + // discard the log line if we've logged too much. + return; + } + } + else { + m_bufferWriteCount = 0; + m_bufferRateStart = ARCH->time(); + } + + if (m_buffer.size() >= m_bufferMaxSize) { + // if the queue is exceeds size limit, + // throw away the oldest item + m_buffer.pop_front(); + } + + m_buffer.push_back(text); + m_bufferWriteCount++; +} + +bool +IpcLogOutputter::isRunning() +{ + ArchMutexLock lock(m_runningMutex); + return m_running; +} + +void +IpcLogOutputter::bufferThread(void*) +{ + m_bufferThreadId = m_bufferThread->getID(); + m_running = true; + + try { + while (isRunning()) { + if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) { + ArchMutexLock lock(m_notifyMutex); + ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1); + } + + sendBuffer(); + } + } + catch (XArch& e) { + LOG((CLOG_ERR "ipc log buffer thread error, %s", e.what())); + } + + LOG((CLOG_DEBUG "ipc log buffer thread finished")); +} + +void +IpcLogOutputter::notifyBuffer() +{ + ArchMutexLock lock(m_notifyMutex); + ARCH->broadcastCondVar(m_notifyCond); +} + +String +IpcLogOutputter::getChunk(size_t count) +{ + ArchMutexLock lock(m_bufferMutex); + + if (m_buffer.size() < count) { + count = m_buffer.size(); + } + + String chunk; + for (size_t i = 0; i < count; i++) { + chunk.append(m_buffer.front()); + chunk.append("\n"); + m_buffer.pop_front(); + } + return chunk; +} + +void +IpcLogOutputter::sendBuffer() +{ + if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) { + return; + } + + IpcLogLineMessage message(getChunk(kMaxSendLines)); + m_sending = true; + m_ipcServer.send(message, kIpcClientGui); + m_sending = false; +} + +void +IpcLogOutputter::bufferMaxSize(UInt16 bufferMaxSize) +{ + m_bufferMaxSize = bufferMaxSize; +} + +UInt16 +IpcLogOutputter::bufferMaxSize() const +{ + return m_bufferMaxSize; +} + +void +IpcLogOutputter::bufferRateLimit(UInt16 writeLimit, double timeLimit) +{ + m_bufferRateWriteLimit = writeLimit; + m_bufferRateTimeLimit = timeLimit; +} diff --git a/src/lib/ipc/IpcLogOutputter.h b/src/lib/ipc/IpcLogOutputter.h new file mode 100644 index 0000000..461f022 --- /dev/null +++ b/src/lib/ipc/IpcLogOutputter.h @@ -0,0 +1,119 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "arch/Arch.h" +#include "arch/IArchMultithread.h" +#include "base/ILogOutputter.h" +#include "ipc/Ipc.h" + +#include <deque> + +class IpcServer; +class Event; +class IpcClientProxy; + +//! Write log to GUI over IPC +/*! +This outputter writes output to the GUI via IPC. +*/ +class IpcLogOutputter : public ILogOutputter { +public: + /*! + If \p useThread is \c true, the buffer will be sent using a thread. + If \p useThread is \c false, then the buffer needs to be sent manually + using the \c sendBuffer() function. + */ + IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread); + virtual ~IpcLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + + //! @name manipulators + //@{ + + //! Notify that the buffer should be sent. + void notifyBuffer(); + + //! Set the buffer size + /*! + Set the maximum size of the buffer to protect memory + from runaway logging. + */ + void bufferMaxSize(UInt16 bufferMaxSize); + + //! Set the rate limit + /*! + Set the maximum number of \p writeRate for every \p timeRate in seconds. + */ + void bufferRateLimit(UInt16 writeLimit, double timeLimit); + + //! Send the buffer + /*! + Sends a chunk of the buffer to the IPC server, normally called + when threaded mode is on. + */ + void sendBuffer(); + + //@} + + //! @name accessors + //@{ + + //! Get the buffer size + /*! + Returns the maximum size of the buffer. + */ + UInt16 bufferMaxSize() const; + + //@} + +private: + void init(); + void bufferThread(void*); + String getChunk(size_t count); + void appendBuffer(const String& text); + bool isRunning(); + +private: + typedef std::deque<String> Buffer; + + IpcServer& m_ipcServer; + Buffer m_buffer; + ArchMutex m_bufferMutex; + bool m_sending; + Thread* m_bufferThread; + bool m_running; + ArchCond m_notifyCond; + ArchMutex m_notifyMutex; + bool m_bufferWaiting; + IArchMultithread::ThreadID + m_bufferThreadId; + UInt16 m_bufferMaxSize; + UInt16 m_bufferRateWriteLimit; + double m_bufferRateTimeLimit; + UInt16 m_bufferWriteCount; + double m_bufferRateStart; + EIpcClientType m_clientType; + ArchMutex m_runningMutex; +}; diff --git a/src/lib/ipc/IpcMessage.cpp b/src/lib/ipc/IpcMessage.cpp new file mode 100644 index 0000000..deef22d --- /dev/null +++ b/src/lib/ipc/IpcMessage.cpp @@ -0,0 +1,69 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" + +IpcMessage::IpcMessage(UInt8 type) : + m_type(type) +{ +} + +IpcMessage::~IpcMessage() +{ +} + +IpcHelloMessage::IpcHelloMessage(EIpcClientType clientType) : + IpcMessage(kIpcHello), + m_clientType(clientType) +{ +} + +IpcHelloMessage::~IpcHelloMessage() +{ +} + +IpcShutdownMessage::IpcShutdownMessage() : +IpcMessage(kIpcShutdown) +{ +} + +IpcShutdownMessage::~IpcShutdownMessage() +{ +} + +IpcLogLineMessage::IpcLogLineMessage(const String& logLine) : +IpcMessage(kIpcLogLine), +m_logLine(logLine) +{ +} + +IpcLogLineMessage::~IpcLogLineMessage() +{ +} + +IpcCommandMessage::IpcCommandMessage(const String& command, bool elevate) : +IpcMessage(kIpcCommand), +m_command(command), +m_elevate(elevate) +{ +} + +IpcCommandMessage::~IpcCommandMessage() +{ +} diff --git a/src/lib/ipc/IpcMessage.h b/src/lib/ipc/IpcMessage.h new file mode 100644 index 0000000..5cc3d79 --- /dev/null +++ b/src/lib/ipc/IpcMessage.h @@ -0,0 +1,85 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "base/EventTypes.h" +#include "base/String.h" +#include "base/Event.h" + +class IpcMessage : public EventData { +public: + virtual ~IpcMessage(); + + //! Gets the message type ID. + UInt8 type() const { return m_type; } + +protected: + IpcMessage(UInt8 type); + +private: + UInt8 m_type; +}; + +class IpcHelloMessage : public IpcMessage { +public: + IpcHelloMessage(EIpcClientType clientType); + virtual ~IpcHelloMessage(); + + //! Gets the message type ID. + EIpcClientType clientType() const { return m_clientType; } + +private: + EIpcClientType m_clientType; +}; + +class IpcShutdownMessage : public IpcMessage { +public: + IpcShutdownMessage(); + virtual ~IpcShutdownMessage(); +}; + + +class IpcLogLineMessage : public IpcMessage { +public: + IpcLogLineMessage(const String& logLine); + virtual ~IpcLogLineMessage(); + + //! Gets the log line. + String logLine() const { return m_logLine; } + +private: + String m_logLine; +}; + +class IpcCommandMessage : public IpcMessage { +public: + IpcCommandMessage(const String& command, bool elevate); + virtual ~IpcCommandMessage(); + + //! Gets the command. + String command() const { return m_command; } + + //! Gets whether or not the process should be elevated on MS Windows. + bool elevate() const { return m_elevate; } + +private: + String m_command; + bool m_elevate; +}; diff --git a/src/lib/ipc/IpcServer.cpp b/src/lib/ipc/IpcServer.cpp new file mode 100644 index 0000000..e05a913 --- /dev/null +++ b/src/lib/ipc/IpcServer.cpp @@ -0,0 +1,187 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcServer.h" + +#include "ipc/Ipc.h" +#include "ipc/IpcClientProxy.h" +#include "ipc/IpcMessage.h" +#include "net/IDataSocket.h" +#include "io/IStream.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/Event.h" +#include "base/Log.h" + +// +// IpcServer +// + +IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_socket(nullptr), + m_address(NetworkAddress(IPC_HOST, IPC_PORT)) +{ + init(); +} + +IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_address(NetworkAddress(IPC_HOST, port)) +{ + init(); +} + +void +IpcServer::init() +{ + m_socket = new TCPListenSocket(m_events, m_socketMultiplexer, IArchNetwork::kINET); + + m_clientsMutex = ARCH->newMutex(); + m_address.resolve(); + + m_events->adoptHandler( + m_events->forIListenSocket().connecting(), m_socket, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleClientConnecting)); +} + +IpcServer::~IpcServer() +{ + if (m_mock) { + return; + } + + if (m_socket != nullptr) { + delete m_socket; + } + + ARCH->lockMutex(m_clientsMutex); + ClientList::iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + deleteClient(*it); + } + m_clients.clear(); + ARCH->unlockMutex(m_clientsMutex); + ARCH->closeMutex(m_clientsMutex); + + m_events->removeHandler(m_events->forIListenSocket().connecting(), m_socket); +} + +void +IpcServer::listen() +{ + m_socket->bind(m_address); +} + +void +IpcServer::handleClientConnecting(const Event&, void*) +{ + barrier::IStream* stream = m_socket->accept(); + if (stream == NULL) { + return; + } + + LOG((CLOG_DEBUG "accepted ipc client connection")); + + ARCH->lockMutex(m_clientsMutex); + IpcClientProxy* proxy = new IpcClientProxy(*stream, m_events); + m_clients.push_back(proxy); + ARCH->unlockMutex(m_clientsMutex); + + m_events->adoptHandler( + m_events->forIpcClientProxy().disconnected(), proxy, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleClientDisconnected)); + + m_events->adoptHandler( + m_events->forIpcClientProxy().messageReceived(), proxy, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleMessageReceived)); + + m_events->addEvent(Event( + m_events->forIpcServer().clientConnected(), this, proxy, Event::kDontFreeData)); +} + +void +IpcServer::handleClientDisconnected(const Event& e, void*) +{ + IpcClientProxy* proxy = static_cast<IpcClientProxy*>(e.getTarget()); + + ArchMutexLock lock(m_clientsMutex); + m_clients.remove(proxy); + deleteClient(proxy); + + LOG((CLOG_DEBUG "ipc client proxy removed, connected=%d", m_clients.size())); +} + +void +IpcServer::handleMessageReceived(const Event& e, void*) +{ + Event event(m_events->forIpcServer().messageReceived(), this); + event.setDataObject(e.getDataObject()); + m_events->addEvent(event); +} + +void +IpcServer::deleteClient(IpcClientProxy* proxy) +{ + m_events->removeHandler(m_events->forIpcClientProxy().messageReceived(), proxy); + m_events->removeHandler(m_events->forIpcClientProxy().disconnected(), proxy); + delete proxy; +} + +bool +IpcServer::hasClients(EIpcClientType clientType) const +{ + ArchMutexLock lock(m_clientsMutex); + + if (m_clients.empty()) { + return false; + } + + ClientList::const_iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + // at least one client is alive and type matches, there are clients. + IpcClientProxy* p = *it; + if (!p->m_disconnecting && p->m_clientType == clientType) { + return true; + } + } + + // all clients must be disconnecting, no active clients. + return false; +} + +void +IpcServer::send(const IpcMessage& message, EIpcClientType filterType) +{ + ArchMutexLock lock(m_clientsMutex); + + ClientList::iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + IpcClientProxy* proxy = *it; + if (proxy->m_clientType == filterType) { + proxy->send(message); + } + } +} diff --git a/src/lib/ipc/IpcServer.h b/src/lib/ipc/IpcServer.h new file mode 100644 index 0000000..d9bbe3e --- /dev/null +++ b/src/lib/ipc/IpcServer.h @@ -0,0 +1,92 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "net/TCPListenSocket.h" +#include "net/NetworkAddress.h" +#include "arch/Arch.h" +#include "base/EventTypes.h" + +#include <list> + +class Event; +class IpcClientProxy; +class IpcMessage; +class IEventQueue; +class SocketMultiplexer; + +//! IPC server for communication between daemon and GUI. +/*! +The IPC server listens on localhost. The IPC client runs on both the +client/server process or the GUI. The IPC server runs on the daemon process. +This allows the GUI to send config changes to the daemon and client/server, +and allows the daemon and client/server to send log data to the GUI. +*/ +class IpcServer { +public: + IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port); + virtual ~IpcServer(); + + //! @name manipulators + //@{ + + //! Opens a TCP socket only allowing local connections. + virtual void listen(); + + //! Send a message to all clients matching the filter type. + virtual void send(const IpcMessage& message, EIpcClientType filterType); + + //@} + //! @name accessors + //@{ + + //! Returns true when there are clients of the specified type connected. + virtual bool hasClients(EIpcClientType clientType) const; + + //@} + +private: + void init(); + void handleClientConnecting(const Event&, void*); + void handleClientDisconnected(const Event&, void*); + void handleMessageReceived(const Event&, void*); + void deleteClient(IpcClientProxy* proxy); + +private: + typedef std::list<IpcClientProxy*> ClientList; + + bool m_mock; + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; + TCPListenSocket* m_socket; + NetworkAddress m_address; + ClientList m_clients; + ArchMutex m_clientsMutex; + +#ifdef TEST_ENV +public: + IpcServer() : + m_mock(true), + m_events(nullptr), + m_socketMultiplexer(nullptr), + m_socket(nullptr) { } +#endif +}; diff --git a/src/lib/ipc/IpcServerProxy.cpp b/src/lib/ipc/IpcServerProxy.cpp new file mode 100644 index 0000000..820e1ab --- /dev/null +++ b/src/lib/ipc/IpcServerProxy.cpp @@ -0,0 +1,123 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcServerProxy.h" + +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// IpcServerProxy +// + +IpcServerProxy::IpcServerProxy(barrier::IStream& stream, IEventQueue* events) : + m_stream(stream), + m_events(events) +{ + m_events->adoptHandler(m_events->forIStream().inputReady(), + stream.getEventTarget(), + new TMethodEventJob<IpcServerProxy>( + this, &IpcServerProxy::handleData)); +} + +IpcServerProxy::~IpcServerProxy() +{ + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream.getEventTarget()); +} + +void +IpcServerProxy::handleData(const Event&, void*) +{ + LOG((CLOG_DEBUG "start ipc handle data")); + + UInt8 code[4]; + UInt32 n = m_stream.read(code, 4); + while (n != 0) { + + LOG((CLOG_DEBUG "ipc read: %c%c%c%c", + code[0], code[1], code[2], code[3])); + + IpcMessage* m = nullptr; + if (memcmp(code, kIpcMsgLogLine, 4) == 0) { + m = parseLogLine(); + } + else if (memcmp(code, kIpcMsgShutdown, 4) == 0) { + m = new IpcShutdownMessage(); + } + else { + LOG((CLOG_ERR "invalid ipc message")); + disconnect(); + } + + // don't delete with this event; the data is passed to a new event. + Event e(m_events->forIpcServerProxy().messageReceived(), this, NULL, Event::kDontFreeData); + e.setDataObject(m); + m_events->addEvent(e); + + n = m_stream.read(code, 4); + } + + LOG((CLOG_DEBUG "finished ipc handle data")); +} + +void +IpcServerProxy::send(const IpcMessage& message) +{ + LOG((CLOG_DEBUG4 "ipc write: %d", message.type())); + + switch (message.type()) { + case kIpcHello: { + const IpcHelloMessage& hm = static_cast<const IpcHelloMessage&>(message); + ProtocolUtil::writef(&m_stream, kIpcMsgHello, hm.clientType()); + break; + } + + case kIpcCommand: { + const IpcCommandMessage& cm = static_cast<const IpcCommandMessage&>(message); + const String command = cm.command(); + ProtocolUtil::writef(&m_stream, kIpcMsgCommand, &command); + break; + } + + default: + LOG((CLOG_ERR "ipc message not supported: %d", message.type())); + break; + } +} + +IpcLogLineMessage* +IpcServerProxy::parseLogLine() +{ + String logLine; + ProtocolUtil::readf(&m_stream, kIpcMsgLogLine + 4, &logLine); + + // must be deleted by event handler. + return new IpcLogLineMessage(logLine); +} + +void +IpcServerProxy::disconnect() +{ + LOG((CLOG_DEBUG "ipc disconnect, closing stream")); + m_stream.close(); +} diff --git a/src/lib/ipc/IpcServerProxy.h b/src/lib/ipc/IpcServerProxy.h new file mode 100644 index 0000000..f2218a4 --- /dev/null +++ b/src/lib/ipc/IpcServerProxy.h @@ -0,0 +1,46 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "base/Event.h" +#include "base/EventTypes.h" + +namespace barrier { class IStream; } +class IpcMessage; +class IpcLogLineMessage; +class IEventQueue; + +class IpcServerProxy { + friend class IpcClient; + +public: + IpcServerProxy(barrier::IStream& stream, IEventQueue* events); + virtual ~IpcServerProxy(); + +private: + void send(const IpcMessage& message); + + void handleData(const Event&, void*); + IpcLogLineMessage* parseLogLine(); + void disconnect(); + +private: + barrier::IStream& m_stream; + IEventQueue* m_events; +}; |
