mirror of
https://github.com/debauchee/barrier.git
synced 2026-02-08 12:53:53 +08:00
- Fixed taking the address of begin() on an empty std::vector. - Fixed nsis makefile to use %ProgramFiles% environment variable. - Fixed nsis makefile to pass the output directory and file to makensis. - Fixed synergy.nsi to get the files from the output directory. That enables a debug build of the installer. - Fixes to compile under VS2005. I did not apply VS2005 project files, instead adding nmake files. nmake is pretty weak but the makefiles can be modified without having visual studio. Also modified the .rc files to not use winres.h. This plus nmake means synergy can now be built using the freely downloadable Microsoft Windows SDK for Vista, available from microsoft's web site. This change removes all of the old VC++6 project files in favor of the nmake files. It also removes the XCode project in favor of ./configure and make. All of the nmake files are named nmake.mak. Only the top level makefile is directly useful (the rest are included by it) so all builds are from the top level directory. nmake knows the following targets: all: build synergy.exe, synergyc.exe and synergys.exe clean: remove all intermediate files, keep programs clobber: clean and remove programs installer: build programs and an installer debug: build a debug version of 'all' release: build a release version of 'all' debug-installer: build an installer of the debug build release-installer: build an installer of the release build The default build version is release so 'all' and 'installer' will build a release version. The installer itself never has debug symbols, just the stuff it installs. The default target is 'all'. To build use: nmake /nologo /f nmake.mak <target> VC++ and VisualStudio users may need to manually run vcvars.bat in a command.exe or cmd.exe window before invoking nmake. The Window 98/Me command.exe may not handle potentially long command lines; I haven't tried to verify if that works.
543 lines
11 KiB
C++
543 lines
11 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* 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 COPYING 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.
|
|
*/
|
|
|
|
#include "CTCPSocket.h"
|
|
#include "CNetworkAddress.h"
|
|
#include "CSocketMultiplexer.h"
|
|
#include "TSocketMultiplexerMethodJob.h"
|
|
#include "XSocket.h"
|
|
#include "CLock.h"
|
|
#include "CLog.h"
|
|
#include "IEventQueue.h"
|
|
#include "IEventJob.h"
|
|
#include "CArch.h"
|
|
#include "XArch.h"
|
|
#include <string.h>
|
|
|
|
//
|
|
// CTCPSocket
|
|
//
|
|
|
|
CTCPSocket::CTCPSocket() :
|
|
m_mutex(),
|
|
m_flushed(&m_mutex, true)
|
|
{
|
|
try {
|
|
m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM);
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
throw XSocketCreate(e.what());
|
|
}
|
|
|
|
init();
|
|
}
|
|
|
|
CTCPSocket::CTCPSocket(CArchSocket socket) :
|
|
m_mutex(),
|
|
m_socket(socket),
|
|
m_flushed(&m_mutex, true)
|
|
{
|
|
assert(m_socket != NULL);
|
|
|
|
// socket starts in connected state
|
|
init();
|
|
onConnected();
|
|
setJob(newJob());
|
|
}
|
|
|
|
CTCPSocket::~CTCPSocket()
|
|
{
|
|
try {
|
|
close();
|
|
}
|
|
catch (...) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::bind(const CNetworkAddress& addr)
|
|
{
|
|
try {
|
|
ARCH->bindSocket(m_socket, addr.getAddress());
|
|
}
|
|
catch (XArchNetworkAddressInUse& e) {
|
|
throw XSocketAddressInUse(e.what());
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
throw XSocketBind(e.what());
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::close()
|
|
{
|
|
// remove ourself from the multiplexer
|
|
setJob(NULL);
|
|
|
|
CLock lock(&m_mutex);
|
|
|
|
// clear buffers and enter disconnected state
|
|
if (m_connected) {
|
|
sendEvent(getDisconnectedEvent());
|
|
}
|
|
onDisconnected();
|
|
|
|
// close the socket
|
|
if (m_socket != NULL) {
|
|
CArchSocket socket = m_socket;
|
|
m_socket = NULL;
|
|
try {
|
|
ARCH->closeSocket(socket);
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
// ignore, there's not much we can do
|
|
LOG((CLOG_WARN "error closing socket: %s", e.what().c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void*
|
|
CTCPSocket::getEventTarget() const
|
|
{
|
|
return const_cast<void*>(reinterpret_cast<const void*>(this));
|
|
}
|
|
|
|
UInt32
|
|
CTCPSocket::read(void* buffer, UInt32 n)
|
|
{
|
|
// copy data directly from our input buffer
|
|
CLock lock(&m_mutex);
|
|
UInt32 size = m_inputBuffer.getSize();
|
|
if (n > size) {
|
|
n = size;
|
|
}
|
|
if (buffer != NULL && n != 0) {
|
|
memcpy(buffer, m_inputBuffer.peek(n), n);
|
|
}
|
|
m_inputBuffer.pop(n);
|
|
|
|
// if no more data and we cannot read or write then send disconnected
|
|
if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) {
|
|
sendEvent(getDisconnectedEvent());
|
|
m_connected = false;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
void
|
|
CTCPSocket::write(const void* buffer, UInt32 n)
|
|
{
|
|
bool wasEmpty;
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// must not have shutdown output
|
|
if (!m_writable) {
|
|
sendEvent(getOutputErrorEvent());
|
|
return;
|
|
}
|
|
|
|
// ignore empty writes
|
|
if (n == 0) {
|
|
return;
|
|
}
|
|
|
|
// copy data to the output buffer
|
|
wasEmpty = (m_outputBuffer.getSize() == 0);
|
|
m_outputBuffer.write(buffer, n);
|
|
|
|
// there's data to write
|
|
m_flushed = false;
|
|
}
|
|
|
|
// make sure we're waiting to write
|
|
if (wasEmpty) {
|
|
setJob(newJob());
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::flush()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
while (m_flushed == false) {
|
|
m_flushed.wait();
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::shutdownInput()
|
|
{
|
|
bool useNewJob = false;
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// shutdown socket for reading
|
|
try {
|
|
ARCH->closeSocketForRead(m_socket);
|
|
}
|
|
catch (XArchNetwork&) {
|
|
// ignore
|
|
}
|
|
|
|
// shutdown buffer for reading
|
|
if (m_readable) {
|
|
sendEvent(getInputShutdownEvent());
|
|
onInputShutdown();
|
|
useNewJob = true;
|
|
}
|
|
}
|
|
if (useNewJob) {
|
|
setJob(newJob());
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::shutdownOutput()
|
|
{
|
|
bool useNewJob = false;
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// shutdown socket for writing
|
|
try {
|
|
ARCH->closeSocketForWrite(m_socket);
|
|
}
|
|
catch (XArchNetwork&) {
|
|
// ignore
|
|
}
|
|
|
|
// shutdown buffer for writing
|
|
if (m_writable) {
|
|
sendEvent(getOutputShutdownEvent());
|
|
onOutputShutdown();
|
|
useNewJob = true;
|
|
}
|
|
}
|
|
if (useNewJob) {
|
|
setJob(newJob());
|
|
}
|
|
}
|
|
|
|
bool
|
|
CTCPSocket::isReady() const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
return (m_inputBuffer.getSize() > 0);
|
|
}
|
|
|
|
UInt32
|
|
CTCPSocket::getSize() const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
return m_inputBuffer.getSize();
|
|
}
|
|
|
|
void
|
|
CTCPSocket::connect(const CNetworkAddress& addr)
|
|
{
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// fail on attempts to reconnect
|
|
if (m_socket == NULL || m_connected) {
|
|
sendConnectionFailedEvent("busy");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (ARCH->connectSocket(m_socket, addr.getAddress())) {
|
|
sendEvent(getConnectedEvent());
|
|
onConnected();
|
|
}
|
|
else {
|
|
// connection is in progress
|
|
m_writable = true;
|
|
}
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
throw XSocketConnect(e.what());
|
|
}
|
|
}
|
|
setJob(newJob());
|
|
}
|
|
|
|
void
|
|
CTCPSocket::init()
|
|
{
|
|
// default state
|
|
m_connected = false;
|
|
m_readable = false;
|
|
m_writable = false;
|
|
|
|
try {
|
|
// turn off Nagle algorithm. we send lots of very short messages
|
|
// that should be sent without (much) delay. for example, the
|
|
// mouse motion messages are much less useful if they're delayed.
|
|
ARCH->setNoDelayOnSocket(m_socket, true);
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
try {
|
|
ARCH->closeSocket(m_socket);
|
|
m_socket = NULL;
|
|
}
|
|
catch (XArchNetwork&) {
|
|
// ignore
|
|
}
|
|
throw XSocketCreate(e.what());
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::setJob(ISocketMultiplexerJob* job)
|
|
{
|
|
// multiplexer will delete the old job
|
|
if (job == NULL) {
|
|
CSocketMultiplexer::getInstance()->removeSocket(this);
|
|
}
|
|
else {
|
|
CSocketMultiplexer::getInstance()->addSocket(this, job);
|
|
}
|
|
}
|
|
|
|
ISocketMultiplexerJob*
|
|
CTCPSocket::newJob()
|
|
{
|
|
// note -- must have m_mutex locked on entry
|
|
|
|
if (m_socket == NULL) {
|
|
return NULL;
|
|
}
|
|
else if (!m_connected) {
|
|
assert(!m_readable);
|
|
if (!(m_readable || m_writable)) {
|
|
return NULL;
|
|
}
|
|
return new TSocketMultiplexerMethodJob<CTCPSocket>(
|
|
this, &CTCPSocket::serviceConnecting,
|
|
m_socket, m_readable, m_writable);
|
|
}
|
|
else {
|
|
if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) {
|
|
return NULL;
|
|
}
|
|
return new TSocketMultiplexerMethodJob<CTCPSocket>(
|
|
this, &CTCPSocket::serviceConnected,
|
|
m_socket, m_readable,
|
|
m_writable && (m_outputBuffer.getSize() > 0));
|
|
}
|
|
}
|
|
|
|
void
|
|
CTCPSocket::sendConnectionFailedEvent(const char* msg)
|
|
{
|
|
CConnectionFailedInfo* info = (CConnectionFailedInfo*)malloc(
|
|
sizeof(CConnectionFailedInfo) + strlen(msg));
|
|
strcpy(info->m_what, msg);
|
|
EVENTQUEUE->addEvent(CEvent(getConnectionFailedEvent(),
|
|
getEventTarget(), info));
|
|
}
|
|
|
|
void
|
|
CTCPSocket::sendEvent(CEvent::Type type)
|
|
{
|
|
EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), NULL));
|
|
}
|
|
|
|
void
|
|
CTCPSocket::onConnected()
|
|
{
|
|
m_connected = true;
|
|
m_readable = true;
|
|
m_writable = true;
|
|
}
|
|
|
|
void
|
|
CTCPSocket::onInputShutdown()
|
|
{
|
|
m_inputBuffer.pop(m_inputBuffer.getSize());
|
|
m_readable = false;
|
|
}
|
|
|
|
void
|
|
CTCPSocket::onOutputShutdown()
|
|
{
|
|
m_outputBuffer.pop(m_outputBuffer.getSize());
|
|
m_writable = false;
|
|
|
|
// we're now flushed
|
|
m_flushed = true;
|
|
m_flushed.broadcast();
|
|
}
|
|
|
|
void
|
|
CTCPSocket::onDisconnected()
|
|
{
|
|
// disconnected
|
|
onInputShutdown();
|
|
onOutputShutdown();
|
|
m_connected = false;
|
|
}
|
|
|
|
ISocketMultiplexerJob*
|
|
CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job,
|
|
bool, bool write, bool error)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// should only check for errors if error is true but checking a new
|
|
// socket (and a socket that's connecting should be new) for errors
|
|
// should be safe and Mac OS X appears to have a bug where a
|
|
// non-blocking stream socket that fails to connect immediately is
|
|
// reported by select as being writable (i.e. connected) even when
|
|
// the connection has failed. this is easily demonstrated on OS X
|
|
// 10.3.4 by starting a synergy client and telling to connect to
|
|
// another system that's not running a synergy server. it will
|
|
// claim to have connected then quickly disconnect (i guess because
|
|
// read returns 0 bytes). unfortunately, synergy attempts to
|
|
// reconnect immediately, the process repeats and we end up
|
|
// spinning the CPU. luckily, OS X does set SO_ERROR on the
|
|
// socket correctly when the connection has failed so checking for
|
|
// errors works. (curiously, sometimes OS X doesn't report
|
|
// connection refused. when that happens it at least doesn't
|
|
// report the socket as being writable so synergy is able to time
|
|
// out the attempt.)
|
|
if (error || true) {
|
|
try {
|
|
// connection may have failed or succeeded
|
|
ARCH->throwErrorOnSocket(m_socket);
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
sendConnectionFailedEvent(e.what().c_str());
|
|
onDisconnected();
|
|
return newJob();
|
|
}
|
|
}
|
|
|
|
if (write) {
|
|
sendEvent(getConnectedEvent());
|
|
onConnected();
|
|
return newJob();
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
ISocketMultiplexerJob*
|
|
CTCPSocket::serviceConnected(ISocketMultiplexerJob* job,
|
|
bool read, bool write, bool error)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
if (error) {
|
|
sendEvent(getDisconnectedEvent());
|
|
onDisconnected();
|
|
return newJob();
|
|
}
|
|
|
|
bool needNewJob = false;
|
|
|
|
if (write) {
|
|
try {
|
|
// write data
|
|
UInt32 n = m_outputBuffer.getSize();
|
|
const void* buffer = m_outputBuffer.peek(n);
|
|
n = (UInt32)ARCH->writeSocket(m_socket, buffer, n);
|
|
|
|
// discard written data
|
|
if (n > 0) {
|
|
m_outputBuffer.pop(n);
|
|
if (m_outputBuffer.getSize() == 0) {
|
|
sendEvent(getOutputFlushedEvent());
|
|
m_flushed = true;
|
|
m_flushed.broadcast();
|
|
needNewJob = true;
|
|
}
|
|
}
|
|
}
|
|
catch (XArchNetworkShutdown&) {
|
|
// remote read end of stream hungup. our output side
|
|
// has therefore shutdown.
|
|
onOutputShutdown();
|
|
sendEvent(getOutputShutdownEvent());
|
|
if (!m_readable && m_inputBuffer.getSize() == 0) {
|
|
sendEvent(getDisconnectedEvent());
|
|
m_connected = false;
|
|
}
|
|
needNewJob = true;
|
|
}
|
|
catch (XArchNetworkDisconnected&) {
|
|
// stream hungup
|
|
onDisconnected();
|
|
sendEvent(getDisconnectedEvent());
|
|
needNewJob = true;
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
// other write error
|
|
LOG((CLOG_WARN "error writing socket: %s", e.what().c_str()));
|
|
onDisconnected();
|
|
sendEvent(getOutputErrorEvent());
|
|
sendEvent(getDisconnectedEvent());
|
|
needNewJob = true;
|
|
}
|
|
}
|
|
|
|
if (read && m_readable) {
|
|
try {
|
|
UInt8 buffer[4096];
|
|
size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
|
|
if (n > 0) {
|
|
bool wasEmpty = (m_inputBuffer.getSize() == 0);
|
|
|
|
// slurp up as much as possible
|
|
do {
|
|
m_inputBuffer.write(buffer, n);
|
|
n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
|
|
} while (n > 0);
|
|
|
|
// send input ready if input buffer was empty
|
|
if (wasEmpty) {
|
|
sendEvent(getInputReadyEvent());
|
|
}
|
|
}
|
|
else {
|
|
// remote write end of stream hungup. our input side
|
|
// has therefore shutdown but don't flush our buffer
|
|
// since there's still data to be read.
|
|
sendEvent(getInputShutdownEvent());
|
|
if (!m_writable && m_inputBuffer.getSize() == 0) {
|
|
sendEvent(getDisconnectedEvent());
|
|
m_connected = false;
|
|
}
|
|
m_readable = false;
|
|
needNewJob = true;
|
|
}
|
|
}
|
|
catch (XArchNetworkDisconnected&) {
|
|
// stream hungup
|
|
sendEvent(getDisconnectedEvent());
|
|
onDisconnected();
|
|
needNewJob = true;
|
|
}
|
|
catch (XArchNetwork& e) {
|
|
// ignore other read error
|
|
LOG((CLOG_WARN "error reading socket: %s", e.what().c_str()));
|
|
}
|
|
}
|
|
|
|
return needNewJob ? newJob() : job;
|
|
}
|