|  | /* | 
|  | * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "wine/port.h" | 
|  |  | 
|  | #include <string.h> | 
|  | #ifdef HAVE_UNISTD_H | 
|  | # include <unistd.h> | 
|  | #endif | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #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, { (DWORD_PTR)(__FILE__ ": xdnd_cs") } | 
|  | }; | 
|  | static CRITICAL_SECTION xdnd_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; | 
|  |  | 
|  |  | 
|  | /************************************************************************** | 
|  | * X11DRV_XDND_EnterEvent | 
|  | * | 
|  | * Handle an XdndEnter event. | 
|  | */ | 
|  | void X11DRV_XDND_EnterEvent( HWND hWnd, XClientMessageEvent *event ) | 
|  | { | 
|  | Atom *xdndtypes; | 
|  | unsigned long count = 0; | 
|  |  | 
|  | TRACE("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 = (Atom*) &event->data.l[2]; | 
|  | } | 
|  |  | 
|  | if (TRACE_ON(xdnd)) | 
|  | { | 
|  | unsigned int i = 0; | 
|  |  | 
|  | wine_tsx11_lock(); | 
|  | for (; i < count; i++) | 
|  | { | 
|  | if (xdndtypes[i] != 0) | 
|  | { | 
|  | char * pn = XGetAtomName(event->display, xdndtypes[i]); | 
|  | TRACE("XDNDEnterAtom %ld: %s\n", xdndtypes[i], pn); | 
|  | XFree(pn); | 
|  | } | 
|  | } | 
|  | wine_tsx11_unlock(); | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | * X11DRV_XDND_PositionEvent | 
|  | * | 
|  | * Handle an XdndPosition event. | 
|  | */ | 
|  | void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) | 
|  | { | 
|  | 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("action req: %ld accept(%d) at x(%d),y(%d)\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 */ | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | * X11DRV_XDND_DropEvent | 
|  | * | 
|  | * Handle an XdndDrop event. | 
|  | */ | 
|  | void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) | 
|  | { | 
|  | XClientMessageEvent e; | 
|  |  | 
|  | TRACE("\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. */ | 
|  | memset(&e, 0, 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(); | 
|  | } | 
|  |  | 
|  | /************************************************************************** | 
|  | * X11DRV_XDND_LeaveEvent | 
|  | * | 
|  | * Handle an XdndLeave event. | 
|  | */ | 
|  | void X11DRV_XDND_LeaveEvent( HWND hWnd, XClientMessageEvent *event ) | 
|  | { | 
|  | TRACE("DND Operation canceled\n"); | 
|  |  | 
|  | X11DRV_XDND_FreeDragDropOp(); | 
|  |  | 
|  | /* FIXME: Notify OLE of DragLeave */ | 
|  | } | 
|  |  | 
|  |  | 
|  | /************************************************************************** | 
|  | * 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)); | 
|  | wine_tsx11_lock(); | 
|  | XFree(data); | 
|  | wine_tsx11_unlock(); | 
|  | } | 
|  |  | 
|  | *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 = 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 <dndfmt> 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 <dndfmt> 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 = 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; | 
|  | } |