Merge branch 'master' of github.com:synergy/synergy
This commit is contained in:
		
						commit
						d816ed6b43
					
				
							
								
								
									
										36
									
								
								ChangeLog
								
								
								
								
							
							
						
						
									
										36
									
								
								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 #4564 - Modifier keys often stuck down on Mac client | ||||||
| Bug #4581 - Starting GUI on Mac crashes instantly on syntool segfault | Bug #4581 - Starting GUI on Mac crashes instantly on syntool segfault | ||||||
| Bug #4520 - Laggy or sluggish cursor (ping spikes) on Mac when using WiFi | Bug #4520 - Laggy or sluggish cursor (ping spikes) on Mac when using WiFi | ||||||
|  | @ -11,8 +34,8 @@ Enhancement #4569 - Reintroduce GUI auto-hide setting (disabled by default) | ||||||
| Enhancement #4570 - Make `--crypto-pass` show deprecated message | Enhancement #4570 - Make `--crypto-pass` show deprecated message | ||||||
| Enhancement #4596 - Typo 'occurred' in WebClient.cpp | Enhancement #4596 - Typo 'occurred' in WebClient.cpp | ||||||
| 
 | 
 | ||||||
| v1.7.1 | v1.7.1-stable | ||||||
| ====== | ============= | ||||||
| Bug #3784 - Double click & drag doesn't select words on client | Bug #3784 - Double click & drag doesn't select words on client | ||||||
| Bug #3052 - Triple-click (select line) does not work | Bug #3052 - Triple-click (select line) does not work | ||||||
| Bug #4367 - Duplicate Alt-S Keyboard Shortcuts on Gui | Bug #4367 - Duplicate Alt-S Keyboard Shortcuts on Gui | ||||||
|  | @ -38,8 +61,8 @@ Enhancement #4540 - Enable Network Security checkbox only when ns plugin exists | ||||||
| Enhancement #4525 - Reorganize app data directory | Enhancement #4525 - Reorganize app data directory | ||||||
| Enhancement #4390 - Disable GUI auto-hide by default | Enhancement #4390 - Disable GUI auto-hide by default | ||||||
| 
 | 
 | ||||||
| 1.7.0 | v1.7.0-beta | ||||||
| ===== | =========== | ||||||
| Enhancement #4313 - SSL encrypted secure connection | Enhancement #4313 - SSL encrypted secure connection | ||||||
| Enhancement #4168 - Plugin manager for GUI | Enhancement #4168 - Plugin manager for GUI | ||||||
| Enhancement #4307 - Always show client auto-detect dialog | Enhancement #4307 - Always show client auto-detect dialog | ||||||
|  | @ -264,3 +287,4 @@ Feature #3119: Mac OS X secondary screen | ||||||
| Task #2905: Unit tests: Clipboard classes | Task #2905: Unit tests: Clipboard classes | ||||||
| Task #3072: Downgrade Linux build machines | Task #3072: Downgrade Linux build machines | ||||||
| Task #3090: CXWindowsKeyState integ test args wrong | Task #3090: CXWindowsKeyState integ test args wrong | ||||||
|  |   | ||||||
|  | @ -17,7 +17,7 @@ | ||||||
| # TODO: split this file up, it's too long! | # TODO: split this file up, it's too long! | ||||||
| 
 | 
 | ||||||
| import sys, os, ConfigParser, shutil, re, ftputil, zipfile, glob, commands | import sys, os, ConfigParser, shutil, re, ftputil, zipfile, glob, commands | ||||||
| from generators import Generator, EclipseGenerator, XcodeGenerator, MakefilesGenerator | from generators import VisualStudioGenerator, EclipseGenerator, XcodeGenerator, MakefilesGenerator | ||||||
| from getopt import gnu_getopt | from getopt import gnu_getopt | ||||||
| 
 | 
 | ||||||
| if sys.version_info >= (2, 4): | if sys.version_info >= (2, 4): | ||||||
|  | @ -254,12 +254,12 @@ class InternalCommands: | ||||||
| 	gmockDir = 'gmock-1.6.0' | 	gmockDir = 'gmock-1.6.0' | ||||||
| 
 | 
 | ||||||
| 	win32_generators = { | 	win32_generators = { | ||||||
| 		1 : Generator('Visual Studio 10'), | 		1 : VisualStudioGenerator('10'), | ||||||
| 		2 : Generator('Visual Studio 10 Win64'), | 		2 : VisualStudioGenerator('10 Win64'), | ||||||
| 		3 : Generator('Visual Studio 9 2008'), | 		3 : VisualStudioGenerator('9 2008'), | ||||||
| 		4 : Generator('Visual Studio 9 2008 Win64'), | 		4 : VisualStudioGenerator('9 2008 Win64'), | ||||||
| 		5 : Generator('Visual Studio 8 2005'), | 		5 : VisualStudioGenerator('8 2005'), | ||||||
| 		6 : Generator('Visual Studio 8 2005 Win64') | 		6 : VisualStudioGenerator('8 2005 Win64') | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	unix_generators = { | 	unix_generators = { | ||||||
|  | @ -319,7 +319,6 @@ class InternalCommands: | ||||||
| 			self.configure(target) | 			self.configure(target) | ||||||
| 
 | 
 | ||||||
| 	def checkGTest(self): | 	def checkGTest(self): | ||||||
|      |  | ||||||
| 		dir = self.extDir + '/' + self.gtestDir | 		dir = self.extDir + '/' + self.gtestDir | ||||||
| 		if (os.path.isdir(dir)): | 		if (os.path.isdir(dir)): | ||||||
| 			return | 			return | ||||||
|  | @ -335,7 +334,6 @@ class InternalCommands: | ||||||
| 		self.zipExtractAll(zip, dir) | 		self.zipExtractAll(zip, dir) | ||||||
| 
 | 
 | ||||||
| 	def checkGMock(self): | 	def checkGMock(self): | ||||||
|      |  | ||||||
| 		dir = self.extDir + '/' + self.gmockDir | 		dir = self.extDir + '/' + self.gmockDir | ||||||
| 		if (os.path.isdir(dir)): | 		if (os.path.isdir(dir)): | ||||||
| 			return | 			return | ||||||
|  | @ -840,7 +838,7 @@ class InternalCommands: | ||||||
| 		pwd = lines[0] | 		pwd = lines[0] | ||||||
| 		 | 		 | ||||||
| 		if (dist): | 		if (dist): | ||||||
| 			self.signFile(pfx, pwd, 'bin', self.dist_name('win')) | 			self.signFile(pfx, pwd, 'bin/Release', self.dist_name('win')) | ||||||
| 		else: | 		else: | ||||||
| 			self.signFile(pfx, pwd, 'bin/Release', 'synergy.exe') | 			self.signFile(pfx, pwd, 'bin/Release', 'synergy.exe') | ||||||
| 			self.signFile(pfx, pwd, 'bin/Release', 'synergyc.exe') | 			self.signFile(pfx, pwd, 'bin/Release', 'synergyc.exe') | ||||||
|  | @ -974,7 +972,13 @@ class InternalCommands: | ||||||
| 		if p.returncode != 0: | 		if p.returncode != 0: | ||||||
| 			raise Exception('Could not get branch name, git error: ' + str(p.returncode)) | 			raise Exception('Could not get branch name, git error: ' + str(p.returncode)) | ||||||
| 	 | 	 | ||||||
| 		return stdout.strip() | 		result = stdout.strip() | ||||||
|  | 
 | ||||||
|  | 		# sometimes, git will prepend "heads/" infront of the branch name, | ||||||
|  | 		# remove this as it's not useful to us and causes ftp issues. | ||||||
|  | 		result = re.sub("heads/", "", result) | ||||||
|  | 
 | ||||||
|  | 		return result | ||||||
| 
 | 
 | ||||||
| 	def find_revision_svn(self): | 	def find_revision_svn(self): | ||||||
| 		if sys.version_info < (2, 4): | 		if sys.version_info < (2, 4): | ||||||
|  | @ -1100,6 +1104,7 @@ class InternalCommands: | ||||||
| 			err = os.system(cmd) | 			err = os.system(cmd) | ||||||
| 			if err != 0: | 			if err != 0: | ||||||
| 				raise Exception('rpmlint failed: ' + str(err)) | 				raise Exception('rpmlint failed: ' + str(err)) | ||||||
|  | 
 | ||||||
| 		finally: | 		finally: | ||||||
| 			self.restore_chdir() | 			self.restore_chdir() | ||||||
| 
 | 
 | ||||||
|  | @ -1294,7 +1299,7 @@ class InternalCommands: | ||||||
| 			arch) | 			arch) | ||||||
| 			 | 			 | ||||||
| 		old = "bin/Release/synergy.msi" | 		old = "bin/Release/synergy.msi" | ||||||
| 		new = "bin/%s" % (filename) | 		new = "bin/Release/%s" % (filename) | ||||||
| 		 | 		 | ||||||
| 		try: | 		try: | ||||||
| 			os.remove(new) | 			os.remove(new) | ||||||
|  | @ -1359,19 +1364,108 @@ class InternalCommands: | ||||||
| 
 | 
 | ||||||
| 	def distftp(self, type, ftp): | 	def distftp(self, type, ftp): | ||||||
| 		if not type: | 		if not type: | ||||||
| 			raise Exception('Type not specified.') | 			raise Exception('Platform type not specified.') | ||||||
| 		 |  | ||||||
| 		if not ftp: |  | ||||||
| 			raise Exception('FTP info not defined.') |  | ||||||
| 		 | 		 | ||||||
| 		self.loadConfig() | 		self.loadConfig() | ||||||
| 		src = self.dist_name(type) |  | ||||||
| 		dest = self.dist_name_rev(type) |  | ||||||
| 		print 'Uploading %s to FTP server %s...' % (dest, ftp.host) |  | ||||||
| 
 | 
 | ||||||
| 		binDir = self.getGenerator().getBinDir('Release') | 		binDir = self.getGenerator().getBinDir('Release') | ||||||
| 		ftp.run(binDir + '/' + src, dest)  | 
 | ||||||
| 		print 'Done' | 		packageSource = binDir + '/' + self.dist_name(type) | ||||||
|  | 		packageTarget = self.dist_name_rev(type) | ||||||
|  | 		ftp.upload(packageSource, packageTarget) | ||||||
|  | 
 | ||||||
|  | 		if type != 'src': | ||||||
|  | 			pluginsDir = binDir + '/plugins' | ||||||
|  | 			nsPluginSource = self.findLibraryFile(type, pluginsDir, 'ns') | ||||||
|  | 			if nsPluginSource: | ||||||
|  | 				nsPluginTarget = self.getLibraryDistFilename(type, pluginsDir, 'ns') | ||||||
|  | 				ftp.upload(nsPluginSource, nsPluginTarget, "plugins") | ||||||
|  | 
 | ||||||
|  | 	def getLibraryDistFilename(self, type, dir, name): | ||||||
|  | 		(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type) | ||||||
|  | 		branch = self.getGitBranchName() | ||||||
|  | 		revision = self.getGitRevision() | ||||||
|  | 		firstPart = '%s-%s-%s-%s' % (name, branch, revision, platform) | ||||||
|  | 
 | ||||||
|  | 		filename = '%s.%s' % (firstPart, libraryExt) | ||||||
|  | 		if type == 'rpm' or type == 'deb': | ||||||
|  | 			# linux is a bit special, include dist type (deb/rpm in filename) | ||||||
|  | 			filename = '%s-%s.%s' % (firstPart, packageExt, libraryExt) | ||||||
|  | 
 | ||||||
|  | 		return filename | ||||||
|  | 
 | ||||||
|  | 	def findLibraryFile(self, type, dir, name): | ||||||
|  | 		if not os.path.exists(dir): | ||||||
|  | 			return None | ||||||
|  | 
 | ||||||
|  | 		(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type) | ||||||
|  | 		ext = libraryExt | ||||||
|  | 
 | ||||||
|  | 		pattern = name + '\.' + ext | ||||||
|  | 
 | ||||||
|  | 		for filename in os.listdir(dir): | ||||||
|  | 			if re.search(pattern, filename): | ||||||
|  | 				return dir + '/' + filename | ||||||
|  | 		 | ||||||
|  | 		return None | ||||||
|  | 
 | ||||||
|  | 	def getDistributePlatformInfo(self, type): | ||||||
|  | 		ext = None | ||||||
|  | 		libraryExt = None | ||||||
|  | 		platform = None | ||||||
|  | 		 | ||||||
|  | 		if type == 'src': | ||||||
|  | 			ext = 'tar.gz' | ||||||
|  | 			platform = 'Source' | ||||||
|  | 			 | ||||||
|  | 		elif type == 'rpm' or type == 'deb': | ||||||
|  | 			ext = type | ||||||
|  | 			libraryExt = 'so' | ||||||
|  | 			platform = self.getLinuxPlatform() | ||||||
|  | 			 | ||||||
|  | 		elif type == 'win': | ||||||
|  | 			# get platform based on last generator used | ||||||
|  | 			ext = 'msi' | ||||||
|  | 			libraryExt = 'dll' | ||||||
|  | 			generator = self.getGeneratorFromConfig().cmakeName | ||||||
|  | 			if generator.find('Win64') != -1: | ||||||
|  | 				platform = 'Windows-x64' | ||||||
|  | 			else: | ||||||
|  | 				platform = 'Windows-x86' | ||||||
|  | 			 | ||||||
|  | 		elif type == 'mac': | ||||||
|  | 			ext = "dmg" | ||||||
|  | 			libraryExt = 'dylib' | ||||||
|  | 			platform = self.getMacPackageName() | ||||||
|  | 		 | ||||||
|  | 		if not platform: | ||||||
|  | 			raise Exception('Unable to detect distributable platform.') | ||||||
|  | 
 | ||||||
|  | 		return (platform, ext, libraryExt) | ||||||
|  | 
 | ||||||
|  | 	def dist_name(self, type): | ||||||
|  | 		(platform, packageExt, libraryExt) = self.getDistributePlatformInfo(type) | ||||||
|  | 		ext = packageExt | ||||||
|  | 		 | ||||||
|  | 		pattern = ( | ||||||
|  | 			re.escape(self.project + '-') + '\d+\.\d+\.\d+' + | ||||||
|  | 			re.escape('-' + platform + '.' + ext)) | ||||||
|  | 
 | ||||||
|  | 		for filename in os.listdir(self.getBinDir('Release')): | ||||||
|  | 			if re.search(pattern, filename): | ||||||
|  | 				return filename | ||||||
|  | 		 | ||||||
|  | 		# still here? package probably not created yet. | ||||||
|  | 		raise Exception('Could not find package name with pattern: ' + pattern) | ||||||
|  | 	 | ||||||
|  | 	def dist_name_rev(self, type): | ||||||
|  | 		branch = self.getGitBranchName() | ||||||
|  | 		revision = self.getGitRevision() | ||||||
|  | 
 | ||||||
|  | 		# find the version number (we're puting the rev in after this) | ||||||
|  | 		pattern = '(\d+\.\d+\.\d+)' | ||||||
|  | 		replace = "%s-%s" % (branch, revision) | ||||||
|  | 		return re.sub(pattern, replace, self.dist_name(type)) | ||||||
| 	 | 	 | ||||||
| 	def getDebianArch(self): | 	def getDebianArch(self): | ||||||
| 		if os.uname()[4][:3] == 'arm': | 		if os.uname()[4][:3] == 'arm': | ||||||
|  | @ -1405,56 +1499,6 @@ class InternalCommands: | ||||||
| 		else: | 		else: | ||||||
| 			raise Exception("unknown os bits: " + os_bits) | 			raise Exception("unknown os bits: " + os_bits) | ||||||
| 
 | 
 | ||||||
| 	def dist_name(self, type): |  | ||||||
| 		ext = None |  | ||||||
| 		platform = None |  | ||||||
| 		 |  | ||||||
| 		if type == 'src': |  | ||||||
| 			ext = 'tar.gz' |  | ||||||
| 			platform = 'Source' |  | ||||||
| 			 |  | ||||||
| 		elif type == 'rpm' or type == 'deb': |  | ||||||
| 		 |  | ||||||
| 			ext = type |  | ||||||
|                         platform = self.getLinuxPlatform() |  | ||||||
| 			 |  | ||||||
| 		elif type == 'win': |  | ||||||
| 			 |  | ||||||
| 			# get platform based on last generator used |  | ||||||
| 			ext = 'msi' |  | ||||||
| 			generator = self.getGeneratorFromConfig().cmakeName |  | ||||||
| 			if generator.find('Win64') != -1: |  | ||||||
| 				platform = 'Windows-x64' |  | ||||||
| 			else: |  | ||||||
| 				platform = 'Windows-x86' |  | ||||||
| 			 |  | ||||||
| 		elif type == 'mac': |  | ||||||
| 			ext = "dmg" |  | ||||||
| 			platform = self.getMacPackageName() |  | ||||||
| 		 |  | ||||||
| 		if not platform: |  | ||||||
| 			raise Exception('Unable to detect package platform.') |  | ||||||
| 		 |  | ||||||
| 		pattern = re.escape(self.project + '-') + '\d+\.\d+\.\d+' + re.escape('-' + platform + '.' + ext) |  | ||||||
| 		 |  | ||||||
| 		target = '' |  | ||||||
| 		if type == 'mac': |  | ||||||
| 			target = 'Release' |  | ||||||
| 
 |  | ||||||
| 		for filename in os.listdir(self.getBinDir(target)): |  | ||||||
| 			if re.search(pattern, filename): |  | ||||||
| 				return filename |  | ||||||
| 		 |  | ||||||
| 		# still here? package probably not created yet. |  | ||||||
| 		raise Exception('Could not find package name with pattern: ' + pattern) |  | ||||||
| 	 |  | ||||||
| 	def dist_name_rev(self, type): |  | ||||||
| 		# find the version number (we're puting the rev in after this) |  | ||||||
| 		pattern = '(\d+\.\d+\.\d+)' |  | ||||||
| 		replace = "%s-%s" % ( |  | ||||||
| 			self.getGitBranchName(), self.getGitRevision()) |  | ||||||
| 		return re.sub(pattern, replace, self.dist_name(type)) |  | ||||||
| 
 |  | ||||||
| 	def dist_usage(self): | 	def dist_usage(self): | ||||||
| 		print ('Usage: %s package [package-type]\n' | 		print ('Usage: %s package [package-type]\n' | ||||||
| 			'\n' | 			'\n' | ||||||
|  | @ -1926,8 +1970,9 @@ class CommandHandler: | ||||||
| 			elif o == '--dir': | 			elif o == '--dir': | ||||||
| 				dir = a | 				dir = a | ||||||
| 		 | 		 | ||||||
| 		ftp = None | 		if not host: | ||||||
| 		if host: | 			raise Exception('FTP host was not specified.') | ||||||
|  | 
 | ||||||
| 		ftp = ftputil.FtpUploader( | 		ftp = ftputil.FtpUploader( | ||||||
| 			host, user, password, dir) | 			host, user, password, dir) | ||||||
| 		 | 		 | ||||||
|  |  | ||||||
|  | @ -23,13 +23,32 @@ class FtpUploader: | ||||||
| 		self.password = password | 		self.password = password | ||||||
| 		self.dir = dir | 		self.dir = dir | ||||||
| 
 | 
 | ||||||
| 	def run(self, src, dest, replace=False): | 	def upload(self, src, dest, subDir=None): | ||||||
| 		 | 		print "Connecting to '%s'" % self.host | ||||||
| 		ftp = FTP(self.host, self.user, self.password) | 		ftp = FTP(self.host, self.user, self.password) | ||||||
| 		ftp.cwd(self.dir) |  | ||||||
| 
 | 
 | ||||||
|  | 		self.changeDir(ftp, self.dir) | ||||||
|  | 
 | ||||||
|  | 		if subDir: | ||||||
|  | 			self.changeDir(ftp, subDir) | ||||||
|  | 		 | ||||||
|  | 		print "Uploading '%s' as '%s'" % (src, dest) | ||||||
| 		f = open(src, 'rb') | 		f = open(src, 'rb') | ||||||
| 		ftp.storbinary('STOR ' + dest, f) | 		ftp.storbinary('STOR ' + dest, f) | ||||||
| 		f.close() | 		f.close() | ||||||
| 		 | 		 | ||||||
| 		ftp.close() | 		ftp.close() | ||||||
|  | 
 | ||||||
|  | 		print "Done" | ||||||
|  | 
 | ||||||
|  | 	def changeDir(self, ftp, dir): | ||||||
|  | 		if dir not in ftp.nlst(): | ||||||
|  | 			print "Creating dir '%s'" % dir | ||||||
|  | 			try: | ||||||
|  | 				ftp.mkd(dir) | ||||||
|  | 			except: | ||||||
|  | 				# sometimes nlst may returns nothing, so mkd fails with 'File exists' | ||||||
|  | 				print "Failed to create dir '%s'" % dir | ||||||
|  | 
 | ||||||
|  | 		print "Changing to dir '%s'" % dir | ||||||
|  | 		ftp.cwd(dir) | ||||||
|  |  | ||||||
|  | @ -30,6 +30,13 @@ class Generator(object): | ||||||
| 	def getSourceDir(self): | 	def getSourceDir(self): | ||||||
| 		return self.sourceDir | 		return self.sourceDir | ||||||
| 
 | 
 | ||||||
|  | class VisualStudioGenerator(Generator): | ||||||
|  | 	def __init__(self, version): | ||||||
|  | 		super(VisualStudioGenerator, self).__init__('Visual Studio ' + version) | ||||||
|  | 
 | ||||||
|  | 	def getBinDir(self, target=''): | ||||||
|  | 		return super(VisualStudioGenerator, self).getBinDir(target) + '/' + target | ||||||
|  | 
 | ||||||
| class MakefilesGenerator(Generator): | class MakefilesGenerator(Generator): | ||||||
| 	def __init__(self): | 	def __init__(self): | ||||||
| 		super(MakefilesGenerator, self).__init__('Unix Makefiles') | 		super(MakefilesGenerator, self).__init__('Unix Makefiles') | ||||||
|  |  | ||||||
|  | @ -1132,12 +1132,12 @@ void MainWindow::downloadBonjour() | ||||||
| { | { | ||||||
| #if defined(Q_OS_WIN) | #if defined(Q_OS_WIN) | ||||||
| 	QUrl url; | 	QUrl url; | ||||||
| 	int arch = checkProcessorArch(); | 	int arch = getProcessorArch(); | ||||||
| 	if (arch == Win_x86) { | 	if (arch == kProcessorArchWin32) { | ||||||
| 		url.setUrl(bonjourBaseUrl + bonjourFilename32); | 		url.setUrl(bonjourBaseUrl + bonjourFilename32); | ||||||
| 		appendLogNote("downloading 32-bit Bonjour"); | 		appendLogNote("downloading 32-bit Bonjour"); | ||||||
| 	} | 	} | ||||||
| 	else if (arch == Win_x64) { | 	else if (arch == kProcessorArchWin64) { | ||||||
| 		url.setUrl(bonjourBaseUrl + bonjourFilename64); | 		url.setUrl(bonjourBaseUrl + bonjourFilename64); | ||||||
| 		appendLogNote("downloading 64-bit Bonjour"); | 		appendLogNote("downloading 64-bit Bonjour"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -29,14 +29,15 @@ | ||||||
| #include <QProcess> | #include <QProcess> | ||||||
| #include <QCoreApplication> | #include <QCoreApplication> | ||||||
| 
 | 
 | ||||||
| static QString kBaseUrl = "http://synergy-project.org/files"; | static const char kBaseUrl[] = "http://synergy-project.org/files"; | ||||||
| static const char kWinProcessorArch32[] = "Windows-x86"; | static const char kDefaultVersion[] = "1.1"; | ||||||
| static const char kWinProcessorArch64[] = "Windows-x64"; | static const char kWinPackagePlatform32[] = "Windows-x86"; | ||||||
| static const char kMacProcessorArch[] = "MacOSX-i386"; | static const char kWinPackagePlatform64[] = "Windows-x64"; | ||||||
| static const char kLinuxProcessorArchDeb32[] = "Linux-i686-deb"; | static const char kMacPackagePlatform[] = "MacOSX%1-i386"; | ||||||
| static const char kLinuxProcessorArchDeb64[] = "Linux-x86_64-deb"; | static const char kLinuxPackagePlatformDeb32[] = "Linux-i686-deb"; | ||||||
| static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm"; | static const char kLinuxPackagePlatformDeb64[] = "Linux-x86_64-deb"; | ||||||
| static const char kLinuxProcessorArchRpm64[] = "Linux-x86_64-rpm"; | static const char kLinuxPackagePlatformRpm32[] = "Linux-i686-rpm"; | ||||||
|  | static const char kLinuxPackagePlatformRpm64[] = "Linux-x86_64-rpm"; | ||||||
| 
 | 
 | ||||||
| #if defined(Q_OS_WIN) | #if defined(Q_OS_WIN) | ||||||
| static const char kWinPluginExt[] = ".dll"; | static const char kWinPluginExt[] = ".dll"; | ||||||
|  | @ -157,10 +158,10 @@ QString PluginManager::getPluginUrl(const QString& pluginName) | ||||||
| 	try { | 	try { | ||||||
| 		QString coreArch = m_CoreInterface.getArch(); | 		QString coreArch = m_CoreInterface.getArch(); | ||||||
| 		if (coreArch.startsWith("x86")) { | 		if (coreArch.startsWith("x86")) { | ||||||
| 			archName = kWinProcessorArch32; | 			archName = kWinPackagePlatform32; | ||||||
| 		} | 		} | ||||||
| 		else if (coreArch.startsWith("x64")) { | 		else if (coreArch.startsWith("x64")) { | ||||||
| 			archName = kWinProcessorArch64; | 			archName = kWinPackagePlatform64; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	catch (...) { | 	catch (...) { | ||||||
|  | @ -170,22 +171,53 @@ QString PluginManager::getPluginUrl(const QString& pluginName) | ||||||
| 
 | 
 | ||||||
| #elif defined(Q_OS_MAC) | #elif defined(Q_OS_MAC) | ||||||
| 
 | 
 | ||||||
| 	archName = kMacProcessorArch; | 	QString macVersion = "1010"; | ||||||
|  | #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 // 10.9
 | ||||||
|  | 	macVersion = "109"; | ||||||
|  | #elif __MAC_OS_X_VERSION_MIN_REQUIRED <= 1080 // 10.8
 | ||||||
|  | 	macVersion = "108"; | ||||||
|  | #elif __MAC_OS_X_VERSION_MIN_REQUIRED <= 1070 // 10.7
 | ||||||
|  | 	emit error(tr("Plugins not supported on this Mac OS X version.")); | ||||||
|  | 	return ""; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 	archName = QString(kMacPackagePlatform).arg(macVersion); | ||||||
| 
 | 
 | ||||||
| #else | #else | ||||||
| 
 | 
 | ||||||
| 	int arch = checkProcessorArch(); | 	QString program("dpkg"); | ||||||
| 	if (arch == Linux_rpm_i686) { | 	QStringList args; | ||||||
| 		archName = kLinuxProcessorArchRpm32; | 	args << "-s" << "synergy"; | ||||||
|  | 
 | ||||||
|  | 	QProcess process; | ||||||
|  | 	process.setReadChannel(QProcess::StandardOutput); | ||||||
|  | 	process.start(program, args); | ||||||
|  | 	bool success = process.waitForStarted(); | ||||||
|  | 
 | ||||||
|  | 	if (!success || !process.waitForFinished()) | ||||||
|  | 	{ | ||||||
|  | 		emit error(tr("Could not get Linux package type.")); | ||||||
|  | 		return ""; | ||||||
| 	} | 	} | ||||||
| 	else if (arch == Linux_rpm_x86_64) { | 
 | ||||||
| 		archName = kLinuxProcessorArchRpm64; | 	bool isDeb = (process.exitCode() == 0); | ||||||
|  | 
 | ||||||
|  | 	int arch = getProcessorArch(); | ||||||
|  | 	if (arch == kProcessorArchLinux32) { | ||||||
|  | 		if (isDeb) { | ||||||
|  | 			archName = kLinuxPackagePlatformDeb32; | ||||||
| 		} | 		} | ||||||
| 	else if (arch == Linux_deb_i686) { | 		else { | ||||||
| 		archName = kLinuxProcessorArchDeb32; | 			archName = kLinuxPackagePlatformRpm32; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else if (arch == kProcessorArchLinux64) { | ||||||
|  | 		if (isDeb) { | ||||||
|  | 			archName = kLinuxPackagePlatformDeb64; | ||||||
|  | 		} | ||||||
|  | 		else { | ||||||
|  | 			archName = kLinuxPackagePlatformRpm64; | ||||||
| 		} | 		} | ||||||
| 	else if (arch == Linux_deb_x86_64) { |  | ||||||
| 		archName = kLinuxProcessorArchDeb64; |  | ||||||
| 	} | 	} | ||||||
| 	else { | 	else { | ||||||
| 		emit error(tr("Could not get Linux architecture type.")); | 		emit error(tr("Could not get Linux architecture type.")); | ||||||
|  | @ -194,13 +226,14 @@ QString PluginManager::getPluginUrl(const QString& pluginName) | ||||||
| 
 | 
 | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 	QString result = kBaseUrl; | 	QString result = QString("%1/plugins/%2/%3/%4/%5") | ||||||
| 	result.append("/plugins/"); | 			.arg(kBaseUrl) | ||||||
| 	result.append(pluginName).append("/1.0/"); | 			.arg(pluginName) | ||||||
| 	result.append(archName); | 			.arg(kDefaultVersion) | ||||||
| 	result.append("/"); | 			.arg(archName) | ||||||
| 	result.append(getPluginOsSpecificName(pluginName)); | 			.arg(getPluginOsSpecificName(pluginName)); | ||||||
| 
 | 
 | ||||||
|  | 	qDebug() << result; | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,18 +15,14 @@ | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #ifndef PROCESSORARCH_H | #pragma once | ||||||
| #define PROCESSORARCH_H |  | ||||||
| 
 | 
 | ||||||
| enum qProcessorArch { | enum qProcessorArch { | ||||||
| 	Win_x86, | 	kProcessorArchWin32, | ||||||
| 	Win_x64, | 	kProcessorArchWin64, | ||||||
| 	Mac_i386, | 	kProcessorArchMac32, | ||||||
| 	Linux_rpm_i686, | 	kProcessorArchMac64, | ||||||
| 	Linux_rpm_x86_64, | 	kProcessorArchLinux32, | ||||||
| 	Linux_deb_i686, | 	kProcessorArchLinux64, | ||||||
| 	Linux_deb_x86_64, | 	kProcessorArchUnknown | ||||||
| 	unknown |  | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| #endif // PROCESSORARCH_H
 |  | ||||||
|  |  | ||||||
|  | @ -29,12 +29,6 @@ | ||||||
| #include <Windows.h> | #include <Windows.h> | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #if defined(Q_OS_LINUX) |  | ||||||
| static const char kLinuxI686[] = "i686"; |  | ||||||
| static const char kLinuxX8664[] = "x86_64"; |  | ||||||
| static const char kUbuntu[] = "Ubuntu"; |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData) | void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData) | ||||||
| { | { | ||||||
| 	for (int i = 0; i < comboBox->count(); ++i) | 	for (int i = 0; i < comboBox->count(); ++i) | ||||||
|  | @ -68,7 +62,7 @@ QString getFirstMacAddress() | ||||||
| 	return mac; | 	return mac; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int checkProcessorArch() | qProcessorArch getProcessorArch() | ||||||
| { | { | ||||||
| #if defined(Q_OS_WIN) | #if defined(Q_OS_WIN) | ||||||
| 	SYSTEM_INFO systemInfo; | 	SYSTEM_INFO systemInfo; | ||||||
|  | @ -76,97 +70,23 @@ int checkProcessorArch() | ||||||
| 
 | 
 | ||||||
| 	switch (systemInfo.wProcessorArchitecture) { | 	switch (systemInfo.wProcessorArchitecture) { | ||||||
| 	case PROCESSOR_ARCHITECTURE_INTEL: | 	case PROCESSOR_ARCHITECTURE_INTEL: | ||||||
| 		return Win_x86; | 		return kProcessorArchWin32; | ||||||
| 	case PROCESSOR_ARCHITECTURE_IA64: | 	case PROCESSOR_ARCHITECTURE_IA64: | ||||||
| 		return Win_x64; | 		return kProcessorArchWin64; | ||||||
| 	case PROCESSOR_ARCHITECTURE_AMD64: | 	case PROCESSOR_ARCHITECTURE_AMD64: | ||||||
| 		return Win_x64; | 		return kProcessorArchWin64; | ||||||
| 	default: | 	default: | ||||||
| 		return unknown; | 		return kProcessorArchUnknown; | ||||||
| 	} |  | ||||||
| #elif defined(Q_OS_MAC) |  | ||||||
| 	return Mac_i386; |  | ||||||
| #else |  | ||||||
| 	bool version32 = false; |  | ||||||
| 	bool debPackaging = false; |  | ||||||
| 
 |  | ||||||
| 	QString program1("uname"); |  | ||||||
| 	QStringList args1("-m"); |  | ||||||
| 	QProcess process1; |  | ||||||
| 	process1.setReadChannel(QProcess::StandardOutput); |  | ||||||
| 	process1.start(program1, args1); |  | ||||||
| 	bool success = process1.waitForStarted(); |  | ||||||
| 
 |  | ||||||
| 	QString out, error; |  | ||||||
| 	if (success) |  | ||||||
| 	{ |  | ||||||
| 		if (process1.waitForFinished()) { |  | ||||||
| 			out = process1.readAllStandardOutput(); |  | ||||||
| 			error = process1.readAllStandardError(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	out = out.trimmed(); |  | ||||||
| 	error = error.trimmed(); |  | ||||||
| 
 |  | ||||||
| 	if (out.isEmpty() || |  | ||||||
| 		!error.isEmpty() || |  | ||||||
| 		!success || |  | ||||||
| 		process1.exitCode() != 0) |  | ||||||
| 	{ |  | ||||||
| 		return unknown; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (out == kLinuxI686) { |  | ||||||
| 		version32 = true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	QString program2("python"); |  | ||||||
| 	QStringList args2("-mplatform"); |  | ||||||
| 	QProcess process2; |  | ||||||
| 	process2.setReadChannel(QProcess::StandardOutput); |  | ||||||
| 	process2.start(program2, args2); |  | ||||||
| 	success = process2.waitForStarted(); |  | ||||||
| 
 |  | ||||||
| 	if (success) |  | ||||||
| 	{ |  | ||||||
| 		if (process2.waitForFinished()) { |  | ||||||
| 			out = process2.readAllStandardOutput(); |  | ||||||
| 			error = process2.readAllStandardError(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	out = out.trimmed(); |  | ||||||
| 	error = error.trimmed(); |  | ||||||
| 
 |  | ||||||
| 	if (out.isEmpty() || |  | ||||||
| 		!error.isEmpty() || |  | ||||||
| 		!success || |  | ||||||
| 		process2.exitCode() != 0) |  | ||||||
| 	{ |  | ||||||
| 		return unknown; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (out.contains(kUbuntu)) { |  | ||||||
| 		debPackaging = true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (version32) { |  | ||||||
| 		if (debPackaging) { |  | ||||||
| 			return Linux_deb_i686; |  | ||||||
| 		} |  | ||||||
| 		else { |  | ||||||
| 			return Linux_rpm_i686; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	else { |  | ||||||
| 		if (debPackaging) { |  | ||||||
| 			return Linux_deb_x86_64; |  | ||||||
| 		} |  | ||||||
| 		else { |  | ||||||
| 			return Linux_rpm_x86_64; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| #endif | #endif | ||||||
| 	return unknown; | 
 | ||||||
|  | #if defined(Q_OS_LINUX) | ||||||
|  | #ifdef __i386__ | ||||||
|  | 	return kProcessorArchLinux32; | ||||||
|  | #else | ||||||
|  | 	return kProcessorArchLinux64; | ||||||
|  | #endif | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 	return kProcessorArchUnknown; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "ProcessorArch.h" | ||||||
|  | 
 | ||||||
| #include <QComboBox> | #include <QComboBox> | ||||||
| #include <QVariant> | #include <QVariant> | ||||||
| #include <QCryptographicHash> | #include <QCryptographicHash> | ||||||
|  | @ -25,4 +27,4 @@ | ||||||
| void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData); | void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData); | ||||||
| QString hash(const QString& string); | QString hash(const QString& string); | ||||||
| QString getFirstMacAddress(); | QString getFirstMacAddress(); | ||||||
| int checkProcessorArch(); | qProcessorArch getProcessorArch(); | ||||||
|  |  | ||||||
|  | @ -46,6 +46,7 @@ EVENT_TYPE_ACCESSOR(ServerApp) | ||||||
| EVENT_TYPE_ACCESSOR(IKeyState) | EVENT_TYPE_ACCESSOR(IKeyState) | ||||||
| EVENT_TYPE_ACCESSOR(IPrimaryScreen) | EVENT_TYPE_ACCESSOR(IPrimaryScreen) | ||||||
| EVENT_TYPE_ACCESSOR(IScreen) | EVENT_TYPE_ACCESSOR(IScreen) | ||||||
|  | EVENT_TYPE_ACCESSOR(Clipboard) | ||||||
| 
 | 
 | ||||||
| // interrupt handler.  this just adds a quit event to the queue.
 | // interrupt handler.  this just adds a quit event to the queue.
 | ||||||
| static | static | ||||||
|  | @ -82,6 +83,7 @@ EventQueue::EventQueue() : | ||||||
| 	m_typesForIKeyState(NULL), | 	m_typesForIKeyState(NULL), | ||||||
| 	m_typesForIPrimaryScreen(NULL), | 	m_typesForIPrimaryScreen(NULL), | ||||||
| 	m_typesForIScreen(NULL), | 	m_typesForIScreen(NULL), | ||||||
|  | 	m_typesForClipboard(NULL), | ||||||
| 	m_readyMutex(new Mutex), | 	m_readyMutex(new Mutex), | ||||||
| 	m_readyCondVar(new CondVar<bool>(m_readyMutex, false)) | 	m_readyCondVar(new CondVar<bool>(m_readyMutex, false)) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -157,6 +157,7 @@ public: | ||||||
| 	IKeyStateEvents&			forIKeyState(); | 	IKeyStateEvents&			forIKeyState(); | ||||||
| 	IPrimaryScreenEvents&		forIPrimaryScreen(); | 	IPrimaryScreenEvents&		forIPrimaryScreen(); | ||||||
| 	IScreenEvents&				forIScreen(); | 	IScreenEvents&				forIScreen(); | ||||||
|  | 	ClipboardEvents&			forClipboard(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	ClientEvents*				m_typesForClient; | 	ClientEvents*				m_typesForClient; | ||||||
|  | @ -177,6 +178,7 @@ private: | ||||||
| 	IKeyStateEvents*			m_typesForIKeyState; | 	IKeyStateEvents*			m_typesForIKeyState; | ||||||
| 	IPrimaryScreenEvents*		m_typesForIPrimaryScreen; | 	IPrimaryScreenEvents*		m_typesForIPrimaryScreen; | ||||||
| 	IScreenEvents*				m_typesForIScreen; | 	IScreenEvents*				m_typesForIScreen; | ||||||
|  | 	ClipboardEvents*			m_typesForClipboard; | ||||||
| 	Mutex*						m_readyMutex; | 	Mutex*						m_readyMutex; | ||||||
| 	CondVar<bool>*				m_readyCondVar; | 	CondVar<bool>*				m_readyCondVar; | ||||||
| 	std::queue<Event>			m_pending; | 	std::queue<Event>			m_pending; | ||||||
|  |  | ||||||
|  | @ -115,7 +115,6 @@ REGISTER_EVENT(ClientListener, connected) | ||||||
| 
 | 
 | ||||||
| REGISTER_EVENT(ClientProxy, ready) | REGISTER_EVENT(ClientProxy, ready) | ||||||
| REGISTER_EVENT(ClientProxy, disconnected) | REGISTER_EVENT(ClientProxy, disconnected) | ||||||
| REGISTER_EVENT(ClientProxy, clipboardChanged) |  | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| // ClientProxyUnknown
 | // ClientProxyUnknown
 | ||||||
|  | @ -175,7 +174,6 @@ REGISTER_EVENT(IPrimaryScreen, fakeInputEnd) | ||||||
| 
 | 
 | ||||||
| REGISTER_EVENT(IScreen, error) | REGISTER_EVENT(IScreen, error) | ||||||
| REGISTER_EVENT(IScreen, shapeChanged) | REGISTER_EVENT(IScreen, shapeChanged) | ||||||
| REGISTER_EVENT(IScreen, clipboardGrabbed) |  | ||||||
| REGISTER_EVENT(IScreen, suspend) | REGISTER_EVENT(IScreen, suspend) | ||||||
| REGISTER_EVENT(IScreen, resume) | REGISTER_EVENT(IScreen, resume) | ||||||
| REGISTER_EVENT(IScreen, fileChunkSending) | REGISTER_EVENT(IScreen, fileChunkSending) | ||||||
|  | @ -187,3 +185,11 @@ REGISTER_EVENT(IScreen, fileRecieveCompleted) | ||||||
| 
 | 
 | ||||||
| REGISTER_EVENT(IpcServer, clientConnected) | REGISTER_EVENT(IpcServer, clientConnected) | ||||||
| REGISTER_EVENT(IpcServer, messageReceived) | REGISTER_EVENT(IpcServer, messageReceived) | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // Clipboard
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | REGISTER_EVENT(Clipboard, clipboardGrabbed) | ||||||
|  | REGISTER_EVENT(Clipboard, clipboardChanged) | ||||||
|  | REGISTER_EVENT(Clipboard, clipboardSending) | ||||||
|  | @ -350,8 +350,7 @@ class ClientProxyEvents : public EventTypes { | ||||||
| public: | public: | ||||||
| 	ClientProxyEvents() : | 	ClientProxyEvents() : | ||||||
| 		m_ready(Event::kUnknown), | 		m_ready(Event::kUnknown), | ||||||
| 		m_disconnected(Event::kUnknown), | 		m_disconnected(Event::kUnknown) { } | ||||||
| 		m_clipboardChanged(Event::kUnknown) { } |  | ||||||
| 
 | 
 | ||||||
| 	//! @name accessors
 | 	//! @name accessors
 | ||||||
| 	//@{
 | 	//@{
 | ||||||
|  | @ -371,20 +370,11 @@ public: | ||||||
| 	*/ | 	*/ | ||||||
| 	Event::Type		disconnected(); | 	Event::Type		disconnected(); | ||||||
| 
 | 
 | ||||||
| 	//! Get clipboard changed event type
 |  | ||||||
| 	/*!
 |  | ||||||
| 	Returns the clipboard changed event type.  This is sent whenever the |  | ||||||
| 	contents of the clipboard has changed.  The data is a pointer to a |  | ||||||
| 	IScreen::ClipboardInfo. |  | ||||||
| 	*/ |  | ||||||
| 	Event::Type		clipboardChanged(); |  | ||||||
| 
 |  | ||||||
| 	//@}
 | 	//@}
 | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	Event::Type		m_ready; | 	Event::Type		m_ready; | ||||||
| 	Event::Type		m_disconnected; | 	Event::Type		m_disconnected; | ||||||
| 	Event::Type		m_clipboardChanged; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class ClientProxyUnknownEvents : public EventTypes { | class ClientProxyUnknownEvents : public EventTypes { | ||||||
|  | @ -634,7 +624,6 @@ public: | ||||||
| 	IScreenEvents() : | 	IScreenEvents() : | ||||||
| 		m_error(Event::kUnknown), | 		m_error(Event::kUnknown), | ||||||
| 		m_shapeChanged(Event::kUnknown), | 		m_shapeChanged(Event::kUnknown), | ||||||
| 		m_clipboardGrabbed(Event::kUnknown), |  | ||||||
| 		m_suspend(Event::kUnknown), | 		m_suspend(Event::kUnknown), | ||||||
| 		m_resume(Event::kUnknown), | 		m_resume(Event::kUnknown), | ||||||
| 		m_fileChunkSending(Event::kUnknown), | 		m_fileChunkSending(Event::kUnknown), | ||||||
|  | @ -657,14 +646,6 @@ public: | ||||||
| 	*/ | 	*/ | ||||||
| 	Event::Type		shapeChanged(); | 	Event::Type		shapeChanged(); | ||||||
| 
 | 
 | ||||||
| 	//! Get clipboard grabbed event type
 |  | ||||||
| 	/*!
 |  | ||||||
| 	Returns the clipboard grabbed event type.  This is sent whenever the |  | ||||||
| 	clipboard is grabbed by some other application so we don't own it |  | ||||||
| 	anymore.  The data is a pointer to a ClipboardInfo. |  | ||||||
| 	*/ |  | ||||||
| 	Event::Type		clipboardGrabbed(); |  | ||||||
| 
 |  | ||||||
| 	//! Get suspend event type
 | 	//! Get suspend event type
 | ||||||
| 	/*!
 | 	/*!
 | ||||||
| 	Returns the suspend event type. This is sent whenever the system goes | 	Returns the suspend event type. This is sent whenever the system goes | ||||||
|  | @ -690,9 +671,49 @@ public: | ||||||
| private: | private: | ||||||
| 	Event::Type		m_error; | 	Event::Type		m_error; | ||||||
| 	Event::Type		m_shapeChanged; | 	Event::Type		m_shapeChanged; | ||||||
| 	Event::Type		m_clipboardGrabbed; |  | ||||||
| 	Event::Type		m_suspend; | 	Event::Type		m_suspend; | ||||||
| 	Event::Type		m_resume; | 	Event::Type		m_resume; | ||||||
| 	Event::Type		m_fileChunkSending; | 	Event::Type		m_fileChunkSending; | ||||||
| 	Event::Type		m_fileRecieveCompleted; | 	Event::Type		m_fileRecieveCompleted; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | class ClipboardEvents : public EventTypes { | ||||||
|  | public: | ||||||
|  | 	ClipboardEvents() : | ||||||
|  | 		m_clipboardGrabbed(Event::kUnknown), | ||||||
|  | 		m_clipboardChanged(Event::kUnknown), | ||||||
|  | 		m_clipboardSending(Event::kUnknown) { } | ||||||
|  | 
 | ||||||
|  | 	//! @name accessors
 | ||||||
|  | 	//@{
 | ||||||
|  | 
 | ||||||
|  | 	//! Get clipboard grabbed event type
 | ||||||
|  | 	/*!
 | ||||||
|  | 	Returns the clipboard grabbed event type.  This is sent whenever the | ||||||
|  | 	clipboard is grabbed by some other application so we don't own it | ||||||
|  | 	anymore.  The data is a pointer to a ClipboardInfo. | ||||||
|  | 	*/ | ||||||
|  | 	Event::Type		clipboardGrabbed(); | ||||||
|  | 
 | ||||||
|  | 	//! Get clipboard changed event type
 | ||||||
|  | 	/*!
 | ||||||
|  | 	Returns the clipboard changed event type.  This is sent whenever the | ||||||
|  | 	contents of the clipboard has changed.  The data is a pointer to a | ||||||
|  | 	IScreen::ClipboardInfo. | ||||||
|  | 	*/ | ||||||
|  | 	Event::Type		clipboardChanged(); | ||||||
|  | 
 | ||||||
|  | 	//! Clipboard sending event type
 | ||||||
|  | 	/*!
 | ||||||
|  | 	Returns the clipboard sending event type. This is used to send  | ||||||
|  | 	clipboard chunks. | ||||||
|  | 	*/ | ||||||
|  | 	Event::Type		clipboardSending(); | ||||||
|  | 
 | ||||||
|  | 	//@}
 | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	Event::Type		m_clipboardGrabbed; | ||||||
|  | 	Event::Type		m_clipboardChanged; | ||||||
|  | 	Event::Type		m_clipboardSending; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ class ServerAppEvents; | ||||||
| class IKeyStateEvents; | class IKeyStateEvents; | ||||||
| class IPrimaryScreenEvents; | class IPrimaryScreenEvents; | ||||||
| class IScreenEvents; | class IScreenEvents; | ||||||
|  | class ClipboardEvents; | ||||||
| 
 | 
 | ||||||
| //! Event queue interface
 | //! Event queue interface
 | ||||||
| /*!
 | /*!
 | ||||||
|  | @ -244,4 +245,5 @@ public: | ||||||
| 	virtual IKeyStateEvents&			forIKeyState() = 0; | 	virtual IKeyStateEvents&			forIKeyState() = 0; | ||||||
| 	virtual IPrimaryScreenEvents&		forIPrimaryScreen() = 0; | 	virtual IPrimaryScreenEvents&		forIPrimaryScreen() = 0; | ||||||
| 	virtual IScreenEvents&				forIScreen() = 0; | 	virtual IScreenEvents&				forIScreen() = 0; | ||||||
|  | 	virtual ClipboardEvents&			forClipboard() = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -207,6 +207,23 @@ removeChar(String& subject, const char c) | ||||||
| 	subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); | 	subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | String | ||||||
|  | sizeTypeToString(size_t n) | ||||||
|  | { | ||||||
|  | 	std::stringstream ss; | ||||||
|  | 	ss << n; | ||||||
|  | 	return ss.str(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t | ||||||
|  | stringToSizeType(String string) | ||||||
|  | { | ||||||
|  | 	std::istringstream iss(string); | ||||||
|  | 	size_t value; | ||||||
|  | 	iss >> value; | ||||||
|  | 	return value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //
 | //
 | ||||||
| // CaselessCmp
 | // CaselessCmp
 | ||||||
| //
 | //
 | ||||||
|  |  | ||||||
|  | @ -84,10 +84,21 @@ void uppercase(String& subject); | ||||||
| 
 | 
 | ||||||
| //! Remove all specific char in suject
 | //! Remove all specific char in suject
 | ||||||
| /*!
 | /*!
 | ||||||
| Remove all specific \c char in \c suject | Remove all specific \c c in \c suject | ||||||
| */ | */ | ||||||
| void removeChar(String& subject, const char c); | void removeChar(String& subject, const char c); | ||||||
| 
 | 
 | ||||||
|  | //! Convert a size type to a string
 | ||||||
|  | /*!
 | ||||||
|  | Convert an size type to a string | ||||||
|  | */ | ||||||
|  | String sizeTypeToString(size_t n); | ||||||
|  | 
 | ||||||
|  | //! Convert a string to a size type
 | ||||||
|  | /*!
 | ||||||
|  | Convert an a \c string to an size type | ||||||
|  | */ | ||||||
|  | size_t stringToSizeType(String string); | ||||||
| 
 | 
 | ||||||
| //! Case-insensitive comparisons
 | //! Case-insensitive comparisons
 | ||||||
| /*!
 | /*!
 | ||||||
|  |  | ||||||
|  | @ -22,12 +22,13 @@ | ||||||
| #include "client/ServerProxy.h" | #include "client/ServerProxy.h" | ||||||
| #include "synergy/Screen.h" | #include "synergy/Screen.h" | ||||||
| #include "synergy/Clipboard.h" | #include "synergy/Clipboard.h" | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
| #include "synergy/DropHelper.h" | #include "synergy/DropHelper.h" | ||||||
| #include "synergy/PacketStreamFilter.h" | #include "synergy/PacketStreamFilter.h" | ||||||
| #include "synergy/ProtocolUtil.h" | #include "synergy/ProtocolUtil.h" | ||||||
| #include "synergy/protocol_types.h" | #include "synergy/protocol_types.h" | ||||||
| #include "synergy/XSynergy.h" | #include "synergy/XSynergy.h" | ||||||
| #include "synergy/FileChunker.h" | #include "synergy/StreamChunker.h" | ||||||
| #include "synergy/IPlatformScreen.h" | #include "synergy/IPlatformScreen.h" | ||||||
| #include "mt/Thread.h" | #include "mt/Thread.h" | ||||||
| #include "net/TCPSocket.h" | #include "net/TCPSocket.h" | ||||||
|  | @ -78,7 +79,8 @@ Client::Client( | ||||||
| 	m_writeToDropDirThread(NULL), | 	m_writeToDropDirThread(NULL), | ||||||
| 	m_socket(NULL), | 	m_socket(NULL), | ||||||
| 	m_useSecureNetwork(false), | 	m_useSecureNetwork(false), | ||||||
| 	m_args(args) | 	m_args(args), | ||||||
|  | 	m_sendClipboardThread(NULL) | ||||||
| { | { | ||||||
| 	assert(m_socketFactory != NULL); | 	assert(m_socketFactory != NULL); | ||||||
| 	assert(m_screen        != NULL); | 	assert(m_screen        != NULL); | ||||||
|  | @ -264,13 +266,16 @@ Client::leave() | ||||||
| 
 | 
 | ||||||
| 	m_active = false; | 	m_active = false; | ||||||
| 	 | 	 | ||||||
| 	// send clipboards that we own and that have changed
 | 	if (m_sendClipboardThread != NULL) { | ||||||
| 	for (ClipboardID id = 0; id < kClipboardEnd; ++id) { | 		StreamChunker::interruptClipboard(); | ||||||
| 		if (m_ownClipboard[id]) { |  | ||||||
| 			sendClipboard(id); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|  | 	m_sendClipboardThread = new Thread( | ||||||
|  | 								new TMethodJob<Client>( | ||||||
|  | 									this, | ||||||
|  | 									&Client::sendClipboardThread, | ||||||
|  | 									NULL)); | ||||||
|  | 
 | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -422,12 +427,12 @@ Client::sendConnectionFailedEvent(const char* msg) | ||||||
| void | void | ||||||
| Client::sendFileChunk(const void* data) | Client::sendFileChunk(const void* data) | ||||||
| { | { | ||||||
| 	FileChunker::FileChunk* fileChunk = reinterpret_cast<FileChunker::FileChunk*>(const_cast<void*>(data)); | 	FileChunk* chunk = reinterpret_cast<FileChunk*>(const_cast<void*>(data)); | ||||||
| 	LOG((CLOG_DEBUG1 "sendFileChunk")); | 	LOG((CLOG_DEBUG1 "send file chunk")); | ||||||
| 	assert(m_server != NULL); | 	assert(m_server != NULL); | ||||||
| 
 | 
 | ||||||
| 	// relay
 | 	// relay
 | ||||||
| 	m_server->fileChunkSending(fileChunk->m_chunk[0], &(fileChunk->m_chunk[1]), fileChunk->m_dataSize); | 	m_server->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  | @ -487,7 +492,7 @@ Client::setupScreen() | ||||||
| 							getEventTarget(), | 							getEventTarget(), | ||||||
| 							new TMethodEventJob<Client>(this, | 							new TMethodEventJob<Client>(this, | ||||||
| 								&Client::handleShapeChanged)); | 								&Client::handleShapeChanged)); | ||||||
| 	m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), | 	m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), | ||||||
| 							getEventTarget(), | 							getEventTarget(), | ||||||
| 							new TMethodEventJob<Client>(this, | 							new TMethodEventJob<Client>(this, | ||||||
| 								&Client::handleClipboardGrabbed)); | 								&Client::handleClipboardGrabbed)); | ||||||
|  | @ -545,7 +550,7 @@ Client::cleanupScreen() | ||||||
| 		} | 		} | ||||||
| 		m_events->removeHandler(m_events->forIScreen().shapeChanged(), | 		m_events->removeHandler(m_events->forIScreen().shapeChanged(), | ||||||
| 							getEventTarget()); | 							getEventTarget()); | ||||||
| 		m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), | 		m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), | ||||||
| 							getEventTarget()); | 							getEventTarget()); | ||||||
| 		delete m_server; | 		delete m_server; | ||||||
| 		m_server = NULL; | 		m_server = NULL; | ||||||
|  | @ -749,6 +754,17 @@ Client::onFileRecieveCompleted() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | Client::sendClipboardThread(void*) | ||||||
|  | { | ||||||
|  | 	// send clipboards that we own and that have changed
 | ||||||
|  | 	for (ClipboardID id = 0; id < kClipboardEnd; ++id) { | ||||||
|  | 		if (m_ownClipboard[id]) { | ||||||
|  | 			sendClipboard(id); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| Client::handleStopRetry(const Event&, void*) | Client::handleStopRetry(const Event&, void*) | ||||||
| { | { | ||||||
|  | @ -768,25 +784,6 @@ Client::writeToDropDirThread(void*) | ||||||
| 					m_receivedFileData); | 					m_receivedFileData); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void |  | ||||||
| Client::clearReceivedFileData() |  | ||||||
| { |  | ||||||
| 	m_receivedFileData.clear(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void |  | ||||||
| Client::setExpectedFileSize(String data) |  | ||||||
| { |  | ||||||
| 	std::istringstream iss(data); |  | ||||||
| 	iss >> m_expectedFileSize; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void |  | ||||||
| Client::fileChunkReceived(String data) |  | ||||||
| { |  | ||||||
| 	m_receivedFileData += data; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void | void | ||||||
| Client::dragInfoReceived(UInt32 fileNum, String data) | Client::dragInfoReceived(UInt32 fileNum, String data) | ||||||
| { | { | ||||||
|  | @ -810,6 +807,10 @@ Client::isReceivedFileSizeValid() | ||||||
| void | void | ||||||
| Client::sendFileToServer(const char* filename) | Client::sendFileToServer(const char* filename) | ||||||
| { | { | ||||||
|  | 	if (m_sendFileThread != NULL) { | ||||||
|  | 		StreamChunker::interruptFile(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	m_sendFileThread = new Thread( | 	m_sendFileThread = new Thread( | ||||||
| 		new TMethodJob<Client>( | 		new TMethodJob<Client>( | ||||||
| 			this, &Client::sendFileThread, | 			this, &Client::sendFileThread, | ||||||
|  | @ -821,7 +822,7 @@ Client::sendFileThread(void* filename) | ||||||
| { | { | ||||||
| 	try { | 	try { | ||||||
| 		char* name  = reinterpret_cast<char*>(filename); | 		char* name  = reinterpret_cast<char*>(filename); | ||||||
| 		FileChunker::sendFileChunks(name, m_events, this); | 		StreamChunker::sendFile(name, m_events, this); | ||||||
| 	} | 	} | ||||||
| 	catch (std::runtime_error error) { | 	catch (std::runtime_error error) { | ||||||
| 		LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); | 		LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); | ||||||
|  |  | ||||||
|  | @ -85,15 +85,6 @@ public: | ||||||
| 	*/ | 	*/ | ||||||
| 	virtual void		handshakeComplete(); | 	virtual void		handshakeComplete(); | ||||||
| 
 | 
 | ||||||
| 	//! Clears the file buffer
 |  | ||||||
| 	void				clearReceivedFileData(); |  | ||||||
| 
 |  | ||||||
| 	//! Set the expected size of receiving file
 |  | ||||||
| 	void				setExpectedFileSize(String data); |  | ||||||
| 
 |  | ||||||
| 	//! Received a chunk of file data
 |  | ||||||
| 	void				fileChunkReceived(String data); |  | ||||||
| 
 |  | ||||||
| 	//! Received drag information
 | 	//! Received drag information
 | ||||||
| 	void				dragInfoReceived(UInt32 fileNum, String data); | 	void				dragInfoReceived(UInt32 fileNum, String data); | ||||||
| 
 | 
 | ||||||
|  | @ -131,7 +122,10 @@ public: | ||||||
| 	bool				isReceivedFileSizeValid(); | 	bool				isReceivedFileSizeValid(); | ||||||
| 
 | 
 | ||||||
| 	//! Return expected file size
 | 	//! Return expected file size
 | ||||||
| 	size_t				getExpectedFileSize() { return m_expectedFileSize; } | 	size_t&				getExpectedFileSize() { return m_expectedFileSize; } | ||||||
|  | 
 | ||||||
|  | 	//! Return received file data
 | ||||||
|  | 	String&				getReceivedFileData() { return m_receivedFileData; } | ||||||
| 
 | 
 | ||||||
| 	//@}
 | 	//@}
 | ||||||
| 
 | 
 | ||||||
|  | @ -194,6 +188,7 @@ private: | ||||||
| 	void				handleFileRecieveCompleted(const Event&, void*); | 	void				handleFileRecieveCompleted(const Event&, void*); | ||||||
| 	void				handleStopRetry(const Event&, void*); | 	void				handleStopRetry(const Event&, void*); | ||||||
| 	void				onFileRecieveCompleted(); | 	void				onFileRecieveCompleted(); | ||||||
|  | 	void				sendClipboardThread(void*); | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
| 	bool				m_mock; | 	bool				m_mock; | ||||||
|  | @ -224,4 +219,5 @@ private: | ||||||
| 	TCPSocket*			m_socket; | 	TCPSocket*			m_socket; | ||||||
| 	bool				m_useSecureNetwork; | 	bool				m_useSecureNetwork; | ||||||
| 	ClientArgs&			m_args; | 	ClientArgs&			m_args; | ||||||
|  | 	Thread*				m_sendClipboardThread; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -19,6 +19,9 @@ | ||||||
| #include "client/ServerProxy.h" | #include "client/ServerProxy.h" | ||||||
| 
 | 
 | ||||||
| #include "client/Client.h" | #include "client/Client.h" | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
|  | #include "synergy/ClipboardChunk.h" | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
| #include "synergy/Clipboard.h" | #include "synergy/Clipboard.h" | ||||||
| #include "synergy/ProtocolUtil.h" | #include "synergy/ProtocolUtil.h" | ||||||
| #include "synergy/option_types.h" | #include "synergy/option_types.h" | ||||||
|  | @ -35,8 +38,6 @@ | ||||||
| // ServerProxy
 | // ServerProxy
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| const UInt16 ServerProxy::m_intervalThreshold = 1; |  | ||||||
| 
 |  | ||||||
| ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* events) : | ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* events) : | ||||||
| 	m_client(client), | 	m_client(client), | ||||||
| 	m_stream(stream), | 	m_stream(stream), | ||||||
|  | @ -51,10 +52,7 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* | ||||||
| 	m_keepAliveAlarm(0.0), | 	m_keepAliveAlarm(0.0), | ||||||
| 	m_keepAliveAlarmTimer(NULL), | 	m_keepAliveAlarmTimer(NULL), | ||||||
| 	m_parser(&ServerProxy::parseHandshakeMessage), | 	m_parser(&ServerProxy::parseHandshakeMessage), | ||||||
| 	m_events(events), | 	m_events(events) | ||||||
| 	m_stopwatch(true), |  | ||||||
| 	m_elapsedTime(0), |  | ||||||
| 	m_receivedDataSize(0) |  | ||||||
| { | { | ||||||
| 	assert(m_client != NULL); | 	assert(m_client != NULL); | ||||||
| 	assert(m_stream != NULL); | 	assert(m_stream != NULL); | ||||||
|  | @ -69,6 +67,11 @@ ServerProxy::ServerProxy(Client* client, synergy::IStream* stream, IEventQueue* | ||||||
| 							new TMethodEventJob<ServerProxy>(this, | 							new TMethodEventJob<ServerProxy>(this, | ||||||
| 								&ServerProxy::handleData)); | 								&ServerProxy::handleData)); | ||||||
| 
 | 
 | ||||||
|  | 	m_events->adoptHandler(m_events->forClipboard().clipboardSending(), | ||||||
|  | 							this, | ||||||
|  | 							new TMethodEventJob<ServerProxy>(this, | ||||||
|  | 								&ServerProxy::handleClipboardSendingEvent)); | ||||||
|  | 
 | ||||||
| 	// send heartbeat
 | 	// send heartbeat
 | ||||||
| 	setKeepAliveRate(kKeepAliveRate); | 	setKeepAliveRate(kKeepAliveRate); | ||||||
| } | } | ||||||
|  | @ -358,8 +361,11 @@ void | ||||||
| ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) | ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) | ||||||
| { | { | ||||||
| 	String data = IClipboard::marshall(clipboard); | 	String data = IClipboard::marshall(clipboard); | ||||||
| 	LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size())); | 	LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum)); | ||||||
| 	ProtocolUtil::writef(m_stream, kMsgDClipboard, id, m_seqNum, &data); | 
 | ||||||
|  | 	StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); | ||||||
|  | 
 | ||||||
|  | 	LOG((CLOG_DEBUG "sent clipboard size=%d", data.size())); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  | @ -545,22 +551,21 @@ void | ||||||
| ServerProxy::setClipboard() | ServerProxy::setClipboard() | ||||||
| { | { | ||||||
| 	// parse
 | 	// parse
 | ||||||
|  | 	static String dataCached; | ||||||
| 	ClipboardID id; | 	ClipboardID id; | ||||||
| 	UInt32 seqNum; | 	UInt32 seq; | ||||||
| 	String data; |  | ||||||
| 	ProtocolUtil::readf(m_stream, kMsgDClipboard + 4, &id, &seqNum, &data); |  | ||||||
| 	LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size())); |  | ||||||
| 	 | 	 | ||||||
| 	// validate
 | 	int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); | ||||||
| 	if (id >= kClipboardEnd) { | 
 | ||||||
| 		return; | 	if (r == kFinish) { | ||||||
| 	} | 		LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size())); | ||||||
| 		 | 		 | ||||||
| 		// forward
 | 		// forward
 | ||||||
| 		Clipboard clipboard; | 		Clipboard clipboard; | ||||||
| 	clipboard.unmarshall(data, 0); | 		clipboard.unmarshall(dataCached, 0); | ||||||
| 		m_client->setClipboard(id, &clipboard); | 		m_client->setClipboard(id, &clipboard); | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
| ServerProxy::grabClipboard() | ServerProxy::grabClipboard() | ||||||
|  | @ -851,50 +856,13 @@ ServerProxy::infoAcknowledgment() | ||||||
| void | void | ||||||
| ServerProxy::fileChunkReceived() | ServerProxy::fileChunkReceived() | ||||||
| { | { | ||||||
| 	// parse
 | 	int result = FileChunk::assemble( | ||||||
| 	UInt8 mark = 0; | 					m_stream, | ||||||
| 	String content; | 					m_client->getReceivedFileData(), | ||||||
| 	ProtocolUtil::readf(m_stream, kMsgDFileTransfer + 4, &mark, &content); | 					m_client->getExpectedFileSize()); | ||||||
| 
 | 
 | ||||||
| 	switch (mark) { | 	if (result == kFinish) { | ||||||
| 	case kFileStart: |  | ||||||
| 		m_client->clearReceivedFileData(); |  | ||||||
| 		m_client->setExpectedFileSize(content); |  | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 			LOG((CLOG_DEBUG2 "recv file data from server: size=%s", content.c_str())); |  | ||||||
| 			m_stopwatch.start(); |  | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileChunk: |  | ||||||
| 		m_client->fileChunkReceived(content); |  | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 			LOG((CLOG_DEBUG2 "recv file data from server: size=%i", content.size())); |  | ||||||
| 			double interval = m_stopwatch.getTime(); |  | ||||||
| 			LOG((CLOG_DEBUG2 "recv file data from server: interval=%f s", interval)); |  | ||||||
| 			m_receivedDataSize += content.size(); |  | ||||||
| 			if (interval >= m_intervalThreshold) { |  | ||||||
| 				double averageSpeed = m_receivedDataSize / interval / 1000; |  | ||||||
| 				LOG((CLOG_DEBUG2 "recv file data from server: average speed=%f kb/s", averageSpeed)); |  | ||||||
| 
 |  | ||||||
| 				m_receivedDataSize = 0; |  | ||||||
| 				m_elapsedTime += interval; |  | ||||||
| 				m_stopwatch.reset(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileEnd: |  | ||||||
| 		m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), m_client)); | 		m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), m_client)); | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished")); |  | ||||||
| 			m_elapsedTime += m_stopwatch.getTime(); |  | ||||||
| 			double averageSpeed = m_client->getExpectedFileSize() / m_elapsedTime / 1000; |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", m_elapsedTime)); |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", m_client->getExpectedFileSize() / 1000)); |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed)); |  | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -910,25 +878,15 @@ ServerProxy::dragInfoReceived() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
| ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) | ServerProxy::handleClipboardSendingEvent(const Event& event, void*) | ||||||
| { | { | ||||||
| 	String chunk(data, dataSize); | 	ClipboardChunk::send(m_stream, event.getData()); | ||||||
| 
 |  | ||||||
| 	switch (mark) { |  | ||||||
| 	case kFileStart: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file sending start: size=%s", data)); |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileChunk: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file chunk sending: size=%i", chunk.size())); |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileEnd: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file sending finished")); |  | ||||||
| 		break; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 	ProtocolUtil::writef(m_stream, kMsgDFileTransfer, mark, &chunk); | void | ||||||
|  | ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) | ||||||
|  | { | ||||||
|  | 	FileChunk::send(m_stream, mark, data, dataSize); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  |  | ||||||
|  | @ -106,6 +106,7 @@ private: | ||||||
| 	void				infoAcknowledgment(); | 	void				infoAcknowledgment(); | ||||||
| 	void				fileChunkReceived(); | 	void				fileChunkReceived(); | ||||||
| 	void				dragInfoReceived(); | 	void				dragInfoReceived(); | ||||||
|  | 	void				handleClipboardSendingEvent(const Event&, void*); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	typedef EResult (ServerProxy::*MessageParser)(const UInt8*); | 	typedef EResult (ServerProxy::*MessageParser)(const UInt8*); | ||||||
|  | @ -129,9 +130,4 @@ private: | ||||||
| 
 | 
 | ||||||
| 	MessageParser		m_parser; | 	MessageParser		m_parser; | ||||||
| 	IEventQueue*		m_events; | 	IEventQueue*		m_events; | ||||||
| 
 |  | ||||||
| 	Stopwatch			m_stopwatch; |  | ||||||
| 	double				m_elapsedTime; |  | ||||||
| 	size_t				m_receivedDataSize; |  | ||||||
| 	static const UInt16	m_intervalThreshold; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -433,8 +433,8 @@ MSWindowsScreen::checkClipboards() | ||||||
| 	if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) { | 	if (m_ownClipboard && !MSWindowsClipboard::isOwnedBySynergy()) { | ||||||
| 		LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); | 		LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); | ||||||
| 		m_ownClipboard = false; | 		m_ownClipboard = false; | ||||||
| 		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); | 		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); | ||||||
| 		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); | 		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1501,8 +1501,8 @@ MSWindowsScreen::onClipboardChange() | ||||||
| 		if (m_ownClipboard) { | 		if (m_ownClipboard) { | ||||||
| 			LOG((CLOG_DEBUG "clipboard changed: lost ownership")); | 			LOG((CLOG_DEBUG "clipboard changed: lost ownership")); | ||||||
| 			m_ownClipboard = false; | 			m_ownClipboard = false; | ||||||
| 			sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); | 			sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); | ||||||
| 			sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); | 			sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	else if (!m_ownClipboard) { | 	else if (!m_ownClipboard) { | ||||||
|  |  | ||||||
|  | @ -954,8 +954,8 @@ OSXScreen::checkClipboards() | ||||||
| 	LOG((CLOG_DEBUG2 "checking clipboard")); | 	LOG((CLOG_DEBUG2 "checking clipboard")); | ||||||
| 	if (m_pasteboard.synchronize()) { | 	if (m_pasteboard.synchronize()) { | ||||||
| 		LOG((CLOG_DEBUG "clipboard changed")); | 		LOG((CLOG_DEBUG "clipboard changed")); | ||||||
| 		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardClipboard); | 		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); | ||||||
| 		sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), kClipboardSelection); | 		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1325,7 +1325,7 @@ XWindowsScreen::handleSystemEvent(const Event& event, void*) | ||||||
| 			if (id != kClipboardEnd) { | 			if (id != kClipboardEnd) { | ||||||
| 				LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); | 				LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); | ||||||
| 				m_clipboard[id]->lost(xevent->xselectionclear.time); | 				m_clipboard[id]->lost(xevent->xselectionclear.time); | ||||||
| 				sendClipboardEvent(m_events->forIScreen().clipboardGrabbed(), id); | 				sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), id); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ if (WIN32) | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
| if (APPLE) | if (APPLE) | ||||||
|  | 	# 10.7 should be supported, but gives is a _NXArgv linker error | ||||||
| 	if (OSX_TARGET_MINOR GREATER 7) | 	if (OSX_TARGET_MINOR GREATER 7) | ||||||
| 		add_subdirectory(ns) | 		add_subdirectory(ns) | ||||||
| 	endif() | 	endif() | ||||||
|  |  | ||||||
|  | @ -85,18 +85,20 @@ if (WIN32) | ||||||
| 			..\\..\\..\\..\\..\\ext\\${OPENSSL_PLAT_DIR}\\out32dll\\ssleay32.* | 			..\\..\\..\\..\\..\\ext\\${OPENSSL_PLAT_DIR}\\out32dll\\ssleay32.* | ||||||
| 			..\\..\\..\\..\\..\\bin\\${CMAKE_CFG_INTDIR} | 			..\\..\\..\\..\\..\\bin\\${CMAKE_CFG_INTDIR} | ||||||
| 	) | 	) | ||||||
| else() | endif() | ||||||
|  | 
 | ||||||
|  | if (UNIX) | ||||||
| 	if (APPLE) | 	if (APPLE) | ||||||
| 		add_custom_command( | 		add_custom_command( | ||||||
| 			TARGET ns | 			TARGET ns | ||||||
| 			POST_BUILD | 			POST_BUILD | ||||||
| 			COMMAND | 			COMMAND | ||||||
| 				mkdir -p | 				mkdir -p | ||||||
| 				${CMAKE_SOURCE_DIR}/bin/plugins | 				${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins | ||||||
| 				&& | 				&& | ||||||
| 				cp | 				cp | ||||||
| 				${CMAKE_SOURCE_DIR}/lib/${CMAKE_CFG_INTDIR}/libns.* | 				${CMAKE_SOURCE_DIR}/lib/${CMAKE_CFG_INTDIR}/libns.* | ||||||
| 				${CMAKE_SOURCE_DIR}/bin/plugins/ | 				${CMAKE_SOURCE_DIR}/bin/${CMAKE_CFG_INTDIR}/plugins/ | ||||||
| 		) | 		) | ||||||
| 	else() | 	else() | ||||||
| 		if (CMAKE_BUILD_TYPE STREQUAL Debug) | 		if (CMAKE_BUILD_TYPE STREQUAL Debug) | ||||||
|  | @ -104,11 +106,11 @@ else() | ||||||
| 				TARGET ns | 				TARGET ns | ||||||
| 				POST_BUILD | 				POST_BUILD | ||||||
| 				COMMAND mkdir -p | 				COMMAND mkdir -p | ||||||
| 					${CMAKE_SOURCE_DIR}/bin/plugins | 					${CMAKE_SOURCE_DIR}/bin/debug/plugins | ||||||
| 					&& | 					&& | ||||||
| 					cp | 					cp | ||||||
| 					${CMAKE_SOURCE_DIR}/lib/debug/libns.* | 					${CMAKE_SOURCE_DIR}/lib/debug/libns.* | ||||||
| 					${CMAKE_SOURCE_DIR}/bin/plugins/ | 					${CMAKE_SOURCE_DIR}/bin/debug/plugins/ | ||||||
| 			) | 			) | ||||||
| 		else() | 		else() | ||||||
| 			add_custom_command( | 			add_custom_command( | ||||||
|  |  | ||||||
|  | @ -35,7 +35,12 @@ | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #define MAX_ERROR_SIZE 65535 | #define MAX_ERROR_SIZE 65535 | ||||||
| #define MAX_RETRY_COUNT 60 | 
 | ||||||
|  | enum { | ||||||
|  | 	// this limit seems extremely high, but mac client seem to generate around
 | ||||||
|  | 	// 50,000 errors before they establish a connection (wtf?)
 | ||||||
|  | 	kMaxRetryCount = 100000 | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| static const char kFingerprintDirName[] = "SSL/Fingerprints"; | static const char kFingerprintDirName[] = "SSL/Fingerprints"; | ||||||
| //static const char kFingerprintLocalFilename[] = "Local.txt";
 | //static const char kFingerprintLocalFilename[] = "Local.txt";
 | ||||||
|  | @ -52,8 +57,8 @@ SecureSocket::SecureSocket( | ||||||
| 		SocketMultiplexer* socketMultiplexer) : | 		SocketMultiplexer* socketMultiplexer) : | ||||||
| 	TCPSocket(events, socketMultiplexer), | 	TCPSocket(events, socketMultiplexer), | ||||||
| 	m_secureReady(false), | 	m_secureReady(false), | ||||||
| 	m_maxRetry(MAX_RETRY_COUNT), | 	m_fatal(false), | ||||||
| 	m_fatal(false) | 	m_maxRetry(kMaxRetryCount) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -63,8 +68,8 @@ SecureSocket::SecureSocket( | ||||||
| 		ArchSocket socket) : | 		ArchSocket socket) : | ||||||
| 	TCPSocket(events, socketMultiplexer, socket), | 	TCPSocket(events, socketMultiplexer, socket), | ||||||
| 	m_secureReady(false), | 	m_secureReady(false), | ||||||
| 	m_maxRetry(MAX_RETRY_COUNT), | 	m_fatal(false), | ||||||
| 	m_fatal(false) | 	m_maxRetry(kMaxRetryCount) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -392,31 +397,25 @@ SecureSocket::checkResult(int status, int& retry) | ||||||
| 	case SSL_ERROR_ZERO_RETURN: | 	case SSL_ERROR_ZERO_RETURN: | ||||||
| 		// connection closed
 | 		// connection closed
 | ||||||
| 		isFatal(true); | 		isFatal(true); | ||||||
| 		LOG((CLOG_DEBUG "SSL connection has been closed")); | 		LOG((CLOG_DEBUG "ssl connection closed")); | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case SSL_ERROR_WANT_READ: | 	case SSL_ERROR_WANT_READ: | ||||||
| 	case SSL_ERROR_WANT_WRITE: | 	case SSL_ERROR_WANT_WRITE: | ||||||
| 	case SSL_ERROR_WANT_CONNECT: | 	case SSL_ERROR_WANT_CONNECT: | ||||||
| 	case SSL_ERROR_WANT_ACCEPT: | 	case SSL_ERROR_WANT_ACCEPT: | ||||||
| 		retry += 1; | 		// it seems like these sort of errors are part of openssl's normal behavior,
 | ||||||
| 		// If there are a lot of retrys, it's worth warning about
 | 		// so we should expect a very high amount of these. sleeping doesn't seem to
 | ||||||
| 		if ( retry % 5 == 0 ) { | 		// help... maybe you just have to swallow the errors (yuck).
 | ||||||
| 			LOG((CLOG_DEBUG "need to retry the same SSL function(%d) retry:%d", errorCode, retry)); | 		retry++; | ||||||
| 		} | 		LOG((CLOG_DEBUG2 "passive ssl error, error=%d, attempt=%d", errorCode, retry)); | ||||||
| 		else if ( retry == (maxRetry() / 2) ) { |  | ||||||
| 			LOG((CLOG_WARN "need to retry the same SSL function(%d) retry:%d", errorCode, retry)); |  | ||||||
| 		} |  | ||||||
| 		else { |  | ||||||
| 			LOG((CLOG_DEBUG2 "need to retry the same SSL function(%d) retry:%d", errorCode, retry)); |  | ||||||
| 		} |  | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case SSL_ERROR_SYSCALL: | 	case SSL_ERROR_SYSCALL: | ||||||
| 		LOG((CLOG_ERR "some secure socket I/O error occurred")); | 		LOG((CLOG_ERR "ssl error occurred (system call failure)")); | ||||||
| 		if (ERR_peek_error() == 0) { | 		if (ERR_peek_error() == 0) { | ||||||
| 			if (status == 0) { | 			if (status == 0) { | ||||||
| 				LOG((CLOG_ERR "an EOF violates the protocol")); | 				LOG((CLOG_ERR "eof violates ssl protocol")); | ||||||
| 			} | 			} | ||||||
| 			else if (status == -1) { | 			else if (status == -1) { | ||||||
| 				// underlying socket I/O reproted an error
 | 				// underlying socket I/O reproted an error
 | ||||||
|  | @ -433,19 +432,19 @@ SecureSocket::checkResult(int status, int& retry) | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case SSL_ERROR_SSL: | 	case SSL_ERROR_SSL: | ||||||
| 		LOG((CLOG_ERR "a failure in the SSL library occurred")); | 		LOG((CLOG_ERR "ssl error occurred (generic failure)")); | ||||||
| 		isFatal(true); | 		isFatal(true); | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	default: | 	default: | ||||||
| 		LOG((CLOG_ERR "unknown secure socket error")); | 		LOG((CLOG_ERR "ssl error occurred (unknown failure)")); | ||||||
| 		isFatal(true); | 		isFatal(true); | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// If the retry max would exceed the allowed, treat it as a fatal error
 | 	// If the retry max would exceed the allowed, treat it as a fatal error
 | ||||||
| 	if (retry > maxRetry()) { | 	if (retry > maxRetry()) { | ||||||
| 		LOG((CLOG_ERR "Maximum retry count exceeded:%d",retry)); | 		LOG((CLOG_ERR "passive ssl error limit exceeded: %d", retry)); | ||||||
| 		isFatal(true); | 		isFatal(true); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,6 +64,9 @@ public: | ||||||
| 	//! Get server which owns this listener
 | 	//! Get server which owns this listener
 | ||||||
| 	Server*				getServer() { return m_server; } | 	Server*				getServer() { return m_server; } | ||||||
| 
 | 
 | ||||||
|  | 	//! Return true if using secure network connection
 | ||||||
|  | 	bool				isSecure() { return m_useSecureNetwork; } | ||||||
|  | 
 | ||||||
| 	//@}
 | 	//@}
 | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |  | ||||||
|  | @ -275,16 +275,7 @@ ClientProxy1_0::leave() | ||||||
| void | void | ||||||
| ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) | ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) | ||||||
| { | { | ||||||
| 	// ignore if this clipboard is already clean
 | 	// ignore -- deprecated in protocol 1.0
 | ||||||
| 	if (m_clipboard[id].m_dirty) { |  | ||||||
| 		// this clipboard is now clean
 |  | ||||||
| 		m_clipboard[id].m_dirty = false; |  | ||||||
| 		Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard); |  | ||||||
| 
 |  | ||||||
| 		String data = m_clipboard[id].m_clipboard.marshall(); |  | ||||||
| 		LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); |  | ||||||
| 		ProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  | @ -450,34 +441,9 @@ ClientProxy1_0::recvInfo() | ||||||
| bool | bool | ||||||
| ClientProxy1_0::recvClipboard() | ClientProxy1_0::recvClipboard() | ||||||
| { | { | ||||||
| 	// parse message
 | 	// deprecated in protocol 1.0
 | ||||||
| 	ClipboardID id; |  | ||||||
| 	UInt32 seqNum; |  | ||||||
| 	String data; |  | ||||||
| 	if (!ProtocolUtil::readf(getStream(), |  | ||||||
| 							kMsgDClipboard + 4, &id, &seqNum, &data)) { |  | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 	LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size())); |  | ||||||
| 
 |  | ||||||
| 	// validate
 |  | ||||||
| 	if (id >= kClipboardEnd) { |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// save clipboard
 |  | ||||||
| 	m_clipboard[id].m_clipboard.unmarshall(data, 0); |  | ||||||
| 	m_clipboard[id].m_sequenceNumber = seqNum; |  | ||||||
| 
 |  | ||||||
| 	// notify
 |  | ||||||
| 	ClipboardInfo* info   = new ClipboardInfo; |  | ||||||
| 	info->m_id             = id; |  | ||||||
| 	info->m_sequenceNumber = seqNum; |  | ||||||
| 	m_events->addEvent(Event(m_events->forClientProxy().clipboardChanged(), |  | ||||||
| 							getEventTarget(), info)); |  | ||||||
| 
 |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| bool | bool | ||||||
| ClientProxy1_0::recvGrabClipboard() | ClientProxy1_0::recvGrabClipboard() | ||||||
|  | @ -499,13 +465,12 @@ ClientProxy1_0::recvGrabClipboard() | ||||||
| 	ClipboardInfo* info   = new ClipboardInfo; | 	ClipboardInfo* info   = new ClipboardInfo; | ||||||
| 	info->m_id             = id; | 	info->m_id             = id; | ||||||
| 	info->m_sequenceNumber = seqNum; | 	info->m_sequenceNumber = seqNum; | ||||||
| 	m_events->addEvent(Event(m_events->forIScreen().clipboardGrabbed(), | 	m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(), | ||||||
| 							getEventTarget(), info)); | 							getEventTarget(), info)); | ||||||
| 
 | 
 | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| //
 | //
 | ||||||
| // ClientProxy1_0::ClientClipboard
 | // ClientProxy1_0::ClientClipboard
 | ||||||
| //
 | //
 | ||||||
|  |  | ||||||
|  | @ -70,7 +70,7 @@ protected: | ||||||
| 	virtual void		resetHeartbeatTimer(); | 	virtual void		resetHeartbeatTimer(); | ||||||
| 	virtual void		addHeartbeatTimer(); | 	virtual void		addHeartbeatTimer(); | ||||||
| 	virtual void		removeHeartbeatTimer(); | 	virtual void		removeHeartbeatTimer(); | ||||||
| 
 | 	virtual bool		recvClipboard(); | ||||||
| private: | private: | ||||||
| 	void				disconnect(); | 	void				disconnect(); | ||||||
| 	void				removeHandlers(); | 	void				removeHandlers(); | ||||||
|  | @ -81,11 +81,9 @@ private: | ||||||
| 	void				handleFlatline(const Event&, void*); | 	void				handleFlatline(const Event&, void*); | ||||||
| 
 | 
 | ||||||
| 	bool				recvInfo(); | 	bool				recvInfo(); | ||||||
| 	bool				recvClipboard(); |  | ||||||
| 	bool				recvGrabClipboard(); | 	bool				recvGrabClipboard(); | ||||||
| 
 | 
 | ||||||
| private: | protected: | ||||||
| 	typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*); |  | ||||||
| 	struct ClientClipboard { | 	struct ClientClipboard { | ||||||
| 	public: | 	public: | ||||||
| 		ClientClipboard(); | 		ClientClipboard(); | ||||||
|  | @ -96,8 +94,12 @@ private: | ||||||
| 		bool			m_dirty; | 		bool			m_dirty; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	ClientInfo			m_info; |  | ||||||
| 	ClientClipboard	m_clipboard[kClipboardEnd]; | 	ClientClipboard	m_clipboard[kClipboardEnd]; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*); | ||||||
|  | 
 | ||||||
|  | 	ClientInfo			m_info; | ||||||
| 	double				m_heartbeatAlarm; | 	double				m_heartbeatAlarm; | ||||||
| 	EventQueueTimer*	m_heartbeatTimer; | 	EventQueueTimer*	m_heartbeatTimer; | ||||||
| 	MessageParser		m_parser; | 	MessageParser		m_parser; | ||||||
|  |  | ||||||
|  | @ -18,22 +18,21 @@ | ||||||
| #include "server/ClientProxy1_5.h" | #include "server/ClientProxy1_5.h" | ||||||
| 
 | 
 | ||||||
| #include "server/Server.h" | #include "server/Server.h" | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
| #include "synergy/ProtocolUtil.h" | #include "synergy/ProtocolUtil.h" | ||||||
| #include "io/IStream.h" | #include "io/IStream.h" | ||||||
| #include "base/Log.h" | #include "base/Log.h" | ||||||
| 
 | 
 | ||||||
|  | #include <sstream> | ||||||
|  | 
 | ||||||
| //
 | //
 | ||||||
| // ClientProxy1_5
 | // ClientProxy1_5
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| const UInt16 ClientProxy1_5::m_intervalThreshold = 1; |  | ||||||
| 
 |  | ||||||
| ClientProxy1_5::ClientProxy1_5(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) : | ClientProxy1_5::ClientProxy1_5(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) : | ||||||
| 	ClientProxy1_4(name, stream, server, events), | 	ClientProxy1_4(name, stream, server, events), | ||||||
| 	m_events(events), | 	m_events(events) | ||||||
| 	m_stopwatch(true), |  | ||||||
| 	m_elapsedTime(0), |  | ||||||
| 	m_receivedDataSize(0) |  | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -52,23 +51,7 @@ ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size) | ||||||
| void | void | ||||||
| ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize) | ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize) | ||||||
| { | { | ||||||
| 	String chunk(data, dataSize); | 	FileChunk::send(getStream(), mark, data, dataSize); | ||||||
| 
 |  | ||||||
| 	switch (mark) { |  | ||||||
| 	case kFileStart: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file sending start: size=%s", data)); |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileChunk: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file chunk sending: size=%i", chunk.size())); |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileEnd: |  | ||||||
| 		LOG((CLOG_DEBUG2 "file sending finished")); |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ProtocolUtil::writef(getStream(), kMsgDFileTransfer, mark, &chunk); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool | bool | ||||||
|  | @ -90,51 +73,15 @@ ClientProxy1_5::parseMessage(const UInt8* code) | ||||||
| void | void | ||||||
| ClientProxy1_5::fileChunkReceived() | ClientProxy1_5::fileChunkReceived() | ||||||
| { | { | ||||||
| 	// parse
 |  | ||||||
| 	UInt8 mark = 0; |  | ||||||
| 	String content; |  | ||||||
| 	ProtocolUtil::readf(getStream(), kMsgDFileTransfer + 4, &mark, &content); |  | ||||||
| 
 |  | ||||||
| 	Server* server = getServer(); | 	Server* server = getServer(); | ||||||
| 	switch (mark) { | 	int result = FileChunk::assemble( | ||||||
| 	case kFileStart: | 					getStream(), | ||||||
| 		server->clearReceivedFileData(); | 					server->getReceivedFileData(), | ||||||
| 		server->setExpectedFileSize(content); | 					server->getExpectedFileSize()); | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 			LOG((CLOG_DEBUG2 "recv file data from client: file size=%s", content.c_str())); |  | ||||||
| 			m_stopwatch.start(); |  | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
| 	 | 	 | ||||||
| 	case kFileChunk: |  | ||||||
| 		server->fileChunkReceived(content); |  | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 				LOG((CLOG_DEBUG2 "recv file data from client: chunck size=%i", content.size())); |  | ||||||
| 				double interval = m_stopwatch.getTime(); |  | ||||||
| 				m_receivedDataSize += content.size(); |  | ||||||
| 				LOG((CLOG_DEBUG2 "recv file data from client: interval=%f s", interval)); |  | ||||||
| 				if (interval >= m_intervalThreshold) { |  | ||||||
| 					double averageSpeed = m_receivedDataSize / interval / 1000; |  | ||||||
| 					LOG((CLOG_DEBUG2 "recv file data from client: average speed=%f kb/s", averageSpeed)); |  | ||||||
| 
 | 
 | ||||||
| 					m_receivedDataSize = 0; | 	if (result == kFinish) { | ||||||
| 					m_elapsedTime += interval; |  | ||||||
| 					m_stopwatch.reset(); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 
 |  | ||||||
| 	case kFileEnd: |  | ||||||
| 		m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server)); | 		m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server)); | ||||||
| 		if (CLOG->getFilter() >= kDEBUG2) { |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished")); |  | ||||||
| 			m_elapsedTime += m_stopwatch.getTime(); |  | ||||||
| 			double averageSpeed = getServer()->getExpectedFileSize() / m_elapsedTime / 1000; |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", m_elapsedTime)); |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", getServer()->getExpectedFileSize() / 1000)); |  | ||||||
| 			LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed)); |  | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "server/ClientProxy1_4.h" | #include "server/ClientProxy1_4.h" | ||||||
| #include "base/Stopwatch.h" | #include "base/Stopwatch.h" | ||||||
|  | #include "common/stdvector.h" | ||||||
| 
 | 
 | ||||||
| class Server; | class Server; | ||||||
| class IEventQueue; | class IEventQueue; | ||||||
|  | @ -37,9 +38,4 @@ public: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	IEventQueue*		m_events; | 	IEventQueue*		m_events; | ||||||
| 
 |  | ||||||
| 	Stopwatch			m_stopwatch; |  | ||||||
| 	double				m_elapsedTime; |  | ||||||
| 	size_t				m_receivedDataSize; |  | ||||||
| 	static const UInt16	m_intervalThreshold; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,110 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "server/ClientProxy1_6.h" | ||||||
|  | 
 | ||||||
|  | #include "server/Server.h" | ||||||
|  | #include "synergy/ProtocolUtil.h" | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
|  | #include "synergy/ClipboardChunk.h" | ||||||
|  | #include "io/IStream.h" | ||||||
|  | #include "base/TMethodEventJob.h" | ||||||
|  | #include "base/Log.h" | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // ClientProxy1_6
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | enum | ||||||
|  | { | ||||||
|  | 	kSslClipboardMaxSize = 1024 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | ClientProxy1_6::ClientProxy1_6(const String& name, synergy::IStream* stream, Server* server, IEventQueue* events) : | ||||||
|  | 	ClientProxy1_5(name, stream, server, events), | ||||||
|  | 	m_events(events) | ||||||
|  | { | ||||||
|  | 	m_events->adoptHandler(m_events->forClipboard().clipboardSending(), | ||||||
|  | 								this, | ||||||
|  | 								new TMethodEventJob<ClientProxy1_6>(this, | ||||||
|  | 									&ClientProxy1_6::handleClipboardSendingEvent)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ClientProxy1_6::~ClientProxy1_6() | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | ClientProxy1_6::setClipboard(ClipboardID id, const IClipboard* clipboard) | ||||||
|  | { | ||||||
|  | 	// ignore if this clipboard is already clean
 | ||||||
|  | 	if (m_clipboard[id].m_dirty) { | ||||||
|  | 		// this clipboard is now clean
 | ||||||
|  | 		m_clipboard[id].m_dirty = false; | ||||||
|  | 		Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard); | ||||||
|  | 
 | ||||||
|  | 		String data = m_clipboard[id].m_clipboard.marshall(); | ||||||
|  | 
 | ||||||
|  | 		size_t size = data.size(); | ||||||
|  | 		LOG((CLOG_DEBUG "sending clipboard %d to \"%s\"", id, getName().c_str())); | ||||||
|  | 
 | ||||||
|  | 		// HACK: if using SSL, don't send large clipboards (#4601)
 | ||||||
|  | 		bool send = true; | ||||||
|  | 		if (getServer()->isSecure() && (size > kSslClipboardMaxSize)) { | ||||||
|  | 			send = false; | ||||||
|  | 			LOG((CLOG_WARN "large clipboards not supported with ssl, size=%d", size)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (send) { | ||||||
|  | 			StreamChunker::sendClipboard(data, size, id, 0, m_events, this); | ||||||
|  | 			LOG((CLOG_DEBUG "sent clipboard size=%d", size)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | ClientProxy1_6::handleClipboardSendingEvent(const Event& event, void*) | ||||||
|  | { | ||||||
|  | 	ClipboardChunk::send(getStream(), event.getData()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool | ||||||
|  | ClientProxy1_6::recvClipboard() | ||||||
|  | { | ||||||
|  | 	// parse message
 | ||||||
|  | 	static String dataCached; | ||||||
|  | 	ClipboardID id; | ||||||
|  | 	UInt32 seq; | ||||||
|  | 
 | ||||||
|  | 	int r = ClipboardChunk::assemble(getStream(), dataCached, id, seq); | ||||||
|  | 
 | ||||||
|  | 	if (r == kFinish) { | ||||||
|  | 		LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seq, dataCached.size())); | ||||||
|  | 		// save clipboard
 | ||||||
|  | 		m_clipboard[id].m_clipboard.unmarshall(dataCached, 0); | ||||||
|  | 		m_clipboard[id].m_sequenceNumber = seq; | ||||||
|  | 		 | ||||||
|  | 		// notify
 | ||||||
|  | 		ClipboardInfo* info = new ClipboardInfo; | ||||||
|  | 		info->m_id = id; | ||||||
|  | 		info->m_sequenceNumber = seq; | ||||||
|  | 		m_events->addEvent(Event(m_events->forClipboard().clipboardChanged(), | ||||||
|  | 								 getEventTarget(), info)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| /*
 | /*
 | ||||||
|  * synergy -- mouse and keyboard sharing utility |  * synergy -- mouse and keyboard sharing utility | ||||||
|  * Copyright (C) 2013 Synergy Si Ltd. |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  *  |  *  | ||||||
|  * This package is free software; you can redistribute it and/or |  * This package is free software; you can redistribute it and/or | ||||||
|  * modify it under the terms of the GNU General Public License |  * modify it under the terms of the GNU General Public License | ||||||
|  | @ -17,30 +17,23 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "base/String.h" | #include "server/ClientProxy1_5.h" | ||||||
| 
 | 
 | ||||||
|  | class Server; | ||||||
| class IEventQueue; | class IEventQueue; | ||||||
| 
 | 
 | ||||||
| class FileChunker { | //! Proxy for client implementing protocol version 1.6
 | ||||||
|  | class ClientProxy1_6 : public ClientProxy1_5 { | ||||||
| public: | public: | ||||||
| 	//! FileChunk data
 | 	ClientProxy1_6(const String& name, synergy::IStream* adoptedStream, Server* server, IEventQueue* events); | ||||||
| 	class FileChunk { | 	~ClientProxy1_6(); | ||||||
| 	public: |  | ||||||
| 		FileChunk(size_t chunkSize) : m_dataSize(chunkSize - 2) |  | ||||||
| 		{ |  | ||||||
| 			m_chunk = new char[chunkSize];  |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		~FileChunk() { delete[] m_chunk; } | 	virtual void		setClipboard(ClipboardID id, const IClipboard* clipboard); | ||||||
| 
 | 	virtual bool		recvClipboard(); | ||||||
| 	public: |  | ||||||
| 		const size_t	m_dataSize; |  | ||||||
| 		char*			m_chunk; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	static void			sendFileChunks(char* filename, IEventQueue* events, void* eventTarget); |  | ||||||
| 	static String		intToString(size_t i); |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	static const size_t m_chunkSize; | 	void				handleClipboardSendingEvent(const Event&, void*); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	IEventQueue*		m_events; | ||||||
| }; | }; | ||||||
|  | @ -25,6 +25,7 @@ | ||||||
| #include "server/ClientProxy1_3.h" | #include "server/ClientProxy1_3.h" | ||||||
| #include "server/ClientProxy1_4.h" | #include "server/ClientProxy1_4.h" | ||||||
| #include "server/ClientProxy1_5.h" | #include "server/ClientProxy1_5.h" | ||||||
|  | #include "server/ClientProxy1_6.h" | ||||||
| #include "synergy/protocol_types.h" | #include "synergy/protocol_types.h" | ||||||
| #include "synergy/ProtocolUtil.h" | #include "synergy/ProtocolUtil.h" | ||||||
| #include "synergy/XSynergy.h" | #include "synergy/XSynergy.h" | ||||||
|  | @ -227,6 +228,10 @@ ClientProxyUnknown::handleData(const Event&, void*) | ||||||
| 			case 5: | 			case 5: | ||||||
| 				m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); | 				m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); | ||||||
| 				break; | 				break; | ||||||
|  | 
 | ||||||
|  | 			case 6: | ||||||
|  | 				m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,13 +22,14 @@ | ||||||
| #include "server/ClientProxyUnknown.h" | #include "server/ClientProxyUnknown.h" | ||||||
| #include "server/PrimaryClient.h" | #include "server/PrimaryClient.h" | ||||||
| #include "server/ClientListener.h" | #include "server/ClientListener.h" | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
| #include "synergy/IPlatformScreen.h" | #include "synergy/IPlatformScreen.h" | ||||||
| #include "synergy/DropHelper.h" | #include "synergy/DropHelper.h" | ||||||
| #include "synergy/option_types.h" | #include "synergy/option_types.h" | ||||||
| #include "synergy/protocol_types.h" | #include "synergy/protocol_types.h" | ||||||
| #include "synergy/XScreen.h" | #include "synergy/XScreen.h" | ||||||
| #include "synergy/XSynergy.h" | #include "synergy/XSynergy.h" | ||||||
| #include "synergy/FileChunker.h" | #include "synergy/StreamChunker.h" | ||||||
| #include "synergy/KeyState.h" | #include "synergy/KeyState.h" | ||||||
| #include "synergy/Screen.h" | #include "synergy/Screen.h" | ||||||
| #include "synergy/PacketStreamFilter.h" | #include "synergy/PacketStreamFilter.h" | ||||||
|  | @ -91,7 +92,8 @@ Server::Server( | ||||||
| 	m_ignoreFileTransfer(false), | 	m_ignoreFileTransfer(false), | ||||||
| 	m_enableDragDrop(enableDragDrop), | 	m_enableDragDrop(enableDragDrop), | ||||||
| 	m_getDragInfoThread(NULL), | 	m_getDragInfoThread(NULL), | ||||||
| 	m_waitDragInfoThread(true) | 	m_waitDragInfoThread(true), | ||||||
|  | 	m_sendClipboardThread(NULL) | ||||||
| { | { | ||||||
| 	// must have a primary client and it must have a canonical name
 | 	// must have a primary client and it must have a canonical name
 | ||||||
| 	assert(m_primaryClient != NULL); | 	assert(m_primaryClient != NULL); | ||||||
|  | @ -503,11 +505,18 @@ Server::switchScreen(BaseClientProxy* dst, | ||||||
| 		m_active->enter(x, y, m_seqNum, | 		m_active->enter(x, y, m_seqNum, | ||||||
| 								m_primaryClient->getToggleMask(), | 								m_primaryClient->getToggleMask(), | ||||||
| 								forScreensaver); | 								forScreensaver); | ||||||
|  | 		// if already sending clipboard, we need to interupt it, otherwise
 | ||||||
|  | 		// clipboard data could be corrupted on the other side
 | ||||||
|  | 		if (m_sendClipboardThread != NULL) { | ||||||
|  | 			StreamChunker::interruptClipboard(); | ||||||
|  | 		} | ||||||
| 		 | 		 | ||||||
| 		// send the clipboard data to new active screen
 | 		// send the clipboard data to new active screen
 | ||||||
| 		for (ClipboardID id = 0; id < kClipboardEnd; ++id) { | 		m_sendClipboardThread = new Thread( | ||||||
| 			m_active->setClipboard(id, &m_clipboards[id].m_clipboard); | 										new TMethodJob<Server>( | ||||||
| 		} | 												this, | ||||||
|  | 												&Server::sendClipboardThread, | ||||||
|  | 												NULL)); | ||||||
| 
 | 
 | ||||||
| 		Server::SwitchToScreenInfo* info = | 		Server::SwitchToScreenInfo* info = | ||||||
| 			Server::SwitchToScreenInfo::alloc(m_active->getName()); | 			Server::SwitchToScreenInfo::alloc(m_active->getName()); | ||||||
|  | @ -1849,6 +1858,14 @@ Server::sendDragInfo(BaseClientProxy* newScreen) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | Server::sendClipboardThread(void*) | ||||||
|  | { | ||||||
|  | 	for (ClipboardID id = 0; id < kClipboardEnd; ++id) { | ||||||
|  | 		m_active->setClipboard(id, &m_clipboards[id].m_clipboard); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy) | Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy) | ||||||
| { | { | ||||||
|  | @ -2023,13 +2040,13 @@ Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta) | ||||||
| void | void | ||||||
| Server::onFileChunkSending(const void* data) | Server::onFileChunkSending(const void* data) | ||||||
| { | { | ||||||
| 	FileChunker::FileChunk* fileChunk = reinterpret_cast<FileChunker::FileChunk*>(const_cast<void*>(data)); | 	FileChunk* chunk = reinterpret_cast<FileChunk*>(const_cast<void*>(data)); | ||||||
| 
 | 
 | ||||||
| 	LOG((CLOG_DEBUG1 "onFileChunkSending")); | 	LOG((CLOG_DEBUG1 "sending file chunk")); | ||||||
| 	assert(m_active != NULL); | 	assert(m_active != NULL); | ||||||
| 
 | 
 | ||||||
| 	// relay
 | 	// relay
 | ||||||
|  	m_active->fileChunkSending(fileChunk->m_chunk[0], &(fileChunk->m_chunk[1]), fileChunk->m_dataSize); |  	m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  | @ -2068,11 +2085,11 @@ Server::addClient(BaseClientProxy* client) | ||||||
| 							client->getEventTarget(), | 							client->getEventTarget(), | ||||||
| 							new TMethodEventJob<Server>(this, | 							new TMethodEventJob<Server>(this, | ||||||
| 								&Server::handleShapeChanged, client)); | 								&Server::handleShapeChanged, client)); | ||||||
| 	m_events->adoptHandler(m_events->forIScreen().clipboardGrabbed(), | 	m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), | ||||||
| 							client->getEventTarget(), | 							client->getEventTarget(), | ||||||
| 							new TMethodEventJob<Server>(this, | 							new TMethodEventJob<Server>(this, | ||||||
| 								&Server::handleClipboardGrabbed, client)); | 								&Server::handleClipboardGrabbed, client)); | ||||||
| 	m_events->adoptHandler(m_events->forClientProxy().clipboardChanged(), | 	m_events->adoptHandler(m_events->forClipboard().clipboardChanged(), | ||||||
| 							client->getEventTarget(), | 							client->getEventTarget(), | ||||||
| 							new TMethodEventJob<Server>(this, | 							new TMethodEventJob<Server>(this, | ||||||
| 								&Server::handleClipboardChanged, client)); | 								&Server::handleClipboardChanged, client)); | ||||||
|  | @ -2104,9 +2121,9 @@ Server::removeClient(BaseClientProxy* client) | ||||||
| 	// remove event handlers
 | 	// remove event handlers
 | ||||||
| 	m_events->removeHandler(m_events->forIScreen().shapeChanged(), | 	m_events->removeHandler(m_events->forIScreen().shapeChanged(), | ||||||
| 							client->getEventTarget()); | 							client->getEventTarget()); | ||||||
| 	m_events->removeHandler(m_events->forIScreen().clipboardGrabbed(), | 	m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), | ||||||
| 							client->getEventTarget()); | 							client->getEventTarget()); | ||||||
| 	m_events->removeHandler(m_events->forClientProxy().clipboardChanged(), | 	m_events->removeHandler(m_events->forClipboard().clipboardChanged(), | ||||||
| 							client->getEventTarget()); | 							client->getEventTarget()); | ||||||
| 
 | 
 | ||||||
| 	// remove from list
 | 	// remove from list
 | ||||||
|  | @ -2326,25 +2343,6 @@ Server::KeyboardBroadcastInfo::alloc(State state, const String& screens) | ||||||
| 	return info; | 	return info; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void |  | ||||||
| Server::clearReceivedFileData() |  | ||||||
| { |  | ||||||
| 	m_receivedFileData.clear(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void |  | ||||||
| Server::setExpectedFileSize(String data) |  | ||||||
| { |  | ||||||
| 	std::istringstream iss(data); |  | ||||||
| 	iss >> m_expectedFileSize; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void |  | ||||||
| Server::fileChunkReceived(String data) |  | ||||||
| { |  | ||||||
| 	m_receivedFileData += data; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool | bool | ||||||
| Server::isReceivedFileSizeValid() | Server::isReceivedFileSizeValid() | ||||||
| { | { | ||||||
|  | @ -2354,6 +2352,10 @@ Server::isReceivedFileSizeValid() | ||||||
| void | void | ||||||
| Server::sendFileToClient(const char* filename) | Server::sendFileToClient(const char* filename) | ||||||
| { | { | ||||||
|  | 	if (m_sendFileThread != NULL) { | ||||||
|  | 		StreamChunker::interruptFile(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	m_sendFileThread = new Thread( | 	m_sendFileThread = new Thread( | ||||||
| 		new TMethodJob<Server>( | 		new TMethodJob<Server>( | ||||||
| 			this, &Server::sendFileThread, | 			this, &Server::sendFileThread, | ||||||
|  | @ -2366,7 +2368,7 @@ Server::sendFileThread(void* data) | ||||||
| 	try { | 	try { | ||||||
| 		char* filename = reinterpret_cast<char*>(data); | 		char* filename = reinterpret_cast<char*>(data); | ||||||
| 		LOG((CLOG_DEBUG "sending file to client, filename=%s", filename)); | 		LOG((CLOG_DEBUG "sending file to client, filename=%s", filename)); | ||||||
| 		FileChunker::sendFileChunks(filename, m_events, this); | 		StreamChunker::sendFile(filename, m_events, this); | ||||||
| 	} | 	} | ||||||
| 	catch (std::runtime_error error) { | 	catch (std::runtime_error error) { | ||||||
| 		LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what())); | 		LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what())); | ||||||
|  | @ -2387,3 +2389,9 @@ Server::dragInfoReceived(UInt32 fileNum, String content) | ||||||
| 
 | 
 | ||||||
| 	m_screen->startDraggingFiles(m_dragFileList); | 	m_screen->startDraggingFiles(m_dragFileList); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool | ||||||
|  | Server::isSecure() const | ||||||
|  | { | ||||||
|  | 	return m_clientListener->isSecure(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -141,15 +141,6 @@ public: | ||||||
| 	*/ | 	*/ | ||||||
| 	void				disconnect(); | 	void				disconnect(); | ||||||
| 
 | 
 | ||||||
| 	//! Clears the file buffer
 |  | ||||||
| 	void				clearReceivedFileData(); |  | ||||||
| 
 |  | ||||||
| 	//! Set the expected size of receiving file
 |  | ||||||
| 	void				setExpectedFileSize(String data); |  | ||||||
| 	 |  | ||||||
| 	//! Received a chunk of file data
 |  | ||||||
| 	void				fileChunkReceived(String data); |  | ||||||
| 
 |  | ||||||
| 	//! Create a new thread and use it to send file to client
 | 	//! Create a new thread and use it to send file to client
 | ||||||
| 	void				sendFileToClient(const char* filename); | 	void				sendFileToClient(const char* filename); | ||||||
| 
 | 
 | ||||||
|  | @ -178,8 +169,14 @@ public: | ||||||
| 	//! Return true if recieved file size is valid
 | 	//! Return true if recieved file size is valid
 | ||||||
| 	bool				isReceivedFileSizeValid(); | 	bool				isReceivedFileSizeValid(); | ||||||
| 
 | 
 | ||||||
| 	//! Return expected file size
 | 	//! Return expected file data size
 | ||||||
| 	size_t				getExpectedFileSize() { return m_expectedFileSize; } | 	size_t&				getExpectedFileSize() { return m_expectedFileSize; } | ||||||
|  | 
 | ||||||
|  | 	//! Return received file data
 | ||||||
|  | 	String&				getReceivedFileData() { return m_receivedFileData; } | ||||||
|  | 
 | ||||||
|  | 	//! Return true if using secure network connection
 | ||||||
|  | 	bool				isSecure() const; | ||||||
| 
 | 
 | ||||||
| 	//@}
 | 	//@}
 | ||||||
| 
 | 
 | ||||||
|  | @ -370,6 +367,9 @@ private: | ||||||
| 	// send drag info to new client screen
 | 	// send drag info to new client screen
 | ||||||
| 	void				sendDragInfo(BaseClientProxy* newScreen); | 	void				sendDragInfo(BaseClientProxy* newScreen); | ||||||
| 
 | 
 | ||||||
|  | 	// thread funciton for sending clipboard
 | ||||||
|  | 	void				sendClipboardThread(void*); | ||||||
|  | 
 | ||||||
| public: | public: | ||||||
| 	bool				m_mock; | 	bool				m_mock; | ||||||
| 
 | 
 | ||||||
|  | @ -480,4 +480,6 @@ private: | ||||||
| 	bool				m_waitDragInfoThread; | 	bool				m_waitDragInfoThread; | ||||||
| 
 | 
 | ||||||
| 	ClientListener*		m_clientListener; | 	ClientListener*		m_clientListener; | ||||||
|  | 
 | ||||||
|  | 	Thread*				m_sendClipboardThread; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "synergy/ArgParser.h" | #include "synergy/ArgParser.h" | ||||||
| 
 | 
 | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
| #include "synergy/App.h" | #include "synergy/App.h" | ||||||
| #include "synergy/ServerArgs.h" | #include "synergy/ServerArgs.h" | ||||||
| #include "synergy/ClientArgs.h" | #include "synergy/ClientArgs.h" | ||||||
|  | @ -288,6 +289,7 @@ ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i) | ||||||
| 	} | 	} | ||||||
| 	else if (isArg(i, argc, argv, NULL, "--enable-crypto")) { | 	else if (isArg(i, argc, argv, NULL, "--enable-crypto")) { | ||||||
| 		argsBase().m_enableCrypto = true; | 		argsBase().m_enableCrypto = true; | ||||||
|  | 		StreamChunker::updateChunkSize(true); | ||||||
| 	} | 	} | ||||||
| 	else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) { | 	else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) { | ||||||
| 		argsBase().m_profileDirectory = argv[++i]; | 		argsBase().m_profileDirectory = argv[++i]; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "synergy/Chunk.h" | ||||||
|  | #include "base/String.h" | ||||||
|  | 
 | ||||||
|  | Chunk::Chunk(size_t size) | ||||||
|  | { | ||||||
|  | 	m_chunk = new char[size]; | ||||||
|  | 	memset(m_chunk, 0, size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Chunk::~Chunk() | ||||||
|  | { | ||||||
|  | 	delete[] m_chunk; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/basic_types.h" | ||||||
|  | 
 | ||||||
|  | class Chunk { | ||||||
|  | public: | ||||||
|  | 	Chunk(size_t size); | ||||||
|  | 	~Chunk(); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  | 	size_t				m_dataSize; | ||||||
|  | 	char*				m_chunk; | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,154 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "synergy/ClipboardChunk.h" | ||||||
|  | 
 | ||||||
|  | #include "synergy/ProtocolUtil.h" | ||||||
|  | #include "synergy/protocol_types.h" | ||||||
|  | #include "io/IStream.h" | ||||||
|  | #include "base/Log.h" | ||||||
|  | 
 | ||||||
|  | ClipboardChunk::ClipboardChunk(size_t size) : | ||||||
|  | 	Chunk(size) | ||||||
|  | { | ||||||
|  | 		m_dataSize = size - CLIPBOARD_CHUNK_META_SIZE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ClipboardChunk* | ||||||
|  | ClipboardChunk::start( | ||||||
|  | 					ClipboardID id, | ||||||
|  | 					UInt32 sequence, | ||||||
|  | 					const String& size) | ||||||
|  | { | ||||||
|  | 	size_t sizeLength = size.size(); | ||||||
|  | 	ClipboardChunk* start = new ClipboardChunk(sizeLength + CLIPBOARD_CHUNK_META_SIZE); | ||||||
|  | 	char* chunk = start->m_chunk; | ||||||
|  | 
 | ||||||
|  | 	chunk[0] = id; | ||||||
|  | 	UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]); | ||||||
|  | 	*seq = sequence; | ||||||
|  | 	chunk[5] = kDataStart; | ||||||
|  | 	memcpy(&chunk[6], size.c_str(), sizeLength); | ||||||
|  | 	chunk[sizeLength + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return start; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ClipboardChunk* | ||||||
|  | ClipboardChunk::data( | ||||||
|  | 					ClipboardID id, | ||||||
|  | 					UInt32 sequence, | ||||||
|  | 					const String& data) | ||||||
|  | { | ||||||
|  | 	size_t dataSize = data.size(); | ||||||
|  | 	ClipboardChunk* chunk = new ClipboardChunk(dataSize + CLIPBOARD_CHUNK_META_SIZE); | ||||||
|  | 	char* chunkData = chunk->m_chunk; | ||||||
|  | 
 | ||||||
|  | 	chunkData[0] = id; | ||||||
|  | 	UInt32* seq = reinterpret_cast<UInt32*>(&chunkData[1]); | ||||||
|  | 	*seq = sequence; | ||||||
|  | 	chunkData[5] = kDataChunk; | ||||||
|  | 	memcpy(&chunkData[6], data.c_str(), dataSize); | ||||||
|  | 	chunkData[dataSize + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return chunk; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ClipboardChunk* | ||||||
|  | ClipboardChunk::end(ClipboardID id, UInt32 sequence) | ||||||
|  | { | ||||||
|  | 	ClipboardChunk* end = new ClipboardChunk(CLIPBOARD_CHUNK_META_SIZE); | ||||||
|  | 	char* chunk = end->m_chunk; | ||||||
|  | 	 | ||||||
|  | 	chunk[0] = id; | ||||||
|  | 	UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]); | ||||||
|  | 	*seq = sequence; | ||||||
|  | 	chunk[5] = kDataEnd; | ||||||
|  | 	chunk[CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return end; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int | ||||||
|  | ClipboardChunk::assemble(synergy::IStream* stream, | ||||||
|  | 					String& dataCached, | ||||||
|  | 					ClipboardID& id, | ||||||
|  | 					UInt32& sequence) | ||||||
|  | { | ||||||
|  | 	static size_t expectedSize; | ||||||
|  | 	UInt8 mark; | ||||||
|  | 	String data; | ||||||
|  | 
 | ||||||
|  | 	if (!ProtocolUtil::readf(stream, kMsgDClipboard + 4, &id, &sequence, &mark, &data)) { | ||||||
|  | 		return kError; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if (mark == kDataStart) { | ||||||
|  | 		expectedSize = synergy::string::stringToSizeType(data); | ||||||
|  | 		LOG((CLOG_DEBUG "start receiving clipboard data")); | ||||||
|  | 		dataCached.clear(); | ||||||
|  | 		return kNotFinish; | ||||||
|  | 	} | ||||||
|  | 	else if (mark == kDataChunk) { | ||||||
|  | 		dataCached.append(data); | ||||||
|  | 		return kNotFinish; | ||||||
|  | 	} | ||||||
|  | 	else if (mark == kDataEnd) { | ||||||
|  | 		// validate
 | ||||||
|  | 		if (id >= kClipboardEnd) { | ||||||
|  | 			return kError; | ||||||
|  | 		} | ||||||
|  | 		else if (expectedSize != dataCached.size()) { | ||||||
|  | 			LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", expectedSize, dataCached.size())); | ||||||
|  | 			return kError; | ||||||
|  | 		} | ||||||
|  | 		return kFinish; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return kError; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | ClipboardChunk::send(synergy::IStream* stream, void* data) | ||||||
|  | { | ||||||
|  | 	ClipboardChunk* clipboardData = reinterpret_cast<ClipboardChunk*>(data); | ||||||
|  | 
 | ||||||
|  | 	LOG((CLOG_DEBUG1 "sending clipboard chunk")); | ||||||
|  | 
 | ||||||
|  | 	char* chunk = clipboardData->m_chunk; | ||||||
|  | 	ClipboardID id = chunk[0]; | ||||||
|  | 	UInt32* seq = reinterpret_cast<UInt32*>(&chunk[1]); | ||||||
|  | 	UInt32 sequence = *seq; | ||||||
|  | 	UInt8 mark = chunk[5]; | ||||||
|  | 	String dataChunk(&chunk[6], clipboardData->m_dataSize); | ||||||
|  | 
 | ||||||
|  | 	switch (mark) { | ||||||
|  | 	case kDataStart: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending clipboard chunk start: size=%s", dataChunk.c_str())); | ||||||
|  | 		break; | ||||||
|  | 
 | ||||||
|  | 	case kDataChunk: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending clipboard chunk data: size=%i", dataChunk.size())); | ||||||
|  | 		break; | ||||||
|  | 
 | ||||||
|  | 	case kDataEnd: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending clipboard finished")); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ProtocolUtil::writef(stream, kMsgDClipboard, id, sequence, mark, &dataChunk); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,55 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "synergy/Chunk.h" | ||||||
|  | #include "synergy/clipboard_types.h" | ||||||
|  | #include "base/String.h" | ||||||
|  | #include "common/basic_types.h" | ||||||
|  | 
 | ||||||
|  | #define CLIPBOARD_CHUNK_META_SIZE 7 | ||||||
|  | 
 | ||||||
|  | namespace synergy { | ||||||
|  | class IStream; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class ClipboardChunk : public Chunk { | ||||||
|  | public: | ||||||
|  | 	ClipboardChunk(size_t size); | ||||||
|  | 
 | ||||||
|  | 	static ClipboardChunk* | ||||||
|  | 						start( | ||||||
|  | 							ClipboardID id, | ||||||
|  | 							UInt32 sequence, | ||||||
|  | 							const String& size); | ||||||
|  | 	static ClipboardChunk* | ||||||
|  | 						data( | ||||||
|  | 							ClipboardID id, | ||||||
|  | 							UInt32 sequence, | ||||||
|  | 							const String& data); | ||||||
|  | 	static ClipboardChunk* | ||||||
|  | 						end(ClipboardID id, UInt32 sequence); | ||||||
|  | 
 | ||||||
|  | 	static int			assemble( | ||||||
|  | 							synergy::IStream* stream, | ||||||
|  | 							String& dataCached, | ||||||
|  | 							ClipboardID& id, | ||||||
|  | 							UInt32& sequence); | ||||||
|  | 
 | ||||||
|  | 	static void			send(synergy::IStream* stream, void* data); | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,152 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
|  | 
 | ||||||
|  | #include "synergy/ProtocolUtil.h" | ||||||
|  | #include "synergy/protocol_types.h" | ||||||
|  | #include "io/IStream.h" | ||||||
|  | #include "base/Stopwatch.h" | ||||||
|  | #include "base/Log.h" | ||||||
|  | 
 | ||||||
|  | static const UInt16 kIntervalThreshold = 1; | ||||||
|  | 
 | ||||||
|  | FileChunk::FileChunk(size_t size) : | ||||||
|  | 	Chunk(size) | ||||||
|  | { | ||||||
|  | 		m_dataSize = size - FILE_CHUNK_META_SIZE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileChunk* | ||||||
|  | FileChunk::start(const String& size) | ||||||
|  | { | ||||||
|  | 	size_t sizeLength = size.size(); | ||||||
|  | 	FileChunk* start = new FileChunk(sizeLength + FILE_CHUNK_META_SIZE); | ||||||
|  | 	char* chunk = start->m_chunk; | ||||||
|  | 	chunk[0] = kDataStart; | ||||||
|  | 	memcpy(&chunk[1], size.c_str(), sizeLength); | ||||||
|  | 	chunk[sizeLength + 1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return start; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileChunk* | ||||||
|  | FileChunk::data(UInt8* data, size_t dataSize) | ||||||
|  | { | ||||||
|  | 	FileChunk* chunk = new FileChunk(dataSize + FILE_CHUNK_META_SIZE); | ||||||
|  | 	char* chunkData = chunk->m_chunk; | ||||||
|  | 	chunkData[0] = kDataChunk; | ||||||
|  | 	memcpy(&chunkData[1], data, dataSize); | ||||||
|  | 	chunkData[dataSize + 1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return chunk; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileChunk* | ||||||
|  | FileChunk::end() | ||||||
|  | { | ||||||
|  | 	FileChunk* end = new FileChunk(FILE_CHUNK_META_SIZE); | ||||||
|  | 	char* chunk = end->m_chunk; | ||||||
|  | 	chunk[0] = kDataEnd; | ||||||
|  | 	chunk[1] = '\0'; | ||||||
|  | 
 | ||||||
|  | 	return end; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int | ||||||
|  | FileChunk::assemble(synergy::IStream* stream, String& dataReceived, size_t& expectedSize) | ||||||
|  | { | ||||||
|  | 	// parse
 | ||||||
|  | 	UInt8 mark = 0; | ||||||
|  | 	String content; | ||||||
|  | 	static size_t receivedDataSize; | ||||||
|  | 	static double elapsedTime; | ||||||
|  | 	static Stopwatch stopwatch; | ||||||
|  | 
 | ||||||
|  | 	if (!ProtocolUtil::readf(stream, kMsgDFileTransfer + 4, &mark, &content)) { | ||||||
|  | 		return kError; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch (mark) { | ||||||
|  | 	case kDataStart: | ||||||
|  | 		dataReceived.clear(); | ||||||
|  | 		expectedSize = synergy::string::stringToSizeType(content); | ||||||
|  | 		receivedDataSize = 0; | ||||||
|  | 		elapsedTime = 0; | ||||||
|  | 		stopwatch.reset(); | ||||||
|  | 
 | ||||||
|  | 		if (CLOG->getFilter() >= kDEBUG2) { | ||||||
|  | 			LOG((CLOG_DEBUG2 "recv file data from client: file size=%s", content.c_str())); | ||||||
|  | 			stopwatch.start(); | ||||||
|  | 		} | ||||||
|  | 		return kNotFinish; | ||||||
|  | 
 | ||||||
|  | 	case kDataChunk: | ||||||
|  | 		dataReceived.append(content); | ||||||
|  | 		if (CLOG->getFilter() >= kDEBUG2) { | ||||||
|  | 				LOG((CLOG_DEBUG2 "recv file data from client: chunck size=%i", content.size())); | ||||||
|  | 				double interval = stopwatch.getTime(); | ||||||
|  | 				receivedDataSize += content.size(); | ||||||
|  | 				LOG((CLOG_DEBUG2 "recv file data from client: interval=%f s", interval)); | ||||||
|  | 				if (interval >= kIntervalThreshold) { | ||||||
|  | 					double averageSpeed = receivedDataSize / interval / 1000; | ||||||
|  | 					LOG((CLOG_DEBUG2 "recv file data from client: average speed=%f kb/s", averageSpeed)); | ||||||
|  | 
 | ||||||
|  | 					receivedDataSize = 0; | ||||||
|  | 					elapsedTime += interval; | ||||||
|  | 					stopwatch.reset(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		return kNotFinish; | ||||||
|  | 
 | ||||||
|  | 	case kDataEnd: | ||||||
|  | 		//m_events->addEvent(Event(m_events->forIScreen().fileRecieveCompleted(), server));
 | ||||||
|  | 		if (CLOG->getFilter() >= kDEBUG2) { | ||||||
|  | 			LOG((CLOG_DEBUG2 "file data transfer finished")); | ||||||
|  | 			elapsedTime += stopwatch.getTime(); | ||||||
|  | 			double averageSpeed = expectedSize / elapsedTime / 1000; | ||||||
|  | 			LOG((CLOG_DEBUG2 "file data transfer finished: total time consumed=%f s", elapsedTime)); | ||||||
|  | 			LOG((CLOG_DEBUG2 "file data transfer finished: total data received=%i kb", expectedSize / 1000)); | ||||||
|  | 			LOG((CLOG_DEBUG2 "file data transfer finished: total average speed=%f kb/s", averageSpeed)); | ||||||
|  | 		} | ||||||
|  | 		return kFinish; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return kError; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | FileChunk::send(synergy::IStream* stream, UInt8 mark, char* data, size_t dataSize) | ||||||
|  | { | ||||||
|  | 	String chunk(data, dataSize); | ||||||
|  | 
 | ||||||
|  | 	switch (mark) { | ||||||
|  | 	case kDataStart: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending file chunk start: size=%s", data)); | ||||||
|  | 		break; | ||||||
|  | 
 | ||||||
|  | 	case kDataChunk: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending file chunk: size=%i", chunk.size())); | ||||||
|  | 		break; | ||||||
|  | 
 | ||||||
|  | 	case kDataEnd: | ||||||
|  | 		LOG((CLOG_DEBUG2 "sending file finished")); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ProtocolUtil::writef(stream, kMsgDFileTransfer, mark, &chunk); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,46 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "synergy/Chunk.h" | ||||||
|  | #include "base/String.h" | ||||||
|  | #include "common/basic_types.h" | ||||||
|  | 
 | ||||||
|  | #define FILE_CHUNK_META_SIZE 2 | ||||||
|  | 
 | ||||||
|  | namespace synergy { | ||||||
|  | class IStream; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class FileChunk : public Chunk { | ||||||
|  | public: | ||||||
|  | 	FileChunk(size_t size); | ||||||
|  | 
 | ||||||
|  | 	static FileChunk*	start(const String& size); | ||||||
|  | 	static FileChunk*	data(UInt8* data, size_t dataSize); | ||||||
|  | 	static FileChunk*	end(); | ||||||
|  | 	static int			assemble( | ||||||
|  | 							synergy::IStream* stream, | ||||||
|  | 							String& dataCached, | ||||||
|  | 							size_t& expectedSize); | ||||||
|  | 	static void			send( | ||||||
|  | 							synergy::IStream* stream, | ||||||
|  | 							UInt8 mark, | ||||||
|  | 							char* data, | ||||||
|  | 							size_t dataSize); | ||||||
|  | }; | ||||||
|  | @ -1,112 +0,0 @@ | ||||||
| /*
 |  | ||||||
|  * synergy -- mouse and keyboard sharing utility |  | ||||||
|  * Copyright (C) 2013 Synergy Si Ltd. |  | ||||||
|  *  |  | ||||||
|  * This package is free software; you can redistribute it and/or |  | ||||||
|  * modify it under the terms of the GNU General Public License |  | ||||||
|  * found in the file LICENSE that should have accompanied this file. |  | ||||||
|  *  |  | ||||||
|  * This package is distributed in the hope that it will be useful, |  | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|  * GNU General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| #include "synergy/FileChunker.h" |  | ||||||
| 
 |  | ||||||
| #include "synergy/protocol_types.h" |  | ||||||
| #include "base/EventTypes.h" |  | ||||||
| #include "base/Event.h" |  | ||||||
| #include "base/IEventQueue.h" |  | ||||||
| #include "base/EventTypes.h" |  | ||||||
| #include "base/Log.h" |  | ||||||
| #include "base/Stopwatch.h" |  | ||||||
| #include "common/stdexcept.h" |  | ||||||
| 
 |  | ||||||
| #include <fstream> |  | ||||||
| #include <sstream> |  | ||||||
| 
 |  | ||||||
| #define PAUSE_TIME_HACK 0.1 |  | ||||||
| 
 |  | ||||||
| using namespace std; |  | ||||||
| 
 |  | ||||||
| const size_t FileChunker::m_chunkSize = 512 * 1024; // 512kb
 |  | ||||||
| 
 |  | ||||||
| void |  | ||||||
| FileChunker::sendFileChunks(char* filename, IEventQueue* events, void* eventTarget) |  | ||||||
| { |  | ||||||
| 	std::fstream file(reinterpret_cast<char*>(filename), std::ios::in | std::ios::binary); |  | ||||||
| 
 |  | ||||||
| 	if (!file.is_open()) { |  | ||||||
| 		throw runtime_error("failed to open file"); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// check file size
 |  | ||||||
| 	file.seekg (0, std::ios::end); |  | ||||||
| 	size_t size = (size_t)file.tellg(); |  | ||||||
| 
 |  | ||||||
| 	// send first message (file size)
 |  | ||||||
| 	String fileSize = intToString(size); |  | ||||||
| 	size_t sizeLength = fileSize.size(); |  | ||||||
| 	FileChunk* sizeMessage = new FileChunk(sizeLength + 2); |  | ||||||
| 	char* chunkData = sizeMessage->m_chunk; |  | ||||||
| 
 |  | ||||||
| 	chunkData[0] = kFileStart; |  | ||||||
| 	memcpy(&chunkData[1], fileSize.c_str(), sizeLength); |  | ||||||
| 	chunkData[sizeLength + 1] = '\0'; |  | ||||||
| 	events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, sizeMessage)); |  | ||||||
| 
 |  | ||||||
| 	// send chunk messages with a fixed chunk size
 |  | ||||||
| 	size_t sentLength = 0; |  | ||||||
| 	size_t chunkSize = m_chunkSize; |  | ||||||
| 	Stopwatch stopwatch; |  | ||||||
| 	stopwatch.start(); |  | ||||||
| 	file.seekg (0, std::ios::beg); |  | ||||||
| 	while (true) { |  | ||||||
| 		if (stopwatch.getTime() > PAUSE_TIME_HACK) { |  | ||||||
| 			// make sure we don't read too much from the mock data.
 |  | ||||||
| 			if (sentLength + chunkSize > size) { |  | ||||||
| 				chunkSize = size - sentLength; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// for fileChunk->m_chunk, the first byte is the chunk mark, last is \0
 |  | ||||||
| 			FileChunk* fileChunk = new FileChunk(chunkSize + 2); |  | ||||||
| 			char* chunkData = fileChunk->m_chunk; |  | ||||||
| 
 |  | ||||||
| 			chunkData[0] = kFileChunk; |  | ||||||
| 			file.read(&chunkData[1], chunkSize); |  | ||||||
| 			chunkData[chunkSize + 1] = '\0'; |  | ||||||
| 			events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, fileChunk)); |  | ||||||
| 
 |  | ||||||
| 			sentLength += chunkSize; |  | ||||||
| 			file.seekg (sentLength, std::ios::beg); |  | ||||||
| 
 |  | ||||||
| 			if (sentLength == size) { |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			stopwatch.reset(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// send last message
 |  | ||||||
| 	FileChunk* transferFinished = new FileChunk(2); |  | ||||||
| 	chunkData = transferFinished->m_chunk; |  | ||||||
| 
 |  | ||||||
| 	chunkData[0] = kFileEnd; |  | ||||||
| 	chunkData[1] = '\0'; |  | ||||||
| 	events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, transferFinished)); |  | ||||||
| 
 |  | ||||||
| 	file.close(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| String |  | ||||||
| FileChunker::intToString(size_t i) |  | ||||||
| { |  | ||||||
| 	stringstream ss; |  | ||||||
| 	ss << i; |  | ||||||
| 	return ss.str(); |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,204 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2013 Synergy Si Ltd. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
|  | 
 | ||||||
|  | #include "synergy/FileChunk.h" | ||||||
|  | #include "synergy/ClipboardChunk.h" | ||||||
|  | #include "synergy/protocol_types.h" | ||||||
|  | #include "base/EventTypes.h" | ||||||
|  | #include "base/Event.h" | ||||||
|  | #include "base/IEventQueue.h" | ||||||
|  | #include "base/EventTypes.h" | ||||||
|  | #include "base/Log.h" | ||||||
|  | #include "base/Stopwatch.h" | ||||||
|  | #include "base/String.h" | ||||||
|  | #include "common/stdexcept.h" | ||||||
|  | 
 | ||||||
|  | #include <fstream> | ||||||
|  | 
 | ||||||
|  | #define PAUSE_TIME_HACK 0.1 | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | 
 | ||||||
|  | #define SOCKET_CHUNK_SIZE 512 * 1024; // 512kb
 | ||||||
|  | #define SECURE_SOCKET_CHUNK_SIZE 2 * 1024; // 2kb
 | ||||||
|  | 
 | ||||||
|  | size_t StreamChunker::s_chunkSize = SOCKET_CHUNK_SIZE; | ||||||
|  | bool StreamChunker::s_isChunkingClipboard = false; | ||||||
|  | bool StreamChunker::s_interruptClipboard = false; | ||||||
|  | bool StreamChunker::s_isChunkingFile = false; | ||||||
|  | bool StreamChunker::s_interruptFile = false; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | StreamChunker::sendFile( | ||||||
|  | 				char* filename, | ||||||
|  | 				IEventQueue* events, | ||||||
|  | 				void* eventTarget) | ||||||
|  | { | ||||||
|  | 	s_isChunkingFile = true; | ||||||
|  | 	 | ||||||
|  | 	std::fstream file(reinterpret_cast<char*>(filename), std::ios::in | std::ios::binary); | ||||||
|  | 
 | ||||||
|  | 	if (!file.is_open()) { | ||||||
|  | 		throw runtime_error("failed to open file"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check file size
 | ||||||
|  | 	file.seekg (0, std::ios::end); | ||||||
|  | 	size_t size = (size_t)file.tellg(); | ||||||
|  | 
 | ||||||
|  | 	// send first message (file size)
 | ||||||
|  | 	String fileSize = synergy::string::sizeTypeToString(size); | ||||||
|  | 	FileChunk* sizeMessage = FileChunk::start(fileSize); | ||||||
|  | 
 | ||||||
|  | 	events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, sizeMessage)); | ||||||
|  | 
 | ||||||
|  | 	// send chunk messages with a fixed chunk size
 | ||||||
|  | 	size_t sentLength = 0; | ||||||
|  | 	size_t chunkSize = s_chunkSize; | ||||||
|  | 	Stopwatch stopwatch; | ||||||
|  | 	stopwatch.start(); | ||||||
|  | 	file.seekg (0, std::ios::beg); | ||||||
|  | 	while (true) { | ||||||
|  | 		if (s_interruptFile) { | ||||||
|  | 			s_interruptFile = false; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if (stopwatch.getTime() > PAUSE_TIME_HACK) { | ||||||
|  | 			// make sure we don't read too much from the mock data.
 | ||||||
|  | 			if (sentLength + chunkSize > size) { | ||||||
|  | 				chunkSize = size - sentLength; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			char* chunkData = new char[chunkSize]; | ||||||
|  | 			file.read(chunkData, chunkSize); | ||||||
|  | 			UInt8* data = reinterpret_cast<UInt8*>(chunkData); | ||||||
|  | 			FileChunk* fileChunk = FileChunk::data(data, chunkSize); | ||||||
|  | 			delete[] chunkData; | ||||||
|  | 
 | ||||||
|  | 			events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, fileChunk)); | ||||||
|  | 
 | ||||||
|  | 			sentLength += chunkSize; | ||||||
|  | 			file.seekg (sentLength, std::ios::beg); | ||||||
|  | 
 | ||||||
|  | 			if (sentLength == size) { | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			stopwatch.reset(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// send last message
 | ||||||
|  | 	FileChunk* end = FileChunk::end(); | ||||||
|  | 
 | ||||||
|  | 	events->addEvent(Event(events->forIScreen().fileChunkSending(), eventTarget, end)); | ||||||
|  | 
 | ||||||
|  | 	file.close(); | ||||||
|  | 	 | ||||||
|  | 	s_isChunkingFile = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | StreamChunker::sendClipboard( | ||||||
|  | 				String& data, | ||||||
|  | 				size_t size, | ||||||
|  | 				ClipboardID id, | ||||||
|  | 				UInt32 sequence, | ||||||
|  | 				IEventQueue* events, | ||||||
|  | 				void* eventTarget) | ||||||
|  | { | ||||||
|  | 	s_isChunkingClipboard = true; | ||||||
|  | 	 | ||||||
|  | 	// send first message (data size)
 | ||||||
|  | 	String dataSize = synergy::string::sizeTypeToString(size); | ||||||
|  | 	ClipboardChunk* sizeMessage = ClipboardChunk::start(id, sequence, dataSize); | ||||||
|  | 	 | ||||||
|  | 	events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, sizeMessage)); | ||||||
|  | 
 | ||||||
|  | 	// send clipboard chunk with a fixed size
 | ||||||
|  | 	size_t sentLength = 0; | ||||||
|  | 	size_t chunkSize = s_chunkSize; | ||||||
|  | 	Stopwatch stopwatch; | ||||||
|  | 	stopwatch.start(); | ||||||
|  | 	 | ||||||
|  | 	while (true) { | ||||||
|  | 		if (s_interruptClipboard) { | ||||||
|  | 			s_interruptClipboard = false; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if (stopwatch.getTime() > 0.1f) { | ||||||
|  | 			// make sure we don't read too much from the mock data.
 | ||||||
|  | 			if (sentLength + chunkSize > size) { | ||||||
|  | 				chunkSize = size - sentLength; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			String chunk(data.substr(sentLength, chunkSize).c_str(), chunkSize); | ||||||
|  | 			ClipboardChunk* dataChunk = ClipboardChunk::data(id, sequence, chunk); | ||||||
|  | 			 | ||||||
|  | 			events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, dataChunk)); | ||||||
|  | 
 | ||||||
|  | 			sentLength += chunkSize; | ||||||
|  | 
 | ||||||
|  | 			if (sentLength == size) { | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			stopwatch.reset(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// send last message
 | ||||||
|  | 	ClipboardChunk* end = ClipboardChunk::end(id, sequence); | ||||||
|  | 
 | ||||||
|  | 	events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, end)); | ||||||
|  | 	 | ||||||
|  | 	s_isChunkingClipboard = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | StreamChunker::updateChunkSize(bool useSecureSocket) | ||||||
|  | { | ||||||
|  | 	if (useSecureSocket) { | ||||||
|  | 		s_chunkSize = SECURE_SOCKET_CHUNK_SIZE; | ||||||
|  | 	} | ||||||
|  | 	else { | ||||||
|  | 		s_chunkSize = SOCKET_CHUNK_SIZE; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | StreamChunker::interruptFile() | ||||||
|  | { | ||||||
|  | 	if (s_isChunkingFile) { | ||||||
|  | 		s_interruptFile = true; | ||||||
|  | 		LOG((CLOG_INFO "previous dragged file has become invalid")); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | StreamChunker::interruptClipboard() | ||||||
|  | { | ||||||
|  | 	if (s_isChunkingClipboard) { | ||||||
|  | 		s_interruptClipboard = true; | ||||||
|  | 		LOG((CLOG_INFO "previous clipboard data has become invalid")); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2013 Synergy Si Ltd. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "synergy/clipboard_types.h" | ||||||
|  | #include "base/String.h" | ||||||
|  | 
 | ||||||
|  | class IEventQueue; | ||||||
|  | 
 | ||||||
|  | class StreamChunker { | ||||||
|  | public: | ||||||
|  | 	static void			sendFile( | ||||||
|  | 							char* filename, | ||||||
|  | 							IEventQueue* events, | ||||||
|  | 							void* eventTarget); | ||||||
|  | 	static void			sendClipboard( | ||||||
|  | 							String& data, | ||||||
|  | 							size_t size, | ||||||
|  | 							ClipboardID id, | ||||||
|  | 							UInt32 sequence, | ||||||
|  | 							IEventQueue* events, | ||||||
|  | 							void* eventTarget); | ||||||
|  | 	static void			updateChunkSize(bool useSecureSocket); | ||||||
|  | 	static void			interruptFile(); | ||||||
|  | 	static void			interruptClipboard(); | ||||||
|  | 	 | ||||||
|  | private: | ||||||
|  | 	static size_t		s_chunkSize; | ||||||
|  | 	static bool			s_isChunkingClipboard; | ||||||
|  | 	static bool			s_interruptClipboard; | ||||||
|  | 	static bool			s_isChunkingFile; | ||||||
|  | 	static bool			s_interruptFile; | ||||||
|  | }; | ||||||
|  | @ -41,7 +41,7 @@ const char*				kMsgDMouseMove		= "DMMV%2i%2i"; | ||||||
| const char*				kMsgDMouseRelMove	= "DMRM%2i%2i"; | const char*				kMsgDMouseRelMove	= "DMRM%2i%2i"; | ||||||
| const char*				kMsgDMouseWheel		= "DMWM%2i%2i"; | const char*				kMsgDMouseWheel		= "DMWM%2i%2i"; | ||||||
| const char*				kMsgDMouseWheel1_0	= "DMWM%2i"; | const char*				kMsgDMouseWheel1_0	= "DMWM%2i"; | ||||||
| const char*				kMsgDClipboard		= "DCLP%1i%4i%s"; | const char*				kMsgDClipboard		= "DCLP%1i%4i%1i%s"; | ||||||
| const char*				kMsgDInfo			= "DINF%2i%2i%2i%2i%2i%2i%2i"; | const char*				kMsgDInfo			= "DINF%2i%2i%2i%2i%2i%2i%2i"; | ||||||
| const char*				kMsgDSetOptions		= "DSOP%4I"; | const char*				kMsgDSetOptions		= "DSOP%4I"; | ||||||
| const char*				kMsgDFileTransfer	= "DFTR%1i%s"; | const char*				kMsgDFileTransfer	= "DFTR%1i%s"; | ||||||
|  |  | ||||||
|  | @ -28,9 +28,10 @@ | ||||||
| //       adds horizontal mouse scrolling
 | //       adds horizontal mouse scrolling
 | ||||||
| // 1.4:  adds crypto support
 | // 1.4:  adds crypto support
 | ||||||
| // 1.5:  adds file transfer and removes home brew crypto
 | // 1.5:  adds file transfer and removes home brew crypto
 | ||||||
|  | // 1.6:  adds clipboard streaming
 | ||||||
| // NOTE: with new version, synergy minor version should increment
 | // NOTE: with new version, synergy minor version should increment
 | ||||||
| static const SInt16		kProtocolMajorVersion = 1; | static const SInt16		kProtocolMajorVersion = 1; | ||||||
| static const SInt16		kProtocolMinorVersion = 5; | static const SInt16		kProtocolMinorVersion = 6; | ||||||
| 
 | 
 | ||||||
| // default contact port number
 | // default contact port number
 | ||||||
| static const UInt16		kDefaultPort = 24800; | static const UInt16		kDefaultPort = 24800; | ||||||
|  | @ -69,13 +70,19 @@ enum EDirectionMask { | ||||||
| 	kBottomMask = 1 << kBottom | 	kBottomMask = 1 << kBottom | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // file transfer constants
 | // Data transfer constants
 | ||||||
| enum EFileTransfer { | enum EDataTransfer { | ||||||
| 	kFileStart = 1, | 	kDataStart = 1, | ||||||
| 	kFileChunk = 2, | 	kDataChunk = 2, | ||||||
| 	kFileEnd = 3 | 	kDataEnd = 3 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // Data received constants
 | ||||||
|  | enum EDataReceived { | ||||||
|  | 	kNotFinish, | ||||||
|  | 	kFinish, | ||||||
|  | 	kError | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| // message codes (trailing NUL is not part of code).  in comments, $n
 | // message codes (trailing NUL is not part of code).  in comments, $n
 | ||||||
|  | @ -225,7 +232,7 @@ extern const char*		kMsgDMouseWheel; | ||||||
| extern const char*		kMsgDMouseWheel1_0; | extern const char*		kMsgDMouseWheel1_0; | ||||||
| 
 | 
 | ||||||
| // clipboard data:  primary <-> secondary
 | // clipboard data:  primary <-> secondary
 | ||||||
| // $2 = sequence number, $3 = clipboard data.  the sequence number
 | // $2 = sequence number, $3 = mark $4 = clipboard data.  the sequence number
 | ||||||
| // is 0 when sent by the primary.  secondary screens should use the
 | // is 0 when sent by the primary.  secondary screens should use the
 | ||||||
| // sequence number from the most recent kMsgCEnter.  $1 = clipboard
 | // sequence number from the most recent kMsgCEnter.  $1 = clipboard
 | ||||||
| // identifier.
 | // identifier.
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,8 @@ | ||||||
| #include "server/Server.h" | #include "server/Server.h" | ||||||
| #include "server/ClientListener.h" | #include "server/ClientListener.h" | ||||||
| #include "client/Client.h" | #include "client/Client.h" | ||||||
| #include "synergy/FileChunker.h" | #include "synergy/FileChunk.h" | ||||||
|  | #include "synergy/StreamChunker.h" | ||||||
| #include "net/SocketMultiplexer.h" | #include "net/SocketMultiplexer.h" | ||||||
| #include "net/NetworkAddress.h" | #include "net/NetworkAddress.h" | ||||||
| #include "net/TCPSocketFactory.h" | #include "net/TCPSocketFactory.h" | ||||||
|  | @ -60,7 +61,6 @@ const size_t kMockFileSize = 1024 * 1024 * 10; // 10MB | ||||||
| 
 | 
 | ||||||
| void getScreenShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h); | void getScreenShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h); | ||||||
| void getCursorPos(SInt32& x, SInt32& y); | void getCursorPos(SInt32& x, SInt32& y); | ||||||
| String intToString(size_t i); |  | ||||||
| UInt8* newMockData(size_t size); | UInt8* newMockData(size_t size); | ||||||
| void createFile(fstream& file, const char* filename, size_t size); | void createFile(fstream& file, const char* filename, size_t size); | ||||||
| 
 | 
 | ||||||
|  | @ -415,38 +415,28 @@ void | ||||||
| NetworkTests::sendMockData(void* eventTarget) | NetworkTests::sendMockData(void* eventTarget) | ||||||
| { | { | ||||||
| 	// send first message (file size)
 | 	// send first message (file size)
 | ||||||
| 	String size = intToString(kMockDataSize); | 	String size = synergy::string::sizeTypeToString(kMockDataSize); | ||||||
| 	size_t sizeLength = size.size(); | 	FileChunk* sizeMessage = FileChunk::start(size); | ||||||
| 	FileChunker::FileChunk* sizeMessage = new FileChunker::FileChunk(sizeLength + 2); |  | ||||||
| 	char* chunkData = sizeMessage->m_chunk; |  | ||||||
| 	 | 	 | ||||||
| 	chunkData[0] = kFileStart; |  | ||||||
| 	memcpy(&chunkData[1], size.c_str(), sizeLength); |  | ||||||
| 	chunkData[sizeLength + 1] = '\0'; |  | ||||||
| 	m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, sizeMessage)); | 	m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, sizeMessage)); | ||||||
| 
 | 
 | ||||||
| 	// send chunk messages with incrementing chunk size
 | 	// send chunk messages with incrementing chunk size
 | ||||||
| 	size_t lastSize = 0; | 	size_t lastSize = 0; | ||||||
| 	size_t sentLength = 0; | 	size_t sentLength = 0; | ||||||
| 	while (true) { | 	while (true) { | ||||||
| 		size_t chunkSize = lastSize + kMockDataChunkIncrement; | 		size_t dataSize = lastSize + kMockDataChunkIncrement; | ||||||
| 
 | 
 | ||||||
| 		// make sure we don't read too much from the mock data.
 | 		// make sure we don't read too much from the mock data.
 | ||||||
| 		if (sentLength + chunkSize > kMockDataSize) { | 		if (sentLength + dataSize > kMockDataSize) { | ||||||
| 			chunkSize = kMockDataSize - sentLength; | 			dataSize = kMockDataSize - sentLength; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// first byte is the chunk mark, last is \0
 | 		// first byte is the chunk mark, last is \0
 | ||||||
| 		FileChunker::FileChunk* fileChunk = new FileChunker::FileChunk(chunkSize + 2); | 		FileChunk* chunk = FileChunk::data(m_mockData, dataSize); | ||||||
| 		char* chunkData = fileChunk->m_chunk; | 		m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, chunk)); | ||||||
| 
 | 
 | ||||||
| 		chunkData[0] = kFileChunk; | 		sentLength += dataSize; | ||||||
| 		memcpy(&chunkData[1], &m_mockData[sentLength], chunkSize); | 		lastSize = dataSize; | ||||||
| 		chunkData[chunkSize + 1] = '\0'; |  | ||||||
| 		m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, fileChunk)); |  | ||||||
| 
 |  | ||||||
| 		sentLength += chunkSize; |  | ||||||
| 		lastSize = chunkSize; |  | ||||||
| 
 | 
 | ||||||
| 		if (sentLength == kMockDataSize) { | 		if (sentLength == kMockDataSize) { | ||||||
| 			break; | 			break; | ||||||
|  | @ -455,11 +445,7 @@ NetworkTests::sendMockData(void* eventTarget) | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	// send last message
 | 	// send last message
 | ||||||
| 	FileChunker::FileChunk* transferFinished = new FileChunker::FileChunk(2); | 	FileChunk* transferFinished = FileChunk::end(); | ||||||
| 	chunkData = transferFinished->m_chunk; |  | ||||||
| 
 |  | ||||||
| 	chunkData[0] = kFileEnd; |  | ||||||
| 	chunkData[1] = '\0'; |  | ||||||
| 	m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, transferFinished)); | 	m_events.addEvent(Event(m_events.forIScreen().fileChunkSending(), eventTarget, transferFinished)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -527,12 +513,4 @@ getCursorPos(SInt32& x, SInt32& y) | ||||||
| 	y = 0; | 	y = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| String |  | ||||||
| intToString(size_t i) |  | ||||||
| { |  | ||||||
| 	stringstream ss; |  | ||||||
| 	ss << i; |  | ||||||
| 	return ss.str(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #endif // WINAPI_CARBON
 | #endif // WINAPI_CARBON
 | ||||||
|  |  | ||||||
|  | @ -61,5 +61,6 @@ public: | ||||||
| 	MOCK_METHOD0(forIKeyState, IKeyStateEvents&()); | 	MOCK_METHOD0(forIKeyState, IKeyStateEvents&()); | ||||||
| 	MOCK_METHOD0(forIPrimaryScreen, IPrimaryScreenEvents&()); | 	MOCK_METHOD0(forIPrimaryScreen, IPrimaryScreenEvents&()); | ||||||
| 	MOCK_METHOD0(forIScreen, IScreenEvents&()); | 	MOCK_METHOD0(forIScreen, IScreenEvents&()); | ||||||
|  | 	MOCK_METHOD0(forClipboard, ClipboardEvents&()); | ||||||
| 	MOCK_CONST_METHOD0(waitForReady, void()); | 	MOCK_CONST_METHOD0(waitForReady, void()); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -82,3 +82,21 @@ TEST(StringTests, removeChar) | ||||||
| 
 | 
 | ||||||
| 	EXPECT_EQ("fbar", subject); | 	EXPECT_EQ("fbar", subject); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | TEST(StringTests, intToString) | ||||||
|  | { | ||||||
|  | 	size_t value = 123; | ||||||
|  | 
 | ||||||
|  | 	String number = string::sizeTypeToString(value); | ||||||
|  | 
 | ||||||
|  | 	EXPECT_EQ("123", number); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TEST(StringTests, stringToUint) | ||||||
|  | { | ||||||
|  | 	String number = "123"; | ||||||
|  | 
 | ||||||
|  | 	size_t value = string::stringToSizeType(number); | ||||||
|  | 
 | ||||||
|  | 	EXPECT_EQ(123, value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -120,7 +120,9 @@ TEST(IpcLogOutputterTests, write_overBufferRateLimit_lastLineTruncated) | ||||||
| 	 | 	 | ||||||
| 	// after waiting the time limit send another to make sure
 | 	// after waiting the time limit send another to make sure
 | ||||||
| 	// we can log after the time limit passes.
 | 	// we can log after the time limit passes.
 | ||||||
| 	ARCH->sleep(0.01); // 10ms
 | 	// HACK: sleep causes the unit test to fail intermittently,
 | ||||||
|  | 	// so lets try 100ms (there must be a better way to solve this)
 | ||||||
|  | 	ARCH->sleep(0.1); // 100ms
 | ||||||
| 	outputter.write(kNOTE, "mock 3"); | 	outputter.write(kNOTE, "mock 3"); | ||||||
| 	outputter.write(kNOTE, "mock 4"); | 	outputter.write(kNOTE, "mock 4"); | ||||||
| 	outputter.sendBuffer(); | 	outputter.sendBuffer(); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | /*
 | ||||||
|  |  * synergy -- mouse and keyboard sharing utility | ||||||
|  |  * Copyright (C) 2015 Synergy Si Inc. | ||||||
|  |  *  | ||||||
|  |  * This package is free software; you can redistribute it and/or | ||||||
|  |  * modify it under the terms of the GNU General Public License | ||||||
|  |  * found in the file LICENSE that should have accompanied this file. | ||||||
|  |  *  | ||||||
|  |  * This package is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "synergy/ClipboardChunk.h" | ||||||
|  | #include "synergy/protocol_types.h" | ||||||
|  | 
 | ||||||
|  | #include "test/global/gtest.h" | ||||||
|  | 
 | ||||||
|  | TEST(ClipboardChunkTests, start_formatStartChunk) | ||||||
|  | { | ||||||
|  | 	ClipboardID id = 0; | ||||||
|  | 	UInt32 sequence = 0; | ||||||
|  | 	String mockDataSize("10"); | ||||||
|  | 	ClipboardChunk* chunk = ClipboardChunk::start(id, sequence, mockDataSize); | ||||||
|  | 
 | ||||||
|  | 	EXPECT_EQ(id, chunk->m_chunk[0]); | ||||||
|  | 	EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]); | ||||||
|  | 	EXPECT_EQ(kDataStart, chunk->m_chunk[5]); | ||||||
|  | 	EXPECT_EQ('1', chunk->m_chunk[6]); | ||||||
|  | 	EXPECT_EQ('0', chunk->m_chunk[7]); | ||||||
|  | 	EXPECT_EQ('\0', chunk->m_chunk[8]); | ||||||
|  | 
 | ||||||
|  | 	delete chunk; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TEST(ClipboardChunkTests, data_formatDataChunk) | ||||||
|  | { | ||||||
|  | 	ClipboardID id = 0; | ||||||
|  | 	UInt32 sequence = 1; | ||||||
|  | 	String mockData("mock data"); | ||||||
|  | 	ClipboardChunk* chunk = ClipboardChunk::data(id, sequence, mockData); | ||||||
|  | 
 | ||||||
|  | 	EXPECT_EQ(id, chunk->m_chunk[0]); | ||||||
|  | 	EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]); | ||||||
|  | 	EXPECT_EQ(kDataChunk, chunk->m_chunk[5]); | ||||||
|  | 	EXPECT_EQ('m', chunk->m_chunk[6]); | ||||||
|  | 	EXPECT_EQ('o', chunk->m_chunk[7]); | ||||||
|  | 	EXPECT_EQ('c', chunk->m_chunk[8]); | ||||||
|  | 	EXPECT_EQ('k', chunk->m_chunk[9]); | ||||||
|  | 	EXPECT_EQ(' ', chunk->m_chunk[10]); | ||||||
|  | 	EXPECT_EQ('d', chunk->m_chunk[11]); | ||||||
|  | 	EXPECT_EQ('a', chunk->m_chunk[12]); | ||||||
|  | 	EXPECT_EQ('t', chunk->m_chunk[13]); | ||||||
|  | 	EXPECT_EQ('a', chunk->m_chunk[14]); | ||||||
|  | 	EXPECT_EQ('\0', chunk->m_chunk[15]); | ||||||
|  | 
 | ||||||
|  | 	delete chunk; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TEST(ClipboardChunkTests, end_formatDataChunk) | ||||||
|  | { | ||||||
|  | 	ClipboardID id = 1; | ||||||
|  | 	UInt32 sequence = 1; | ||||||
|  | 	ClipboardChunk* chunk = ClipboardChunk::end(id, sequence); | ||||||
|  | 
 | ||||||
|  | 	EXPECT_EQ(id, chunk->m_chunk[0]); | ||||||
|  | 	EXPECT_EQ(sequence, (UInt32)chunk->m_chunk[1]); | ||||||
|  | 	EXPECT_EQ(kDataEnd, chunk->m_chunk[5]); | ||||||
|  | 	EXPECT_EQ('\0', chunk->m_chunk[6]); | ||||||
|  | 
 | ||||||
|  | 	delete chunk; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue