Two weeks ago, Cloudflare had a major outage that took down a good half of the internet. Their postmortem included a snippet of Rust code with an unwrap(), causing much internet discourse. Now it is my turn to release Rust software with a healthy dose of unwrap()s, assert!s, and checked arithmetic enabled in release builds.
Niri is a scrollable-tiling Wayland compositor. Windows are arranged in columns on an infinite strip going to the right. Opening a new window never causes existing windows to resize.
Here are the improvements from the last release.
Note
Packagers: niri now depends on libdisplay-info 0.3. Most niri packages updated this dependency back in September using a patch—this patch is no longer necessary.
Alt-Tab
After multiple major iterations and a lot of work by @rustn00b, we have an Alt-Tab recent windows switcher!
niri-alt-tab.mp4
It's got live window previews, window titles that fade if they're too long, and supports windows blocked out from screencasts—they will draw as black rectangles with their titles hidden. Just like in GNOME, there's a small delay to drawing the interface, so that quickly hitting Alt-Tab will switch to the previous window with no visual disturbance.
There are some interesting design differences compared to Alt-Tab in other desktops. On niri, I expect it's common to have multiple terminals open, so Alt-Tab must go by windows (not by apps), and the previews must be big enough for you to be able to pick the right window. So, instead of a panel showing all windows at once, we have a scrolling layout with a few large previews.
Then, since niri is primarily tiling, you might frequently focus intermediate windows while navigating the workspace. Think pressing Super→ a few times to reach some window towards the end, or moving the mouse across the screen with focus-follows-mouse. These intermediate windows shouldn't "pollute" the Alt-Tab list, so we have a small debounce delay before a focused window is marked as recent.
Workspaces in niri can get long (especially if you forget how many terminals you open), so our Alt-Tab can scope windows by current workspace or by current output by pressing W or O.
The default binds are AltTab / ModTab to switch across all windows, and Alt` / Mod` to switch between windows of the current application. Note that if you bound something else to those keys, your existing binds will take precedence.
The recent windows configuration wiki page explains how to change these key bindings, as well as everything else: open delay, debounce, window preview size, and so on.
Finally, the niri IPC now exposes the window focus_timestamp and an event stream event, so you can use the recent windows list in your own desktop components and scripts.
Un/fullscreen animations
The transitions for windows going to and from fullscreen are now fully animated. No more windows jumping and growing to fullscreen size in a single frame.
The black backdrop fades in and out and grows if needed, and clip-to-geometry rounded corners smoothly transition between square and circular during this animation.
fullscreen.anim.on.alacritty.mp4
fullscreen.anim.on.dialog.mp4
Even if you run with animations disabled, you will see an improvement: windows will no longer jump up and down when un/fullscreening.
fullscreen.anim.off.mp4
True maximize
One of the bigger changes in this release that required several weeks of work: niri now supports the true Wayland maximize. This is the normal "maximize button next to the X button" or "double-click on the titlebar" maximize.
niri-maximize.mp4
We historically didn't implement true Wayland maximize because it's very similar to, yet slightly different from, our full-width columns (the maximize-column bind). These preserve gaps, borders and struts, and can contain multiple windows. The true Wayland maximize, on the other hand, makes a single window occupy the entire working area, with no gaps or struts.
| Full-width column | Maximized window |
|---|---|
|
|
After plenty of requests, and thinking about it, I reconsidered. Double-click-to-maximize and the maximize button are things that users (especially new users) expect to work; additionally, plenty of people had asked for variations of "maximize without borders and gaps".
Ironically, after implementing true maximize, it has by far become my favorite window sizing mode. I keep windows true-maximized all the time: the browser, code editor, terminal ssh'd into a remote server.
Since the maximize-column and open-maximized names are taken by full-width columns, the true-maximize action is called maximize-window-to-edges. This name reflects what it does—maximize to the entire working area, right up to the screen edges or exclusive layer-shell surfaces.
Similarly to fullscreen, true maximize always puts the window into the scrolling layout (since otherwise it would easily obscure all windows below). Naturally, you can still scroll left and right from a maximized window.
You can read more about fullscreen and maximize on the new wiki page.
Drag windows horizontally to scroll
On a trackpad, you can swipe left and right with three fingers to scroll the view. With a mouse, however, you have to either do a SuperMMB drag, which requires reaching for the keyboard, or switch windows via the Overview hot corner, which causes a lot of movement on screen.
To make scrolling the view mouse-only less annoying, I added a new behavior directly inspired by PaperWM: dragging tiled windows by their titlebar horizontally will scroll the view instead of moving the window. To move the window, you can still drag vertically (or with Super, or in the Overview).
niri-horizontal-scroll-mouse.mp4
This also works on touchscreens, finally adding an easy way to scroll the view by touch. I opted to make the touchscreen gesture work even when holding Super, since with touch there's no concern of quickly moving windows across monitors, like you sometimes need to do with a mouse.
niri-horizontal-scroll-touch.mp4
As a bonus, tapping with a second finger while moving a window now switches it between floating and tiling, just like pressing the right mouse button does.
Per-output and per-workspace layout config
You can now put layout {} sections inside output {} and workspace "name" {} to override layout settings for specific outputs and named workspaces.
output "SomeCompany VerticalMonitor 1234" {
transform "90"
// Layout config overrides just for this output.
layout {
default-column-width { proportion 1.0; }
// ...any other setting.
}
}
output "SomeCompany UltrawideMonitor 1234" {
// Narrower proportions and more presets for an ultrawide.
layout {
default-column-width { proportion 0.25; }
preset-column-widths {
proportion 0.2
proportion 0.25
proportion 0.5
proportion 0.75
proportion 0.8
}
}
}
workspace "aesthetic" {
// Layout config overrides just for this named workspace.
layout {
gaps 32
struts {
left 64
right 64
bottom 64
top 64
}
border {
on
width 4
}
// ...any other setting.
}
}While simple on the surface, this feature required a complete overhaul of niri's config loading and parsing to support merging settings from multiple definitions of the layout {} section. As it happens, this was an important prerequisite for...
Config includes
You can now include other files in your niri config.
// Some settings...
include "colors.kdl"
// Some more settings...Included files have the same structure as the main config file. Settings from included files are merged with the settings from the main config. Includes are positional: they will override options set prior to them in the file.
Includes are useful for splitting your config into manageable chunks, and they make it easy for third-party tools to robustly inject niri configuration. For example, my top-level niri config currently looks like this:
// Configuration split into files by sections.
include "debug.kdl"
include "input.kdl"
include "outputs.kdl"
include "workspaces.kdl"
include "layout.kdl"
include "animations.kdl"
include "misc.kdl"
include "binds.kdl"
// Autogenerated settings from DankMaterialShell (DMS).
// https://github.com/AvengeMedia/DankMaterialShell
include "dms/colors.kdl"
include "dms/wpblur.kdl"
// These includes are last, they will override settings from DMS.
include "decorations.kdl"
include "rules.kdl"See the config includes wiki page for more details, for instance how different config sections are merged together.
DisplayLink support
Thanks to @cmeissl and @scottmckendry, niri now supports monitors connected through DisplayLink docks. This is especially important for Asahi Macs, which currently don't support any other way of connecting external displays.
Reduced screen blanking
In this release, I finally adapted the logic from cosmic-comp to avoid screen blanking when possible. If your login manager / TTY resolution and refresh rate match the ones in niri, your screen should switch to niri without flickering to black. This works both when starting niri and when switching the TTY back to a running niri session.
niri-flickerfree-login.mp4
niri-flickerfree-tty.mp4
Even if the screen does blank on your setup (common for high refresh rate monitors where the TTY tends to use a 60 Hz mode, since that's usually the monitor-preferred one), niri should start up faster, as it will do just one modeset instead of two.
Custom output modes
@ToxicMushroom, building upon the initial work of @cmeissl, implemented custom modes and modelines in niri. When configuring an output mode, you can set custom=true to force the use of a given mode, even if it's not advertised by the monitor. Alternatively, you can provide a full custom modeline.
Custom modes can be useful when the monitor's EDID doesn't advertise the right mode for some reason. For example, some monitors can use a lower refresh rate than any of the advertised modes, which can be useful for reduced power consumption.
Keep in mind that custom modes are not guaranteed to work. Niri is asking the monitor to run in a mode that is not supported by the manufacturer. Trying a non-working mode will generally turn the monitor blank.
Caution
Custom modes may damage your monitor, especially if it's a CRT. Follow the maximum supported limits in your monitor's instructions.
output "eDP-1" {
// Custom modes. Caution: may damage your display.
mode custom=true "1920x1080@100"
// Or:
// modeline 173.00 1920 2048 2248 2576 1080 1083 1088 1120 "-hsync" "+vsync"
}Screen reader enhancements
Thanks to an issue report, I found out that niri wasn't entirely correctly signalling keyboard modifiers to screen readers. This has been fixed, so combos like Orca + Ctrl + Space and Orca + Shift + A now work on niri.
The new Alt-Tab switcher should also be a nice addition here; from what I understand, it's a common navigation method for screen reader users. I made it speak the selected window title, which, if I'm not mistaken, is the expected behavior. Watch with sound:
niri-alt-tab-orca.mp4
Other improvements in this release
- @Fireye04 and @Aadniz made the hot corners configurable, including per-output.
- @ilyx-me made it possible to run windowed
niri --sessionin WSL. - @shaunren added the
ignore-drm-devicedebug option that prevents niri from touching the given DRM device. This is useful for GPU passthrough when you need exclusive access to a certain device. - @Szybet made the
calibration-matrixinput setting work for touchscreens. - @nenikitov added proportional change support (
+10%,-10%,10%) to themove-floating-windowaction. - @iynaix added a
--pathargument to thescreenshot,screenshot-windowandscreenshot-screenactions that sets the path where niri will write the screenshot. - @ThatOneCalculator added a new
ScreenshotCapturedIPC event that fires whenever niri captures a screenshot, with the output file path. - The default config now includes play/stop/prev/next media key binds (thanks @anagram3k) and limits the volume adjustment to 100% (thanks @whiskeyPeak).
- Niri will now retry adding a DRM device if it previously failed. This fixes some cases where plugging a monitor into a laptop's port connected to the discrete GPU didn't work.
- Added support for the panel orientation DRM property, used to set the default screen orientation on some devices.
- Niri no longer sets the max bpc DRM property. It didn't really do any help, at the same time it started hitting an annoying driver bug on some OLED panels. The
keep-max-bpc-unchangeddebug flag is now deprecated and does nothing. - Narrowed the interactive move insert hint in the overview between workspaces. When it was full-width, it gave a false impression that the window would be maximized on the new workspace.
- @ArijanJ made it so when the cursor is hidden via
hide-when-typingorhide-after-inactive-ms, it will still show up in the screenshot UI, letting you optionally include it in the screenshot. - Added laptop lid state monitoring through logind. This should fix any issues where niri didn't notice the laptop lid opening after a sleep cycle (due to libinput dropping the event), which then caused the laptop display to erroneously turn off upon connecting an external monitor.
- Niri will now match the
default-column-widthto a preset width when opening a window. This fixes the first ModR press (switch preset column width) "doing nothing" for certain windows, like foot in constrain-to-terminal-grid sizing mode. - @miku4k changed niri to create the screenshot file directory with parent directories if they don't exist. This makes saving screenshots to disk work out of the box in more cases.
- @elivance fixed parsing of the case-sensitive
XF86ScreenSaverkey. Niri parses keys case-insensitively. Unfortunately, there's a collidingXF86_Screensaverkey (note the smalls) which is not the key that you generally want. Now, for this specific key, niri also checks for a case-sensitive match to disambiguate. - @valpackett fixed niri building with the systemd feature on musl libc systems.
- @feschber fixed discrete scroll event speed in the virtual-pointer protocol.
- Fixed inability to override border/focus-ring/tab-indicator gradient with a plain color in window rules.
- Fixed window rule border/focus-ring width not getting rounded to physical pixels, leading to small animation jank.
- Fixed drag-and-drop cursor surface being slightly offset.
- Fixed a small jump when releasing an interactively moved window in the overview.
- Fixed the hot corner triggering during the view scrolling gesture.
- Fixed incorrect alpha handling in the layout
background-color. - Removed Herobrine.
- Updated Smithay:
- Improved correctness of window commit tracking.
- Fixed Qt layer-shell popup grabs. Popup menus from LXQt panel and desktop now correctly grab the keyboard focus, and can be closed by clicking outside.
- Fixed visual ordering of popups when several open at once, for example popup + tooltip in GTK 4, or some popups in xwayland-satellite.
- Fixed importing DMA-BUFs from v4l2 devices.
- Fixed a possible integer overflow in the damage shaper, which could cause niri crashes with some misbehaving clients.
- Added a tablet pressure workaround that improves behavior for some applications like wine and MyPaint.
- Added support for layer-shell v5 with its
set_exclusive_edge()request, and changed layer surface ordering to consider exclusive zone surfaces first.
Funding
I work on niri in the spare time that I have from my university studies. If you like what I do, you can support my work on GitHub Sponsors. Big thanks to all current and past sponsors!


