used a hidden drop window to get drag filename

This commit is contained in:
jerry 2014-04-22 10:33:16 +00:00
parent 7b8cdb6b38
commit b85a9b628e
7 changed files with 433 additions and 59 deletions

View File

@ -0,0 +1,178 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2014 Bolton Software 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 COPYING 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 "platform/MSWindowsDropTarget.h"
#include "base/Log.h"
#include "common/common.h"
#include <stdio.h>
#include <Shlobj.h>
void getDropData(IDataObject *pDataObject);
CMSWindowsDropTarget* CMSWindowsDropTarget::s_instance = NULL;
CMSWindowsDropTarget::CMSWindowsDropTarget() :
m_refCount(1),
m_allowDrop(false)
{
s_instance = this;
}
CMSWindowsDropTarget::~CMSWindowsDropTarget()
{
}
CMSWindowsDropTarget&
CMSWindowsDropTarget::instance()
{
assert(s_instance != NULL);
return *s_instance;
}
HRESULT
CMSWindowsDropTarget::DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
{
// check if data object contain drop
m_allowDrop = queryDataObject(dataObject);
if (m_allowDrop) {
getDropData(dataObject);
}
*effect = DROPEFFECT_NONE;
return S_OK;
}
HRESULT
CMSWindowsDropTarget::DragOver(DWORD keyState, POINTL point, DWORD* effect)
{
*effect = DROPEFFECT_NONE;
return S_OK;
}
HRESULT
CMSWindowsDropTarget::DragLeave(void)
{
return S_OK;
}
HRESULT
CMSWindowsDropTarget::Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
{
*effect = DROPEFFECT_NONE;
return S_OK;
}
bool
CMSWindowsDropTarget::queryDataObject(IDataObject* dataObject)
{
// check if it supports CF_HDROP using a HGLOBAL
FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
return dataObject->QueryGetData(&fmtetc) == S_OK ? true : false;
}
void
CMSWindowsDropTarget::setDraggingFilename(char* const filename)
{
m_dragFilename = filename;
}
std::string
CMSWindowsDropTarget::getDraggingFilename()
{
return m_dragFilename;
}
void
CMSWindowsDropTarget::clearDraggingFilename()
{
m_dragFilename.clear();
}
void
getDropData(IDataObject* dataObject)
{
// construct a FORMATETC object
FORMATETC fmtEtc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgMed;
// See if the dataobject contains any DROP stored as a HGLOBAL
if(dataObject->QueryGetData(&fmtEtc) == S_OK) {
if(dataObject->GetData(&fmtEtc, &stgMed) == S_OK) {
// get data here
PVOID data = GlobalLock(stgMed.hGlobal);
// data object global handler contains:
// DROPFILESfilename1 filename2 two spaces as the end
// TODO: get multiple filenames
wchar_t* wcData = (wchar_t*)((LPBYTE)data + sizeof(DROPFILES));
// convert wchar to char
char* filename = new char[wcslen(wcData) + 1];
filename[wcslen(wcData)] = '\0';
wcstombs(filename, wcData, wcslen(wcData));
CMSWindowsDropTarget::instance().setDraggingFilename(filename);
GlobalUnlock(stgMed.hGlobal);
// release the data using the COM API
ReleaseStgMedium(&stgMed);
delete[] filename;
}
}
}
HRESULT __stdcall
CMSWindowsDropTarget::QueryInterface (REFIID iid, void ** object)
{
if (iid == IID_IDropTarget || iid == IID_IUnknown) {
AddRef();
*object = this;
return S_OK;
}
else {
*object = 0;
return E_NOINTERFACE;
}
}
ULONG __stdcall
CMSWindowsDropTarget::AddRef(void)
{
return InterlockedIncrement(&m_refCount);
}
ULONG __stdcall
CMSWindowsDropTarget::Release(void)
{
LONG count = InterlockedDecrement(&m_refCount);
if (count == 0) {
delete this;
return 0;
}
else {
return count;
}
}

View File

@ -0,0 +1,59 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2014 Bolton Software 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 COPYING 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 <string>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <oleidl.h>
class CMSWindowsScreen;
class CMSWindowsDropTarget : public IDropTarget {
public:
CMSWindowsDropTarget();
~CMSWindowsDropTarget();
// IUnknown implementation
HRESULT __stdcall QueryInterface(REFIID iid, void** object);
ULONG __stdcall AddRef(void);
ULONG __stdcall Release(void);
// IDropTarget implementation
HRESULT __stdcall DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect);
HRESULT __stdcall DragOver(DWORD keyState, POINTL point, DWORD* effect);
HRESULT __stdcall DragLeave(void);
HRESULT __stdcall Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect);
void setDraggingFilename(char* const);
std::string getDraggingFilename();
void clearDraggingFilename();
static CMSWindowsDropTarget&
instance();
private:
bool queryDataObject(IDataObject* dataObject);
long m_refCount;
bool m_allowDrop;
std::string m_dragFilename;
static CMSWindowsDropTarget*
s_instance;
};

View File

@ -18,6 +18,7 @@
#include "platform/MSWindowsScreen.h" #include "platform/MSWindowsScreen.h"
#include "platform/MSWindowsDropTarget.h"
#include "client/Client.h" #include "client/Client.h"
#include "platform/MSWindowsClipboard.h" #include "platform/MSWindowsClipboard.h"
#include "platform/MSWindowsDesks.h" #include "platform/MSWindowsDesks.h"
@ -118,7 +119,9 @@ CMSWindowsScreen::CMSWindowsScreen(
m_keyState(NULL), m_keyState(NULL),
m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0),
m_showingMouse(false), m_showingMouse(false),
m_events(events) m_events(events),
m_dropWindow(NULL),
m_dropWindowSize(20)
{ {
assert(s_windowInstance != NULL); assert(s_windowInstance != NULL);
assert(s_screen == NULL); assert(s_screen == NULL);
@ -157,6 +160,11 @@ CMSWindowsScreen::CMSWindowsScreen(
else { else {
LOG((CLOG_ERR "failed to get desktop path, no drop target available, error=%d", GetLastError())); LOG((CLOG_ERR "failed to get desktop path, no drop target available, error=%d", GetLastError()));
} }
OleInitialize(0);
m_dropWindow = createDropWindow(m_class, "DropWindow");
m_dropTarget = new CMSWindowsDropTarget();
RegisterDragDrop(m_dropWindow, m_dropTarget);
} }
catch (...) { catch (...) {
delete m_keyState; delete m_keyState;
@ -190,6 +198,11 @@ CMSWindowsScreen::~CMSWindowsScreen()
destroyWindow(m_window); destroyWindow(m_window);
destroyClass(m_class); destroyClass(m_class);
RevokeDragDrop(m_dropWindow);
m_dropTarget->Release();
OleUninitialize();
destroyWindow(m_dropWindow);
s_screen = NULL; s_screen = NULL;
} }
@ -345,32 +358,33 @@ CMSWindowsScreen::leave()
m_isOnScreen = false; m_isOnScreen = false;
forceShowCursor(); forceShowCursor();
if (isDraggingStarted()) { if (isDraggingStarted() && !m_isPrimary) {
CString& draggingFilename = getDraggingFilename(); m_sendDragThread = new CThread(
size_t size = draggingFilename.size(); new TMethodJob<CMSWindowsScreen>(
this,
&CMSWindowsScreen::sendDragThread));
}
if (!m_isPrimary) { return true;
// TODO: fake these keys properly }
fakeKeyDown(kKeyEscape, 8192, 1);
fakeKeyUp(1);
fakeMouseButton(kButtonLeft, false); void
CMSWindowsScreen::sendDragThread(void*)
{
CString& draggingFilename = getDraggingFilename();
size_t size = draggingFilename.size();
if (draggingFilename.empty() == false) { if (draggingFilename.empty() == false) {
CClientApp& app = CClientApp::instance(); CClientApp& app = CClientApp::instance();
CClient* client = app.getClientPtr(); CClient* client = app.getClientPtr();
UInt32 fileCount = 1; UInt32 fileCount = 1;
LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str())); LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str()));
client->draggingInfoSending(fileCount, draggingFilename, size); client->draggingInfoSending(fileCount, draggingFilename, size);
LOG((CLOG_DEBUG "send dragging file to server")); LOG((CLOG_DEBUG "send dragging file to server"));
client->sendFileToServer(draggingFilename.c_str()); client->sendFileToServer(draggingFilename.c_str());
}
}
m_draggingStarted = false;
} }
return true; m_draggingStarted = false;
} }
bool bool
@ -858,6 +872,29 @@ CMSWindowsScreen::createWindow(ATOM windowClass, const char* name) const
return window; return window;
} }
HWND
CMSWindowsScreen::createDropWindow(ATOM windowClass, const char* name) const
{
HWND window = CreateWindowEx(
WS_EX_TOPMOST |
WS_EX_TRANSPARENT |
WS_EX_ACCEPTFILES,
reinterpret_cast<LPCTSTR>(m_class),
name,
WS_POPUP,
0, 0, m_dropWindowSize, m_dropWindowSize,
NULL, NULL,
s_windowInstance,
NULL);
if (window == NULL) {
LOG((CLOG_ERR "failed to create drop window: %d", GetLastError()));
throw XScreenOpenFailure();
}
return window;
}
void void
CMSWindowsScreen::destroyWindow(HWND hwnd) const CMSWindowsScreen::destroyWindow(HWND hwnd) const
{ {
@ -1801,9 +1838,48 @@ CString&
CMSWindowsScreen::getDraggingFilename() CMSWindowsScreen::getDraggingFilename()
{ {
if (m_draggingStarted) { if (m_draggingStarted) {
char filename[MAX_PATH]; m_dropTarget->clearDraggingFilename();
m_shellEx.getDraggingFilename(filename); m_draggingFilename.clear();
m_draggingFilename = filename;
int halfSize = m_dropWindowSize / 2;
SInt32 xPos = m_isPrimary ? m_xCursor : m_xCenter;
SInt32 yPos = m_isPrimary ? m_yCursor : m_yCenter;
xPos = (xPos - halfSize) < 0 ? 0 : xPos - halfSize;
yPos = (yPos - halfSize) < 0 ? 0 : yPos - halfSize;
SetWindowPos(
m_dropWindow,
HWND_TOPMOST,
xPos,
yPos,
m_dropWindowSize,
m_dropWindowSize,
SWP_SHOWWINDOW);
// TODO: fake these keys properly
fakeKeyDown(kKeyEscape, 8192, 1);
fakeKeyUp(1);
fakeMouseButton(kButtonLeft, false);
CString filename;
DOUBLE timeout = ARCH->time() + .5f;
while (ARCH->time() < timeout) {
ARCH->sleep(.05f);
filename = m_dropTarget->getDraggingFilename();
if (!filename.empty()) {
break;
}
}
ShowWindow(m_dropWindow, SW_HIDE);
if (!filename.empty()) {
m_draggingFilename = filename;
}
if (m_draggingFilename.empty()) {
LOG((CLOG_DEBUG "failed to get drag file name from OLE"));
}
} }
return m_draggingFilename; return m_draggingFilename;

View File

@ -34,6 +34,7 @@ class CMSWindowsDesks;
class CMSWindowsKeyState; class CMSWindowsKeyState;
class CMSWindowsScreenSaver; class CMSWindowsScreenSaver;
class CThread; class CThread;
class CMSWindowsDropTarget;
//! Implementation of IPlatformScreen for Microsoft Windows //! Implementation of IPlatformScreen for Microsoft Windows
class CMSWindowsScreen : public CPlatformScreen { class CMSWindowsScreen : public CPlatformScreen {
@ -135,6 +136,7 @@ private:
ATOM createDeskWindowClass(bool isPrimary) const; ATOM createDeskWindowClass(bool isPrimary) const;
void destroyClass(ATOM windowClass) const; void destroyClass(ATOM windowClass) const;
HWND createWindow(ATOM windowClass, const char* name) const; HWND createWindow(ATOM windowClass, const char* name) const;
HWND createDropWindow(ATOM windowClass, const char* name) const;
void destroyWindow(HWND) const; void destroyWindow(HWND) const;
// convenience function to send events // convenience function to send events
@ -216,6 +218,9 @@ private: // HACK
bool isModifierRepeat(KeyModifierMask oldState, bool isModifierRepeat(KeyModifierMask oldState,
KeyModifierMask state, WPARAM wParam) const; KeyModifierMask state, WPARAM wParam) const;
// send drag info and data back to server
void sendDragThread(void*);
private: private:
struct CHotKeyItem { struct CHotKeyItem {
public: public:
@ -325,4 +330,11 @@ private:
IEventQueue* m_events; IEventQueue* m_events;
CString m_desktopPath; CString m_desktopPath;
CMSWindowsDropTarget*
m_dropTarget;
HWND m_dropWindow;
const int m_dropWindowSize;
CThread* m_sendDragThread;
}; };

View File

@ -2102,6 +2102,11 @@ COSXScreen::getDraggingFilename()
CString fileList(info); CString fileList(info);
m_draggingFilename = fileList; m_draggingFilename = fileList;
} }
// fake a escape key down and up then left mouse button up
fakeKeyDown(kKeyEscape, 8192, 1);
fakeKeyUp(1);
fakeMouseButton(kButtonLeft, false);
} }
return m_draggingFilename; return m_draggingFilename;
} }

View File

@ -85,7 +85,9 @@ CServer::CServer(
m_sendFileThread(NULL), m_sendFileThread(NULL),
m_writeToDropDirThread(NULL), m_writeToDropDirThread(NULL),
m_ignoreFileTransfer(false), m_ignoreFileTransfer(false),
m_enableDragDrop(enableDragDrop) m_enableDragDrop(enableDragDrop),
m_getDragInfoThread(NULL),
m_waitDragInfoThread(true)
{ {
// 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);
@ -1658,6 +1660,9 @@ CServer::onMouseDown(ButtonID id)
// relay // relay
m_active->mouseDown(id); m_active->mouseDown(id);
// reset this variable back to default value true
m_waitDragInfoThread = true;
} }
void void
@ -1759,46 +1764,76 @@ CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
// should we switch or not? // should we switch or not?
if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
if (m_enableDragDrop && m_screen->isDraggingStarted() && m_active != newScreen) { if (m_enableDragDrop
CString& dragFileList = m_screen->getDraggingFilename(); && m_screen->isDraggingStarted()
size_t size = dragFileList.size() + 1; && m_active != newScreen
char* fileList = NULL; && m_waitDragInfoThread) {
UInt32 fileCount = 1; if (m_getDragInfoThread == NULL) {
if (dragFileList.empty() == false) { m_getDragInfoThread = new CThread(
fileList = new char[size]; new TMethodJob<CServer>(
memcpy(fileList, dragFileList.c_str(), size); this,
fileList[size - 1] = '\0'; &CServer::getDragInfoThread));
}
return false;
}
if (m_getDragInfoThread == NULL) {
// switch screen
switchScreen(newScreen, x, y, false);
// send drag file info to client if there is any
if (m_dragFileList.size() > 0) {
sendDragInfo(newScreen);
m_dragFileList.clear();
} }
// fake a escape key down and up then left mouse button up m_waitDragInfoThread = true;
m_screen->keyDown(kKeyEscape, 8192, 1);
m_screen->keyUp(kKeyEscape, 8192, 1); return true;
m_screen->mouseUp(kButtonLeft); }
}
return false;
}
void
CServer::getDragInfoThread(void*)
{
m_dragFileList.clear();
CString& dragFileList = m_screen->getDraggingFilename();
if (!dragFileList.empty()) {
m_dragFileList.push_back(dragFileList);
}
#if defined(__APPLE__) #if defined(__APPLE__)
// on mac it seems that after faking a LMB up, system would signal back
// on mac it seems that after faking a LMB up, system would signal back // to synergy a mouse up event, which doesn't happen on windows. as a
// to synergy a mouse up event, which doesn't happen on windows. as a // result, synergy would send dragging file to client twice. This variable
// result, synergy would send dragging file to client twice. This variable // is used to ignore the first file sending.
// is used to ignore the first file sending. m_ignoreFileTransfer = true;
m_ignoreFileTransfer = true;
#endif #endif
if (dragFileList.empty() == false) {
LOG((CLOG_DEBUG2 "sending drag information to client"));
LOG((CLOG_DEBUG3 "dragging file list: %s", fileList));
LOG((CLOG_DEBUG3 "dragging file list string size: %i", size));
newScreen->draggingInfoSending(fileCount, fileList, size);
}
}
// switch screen m_waitDragInfoThread = false;
switchScreen(newScreen, x, y, false); m_getDragInfoThread = NULL;
}
return true; void
} CServer::sendDragInfo(CBaseClientProxy* newScreen)
else { {
return false; // TODO: support multiple files dragging
CString& dragFile = m_dragFileList.at(0);
size_t size = dragFile.size() + 1;
char* fileList = NULL;
UInt32 fileCount = 1;
if (dragFile.empty() == false) {
fileList = new char[size];
memcpy(fileList, dragFile.c_str(), size);
fileList[size - 1] = '\0';
LOG((CLOG_DEBUG2 "sending drag information to client"));
LOG((CLOG_DEBUG3 "dragging file list: %s", fileList));
LOG((CLOG_DEBUG3 "dragging file list string size: %i", size));
newScreen->draggingInfoSending(fileCount, fileList, size);
} }
} }

View File

@ -365,6 +365,12 @@ private:
// thread function for writing file to drop directory // thread function for writing file to drop directory
void writeToDropDirThread(void*); void writeToDropDirThread(void*);
// thread function for getting drag filename
void getDragInfoThread(void*);
// send drag info to new client screen
void sendDragInfo(CBaseClientProxy* newScreen);
public: public:
bool m_mock; bool m_mock;
@ -472,4 +478,7 @@ private:
CString m_dragFileExt; CString m_dragFileExt;
bool m_ignoreFileTransfer; bool m_ignoreFileTransfer;
bool m_enableDragDrop; bool m_enableDragDrop;
CThread* m_getDragInfoThread;
bool m_waitDragInfoThread;
}; };