diff --git a/src/lib/platform/MSWindowsDropTarget.cpp b/src/lib/platform/MSWindowsDropTarget.cpp new file mode 100644 index 00000000..9b1dfd58 --- /dev/null +++ b/src/lib/platform/MSWindowsDropTarget.cpp @@ -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 . + */ + +#include "platform/MSWindowsDropTarget.h" + +#include "base/Log.h" +#include "common/common.h" + +#include +#include + +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; + } +} diff --git a/src/lib/platform/MSWindowsDropTarget.h b/src/lib/platform/MSWindowsDropTarget.h new file mode 100644 index 00000000..9e4f1be8 --- /dev/null +++ b/src/lib/platform/MSWindowsDropTarget.h @@ -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 . + */ + +#pragma once + +#include +#define WIN32_LEAN_AND_MEAN +#include +#include + +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; +}; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index 3422fd03..9d0081bb 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -18,6 +18,7 @@ #include "platform/MSWindowsScreen.h" +#include "platform/MSWindowsDropTarget.h" #include "client/Client.h" #include "platform/MSWindowsClipboard.h" #include "platform/MSWindowsDesks.h" @@ -118,7 +119,9 @@ CMSWindowsScreen::CMSWindowsScreen( m_keyState(NULL), m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), m_showingMouse(false), - m_events(events) + m_events(events), + m_dropWindow(NULL), + m_dropWindowSize(20) { assert(s_windowInstance != NULL); assert(s_screen == NULL); @@ -157,6 +160,11 @@ CMSWindowsScreen::CMSWindowsScreen( else { 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 (...) { delete m_keyState; @@ -190,6 +198,11 @@ CMSWindowsScreen::~CMSWindowsScreen() destroyWindow(m_window); destroyClass(m_class); + RevokeDragDrop(m_dropWindow); + m_dropTarget->Release(); + OleUninitialize(); + destroyWindow(m_dropWindow); + s_screen = NULL; } @@ -345,32 +358,33 @@ CMSWindowsScreen::leave() m_isOnScreen = false; forceShowCursor(); - if (isDraggingStarted()) { - CString& draggingFilename = getDraggingFilename(); - size_t size = draggingFilename.size(); + if (isDraggingStarted() && !m_isPrimary) { + m_sendDragThread = new CThread( + new TMethodJob( + this, + &CMSWindowsScreen::sendDragThread)); + } - if (!m_isPrimary) { - // TODO: fake these keys properly - fakeKeyDown(kKeyEscape, 8192, 1); - fakeKeyUp(1); + return true; +} - fakeMouseButton(kButtonLeft, false); +void +CMSWindowsScreen::sendDragThread(void*) +{ + CString& draggingFilename = getDraggingFilename(); + size_t size = draggingFilename.size(); - if (draggingFilename.empty() == false) { - CClientApp& app = CClientApp::instance(); - CClient* client = app.getClientPtr(); - UInt32 fileCount = 1; - LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str())); - client->draggingInfoSending(fileCount, draggingFilename, size); - LOG((CLOG_DEBUG "send dragging file to server")); - client->sendFileToServer(draggingFilename.c_str()); - } - } - - m_draggingStarted = false; + if (draggingFilename.empty() == false) { + CClientApp& app = CClientApp::instance(); + CClient* client = app.getClientPtr(); + UInt32 fileCount = 1; + LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str())); + client->draggingInfoSending(fileCount, draggingFilename, size); + LOG((CLOG_DEBUG "send dragging file to server")); + client->sendFileToServer(draggingFilename.c_str()); } - return true; + m_draggingStarted = false; } bool @@ -858,6 +872,29 @@ CMSWindowsScreen::createWindow(ATOM windowClass, const char* name) const 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(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 CMSWindowsScreen::destroyWindow(HWND hwnd) const { @@ -1801,9 +1838,48 @@ CString& CMSWindowsScreen::getDraggingFilename() { if (m_draggingStarted) { - char filename[MAX_PATH]; - m_shellEx.getDraggingFilename(filename); - m_draggingFilename = filename; + m_dropTarget->clearDraggingFilename(); + m_draggingFilename.clear(); + + 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; diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h index 4350cd7f..d3543a6c 100644 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -34,6 +34,7 @@ class CMSWindowsDesks; class CMSWindowsKeyState; class CMSWindowsScreenSaver; class CThread; +class CMSWindowsDropTarget; //! Implementation of IPlatformScreen for Microsoft Windows class CMSWindowsScreen : public CPlatformScreen { @@ -135,6 +136,7 @@ private: ATOM createDeskWindowClass(bool isPrimary) const; void destroyClass(ATOM windowClass) const; HWND createWindow(ATOM windowClass, const char* name) const; + HWND createDropWindow(ATOM windowClass, const char* name) const; void destroyWindow(HWND) const; // convenience function to send events @@ -216,6 +218,9 @@ private: // HACK bool isModifierRepeat(KeyModifierMask oldState, KeyModifierMask state, WPARAM wParam) const; + // send drag info and data back to server + void sendDragThread(void*); + private: struct CHotKeyItem { public: @@ -325,4 +330,11 @@ private: IEventQueue* m_events; CString m_desktopPath; + + CMSWindowsDropTarget* + m_dropTarget; + HWND m_dropWindow; + const int m_dropWindowSize; + + CThread* m_sendDragThread; }; diff --git a/src/lib/platform/OSXScreen.cpp b/src/lib/platform/OSXScreen.cpp index 0f19bf28..86e86856 100644 --- a/src/lib/platform/OSXScreen.cpp +++ b/src/lib/platform/OSXScreen.cpp @@ -2102,6 +2102,11 @@ COSXScreen::getDraggingFilename() CString fileList(info); 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; } diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 99c38eab..186d294d 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -85,7 +85,9 @@ CServer::CServer( m_sendFileThread(NULL), m_writeToDropDirThread(NULL), 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 assert(m_primaryClient != NULL); @@ -1658,6 +1660,9 @@ CServer::onMouseDown(ButtonID id) // relay m_active->mouseDown(id); + + // reset this variable back to default value true + m_waitDragInfoThread = true; } void @@ -1759,46 +1764,76 @@ CServer::onMouseMovePrimary(SInt32 x, SInt32 y) // should we switch or not? if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { - if (m_enableDragDrop && m_screen->isDraggingStarted() && m_active != newScreen) { - CString& dragFileList = m_screen->getDraggingFilename(); - size_t size = dragFileList.size() + 1; - char* fileList = NULL; - UInt32 fileCount = 1; - if (dragFileList.empty() == false) { - fileList = new char[size]; - memcpy(fileList, dragFileList.c_str(), size); - fileList[size - 1] = '\0'; + if (m_enableDragDrop + && m_screen->isDraggingStarted() + && m_active != newScreen + && m_waitDragInfoThread) { + if (m_getDragInfoThread == NULL) { + m_getDragInfoThread = new CThread( + new TMethodJob( + this, + &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_screen->keyDown(kKeyEscape, 8192, 1); - m_screen->keyUp(kKeyEscape, 8192, 1); - m_screen->mouseUp(kButtonLeft); + m_waitDragInfoThread = true; + + return true; + } + } + + 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__) - - // 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 - // result, synergy would send dragging file to client twice. This variable - // is used to ignore the first file sending. - m_ignoreFileTransfer = true; + // 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 + // result, synergy would send dragging file to client twice. This variable + // is used to ignore the first file sending. + m_ignoreFileTransfer = true; #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 - switchScreen(newScreen, x, y, false); + m_waitDragInfoThread = false; + m_getDragInfoThread = NULL; +} - return true; - } - else { - return false; +void +CServer::sendDragInfo(CBaseClientProxy* newScreen) +{ + // 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); } } diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index 3b897634..925bdce8 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -365,6 +365,12 @@ private: // thread function for writing file to drop directory void writeToDropDirThread(void*); + // thread function for getting drag filename + void getDragInfoThread(void*); + + // send drag info to new client screen + void sendDragInfo(CBaseClientProxy* newScreen); + public: bool m_mock; @@ -472,4 +478,7 @@ private: CString m_dragFileExt; bool m_ignoreFileTransfer; bool m_enableDragDrop; + + CThread* m_getDragInfoThread; + bool m_waitDragInfoThread; };