diff options
-rw-r--r-- | include/mgl/window/window.h | 123 | ||||
-rw-r--r-- | include/mgl/window/x11/window.h | 9 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/window/window.c | 1964 | ||||
-rw-r--r-- | src/window/x11/window.c | 2066 | ||||
-rw-r--r-- | tests/main.c | 12 |
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; + } } } |