fix(bar): reduce keyboard layout CPU usage

This commit is contained in:
Ly-sec
2026-05-09 13:51:34 +02:00
parent 72ae75eab3
commit acee4e4860
4 changed files with 80 additions and 22 deletions
@@ -3,6 +3,7 @@
#include "core/process.h"
#include "util/string_utils.h"
#include <chrono>
#include <cstdlib>
#include <json.hpp>
#include <sys/wait.h>
@@ -11,6 +12,21 @@
namespace {
constexpr auto kCurrentLayoutCacheTtl = std::chrono::seconds(1);
struct CurrentLayoutCache {
std::optional<std::string> 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<std::string> runAndCapture(const std::vector<std::string>& 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<KeyboardLayoutState> HyprlandKeyboardBackend::layoutState() const {
@@ -91,16 +111,29 @@ std::optional<std::string> 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<std::string> 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<std::string> 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);
}
+39 -6
View File
@@ -3,6 +3,7 @@
#include "core/process.h"
#include "util/string_utils.h"
#include <chrono>
#include <cstdlib>
#include <json.hpp>
#include <sys/wait.h>
@@ -11,6 +12,21 @@
namespace {
constexpr auto kCurrentLayoutCacheTtl = std::chrono::seconds(1);
struct CurrentLayoutCache {
std::optional<std::string> 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<std::string> runAndCapture(const std::vector<std::string>& 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<KeyboardLayoutState> SwayKeyboardBackend::layoutState() const {
@@ -91,15 +111,28 @@ std::optional<std::string> 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<std::string> 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<std::string> 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);
}
@@ -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) {
@@ -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;
};