From acee4e486088f370cfe2df4a0c7c83b7b53377aa Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 9 May 2026 13:51:34 +0200 Subject: [PATCH] fix(bar): reduce keyboard layout CPU usage --- .../hyprland/hyprland_keyboard_backend.cpp | 45 ++++++++++++++++--- .../sway/sway_keyboard_backend.cpp | 45 ++++++++++++++++--- .../bar/widgets/keyboard_layout_widget.cpp | 11 +---- .../bar/widgets/keyboard_layout_widget.h | 1 - 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/compositors/hyprland/hyprland_keyboard_backend.cpp b/src/compositors/hyprland/hyprland_keyboard_backend.cpp index 9b9a9469b..2a96b7495 100644 --- a/src/compositors/hyprland/hyprland_keyboard_backend.cpp +++ b/src/compositors/hyprland/hyprland_keyboard_backend.cpp @@ -3,6 +3,7 @@ #include "core/process.h" #include "util/string_utils.h" +#include #include #include #include @@ -11,6 +12,21 @@ namespace { + constexpr auto kCurrentLayoutCacheTtl = std::chrono::seconds(1); + + struct CurrentLayoutCache { + std::optional value; + std::chrono::steady_clock::time_point fetchedAt{}; + bool valid = false; + }; + + CurrentLayoutCache& currentLayoutCache() { + static CurrentLayoutCache cache; + return cache; + } + + void invalidateCurrentLayoutCache() { currentLayoutCache() = CurrentLayoutCache{}; } + [[nodiscard]] std::optional runAndCapture(const std::vector& args) { if (args.empty() || args.front().empty()) { return std::nullopt; @@ -75,7 +91,11 @@ bool HyprlandKeyboardBackend::cycleLayout() const { if (!m_enabled) { return false; } - return process::runSync({"hyprctl", "switchxkblayout", "all", "next"}); + const bool ok = process::runSync({"hyprctl", "switchxkblayout", "all", "next"}); + if (ok) { + invalidateCurrentLayoutCache(); + } + return ok; } std::optional HyprlandKeyboardBackend::layoutState() const { @@ -91,16 +111,29 @@ std::optional HyprlandKeyboardBackend::currentLayoutName() const { return std::nullopt; } + const auto now = std::chrono::steady_clock::now(); + auto& cache = currentLayoutCache(); + if (cache.valid && now - cache.fetchedAt < kCurrentLayoutCacheTtl) { + return cache.value; + } + + auto finish = [&](std::optional value) { + cache.value = std::move(value); + cache.fetchedAt = now; + cache.valid = true; + return cache.value; + }; + const auto payload = runAndCapture({"hyprctl", "devices", "-j"}); if (!payload.has_value() || payload->empty()) { - return std::nullopt; + return finish(std::nullopt); } try { const auto json = nlohmann::json::parse(*payload); const auto keyboardsIt = json.find("keyboards"); if (keyboardsIt == json.end() || !keyboardsIt->is_array()) { - return std::nullopt; + return finish(std::nullopt); } for (const auto& keyboard : *keyboardsIt) { if (!keyboard.is_object()) { @@ -111,12 +144,12 @@ std::optional HyprlandKeyboardBackend::currentLayoutName() const { } const std::string layout = keyboard.value("active_keymap", ""); if (!layout.empty() && layout != "error") { - return layout; + return finish(layout); } } } catch (const nlohmann::json::exception&) { - return std::nullopt; + return finish(std::nullopt); } - return std::nullopt; + return finish(std::nullopt); } diff --git a/src/compositors/sway/sway_keyboard_backend.cpp b/src/compositors/sway/sway_keyboard_backend.cpp index 3b9a24a13..855b7f631 100644 --- a/src/compositors/sway/sway_keyboard_backend.cpp +++ b/src/compositors/sway/sway_keyboard_backend.cpp @@ -3,6 +3,7 @@ #include "core/process.h" #include "util/string_utils.h" +#include #include #include #include @@ -11,6 +12,21 @@ namespace { + constexpr auto kCurrentLayoutCacheTtl = std::chrono::seconds(1); + + struct CurrentLayoutCache { + std::optional value; + std::chrono::steady_clock::time_point fetchedAt{}; + bool valid = false; + }; + + CurrentLayoutCache& currentLayoutCache() { + static CurrentLayoutCache cache; + return cache; + } + + void invalidateCurrentLayoutCache() { currentLayoutCache() = CurrentLayoutCache{}; } + [[nodiscard]] std::optional runAndCapture(const std::vector& args) { if (args.empty() || args.front().empty()) { return std::nullopt; @@ -75,7 +91,11 @@ bool SwayKeyboardBackend::cycleLayout() const { if (!isAvailable()) { return false; } - return process::runSync({m_msgCommand, "input", "type:keyboard", "xkb_switch_layout", "next"}); + const bool ok = process::runSync({m_msgCommand, "input", "type:keyboard", "xkb_switch_layout", "next"}); + if (ok) { + invalidateCurrentLayoutCache(); + } + return ok; } std::optional SwayKeyboardBackend::layoutState() const { @@ -91,15 +111,28 @@ std::optional SwayKeyboardBackend::currentLayoutName() const { return std::nullopt; } + const auto now = std::chrono::steady_clock::now(); + auto& cache = currentLayoutCache(); + if (cache.valid && now - cache.fetchedAt < kCurrentLayoutCacheTtl) { + return cache.value; + } + + auto finish = [&](std::optional value) { + cache.value = std::move(value); + cache.fetchedAt = now; + cache.valid = true; + return cache.value; + }; + const auto payload = runAndCapture({m_msgCommand, "-t", "get_inputs", "--raw"}); if (!payload.has_value() || payload->empty()) { - return std::nullopt; + return finish(std::nullopt); } try { const auto json = nlohmann::json::parse(*payload); if (!json.is_array()) { - return std::nullopt; + return finish(std::nullopt); } for (const auto& input : json) { if (!input.is_object()) { @@ -110,12 +143,12 @@ std::optional SwayKeyboardBackend::currentLayoutName() const { } const std::string layout = input.value("xkb_active_layout_name", ""); if (!layout.empty()) { - return layout; + return finish(layout); } } } catch (const nlohmann::json::exception&) { - return std::nullopt; + return finish(std::nullopt); } - return std::nullopt; + return finish(std::nullopt); } diff --git a/src/shell/bar/widgets/keyboard_layout_widget.cpp b/src/shell/bar/widgets/keyboard_layout_widget.cpp index d73b07d3f..9dd8ac865 100644 --- a/src/shell/bar/widgets/keyboard_layout_widget.cpp +++ b/src/shell/bar/widgets/keyboard_layout_widget.cpp @@ -23,7 +23,6 @@ namespace { constexpr Logger kLog("keyboard_layout_widget"); constexpr auto kRefreshTickInterval = std::chrono::milliseconds(40); constexpr int kRefreshBurstAttempts = 8; - constexpr auto kIdleProbeInterval = std::chrono::milliseconds(350); constexpr std::string_view kUnknownLabel = "--"; constexpr std::string_view kVerticalStableLabel = "WWW"; @@ -327,14 +326,8 @@ void KeyboardLayoutWidget::create() { setRoot(std::move(area)); - // Keyboard layout changes are not event-pushed for all compositor backends. - // Keep a low-frequency idle probe so passive switches are reflected promptly. - m_idleRefreshTimer.startRepeating(kIdleProbeInterval, [this]() { - if (m_refreshAttemptsRemaining > 0) { - return; - } - requestUpdate(); - }); + // The bar's normal second tick refreshes passive compositor-side layout changes. + // Click-initiated switches still use the short burst timer below for responsive feedback. } void KeyboardLayoutWidget::doLayout(Renderer& renderer, float containerWidth, float containerHeight) { diff --git a/src/shell/bar/widgets/keyboard_layout_widget.h b/src/shell/bar/widgets/keyboard_layout_widget.h index f0b446759..4089ffdc4 100644 --- a/src/shell/bar/widgets/keyboard_layout_widget.h +++ b/src/shell/bar/widgets/keyboard_layout_widget.h @@ -44,7 +44,6 @@ private: bool m_clickArmed = false; int m_refreshAttemptsRemaining = 0; Timer m_refreshTimer; - Timer m_idleRefreshTimer; bool m_isVertical = false; bool m_lastVertical = false; };