Created
December 26, 2017 12:37
-
-
Save rikkimax/adb22b039be713aef70f2f41a2e43739 to your computer and use it in GitHub Desktop.
drag&drop for dplug:window X11+WinAPI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From 5d08d297b12a2d4374a22e1515f72e68b6f990ba Mon Sep 17 00:00:00 2001 | |
| From: rikki cattermole <alphaglosined@gmail.com> | |
| Date: Mon, 18 Dec 2017 00:40:09 +1300 | |
| Subject: [PATCH] Windows & X11 support for drag&drop | |
| --- | |
| examples/window/main.d | 25 +++- | |
| gui/dplug/gui/graphics.d | 10 ++ | |
| window/dplug/window/win32window.d | 59 ++++++++- | |
| window/dplug/window/window.d | 6 + | |
| window/dplug/window/x11window.d | 272 +++++++++++++++++++++++++++++++++++--- | |
| 5 files changed, 349 insertions(+), 23 deletions(-) | |
| diff --git a/examples/window/main.d b/examples/window/main.d | |
| index 37acebe..142eb10 100644 | |
| --- a/examples/window/main.d | |
| +++ b/examples/window/main.d | |
| @@ -5,6 +5,8 @@ import dplug.window; | |
| import gfm.math.box; | |
| import std.stdio; | |
| +bool stopMe; | |
| + | |
| class WindowListener : IWindowListener { | |
| IWindow window; | |
| ImageRef!RGBA image; | |
| @@ -66,6 +68,10 @@ class WindowListener : IWindowListener { | |
| assumeNothrowNoGC(&func)(key); | |
| + if (key == Key.escape) { | |
| + stopMe = true; | |
| + } | |
| + | |
| return true; | |
| } | |
| @@ -78,6 +84,16 @@ class WindowListener : IWindowListener { | |
| } | |
| } | |
| } | |
| + | |
| + void onDragDrop(scope string filename, int x, int y) { | |
| + static void func(scope string filename, int x, int y) { | |
| + writeln("On Drag and drop operation as ", x, "x", y, " for ", filename); | |
| + } | |
| + | |
| + assumeNothrowNoGC(&func)(filename, x, y); | |
| + } | |
| + | |
| + bool supportsDragAndDrop() { return true; } | |
| ImageRef!RGBA onResized(int width, int height) { | |
| if (image.pixels !is null) { | |
| @@ -108,11 +124,14 @@ class WindowListener : IWindowListener { | |
| void main() { | |
| writeln("Hi!"); | |
| - auto listener = mallocEmplace!WindowListener; | |
| + auto listener = mallocNew!WindowListener; | |
| IWindow window = createWindow(null, null, listener, WindowBackend.autodetect, 800, 600); | |
| listener.window = window; | |
| - window.waitEventAndDispatch; | |
| - | |
| + | |
| + while(!window.terminated() && !stopMe) { | |
| + window.waitEventAndDispatch; | |
| + } | |
| + | |
| writeln("END"); | |
| } | |
| diff --git a/gui/dplug/gui/graphics.d b/gui/dplug/gui/graphics.d | |
| index b58fd84..72c46c7 100644 | |
| --- a/gui/dplug/gui/graphics.d | |
| +++ b/gui/dplug/gui/graphics.d | |
| @@ -262,6 +262,16 @@ nothrow: | |
| return outer._areasToRender[].boundingBox(); | |
| } | |
| + override void onDragDrop(scope string filename,int x,int y) nothrow @nogc | |
| + { | |
| + } | |
| + | |
| + | |
| + override bool supportsDragAndDrop() nothrow @nogc | |
| + { | |
| + return false; | |
| + } | |
| + | |
| override ImageRef!RGBA onResized(int width, int height) | |
| { | |
| return outer.doResize(width, height); | |
| diff --git a/window/dplug/window/win32window.d b/window/dplug/window/win32window.d | |
| index a67e9e9..b2fcc67 100644 | |
| --- a/window/dplug/window/win32window.d | |
| +++ b/window/dplug/window/win32window.d | |
| @@ -47,7 +47,7 @@ version(Windows) | |
| import core.sys.windows.winuser; | |
| import core.sys.windows.winbase; | |
| import core.sys.windows.wingdi; | |
| - | |
| + import core.sys.windows.shellapi; | |
| HINSTANCE getModuleHandle() nothrow @nogc | |
| { | |
| @@ -108,6 +108,8 @@ version(Windows) | |
| int mSec = 15; // refresh at 60 hz if possible | |
| SetTimer(_hwnd, TIMER_ID, mSec, null); | |
| + | |
| + DragAcceptFiles(_hwnd, _listener.supportsDragAndDrop()); | |
| } | |
| SetFocus(_hwnd); | |
| @@ -326,6 +328,61 @@ version(Windows) | |
| return DefWindowProcA(hwnd, uMsg, wParam, lParam); | |
| } | |
| + case WM_DROPFILES: | |
| + { | |
| + import std.utf : byChar, codeLength; | |
| + | |
| + if (_listener.supportsDragAndDrop()) | |
| + { | |
| + HDROP hdrop = cast(HDROP)wParam; | |
| + POINT point; | |
| + DragQueryPoint(hdrop, &point); | |
| + | |
| + wchar[] buffer1 = mallocSlice!wchar(256); | |
| + char[] buffer2 = mallocSlice!char(256); | |
| + size_t count, len1, len2; | |
| + | |
| + while((len1 = DragQueryFileW(hdrop, count, null, 0)) != 0) | |
| + { | |
| + if (buffer1.length < len1) | |
| + { | |
| + freeSlice(buffer1); | |
| + buffer1 = mallocSlice!wchar(len1); | |
| + } | |
| + | |
| + DragQueryFileW(hdrop, count++, buffer1.ptr, buffer1.length); | |
| + | |
| + try { | |
| + len2 = codeLength!char(buffer1[0 .. len1]); | |
| + } catch (Exception e) { | |
| + continue; | |
| + } | |
| + | |
| + if (buffer2.length < len2) | |
| + { | |
| + freeSlice(buffer2); | |
| + buffer2 = mallocSlice!char(len2); | |
| + } | |
| + | |
| + size_t offset; | |
| + foreach(c; buffer1[0 .. len1].byChar) | |
| + { | |
| + buffer2[offset++] = c; | |
| + } | |
| + | |
| + _listener.onDragDrop(cast(string)buffer2[0 .. len2], point.x, point.y); | |
| + } | |
| + | |
| + freeSlice(buffer1); | |
| + freeSlice(buffer2); | |
| + | |
| + DragFinish(hdrop); | |
| + return 0; | |
| + } | |
| + else | |
| + return DefWindowProcA(hwnd, uMsg, wParam, lParam); | |
| + } | |
| + | |
| default: | |
| return DefWindowProcA(hwnd, uMsg, wParam, lParam); | |
| } | |
| diff --git a/window/dplug/window/window.d b/window/dplug/window/window.d | |
| index 102e35f..016f400 100644 | |
| --- a/window/dplug/window/window.d | |
| +++ b/window/dplug/window/window.d | |
| @@ -115,6 +115,12 @@ nothrow @nogc: | |
| /// The pixel format cannot change over the lifetime of the window. | |
| void onDraw(WindowPixelFormat pf); | |
| + /// Called when a drag and drop operation completes | |
| + void onDragDrop(scope string filename, int x, int y); | |
| + | |
| + /// Is drag and drop operations supported? | |
| + bool supportsDragAndDrop(); | |
| + | |
| /// The drawing area size has changed. | |
| /// Always called at least once before onDraw. | |
| /// Returns: the location of the full rendered framebuffer. | |
| diff --git a/window/dplug/window/x11window.d b/window/dplug/window/x11window.d | |
| index 7499c2b..0094f00 100644 | |
| --- a/window/dplug/window/x11window.d | |
| +++ b/window/dplug/window/x11window.d | |
| @@ -40,6 +40,8 @@ import derelict.x11.Xutil; | |
| import derelict.x11.extensions.Xrandr; | |
| import derelict.x11.extensions.randr; | |
| +import core.stdc.string : strlen; | |
| + | |
| // TODO: remove data races with the globals | |
| // TODO: check with multiple instances | |
| @@ -53,6 +55,22 @@ __gshared int _screen; // TODO: could be made a field with | |
| // Reverse mapping | |
| __gshared Map!(Window, X11Window) x11WindowMapping; | |
| +// XDND related Atoms | |
| +__gshared Atom | |
| + XdndEnter, | |
| + XdndPosition, | |
| + XdndStatus, | |
| + XdndTypeList, | |
| + XdndActionCopy, | |
| + XdndDrop, | |
| + XdndLeave, | |
| + XdndFinished, | |
| + XdndSelection, | |
| + XdndProxy, | |
| + XA_ATOM, | |
| + XA_TARGETS; | |
| + | |
| + | |
| // This is an extension to X11, almost always should exist on modern systems | |
| // If it becomes a problem, version out its usage, it'll work just won't be as nice event wise | |
| extern(C) bool XkbSetDetectableAutoRepeat(Display*, bool, bool*); | |
| @@ -74,8 +92,23 @@ nothrow: | |
| _black_pixel = assumeNoGC(&BlackPixel)(_display, _screen); | |
| assumeNoGC(&XkbSetDetectableAutoRepeat)(_display, true, null); | |
| - if(_display == null) | |
| - assert(false); | |
| + if(_display == null) | |
| + assert(false); | |
| + | |
| + XdndEnter = assumeNoGC(&XInternAtom)(_display, "XdndEnter".ptr, false); | |
| + XdndPosition = assumeNoGC(&XInternAtom)(_display, "XdndPosition".ptr, false); | |
| + XdndStatus = assumeNoGC(&XInternAtom)(_display, "XdndStatus".ptr, false); | |
| + XdndTypeList = assumeNoGC(&XInternAtom)(_display, "XdndTypeList".ptr, false); | |
| + XdndActionCopy = assumeNoGC(&XInternAtom)(_display, "XdndActionCopy".ptr, false); | |
| + XdndDrop = assumeNoGC(&XInternAtom)(_display, "XdndDrop".ptr, false); | |
| + XdndLeave = assumeNoGC(&XInternAtom)(_display, "XdndLeave".ptr, false); | |
| + XdndFinished = assumeNoGC(&XInternAtom)(_display, "XdndFinished".ptr, false); | |
| + XdndSelection = assumeNoGC(&XInternAtom)(_display, "XdndSelection".ptr, false); | |
| + XdndProxy = assumeNoGC(&XInternAtom)(_display, "XdndProxy".ptr, false); | |
| + XA_ATOM = assumeNoGC(&XInternAtom)(_display, "ATOM".ptr, false); | |
| + XA_TARGETS = assumeNoGC(&XInternAtom)(_display, "TARGETS".ptr, false); | |
| + | |
| + xdndBufferSelector = assumeNoGC(&XInternAtom)(_display, "PRIMARY".ptr, false); | |
| if (parentWindow is null) | |
| { | |
| @@ -113,11 +146,24 @@ nothrow: | |
| // | |
| - _closeAtom = assumeNoGC(&XInternAtom)(_display, cast(char*)("WM_DELETE_WINDOW".ptr), cast(Bool)false); | |
| - assumeNoGC(&XSetWMProtocols)(_display, _windowId, &_closeAtom, 1); | |
| + if (listener !is null && listener.supportsDragAndDrop()) | |
| + { | |
| + supportsXDND = true; | |
| - assumeNoGC(&XMapWindow)(_display, _windowId); | |
| - assumeNoGC(&XFlush)(_display); | |
| + Atom XdndAware = assumeNoGC(&XInternAtom)(_display, "XdndAware".ptr, false); | |
| + Atom version_ = 5; | |
| + assumeNoGC(&XChangeProperty)(_display, _windowId, XdndAware, XA_ATOM, 32, PropModeReplace, cast(ubyte*)&version_, 1); | |
| + } | |
| + | |
| + // | |
| + | |
| + _closeAtom = assumeNoGC(&XInternAtom)(_display, cast(char*)("WM_DELETE_WINDOW".ptr), cast(Bool)false); | |
| + assumeNoGC(&XSetWMProtocols)(_display, _windowId, &_closeAtom, 1); | |
| + | |
| + assumeNoGC(&XMapWindow)(_display, _windowId); | |
| + assumeNoGC(&XFlush)(_display); | |
| + | |
| + // | |
| assumeNoGC(&XSelectInput)(_display, _windowId, ExposureMask | KeyPressMask | StructureNotifyMask | | |
| KeyReleaseMask | KeyPressMask | ButtonReleaseMask | ButtonPressMask | PointerMotionMask); | |
| @@ -139,8 +185,10 @@ nothrow: | |
| creationTime = getTimeMs(); | |
| lastTimeGot = creationTime; | |
| - _eventLoop = makeThread(&asyncEventHandling); | |
| - _eventLoop.start(); | |
| + version(none) { | |
| + _eventLoop = makeThread(&asyncEventHandling); | |
| + _eventLoop.start(); | |
| + } | |
| } | |
| ~this() | |
| @@ -209,7 +257,7 @@ nothrow: | |
| // Implements IWindow | |
| override void waitEventAndDispatch() | |
| { | |
| - while(assumeNoGC(&XPending)(_display)) | |
| + if (assumeNoGC(&XPending)(_display)) | |
| { | |
| XEvent event; | |
| assumeNoGC(&XNextEvent)(_display, &event); | |
| @@ -219,7 +267,7 @@ nothrow: | |
| if (theWindow is null) | |
| { | |
| // well hello, I didn't expect this.. goodbye | |
| - continue; | |
| + return; | |
| } | |
| handleEvents(event, theWindow); | |
| @@ -307,6 +355,10 @@ private: | |
| int lastMouseX, lastMouseY; | |
| Thread _eventLoop; | |
| + | |
| + bool supportsXDND; | |
| + Atom xdndToBeRequested, xdndBufferSelector; | |
| + Window xdndSourceWindow; | |
| } | |
| void handleEvents(ref XEvent event, X11Window theWindow) | |
| @@ -450,17 +502,150 @@ void handleEvents(ref XEvent event, X11Window theWindow) | |
| break; | |
| case ClientMessage: | |
| - if (event.xclient.data.l[0] == _closeAtom) | |
| - { | |
| - _terminated = true; | |
| - x11WindowMapping.remove(_windowId); | |
| - assumeNoGC(&XDestroyImage)(_graphicImage); | |
| - assumeNoGC(&XFreeGC)(_display, _graphicGC); | |
| - assumeNoGC(&XDestroyWindow)(_display, _windowId); | |
| - assumeNoGC(&XFlush)(_display); | |
| - } | |
| + if (event.xclient.message_type == _closeAtom) | |
| + { | |
| + _terminated = true; | |
| + x11WindowMapping.remove(_windowId); | |
| + assumeNoGC(&XDestroyImage)(_graphicImage); | |
| + assumeNoGC(&XFreeGC)(_display, _graphicGC); | |
| + assumeNoGC(&XDestroyWindow)(_display, _windowId); | |
| + assumeNoGC(&XFlush)(_display); | |
| + } | |
| + else if (!supportsXDND) | |
| + { | |
| + } | |
| + else if (event.xclient.message_type == XdndEnter) | |
| + { | |
| + bool moreThanThreeTypes = (event.xclient.data.l[1] & 1) == 1; | |
| + xdndSourceWindow = cast(Window)event.xclient.data.l[0]; | |
| + xdndToBeRequested = None; | |
| + | |
| + if (moreThanThreeTypes) | |
| + { | |
| + WindowProperty property = readWindowProperty(_display, xdndSourceWindow, XdndTypeList); | |
| + | |
| + if (property.type == XA_ATOM) | |
| + { | |
| + xdndToBeRequested = chooseAtomXDND(_display, (cast(Atom*)property.data)[0 .. property.numberOfItems]); | |
| + } | |
| + XFree(property.data); | |
| + } | |
| + else | |
| + { | |
| + Atom[3] listOfAtoms = [cast(Atom)event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]]; | |
| + xdndToBeRequested = chooseAtomXDND(_display, listOfAtoms[]); | |
| + } | |
| + } | |
| + else if (event.xclient.message_type == XdndPosition) | |
| + { | |
| + XClientMessageEvent message; | |
| + message.type = ClientMessage; | |
| + message.display = event.xclient.display; | |
| + message.window = event.xclient.data.l[0]; | |
| + message.message_type = XdndStatus; | |
| + message.format = 32; | |
| + message.data.l[0] = _windowId; | |
| + message.data.l[1] = xdndToBeRequested != None; | |
| + message.data.l[4] = XdndActionCopy; | |
| + | |
| + XSendEvent(_display, event.xclient.data.l[0], false, NoEventMask, cast(XEvent*)&message); | |
| + XFlush(_display); | |
| + } | |
| + else if (event.xclient.message_type == XdndLeave) | |
| + { | |
| + } | |
| + else if (event.xclient.message_type == XdndDrop) | |
| + { | |
| + if (xdndToBeRequested == None) | |
| + { | |
| + XClientMessageEvent message; | |
| + message.type = ClientMessage; | |
| + message.display = event.xclient.display; | |
| + message.window = event.xclient.data.l[0]; | |
| + message.message_type = XdndFinished; | |
| + message.format = 32; | |
| + message.data.l[0] = _windowId; | |
| + message.data.l[2] = None; | |
| + | |
| + XSendEvent(_display, event.xclient.data.l[0], false, NoEventMask, cast(XEvent*)&message); | |
| + } | |
| + else | |
| + { | |
| + XConvertSelection(_display, XdndSelection, xdndToBeRequested, xdndBufferSelector, _windowId, event.xclient.data.l[2]); | |
| + } | |
| + } | |
| break; | |
| + case SelectionNotify: | |
| + if (event.xselection.property == None) | |
| + break; | |
| + else if (!supportsXDND) | |
| + break; | |
| + | |
| + Atom target = event.xselection.target; | |
| + WindowProperty property = readWindowProperty(_display, _windowId, xdndBufferSelector); | |
| + | |
| + if (target == XA_TARGETS) | |
| + { | |
| + WindowProperty propertyTL = readWindowProperty(_display, xdndSourceWindow, XdndTypeList); | |
| + | |
| + if (propertyTL.type == XA_ATOM) | |
| + { | |
| + xdndToBeRequested = chooseAtomXDND(_display, (cast(Atom*)propertyTL.data)[0 .. propertyTL.numberOfItems]); | |
| + if (xdndToBeRequested != None) | |
| + { | |
| + XConvertSelection(_display, XdndSelection, xdndToBeRequested, xdndBufferSelector, _windowId, event.xclient.data.l[2]); | |
| + } | |
| + } | |
| + XFree(propertyTL.data); | |
| + } | |
| + else if (target == xdndToBeRequested) | |
| + { | |
| + char* str = cast(char*)property.data; | |
| + string text = cast(string)str[0 .. strlen(str)]; | |
| + | |
| + Window queryPointer1, queryPointer2; | |
| + int x, y, queryPointer3, queryPointer4; | |
| + uint queryPointer5; | |
| + XQueryPointer(_display, _windowId, &queryPointer1, &queryPointer2, &queryPointer3, &queryPointer4, &x, &y, &queryPointer5); | |
| + | |
| + size_t start; | |
| + foreach(i, c; text) | |
| + { | |
| + if (c == '\n') | |
| + { | |
| + listener.onDragDrop(text[start .. i], x, y); | |
| + start = i + 1; | |
| + } | |
| + } | |
| + | |
| + if (start < text.length) | |
| + { | |
| + listener.onDragDrop(text[start .. $], x, y); | |
| + } | |
| + | |
| + XClientMessageEvent message; | |
| + message.type = ClientMessage; | |
| + message.display = _display; | |
| + message.window = xdndSourceWindow; | |
| + message.message_type = XdndFinished; | |
| + message.format = 32; | |
| + message.data.l[0] = _windowId; | |
| + message.data.l[1] = 1; | |
| + message.data.l[2] = XdndActionCopy; | |
| + | |
| + XSendEvent(_display, xdndSourceWindow, false, NoEventMask, cast(XEvent*)&message); | |
| + | |
| + XDeleteProperty(_display, _windowId, xdndBufferSelector); | |
| + XSync(_display, false); | |
| + } | |
| + | |
| + if (property.data !is null) | |
| + { | |
| + XFree(property.data); | |
| + } | |
| + break; | |
| + | |
| default: | |
| break; | |
| } | |
| @@ -515,3 +700,52 @@ MouseState mouseStateFromX11(uint state) { | |
| (state & Mod1Mask) == Mod1Mask); | |
| } | |
| +struct WindowProperty { | |
| + import core.stdc.config : c_ulong; | |
| + Atom type; | |
| + int format; | |
| + c_ulong numberOfItems; | |
| + ubyte* data; | |
| +} | |
| + | |
| +WindowProperty readWindowProperty(Display* display, Window window, Atom property) nothrow @nogc | |
| +{ | |
| + import core.stdc.config : c_ulong; | |
| + | |
| + c_ulong readByteCount = 1024; | |
| + WindowProperty ret; | |
| + | |
| + while(readByteCount > 0) | |
| + { | |
| + assumeNothrowNoGC(&XGetWindowProperty)(display, window, property, 0, readByteCount, false, AnyPropertyType, &ret.type, | |
| + &ret.format, &ret.numberOfItems, &readByteCount, &ret.data); | |
| + } | |
| + | |
| + return ret; | |
| +} | |
| + | |
| +Atom chooseAtomXDND(Display* display, Atom[] atoms) | |
| +{ | |
| + Atom ret = None; | |
| + | |
| + foreach(atom; atoms) | |
| + { | |
| + char* str = XGetAtomName(display, atom); | |
| + string name = cast(string)str[0 .. strlen(str)]; | |
| + | |
| + if (name == "UTF8_STRING") | |
| + { | |
| + return atom; | |
| + } | |
| + else if (name == "text/plain;charset=utf-8") | |
| + { | |
| + return atom; | |
| + } | |
| + else if (ret == None && name == "text/plain") | |
| + { | |
| + ret = atom; | |
| + } | |
| + } | |
| + | |
| + return ret; | |
| +} | |
| \ No newline at end of file | |
| -- | |
| 2.12.2.windows.2 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment