diff --git a/examples/triangle-opengl.c b/examples/triangle-opengl.c index ff9e7d3b..05d9b3dd 100644 --- a/examples/triangle-opengl.c +++ b/examples/triangle-opengl.c @@ -80,8 +80,89 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, glfwSetWindowShouldClose(window, GLFW_TRUE); } +void drag_callback(GLFWwindow* window, int event, double xpos, double ypos, int availableFormats, int *format, int availableActions, int *action) +{ + int width, height; + glfwGetWindowSize(window, &width, &height); + switch(event) + { + case GLFW_DND_ENTER: + { + printf("ENTER (%d, %d)\n", (int)xpos, (int)ypos); + break; + } + case GLFW_DND_DRAG: + { + printf("DRAG (%d, %d)\n", (int)xpos, (int)ypos); + + if ((int) xpos < (width / 2)) + { + if ((availableFormats & GLFW_DND_PATHS) == GLFW_DND_PATHS) + { + printf("accepting paths\n"); + *format = GLFW_DND_PATHS; + } + else + { + printf("reject\n"); + *action = GLFW_DND_NONE; + } + } + else { + if ((availableFormats & GLFW_DND_TEXT) == GLFW_DND_TEXT) + { + printf("accepting text\n"); + *format = GLFW_DND_TEXT; + } + else + { + printf("reject\n"); + *action = GLFW_DND_NONE; + } + } + + break; + } + case GLFW_DND_LEAVE: + { + printf("LEAVE\n"); + break; + } + } +} + +void drop_callback(GLFWwindow* window, int count, const char** paths) +{ + for (int i = 0; i < count; ++i) + printf("path[%d]: %s\n", i, paths[i]); +} + +void dropex_callback(GLFWwindow* window, int format, int data_count, void *data, int *action) +{ + printf("DROP\n"); + + switch(format) + { + case GLFW_DND_PATHS: + { + const char **paths = (const char **) data; + for(int i = 0; i < data_count; ++i) + printf("path[%d]: %s\n", i, paths[i]); + break; + } + case GLFW_DND_TEXT: + printf("text: %s\n", (const char *) data); + break; + default: + break; + } +} + int main(void) { + (void)setvbuf(stdout, NULL, _IONBF, 0); + (void)setvbuf(stderr, NULL, _IONBF, 0); + glfwSetErrorCallback(error_callback); if (!glfwInit()) @@ -99,6 +180,9 @@ int main(void) } glfwSetKeyCallback(window, key_callback); + glfwSetDragCallback(window, drag_callback); + glfwSetDropCallback(window, drop_callback); + glfwSetDropCallbackEx(window, dropex_callback); glfwMakeContextCurrent(window); gladLoadGL(glfwGetProcAddress); @@ -149,7 +233,8 @@ int main(void) mat4x4 m, p, mvp; mat4x4_identity(m); - mat4x4_rotate_Z(m, m, (float) glfwGetTime()); + if (glfwGetWindowAttrib(window, GLFW_DND_DRAGGING)) + mat4x4_rotate_Z(m, m, (float) glfwGetTime()); mat4x4_ortho(p, -ratio, ratio, -1.f, 1.f, 1.f, -1.f); mat4x4_mul(mvp, p, m); diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 8a43134c..8154753f 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -927,6 +927,9 @@ extern "C" { */ #define GLFW_MOUSE_PASSTHROUGH 0x0002000D +// TODO doc +#define GLFW_DND_DRAGGING 0x0002000E + /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_RED_BITS). @@ -1309,6 +1312,25 @@ extern "C" { #define GLFW_PLATFORM_NULL 0x00060005 /*! @} */ +// TODO doc +#define GLFW_DND_NONE 0 + +// Drag and drop events +#define GLFW_DND_ENTER 0xdd100001 +#define GLFW_DND_DRAG 0xdd100002 +#define GLFW_DND_LEAVE 0xdd100003 + +// Drag and drop actions +// GLFW_DND_NONE +#define GLFW_DND_COPY 0xdd200001 +#define GLFW_DND_LINK 0xdd200002 +#define GLFW_DND_MOVE 0xdd200004 + +// Drag and drop formats +// GLFW_DND_NONE +#define GLFW_DND_TEXT 0xdd300002 +#define GLFW_DND_PATHS 0xdd300001 + #define GLFW_DONT_CARE -1 @@ -1884,6 +1906,9 @@ typedef void (* GLFWcharfun)(GLFWwindow* window, unsigned int codepoint); */ typedef void (* GLFWcharmodsfun)(GLFWwindow* window, unsigned int codepoint, int mods); +// TODO doc +typedef void (* GLFWdragfun)(GLFWwindow* window, int event, double xpos, double ypos, int availableFormats, int *format, int availableActions, int *action); + /*! @brief The function pointer type for path drop callbacks. * * This is the function pointer type for path drop callbacks. A path drop @@ -1908,6 +1933,9 @@ typedef void (* GLFWcharmodsfun)(GLFWwindow* window, unsigned int codepoint, int */ typedef void (* GLFWdropfun)(GLFWwindow* window, int path_count, const char* paths[]); +// TODO doc +typedef void (* GLFWdropfunex)(GLFWwindow* window, int format, int data_count, void *data, int *action); + /*! @brief The function pointer type for monitor configuration callbacks. * * This is the function pointer type for monitor configuration callbacks. @@ -5293,6 +5321,9 @@ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcu */ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun callback); +// TODO doc +GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* window, GLFWdragfun callback); + /*! @brief Sets the path drop callback. * * This function sets the path drop callback of the specified window, which is @@ -5330,6 +5361,9 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun ca */ GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun callback); +// TODO doc +GLFWAPI GLFWdropfunex glfwSetDropCallbackEx(GLFWwindow* window, GLFWdropfunex callback); + /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. diff --git a/src/init.c b/src/init.c index d07a492e..d84c13f6 100644 --- a/src/init.c +++ b/src/init.c @@ -266,6 +266,41 @@ float _glfw_fmaxf(float a, float b) return b; } +int _glfw_ffs(int i) +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_ffs(i); +#elif defined(_MSC_VER) + unsigned long n, u = i; + return _BitScanForward(&n, u) ? n + 1 : 0; +#else + // https://en.wikipedia.org/wiki/Find_first_set#CTZ + unsigned int u = i; + int n = 1; + if (!u) return 0; + if (!(u & 0x0000FFFF)) { + n += 16; + u >>= 16; + } + if (!(u & 0x000000FF)) { + n += 8; + u >>= 8; + } + if (!(u & 0x0000000F)) { + n += 4; + u >>= 4; + } + if (!(u & 0x00000003)) { + n += 2; + u >>= 2; + } + if (!(u & 0x00000001)) { + n += 1; + } + return n; +#endif +} + void* _glfw_calloc(size_t count, size_t size) { if (count && size) diff --git a/src/input.c b/src/input.c index 36128e10..edef6cd8 100644 --- a/src/input.c +++ b/src/input.c @@ -401,6 +401,14 @@ void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered) window->callbacks.cursorEnter((GLFWwindow*) window, entered); } +// Notifies shared code of data being dragged in/over/out of a window +// +void _glfwInputDrag(_GLFWwindow* window, int event, double xpos, double ypos, int availableFormats, int *format, int availableActions, int *action) +{ + if (window->callbacks.drag) + window->callbacks.drag((GLFWwindow*) window, event, xpos, ypos, availableFormats, format, availableActions, action); +} + // Notifies shared code of files or directories dropped on a window // void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) @@ -413,6 +421,14 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) window->callbacks.drop((GLFWwindow*) window, count, paths); } +// Notifies shared code of data dropped on a window +// +void _glfwInputDropEx(_GLFWwindow* window, int format, int data_count, void *data, int *action) +{ + if (window->callbacks.dropex) + window->callbacks.dropex((GLFWwindow*) window, format, data_count, data, action); +} + // Notifies shared code of a joystick connection or disconnection // void _glfwInputJoystick(_GLFWjoystick* js, int event) @@ -1012,6 +1028,16 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle, return cbfun; } +GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* handle, GLFWdragfun cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWdragfun, window->callbacks.drag, cbfun); + return cbfun; +} + GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; @@ -1022,6 +1048,16 @@ GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun) return cbfun; } +GLFWAPI GLFWdropfunex glfwSetDropCallbackEx(GLFWwindow* handle, GLFWdropfunex cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWdropfunex, window->callbacks.dropex, cbfun); + return cbfun; +} + GLFWAPI int glfwJoystickPresent(int jid) { _GLFWjoystick* js; diff --git a/src/internal.h b/src/internal.h index 8d576d1d..b46b7c40 100644 --- a/src/internal.h +++ b/src/internal.h @@ -58,6 +58,17 @@ #define _GLFW_MESSAGE_SIZE 1024 +#define _GLFW_DND_MASK 0x000fffff + +#define _GLFW_DND_TEXT_INDEX 1 +#define _GLFW_DND_PATHS_INDEX 0 +#define _GLFW_DND_FORMAT_COUNT 2 + +#define _GLFW_DND_COPY_INDEX 0 +#define _GLFW_DND_LINK_INDEX 1 +#define _GLFW_DND_MOVE_INDEX 2 +#define _GLFW_DND_ACTION_COUNT 3 + typedef int GLFWbool; typedef void (*GLFWproc)(void); @@ -533,6 +544,7 @@ struct _GLFWwindow GLFWbool floating; GLFWbool focusOnShow; GLFWbool mousePassthrough; + GLFWbool dndDragging; GLFWbool shouldClose; void* userPointer; GLFWbool doublebuffer; @@ -573,7 +585,9 @@ struct _GLFWwindow GLFWkeyfun key; GLFWcharfun character; GLFWcharmodsfun charmods; + GLFWdragfun drag; GLFWdropfun drop; + GLFWdropfunex dropex; } callbacks; // This is defined in platform.h @@ -928,7 +942,9 @@ void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered); +void _glfwInputDrag(_GLFWwindow* window, int event, double xpos, double ypos, int availableFormats, int *format, int availableActions, int *action); void _glfwInputDrop(_GLFWwindow* window, int count, const char** names); +void _glfwInputDropEx(_GLFWwindow* window, int format, int data_count, void *data, int *action); void _glfwInputJoystick(_GLFWjoystick* js, int event); void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); @@ -1007,6 +1023,7 @@ int _glfw_min(int a, int b); int _glfw_max(int a, int b); float _glfw_fminf(float a, float b); float _glfw_fmaxf(float a, float b); +int _glfw_ffs(int i); void* _glfw_calloc(size_t count, size_t size); void* _glfw_realloc(void* pointer, size_t size); diff --git a/src/win32_init.c b/src/win32_init.c index 8704150c..9a289e80 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -167,6 +167,19 @@ static GLFWbool loadLibraries(void) _glfwPlatformGetModuleSymbol(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo"); } + _glfw.win32.ole32.instance = _glfwPlatformLoadModule("ole32.dll"); + if (_glfw.win32.ole32.instance) + { + _glfw.win32.ole32.OleInitialize_ = (PFN_OleInitialize) + _glfwPlatformGetModuleSymbol(_glfw.win32.ole32.instance, "OleInitialize"); + _glfw.win32.ole32.OleUninitialize_ = (PFN_OleUninitialize) + _glfwPlatformGetModuleSymbol(_glfw.win32.ole32.instance, "OleUninitialize"); + _glfw.win32.ole32.RegisterDragDrop_ = (PFN_RegisterDragDrop) + _glfwPlatformGetModuleSymbol(_glfw.win32.ole32.instance, "RegisterDragDrop"); + _glfw.win32.ole32.RevokeDragDrop_ = (PFN_RevokeDragDrop) + _glfwPlatformGetModuleSymbol(_glfw.win32.ole32.instance, "RevokeDragDrop"); + } + return GLFW_TRUE; } @@ -191,6 +204,9 @@ static void freeLibraries(void) if (_glfw.win32.ntdll.instance) _glfwPlatformFreeModule(_glfw.win32.ntdll.instance); + + if (_glfw.win32.ole32.instance) + _glfwPlatformFreeModule(_glfw.win32.ole32.instance); } // Create key code translation tables @@ -430,6 +446,27 @@ static GLFWbool createHelperWindow(void) return GLFW_TRUE; } +// Initialize OLE as well as the IDropTarget vtable +// +static GLFWbool initDragDrop() +{ + if (FAILED(OleInitialize(NULL))) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, + "Win32: Failed to initialize OLE"); + return GLFW_FALSE; + } + + //_glfw.win32.dropTargetVtbl.QueryInterface = _glfwDropTarget_QueryInterface; + _glfw.win32.dropTargetVtbl.AddRef = _glfwDropTarget_AddRef; + _glfw.win32.dropTargetVtbl.Release = _glfwDropTarget_Release; + _glfw.win32.dropTargetVtbl.DragEnter = _glfwDropTarget_DragEnter; + _glfw.win32.dropTargetVtbl.DragOver = _glfwDropTarget_DragOver; + _glfw.win32.dropTargetVtbl.DragLeave = _glfwDropTarget_DragLeave; + _glfw.win32.dropTargetVtbl.Drop = _glfwDropTarget_Drop; + + return GLFW_TRUE; +} ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// @@ -699,6 +736,9 @@ int _glfwInitWin32(void) if (!createHelperWindow()) return GLFW_FALSE; + if (!initDragDrop()) + return GLFW_FALSE; + _glfwPollMonitorsWin32(); return GLFW_TRUE; } @@ -708,6 +748,8 @@ void _glfwTerminateWin32(void) if (_glfw.win32.deviceNotificationHandle) UnregisterDeviceNotification(_glfw.win32.deviceNotificationHandle); + OleUninitialize(); + if (_glfw.win32.helperWindowHandle) DestroyWindow(_glfw.win32.helperWindowHandle); if (_glfw.win32.helperWindowClass) diff --git a/src/win32_platform.h b/src/win32_platform.h index 82b34bb9..ee95eeb4 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -64,11 +64,15 @@ // GLFW uses OEM cursor resources #define OEMRESOURCE +// define COM C object macros +#define COBJMACROS + #include #include #include #include #include +#include // HACK: Define macros that some windows.h variants don't #ifndef WM_MOUSEHWHEEL @@ -316,6 +320,16 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*, typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG); #define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_ +// ole32.dll function pointer typedefs +typedef HRESULT (WINAPI * PFN_OleInitialize)(LPVOID); +typedef VOID (WINAPI * PFN_OleUninitialize)(VOID); +typedef HRESULT (WINAPI * PFN_RegisterDragDrop)(HWND,LPDROPTARGET); +typedef HRESULT (WINAPI * PFN_RevokeDragDrop)(HWND); +#define OleInitialize _glfw.win32.ole32.OleInitialize_ +#define OleUninitialize _glfw.win32.ole32.OleUninitialize_ +#define RegisterDragDrop _glfw.win32.ole32.RegisterDragDrop_ +#define RevokeDragDrop _glfw.win32.ole32.RevokeDragDrop_ + // WGL extension pointer typedefs typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); @@ -408,6 +422,18 @@ typedef struct _GLFWlibraryWGL GLFWbool ARB_context_flush_control; } _GLFWlibraryWGL; +typedef struct _GLFWdroptarget +{ + IDropTargetVtbl* lpVtbl; + ULONG cRefCount; + _GLFWwindow* window; + int availableFormats; + int chosenFormat; + int availableActions; + int proposedAction; + int chosenAction; +} _GLFWdroptarget; + // Win32-specific per-window data // typedef struct _GLFWwindowWin32 @@ -432,6 +458,8 @@ typedef struct _GLFWwindowWin32 int lastCursorPosX, lastCursorPosY; // The last received high surrogate when decoding pairs of UTF-16 messages WCHAR highSurrogate; + + _GLFWdroptarget dropTarget; } _GLFWwindowWin32; // Win32-specific global data @@ -457,6 +485,7 @@ typedef struct _GLFWlibraryWin32 RAWINPUT* rawInput; int rawInputSize; UINT mouseTrailSize; + IDropTargetVtbl dropTargetVtbl; struct { HINSTANCE instance; @@ -499,6 +528,15 @@ typedef struct _GLFWlibraryWin32 HINSTANCE instance; PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_; } ntdll; + + struct { + HINSTANCE instance; + + PFN_OleInitialize OleInitialize_; + PFN_OleUninitialize OleUninitialize_; + PFN_RegisterDragDrop RegisterDragDrop_; + PFN_RevokeDragDrop RevokeDragDrop_; + } ole32; } _GLFWlibraryWin32; // Win32-specific per-monitor data @@ -622,3 +660,11 @@ GLFWbool _glfwCreateContextWGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); +// IDropTarget vtable functions +//HRESULT STDMETHODCALLTYPE _glfwDropTarget_QueryInterface(IDropTarget *_This, REFIID riid, void **ppvObject); +ULONG STDMETHODCALLTYPE _glfwDropTarget_AddRef(IDropTarget *_This); +ULONG STDMETHODCALLTYPE _glfwDropTarget_Release(IDropTarget *_This); +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragEnter(IDropTarget *_This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragOver(IDropTarget *_This, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragLeave(IDropTarget *_This); +HRESULT STDMETHODCALLTYPE _glfwDropTarget_Drop(IDropTarget *_This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); diff --git a/src/win32_window.c b/src/win32_window.c index f069c115..3fa7407f 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -30,6 +30,7 @@ #include "internal.h" #include +#include // TODO remove #include #include #include @@ -1426,7 +1427,15 @@ static int createNativeWindow(_GLFWwindow* window, } } - DragAcceptFiles(window->win32.handle, TRUE); + //DragAcceptFiles(window->win32.handle, TRUE); + window->win32.dropTarget.lpVtbl = &_glfw.win32.dropTargetVtbl; + window->win32.dropTarget.window = window; + if (FAILED(RegisterDragDrop(window->win32.handle, (LPDROPTARGET)&window->win32.dropTarget))) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, + "Win32: Failed to register window for drag & drop"); + return GLFW_FALSE; + } if (fbconfig->transparent) { @@ -1515,6 +1524,8 @@ void _glfwDestroyWindowWin32(_GLFWwindow* window) if (_glfw.win32.capturedCursorWindow == window) releaseCursor(); + RevokeDragDrop(window->win32.handle); + if (window->win32.handle) { RemovePropW(window->win32.handle, L"GLFW"); @@ -2500,3 +2511,232 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) return window->win32.handle; } +/*static GLFWbool _glfwIsEqualIID(REFIID riid1, REFIID riid2) +{ + return riid1->Data1 == riid2->Data1 + && riid1->Data2 == riid2->Data2 + && riid1->Data3 == riid2->Data3 + && (memcmp(riid1->Data4, riid2->Data4, sizeof(riid1->Data4)) == 0); +} + +// IDropTarget vtable functions +HRESULT STDMETHODCALLTYPE _glfwDropTarget_QueryInterface(IDropTarget *_This, REFIID riid, void **ppvObject) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + printf("DropTarget_QueryInterface\n"); + if(!ppvObject) + return E_POINTER; + + if (_glfwIsEqualIID(riid, &IID_IUnknown) || _glfwIsEqualIID(riid, &IID_IDropTarget)) + { + *ppvObject = This; + This->lpVtbl->AddRef(This); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; +}*/ + +// IDropTarget utility functions +static void getActions(_GLFWdroptarget *This, DWORD dwEffect) +{ + This->availableActions = GLFW_DND_NONE; + This->proposedAction = GLFW_DND_NONE; + if ((dwEffect & DROPEFFECT_COPY) == DROPEFFECT_COPY) + { + This->availableActions |= GLFW_DND_COPY; + if (!This->proposedAction) + This->proposedAction = GLFW_DND_COPY; + } + if ((dwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK) + { + This->availableActions |= GLFW_DND_LINK; + if (!This->proposedAction) + This->proposedAction = GLFW_DND_LINK; + } + if ((dwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE) + { + This->availableActions |= GLFW_DND_MOVE; + if (!This->proposedAction) + This->proposedAction = GLFW_DND_MOVE; + } +} + +static void setActions(_GLFWdroptarget *This, DWORD *pdwEffect) +{ + if((This->chosenAction & GLFW_DND_COPY) == GLFW_DND_COPY) + *pdwEffect = DROPEFFECT_COPY; + else if((This->chosenAction & GLFW_DND_LINK) == GLFW_DND_LINK) + *pdwEffect = DROPEFFECT_LINK; + else if((This->chosenAction & GLFW_DND_MOVE) == GLFW_DND_MOVE) + *pdwEffect = DROPEFFECT_MOVE; + else + *pdwEffect = DROPEFFECT_NONE; +} + +static FORMATETC *withClipFormat(FORMATETC *pFmt, CLIPFORMAT cf) +{ + pFmt->cfFormat = cf; + return pFmt; +} + +// IDropTarget vtable functions +ULONG STDMETHODCALLTYPE _glfwDropTarget_AddRef(IDropTarget *_This) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + printf("DropTarget_AddRef\n"); + return ++This->cRefCount; +} + +ULONG STDMETHODCALLTYPE _glfwDropTarget_Release(IDropTarget *_This) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + printf("DropTarget_Release\n"); + return --This->cRefCount; +} + +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragEnter(IDropTarget *_This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + FORMATETC fmt = { 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stgmed; + + printf("DropTarget_DragEnter\n"); + + getActions(This, *pdwEffect); + + This->availableFormats = GLFW_DND_NONE; + + if (SUCCEEDED(IDataObject_QueryGetData(pDataObj, withClipFormat(&fmt, CF_HDROP))) + && SUCCEEDED(IDataObject_GetData(pDataObj, &fmt, &stgmed))) + { + This->availableFormats |= GLFW_DND_PATHS; + printf("paths\n"); + } + if (SUCCEEDED(IDataObject_QueryGetData(pDataObj, withClipFormat(&fmt, CF_TEXT))) + && SUCCEEDED(IDataObject_GetData(pDataObj, &fmt, &stgmed))) + { + This->availableFormats |= GLFW_DND_TEXT; + printf("text\n"); + ReleaseStgMedium(&stgmed); + } + + This->window->dndDragging = GLFW_TRUE; + + This->chosenFormat = This->availableFormats; + This->chosenAction = This->proposedAction; + _glfwInputDrag(This->window, GLFW_DND_ENTER, pt.x, pt.y, + This->availableFormats, + &This->chosenFormat, + This->availableActions, + &This->chosenAction); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragOver(IDropTarget *_This, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + printf("DropTarget_DragOver\n"); + + getActions(This, *pdwEffect); + + _glfwInputCursorPos(This->window, pt.x, pt.y); + + This->chosenAction = This->proposedAction; + _glfwInputDrag(This->window, GLFW_DND_DRAG, pt.x, pt.y, + This->availableFormats, + &This->chosenFormat, + This->availableActions, + &This->chosenAction); + + setActions(This, pdwEffect); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE _glfwDropTarget_DragLeave(IDropTarget *_This) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + printf("DropTarget_DragLeave\n"); + + This->window->dndDragging = GLFW_FALSE; + + This->chosenFormat = + This->availableFormats = GLFW_DND_NONE; + This->chosenAction = + This->proposedAction = + This->availableActions = GLFW_DND_NONE; + _glfwInputDrag(This->window, GLFW_DND_LEAVE, 0.0, 0.0, + This->availableFormats, + &This->chosenFormat, + This->availableActions, + &This->chosenAction); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE _glfwDropTarget_Drop(IDropTarget *_This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + _GLFWdroptarget *This = (_GLFWdroptarget*) _This; + FORMATETC fmt = { 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stgmed; + + printf("DropTarget_Drop\n"); + *pdwEffect = DROPEFFECT_NONE; + + This->window->dndDragging = GLFW_FALSE; + + if ((This->chosenFormat & GLFW_DND_PATHS) == GLFW_DND_PATHS + && SUCCEEDED(IDataObject_GetData(pDataObj, withClipFormat(&fmt, CF_HDROP), &stgmed))) + { + HDROP drop = (HDROP) GlobalLock(stgmed.hGlobal); + const int count = DragQueryFileW(drop, 0xffffffff, NULL, 0); + char** paths = _glfw_calloc(count, sizeof(char*)); + int i; + + printf("GetData OK (paths)\n"); + _glfwInputCursorPos(This->window, pt.x, pt.y); + + for (i = 0; i < count; ++i) + { + const UINT length = DragQueryFileW(drop, i, NULL, 0); + WCHAR* buffer = _glfw_calloc((size_t) length + 1, sizeof(WCHAR)); + + DragQueryFileW(drop, i, buffer, length + 1); + paths[i] = _glfwCreateUTF8FromWideStringWin32(buffer); + + _glfw_free(buffer); + } + + _glfwInputDrop(This->window, count, (const char**) paths); + + _glfwInputDropEx(This->window, GLFW_DND_PATHS, count, paths, + &This->chosenAction); + + for (i = 0; i < count; ++i) + _glfw_free(paths[i]); + _glfw_free(paths); + } + else if ((This->chosenFormat & GLFW_DND_TEXT) == GLFW_DND_TEXT + && SUCCEEDED(IDataObject_GetData(pDataObj, withClipFormat(&fmt, CF_TEXT), &stgmed))) + { + char *text = (char*) GlobalLock(stgmed.hGlobal); + + printf("GetData OK (text)\n"); + _glfwInputCursorPos(This->window, pt.x, pt.y); + + _glfwInputDropEx(This->window, GLFW_DND_TEXT, -1, text, + &This->chosenAction); + } + else + return S_OK; + + GlobalUnlock(stgmed.hGlobal); + ReleaseStgMedium(&stgmed); + + setActions(This, pdwEffect); + + return S_OK; +} diff --git a/src/window.c b/src/window.c index f740580a..5e384c6e 100644 --- a/src/window.c +++ b/src/window.c @@ -860,6 +860,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) return window->focusOnShow; case GLFW_MOUSE_PASSTHROUGH: return window->mousePassthrough; + case GLFW_DND_DRAGGING: + return window->dndDragging; case GLFW_TRANSPARENT_FRAMEBUFFER: return _glfw.platform.framebufferTransparent(window); case GLFW_RESIZABLE: diff --git a/src/x11_init.c b/src/x11_init.c index 11aeb9e5..36450c17 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -910,11 +910,6 @@ static GLFWbool initExtensions(void) // the keyboard mapping. createKeyTables(); - // String format atoms - _glfw.x11.NULL_ = XInternAtom(_glfw.x11.display, "NULL", False); - _glfw.x11.UTF8_STRING = XInternAtom(_glfw.x11.display, "UTF8_STRING", False); - _glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False); - // Custom selection property atom _glfw.x11.GLFW_SELECTION = XInternAtom(_glfw.x11.display, "GLFW_SELECTION", False); @@ -925,6 +920,7 @@ static GLFWbool initExtensions(void) _glfw.x11.PRIMARY = XInternAtom(_glfw.x11.display, "PRIMARY", False); _glfw.x11.INCR = XInternAtom(_glfw.x11.display, "INCR", False); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False); + _glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False); // Clipboard manager atoms _glfw.x11.CLIPBOARD_MANAGER = @@ -937,11 +933,24 @@ static GLFWbool initExtensions(void) _glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", False); _glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", False); _glfw.x11.XdndStatus = XInternAtom(_glfw.x11.display, "XdndStatus", False); - _glfw.x11.XdndActionCopy = XInternAtom(_glfw.x11.display, "XdndActionCopy", False); + _glfw.x11.XdndLeave = XInternAtom(_glfw.x11.display, "XdndLeave", False); + _glfw.x11.XdndActions[_GLFW_DND_COPY_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionCopy", False); + _glfw.x11.XdndActions[_GLFW_DND_LINK_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionLink", False); + _glfw.x11.XdndActions[_GLFW_DND_MOVE_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionMove", False); _glfw.x11.XdndDrop = XInternAtom(_glfw.x11.display, "XdndDrop", False); _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False); + + // Format atoms (shared by Xdnd and selection) + _glfw.x11.NULL_ = XInternAtom(_glfw.x11.display, "NULL", False); + _glfw.x11.UTF8_STRING = XInternAtom(_glfw.x11.display, "UTF8_STRING", False); + _glfw.x11.STRING = XInternAtom(_glfw.x11.display, "STRING", False); + _glfw.x11.TEXT = XInternAtom(_glfw.x11.display, "TEXT", False); + _glfw.x11.text_plain = XInternAtom(_glfw.x11.display, "text/plain", False); _glfw.x11.text_uri_list = XInternAtom(_glfw.x11.display, "text/uri-list", False); // ICCCM, EWMH and Motif window property atoms diff --git a/src/x11_platform.h b/src/x11_platform.h index ecaa0fa4..8018c706 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -97,6 +97,9 @@ typedef struct __GLXFBConfig* GLXFBConfig; typedef struct __GLXcontext* GLXContext; typedef void (*__GLXextproc)(void); +// TODO for dev; remove +typedef char* (* PFN_XGetAtomName)(Display*,Atom); + typedef XClassHint* (* PFN_XAllocClassHint)(void); typedef XSizeHints* (* PFN_XAllocSizeHints)(void); typedef XWMHints* (* PFN_XAllocWMHints)(void); @@ -622,12 +625,12 @@ typedef struct _GLFWlibraryX11 Atom XdndEnter; Atom XdndPosition; Atom XdndStatus; - Atom XdndActionCopy; + Atom XdndLeave; + Atom XdndActions[_GLFW_DND_ACTION_COUNT]; Atom XdndDrop; Atom XdndFinished; Atom XdndSelection; Atom XdndTypeList; - Atom text_uri_list; // Selection (clipboard) atoms Atom TARGETS; @@ -637,12 +640,17 @@ typedef struct _GLFWlibraryX11 Atom PRIMARY; Atom CLIPBOARD_MANAGER; Atom SAVE_TARGETS; - Atom NULL_; - Atom UTF8_STRING; - Atom COMPOUND_STRING; Atom ATOM_PAIR; Atom GLFW_SELECTION; + // Format atoms (shared by Xdnd and Selection) + Atom NULL_; + Atom UTF8_STRING; + Atom STRING; + Atom TEXT; + Atom text_plain; + Atom text_uri_list; + struct { void* handle; GLFWbool utf8; @@ -800,7 +808,12 @@ typedef struct _GLFWlibraryX11 struct { int version; Window source; - Atom format; + Atom formatAtoms[_GLFW_DND_FORMAT_COUNT]; + int availableFormats; + int chosenFormat; + int availableActions; + int proposedAction; + int chosenAction; } xdnd; struct { diff --git a/src/x11_window.c b/src/x11_window.c index 4f605771..e26efc26 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1534,15 +1534,22 @@ static void processEvent(XEvent *event) // A drag operation has entered the window unsigned long count; Atom* formats = NULL; + double xpos, ypos; const GLFWbool list = event->xclient.data.l[1] & 1; + memset(&_glfw.x11.xdnd, 0, sizeof(_glfw.x11.xdnd)); _glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _glfw.x11.xdnd.format = None; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; + // Xdnd doesn't support the concepts "available actions" and + // "proposed action"; default to all and copy + _glfw.x11.xdnd.availableActions = + GLFW_DND_COPY | GLFW_DND_LINK | GLFW_DND_MOVE; + _glfw.x11.xdnd.proposedAction = GLFW_DND_COPY; + if (list) { count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source, @@ -1556,27 +1563,75 @@ static void processEvent(XEvent *event) formats = (Atom*) event->xclient.data.l + 2; } + // TODO remove + PFN_XGetAtomName GetAtomName = (PFN_XGetAtomName) + _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XGetAtomName"); for (unsigned int i = 0; i < count; i++) { - if (formats[i] == _glfw.x11.text_uri_list) + printf("format (%d): %s\n", i, GetAtomName(_glfw.x11.display, formats[i])); + if (!_glfw.x11.xdnd.formatAtoms[_GLFW_DND_PATHS_INDEX] + && formats[i] == _glfw.x11.text_uri_list) { - _glfw.x11.xdnd.format = _glfw.x11.text_uri_list; - break; + _glfw.x11.xdnd.formatAtoms[_GLFW_DND_PATHS_INDEX] = formats[i]; + _glfw.x11.xdnd.availableFormats |= GLFW_DND_PATHS; + } + else if (!_glfw.x11.xdnd.formatAtoms[_GLFW_DND_TEXT_INDEX] + && (formats[i] == _glfw.x11.text_plain + || formats[i] == _glfw.x11.UTF8_STRING + || formats[i] == _glfw.x11.STRING + || formats[i] == _glfw.x11.TEXT)) + { + _glfw.x11.xdnd.formatAtoms[_GLFW_DND_TEXT_INDEX] = formats[i]; + _glfw.x11.xdnd.availableFormats |= GLFW_DND_TEXT; } } if (list && formats) XFree(formats); + + window->dndDragging = GLFW_TRUE; + + _glfwGetCursorPosX11(window, &xpos, &ypos); + + _glfw.x11.xdnd.chosenFormat = _glfw.x11.xdnd.availableFormats; + _glfw.x11.xdnd.chosenAction = _glfw.x11.xdnd.proposedAction; + _glfwInputDrag(window, GLFW_DND_ENTER, xpos, ypos, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); + } + else if (event->xclient.message_type == _glfw.x11.XdndLeave) + { + // A drag operation has left the window or was canceled + window->dndDragging = GLFW_FALSE; + + _glfw.x11.xdnd.chosenFormat = + _glfw.x11.xdnd.availableFormats = GLFW_DND_NONE; + _glfw.x11.xdnd.chosenAction = + _glfw.x11.xdnd.proposedAction = + _glfw.x11.xdnd.availableActions = GLFW_DND_NONE; + _glfwInputDrag(window, GLFW_DND_LEAVE, 0.0, 0.0, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); } else if (event->xclient.message_type == _glfw.x11.XdndDrop) { // The drag operation has finished by dropping on the window Time time = CurrentTime; + int formatIndex = _glfw_ffs(_glfw.x11.xdnd.chosenFormat + & _GLFW_DND_MASK) - 1; + + window->dndDragging = GLFW_FALSE; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.chosenAction + && formatIndex >= 0 && formatIndex < _GLFW_DND_FORMAT_COUNT + && _glfw.x11.xdnd.formatAtoms[formatIndex]) { if (_glfw.x11.xdnd.version >= 1) time = event->xclient.data.l[2]; @@ -1584,7 +1639,7 @@ static void processEvent(XEvent *event) // Request the chosen format from the source window XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, - _glfw.x11.xdnd.format, + _glfw.x11.xdnd.formatAtoms[formatIndex], _glfw.x11.XdndSelection, window->x11.handle, time); @@ -1609,12 +1664,29 @@ static void processEvent(XEvent *event) // The drag operation has moved over the window const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; const int yabs = (event->xclient.data.l[2]) & 0xffff; + const Atom action = event->xclient.data.l[4]; Window dummy; int xpos, ypos; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; + if (_glfw.x11.xdnd.version >= 2) + { + _glfw.x11.xdnd.proposedAction = GLFW_DND_NONE; + for(int i = 0; i < _GLFW_DND_ACTION_COUNT; ++i) + { + if(action == _glfw.x11.XdndActions[i]) + { + _glfw.x11.xdnd.proposedAction = + (GLFW_DND_COPY & ~_GLFW_DND_MASK) | (1 << i); + break; + } + } + } + else + _glfw.x11.xdnd.proposedAction = GLFW_DND_COPY; + XTranslateCoordinates(_glfw.x11.display, _glfw.x11.root, window->x11.handle, @@ -1624,6 +1696,13 @@ static void processEvent(XEvent *event) _glfwInputCursorPos(window, xpos, ypos); + _glfw.x11.xdnd.chosenAction = _glfw.x11.xdnd.proposedAction; + _glfwInputDrag(window, GLFW_DND_DRAG, xpos, ypos, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); + XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndStatus; @@ -1632,12 +1711,19 @@ static void processEvent(XEvent *event) reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.chosenAction) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle if (_glfw.x11.xdnd.version >= 2) - reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; + { + int actionIndex = _glfw_ffs(_glfw.x11.xdnd.chosenAction + & _GLFW_DND_MASK) - 1; + if(actionIndex >= 0 && actionIndex < _GLFW_DND_ACTION_COUNT) + { + reply.xclient.data.l[4] = _glfw.x11.XdndActions[actionIndex]; + } + } } XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, @@ -1662,14 +1748,38 @@ static void processEvent(XEvent *event) if (result) { - int count; - char** paths = _glfwParseUriList(data, &count); + int formatIndex = _glfw_ffs(_glfw.x11.xdnd.chosenFormat + & _GLFW_DND_MASK) - 1; + if(formatIndex >= 0 && formatIndex < _GLFW_DND_FORMAT_COUNT) + { + int format = (GLFW_DND_TEXT & ~_GLFW_DND_MASK) | (1 << formatIndex); + switch (format) + { + case GLFW_DND_PATHS: + { + int count; + char** paths = _glfwParseUriList(data, &count); - _glfwInputDrop(window, count, (const char**) paths); + _glfwInputDrop(window, count, (const char **) paths); - for (int i = 0; i < count; i++) - _glfw_free(paths[i]); - _glfw_free(paths); + _glfwInputDropEx(window, format, count, paths, + &_glfw.x11.xdnd.chosenAction); + + for (int i = 0; i < count; ++i) + _glfw_free(paths[i]); + _glfw_free(paths); + break; + } + case GLFW_DND_TEXT: + { + _glfwInputDropEx(window, format, -1, data, + &_glfw.x11.xdnd.chosenAction); + break; + } + default: + break; + } + } } if (data) @@ -1683,7 +1793,13 @@ static void processEvent(XEvent *event) reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; + + int actionIndex = _glfw_ffs(_glfw.x11.xdnd.chosenAction + & _GLFW_DND_MASK) - 1; + if(actionIndex >= 0 && actionIndex < _GLFW_DND_ACTION_COUNT) + { + reply.xclient.data.l[2] = _glfw.x11.XdndActions[actionIndex]; + } XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply);