aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-09-09 22:59:06 +0200
committerdec05eba <dec05eba@protonmail.com>2022-09-09 23:01:03 +0200
commitbe7e74fe9b51123dec29f9ed9c0441dc8725dd1a (patch)
tree5eb7c73910e2526cc4b009bc677e76d2b66f7ec4
parente6167bc9300d497b1f2edf3a307164081e24f2bd (diff)
Support png,jpg and gif clipboard. Use callback for clipboard
-rw-r--r--TODO1
-rw-r--r--include/mgl/window/window.h24
-rw-r--r--src/window/window.c180
-rw-r--r--tests/main.c14
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 <string.h>
#include <errno.h>
#include <stdio.h>
+#include <assert.h>
#include <unistd.h>
/* 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);