aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2024-01-11 20:18:34 +0100
committerdec05eba <dec05eba@protonmail.com>2024-01-11 20:18:34 +0100
commit2b9389ae016c8048a16ba033f27ca5c256985e4d (patch)
tree56665698e299fe9a5754be2607719369059e5c53
parentf3d89d03acaad7ebd750d9a39e4ca8c589de4782 (diff)
Display windows with minimal delay, locked to 60 fps
-rw-r--r--.gitignore1
-rwxr-xr-xbuild.sh11
-rw-r--r--main.c5
-rwxr-xr-xngxcbin15232 -> 17120 bytes
-rw-r--r--project.conf4
-rw-r--r--src/compositor.c150
-rw-r--r--src/compositor.h31
-rw-r--r--src/main.c185
-rw-r--r--src/window_texture.c172
-rw-r--r--src/window_texture.h32
10 files changed, 583 insertions, 8 deletions
diff --git a/.gitignore b/.gitignore
index 0a2d532..b726535 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.o
+build/
# Compiled sibs files
sibs-build/
diff --git a/build.sh b/build.sh
index b05496a..4ae804d 100755
--- a/build.sh
+++ b/build.sh
@@ -6,7 +6,12 @@ cd "$script_dir"
CC=${CC:-gcc}
opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Wshadow"
-libs="$(pkg-config --libs gl)"
-includes="$(pkg-config --cflags gl)"
+dependencies="gl x11 xrandr xcomposite xfixes"
+libs="$(pkg-config --libs $dependencies)"
+includes="$(pkg-config --cflags $dependencies)"
-$CC main.c -o ngxc $opts $libs $includes
+mkdir -p build
+$CC -c src/compositor.c -o build/compositor.o $opts $includes
+$CC -c src/window_texture.c -o build/window_texture.o $opts $includes
+$CC -c src/main.c -o build/main.o $opts $includes
+$CC build/*.o -o ngxc $opts $libs
diff --git a/main.c b/main.c
deleted file mode 100644
index e6173ef..0000000
--- a/main.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include <stdio.h>
-
-int main(int argc, char **argv) {
- return 0;
-}
diff --git a/ngxc b/ngxc
index 6bae59b..201bf0c 100755
--- a/ngxc
+++ b/ngxc
Binary files differ
diff --git a/project.conf b/project.conf
index 2f4458d..a773cc6 100644
--- a/project.conf
+++ b/project.conf
@@ -6,3 +6,7 @@ platforms = ["posix"]
[dependencies]
gl = ">=1"
+x11 = ">=1"
+xrandr = ">=1"
+xcomposite = ">=0"
+xfixes = ">=1" \ No newline at end of file
diff --git a/src/compositor.c b/src/compositor.c
new file mode 100644
index 0000000..4b622c0
--- /dev/null
+++ b/src/compositor.c
@@ -0,0 +1,150 @@
+#include "compositor.h"
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+
+static double clock_get_monotonic_seconds(void) {
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001;
+}
+
+static ngxc_window* ngxc_compositor_get_window_by_id(ngxc_compositor *self, Window window) {
+ for(int i = 0; i < self->num_windows; ++i) {
+ ngxc_window *window_obj = &self->windows[i];
+ if(window_obj->texture.window == window)
+ return window_obj;
+ }
+ return NULL;
+}
+
+void ngxc_compositor_init(ngxc_compositor *self, Display *dpy, Window composite_window) {
+ memset(self, 0, sizeof(*self));
+ self->dpy = dpy;
+ self->composite_window = composite_window;
+ self->frame_timer = clock_get_monotonic_seconds();
+}
+
+void ngxc_compositor_deinit(ngxc_compositor *self) {
+
+}
+
+void ngxc_compositor_add_window(ngxc_compositor *self, Window window) {
+ if(self->num_windows == NGXC_COMPOSITOR_MAX_WINDOWS) {
+ fprintf(stderr, "error: reached max number of trackable windows (%d), ignoring window with id %ld\n", NGXC_COMPOSITOR_MAX_WINDOWS, window);
+ return;
+ }
+
+ XWindowAttributes xattr;
+ if(!XGetWindowAttributes(self->dpy, window, &xattr)) {
+ fprintf(stderr, "error: failed to get window attributes, ignoring window with id %ld\n", window);
+ return;
+ }
+
+ //Window c;
+ //XTranslateCoordinates(self->dpy, window, DefaultRootWindow(self->dpy), 0, 0, &xattr.x, &xattr.y, &c);
+
+ ngxc_window *window_obj = &self->windows[self->num_windows];
+ if(window_texture_init(&window_obj->texture, self->dpy, window) != 0) {
+ fprintf(stderr, "error: failed to create window texture, ignoring window with id %ld\n", window);
+ return;
+ }
+
+ window_obj->x = xattr.x;
+ window_obj->y = xattr.y;
+ ++self->num_windows;
+
+ fprintf(stderr, "ADDED WINDOW: %ld, x: %d, y: %d\n", window, xattr.x, xattr.y);
+}
+
+void ngxc_compositor_remove_window(ngxc_compositor *self, Window window) {
+ for(int i = 0; i < self->num_windows; ++i) {
+ if(self->windows[i].texture.window != window)
+ continue;
+
+ window_texture_deinit(&self->windows[i].texture);
+ for(int j = i + 1; j < self->num_windows; ++j) {
+ self->windows[j - 1] = self->windows[j];
+ }
+ break;
+ }
+}
+
+static void render_texture(const ngxc_window *window) {
+ float matrix[16] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+ };
+
+ matrix[0] = 1.0f / (float)window->texture.width;
+ matrix[5] = 1.0f / (float)window->texture.height;
+
+ glMatrixMode(GL_TEXTURE);
+ glLoadMatrixf(matrix);
+ glMatrixMode(GL_MODELVIEW);
+ glBindTexture(GL_TEXTURE_2D, window->texture.texture_id);
+
+ glColor4ub(255, 255, 255, 255);
+ //glTranslatef(window->x, window->y, 0.0f);
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, 0.0f);
+ glVertex2f(window->x, window->y);
+
+ glTexCoord2f(window->texture.width, 0.0f);
+ glVertex2f(window->x + window->texture.width, window->y);
+
+ glTexCoord2f(window->texture.width, window->texture.height);
+ glVertex2f(window->x + window->texture.width, window->y + window->texture.height);
+
+ glTexCoord2f(0.0f, window->texture.height);
+ glVertex2f(window->x, window->y + window->texture.height);
+ glEnd();
+ glLoadIdentity();
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+/* TODO: Skip windows that are not visible on the screen */
+/* TODO: Disable compositing for fullscreen windows, if that option is enabled */
+void ngxc_compositor_render(ngxc_compositor *self) {
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ for(int i = 0; i < self->num_windows; ++i) {
+ const ngxc_window *window_obj = &self->windows[i];
+ render_texture(window_obj);
+ }
+
+ const double now = clock_get_monotonic_seconds();
+ const double frame_duration = now - self->frame_timer;
+
+ glXSwapBuffers(self->dpy, self->composite_window);
+ glFlush();
+ glFinish();
+
+ const double time_limit = 1.0f / 60.0f; // TODO: Get from randr
+ const double delayed_update = time_limit - frame_duration - 0.002;
+ //fprintf(stderr, "delayed update: %f\n", delayed_update);
+ if(delayed_update > 0.0)
+ usleep(delayed_update * 1000000.0);
+
+ self->frame_timer = clock_get_monotonic_seconds();
+}
+
+void ngxc_compositor_on_configure(ngxc_compositor *self, const XConfigureEvent *configure_event) {
+ ngxc_window *window_obj = ngxc_compositor_get_window_by_id(self, configure_event->window);
+ if(!window_obj)
+ return;
+
+ window_obj->x = configure_event->x;
+ window_obj->y = configure_event->y;
+
+ if(configure_event->width != window_obj->texture.width || configure_event->height != window_obj->texture.height) {
+ window_texture_on_resize(&window_obj->texture);
+ }
+}
diff --git a/src/compositor.h b/src/compositor.h
new file mode 100644
index 0000000..3d8d876
--- /dev/null
+++ b/src/compositor.h
@@ -0,0 +1,31 @@
+#ifndef NGXC_COMPOSITOR_H
+#define NGXC_COMPOSITOR_H
+
+#include "window_texture.h"
+
+// TODO: Make dynamic
+#define NGXC_COMPOSITOR_MAX_WINDOWS 100
+
+typedef struct {
+ WindowTexture texture;
+ int x;
+ int y;
+} ngxc_window;
+
+typedef struct {
+ Display *dpy;
+ Window composite_window;
+ double frame_timer;
+ ngxc_window windows[NGXC_COMPOSITOR_MAX_WINDOWS];
+ int num_windows;
+} ngxc_compositor;
+
+void ngxc_compositor_init(ngxc_compositor *self, Display *dpy, Window composite_window);
+void ngxc_compositor_deinit(ngxc_compositor *self);
+
+void ngxc_compositor_add_window(ngxc_compositor *self, Window window);
+void ngxc_compositor_remove_window(ngxc_compositor *self, Window window);
+void ngxc_compositor_render(ngxc_compositor *self);
+void ngxc_compositor_on_configure(ngxc_compositor *self, const XConfigureEvent *configure_event);
+
+#endif /* NGXC_COMPOSITOR_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..d660e1d
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,185 @@
+#include "compositor.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/shapeconst.h>
+
+static void fatal_if(bool condition, const char *fmt, ...) {
+ if(!condition)
+ return;
+
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "fatal: ");
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ exit(1);
+}
+
+static int x_error_handler(Display *dpy, XErrorEvent *ee) {
+ (void)dpy;
+ (void)ee;
+ return 0;
+}
+
+static void glx_context_choose(Display *dpy, GLXFBConfig **configs, XVisualInfo **visual_info) {
+ const int visual_attribs[] = {
+ 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, 0,
+ GLX_DEPTH_SIZE, 0,
+ None,
+ };
+
+ int c;
+ *configs = glXChooseFBConfig(dpy, 0, visual_attribs, &c);
+ fatal_if(!configs, "failed to choose fb config");
+
+ for (int i = 0; i < c; i++) {
+ GLXFBConfig *config = configs[i];
+ XVisualInfo *visual = glXGetVisualFromFBConfig(dpy, *config);
+ if(!visual)
+ continue;
+
+ /*if(attr.depth != visual->depth) {
+ XFree(visual);
+ continue;
+ }*/
+
+ *visual_info = visual;
+ *configs = config;
+ return;
+ }
+
+ fatal_if(true, "failed to find a valid fb config");
+}
+
+static void allow_input_passthrough(Display *dpy, Window w) {
+ XserverRegion region = XFixesCreateRegion(dpy, NULL, 0);
+
+ XFixesSetWindowShapeRegion(dpy, w, ShapeBounding, 0, 0, 0);
+ XFixesSetWindowShapeRegion(dpy, w, ShapeInput, 0, 0, region);
+
+ XFixesDestroyRegion (dpy, region);
+}
+
+static bool is_compositor_running(Display *dpy) {
+ Atom prop_atom = XInternAtom(dpy, "_NET_WM_CM_S0", False);
+ return XGetSelectionOwner(dpy, prop_atom) != None;
+}
+
+static void set_compositor_owner(Display *dpy, Window window) {
+ Atom prop_atom = XInternAtom(dpy, "_NET_WM_CM_S0", False);
+ XSetSelectionOwner(dpy, prop_atom, window, CurrentTime);
+}
+
+static bool x11_supports_composite_named_window_pixmap(Display *display) {
+ int extension_major;
+ int extension_minor;
+ if(!XCompositeQueryExtension(display, &extension_major, &extension_minor))
+ return false;
+
+ int major_version;
+ int minor_version;
+ return XCompositeQueryVersion(display, &major_version, &minor_version) && (major_version > 0 || minor_version >= 2);
+}
+
+int main(int argc, char **argv) {
+ /* Make nvidia act reasonably */
+ setenv("__GL_MaxFramesAllowed", "1", true);
+ setenv("__GL_YIELD", "", true);
+
+ Display *dpy = XOpenDisplay(NULL);
+ fatal_if(!dpy, "failed to connect to the X server");
+ XSetErrorHandler(x_error_handler);
+
+ fatal_if(!x11_supports_composite_named_window_pixmap(dpy), "your X server server is too old (Xcomposite extension version is less than 0.2)");
+ fatal_if(is_compositor_running(dpy), "another compositor is already running");
+
+ const Window root_window = DefaultRootWindow(dpy);
+ XSelectInput(dpy, root_window, SubstructureNotifyMask);
+
+ GLXFBConfig *fb_config;
+ XVisualInfo *xvisual_info;
+ glx_context_choose(dpy, &fb_config, &xvisual_info);
+ GLXContext glx_context = glXCreateNewContext(dpy, *fb_config, GLX_RGBA_TYPE, NULL, True);
+ fatal_if(!glx_context, "failed to create glx context");
+
+ //Colormap colormap = XCreateColormap(dpy, root_window, xvisual_info->visual, AllocNone);
+ //fatal_if(!colormap, "failed to create colormap");
+
+ const Window overlay_window = XCompositeGetOverlayWindow(dpy, root_window);
+ fatal_if(!overlay_window, "failed to get overlay window");
+ allow_input_passthrough(dpy, overlay_window);
+
+ // TODO: Create one window for each monitor
+ const int composite_window_width = WidthOfScreen(DefaultScreenOfDisplay(dpy));
+ const int composite_window_height = HeightOfScreen(DefaultScreenOfDisplay(dpy));
+ const Window composite_window = XCreateWindow(dpy, overlay_window, 0, 0, composite_window_width, composite_window_height, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
+ fatal_if(!composite_window, "failed to create X window");
+ // TODO: Set window title to ngxc and other properties that are needed
+ set_compositor_owner(dpy, composite_window);
+
+ glXMakeContextCurrent(dpy, composite_window, composite_window, glx_context);
+
+ glXSwapIntervalEXT(dpy, composite_window, true);
+ glEnable(GL_TEXTURE_2D);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ allow_input_passthrough(dpy, composite_window);
+ XMapWindow(dpy, composite_window);
+ XFlush(dpy);
+
+ // TODO: Also do this on monitor reconfigure
+ glViewport(0, 0, composite_window_width, composite_window_height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0.0, composite_window_width, composite_window_height, 0.0, 0.0, 1.0);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ ngxc_compositor compositor;
+ ngxc_compositor_init(&compositor, dpy, composite_window);
+
+ Window root_return, parent_return;
+ Window *children = NULL;
+ unsigned int num_children = 0;
+ XQueryTree(dpy, root_window, &root_return, &parent_return, &children, &num_children);
+
+ for(unsigned int i = 0; i < num_children; ++i) {
+ ngxc_compositor_add_window(&compositor, children[i]);
+ }
+
+ if(children)
+ XFree(children);
+
+ XEvent xev;
+ for(;;) {
+ while(XPending(dpy)) {
+ XNextEvent(dpy, &xev);
+ switch(xev.type) {
+ case ConfigureNotify: {
+ /* TODO: Instead of doing this potentially multiple times at once, only do this for the last configure notify event for each window */
+ ngxc_compositor_on_configure(&compositor, &xev.xconfigure);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ ngxc_compositor_render(&compositor);
+ }
+
+ return 0;
+}
diff --git a/src/window_texture.c b/src/window_texture.c
new file mode 100644
index 0000000..c3bde72
--- /dev/null
+++ b/src/window_texture.c
@@ -0,0 +1,172 @@
+#include "window_texture.h"
+#include <X11/extensions/Xcomposite.h>
+#include <stdio.h>
+
+int window_texture_init(WindowTexture *window_texture, Display *display, Window window) {
+ window_texture->display = display;
+ window_texture->window = window;
+ window_texture->pixmap = None;
+ window_texture->glx_pixmap = None;
+ window_texture->texture_id = 0;
+ window_texture->redirected = 0;
+ window_texture->width = 0;
+ window_texture->height = 0;
+
+ XCompositeRedirectWindow(display, window, CompositeRedirectManual);
+ window_texture->redirected = 1;
+ return window_texture_on_resize(window_texture);
+}
+
+static void window_texture_cleanup(WindowTexture *self, int delete_texture) {
+ if(delete_texture && self->texture_id) {
+ glDeleteTextures(1, &self->texture_id);
+ self->texture_id = 0;
+ }
+
+ if(self->glx_pixmap) {
+ glXDestroyPixmap(self->display, self->glx_pixmap);
+ glXReleaseTexImageEXT(self->display, self->glx_pixmap, GLX_FRONT_EXT);
+ self->glx_pixmap = None;
+ }
+
+ if(self->pixmap) {
+ XFreePixmap(self->display, self->pixmap);
+ self->pixmap = None;
+ }
+}
+
+void window_texture_deinit(WindowTexture *self) {
+ if(self->redirected) {
+ XCompositeUnredirectWindow(self->display, self->window, CompositeRedirectManual);
+ self->redirected = 0;
+ }
+ window_texture_cleanup(self, 1);
+}
+
+int window_texture_on_resize(WindowTexture *self) {
+ window_texture_cleanup(self, 0);
+
+ int result = 0;
+ GLXFBConfig *configs = NULL;
+ Pixmap pixmap = None;
+ GLXPixmap glx_pixmap = None;
+ GLuint texture_id = 0;
+ int glx_pixmap_bound = 0;
+
+ const int pixmap_config[] = {
+ GLX_BIND_TO_TEXTURE_RGB_EXT, True,
+ GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT | GLX_WINDOW_BIT,
+ GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
+ /*GLX_BIND_TO_MIPMAP_TEXTURE_EXT, True,*/
+ GLX_BUFFER_SIZE, 24,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 0,
+ None
+ };
+
+ const int pixmap_attribs[] = {
+ GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
+ GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGB_EXT,
+ /*GLX_MIPMAP_TEXTURE_EXT, True,*/
+ None
+ };
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(self->display, self->window, &attr)) {
+ fprintf(stderr, "Failed to get window attributes\n");
+ return 1;
+ }
+
+ GLXFBConfig config;
+ int c;
+ configs = glXChooseFBConfig(self->display, 0, pixmap_config, &c);
+ if(!configs) {
+ fprintf(stderr, "Failed to choose fb config\n");
+ return 1;
+ }
+
+ int found = 0;
+ for (int i = 0; i < c; i++) {
+ config = configs[i];
+ XVisualInfo *visual = glXGetVisualFromFBConfig(self->display, config);
+ if (!visual)
+ continue;
+
+ if (attr.depth != visual->depth) {
+ XFree(visual);
+ continue;
+ }
+ XFree(visual);
+ found = 1;
+ break;
+ }
+
+ if(!found) {
+ fprintf(stderr, "No matching fb config found\n");
+ result = 1;
+ goto cleanup;
+ }
+
+ pixmap = XCompositeNameWindowPixmap(self->display, self->window);
+ if(!pixmap) {
+ result = 2;
+ goto cleanup;
+ }
+
+ glx_pixmap = glXCreatePixmap(self->display, config, pixmap, pixmap_attribs);
+ if(!glx_pixmap) {
+ result = 3;
+ goto cleanup;
+ }
+
+ if(self->texture_id == 0) {
+ glGenTextures(1, &texture_id);
+ if(texture_id == 0) {
+ result = 4;
+ goto cleanup;
+ }
+ glBindTexture(GL_TEXTURE_2D, texture_id);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, self->texture_id);
+ }
+
+ glXBindTexImageEXT(self->display, glx_pixmap, GLX_FRONT_EXT, NULL);
+ glx_pixmap_bound = 1;
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+
+ /*
+ float fLargest = 0.0f;
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
+ */
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ self->width = attr.width;
+ self->height = attr.height;
+
+ XFree(configs);
+ self->pixmap = pixmap;
+ self->glx_pixmap = glx_pixmap;
+ if(texture_id != 0)
+ self->texture_id = texture_id;
+ return 0;
+
+ cleanup:
+ if(texture_id != 0) glDeleteTextures(1, &texture_id);
+ if(glx_pixmap) glXDestroyPixmap(self->display, glx_pixmap);
+ if(glx_pixmap_bound) glXReleaseTexImageEXT(self->display, glx_pixmap, GLX_FRONT_EXT);
+ if(pixmap) XFreePixmap(self->display, pixmap);
+ if(configs) XFree(configs);
+ return result;
+}
+
+GLuint window_texture_get_opengl_texture_id(WindowTexture *self) {
+ return self->texture_id;
+}
diff --git a/src/window_texture.h b/src/window_texture.h
new file mode 100644
index 0000000..39826bc
--- /dev/null
+++ b/src/window_texture.h
@@ -0,0 +1,32 @@
+#ifndef NGXC_WINDOW_TEXTURE_H
+#define NGXC_WINDOW_TEXTURE_H
+
+#define GLX_GLXEXT_PROTOTYPES
+#include <GL/glx.h>
+#include <GL/glxext.h>
+#include <X11/Xlib.h>
+
+typedef struct {
+ Display *display;
+ Window window;
+ Pixmap pixmap;
+ GLXPixmap glx_pixmap;
+ GLuint texture_id;
+ int redirected;
+ int width;
+ int height;
+} WindowTexture;
+
+/* Returns 0 on success */
+int window_texture_init(WindowTexture *window_texture, Display *display, Window window);
+void window_texture_deinit(WindowTexture *self);
+
+/*
+ This should ONLY be called when the target window is resized.
+ Returns 0 on success.
+*/
+int window_texture_on_resize(WindowTexture *self);
+
+GLuint window_texture_get_opengl_texture_id(WindowTexture *self);
+
+#endif /* NGXC_WINDOW_TEXTURE_H */