diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | include/mgl/gl.h | 2 | ||||
-rw-r--r-- | include/mgl/mgl.h | 6 | ||||
-rw-r--r-- | include/mgl/window/event.h | 25 | ||||
-rw-r--r-- | include/mgl/window/window.h | 15 | ||||
-rw-r--r-- | project.conf | 3 | ||||
-rw-r--r-- | src/gl.c | 2 | ||||
-rw-r--r-- | src/mgl.c | 56 | ||||
-rw-r--r-- | src/window/window.c | 376 | ||||
-rw-r--r-- | tests/main.c | 4 |
10 files changed, 471 insertions, 22 deletions
@@ -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 @@ -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 } }; @@ -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; |