aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-11-07 06:30:59 +0100
committerdec05eba <dec05eba@protonmail.com>2021-11-07 06:30:59 +0100
commit214336492da0d184d5ad4ac64c31920954c5f7e7 (patch)
treeebcb9e7ace6022f7afe383f1aea986260fffd09f
parentcb679636f77fe2a03e8dab3a511e28e1ab898316 (diff)
Implement text input
-rw-r--r--include/mgl/window/event.h7
-rw-r--r--include/mgl/window/window.h5
-rw-r--r--src/window/window.c214
-rw-r--r--tests/main.c17
4 files changed, 206 insertions, 37 deletions
diff --git a/include/mgl/window/event.h b/include/mgl/window/event.h
index c928d0e..0836098 100644
--- a/include/mgl/window/event.h
+++ b/include/mgl/window/event.h
@@ -3,6 +3,7 @@
#include "key.h"
#include <stdbool.h>
+#include <stdint.h>
typedef struct mgl_event mgl_event;
@@ -12,6 +13,10 @@ typedef struct {
} mgl_size_event;
typedef struct {
+ uint32_t codepoint;
+} mgl_text_event;
+
+typedef struct {
int code; /* mgl_key */
bool alt;
bool control;
@@ -43,6 +48,7 @@ typedef enum {
MGL_EVENT_UNKNOWN,
MGL_EVENT_CLOSED, /* Window closed */
MGL_EVENT_RESIZED, /* Window resized */
+ MGL_EVENT_TEXT_ENTERED,
MGL_EVENT_KEY_PRESSED,
MGL_EVENT_KEY_RELEASED,
MGL_EVENT_MOUSE_BUTTON_PRESSED,
@@ -54,6 +60,7 @@ struct mgl_event {
int type; /* mgl_event_type */
union {
mgl_size_event size;
+ mgl_text_event text;
mgl_key_event key;
mgl_mouse_button_event mouse_button;
mgl_mouse_move_event mouse_move;
diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h
index cb8baf6..9d7086c 100644
--- a/include/mgl/window/window.h
+++ b/include/mgl/window/window.h
@@ -5,9 +5,8 @@
#include "../system/vec.h"
#include <stdbool.h>
-/* Vsync is automatically set for windows created, if supported by the system */
+/* Vsync is automatically set for created windows, if supported by the system */
-typedef struct __GLXcontextRec *GLXContext;
typedef struct mgl_event mgl_event;
/* x11 window handle. TODO: Add others when wayland, etc is added */
typedef unsigned long mgl_window_handle;
@@ -21,7 +20,7 @@ typedef struct {
struct mgl_window {
mgl_window_handle window;
- GLXContext glx_context;
+ void *context;
mgl_vec2i size;
/* relative to the top left of the window. only updates when the cursor is inside the window */
mgl_vec2i cursor_position;
diff --git a/src/window/window.c b/src/window/window.c
index b7f9fd8..cec6417 100644
--- a/src/window/window.c
+++ b/src/window/window.c
@@ -1,10 +1,69 @@
#include "../../include/mgl/window/window.h"
#include "../../include/mgl/window/event.h"
#include "../../include/mgl/mgl.h"
+#include "../../include/mgl/system/utf8.h"
#include <X11/Xutil.h>
+#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
+/* Should be in range [2,] */
+#define MAX_STACKED_EVENTS 32
+
+typedef struct {
+ mgl_event stack[MAX_STACKED_EVENTS];
+ int start;
+ int end;
+ int size;
+} x11_events_circular_buffer;
+
+static void x11_events_circular_buffer_init(x11_events_circular_buffer *self) {
+ self->start = 0;
+ self->end = 0;
+ self->size = 0;
+}
+
+static bool x11_events_circular_buffer_append(x11_events_circular_buffer *self, const mgl_event *event) {
+ if(self->size == MAX_STACKED_EVENTS)
+ return false;
+
+ self->stack[self->end] = *event;
+ self->end = (self->end + 1) % MAX_STACKED_EVENTS;
+ ++self->size;
+ return true;
+}
+
+static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl_event *event) {
+ if(self->size == 0)
+ return false;
+
+ *event = self->stack[self->start];
+ self->start = (self->start + 1) % MAX_STACKED_EVENTS;
+ --self->size;
+ return true;
+}
+
+typedef struct {
+ GLXContext glx_context;
+ XIM xim;
+ XIC xic;
+ /*
+ Used to stack text event on top of key press/release events and other text events.
+ For example pressing a key should give the user both key press and text events
+ and for IM with multiple characters entered (such as chinese), we want to trigger
+ an event for all of them.
+ */
+ x11_events_circular_buffer events;
+} x11_context;
+
+static bool x11_context_append_event(x11_context *self, const mgl_event *event) {
+ return x11_events_circular_buffer_append(&self->events, event);
+}
+
+static bool x11_context_pop_event(x11_context *self, mgl_event *event) {
+ return x11_events_circular_buffer_pop(&self->events, event);
+}
+
/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */
/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */
@@ -46,21 +105,37 @@ int mgl_window_create(mgl_window *self, const char *title, int width, int height
static int mgl_window_init(mgl_window *self, const char *title, int width, int height, mgl_window_handle parent_window, Window existing_window) {
self->window = 0;
+ self->context = NULL;
+ self->open = false;
+
+ self->context = malloc(sizeof(x11_context));
+ if(!self->context) {
+ fprintf(stderr, "Failed to allocate x11 context\n");
+ return -1;
+ }
+
+ x11_context *x11_context = self->context;
+ x11_context->glx_context = NULL,
+ x11_context->xim = NULL,
+ x11_context->xic = NULL;
+ x11_events_circular_buffer_init(&x11_context->events);
mgl_context *context = mgl_get_context();
if(parent_window == 0)
parent_window = DefaultRootWindow(context->connection);
- self->glx_context = context->gl.glXCreateContext(context->connection, context->visual_info, NULL, 1);
- if(!self->glx_context) {
+ x11_context->glx_context = context->gl.glXCreateContext(context->connection, context->visual_info, NULL, 1);
+ if(!x11_context->glx_context) {
fprintf(stderr, "glXCreateContext failed\n");
+ mgl_window_deinit(self);
return -1;
}
Colormap color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), ((XVisualInfo*)context->visual_info)->visual, AllocNone);
if(!color_map) {
fprintf(stderr, "XCreateColormap failed\n");
+ mgl_window_deinit(self);
return -1;
}
@@ -80,6 +155,7 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h
mgl_window_deinit(self);
return -1;
}
+
XFreeColormap(context->connection, color_map);
self->window = existing_window;
} else {
@@ -106,7 +182,7 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h
XFlush(context->connection);
/* TODO: Check for failure? */
- context->gl.glXMakeCurrent(context->connection, self->window, self->glx_context);
+ context->gl.glXMakeCurrent(context->connection, self->window, x11_context->glx_context);
set_vertical_sync_enabled(self->window, 1);
context->gl.glEnable(GL_TEXTURE_2D);
context->gl.glEnable(GL_BLEND);
@@ -126,6 +202,23 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h
XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u);
mgl_window_on_resize(self, gwa.width, gwa.height);
+
+ x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL);
+ if(!x11_context->xim) {
+ fprintf(stderr, "XOpenIM failed\n");
+ mgl_window_deinit(self);
+ return -1;
+ }
+
+ x11_context->xic = XCreateIC(x11_context->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, self->window, NULL);
+ if(!x11_context->xic) {
+ fprintf(stderr, "XCreateIC failed\n");
+ mgl_window_deinit(self);
+ return -1;
+ }
+
+ XSetICFocus(x11_context->xic);
+
self->open = true;
return 0;
}
@@ -140,14 +233,31 @@ 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();
- if(self->glx_context) {
- context->gl.glXDestroyContext(context->connection, self->glx_context);
- self->glx_context = NULL;
+ x11_context *x11_context = self->context;
+ if(x11_context) {
+ if(x11_context->xic) {
+ XDestroyIC(x11_context->xic);
+ x11_context->xic = NULL;
+ }
+
+ if(x11_context->xim) {
+ XCloseIM(x11_context->xim);
+ x11_context->xim = NULL;
+ }
+
+ if(x11_context->glx_context) {
+ context->gl.glXDestroyContext(context->connection, x11_context->glx_context);
+ x11_context->glx_context = NULL;
+ }
+
+ free(x11_context);
+ self->context = NULL;
}
if(self->window) {
XDestroyWindow(context->connection, self->window);
self->window = 0;
}
+ self->open = false;
}
static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) {
@@ -207,82 +317,117 @@ static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
return MGL_BUTTON_UNKNOWN;
}
+static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context) {
+ event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0));
+ event->key.alt = ((xkey->state & Mod1Mask) != 0);
+ event->key.control = ((xkey->state & ControlMask) != 0);
+ event->key.shift = ((xkey->state & ShiftMask) != 0);
+ event->key.system = ((xkey->state & Mod5Mask) != 0); /* TODO: Fix, doesn't work */
+}
+
+static void mgl_window_handle_text_event(mgl_window *self, XEvent *xev) {
+ /* Ignore silent keys */
+ if(XFilterEvent(xev, None))
+ return;
+
+ x11_context *x11_context = self->context;
+ char buf[128];
+
+ Status status;
+ KeySym ksym;
+ const size_t input_str_len = Xutf8LookupString(x11_context->xic, &xev->xkey, buf, sizeof(buf), &ksym, &status);
+ /* TODO: Handle XBufferOverflow */
+ if(status == XBufferOverflow || input_str_len == 0)
+ return;
+
+ /* TODO: Set XIC location on screen with XSetICValues */
+ if(!mgl_utf8_is_valid((const unsigned char*)buf, input_str_len))
+ return;
+
+ for(size_t i = 0; i < input_str_len;) {
+ uint32_t codepoint = 0;
+ const size_t clen = mgl_utf8_decode((const unsigned char*)&buf[i], &codepoint);
+ i += clen;
+
+ mgl_event text_event;
+ text_event.type = MGL_EVENT_TEXT_ENTERED;
+ text_event.text.codepoint = codepoint;
+ if(!x11_context_append_event(x11_context, &text_event))
+ break;
+ }
+}
+
/* Returns true if processed */
-static bool mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context) {
+static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context) {
/* TODO: Handle wm_delete_window event */
switch(xev->type) {
case KeyPress: {
/* TODO: Fill with correct data */
event->type = MGL_EVENT_KEY_PRESSED;
- event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xev->xkey.keycode, 0));
- event->key.alt = ((xev->xkey.state & Mod1Mask) != 0);
- event->key.control = ((xev->xkey.state & ControlMask) != 0);
- event->key.shift = ((xev->xkey.state & ShiftMask) != 0);
- event->key.system = ((xev->xkey.state & Mod5Mask) != 0); /* TODO: Fix, doesn't work */
- return true;
+ mgl_window_handle_key_event(self, &xev->xkey, event, context);
+ mgl_window_handle_text_event(self, xev);
+ return;
}
case KeyRelease: {
/* TODO: Fill with correct data */
event->type = MGL_EVENT_KEY_RELEASED;
- event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xev->xkey.keycode, 0));
- event->key.alt = ((xev->xkey.state & Mod1Mask) != 0);
- event->key.control = ((xev->xkey.state & ControlMask) != 0);
- event->key.shift = ((xev->xkey.state & ShiftMask) != 0);
- event->key.system = ((xev->xkey.state & Mod5Mask) != 0); /* TODO: Fix, doesn't work */
- return true;
+ mgl_window_handle_key_event(self, &xev->xkey, event, context);
+ return;
}
case ButtonPress: {
event->type = MGL_EVENT_MOUSE_BUTTON_PRESSED;
event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
event->mouse_button.x = xev->xbutton.x;
event->mouse_button.y = xev->xbutton.y;
- return true;
+ return;
}
case ButtonRelease: {
event->type = MGL_EVENT_MOUSE_BUTTON_RELEASED;
event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
event->mouse_button.x = xev->xbutton.x;
event->mouse_button.y = xev->xbutton.y;
- return true;
+ return;
}
case ConfigureNotify: {
- while(XCheckTypedWindowEvent(mgl_get_context()->connection, self->window, ConfigureNotify, xev)) {}
+ while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {}
if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.x) {
mgl_window_on_resize(self, xev->xconfigure.width, xev->xconfigure.height);
event->type = MGL_EVENT_RESIZED;
event->size.width = self->size.x;
event->size.height = self->size.y;
- return true;
+ return;
}
- break;
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
}
case MotionNotify: {
- while(XCheckTypedWindowEvent(mgl_get_context()->connection, self->window, MotionNotify, xev)) {}
+ while(XCheckTypedWindowEvent(context->connection, self->window, MotionNotify, xev)) {}
self->cursor_position.x = xev->xmotion.x;
self->cursor_position.y = xev->xmotion.y;
event->type = MGL_EVENT_MOUSE_MOVED;
event->mouse_move.x = self->cursor_position.x;
event->mouse_move.y = self->cursor_position.y;
- return true;
+ return;
}
case ClientMessage: {
- mgl_context *context = mgl_get_context();
if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) {
event->type = MGL_EVENT_CLOSED;
self->open = false;
- return true;
+ return;
} else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) {
xev->xclient.window = DefaultRootWindow(context->connection);
XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev);
}
- break;
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ default: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
}
}
- /*fprintf(stderr, "unhandled event type: %d\n", xev->type);*/
- event->type = MGL_EVENT_UNKNOWN;
- return false;
}
void mgl_window_clear(mgl_window *self, mgl_color color) {
@@ -294,6 +439,11 @@ void mgl_window_clear(mgl_window *self, mgl_color color) {
bool mgl_window_poll_event(mgl_window *self, mgl_event *event) {
mgl_context *context = mgl_get_context();
Display *display = context->connection;
+ x11_context *x11_context = self->context;
+
+ if(x11_context_pop_event(x11_context, event))
+ return true;
+
if(XPending(display)) {
XEvent xev; /* TODO: Move to window struct */
XNextEvent(display, &xev);
diff --git a/tests/main.c b/tests/main.c
index 3ead877..85754f2 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -51,7 +51,7 @@ static void draw(mgl_window *window, void *userdata) {
u->fps_counter = 0;
}
char str[255];
- snprintf(str, sizeof(str), "fps: %d", u->fps);
+ snprintf(str, sizeof(str), "hello|world\nfps: %d", u->fps);
mgl_text text;
mgl_text_init(&text, u->font, str, strlen(str));
@@ -216,7 +216,20 @@ int main(int argc, char **argv) {
mgl_event event;
while(mgl_window_is_open(&window)) {
while(mgl_window_poll_event(&window, &event)) {
-
+ switch(event.type) {
+ case MGL_EVENT_TEXT_ENTERED: {
+ fprintf(stderr, "text event, codepoint: %u\n", event.text.codepoint);
+ break;
+ }
+ case MGL_EVENT_KEY_PRESSED: {
+ fprintf(stderr, "key press event, code: %u\n", event.key.code);
+ break;
+ }
+ case MGL_EVENT_KEY_RELEASED: {
+ fprintf(stderr, "key release event, code: %u\n", event.key.code);
+ break;
+ }
+ }
}
mgl_window_clear(&window, (mgl_color){0, 0, 0, 255});