aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2023-08-25 03:06:42 +0200
committerdec05eba <dec05eba@protonmail.com>2023-08-25 10:43:40 +0200
commit30d9f5392fb07105e792963d74786024adc79dd5 (patch)
tree47d21dff4323b03ab738366908f2171ba01a00c4
parentef4a993d20ceb791ac62dd219ee7d63524e04a3e (diff)
Add monitor events, limit fps to monitor the window is in, reduce latency (glFinish, fps limit)
-rw-r--r--README.md4
-rw-r--r--include/mgl/gl.h2
-rw-r--r--include/mgl/mgl.h6
-rw-r--r--include/mgl/window/event.h25
-rw-r--r--include/mgl/window/window.h15
-rw-r--r--project.conf3
-rw-r--r--src/gl.c2
-rw-r--r--src/mgl.c56
-rw-r--r--src/window/window.c376
-rw-r--r--tests/main.c4
10 files changed, 471 insertions, 22 deletions
diff --git a/README.md b/README.md
index 8bceb2c..b2a915a 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# Minimal Graphics Library
-Written in C and uses OpenGL 2.0 to support as many platforms as possible.\
+Written in C and uses OpenGL 2.1 to support as many platforms as possible.\
Right now mgl only supports x11.
# Dependencies
## Build
-`x11, xrender`
+`x11, xrender, xrandr`
## Runtime
`libglvnd (libGL.so)`
# Notes
diff --git a/include/mgl/gl.h b/include/mgl/gl.h
index cf2cd47..c1059b6 100644
--- a/include/mgl/gl.h
+++ b/include/mgl/gl.h
@@ -77,6 +77,8 @@ typedef struct {
const unsigned char* (*glGetString)(unsigned int name);
void (*glGetIntegerv)(unsigned int pname, int *params);
void (*glPixelStorei)(unsigned int pname, int param);
+ void (*glFlush)(void);
+ void (*glFinish)(void);
/* Optional*/
void (*glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval);
diff --git a/include/mgl/mgl.h b/include/mgl/mgl.h
index f2373b7..4e1a181 100644
--- a/include/mgl/mgl.h
+++ b/include/mgl/mgl.h
@@ -13,6 +13,12 @@ struct mgl_context {
unsigned long wm_delete_window_atom;
unsigned long net_wm_ping_atom;
unsigned long net_wm_pid_atom;
+
+ int render_event_base;
+ int render_error_base;
+
+ int randr_event_base;
+ int randr_error_base;
};
/*
diff --git a/include/mgl/window/event.h b/include/mgl/window/event.h
index 31af4f1..4813154 100644
--- a/include/mgl/window/event.h
+++ b/include/mgl/window/event.h
@@ -42,6 +42,23 @@ typedef struct {
int y; /* relative to the top of the window */
} mgl_mouse_move_event;
+typedef struct {
+ const char *name; /* This name may only be valid until the next time |mgl_window_poll_event| is called */
+ int id;
+ int x;
+ int y;
+ int width;
+ int height;
+ int refresh_rate; /* 0 if unknown */
+} mgl_monitor_generic_event;
+
+typedef mgl_monitor_generic_event mgl_monitor_connected_event;
+typedef mgl_monitor_generic_event mgl_monitor_property_changed_event;
+
+typedef struct {
+ int id;
+} mgl_monitor_disconnected_event;
+
typedef enum {
MGL_EVENT_UNKNOWN,
MGL_EVENT_CLOSED, /* Window closed */
@@ -54,7 +71,10 @@ typedef enum {
MGL_EVENT_MOUSE_BUTTON_PRESSED,
MGL_EVENT_MOUSE_BUTTON_RELEASED,
MGL_EVENT_MOUSE_WHEEL_SCROLLED,
- MGL_EVENT_MOUSE_MOVED
+ MGL_EVENT_MOUSE_MOVED,
+ MGL_EVENT_MONITOR_CONNECTED,
+ MGL_EVENT_MONITOR_DISCONNECTED,
+ MGL_EVENT_MONITOR_PROPERTY_CHANGED,
} mgl_event_type;
struct mgl_event {
@@ -66,6 +86,9 @@ struct mgl_event {
mgl_mouse_button_event mouse_button;
mgl_mouse_wheel_scroll_event mouse_wheel_scroll;
mgl_mouse_move_event mouse_move;
+ mgl_monitor_connected_event monitor_connected;
+ mgl_monitor_disconnected_event monitor_disconnected;
+ mgl_monitor_property_changed_event monitor_property_changed;
};
};
diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h
index cb5a2eb..35b677d 100644
--- a/include/mgl/window/window.h
+++ b/include/mgl/window/window.h
@@ -27,9 +27,19 @@ typedef struct {
mgl_vec2i size;
} mgl_scissor;
+typedef struct {
+ int id; /* output id */
+ int crtc_id;
+ const char *name;
+ mgl_vec2i pos;
+ mgl_vec2i size;
+ 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;
@@ -40,11 +50,16 @@ struct mgl_window {
bool key_repeat_enabled;
bool vsync_enabled;
double frame_time_limit;
+ double frame_time_limit_monitor;
mgl_clock frame_timer;
char *clipboard_string;
size_t clipboard_size;
+
+ mgl_monitor *monitors; /* TODO: Move these to mgl file */
+ int num_monitors;
};
+/* TODO: Some of these parameters only apply to new window */
typedef struct {
mgl_vec2i position;
mgl_vec2i size;
diff --git a/project.conf b/project.conf
index b22578c..d218df6 100644
--- a/project.conf
+++ b/project.conf
@@ -9,4 +9,5 @@ expose_include_dirs = ["include"]
[dependencies]
x11 = ">=0.5"
-xrender = ">=0.5" \ No newline at end of file
+xrender = ">=0.5"
+xrandr = ">=0.5" \ No newline at end of file
diff --git a/src/gl.c b/src/gl.c
index 4a7dd12..5bcc68e 100644
--- a/src/gl.c
+++ b/src/gl.c
@@ -90,6 +90,8 @@ int mgl_gl_load(mgl_gl *self) {
{ &self->glGetString, "glGetString" },
{ &self->glGetIntegerv, "glGetIntegerv" },
{ &self->glPixelStorei, "glPixelStorei" },
+ { &self->glFlush, "glFlush" },
+ { &self->glFinish, "glFinish" },
{ NULL, NULL }
};
diff --git a/src/mgl.c b/src/mgl.c
index 5b87134..2a5325e 100644
--- a/src/mgl.c
+++ b/src/mgl.c
@@ -1,6 +1,10 @@
#include "../include/mgl/mgl.h"
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xrandr.h>
+#include <stdbool.h>
+#include <string.h>
#include <stdio.h>
#ifndef NDEBUG
#include <stdlib.h>
@@ -22,9 +26,39 @@ static int ignore_xioerror(Display *display) {
return 0;
}
+static bool xrender_is_supported(Display *display, int *event_base, int *error_base) {
+ *event_base = 0;
+ *error_base = 0;
+ if(!XRenderQueryExtension(display, event_base, error_base))
+ return false;
+
+ int major_version = 0;
+ int minor_version = 0;
+ if(!XRenderQueryVersion(display, &major_version, &minor_version))
+ return false;
+
+ return major_version > 0 || (major_version == 0 && minor_version >= 7);
+}
+
+static bool xrandr_is_supported(Display *display, int *event_base, int *error_base) {
+ *event_base = 0;
+ *error_base = 0;
+ if(!XRRQueryExtension(display, event_base, error_base))
+ return false;
+
+ int major_version = 0;
+ int minor_version = 0;
+ if(!XRRQueryVersion(display, &major_version, &minor_version))
+ return false;
+
+ return major_version > 1 || (major_version == 1 && minor_version >= 2);
+}
+
int mgl_init(void) {
++init_count;
if(init_count == 1) {
+ memset(&context, 0, sizeof(context));
+
context.connection = XOpenDisplay(NULL);
if(!context.connection) {
fprintf(stderr, "mgl error: XOpenDisplay failed\n");
@@ -34,6 +68,22 @@ int mgl_init(void) {
prev_xerror = XSetErrorHandler(ignore_xerror);
prev_xioerror = XSetIOErrorHandler(ignore_xioerror);
+
+ if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) {
+ fprintf(stderr, "mgl error: x11 render extension is not supported by your X server\n");
+ mgl_deinit();
+ return -1;
+ }
+
+ if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) {
+ fprintf(stderr, "mgl error: x11 randr extension is not supported by your X server\n");
+ mgl_deinit();
+ return -1;
+ }
+
+ fprintf(stderr, "randr event base: %d\n", context.randr_event_base);
+ XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);
+
XInitThreads();
XkbSetDetectableAutoRepeat(context.connection, True, NULL);
@@ -51,13 +101,17 @@ int mgl_init(void) {
void mgl_deinit(void) {
if(init_count == 1) {
- if(context.connection) {
+ if(prev_xioerror) {
XSetIOErrorHandler(prev_xioerror);
prev_xioerror = NULL;
+ }
+ if(prev_xerror) {
XSetErrorHandler(prev_xerror);
prev_xerror = NULL;
+ }
+ if(context.connection) {
XCloseDisplay(context.connection);
context.connection = NULL;
diff --git a/src/window/window.c b/src/window/window.c
index 980e811..8035239 100644
--- a/src/window/window.c
+++ b/src/window/window.c
@@ -6,6 +6,7 @@
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xrandr.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@@ -14,6 +15,7 @@
#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
@@ -51,6 +53,8 @@ static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl
return true;
}
+#define MAX_MONITORS 12
+
typedef struct {
GLXContext glx_context;
XIM xim;
@@ -74,6 +78,11 @@ typedef struct {
GLXFBConfig *fbconfigs;
GLXFBConfig fbconfig;
XVisualInfo *visual_info;
+
+ /* 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
@@ -163,6 +172,9 @@ static int x11_context_init(x11_context *self, bool alpha) {
self->fbconfig = NULL;
self->visual_info = NULL;
+ memset(self->monitors, 0, sizeof(self->monitors));
+ self->num_monitors = 0;
+
if(!glx_context_choose(context, &self->fbconfigs, &self->fbconfig, &self->visual_info, alpha)) {
x11_context_deinit(self);
return -1;
@@ -176,8 +188,10 @@ static int x11_context_init(x11_context *self, bool alpha) {
const char data[1] = {0};
Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1);
- if(!blank_bitmap)
+ 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);
@@ -195,6 +209,15 @@ static int x11_context_init(x11_context *self, bool alpha) {
void x11_context_deinit(x11_context *self) {
mgl_context *context = mgl_get_context();
+ 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;
+
if(self->color_map) {
XFreeColormap(context->connection, self->color_map);
self->color_map = None;
@@ -246,6 +269,140 @@ 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;
+}
+
+typedef void (*active_monitor_callback)(const mgl_monitor *monitor, void *userdata);
+static void for_each_active_monitor_output(Display *display, 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 && out_info->nameLen < (int)sizeof(display_name)) {
+ memcpy(display_name, out_info->name, out_info->nameLen);
+ display_name[out_info->nameLen] = '\0';
+
+ 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) */
@@ -271,6 +428,34 @@ static void set_vertical_sync_enabled(Window window, int enabled) {
fprintf(stderr, "mgl warning: setting vertical sync failed\n");
}
+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;
@@ -295,9 +480,13 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
self->focused = false;
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 };
mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
if(window_size.x <= 0 || window_size.y <= 0) {
@@ -306,6 +495,8 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
}
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");
@@ -319,6 +510,9 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
return -1;
}
+ self->monitors = x11_context->monitors;
+ self->num_monitors = 0;
+
mgl_context *context = mgl_get_context();
Window parent_window = params ? params->parent_window : None;
@@ -360,11 +554,15 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
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(hide_window)
XUnmapWindow(context->connection, existing_window);
} else {
- self->window = XCreateWindow(context->connection, parent_window, params->position.x, params->position.y,
+ self->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y,
window_size.x, window_size.y, 0,
x11_context->visual_info->depth, InputOutput, x11_context->visual_info->visual,
CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr);
@@ -421,8 +619,6 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
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);
- mgl_window_on_resize(self, self->size.x, self->size.y);
-
x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL);
if(!x11_context->xim) {
fprintf(stderr, "mgl error: XOpenIM failed\n");
@@ -437,6 +633,12 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window
return -1;
}
+ 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;
@@ -590,7 +792,7 @@ static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
return MGL_BUTTON_UNKNOWN;
}
-static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context, bool pressed) {
+static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context) {
event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0));
event->key.alt = ((xkey->state & Mod1Mask) != 0);
event->key.control = ((xkey->state & ControlMask) != 0);
@@ -638,11 +840,146 @@ static void mgl_window_handle_text_event(mgl_window *self, XEvent *xev) {
}
}
+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) {
+ 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: {
- x11_context *x11_context = self->context;
-
if(!self->key_repeat_enabled && xev->xkey.keycode == x11_context->prev_keycode_pressed && !x11_context->key_was_released) {
event->type = MGL_EVENT_UNKNOWN;
return;
@@ -652,17 +989,16 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
x11_context->key_was_released = false;
event->type = MGL_EVENT_KEY_PRESSED;
- mgl_window_handle_key_event(self, &xev->xkey, event, context, true);
+ mgl_window_handle_key_event(self, &xev->xkey, event, context);
mgl_window_handle_text_event(self, xev);
return;
}
case KeyRelease: {
- x11_context *x11_context = self->context;
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, false);
+ mgl_window_handle_key_event(self, &xev->xkey, event, context);
return;
}
case ButtonPress: {
@@ -694,7 +1030,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
return;
}
case FocusIn: {
- x11_context *x11_context = self->context;
XSetICFocus(x11_context->xic);
XWMHints* hints = XGetWMHints(context->connection, self->window);
@@ -709,7 +1044,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
return;
}
case FocusOut: {
- x11_context *x11_context = self->context;
XUnsetICFocus(x11_context->xic);
event->type = MGL_EVENT_LOST_FOCUS;
self->focused = false;
@@ -717,6 +1051,10 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
}
case ConfigureNotify: {
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);
@@ -725,6 +1063,7 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
event->size.height = self->size.y;
return;
}
+
event->type = MGL_EVENT_UNKNOWN;
return;
}
@@ -743,8 +1082,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event
return;
}
case SelectionRequest: {
- x11_context *x11_context = self->context;
-
XSelectionEvent selection_event;
selection_event.type = SelectionNotify;
selection_event.requestor = xev->xselectionrequest.requestor;
@@ -840,7 +1177,7 @@ bool mgl_window_poll_event(mgl_window *self, mgl_event *event) {
if(XPending(display)) {
XEvent xev; /* TODO: Move to window struct */
XNextEvent(display, &xev);
- if(xev.xany.window == self->window || xev.type == ClientMessage)
+ if(xev.xany.window == self->window || xev.type == ClientMessage || xev.type - context->randr_event_base >= 0)
mgl_window_on_receive_event(self, &xev, event, context);
else
event->type = MGL_EVENT_UNKNOWN;
@@ -853,12 +1190,19 @@ bool mgl_window_poll_event(mgl_window *self, mgl_event *event) {
void mgl_window_display(mgl_window *self) {
mgl_context *context = mgl_get_context();
context->gl.glXSwapBuffers(context->connection, self->window);
+ context->gl.glFlush();
+ context->gl.glFinish();
if(self->frame_time_limit > 0.000001) {
double time_left_to_sleep = self->frame_time_limit - mgl_clock_get_elapsed_time_seconds(&self->frame_timer);
if(time_left_to_sleep > 0.000001)
usleep(time_left_to_sleep * 1000000.0);
mgl_clock_restart(&self->frame_timer);
+ } else if(self->frame_time_limit_monitor >= 0.000001) {
+ double time_left_to_sleep = self->frame_time_limit_monitor - mgl_clock_get_elapsed_time_seconds(&self->frame_timer);
+ if(time_left_to_sleep > 0.000001)
+ usleep(time_left_to_sleep * 1000000.0);
+ mgl_clock_restart(&self->frame_timer);
}
}
@@ -972,7 +1316,7 @@ void mgl_window_set_framerate_limit(mgl_window *self, int fps) {
if(fps <= 0)
self->frame_time_limit = 0.0;
else
- self->frame_time_limit = 1.0 / fps;
+ self->frame_time_limit = 1.0 / (double)fps;
}
void mgl_window_set_vsync_enabled(mgl_window *self, bool enabled) {
diff --git a/tests/main.c b/tests/main.c
index 2155181..720cf06 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -300,7 +300,9 @@ int main(int argc, char **argv) {
} else if(event.key.code == MGL_KEY_F) {
mgl_window_set_fullscreen(&window, !mgl_window_is_fullscreen(&window));
} else if(event.key.code == MGL_KEY_X) {
- mgl_window_set_vsync_enabled(&window, !mgl_window_is_vsync_enabled(&window));
+ bool enable_vsync = !mgl_window_is_vsync_enabled(&window);
+ mgl_window_set_vsync_enabled(&window, enable_vsync);
+ fprintf(stderr, "vsync %s\n", enable_vsync ? "enabled" : "disabled");
}
fprintf(stderr, "key press event, code: %u\n", event.key.code);
break;