From 214336492da0d184d5ad4ac64c31920954c5f7e7 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 7 Nov 2021 06:30:59 +0100 Subject: Implement text input --- src/window/window.c | 214 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 182 insertions(+), 32 deletions(-) (limited to 'src/window') 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 +#include #include #include +/* 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); -- cgit v1.2.3