├── .gitignore ├── pics └── screenshot1.png ├── LICENSE ├── src ├── opengl.h ├── cwm.h ├── wm.h └── main.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | x-compositing-wm -------------------------------------------------------------------------------- /pics/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obiwac/x-compositing-wm/HEAD/pics/screenshot1.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Obiwac 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/opengl.h: -------------------------------------------------------------------------------- 1 | // this file contains OpenGL helpers for the window manager 2 | 3 | // shaders 4 | 5 | static void gl_compile_shader_and_check_for_errors /* lmao */ (GLuint shader, const char* source) { 6 | glShaderSource(shader, 1, &source, 0); 7 | glCompileShader(shader); 8 | 9 | GLint log_length; 10 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); 11 | 12 | char* log_buffer = (char*) malloc(log_length); // 'log_length' includes null character 13 | glGetShaderInfoLog(shader, log_length, NULL, log_buffer); 14 | 15 | if (log_length) { 16 | fprintf(stderr, "[SHADER_ERROR] %s\n", log_buffer); 17 | exit(1); // no real need to free 'log_buffer' here 18 | } 19 | 20 | free(log_buffer); 21 | } 22 | 23 | GLuint gl_create_shader_program(const char* vertex_source, const char* fragment_source) { 24 | GLuint program = glCreateProgram(); 25 | 26 | GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); 27 | GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); 28 | 29 | gl_compile_shader_and_check_for_errors(vertex_shader, vertex_source); 30 | gl_compile_shader_and_check_for_errors(fragment_shader, fragment_source); 31 | 32 | glAttachShader(program, vertex_shader); 33 | glAttachShader(program, fragment_shader); 34 | 35 | glLinkProgram(program); 36 | 37 | glDeleteShader(vertex_shader); 38 | glDeleteShader(fragment_shader); 39 | 40 | return program; 41 | } 42 | 43 | // VAO / VBO / IBO 44 | 45 | void gl_create_vao_vbo_ibo(GLuint* vao, GLuint* vbo, GLuint* ibo) { 46 | glGenVertexArrays(1, vao); 47 | glBindVertexArray(*vao); 48 | 49 | glGenBuffers(1, vbo); 50 | glBindBuffer(GL_ARRAY_BUFFER, *vbo); 51 | 52 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); 53 | glEnableVertexAttribArray(0); 54 | 55 | glGenBuffers(1, ibo); 56 | } 57 | 58 | void gl_set_vao_vbo_ibo_data(GLuint vao, GLuint vbo, GLsizeiptr vbo_size, const void* vbo_data, GLuint ibo, GLsizeiptr ibo_size, const void* ibo_data) { 59 | glBindVertexArray(vao); 60 | 61 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 62 | glBufferData(GL_ARRAY_BUFFER, vbo_size, vbo_data, GL_STATIC_DRAW); 63 | 64 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); 65 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo_size, ibo_data, GL_STATIC_DRAW); 66 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # x-compositing-wm 2 | 3 | Extremely basic X11 compositing window manager written in C with Xlib and OpenGL. 4 | 5 | ![alt text](pics/screenshot1.png "Screenshot 1") 6 | 7 | ## Disclaimer 8 | 9 | This is very much *not* intended for use, but merely as a tool to (hopefully) help understand how to write an X11 compositing window manager better than what the quite frankly pisspoor documentation provides. 10 | As such, many best practices are not employed, and a lot of features you'd want to implement aren't implemented (list near the end of this document). 11 | It's also worth noting that I'm in no way shape or form an expert on X, this was just a weekend project that works by miracle. 12 | 13 | This was also originally meant as a prototype to AQUA's `wm.*` devices, so some coding decisions and comments may seem a bit strange. 14 | 15 | I may or may not end up updating this with new features, but I welcome any pull requests you might have! 16 | 17 | ## Compiling and installing 18 | 19 | On Linux or *BSD or whatever, compile with: 20 | 21 | ```sh 22 | $ cc src/main.c -Isrc -I/usr/local/include -L/usr/local/lib -lX11 -lGL -lGLEW -lXcomposite -lXfixes -lXinerama -lm -o x-compositing-wm 23 | ``` 24 | 25 | This creates an `x-compositing-wm` executable which you can put anywhere really (like `/usr/local/bin/` or `~/.local/bin/` or whatever). 26 | You can then set this to run automatically when you run `startx` by adding something like this to the end of your `~/.xinitrc`: 27 | 28 | ```sh 29 | exec x-compositing-wm 30 | ``` 31 | 32 | ## Features 33 | 34 | - Basic window interaction. 35 | - Basic graphical effects (shadows, rounded corners). 36 | - Basic. 37 | - Basic animations (smoothing when moving/resizing windows, animations when creating windows, &c). 38 | - Basic EWMH compliance (so it can work with programs like OBS). 39 | 40 | ## Default keybindings 41 | 42 | - Super+Left click and drag: Move window. 43 | - Super+Right click and drag: Resize window. 44 | - Super+F1: Quit WM. 45 | - Super+F: Make window fullscreen. 46 | - Super+Alt+F: Make window fullfullscreen. 47 | - Super+V: Enable or disable vsync (GIMP doesn't work with vsync for reasons I haven't had time to investigate). 48 | - Super+R: Restart WM. 49 | - Super+T: Open xterm instance. 50 | 51 | ## List of things you'll want to add in your own compositing WM 52 | 53 | - More error handling. 54 | - A fallback for when modern OpenGL (3.3) is not available. 55 | - Probably use `XDamage` to not needlessly update regions of the screen that don't need updating, although TBH I'm not sure if these days it's really a big deal. 56 | - Freeing allocated memory correctly. 57 | - Capturing focus events so clients can ask for focus (necessary for dropdowns to work properly, which are their own separate windows most of the time). 58 | - Apparently it's better performance-wise to use XCB instead of Xlib these days. You may wanna look into that. 59 | - Make resizing windows not dumb. 60 | -------------------------------------------------------------------------------- /src/cwm.h: -------------------------------------------------------------------------------- 1 | // this file contains helpers for the compositing part of the window manager prototype 2 | 3 | #include 4 | 5 | #include 6 | 7 | // we need to use Xfixes because, for whatever reason, X (and even Xcomposite, which is even weirder) doesn't include a way to make windows transparent to events 8 | 9 | #include 10 | #include 11 | 12 | // we use GLEW to help us load most of the OpenGL functions we're using 13 | // it is important that it goes before the 'glx.h' include 14 | 15 | #include 16 | #include 17 | 18 | // standard library includes 19 | 20 | #include 21 | #include 22 | 23 | // defines and stuff for GLX 24 | 25 | #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 26 | #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 27 | 28 | typedef GLXContext (*glXCreateContextAttribsARB_t) (Display*, GLXFBConfig, GLXContext, Bool, const int*); 29 | 30 | typedef void (*glXBindTexImageEXT_t) (Display*, GLXDrawable, int, const int*); 31 | typedef void (*glXReleaseTexImageEXT_t) (Display*, GLXDrawable, int); 32 | 33 | typedef void (*glXSwapIntervalEXT_t) (Display*, GLXDrawable, int); 34 | 35 | // structures and types 36 | 37 | typedef struct { 38 | wm_t* wm; 39 | 40 | int vsync; 41 | struct timeval previous_time; 42 | 43 | Window overlay_window; 44 | Window output_window; 45 | 46 | GLXContext glx_context; 47 | 48 | int glx_config_count; 49 | GLXFBConfig* glx_configs; 50 | 51 | glXBindTexImageEXT_t glXBindTexImageEXT; 52 | glXReleaseTexImageEXT_t glXReleaseTexImageEXT; 53 | } cwm_t; 54 | 55 | typedef struct { 56 | Pixmap x_pixmap; 57 | GLXPixmap pixmap; 58 | } cwm_window_internal_t; 59 | 60 | // functions 61 | 62 | void new_cwm(cwm_t* cwm, wm_t* wm) { 63 | memset(cwm, 0, sizeof(*cwm)); 64 | cwm->wm = wm; 65 | 66 | // make it so that our compositing window manager can be recognized as such by other processes 67 | 68 | Window screen_owner = XCreateSimpleWindow(wm->display, wm->root_window, 0, 0, 1, 1, 0, 0, 0); 69 | Xutf8SetWMProperties(wm->display, screen_owner, "xcompmgr", "xcompmgr", NULL, 0, NULL, NULL, NULL); 70 | 71 | char name[] = "_NET_WM_CM_S##"; 72 | snprintf(name, sizeof(name), "_NET_WM_CM_S%d", cwm->wm->screen); 73 | 74 | Atom atom = XInternAtom(wm->display, name, 0); 75 | XSetSelectionOwner(wm->display, atom, screen_owner, 0); 76 | 77 | // we want to enable manual redirection, because we want to track damage and flush updates ourselves 78 | // if we were to pass 'CompositeRedirectAutomatic' instead, the server would handle all that internally 79 | 80 | XCompositeRedirectSubwindows(wm->display, wm->root_window, CompositeRedirectManual); 81 | 82 | // get the overlay window 83 | // this window allows us to draw what we want on a layer between normal windows and the screensaver without interference 84 | 85 | cwm->overlay_window = XCompositeGetOverlayWindow(wm->display, wm->root_window); 86 | 87 | // explained in more detail in the comment before '#include ' 88 | // basically, make the overlay transparent to events and pass them on through to lower windows 89 | 90 | XserverRegion region = XFixesCreateRegion(wm->display, NULL, 0); 91 | XFixesSetWindowShapeRegion(wm->display, cwm->overlay_window, ShapeInput, 0, 0, region); 92 | XFixesDestroyRegion(wm->display, region); 93 | 94 | // create the output window 95 | // this window is where the actual drawing is going to happen 96 | 97 | /* const */ int default_visual_attributes[] = { 98 | GLX_RGBA, GLX_DOUBLEBUFFER, 99 | GLX_SAMPLE_BUFFERS, 1, 100 | GLX_SAMPLES, 4, 101 | GLX_RED_SIZE, 8, 102 | GLX_GREEN_SIZE, 8, 103 | GLX_BLUE_SIZE, 8, 104 | GLX_ALPHA_SIZE, 8, 105 | GLX_DEPTH_SIZE, 16, 0 106 | }; 107 | 108 | XVisualInfo* default_visual = glXChooseVisual(wm->display, wm->screen, default_visual_attributes); 109 | if (!default_visual) wm_error(wm, "Failed to get default GLX visual"); 110 | 111 | XSetWindowAttributes attributes = { 112 | .colormap = XCreateColormap(wm->display, wm->root_window, default_visual->visual, AllocNone), 113 | .border_pixel = 0, 114 | }; 115 | 116 | cwm->output_window = XCreateWindow( 117 | wm->display, wm->root_window, 0, 0, wm->width, wm->height, 0, default_visual->depth, 118 | InputOutput, default_visual->visual, CWBorderPixel | CWColormap, &attributes); 119 | 120 | XReparentWindow(wm->display, cwm->output_window, cwm->overlay_window, 0, 0); 121 | XMapRaised(wm->display, cwm->output_window); 122 | 123 | // get the GLX frame buffer configurations that match our specified attributes 124 | // generally we'll just be using the first one ('glx_configs[0]') 125 | 126 | const int config_attributes[] = { 127 | GLX_BIND_TO_TEXTURE_RGBA_EXT, 1, 128 | GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT, 129 | GLX_RENDER_TYPE, GLX_RGBA_BIT, 130 | GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, 131 | GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 132 | GLX_X_RENDERABLE, 1, 133 | GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint) GLX_DONT_CARE, 134 | GLX_BUFFER_SIZE, 32, 135 | // GLX_SAMPLE_BUFFERS, 1, 136 | // GLX_SAMPLES, 4, 137 | GLX_DOUBLEBUFFER, 1, 138 | GLX_RED_SIZE, 8, 139 | GLX_GREEN_SIZE, 8, 140 | GLX_BLUE_SIZE, 8, 141 | GLX_ALPHA_SIZE, 8, 142 | GLX_STENCIL_SIZE, 0, 143 | GLX_DEPTH_SIZE, 16, 0 144 | }; 145 | 146 | cwm->glx_configs = glXChooseFBConfig(wm->display, wm->screen, config_attributes, &cwm->glx_config_count); 147 | if (!cwm->glx_configs) wm_error(wm, "Failed to get GLX frame buffer configurations"); 148 | 149 | // create our OpenGL context 150 | // we must load the 'glXCreateContextAttribsARB' function ourselves 151 | 152 | const int gl_version_attributes[] = { // we want OpenGL 3.3 153 | GLX_CONTEXT_MAJOR_VERSION_ARB, 3, 154 | GLX_CONTEXT_MINOR_VERSION_ARB, 3, 155 | GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 0 156 | }; 157 | 158 | glXCreateContextAttribsARB_t glXCreateContextAttribsARB = (glXCreateContextAttribsARB_t) glXGetProcAddressARB((const GLubyte*) "glXCreateContextAttribsARB"); 159 | cwm->glx_context = glXCreateContextAttribsARB(wm->display, cwm->glx_configs[0], NULL, 1, gl_version_attributes); 160 | if (!cwm->glx_context) wm_error(wm, "Failed to create OpenGL context"); 161 | 162 | // load the other two functions we need but don't have 163 | 164 | cwm->glXBindTexImageEXT = (glXBindTexImageEXT_t) glXGetProcAddress((const GLubyte*) "glXBindTexImageEXT"); 165 | cwm->glXReleaseTexImageEXT = (glXReleaseTexImageEXT_t) glXGetProcAddress((const GLubyte*) "glXReleaseTexImageEXT"); 166 | 167 | // finally, make the context we just made the OpenGL context of this thread 168 | glXMakeCurrent(wm->display, cwm->output_window, cwm->glx_context); 169 | 170 | // initialize GLEW 171 | // this will be needed for most modern OpenGL calls 172 | 173 | glewExperimental = GL_TRUE; 174 | if (glewInit() != GLEW_OK) wm_error(wm, "Failed to initialize GLEW"); 175 | 176 | // enable adaptive vsync (-1 for adaptive vsync, 1 for non-adaptive vsync, 0 for no vsync) 177 | // this extension seems completely broken on NVIDIA 178 | 179 | // glXSwapIntervalEXT_t glXSwapIntervalEXT = (glXSwapIntervalEXT_t) glXGetProcAddress((const GLubyte*) "glXSwapIntervalEXT"); 180 | // glXSwapIntervalEXT(wm->display, cwm->output_window, 0); 181 | 182 | cwm->vsync = 1; 183 | 184 | // blacklist the overlay and output windows for events 185 | 186 | wm->event_blacklisted_windows = (Window*) realloc(wm->event_blacklisted_windows, (2 + wm->event_blacklisted_window_count) * sizeof(Window)); 187 | 188 | wm->event_blacklisted_windows[wm->event_blacklisted_window_count + 0] = cwm->overlay_window; 189 | wm->event_blacklisted_windows[wm->event_blacklisted_window_count + 1] = cwm->output_window; 190 | // wm->event_blacklisted_windows[wm->event_blacklisted_window_count + 2] = screen_owner; 191 | 192 | wm->event_blacklisted_window_count += 2; 193 | 194 | // setup the timing code (window managers don't seem to be able to vsync) 195 | 196 | gettimeofday(&cwm->previous_time, 0); 197 | } 198 | 199 | uint64_t cwm_swap(cwm_t* cwm) { 200 | glXSwapBuffers(cwm->wm->display, cwm->output_window); 201 | 202 | // return the time in microseconds between this frame and the last 203 | 204 | struct timeval current_time; 205 | gettimeofday(¤t_time, 0); 206 | 207 | int64_t delta = (current_time.tv_sec - cwm->previous_time.tv_sec) * 1000000 + current_time.tv_usec - cwm->previous_time.tv_usec; 208 | 209 | cwm->previous_time = current_time; 210 | return delta; 211 | } 212 | 213 | static cwm_window_internal_t* cwm_get_window_internal(cwm_t* cwm, wm_window_t* window) { 214 | cwm_window_internal_t* window_internal = (cwm_window_internal_t*) window->internal; 215 | 216 | if (!window_internal) { 217 | window->internal = (void*) malloc(sizeof(cwm_window_internal_t)); 218 | memset(window->internal, 0, sizeof(cwm_window_internal_t)); 219 | } 220 | 221 | return (cwm_window_internal_t*) window->internal; 222 | } 223 | 224 | // event handler functions 225 | 226 | void cwm_create_event(cwm_t* cwm, unsigned window_index) { 227 | wm_window_t* window = &cwm->wm->windows[window_index]; 228 | cwm_window_internal_t* window_internal = cwm_get_window_internal(cwm, window); 229 | } 230 | 231 | static inline void __cwm_free_pixmap(cwm_t* cwm, cwm_window_internal_t* window_internal) { 232 | if (window_internal->x_pixmap) { 233 | XFreePixmap(cwm->wm->display, window_internal->x_pixmap); 234 | window_internal->x_pixmap = 0; 235 | } 236 | 237 | if (window_internal->pixmap) { 238 | glXDestroyPixmap(cwm->wm->display, window_internal->pixmap); 239 | window_internal->pixmap = 0; 240 | } 241 | } 242 | 243 | void cwm_modify_event(cwm_t* cwm, unsigned window_index) { 244 | wm_window_t* window = &cwm->wm->windows[window_index]; 245 | cwm_window_internal_t* window_internal = cwm_get_window_internal(cwm, window); 246 | 247 | // delete pixmap since we're likely gonna need to update it 248 | 249 | __cwm_free_pixmap(cwm, window_internal); 250 | } 251 | 252 | void cwm_destroy_event(cwm_t* cwm, unsigned window_index) { 253 | wm_window_t* window = &cwm->wm->windows[window_index]; 254 | cwm_window_internal_t* window_internal = cwm_get_window_internal(cwm, window); 255 | 256 | __cwm_free_pixmap(cwm, window_internal); 257 | free(window_internal); 258 | } 259 | 260 | // rendering functions 261 | 262 | #define glXGetFBConfigAttribChecked(a, b, attr, c) \ 263 | if (glXGetFBConfigAttrib((a), (b), (attr), (c))) { \ 264 | fprintf(stderr, "WARNING Cannot get FBConfig attribute " #attr "\n"); \ 265 | } 266 | 267 | void cwm_bind_window_texture(cwm_t* cwm, unsigned window_index) { 268 | wm_window_t* window = &cwm->wm->windows[window_index]; 269 | cwm_window_internal_t* window_internal = cwm_get_window_internal(cwm, window); 270 | 271 | if (!window->exists) return; 272 | if (!window->visible) return; 273 | 274 | // TODO 'XGrabServer'/'XUngrabServer' necessary? 275 | // it seems to make things 10x faster for whatever reason 276 | // which is actually good for recording using OBS with XSHM 277 | 278 | if (!cwm->vsync) XGrabServer(cwm->wm->display); 279 | // glXWaitX(); // same as 'XSync', but a tad more efficient 280 | 281 | // update the window's pixmap 282 | 283 | if (!window_internal->pixmap) { 284 | XWindowAttributes attribs; 285 | XGetWindowAttributes(cwm->wm->display, window->window, &attribs); 286 | 287 | int format; 288 | GLXFBConfig config; 289 | 290 | for (int i = 0; i < cwm->glx_config_count; i++) { 291 | config = cwm->glx_configs[i]; 292 | 293 | int has_alpha; 294 | glXGetFBConfigAttribChecked(cwm->wm->display, config, GLX_BIND_TO_TEXTURE_RGBA_EXT, &has_alpha); 295 | 296 | XVisualInfo* visual = glXGetVisualFromFBConfig(cwm->wm->display, config); 297 | int visual_depth = visual->depth; 298 | free(visual); 299 | 300 | if (attribs.depth != visual_depth) { 301 | continue; 302 | } 303 | 304 | // found the config we want, break 305 | 306 | format = has_alpha ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; 307 | break; 308 | } 309 | 310 | const int pixmap_attributes[] = { 311 | GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT, 312 | GLX_TEXTURE_FORMAT_EXT, format, 0 // GLX_TEXTURE_FORMAT_RGB_EXT 313 | }; 314 | 315 | window_internal->x_pixmap = XCompositeNameWindowPixmap(cwm->wm->display, window->window); 316 | window_internal->pixmap = glXCreatePixmap(cwm->wm->display, config, window_internal->x_pixmap, pixmap_attributes); 317 | } 318 | 319 | cwm->glXBindTexImageEXT(cwm->wm->display, window_internal->pixmap, GLX_FRONT_LEFT_EXT, NULL); 320 | } 321 | 322 | void cwm_unbind_window_texture(cwm_t* cwm, unsigned window_index) { 323 | wm_window_t* window = &cwm->wm->windows[window_index]; 324 | cwm_window_internal_t* window_internal = cwm_get_window_internal(cwm, window); 325 | 326 | cwm->glXReleaseTexImageEXT(cwm->wm->display, window_internal->pixmap, GLX_FRONT_LEFT_EXT); 327 | if (!cwm->vsync) XUngrabServer(cwm->wm->display); 328 | } 329 | -------------------------------------------------------------------------------- /src/wm.h: -------------------------------------------------------------------------------- 1 | // this file contains helpers for the window manager 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #if !defined(DEBUGGING) 15 | #define DEBUGGING 0 16 | #endif 17 | 18 | #define WM_NAME "Basic X compositing WM" 19 | 20 | // structures and types 21 | 22 | typedef void (*wm_keyboard_event_callback_t) (void*, unsigned window, unsigned press, unsigned modifiers, unsigned key); 23 | typedef int (*wm_click_event_callback_t) (void*, unsigned window, unsigned press, unsigned modifiers, unsigned button, float x, float y); 24 | typedef void (*wm_move_event_callback_t) (void*, unsigned window, unsigned modifiers, float x, float y); 25 | 26 | typedef void (*wm_create_event_callback_t) (void*, unsigned window); 27 | typedef void (*wm_modify_event_callback_t) (void*, unsigned window, int visible, float x, float y, float width, float height); 28 | typedef void (*wm_destroy_event_callback_t) (void*, unsigned window); 29 | 30 | typedef struct { 31 | int exists; 32 | Window window; 33 | 34 | int visible; 35 | 36 | int x, y; 37 | int width, height; 38 | 39 | // this is extra data that can be allocated by extensions such as a compositor 40 | void* internal; 41 | } wm_window_t; 42 | 43 | typedef struct { 44 | Display* display; 45 | int screen; 46 | 47 | Window root_window; 48 | 49 | unsigned width; 50 | unsigned height; 51 | 52 | wm_window_t* windows; 53 | int window_count; 54 | 55 | // individual monitor information 56 | 57 | int monitor_count; 58 | XineramaScreenInfo* monitor_infos; 59 | 60 | // atoms (used for communicating information about the window manager to other clients) 61 | 62 | Atom client_list_atom; 63 | 64 | // list of windows that are blacklisted for events 65 | // this is mostly useful for non-application windows that the client doesn't care about 66 | 67 | Window* event_blacklisted_windows; 68 | int event_blacklisted_window_count; 69 | 70 | // event callbacks 71 | 72 | wm_keyboard_event_callback_t keyboard_event_callback; 73 | wm_click_event_callback_t click_event_callback; 74 | wm_move_event_callback_t move_event_callback; 75 | 76 | wm_create_event_callback_t create_event_callback; 77 | wm_modify_event_callback_t modify_event_callback; 78 | wm_destroy_event_callback_t destroy_event_callback; 79 | } wm_t; 80 | 81 | // utility functions 82 | 83 | static void wm_error(wm_t* wm, const char* message) { 84 | fprintf(stderr, "[WM_ERROR:%p] %s\n", wm, message); 85 | exit(1); 86 | } 87 | 88 | // don't forget for all the functions dealing with the y coordinate: 89 | // X coordinates start from the top left, whereas AQUA coordinates start from the bottom left (where they should be!) 90 | 91 | static inline float wm_width_dimension_to_float (wm_t* wm, int pixels) { return (float) pixels / wm->width * 2; } 92 | static inline float wm_height_dimension_to_float(wm_t* wm, int pixels) { return (float) pixels / wm->height * 2; } 93 | 94 | static inline float wm_x_coordinate_to_float(wm_t* wm, int pixels) { return wm_width_dimension_to_float (wm, pixels) - 1; } 95 | static inline float wm_y_coordinate_to_float(wm_t* wm, int pixels) { return -wm_height_dimension_to_float(wm, pixels) + 1; } 96 | 97 | static inline int wm_float_to_width_dimension (wm_t* wm, float x) { return (int) round(x / 2 * wm->width); } 98 | static inline int wm_float_to_height_dimension(wm_t* wm, float x) { return (int) round(x / 2 * wm->height); } 99 | 100 | static inline int wm_float_to_x_coordinate(wm_t* wm, float x) { return wm_float_to_width_dimension (wm, x + 1); } 101 | static inline int wm_float_to_y_coordinate(wm_t* wm, float x) { return wm_float_to_height_dimension(wm, -x + 1); } 102 | 103 | static int wm_event_blacklisted_window(wm_t* wm, Window window) { 104 | for (int i = 0; i < wm->event_blacklisted_window_count; i++) { 105 | if (window == wm->event_blacklisted_windows[i]) { 106 | return 1; 107 | } 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | static int wm_find_window_by_xid(wm_t* wm, Window xid) { 114 | for (int i = 0; i < wm->window_count; i++) { 115 | wm_window_t* window = &wm->windows[i]; 116 | 117 | // it's really super important to verify our window actually exists 118 | // we could have a window that doesn't exist anymore, but that had the same ID as one that currently exists 119 | 120 | if (window->exists && window->window == xid) { 121 | return i; 122 | } 123 | } 124 | 125 | // this shouldn't ever happen normally 126 | // we allow it when debugging, because sometimes it's useful to run our WM at the same time as another is running 127 | // so even if *this* WM has never heard of a certain window, it's possible it's been modified in our previous WM 128 | 129 | #if !defined(DEBUGGING) 130 | wm_error(wm, "Nonexistant window XID"); 131 | #endif 132 | 133 | return -1; 134 | } 135 | 136 | static void wm_sync_window(wm_t* wm, wm_window_t* window) { 137 | XWindowAttributes attributes; 138 | 139 | XGetWindowAttributes(wm->display, window->window, &attributes); 140 | 141 | window->visible = attributes.map_state == IsViewable; 142 | 143 | window->x = attributes.x; 144 | window->y = attributes.y; 145 | 146 | window->width = attributes.width; 147 | window->height = attributes.height; 148 | 149 | // TODO also get opacity of window here using the '_NET_WM_WINDOW_OPACITY' atom 150 | // see if this also is useful for checking if a window actually uses transparency at all (so that programs like OBS don't break) 151 | // (although I don't know how much of OBS breaking is due to GLX transparency not being properly implemented by me or due to some other reason where the alpha channel has garbage written in it) 152 | // when you re-implement transparency, don't forget to set the 'GLX_TEXTURE_FORMAT_EXT' attribute in 'cwm.h' and uncomment opacity in the shader code in 'main.c' 153 | } 154 | 155 | static void wm_update_client_list(wm_t* wm) { 156 | int existing_window_count = 0; 157 | 158 | for (int i = 0; i < wm->window_count; i++) { 159 | existing_window_count += wm->windows[i].exists; 160 | } 161 | 162 | Window client_list[existing_window_count]; 163 | 164 | for (int i = 0; i < existing_window_count; i++) { 165 | client_list[i] = wm->windows[i].window; 166 | } 167 | 168 | XChangeProperty(wm->display, wm->root_window, wm->client_list_atom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) client_list, existing_window_count); 169 | } 170 | 171 | static int wm_error_handler(Display* display, XErrorEvent* event) { 172 | if (!event->resourceid) return 0; // invalid window 173 | 174 | char buffer[1024]; 175 | XGetErrorText(display, event->error_code, buffer, sizeof(buffer)); 176 | 177 | printf("XError code = %d, string = %s, resource ID = 0x%lx\n", event->error_code, buffer, event->resourceid); 178 | return 0; 179 | } 180 | 181 | // exposed wm functions 182 | 183 | void new_wm(wm_t* wm) { 184 | memset(wm, 0, sizeof(*wm)); 185 | 186 | wm->display = XOpenDisplay(NULL /* default to 'DISPLAY' environment variable */); 187 | if (!wm->display) wm_error(wm, "Failed to open display"); 188 | 189 | // this call is here for debugging, so that errors are reported synchronously as they occur 190 | XSynchronize(wm->display, DEBUGGING); 191 | 192 | // get screen and root window 193 | 194 | wm->screen = DefaultScreen(wm->display); 195 | wm->root_window = DefaultRootWindow(wm->display); 196 | 197 | // get width/height of root window 198 | 199 | XWindowAttributes attributes; 200 | XGetWindowAttributes(wm->display, wm->root_window, &attributes); 201 | 202 | wm->width = attributes.width; 203 | wm->height = attributes.height; 204 | 205 | // tell X to send us all 'CreateNotify', 'ConfigureNotify', and 'DestroyNotify' events ('SubstructureNotifyMask' also sends back some other events but we're not using those) 206 | 207 | XSelectInput(wm->display, wm->root_window, SubstructureNotifyMask | PointerMotionMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask); 208 | 209 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("F1")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 210 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("q")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 211 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("f")), Mod4Mask | Mod1Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 212 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("f")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 213 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("t")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 214 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("v")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 215 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XStringToKeysym("r")), Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 216 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XK_Print) /* PrtSc */, Mod4Mask | Mod1Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 217 | XGrabKey(wm->display, XKeysymToKeycode(wm->display, XK_Print) /* PrtSc */, Mod4Mask, wm->root_window, 0, GrabModeAsync, GrabModeAsync); 218 | 219 | // setup our atoms (explained in more detail in the 'wm_t' struct) 220 | // we also need to specify which atoms are supported in '_NET_SUPPORTED' 221 | 222 | wm->client_list_atom = XInternAtom(wm->display, "_NET_CLIENT_LIST", 0); 223 | 224 | Atom supported_list_atom = XInternAtom(wm->display, "_NET_SUPPORTED", 0); 225 | Atom supported_atoms[] = { supported_list_atom, wm->client_list_atom }; 226 | 227 | XChangeProperty(wm->display, wm->root_window, supported_list_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*) supported_atoms, sizeof(supported_atoms) / sizeof(*supported_atoms)); 228 | 229 | // now, we move on to '_NET_SUPPORTING_WM_CHECK' 230 | // this is a bit weird, but it's all specified by the EWMH spec: https://developer.gnome.org/wm-spec/ 231 | 232 | Atom supporting_wm_check_atom = XInternAtom(wm->display, "_NET_SUPPORTING_WM_CHECK", 0); 233 | Window support_window = XCreateSimpleWindow(wm->display, wm->root_window, 0, 0, 1, 1, 0, 0, 0); 234 | 235 | Window support_window_list[1] = { support_window }; 236 | 237 | XChangeProperty(wm->display, wm->root_window, supporting_wm_check_atom, XA_WINDOW, 32, PropModeReplace, (const unsigned char*) support_window_list, 1); 238 | XChangeProperty(wm->display, support_window, supporting_wm_check_atom, XA_WINDOW, 32, PropModeReplace, (const unsigned char*) support_window_list, 1); 239 | 240 | Atom name_atom = XInternAtom(wm->display, "_NET_WM_NAME", 0); 241 | XChangeProperty(wm->display, support_window, name_atom, XA_STRING, 8, PropModeReplace, (const unsigned char*) WM_NAME, sizeof(WM_NAME)); 242 | 243 | // get all monitors and their individual resolutions 244 | 245 | wm->monitor_infos = XineramaQueryScreens(wm->display, &wm->monitor_count); 246 | 247 | // TODO REMME, this was just for testing 248 | 249 | // wm->monitor_count = 3; // virtual monitors 250 | // wm->monitor_infos = (XineramaScreenInfo*) malloc(wm->monitor_count * sizeof(XineramaScreenInfo)); 251 | 252 | // // first virtual monitor (1920x1080+0+0) 253 | 254 | // wm->monitor_infos[0].x_org = 0; 255 | // wm->monitor_infos[0].y_org = 0; 256 | 257 | // wm->monitor_infos[0].width = 1920; 258 | // wm->monitor_infos[0].height = 1080; 259 | 260 | // // second virtual monitor (640x1024+1920+56) 261 | 262 | // wm->monitor_infos[1].x_org = 1920; 263 | // wm->monitor_infos[1].y_org = 56; 264 | 265 | // wm->monitor_infos[1].width = 640; 266 | // wm->monitor_infos[1].height = 1024; 267 | 268 | // // third virtual monitor (640x1024+2560+56) 269 | 270 | // wm->monitor_infos[2].x_org = 2560; 271 | // wm->monitor_infos[2].y_org = 56; 272 | 273 | // wm->monitor_infos[2].width = 640; 274 | // wm->monitor_infos[2].height = 1024; 275 | 276 | // create our own error handler so X doesn't crash 277 | 278 | XSetErrorHandler(wm_error_handler); 279 | 280 | // setup event blacklist 281 | // the first window we wanna blacklist is the root window (TODO really necessary? I think it works just fine without... investigate!) 282 | // we also wanna blacklist the supporting window from earlier 283 | 284 | wm->event_blacklisted_windows = (Window*) malloc(sizeof(Window)); 285 | wm->event_blacklisted_window_count = 1; 286 | 287 | wm->event_blacklisted_windows[0] = support_window; 288 | 289 | // wm->event_blacklisted_window_count = 1; 290 | // wm->event_blacklisted_windows[0] = wm->root_window; 291 | 292 | // setup windows 293 | 294 | wm->windows = (wm_window_t*) malloc(1); 295 | wm->window_count = 0; 296 | } 297 | 298 | int wm_x_resolution(wm_t* wm) { return wm->width; } 299 | int wm_y_resolution(wm_t* wm) { return wm->height; } 300 | 301 | int wm_monitor_count(wm_t* wm) { return wm->monitor_count; } 302 | 303 | float wm_monitor_x(wm_t* wm, int monitor_index) { return wm_x_coordinate_to_float(wm, wm->monitor_infos[monitor_index].x_org + wm->monitor_infos[monitor_index].width / 2); } 304 | float wm_monitor_y(wm_t* wm, int monitor_index) { return wm_y_coordinate_to_float(wm, wm->monitor_infos[monitor_index].y_org + wm->monitor_infos[monitor_index].height / 2); } 305 | 306 | float wm_monitor_width (wm_t* wm, int monitor_index) { return wm_width_dimension_to_float (wm, wm->monitor_infos[monitor_index].width ); } 307 | float wm_monitor_height(wm_t* wm, int monitor_index) { return wm_height_dimension_to_float(wm, wm->monitor_infos[monitor_index].height); } 308 | 309 | // useful functions for managing windows 310 | 311 | void wm_close_window(wm_t* wm, unsigned window_id) { 312 | // all this fuss is to close a window softly 313 | // use the 'wm_kill_window' to force-close a window 314 | 315 | XEvent event; 316 | 317 | event.xclient.type = ClientMessage; 318 | event.xclient.window = wm->windows[window_id].window; 319 | event.xclient.message_type = XInternAtom(wm->display, "WM_PROTOCOLS", 1); 320 | event.xclient.format = 32; 321 | event.xclient.data.l[0] = XInternAtom(wm->display, "WM_DELETE_WINDOW", 0); 322 | event.xclient.data.l[1] = CurrentTime; 323 | 324 | XSendEvent(wm->display, wm->windows[window_id].window, 0, NoEventMask, &event); 325 | } 326 | 327 | void wm_kill_window(wm_t* wm, unsigned window_id) { 328 | // this function properly kills windows 329 | // use this sparingly, when force-closing an unresponsive window for example 330 | 331 | XDestroyWindow(wm->display, wm->windows[window_id].window); 332 | } 333 | 334 | void wm_move_window(wm_t* wm, unsigned window_id, float x, float y, float width, float height) { 335 | XMoveResizeWindow(wm->display, wm->windows[window_id].window, 336 | wm_float_to_x_coordinate(wm, x - width / 2), wm_float_to_y_coordinate(wm, y + height / 2), 337 | wm_float_to_width_dimension(wm, width), wm_float_to_height_dimension(wm, height)); 338 | } 339 | 340 | void wm_focus_window(wm_t* wm, unsigned window_id) { 341 | Window window = wm->windows[window_id].window; 342 | 343 | XSetInputFocus(wm->display, window, RevertToParent, CurrentTime); 344 | XMapRaised(wm->display, window); 345 | } 346 | 347 | // event processing call 348 | 349 | int wm_process_events(wm_t* wm, void* thing) { 350 | int events_left = XPending(wm->display); 351 | 352 | if (events_left) { 353 | XEvent event; 354 | XNextEvent(wm->display, &event); 355 | 356 | int type = event.type; 357 | 358 | if (type == KeyPress || type == KeyRelease) { 359 | if (wm->keyboard_event_callback) { 360 | wm->keyboard_event_callback(thing, wm_find_window_by_xid(wm, event.xkey.window), 361 | event.xkey.type == KeyPress, event.xkey.state, event.xkey.keycode); 362 | } 363 | } 364 | 365 | else if (type == ButtonPress || type == ButtonRelease) { 366 | if (wm->click_event_callback) { 367 | unsigned window = -1; 368 | 369 | if (!wm_event_blacklisted_window(wm, event.xbutton.window)) { 370 | window = wm_find_window_by_xid(wm, event.xbutton.window); 371 | } 372 | 373 | if (wm->click_event_callback(thing, window, 374 | event.xbutton.type == ButtonPress, event.xbutton.state, event.xbutton.button, 375 | wm_x_coordinate_to_float(wm, event.xbutton.x_root), wm_y_coordinate_to_float(wm, event.xbutton.y_root))) { 376 | 377 | // pass the event on to the client 378 | XAllowEvents(wm->display, ReplayPointer, CurrentTime); 379 | } 380 | 381 | else { 382 | // if we shouldn't pass the event on to the client, we still need to sync the pointer or else we hang 383 | XAllowEvents(wm->display, SyncPointer, CurrentTime); 384 | } 385 | } 386 | } 387 | 388 | else if (type == MotionNotify) { 389 | if (wm->move_event_callback) { 390 | wm->move_event_callback(thing, wm_find_window_by_xid(wm, event.xmotion.subwindow), 391 | event.xmotion.state, 392 | wm_x_coordinate_to_float(wm, event.xmotion.x_root), wm_y_coordinate_to_float(wm, event.xmotion.y_root)); 393 | } 394 | } 395 | 396 | // window notification events 397 | 398 | else if (type == CreateNotify) { 399 | Window x_window = event.xcreatewindow.window; 400 | if (wm_event_blacklisted_window(wm, x_window)) goto done; 401 | 402 | wm_window_t* window = (wm_window_t*) 0; 403 | int window_index; 404 | 405 | // search for an empty space in the window list 406 | 407 | for (window_index = 0; window_index < wm->window_count; window_index++) { 408 | if (!wm->windows[window_index].exists) { 409 | window = &wm->windows[window_index]; 410 | break; 411 | } 412 | } 413 | 414 | // if no empty space found, add one 415 | 416 | if (!window) { 417 | wm->windows = (wm_window_t*) realloc(wm->windows, (wm->window_count + 1) * sizeof(wm_window_t)); 418 | window_index = wm->window_count; 419 | 420 | window = &wm->windows[window_index]; 421 | wm->window_count++; 422 | } 423 | 424 | memset(window, 0, sizeof(*window)); 425 | 426 | window->exists = 1; 427 | window->window = x_window; 428 | 429 | if (wm->create_event_callback) { 430 | wm->create_event_callback(thing, window_index); 431 | } 432 | 433 | // set up some other stuff for the window 434 | // this is saying we want focus change and button events from the window 435 | 436 | XSelectInput(wm->display, x_window, FocusChangeMask); 437 | XGrabButton(wm->display, AnyButton, AnyModifier, x_window, 1, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeSync, GrabModeSync, 0, 0); 438 | 439 | wm_update_client_list(wm); 440 | } 441 | 442 | // TODO 'VisibilityNotify'? 443 | 444 | else if (type == ConfigureNotify || type == MapNotify /* show window */ || type == UnmapNotify /* hide window */) { 445 | Window x_window; 446 | 447 | if (type == ConfigureNotify) x_window = event.xconfigure.window; 448 | else if (type == MapNotify) x_window = event.xmap.window; 449 | else if (type == UnmapNotify) x_window = event.xunmap.window; 450 | 451 | if (wm_event_blacklisted_window(wm, x_window)) goto done; 452 | 453 | int window_index = wm_find_window_by_xid(wm, x_window); 454 | if (window_index < 0) goto done; 455 | wm_window_t* window = &wm->windows[window_index]; 456 | 457 | int was_visible = window->visible; 458 | wm_sync_window(wm, window); 459 | 460 | // if window wasn't visible before but is now, center it to the cursor position 461 | 462 | if (window->visible && !was_visible && !window->x && !window->y) { 463 | __attribute__((unused)) Window rw, cw; // root_return, child_return 464 | __attribute__((unused)) int wx, wy; // win_x_return, win_y_return 465 | __attribute__((unused)) unsigned int mask; // mask_return 466 | 467 | int x, y; 468 | XQueryPointer(wm->display, window->window, &rw, &cw, &x, &y, &wx, &wy, &mask); 469 | 470 | window->x = x - window->width / 2; 471 | window->y = y - window->height / 2; 472 | 473 | XMoveWindow(wm->display, window->window, window->x, window->y); 474 | } 475 | 476 | if (wm->modify_event_callback) { 477 | wm->modify_event_callback(thing, window_index, window->visible, 478 | wm_x_coordinate_to_float(wm, window->x + window->width / 2), wm_y_coordinate_to_float(wm, window->y + window->height / 2), 479 | wm_width_dimension_to_float (wm, window->width), wm_height_dimension_to_float(wm, window->height)); 480 | } 481 | } 482 | 483 | // else if (type == FocusIn) { 484 | // Window x_window = event.xfocus.window; 485 | // if (wm_event_blacklisted_window(wm, x_window)) goto done; 486 | 487 | // int window_index = wm_find_window_by_xid(wm, x_window); 488 | // if (window_index < 0) goto done; 489 | 490 | // if (wm->focus_event_callback) { 491 | // wm->focus_event_callback(thing, window_index); 492 | // } 493 | // } 494 | 495 | else if (type == DestroyNotify) { 496 | Window x_window = event.xdestroywindow.window; 497 | if (!x_window) goto done; 498 | 499 | int window_index = wm_find_window_by_xid(wm, x_window); 500 | if (window_index < 0) goto done; 501 | wm_window_t* window = &wm->windows[window_index]; 502 | 503 | if (wm->destroy_event_callback) { 504 | wm->destroy_event_callback(thing, window_index); 505 | } 506 | 507 | // remove the window from our list 508 | wm->windows[window_index].exists = 0; 509 | 510 | wm_update_client_list(wm); 511 | } 512 | } 513 | 514 | done: 515 | return events_left; 516 | } 517 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | 2 | #define DEBUGGING 1 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | // structures and types 12 | 13 | typedef struct { 14 | unsigned internal_id; 15 | 16 | int exists; 17 | int visible; 18 | 19 | uint64_t farness; 20 | 21 | float opacity; 22 | float x, y; 23 | float width, height; 24 | 25 | float visual_opacity; 26 | float visual_x, visual_y; 27 | float visual_width, visual_height; 28 | 29 | float visual_shadow_opacity; 30 | float visual_shadow_radius; 31 | float visual_shadow_y_offset; 32 | 33 | int maximized; 34 | 35 | float unmaximized_x, unmaximized_y; 36 | float unmaximized_width, unmaximized_height; 37 | 38 | int always_on_top; // TODO doesn't always on top mean always focused to X? 39 | // it appears not, but this still needs to be implemented 40 | // also maybe creating a proper linked list system for windows before implementing will make this easier 41 | 42 | // OpenGL stuff 43 | 44 | int index_count; 45 | GLuint vao, vbo, ibo; 46 | } window_t; 47 | 48 | typedef enum { 49 | ACTION_NONE = 0, 50 | ACTION_MOVE, ACTION_RESIZE 51 | } action_t; 52 | 53 | typedef struct { 54 | wm_t wm; 55 | cwm_t cwm; 56 | 57 | int x_resolution; 58 | int y_resolution; 59 | 60 | int running; 61 | 62 | window_t* windows; 63 | int window_count; 64 | 65 | // focused window and current action stuff 66 | 67 | unsigned focused_window_id; 68 | float focused_window_x, focused_window_y; 69 | 70 | action_t action; 71 | 72 | // monitor configuration info 73 | 74 | int monitor_count; 75 | 76 | float* monitor_xs, *monitor_ys; 77 | float* monitor_widths, *monitor_heights; 78 | 79 | // OpenGL stuff 80 | 81 | GLuint shader; 82 | GLuint texture_uniform; 83 | 84 | GLuint opacity_uniform; 85 | GLuint depth_uniform; 86 | 87 | GLuint position_uniform; 88 | GLuint size_uniform; 89 | 90 | // shadow stuff 91 | 92 | int shadow_index_count; 93 | GLuint shadow_vao, shadow_vbo, shadow_ibo; 94 | 95 | GLuint shadow_shader; 96 | 97 | GLuint shadow_strength_uniform; 98 | 99 | GLuint shadow_depth_uniform; 100 | GLuint shadow_position_uniform; 101 | GLuint shadow_size_uniform; 102 | 103 | GLuint shadow_spread_uniform; 104 | } my_wm_t; 105 | 106 | // useful functions 107 | 108 | static unsigned window_internal_id_to_index(my_wm_t* wm, unsigned internal_id) { 109 | for (int i = 0; i < wm->window_count; i++) { 110 | window_t* window = &wm->windows[i]; 111 | 112 | // it's really super important to verify our window actually exists 113 | // we could have a window that doesn't exist anymore, but that had the same ID as one that currently exists 114 | 115 | if (window->exists && window->internal_id == internal_id) { 116 | return i; 117 | } 118 | } 119 | 120 | // no window with that internal id was found 121 | return -1; 122 | } 123 | 124 | static void print_window_stack(my_wm_t* wm) { // debugging function 125 | printf("Window stack (%d windows):\n", wm->window_count); 126 | 127 | for (int i = 0; i < wm->window_count; i++) { 128 | window_t* window = &wm->windows[i]; 129 | 130 | if (window->exists) printf("\t[%d]: internal_id = %d, visible = %d, farness = %lu\n", i, window->internal_id, window->visible, window->farness); 131 | else printf("\t[%d]: empty space\n", i); 132 | } 133 | 134 | printf("\n"); 135 | } 136 | 137 | static void focus_window(my_wm_t* wm, unsigned window_id, int internally) { 138 | unsigned internal_id = wm->windows[window_id].internal_id; 139 | 140 | if (internally) { 141 | wm_focus_window(&wm->wm, internal_id); 142 | } 143 | 144 | for (int i = 0; i < wm->window_count; i++) { 145 | window_t* window = &wm->windows[i]; 146 | 147 | /* if (window->always_on_top) window->farness = 0; 148 | else */ window->farness++; 149 | } 150 | 151 | wm->windows[window_id].farness = 0; 152 | 153 | // sort windows 154 | // this could be a much more efficient system with linked lists (as I believe X does internally), but this is fine for now 155 | 156 | for (int i = 0; i < wm->window_count; i++) { 157 | for (int j = i + 1; j < wm->window_count; j++) { 158 | if (wm->windows[i].farness < wm->windows[j].farness) { 159 | window_t temp; 160 | 161 | memcpy(&temp, &wm->windows[i], sizeof(window_t)); 162 | memcpy(&wm->windows[i], &wm->windows[j], sizeof(window_t)); 163 | memcpy(&wm->windows[j], &temp, sizeof(window_t)); 164 | } 165 | } 166 | } 167 | 168 | wm->focused_window_id = window_internal_id_to_index(wm, internal_id); 169 | } 170 | 171 | static void unfocus_window(my_wm_t* wm) { 172 | // loop backwards through all the windows and check if they're a valid candidate for refocusing 173 | // we start at 'wm->window_count - 2', because we want the index and two ignore the last window on the stack (hopefully our focused window) 174 | 175 | for (int i = wm->focused_window_id - 1; i >= 0; i--) { 176 | if (i != wm->focused_window_id) { // just to make sure, but this shouldn't happen 177 | window_t* window = &wm->windows[i]; 178 | 179 | if (window->exists && window->visible) { 180 | focus_window(wm, i, 1); 181 | break; 182 | } 183 | } 184 | } 185 | } 186 | 187 | static void maximize_window(my_wm_t* wm, unsigned window_id, int single_monitor) { 188 | window_t* window = &wm->windows[window_id]; 189 | 190 | if (window->maximized) { 191 | window->maximized = 0; 192 | wm_move_window(&wm->wm, window->internal_id, window->unmaximized_x, window->unmaximized_y, window->unmaximized_width, window->unmaximized_height); 193 | 194 | return; 195 | } 196 | 197 | window->unmaximized_x = window->x; 198 | window->unmaximized_y = window->y; 199 | 200 | window->unmaximized_width = window->width; 201 | window->unmaximized_height = window->height; 202 | 203 | window->maximized = 1; 204 | 205 | if (single_monitor) { 206 | // find closest monitor to the centre of the window 207 | 208 | for (int i = 0; i < wm->monitor_count; i++) { 209 | float monitor_x = wm->monitor_xs [i]; 210 | float monitor_y = wm->monitor_ys [i]; 211 | 212 | float monitor_width = wm->monitor_widths [i]; 213 | float monitor_height = wm->monitor_heights[i]; 214 | 215 | if (window->x >= monitor_x - monitor_width / 2 && window->x <= monitor_x + monitor_width / 2 && 216 | window->y >= monitor_y - monitor_height / 2 && window->y <= monitor_y + monitor_height / 2) { 217 | 218 | wm_move_window(&wm->wm, window->internal_id, monitor_x, monitor_y, monitor_width, monitor_height); 219 | return; 220 | } 221 | } 222 | 223 | // if we can't find a valid monitor, no worries, we'll just fill the whole screen 224 | } 225 | 226 | wm_move_window(&wm->wm, window->internal_id, 0.0, 0.0, 2.0, 2.0); 227 | } 228 | 229 | // event callback functions 230 | 231 | static char* first_argument; 232 | 233 | void keyboard_event(my_wm_t* wm, unsigned internal_id, unsigned press, unsigned modifiers, unsigned key) { 234 | int alt = modifiers & 0x8; 235 | int super = modifiers & 0x40; 236 | 237 | if (press && super && key == 67) wm->running = 0; // Super+F1 238 | if (press && super && key == 24) wm_close_window(&wm->wm, wm->windows[wm->focused_window_id].internal_id); // Super+Q (quit) 239 | if (press && super && alt && key == 41) maximize_window(wm, wm->focused_window_id, 0); // Super+Alt+F (fullfullscreen) 240 | if (press && super && !alt && key == 41) maximize_window(wm, wm->focused_window_id, 1); // Super+F (fullscreen) 241 | if (press && super && key == 55) wm->cwm.vsync = !wm->cwm.vsync; // Super+V (vsync) 242 | 243 | if (press && super && key == 27) { // Super+R (restart) 244 | execl(first_argument, first_argument, NULL); 245 | exit(1); 246 | } 247 | 248 | if (press && super && key == 28) { // Super+T (terminal) 249 | /*if (!fork()) { 250 | execl("/usr/local/bin/xterm", "/usr/local/bin/xterm", NULL); 251 | exit(1); 252 | }*/ 253 | 254 | system("xterm &"); 255 | } 256 | 257 | if (press && super && !alt && key == 107) { // Super+PrtSc (screenshot of selection to clipboard) 258 | system("scrot -fs '/tmp/screenshot-selection-aquabsd-%F-%T.png' -e 'xclip -selection clipboard -target image/png -i $f && rm $f' &"); 259 | } 260 | 261 | if (press && super && alt && key == 107) { // Super+Alt+PrtSc (screenshot of window to clipboard) 262 | system("scrot -u '/tmp/screenshot-selection-aquabsd-$wx$h-%F-%T.png' -e 'xclip -selection clipboard -target image/png -i $f && rm $f' &"); 263 | } 264 | } 265 | 266 | int click_event(my_wm_t* wm, unsigned internal_id, unsigned press, unsigned modifiers, unsigned button, float x, float y) { 267 | int window_index; 268 | 269 | if (press) { 270 | if (internal_id == -1) return 0; 271 | window_index = window_internal_id_to_index(wm, internal_id); 272 | 273 | focus_window(wm, window_index, 1); 274 | 275 | wm->focused_window_x = wm->windows[wm->focused_window_id].x - x; 276 | wm->focused_window_y = wm->windows[wm->focused_window_id].y - y; 277 | 278 | } else if (wm->action) { // releasing and were already doing something 279 | window_t* window = &wm->windows[wm->focused_window_id]; 280 | 281 | window->opacity = 1.0; 282 | wm_move_window(&wm->wm, window->internal_id, window->x, window->y, window->width, window->height); 283 | 284 | wm->action = ACTION_NONE; 285 | return 0; 286 | } 287 | 288 | if (modifiers & Mod4Mask && press) { 289 | if (button == 1) wm->action = ACTION_MOVE; 290 | else if (button == 3) wm->action = ACTION_RESIZE; 291 | 292 | wm->windows[wm->focused_window_id].opacity = 0.9; 293 | 294 | return 0; 295 | } 296 | 297 | return 1; 298 | } 299 | 300 | void move_event(my_wm_t* wm, unsigned internal_id, unsigned modifiers, float x, float y) { 301 | if (wm->action && !wm->windows[wm->focused_window_id].maximized) { 302 | window_t* window = &wm->windows[wm->focused_window_id]; 303 | 304 | if (wm->action == ACTION_MOVE) { 305 | window->x = x + wm->focused_window_x; 306 | window->y = y + wm->focused_window_y; 307 | } 308 | 309 | else if (wm->action == ACTION_RESIZE) { 310 | window->width = 2 * fabs(x - window->x); 311 | window->height = 2 * fabs(y - window->y); 312 | 313 | // this is not ideal for performance in certain applications, but it looks hella cool for apps that work correctly nonetheless 314 | // basically the problem is that you're calling a 'ConfigureNotify' event each time, 315 | // which means the compositor needs to create a new pixmap for each frame 316 | // not good... 317 | 318 | wm_move_window(&wm->wm, window->internal_id, window->x, window->y, window->width, window->height); 319 | } 320 | } 321 | } 322 | 323 | void create_event(my_wm_t* wm, unsigned internal_id) { 324 | cwm_create_event(&wm->cwm, internal_id); 325 | 326 | int window_index = 0; 327 | 328 | for (; window_index < wm->window_count; window_index++) { 329 | if (!wm->windows[window_index].exists) { 330 | goto got_space; 331 | } 332 | } 333 | 334 | wm->windows = (window_t*) realloc(wm->windows, ++wm->window_count * sizeof(*wm->windows)); 335 | window_index = wm->window_count - 1; 336 | 337 | got_space: {} 338 | 339 | window_t* window = &wm->windows[window_index]; 340 | memset(window, 0, sizeof(*window)); 341 | 342 | window->internal_id = internal_id; 343 | window->exists = 1; 344 | window->opacity = 1.0; 345 | 346 | gl_create_vao_vbo_ibo(&window->vao, &window->vbo, &window->ibo); 347 | } 348 | 349 | void modify_event(my_wm_t* wm, unsigned internal_id, int visible, float x, float y, float width, float height) { 350 | cwm_modify_event(&wm->cwm, internal_id); 351 | 352 | int window_index = window_internal_id_to_index(wm, internal_id); 353 | window_t* window = &wm->windows[window_index]; 354 | 355 | int was_visible = window->visible; 356 | window->visible = visible; 357 | 358 | window->x = x; 359 | window->y = y; 360 | 361 | window->width = width; 362 | window->height = height; 363 | 364 | // regenerate vertex attributes and indices 365 | 366 | #define TAU 6.283185 367 | 368 | #define CORNER_RESOLUTION 8 369 | #define CORNER_RADIUS 3 // pixels 370 | 371 | float x_radius = 4 * (float) CORNER_RADIUS / wm->x_resolution / width; 372 | float y_radius = 4 * (float) CORNER_RADIUS / wm->y_resolution / height; 373 | 374 | // loop through all the vertex pairs 375 | 376 | GLfloat vertex_positions[CORNER_RESOLUTION * 2 * 4 + 4]; 377 | GLubyte indices[CORNER_RESOLUTION * 2 * 6 + 3]; 378 | 379 | int prev_index_pair[2] = { -1 }; 380 | 381 | for (int i = 0; i < CORNER_RESOLUTION * 2 + 1; i++) { 382 | // calculate indices 383 | 384 | int index_pair[2] = { i * 2, i * 2 + 1 }; 385 | 386 | if (prev_index_pair[0] >= 0) { 387 | indices[i * 6 + 0] = prev_index_pair[0]; 388 | indices[i * 6 + 1] = prev_index_pair[1]; 389 | indices[i * 6 + 2] = index_pair[1]; 390 | indices[i * 6 + 3] = prev_index_pair[0]; 391 | indices[i * 6 + 4] = index_pair[1]; 392 | indices[i * 6 + 5] = index_pair[0]; 393 | } 394 | 395 | memcpy(prev_index_pair, index_pair, sizeof(prev_index_pair)); 396 | 397 | // calculate vertices 398 | 399 | float theta = (float) (i - (i > CORNER_RESOLUTION / 2)) / CORNER_RESOLUTION * TAU / 2; 400 | 401 | float corner_x = cos(theta) * x_radius; 402 | float corner_y = sin(theta) * y_radius; 403 | 404 | float x = (i <= CORNER_RESOLUTION / 2 ? 0.5 - x_radius : -0.5 + x_radius) + corner_x; 405 | float y = 0.5 - y_radius + corner_y; 406 | 407 | vertex_positions[i * 4 + 0] = x; 408 | vertex_positions[i * 4 + 1] = y; 409 | 410 | vertex_positions[i * 4 + 2] = x; 411 | vertex_positions[i * 4 + 3] = -y; 412 | } 413 | 414 | window->index_count = sizeof(indices) / sizeof(*indices); 415 | gl_set_vao_vbo_ibo_data(window->vao, window->vbo, sizeof(vertex_positions), vertex_positions, window->ibo, sizeof(indices), indices); 416 | 417 | if (window->visible && !was_visible) { 418 | window->opacity = 1.0; 419 | window->visual_opacity = 0.0; 420 | 421 | wm_move_window(&wm->wm, window->internal_id, window->x, window->y, window->width, window->height); 422 | 423 | window->visual_x = window->x; 424 | window->visual_y = window->y; 425 | 426 | window->visual_width = window->width * 0.9; 427 | window->visual_height = window->height * 0.9; 428 | 429 | focus_window(wm, window_index, 0); 430 | } 431 | 432 | else if (window_index == wm->focused_window_id && !window->visible && was_visible) { 433 | unfocus_window(wm); 434 | } 435 | } 436 | 437 | void destroy_event(my_wm_t* wm, unsigned internal_id) { 438 | cwm_destroy_event(&wm->cwm, internal_id); 439 | 440 | unsigned window_index = window_internal_id_to_index(wm, internal_id); 441 | window_t* window = &wm->windows[window_index]; 442 | 443 | window->exists = 0; 444 | } 445 | 446 | // main functions 447 | 448 | static void render_window(my_wm_t* wm, unsigned window_id, float delta) { 449 | window_t* window = &wm->windows[window_id]; 450 | 451 | if (!window->exists ) return; 452 | if (!window->visible) return; 453 | 454 | // calculate visual window coordinates and size (animation) 455 | 456 | delta = MIN(0.1, delta); // just make sure this doesn't get too crazy 457 | // TODO see if this actually does anything 458 | 459 | window->visual_opacity += (window->opacity - window->visual_opacity) * delta * 10; 460 | 461 | window->visual_x += (window->x - window->visual_x) * delta * 20; 462 | window->visual_y += (window->y - window->visual_y) * delta * 20; 463 | 464 | // TODO for some reason, when disabling vsync, windows take a real long time before starting their "appearing" animation 465 | // maybe this is because of this? 466 | 467 | window->visual_width += (window->width - window->visual_width ) * delta * 30; 468 | window->visual_height += (window->height - window->visual_height) * delta * 30; 469 | 470 | float x = window->visual_x; 471 | float y = window->visual_y; 472 | 473 | float width = window->visual_width; 474 | float height = window->visual_height; 475 | 476 | // check if window coordinates and size are pixel aligned 477 | // rounding here instead of simply flooring to preserve proper subpixel rendering when animating 478 | 479 | int width_pixels = (int) round(width / 2 * wm->x_resolution); 480 | int height_pixels = (int) round(height / 2 * wm->y_resolution); 481 | 482 | if (width_pixels % 2) x += 0.5 / wm->x_resolution * 2; // if width odd, add half a pixel to x 483 | if (height_pixels % 2) y += 0.5 / wm->y_resolution * 2; // if height odd, subtract half a pixel to y 484 | 485 | // calculate window depth 486 | 487 | float depth = 1.0 - (float) window_id / wm->window_count; 488 | 489 | // draw the window contents 490 | 491 | glUseProgram(wm->shader); 492 | glUniform1i(wm->texture_uniform, 0); 493 | 494 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 495 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 496 | 497 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 498 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 499 | 500 | glActiveTexture(GL_TEXTURE0); 501 | cwm_bind_window_texture(&wm->cwm, window->internal_id); 502 | 503 | // actually draw the window 504 | 505 | glUniform1f(wm->opacity_uniform, window->visual_opacity); 506 | glUniform1f(wm->depth_uniform, depth); 507 | 508 | glUniform2f(wm->position_uniform, x, y); 509 | glUniform2f(wm->size_uniform, width, height); 510 | 511 | glBindVertexArray(window->vao); 512 | glDrawElements(GL_TRIANGLES, window->index_count, GL_UNSIGNED_BYTE, NULL); 513 | 514 | cwm_unbind_window_texture(&wm->cwm, window->internal_id); 515 | 516 | // draw the shadow 517 | // we do this after drawing the window contents so we can take advantage of alpha sorting 518 | 519 | glUseProgram(wm->shadow_shader); 520 | 521 | float shadow_opacity = 0.15 + 0.1 * (window_id == wm->focused_window_id); 522 | 523 | // TODO do I really want to disable shadows on maximized windows? 524 | 525 | // if (window->maximized) { // we want to phase out the shadow much slower when maximizing our window 526 | // window->visual_shadow_opacity -= window->visual_shadow_opacity * delta * 10; 527 | // } 528 | 529 | // else { 530 | window->visual_shadow_opacity += (shadow_opacity - window->visual_shadow_opacity) * delta * 30; 531 | // } 532 | 533 | glUniform1f(wm->shadow_strength_uniform, window->visual_opacity * window->visual_shadow_opacity); 534 | 535 | float shadow_radius = (float) (64 + (64 * (window_id == wm->focused_window_id))); // pixels 536 | window->visual_shadow_radius += (shadow_radius - window->visual_shadow_radius) * delta * 20; 537 | 538 | float spread_x = 4 * window->visual_shadow_radius / wm->x_resolution; 539 | float spread_y = 4 * window->visual_shadow_radius / wm->y_resolution; 540 | 541 | glUniform2f(wm->shadow_spread_uniform, spread_x, spread_y); 542 | 543 | float y_offset = -spread_y / 32 - spread_y / 16 * (window_id == wm->focused_window_id); 544 | window->visual_shadow_y_offset += (y_offset - window->visual_shadow_y_offset) * delta * 10; 545 | 546 | glUniform1f(wm->shadow_depth_uniform, depth); 547 | glUniform2f(wm->shadow_position_uniform, x, y /* + window->visual_shadow_y_offset / 2 */); 548 | glUniform2f(wm->shadow_size_uniform, width, height); 549 | 550 | glBindVertexArray(wm->shadow_vao); 551 | glDrawElements(GL_TRIANGLES, wm->shadow_index_count, GL_UNSIGNED_BYTE, NULL); 552 | } 553 | 554 | int main(int argc, char* argv[]) { 555 | first_argument = argv[0]; 556 | 557 | my_wm_t _wm; 558 | my_wm_t* wm = &_wm; 559 | memset(wm, 0, sizeof(*wm)); 560 | 561 | // create a compositing window manager 562 | 563 | new_wm(&wm->wm); 564 | new_cwm(&wm->cwm, &wm->wm); 565 | 566 | wm->x_resolution = wm_x_resolution(&wm->wm); 567 | wm->y_resolution = wm_y_resolution(&wm->wm); 568 | 569 | // get info about the monitor configuration 570 | 571 | wm->monitor_count = wm_monitor_count(&wm->wm); 572 | 573 | wm->monitor_xs = (float*) malloc(wm->monitor_count * sizeof(float)); 574 | wm->monitor_ys = (float*) malloc(wm->monitor_count * sizeof(float)); 575 | 576 | wm->monitor_widths = (float*) malloc(wm->monitor_count * sizeof(float)); 577 | wm->monitor_heights = (float*) malloc(wm->monitor_count * sizeof(float)); 578 | 579 | for (int i = 0; i < wm->monitor_count; i++) { 580 | wm->monitor_xs [i] = wm_monitor_x (&wm->wm, i); 581 | wm->monitor_ys [i] = wm_monitor_y (&wm->wm, i); 582 | 583 | wm->monitor_widths [i] = wm_monitor_width (&wm->wm, i); 584 | wm->monitor_heights[i] = wm_monitor_height(&wm->wm, i); 585 | } 586 | 587 | // register all the event callbacks 588 | // ideally, there would be proper functions to do this 589 | 590 | wm->wm.keyboard_event_callback = (wm_keyboard_event_callback_t) keyboard_event; 591 | wm->wm.click_event_callback = (wm_click_event_callback_t) click_event; 592 | wm->wm.move_event_callback = (wm_move_event_callback_t) move_event; 593 | 594 | wm->wm.create_event_callback = (wm_create_event_callback_t) create_event; 595 | wm->wm.modify_event_callback = (wm_modify_event_callback_t) modify_event; 596 | wm->wm.destroy_event_callback = (wm_destroy_event_callback_t) destroy_event; 597 | 598 | // run any startup programs here 599 | 600 | // system("code-oss"); 601 | 602 | // OpenGL stuff 603 | 604 | const char* vertex_shader_source = "#version 330\n" 605 | "layout(location = 0) in vec2 vertex_position;" 606 | "out vec2 local_position;" 607 | 608 | "uniform float depth;" 609 | "uniform vec2 position;" 610 | "uniform vec2 size;" 611 | 612 | "void main(void) {" 613 | " local_position = vertex_position;" 614 | " gl_Position = vec4(vertex_position * size + position, depth, 1.0);" 615 | "}"; 616 | 617 | const char* fragment_shader_source = "#version 330\n" 618 | "in vec2 local_position;" 619 | "out vec4 fragment_colour;" 620 | 621 | "uniform float opacity;" 622 | "uniform sampler2D texture_sampler;" 623 | 624 | "void main(void) {" 625 | " vec4 colour = texture(texture_sampler, local_position * vec2(1.0, -1.0) + vec2(0.5));" 626 | " float alpha = opacity * (1.0 - colour.a);" 627 | 628 | " fragment_colour = vec4(colour.rgb, alpha);" 629 | "}"; 630 | 631 | wm->shader = gl_create_shader_program(vertex_shader_source, fragment_shader_source); 632 | wm->texture_uniform = glGetUniformLocation(wm->shader, "texture_sampler"); 633 | 634 | wm->opacity_uniform = glGetUniformLocation(wm->shader, "opacity"); 635 | wm->depth_uniform = glGetUniformLocation(wm->shader, "depth"); 636 | 637 | wm->position_uniform = glGetUniformLocation(wm->shader, "position"); 638 | wm->size_uniform = glGetUniformLocation(wm->shader, "size"); 639 | 640 | // shadow stuff 641 | 642 | const GLubyte shadow_indices[] = { 0, 1, 2, 0, 2, 3 }; 643 | 644 | const GLfloat shadow_vertex_positions[] = { 645 | -0.5, 0.5, 646 | -0.5, -0.5, 647 | 0.5, -0.5, 648 | 0.5, 0.5, 649 | }; 650 | 651 | gl_create_vao_vbo_ibo(&wm->shadow_vao, &wm->shadow_vbo, &wm->shadow_ibo); 652 | 653 | wm->shadow_index_count = sizeof(shadow_indices) / sizeof(*shadow_indices); 654 | gl_set_vao_vbo_ibo_data(wm->shadow_vao, wm->shadow_vbo, sizeof(shadow_vertex_positions), shadow_vertex_positions, wm->shadow_ibo, sizeof(shadow_indices), shadow_indices); 655 | 656 | const char* shadow_vertex_shader_source = "#version 330\n" 657 | "layout(location = 0) in vec2 vertex_position;" 658 | 659 | "out vec2 map_position;" 660 | 661 | "uniform float depth;" 662 | "uniform vec2 position;" 663 | "uniform vec2 size;" 664 | "uniform vec2 spread;" 665 | 666 | "void main(void) {" 667 | " map_position = vertex_position * (size + spread);" 668 | " gl_Position = vec4(map_position + position, depth, 1.0);" 669 | "}"; 670 | 671 | const char* shadow_fragment_shader_source = "#version 330\n" 672 | "in vec2 map_position;" 673 | "out vec4 fragment_colour;" 674 | 675 | "uniform float strength;" 676 | 677 | "uniform vec2 size;" 678 | "uniform vec2 spread;" 679 | 680 | "void main(void) {" 681 | 682 | // find distance from point on shadow plane to window bounds 683 | 684 | " float dx = (2 * abs(map_position.x) - size.x + spread.x / 8) / spread.x;" 685 | " float dy = (2 * abs(map_position.y) - size.y + spread.y / 8) / spread.y;" 686 | 687 | " if (map_position.y > 0) dy *= 1.5;" 688 | " if (map_position.y < 0) dy /= 1.2;" 689 | 690 | " dx = clamp(dx, 0, 1);" 691 | " dy = clamp(dy, 0, 1);" 692 | 693 | // calculate the shadow colour 694 | 695 | " float value = 1.0 - clamp(length(vec2(dx, dy)), 0, 1);" 696 | " fragment_colour = vec4(0.0, 0.0, 0.0, value * value) * strength;" 697 | "}"; 698 | 699 | wm->shadow_shader = gl_create_shader_program(shadow_vertex_shader_source, shadow_fragment_shader_source); 700 | 701 | wm->shadow_strength_uniform = glGetUniformLocation(wm->shadow_shader, "strength"); 702 | 703 | wm->shadow_depth_uniform = glGetUniformLocation(wm->shadow_shader, "depth"); 704 | wm->shadow_position_uniform = glGetUniformLocation(wm->shadow_shader, "position"); 705 | wm->shadow_size_uniform = glGetUniformLocation(wm->shadow_shader, "size"); 706 | 707 | wm->shadow_spread_uniform = glGetUniformLocation(wm->shadow_shader, "spread"); 708 | 709 | glEnable(GL_DEPTH_TEST); 710 | glEnable(GL_BLEND); 711 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 712 | 713 | // main loop 714 | 715 | float average_delta = 0.0; 716 | 717 | wm->running = 1; 718 | while (wm->running) { 719 | while (wm_process_events(&wm->wm, wm)); 720 | 721 | // glClearColor(0.4, 0.2, 0.4, 1.0); 722 | // gruvbox background colour (#292828) 723 | glClearColor(0.16015625, 0.15625, 0.15625, 1.); 724 | 725 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 726 | 727 | // render our windows 728 | 729 | for (int i = 0; i < wm->window_count; i++) { 730 | render_window(wm, i, average_delta); 731 | } 732 | 733 | float delta = (float) cwm_swap(&wm->cwm) / 1000000; 734 | 735 | average_delta += delta; 736 | average_delta /= 2; 737 | 738 | // printf("average fps %f\n", 1 / average_delta); 739 | } 740 | } 741 | --------------------------------------------------------------------------------