diff --git a/ChangeLog b/ChangeLog index 075c4f59..ec6c108c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 #4581 - Starting GUI on Mac crashes instantly on syntool segfault 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 #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 #3052 - Triple-click (select line) does not work 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 #4390 - Disable GUI auto-hide by default -1.7.0 -===== +v1.7.0-beta +=========== Enhancement #4313 - SSL encrypted secure connection Enhancement #4168 - Plugin manager for GUI 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 #3072: Downgrade Linux build machines Task #3090: CXWindowsKeyState integ test args wrong + \ No newline at end of file diff --git a/ext/toolchain/commands1.py b/ext/toolchain/commands1.py index cb14ae72..e1e80e69 100644 --- a/ext/toolchain/commands1.py +++ b/ext/toolchain/commands1.py @@ -17,7 +17,7 @@ # TODO: split this file up, it's too long! 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 if sys.version_info >= (2, 4): @@ -55,14 +55,14 @@ class Toolchain: 'reformat' : ['', []], 'open' : ['', []], 'genlist' : ['', []], - 'reset' : ['', []], - 'signwin' : ['', ['pfx=', 'pwd=', 'dist']], - 'signmac' : ['', []] + 'reset' : ['', []], + 'signwin' : ['', ['pfx=', 'pwd=', 'dist']], + 'signmac' : ['', []] } # aliases to valid commands cmd_alias_dict = { - 'info' : 'about', + 'info' : 'about', 'help' : 'usage', 'package' : 'dist', 'docs' : 'doxygen', @@ -254,12 +254,12 @@ class InternalCommands: gmockDir = 'gmock-1.6.0' win32_generators = { - 1 : Generator('Visual Studio 10'), - 2 : Generator('Visual Studio 10 Win64'), - 3 : Generator('Visual Studio 9 2008'), - 4 : Generator('Visual Studio 9 2008 Win64'), - 5 : Generator('Visual Studio 8 2005'), - 6 : Generator('Visual Studio 8 2005 Win64') + 1 : VisualStudioGenerator('10'), + 2 : VisualStudioGenerator('10 Win64'), + 3 : VisualStudioGenerator('9 2008'), + 4 : VisualStudioGenerator('9 2008 Win64'), + 5 : VisualStudioGenerator('8 2005'), + 6 : VisualStudioGenerator('8 2005 Win64') } unix_generators = { @@ -319,7 +319,6 @@ class InternalCommands: self.configure(target) def checkGTest(self): - dir = self.extDir + '/' + self.gtestDir if (os.path.isdir(dir)): return @@ -335,7 +334,6 @@ class InternalCommands: self.zipExtractAll(zip, dir) def checkGMock(self): - dir = self.extDir + '/' + self.gmockDir if (os.path.isdir(dir)): return @@ -524,14 +522,14 @@ class InternalCommands: version = commands.getoutput("qmake --version") result = re.search('(\d+)\.(\d+)\.(\d)', version) - if not result: + if not result: raise Exception("Could not get qmake version.") - major = int(result.group(1)) - minor = int(result.group(2)) - rev = int(result.group(3)) - - return (major, minor, rev) + major = int(result.group(1)) + minor = int(result.group(2)) + rev = int(result.group(3)) + + return (major, minor, rev) def getMacSdkDir(self): sdkName = "macosx" + self.macSdk @@ -575,10 +573,10 @@ class InternalCommands: # if return code from cmake is not 0, then either something has # gone terribly wrong with --version, or it genuinely doesn't exist. print ('Could not find `%s` in system path.\n' - 'Download the latest version from:\n %s') % ( + 'Download the latest version from:\n %s') % ( self.cmake_cmd, self.cmake_url) raise Exception('Cannot continue without CMake.') - else: + else: return self.cmake_cmd def persist_qt(self): @@ -786,15 +784,15 @@ class InternalCommands: # reorganize Qt frameworks layout on Mac 10.9.5 or later # http://goo.gl/BFnQ8l # QtCore example: - # QtCore.framework/ - # QtCore -> Versions/Current/QtCore - # Resources -> Versions/Current/Resources - # Versions/ - # Current -> 5 - # 5/ - # QtCore - # Resources/ - # Info.plist + # QtCore.framework/ + # QtCore -> Versions/Current/QtCore + # Resources -> Versions/Current/Resources + # Versions/ + # Current -> 5 + # 5/ + # QtCore + # Resources/ + # Info.plist targetDir = self.getGenerator().getBinDir(target) target = targetDir + "/Synergy.app/Contents/Frameworks" @@ -840,7 +838,7 @@ class InternalCommands: pwd = lines[0] if (dist): - self.signFile(pfx, pwd, 'bin', self.dist_name('win')) + self.signFile(pfx, pwd, 'bin/Release', self.dist_name('win')) else: self.signFile(pfx, pwd, 'bin/Release', 'synergy.exe') self.signFile(pfx, pwd, 'bin/Release', 'synergyc.exe') @@ -974,7 +972,13 @@ class InternalCommands: if p.returncode != 0: 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): if sys.version_info < (2, 4): @@ -1026,7 +1030,7 @@ class InternalCommands: elif type == 'rpm': if sys.platform == 'linux2': - self.distRpm() + self.distRpm() else: package_unsupported = True @@ -1062,9 +1066,9 @@ class InternalCommands: ("Package type, '%s' is not supported for platform, '%s'") % (type, sys.platform)) - def distRpm(self): - rpmDir = self.getGenerator().buildDir + '/rpm' - if os.path.exists(rpmDir): + def distRpm(self): + rpmDir = self.getGenerator().buildDir + '/rpm' + if os.path.exists(rpmDir): shutil.rmtree(rpmDir) os.makedirs(rpmDir) @@ -1072,51 +1076,52 @@ class InternalCommands: templateFile = open(self.cmake_dir + '/synergy.spec.in') template = templateFile.read() - template = template.replace('${in:version}', self.getVersionFromCmake()) - + template = template.replace('${in:version}', self.getVersionFromCmake()) + specPath = rpmDir + '/synergy.spec' specFile = open(specPath, 'w') specFile.write(template) specFile.close() - version = self.getVersionFromCmake() - target = '../../bin/synergy-%s-%s.rpm' % ( - version, self.getLinuxPlatform()) - + version = self.getVersionFromCmake() + target = '../../bin/synergy-%s-%s.rpm' % ( + version, self.getLinuxPlatform()) + try: self.try_chdir(rpmDir) - cmd = 'rpmbuild -bb --define "_topdir `pwd`" synergy.spec' - print "Command: " + cmd + cmd = 'rpmbuild -bb --define "_topdir `pwd`" synergy.spec' + print "Command: " + cmd err = os.system(cmd) if err != 0: raise Exception('rpmbuild failed: ' + str(err)) self.unixMove('RPMS/*/*.rpm', target) - cmd = 'rpmlint ' + target - print "Command: " + cmd + cmd = 'rpmlint ' + target + print "Command: " + cmd err = os.system(cmd) if err != 0: raise Exception('rpmlint failed: ' + str(err)) + finally: self.restore_chdir() - def distDeb(self): + def distDeb(self): buildDir = self.getGenerator().buildDir binDir = self.getGenerator().binDir resDir = self.cmake_dir - version = self.getVersionFromCmake() - package = '%s-%s-%s' % ( - self.project, version, self.getLinuxPlatform()) + version = self.getVersionFromCmake() + package = '%s-%s-%s' % ( + self.project, version, self.getLinuxPlatform()) - debDir = '%s/deb' % buildDir - if os.path.exists(debDir): + debDir = '%s/deb' % buildDir + if os.path.exists(debDir): shutil.rmtree(debDir) - metaDir = '%s/%s/DEBIAN' % (debDir, package) + metaDir = '%s/%s/DEBIAN' % (debDir, package) os.makedirs(metaDir) templateFile = open(resDir + '/deb/control.in') @@ -1194,14 +1199,14 @@ class InternalCommands: self.try_chdir(debDir) # TODO: consider dpkg-buildpackage (higher level tool) - cmd = 'fakeroot dpkg-deb --build %s' % package - print "Command: " + cmd + cmd = 'fakeroot dpkg-deb --build %s' % package + print "Command: " + cmd err = os.system(cmd) if err != 0: raise Exception('dpkg-deb failed: ' + str(err)) - cmd = 'lintian %s.deb' % package - print "Command: " + cmd + cmd = 'lintian %s.deb' % package + print "Command: " + cmd err = os.system(cmd) if err != 0: raise Exception('lintian failed: ' + str(err)) @@ -1294,7 +1299,7 @@ class InternalCommands: arch) old = "bin/Release/synergy.msi" - new = "bin/%s" % (filename) + new = "bin/Release/%s" % (filename) try: os.remove(new) @@ -1320,7 +1325,7 @@ class InternalCommands: if generator.endswith('Win64'): arch = 'x64' - installDirVar = '$PROGRAMFILES64' + installDirVar = '$PROGRAMFILES64' templateFile = open(self.cmake_dir + '\Installer.nsi.in') template = templateFile.read() @@ -1359,54 +1364,54 @@ class InternalCommands: def distftp(self, type, ftp): if not type: - raise Exception('Type not specified.') - - if not ftp: - raise Exception('FTP info not defined.') + raise Exception('Platform type not specified.') 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') - ftp.run(binDir + '/' + src, dest) - print 'Done' - - def getDebianArch(self): - if os.uname()[4][:3] == 'arm': - return 'armhf' - # os_bits should be loaded with '32bit' or '64bit' - import platform - (os_bits, other) = platform.architecture() + 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 - # get platform based on current platform - if os_bits == '32bit': - return 'i386' - elif os_bits == '64bit': - return 'amd64' - else: - raise Exception("unknown os bits: " + os_bits) + return None - def getLinuxPlatform(self): - if os.uname()[4][:3] == 'arm': - return 'Linux-armv6l' - - # os_bits should be loaded with '32bit' or '64bit' - import platform - (os_bits, other) = platform.architecture() - - # get platform based on current platform - if os_bits == '32bit': - return 'Linux-i686' - elif os_bits == '64bit': - return 'Linux-x86_64' - else: - raise Exception("unknown os bits: " + os_bits) - - def dist_name(self, type): + def getDistributePlatformInfo(self, type): ext = None + libraryExt = None platform = None if type == 'src': @@ -1414,14 +1419,14 @@ class InternalCommands: platform = 'Source' elif type == 'rpm' or type == 'deb': - ext = type - platform = self.getLinuxPlatform() + 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' @@ -1430,18 +1435,23 @@ class InternalCommands: elif type == 'mac': ext = "dmg" + libraryExt = 'dylib' 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' + raise Exception('Unable to detect distributable platform.') - for filename in os.listdir(self.getBinDir(target)): + 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 @@ -1449,11 +1459,45 @@ class InternalCommands: 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" % ( - self.getGitBranchName(), self.getGitRevision()) + replace = "%s-%s" % (branch, revision) return re.sub(pattern, replace, self.dist_name(type)) + + def getDebianArch(self): + if os.uname()[4][:3] == 'arm': + return 'armhf' + + # os_bits should be loaded with '32bit' or '64bit' + import platform + (os_bits, other) = platform.architecture() + + # get platform based on current platform + if os_bits == '32bit': + return 'i386' + elif os_bits == '64bit': + return 'amd64' + else: + raise Exception("unknown os bits: " + os_bits) + + def getLinuxPlatform(self): + if os.uname()[4][:3] == 'arm': + return 'Linux-armv6l' + + # os_bits should be loaded with '32bit' or '64bit' + import platform + (os_bits, other) = platform.architecture() + + # get platform based on current platform + if os_bits == '32bit': + return 'Linux-i686' + elif os_bits == '64bit': + return 'Linux-x86_64' + else: + raise Exception("unknown os bits: " + os_bits) def dist_usage(self): print ('Usage: %s package [package-type]\n' @@ -1517,7 +1561,7 @@ class InternalCommands: oldGenerator = self.findGeneratorFromConfig() if not oldGenerator == None: - for target in ['debug', 'release']: + for target in ['debug', 'release']: buildDir = oldGenerator.getBuildDir(target) cmakeCacheFilename = 'CMakeCache.txt' @@ -1673,7 +1717,7 @@ class InternalCommands: return generators[generator_id] - def get_vcvarsall(self, generator): + def get_vcvarsall(self, generator): import platform, _winreg # os_bits should be loaded with '32bit' or '64bit' @@ -1703,7 +1747,7 @@ class InternalCommands: if os_bits == '64bit': path = value + r'vc\vcvarsall.bat' else: - path = value + r'vcvarsall.bat' + path = value + r'vcvarsall.bat' if not os.path.exists(path): raise Exception("'%s' not found." % path) @@ -1833,7 +1877,7 @@ class InternalCommands: # qt 4.3 generates ui_ files. for filename in glob.glob("src/gui/ui_*"): - os.remove(filename) + os.remove(filename) # the command handler should be called only from hm.py (i.e. directly # from the command prompt). the purpose of this class is so that we @@ -1902,7 +1946,7 @@ class CommandHandler: type = None if len(self.args) > 0: - type = self.args[0] + type = self.args[0] self.ic.dist(type, self.vcRedistDir, self.qtDir) @@ -1926,10 +1970,11 @@ class CommandHandler: elif o == '--dir': dir = a - ftp = None - if host: - ftp = ftputil.FtpUploader( - host, user, password, dir) + if not host: + raise Exception('FTP host was not specified.') + + ftp = ftputil.FtpUploader( + host, user, password, dir) self.ic.distftp(type, ftp) diff --git a/ext/toolchain/ftputil.py b/ext/toolchain/ftputil.py index 3cc8c9fe..b9a19306 100644 --- a/ext/toolchain/ftputil.py +++ b/ext/toolchain/ftputil.py @@ -23,13 +23,32 @@ class FtpUploader: self.password = password 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.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') ftp.storbinary('STOR ' + dest, f) f.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) diff --git a/ext/toolchain/generators.py b/ext/toolchain/generators.py index f07beda4..3a754759 100644 --- a/ext/toolchain/generators.py +++ b/ext/toolchain/generators.py @@ -30,6 +30,13 @@ class Generator(object): def getSourceDir(self): 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): def __init__(self): super(MakefilesGenerator, self).__init__('Unix Makefiles') diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index e3b8e2dd..323a1b6c 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -1132,12 +1132,12 @@ void MainWindow::downloadBonjour() { #if defined(Q_OS_WIN) QUrl url; - int arch = checkProcessorArch(); - if (arch == Win_x86) { + int arch = getProcessorArch(); + if (arch == kProcessorArchWin32) { url.setUrl(bonjourBaseUrl + bonjourFilename32); appendLogNote("downloading 32-bit Bonjour"); } - else if (arch == Win_x64) { + else if (arch == kProcessorArchWin64) { url.setUrl(bonjourBaseUrl + bonjourFilename64); appendLogNote("downloading 64-bit Bonjour"); } diff --git a/src/gui/src/PluginManager.cpp b/src/gui/src/PluginManager.cpp index 818a19c9..fd3a24e4 100644 --- a/src/gui/src/PluginManager.cpp +++ b/src/gui/src/PluginManager.cpp @@ -29,14 +29,15 @@ #include #include -static QString kBaseUrl = "http://synergy-project.org/files"; -static const char kWinProcessorArch32[] = "Windows-x86"; -static const char kWinProcessorArch64[] = "Windows-x64"; -static const char kMacProcessorArch[] = "MacOSX-i386"; -static const char kLinuxProcessorArchDeb32[] = "Linux-i686-deb"; -static const char kLinuxProcessorArchDeb64[] = "Linux-x86_64-deb"; -static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm"; -static const char kLinuxProcessorArchRpm64[] = "Linux-x86_64-rpm"; +static const char kBaseUrl[] = "http://synergy-project.org/files"; +static const char kDefaultVersion[] = "1.1"; +static const char kWinPackagePlatform32[] = "Windows-x86"; +static const char kWinPackagePlatform64[] = "Windows-x64"; +static const char kMacPackagePlatform[] = "MacOSX%1-i386"; +static const char kLinuxPackagePlatformDeb32[] = "Linux-i686-deb"; +static const char kLinuxPackagePlatformDeb64[] = "Linux-x86_64-deb"; +static const char kLinuxPackagePlatformRpm32[] = "Linux-i686-rpm"; +static const char kLinuxPackagePlatformRpm64[] = "Linux-x86_64-rpm"; #if defined(Q_OS_WIN) static const char kWinPluginExt[] = ".dll"; @@ -157,10 +158,10 @@ QString PluginManager::getPluginUrl(const QString& pluginName) try { QString coreArch = m_CoreInterface.getArch(); if (coreArch.startsWith("x86")) { - archName = kWinProcessorArch32; + archName = kWinPackagePlatform32; } else if (coreArch.startsWith("x64")) { - archName = kWinProcessorArch64; + archName = kWinPackagePlatform64; } } catch (...) { @@ -170,22 +171,53 @@ QString PluginManager::getPluginUrl(const QString& pluginName) #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 - int arch = checkProcessorArch(); - if (arch == Linux_rpm_i686) { - archName = kLinuxProcessorArchRpm32; + QString program("dpkg"); + QStringList args; + 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 { + archName = kLinuxPackagePlatformRpm32; + } } - else if (arch == Linux_deb_i686) { - archName = kLinuxProcessorArchDeb32; - } - else if (arch == Linux_deb_x86_64) { - archName = kLinuxProcessorArchDeb64; + else if (arch == kProcessorArchLinux64) { + if (isDeb) { + archName = kLinuxPackagePlatformDeb64; + } + else { + archName = kLinuxPackagePlatformRpm64; + } } else { emit error(tr("Could not get Linux architecture type.")); @@ -194,13 +226,14 @@ QString PluginManager::getPluginUrl(const QString& pluginName) #endif - QString result = kBaseUrl; - result.append("/plugins/"); - result.append(pluginName).append("/1.0/"); - result.append(archName); - result.append("/"); - result.append(getPluginOsSpecificName(pluginName)); + QString result = QString("%1/plugins/%2/%3/%4/%5") + .arg(kBaseUrl) + .arg(pluginName) + .arg(kDefaultVersion) + .arg(archName) + .arg(getPluginOsSpecificName(pluginName)); + qDebug() << result; return result; } diff --git a/src/gui/src/ProcessorArch.h b/src/gui/src/ProcessorArch.h index dacf6468..76f6e96a 100644 --- a/src/gui/src/ProcessorArch.h +++ b/src/gui/src/ProcessorArch.h @@ -15,18 +15,14 @@ * along with this program. If not, see . */ -#ifndef PROCESSORARCH_H -#define PROCESSORARCH_H +#pragma once enum qProcessorArch { - Win_x86, - Win_x64, - Mac_i386, - Linux_rpm_i686, - Linux_rpm_x86_64, - Linux_deb_i686, - Linux_deb_x86_64, - unknown + kProcessorArchWin32, + kProcessorArchWin64, + kProcessorArchMac32, + kProcessorArchMac64, + kProcessorArchLinux32, + kProcessorArchLinux64, + kProcessorArchUnknown }; - -#endif // PROCESSORARCH_H diff --git a/src/gui/src/QUtility.cpp b/src/gui/src/QUtility.cpp index ac1f8b32..14d1d1e1 100644 --- a/src/gui/src/QUtility.cpp +++ b/src/gui/src/QUtility.cpp @@ -29,12 +29,6 @@ #include #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) { for (int i = 0; i < comboBox->count(); ++i) @@ -68,7 +62,7 @@ QString getFirstMacAddress() return mac; } -int checkProcessorArch() +qProcessorArch getProcessorArch() { #if defined(Q_OS_WIN) SYSTEM_INFO systemInfo; @@ -76,97 +70,23 @@ int checkProcessorArch() switch (systemInfo.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_INTEL: - return Win_x86; + return kProcessorArchWin32; case PROCESSOR_ARCHITECTURE_IA64: - return Win_x64; + return kProcessorArchWin64; case PROCESSOR_ARCHITECTURE_AMD64: - return Win_x64; + return kProcessorArchWin64; default: - return unknown; - } -#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; - } + return kProcessorArchUnknown; } #endif - return unknown; + +#if defined(Q_OS_LINUX) +#ifdef __i386__ + return kProcessorArchLinux32; +#else + return kProcessorArchLinux64; +#endif +#endif + + return kProcessorArchUnknown; } diff --git a/src/gui/src/QUtility.h b/src/gui/src/QUtility.h index 1b38409c..89861dda 100644 --- a/src/gui/src/QUtility.h +++ b/src/gui/src/QUtility.h @@ -17,6 +17,8 @@ #pragma once +#include "ProcessorArch.h" + #include #include #include @@ -25,4 +27,4 @@ void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData); QString hash(const QString& string); QString getFirstMacAddress(); -int checkProcessorArch(); +qProcessorArch getProcessorArch(); diff --git a/src/lib/base/EventQueue.cpp b/src/lib/base/EventQueue.cpp index 0aab4277..71208661 100644 --- a/src/lib/base/EventQueue.cpp +++ b/src/lib/base/EventQueue.cpp @@ -46,6 +46,7 @@ EVENT_TYPE_ACCESSOR(ServerApp) EVENT_TYPE_ACCESSOR(IKeyState) EVENT_TYPE_ACCESSOR(IPrimaryScreen) EVENT_TYPE_ACCESSOR(IScreen) +EVENT_TYPE_ACCESSOR(Clipboard) // interrupt handler. this just adds a quit event to the queue. static @@ -82,6 +83,7 @@ EventQueue::EventQueue() : m_typesForIKeyState(NULL), m_typesForIPrimaryScreen(NULL), m_typesForIScreen(NULL), + m_typesForClipboard(NULL), m_readyMutex(new Mutex), m_readyCondVar(new CondVar(m_readyMutex, false)) { diff --git a/src/lib/base/EventQueue.h b/src/lib/base/EventQueue.h index ed47c2b8..cf6dd01d 100644 --- a/src/lib/base/EventQueue.h +++ b/src/lib/base/EventQueue.h @@ -157,6 +157,7 @@ public: IKeyStateEvents& forIKeyState(); IPrimaryScreenEvents& forIPrimaryScreen(); IScreenEvents& forIScreen(); + ClipboardEvents& forClipboard(); private: ClientEvents* m_typesForClient; @@ -177,6 +178,7 @@ private: IKeyStateEvents* m_typesForIKeyState; IPrimaryScreenEvents* m_typesForIPrimaryScreen; IScreenEvents* m_typesForIScreen; + ClipboardEvents* m_typesForClipboard; Mutex* m_readyMutex; CondVar* m_readyCondVar; std::queue m_pending; diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp index 0629cef3..0fdb87d7 100644 --- a/src/lib/base/EventTypes.cpp +++ b/src/lib/base/EventTypes.cpp @@ -115,7 +115,6 @@ REGISTER_EVENT(ClientListener, connected) REGISTER_EVENT(ClientProxy, ready) REGISTER_EVENT(ClientProxy, disconnected) -REGISTER_EVENT(ClientProxy, clipboardChanged) // // ClientProxyUnknown @@ -175,7 +174,6 @@ REGISTER_EVENT(IPrimaryScreen, fakeInputEnd) REGISTER_EVENT(IScreen, error) REGISTER_EVENT(IScreen, shapeChanged) -REGISTER_EVENT(IScreen, clipboardGrabbed) REGISTER_EVENT(IScreen, suspend) REGISTER_EVENT(IScreen, resume) REGISTER_EVENT(IScreen, fileChunkSending) @@ -187,3 +185,11 @@ REGISTER_EVENT(IScreen, fileRecieveCompleted) REGISTER_EVENT(IpcServer, clientConnected) REGISTER_EVENT(IpcServer, messageReceived) + +// +// Clipboard +// + +REGISTER_EVENT(Clipboard, clipboardGrabbed) +REGISTER_EVENT(Clipboard, clipboardChanged) +REGISTER_EVENT(Clipboard, clipboardSending) \ No newline at end of file diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h index 75a0800c..33ee28cc 100644 --- a/src/lib/base/EventTypes.h +++ b/src/lib/base/EventTypes.h @@ -350,8 +350,7 @@ class ClientProxyEvents : public EventTypes { public: ClientProxyEvents() : m_ready(Event::kUnknown), - m_disconnected(Event::kUnknown), - m_clipboardChanged(Event::kUnknown) { } + m_disconnected(Event::kUnknown) { } //! @name accessors //@{ @@ -371,20 +370,11 @@ public: */ 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: Event::Type m_ready; Event::Type m_disconnected; - Event::Type m_clipboardChanged; }; class ClientProxyUnknownEvents : public EventTypes { @@ -634,7 +624,6 @@ public: IScreenEvents() : m_error(Event::kUnknown), m_shapeChanged(Event::kUnknown), - m_clipboardGrabbed(Event::kUnknown), m_suspend(Event::kUnknown), m_resume(Event::kUnknown), m_fileChunkSending(Event::kUnknown), @@ -657,14 +646,6 @@ public: */ 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 /*! Returns the suspend event type. This is sent whenever the system goes @@ -690,9 +671,49 @@ public: private: Event::Type m_error; Event::Type m_shapeChanged; - Event::Type m_clipboardGrabbed; Event::Type m_suspend; Event::Type m_resume; Event::Type m_fileChunkSending; 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; +}; diff --git a/src/lib/base/IEventQueue.h b/src/lib/base/IEventQueue.h index 25c7bbd5..0dcc088a 100644 --- a/src/lib/base/IEventQueue.h +++ b/src/lib/base/IEventQueue.h @@ -48,6 +48,7 @@ class ServerAppEvents; class IKeyStateEvents; class IPrimaryScreenEvents; class IScreenEvents; +class ClipboardEvents; //! Event queue interface /*! @@ -244,4 +245,5 @@ public: virtual IKeyStateEvents& forIKeyState() = 0; virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0; virtual IScreenEvents& forIScreen() = 0; + virtual ClipboardEvents& forClipboard() = 0; }; diff --git a/src/lib/base/String.cpp b/src/lib/base/String.cpp index 5760e4d4..99a2bb43 100644 --- a/src/lib/base/String.cpp +++ b/src/lib/base/String.cpp @@ -207,6 +207,23 @@ removeChar(String& subject, const char c) 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 // diff --git a/src/lib/base/String.h b/src/lib/base/String.h index 33e4f3d8..de34ad0a 100644 --- a/src/lib/base/String.h +++ b/src/lib/base/String.h @@ -84,10 +84,21 @@ void uppercase(String& subject); //! 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); +//! 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 /*! diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 849dd102..ff57d540 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -22,12 +22,13 @@ #include "client/ServerProxy.h" #include "synergy/Screen.h" #include "synergy/Clipboard.h" +#include "synergy/FileChunk.h" #include "synergy/DropHelper.h" #include "synergy/PacketStreamFilter.h" #include "synergy/ProtocolUtil.h" #include "synergy/protocol_types.h" #include "synergy/XSynergy.h" -#include "synergy/FileChunker.h" +#include "synergy/StreamChunker.h" #include "synergy/IPlatformScreen.h" #include "mt/Thread.h" #include "net/TCPSocket.h" @@ -78,7 +79,8 @@ Client::Client( m_writeToDropDirThread(NULL), m_socket(NULL), m_useSecureNetwork(false), - m_args(args) + m_args(args), + m_sendClipboardThread(NULL) { assert(m_socketFactory != NULL); assert(m_screen != NULL); @@ -263,13 +265,16 @@ Client::leave() m_screen->leave(); m_active = false; - - // send clipboards that we own and that have changed - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - if (m_ownClipboard[id]) { - sendClipboard(id); - } + + if (m_sendClipboardThread != NULL) { + StreamChunker::interruptClipboard(); } + + m_sendClipboardThread = new Thread( + new TMethodJob( + this, + &Client::sendClipboardThread, + NULL)); return true; } @@ -422,12 +427,12 @@ Client::sendConnectionFailedEvent(const char* msg) void Client::sendFileChunk(const void* data) { - FileChunker::FileChunk* fileChunk = reinterpret_cast(const_cast(data)); - LOG((CLOG_DEBUG1 "sendFileChunk")); + FileChunk* chunk = reinterpret_cast(const_cast(data)); + LOG((CLOG_DEBUG1 "send file chunk")); assert(m_server != NULL); // 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 @@ -487,7 +492,7 @@ Client::setupScreen() getEventTarget(), new TMethodEventJob(this, &Client::handleShapeChanged)); - m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), + m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), getEventTarget(), new TMethodEventJob(this, &Client::handleClipboardGrabbed)); @@ -545,7 +550,7 @@ Client::cleanupScreen() } m_events->removeHandler(m_events->forIScreen().shapeChanged(), getEventTarget()); - m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), + m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), getEventTarget()); delete m_server; 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 Client::handleStopRetry(const Event&, void*) { @@ -768,25 +784,6 @@ Client::writeToDropDirThread(void*) 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 Client::dragInfoReceived(UInt32 fileNum, String data) { @@ -810,6 +807,10 @@ Client::isReceivedFileSizeValid() void Client::sendFileToServer(const char* filename) { + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + } + m_sendFileThread = new Thread( new TMethodJob( this, &Client::sendFileThread, @@ -821,7 +822,7 @@ Client::sendFileThread(void* filename) { try { char* name = reinterpret_cast(filename); - FileChunker::sendFileChunks(name, m_events, this); + StreamChunker::sendFile(name, m_events, this); } catch (std::runtime_error error) { LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index 62bc2473..3a19d853 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -85,15 +85,6 @@ public: */ 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 void dragInfoReceived(UInt32 fileNum, String data); @@ -131,7 +122,10 @@ public: bool isReceivedFileSizeValid(); //! 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 handleStopRetry(const Event&, void*); void onFileRecieveCompleted(); + void sendClipboardThread(void*); public: bool m_mock; @@ -224,4 +219,5 @@ private: TCPSocket* m_socket; bool m_useSecureNetwork; ClientArgs& m_args; + Thread* m_sendClipboardThread; }; diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index 914dd4ee..935c9a31 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -19,6 +19,9 @@ #include "client/ServerProxy.h" #include "client/Client.h" +#include "synergy/FileChunk.h" +#include "synergy/ClipboardChunk.h" +#include "synergy/StreamChunker.h" #include "synergy/Clipboard.h" #include "synergy/ProtocolUtil.h" #include "synergy/option_types.h" @@ -35,8 +38,6 @@ // ServerProxy // -const UInt16 ServerProxy::m_intervalThreshold = 1; - ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* events) : m_client(client), m_stream(stream), @@ -51,10 +52,7 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* m_keepAliveAlarm(0.0), m_keepAliveAlarmTimer(NULL), m_parser(&ServerProxy::parseHandshakeMessage), - m_events(events), - m_stopwatch(true), - m_elapsedTime(0), - m_receivedDataSize(0) + m_events(events) { assert(m_client != NULL); assert(m_stream != NULL); @@ -69,6 +67,11 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* new TMethodEventJob(this, &ServerProxy::handleData)); + m_events->adoptHandler(m_events->forClipboard().clipboardSending(), + this, + new TMethodEventJob(this, + &ServerProxy::handleClipboardSendingEvent)); + // send heartbeat setKeepAliveRate(kKeepAliveRate); } @@ -358,8 +361,11 @@ void ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) { String data = IClipboard::marshall(clipboard); - LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size())); - ProtocolUtil::writef(m_stream, kMsgDClipboard, id, m_seqNum, &data); + LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum)); + + StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); + + LOG((CLOG_DEBUG "sent clipboard size=%d", data.size())); } void @@ -545,21 +551,20 @@ void ServerProxy::setClipboard() { // parse + static String dataCached; ClipboardID id; - UInt32 seqNum; - String data; - ProtocolUtil::readf(m_stream, kMsgDClipboard + 4, &id, &seqNum, &data); - LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size())); + UInt32 seq; + + int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); - // validate - if (id >= kClipboardEnd) { - return; + if (r == kFinish) { + LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size())); + + // forward + Clipboard clipboard; + clipboard.unmarshall(dataCached, 0); + m_client->setClipboard(id, &clipboard); } - - // forward - Clipboard clipboard; - clipboard.unmarshall(data, 0); - m_client->setClipboard(id, &clipboard); } void @@ -851,50 +856,13 @@ ServerProxy::infoAcknowledgment() void ServerProxy::fileChunkReceived() { - // parse - UInt8 mark = 0; - String content; - ProtocolUtil::readf(m_stream, kMsgDFileTransfer + 4, &mark, &content); + int result = FileChunk::assemble( + m_stream, + m_client->getReceivedFileData(), + m_client->getExpectedFileSize()); - switch (mark) { - 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: + if (result == kFinish) { 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; } } @@ -909,26 +877,16 @@ ServerProxy::dragInfoReceived() m_client->dragInfoReceived(fileNum, content); } +void +ServerProxy::handleClipboardSendingEvent(const Event& event, void*) +{ + ClipboardChunk::send(m_stream, event.getData()); +} + void ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) { - String chunk(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(m_stream, kMsgDFileTransfer, mark, &chunk); + FileChunk::send(m_stream, mark, data, dataSize); } void diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index 336c640b..144fd000 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -106,6 +106,7 @@ private: void infoAcknowledgment(); void fileChunkReceived(); void dragInfoReceived(); + void handleClipboardSendingEvent(const Event&, void*); private: typedef EResult (ServerProxy::*MessageParser)(const UInt8*); @@ -129,9 +130,4 @@ private: MessageParser m_parser; IEventQueue* m_events; - - Stopwatch m_stopwatch; - double m_elapsedTime; - size_t m_receivedDataSize; - static const UInt16 m_intervalThreshold; }; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index 9252466c..efb92266 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -433,8 +433,8 @@ MSWindowsScreen::checkClipboards() if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) { LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); m_ownClipboard = false; - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); } } @@ -1501,8 +1501,8 @@ MSWindowsScreen::onClipboardChange() if (m_ownClipboard) { LOG((CLOG_DEBUG "clipboard changed: lost ownership")); m_ownClipboard = false; - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); } } else if (!m_ownClipboard) { diff --git a/src/lib/platform/OSXScreen.cpp b/src/lib/platform/OSXScreen.cpp index 2a92d51f..acdeb4d5 100644 --- a/src/lib/platform/OSXScreen.cpp +++ b/src/lib/platform/OSXScreen.cpp @@ -954,8 +954,8 @@ OSXScreen::checkClipboards() LOG((CLOG_DEBUG2 "checking clipboard")); if (m_pasteboard.synchronize()) { LOG((CLOG_DEBUG "clipboard changed")); - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); } } diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index 14486de9..067c4f3e 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -1325,7 +1325,7 @@ XWindowsScreen::handleSystemEvent(const Event& event, void*) if (id != kClipboardEnd) { LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); m_clipboard[id]->lost(xevent->xselectionclear.time); - sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), id); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), id); return; } } diff --git a/src/lib/plugin/CMakeLists.txt b/src/lib/plugin/CMakeLists.txt index 0989ab27..81416002 100644 --- a/src/lib/plugin/CMakeLists.txt +++ b/src/lib/plugin/CMakeLists.txt @@ -19,6 +19,7 @@ if (WIN32) endif() if (APPLE) + # 10.7 should be supported, but gives is a _NXArgv linker error if (OSX_TARGET_MINOR GREATER 7) add_subdirectory(ns) endif() diff --git a/src/lib/plugin/ns/CMakeLists.txt b/src/lib/plugin/ns/CMakeLists.txt index 765ea795..3e40a472 100644 --- a/src/lib/plugin/ns/CMakeLists.txt +++ b/src/lib/plugin/ns/CMakeLists.txt @@ -85,18 +85,20 @@ if (WIN32) ..\\..\\..\\..\\..\\ext\\${OPENSSL_PLAT_DIR}\\out32dll\\ssleay32.* ..\\..\\..\\..\\..\\bin\\${CMAKE_CFG_INTDIR} ) -else() +endif() + +if (UNIX) if (APPLE) add_custom_command( TARGET ns POST_BUILD COMMAND mkdir -p - ${CMAKE_SOURCE_DIR}/bin/plugins + ${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins && cp ${CMAKE_SOURCE_DIR}/lib/${CMAKE_CFG_INTDIR}/libns.* - ${CMAKE_SOURCE_DIR}/bin/plugins/ + ${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins/ ) else() if (CMAKE_BUILD_TYPE STREQUAL Debug) @@ -104,11 +106,11 @@ else() TARGET ns POST_BUILD COMMAND mkdir -p - ${CMAKE_SOURCE_DIR}/bin/plugins + ${CMAKE_SOURCE_DIR}/bin/debug/plugins && cp ${CMAKE_SOURCE_DIR}/lib/debug/libns.* - ${CMAKE_SOURCE_DIR}/bin/plugins/ + ${CMAKE_SOURCE_DIR}/bin/debug/plugins/ ) else() add_custom_command( diff --git a/src/lib/plugin/ns/SecureSocket.cpp b/src/lib/plugin/ns/SecureSocket.cpp index 91f90bce..9d1c10f5 100644 --- a/src/lib/plugin/ns/SecureSocket.cpp +++ b/src/lib/plugin/ns/SecureSocket.cpp @@ -35,7 +35,12 @@ // #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 kFingerprintLocalFilename[] = "Local.txt"; @@ -52,8 +57,8 @@ SecureSocket::SecureSocket( SocketMultiplexer* socketMultiplexer) : TCPSocket(events, socketMultiplexer), 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) : TCPSocket(events, socketMultiplexer, socket), 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: // connection closed isFatal(true); - LOG((CLOG_DEBUG "SSL connection has been closed")); + LOG((CLOG_DEBUG "ssl connection closed")); break; case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: - retry += 1; - // If there are a lot of retrys, it's worth warning about - if ( retry % 5 == 0 ) { - LOG((CLOG_DEBUG "need to retry the same SSL function(%d) retry:%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)); - } + // it seems like these sort of errors are part of openssl's normal behavior, + // so we should expect a very high amount of these. sleeping doesn't seem to + // help... maybe you just have to swallow the errors (yuck). + retry++; + LOG((CLOG_DEBUG2 "passive ssl error, error=%d, attempt=%d", errorCode, retry)); break; 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 (status == 0) { - LOG((CLOG_ERR "an EOF violates the protocol")); + LOG((CLOG_ERR "eof violates ssl protocol")); } else if (status == -1) { // underlying socket I/O reproted an error @@ -433,19 +432,19 @@ SecureSocket::checkResult(int status, int& retry) break; case SSL_ERROR_SSL: - LOG((CLOG_ERR "a failure in the SSL library occurred")); + LOG((CLOG_ERR "ssl error occurred (generic failure)")); isFatal(true); break; default: - LOG((CLOG_ERR "unknown secure socket error")); + LOG((CLOG_ERR "ssl error occurred (unknown failure)")); isFatal(true); break; } // If the retry max would exceed the allowed, treat it as a fatal error if (retry > maxRetry()) { - LOG((CLOG_ERR "Maximum retry count exceeded:%d",retry)); + LOG((CLOG_ERR "passive ssl error limit exceeded: %d", retry)); isFatal(true); } diff --git a/src/lib/server/ClientListener.h b/src/lib/server/ClientListener.h index c0b35ab3..70ecb2eb 100644 --- a/src/lib/server/ClientListener.h +++ b/src/lib/server/ClientListener.h @@ -64,6 +64,9 @@ public: //! Get server which owns this listener Server* getServer() { return m_server; } + //! Return true if using secure network connection + bool isSecure() { return m_useSecureNetwork; } + //@} private: diff --git a/src/lib/server/ClientProxy1_0.cpp b/src/lib/server/ClientProxy1_0.cpp index 9cca86d6..c124624f 100644 --- a/src/lib/server/ClientProxy1_0.cpp +++ b/src/lib/server/ClientProxy1_0.cpp @@ -275,16 +275,7 @@ ClientProxy1_0::leave() void ClientProxy1_0::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(); - LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); - ProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); - } + // ignore -- deprecated in protocol 1.0 } void @@ -450,33 +441,8 @@ ClientProxy1_0::recvInfo() bool ClientProxy1_0::recvClipboard() { - // parse message - ClipboardID id; - UInt32 seqNum; - String data; - if (!ProtocolUtil::readf(getStream(), - kMsgDClipboard + 4, &id, &seqNum, &data)) { - 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; + // deprecated in protocol 1.0 + return false; } bool @@ -499,13 +465,12 @@ ClientProxy1_0::recvGrabClipboard() ClipboardInfo* info = new ClipboardInfo; info->m_id = id; info->m_sequenceNumber = seqNum; - m_events->addEvent(Event(m_events->forIScreen().clipboardGrabbed(), + m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(), getEventTarget(), info)); return true; } - // // ClientProxy1_0::ClientClipboard // diff --git a/src/lib/server/ClientProxy1_0.h b/src/lib/server/ClientProxy1_0.h index 57f4a1fe..4c7f020b 100644 --- a/src/lib/server/ClientProxy1_0.h +++ b/src/lib/server/ClientProxy1_0.h @@ -70,7 +70,7 @@ protected: virtual void resetHeartbeatTimer(); virtual void addHeartbeatTimer(); virtual void removeHeartbeatTimer(); - + virtual bool recvClipboard(); private: void disconnect(); void removeHandlers(); @@ -81,11 +81,9 @@ private: void handleFlatline(const Event&, void*); bool recvInfo(); - bool recvClipboard(); bool recvGrabClipboard(); -private: - typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*); +protected: struct ClientClipboard { public: ClientClipboard(); @@ -96,8 +94,12 @@ private: bool m_dirty; }; - ClientInfo m_info; ClientClipboard m_clipboard[kClipboardEnd]; + +private: + typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*); + + ClientInfo m_info; double m_heartbeatAlarm; EventQueueTimer* m_heartbeatTimer; MessageParser m_parser; diff --git a/src/lib/server/ClientProxy1_5.cpp b/src/lib/server/ClientProxy1_5.cpp index 43016db3..bc8ddde6 100644 --- a/src/lib/server/ClientProxy1_5.cpp +++ b/src/lib/server/ClientProxy1_5.cpp @@ -18,22 +18,21 @@ #include "server/ClientProxy1_5.h" #include "server/Server.h" +#include "synergy/FileChunk.h" +#include "synergy/StreamChunker.h" #include "synergy/ProtocolUtil.h" #include "io/IStream.h" #include "base/Log.h" +#include + // // ClientProxy1_5 // -const UInt16 ClientProxy1_5::m_intervalThreshold = 1; - ClientProxy1_5::ClientProxy1_5(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) : ClientProxy1_4(name, stream, server, events), - m_events(events), - m_stopwatch(true), - m_elapsedTime(0), - m_receivedDataSize(0) + m_events(events) { } @@ -52,23 +51,7 @@ ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size) void ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize) { - String chunk(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); + FileChunk::send(getStream(), mark, data, dataSize); } bool @@ -90,51 +73,15 @@ ClientProxy1_5::parseMessage(const UInt8* code) void ClientProxy1_5::fileChunkReceived() { - // parse - UInt8 mark = 0; - String content; - ProtocolUtil::readf(getStream(), kMsgDFileTransfer + 4, &mark, &content); - Server* server = getServer(); - switch (mark) { - case kFileStart: - server->clearReceivedFileData(); - server->setExpectedFileSize(content); - if (CLOG->getFilter() >= kDEBUG2) { - LOG((CLOG_DEBUG2 "recv file data from client: file size=%s", content.c_str())); - m_stopwatch.start(); - } - break; + int result = FileChunk::assemble( + getStream(), + server->getReceivedFileData(), + server->getExpectedFileSize()); + - 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; - m_elapsedTime += interval; - m_stopwatch.reset(); - } - } - break; - - case kFileEnd: + if (result == kFinish) { 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; } } diff --git a/src/lib/server/ClientProxy1_5.h b/src/lib/server/ClientProxy1_5.h index d3016eed..3347f170 100644 --- a/src/lib/server/ClientProxy1_5.h +++ b/src/lib/server/ClientProxy1_5.h @@ -19,6 +19,7 @@ #include "server/ClientProxy1_4.h" #include "base/Stopwatch.h" +#include "common/stdvector.h" class Server; class IEventQueue; @@ -37,9 +38,4 @@ public: private: IEventQueue* m_events; - - Stopwatch m_stopwatch; - double m_elapsedTime; - size_t m_receivedDataSize; - static const UInt16 m_intervalThreshold; }; diff --git a/src/lib/server/ClientProxy1_6.cpp b/src/lib/server/ClientProxy1_6.cpp new file mode 100644 index 00000000..a27e8ce6 --- /dev/null +++ b/src/lib/server/ClientProxy1_6.cpp @@ -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 . + */ + +#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(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; +} diff --git a/src/lib/synergy/FileChunker.h b/src/lib/server/ClientProxy1_6.h similarity index 57% rename from src/lib/synergy/FileChunker.h rename to src/lib/server/ClientProxy1_6.h index 38db8166..d8c69b11 100644 --- a/src/lib/synergy/FileChunker.h +++ b/src/lib/server/ClientProxy1_6.h @@ -1,6 +1,6 @@ /* * 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 * modify it under the terms of the GNU General Public License @@ -17,30 +17,23 @@ #pragma once -#include "base/String.h" +#include "server/ClientProxy1_5.h" +class Server; class IEventQueue; -class FileChunker { +//! Proxy for client implementing protocol version 1.6 +class ClientProxy1_6 : public ClientProxy1_5 { public: - //! FileChunk data - class FileChunk { - public: - FileChunk(size_t chunkSize) : m_dataSize(chunkSize - 2) - { - m_chunk = new char[chunkSize]; - } + ClientProxy1_6(const String& name, synergy::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_6(); - ~FileChunk() { delete[] m_chunk; } - - public: - const size_t m_dataSize; - char* m_chunk; - }; - - static void sendFileChunks(char* filename, IEventQueue* events, void* eventTarget); - static String intToString(size_t i); + virtual void setClipboard(ClipboardID id, const IClipboard* clipboard); + virtual bool recvClipboard(); private: - static const size_t m_chunkSize; + void handleClipboardSendingEvent(const Event&, void*); + +private: + IEventQueue* m_events; }; diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp index 28191792..a328d213 100644 --- a/src/lib/server/ClientProxyUnknown.cpp +++ b/src/lib/server/ClientProxyUnknown.cpp @@ -25,6 +25,7 @@ #include "server/ClientProxy1_3.h" #include "server/ClientProxy1_4.h" #include "server/ClientProxy1_5.h" +#include "server/ClientProxy1_6.h" #include "synergy/protocol_types.h" #include "synergy/ProtocolUtil.h" #include "synergy/XSynergy.h" @@ -227,6 +228,10 @@ ClientProxyUnknown::handleData(const Event&, void*) case 5: m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); break; + + case 6: + m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); + break; } } diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 0bea6aa9..8b7c4afd 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -22,13 +22,14 @@ #include "server/ClientProxyUnknown.h" #include "server/PrimaryClient.h" #include "server/ClientListener.h" +#include "synergy/FileChunk.h" #include "synergy/IPlatformScreen.h" #include "synergy/DropHelper.h" #include "synergy/option_types.h" #include "synergy/protocol_types.h" #include "synergy/XScreen.h" #include "synergy/XSynergy.h" -#include "synergy/FileChunker.h" +#include "synergy/StreamChunker.h" #include "synergy/KeyState.h" #include "synergy/Screen.h" #include "synergy/PacketStreamFilter.h" @@ -91,7 +92,8 @@ Server::Server( m_ignoreFileTransfer(false), m_enableDragDrop(enableDragDrop), m_getDragInfoThread(NULL), - m_waitDragInfoThread(true) + m_waitDragInfoThread(true), + m_sendClipboardThread(NULL) { // must have a primary client and it must have a canonical name assert(m_primaryClient != NULL); @@ -503,11 +505,18 @@ Server::switchScreen(BaseClientProxy* dst, m_active->enter(x, y, m_seqNum, m_primaryClient->getToggleMask(), forScreensaver); - - // send the clipboard data to new active screen - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - m_active->setClipboard(id, &m_clipboards[id].m_clipboard); + // 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 + m_sendClipboardThread = new Thread( + new TMethodJob( + this, + &Server::sendClipboardThread, + NULL)); Server::SwitchToScreenInfo* info = 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 Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy) { @@ -2023,13 +2040,13 @@ Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta) void Server::onFileChunkSending(const void* data) { - FileChunker::FileChunk* fileChunk = reinterpret_cast(const_cast(data)); + FileChunk* chunk = reinterpret_cast(const_cast(data)); - LOG((CLOG_DEBUG1 "onFileChunkSending")); + LOG((CLOG_DEBUG1 "sending file chunk")); assert(m_active != NULL); // 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 @@ -2068,11 +2085,11 @@ Server::addClient(BaseClientProxy* client) client->getEventTarget(), new TMethodEventJob(this, &Server::handleShapeChanged, client)); - m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), + m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), client->getEventTarget(), new TMethodEventJob(this, &Server::handleClipboardGrabbed, client)); - m_events->adoptHandler(m_events->forClientProxy().clipboardChanged(), + m_events->adoptHandler(m_events->forClipboard().clipboardChanged(), client->getEventTarget(), new TMethodEventJob(this, &Server::handleClipboardChanged, client)); @@ -2104,9 +2121,9 @@ Server::removeClient(BaseClientProxy* client) // remove event handlers m_events->removeHandler(m_events->forIScreen().shapeChanged(), client->getEventTarget()); - m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), + m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), client->getEventTarget()); - m_events->removeHandler(m_events->forClientProxy().clipboardChanged(), + m_events->removeHandler(m_events->forClipboard().clipboardChanged(), client->getEventTarget()); // remove from list @@ -2326,25 +2343,6 @@ Server::KeyboardBroadcastInfo::alloc(State state, const String& screens) 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 Server::isReceivedFileSizeValid() { @@ -2354,6 +2352,10 @@ Server::isReceivedFileSizeValid() void Server::sendFileToClient(const char* filename) { + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + } + m_sendFileThread = new Thread( new TMethodJob( this, &Server::sendFileThread, @@ -2366,7 +2368,7 @@ Server::sendFileThread(void* data) try { char* filename = reinterpret_cast(data); 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) { 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); } + +bool +Server::isSecure() const +{ + return m_clientListener->isSecure(); +} diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index 742a0a9c..63551efa 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -141,15 +141,6 @@ public: */ 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 void sendFileToClient(const char* filename); @@ -178,8 +169,14 @@ public: //! Return true if recieved file size is valid bool isReceivedFileSizeValid(); - //! Return expected file size - size_t getExpectedFileSize() { return m_expectedFileSize; } + //! Return expected file data size + 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 void sendDragInfo(BaseClientProxy* newScreen); + // thread funciton for sending clipboard + void sendClipboardThread(void*); + public: bool m_mock; @@ -480,4 +480,6 @@ private: bool m_waitDragInfoThread; ClientListener* m_clientListener; + + Thread* m_sendClipboardThread; }; diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index bcc731ab..a2021481 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -17,6 +17,7 @@ #include "synergy/ArgParser.h" +#include "synergy/StreamChunker.h" #include "synergy/App.h" #include "synergy/ServerArgs.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")) { argsBase().m_enableCrypto = true; + StreamChunker::updateChunkSize(true); } else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) { argsBase().m_profileDirectory = argv[++i]; diff --git a/src/lib/synergy/Chunk.cpp b/src/lib/synergy/Chunk.cpp new file mode 100644 index 00000000..fe4aeae7 --- /dev/null +++ b/src/lib/synergy/Chunk.cpp @@ -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 . + */ + +#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; +} diff --git a/src/lib/synergy/Chunk.h b/src/lib/synergy/Chunk.h new file mode 100644 index 00000000..13431950 --- /dev/null +++ b/src/lib/synergy/Chunk.h @@ -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 . + */ + +#pragma once + +#include "common/basic_types.h" + +class Chunk { +public: + Chunk(size_t size); + ~Chunk(); + +public: + size_t m_dataSize; + char* m_chunk; +}; diff --git a/src/lib/synergy/ClipboardChunk.cpp b/src/lib/synergy/ClipboardChunk.cpp new file mode 100644 index 00000000..0a0cc5c6 --- /dev/null +++ b/src/lib/synergy/ClipboardChunk.cpp @@ -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 . + */ + +#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(&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(&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(&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(data); + + LOG((CLOG_DEBUG1 "sending clipboard chunk")); + + char* chunk = clipboardData->m_chunk; + ClipboardID id = chunk[0]; + UInt32* seq = reinterpret_cast(&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); +} diff --git a/src/lib/synergy/ClipboardChunk.h b/src/lib/synergy/ClipboardChunk.h new file mode 100644 index 00000000..3715b941 --- /dev/null +++ b/src/lib/synergy/ClipboardChunk.h @@ -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 . + */ + +#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); +}; diff --git a/src/lib/synergy/FileChunk.cpp b/src/lib/synergy/FileChunk.cpp new file mode 100644 index 00000000..7a843cb4 --- /dev/null +++ b/src/lib/synergy/FileChunk.cpp @@ -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 . + */ + +#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); +} diff --git a/src/lib/synergy/FileChunk.h b/src/lib/synergy/FileChunk.h new file mode 100644 index 00000000..2c2e0ee5 --- /dev/null +++ b/src/lib/synergy/FileChunk.h @@ -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 . + */ + +#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); +}; diff --git a/src/lib/synergy/FileChunker.cpp b/src/lib/synergy/FileChunker.cpp deleted file mode 100644 index 7936ecc6..00000000 --- a/src/lib/synergy/FileChunker.cpp +++ /dev/null @@ -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 . - */ - -#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 -#include - -#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(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(); -} diff --git a/src/lib/synergy/StreamChunker.cpp b/src/lib/synergy/StreamChunker.cpp new file mode 100644 index 00000000..46230ccd --- /dev/null +++ b/src/lib/synergy/StreamChunker.cpp @@ -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 . + */ + +#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 + +#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(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(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")); + } +} diff --git a/src/lib/synergy/StreamChunker.h b/src/lib/synergy/StreamChunker.h new file mode 100644 index 00000000..b0bfb341 --- /dev/null +++ b/src/lib/synergy/StreamChunker.h @@ -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 . + */ + +#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; +}; diff --git a/src/lib/synergy/protocol_types.cpp b/src/lib/synergy/protocol_types.cpp index 9f2eef9d..1e274b06 100644 --- a/src/lib/synergy/protocol_types.cpp +++ b/src/lib/synergy/protocol_types.cpp @@ -41,7 +41,7 @@ const char* kMsgDMouseMove = "DMMV%2i%2i"; const char* kMsgDMouseRelMove = "DMRM%2i%2i"; const char* kMsgDMouseWheel = "DMWM%2i%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* kMsgDSetOptions = "DSOP%4I"; const char* kMsgDFileTransfer = "DFTR%1i%s"; diff --git a/src/lib/synergy/protocol_types.h b/src/lib/synergy/protocol_types.h index 69f18f11..0fc0c265 100644 --- a/src/lib/synergy/protocol_types.h +++ b/src/lib/synergy/protocol_types.h @@ -28,9 +28,10 @@ // adds horizontal mouse scrolling // 1.4: adds crypto support // 1.5: adds file transfer and removes home brew crypto +// 1.6: adds clipboard streaming // NOTE: with new version, synergy minor version should increment static const SInt16 kProtocolMajorVersion = 1; -static const SInt16 kProtocolMinorVersion = 5; +static const SInt16 kProtocolMinorVersion = 6; // default contact port number static const UInt16 kDefaultPort = 24800; @@ -69,13 +70,19 @@ enum EDirectionMask { kBottomMask = 1 << kBottom }; -// file transfer constants -enum EFileTransfer { - kFileStart = 1, - kFileChunk = 2, - kFileEnd = 3 +// Data transfer constants +enum EDataTransfer { + kDataStart = 1, + kDataChunk = 2, + kDataEnd = 3 }; +// Data received constants +enum EDataReceived { + kNotFinish, + kFinish, + kError +}; // // 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; // 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 // sequence number from the most recent kMsgCEnter. $1 = clipboard // identifier. diff --git a/src/test/integtests/net/NetworkTests.cpp b/src/test/integtests/net/NetworkTests.cpp index 94d9cac0..cd9be3c2 100644 --- a/src/test/integtests/net/NetworkTests.cpp +++ b/src/test/integtests/net/NetworkTests.cpp @@ -28,7 +28,8 @@ #include "server/Server.h" #include "server/ClientListener.h" #include "client/Client.h" -#include "synergy/FileChunker.h" +#include "synergy/FileChunk.h" +#include "synergy/StreamChunker.h" #include "net/SocketMultiplexer.h" #include "net/NetworkAddress.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 getCursorPos(SInt32& x, SInt32& y); -String intToString(size_t i); UInt8* newMockData(size_t size); void createFile(fstream& file, const char* filename, size_t size); @@ -415,38 +415,28 @@ void NetworkTests::sendMockData(void* eventTarget) { // send first message (file size) - String size = intToString(kMockDataSize); - size_t sizeLength = size.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'; + String size = synergy::string::sizeTypeToString(kMockDataSize); + FileChunk* sizeMessage = FileChunk::start(size); + m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, sizeMessage)); // send chunk messages with incrementing chunk size size_t lastSize = 0; size_t sentLength = 0; while (true) { - size_t chunkSize = lastSize + kMockDataChunkIncrement; + size_t dataSize = lastSize + kMockDataChunkIncrement; // make sure we don't read too much from the mock data. - if (sentLength + chunkSize > kMockDataSize) { - chunkSize = kMockDataSize - sentLength; + if (sentLength + dataSize > kMockDataSize) { + dataSize = kMockDataSize - sentLength; } // first byte is the chunk mark, last is \0 - FileChunker::FileChunk* fileChunk = new FileChunker::FileChunk(chunkSize + 2); - char* chunkData = fileChunk->m_chunk; + FileChunk* chunk = FileChunk::data(m_mockData, dataSize); + m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, chunk)); - chunkData[0] = kFileChunk; - memcpy(&chunkData[1], &m_mockData[sentLength], chunkSize); - chunkData[chunkSize + 1] = '\0'; - m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, fileChunk)); - - sentLength += chunkSize; - lastSize = chunkSize; + sentLength += dataSize; + lastSize = dataSize; if (sentLength == kMockDataSize) { break; @@ -455,11 +445,7 @@ NetworkTests::sendMockData(void* eventTarget) } // send last message - FileChunker::FileChunk* transferFinished = new FileChunker::FileChunk(2); - chunkData = transferFinished->m_chunk; - - chunkData[0] = kFileEnd; - chunkData[1] = '\0'; + FileChunk* transferFinished = FileChunk::end(); m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, transferFinished)); } @@ -527,12 +513,4 @@ getCursorPos(SInt32& x, SInt32& y) y = 0; } -String -intToString(size_t i) -{ - stringstream ss; - ss << i; - return ss.str(); -} - #endif // WINAPI_CARBON diff --git a/src/test/mock/synergy/MockEventQueue.h b/src/test/mock/synergy/MockEventQueue.h index 7977b41d..d6256790 100644 --- a/src/test/mock/synergy/MockEventQueue.h +++ b/src/test/mock/synergy/MockEventQueue.h @@ -61,5 +61,6 @@ public: MOCK_METHOD0(forIKeyState, IKeyStateEvents&()); MOCK_METHOD0(forIPrimaryScreen, IPrimaryScreenEvents&()); MOCK_METHOD0(forIScreen, IScreenEvents&()); + MOCK_METHOD0(forClipboard, ClipboardEvents&()); MOCK_CONST_METHOD0(waitForReady, void()); }; diff --git a/src/test/unittests/base/StringTests.cpp b/src/test/unittests/base/StringTests.cpp index 18694302..6381cac5 100644 --- a/src/test/unittests/base/StringTests.cpp +++ b/src/test/unittests/base/StringTests.cpp @@ -82,3 +82,21 @@ TEST(StringTests, removeChar) 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); +} diff --git a/src/test/unittests/ipc/IpcLogOutputterTests.cpp b/src/test/unittests/ipc/IpcLogOutputterTests.cpp index 254752c4..9233256b 100644 --- a/src/test/unittests/ipc/IpcLogOutputterTests.cpp +++ b/src/test/unittests/ipc/IpcLogOutputterTests.cpp @@ -120,7 +120,9 @@ TEST(IpcLogOutputterTests, write_overBufferRateLimit_lastLineTruncated) // after waiting the time limit send another to make sure // 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 4"); outputter.sendBuffer(); diff --git a/src/test/unittests/synergy/ClipboardChunkTests.cpp b/src/test/unittests/synergy/ClipboardChunkTests.cpp new file mode 100644 index 00000000..3666fe45 --- /dev/null +++ b/src/test/unittests/synergy/ClipboardChunkTests.cpp @@ -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 . + */ + +#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; +}