From be7e74fe9b51123dec29f9ed9c0441dc8725dd1a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 9 Sep 2022 22:59:06 +0200 Subject: Support png,jpg and gif clipboard. Use callback for clipboard --- TODO | 1 + include/mgl/window/window.h | 24 +++++- src/window/window.c | 180 ++++++++++++++++++++++++++++++++------------ tests/main.c | 14 ++-- 4 files changed, 164 insertions(+), 55 deletions(-) diff --git a/TODO b/TODO index 16fa40e..df13b75 100644 --- a/TODO +++ b/TODO @@ -7,3 +7,4 @@ Experiment with reducing latency by removing GLX_DOUBLEBUFFER and using glFlush& Only render one glyph for missing symbols.\ High precision mouse wheel event by using xi2, which also allows us to get which scroll wheel was used and scrolling in y direction.\ Use XPresent (PresentNotifyMSC, glSwapBuffers, wait for PresentCompleteNotify before doing presentNotifyMsc and glSwapBuffers again), see https://invent.kde.org/plasma/kwin/-/issues/27. This doesn't work on nvidia because nvidia never sends the PresentCompleteNotify event. +Support drag and drop. \ No newline at end of file diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h index f42d592..7dd85b4 100644 --- a/include/mgl/window/window.h +++ b/include/mgl/window/window.h @@ -54,6 +54,19 @@ typedef struct { bool override_redirect; /* false by default */ } mgl_window_create_params; +typedef enum { + MGL_CLIPBOARD_TYPE_STRING, + MGL_CLIPBOARD_TYPE_IMAGE_PNG, + MGL_CLIPBOARD_TYPE_IMAGE_JPG, + MGL_CLIPBOARD_TYPE_IMAGE_GIF, +} mgl_clipboard_type; + +/* + Return true to continue. |mgl_window_get_clipboard| returns false if this returns false. + Note: |size| is the size of the current data, not the total data (if the callback only contains a part of the data). +*/ +typedef bool (*mgl_clipboard_callback)(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata); + /* |params| can be NULL */ int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params); int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window); @@ -101,8 +114,15 @@ void mgl_window_set_size(mgl_window *self, mgl_vec2i size); void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum); void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size); -/* A malloc'ed and null terminated string is returned in |str|, it should be deallocated with free */ -bool mgl_window_get_clipboard(mgl_window *self, char **str, size_t *size); +bool mgl_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata); +/* + A new string is allocated and the pointer is copied to |str| with the size returned in |size|. + |str| should be deallocated with |free| by the user. + This function returns false if there is nothing to copy, or if the clipboard + contains clipboard data that is not a string or if it fails to copy the data + for any other reason. +*/ +bool mgl_window_get_clipboard_string(mgl_window *self, char **str, size_t *size); void mgl_window_set_key_repeat_enabled(mgl_window *self, bool enabled); #endif /* MGL_WINDOW_H */ diff --git a/src/window/window.c b/src/window/window.c index c03d681..b4443e2 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -9,6 +9,7 @@ #include #include #include +#include #include /* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */ @@ -57,7 +58,10 @@ typedef struct { Atom targets_atom; Atom text_atom; Atom utf8_string_atom; - Atom target_property_atom; + Atom image_png; + Atom image_jpg; + Atom image_jpeg; + Atom image_gif; Atom incr_atom; Cursor default_cursor; Cursor invisible_cursor; @@ -87,7 +91,10 @@ static int x11_context_init(x11_context *self) { self->targets_atom = XInternAtom(context->connection, "TARGETS", False); self->text_atom = XInternAtom(context->connection, "TEXT", False); self->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", True); - self->target_property_atom = XInternAtom(context->connection, "MGL_CLIPBOARD_TARGET_PROPERTY", False); + self->image_png = XInternAtom(context->connection, "image/png", True); + self->image_jpg = XInternAtom(context->connection, "image/jpg", True); + self->image_jpeg = XInternAtom(context->connection, "image/jpeg", True); + self->image_gif = XInternAtom(context->connection, "image/gif", True); self->incr_atom = XInternAtom(context->connection, "INCR", False); self->default_cursor = None; self->invisible_cursor = None; @@ -873,59 +880,76 @@ void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size) { self->clipboard_size = size; } -/* TODO: Right now the returned str is free'd with free, but shouldn't it be free'd with free? */ -bool mgl_window_get_clipboard(mgl_window *self, char **str, size_t *size) { +static Atom find_matching_atom(const Atom *supported_atoms, size_t num_supported_atoms, const Atom *atoms, size_t num_atoms) { + for(size_t j = 0; j < num_supported_atoms; ++j) { + for(size_t i = 0; i < num_atoms; ++i) { + if(atoms[i] == supported_atoms[j]) + return atoms[i]; + } + } + return None; +} + +static mgl_clipboard_type atom_type_to_supported_clipboard_type(x11_context *x11_context, Atom type, int format) { + if((type == x11_context->utf8_string_atom || type == XA_STRING || type == x11_context->text_atom) && format == 8) { + return MGL_CLIPBOARD_TYPE_STRING; + } else if(type == x11_context->image_png && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_PNG; + } else if((type == x11_context->image_jpg || type == x11_context->image_jpeg) && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_JPG; + } else if(type == x11_context->image_gif && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_GIF; + } else { + return -1; + } +} + +bool mgl_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata) { + assert(callback); + mgl_context *context = mgl_get_context(); x11_context *x11_context = self->context; - *str = NULL; - *size = 0; - Window selection_owner = XGetSelectionOwner(context->connection, x11_context->clipboard_atom); - if(!selection_owner) { - char *empty_str = malloc(1); - if(!empty_str) - return false; - - empty_str[0] = '\0'; - return true; - } + if(!selection_owner) + return false; /* Return data immediately if we are the owner of the clipboard, because we can't process other events in the below event loop */ if(selection_owner == self->window) { - if(!self->clipboard_string) { - char *empty_str = malloc(1); - if(!empty_str) - return false; - - empty_str[0] = '\0'; - return true; - } - - char *data = malloc(self->clipboard_size + 1); - if(!data) + if(!self->clipboard_string) return false; - memcpy(data, self->clipboard_string, self->clipboard_size); - data[self->clipboard_size] = '\0'; - *str = data; - *size = self->clipboard_size; - return true; + return callback((const unsigned char*)self->clipboard_string, self->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata); } XEvent xev; while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) {} - XConvertSelection(context->connection, x11_context->clipboard_atom, x11_context->utf8_string_atom ? x11_context->utf8_string_atom : XA_STRING, x11_context->target_property_atom, self->window, CurrentTime); + /* Sorted by preference */ + /* TODO: Support more types (BITMAP?, PIXMAP?) */ + const Atom supported_clipboard_types[] = { + x11_context->utf8_string_atom, + XA_STRING, + x11_context->text_atom, + x11_context->image_png, + x11_context->image_jpg, + x11_context->image_jpeg, + x11_context->image_gif, + }; + const unsigned long num_supported_clipboard_types = sizeof(supported_clipboard_types) / sizeof(supported_clipboard_types[0]); + + Atom requested_clipboard_type = None; + const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False); + XConvertSelection(context->connection, x11_context->clipboard_atom, XA_TARGETS, x11_context->clipboard_atom, self->window, CurrentTime); mgl_clock timeout_timer; mgl_clock_init(&timeout_timer); - const double timeout_seconds = 1.0; + const double timeout_seconds = 5.0; while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) { while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) { - if(!xev.xselection.property || xev.xselection.selection != x11_context->clipboard_atom) + if(!xev.xselection.property || !xev.xselection.target || xev.xselection.selection != x11_context->clipboard_atom) continue; Atom type; @@ -933,23 +957,41 @@ bool mgl_window_get_clipboard(mgl_window *self, char **str, size_t *size) { unsigned long items; unsigned long remaining_bytes; unsigned char *data = NULL; - if(XGetWindowProperty(context->connection, self->window, x11_context->target_property_atom, 0, INT32_MAX, False, AnyPropertyType, &type, &format, &items, &remaining_bytes, &data) == Success) { - bool got_clipboard = false; - if(type != x11_context->incr_atom) { - if(type == x11_context->utf8_string_atom && format == 8) { - *str = (char*)data; - *size = items; - got_clipboard = true; - } else if(type == XA_STRING && format == 8) { - *str = (char*)data; - *size = items; - got_clipboard = true; + + if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) { + if(XGetWindowProperty(context->connection, self->window, x11_context->clipboard_atom, 0, 1024, False, AnyPropertyType, &type, &format, &items, &remaining_bytes, &data) == Success && data) { + bool got_clipboard = false; + if(type != x11_context->incr_atom && type == XA_ATOM && format == 32) { + requested_clipboard_type = find_matching_atom(supported_clipboard_types, num_supported_clipboard_types, (Atom*)data, items); + if(requested_clipboard_type == None) { + /* Pasting clipboard data type we dont support */ + return false; + } else { + XConvertSelection(context->connection, x11_context->clipboard_atom, requested_clipboard_type, x11_context->clipboard_atom, self->window, CurrentTime); + } } + XFree(data); } + } else if(xev.xselection.target == requested_clipboard_type) { + bool success = true; + unsigned long offset = 0; + while(XGetWindowProperty(context->connection, self->window, x11_context->clipboard_atom, offset, 32768, False, AnyPropertyType, &type, &format, &items, &remaining_bytes, &data) == Success && data) { + const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(x11_context, type, format); + if(type != x11_context->incr_atom && clipboard_type != -1) { + if(!callback(data, items, clipboard_type, userdata)) { + success = false; + break; + } + } + offset += (items * (format/8)); // format is the bit size of the data + XFree(data); - XDeleteProperty(context->connection, self->window, x11_context->target_property_atom); - if(got_clipboard) - return true; + if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds) { + success = false; + break; + } + } + return success; } } } @@ -958,6 +1000,48 @@ bool mgl_window_get_clipboard(mgl_window *self, char **str, size_t *size) { return false; } +typedef struct { + char **str; + size_t *size; +} ClipboardStringCallbackData; + +static bool clipboard_copy_string_callback(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata) { + ClipboardStringCallbackData *callback_data = userdata; + if(clipboard_type != MGL_CLIPBOARD_TYPE_STRING) { + free(*callback_data->str); + *callback_data->str = NULL; + *callback_data->size = 0; + return false; + } + + char *new_data = realloc(*callback_data->str, *callback_data->size + size); + if(!new_data) { + free(*callback_data->str); + *callback_data->str = NULL; + *callback_data->size = 0; + return false; + } + + memcpy(new_data + *callback_data->size, data, size); + + *callback_data->str = new_data; + *callback_data->size += size; + return true; +} + +bool mgl_window_get_clipboard_string(mgl_window *self, char **str, size_t *size) { + assert(str); + assert(size); + + *str = NULL; + *size = 0; + + ClipboardStringCallbackData callback_data; + callback_data.str = str; + callback_data.size = size; + return mgl_window_get_clipboard(self, clipboard_copy_string_callback, &callback_data); +} + void mgl_window_set_key_repeat_enabled(mgl_window *self, bool enabled) { self->key_repeat_enabled = enabled; } diff --git a/tests/main.c b/tests/main.c index df68dd5..7d94461 100644 --- a/tests/main.c +++ b/tests/main.c @@ -215,6 +215,11 @@ static void test_utf8() { require_equals(mgl_utf8_get_start_of_codepoint("abö", 3, 4), 2); } +static bool clipboard_callback(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata) { + fprintf(stderr, "clipboard type: %d, size: %zu, data: %s\n", clipboard_type, size, data); + return true; +} + int main(int argc, char **argv) { test_hash_map(); test_utf8(); @@ -287,11 +292,10 @@ int main(int argc, char **argv) { mgl_window_set_clipboard(&window, str, strlen(str)); fprintf(stderr, "Copied '%s' to the clipboard\n", str); } else if(event.key.code == MGL_KEY_V) { - char *str; - size_t size; - if(mgl_window_get_clipboard(&window, &str, &size)) { - fprintf(stderr, "Copied '%s' from the clipboard\n", str); - free(str); + if(mgl_window_get_clipboard(&window, clipboard_callback, NULL)) { + fprintf(stderr, "Successfully copied clipboard!\n"); + } else { + fprintf(stderr, "Failed to copy clipboard!\n"); } } fprintf(stderr, "key press event, code: %u\n", event.key.code); -- cgit v1.2.3