Omarchy’s Display panel with resolution-aware CRT scanline presets. It preserves the stock brightness, text-size, display-scale, and monitor controls, then adds a CRT Scanlines power toggle with Light and Heavy presets.
Screenshot disclaimer: The scanlines look better in person than they do in compressed or browser-scaled screenshots, which can soften the effect and introduce moiré.
The root preview is the original lossless 1280×913 PNG, displayed responsively at full README width with no destructive resampling or JPEG conversion.
| Preset | Character |
|---|---|
| Light | Subtle, all-day scanlines with no warmth |
| Heavy | Darker gaps, warmer phosphors, deeper vignette, and stronger saturation |
The power toggle unloads the shader without changing the selected preset. Light or Heavy can also be selected while power is off, and that choice is restored the next time the effect is enabled. When switching off, the plugin first presents a neutral passthrough shader for multiple full frames, then unloads the visually identical pipeline. Each handoff also triggers Hyprland’s full monitor-damage path—not only its screen-shader refresh—so wallpaper and layer surfaces such as the top bar cannot alternate stale buffers. Once Off, no shader remains loaded.
Both presets use the same scanline pitch and CONTRAST 0.34. They differ in beam profile, color, warmth, vignette, saturation, and the strength of 4K phosphor texture—not line size or midtone contrast.
Everything from Omarchy’s stock Display panel continues to work:
- Display brightness
- Shell and GTK text size
- Display scale presets
- Multi-monitor enable/disable controls
- Scroll-wheel brightness adjustment and OSD
The effect is calculated from physical output pixels rather than logical coordinates:
textureSize(tex, 0)returns the physical framebuffer size for each output. A 2256×1504 display at 1.6× scale reports 2256×1504—not 1410×940.gl_FragCoordmaps 1:1 to physical pixels.- Beam phase uses integer modulo:
int(gl_FragCoord.y) % pitch.
Typical sine/UV scanlines can accumulate phase error and produce drifting line thickness. Here, every scanline cell is generated by identical integer arithmetic. Only symmetric 2px and 4px cells are used—never the asymmetric 3px pattern that made 4K alternate between one-row and two-row bands. Every cell is physically 50% lit / 50% dark: one row per half at 2px, or two rows per half at 4px.
| Output height | Pitch | Scanlines |
|---|---|---|
| 720 | 4px | 180 |
| 900 | 4px | 225 |
| 1000 | 4px | 250 |
| 1080 | 2px | 540 |
| 1504 | 2px | 752 |
| 1600 | 2px | 800 |
| 1920 (native portrait) | 4px | 480 |
| 2160 (4K) | 4px | 540 |
| 2880 | 4px | 720 |
| 4320 (8K) | 4px | 1080 |
The shader resolves output dimensions independently on each render pass, so different monitors, scales, hotplug events, and mode changes require no shader regeneration. A native 1000×1920 portrait framebuffer receives the visible 4px high-density Light profile. Hyprland applies screen shaders before output rotation, however, so a landscape framebuffer rotated into portrait also rotates the scanline axis; Hyprland currently exposes no monitor-transform uniform to custom screen shaders.
- Linear-light processing: samples use the exact piecewise sRGB transfer before beam and color calculations.
- Color-stable scanlines: no per-column RGB aperture mask, avoiding colored box ghosts on LCD subpixels, fractionally scaled UIs, browsers, and screenshots.
- 4K phosphor notches: 4K-class landscape outputs add a subtle, monochrome three-column ramp for incremental left-to-right texture without altering source hue.
- Damage-safe 4K softness: a gentle phosphor response softens antialiased text edges without spatial blur, glow, or neighboring texture reads.
- Damage-safe sampling: every output pixel reads only its matching source pixel, so moving UI cannot expose stale/background-colored boxes across Hyprland damage rectangles.
- Resolution-independent 50/50 bands: every 2px cell is one lit plus one dark row; every 4px cell is two lit plus two translucent-dark rows.
- Analytic brightness compensation: average scanline-beam energy is normalized instead of using an arbitrary brightness fudge factor.
- Brightness-dependent beam width: highlights use a wider beam, like a real CRT.
- Clean zero-cost Off: power transitions repaint the complete output through a neutral handoff before genuinely unloading the shader, preventing layer/background strobe without leaving an identity shader running.
- Static shader: no
timeuniform, flicker, or rolling animation, so Hyprland damage tracking and idle efficiency remain effective.
- Omarchy Quattro
- Hyprland with Lua configuration and
decoration.screen_shadersupport - No external packages, elevated privileges, or install hooks
The plugin invokes only Omarchy/Hyprland commands already present on an Omarchy system. Power and preset state are stored beneath ~/.local/state/omarchy/; the plugin does not overwrite user configuration.
Install and enable the plugin:
omarchy plugin add https://github.com/TyRichards/omarchy-display-scanlines.git --enableDisable the stock Display widget to avoid showing two display panels:
omarchy plugin disable omarchy.monitorIf needed, place Display Scanlines in the right bar section:
omarchy bar move io.github.tyrichards.display-scanlines --section rightUse the switch at the bottom of the Display panel to turn scanlines on or off. Click Light or Heavy to select the preset independently; the selected button remains active while power is off.
The bundled CLI can also control power and preset separately:
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines on
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines off
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines toggle
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines light
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines heavy
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines statusShell IPC is available as well:
omarchy-shell display-scanlines crt toggle
omarchy-shell display-scanlines crt light
omarchy-shell display-scanlines crt heavy
omarchy-shell display-scanlines stateSUPER + CTRL + MINUS makes a fitting power shortcut—the minus key is the scanline. It toggles the effect without changing the saved Light/Heavy preset:
-- This chord is declared upstream as code:20, so unbind that exact form.
hl.unbind("SUPER + CTRL + code:20")
o.bind("SUPER + CTRL + code:20", "Toggle CRT scanlines", "omarchy-shell -q display-scanlines crt toggle")This replaces Omarchy’s default Expand window left a lot binding.
The selected Light/Heavy preset is saved independently of power in:
~/.local/state/omarchy/display-scanlines/preset
While enabled, login and hyprctl reload persistence is provided by:
~/.local/state/omarchy/toggles/hypr/zz-display-scanlines.lua
Turning power off removes that toggle module but keeps the preset file. Generated shaders also live beneath ~/.local/state/omarchy/display-scanlines/.
The two presets share one source body, shaders/crt.body.glsl. The CLI prepends CRT_LEVEL and rebuilds the selected generated shader whenever needed. Do not edit generated files.
Turn the effect off, remove the plugin, and restore Omarchy’s stock Display widget:
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/bin/display-scanlines off
omarchy plugin remove io.github.tyrichards.display-scanlines --yes
omarchy plugin enable omarchy.monitor rightomarchy plugin validate ~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines
qmllint -I "$OMARCHY_PATH/shell" \
~/.config/omarchy/plugins/io.github.tyrichards.display-scanlines/Panel.qml- GLSL preprocessor conditions use integer
USE_*flags. Floating-point literals in#ifare invalid and can silently select the wrong branch on some drivers. scanlinePitch()exists in both GLSL andModel.js; the shader is authoritative, while JavaScript supplies the panel readout.- The Heavy preset’s Framework-sized 2px gaps are controlled primarily by
BEAM_MAX. In every 4px profile,WIDE_GAPcontrols how much source light remains visible through the equal-width dark half. FOUR_K_SOFTNESSandFOUR_K_NOTCHapply only when the physical framebuffer is at least 3000×1800, leaving the 2256×1504 Framework render path untouched.
MIT. The Display panel and model were derived from Omarchy’s MIT-licensed native Display widget. See LICENSE.


