CRTty
Inject custom fragment shaders into kitty (or any EGL/GLX application) - no patches, no special drivers. Ships with a built-in CRT monitor effect (scanlines, phosphor glow, barrel distortion, vignette, chromatic aberration).
Highlights
- Built-in effects:
crt,greyscale,invert - Custom
.glslshaders with live hot-reload - Auto uniforms for animation:
u_time,u_resolution - No app patching required (
LD_PRELOADonly)
Installation
Requires Linux with glibc, OpenGL 3.3+, and Rust stable (build only).
From source
&&
Arch Linux (AUR)
Nix
System-wide
Uninstall
Usage
Custom .glsl shaders are hot-reloaded - edit the file, save, and
the effect updates live without restarting kitty. Changes are picked up
on the next kitty redraw (cursor blink, typing, any terminal output).
If the shader fails to compile, a red error overlay is shown and the
GLSL error is printed to stderr (then it auto-recovers on next valid save).
Custom GLSL shaders
Write a standard GLSL 330 core fragment shader. It receives these inputs automatically:
| Uniform / Input | Type | Description |
|---|---|---|
in vec2 v_uv |
vec2 | Texture coordinates (0–1) |
uniform sampler2D u_input |
sampler2D | Screen contents |
uniform float u_time |
float | Seconds since init (for animation) |
uniform vec2 u_resolution |
vec2 | Viewport size in pixels |
Must write out vec4 o_color. All uniforms except u_input are optional.
Example — animated RGB wave:
#version 330 core
in vec2 v_uv;
out vec4 o_color;
uniform sampler2D u_input;
uniform float u_time;
uniform vec2 u_resolution;
void main() {
float wave = sin(v_uv.y * 40.0 + u_time * 3.0) * 0.003;
vec3 c;
c.r = texture(u_input, v_uv + vec2(wave, 0.0)).r;
c.g = texture(u_input, v_uv).g;
c.b = texture(u_input, v_uv - vec2(wave, 0.0)).b;
float scan = 0.95 + 0.05 * sin(v_uv.y * u_resolution.y * 3.14 + u_time * 2.0);
o_color = vec4(c * scan, 1.0);
}
See examples/ for more sample shaders.
Write your own effect
Create a new Rust library crate:
&&
Cargo.toml:
[]
= ["cdylib"]
[]
= { = "https://github.com/<you>/CRTty" }
src/lib.rs:
;
main!;
Build and use:
LD_PRELOAD=/target/release/libmy_shader.so ENABLE_CRTTY=1
The Effect trait
Helpers for setting uniforms:
get_uniform_location // -> i32
uniform_1f
uniform_1i
How it works
App (GLFW / EGL / GLX)
│
├─ dlsym(handle, "eglSwapBuffers")
│ ↑ intercepted by your .so
│
└─ eglSwapBuffers(dpy, surface)
├─ glCopyTexSubImage2D → capture framebuffer
├─ Bind your shader (GLSL 330 core)
├─ Your set_uniforms() runs
├─ Draw fullscreen triangle
└─ Call real eglSwapBuffers
Project structure
src/
lib.rs Effect trait, main! macro
hook.rs dlsym interception
pass.rs Render pass engine
gl.rs GL function pointers + helpers
config.rs Config file parser
effects/
crt.rs Built-in CRT effect
greyscale.rs Greyscale effect
invert.rs Color inversion effect
custom.rs Runtime GLSL file loader
cli/
src/main.rs CLI launcher (crtty binary)
crt/
src/lib.rs Default cdylib (Builtin::from_env())
examples/
wave.glsl Animated RGB wave + scanlines
PKGBUILD Arch Linux / AUR package
flake.nix Nix flake
CRT effect configuration
Edit ~/.config/crtty.conf:
enabled=1
scanline_intensity=0.75
phosphor_strength=1.1
curvature=0.04
vignette=0.35
aberration=0.003
| Parameter | Range | Description |
|---|---|---|
enabled |
0/1 |
Master switch |
scanline_intensity |
0.0–1.0 | Horizontal raster line darkness |
phosphor_strength |
0.0–3.0 | Bloom intensity |
curvature |
0.0–0.5 | Barrel distortion |
vignette |
0.0–2.0 | Corner darkening |
aberration |
0.0–0.05 | RGB channel offset |
License
MIT