• v26.04 8ed0da44d9

    Stable

    andrei released this 2026-04-25 18:20:03 +07:00 | 36 commits to main since this release

    📅 Originally published on GitHub: Sat, 25 Apr 2026 13:50:41 GMT
    🏷️ Git tag created: Sat, 25 Apr 2026 11:20:03 GMT

    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.

    As you may have noticed, niri now lives in a GitHub org rather than my (@YaLTeR) personal account.

    The primary reason was the ability to give out issue triage permissions: I'd like to give a massive thanks to @Sempyos for triaging all of our issues and pull requests, answering many, many questions, and helping people diagnose their problems with niri.

    We've also moved a few niri-adjacent projects to the GitHub org, like the awesome-niri list of related projects maintained by @Vortriz and a new artwork repo by @bluelinden and @HumpityDumpityDumber—two of the creators of our project logo. In the artwork repo, you can find a badge and several wallpapers, including two stunning 3D works created by @Duncan-Rose in Blender:

    pool cut

    The main niri repo also flew past 20,000 stars in February! 🌟 Thanks everyone for support.

    Note

    Packagers:

    Now with introductions out of the way, here are the improvements from the last release.

    image

    Blur

    It's here. The most requested niri feature by far. Our highest upvoted issue on GitHub. After tireless fork maintenance by @visualglitch91 and @Naxdy, blur is in mainline niri for everyone to use!

    image

    Windows and layer-shell components can request blur through the ext-background-effect Wayland protocol with no extra niri configuration. Many already do:

    Toolkits:

    For apps that don't support ext-background-effect yet, you can enable blur through the niri config:

    // Enable blur behind the Alacritty terminal.
    window-rule {
        match app-id="^Alacritty$"
    
        background-effect {
            blur true
        }
    }
    
    // Enable blur behind the fuzzel launcher.
    layer-rule {
        match namespace="^launcher$"
    
        background-effect {
            blur true
        }
    }
    

    Keep in mind that niri-configured blur needs the right geometry-corner-radius, and it won't work with complex surface shapes. See the Window Effects wiki page for details.

    image
    Have I seen this screenshot before?..

    We have both normal blur and xray blur that always shows the wallpaper. Xray blur is the default because it's much more efficient: niri computes the blurred wallpaper once, and then reuses it as a static image, which is extremely cheap. Only if the wallpaper changes, the blur is recomputed (so an animated background will shrink the efficiency gains).

    If you prefer non-xray (normal) blur, you can enable it with a window/layer rule. For example, you can set it on top and overlay layers (that usually overlap other content), via the new layer matcher:

    // Make top and overlay layers use the regular blur (if enabled),
    // while bottom and background layers keep using the efficient xray blur.
    layer-rule {
        match layer="top"
        match layer="overlay"
    
        background-effect {
            xray false
        }
    }
    
    image

    So, if blur is so good, where's blur 2? Err, I mean, why did it take so long to add?

    In short, background blur turned out to be a massive undertaking. Not because of the blur algorithm itself (by the way, if you want to learn about different blurs, including the widely used Dual Kawase, I highly recommend this blog post), but because window background effects in general required a lot of thinking and additions to the code, especially to make them as efficient as possible. This is one of the most complex niri features thus far.

    Xray and non-xray effects are also pretty much two entirely separate and very different beasts, code-wise. Non-xray reads back the just-rendered pixels in the middle of a frame, blurs them, then continues drawing the frame. This required extensive refactors of Smithay's rendering architecture (big thanks to @Drakulix!). Xray on the other hand requires threading the window positions all throughout the rendering code to draw the right cut-out of the background.

    But it gets worse: we have our Overview. It was quite a challenge figuring out how to support xray blur in the overview, while maintaining the property that it is never re-rendered.

    https://github.com/user-attachments/assets/b5b18398-04a2-4af5-8440-15ce3b4bd87a

    I also had to get both of them working with all other niri features, like blocking out from screencasts. When the window itself is blocked out that's easy, but what if something in the background layer, inside the blur, is blocked out? An unusual case for sure, but hardly a good exclude if your sensitive data gets accidentally leaked.

    https://github.com/user-attachments/assets/71980240-eda0-48de-80ad-dae06b6aa4b4

    By the way, I made it so xray can be used on its own, without the blur. As well as the noise and saturation effects (normally for reducing blur color banding and bumping the vividness). For example:

    window-rule {
        match app-id="Alacritty"
    
        // Xray without the blur!
        background-effect {
            xray true
        }
    }
    
    image

    One more thing you can do starting from this release is to configure niri to apply transparency and background effects to pop-up menus, using the new popups block in window or layer rules.

    // Blur the background behind pop-up menus in Loupe.
    window-rule {
        match app-id="Loupe"
    
        popups {
            // Matches the default libadwaita pop-up corner radius.
            geometry-corner-radius 15
    
            // Note: it'll look better to set background opacity
            // through your GTK theme CSS and not here.
            // This is just an example that makes it look obvious.
            opacity 0.5
    
            background-effect {
                blur true
            }
        }
    }
    
    Screenshot from 2026-04-24 16-14-18

    Keep in mind that pop-up rules tend to bump even more into problems with application behavior and surface shapes. For example, web apps or Electron don't use Wayland pop-ups at all; they're entirely emulated inside the client—niri cannot do anything with them.

    Shape-wise, in GTK 4, pop-ups with has-arrow=true won't look right because they aren't rounded rectangles. Thankfully, clients implementing ext-background-effect can shape their blur in any sort of elaborate pattern.

    Well, enough about blur, we've got more interesting things to cover!

    image
    Credit: Houl Floof

    Optional includes

    Pretty much right after I added config includes last release (before I merged them even), people started requesting optional includes—that can be absent without failing config loading. Some use-cases are being able to change parts of an immutable niri config on NixOS, or having local/private overrides for parts of the config.

    I pushed back for a time because I think some of those problems should be solved elsewhere, rather than requiring every program with includes to support optional. However, the added code complexity was rather low, so I eventually went ahead and accepted @johnrichardrinehart's implementation.

    Starting from this release, you can make an include optional by setting optional=true:

    // Won't fail if this file doesn't exist.
    include optional=true "optional-config.kdl"
    
    // Regular include, will fail if the file doesn't exist.
    include "required-config.kdl"
    

    When an optional include file is missing, niri will emit a warning in the logs on every config reload. This reminds you that the file is missing while still loading the config successfully.

    The optional file is still watched for changes, so if you create it later, the config will automatically reload and apply the new settings. Finally, optional=true only affects whether a missing file causes an error, so if the file exists but contains invalid syntax or other errors, those errors will still cause a parsing failure.

    While we're talking about includes: they now expand paths starting with ~ to the home directory, so ~/file.kdl will expand to /home/user/file.kdl. Thanks to @HigherOrderLogic and @BennyDeeDev for prototype implementations of this feature.

    Pointer warping while scrolling

    Last release, I made dragging windows horizontally by their titlebars scroll the view left and right. This made mouse-only navigation much more convenient, but I still felt that something was missing.

    This release makes the pointer warp from one side of the screen to the other during view scrolling gestures, similarly to Blender. It makes scrolling through several windows natural and convenient, even when you start right next to the monitor edge.

    https://github.com/user-attachments/assets/c7e833c8-517c-4988-a758-a5c9b9a84173

    Screencasting features

    Earlier in the release cycle, I spent some time improving various aspects of our screencasting support. In niri, you can screencast through xdg-desktop-portal-gnome via PipeWire (the recommended approach), or through wlr-screencopy (mainly intended for tools such as wf-recorder). Both of these have seen improvements.

    Pointer in window screencasts

    When sharing the screen, you generally want to include the cursor in the video stream. In PipeWire, you can either do the simple thing and just draw the cursor directly inside the video frames, or you can attach it as a separate frame metadata. In this mode, the video stream itself doesn't contain the cursor, instead, the compositor sends a separate buffer with the cursor icon and coordinates. The consuming application itself (such as OBS, or your browser in a video meeting) then has to draw the cursor on top.

    This allows the consuming application to control the cursor visibility. You might have seen this toggle in OBS; it can work thanks to the metadata cursor mode:

    image

    Ever since I implemented PipeWire screencasting in niri about a month into development, it's been using the embedded cursor mode for simplicity. I rendered the cursor for monitor streams and hid it for window streams, and this mostly did what you wanted (and the cursor toggle in OBS didn't work).

    Doing it properly has always been in the back of my mind though. I was most missing cursor in window streams because I pretty much always use the window target when screensharing in meetings.

    Well, in the summer of last year, @abmantis took up the task. The road was quite bumpy though: they hit and managed to debug a memory corruption issue in PipeWire (that other compositors haven't hit due to more eagerly overriding unchanged data every frame). The bug was thankfully promptly fixed by a PipeWire developer.

    It took me several more months to get to the PR (busy with uni and other things as usual), then some heavy refactoring to make it work correctly and iron out all the edge cases, and now niri does screencasting with cursor metadata!

    The implementation is quite comprehensive. It works in both monitor and window capture modes and draws the cursor along with its drag-and-drop icon (if any). In window capture mode, the cursor is shown only when it's targeting the window or any of its pop-ups. So, for example, if you cast a window fully covered by another window, and move your mouse on top of that, the screencast of the window below will not show the cursor.

    https://github.com/user-attachments/assets/bba92cb3-c78e-4af2-b1fb-049a370c6491

    Metadata cursor is also intended to support an optimization where if you move your mouse over an otherwise unchanging screen, the compositor can skip sending these stationary video frames and only update the cursor position. Unfortunately, the OBS PipeWire code doesn't quite allow for this code path yet, so I couldn't do this for the time being.

    While working on this, I also found several disagreements between the intended meaning in PipeWire (indicated through comments in header files) and what the code was doing in consumers such as libwebrtc (used in all browsers). This is unfortunate since compositors have to not only work around these problems, but also keep the workarounds forever, as they can't tell how old is the library on the other side of the PipeWire stream. It would be good to have high-quality PipeWire producer and consumer examples for these more complex scenarios, that get all the small details right.

    Anyhow! Metadata cursor is here and works with everything I tested. As a bonus, I added pointer capture to window screenshots with a new flag on the action: screenshot-window show-pointer=true.

    Identity screenshot with a popup open and a pointer with tooltip hovering over a button in the popup.

    Delayed start for dynamic cast target

    Dynamic cast target is a niri feature that lets you instantly switch what you're screencasting with a keybind. I personally use it all the time because it's very convenient to toggle screencasting between different windows without going through some video conferencing screen sharing UI or having to cast the entire monitor.

    You start a dynamic cast by selecting a special "niri Dynamic Cast Target" in the window picker. The dynamic cast always starts as a blank video stream to avoid sharing something sensitive by mistake.

    Before this release, the dynamic cast literally started as a 1×1 black pixel video stream. This worked just fine in every app... except, apparently, everyone's favorite Microsoft Teams. So, in this release I changed the dynamic cast to delay starting the video stream until the first dynamic target is selected. As far as the screen sharing programs are concerned, you're just taking a bit longer to pick what to screen share. No more slightly odd brief 1×1 video.

    Cast IPC

    It can be useful to know if there's an ongoing screencast. For example, desktop bars may want to show a screen recording indicator to alert you of any unintended screen capture.

    For PipeWire, a bar could enumerate all ongoing video streams and try to figure out which ones are screencasts, but it's error prone, and for wlr-screencopy there's no way at all to tell from outside the compositor.

    So in this release, I added screencast IPC to niri. You can see currently active screencasts with niri msg casts. Desktop components can subscribe to the niri event stream and listen for the new cast events.

    The Cast object provides various bits of information: kind (PipeWire or wlr-screencopy), current target (output, window), whether the cast is active. PipeWire screencasts provide their node ID which you can use to find out the consumer, while wlr-screencopy screencasts provide the client process ID for the same purpose.

    DankMaterialShell already shows a screencasting indicator using the niri IPC:

    simple screencast indicator in DMS

    And if you need more, it's easy to make a plugin that shows all exposed information.

    DMS plugin with detailed screencast info showing three PipeWire screencasts showing names: obs, firefox-bin, chrome
    While working on this, I found that I had a bunch of duplicate screencast sources in OBS.

    Keep in mind that for wlr-screencopy, there's no robust way to tell apart different screencasts and screenshots, so I had to come up with some heuristics. Notably, xdg-desktop-portal-wlr always uses and keeps alive a single wlr-screencopy manager object, so there's no way to tell when a screencast has stopped short of using a timeout for when the last frame was requested.

    All of these wlr-screencopy problems are fixed in the new ext-image-copy-capture protocol, but we don't have it in niri just yet (and some clients will remain legacy anyway).

    Also, with cast IPC providing IDs for screencasts, we can add actions to manipulate them. The new niri msg action stop-cast --session-id <ID> will force-stop a PipeWire screencast (wlr-screencopy ones cannot currently be stopped through IPC).

    https://github.com/user-attachments/assets/23fec208-a0dd-4dd8-83e9-e8b6581f3a86

    Miscellaneous fixes

    Some more random things I fixed in this release:

    • Copying with damage would always include the cursor even if the wlr-screencopy client said not to; now this is honored.
    • Fixed behavior when a wlr-screencopy client requests multiple frame copies with damage at once. I don't know of any client that does this but now it should work.
    • Fixed the niri wlr-screencopy data never getting freed in some cases, like when the client was killed.
    • Reduced the default PipeWire screencast buffer count from 16 to 8.
    • @kriive worked around a use-after-free bug in pipewire-rs by reordering some struct fields in niri.
    • Fixed wrong rendering z-order that could appear for one frame when switching the dynamic cast target to a window.

    Animation improvements

    In winter, I felt like doing a bit of an "animation detox" and spent several weeks with some niri animations disabled. (If you're curious, I turn off window open, close, resize and movement animations, and leave horizontal view movement since it helps with spatial awareness.) While doing this, I noticed some jank in the unfullscreen/unmaximize animation.

    You can configure individual animations differently in niri. However, in several cases, two animations are meant to run together and match exactly. In those cases, niri will synchronize the animations—for example, it can run some animation that is otherwise disabled.

    In particular, a window resize animation is synchronized with the horizontal view movement animation that it causes. This way, resizing a window next to the right edge of the monitor will "grow" it to the left instead of an awkward combination of growing to the right and moving back in-view.

    The problem that I found is that while fullscreen/maximize correctly synchronized the view movement, unfullscreen/unmaximize didn't. So the window would unmaximize instantly (window-resize is off) but slowly scroll back into position.

    This is now fixed:

    https://github.com/user-attachments/assets/6db06b54-c83e-462d-b3bc-3259326a1d41

    Another animation issue that's been bugging me for a while was fairly specific. When you "drag out" a maximized window, it will automatically unmaximize. If it was floating before you maximized it, it will also automatically return to floating. And when you did this specific action—drag a window to unmaximize into floating—it would skip the horizontal view movement animation of other tiled windows on the same workspace.

    https://github.com/user-attachments/assets/d8273330-2a51-44ea-b4e8-6f380f83da35

    This was a tricky issue to find because it was at an intersection with another feature: left-right workspace scrolling if you drag-and-drop near a monitor edge. If this drag-and-drop scrolled the view, the view needs to resnap to a window edge, but if it never scrolled the view, then the view should remain exactly as it was, no resnapping. It turned out that this drag-and-drop finalization code ran right after the horizontal view animation was started, and since no scrolling had occurred, it immediately skipped the animation.

    The fix was to take a possibly running animation into account explicitly (and add a test of course).

    Finally, there was always some weirdness when "dragging out" the leftmost column on a workspace, specifically when it wasn't focused. (You can easily hit this when moving windows from the overview.)

    "Dragging out" in this case preserves the view position, which is intended: the focused window (not the one we're dragging out) always remains fixed in the view, regardless of what's happening around it. But dropping the window back would awkwardly put it on the right side instead of where it previously was.

    https://github.com/user-attachments/assets/ca5fd740-32a5-4d2c-a21e-b39401fcebc4

    After carefully reading through the relevant code (which is among the earliest code I wrote in niri since this is a fundamental windowing operation, but also changed shape many times over development), I noticed that some operation ordering wasn't quite logical when inserting the leftmost column into a workspace, and was able to refactor things a bit to make it work right.

    And the last small animation fix was to prevent the slowdown/speedup setting affecting the duration the config error notification is visible on screen.

    IME in pop-ups

    We fixed (or rather worked around) one long-standing annoying problem: GTK 4 pop-ups with input fields didn't work if you were running an IME like Fcitx5. Effectively, you couldn't open any pop-up with a text entry.

    The underlying issue is that Smithay's abstractions don't allow for multiple input grabs at once. Pop-ups generally take a pointer and keyboard grab (notice how when a pop-up is open, moving the mouse over other windows doesn't trigger any hover effects in them), but an IME also works through a keyboard grab in order to handle key events. These two conflicted with each other in niri, so it dropped the pop-up grab, which closed the pop-up immediately upon opening it.

    A proper fix would be rearchitecting this part of Smithay, but until then, I loosened some checks, allowing this grab sequence to work. Finally, IME users are able to rename files in Nautilus.

    image

    Escape to cancel drag-and-drop

    Pressing Escape during drag-and-drop will now cancel the operation. I wanted to add this for a while since it's a common gesture, so I did as soon as Smithay recently merged the necessary code.

    Input device improvements

    We've had an assortment of improvements to input devices:

    • Fixed compounding slowdown over time when using a high Hz mouse with cursor hide-after-inactive-ms or an idle monitoring daemon.
    • If you have libwayland-server v1.23 or later, niri will increase its Wayland buffer size, so moving a high Hz mouse over non-responsive windows will no longer quickly crash them.
    • @qqwa added the map-to-focused-output tablet option that makes the tablet target the currently focused output rather than some single configured output.
    • @skrmc fixed an issue where putting the cursor at the topmost pixels on a workspace wouldn't always target a maximized window under the cursor.
    • Fixed Alt-Tab reacting to mouse input before it's visible.
    • @ArijanJ made trackball (on-button-down) scrolling work in the overview.
    • @mgabor3141 made the Num Lock state preserve across loading a custom .xkb file keymap.
    • @Atan-D-RP4 fixed niri being unable to use any input devices when starting from a different TTY via tmux.
    • Enabled the loading of libinput plugins.

    GPU profiling

    One of my main blockers for blur in niri has always been the lack of GPU profiling integration in Smithay. Blur is a heavy operation, and I wanted to see its performance behavior to make good decisions about the code architecture.

    In Smithay and niri we use Tracy, a highly capable frame profiler. It supports showing GPU zones, however collecting timestamps from the GPU requires a fair bit of integration work: you need to submit timestamp queries along with your GPU work, then keep a queue of in-flight queries, periodically collect the values of completed queries and upload them to Tracy. At the end of 2025, I sat down and did the necessary work in Smithay which enables both profiling GPU operations done by Smithay itself, and for compositors to annotate their own GPU operations.

    Here's an example Tracy recording with GPU zones shown in red at the top, and CPU zones in teal below.

    image

    On this recording niri draws a single frame, first to the DRM buffer (goes to the monitor), then, separately, to a buffer for an ongoing PipeWire screencast. You can see the screencast rendering on the GPU in parallel with some CPU work, then as soon as it's done, the CPU is notified, and sends the finished frame over PipeWire to the screencast consumer.

    On multi-GPU systems (common on laptops if you have an integrated + discrete GPU), Tracy will show multiple GPU tracks:

    image

    On this frame profile, I have a laptop with the main screen (connected to the iGPU) and an external screen (connected to the dGPU). You can see the main GPU rendering both screens (niri renders everything on the main GPU), then the external screen contents are copied over to the dGPU where they are rendered in a single texture draw.

    This profiling integration allowed me to verify that blur isn't slower than expected (actually it turned out to run faster than I thought it would). Also, it's now much easier to diagnose dropped frames caused by GPU rendering stalls.

    Rendering optimizations

    In Smithay and, by extension, niri, rendering works by first constructing a render list, a Vec of render elements that describe exactly how the final scene is laid out on screen. This render list is then processed by the damage tracker to cut out all invisible and unchanged regions, and then, only if anything needs to be redrawn, the damage tracker hands the elements over to the GPU—just the ones that changed. Compositors try hard to minimize unnecessary redrawing and waking up the GPU to conserve battery.

    When designing the rendering architecture in niri, I implemented everything through iterators. Functions like Workspace::render() would return a type like -> impl Iterator<Item = SomeRenderElement>, aggregating and processing render elements from their constituent parts (like individual windows on a workspace). At the top level, Niri::render() would collect from such an iterator into a Vec of render elements.

    Generally, this code structure avoids intermediate allocations (returning an iterator like this compiles down to a state machine that creates all items on-demand as they are pulled by the caller). It also avoids doing unnecessary work since the caller can cut the iterator short at any time if it doesn't need some of the items.

    However, as you may know if you have dealt with complex iterators in Rust, there's a whole range of annoyances that come with this kind of structure. For a start, it's hard to write any logic, like conditionals, around returning iterators. Since this -> impl Iterator must be a single type, you cannot just write:

    if condition {
        return one_iterator;
    } else {
        return another_iterator;
    }
    

    It won't compile as these are two different types. You have to come up with workarounds.

    Then, in many cases in niri, the returned iterator would borrow from &self, leading to complex lifetimes. I actually designed for this from the start, with render() functions intentionally borrowing a shared &self, preceded by a separate update(&mut self) step. However, rendering also needs an exclusive &mut Renderer, and this thing did cause annoying borrowing issues every now and then.

    In several cases, the borrowing is not practical to work around, so I had to fall back to returning a Vec from intermediate functions, which is a short-lived temporary allocation that's immediately freed. Especially unfortunate is that the iterator approach doesn't really work across crate boundaries, so Smithay's surface rendering function returns a Vec—and niri calls it per Wayland window and pop-up, causing many temporary allocations during rendering.

    For a few months, thoughts brewed in my head on how to rearchitect this. Finally, in December, I felt like I had a solid, working idea, and attempted the refactor.

    The idea was to replace pull-based functions with push-based ones. Instead of returning -> impl Iterator<Element>, all rendering functions would accept a push: &mut dyn FnMut(Element) closure, and call it to push their render elements to the list. At the top level, push would simply final_vec.push(element), and intermediate rendering functions would forward this push function down. (This design is not unlike how render tree construction works in GTK 4.)

    The refactor honestly succeeded beyond my expectations. It solved pretty much all problems I've had. Conditionals become trivial and just work. No complex iterator chains. Functions can still do their logic by wrapping the parent's push in their own closure. There's no borrowing. All temporary Vecs gone. As for cutting iterators short, we didn't actually need it in niri.

    It also sped up the render list construction by 2-3× on my main machines (I didn't expect that):

    image image

    And, wildly enough, by 8× on my ancient Eee PC! Render list construction does not include the rendering time, which still dominates the frame duration. But it happens much more frequently, even when no actual rendering is needed afterwards, so the improvement is very welcome.

    I measured performance and memory use with Tracy. Here's an example profile showing old and new render list construction side-by-side:

    image

    The orange line at the bottom tracks the allocated memory. You can see that the previous rendering allocates and drops many times, while the only allocations in the new rendering are pretty much growing the output vector (the steps are the vector capacity increasing as more elements are added—it should be possible to reuse the same vector to get rid of even those, but I haven't got around to it yet).

    If you're curious for a more detailed motivation and want to see the diff, which somehow turned out to be negative, see the pull request.

    Old laptop support

    There's been a long-standing niri issue where screenshots (both built-in and through wlr-screencopy tools) didn't work on old Intel laptops with a weird error. Last week, @xdagiz finally dug in and figured it out: a wrong OpenGL enum value in Smithay.

    Also, I did some small optimizations to the niri shaders and managed to fit our resize and clip into the (extremely limited) GPU of an ancient ASUS Eee PC that I have lying around, meaning that window resize animations and compositor-rounded corners now work there (can't say they are particularly smooth though).

    Both things combine to show you the following image:

    Screenshot showing our experimental beginning of a niri rewrite to C++ running on an old Eee PC. This is a very important change because it helps distros which have been reluctant to package Rust software thus far, and will allow niri to run on older hardware with more performance.

    Other improvements in this release

    • @cmeissl fixed a VRAM leak that occurred on some systems after closing certain apps.
    • @sodiboo and @HigherOrderLogic implemented the ext-foreign-toplevel-list protocol which will help Quickshell and other shells associate Wayland window objects with niri IPC window IDs.
    • @Ind-E made it so the error message for a duplicate bind in the config also shows the first definition of the same bind.
    • Mod+LMB window dragging is now indicated with a grabbing cursor (thanks @kchibisov).
    • @zimward added the --path argument to niri msg action load-config-file which lets you switch to a different niri config at runtime.
    • @Fingel added DMA-BUF support to nested niri, which makes hardware acceleration work there again, now that Mesa's wl_drm is deprecated and phased out.
    • Removed padding that niri added to layer-shell pop-ups near monitor edges, as it was more confusing than helpful.
    • The default config now binds Mod+M to maximize-window-to-edges and Mod+Shift+R to switch-preset-column-width-back.
    • Added the force-disable-connectors-on-resume debug flag to force a screen blank on TTY switch into niri or waking up from suspend, which can help on some rare hardware configurations.
    • Putting a window into windowed fullscreen now correctly squares the corners.
    • Fixed constant screen repainting while the overview is open.
    • Slightly corrected the relative-to=workspace-view gradient border rendering for interactively dragged windows.
    • @jakobhellermann prettified diaeresis shortcut rendering in the Important Hotkeys dialog.
    • Fixed the description of expel-window-from-column in niri msg action to say that it expels the bottom window (this has been the behavior for a few niri releases already).
    • Fixed several panics possible if a client tries to use a recently removed output.
    • Fixed broken rendering when clip-to-geometry is applied to a client that attaches y_invert buffers.
    • @tobhe fixed building on OpenBSD.
    • @titaniumtraveler made nested niri set its window app-id.
    • @DuskyElf changed niri to re-evaluate the ignore-drm-device debug settings when a new GPU is plugged in, allowing to use /dev/dri/by-path/ symlinks there.
    • Updated Smithay:
      • Improved automatic GPU selection on some devices such as ARM Macs. Asahi and Pinephone should now run niri out of the box with no manual render-drm-device configuration necessary.
      • Improved the behavior of some layer-shell clients like wl_shimeji by not considering subsurfaces for layer surface positioning.
      • Improved support for docks that cause monitor EDID to be loaded late.
      • Made screenshots and screencasts work on older Intel systems.
      • Fixed stale outputs being left behind when some USB-C docks are disconnected while the computer is suspended.
      • Fixed a zxdg_exporter_v2 panic with some clients.
      • Fixed a memory leak when clients using the clipboard protocols don't destroy them explicitly.
      • Fixed a panic when the client tries to set unrecognized text-input content hint and purpose (this started to happen in the GTK 4.23 development release).
      • Various fixes to drag-and-drop, IME text input and multi-GPU, as well as various performance improvements.

    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!

    Downloads
  • v25.11 b35bcae35b

    Stable

    andrei released this 2025-11-29 17:00:30 +07:00 | 245 commits to main since this release

    📅 Originally published on GitHub: Sat, 29 Nov 2025 10:06:23 GMT
    🏷️ Git tag created: Sat, 29 Nov 2025 10:00:30 GMT

    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 supports 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!

    https://github.com/user-attachments/assets/4996d0be-638f-403b-b111-89a343d50c00

    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.

    https://github.com/user-attachments/assets/05b340b0-4e5e-4b66-93e3-bd42734f33c6

    https://github.com/user-attachments/assets/d736341a-7e77-4c06-8fad-96857e2da93a

    Even if you run with animations disabled, you will see an improvement: windows will no longer jump up and down when un/fullscreening.

    https://github.com/user-attachments/assets/f3316e87-6086-4608-ae2e-a6e202cf03f3

    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.

    https://github.com/user-attachments/assets/f2b9bb97-a611-491b-be2d-6c8f95cf2737

    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).

    https://github.com/user-attachments/assets/b3dca1c3-fd6f-4139-a431-91de8fc048a5

    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.

    https://github.com/user-attachments/assets/59d83b61-dfd9-4b28-983c-4f12a6ea6e19

    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.

    https://github.com/user-attachments/assets/7af85cd6-d218-409e-8d0c-414dade0249e

    https://github.com/user-attachments/assets/78dc353d-4017-43ed-84fc-b59d1c66ace3

    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:

    https://github.com/user-attachments/assets/3a399726-0d22-4497-9cc6-d4fcc444f882

    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 --session in WSL.
    • @shaunren added the ignore-drm-device debug 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-matrix input setting work for touchscreens.
    • @nenikitov added proportional change support (+10%, -10%, 10%) to the move-floating-window action.
    • @iynaix added a --path argument to the screenshot, screenshot-window and screenshot-screen actions that sets the path where niri will write the screenshot.
    • @ThatOneCalculator added a new ScreenshotCaptured IPC 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-unchanged debug flag is now deprecated and does nothing.
    • The output { background-color "..."; } setting is now deprecated, use the new output { layout { background-color "..."; } } setting instead.
    • 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-typing or hide-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-width to 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 XF86ScreenSaver key. Niri parses keys case-insensitively. Unfortunately, there's a colliding XF86_Screensaver key (note the small s) 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!

    Downloads
  • v25.08 01be0e65f4

    Stable

    andrei released this 2025-08-30 20:35:06 +07:00 | 447 commits to main since this release

    📅 Originally published on GitHub: Sat, 30 Aug 2025 13:38:23 GMT
    🏷️ Git tag created: Sat, 30 Aug 2025 13:35:06 GMT

    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.

    A month ago, on July 31st, we hit 10,000 stars on GitHub! Thanks everyone for your support. 😄 Also, on August 10, niri turned two years old since the initial commit.

    With introductions out of the way, here are the improvements from the last release.

    Note

    Packagers:

    • Niri still requires libdisplay-info 0.2 (not 0.3). When libdisplay-info-rs updates its requirement, you will likely be able to bump it in niri with no other changes.
    • Some tests now require surfaceless EGL to be available at test time. If this is problematic, you can ignore them with cargo test -- --skip=::egl.
    • The wiki contents have moved from wiki/ to docs/wiki/.
    • We have a new Integrating niri wiki page with information on integrating niri into distributions, which you may find helpful.

    A logo

    After many, many ideas and discussions (first on Matrix, then on GitHub), and several cool design proposals, niri now has logo!

    niri logo

    It's a candle! The logo comes in four versions: full-sized, simple full-sized, icon, and simple icon. The simple versions are single-color and suitable for smaller sizes.

    full-sized icon
    normal
    simple

    The logo is intentionally recolorable, and you might've already seen several versions across our wiki, Matrix, and Discord. In fact, there's a webpage that lets you quickly adjust the color and download an SVG.

    Big thanks to @ixxie, @bluelinden, and @HumpityDumpityDumber for creating and iterating on this design.

    New wiki

    Thanks to @chinatsu, we have a new interface to browse our wiki! Check it out: https://yalter.github.io/niri/

    The main improvement over GitHub Wiki is the interactive full-text search.

    Screenshot of the search in the new wiki.

    The site uses Material for MkDocs, and we retained full compatibility with GitHub Wiki, meaning all existing links keep working. All wiki pages will continue to be deployed to both the new site and the GitHub Wiki.

    The new site allows us to make various customizations. For example, @chinatsu made all of our "Since: version" annotations render as badges and link to the corresponding release notes.

    Screenshot of a Since badge.

    Also, thanks everyone for several suggestions and test wikis in the GitHub discussion!

    xwayland-satellite integration

    There are still plenty of apps, and notably games, that use X11 and thus require Xwayland to work. Setting this up has been a common stumbling block for new niri users, because unlike most other compositors, we don't integrate Xwayland.

    This niri release solves that problem by integrating xwayland-satellite. Make sure xwayland-satellite >= 0.7 is installed, remove any manual $DISPLAY configuration you may have had, and Xwayland will just work. If your xwayland-satellite binary is in a non-standard location, you can configure it with a new option.

    For all intents and purposes, this integration works the same way as Xwayland integration in other compositors. Niri will create X11 sockets on disk, export $DISPLAY, and spawn xwayland-satellite on-demand when an X11 client connects. If xwayland-satellite dies, niri will automatically restart it.

    This integration also removes the startup race condition, meaning you can put X11 apps into spawn-at-startup and systemd autostart.

    We're using xwayland-satellite rather than Xwayland directly because those same reasons for avoiding Xwayland still apply. xwayland-satellite takes on the bulk of the work of dealing with the X11 peculiarities, giving niri normal Wayland windows to manage.

    As a reminder, xwayland-satellite can perfectly run Steam, games, Proton, JetBrains IDEs, Ghidra, Electron apps, and most other X11 clients. But, applications that try to position windows and bars at specific screen coordinates will likely break and need a nested compositor to run.

    Huge thanks to @Supreeeme for all the continued development on xwayland-satellite, and making it work as well as it does!

    Screen reader support

    A series of posts by fireborn earlier this year on the screen reader situation in Linux got me curious: how does one support screen readers in a Wayland compositor? The documentation is unfortunately scarce and difficult to find. Thankfully, @DataTriny from the AccessKit project came across my issue, pointed me at the right protocols, and answered a lot of my questions.

    So, as of this release, niri has basic support for screen readers! We implement the org.freedesktop.a11y.KeyboardMonitor D-Bus interface for Orca to listen and grab keyboard keys, and we expose the main niri UI elements via AccessKit. Specifically, niri will announce:

    • workspace switching, for example it'll say "Workspace 2";
    • the exit confirmation dialog;
    • entering the screenshot UI and the overview;
    • whenever a config parse error occurs;
    • the important hotkeys list.

    Here's a demo video, watch with sound on.

    https://github.com/user-attachments/assets/afceba6f-79f1-47ec-b859-a0fcb7f8eae3

    I also added a default config binding SuperAltS to toggle Orca, which is the standard key binding for this. Orca still requires an X11 socket, so the new xwayland-satellite integration really helps here too.

    The current screen reader support and further considerations are documented on the new Accessibility wiki page.

    I also want to thank @tyrylu for his talk at this year's GUADEC that sheds some light on the current developments in Linux accessibility, and for answering a slew of my questions when I caught him afterward.

    Modal exit confirmation

    The screen reader integration necessitated making our exit confirmation dialog a proper modal dialog that takes full keyboard focus. A natural extension of this was to add full-screen dimming, emphasizing that you're about to log out of the entire session, and a nice open/close animation (which you can of course disable if you want).

    Hopefully, this will mark the end of me accidentally logging out instead of closing the nested development niri window. (It didn't. Mere dimming is no match for the speed of a well-practiced SuperShiftEEnter motion.)

    https://github.com/user-attachments/assets/10eaa5d9-3476-4a0f-90e9-46cf2ae39bb9

    Screenshot UI improvements

    In the last release, the screenshot UI learned to respond to some keyboard window movement bindings by moving the screenshot selection. Now, @iostapyshyn made the screenshot UI also handle the move-column-left-or-to-monitor-left/right and move-window-up-or-to-workspace-up/down actions, while I implemented move-column/window-to-monitor that moves the selection across monitors.

    This works similarly to a floating window: the selection origin is preserved relatively, and the size is adjusted by the monitor scale difference. Under the right conditions, it'll match a floating window exactly.

    Also, holding Space while dragging out a selection with the mouse will now let you move the selection!

    https://github.com/user-attachments/assets/f5c896ea-390d-4ca1-907b-60667c7324a7

    I made it work with a second touch on a touchscreen too. This was inspired by how in osu! you can drag the cursor with one finger and touch with a second/third to "click".

    https://github.com/user-attachments/assets/9c6b3574-4c35-48d1-bef3-d311a6dc2c33

    Keyboard layout from systemd-localed

    To aid distribution integration, niri learned to read the keyboard layout from systemd-localed at org.freedesktop.locale1 over D-Bus. This is now the default behavior when there's no explicit XKB layout configuration, so it should "just work" for distribution installers that set the layout via localed.

    Screencasting fixes

    Some NVIDIA users saw flickering when screencasting in Discord or OBS. Last release added a wait-for-frame-completion-in-pipewire debug flag to work around the problem.

    In this release, I fixed the problem properly (by delaying sending screencast frames over PipeWire until they finished rendering) and removed the debug flag. As a reminder, debug flags are not covered by our config breaking change policy.

    I also corrected the app IDs that niri communicates to xdg-desktop-portal-gnome, making it correctly show the icons for most applications in the window picker.

    Screencast window selection dialog showing windows with icons.

    ext-workspace protocol

    I implemented the ext-workspace Wayland protocol in niri. It gives desktop components information about workspaces, so bars can use it to make a workspace indicator compatible with different compositors.

    Along the way, I found a number of bugs in the ext-workspace code in various bars. All those bugs are now fixed, so you can use the ext/workspaces module in Waybar 0.14, and in upcoming versions of sfwbar and xfce4-panel. Work is ongoing to implement ext-workspace in lxqt-panel and Quickshell.

    Window positions and sizes in the IPC

    After consuming several people (thanks @calops and @aeghn for the intermediate discussions and PRs), the challenge of adding window sizes and positions to the niri IPC was finally bested by @yrkv. Windows returned by niri msg windows and the event stream now provide the following layout information:

    • for windows in the scrolling layout: index of the window's column on the workspace, and index of the window inside that column;
    • for floating windows: position on screen;
    • for all windows: size of the tile (including borders) and of the underlying Wayland window, and the Wayland window's offset inside the tile.

    You can find the detailed documentation in niri-ipc.

    This data already lets you order your window lists to match the workspace, make visual displays for floating windows, or keep track of how many columns are to the left/right of a given window. Here's a quick demo I put together in quickshell which tracks floating windows on the current workspace.

    https://github.com/user-attachments/assets/57903ed8-272f-44b4-a945-0ac8b9074db8

    Some things are still missing, like the current "scroll position" or floating window stacking order. They will be added later, when we figure out the best way to expose them (there's a tricky trade-off between ease of use and spamming the IPC too much).

    As another small IPC improvement, @HigherOrderLogic added the ConfigLoaded event and a disable-failed option for the built-in niri config notification. Together, these let desktop shells implement this notification in a custom manner.

    Config additions and fixes

    One common source of confusion about the niri config is the spawn command used for running programs. Unlike similar commands in other compositors, niri's spawn runs the given binary directly, without going through the shell, meaning that you have to manually split arguments and cannot use shell expansions or pipelines.

    In this release, I added spawn-sh and spawn-sh-at-startup that accept a single argument—the command to run—and run it through the shell. With spawn-sh, all complex commands work as expected:

    binds {
        // Works with spawn-sh: all arguments in the same string.
        Mod+D { spawn-sh "alacritty -e /usr/bin/fish"; }
    
        // Works with spawn-sh: shell variable ($MAIN_OUTPUT), ~ expansion.
        Mod+T { spawn-sh "grim -o $MAIN_OUTPUT ~/screenshot.png"; }
    
        // Works with spawn-sh: process substitution.
        Mod+Q { spawn-sh "notify-send clipboard \"$(wl-paste)\""; }
    
        // Works with spawn-sh: multiple commands.
        Super+Alt+S { spawn-sh "pkill orca || exec orca"; }
    }
    

    Under the hood, spawn-sh "some command" is equivalent to spawn "sh" "-c" "some command"—it's just a less confusing shorthand.

    Now, on to the rest of the config additions and improvements in this release:

    • @two-horned implemented the switch-preset-column-width-back and switch-preset-window-height-back actions that reverse the existing preset-switching ones.
    • @HigherOrderLogic added the cubic-bezier animation easing type that lets you use a custom cubic Bézier curve with the same parameters as in CSS.
    • @chinatsu added the hide-not-bound hotkey-overlay setting that prevents the Important Hotkeys dialog from showing (not bound) hotkeys.
    • The pointing device accel-speed and animation slowdown were changed to accept integer values without having to spell out the floating point .0. This also added parsing-time limits to the values: [-1; 1] to accel-speed and [0; 231) to slowdown. While this is technically a config breaking change, the values outside that range weren't valid either way.
    • @lierdakil added the scroll-button-lock option for pointing devices.
    • @bkuri, after some struggling against Claude Code, implemented separate vertical and horizontal scroll-factor options for pointing devices.
    • Fixed hot reloading for trackball, tablet, and touch libinput settings.
    • @abdavis added clamping to configured color values after color space conversion, fixing a rendering difference between solid colors and gradients.
    • @Gwenodai added the skip-cursor-only-updates-during-vrr debug flag that prevents cursor movement from redrawing the screen when variable refresh rate is on. This is useful for games where the cursor isn't drawn internally to prevent erratic VRR shifts in response to cursor movement.
    • @sashomasho added the deactivate-unfocused-windows debug flag that works around incorrect keyboard focus tracking from various Chromium- and Electron-based applications. If your chat window thinks it's focused, and doesn't show message notifications, despite being on a different workspace, then this debug flag might help.
    • @bbedward added the keep-max-bpc-unchanged debug flag as a workaround for a bug with AMDGPU and some OLED panels that causes niri to fail to turn them on.
    • Clarified and improved the example values for some options in the default config. @schuelermine set the close-window bind as repeat=false. @dev-nicolaos added backlight adjustment binds using brightnessctl.
    • Num Lock state is now preserved across XKB config changes.
    • @sodiboo made config hot reloading work for /etc/niri/config.kdl.

    Other improvements in this release

    • @HigherOrderLogic made our FreeDesktop idle inhibitor also register its D-Bus service at /ScreenSaver, making it work with more apps, like VLC.
    • @my4ng made niri turn off HDR. We don't support HDR yet, so enabled HDR carried over from other compositors would just result in broken colors.
    • @zgibberish fixed --focus=false not working when using move-column-to-workspace-up/down on a floating window.
    • Added --focus=false argument to move-window-to-workspace-up/down.
    • @artrixdotdev added Nushell completion support: niri completions nushell.
    • Fixed tiled window popup menus sometimes appearing underneath exclusive-zone layer-shell components (the unconstraining logic wasn't taking these exclusive zones into account).
    • Changed the ext-session-lock surface size to round rather than floor, which removes a 1 px "border" on some fractional scales.
    • Fixed ext-session-lock clients that got denied the lock being able to create and show a lock surface. In particular, this fixes extra gtklock instances being able to destroy the active lock client, leaving you with an empty red locked session screen.
    • @notpeelz made niri set the logind LockedHint when the screen is locked through ext-session-lock. This for example enables idle daemons to only put the computer to sleep when it is locked.
    • @sodiboo added SIGINT, SIGTERM and SIGHUP handling to niri: these will now exit niri properly, cleaning up the Wayland sockets and other resources.
    • @Vladimir-csp added a check that makes uwsm start niri.desktop work for those who use UWSM.
    • @vanderlokken added niri msg action load-config-file to force-reload the config file without waiting for hot reloading.
    • Fixed a small animation bug when quickly resizing tiled windows back and forth. This problem was fairly difficult to trigger by accident (unless you're specifically trying to reproduce it), but it caused further problems "downstream" for combined window actions.
    • Removed redundant "device is inactive" warning spam when switching to a different TTY.
    • Fixed changing floating window height resetting the preset width index rather than the preset height index.
    • Thanks to @zimward and @matejc for adding Alpine/musl and FreeBSD CI jobs respectively, ensuring that niri keeps building on those systems.
    • Updated Smithay:
      • Added support for version 2 of the cursor-shape protocol.
      • Fixed the keymap sent to clients, making wvkbd work.
      • Fixed integer overflows from layer surfaces setting extremely high margin values.
      • Fixed an issue where a clipboard client that terminates unexpectedly did not cause an immediate empty clipboard event.
      • Fixed mouse input to the bottommost and rightmost edges of a surface sometimes getting missed.

    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!

    Downloads
  • Stable

    andrei released this 2025-05-25 13:03:00 +07:00 | 652 commits to main since this release

    📅 Originally published on GitHub: Sun, 25 May 2025 06:06:09 GMT
    🏷️ Git tag created: Sun, 25 May 2025 06:03:00 GMT

    This is a hotfix release for niri v25.05.

    • Fixed handling of layer surfaces unmapped through a null buffer commit: they will now receive an initial configure as necessary (thanks @alex-huff). This makes the kitty quick access terminal work.
    • Fixed unmapped layer surfaces preventing popups from appearing (thanks @alex-huff).
    • Fixed brief hover events when the cursor is hidden (thanks @Duncaen). Also fixed brief cursor hovers when a hidden cursor warps (i.e. with warp-mouse-to-focus or with focus-monitor-right etc.).
    • Renamed un/set/toggle-urgent to un/set/toggle-window-urgent, their intended name. This was my oversight. This change is not config-breaking because these actions aren't bindable, but it does mean you need to change the name when calling niri msg action. I went ahead with this break because I don't believe these actions are used anywhere yet (and others confirmed that).
    • Fixed screen not always redrawing on niri msg set-window-urgent and other urgency actions.
    • Updated Smithay:
      • Fixed numlock activating only after pressing another modifier key (thanks @erdii).
      • Fixed incorrect reporting of tablet pen tilt.
      • Fixed certain clients like fcitx causing frequent keyboard keymap events to clients.
    Downloads
  • v25.05 ae89cb6017

    Stable

    andrei released this 2025-05-17 20:08:37 +07:00 | 667 commits to main since this release

    📅 Originally published on GitHub: Sat, 17 May 2025 13:19:09 GMT
    🏷️ Git tag created: Sat, 17 May 2025 13:08:37 GMT

    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: the niri default config now spawns waybar at startup so as not to start with a blank desktop. Please consider adding Waybar as a recommended dependency (or changing it to some other bar).

    The Overview

    The big new thing in niri v25.05 is the Overview. It zooms out your workspaces and windows to let you see what's going on at a glance, navigate, and drag windows around, all without having to touch the keyboard.

    https://github.com/user-attachments/assets/379a5d1f-acdb-4c11-b36c-e85fd91f0995

    You can open it with the toggle-overview bind, via the new top-left hot corner, or using a touchpad four-finger swipe. While in the overview, all keyboard shortcuts keep working, while pointing devices get easier:

    • Mouse: left click and drag windows to move them, right click and drag to scroll workspaces left/right, scroll to switch workspaces (no holding Mod required).
    • Touchpad: two-finger scrolling that matches the normal three-finger gestures.
    • Touchscreen: one-finger scrolling, or one-finger long press to move a window.

    https://github.com/user-attachments/assets/7d9deada-dfb5-4cad-93f2-23b0d72a7877

    Drag-and-drop will scroll the workspaces up/down in the overview, and will activate a workspace when holding it for a moment. Combined with the hot corner, this lets you do a mouse-only DnD across workspaces.

    https://github.com/user-attachments/assets/5f09c5b7-ff40-462b-8b9c-f1b8073a2cbb

    By the way, this new drag-and-drop hold will also bring floating windows to the top outside the overview.

    You can drag-and-drop a window to a new workspace above, below, or between existing workspaces.

    https://github.com/user-attachments/assets/b76d5349-aa20-4889-ab90-0a51554c789d

    To make the spatial model work for the overview, niri now draws a separate background under each workspace. For layer-shell tools, the background and bottom layers zoom out together with the workspaces, while the top and overlay layers remain on top of the overview. Make sure to put your bar on the top layer.

    The background behind the workspaces in the overview is called the backdrop. There are new backdrop-color settings both globally in the overview {} section and per-output.

    If you want something more interesting in the backdrop, there's a new place-within-backdrop layer rule. You can use it on a wallpaper tool showing a blurred version of your background, for example.

    image

    Since backgrounds are now tied to workspaces, they will also move together with workspaces. If you don't like this, you can combine place-within-backdrop with a transparent background-color to get the stationary wallpaper back. Check the overview wiki page for an example configuration. There, you will also find how to change the overview zoom level or disable the hot corner.

    There have also been smaller spatial model fixes to accommodate the overview. For example, the top layer-shell layer and the interactively dragged window now render on top of the background- and bottom-layer popups.

    Finally, @CharlieQLe added an IPC request and event for monitoring the overview's open state.

    Screencasting features

    For this release, I worked on several new features for screencasting and screensharing. To keep track of them, I wrote a new wiki page that describes all screencasting-related functionality in niri. Check it out!

    I should also mention the new wait-for-frame-completion-in-pipewire debug flag by @coleleavitt. If you started having glitches when screencasting on NVIDIA (e.g., in Discord), this flag should help, until we support explicit sync for PipeWire screencasts.

    Dynamic screencast target

    A dynamic cast target is a special target that can change what it streams. You can select it as a special "niri Dynamic Cast Target" in the window selection dialog:

    Screencast dialog showing niri Dynamic Cast Target.

    The dynamic target starts as an empty, transparent video stream. Then, you can use the following binds to change what it shows:

    • set-dynamic-cast-window to cast the focused window.
    • set-dynamic-cast-monitor to cast the focused monitor.
    • clear-dynamic-cast-target to go back to an empty stream.

    You can also use these actions from the command line, for example, to interactively pick which window to cast. Check the screencasting wiki page for more details and examples.

    https://github.com/user-attachments/assets/c617a9d6-7d5e-4f1f-b8cc-9301182d9634

    Windowed fullscreen

    A common feature in WMs is fake, or detached, or windowed fullscreen. The compositor tells the window that it went fullscreen, but in reality keeps it as a normal window.

    This is useful when screencasting browser-based presentations like Google Slides, where you usually want to hide the browser UI, which requires fullscreening the browser. Real fullscreen is not always convenient, for example, if you have an ultrawide monitor, or just want to leave the browser as a smaller window, without taking up an entire monitor.

    Now, niri can help, with the new toggle-windowed-fullscreen bind. It tells the app that it went fullscreen, while in reality leaving it as a normal window that you can resize and put wherever you want.

    binds {
        Mod+Ctrl+Shift+F { toggle-windowed-fullscreen; }
    }
    

    Here's an example showing a windowed-fullscreen Google Slides presentation, along with the presenter view and a meeting app, all on the same monitor:

    Windowed Google Slides presentation, another window showing the presenter view, and another window showing Zoom UI casting the presentation.

    Screenshot UI

    For this release, I made the screenshot UI support tablet and touchscreen input for drawing the selection, closing this long-standing gap. I also added a small capture button to the panel at the bottom, making it possible to select and save a screenshot without a keyboard.

    https://github.com/user-attachments/assets/0748412e-da21-4909-9c86-2244d8bb068c

    Keyboard use got better too: the screenshot UI will now recognize some windowing binds like move-column-left/right, move-window-up/down and set-window-width/height, and move the selection region as if it was a floating window. Thanks to @nnyyxxxx for prototyping the implementation! Naturally, this opens the door for making a bunch more windowing actions work on the screenshot UI selection.

    Finally, @TobyBridle added a show-pointer flag to screenshot and screenshot-screen actions to control whether the mouse cursor is included in the image.

    Window urgency

    Urgency is an unspecified but commonly implemented Wayland behavior where a window can request the user's attention. The compositor will then draw it with a red border, and signal this urgency to other desktop components.

    Now, @Duncaen implemented window urgency in niri. It comes with a host of urgent-color settings on borders, focus-rings and tab indicators, an is-urgent window rule matcher, and urgency indicators for windows and workspaces in the IPC.

    Urgency is cleared once you focus a window. You can also manually toggle urgency for a specific window with the new toggle-urgent, set-urgent, and unset-urgent actions.

    @Duncaen is also adding niri urgency support to Waybar in this PR.

    IPC improvements

    We have several new things in the IPC system.

    @bbb651 implemented niri msg pick-window that lets you select a window by clicking. The command then outputs information about this window, which can be used for scripting. For example, you can make a "screenshot clicked window" script:

    $ niri msg action screenshot-window --id="$(niri msg --json pick-window | jq .id)"
    

    @nnyyxxxx added niri msg pick-color that prints the color of the selected pixel. They also hooked it up to the PickColor method of the Screenshot portal, making the color picker work in apps.

    https://github.com/user-attachments/assets/93cc4381-851b-4f3f-aa4e-2042acd00b72

    The niri IPC socket had so far been conservatively limited to a single request per connection. @titaniumtraveler helped to lift this limitation: now niri will keep reading and replying to requests as they arrive. Keep in mind that requests are still processed one by one and not atomically; read the niri-ipc documentation for more details.

    Finally, there's a small UX improvement. It's a common situation after updating niri that the running niri compositor is still the old version (since you haven't restarted it yet), but the niri binary (and hence the niri msg CLI) is already the new version. Then, using a newly added niri msg feature will ask the running niri compositor something that it doesn't understand yet, yielding an error.

    When this happens, niri msg will additionally request the version of the running niri compositor, and notify you if it doesn't match the CLI version, prompting you to restart. Unfortunately, this check didn't fire if niri msg failed parsing the response, leading to some confusion.

    In the new release, I made the check work properly for this case too.

    Windowing actions

    We've got a number of new actions for working with windows:

    • @annikahannig added focus-monitor, move-window-to-monitor, and move-column-to-monitor by monitor name (in addition to the existing directional actions).
    • @Duncaen added focus-column and move-column-to-index that work by index of the column within the workspace.
    • @nnyyxxxx worked on a new move-window-to-workspace --focus=false flag that will send the active window to a different workspace without following it.
    • @yzy-1 added --focus=false to the move-column-to-workspace/up/down actions.
    • Fixed panic when passing out-of-bounds index to move-workspace-to-index --reference.
    • Fixed move-workspace-to-index being 0-based instead of 1-based (we use 1-based workspace indices elsewhere).

    I also added a new center-visible-columns action that centers all fully visible columns on screen, as a companion to expand-column-to-available-width from the last release.

    https://github.com/user-attachments/assets/55267aeb-5dc8-4b0e-a49a-6961e9d9c3aa

    Finally, while not a new action, consume-or-expel-window-left will now restore the view position similarly to closing a window. This is especially useful with tabbed columns: opening a new window and immediately consuming it left will add it as a tab without messing up the view.

    https://github.com/user-attachments/assets/eb49c7a4-0bc9-4125-9b48-ff0e071c5171

    Input device settings

    We added several settings for input devices. Read more about them on the input configuration wiki page.

    • Added drag to touchpad that allows disabling tap-and-drag (thanks @alexdavid).
    • Added off to touch to disable touchscreen devices (thanks @nnyyxxxx).
    • Added left-handed to trackpoint (thanks @dbeley).
    • Added mod-key and mod-key-nested that let you change the Mod key (thanks @notpeelz).
    • Added numlock setting to keyboard to automatically enable Num Lock at startup (thanks @erdii). Note that there's a known issue where it only works after pressing a modifier key (Super, Alt, etc.).
    • @TyberiusPrime extended warp-mouse-to-focus with modes: mode=center-xy and mode=center-xy-always make the mouse always go to the center of the window both vertically and horizontally.

    Output focusing at startup

    @lualeet added a new focus-at-startup output flag that will make niri focus that output when starting up if it is connected. Additionally, niri will now start with the mouse cursor centered in the focused monitor.

    Screen locker red flash fix

    Niri will now wait for a lock surface to appear before rendering a locked session, thereby fixing the infamous red flash when locking the screen. Note that semitransparent lock surfaces will still show a red background, so you'll want to disable the fade-in in hyprlock.

    https://github.com/user-attachments/assets/3bca8cae-56fd-40c9-a5cb-17e5bd976050

    Tiled state window rule

    The Tiled state is one of the states a Wayland compositor can set on a toplevel window. Generally, windows react to it by removing their shadows and squaring rounded corners. Terminals stop snapping their window size to the terminal grid.

    By default, niri sets the Tiled state together with prefer-no-csd. However, it can be useful to control the Tiled state separately, for example if you want to keep client-side decorations (for mouse-only dragging and closing) together with square corners (for visuals).

    In this release, I exposed it as the tiled-state window rule. Apart from blanket enabling, you can also set it based on whether the window is tiled or floating. For example:

    // Make tiled windows rectangular while using CSD.
    window-rule {
        match is-floating=false
        tiled-state true
    }
    

    https://github.com/user-attachments/assets/270d823f-0fe4-4065-8b3a-5acebc693c8d

    More efficient offscreening

    Offscreening is rendering a window into an intermediate texture rather than straight to the monitor. It's often used to correctly apply alpha blending when compositing multiple layers.

    So far, offscreening in niri was rather inefficient because it was only used during short animations. The intermediate texture would get recreated from scratch every frame. Due to this, I couldn't add any longer-running transparency effects because I didn't want to use the inefficient offscreening.

    For this release, I reworked how offscreening works in niri: it reuses the intermediate texture whenever possible, and it correctly tracks damage both "inside" and "outside" the offscreen. So when an offscreened window redraws, only the damaged part will redraw inside the intermediate texture, and this damage will be propagated out, to be used when compositing the texture itself.

    Practically, this means slightly improved animation efficiency for window opening and resizing. It also enables using offscreening for longer-running effects. In particular, I could finally make windows semitransparent as you're dragging them around, making it easier to see where they will land inside the tiling layout.

    https://github.com/user-attachments/assets/632093d3-6332-4801-bec6-d384578ee0eb

    Animations for tabbed columns also use transparency. Before, they didn't use offscreens, resulting in blending artifacts in some conditions. Now they use the new offscreens, so transparency always looks correct.

    Comparing before and after in the middle of a tabbed column animation with orange inactive border.

    Regular window opacity does not use offscreening, so border or focus ring may still show through.

    Another improvement from the offscreening rework is that offscreens now track which surfaces are visible "inside" the temporary texture. If a surface is completely obscured or otherwise invisible "inside" the temporary texture, it will no longer get frame callbacks. This is a minor efficiency improvement, but it happens to work around an issue with Firefox's experimental subsurface compositor.

    baba-is-float layer rule

    The last niri release had a secret April Fools' feature: the baba-is-float window rule that makes windows FLOAT up and down. Now this widely acclaimed feature is available for layer surfaces too!

    // Make fuzzel FLOAT.
    layer-rule {
        match namespace="^launcher$"
        baba-is-float true
    }
    

    https://github.com/user-attachments/assets/3f4cb1a4-40b2-4766-98b7-eec014c19509

    Other improvements in this release

    • Fixed cursor hiding with hide-when-typing or hide-after-inactive-ms breaking tooltips, and input in some first-person games (thanks @bbogdan-ov).
    • Added a background-color setting to layout {} that sets the background color for all workspaces/outputs.
    • Added anchors to links throughout the wiki. Thanks @chinatsu for implementing anchor support in the wiki deployment GitHub action that we use, and for fixing the links.
    • Added niri completions subcommand to generate shell completions (thanks @titaniumtraveler).
    • Added top, bottom, left, right values for default-floating-position relative-to= which align the window to the center of a side of a monitor (thanks @Mandarancio).
    • Added support for negative shadow spread (thanks @LunarEclipse363).
    • Added the honor-xdg-activation-with-invalid-serial debug flag that fixes focusing the window when clicking on a tray icon for Discord, Telegram, and some other apps. (It is a bug in the apps or their toolkits that they need this debug flag.)
    • Added niri msg action toggle-keyboard-shortcuts-inhibit—the bind was added last release, but we forgot the CLI/IPC action (thanks @sodiboo).
    • Renamed ISO_Level3/5_Shift to Mod5/3 in the Important Hotkeys dialog.
    • In the default config, added custom Important Hotkeys titles for the spawn binds.
    • Fixed window opening animation being off-center during a concurrent resize animation.
    • Fixed clicking on a partially offscreen window with animations disabled hitting the window at its final position, rather than its visible (initial) position. This makes the behavior consistent with enabled animations.
    • Fixed putting center-focused-column "always" in the config not updating the view position right away.
    • Fixed swipe and DnD scroll gestures not taking into account the time between the last pointer movement and button release, leading to unintended jumps.
    • Made it possible for the view position to animate during a DnD scroll. This makes windows scroll into view when "dragging out" the first or the last window on a workspace, like they used to two releases ago.
    • Removed cancellation for the workspace-switch gesture. Now when you add or lift a finger during the gesture, it will complete as if you lifted all fingers, rather than resetting back to the starting position.
    • Fixed swipe gesture forgetting the previous workspace when starting and stopping on the same workspace.
    • Fixed a jump when "catching" a workspace-switch animation with a workspace-switch gesture.
    • Made xdg-activation also consider pointer focus for token validity.
    • Fixed popups showing up inside the animating resizing window (in addition to rendering normally on top).
    • Fixed panic at startup when the configured xkb keymap fails to compile.
    • Fixed panic when trying to interactively resize from a tab indicator.
    • Fixed panic and broken frames with some overdamped spring animation settings.
    • Fixed tab indicator width, gap, gaps_between being able to round down to 0 physical pixels when they are set to above 0 in the config.
    • Fixed the interactively moved window getting input on all outputs rather than just the correct one (you could observe this with a combination of touchscreen and mouse input).
    • Fixed wrong min/max size computation for windows while going out of fullscreen.
    • Fixed interactively moved window not always updating properly.
    • Fixed removing a window that is going into fullscreen not resetting the stored unfullscreen view position.
    • Fixed interactively resizing a window that is going out of fullscreen not resetting the stored unfullscreen view position.
    • Fixed DnD scroll not stopping when interactively moving a fullscreen window that unfullscreens to floating.
    • Changed logging to go to stderr rather than stdout (thanks @titaniumtraveler).
    • Updated Smithay:
      • Fixed panic on monitor hotplugging.
      • Fixed panic on some ARM devices.
      • Fixed panic upon receiving negative damage width/height from a Wayland client.
      • Fixed panic upon receiving subsurface place_above/below with no parent.
      • Fixed presentation feedback getting discarded for subsurfaces.
      • Fixed some popups going off-screen instead of shrinking.
      • Fixed cursor shape sometimes getting stuck in Chromium.

    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!


    And here's a bonus for everyone who got all the way through this huge release, an ASUS Eee PC from 2008 running the overview!

    https://github.com/user-attachments/assets/a0332c82-b412-461b-a1e8-094ec041f127

    Downloads
  • v25.02 b94a5db879

    Stable

    andrei released this 2025-02-22 14:20:26 +07:00 | 926 commits to main since this release

    📅 Originally published on GitHub: Sat, 22 Feb 2025 07:24:21 GMT
    🏷️ Git tag created: Sat, 22 Feb 2025 07:20:26 GMT

    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: I fixed the problem where some tests required RAYON_NUM_THREADS=1 on a heavily multithreaded CPU. Please remove that variable if you had it set, so that we don't miss any new bugs.

    https://github.com/user-attachments/assets/a9e37678-5783-4de0-b2c7-a26094cd635f

    Tabbed columns

    Columns can now present windows as tabs, rather than vertically stacked tiles. This is useful when you have limited vertical space, or when you frequently switch between two large windows and want to avoid scrolling.

    Add this new bind to your config to switch a column to tabbed mode:

    binds {
       Mod+W { toggle-column-tabbed-display; }
    }
    

    This is the only new bind you will need. All other keyboard and mouse navigation works exactly the same as with regular columns: switch tabs with focus-window-down/up, add or remove windows with consume-window-into-column/expel-window-from-column, and so on. (Thanks @elkowar for this wonderful UX idea!)

    https://github.com/user-attachments/assets/5b357895-bcf3-42eb-a2e1-2b220d63d0c9

    There are a few new actions that help navigate the tabs. All of them also work on regular columns.

    • focus-window-top/bottom focuses the topmost or the bottommost window in a column.
    • focus-window-down-or-top and focus-window-up-or-bottom cycle the navigation so the focus jumps from the last to the first window and vice versa.
    • focus-window-in-column <index> focuses a specific window by index.

    The tab indicator can be customized in several ways and moved to top/bottom/right of the column. See the wiki page for more details.

    Tab indicator at the top of the column.

    You can also make windows open as tabbed columns by default globally or with a window rule. This goes well with the hide-when-single-tab setting for the tab indicator.

    Shadows

    Niri can now draw shadows behind windows. Apart from being a nice aesthetic effect, shadows help to delineate floating and otherwise overlapping windows. They are especially useful when you disable or clip away client-side decorations (which commonly include shadows of their own).

    Shadows help to tell windows apart.

    Niri shadows are not enabled by default to not clash with the shadows coming from client-side decorations. Turning them on is simple enough:

    // Enable shadows.
    layout {
        shadow {
            on
        }
    }
    
    // Also ask windows to omit client-side decorations, so that
    // they don't draw their own window shadows.
    prefer-no-csd
    

    You can customize properties like softness (blur radius), spread, offset, and color, both globally and for individual windows. Like borders and focus rings, shadows will follow the window corner radius that you set via geometry-corner-radius.

    Hard shadows example.

    Shadows also work on layer-shell surfaces. Due to the higher variety among layer-shell components, we don't enable shadows for them automatically; you need to explicitly enable them with a layer rule. For example:

    // Add a shadow for fuzzel.
    layer-rule {
        match namespace="^launcher$"
        
        shadow {
            on
        }
    
        // Fuzzel defaults to 10 px rounded corners.
        geometry-corner-radius 10
    }
    

    Shadow behind fuzzel.

    Drag-and-drop view scrolling

    In this release I finally addressed one of the longer-standing UX issues: you can now scroll the view left and right during a drag-and-drop operation by moving the mouse close to the monitor's edge. This is similar to how you can scroll various lists and scrolling views in applications during drag-and-drop.

    There's a small debounce delay before the scrolling starts so that it doesn't trigger when quickly moving the mouse across monitors. You can customize this, as well as other parameters like maximum scrolling speed, in the new config section.

    In addition to drag-and-drop, this gesture will trigger when dragging a window in the tiling layout. Dragging floating windows however won't scroll the view.

    https://github.com/user-attachments/assets/9e1f166e-9194-4830-a482-34a00a9a2d61

    Screencast target window rule

    There's a new is-window-cast-target=true window rule that matches windows "targetted" by an individual-window screencast. You can use it, for example, to highlight the window that you're screensharing by changing its border/focus ring colors.

    // Indicate screencasted windows with red colors.
    window-rule {
        match is-window-cast-target=true
    
        focus-ring {
            active-color "#f38ba8"
            inactive-color "#7d0d2d"
        }
    
        border {
            inactive-color "#7d0d2d"
        }
    
        shadow {
            color "#7d0d2d70"
        }
    
        tab-indicator {
            active-color "#f38ba8"
            inactive-color "#7d0d2d"
        }
    }
    

    Screencasted window highlighted in a red color.

    Thanks @elkowar for the suggestion!

    Custom titles for Important Hotkeys

    We have an Important Hotkeys dialog in niri that pops up at startup with a list of the main binds to get you going. The binds in this list and their titles are hardcoded (so that you're not spammed with all the keys bound by default).

    In this release, you can customize this list using the new hotkey-overlay-title property.

    • To add a bind to the dialog, or change an existing bind, set it to the title that you want to show:
      binds {
          Mod+Shift+S hotkey-overlay-title="Toggle Dark/Light Style" { spawn "some-script.sh"; }
      }
      
    • To hide an existing bind from the dialog, set it to null:
      binds {
          Mod+Q hotkey-overlay-title=null { close-window; }
      }
      

    This is especially useful for binds that spawn programs, as niri can't automatically deduce good titles for them. For example, here's my Important Hotkeys list where I gave nice titles to most spawn binds (everything below PrtSc):

    Important Hotkeys with many customized titles.

    These custom titles also support full Pango markup which allows you to change styles, colors, and fonts.

    Important Hotkeys with custom Pango markup.

    I also made two cosmetic changes to the key combo rendering:

    • Ctrl and Shift are now sorted before Alt, matching most other programs. However, when Alt is the Mod key (running niri as a window), then it will be sorted first to emphasize that.
    • Space is now rendered with capital S. These names come from xkb in all kinds of spelling variations, and we have to "prettify" them manually in niri. Space seems fairly common, so I added it to the code.

    Expand to available width

    Sometimes windows don't quite neatly divide into preset widths, making it hard to fill the space on the monitor exactly. The new expand-column-to-available-width bind addresses this: it expands the focused window to take up all remaining free space on the screen.

    Since windows on niri can scroll in and out of view, this bind considers the current window positions. All fully visible windows remain on screen.

    https://github.com/user-attachments/assets/cdb99398-77a3-42aa-8406-6a56e32559ca

    Keyboard shortcuts inhibit protocol

    @sodiboo implemented the keyboard-shortcuts-inhibit Wayland protocol. It is used by apps like virtual machines or remote desktop clients to let them pass compositor bindings to the target system.

    You can force-deactivate the inhibiting and get your niri shortcuts back using the following new action. Make sure to add it to your config:

    binds {
        Mod+Escape { toggle-keyboard-shortcuts-inhibit; }
    }
    

    You can also make certain binds ignore inhibiting with the new allow-inhibiting=false property. They will always be handled by niri and never passed to the window.

    binds {
        // This bind will always work, even when using a virtual machine.
        Super+Alt+L allow-inhibiting=false { spawn "swaylock"; }
    }
    

    Screenshot without writing to disk

    Thanks to @sornas, you can now capture screenshots only to the clipboard, without writing the image to disk. Simply press CtrlC in the screenshot UI instead of Space or Enter.

    screenshot-screen and screenshot-window binds can do this with a new write-to-disk=false flag:

    binds {
        Ctrl+Print { screenshot-screen write-to-disk=false; }
        Alt+Print { screenshot-window write-to-disk=false; }
    }
    

    Or, on the command line:

    $ niri msg action screenshot-window --write-to-disk=false
    

    Other improvements in this release

    • @sodiboo implemented the wlr-virtual-pointer Wayland protocol required for tools like wayvnc and lan-mouse. These tools should now work with niri, however, keep in mind that you won't able to use niri keyboard binds through them due to a limitation of how the virtual keyboard protocol is currently implemented in Smithay.
    • @bbb651 added the scroll-factor window rule property. It works the same way as the input setting but can be set for a specific window.
    • @Faervan added the toggle-window-rule-opacity action which lets you temporarily make a window fully opaque, if it was semitransparent from a window rule.
    • @notpeelz added a setting to entirely disable the primary clipboard (middle click to paste selected text): clipboard { disable-primary; } at the top level of the niri config.
    • @Kirottu added two actions to move workspaces: niri msg action move-workspace-to-index <INDEX> moves a workspace to a specific position on its monitor, and niri msg action move-workspace-to-monitor <OUTPUT> moves a workspace to a different monitor.
    • @ezemtsov made niri msg action switch-layout accept a layout index (for example, 0 or 1 for the first or the second layout respectively) in addition to next and prev.
    • @m4rch3n1ng added a way to load the keyboard keymap from an .xkb file. See the wiki page for details.
    • @pranaless made the horizontal view movement gesture play better with center-focused-column "on-overflow": it will now snap a window to the center of the screen when it can't fully fit together with the adjacent window.
    • @zzzsyyy added a drag-lock input flag for touchpads that enables libinput drag lock.
    • @JohnDowson added a calibration-matrix input setting for tablets. See the wiki page for details.
    • @valpackett implemented the necessary D-Bus API to apply configuration changes from the Displays panel in gnome-control-center. These changes are transient, similarly to niri msg output: they are not written into your niri config and will be forgotten after a restart.
    • Enabled the fancy miette errors, which makes config parsing errors printed by niri clearer and easier to understand.
    • Changed idle activity notifications to happen at most once per event loop iteration, which fixes lags on some system configurations, especially when using high polling rate mice.
    • Fixed pointer clicks going "through" window borders to the layer-shell surface below.
    • Fixed a bug where the window corner radius did not always apply immediately after changing the config.
    • Fixed one of the longest-standing issues where a fixed preset column width did not take the focused window's border into account. (This went unfixed for so long because it required a refactor to column width handling.) From now on, all ways to set a fixed window size correctly operate on the window excluding the borders.
    • Fixed window focus inside a column jumping down when a window above disappeared. This seems to have been the longest-standing bug in niri to date: it was there from the very first commit of the layout code, three days after I created the niri repository.
    • Fixed the Enter key press to confirm exiting niri also making its way to the window underneath and potentially triggering some action there.
    • Fixed a panic when using animations with overdamped springs.
    • Fixed the niri-session script incompatibility with POSIX sh (thanks @z-erica).
    • Fixed Mod + middle mouse button (to start a view scroll gesture) activating the window under the cursor on press.
    • Named workspaces no longer forget their original monitor when a new window opens on them. This change makes named workspaces "stick" to their original monitor more. For example, disconnecting a monitor with named workspaces, then doing some work, then connecting that monitor again, will move its named workspaces back. After this change, the only way to update a named workspace's original monitor is to explicitly move a named workspace to a different monitor with a bind.
    • Actions that move a workspace across monitors (like move-workspace-to-monitor-right) now update the workspace's original monitor even if the movement itself did nothing (for example, you tried to move to monitor right, but you were already on the rightmost monitor).
    • When live-reloading the config, niri now parses the config on a different thread, preventing a microstutter.
    • Niri will now send windows a single frame callback when asking them to resize or change state, even when they are currently invisible. This became relevant with tabbed columns where invisible windows (from other tabs) influence the column size.
    • Fixed windows receiving duplicate configure events when requesting un/fullscreen in some cases. (They weren't wrong events, just unnecessary.)
    • Fixed backend detection logic ignoring WAYLAND_SOCKET (thanks @bbb651).
    • Corrected behavior when opening or closing windows while no outputs are connected. Before, this likely caused problems and maybe even panics, but I haven't verified it.
    • @notpeelz fixed logging initialization happening too late and potentially missing one warning.
    • Changed client-server tests to work in-memory, without creating and using on-disk Wayland sockets. This fixes the problem where on highly multi-threaded CPUs it was possible to run out of Wayland socket numbers and fail the test.
    • Updated Smithay:
      • Fixed an IME double-input bug in Chromium and Chromium-based apps.
      • Fixed a panic after ~50 days of uptime due to an integer overflow.
      • Implemented the idle-notify v2 protocol which lets tools monitor the user's input activity, ignoring any idle inhibitors.
      • Implemented the ext-data-control protocol (same as wlr-data-control but graduated).

    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!

    Downloads
  • v25.01 e05bc269e6

    Stable

    andrei released this 2025-01-11 23:53:25 +07:00 | 1105 commits to main since this release

    📅 Originally published on GitHub: Sat, 11 Jan 2025 17:27:52 GMT
    🏷️ Git tag created: Sat, 11 Jan 2025 16:53:25 GMT

    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... hang on, how come we jumped from v0.1 all the way to v25?

    Starting now, niri escapes ZeroVer is switching to year.month versioning. In 25.01, the "25" is year 2025, and "01" is month 01 (January). So version 25.01 tells you that this release was tagged in January of 2025.

    Hotfix releases will use the third component. For example, the first hotfix for the 25.01 release would be called 25.01.1.

    There are a few reasons for this change.

    • For niri, semver isn't very useful. Big and small features are added every release, and so far we've managed to avoid any breaking changes to the config file. Calendar versioning at least tells you how old of a version you're running.
    • v0.1.x left no place for a hotfix version. I couldn't even put v0.1.10.1 into Cargo.toml because it has four components instead of three. The new versioning has just two components, leaving one extra for the hotfix version.
    • I feel like niri is now sufficiently featureful to graduate from v0.1. :) I expanded the Status section of the README to cover some of the frequently asked "is this thing supported" questions.

    Similar versioning is also used in other projects like Helix, NixOS and Ubuntu.

    With this change, the niri releases remain unscheduled: once every few months, and not bound to any particular cycle. Whenever I feel that it's a good time for a new version.

    Note

    Packagers: niri now requires Rust 1.80. Also, there are new environment variables to override the niri version string and commit: NIRI_BUILD_VERSION_STRING and NIRI_BUILD_COMMIT. The new Packaging niri wiki page shows how to use them, along with everything else important for packaging.

    New niri tests need XDG_RUNTIME_DIR to be set. You can use export XDG_RUNTIME_DIR="$(mktemp -d)".

    If some tests fail with Err::AlreadyInUse on a heavily multi-threaded CPU, set RAYON_NUM_THREADS=1. This is tracked in #953.

    Floating windows

    Floating windows are here! It took a big refactor and a good month of hard work, but the most liked niri feature request is done.

    Like other WMs, niri will auto-float dialogs and fixed-size windows. With no extra configuration, this release does away with most of the annoying dialog scrolling.

    https://github.com/user-attachments/assets/d560582c-07fd-4fcb-bf86-9ed305df515f

    Opening LibreOffice no longer causes the view to shift an ultrawide worth to the right.

    Being a scrolling WM, there were several options and design decisions to consider for how floating windows should work. I opted for a setup familiar from other tiling WMs: floating windows are on a separate "layer" that always shows on top of the tiled windows, and the floating layout does not scroll. Each workspace/monitor has its own floating layout, just like each workspace/monitor has its own tiling layout.

    Finally, you can properly showcase your scrollable-tiling WM setup.

    There's a surprising number of features and small details that go into a good floating experience. Things like correct parent-child stacking, focus-follows-mouse activating but not raising the window, or restoring the floating size and position after moving the window to the tiling layout and back.

    https://github.com/user-attachments/assets/376ef176-c546-400e-a977-b21a679dda81

    Since floating windows live on a workspace, and workspaces can move between monitors, it's important that floating windows never end up "out of bounds" and unreachable outside the monitor.

    Internally, niri remembers floating window positions relative to the monitor size, and will always push windows slightly away from the monitor edges. This way, windows are always visible, and moving the workspace to a smaller monitor will roughly preserve the window layout. Furthermore, moving the workspace to a smaller monitor and back will restore the original window positions exactly.

    In the following demo, I'm resizing a nested niri with three floating windows, simulating monitor resolution changes.

    https://github.com/user-attachments/assets/3c4d43bf-4e95-4682-acc2-4598ec06cd20

    There's a set of actions for focusing the floating or the tiling layout, and for moving windows around. The updated default config includes switch-focus-between-floating-and-tiling bound to ModShiftV and toggle-window-floating bound to ModV. All relevant existing binds keep working when the focus is on the floating layout, e.g. focus-column-right will activate the next floating window to the right.

    Additionally, on a mouse, you can easily move a window between floating and tiling by right-clicking while dragging it. You can tell which of the two it "targets" by the presence of the tiling insertion hint.

    https://github.com/user-attachments/assets/c09496b1-db40-48a5-9ba4-90ef55768dac

    There's a new is-floating window rule matcher, and new open-floating and default-floating-position rules.

    You can use open-floating to float some window that isn't covered by the auto-floating heuristics, like the Firefox Picture-in-Picture player. And default-floating-position supports putting floating windows relative to the four corners of a monitor:

    // Open the Firefox Picture-in-Picture window at the bottom-left corner of the screen
    // with a small gap.
    window-rule {
        match app-id="firefox$" title="^Picture-in-Picture$"
    
        open-floating true
        default-floating-position x=32 y=32 relative-to="bottom-left"
    }
    

    Meanwhile, real tiling WM users like @algernon can set a blanket open-floating false rule to disable all auto-floating heuristics. Rest assured that our new set of 3135 snapshot tests across all possible window opening settings will keep this working.

    All in all, this release contains a fairly complete per-workspace floating layout. Going forward, we can expand the functionality, for example by adding a sticky/show on all workspaces window flag. Or perhaps by putting modal dialogs as floating right into the scrolling layout.

    Also, when resizing tiled windows, their height is now clamped to the monitor height. It used to be unlimited so that you could take window screenshots larger than the monitor size, but now you can do that with a floating window.

    Layer-shell improvements

    This release has several fixes to layer-shell handling.

    • @cmeissl fixed the problem where the pop-up menu on Waybar and other GTK 3 bars would sometimes get stuck and fail to open. The way it was fixed disables keyboard navigation in those menus, but this is consistent with other compositors like Sway.
    • @cmeissl also fixed some clients crashing when opening nested pop-up menus (like lxqt-panel app menus).
    • @calops fixed niri not always activating the window below when clicking through layer-shell surfaces. Previously, that code didn't account for the surface's input region.
    • Pop-up menus from all layer-shell surfaces now render on top of regular windows. So putting Waybar at the top layer is no longer necessary for usable context menus.
    • Niri will now give bottom and background layer-shell surfaces on-demand keyboard focus and allow them to take pop-up grabs.
    • Certain actions like focus-column-right will now move the focus from an on-demand layer-shell surface back to the main layout, allowing you to "escape" layer-shell with just a keyboard.

    Combined, these improvements make the desktop icons components from LXQt and Xfce "just work" on niri. Thanks @stefonarch from LXQt for helping me test this and working on the LXQt niri session.

    Xfce desktop icons with a context menu, terminal, and file manager on niri.

    Layer rules

    At last, you can block out layer-shell notifications from screencasts just like windows:

    // Block out mako notifications from screencasts.
    layer-rule {
        match namespace="^notifications$"
        block-out-from "screencast"
    }
    

    Layer rules work very similarly to window rules but with a different set of matchers and properties. See the wiki page for more details.

    You can currently match by layer-shell namespace, and set block-out-from and opacity. To find out the namespace, use niri msg layers which lists all currently open layer-shell surfaces.

    Drag-and-drop focus switch

    Drag-and-dropping something will now focus the output where you dropped it. This makes dragging a Firefox tab to a different output open it on that output.

    https://github.com/user-attachments/assets/de062b76-d3dc-4025-bbb0-289b04209c87

    Successful drag-and-dropping (when the target window accepts the drop) will also now activate the target window. This should reduce the confusion when you try to press some app shortcut right after a DnD and it ends up in the wrong (previous) window.

    IPC and niri msg improvements

    I've expanded the IPC interface and niri msg a bit in this release:

    • Added the process ID and whether the window is floating to niri msg windows.
    • Added niri msg layers which shows information about layer-shell surfaces.
    • Added the set-window-width, switch-preset-window-width, and center-window actions that mirror the corresponding -column actions, but can accept their target window by --id.
    • Actions that can accept negative sizes like niri msg set-column-width -10% no longer require a double-dash before the negative size. (I set the argument parser option that allows these arguments to start with a hyphen.)

    Mouse click bindings

    Thanks to @bbb651, you can now bind actions to mouse clicks:

    binds {
        Mod+Ctrl+MouseLeft { close-window; }
        Mod+MouseMiddle    { maximize-column; }
    }
    

    These mouse-click binds will operate on the window that was focused at the time of the click, not the window you're clicking.

    Window swapping

    @rustn00b added the swap-window-left/right actions that swap windows between two adjacent columns. They can be useful for a master/stack-like layout.

    https://github.com/user-attachments/assets/7c7db95f-805a-4b72-8c05-a9eec402259c

    New window focusing

    Thanks to @cmeissl, niri will now pass an xdg-activation token to the processes it spawns (with spawn binds or niri msg action spawn). As such, new windows that correctly activate themselves from the token will now get focused even when opening on a different workspace, or from a fullscreen window.

    When developing Wayland applications, you can verify that you use the xdg-activation token correctly with the new strict-new-window-focus-policy debug flag. It disables all heuristic automatic focusing, so only those new windows that use xdg-activation will get focused.

    Lazy PipeWire initialization and resilience

    Instead of at compositor startup, niri will now initialize PipeWire on-demand (at the first screencast attempt). This makes it possible to run PipeWire after niri startup (e.g. with spawn-at-startup "pipewire").

    Also, niri now handles PipeWire restarts, and will reinitialize PipeWire next time it's needed. So restarting PipeWire should no longer make it impossible to screencast.

    Other improvements in this release

    • Added the empty-workspace-above-first layout option (thanks @FluxTape). If set, niri will always keep an empty workspace at the very start, in addition to the empty workspace at the very end.
    • Added the focus-window-previous action (thanks @zeroeightysix).
    • Added the focus-monitor-next/previous, move-window/column/workspace-to-monitor-next/previous actions (thanks @julianschuler). These use internal monitor ordering (that should be consistent across reboots) and loop around.
    • Added the un/set-workspace-name actions that can be useful in scripts to change workspace names at runtime (thanks @rustn00b).
    • Added the open-focused true/false window rule to force or prevent a window from getting focused on opening.
    • Added the default-window-height window rule that works similarly to the existing default-column-width, but for height.
    • expel-window-from-column now always expels the bottom-most window in a column (rather than the focused one), and doesn't switch focus to the expelled column. This way, it becomes the exact opposite of consume-window-from-column. If you prefer the old behavior, use consume-or-expel-window-right.
    • Changed is-active-in-column window rule matcher to match true during the initial window opening (thanks @TheZoq2). This makes sense as a new window always opens in a separate column, therefore it is active in its column right away.
    • Niri will now power on monitors when the session is unlocked (thanks @salman-farooq-sh). This helps with fingerprint and other unlocking methods that don't generate an input event.
    • Fixed xdg-desktop-portal notifications by setting niri to use the gtk impl for the Notification portal.
    • Fixed border { off; } unable to override an earlier border { on; } window rule.
    • Fixed a crash when connecting two monitors with identical make/model/serial (or one monitor through both HDMI and DP). Niri will detect this and automatically unname the second monitor. So the disable-monitor-names debug flag is no longer necessary for such setups.
    • Fixed windows on other workspaces losing their Activated state (becoming visually inactive). This was a v0.1.10 regression. Windows keep their Activated state when switching workspaces to reduce unnecessary visual changes.
    • Fixed a crash when calling move-window-to-workspace to an unfocused monitor.
    • Fixed several actions applying to the windows below while interactively moving a window.
    • Fixed a potential crash when disconnecting a monitor while screencasting it.
    • Fixed potentially misaligned borders on the interactively moved window when hot-reloading the output scale config in-flight.
    • Fixed a wrong visual position when disconnecting a monitor while the primary monitor is in the middle of a workspace switch.
    • Fixed initial window size requested by niri not honoring min/max size from window rules.
    • Fixed potential crash when dropping an interactively moved window on a different monitor with a workspace switch in progress.
    • Added Xrgb and Xbgr as preferred formats for the primary plane (previously only Argb and Abgr were preferred). I haven't seen it affect anything thus far, but maybe it fixes a bandwidth limitation on some setups.
    • Refactored animation timing. This shouldn't have changed much visually (maybe some tiny issues were fixed), but it made the system much more robust, and let me write tests for many more scenarios. I wrote a wiki page about the new system if you're curious about the technical details.
    • Added the force-pipewire-invalid-modifier debug flag that forces PipeWire screencasting to use the invalid modifier.
    • Added the restrict-primary-scanout-to-matching-format debug flag.
    • Debug flags for enable-overlay-planes and disable-cursor-plane now apply immediately, without having to reconnect the monitor.
    • Updated Smithay:
      • Fixed some clients crashing when opening nested pop-up menus (lxqt-panel app menus).
      • Implemented presentation-time v2 protocol (well-defined refresh rate reporting for VRR).
      • Fixed xdg-foreign assigning window parents in reverse, so now portal dialogs are correctly parented.
    • Updated rustix:
      • Fixed a crash at startup on M2 Apple and other AArch64 devices.

    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!

    Downloads
  • Stable

    andrei released this 2024-11-13 14:42:56 +07:00 | 1323 commits to main since this release

    📅 Originally published on GitHub: Wed, 13 Nov 2024 07:58:29 GMT
    🏷️ Git tag created: Wed, 13 Nov 2024 07:42:56 GMT

    This is a hotfix release for niri v0.1.10.

    • Fixed scrolling not working when the mouse {} or touchpad {} section is omitted from the config file.
    • Made the mouse cursor show up on scroll which makes scrolling work when the cursor was hidden (thanks @r-vdp).
    • Fixed a crash when holding Space in the screenshot UI.
    • Bound touch-dragging with held Mod to interactive window move.
    Downloads
  • Stable

    andrei released this 2024-11-09 22:15:23 +07:00 | 1328 commits to main since this release

    📅 Originally published on GitHub: Sat, 09 Nov 2024 16:06:10 GMT
    🏷️ Git tag created: Sat, 09 Nov 2024 15:15:23 GMT

    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.

    Interactive window moving

    While not full-blown floating window support quite yet, this is an important step towards that. You can now move windows by dragging them by title bars, or anywhere while holding Mod.

    https://github.com/user-attachments/assets/b0c36fc3-d4ca-459b-819b-fc7ee223a3d7

    To prevent accidental layout changes, the windows rubber-band a little before you drag them out.

    https://github.com/user-attachments/assets/7c50c95b-56fe-4d21-b659-ad8cd7f4fad7

    Furthermore, I made both interactive moving and resizing work on a touchscreen.

    https://github.com/user-attachments/assets/275f27fd-8c26-40a9-82c9-0cd975a96688

    Thanks to @Pajn for implementing a fairly complete proof-of-concept of this feature!

    Locked pointer location hint

    @sodiboo implemented the pointer location hint request. Apps like Blender use it to tell the compositor the final location after a locked pointer movement so that the compositor can update its own pointer location to match it.

    https://github.com/user-attachments/assets/76e92efb-7c93-4b1f-876e-fbc61912f7ea

    Laptop lid and tablet mode switch bindings

    Thanks to @cmeissl, you can now bind commands to laptop lid opening/closing and tablet mode switching. You can use this to automatically enable an on-screen keyboard when a convertible laptop enters tablet mode. See the switch events wiki page for more information and examples.

    Additionally, I implemented disabling of the internal laptop monitor when closing the lid. So your workspaces will automatically move to the external screen. If for some reason this breaks for you, set the new keep-laptop-panel-on-when-lid-is-closed debug config flag.

    Pointer hiding

    @yzy-1 implemented new cursor hiding options: hide when typing (on any key press), and hide after a set inactivity period. See the wiki page for more details.

    cursor {
        hide-when-typing
    
        // Or, after a timeout:
        // hide-after-inactive-ms 1000
    }
    

    To complement this, there are a few improvements to the hidden pointer behavior. The pointer will now show up on mouse button press, and on the contrary, it will stay hidden on programmatic and keyboard-triggered movement, like focusing a different monitor, or when using warp-mouse-to-focus.

    Input configuration improvements

    Thanks to @tazjin, @chillinbythetree and @elipp for:

    • Adding a trackball input config section.
    • Adding a scroll-button setting to mice, touchpads, trackpoints, and trackballs.
    • Adding a scroll-factor setting to mice and touchpads that you can use to speed up or slow down scrolling.

    See the input config wiki page for more information.

    Other improvements in this release

    • Tablet input no longer follows the monitor rotation: you need to rotate your graphics tablet together with your monitor. This makes convertible laptops work properly; this is also how input works on other desktop environments. Thanks @cmeissl.
    • The GTK Access portal is now explicitly set in niri-portals.conf, which makes it work. It is required for applications requesting PipeWire webcam and microphone access, such as the Firefox package on Fedora 41. Thanks @cmeissl.
    • The niri-ipc crate is now published to crates.io.
    • Active workspace is now preserved across monitor disconnects and reconnects.
    • Added a window --id argument to niri msg action consume-or-expel-window-left/right and to the IPC.
    • Added an explicit power-on-monitors action that can be useful with certain hardware. Niri still automatically powers on monitors on any input event.
    • Added support for running niri as a dinit service: files in resources/dinit/ and corresponding code in niri-session (thanks @markK24).
    • Added a disable-monitor-names debug config flag as a workaround for niri crashing when plugging in two monitors reporting the exact same make/model/serial. This issue is tracked in #734.
    • The focused window will now become visually inactive when a layer-shell app in front has keyboard focus.
    • Fixed focus-window-up-or-column-right focusing left instead of right.
    • Fixed an animation jump when expelling a narrower window from a column with uneven window widths.
    • Fixed the logind power key inhibit file descriptor leaking into processes spawned by niri.
    • Fixed window close view position restoration triggering for windows that didn't get focused upon opening.
    • Fixed a crash when an output disappears immediately after connecting.
    • Fixed used xdg-activation token memory leak.
    • Fixed lock screen clients hanging until a monitor is enabled when no monitors are enabled.
    • Updated Smithay:
      • Fixed memory leak when locking the screen.
      • Fixed occasional visual freezing of GTK and other apps.
      • Fixed a regression that made it so increasing the output scale in niri v0.1.9 didn't propagate to some clients, keeping them blurry.
    Downloads
  • v0.1.9 6a48728ffb

    Stable

    andrei released this 2024-09-14 17:59:43 +07:00 | 1436 commits to main since this release

    📅 Originally published on GitHub: Sat, 14 Sep 2024 11:44:06 GMT
    🏷️ Git tag created: Sat, 14 Sep 2024 10:59:43 GMT

    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 requires libdisplay-info.

    New IPC functionality

    In this release, I designed and implemented an event stream in niri's IPC which lets you continuously listen to compositor events like workspace or window changes. The event stream enables taskbar applications to make correct and efficient widgets for niri.

    I implemented the niri modules for workspaces, focused window, and keyboard layout in Waybar, available in its fresh 0.11.0 release. Pull requests are open for yambar and ironbar thanks to their contributors.

    https://github.com/user-attachments/assets/6fe9fc99-9246-45e3-a3b2-48ec79282cdd

    IPC windows and workspaces now have unique IDs, and all individual window and workspace actions can address a specific window or workspace by its ID. On the command line, a new niri msg windows command lists all windows with their IDs, and window commands accept an --id <ID> argument to target a specific window, for example:

    $ niri msg action fullscreen-window --id 2
    

    Also, there's a new niri msg action focus-window --id <ID> action and a new niri msg keyboard-layouts command.

    I wrote some documentation on the programmatic access to the niri IPC socket. I also set up an online rustdoc for the niri-ipc crate where I documented every IPC type and request. Please refer there when working with the niri IPC.

    Unfortunately, while adding ID arguments to IPC actions, I discovered a backward incompatibility trap in serde-json. The default enum representation—externally tagged—prevents you from changing a unit variant to a struct variant, because the representation gains an extra dictionary. "FullscreenWindow" becomes {"FullscreenWindow":{}}, and the former does not parse with the new definition.

    I decided to make a JSON breaking change, converting all unit Action enum variants to struct variants (with or without fields). I doubt anyone used them directly through JSON since these actions could only address the focused window or column. All enum variants that already had fields are unchanged, and the niri msg CLI is also unaffected.

    With this breaking change out of the way, any further JSON additions should remain backward compatible, so that existing scripts and programs communicating with niri will keep working with new niri versions.

    Height distribution changes

    One common complaint about niri's layout was the ability to make a multi-window column not "add up" to the total height of the monitor. The behavior was also fairly unobvious: with two windows in a column, you resize one, and the other resizes along as expected. Then, you resize the other, but the first window doesn't react. It felt like a bug.

    Last time there was a design problem (unwanted scrolling with focus-follows-mouse), we quickly found a solution by brainstorming in a Discussion. So, I made a big write-up about window heights in https://github.com/YaLTeR/niri/discussions/593. While there hasn't been much discussion, the act of laying out in writing all considerations and constraints had spawned a potential solution in my mind, which turned out to work quite well.

    In this release, I reworked the window height distribution to do the expected thing in more cases. A column of two or more windows will always try to match the monitor height, as long as the minimum window sizes allow that. Resizing one window will resize all other windows in a column proportionally. The window that you resized last retains its height just like before, which lets you size one window in a column exactly to fit something, unaffected by adding more windows into the column, or moving it across monitors.

    Keep in mind that a single-window column can still be resized arbitrarily, including shorter or taller than the monitor. Until floating windows are implemented, this is necessary for some uses that require exact-sized windows.

    https://github.com/user-attachments/assets/d4f35887-a275-4531-a223-d518b6ebee8b

    Additionally, I found and fixed a small issue where windows in a column would occasionally "snap" to a smaller size when resizing.

    Preset window heights

    @TheAngusMcFire implemented a preset-window-heights layout option and a corresponding switch-preset-window-height bind, which work like the existing column width presets.

    By default, it's bound to ModShiftR, which is consistent with Shift making resize binds affect the height rather than the width. The default bind to resetting the window height therefore moved to ModCtrlR. (None of this affects you if you already have a niri config; you'll need to add any new binds manually.)

    Output names

    You might be familiar with this sight:

    $ niri msg focused-output
    Output "Unknown Unknown Unknown" (DP-1)
      ...
    

    Thanks to @cmeissl finishing the libdisplay-info bindings, this sight is no more.

    $ niri msg focused-output
    Output "Acer Technologies XV320QU LV 420615FCD4200" (DP-1)
      ...
    

    Following this, all throughout niri I implemented the ability to address outputs by name. This includes config output, map-to-output, open-on-output; niri msg output; wlr-output-management tools (wdisplays, kanshi); and xdg-desktop-portal-gnome screencasting where the screen selector will now show the monitor model and screencast session restore will remember the output name rather than the connector.

    xdg-desktop-portal-gnome monitor selector showing monitor models

    The recommended way to configure everything output-related is now by name (as shown in niri msg outputs). This way, configuration does not depend on the connector name that can be non-deterministic with multiple GPUs or when using thunderbolt docks.

    // Previously: output "DP-1" {
    output "Dell Inc. Dell S2716DG #ASOwvAqQj0Dd" {
        mode "2560x1440@143.998"
        // ...
    }
    

    I was also finally able to change the monitor sorting order to use the output name rather than the connector name, once again making it more deterministic. Note that this may swap your monitor positions if you were using multiple monitors and haven't manually configured them.

    Transactional updates

    One of Wayland's premises is that "every frame is perfect" except the first one. The compositor is in full control of the display, and window state changes are atomic and correlate to specific compositor requests.

    This allows the compositor to synchronize updates for multiple windows: render the old state until all windows update, then switch to the new state all at once, with no broken frame in between.

    However, possible doesn't mean easy, and different kinds of transactional updates need different approaches in the code. For this release, I implemented two relatively common cases.

    Resizing

    Thanks to the scrollable tiling nature, niri doesn't need to synchronize resizes among all windows on a workspace. However, windows in one column must still resize in unison: they must have the same width, and their heights must add up exactly to the monitor height.

    https://github.com/user-attachments/assets/8e1be961-5b72-4fb6-a769-a36cdc830313

    Closing

    Closing a window resizes all other windows in the column to take up the freed space. Normally, resize and close animations hide this, but if you disable animations, the flicker becomes very noticeable. The closing transaction fixes this: niri waits until other windows have resized before hiding the closed window.

    https://github.com/user-attachments/assets/97c87ce9-3cc8-415d-92c1-4cfeb2260173

    On-demand VRR

    Thanks to @my4ng, we now have on-demand variable refresh rate as a window rule.

    Some monitors flicker at the lowest VRR refresh rate, some drivers have VRR bugs, and some clients don't handle VRR too well. Now, niri can enable VRR only when a specific window is on screen (for example, a video player, or a game), thereby avoiding most of those issues.

    Configure your output with on-demand=true:

    output "Acer Technologies XV320QU LV 420615FCD4200" {
        // ...
        // This will keep VRR off unless enabled by a window rule.
        variable-refresh-rate on-demand=true
    }
    

    Then, add variable-refresh-rate true window rules as necessary:

    // Enable VRR when mpv is on screen.
    window-rule {
        match app-id="^mpv$"
        variable-refresh-rate true
    }
    

    NVIDIA flickering fix

    There was a problem with NVIDIA flickering on niri, which the user could fix by enabling the wait-for-frame-completion-before-queueing debug flag. Turns out, this was only necessary because ages ago I forgot to add a check in the code. 🤦

    Starting from this release, you should no longer need to set that debug flag, and NVIDIA GPUs should no longer flicker on niri out of the box (fingers crossed).

    Small UX improvements

    The horizontal touchpad swipe gesture will no longer go past the first or last column on the workspace.

    https://github.com/user-attachments/assets/a92349e6-bc45-40b1-89d6-57f2e7fd068c

    And focus-follows-mouse will no longer "catch" windows on workspaces as you're switching away from them, which is especially important when using the new workspaces bar modules.

    https://github.com/user-attachments/assets/ca6e4b17-13aa-476d-ada8-313c4a5f5c7c

    Other improvements in this release

    • Niri will now attempt to read the config file from /etc/niri/config.kdl when ~/.config/niri/config.kdl is missing.
    • Added an always-center-single-column layout option that makes a single column on a workspace always centered (thanks @elkowar).
    • Switching a workspace by index and back-and-forth is now animated the same way as switching a workspace up or down.
    • On-demand layer-shell surfaces will now automatically get focus when they appear, which works better with some application launchers like lxqt-runner.
    • Added a NIRI_DISABLE_SYSTEM_MANAGER_NOTIFY environment variable; when set to 1 it will suppress niri's own notification to systemd or NOTIFY_FD. This is useful for some custom systemd setups like uwsm.
    • Niri will try harder to light up monitors, which may help it get a better resolution on some multi-monitor setups on some hardware.
    • Fixed xdg-desktop-portal-gnome unable to open a file chooser from Xwayland windows.
    • Fixed crashes when a resume from suspend or a monitor power-on sends bogus vblank events on some hardware.
    • Fixed niri msg action do-screen-transition rendering wrong across monitor scale and transform changes.
    • Fixed move-column-to-workspace to a different output only moving one window and not the whole column.
    • Fixed set-window-height N% actually trying to use 100×N% height.
    • Fixed niri not informing layer-shell surfaces of changes to preferred scale and transform.
    • Portal screencasts now use damage to reduce unnecessary rendering.
    • Improved portal screencast frame timing to fix an occasional stuck wrong frame and niri sometimes sending frames too often.
    • Fixed unsync subsurfaces of layer-shell, cursors, and DnD icons not causing output redraws when they should. (Does anything even use those currently?)
    • Exclusive layer-shell surfaces are now preferred for keyboard focus to on-demand layer-shell surfaces on the same layer.
    • Updated Smithay:
      • Fixed layer-shell pop-up menu cursor input being slightly offset.
      • Fixed inverted scroll direction when running the compositor as a nested window.
    Downloads