diff --git a/include/MiniFB.h b/include/MiniFB.h index c5c61208..b9e1fde4 100644 --- a/include/MiniFB.h +++ b/include/MiniFB.h @@ -52,6 +52,8 @@ void mfb_set_char_input_callback(struct mfb_window *window, mfb_c void mfb_set_mouse_button_callback(struct mfb_window *window, mfb_mouse_button_func callback); void mfb_set_mouse_move_callback(struct mfb_window *window, mfb_mouse_move_func callback); void mfb_set_mouse_scroll_callback(struct mfb_window *window, mfb_mouse_scroll_func callback); +void mfb_set_file_drag_callback(struct mfb_window *window, mfb_file_drag_func callback); +void mfb_set_file_drop_callback(struct mfb_window *window, mfb_file_drop_func callback); // Getters const char * mfb_get_key_name(mfb_key key); diff --git a/include/MiniFB_enums.h b/include/MiniFB_enums.h index d3dca1ad..167a00b2 100755 --- a/include/MiniFB_enums.h +++ b/include/MiniFB_enums.h @@ -182,4 +182,6 @@ typedef void(*mfb_char_input_func)(struct mfb_window *window, unsigned int code) typedef void(*mfb_mouse_button_func)(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool isPressed); typedef void(*mfb_mouse_move_func)(struct mfb_window *window, int x, int y); typedef void(*mfb_mouse_scroll_func)(struct mfb_window *window, mfb_key_mod mod, float deltaX, float deltaY); +typedef void(*mfb_file_drag_func)(struct mfb_window *window, int x, int y); +typedef void(*mfb_file_drop_func)(struct mfb_window *window, char *file_list, int x, int y); diff --git a/src/MiniFB_common.c b/src/MiniFB_common.c index c442832d..173a7f0f 100755 --- a/src/MiniFB_common.c +++ b/src/MiniFB_common.c @@ -1,6 +1,7 @@ -#include "MiniFB.h" +#include "../include/MiniFB.h" #include "WindowData.h" -#include +#include "MiniFB_internal.h" +#include //------------------------------------- short int g_keycodes[512] = { 0 }; @@ -87,6 +88,22 @@ mfb_set_mouse_scroll_callback(struct mfb_window *window, mfb_mouse_scroll_func c } } +//------------------------------------- +void mfb_set_file_drag_callback(struct mfb_window *window, mfb_file_drag_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->file_drag_func = callback; + } +} + +//------------------------------------- +void mfb_set_file_drop_callback(struct mfb_window *window, mfb_file_drop_func callback) { + if(window != 0x0) { + SWindowData *window_data = (SWindowData *) window; + window_data->file_drop_func = callback; + } +} + //------------------------------------- void mfb_set_user_data(struct mfb_window *window, void *user_data) { @@ -224,6 +241,24 @@ mfb_get_key_buffer(struct mfb_window *window) { return 0; } +//------------------------------------- +/* get next filename in file list (returns 0 when there are none) */ +char *mfb_get_dropped_file(char *file_list) +{ + const char *delim = "\n\r"; + + if (!file_list) + return 0; + + /* a new file list has been provided: tokenize it and return first token */ + if (strchr(file_list, '\n')) + return strtok(file_list, delim); + + /* return next token from previous file list */ + else + return strtok(0, delim); +} + //------------------------------------- const char * mfb_get_key_name(mfb_key key) { diff --git a/src/MiniFB_internal.h b/src/MiniFB_internal.h index 3d73fe45..44df46fb 100755 --- a/src/MiniFB_internal.h +++ b/src/MiniFB_internal.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "../include/MiniFB.h" #include "WindowData.h" #define kCall(func, ...) if(window_data && window_data->func) window_data->func((struct mfb_window *) window_data, __VA_ARGS__); diff --git a/src/MiniFB_linux.c b/src/MiniFB_linux.c index 5d6611d5..727b2f63 100644 --- a/src/MiniFB_linux.c +++ b/src/MiniFB_linux.c @@ -1,7 +1,7 @@ #if defined(__linux__) #include -#include +#include "../include/MiniFB.h" extern double g_timer_frequency; extern double g_timer_resolution; @@ -10,7 +10,7 @@ extern double g_timer_resolution; //#define kClock CLOCK_REALTIME uint64_t -mfb_timer_tick() { +mfb_timer_tick(void) { struct timespec time; if (clock_gettime(kClock, &time) != 0) { @@ -21,7 +21,7 @@ mfb_timer_tick() { } void -mfb_timer_init() { +mfb_timer_init(void) { struct timespec res; if (clock_getres(kClock, &res) != 0) { diff --git a/src/MiniFB_timer.c b/src/MiniFB_timer.c index 2dd755a9..f1fd1261 100644 --- a/src/MiniFB_timer.c +++ b/src/MiniFB_timer.c @@ -1,4 +1,4 @@ -#include "MiniFB.h" +#include "../include/MiniFB.h" #include "MiniFB_internal.h" #include diff --git a/src/WindowData.h b/src/WindowData.h index 13ed2ddb..de4c6525 100644 --- a/src/WindowData.h +++ b/src/WindowData.h @@ -2,7 +2,7 @@ #include #include -#include +#include "../include/MiniFB_enums.h" //------------------------------------- typedef struct { @@ -16,6 +16,8 @@ typedef struct { mfb_mouse_button_func mouse_btn_func; mfb_mouse_move_func mouse_move_func; mfb_mouse_scroll_func mouse_wheel_func; + mfb_file_drag_func file_drag_func; + mfb_file_drop_func file_drop_func; uint32_t window_width; uint32_t window_height; diff --git a/src/windows/WinMiniFB.c b/src/windows/WinMiniFB.c index 933785f5..d3e37834 100644 --- a/src/windows/WinMiniFB.c +++ b/src/windows/WinMiniFB.c @@ -1,6 +1,5 @@ -#include -#include -#include +#include "../MiniFB_internal.h" +#include "../WindowData.h" #include "WindowData_Win.h" #if defined(USE_OPENGL_API) #include "gl/MiniFB_GL.h" @@ -9,6 +8,15 @@ #include /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define WANT_DPI_AWARENESS 1 +#ifdef MINIFB_NO_DPI_AWARENESS + #undef WANT_DPI_AWARENESS +#endif + +#ifndef USER_DEFAULT_SCREEN_DPI +# define USER_DEFAULT_SCREEN_DPI 96.0 +#endif + // Copied (and modified) from Windows Kit 10 to avoid setting _WIN32_WINNT to a higher version typedef enum mfb_PROCESS_DPI_AWARENESS { mfb_PROCESS_DPI_UNAWARE = 0, @@ -49,9 +57,60 @@ HMODULE mfb_shcore_dll = 0x0; PFN_SetProcessDpiAwareness mfb_SetProcessDpiAwareness = 0x0; PFN_GetDpiForMonitor mfb_GetDpiForMonitor = 0x0; +// private +static void *toWchar(const char *src) +{ + if(!src) + return 0; + +#ifndef _UNICODE + return strdup(src); +#else + wchar_t *out = 0; + size_t src_length; + int length; + + src_length = strlen(src); + length = MultiByteToWideChar(CP_UTF8, 0, src, src_length, 0, 0); + out = malloc((length+1) * sizeof(*out)); + if (out) { + MultiByteToWideChar(CP_UTF8, 0, src, src_length, out, length); + out[length] = L'\0'; + } + return out; +#endif +} + +// private +static char *toUtf8(const void* src) +{ + if (!src) + return 0; + +#ifndef _UNICODE + return strdup(src); +#else + char *out = 0; + size_t src_length = 0; + int length; + + src_length = wcslen(src); + length = WideCharToMultiByte(CP_UTF8, 0, src, src_length, + 0, 0, NULL, NULL); + out = malloc((length+1) * sizeof(char)); + if (out) { + WideCharToMultiByte(CP_UTF8, 0, src, src_length, + out, length, NULL, NULL); + out[length] = '\0'; + } + return out; +#endif +} + //-- void load_functions() { +#ifdef WANT_DPI_AWARENESS if(mfb_user32_dll == 0x0) { mfb_user32_dll = LoadLibraryA("user32.dll"); if (mfb_user32_dll != 0x0) { @@ -69,13 +128,14 @@ load_functions() { mfb_GetDpiForMonitor = (PFN_GetDpiForMonitor) GetProcAddress(mfb_shcore_dll, "GetDpiForMonitor"); } } +#endif } //-- // NOT Thread safe. Just convenient (Don't do this at home guys) char * GetErrorMessage() { - static char buffer[256]; + TCHAR buffer[256]; buffer[0] = 0; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -86,12 +146,13 @@ GetErrorMessage() { sizeof(buffer), NULL); - return buffer; + return toUtf8(buffer); } //-- void dpi_aware() { +#ifdef WANT_DPI_AWARENESS if (mfb_SetProcessDpiAwarenessContext != 0x0) { if(mfb_SetProcessDpiAwarenessContext(mfb_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == false) { uint32_t error = GetLastError(); @@ -116,11 +177,13 @@ dpi_aware() { fprintf(stderr, "Error (SetProcessDPIAware): %s\n", GetErrorMessage()); } } +#endif } //-- void get_monitor_scale(HWND hWnd, float *scale_x, float *scale_y) { +#ifdef WANT_DPI_AWARENESS UINT x, y; if(mfb_GetDpiForMonitor != 0x0) { @@ -147,6 +210,12 @@ get_monitor_scale(HWND hWnd, float *scale_x, float *scale_y) { *scale_y = 1; } } +#else + if (scale_x) + *scale_x = 1; + if (scale_y) + *scale_y = 1; +#endif } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -319,7 +388,7 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { kCall(mouse_wheel_func, translate_mod(), 0.0f, (SHORT)HIWORD(wParam) / (float)WHEEL_DELTA); } break; - +#if (WINVER >= _WIN32_WINNT_VISTA) /* only for Windows Vista and later */ case WM_MOUSEHWHEEL: // This message is only sent on Windows Vista and later // NOTE: The X-axis is inverted for consistency with macOS and X11 @@ -327,9 +396,11 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { kCall(mouse_wheel_func, translate_mod(), -((SHORT)HIWORD(wParam) / (float)WHEEL_DELTA), 0.0f); } break; - +#endif case WM_MOUSEMOVE: if (window_data) { + float scale_x, scale_y; + get_monitor_scale(hWnd, &scale_x, &scale_y); if (window_data_win->mouse_inside == false) { window_data_win->mouse_inside = true; TRACKMOUSEEVENT tme; @@ -339,8 +410,8 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { tme.hwndTrack = hWnd; TrackMouseEvent(&tme); } - window_data->mouse_pos_x = (int)(short) LOWORD(lParam); - window_data->mouse_pos_y = (int)(short) HIWORD(lParam); + window_data->mouse_pos_x = (int)(short) LOWORD(lParam) * scale_x; + window_data->mouse_pos_y = (int)(short) HIWORD(lParam) * scale_y; kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); } break; @@ -348,6 +419,7 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { case WM_MOUSELEAVE: if (window_data) { window_data_win->mouse_inside = false; + kCall(file_drag_func, -1, -1); } break; @@ -391,12 +463,68 @@ WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { kCall(active_func, false); } break; + + case WM_DROPFILES: + { + float scale_x, scale_y; + TCHAR szName[MAX_PATH]; + HDROP hDrop = (HDROP)wParam; + POINT pt; + int numFiles = DragQueryFile(hDrop, 0xFFFFFFFF, szName, MAX_PATH); + int currentSize = 1; + int i; + DragQueryPoint(hDrop, &pt); + get_monitor_scale(hWnd, &scale_x, &scale_y); + + memset(window_data_win->dropString, 0, window_data_win->dropStringSize); + for (i = 0; i < numFiles; i++) + { + char *utf8str; + DragQueryFile(hDrop, i, szName, MAX_PATH); + utf8str = toUtf8(szName); + currentSize += strlen(utf8str) + 8; + if (window_data_win->dropStringSize < currentSize) + { + int Osz = window_data_win->dropStringSize; + + if (!window_data_win->dropString) + window_data_win->dropStringSize = currentSize * 2; + + if (window_data_win->dropStringSize < MAX_PATH) + window_data_win->dropStringSize = MAX_PATH; + + window_data_win->dropStringSize *= 2; + window_data_win->dropString = realloc(window_data_win->dropString, window_data_win->dropStringSize); + + memset(window_data_win->dropString + Osz, 0, window_data_win->dropStringSize - Osz); + } + strcat(window_data_win->dropString, utf8str); + strcat(window_data_win->dropString, "\n"); + free(utf8str); + } + + kCall(file_drop_func, window_data_win->dropString, pt.x * scale_x, pt.y * scale_y); + kCall(file_drag_func, -1, -1); + DragFinish(hDrop); + break; + } default: { res = DefWindowProc(hWnd, message, wParam, lParam); } } + + /* hacky method of testing if file is being dragged in from outside window */ + /* if the mouse is in the window, no WM_MOUSEMOVE events are being sent, and the left mouse button is down, something outside the window is being dragged */ + if (window_data_win && window_data_win->mouse_inside == false && GetKeyState(VK_LBUTTON) < 0) + { + POINT p; + if (GetCursorPos(&p) && ScreenToClient(hWnd, &p)) + { + kCall(file_drag_func, p.x, p.y); + } + } return res; } @@ -407,10 +535,13 @@ struct mfb_window * mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { RECT rect = { 0 }; int x = 0, y = 0; + void *Wtitle = 0; load_functions(); dpi_aware(); init_keycodes(); + + Wtitle = toWchar(title); SWindowData *window_data = malloc(sizeof(SWindowData)); if (window_data == 0x0) { @@ -497,7 +628,7 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) window_data_win->wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; window_data_win->wc.lpfnWndProc = WndProc; window_data_win->wc.hCursor = LoadCursor(0, IDC_ARROW); - window_data_win->wc.lpszClassName = title; + window_data_win->wc.lpszClassName = Wtitle; RegisterClass(&window_data_win->wc); calc_dst_factor(window_data, width, height); @@ -507,7 +638,7 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) window_data_win->window = CreateWindowEx( 0, - title, title, + Wtitle, Wtitle, s_window_style, x, y, window_data->window_width, window_data->window_height, @@ -525,6 +656,7 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) SetWindowPos(window_data_win->window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); ShowWindow(window_data_win->window, SW_NORMAL); + DragAcceptFiles(window_data_win->window, TRUE); window_data_win->hdc = GetDC(window_data_win->window); @@ -951,7 +1083,7 @@ extern double g_timer_frequency; extern double g_timer_resolution; uint64_t -mfb_timer_tick() { +mfb_timer_tick(void) { int64_t counter; QueryPerformanceCounter((LARGE_INTEGER *) &counter); @@ -960,7 +1092,7 @@ mfb_timer_tick() { } void -mfb_timer_init() { +mfb_timer_init(void) { uint64_t frequency; QueryPerformanceFrequency((LARGE_INTEGER *) &frequency); diff --git a/src/windows/WindowData_Win.h b/src/windows/WindowData_Win.h index 74fa04a4..033689ee 100644 --- a/src/windows/WindowData_Win.h +++ b/src/windows/WindowData_Win.h @@ -1,8 +1,10 @@ #pragma once -#include +#include "../../include/MiniFB_enums.h" #define WIN32_LEAN_AND_MEAN #include +#include /* FIXME some toolchains may require uppercase ShlObj.h */ +#include typedef struct { HWND window; @@ -16,4 +18,6 @@ typedef struct { #endif struct mfb_timer *timer; bool mouse_inside; + char *dropString; + int dropStringSize; } SWindowData_Win; diff --git a/src/x11/WindowData_X11.h b/src/x11/WindowData_X11.h index 646ec358..29444517 100644 --- a/src/x11/WindowData_X11.h +++ b/src/x11/WindowData_X11.h @@ -1,8 +1,9 @@ #pragma once -#include +#include "../../include/MiniFB_enums.h" #include #include +#include #if defined(USE_OPENGL_API) #include #endif @@ -25,4 +26,26 @@ typedef struct { #endif struct mfb_timer *timer; + + Window root; + Atom XdndSelection; + Atom XdndAware; + Atom XdndEnter; + Atom XdndLeave; + Atom XdndTypeList; + Atom XdndPosition; + Atom XdndActionCopy; + Atom XdndStatus; + Atom XdndDrop; + Atom XdndFinished; + Atom UTF8_STRING; + int drop_x; + int drop_y; + struct{ + Window sourceWindow; + char *string; + char *type1; + char *type2; + char *type3; + } xdnd; } SWindowData_X11; diff --git a/src/x11/X11MiniFB.c b/src/x11/X11MiniFB.c index 446eb5ee..1a8e2011 100644 --- a/src/x11/X11MiniFB.c +++ b/src/x11/X11MiniFB.c @@ -12,10 +12,10 @@ #include #include #include +#include #include -#include -#include -#include "WindowData.h" +#include "../MiniFB_internal.h" +#include "../WindowData.h" #include "WindowData_X11.h" #if defined(USE_OPENGL_API) @@ -204,6 +204,28 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) window_data_x11->timer = mfb_timer_create(); mfb_set_keyboard_callback((struct mfb_window *) window_data, keyboard_default); + + // Find or create drag and drop atoms + // Atoms for Xdnd + window_data_x11->root = defaultRootWindow; + window_data_x11->XdndAware = XInternAtom(window_data_x11->display, "XdndAware", True); + window_data_x11->XdndEnter = XInternAtom(window_data_x11->display, "XdndEnter", True); + window_data_x11->XdndPosition = XInternAtom(window_data_x11->display, "XdndPosition", True); + window_data_x11->XdndStatus = XInternAtom(window_data_x11->display, "XdndStatus", True); + window_data_x11->XdndActionCopy = XInternAtom(window_data_x11->display, "XdndActionCopy", True); + window_data_x11->XdndDrop = XInternAtom(window_data_x11->display, "XdndDrop", True); + window_data_x11->XdndLeave = XInternAtom(window_data_x11->display, "XdndLeave", True); + window_data_x11->XdndFinished = XInternAtom(window_data_x11->display, "XdndFinished", True); + window_data_x11->XdndSelection = XInternAtom(window_data_x11->display, "XdndSelection", True); + window_data_x11->UTF8_STRING = XInternAtom(window_data_x11->display, "UTF8_STRING", False); + + // Enable Xdnd + if(window_data_x11->XdndAware!=None) + { + //Announce XDND support + Atom version=5; + XChangeProperty(window_data_x11->display, window_data_x11->window, window_data_x11->XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*)&version, 1); + } #if defined(_DEBUG) || defined(DEBUG) printf("Window created using X11 API\n"); @@ -219,8 +241,41 @@ int translate_key(int scancode); int translate_mod(int state); int translate_mod_ex(int key, int state, int is_pressed); +// Retrieve a single window property of the specified type +// Inspired by fghGetWindowProperty from freeglut +// +static unsigned long mfbGetWindowProperty(Display *display, + Window window, + Atom property, + Atom type, + unsigned char** value) +{ + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + + XGetWindowProperty(display, + window, + property, + 0, + LONG_MAX, + False, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + + if (actualType != type) + return 0; + + return itemCount; +} + static void processEvent(SWindowData *window_data, XEvent *event) { + SWindowData_X11 *window_data_x11 = (SWindowData_X11 *) window_data->specific; switch (event->type) { case KeyPress: case KeyRelease: @@ -291,11 +346,147 @@ processEvent(SWindowData *window_data, XEvent *event) { window_data_x11->image_scaler_width = 0; window_data_x11->image_scaler_height = 0; } - XClearWindow(window_data_x11->display, window_data_x11->window); + // XXX I commented this so there is no tearing/flicker as the window is moved + //XClearWindow(window_data_x11->display, window_data_x11->window); #endif kCall(resize_func, window_data->window_width, window_data->window_height); } break; + + case ClientMessage: + { + if(event->xclient.message_type == window_data_x11->XdndEnter) + { + // Xdnd Enter: the drag&drop event has started in the window, + // we could be getting the type and possible conversions here + // but since we use always string conversion we don't need + // it + } + else if(event->xclient.message_type == window_data_x11->XdndDrop) + { + // Xdnd Drop: The drag&drop event has finished dropping on + // the window, ask to convert the selection + window_data_x11->xdnd.sourceWindow = event->xclient.data.l[0]; + XConvertSelection(window_data_x11->display, /* display */ + window_data_x11->XdndSelection, /* selection */ + window_data_x11->UTF8_STRING, /* target */ + window_data_x11->XdndSelection, /* property */ + window_data_x11->window, /* requestor */ + CurrentTime); /* time */ + } + else if(event->xclient.message_type == window_data_x11->XdndLeave) + { + int x = -1; + int y = -1; + window_data_x11->drop_x = x; + window_data_x11->drop_y = y; + kCall(file_drag_func, x, y); + } + else if(event->xclient.message_type == window_data_x11->XdndPosition) + { + // Xdnd Position: get coordinates of the mouse inside the window + // and update the mouse position + int absX = (event->xclient.data.l[2]>>16) & 0xFFFF; + int absY = (event->xclient.data.l[2]) & 0xFFFF; + + Window child; + int x, y; + + XTranslateCoordinates(window_data_x11->display, + window_data_x11->root, + window_data_x11->window, + absX, absY, &x, &y, &child); + + /* mouse move event */ + window_data_x11->drop_x = x; + window_data_x11->drop_y = y; + kCall(file_drag_func, x, y); + + // Xdnd: reply with an XDND status message + XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = event->xclient.display; + m.window = event->xclient.data.l[0]; + m.message_type = window_data_x11->XdndStatus; + m.format=32; + m.data.l[0] = window_data_x11->window; + m.data.l[1] = 1; // Always accept the dnd with no rectangle + m.data.l[2] = 0; // Specify an empty rectangle + m.data.l[3] = 0; + m.data.l[4] = window_data_x11->XdndActionCopy; // We only accept copying + + XSendEvent(window_data_x11->display, event->xclient.data.l[0], False, NoEventMask, (XEvent*)&m); + XFlush(window_data_x11->display); + } + } + break; + + case SelectionNotify: + { + if(event->xselection.property != None) + { + // Xdnd: got a selection notification from the conversion + // we asked for, get the data and finish the d&d event + char *data; + free(window_data_x11->xdnd.string); + window_data_x11->xdnd.string = NULL; + int result = mfbGetWindowProperty(window_data_x11->display, + event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + + if (result) + { + window_data_x11->xdnd.string = strdup(data); + + /* strip "file://" prefixes */ + const char *prefix = "file://"; + char *str = window_data_x11->xdnd.string; + char *ss; + while ((ss = strstr(str, prefix))) + { + memmove(ss, ss + strlen(prefix), strlen(ss + strlen(prefix))+1); + } + + /* clean up strings that use percent notation */ + for (ss = strchr(str, '%'); ss; ss = strchr(ss+1, '%')) + { + int p; + sscanf(ss+1, "%2X", &p); + *ss = p; + memmove(ss+1, ss + 3, strlen(ss + 3)+1); + } + } + + XClientMessageEvent m; + memset(&m, 0, sizeof(m)); + m.type = ClientMessage; + m.display = window_data_x11->display; + m.window = window_data_x11->xdnd.sourceWindow; + m.message_type = window_data_x11->XdndFinished; + m.format=32; + m.data.l[0] = window_data_x11->window; + m.data.l[1] = result; + m.data.l[2] = window_data_x11->XdndActionCopy; //We only ever copy. + + // Reply that all is well. + XSendEvent(window_data_x11->display, window_data_x11->xdnd.sourceWindow, False, NoEventMask, (XEvent*)&m); + + XSync(window_data_x11->display, False); + + XFree(data); + + if (result) + { + /* callback */ + kCall(file_drop_func, window_data_x11->xdnd.string, window_data_x11->drop_x, window_data_x11->drop_y); + } + kCall(file_drag_func, -1, -1); + } + break; + } case EnterNotify: case LeaveNotify: