diff --git a/src/Makefile b/src/Makefile index 3902905e..dd6e49d9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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) diff --git a/src/input.c b/src/input.c index 1a158f3d..cd85bdd3 100644 --- a/src/input.c +++ b/src/input.c @@ -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); diff --git a/src/readline.c b/src/readline.c index 348ac156..662ea8ae 100644 --- a/src/readline.c +++ b/src/readline.c @@ -70,10 +70,12 @@ #include 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; diff --git a/src/screen.h b/src/screen.h index 3f2e84f0..785d70b6 100644 --- a/src/screen.h +++ b/src/screen.h @@ -32,6 +32,12 @@ #ifdef WIN32 #include // for HWND #endif +#if defined(USE_SDL) || defined(USE_SDL2) +#include // for Display, Window +#endif +#if defined(USE_X11) +#include // 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); diff --git a/src/sdlscreen.c b/src/sdlscreen.c index eaca8d90..eb30c8fb 100644 --- a/src/sdlscreen.c +++ b/src/sdlscreen.c @@ -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