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 --- src/window/window.c | 180 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 48 deletions(-) (limited to 'src') 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; } -- cgit v1.2.3