SDL/x11 : make Copy/Paste and Drag&Drop work

This commit is contained in:
Thomas Bernard 2018-07-13 01:31:15 +02:00
parent 8ec57abf88
commit d7c6193c21
5 changed files with 304 additions and 146 deletions

View File

@ -511,9 +511,11 @@ endif
LOPT = -lm
ifeq ($(API),sdl)
LOPT += $(shell sdl-config --libs) -lSDL_image
LOPT += -lX11
endif
ifeq ($(API),sdl2)
LOPT += $(shell sdl2-config --libs) -lSDL2_image
LOPT += -lX11
endif
LOPT += $(TTFLOPT)
LOPT += $(shell pkg-config --libs libpng)

View File

@ -83,7 +83,7 @@ int Snap_axis_origin_Y;
char * Drop_file_name = NULL;
#if defined(USE_X11)
#if defined(USE_X11) || defined(SDL_VIDEO_DRIVER_X11)
char * X11_clipboard = NULL;
#endif
@ -319,6 +319,182 @@ int Move_cursor_with_constraints()
// WM events management
#if defined(USE_X11) || defined(SDL_VIDEO_DRIVER_X11)
static int xdnd_version = 5;
static Window xdnd_source = None;
static void Handle_ClientMessage(const XClientMessageEvent * xclient)
{
#if defined(SDL_VIDEO_DRIVER_X11)
Display * X11_display;
Window X11_window;
if (!GFX2_Get_X11_Display_Window(&X11_display, &X11_window))
{
GFX2_Log(GFX2_ERROR, "Failed to get X11 display and window\n");
return;
}
#endif
if (xclient->message_type == XInternAtom(X11_display, "XdndEnter", False))
{
//int list = xclient->data.l[1] & 1;
xdnd_version = xclient->data.l[1] >> 24;
xdnd_source = xclient->data.l[0];
GFX2_Log(GFX2_DEBUG, "XdndEnter version=%d source=%lu\n", xdnd_version, xdnd_source);
}
else if (xclient->message_type == XInternAtom(X11_display, "XdndLeave", False))
{
GFX2_Log(GFX2_DEBUG, "XdndLeave\n");
}
else if (xclient->message_type == XInternAtom(X11_display, "XdndPosition", False))
{
XEvent reply;
int x_abs, y_abs;
int x_pos, y_pos;
Window root_window, child;
unsigned int width, height;
unsigned int border_width, depth;
x_abs = (xclient->data.l[2] >> 16) & 0xffff;
y_abs = xclient->data.l[2] & 0xffff;
// reply with XdndStatus
// see https://github.com/glfw/glfw/blob/a9a5a0b016215b4e40a19acb69577d91cf21a563/src/x11_window.c
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = xclient->data.l[0]; // drag & drop source window
reply.xclient.message_type = XInternAtom(X11_display, "XdndStatus", False);
reply.xclient.format = 32;
reply.xclient.data.l[0] = xclient->window;
if (XGetGeometry(X11_display, X11_window, &root_window, &x_pos, &y_pos, &width, &height, &border_width, &depth)
&& XTranslateCoordinates(X11_display, X11_window, root_window, 0, 0, &x_abs, &y_abs, &child))
{
reply.xclient.data.l[2] = (x_abs & 0xffff) << 16 | (y_abs & 0xffff);
reply.xclient.data.l[3] = (width & 0xffff) << 16 | (height & 0xffff);
}
// Reply that we are ready to copy the dragged data
reply.xclient.data.l[1] = 1; // Accept with no rectangle
if (xdnd_version >= 2)
reply.xclient.data.l[4] = XInternAtom(X11_display, "XdndActionCopy", False);
XSendEvent(X11_display, xclient->data.l[0], False, NoEventMask, &reply);
}
else if (xclient->message_type == XInternAtom(X11_display, "XdndDrop", False))
{
Atom selection = XInternAtom(X11_display, "XdndSelection", False);
Time time = CurrentTime;
if (xdnd_version >= 1)
time = xclient->data.l[2];
XConvertSelection(X11_display,
selection,
XInternAtom(X11_display, "text/uri-list", False),
selection,
xclient->window,
time);
}
else
{
GFX2_Log(GFX2_INFO, "Unhandled ClientMessage message_type=\"%s\"\n", XGetAtomName(X11_display, xclient->message_type));
}
}
static int Handle_SelectionNotify(const XSelectionEvent* xselection)
{
int user_feedback_required = 0;
Atom type = 0;
int format = 0;
#if defined(SDL_VIDEO_DRIVER_X11)
Display * X11_display;
Window X11_window;
if (!GFX2_Get_X11_Display_Window(&X11_display, &X11_window))
{
GFX2_Log(GFX2_ERROR, "Failed to get X11 display and window\n");
return 0;
}
#endif
if (xselection->property == XInternAtom(X11_display, "XdndSelection", False))
{
int r;
unsigned long count = 0, bytesAfter = 0;
unsigned char * value = NULL;
r = XGetWindowProperty(X11_display, xselection->requestor, xselection->property, 0, LONG_MAX,
False, xselection->target /* type */, &type, &format,
&count, &bytesAfter, &value);
if (r == Success && value != NULL)
{
if (format == 8)
{
int i, j;
Drop_file_name = malloc(count + 1);
i = 0; j = 0;
if (count > 7 && 0 == memcmp(value, "file://", 7))
i = 7;
while (i < (int)count && value[i] != 0 && value[i] != '\n' && value[i] != '\r')
{
if (i < ((int)count + 2) && value[i] == '%')
{
// URI-Decode : "%NN" to char of value 0xNN
i++;
Drop_file_name[j] = (value[i] - ((value[i] >= 'A') ? 'A' - 10 : '0')) << 4;
i++;
Drop_file_name[j++] |= (value[i] - ((value[i] >= 'A') ? 'A' - 10 : '0'));
i++;
}
else
{
Drop_file_name[j++] = (char)value[i++];
}
}
Drop_file_name[j++] = '\0';
}
XFree(value);
}
if (xdnd_version >= 2)
{
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = xdnd_source;
reply.xclient.message_type = XInternAtom(X11_display, "XdndFinished", False);
reply.xclient.format = 32;
reply.xclient.data.l[0] = X11_window;
reply.xclient.data.l[1] = 1; // success
reply.xclient.data.l[2] = XInternAtom(X11_display, "XdndActionCopy", False);
XSendEvent(X11_display, xdnd_source, False, NoEventMask, &reply);
}
}
else if (xselection->selection == XInternAtom(X11_display, "CLIPBOARD", False)
|| xselection->selection == XInternAtom(X11_display, "PRIMARY", False))
{
int r;
unsigned long count = 0, bytesAfter = 0;
unsigned char * value = NULL;
r = XGetWindowProperty(X11_display, X11_window, xselection->property, 0, LONG_MAX,
False, xselection->target /* type */, &type, &format,
&count, &bytesAfter, &value);
if (r == Success && value != NULL)
{
X11_clipboard = strdup((char *)value);
XFree(value);
}
user_feedback_required = 1;
}
else
{
GFX2_Log(GFX2_INFO, "Unhandled SelectNotify selection=%s\n", XGetAtomName(X11_display, xselection->selection));
}
return user_feedback_required;
}
#endif
#if defined(USE_SDL)
void Handle_window_resize(SDL_ResizeEvent event)
{
@ -945,6 +1121,10 @@ int Get_input(int sleep_time)
switch(event.type)
{
#if defined(USE_SDL)
case SDL_ACTIVEEVENT:
GFX2_Log(GFX2_DEBUG, "SDL_ACTIVEEVENT gain=%d state=%d\n", event.active.gain, event.active.state);
break;
case SDL_VIDEORESIZE:
Handle_window_resize(event.resize);
user_feedback_required = 1;
@ -1111,8 +1291,38 @@ int Get_input(int sleep_time)
// Drop of zero files. Thanks for the information, Bill.
}
}
#elif defined(SDL_VIDEO_DRIVER_X11)
#if defined(USE_SDL)
#define xevent event.syswm.msg->event.xevent
#else
GFX2_Log(GFX2_DEBUG, "Unhandled SDL_SYSWMEVENT\n");
#define xevent event.syswm.msg->msg.x11.event
#endif
switch (xevent.type)
{
case ClientMessage:
Handle_ClientMessage(&(xevent.xclient));
break;
case SelectionNotify:
if (Handle_SelectionNotify(&(xevent.xselection)))
user_feedback_required = 1;
break;
case ButtonPress:
case ButtonRelease:
case MotionNotify:
// ignore
break;
case GenericEvent:
GFX2_Log(GFX2_DEBUG, "SDL_SYSWMEVENT x11 GenericEvent extension=%d evtype=%d\n",
xevent.xgeneric.extension,
xevent.xgeneric.evtype);
break;
case PropertyNotify:
GFX2_Log(GFX2_DEBUG, "SDL_SYSWMEVENT x11 PropertyNotify\n");
break;
default:
GFX2_Log(GFX2_DEBUG, "Unhandled SDL_SYSWMEVENT x11 event type=%d\n", xevent.type);
}
#undef xevent
#endif
break;
@ -1255,8 +1465,6 @@ int Get_input(int sleep_time)
}
#elif defined(USE_X11)
int user_feedback_required = 0; // Flag qui indique si on doit arrêter de traiter les évènements ou si on peut enchainer
static int xdnd_version = 5;
static Window xdnd_source = None;
Color_cycling();
// Commit any pending screen update.
@ -1415,145 +1623,15 @@ int Get_input(int sleep_time)
}
else
{
// unrecognized WM event.
GFX2_Log(GFX2_INFO, "unrecognized WM event : %s\n", XGetAtomName(X11_display, (Atom)event.xclient.data.l[0]));
}
}
else if (event.xclient.message_type == XInternAtom(X11_display, "XdndEnter", False))
{
//int list = event.xclient.data.l[1] & 1;
xdnd_version = event.xclient.data.l[1] >> 24;
xdnd_source = event.xclient.data.l[0];
GFX2_Log(GFX2_DEBUG, "XdndEnter version=%d source=%lu\n", xdnd_version, xdnd_source);
}
else if (event.xclient.message_type == XInternAtom(X11_display, "XdndLeave", False))
{
GFX2_Log(GFX2_DEBUG, "XdndLeave\n");
}
else if (event.xclient.message_type == XInternAtom(X11_display, "XdndPosition", False))
{
XEvent reply;
int x_abs, y_abs;
int x_pos, y_pos;
Window root_window, child;
unsigned int width, height;
unsigned int border_width, depth;
x_abs = (event.xclient.data.l[2] >> 16) & 0xffff;
y_abs = event.xclient.data.l[2] & 0xffff;
// reply with XdndStatus
// see https://github.com/glfw/glfw/blob/a9a5a0b016215b4e40a19acb69577d91cf21a563/src/x11_window.c
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = event.xclient.data.l[0]; // drag & drop source window
reply.xclient.message_type = XInternAtom(X11_display, "XdndStatus", False);
reply.xclient.format = 32;
reply.xclient.data.l[0] = event.xclient.window;
if (XGetGeometry(X11_display, X11_window, &root_window, &x_pos, &y_pos, &width, &height, &border_width, &depth)
&& XTranslateCoordinates(X11_display, X11_window, root_window, 0, 0, &x_abs, &y_abs, &child))
{
reply.xclient.data.l[2] = (x_abs & 0xffff) << 16 | (y_abs & 0xffff);
reply.xclient.data.l[3] = (width & 0xffff) << 16 | (height & 0xffff);
}
// Reply that we are ready to copy the dragged data
reply.xclient.data.l[1] = 1; // Accept with no rectangle
if (xdnd_version >= 2)
{
reply.xclient.data.l[4] = XInternAtom(X11_display, "XdndActionCopy", False);
}
XSendEvent(X11_display, event.xclient.data.l[0], False, NoEventMask, &reply);
}
else if (event.xclient.message_type == XInternAtom(X11_display, "XdndDrop", False))
{
Time time = CurrentTime;
if (xdnd_version >= 1)
time = event.xclient.data.l[2];
XConvertSelection(X11_display,
XInternAtom(X11_display, "XdndSelection", False),
XInternAtom(X11_display, "text/uri-list", False),
XInternAtom(X11_display, "XdndSelection", False),
event.xclient.window,
time);
}
else
Handle_ClientMessage(&event.xclient);
break;
case SelectionNotify:
if (event.xselection.property == XInternAtom(X11_display, "XdndSelection", False))
{
Atom type = 0;
int format = 0;
int r;
unsigned long count = 0, bytesAfter = 0;
unsigned char * value = NULL;
r = XGetWindowProperty(X11_display, event.xselection.requestor, event.xselection.property, 0, LONG_MAX,
False, event.xselection.target /* type */, &type, &format,
&count, &bytesAfter, &value);
if (r == Success && value != NULL)
{
if (format == 8)
{
int i, j;
Drop_file_name = malloc(count + 1);
i = 0; j = 0;
if (count > 7 && 0 == memcmp(value, "file://", 7))
i = 7;
while (i < (int)count && value[i] != 0 && value[i] != '\n' && value[i] != '\r')
{
if (i < ((int)count + 2) && value[i] == '%')
{
// URI-Decode : "%NN" to char of value 0xNN
i++;
Drop_file_name[j] = (value[i] - ((value[i] >= 'A') ? 'A' - 10 : '0')) << 4;
i++;
Drop_file_name[j++] |= (value[i] - ((value[i] >= 'A') ? 'A' - 10 : '0'));
i++;
}
else
{
Drop_file_name[j++] = (char)value[i++];
}
}
Drop_file_name[j++] = '\0';
}
XFree(value);
}
if (xdnd_version >= 2)
{
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = xdnd_source;
reply.xclient.message_type = XInternAtom(X11_display, "XdndFinished", False);
reply.xclient.format = 32;
reply.xclient.data.l[0] = X11_window;
reply.xclient.data.l[1] = 1; // success
reply.xclient.data.l[2] = XInternAtom(X11_display, "XdndActionCopy", False);
XSendEvent(X11_display, xdnd_source, False, NoEventMask, &reply);
}
}
else if (event.xselection.selection == XInternAtom(X11_display, "CLIPBOARD", False)
|| event.xselection.selection == XInternAtom(X11_display, "PRIMARY", False))
{
Atom type = 0;
int format = 0;
int r;
unsigned long count = 0, bytesAfter = 0;
unsigned char * value = NULL;
r = XGetWindowProperty(X11_display, X11_window, event.xselection.property, 0, LONG_MAX,
False, event.xselection.target /* type */, &type, &format,
&count, &bytesAfter, &value);
if (r == Success && value != NULL)
{
X11_clipboard = strdup((char *)value);
XFree(value);
}
if (Handle_SelectionNotify(&event.xselection))
user_feedback_required = 1;
}
break;
default:
GFX2_Log(GFX2_INFO, "X11 event.type = %d not handled\n", event.type);

View File

@ -70,10 +70,12 @@
#include <X11/Xlib.h>
extern Display * X11_display;
extern Window X11_window;
extern char * X11_clipboard;
#elif defined(__macosx__)
const char * get_paste_board(void);
#endif
#if defined(USE_X11) || defined(SDL_VIDEO_DRIVER_X11)
extern char * X11_clipboard;
#endif
// Virtual keyboard is ON by default on these platforms:
#if defined(__GP2X__) || defined(__WIZ__) || defined(__CAANOO__) || defined(GCWZERO)
@ -407,7 +409,7 @@ bye:
if (unicode)
*unicode = NULL;
return haiku_get_clipboard();
#elif defined(USE_X11) || defined(__macosx__) || defined(USE_SDL2)
#elif defined(USE_X11) || defined(__macosx__) || defined(USE_SDL2) || (defined(USE_SDL) && defined(SDL_VIDEO_DRIVER_X11))
if (unicode)
*unicode = NULL;
#if defined(USE_SDL2)
@ -420,11 +422,24 @@ bye:
char * utf8_str = SDL_GetClipboardText();
if (utf8_str != NULL)
{
#elif defined(USE_X11)
#elif defined(USE_X11) || (defined(USE_SDL) && defined(SDL_VIDEO_DRIVER_X11))
{
int i;
Atom selection = XInternAtom(X11_display, "CLIPBOARD", False);
Window selection_owner = XGetSelectionOwner(X11_display, selection);
Atom selection;
Window selection_owner;
#if defined(SDL_VIDEO_DRIVER_X11)
Display * X11_display;
Window X11_window;
int old_wmevent_state;
if (!GFX2_Get_X11_Display_Window(&X11_display, &X11_window))
{
GFX2_Log(GFX2_ERROR, "Failed to get X11 display and window\n");
return NULL;
}
#endif
selection = XInternAtom(X11_display, "CLIPBOARD", False);
selection_owner = XGetSelectionOwner(X11_display, selection);
if (selection_owner == None)
{
@ -433,6 +448,10 @@ bye:
}
if (selection_owner == None)
return NULL;
#if defined(USE_SDL)
old_wmevent_state = SDL_EventState(SDL_SYSWMEVENT, SDL_QUERY);
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif
XConvertSelection(X11_display, selection, XInternAtom(X11_display, "UTF8_STRING", False),
XInternAtom(X11_display, "GFX2_CLIP", False), /* Property */
@ -442,6 +461,9 @@ bye:
{
Get_input(20);
}
#if defined(USE_SDL)
SDL_EventState(SDL_SYSWMEVENT, old_wmevent_state);
#endif
if (X11_clipboard != NULL)
{
char * utf8_str = X11_clipboard;

View File

@ -32,6 +32,12 @@
#ifdef WIN32
#include <windows.h> // for HWND
#endif
#if defined(USE_SDL) || defined(USE_SDL2)
#include <SDL_syswm.h> // for Display, Window
#endif
#if defined(USE_X11)
#include <X11/Xlib.h> // for Display, Window
#endif
#include "struct.h"
#include "global.h"
@ -70,6 +76,10 @@ void GFX2_UpdateScreen(void);
HWND GFX2_Get_Window_Handle(void);
#endif
#if defined(USE_X11) || defined(SDL_VIDEO_DRIVER_X11)
int GFX2_Get_X11_Display_Window(Display * * display, Window * window);
#endif
/// Set application icon(s)
void Define_icon(void);

View File

@ -530,8 +530,42 @@ HWND GFX2_Get_Window_Handle(void)
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
SDL_GetWMInfo(&wminfo);
#if defined(USE_SDL)
if (SDL_GetWMInfo(&wminfo) <= 0)
return NULL;
return wminfo.window;
#else
if (Window_SDL == NULL)
return NULL;
if (!SDL_GetWindowWMInfo(Window_SDL, &wminfo))
return NULL;
return wminfo.info.win.window;
#endif
}
#endif
#ifdef SDL_VIDEO_DRIVER_X11
int GFX2_Get_X11_Display_Window(Display * * display, Window * window)
{
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
#if defined(USE_SDL)
// SDL 1.x
if (SDL_GetWMInfo(&wminfo) <= 0)
return 0;
*display = wminfo.info.x11.display;
*window = wminfo.info.x11.wmwindow;
#else
// SDL 2.x
if (Window_SDL == NULL)
return 0;
if (!SDL_GetWindowWMInfo(Window_SDL, &wminfo))
return 0;
*display = wminfo.info.x11.display;
*window = wminfo.info.x11.window;
#endif
return 1;
}
#endif
@ -546,8 +580,20 @@ void Allow_drag_and_drop(int flag)
// Inform Windows that we accept drag-n-drop events or not
#ifdef __WIN32__
DragAcceptFiles(GFX2_Get_Window_Handle(), flag?TRUE:FALSE);
SDL_EventState (SDL_SYSWMEVENT,flag?SDL_ENABLE:SDL_DISABLE );
#else
SDL_EventState (SDL_SYSWMEVENT, flag?SDL_ENABLE:SDL_DISABLE);
#elif defined(SDL_VIDEO_DRIVER_X11)
Atom version = flag ? 5 : 0;
Display * display;
Window window;
if (GFX2_Get_X11_Display_Window(&display, &window))
{
XChangeProperty(display, window,
XInternAtom(display, "XdndAware", False),
XA_ATOM, 32, PropModeReplace, (unsigned char *)&version, 1);
SDL_EventState (SDL_SYSWMEVENT, flag?SDL_ENABLE:SDL_DISABLE);
}
#else
(void)flag; // unused
#endif
#endif