/* * Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @summary Native window for TaskXDragDrop. This is taken from * XDnD Tiger AWT testsuite. * @author Aruna Samji (aruna.samji@sun.com) */ #include #include #include #include #include #include #include #include //#define TRACE /* XDnD atom names */ static const char* XAtomName_XdndActionCopy = "XdndActionCopy"; static const char* XAtomName_XdndAware = "XdndAware"; static const char* XAtomName_XdndDrop = "XdndDrop"; static const char* XAtomName_XdndEnter = "XdndEnter"; static const char* XAtomName_XdndFinished = "XdndFinished"; static const char* XAtomName_XdndLeave = "XdndLeave"; static const char* XAtomName_XdndPosition = "XdndPosition"; static const char* XAtomName_XdndProxy = "XdndProxy"; static const char* XAtomName_XdndSelection = "XdndSelection"; static const char* XAtomName_XdndStatus = "XdndStatus"; static const char* XAtomName_XdndActionMove = "XdndActionMove"; static const char* XAtomName_XdndActionLink = "XdndActionLink"; static const char* XAtomName_TextPlain = "text/plain"; /* XDnD atoms */ static Atom _XA_XdndActionCopy = None; static Atom _XA_XdndAware = None; static Atom _XA_XdndDrop = None; static Atom _XA_XdndEnter = None; static Atom _XA_XdndFinished = None; static Atom _XA_XdndLeave = None; static Atom _XA_XdndPosition = None; static Atom _XA_XdndProxy = None; static Atom _XA_XdndSelection = None; static Atom _XA_XdndStatus = None; static Atom _XA_TextPlain = None; static Atom _XA_XdndActionMove = None; static Atom _XA_XdndActionLink = None; /* XDnD constants */ static const Atom XDnDVersion = 4; static const unsigned int DND_VERSION_SHIFT = 24; static const unsigned int ACCEPT_DROP_FLAG = 1; /* shared constants */ static const EventMask DRAG_MASK = ButtonMotionMask | PointerMotionMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask; /* shared function prototypes */ static void init(); static void remove_dnd_grab(Display* dpy, Time time); static void cleanup_drag(Display* dpy); static void register_drag_source(Widget widget); /* XDnD support */ static Boolean intern_xdnd_atoms(Display* dpy); static void update_dnd_version(int version); static int get_dnd_version(); static void set_same_answer(int x, int y, int w, int h); static Boolean is_same_answer(int x, int y); static Boolean register_xdnd_drop_target(Widget widget); /* shared state variables */ static Boolean drag_in_progress = False; static Window target_win = None; static Window target_proxy_win = None; static char* drop_data = NULL; int main(int argc, char **argv) { XtAppContext app; Widget widget = XtAppInitialize (&app, "XDnD_main", NULL, 0, &argc, argv, NULL, NULL, 0); XtVaSetValues(widget, XtNx, 550, XtNy, 220, XtNwidth, 200, XtNheight, 150, NULL); XtRealizeWidget(widget); init(); intern_xdnd_atoms(XtDisplay(widget)); register_drag_source(widget); register_xdnd_drop_target(widget); XSetSelectionOwner(XtDisplay(widget), XA_PRIMARY, None, CurrentTime); #ifdef DEBUG fprintf(stderr,"Root=%X\n", XRootWindowOfScreen(XtScreen(widget))); #endif for (;;) { XEvent event; Window root_win = XRootWindowOfScreen(XtScreen(widget)); XtAppNextEvent(app, &event); #ifdef DEBUG fprintf(stderr,"Event type=%d win=%X\n", event.type, event.xany.window); if (event.type == MotionNotify) { fprintf(stderr," subwin=%X\n", event.xmotion.subwindow); } #endif if (drag_in_progress && event.xany.window == root_win) { switch (event.type) { case ButtonPress: case ButtonRelease: case MotionNotify: case EnterNotify: case LeaveNotify: { Window child; XTranslateCoordinates(XtDisplay(widget), root_win, XtWindow(widget), event.xbutton.x, event.xbutton.y, &event.xbutton.x, &event.xbutton.y, &child); } case KeyPress: case KeyRelease: event.xany.window = XtWindow(widget); } } XtDispatchEvent(&event); } return 0; } static Boolean bad_window = False; static int (*original_x_errhandler)(Display* dpy, XErrorEvent*); static int x_errhandler(Display *dpy, XErrorEvent *err) { if (err->error_code == BadWindow) { bad_window = True; } { char errstr[256]; XGetErrorText( dpy, err->error_code, errstr, 256 ); fprintf(stderr, "Error: %s %d\n Major opcode: %d\n Resource id: 0x%lx\n", errstr, err->error_code, err->request_code, err->resourceid); } return 0; } static void init() { original_x_errhandler = XSetErrorHandler(x_errhandler); } static Boolean intern_xdnd_atoms(Display* dpy) { _XA_XdndActionCopy = XInternAtom(dpy, XAtomName_XdndActionCopy, False); if (_XA_XdndActionCopy == None) { return False; } _XA_XdndActionMove = XInternAtom(dpy, XAtomName_XdndActionMove, False); if (_XA_XdndActionMove == None) { return False; } _XA_XdndActionLink = XInternAtom(dpy, XAtomName_XdndActionLink, False); if (_XA_XdndActionLink == None) { return False; } _XA_XdndAware = XInternAtom(dpy, XAtomName_XdndAware, False); if (_XA_XdndAware == None) { return False; } _XA_XdndDrop = XInternAtom(dpy, XAtomName_XdndDrop, False); if (_XA_XdndDrop == None) { return False; } _XA_XdndEnter = XInternAtom(dpy, XAtomName_XdndEnter, False); if (_XA_XdndEnter == None) { return False; } _XA_XdndFinished = XInternAtom(dpy, XAtomName_XdndFinished, False); if (_XA_XdndFinished == None) { return False; } _XA_XdndLeave = XInternAtom(dpy, XAtomName_XdndLeave, False); if (_XA_XdndLeave == None) { return False; } _XA_XdndPosition = XInternAtom(dpy, XAtomName_XdndPosition, False); if (_XA_XdndPosition == None) { return False; } _XA_XdndProxy = XInternAtom(dpy, XAtomName_XdndProxy, False); if (_XA_XdndProxy == None) { return False; } _XA_XdndSelection = XInternAtom(dpy, XAtomName_XdndSelection, False); if (_XA_XdndSelection == None) { return False; } _XA_XdndStatus = XInternAtom(dpy, XAtomName_XdndStatus, False); if (_XA_XdndStatus == None) { return False; } _XA_TextPlain = XInternAtom(dpy, XAtomName_TextPlain, False); if (_XA_TextPlain == None) { return False; } return True; } /******************************* Drag source support **************************/ static Boolean xdnd_convert_proc(Widget w, Atom * selection, Atom * target, Atom * type, XtPointer * value, unsigned long *length, int32_t *format) { #ifdef DEBUG fprintf(stderr,"xdnd_convert_proc\n"); #endif if (*target == _XA_TextPlain) { char* str = drop_data; int len = strlen(str); char* copy = (char*)XtMalloc(len); strcpy(copy, str); *type = *target; *value = (XtPointer)copy; *length = len; *format = 8; return True; } return False; } static Window find_receiver_window(Widget wid, int x, int y) { Display* dpy = XtDisplay(wid); Atom type; int format; unsigned long nitems; unsigned long after; unsigned char *data; Window src_w = XtWindow(wid); Window dest_w = XRootWindowOfScreen(XtScreen(wid)); Window ret_w = None; Window child_w = None; int src_x = x; int src_y = y; while (dest_w != None) { XTranslateCoordinates(dpy, src_w, dest_w, src_x, src_y, &src_x, &src_y, &child_w); if (child_w == None) { break; } src_w = dest_w; dest_w = child_w; type = None; XGetWindowProperty(dpy, dest_w, _XA_XdndAware, 0, 1, False, AnyPropertyType, &type, &format, &nitems, &after, &data); if (type == XA_ATOM && data != NULL) { ret_w = dest_w; } XFree(data); data = NULL; } return ret_w; } static void update_receiver_window(Display* dpy, Window win) { Atom type; int format; unsigned long nitems; unsigned long after; unsigned char *data; if (win == None) { target_win = None; target_proxy_win = None; return; } XGetWindowProperty(dpy, win, _XA_XdndAware, 0, 1, False, AnyPropertyType, &type, &format, &nitems, &after, &data); if (type == XA_ATOM && data != NULL) { Window proxy_win = None; int target_version = *((int*)data); update_dnd_version(target_version); XFree(data); XGetWindowProperty(dpy, win, _XA_XdndProxy, 0, 1, False, XA_WINDOW, &type, &format, &nitems, &after, &data); if (type == XA_WINDOW && data != None) { proxy_win = *((Window*)data); XFree(data); XGetWindowProperty(dpy, proxy_win, _XA_XdndProxy, 0, 1, False, XA_WINDOW, &type, &format, &nitems, &after, &data); if (type != XA_WINDOW || data == NULL || *((Window*)data) != proxy_win) { proxy_win = None; } XFree(data); if (proxy_win == None) { proxy_win = win; } } target_win = win; target_proxy_win = proxy_win; } else { XFree(data); } if (target_proxy_win == None) { target_proxy_win = target_win; } { XWindowAttributes xwa; XGetWindowAttributes(dpy, win, &xwa); XSelectInput(dpy, win, xwa.your_event_mask | EnterWindowMask | LeaveWindowMask); } } static void xdnd_send_enter(Display* dpy, Window source_win, XMotionEvent* event) { XClientMessageEvent enter; enter.type = ClientMessage; enter.window = target_win; enter.format = 32; enter.message_type = _XA_XdndEnter; enter.data.l[0] = source_win; enter.data.l[1] = get_dnd_version() << DND_VERSION_SHIFT; enter.data.l[2] = _XA_TextPlain; enter.data.l[3] = 0; enter.data.l[4] = 0; set_same_answer(event->x_root - 1, event->y_root - 1, 3, 3); #ifdef DEBUG fprintf(stderr,"xdnd_send_enter win=%X\n", target_proxy_win); #endif XSendEvent(dpy, target_proxy_win, False, NoEventMask, (XEvent*)&enter); } static void send_enter(Display* dpy, Window source_win, XMotionEvent* event) { xdnd_send_enter(dpy, source_win, event); } static void xdnd_send_leave(Display* dpy, Window source_win, XMotionEvent* event) { XClientMessageEvent leave; leave.type = ClientMessage; leave.window = target_win; leave.format = 32; leave.message_type = _XA_XdndLeave; leave.data.l[0] = source_win; leave.data.l[1] = 0; leave.data.l[2] = 0; leave.data.l[3] = 0; leave.data.l[4] = 0; #ifdef DEBUG fprintf(stderr,"xdnd_send_leave win=%X\n", target_proxy_win); #endif XSendEvent(dpy, target_proxy_win, False, NoEventMask, (XEvent*)&leave); } static void send_leave(Display* dpy, Window source_win, XMotionEvent* event) { xdnd_send_leave(dpy, source_win, event); } static void xdnd_send_move(Display* dpy, Window source_win, XMotionEvent* event) { XClientMessageEvent move; move.type = ClientMessage; move.window = target_win; move.format = 32; move.message_type = _XA_XdndPosition; move.data.l[0] = source_win; move.data.l[1] = 0; /* flags */ move.data.l[2] = event->x_root << 16 | event->y_root; move.data.l[3] = event->time; /*move.data.l[4] = _XA_XdndActionCopy;*/ move.data.l[4] = _XA_XdndActionMove; if (event != NULL) { switch (event->state & (ShiftMask | ControlMask)) { case 0 : move.data.l[4] = _XA_XdndActionCopy; break; case ShiftMask : move.data.l[4] = _XA_XdndActionMove; break; case ControlMask : move.data.l[4] = _XA_XdndActionCopy; break; case (ShiftMask | ControlMask) : move.data.l[4] = _XA_XdndActionLink; break; } } #ifdef DEBUG fprintf(stderr,"xdnd_send_move win=%X\n", target_proxy_win); #endif XSendEvent(dpy, target_proxy_win, False, NoEventMask, (XEvent*)&move); } static void send_move(Display* dpy, Window source_win, XMotionEvent* event) { xdnd_send_move(dpy, source_win, event); } static void handle_move(Widget w, XMotionEvent* event) { Display* dpy = XtDisplay(w); Window source_win = XtWindow(w); Window receiver_win = None; int flags = 2 | 3 << 4 | 2 << 8; receiver_win = find_receiver_window(w, event->x, event->y); #ifdef DEBUG fprintf(stderr,"handle_move receiver=%X\n", receiver_win); fprintf(stderr," target=%X proxy=%X\n", target_win, target_proxy_win); #endif if (target_win != receiver_win) { if (target_win != None) { send_leave(dpy, source_win, event); } update_receiver_window(dpy, receiver_win); if (target_win != None) { send_enter(dpy, source_win, event); } } if (target_win != None) { send_move(dpy, source_win, event); } } static void handle_xdnd_status(Widget w, XClientMessageEvent* event) { #ifdef TRACE fprintf(stderr,"[Source] dragOver {XDnD}\n"); #endif #ifdef DEBUG fprintf(stderr,"handle_xdnd_status\n"); #endif } static void handle_xdnd_finished(Widget w, XClientMessageEvent* event) { #ifdef TRACE fprintf(stderr,"[Source] dragDropEnd {XDnD}\n"); #endif #ifdef DEBUG fprintf(stderr,"handle_xdnd_finished\n"); #endif cleanup_drag(XtDisplay(w)); fprintf(stderr,"finished\n"); //exit(0); } static void xdnd_handle_client_message(Widget w, XClientMessageEvent* event) { if (event->message_type == _XA_XdndStatus) { handle_xdnd_status(w, event); } else if (event->message_type == _XA_XdndFinished) { handle_xdnd_finished(w, event); } } static void handle_client_message(Widget w, XClientMessageEvent* event) { xdnd_handle_client_message(w, event); } static void xdnd_send_drop(Display* dpy, Window source_win, XButtonEvent* event) { XClientMessageEvent drop; drop.type = ClientMessage; drop.window = target_win; drop.format = 32; drop.message_type = _XA_XdndDrop; drop.data.l[0] = source_win; drop.data.l[1] = 1 << 24; /* flags */ drop.data.l[2] = 0; /* ### */ drop.data.l[3] = event->time; drop.data.l[4] = 0; XSendEvent(dpy, target_proxy_win, False, NoEventMask, (XEvent*)&drop); } static void send_drop(Display* dpy, Window source_win, XButtonEvent* event) { xdnd_send_drop(dpy, source_win, event); } static void drag_event_handler(Widget w, XtPointer client_data, XEvent * event, Boolean * cont) { #ifdef DEBUG fprintf(stderr,"drag_event_handler type=%d in_progress=%X\n", event->type, drag_in_progress); #endif if (!drag_in_progress) { XtRemoveEventHandler(w, DRAG_MASK, False, drag_event_handler, NULL); return; } switch (event->type) { case KeyRelease: { KeySym keysym = XKeycodeToKeysym(XtDisplay(w), event->xkey.keycode, 0); #ifdef DEBUG fprintf(stderr,"\tKeyRelease code=%X sym=%X\n", event->xkey.keycode); #endif if (keysym == XK_Escape) { remove_dnd_grab(XtDisplay(w), event->xkey.time); cleanup_drag(XtDisplay(w)); } *cont = False; break; } case KeyPress: case ButtonPress: break; case MotionNotify: { handle_move(w, (XMotionEvent*)event); break; } case ButtonRelease: #ifdef DEBUG fprintf(stderr,"\tButtonRelease button=%d\n", event->xbutton.button); #endif if (event->xbutton.button == Button1) { remove_dnd_grab(XtDisplay(w), event->xbutton.time); if (target_win != None) { send_drop(XtDisplay(w), XtWindow(w), &event->xbutton); #ifdef DEBUG fprintf(stderr,"DROP_START sent\n"); #endif } else { cleanup_drag(XtDisplay(w)); } *cont = True; } else { *cont = False; } break; case ClientMessage: handle_client_message(w, &event->xclient); break; } } static void initiate_drag(Widget w, XEvent* event) { Display* dpy = XtDisplay(w); int num_targets = 1; Atom target = XA_STRING; int index = -1; unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask; unsigned int root_event_mask = ButtonMotionMask | EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask; if (XtOwnSelection(w, _XA_XdndSelection, event->xmotion.time, xdnd_convert_proc, NULL, NULL) != True) { #ifdef DEBUG fprintf(stderr,"initiate_drag: cannot own selection\n"); #endif return; } { Window root_win = RootWindowOfScreen(XtScreen(w)); XWindowAttributes xwa; Cursor cursor = XCreateFontCursor(dpy, XC_hand2); XGetWindowAttributes(dpy, root_win, &xwa); XSelectInput(dpy, root_win, xwa.your_event_mask | root_event_mask); if (XGrabPointer(dpy, root_win, True, event_mask, GrabModeSync, GrabModeAsync, None, cursor, event->xmotion.time) != GrabSuccess) { #ifdef DEBUG fprintf(stderr,"initiate_drag: XGrabPointer fails\n"); #endif return; } if (XGrabKeyboard(dpy, root_win, False, GrabModeSync, GrabModeAsync, event->xmotion.time) != GrabSuccess) { #ifdef DEBUG fprintf(stderr,"initiate_drag: XGrabKeyboard fails\n"); #endif return; } XAllowEvents(dpy, SyncPointer, event->xmotion.time); } XtInsertEventHandler(w, DRAG_MASK, True, drag_event_handler, NULL, XtListHead); } static void drag_gesture_recognizer(Widget w, XtPointer client_data, XEvent * event, Boolean * cont) { #ifdef DEBUG fprintf(stderr,"drag_gesture_recognizer: type=%d\n", event->type); #endif if (event->type != MotionNotify) { return; } if (drag_in_progress) { #ifdef DEBUG fprintf(stderr,"initiate_drag: in progress\n"); #endif return; } #ifdef DEBUG fprintf(stderr,"drag_gesture_recognizer: start\n"); #endif initiate_drag(w, event); drag_in_progress = True; } static void register_drag_source(Widget w) { #ifdef DEBUG fprintf(stderr,"register_drag_source: w=%X\n", w); #endif XtAddEventHandler(w, ButtonMotionMask | ButtonPressMask, False, drag_gesture_recognizer, (XtPointer)NULL); } static void remove_dnd_grab(Display* dpy, Time time) { XUngrabPointer(dpy, time); XUngrabKeyboard(dpy, time); } static void cleanup_drag(Display* dpy) { #ifdef DEBUG fprintf(stderr,"cleanup_drag\n"); #endif drag_in_progress = False; target_win = None; target_proxy_win = None; } /* XDnD state variables */ static int dnd_version = 0; static int same_x = 0; static int same_y = 0; static int same_w = 0; static int same_h = 0; static void set_same_answer(int x, int y, int w, int h) { same_x = x; same_y = y; same_w = w; same_h = h; } static Boolean is_same_answer(int x, int y) { if (!drag_in_progress) { return False; } if (x >= same_x && x < same_x + same_w && y >= same_y && y < same_y + same_h) { return True; } return False; } static void update_dnd_version(int version) { dnd_version = XDnDVersion < version ? XDnDVersion : version; } static int get_dnd_version() { return dnd_version; } /******************************* Drop target support **************************/ static void handle_xdnd_enter(Widget w, XEvent* event) { #ifdef TRACE fprintf(stderr,"[Target] dragEnter {XDnD}\n"); #endif #ifdef DEBUG fprintf(stderr,"handle_xdnd_enter\n"); #endif } static void handle_xdnd_leave(Widget w, XEvent* event) { #ifdef TRACE fprintf(stderr,"[Target] dragExit {XDnD}\n"); #endif #ifdef DEBUG fprintf(stderr,"handle_xdnd_leave\n"); #endif } static void handle_xdnd_position(Widget w, XEvent* event) { Window drag_source = event->xclient.data.l[0]; XClientMessageEvent response; response.type = ClientMessage; response.window = drag_source; response.format = 32; response.message_type = _XA_XdndStatus; response.data.l[0] = XtWindow(w); response.data.l[1] = 0; /* flags */ response.data.l[2] = 0; /* x, y */ response.data.l[3] = 0; /* w, h */ response.data.l[4] = 0; /* action */ response.data.l[1] |= ACCEPT_DROP_FLAG; /*response.data.l[4] = _XA_XdndActionCopy;*/ response.data.l[4] = _XA_XdndActionMove; #ifdef TRACE fprintf(stderr,"[Target] dragOver {XDnD}\n"); #endif #ifdef DEBUG fprintf(stderr,"handle_xdnd_position win=%ld\n", event->xclient.data.l[0]); #endif XSendEvent(XtDisplay(w), drag_source, False, NoEventMask, (XEvent*)&response); } static void selection_callback_proc(Widget w, XtPointer client_data, Atom* selection, Atom* type, XtPointer value, unsigned long* length, int* format) { if (*type == _XA_TextPlain) { #ifdef DEBUG fprintf(stderr,"selection_callback_proc data=%s\n", (char*)value); #endif #ifdef TRACE fprintf(stderr,"[Target] drop {XDnD} data=%s\n", (char*)value); #endif } if (value != NULL && *length != 0) { drop_data = calloc(1, 2*(*length)); strcpy(drop_data, (char*)value); strcpy(drop_data + (*length), (char*)value); #ifdef TRACE fprintf(stderr,"[Target] drag data=%s\n", drop_data); #endif } if (value != NULL) { XtFree((char*)value); } } static void handle_xdnd_drop(Widget w, XEvent* event) { #ifdef TRACE fprintf(stderr,"[Target] drop {XDnD}\n"); #endif XtGetSelectionValue(w, _XA_XdndSelection, _XA_TextPlain, selection_callback_proc, NULL, XtLastTimestampProcessed(XtDisplay(w))); #ifdef DEBUG fprintf(stderr,"handle_xdnd_drop\n"); #endif { Window drag_source = event->xclient.data.l[0]; XClientMessageEvent finished; finished.type = ClientMessage; finished.window = drag_source; finished.format = 32; finished.message_type = _XA_XdndFinished; finished.data.l[0] = XtWindow(w); finished.data.l[1] = 0; /* flags */ XSendEvent(XtDisplay(w), drag_source, False, NoEventMask, (XEvent*)&finished); } } static void target_client_message_handler(Widget w, XtPointer client_data, XEvent* event, Boolean* cont) { #ifdef DEBUG fprintf(stderr,"target_client_message_handler\n"); #endif if (event->type != ClientMessage) { return; } if (event->xclient.message_type == _XA_XdndEnter) { handle_xdnd_enter(w, event); } else if (event->xclient.message_type == _XA_XdndPosition) { handle_xdnd_position(w, event); } else if (event->xclient.message_type == _XA_XdndLeave) { handle_xdnd_leave(w, event); } else if (event->xclient.message_type == _XA_XdndDrop) { handle_xdnd_drop(w, event); } } static Boolean register_xdnd_drop_target(Widget widget) { Widget w = NULL; for (w = widget; w != NULL && !XtIsShell(w); w = XtParent(w)); if (w == NULL || XtWindow(w) == None) { return False; } #ifdef DEBUG fprintf(stderr,"register_xdnd_drop_target win=%X\n", XtWindow(w)); #endif XChangeProperty(XtDisplay(w), XtWindow(w), _XA_XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*)&XDnDVersion, 1); XtAddEventHandler(w, EnterWindowMask | LeaveWindowMask, True, target_client_message_handler, NULL); } /*************************** End drop target support **************************/