From 0c4af8d01da93cc35af74e7d29bd869cd8161edf Mon Sep 17 00:00:00 2001 From: Gabriel Monteiro de Souza Date: Sun, 30 Aug 2026 18:51:30 -0300 Subject: [PATCH 1/3] feat(switcher): add MRU order option --- assets/translations/en.json | 7 +- example.toml | 4 + src/config/config_types.h | 7 ++ src/config/schema/config_schema.cpp | 8 ++ src/shell/settings/settings_registry.cpp | 5 ++ src/shell/switcher/window_switcher.cpp | 103 ++++++++++++++++++++--- src/shell/switcher/window_switcher.h | 4 + 7 files changed, 126 insertions(+), 12 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index f4240284a3..e97742ec8d 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1257,7 +1257,8 @@ "wallpaper": "Wallpaper", "weather": "Weather", "widget-list": "Widget List", - "widgets": "Widgets" + "widgets": "Widgets", + "window-switcher": "Window Switcher" }, "sections": { "accessibility": "Accessibility", @@ -3059,6 +3060,10 @@ "description": "Copy the current wallpaper and colors to Noctalia Greeter (requires administrator approval)", "label": "Noctalia Greeter" }, + "window-switcher-mru": { + "description": "Order windows in the switcher by most recently used rather than workspace and screen layout", + "label": "Use MRU Order" + }, "telemetry": { "description": "Sends a small anonymous startup ping with version, OS, compositor, monitor resolutions, RAM, and UI scale", "label": "Anonymous Startup Ping" diff --git a/example.toml b/example.toml index c55ab8d635..13e37e87b7 100644 --- a/example.toml +++ b/example.toml @@ -104,6 +104,10 @@ prefix = "win" [shell.mpris] blacklist = [] # ignore MPRIS players by bus/identity/desktop entry token + +[shell.window_switcher] +mru = false # order windows by most recently used (Alt+Tab toggle) instead of workspace layout + # ── Wallpaper ───────────────────────────────────────────────────────────────── [wallpaper] diff --git a/src/config/config_types.h b/src/config/config_types.h index 21f1374c42..02c442382e 100644 --- a/src/config/config_types.h +++ b/src/config/config_types.h @@ -1061,6 +1061,12 @@ struct ShellConfig { bool operator==(const PrivacyConfig&) const = default; }; + struct WindowSwitcherConfig { + bool mru = false; + + bool operator==(const WindowSwitcherConfig&) const = default; + }; + float cornerRadiusScale = 1.0F; bool buttonBorders = true; bool inputBorders = true; @@ -1109,6 +1115,7 @@ struct ShellConfig { ShadowConfig shadow; PanelConfig panel; LauncherConfig launcher; + WindowSwitcherConfig windowSwitcher; KeyboardLayoutConfig keyboardLayout; ScreenCornersConfig screenCorners; MprisConfig mpris; diff --git a/src/config/schema/config_schema.cpp b/src/config/schema/config_schema.cpp index e077efbd04..fb274d80d5 100644 --- a/src/config/schema/config_schema.cpp +++ b/src/config/schema/config_schema.cpp @@ -1373,6 +1373,13 @@ namespace noctalia::config::schema { return s; } + const Schema& shellWindowSwitcherSchema() { + static const Schema s = { + field(&ShellConfig::WindowSwitcherConfig::mru, "mru"), + }; + return s; + } + const Schema& shellScreenCornersSchema() { static const Schema s = { field(&ShellConfig::ScreenCornersConfig::enabled, "enabled"), @@ -1562,6 +1569,7 @@ namespace noctalia::config::schema { subTable(&ShellConfig::panel, "panel", shellPanelSchema()), subTable(&ShellConfig::launcher, "launcher", shellLauncherSchema()), subTable(&ShellConfig::keyboardLayout, "keyboard_layout", shellKeyboardLayoutSchema()), + subTable(&ShellConfig::windowSwitcher, "window_switcher", shellWindowSwitcherSchema()), subTable(&ShellConfig::screenCorners, "screen_corners", shellScreenCornersSchema()), subTable(&ShellConfig::mpris, "mpris", shellMprisSchema()), subTable(&ShellConfig::screenshot, "screenshot", shellScreenshotSchema()), diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index e38e241756..e3c38023a3 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -1917,6 +1917,11 @@ namespace settings { tr("settings.schema.shell.screenshot-pipe-to-command.description"), {"shell", "screenshot", "pipe_to_command"}, ToggleSetting{cfg.shell.screenshot.pipeToCommand}, "screenshot capture pipe command stdin" )); + entries.push_back(makeEntry( + SettingsSection::Shell, "window-switcher", tr("settings.schema.shell.window-switcher-mru.label"), + tr("settings.schema.shell.window-switcher-mru.description"), {"shell", "window_switcher", "mru"}, + ToggleSetting{cfg.shell.windowSwitcher.mru}, "window switcher alt tab mru most recently used" + )); { auto e = makeEntry( SettingsSection::Shell, "screenshot", tr("settings.schema.shell.screenshot-pipe-command.label"), diff --git a/src/shell/switcher/window_switcher.cpp b/src/shell/switcher/window_switcher.cpp index e9acdff6e7..c5f32e5c7d 100644 --- a/src/shell/switcher/window_switcher.cpp +++ b/src/shell/switcher/window_switcher.cpp @@ -224,6 +224,16 @@ namespace { return {}; } + [[nodiscard]] std::string currentFocusedWindowKey(const CompositorPlatform& platform) { + if (const auto focusedId = platform.focusedCompositorWindowId(); focusedId.has_value() && !focusedId->empty()) { + return canonicalWindowId(*focusedId); + } + if (const auto active = platform.activeToplevel(); active.has_value() && active->handle != nullptr) { + return "handle:" + std::to_string(reinterpret_cast(active->handle)); + } + return {}; + } + [[nodiscard]] std::uintptr_t resolveCloseHandle( const CompositorPlatform& platform, std::string_view windowId, std::string_view appId, std::string_view title ) { @@ -274,6 +284,8 @@ namespace { std::int32_t sortX = 0; std::int32_t sortY = 0; std::uint64_t toplevelOrder = 0; + // Initialize to max so non-MRU items gracefully fall to the back + std::size_t mruIndex = std::numeric_limits::max(); }; [[nodiscard]] WindowSwitcherEntry makeEntryFromAssignment( @@ -329,7 +341,8 @@ namespace { void buildWindowEntries( const CompositorPlatform& platform, IconResolver& iconResolver, int iconSize, - std::vector& out, const std::optional& focusedId + std::vector& out, const std::optional& focusedId, bool useMru = false, + const std::deque& mruKeys = {} ) { std::unordered_map assignmentById; assignmentById.reserve(32); @@ -351,11 +364,26 @@ namespace { std::vector candidates; candidates.reserve(assignmentById.size() + liveToplevelById.size()); + std::unordered_map mruRanks; + if (useMru) { + mruRanks.reserve(mruKeys.size()); + for (std::size_t i = 0; i < mruKeys.size(); ++i) { + mruRanks.try_emplace(mruKeys[i], i); + } + } + auto addCandidate = [&](WindowSwitcherCandidate candidate, const std::string& key) { if (key.empty() || seenKeys.contains(key)) { return; } seenKeys.insert(key); + + if (useMru) { + const std::string idKey = identityKeyForEntry(candidate.entry); + if (auto it = mruRanks.find(idKey); it != mruRanks.end()) { + candidate.mruIndex = it->second; + } + } candidates.push_back(std::move(candidate)); }; @@ -391,7 +419,23 @@ namespace { addCandidate(std::move(candidate), key); } + std::optional focusedKey; + if (focusedId.has_value()) { + focusedKey = canonicalWindowId(*focusedId); + if (focusedKey->empty()) { + focusedKey = *focusedId; + } + } + if (!focusedKey.has_value()) { + if (const auto active = platform.activeToplevel(); active.has_value() && active->handle != nullptr) { + focusedKey = "handle:" + std::to_string(reinterpret_cast(active->handle)); + } + } + std::ranges::stable_sort(candidates, [](const WindowSwitcherCandidate& a, const WindowSwitcherCandidate& b) { + if (a.mruIndex != b.mruIndex) { + return a.mruIndex < b.mruIndex; + } if (a.workspaceKey != b.workspaceKey) { return a.workspaceKey < b.workspaceKey; } @@ -412,14 +456,6 @@ namespace { out.clear(); out.reserve(candidates.size()); - std::optional focusedKey; - if (focusedId.has_value()) { - focusedKey = canonicalWindowId(*focusedId); - if (focusedKey->empty()) { - focusedKey = *focusedId; - } - } - if (focusedKey.has_value()) { for (auto it = candidates.begin(); it != candidates.end(); ++it) { const std::string key = identityKeyForEntry(it->entry); @@ -577,8 +613,33 @@ void WindowSwitcher::onOutputChange() { } } +void WindowSwitcher::recordFocusedWindow() { + if (m_platform == nullptr) { + return; + } + const bool useMru = (m_config != nullptr) ? m_config->config().shell.windowSwitcher.mru : false; + if (useMru) { + promoteMruKey(currentFocusedWindowKey(*m_platform)); + } +} + +void WindowSwitcher::promoteMruKey(const std::string& key) { + if (key.empty()) { + return; + } + auto it = std::ranges::find(m_mruKeys, key); + if (it != m_mruKeys.end()) { + if (it != m_mruKeys.begin()) { + std::rotate(m_mruKeys.begin(), it, it + 1); + } + } else { + m_mruKeys.insert(m_mruKeys.begin(), key); + } +} + void WindowSwitcher::onToplevelChange() { if (!m_active) { + recordFocusedWindow(); return; } const std::size_t previousCount = m_windows.size(); @@ -595,6 +656,9 @@ void WindowSwitcher::show(wl_output* output) { } const bool wasActive = m_active; + if (!wasActive) { + recordFocusedWindow(); + } refreshWindows(); m_output = output; @@ -640,9 +704,25 @@ void WindowSwitcher::refreshWindows() { } } + const bool useMru = (m_config != nullptr) ? m_config->config().shell.windowSwitcher.mru : false; + IconResolver iconResolver; const int iconSize = 96; - buildWindowEntries(*m_platform, iconResolver, iconSize, m_windows, m_platform->focusedCompositorWindowId()); + buildWindowEntries( + *m_platform, iconResolver, iconSize, m_windows, m_platform->focusedCompositorWindowId(), useMru, m_mruKeys + ); + + if (useMru) { + std::unordered_set currentKeys; + currentKeys.reserve(m_windows.size()); + for (const auto& entry : m_windows) { + const std::string k = identityKeyForEntry(entry); + if (!k.empty()) { + currentKeys.insert(k); + } + } + std::erase_if(m_mruKeys, [&](const std::string& k) { return !currentKeys.contains(k); }); + } if (selectedKey.has_value()) { for (std::size_t i = 0; i < m_windows.size(); ++i) { @@ -704,10 +784,11 @@ void WindowSwitcher::activateSelected() { if (m_platform == nullptr || m_windows.empty() || m_selectedIndex >= m_windows.size()) { return; } + const WindowSwitcherEntry entry = m_windows[m_selectedIndex]; + promoteMruKey(identityKeyForEntry(entry)); // Hyprland ignores zwlr_foreign_toplevel_handle_v1.activate while an exclusive // keyboard layer-shell surface is mapped (hyprwm/Hyprland#4829). Snapshot the // selection, tear the overlay down, then activate on the next loop tick. - const WindowSwitcherEntry entry = m_windows[m_selectedIndex]; CompositorPlatform* platform = m_platform; hide(); DeferredCall::callLater([platform, entry]() { activateWindowSwitcherEntry(*platform, entry); }); diff --git a/src/shell/switcher/window_switcher.h b/src/shell/switcher/window_switcher.h index 22d42e55ef..2f42d4e78d 100644 --- a/src/shell/switcher/window_switcher.h +++ b/src/shell/switcher/window_switcher.h @@ -4,6 +4,7 @@ #include "wayland/wayland_seat.h" #include +#include #include #include #include @@ -55,6 +56,8 @@ class WindowSwitcher { void buildScene(Instance& instance, std::uint32_t width, std::uint32_t height); void positionGrid(Instance& instance, float screenW, float screenH); void syncGridSelection(); + void recordFocusedWindow(); + void promoteMruKey(const std::string& key); WaylandConnection* m_wayland = nullptr; RenderContext* m_renderContext = nullptr; @@ -64,6 +67,7 @@ class WindowSwitcher { Instance* m_instance = nullptr; std::vector m_windows; + std::deque m_mruKeys; std::size_t m_selectedIndex = 0; std::size_t m_gridColumns = 5; wl_output* m_output = nullptr; From 4d38b29e3c0d898039e0a5282839f1ef80d03546 Mon Sep 17 00:00:00 2001 From: Gabriel Monteiro de Souza Date: Sun, 30 Aug 2026 19:11:09 -0300 Subject: [PATCH 2/3] feat(switcher): add MRU option on docs --- docs/user/configuration/shell.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user/configuration/shell.mdx b/docs/user/configuration/shell.mdx index 33a9de1a88..a6989901aa 100644 --- a/docs/user/configuration/shell.mdx +++ b/docs/user/configuration/shell.mdx @@ -161,6 +161,9 @@ command = "swaylock -f" [shell.mpris] blacklist = [] # optional list of players to hide from media widgets/control-center +[shell.window_switcher] +mru = false # order windows by most recently used (Alt+Tab toggle) instead of workspace layout + [shell.privacy] mic_filter_regex = "" # ignore matching microphone application names cam_filter_regex = "" # ignore matching camera process names From 06ceb7aae03f766d2713298b2fafd7340e9c3de1 Mon Sep 17 00:00:00 2001 From: Lemmy Date: Tue, 1 Sep 2026 22:27:55 -0400 Subject: [PATCH 3/3] fix(switcher): key MRU order to real window identity --- assets/translations/en.json | 8 +- docs/user/configuration/shell.mdx | 2 +- example.toml | 3 +- src/shell/settings/settings_registry.cpp | 10 +- src/shell/switcher/window_switcher.cpp | 121 ++++++++++++----------- src/shell/switcher/window_switcher.h | 1 + 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index e97742ec8d..7e61facd9b 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -3060,10 +3060,6 @@ "description": "Copy the current wallpaper and colors to Noctalia Greeter (requires administrator approval)", "label": "Noctalia Greeter" }, - "window-switcher-mru": { - "description": "Order windows in the switcher by most recently used rather than workspace and screen layout", - "label": "Use MRU Order" - }, "telemetry": { "description": "Sends a small anonymous startup ping with version, OS, compositor, monitor resolutions, RAM, and UI scale", "label": "Anonymous Startup Ping" @@ -3071,6 +3067,10 @@ "time-format": { "description": "Default time format for shell UI without its own setting", "label": "Time Format" + }, + "window-switcher-mru": { + "description": "Order windows in the switcher by most recently used rather than workspace and screen layout", + "label": "Most Recently Used Order" } }, "system": { diff --git a/docs/user/configuration/shell.mdx b/docs/user/configuration/shell.mdx index a6989901aa..de8ef586c4 100644 --- a/docs/user/configuration/shell.mdx +++ b/docs/user/configuration/shell.mdx @@ -162,7 +162,7 @@ command = "swaylock -f" blacklist = [] # optional list of players to hide from media widgets/control-center [shell.window_switcher] -mru = false # order windows by most recently used (Alt+Tab toggle) instead of workspace layout +mru = false # order windows by most recently used (Alt+Tab) instead of workspace layout [shell.privacy] mic_filter_regex = "" # ignore matching microphone application names diff --git a/example.toml b/example.toml index 13e37e87b7..803dbc116b 100644 --- a/example.toml +++ b/example.toml @@ -104,9 +104,8 @@ prefix = "win" [shell.mpris] blacklist = [] # ignore MPRIS players by bus/identity/desktop entry token - [shell.window_switcher] -mru = false # order windows by most recently used (Alt+Tab toggle) instead of workspace layout +mru = false # order windows by most recently used (Alt+Tab) instead of workspace layout # ── Wallpaper ───────────────────────────────────────────────────────────────── diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index e3c38023a3..0095ac14bc 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -1917,11 +1917,6 @@ namespace settings { tr("settings.schema.shell.screenshot-pipe-to-command.description"), {"shell", "screenshot", "pipe_to_command"}, ToggleSetting{cfg.shell.screenshot.pipeToCommand}, "screenshot capture pipe command stdin" )); - entries.push_back(makeEntry( - SettingsSection::Shell, "window-switcher", tr("settings.schema.shell.window-switcher-mru.label"), - tr("settings.schema.shell.window-switcher-mru.description"), {"shell", "window_switcher", "mru"}, - ToggleSetting{cfg.shell.windowSwitcher.mru}, "window switcher alt tab mru most recently used" - )); { auto e = makeEntry( SettingsSection::Shell, "screenshot", tr("settings.schema.shell.screenshot-pipe-command.label"), @@ -1937,6 +1932,11 @@ namespace settings { e.visibleWhen = [](const Config& c) { return c.shell.screenshot.pipeToCommand; }; entries.push_back(std::move(e)); } + entries.push_back(makeEntry( + SettingsSection::Shell, "window-switcher", tr("settings.schema.shell.window-switcher-mru.label"), + tr("settings.schema.shell.window-switcher-mru.description"), {"shell", "window_switcher", "mru"}, + ToggleSetting{cfg.shell.windowSwitcher.mru}, "window switcher alt tab mru most recently used" + )); entries.push_back(makeEntry( SettingsSection::Osd, "osd", tr("settings.schema.shell.osd-enabled.label"), tr("settings.schema.shell.osd-enabled.description"), {"osd", "enabled"}, ToggleSetting{cfg.osd.enabled}, diff --git a/src/shell/switcher/window_switcher.cpp b/src/shell/switcher/window_switcher.cpp index c5f32e5c7d..ff24516005 100644 --- a/src/shell/switcher/window_switcher.cpp +++ b/src/shell/switcher/window_switcher.cpp @@ -34,6 +34,8 @@ #include #include +#include +#include #include #include #include @@ -225,13 +227,11 @@ namespace { } [[nodiscard]] std::string currentFocusedWindowKey(const CompositorPlatform& platform) { - if (const auto focusedId = platform.focusedCompositorWindowId(); focusedId.has_value() && !focusedId->empty()) { - return canonicalWindowId(*focusedId); - } - if (const auto active = platform.activeToplevel(); active.has_value() && active->handle != nullptr) { - return "handle:" + std::to_string(reinterpret_cast(active->handle)); + const auto focusedId = platform.focusedCompositorWindowId(); + if (!focusedId.has_value()) { + return {}; } - return {}; + return canonicalWindowId(*focusedId); } [[nodiscard]] std::uintptr_t resolveCloseHandle( @@ -284,7 +284,7 @@ namespace { std::int32_t sortX = 0; std::int32_t sortY = 0; std::uint64_t toplevelOrder = 0; - // Initialize to max so non-MRU items gracefully fall to the back + // Windows with no MRU rank sort after every ranked window. std::size_t mruIndex = std::numeric_limits::max(); }; @@ -339,10 +339,29 @@ namespace { } } + // Identity keys of every window the switcher can list right now. Compositors reuse + // window ids (Hyprland reuses addresses), so MRU ranks must expire with the window. + [[nodiscard]] std::unordered_set liveWindowKeys(const CompositorPlatform& platform) { + std::unordered_set keys; + keys.reserve(32); + for (const auto& assignment : platform.workspaceWindowAssignments()) { + if (std::string key = canonicalWindowId(assignment.windowId); !key.empty()) { + keys.insert(std::move(key)); + } + } + + std::unordered_map liveToplevelById; + indexLiveToplevelsByWindowId(platform, liveToplevelById); + for (const auto& live : liveToplevelById) { + keys.insert(live.first); + } + return keys; + } + void buildWindowEntries( const CompositorPlatform& platform, IconResolver& iconResolver, int iconSize, - std::vector& out, const std::optional& focusedId, bool useMru = false, - const std::deque& mruKeys = {} + std::vector& out, const std::optional& focusedId, + const std::deque* mruKeys ) { std::unordered_map assignmentById; assignmentById.reserve(32); @@ -364,11 +383,12 @@ namespace { std::vector candidates; candidates.reserve(assignmentById.size() + liveToplevelById.size()); + // Empty while MRU ordering is off, which leaves every candidate at rank max. std::unordered_map mruRanks; - if (useMru) { - mruRanks.reserve(mruKeys.size()); - for (std::size_t i = 0; i < mruKeys.size(); ++i) { - mruRanks.try_emplace(mruKeys[i], i); + if (mruKeys != nullptr) { + mruRanks.reserve(mruKeys->size()); + for (std::size_t i = 0; i < mruKeys->size(); ++i) { + mruRanks.try_emplace((*mruKeys)[i], i); } } @@ -377,12 +397,8 @@ namespace { return; } seenKeys.insert(key); - - if (useMru) { - const std::string idKey = identityKeyForEntry(candidate.entry); - if (auto it = mruRanks.find(idKey); it != mruRanks.end()) { - candidate.mruIndex = it->second; - } + if (const auto rank = mruRanks.find(key); rank != mruRanks.end()) { + candidate.mruIndex = rank->second; } candidates.push_back(std::move(candidate)); }; @@ -419,19 +435,6 @@ namespace { addCandidate(std::move(candidate), key); } - std::optional focusedKey; - if (focusedId.has_value()) { - focusedKey = canonicalWindowId(*focusedId); - if (focusedKey->empty()) { - focusedKey = *focusedId; - } - } - if (!focusedKey.has_value()) { - if (const auto active = platform.activeToplevel(); active.has_value() && active->handle != nullptr) { - focusedKey = "handle:" + std::to_string(reinterpret_cast(active->handle)); - } - } - std::ranges::stable_sort(candidates, [](const WindowSwitcherCandidate& a, const WindowSwitcherCandidate& b) { if (a.mruIndex != b.mruIndex) { return a.mruIndex < b.mruIndex; @@ -456,6 +459,14 @@ namespace { out.clear(); out.reserve(candidates.size()); + std::optional focusedKey; + if (focusedId.has_value()) { + focusedKey = canonicalWindowId(*focusedId); + if (focusedKey->empty()) { + focusedKey = *focusedId; + } + } + if (focusedKey.has_value()) { for (auto it = candidates.begin(); it != candidates.end(); ++it) { const std::string key = identityKeyForEntry(it->entry); @@ -613,27 +624,30 @@ void WindowSwitcher::onOutputChange() { } } +bool WindowSwitcher::mruEnabled() const { return m_config != nullptr && m_config->config().shell.windowSwitcher.mru; } + void WindowSwitcher::recordFocusedWindow() { - if (m_platform == nullptr) { + if (m_platform == nullptr || !mruEnabled()) { return; } - const bool useMru = (m_config != nullptr) ? m_config->config().shell.windowSwitcher.mru : false; - if (useMru) { - promoteMruKey(currentFocusedWindowKey(*m_platform)); - } + promoteMruKey(currentFocusedWindowKey(*m_platform)); } void WindowSwitcher::promoteMruKey(const std::string& key) { - if (key.empty()) { + if (key.empty() || m_platform == nullptr) { return; } + + const std::unordered_set live = liveWindowKeys(*m_platform); + std::erase_if(m_mruKeys, [&](const std::string& existing) { return existing != key && !live.contains(existing); }); + auto it = std::ranges::find(m_mruKeys, key); - if (it != m_mruKeys.end()) { - if (it != m_mruKeys.begin()) { - std::rotate(m_mruKeys.begin(), it, it + 1); - } - } else { + if (it == m_mruKeys.end()) { m_mruKeys.insert(m_mruKeys.begin(), key); + return; + } + if (it != m_mruKeys.begin()) { + std::rotate(m_mruKeys.begin(), it, it + 1); } } @@ -704,26 +718,13 @@ void WindowSwitcher::refreshWindows() { } } - const bool useMru = (m_config != nullptr) ? m_config->config().shell.windowSwitcher.mru : false; - IconResolver iconResolver; const int iconSize = 96; buildWindowEntries( - *m_platform, iconResolver, iconSize, m_windows, m_platform->focusedCompositorWindowId(), useMru, m_mruKeys + *m_platform, iconResolver, iconSize, m_windows, m_platform->focusedCompositorWindowId(), + mruEnabled() ? &m_mruKeys : nullptr ); - if (useMru) { - std::unordered_set currentKeys; - currentKeys.reserve(m_windows.size()); - for (const auto& entry : m_windows) { - const std::string k = identityKeyForEntry(entry); - if (!k.empty()) { - currentKeys.insert(k); - } - } - std::erase_if(m_mruKeys, [&](const std::string& k) { return !currentKeys.contains(k); }); - } - if (selectedKey.has_value()) { for (std::size_t i = 0; i < m_windows.size(); ++i) { const std::string key = identityKeyForEntry(m_windows[i]); @@ -785,7 +786,9 @@ void WindowSwitcher::activateSelected() { return; } const WindowSwitcherEntry entry = m_windows[m_selectedIndex]; - promoteMruKey(identityKeyForEntry(entry)); + if (mruEnabled()) { + promoteMruKey(identityKeyForEntry(entry)); + } // Hyprland ignores zwlr_foreign_toplevel_handle_v1.activate while an exclusive // keyboard layer-shell surface is mapped (hyprwm/Hyprland#4829). Snapshot the // selection, tear the overlay down, then activate on the next loop tick. diff --git a/src/shell/switcher/window_switcher.h b/src/shell/switcher/window_switcher.h index 2f42d4e78d..28659d5aa0 100644 --- a/src/shell/switcher/window_switcher.h +++ b/src/shell/switcher/window_switcher.h @@ -56,6 +56,7 @@ class WindowSwitcher { void buildScene(Instance& instance, std::uint32_t width, std::uint32_t height); void positionGrid(Instance& instance, float screenW, float screenH); void syncGridSelection(); + [[nodiscard]] bool mruEnabled() const; void recordFocusedWindow(); void promoteMruKey(const std::string& key);