diff --git a/examples/basic/dnd.c b/examples/basic/dnd.c index 0a2495e..8d0f47d 100644 --- a/examples/basic/dnd.c +++ b/examples/basic/dnd.c @@ -1,6 +1,6 @@ #include -MwWidget window, instructions, text1, cur_text; +MwWidget window, box, instructions, cur_text, text_listbox; static void MWAPI resize(MwWidget handle, void* user_data, void* call_data) { unsigned int w, h; @@ -11,17 +11,7 @@ static void MWAPI resize(MwWidget handle, void* user_data, void* call_data) { w = MwGetInteger(handle, MwNwidth); h = MwGetInteger(handle, MwNheight); - MwVaApply(instructions, - MwNy, 50, - MwNwidth, w - 50 * 2, - MwNheight, h - 125 - 50 * 3, - NULL); - - MwVaApply(text1, - MwNy, 150, - MwNwidth, w - 25 * 2, - MwNheight, h - 125 - 50 * 3, - NULL); + MwVaApply(box, MwNwidth, w, MwNheight, h, NULL); } static void MWAPI dnd(MwWidget handle, void* user_data, void* call_data) { char* filename = call_data; @@ -30,29 +20,35 @@ static void MWAPI dnd(MwWidget handle, void* user_data, void* call_data) { (void)user_data; if(filename != NULL) { - MwVaApply(cur_text, MwNtext, filename, NULL); - MwForceRender(cur_text); + MwListBoxSet(text_listbox, -1, -1, filename); + MwForceRender(text_listbox); } } int main() { - MwLibraryInit(); + MwSizeHints hints; - window = MwVaCreateWidget(MwWindowClass, "main", NULL, MwDEFAULT, MwDEFAULT, 400, 400, - MwNtitle, "dnd", + MwLibraryInit(); + hints.min_width = hints.min_height = 600; + + window = MwVaCreateWidget(MwWindowClass, "main", NULL, MwDEFAULT, MwDEFAULT, 600, 600, + MwNtitle, "dnd", + MwNsizeHints, &hints, + NULL); + + box = MwVaCreateWidget(MwBoxClass, NULL, window, 0, 0, 800, 800, MwNorientation, MwVERTICAL, MwNpadding, 25, NULL); + + instructions = MwVaCreateWidget(MwLabelClass, "button", box, 50, 50, 750, 50, + MwNtext, "drag files onto this text box, and their names will show up below.", MwNacceptsDnD, 1, NULL); - instructions = MwVaCreateWidget(MwLabelClass, "button", window, 50, 50, 300, 125, - MwNtext, "drag a file and its name will show up below.", - NULL); - text1 = MwVaCreateWidget(MwLabelClass, "label", window, 25, 150, 300, 75, + text_listbox = MwVaCreateWidget(MwListBoxClass, "label", box, 25, 150, 750, 700, MwNtext, "", + MwNacceptsDnD, 1, NULL); - cur_text = text1; - MwAddUserHandler(window, MwNresizeHandler, resize, NULL); - MwAddUserHandler(window, MwNdragAndDropHandler, dnd, NULL); + MwAddUserHandler(instructions, MwNdragAndDropHandler, dnd, NULL); resize(window, NULL, NULL); diff --git a/include/Mw/LowLevel/Wayland.h b/include/Mw/LowLevel/Wayland.h index 44a3983..7a2c25c 100644 --- a/include/Mw/LowLevel/Wayland.h +++ b/include/Mw/LowLevel/Wayland.h @@ -456,6 +456,8 @@ struct _MwLLWayland { MwU64 next_elapsed; long start_time; long end_time; + + MwBool accepts_dnd; }; struct _MwLLWaylandColor { diff --git a/include/Mw/LowLevel/X11.h b/include/Mw/LowLevel/X11.h index e1b7416..9663097 100644 --- a/include/Mw/LowLevel/X11.h +++ b/include/Mw/LowLevel/X11.h @@ -27,20 +27,36 @@ struct _MwLLX11 { unsigned int width; unsigned int height; - Display* display; - Window window; - Pixmap pixmap; - GC gc; - Colormap colormap; - Atom wm_delete; - Atom wm_protocols; - Atom utf8_string; - Atom compound_text; - Atom text; - Atom clipboard; - Atom selection; - XIM xim; - XIC xic; + Display* display; + Window window; + Pixmap pixmap; + GC gc; + Colormap colormap; + Atom wm_delete; + Atom wm_protocols; + Atom utf8_string; + Atom compound_text; + Atom text; + Atom clipboard; + Atom selection; + Atom xdnd_type_list; + Atom xdnd_selection; + Atom xdnd_enter; + Atom xdnd_position; + Atom xdnd_status; + Atom xdnd_leave; + Atom xdnd_drop; + Atom xdnd_finished; + Atom xdnd_action_copy; + Atom xdnd_text_uri_list; + Atom xdnd_text_plain; + Atom xdnd_aware; + int xdnd_source; + unsigned char xdnd_version; + int xdnd_format; + + XIM xim; + XIC xic; long clipboard_time; int clipboard_pending; /* 1 if UTF8_STRING, 2 if COMPOUND_TEXT, 3 if TEXT, 4 if STRING, otherwise 0 */ diff --git a/src/backend/wayland/interfaces.c b/src/backend/wayland/interfaces.c index eaabf16..dd58f9a 100644 --- a/src/backend/wayland/interfaces.c +++ b/src/backend/wayland/interfaces.c @@ -19,6 +19,7 @@ static void setup_zwp_clipboard(MwLL self, struct wl_seat* wl_seat); static void destroy_clipboard(MwLL self, struct wl_seat* wl_seat); static void destroy_zwp_clipboard(MwLL self, struct wl_seat* wl_seat); +static bool hit_detect(MwLL child, MwLL* _topmost_parent, MwPoint* _point, MwPoint* _relative_mouse_pos, MwPoint* _absolute_pos); /* Recursively dispatch a key event to a widget and its children */ static void recursive_dispatch_key(MwLL handle, int* k) { @@ -168,6 +169,57 @@ static void wl_data_device_leave(void* data, self->offer.wl = NULL; } }; + +static MwBool motion_cursor_change(MwLL self, wl_clipboard_device_context_t* ctx) { + if(self->wayland.type == MwLL_WAYLAND_TOPLEVEL && self->wayland.accepts_dnd) { + return MwTRUE; + } else if(self->common.user) { + MwWidget w = self->common.user; + int i, n; + for(i = 0; i < arrlen(w->children); i++) { + if(w->children[i]->lowlevel->wayland.type == MwLL_WAYLAND_SUBLEVEL) { + MwLL child = w->children[i]->lowlevel; + MwLL topmost_parent = child; + MwPoint point; + MwPoint relative_mouse_pos; + MwPoint absolute_pos; + + if(hit_detect(child, &topmost_parent, &point, &relative_mouse_pos, &absolute_pos)) { + return MwTRUE; + } else { + return motion_cursor_change(child, ctx); + } + } + } + } + return MwFALSE; +} + +static void clipboard_dispatch(MwLL self, char* buf) { + if(self->wayland.type == MwLL_WAYLAND_TOPLEVEL && self->wayland.accepts_dnd) { + MwLLDispatch(self, drag_and_drop, buf); + } else if(self->common.user) { + MwWidget w = self->common.user; + int i, n; + for(i = 0; i < arrlen(w->children); i++) { + if(w->children[i]->lowlevel->wayland.type == MwLL_WAYLAND_SUBLEVEL) { + MwLL child = w->children[i]->lowlevel; + MwLL topmost_parent = child; + MwPoint point; + MwPoint relative_mouse_pos; + MwPoint absolute_pos; + + if(hit_detect(child, &topmost_parent, &point, &relative_mouse_pos, &absolute_pos) && child->wayland.accepts_dnd) { + MwLLDispatch(child, drag_and_drop, buf); + return; + } else { + clipboard_dispatch(child, buf); + } + } + } + } +} + static void wl_data_device_motion(void* data, struct wl_data_device* wl_data_device, uint32_t time, @@ -179,8 +231,15 @@ static void wl_data_device_motion(void* data, (void)y; wl_clipboard_device_context_t* self = data; + self->ll->wayland.cur_mouse_pos.x = wl_fixed_to_int(x); + self->ll->wayland.cur_mouse_pos.y = wl_fixed_to_int(y); + if(self->selected_mime_type[0] != '\0' && self->offer.wl) { - wl_data_offer_set_actions(self->offer.wl, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + if(motion_cursor_change(self->ll, self)) { + wl_data_offer_set_actions(self->offer.wl, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + } else { + wl_data_offer_set_actions(self->offer.wl, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); + }; } }; static void wl_data_device_drop(void* data, @@ -193,7 +252,6 @@ static void wl_data_device_drop(void* data, int rc = 0; if(pipe(fds) != 0) { - return; } @@ -226,10 +284,20 @@ static void wl_data_device_drop(void* data, setup_clipboard(self->ll, self->ll->wayland.pointer_seat); - if(strstr(buf, "file://")) { - MwLLDispatch(self->ll, drag_and_drop, buf + sizeof("file://") - 1); - } else { - MwLLDispatch(self->ll, drag_and_drop, buf); + char* buf_ptr = buf; + while(buf_ptr) { + char* next = strstr(buf_ptr, "\n"); + if(next) { + *next = '\0'; + if(strstr(buf_ptr, "file://")) { + clipboard_dispatch(self->ll, buf_ptr + sizeof("file://") - 1); + } else { + clipboard_dispatch(self->ll, buf_ptr); + } + buf_ptr = next + 1; + } else { + buf_ptr = NULL; + } } arrfree(buf); diff --git a/src/backend/wayland/wayland.c b/src/backend/wayland/wayland.c index 6154664..bd17f84 100644 --- a/src/backend/wayland/wayland.c +++ b/src/backend/wayland/wayland.c @@ -1794,7 +1794,6 @@ static void MwLLGetCursorCoordImpl(MwLL handle, MwPoint* point) { } static void MwLLGetScreenSizeImpl(MwLL handle, MwRect* rect) { - rect->x = 0; rect->y = 0; rect->width = handle->wayland.mw; @@ -1803,6 +1802,7 @@ static void MwLLGetScreenSizeImpl(MwLL handle, MwRect* rect) { static void MwLLSetupDragAndDropImpl(MwLL handle) { (void)handle; + handle->wayland.accepts_dnd = MwTRUE; } static void MwLLBeginStateChangeImpl(MwLL handle) { diff --git a/src/backend/x11.c b/src/backend/x11.c index 8b908e4..eaa64e0 100644 --- a/src/backend/x11.c +++ b/src/backend/x11.c @@ -82,6 +82,8 @@ static struct symtbl { void (*XSetICFocus)(XIC); XImage* (*XGetImage)(Display*, Drawable, int, int, unsigned int, unsigned int, unsigned long, int); int (*XRaiseWindow)(Display*, Window); + int (*XFlush)(Display*); + char* (*XGetAtomName)(Display*, Atom); XSizeHints* (*XAllocSizeHints)(void); XVisualInfo* (*XGetVisualInfo)(Display*, long, XVisualInfo*, int*); @@ -186,6 +188,8 @@ static struct symtbl { #define XChangeWindowAttributes xsymtbl.XChangeWindowAttributes #define XGetImage xsymtbl.XGetImage #define XRaiseWindow xsymtbl.XRaiseWindow +#define XFlush xsymtbl.XFlush +#define XGetAtomName xsymtbl.XGetAtomName #if USE_XRENDER #define XRenderFindStandardFormat xsymtbl.XRenderFindStandardFormat @@ -425,6 +429,7 @@ static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { r->x11.colormap = DefaultColormap(r->x11.display, XDefaultScreen(r->x11.display)); r->x11.wm_delete = XInternAtom(r->x11.display, "WM_DELETE_WINDOW", False); r->x11.wm_protocols = XInternAtom(r->x11.display, "WM_PROTOCOLS", False); + XSetWMProtocols(r->x11.display, r->x11.window, &r->x11.wm_delete, 1); r->x11.utf8_string = XInternAtom(r->x11.display, "UTF8_STRING", False); @@ -433,6 +438,21 @@ static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { r->x11.clipboard = XInternAtom(r->x11.display, "CLIPBOARD", False); r->x11.selection = XInternAtom(r->x11.display, "_MILSKO_SELECTION_", False); + r->x11.xdnd_type_list = XInternAtom(r->x11.display, "XdndTypeList", False); + r->x11.xdnd_selection = XInternAtom(r->x11.display, "XdndSelection", False); + r->x11.xdnd_enter = XInternAtom(r->x11.display, "XdndEnter", False); + r->x11.xdnd_position = XInternAtom(r->x11.display, "XdndPosition", False); + r->x11.xdnd_status = XInternAtom(r->x11.display, "XdndStatus", False); + r->x11.xdnd_leave = XInternAtom(r->x11.display, "XdndLeave", False); + r->x11.xdnd_drop = XInternAtom(r->x11.display, "XdndDrop", False); + r->x11.xdnd_finished = XInternAtom(r->x11.display, "XdndFinished", False); + r->x11.xdnd_action_copy = XInternAtom(r->x11.display, "XdndActionCopy", False); + r->x11.xdnd_text_uri_list = XInternAtom(r->x11.display, "text/uri-list", False); + r->x11.xdnd_text_plain = XInternAtom(r->x11.display, "text/plain", False); + r->x11.xdnd_aware = XInternAtom(r->x11.display, "XdndAware", False); + r->x11.xdnd_version = 5; + r->x11.xdnd_format = None; + r->x11.clipboard_pending = 0; r->x11.gc = XCreateGC(r->x11.display, r->x11.window, 0, NULL); @@ -704,10 +724,15 @@ static void MwLLNextEventImpl(MwLL handle) { while(XCheckTypedWindowEvent(handle->x11.display, handle->x11.window, ClientMessage, &ev) || XCheckTypedWindowEvent(handle->x11.display, handle->x11.window, SelectionNotify, &ev) || XCheckWindowEvent(handle->x11.display, handle->x11.window, mask, &ev)) { int render = 0; - if(ev.type == Expose) { + switch(ev.type) { + case Expose: + { handle->x11.force_render = 0; render = 1; - } else if(ev.type == ButtonPress) { + break; + }; + case ButtonPress: + { MwMouse p; p.point.x = ev.xbutton.x; p.point.y = ev.xbutton.y; @@ -728,7 +753,10 @@ static void MwLLNextEventImpl(MwLL handle) { #endif MwLLDispatch(handle, down, &p); - } else if(ev.type == ButtonRelease) { + break; + }; + case ButtonRelease: + { MwMouse p; p.point.x = ev.xbutton.x; p.point.y = ev.xbutton.y; @@ -745,24 +773,122 @@ static void MwLLNextEventImpl(MwLL handle) { } MwLLDispatch(handle, up, &p); - } else if(ev.type == ConfigureNotify) { + break; + }; + case ConfigureNotify: + { if(handle->x11.width != (unsigned int)ev.xconfigure.width || handle->x11.height != (unsigned int)ev.xconfigure.height) { MwLLDispatch(handle, resize, NULL); destroy_pixmap(handle); create_pixmap(handle); render = 1; + break; } handle->x11.width = ev.xconfigure.width; handle->x11.height = ev.xconfigure.height; - } else if(ev.type == ClientMessage) { + }; + case ClientMessage: + { + if(ev.xclient.message_type == handle->x11.wm_protocols && ev.xclient.data.l[0] == (long)handle->x11.wm_delete) { MwLLDispatch(handle, close, NULL); } - } else if(ev.type == FocusIn) { + + if( + ev.xclient.message_type == handle->x11.xdnd_enter || + ev.xclient.message_type == handle->x11.xdnd_position || + ev.xclient.message_type == handle->x11.xdnd_drop) { + char* name = XGetAtomName(handle->x11.display, ev.xclient.message_type); + + if(ev.xclient.message_type == handle->x11.xdnd_enter) { + Bool list = ev.xclient.data.l[1] & 1; + int version = ev.xclient.data.l[1] >> 24; + unsigned long count = 0; + unsigned char* formats = NULL; + unsigned long i; + handle->x11.xdnd_source = ev.xclient.data.l[0]; + if(list) { + Atom actualType; + int32_t actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty((Display*)handle->x11.display, + handle->x11.xdnd_source, + handle->x11.xdnd_type_list, + 0, + LONG_MAX, + False, + 4, + &actualType, + &actualFormat, + &count, + &bytesAfter, + (unsigned char**)&formats); + } else { + count = 0; + + if(ev.xclient.data.l[2] != None) + formats[count++] = ev.xclient.data.l[2]; + if(ev.xclient.data.l[3] != None) + formats[count++] = ev.xclient.data.l[3]; + if(ev.xclient.data.l[4] != None) + formats[count++] = ev.xclient.data.l[4]; + } + + for(i = 0; i < count; i++) { + if(formats[i] == handle->x11.xdnd_text_uri_list || formats[i] == handle->x11.xdnd_text_plain) { + handle->x11.xdnd_format = formats[i]; + printf("format %d\n", handle->x11.xdnd_format); + break; + } + } + + if(list) { + XFree(formats); + } + } + if(ev.xclient.message_type == handle->x11.xdnd_position && handle->x11.xdnd_version >= 5) { + XEvent reply; + reply.xclient.message_type = handle->x11.xdnd_status; + + if(handle->x11.xdnd_format) { + reply.xclient.data.l[1] = 1; + if(handle->x11.xdnd_version >= 2) reply.xclient.data.l[4] = handle->x11.xdnd_action_copy; + } + XSendEvent(handle->x11.display, handle->x11.xdnd_source, False, NoEventMask, &reply); + XFlush(handle->x11.display); + } + if(ev.xclient.message_type == handle->x11.xdnd_drop && handle->x11.xdnd_version >= 5) { + Time time = CurrentTime; + if(handle->x11.xdnd_version >= 1) + time = ev.xclient.data.l[2]; + + if(handle->x11.xdnd_format) { + XConvertSelection((Display*)handle->x11.display, + handle->x11.xdnd_selection, + handle->x11.xdnd_format, + handle->x11.xdnd_selection, + (Window)handle->x11.window, + time); + } + } + + XFree(name); + } + break; + }; + case FocusIn: + { MwLLDispatch(handle, focus_in, NULL); - } else if(ev.type == FocusOut) { + break; + }; + case FocusOut: + { MwLLDispatch(handle, focus_out, NULL); - } else if(ev.type == MotionNotify) { + break; + }; + case MotionNotify: + { MwPoint p; XWindowAttributes attr; @@ -780,7 +906,11 @@ static void MwLLNextEventImpl(MwLL handle) { if(handle->x11.grabbed && (p.x != 0 || p.y != 0)) { XWarpPointer(handle->x11.display, None, handle->x11.window, 0, 0, 0, 0, attr.width / 2, attr.height / 2); } - } else if(ev.type == KeyPress || ev.type == KeyRelease) { + break; + }; + case KeyPress: + case KeyRelease: + { int n = -1; char str[512]; KeySym sym; @@ -844,10 +974,60 @@ static void MwLLNextEventImpl(MwLL handle) { MwLLDispatch(handle, key_released, &n); } } - } else if(ev.type == SelectionNotify) { + break; + }; + case SelectionNotify: + { handle->x11.clipboard_pending = 0; - if(ev.xselection.property != None) { + if(ev.xselection.property == handle->x11.xdnd_selection) { + XEvent reply; + char* data; + unsigned long result; + Atom actualType; + int32_t actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty(handle->x11.display, + ev.xselection.requestor, ev.xselection.property, + 0, LONG_MAX, False, ev.xselection.target, + &actualType, &actualFormat, &result, &bytesAfter, + (unsigned char**)&data); + + if(result == 0) + break; + + /* !! untested lmao !! */ + printf("File(s) dropped: %s\n", data); + char* data_ptr = data; + while(data_ptr) { + char* next = strstr(data, "\n"); + if(next) *next = '\0'; + + printf("%s\n", data_ptr); + + if(strstr(data_ptr, "file://")) { + MwLLDispatch(handle, drag_and_drop, data_ptr + sizeof("file://") - 1); + } else { + MwLLDispatch(handle, drag_and_drop, data_ptr); + } + if(next) data_ptr = next + 1; + else + data_ptr = NULL; + } + + if(data) + XFree(data); + + if(handle->x11.xdnd_version >= 2) { + reply.xclient.message_type = handle->x11.xdnd_finished; + reply.xclient.data.l[1] = result; + reply.xclient.data.l[2] = handle->x11.xdnd_action_copy; + + XSendEvent((Display*)handle->x11.display, handle->x11.xdnd_source, False, NoEventMask, &reply); + XFlush((Display*)handle->x11.display); + } + } else if(ev.xselection.property != None) { Atom type; int format; unsigned long nitems, after, size; @@ -884,9 +1064,9 @@ static void MwLLNextEventImpl(MwLL handle) { free(buf); } } - XDeleteProperty(handle->x11.display, handle->x11.window, handle->x11.selection); } + } break; } if(render) { int x, y; @@ -1365,7 +1545,7 @@ static void MwLLGetScreenSizeImpl(MwLL handle, MwRect* rect) { } static void MwLLSetupDragAndDropImpl(MwLL handle) { - (void)handle; + XChangeProperty(handle->x11.display, handle->x11.window, handle->x11.xdnd_aware, 4, 32, PropModeReplace, &handle->x11.xdnd_version, 1); } static void MwLLBeginStateChangeImpl(MwLL handle) { @@ -1512,6 +1692,8 @@ static int MwLLX11CallInitImpl(void) { X11_FUNC_LOAD(XSetICFocus); X11_FUNC_LOAD(XGetImage); X11_FUNC_LOAD(XRaiseWindow); + X11_FUNC_LOAD(XFlush); + X11_FUNC_LOAD(XGetAtomName); X11_FUNC_LOAD(XAllocSizeHints); X11_FUNC_LOAD(XGetVisualInfo);