Merge branch 'master' of github.com:synergy/synergy

This commit is contained in:
Adam Potolsky 2015-05-26 13:18:22 -07:00
commit d816ed6b43
53 changed files with 1569 additions and 787 deletions

View File

@ -1,5 +1,28 @@
v1.7.2 v1.7.3-stable
====== =============
Bug #4565 - Incorrect plugin downloads on Debian and Mint
Bug #4677 - Windows service log file grows to very large size
Bug #4651 - High logging rate causes Windows service to crash
Bug #4650 - SSL error log message repeats excessively and freezes cursor
Bug #4624 - Runaway logging causes GUI to freeze
Bug #4617 - Windows service randomly stops after 'ssl handshake failure' error
Bug #4601 - Large clipboard data with SSL causes 'protocol is shutdown' error
Bug #4593 - Locking Windows server causes SSL_ERROR_SSL to repeat
Bug #4577 - Memory leak in GUI on Windows caused by logging
Bug #4538 - Windows service crashes intermittently with no error
Bug #4341 - GUI freezes on first load when reading log
Bug #4566 - Client or server crashes with 'ssl handshake failure' error
Bug #4706 - Installer is not output to build config dir on Windows
Bug #4704 - Plugin 'ns' release build is overwritten with debug version on Linux
Bug #4703 - Plugins are not built to config directory on Mac
Bug #4697 - Timing can allow an SSL socket to be used after cleanup call
Enhancement #4661 - Log error but do not crash when failing to load plugins
Enhancement #4708 - Download ns plugin for specific Mac versions
Enhancement #4587 - Include OpenSSL binaries in source for easier building
Enhancement #4695 - Automatically upload plugins as Buildbot step
v1.7.2-stable
=============
Bug #4564 - Modifier keys often stuck down on Mac client Bug #4564 - Modifier keys often stuck down on Mac client
Bug #4581 - Starting GUI on Mac crashes instantly on syntool segfault Bug #4581 - Starting GUI on Mac crashes instantly on syntool segfault
Bug #4520 - Laggy or sluggish cursor (ping spikes) on Mac when using WiFi Bug #4520 - Laggy or sluggish cursor (ping spikes) on Mac when using WiFi
@ -11,8 +34,8 @@ Enhancement #4569 - Reintroduce GUI auto-hide setting (disabled by default)
Enhancement #4570 - Make `--crypto-pass` show deprecated message Enhancement #4570 - Make `--crypto-pass` show deprecated message
Enhancement #4596 - Typo 'occurred' in WebClient.cpp Enhancement #4596 - Typo 'occurred' in WebClient.cpp
v1.7.1 v1.7.1-stable
====== =============
Bug #3784 - Double click & drag doesn't select words on client Bug #3784 - Double click & drag doesn't select words on client
Bug #3052 - Triple-click (select line) does not work Bug #3052 - Triple-click (select line) does not work
Bug #4367 - Duplicate Alt-S Keyboard Shortcuts on Gui Bug #4367 - Duplicate Alt-S Keyboard Shortcuts on Gui
@ -38,8 +61,8 @@ Enhancement #4540 - Enable Network Security checkbox only when ns plugin exists
Enhancement #4525 - Reorganize app data directory Enhancement #4525 - Reorganize app data directory
Enhancement #4390 - Disable GUI auto-hide by default Enhancement #4390 - Disable GUI auto-hide by default
1.7.0 v1.7.0-beta
===== ===========
Enhancement #4313 - SSL encrypted secure connection Enhancement #4313 - SSL encrypted secure connection
Enhancement #4168 - Plugin manager for GUI Enhancement #4168 - Plugin manager for GUI
Enhancement #4307 - Always show client auto-detect dialog Enhancement #4307 - Always show client auto-detect dialog
@ -264,3 +287,4 @@ Feature #3119: Mac OS X secondary screen
Task #2905: Unit tests: Clipboard classes Task #2905: Unit tests: Clipboard classes
Task #3072: Downgrade Linux build machines Task #3072: Downgrade Linux build machines
Task #3090: CXWindowsKeyState integ test args wrong Task #3090: CXWindowsKeyState integ test args wrong

View File

@ -17,7 +17,7 @@
# TODO: split this file up, it's too long! # TODO: split this file up, it's too long!
import sys, os, ConfigParser, shutil, re, ftputil, zipfile, glob, commands import sys, os, ConfigParser, shutil, re, ftputil, zipfile, glob, commands
from generators import Generator, EclipseGenerator, XcodeGenerator, MakefilesGenerator from generators import VisualStudioGenerator, EclipseGenerator, XcodeGenerator, MakefilesGenerator
from getopt import gnu_getopt from getopt import gnu_getopt
if sys.version_info >= (2, 4): if sys.version_info >= (2, 4):
@ -254,12 +254,12 @@ class InternalCommands:
gmockDir = 'gmock-1.6.0' gmockDir = 'gmock-1.6.0'
win32_generators = { win32_generators = {
1 : Generator('Visual Studio 10'), 1 : VisualStudioGenerator('10'),
2 : Generator('Visual Studio 10 Win64'), 2 : VisualStudioGenerator('10 Win64'),
3 : Generator('Visual Studio 9 2008'), 3 : VisualStudioGenerator('9 2008'),
4 : Generator('Visual Studio 9 2008 Win64'), 4 : VisualStudioGenerator('9 2008 Win64'),
5 : Generator('Visual Studio 8 2005'), 5 : VisualStudioGenerator('8 2005'),
6 : Generator('Visual Studio 8 2005 Win64') 6 : VisualStudioGenerator('8 2005 Win64')
} }
unix_generators = { unix_generators = {
@ -319,7 +319,6 @@ class InternalCommands:
self.configure(target) self.configure(target)
def checkGTest(self): def checkGTest(self):
dir = self.extDir + '/' + self.gtestDir dir = self.extDir + '/' + self.gtestDir
if (os.path.isdir(dir)): if (os.path.isdir(dir)):
return return
@ -335,7 +334,6 @@ class InternalCommands:
self.zipExtractAll(zip, dir) self.zipExtractAll(zip, dir)
def checkGMock(self): def checkGMock(self):
dir = self.extDir + '/' + self.gmockDir dir = self.extDir + '/' + self.gmockDir
if (os.path.isdir(dir)): if (os.path.isdir(dir)):
return return
@ -840,7 +838,7 @@ class InternalCommands:
pwd = lines[0] pwd = lines[0]
if (dist): if (dist):
self.signFile(pfx, pwd, 'bin', self.dist_name('win')) self.signFile(pfx, pwd, 'bin/Release', self.dist_name('win'))
else: else:
self.signFile(pfx, pwd, 'bin/Release', 'synergy.exe') self.signFile(pfx, pwd, 'bin/Release', 'synergy.exe')
self.signFile(pfx, pwd, 'bin/Release', 'synergyc.exe') self.signFile(pfx, pwd, 'bin/Release', 'synergyc.exe')
@ -974,7 +972,13 @@ class InternalCommands:
if p.returncode != 0: if p.returncode != 0:
raise Exception('Could not get branch name, git error: ' + str(p.returncode)) raise Exception('Could not get branch name, git error: ' + str(p.returncode))
return stdout.strip() result = stdout.strip()
# sometimes, git will prepend "heads/" infront of the branch name,
# remove this as it's not useful to us and causes ftp issues.
result = re.sub("heads/", "", result)
return result
def find_revision_svn(self): def find_revision_svn(self):
if sys.version_info < (2, 4): if sys.version_info < (2, 4):
@ -1100,6 +1104,7 @@ class InternalCommands:
err = os.system(cmd) err = os.system(cmd)
if err != 0: if err != 0:
raise Exception('rpmlint failed: ' + str(err)) raise Exception('rpmlint failed: ' + str(err))
finally: finally:
self.restore_chdir() self.restore_chdir()
@ -1294,7 +1299,7 @@ class InternalCommands:
arch) arch)
old = "bin/Release/synergy.msi" old = "bin/Release/synergy.msi"
new = "bin/%s" % (filename) new = "bin/Release/%s" % (filename)
try: try:
os.remove(new) os.remove(new)
@ -1359,19 +1364,108 @@ class InternalCommands:
def distftp(self, type, ftp): def distftp(self, type, ftp):
if not type: if not type:
raise Exception('Type not specified.') raise Exception('Platform type not specified.')
if not ftp:
raise Exception('FTP info not defined.')
self.loadConfig() self.loadConfig()
src = self.dist_name(type)
dest = self.dist_name_rev(type)
print 'Uploading %s to FTP server %s...' % (dest, ftp.host)
binDir = self.getGenerator().getBinDir('Release') binDir = self.getGenerator().getBinDir('Release')
ftp.run(binDir + '/' + src, dest)
print 'Done' packageSource = binDir + '/' + self.dist_name(type)
packageTarget = self.dist_name_rev(type)
ftp.upload(packageSource, packageTarget)
if type != 'src':
pluginsDir = binDir + '/plugins'
nsPluginSource = self.findLibraryFile(type, pluginsDir, 'ns')
if nsPluginSource:
nsPluginTarget = self.getLibraryDistFilename(type, pluginsDir, 'ns')
ftp.upload(nsPluginSource, nsPluginTarget, "plugins")
def getLibraryDistFilename(self, type, dir, name):
(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type)
branch = self.getGitBranchName()
revision = self.getGitRevision()
firstPart = '%s-%s-%s-%s' % (name, branch, revision, platform)
filename = '%s.%s' % (firstPart, libraryExt)
if type == 'rpm' or type == 'deb':
# linux is a bit special, include dist type (deb/rpm in filename)
filename = '%s-%s.%s' % (firstPart, packageExt, libraryExt)
return filename
def findLibraryFile(self, type, dir, name):
if not os.path.exists(dir):
return None
(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type)
ext = libraryExt
pattern = name + '\.' + ext
for filename in os.listdir(dir):
if re.search(pattern, filename):
return dir + '/' + filename
return None
def getDistributePlatformInfo(self, type):
ext = None
libraryExt = None
platform = None
if type == 'src':
ext = 'tar.gz'
platform = 'Source'
elif type == 'rpm' or type == 'deb':
ext = type
libraryExt = 'so'
platform = self.getLinuxPlatform()
elif type == 'win':
# get platform based on last generator used
ext = 'msi'
libraryExt = 'dll'
generator = self.getGeneratorFromConfig().cmakeName
if generator.find('Win64') != -1:
platform = 'Windows-x64'
else:
platform = 'Windows-x86'
elif type == 'mac':
ext = "dmg"
libraryExt = 'dylib'
platform = self.getMacPackageName()
if not platform:
raise Exception('Unable to detect distributable platform.')
return (platform, ext, libraryExt)
def dist_name(self, type):
(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type)
ext = packageExt
pattern = (
re.escape(self.project + '-') + '\d+\.\d+\.\d+' +
re.escape('-' + platform + '.' + ext))
for filename in os.listdir(self.getBinDir('Release')):
if re.search(pattern, filename):
return filename
# still here? package probably not created yet.
raise Exception('Could not find package name with pattern: ' + pattern)
def dist_name_rev(self, type):
branch = self.getGitBranchName()
revision = self.getGitRevision()
# find the version number (we're puting the rev in after this)
pattern = '(\d+\.\d+\.\d+)'
replace = "%s-%s" % (branch, revision)
return re.sub(pattern, replace, self.dist_name(type))
def getDebianArch(self): def getDebianArch(self):
if os.uname()[4][:3] == 'arm': if os.uname()[4][:3] == 'arm':
@ -1405,56 +1499,6 @@ class InternalCommands:
else: else:
raise Exception("unknown os bits: " + os_bits) raise Exception("unknown os bits: " + os_bits)
def dist_name(self, type):
ext = None
platform = None
if type == 'src':
ext = 'tar.gz'
platform = 'Source'
elif type == 'rpm' or type == 'deb':
ext = type
platform = self.getLinuxPlatform()
elif type == 'win':
# get platform based on last generator used
ext = 'msi'
generator = self.getGeneratorFromConfig().cmakeName
if generator.find('Win64') != -1:
platform = 'Windows-x64'
else:
platform = 'Windows-x86'
elif type == 'mac':
ext = "dmg"
platform = self.getMacPackageName()
if not platform:
raise Exception('Unable to detect package platform.')
pattern = re.escape(self.project + '-') + '\d+\.\d+\.\d+' + re.escape('-' + platform + '.' + ext)
target = ''
if type == 'mac':
target = 'Release'
for filename in os.listdir(self.getBinDir(target)):
if re.search(pattern, filename):
return filename
# still here? package probably not created yet.
raise Exception('Could not find package name with pattern: ' + pattern)
def dist_name_rev(self, type):
# find the version number (we're puting the rev in after this)
pattern = '(\d+\.\d+\.\d+)'
replace = "%s-%s" % (
self.getGitBranchName(), self.getGitRevision())
return re.sub(pattern, replace, self.dist_name(type))
def dist_usage(self): def dist_usage(self):
print ('Usage: %s package [package-type]\n' print ('Usage: %s package [package-type]\n'
'\n' '\n'
@ -1926,8 +1970,9 @@ class CommandHandler:
elif o == '--dir': elif o == '--dir':
dir = a dir = a
ftp = None if not host:
if host: raise Exception('FTP host was not specified.')
ftp = ftputil.FtpUploader( ftp = ftputil.FtpUploader(
host, user, password, dir) host, user, password, dir)

View File

@ -23,13 +23,32 @@ class FtpUploader:
self.password = password self.password = password
self.dir = dir self.dir = dir
def run(self, src, dest, replace=False): def upload(self, src, dest, subDir=None):
print "Connecting to '%s'" % self.host
ftp = FTP(self.host, self.user, self.password) ftp = FTP(self.host, self.user, self.password)
ftp.cwd(self.dir)
self.changeDir(ftp, self.dir)
if subDir:
self.changeDir(ftp, subDir)
print "Uploading '%s' as '%s'" % (src, dest)
f = open(src, 'rb') f = open(src, 'rb')
ftp.storbinary('STOR ' + dest, f) ftp.storbinary('STOR ' + dest, f)
f.close() f.close()
ftp.close() ftp.close()
print "Done"
def changeDir(self, ftp, dir):
if dir not in ftp.nlst():
print "Creating dir '%s'" % dir
try:
ftp.mkd(dir)
except:
# sometimes nlst may returns nothing, so mkd fails with 'File exists'
print "Failed to create dir '%s'" % dir
print "Changing to dir '%s'" % dir
ftp.cwd(dir)

View File

@ -30,6 +30,13 @@ class Generator(object):
def getSourceDir(self): def getSourceDir(self):
return self.sourceDir return self.sourceDir
class VisualStudioGenerator(Generator):
def __init__(self, version):
super(VisualStudioGenerator, self).__init__('Visual Studio ' + version)
def getBinDir(self, target=''):
return super(VisualStudioGenerator, self).getBinDir(target) + '/' + target
class MakefilesGenerator(Generator): class MakefilesGenerator(Generator):
def __init__(self): def __init__(self):
super(MakefilesGenerator, self).__init__('Unix Makefiles') super(MakefilesGenerator, self).__init__('Unix Makefiles')

View File

@ -1132,12 +1132,12 @@ void MainWindow::downloadBonjour()
{ {
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QUrl url; QUrl url;
int arch = checkProcessorArch(); int arch = getProcessorArch();
if (arch == Win_x86) { if (arch == kProcessorArchWin32) {
url.setUrl(bonjourBaseUrl + bonjourFilename32); url.setUrl(bonjourBaseUrl + bonjourFilename32);
appendLogNote("downloading 32-bit Bonjour"); appendLogNote("downloading 32-bit Bonjour");
} }
else if (arch == Win_x64) { else if (arch == kProcessorArchWin64) {
url.setUrl(bonjourBaseUrl + bonjourFilename64); url.setUrl(bonjourBaseUrl + bonjourFilename64);
appendLogNote("downloading 64-bit Bonjour"); appendLogNote("downloading 64-bit Bonjour");
} }

View File

@ -29,14 +29,15 @@
#include <QProcess> #include <QProcess>
#include <QCoreApplication> #include <QCoreApplication>
static QString kBaseUrl = "http://synergy-project.org/files"; static const char kBaseUrl[] = "http://synergy-project.org/files";
static const char kWinProcessorArch32[] = "Windows-x86"; static const char kDefaultVersion[] = "1.1";
static const char kWinProcessorArch64[] = "Windows-x64"; static const char kWinPackagePlatform32[] = "Windows-x86";
static const char kMacProcessorArch[] = "MacOSX-i386"; static const char kWinPackagePlatform64[] = "Windows-x64";
static const char kLinuxProcessorArchDeb32[] = "Linux-i686-deb"; static const char kMacPackagePlatform[] = "MacOSX%1-i386";
static const char kLinuxProcessorArchDeb64[] = "Linux-x86_64-deb"; static const char kLinuxPackagePlatformDeb32[] = "Linux-i686-deb";
static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm"; static const char kLinuxPackagePlatformDeb64[] = "Linux-x86_64-deb";
static const char kLinuxProcessorArchRpm64[] = "Linux-x86_64-rpm"; static const char kLinuxPackagePlatformRpm32[] = "Linux-i686-rpm";
static const char kLinuxPackagePlatformRpm64[] = "Linux-x86_64-rpm";
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
static const char kWinPluginExt[] = ".dll"; static const char kWinPluginExt[] = ".dll";
@ -157,10 +158,10 @@ QString PluginManager::getPluginUrl(const QString& pluginName)
try { try {
QString coreArch = m_CoreInterface.getArch(); QString coreArch = m_CoreInterface.getArch();
if (coreArch.startsWith("x86")) { if (coreArch.startsWith("x86")) {
archName = kWinProcessorArch32; archName = kWinPackagePlatform32;
} }
else if (coreArch.startsWith("x64")) { else if (coreArch.startsWith("x64")) {
archName = kWinProcessorArch64; archName = kWinPackagePlatform64;
} }
} }
catch (...) { catch (...) {
@ -170,22 +171,53 @@ QString PluginManager::getPluginUrl(const QString& pluginName)
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)
archName = kMacProcessorArch; QString macVersion = "1010";
#if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 // 10.9
macVersion = "109";
#elif __MAC_OS_X_VERSION_MIN_REQUIRED <= 1080 // 10.8
macVersion = "108";
#elif __MAC_OS_X_VERSION_MIN_REQUIRED <= 1070 // 10.7
emit error(tr("Plugins not supported on this Mac OS X version."));
return "";
#endif
archName = QString(kMacPackagePlatform).arg(macVersion);
#else #else
int arch = checkProcessorArch(); QString program("dpkg");
if (arch == Linux_rpm_i686) { QStringList args;
archName = kLinuxProcessorArchRpm32; args << "-s" << "synergy";
QProcess process;
process.setReadChannel(QProcess::StandardOutput);
process.start(program, args);
bool success = process.waitForStarted();
if (!success || !process.waitForFinished())
{
emit error(tr("Could not get Linux package type."));
return "";
} }
else if (arch == Linux_rpm_x86_64) {
archName = kLinuxProcessorArchRpm64; bool isDeb = (process.exitCode() == 0);
int arch = getProcessorArch();
if (arch == kProcessorArchLinux32) {
if (isDeb) {
archName = kLinuxPackagePlatformDeb32;
} }
else if (arch == Linux_deb_i686) { else {
archName = kLinuxProcessorArchDeb32; archName = kLinuxPackagePlatformRpm32;
}
}
else if (arch == kProcessorArchLinux64) {
if (isDeb) {
archName = kLinuxPackagePlatformDeb64;
}
else {
archName = kLinuxPackagePlatformRpm64;
} }
else if (arch == Linux_deb_x86_64) {
archName = kLinuxProcessorArchDeb64;
} }
else { else {
emit error(tr("Could not get Linux architecture type.")); emit error(tr("Could not get Linux architecture type."));
@ -194,13 +226,14 @@ QString PluginManager::getPluginUrl(const QString& pluginName)
#endif #endif
QString result = kBaseUrl; QString result = QString("%1/plugins/%2/%3/%4/%5")
result.append("/plugins/"); .arg(kBaseUrl)
result.append(pluginName).append("/1.0/"); .arg(pluginName)
result.append(archName); .arg(kDefaultVersion)
result.append("/"); .arg(archName)
result.append(getPluginOsSpecificName(pluginName)); .arg(getPluginOsSpecificName(pluginName));
qDebug() << result;
return result; return result;
} }

View File

@ -15,18 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef PROCESSORARCH_H #pragma once
#define PROCESSORARCH_H
enum qProcessorArch { enum qProcessorArch {
Win_x86, kProcessorArchWin32,
Win_x64, kProcessorArchWin64,
Mac_i386, kProcessorArchMac32,
Linux_rpm_i686, kProcessorArchMac64,
Linux_rpm_x86_64, kProcessorArchLinux32,
Linux_deb_i686, kProcessorArchLinux64,
Linux_deb_x86_64, kProcessorArchUnknown
unknown
}; };
#endif // PROCESSORARCH_H

View File

@ -29,12 +29,6 @@
#include <Windows.h> #include <Windows.h>
#endif #endif
#if defined(Q_OS_LINUX)
static const char kLinuxI686[] = "i686";
static const char kLinuxX8664[] = "x86_64";
static const char kUbuntu[] = "Ubuntu";
#endif
void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData) void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData)
{ {
for (int i = 0; i < comboBox->count(); ++i) for (int i = 0; i < comboBox->count(); ++i)
@ -68,7 +62,7 @@ QString getFirstMacAddress()
return mac; return mac;
} }
int checkProcessorArch() qProcessorArch getProcessorArch()
{ {
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
SYSTEM_INFO systemInfo; SYSTEM_INFO systemInfo;
@ -76,97 +70,23 @@ int checkProcessorArch()
switch (systemInfo.wProcessorArchitecture) { switch (systemInfo.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL: case PROCESSOR_ARCHITECTURE_INTEL:
return Win_x86; return kProcessorArchWin32;
case PROCESSOR_ARCHITECTURE_IA64: case PROCESSOR_ARCHITECTURE_IA64:
return Win_x64; return kProcessorArchWin64;
case PROCESSOR_ARCHITECTURE_AMD64: case PROCESSOR_ARCHITECTURE_AMD64:
return Win_x64; return kProcessorArchWin64;
default: default:
return unknown; return kProcessorArchUnknown;
}
#elif defined(Q_OS_MAC)
return Mac_i386;
#else
bool version32 = false;
bool debPackaging = false;
QString program1("uname");
QStringList args1("-m");
QProcess process1;
process1.setReadChannel(QProcess::StandardOutput);
process1.start(program1, args1);
bool success = process1.waitForStarted();
QString out, error;
if (success)
{
if (process1.waitForFinished()) {
out = process1.readAllStandardOutput();
error = process1.readAllStandardError();
}
}
out = out.trimmed();
error = error.trimmed();
if (out.isEmpty() ||
!error.isEmpty() ||
!success ||
process1.exitCode() != 0)
{
return unknown;
}
if (out == kLinuxI686) {
version32 = true;
}
QString program2("python");
QStringList args2("-mplatform");
QProcess process2;
process2.setReadChannel(QProcess::StandardOutput);
process2.start(program2, args2);
success = process2.waitForStarted();
if (success)
{
if (process2.waitForFinished()) {
out = process2.readAllStandardOutput();
error = process2.readAllStandardError();
}
}
out = out.trimmed();
error = error.trimmed();
if (out.isEmpty() ||
!error.isEmpty() ||
!success ||
process2.exitCode() != 0)
{
return unknown;
}
if (out.contains(kUbuntu)) {
debPackaging = true;
}
if (version32) {
if (debPackaging) {
return Linux_deb_i686;
}
else {
return Linux_rpm_i686;
}
}
else {
if (debPackaging) {
return Linux_deb_x86_64;
}
else {
return Linux_rpm_x86_64;
}
} }
#endif #endif
return unknown;
#if defined(Q_OS_LINUX)
#ifdef __i386__
return kProcessorArchLinux32;
#else
return kProcessorArchLinux64;
#endif
#endif
return kProcessorArchUnknown;
} }

View File

@ -17,6 +17,8 @@
#pragma once #pragma once
#include "ProcessorArch.h"
#include <QComboBox> #include <QComboBox>
#include <QVariant> #include <QVariant>
#include <QCryptographicHash> #include <QCryptographicHash>
@ -25,4 +27,4 @@
void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData); void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData);
QString hash(const QString& string); QString hash(const QString& string);
QString getFirstMacAddress(); QString getFirstMacAddress();
int checkProcessorArch(); qProcessorArch getProcessorArch();

View File

@ -46,6 +46,7 @@ EVENT_TYPE_ACCESSOR(ServerApp)
EVENT_TYPE_ACCESSOR(IKeyState) EVENT_TYPE_ACCESSOR(IKeyState)
EVENT_TYPE_ACCESSOR(IPrimaryScreen) EVENT_TYPE_ACCESSOR(IPrimaryScreen)
EVENT_TYPE_ACCESSOR(IScreen) EVENT_TYPE_ACCESSOR(IScreen)
EVENT_TYPE_ACCESSOR(Clipboard)
// interrupt handler. this just adds a quit event to the queue. // interrupt handler. this just adds a quit event to the queue.
static static
@ -82,6 +83,7 @@ EventQueue::EventQueue() :
m_typesForIKeyState(NULL), m_typesForIKeyState(NULL),
m_typesForIPrimaryScreen(NULL), m_typesForIPrimaryScreen(NULL),
m_typesForIScreen(NULL), m_typesForIScreen(NULL),
m_typesForClipboard(NULL),
m_readyMutex(new Mutex), m_readyMutex(new Mutex),
m_readyCondVar(new CondVar<bool>(m_readyMutex, false)) m_readyCondVar(new CondVar<bool>(m_readyMutex, false))
{ {

View File

@ -157,6 +157,7 @@ public:
IKeyStateEvents& forIKeyState(); IKeyStateEvents& forIKeyState();
IPrimaryScreenEvents& forIPrimaryScreen(); IPrimaryScreenEvents& forIPrimaryScreen();
IScreenEvents& forIScreen(); IScreenEvents& forIScreen();
ClipboardEvents& forClipboard();
private: private:
ClientEvents* m_typesForClient; ClientEvents* m_typesForClient;
@ -177,6 +178,7 @@ private:
IKeyStateEvents* m_typesForIKeyState; IKeyStateEvents* m_typesForIKeyState;
IPrimaryScreenEvents* m_typesForIPrimaryScreen; IPrimaryScreenEvents* m_typesForIPrimaryScreen;
IScreenEvents* m_typesForIScreen; IScreenEvents* m_typesForIScreen;
ClipboardEvents* m_typesForClipboard;
Mutex* m_readyMutex; Mutex* m_readyMutex;
CondVar<bool>* m_readyCondVar; CondVar<bool>* m_readyCondVar;
std::queue<Event> m_pending; std::queue<Event> m_pending;

View File

@ -115,7 +115,6 @@ REGISTER_EVENT(ClientListener, connected)
REGISTER_EVENT(ClientProxy, ready) REGISTER_EVENT(ClientProxy, ready)
REGISTER_EVENT(ClientProxy, disconnected) REGISTER_EVENT(ClientProxy, disconnected)
REGISTER_EVENT(ClientProxy, clipboardChanged)
// //
// ClientProxyUnknown // ClientProxyUnknown
@ -175,7 +174,6 @@ REGISTER_EVENT(IPrimaryScreen, fakeInputEnd)
REGISTER_EVENT(IScreen, error) REGISTER_EVENT(IScreen, error)
REGISTER_EVENT(IScreen, shapeChanged) REGISTER_EVENT(IScreen, shapeChanged)
REGISTER_EVENT(IScreen, clipboardGrabbed)
REGISTER_EVENT(IScreen, suspend) REGISTER_EVENT(IScreen, suspend)
REGISTER_EVENT(IScreen, resume) REGISTER_EVENT(IScreen, resume)
REGISTER_EVENT(IScreen, fileChunkSending) REGISTER_EVENT(IScreen, fileChunkSending)
@ -187,3 +185,11 @@ REGISTER_EVENT(IScreen, fileRecieveCompleted)
REGISTER_EVENT(IpcServer, clientConnected) REGISTER_EVENT(IpcServer, clientConnected)
REGISTER_EVENT(IpcServer, messageReceived) REGISTER_EVENT(IpcServer, messageReceived)
//
// Clipboard
//
REGISTER_EVENT(Clipboard, clipboardGrabbed)
REGISTER_EVENT(Clipboard, clipboardChanged)
REGISTER_EVENT(Clipboard, clipboardSending)

View File

@ -350,8 +350,7 @@ class ClientProxyEvents : public EventTypes {
public: public:
ClientProxyEvents() : ClientProxyEvents() :
m_ready(Event::kUnknown), m_ready(Event::kUnknown),
m_disconnected(Event::kUnknown), m_disconnected(Event::kUnknown) { }
m_clipboardChanged(Event::kUnknown) { }
//! @name accessors //! @name accessors
//@{ //@{
@ -371,20 +370,11 @@ public:
*/ */
Event::Type disconnected(); Event::Type disconnected();
//! Get clipboard changed event type
/*!
Returns the clipboard changed event type. This is sent whenever the
contents of the clipboard has changed. The data is a pointer to a
IScreen::ClipboardInfo.
*/
Event::Type clipboardChanged();
//@} //@}
private: private:
Event::Type m_ready; Event::Type m_ready;
Event::Type m_disconnected; Event::Type m_disconnected;
Event::Type m_clipboardChanged;
}; };
class ClientProxyUnknownEvents : public EventTypes { class ClientProxyUnknownEvents : public EventTypes {
@ -634,7 +624,6 @@ public:
IScreenEvents() : IScreenEvents() :
m_error(Event::kUnknown), m_error(Event::kUnknown),
m_shapeChanged(Event::kUnknown), m_shapeChanged(Event::kUnknown),
m_clipboardGrabbed(Event::kUnknown),
m_suspend(Event::kUnknown), m_suspend(Event::kUnknown),
m_resume(Event::kUnknown), m_resume(Event::kUnknown),
m_fileChunkSending(Event::kUnknown), m_fileChunkSending(Event::kUnknown),
@ -657,14 +646,6 @@ public:
*/ */
Event::Type shapeChanged(); Event::Type shapeChanged();
//! Get clipboard grabbed event type
/*!
Returns the clipboard grabbed event type. This is sent whenever the
clipboard is grabbed by some other application so we don't own it
anymore. The data is a pointer to a ClipboardInfo.
*/
Event::Type clipboardGrabbed();
//! Get suspend event type //! Get suspend event type
/*! /*!
Returns the suspend event type. This is sent whenever the system goes Returns the suspend event type. This is sent whenever the system goes
@ -690,9 +671,49 @@ public:
private: private:
Event::Type m_error; Event::Type m_error;
Event::Type m_shapeChanged; Event::Type m_shapeChanged;
Event::Type m_clipboardGrabbed;
Event::Type m_suspend; Event::Type m_suspend;
Event::Type m_resume; Event::Type m_resume;
Event::Type m_fileChunkSending; Event::Type m_fileChunkSending;
Event::Type m_fileRecieveCompleted; Event::Type m_fileRecieveCompleted;
}; };
class ClipboardEvents : public EventTypes {
public:
ClipboardEvents() :
m_clipboardGrabbed(Event::kUnknown),
m_clipboardChanged(Event::kUnknown),
m_clipboardSending(Event::kUnknown) { }
//! @name accessors
//@{
//! Get clipboard grabbed event type
/*!
Returns the clipboard grabbed event type. This is sent whenever the
clipboard is grabbed by some other application so we don't own it
anymore. The data is a pointer to a ClipboardInfo.
*/
Event::Type clipboardGrabbed();
//! Get clipboard changed event type
/*!
Returns the clipboard changed event type. This is sent whenever the
contents of the clipboard has changed. The data is a pointer to a
IScreen::ClipboardInfo.
*/
Event::Type clipboardChanged();
//! Clipboard sending event type
/*!
Returns the clipboard sending event type. This is used to send
clipboard chunks.
*/
Event::Type clipboardSending();
//@}
private:
Event::Type m_clipboardGrabbed;
Event::Type m_clipboardChanged;
Event::Type m_clipboardSending;
};

View File

@ -48,6 +48,7 @@ class ServerAppEvents;
class IKeyStateEvents; class IKeyStateEvents;
class IPrimaryScreenEvents; class IPrimaryScreenEvents;
class IScreenEvents; class IScreenEvents;
class ClipboardEvents;
//! Event queue interface //! Event queue interface
/*! /*!
@ -244,4 +245,5 @@ public:
virtual IKeyStateEvents& forIKeyState() = 0; virtual IKeyStateEvents& forIKeyState() = 0;
virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0; virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0;
virtual IScreenEvents& forIScreen() = 0; virtual IScreenEvents& forIScreen() = 0;
virtual ClipboardEvents& forClipboard() = 0;
}; };

View File

@ -207,6 +207,23 @@ removeChar(String& subject, const char c)
subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end());
} }
String
sizeTypeToString(size_t n)
{
std::stringstream ss;
ss << n;
return ss.str();
}
size_t
stringToSizeType(String string)
{
std::istringstream iss(string);
size_t value;
iss >> value;
return value;
}
// //
// CaselessCmp // CaselessCmp
// //

View File

@ -84,10 +84,21 @@ void uppercase(String& subject);
//! Remove all specific char in suject //! Remove all specific char in suject
/*! /*!
Remove all specific \c char in \c suject Remove all specific \c c in \c suject
*/ */
void removeChar(String& subject, const char c); void removeChar(String& subject, const char c);
//! Convert a size type to a string
/*!
Convert an size type to a string
*/
String sizeTypeToString(size_t n);
//! Convert a string to a size type
/*!
Convert an a \c string to an size type
*/
size_t stringToSizeType(String string);
//! Case-insensitive comparisons //! Case-insensitive comparisons
/*! /*!

View File

@ -22,12 +22,13 @@
#include "client/ServerProxy.h" #include "client/ServerProxy.h"
#include "synergy/Screen.h" #include "synergy/Screen.h"
#include "synergy/Clipboard.h" #include "synergy/Clipboard.h"
#include "synergy/FileChunk.h"
#include "synergy/DropHelper.h" #include "synergy/DropHelper.h"
#include "synergy/PacketStreamFilter.h" #include "synergy/PacketStreamFilter.h"
#include "synergy/ProtocolUtil.h" #include "synergy/ProtocolUtil.h"
#include "synergy/protocol_types.h" #include "synergy/protocol_types.h"
#include "synergy/XSynergy.h" #include "synergy/XSynergy.h"
#include "synergy/FileChunker.h" #include "synergy/StreamChunker.h"
#include "synergy/IPlatformScreen.h" #include "synergy/IPlatformScreen.h"
#include "mt/Thread.h" #include "mt/Thread.h"
#include "net/TCPSocket.h" #include "net/TCPSocket.h"
@ -78,7 +79,8 @@ Client::Client(
m_writeToDropDirThread(NULL), m_writeToDropDirThread(NULL),
m_socket(NULL), m_socket(NULL),
m_useSecureNetwork(false), m_useSecureNetwork(false),
m_args(args) m_args(args),
m_sendClipboardThread(NULL)
{ {
assert(m_socketFactory != NULL); assert(m_socketFactory != NULL);
assert(m_screen != NULL); assert(m_screen != NULL);
@ -264,13 +266,16 @@ Client::leave()
m_active = false; m_active = false;
// send clipboards that we own and that have changed if (m_sendClipboardThread != NULL) {
for (ClipboardID id = 0; id < kClipboardEnd; ++id) { StreamChunker::interruptClipboard();
if (m_ownClipboard[id]) {
sendClipboard(id);
}
} }
m_sendClipboardThread = new Thread(
new TMethodJob<Client>(
this,
&Client::sendClipboardThread,
NULL));
return true; return true;
} }
@ -422,12 +427,12 @@ Client::sendConnectionFailedEvent(const char* msg)
void void
Client::sendFileChunk(const void* data) Client::sendFileChunk(const void* data)
{ {
FileChunker::FileChunk* fileChunk = reinterpret_cast<FileChunker::FileChunk*>(const_cast<void*>(data)); FileChunk* chunk = reinterpret_cast<FileChunk*>(const_cast<void*>(data));
LOG((CLOG_DEBUG1 "sendFileChunk")); LOG((CLOG_DEBUG1 "send file chunk"));
assert(m_server != NULL); assert(m_server != NULL);
// relay // relay
m_server->fileChunkSending(fileChunk->m_chunk[0], &(fileChunk->m_chunk[1]), fileChunk->m_dataSize); m_server->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
} }
void void
@ -487,7 +492,7 @@ Client::setupScreen()
getEventTarget(), getEventTarget(),
new TMethodEventJob<Client>(this, new TMethodEventJob<Client>(this,
&Client::handleShapeChanged)); &Client::handleShapeChanged));
m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
getEventTarget(), getEventTarget(),
new TMethodEventJob<Client>(this, new TMethodEventJob<Client>(this,
&Client::handleClipboardGrabbed)); &Client::handleClipboardGrabbed));
@ -545,7 +550,7 @@ Client::cleanupScreen()
} }
m_events->removeHandler(m_events->forIScreen().shapeChanged(), m_events->removeHandler(m_events->forIScreen().shapeChanged(),
getEventTarget()); getEventTarget());
m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
getEventTarget()); getEventTarget());
delete m_server; delete m_server;
m_server = NULL; m_server = NULL;
@ -749,6 +754,17 @@ Client::onFileRecieveCompleted()
} }
} }
void
Client::sendClipboardThread(void*)
{
// send clipboards that we own and that have changed
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
if (m_ownClipboard[id]) {
sendClipboard(id);
}
}
}
void void
Client::handleStopRetry(const Event&, void*) Client::handleStopRetry(const Event&, void*)
{ {
@ -768,25 +784,6 @@ Client::writeToDropDirThread(void*)
m_receivedFileData); m_receivedFileData);
} }
void
Client::clearReceivedFileData()
{
m_receivedFileData.clear();
}
void
Client::setExpectedFileSize(String data)
{
std::istringstream iss(data);
iss >> m_expectedFileSize;
}
void
Client::fileChunkReceived(String data)
{
m_receivedFileData += data;
}
void void
Client::dragInfoReceived(UInt32 fileNum, String data) Client::dragInfoReceived(UInt32 fileNum, String data)
{ {
@ -810,6 +807,10 @@ Client::isReceivedFileSizeValid()
void void
Client::sendFileToServer(const char* filename) Client::sendFileToServer(const char* filename)
{ {
if (m_sendFileThread != NULL) {
StreamChunker::interruptFile();
}
m_sendFileThread = new Thread( m_sendFileThread = new Thread(
new TMethodJob<Client>( new TMethodJob<Client>(
this, &Client::sendFileThread, this, &Client::sendFileThread,
@ -821,7 +822,7 @@ Client::sendFileThread(void* filename)
{ {
try { try {
char* name = reinterpret_cast<char*>(filename); char* name = reinterpret_cast<char*>(filename);
FileChunker::sendFileChunks(name, m_events, this); StreamChunker::sendFile(name, m_events, this);
} }
catch (std::runtime_error error) { catch (std::runtime_error error) {
LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); LOG((CLOG_ERR "failed sending file chunks: %s", error.what()));

View File

@ -85,15 +85,6 @@ public:
*/ */
virtual void handshakeComplete(); virtual void handshakeComplete();
//! Clears the file buffer
void clearReceivedFileData();
//! Set the expected size of receiving file
void setExpectedFileSize(String data);
//! Received a chunk of file data
void fileChunkReceived(String data);
//! Received drag information //! Received drag information
void dragInfoReceived(UInt32 fileNum, String data); void dragInfoReceived(UInt32 fileNum, String data);
@ -131,7 +122,10 @@ public:
bool isReceivedFileSizeValid(); bool isReceivedFileSizeValid();
//! Return expected file size //! Return expected file size
size_t getExpectedFileSize() { return m_expectedFileSize; } size_t& getExpectedFileSize() { return m_expectedFileSize; }
//! Return received file data
String& getReceivedFileData() { return m_receivedFileData; }
//@} //@}
@ -194,6 +188,7 @@ private:
void handleFileRecieveCompleted(const Event&, void*); void handleFileRecieveCompleted(const Event&, void*);
void handleStopRetry(const Event&, void*); void handleStopRetry(const Event&, void*);
void onFileRecieveCompleted(); void onFileRecieveCompleted();
void sendClipboardThread(void*);
public: public:
bool m_mock; bool m_mock;
@ -224,4 +219,5 @@ private:
TCPSocket* m_socket; TCPSocket* m_socket;
bool m_useSecureNetwork; bool m_useSecureNetwork;
ClientArgs& m_args; ClientArgs& m_args;
Thread* m_sendClipboardThread;
}; };

View File

@ -19,6 +19,9 @@
#include "client/ServerProxy.h" #include "client/ServerProxy.h"
#include "client/Client.h" #include "client/Client.h"
#include "synergy/FileChunk.h"
#include "synergy/ClipboardChunk.h"
#include "synergy/StreamChunker.h"
#include "synergy/Clipboard.h" #include "synergy/Clipboard.h"
#include "synergy/ProtocolUtil.h" #include "synergy/ProtocolUtil.h"
#include "synergy/option_types.h" #include "synergy/option_types.h"
@ -35,8 +38,6 @@
// ServerProxy // ServerProxy
// //
const UInt16 ServerProxy::m_intervalThreshold = 1;
ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* events) : ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* events) :
m_client(client), m_client(client),
m_stream(stream), m_stream(stream),
@ -51,10 +52,7 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue*
m_keepAliveAlarm(0.0), m_keepAliveAlarm(0.0),
m_keepAliveAlarmTimer(NULL), m_keepAliveAlarmTimer(NULL),
m_parser(&ServerProxy::parseHandshakeMessage), m_parser(&ServerProxy::parseHandshakeMessage),
m_events(events), m_events(events)
m_stopwatch(true),
m_elapsedTime(0),
m_receivedDataSize(0)
{ {
assert(m_client != NULL); assert(m_client != NULL);
assert(m_stream != NULL); assert(m_stream != NULL);
@ -69,6 +67,11 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue*
new TMethodEventJob<ServerProxy>(this, new TMethodEventJob<ServerProxy>(this,
&ServerProxy::handleData)); &ServerProxy::handleData));
m_events->adoptHandler(m_events->forClipboard().clipboardSending(),
this,
new TMethodEventJob<ServerProxy>(this,
&ServerProxy::handleClipboardSendingEvent));
// send heartbeat // send heartbeat
setKeepAliveRate(kKeepAliveRate); setKeepAliveRate(kKeepAliveRate);
} }
@ -358,8 +361,11 @@ void
ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard)
{ {
String data = IClipboard::marshall(clipboard); String data = IClipboard::marshall(clipboard);
LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size())); LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum));
ProtocolUtil::writef(m_stream, kMsgDClipboard, id, m_seqNum, &data);
StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this);
LOG((CLOG_DEBUG "sent clipboard size=%d", data.size()));
} }
void void
@ -545,22 +551,21 @@ void
ServerProxy::setClipboard() ServerProxy::setClipboard()
{ {
// parse // parse
static String dataCached;
ClipboardID id; ClipboardID id;
UInt32 seqNum; UInt32 seq;
String data;
ProtocolUtil::readf(m_stream, kMsgDClipboard + 4, &id, &seqNum, &data);
LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size()));
// validate int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq);
if (id >= kClipboardEnd) {
return; if (r == kFinish) {
} LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size()));
// forward // forward
Clipboard clipboard; Clipboard clipboard;
clipboard.unmarshall(data, 0); clipboard.unmarshall(dataCached, 0);
m_client->setClipboard(id, &clipboard); m_client->setClipboard(id, &clipboard);
} }
}
void void
ServerProxy::grabClipboard() ServerProxy::grabClipboard()
@ -851,50 +856,13 @@ ServerProxy::infoAcknowledgment()
void void
ServerProxy::fileChunkReceived() ServerProxy::fileChunkReceived()
{ {
// parse int result = FileChunk::assemble(
UInt8 mark = 0; m_stream,
String content; m_client->getReceivedFileData(),
ProtocolUtil::readf(m_stream, kMsgDFileTransfer + 4, &mark, &content); m_client->getExpectedFileSize());
switch (mark) { if (result == kFinish) {
case kFileStart:
m_client->clearReceivedFileData();
m_client->setExpectedFileSize(content);
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from server: size=%s", content.c_str()));
m_stopwatch.start();
}
break;
case kFileChunk:
m_client->fileChunkReceived(content);
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from server: size=%i", content.size()));
double interval = m_stopwatch.getTime();
LOG((CLOG_DEBUG2 "recv file data from server: interval=%f s", interval));
m_receivedDataSize += content.size();
if (interval >= m_intervalThreshold) {
double averageSpeed = m_receivedDataSize / interval / 1000;
LOG((CLOG_DEBUG2 "recv file data from server: average speed=%f kb/s", averageSpeed));
m_receivedDataSize = 0;
m_elapsedTime += interval;
m_stopwatch.reset();
}
}
break;
case kFileEnd:
m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), m_client)); m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), m_client));
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "file data transfer finished"));
m_elapsedTime += m_stopwatch.getTime();
double averageSpeed = m_client->getExpectedFileSize() / m_elapsedTime / 1000;
LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", m_elapsedTime));
LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", m_client->getExpectedFileSize() / 1000));
LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed));
}
break;
} }
} }
@ -910,25 +878,15 @@ ServerProxy::dragInfoReceived()
} }
void void
ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) ServerProxy::handleClipboardSendingEvent(const Event& event, void*)
{ {
String chunk(data, dataSize); ClipboardChunk::send(m_stream, event.getData());
switch (mark) {
case kFileStart:
LOG((CLOG_DEBUG2 "file sending start: size=%s", data));
break;
case kFileChunk:
LOG((CLOG_DEBUG2 "file chunk sending: size=%i", chunk.size()));
break;
case kFileEnd:
LOG((CLOG_DEBUG2 "file sending finished"));
break;
} }
ProtocolUtil::writef(m_stream, kMsgDFileTransfer, mark, &chunk); void
ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
{
FileChunk::send(m_stream, mark, data, dataSize);
} }
void void

View File

@ -106,6 +106,7 @@ private:
void infoAcknowledgment(); void infoAcknowledgment();
void fileChunkReceived(); void fileChunkReceived();
void dragInfoReceived(); void dragInfoReceived();
void handleClipboardSendingEvent(const Event&, void*);
private: private:
typedef EResult (ServerProxy::*MessageParser)(const UInt8*); typedef EResult (ServerProxy::*MessageParser)(const UInt8*);
@ -129,9 +130,4 @@ private:
MessageParser m_parser; MessageParser m_parser;
IEventQueue* m_events; IEventQueue* m_events;
Stopwatch m_stopwatch;
double m_elapsedTime;
size_t m_receivedDataSize;
static const UInt16 m_intervalThreshold;
}; };

View File

@ -433,8 +433,8 @@ MSWindowsScreen::checkClipboards()
if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) { if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) {
LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received"));
m_ownClipboard = false; m_ownClipboard = false;
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
} }
} }
@ -1501,8 +1501,8 @@ MSWindowsScreen::onClipboardChange()
if (m_ownClipboard) { if (m_ownClipboard) {
LOG((CLOG_DEBUG "clipboard changed: lost ownership")); LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
m_ownClipboard = false; m_ownClipboard = false;
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
} }
} }
else if (!m_ownClipboard) { else if (!m_ownClipboard) {

View File

@ -954,8 +954,8 @@ OSXScreen::checkClipboards()
LOG((CLOG_DEBUG2 "checking clipboard")); LOG((CLOG_DEBUG2 "checking clipboard"));
if (m_pasteboard.synchronize()) { if (m_pasteboard.synchronize()) {
LOG((CLOG_DEBUG "clipboard changed")); LOG((CLOG_DEBUG "clipboard changed"));
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
} }
} }

View File

@ -1325,7 +1325,7 @@ XWindowsScreen::handleSystemEvent(const Event& event, void*)
if (id != kClipboardEnd) { if (id != kClipboardEnd) {
LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time));
m_clipboard[id]->lost(xevent->xselectionclear.time); m_clipboard[id]->lost(xevent->xselectionclear.time);
sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), id); sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), id);
return; return;
} }
} }

View File

@ -19,6 +19,7 @@ if (WIN32)
endif() endif()
if (APPLE) if (APPLE)
# 10.7 should be supported, but gives is a _NXArgv linker error
if (OSX_TARGET_MINOR GREATER 7) if (OSX_TARGET_MINOR GREATER 7)
add_subdirectory(ns) add_subdirectory(ns)
endif() endif()

View File

@ -85,18 +85,20 @@ if (WIN32)
..\\..\\..\\..\\..\\ext\\${OPENSSL_PLAT_DIR}\\out32dll\\ssleay32.* ..\\..\\..\\..\\..\\ext\\${OPENSSL_PLAT_DIR}\\out32dll\\ssleay32.*
..\\..\\..\\..\\..\\bin\\${CMAKE_CFG_INTDIR} ..\\..\\..\\..\\..\\bin\\${CMAKE_CFG_INTDIR}
) )
else() endif()
if (UNIX)
if (APPLE) if (APPLE)
add_custom_command( add_custom_command(
TARGET ns TARGET ns
POST_BUILD POST_BUILD
COMMAND COMMAND
mkdir -p mkdir -p
${CMAKE_SOURCE_DIR}/bin/plugins ${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins
&& &&
cp cp
${CMAKE_SOURCE_DIR}/lib/${CMAKE_CFG_INTDIR}/libns.* ${CMAKE_SOURCE_DIR}/lib/${CMAKE_CFG_INTDIR}/libns.*
${CMAKE_SOURCE_DIR}/bin/plugins/ ${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins/
) )
else() else()
if (CMAKE_BUILD_TYPE STREQUAL Debug) if (CMAKE_BUILD_TYPE STREQUAL Debug)
@ -104,11 +106,11 @@ else()
TARGET ns TARGET ns
POST_BUILD POST_BUILD
COMMAND mkdir -p COMMAND mkdir -p
${CMAKE_SOURCE_DIR}/bin/plugins ${CMAKE_SOURCE_DIR}/bin/debug/plugins
&& &&
cp cp
${CMAKE_SOURCE_DIR}/lib/debug/libns.* ${CMAKE_SOURCE_DIR}/lib/debug/libns.*
${CMAKE_SOURCE_DIR}/bin/plugins/ ${CMAKE_SOURCE_DIR}/bin/debug/plugins/
) )
else() else()
add_custom_command( add_custom_command(

View File

@ -35,7 +35,12 @@
// //
#define MAX_ERROR_SIZE 65535 #define MAX_ERROR_SIZE 65535
#define MAX_RETRY_COUNT 60
enum {
// this limit seems extremely high, but mac client seem to generate around
// 50,000 errors before they establish a connection (wtf?)
kMaxRetryCount = 100000
};
static const char kFingerprintDirName[] = "SSL/Fingerprints"; static const char kFingerprintDirName[] = "SSL/Fingerprints";
//static const char kFingerprintLocalFilename[] = "Local.txt"; //static const char kFingerprintLocalFilename[] = "Local.txt";
@ -52,8 +57,8 @@ SecureSocket::SecureSocket(
SocketMultiplexer* socketMultiplexer) : SocketMultiplexer* socketMultiplexer) :
TCPSocket(events, socketMultiplexer), TCPSocket(events, socketMultiplexer),
m_secureReady(false), m_secureReady(false),
m_maxRetry(MAX_RETRY_COUNT), m_fatal(false),
m_fatal(false) m_maxRetry(kMaxRetryCount)
{ {
} }
@ -63,8 +68,8 @@ SecureSocket::SecureSocket(
ArchSocket socket) : ArchSocket socket) :
TCPSocket(events, socketMultiplexer, socket), TCPSocket(events, socketMultiplexer, socket),
m_secureReady(false), m_secureReady(false),
m_maxRetry(MAX_RETRY_COUNT), m_fatal(false),
m_fatal(false) m_maxRetry(kMaxRetryCount)
{ {
} }
@ -392,31 +397,25 @@ SecureSocket::checkResult(int status, int& retry)
case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_ZERO_RETURN:
// connection closed // connection closed
isFatal(true); isFatal(true);
LOG((CLOG_DEBUG "SSL connection has been closed")); LOG((CLOG_DEBUG "ssl connection closed"));
break; break;
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_ACCEPT:
retry += 1; // it seems like these sort of errors are part of openssl's normal behavior,
// If there are a lot of retrys, it's worth warning about // so we should expect a very high amount of these. sleeping doesn't seem to
if ( retry % 5 == 0 ) { // help... maybe you just have to swallow the errors (yuck).
LOG((CLOG_DEBUG "need to retry the same SSL function(%d) retry:%d", errorCode, retry)); retry++;
} LOG((CLOG_DEBUG2 "passive ssl error, error=%d, attempt=%d", errorCode, retry));
else if ( retry == (maxRetry() / 2) ) {
LOG((CLOG_WARN "need to retry the same SSL function(%d) retry:%d", errorCode, retry));
}
else {
LOG((CLOG_DEBUG2 "need to retry the same SSL function(%d) retry:%d", errorCode, retry));
}
break; break;
case SSL_ERROR_SYSCALL: case SSL_ERROR_SYSCALL:
LOG((CLOG_ERR "some secure socket I/O error occurred")); LOG((CLOG_ERR "ssl error occurred (system call failure)"));
if (ERR_peek_error() == 0) { if (ERR_peek_error() == 0) {
if (status == 0) { if (status == 0) {
LOG((CLOG_ERR "an EOF violates the protocol")); LOG((CLOG_ERR "eof violates ssl protocol"));
} }
else if (status == -1) { else if (status == -1) {
// underlying socket I/O reproted an error // underlying socket I/O reproted an error
@ -433,19 +432,19 @@ SecureSocket::checkResult(int status, int& retry)
break; break;
case SSL_ERROR_SSL: case SSL_ERROR_SSL:
LOG((CLOG_ERR "a failure in the SSL library occurred")); LOG((CLOG_ERR "ssl error occurred (generic failure)"));
isFatal(true); isFatal(true);
break; break;
default: default:
LOG((CLOG_ERR "unknown secure socket error")); LOG((CLOG_ERR "ssl error occurred (unknown failure)"));
isFatal(true); isFatal(true);
break; break;
} }
// If the retry max would exceed the allowed, treat it as a fatal error // If the retry max would exceed the allowed, treat it as a fatal error
if (retry > maxRetry()) { if (retry > maxRetry()) {
LOG((CLOG_ERR "Maximum retry count exceeded:%d",retry)); LOG((CLOG_ERR "passive ssl error limit exceeded: %d", retry));
isFatal(true); isFatal(true);
} }

View File

@ -64,6 +64,9 @@ public:
//! Get server which owns this listener //! Get server which owns this listener
Server* getServer() { return m_server; } Server* getServer() { return m_server; }
//! Return true if using secure network connection
bool isSecure() { return m_useSecureNetwork; }
//@} //@}
private: private:

View File

@ -275,16 +275,7 @@ ClientProxy1_0::leave()
void void
ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard)
{ {
// ignore if this clipboard is already clean // ignore -- deprecated in protocol 1.0
if (m_clipboard[id].m_dirty) {
// this clipboard is now clean
m_clipboard[id].m_dirty = false;
Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard);
String data = m_clipboard[id].m_clipboard.marshall();
LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size()));
ProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data);
}
} }
void void
@ -450,34 +441,9 @@ ClientProxy1_0::recvInfo()
bool bool
ClientProxy1_0::recvClipboard() ClientProxy1_0::recvClipboard()
{ {
// parse message // deprecated in protocol 1.0
ClipboardID id;
UInt32 seqNum;
String data;
if (!ProtocolUtil::readf(getStream(),
kMsgDClipboard + 4, &id, &seqNum, &data)) {
return false; return false;
} }
LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size()));
// validate
if (id >= kClipboardEnd) {
return false;
}
// save clipboard
m_clipboard[id].m_clipboard.unmarshall(data, 0);
m_clipboard[id].m_sequenceNumber = seqNum;
// notify
ClipboardInfo* info = new ClipboardInfo;
info->m_id = id;
info->m_sequenceNumber = seqNum;
m_events->addEvent(Event(m_events->forClientProxy().clipboardChanged(),
getEventTarget(), info));
return true;
}
bool bool
ClientProxy1_0::recvGrabClipboard() ClientProxy1_0::recvGrabClipboard()
@ -499,13 +465,12 @@ ClientProxy1_0::recvGrabClipboard()
ClipboardInfo* info = new ClipboardInfo; ClipboardInfo* info = new ClipboardInfo;
info->m_id = id; info->m_id = id;
info->m_sequenceNumber = seqNum; info->m_sequenceNumber = seqNum;
m_events->addEvent(Event(m_events->forIScreen().clipboardGrabbed(), m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(),
getEventTarget(), info)); getEventTarget(), info));
return true; return true;
} }
// //
// ClientProxy1_0::ClientClipboard // ClientProxy1_0::ClientClipboard
// //

View File

@ -70,7 +70,7 @@ protected:
virtual void resetHeartbeatTimer(); virtual void resetHeartbeatTimer();
virtual void addHeartbeatTimer(); virtual void addHeartbeatTimer();
virtual void removeHeartbeatTimer(); virtual void removeHeartbeatTimer();
virtual bool recvClipboard();
private: private:
void disconnect(); void disconnect();
void removeHandlers(); void removeHandlers();
@ -81,11 +81,9 @@ private:
void handleFlatline(const Event&, void*); void handleFlatline(const Event&, void*);
bool recvInfo(); bool recvInfo();
bool recvClipboard();
bool recvGrabClipboard(); bool recvGrabClipboard();
private: protected:
typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*);
struct ClientClipboard { struct ClientClipboard {
public: public:
ClientClipboard(); ClientClipboard();
@ -96,8 +94,12 @@ private:
bool m_dirty; bool m_dirty;
}; };
ClientInfo m_info;
ClientClipboard m_clipboard[kClipboardEnd]; ClientClipboard m_clipboard[kClipboardEnd];
private:
typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*);
ClientInfo m_info;
double m_heartbeatAlarm; double m_heartbeatAlarm;
EventQueueTimer* m_heartbeatTimer; EventQueueTimer* m_heartbeatTimer;
MessageParser m_parser; MessageParser m_parser;

View File

@ -18,22 +18,21 @@
#include "server/ClientProxy1_5.h" #include "server/ClientProxy1_5.h"
#include "server/Server.h" #include "server/Server.h"
#include "synergy/FileChunk.h"
#include "synergy/StreamChunker.h"
#include "synergy/ProtocolUtil.h" #include "synergy/ProtocolUtil.h"
#include "io/IStream.h" #include "io/IStream.h"
#include "base/Log.h" #include "base/Log.h"
#include <sstream>
// //
// ClientProxy1_5 // ClientProxy1_5
// //
const UInt16 ClientProxy1_5::m_intervalThreshold = 1;
ClientProxy1_5::ClientProxy1_5(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) : ClientProxy1_5::ClientProxy1_5(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) :
ClientProxy1_4(name, stream, server, events), ClientProxy1_4(name, stream, server, events),
m_events(events), m_events(events)
m_stopwatch(true),
m_elapsedTime(0),
m_receivedDataSize(0)
{ {
} }
@ -52,23 +51,7 @@ ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
void void
ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize) ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
{ {
String chunk(data, dataSize); FileChunk::send(getStream(), mark, data, dataSize);
switch (mark) {
case kFileStart:
LOG((CLOG_DEBUG2 "file sending start: size=%s", data));
break;
case kFileChunk:
LOG((CLOG_DEBUG2 "file chunk sending: size=%i", chunk.size()));
break;
case kFileEnd:
LOG((CLOG_DEBUG2 "file sending finished"));
break;
}
ProtocolUtil::writef(getStream(), kMsgDFileTransfer, mark, &chunk);
} }
bool bool
@ -90,51 +73,15 @@ ClientProxy1_5::parseMessage(const UInt8* code)
void void
ClientProxy1_5::fileChunkReceived() ClientProxy1_5::fileChunkReceived()
{ {
// parse
UInt8 mark = 0;
String content;
ProtocolUtil::readf(getStream(), kMsgDFileTransfer + 4, &mark, &content);
Server* server = getServer(); Server* server = getServer();
switch (mark) { int result = FileChunk::assemble(
case kFileStart: getStream(),
server->clearReceivedFileData(); server->getReceivedFileData(),
server->setExpectedFileSize(content); server->getExpectedFileSize());
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from client: file size=%s", content.c_str()));
m_stopwatch.start();
}
break;
case kFileChunk:
server->fileChunkReceived(content);
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from client: chunck size=%i", content.size()));
double interval = m_stopwatch.getTime();
m_receivedDataSize += content.size();
LOG((CLOG_DEBUG2 "recv file data from client: interval=%f s", interval));
if (interval >= m_intervalThreshold) {
double averageSpeed = m_receivedDataSize / interval / 1000;
LOG((CLOG_DEBUG2 "recv file data from client: average speed=%f kb/s", averageSpeed));
m_receivedDataSize = 0; if (result == kFinish) {
m_elapsedTime += interval;
m_stopwatch.reset();
}
}
break;
case kFileEnd:
m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server)); m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server));
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "file data transfer finished"));
m_elapsedTime += m_stopwatch.getTime();
double averageSpeed = getServer()->getExpectedFileSize() / m_elapsedTime / 1000;
LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", m_elapsedTime));
LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", getServer()->getExpectedFileSize() / 1000));
LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed));
}
break;
} }
} }

View File

@ -19,6 +19,7 @@
#include "server/ClientProxy1_4.h" #include "server/ClientProxy1_4.h"
#include "base/Stopwatch.h" #include "base/Stopwatch.h"
#include "common/stdvector.h"
class Server; class Server;
class IEventQueue; class IEventQueue;
@ -37,9 +38,4 @@ public:
private: private:
IEventQueue* m_events; IEventQueue* m_events;
Stopwatch m_stopwatch;
double m_elapsedTime;
size_t m_receivedDataSize;
static const UInt16 m_intervalThreshold;
}; };

View File

@ -0,0 +1,110 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "server/ClientProxy1_6.h"
#include "server/Server.h"
#include "synergy/ProtocolUtil.h"
#include "synergy/StreamChunker.h"
#include "synergy/ClipboardChunk.h"
#include "io/IStream.h"
#include "base/TMethodEventJob.h"
#include "base/Log.h"
//
// ClientProxy1_6
//
enum
{
kSslClipboardMaxSize = 1024
};
ClientProxy1_6::ClientProxy1_6(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) :
ClientProxy1_5(name, stream, server, events),
m_events(events)
{
m_events->adoptHandler(m_events->forClipboard().clipboardSending(),
this,
new TMethodEventJob<ClientProxy1_6>(this,
&ClientProxy1_6::handleClipboardSendingEvent));
}
ClientProxy1_6::~ClientProxy1_6()
{
}
void
ClientProxy1_6::setClipboard(ClipboardID id, const IClipboard* clipboard)
{
// ignore if this clipboard is already clean
if (m_clipboard[id].m_dirty) {
// this clipboard is now clean
m_clipboard[id].m_dirty = false;
Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard);
String data = m_clipboard[id].m_clipboard.marshall();
size_t size = data.size();
LOG((CLOG_DEBUG "sending clipboard %d to \"%s\"", id, getName().c_str()));
// HACK: if using SSL, don't send large clipboards (#4601)
bool send = true;
if (getServer()->isSecure() && (size > kSslClipboardMaxSize)) {
send = false;
LOG((CLOG_WARN "large clipboards not supported with ssl, size=%d", size));
}
if (send) {
StreamChunker::sendClipboard(data, size, id, 0, m_events, this);
LOG((CLOG_DEBUG "sent clipboard size=%d", size));
}
}
}
void
ClientProxy1_6::handleClipboardSendingEvent(const Event& event, void*)
{
ClipboardChunk::send(getStream(), event.getData());
}
bool
ClientProxy1_6::recvClipboard()
{
// parse message
static String dataCached;
ClipboardID id;
UInt32 seq;
int r = ClipboardChunk::assemble(getStream(), dataCached, id, seq);
if (r == kFinish) {
LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seq, dataCached.size()));
// save clipboard
m_clipboard[id].m_clipboard.unmarshall(dataCached, 0);
m_clipboard[id].m_sequenceNumber = seq;
// notify
ClipboardInfo* info = new ClipboardInfo;
info->m_id = id;
info->m_sequenceNumber = seq;
m_events->addEvent(Event(m_events->forClipboard().clipboardChanged(),
getEventTarget(), info));
}
return true;
}

View File

@ -1,6 +1,6 @@
/* /*
* synergy -- mouse and keyboard sharing utility * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2013 Synergy Si Ltd. * Copyright (C) 2015 Synergy Si Inc.
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -17,30 +17,23 @@
#pragma once #pragma once
#include "base/String.h" #include "server/ClientProxy1_5.h"
class Server;
class IEventQueue; class IEventQueue;
class FileChunker { //! Proxy for client implementing protocol version 1.6
class ClientProxy1_6 : public ClientProxy1_5 {
public: public:
//! FileChunk data ClientProxy1_6(const String& name, synergy::IStream* adoptedStream, Server* server, IEventQueue* events);
class FileChunk { ~ClientProxy1_6();
public:
FileChunk(size_t chunkSize) : m_dataSize(chunkSize - 2)
{
m_chunk = new char[chunkSize];
}
~FileChunk() { delete[] m_chunk; } virtual void setClipboard(ClipboardID id, const IClipboard* clipboard);
virtual bool recvClipboard();
public:
const size_t m_dataSize;
char* m_chunk;
};
static void sendFileChunks(char* filename, IEventQueue* events, void* eventTarget);
static String intToString(size_t i);
private: private:
static const size_t m_chunkSize; void handleClipboardSendingEvent(const Event&, void*);
private:
IEventQueue* m_events;
}; };

View File

@ -25,6 +25,7 @@
#include "server/ClientProxy1_3.h" #include "server/ClientProxy1_3.h"
#include "server/ClientProxy1_4.h" #include "server/ClientProxy1_4.h"
#include "server/ClientProxy1_5.h" #include "server/ClientProxy1_5.h"
#include "server/ClientProxy1_6.h"
#include "synergy/protocol_types.h" #include "synergy/protocol_types.h"
#include "synergy/ProtocolUtil.h" #include "synergy/ProtocolUtil.h"
#include "synergy/XSynergy.h" #include "synergy/XSynergy.h"
@ -227,6 +228,10 @@ ClientProxyUnknown::handleData(const Event&, void*)
case 5: case 5:
m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events);
break; break;
case 6:
m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events);
break;
} }
} }

View File

@ -22,13 +22,14 @@
#include "server/ClientProxyUnknown.h" #include "server/ClientProxyUnknown.h"
#include "server/PrimaryClient.h" #include "server/PrimaryClient.h"
#include "server/ClientListener.h" #include "server/ClientListener.h"
#include "synergy/FileChunk.h"
#include "synergy/IPlatformScreen.h" #include "synergy/IPlatformScreen.h"
#include "synergy/DropHelper.h" #include "synergy/DropHelper.h"
#include "synergy/option_types.h" #include "synergy/option_types.h"
#include "synergy/protocol_types.h" #include "synergy/protocol_types.h"
#include "synergy/XScreen.h" #include "synergy/XScreen.h"
#include "synergy/XSynergy.h" #include "synergy/XSynergy.h"
#include "synergy/FileChunker.h" #include "synergy/StreamChunker.h"
#include "synergy/KeyState.h" #include "synergy/KeyState.h"
#include "synergy/Screen.h" #include "synergy/Screen.h"
#include "synergy/PacketStreamFilter.h" #include "synergy/PacketStreamFilter.h"
@ -91,7 +92,8 @@ Server::Server(
m_ignoreFileTransfer(false), m_ignoreFileTransfer(false),
m_enableDragDrop(enableDragDrop), m_enableDragDrop(enableDragDrop),
m_getDragInfoThread(NULL), m_getDragInfoThread(NULL),
m_waitDragInfoThread(true) m_waitDragInfoThread(true),
m_sendClipboardThread(NULL)
{ {
// must have a primary client and it must have a canonical name // must have a primary client and it must have a canonical name
assert(m_primaryClient != NULL); assert(m_primaryClient != NULL);
@ -503,11 +505,18 @@ Server::switchScreen(BaseClientProxy* dst,
m_active->enter(x, y, m_seqNum, m_active->enter(x, y, m_seqNum,
m_primaryClient->getToggleMask(), m_primaryClient->getToggleMask(),
forScreensaver); forScreensaver);
// if already sending clipboard, we need to interupt it, otherwise
// clipboard data could be corrupted on the other side
if (m_sendClipboardThread != NULL) {
StreamChunker::interruptClipboard();
}
// send the clipboard data to new active screen // send the clipboard data to new active screen
for (ClipboardID id = 0; id < kClipboardEnd; ++id) { m_sendClipboardThread = new Thread(
m_active->setClipboard(id, &m_clipboards[id].m_clipboard); new TMethodJob<Server>(
} this,
&Server::sendClipboardThread,
NULL));
Server::SwitchToScreenInfo* info = Server::SwitchToScreenInfo* info =
Server::SwitchToScreenInfo::alloc(m_active->getName()); Server::SwitchToScreenInfo::alloc(m_active->getName());
@ -1849,6 +1858,14 @@ Server::sendDragInfo(BaseClientProxy* newScreen)
} }
} }
void
Server::sendClipboardThread(void*)
{
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
}
}
void void
Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy) Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
{ {
@ -2023,13 +2040,13 @@ Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
void void
Server::onFileChunkSending(const void* data) Server::onFileChunkSending(const void* data)
{ {
FileChunker::FileChunk* fileChunk = reinterpret_cast<FileChunker::FileChunk*>(const_cast<void*>(data)); FileChunk* chunk = reinterpret_cast<FileChunk*>(const_cast<void*>(data));
LOG((CLOG_DEBUG1 "onFileChunkSending")); LOG((CLOG_DEBUG1 "sending file chunk"));
assert(m_active != NULL); assert(m_active != NULL);
// relay // relay
m_active->fileChunkSending(fileChunk->m_chunk[0], &(fileChunk->m_chunk[1]), fileChunk->m_dataSize); m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
} }
void void
@ -2068,11 +2085,11 @@ Server::addClient(BaseClientProxy* client)
client->getEventTarget(), client->getEventTarget(),
new TMethodEventJob<Server>(this, new TMethodEventJob<Server>(this,
&Server::handleShapeChanged, client)); &Server::handleShapeChanged, client));
m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
client->getEventTarget(), client->getEventTarget(),
new TMethodEventJob<Server>(this, new TMethodEventJob<Server>(this,
&Server::handleClipboardGrabbed, client)); &Server::handleClipboardGrabbed, client));
m_events->adoptHandler(m_events->forClientProxy().clipboardChanged(), m_events->adoptHandler(m_events->forClipboard().clipboardChanged(),
client->getEventTarget(), client->getEventTarget(),
new TMethodEventJob<Server>(this, new TMethodEventJob<Server>(this,
&Server::handleClipboardChanged, client)); &Server::handleClipboardChanged, client));
@ -2104,9 +2121,9 @@ Server::removeClient(BaseClientProxy* client)
// remove event handlers // remove event handlers
m_events->removeHandler(m_events->forIScreen().shapeChanged(), m_events->removeHandler(m_events->forIScreen().shapeChanged(),
client->getEventTarget()); client->getEventTarget());
m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
client->getEventTarget()); client->getEventTarget());
m_events->removeHandler(m_events->forClientProxy().clipboardChanged(), m_events->removeHandler(m_events->forClipboard().clipboardChanged(),
client->getEventTarget()); client->getEventTarget());
// remove from list // remove from list
@ -2326,25 +2343,6 @@ Server::KeyboardBroadcastInfo::alloc(State state, const String& screens)
return info; return info;
} }
void
Server::clearReceivedFileData()
{
m_receivedFileData.clear();
}
void
Server::setExpectedFileSize(String data)
{
std::istringstream iss(data);
iss >> m_expectedFileSize;
}
void
Server::fileChunkReceived(String data)
{
m_receivedFileData += data;
}
bool bool
Server::isReceivedFileSizeValid() Server::isReceivedFileSizeValid()
{ {
@ -2354,6 +2352,10 @@ Server::isReceivedFileSizeValid()
void void
Server::sendFileToClient(const char* filename) Server::sendFileToClient(const char* filename)
{ {
if (m_sendFileThread != NULL) {
StreamChunker::interruptFile();
}
m_sendFileThread = new Thread( m_sendFileThread = new Thread(
new TMethodJob<Server>( new TMethodJob<Server>(
this, &Server::sendFileThread, this, &Server::sendFileThread,
@ -2366,7 +2368,7 @@ Server::sendFileThread(void* data)
try { try {
char* filename = reinterpret_cast<char*>(data); char* filename = reinterpret_cast<char*>(data);
LOG((CLOG_DEBUG "sending file to client, filename=%s", filename)); LOG((CLOG_DEBUG "sending file to client, filename=%s", filename));
FileChunker::sendFileChunks(filename, m_events, this); StreamChunker::sendFile(filename, m_events, this);
} }
catch (std::runtime_error error) { catch (std::runtime_error error) {
LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what())); LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what()));
@ -2387,3 +2389,9 @@ Server::dragInfoReceived(UInt32 fileNum, String content)
m_screen->startDraggingFiles(m_dragFileList); m_screen->startDraggingFiles(m_dragFileList);
} }
bool
Server::isSecure() const
{
return m_clientListener->isSecure();
}

View File

@ -141,15 +141,6 @@ public:
*/ */
void disconnect(); void disconnect();
//! Clears the file buffer
void clearReceivedFileData();
//! Set the expected size of receiving file
void setExpectedFileSize(String data);
//! Received a chunk of file data
void fileChunkReceived(String data);
//! Create a new thread and use it to send file to client //! Create a new thread and use it to send file to client
void sendFileToClient(const char* filename); void sendFileToClient(const char* filename);
@ -178,8 +169,14 @@ public:
//! Return true if recieved file size is valid //! Return true if recieved file size is valid
bool isReceivedFileSizeValid(); bool isReceivedFileSizeValid();
//! Return expected file size //! Return expected file data size
size_t getExpectedFileSize() { return m_expectedFileSize; } size_t& getExpectedFileSize() { return m_expectedFileSize; }
//! Return received file data
String& getReceivedFileData() { return m_receivedFileData; }
//! Return true if using secure network connection
bool isSecure() const;
//@} //@}
@ -370,6 +367,9 @@ private:
// send drag info to new client screen // send drag info to new client screen
void sendDragInfo(BaseClientProxy* newScreen); void sendDragInfo(BaseClientProxy* newScreen);
// thread funciton for sending clipboard
void sendClipboardThread(void*);
public: public:
bool m_mock; bool m_mock;
@ -480,4 +480,6 @@ private:
bool m_waitDragInfoThread; bool m_waitDragInfoThread;
ClientListener* m_clientListener; ClientListener* m_clientListener;
Thread* m_sendClipboardThread;
}; };

View File

@ -17,6 +17,7 @@
#include "synergy/ArgParser.h" #include "synergy/ArgParser.h"
#include "synergy/StreamChunker.h"
#include "synergy/App.h" #include "synergy/App.h"
#include "synergy/ServerArgs.h" #include "synergy/ServerArgs.h"
#include "synergy/ClientArgs.h" #include "synergy/ClientArgs.h"
@ -288,6 +289,7 @@ ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i)
} }
else if (isArg(i, argc, argv, NULL, "--enable-crypto")) { else if (isArg(i, argc, argv, NULL, "--enable-crypto")) {
argsBase().m_enableCrypto = true; argsBase().m_enableCrypto = true;
StreamChunker::updateChunkSize(true);
} }
else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) { else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) {
argsBase().m_profileDirectory = argv[++i]; argsBase().m_profileDirectory = argv[++i];

30
src/lib/synergy/Chunk.cpp Normal file
View File

@ -0,0 +1,30 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/Chunk.h"
#include "base/String.h"
Chunk::Chunk(size_t size)
{
m_chunk = new char[size];
memset(m_chunk, 0, size);
}
Chunk::~Chunk()
{
delete[] m_chunk;
}

30
src/lib/synergy/Chunk.h Normal file
View File

@ -0,0 +1,30 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "common/basic_types.h"
class Chunk {
public:
Chunk(size_t size);
~Chunk();
public:
size_t m_dataSize;
char* m_chunk;
};

View File

@ -0,0 +1,154 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/ClipboardChunk.h"
#include "synergy/ProtocolUtil.h"
#include "synergy/protocol_types.h"
#include "io/IStream.h"
#include "base/Log.h"
ClipboardChunk::ClipboardChunk(size_t size) :
Chunk(size)
{
m_dataSize = size - CLIPBOARD_CHUNK_META_SIZE;
}
ClipboardChunk*
ClipboardChunk::start(
ClipboardID id,
UInt32 sequence,
const String& size)
{
size_t sizeLength = size.size();
ClipboardChunk* start = new ClipboardChunk(sizeLength + CLIPBOARD_CHUNK_META_SIZE);
char* chunk = start->m_chunk;
chunk[0] = id;
UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]);
*seq = sequence;
chunk[5] = kDataStart;
memcpy(&chunk[6], size.c_str(), sizeLength);
chunk[sizeLength + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
return start;
}
ClipboardChunk*
ClipboardChunk::data(
ClipboardID id,
UInt32 sequence,
const String& data)
{
size_t dataSize = data.size();
ClipboardChunk* chunk = new ClipboardChunk(dataSize + CLIPBOARD_CHUNK_META_SIZE);
char* chunkData = chunk->m_chunk;
chunkData[0] = id;
UInt32* seq = reinterpret_cast<UInt32*>(&chunkData[1]);
*seq = sequence;
chunkData[5] = kDataChunk;
memcpy(&chunkData[6], data.c_str(), dataSize);
chunkData[dataSize + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
return chunk;
}
ClipboardChunk*
ClipboardChunk::end(ClipboardID id, UInt32 sequence)
{
ClipboardChunk* end = new ClipboardChunk(CLIPBOARD_CHUNK_META_SIZE);
char* chunk = end->m_chunk;
chunk[0] = id;
UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]);
*seq = sequence;
chunk[5] = kDataEnd;
chunk[CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
return end;
}
int
ClipboardChunk::assemble(synergy::IStream* stream,
String& dataCached,
ClipboardID& id,
UInt32& sequence)
{
static size_t expectedSize;
UInt8 mark;
String data;
if (!ProtocolUtil::readf(stream, kMsgDClipboard + 4, &id, &sequence, &mark, &data)) {
return kError;
}
if (mark == kDataStart) {
expectedSize = synergy::string::stringToSizeType(data);
LOG((CLOG_DEBUG "start receiving clipboard data"));
dataCached.clear();
return kNotFinish;
}
else if (mark == kDataChunk) {
dataCached.append(data);
return kNotFinish;
}
else if (mark == kDataEnd) {
// validate
if (id >= kClipboardEnd) {
return kError;
}
else if (expectedSize != dataCached.size()) {
LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", expectedSize, dataCached.size()));
return kError;
}
return kFinish;
}
return kError;
}
void
ClipboardChunk::send(synergy::IStream* stream, void* data)
{
ClipboardChunk* clipboardData = reinterpret_cast<ClipboardChunk*>(data);
LOG((CLOG_DEBUG1 "sending clipboard chunk"));
char* chunk = clipboardData->m_chunk;
ClipboardID id = chunk[0];
UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]);
UInt32 sequence = *seq;
UInt8 mark = chunk[5];
String dataChunk(&chunk[6], clipboardData->m_dataSize);
switch (mark) {
case kDataStart:
LOG((CLOG_DEBUG2 "sending clipboard chunk start: size=%s", dataChunk.c_str()));
break;
case kDataChunk:
LOG((CLOG_DEBUG2 "sending clipboard chunk data: size=%i", dataChunk.size()));
break;
case kDataEnd:
LOG((CLOG_DEBUG2 "sending clipboard finished"));
break;
}
ProtocolUtil::writef(stream, kMsgDClipboard, id, sequence, mark, &dataChunk);
}

View File

@ -0,0 +1,55 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/Chunk.h"
#include "synergy/clipboard_types.h"
#include "base/String.h"
#include "common/basic_types.h"
#define CLIPBOARD_CHUNK_META_SIZE 7
namespace synergy {
class IStream;
};
class ClipboardChunk : public Chunk {
public:
ClipboardChunk(size_t size);
static ClipboardChunk*
start(
ClipboardID id,
UInt32 sequence,
const String& size);
static ClipboardChunk*
data(
ClipboardID id,
UInt32 sequence,
const String& data);
static ClipboardChunk*
end(ClipboardID id, UInt32 sequence);
static int assemble(
synergy::IStream* stream,
String& dataCached,
ClipboardID& id,
UInt32& sequence);
static void send(synergy::IStream* stream, void* data);
};

View File

@ -0,0 +1,152 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/FileChunk.h"
#include "synergy/ProtocolUtil.h"
#include "synergy/protocol_types.h"
#include "io/IStream.h"
#include "base/Stopwatch.h"
#include "base/Log.h"
static const UInt16 kIntervalThreshold = 1;
FileChunk::FileChunk(size_t size) :
Chunk(size)
{
m_dataSize = size - FILE_CHUNK_META_SIZE;
}
FileChunk*
FileChunk::start(const String& size)
{
size_t sizeLength = size.size();
FileChunk* start = new FileChunk(sizeLength + FILE_CHUNK_META_SIZE);
char* chunk = start->m_chunk;
chunk[0] = kDataStart;
memcpy(&chunk[1], size.c_str(), sizeLength);
chunk[sizeLength + 1] = '\0';
return start;
}
FileChunk*
FileChunk::data(UInt8* data, size_t dataSize)
{
FileChunk* chunk = new FileChunk(dataSize + FILE_CHUNK_META_SIZE);
char* chunkData = chunk->m_chunk;
chunkData[0] = kDataChunk;
memcpy(&chunkData[1], data, dataSize);
chunkData[dataSize + 1] = '\0';
return chunk;
}
FileChunk*
FileChunk::end()
{
FileChunk* end = new FileChunk(FILE_CHUNK_META_SIZE);
char* chunk = end->m_chunk;
chunk[0] = kDataEnd;
chunk[1] = '\0';
return end;
}
int
FileChunk::assemble(synergy::IStream* stream, String& dataReceived, size_t& expectedSize)
{
// parse
UInt8 mark = 0;
String content;
static size_t receivedDataSize;
static double elapsedTime;
static Stopwatch stopwatch;
if (!ProtocolUtil::readf(stream, kMsgDFileTransfer + 4, &mark, &content)) {
return kError;
}
switch (mark) {
case kDataStart:
dataReceived.clear();
expectedSize = synergy::string::stringToSizeType(content);
receivedDataSize = 0;
elapsedTime = 0;
stopwatch.reset();
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from client: file size=%s", content.c_str()));
stopwatch.start();
}
return kNotFinish;
case kDataChunk:
dataReceived.append(content);
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "recv file data from client: chunck size=%i", content.size()));
double interval = stopwatch.getTime();
receivedDataSize += content.size();
LOG((CLOG_DEBUG2 "recv file data from client: interval=%f s", interval));
if (interval >= kIntervalThreshold) {
double averageSpeed = receivedDataSize / interval / 1000;
LOG((CLOG_DEBUG2 "recv file data from client: average speed=%f kb/s", averageSpeed));
receivedDataSize = 0;
elapsedTime += interval;
stopwatch.reset();
}
}
return kNotFinish;
case kDataEnd:
//m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server));
if (CLOG->getFilter() >= kDEBUG2) {
LOG((CLOG_DEBUG2 "file data transfer finished"));
elapsedTime += stopwatch.getTime();
double averageSpeed = expectedSize / elapsedTime / 1000;
LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", elapsedTime));
LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", expectedSize / 1000));
LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed));
}
return kFinish;
}
return kError;
}
void
FileChunk::send(synergy::IStream* stream, UInt8 mark, char* data, size_t dataSize)
{
String chunk(data, dataSize);
switch (mark) {
case kDataStart:
LOG((CLOG_DEBUG2 "sending file chunk start: size=%s", data));
break;
case kDataChunk:
LOG((CLOG_DEBUG2 "sending file chunk: size=%i", chunk.size()));
break;
case kDataEnd:
LOG((CLOG_DEBUG2 "sending file finished"));
break;
}
ProtocolUtil::writef(stream, kMsgDFileTransfer, mark, &chunk);
}

View File

@ -0,0 +1,46 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/Chunk.h"
#include "base/String.h"
#include "common/basic_types.h"
#define FILE_CHUNK_META_SIZE 2
namespace synergy {
class IStream;
};
class FileChunk : public Chunk {
public:
FileChunk(size_t size);
static FileChunk* start(const String& size);
static FileChunk* data(UInt8* data, size_t dataSize);
static FileChunk* end();
static int assemble(
synergy::IStream* stream,
String& dataCached,
size_t& expectedSize);
static void send(
synergy::IStream* stream,
UInt8 mark,
char* data,
size_t dataSize);
};

View File

@ -1,112 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2013 Synergy Si 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 "synergy/FileChunker.h"
#include "synergy/protocol_types.h"
#include "base/EventTypes.h"
#include "base/Event.h"
#include "base/IEventQueue.h"
#include "base/EventTypes.h"
#include "base/Log.h"
#include "base/Stopwatch.h"
#include "common/stdexcept.h"
#include <fstream>
#include <sstream>
#define PAUSE_TIME_HACK 0.1
using namespace std;
const size_t FileChunker::m_chunkSize = 512 * 1024; // 512kb
void
FileChunker::sendFileChunks(char* filename, IEventQueue* events, void* eventTarget)
{
std::fstream file(reinterpret_cast<char*>(filename), std::ios::in | std::ios::binary);
if (!file.is_open()) {
throw runtime_error("failed to open file");
}
// check file size
file.seekg (0, std::ios::end);
size_t size = (size_t)file.tellg();
// send first message (file size)
String fileSize = intToString(size);
size_t sizeLength = fileSize.size();
FileChunk* sizeMessage = new FileChunk(sizeLength + 2);
char* chunkData = sizeMessage->m_chunk;
chunkData[0] = kFileStart;
memcpy(&chunkData[1], fileSize.c_str(), sizeLength);
chunkData[sizeLength + 1] = '\0';
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, sizeMessage));
// send chunk messages with a fixed chunk size
size_t sentLength = 0;
size_t chunkSize = m_chunkSize;
Stopwatch stopwatch;
stopwatch.start();
file.seekg (0, std::ios::beg);
while (true) {
if (stopwatch.getTime() > PAUSE_TIME_HACK) {
// make sure we don't read too much from the mock data.
if (sentLength + chunkSize > size) {
chunkSize = size - sentLength;
}
// for fileChunk->m_chunk, the first byte is the chunk mark, last is \0
FileChunk* fileChunk = new FileChunk(chunkSize + 2);
char* chunkData = fileChunk->m_chunk;
chunkData[0] = kFileChunk;
file.read(&chunkData[1], chunkSize);
chunkData[chunkSize + 1] = '\0';
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, fileChunk));
sentLength += chunkSize;
file.seekg (sentLength, std::ios::beg);
if (sentLength == size) {
break;
}
stopwatch.reset();
}
}
// send last message
FileChunk* transferFinished = new FileChunk(2);
chunkData = transferFinished->m_chunk;
chunkData[0] = kFileEnd;
chunkData[1] = '\0';
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, transferFinished));
file.close();
}
String
FileChunker::intToString(size_t i)
{
stringstream ss;
ss << i;
return ss.str();
}

View File

@ -0,0 +1,204 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2013 Synergy Si 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 "synergy/StreamChunker.h"
#include "synergy/FileChunk.h"
#include "synergy/ClipboardChunk.h"
#include "synergy/protocol_types.h"
#include "base/EventTypes.h"
#include "base/Event.h"
#include "base/IEventQueue.h"
#include "base/EventTypes.h"
#include "base/Log.h"
#include "base/Stopwatch.h"
#include "base/String.h"
#include "common/stdexcept.h"
#include <fstream>
#define PAUSE_TIME_HACK 0.1
using namespace std;
#define SOCKET_CHUNK_SIZE 512 * 1024; // 512kb
#define SECURE_SOCKET_CHUNK_SIZE 2 * 1024; // 2kb
size_t StreamChunker::s_chunkSize = SOCKET_CHUNK_SIZE;
bool StreamChunker::s_isChunkingClipboard = false;
bool StreamChunker::s_interruptClipboard = false;
bool StreamChunker::s_isChunkingFile = false;
bool StreamChunker::s_interruptFile = false;
void
StreamChunker::sendFile(
char* filename,
IEventQueue* events,
void* eventTarget)
{
s_isChunkingFile = true;
std::fstream file(reinterpret_cast<char*>(filename), std::ios::in | std::ios::binary);
if (!file.is_open()) {
throw runtime_error("failed to open file");
}
// check file size
file.seekg (0, std::ios::end);
size_t size = (size_t)file.tellg();
// send first message (file size)
String fileSize = synergy::string::sizeTypeToString(size);
FileChunk* sizeMessage = FileChunk::start(fileSize);
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, sizeMessage));
// send chunk messages with a fixed chunk size
size_t sentLength = 0;
size_t chunkSize = s_chunkSize;
Stopwatch stopwatch;
stopwatch.start();
file.seekg (0, std::ios::beg);
while (true) {
if (s_interruptFile) {
s_interruptFile = false;
break;
}
if (stopwatch.getTime() > PAUSE_TIME_HACK) {
// make sure we don't read too much from the mock data.
if (sentLength + chunkSize > size) {
chunkSize = size - sentLength;
}
char* chunkData = new char[chunkSize];
file.read(chunkData, chunkSize);
UInt8* data = reinterpret_cast<UInt8*>(chunkData);
FileChunk* fileChunk = FileChunk::data(data, chunkSize);
delete[] chunkData;
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, fileChunk));
sentLength += chunkSize;
file.seekg (sentLength, std::ios::beg);
if (sentLength == size) {
break;
}
stopwatch.reset();
}
}
// send last message
FileChunk* end = FileChunk::end();
events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, end));
file.close();
s_isChunkingFile = false;
}
void
StreamChunker::sendClipboard(
String& data,
size_t size,
ClipboardID id,
UInt32 sequence,
IEventQueue* events,
void* eventTarget)
{
s_isChunkingClipboard = true;
// send first message (data size)
String dataSize = synergy::string::sizeTypeToString(size);
ClipboardChunk* sizeMessage = ClipboardChunk::start(id, sequence, dataSize);
events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, sizeMessage));
// send clipboard chunk with a fixed size
size_t sentLength = 0;
size_t chunkSize = s_chunkSize;
Stopwatch stopwatch;
stopwatch.start();
while (true) {
if (s_interruptClipboard) {
s_interruptClipboard = false;
break;
}
if (stopwatch.getTime() > 0.1f) {
// make sure we don't read too much from the mock data.
if (sentLength + chunkSize > size) {
chunkSize = size - sentLength;
}
String chunk(data.substr(sentLength, chunkSize).c_str(), chunkSize);
ClipboardChunk* dataChunk = ClipboardChunk::data(id, sequence, chunk);
events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, dataChunk));
sentLength += chunkSize;
if (sentLength == size) {
break;
}
stopwatch.reset();
}
}
// send last message
ClipboardChunk* end = ClipboardChunk::end(id, sequence);
events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, end));
s_isChunkingClipboard = false;
}
void
StreamChunker::updateChunkSize(bool useSecureSocket)
{
if (useSecureSocket) {
s_chunkSize = SECURE_SOCKET_CHUNK_SIZE;
}
else {
s_chunkSize = SOCKET_CHUNK_SIZE;
}
}
void
StreamChunker::interruptFile()
{
if (s_isChunkingFile) {
s_interruptFile = true;
LOG((CLOG_INFO "previous dragged file has become invalid"));
}
}
void
StreamChunker::interruptClipboard()
{
if (s_isChunkingClipboard) {
s_interruptClipboard = true;
LOG((CLOG_INFO "previous clipboard data has become invalid"));
}
}

View File

@ -0,0 +1,48 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2013 Synergy Si 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 "synergy/clipboard_types.h"
#include "base/String.h"
class IEventQueue;
class StreamChunker {
public:
static void sendFile(
char* filename,
IEventQueue* events,
void* eventTarget);
static void sendClipboard(
String& data,
size_t size,
ClipboardID id,
UInt32 sequence,
IEventQueue* events,
void* eventTarget);
static void updateChunkSize(bool useSecureSocket);
static void interruptFile();
static void interruptClipboard();
private:
static size_t s_chunkSize;
static bool s_isChunkingClipboard;
static bool s_interruptClipboard;
static bool s_isChunkingFile;
static bool s_interruptFile;
};

View File

@ -41,7 +41,7 @@ const char* kMsgDMouseMove = "DMMV%2i%2i";
const char* kMsgDMouseRelMove = "DMRM%2i%2i"; const char* kMsgDMouseRelMove = "DMRM%2i%2i";
const char* kMsgDMouseWheel = "DMWM%2i%2i"; const char* kMsgDMouseWheel = "DMWM%2i%2i";
const char* kMsgDMouseWheel1_0 = "DMWM%2i"; const char* kMsgDMouseWheel1_0 = "DMWM%2i";
const char* kMsgDClipboard = "DCLP%1i%4i%s"; const char* kMsgDClipboard = "DCLP%1i%4i%1i%s";
const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i"; const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i";
const char* kMsgDSetOptions = "DSOP%4I"; const char* kMsgDSetOptions = "DSOP%4I";
const char* kMsgDFileTransfer = "DFTR%1i%s"; const char* kMsgDFileTransfer = "DFTR%1i%s";

View File

@ -28,9 +28,10 @@
// adds horizontal mouse scrolling // adds horizontal mouse scrolling
// 1.4: adds crypto support // 1.4: adds crypto support
// 1.5: adds file transfer and removes home brew crypto // 1.5: adds file transfer and removes home brew crypto
// 1.6: adds clipboard streaming
// NOTE: with new version, synergy minor version should increment // NOTE: with new version, synergy minor version should increment
static const SInt16 kProtocolMajorVersion = 1; static const SInt16 kProtocolMajorVersion = 1;
static const SInt16 kProtocolMinorVersion = 5; static const SInt16 kProtocolMinorVersion = 6;
// default contact port number // default contact port number
static const UInt16 kDefaultPort = 24800; static const UInt16 kDefaultPort = 24800;
@ -69,13 +70,19 @@ enum EDirectionMask {
kBottomMask = 1 << kBottom kBottomMask = 1 << kBottom
}; };
// file transfer constants // Data transfer constants
enum EFileTransfer { enum EDataTransfer {
kFileStart = 1, kDataStart = 1,
kFileChunk = 2, kDataChunk = 2,
kFileEnd = 3 kDataEnd = 3
}; };
// Data received constants
enum EDataReceived {
kNotFinish,
kFinish,
kError
};
// //
// message codes (trailing NUL is not part of code). in comments, $n // message codes (trailing NUL is not part of code). in comments, $n
@ -225,7 +232,7 @@ extern const char* kMsgDMouseWheel;
extern const char* kMsgDMouseWheel1_0; extern const char* kMsgDMouseWheel1_0;
// clipboard data: primary <-> secondary // clipboard data: primary <-> secondary
// $2 = sequence number, $3 = clipboard data. the sequence number // $2 = sequence number, $3 = mark $4 = clipboard data. the sequence number
// is 0 when sent by the primary. secondary screens should use the // is 0 when sent by the primary. secondary screens should use the
// sequence number from the most recent kMsgCEnter. $1 = clipboard // sequence number from the most recent kMsgCEnter. $1 = clipboard
// identifier. // identifier.

View File

@ -28,7 +28,8 @@
#include "server/Server.h" #include "server/Server.h"
#include "server/ClientListener.h" #include "server/ClientListener.h"
#include "client/Client.h" #include "client/Client.h"
#include "synergy/FileChunker.h" #include "synergy/FileChunk.h"
#include "synergy/StreamChunker.h"
#include "net/SocketMultiplexer.h" #include "net/SocketMultiplexer.h"
#include "net/NetworkAddress.h" #include "net/NetworkAddress.h"
#include "net/TCPSocketFactory.h" #include "net/TCPSocketFactory.h"
@ -60,7 +61,6 @@ const size_t kMockFileSize = 1024 * 1024 * 10; // 10MB
void getScreenShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h); void getScreenShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h);
void getCursorPos(SInt32& x, SInt32& y); void getCursorPos(SInt32& x, SInt32& y);
String intToString(size_t i);
UInt8* newMockData(size_t size); UInt8* newMockData(size_t size);
void createFile(fstream& file, const char* filename, size_t size); void createFile(fstream& file, const char* filename, size_t size);
@ -415,38 +415,28 @@ void
NetworkTests::sendMockData(void* eventTarget) NetworkTests::sendMockData(void* eventTarget)
{ {
// send first message (file size) // send first message (file size)
String size = intToString(kMockDataSize); String size = synergy::string::sizeTypeToString(kMockDataSize);
size_t sizeLength = size.size(); FileChunk* sizeMessage = FileChunk::start(size);
FileChunker::FileChunk* sizeMessage = new FileChunker::FileChunk(sizeLength + 2);
char* chunkData = sizeMessage->m_chunk;
chunkData[0] = kFileStart;
memcpy(&chunkData[1], size.c_str(), sizeLength);
chunkData[sizeLength + 1] = '\0';
m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, sizeMessage)); m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, sizeMessage));
// send chunk messages with incrementing chunk size // send chunk messages with incrementing chunk size
size_t lastSize = 0; size_t lastSize = 0;
size_t sentLength = 0; size_t sentLength = 0;
while (true) { while (true) {
size_t chunkSize = lastSize + kMockDataChunkIncrement; size_t dataSize = lastSize + kMockDataChunkIncrement;
// make sure we don't read too much from the mock data. // make sure we don't read too much from the mock data.
if (sentLength + chunkSize > kMockDataSize) { if (sentLength + dataSize > kMockDataSize) {
chunkSize = kMockDataSize - sentLength; dataSize = kMockDataSize - sentLength;
} }
// first byte is the chunk mark, last is \0 // first byte is the chunk mark, last is \0
FileChunker::FileChunk* fileChunk = new FileChunker::FileChunk(chunkSize + 2); FileChunk* chunk = FileChunk::data(m_mockData, dataSize);
char* chunkData = fileChunk->m_chunk; m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, chunk));
chunkData[0] = kFileChunk; sentLength += dataSize;
memcpy(&chunkData[1], &m_mockData[sentLength], chunkSize); lastSize = dataSize;
chunkData[chunkSize + 1] = '\0';
m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, fileChunk));
sentLength += chunkSize;
lastSize = chunkSize;
if (sentLength == kMockDataSize) { if (sentLength == kMockDataSize) {
break; break;
@ -455,11 +445,7 @@ NetworkTests::sendMockData(void* eventTarget)
} }
// send last message // send last message
FileChunker::FileChunk* transferFinished = new FileChunker::FileChunk(2); FileChunk* transferFinished = FileChunk::end();
chunkData = transferFinished->m_chunk;
chunkData[0] = kFileEnd;
chunkData[1] = '\0';
m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, transferFinished)); m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, transferFinished));
} }
@ -527,12 +513,4 @@ getCursorPos(SInt32& x, SInt32& y)
y = 0; y = 0;
} }
String
intToString(size_t i)
{
stringstream ss;
ss << i;
return ss.str();
}
#endif // WINAPI_CARBON #endif // WINAPI_CARBON

View File

@ -61,5 +61,6 @@ public:
MOCK_METHOD0(forIKeyState, IKeyStateEvents&()); MOCK_METHOD0(forIKeyState, IKeyStateEvents&());
MOCK_METHOD0(forIPrimaryScreen, IPrimaryScreenEvents&()); MOCK_METHOD0(forIPrimaryScreen, IPrimaryScreenEvents&());
MOCK_METHOD0(forIScreen, IScreenEvents&()); MOCK_METHOD0(forIScreen, IScreenEvents&());
MOCK_METHOD0(forClipboard, ClipboardEvents&());
MOCK_CONST_METHOD0(waitForReady, void()); MOCK_CONST_METHOD0(waitForReady, void());
}; };

View File

@ -82,3 +82,21 @@ TEST(StringTests, removeChar)
EXPECT_EQ("fbar", subject); EXPECT_EQ("fbar", subject);
} }
TEST(StringTests, intToString)
{
size_t value = 123;
String number = string::sizeTypeToString(value);
EXPECT_EQ("123", number);
}
TEST(StringTests, stringToUint)
{
String number = "123";
size_t value = string::stringToSizeType(number);
EXPECT_EQ(123, value);
}

View File

@ -120,7 +120,9 @@ TEST(IpcLogOutputterTests, write_overBufferRateLimit_lastLineTruncated)
// after waiting the time limit send another to make sure // after waiting the time limit send another to make sure
// we can log after the time limit passes. // we can log after the time limit passes.
ARCH->sleep(0.01); // 10ms // HACK: sleep causes the unit test to fail intermittently,
// so lets try 100ms (there must be a better way to solve this)
ARCH->sleep(0.1); // 100ms
outputter.write(kNOTE, "mock 3"); outputter.write(kNOTE, "mock 3");
outputter.write(kNOTE, "mock 4"); outputter.write(kNOTE, "mock 4");
outputter.sendBuffer(); outputter.sendBuffer();

View File

@ -0,0 +1,76 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Inc.
*
* 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 "synergy/ClipboardChunk.h"
#include "synergy/protocol_types.h"
#include "test/global/gtest.h"
TEST(ClipboardChunkTests, start_formatStartChunk)
{
ClipboardID id = 0;
UInt32 sequence = 0;
String mockDataSize("10");
ClipboardChunk* chunk = ClipboardChunk::start(id, sequence, mockDataSize);
EXPECT_EQ(id, chunk->m_chunk[0]);
EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]);
EXPECT_EQ(kDataStart, chunk->m_chunk[5]);
EXPECT_EQ('1', chunk->m_chunk[6]);
EXPECT_EQ('0', chunk->m_chunk[7]);
EXPECT_EQ('\0', chunk->m_chunk[8]);
delete chunk;
}
TEST(ClipboardChunkTests, data_formatDataChunk)
{
ClipboardID id = 0;
UInt32 sequence = 1;
String mockData("mock data");
ClipboardChunk* chunk = ClipboardChunk::data(id, sequence, mockData);
EXPECT_EQ(id, chunk->m_chunk[0]);
EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]);
EXPECT_EQ(kDataChunk, chunk->m_chunk[5]);
EXPECT_EQ('m', chunk->m_chunk[6]);
EXPECT_EQ('o', chunk->m_chunk[7]);
EXPECT_EQ('c', chunk->m_chunk[8]);
EXPECT_EQ('k', chunk->m_chunk[9]);
EXPECT_EQ(' ', chunk->m_chunk[10]);
EXPECT_EQ('d', chunk->m_chunk[11]);
EXPECT_EQ('a', chunk->m_chunk[12]);
EXPECT_EQ('t', chunk->m_chunk[13]);
EXPECT_EQ('a', chunk->m_chunk[14]);
EXPECT_EQ('\0', chunk->m_chunk[15]);
delete chunk;
}
TEST(ClipboardChunkTests, end_formatDataChunk)
{
ClipboardID id = 1;
UInt32 sequence = 1;
ClipboardChunk* chunk = ClipboardChunk::end(id, sequence);
EXPECT_EQ(id, chunk->m_chunk[0]);
EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]);
EXPECT_EQ(kDataEnd, chunk->m_chunk[5]);
EXPECT_EQ('\0', chunk->m_chunk[6]);
delete chunk;
}