mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
fix(bar): reduce keyboard layout CPU usage
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user