aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-11-16 10:56:52 +0100
committerdec05eba <dec05eba@protonmail.com>2021-11-16 10:56:52 +0100
commiteba962c3e353b3fd3f11e30a7d6f48585e3a153c (patch)
treea5b93bb2c4d5af7db63ab9d1e07e1ce7b0efddd1
parent8df2299beba0f1465c3167edb67dd593c5c9e8db (diff)
Window: add set/get clipboard
-rw-r--r--include/mgl/window/window.h7
-rw-r--r--src/window/window.c193
-rw-r--r--tests/main.c11
3 files changed, 207 insertions, 4 deletions
diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h
index 812a5b2..95e3273 100644
--- a/include/mgl/window/window.h
+++ b/include/mgl/window/window.h
@@ -7,6 +7,7 @@
#include "key.h"
#include "mouse_button.h"
#include <stdbool.h>
+#include <stddef.h>
/* Vsync is automatically set for created windows, if supported by the system */
@@ -32,6 +33,8 @@ struct mgl_window {
bool focused;
double frame_time_limit;
mgl_clock frame_timer;
+ char *clipboard_string;
+ size_t clipboard_size;
};
typedef struct {
@@ -75,4 +78,8 @@ void mgl_window_set_size(mgl_window *self, mgl_vec2i size);
/* if |minimum| is (0, 0) then there is no minimum limit, if |maximum| is (0, 0) then there is no maximum limit */
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);
+
#endif /* MGL_WINDOW_H */
diff --git a/src/window/window.c b/src/window/window.c
index 15faff3..5599c58 100644
--- a/src/window/window.c
+++ b/src/window/window.c
@@ -4,6 +4,7 @@
#include "../../include/mgl/system/utf8.h"
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@@ -52,6 +53,12 @@ typedef struct {
GLXContext glx_context;
XIM xim;
XIC xic;
+ Atom clipboard_atom;
+ Atom targets_atom;
+ Atom text_atom;
+ Atom utf8_string_atom;
+ Atom target_property_atom;
+ Atom incr_atom;
Cursor default_cursor;
Cursor invisible_cursor;
/*
@@ -73,6 +80,12 @@ static int x11_context_init(x11_context *self) {
self->glx_context = NULL;
self->xim = NULL;
self->xic = NULL;
+ self->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False);
+ 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->incr_atom = XInternAtom(context->connection, "INCR", False);
self->default_cursor = None;
self->invisible_cursor = None;
@@ -215,6 +228,8 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
self->focused = false;
self->frame_time_limit = 0.0;
mgl_clock_init(&self->frame_timer);
+ self->clipboard_string = NULL;
+ self->clipboard_size = 0;
mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
if(window_size.x <= 0 || window_size.y <= 0) {
@@ -292,7 +307,7 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
}
if(params)
- mgl_window_set_size_limits(self, params->min_size, params->min_size);
+ mgl_window_set_size_limits(self, params->min_size, params->max_size);
/* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */
Atom wm_protocol_atoms[2] = {
@@ -352,15 +367,24 @@ int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle exi
void mgl_window_deinit(mgl_window *self) {
mgl_context *context = mgl_get_context();
x11_context *x11_context = self->context;
+
if(x11_context) {
x11_context_deinit(x11_context);
free(x11_context);
self->context = NULL;
}
+
+ if(self->clipboard_string) {
+ free(self->clipboard_string);
+ self->clipboard_string = NULL;
+ }
+ self->clipboard_size = 0;
+
if(self->window) {
XDestroyWindow(context->connection, self->window);
self->window = 0;
}
+
self->open = false;
}
@@ -525,12 +549,12 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
case FocusIn: {
event->type = MGL_EVENT_GAINED_FOCUS;
self->focused = true;
- break;
+ return;
}
case FocusOut: {
event->type = MGL_EVENT_LOST_FOCUS;
self->focused = false;
- break;
+ return;
}
case ConfigureNotify: {
while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {}
@@ -555,6 +579,57 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
event->mouse_move.y = self->cursor_position.y;
return;
}
+ case SelectionClear: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case SelectionRequest: {
+ x11_context *x11_context = self->context;
+
+ XSelectionEvent selection_event;
+ selection_event.type = SelectionNotify;
+ selection_event.requestor = xev->xselectionrequest.requestor;
+ selection_event.selection = xev->xselectionrequest.selection;
+ selection_event.property = xev->xselectionrequest.property;
+ selection_event.time = xev->xselectionrequest.time;
+ selection_event.target = xev->xselectionrequest.target;
+
+ if(selection_event.selection == x11_context->clipboard_atom) {
+ /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */
+ if(selection_event.target == x11_context->targets_atom) {
+ /* A client requested for our valid conversion targets */
+ int num_targets = 3;
+ Atom targets[4];
+ targets[0] = x11_context->targets_atom;
+ targets[1] = x11_context->text_atom;
+ targets[2] = XA_STRING;
+ if(x11_context->utf8_string_atom) {
+ targets[3] = x11_context->utf8_string_atom;
+ num_targets = 4;
+ }
+
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets);
+ selection_event.target = x11_context->targets_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ } else if(selection_event.target == XA_STRING || (!x11_context->utf8_string_atom && selection_event.target == x11_context->text_atom)) {
+ /* A client requested ascii clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, self->clipboard_string, self->clipboard_size);
+ selection_event.target = XA_STRING;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ } else if(x11_context->utf8_string_atom && (selection_event.target == x11_context->utf8_string_atom || selection_event.target == x11_context->text_atom)) {
+ /* A client requested utf8 clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, x11_context->utf8_string_atom, 8, PropModeReplace, self->clipboard_string, self->clipboard_size);
+ selection_event.target = x11_context->utf8_string_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ }
+ }
+
+ /* Repond to the client that wants our clipboard content */
+ selection_event.property = None;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
case ClientMessage: {
if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) {
event->type = MGL_EVENT_CLOSED;
@@ -646,9 +721,9 @@ bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button
void mgl_window_close(mgl_window *self) {
mgl_context *context = mgl_get_context();
- XUnmapWindow(context->connection, self->window);
XDestroyWindow(context->connection, self->window);
XSync(context->connection, False);
+ self->window = None;
self->open = false;
}
@@ -703,3 +778,113 @@ void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i m
fprintf(stderr, "Warning: failed to set window size hints\n");
}
}
+
+void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size) {
+ mgl_context *context = mgl_get_context();
+ x11_context *x11_context = self->context;
+
+ if(self->clipboard_string) {
+ free(self->clipboard_string);
+ self->clipboard_string = NULL;
+ self->clipboard_size = 0;
+ }
+
+ self->clipboard_string = malloc(size + 1);
+ if(!self->clipboard_string) {
+ fprintf(stderr, "Error: failed to allocate string for clipboard\n");
+ return;
+ }
+ memcpy(self->clipboard_string, str, size);
+ self->clipboard_string[size] = '\0';
+ self->clipboard_size = size;
+
+ XSetSelectionOwner(context->connection, x11_context->clipboard_atom, self->window, CurrentTime);
+
+ /* Check if setting the selection owner was successful */
+ if(XGetSelectionOwner(context->connection, x11_context->clipboard_atom) != self->window)
+ fprintf(stderr, "Error: mgl_window_set_clipboard failed\n");
+}
+
+/* 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) {
+ 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;
+ }
+
+ /* 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)
+ return false;
+
+ memcpy(data, self->clipboard_string, self->clipboard_size + 1);
+ *str = data;
+ *size = self->clipboard_size;
+ return true;
+ }
+
+ 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);
+
+ mgl_clock timeout_timer;
+ mgl_clock_init(&timeout_timer);
+
+ const double timeout_seconds = 1.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)
+ continue;
+
+ Atom type;
+ int format;
+ 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;
+ }
+ }
+
+ XDeleteProperty(context->connection, self->window, x11_context->target_property_atom);
+ if(got_clipboard)
+ return true;
+ }
+ }
+ }
+
+ /* Timed out waiting for clipboard */
+ return false;
+}
diff --git a/tests/main.c b/tests/main.c
index 51487dc..a9aebec 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -255,6 +255,17 @@ int main(int argc, char **argv) {
break;
}
case MGL_EVENT_KEY_PRESSED: {
+ if(event.key.code == MGL_KEY_C) {
+ mgl_window_set_clipboard(&window, "hello", 5);
+ fprintf(stderr, "Copied 'hello' to the clipboard\n");
+ } 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);
+ }
+ }
fprintf(stderr, "key press event, code: %u\n", event.key.code);
break;
}