Decided not to pursue this for now. The idea was to extract input handling from
input/mod.rs into the respective modules, plus get rid of manual cursor shape
resetting, but there are some roadblocks:
- The pointer is locked when those methods are called, meaning you can't get
the current location (have to store from enter/motion) and, more crucially,
can't set grabs—we need this for the Overview.
- Gesture begin/end is handled at the Wayland object level, meaning
PointerTargets get to deal with "improper" events themselves, reducing their
utility. cosmic-comp spawns an idle callback for this which seems awkward and
is asking for problems.
- We're not actually removing much code at all, only adding more code for the
blanket wrappers and such.
* Add per-axis scroll speed config for input devices.
Accepts negative values to inverse scroll direction.
Properly complements/overrides global `scroll-direction` setting.
Includes docs and tests.
* Update per-axis scroll factor implementation after testing
- Refined configuration structure in niri-config
- Updated input handling to use per-axis scroll factors
- Added comprehensive test snapshots
- Updated documentation with per-axis examples
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify per-axis scroll factor implementation per review feedback
- Make documentation concise and clear
- Remove unnecessary comments and test helper functions
- Use inline snapshots for tests
- Rename get_factors() to h_v_factors() for clarity
- Remove unnecessary .clone() calls (ScrollFactor is Copy)
- Reduce test count to essential cases only
- Fix comment about window factor override behavior
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove unnecessary ScrollFactor::new() helper function
The maintainer prefers minimal code, so removing this helper and
constructing ScrollFactor directly in tests.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix scroll factor behavior - all settings now multiply with window factor
Per maintainer feedback, both combined and per-axis scroll settings
should multiply with the window-specific scroll factor, not override it.
This ensures consistent behavior regardless of configuration method.
Also removed the now-unused has_per_axis_override() method.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Final cleanup: remove redundant comments and unused snapshot files
- Removed unused snapshot files (now using inline snapshots)
- Removed redundant inline comments in tests
- Simplified test descriptions to be more concise
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Convert scroll factor parsing tests to use assert_debug_snapshot
Updates parse_scroll_factor_combined, parse_scroll_factor_split, and
parse_scroll_factor_partial tests to use assert_debug_snapshot instead
of manual assert_eq comparisons, as requested in PR review.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Convert to inline snapshots as requested
- Convert all scroll factor parsing tests to use inline snapshots instead of external files
- Remove external snapshot files to keep test directory clean
- All tests still pass with inline snapshot assertions
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix missed assert_eq in parse_scroll_factor_mixed test
Converts the remaining assert_eq calls to assert_debug_snapshot
with inline snapshots in the mixed syntax test function.
Also fixes raw string delimiters from ### to #.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Convert scroll_factor_h_v_factors test to use assert_debug_snapshot
Makes all scroll factor tests consistent by using snapshots instead
of assert_eq for better maintainability.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fixes
---------
Co-authored-by: Bernardo Kuri <github@bkuri.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update index.md
a simple suggestion for a location-agnostic index page
* Update docs/wiki/index.md
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add wiki based on mkdocs
* wording fixes
* fix github bg color on narrow
* Fix left sidebar section headers being bigger than pages
* fix hover accent
* fix list rendering on fractional layout
* fix videos
* fix automatic full links
* remove redundant commented css
* improve dark mode contrast
* update pygments for better child node coloring
* update logo
* remove blank lines
* add systemd language hint
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* feat: config reload ipc event
* cleanups
* Rename and move the new config option
* rename to ConfigLoaded and emit at connection
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add window sizes and positions to the IPC
* basic fixes
* report window_loc instead of window pos
* clean ups
* make scrolling indices 1-based
* add printing to niri msg windows
* don't include render offset in floating tile pos
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
To be used for animation tests that need animation snapshots.
The renderer is optional to avoid creating it thousands of times in tests when
it's not needed, plus it can deadlock in mesa apparently.
* niri-config: add disable-set-bpc option
setting bpc to 8 bricks some OLED displays driven by amdgpu
* change to keep-max-bpc-unchanged and add to wiki
* fmt
* Update wiki/Configuration:-Debug-Options.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* refactor config load logic, and properly watch the system config path
* move config creation to niri-config, and make the errors a bit nicer
notably, "error creating config" is now a cause for "error loading
config", instead of it being one error and then "error loading config:
no such file or directory". also, failure to load a config is now
printed as an error level diagnostic (because it is indeed an error, not
just a warning you can shrug off)
* refactor watcher tests; add some new ones
now they check for the file contents too! and i added some tests for
ConfigPath::Regular, including a messy one with many symlink swaps
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Without explicit sync, we have no way to signal the PipeWire consumer when the
rendering is done. So, wait until it's done before giving it the frame.
This should fix flickering screencasts on NVIDIA.
We're gonna need to store these in the future, and the lifetime on the pw-rs
Buffer prevents us from easily doing that. Besides, we'll need access to
metadata which pw-rs doesn't expose yet.
Makes Orca work with niri:
- keyboard watching and announcing everywhere (not just GTK 3 windows)
- grabs for the Orca modifier (with double-press to pass through) and keystrokes
* Add Nvidia.md leaf file, add links in sidebar & getting started
* squash review-commits from gh to one commit
* heap reuse ratio from 1 => 0 to match currently shipped solution
* Update wiki/Nvidia.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Set logind LockedHint on lock/unlock
* fixup! Set logind LockedHint on lock/unlock
- use warn!() instead of error!()
- extract dbus call into a separate method
* fixup! Set logind LockedHint on lock/unlock
- Update LockedHint in refresh_and_flush_clients
* fixup! Set logind LockedHint on lock/unlock
woops
* fixup! Set logind LockedHint on lock/unlock
- only call SetLockedHint if niri was run with `--session`
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add nushell completion support
Adds `clap_complete_nushell` crate and implements it into the `niri
completions` command.
* Add nushell to flake.nix autocompletions
* Convert to `TryFrom`
* Fix linting errors
* Move types down
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Minor change so that `get_pointer()` (which has a lock) does not get
called twice. Also moved the call to `current_location()` to the scope
where it is needed.
* feat: add hint to disable "Important Hotkeys" in the default config file
* Update resources/default-config.kdl
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Fix: Correct typo in xwayland module and update documentation
This commit includes several improvements:
1. **Code Fix (clippy):**
- I corrected a typo in `src/utils/xwayland/mod.rs` from `OFlags::WRONGLY` to `OFlags::WRONLY`. This was identified by `clippy` during the build process.
2. **Documentation Updates:**
- **README.md**:
- I clarified the sentence about finding help in the Matrix channel to be more inviting for new users.
- I corrected a future date typo in the Media section for an interview (June 2025 to June 2024).
- **wiki/Getting-Started.md**:
- I changed the Russian month "мая" to "May" in an example output for better international readability.
- I improved keybinding notation for monitor focus/move keys (e.g., Mod+Shift+H / J / K / L) to avoid ambiguity.
- I updated `apt-get` to `apt` in Ubuntu dependency installation commands for modern practice.
No new typos were found by `typos-cli` in this pass.
* Revert README&GS.md to previous version
* Apply rustfmt
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
The oklch color space often creates weird values when parsed by csscolorparser.
clamping the output to values between 0 and 1 should fix inconsistent color handling
on borders.
* Expand screenshot UI to handle more moving actions
Currently, screenshot UI handles MoveColumn{Left,Right} and
MoveWindow{Up,Down} which move the screenshot selection as if it were a
floating window. Expand this to include MoveColumn*OrToMonitor* and
MoveWindow*OrToWorkspace* and adjust their logic to move the screenshot
selection.
* Update src/input/mod.rs
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Instead of hard-linked locations tried to reword to simplify for both XDG-compliance and/or non-compliance. Additionally, replace 'ln ...' references with "add-wants" from systemctl. This advantage is that it creates all the right directories and the only thing the user needs to worry about later is possibly removing a symlink manually.
* force xdg deactivation on invisable workspaces
This debug option provides a workaround for many Chromium-based chat
applications that fail to show notifications when they're active in
a workspace that's not currently visible and don't have keyboard focus
Signed-off-by: Alex Yosifov <sashomasho@gmail.com>
* fixes
---------
Signed-off-by: Alex Yosifov <sashomasho@gmail.com>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
gtklock doesn't mind the fact that it got denied the lock, and just creates a
new lock surface anyway. And we happily replace the running lock with it.
* add option to hide unbound actions in hotkey overlay
* fix config test, add some docs
* Add kdl language hint
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Improve docs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* hide_unbound -> hide_not_bound
* forgot to rename in wiki
* filter actions before calling format
* use any instead of contains
* retain instead of filter
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Overlooked this when reviewing. This change is not cfg-breaking (since you
can't bind these directly), but it does break calling these actions through
IPC. I don't imagine they are widely used though, and the original PR author
who also implemented urgency for bars said he didn't use these actions either.
According to the zwlr_layer_surface_v1 documentation: Unmapping a
layer_surface means that the surface cannot be shown by the compositor
until it is explicitly mapped again. The layer_surface returns to the
state it had right after layer_shell.get_layer_surface. The client can
re-map the surface by performing a commit without any buffer attached,
waiting for a configure event and handling it as usual.
Before this commit, no configure event was sent when a client performed
a commit without any buffer attached.
* Implement IPC for the overview state
* Update Overview IPC to maintain naming consistency, renamed OverviewToggled to be more clear, simplify overview state request on the server, consolidate ipc refresh
* Fix Overview is_open in IPC client
* Change opened to is_open
* Update niri-ipc/src/lib.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update niri-ipc/src/state.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update src/ipc/client.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update src/ipc/client.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add overview state to EventStreamStatePart replicate and apply
* Fix formatting
* Rename Overview to OverviewState
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Questionable exercise converting to serde with much more boilerplate, and
breaking compat with older zvariant versions. Plus maybe this will be
undeprecated back.
I think this makes for marginally better readability, since you don't
have to wonder whether config_errored is set anywhere else. It's also
slightly terser.
Workspace gaps are dependent on screen size, so it makes sense to make shadows
depend on the screen size to, to avoid them filling more or less of the gap.
Compared to third-party implementations such as waycorner:
- It works during interactive window move (no surfaces receive pointer
focus in this case, so this cannot work through layer-shell).
- It works during drag-and-drop.
- It disables itself over fullscreen windows.
- It does not prevent direct scanout.
This design makes more sense spatially, and is required for the
Overview. Gaps also make it clear how clipping windows to workspace
bounds works.
Background and bottom layer-shell surfaces get duplicated for each
workspace, while top and overlay stay "on top".
Fixes cases like: do a quick movement with mouse, then hold it in-place for a
while (no events generated), then release the gesture (it uses all that
built-up speed). This also happens with DnD scroll and makes it go further than
intended.
* screenshot: make selection area modifiable via keybinds
* input: run fmt
* Reimplement screenshot UI binds in a better way
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* wiki: Documented flags for Electron based applications
* Update wiki/Application-Issues.md
Co-authored-by: Kent Daleng <lolexplode@gmail.com>
* wiki: remove Arch specific files for Electron
* Apply suggestions from code review
---------
Co-authored-by: Kent Daleng <lolexplode@gmail.com>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Currently we can't use logging in paths like niri msg that have meaningful
stdout. Logging to stderr makes that possible. Even if we don't want to log
anything in niri msg code paths, it's easy to have something accidentally log.
* add check-links step, fix some links
* don't depend on build right now
* fix fragment
* reintroduce dependency for build
* don't only check links on push to main
* maybe this is a more sensible dependency tree for this stuff
* change commented suggestions, try v2.0.2 for action
* describe why we're on v2.0.2
* revert to %E2%80%90 (works with lychee anyway)
* wiki testing
* wiki updates
* use .md with anchors, revert sidebar
* bump wiki action
* add some more anchors, fix some language
* change links to be more descriptive by themselves
This both avoids sending frame callbacks to surfaces invisible on the offscreen
(fixing Firefox with subsurface compositing in the process), and fixes
searching for split popups during the resize animation.
This is the second part of the damage equation: now the offscreen element
itself reports correct damage, so partial redraws to the texture don't cause
full redraws of the texture element itself.
* Allow disabling tap-and-drag
Similar to https://github.com/YaLTeR/niri/pull/1088, this adds a new
touchpad `drag` configuration option that configures tap-and-drag
behavior.
Currently tap-and-drag is always enabled when the `tap` setting is
enabled, but other compositors allow setting this separately.
* Update wiki/Configuration:-Input.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* feat: added top, left, bottom, right alignement options
* feat: implemented extra alignement
* feat: added example
* doc: updated documentation with extra alignements
* doc: moved example in wiki and typo correction
* fix: relative position should be positive and not negative
* fixes
---------
Co-authored-by: Martino Ferrari <martinogiordano.ferrari@iter.org>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This actually doesn't matter in most cases currently, because it more or less
checks for *anything* to have a keyboard focus, so if you have some focused
window while clicking on a mako notification, that already qualifies.
Signed-off-by: Ivan Molodetskikh <yalterz@gmail.com>
The gradient area should be relative to each tab's geometry. In most cases
these geometries will all match, but not when some tabs have a different size,
for example when they have a fixed size.
Lets hidden windows respond to events like resizes immediately. This is mainly
relevant for tabbed columns.
This commit doesn't actually force sending the frame callbacks in case we don't
redraw. We'll see if this is a problem or not.
I looked at cosmic-comp as a sanity check and they do the same thing,
I ended up yoinking their function name because it reads better,
not sure about "unrestricted" vs "privileged".
The way named workspaces are generally used makes them more "attached" to their
original output.
For example, you have a two-monitor setup with named workspaces on both. When
you disconnect the monitor to go somewhere and work for a while, then return,
you probably want your named workspaces to return to where they were on your
second monitor.
This is in contrast to unnamed workspaces which are more transient and should
more easily follow you wherever you're working.
Moving is an explicit action that puts the workspace on a specific monitor. It
makes sense to update the original output even if the workspace already happens
to be on the target monitor.
* Added move-workspace-to-index and move-workspace-to-monitor IPC actions
* Added redraws to the workspace handling actions, fixed tests that panicked, fixed other mentioned problems.
* Fixed workspace focusing and handling numbered workspaces with `move-workspace-to-index`
* Fixed more inconsistencies with move-workspace-to-monitor
* Added back `self.workspace_switch = None`
* Reordered some workspace cleanup logic
* Fix formatting
* Add missing blank lines
* Fix moving workspace to same monitor and wrong current index updating
* Move function up and add fixme comment
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
I know of a single compositor that supports `WAYLAND_SOCKET` but not
`WAYLAND_DISPLAY`: https://gitlab.freedesktop.org/mstoeckl/windowtolayer
This should also make niri more robust against accidentally setting
`WAYLAND_SOCKET` when starting as a session, before programs could fail
if they preffered `WAYLAND_SOCKET` over `WAYLAND_DISPLAY`
* Highlight that the path in niri.service should be checked
Having just installed niri I ran into this issue. When building from source on Ubuntu the install location using the instructions in this document is /usr/local//bin/niri.
However niri.service pointed to /usr/bin/niri so my session would not start at all. Hopefully this update helps
* Update wiki/Getting-Started.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add un/set workspace name actions
* Add SetWorkspaceName reference to proptests
* Simplify unname_workspace
* Add ewaf version of set first workspace name test
* Simplify more
* Fix comment
* Make workspace in set-workspace-name a positional option
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Swap the active window with the a neighboring column's active window.
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Take into account PR comments
- no longer behave like an expel when a swap is made in a direction
where there is no column to swap with
- fix janky animation
* Set is-active-in-column to true for unmapped windows
* Update wiki/Configuration:-Window-Rules.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
A small part of the window always remains on-screen regardless of the working
area changes.
Interactive move lets the user position the window anywhere; automatic actions
like toggle-window-floating and dialog opening try to put the window fully
on-screen.
The size-fraction canonical floating window position remains unclamped, and
clamping happens when recomputing the logical position.
* Update Configuration:-Key-Bindings.md
Added Leve5 notes with scant instruction on how to use.
* Update wiki/Configuration:-Key-Bindings.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
These tests make a real Niri instance and real Wayland clients (via manual
wayland-rs implementation), both on the same event loop local to the test. This
allows testing the full Wayland interaction, including arbitrary event ordering
and delays.
To start off, add a massive powerset test for the settings that influence where
a window may open.
This helps with:
- System setups starting PipeWire late (after niri startup, but before any
screencast).
- Tests which don't even want to start PipeWire.
Leave the Workspace to do the workspace parts, and extract the scrolling parts
into a new file. This is a pre-requisite for things like the floating layer
(which will live in a workspace alongside the scrolling layer).
As part of this huge refactor, I found and fixed at least these issues:
- Wrong horizontal popup unconstraining for a smaller window in an
always-centered column.
- Wrong workspace switch in focus_up_or_right().
most of the time the activation token is passed
while the window is still unmapped. in this case
store the intend to activate the window for
later retrieval on map.
Okay, this might be one of the oldest layout issues to have remained uncaught.
Well, maybe as I add more randomized tests, I'll catch even more of those.
In sway, focus-follows-mouse keeps working during DnD, but not in niri.
So it can be surprising when you DnD something into another app, but it
doesn't get automatically focused. This commit fixes that.
Even if the DnD is not validated, or if there's no target surface (e.g.
dropped on the niri background), focus the target output, since that's
how Firefox's drag-tab-into-new-window works for example.
This way, expel becomes symmetric with consume. This is also how it
works in PaperWM. Though, in PaperWM if the expelled window was focused,
it will remain focused, while in this commit it is never focused, making
it the exact opposite of consume.
Use consume-or-expel-window-right for the old expel behavior.
* Unhide the pointer on scroll events
Since we reset the surface under the pointer when we hide the pointer
(see update_pointer_contents), scroll events don't work when the pointer
is hidden.
So to make scrolling work, we make sure that we unhide the pointer when
a scrolling event occurs.
* Update src/input/mod.rs
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This has the following benefits:
1. connector_connected() is now more closely mirroring
connector_disconnected() in that it merely lights up the connector,
and doesn't check if the connector should be off from the config.
2. We can use more complex on/off logic that depends on multiple
connectors. For example, this commit adds logic to only disable the
laptop panel on lid close if there are other connected outputs.
We don't want to disable the laptop panel on lid close if it's the only
connected output because it causes screen lockers to create their
surface from scratch on normal laptop unsuspend, which is undesirable
and also confuses some screen lockers.
valid tokens will stay around until explicitly cleaned-up.
remove the token after it has been successfully used
or we consider it timed out to prevent leaking the memory
used by the activation tokens
For keyboard-only use, especially with warp-mouse-to-focus, the
intention is that the cursor stays hidden from keyboard and other
automatic actions, and only shows up with an actual mouse movement.
I originally preferred on-key-press, but when-typing feels more natural
and matches sway. This setting had not been in a stable release yet so
this is not stable release cfg breaking.
* support spawn action on switch events
this adds a new config section named `switch-events`
that allows to bind `spawn` action to certain switch
toggles.
* Expand docs
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* niri-config: add trackball configuration struct
The available options are mostly the same as for mice. I've verified that each
option is applicable to trackballs in the libinput CLI.
* input: apply trackball config settings
* Added dinit services
* Added dinit support to niri-session
* Replaced shutdown script for dinit with a single command execution
* Added dinit service files to Getting Started install tables
* Fix typo in resources/dinit/niri
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Fixed mistakes in wiki/Getting-Started.md
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* niri-session does not start dinit anymore
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This allows `niri-visual-tests` to still be built and run in the dev
shell where it's necessary, as well as brings back the nightly `rustfmt`
used by the project
We can't use `fenix` again though as it doesn't wrap `ld` like nixpkgs
and rust-overlay do; without it, the way we link `dlopen()`'d libraries
breaks
The (debug) package is already set as a check and will still be built
with this, but Nix will now also check other outputs automatically --
such as the dev shell
Some highlights include:
- Removing some unnecessary dependencies of the package itself
- Allowing for overriding the package
- Adding Cargo feature toggles
- Installing all niri-related resources
- Avoiding `LD_LIBRARY_PATH` hacks
Previously, inputs like Crane and Fenix were used to only build the
`niri` package. This isn't really required, and can easily be replaced
by nixpkgs' `rustPlatform` -- which will also lead to less dependencies
being pulled into user's lockfiles
This is a breaking change, but likely nobody uses this through raw JSON
yet, and this allows us to add fields to any action later on without
another breaking change.
This is a JSON-breaking change for the IPC actions that changed from
unit variants to struct variants. Unfortunately, I couldn't find a way
with serde to both preserve a single variant, and make it serialize to
the old value when the new field is None. I don't think anyone is using
these actions from JSON at the moment, so this breaking change is fine.
It's not added to clap because there's no convenient mutually-exclusive
argument enum derive yet (to have either the current <REFERENCE> or an
--id <ID>). It's not added to config parsing because I don't see how it
could be useful there. As such, it's only accessible through raw IPC.
this boils down to adding some extra dependencies to the shell
environment. they're also inherited from craneArgs because the ones from
the package are actually transformed into the WRONG outputs of the
packages. also refactors to use craneLib.devShell because it's somewhat
cleaner.
When the window is alone in its column this logic intentionally isn't
triggered. Until we have a floating layer, there's no other way to get a
window larger than the screen, which I need.
The intention is to make columns add up to the working area height most
of the time, while still preserving the ability to have one fixed-height
window.
Automatic heights are now distributed according to their weight, rather
than evenly. This is similar to flex-grow in CSS or fraction in Typst.
Resizing one window in a column still makes that window fixed, however
it changes all other windows to automatic height, computing their
weights in such a way as to preserve their apparent heights.
This makes sure the failing codeblocks do fail. This also optimizes the algorithm a bit by removing a `.collect()`
Signed-off-by: Suyashtnt <suyashtnt@gmail.com>
* Implement focus-window-up/down-or-monitor calls
* Fixed wrong naming of focus-window-or-monitor commands
* fix copy pase errors for focusing direction
* Fixed wrong behaviour when the current workspace is empty
* Cleanup navigation code to reduce complexity
* Fix wrong comments and add testcases for FocusWindowOrMonitorUp/Down
---------
Co-authored-by: Christian Rieger <christian.rieger@student.tugraz.at>
* Keep monitors powered off upon connecting a new one
Update src/backend/tty.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Update src/backend/tty.rs
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
fix tests
* Update
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
When using opacity as unfocused indicator, it will show up on the
screencast, which is undesired.
This is not a problem for window screen*shot*s where the window is
focused.
This got a bit broken with fractional layout. The current logic seems to
give exact results for integer scales again, but for fractional scales
sometimes the resulting height goes beyond the maximum, even clearly by
more than one logical pixel. Not entirely sure why that is.
Lets borders, gaps, and everything else stay pixel-perfect even with
fractional scale. Allows setting fractional border widths, gaps,
struts.
See the new wiki .md for more details.
This is called "events <mode>" in Sway, but we decided to use more abstracted
form for consistency with the other config items. "disabled-on-external-mouse"
is added only to touchpads, but there might be other devices that support this
option.
I think "off" also applies to keyboards, but I'm not going to add the one
because we don't have libinput machinery for the keyboard config, and it's
unlikely that user wants to disable _all_ keyboards. OTOH, pointer devices can
be disabled per type. Perhaps, this should be revisited after implementing #371.
This is an implementation of named, pre-declared workspaces. With this
implementation, workspaces can be declared in the configuration file by
name:
```
workspace "name" {
open-on-output "winit"
}
```
The `open-on-output` property is optional, and can be skipped, in which
case the workspace will open on the primary output.
All actions that were able to target a workspace by index can now target
them by either an index, or a name. In case of the command line, where
we do not have types available, this means that workspace names that
also pass as `u8` cannot be switched to by name, only by index.
Unlike dynamic workspaces, named workspaces do not close when they are
empty, they remain static. Like dynamic workspaces, named workspaces are
bound to a particular output. Switching to a named workspace, or moving
a window or column to one will also switch to, or move the thing in
question to the output of the workspace.
When reloading the configuration, newly added named workspaces will be
created, and removed ones will lose their name. If any such orphaned
workspace was empty, they will be removed. If they weren't, they'll
remain as a dynamic workspace, without a name. Re-declaring a workspace
with the same name later will create a new one.
Additionally, this also implements a `open-on-workspace "<name>"` window
rule. Matching windows will open on the given workspace (or the current
one, if the named workspace does not exist).
Signed-off-by: Gergely Nagy <niri@gergo.csillger.hu>
The interactive resize may have ended, but we're still waiting for the
last commit of the respective window. When cancelling, we should cancel
those ones too.
In my tests this was necessary to develop Niri using non-NixOS Nix.
Otherwise Niri panics with this error message: called `Result::unwrap()`
on an `Err` value: EventLoopCreation(NotSupported(NotSupportedError)).
* Implement version checking in IPC
implement version checking; streamed IPC
streamed IPC will allow multiple requests per connection
add nonsense request
change inline struct to json macro
only check version if request actually fails
fix usage of inspect_err (MSRV 1.72.0; stabilized 1.76.0)
"nonsense request" -> "return error"
oneshot connections
* Change some things around
* Unqualify niri_ipc::Transform
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Helps with the jank caused by lack of transactions when consuming to the
left/right. Resize triggers a few frames later and restarts the
movement. Now it only restarts the vertical and not the horizontal
movement.
This fixes a problem where consume-into-column would use resize
animation config instead of the window-movement config in most cases
(since a resize comes very shortly after the move starts).
A similar change to the column movement anim is more detrimental than
it's worth.
- Keep a root surface cache to be accessible in surface destroyed()
- Only snapshot during / right before closing, rather than every frame
- Store textures rather than elements to handle scale and alpha properly
* implement workspace back and forth
* Make our own ID counter instead of SerialCounter, use a newtype
* Rename FocusWorkspaceBackAndForth to FocusWorkspacePrevious
* Add focus-workspace-previous to tests
* Don't special case in switch_workspace_previous
* Minor clean up
* Add switch_workspace_auto_back_and_forth to tests
* Skip animation on switch_workspace_previous
* Preserve previous_workspace_id on workspace movement
* Make Workspace::id private with a getter
Reduce the chance it gets overwritten.
* Add test for workspace ID uniqueness
* Update previous workspace ID upon moving workspace across monitors
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add clickfinger in touchpad config
* Change `clickfinger` to `click-method`
* Change `bottom_areas` to `button_areas`
* Change button_areas to button-areas
For consistency.
* Reorder click methods in error message
The most usual one comes first.
* default-config: Move click-method down
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
gamescope + Minecraft with NeoForge throws an error upon starting if
there are no frame callbacks, thus making it the first client that has a
problem. Also, apparently, Veloren disconnects from server with VSync
and no frame callbacks.
* Implement wlr-screencopy
* Finish the implementation
Lots of changes, mainly to fix transform handling. Turns out, grim
expects transformed buffers and untransforms them by itself using info
from wl_output. This means that render helpers needed to learn how to
actually render transformed buffers.
Also, it meant that y_invert is no longer needed.
Next, moved the rendering to the Screencopy frame handler. Turns out,
copy() is more or less expected to return immediately, whereas
copy_with_damage() is expected to wait until the next VBlank. At least
that's the intent I parse reading the protocol.
Finally, brought the version from 3 down to 1, because
copy_with_damage() will need bigger changes. Grim still works, others
not really, mainly because they bind v3 unnecessarily, even if they
don't use the damage request.
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Add dinit support
- Add --notify-fd cli flag for ready notifications
- Set dinit activation environment when "dinit" feature flag is enabled
* Make systemd and dinit environment activation additive
* Use NOTIFY_FD env variable instead of --notify-fd cli flag for sending ready notifications
* Format with rustfmt
It can currently happen that the estimated VBlank timer fires right
before a real VBlank, which can cause some sequence collisions, which
might cause frame callbacks to never be sent. To prevent this, just
track the frame callback sequence fully separately. There isn't really
any harm in this, and if we accidentally increment it more frequently
than necessary then nothing terrible will happen.
* add dev dependencies to flake
* parse only one default-column-width
* require exactly one action per bind, and unique keys for binds
* use proper filename for config errors if possible
* fix duplicate keybinds after invalid action, lose some sanity
Before this commit:
- niri queues frame
- successful VBlank happens, sequence is bumped, frame callbacks are
sent
- niri receives commit, redraws, queues next frame, tries to send frame
callbacks, but there wasn't a new VBlank yet, so the sequence is old,
and frame callbacks aren't sent
- frame callbacks are sent only next VBlank
This both enables locking while monitors are powered off (they have no
buffer attached at that point on a TTY, so no sensitive content can
become visible), and fixes the condition below to check even if the
rendering was skipped.
xdg-desktop-portal currently has no way of disabling the Inhibit portal
or ever returning an error to the application from it. Thus Flatpak
Firefox will never fall back to its Wayland backend. To remedy this,
let's actually implement the FDO Inhibit interface that the portal can
use.
includes fixes for wrong direct scan-out transform
and damage artifacts on output transform changes.
also includes a fix for a race in popup surface re-use.
xdp-gnome restores by a combination of model + make + serial. We
currently can't set those reliably (until libdisplay-info most monitors
will have them unknown) so pass the connector name instead. This will
work as expected in most cases.
This reverts commit 43e2cf14d2.
SDL2 until very recently (unreleased version) has had a bug where
changing the decoration mode to client-side during its initial window
creation would keep the window permanently hidden. Breaking all SDL2
apps for years to come is unfortunately not a good solution.
Actually, how did it even fork before? Pretty sure it was storing render
formats, not texture formats, but with render formats
weston-simple-dmabuf-feedback doesn't work?
Windows are now wrapped in Tiles, which keep track of window-specific
decorations. Particularly, I implemented a black fullscreen backdrop,
which finally brings fullscreened windows smaller than the screen in
line with how the Wayland protocol says they should look—centered in a
black rectangle. I also implemented window borders, which are similar to
the focus ring, but always visible (and hence affect the layout and
sizing).
Most visibly, fixes screen not immediately redrawing upon layer-shell
popup commits.
There's still a number of places with questionable handling left, mostly
to do with subsurfaces (like, find_popup_root_surface() doesn't go up to
subsurfaces), and session-lock. I don't have good clients to test these.
* Add optional fallback to workspace focus/move for window focus/move commands
* Refactored to separate commands
* fix indentation
* fix white space
* Stylistic fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
<!-- Please describe the issue here at the top, then fill in the system information below. -->
<!-- Attaching your full niri config can help diagnose the problem. -->
<!--
If you have a problem with a specific app, please verify that it is running on Wayland, rather than X11. An easy way is to run xeyes and mouse over the app: xeyes will be able to "see" only X11 windows.
You can also check what process the window PID belongs to:
The project has grown quite a bit, and we could use all help that we can.
Make sure to join our Matrix chat if you have any questions or want to discuss anything: https://matrix.to/#/#niri:matrix.org
## Issues and discussions
This is a good way to help many new and existing users without programming knowledge.
- Answer and help people in GitHub issues and discussions.
- Check and point out duplicate issues.
- Check for issues that are likely application bugs (and not niri bugs).
- Ask or try to reproduce on another non-Smithay-based compositor (sway, KDE/KWin, GNOME/Mutter). If the issue reproduces, it's likely an application bug.
- Ask or try to reproduce on another *Smithay-based* compositor ([cosmic-comp], [anvil]). If the issue reproduces only on Smithay compositors, it may be a Smithay bug.
- Make sure you're testing the Wayland version of the app on all compositors. Apps may silently use X11 when an X11 `$DISPLAY` is available.
- Problems with X11 apps should be reported to [xwayland-satellite]. When testing xwayland-satellite on different compositors, make sure you use xwayland-satellite's `$DISPLAY` (rather than another compositor's built-in Xwayland `$DISPLAY`).
- After testing, mention where you could and couldn't reproduce, as well as the exact steps to reproduce if the issue is missing them.
- Try to reproduce the issue on your own system and write if you could or couldn't reproduce it.
- Upvote issues with a thumbs up reaction as you like.
- Ideas and feature requests from new users should go to Discussions.
If your issue is a duplicate, or not a niri issue (application bug, hardware problem, configuration problem), then please close it.
## Reviewing and testing pull requests
With the growing popularity, the volume of pull requests is honestly more than I can manage myself in my free time.
I would really appreciate help with testing and reviewing them.
### Testing
Pick a pull request you like, then build it and give it a go.
The [Developing niri wiki page](https://yalter.github.io/niri/Development:-Developing-niri) has guidance on running niri test builds.
Be really thorough with your testing.
We're striving for polished features in niri, so point out any issues and bugs, even small ones like animation jank.
- Think of weird edge cases or unexpected interactions and try them to see that they work reasonably.
- Try to break the feature and check that it behaves well.
- Where applicable, try different input devices: keyboard, mouse, trackpad, tablet, touchscreen.
- Watch out for any new performance drops.
For bug fixes, first make sure you can reproduce the bug, then do the same steps in the PR test build, and verify that the bug is fixed.
Be similarly thorough: test any similar or related edge cases to verify that the fix doesn't introduce any new problems.
Write your findings in the pull request: any issues you found, or if everything worked well.
Re-test after the author updates the code to see that your issues were fixed.
Don't hesitate to test even if someone else already did; very frequently different people will stumble upon different problems.
### Reviewing
Reviewing pull requests is something I need the most help with since there are a lot of them, and it's quite time-consuming.
Anyone with code accepted into niri is welcome, but this is not a requirement; even if you aren't familiar with Rust you may find some logic problems.
Pick a pull request, then review its code.
- Check that everything looks good, check various conditions for edge cases.
- See if there are any scenarios the author forgot to handle.
- Check that the code fits well into the rest of niri, follows its design and code style.
- I understand this is vague. The idea is: look at the surrounding code and at similar modules (e.g. when implementing a new protocol, check other protocol implementations), and try to follow the style and structure.
- Check for unrelated changes that may be better split into their own pull request.
- Check that the wiki had been updated if necessary (for example, new config options were documented with examples, and have a correct Since annotation).
Point out everything you find as review comments (don't forget to submit the review).
Be constructive and respectful; some people may be new to programming and Rust.
As the author addresses the comments and issues, check the code again to see that the problems were fixed.
If everything looks good, say that, so I know someone has reviewed the PR.
As with testing, don't hesitate to look through and comment even if someone else already had.
Extra pairs of eyes catch more problems.
## Writing pull requests
When creating pull requests, please keep the following in mind.
- Make sure new features align with niri's design directions. Ideally, there should be an existing issue or discussion where we settled on that solution.
- Keep pull requests focused on a single feature or bug fix with no unrelated changes.
- Try to split your changes into small, self-contained commits. Every commit should build and pass tests. This makes it much easier to review your PR, and bisect for regressions in the future.
- When addressing PR comments, try to squash the changes straight into the relevant commits.
- In some cases when the requested changes are big/unclear, you can leave them as separate commits on top, but please squash and otherwise clean up the history when the changes are finalized.
- To update the main branch, please rebase instead of merging. Try to force-push the main update rebase separately from other changes, this way it's easy to skip during review since it's usually not interesting.
- When working on bigger features, I usually start with a big messy commit, then gradually split out smaller self-contained changes from it as the code gets into shape.
- [git-rebase.io](https://git-rebase.io/) is a helpful guide for splitting commits and cleaning up history in git.
- When you address a review comment, mark it as resolved.
- Remember to [run tests](https://yalter.github.io/niri/Development:-Developing-niri#tests) and format the code with `cargo +nightly fmt --all`.
- For new layout actions, remember to add them to the randomized tests. For weird Wayland handling, adding client-server tests in `src/tests/` could be very useful.
- Test your changes by hand thoroughly, including for edge cases and weird interactions. See the Testing section above for some tips.
- Remember to document new config options on the wiki.
- When opening a pull request, ensure "Allow edits from maintainers" is enabled, so I can make final tweaks before merging.

## About
Windows are arranged in columns on an infinite strip going to the right.
Opening a new window never causes existing windows to resize.
Every monitor has its own separate window strip.
Windows can never "overflow" onto an adjacent monitor.
Workspaces are dynamic and arranged vertically.
Every monitor has an independent set of workspaces, and there's always one empty workspace present all the way down.
The workspace arrangement is preserved across disconnecting and connecting monitors where it makes sense.
When a monitor disconnects, its workspaces will move to another monitor, but upon reconnection they will move back to the original monitor.
## Features
- Built from the ground up for scrollable tiling
- [Dynamic workspaces](https://yalter.github.io/niri/Workspaces.html) like in GNOME
- An [Overview](https://github.com/user-attachments/assets/379a5d1f-acdb-4c11-b36c-e85fd91f0995) that zooms out workspaces and windows
- Built-in screenshot UI
- Monitor and window screencasting through xdg-desktop-portal-gnome
- You can [block out](https://yalter.github.io/niri/Configuration%3A-Window-Rules.html#block-out-from) sensitive windows from screencasts
- [Dynamic cast target](https://yalter.github.io/niri/Screencasting.html#dynamic-screencast-target) that can change what it shows on the go
- [Touchpad](https://github.com/YaLTeR/niri/assets/1794388/946a910e-9bec-4cd1-a923-4a9421707515) and [mouse](https://github.com/YaLTeR/niri/assets/1794388/8464e65d-4bf2-44fa-8c8e-5883355bd000) gestures
- Group windows into [tabs](https://yalter.github.io/niri/Tabs.html)
- [Gradient borders](https://yalter.github.io/niri/Configuration%3A-Layout.html#gradients) with Oklab and Oklch support
- [Animations](https://github.com/YaLTeR/niri/assets/1794388/ce178da2-af9e-4c51-876f-8709c241d95e) with support for [custom shaders](https://github.com/YaLTeR/niri/assets/1794388/27a238d6-0a22-4692-b794-30dc7a626fad)
Also check out this video from Brodie Robertson that showcases a lot of the niri functionality: [Niri Is My New Favorite Wayland Compositor](https://youtu.be/DeYx2exm04M)
## Status
A lot of the essential functionality is implemented, plus some goodies on top.
Feel free to give niri a try.
Have your waybars and fuzzels ready: niri is not a complete desktop environment.
Niri is stable for day-to-day use and does most things expected of a Wayland compositor.
Many people are daily-driving niri, and are happy to help in our [Matrix channel].
Follow the instructions on the [Getting Started](https://yalter.github.io/niri/Getting-Started.html) page.
Have your [waybar]s and [fuzzel]s ready: niri is not a complete desktop environment.
Also check out [awesome-niri], a list of niri-related links and projects.
## Idea
Here are some points you may have questions about:
Niri implements scrollable tiling, heavily inspired by [PaperWM].
Windows are arranged in columns on an infinite strip going to the right.
Every column takes up a full monitor worth of height, divided among its windows.
- **Multi-monitor**: yes, a core part of the design from the very start. Mixed DPI works.
- **Fractional scaling**: yes, plus all niri UI stays pixel-perfect.
- **NVIDIA**: seems to work fine.
- **Floating windows**: yes, starting from niri 25.01.
- **Input devices**: niri supports tablets, touchpads, and touchscreens.
You can map the tablet to a specific monitor, or use [OpenTabletDriver].
We have touchpad gestures, but no touchscreen gestures yet.
- **Wlr protocols**: yes, we have most of the important ones like layer-shell, gamma-control, screencopy.
You can check on [wayland.app](https://wayland.app) at the bottom of each protocol's page.
- **Performance**: while I run niri on beefy machines, I try to stay conscious of performance.
I've seen someone use it fine on an EeePC900 from2008, of all things.
- **Xwayland**: no built-in support, but xwayland-satellite is [easy to set up](https://yalter.github.io/niri/Xwayland.html#using-xwayland-satellite) and works very well.
- Steam and games, including Proton: work perfectly through xwayland-satellite.
- JetBrains IDEs, Ghidra: work well through xwayland-satellite.
- Discord and other Electron apps: work well through xwayland-satellite.
- Chromium and VSCode: work perfectly natively on Wayland with the right flags.
- X11 apps that want to position windows or bars at specific screen coordinates: won't work well; you can run them in a nested compositor like [labwc](https://yalter.github.io/niri/Xwayland.html#using-the-labwc-wayland-compositor) or [rootful Xwayland](https://yalter.github.io/niri/Xwayland.html#directly-running-xwayland-in-rootful-mode).
- Display scaling (integer or fractional) keeps X11 apps crisp, but you need the latest xwayland-satellite.
For games, you can run them in [gamescope] at native resolution, even with display scaling.
With multiple monitors, every monitor has its own separate window strip.
Windows can never "overflow" onto an adjacent monitor.
## Media
This is one of the reasons that prompted me to try writing my own compositor.
PaperWM is a solid implementation, but, being a GNOME Shell extension, it has to work around Shell's global window coordinate space to prevent windows from overflowing.
[niri: Making a Wayland compositor in Rust](https://youtu.be/Kmz8ODolnDg?list=PLRdS-n5seLRqrmWDQY4KDqtRMfIwU0U3T) · *December 2024*
Niri also has dynamic workspaces which work similar to GNOME Shell.
Since windows go left-to-right horizontally, workspaces are arranged vertically.
Every monitor has an independent set of workspaces, and there's always one empty workspace present all the way down.
My talk from the 2024 Moscow RustCon about niri, and how I do randomized property testing and profiling, and measure input latency.
The talk is in Russian, but I prepared full English subtitles that you can find in YouTube's subtitle language selector.
Niri tries to preserve the workspace arrangement as much as possible upon disconnecting and connecting monitors.
When a monitor disconnects, its workspaces will move to another monitor, but upon reconnection they will move back to the original monitor.
[An interview with Ivan, the developer behind Niri](https://www.trommelspeicher.de/podcast/special_the_developer_behind_niri) · *June 2025*
## Building
An interview by a German tech podcast Das Triumvirat (in English).
We talk about niri development and history, and my experience building and maintaining niri.
First, install the dependencies for your distribution.
[A tour of the niri scrolling-tiling Wayland compositor](https://lwn.net/Articles/1025866/) · *July 2025*
- Ubuntu:
An LWN article with a nice overview and introduction to niri.
| <kbd>Mod</kbd><kbd>Q</kbd> | Close the focused window |
| <kbd>Mod</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>←</kbd> | Focus the column to the left |
| <kbd>Mod</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>→</kbd> | Focus the column to the right |
| <kbd>Mod</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>↓</kbd> | Focus the window below in a column |
| <kbd>Mod</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>↑</kbd> | Focus the window above in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>←</kbd> | Move the focused column to the left |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>→</kbd> | Move the focused column to the right |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↓</kbd> | Move the focused window below in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↑</kbd> | Move the focused window above in a column |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Focus the monitor to the side |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Move the focused window to the monitor to the side |
| <kbd>Mod</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>PageDown</kbd> | Switch to the workspace below |
| <kbd>Mod</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>PageUp</kbd> | Switch to the workspace above |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>PageDown</kbd> | Move the focused window to the workspace below |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>PageUp</kbd> | Move the focused window to the workspace above |
| <kbd>Mod</kbd><kbd>1</kbd>–<kbd>9</kbd> | Switch to a workspace by index |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>1</kbd>–<kbd>9</kbd> | Move the focused window to a workspace by index |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageDown</kbd> | Move the focused workspace down |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageUp</kbd> | Move the focused workspace up |
| <kbd>Mod</kbd><kbd>,</kbd> | Consume the window to the right into the focused column |
| <kbd>Mod</kbd><kbd>.</kbd> | Expel the focused window into its own column |
| <kbd>Mod</kbd><kbd>R</kbd> | Toggle between preset column widths |
| <kbd>Mod</kbd><kbd>F</kbd> | Maximize column |
| <kbd>Mod</kbd><kbd>C</kbd> | Center column within view |
| <kbd>Mod</kbd><kbd>-</kbd> | Decrease column width by 10% |
| <kbd>Mod</kbd><kbd>=</kbd> | Increase column width by 10% |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>-</kbd> | Decrease window height by 10% |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>=</kbd> | Increase window height by 10% |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>F</kbd> | Toggle full-screen on the focused window |
| <kbd>PrtSc</kbd> | Take an area screenshot. Select the area to screenshot with mouse, then press Space to save the screenshot, or Escape to cancel |
| <kbd>Alt</kbd><kbd>PrtSc</kbd> | Take a screenshot of the focused window to clipboard and to `~/Pictures/Screenshots/` |
| <kbd>Ctrl</kbd><kbd>PrtSc</kbd> | Take a screenshot of the focused monitor to clipboard and to `~/Pictures/Screenshots/` |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>T</kbd> | Toggle debug tinting of rendered elements |
If the application has a [desktop entry](https://specifications.freedesktop.org/menu-spec/latest/menu-add-example.html), you can put the command-line arguments into the `Exec` section.
### VSCode
If you're having issues with some VSCode hotkeys, try starting `Xwayland` and setting the `DISPLAY=:0` environment variable for VSCode.
That is, still running VSCode with the Wayland backend, but with `DISPLAY` set to a running Xwayland instance.
Apparently, VSCode currently unconditionally queries the X server for a keymap.
### WezTerm
> [!NOTE]
> Both of these issues seem to be fixed in the nightly build of WezTerm.
There's [a bug](https://github.com/wezterm/wezterm/issues/4708) in WezTerm that it waits for a zero-sized Wayland configure event, so its window never shows up in niri. To work around it, put this window rule in the niri config (included in the default config):
```kdl
window-rule{
matchapp-id=r#"^org\.wezfurlong\.wezterm$"#
default-column-width{}
}
```
This empty default column width lets WezTerm pick its own initial width which makes it show up properly.
There's [another bug](https://github.com/wezterm/wezterm/issues/6472) in WezTerm that causes it to choose a wrong size when it's in a tiled state, and prevent resizing it.
Niri puts windows in the tiled state with [`prefer-no-csd`](./Configuration:-Miscellaneous.md#prefer-no-csd).
So if you hit this problem, comment out `prefer-no-csd` in the niri config and restart WezTerm.
### Ghidra
Some Java apps like Ghidra can show up blank under xwayland-satellite.
To fix this, run them with the `_JAVA_AWT_WM_NONREPARENTING=1` environment variable.
### rofi-wayland
There's a bug in rofi-wayland that prevents it from accepting keyboard input on niri with errors in the output.
It's been fixed in rofi, but [the fix had not been released yet](https://github.com/davatorium/rofi/discussions/2008).
### Zen Browser
For some reason, DMABUF screencasts are disabled in the Zen Browser, so screencasting doesn't work out of the box on niri.
To fix it, open `about:config` and set `widget.dmabuf.force-enabled` to `true`.
### Fullscreen games
Some video games, both Linux-native and on Wine, have various issues when using non-stacking desktop environments.
Most of these can be avoided with Valve's [gamescope](https://github.com/ValveSoftware/gamescope), for example:
This command will run *<game>* in 1080p fullscreen—make sure to replace the width and height values to match your desired resolution.
`--force-grab-cursor` forces gamescope to use relative mouse movement which prevents the cursor from escaping the game's window on multi-monitor setups.
Note that `--backend sdl` is currently also required as gamescope's default Wayland backend doesn't lock the cursor properly (possibly related to https://github.com/ValveSoftware/gamescope/issues/1711).
Steam users should use gamescope through a game's [launch options](https://help.steampowered.com/en/faqs/view/7D01-D2DD-D75E-2955) by replacing the game executable with `%command%`.
Other game launchers such as [Lutris](https://lutris.net/) have their own ways of setting gamescope options.
Running X11-based games with this method doesn't require Xwayland as gamescope creates its own Xwayland server.
You can run Wayland-native games as well by passing `--expose-wayland` to gamescope, therefore eliminating X11 from the equation.
### Steam
On some systems, Steam will show a fully black window.
To fix this, navigate to Settings -> Interface (via Steam's tray icon, or by blindly finding the Steam menu at the top left of the window), then **disable** GPU accelerated rendering in web views.
Restart Steam and it should now work fine.
If you do not want to disable GPU accelerated rendering you can instead try to pass the launch argument `-system-composer` instead.
Steam notifications don't run through the standard notification daemon and show up as floating windows in the center of the screen.
You can move them to a more convenient location by adding a window rule in your niri config:
Each animation can be either an easing or a spring.
#### Easing
This is a relatively common animation type that changes the value over a set duration using an interpolation curve.
To use this animation, set the following parameters:
-`duration-ms`: duration of the animation in milliseconds.
-`curve`: the easing curve to use.
```kdl
animations{
window-open{
duration-ms150
curve"ease-out-expo"
}
}
```
Currently, niri only supports four curves:
-`ease-out-quad` <sup>Since: 0.1.5</sup>
-`ease-out-cubic`
-`ease-out-expo`
-`linear` <sup>Since: 0.1.6</sup>
You can get a feel for them on pages like [easings.net](https://easings.net/).
#### Spring
Spring animations use a model of a physical spring to animate the value.
They notably feel better with touchpad gestures, because they take into account the velocity of your fingers as you release the swipe.
Springs can also oscillate / bounce at the end with the right parameters if you like that sort of thing, but they don't have to (and by default they mostly don't).
Due to springs using a physical model, the animation parameters are less obvious and generally should be tuned with trial and error.
Notably, you cannot directly set the duration.
You can use the [Elastic](https://flathub.org/apps/app.drey.Elastic) app to help visualize how the spring parameters change the animation.
A spring animation is configured like this, with three mandatory parameters:
Sometimes, when two animations are meant to play together synchronized, niri will drive them both with the same configuration.
For example, if a window resize causes the view to move, then that view movement animation will also use the `window-resize` configuration (rather than the `horizontal-view-movement` configuration).
This is especially important for animated resizes to look good when using `center-focused-column "always"`.
As another example, resizing a window in a column vertically causes other windows to move up or down into their new position.
This movement will use the `window-resize` configuration, rather than the `window-movement` configuration, to keep the animations synchronized.
A few actions are still missing this synchronization logic, since in some cases it is difficult to implement properly.
Therefore, for the best results, consider using the same parameters for related animations (they are all the same by default):
Niri has several options that are only useful for debugging, or are experimental and have known issues.
They are not meant for normal use.
> [!CAUTION]
> These options are **not** covered by the [config breaking change policy](./Configuration:-Introduction.md#breaking-change-policy).
> They can change or stop working at any point with little notice.
Here are all the options at a glance:
```kdl
debug{
preview-render"screencast"
// preview-render "screen-capture"
enable-overlay-planes
disable-cursor-plane
disable-direct-scanout
restrict-primary-scanout-to-matching-format
render-drm-device"/dev/dri/renderD129"
force-pipewire-invalid-modifier
dbus-interfaces-in-non-session-instances
wait-for-frame-completion-before-queueing
emulate-zero-presentation-time
disable-resize-throttling
disable-transactions
keep-laptop-panel-on-when-lid-is-closed
disable-monitor-names
strict-new-window-focus-policy
honor-xdg-activation-with-invalid-serial
skip-cursor-only-updates-during-vrr
deactivate-unfocused-windows
keep-max-bpc-unchanged
}
binds{
Mod+Shift+Ctrl+T{ toggle-debug-tint;}
Mod+Shift+Ctrl+O{ debug-toggle-opaque-regions;}
Mod+Shift+Ctrl+D{ debug-toggle-damage;}
}
```
### `preview-render`
Make niri render the monitors the same way as for a screencast or a screen capture.
Useful for previewing the `block-out-from` window rule.
```kdl
debug{
preview-render"screencast"
// preview-render "screen-capture"
}
```
### `enable-overlay-planes`
Enable direct scanout into overlay planes.
May cause frame drops during some animations on some hardware (which is why it is not the default).
Direct scanout into the primary plane is always enabled.
```kdl
debug{
enable-overlay-planes
}
```
### `disable-cursor-plane`
Disable the use of the cursor plane.
The cursor will be rendered together with the rest of the frame.
Useful to work around driver bugs on specific hardware.
```kdl
debug{
disable-cursor-plane
}
```
### `disable-direct-scanout`
Disable direct scanout to both the primary plane and the overlay planes.
```kdl
debug{
disable-direct-scanout
}
```
### `restrict-primary-scanout-to-matching-format`
Restricts direct scanout to the primary plane to when the window buffer exactly matches the composition swapchain format.
This flag may prevent unexpected bandwidth changes when going between composition and scanout.
The plan is to make it default in the future, when we implement a way to tell the clients the composition swapchain format.
As is, it may prevent some clients (mpv on my machine) from scanning out to the primary plane.
```kdl
debug{
restrict-primary-scanout-to-matching-format
}
```
### `render-drm-device`
Override the DRM device that niri will use for all rendering.
You can set this to make niri use a different primary GPU than the default one.
```kdl
debug{
render-drm-device"/dev/dri/renderD129"
}
```
### `force-pipewire-invalid-modifier`
<sup>Since: 25.01</sup>
Forces PipeWire screencasting to use the invalid modifier, even when DRM offers more modifiers.
Useful for testing the invalid modifier code path that is hit by drivers that don't support modifiers.
```kdl
debug{
force-pipewire-invalid-modifier
}
```
### `dbus-interfaces-in-non-session-instances`
Make niri create its D-Bus interfaces even if it's not running as a `--session`.
Useful for testing screencasting changes without having to relogin.
The main niri instance will *not* currently take back the interfaces when you close the test instance, so you will need to relogin in the end to make screencasting work again.
```kdl
debug{
dbus-interfaces-in-non-session-instances
}
```
### `wait-for-frame-completion-before-queueing`
Wait until every frame is done rendering before handing it over to DRM.
Useful for diagnosing certain synchronization and performance problems.
```kdl
debug{
wait-for-frame-completion-before-queueing
}
```
### `emulate-zero-presentation-time`
Emulate zero (unknown) presentation time returned from DRM.
This is a thing on NVIDIA proprietary drivers, so this flag can be used to test that niri doesn't break too hard on those systems.
```kdl
debug{
emulate-zero-presentation-time
}
```
### `disable-resize-throttling`
<sup>Since: 0.1.9</sup>
Disable throttling resize events sent to windows.
By default, when resizing quickly (e.g. interactively), a window will only receive the next size once it has made a commit for the previously requested size.
This is required for resize transactions to work properly, and it also helps certain clients which don't batch incoming resizes from the compositor.
Disabling resize throttling will send resizes to windows as fast as possible, which is potentially very fast (for example, on a 1000 Hz mouse).
```kdl
debug{
disable-resize-throttling
}
```
### `disable-transactions`
<sup>Since: 0.1.9</sup>
Disable transactions (resize and close).
By default, windows which must resize together, do resize together.
For example, all windows in a column must resize at the same time to maintain the combined column height equal to the screen height, and to maintain the same window width.
Transactions make niri wait until all windows finish resizing before showing them all on screen in one, synchronized frame.
For them to work properly, resize throttling shouldn't be disabled (with the previous debug flag).
```kdl
debug{
disable-transactions
}
```
### `keep-laptop-panel-on-when-lid-is-closed`
<sup>Since: 0.1.10</sup>
By default, niri will disable the internal laptop monitor when the laptop lid is closed.
This flag turns off this behavior and will leave the internal laptop monitor on.
```kdl
debug{
keep-laptop-panel-on-when-lid-is-closed
}
```
### `disable-monitor-names`
<sup>Since: 0.1.10</sup>
Disables the make/model/serial monitor names, as if niri fails to read them from the EDID.
Use this flag to work around a crash present in 0.1.9 and 0.1.10 when connecting two monitors with matching make/model/serial.
```kdl
debug{
disable-monitor-names
}
```
### `strict-new-window-focus-policy`
<sup>Since: 25.01</sup>
Disables heuristic automatic focusing for new windows.
Only windows that activate themselves with a valid xdg-activation token will be focused.
```kdl
debug{
strict-new-window-focus-policy
}
```
### `honor-xdg-activation-with-invalid-serial`
<sup>Since: 25.05</sup>
Widely-used clients such as Discord and Telegram make fresh xdg-activation tokens upon clicking on their tray icon or on their notification.
Most of the time, these fresh tokens will have invalid serials, because the app needs to be focused to get a valid serial, and if the user clicks on a tray icon or a notification, it is usually because the app *isn't* focused, and the user wants to focus it.
By default, niri ignores xdg-activation tokens with invalid serials, to prevent windows from randomly stealing focus.
This debug flag makes niri honor such tokens, making the aforementioned widely-used apps get focus when clicking on their tray icon or notification.
Amusingly, clicking on a notification sends the app a perfectly valid activation token from the notification daemon, but these apps seem to simply ignore it.
Maybe in the future these apps/toolkits (Electron, Qt) are fixed, making this debug flag unnecessary.
```kdl
debug{
honor-xdg-activation-with-invalid-serial
}
```
### `skip-cursor-only-updates-during-vrr`
<sup>Since: next release</sup>
Skips redrawing the screen from cursor input while variable refresh rate is active.
Useful for games where the cursor isn't drawn internally to prevent erratic VRR shifts in response to cursor movement.
Note that the current implementation has some issues, for example when there's nothing redrawing the screen (like a game), the rendering will appear to completely freeze (since cursor movements won't cause redraws).
```kdl
debug{
skip-cursor-only-updates-during-vrr
}
```
### `deactivate-unfocused-windows`
<sup>Since: next release</sup>
Some clients (notably, Chromium- and Electron-based, like Teams or Slack) erroneously use the Activated xdg window state instead of keyboard focus for things like deciding whether to send notifications for new messages, or for picking where to show an IME popup.
Niri keeps the Activated state on unfocused workspaces and invisible tabbed windows (to reduce unwanted animations), surfacing bugs in these applications.
Set this debug flag to work around these problems.
It will cause niri to drop the Activated state for all unfocused windows.
```kdl
debug{
deactivate-unfocused-windows
}
```
### `keep-max-bpc-unchanged`
<sup>Since: next release</sup>
When connecting monitors, niri sets their max bpc to 8 in order to reduce display bandwidth and to potentially allow more monitors to be connected at once.
Restricting bpc to 8 is not a problem since we don't support HDR or color management yet and can't really make use of higher bpc.
Apparently, setting max bpc to 8 breaks some displays driven by AMDGPU.
If this happens to you, set this debug flag, which will prevent niri from changing max bpc.
> By default, `localectl` will set the TTY keymap to the closest match of the XKB keymap.
> You can prevent that with a `--no-convert` flag, for example: `localectl set-x11-keymap --no-convert "us,ru"`.
>
> These settings are picked up by some other programs too, like GDM.
When using multiple layouts, niri can remember the current layout globally (the default) or per-window.
You can control this with the `track-layout` option.
-`global`: layout change is global for all windows.
-`window`: layout is tracked for each window individually.
```kdl
input{
keyboard{
track-layout"global"
}
}
```
#### Repeat
Delay is in milliseconds before the keyboard repeat starts.
Rate is in characters per second.
```kdl
input{
keyboard{
repeat-delay600
repeat-rate25
}
}
```
#### Num Lock
<sup>Since: 25.05</sup>
Set the `numlock` flag to turn on Num Lock automatically at startup.
You might want to disable (comment out) `numlock` if you're using a laptop with a keyboard that overlays Num Lock keys on top of regular keys.
```kdl
input{
keyboard{
numlock
}
}
```
### Pointing Devices
Most settings for the pointing devices are passed directly to libinput.
Other Wayland compositors also use libinput, so it's likely you will find the same settings there.
For flags like `tap`, omit them or comment them out to disable the setting.
A few settings are common between input devices:
-`off`: if set, no events will be sent from this device.
A few settings are common between `touchpad`, `mouse`, `trackpoint`, and `trackball`:
-`natural-scroll`: if set, inverts the scrolling direction.
-`accel-speed`: pointer acceleration speed, valid values are from `-1.0` to `1.0` where the default is `0.0`.
-`accel-profile`: can be `adaptive` (the default) or `flat` (disables pointer acceleration).
-`scroll-method`: when to generate scroll events instead of pointer motion events, can be `no-scroll`, `two-finger`, `edge`, or `on-button-down`.
The default and supported methods vary depending on the device type.
-`scroll-button`: <sup>Since: 0.1.10</sup> the button code used for the `on-button-down` scroll method. You can find it in `libinput debug-events`.
-`scroll-button-lock`: <sup>Since: next release</sup> when enabled, the button does not need to be held down. Pressing once engages scrolling, pressing a second time disengages it, and double click acts as single click of the the underlying button.
-`left-handed`: if set, changes the device to left-handed mode.
-`middle-emulation`: emulate a middle mouse click by pressing left and right mouse buttons at once.
Settings specific to `touchpad`s:
-`tap`: tap-to-click.
-`dwt`: disable-when-typing.
-`dwtp`: disable-when-trackpointing.
-`drag`: <sup>Since: 25.05</sup> can be `true` or `false`, controls if tap-and-drag is enabled.
-`drag-lock`: <sup>Since: 25.02</sup> if set, lifting the finger off for a short time while dragging will not drop the dragged item. See the [libinput documentation](https://wayland.freedesktop.org/libinput/doc/latest/tapping.html#tap-and-drag).
-`tap-button-map`: can be `left-right-middle` or `left-middle-right`, controls which button corresponds to a two-finger tap and a three-finger tap.
-`click-method`: can be `button-areas` or `clickfinger`, changes the [click method](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html).
-`disabled-on-external-mouse`: do not send events while external pointer device is plugged in.
Settings specific to `touchpad` and `mouse`:
-`scroll-factor`: <sup>Since: 0.1.10</sup> scales the scrolling speed by this value.
<sup>Since: next release</sup> You can also override horizontal and vertical scroll factor separately like so: `scroll-factor horizontal=2.0 vertical=-1.0`
Settings specific to `tablet`s:
-`calibration-matrix`: <sup>Since: 25.02</sup> set to six floating point numbers to change the calibration matrix. See the [`LIBINPUT_CALIBRATION_MATRIX` documentation](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html) for examples.
Tablets and touchscreens are absolute pointing devices that can be mapped to a specific output like so:
```kdl
input{
tablet{
map-to-output"eDP-1"
}
touch{
map-to-output"eDP-1"
}
}
```
Valid output names are the same as the ones used for output configuration.
<sup>Since: 0.1.7</sup> When a tablet is not mapped to any output, it will map to the union of all connected outputs, without aspect ratio correction.
### General Settings
These settings are not specific to a particular input device.
#### `disable-power-key-handling`
By default, niri will take over the power button to make it sleep instead of power off.
Set this if you would like to configure the power button elsewhere (i.e. `logind.conf`).
```kdl
input{
disable-power-key-handling
}
```
#### `warp-mouse-to-focus`
Makes the mouse warp to newly focused windows.
Does not make the cursor visible if it had been hidden.
```kdl
input{
warp-mouse-to-focus
}
```
By default, the cursor warps *separately* horizontally and vertically.
I.e. if moving the mouse only horizontally is enough to put it inside the newly focused window, then the mouse will move only horizontally, and not vertically.
<sup>Since: 25.05</sup> You can customize this with the `mode` property.
-`mode="center-xy"`: warps by both X and Y coordinates together.
So if the mouse was anywhere outside the newly focused window, it will warp to the center of the window.
-`mode="center-xy-always"`: warps by both X and Y coordinates together, even if the mouse was already somewhere inside the newly focused window.
```kdl
input{
warp-mouse-to-focusmode="center-xy"
}
```
#### `focus-follows-mouse`
Focuses windows and outputs automatically when moving the mouse over them.
```kdl
input{
focus-follows-mouse
}
```
<sup>Since: 0.1.8</sup> You can optionally set `max-scroll-amount`.
Then, focus-follows-mouse won't focus a window if it will result in the view scrolling more than the set amount.
The value is a percentage of the working area width.
```kdl
input{
// Allow focus-follows-mouse when it results in scrolling at most 10% of the screen.
focus-follows-mousemax-scroll-amount="10%"
}
```
```kdl
input{
// Allow focus-follows-mouse only when it will not scroll the view.
focus-follows-mousemax-scroll-amount="0%"
}
```
#### `workspace-auto-back-and-forth`
Normally, switching to the same workspace by index twice will do nothing (since you're already on that workspace).
If this flag is enabled, switching to the same workspace by index twice will switch back to the previous workspace.
Niri will correctly switch to the workspace you came from, even if workspaces were reordered in the meantime.
```kdl
input{
workspace-auto-back-and-forth
}
```
#### `mod-key`, `mod-key-nested`
<sup>Since: 25.05</sup>
Customize the `Mod` key for [key bindings](./Configuration:-Key-Bindings.md).
Only valid modifiers are allowed, e.g. `Super`, `Alt`, `Mod3`, `Mod5`, `Ctrl`, `Shift`.
By default, `Mod` is equal to `Super` when running niri on a TTY, and to `Alt` when running niri as a nested winit window.
> [!NOTE]
> There are a lot of default bindings with Mod, none of them "make it through" to the underlying window.
> You probably don't want to set `mod-key` to Ctrl or Shift, since Ctrl is commonly used for app hotkeys, and Shift is used for, well, regular typing.
```kdl
// Switch the mod keys around: use Alt normally, and Super inside a nested window.
Niri will load configuration from `$XDG_CONFIG_HOME/niri/config.kdl` or `~/.config/niri/config.kdl`, falling back to `/etc/niri/config.kdl`.
If both of these files are missing, niri will create `$XDG_CONFIG_HOME/niri/config.kdl` with the contents of [the default configuration file](https://github.com/YaLTeR/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
Please use the default configuration file as the starting point for your custom configuration.
The configuration is live-reloaded.
Simply edit and save the config file, and your changes will be applied.
This includes key bindings, output settings like mode, window rules, and everything else.
You can run `niri validate` to parse the config and see any errors.
To use a different config file path, pass it in the `--config` or `-c` argument to `niri`.
You can also set `$NIRI_CONFIG` to the path of the config file.
`--config` always takes precedence.
If `--config` or `$NIRI_CONFIG` doesn't point to a real file, the config will not be loaded.
If `$NIRI_CONFIG` is set to an empty string, it is ignored and the default config location is used instead.
### Syntax
The config is written in [KDL].
#### Comments
Lines starting with `//` are comments; they are ignored.
Also, you can put `/-` in front of a section to comment out the entire section:
```kdl
/-output "eDP-1" {
// Everything inside here is ignored.
// The display won't be turned off
// as the whole section is commented out.
off
}
```
#### Flags
Toggle options in niri are commonly represented as flags.
Writing out the flag enables it, and omitting it or commenting it out disables it.
For example:
```kdl
// "Focus follows mouse" is enabled.
input{
focus-follows-mouse
// Other settings...
}
```
```kdl
// "Focus follows mouse" is disabled.
input{
// focus-follows-mouse
// Other settings...
}
```
#### Sections
Most sections cannot be repeated. For example:
```kdl
// This is valid: every section appears once.
input{
keyboard{
// ...
}
touchpad{
// ...
}
}
```
```kdl,must-fail
// This is NOT valid: input section appears twice.
input {
keyboard {
// ...
}
}
input {
touchpad {
// ...
}
}
```
Exceptions are, for example, sections that configure different devices by name:
<!-- NOTE: this may break in the future -->
```kdl
output "eDP-1" {
// ...
}
// This is valid: this section configures a different output.
output "HDMI-A-1" {
// ...
}
// This is NOT valid: "eDP-1" already appeared above.
// It will either throw a config parsing error, or otherwise not work.
output "eDP-1" {
// ...
}
```
### Defaults
Omitting most of the sections of the config file will leave you with the default values for that section.
A notable exception is [`binds {}`](./Configuration:-Key-Bindings.md): they do not get filled with defaults, so make sure you do not erase this section.
### Breaking Change Policy
As a rule, niri updates should not break existing config files.
(For example, the default config from niri v0.1.0 still parses fine on v25.02 as I'm writing this.)
Exceptions can be made for parsing bugs.
For example, niri used to accept multiple binds to the same key, but this was not intended and did not do anything (the first bind was always used).
A patch release changed niri from silently accepting this to causing a parsing failure.
This is not a blanket rule, I will consider the potential impact of every breaking change like this before deciding to carry on with it.
Keep in mind that the breaking change policy applies only to niri releases.
Commits between releases can and do occasionally break the config as new features are ironed out.
However, I do try to limit these, since several people are running git builds.
Key bindings are declared in the `binds {}` section of the config.
> [!NOTE]
> This is one of the few sections that *does not* get automatically filled with defaults if you omit it, so make sure to copy it from the default config.
Each bind is a hotkey followed by one action enclosed in curly brackets.
For example:
```kdl
binds{
Mod+Left{ focus-column-left;}
Super+Alt+L{ spawn"swaylock";}
}
```
The hotkey consists of modifiers separated by `+` signs, followed by an XKB key name in the end.
Valid modifiers are:
-`Ctrl` or `Control`;
-`Shift`;
-`Alt`;
-`Super` or `Win`;
-`ISO_Level3_Shift` or `Mod5`—this is the AltGr key on certain layouts;
-`ISO_Level5_Shift`: can be used with an xkb lv5 option like `lv5:caps_switch`;
-`Mod`.
`Mod` is a special modifier that is equal to `Super` when running niri on a TTY, and to `Alt` when running niri as a nested winit window.
This way, you can test niri in a window without causing too many conflicts with the host compositor's key bindings.
For this reason, most of the default keys use the `Mod` modifier.
<sup>Since: 25.05</sup> You can customize the `Mod` key [in the `input` section of the config](./Configuration:-Input.md#mod-key-mod-key-nested).
> [!TIP]
> To find an XKB name for a particular key, you may use a program like [`wev`](https://git.sr.ht/~sircmpwn/wev).
>
> Open it from a terminal and press the key that you want to detect.
> Here, look at `sym: Left` and `sym: Right`: these are the key names.
> I was pressing the left and the right arrow in this example.
>
> Keep in mind that binding shifted keys requires spelling out Shift and the unshifted version of the key, according to your XKB layout.
> For example, on the US QWERTY layout, <kbd><</kbd> is on <kbd>Shift</kbd> + <kbd>,</kbd>, so to bind it, you spell out something like `Mod+Shift+Comma`.
>
> As another example, if you've configured the French [BÉPO](https://en.wikipedia.org/wiki/B%C3%89PO) XKB layout, your <kbd><</kbd> is on <kbd>AltGr</kbd> + <kbd>«</kbd>.
> <kbd>AltGr</kbd> is `ISO_Level3_Shift`, or equivalently `Mod5`, so to bind it, you spell out something like `Mod+Mod5+guillemotleft`.
>
> When resolving latin keys, niri will search for the *first* configured XKB layout that has the latin key.
> So for example with US QWERTY and RU layouts configured, US QWERTY will be used for latin binds.
<sup>Since: 0.1.8</sup> Binds will repeat by default (i.e. holding down a bind will make it trigger repeatedly).
You can disable that for specific binds with `repeat=false`:
```kdl
binds{
Mod+Trepeat=false{ spawn"alacritty";}
}
```
Binds can also have a cooldown, which will rate-limit the bind and prevent it from repeatedly triggering too quickly.
```kdl
binds{
Mod+Tcooldown-ms=500{ spawn"alacritty";}
}
```
This is mostly useful for the scroll bindings.
### Scroll Bindings
You can bind mouse wheel scroll ticks using the following syntax.
These binds will change direction based on the `natural-scroll` setting.
Touchpad scrolling is continuous, so for these binds it is split into discrete intervals based on distance travelled.
These binds are also affected by touchpad's `natural-scroll`, so these example binds are "inverted", since niri has `natural-scroll` enabled for touchpads by default.
-`screenshot`: opens the built-in interactive screenshot UI.
-`screenshot-screen`, `screenshow-window`: takes a screenshot of the focused screen or window respectively.
The screenshot is both stored to the clipboard and saved to disk, according to the [`screenshot-path` option](./Configuration:-Miscellaneous.md#screenshot-path).
<sup>Since: 25.02</sup> You can disable saving to disk for a specific bind with the `write-to-disk=false` property:
In the interactive screenshot UI, pressing <kbd>Ctrl</kbd><kbd>C</kbd> will copy the screenshot to the clipboard without writing it to disk.
<sup>Since: 25.05</sup> You can hide the mouse pointer in screenshots with the `show-pointer=false` property:
```kdl
binds{
// The pointer will be hidden by default
// (you can still show it by pressing P).
Print{ screenshotshow-pointer=false;}
// The pointer will be hidden on the screenshot.
Ctrl+Print{ screenshot-screenshow-pointer=false;}
}
```
#### `toggle-keyboard-shortcuts-inhibit`
<sup>Since: 25.02</sup>
Applications such as remote-desktop clients and software KVM switches may request that niri stops processing its keyboard shortcuts so that they may, for example, forward the key presses as-is to a remote machine.
`toggle-keyboard-shortcuts-inhibit` is an escape hatch that toggles the inhibitor.
It's a good idea to bind it, so a buggy application can't hold your session hostage.
```kdl
binds{
Mod+Escape{ toggle-keyboard-shortcuts-inhibit;}
}
```
You can also make certain binds ignore inhibiting with the `allow-inhibiting=false` property.
They will always be handled by niri and never passed to the window.
```kdl
binds{
// This bind will always work, even when using a virtual machine.
Layer rules let you adjust behavior for individual layer-shell surfaces.
They have `match` and `exclude` directives that control which layer-shell surfaces the rule should apply to, and a number of properties that you can set.
Layer rules are processed and work very similarly to window rules, just with different matchers and properties.
Please read the [window rules wiki page](./Configuration:-Window-Rules.md) to learn how matching works.
Here are all matchers and properties that a layer rule could have:
```kdl
layer-rule{
matchnamespace="waybar"
matchat-startup=true
// Properties that apply continuously.
opacity0.5
block-out-from"screencast"
// block-out-from "screen-capture"
shadow{
on
// off
softness40
spread5
offsetx=0y=5
draw-behind-windowtrue
color"#00000064"
// inactive-color "#00000064"
}
geometry-corner-radius12
place-within-backdroptrue
baba-is-floattrue
}
```
### Layer Surface Matching
Let's look at the matchers in more detail.
#### `namespace`
This is a regular expression that should match anywhere in the surface namespace.
You can read about the supported regular expression syntax [here](https://docs.rs/regex/latest/regex/#syntax).
```kdl
// Match surfaces with namespace containing "waybar",
layer-rule{
matchnamespace="waybar"
}
```
You can find the namespaces of all open layer-shell surfaces by running `niri msg layers`.
#### `at-startup`
Can be `true` or `false`.
Matches during the first 60 seconds after starting niri.
```kdl
// Show layer-shell surfaces with 0.5 opacity at niri startup, but not afterwards.
layer-rule{
matchat-startup=true
opacity0.5
}
```
### Dynamic Properties
These properties apply continuously to open layer-shell surfaces.
#### `block-out-from`
You can block out surfaces from xdg-desktop-portal screencasts or all screen captures.
They will be replaced with solid black rectangles.
This can be useful for notifications.
The same caveats and instructions apply as for the [`block-out-from` window rule](./Configuration:-Window-Rules.md#block-out-from), so check the documentation there.

```kdl
// Block out mako notifications from screencasts.
layer-rule{
matchnamespace="^notifications$"
block-out-from"screencast"
}
```
#### `opacity`
Set the opacity of the surface.
`0.0` is fully transparent, `1.0` is fully opaque.
This is applied on top of the surface's own opacity, so semitransparent surfaces will become even more transparent.
Opacity is applied to every child of the layer-shell surface individually, so subsurfaces and pop-up menus will show window content behind them.
```kdl
// Make fuzzel semitransparent.
layer-rule{
matchnamespace="^launcher$"
opacity0.95
}
```
#### `shadow`
<sup>Since: 25.02</sup>
Override the shadow options for the surface.
These rules have the same options as the normal [`shadow` config in the layout section](./Configuration:-Layout.md#shadow), so check the documentation there.
Unlike window shadows, layer surface shadows always need to be enabled with a layer rule.
That is, enabling shadows in the layout config section won't automatically enable them for layer surfaces.
> [!NOTE]
> Layer surfaces have no way to tell niri about their *visual geometry*.
> For example, if a layer surface includes some invisible margins (like mako), niri has no way of knowing that, and will draw the shadow behind the entire surface, including the invisible margins.
>
> So to use niri shadows, you'll need to configure layer-shell clients to remove their own margins or shadows.
```kdl
// Add a shadow for fuzzel.
layer-rule{
matchnamespace="^launcher$"
shadow{
on
}
// Fuzzel defaults to 10 px rounded corners.
geometry-corner-radius10
}
```
#### `geometry-corner-radius`
<sup>Since: 25.02</sup>
Set the corner radius of the surface.
This setting will only affect the shadow—it will round its corners to match the geometry corner radius.
```kdl
layer-rule{
matchnamespace="^launcher$"
geometry-corner-radius12
}
```
#### `place-within-backdrop`
<sup>Since: 25.05</sup>
Set to `true` to place the surface into the backdrop visible in the [Overview](./Overview.md) and between workspaces.
This will only work for *background* layer surfaces that ignore exclusive zones (typical for wallpaper tools).
Layers within the backdrop will ignore all input.
```kdl
// Put swaybg inside the overview backdrop.
layer-rule{
matchnamespace="^wallpaper$"
place-within-backdroptrue
}
```
#### `baba-is-float`
<sup>Since: 25.05</sup>
Make your layer surfaces FLOAT up and down.
This is a natural extension of the [April Fools' 2025 feature](./Configuration:-Window-Rules.md#baba-is-float).
Set gaps around (inside and outside) windows in logical pixels.
<sup>Since: 0.1.7</sup> You can use fractional values.
The value will be rounded to physical pixels according to the scale factor of every output.
For example, `gaps 0.5` on an output with `scale 2` will result in one physical-pixel wide gaps.
<sup>Since: 0.1.8</sup> You can emulate "inner" vs. "outer" gaps with negative `struts` values (see the struts section below).
```kdl
layout{
gaps16
}
```
### `center-focused-column`
When to center a column when changing focus.
This can be set to:
-`"never"`: no special centering, focusing an off-screen column will scroll it to the left or right edge of the screen. This is the default.
-`"always"`, the focused column will always be centered.
-`"on-overflow"`, focusing a column will center it if it doesn't fit on screen together with the previously focused column.
```kdl
layout{
center-focused-column"always"
}
```
### `always-center-single-column`
<sup>Since: 0.1.9</sup>
If set, niri will always center a single column on a workspace, regardless of the `center-focused-column` option.
```kdl
layout{
always-center-single-column
}
```
### `empty-workspace-above-first`
<sup>Since: 25.01</sup>
If set, niri will always add an empty workspace at the very start, in addition to the empty workspace at the very end.
```kdl
layout{
empty-workspace-above-first
}
```
### `default-column-display`
<sup>Since: 25.02</sup>
Sets the default display mode for new columns.
Can be `normal` or `tabbed`.
```kdl
// Make all new columns tabbed by default.
layout{
default-column-display"tabbed"
// You may also want to hide the tab indicator
// when there's only a single window in a column.
tab-indicator{
hide-when-single-tab
}
}
```
### `preset-column-widths`
Set the widths that the `switch-preset-column-width` action (Mod+R) toggles between.
`proportion` sets the width as a fraction of the output width, taking gaps into account.
For example, you can perfectly fit four windows sized `proportion 0.25` on an output, regardless of the gaps setting.
The default preset widths are <sup>1</sup>⁄<sub>3</sub>, <sup>1</sup>⁄<sub>2</sub> and <sup>2</sup>⁄<sub>3</sub> of the output.
`fixed` sets the window width in logical pixels exactly.
```kdl
layout{
// Cycle between 1/3, 1/2, 2/3 of the output, and a fixed 1280 logical pixels.
preset-column-widths{
proportion0.33333
proportion0.5
proportion0.66667
fixed1280
}
}
```
### `default-column-width`
Set the default width of the new windows.
The syntax is the same as in `preset-column-widths` above.
```kdl
layout{
// Open new windows sized 1/3 of the output.
default-column-width{ proportion0.33333;}
}
```
You can also leave the brackets empty, then the windows themselves will decide their initial width.
```kdl
layout{
// New windows decide their initial width themselves.
default-column-width{}
}
```
> [!NOTE]
> `default-column-width {}` causes niri to send a (0, H) size in the initial configure request.
>
> This is a bit [unclearly defined](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/155) in the Wayland protocol, so some clients may misinterpret it.
> Either way, `default-column-width {}` is most useful for specific windows, in form of a [window rule](./Configuration:-Window-Rules.md#default-column-width) with the same syntax.
### `preset-window-heights`
<sup>Since: 0.1.9</sup>
Set the heights that the `switch-preset-window-height` action (Mod+Shift+R) toggles between.
`proportion` sets the height as a fraction of the output height, taking gaps into account.
The default preset heights are <sup>1</sup>⁄<sub>3</sub>, <sup>1</sup>⁄<sub>2</sub> and <sup>2</sup>⁄<sub>3</sub> of the output.
`fixed` sets the height in logical pixels exactly.
```kdl
layout{
// Cycle between 1/3, 1/2, 2/3 of the output, and a fixed 720 logical pixels.
preset-window-heights{
proportion0.33333
proportion0.5
proportion0.66667
fixed720
}
}
```
### `focus-ring` and `border`
Focus ring and border are drawn around windows and indicate the active window.
They are very similar and have the same options.
The difference is that the focus ring is drawn only around the active window, whereas borders are drawn around all windows and affect their sizes (windows shrink to make space for the borders).
|  |  |
> [!TIP]
> By default, focus ring and border are rendered as a solid background rectangle behind windows.
> That is, they will show up through semitransparent windows.
> This is because windows using client-side decorations can have an arbitrary shape.
>
> If you don't like that, you should uncomment the [`prefer-no-csd` setting](./Configuration:-Miscellaneous.md#prefer-no-csd) at the top level of the config.
> Niri will draw focus rings and borders *around* windows that agree to omit their client-side decorations.
>
> Alternatively, you can override this behavior with the [`draw-border-with-background` window rule](./Configuration:-Window-Rules.md#draw-border-with-background).
Focus ring and border have the following options.
```kdl
layout{
// focus-ring has the same options.
border{
// Uncomment this line to disable the border.
// off
// Width of the border in logical pixels.
width4
active-color"#ffc87f"
inactive-color"#505050"
// Color of the border around windows that request your attention.
- CSS-like notation: `"rgb(255, 127, 0)"`, `"rgba()"`, `"hsl()"` and a few others.
`active-color` is the color of the focus ring / border around the active window, and `inactive-color` is the color of the focus ring / border around all other windows.
The *focus ring* is only drawn around the active window on each monitor, so with a single monitor you will never see its `inactive-color`.
You will see it if you have multiple monitors, though.
There's also a *deprecated* syntax for setting colors with four numbers representing R, G, B and A: `active-color 127 200 255 255`.
#### Gradients
Similarly to colors, you can set `active-gradient` and `inactive-gradient`, which will take precedence.
Gradients are rendered the same as CSS [`linear-gradient(angle, from, to)`](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient).
The angle works the same as in `linear-gradient`, and is optional, defaulting to `180` (top-to-bottom gradient).
You can use any CSS linear-gradient tool on the web to set these up, like [css-gradient.com](https://www.css-gradient.com/).
```kdl
layout{
focus-ring{
active-gradientfrom="#80c8ff"to="#bbddff"angle=45
}
}
```
Gradients can be colored relative to windows individually (the default), or to the whole view of the workspace.
|  |  |
<sup>Since: 0.1.8</sup> You can set the gradient interpolation color space using syntax like `in="srgb-linear"` or `in="oklch longer hue"`.
Supported color spaces are:
-`srgb` (the default),
-`srgb-linear`,
-`oklab`,
-`oklch` with `shorter hue` or `longer hue` or `increasing hue` or `decreasing hue`.
They are rendered the same as CSS.
For example, `active-gradient from="#f00f" to="#0f05" angle=45 in="oklch longer hue"` will look the same as CSS `linear-gradient(45deg in oklch longer hue, #f00f, #0f05)`.

`softness` controls the shadow softness/size in logical pixels, same as [CSS box-shadow] *blur radius*.
Setting `softness 0` will give you hard shadows.
`spread` is the distance to expand the window rectangle in logical pixels, same as CSS box-shadow spread.
<sup>Since: 25.05</sup> Spread can be negative.
`offset` moves the shadow relative to the window in logical pixels, same as CSS box-shadow offset.
For example, `offset x=2 y=2` will move the shadow 2 logical pixels downwards and to the right.
Set `draw-behind-window` to `true` to make shadows draw behind the window rather than just around it.
Note that niri has no way of knowing about the CSD window corner radius.
It has to assume that windows have square corners, leading to shadow artifacts inside the CSD rounded corners.
This setting fixes those artifacts.
However, instead you may want to set `prefer-no-csd` and/or `geometry-corner-radius`.
Then, niri will know the corner radius and draw the shadow correctly, without having to draw it behind the window.
These will also remove client-side shadows if the window draws any.
`color` is the shadow color and opacity.
`inactive-color` lets you override the shadow color for inactive windows; by default, a more transparent `color` is used.
Shadow drawing will follow the window corner radius set with the [`geometry-corner-radius` window rule](./Configuration:-Window-Rules.md#geometry-corner-radius).
> [!NOTE]
> Currently, shadow drawing only supports matching radius for all corners. If you set `geometry-corner-radius` to four values instead of one, the first (top-left) corner radius will be used for shadows.
```kdl
// Enable shadows.
layout{
shadow{
on
}
}
// Also ask windows to omit client-side decorations, so that
screenshot-path"~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
environment{
QT_QPA_PLATFORM"wayland"
DISPLAYnull
}
cursor{
xcursor-theme"breeze_cursors"
xcursor-size48
hide-when-typing
hide-after-inactive-ms1000
}
overview{
zoom0.5
backdrop-color"#262626"
workspace-shadow{
// off
softness40
spread10
offsetx=0y=10
color"#00000050"
}
}
xwayland-satellite{
// off
path"xwayland-satellite"
}
clipboard{
disable-primary
}
hotkey-overlay{
skip-at-startup
hide-not-bound
}
config-notification{
disable-failed
}
```
### `spawn-at-startup`
Add lines like this to spawn processes at niri startup.
`spawn-at-startup` accepts a path to the program binary as the first argument, followed by arguments to the program.
This option works the same way as the [`spawn` key binding action](./Configuration:-Key-Bindings.md#spawn), so please read about all its subtleties there.
```kdl
spawn-at-startup"waybar"
spawn-at-startup"alacritty"
```
Note that running niri as a systemd session supports xdg-desktop-autostart out of the box, which may be more convenient to use.
Thanks to this, apps that you configured to autostart in GNOME will also "just work" in niri, without any manual `spawn-at-startup` configuration.
### `spawn-sh-at-startup`
<sup>Since: next release</sup>
Add lines like this to run shell commands at niri startup.
The argument is a single string that is passed verbatim to `sh`.
You can use shell variables, pipelines, `~` expansion and everything else as expected.
See detailed description in the docs for the [`spawn-sh` key binding action](./Configuration:-Key-Bindings.md#spawn-sh).
This flag will make niri ask the applications to omit their client-side decorations.
If an application will specifically ask for CSD, the request will be honored.
Additionally, clients will be informed that they are tiled, removing some rounded corners.
With `prefer-no-csd` set, applications that negotiate server-side decorations through the xdg-decoration protocol will have focus ring and border drawn around them *without* a solid colored background.
> [!NOTE]
> Unlike most other options, changing `prefer-no-csd` will not entirely affect already running applications.
> It will make some windows rectangular, but won't remove the title bars.
> This mainly has to do with niri working around a [bug in SDL2](https://github.com/libsdl-org/SDL/issues/8173) that prevents SDL2 applications from starting.
>
> Restart applications after changing `prefer-no-csd` in the config to fully apply it.
```kdl
prefer-no-csd
```
### `screenshot-path`
Set the path where screenshots are saved.
A `~` at the front will be expanded to the home directory.
The path is formatted with `strftime(3)` to give you the screenshot date and time.
Niri will create the last folder of the path if it doesn't exist.
```kdl
screenshot-path"~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
```
You can also set this option to `null` to disable saving screenshots to disk.
```kdl
screenshot-pathnull
```
### `environment`
Override environment variables for processes spawned by niri.
```kdl
environment{
// Set a variable like this:
// QT_QPA_PLATFORM "wayland"
// Remove a variable by using null as the value:
// DISPLAY null
}
```
### `cursor`
Change the theme and size of the cursor as well as set the `XCURSOR_THEME` and `XCURSOR_SIZE` environment variables.
```kdl
cursor{
xcursor-theme"breeze_cursors"
xcursor-size48
}
```
#### `hide-when-typing`
<sup>Since: 0.1.10</sup>
If set, hides the cursor when pressing a key on the keyboard.
> [!NOTE]
> This setting might interfere with games running in Wine in native Wayland mode that use mouselook, such as first-person games.
> If your character's point of view jumps down when you press a key and move the mouse simultaneously, try disabling this setting.
```kdl
cursor{
hide-when-typing
}
```
#### `hide-after-inactive-ms`
<sup>Since: 0.1.10</sup>
If set, the cursor will automatically hide once this number of milliseconds passes since the last cursor movement.
```kdl
cursor{
// Hide the cursor after one second of inactivity.
hide-after-inactive-ms1000
}
```
### `overview`
<sup>Since: 25.05</sup>
Settings for the [Overview](./Overview.md).
#### `zoom`
Control how much the workspaces zoom out in the overview.
`zoom` ranges from 0 to 0.75 where lower values make everything smaller.
```kdl
// Make workspaces four times smaller than normal in the overview.
overview{
zoom0.25
}
```
#### `backdrop-color`
Set the backdrop color behind workspaces in the overview.
The backdrop is also visible between workspaces when switching.
The alpha channel for this color will be ignored.
```kdl
// Make the backdrop light.
overview{
backdrop-color"#777777"
}
```
You can also set the color per-output [in the output config](./Configuration:-Outputs.md#backdrop-color).
#### `workspace-shadow`
Control the shadow behind workspaces visible in the overview.
Settings here mirror the normal [`shadow` config in the layout section](./Configuration:-Layout.md#shadow), so check the documentation there.
Workspace shadows are configured for a workspace size normalized to 1080 pixels tall, then zoomed out together with the workspace.
Practically, this means that you'll want bigger spread, offset, and softness compared to window shadows.
```kdl
// Disable workspace shadows in the overview.
overview{
workspace-shadow{
off
}
}
```
### `xwayland-satellite`
<sup>Since: next release</sup>
Settings for integration with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite).
When a recent enough xwayland-satellite is detected, niri will create the X11 sockets and set `DISPLAY`, then automatically spawn `xwayland-satellite` when an X11 client tries to connect.
If Xwayland dies, niri will keep watching the X11 socket and restart `xwayland-satellite` as needed.
This is very similar to how built-in Xwayland works in other compositors.
`off` disables the integration: niri won't create an X11 socket and won't set the `DISPLAY` environment variable.
`path` sets the path to the `xwayland-satellite` binary.
By default, it's just `xwayland-satellite`, so it's looked up like any other non-absolute program name.
Set the `disable-primary` flag to disable the primary clipboard (middle-click paste).
Toggling this flag will only apply to applications started afterward.
```kdl
clipboard{
disable-primary
}
```
### `hotkey-overlay`
Settings for the "Important Hotkeys" overlay.
#### `skip-at-startup`
Set the `skip-at-startup` flag if you don't want to see the hotkey help at niri startup.
```kdl
hotkey-overlay{
skip-at-startup
}
```
#### `hide-not-bound`
<sup>Since: next release</sup>
By default, niri will show the most important actions even if they aren't bound to any key, to prevent confusion.
Set the `hide-not-bound` flag if you want to hide all actions not bound to any key.
```kdl
hotkey-overlay{
hide-not-bound
}
```
You can customize which binds the hotkey overlay shows using the [`hotkey-overlay-title` property](./Configuration:-Key-Bindings.md#custom-hotkey-overlay-titles).
### `config-notification`
<sup>Since: next release</sup>
Settings for the config created/failed notification.
Set the `disable-failed` flag to disable the "Failed to parse the config file" notification.
Named workspaces initially appear in the order they are declared in the config file.
When editing the config while niri is running, newly declared named workspaces will appear at the very top of a monitor.
If you delete some named workspace from the config, the workspace will become normal (unnamed), and if there are no windows on it, it will be removed (as any other normal workspace).
There's no way to give a name to an already existing workspace, but you can simply move windows that you want to a new, empty named workspace.
<sup>Since: 0.1.9</sup> `open-on-output` can now use monitor manufacturer, model, and serial.
Before, it could only use the connector name.
<sup>Since: 25.01</sup> You can use `set-workspace-name` and `unset-workspace-name` actions to change workspace names dynamically.
<sup>Since: 25.02</sup> Named workspaces no longer update/forget their original output when opening a new window on them (unnamed workspaces will keep doing that).
This means that named workspaces "stick" to their original output in more cases, reflecting their more permanent nature.
Explicitly moving a named workspace to a different monitor will still update its original output.
Values with `flipped` additionally flip the output.
```kdl
output"HDMI-A-1"{
transform"90"
}
```
### `position`
Set the position of the output in the global coordinate space.
This affects directional monitor actions like `focus-monitor-left`, and cursor movement.
The cursor can only move between directly adjacent outputs.
> [!NOTE]
> Output scale and rotation has to be taken into account for positioning: outputs are sized in logical, or scaled, pixels.
> For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080, so to put another output directly adjacent to it on the right, set its x to 1920.
> If the position is unset or results in an overlap, the output is instead placed automatically.
```kdl
output"HDMI-A-1"{
positionx=1280y=0
}
```
#### Automatic Positioning
Niri repositions outputs from scratch every time the output configuration changes (which includes monitors disconnecting and connecting).
The following algorithm is used for positioning outputs.
1. Collect all connected monitors and their logical sizes.
1. Sort them by their name. This makes it so the automatic positioning does not depend on the order the monitors are connected. This is important because the connection order is non-deterministic at compositor startup.
1. Try to place every output with explicitly configured `position`, in order. If the output overlaps previously placed outputs, place it to the right of all previously placed outputs. In this case, niri will also print a warning.
1. Place every output without explicitly configured `position` by putting it to the right of all previously placed outputs.
### `variable-refresh-rate`
<sup>Since: 0.1.5</sup>
This flag enables variable refresh rate (VRR, also known as adaptive sync, FreeSync, or G-Sync), if the output supports it.
You can check whether an output supports VRR in `niri msg outputs`.
> [!NOTE]
> Some drivers have various issues with VRR.
>
> If the cursor moves at a low framerate with VRR, try setting the [`disable-cursor-plane` debug flag](./Configuration:-Debug-Options.md#disable-cursor-plane) and reconnecting the monitor.
>
> If a monitor is not detected as VRR-capable when it should, sometimes unplugging a different monitor fixes it.
>
> Some monitors will continuously modeset (flash black) with VRR enabled; I'm not sure if there's a way to fix it.
```kdl
output"HDMI-A-1"{
variable-refresh-rate
}
```
<sup>Since: 0.1.9</sup> You can also set the `on-demand=true` property, which will only enable VRR when this output shows a window matching the `variable-refresh-rate` window rule.
This is helpful to avoid various issues with VRR, since it can be disabled most of the time, and only enabled for specific windows, like games or video players.
```kdl
output"HDMI-A-1"{
variable-refresh-rateon-demand=true
}
```
### `focus-at-startup`
<sup>Since: 25.05</sup>
Focus this output by default when niri starts.
If multiple outputs with `focus-at-startup` are connected, they are prioritized in the order that they appear in the config.
When none of the connected outputs are explicitly `focus-at-startup`, niri will focus the first one sorted by name (same output sorting as used elsewhere in niri).
These are regular expressions that should match anywhere in the window title and app ID respectively.
You can read about the supported regular expression syntax [here](https://docs.rs/regex/latest/regex/#syntax).
```kdl
// Match windows with title containing "Mozilla Firefox",
// or windows with app ID containing "Alacritty".
window-rule{
matchtitle="Mozilla Firefox"
matchapp-id="Alacritty"
}
```
Raw KDL strings can be helpful for writing out regular expressions:
```kdl
window-rule{
excludeapp-id=r#"^org\.keepassxc\.KeePassXC$"#
}
```
You can find the title and the app ID of the currently focused window by running `niri msg focused-window`.
> [!TIP]
> Another way to find the window title and app ID is to configure the `wlr/taskbar` module in [Waybar](https://github.com/Alexays/Waybar) to include them in the tooltip:
>
> ```json
> "wlr/taskbar": {
> "tooltip-format": "{title} | {app_id}",
> }
> ```
#### `is-active`
Can be `true` or `false`.
Matches active windows (same windows that have the active border / focus ring color).
Every workspace on the focused monitor will have one active window.
This means that you will usually have multiple active windows (one per workspace), and when you switch between workspaces, you can see two active windows at once.
```kdl
window-rule{
matchis-active=true
}
```
#### `is-focused`
Can be `true` or `false`.
Matches the window that has the keyboard focus.
Contrary to `is-active`, there can only be a single focused window.
Also, when opening a layer-shell application launcher or pop-up menu, the keyboard focus goes to layer-shell.
While layer-shell has the keyboard focus, windows will not match this rule.
```kdl
window-rule{
matchis-focused=true
}
```
#### `is-active-in-column`
<sup>Since: 0.1.6</sup>
Can be `true` or `false`.
Matches the window that is the "active" window in its column.
Contrary to `is-active`, there is always one `is-active-in-column` window in each column.
It is the window that was last focused in the column, i.e. the one that will gain focus if this column is focused.
<sup>Since: 25.01</sup> This rule will match `true` during the initial window opening.
```kdl
window-rule{
matchis-active-in-column=true
}
```
#### `is-floating`
<sup>Since: 25.01</sup>
Can be `true` or `false`.
Matches floating windows.
> [!NOTE]
> This matcher will apply only after the window is already open.
> This means that you cannot use it to change the window opening properties like `default-window-height` or `open-on-workspace`.
```kdl
window-rule{
matchis-floating=true
}
```
#### `is-window-cast-target`
<sup>Since: 25.02</sup>
Can be `true` or `false`.
Matches `true` for windows that are target of an ongoing window screencast.
> [!NOTE]
> This only matches individual-window screencasts.
> It will not match windows that happen to be visible in a monitor screencast, for example.
```kdl
// Indicate screencasted windows with red colors.
window-rule{
matchis-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"
}
}
```
Example:

#### `is-urgent`
<sup>Since: 25.05</sup>
Can be `true` or `false`.
Matches windows that request the user's attention.
```kdl
window-rule{
matchis-urgent=true
}
```
#### `at-startup`
<sup>Since: 0.1.6</sup>
Can be `true` or `false`.
Matches during the first 60 seconds after starting niri.
This is useful for properties like `open-on-output` which you may want to apply only right after starting niri.
```kdl
// Open windows on the HDMI-A-1 monitor at niri startup, but not afterwards.
window-rule{
matchat-startup=true
open-on-output"HDMI-A-1"
}
```
### Window Opening Properties
These properties apply once, when a window first opens.
To be precise, they apply at the point when niri sends the initial configure request to the window.
#### `default-column-width`
Set the default width for the new window.
This works for floating windows too, despite the word "column" in the name.
```kdl
// Give Blender and GIMP some guaranteed width on opening.
window-rule{
matchapp-id="^blender$"
// GIMP app ID contains the version like "gimp-2.99",
// so we only match the beginning (with ^) and not the end.
matchapp-id="^gimp"
default-column-width{ fixed1200;}
}
```
#### `default-window-height`
<sup>Since: 25.01</sup>
Set the default height for the new window.
```kdl
// Open the Firefox picture-in-picture window as floating with 480×270 size.
These properties apply continuously to open windows.
#### `block-out-from`
You can block out windows from xdg-desktop-portal screencasts.
They will be replaced with solid black rectangles.
This can be useful for password managers or messenger windows, etc.
For layer-shell notification pop-ups and the like, you can use a [`block-out-from` layer rule](./Configuration:-Layer-Rules.md#block-out-from).

To preview and set up this rule, check the `preview-render` option in the debug section of the config.
> [!CAUTION]
> The window is **not** blocked out from third-party screenshot tools.
> If you open some screenshot tool with preview while screencasting, blocked out windows **will be visible** on the screencast.
The built-in screenshot UI is not affected by this problem though.
If you open the screenshot UI while screencasting, you will be able to select the area to screenshot while seeing all windows normally, but on a screencast the selection UI will display with windows blocked out.
```kdl
// Block out password managers from screencasts.
window-rule{
matchapp-id=r#"^org\.keepassxc\.KeePassXC$"#
matchapp-id=r#"^org\.gnome\.World\.Secrets$"#
block-out-from"screencast"
}
```
Alternatively, you can block out the window out of *all* screen captures, including third-party screenshot tools.
This way you avoid accidentally showing the window on a screencast when opening a third-party screenshot preview.
This setting will still let you use the interactive built-in screenshot UI, but it will block out the window from the fully automatic screenshot actions, such as `screenshot-screen` and `screenshot-window`.
The reasoning is that with an interactive selection, you can make sure that you avoid screenshotting sensitive content.
```kdl
window-rule{
block-out-from"screen-capture"
}
```
> [!WARNING]
> Be careful when blocking out windows based on a dynamically changing window title.
>
> For example, you might try to block out specific Firefox tabs like this:
>
> ```kdl
> window-rule {
> // Doesn't quite work! Try to block out the Gmail tab.
> match app-id="firefox$" title="- Gmail "
>
> block-out-from "screencast"
> }
> ```
>
> It will work, but when switching from a sensitive tab to a regular tab, the contents of the sensitive tab **will show up on a screencast** for an instant.
>
> This is because window title (and app ID) are not double-buffered in the Wayland protocol, so they are not tied to specific window contents.
> There's no robust way for Firefox to synchronize visibly showing a different tab and changing the window title.
#### `opacity`
Set the opacity of the window.
`0.0` is fully transparent, `1.0` is fully opaque.
This is applied on top of the window's own opacity, so semitransparent windows will become even more transparent.
Opacity is applied to every surface of the window individually, so subsurfaces and pop-up menus will show window content behind them.

Also, focus ring and border with background will show through semitransparent windows (see `prefer-no-csd` and the `draw-border-with-background` window rule below).
Opacity can be toggled on or off for a window using the [`toggle-window-rule-opacity`](./Configuration:-Key-Bindings.md#toggle-window-rule-opacity) action.
```kdl
// Make inactive windows semitransparent.
window-rule{
matchis-active=false
opacity0.95
}
```
#### `variable-refresh-rate`
<sup>Since: 0.1.9</sup>
If set to true, whenever this window displays on an output with on-demand VRR, it will enable VRR on that output.
```kdl
// Configure some output with on-demand VRR.
output"HDMI-A-1"{
variable-refresh-rateon-demand=true
}
// Enable on-demand VRR when mpv displays on the output.
window-rule{
matchapp-id="^mpv$"
variable-refresh-ratetrue
}
```
#### `default-column-display`
<sup>Since: 25.02</sup>
Set the default display mode for columns created from this window.
This is used any time a window goes into its own column.
For example:
- Opening a new window.
- Expelling a window into its own column.
- Moving a window from the floating layout to the tiling layout.
```kdl
// Make Evince windows open as tabbed columns.
window-rule{
matchapp-id="^evince$"
default-column-display"tabbed"
}
```
#### `default-floating-position`
<sup>Since: 25.01</sup>
Set the initial position for this window when it opens on, or moves to the floating layout.
Afterward, the window will remember its last floating position.
By default, new floating windows open at the center of the screen, and windows from the tiling layout open close to their visual screen position.
The position uses logical coordinates relative to the working area.
By default, they are relative to the top-left corner of the working area, but you can change this by setting `relative-to` to one of these values: `top-left`, `top-right`, `bottom-left`, `bottom-right`, `top`, `bottom`, `left`, or `right`.
For example, if you have a bar at the top, then `x=0 y=0` will put the top-left corner of the window directly below the bar.
If instead you write `x=0 y=0 relative-to="top-right"`, then the top-right corner of the window will align with the top-right corner of the workspace, also directly below the bar.
When only one side is specified (e.g. top) the window will align to the center of that side.
The coordinates change direction based on `relative-to`.
For example, by default (top-left), `x=100 y=200` will put the window 100 pixels to the right and 200 pixels down from the top-left corner.
If you use `x=100 y=200 relative-to="bottom-left"`, it will put the window 100 pixels to the right and 200 pixels *up* from the bottom-left corner.
```kdl
// Open the Firefox picture-in-picture window at the bottom-left corner of the screen
|  |  |
```kdl
window-rule{
draw-border-with-backgroundfalse
}
```
#### `focus-ring` and `border`
<sup>Since: 0.1.6</sup>
Override the focus ring and border options for the window.
These rules have the same options as the normal [`focus-ring` and `border` config in the layout section](./Configuration:-Layout.md#focus-ring-and-border), so check the documentation there.
However, in addition to `off` to disable the border/focus ring, this window rule has an `on` flag that enables the border/focus ring for the window even if it was otherwise disabled.
The `on` flag has precedence over the `off` flag, in case both are set.
```kdl
window-rule{
focus-ring{
off
width2
}
}
window-rule{
border{
on
width8
}
}
```
#### `shadow`
<sup>Since: 25.02</sup>
Override the shadow options for the window.
This rule has the same options as the normal [`shadow` config in the layout section](./Configuration:-Layout.md#shadow), so check the documentation there.
However, in addition to `on` to enable the shadow, this window rule has an `off` flag that disables the shadow for the window even if it was otherwise enabled.
The `on` flag has precedence over the `off` flag, in case both are set.
```kdl
// Turn on shadows for floating windows.
window-rule{
matchis-floating=true
shadow{
on
}
}
```
#### `tab-indicator`
<sup>Since: 25.02</sup>
Override the tab indicator options for the window.
Options in this rule match the same options as the normal [`tab-indicator` config in the layout section](./Configuration:-Layout.md#tab-indicator), so check the documentation there.
```kdl
// Make KeePassXC tab have a dark red inactive color.
window-rule{
matchapp-id=r#"^org\.keepassxc\.KeePassXC$"#
tab-indicator{
inactive-color"darkred"
}
}
```
#### `geometry-corner-radius`
<sup>Since: 0.1.6</sup>
Set the corner radius of the window.
On its own, this setting will only affect the border and the focus ring—they will round their corners to match the geometry corner radius.
If you'd like to force-round the corners of the window itself, set [`clip-to-geometry true`](#clip-to-geometry) in addition to this setting.
```kdl
window-rule{
geometry-corner-radius12
}
```
The radius is set in logical pixels, and controls the radius of the window itself, that is, the inner radius of the border:

Instead of one radius, you can set four, for each corner.
The order is the same as in CSS: top-left, top-right, bottom-right, bottom-left.
```kdl
window-rule{
geometry-corner-radius8800
}
```
This way, you can match GTK 3 applications which have square bottom corners:

#### `clip-to-geometry`
<sup>Since: 0.1.6</sup>
Clips the window to its visual geometry.
This will cut out any client-side window shadows, and also round window corners according to `geometry-corner-radius`.

```kdl
window-rule{
clip-to-geometrytrue
}
```
Enable border, set [`geometry-corner-radius`](#geometry-corner-radius) and `clip-to-geometry`, and you've got a classic setup:

```kdl
prefer-no-csd
layout{
focus-ring{
off
}
border{
width2
}
}
window-rule{
geometry-corner-radius12
clip-to-geometrytrue
}
```
#### `tiled-state`
<sup>Since: 25.05</sup>
Informs the window that it is tiled.
Usually, windows will react by becoming rectangular and hiding their client-side shadows.
Windows that snap their size to a grid (e.g. terminals like [foot](https://codeberg.org/dnkl/foot)) will usually disable this snapping when they are tiled.
By default, niri will set the tiled state to `true` together with [`prefer-no-csd`](./Configuration:-Miscellaneous.md#prefer-no-csd) in order to improve behavior for apps that don't support server-side decorations.
You can use this window rule to override this, for example to get rectangular windows with CSD.
```kdl
// Make tiled windows rectangular while using CSD.
> *Time, Dr. Freeman? Is it really that... time again?*
A compositor deals with one or more monitors on mostly fixed refresh cycles.
For example, a 170 Hz monitor can draw a frame every ~5.88 ms.
Most of the time, the compositor doesn't actually redraw the monitor: when nothing changes on screen (e.g. you're reading a document and aren't moving your cursor), it would be wasteful to wake up the GPU to composite the same image.
During an animation however, screen contents do change every frame.
Niri will generally start drawing the next frame as soon as the previous one shows up on screen.
Since the monitor refresh cycle is fixed in most cases (even with VRR, there's a maximum refresh rate), the compositor can predict when the next frame will show up on the monitor, and render ongoing animations for that exact moment in time.
This way, all animation frames are perfectly timed with no jitter, regardless of when exactly the rendering code had a chance to run.
For example, even if the compositor has to process new window events, delaying the rendering by a few ms, the animation timing will remain exactly aligned to the monitor refresh cycle.
There are hence several properties that a compositor wants from its timing system.
1. It should be possible to get the state of the animations at a specific time in the near future, for rendering a frame exactly timed to when the monitor will show it.
- This time override ability should be usable in tests to advance the time in a fully controlled fashion.
1. Animations in response to user actions should begin at the moment when the action happens.
For example, pressing a workspace switch key should start the animation at the instant when the user pressed the key (rather than, say, slightly in the future where we predicted the next monitor frame, which we had already rendered by now).
1. During the processing of a single action, querying the current time should return the exact same value.
Even if the processing finishes a few microseconds after it started, querying the time in the end should return the same thing.
This generally makes writing code much more sane; otherwise you'd need to for example avoid reading the position of some element twice in a row, since it could have moved by one pixel in-between, screwing with the logic.
Also, fetching the current system time [can be quite expensive](https://mastodon.online/@YaLTeR/109934977035721850) in terms of overhead.
1. It should be reasonably easy to implement an animation slow-down preference, so all animations can be slowed down or sped up by the same factor.
The solution in niri is a `LazyClock`, a clock that remembers one timestamp.
Initially, the timestamp is empty, so when you ask `LazyClock` for the current time, it will fetch and return the system time, and also remember it.
Subsequently, it will keep returning the same timestamp that it had remembered.
You can also clear the timestamp, then `LazyClock` will fetch the system time anew when it's needed.
In niri, the timestamp is cleared at the end of every event loop iteration, right before going to sleep waiting for new events.
This way, anything that happens next (like a user key press) will fetch and use the most up-to-date timestamp as soon as one is needed, but then the processing code will keep getting the exact same timestamp, since `LazyClock` stores it.
You can also just manually set the timestamp to a specific value.
This is how we render a frame for the predicted time of when the monitor will show it.
Also, this is used by tests: they simply always set the timestamp and never use the system time.
Finally, there's an `AdjustableClock` wrapper on top that provides the ability to control the slow-down rate by modifying the timestamps returned by the clock.
An important detail is that with rate changes, timestamps from the `AdjustableClock` will drift away and become unrelated to the system time.
However, our target timestamp (for rendering) comes from the system time, so the override works directly on the underlying `LazyClock`.
That is, overriding the timestamp and then querying the `AdjustableClock` will return a *different* timestamp that is correct and consistent with the adjustments made by `AdjustableClock`.
This is reflected in the API by naming the function `Clock::set_unadjusted()` (and there's also `Clock::now_unadjusted()` to get the raw timestamp).
The clock is shared among all animations in niri through passing around and storing a reference-counted pointer.
This way, overriding the time automatically applies to everything, whereas in tests we can use a separate clock per test so that they don't interfere with each other.
These are some of the general principles for the design of niri's window layout.
They can be sidestepped in specific circumstances if there's a good reason.
1. Opening a new window should not affect the sizes of any existing windows.
1. The focused window should not move around on its own.
- In particular: windows opening, closing, and resizing to the left of the focused window should not cause it to visually move.
1. Actions should apply immediately.
- Things like resizing or consuming into column take effect immediately, even if the window needs time to catch up.
- This is important both for compositor responsiveness and predictability, and for keeping the code sane and free of edge cases and unnecessary asynchrony.
1. If a window or popup is larger than the screen, it should be aligned in the top left corner.
- The top left area of a window is more likely to contain something important, so it should always be visible.
1. Setting window width or height to a fixed pixel size (e.g. `set-column-width 1280` or `default-column-width { fixed 1280; }`) will set the size of the window itself, however setting to a proportional size (e.g. `set-column-width 50%`) will set the size of the tile, including the border added by niri.
- With proportions, the user is looking to tile multiple windows on the screen, so they should include borders.
- With fixed sizes, the user wants to test a specific client size or take a specifically sized screenshot, so they should affect the window directly.
- After the size is set, it is always converted to a value that includes the borders, to make the code sane. That is, `set-column-width 1000` followed by changing the niri border width will resize the window accordingly.
And here are some more principles I try to follow throughout niri.
1. When disabled, eye-candy features should not affect the performance.
- Things like animations and custom shaders do not run and are not present in the render tree when disabled. Extra offscreen rendering is avoided.
- Animations specifically are still "started" even when disabled, but with a duration of 0 (this way, they end as soon as the time is advanced). This does not impact performance, but helps avoid a lot of edge cases in the code.
1. Eye-candy features should not cause unreasonable excessive rendering.
- For example, clip-to-geometry will prevent direct scanout in many cases (since the window surface is not completely visible). But in the cases where the surface or the subsurface *is* completely visible (fully within the clipped region), it will still allow for direct scanout.
- For example, animations *can* cause damage and even draw to an offscreen every frame, because they are expected to be short (and can be disabled). However, something like the rounded corners shader should not offscreen or cause excessive damage every frame, because it is long-running and constantly active.
1. Be mindful of invisible state.
This is niri state that is not immediately apparent from looking at the screen. This is not bad per se, but you should carefully consider how to reduce the surprise factor.
- For example, when a monitor disconnects, all its workspaces move to another connected monitor. In order to be able to restore these workspaces when the first monitor connects again, these workspaces keep the knowledge of which was their *original monitor*—this is an example of invisible state, since you can't tell it in any way by looking at the screen. This can have surprising consequences: imagine disconnecting a monitor at home, going to work, completely rearranging the windows there, then coming back home, and suddenly some random workspaces end up on your home monitor. In order to reduce this surprise factor, whenever a new window appears on a workspace, that workspace resets its *original monitor* to its current monitor. This way, the workspaces you actively worked on remain where they were.
- For example, niri preserves the view position whenever a window appears, or whenever a window goes full-screen, to restore it afterward. This way, dealing with temporary things like dialogs opening and closing, or toggling full-screen, becomes less annoying, since it doesn't mess up the view position. This is also invisible state, as you cannot tell by looking at the screen where closing a window will restore the view position. If taken to the extreme (previous view position saved forever for every open window), this can be surprising, as closing long-running windows would result in the view shifting around pretty much randomly. To reduce this surprise factor, niri remembers only one last view position per workspace, and forgets this stored view position upon window focus change.
The main way of testing niri during development is running it as a nested window. The second step is usually switching to a different TTY and running niri there.
Once a feature or fix is reasonably complete, you generally want to run a local build as your main compositor for proper testing. The easiest way to do that is to install niri normally (from a distro package for example), then overwrite the binary with `sudo cp ./target/release/niri /usr/bin/niri`. Do make sure that you know how to revert to a working version in case everything breaks though.
If you use an RPM-based distro, you can generate an RPM package for a local build with `cargo generate-rpm`.
## Logging Levels
Niri uses [`tracing`](https://lib.rs/crates/tracing) for logging. This is how logging levels are used:
-`error!`: programming errors and bugs that are recoverable. Things you'd normally use `unwrap()` for. However, when a Wayland compositor crashes, it brings down the entire session, so it's better to recover and log an `error!` whenever reasonable. If you see an `ERROR` in the niri log, that always indicates a *bug*.
-`warn!`: something bad but still *possible* happened. Informing the user that they did something wrong, or that their hardware did something weird, falls into this category. For example, config parsing errors should be indicated with a `warn!`.
-`info!`: the most important messages related to normal operation. Running niri with `RUST_LOG=niri=info` should not make the user want to disable logging altogether.
-`debug!`: less important messages related to normal operation. Running niri with `debug!` messages hidden should not negatively impact the UX.
-`trace!`: everything that can be useful for debugging but is otherwise too spammy or performance intensive. `trace!` messages are *compiled out* of release builds.
## Tests
We have some unit tests, most prominently for the layout code and for config parsing.
When adding new operations to the layout, add them to the `Op` enum at the bottom of `src/layout/mod.rs` (this will automatically include it in the randomized tests), and if applicable to the `every_op` arrays below.
When adding new config options, include them in the config parsing test.
### Running Tests
Make sure to run `cargo test --all` to run tests from sub-crates too.
Some tests are a bit too slow to run normally, like the randomized tests of the layout code, so they are normally skipped. Set the `RUN_SLOW_TESTS` variable to run them:
```
env RUN_SLOW_TESTS=1 cargo test --all
```
It also usually helps to run the randomized tests for a longer period, so that they can explore more inputs. You can control this with environment variables. This is how I usually run tests before pushing:
```
env RUN_SLOW_TESTS=1 PROPTEST_CASES=200000 PROPTEST_MAX_GLOBAL_REJECTS=200000 RUST_BACKTRACE=1 cargo test --release --all
```
### Visual Tests
The `niri-visual-tests` sub-crate is a GTK application that runs hard-coded test cases so that you can visually check that they look right. It uses mock windows with the real layout and rendering code. It is especially helpful when working on animations.
## Profiling
We have integration with the [Tracy](https://github.com/wolfpld/tracy) profiler which you can enable by building niri with a feature flag:
Then you can open Tracy (you will need the latest stable release) and attach to a running niri instance to collect profiling data. Profiling data is collected "on demand"—that is, only when Tracy is connected. You can run a niri build like this as your main compositor if you'd like.
> [!NOTE]
> If you need to profile niri startup or the niri CLI, you can opt for "always on" profiling instead, using this feature flag:
This is generated with the `publish-wiki` job in `.github/workflows/ci.yml`.
In order to have this job run as expected in your fork, you'll need to enable the wiki feature in your repo's settings on GitHub.
This could be useful as a contributor to verify that the wiki generates the way you expect it to.
## The documentation site
The documentation site is generated with [mkdocs](https://www.mkdocs.org/).
The configuration files are found in `docs/`.
To set up and run the documentation site locally, it is recommended to use [uv](https://docs.astral.sh/uv/).
### Serving the site locally with uv
In the `docs/` subdirectory:
-`uv sync`
-`uv run mkdocs serve`
The documentation site should now be available on http://127.0.0.1:8000/niri/
Changes made to the documentation while the development server is running will cause an automatic page refresh in the browser.
> [!TIP]
> Images may not be visible, as they are stored on Git LFS.
> If this is the case, run `git lfs pull`.
## Elements
Elements such as links, admonitions, images, and snippets should work as expected in markdown file previews on GitHub, the GitHub repo's wiki, and in the documentation site.
### Links
Links should in all cases be relative (e.g. `./FAQ.md`), unless it's an external one.
Links should have anchors if they are meant to lead the user to a specific section on a page (e.g. `./Getting-Started.md#nvidia`).
> [!TIP]
> mkdocs will terminate if relative links lead to non-existing documents or non-existing anchors.
> This means that the CI pipeline will fail when building documentation, as will `mkdocs serve` locally.
### Admonitions
> [!IMPORTANT]
> This is an important distinction from other `mkdocs`-based documentation you might have encountered.
Admonitions, or alerts should be written [the way GitHub defines them](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts).
The above admonition is written like this:
```
> [!IMPORTANT]
> This is an important distinction from other `mkdocs`-based documentation you might have encountered.
```
### Images
Images should have relative links to resources in `docs/wiki/img/`, and should contain sensible alt-text.
### Videos
For compatibility with both mkdocs and GitHub Wiki, videos need to be wrapped in a `<video>` tag (displayed by mkdocs) and have the video link again as fallback text (displayed by GitHub Wiki) padded with blank lines.
There are two main coordinate spaces in niri: physical (pixels of every individual output) and logical (shared among all outputs, takes into account the scale of every output).
Wayland clients mostly work in the logical space, and it's the most convenient space to do all the layout in, since it bakes in the output scaling factor.
However, many things need to be sized or positioned at integer physical coordinates.
For example, Wayland toplevel buffers are assumed to be placed at an integer physical pixel on an output (and `WaylandSurfaceRenderElement` will do that for you).
Borders and focus rings should also have a width equal to an integer number of physical pixels to stay crisp (not to mention that `SolidColorRenderElement` does not anti-alias lines at fractional pixel positions).
Integer physical coordinates do not necessarily correspond to integer logical coordinates though.
Even with an integer scale = 2, a physical pixel at (1, 1) will be at the logical position of (0.5, 0.5).
This problem becomes much worse with fractional scale factors where most integer logical coordinates will fall on fractional physical coordinates.
Thus, niri uses fractional logical coordinates for most of its layout.
However, one needs to be very careful to keep things aligned to the physical grid to avoid artifacts like:
* Border width alternating 1 px thicker/thinner
* Border showing 1 px off from the window at certain positions
* 1 px gaps around rounded corners
* Slightly blurry window contents during resizes
* And so on...
The way it's handled in niri is:
1. All relevant sizes on a workspace are rounded to an integer physical coordinate according to the current output scale. Things like struts, gaps, border widths, working area location.
It's important to understand that they remain fractional numbers in the logical space, but these numbers correspond to an integer number of pixels in the physical space.
The rounding looks something like: `(logical_size * scale).round() / scale`.
Whenever a workspace moves to an output with a different scale (or the output scale changes), all sizes are re-rounded from their original configured values to align with the new physical space.
2. The view offset and individual column/tile render offsets are *not* rounded to physical pixels, but:
3.`tiles_with_render_positions()` rounds tile positions to physical pixels as it returns them,
4. Custom shaders like opening, closing and resizing windows, are also careful to keep positions and sizes rounded to the physical pixels.
The idea is that every tile can assume that it is rendered at an integer physical coordinate, therefore when shifting the position by, say, border width (also rounded to integer physical coordinates), the new position will stay rounded to integer physical coordinates.
The same logic works for the rest of the layout thanks to gaps, struts and working area being similarly rounded.
This way, the entire layout is always aligned, as long as it is positioned at an integer physical coordinate (which rounding the tile positions effectively achieves).
On a TTY, only one frame can be submitted to an output at a time, and the compositor must wait until the output repaints (indicated by a VBlank) to be able to submit the next frame.
In niri we keep track of this via the `RedrawState` enum that you can find in an `OutputState`.
Here's a diagram of state transitions for the `RedrawState` state machine:
<img alt="RedrawState state transition diagram" src="./img/RedrawState-light.drawio.png">
</picture>
`Idle` is the default state, when the output does not need to be repainted.
Any operation that may cause the screen to update calls `queue_redraw()`, which moves the output to a `Queued` state.
Then, at the end of an event loop dispatch, niri calls `redraw()` for every `Queued` output.
If the redraw causes damage (i.e. something on the output changed), we move into the `WaitingForVBlank` state, since we cannot redraw until we receive a VBlank event.
However, if there's no damage, we do not return to `Idle` right away.
Instead, we set a timer to fire roughly at when the next VBlank would occur, and transition to a `WaitingForEstimatedVBlank` state.
This is necessary in order to throttle frame callbacks sent to applications to at most once per output refresh cycle.
Without this throttling, applications can start continuously redrawing without damage (for instance, if the application window is partially off-screen, and it is only the off-screen part that changes), and eating a lot of CPU in the process.
Then, either the estimated VBlank timer completes, and we go back to `Idle`, or maybe we call `queue_redraw()` once more and try to redraw again.
When starting niri from a display manager like GDM, or otherwise through the `niri-session` binary, it runs as a systemd service.
This provides the necessary systemd integration to run programs like `mako` and services like `xdg-desktop-portal` bound to the graphical session.
Here's an example on how you might set up [`mako`](https://github.com/emersion/mako), [`waybar`](https://github.com/Alexays/Waybar), [`swaybg`](https://github.com/swaywm/swaybg) and [`swayidle`](https://github.com/swaywm/swayidle) to run as systemd services with niri.
Unlike [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup), this lets you easily monitor their status and output, and restart or reload them.
This will create links in `~/.config/systemd/user/niri.service.wants/`, a special systemd folder for services that need to start together with `niri.service`.
3. `swaybg` does not provide a systemd unit, since you need to pass the background image as a command-line argument.
So we will make our own.
Create `~/.config/systemd/user/swaybg.service` with the following contents:
```systemd
[Unit]
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target
[Service]
ExecStart=/usr/bin/swaybg -m fill -i "%h/Pictures/LakeSide.png"
Restart=on-failure
```
Replace the image path with the one you want.
`%h` is expanded to your home directory.
After editing `swaybg.service`, run `systemctl --user daemon-reload` so systemd picks up the changes in the file.
Now these three utilities will be started together with the niri session and stopped when it exits.
You can also restart them with a command like `systemctl --user restart waybar.service`, for example after editing their config files.
To remove a service from niri startup, remove its symbolic link from `~/.config/systemd/user/niri.service.wants/`.
Then, run `systemctl --user daemon-reload`.
### Running Programs Across Logout
When running niri as a session, exiting it (logging out) will kill all programs that you've started within. However, sometimes you want a program, like `tmux`, `dtach` or similar, to persist in this case. To do this, run it in a transient systemd scope:
### How to disable client-side decorations/make windows rectangular?
Uncomment the [`prefer-no-csd` setting](./Configuration:-Miscellaneous.md#prefer-no-csd) at the top level of the config, and then restart your apps.
Then niri will ask windows to omit client-side decorations, and also inform them that they are being tiled (which makes some windows rectangular, even if they cannot omit the decorations).
Note that currently this will prevent edge window resize handles from showing up.
You can still resize windows by holding <kbd>Mod</kbd> and the right mouse button.
### Why are transparent windows tinted? / Why is the border/focus ring showing up through semitransparent windows?
Uncomment the [`prefer-no-csd` setting](./Configuration:-Miscellaneous.md#prefer-no-csd) at the top level of the config, and then restart your apps.
Niri will draw focus rings and borders *around* windows that agree to omit their client-side decorations.
By default, focus ring and border are rendered as a solid background rectangle behind windows.
That is, they will show up through semitransparent windows.
This is because windows using client-side decorations can have an arbitrary shape.
You can also override this behavior with the [`draw-border-with-background` window rule](./Configuration:-Window-Rules.md#draw-border-with-background).
### How to enable rounded corners for all windows?
Put this window rule in your config:
```kdl
window-rule{
geometry-corner-radius12
clip-to-geometrytrue
}
```
For more information, check the [`geometry-corner-radius` window rule](./Configuration:-Window-Rules.md#geometry-corner-radius).
### How to hide the "Important Hotkeys" pop-up at the start?
Put this into your config:
```kdl
hotkey-overlay{
skip-at-startup
}
```
### How to run X11 apps like Steam or Discord?
To run X11 apps, you can use [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite).
Check [the Xwayland wiki page](./Xwayland.md) for instructions.
Keep in mind that you can run many Electron apps such as VSCode natively on Wayland by passing the right flags, e.g. `code --ozone-platform-hint=auto`
### Why doesn't niri integrate Xwayland like other compositors?
A combination of factors:
- Integrating Xwayland is quite a bit of work, as the compositor needs to implement parts of an X11 window manager.
- You need to appease the X11 ideas of windowing, whereas for niri I want to have the best code for Wayland.
- niri doesn't have a good global coordinate system required by X11.
- You tend to get an endless stream of X11 bugs that take further time and effort away from other tasks.
- There aren't actually that many X11-only clients nowadays, and xwayland-satellite takes perfect care of most of those.
- niri isn't a Big Serious Desktop Environment which Must Support All Use Cases (and is Backed By Some Corporation).
All in all, the situation works out in favor of avoiding Xwayland integration.
Also, in the next release niri will have seamless built-in xwayland-satellite integration, that will solve the big rough edge of having to set it up manually.
Besides, I wouldn't be too surprised if, down the road, xwayland-satellite becomes the standard way of integrating Xwayland into new compositors, since it takes on the bulk of the annoying work, and isolates the compositor from misbehaving clients.
### Can I enable blur behind semitransparent windows?
Not yet, follow/upvote [this issue](https://github.com/YaLTeR/niri/issues/54).
There's also [a PR](https://github.com/YaLTeR/niri/pull/1634) adding blur to niri which you can build and run manually.
Keep in mind that it's an experimental implementation that may have problems and performance concerns.
### Can I make a window sticky / pinned / always on top / appear on all workspaces?
Not yet, follow/upvote [this issue](https://github.com/YaLTeR/niri/issues/932).
You can emulate this with a script that uses the niri IPC.
For example, [nirius](https://git.sr.ht/~tsdh/nirius) seems to have this feature (`toggle-follow-mode`).
### How do I make the Bitwarden window in Firefox open as floating?
Firefox seems to first open the Bitwarden window with a generic Firefox title, and only later change the window title to Bitwarden, so you can't effectively target it with an `open-floating` window rule.
You'll need to use a script, for example [this one](https://github.com/YaLTeR/niri/discussions/1599) or other ones (search niri issues and discussions for Bitwarden).
The easiest way to get niri is to install one of the distribution packages.
Here are some of them: [Fedora COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri/) and [nightly COPR](https://copr.fedorainfracloud.org/coprs/yalter/niri-git/) (which I maintain myself), [NixOS Flake](https://github.com/sodiboo/niri-flake), and some more from repology below.
See the [Building](#building) section if you'd like to compile niri yourself and the [Packaging niri](./Packaging-niri.md) page if you want to package niri.
After installing, start niri from your display manager like GDM.
Press <kbd>Super</kbd><kbd>T</kbd> to run a terminal ([Alacritty]) and <kbd>Super</kbd><kbd>D</kbd> to run an application launcher ([fuzzel]).
To exit niri, press <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd>.
If you're not using a display manager, you should run `niri-session` (systemd/dinit) or `niri --session` (others) from a TTY.
The `--session` flag will make niri import its environment variables globally into the system manager and D-Bus, and start its D-Bus services.
The `niri-session` script will additionally start niri as a systemd/dinit service, which starts up a graphical session target required by some services like portals.
You can also run `niri` inside an existing desktop session.
Then it will open as a window, where you can give it a try.
Note that this windowed mode is mainly meant for development, so it is a bit buggy (in particular, there are issues with hotkeys).
Next, see the [list of important software](./Important-Software.md) required for normal desktop use, like a notification daemon and portals.
Also, check the [configuration introduction](./Configuration:-Introduction.md) page to get started configuring niri.
There you can find links to other pages containing thorough documentation and examples for all options.
Finally, the [Xwayland](./Xwayland.md) page explains how to run X11 applications on niri.
### Desktop environments
Some desktop environments and shells work with niri and can give a more out-of-the-box experience:
- [LXQt](https://lxqt-project.org/) officially supports niri, see [their wiki](https://github.com/lxqt/lxqt/wiki/ConfigWaylandSettings#general) for details on setting it up.
- Many [XFCE](https://www.xfce.org/) components work on Wayland, including niri. See [their wiki](https://wiki.xfce.org/releng/wayland_roadmap#component_specific_status) for details.
- There are complete desktop shells based on Quickshell that support niri, for example [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell) and [Noctalia](https://github.com/noctalia-dev/noctalia-shell).
- You can run a [COSMIC](https://system76.com/cosmic/) session with niri using [cosmic-ext-extra-sessions](https://github.com/Drakulix/cosmic-ext-extra-sessions).
### NVIDIA
The NVIDIA drivers currently have an issue with high VRAM usage due to a heap reuse quirk.
You're recommended to apply a manual fix documented [here](./Nvidia.md) if you run niri on an NVIDIA GPU.
NVIDIA GPUs can have problems running niri (for example, the screen remains black upon starting from a TTY).
Sometimes, the problems can be fixed.
You can try the following:
1. Update NVIDIA drivers. You need a GPU and drivers recent enough to support GBM.
2. Make sure kernel modesetting is enabled. This usually involves adding `nvidia-drm.modeset=1` to the kernel command line. Find and follow a guide for your distribution. Guides from other Wayland compositors can help.
### Asahi, ARM, and other kmsro devices
On some of these systems, niri fails to correctly detect the primary render device.
If you're getting a black screen when starting niri on a TTY, you can try to set the device manually.
First, find which devices you have:
```
$ ls -l /dev/dri/
drwxr-xr-x@ - root 14 мая 07:07 by-path
crw-rw----@ 226,0 root 14 мая 07:07 card0
crw-rw----@ 226,1 root 14 мая 07:07 card1
crw-rw-rw-@ 226,128 root 14 мая 07:07 renderD128
crw-rw-rw-@ 226,129 root 14 мая 07:07 renderD129
```
You will likely have one `render` device and two `card` devices.
Open the niri config file at `~/.config/niri/config.kdl` and put your `render` device path like this:
```kdl
debug{
render-drm-device"/dev/dri/renderD128"
}
```
Save, then try to start niri again.
If you still get a black screen, try using each of the `card` devices.
### Nix/NixOS
There's a common problem of mesa drivers going out of sync with niri, so make sure your system mesa version matches the niri mesa version.
When this happens, you usually see a black screen when trying to start niri from a TTY.
Also, on Intel graphics, you may need a workaround described [here](https://nixos.wiki/wiki/Intel_Graphics).
### Virtual Machines
To run niri in a VM, make sure to enable 3D acceleration.
## Main Default Hotkeys
When running on a TTY, the Mod key is <kbd>Super</kbd>.
When running in a window, the Mod key is <kbd>Alt</kbd>.
The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kbd> will move the focused window or column there.
| Hotkey | Description |
| ------ | ----------- |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>/</kbd> | Show a list of important niri hotkeys |
| <kbd>Mod</kbd><kbd>Q</kbd> | Close the focused window |
| <kbd>Mod</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>←</kbd> | Focus the column to the left |
| <kbd>Mod</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>→</kbd> | Focus the column to the right |
| <kbd>Mod</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>↓</kbd> | Focus the window below in a column |
| <kbd>Mod</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>↑</kbd> | Focus the window above in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>H</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>←</kbd> | Move the focused column to the left |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>→</kbd> | Move the focused column to the right |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>J</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↓</kbd> | Move the focused window below in a column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>K</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>↑</kbd> | Move the focused window above in a column |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Focus the monitor to the side |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>↑</kbd><kbd>→</kbd> | Move the focused column to the monitor to the side |
| <kbd>Mod</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>PageDown</kbd> | Switch to the workspace below |
| <kbd>Mod</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>PageUp</kbd> | Switch to the workspace above |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>PageDown</kbd> | Move the focused column to the workspace below |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>PageUp</kbd> | Move the focused column to the workspace above |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>U</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageDown</kbd> | Move the focused workspace down |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>I</kbd> or <kbd>Mod</kbd><kbd>Shift</kbd><kbd>PageUp</kbd> | Move the focused workspace up |
| <kbd>Mod</kbd><kbd>,</kbd> | Consume the window to the right into the focused column |
| <kbd>Mod</kbd><kbd>.</kbd> | Expel the bottom window in the focused column into its own column |
| <kbd>Mod</kbd><kbd>[</kbd> | Consume or expel the focused window to the left |
| <kbd>Mod</kbd><kbd>]</kbd> | Consume or expel the focused window to the right |
| <kbd>Mod</kbd><kbd>R</kbd> | Toggle between preset column widths |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>R</kbd> | Toggle between preset column heights |
| <kbd>Mod</kbd><kbd>F</kbd> | Maximize column |
| <kbd>Mod</kbd><kbd>C</kbd> | Center column within view |
| <kbd>Mod</kbd><kbd>-</kbd> | Decrease column width by 10% |
| <kbd>Mod</kbd><kbd>=</kbd> | Increase column width by 10% |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>-</kbd> | Decrease window height by 10% |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>=</kbd> | Increase window height by 10% |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>R</kbd> | Reset window height back to automatic |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>F</kbd> | Toggle full-screen on the focused window |
| <kbd>Mod</kbd><kbd>V</kbd> | Move the focused window between the floating and the tiling layout |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>V</kbd> | Switch focus between the floating and the tiling layout |
| <kbd>PrtSc</kbd> | Take an area screenshot. Select the area to screenshot with mouse, then press Space to save the screenshot, or Escape to cancel |
| <kbd>Alt</kbd><kbd>PrtSc</kbd> | Take a screenshot of the focused window to clipboard and to `~/Pictures/Screenshots/` |
| <kbd>Ctrl</kbd><kbd>PrtSc</kbd> | Take a screenshot of the focused monitor to clipboard and to `~/Pictures/Screenshots/` |
| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>E</kbd> or <kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>Delete</kbd> | Exit niri |
## Building
First, install the dependencies for your distribution.
For example, you can replace systemd integration with dinit integration using `cargo build --release --no-default-features --features dinit,dbus,xdp-gnome-screencast`.
> [!WARNING]
> Do NOT build with `--all-features`!
>
> Some features are meant only for development use.
> For example, one of the features enables collection of profiling data into a memory buffer that will grow indefinitely until you run out of memory.
### NixOS/Nix
We have a community-maintained flake which provides a devshell with required dependencies. Use `nix build` to build niri, and then run `./results/bin/niri`.
If you're not on NixOS, you may need [NixGL](https://github.com/nix-community/nixGL) to run the resulting binary:
```sh
nix run --impure github:guibou/nixGL -- ./results/bin/niri
```
### Manual Installation
If installing directly without a package, the recommended file destinations are slightly different.
In this case, put the files in the directories indicated in the table below.
These may vary depending on your distribution.
Don't forget to make sure that the path to `niri` in niri.service is correct.
You can communicate with the running niri instance over an IPC socket.
Check `niri msg --help` for available commands.
The `--json` flag prints the response in JSON, rather than formatted.
For example, `niri msg --json outputs`.
> [!TIP]
> If you're getting parsing errors from `niri msg` after upgrading niri, make sure that you've restarted niri itself.
> You might be trying to run a newer `niri msg` against an older `niri` compositor.
### Event Stream
<sup>Since: 0.1.9</sup>
While most niri IPC requests return a single response, the event stream request will make niri continuously stream events into the IPC connection until it is closed.
This is useful for implementing various bars and indicators that update as soon as something happens, without continuous polling.
The event stream IPC is designed to give you the complete current state up-front, then follow up with updates to that state.
This way, your state can never "desync" from niri, and you don't need to make any other IPC information requests.
Where reasonable, event stream state updates are atomic, though this is not always the case.
For example, a window may end up with a workspace id for a workspace that had already been removed.
This can happen if the corresponding workspaces-changed event arrives before the corresponding window-changed event.
To get a taste of the events, run `niri msg event-stream`.
Though, this is more of a debug function than anything.
You can get raw events from `niri msg --json event-stream`, or by connecting to the niri socket and requesting an event stream manually.
You can find the full list of events along with documentation [here](https://yalter.github.io/niri/niri_ipc/enum.Event.html).
### Programmatic Access
`niri msg --json` is a thin wrapper over writing and reading to a socket.
When implementing more complex scripts and modules, you're encouraged to access the socket directly.
Connect to the UNIX domain socket located at `$NIRI_SOCKET` in the filesystem.
Write your request encoded in JSON on a single line, followed by a newline character, or by flushing and shutting down the write end of the connection.
Read the reply as JSON, also on a single line.
You can use `socat` to test communicating with niri directly:
Since niri is not a complete desktop environment, you will very likely want to run the following software to make sure that other apps work fine.
### Notification Daemon
Many apps need one. For example, [mako](https://github.com/emersion/mako) works well. Use [a systemd setup](./Example-systemd-Setup.md) or [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup).
### Portals
These provide a cross-desktop API for apps to use for various things like file pickers or UI settings. Flatpak apps in particular require working portals.
Portals **require** [running niri as a session](./Getting-Started.md), which means through the `niri-session` script or from a display manager. You will want the following portals installed:
*`xdg-desktop-portal-gtk`: implements most of the basic functionality, this is the "default fallback portal".
*`xdg-desktop-portal-gnome`: required for screencasting support.
*`gnome-keyring`: implements the Secret portal, required for certain apps to work.
Then systemd should start them on-demand automatically. These particular portals are configured in `niri-portals.conf` which [must be installed](./Getting-Started.md#manual-installation) in the correct location.
Since we're using `xdg-desktop-portal-gnome`, Flatpak apps will read the GNOME UI settings. For example, to enable the dark style, run:
Note that if you're using the provided `resources/niri-portals.conf`, you also need to install the `nautilus` file manager in order for file chooser dialogues to work properly. This is necessary because xdg-desktop-portal-gnome uses nautilus as the file chooser by default starting from version 47.0.
If you do not want to install `nautilus` (say you use `nemo` instead), you can set `org.freedesktop.impl.portal.FileChooser=gtk;` in `niri-portals.conf` to use the GTK portal for file chooser dialogues.
### Authentication Agent
Required when apps need to ask for root permissions. Something like `plasma-polkit-agent` works fine. Start it [with systemd](./Example-systemd-Setup.md) or with [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup).
Note that to start `plasma-polkit-agent` with systemd on Fedora, you'll need to override its systemd service to add the correct dependency. Run:
Things to keep in mind with layer-shell components (bars, launchers, etc.):
1. When a full-screen window is active and covers the entire screen, it will render above the top layer, and it will be prioritized for keyboard focus. If your launcher uses the top layer, and you try to run it while looking at a full-screen window, it won't show up. Only the overlay layer will show up on top of full-screen windows.
1. Components on the bottom and background layers will receive *on-demand* keyboard focus as expected. However, they will only receive *exclusive* keyboard focus when there are no windows on the workspace.
1. When opening the [Overview](./Overview.md), components on the bottom and background layers will zoom out and remain on the workspaces, while the top and overlay layers remain on top of the Overview. So, if you want the bar to remain on top, put it on the *top* layer.
Presently, there is a quirk in the NVIDIA drivers that affects niri's VRAM usage (the driver does not properly release VRAM back into the pool). Niri *should* use on the order of 100 MiB of VRAM (as checked in [nvtop](https://github.com/Syllo/nvtop)); if you see anywhere close to 1 GiB of VRAM in use, you are likely hitting this issue (heap not returning freed buffers to the driver).
Luckily, you can mitigate this by configuring the NVIDIA drivers with a per-process application profile as follows:
*`sudo mkdir -p /etc/nvidia/nvidia-application-profiles-rc.d` to make the config dir if it does not exist (it most likely does not if you are reading this)
* write the following JSON blob to set the `GLVidHeapReuseRatio` config value for the `niri` process into the file `/etc/nvidia/nvidia-application-profiles-rc.d/50-limit-free-buffer-pool-in-wayland-compositors.json`:
```json
{
"rules": [
{
"pattern": {
"feature": "procname",
"matches": "niri"
},
"profile": "Limit Free Buffer Pool On Wayland Compositors"
}
],
"profiles": [
{
"name": "Limit Free Buffer Pool On Wayland Compositors",
"settings": [
{
"key": "GLVidHeapReuseRatio",
"value": 0
}
]
}
]
}
```
(The file in `/etc/nvidia/nvidia-application-profiles-rc.d/` can be named anything, and does not actually need an extension).
Restart niri after writing the config file to apply the change.
The upstream issue that this solution was pulled from is [here](https://github.com/NVIDIA/egl-wayland/issues/126#issuecomment-2379945259). There is a (slim) chance that NVIDIA updates their built-in application profiles to apply this to niri automatically; it is unlikely that the underlying heuristic will see a proper fix.
The fix shipped in the driver at the time of writing uses a value of 0, while the initial config posted by an Nvidia engineer approximately a year prior used a value of 1.
### Screencast flickering fix
<sup>Until: next release</sup>
If you have screencast glitches or flickering on NVIDIA, set this in the niri config:
```kdl,must-fail
debug {
wait-for-frame-completion-in-pipewire
}
```
This will become unnecessary once niri supports explicit sync for PipeWire screencasts: https://github.com/YaLTeR/niri/issues/1432
Open it with the `toggle-overview` bind, via the top-left hot corner, or using a touchpad four-finger swipe up.
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.
> [!TIP]
> The overview needs to draw a background under every workspace.
> So, layer-shell surfaces work this way: the *background* and *bottom* layers zoom out together with the workspaces, while the *top* and *overlay* layers remain on top of the overview.
>
> Put your bar on the *top* layer.
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.
See the full documentation for the `overview {}` section [here](./Configuration:-Miscellaneous.md#overview).
You can set the zoom-out level like this:
```kdl
// Make workspaces four times smaller than normal in the overview.
overview{
zoom0.25
}
```
To change the color behind the workspaces, use the `backdrop-color` setting:
```kdl
// Make the backdrop light.
overview{
backdrop-color"#777777"
}
```
You can also disable the hot corner:
```kdl
// Disable the hot corners.
gestures{
hot-corners{
off
}
}
```
### Backdrop customization
Apart from setting a custom backdrop color like described above, you can also put a layer-shell wallpaper into the backdrop with a [layer rule](./Configuration:-Layer-Rules.md#place-within-backdrop), for example:
```kdl
// Put swaybg inside the overview backdrop.
layer-rule{
matchnamespace="^wallpaper$"
place-within-backdroptrue
}
```
This will only work for *background* layer surfaces that ignore exclusive zones (typical for wallpaper tools).
You can run two different wallpaper tools (like swaybg and swww), one for the backdrop and one for the normal workspace background.
This way you could set the backdrop one to a blurred version of the wallpaper for a nice effect.
You can also combine this with a transparent background color if you don't like the wallpaper moving together with workspaces:
```kdl
// Make the wallpaper stationary, rather than moving with workspaces.
layer-rule{
// This is for swaybg; change for other wallpaper tools.
// Find the right namespace by running niri msg layers.
matchnamespace="^wallpaper$"
place-within-backdroptrue
}
// Set transparent workspace background color.
layout{
background-color"transparent"
}
// Optionally, disable the workspace shadows in the overview.
When building niri, check `Cargo.toml` for a list of build features.
For example, you can replace systemd integration with dinit integration using `cargo build --release --no-default-features --features dinit,dbus,xdp-gnome-screencast`.
The defaults however should work fine for most distributions.
> [!WARNING]
> Do NOT build with `--all-features`!
>
> Some features are meant only for development use.
> For example, one of the features enables collection of profiling data into a memory buffer that will grow indefinitely until you run out of memory.
The `niri-visual-tests` sub-crate/binary is development-only and should not be packaged.
The recommended way to package niri is so that it runs as a standalone desktop session.
To do that, put files into the correct directories according to this table.
Doing this will make niri appear in GDM and other display managers.
### Running tests
A bulk of our tests spawn niri compositor instances and test Wayland clients.
This does not require a graphical session, however due to test parallelism, it can run into file descriptor limits on high core count systems.
If you run into this problem, you may need to limit not just the Rust test harness thread count, but also the Rayon thread count, since some niri tests use internal Rayon threading:
```
$ export RAYON_NUM_THREADS=2
...proceed to run cargo test, perhaps with --test-threads=2
```
Don't forget to exclude the development-only `niri-visual-tests` crate when running tests.
You may also want to set the `RUN_SLOW_TESTS=1` environment variable to run the slower tests.
### Version string
The niri version string includes its version and commit hash:
```
$ niri --version
niri 25.01 (e35c630)
```
When building in a packaging system, there's usually no repository, so the commit hash is unavailable and the version will show "unknown commit".
In this case, please set the commit hash manually:
```
$ export NIRI_BUILD_COMMIT="e35c630"
...proceed to build niri
```
You can also override the version string entirely, in this case please make sure the corresponding niri version stays intact:
at /builddir/build/BUILD/rust-1.83.0-build/rustc-1.83.0-src/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```
Important things to look for:
- The panic message is there: "overflow when subtracting durations".
- The backtrace goes all the way up to `main` and includes `cause_panic`.
- The backtrace includes the file and line number for `cause_panic`: `at /.../src/utils/mod.rs:382:13`.
If possible, please ensure that your niri package on its own has good panics, i.e. *without* installing debuginfo or other packages.
The user likely won't have debuginfo installed when their compositor first crashes, and we really want to be able to diagnose and fix all crashes right away.
### Rust dependencies
Every niri release comes with a vendored dependencies archive from `cargo vendor`.
You can use it to build the corresponding niri release completely offline.
If you don't want to use vendored dependencies, consider following the niri release's `Cargo.lock`.
It contains the exact dependency versions that I used when testing the release.
If you need to change the versions of some dependencies, pay extra attention to `smithay` and `smithay-drm-extras` commit hash.
These crates don't currently have regular stable releases, so niri uses git snapshots.
Upstream frequently has breaking changes (API and behavior), so you're strongly advised to use the exact commit hash from the niri release's `Cargo.lock`.
The primary screencasting interface that niri offers is through portals and pipewire.
It is supported by [OBS], Firefox, Chromium, Electron, Telegram, and other apps.
You can screencast both monitors and individual windows.
In order to use it, you need a working D-Bus session, pipewire, `xdg-desktop-portal-gnome`, and [running niri as a session](./Getting-Started.md) (i.e. through `niri-session` or from a display manager).
On widely used distros this should all "just work".
Alternatively, you can use tools that rely on the `wlr-screencopy` protocol, which niri also supports.
There are several features in niri designed for screencasting.
Let's take a look!
### Block out windows
You can block out specific windows from screencasts, replacing them with solid black rectangles.
This can be useful for password managers or messenger windows, etc.

This is controlled through the `block-out-from` window rule, for example:
```kdl
// Block out password managers from screencasts.
window-rule{
matchapp-id=r#"^org\.keepassxc\.KeePassXC$"#
matchapp-id=r#"^org\.gnome\.World\.Secrets$"#
block-out-from"screencast"
}
```
You can similarly block out layer surfaces, using a layer rule:
```kdl
// Block out mako notifications from screencasts.
layer-rule{
matchnamespace="^notifications$"
block-out-from"screencast"
}
```
Check [the corresponding wiki section](./Configuration:-Window-Rules.md#block-out-from) for more details and examples.
### Dynamic screencast target
<sup>Since: 25.05</sup>
Niri provides a special screencast stream that you can change dynamically.
It shows up as "niri Dynamic Cast Target" in the screencast window dialog.
If the cast target disappears (e.g. the target window closes), the stream goes back to empty.
All dynamic casts share the same target, but new ones start out empty until the next time you change it (to avoid surprises and sharing something sensitive by mistake).
### Indicate screencasted windows
<sup>Since: 25.02</sup>
The [`is-window-cast-target=true` window rule](./Configuration:-Window-Rules.md#is-window-cast-target) matches windows targeted by an ongoing window screencast.
You use it with a special border color to clearly indicate screencasted windows.
This also works for windows targeted by dynamic screencasts.
However, it will not work for windows that just happen to be visible in a full-monitor screencast.
```kdl
// Indicate screencasted windows with red colors.
window-rule{
matchis-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"
}
}
```
Example:

### Windowed (fake/detached) fullscreen
<sup>Since: 25.05</sup>
When screencasting browser-based presentations like Google Slides, you usually want to hide the browser UI, which requires making the browser fullscreen.
This 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.
The `toggle-windowed-fullscreen` bind helps with this.
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.
```kdl
binds{
Mod+Ctrl+Shift+F{ toggle-windowed-fullscreen;}
}
```
Keep in mind that not all apps react to fullscreening, so it may sometimes look as if the bind did nothing.
Here's an example showing a windowed-fullscreen Google Slides [presentation](https://youtu.be/Kmz8ODolnDg), along with the presenter view and a meeting app:

You can switch a column to present windows as tabs, rather than as vertical tiles.
All tabs in a column have the same window size, so this is useful to get more vertical space.

Use this bind to toggle a column between normal and tabbed display:
```kdl
binds{
Mod+W{ toggle-column-tabbed-display;}
}
```
All other binds remain the same: switch tabs with `focus-window-down/up`, add or remove windows with `consume-window-into-column`/`expel-window-from-column`, and so on.
Unlike regular columns, tabbed columns can go full-screen with multiple windows.
### Tab indicator
Tabbed columns show a tab indicator on the side.
You can click on the indicator to switch tabs.
See the [`tab-indicator` section in the layout section](./Configuration:-Layout.md#tab-indicator) to configure it.
By default, the indicator draws "outside" the column, so it can overlay other windows or go off-screen.
The `place-within-column` flag puts the indicator "inside" the column, adjusting the window size to make space for it.
This is especially useful for thicker tab indicators, or when you have very small gaps.
| Default | `place-within-column` |
| --- | --- |
|  |  |
<img alt="Two monitors. First with three workspaces, second with two workspaces." src="./img/workspaces-light.png">
</picture>
You can move a workspace to a different monitor using binds like `move-workspace-to-monitor-left/right/up/down` and `move-workspace-to-monitor-next/previous`.
When you disconnect a monitor, its workspaces will automatically move to a different monitor.
But, they will also "remember" their original monitor, so when you reconnect it, the workspaces will automatically move back to it.
> [!TIP]
> From other tiling WMs, you may be used to thinking about workspaces like this: "These are all of my workspaces. I can show workspace X on my first monitor, and workspace Y on my second monitor."
> In niri, instead, think like this: "My first monitor contains these workspaces, including X and Y, and my second monitor contains these other workspaces. I can switch my first monitor to workspace X or Y. I can move workspace Y to my second monitor to show it there."
### Addressing workspaces by index
Several actions in niri can address workspaces "by index": `focus-workspace 2`, `move-column-to-workspace 4`.
This index refers to whichever workspace *currently happens to be* at this position on the focused monitor.
So, `focus-workspace 2` will always put you on the second workspace of the monitor, whichever workspace that currently is.
This is an important distinction from WMs with static workspace systems.
In niri, workspaces *do not have indices on their own*.
If you take the first workspace and move it further down on the monitor, `focus-workspace 1` will now put you on a different workspace (the one that was below the first workspace before you moved it).
When you want to have a more permanent workspace in niri, you can create a [named workspace](./Configuration:-Named-Workspaces.md) in the config or via the `set-workspace-name` action.
You can refer to named workspaces by name, e.g. `focus-workspace "browser"`, and they won't disappear when they become empty.
> [!TIP]
> You can try to emulate static workspaces by creating workspaces named "one", "two", "three", ..., and binding keys to `focus-workspace "one"`, `focus-workspace "two"`, ...
> This can work to some extent, but it can become somewhat confusing, since you can still move these workspaces up and down and between monitors.
>
> If you're coming from a static workspace WM, I suggest *not* doing that, but instead trying the "niri way" with dynamic workspaces, focusing and moving up/down instead of by index.
> Thanks to scrollable tiling, you generally need fewer workspaces than on a traditional tiling WM.
### Example workflow
This is how I like to use workspaces.
I will usually have my browser on the topmost workspace, then one workspace per project (or a "thing") I'm working on.
On a single workspace I have 1–2 windows that fit inside a monitor that I switch between frequently, and maybe extra windows scrolled outside the view, usually either ones I need rarely, or temporary windows that I quickly close.
When I need another permanent window, I'll put it on a new workspace.
I actively move workspaces up and down as I'm working on things to make what I need accessible in one motion.
For example, I usually frequently switch between the browser and whatever I'm doing, so I always move whatever I'm currently doing to right below the browser, so a single `focus-workspace-up/down` gets me where I want.
X11 is very cursed, so built-in Xwayland support [is not planned at the moment](./FAQ.md#why-doesnt-niri-integrate-xwayland-like-other-compositors).
However, there are multiple solutions to running X11 apps in niri.
## Using xwayland-satellite
[xwayland-satellite] implements rootless Xwayland in a separate application, without the host compositor's involvement.
It makes X11 windows appear as normal windows, just like a native Xwayland integration.
xwayland-satellite works well with most applications: Steam, games, Discord, even more exotic things like Ardour with wine Windows VST plugins.
However, X11 apps that want to position windows or bars at specific screen coordinates won't behave correctly.
> [!NOTE]
> In the next release, niri will have [built-in xwayland-satellite integration](./Configuration:-Miscellaneous.md#xwayland-satellite).
> You can try it by installing git versions of both niri and xwayland-satellite.
> With no further configuration, niri will create X11 sockets, then when an X11 client connects, automatically start xwayland-satellite.
>
> This matches how other compositors run Xwayland (but in niri's case, it's xwayland-satellite rather than Xwayland itself).
> It also makes X11 apps work fine in `spawn-at-startup` and in XDG autostart.
Install it from your package manager, or build it according to instructions from its README, then run the `xwayland-satellite` binary.
Look for a log message like: `Connected to Xwayland on :0`.
Now you can start X11 applications on this X11 DISPLAY:
```
env DISPLAY=:0 flatpak run com.valvesoftware.Steam
```

You can also automatically run it at startup, and set `DISPLAY` by default for all apps by adding it to the [`environment`](./Configuration:-Miscellaneous.md#environment) section of the niri config:
> If the `:0` DISPLAY is already taken (for example, by some other Xwayland server like `xwayland-run`), `xwayland-satellite` will try the next DISPLAY numbers in order: `:1`, `:2`, etc. and tell you which one it used in its output.
> Then, you will need to use that DISPLAY number for the `env` command or for the niri [`environment`](./Configuration:-Miscellaneous.md#environment) section.
>
> You can also force a specific DISPLAY number like so: `xwayland-satellite :12` will start on `DISPLAY=:12`.
## Using the labwc Wayland compositor
[Labwc](https://github.com/labwc/labwc) is a traditional stacking Wayland compositor with Xwayland.
You can run it as a window, then run X11 apps inside.
1. Install labwc from your distribution packages.
1. Run it inside niri with the `labwc` command.
It will open as a new window.
1. Run an X11 application on the X11 DISPLAY that it provides, e.g. `env DISPLAY=:0 glxgears`
[xwayland-run] is a helper utility to run an X11 client within a dedicated Xwayland rootful server.
It takes care of starting Xwayland, setting the X11 DISPLAY environment variable, setting up xauth and running the specified X11 client using the newly started Xwayland instance.
When the X11 client terminates, xwayland-run will automatically close the dedicated Xwayland server.
It is also possible to run the X11 application in [Cage](https://github.com/cage-kiosk/cage), which runs a nested Wayland session which also supports Xwayland, where the X11 application can run in.
Compared to the Xwayland rootful method, this does not require running an extra X11 window manager, and can be used with one command `cage -- /path/to/application`. However, it can also cause issues if multiple windows are launched inside Cage, since Cage is meant to be used in kiosks, every new window will be automatically full-screened and take over the previously opened window.
To use Cage you need to:
1. Install `cage`, it should be in most repositories.
2. Run `cage -- /path/to/application` and enjoy your X11 program on niri.
Optionally one can also modify the desktop entry for the application and add the `cage --` prefix to the `Exec` property. The Spotify Flatpak for example would look something like this:
```ini
[Desktop Entry]
Type=Application
Name=Spotify
GenericName=Online music streaming service
Comment=Access all of your favorite music
Icon=com.spotify.Client
Exec=cage -- flatpak run com.spotify.Client
Terminal=false
```
## Using gamescope
You can use [gamescope](https://github.com/ValveSoftware/gamescope) to run X11 games and even Steam itself.
Similar to Cage, gamescope will only show a single, topmost window, so it's not very suitable to running regular apps.
But you can run Steam in gamescope and then start some game from Steam just fine.
```
gamescope -- flatpak run com.valvesoftware.Steam
```
To run gamescope fullscreen, you can pass flags that set the necessary resolution, and a flag that starts it in fullscreen mode:
style="display:inline;mix-blend-mode:normal;fill:#2d2d2d;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;stroke-opacity:1;paint-order:stroke fill markers"
style="display:inline;mix-blend-mode:normal;fill:#3ba8bc;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:square;stroke-opacity:1;paint-order:stroke fill markers"
style="display:inline;mix-blend-mode:normal;fill:#3ba8bc;fill-opacity:0.151125;stroke:none;stroke-width:2.11667;stroke-linecap:square;stroke-opacity:1;paint-order:stroke fill markers;filter:url(#filter70)"
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.