Skip to content
 
 

Latest commit

 

History

3,281 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

niri

A scrollable-tiling Wayland compositor.

Matrix GitHub License GitHub Release

Getting Started | Configuration | Setup Showcase

niri with a few windows open

Important

This is biri, a custom fork of niri.

It tracks upstream niri and adds a set of extra features on top: vertical scrolling for portrait outputs, GPU post-process shaders (global, per-region, and per-window), file-based animated focus-ring shaders, a consolidated multi-monitor carousel overview, dynamic overview zoom presets, isolated "signage" outputs, and runtime touchpad/DWT toggles. See Fork Features below for the full list.

Everything documented for upstream niri still applies. Bugs you hit here should be reported to this fork, not to upstream niri.

Warning

Branch rename (2026-09-03). The fork's working branch is now main (formerly barrulus-custom), and the pristine upstream mirror is now upstream (formerly main). Open pull requests against main. If you track this repo, update flake inputs, package scripts, and clones from barrulus/biri/barrulus-custom to barrulus/biri/main. The old barrulus-custom branch has been removed.

biri-demo-480p.mp4

Fork Features

These exist only in biri, not in upstream niri. Unless noted, each is off by default and inert when unconfigured.

A standard install keeps the upstream command names, including niri and niri-session, and reads ~/.config/niri/config.kdl ($XDG_CONFIG_HOME/niri/config.kdl if set). The examples below use that default location. A different config path must be selected explicitly with niri -c /path/to/config.kdl or NIRI_CONFIG.

Vertical scrolling for portrait outputs

Use main-axis "vertical" to arrange windows in rows that scroll top-to-bottom. Configure it per output to keep a portrait monitor scrolling vertically alongside a landscape monitor scrolling horizontally:

output "DP-2" {
    layout {
        main-axis "vertical"
        default-column-width { proportion 0.5; }
    }
}

Replace DP-2 with your output name. In vertical mode, default-column-width sets row height; proportion 0.5 fits two rows in the visible area. Window content stays upright, and directional focus and move shortcuts keep their screen directions.

You can also set the axis globally or per named workspace, and changes apply on config reload. See Layout: main-axis for sizing, gestures and workspace behavior.

Post-process shaders

A GLSL fragment shader pipeline layered on top of niri's rendering, in three scopes that can all be active at once:

  • global-shader — a full-screen post-process pass over the whole composited output: colour grading, CRT scanlines, night-light tints, motion-blur trails, and so on. TTY/DRM backend only.
  • region-shader — the same shader contract scoped to a fixed screen rectangle, optionally pinned to one output. Repeatable.
  • shader {} in a window-rule — a shader applied to a single window's content, with borders and shadows rendered outside it. Animated, and only redrawn while the window is actually visible.

Supporting machinery:

  • Named window-shaders presets driven by the toggle-window-shader and cycle-window-shader binds: flip the focused window's shader off/on, or rotate it through your presets at runtime — no config editing or reload needed.

  • Per-output colour filters: output "eDP-1" { shader { preset "grayscale"; }; } — built-in grayscale, invert, saturation and temperature filters with no GLSL to write, plus toggle-output-shader and cycle-output-shader binds. Answers upstream niri #4355, #4303 and #4405.

  • Two API flavours: a native niri mode and a hyprland mode that accepts most Hyprland screen_shader files with light edits.

  • Multi-pass chains via repeatable pass {} blocks, where each pass reads the previous pass's output.

  • A previous-frame feedback buffer (niri_prev / tex2D_prev) plus a dedicated global_buffer pass for trails and accumulation effects.

  • Redraw scheduling (redraw "auto" | "on-damage" | "continuous") so a static shader doesn't force a continuous redraw loop, and cursor-radius to reshade only a box around the cursor and keep the rest of the output scanout-eligible.

  • shader-animation-max-fps to cap shader-driven redraws independently of the output refresh rate.

  • shaders-in-capture (top-level flag) to opt shader output into portal screencasts and screencopy; by default shaders stay a local display effect and never leak into shared or recorded content.

  • Hot-reload on config reload; a shader that fails to compile logs a warning and leaves the screen rendering normally.

Note the cost: an active global shader disables direct scanout and redraws the whole output every frame. The shader documentation covers this in detail.

Custom focus-ring shaders

Load a GLSL file for a focus ring or border, with a different effect for each application. Shader files reload automatically when saved; adding or changing an effect needs no compositor rebuild.

Copy resources/shaders/focus-ring/ to ~/.config/niri/focus-ring/, then add a window rule:

window-rule {
    match app-id=r#"^com\.mitchellh\.ghostty$"#
    focus-ring {
        on
        width 6
        shader {
            path "~/.config/niri/focus-ring/rainbow-ripple.frag"
            padding 14
        }
    }
}

The supplied rainbow shader has flowing pastel colours, uneven wax-like edges, and drifting highlights. Edit its .frag file to change the material, or point another window rule at pulse.frag or your own shader. Rings stay hollow behind transparent windows; extra drawing space from padding does not change window sizes. The same shader block works in layout { focus-ring { ... } } and in borders.

For light spilling onto the focused window and nearby windows, add light inside the existing shader block. It follows the shader's actual bright spots, including the travelling pulse in lightning.frag:

shader {
    path "~/.config/niri/focus-ring/lightning.frag"
    padding 24
    light spread=80 intensity=1.0 threshold=0.5
}

spread controls the glow's reach, intensity its brightness, and threshold excludes dim parts of the ring. This is a soft screen-space lighting effect; shader files and lighting settings remain editable without rebuilding.

Animation respects shader-animation-max-fps, and static shaders can use animated false. Invalid shaders log an error and fall back to configured colours. See Custom focus-ring and border shaders for the shader contract, reload behaviour, and examples. The original rainbow-ripple configuration remains supported.

Consolidated carousel overview

overview { consolidated-carousel { ... } } replaces the per-monitor overview on multi-monitor setups with a single-screen, cover-flow style browser. Zooming one output's overview out past reveal-zoom continuously reveals the other outputs as perspective panels receding to the sides, fully assembled by assembled-zoom — no snapping or thresholds.

Rotate the ring with the normal focus-column-left/right binds, Shift+scroll, or by clicking a side panel; any of these work at any zoom level, pulling out to show the ring and then returning to where you were. Rotating a sibling output into the centre gives you its live, interactive workspace strip ("the lens"), and clicking a window there — or pressing Enter — jumps focus straight to that window on its real output.

See Configuration: Miscellaneous.

Dynamic overview zoom

overview { zoom-presets 0.5 0.25 0.1 } plus three new actions — overview-zoom-in, overview-zoom-out, and overview-zoom-cycle — let you move between zoom levels while in the Overview. The zoom actions also open and close the overview at the ends of the range, so two binds (typically Mod+scroll) cover the whole interaction. Transitions are animated via the new overview-zoom animation.

Isolated outputs

An isolated flag on an output keeps compositor UI — the overview, the Alt-Tab switcher, the hotkey overlay, and the config error notification — off that screen, for projection, digital signage, shop-front visuals, or a clean capture feed. Windows and layer-shell surfaces still render normally. power-off-monitors skip-isolated=true leaves isolated outputs lit while the rest sleep on idle.

Runtime input toggles

Both compose with the config settings (config and toggle must agree), reset on restart, and apply to newly hot-plugged touchpads.

Fast-tracked upstream PRs

Open niri pull requests merged here ahead of upstream, several of them originally combined in niri-qol (now absorbed into this fork):

  • Hidden workspaces (niri#2997) — a named workspace can be hidden: it keeps its windows but disappears from the workspace strip, the overview, and workspace switching until toggled back. Declare it hidden at startup with workspace "name" { hidden true }, or change it at runtime with the toggle-workspace-visibility, hide-workspace and unhide-workspace actions (binds or niri msg action; add focus=true to jump to the workspace as it appears). Hidden workspaces also stay out of the consolidated carousel's panels.
  • Sticky floating windows (niri#3302) — floating windows that follow you across all workspaces of their output. Set open-sticky true in a window rule (implies open-floating), or toggle any floating window with the toggle-window-sticky bind. In the consolidated carousel, sticky windows show on every workspace panel, and clicking one in the lens focuses it.
  • Virtual outputs (niri#3800) — outputs that exist without a physical monitor, for a headless session over SSH, a Sunshine/Moonlight streaming target, a tablet used as a second screen, or a wayvnc target. Create and remove them at runtime with niri msg output, or declare them in config with output "name" { create-virtual ... }. Works on both the TTY and headless backends. See Virtual Outputs — one used for streaming usually wants the isolated flag too.
  • float-above-fullscreen (niri#4062) — a window rule that keeps a floating window visible on top when a fullscreen window occupies the workspace. Off by default.
  • Per-keyboard configuration (niri#4459) — give a keyboard block a device name to configure one physical keyboard: keyboard "Logitech USB Receiver" { xkb { layout "gb" } } next to the unnamed block. A named block inherits anything it leaves unset from the unnamed one, and the keymap follows whichever keyboard you last typed on, so an external board can run a different layout or options than the built-in one without a switching script. Num lock, locale1 settings and the IPC layout event survive the switch (fixed on top of the upstream PR). See Per-Keyboard Configuration.
workspace "scratch" {
    hidden true
}

window-rule {
    match app-id="firefox$" title="^Picture-in-Picture$"
    open-sticky true
    float-above-fullscreen true
}

binds {
    Mod+H { toggle-workspace-visibility "scratch"; }
    Mod+S { toggle-window-sticky; }
}

If these land upstream, the upstream versions replace them here.

About

By default, windows are arranged in columns on an infinite strip going to the right. The fork's vertical layout arranges them in rows on a strip going down instead. Opening a new window never causes existing windows to resize.

Every monitor has its own separate window strip. Windows can never "overflow" onto an adjacent monitor.

Workspaces are dynamic and arranged vertically by default, or horizontally when viewing a vertical layout. Every monitor has an independent set of workspaces, and there's always one empty workspace at the end.

The workspace arrangement is preserved across disconnecting and connecting monitors where it makes sense. When a monitor disconnects, its workspaces will move to another monitor, but upon reconnection they will move back to the original monitor.

Features

Video Demo

demo.mp4

Also check out these videos that showcase a lot of the niri functionality:

Status

Niri is stable for day-to-day use and does most things expected of a Wayland compositor. Many people are daily-driving niri, and are happy to help in our Matrix channel.

Give it a try! Follow the instructions on the Getting Started page. Grab a desktop shell like DankMaterialShell or Noctalia (or build a more traditional setup): niri by itself is not a complete desktop environment. Also check out awesome-niri, a list of niri-related links and projects.

Here are some points you may have questions about:

  • Multi-monitor: yes, a core part of the design from the very start. Mixed DPI works.
  • Fractional scaling: yes, plus all niri UI stays pixel-perfect.
  • NVIDIA: seems to work fine.
  • Floating windows: yes, starting from niri 25.01.
  • Input devices: niri supports tablets, touchpads, and touchscreens. You can map the tablet to a specific monitor, or use OpenTabletDriver. We have touchpad gestures, but no touchscreen gestures yet.
  • Wlr protocols: yes, we have most of the important ones like layer-shell, gamma-control, screencopy. You can check on wayland.app at the bottom of each protocol's page.
  • Performance: while I run niri on beefy machines, I try to stay conscious of performance. I've seen someone use it fine on an Eee PC 900 from 2008, of all things.
  • Xwayland: integrated via xwayland-satellite starting from niri 25.08.

Media

niri: Making a Wayland compositor in Rust · December 2024

My talk from the 2024 Moscow RustCon about niri, and how I do randomized property testing and profiling, and measure input latency. The talk is in Russian, but I prepared full English subtitles that you can find in YouTube's subtitle language selector.

An interview with Ivan, the developer behind Niri · June 2025

An interview by a German tech podcast Das Triumvirat (in English). We talk about niri development and history, and my experience building and maintaining niri.

A tour of the niri scrolling-tiling Wayland compositor · July 2025

An LWN article with a nice overview and introduction to niri.

Contributing

If you'd like to help with niri, there are plenty of both coding- and non-coding-related ways to do so. See CONTRIBUTING.md for an overview.

For the fork-specific features listed above, open issues and pull requests against this repository rather than upstream niri.

Inspiration

Niri is heavily inspired by PaperWM which implements scrollable tiling on top of GNOME Shell.

One of the reasons that prompted me to try writing my own compositor is being able to properly separate the monitors. Being a GNOME Shell extension, PaperWM has to work against Shell's global window coordinate space to prevent windows from overflowing.

Tile Scrollably Elsewhere

Here are some other projects which implement a similar workflow:

Contact

Our main communication channel is a Matrix chat, feel free to join and ask a question: https://matrix.to/#/#niri:matrix.org

We also have a community Discord server: https://discord.gg/vT8Sfjy7sx

About

A scrollable-tiling Wayland compositor.

Resources

Contributing

Stars

46 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages