aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/mgl/window/window.h123
-rw-r--r--include/mgl/window/x11/window.h9
-rw-r--r--meson.build1
-rw-r--r--src/window/window.c1964
-rw-r--r--src/window/x11/window.c2066
-rw-r--r--tests/main.c12
6 files changed, 2209 insertions, 1966 deletions
diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h
index 748e728..07c19ca 100644
--- a/include/mgl/window/window.h
+++ b/include/mgl/window/window.h
@@ -9,6 +9,8 @@
#include <stdbool.h>
#include <stddef.h>
+#define MGL_MAX_MONITORS 12
+
/* Vsync is automatically set for created windows, if supported by the system */
typedef union _XEvent XEvent;
@@ -16,6 +18,7 @@ typedef struct mgl_event mgl_event;
/* x11 window handle. TODO: Add others when wayland, etc is added */
typedef unsigned long mgl_window_handle;
+typedef void* mgl_connection;
typedef struct mgl_window mgl_window;
typedef struct {
@@ -37,29 +40,15 @@ typedef struct {
int refresh_rate;
} mgl_monitor;
-struct mgl_window {
- mgl_window_handle window;
- void *context;
- mgl_vec2i pos;
- mgl_vec2i size;
- /* relative to the top left of the window. only updates when the cursor is inside the window */
- mgl_vec2i cursor_position;
- mgl_view view;
- mgl_scissor scissor;
- bool open;
- bool focused;
- bool key_repeat_enabled;
- bool vsync_enabled; /* true by default */
- double frame_time_limit;
- double frame_time_limit_monitor;
- mgl_clock frame_timer;
- char *clipboard_string;
- size_t clipboard_size;
- bool low_latency; /* false by default */
+typedef enum {
+ MGL_WINDOW_SYSTEM_NATIVE, /* Use X11 on X11 and Wayland on Wayland */
+ MGL_WINDOW_SYSTEM_X11 /* Use X11 on X11 and XWayland on Wayland */
+} mgl_window_system;
- mgl_monitor *monitors; /* TODO: Move these to mgl file */
- int num_monitors;
-};
+typedef enum {
+ MGL_RENDER_API_GLX, /* Only available when using X11 (or XWayland) */
+ MGL_RENDER_API_EGL
+} mgl_render_api;
typedef enum {
MGL_WINDOW_TYPE_NORMAL,
@@ -68,9 +57,69 @@ typedef enum {
} mgl_window_type;
typedef enum {
- MGL_RENDER_API_GLX,
- MGL_RENDER_API_EGL
-} mgl_render_api;
+ MGL_CLIPBOARD_TYPE_STRING = 1 << 0,
+ MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1,
+ MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2,
+ MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3,
+} mgl_clipboard_type;
+
+#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF
+#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF)
+
+/*
+ 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);
+typedef void (*mgl_active_monitor_callback)(const mgl_monitor *monitor, void *userdata);
+
+struct mgl_window {
+ mgl_window_handle (*get_system_handle)(const mgl_window *self);
+ void (*close)(mgl_window *self);
+ bool (*poll_event)(mgl_window *self, mgl_event *event);
+ bool (*inject_x11_event)(mgl_window *self, XEvent *xev, mgl_event *event); /* Optional */
+ void (*swap_buffers)(mgl_window *self);
+ void (*set_visible)(mgl_window *self, bool visible);
+ bool (*is_key_pressed)(const mgl_window *self, mgl_key key);
+ bool (*is_mouse_button_pressed)(const mgl_window *self, mgl_mouse_button button);
+ void (*set_title)(mgl_window *self, const char *title);
+ void (*set_cursor_visible)(mgl_window *self, bool visible);
+ void (*set_vsync_enabled)(mgl_window *self, bool enabled);
+ bool (*is_vsync_enabled)(const mgl_window *self);
+ void (*set_fullscreen)(mgl_window *self, bool fullscreen);
+ bool (*is_fullscreen)(const mgl_window *self);
+ void (*set_position)(mgl_window *self, mgl_vec2i position);
+ void (*set_size)(mgl_window *self, mgl_vec2i size);
+ void (*set_size_limits)(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum);
+ void (*set_clipboard)(mgl_window *self, const char *str, size_t size);
+ bool (*get_clipboard)(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types);
+ bool (*get_clipboard_string)(mgl_window *self, char **str, size_t *size);
+ void (*set_key_repeat_enabled)(mgl_window *self, bool enabled);
+ void (*flush)(mgl_window *self);
+ void* (*get_egl_display)(mgl_window *self);
+ void* (*get_egl_context)(mgl_window *self);
+ void (*for_each_active_monitor_output)(mgl_window *self, mgl_active_monitor_callback callback, void *userdata);
+
+ void *impl;
+
+ bool vsync_enabled; /* true by default */
+ bool low_latency; /* false by default */
+ bool open;
+ bool focused;
+ bool key_repeat_enabled; /* true by default */
+ double frame_time_limit;
+ double frame_time_limit_monitor;
+ mgl_clock frame_timer;
+ mgl_vec2i pos;
+ mgl_vec2i size;
+ /* relative to the top left of the window. only updates when the cursor is inside the window */
+ mgl_vec2i cursor_position;
+ mgl_view view;
+ mgl_scissor scissor;
+ /* This only contains connected and active monitors */
+ mgl_monitor monitors[MGL_MAX_MONITORS];
+ int num_monitors;
+};
/* TODO: Some of these parameters only apply to new window */
typedef struct {
@@ -87,25 +136,10 @@ typedef struct {
const char *class_name;
mgl_window_type window_type; /* default: normal */
mgl_window_handle transient_for_window; /* 0 = none */
- mgl_render_api render_api; /* default: MGL_RENDER_API_GLX */
+ mgl_render_api render_api; /* default: MGL_RENDER_API_GLX on X11 and MGL_RENDER_API_EGL on Wayland */
+ mgl_window_system window_system; /* default: MGL_WINDOW_SYSTEM_NATIVE */
} mgl_window_create_params;
-typedef enum {
- MGL_CLIPBOARD_TYPE_STRING = 1 << 0,
- MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1,
- MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2,
- MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3,
-} mgl_clipboard_type;
-
-#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF
-#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF)
-
-/*
- 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. Note: vsync is enabled by default */
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);
@@ -142,6 +176,8 @@ bool mgl_window_has_focus(const mgl_window *self);
bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key);
bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button);
+/* Returns 0 if none is available */
+mgl_window_handle mgl_window_get_system_handle(const mgl_window *self);
void mgl_window_close(mgl_window *self);
void mgl_window_set_title(mgl_window *self, const char *title);
void mgl_window_set_cursor_visible(mgl_window *self, bool visible);
@@ -187,7 +223,6 @@ void mgl_window_flush(mgl_window *self);
void* mgl_window_get_egl_display(mgl_window *self);
void* mgl_window_get_egl_context(mgl_window *self);
-typedef void (*mgl_active_monitor_callback)(const mgl_monitor *monitor, void *userdata);
-void mgl_for_each_active_monitor_output(void *display, mgl_active_monitor_callback callback, void *userdata);
+void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata);
#endif /* MGL_WINDOW_H */
diff --git a/include/mgl/window/x11/window.h b/include/mgl/window/x11/window.h
new file mode 100644
index 0000000..9ffc968
--- /dev/null
+++ b/include/mgl/window/x11/window.h
@@ -0,0 +1,9 @@
+#ifndef MGL_X11_WINDOW_H
+#define MGL_X11_WINDOW_H
+
+#include "../window.h"
+
+bool mgl_x11_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window);
+void mgl_x11_window_deinit(mgl_window *self);
+
+#endif /* MGL_X11_WINDOW_H */
diff --git a/meson.build b/meson.build
index 7369b48..5f20c58 100644
--- a/meson.build
+++ b/meson.build
@@ -22,6 +22,7 @@ src = [
'src/system/utf8.c',
'src/system/clock.c',
'src/mgl.c',
+ 'src/window/x11/window.c',
'src/window/window.c',
'src/window/key.c',
'src/gl.c',
diff --git a/src/window/window.c b/src/window/window.c
index 55efffd..56df01a 100644
--- a/src/window/window.c
+++ b/src/window/window.c
@@ -1,1575 +1,31 @@
#include "../../include/mgl/window/window.h"
-#include "../../include/mgl/window/event.h"
+#include "../../include/mgl/window/x11/window.h"
#include "../../include/mgl/mgl.h"
-#include "../../include/mgl/system/utf8.h"
-#include <X11/Xutil.h>
-#include <X11/cursorfont.h>
-#include <X11/Xatom.h>
-#include <X11/extensions/Xrender.h>
-#include <X11/extensions/Xrandr.h>
-#include <X11/XF86keysym.h>
+
#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <limits.h>
-#include <assert.h>
#include <unistd.h>
+#include <assert.h>
+#include <string.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 */
-/* TODO: Separate events from windows. Especially when it comes to monitor events */
-
-/* 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;
-}
-
-static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) {
- XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual);
- if(!pict_format)
- return false;
- return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0);
-}
-
-#define MAX_MONITORS 12
-
-typedef struct {
- GLXContext glx_context;
- GLXFBConfig *fbconfigs;
- GLXFBConfig fbconfig;
- XVisualInfo *visual_info;
-} x11_context_glx;
-
-static bool glx_context_choose(mgl_context *context, x11_context_glx *glx, bool alpha) {
- const int attr[] = {
- GLX_RENDER_TYPE, GLX_RGBA_BIT,
- GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
- GLX_DOUBLEBUFFER, True,
- GLX_RED_SIZE, 8,
- GLX_GREEN_SIZE, 8,
- GLX_BLUE_SIZE, 8,
- GLX_ALPHA_SIZE, alpha ? 8 : 0,
- // TODO:
- //GLX_DEPTH_SIZE, 0,
- None
- };
-
- glx->fbconfigs = NULL;
- glx->visual_info = NULL;
- glx->fbconfig = NULL;
-
- int numfbconfigs = 0;
- glx->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs);
- for(int i = 0; i < numfbconfigs; i++) {
- glx->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, glx->fbconfigs[i]);
- if(!glx->visual_info)
- continue;
-
- if(xvisual_match_alpha(context->connection, glx->visual_info, alpha)) {
- glx->fbconfig = glx->fbconfigs[i];
- break;
- } else {
- XFree(glx->visual_info);
- glx->visual_info = NULL;
- glx->fbconfig = NULL;
- }
- }
-
- if(!glx->visual_info) {
- if(glx->fbconfigs) {
- XFree(glx->fbconfigs);
- glx->fbconfigs = NULL;
- }
- glx->fbconfig = NULL;
-
- fprintf(stderr, "mgl error: no appropriate visual found\n");
- return false;
- }
-
- return true;
-}
-
-static void x11_context_glx_deinit(x11_context_glx *self) {
- mgl_context *context = mgl_get_context();
-
- if(self->glx_context) {
- context->gl.glXMakeContextCurrent(context->connection, None, None, NULL);
- context->gl.glXDestroyContext(context->connection, self->glx_context);
- self->glx_context = NULL;
- }
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-
- if(self->fbconfigs) {
- XFree(self->fbconfigs);
- self->fbconfigs = NULL;
- }
-
- self->fbconfig = NULL;
-}
-
-static bool x11_context_glx_init(x11_context_glx *self, bool alpha) {
- mgl_context *context = mgl_get_context();
- memset(self, 0, sizeof(*self));
-
- if(!glx_context_choose(context, self, alpha)) {
- x11_context_glx_deinit(self);
- return false;
- }
-
- self->glx_context = context->gl.glXCreateNewContext(context->connection, self->fbconfig, GLX_RGBA_TYPE, 0, True);
- if(!self->glx_context) {
- fprintf(stderr, "mgl error: x11_context_glx_init: glXCreateContext failed\n");
- x11_context_glx_deinit(self);
- return false;
- }
-
- return true;
-}
-
-static bool x11_context_glx_make_context_current(x11_context_glx *self, Window window) {
- mgl_context *context = mgl_get_context();
- return context->gl.glXMakeContextCurrent(context->connection, window, window, self->glx_context) != 0;
-}
-
-static void x11_context_glx_swap_buffers(x11_context_glx *self, Window window) {
- (void)self;
- mgl_context *context = mgl_get_context();
- context->gl.glXSwapBuffers(context->connection, window);
-}
-
-static XVisualInfo* x11_context_glx_get_xvisual_info(x11_context_glx *self) {
- return self->visual_info;
-}
-
-static bool x11_context_glx_set_swap_interval(x11_context_glx *self, Window window, int enabled) {
- (void)self;
- mgl_context *context = mgl_get_context();
-
- int result = 0;
- if(context->gl.glXSwapIntervalEXT) {
- context->gl.glXSwapIntervalEXT(context->connection, window, enabled ? 1 : 0);
- } else if(context->gl.glXSwapIntervalMESA) {
- result = context->gl.glXSwapIntervalMESA(enabled ? 1 : 0);
- } else if(context->gl.glXSwapIntervalSGI) {
- result = context->gl.glXSwapIntervalSGI(enabled ? 1 : 0);
- } else {
- static int warned = 0;
- if (!warned) {
- warned = 1;
- fprintf(stderr, "mgl warning: setting vertical sync not supported\n");
- }
- }
-
- if(result != 0)
- fprintf(stderr, "mgl warning: setting vertical sync failed\n");
-
- return result == 0;
-}
-
-typedef struct {
- EGLDisplay egl_display;
- EGLSurface egl_surface;
- EGLContext egl_context;
- EGLConfig *configs;
- EGLConfig ecfg;
- XVisualInfo *visual_info;
-} x11_context_egl;
-
-static int32_t egl_get_config_attrib(x11_context_egl *egl, EGLConfig ecfg, int32_t attribute_name) {
- mgl_context *context = mgl_get_context();
- int32_t value = 0;
- context->gl.eglGetConfigAttrib(egl->egl_display, ecfg, attribute_name, &value);
- return value;
-}
-
-static bool egl_context_choose(mgl_context *context, x11_context_egl *egl, bool alpha) {
- egl->configs = NULL;
- egl->ecfg = NULL;
- egl->visual_info = NULL;
-
- int32_t num_configs = 0;
- context->gl.eglGetConfigs(egl->egl_display, NULL, 0, &num_configs);
- if(num_configs == 0) {
- fprintf(stderr, "mgl error: no configs found\n");
- return false;
- }
-
- egl->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig));
- if(!egl->configs) {
- fprintf(stderr, "mgl error: failed to allocate %d configs\n", (int)num_configs);
- return false;
- }
-
- context->gl.eglGetConfigs(egl->egl_display, egl->configs, num_configs, &num_configs);
- for(int i = 0; i < num_configs; i++) {
- egl->ecfg = egl->configs[i];
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
- continue;
-
- if(!(egl_get_config_attrib(egl, egl->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_RED_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_GREEN_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_BLUE_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0))
- continue;
-
- XVisualInfo vi = {0};
- vi.visualid = egl_get_config_attrib(egl, egl->ecfg, EGL_NATIVE_VISUAL_ID);
- if(!vi.visualid)
- continue;
-
- int vis_count = 0;
- egl->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count);
- if(!egl->visual_info)
- continue;
-
- if(xvisual_match_alpha(context->connection, egl->visual_info, alpha)) {
- break;
- } else {
- XFree(egl->visual_info);
- egl->visual_info = NULL;
- egl->ecfg = NULL;
- }
- }
-
- if(!egl->visual_info) {
- if(egl->configs) {
- free(egl->configs);
- egl->configs = NULL;
- }
- egl->ecfg = NULL;
-
- fprintf(stderr, "mgl error: no appropriate visual found\n");
- return false;
- }
-
- return true;
-}
-
-static void x11_context_egl_deinit(x11_context_egl *self) {
- mgl_context *context = mgl_get_context();
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-
- if(self->configs) {
- free(self->configs);
- self->configs = NULL;
- }
-
- if(self->egl_context) {
- context->gl.eglMakeCurrent(self->egl_display, NULL, NULL, NULL);
- context->gl.eglDestroyContext(self->egl_display, self->egl_context);
- self->egl_context = NULL;
- }
-
- if(self->egl_surface) {
- context->gl.eglDestroySurface(self->egl_display, self->egl_surface);
- self->egl_surface = NULL;
- }
-
- if(self->egl_display) {
- context->gl.eglTerminate(self->egl_display);
- self->egl_display = NULL;
- }
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-}
-
-static bool x11_context_egl_init(x11_context_egl *self, bool alpha) {
- mgl_context *context = mgl_get_context();
- memset(self, 0, sizeof(*self));
-
- const int32_t ctxattr[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE, EGL_NONE
- };
-
- context->gl.eglBindAPI(EGL_OPENGL_API);
-
- self->egl_display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection);
- if(!self->egl_display) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: eglGetDisplay failed\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- if(!context->gl.eglInitialize(self->egl_display, NULL, NULL)) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: eglInitialize failed\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- if(!egl_context_choose(context, self, alpha)) {
- x11_context_egl_deinit(self);
- return false;
- }
-
- self->egl_context = context->gl.eglCreateContext(self->egl_display, self->ecfg, NULL, ctxattr);
- if(!self->egl_context) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: failed to create egl context\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- return true;
-}
-
-static bool x11_context_egl_make_context_current(x11_context_egl *self, Window window) {
- (void)window;
- mgl_context *context = mgl_get_context();
-
- if(!self->egl_surface) {
- self->egl_surface = context->gl.eglCreateWindowSurface(self->egl_display, self->ecfg, (EGLNativeWindowType)window, NULL);
- if(!self->egl_surface) {
- fprintf(stderr, "mgl error: x11_context_egl_make_context_current: failed to create window surface\n");
- return false;
- }
- }
-
- return context->gl.eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context) != 0;
-}
-
-static void x11_context_egl_swap_buffers(x11_context_egl *self, Window window) {
- (void)window;
- mgl_context *context = mgl_get_context();
- context->gl.eglSwapBuffers(self->egl_display, self->egl_surface);
-}
-
-static XVisualInfo* x11_context_egl_get_xvisual_info(x11_context_egl *self) {
- return self->visual_info;
-}
-
-static bool x11_context_egl_set_swap_interval(x11_context_egl *self, Window window, int enabled) {
- (void)window;
- mgl_context *context = mgl_get_context();
- return context->gl.eglSwapInterval(self->egl_display, enabled) == 1;
-}
-
-typedef struct {
- x11_context_glx glx;
- x11_context_egl egl;
- mgl_render_api render_api;
- Colormap color_map;
- XIM xim;
- XIC xic;
- Atom clipboard_atom;
- Atom targets_atom;
- Atom text_atom;
- Atom utf8_string_atom;
- Atom image_png_atom;
- Atom image_jpg_atom;
- Atom image_jpeg_atom;
- Atom image_gif_atom;
- Atom incr_atom;
- Atom net_wm_state_atom;
- Atom net_wm_state_fullscreen_atom;
- Atom net_wm_state_above_atom;
- Atom net_wm_name_atom;
- Atom net_wm_window_type_atom;
- Atom net_wm_window_type_normal_atom;
- Atom net_wm_window_type_dialog_atom;
- Atom net_wm_window_type_notification_atom;
- Atom net_wm_window_type_utility;
- Atom motif_wm_hints_atom;
- Cursor default_cursor;
- Cursor invisible_cursor;
- unsigned int prev_keycode_pressed;
- bool key_was_released;
- bool support_alpha;
-
- /* This only contains connected and active monitors */
- mgl_monitor monitors[MAX_MONITORS];
- int num_monitors;
-
- /*
- 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 void x11_context_deinit(x11_context *self);
-
-static int x11_context_init(x11_context *self, bool alpha, mgl_render_api render_api) {
- mgl_context *context = mgl_get_context();
-
+int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
+ // TODO: Handle |params->window_system|
memset(self, 0, sizeof(*self));
-
- self->render_api = render_api;
-
- /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */
-
- /* TODO: Create all of these with one XInternAtoms call instead */
- 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", False);
- self->image_png_atom = XInternAtom(context->connection, "image/png", False);
- self->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False);
- self->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False);
- self->image_gif_atom = XInternAtom(context->connection, "image/gif", False);
- self->incr_atom = XInternAtom(context->connection, "INCR", False);
- self->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False);
- self->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False);
- self->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False);
- self->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False);
- self->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False);
- self->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False);
- self->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False);
- self->net_wm_window_type_notification_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
- self->net_wm_window_type_utility = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_UTILITY", False);
- self->motif_wm_hints_atom = XInternAtom(context->connection, "_MOTIF_WM_HINTS", False);
-
- self->support_alpha = alpha;
-
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- if(!x11_context_glx_init(&self->glx, alpha)) {
- x11_context_deinit(self);
- return -1;
- }
- break;
- }
- case MGL_RENDER_API_EGL: {
- if(!x11_context_egl_init(&self->egl, alpha)) {
- x11_context_deinit(self);
- return -1;
- }
- break;
- }
- }
-
- self->default_cursor = XCreateFontCursor(context->connection, XC_arrow);
- if(!self->default_cursor) {
- x11_context_deinit(self);
- return -1;
- }
-
- const char data[1] = {0};
- Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1);
- if(!blank_bitmap) {
- x11_context_deinit(self);
- return -1;
- }
-
- XColor dummy;
- self->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0);
- XFreePixmap(context->connection, blank_bitmap);
- if(!self->invisible_cursor) {
- x11_context_deinit(self);
- return -1;
- }
-
- x11_events_circular_buffer_init(&self->events);
-
- return 0;
-}
-
-static void x11_context_clear_monitors(x11_context *self) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->name) {
- free((char*)monitor->name);
- monitor->name = NULL;
- }
- }
- self->num_monitors = 0;
-}
-
-void x11_context_deinit(x11_context *self) {
- mgl_context *context = mgl_get_context();
-
- x11_context_clear_monitors(self);
-
- if(self->color_map) {
- XFreeColormap(context->connection, self->color_map);
- self->color_map = None;
- }
-
- if(self->invisible_cursor) {
- XFreeCursor(context->connection, self->invisible_cursor);
- self->invisible_cursor = None;
- }
-
- if(self->default_cursor) {
- XFreeCursor(context->connection, self->default_cursor);
- self->default_cursor = None;
- }
-
- if(self->xic) {
- XDestroyIC(self->xic);
- self->xic = NULL;
- }
-
- if(self->xim) {
- XCloseIM(self->xim);
- self->xim = NULL;
- }
-
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- x11_context_glx_deinit(&self->glx);
- break;
- }
- case MGL_RENDER_API_EGL: {
- x11_context_egl_deinit(&self->egl);
- break;
- }
- }
-}
-
-static bool x11_context_make_gl_context_current(x11_context *self, Window window) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_make_context_current(&self->glx, window);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_make_context_current(&self->egl, window);
- }
- return false;
-}
-
-static void x11_context_swap_buffers(x11_context *self, Window window) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- x11_context_glx_swap_buffers(&self->glx, window);
- break;
- }
- case MGL_RENDER_API_EGL: {
- x11_context_egl_swap_buffers(&self->egl, window);
- break;
- }
- }
-}
-
-static XVisualInfo* x11_context_get_xvisual_info(x11_context *self) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_get_xvisual_info(&self->glx);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_get_xvisual_info(&self->egl);
- }
- return NULL;
-}
-
-static bool x11_context_set_swap_interval(x11_context *self, Window window, int enabled) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_set_swap_interval(&self->glx, window, enabled);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_set_swap_interval(&self->egl, window, enabled);
- }
- return false;
-}
-
-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);
-}
-
-static mgl_monitor* x11_context_add_monitor(x11_context *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) {
- if(self->num_monitors == MAX_MONITORS)
- return NULL;
-
- mgl_monitor *monitor = &self->monitors[self->num_monitors];
- monitor->id = output_id;
- monitor->crtc_id = crtc_id;
- monitor->name = strdup(name);
- if(!monitor->name)
- return NULL;
- monitor->pos = pos;
- monitor->size = size;
- monitor->refresh_rate = refresh_rate;
- self->num_monitors++;
-
- return monitor;
-}
-
-static mgl_monitor* x11_context_get_monitor_by_id(x11_context *self, RROutput output_id) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->id == (int)output_id)
- return monitor;
- }
- return NULL;
-}
-
-static mgl_monitor* x11_context_get_monitor_by_crtc_id(x11_context *self, RRCrtc crtc_id) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->crtc_id == (int)crtc_id)
- return monitor;
- }
- return NULL;
-}
-
-static bool x11_context_remove_monitor(x11_context *self, RROutput output_id, mgl_event *event) {
- int index_to_remove = -1;
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->id == (int)output_id) {
- index_to_remove = i;
- break;
- }
- }
-
- if(index_to_remove == -1)
- return false;
-
- mgl_monitor *monitor = &self->monitors[index_to_remove];
- free((char*)monitor->name);
- monitor->name = NULL;
-
- for(int i = index_to_remove + 1; i < self->num_monitors; ++i) {
- self->monitors[i - 1] = self->monitors[i];
- }
- self->num_monitors--;
-
- event->monitor_disconnected.id = output_id;
- return true;
-}
-
-static int round_int(double value) {
- return value + 0.5;
-}
-
-static int monitor_info_get_framerate(const XRRModeInfo *mode_info) {
- double v_total = mode_info->vTotal;
- if(mode_info->modeFlags & RR_DoubleScan) {
- v_total *= 2;
- }
-
- if(mode_info->modeFlags & RR_Interlace) {
- v_total /= 2;
- }
-
- if(mode_info->hTotal > 0 && v_total > 0.0001) {
- return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total));
- } else {
- return 0;
- }
-}
-
-static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
- for(int i = 0; i < sr->nmode; ++i) {
- if(sr->modes[i].id == id)
- return &sr->modes[i];
- }
- return NULL;
-}
-
-void mgl_for_each_active_monitor_output(void *display, mgl_active_monitor_callback callback, void *userdata) {
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return;
-
- char display_name[256];
- for(int i = 0; i < screen_res->noutput; ++i) {
- XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
- if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
- XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(crt_info && crt_info->mode) {
- const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info) {
- snprintf(display_name, sizeof(display_name), "%s", out_info->name);
- mgl_monitor monitor = {
- .id = screen_res->outputs[i],
- .crtc_id = out_info->crtc,
- .name = display_name,
- .pos = { .x = crt_info->x, .y = crt_info->y },
- .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
- .refresh_rate = monitor_info_get_framerate(mode_info),
- };
- callback(&monitor, userdata);
- }
- }
- if(crt_info)
- XRRFreeCrtcInfo(crt_info);
- }
- if(out_info)
- XRRFreeOutputInfo(out_info);
- }
-
- XRRFreeScreenResources(screen_res);
-}
-
-static void monitor_callback_add_to_x11_context(const mgl_monitor *monitor, void *userdata) {
- x11_context *x11_context = userdata;
- x11_context_add_monitor(x11_context, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate);
-}
-
-/* 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) */
-static void set_vertical_sync_enabled(mgl_window *self, int enabled) {
- x11_context_set_swap_interval(self->context, self->window, enabled);
-}
-
-static void mgl_window_set_frame_time_limit_monitor(mgl_window *self) {
- int monitor_refresh_rate = 0;
- mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 };
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x
- && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y)
- {
- monitor_refresh_rate = monitor->refresh_rate;
- break;
- }
- }
-
- if(monitor_refresh_rate == 0 && self->num_monitors > 0)
- monitor_refresh_rate = self->monitors[0].refresh_rate;
-
- if(monitor_refresh_rate == 0)
- monitor_refresh_rate = 60;
-
- self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate;
-}
-
-static void mgl_window_on_move(mgl_window *self, int x, int y) {
- self->pos.x = x;
- self->pos.y = y;
- mgl_window_set_frame_time_limit_monitor(self);
-}
-
-static void mgl_window_on_resize(mgl_window *self, int width, int height) {
- self->size.x = width;
- self->size.y = height;
-
- mgl_view view;
- view.position = (mgl_vec2i){ 0, 0 };
- view.size = self->size;
- mgl_window_set_view(self, &view);
- mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size });
-}
-
-static unsigned long mgl_color_to_x11_pixel(mgl_color color) {
- if(color.a == 0)
- return 0;
- return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
-}
-
-static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
- switch(window_type) {
- case MGL_WINDOW_TYPE_NORMAL: {
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_normal_atom, 1L);
- break;
- }
- case MGL_WINDOW_TYPE_DIALOG: {
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_dialog_atom, 1L);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_state_above_atom, 1L);
- break;
- }
- case MGL_WINDOW_TYPE_NOTIFICATION: {
- const Atom data[2] = {
- x11_context->net_wm_window_type_notification_atom,
- x11_context->net_wm_window_type_utility
- };
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)data, 2L);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_state_above_atom, 1L);
- break;
- }
- }
-}
-
-typedef struct {
- unsigned long flags;
- unsigned long functions;
- unsigned long decorations;
- long input_mode;
- unsigned long status;
-} MotifHints;
-
-#define MWM_HINTS_DECORATIONS 2
-
-#define MWM_DECOR_NONE 0
-#define MWM_DECOR_ALL 1
-
-static void mgl_window_set_decorations_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- MotifHints motif_hints = {0};
- motif_hints.flags = MWM_HINTS_DECORATIONS;
- motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE;
-
- XChangeProperty(context->connection, self->window, x11_context->motif_wm_hints_atom, x11_context->motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long));
-}
-
-static int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) {
- self->window = 0;
- self->context = NULL;
- self->open = false;
- self->focused = false;
+ self->vsync_enabled = true;
self->key_repeat_enabled = true;
- self->frame_time_limit = 0.0;
- self->frame_time_limit_monitor = 0.0;
mgl_clock_init(&self->frame_timer);
- self->clipboard_string = NULL;
- self->clipboard_size = 0;
- self->monitors = NULL;
- self->num_monitors = 0;
- self->pos = (mgl_vec2i){ 0, 0 };
- self->low_latency = false;
-
- mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
- if(window_size.x <= 0 || window_size.y <= 0) {
- window_size.x = 640;
- window_size.y = 480;
- }
- self->size = window_size;
-
- mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 };
-
- self->context = malloc(sizeof(x11_context));
- if(!self->context) {
- fprintf(stderr, "mgl error: failed to allocate x11 context\n");
- return -1;
- }
-
- x11_context *x11_context = self->context;
- if(x11_context_init(x11_context, params ? params->support_alpha : false, params ? params->render_api : MGL_RENDER_API_GLX) != 0) {
- fprintf(stderr, "mgl error: x11_context_init failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- self->monitors = x11_context->monitors;
- self->num_monitors = 0;
-
- mgl_context *context = mgl_get_context();
- context->current_window = self;
-
- Window parent_window = params ? params->parent_window : None;
- if(parent_window == 0)
- parent_window = DefaultRootWindow(context->connection);
-
- XVisualInfo *visual_info = x11_context_get_xvisual_info(x11_context);
- x11_context->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), visual_info->visual, AllocNone);
- if(!x11_context->color_map) {
- fprintf(stderr, "mgl error: XCreateColormap failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- XSetWindowAttributes window_attr;
- window_attr.override_redirect = params ? params->override_redirect : false;
- window_attr.colormap = x11_context->color_map;
- window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 255 });
- window_attr.border_pixel = 0;
- window_attr.bit_gravity = NorthWestGravity;
- window_attr.event_mask =
- KeyPressMask | KeyReleaseMask |
- ButtonPressMask | ButtonReleaseMask |
- PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask |
- StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask;
-
- const bool hide_window = params ? params->hidden : false;
-
- if(existing_window) {
- if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) {
- fprintf(stderr, "mgl error: XChangeWindowAttributes failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- if(params && params->size.x > 0 && params->size.y > 0) {
- XResizeWindow(context->connection, existing_window, params->size.x, params->size.y);
- }
-
- self->window = existing_window;
-
- if(params && params->hide_decorations) {
- mgl_window_set_decorations_visible(self, false);
- }
-
- if(hide_window)
- XUnmapWindow(context->connection, existing_window);
- } else {
- self->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y,
- window_size.x, window_size.y, 0,
- visual_info->depth, InputOutput, visual_info->visual,
- CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr);
- if(!self->window) {
- fprintf(stderr, "mgl error: XCreateWindow failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- if(params && params->hide_decorations) {
- mgl_window_set_decorations_visible(self, false);
- }
-
- mgl_window_set_title(self, title);
- if(!hide_window)
- XMapWindow(context->connection, self->window);
- }
-
- if(params)
- 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] = {
- context->wm_delete_window_atom,
- context->net_wm_ping_atom
- };
- XSetWMProtocols(context->connection, self->window, wm_protocol_atoms, 2);
-
- if(context->net_wm_pid_atom) {
- const long pid = getpid();
- XChangeProperty(context->connection, self->window, context->net_wm_pid_atom, XA_CARDINAL,
- 32, PropModeReplace, (const unsigned char*)&pid, 1);
- }
-
- char host_name[HOST_NAME_MAX];
- if(gethostname(host_name, sizeof(host_name)) == 0) {
- XTextProperty txt_prop;
- txt_prop.value = (unsigned char*)host_name;
- txt_prop.encoding = XA_STRING;
- txt_prop.format = 8;
- txt_prop.nitems = strlen(host_name);
- XSetWMClientMachine(context->connection, self->window, &txt_prop);
- }
-
- if(params && params->class_name) {
- XClassHint class_hint = { params->class_name, params->class_name };
- XSetClassHint(context->connection, self->window, &class_hint);
- }
-
- if(params && params->transient_for_window) {
- XSetTransientForHint(context->connection, self->window, params->transient_for_window);
- }
-
- /* TODO: Move this to above XMapWindow? */
- mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL;
- mgl_set_window_type(self, window_type);
-
- XFlush(context->connection);
-
- if(!x11_context_make_gl_context_current(x11_context, self->window)) {
- fprintf(stderr, "mgl error: failed to make opengl context current!\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- self->vsync_enabled = true;
- set_vertical_sync_enabled(self, self->vsync_enabled ? 1 : 0);
-
- context->gl.glEnable(GL_TEXTURE_2D);
- context->gl.glEnable(GL_BLEND);
- context->gl.glEnable(GL_SCISSOR_TEST);
- context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- context->gl.glEnableClientState(GL_VERTEX_ARRAY);
- context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- context->gl.glEnableClientState(GL_COLOR_ARRAY);
- context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
- context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- Window dummy_w;
- int dummy_i;
- unsigned int dummy_u;
- XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u);
-
- x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL);
- if(!x11_context->xim) {
- fprintf(stderr, "mgl error: 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, "mgl error: XCreateIC failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- // TODO: This should be done once and monitor events should be done once, no matter how many windows you have
- x11_context_clear_monitors(x11_context);
- mgl_for_each_active_monitor_output(context->connection, monitor_callback_add_to_x11_context, x11_context);
- self->num_monitors = x11_context->num_monitors;
-
- mgl_window_on_resize(self, self->size.x, self->size.y);
- mgl_window_on_move(self, window_pos.x, window_pos.y);
-
- self->open = true;
- self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */
- return 0;
+ return mgl_x11_window_init(self, title, params, 0) ? 0 : -1;
}
int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params) {
- return mgl_window_init(self, title, params, None);
+ return mgl_window_init(self, title, params, 0);
}
-/* TODO: Test this */
int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window) {
return mgl_window_init(self, "", NULL, existing_window);
}
void mgl_window_deinit(mgl_window *self) {
- x11_context *x11_context = self->context;
-
- mgl_window_close(self);
-
- 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;
-
- self->open = false;
-
- mgl_context *context = mgl_get_context();
- if(context->current_window == self)
- context->current_window = NULL;
-}
-
-/* Returns MGL_KEY_UNKNOWN on no match */
-static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) {
- if(key_sym >= XK_A && key_sym <= XK_Z)
- return MGL_KEY_A + (key_sym - XK_A);
- /* TODO: Check if this ever happens */
- if(key_sym >= XK_a && key_sym <= XK_z)
- return MGL_KEY_A + (key_sym - XK_a);
- if(key_sym >= XK_0 && key_sym <= XK_9)
- return MGL_KEY_NUM0 + (key_sym - XK_0);
- if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9)
- return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0);
-
- /* TODO: Fill in the rest */
- switch(key_sym) {
- case XK_space: return MGL_KEY_SPACE;
- case XK_BackSpace: return MGL_KEY_BACKSPACE;
- case XK_Tab: return MGL_KEY_TAB;
- case XK_Return: return MGL_KEY_ENTER;
- case XK_Escape: return MGL_KEY_ESCAPE;
- case XK_Control_L: return MGL_KEY_LCONTROL;
- case XK_Shift_L: return MGL_KEY_LSHIFT;
- case XK_Alt_L: return MGL_KEY_LALT;
- case XK_Super_L: return MGL_KEY_LSYSTEM;
- case XK_Control_R: return MGL_KEY_RCONTROL;
- case XK_Shift_R: return MGL_KEY_RSHIFT;
- case XK_Alt_R: return MGL_KEY_RALT;
- case XK_Super_R: return MGL_KEY_RSYSTEM;
- case XK_Delete: return MGL_KEY_DELETE;
- case XK_Home: return MGL_KEY_HOME;
- case XK_Left: return MGL_KEY_LEFT;
- case XK_Up: return MGL_KEY_UP;
- case XK_Right: return MGL_KEY_RIGHT;
- case XK_Down: return MGL_KEY_DOWN;
- case XK_Page_Up: return MGL_KEY_PAGEUP;
- case XK_Page_Down: return MGL_KEY_PAGEDOWN;
- case XK_End: return MGL_KEY_END;
- case XK_F1: return MGL_KEY_F1;
- case XK_F2: return MGL_KEY_F2;
- case XK_F3: return MGL_KEY_F3;
- case XK_F4: return MGL_KEY_F4;
- case XK_F5: return MGL_KEY_F5;
- case XK_F6: return MGL_KEY_F6;
- case XK_F7: return MGL_KEY_F7;
- case XK_F8: return MGL_KEY_F8;
- case XK_F9: return MGL_KEY_F9;
- case XK_F10: return MGL_KEY_F10;
- case XK_F11: return MGL_KEY_F11;
- case XK_F12: return MGL_KEY_F12;
- case XK_F13: return MGL_KEY_F13;
- case XK_F14: return MGL_KEY_F14;
- case XK_F15: return MGL_KEY_F15;
- case XK_Insert: return MGL_KEY_INSERT;
- case XK_Pause: return MGL_KEY_PAUSE;
- case XK_Print: return MGL_KEY_PRINTSCREEN;
- case XK_KP_Insert: return MGL_KEY_NUMPAD0;
- case XK_KP_End: return MGL_KEY_NUMPAD1;
- case XK_KP_Down: return MGL_KEY_NUMPAD2;
- case XK_KP_Page_Down: return MGL_KEY_NUMPAD3;
- case XK_KP_Left: return MGL_KEY_NUMPAD4;
- case XK_KP_Begin: return MGL_KEY_NUMPAD5;
- case XK_KP_Right: return MGL_KEY_NUMPAD6;
- case XK_KP_Home: return MGL_KEY_NUMPAD7;
- case XK_KP_Up: return MGL_KEY_NUMPAD8;
- case XK_KP_Page_Up: return MGL_KEY_NUMPAD9;
- case XK_KP_Enter: return MGL_KEY_NUMPAD_ENTER;
- case XF86XK_AudioLowerVolume: return MGL_KEY_AUDIO_LOWER_VOLUME;
- case XF86XK_AudioRaiseVolume: return MGL_KEY_AUDIO_RAISE_VOLUME;
- case XF86XK_AudioPlay: return MGL_KEY_AUDIO_PLAY;
- case XF86XK_AudioStop: return MGL_KEY_AUDIO_STOP;
- case XF86XK_AudioPause: return MGL_KEY_AUDIO_PAUSE;
- case XF86XK_AudioMute: return MGL_KEY_AUDIO_MUTE;
- case XF86XK_AudioPrev: return MGL_KEY_AUDIO_PREV;
- case XF86XK_AudioNext: return MGL_KEY_AUDIO_NEXT;
- case XF86XK_AudioRewind: return MGL_KEY_AUDIO_REWIND;
- case XF86XK_AudioForward: return MGL_KEY_AUDIO_FORWARD;
- case XK_dead_acute: return MGL_KEY_DEAD_ACUTE;
- case XK_apostrophe: return MGL_KEY_APOSTROPHE;
- case XK_F16: return MGL_KEY_F16;
- case XK_F17: return MGL_KEY_F17;
- case XK_F18: return MGL_KEY_F18;
- case XK_F19: return MGL_KEY_F19;
- case XK_F20: return MGL_KEY_F20;
- case XK_F21: return MGL_KEY_F21;
- case XK_F22: return MGL_KEY_F22;
- case XK_F23: return MGL_KEY_F23;
- case XK_F24: return MGL_KEY_F24;
- }
- return MGL_KEY_UNKNOWN;
-}
-
-static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
- switch(button) {
- case Button1: return MGL_BUTTON_LEFT;
- case Button2: return MGL_BUTTON_MIDDLE;
- case Button3: return MGL_BUTTON_RIGHT;
- case 8: return MGL_BUTTON_XBUTTON1;
- case 9: return MGL_BUTTON_XBUTTON2;
- }
- 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) || ((xkey->state & Mod5Mask) != 0);
- event->key.control = ((xkey->state & ControlMask) != 0);
- event->key.shift = ((xkey->state & ShiftMask) != 0);
- event->key.system = ((xkey->state & Mod4Mask) != 0);
-}
-
-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 */
-
- for(size_t i = 0; i < input_str_len;) {
- const unsigned char *cp = (const unsigned char*)&buf[i];
- uint32_t codepoint;
- size_t clen;
- if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) {
- codepoint = *cp;
- clen = 1;
- }
-
- mgl_event text_event;
- text_event.type = MGL_EVENT_TEXT_ENTERED;
- text_event.text.codepoint = codepoint;
- text_event.text.size = clen;
- memcpy(text_event.text.str, &buf[i], clen);
- text_event.text.str[clen] = '\0';
- /* Text may contain multiple codepoints so they need to separated into multiple events */
- if(!x11_context_append_event(x11_context, &text_event))
- break;
-
- i += clen;
- }
-}
-
-static bool mgl_on_monitor_added(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) {
- char display_name[256];
- mgl_monitor *monitor = NULL;
- XRRCrtcInfo *crt_info = NULL;
- const XRRModeInfo *mode_info = NULL;
-
- if(!rr_output_change_event->mode)
- return false;
-
- if(x11_context_get_monitor_by_id(x11_context, output_id))
- return false;
-
- if(out_info->nameLen >= (int)sizeof(display_name))
- return false;
-
- crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(!crt_info)
- goto done;
-
- mode_info = get_mode_info(screen_res, rr_output_change_event->mode);
- if(!mode_info)
- goto done;
-
- memcpy(display_name, out_info->name, out_info->nameLen);
- display_name[out_info->nameLen] = '\0';
-
- monitor = x11_context_add_monitor(x11_context, output_id, out_info->crtc, display_name,
- (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y },
- (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height },
- monitor_info_get_framerate(mode_info));
-
- if(!monitor)
- goto done;
-
- event->monitor_connected.id = monitor->id;
- event->monitor_connected.name = monitor->name;
- event->monitor_connected.x = monitor->pos.x;
- event->monitor_connected.y = monitor->pos.y;
- event->monitor_connected.width = monitor->size.x;
- event->monitor_connected.height = monitor->size.y;
- event->monitor_connected.refresh_rate = monitor->refresh_rate;
-
- done:
- if(crt_info)
- XRRFreeCrtcInfo(crt_info);
- return monitor != NULL;
-}
-
-static bool mgl_on_monitor_state_changed(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) {
- bool state_changed = false;
- XRROutputInfo *out_info = NULL;
-
- if(!rr_output_change_event->output)
- return false;
-
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return false;
-
- out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output);
- if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
- state_changed = mgl_on_monitor_added(display, x11_context, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event);
- } else {
- state_changed = x11_context_remove_monitor(x11_context, rr_output_change_event->output, event);
- }
-
- if(out_info)
- XRRFreeOutputInfo(out_info);
-
- XRRFreeScreenResources(screen_res);
- return state_changed;
-}
-
-static bool mgl_on_monitor_property_changed(Display *display, x11_context *x11_context, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) {
- if(!rr_crtc_change_event->crtc)
- return false;
-
- mgl_monitor *monitor = x11_context_get_monitor_by_crtc_id(x11_context, rr_crtc_change_event->crtc);
- if(!monitor)
- return false;
-
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return false;
-
- monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y };
- monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height };
- const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode);
- if(mode_info)
- monitor->refresh_rate = monitor_info_get_framerate(mode_info);
-
- XRRFreeScreenResources(screen_res);
-
- event->monitor_property_changed.id = monitor->id;
- event->monitor_property_changed.name = monitor->name;
- event->monitor_property_changed.x = monitor->pos.x;
- event->monitor_property_changed.y = monitor->pos.y;
- event->monitor_property_changed.width = monitor->size.x;
- event->monitor_property_changed.height = monitor->size.y;
- event->monitor_property_changed.refresh_rate = monitor->refresh_rate;
- return true;
-}
-
-/* Returns true if an event was generated */
-static bool mgl_on_rr_notify(mgl_context *context, x11_context *x11_context, XEvent *xev, int subtype, mgl_event *event) {
- switch(subtype) {
- case RRNotify_CrtcChange: {
- XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev;
- return mgl_on_monitor_property_changed(context->connection, x11_context, rr_crtc_change_event, event);
- }
- case RRNotify_OutputChange: {
- XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev;
- return mgl_on_monitor_state_changed(context->connection, x11_context, rr_output_change_event, event);
- }
- }
- return false;
-}
-
-static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context, bool injected) {
- x11_context *x11_context = self->context;
- switch(xev->type - context->randr_event_base) {
- case RRScreenChangeNotify: {
- XRRUpdateConfiguration(xev);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case RRNotify: {
- XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev;
- if(mgl_on_rr_notify(context, x11_context, xev, rr_event->subtype, event)) {
- self->num_monitors = x11_context->num_monitors;
- return;
- }
-
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
-
- switch(xev->type) {
- case KeyPress: {
- if(!self->key_repeat_enabled && xev->xkey.keycode == x11_context->prev_keycode_pressed && !x11_context->key_was_released) {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
-
- x11_context->prev_keycode_pressed = xev->xkey.keycode;
- x11_context->key_was_released = false;
-
- event->type = MGL_EVENT_KEY_PRESSED;
- mgl_window_handle_key_event(self, &xev->xkey, event, context);
- mgl_window_handle_text_event(self, xev);
- return;
- }
- case KeyRelease: {
- if(xev->xkey.keycode == x11_context->prev_keycode_pressed)
- x11_context->key_was_released = true;
-
- event->type = MGL_EVENT_KEY_RELEASED;
- mgl_window_handle_key_event(self, &xev->xkey, event, context);
- return;
- }
- case ButtonPress: {
- if(xev->xbutton.button == Button4) {
- /* Mouse scroll up */
- event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
- event->mouse_wheel_scroll.delta = 1;
- event->mouse_wheel_scroll.x = xev->xbutton.x;
- event->mouse_wheel_scroll.y = xev->xbutton.y;
- } else if(xev->xbutton.button == Button5) {
- /* Mouse scroll down */
- event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
- event->mouse_wheel_scroll.delta = -1;
- event->mouse_wheel_scroll.x = xev->xbutton.x;
- event->mouse_wheel_scroll.y = xev->xbutton.y;
- } else {
- 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;
- }
- 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;
- }
- case FocusIn: {
- XSetICFocus(x11_context->xic);
-
- XWMHints* hints = XGetWMHints(context->connection, self->window);
- if(hints) {
- hints->flags &= ~XUrgencyHint;
- XSetWMHints(context->connection, self->window, hints);
- XFree(hints);
- }
-
- event->type = MGL_EVENT_GAINED_FOCUS;
- self->focused = true;
- return;
- }
- case FocusOut: {
- XUnsetICFocus(x11_context->xic);
- event->type = MGL_EVENT_LOST_FOCUS;
- self->focused = false;
- return;
- }
- case ConfigureNotify: {
- if(!injected)
- while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {}
-
- if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) {
- mgl_window_on_move(self, xev->xconfigure.x, xev->xconfigure.y);
- }
-
- if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) {
- 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;
- }
-
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case MotionNotify: {
- if(!injected)
- 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;
- }
- case SelectionClear: {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case SelectionRequest: {
- 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);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- } 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);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- } 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);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
-
- selection_event.property = None;
- XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
- XFlush(context->connection);
- 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;
- self->open = false;
- 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);
- XFlush(context->connection);
- }
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case MappingNotify: {
- /* TODO: Only handle this globally once for mgl, not for each window */
- XRefreshKeyboardMapping(&xev->xmapping);
- event->type = MGL_EVENT_MAPPING_CHANGED;
- event->mapping_changed.type = MappingModifier;
- switch(xev->xmapping.request) {
- case MappingModifier:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_MODIFIER;
- break;
- case MappingKeyboard:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_KEYBOARD;
- break;
- case MappingPointer:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_POINTER;
- break;
- }
- return;
- }
- default: {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
+ mgl_x11_window_deinit(self);
}
void mgl_window_clear(mgl_window *self, mgl_color color) {
@@ -1579,36 +35,19 @@ 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);
- if(xev.xany.window == self->window || xev.type == ClientMessage || xev.type == MappingNotify || xev.type - context->randr_event_base >= 0)
- mgl_window_on_receive_event(self, &xev, event, context, false);
- else
- event->type = MGL_EVENT_UNKNOWN;
- return true;
- } else {
- return false;
- }
+ return self->poll_event(self, event);
}
bool mgl_window_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) {
- mgl_context *context = mgl_get_context();
- event->type = MGL_EVENT_UNKNOWN;
- mgl_window_on_receive_event(self, xev, event, context, true);
- return event->type != MGL_EVENT_UNKNOWN;
+ if(self->inject_x11_event)
+ return self->inject_x11_event(self, xev, event);
+ else
+ return false;
}
void mgl_window_display(mgl_window *self) {
mgl_context *context = mgl_get_context();
- x11_context_swap_buffers(self->context, self->window);
+ self->swap_buffers(self);
if(self->low_latency) {
context->gl.glFlush();
context->gl.glFinish();
@@ -1653,12 +92,7 @@ void mgl_window_get_scissor(mgl_window *self, mgl_scissor *scissor) {
}
void mgl_window_set_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- if(visible)
- XMapWindow(context->connection, self->window);
- else
- XUnmapWindow(context->connection, self->window);
- XFlush(context->connection);
+ self->set_visible(self, visible);
}
bool mgl_window_is_open(const mgl_window *self) {
@@ -1674,19 +108,7 @@ bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key) {
if(key < 0 || key >= __MGL_NUM_KEYS__)
return false;
- mgl_context *context = mgl_get_context();
- const KeySym keysym = mgl_key_to_x11_keysym(key);
- if(keysym == XK_VoidSymbol)
- return false;
-
- KeyCode keycode = XKeysymToKeycode(context->connection, keysym);
- if(keycode == 0)
- return false;
-
- char keys[32];
- XQueryKeymap(context->connection, keys);
-
- return (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
+ return self->is_key_pressed(self, key);
}
/* TODO: Track keys with events instead, but somehow handle window focus lost */
@@ -1694,48 +116,23 @@ bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button
if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__)
return false;
- mgl_context *context = mgl_get_context();
- Window root, child;
- int root_x, root_y, win_x, win_y;
-
- unsigned int buttons_mask = 0;
- XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask);
-
- switch(button) {
- case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask;
- case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask;
- case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask;
- case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */
- case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */
- default: return false;
- }
+ return self->is_mouse_button_pressed(self, button);
+}
- return false;
+mgl_window_handle mgl_window_get_system_handle(const mgl_window *self) {
+ return self->get_system_handle(self);
}
void mgl_window_close(mgl_window *self) {
- mgl_context *context = mgl_get_context();
- if(self->window) {
- XDestroyWindow(context->connection, self->window);
- XSync(context->connection, False);
- self->window = None;
- }
- self->open = false;
+ self->close(self);
}
void mgl_window_set_title(mgl_window *self, const char *title) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- XStoreName(context->connection, self->window, title);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_name_atom, x11_context->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title));
- XFlush(context->connection);
+ self->set_title(self, title);
}
void mgl_window_set_cursor_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
- XDefineCursor(context->connection, self->window, visible ? x11_context->default_cursor : x11_context->invisible_cursor);
+ self->set_cursor_visible(self, visible);
}
void mgl_window_set_framerate_limit(mgl_window *self, int fps) {
@@ -1746,52 +143,19 @@ void mgl_window_set_framerate_limit(mgl_window *self, int fps) {
}
void mgl_window_set_vsync_enabled(mgl_window *self, bool enabled) {
- self->vsync_enabled = enabled;
- set_vertical_sync_enabled(self, self->vsync_enabled ? 1 : 0);
+ self->set_vsync_enabled(self, enabled);
}
bool mgl_window_is_vsync_enabled(const mgl_window *self) {
- return self->vsync_enabled;
+ return self->is_vsync_enabled(self);
}
void mgl_window_set_fullscreen(mgl_window *self, bool fullscreen) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- XEvent xev;
- xev.type = ClientMessage;
- xev.xclient.window = self->window;
- xev.xclient.message_type = x11_context->net_wm_state_atom;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = fullscreen ? 1 : 0;
- xev.xclient.data.l[1] = x11_context->net_wm_state_fullscreen_atom;
- xev.xclient.data.l[2] = 0;
- xev.xclient.data.l[3] = 1;
- xev.xclient.data.l[4] = 0;
-
- if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
- fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
- return;
- }
-
- XFlush(context->connection);
+ self->set_fullscreen(self, fullscreen);
}
bool mgl_window_is_fullscreen(const mgl_window *self) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- bool is_fullscreen = false;
- Atom type = None;
- int format = 0;
- unsigned long items = 0;
- unsigned long remaining_bytes = 0;
- unsigned char *data = NULL;
- if(XGetWindowProperty(context->connection, self->window, x11_context->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
- is_fullscreen = format == 32 && *(unsigned long*)data == x11_context->net_wm_state_fullscreen_atom;
- XFree(data);
- }
- return is_fullscreen;
+ return self->is_fullscreen(self);
}
void mgl_window_set_low_latency(mgl_window *self, bool low_latency) {
@@ -1803,262 +167,23 @@ bool mgl_window_is_low_latency_enabled(const mgl_window *self) {
}
void mgl_window_set_position(mgl_window *self, mgl_vec2i position) {
- XMoveWindow(mgl_get_context()->connection, self->window, position.x, position.y);
- XFlush(mgl_get_context()->connection);
+ self->set_position(self, position);
}
void mgl_window_set_size(mgl_window *self, mgl_vec2i size) {
- if(size.x < 0)
- size.x = 0;
- if(size.y < 0)
- size.y = 0;
- XResizeWindow(mgl_get_context()->connection, self->window, size.x, size.y);
- XFlush(mgl_get_context()->connection);
+ self->set_size(self, size);
}
void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) {
- XSizeHints *size_hints = XAllocSizeHints();
- if(size_hints) {
- size_hints->width = self->size.x;
- size_hints->min_width = minimum.x;
- size_hints->max_width = maximum.x;
-
- size_hints->height = self->size.y;
- size_hints->min_height = minimum.y;
- size_hints->max_height = maximum.y;
-
- size_hints->flags = PSize;
- if(size_hints->min_width || size_hints->min_height)
- size_hints->flags |= PMinSize;
- if(size_hints->max_width || size_hints->max_height)
- size_hints->flags |= PMaxSize;
-
- mgl_context *context = mgl_get_context();
- XSetWMNormalHints(context->connection, self->window, size_hints);
- XFree(size_hints);
- XFlush(context->connection);
- } else {
- fprintf(stderr, "mgl warning: failed to set window size hints\n");
- }
+ self->set_size_limits(self, minimum, maximum);
}
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;
- }
-
- XSetSelectionOwner(context->connection, x11_context->clipboard_atom, self->window, CurrentTime);
- XFlush(context->connection);
-
- /* Check if setting the selection owner was successful */
- if(XGetSelectionOwner(context->connection, x11_context->clipboard_atom) != self->window) {
- fprintf(stderr, "mgl error: mgl_window_set_clipboard failed\n");
- return;
- }
-
- self->clipboard_string = malloc(size + 1);
- if(!self->clipboard_string) {
- fprintf(stderr, "mgl error: failed to allocate string for clipboard\n");
- return;
- }
- memcpy(self->clipboard_string, str, size);
- self->clipboard_string[size] = '\0';
- self->clipboard_size = 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_atom && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_PNG;
- } else if((type == x11_context->image_jpg_atom || type == x11_context->image_jpeg_atom) && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_JPG;
- } else if(type == x11_context->image_gif_atom && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_GIF;
- } else {
- return -1;
- }
+ self->set_clipboard(self, str, size);
}
bool mgl_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) {
- assert(callback);
-
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- Window selection_owner = XGetSelectionOwner(context->connection, x11_context->clipboard_atom);
-
- 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)
- return false;
-
- 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)) {}
-
- /* Sorted by preference */
- /* TODO: Support more types (BITMAP?, PIXMAP?) */
- Atom supported_clipboard_types[7];
-
- int supported_clipboard_type_index = 0;
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_png_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_jpeg_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_gif_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->utf8_string_atom;
- supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING;
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->text_atom;
- }
-
- const unsigned long num_supported_clipboard_types = supported_clipboard_type_index;
-
- 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);
- bool success = false;
-
- const double timeout_seconds = 5.0;
- while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
- /* TODO: Wait for SelectionNotify event instead */
- while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) {
- if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
- break;
-
- if(!xev.xselection.property)
- continue;
-
- if(!xev.xselection.target || xev.xselection.selection != x11_context->clipboard_atom)
- continue;
-
- Atom type = None;
- int format;
- unsigned long items;
- unsigned long remaining_bytes = 0;
- unsigned char *data = NULL;
-
- if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) {
- /* TODO: Wait for PropertyNotify event instead */
- if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
- 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 */
- XFree(data);
- goto done;
- } 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 got_data = false;
- long chunk_size = 65536;
-
- //XDeleteProperty(context->connection, self->window, x11_context->incr_atom);
- //XFlush(context->connection);
-
- while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
- unsigned long offset = 0;
-
- /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */
- /* TODO: Wait for PropertyNotify event instead */
- while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) {
- if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
- break;
-
- if(type == x11_context->incr_atom) {
- if(data)
- chunk_size = *(long*)data;
- XDeleteProperty(context->connection, self->window, x11_context->incr_atom);
- XFlush(context->connection);
- XFree(data);
- data = NULL;
- break;
- } else if(type == requested_clipboard_type) {
- got_data = true;
- }
-
- if(!got_data && items == 0) {
- XDeleteProperty(context->connection, self->window, type);
- XFlush(context->connection);
- if(data) {
- XFree(data);
- data = NULL;
- }
- break;
- }
-
- const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */
- if(got_data) {
- const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(x11_context, type, format);
- if(data && num_items_bytes > 0 && clipboard_type != -1) {
- if(!callback(data, num_items_bytes, clipboard_type, userdata)) {
- XFree(data);
- goto done;
- }
- }
- }
- offset += num_items_bytes;
-
- if(data) {
- XFree(data);
- data = NULL;
- }
-
- if(got_data && num_items_bytes == 0/* && format == 8*/) {
- success = true;
- goto done;
- }
-
- if(remaining_bytes == 0)
- break;
- }
- }
-
- goto done;
- }
- }
- }
-
- done:
- if(requested_clipboard_type)
- XDeleteProperty(context->connection, self->window, requested_clipboard_type);
- return success;
+ return self->get_clipboard(self, callback, userdata, clipboard_types);
}
typedef struct {
@@ -2110,26 +235,21 @@ 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) {
- self->key_repeat_enabled = enabled;
+ self->set_key_repeat_enabled(self, enabled);
}
void mgl_window_flush(mgl_window *self) {
- mgl_context *context = mgl_get_context();
- XFlush(context->connection);
+ self->flush(self);
}
void* mgl_window_get_egl_display(mgl_window *self) {
- x11_context *x11_context = self->context;
- if(x11_context->render_api == MGL_RENDER_API_EGL)
- return x11_context->egl.egl_display;
- else
- return NULL;
+ return self->get_egl_display(self);
}
void* mgl_window_get_egl_context(mgl_window *self) {
- x11_context *x11_context = self->context;
- if(x11_context->render_api == MGL_RENDER_API_EGL)
- return x11_context->egl.egl_context;
- else
- return NULL;
+ return self->get_egl_context(self);
+}
+
+void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) {
+ self->for_each_active_monitor_output(self, callback, userdata);
}
diff --git a/src/window/x11/window.c b/src/window/x11/window.c
new file mode 100644
index 0000000..59df3bc
--- /dev/null
+++ b/src/window/x11/window.c
@@ -0,0 +1,2066 @@
+#include "../../../include/mgl/window/x11/window.h"
+#include "../../../include/mgl/window/event.h"
+#include "../../../include/mgl/mgl.h"
+#include "../../../include/mgl/system/utf8.h"
+#include "../../../include/mgl/system/clock.h"
+#include <X11/Xutil.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/XF86keysym.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.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 */
+/* TODO: Separate events from windows. Especially when it comes to monitor events */
+
+/* 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;
+
+typedef struct {
+ GLXContext glx_context;
+ GLXFBConfig *fbconfigs;
+ GLXFBConfig fbconfig;
+ XVisualInfo *visual_info;
+} mgl_x11_window_glx;
+
+typedef struct {
+ EGLDisplay egl_display;
+ EGLSurface egl_surface;
+ EGLContext egl_context;
+ EGLConfig *configs;
+ EGLConfig ecfg;
+ XVisualInfo *visual_info;
+} mgl_x11_window_egl;
+
+typedef struct {
+ void *dpy;
+ Window window;
+ char *clipboard_string;
+ size_t clipboard_size;
+
+ mgl_x11_window_glx glx;
+ mgl_x11_window_egl egl;
+ mgl_render_api render_api;
+ Colormap color_map;
+ XIM xim;
+ XIC xic;
+ Atom clipboard_atom;
+ Atom targets_atom;
+ Atom text_atom;
+ Atom utf8_string_atom;
+ Atom image_png_atom;
+ Atom image_jpg_atom;
+ Atom image_jpeg_atom;
+ Atom image_gif_atom;
+ Atom incr_atom;
+ Atom net_wm_state_atom;
+ Atom net_wm_state_fullscreen_atom;
+ Atom net_wm_state_above_atom;
+ Atom net_wm_name_atom;
+ Atom net_wm_window_type_atom;
+ Atom net_wm_window_type_normal_atom;
+ Atom net_wm_window_type_dialog_atom;
+ Atom net_wm_window_type_notification_atom;
+ Atom net_wm_window_type_utility;
+ Atom motif_wm_hints_atom;
+ Cursor default_cursor;
+ Cursor invisible_cursor;
+ unsigned int prev_keycode_pressed;
+ bool key_was_released;
+ bool support_alpha;
+
+ XEvent xev;
+
+ /*
+ 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;
+} mgl_x11_window;
+
+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;
+}
+
+static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) {
+ XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual);
+ if(!pict_format)
+ return false;
+ return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0);
+}
+
+static bool glx_context_choose(mgl_context *context, mgl_x11_window_glx *glx, bool alpha) {
+ const int attr[] = {
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ GLX_DOUBLEBUFFER, True,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, alpha ? 8 : 0,
+ // TODO:
+ //GLX_DEPTH_SIZE, 0,
+ None
+ };
+
+ glx->fbconfigs = NULL;
+ glx->visual_info = NULL;
+ glx->fbconfig = NULL;
+
+ int numfbconfigs = 0;
+ glx->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs);
+ for(int i = 0; i < numfbconfigs; i++) {
+ glx->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, glx->fbconfigs[i]);
+ if(!glx->visual_info)
+ continue;
+
+ if(xvisual_match_alpha(context->connection, glx->visual_info, alpha)) {
+ glx->fbconfig = glx->fbconfigs[i];
+ break;
+ } else {
+ XFree(glx->visual_info);
+ glx->visual_info = NULL;
+ glx->fbconfig = NULL;
+ }
+ }
+
+ if(!glx->visual_info) {
+ if(glx->fbconfigs) {
+ XFree(glx->fbconfigs);
+ glx->fbconfigs = NULL;
+ }
+ glx->fbconfig = NULL;
+
+ fprintf(stderr, "mgl error: no appropriate visual found\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void mgl_x11_window_glx_deinit(mgl_x11_window_glx *self) {
+ mgl_context *context = mgl_get_context();
+
+ if(self->glx_context) {
+ context->gl.glXMakeContextCurrent(context->connection, None, None, NULL);
+ context->gl.glXDestroyContext(context->connection, self->glx_context);
+ self->glx_context = NULL;
+ }
+
+ if(self->visual_info) {
+ XFree(self->visual_info);
+ self->visual_info = NULL;
+ }
+
+ if(self->fbconfigs) {
+ XFree(self->fbconfigs);
+ self->fbconfigs = NULL;
+ }
+
+ self->fbconfig = NULL;
+}
+
+static bool mgl_x11_window_glx_init(mgl_x11_window_glx *self, bool alpha) {
+ mgl_context *context = mgl_get_context();
+ memset(self, 0, sizeof(*self));
+
+ if(!glx_context_choose(context, self, alpha)) {
+ mgl_x11_window_glx_deinit(self);
+ return false;
+ }
+
+ self->glx_context = context->gl.glXCreateNewContext(context->connection, self->fbconfig, GLX_RGBA_TYPE, 0, True);
+ if(!self->glx_context) {
+ fprintf(stderr, "mgl error: mgl_x11_window_glx_init: glXCreateContext failed\n");
+ mgl_x11_window_glx_deinit(self);
+ return false;
+ }
+
+ return true;
+}
+
+static bool mgl_x11_window_glx_make_context_current(mgl_x11_window_glx *self, Window window) {
+ mgl_context *context = mgl_get_context();
+ return context->gl.glXMakeContextCurrent(context->connection, window, window, self->glx_context) != 0;
+}
+
+static void mgl_x11_window_glx_swap_buffers(mgl_x11_window_glx *self, Window window) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ context->gl.glXSwapBuffers(context->connection, window);
+}
+
+static XVisualInfo* mgl_x11_window_glx_get_xvisual_info(mgl_x11_window_glx *self) {
+ return self->visual_info;
+}
+
+static bool mgl_x11_window_glx_set_swap_interval(mgl_x11_window_glx *self, Window window, int enabled) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+
+ int result = 0;
+ if(context->gl.glXSwapIntervalEXT) {
+ context->gl.glXSwapIntervalEXT(context->connection, window, enabled ? 1 : 0);
+ } else if(context->gl.glXSwapIntervalMESA) {
+ result = context->gl.glXSwapIntervalMESA(enabled ? 1 : 0);
+ } else if(context->gl.glXSwapIntervalSGI) {
+ result = context->gl.glXSwapIntervalSGI(enabled ? 1 : 0);
+ } else {
+ static int warned = 0;
+ if (!warned) {
+ warned = 1;
+ fprintf(stderr, "mgl warning: setting vertical sync not supported\n");
+ }
+ }
+
+ if(result != 0)
+ fprintf(stderr, "mgl warning: setting vertical sync failed\n");
+
+ return result == 0;
+}
+
+static int32_t egl_get_config_attrib(mgl_x11_window_egl *egl, EGLConfig ecfg, int32_t attribute_name) {
+ mgl_context *context = mgl_get_context();
+ int32_t value = 0;
+ context->gl.eglGetConfigAttrib(egl->egl_display, ecfg, attribute_name, &value);
+ return value;
+}
+
+static bool egl_context_choose(mgl_context *context, mgl_x11_window_egl *egl, bool alpha) {
+ egl->configs = NULL;
+ egl->ecfg = NULL;
+ egl->visual_info = NULL;
+
+ int32_t num_configs = 0;
+ context->gl.eglGetConfigs(egl->egl_display, NULL, 0, &num_configs);
+ if(num_configs == 0) {
+ fprintf(stderr, "mgl error: no configs found\n");
+ return false;
+ }
+
+ egl->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig));
+ if(!egl->configs) {
+ fprintf(stderr, "mgl error: failed to allocate %d configs\n", (int)num_configs);
+ return false;
+ }
+
+ context->gl.eglGetConfigs(egl->egl_display, egl->configs, num_configs, &num_configs);
+ for(int i = 0; i < num_configs; i++) {
+ egl->ecfg = egl->configs[i];
+
+ if(egl_get_config_attrib(egl, egl->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
+ continue;
+
+ if(!(egl_get_config_attrib(egl, egl->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
+ continue;
+
+ if(egl_get_config_attrib(egl, egl->ecfg, EGL_RED_SIZE) != 8)
+ continue;
+
+ if(egl_get_config_attrib(egl, egl->ecfg, EGL_GREEN_SIZE) != 8)
+ continue;
+
+ if(egl_get_config_attrib(egl, egl->ecfg, EGL_BLUE_SIZE) != 8)
+ continue;
+
+ if(egl_get_config_attrib(egl, egl->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0))
+ continue;
+
+ XVisualInfo vi = {0};
+ vi.visualid = egl_get_config_attrib(egl, egl->ecfg, EGL_NATIVE_VISUAL_ID);
+ if(!vi.visualid)
+ continue;
+
+ int vis_count = 0;
+ egl->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count);
+ if(!egl->visual_info)
+ continue;
+
+ if(xvisual_match_alpha(context->connection, egl->visual_info, alpha)) {
+ break;
+ } else {
+ XFree(egl->visual_info);
+ egl->visual_info = NULL;
+ egl->ecfg = NULL;
+ }
+ }
+
+ if(!egl->visual_info) {
+ if(egl->configs) {
+ free(egl->configs);
+ egl->configs = NULL;
+ }
+ egl->ecfg = NULL;
+
+ fprintf(stderr, "mgl error: no appropriate visual found\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void mgl_x11_window_egl_deinit(mgl_x11_window_egl *self) {
+ mgl_context *context = mgl_get_context();
+
+ if(self->visual_info) {
+ XFree(self->visual_info);
+ self->visual_info = NULL;
+ }
+
+ if(self->configs) {
+ free(self->configs);
+ self->configs = NULL;
+ }
+
+ if(self->egl_context) {
+ context->gl.eglMakeCurrent(self->egl_display, NULL, NULL, NULL);
+ context->gl.eglDestroyContext(self->egl_display, self->egl_context);
+ self->egl_context = NULL;
+ }
+
+ if(self->egl_surface) {
+ context->gl.eglDestroySurface(self->egl_display, self->egl_surface);
+ self->egl_surface = NULL;
+ }
+
+ if(self->egl_display) {
+ context->gl.eglTerminate(self->egl_display);
+ self->egl_display = NULL;
+ }
+
+ if(self->visual_info) {
+ XFree(self->visual_info);
+ self->visual_info = NULL;
+ }
+}
+
+static bool mgl_x11_window_egl_init(mgl_x11_window_egl *self, bool alpha) {
+ mgl_context *context = mgl_get_context();
+ memset(self, 0, sizeof(*self));
+
+ const int32_t ctxattr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE, EGL_NONE
+ };
+
+ context->gl.eglBindAPI(EGL_OPENGL_API);
+
+ self->egl_display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection);
+ if(!self->egl_display) {
+ fprintf(stderr, "mgl error: mgl_x11_window_egl_init failed: eglGetDisplay failed\n");
+ mgl_x11_window_egl_deinit(self);
+ return false;
+ }
+
+ if(!context->gl.eglInitialize(self->egl_display, NULL, NULL)) {
+ fprintf(stderr, "mgl error: mgl_x11_window_egl_init failed: eglInitialize failed\n");
+ mgl_x11_window_egl_deinit(self);
+ return false;
+ }
+
+ if(!egl_context_choose(context, self, alpha)) {
+ mgl_x11_window_egl_deinit(self);
+ return false;
+ }
+
+ self->egl_context = context->gl.eglCreateContext(self->egl_display, self->ecfg, NULL, ctxattr);
+ if(!self->egl_context) {
+ fprintf(stderr, "mgl error: mgl_x11_window_egl_init failed: failed to create egl context\n");
+ mgl_x11_window_egl_deinit(self);
+ return false;
+ }
+
+ return true;
+}
+
+static bool mgl_x11_window_egl_make_context_current(mgl_x11_window_egl *self, Window window) {
+ (void)window;
+ mgl_context *context = mgl_get_context();
+
+ if(!self->egl_surface) {
+ self->egl_surface = context->gl.eglCreateWindowSurface(self->egl_display, self->ecfg, (EGLNativeWindowType)window, NULL);
+ if(!self->egl_surface) {
+ fprintf(stderr, "mgl error: mgl_x11_window_egl_make_context_current: failed to create window surface\n");
+ return false;
+ }
+ }
+
+ return context->gl.eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context) != 0;
+}
+
+static void mgl_x11_window_egl_swap_buffers(mgl_x11_window_egl *self, Window window) {
+ (void)window;
+ mgl_context *context = mgl_get_context();
+ context->gl.eglSwapBuffers(self->egl_display, self->egl_surface);
+}
+
+static XVisualInfo* mgl_x11_window_egl_get_xvisual_info(mgl_x11_window_egl *self) {
+ return self->visual_info;
+}
+
+static bool mgl_x11_window_egl_set_swap_interval(mgl_x11_window_egl *self, Window window, int enabled) {
+ (void)window;
+ mgl_context *context = mgl_get_context();
+ return context->gl.eglSwapInterval(self->egl_display, enabled) == 1;
+}
+
+static void mgl_x11_window_clear_monitors(mgl_window *self) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->name) {
+ free((char*)monitor->name);
+ monitor->name = NULL;
+ }
+ }
+ self->num_monitors = 0;
+}
+
+static bool mgl_x11_window_make_gl_context_current(mgl_x11_window *self, Window window) {
+ switch(self->render_api) {
+ case MGL_RENDER_API_GLX:
+ return mgl_x11_window_glx_make_context_current(&self->glx, window);
+ case MGL_RENDER_API_EGL:
+ return mgl_x11_window_egl_make_context_current(&self->egl, window);
+ }
+ return false;
+}
+
+static XVisualInfo* mgl_x11_window_get_xvisual_info(mgl_x11_window *self) {
+ switch(self->render_api) {
+ case MGL_RENDER_API_GLX:
+ return mgl_x11_window_glx_get_xvisual_info(&self->glx);
+ case MGL_RENDER_API_EGL:
+ return mgl_x11_window_egl_get_xvisual_info(&self->egl);
+ }
+ return NULL;
+}
+
+static bool mgl_x11_window_set_swap_interval(mgl_x11_window *self, Window window, int enabled) {
+ switch(self->render_api) {
+ case MGL_RENDER_API_GLX:
+ return mgl_x11_window_glx_set_swap_interval(&self->glx, window, enabled);
+ case MGL_RENDER_API_EGL:
+ return mgl_x11_window_egl_set_swap_interval(&self->egl, window, enabled);
+ }
+ return false;
+}
+
+static bool mgl_x11_window_append_event(mgl_x11_window *self, const mgl_event *event) {
+ return x11_events_circular_buffer_append(&self->events, event);
+}
+
+static bool mgl_x11_window_pop_event(mgl_x11_window *self, mgl_event *event) {
+ return x11_events_circular_buffer_pop(&self->events, event);
+}
+
+static mgl_monitor* mgl_x11_window_add_monitor(mgl_window *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) {
+ if(self->num_monitors == MGL_MAX_MONITORS)
+ return NULL;
+
+ mgl_monitor *monitor = &self->monitors[self->num_monitors];
+ monitor->id = output_id;
+ monitor->crtc_id = crtc_id;
+ monitor->name = strdup(name);
+ if(!monitor->name)
+ return NULL;
+ monitor->pos = pos;
+ monitor->size = size;
+ monitor->refresh_rate = refresh_rate;
+ self->num_monitors++;
+
+ return monitor;
+}
+
+static mgl_monitor* mgl_x11_window_get_monitor_by_id(mgl_window *self, RROutput output_id) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->id == (int)output_id)
+ return monitor;
+ }
+ return NULL;
+}
+
+static mgl_monitor* mgl_x11_window_get_monitor_by_crtc_id(mgl_window *self, RRCrtc crtc_id) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->crtc_id == (int)crtc_id)
+ return monitor;
+ }
+ return NULL;
+}
+
+static bool mgl_x11_window_remove_monitor(mgl_window *self, RROutput output_id, mgl_event *event) {
+ int index_to_remove = -1;
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->id == (int)output_id) {
+ index_to_remove = i;
+ break;
+ }
+ }
+
+ if(index_to_remove == -1)
+ return false;
+
+ mgl_monitor *monitor = &self->monitors[index_to_remove];
+ free((char*)monitor->name);
+ monitor->name = NULL;
+
+ for(int i = index_to_remove + 1; i < self->num_monitors; ++i) {
+ self->monitors[i - 1] = self->monitors[i];
+ }
+ self->num_monitors--;
+
+ event->monitor_disconnected.id = output_id;
+ event->type = MGL_EVENT_MONITOR_DISCONNECTED;
+ return true;
+}
+
+static int round_int(double value) {
+ return value + 0.5;
+}
+
+static int monitor_info_get_framerate(const XRRModeInfo *mode_info) {
+ double v_total = mode_info->vTotal;
+ if(mode_info->modeFlags & RR_DoubleScan) {
+ v_total *= 2;
+ }
+
+ if(mode_info->modeFlags & RR_Interlace) {
+ v_total /= 2;
+ }
+
+ if(mode_info->hTotal > 0 && v_total > 0.0001) {
+ return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total));
+ } else {
+ return 0;
+ }
+}
+
+static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
+ for(int i = 0; i < sr->nmode; ++i) {
+ if(sr->modes[i].id == id)
+ return &sr->modes[i];
+ }
+ return NULL;
+}
+
+static void mgl_x11_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ XRRScreenResources *screen_res = XRRGetScreenResources(context->connection, DefaultRootWindow(context->connection));
+ if(!screen_res)
+ return;
+
+ char display_name[256];
+ for(int i = 0; i < screen_res->noutput; ++i) {
+ XRROutputInfo *out_info = XRRGetOutputInfo(context->connection, screen_res, screen_res->outputs[i]);
+ if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
+ XRRCrtcInfo *crt_info = XRRGetCrtcInfo(context->connection, screen_res, out_info->crtc);
+ if(crt_info && crt_info->mode) {
+ const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
+ if(mode_info) {
+ snprintf(display_name, sizeof(display_name), "%s", out_info->name);
+ mgl_monitor monitor = {
+ .id = screen_res->outputs[i],
+ .crtc_id = out_info->crtc,
+ .name = display_name,
+ .pos = { .x = crt_info->x, .y = crt_info->y },
+ .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
+ .refresh_rate = monitor_info_get_framerate(mode_info),
+ };
+ callback(&monitor, userdata);
+ }
+ }
+ if(crt_info)
+ XRRFreeCrtcInfo(crt_info);
+ }
+ if(out_info)
+ XRRFreeOutputInfo(out_info);
+ }
+
+ XRRFreeScreenResources(screen_res);
+}
+
+static void monitor_callback_add_to_mgl_x11_window(const mgl_monitor *monitor, void *userdata) {
+ mgl_window *mgl_window = userdata;
+ mgl_x11_window_add_monitor(mgl_window, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate);
+}
+
+/* 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) */
+static void set_vertical_sync_enabled(mgl_x11_window *self, int enabled) {
+ mgl_x11_window_set_swap_interval(self, self->window, enabled);
+}
+
+static void mgl_x11_window_set_frame_time_limit_monitor(mgl_window *self) {
+ int monitor_refresh_rate = 0;
+ mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 };
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x
+ && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y)
+ {
+ monitor_refresh_rate = monitor->refresh_rate;
+ break;
+ }
+ }
+
+ if(monitor_refresh_rate == 0 && self->num_monitors > 0)
+ monitor_refresh_rate = self->monitors[0].refresh_rate;
+
+ if(monitor_refresh_rate == 0)
+ monitor_refresh_rate = 60;
+
+ self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate;
+}
+
+static void mgl_x11_window_on_move(mgl_window *self, int x, int y) {
+ self->pos.x = x;
+ self->pos.y = y;
+ mgl_x11_window_set_frame_time_limit_monitor(self);
+}
+
+static void mgl_x11_window_on_resize(mgl_window *self, int width, int height) {
+ self->size.x = width;
+ self->size.y = height;
+
+ mgl_view view;
+ view.position = (mgl_vec2i){ 0, 0 };
+ view.size = self->size;
+ mgl_window_set_view(self, &view);
+ mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size });
+}
+
+static unsigned long mgl_color_to_x11_pixel(mgl_color color) {
+ if(color.a == 0)
+ return 0;
+ return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
+}
+
+static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ switch(window_type) {
+ case MGL_WINDOW_TYPE_NORMAL: {
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_normal_atom, 1L);
+ break;
+ }
+ case MGL_WINDOW_TYPE_DIALOG: {
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_dialog_atom, 1L);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L);
+ break;
+ }
+ case MGL_WINDOW_TYPE_NOTIFICATION: {
+ const Atom data[2] = {
+ impl->net_wm_window_type_notification_atom,
+ impl->net_wm_window_type_utility
+ };
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)data, 2L);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L);
+ break;
+ }
+ }
+}
+
+typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long input_mode;
+ unsigned long status;
+} MotifHints;
+
+#define MWM_HINTS_DECORATIONS 2
+
+#define MWM_DECOR_NONE 0
+#define MWM_DECOR_ALL 1
+
+static void mgl_x11_window_set_decorations_visible(mgl_window *self, bool visible) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ MotifHints motif_hints = {0};
+ motif_hints.flags = MWM_HINTS_DECORATIONS;
+ motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE;
+
+ XChangeProperty(context->connection, impl->window, impl->motif_wm_hints_atom, impl->motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long));
+}
+
+static void mgl_x11_window_set_title(mgl_window *self, const char *title) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ XStoreName(context->connection, impl->window, title);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_name_atom, impl->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title));
+ XFlush(context->connection);
+}
+
+static void mgl_x11_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) {
+ mgl_x11_window *impl = self->impl;
+ XSizeHints *size_hints = XAllocSizeHints();
+ if(size_hints) {
+ size_hints->width = self->size.x;
+ size_hints->min_width = minimum.x;
+ size_hints->max_width = maximum.x;
+
+ size_hints->height = self->size.y;
+ size_hints->min_height = minimum.y;
+ size_hints->max_height = maximum.y;
+
+ size_hints->flags = PSize;
+ if(size_hints->min_width || size_hints->min_height)
+ size_hints->flags |= PMinSize;
+ if(size_hints->max_width || size_hints->max_height)
+ size_hints->flags |= PMaxSize;
+
+ mgl_context *context = mgl_get_context();
+ XSetWMNormalHints(context->connection, impl->window, size_hints);
+ XFree(size_hints);
+ XFlush(context->connection);
+ } else {
+ fprintf(stderr, "mgl warning: failed to set window size hints\n");
+ }
+}
+
+static bool mgl_x11_setup_window(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) {
+ mgl_x11_window *impl = self->impl;
+ impl->window = 0;
+ impl->clipboard_string = NULL;
+ impl->clipboard_size = 0;
+
+ mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
+ if(window_size.x <= 0 || window_size.y <= 0) {
+ window_size.x = 640;
+ window_size.y = 480;
+ }
+ self->size = window_size;
+
+ mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 };
+ mgl_context *context = mgl_get_context();
+
+ Window parent_window = params ? params->parent_window : None;
+ if(parent_window == 0)
+ parent_window = DefaultRootWindow(context->connection);
+
+ XVisualInfo *visual_info = mgl_x11_window_get_xvisual_info(impl);
+ impl->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), visual_info->visual, AllocNone);
+ if(!impl->color_map) {
+ fprintf(stderr, "mgl error: XCreateColormap failed\n");
+ return false;
+ }
+
+ XSetWindowAttributes window_attr;
+ window_attr.override_redirect = params ? params->override_redirect : false;
+ window_attr.colormap = impl->color_map;
+ window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 255 });
+ window_attr.border_pixel = 0;
+ window_attr.bit_gravity = NorthWestGravity;
+ window_attr.event_mask =
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask |
+ StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask;
+
+ const bool hide_window = params ? params->hidden : false;
+
+ if(existing_window) {
+ if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) {
+ fprintf(stderr, "mgl error: XChangeWindowAttributes failed\n");
+ return false;
+ }
+
+ if(params && params->size.x > 0 && params->size.y > 0) {
+ XResizeWindow(context->connection, existing_window, params->size.x, params->size.y);
+ }
+
+ impl->window = existing_window;
+
+ if(params && params->hide_decorations) {
+ mgl_x11_window_set_decorations_visible(self, false);
+ }
+
+ if(hide_window)
+ XUnmapWindow(context->connection, existing_window);
+ } else {
+ impl->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y,
+ window_size.x, window_size.y, 0,
+ visual_info->depth, InputOutput, visual_info->visual,
+ CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr);
+ if(!impl->window) {
+ fprintf(stderr, "mgl error: XCreateWindow failed\n");
+ return false;
+ }
+
+ if(params && params->hide_decorations) {
+ mgl_x11_window_set_decorations_visible(self, false);
+ }
+
+ mgl_x11_window_set_title(self, title);
+ if(!hide_window)
+ XMapWindow(context->connection, impl->window);
+ }
+
+ if(params)
+ mgl_x11_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] = {
+ context->wm_delete_window_atom,
+ context->net_wm_ping_atom
+ };
+ XSetWMProtocols(context->connection, impl->window, wm_protocol_atoms, 2);
+
+ if(context->net_wm_pid_atom) {
+ const long pid = getpid();
+ XChangeProperty(context->connection, impl->window, context->net_wm_pid_atom, XA_CARDINAL,
+ 32, PropModeReplace, (const unsigned char*)&pid, 1);
+ }
+
+ char host_name[HOST_NAME_MAX];
+ if(gethostname(host_name, sizeof(host_name)) == 0) {
+ XTextProperty txt_prop;
+ txt_prop.value = (unsigned char*)host_name;
+ txt_prop.encoding = XA_STRING;
+ txt_prop.format = 8;
+ txt_prop.nitems = strlen(host_name);
+ XSetWMClientMachine(context->connection, impl->window, &txt_prop);
+ }
+
+ if(params && params->class_name) {
+ XClassHint class_hint = { (char*)params->class_name, (char*)params->class_name };
+ XSetClassHint(context->connection, impl->window, &class_hint);
+ }
+
+ if(params && params->transient_for_window) {
+ XSetTransientForHint(context->connection, impl->window, params->transient_for_window);
+ }
+
+ /* TODO: Move this to above XMapWindow? */
+ mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL;
+ mgl_set_window_type(self, window_type);
+
+ XFlush(context->connection);
+
+ if(!mgl_x11_window_make_gl_context_current(impl, impl->window)) {
+ fprintf(stderr, "mgl error: failed to make opengl context current!\n");
+ return false;
+ }
+
+ self->vsync_enabled = true;
+ set_vertical_sync_enabled(impl, self->vsync_enabled ? 1 : 0);
+
+ context->gl.glEnable(GL_TEXTURE_2D);
+ context->gl.glEnable(GL_BLEND);
+ context->gl.glEnable(GL_SCISSOR_TEST);
+ context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ context->gl.glEnableClientState(GL_VERTEX_ARRAY);
+ context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ context->gl.glEnableClientState(GL_COLOR_ARRAY);
+ context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ Window dummy_w;
+ int dummy_i;
+ unsigned int dummy_u;
+ XQueryPointer(context->connection, impl->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u);
+
+ impl->xim = XOpenIM(context->connection, NULL, NULL, NULL);
+ if(!impl->xim) {
+ fprintf(stderr, "mgl error: XOpenIM failed\n");
+ return false;
+ }
+
+ impl->xic = XCreateIC(impl->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, impl->window, NULL);
+ if(!impl->xic) {
+ fprintf(stderr, "mgl error: XCreateIC failed\n");
+ return false;
+ }
+
+ // TODO: This should be done once and monitor events should be done once, no matter how many windows you have
+ mgl_x11_window_clear_monitors(self);
+ mgl_x11_window_for_each_active_monitor_output(self, monitor_callback_add_to_mgl_x11_window, self);
+
+ mgl_x11_window_on_resize(self, self->size.x, self->size.y);
+ mgl_x11_window_on_move(self, window_pos.x, window_pos.y);
+
+ self->open = true;
+ self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */
+ return true;
+}
+
+/* Returns MGL_KEY_UNKNOWN on no match */
+static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) {
+ if(key_sym >= XK_A && key_sym <= XK_Z)
+ return MGL_KEY_A + (key_sym - XK_A);
+ /* TODO: Check if this ever happens */
+ if(key_sym >= XK_a && key_sym <= XK_z)
+ return MGL_KEY_A + (key_sym - XK_a);
+ if(key_sym >= XK_0 && key_sym <= XK_9)
+ return MGL_KEY_NUM0 + (key_sym - XK_0);
+ if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9)
+ return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0);
+
+ /* TODO: Fill in the rest */
+ switch(key_sym) {
+ case XK_space: return MGL_KEY_SPACE;
+ case XK_BackSpace: return MGL_KEY_BACKSPACE;
+ case XK_Tab: return MGL_KEY_TAB;
+ case XK_Return: return MGL_KEY_ENTER;
+ case XK_Escape: return MGL_KEY_ESCAPE;
+ case XK_Control_L: return MGL_KEY_LCONTROL;
+ case XK_Shift_L: return MGL_KEY_LSHIFT;
+ case XK_Alt_L: return MGL_KEY_LALT;
+ case XK_Super_L: return MGL_KEY_LSYSTEM;
+ case XK_Control_R: return MGL_KEY_RCONTROL;
+ case XK_Shift_R: return MGL_KEY_RSHIFT;
+ case XK_Alt_R: return MGL_KEY_RALT;
+ case XK_Super_R: return MGL_KEY_RSYSTEM;
+ case XK_Delete: return MGL_KEY_DELETE;
+ case XK_Home: return MGL_KEY_HOME;
+ case XK_Left: return MGL_KEY_LEFT;
+ case XK_Up: return MGL_KEY_UP;
+ case XK_Right: return MGL_KEY_RIGHT;
+ case XK_Down: return MGL_KEY_DOWN;
+ case XK_Page_Up: return MGL_KEY_PAGEUP;
+ case XK_Page_Down: return MGL_KEY_PAGEDOWN;
+ case XK_End: return MGL_KEY_END;
+ case XK_F1: return MGL_KEY_F1;
+ case XK_F2: return MGL_KEY_F2;
+ case XK_F3: return MGL_KEY_F3;
+ case XK_F4: return MGL_KEY_F4;
+ case XK_F5: return MGL_KEY_F5;
+ case XK_F6: return MGL_KEY_F6;
+ case XK_F7: return MGL_KEY_F7;
+ case XK_F8: return MGL_KEY_F8;
+ case XK_F9: return MGL_KEY_F9;
+ case XK_F10: return MGL_KEY_F10;
+ case XK_F11: return MGL_KEY_F11;
+ case XK_F12: return MGL_KEY_F12;
+ case XK_F13: return MGL_KEY_F13;
+ case XK_F14: return MGL_KEY_F14;
+ case XK_F15: return MGL_KEY_F15;
+ case XK_Insert: return MGL_KEY_INSERT;
+ case XK_Pause: return MGL_KEY_PAUSE;
+ case XK_Print: return MGL_KEY_PRINTSCREEN;
+ case XK_KP_Insert: return MGL_KEY_NUMPAD0;
+ case XK_KP_End: return MGL_KEY_NUMPAD1;
+ case XK_KP_Down: return MGL_KEY_NUMPAD2;
+ case XK_KP_Page_Down: return MGL_KEY_NUMPAD3;
+ case XK_KP_Left: return MGL_KEY_NUMPAD4;
+ case XK_KP_Begin: return MGL_KEY_NUMPAD5;
+ case XK_KP_Right: return MGL_KEY_NUMPAD6;
+ case XK_KP_Home: return MGL_KEY_NUMPAD7;
+ case XK_KP_Up: return MGL_KEY_NUMPAD8;
+ case XK_KP_Page_Up: return MGL_KEY_NUMPAD9;
+ case XK_KP_Enter: return MGL_KEY_NUMPAD_ENTER;
+ case XF86XK_AudioLowerVolume: return MGL_KEY_AUDIO_LOWER_VOLUME;
+ case XF86XK_AudioRaiseVolume: return MGL_KEY_AUDIO_RAISE_VOLUME;
+ case XF86XK_AudioPlay: return MGL_KEY_AUDIO_PLAY;
+ case XF86XK_AudioStop: return MGL_KEY_AUDIO_STOP;
+ case XF86XK_AudioPause: return MGL_KEY_AUDIO_PAUSE;
+ case XF86XK_AudioMute: return MGL_KEY_AUDIO_MUTE;
+ case XF86XK_AudioPrev: return MGL_KEY_AUDIO_PREV;
+ case XF86XK_AudioNext: return MGL_KEY_AUDIO_NEXT;
+ case XF86XK_AudioRewind: return MGL_KEY_AUDIO_REWIND;
+ case XF86XK_AudioForward: return MGL_KEY_AUDIO_FORWARD;
+ case XK_dead_acute: return MGL_KEY_DEAD_ACUTE;
+ case XK_apostrophe: return MGL_KEY_APOSTROPHE;
+ case XK_F16: return MGL_KEY_F16;
+ case XK_F17: return MGL_KEY_F17;
+ case XK_F18: return MGL_KEY_F18;
+ case XK_F19: return MGL_KEY_F19;
+ case XK_F20: return MGL_KEY_F20;
+ case XK_F21: return MGL_KEY_F21;
+ case XK_F22: return MGL_KEY_F22;
+ case XK_F23: return MGL_KEY_F23;
+ case XK_F24: return MGL_KEY_F24;
+ }
+ return MGL_KEY_UNKNOWN;
+}
+
+static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
+ switch(button) {
+ case Button1: return MGL_BUTTON_LEFT;
+ case Button2: return MGL_BUTTON_MIDDLE;
+ case Button3: return MGL_BUTTON_RIGHT;
+ case 8: return MGL_BUTTON_XBUTTON1;
+ case 9: return MGL_BUTTON_XBUTTON2;
+ }
+ return MGL_BUTTON_UNKNOWN;
+}
+
+static void mgl_window_handle_key_event(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) || ((xkey->state & Mod5Mask) != 0);
+ event->key.control = ((xkey->state & ControlMask) != 0);
+ event->key.shift = ((xkey->state & ShiftMask) != 0);
+ event->key.system = ((xkey->state & Mod4Mask) != 0);
+}
+
+static void mgl_window_handle_text_event(mgl_x11_window *self, XEvent *xev) {
+ /* Ignore silent keys */
+ if(XFilterEvent(xev, None))
+ return;
+
+ char buf[128];
+
+ Status status;
+ KeySym ksym;
+ const size_t input_str_len = Xutf8LookupString(self->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 */
+
+ for(size_t i = 0; i < input_str_len;) {
+ const unsigned char *cp = (const unsigned char*)&buf[i];
+ uint32_t codepoint;
+ size_t clen;
+ if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) {
+ codepoint = *cp;
+ clen = 1;
+ }
+
+ mgl_event text_event;
+ text_event.type = MGL_EVENT_TEXT_ENTERED;
+ text_event.text.codepoint = codepoint;
+ text_event.text.size = clen;
+ memcpy(text_event.text.str, &buf[i], clen);
+ text_event.text.str[clen] = '\0';
+ /* Text may contain multiple codepoints so they need to separated into multiple events */
+ if(!mgl_x11_window_append_event(self, &text_event))
+ break;
+
+ i += clen;
+ }
+}
+
+static bool mgl_on_monitor_added(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) {
+ char display_name[256];
+ mgl_monitor *monitor = NULL;
+ XRRCrtcInfo *crt_info = NULL;
+ const XRRModeInfo *mode_info = NULL;
+
+ if(!rr_output_change_event->mode)
+ return false;
+
+ if(mgl_x11_window_get_monitor_by_id(mgl_window, output_id))
+ return false;
+
+ if(out_info->nameLen >= (int)sizeof(display_name))
+ return false;
+
+ crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
+ if(!crt_info)
+ goto done;
+
+ mode_info = get_mode_info(screen_res, rr_output_change_event->mode);
+ if(!mode_info)
+ goto done;
+
+ memcpy(display_name, out_info->name, out_info->nameLen);
+ display_name[out_info->nameLen] = '\0';
+
+ monitor = mgl_x11_window_add_monitor(mgl_window, output_id, out_info->crtc, display_name,
+ (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y },
+ (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height },
+ monitor_info_get_framerate(mode_info));
+
+ if(!monitor)
+ goto done;
+
+ event->monitor_connected.id = monitor->id;
+ event->monitor_connected.name = monitor->name;
+ event->monitor_connected.x = monitor->pos.x;
+ event->monitor_connected.y = monitor->pos.y;
+ event->monitor_connected.width = monitor->size.x;
+ event->monitor_connected.height = monitor->size.y;
+ event->monitor_connected.refresh_rate = monitor->refresh_rate;
+ event->type = MGL_EVENT_MONITOR_CONNECTED;
+
+ done:
+ if(crt_info)
+ XRRFreeCrtcInfo(crt_info);
+ return monitor != NULL;
+}
+
+static bool mgl_on_monitor_state_changed(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) {
+ bool state_changed = false;
+ XRROutputInfo *out_info = NULL;
+
+ if(!rr_output_change_event->output)
+ return false;
+
+ XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
+ if(!screen_res)
+ return false;
+
+ out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output);
+ if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
+ state_changed = mgl_on_monitor_added(display, mgl_window, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event);
+ } else {
+ state_changed = mgl_x11_window_remove_monitor(mgl_window, rr_output_change_event->output, event);
+ }
+
+ if(out_info)
+ XRRFreeOutputInfo(out_info);
+
+ XRRFreeScreenResources(screen_res);
+ return state_changed;
+}
+
+static bool mgl_on_monitor_property_changed(Display *display, mgl_window *mgl_window, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) {
+ if(!rr_crtc_change_event->crtc)
+ return false;
+
+ mgl_monitor *monitor = mgl_x11_window_get_monitor_by_crtc_id(mgl_window, rr_crtc_change_event->crtc);
+ if(!monitor)
+ return false;
+
+ XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
+ if(!screen_res)
+ return false;
+
+ monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y };
+ monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height };
+ const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode);
+ if(mode_info)
+ monitor->refresh_rate = monitor_info_get_framerate(mode_info);
+
+ XRRFreeScreenResources(screen_res);
+
+ event->monitor_property_changed.id = monitor->id;
+ event->monitor_property_changed.name = monitor->name;
+ event->monitor_property_changed.x = monitor->pos.x;
+ event->monitor_property_changed.y = monitor->pos.y;
+ event->monitor_property_changed.width = monitor->size.x;
+ event->monitor_property_changed.height = monitor->size.y;
+ event->monitor_property_changed.refresh_rate = monitor->refresh_rate;
+ event->type = MGL_EVENT_MONITOR_PROPERTY_CHANGED;
+ return true;
+}
+
+/* Returns true if an event was generated */
+static bool mgl_on_rr_notify(mgl_context *context, mgl_window *mgl_window, XEvent *xev, int subtype, mgl_event *event) {
+ switch(subtype) {
+ case RRNotify_CrtcChange: {
+ XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev;
+ return mgl_on_monitor_property_changed(context->connection, mgl_window, rr_crtc_change_event, event);
+ }
+ case RRNotify_OutputChange: {
+ XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev;
+ return mgl_on_monitor_state_changed(context->connection, mgl_window, rr_output_change_event, event);
+ }
+ }
+ return false;
+}
+
+static void mgl_x11_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context, bool injected) {
+ mgl_x11_window *impl = self->impl;
+ switch(xev->type - context->randr_event_base) {
+ case RRScreenChangeNotify: {
+ XRRUpdateConfiguration(xev);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case RRNotify: {
+ XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev;
+ if(mgl_on_rr_notify(context, self, xev, rr_event->subtype, event)) {
+ // TODO:
+ //impl->num_monitors = mgl_x11_window->num_monitors;
+ return;
+ }
+
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+
+ switch(xev->type) {
+ case KeyPress: {
+ if(!self->key_repeat_enabled && xev->xkey.keycode == impl->prev_keycode_pressed && !impl->key_was_released) {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+
+ impl->prev_keycode_pressed = xev->xkey.keycode;
+ impl->key_was_released = false;
+
+ event->type = MGL_EVENT_KEY_PRESSED;
+ mgl_window_handle_key_event(&xev->xkey, event, context);
+ mgl_window_handle_text_event(impl, xev);
+ return;
+ }
+ case KeyRelease: {
+ if(xev->xkey.keycode == impl->prev_keycode_pressed)
+ impl->key_was_released = true;
+
+ event->type = MGL_EVENT_KEY_RELEASED;
+ mgl_window_handle_key_event(&xev->xkey, event, context);
+ return;
+ }
+ case ButtonPress: {
+ if(xev->xbutton.button == Button4) {
+ /* Mouse scroll up */
+ event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
+ event->mouse_wheel_scroll.delta = 1;
+ event->mouse_wheel_scroll.x = xev->xbutton.x;
+ event->mouse_wheel_scroll.y = xev->xbutton.y;
+ } else if(xev->xbutton.button == Button5) {
+ /* Mouse scroll down */
+ event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
+ event->mouse_wheel_scroll.delta = -1;
+ event->mouse_wheel_scroll.x = xev->xbutton.x;
+ event->mouse_wheel_scroll.y = xev->xbutton.y;
+ } else {
+ 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;
+ }
+ 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;
+ }
+ case FocusIn: {
+ XSetICFocus(impl->xic);
+
+ XWMHints* hints = XGetWMHints(context->connection, impl->window);
+ if(hints) {
+ hints->flags &= ~XUrgencyHint;
+ XSetWMHints(context->connection, impl->window, hints);
+ XFree(hints);
+ }
+
+ event->type = MGL_EVENT_GAINED_FOCUS;
+ self->focused = true;
+ return;
+ }
+ case FocusOut: {
+ XUnsetICFocus(impl->xic);
+ event->type = MGL_EVENT_LOST_FOCUS;
+ self->focused = false;
+ return;
+ }
+ case ConfigureNotify: {
+ if(!injected)
+ while(XCheckTypedWindowEvent(context->connection, impl->window, ConfigureNotify, xev)) {}
+
+ if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) {
+ mgl_x11_window_on_move(self, xev->xconfigure.x, xev->xconfigure.y);
+ }
+
+ if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) {
+ mgl_x11_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;
+ }
+
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case MotionNotify: {
+ if(!injected)
+ while(XCheckTypedWindowEvent(context->connection, impl->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;
+ }
+ case SelectionClear: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case SelectionRequest: {
+ 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 == impl->clipboard_atom) {
+ /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */
+ if(selection_event.target == impl->targets_atom) {
+ /* A client requested for our valid conversion targets */
+ int num_targets = 3;
+ Atom targets[4];
+ targets[0] = impl->targets_atom;
+ targets[1] = impl->text_atom;
+ targets[2] = XA_STRING;
+ if(impl->utf8_string_atom) {
+ targets[3] = impl->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 = impl->targets_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ } else if(selection_event.target == XA_STRING || (!impl->utf8_string_atom && selection_event.target == impl->text_atom)) {
+ /* A client requested ascii clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size);
+ selection_event.target = XA_STRING;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ } else if(impl->utf8_string_atom && (selection_event.target == impl->utf8_string_atom || selection_event.target == impl->text_atom)) {
+ /* A client requested utf8 clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, impl->utf8_string_atom, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size);
+ selection_event.target = impl->utf8_string_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+
+ selection_event.property = None;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ 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;
+ self->open = false;
+ 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);
+ XFlush(context->connection);
+ }
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case MappingNotify: {
+ /* TODO: Only handle this globally once for mgl, not for each window */
+ XRefreshKeyboardMapping(&xev->xmapping);
+ event->type = MGL_EVENT_MAPPING_CHANGED;
+ event->mapping_changed.type = MappingModifier;
+ switch(xev->xmapping.request) {
+ case MappingModifier:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_MODIFIER;
+ break;
+ case MappingKeyboard:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_KEYBOARD;
+ break;
+ case MappingPointer:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_POINTER;
+ break;
+ }
+ return;
+ }
+ default: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+}
+
+static bool mgl_x11_window_poll_event(mgl_window *self, mgl_event *event) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ Display *display = context->connection;
+
+ if(mgl_x11_window_pop_event(impl, event))
+ return true;
+
+ if(XPending(display)) {
+ XNextEvent(display, &impl->xev);
+ if(impl->xev.xany.window == impl->window || impl->xev.type == ClientMessage || impl->xev.type == MappingNotify || impl->xev.type - context->randr_event_base >= 0)
+ mgl_x11_window_on_receive_event(self, &impl->xev, event, context, false);
+ else
+ event->type = MGL_EVENT_UNKNOWN;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool mgl_x11_window_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) {
+ mgl_context *context = mgl_get_context();
+ event->type = MGL_EVENT_UNKNOWN;
+ mgl_x11_window_on_receive_event(self, xev, event, context, true);
+ return event->type != MGL_EVENT_UNKNOWN;
+}
+
+static void mgl_x11_window_swap_buffers(mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ switch(impl->render_api) {
+ case MGL_RENDER_API_GLX: {
+ mgl_x11_window_glx_swap_buffers(&impl->glx, impl->window);
+ break;
+ }
+ case MGL_RENDER_API_EGL: {
+ mgl_x11_window_egl_swap_buffers(&impl->egl, impl->window);
+ break;
+ }
+ }
+}
+
+static void mgl_x11_window_set_visible(mgl_window *self, bool visible) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(visible)
+ XMapWindow(context->connection, impl->window);
+ else
+ XUnmapWindow(context->connection, impl->window);
+ XFlush(context->connection);
+}
+
+/* TODO: Track keys with events instead, but somehow handle window focus lost */
+static bool mgl_x11_window_is_key_pressed(const mgl_window *self, mgl_key key) {
+ (void)self;
+ if(key < 0 || key >= __MGL_NUM_KEYS__)
+ return false;
+
+ mgl_context *context = mgl_get_context();
+ const KeySym keysym = mgl_key_to_x11_keysym(key);
+ if(keysym == XK_VoidSymbol)
+ return false;
+
+ KeyCode keycode = XKeysymToKeycode(context->connection, keysym);
+ if(keycode == 0)
+ return false;
+
+ char keys[32];
+ XQueryKeymap(context->connection, keys);
+
+ return (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
+}
+
+/* TODO: Track keys with events instead, but somehow handle window focus lost */
+static bool mgl_x11_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button) {
+ (void)self;
+ if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__)
+ return false;
+
+ mgl_context *context = mgl_get_context();
+ Window root, child;
+ int root_x, root_y, win_x, win_y;
+
+ unsigned int buttons_mask = 0;
+ XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask);
+
+ switch(button) {
+ case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask;
+ case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask;
+ case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask;
+ case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */
+ case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */
+ default: return false;
+ }
+
+ return false;
+}
+
+static mgl_window_handle mgl_x11_window_get_system_handle(const mgl_window *self) {
+ const mgl_x11_window *impl = self->impl;
+ return impl->window;
+}
+
+static void mgl_x11_window_close(mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(impl->window) {
+ XDestroyWindow(context->connection, impl->window);
+ XSync(context->connection, False);
+ impl->window = None;
+ }
+ self->open = false;
+}
+
+static void mgl_x11_window_set_cursor_visible(mgl_window *self, bool visible) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ XDefineCursor(context->connection, impl->window, visible ? impl->default_cursor : impl->invisible_cursor);
+}
+
+static void mgl_x11_window_set_vsync_enabled(mgl_window *self, bool enabled) {
+ mgl_x11_window *impl = self->impl;
+ self->vsync_enabled = enabled;
+ set_vertical_sync_enabled(impl, self->vsync_enabled ? 1 : 0);
+}
+
+static bool mgl_x11_window_is_vsync_enabled(const mgl_window *self) {
+ return self->vsync_enabled;
+}
+
+static void mgl_x11_window_set_fullscreen(mgl_window *self, bool fullscreen) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ XEvent xev;
+ xev.type = ClientMessage;
+ xev.xclient.window = impl->window;
+ xev.xclient.message_type = impl->net_wm_state_atom;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = fullscreen ? 1 : 0;
+ xev.xclient.data.l[1] = impl->net_wm_state_fullscreen_atom;
+ xev.xclient.data.l[2] = 0;
+ xev.xclient.data.l[3] = 1;
+ xev.xclient.data.l[4] = 0;
+
+ if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
+ fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
+ return;
+ }
+
+ XFlush(context->connection);
+}
+
+static bool mgl_x11_window_is_fullscreen(const mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ bool is_fullscreen = false;
+ Atom type = None;
+ int format = 0;
+ unsigned long items = 0;
+ unsigned long remaining_bytes = 0;
+ unsigned char *data = NULL;
+ if(XGetWindowProperty(context->connection, impl->window, impl->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
+ is_fullscreen = format == 32 && *(unsigned long*)data == impl->net_wm_state_fullscreen_atom;
+ XFree(data);
+ }
+ return is_fullscreen;
+}
+
+static void mgl_x11_window_set_position(mgl_window *self, mgl_vec2i position) {
+ mgl_x11_window *impl = self->impl;
+ XMoveWindow(mgl_get_context()->connection, impl->window, position.x, position.y);
+ XFlush(mgl_get_context()->connection);
+}
+
+static void mgl_x11_window_set_size(mgl_window *self, mgl_vec2i size) {
+ mgl_x11_window *impl = self->impl;
+ if(size.x < 0)
+ size.x = 0;
+ if(size.y < 0)
+ size.y = 0;
+ XResizeWindow(mgl_get_context()->connection, impl->window, size.x, size.y);
+ XFlush(mgl_get_context()->connection);
+}
+
+static void mgl_x11_window_set_clipboard(mgl_window *self, const char *str, size_t size) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ if(impl->clipboard_string) {
+ free(impl->clipboard_string);
+ impl->clipboard_string = NULL;
+ impl->clipboard_size = 0;
+ }
+
+ XSetSelectionOwner(context->connection, impl->clipboard_atom, impl->window, CurrentTime);
+ XFlush(context->connection);
+
+ /* Check if setting the selection owner was successful */
+ if(XGetSelectionOwner(context->connection, impl->clipboard_atom) != impl->window) {
+ fprintf(stderr, "mgl error: mgl_x11_window_set_clipboard failed\n");
+ return;
+ }
+
+ impl->clipboard_string = malloc(size + 1);
+ if(!impl->clipboard_string) {
+ fprintf(stderr, "mgl error: failed to allocate string for clipboard\n");
+ return;
+ }
+ memcpy(impl->clipboard_string, str, size);
+ impl->clipboard_string[size] = '\0';
+ impl->clipboard_size = 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(mgl_x11_window *mgl_x11_window, Atom type, int format) {
+ if((type == mgl_x11_window->utf8_string_atom || type == XA_STRING || type == mgl_x11_window->text_atom) && format == 8) {
+ return MGL_CLIPBOARD_TYPE_STRING;
+ } else if(type == mgl_x11_window->image_png_atom && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_PNG;
+ } else if((type == mgl_x11_window->image_jpg_atom || type == mgl_x11_window->image_jpeg_atom) && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_JPG;
+ } else if(type == mgl_x11_window->image_gif_atom && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_GIF;
+ } else {
+ return -1;
+ }
+}
+
+static bool mgl_x11_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) {
+ mgl_x11_window *impl = self->impl;
+ assert(callback);
+
+ mgl_context *context = mgl_get_context();
+ Window selection_owner = XGetSelectionOwner(context->connection, impl->clipboard_atom);
+
+ 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 == impl->window) {
+ if(!impl->clipboard_string)
+ return false;
+
+ return callback((const unsigned char*)impl->clipboard_string, impl->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata);
+ }
+
+ XEvent xev;
+ while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) {}
+
+ /* Sorted by preference */
+ /* TODO: Support more types (BITMAP?, PIXMAP?) */
+ Atom supported_clipboard_types[7];
+
+ int supported_clipboard_type_index = 0;
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_png_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_jpeg_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_gif_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->utf8_string_atom;
+ supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING;
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->text_atom;
+ }
+
+ const unsigned long num_supported_clipboard_types = supported_clipboard_type_index;
+
+ Atom requested_clipboard_type = None;
+ const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False);
+ XConvertSelection(context->connection, impl->clipboard_atom, XA_TARGETS, impl->clipboard_atom, impl->window, CurrentTime);
+
+ mgl_clock timeout_timer;
+ mgl_clock_init(&timeout_timer);
+ bool success = false;
+
+ const double timeout_seconds = 5.0;
+ while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
+ /* TODO: Wait for SelectionNotify event instead */
+ while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) {
+ if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
+ break;
+
+ if(!xev.xselection.property)
+ continue;
+
+ if(!xev.xselection.target || xev.xselection.selection != impl->clipboard_atom)
+ continue;
+
+ Atom type = None;
+ int format;
+ unsigned long items;
+ unsigned long remaining_bytes = 0;
+ unsigned char *data = NULL;
+
+ if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) {
+ /* TODO: Wait for PropertyNotify event instead */
+ if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
+ if(type != impl->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 */
+ XFree(data);
+ goto done;
+ } else {
+ XConvertSelection(context->connection, impl->clipboard_atom, requested_clipboard_type, impl->clipboard_atom, impl->window, CurrentTime);
+ }
+ }
+
+ XFree(data);
+ }
+ } else if(xev.xselection.target == requested_clipboard_type) {
+ bool got_data = false;
+ long chunk_size = 65536;
+
+ //XDeleteProperty(context->connection, self->window, mgl_x11_window->incr_atom);
+ //XFlush(context->connection);
+
+ while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
+ unsigned long offset = 0;
+
+ /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */
+ /* TODO: Wait for PropertyNotify event instead */
+ while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) {
+ if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
+ break;
+
+ if(type == impl->incr_atom) {
+ if(data)
+ chunk_size = *(long*)data;
+ XDeleteProperty(context->connection, impl->window, impl->incr_atom);
+ XFlush(context->connection);
+ XFree(data);
+ data = NULL;
+ break;
+ } else if(type == requested_clipboard_type) {
+ got_data = true;
+ }
+
+ if(!got_data && items == 0) {
+ XDeleteProperty(context->connection, impl->window, type);
+ XFlush(context->connection);
+ if(data) {
+ XFree(data);
+ data = NULL;
+ }
+ break;
+ }
+
+ const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */
+ if(got_data) {
+ const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(impl, type, format);
+ if(data && num_items_bytes > 0 && (int)clipboard_type != -1) {
+ if(!callback(data, num_items_bytes, clipboard_type, userdata)) {
+ XFree(data);
+ goto done;
+ }
+ }
+ }
+ offset += num_items_bytes;
+
+ if(data) {
+ XFree(data);
+ data = NULL;
+ }
+
+ if(got_data && num_items_bytes == 0/* && format == 8*/) {
+ success = true;
+ goto done;
+ }
+
+ if(remaining_bytes == 0)
+ break;
+ }
+ }
+
+ goto done;
+ }
+ }
+ }
+
+ done:
+ if(requested_clipboard_type)
+ XDeleteProperty(context->connection, impl->window, requested_clipboard_type);
+ return success;
+}
+
+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;
+}
+
+static bool mgl_x11_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;
+ const bool success = mgl_x11_window_get_clipboard(self, clipboard_copy_string_callback, &callback_data, MGL_CLIPBOARD_TYPE_STRING);
+ if(!success) {
+ free(*str);
+ *str = NULL;
+ *size = 0;
+ }
+ return success;
+}
+
+static void mgl_x11_window_set_key_repeat_enabled(mgl_window *self, bool enabled) {
+ self->key_repeat_enabled = enabled;
+}
+
+static void mgl_x11_window_flush(mgl_window *self) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ XFlush(context->connection);
+}
+
+static void* mgl_x11_window_get_egl_display(mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ if(impl->render_api == MGL_RENDER_API_EGL)
+ return impl->egl.egl_display;
+ else
+ return NULL;
+}
+
+static void* mgl_x11_window_get_egl_context(mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ if(impl->render_api == MGL_RENDER_API_EGL)
+ return impl->egl.egl_context;
+ else
+ return NULL;
+}
+
+bool mgl_x11_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
+ mgl_x11_window *impl = calloc(1, sizeof(mgl_x11_window));
+ if(!impl)
+ return false;
+
+ *self = (mgl_window) {
+ .get_system_handle = mgl_x11_window_get_system_handle,
+ .close = mgl_x11_window_close,
+ .inject_x11_event = mgl_x11_window_inject_x11_event,
+ .poll_event = mgl_x11_window_poll_event,
+ .swap_buffers = mgl_x11_window_swap_buffers,
+ .set_visible = mgl_x11_window_set_visible,
+ .is_key_pressed = mgl_x11_window_is_key_pressed,
+ .is_mouse_button_pressed = mgl_x11_window_is_mouse_button_pressed,
+ .set_title = mgl_x11_window_set_title,
+ .set_cursor_visible = mgl_x11_window_set_cursor_visible,
+ .set_vsync_enabled = mgl_x11_window_set_vsync_enabled,
+ .is_vsync_enabled = mgl_x11_window_is_vsync_enabled,
+ .set_fullscreen = mgl_x11_window_set_fullscreen,
+ .is_fullscreen = mgl_x11_window_is_fullscreen,
+ .set_position = mgl_x11_window_set_position,
+ .set_size = mgl_x11_window_set_size,
+ .set_size_limits = mgl_x11_window_set_size_limits,
+ .set_clipboard = mgl_x11_window_set_clipboard,
+ .get_clipboard = mgl_x11_window_get_clipboard,
+ .get_clipboard_string = mgl_x11_window_get_clipboard_string,
+ .set_key_repeat_enabled = mgl_x11_window_set_key_repeat_enabled,
+ .flush = mgl_x11_window_flush,
+ .get_egl_display = mgl_x11_window_get_egl_display,
+ .get_egl_context = mgl_x11_window_get_egl_context,
+ .for_each_active_monitor_output = mgl_x11_window_for_each_active_monitor_output,
+
+ .impl = impl,
+ };
+
+ impl->render_api = params ? params->render_api : MGL_RENDER_API_GLX;
+
+ /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */
+
+ mgl_context *context = mgl_get_context();
+ /* TODO: Create all of these with one XInternAtoms call instead */
+ impl->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False);
+ impl->targets_atom = XInternAtom(context->connection, "TARGETS", False);
+ impl->text_atom = XInternAtom(context->connection, "TEXT", False);
+ impl->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", False);
+ impl->image_png_atom = XInternAtom(context->connection, "image/png", False);
+ impl->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False);
+ impl->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False);
+ impl->image_gif_atom = XInternAtom(context->connection, "image/gif", False);
+ impl->incr_atom = XInternAtom(context->connection, "INCR", False);
+ impl->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False);
+ impl->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False);
+ impl->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False);
+ impl->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False);
+ impl->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False);
+ impl->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ impl->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ impl->net_wm_window_type_notification_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
+ impl->net_wm_window_type_utility = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ impl->motif_wm_hints_atom = XInternAtom(context->connection, "_MOTIF_WM_HINTS", False);
+
+ impl->support_alpha = params && params->support_alpha;
+
+ switch(impl->render_api) {
+ case MGL_RENDER_API_GLX: {
+ if(!mgl_x11_window_glx_init(&impl->glx, impl->support_alpha)) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+ break;
+ }
+ case MGL_RENDER_API_EGL: {
+ if(!mgl_x11_window_egl_init(&impl->egl, impl->support_alpha)) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+ break;
+ }
+ }
+
+ impl->default_cursor = XCreateFontCursor(context->connection, XC_arrow);
+ if(!impl->default_cursor) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+
+ const char data[1] = {0};
+ Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1);
+ if(!blank_bitmap) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+
+ XColor dummy;
+ impl->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0);
+ XFreePixmap(context->connection, blank_bitmap);
+ if(!impl->invisible_cursor) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+
+ x11_events_circular_buffer_init(&impl->events);
+
+ if(!mgl_x11_setup_window(self, title, params, existing_window)) {
+ mgl_x11_window_deinit(self);
+ return false;
+ }
+
+ context->current_window = self;
+ return true;
+}
+
+void mgl_x11_window_deinit(mgl_window *self) {
+ mgl_x11_window *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(!impl)
+ return;
+
+ mgl_x11_window_clear_monitors(self);
+
+ if(impl->color_map) {
+ XFreeColormap(context->connection, impl->color_map);
+ impl->color_map = None;
+ }
+
+ if(impl->invisible_cursor) {
+ XFreeCursor(context->connection, impl->invisible_cursor);
+ impl->invisible_cursor = None;
+ }
+
+ if(impl->default_cursor) {
+ XFreeCursor(context->connection, impl->default_cursor);
+ impl->default_cursor = None;
+ }
+
+ if(impl->xic) {
+ XDestroyIC(impl->xic);
+ impl->xic = NULL;
+ }
+
+ if(impl->xim) {
+ XCloseIM(impl->xim);
+ impl->xim = NULL;
+ }
+
+ switch(impl->render_api) {
+ case MGL_RENDER_API_GLX: {
+ mgl_x11_window_glx_deinit(&impl->glx);
+ break;
+ }
+ case MGL_RENDER_API_EGL: {
+ mgl_x11_window_egl_deinit(&impl->egl);
+ break;
+ }
+ }
+
+ mgl_x11_window_close(self);
+
+ if(impl->clipboard_string) {
+ free(impl->clipboard_string);
+ impl->clipboard_string = NULL;
+ }
+ impl->clipboard_size = 0;
+
+ self->open = false;
+
+ if(context->current_window == self)
+ context->current_window = NULL;
+
+ free(self->impl);
+ self->impl = NULL;
+}
diff --git a/tests/main.c b/tests/main.c
index 48fb2d8..44e3a27 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -321,6 +321,18 @@ int main(void) {
fprintf(stderr, "key release event, code: %u\n", event.key.code);
break;
}
+ case MGL_EVENT_MONITOR_PROPERTY_CHANGED: {
+ fprintf(stderr, "monitor property changed\n");
+ break;
+ }
+ case MGL_EVENT_MONITOR_CONNECTED: {
+ fprintf(stderr, "monitor connected\n");
+ break;
+ }
+ case MGL_EVENT_MONITOR_DISCONNECTED: {
+ fprintf(stderr, "monitor disconnected\n");
+ break;
+ }
}
}