`CARGO_BUILD_RUSTFLAGS` has a very low precedence and is overruled by
global `target.<tuple>.rustflags`.
Since these rustflags are very important, ensure that they have a higher
precedence. We could even go for `CARGO_ENCODED_RUSTFLAGS` which has the
highest precedence, but is slightly annoying to construct and probably
not worth it.
List current max-bpc values in outputs.
fix: remove 16 bpc and change default behaviour
feat(ipc): add max_bpc and format to output
fix: bpc on output config change
docs: add bpc to Outputs
feat: use atomic commits for connector properties
fix: drm `value_type` breaking change
fix: minor changes based on PR review
Rename bpc to max-bpc.
Add max-bpc output action.
refactor: add set_connector_properties
Fix niri-config parse test.
fix: bail when outside valid max bpc range
Some Bluetooth earbuds send alternating XF86AudioPlay/XF86AudioPause
keycodes on the same physical press. Without a binding for XF86AudioPause,
every other press is dropped, making playback toggle unreliable.
We want to include them in the tarballs alongside the wiki. I tried
toggling the include LFS in archives option, but it quickly used up most
of our free LFS traffic. So let's move these files in-repo, they are not
that big.
I optimized the files with:
oxipng -o max --strip safe docs/wiki/img/*.png
Need to rememeber to do that for any new .png files.
* tty: Re-evaluate ignored nodes on udev add events
When a DRM device is removed and rescanned (e.g. during a dGPU
suspend/resume cycle), the kernel may assign it a new device ID.
Re-evaluating the config paths specifically on UdevEvent::Added
ensures that symlinks like /dev/dri/by-path/... are resolved to
their new underlying IDs, preventing niri from accidentally
opening an ignored hotplugged device.
This check is restricted to device additions to avoid unnecessary
filesystem I/O on the hot path during bursts of other udev events.
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Output::from_resource() succeeds even after the global has been disabled
and removed from niri. Clients operating on these disabled outputs could
cause panics in several places because niri assumed the output existed.
* fix: no longer input locks when TTY is switched before full compositor
start
* reword
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* backend/winit: DMA-BUF setup for Nvidia support
Creates a dmabuf global in the same manner as the tty backend. This
fixes applications failing to launch on Nvidia when using the winit
backend.
This code is adapted from Smithay's Anvil compositor.
See smithay/anvil/src/winit.rs
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
clean up foreign toplevel destruction: don't `retain` to remove one item
document why we have duplicate destruction logic in foreign toplevel
messy to have two of the same comment but like how else do i make that
information readily available to both
Co-authored-by: HigherOrderLogic <73709188+HigherOrderLogic@users.noreply.github.com>
Height presets aren't frequently needed in my experience, but switching
preset width back is very useful on 21:9 and wider monitors where you
have many more presets.
The damage tracker stores framebuffer effect cache, so we want anything
that renders repeatedly to render through a reused damage tracker. This
way, the cache persists and is reused across renders.
This will be important for the non-xray background effects.
As far as I can tell, the only place where we can hit this currently is
do-screen-transition when no outputs are connected. A check could be
trivially added there, but I don't think it's worth enforcing it with an
error! -- it's just an optimization (avoiding running unnecessary code)
while being rare and difficult to find if it does get logged.
* document the reverse switching functionality introduced in #1670
Toggling in reverse through preset widths & heights was added in #1670.
However, it's really difficult to find in the docs. The sole exception is a comment in the default config.kdl, but that only documents one of the new settings (width).
I had to open up `bindings.rs` on github source code to even find the right setting for the other. This information should be available in the docs or config somewhere.
## Alternatives Considered
I considered documenting the preset-toggling functionality, including reverse toggling, in `bindings` wiki, but then the original (non-reversed) toggling would be documented in multiple places. More importantly, bindings seems to be a guide on how to set bindings, not what actions are available for use with bindings.
* Update docs/wiki/Configuration:-Layout.md
* Update docs/wiki/Configuration:-Layout.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
The virtual keyboard is reverted to the same state as it was in v25.08, i.e.
cannot trigger compositor binds. A bigger Smithay refactor is necessary to
properly support them without breaking other things.
Fixes wtype being completely broken.
* remove pre-commit hook when surface is destroyed
this re-uses the already existing remove_default_dmabuf_pre_commit_hook
during surface destruction instead of just removing the hook from the
stored list of hooks.
* do not add dmabuf pre-commit hook for destroyed surfaces
this prevents surfaces getting stored indefinitely in case
some logic tries to add the hook for an already
destroyed surface.
* align surface/toplevel destruction order for client destruction
resource destruction has undefined order in case
the client does not explicitly destroy the resourced
and wait for destruction to complete.
the same applies for clients exiting unexpectedly.
* rearrange some things
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* ipc: allow load-config to relocate the path of the config
* doc: add info about alternative configuration paths and relocating
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update docs/wiki/Integrating-niri.md
* Update niri-ipc/src/lib.rs
* Update src/ipc/server.rs
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Update Electron info
There were changes made that remove the env variable:
https://github.com/electron/electron/issues/48001
* Clarify Electron versions
* Link to the electron section of the wiki
* Edit wording and link to the electron section of the wiki
Also includes the necessary code to handle the virtual keyboard
compositor-side. Similar to the virtual pointer, we have an InputDevice
impl that allows reusing the logic from process_input_event().
Co-authored-by: wxt <3264117476@qq.com>
Before we cleaned up when binding a new manager, meaning that after a
screencopy client exited, the queue kept existing until a new one is
bound. We'll need precise tracking for the screencast IPC, so this
commit refactors to do just that: clean up the queue immediately when
all referring objects no longer exist.
This commit also fixes an issue where destroyed frames (e.g. from a
killed client) didn't clean the corresponding screencopy objects,
leading them to exist forever.
Add CastSessionId and CastStreamId newtypes. This lifts the atomic
counters from the D-Bus mutter_screen_cast module to a shared location,
preparing for adding screencopy cast tracking which will need the same
ID types.
The Cast struct fields were ordered such that `stream` was dropped before
`_listener`. In Rust, struct fields are dropped in declaration order.
Because `StreamListener` attempts to unregister itself from the stream on
drop, and `StreamRc` destroys the underlying PipeWire stream on drop, the
previous order caused `_listener` to access the stream after it had
already been freed.
This reorders the fields so `_listener` is declared before `stream`,
ensuring the listener unregisters itself while the stream is still valid.
* Apply suggestion from @YaLTeR
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Improve dinit service files and niri-session
Two main changes were made:
- After a discussion in davmac314/dinit#496, 2 dinit services are now
provided. The first one is 'niri', which runs niri itself, and the
second one is 'niri.target' which brings up all the dependences from
user configuration.
- Made the behaviour of 'niri-session' when running under dinit closer
to the behaviour when running under systemd. In particular, now the
script wait for service completion, because some login managers shut
the session down the moment the startup script completes.
* Update paths in docs
Our current rendering code constructs and returns complex
`-> impl Iterator<Item = SomeRenderElement>` types that are collected
into a vector at the top level Niri::render(). This causes some
problems:
- It's hard to write logic around returning iterators. Especially things
like conditions, since the returned iterator must have a single type,
you can't branch and return different iterators. This will be solved
by gen fn but alas it's not here yet.
- In many cases, the returned `-> impl Iterator` will borrow from &self
leading to complex lifetimes. In certain cases, it is also desirable
for it to borrow the &mut NiriRenderer, which causes a lot of issues
because it's exclusive (&mut).
- Sometimes those issues are too hard to deal with, leading to the
escape hatch of allocating and returning a temporary
Vec<SomeRenderElement>, like in
Scrolling/FloatingSpace::render_elements(). These allocations are
unfortunate because they are not really necessary.
- It's impossible to use some downstream combinators with this
`-> impl Iterator` approach, leading to functions like Smithay's
render_elements_from_surface_tree() returning a Vec. This is extra
unfortunate because it results in a temporary allocation per Wayland
toplevel/popup.
- It's hard to properly create profiling spans for the rendering
functions since the spans are dropped when the (lazy) iterator is
returned and not when all the code actually completes.
- The code compiles down to complex state machines in generated iterator
types with logic located in Iterator::next(), which makes it annoying
to follow in debuggers and profiling tools.
This refactor changes the code to push-based iteration: rendering
functions receive a push() closure that they call to push their render
elements. It solves all of the aforementioned problems:
- The logic becomes simpler. Just use conditionals and loops as normal.
- No borrowing and lifetimes since we're not returning anything.
- All temporary Vecs are removed because the problems they worked around
no longer exist.
- The new push_elements_from_surface_tree() helper is the same as
render_elements_from_surface_tree() but doesn't allocate a temporary
Vec since it's not necessary; the push() closure can be passed down.
- Profiling spans work normally since the function returns when it ran
all of the logic.
- The code compiles down to normal functions and calls as expected.
Generally, the iterator approach gives these advantages:
- You can wrap the returned items in the upstream logic. This is
possible in exactly the same way with the push closure.
- You can decide to cut the iterator short in the upstream logic. This
is not possible with push-based iteration, but we don't actually use
it anywhere.
I chose the push closure type to be &mut dyn FnMut(SomeRenderElement).
It's deliberately not a generic impl FnMut() to avoid duplicating the
rendering logic when it's called from several different places. But it's
still a normal closure that can capture the outside context.
While my original idea for this refactor was to simplify the logic while
getting rid of temporary Vecs, it also appears to have brought a
consistent 2-3x speedup to the whole render list construction. On an old
Eee PC laptop I even observed a 8x speedup.
The refactor also results in smaller binary size, presumably due to
removing many iterator combinators and state tracking.
Fixes menu in Telegram. Some weird behavior is still possible e.g. with
gtk4-widget-factory and dropdowns on entries, but things seem to be
slightly less broken this way.
The wording in the deleted comment still stands: Smithay doesn't handle
overlapping grabs. However, in this case things appear to more or less
work themselves out. IME seems to re-request its grab every time an
input field is focused, replacing the popup keyboard grab. And the popup
keyboard grab doesn't seem to mind being replaced this way.
When the column was added immediately to the left of the current column
and activated, the new idx would be equal to active_column_idx, which
would skip activate_column() with its variable resets.
* Use Grabbing cursors for interactive move
There was no real indication that something can be dragged and thus
it's generally harder to discover for someone not familiar with Mod+LMB
to start dragging window around.
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Just use the glib function.
Turns out url comes with a huge dep tree. Well, I guess back when I
wrote this, we didn't have glib in our deps, but we had for a long time.
Before this, focus ring/border width was incorrectly rounded only for
layout config, which does not take into account window rules overrides.
This means that setting width in window rules prevented correct rounding
altogether.
Tests didn't check this because window rules weren't tested. This commit
also adds basic window rules random generation, making the tests catch
this problem too.
Makes it consistent with the MRU and improves the behavior:
- considers MRU's debounce
- fixes problematic cases where focus changes to non-Layout and back
Historic commit description log:
The MRU actions `focus-window-mru-previous` and `focus-window-mru-next`
are used to navigate windows in most-recently-used or
least-recently-used order.
Whenever a window is focused, it records a timestamp that be used to
sort windows in MRU order. This timestamp is not updated immediately,
but only after a small delay (lock-in period) to ensure that the
focus wasn't transfered to another window in the meantime. This
strategy avoids upsetting the MRU order with focus events generated by
intermediate windows when moving between two non contiguous windows.
The lock-in delay can be configured using the `focus-lockin-ms`
configuration argument.
Calling either of the `focus-window-mru` actions starts an MRU window
traversal sequence if one isn't already in progress. When a sequence is
in progress, focus timestamps are no longer updated.
A traversal sequence ends when:
- either the `Mod` key is released, the focus then stays on the chosen
window and its timestamp is immediately refreshed,
- or if the `Escape` key is pressed, the focus returns to the window
that initially had the focus when the sequence was started.
Rename WindowMRU fields
Improve window close handling during MRU traversal
When the focused window is closed during an MRU traversal, it moves
to the previous window in MRU order instead of the default behavior.
Removed dbg! calls
Merge remote-tracking branch 'upstream/main' into window-mru
Hardcode Alt-Tab/Alt-shift-Tab for MRU window nav
- Add a `PRESET_BINDINGS` containing MRU navigation actions.
`PRESET_BINDINGS` are overridden by user configuration so these remain
available if the user needs them for another purpose
- Releasing the `Alt` key ends any in-progress MRU window traversal
Remove `focus-window-mru` actions from config
These actions are configured in presets but no longer available
for the bindings section of the configuration
Cancel MRU traversal with Alt-Esc
Had been forgotten in prior commit and was using `Mod` instead of `Alt`
Rephrase some comments
Fix Alt-Esc not cancelling window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Lock-in focus immediately on user interaction
As per suggestion by @bbb651, focus is locked-in immediately if a window
is interacted with, ie. receives key events or pointer clicks.
This change is also an opportunity to make the lockin timer less aggresive.
Merge remote-tracking branch 'upstream/main' into window-mru
Simplify WindowMRU::new
Now that there is a more general Niri::lockin_focus method, leverage
it in WindowMRU.
Replace Duration with Instant in WindowMRU timestamp
Merge remote-tracking branch 'upstream/main' into window-mru
Address PR comments - partial
- Swapped meaning of next and previous for MRU traversal
- Fixed comment that still referred to `Mod` as leader key for MRU traversal
instead of `Alt`
- Fixed doc comments that were missing a period
- Stop using BinaryHeap in `WindowMRU::new()`
- Replaced `WindowMRU::mru_with()` method with a simpler `advance()`
- Simplified `Alt` key release handling code in `State::on_keyboard()`
Simplify early-mru-commit logic
No longer perform the mru-commit/lockin_focus in the next event loop callback.
Instead it is handled directly when it is determined that an event (pointer
or kbd) is forwarded to the active window.
Handle PR comments
- `focus_lockin` variables and configuration item renamed to `mru_commit`.
- added the Esc key to `suppressed_keys` if it was used to cancel an MRU
traversal.
- removed `WindowMRU::mru_next` and `WindowMRU::mru_previous` methods
as they didn't really provide more than the generic `WindowMRU::advance`
method.
- removed obsolete `Niri::event_forwarded_to_focused_client` boolean
- added calls to `mru_commit()` (formerly `focus_lockin`) in:
- `State::on_pointer_axis()`
- `State::on_tablet_tool_axis()`
- `State::on_tablet_tool_tip()`
- `State::on_tablet_tool_proximity()`
- `State::on_tablet_tool_button()`
- `State::on_gesture_swipe_begin()`
- `State::on_gesture_pinch_begin()`
- `State::on_gesture_hold_begin()`
- `State::on_touch_down()`
Merge remote-tracking branch 'upstream/main' into window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Add MRU window navigation actions
The MRU actions `focus-window-mru-previous` and `focus-window-mru-next`
are used to navigate windows in most-recently-used or
least-recently-used order.
Whenever a window is focused, it records a timestamp that be used to
sort windows in MRU order. This timestamp is not updated immediately,
but only after a small delay (lock-in period) to ensure that the
focus wasn't transfered to another window in the meantime. This
strategy avoids upsetting the MRU order with focus events generated by
intermediate windows when moving between two non contiguous windows.
The lock-in delay can be configured using the `focus-lockin-ms`
configuration argument.
Calling either of the `focus-window-mru` actions starts an MRU window
traversal sequence if one isn't already in progress. When a sequence is
in progress, focus timestamps are no longer updated.
A traversal sequence ends when:
- either the `Mod` key is released, the focus then stays on the chosen
window and its timestamp is immediately refreshed,
- or if the `Escape` key is pressed, the focus returns to the window
that initially had the focus when the sequence was started.
Rename WindowMRU fields
Improve window close handling during MRU traversal
When the focused window is closed during an MRU traversal, it moves
to the previous window in MRU order instead of the default behavior.
Removed dbg! calls
Merge remote-tracking branch 'upstream/main' into window-mru
Hardcode Alt-Tab/Alt-shift-Tab for MRU window nav
- Add a `PRESET_BINDINGS` containing MRU navigation actions.
`PRESET_BINDINGS` are overridden by user configuration so these remain
available if the user needs them for another purpose
- Releasing the `Alt` key ends any in-progress MRU window traversal
Remove `focus-window-mru` actions from config
These actions are configured in presets but no longer available
for the bindings section of the configuration
Cancel MRU traversal with Alt-Esc
Had been forgotten in prior commit and was using `Mod` instead of `Alt`
Rephrase some comments
Fix Alt-Esc not cancelling window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Lock-in focus immediately on user interaction
As per suggestion by @bbb651, focus is locked-in immediately if a window
is interacted with, ie. receives key events or pointer clicks.
This change is also an opportunity to make the lockin timer less aggresive.
Merge remote-tracking branch 'upstream/main' into window-mru
Simplify WindowMRU::new
Now that there is a more general Niri::lockin_focus method, leverage
it in WindowMRU.
Replace Duration with Instant in WindowMRU timestamp
Merge remote-tracking branch 'upstream/main' into window-mru
Address PR comments - partial
- Swapped meaning of next and previous for MRU traversal
- Fixed comment that still referred to `Mod` as leader key for MRU traversal
instead of `Alt`
- Fixed doc comments that were missing a period
- Stop using BinaryHeap in `WindowMRU::new()`
- Replaced `WindowMRU::mru_with()` method with a simpler `advance()`
- Simplified `Alt` key release handling code in `State::on_keyboard()`
Simplify early-mru-commit logic
No longer perform the mru-commit/lockin_focus in the next event loop callback.
Instead it is handled directly when it is determined that an event (pointer
or kbd) is forwarded to the active window.
Handle PR comments
- `focus_lockin` variables and configuration item renamed to `mru_commit`.
- added the Esc key to `suppressed_keys` if it was used to cancel an MRU
traversal.
- removed `WindowMRU::mru_next` and `WindowMRU::mru_previous` methods
as they didn't really provide more than the generic `WindowMRU::advance`
method.
- removed obsolete `Niri::event_forwarded_to_focused_client` boolean
- added calls to `mru_commit()` (formerly `focus_lockin`) in:
- `State::on_pointer_axis()`
- `State::on_tablet_tool_axis()`
- `State::on_tablet_tool_tip()`
- `State::on_tablet_tool_proximity()`
- `State::on_tablet_tool_button()`
- `State::on_gesture_swipe_begin()`
- `State::on_gesture_pinch_begin()`
- `State::on_gesture_hold_begin()`
- `State::on_touch_down()`
Merge remote-tracking branch 'upstream/main' into window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Include never focused windows in MRU list
Remove mru_commit_ms from configurable options
For now the value is hard-coded to 750ms
Merge remote-tracking branch 'upstream/main' into HEAD
Add hotkey_overlay_tile for PRESET_BINDINGS
Merge remote-tracking branch 'origin/window-mru' into HEAD
Merge remote-tracking branch 'upstream/main' into window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Merge remote-tracking branch 'upstream/main' into window-mru
Firt shot an MruUi
The UI doesn't actually do anything yet. For now it just puts up thumbnails
for existing windows in MRU order.
Added MRU texture cache + simplifications
Working version
Removed previous Mru code
Tidy up Action names
Added Home/End bindings
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Add scope and filtering to Mru window navigation
Feed todo list
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Clippy: Boxed the focus ring
The UI object doesn't get moved around much so it isn't clear if
this actually important. Boxing keeps clippy happy because of the
size difference between an Open vs a Closed MRU UI.
Bump rust version to 1.83
Avoids getting yelled at by clippy for using features that weren't yet available in 1.80.1
Applied clippy lints
Fix MruFilter::None conversion
MruFilter variant was getting ignored
cargo fmt
Update rust tool chain in CI
Had only been updated in Cargo.toml, this causes build
failures on Github
Support changing Mru modes with the Mru UI open
Fix texture cache optimization
When the Mru parameters were changed while the MruUI was open, the
texture cache is rebuilt but attempts to reuse existing Textures
that are still usable in the updated Mru list. The index of the
retained texture could be miscalculated and resulted in the wrong
texture being used for a given window Id.
Make MruAdvance available as a Bind action
For consistency, MruAdvance bindings are carried over when the MruUI is open.
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Preset binds added as a source for MRU UI binds
Surprisingly the status prior to the patch should have prevented the UI
bindings to advance through the Mru list from working properly.
Use iterators to find bindings
This allows the caller, eg. `on_keyboard` to choose the full list
of bindings that should be searched through by composing iterators.
Prior to the change the PRESET_BINDINGS were always included regardless
of caller. With this approach, `on_keyboard` can add in the MRU_UI-
specific bindings if it detects that the MRU UI is open.
Make scope and filter optional in mru-advance
This avoids unexpected behavior when navigating MRU with a filter, e.g. App-Id,
with arrow keys for instance, which would result in changing navigation
to ignore the app-id filter. With the change, mru-advance has an optional
scope and filter that allows a key bind to leave the current navigation mode
unchanged.
Add title under window thumbnails
- Reworked the texture cache to use TextureBuffer-s instead of BakedBuffer.
- Add convenience methods to access TextureCache content.
Some tidying up.
Fade title out if it doesn't fit in available size
Add bindings to change the MruScope
Fix panic rendering title when cairo surface was busy
Also avoid interpreting markup in window titles.
Bring branch in line with window-mru-ui-squashed
Add navigation animation in MRU UI
Only handles motion between thumbnails
Add thumbnail close animation
For now, the animation only tracks when the corresponding window is closed.
Add animations on filter and scope changes
Add open/close animation to MRU Ui
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Fix animations on scope/filter changes
Previous implementation would evict wrong textures from the cache.
And get thumbnail animations wrong.
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Fix panic on change of scope/filter when Mru list is empty.
Add doc comment to method that could trigger a panic
Simplify thumbnail ordering logic
Improve scope/filter change animations
- direction is no longer a factor when an Mru UI is opened (previously
the first thumbnail would be the currently focused window when
moving in the "forward" direction, and when moving in the "backward"
direction the focused window would have its thumbnail last in the
list. This made animations kind of confusing when switching scopes
or filtering.
The updated version always places the thumbnails in most recent
focus order. So when the MRU UI is brought up in the "backward"
direction, the last thumbnail in the MRU list starts selected.
- closing animations no longer use the view referential, but use
the output referential instead. This makes disappearing thumbnails
appear stationary on screen even if the view is moving. This tends to
look less confusing than the previous approach.
Applied clippy lints
Preserve scope during fwd/backward navigation
Change preset keybinding declarations from const to static
Add thumbnail selection animation
This is still very much a work in progress:
- the focus ring is not shown until the animation completes
- if the tile is resized during the animation, the net effect looks
pretty bad because proportions skip directly to those requested
instead of transitioning smoothly.
Both points should be addressed by using regular tile rendering to an
OffscreenBuffer but I haven't much success there.
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Fix niri-config parse test
Use OffscreenBuffer to render ThumbnailSelection animation
todo: fix thumbnail destination if the target workspace is being swapped.
Handle workspace switch during thumbnail select animation
Close Overview when MRU UI is opened
Add configuration option to disable MRU UI
Make mod-key for MRU UI configurable
Avoid collecting MRU UI bindings on each input
Bindings are cached when first accessed, the cache is invalidated
whenever the configuration changes.
Close MRU UI when Overview is opened
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Fix MRU UI opened bindings always active
Remove mru-advance from actions available for config keybind
Because the MRU UI assumes that all key-bindings use the mod-key
defined in for `recent-windows`, behavior can be disconcerting
if arbitrary keybindings are allowed in the configuration (e.g.
UI opens and immediately closes because the mod-key is not being
held).
Include focus timestamp in Window IPC messages
Timestamps are serialized as time::SystemTime, which in JSON form is represented
as *two* fields, secs and nanos.
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Only do Thumbnail Select Anim if MRU UI stayed open long enough
Threshold is hard coded in window_mru_ui.rs (250ms).
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Add a few WindowMru tests
Forward Mod-key release when closing MRU UI
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Remove extraneous thumbnail motion on Mru filter change
Fix missing alpha in Mru thumbnail open animation
Add Mod+h and Mod+l bindings for MRU navigation
Change CloseWindow binding in MRU to Mod+Shift+q
Keep MRU UI on display it was initially opened on
Bump up the MRU IU selection anim threshold
Allow MRU thumbnail selection with mouse pointer
Allow MRU thumbnail selection using touch
Needs testing, Idk if this works for lack of a touchscreen.
Fix missing fade-out animation for thumbnails on MRU UI close
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Make thumbnail selection animation optional
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Fix niri-config parse test case
Add shortcut to cycle through MRU scopes
- added MruCycleScope action to trigger cycling
- added an indication panel to show the current scope
- recall previous scope when opening the MRU UI
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Improve MRU thumbnail scaling
Prior to the commit, thumbnails were just 2x downscaling of their corresponding
window. Now they are also scaled based on the relative height of the window
on its output display. This avoids having a thumbnail taking up the entire
screen on the display where the MRU UI is displayed.
Merge remote-tracking branch 'upstream/main' into window-mru-ui
Use resolved window rules for thumbnails
Previously parameters such as the corner-radius didn't follow the general
config and used an MRU UI specific default.
Align thumbnail size and position to physical pixels
clarify param names in generate_tile_texture
Revert MSRV 1.83
Close MRU UI on click/touch outside of a thumbnail
MRU - display window title under all thumbnails
MRU - revert to pre-defined thumbnail corner radius
MRU - Removed thumb title font size adjustment
This didn't look as if it was necessary. (unscientific assesment)
MRU - reverted to Mod+Q to quit selected thumbnail
Merge remote-tracking branch 'upstream/main' into window-mru-ui
MRU - Update focus ring when moving mouse over a thumbnail
restore code that went missing
switch focus timestamp to monotonic time
We don't want the monotonicity of SystemClock here. Instant itself isn't
serializable, but our monotonic clock timestamps are, and they are
consistent across processes too.
axe thumbnail close animation
I'm still not quite convinced about it. Maybe we'll reintroduce it later
with better architecture; for now though, it causes quite a bit of
complexity.
minor cleanups
remove unnecessary option
replace open animation with delay
Avoids flashing the whole screen for quick Alt-Tabs. Duration taken from
GNOME Shell.
make mod key different in nested
replace SelectedThumbnail with MappedId
don't hide focus ring during alt tab
wip refactor everything and render live windows
rename some constants
replace focus ring with background + border
extract thumbnail constructors
reimplement title fade with a shader
reimplement ui fade out on closing
fix preview scaling
add min scale for very small windows
add keyboard focus for mru
fixes activating alt on target window
revert/simplify pointer code changes
fixes mouse not clamped to output when in alt-tab; should fix touch
going through
move touch handling to below screenshot ui
remove unneeded touch overview grab code
rename to mru.rs
move mru tests into separate file
also close mru when clicking on other outputs
roll back no longer necessary event filtering
rework mru keyboard binds
convert some regular binds to MRU binds
hide window title when blocked out
verify that mru bind uses a keyboard key
improve selection visibility & indicate urgency
freeze alt-tab view on pointer motion
add WindowFocusTimestampChanged event, separate struct for Timestamp
minor cleanups
scope panel fixes
simplify scope cycling
honor geometry corner radius
don't trigger focus-follows-mouse in the MRU
remove unnecessary argument
cache backdrop buffers
remove unnecessary mru close
allow to screenshot the mru
support bob offset
improve mru redraws
pass config instead of options
add open-delay-ms option
add highlight options
rename window-mru-ui-open-close to recent-windows-close
add preview options
fix scope change and remove window delta anim
improve unselected scope panel text contrast
move panel back up so it doesn't overlap the screenshot one
rename preview to previews in config
render highlight background with focusring
fix highlight pos rounding
add highlight corner-radius setting
remove allocation from inner render
use offscreen for mru closing fade
make scope only affect MRU open
otherwise you can't change scope at runtime easily
replace todo with fixme
include title height in thumbnail under
remove cloning from set scope/filter
remove animate close todo
update field name in mapped
remove commented out closing thumbnails
I decided not to do this for now.
rename filter from None to All and skip in knuffel
None is confusing with Option
write docs
make inactive urgent more prominent
remove reopen from scartch todo
explicitly mention app id in filter
make scroll binds work in the mru
add fixmes
don't select next window when nothing is focused
add missing anim config merge
fixes
replace click selection with pointer motion + confirm
simplify close mru ui call
rename mrucloserequest variants
mru confirm fixes
support tablet input
mru commit cleanups
remove most mru commit calls
they didn't actualy do anything as implemented. If we want to bring them
back we need to refactor a bit to join them with activate_window() call.
make regular mouse binds also work in mru
fixes
fixes
move types up
fix tracy span
I feel this is more intuitive compared to them doing nothing. True
maximize is kinda similar to full-width in spirit, so make the actions
behave the same.
Probably the tile window loc wasn't zoomed when it should. But also the
interactive move grab case doesn't consider the window loc at all, which
seems to work ok, so let's do that until a problem is found with it.
Several reasons for this:
- Remove the sizing code from interactive move duplicated with
toggle_window_floating.
- Make the tiled size restore too instead of requesting 0x0 (which
doesn't work for windows in the Tiled state).
We can't check recursive includes across "dir/" followed by "../"
because dir may be a symlink, so "dir/../" may resolve to a different
folder. But this is already good within the same folder.
We can do it now that it's non-Copy. This also fixes a new stack
overflow when running the random test in debug mode (which somehow
occurs even though it's skipped in debug mode) that appeared after
adding LayoutPart for some unbeknownst to me reason.
I didn't properly update it for the Smithay refactor. It was reading
initial_configure_sent too early. This worked before when niri had to reset it
manually, but it no longer works now that it is automatically reset already
before entering this function.
This can happen if a surface unmaps by committing a null buffer and then
immediately does the initial commit without a sync roundtrip, while there are
pending configures from the compositor in-flight. In this case, the surface
cannot tell that the pending configures were meant for "before unmapping" and
considers them to be the new initial configure.
From our point of view, we don't get to do a proper initial configuration
sequence in this case, and receive a mapping commit without our initial
configure state. We cannot really do much about it, but it is not an error when
this specific situation happens.
This turned out to require quite a few changes.
We keep track of the tile resize animation progress separately now, in order to
provide a resizing black fullscreen backdrop for non-resizable windows.
The window is always rendered in the middle of the tile, which once again aids
with the resizing black fullscreen backdrop.
The backdrop itself will fade in from transparency so that it's less jarring.
The resize animation now keeps track of the fullscreen progress to deal with
the case where an unfullscreen resize is interrupted by another non-fullscreen
resize. In this case, the fullscreen progress continues animating to avoid
sudden disappearance of the fullscreen backdrop.
Some things like border visibility switch to this fullscreen progress once again
to avoid jarring appearance/disappearance.
The border radius animates in accordance with the fullscreen progress to match
the visuals.
We already did that for Tiles, but for Columns we only tracked what was
effectively pending fullscreen. We used it in several places where the current
fullscreen should've been used instead, like the tile origin or the view offset.
This commit splits the two and makes every place use the right one.
Fixes things like tiles briefly appearing at y=0 between issuing the fullscreen
command and the tile committing in response to the fullscreen configure.
* Add corner selection in config
* Add hot corner docs
* Working per-monitor hot corners
Handle defaults
* run cargo fmt --all
* Fix hot corners in is_sticky_obscured_under
* Change default to fall back to gesture hot corners if output hot corners are unset
* Add hot corner output config docs
* Support fractional scaling
* Trigger hot corners over widgets
* Improve float handling
Fixed YaLTeR/niri/pull/2108
* Refactor
* Bug Fixes
* Amend docs
Fix styling
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Integrate code review
Move is_inside_hot_corner
* fixes
---------
Co-authored-by: Aadniz <8147434+Aadniz@users.noreply.github.com>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* Included example for media keys: play/pause, stop, previous and next in the default config
* Update resources/default-config.kdl
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
* wiki/Xwayland: Added a mention of Native Wayland for gaming compatibility
* Update docs/wiki/Xwayland.md
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Screen readers expect closing a modal dialog to reannounce the previous focus.
This makes the exit confirm dialog more modal in this sense: it will unfocus
the layout and then focus it back when closed, giving the desired behavior.
* 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.
2024-10-29 21:52:03 -07:00
5600 changed files with 140960 additions and 20914 deletions
<!-- 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. -->
<details><summary>Config</summary>
```kdl
insertconfighere
```
</details>
<!--
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://niri-wm.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://niri-wm.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.
### How to get your pull request reviewed more quickly
- Make it small and self-contained. Avoid mixing several unrelated changes in one PR.
- Split the PR into small and self-contained commits. This makes it much easier to review.
- Discuss new features, options, or behavior changes beforehand; make sure there's consensus about the design.
- When creating the pull request, clearly write what it does, what problem it solves, how to test it.
- Follow the rest of the advice from this document.
## AI contributions
If you use LLMs for your contribution (issue, comment, pull request), then it is *your job* to check and clean up its output, just like with any other tool.
*You* have to spend the time doing this.
Particularly:
- If I can tell that a pull request is mostly LLM-generated, then very likely this pull request will take *significantly more time and effort* than usual to review and finish. This is based on my prior review experience. Therefore, I'm not interested in such pull requests—there's always plenty of human-written ones which take priority.
- When using an LLM to prepare an issue, the text usually has a lot of unnecessary wording and irrelevant details. Anyone looking at such an issue will quickly lose interest in reading through it (myself certainly). Clean up the text and keep only those details that actually matter.
- When using an LLM to comment on an issue, *you* have to verify that the comment makes sense, contributes something useful, and doesn't have unnecessary repetition.
<img width="1280" height="720" alt="niri with a few windows open" src="https://github.com/user-attachments/assets/dea5909e-1859-4aaa-9d88-d37f9663e00b" />
## About
@@ -28,26 +28,76 @@ When a monitor disconnects, its workspaces will move to another monitor, but upo
## Features
-Scrollable tiling
- Dynamic workspaces like in GNOME
-Built from the ground up for scrollable tiling
-[Dynamic workspaces](https://niri-wm.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://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules#block-out-from) sensitive windows from screencasts
- [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
- You can [block out](https://niri-wm.github.io/niri/Configuration%3A-Window-Rules.html#block-out-from) sensitive windows from screencasts
- [Dynamic cast target](https://niri-wm.github.io/niri/Screencasting.html#dynamic-screencast-target) that can change what it shows on the go
- [Touchpad](https://github.com/niri-wm/niri/assets/1794388/946a910e-9bec-4cd1-a923-4a9421707515) and [mouse](https://github.com/niri-wm/niri/assets/1794388/8464e65d-4bf2-44fa-8c8e-5883355bd000) gestures
- Group windows into [tabs](https://niri-wm.github.io/niri/Tabs.html)
- [Gradient borders](https://github.com/YaLTeR/niri/wiki/Configuration:-Layout#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)
- [Gradient borders](https://niri-wm.github.io/niri/Configuration%3A-Layout.html#gradients) with Oklab and Oklch support
- [Background blur](https://niri-wm.github.io/niri/Window-Effects.html) for windows and layer-shell surfaces
- [Animations](https://github.com/niri-wm/niri/assets/1794388/ce178da2-af9e-4c51-876f-8709c241d95e) with support for [custom shaders](https://github.com/niri-wm/niri/assets/1794388/27a238d6-0a22-4692-b794-30dc7a626fad)
- Live-reloading config
- Works with [screen readers](https://niri-wm.github.io/niri/Accessibility.html)
Niri has basic support for screen readers (specifically, [Orca](https://orca.gnome.org)) when running as a full desktop session, i.e. you need to start niri through a display manager or through `niri-session`.
To avoid conflicts with an already running compositor, niri won't expose accessibility interfaces when started as a nested window, or as a plain `/usr/bin/niri` on a TTY.
We implement the `org.freedesktop.a11y.KeyboardMonitor` D-Bus interface for Orca to listen and grab keyboard keys, and we expose the main niri UI elements via [AccessKit](https://accesskit.dev).
Specifically, niri will announce:
- workspace switching, for example it'll say "Workspace 2" when you switch to the second workspace;
- the exit confirmation dialog (appears on <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd> by default);
- <sup>Since: 25.11</sup> niri has an <kbd>Alt</kbd><kbd>Tab</kbd> window switcher where it will announce the selected window title;
- entering the screenshot UI and the overview (niri will say when these are focused, nothing else for now);
- whenever a config parse error occurs;
- the important hotkeys list (for now, as one big announcement without tab navigation; appears on <kbd>Super</kbd><kbd>Shift</kbd><kbd>/</kbd> by default).
Make sure [Xwayland](./Xwayland.md) works, then run `orca`.
The default config binds <kbd>Super</kbd><kbd>Alt</kbd><kbd>S</kbd> to toggle Orca, which is the standard key binding.
Note that there are some limitations:
- We don't have a bind to move focus to layer-shell panels. This is not hard to add, but it would be good to have some consensus or prior art with LXQt/Xfce on how exactly this should work.
- You need to have a screen connected and enabled. Without a screen, niri won't give focus any window. This makes sense for sighted users, and I'm not entirely sure what makes the most sense for accessibility purposes (maybe, it'd be better solved with virtual monitors).
- You need working EGL (hardware acceleration).
- We don't have screen curtain functionality yet.
If you're shipping niri and would like to make it work better for screen readers out of the box, consider the following changes to the default niri config:
- Change the default terminal from Alacritty to one that supports screen readers. For example, [GNOME Console](https://gitlab.gnome.org/GNOME/console) or [GNOME Terminal](https://gitlab.gnome.org/GNOME/gnome-terminal) should work well.
- Change the default application launcher and screen locker to ones that support screen readers. For example, [xfce4-appfinder](https://docs.xfce.org/xfce/xfce4-appfinder/start) is an accessible launcher. Suggestions welcome! Likely, something GTK-based will work fine.
- Add some [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup) command that plays a sound which will indicate to users that niri has finished loading.
- Add `spawn-at-startup "orca"` to run Orca automatically at niri startup.
## Desktop zoom
There's no built-in zoom yet, but you can use third-party utilities like [wooz](https://github.com/negrel/wooz).
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.
### JetBrains IDEs
JetBrains IDEs can run directly on Wayland, but it's not the default.
For JetBrainsRuntime > 17, you can set the flag `-Dawt.toolkit.name=WLToolkit` inside of `help -> edit custom vm options -> add`.
If the settings window fails to load under Wayland, and the UI becomes unresponsive afterwards, also set the flag `-Dsun.awt.wl.WindowDecorationStyle=builtin` in the custom vm options. This gives the settings window a titlebar, but it at least makes the IDE functional.
### 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.
### 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`.
### GTK 4 dead keys / Compose
GTK 4.20 [stopped](https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8556) handling dead keys and Compose on its own on Wayland.
To make them work, either run an IME like IBus or Fcitx5, or set the `GTK_IM_MODULE=simple` environment variable.
```kdl
environment{
GTK_IM_MODULE"simple"
}
```
Note that the niri environment config does not propagate to apps and shells started by systemd, for example to DankMaterialShell and its application launcher.
You can set the variable in your login shell config (i.e. `~/.bash_profile`) instead, though keep in mind that then it will be set for all compositors, not just niri.
### 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:
If you have rounded corners on your Waybar and they show up with black pixels in the corners, then set your Waybar opacity to 0.99, which should fix it.
GTK 3 seems to have a bug where it reports a surface as fully opaque even if it has rounded corners.
This leads to niri filling the transparent pixels inside the corners with black.
Setting the surface opacity to something below 1 fixes the problem because then GTK no longer reports the surface as opaque.
spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
}
exit-confirmation-open-close {
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
}
screenshot-ui-open {
duration-ms 200
curve "ease-out-quad"
}
overview-open-close {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
recent-windows-close {
spring damping-ratio=1.0 stiffness=800 epsilon=0.001
}
}
```
@@ -76,14 +88,24 @@ animations {
}
```
Currently, niri only supports four curves:
Currently, niri only supports five curves.
You can get a feel for them on pages like [easings.net](https://easings.net/).
- `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/).
- `cubic-bezier` <sup>Since: 25.08</sup>
A custom [cubic Bézier curve](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions). You need to set 4 numbers defining the control points of the curve, for example:
```kdl
animations {
window-open {
// Same as CSS cubic-bezier(0.05, 0.7, 0.1, 1)
curve "cubic-bezier" 0.05 0.7 0.1 1
}
}
```
You can tweak the cubic-bezier parameters on pages like [easings.co](https://easings.co?curve=0.05,0.7,0.1,1).
#### Spring
@@ -159,7 +181,7 @@ animations {
##### `custom-shader`
<sup>Since: 0.1.6, experimental</sup>
<sup>Since: 0.1.6</sup>
You can write a custom shader for drawing the window during an open animation.
@@ -219,7 +241,7 @@ animations {
##### `custom-shader`
<sup>Since: 0.1.6, experimental</sup>
<sup>Since: 0.1.6</sup>
You can write a custom shader for drawing the window during a close animation.
@@ -315,7 +337,7 @@ animations {
##### `custom-shader`
<sup>Since: 0.1.6, experimental</sup>
<sup>Since: 0.1.6</sup>
You can write a custom shader for drawing the window during a resize animation.
@@ -359,6 +381,22 @@ animations {
}
```
#### `exit-confirmation-open-close`
<sup>Since: 25.08</sup>
The open/close animation of the exit confirmation dialog.
This one uses an underdamped spring by default (`damping-ratio=0.6`) which causes a slight oscillation in the end.
```kdl
animations {
exit-confirmation-open-close {
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
}
}
```
#### `screenshot-ui-open`
<sup>Since: 0.1.8</sup>
@@ -374,6 +412,34 @@ animations {
}
```
#### `overview-open-close`
<sup>Since: 25.05</sup>
The open/close zoom animation of the [Overview](./Overview.md).
```kdl
animations {
overview-open-close {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
}
```
#### `recent-windows-close`
<sup>Since: 25.11</sup>
The close fade-out animation of the recent windows switcher.
```kdl
animations {
recent-windows-close {
spring damping-ratio=1.0 stiffness=800 epsilon=0.001
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
force-disable-connectors-on-resume
render-drm-device"/dev/dri/renderD129"
ignore-drm-device"/dev/dri/renderD128"
ignore-drm-device"/dev/dri/renderD130"
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
}
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
}
```
### `force-disable-connectors-on-resume`
<sup>Since: 26.04</sup>
Force-disables all outputs upon resuming niri (TTY switch or waking up from suspend).
This causes a modeset/screen blank on all outputs.
If niri rendering is corrupted, or monitors don't light up after a TTY switch, you can try this flag.
```kdl
debug{
force-disable-connectors-on-resume
}
```
### `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"
}
```
### `ignore-drm-device`
<sup>Since: 25.11</sup>
List DRM devices that niri will ignore.
Useful for GPU passthrough when you don't want niri to open a certain device.
```kdl
debug{
ignore-drm-device"/dev/dri/renderD128"
ignore-drm-device"/dev/dri/renderD130"
}
```
### `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: 25.08</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: 25.08</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
}
```
### Key Bindings
These are not debug options, but rather key bindings.
#### `toggle-debug-tint`
Tints all surfaces green, unless they are being directly scanned out.
Useful to check if direct scanout is working.
```kdl
binds{
Mod+Shift+Ctrl+T{ toggle-debug-tint;}
}
```
#### `debug-toggle-opaque-regions`
<sup>Since: 0.1.6</sup>
Tints regions marked as opaque with blue and the rest of the render elements with red.
Useful to check how Wayland surfaces and internal render elements mark their parts as opaque, which is a rendering performance optimization.
You can include other files at the top level of the config.
```kdl,must-fail
// Some settings...
include "colors.kdl"
// Some more settings...
```
Included files have the same structure as the main config file.
Settings from included files will be merged with the settings from the main config file.
Included config files can in turn include more files.
All included files are watched for changes, and the config live-reloads when any of them change.
You can include by filename or path.
* Relative to the current file: `other.kdl` or `./other.kdl`
* By absolute path: `/path/to/file.kdl`
* <sup>Since: 26.04</sup> Home dir paths: `~/file.kdl` expands to `/home/user/file.kdl`
Includes work only at the top level of the config:
```kdl,must-fail
// All good: include at the top level.
include "something.kdl"
layout {
// NOT allowed: include inside some other section.
include "other.kdl"
}
```
### Positionality
Includes are *positional*.
They will override options set *prior* to them.
Window rules from included files will be inserted at the position of the `include` line.
For example:
```kdl
// colors.kdl
layout {
border {
active-color "green"
}
}
overview {
backdrop-color "green"
}
```
```kdl,must-fail
// config.kdl
layout {
border {
active-color "red"
}
}
// This overrides the border color and the backdrop color to green.
include "colors.kdl"
// This sets the overview backdrop color to red again.
overview {
backdrop-color "red"
}
```
The end result:
- the border color is green (from `colors.kdl`),
- the overview backdrop color is red (it was set *after* `colors.kdl`).
Another example:
```kdl
// rules.kdl
window-rule {
match app-id="Alacritty"
open-maximized false
}
```
```kdl,must-fail
// config.kdl
window-rule {
open-maximized true
}
// Window rules get inserted at this position.
include "rules.kdl"
window-rule {
match app-id="firefox$"
open-maximized true
}
```
This is equivalent to the following config file:
```kdl
window-rule {
open-maximized true
}
// Included from rules.kdl.
window-rule {
match app-id="Alacritty"
open-maximized false
}
window-rule {
match app-id="firefox$"
open-maximized true
}
```
### Optional includes
<sup>Since: 26.04</sup>
By default, including a nonexistent file will cause an error.
You can allow nonexistent includes by setting `optional=true`:
```kdl,must-fail
// Won't fail if this file doesn't exist.
include optional=true "optional-config.kdl"
// Regular include, will fail if the file doesn't exist.
include "required-config.kdl"
```
When an optional include file is missing, niri will emit a warning in the logs on every config reload.
This reminds you that the file is missing while still loading the config successfully.
The optional file is still watched for changes, so if you create it later, the config will automatically reload and apply the new settings.
Note that `optional` only affects whether a missing file causes an error.
If the file exists but contains invalid syntax or other errors, those errors will still cause a parsing failure.
### Merging
Most config sections are merged between includes, meaning that you can set only a few properties, and only those properties will change.
```kdl
// colors.kdl
layout {
// Does not affect gaps, border width, etc.
// Only changes colors as written.
focus-ring {
active-color "blue"
}
border {
active-color "green"
}
}
```
```kdl,must-fail
// config.kdl
include "colors.kdl"
layout {
// Does not set border and focus-ring colors,
// so colors from colors.kdl are used.
gaps 8
border {
width 8
}
}
```
#### Multipart sections
Multipart sections like `window-rule`, `output`, or `workspace` are inserted as is without merging:
```kdl
// laptop.kdl
output "eDP-1" {
// ...
}
```
```kdl,must-fail
// config.kdl
output "DP-2" {
// ...
}
include "laptop.kdl"
// End result: both DP-2 and eDP-1 settings.
```
#### Binds
`binds` will override previously-defined conflicting keys:
```kdl
// binds.kdl
binds {
Mod+T { spawn "alacritty"; }
}
```
```kdl,must-fail
// config.kdl
include "binds.kdl"
binds {
// Overrides Mod+T from binds.kdl.
Mod+T { spawn "foot"; }
}
```
#### Flags
Most flags can be disabled with `false`:
```kdl
// csd.kdl
// Write "false" to explicitly disable.
prefer-no-csd false
```
```kdl,must-fail
// config.kdl
// Enable prefer-no-csd in the main config.
prefer-no-csd
// Including csd.kdl will disable it again.
include "csd.kdl"
```
#### Non-merging sections
Some sections where the contents represent a combined structure are not merged.
Examples are `struts`, `preset-column-widths`, individual subsections in `animations`, pointing device sections in `input`.
```kdl
// struts.kdl
layout {
struts {
left 64
right 64
}
}
```
```kdl,must-fail
// config.kdl
layout {
struts {
top 64
bottom 64
}
}
include "struts.kdl"
// Struts are not merged.
// End result is only left and right struts.
```
### Border special case
There's one special case that differs between the main config and included configs.
Writing `layout { border {} }` in an included config does nothing (since no properties are changed).
However, writing the same in the main config will *enable* the border, i.e. it's equivalent to `layout { border { on; } }`.
So, if you want to move your layout configuration from the main config to a separate file, remember to add `on` to the border section, for example:
```kdl
// separate.kdl
layout {
border {
// Add this line:
on
width 4
active-color "#ffc87f"
inactive-color "#505050"
}
}
```
The reason for this special case is that this is how it historically worked: back when I added borders, we didn't have any `on` flags, so I made writing the `border {}` section enable the border, with an explicit `off` to disable it.
It wouldn't be too problematic to change it, however the default config always had a pre-filled `layout { border { off; } }` section with a note saying that commenting out the `off` is enough to enable the border.
Many people likely have this part of the default config embedded in their configs now, so changing how it works would just cause a lot of confusion.
> 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.
@@ -125,6 +204,22 @@ input {
}
```
#### 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.
@@ -142,7 +237,9 @@ A few settings are common between `touchpad`, `mouse`, `trackpoint`, and `trackb
- `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`: the button code used for the `on-button-down` scroll method. You can find it in `libinput debug-events`.
- `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: 25.08</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:
@@ -150,13 +247,23 @@ 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`, `mouse` and `tablet`:
Settings specific to `touchpad` and `mouse`:
- `left-handed`: if set, changes the device to left-handed mode.
- `scroll-factor`: <sup>Since: 0.1.10</sup> scales the scrolling speed by this value.
<sup>Since: 25.08</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` and `touch`:
- `calibration-matrix`: 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.
- <sup>Since: 25.02</sup> for `tablet`
- <sup>Since: 25.11</sup> for `touch`
Tablets and touchscreens are absolute pointing devices that can be mapped to a specific output like so:
@@ -176,6 +283,16 @@ 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.
Settings specific to `tablet`:
- `map-to-focused-output`: <sup>Since: 26.04</sup> will map the tablet to the focused output, takes precedence over `map-to-output`.
- `map-to-focused-window`: <sup>Since: next release</sup> will map the tablet to the focused window's geometry, takes precedence over `map-to-focused-output` and `map-to-output`.
Falls back to those when no window is focused (for example, in the overview).
When the tablet is also mapped to a specific output via `map-to-output`, the `map-to-focused-window` flag will map the tablet to the active window on that output.
If the tablet isn't mapped to any specific output, it will map the tablet to the current focused window regardless of where it is.
### General Settings
These settings are not specific to a particular input device.
@@ -195,7 +312,7 @@ input {
Makes the mouse warp to newly focused windows.
X and Y coordinates are computed separately, i.e. if moving the mouse only horizontally is enough to put it inside the newly focused window, then it will move only horizontally.
Does not make the cursor visible if it had been hidden.
```kdl
input {
@@ -203,6 +320,21 @@ input {
}
```
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-focus mode="center-xy"
}
```
#### `focus-follows-mouse`
Focuses windows and outputs automatically when moving the mouse over them.
@@ -243,3 +375,24 @@ 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.
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/niri-wm/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.
@@ -129,15 +133,12 @@ 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 {}`: they do not get filled with defaults, so make sure you do not erase this 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.
- `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).
>
@@ -49,6 +52,15 @@ For this reason, most of the default keys use the `Mod` modifier.
>
> 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`:
@@ -98,6 +110,65 @@ binds {
Both mouse wheel and touchpad scroll binds will prevent applications from receiving any scroll events when their modifiers are held down.
For example, if you have a `Mod+WheelScrollDown` bind, then while holding `Mod`, all mouse wheel scrolling will be consumed by niri.
### Mouse Click Bindings
<sup>Since: 25.01</sup>
You can bind mouse clicks using the following syntax.
```kdl
binds {
Mod+MouseLeft { close-window; }
Mod+MouseRight { close-window; }
Mod+MouseMiddle { close-window; }
Mod+MouseForward { close-window; }
Mod+MouseBack { close-window; }
}
```
Mouse clicks operate on the window that was focused at the time of the click, not the window you're clicking.
Note that binding `Mod+MouseLeft` or `Mod+MouseRight` will override the corresponding gesture (moving or resizing the window).
### Custom Hotkey Overlay Titles
<sup>Since: 25.02</sup>
The hotkey overlay (the Important Hotkeys dialog) shows a hardcoded list of binds.
You can customize this list using the `hotkey-overlay-title` property.
To add a bind to the hotkey overlay, set the property to the title that you want to show:
- `screenshot`: opens the built-in interactive screenshot UI.
- `screenshot-screen`, `screenshot-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:
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
matchlayer="top"
// 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
background-effect{
xraytrue
blurtrue
noise0.05
saturation3
}
popups{
opacity0.5
geometry-corner-radius6
background-effect{
xraytrue
blurtrue
noise0.05
saturation3
}
}
}
```
### 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
}
```
#### `layer`
<sup>Since: 26.04</sup>
Matches surfaces on this layer-shell layer.
Can be `"background"`, `"bottom"`, `"top"`, or `"overlay"`.
```kdl
// Make all overlay-layer surfaces FLOAT.
layer-rule{
matchlayer="overlay"
baba-is-floattrue
}
```
### 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).
```kdl
// Make fuzzel FLOAT.
layer-rule{
matchnamespace="^launcher$"
baba-is-floattrue
}
```
#### `background-effect`
<sup>Since: 26.04</sup>
Override the background effect options for this surface.
-`xray`: set to `true` to enable the xray effect, or `false` to disable it.
-`blur`: set to `true` to enable blur behind this surface, or `false` to force-disable it.
-`noise`: amount of pixel noise added to the background (helps with color banding from blur).
-`saturation`: color saturation of the background (`0` is desaturated, `1` is normal, `2` is 200% saturation).
See the [window effects page](./Window-Effects.md) for an overview of background effects.
```kdl
// Make top and overlay layers use the regular blur (if enabled),
// while bottom and background layers keep using the efficient xray blur.
layer-rule{
matchlayer="top"
matchlayer="overlay"
background-effect{
xrayfalse
}
}
```
#### `popups`
<sup>Since: 26.04</sup>
Override properties for this layer surface's pop-ups (e.g. a menu opened by clicking an item in Waybar).
The properties work the same way as the corresponding layer-rule properties, except that they apply to the layer surface's pop-ups rather than to the layer surface itself.
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
Other properties apply independently.
> [!NOTE]
> This block affects only pop-ups created by the app via Wayland's [xdg-popup](https://wayland.app/protocols/xdg-shell#xdg_popup) (which should be most of them).
>
> Some desktop shells will emulate pop-ups by drawing something that looks like a pop-up inside a regular layer surface.
> As far as niri is concerned, those are just layer surfaces and not pop-ups, so this block won't apply to them.
>
> This block also does not affect input-method pop-ups, such as Fcitx.
```kdl
// Blur the background behind Waybar popup menus.
layer-rule{
matchnamespace="^waybar$"
popups{
// Match the default GTK 3 popup corner radius.
geometry-corner-radius6
opacity0.85
background-effect{
blurtrue
}
}
}
```
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the layer surface correctly sets its Wayland geometry to exclude any shadows.
Pop-ups with custom shapes will need the app to implement the [ext-background-effect protocol](https://wayland.app/protocols/ext-background-effect-v1) to work properly.
<sup>Since: 25.11</sup> You can override these settings for specific [outputs](./Configuration:-Outputs.md#layout-config-overrides) and [named workspaces](./Configuration:-Named-Workspaces.md#layout-config-overrides).
### `gaps`
Set gaps around (inside and outside) windows in logical pixels.
@@ -99,15 +142,48 @@ layout {
}
```
### `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.
<sup>Since: 25.08</sup> You can use the `switch-preset-column-width-back` action (Mod+Shift+R) to toggle in reverse.
`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 width in logical pixels exactly.
`fixed` sets the window width in logical pixels exactly.
```kdl
layout {
@@ -121,13 +197,6 @@ layout {
}
```
> [!NOTE]
> Currently, due to an oversight, a preset `fixed` width does not take borders into account.
> I.e., preset `fixed 1000` with 4-wide borders will make the window 992 logical pixels wide.
> This may eventually be corrected.
>
> All other ways of using `fixed` (i.e. `default-column-width` or `set-column-width`) do take borders into account and give you the exact window width that you request.
### `default-column-width`
Set the default width of the new windows.
@@ -154,15 +223,14 @@ layout {
> `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.
> In practice, the only problematic client I saw is [foot](https://codeberg.org/dnkl/foot/), which takes this as a request to have a literal zero width.
>
> Either way, `default-column-width {}` is most useful for specific windows, in form of a [window rule](https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules) with the same syntax.
> 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.
Set the heights that the `switch-preset-window-height` action (Mod+Ctrl+Shift+R) toggles between.
<sup>Since: 25.08</sup> You can use the `switch-preset-window-height-back` action (not bound by default) to toggle in reverse.
`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.
@@ -190,17 +258,17 @@ The difference is that the focus ring is drawn only around the active window, wh
|  |  |
> [!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 at the [top level](./Configuration:-Miscellaneous.md) of the config.
> 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](https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules).
> 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.
@@ -217,6 +285,9 @@ layout {
active-color "#ffc87f"
inactive-color "#505050"
// Color of the border around windows that request your attention.
@@ -260,7 +331,7 @@ Similarly to colors, you can set `active-gradient` and `inactive-gradient`, whic
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 [this one](https://www.css-gradient.com/).
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 {
@@ -298,7 +369,7 @@ Supported color spaces are:
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)`.


```kdl
layout {
@@ -308,6 +379,109 @@ layout {
}
```
### `shadow`
<sup>Since: 25.02</sup>
Shadow rendered behind a window.
Set `on` to enable the shadow.
`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
}
blur{
// off
passes3
offset3.0
noise0.02
saturation1.5
}
```
### `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: 25.08</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
}
```
Note that these variables do not propagate to the systemd global environment, so tools and applications started by systemd do not see them.
In particular, if you start a desktop shell like DankMaterialShell through systemd, then use its built-in application launcher, the apps won't see these environment variables.
If you want all processes to see the environment variables, you can set them in your login shell config instead (i.e. `~/.bash_profile`).
The `niri-session` shell script runs through the login shell and imports all environment variables to systemd before starting niri.
Keep in mind that all compositors will see variables set in the login shell, not just niri.
### `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: 25.08</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: 25.08</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: 25.08</sup>
Settings for the config created/failed notification.
Set the `disable-failed` flag to disable the "Failed to parse the config file" notification.
For example, if you have a custom one.
```kdl
config-notification{
disable-failed
}
```
### `blur`
<sup>Since: 26.04</sup>
Blur configuration that affects all background blur.
See the [window effects page](./Window-Effects.md) for an overview of background effects.
```kdl
// These are the default values:
blur{
// off
passes3
offset3
noise0.02
saturation1.5
}
```
#### `off`
By default, blur is available on request by a window or layer surface (via the `ext-background-effect` protocol).
You can also enable it manually with the `blur true` background effect [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rule.
Setting the `off` flag will disable all blur, both requested by the window, and configured in window rules.
```kdl
blur{
off
}
```
#### `passes` and `offset`
`passes` controls the number of downsample/upsample passes for dual kawase blur.
More passes produce a larger, smoother blur, but cost more GPU resources.
`offset` is the pixel offset multiplier for each pass.
Offset `1` is the original dual kawase blur.
Larger values produce a smoother blur, at no additional GPU cost.
However, setting `offset` too big will produce visual artifacts.
You will need to increase `passes` to be able to use a bigger `offset` without artifacts.
When configuring blur, try increasing `offset` first (since it doesn't cause any extra GPU load) until you start getting artifacts.
Then, if you still need smoother blur, increase `passes` by 1.
Keep doing this until you get the desired visuals.
```kdl
blur{
passes3
offset3.0
}
```
#### `noise`
Amount of noise to add on top of the blur.
This is helpful to reduce color banding artifacts.
```kdl
blur{
noise0.02
}
```
#### `saturation`
Color saturation applied to the blurred background.
Values above `1` increase saturation; values below `1` reduce it.
@@ -39,3 +39,59 @@ There's no way to give a name to an already existing workspace, but you can simp
<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.
### Layout config overrides
<sup>Since: 25.11</sup>
You can customize layout settings for named workspaces with a `layout {}` block:
```kdl
workspace "aesthetic" {
// Layout config overrides just for this named workspace.
layout {
gaps 32
struts {
left 64
right 64
bottom 64
top 64
}
border {
on
width 4
}
// ...any other setting.
}
}
```
It accepts all the same options as [the top-level `layout {}` block](./Configuration:-Layout.md), except:
- `empty-workspace-above-first`: this is an output-level setting, doesn't make sense on a workspace.
- `insert-hint`: currently we always draw these at the output level, so it's not customizable per-workspace.
In order to unset a flag, write it with `false`, e.g.:
@@ -143,7 +205,7 @@ 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) and reconnecting the monitor.
> 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.
>
@@ -164,17 +226,157 @@ output "HDMI-A-1" {
}
```
### `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).
In this section you can configure the recent windows switcher (Alt-Tab).
Here is an outline of the available settings and their default values:
```kdl
recent-windows{
// off
debounce-ms750
open-delay-ms150
highlight{
active-color"#999999ff"
urgent-color"#ff9999ff"
padding30
corner-radius0
}
previews{
max-height480
max-scale0.5
}
binds{
Alt+Tab{ next-window;}
Alt+Shift+Tab{ previous-window;}
Alt+grave{ next-windowfilter="app-id";}
Alt+Shift+grave{ previous-windowfilter="app-id";}
Mod+Tab{ next-window;}
Mod+Shift+Tab{ previous-window;}
Mod+grave{ next-windowfilter="app-id";}
Mod+Shift+grave{ previous-windowfilter="app-id";}
}
}
```
`off` disables the recent windows switcher altogether.
### `debounce-ms`
Delay, in milliseconds, between the window receiving focus and getting "committed" to the recent windows list.
When you want to focus some window, you might end up focusing some unrelated windows on the way:
- with keyboard navigation, the windows between your current one and the target one;
- with [`focus-follows-mouse`](./Configuration:-Input.md#focus-follows-mouse), the windows you happen to cross with the mouse pointer on the way to the target window.
The debounce delay prevents those intermediate windows from polluting the recent windows list.
Note that some actions, like keyboard input into the target window, will skip this delay and commit the window to the list immediately.
This way, the recent windows list stays responsive while not getting polluted too much with unintended windows.
If you want windows to appear in recent windows right away, including intermediate windows, you can reduce the delay or set it to zero:
```kdl
recent-windows{
// Commit windows to the recent windows list as soon as they're focused,
// with no debounce delay.
debounce-ms0
}
```
### `open-delay-ms`
Delay, in milliseconds, between pressing the Alt-Tab bind and the recent windows switcher visually appearing on screen.
The switcher is delayed by default so that quickly tapping Alt-Tab to switch windows wouldn't cause annoying fullscreen visual changes.
```kdl
recent-windows{
// Make the switcher appear instantly.
open-delay-ms0
}
```
### `highlight`
Controls the highlight behind the focused window preview in the recent windows switcher.
-`active-color`: normal color of the focused window highlight.
-`urgent-color`: color of an urgent focused window highlight, also visible in a darker shade on unfocused windows.
-`padding`: padding of the highlight around the window preview, in logical pixels.
-`corner-radius`: corner radius of the highlight.
```kdl
recent-windows{
// Round the corners on the highlight.
highlight{
corner-radius14
}
}
```
### `previews`
Controls the window previews in the switcher.
-`max-scale`: maximum scale of the window previews.
Windows cannot be scaled bigger than this value.
-`max-height`: maximum height of the window previews.
Further limits the size of the previews in order to occupy less space on large monitors.
On smaller monitors, the previews will be primarily limited by `max-scale`, and on larger monitors they will be primarily limited by `max-height`.
The `max-scale` limit is imposed twice: on the final window scale, and on the window height which cannot exceed `monitor height × max scale`.
```kdl
recent-windows{
// Make the previews smaller to fit more on screen.
previews{
max-height320
}
}
```
```kdl
recent-windows{
// Make the previews larger to see the window contents.
previews{
max-height1080
max-scale0.75
}
}
```
### `binds`
Configure binds that open and navigate the recent windows switcher.
The defaults are <kbd>Alt</kbd><kbd>Tab</kbd> / <kbd>Mod</kbd><kbd>Tab</kbd> to switch across all windows, and <kbd>Alt</kbd><kbd>\`</kbd> / <kbd>Mod</kbd><kbd>\`</kbd> to switch between windows of the current application.
Adding <kbd>Shift</kbd> will switch windows backwards.
Adding the recent windows `binds {}` section to your config removes all default binds.
You can copy the ones you need from the summary at the top of this wiki page.
```kdl
recent-windows{
// Even an empty binds {} section will remove all default binds.
binds{
}
}
```
The available actions are `next-window` and `previous-window`.
They can optionally have the following properties:
-`filter="app-id"`: filters the switcher to the windows of the currently selected application, as determined by the Wayland app ID.
-`scope="all"`, `scope="output"`, `scope="workspace"`: sets the pre-selected scope when this bind is used to open the recent windows switcher.
```kdl
recent-windows{
// Pre-select the "Output" scope when switching windows.
The recent windows binds have lower precedence than the [normal binds](./Configuration:-Key-Bindings.md), meaning that if you have <kbd>Alt</kbd><kbd>Tab</kbd> bound to something else in the normal binds, the `recent-windows` bind won't work.
In this case, you can remove the conflicting normal bind.
All binds in this section must have a modifier key like <kbd>Alt</kbd> or <kbd>Mod</kbd> because the recent windows switcher remains open only while you hold any modifier key.
#### Bindings inside the switcher
When the switcher is open, some hardcoded binds are available:
- <kbd>Escape</kbd> cancels the switcher.
- <kbd>Enter</kbd> closes the switcher confirming the current window.
- <kbd>A</kbd>, <kbd>W</kbd>, <kbd>O</kbd> select a specific scope.
- <kbd>S</kbd> cycles between scopes, as indicated by the panel at the top.
- <kbd>←</kbd>, <kbd>→</kbd>, <kbd>Home</kbd>, <kbd>End</kbd> move the selection directionally.
Additionally, certain regular binds will automatically work in the switcher:
- focus column left/right and their variants: will move the selection left/right inside the switcher.
- focus column first/last: will move the selection to the first or last window.
- close window: will close the window currently focused in the switcher.
- screenshot: will open the screenshot UI.
The way this works is by finding all regular binds corresponding to these actions and taking just the trigger key without modifiers.
For example, if you have <kbd>Mod</kbd><kbd>Shift</kbd><kbd>C</kbd> bound to `close-window`, in the window switcher pressing <kbd>C</kbd> on its own will close the window.
This way we don't need to hardcode things like HJKL directional movements.
If you have, say, Colemak-DH MNEI binds instead, they will work for you in the window switcher (as long as they don't conflict with the hardcoded ones).
lid-close { spawn "notify-send" "The laptop lid is closed!"; }
lid-open { spawn "notify-send" "The laptop lid is open!"; }
}
```
@@ -37,6 +39,9 @@ switch-events {
These events trigger when a convertible laptop goes into or out of tablet mode.
In tablet mode, the keyboard and mouse are usually inaccessible, so you can use these events to activate the on-screen keyboard.
> [!NOTE]
> The commands below are just examples, you will need to provide your own on-screen keyboard, such as [sysboard](https://github.com/System64fumo/sysboard) or [wvkbd](https://github.com/jjsullivan5196/wvkbd).
> *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 170Hz monitor can draw a frame every ~5.88ms.
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 that I try to follow throughout niri.
They can be sidestepped in specific circumstances if there's a good reason.
### Opening a new window should not affect the sizes of any existing windows.
This is the main annoyance with traditional tiling: you want to open a new window, but it messes with your existing window sizes.
Especially when you're looking at a big window like a browser or an image editor, want to open a quick terminal for something, and it makes the big window unusably small, or reflows the content, or clips part of the window.
The usual workaround in tiling WMs is to use more workspaces: when you need a new window, you go to an empty workspace and open it there (this way, you also get your entire screen for the new window, rather than a smaller part of it).
Scrollable tiling offers an alternative: for temporary windows, you can just open them, do what you need, and close, all without messing up the other windows or having to go to a new workspace.
It also lets you group together more related windows on the same workspace by having less frequently used ones scrolled out of the view.
### 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.
The focused window is the window you're working in.
And stuff happening outside the view shouldn't mess with what you're focused on.
### Actions should apply immediately.
This is important both for compositor responsiveness and predictability, and for keeping the code sane and free of edge cases and unnecessary asynchrony.
- Things like resizing or consuming into column take effect immediately, even if the window needs time to catch up.
- An animated workspace switch makes your input go to the final workspace and window instantly, without waiting for the animation.
- Opening the overview (which has a zoom-out animation) lets you grab windows right away, and closing the overview makes your input immediately go back to the windows, without waiting for the zoom back in.
### 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.
### 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.
### 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.
## Window layout
Here are some design considerations for the window layout logic.
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.
1. Fullscreen windows are a normal part of the scrolling layout.
This is a cool idea that scrollable tiling is uniquely positioned to implement.
Fullscreen windows aren't on some "special" layer that covers everything; instead, they are normal tiles that you can switch away from, without disturbing the fullscreen status.
Of course, you do want to cover your entire monitor when focused on a fullscreen window.
This is specifically hardcoded into the logic: when the view is stationary on a focused fullscreen window, the top layer-shell layer and the floating windows hide away.
This is also why fullscreening a floating window makes it go into the scrolling layout.
## Default config
The [default config](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl) is intended to give a familiar, helpful, and not too jarring experience to new niri users.
Importantly, it is not a "suggested rice config"; we don't want to startle people with full-on rainbow borders and crazy shaders.
Since we're not a complete desktop environment (and don't have the contributor base to become one), we cannot provide a fully integrated experience—distro spins are better positioned to do this.
As such, new niri users are expected to read through and tinker with the default niri config.
The default config is therefore thoroughly commented with links to the relevant wiki sections.
We don't include every possible option in the default config to avoid overwhelming users too much; anything overly specific or uncommon can stay on the wiki.
The general rule is to include things that users are reasonably expected to want to change or know how to do.
We do also advertise our more unique features though like screencast block-out-from.
We default to CSD (`prefer-no-csd` is commented out).
This gives new users easy and familiar way to move and close windows via their titlebars, especially considering that niri doesn't have serverside titlebars (so far at least).
Focus rings are drawn fully behind windows by default.
While this unfortunately messes with window transparency, [which is a common source of confusion](./FAQ.md#why-are-transparent-windows-tinted-why-is-the-borderfocus-ring-showing-up-through-semitransparent-windows), defaulting to drawing focus rings only around windows would be even worse because it has holes inside clientside rounded corners.
The ideal solution here would be to propose a Wayland protocol for windows to report their corner radius to the compositor (which would generally help for serverside decorations in different compositors).
The default focus ring is quite thick at 4px to look well with clientside-decorated windows and be obviously noticeable, and the default gaps are also quite big at 16px to look well with the default focus ring width.
The default input settings like touchpad tap and natural-scroll are how I believe most people want to use their computers.
Shadows default to off because they are a fairly performance-intensive shader, and because many clientside-decorated windows already draw their own shadows.
The default screenshot-path matches GNOME Shell.
Default window rules are limited to fixing known severe issues (WezTerm) and doing something the absolute majority likely wants (make Firefox Picture-in-Picture player floating—it can't do that on its own currently, maybe the pip protocol will change that).
The default binds largely come from my own experience using PaperWM, and from other compositors.
They assume QWERTY.
The binds are ordered in a way to gradually introduce you to different bind configuration concepts.
The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kbd> will move the focused window or column there.
Adding <kbd>Shift</kbd> does an alternative action: for focus and movement it starts going across monitors, for resizes it starts acting on window height rather than width, etc.
Workspace switching on <kbd>Mod</kbd><kbd>U</kbd>/<kbd>I</kbd> is one key up from <kbd>Mod</kbd><kbd>J</kbd>/<kbd>K</kbd> used for window switching.
Since <kbd>Alt</kbd> is a modifier in nested niri, binds with explicit <kbd>Alt</kbd> are mainly the ones only useful on the host, for example spawning a screen locker.
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.
@@ -25,6 +25,7 @@ The way it's handled in niri is:
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.
This is a checklist of things to release a new niri version.
We'll use `26.04` as the example new version.
When making a patch release, append the patch number like `26.04.1`.
## Prepare the release notes
Plan for a few days of work, this usually takes a while.
During this process, also check:
- that all additions are marked with "next release" on the wiki,
- if anything needs updating in `README.md`.
## Bump version
We use `year.month.patch` versioning.
If the month contains a leading zero, drop it from the crate version (Cargo requirement).
You can use the command from [cargo-edit](https://github.com/killercup/cargo-edit):
```
cargo set-version 26.4.0
```
Then, manually update version in:
-`[package.metadata.generate-rpm]` in Cargo.toml
- Dependency example in `niri-ipc/README.md`
- Dependency example in `niri-ipc/src/lib.rs`
Do a full text search for the old version to make sure there are no other places.
## Replace all "Since:next release" mentions
Do a full text search for `next release`, replace everything with the new version number.
## Build, test, push, and have the CI run
Run all tests:
```
RUN_SLOW_TESTS=1 cargo test --release --all
```
- Run `cargo package -p niri-ipc` and make sure it succeeds.
- Make sure the CI passes.
- Make sure the niri-git COPR build passes.
## Trigger the "Prepare release" workflow on GitHub Actions
Set the "Public version" input to a version like `26.04`.
This workflow will:
- do some pre-release checks like grepping the wiki for "next version",
- make a vendored dependency archive,
- build and test niri with that dependency archive,
- draft a new GitHub release with the archive attached.
It will NOT override an existing draft release with the same name so the release notes are safe.
Make sure it succeeds and grab the vendored dependency archive that it produces.
## Update the niri COPR spec, update licenses in .spec.rpkg
You can grab the previous spec from [the last build](https://copr.fedorainfracloud.org/coprs/yalter/niri/builds/) in the COPR.
- Update version global to `26.04`.
- Update commit global to the commit hash corresponding to the release commit.
You can use `git rev-parse HEAD`.
- Reset the `Release:` number to 1 if it was higher.
To run a test build, you can download the vendored dependency archive from the last step.
Comment/uncomment `Source:` and `%autosetup` lines accordingly.
Download the source files:
```
spectool -g niri.spec
```
Build RPMs:
```
fedpkg --release 44 mockbuild
```
During the build, it will print the list of licenses.
Update it in both the COPR spec and in `niri.spec.rpkg` accordingly.
If you had to update `niri.spec.rpkg` and therefore make another commit to the niri repo, make sure to update the commit hash in the COPR spec again.
Revert any temporary changes that you did to the COPR spec for local testing.
## Create and push the release git tag
The tag starts with a `v`:
```
git tag -am "v26.04 release" v26.04
git push origin v26.04
```
While you can let GitHub create the tag automatically upon creating the release, this is not recommended.
GitHub creates a *lightweight* tag, but we want an annotated tag that plays better with various tooling.
## Publish the release on GitHub
- Either upload the vendored dependencies file to your draft release with the release notes, or move the release notes to the GitHub-created release (the difference is that it's attributed to github-actions).
@@ -2,26 +2,23 @@ When starting niri from a display manager like GDM, or otherwise through the `ni
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.
In contrast to the `spawn-at-startup` config option, this lets you easily monitor their status and output, and restart or reload them.
Unlike [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup), this lets you easily monitor their status and output, and restart or reload them.
4. `swaybg` does not provide a systemd unit, since you need to pass the background image as a command-line argument.
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.
Put the following into`~/.config/systemd/user/swaybg.service`:
Create`~/.config/systemd/user/swaybg.service` with the following contents:
```
```systemd
[Unit]
PartOf=graphical-session.target
After=graphical-session.target
@@ -37,15 +34,16 @@ In contrast to the `spawn-at-startup` config option, this lets you easily monito
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 fix lag on external monitors connected to a hybrid GPU laptop?
Hybrid GPU laptops (which have both an integrated and a discrete GPU) generally connect the external monitor port to the discrete GPU.
Meanwhile, the built-in monitor is connected to the integrated GPU, and the integrated GPU is used for rendering by default.
This is good and expected because the integrated GPU uses significantly less battery compared to the discrete GPU.
However, this means that niri has to render the external monitor contents on the integrated GPU, then copy them over to the discrete GPU for display.
On some laptops this can cause lag and stuttering (it gets worse with monitor resolution and refresh rate).
If your laptop has a MUX switch—usually a GPU toggle in the UEFI settings—then you can switch it to use the discrete GPU, then niri will render on the discrete GPU, and the external monitor won't lag.
Otherwise, you can try configuring niri to render on the discrete GPU via the [`render-drm-device`](./Configuration:-Debug-Options.md#render-drm-device) debug option.
Keep in mind that using the discrete GPU for rendering will make the laptop's battery deplete much faster.
### 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 or Discord natively on Wayland by passing the right flags, as described [here](./Application-Issues.md#electron-applications).
### 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.
<sup>Since: 25.08</sup> niri has seamless built-in xwayland-satellite integration that by and large works as well as built-in Xwayland in other compositors, solving the hurdle of having to set it up manually.
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?
<sup>Since: 26.04</sup> Yes.
See the [window effects](./Window-Effects.md) wiki page.
### Can I make a window sticky / pinned / always on top / appear on all workspaces?
Not yet, follow/upvote [this issue](https://github.com/niri-wm/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/niri-wm/niri/discussions/1599) or other ones (search niri issues and discussions for Bitwarden).
### Can I open a window directly in the current column / in the same column as another window?
No, but you can script the behavior you want with the [niri IPC](./IPC.md).
Listen to the event stream for a new window opening, then call an action like `consume-or-expel-window-left`.
Adding this directly to niri is challenging:
- The act of "opening a window directly in some column" by itself is quite involved. Niri will have to compute the exact initial window size provided how other windows in a column would resize in response. This logic exists, but it isn't directly pluggable to the code computing a size for a new window. Then, it'll need to handle all sorts of edge cases like the column disappearing, or new windows getting added to the column, before the target window had a chance to appear.
- How do you indicate if a new window should spawn in an existing column (and in which one), as opposed to a new column? Different people seem to have different needs here (including very complex rules based on parent PID, etc.), and it's very unclear design-wise what kind of (simple) setting is actually needed and would be useful. See also https://github.com/niri-wm/niri/discussions/1125.
### Why does moving the mouse against a monitor edge focus the next window, but only sometimes?
This can happen with [`focus-follows-mouse`](./Configuration:-Input.md#focus-follows-mouse).
When using client-side decorations, windows are supposed to have some margins outside their geometry for the mouse resizing handles.
These margins "peek out" of the monitor edges since they're outside the window geometry, and `focus-follows-mouse` triggers when the mouse crosses them.
It doesn't always happen:
- Some toolkits don't put resize handles outside the window geometry. Then there's no input area outside, so nowhere for `focus-follows-mouse` to trigger.
- If the current window has its own margin for resizing, and it extends all the way to the monitor edge, then `focus-follows-mouse` won't trigger because the mouse will never leave the current window.
To fix this, you can:
- Use `focus-follows-mouse max-scroll-amount="0%"`, which will prevent `focus-follows-mouse` from triggering when it would cause scrolling.
- Set `prefer-no-csd` which will generally cause clients to remove those resizing margins.
### How do I recover from a dead screen locker / from a red screen?
When your screen locker dies, you will be left with a red screen.
This is niri's locked session background.
You can recover from this by spawning a new screen locker.
One way is to switch to a different TTY (with a shortcut like <kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F3</kbd>) and spawning a screen locker to niri's Wayland display, e.g. `WAYLAND_DISPLAY=wayland-1 swaylock`.
Another way is to set `allow-when-locked=true` on your screen locker bind, then you can press it on the red screen to get a fresh screen locker.
### How do I change output configuration based on connected monitors?
If you require different output configurations depending on what outputs are connected then you can use [Kanshi](https://gitlab.freedesktop.org/emersion/kanshi).
Kanshi has its own simple configuration and communicates with niri via IPC. You may want to launch kanshi from the niri config.kdl e.g. `spawn-at-startup "/usr/bin/kanshi"`
For example, if you wish to scale your laptop display differently when an external monitor is connected, you might use a Kanshi config like this:
```
profile {
output eDP-1 enable scale 1.0
}
profile {
output HDMI-A-1 enable scale 1.0 position 0,0
output eDP-1 enable scale 1.25 position 1920,0
}
```
### Why does Firefox or Thunderbird have 1 px smaller border?
They draw their own 1 px dark border around the window, which obscures one pixel of niri's border.
If you don't like this, set the [`clip-to-geometry true` window rule](./Configuration:-Window-Rules.md#clip-to-geometry).
There are several ways to make a window big on niri: maximizing the column, maximizing the window to edges, and fullscreening the window.
Let's look at their differences.
## Maximized (full-width) columns
Maximizing the column via `maximize-column` (bound to <kbd>Mod</kbd><kbd>F</kbd> by default) expands its width to cover the whole screen.
Maximized columns still leave space for [struts] and [gaps], and can contain multiple windows.
The windows retain their borders.
This is the simplest of the sizing modes, and is equivalent to `proportion 1.0` column width, or `set-column-width "100%"`.

You can make a window open in a maximized column with the [`open-maximized true`](./Configuration:-Window-Rules.md#open-maximized) window rule.
## Windows maximized to edges
<sup>Since: 25.11</sup>
You can maximize an individual window via `maximize-window-to-edges` (bound to <kbd>Mod</kbd><kbd>M</kbd> by default).
This is the same maximize as you can find on other desktop environments and operating systems: it expands a window to the edges of the available screen area.
You will still see your bar, but not struts, gaps, or borders.
Windows are aware of their maximized-to-edges status and generally respond by squaring their corners.
Windows can also control maximizing-to-edges: when you click on the square icon in the window's titlebar, or double-click on the titlebar, the window will request niri to maximize or unmaximize itself.
You can put multiple maximized windows into a [tabbed column](./Tabs.md), but not into a regular column.

You can make a window open maximized-to-edges, or prevent a window from maximizing upon opening, with the [`open-maximized-to-edges`](./Configuration:-Window-Rules.md#open-maximized-to-edges) window rule.
## Fullscreen windows
Windows can go fullscreen, usually seen with video players, presentations or games.
You can also force a window to go fullscreen via `fullscreen-window` (bound to <kbd>Mod</kbd><kbd>Shift</kbd><kbd>F</kbd> by default).
Fullscreen windows cover the entire screen.
Similarly to maximize-to-edges, windows are aware of their fullscreen status, and can respond by hiding their titlebars or other parts of the UI.
Niri renders a solid black backdrop behind fullscreen windows.
This backdrop helps match the screen size when the window itself remains too small (e.g. if you try to fullscreen a fixed-size dialog window), which is the behavior [defined by the Wayland protocol](https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_fullscreen).
When a fullscreen window is focused and not animating, it will cover floating windows and the top layer-shell layer.
If you want for example your layer-shell notifications or launcher to appear over fullscreen windows, configure the respective tools to put them on the overlay layer-shell layer.

You can make a window open fullscreen, or prevent a window from fullscreening upon opening, with the [`open-fullscreen`](./Configuration:-Window-Rules.md#open-fullscreen) window rule.
## Common behaviors across fullscreen and maximize
Fullscreen or maximized-to-edges windows can only be in the scrolling layout.
So if you try to fullscreen or maximize a [floating window](./Floating-Windows.md), it'll move into the scrolling layout.
Then, unfullscreening/unmaximizing will bring it back into the floating layout automatically.
Thanks to scrollable tiling, fullscreen and maximized windows remain a normal participant of the layout: you can scroll left and right from them and see other windows.

Fullscreen and maximize-to-edges are both special states that the windows are aware of and can control.
Windows sometimes want to restore their fullscreen or, more frequently, maximized state when they open.
The best opportunity for this is during the *initial configure* sequence when the window tells niri everything it should know before opening the window.
If the window does this, then `open-maximized-to-edges` and `open-fullscreen` window rules have a chance to block or adjust the request.
However, some clients tend to request to be maximized shortly *after* the initial configure sequence, when the niri already sent them the initial size request (sometimes even after showing on screen, resulting in a quick resize right after opening).
From niri's point of view, the window is already open by this point, so if the window does this, then the `open-maximized-to-edges` and `open-fullscreen` window rules don't do anything.
## Windowed fullscreen
<sup>Since: 25.05</sup>
Niri can also tell a window that it's in fullscreen without actually making it fullscreen, via the `toggle-windowed-fullscreen` action.
This is generally useful for screencasting browser-based presentations, when you want to hide the browser UI, but still have the window sized as a normal window.
When in windowed fullscreen, you can use the niri action to maximize or unmaximize the window.
Window-side titlebar maximize buttons and gestures may not work, since the window will always think that it's in fullscreen.
See also windowed fullscreen on the [screencasting features wiki page](./Screencasting.md#windowed-fakedetached-fullscreen).
After running these commands, log out, choose Niri in your display manager, and log back in.
Or, if not using a display manager, run `niri-session` on a TTY.
The default niri config will run Waybar, so you might get two bars on screen.
To fix this, stop Waybar with `pkill waybar` command, then open `~/.config/niri/config.kdl` and delete the `spawn-at-startup "waybar"` line.
Check the DankMaterialShell's [compositor setup page](https://danklinux.com/docs/dankmaterialshell/compositors#niri-configuration) to learn how to configure DMS-specific binds and other niri integrations.
## Slower and more considered start
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.
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, including a [pacstall package](https://pacstall.dev/packages/niri/) for Debian-based distros.
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.
@@ -10,33 +44,38 @@ 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 overview](./Configuration:-Overview.md) page to get started configuring niri.
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
NVIDIA GPUs tend to have problems running niri (for example, the screen remains black upon starting from a TTY).
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.
If niri runs but the screen flickers, try adding this into your niri config:
```
debug {
wait-for-frame-completion-before-queueing
}
```
### Asahi, ARM, and other kmsro devices
On some of these systems, niri fails to correctly detect the primary render device.
@@ -57,7 +96,7 @@ 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"
}
@@ -69,14 +108,15 @@ 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).
Also, on Intel graphics, you may need a workaround described [here](https://wiki.nixos.org/wiki/Intel_Graphics).
### Virtual Machines
To run niri in a VM, make sure to enable 3D acceleration.
## Default Hotkeys
## 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>.
@@ -98,24 +138,18 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| <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>Home</kbd> and <kbd>Mod</kbd><kbd>End</kbd> | Focus the first or the last column |
| <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>Home</kbd> and <kbd>Mod</kbd><kbd>Ctrl</kbd><kbd>End</kbd> | Move the focused column to the very start or to the very end |
| <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>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 column 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>[</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>F</kbd> | Maximize column |
| <kbd>Mod</kbd><kbd>R</kbd> and <kbd>Mod</kbd><kbd>Shift</kbd><kbd>R</kbd> | Toggle between preset column widths forward and back |
| <kbd>Mod</kbd><kbd>M</kbd> | Maximize window |
| <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% |
@@ -123,16 +157,18 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| <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/` |
@@ -2,7 +2,7 @@ Since niri is not a complete desktop environment, you will very likely want to r
### 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`.
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
@@ -14,7 +14,7 @@ Portals **require** [running niri as a session](./Getting-Started.md), which mea
* `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](https://github.com/YaLTeR/niri/wiki/Getting-Started#installation) in the correct location.
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:
@@ -22,9 +22,16 @@ Since we're using `xdg-desktop-portal-gnome`, Flatpak apps will read the GNOME U
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.
> [!WARNING]
> Do not set the `GDK_BACKEND` environment variable globally as this will break the screencast portal.
### 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`.
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:
This page contains various bits of information helpful for integrating niri in a distribution.
First, for creating a niri package, see the [Packaging](./Packaging-niri.md) page.
### Configuration
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/niri-wm/niri/blob/main/resources/default-config.kdl), which are embedded into the niri binary at build time.
This means that you can customize your distribution defaults by creating `/etc/niri/config.kdl`.
When this file is present, niri *will not* automatically create a config at `~/.config/niri/`, so you'll need to direct your users how to do it themselves.
Keep in mind that we update the default config in new releases, so if you have a custom `/etc/niri/config.kdl`, you likely want to inspect and apply the relevant changes too.
The default configuration locations can be overridden with the `NIRI_CONFIG` environment variable.
<sup>Since: 26.04</sup> You can also change the configuration path at runtime via the niri IPC or using the command `niri msg action load-config-file --path <path-to-config.kdl>`.
<sup>Since: 25.11</sup> You can split the niri config file into multiple files using [`include`](./Configuration:-Include.md).
### Xwayland
Xwayland is required for running X11 apps and games, and also the Orca screen reader.
<sup>Since: 25.08</sup> Niri integrates with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) out of the box.
The integration requires xwayland-satellite >= 0.7 available in `$PATH`.
Please consider making niri depend on (or at least recommend) the xwayland-satellite package.
If you had a custom config which manually started `xwayland-satellite` and set `$DISPLAY`, you should remove those customizations for the automatic integration to work.
You can change the path where niri looks for xwayland-satellite using the [`xwayland-satellite` top-level option](./Configuration:-Miscellaneous.md#xwayland-satellite).
### Keyboard layout
<sup>Since: 25.08</sup> By default (unless [manually configured](./Configuration:-Input.md#layout) otherwise), niri reads keyboard layout settings from systemd-localed at `org.freedesktop.locale1` over D-Bus.
Make sure your system installer sets the keyboard layout via systemd-localed, and niri should pick it up.
### Autostart
Niri works with the normal systemd autostart.
The default [niri.service](https://github.com/niri-wm/niri/blob/main/resources/niri.service) brings up `graphical-session.target` as well as `xdg-desktop-autostart.target`.
To make a program run at niri startup without editing the niri config, you can either link its .desktop to `~/.config/autostart/`, or use a .service file with `WantedBy=graphical-session.target`.
See the [example systemd setup](./Example-systemd-Setup.md) page for some examples.
If this is inconvenient, you can also add [`spawn-at-startup`](./Configuration:-Miscellaneous.md#spawn-at-startup) lines in the niri config.
### Screen readers
<sup>Since: 25.08</sup> Niri works with the [Orca](https://orca.gnome.org) screen reader.
Please see the [Accessibility](./Accessibility.md) page for details and advice for accessibility-focused distributions.
### Desktop components
You very likely want to run at least a notification daemon, portals, and an authentication agent.
This is detailed on the [Important Software](./Important-Software.md) page.
On top of that, you may want to preconfigure some desktop shell components to make the experience less barebones.
Niri's default config spawns [Waybar](https://github.com/Alexays/Waybar), which is a good starting point, but you may want to consider changing its default configuration to be less of a kitchen sink, and adding the `niri/workspaces` module.
You will probably also want a desktop background tool ([swaybg](https://github.com/swaywm/swaybg) or [awww (which used to be swww)](https://codeberg.org/LGFae/awww/)), and a nicer screen locker (compared to the default `swaylock`), like [hyprlock](https://github.com/hyprwm/hyprlock/).
Alternatively, some desktop environments and shells work with niri, and can give a more cohesive experience in one package:
- [LXQt](https://lxqt-project.org/) officially supports niri, see [their wiki](https://lxqt-project.org/wiki/Wayland-Session) 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).
### Security model
See the [Security Model](./Security-Model.md) page for an overview of niri's security model.
Things to keep in mind with layer-shell components (bars, launchers, etc.):
1. When a [full-screen](./Fullscreen-and-Maximize.md) 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.
The name "niri" is canonically written in lower-case, but feel free to capitalize it if you'd like, especially at the start of sentences where the grammatical rules require it.
This name is not intended to mean or stand for anything.
Our logo comes in four versions: full-sized, simple full-sized, icon, and simple icon.
The simple versions are single-color and suitable for smaller sizes.
In fact, there's [a webpage](https://nirilogo.raurutuchr.ink) that lets you quickly adjust the color and download an SVG.
All versions of the logo are licensed under [CCBY-SA](https://creativecommons.org/licenses/by-sa/4.0/).
The full-sized logo is based on the [Cherry Bomb One](https://github.com/satsuyako/CherryBomb) font, licensed under the [SIL Open Font License 1.1](https://openfontlicense.org/).
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: 25.08</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 debug flag has since been removed because the problem was properly fixed in niri.
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 awww), 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.
See the [Integrating niri](./Integrating-niri.md) page for further information on distribution integration.
### Recommended dependencies
First of all, make sure niri depends on `libwayland-server`.
This library is currently loaded dynamically, so it's not picked up as a dependency at niri build time.
Then, the following dependencies are optional, but strongly recommended.
Set them as automatically-installed optional dependencies, if possible.
-`xwayland-satellite`: required to run X11 applications (Steam, Discord, etc.).
-`xdg-desktop-portal-gnome`: required for screencasting.
-`xdg-desktop-portal-gtk`: configured as the fallback portal in `niri-portals.conf`.
(This is in general the standard fallback portal that you want installed.)
-`gnome-keyring`: configured as the Secret portal provider in `niri-portals.conf`.
- Your distro's GPU driver package, such as `mesa-dri-drivers` and `mesa-libEGL`.
Working hardware acceleration is required for running niri.
- Some notification daemon like `mako`, generally required for apps to work correctly.
Finally, you may want to auto-install some of the applications bound in niri's [default configuration file](https://github.com/niri-wm/niri/blob/main/resources/default-config.kdl) (search for `spawn`), such as `alacritty` and `fuzzel`.
### 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.
Some tests require surfaceless EGL to be available at test time.
If this is problematic, you can skip them like so:
```
$ cargo test -- --skip=::egl
```
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`.
### Shell completions
You can generate shell completions for several shells via `niri completions <SHELL>`, i.e. `niri completions bash`.
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:

### Screen mirroring
For presentations it can be useful to mirror an output to another.
Currently, niri doesn't have built-in output mirroring, but you can use a third-party tool [`wl-mirror`](https://github.com/Ferdi265/wl-mirror) that mirrors an output to a window.
Note that the command below requires [`jq`](https://jqlang.org/download/) to be installed.
Niri assumes that programs running unsandboxed on the host are **trusted**.
This is a reasonable assumption because programs running on the host have a wide variety of ways to get all access they need, even without niri.
For instance:
- They can set `$LD_PRELOAD` in `.bashrc` or similar files to load an arbitrary library into all processes.
- They can replace binaries in `$PATH` with malicious code.
- They can interpose any socket in `$XDG_RUNTIME_DIR`, like Wayland, and do keylogging or record window contents.
- They can scan the filesystem for secrets: SSH keys, password stores, etc.
- They can connect to an unlocked keyring and steal credentials.
- And so on and so forth.
## Unsandboxed clients
Anything with access to niri's Wayland socket can, among other things:
- Record the user's screen via [wlr-screencopy](https://wayland.app/protocols/wlr-screencopy-unstable-v1).
- Emulate input via [wlr-virtual-pointer](https://wayland.app/protocols/wlr-virtual-pointer-unstable-v1) and [virtual-keyboard](https://wayland.app/protocols/virtual-keyboard-unstable-v1).
- Get the user's clipboard contents via [wlr-data-control](https://wayland.app/protocols/ext-data-control-v1).
- Create arbitrary fullscreen surfaces through [wlr-layer-shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1) that can steal the user's input, pretend to be a password entry, or lock the user out of their session.
- Kill a running lockscreen, create a new lock surface, and tell niri to unlock a locked session.
Anything with access to niri's [IPC](./IPC.md) socket can, among other things:
- Spawn a Wayland client which can do everything in the list above.
Anything with access to niri's D-Bus interfaces can, among other things:
- Record the user's screen via the screencast interface.
- Fully listen to and emulate input from the user's keyboard via the accessibility interface.
Also, while niri doesn't directly integrate Xwayland, it's worth reminding that anything with access to the X11 `$DISPLAY` (which comes both as a socket file on disk **and** as an abstract socket in the network namespace) can intercept and emulate all input and record the contents of any X11 windows on the same `$DISPLAY` (but not Wayland windows).
## Running untrusted clients
Considering all of the above, for running untrusted clients, you need a proper sandbox that:
- Removes niri's IPC socket.
- Prevents D-Bus access to host services.
- Uses a filtered Wayland socket.
For creating a filtered Wayland socket, you can use the [security-context](https://wayland.app/protocols/security-context-v1) protocol which niri implements.
All unsafe protocols are made inaccessible through this filtered Wayland socket.
One sandbox that satisfies all of these criteria is the [Flatpak](https://flatpak.org/) sandbox.
Importantly, filtering just the Wayland socket (and leaving, for example, unrestricted D-Bus access) is **not enough** to prevent untrusted clients from doing bad things.
## Lock screen
When the session is locked via [ext-session-lock](https://wayland.app/protocols/ext-session-lock-v1), most actions (keybindings) are automatically disabled.
Only a very small set of safe actions is allowed.
In particular, spawning will not work, with the exception of binds explicitly configured with `allow-when-locked=true`.
Importantly, the **quit** action is allowed—you can always quit niri, even when on a lock screen.
Therefore, you must ensure that quitting niri does not drop you into an unprotected TTY commandline.
Usually, a display manager, like GDM, will do this for you: when niri exits (via the quit bind or if it crashes), it'll put you back into a safe password prompt.
Other than quitting, the only way to exit a lock screen is for the lock screen client to tell niri to unlock the session.
If the lock screen client crashes, the session remains locked with a solid red background.
In this case, another lock screen client can take over (so you can start a fresh lock screen if it crashes, and still unlock your session).
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` |
| --- | --- |
|  |  |
You can apply background effects to windows and layer-shell surfaces.
These include blur, xray, saturation, and noise.
They can be enabled in the `background-effect {}` section of [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rules.

The window needs to be semitransparent for you to see the background effect (otherwise it's fully covered by the opaque window).
Focus ring and border can also cover the background effect, see [this FAQ entry](./FAQ.md#why-are-transparent-windows-tinted-why-is-the-borderfocus-ring-showing-up-through-semitransparent-windows) for how to change this.
### Blur
Windows and layer surfaces can request their background to be blurred via the [`ext-background-effect` protocol](https://wayland.app/protocols/ext-background-effect-v1).
In this case, the application will usually offer some "background blur" setting that you'll need to enable in its configuration.
You can also enable blur on the niri side with the `blur true` background effect window rule:
```kdl
// Enable blur behind the Alacritty terminal.
window-rule{
matchapp-id="^Alacritty$"
background-effect{
blurtrue
}
}
// Enable blur behind the fuzzel launcher.
layer-rule{
matchnamespace="^launcher$"
background-effect{
blurtrue
}
}
```
Blur enabled via the window rule will follow the window corner radius set via [`geometry-corner-radius`](./Configuration:-Window-Rules.md#geometry-corner-radius).
On the other hand, blur enabled through `ext-background-effect` will exactly follow the shape requested by the window.
If the window or layer has clientside rounded corners or other complex shape, it should set a corresponding blur shape through `ext-background-effect`, then it will get correctly shaped background blur without any manual niri configuration.
Windows can also blur their pop-up menus using `ext-background-effect`.
On the niri side, you can do it with a `popups` block inside [`window-rule`](./Configuration:-Window-Rules.md#popups) and [`layer-rule`](./Configuration:-Layer-Rules.md#popups).
See those wiki pages for examples and limitations.
Global blur settings are configured in the [`blur {}` config section](./Configuration:-Miscellaneous.md#blur) and apply to all background blur.
### Xray
Xray makes the window background "see through" to your wallpaper, ignoring any other windows below.
You can enable it with `xray true` background effect [window](./Configuration:-Window-Rules.md#background-effect) or [layer](./Configuration:-Layer-Rules.md#background-effect) rule.
Xray is automatically enabled by default if any other background effect (like blur) is active.
This is because it's much more efficient: with xray active, niri only needs to blur the background once, and then can reuse this blurred version with no extra work (since the wallpaper changes very rarely).
If you have an animated wallpaper, xray will still have to recompute blur every frame, but that happens once and shared among all windows, rather than recomputed separately for each window.
#### Non-xray effects (experimental)
You can disable xray with `xray false` background effect window rule.
This gives you the normal kind of blur where everything below a window is blurred.
Keep in mind that non-xray blur and other non-xray effects are more expensive as niri has to recompute them any time you move the window, or the contents underneath change.
> [!WARNING]
> Non-xray effects are currently experimental because they have some known limitations.
>
> - They disappear during window open/close animations and while dragging a tiled window.
> Fixing this requires a refactor to the niri rendering code to defer offscreen rendering, and possibly other refactors.
### Implementation notes
The `ext-background-effect` protocol supports any wl_surface.
We currently implement it only for toplevels, layer surfaces, and pop-ups, which should cover the vast majority of what's actually used by applications.
For pop-ups, effects default to *non-xray* because pop-ups generally appear on top of windows.
In particular, the following surface types don't support `ext-background-effect`.
They can be implemented as the need arises.
- Subsurfaces. Would require implementing `clip-to-geometry` support for background effects.
- Lock surfaces. Not useful as it would just show our red locked session background.
- Cursor and drag-and-drop icon.
The main challenge here will be screencasts where the cursor is rendered separately.
This is problematic because non-xray effects require rendering the whole scene in one go rather than separately.
<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.
However, there are multiple solutions to running X11 apps in niri.
## Using xwayland-satellite
[xwayland-satellite] is a new project that essentially implements rootless Xwayland in a separate application, without the host compositor's involvement.
While it is still somewhat experimental, it handles a lot of applications correctly, like Steam, games and Discord.
<sup>Since: 25.08</sup> Niri integrates with [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) out of the box.
Ensure xwayland-satellite >= 0.7 is installed and available in `$PATH`.
With no further configuration, niri will create X11 sockets on disk, export `$DISPLAY`, and spawn xwayland-satellite on-demand when an X11 client connects.
If xwayland-satellite dies, niri will automatically restart it.
Build it according to instructions from its README, then run the `xwayland-satellite` binary.
Now you can start X11 applications on the X11 DISPLAY that it provides:
If you had a custom config which manually started `xwayland-satellite` and set `$DISPLAY`, you should remove those customizations for the automatic integration to work.
```
env DISPLAY=:0 flatpak run com.valvesoftware.Steam
To check that the integration works, verify that the niri output says something like `listening on X11 socket: :0`:
```sh
$ journalctl --user-unit=niri -b
systemd[2338]: Starting niri.service - A scrollable-tiling Wayland compositor...
niri[2474]: 2025-08-29T04:07:40.043402Z INFO niri: starting version 25.05.1 (0.0.git.2345.d9833fc1)
(...)
niri[2474]: 2025-08-29T04:07:40.690512Z INFO niri: listening on Wayland socket: wayland-1
niri[2474]: 2025-08-29T04:07:40.690520Z INFO niri: IPC listening on: /run/user/1000/niri.wayland-1.2474.sock
niri[2474]: 2025-08-29T04:07:40.700137Z INFO niri: listening on X11 socket: :0
systemd[2338]: Started niri.service - A scrollable-tiling Wayland compositor.
$ echo $DISPLAY
:0
```
They will appear as normal windows.

You can also set `DISPLAY` by default for all apps by adding it to the `environment` section of the niri config:
We're using xwayland-satellite rather than Xwayland directly because [X11 is very cursed](./FAQ.md#why-doesnt-niri-integrate-xwayland-like-other-compositors).
xwayland-satellite takes on the bulk of the work dealing with the X11 peculiarities from us, giving niri normal Wayland windows to manage.
```kdl
environment {
DISPLAY ":0"
}
```
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 and will need a nested compositor to run.
See sections below for how to do that.
> [!NOTE]
> 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` block.
>
> 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`
This method involves invoking XWayland directly and running it as its own window, it also requires an extra X11 window manager running inside it.


Here's how you do it:
@@ -107,6 +121,14 @@ Exec=cage -- flatpak run com.spotify.Client
Terminal=false
```
## Proton-GE native Wayland
It's possible to run some games as native Wayland clients, sidestepping the issues related to X11. You can do it with a custom version of Proton like [Proton-GE](https://github.com/GloriousEggroll/proton-ge-custom) by setting the `PROTON_ENABLE_WAYLAND=1` environmental variable in the game's launch parameters. Do note that for now this is an experimental feature, might not work with every game and might have its own issues.
```
PROTON_ENABLE_WAYLAND=1 %command%
```
## Using gamescope
You can use [gamescope](https://github.com/ValveSoftware/gamescope) to run X11 games and even Steam itself.
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)"
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"
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.