From 7331ad9bdfebd38fb342482f11c90c9165234b86 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Sun, 30 Dec 2018 10:20:50 +0100 Subject: [PATCH] X11 picture paste support (PNG format) works with what GIMP puts in the X11 CLIPBOARD --- src/fileformats.c | 59 +++++++++++++++++++++++++++++++------ src/fileformats.h | 1 + src/filesel.c | 2 +- src/input.c | 20 ++++++++++++- src/input.h | 8 +++++ src/loadsave.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++ src/readline.c | 4 +-- 7 files changed, 155 insertions(+), 14 deletions(-) diff --git a/src/fileformats.c b/src/fileformats.c index fc9f9821..7d33c822 100644 --- a/src/fileformats.c +++ b/src/fileformats.c @@ -91,10 +91,6 @@ #include "fileformats.h" #include "oldies.h" -#ifndef __no_pnglib__ -static void Load_PNG_Sub(T_IO_Context * context, FILE * file); -#endif - #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif @@ -3769,7 +3765,7 @@ void Load_ICO(T_IO_Context * context) #ifndef __no_pnglib__ if (png_sig_cmp(png_header, 0, 8) == 0) { - Load_PNG_Sub(context, file); + Load_PNG_Sub(context, file, NULL, 0); } #else if (0 == memcmp(png_header, "\x89PNG", 4)) @@ -6239,8 +6235,39 @@ static int PNG_read_unknown_chunk(png_structp ptr, png_unknown_chunkp chunk) } +struct PNG_memory_buffer { + const char * buffer; + unsigned long offset; + unsigned long size; +}; + +/// read from memory buffer +static void PNG_memory_read(png_structp png_ptr, png_bytep p, png_size_t count) +{ + struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); + GFX2_Log(GFX2_DEBUG, "PNG_memory_read(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer); + if (buffer == NULL || p == NULL) + return; + if (buffer->offset + count <= buffer->size) + { + memcpy(p, buffer->buffer + buffer->offset, count); + buffer->offset += count; + } + else + { + unsigned long available_count = buffer->size - buffer->offset; + GFX2_Log(GFX2_DEBUG, "PNG_memory_read(): only %lu bytes available\n", available_count); + if (available_count > 0) + { + memcpy(p, buffer->buffer + buffer->offset, available_count); + buffer->offset += available_count; + } + } +} + + /// Read PNG format file -static void Load_PNG_Sub(T_IO_Context * context, FILE * file) +void Load_PNG_Sub(T_IO_Context * context, FILE * file, const char * memory_buffer, unsigned long memory_buffer_size) { png_structp png_ptr; png_infop info_ptr = NULL; @@ -6256,12 +6283,22 @@ static void Load_PNG_Sub(T_IO_Context * context, FILE * file) png_byte color_type; png_byte bit_depth; byte bpp; + struct PNG_memory_buffer buffer; // Setup a return point. If a pnglib loading error occurs // in this if(), the else will be executed. if (!setjmp(png_jmpbuf(png_ptr))) { - png_init_io(png_ptr, file); + // to read from memory, I need to use png_set_read_fn() instead of calling png_init_io() + if (file != NULL) + png_init_io(png_ptr, file); + else + { + buffer.buffer = memory_buffer; + buffer.offset = 8; // skip header + buffer.size = memory_buffer_size; + png_set_read_fn(png_ptr, &buffer, PNG_memory_read); + } // Inform pnglib we already loaded the header. png_set_sig_bytes(png_ptr, 8); @@ -6344,7 +6381,11 @@ static void Load_PNG_Sub(T_IO_Context * context, FILE * file) ratio = PIXEL_TALL; } } - Pre_load(context,png_get_image_width(png_ptr,info_ptr),png_get_image_height(png_ptr,info_ptr),File_length_file(file),FORMAT_PNG,ratio,bpp); + Pre_load(context, + png_get_image_width(png_ptr, info_ptr), + png_get_image_height(png_ptr, info_ptr), + file != NULL ? File_length_file(file) : memory_buffer_size, + FORMAT_PNG, ratio, bpp); if (File_error==0) { @@ -6569,7 +6610,7 @@ void Load_PNG(T_IO_Context * context) { // Do we recognize a png file signature ? if ( !png_sig_cmp(png_header, 0, 8)) - Load_PNG_Sub(context, file); + Load_PNG_Sub(context, file, NULL, 0); else File_error=1; } diff --git a/src/fileformats.h b/src/fileformats.h index 48298ec1..87c11ca4 100644 --- a/src/fileformats.h +++ b/src/fileformats.h @@ -140,6 +140,7 @@ void Save_ICO(T_IO_Context *); void Test_PNG(T_IO_Context *, FILE *); void Load_PNG(T_IO_Context *); void Save_PNG(T_IO_Context *); +void Load_PNG_Sub(T_IO_Context * context, FILE * file, const char * memory_buffer, unsigned long memory_buffer_size); #endif // -- INFO (Amiga ICONS) ---------------------------------------------------- diff --git a/src/filesel.c b/src/filesel.c index 9646cdf4..d09fa7f3 100644 --- a/src/filesel.c +++ b/src/filesel.c @@ -1733,7 +1733,7 @@ byte Button_Load_or_Save(T_Selector_settings *settings, byte load, T_IO_Context Display_bookmark(bookmark_dropdown[temp],temp); } -#if defined(WIN32) +#if defined(WIN32) || defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) if (load) Window_set_normal_button(62,180,115,14,"From Clipboard",0,1,SHORTCUT_PASTE); // 14 else diff --git a/src/input.c b/src/input.c index e880d480..a2d5c835 100644 --- a/src/input.c +++ b/src/input.c @@ -86,6 +86,7 @@ word * Drop_file_name_unicode = NULL; #if defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) char * X11_clipboard = NULL; +unsigned long X11_clipboard_size = 0; #endif // -- @@ -397,6 +398,7 @@ static void Handle_ClientMessage(const XClientMessageEvent * xclient) } else { + /// @todo XFree the result of XGetAtomName GFX2_Log(GFX2_INFO, "Unhandled ClientMessage message_type=\"%s\"\n", XGetAtomName(X11_display, xclient->message_type)); } } @@ -478,6 +480,7 @@ static int Handle_SelectionNotify(const XSelectionEvent* xselection) unsigned long count = 0, bytesAfter = 0; unsigned char * value = NULL; + /// @todo XFree the result of XGetAtomName GFX2_Log(GFX2_DEBUG, "xselection: selection=%s property=%s target=%s\n", XGetAtomName(X11_display, xselection->selection), xselection->property == None ? "None" : XGetAtomName(X11_display, xselection->property), @@ -489,9 +492,22 @@ static int Handle_SelectionNotify(const XSelectionEvent* xselection) &count, &bytesAfter, &value); if (r == Success && value != NULL) { - X11_clipboard = strdup((char *)value); + /// @todo XFree the result of XGetAtomName + GFX2_Log(GFX2_DEBUG, "Clipboard value=%p %lu bytes format=%d type=%s\n", + value, count, format, XGetAtomName(X11_display, type)); + X11_clipboard_size = count; + if (xselection->target == XInternAtom(X11_display, "UTF8_STRING", False)) + X11_clipboard = strdup((char *)value); // Text Clipboard + else if (xselection->target == XInternAtom(X11_display, "image/png", False)) + { // Picture clipboard (PNG) + X11_clipboard = malloc(count); + if (X11_clipboard != NULL) + memcpy(X11_clipboard, value, count); + } XFree(value); } + else + GFX2_Log(GFX2_INFO, "XGetWindowProperty failed. r=%d value=%p\n", r, value); user_feedback_required = 1; } else @@ -501,6 +517,7 @@ static int Handle_SelectionNotify(const XSelectionEvent* xselection) } else { + /// @todo XFree the result of XGetAtomName GFX2_Log(GFX2_INFO, "Unhandled SelectNotify selection=%s\n", XGetAtomName(X11_display, xselection->selection)); } return user_feedback_required; @@ -1659,6 +1676,7 @@ int Get_input(int sleep_time) } else { + /// @todo XFree the result of XGetAtomName GFX2_Log(GFX2_INFO, "unrecognized WM event : %s\n", XGetAtomName(X11_display, (Atom)event.xclient.data.l[0])); } } diff --git a/src/input.h b/src/input.h index d3e7f851..39ea807c 100644 --- a/src/input.h +++ b/src/input.h @@ -77,6 +77,14 @@ extern int Snap_axis_origin_Y; extern char * Drop_file_name; extern word * Drop_file_name_unicode; +#if defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) +/// +/// malloc'ed copy of the X11 clipboard +extern char * X11_clipboard; +extern unsigned long X11_clipboard_size; +#endif + + #if defined __HAIKU__ #define SHORTCUT_COPY (KEY_c|MOD_ALT) #elif defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__) || defined(__macosx__) diff --git a/src/loadsave.c b/src/loadsave.c index 55c94d0b..274992c8 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -46,6 +46,9 @@ #include #include #endif +#ifndef __no_pnglib__ +#include +#endif #include "gfx2log.h" #include "buttons.h" @@ -74,6 +77,13 @@ #include "fileformats.h" #include "bitcount.h" +#if defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) +#include "input.h" +#if defined(USE_X11) +extern Display * X11_display; +extern Window X11_window; +#endif +#endif #if defined(USE_SDL) || defined(USE_SDL2) // -- SDL_Image ------------------------------------------------------------- @@ -1424,6 +1434,71 @@ static void Load_ClipBoard_Image(T_IO_Context * context) } } CloseClipboard(); +#elif defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) + int i; + 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; + } + if (X11_display == NULL) + { +#if defined(USE_SDL) + char video_driver_name[32]; + GFX2_Log(GFX2_WARNING, "X11 display is NULL. X11 is needed for Copy/Paste. SDL video driver is currently %s\n", SDL_VideoDriverName(video_driver_name, sizeof(video_driver_name))); +#endif + return; + } +#endif + + selection = XInternAtom(X11_display, "CLIPBOARD", False); + selection_owner = XGetSelectionOwner(X11_display, selection); + if (selection_owner == None) + return; +#if defined(USE_SDL) || defined(USE_SDL2) + old_wmevent_state = SDL_EventState(SDL_SYSWMEVENT, SDL_QUERY); + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); +#endif + +#ifndef __no_pnglib__ + XConvertSelection(X11_display, selection, XInternAtom(X11_display, "image/png"/*"PIXMAP"*/, False), + XInternAtom(X11_display, "GFX2_CLIP", False), /* Property */ + X11_window, CurrentTime); + // wait for the event to be received + for(i = 0; X11_clipboard == NULL && i < 10; i++) + { + Get_input(20); + } +#if defined(USE_SDL) || defined(USE_SDL2) + SDL_EventState(SDL_SYSWMEVENT, old_wmevent_state); +#endif + + if (X11_clipboard != NULL) + { + if (png_sig_cmp((byte *)X11_clipboard, 0, 8) == 0) + { + File_error = 0; + Load_PNG_Sub(context, NULL, X11_clipboard, X11_clipboard_size); + } + else + { + GFX2_Log(GFX2_WARNING, "Clipboard content not in PNG format\n"); + } + free(X11_clipboard); + X11_clipboard = NULL; + X11_clipboard_size = 0; + } +#else + GFX2_Log(GFX2_ERROR, "Need PNG support for X11 image copy/paste\n"); +#endif + #else GFX2_Log(GFX2_ERROR, "Load_ClipBoard_Image() not implemented on this platform yet\n"); File_error = 1; diff --git a/src/readline.c b/src/readline.c index 13aa463d..ff5f90ae 100644 --- a/src/readline.c +++ b/src/readline.c @@ -72,9 +72,6 @@ extern Window X11_window; #elif defined(__macosx__) const char * get_paste_board(void); #endif -#if defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11)) -extern char * X11_clipboard; -#endif // Virtual keyboard is ON by default on these platforms: #if defined(__GP2X__) || defined(__WIZ__) || defined(__CAANOO__) || defined(GCWZERO) @@ -490,6 +487,7 @@ bye: { char * utf8_str = X11_clipboard; X11_clipboard = NULL; + X11_clipboard_size = 0; #else { // mac OS without X11