From 032bdc9f1dffbf2e207b54fa6e0bcacab2fc27d7 Mon Sep 17 00:00:00 2001 From: Ulrich Czekalla Date: Thu, 20 Nov 2003 22:09:51 +0000 Subject: [PATCH] Initial implementation to support XDND protocol. --- dlls/x11drv/Makefile.in | 1 + dlls/x11drv/event.c | 3 +- dlls/x11drv/window.c | 21 ++ dlls/x11drv/x11drv.h | 19 ++ dlls/x11drv/xdnd.c | 559 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 dlls/x11drv/xdnd.c diff --git a/dlls/x11drv/Makefile.in b/dlls/x11drv/Makefile.in index dc24c195be7..481b00b780d 100644 --- a/dlls/x11drv/Makefile.in +++ b/dlls/x11drv/Makefile.in @@ -35,6 +35,7 @@ C_SRCS = \ winpos.c \ x11ddraw.c \ x11drv_main.c \ + xdnd.c \ xrandr.c \ xrender.c \ xvidmode.c diff --git a/dlls/x11drv/event.c b/dlls/x11drv/event.c index 1d2d4906ee2..67cb16e359b 100644 --- a/dlls/x11drv/event.c +++ b/dlls/x11drv/event.c @@ -1227,7 +1227,8 @@ static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event ) else if (event->data.l[0] == DndURL) EVENT_DropURLs(hWnd, event); } - else { + else if (!X11DRV_XDND_Event(hWnd, event)) + { #if 0 /* enable this if you want to see the message */ unsigned char* p_data = NULL; diff --git a/dlls/x11drv/window.c b/dlls/x11drv/window.c index 77e1f2e11f5..0d334237146 100644 --- a/dlls/x11drv/window.c +++ b/dlls/x11drv/window.c @@ -81,7 +81,24 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] = "_NET_WM_PID", "_NET_WM_PING", "_NET_WM_NAME", + "XdndAware", + "XdndEnter", + "XdndPosition", + "XdndStatus", + "XdndLeave", + "XdndFinished", + "XdndDrop", + "XdndActionCopy", + "XdndActionMove", + "XdndActionLink", + "XdndActionAsk", + "XdndActionPrivate", + "XdndSelection", + "XdndTarget", + "XdndTypeList", "image/gif", + "text/html", + "text/plain", "text/rtf", "text/richtext" }; @@ -356,6 +373,7 @@ void X11DRV_set_wm_hints( Display *display, WND *win ) XWMHints* wm_hints; Atom protocols[3]; MwmHints mwm_hints; + Atom dndVersion = 4; int i; wine_tsx11_lock(); @@ -428,6 +446,9 @@ void X11DRV_set_wm_hints( Display *display, WND *win ) x11drv_atom(_MOTIF_WM_HINTS), 32, PropModeReplace, (char*)&mwm_hints, sizeof(mwm_hints)/sizeof(long) ); + XChangeProperty( display, data->whole_window, x11drv_atom(XdndAware), + XA_ATOM, 32, PropModeReplace, (unsigned char*)&dndVersion, 1 ); + wm_hints = XAllocWMHints(); wine_tsx11_unlock(); diff --git a/dlls/x11drv/x11drv.h b/dlls/x11drv/x11drv.h index 3b0ecb3310a..245790ab5f3 100644 --- a/dlls/x11drv/x11drv.h +++ b/dlls/x11drv/x11drv.h @@ -231,6 +231,8 @@ extern void X11DRV_XRender_UpdateDrawable(X11DRV_PDEVICE *physDev); extern void X11DRV_OpenGL_Init(Display *display); extern XVisualInfo *X11DRV_setup_opengl_visual(Display *display); +extern int X11DRV_XDND_Event(HWND hWnd, XClientMessageEvent *event); + /* exported dib functions for now */ /* Additional info for DIB section objects */ @@ -390,7 +392,24 @@ enum x11drv_atoms XATOM__NET_WM_PID, XATOM__NET_WM_PING, XATOM__NET_WM_NAME, + XATOM_XdndAware, + XATOM_XdndEnter, + XATOM_XdndPosition, + XATOM_XdndStatus, + XATOM_XdndLeave, + XATOM_XdndFinished, + XATOM_XdndDrop, + XATOM_XdndActionCopy, + XATOM_XdndActionMove, + XATOM_XdndActionLink, + XATOM_XdndActionAsk, + XATOM_XdndActionPrivate, + XATOM_XdndSelection, + XATOM_XdndTarget, + XATOM_XdndTypeList, XATOM_image_gif, + XATOM_text_html, + XATOM_text_plain, XATOM_text_rtf, XATOM_text_richtext, NB_XATOMS diff --git a/dlls/x11drv/xdnd.c b/dlls/x11drv/xdnd.c new file mode 100644 index 00000000000..2f50d4fd19a --- /dev/null +++ b/dlls/x11drv/xdnd.c @@ -0,0 +1,559 @@ +/* + * XDND handler code + * + * Copyright 2003 Ulrich Czekalla + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "ts_xlib.h" +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#include + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "winerror.h" +#include "wownt32.h" + +#include "x11drv.h" +#include "shlobj.h" /* DROPFILES */ + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(xdnd); + +/* Maximum wait time for selection notify */ +#define SELECTION_RETRIES 500 /* wait for .1 seconds */ +#define SELECTION_WAIT 1000 /* us */ + +typedef struct tagXDNDDATA +{ + int cf_win; + Atom cf_xdnd; + void *data; + unsigned int size; + struct tagXDNDDATA *next; +} XDNDDATA, *LPXDNDDATA; + +static LPXDNDDATA XDNDData = NULL; +static POINT XDNDxy = { 0, 0 }; + +static void X11DRV_XDND_InsertXDNDData(int property, int format, void* data, unsigned int len); +static int X11DRV_XDND_DeconstructTextPlain(int property, void* data, int len); +static int X11DRV_XDND_DeconstructTextHTML(int property, void* data, int len); +static int X11DRV_XDND_MapFormat(unsigned int property, unsigned char *data, int len); +static void X11DRV_XDND_ResolveProperty(Display *display, Window xwin, Time tm, + Atom *types, unsigned long *count); +static void X11DRV_XDND_SendDropFiles(HWND hwnd); +static void X11DRV_XDND_FreeDragDropOp(void); +static unsigned int X11DRV_XDND_UnixToDos(char** lpdest, char* lpsrc, int len); +static DROPFILES* X11DRV_XDND_BuildDropFiles(char* filename, unsigned int len, POINT pt); + +static CRITICAL_SECTION xdnd_cs; +static CRITICAL_SECTION_DEBUG critsect_debug = +{ + 0, 0, &xdnd_cs, + { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, + 0, 0, { 0, (DWORD)(__FILE__ ": xdnd_cs") } +}; +static CRITICAL_SECTION xdnd_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; + +/************************************************************************** + * X11DRV_XDND_Event + * + * Entry point for X11 XDND events. Returns FALSE if event is not handled. + */ +int X11DRV_XDND_Event(HWND hWnd, XClientMessageEvent *event) +{ + int isXDNDMsg = 1; + + TRACE("0x%p\n", hWnd); + + if (event->message_type == x11drv_atom(XdndEnter)) + { + Atom *xdndtypes; + unsigned long count = 0; + + TRACE("XDNDEnter: ver(%ld) check-XdndTypeList(%ld) data=%ld,%ld,%ld,%ld,%ld\n", + (event->data.l[1] & 0xFF000000) >> 24, (event->data.l[1] & 1), + event->data.l[0], event->data.l[1], event->data.l[2], + event->data.l[3], event->data.l[4]); + + /* If the source supports more than 3 data types we retrieve + * the entire list. */ + if (event->data.l[1] & 1) + { + Atom acttype; + int actfmt; + unsigned long bytesret; + + /* Request supported formats from source window */ + wine_tsx11_lock(); + XGetWindowProperty(event->display, event->data.l[0], x11drv_atom(XdndTypeList), + 0, 65535, FALSE, AnyPropertyType, &acttype, &actfmt, &count, + &bytesret, (unsigned char**)&xdndtypes); + wine_tsx11_unlock(); + } + else + { + count = 3; + xdndtypes = &event->data.l[2]; + } + + if (TRACE_ON(xdnd)) + { + unsigned int i = 0; + + for (; i < count; i++) + { + if (xdndtypes[i] != 0) + { + char * pn = TSXGetAtomName(event->display, xdndtypes[i]); + TRACE("XDNDEnterAtom %ld: %s\n", xdndtypes[i], pn); + TSXFree(pn); + } + } + } + + /* Do a one-time data read and cache results */ + X11DRV_XDND_ResolveProperty(event->display, event->window, + event->data.l[1], xdndtypes, &count); + + if (event->data.l[1] & 1) + XFree(xdndtypes); + } + else if (event->message_type == x11drv_atom(XdndPosition)) + { + XClientMessageEvent e; + int accept = 0; /* Assume we're not accepting */ + + XDNDxy.x = event->data.l[2] >> 16; + XDNDxy.y = event->data.l[2] & 0xFFFF; + + /* FIXME: Notify OLE of DragEnter. Result determines if we accept */ + + if (GetWindowLongW( hWnd, GWL_EXSTYLE ) & WS_EX_ACCEPTFILES) + accept = 1; + + TRACE("XDNDPosition. action req: %ld accept(%d) at x(%ld),y(%ld)\n", + event->data.l[4], accept, XDNDxy.x, XDNDxy.y); + + /* + * Let source know if we're accepting the drop by + * sending a status message. + */ + e.type = ClientMessage; + e.display = event->display; + e.window = event->data.l[0]; + e.message_type = x11drv_atom(XdndStatus); + e.format = 32; + e.data.l[0] = event->window; + e.data.l[1] = accept; + e.data.l[2] = 0; /* Empty Rect */ + e.data.l[3] = 0; /* Empty Rect */ + if (accept) + e.data.l[4] = event->data.l[4]; + else + e.data.l[4] = None; + wine_tsx11_lock(); + XSendEvent(event->display, event->data.l[0], False, NoEventMask, (XEvent*)&e); + wine_tsx11_unlock(); + + /* FIXME: if drag accepted notify OLE of DragOver */ + } + else if (event->message_type == x11drv_atom(XdndDrop)) + { + XClientMessageEvent e; + + TRACE("XDNDDrop\n"); + + /* If we have a HDROP type we send a WM_ACCEPTFILES.*/ + if (GetWindowLongW( hWnd, GWL_EXSTYLE ) & WS_EX_ACCEPTFILES) + X11DRV_XDND_SendDropFiles( hWnd ); + + /* FIXME: Notify OLE of Drop */ + X11DRV_XDND_FreeDragDropOp(); + + /* Tell the target we are finished. */ + bzero(&e, sizeof(e)); + e.type = ClientMessage; + e.display = event->display; + e.window = event->data.l[0]; + e.message_type = x11drv_atom(XdndFinished); + e.format = 32; + e.data.l[0] = event->window; + wine_tsx11_lock(); + XSendEvent(event->display, event->data.l[0], False, NoEventMask, (XEvent*)&e); + wine_tsx11_unlock(); + } + else if (event->message_type == x11drv_atom(XdndLeave)) + { + TRACE("DND Operation canceled\n"); + + X11DRV_XDND_FreeDragDropOp(); + + /* FIXME: Notify OLE of DragLeave */ + } + else /* Not an XDND message */ + isXDNDMsg = 0; + + return isXDNDMsg; +} + + +/************************************************************************** + * X11DRV_XDND_ResolveProperty + * + * Resolve all MIME types to windows clipboard formats. All data is cached. + */ +static void X11DRV_XDND_ResolveProperty(Display *display, Window xwin, Time tm, + Atom *types, unsigned long *count) +{ + unsigned int i, j; + BOOL res; + XEvent xe; + Atom acttype; + int actfmt; + unsigned long bytesret, icount; + int entries = 0; + unsigned char* data = NULL; + + TRACE("count(%ld)\n", *count); + + X11DRV_XDND_FreeDragDropOp(); /* Clear previously cached data */ + + for (i = 0; i < *count; i++) + { + TRACE("requesting atom %ld from xwin %ld\n", types[i], xwin); + + if (types[i] == 0) + continue; + + wine_tsx11_lock(); + XConvertSelection(display, x11drv_atom(XdndSelection), types[i], + x11drv_atom(XdndTarget), xwin, /*tm*/CurrentTime); + wine_tsx11_unlock(); + + /* + * Wait for SelectionNotify + */ + for (j = 0; j < SELECTION_RETRIES; j++) + { + wine_tsx11_lock(); + res = XCheckTypedWindowEvent(display, xwin, SelectionNotify, &xe); + wine_tsx11_unlock(); + if (res && xe.xselection.selection == x11drv_atom(XdndSelection)) break; + + usleep(SELECTION_WAIT); + } + + if (xe.xselection.property == None) + continue; + + wine_tsx11_lock(); + XGetWindowProperty(display, xwin, x11drv_atom(XdndTarget), 0, 65535, FALSE, + AnyPropertyType, &acttype, &actfmt, &icount, &bytesret, &data); + wine_tsx11_unlock(); + + entries += X11DRV_XDND_MapFormat(types[i], data, icount * (actfmt / 8)); + TSXFree(data); + } + + *count = entries; +} + + +/************************************************************************** + * X11DRV_XDND_InsertXDNDData + * + * Cache available XDND property + */ +static void X11DRV_XDND_InsertXDNDData(int property, int format, void* data, unsigned int len) +{ + LPXDNDDATA current = (LPXDNDDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(XDNDDATA)); + + if (current) + { + EnterCriticalSection(&xdnd_cs); + current->next = XDNDData; + current->cf_xdnd = property; + current->cf_win = format; + current->data = data; + current->size = len; + XDNDData = current; + LeaveCriticalSection(&xdnd_cs); + } +} + + +/************************************************************************** + * X11DRV_XDND_MapFormat + * + * Map XDND MIME format to windows clipboard format. + */ +static int X11DRV_XDND_MapFormat(unsigned int property, unsigned char *data, int len) +{ + void* xdata; + int count = 0; + + TRACE("%d: %s\n", property, data); + + /* Always include the raw type */ + xdata = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len); + memcpy(xdata, data, len); + X11DRV_XDND_InsertXDNDData(property, property, xdata, len); + count++; + + if (property == x11drv_atom(text_plain)) + count += X11DRV_XDND_DeconstructTextPlain(property, data, len); + else if (property == x11drv_atom(text_html)) + count += X11DRV_XDND_DeconstructTextHTML(property, data, len); + + return count; +} + + +/************************************************************************** + * X11DRV_XDND_DeconstructTextPlain + * + * Interpret text/plain Data and add records to linked list + */ +static int X11DRV_XDND_DeconstructTextPlain(int property, void* data, int len) +{ + char *p = (char*) data; + char* dostext; + int count = 0; + + /* Always suppply plain text */ + X11DRV_XDND_UnixToDos(&dostext, (char*)data, len); + X11DRV_XDND_InsertXDNDData(property, CF_TEXT, dostext, strlen(dostext)); + count++; + + TRACE("CF_TEXT (%d): %s\n", CF_TEXT, dostext); + + /* Check for additional mappings */ + while (*p != '\0' && *p != ':') /* Advance to end of protocol */ + p++; + + if (*p == ':') + { + if (!strncasecmp(data, "http", 4)) + { + X11DRV_XDND_InsertXDNDData(property, RegisterClipboardFormatA("UniformResourceLocator"), + strdup(dostext), strlen(dostext)); + count++; + + TRACE("UniformResourceLocator: %s\n", dostext); + } + else if (!strncasecmp(data, "file", 4)) + { + DROPFILES* pdf; + + pdf = X11DRV_XDND_BuildDropFiles(p+1, len - 5, XDNDxy); + if (pdf) + { + unsigned int size = HeapSize(GetProcessHeap(), 0, pdf); + + X11DRV_XDND_InsertXDNDData(property, CF_HDROP, pdf, size); + count++; + } + + TRACE("CF_HDROP: %p\n", pdf); + } + } + + return count; +} + + +/************************************************************************** + * X11DRV_XDND_DeconstructTextHTML + * + * Interpret text/html data and add records to linked list + */ +static int X11DRV_XDND_DeconstructTextHTML(int property, void* data, int len) +{ + char* dostext; + + X11DRV_XDND_UnixToDos(&dostext, data, len); + + X11DRV_XDND_InsertXDNDData(property, + RegisterClipboardFormatA("UniformResourceLocator"), dostext, strlen(dostext)); + + TRACE("UniformResourceLocator: %s\n", dostext); + + return 1; +} + + +/************************************************************************** + * X11DRV_XDND_SendDropFiles + */ +static void X11DRV_XDND_SendDropFiles(HWND hwnd) +{ + LPXDNDDATA current; + + EnterCriticalSection(&xdnd_cs); + + current = XDNDData; + + /* Find CF_HDROP type if any */ + while (current != NULL) + { + if (current->cf_win == CF_HDROP) + break; + current = current->next; + } + + if (current != NULL) + { + DROPFILES *lpDrop = (DROPFILES*) current->data; + + if (lpDrop) + { + lpDrop->pt.x = XDNDxy.x; + lpDrop->pt.y = XDNDxy.y; + + TRACE("Sending WM_DROPFILES: hWnd(0x%p) %p(%s)\n", hwnd, + ((char*)lpDrop) + lpDrop->pFiles, ((char*)lpDrop) + lpDrop->pFiles); + + PostMessageA(hwnd, WM_DROPFILES, (WPARAM)lpDrop, 0L); + } + } + + LeaveCriticalSection(&xdnd_cs); +} + + +/************************************************************************** + * X11DRV_XDND_FreeDragDropOp + */ +static void X11DRV_XDND_FreeDragDropOp(void) +{ + LPXDNDDATA next; + LPXDNDDATA current; + + TRACE("\n"); + + EnterCriticalSection(&xdnd_cs); + + current = XDNDData; + + /** Free data cache */ + while (current != NULL) + { + next = current->next; + HeapFree(GetProcessHeap(), 0, current); + current = next; + } + + XDNDData = NULL; + XDNDxy.x = XDNDxy.y = 0; + + LeaveCriticalSection(&xdnd_cs); +} + + + +/************************************************************************** + * X11DRV_XDND_BuildDropFiles + */ +static DROPFILES* X11DRV_XDND_BuildDropFiles(char* filename, unsigned int len, POINT pt) +{ + char* pfn; + int pathlen; + char path[MAX_PATH]; + DROPFILES *lpDrop = NULL; + + /* Advance to last starting slash */ + pfn = filename + 1; + while (*pfn && (*pfn == '\\' || *pfn =='/')) + { + pfn++; + filename++; + } + + /* Remove any trailing \r or \n */ + while (*pfn) + { + if (*pfn == '\r' || *pfn == '\n') + { + *pfn = 0; + break; + } + pfn++; + } + + TRACE("%s\n", filename); + + pathlen = GetLongPathNameA(filename, path, MAX_PATH); + if (pathlen) + { + lpDrop = (DROPFILES*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(DROPFILES) + pathlen + 1); + + lpDrop->pFiles = sizeof(DROPFILES); + lpDrop->pt.x = pt.x; + lpDrop->pt.y = pt.y; + lpDrop->fNC = 0; + lpDrop->fWide = FALSE; + + strcpy(((char*)lpDrop)+lpDrop->pFiles, path); + } + + TRACE("resolved %s\n", lpDrop ? filename : NULL); + + return lpDrop; +} + + +/************************************************************************** + * X11DRV_XDND_UnixToDos + */ +static unsigned int X11DRV_XDND_UnixToDos(char** lpdest, char* lpsrc, int len) +{ + int i; + unsigned int destlen, lines; + + for (i = 0, lines = 0; i <= len; i++) + { + if (lpsrc[i] == '\n') + lines++; + } + + destlen = len + lines + 1; + + if (lpdest) + { + char* lpstr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, destlen); + for (i = 0, lines = 0; i <= len; i++) + { + if (lpsrc[i] == '\n') + lpstr[++lines + i] = '\r'; + lpstr[lines + i] = lpsrc[i]; + } + + *lpdest = lpstr; + } + + return lines; +}