/*
 * AMOURANTHRTX · fullscreen passthrough + transparency · X11 IN
 * Compute RTX linear out (field|SPV free) · draw sharp with alpha
 * Mouse + start button stay on LXQt under us (ShapeInput empty)
 * Quit: Ctrl+Shift+Q  or  SIGINT/SIGTERM  or  --seconds N
 *
 *   ./Build/x pass
 */
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/shape.h>
#include <X11/keysym.h>

#include <math.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifndef GL_SHADER_STORAGE_BUFFER
#define GL_SHADER_STORAGE_BUFFER 0x90D2
#endif
#ifndef GL_SHADER_STORAGE_BARRIER_BIT
#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000
#endif
#ifndef GL_COMPUTE_SHADER
#define GL_COMPUTE_SHADER 0x91B9
#endif
#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
#endif

static volatile sig_atomic_t g_run = 1;
static void on_sig(int s) {
    (void)s;
    g_run = 0;
}

static const char *COMP_SRC =
    "#version 430\n"
    "layout(local_size_x = 16, local_size_y = 16) in;\n"
    "layout(std430, binding = 0) buffer Field { int field[]; };\n"
    "layout(std430, binding = 1) buffer Free  { int free_spv[]; };\n"
    "layout(std430, binding = 2) readonly buffer Params {\n"
    "  int width; int height; int seed; int _pad;\n"
    "};\n"
    "int bgf(int a, int b) { return (a - b) | 1; }\n"
    "int spv(int a, int b, int c) { return (a ^ b ^ c) | 1; }\n"
    "void main() {\n"
    "  int x = int(gl_GlobalInvocationID.x);\n"
    "  int y = int(gl_GlobalInvocationID.y);\n"
    "  if (x >= width || y >= height) return;\n"
    "  int i = y * width + x;\n"
    "  int cx = width/2, cy = height/2;\n"
    "  int dx = x - cx, dy = y - cy;\n"
    "  int ax = dx < 0 ? -dx : dx;\n"
    "  int ay = dy < 0 ? -dy : dy;\n"
    "  int cheb = ax > ay ? ax : ay;\n"
    "  int rad = min(width, height) / 4;\n"
    "  field[i] = bgf(cheb, rad);\n"
    "  free_spv[i] = spv(x, y, seed);\n"
    "}\n";

static const char *VERT_SRC =
    "#version 430\n"
    "out vec2 uv;\n"
    "void main() {\n"
    "  vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\n"
    "  uv = p;\n"
    "  gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n"
    "}\n";

/* sharp AMOURANTH · not frosted · alpha passthrough · fade RTX to black/white */
static const char *FRAG_SRC =
    "#version 430\n"
    "in vec2 uv;\n"
    "out vec4 fc;\n"
    "uniform sampler2D u_tex;\n"
    "uniform float u_alpha;\n"
    "uniform float u_fade;\n"   /* 0 = shell overlay · 1 = full black or white */
    "uniform float u_fade_w;\n" /* 0 = fade black · 1 = fade white */
    "void main() {\n"
    "  vec4 t = texture(u_tex, vec2(uv.x, 1.0 - uv.y));\n"
    "  float a = t.a * u_alpha;\n"
    "  vec3 shell = t.rgb;\n"
    "  /* passthrough look */\n"
    "  vec4 base = vec4(shell, a);\n"
    "  /* full cover target · alpha 1 · black or white */\n"
    "  vec3 solid = mix(vec3(0.0), vec3(1.0), u_fade_w);\n"
    "  vec4 cover = vec4(solid, 1.0);\n"
    "  float f = clamp(u_fade, 0.0, 1.0);\n"
    "  /* sharp mix · no blur frost */\n"
    "  fc = mix(base, cover, f);\n"
    "}\n";

static void die(const char *m) {
    fprintf(stderr, "rtx_pass: %s\n", m);
    exit(1);
}

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *);

static GLuint compile_shader(GLenum type, const char *src) {
    GLuint s = glCreateShader(type);
    glShaderSource(s, 1, &src, NULL);
    glCompileShader(s);
    GLint ok = 0;
    glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        char log[4096];
        glGetShaderInfoLog(s, sizeof log, NULL, log);
        fprintf(stderr, "shader:\n%s\n", log);
        die("compile");
    }
    return s;
}

static GLuint link_prog(GLuint a, GLuint b) {
    GLuint p = glCreateProgram();
    glAttachShader(p, a);
    if (b) glAttachShader(p, b);
    glLinkProgram(p);
    GLint ok = 0;
    glGetProgramiv(p, GL_LINK_STATUS, &ok);
    if (!ok) {
        char log[4096];
        glGetProgramInfoLog(p, sizeof log, NULL, log);
        fprintf(stderr, "link:\n%s\n", log);
        die("link");
    }
    return p;
}

static void field_to_rgba(const int *field, const int *free_spv, int w, int h, unsigned char *rgba, float edge_boost) {
    (void)edge_boost;
    for (int i = 0; i < w * h; i++) {
        int d = field[i];
        int a = d < 0 ? -d : d;
        int band = a & 255;
        int inside = d < 0;
        int fr = free_spv[i];
        int spark = fr & 31;
        unsigned char r, g, b, al;
        if (inside) {
            /* interior mostly transparent — desktop passthrough */
            r = 24;
            g = 48;
            b = 36;
            al = 28; /* light veil */
        } else if (band < 6) {
            /* sharp shell edge */
            r = g = b = 235;
            al = 200;
        } else if (band < 24) {
            r = 40;
            g = 90;
            b = 70;
            al = (unsigned char)(90 - band);
        } else {
            /* far field fully clear — true passthrough */
            r = g = b = 0;
            al = 0;
        }
        if (spark == 1 && al > 0) {
            r = (unsigned char)(r + 50 > 255 ? 255 : r + 50);
            al = (unsigned char)(al + 20 > 255 ? 255 : al + 20);
        }
        rgba[i * 4 + 0] = r;
        rgba[i * 4 + 1] = g;
        rgba[i * 4 + 2] = b;
        rgba[i * 4 + 3] = al;
    }
}

/* smoothstep-ish without lib dependency */
static float clampf(float x, float a, float b) {
    if (x < a) return a;
    if (x > b) return b;
    return x;
}
static float smooth01(float t) {
    t = clampf(t, 0.f, 1.f);
    return t * t * (3.f - 2.f * t);
}

int main(int argc, char **argv) {
    int cw = 512, ch = 512, seed = 7;
    /* transparency: -1 = FULL passthrough clear · [0..1] shell opacity · we know SORT */
    float alpha = -1.f;
    int seconds = 0; /* 0 = until quit */
    int once = 0;
    /* fade: 0 none · 1 black · 2 white · 3 free demo black then white */
    int fade_mode = 0;
    float fade_hold = 2.5f; /* seconds per fade ramp in demo / single */
    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--w") && i + 1 < argc) cw = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--h") && i + 1 < argc) ch = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--seed") && i + 1 < argc) seed = atoi(argv[++i]);
        else if ((!strcmp(argv[i], "--alpha") || !strcmp(argv[i], "--transparency") ||
                  !strcmp(argv[i], "-t")) &&
                 i + 1 < argc)
            alpha = (float)atof(argv[++i]);
        else if (!strcmp(argv[i], "--seconds") && i + 1 < argc) seconds = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--once")) once = 1;
        else if (!strcmp(argv[i], "--fade") && i + 1 < argc) {
            const char *m = argv[++i];
            if (!strcmp(m, "black") || !strcmp(m, "b") || !strcmp(m, "0")) fade_mode = 1;
            else if (!strcmp(m, "white") || !strcmp(m, "w") || !strcmp(m, "1")) fade_mode = 2;
            else if (!strcmp(m, "demo") || !strcmp(m, "show") || !strcmp(m, "free")) fade_mode = 3;
            else fade_mode = 0;
        } else if (!strcmp(argv[i], "--fade-sec") && i + 1 < argc) {
            fade_hold = (float)atof(argv[++i]);
            if (fade_hold < 0.2f) fade_hold = 0.2f;
        } else if (!strcmp(argv[i], "--demo") || !strcmp(argv[i], "--show-fade")) {
            fade_mode = 3;
            if (seconds <= 0) seconds = 12;
        }
    }
    /* -1 or any negative → full clear passthrough (shell invisible; fade still covers) */
    int pass_clear = (alpha < 0.f) ? 1 : 0;
    float shell_a = pass_clear ? 0.f : (alpha > 1.f ? 1.f : alpha);
    if (fade_mode == 3 && seconds <= 0) seconds = 12;

    signal(SIGINT, on_sig);
    signal(SIGTERM, on_sig);

    Display *dpy = XOpenDisplay(NULL);
    if (!dpy) die("XOpenDisplay");
    int screen = DefaultScreen(dpy);

    /* ARGB FBConfig for real transparency under picom */
    static int fb_attr[] = {
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
        GLX_DOUBLEBUFFER, True,
        GLX_RED_SIZE, 8,
        GLX_GREEN_SIZE, 8,
        GLX_BLUE_SIZE, 8,
        GLX_ALPHA_SIZE, 8,
        GLX_DEPTH_SIZE, 16,
        None};
    int ncfg = 0;
    GLXFBConfig *cfgs = glXChooseFBConfig(dpy, screen, fb_attr, &ncfg);
    if (!cfgs || ncfg < 1) die("no ARGB GLX FBConfig (need composite/picom path)");

    GLXFBConfig fbc = NULL;
    XVisualInfo *vi = NULL;
    XRenderPictFormat *fmt = NULL;
    for (int i = 0; i < ncfg; i++) {
        vi = glXGetVisualFromFBConfig(dpy, cfgs[i]);
        if (!vi) continue;
        fmt = XRenderFindVisualFormat(dpy, vi->visual);
        if (fmt && fmt->direct.alphaMask > 0) {
            fbc = cfgs[i];
            break;
        }
        XFree(vi);
        vi = NULL;
    }
    if (!fbc || !vi) die("no visual with alpha mask");

    int sw = DisplayWidth(dpy, screen);
    int sh = DisplayHeight(dpy, screen);

    XSetWindowAttributes swa;
    swa.colormap = XCreateColormap(dpy, RootWindow(dpy, screen), vi->visual, AllocNone);
    swa.background_pixmap = None;
    swa.border_pixel = 0;
    swa.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask;
    swa.override_redirect = True; /* cover full screen; we pass input via shape */

    Window win = XCreateWindow(
        dpy, RootWindow(dpy, screen),
        0, 0, (unsigned)sw, (unsigned)sh, 0,
        vi->depth, InputOutput, vi->visual,
        CWColormap | CWBackPixmap | CWBorderPixel | CWEventMask | CWOverrideRedirect,
        &swa);
    XStoreName(dpy, win, "AMOURANTHRTX passthrough");

    /* stack: above wallpaper, but input falls through */
    Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
    Atom wm_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
    Atom wm_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
    Atom wm_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
    XChangeProperty(dpy, win, wm_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_dock, 1);
    XChangeProperty(dpy, win, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_above, 1);

    /* click-through: empty input shape · mouse hits LXQt under us */
    XShapeCombineRectangles(dpy, win, ShapeInput, 0, 0, NULL, 0, ShapeSet, YXBanded);

    XMapRaised(dpy, win);
    XFlush(dpy);

    glXCreateContextAttribsARBProc create_ctx = (glXCreateContextAttribsARBProc)
        glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
    GLXContext ctx = NULL;
    if (create_ctx) {
        int ctx_attr[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
            GLX_CONTEXT_MINOR_VERSION_ARB, 3,
            None};
        ctx = create_ctx(dpy, fbc, NULL, True, ctx_attr);
    }
    if (!ctx) ctx = glXCreateNewContext(dpy, fbc, GLX_RGBA_TYPE, NULL, True);
    if (!ctx) die("GL context");
    if (!glXMakeCurrent(dpy, win, ctx)) die("glXMakeCurrent");

    printf("AMOURANTHRTX · pass · %dx%d · transparency=%s\n", sw, sh,
           pass_clear ? "-1 CLEAR" : (shell_a == 0.f ? "0" : "set"));
    if (pass_clear) printf("transparency -1 · full passthrough · SORT top · input through\n");
    else printf("shell alpha=%.2f · SORT top · input through\n", shell_a);
    printf("renderer: %s\n", glGetString(GL_RENDERER));
    printf("quit: Ctrl+Shift+Q\n");
    if (fade_mode == 1) printf("fade → BLACK\n");
    else if (fade_mode == 2) printf("fade → WHITE\n");
    else if (fade_mode == 3) printf("fade demo · BLACK/WHITE · %.1fs\n", fade_hold);

    /* global quit grab (works even with empty ShapeInput) */
    KeyCode q = XKeysymToKeycode(dpy, XK_q);
    unsigned mods = ControlMask | ShiftMask;
    XGrabKey(dpy, q, mods, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
    XGrabKey(dpy, q, mods | LockMask, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
    XGrabKey(dpy, q, mods | Mod2Mask, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
    XGrabKey(dpy, q, mods | LockMask | Mod2Mask, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
    XSelectInput(dpy, DefaultRootWindow(dpy), KeyPressMask);

    GLuint cs = compile_shader(GL_COMPUTE_SHADER, COMP_SRC);
    GLuint cprog = link_prog(cs, 0);
    glDeleteShader(cs);

    GLuint vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC);
    GLuint fs = compile_shader(GL_FRAGMENT_SHADER, FRAG_SRC);
    GLuint dprog = link_prog(vs, fs);
    glDeleteShader(vs);
    glDeleteShader(fs);

    size_t np = (size_t)cw * (size_t)ch;
    size_t bytes = np * sizeof(int);
    int params[4] = {cw, ch, seed, 0};
    GLuint ssbo[3];
    glGenBuffers(3, ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)bytes, NULL, GL_DYNAMIC_COPY);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)bytes, NULL, GL_DYNAMIC_COPY);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[2]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof params, params, GL_DYNAMIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo[0]);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo[1]);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssbo[2]);

    glUseProgram(cprog);
    glDispatchCompute((GLuint)((cw + 15) / 16), (GLuint)((ch + 15) / 16), 1);
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    int *field = (int *)malloc(bytes);
    int *freeb = (int *)malloc(bytes);
    unsigned char *rgba = (unsigned char *)malloc(np * 4);
    if (!field || !freeb || !rgba) die("oom");
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]);
    glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)bytes, field);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]);
    glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)bytes, freeb);
    field_to_rgba(field, freeb, cw, ch, rgba, 1.f);
    printf("compute one-shot OK · field0=%d free0=%d · dual no fold\n", field[0], freeb[0]);

    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* sharp · not frosted */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cw, ch, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);
    glClearColor(0.f, 0.f, 0.f, 0.f);

    GLint loc_tex = glGetUniformLocation(dprog, "u_tex");
    GLint loc_a = glGetUniformLocation(dprog, "u_alpha");
    GLint loc_fade = glGetUniformLocation(dprog, "u_fade");
    GLint loc_fw = glGetUniformLocation(dprog, "u_fade_w");

    struct timespec ts0;
    clock_gettime(CLOCK_MONOTONIC, &ts0);
    time_t t0 = time(NULL);
    int frame = 0;
    float last_report = -1.f;
    while (g_run) {
        while (XPending(dpy)) {
            XEvent ev;
            XNextEvent(dpy, &ev);
            if (ev.type == KeyPress) {
                KeySym ks = XLookupKeysym(&ev.xkey, 0);
                unsigned st = ev.xkey.state & (ControlMask | ShiftMask | Mod1Mask);
                if (ks == XK_q && (st & ControlMask) && (st & ShiftMask)) g_run = 0;
            }
            if (ev.type == ConfigureNotify) {
                sw = ev.xconfigure.width;
                sh = ev.xconfigure.height;
            }
        }
        if (seconds > 0 && (int)(time(NULL) - t0) >= seconds) g_run = 0;

        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
        float elapsed = (float)(ts.tv_sec - ts0.tv_sec) + (float)(ts.tv_nsec - ts0.tv_nsec) * 1e-9f;

        /* fade amount + black/white selector */
        float fade = 0.f;
        float fade_w = 0.f; /* 0 black · 1 white */
        if (fade_mode == 1) {
            fade = smooth01(elapsed / fade_hold);
            fade_w = 0.f;
        } else if (fade_mode == 2) {
            fade = smooth01(elapsed / fade_hold);
            fade_w = 1.f;
        } else if (fade_mode == 3) {
            /* free demo timeline:
             * 0..H shells
             * H..2H → black
             * 2H..2.5H hold black
             * 2.5H..3.5H → white
             * 3.5H..4H hold white
             * 4H..5H back to shells
             * then loop
             */
            float H = fade_hold;
            float cyc = 5.f * H;
            float t = fmodf(elapsed, cyc);
            if (t < H) {
                fade = 0.f;
                fade_w = 0.f;
            } else if (t < 2.f * H) {
                fade = smooth01((t - H) / H);
                fade_w = 0.f;
            } else if (t < 2.5f * H) {
                fade = 1.f;
                fade_w = 0.f;
            } else if (t < 3.5f * H) {
                fade = 1.f;
                fade_w = smooth01((t - 2.5f * H) / H); /* black → white while covered */
            } else if (t < 4.f * H) {
                fade = 1.f;
                fade_w = 1.f;
            } else {
                fade = 1.f - smooth01((t - 4.f * H) / H); /* white → shells */
                fade_w = 1.f;
            }
            if (elapsed - last_report > 0.5f) {
                const char *ph =
                    (t < H)             ? "shells/passthrough"
                    : (t < 2.f * H)     ? "FADE → BLACK"
                    : (t < 2.5f * H)    ? "FULL BLACK"
                    : (t < 3.5f * H)    ? "BLACK → WHITE"
                    : (t < 4.f * H)     ? "FULL WHITE"
                                        : "WHITE → shells";
                printf("  [demo] t=%.1fs  %s  fade=%.2f  w=%.2f\n", elapsed, ph, fade, fade_w);
                last_report = elapsed;
            }
        }

        /* optional seed drift for free sparkle without re-folding field law */
        if (!once && (frame % 30) == 0) {
            params[2] = seed + frame;
            glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[2]);
            glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof params, params);
            glUseProgram(cprog);
            glDispatchCompute((GLuint)((cw + 15) / 16), (GLuint)((ch + 15) / 16), 1);
            glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
            glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]);
            glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)bytes, field);
            glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]);
            glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)bytes, freeb);
            field_to_rgba(field, freeb, cw, ch, rgba, 1.f);
            glBindTexture(GL_TEXTURE_2D, tex);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cw, ch, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
        }

        glViewport(0, 0, sw, sh);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(dprog);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glUniform1i(loc_tex, 0);
        glUniform1f(loc_a, shell_a); /* -1 path → 0 clear passthrough */
        glUniform1f(loc_fade, fade);
        glUniform1f(loc_fw, fade_w);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glXSwapBuffers(dpy, win);
        frame++;
        usleep(16000);
    }

    XUngrabKey(dpy, q, mods, DefaultRootWindow(dpy));
    free(field);
    free(freeb);
    free(rgba);
    glDeleteTextures(1, &tex);
    glDeleteProgram(cprog);
    glDeleteProgram(dprog);
    glDeleteBuffers(3, ssbo);
    glXMakeCurrent(dpy, None, NULL);
    glXDestroyContext(dpy, ctx);
    XDestroyWindow(dpy, win);
    XFree(cfgs);
    XFree(vi);
    XCloseDisplay(dpy);
    printf("passthrough exit · remain IN X11 · mouse/start untouched\n");
    return 0;
}
