Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Display Scanlines

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.

Display Scanlines main preview

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.

Presets

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

Additional previews

CRT scanlines across a terminal music visualizer

Display Scanlines panel across the full Omarchy desktop

Why the scanlines stay even

The effect is calculated from physical output pixels rather than logical coordinates:

  1. 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.
  2. gl_FragCoord maps 1:1 to physical pixels.
  3. 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.

Image-quality details

  • 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 time uniform, flicker, or rolling animation, so Hyprland damage tracking and idle efficiency remain effective.

Requirements

  • Omarchy Quattro
  • Hyprland with Lua configuration and decoration.screen_shader support
  • 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

Install and enable the plugin:

omarchy plugin add https://github.com/TyRichards/omarchy-display-scanlines.git --enable

Disable the stock Display widget to avoid showing two display panels:

omarchy plugin disable omarchy.monitor

If needed, place Display Scanlines in the right bar section:

omarchy bar move io.github.tyrichards.display-scanlines --section right

Use

Use 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 status

Shell 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 state

Optional keybinding

SUPER + 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.

Persistence and generated files

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.

Remove

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 right

Validate

omarchy 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

Implementation notes

  • GLSL preprocessor conditions use integer USE_* flags. Floating-point literals in #if are invalid and can silently select the wrong branch on some drivers.
  • scanlinePitch() exists in both GLSL and Model.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_GAP controls how much source light remains visible through the equal-width dark half.
  • FOUR_K_SOFTNESS and FOUR_K_NOTCH apply only when the physical framebuffer is at least 3000×1800, leaving the 2256×1504 Framework render path untouched.

License

MIT. The Display panel and model were derived from Omarchy’s MIT-licensed native Display widget. See LICENSE.

About

Resolution-aware CRT scanline presets for Omarchy's Display panel

Topics

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages