Skip to content

Conversation

@rustn00b
Copy link
Contributor

@rustn00b rustn00b commented May 31, 2025

This PR is an updated take on #977 which it supersedes. It
provides a UI to navigate currently open windows sorted in most-recently-used
order (MRU). At present it is not yet ready for prime time, most notably
because features like animations are missing. However (imho) it has proven to
be quite usable!

Because it includes a separate UI to navigate thumbnails of the open windows, it
should not suffer from the same shortcoming as #977 where the latter would mess
up the current disposition of tabs and workspaces because it relied on actual
focus changes to perform the MRU traversal.

Here's the list of what is supported:

  • activate the MRU UI using either Alt-Tab or Alt-Grave, the former traverses
    among all open windows ("None" filter), while the latter only traverses
    windows that share the same app-id as the currently focused window ("AppId"
    filter).

    Each window is represented by a thumbnail version of the original.

    Adding Shift to the key binding makes the traversal happen in the opposite
    order.

  • when the UI is opened:

    • regular key bindings are filtered.

    • using Alt-Tab or Alt-Grave allow switching.
      traversal modes on the fly. Toggling the AppId filter uses the app-id
      of the current MRU selection (as opposed to the window that had the focus
      when the UI was opened).

    • releasing the Alt key transfers the focus to whatever thumbnail was
      selected in the MRU UI.

      Note: This implies that using key bindings that don't use the Alt modifier
      won't behave quite as expected because the UI will not close when they
      are released. So for now at least it's Alt-* only.

    • Esc closes the MRU UI and returns focus to the
      window that originally held it when the UI was invoked.

    • q closes the window/application for the currently selected
      thumbnail.

    • a, o or w change the "scope" of the windows shown in the MRU UI:

      • a: consider all open windows
      • o: consider only windows on the current Output
      • w: consider only windows in the current Workspace
    • s Cycle through "scopes" (All/Workspace/Output)

    • j/k and Left/Right can also be used to navigate thumbnails.

    • Home/End jump to the first (respectively last) thumbnail.

Key bindings are all hard coded (except for a configurable common modifier key).
Those that open the MRU UI (Alt-Tab, Alt-Grave and their Shift-ed variants) use a lower binding priority so
they shouldn't interfere with bindings already set in the configuration file. The flip-side
is that no error is reported if an existing binding masks one of those of the MRU UI.

As of 414284c, the UI can be configured in the recent-windows section of the Niri configuration file:

  • The modifier key can be set to something other than "Alt" (the default), e.g. "Super"
  • The UI can be disabled with the off child node.
recent-windows {
    // Uncomment to disable the recent windows interface
    // off

    // Choose the modifier key for the recent windows interface
    mod-key "super"

    // use thumbnail selection animation (expands selected thumbnail to selected window's position)
    // enable-selection-animation
}

Most glaring remaining todos:

  • Animations
  • Support for pointer actions, eg. clicking on thumbnail should close
    the UI and focus the corresponding window.
  • Allow the UI key bindings to be disabled
  • Configurable modifier key for the hard-coded keybinds, e.g. <MOD> + Tab
  • Add a threshold duration for open-to-close of the MRU UI. If the threshold isn't reached, skip displaying
    the thumbnail selection animation.
  • Add navigation bindings for H and L
  • Fix scope changes causing motion of the selected thumbnail when there are enough other tiles to fill the display to either side.
  • Don't transfer the UI to a different monitor if the mouse is moved there.
  • Rework how the window border is rendered for thumbnails.
  • Rework shortcuts and mechanism to cycle through scope filters (All/Workspace/Output)
  • Adjust thumbnail size for the display where the MRU UI gets displayed instead of simply using 1/2 size.
  • Moving the mouse over thumbnails should update the focus ring.
  • Display all window titles instead of only the one for the focused thumbnail.
  • Close the UI on mouse click outside of a thumbnail.
  • Add explicit clipping to thumbnails

Despite all that, hope you all enjoy!

@rustn00b rustn00b marked this pull request as draft May 31, 2025 15:52
@jbms
Copy link

jbms commented May 31, 2025

It would be great for all the key bindings to be fully customizable.

Also it would be great if this could be fully controlled by ipc (e.g. list of window IDs to include in preview), so that you could have layer shell application that handles its own key bindings and then modifies the preview.

@rustn00b
Copy link
Contributor Author

rustn00b commented May 31, 2025

It would be great for all the key bindings to be fully customizable.

The tricky bit here is that the UI needs to be dismissed with the mod key (Alt for now) is released. Hard-coding the bindings side-steps that problem by just wiring the behavior directly in Niri. Something more flexible would involve figuring out what mod (mods?) were used to open the UI to then detect when they are released. Doable, but might be clunky, especially if there is a more general way to deal with key release bindings in the wings.

Wrt bindings used within the UI itself, either the config would need to support some form of "mode" or maybe a separate binding section would be needed for this UI. Idk.

Also it would be great if this could be fully controlled by ipc.

This shouldn't be too difficult, basically the UI first builds a list of window IDs when it first opens. To fit into what you describe, the only difference would be to allow that list to be fed from IPC.

Although these ideas are definitely interesting, I think there's more work to be done on just the base functionality before tackling more new stuff (at least on my side)!

@YaLTeR
Copy link
Owner

YaLTeR commented Jun 1, 2025

Before looking into the code, would it be feasible to have 1 commit with the previous MRU PR (that I reviewed) and then 1 commit with all the new stuff? So it's easier for me to review just the new changes.

basically the UI first builds a list of window IDs when it first opens

Does it work with windows opening/closing dynamically?

@rustn00b
Copy link
Contributor Author

rustn00b commented Jun 1, 2025

Does it work with windows opening/closing dynamically?

Yes, at least that is the intent. You can actually use the Alt-q from within the UI to close windows as you navigate.

would it be feasible to have 1 commit with the previous MRU PR (that I reviewed) and then 1 commit with all the new stuff.

Aside from the focus timestamp tracking, most of the MRU code has changed. I can try to create a branch from current main, apply/squash the old PR onto there and do that again with the new PR. From there do you mean that I should close the current PR and open a new one containing just those two commits?

@rustn00b
Copy link
Contributor Author

rustn00b commented Jun 1, 2025

I set up a new branch with just the two commits on top of 8ba57fc, the first matches the most recent state of #977, and the second has the rolled up changes for this PR. It is available here.

@YaLTeR, should I just close this PR and reopen another based on the "squashed" branch? Tell me whatever makes things simplest for you.

@YaLTeR
Copy link
Owner

YaLTeR commented Jun 1, 2025

Oh, if most of the code changed, then I guess that's not necessary. Thanks for the branch, I can use it as a reference, so just keep it up for now.

I see that you bumped MSRV from 1.80 to 1.83 and applied some clippy lints (is_none_or). Is that strictly necessary? I don't have a concrete MSRV policy for niri but if it's just something minor like is_none_or then I'd prefer keeping 1.80 MSRV for now. Regardless, those changes should be in a separate PR.

There's also one formatting change to window_matches() that I noticed, why did that happen?

@rustn00b
Copy link
Contributor Author

rustn00b commented Jun 1, 2025

I see that you bumped MSRV from 1.80 to 1.83 and applied some clippy lints (is_none_or). Is that strictly necessary?

It was needed to support ControlFlow::Break / ControlFlow::Continue, which made things look clearer than the version I came up with before.

Regardless, those changes should be in a separate PR.

So, I'll wait on your opinion regarding the appropriateness of using ControlFlow in this PR, and assuming you are in agreement, then I'll break out the MSRV part and do a separate PR.

There's also one formatting change to window_matches() that I noticed, why did that happen?

That was cargo-fmt, definitely nothing manual.

@rustn00b rustn00b mentioned this pull request Jun 2, 2025
@rustn00b
Copy link
Contributor Author

rustn00b commented Jun 2, 2025

Regardless, those changes should be in a separate PR.

Available in PR #1717

@rustn00b
Copy link
Contributor Author

ac4c5d3 adds some animations giving the whole thing a more polished feel. Ideally transitions out of the UI should make it more obvious how the focus transfers to the focused window. I'm leaning toward a resize + move animation to make things nice and obvious.

@rustn00b
Copy link
Contributor Author

rustn00b commented Jul 29, 2025

This last update adds a thumbnail selection animation that improves the UX as it gives a visual indication of where the window for the selected thumbnail will appear. For now, the selected thumbnail doesn't properly handle cases where the window is resized while the thumbnail is expanding (the animation skips directly to the requested aspect ratio instead of scaling smoothly) and it also doesn't display the focus ring. Implementing these would involve duplicating stuff already better done in tile.rs. Unfortunately I haven't managed to grok how to use the OffscreenBuffer properly, which seems like the way to do it right.

@madeleine313
Copy link

Hello.

May I ask whether an option to disable this feature would be available? My concern being that I cannot use Alt and Tab at the same time as it interferes with some software/clients I use, and being able to disable this feature would be a work-around for not being able to customise the modifier(s)/keybindings if the bindings end up being hardcoded.

I would personally like to be able to use Super + Tab (which also brings up the "alt-tab menu" in GNOME and if I remember correctly Plasma as well) instead of Alt + Tab.

Thank you for your work and time!

@rustn00b
Copy link
Contributor Author

rustn00b commented Aug 7, 2025

Hi there!

May I ask whether an option to disable this feature would be available?

Not a bad idea, one could easily imagine a user either not wanting interfering keybinds or just wanting to use some different external tool to navigate recently used windows. I'll push this onto the todo list.

I would personally like to be able to use Super + Tab

Amusingly the PR from which this one is derived did use Super + Tab, but the consensus on the comment thread at the time was that Alt + Tab was preferable! Anyway, this seems like a pretty good indication that a little time should be spent on making this at least somewhat configurable. One possible step in that direction would be to allow configuration of the modifier that opens the UI, e.g. <MOD> + Tab. Also added to the todos.

@graham00
Copy link

graham00 commented Aug 13, 2025

All this looks great!

Also it would be great if this could be fully controlled by ipc.

This shouldn't be too difficult, basically the UI first builds a list of window IDs when it first opens. To fit into what you describe, the only difference would be to allow that list to be fed from IPC.

Just to add to that - I heavily use a type-to-seek ipc<->fuzzel script to jump to specifically titled applications and/or named workspaces:

#!/usr/bin/env bash
windows_list=$(niri msg -j windows | jq -r '.[] | "\(.id)\t\(.title) (\(.app_id))"')
workspaces_list=$(niri msg -j workspaces | jq -r '.[] | select(.name != null) | "WS: \(.name)"')
selected_line=$(printf "%s\n%s" "$windows_list" "$workspaces_list" | fuzzel --dmenu --width=200 --lines=55)
if [ -n "$selected_line" ]; then
    if [[ "$selected_line" == "WS: "* ]]; then
        workspace_name=$(echo "$selected_line" | sed 's/^WS: //')
        niri msg action focus-workspace "$workspace_name"
    else
        window_id=$(echo "$selected_line" | cut -f1)
        niri msg action focus-window --id "$window_id"
    fi
fi

Having MRU data available via IPC would be a godsend.

@rustn00b
Copy link
Contributor Author

Having MRU data available via IPC would be a godsend.

30c5cf3 includes focus timestamp data in the the JSON output of niri msg windows (and in the event-stream). Timestamps are serialized using two fields, e.g.:

    "focus_timestamp": {
      "secs_since_epoch": 1755189524,
      "nanos_since_epoch": 71588761
    }

If for whatever reason a window never received focus, the focus_timestamp should be null (I haven't tested this), though Serde also offers the option to just skip the field in that case which may be a better solution. idk.

This isn't quite "full control over IPC", but it should still provide enough data to implement external MRU navigation.

@elcste
Copy link

elcste commented Aug 14, 2025

This is amazing!!! I tried sway a few years ago but missed a good MRU switcher and went back to Gnome. Alt+grave for same app is the icing on the cake and q to close is the cherry on top :-)

I know this is not complete, but my feedback is that the animation is a little much for me when quickly switching back and forth between two windows. It looks good when pressing tab multiple times to select a window. I think Gnome Shell has a timeout and doesn't show an animation for their switcher if it is invoked very quickly.

Thanks again!

@rustn00b
Copy link
Contributor Author

@elcste, that makes sense. Adding a threshold time to trigger animations shouldn't be too hard to do so I'll just add that to the Todos. Thanks for the comment!

@rustn00b
Copy link
Contributor Author

0a7df42 inhibits the thumbnail selection animation unless the MRU UI was open for longer than a threshold duration. The threshold is hard-coded to 250ms, if that settings turns out not to be comfortable for everyone then it could be migrated to the "recent-windows" configuration section. Until such a time, I'm partial to keeping config clutter under control.

@elcste
Copy link

elcste commented Aug 15, 2025

That's a great improvement. I might play with the timeout and try slightly longer. I'll report back if I really prefer it.

One other thing I've noticed: If I press Alt+Tab with only one window open – or Alt+Grave with only one window open of the same app – focus doesn't return correctly to the window. Clicking in the window doesn't help. I have have to do something outside that window, like switch workspaces, open fuzzel or invoke this switcher (like I can Alt+Tab if I got stuck on Alt+Grave). The apps I tried with were Alacritty, Ptyxis, Firefox and Sublime Text in case it matters.

@rustn00b
Copy link
Contributor Author

If I press Alt+Tab with only one window open – or Alt+Grave with only one window open of the same app – focus doesn't return correctly to the window

@elcste, I haven't run into this personally although I do use alacritty as my "test subject". My usual (manual...) tests include the cases you describe (calling the UI with 0, 1, several windows). It's also intriguing that even clicking in the window doesn't trigger a focus update. The only similar situation I've encountered is with niri-in-niri and switching windows in the "outer-niri" while a modifier key was active in the "inner-niri".

Is this something you've encountered with just the latest version from yesterday or is this something you've noticed in older versions?

@elcste
Copy link

elcste commented Aug 16, 2025

I think I noticed it before you added the animation threshold. I can't remember now if you had added the IPC commit or not.

A little more I noticed. It seems like it could relate to the Alt key still being active somehow? I'm seeing things that also happen if I hold the Alt key.

  • After using the switcher with one Ptyxis window, when I type I get the app error "visual bell" of invalid input.
  • In an app with a menu, the mnemonic letters are underlined.
    • In Sublime Text, the main menu bar. And focus seems to be stuck on the menubar, which I can flick
    • In Ptyxis and Gnome Disks if I press Alt+Grave with the app menus open, I see the mnemonic letters in those menus.

I use some xkb options that move around some modifier keys like Alt, but I get the same result with the stock config file.

@rustn00b
Copy link
Contributor Author

@elcste: Ah, thanks for those indications. With your description I was able to hit it too, and now I'm wondering how I could have missed noticing it up until then! The tldr is that while the Mod (e.g. Alt) key press event was sent to the focused app, the corresponding release event was getting intercepted. So 63de4d0 now forwards the release event and this should hopefully address the issue you were seeing.

@elcste
Copy link

elcste commented Aug 17, 2025

@rustn00b That must have been it – I'm not seeing it after your latest updates :-)

@Sempyos
Copy link
Contributor

Sempyos commented Aug 18, 2025

I've been playing with this for a while and have had no issues from my fiddling, great works !

@YaLTeR
Copy link
Owner

YaLTeR commented Aug 19, 2025

Haven't looked at the code yet, some feedback from 2 minutes of playing:

  1. Wow this is really cool! With anims and all even

  2. Wait you even got the window titles working, nice

  3. It should be H and L, not J and K

  4. Not sure about the Q. It's right next to Tab, and there's no confirmation, making it very easy to close a window by accident.

  5. The window shouldn't animate from within the Alt-Tab to its position on the workspace. Just fade out or close the menu, no extra anim necessary.

  6. The anim when switching between A-O-W modes is a bit jank, the windows move horizontally out of nowhere and other windows jump in.

  7. Pressing Alt-Tab shouldn't close the Overview, it should just draw over it.

  8. It should be sized relatively to the monitor logical height (like the Overview). Right now, switching from 2560x1440 to 1280x720, or equivalently changing scale from 1 to 2, results in a 2x taller Alt-Tab, when it should result in the same size Alt-Tab.

  9. Windows don't redraw in Alt-Tab as can be seen with weston-simple-egl, they should.

    • Pay attention to windows that change size like wleird-resize-loop. This should work fine.
  10. Mouse should be able to at least click to select the target window (and touch and tablet too).

    Right now, mouse seems to go "below" the Alt-Tab. I was actually planning to look into refactoring our Keyboard/PointerTarget to better support modal dialogs, I believe that would be useful for Alt-Tab too to solve this kind of issue.

  11. Moving mouse to another monitor switches the Alt-Tab to that monitor. This is fairly abrupt; I think it should stay where it was. Also, all monitors should probably be dimmed, not just the monitor with the Alt-Tab.

  12. Not sure how you draw the border in Alt-Tab, but it should definitely be thicker, and also it breaks on rounded corners:

    image

    I've also seen the bottom border to be missing, which leads me to believe that you draw the border on the original Tile then downscale it, kinda like the Overview. Instead, perhaps try drawing the border on the already-downscaled Tile so that the border itself is not scaled?

  13. Design-wise will need to play around and think more about it, remember what GNOME Shell does and what others do. Not prepared to give any feedback rn.

@yuukibarns
Copy link

I seem to find something not quite intuitive: when we switch from window A to an empty workspace below, then mru ui will still put the window A at the first, which means we will switch to window B after the window A in the list instead of more intuitive one, i.e., window A itself. However focus-window-previous does switch back to window A and then cycle between it and window B.

@YaLTeR
Copy link
Owner

YaLTeR commented Nov 15, 2025

I seem to find something not quite intuitive: when we switch from window A to an empty workspace below, then mru ui will still put the window A at the first, which means we will switch to window B after the window A in the list instead of more intuitive one, i.e., window A itself. However focus-window-previous does switch back to window A and then cycle between it and window B.

I did notice this today actually when reimplementing focus-window-previous. I cross-checked with GNOME, and their Alt-Tab also puts the window second, like niri currently does. I agree this feels less intuitive. I wonder what others do here like KDE or labwc?

@stefonarch
Copy link

Labwc doesn't change order if the switcher is opened on an empty workspace, kwin does a strange thing when not filtering per workspace: 3 windows, switching between 1 and 2 as most recent, when opening the switcher on the empty workspace 3 is selected...

@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

I additionally checked Windows. There, if you minimize all windows, Alt-Tab keeps the first window selected (doesn't switch next). But Alt-Shift-Tab also keeps the first window selected.

I think it's the most intuitive if in this case Alt-Tab keeps the first window, while Alt-Shift-Tab still selects the very last window. So that's what I just pushed in niri.

Also made scroll wheel binds work in the MRU (they are also in the "automatically picked up from normal binds" category, so make sure they're bound).

@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

Some more cleanups and fixes, incl. made tablet input work in the MRU and regular mouse binds get autoconverted too.

@YaLTeR YaLTeR marked this pull request as ready for review November 16, 2025 08:41
@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

Alright, everything's squashed now, should be good to merge. Unless some last minute issues come up.

@Vortriz
Copy link

Vortriz commented Nov 16, 2025

Tested with default config. Working without any hitches 👍

@yuukibarns
Copy link

I have a somewhat nuance feature request for the case when there is no window focused. Currently, the selected window will popup abruptly without any sliding animation but in the normal case there is a sliding animation from the right to the center.

rustn00b and others added 3 commits November 16, 2025 16:20
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
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
@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

Hmm? Not sure I understand

@yuukibarns
Copy link

Sorry for my bad English but I mean that when there is a focused window (the normal case), the switcher will arise as the second window slides in from the right to the center as if the switcher just appears in the middle of the sliding animation. But in the case when there is no focused window, the current behaviour is popping up directly without any animation. I hope there can also be some "slide from right to center" animation for the "no focused window" case.

@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

In the last commits I made it so that opening Alt-Tab from unfocused picks the first window rather than the second. That's why you don't see an animation. In general it's still there if you try Alt-Shift-Tab for example.

@yuukibarns
Copy link

Yes, I see but I mean that I wonder if we can add some slide animation even for the first window. I know this is not for everyone (that's why I call it "somewhat nuance"), but I think the current popup behavior of the first window is a little abrupt.

@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

Eh, idk. On other systems it also appears without animation. In niri (and GNOME) specifically there's already a 150 ms delay, so any further animation is adding even more delay until things "settle", I'd rather not probably.

@YaLTeR YaLTeR merged commit aecbd67 into YaLTeR:main Nov 16, 2025
13 checks passed
@YaLTeR
Copy link
Owner

YaLTeR commented Nov 16, 2025

Alright let's continue on main. Thanks again @rustn00b for all of the work, and everyone for intermediate testing!

@rustn00b
Copy link
Contributor Author

Thanks to all of those who chimed in here and made this happen!

edpyt added a commit to edpyt/dotfiles that referenced this pull request Nov 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.