Skip to content
/ CRTty Public

Post-processing shader framework for kitty/alacritty terminal via LD_PRELOAD

License

Notifications You must be signed in to change notification settings

kosa12/CRTty

Repository files navigation

CRTty

Post-processing shader framework for kitty and Alacritty via LD_PRELOAD

CRTty preview

Inject custom fragment shaders into kitty/alacritty (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 .glsl shaders with live hot-reload
  • Auto uniforms for animation: u_time, u_resolution
  • No app patching required (LD_PRELOAD only)

Installation

Requires Linux with glibc, OpenGL 3.3+, and Rust stable (build only).

From source

git clone https://github.com/kosa12/CRTty && cd CRTty
make install        # builds + installs to ~/.local

Arch Linux (AUR)

yay -S crtty-git

Nix

nix profile install github:kosa12/CRTty

System-wide

sudo make PREFIX=/usr install

Uninstall

make uninstall

Usage

crtty                        # launch kitty with CRT effect (default)
crtty --app alacritty        # launch alacritty with CRT effect
crtty --list                 # show all available effects
crtty --migrate-config       # migrate legacy ~/.config/crtty.conf to kitty.conf
crtty -s greyscale           # use a different effect
crtty -s ./my_shader.glsl    # use a custom GLSL file
crtty -s examples/retro.glsl # built-in retro example
crtty -s crt -- --hold       # pass extra args to selected app

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);
}
crtty -s ./wave.glsl

See examples/ for more sample shaders.

Write your own effect

Create a new Rust library crate:

cargo new --lib my-shader && cd my-shader

Cargo.toml:

[lib]
crate-type = ["cdylib"]

[dependencies]
crtty = { git = "https://github.com/<you>/CRTty" }

src/lib.rs:

struct Grayscale;

impl crtty::Effect for Grayscale {
    fn fragment_shader(&self) -> &str {
        "#version 330 core
         in vec2 v_uv;
         out vec4 o_color;
         uniform sampler2D u_input;
         void main() {
             vec3 c = texture(u_input, v_uv).rgb;
             float l = dot(c, vec3(0.299, 0.587, 0.114));
             o_color = vec4(l, l, l, 1.0);
         }"
    }
}

crtty::main!(Grayscale);

Build and use:

cargo build --release
LD_PRELOAD=$(pwd)/target/release/libmy_shader.so ENABLE_CRTTY=1 kitty

The Effect trait

pub trait Effect: Send + 'static {
    /// GLSL 330 core fragment shader.
    /// Receives `in vec2 v_uv` and `uniform sampler2D u_input`.
    /// Must write `out vec4 o_color`.
    fn fragment_shader(&self) -> &str;

    /// Called once after shader compilation. Cache uniform locations here.
    fn setup(&mut self, _program: u32) {}

    /// Called each frame. Set your custom uniforms.
    fn set_uniforms(&self, _program: u32, _w: i32, _h: i32, _frame: u64) {}

    /// Per-frame toggle. Default: true.
    fn enabled(&self) -> bool { true }

    /// Env var that must be "1" to activate. Default: "ENABLE_CRTTY".
    fn env_var(&self) -> Option<&str> { Some("ENABLE_CRTTY") }
}

Helpers for setting uniforms:

crtty::gl::get_uniform_location(program, "my_param")  // -> i32
crtty::gl::uniform_1f(location, 0.5)
crtty::gl::uniform_1i(location, 1)

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

Per-app configs:

  • ~/.config/crtty/kitty.conf
  • ~/.config/crtty/alacritty.conf

If you already have a legacy ~/.config/crtty.conf, CRTty automatically migrates it to ~/.config/crtty/kitty.conf on first run.

Manual migration command:

crtty --migrate-config
crtty --app alacritty --migrate-config

Example config:

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

About

Post-processing shader framework for kitty/alacritty terminal via LD_PRELOAD

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •