This patch is adding Drag-and-Drop support to wmiv based on Xdnd protocol handling library.
BTW as you may have seen with my last patch, these days i am doing some DnD tests. This is the implementation I did and used to test wmaker DnD. It's based on some common xdnd protocol implementation I updated for wmiv and to pass the checkpatch stage. As far as i saw there is still one issue in the wmaker DnD protocol implementation, I am still trying to pinpoint it. Seems it does not handle drop from some tools at the first drop (yes i know it's pretty vague). For example, it will work with Thunar but not with Rox. regards, david --- util/Makefile.am | 4 +- util/README | 2 + util/wmiv.c | 60 ++++- util/xdnd.c | 680 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ util/xdnd.h | 171 ++++++++++++++ 5 files changed, 912 insertions(+), 5 deletions(-) create mode 100755 util/xdnd.c create mode 100755 util/xdnd.h diff --git a/util/Makefile.am b/util/Makefile.am index b5c9d04..16a1295 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -71,11 +71,13 @@ wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \ wmmenugen_parse_xdg.c wmiv_LDADD = \ + $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/WINGs/libWUtil.la \ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @XLIBS@ \ @GFXLIBS@ $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) $(LIBEXIF) -wmiv_SOURCES = wmiv.c wmiv.h +wmiv_SOURCES = wmiv.c wmiv.h xdnd.c xdnd.h CLEANFILES = wmaker.inst diff --git a/util/README b/util/README index 76b779f..73f2968 100644 --- a/util/README +++ b/util/README @@ -22,6 +22,8 @@ wxcopy- copy input file or stdin into X cutbuffer wxpaste- copy content of X cutbuffer into stdout +wmiv- an image viewer + wmsetbg- set the workspace background into a image and make it persist between sessions. diff --git a/util/wmiv.c b/util/wmiv.c index 45cc4e3..22f0cca 100755 --- a/util/wmiv.c +++ b/util/wmiv.c @@ -35,6 +35,7 @@ #include <unistd.h> #include <sys/stat.h> #include "config.h" +#include "xdnd.h" #ifdef HAVE_EXIF #include <libexif/exif-data.h> @@ -63,7 +64,7 @@ Pixmap pix; const char *APPNAME = "wmiv"; int APPVERSION_MAJOR = 0; -int APPVERSION_MINOR = 7; +int APPVERSION_MINOR = 8; int NEXT = 0; int PREV = 1; float zoom_factor = 0; @@ -629,6 +630,7 @@ void linked_list_free(linked_list_t *list) free((char *)link->data); free(link); } + current_link = NULL; } /* @@ -686,6 +688,10 @@ int main(int argc, char **argv) XClassHint *class_hints; XSizeHints *size_hints; XWMHints *win_hints; + Atom delWindow; + Atom xdnd_aware; + Atom xdnd_version = XDND_VERSION; + #ifdef USE_XPM Pixmap icon_pixmap, icon_shape; #endif @@ -792,11 +798,13 @@ int main(int argc, char **argv) size_hints->width = img->width; size_hints->height = img->height; - Atom delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", 0); + delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(dpy, win, &delWindow, 1); change_title(&title_property, reading_filename); - + xdnd_aware = XInternAtom (dpy, "XdndAware", False); + XChangeProperty(dpy, win, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *) &xdnd_version, 1); win_hints = XAllocWMHints(); + if (win_hints) { win_hints->flags = StateHint|InputHint|WindowGroupHint; @@ -828,9 +836,46 @@ int main(int argc, char **argv) while (!quit) { XNextEvent(dpy, &e); if (e.type == ClientMessage) { + unsigned char *dropBuffer; + + if (xdnd_get_drop(dpy, &e, &dropBuffer)) { + char *handled_uri_header = "file://"; + char *filename_separator = "\n"; + char *ptr = strtok((char *)dropBuffer, filename_separator); + + linked_list_free(&list); + linked_list_init(&list); + + Bool find_one_file = False; + while (ptr != NULL) { + if (strstr(ptr, handled_uri_header) == ptr) { + char *tmp_file; + if (ptr[strlen(ptr) - 1] == '\r') + ptr[strlen(ptr) - 1] = '\0'; + tmp_file = ptr + strlen(handled_uri_header); + + /* drag-and-drop file name formatas per the spec is encoded as an URI */ + decode_uri(tmp_file); + if (connect_dir(tmp_file, &list)) + find_one_file = True; + } + ptr = strtok(NULL, filename_separator); + } + free(dropBuffer); + + if (find_one_file) { + max_index = list.count; + current_link = list.last; + change_image(NEXT); + } else { + if (img) + RReleaseImage(img); + img = draw_failed_image(); + } + } if (e.xclient.data.l[0] == delWindow) quit = 1; - break; + continue; } if (e.type == FocusIn) { focus = True; @@ -850,6 +895,13 @@ int main(int argc, char **argv) XConfigureEvent xce = e.xconfigure; if (xce.width != img->width || xce.height != img->height) { RImage *old_img = img; + + /* there is no file loaded and the window is resized */ + if (!current_link) { + XResizeWindow(dpy, win, img->width, img->height); + continue; + } + img = load_oriented_image(ctx, current_link->data, 0); if (!img) { /* keep the old img and window size */ diff --git a/util/xdnd.c b/util/xdnd.c new file mode 100755 index 0000000..be47a99 --- /dev/null +++ b/util/xdnd.c @@ -0,0 +1,680 @@ +/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol + Copyright (C) 1996-2000 Paul Sheer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + */ + + +/* + Modified 2014-08-13 - for wmiv usage + Released 1998-08-07 +*/ + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/time.h> +#include "xdnd.h" + +static void xdnd_send_status(DndClass *dnd, Window window, Window from, int will_accept, int want_position, + int x, int y, int w, int h, Atom action); +static void xdnd_send_finished(DndClass *nd, Window window, Window from, int error); +static int xdnd_convert_selection(DndClass *dnd, Window window, Window requester, Atom type); +static int xdnd_get_selection(DndClass *dnd, Window from, Atom property, Window insert); + +/* #define DND_DEBUG */ + +#define xdnd_xfree(x) { if (x) { free(x); x = 0; } } + +#ifdef DND_DEBUG + +#include <sys/time.h> +#include <unistd.h> + +char *xdnd_debug_milliseconds(void) +{ + struct timeval tv; + static char r[22]; + gettimeofday(&tv, 0); + sprintf(r, "%.2ld.%.3ld", tv.tv_sec % 100L, tv.tv_usec / 1000L); + return r; +} + +#define dnd_debug1(a) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds()) +#define dnd_debug2(a, b) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b) +#define dnd_debug3(a, b, c) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b, c) +#define dnd_debug4(a, b, c, d) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds(), b, c, d) +#else +#define dnd_debug1(a) +#define dnd_debug2(a, b) +#define dnd_debug3(a, b, c) +#define dnd_debug4(a, b, c, d) +#endif + +#define dnd_warning(a) fprintf(stderr, a) + +#define dnd_version_at_least(a, b) ((a) >= (b)) + +void xdnd_reset(DndClass *dnd) +{ + dnd->stage = XDND_DROP_STAGE_IDLE; + dnd->dragging_version = 0; + dnd->internal_drag = 0; + dnd->want_position = 0; + dnd->ready_to_drop = 0; + dnd->will_accept = 0; + dnd->rectangle.x = dnd->rectangle.y = 0; + dnd->rectangle.width = dnd->rectangle.height = 0; + dnd->dropper_window = 0; + dnd->dropper_toplevel = 0; + dnd->dragger_window = 0; + dnd->dragger_typelist = 0; + dnd->desired_type = 0; + dnd->time = 0; +} + +/* + decode_uri: transform the given URI to UTF-8 string +*/ +void decode_uri(char *uri) +{ + char *last = uri + strlen(uri); + while (uri < last-2) { + if (*uri == '%') { + int h; + if (sscanf(uri+1, "%2X", &h) != 1) + break; + *uri = h; + memmove(uri+1, uri+3, last - (uri+2)); + last -= 2; + } + uri++; + } +} + +void xdnd_init(DndClass *dnd, Display *display) +{ + memset(dnd, 0, sizeof(*dnd)); + + dnd->display = display; + dnd->root_window = DefaultRootWindow(display); + dnd->version = XDND_VERSION; + + dnd->XdndAware = XInternAtom(dnd->display, "XdndAware", False); + dnd->XdndSelection = XInternAtom(dnd->display, "XdndSelection", False); + dnd->XdndEnter = XInternAtom(dnd->display, "XdndEnter", False); + dnd->XdndLeave = XInternAtom(dnd->display, "XdndLeave", False); + dnd->XdndPosition = XInternAtom(dnd->display, "XdndPosition", False); + dnd->XdndDrop = XInternAtom(dnd->display, "XdndDrop", False); + dnd->XdndFinished = XInternAtom(dnd->display, "XdndFinished", False); + dnd->XdndStatus = XInternAtom(dnd->display, "XdndStatus", False); + dnd->XdndActionCopy = XInternAtom(dnd->display, "XdndActionCopy", False); + dnd->XdndActionMove = XInternAtom(dnd->display, "XdndActionMove", False); + dnd->XdndActionLink = XInternAtom(dnd->display, "XdndActionLink", False); + dnd->XdndActionAsk = XInternAtom(dnd->display, "XdndActionAsk", False); + dnd->XdndActionPrivate = XInternAtom(dnd->display, "XdndActionPrivate", False); + dnd->XdndTypeList = XInternAtom(dnd->display, "XdndTypeList", False); + dnd->XdndActionList = XInternAtom(dnd->display, "XdndActionList", False); + dnd->XdndActionDescription = XInternAtom(dnd->display, "XdndActionDescription", False); + + dnd->Xdnd_NON_PROTOCOL_ATOM = XInternAtom(dnd->display, "JXSelectionWindowProperty", False); + + xdnd_reset(dnd); +} + +/* typelist is a null terminated array */ +static int array_length(Atom *a) +{ + int n; + for (n = 0; a[n]; n++); + return n; +} + +void xdnd_set_type_list(DndClass *dnd, Window window, Atom *typelist) +{ + int n; + n = array_length(typelist); + XChangeProperty(dnd->display, window, dnd->XdndTypeList, XA_ATOM, 32, + PropModeReplace, (unsigned char *) typelist, n); +} + +/* result must be free'd */ +void xdnd_get_type_list(DndClass *dnd, Window window, Atom **typelist) +{ + Atom type, *a; + int format, i; + unsigned long count, remaining; + unsigned char *data = NULL; + + *typelist = 0; + + XGetWindowProperty(dnd->display, window, dnd->XdndTypeList, + 0, 0x8000000L, False, XA_ATOM, + &type, &format, &count, &remaining, &data); + + if (type != XA_ATOM || format != 32 || count == 0 || !data) { + if (data) + XFree(data); + dnd_debug2("XGetWindowPropertyfailed in xdnd_get_type_list- dnd->XdndTypeList = %ld", dnd->XdndTypeList); + return; + } + *typelist = malloc((count + 1) * sizeof(Atom)); + a = (Atom *) data; + for (i = 0; i < count; i++) + (*typelist)[i] = a[i]; + (*typelist)[count] = 0; + + XFree(data); +} + +void xdnd_get_three_types(DndClass *dnd, XEvent *xevent, Atom **typelist) +{ + int i; + (void) dnd; + + *typelist = malloc((XDND_THREE + 1) * sizeof(Atom)); + for (i = 0; i < XDND_THREE; i++) + (*typelist)[i] = XDND_ENTER_TYPE(xevent, i); + /* although (*typelist)[1] or (*typelist)[2] may also be set to nill */ + (*typelist)[XDND_THREE] = 0; +} + +static void xdnd_send_event(DndClass *dnd, Window window, XEvent *xevent) +{ + dnd_debug4("xdnd_send_event(), window = %ld, l[0] = %ld, l[4] = %ld", + window, xevent->xclient.data.l[0], xevent->xclient.data.l[4]); + dnd_debug2("xdnd_send_event(), from widget widget %s", (char *) "<WIDGET>"); + XSendEvent(dnd->display, window, 0, 0, xevent); +} + +static void xdnd_send_status(DndClass *dnd, Window window, Window from, int will_accept, + int want_position, int x, int y, int w, int h, Atom action) +{ + XEvent xevent; + + memset(&xevent, 0, sizeof(xevent)); + + xevent.xany.type = ClientMessage; + xevent.xany.display = dnd->display; + xevent.xclient.window = window; + xevent.xclient.message_type = dnd->XdndStatus; + xevent.xclient.format = 32; + + XDND_STATUS_TARGET_WIN(&xevent) = from; + XDND_STATUS_WILL_ACCEPT_SET(&xevent, will_accept); + if (will_accept) + XDND_STATUS_WANT_POSITION_SET(&xevent, want_position); + if (want_position) + XDND_STATUS_RECT_SET(&xevent, x, y, w, h); + if (dnd_version_at_least(dnd->dragging_version, 2)) + if (will_accept) + XDND_STATUS_ACTION(&xevent) = action; + + xdnd_send_event(dnd, window, &xevent); +} + + +/* error is not actually used, i think future versions of the protocol should return an error status + to the calling window with the XdndFinished client message */ +static void xdnd_send_finished(DndClass *dnd, Window window, Window from, int error) +{ + XEvent xevent; + (void) error; + + memset(&xevent, 0, sizeof(xevent)); + + xevent.xany.type = ClientMessage; + xevent.xany.display = dnd->display; + xevent.xclient.window = window; + xevent.xclient.message_type = dnd->XdndFinished; + xevent.xclient.format = 32; + + XDND_FINISHED_TARGET_WIN(&xevent) = from; + + xdnd_send_event(dnd, window, &xevent); +} + +/* returns non-zero on error - i.e. no selection owner set. Type is of course the mime type */ +static int xdnd_convert_selection(DndClass *dnd, Window window, Window requester, Atom type) +{ + window = XGetSelectionOwner(dnd->display, dnd->XdndSelection); + if (!window) { + dnd_debug1("xdnd_convert_selection(): XGetSelectionOwner failed"); + return 1; + } + XConvertSelection(dnd->display, dnd->XdndSelection, type, + dnd->Xdnd_NON_PROTOCOL_ATOM, requester, CurrentTime); + return 0; +} + +static int paste_prop_internal(DndClass *dnd, Window from, Window insert, unsigned long prop, int delete_prop) +{ + long nread = 0; + unsigned long nitems; + unsigned long bytes_after; + int error = 0; + do { + Atom actual_type; + int actual_fmt; + unsigned char *s = 0; + if (XGetWindowProperty(dnd->display, insert, prop, + nread / 4, 65536, delete_prop, + AnyPropertyType, &actual_type, &actual_fmt, + &nitems, &bytes_after, &s) != Success) { + XFree(s); + return 1; + } + nread += nitems; + if (dnd->widget_insert_drop && !error) + error = (*dnd->widget_insert_drop) (dnd, s, nitems, bytes_after, insert, from, actual_fmt); + XFree(s); + } while (bytes_after); + if (!nread) + return 1; + return 0; +} + +/* + * Respond to a notification that a primary selection has been sent (supports INCR) + */ +static int xdnd_get_selection(DndClass *dnd, Window from, Atom prop, Window insert) +{ + struct timeval tv, tv_start; + unsigned long bytes_after; + Atom actual_type; + int actual_fmt; + unsigned long nitems; + unsigned char *s = 0; + if (prop == None) + return 1; + if (XGetWindowProperty + (dnd->display, insert, prop, 0, 8, False, AnyPropertyType, &actual_type, &actual_fmt, + &nitems, &bytes_after, &s) != Success) { + XFree(s); + return 1; + } + XFree(s); + if (actual_type != XInternAtom(dnd->display, "INCR", False)) + return paste_prop_internal(dnd, from, insert, prop, True); + XDeleteProperty(dnd->display, insert, prop); + gettimeofday(&tv_start, 0); + for (;;) { + long t; + fd_set r; + XEvent xe; + if (XCheckMaskEvent(dnd->display, PropertyChangeMask, &xe)) { + if (xe.type == PropertyNotify && xe.xproperty.state == PropertyNewValue) { + /* time between arrivals of data */ + gettimeofday(&tv_start, 0); + if (paste_prop_internal(dnd, from, insert, prop, True)) + break; + } + } else { + tv.tv_sec = 0; + tv.tv_usec = 10000; + FD_ZERO(&r); + FD_SET(ConnectionNumber(dnd->display), &r); + select(ConnectionNumber(dnd->display) + 1, &r, 0, 0, &tv); + if (FD_ISSET(ConnectionNumber(dnd->display), &r)) + continue; + } + gettimeofday(&tv, 0); + t = (tv.tv_sec - tv_start.tv_sec) * 1000000L + (tv.tv_usec - tv_start.tv_usec); + /* no data for five seconds, so quit */ + if (t > 5000000L) + return 1; + } + return 0; +} + +/* returns non-zero if event is handled */ +int xdnd_handle_drop_events(DndClass *dnd, XEvent *xevent) +{ + int result = 0; + if (xevent->type == SelectionNotify) { + dnd_debug1("got SelectionNotify"); + if (xevent->xselection.property == dnd->Xdnd_NON_PROTOCOL_ATOM && dnd->stage == XDND_DROP_STAGE_CONVERTING) { + int error; + dnd_debug1(" property is Xdnd_NON_PROTOCOL_ATOM - getting selection"); + error = xdnd_get_selection(dnd, dnd->dragger_window, xevent->xselection.property, xevent->xany.window); + /* error is not actually used, i think future versions of the protocol maybe should return + an error status to the calling window with the XdndFinished client message */ + if (dnd_version_at_least(dnd->dragging_version, 2)) { +#if XDND_VERSION >= 3 + xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_toplevel, error); +#else + xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_window, error); +#endif + dnd_debug1(" sending finished"); + } + xdnd_xfree(dnd->dragger_typelist); + xdnd_reset(dnd); + dnd->stage = XDND_DROP_STAGE_IDLE; + result = 1; + } else { + dnd_debug1(" property is not Xdnd_NON_PROTOCOL_ATOM - ignoring"); + } + } else if (xevent->type == ClientMessage) { + dnd_debug2("got ClientMessage to xevent->xany.window = %ld", xevent->xany.window); + if (xevent->xclient.message_type == dnd->XdndEnter) { + dnd_debug2(" message_type is XdndEnter, version = %ld", XDND_ENTER_VERSION(xevent)); +#if XDND_VERSION >= 3 + if (XDND_ENTER_VERSION(xevent) < 3) + return 0; +#endif + xdnd_reset(dnd); + dnd->dragger_window = XDND_ENTER_SOURCE_WIN(xevent); +#if XDND_VERSION >= 3 + dnd->dropper_toplevel = xevent->xany.window; + dnd->dropper_window = 0; /* enter goes to the top level window only, + so we don't really know what the + sub window is yet */ +#else + dnd->dropper_window = xevent->xany.window; +#endif + xdnd_xfree(dnd->dragger_typelist); + if (XDND_ENTER_THREE_TYPES(xevent)) { + dnd_debug1(" three types only"); + xdnd_get_three_types(dnd, xevent, &dnd->dragger_typelist); + } else { + dnd_debug1(" more than three types - getting list"); + xdnd_get_type_list(dnd, dnd->dragger_window, &dnd->dragger_typelist); + } + if (dnd->dragger_typelist) + dnd->stage = XDND_DROP_STAGE_ENTERED; + else { + dnd_debug1("typelist returned as zero!"); + } + + dnd->dragging_version = XDND_ENTER_VERSION(xevent); + result = 1; + } else if (xevent->xclient.message_type == dnd->XdndLeave) { +#if XDND_VERSION >= 3 + if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window) + xevent->xany.window = dnd->dropper_window; +#endif + dnd_debug1(" message_type is XdndLeave"); + if (dnd->dragger_window == XDND_LEAVE_SOURCE_WIN(xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) { + dnd_debug1(" leaving"); + if (dnd->widget_apply_leave) + (*dnd->widget_apply_leave) (dnd, xevent->xany.window); + dnd->stage = XDND_DROP_STAGE_IDLE; + xdnd_xfree(dnd->dragger_typelist); + result = 1; + dnd->dropper_toplevel = dnd->dropper_window = 0; + } else { + dnd_debug1(" wrong stage or from wrong window"); + } + } else if (xevent->xclient.message_type == dnd->XdndPosition) { + dnd_debug2(" message_type is XdndPosition to %ld", xevent->xany.window); + if (dnd->dragger_window == XDND_POSITION_SOURCE_WIN(xevent) + && dnd->stage == XDND_DROP_STAGE_ENTERED) { + int want_position; + Atom action; + XRectangle rectangle; + Window last_window; + last_window = dnd->dropper_window; +#if XDND_VERSION >= 3 + /* version 3 gives us the top-level window only. + WE have to find the child that the pointer is over: */ + if (1 || xevent->xany.window != dnd->dropper_toplevel || !dnd->dropper_window) { + Window parent, child, new_child = 0; + dnd->dropper_toplevel = xevent->xany.window; + parent = dnd->root_window; + child = dnd->dropper_toplevel; + for (;;) { + int xd, yd; + new_child = 0; + if (!XTranslateCoordinates(dnd->display, parent, child, + XDND_POSITION_ROOT_X(xevent), XDND_POSITION_ROOT_Y(xevent), + &xd, &yd, &new_child)) + break; + if (!new_child) + break; + child = new_child; + } + dnd->dropper_window = xevent->xany.window = child; + dnd_debug2(" child window translates to %ld", dnd->dropper_window); + } else if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window) { + xevent->xany.window = dnd->dropper_window; + dnd_debug2(" child window previously found: %ld", dnd->dropper_window); + } +#endif + action = dnd->XdndActionCopy; + dnd->supported_action = dnd->XdndActionCopy; + dnd->x = XDND_POSITION_ROOT_X(xevent); + dnd->y = XDND_POSITION_ROOT_Y(xevent); + dnd->time = CurrentTime; + if (dnd_version_at_least(dnd->dragging_version, 1)) + dnd->time = XDND_POSITION_TIME(xevent); + if (dnd_version_at_least(dnd->dragging_version, 1)) + action = XDND_POSITION_ACTION(xevent); +#if XDND_VERSION >= 3 + if (last_window && last_window != xevent->xany.window) + if (dnd->widget_apply_leave) + (*dnd->widget_apply_leave) (dnd, last_window); +#endif + dnd->will_accept = (*dnd->widget_apply_position) (dnd, xevent->xany.window, dnd->dragger_window, + action, dnd->x, dnd->y, dnd->time, dnd->dragger_typelist, + &want_position, &dnd->supported_action, &dnd->desired_type, &rectangle); + dnd_debug2(" will accept = %d", dnd->will_accept); +#if XDND_VERSION >= 3 + dnd_debug2(" sending status of %ld", dnd->dropper_toplevel); + xdnd_send_status(dnd, dnd->dragger_window, dnd->dropper_toplevel, dnd->will_accept, + want_position, rectangle.x, rectangle.y, + rectangle.width, rectangle.height, dnd->supported_action); +#else + dnd_debug2(" sending status of %ld", xevent->xany.window); + xdnd_send_status(dnd, dnd->dragger_window, xevent->xany.window, dnd->will_accept, + want_position, rectangle.x, rectangle.y, rectangle.width, + rectangle.height, dnd->supported_action); +#endif + result = 1; + } else { + dnd_debug1(" wrong stage or from wrong window"); + } + } else if (xevent->xclient.message_type == dnd->XdndDrop) { +#if XDND_VERSION >= 3 + if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window) + xevent->xany.window = dnd->dropper_window; +#endif + dnd_debug1(" message_type is XdndDrop"); + if (dnd->dragger_window == XDND_DROP_SOURCE_WIN(xevent) + && dnd->stage == XDND_DROP_STAGE_ENTERED) { + dnd->time = CurrentTime; + if (dnd_version_at_least(dnd->dragging_version, 1)) + dnd->time = XDND_DROP_TIME(xevent); + if (dnd->will_accept) { + dnd_debug1(" will_accept is true - converting selectiong"); + dnd_debug2(" my window is %ld", dnd->dropper_window); + dnd_debug2(" source window is %ld", dnd->dragger_window); + xdnd_convert_selection(dnd, dnd->dragger_window, dnd->dropper_window, dnd->desired_type); + dnd->stage = XDND_DROP_STAGE_CONVERTING; + } else { + dnd_debug1(" will_accept is false - sending finished"); + if (dnd_version_at_least(dnd->dragging_version, 2)) { +#if XDND_VERSION >= 3 + xdnd_send_finished(dnd, dnd->dragger_window, dnd->dropper_toplevel, 1); +#else + xdnd_send_finished(dnd, dnd->dragger_window, xevent->xany.window, 1); +#endif + } + xdnd_xfree(dnd->dragger_typelist); + xdnd_reset(dnd); + dnd->stage = XDND_DROP_STAGE_IDLE; + } + result = 1; + } else { + dnd_debug1(" wrong stage or from wrong window"); + } + } + } + return result; +} + +struct xdnd_get_drop_info { + unsigned char *drop_data; + int drop_data_length; + int x, y; + Atom return_type; + Atom return_action; + Atom *typelist; + Atom *actionlist; +}; + +static int widget_insert_drop(DndClass *dnd, unsigned char *data, int length, + int remaining, Window into, Window from, Atom type) +{ + struct xdnd_get_drop_info *i; + (void) remaining; + (void) into; + (void) from; + (void) type; + + i = (struct xdnd_get_drop_info *) dnd->user_hook1; + if (!i->drop_data) { + i->drop_data = malloc(length); + if (!i->drop_data) + return 1; + memcpy(i->drop_data, data, length); + i->drop_data_length = length; + } else { + unsigned char *t; + t = malloc(i->drop_data_length + length); + if (!t) { + free(i->drop_data); + i->drop_data = 0; + return 1; + } + memcpy(t, i->drop_data, i->drop_data_length); + memcpy(t + i->drop_data_length, data, length); + free(i->drop_data); + i->drop_data = t; + i->drop_data_length += length; + } + return 0; +} + +static int widget_apply_position(DndClass *dnd, Window widgets_window, Window from, + Atom action, int x, int y, Time t, Atom *typelist, + int *want_position, Atom *supported_action_return, + Atom *desired_type, XRectangle *rectangle) +{ + int i, j; + struct xdnd_get_drop_info *info; + Atom *dropper_typelist, supported_type = 0; + Atom *supported_actions, supported_action = 0; + (void) widgets_window; + (void) from; + (void) t; + + info = (struct xdnd_get_drop_info *) dnd->user_hook1; + dropper_typelist = info->typelist; + supported_actions = info->actionlist; + + if (dropper_typelist) { + /* find a correlation: */ + for (j = 0; dropper_typelist[j]; j++) { + for (i = 0; typelist[i]; i++) { + if (typelist[i] == dropper_typelist[j]) { + supported_type = typelist[i]; + break; + } + } + if (supported_type) + break; + } + } else { + /* user did not specify, so return first type */ + supported_type = typelist[0]; + } + /* not supported, so return false */ + if (!supported_type) + return 0; + + if (supported_actions) { + for (j = 0; supported_actions[j]; j++) { + if (action == supported_actions[j]) { + supported_action = action; + break; + } + } + } else { + /* user did not specify */ + if (action == dnd->XdndActionCopy) + supported_action = action; + } + if (!supported_action) + return 0; + + *want_position = 1; + rectangle->x = rectangle->y = 0; + rectangle->width = rectangle->height = 0; + + info->return_action = *supported_action_return = supported_action; + info->return_type = *desired_type = supported_type; + info->x = x; + info->y = y; + + return 1; +} + +Atom xdnd_get_drop(Display *display, XEvent *xevent, unsigned char **data) +{ + Atom action = 0; + static int initialised = 0; + static DndClass dnd; + static Atom typelist[2]; + + if (!initialised) { + const char *m_dndMimeTypes[] = { "text/uri-list" }; + xdnd_init(&dnd, display); + XInternAtoms(display, (char **)m_dndMimeTypes, 1, 0, typelist); + typelist[1] = 0; + initialised = 1; + } + + if (xevent->type != ClientMessage || xevent->xclient.message_type != dnd.XdndEnter) { + return 0; + } else { + struct xdnd_get_drop_info i; + + /* setup user structure */ + memset(&i, 0, sizeof(i)); + i.typelist = typelist; + dnd.user_hook1 = &i; + + /* setup methods */ + dnd.widget_insert_drop = widget_insert_drop; + dnd.widget_apply_position = widget_apply_position; + + /* main loop */ + for (;;) { + xdnd_handle_drop_events(&dnd, xevent); + if (dnd.stage == XDND_DROP_STAGE_IDLE) + break; + XNextEvent(dnd.display, xevent); + } + /* return results */ + if (i.drop_data) { + *data = i.drop_data; + action = i.return_action; + } + } + return action; +} diff --git a/util/xdnd.h b/util/xdnd.h new file mode 100755 index 0000000..91ef33b --- /dev/null +++ b/util/xdnd.h @@ -0,0 +1,171 @@ +/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol + Copyright (C) 1996-2000 Paul Sheer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + */ + +#ifndef _X_DND_H +#define _X_DND_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* you can set this to either 2 (which support 0 and 1 as well) or 3 */ +#define XDND_VERSION 3 + + +/* XdndEnter */ +#define XDND_THREE 3 +#define XDND_ENTER_SOURCE_WIN(e) ((e)->xclient.data.l[0]) +#define XDND_ENTER_THREE_TYPES(e) (((e)->xclient.data.l[1] & 0x1UL) == 0) +#define XDND_ENTER_THREE_TYPES_SET(e, b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL) +#define XDND_ENTER_VERSION(e) ((e)->xclient.data.l[1] >> 24) +#define XDND_ENTER_VERSION_SET(e, v) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24) +#define XDND_ENTER_TYPE(e, i) ((e)->xclient.data.l[2 + i]) /* i => (0, 1, 2) */ + +/* XdndPosition */ +#define XDND_POSITION_SOURCE_WIN(e) ((e)->xclient.data.l[0]) +#define XDND_POSITION_ROOT_X(e) ((e)->xclient.data.l[2] >> 16) +#define XDND_POSITION_ROOT_Y(e) ((e)->xclient.data.l[2] & 0xFFFFUL) +#define XDND_POSITION_ROOT_SET(e, x, y) (e)->xclient.data.l[2] = ((x) << 16) | ((y) & 0xFFFFUL) +#define XDND_POSITION_TIME(e) ((e)->xclient.data.l[3]) +#define XDND_POSITION_ACTION(e) ((e)->xclient.data.l[4]) + +/* XdndStatus */ +#define XDND_STATUS_TARGET_WIN(e) ((e)->xclient.data.l[0]) +#define XDND_STATUS_WILL_ACCEPT(e) ((e)->xclient.data.l[1] & 0x1L) +#define XDND_STATUS_WILL_ACCEPT_SET(e, b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL) +#define XDND_STATUS_WANT_POSITION(e) ((e)->xclient.data.l[1] & 0x2UL) +#define XDND_STATUS_WANT_POSITION_SET(e, b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x2UL) | (((b) == 0) ? 0 : 0x2UL) +#define XDND_STATUS_RECT_X(e) ((e)->xclient.data.l[2] >> 16) +#define XDND_STATUS_RECT_Y(e) ((e)->xclient.data.l[2] & 0xFFFFL) +#define XDND_STATUS_RECT_WIDTH(e) ((e)->xclient.data.l[3] >> 16) +#define XDND_STATUS_RECT_HEIGHT(e) ((e)->xclient.data.l[3] & 0xFFFFL) +#define XDND_STATUS_RECT_SET(e, x, y, w, h) {(e)->xclient.data.l[2] = ((x) << 16) | ((y) & 0xFFFFUL); (e)->xclient.data.l[3] = ((w) << 16) | ((h) & 0xFFFFUL); } +#define XDND_STATUS_ACTION(e) ((e)->xclient.data.l[4]) + +/* XdndLeave */ +#define XDND_LEAVE_SOURCE_WIN(e) ((e)->xclient.data.l[0]) + +/* XdndDrop */ +#define XDND_DROP_SOURCE_WIN(e) ((e)->xclient.data.l[0]) +#define XDND_DROP_TIME(e) ((e)->xclient.data.l[2]) + +/* XdndFinished */ +#define XDND_FINISHED_TARGET_WIN(e) ((e)->xclient.data.l[0]) + +typedef struct _DndClass DndClass; + +struct _DndClass { +/* insert chars sequentionally into the target widget, type will be the same as `desired_type' + returned from widget_apply_position. This may be called several times in succession + with sequention blocks of data. Must return non-zero on failure */ + int (*widget_insert_drop)(DndClass *dnd, unsigned char *data, int length, + int remaining, Window into, Window from, Atom type); + +/* must update the widgets border to its default appearance */ + void (*widget_apply_leave)(DndClass *dnd, Window widgets_window); + +/* must update the widgets border to give the appearance of being able to recieve a drop, + plus return all data to pointers. As per the protocol, if the widget cannot + perform the action specified by `action' then it should return either XdndActionPrivate + or XdndActionCopy into supported_action (leaving 0 supported_action unchanged is equivalent + to XdndActionCopy). Returns 1 if ready to ok drop */ + int (*widget_apply_position)(DndClass *dnd, Window widgets_window, Window from, + Atom action, int x, int y, Time t, Atom *typelist, int *want_position, + Atom *supported_action, Atom *desired_type, XRectangle *rectangle); + + void *pad1[8]; + + Display *display; + + Atom XdndAware; + Atom XdndSelection; + Atom XdndEnter; + Atom XdndLeave; + Atom XdndPosition; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndActionMove; + Atom XdndActionLink; + Atom XdndActionAsk; + Atom XdndActionPrivate; + Atom XdndTypeList; + Atom XdndActionList; + Atom XdndActionDescription; + + Atom Xdnd_NON_PROTOCOL_ATOM; + Atom version; + + Atom pad2[16]; + + Window root_window; + +#define XDND_DROP_STAGE_IDLE 0 +#define XDND_DRAG_STAGE_DRAGGING 1 +#define XDND_DRAG_STAGE_ENTERED 2 +#define XDND_DROP_STAGE_CONVERTING 3 +#define XDND_DROP_STAGE_ENTERED 4 + int stage; + int dragging_version; + int internal_drag; + int want_position; + int ready_to_drop; + int will_accept; + XRectangle rectangle; + Window dropper_window, dragger_window; + Atom *dragger_typelist; + Atom desired_type; + Atom supported_action; + Time time; +/* drop position from last XdndPosition */ + int x, y; + int pad3[16]; + +/* move euclidian pixels before considering this to be an actual drag */ + float drag_threshold; + +/* block for only this many seconds on not receiving a XdndFinished from target, default : 10 */ + int time_out; + +#define XDND_OPTION_NO_HYSTERESIS (1<<0) + int options; + +/* user hooks */ + void *user_hook1; + void *user_hook2; + void *user_hook3; + Window dropper_toplevel; + void *pad4[15]; +}; + +void decode_uri(char *uri); +void xdnd_init(DndClass *dnd, Display *display); +/* Returns 1 if event is handled, This must be placed in the widget +libraries main event loop and be called if the event type is +ClientMessage or SelectionNotify */ +int xdnd_handle_drop_events(DndClass *dnd, XEvent *xevent); +Atom xdnd_get_drop(Display *display, XEvent *xevent, unsigned char **data); + + +#ifdef __cplusplus +} +#endif + +#endif /* !_X_DND_H */
0001-util-wmiv-Add-drag-and-drop-support.patch
Description: Binary data