synergy-plus へファイルのクリップボード共有機能実装

前回の続きで、synergy-plus にタイトルの通りファイルに対するクリップボード有機能を実装する。実現方法はファイルのデータ本体をやりとりするという方法もあるが、そうすると巨大なファイルの際に問題が発生するのが目に見えているので、Windows間限定になってしまうが、Windowsの管理共有機能を利用する。

CF_HDROP

エクスプローラからファイルがクリップボードにコピーされるとフォーマットIDとしてはCF_HDROPになり、ファイルの取得方法はファイルのDrag&Drop時と同じようにDragQueryFile関数で取得できる。また、Win2000以降はファイル名にUnicode(UTF16)が使われるのでこれらを考慮して実装する。

変更箇所

元々がかなり整理して作られていたので変更しなければならない箇所はすぐにわかる。クリップボードに送られるフォーマットごとにWindows環境用は、CMSWindowsClipboardXXXConverterという名前がついている。そのため、今回はCMSWindowsClipboardFilePathConverter.cpp/hを作成する。また、作成したConverterを利用するためにいくつかの変更を行った。作り方は他のConverterのソースが参考になる。

IClipboard.h

synergyは複数のOS間で利用できるように作られているので、クリップボードのフォーマットを独自に定義してあり、IClipboard.hにある。ここに、ファイルのパスを表すkFilePathを追加し、これを用いて機能を実現することに。もし、ファイルパスではなくファイルのデータ丸ごとやりとりする場合等はここに別の値を追加してやればできるだろう。どうやらUTF8で文字のやりとりが多いようなのでそれに従ってファイルパスもUTF16ではなくUTF8に変換して送ることにした。

diff --git a/lib/synergy/IClipboard.h b/lib/synergy/IClipboard.h
--- a/lib/synergy/IClipboard.h
+++ b/lib/synergy/IClipboard.h
@@ -56,6 +56,7 @@
 		kText,			//!< Text format, UTF-8, newline is LF
 		kBitmap,		//!< Bitmap format, BMP 24/32bpp, BI_RGB
 		kHTML,			//!< HTML format, HTML fragment, UTF-8, newline is LF
+		kFilePath,		//!< File Path format, UTF-8, separetor is \t
 		kNumFormats		//!< The number of clipboard formats
 	};
CMSWindowsClipborad.cpp

MSWindowsとファイル名にあるものはすべてWindows用の実装になっている。ここのコンストラクタで各種コンバータが登録されているので作成したCMSWindowsClipboardFilePathConverterを登録するように追加する。

diff --git a/lib/platform/CMSWindowsClipboard.cpp b/lib/platform/CMSWindowsClipboard.cpp
--- a/lib/platform/CMSWindowsClipboard.cpp
+++ b/lib/platform/CMSWindowsClipboard.cpp
@@ -17,6 +17,7 @@
 #include "CMSWindowsClipboardUTF16Converter.h"
 #include "CMSWindowsClipboardBitmapConverter.h"
 #include "CMSWindowsClipboardHTMLConverter.h"
+#include "CMSWindowsClipboardFilePathConverter.h"
 #include "CLog.h"
 #include "CArchMiscWindows.h"
 
@@ -39,6 +40,7 @@
 	}
 	m_converters.push_back(new CMSWindowsClipboardBitmapConverter);
 	m_converters.push_back(new CMSWindowsClipboardHTMLConverter);
+	m_converters.push_back(new CMSWindowsClipboardFilePathConverter);
 }
 
 CMSWindowsClipboard::~CMSWindowsClipboard()
CMSWindowsClipboardFilePathConverter.cpp

クリップボードにコピーされたファイルパスは管理共有を使ったUNCパスに変換(複数アイテムの場合はタブ(\t)を区切り文字)し、UTF8にした後で相手先に送信する。受信側は逆順に展開し、クリップボードにデータとしてセットするように実装している。

#include "CMSWindowsClipboardFilePathConverter.h"
#include "CLog.h"
#include "shellapi.h"
#include "shlobj.h"
#include "CUnicode.h"

#define	WCToCString(x)	(CString((const char*)(x), wcslen(x)*sizeof(wchar_t)))

//
// CMSWindowsClipboardFilePathConverter
//

CMSWindowsClipboardFilePathConverter::CMSWindowsClipboardFilePathConverter()
{
	OSVERSIONINFO os_info;
	os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	::GetVersionEx(&os_info);
	if ( os_info.dwPlatformId == VER_PLATFORM_WIN32_NT )
	{	//	Widechar
		useWideChar = TRUE;
		
		//	ARCH->getHostName()
		DWORD size = sizeof(computerNameWC);
		if ( ::GetComputerNameW(computerNameWC, &size) == FALSE )
		{	//	Failed
			computerNameWC[0] = 0;
		}
	}
	else
	{	//	Multibyte
		useWideChar = FALSE;
		DWORD size = sizeof(computerNameMB);
		if ( ::GetComputerNameA(computerNameMB, &size) == FALSE )
		{	//	Failed
			computerNameMB[0] = 0;
		}
	}
	separator = CUnicode::textToUTF8(CString("\t"));
}

CMSWindowsClipboardFilePathConverter::~CMSWindowsClipboardFilePathConverter()
{

}

IClipboard::EFormat
CMSWindowsClipboardFilePathConverter::getFormat() const
{
	return IClipboard::kFilePath;
}

UINT
CMSWindowsClipboardFilePathConverter::getWin32Format() const
{
	return CF_HDROP;
}

HANDLE
CMSWindowsClipboardFilePathConverter::fromIClipboard(const CString& data) const
{	//	data is UTF8
	std::vector<CString> fileList;
	{	//	separate data with separator('\t')
		CString s = data;
		for (int p = 0; (p = s.find(separator)) != s.npos; ) {
#ifdef _DEBUG
			LOG((CLOG_DEBUG "> %s", CUnicode::UTF8ToText(s.substr(0, p)).c_str()));
#endif
			fileList.push_back(s.substr(0, p));
			s = s.substr(p + separator.size());
		}
		if ( s.length() > 1 )
		{
#ifdef _DEBUG
			LOG((CLOG_DEBUG ">* %s", CUnicode::UTF8ToText(s).c_str()));
#endif
			fileList.push_back(s);
		}
	}

	LOG((CLOG_DEBUG "file: recv count > %d / widechar > %d", fileList.size(), useWideChar));
#ifdef _DEBUG
	LOG((CLOG_DEBUG "      %s", CUnicode::UTF8ToText(data).c_str()));
	for ( int i = 0; i < fileList.size(); i++ )
	{
		LOG((CLOG_DEBUG "> %s", CUnicode::UTF8ToText(fileList[i]).c_str()));
	}
#endif

	LPDROPFILES lpDropFile;
    HDROP   hDrop;
    UINT    bufferSize = 0;

	//	calc buffer size & convert encoding
    if ( useWideChar == TRUE )
	{	//	widechar
		for ( UINT i = 0;i < fileList.size(); i++ )
		{	//	Widechar is UTF16 in Windows
			fileList[i] = CUnicode::UTF8ToUTF16(fileList[i]);
			bufferSize += fileList[i].size() + sizeof(wchar_t);	//	length + NULL
		}
    }
    else
    {   // multibyte
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			fileList[i] = CUnicode::UTF8ToText(fileList[i]);
			bufferSize += fileList[i].size() + 1;
		}
    }

    hDrop = (HDROP)::GlobalAlloc(GHND,sizeof(DROPFILES) + bufferSize + 2);
    if (hDrop == NULL)
	{
		return NULL;
	}
    lpDropFile = (LPDROPFILES) ::GlobalLock(hDrop);
    lpDropFile->pFiles = sizeof(DROPFILES);
    lpDropFile->pt.x = 0;
    lpDropFile->pt.y = 0;
    lpDropFile->fNC = FALSE;
    lpDropFile->fWide = useWideChar;
    if ( useWideChar == TRUE )
    {	//	widechar
        wchar_t *buf;
        buf = (wchar_t*)(&lpDropFile[1]);
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			memcpy(buf, fileList[i].c_str(), fileList[i].size());
            buf += (fileList[i].size()/sizeof(wchar_t));
            *buf++ = 0;
        }
		*buf = 0;
    }
    else
    {   //	multibyte
        char *buf;
        buf = (char *)(&lpDropFile[1]);
        for ( UINT i = 0; i < fileList.size(); i++ )
        {
			memcpy(buf, fileList[i].c_str(), fileList[i].size());
            buf += fileList[i].size();
            *buf++ = 0;
        }
		*buf = 0;
    }
    ::GlobalUnlock(hDrop);
    return hDrop;
}

CString
CMSWindowsClipboardFilePathConverter::toIClipboard(HANDLE data) const
{
	CString fileList;
	UINT fileCount = 0;

	//	FileCount
	if ( useWideChar == TRUE )
		fileCount = ::DragQueryFileW((HDROP)data, (UINT)-1, NULL, 0);
	else
		fileCount = ::DragQueryFileA((HDROP)data, (UINT)-1, NULL, 0);

	LOG((CLOG_DEBUG "file: send count > %d / widechar > %d", fileCount, useWideChar));

	if ( fileCount == 0 )
	{	//	nothing
		return CString();
	}

	//	Get file path from clipboard
	if ( useWideChar == TRUE )
	{
		wchar_t szPath[MAX_PATH];
		for ( UINT i = 0; i < fileCount; i++ )
		{
			::DragQueryFileW((HDROP)data, i, szPath, sizeof(szPath));
			CString cnvPath = convertPath(szPath);
			fileList.append(cnvPath);
			fileList.append(separator);	//	separate with '\t'
			LOG((CLOG_DEBUG "      %s", CUnicode::UTF8ToText(cnvPath).c_str()));
		}
	}
	else
	{
		char szPath[MAX_PATH];
		for ( UINT i = 0; i < fileCount; i++ )
		{
			::DragQueryFileA((HDROP)data, i, szPath, sizeof(szPath));
			CString cnvPath = convertPath(szPath);
			fileList.append(cnvPath);
			fileList.append(separator);	//	separate with '\t'
			LOG((CLOG_DEBUG "      %s", szPath));
			LOG((CLOG_DEBUG "      -> %s", cnvPath.c_str()));
		}
	}

	return fileList;
}

// Convert to UNC path using administrative shared folder
CString
CMSWindowsClipboardFilePathConverter::convertPath(char* szPath) const
{
	CString path = CString(szPath);
	CString unc;
	if ( path.substr(0, 2) == "\\\\" )
	{	//	Remote Path
		unc = CString(path);
	}
	else
	{	//	Local Path
		unc = CString("\\\\");
		unc.append(computerNameMB);
		unc.append("\\");
		unc.append(path.replace(1, 1, "$"));	//	Convert 'C:\...' to 'C$\...'
	}
	return CUnicode::textToUTF8(unc);
}
CString
CMSWindowsClipboardFilePathConverter::convertPath(wchar_t* szPath) const
{
	size_t lenPath = wcslen(szPath);

	if ( lenPath < 3 )
		return CString();

	CString unc;
	if ( szPath[0] == L'\\' && szPath[1] == L'\\' )
	{	//	Remote Path
		unc = WCToCString(szPath);
	}
	else
	{	//	Local Path
		unc = WCToCString(L"\\\\");
		unc.append(WCToCString((wchar_t*)computerNameWC));
		unc.append(WCToCString(L"\\"));
		char* drive = new char[sizeof(wchar_t)];
		memcpy(drive, szPath, sizeof(wchar_t));
		unc.append(drive, 1 * sizeof(wchar_t));	//	Convert 'C:\...' to 'C$\...'
		delete drive;
		unc.append(WCToCString(L"$"));
		unc.append(WCToCString(szPath+2));
	}
	return CUnicode::UTF16ToUTF8(unc);	//	Widechar is UTF16 in Windows
}
CMSWindowsClipboardFilePathConverter.h
#ifndef CMSWindowsClipboardFilePathConverter_H
#define CMSWindowsClipboardFilePathConverter_H

#include "CMSWindowsClipboard.h"

//! Convert to/from some text encoding
class CMSWindowsClipboardFilePathConverter :
				public IMSWindowsClipboardConverter {
private:
	CString	separator;
	BOOL	useWideChar;
	char	computerNameMB[MAX_COMPUTERNAME_LENGTH+1];
	wchar_t	computerNameWC[MAX_COMPUTERNAME_LENGTH+1];
	CString	convertPath(char* szPath) const;
	CString	convertPath(wchar_t* szPath) const;
public:
	CMSWindowsClipboardFilePathConverter();
	virtual ~CMSWindowsClipboardFilePathConverter();

	// IMSWindowsClipboardConverter overrides
	virtual IClipboard::EFormat
						getFormat() const;
	virtual UINT		getWin32Format() const;
	virtual HANDLE		fromIClipboard(const CString&) const;
	virtual CString		toIClipboard(HANDLE) const;
};

#endif

完成

これでWindows間においてsynergy-plusを介してファイルについてクリップボード共有が行えるようになった。まだWindows7環境に移っていないが、WinXP間においては問題なく動いている。なお、管理共有経由のファイルコピーになるため、それぞれのマシンのログインユーザが相手側のAdministrator権限を持ったユーザアカウント(User/Pass)に一致している必要がある(はず…)。

バイナリとソース

synergy-plusをホストしているGoogle Codeがhgからsvnへ戻った(?)かなにかで、元となったリビジョンがわからなくなった。なので再整理後にソース一式とバイナリをアップしておこうと思う。