Add ability to initiate connection from server (#7222)

* Add a new hidden readonly option "initiateConnectionFromServer"

* Add option "clientHostMode"

* Add host mode to the client GUI

* Hide main window controls for the client host mode

* Store client address into the server config file

* Add client mode the server

* Pass option --host into the client

* Add --host parameter into the synergyc

* Add InverseSocketFactory

* Add InverseClientSocket

* Add InverseServerSocket

* Add AutoArchSocket

* Add listener on the client side

* Add connection from the server side

* Fix code smells

* Additional code smells

* Requested changes

Co-authored-by: Serhii Hadzhilov <serhii@symless.com>
This commit is contained in:
Serhii Hadzhilov 2022-09-22 11:12:13 +03:00 committed by GitHub
parent 452c631d6e
commit c37b0d2fcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1468 additions and 116 deletions

View file

@ -1,3 +1,9 @@
1.14.6
======
Enhancements:
- #7222 Ability to initiate connection from server
1.14.5
======

View file

@ -6,9 +6,9 @@ cmake_minimum_required (VERSION 3.4)
set (SYNERGY_VERSION_MAJOR 1)
set (SYNERGY_VERSION_MINOR 14)
set (SYNERGY_VERSION_PATCH 5)
set (SYNERGY_VERSION_PATCH 6)
set (SYNERGY_VERSION_BUILD 1)
set (SYNERGY_VERSION_STAGE "stable")
set (SYNERGY_VERSION_STAGE "snapshot")
#
# Version from CI

View file

@ -83,7 +83,10 @@ const char* AppConfig::m_SynergySettingsName[] = {
"eliteBackersUrl",
"guid",
"licenseRegistryUrl",
"licenseNextCheck"
"licenseNextCheck",
"initiateConnectionFromServer",
"clientHostMode",
"serverClientMode"
};
static const char* logLevelNames[] =
@ -99,19 +102,19 @@ AppConfig::AppConfig() :
m_Port(24800),
m_Interface(),
m_LogLevel(0),
m_LogToFile(),
m_WizardLastRun(0),
m_ProcessMode(DEFAULT_PROCESS_MODE),
m_StartedBefore(),
m_AutoConfig(true),
m_AutoConfigServer(),
m_ElevateMode(defaultElevateMode),
m_Edition(kUnregistered),
m_CryptoEnabled(false),
m_AutoHide(false),
m_LastExpiringWarningTime(0),
m_AutoConfigServer(),
m_MinimizeToTray(false),
m_Edition(kUnregistered),
m_LogToFile(),
m_StartedBefore(),
m_ActivationHasRun(),
m_MinimizeToTray(false),
m_ServerGroupChecked(),
m_UseExternalConfig(),
m_UseInternalConfig(),
@ -253,6 +256,9 @@ void AppConfig::loadSettings()
m_guid = loadCommonSetting(kGuid, QUuid::createUuid()).toString();
m_licenseRegistryUrl = loadCommonSetting(kLicenseRegistryUrl, "https://api2.prod.symless.com/license/register").toString();
m_licenseNextCheck = loadCommonSetting(kLicenseNextCheck, 0).toULongLong();
m_ClientHostMode = loadSetting(kClientHostMode, true).toBool();
m_ServerClientMode = loadSetting(kServerClientMode, true).toBool();
m_InitiateConnectionFromServer = loadSetting(kInitiateConnectionFromServer, false).toBool();
//only change the serial key if the settings being loaded contains a key
bool updateSerial = ConfigWriter::make()
@ -320,6 +326,8 @@ void AppConfig::saveSettings()
setSetting(kPreventSleep, m_PreventSleep);
setSetting(kLanguageSync, m_LanguageSync);
setSetting(kInvertScrollDirection, m_InvertScrollDirection);
setSetting(kClientHostMode, m_ClientHostMode);
setSetting(kServerClientMode, m_ServerClientMode);
}
m_unsavedChanges = false;
@ -512,6 +520,18 @@ void AppConfig::setLanguageSync(bool newValue) {
bool AppConfig::getPreventSleep() const { return m_PreventSleep; }
bool AppConfig::getClientHostMode() const {
return (m_ClientHostMode && getInitiateConnectionFromServer());
}
bool AppConfig::getServerClientMode() const {
return (m_ServerClientMode && getInitiateConnectionFromServer());
}
bool AppConfig::getInitiateConnectionFromServer() const {
return m_InitiateConnectionFromServer;
}
void AppConfig::setPreventSleep(bool newValue) {
setSettingModified(m_PreventSleep, newValue);
}
@ -641,6 +661,14 @@ void AppConfig::setServerHostname(const QString& newValue) {
setSettingModified(m_ServerHostname, newValue);
}
void AppConfig::setClientHostMode(bool newValue) {
setSettingModified(m_ClientHostMode, newValue);
}
void AppConfig::setServerClientMode(bool newValue) {
setSettingModified(m_ServerClientMode, newValue);
}
template<typename T>
void AppConfig::setSettingModified(T &variable, const T& newValue) {
if (variable != newValue)

View file

@ -125,6 +125,9 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase
bool getLanguageSync() const;
void setPreventSleep(bool b);
bool getPreventSleep() const;
bool getClientHostMode() const;
bool getServerClientMode() const;
bool getInitiateConnectionFromServer() const;
#ifndef SYNERGY_ENTERPRISE
bool activationHasRun() const;
AppConfig& activationHasRun(bool value);
@ -157,6 +160,8 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase
void setUseInternalConfig(bool) ;
void setClientGroupChecked(bool) ;
void setServerHostname(const QString&);
void setClientHostMode(bool newValue);
void setServerClientMode(bool newValue);
/// @brief Set the path to the TLS/SSL certificate file that will be used
/// @param [in] path The path to the Certificate
@ -219,7 +224,10 @@ protected:
kEliteBackersUrl,
kGuid,
kLicenseRegistryUrl,
kLicenseNextCheck
kLicenseNextCheck,
kInitiateConnectionFromServer,
kClientHostMode,
kServerClientMode
};
void setScreenName(const QString& s);
@ -269,6 +277,9 @@ protected:
bool m_InvertScrollDirection = false;
bool m_LanguageSync = true;
bool m_PreventSleep = false;
bool m_InitiateConnectionFromServer = false;
bool m_ClientHostMode = true;
bool m_ServerClientMode = true;
bool m_ServerGroupChecked;
bool m_UseExternalConfig;

View file

@ -307,6 +307,7 @@ void MainWindow::loadSettings()
enableClient(appConfig().getClientGroupChecked());
m_pLineEditHostname->setText(appConfig().getServerHostname());
m_pLineEditClienIp->setText(serverConfig().getClientAddress());
}
void MainWindow::initConnections()
@ -325,6 +326,7 @@ void MainWindow::saveSettings()
appConfig().setServerGroupChecked(m_pRadioGroupServer->isChecked());
appConfig().setClientGroupChecked(m_pRadioGroupClient->isChecked());
appConfig().setServerHostname(m_pLineEditHostname->text());
serverConfig().setClientAddress(m_pLineEditClienIp->text());
/* Save everything */
GUI::Config::ConfigWriter::make()->globalSave();
@ -836,7 +838,7 @@ bool MainWindow::clientArgs(QStringList& args, QString& app)
}
#endif
if (m_pLineEditHostname->text().isEmpty())
if (m_pLineEditHostname->text().isEmpty() && !appConfig().getClientHostMode())
{
#if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
//check if autoconfig mode is enabled
@ -858,6 +860,11 @@ bool MainWindow::clientArgs(QStringList& args, QString& app)
#endif
}
if (appConfig().getClientHostMode()) {
args <<"--host";
args <<":" + QString::number(appConfig().port());
}
else {
QString hostName = m_pLineEditHostname->text();
// if interface is IPv6 - ensure that ip is in square brackets
if (hostName.count(':') > 1) {
@ -868,8 +875,9 @@ bool MainWindow::clientArgs(QStringList& args, QString& app)
hostName.push_back(']');
}
}
args << hostName + ":" + QString::number(appConfig().port());
}
return true;
}
@ -947,6 +955,14 @@ bool MainWindow::serverArgs(QStringList& args, QString& app)
return false;
}
if (appConfig().getServerClientMode() &&
m_pLineEditClienIp->text().isEmpty())
{
QMessageBox::warning(this, tr("Client IP address or name is empty"),
tr("Please fill in a client IP address or name."));
return false;
}
#if defined(Q_OS_WIN)
// wrap in quotes so a malicious user can't start \Program.exe as admin.
app = QString("\"%1\"").arg(app);
@ -1330,6 +1346,8 @@ void MainWindow::on_m_pActionSettings_triggered()
auto result = SettingsDialog(this, appConfig()).exec();
if(result == QDialog::Accepted)
{
enableServer(appConfig().getServerGroupChecked());
enableClient(appConfig().getClientGroupChecked());
auto state = synergyState();
if ((state == synergyConnected) || (state == synergyConnecting) || (state == synergyListening)) {
restartSynergy();
@ -1497,6 +1515,17 @@ void MainWindow::enableServer(bool enable)
if (enable)
{
if (m_AppConfig->getServerClientMode()) {
m_pLabelClientIp->show();
m_pLineEditClienIp->show();
m_pButtonConnectToClient->show();
}
else {
m_pLabelClientIp->hide();
m_pLineEditClienIp->hide();
m_pButtonConnectToClient->hide();
}
m_pButtonConfigureServer->show();
m_pLabelServerState->show();
updateLocalFingerprint();
@ -1507,6 +1536,9 @@ void MainWindow::enableServer(bool enable)
m_pLabelFingerprint->hide();
m_pButtonConfigureServer->hide();
m_pLabelServerState->hide();
m_pLabelClientIp->hide();
m_pLineEditClienIp->hide();
m_pButtonConnectToClient->hide();
}
}
@ -1516,10 +1548,19 @@ void MainWindow::enableClient(bool enable)
m_pRadioGroupClient->setChecked(enable);
if (enable)
{
if (m_AppConfig->getClientHostMode())
{
m_pLabelServerName->hide();
m_pLineEditHostname->hide();
m_pButtonConnect->hide();
}
else
{
m_pLabelServerName->show();
m_pLineEditHostname->show();
m_pButtonConnect->show();
}
m_pButtonToggleStart->setEnabled(enable);
}
else
@ -1558,3 +1599,8 @@ void MainWindow::on_m_pButtonConnect_clicked()
on_m_pButtonApply_clicked();
}
void MainWindow::on_m_pButtonConnectToClient_clicked()
{
on_m_pButtonApply_clicked();
}

View file

@ -263,6 +263,7 @@ private slots:
void on_m_pComboServerList_currentIndexChanged(const QString &arg1);
void on_m_pButtonConnect_clicked();
void on_m_pButtonConnectToClient_clicked();
signals:
void windowShown();

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>720</width>
<height>658</height>
<height>552</height>
</rect>
</property>
<property name="sizePolicy">
@ -244,11 +244,41 @@ background-color: rgba(192,192,192, 0.1);
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="m_pLabelClientIp">
<property name="text">
<string>Client IP address or name:</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="m_pLineEditClienIp"/>
</item>
<item>
<widget class="QPushButton" name="m_pButtonConnectToClient">
<property name="text">
<string>Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="synergy_widgets::ServerStateLabel" name="m_pLabelServerState">
<property name="text">
@ -685,7 +715,6 @@ background-color: rgba(192,192,192, 0.1);
<font>
<family>Arial</family>
<pointsize>-1</pointsize>
<weight>87</weight>
<bold>true</bold>
</font>
</property>

View file

@ -150,6 +150,10 @@ void ServerConfig::saveSettings()
settings().setValue("clipboardSharing", clipboardSharing());
settings().setValue("clipboardSharingSize", QVariant::fromValue(clipboardSharingSize()));
if (!getClientAddress().isEmpty()) {
settings().setValue("clientAddress", getClientAddress());
}
writeSettings(settings(), switchCorners(), "switchCorner");
settings().beginWriteArray("screens");
@ -204,6 +208,7 @@ void ServerConfig::loadSettings()
setClipboardSharingSize(settings().value("clipboardSharingSize",
(int) ServerConfig::defaultClipboardSharingSize()).toULongLong());
setClipboardSharing(settings().value("clipboardSharing", true).toBool());
setClientAddress(settings().value("clientAddress", "").toString());
readSettings(settings(), switchCorners(), "switchCorner", false, NumSwitchCorners);
@ -296,6 +301,10 @@ QTextStream& operator<<(QTextStream& outStream, const ServerConfig& config)
outStream << "\t" << "clipboardSharing = " << (config.clipboardSharing() ? "true" : "false") << endl;
outStream << "\t" << "clipboardSharingSize = " << config.clipboardSharingSize() << endl;
if (!config.getClientAddress().isEmpty()) {
outStream << "\t" << "clientAddress = " << config.getClientAddress() << endl;
}
if (config.hasSwitchDelay())
outStream << "\t" << "switchDelay = " << config.switchDelay() << endl;
@ -554,6 +563,22 @@ size_t ServerConfig::setClipboardSharingSize(size_t size) {
return size;
}
void ServerConfig::setClientAddress(const QString& address) {
if (m_pAppConfig->getServerClientMode()) {
m_clientAddress = address;
}
}
QString ServerConfig::getClientAddress() const {
QString clientAddress;
if (m_pAppConfig->getServerClientMode()) {
clientAddress = m_clientAddress.trimmed();
}
return clientAddress;
}
QSettings &ServerConfig::settings() {
using GUI::Config::ConfigWriter;

View file

@ -89,6 +89,8 @@ class ServerConfig : public BaseConfig, public GUI::Config::ConfigBase
bool isScreenExists(const QString& screenName) const;
void addClient(const QString& clientName);
bool isHotkeysAvailable() const;
QString getClientAddress() const;
void setClientAddress(const QString& address);
protected:
QSettings& settings();
@ -147,6 +149,7 @@ class ServerConfig : public BaseConfig, public GUI::Config::ConfigBase
bool m_DisableLockToScreen;
bool m_ClipboardSharing;
size_t m_ClipboardSharingSize;
QString m_clientAddress;
MainWindow* m_pMainWindow;
};

View file

@ -53,8 +53,11 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) :
buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
enableControls(appConfig().isWritable());
m_pCheckBoxLanguageSync->setVisible(m_pMainWindow->synergyType() == MainWindow::synergyClient);
m_pCheckBoxScrollDirection->setVisible(m_pMainWindow->synergyType() == MainWindow::synergyClient);
const auto isClientMode = m_pMainWindow->synergyType() == MainWindow::synergyClient;
m_pCheckBoxLanguageSync->setVisible(isClientMode);
m_pCheckBoxScrollDirection->setVisible(isClientMode);
m_pCheckBoxClientHostMode->setVisible(isClientMode && appConfig().getInitiateConnectionFromServer());
m_pCheckBoxServerClientMode->setVisible(!isClientMode && appConfig().getInitiateConnectionFromServer());
const auto& serveConfig = m_pMainWindow->serverConfig();
m_pLineEditScreenName->setValidator(new validators::ScreenNameValidator(m_pLineEditScreenName, m_pLabelNameError, (&serveConfig.screens())));
@ -72,6 +75,8 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) :
connect(m_pComboElevate, SIGNAL(currentIndexChanged(int)), this, SLOT(onChange()));
connect(m_pCheckBoxLanguageSync, SIGNAL(clicked()), this, SLOT(onChange()));
connect(m_pCheckBoxScrollDirection, SIGNAL(clicked()), this, SLOT(onChange()));
connect(m_pCheckBoxClientHostMode, SIGNAL(clicked()), this, SLOT(onChange()));
connect(m_pCheckBoxServerClientMode,SIGNAL(clicked()), this, SLOT(onChange()));
adjustSize();
}
@ -96,6 +101,8 @@ void SettingsDialog::accept()
appConfig().setCryptoEnabled(m_pCheckBoxEnableCrypto->isChecked());
appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked());
appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked());
appConfig().setClientHostMode(m_pCheckBoxClientHostMode->isChecked());
appConfig().setServerClientMode(m_pCheckBoxServerClientMode->isChecked());
appConfig().saveSettings();
QDialog::accept();
@ -155,6 +162,8 @@ void SettingsDialog::loadFromConfig() {
m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled());
m_pCheckBoxLanguageSync->setChecked(m_appConfig.getLanguageSync());
m_pCheckBoxScrollDirection->setChecked(m_appConfig.getInvertScrollDirection());
m_pCheckBoxClientHostMode->setChecked(m_appConfig.getClientHostMode());
m_pCheckBoxServerClientMode->setChecked(m_appConfig.getServerClientMode());
setupSeurity();
@ -367,6 +376,8 @@ bool SettingsDialog::isModified()
|| appConfig().getCryptoEnabled() != m_pCheckBoxEnableCrypto->isChecked()
|| appConfig().isSystemScoped() != m_isSystemAtStart
|| appConfig().getLanguageSync() != m_pCheckBoxLanguageSync->isChecked()
|| appConfig().getClientHostMode() != m_pCheckBoxClientHostMode->isChecked()
|| appConfig().getServerClientMode() != m_pCheckBoxServerClientMode->isChecked()
|| appConfig().getInvertScrollDirection() != m_pCheckBoxScrollDirection->isChecked())
);
}
@ -390,6 +401,8 @@ void SettingsDialog::enableControls(bool enable) {
m_labelAdminRightsMessage->setVisible(!enable);
m_pCheckBoxLanguageSync->setEnabled(enable);
m_pCheckBoxScrollDirection->setEnabled(enable);
m_pCheckBoxClientHostMode->setEnabled(enable);
m_pCheckBoxServerClientMode->setEnabled(enable);
if (enable) {
m_pLabelLogPath->setEnabled(m_pCheckBoxLogToFile->isChecked());

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>404</width>
<height>620</height>
<height>819</height>
</rect>
</property>
<property name="sizePolicy">
@ -337,62 +337,7 @@ background-color: rgba(192,192,192, 0.1);
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="4" column="0">
<widget class="QLabel" name="m_pLabelElevate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Elevate privileges</string>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QCheckBox" name="m_pCheckBoxLanguageSync">
<property name="text">
<string>&amp;Use server's keyboard language on this machine</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="m_pLabel_27">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>User interface language</string>
</property>
<property name="buddy">
<cstring>m_pComboLanguage</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QComboBox" name="m_pComboElevate">
<property name="toolTip">
<string>Specify when the Synergy service should run at an elevated privilege level</string>
@ -417,16 +362,85 @@ background-color: rgba(192,192,192, 0.1);
</item>
</widget>
</item>
<item row="0" column="1">
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="m_pCheckBoxLanguageSync">
<property name="text">
<string>&amp;Use server's keyboard language on this machine</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="m_pCheckBoxClientHostMode">
<property name="text">
<string>Host mode</string>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QLabel" name="m_pLabelElevate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Elevate privileges</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="m_pComboLanguage"/>
</item>
<item row="1" column="0" colspan="3">
<item row="2" column="0" colspan="3">
<widget class="QCheckBox" name="m_pCheckBoxScrollDirection">
<property name="text">
<string>Invert scroll direction on this computer</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="m_pLabel_27">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>User interface language</string>
</property>
<property name="buddy">
<cstring>m_pComboLanguage</cstring>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="m_pCheckBoxServerClientMode">
<property name="text">
<string>Client mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -695,7 +709,6 @@ background-color: rgba(192,192,192, 0.1);
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>

View file

@ -74,7 +74,6 @@ Client::Client(
m_events(events),
m_sendFileThread(nullptr),
m_writeToDropDirThread(nullptr),
m_socket(NULL),
m_useSecureNetwork(args.m_enableCrypto),
m_args(args),
m_enableClipboard(true),
@ -135,6 +134,11 @@ Client::connect(size_t addressIndex)
}
try {
if (m_args.m_hostMode)
{
LOG((CLOG_NOTE "waiting for server conection on %i port", m_serverAddress.getPort()));
}
else {
// resolve the server hostname. do this every time we connect
// in case we couldn't resolve the address earlier or the address
// has changed (which can happen frequently if this is a laptop
@ -150,14 +154,12 @@ Client::connect(size_t addressIndex)
ARCH->addrToString(m_serverAddress.getAddress()).c_str(),
m_serverAddress.getPort()));
}
}
// create the socket
IDataSocket* socket = m_socketFactory->create(m_useSecureNetwork, ARCH->getAddrFamily(m_serverAddress.getAddress()));
m_socket = dynamic_cast<TCPSocket*>(socket);
// filter socket messages, including a packetizing filter
m_stream = socket;
m_stream = new PacketStreamFilter(m_events, m_stream, true);
m_stream = new PacketStreamFilter(m_events, socket, true);
// connect
LOG((CLOG_DEBUG1 "connecting to server"));
@ -546,10 +548,12 @@ Client::setupTimer()
{
assert(m_timer == NULL);
if (!m_args.m_hostMode) {
m_timer = m_events->newOneShotTimer(2.0, NULL);
m_events->adoptHandler(Event::kTimer, m_timer,
new TMethodEventJob<Client>(this,
&Client::handleConnectTimeout));
}
}
void

View file

@ -238,7 +238,6 @@ private:
using AutoThread = std::unique_ptr<Thread>;
AutoThread m_sendFileThread;
AutoThread m_writeToDropDirThread;
TCPSocket* m_socket;
bool m_useSecureNetwork;
bool m_enableClipboard;
size_t m_maximumClipboardSize;

View file

@ -14,8 +14,14 @@
# 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")
file(GLOB headers
"*.h"
"InverseSockets/*.h"
)
file(GLOB sources
"*.cpp"
"InverseSockets/*.cpp"
)
if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})

View file

@ -0,0 +1,201 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "AutoArchSocket.h"
#include "base/Log.h"
#include "arch/Arch.h"
#include "arch/XArch.h"
#include "net/XSocket.h"
AutoArchSocket::AutoArchSocket(IArchNetwork::EAddressFamily family)
{
try {
m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM);
LOG((CLOG_DEBUG "opening new socket: %08X", m_socket));
}
catch (const XArchNetwork& e) {
throw XSocketCreate(e.what());
}
}
AutoArchSocket::~AutoArchSocket()
{
closeSocket();
}
void AutoArchSocket::setNoDelayOnSocket(bool value)
{
if (isValid()) {
ARCH->setNoDelayOnSocket(m_socket, value);
}
}
void AutoArchSocket::setReuseAddrOnSocket(bool value)
{
if (isValid()) {
ARCH->setReuseAddrOnSocket(m_socket, value);
}
}
void AutoArchSocket::closeSocket()
{
if (isValid()) {
try {
LOG((CLOG_DEBUG "closing socket: %08X", m_socket));
ARCH->closeSocket(m_socket);
m_socket = nullptr;
}
catch (const XArchNetwork& e) {
// ignore, there's not much we can do
LOG((CLOG_WARN "error closing socket: %s", e.what()));
}
}
else {
LOG((CLOG_WARN "error closing socket because the socket already closed"));
}
}
void AutoArchSocket::bindSocket(const NetworkAddress &addr)
{
if (isValid()) {
try {
ARCH->bindSocket(m_socket, addr.getAddress());
}
catch (const XArchNetworkAddressInUse& e) {
throw XSocketAddressInUse(e.what());
}
catch (const XArchNetwork& e) {
throw XSocketBind(e.what());
}
}
}
void AutoArchSocket::bindAndListen(const NetworkAddress &addr)
{
try {
setReuseAddrOnSocket();
bindSocket(addr);
listenOnSocket();
}
catch (const XArchNetworkAddressInUse& e) {
throw XSocketAddressInUse(e.what());
}
catch (const XArchNetwork& e) {
throw XSocketBind(e.what());
}
}
void AutoArchSocket::listenOnSocket()
{
if (isValid()) {
ARCH->listenOnSocket(m_socket);
}
}
ArchSocket AutoArchSocket::acceptSocket()
{
return ARCH->acceptSocket(m_socket, nullptr);
}
void AutoArchSocket::closeSocketForRead()
{
if (isValid()) {
try {
ARCH->closeSocketForRead(m_socket);
}
catch (const XArchNetwork& e) {
// ignore, there's not much we can do
LOG((CLOG_WARN "error closing socket: %s", e.what()));
}
}
}
void AutoArchSocket::closeSocketForWrite()
{
if (isValid()) {
try {
ARCH->closeSocketForWrite(m_socket);
}
catch (const XArchNetwork& e) {
// ignore, there's not much we can do
LOG((CLOG_WARN "error closing socket: %s", e.what()));
}
}
}
bool AutoArchSocket::connectSocket(const NetworkAddress &addr)
{
bool result = false;
if (isValid()) {
// 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.
setNoDelayOnSocket();
result = ARCH->connectSocket(m_socket, addr.getAddress());
}
return result;
}
size_t AutoArchSocket::readSocket(UInt8* buffer, size_t size)
{
size_t result = 0;
if (isValid()) {
result = ARCH->readSocket(m_socket, buffer, size);
}
return result;
}
size_t AutoArchSocket::writeSocket(const UInt8* buffer, size_t size)
{
size_t result = 0;
if (isValid()) {
result = ARCH->writeSocket(m_socket, buffer, size);
}
return result;
}
void AutoArchSocket::throwErrorOnSocket()
{
if (isValid()) {
ARCH->throwErrorOnSocket(m_socket);
}
}
ArchSocket AutoArchSocket::getRawSocket() const
{
return m_socket;
}
bool AutoArchSocket::isValid() const
{
return (m_socket != nullptr);
}
void AutoArchSocket::operator =(ArchSocket socket)
{
if (isValid()) {
closeSocket();
}
m_socket = socket;
}

View file

@ -0,0 +1,53 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "net/NetworkAddress.h"
class AutoArchSocket
{
public:
explicit AutoArchSocket(IArchNetwork::EAddressFamily family);
~AutoArchSocket();
AutoArchSocket(const AutoArchSocket&) = delete;
AutoArchSocket& operator =(const AutoArchSocket&) = delete;
void setNoDelayOnSocket(bool value = true);
void setReuseAddrOnSocket(bool value = true);
void listenOnSocket();
ArchSocket acceptSocket();
void bindSocket(const NetworkAddress& addr);
bool connectSocket(const NetworkAddress& addr);
void bindAndListen(const NetworkAddress& addr);
void closeSocket();
void closeSocketForRead();
void closeSocketForWrite();
size_t readSocket(UInt8* buffer, size_t size);
size_t writeSocket(const UInt8* buffer, size_t size);
void throwErrorOnSocket();
bool isValid() const;
ArchSocket getRawSocket() const;
void operator =(ArchSocket socket);
private:
ArchSocket m_socket = nullptr;
};

View file

@ -0,0 +1,452 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "InverseClientSocket.h"
#include "net/NetworkAddress.h"
#include "net/SocketMultiplexer.h"
#include "net/TSocketMultiplexerMethodJob.h"
#include "net/XSocket.h"
#include "mt/Lock.h"
#include "arch/Arch.h"
#include "arch/XArch.h"
#include "base/Log.h"
#include "base/IEventQueue.h"
#include "base/IEventJob.h"
#include <cstring>
#include <cstdlib>
#include <memory>
//
// InverseClientSocket
//
InverseClientSocket::InverseClientSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
IDataSocket(events),
m_events(events),
m_socket(family),
m_listener(family),
m_flushed(&m_mutex, true),
m_socketMultiplexer(socketMultiplexer)
{
}
InverseClientSocket::~InverseClientSocket()
{
try {
// warning virtual function in destructor is very danger practice
InverseClientSocket::close();
}
catch (...) {
LOG((CLOG_DEBUG "error while TCP socket destruction"));
}
}
void
InverseClientSocket::bind(const NetworkAddress& addr)
{
m_socket.bindSocket(addr);
}
void
InverseClientSocket::close()
{
setJob(nullptr);
Lock lock(&m_mutex);
onDisconnected();
}
void*
InverseClientSocket::getEventTarget() const
{
return const_cast<void*>(static_cast<const void*>(this));
}
UInt32
InverseClientSocket::read(void* buffer, UInt32 n)
{
// copy data directly from our input buffer
Lock lock(&m_mutex);
UInt32 size = m_inputBuffer.getSize();
if (n > size) {
n = size;
}
if (buffer != nullptr && 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(m_events->forISocket().disconnected());
m_connected = false;
}
return n;
}
void
InverseClientSocket::write(const void* buffer, UInt32 n)
{
bool wasEmpty;
{
Lock lock(&m_mutex);
// must not have shutdown output
if (!m_writable) {
sendEvent(m_events->forIStream().outputError());
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(m_socket.getRawSocket()));
}
}
void
InverseClientSocket::flush()
{
Lock lock(&m_mutex);
while (m_flushed == false) {
m_flushed.wait();
}
}
void
InverseClientSocket::shutdownInput()
{
bool useNewJob = false;
{
Lock lock(&m_mutex);
// shutdown socket for reading
m_socket.closeSocketForRead();
// shutdown buffer for reading
if (m_readable) {
sendEvent(m_events->forIStream().inputShutdown());
onInputShutdown();
useNewJob = true;
}
}
if (useNewJob) {
setJob(newJob(m_socket.getRawSocket()));
}
}
void
InverseClientSocket::shutdownOutput()
{
bool useNewJob = false;
{
Lock lock(&m_mutex);
// shutdown socket for writing
m_socket.closeSocketForWrite();
// shutdown buffer for writing
if (m_writable) {
sendEvent(m_events->forIStream().outputShutdown());
onOutputShutdown();
useNewJob = true;
}
}
if (useNewJob) {
setJob(newJob(m_socket.getRawSocket()));
}
}
bool
InverseClientSocket::isReady() const
{
Lock lock(&m_mutex);
return (m_inputBuffer.getSize() > 0);
}
bool
InverseClientSocket::isFatal() const
{
// TCP sockets aren't ever left in a fatal state.
LOG((CLOG_ERR "isFatal() not valid for non-secure connections"));
return false;
}
UInt32
InverseClientSocket::getSize() const
{
Lock lock(&m_mutex);
return m_inputBuffer.getSize();
}
void
InverseClientSocket::connect(const NetworkAddress& addr)
{
{
Lock lock(&m_mutex);
m_listener.bindAndListen(addr);
m_writable = true;
m_readable = true;
}
setJob(newJob(m_listener.getRawSocket()));
}
InverseClientSocket::EJobResult
InverseClientSocket::doRead()
{
UInt8 buffer[4096] = {0};
size_t bytesRead = m_socket.readSocket(buffer, sizeof(buffer));
if (bytesRead > 0) {
bool wasEmpty = (m_inputBuffer.getSize() == 0);
// slurp up as much as possible
do {
m_inputBuffer.write(buffer, static_cast<UInt32>(bytesRead));
bytesRead = m_socket.readSocket(buffer, sizeof(buffer));
} while (bytesRead > 0);
// send input ready if input buffer was empty
if (wasEmpty) {
sendEvent(m_events->forIStream().inputReady());
}
}
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(m_events->forIStream().inputShutdown());
if (!m_writable && m_inputBuffer.getSize() == 0) {
sendEvent(m_events->forISocket().disconnected());
m_connected = false;
}
m_readable = false;
return InverseClientSocket::EJobResult::kNew;
}
return InverseClientSocket::EJobResult::kRetry;
}
InverseClientSocket::EJobResult
InverseClientSocket::doWrite()
{
UInt32 bufferSize = m_outputBuffer.getSize();
auto buffer = static_cast<const UInt8*>(m_outputBuffer.peek(bufferSize));
const auto bytesWrote = static_cast<UInt32>(m_socket.writeSocket(buffer, bufferSize));
if (bytesWrote > 0) {
discardWrittenData(bytesWrote);
return InverseClientSocket::EJobResult::kNew;
}
return InverseClientSocket::EJobResult::kRetry;
}
void
InverseClientSocket::setJob(ISocketMultiplexerJob* job)
{
// multiplexer will delete the old job
if (job == nullptr) {
m_socketMultiplexer->removeSocket(this);
}
else {
m_socketMultiplexer->addSocket(this, job);
}
}
ISocketMultiplexerJob*
InverseClientSocket::newJob(ArchSocket socket)
{
// note -- must have m_mutex locked on entry
ISocketMultiplexerJob* result = nullptr;
if (socket) {
auto isWritable = m_writable;
auto handler = &InverseClientSocket::serviceConnecting;
if (m_connected) {
handler = &InverseClientSocket::serviceConnected;
isWritable = (isWritable && (m_outputBuffer.getSize() > 0));
}
if (m_readable || isWritable) {
result = new TSocketMultiplexerMethodJob<InverseClientSocket>(this, handler, socket, m_readable, isWritable);
}
}
return result;
}
void
InverseClientSocket::sendConnectionFailedEvent(const char* msg)
{
auto info = new ConnectionFailedInfo(msg);
m_events->addEvent(Event(m_events->forIDataSocket().connectionFailed(),
getEventTarget(), info, Event::kDontFreeData));
}
void
InverseClientSocket::sendEvent(Event::Type type)
{
m_events->addEvent(Event(type, getEventTarget()));
}
void
InverseClientSocket::discardWrittenData(int bytesWrote)
{
m_outputBuffer.pop(bytesWrote);
if (m_outputBuffer.getSize() == 0) {
sendEvent(m_events->forIStream().outputFlushed());
m_flushed = true;
m_flushed.broadcast();
}
}
void
InverseClientSocket::onConnected()
{
sendEvent(m_events->forIDataSocket().connected());
m_connected = true;
m_readable = true;
m_writable = true;
}
void
InverseClientSocket::onInputShutdown()
{
m_inputBuffer.pop(m_inputBuffer.getSize());
m_readable = false;
}
void
InverseClientSocket::onOutputShutdown()
{
m_outputBuffer.pop(m_outputBuffer.getSize());
m_writable = false;
// we're now flushed
m_flushed = true;
m_flushed.broadcast();
}
void
InverseClientSocket::onDisconnected()
{
if (m_connected) {
sendEvent(m_events->forISocket().disconnected());
}
// disconnected
onInputShutdown();
onOutputShutdown();
m_connected = false;
}
ISocketMultiplexerJob*
InverseClientSocket::serviceConnecting(ISocketMultiplexerJob* job,
bool read, bool, bool)
{
Lock lock(&m_mutex);
if (read) {
m_socket = m_listener.acceptSocket();
onConnected();
return newJob(m_socket.getRawSocket());
}
return job;
}
ISocketMultiplexerJob*
InverseClientSocket::serviceConnected(ISocketMultiplexerJob* job,
bool read, bool write, bool error)
{
Lock lock(&m_mutex);
if (error) {
onDisconnected();
return newJob(m_listener.getRawSocket());
}
EJobResult result = InverseClientSocket::EJobResult::kRetry;
if (write) {
try {
result = doWrite();
}
catch (const XArchNetworkShutdown&) {
// remote read end of stream hungup. our output side
// has therefore shutdown.
onOutputShutdown();
sendEvent(m_events->forIStream().outputShutdown());
if (!m_readable && m_inputBuffer.getSize() == 0) {
sendEvent(m_events->forISocket().disconnected());
m_connected = false;
}
result = InverseClientSocket::EJobResult::kNew;
}
catch (const XArchNetworkDisconnected&) {
// stream hungup
onDisconnected();
result = InverseClientSocket::EJobResult::kNew;
}
catch (const XArchNetwork& e) {
// other write error
LOG((CLOG_WARN "error writing socket: %s", e.what()));
onDisconnected();
sendEvent(m_events->forIStream().outputError());
result = InverseClientSocket::EJobResult::kNew;
}
}
if (read && m_readable) {
try {
result = doRead();
}
catch (const XArchNetworkDisconnected&) {
// stream hungup
onDisconnected();
result = InverseClientSocket::EJobResult::kNew;
}
catch (const XArchNetwork& e) {
// ignore other read error
LOG((CLOG_WARN "error reading socket: %s", e.what()));
}
}
if (result == InverseClientSocket::EJobResult::kBreak) {
return nullptr;
}
return result == InverseClientSocket::EJobResult::kNew ? newJob(m_socket.getRawSocket()) : job;
}

View file

@ -0,0 +1,111 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "net/IDataSocket.h"
#include "io/StreamBuffer.h"
#include "mt/CondVar.h"
#include "mt/Mutex.h"
#include "arch/IArchNetwork.h"
#include "AutoArchSocket.h"
class ISocketMultiplexerJob;
class IEventQueue;
class SocketMultiplexer;
class InverseClientSocket : public IDataSocket {
public:
InverseClientSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family = IArchNetwork::kINET);
InverseClientSocket(InverseClientSocket const &) =delete;
InverseClientSocket(InverseClientSocket &&) =delete;
~InverseClientSocket() override;
InverseClientSocket& operator=(InverseClientSocket const &) =delete;
InverseClientSocket& operator=(InverseClientSocket &&) =delete;
// ISocket overrides
void bind(const NetworkAddress&) override;
void close() override;
void* getEventTarget() const override;
// IStream overrides
UInt32 read(void* buffer, UInt32 n) override;
void write(const void* buffer, UInt32 n) override;
void flush() override;
void shutdownInput() override;
void shutdownOutput() override;
bool isReady() const override;
bool isFatal() const override;
UInt32 getSize() const override;
// IDataSocket overrides
void connect(const NetworkAddress&) override;
virtual ISocketMultiplexerJob*
newJob(ArchSocket socket);
protected:
enum class EJobResult {
kBreak = -1, //!< Break the Job chain
kRetry, //!< Retry the same job
kNew //!< Require a new job
};
ArchSocket getSocket() { return m_socket.getRawSocket(); }
IEventQueue* getEvents() { return m_events; }
virtual EJobResult doRead();
virtual EJobResult doWrite();
void setJob(ISocketMultiplexerJob*);
bool isReadable() const { return m_readable; }
bool isWritable() const { return m_writable; }
Mutex& getMutex() { return m_mutex; }
void sendEvent(Event::Type);
void discardWrittenData(int bytesWrote);
private:
void sendConnectionFailedEvent(const char*);
void onConnected();
void onInputShutdown();
void onOutputShutdown();
void onDisconnected();
ISocketMultiplexerJob*
serviceConnecting(ISocketMultiplexerJob*,
bool, bool, bool);
ISocketMultiplexerJob*
serviceConnected(ISocketMultiplexerJob*,
bool, bool, bool);
bool m_readable = false;
bool m_writable = false;
bool m_connected = false;
IEventQueue* m_events;
StreamBuffer m_inputBuffer;
StreamBuffer m_outputBuffer;
Mutex m_mutex;
AutoArchSocket m_socket;
AutoArchSocket m_listener;
CondVar<bool> m_flushed;
SocketMultiplexer* m_socketMultiplexer;
};

View file

@ -0,0 +1,122 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "InverseServerSocket.h"
#include "net/NetworkAddress.h"
#include "net/SocketMultiplexer.h"
#include "net/TCPSocket.h"
#include "net/TSocketMultiplexerMethodJob.h"
#include "net/XSocket.h"
#include "io/XIO.h"
#include "mt/Lock.h"
#include "mt/Mutex.h"
#include "arch/Arch.h"
#include "arch/XArch.h"
#include "base/Log.h"
#include "base/IEventQueue.h"
//
// InverseServerSocket
//
InverseServerSocket::InverseServerSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
m_socket(family),
m_events(events),
m_socketMultiplexer(socketMultiplexer)
{
}
InverseServerSocket::~InverseServerSocket()
{
m_socketMultiplexer->removeSocket(this);
}
void
InverseServerSocket::bind(const NetworkAddress& addr)
{
Lock lock(&m_mutex);
m_socket.connectSocket(addr);
setListeningJob(true);
}
void
InverseServerSocket::close()
{
Lock lock(&m_mutex);
m_socketMultiplexer->removeSocket(this);
m_socket.closeSocket();
}
void*
InverseServerSocket::getEventTarget() const
{
return const_cast<void*>(static_cast<const void*>(this));
}
IDataSocket*
InverseServerSocket::accept()
{
IDataSocket* socket = nullptr;
try {
socket = new TCPSocket(m_events, m_socketMultiplexer, m_socket.getRawSocket());
if (socket != nullptr) {
setListeningJob();
}
return socket;
}
catch (const XArchNetwork&) {
if (socket != nullptr) {
delete socket;
setListeningJob();
}
return nullptr;
}
catch (const std::exception &ex) {
if (socket != nullptr) {
delete socket;
setListeningJob();
}
throw;
}
}
void
InverseServerSocket::setListeningJob(bool read)
{
m_socketMultiplexer->addSocket(this,
new TSocketMultiplexerMethodJob<InverseServerSocket>(
this, &InverseServerSocket::serviceListening,
m_socket.getRawSocket(), true, read));
}
ISocketMultiplexerJob*
InverseServerSocket::serviceListening(ISocketMultiplexerJob* job,
bool, bool write, bool error)
{
if (error) {
close();
return nullptr;
}
if (write) {
m_events->addEvent(Event(m_events->forIListenSocket().connecting(), this));
// stop polling on this socket until the client accepts
return nullptr;
}
return job;
}

View file

@ -0,0 +1,61 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "net/IListenSocket.h"
#include "arch/IArchNetwork.h"
#include "mt/Mutex.h"
#include "AutoArchSocket.h"
class ISocketMultiplexerJob;
class IEventQueue;
class SocketMultiplexer;
class InverseServerSocket : public IListenSocket {
public:
InverseServerSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
InverseServerSocket(InverseServerSocket const &) =delete;
InverseServerSocket(InverseServerSocket &&) =delete;
~InverseServerSocket() override;
InverseServerSocket& operator=(InverseServerSocket const &) =delete;
InverseServerSocket& operator=(InverseServerSocket &&) =delete;
// ISocket overrides
void bind(const NetworkAddress&) override;
void close() override;
void* getEventTarget() const override;
// IListenSocket overrides
IDataSocket* accept() override;
protected:
void setListeningJob(bool read = false);
public:
ISocketMultiplexerJob*
serviceListening(ISocketMultiplexerJob*,
bool, bool, bool);
private:
AutoArchSocket m_socket;
Mutex m_mutex;
IEventQueue* m_events;
SocketMultiplexer* m_socketMultiplexer;
};

View file

@ -0,0 +1,59 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "InverseSocketFactory.h"
#include "net/InverseSockets/InverseClientSocket.h"
#include "net/InverseSockets/InverseServerSocket.h"
#include "net/SecureSocket.h"
#include "net/SecureListenSocket.h"
//
// InverseSocketFactory
//
InverseSocketFactory::InverseSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
m_events(events),
m_socketMultiplexer(socketMultiplexer)
{
}
IDataSocket*
InverseSocketFactory::create(bool secure, IArchNetwork::EAddressFamily family) const
{
if (secure) {
auto secureSocket = new SecureSocket(m_events, m_socketMultiplexer, family);
secureSocket->initSsl (false);
return secureSocket;
}
else {
return new InverseClientSocket(m_events, m_socketMultiplexer, family);
}
}
IListenSocket*
InverseSocketFactory::createListen(bool secure, IArchNetwork::EAddressFamily family) const
{
IListenSocket* socket = nullptr;
if (secure) {
socket = new SecureListenSocket(m_events, m_socketMultiplexer, family);
}
else {
socket = new InverseServerSocket(m_events, m_socketMultiplexer, family);
}
return socket;
}

View file

@ -0,0 +1,34 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2022 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 "net/ISocketFactory.h"
class IEventQueue;
class SocketMultiplexer;
class InverseSocketFactory : public ISocketFactory {
public:
InverseSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
// ISocketFactory overrides
IDataSocket* create(bool secure, IArchNetwork::EAddressFamily family = IArchNetwork::kINET) const override;
IListenSocket* createListen(bool secure, IArchNetwork::EAddressFamily family = IArchNetwork::kINET) const override;
private:
IEventQueue* m_events = nullptr;
SocketMultiplexer* m_socketMultiplexer = nullptr;
};

View file

@ -653,6 +653,11 @@ Config::formatInterval(const Interval& x)
(int)(x.second * 100.0f + 0.5f));
}
String Config::getClientAddress() const
{
return m_clientAddress;
}
void
Config::readSection(ConfigReadContext& s)
{
@ -773,6 +778,9 @@ Config::readSectionOptions(ConfigReadContext& s)
else if (name == "clipboardSharingSize") {
addOption("", kOptionClipboardSharingSize, s.parseInt(value));
}
else if (name == "clientAddress") {
m_clientAddress = value;
}
else {
handled = false;
}

View file

@ -450,6 +450,11 @@ public:
*/
static String formatInterval(const Interval&);
//! Get client address as a string
/*!
* Return client address a string.
*/
String getClientAddress() const;
//@}
private:
@ -481,6 +486,7 @@ private:
InputFilter m_inputFilter;
bool m_hasLockToScreenAction;
IEventQueue* m_events;
String m_clientAddress;
};
//! Configuration read context

View file

@ -117,6 +117,9 @@ ArgParser::parseClientArgs(lib::synergy::ClientArgs& args, int argc, const char*
else if (isArg(i, argc, argv, nullptr, "--invert-scroll")) {
args.m_clientScrollDirection = lib::synergy::ClientScrollDirection::INVERT_SERVER;
}
else if (isArg(i, argc, argv, nullptr, "--host")) {
args.m_hostMode = true;
}
else {
if (i + 1 == argc) {
args.m_synergyAddress = argv[i];

View file

@ -26,6 +26,7 @@
#include "synergy/ClientArgs.h"
#include "net/NetworkAddress.h"
#include "net/TCPSocketFactory.h"
#include "net/InverseSockets/InverseSocketFactory.h"
#include "net/SocketMultiplexer.h"
#include "net/XSocket.h"
#include "mt/Thread.h"
@ -128,6 +129,7 @@ ClientApp::help()
" [--yscroll <delta>]"
" [--sync-language]"
" [--invert-scroll]"
" [--host]"
WINAPI_ARG
HELP_SYS_ARGS
HELP_COMMON_ARGS
@ -142,6 +144,7 @@ ClientApp::help()
" 120 by default.\n"
" --sync-language set this parameter to enable language synchronization.\n"
" --invert-scroll invert scroll direction on this computer.\n"
" --host client starts a listener and waits for a server connection.\n"
HELP_COMMON_INFO_2
"\n"
"* marks defaults.\n"
@ -366,7 +369,7 @@ ClientApp::openClient(const String& name, const NetworkAddress& address,
m_events,
name,
address,
new TCPSocketFactory(m_events, getSocketMultiplexer()),
getSocketFactory(),
screen,
args());
@ -599,3 +602,17 @@ ClientApp::startNode()
m_bye(kExitFailed);
}
}
ISocketFactory* ClientApp::getSocketFactory() const
{
ISocketFactory* socketFactory = nullptr;
if (args().m_hostMode) {
socketFactory = new InverseSocketFactory(m_events, getSocketMultiplexer());
}
else {
socketFactory = new TCPSocketFactory(m_events, getSocketMultiplexer());
}
return socketFactory;
}

View file

@ -25,6 +25,7 @@ class Event;
class Client;
class NetworkAddress;
class Thread;
class ISocketFactory;
namespace lib {
namespace synergy {
class ClientArgs;
@ -82,6 +83,8 @@ public:
Client* getClientPtr() { return m_client; }
private:
ISocketFactory* getSocketFactory() const;
Client* m_client;
synergy::Screen* m_clientScreen;
NetworkAddress* m_serverAddress;

View file

@ -46,6 +46,12 @@ namespace lib {
*/
ClientScrollDirection m_clientScrollDirection = ClientScrollDirection::SERVER;
/**
* @brief m_hostMode - activates host mode.
* Client starts a listener and waits for a server connection.
*/
bool m_hostMode = false;
};
}
}

View file

@ -29,6 +29,7 @@
#include "synergy/ServerArgs.h"
#include "net/SocketMultiplexer.h"
#include "net/TCPSocketFactory.h"
#include "net/InverseSockets/InverseSocketFactory.h"
#include "net/XSocket.h"
#include "arch/Arch.h"
#include "base/EventQueue.h"
@ -633,8 +634,8 @@ ClientListener*
ServerApp::openClientListener(const NetworkAddress& address)
{
ClientListener* listen = new ClientListener(
address,
new TCPSocketFactory(m_events, getSocketMultiplexer()),
getAddress(address),
getSocketFactory(),
m_events,
args().m_enableCrypto);
@ -678,6 +679,34 @@ ServerApp::handleScreenSwitched(const Event& e, void*)
{
}
ISocketFactory* ServerApp::getSocketFactory() const
{
ISocketFactory* socketFactory = nullptr;
const auto clientAddress = args().m_config->getClientAddress();
if (clientAddress.empty()) {
socketFactory = new TCPSocketFactory(m_events, getSocketMultiplexer());
}
else {
socketFactory = new InverseSocketFactory(m_events, getSocketMultiplexer());
}
return socketFactory;
}
NetworkAddress ServerApp::getAddress(const NetworkAddress& address) const
{
const auto clientAddress = args().m_config->getClientAddress();
if (clientAddress.empty()) {
return address;
}
else {
NetworkAddress addr(clientAddress.c_str(), kDefaultPort);
addr.resolve();
return addr;
}
}
int
ServerApp::mainLoop()
{

View file

@ -45,6 +45,7 @@ class ClientListener;
class EventQueueTimer;
class ILogOutputter;
class IEventQueue;
class ISocketFactory;
namespace lib {
namespace synergy{
class ServerArgs;
@ -119,6 +120,8 @@ public:
private:
void handleScreenSwitched(const Event&, void* data);
ISocketFactory* getSocketFactory() const;
NetworkAddress getAddress(const NetworkAddress& address) const;
};
// configuration file name