mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
hyprland: more IPC usage and dont use hyprctl for kb
This commit is contained in:
@@ -380,6 +380,7 @@ void Application::initServices() {
|
||||
}
|
||||
});
|
||||
m_compositorPlatform.setWorkspaceChangeCallback([this]() { m_bar.refresh(); });
|
||||
m_compositorPlatform.setKeyboardLayoutChangeCallback([this]() { m_bar.refresh(); });
|
||||
m_wayland.setToplevelChangeCallback([this]() {
|
||||
m_bar.refresh();
|
||||
m_dock.refresh();
|
||||
@@ -1302,6 +1303,7 @@ std::vector<PollSource*> Application::currentPollSources() {
|
||||
sources.push_back(&m_timerPollSource);
|
||||
sources.push_back(&m_keyRepeatPollSource);
|
||||
sources.push_back(&m_workspacePollSource);
|
||||
sources.push_back(&m_keyboardLayoutPollSource);
|
||||
if constexpr (kLockKeysEnabled) {
|
||||
sources.push_back(&m_lockKeysPollSource);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
#include "wayland/clipboard_poll_source.h"
|
||||
#include "wayland/clipboard_service.h"
|
||||
#include "wayland/key_repeat_poll_source.h"
|
||||
#include "wayland/keyboard_layout_poll_source.h"
|
||||
#include "wayland/virtual_keyboard_service.h"
|
||||
#include "wayland/wayland_connection.h"
|
||||
#include "wayland/workspace_poll_source.h"
|
||||
@@ -202,6 +203,7 @@ private:
|
||||
TimerPollSource m_timerPollSource;
|
||||
KeyRepeatPollSource m_keyRepeatPollSource{m_wayland};
|
||||
WorkspacePollSource m_workspacePollSource{m_compositorPlatform};
|
||||
KeyboardLayoutPollSource m_keyboardLayoutPollSource{m_compositorPlatform};
|
||||
LockKeysPollSource m_lockKeysPollSource{m_lockKeysService};
|
||||
std::unique_ptr<BrightnessPollSource> m_brightnessPollSource;
|
||||
std::unique_ptr<PipeWirePollSource> m_pipewirePollSource;
|
||||
|
||||
@@ -91,6 +91,34 @@ namespace {
|
||||
return m_backend.currentLayoutName();
|
||||
}
|
||||
|
||||
bool connectSocket() override {
|
||||
if constexpr (requires { m_backend.connectSocket(); }) {
|
||||
return m_backend.connectSocket();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setChangeCallback(ChangeCallback callback) override {
|
||||
if constexpr (requires { m_backend.setChangeCallback(std::move(callback)); }) {
|
||||
m_backend.setChangeCallback(std::move(callback));
|
||||
}
|
||||
}
|
||||
[[nodiscard]] int pollFd() const noexcept override {
|
||||
if constexpr (requires { m_backend.pollFd(); }) {
|
||||
return m_backend.pollFd();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
void dispatchPoll(short revents) override {
|
||||
if constexpr (requires { m_backend.dispatchPoll(revents); }) {
|
||||
m_backend.dispatchPoll(revents);
|
||||
}
|
||||
}
|
||||
void cleanup() override {
|
||||
if constexpr (requires { m_backend.cleanup(); }) {
|
||||
m_backend.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BackendT m_backend;
|
||||
};
|
||||
@@ -532,6 +560,27 @@ std::vector<std::string> CompositorPlatform::keyboardLayoutNames() const {
|
||||
return m_wayland.keyboardLayoutNames();
|
||||
}
|
||||
|
||||
void CompositorPlatform::setKeyboardLayoutChangeCallback(ChangeCallback callback) {
|
||||
m_keyboardLayoutChangeCallback = std::move(callback);
|
||||
if (m_keyboardLayoutBackend != nullptr) {
|
||||
m_keyboardLayoutBackend->setChangeCallback(m_keyboardLayoutChangeCallback);
|
||||
m_keyboardLayoutBackend->connectSocket();
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorPlatform::addKeyboardLayoutPollFds(std::vector<pollfd>& fds) const {
|
||||
if (m_keyboardLayoutBackend != nullptr && m_keyboardLayoutBackend->pollFd() >= 0) {
|
||||
fds.push_back(
|
||||
{.fd = m_keyboardLayoutBackend->pollFd(), .events = m_keyboardLayoutBackend->pollEvents(), .revents = 0});
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorPlatform::dispatchKeyboardLayoutPoll(const std::vector<pollfd>& fds, std::size_t startIdx) {
|
||||
if (m_keyboardLayoutBackend != nullptr && m_keyboardLayoutBackend->pollFd() >= 0 && startIdx < fds.size()) {
|
||||
m_keyboardLayoutBackend->dispatchPoll(fds[startIdx].revents);
|
||||
}
|
||||
}
|
||||
|
||||
bool CompositorPlatform::setOutputPower(bool on) const {
|
||||
return m_outputPowerBackend != nullptr && m_outputPowerBackend->setOutputPower(m_wayland, on);
|
||||
}
|
||||
|
||||
@@ -102,6 +102,10 @@ public:
|
||||
[[nodiscard]] std::string currentKeyboardLayoutName() const;
|
||||
[[nodiscard]] std::vector<std::string> keyboardLayoutNames() const;
|
||||
|
||||
void setKeyboardLayoutChangeCallback(ChangeCallback callback);
|
||||
void addKeyboardLayoutPollFds(std::vector<pollfd>& fds) const;
|
||||
void dispatchKeyboardLayoutPoll(const std::vector<pollfd>& fds, std::size_t startIdx);
|
||||
|
||||
[[nodiscard]] bool setOutputPower(bool on) const;
|
||||
|
||||
[[nodiscard]] bool tracksOverviewState() const noexcept;
|
||||
@@ -132,6 +136,7 @@ private:
|
||||
std::unique_ptr<compositors::OutputPowerBackend> m_outputPowerBackend;
|
||||
std::unique_ptr<KeyboardLayoutBackend> m_keyboardLayoutBackend;
|
||||
ChangeCallback m_workspaceChangeCallback;
|
||||
ChangeCallback m_keyboardLayoutChangeCallback;
|
||||
std::vector<WorkspaceModelSnapshot> m_lastWorkspaceModelSnapshot;
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
@@ -1,80 +1,22 @@
|
||||
#include "compositors/hyprland/hyprland_keyboard_backend.h"
|
||||
|
||||
#include "core/process.h"
|
||||
#include "core/log.h"
|
||||
#include "util/string_utils.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <json.hpp>
|
||||
#include <sys/wait.h>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int pipefd[2];
|
||||
if (::pipe(pipefd) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) {
|
||||
::close(pipefd[0]);
|
||||
::close(pipefd[1]);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
::close(pipefd[0]);
|
||||
::dup2(pipefd[1], STDOUT_FILENO);
|
||||
::close(pipefd[1]);
|
||||
|
||||
std::vector<char*> argv;
|
||||
argv.reserve(args.size() + 1);
|
||||
for (const auto& arg : args) {
|
||||
argv.push_back(const_cast<char*>(arg.c_str()));
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
::execvp(argv[0], argv.data());
|
||||
::_exit(127);
|
||||
}
|
||||
|
||||
::close(pipefd[1]);
|
||||
std::string output;
|
||||
char buffer[4096];
|
||||
ssize_t count = 0;
|
||||
while ((count = ::read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||
output.append(buffer, static_cast<std::size_t>(count));
|
||||
}
|
||||
::close(pipefd[0]);
|
||||
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
constexpr Logger kLog("keyboard_hyprland");
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -85,17 +27,16 @@ HyprlandKeyboardBackend::HyprlandKeyboardBackend(std::string_view compositorHint
|
||||
m_enabled = hinted || (signature != nullptr && signature[0] != '\0');
|
||||
}
|
||||
|
||||
HyprlandKeyboardBackend::~HyprlandKeyboardBackend() { cleanup(); }
|
||||
|
||||
bool HyprlandKeyboardBackend::isAvailable() const noexcept { return m_enabled; }
|
||||
|
||||
bool HyprlandKeyboardBackend::cycleLayout() const {
|
||||
if (!m_enabled) {
|
||||
if (!m_enabled || m_requestSocketPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
const bool ok = process::runSync({"hyprctl", "switchxkblayout", "all", "next"});
|
||||
if (ok) {
|
||||
invalidateCurrentLayoutCache();
|
||||
}
|
||||
return ok;
|
||||
std::string response;
|
||||
return sendRequest("switchxkblayout all next", response);
|
||||
}
|
||||
|
||||
std::optional<KeyboardLayoutState> HyprlandKeyboardBackend::layoutState() const {
|
||||
@@ -107,49 +48,278 @@ std::optional<KeyboardLayoutState> HyprlandKeyboardBackend::layoutState() const
|
||||
}
|
||||
|
||||
std::optional<std::string> HyprlandKeyboardBackend::currentLayoutName() const {
|
||||
if (!m_enabled) {
|
||||
if (!m_enabled || m_currentLayoutName.empty()) {
|
||||
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 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 finish(std::nullopt);
|
||||
}
|
||||
for (const auto& keyboard : *keyboardsIt) {
|
||||
if (!keyboard.is_object()) {
|
||||
continue;
|
||||
}
|
||||
if (!keyboard.value("main", false)) {
|
||||
continue;
|
||||
}
|
||||
const std::string layout = keyboard.value("active_keymap", "");
|
||||
if (!layout.empty() && layout != "error") {
|
||||
return finish(layout);
|
||||
}
|
||||
}
|
||||
} catch (const nlohmann::json::exception&) {
|
||||
return finish(std::nullopt);
|
||||
}
|
||||
|
||||
return finish(std::nullopt);
|
||||
return m_currentLayoutName;
|
||||
}
|
||||
|
||||
bool HyprlandKeyboardBackend::connectSocket() {
|
||||
if (!m_enabled || !ensureSocketPaths()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
m_eventSocketFd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (m_eventSocketFd < 0) {
|
||||
kLog.warn("failed to create hyprland keyboard IPC socket: {}", std::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
if (m_eventSocketPath.size() >= sizeof(addr.sun_path)) {
|
||||
kLog.warn("hyprland keyboard IPC socket path too long");
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
std::memcpy(addr.sun_path, m_eventSocketPath.c_str(), m_eventSocketPath.size() + 1);
|
||||
|
||||
if (::connect(m_eventSocketFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
|
||||
kLog.warn("failed to connect to hyprland keyboard IPC {}: {}", m_eventSocketPath, std::strerror(errno));
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
const int flags = ::fcntl(m_eventSocketFd, F_GETFL, 0);
|
||||
if (flags >= 0) {
|
||||
(void)::fcntl(m_eventSocketFd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
seedLayoutFromDevices();
|
||||
kLog.info("connected to hyprland keyboard IPC at {}", m_eventSocketPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::setChangeCallback(ChangeCallback callback) { m_changeCallback = std::move(callback); }
|
||||
|
||||
void HyprlandKeyboardBackend::dispatchPoll(short revents) {
|
||||
if (m_eventSocketFd < 0) {
|
||||
return;
|
||||
}
|
||||
if ((revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
|
||||
kLog.warn("hyprland keyboard IPC disconnected");
|
||||
cleanup();
|
||||
if (m_changeCallback) {
|
||||
m_changeCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((revents & POLLIN) != 0) {
|
||||
readSocket();
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::cleanup() {
|
||||
if (m_eventSocketFd >= 0) {
|
||||
::close(m_eventSocketFd);
|
||||
m_eventSocketFd = -1;
|
||||
}
|
||||
m_readBuffer.clear();
|
||||
m_currentLayoutName.clear();
|
||||
m_mainKeyboardName.clear();
|
||||
}
|
||||
|
||||
bool HyprlandKeyboardBackend::ensureSocketPaths() {
|
||||
if (!m_requestSocketPath.empty() && !m_eventSocketPath.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* signature = std::getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
if (signature == nullptr || signature[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hyprDir;
|
||||
const char* runtimeDir = std::getenv("XDG_RUNTIME_DIR");
|
||||
if (runtimeDir != nullptr && runtimeDir[0] != '\0') {
|
||||
hyprDir = std::string(runtimeDir) + "/hypr/" + signature;
|
||||
}
|
||||
|
||||
if (hyprDir.empty() || !std::filesystem::is_directory(hyprDir)) {
|
||||
hyprDir = std::string("/tmp/hypr/") + signature;
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(hyprDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_requestSocketPath = hyprDir + "/.socket.sock";
|
||||
m_eventSocketPath = hyprDir + "/.socket2.sock";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HyprlandKeyboardBackend::sendRequest(const std::string& request, std::string& response) const {
|
||||
if (m_requestSocketPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int fd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
if (m_requestSocketPath.size() >= sizeof(addr.sun_path)) {
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
std::memcpy(addr.sun_path, m_requestSocketPath.c_str(), m_requestSocketPath.size() + 1);
|
||||
|
||||
if (::connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
while (offset < request.size()) {
|
||||
const ssize_t written = ::send(fd, request.data() + offset, request.size() - offset, MSG_NOSIGNAL);
|
||||
if (written < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
offset += static_cast<std::size_t>(written);
|
||||
}
|
||||
|
||||
::shutdown(fd, SHUT_WR);
|
||||
|
||||
std::string out;
|
||||
char buffer[4096];
|
||||
while (true) {
|
||||
const ssize_t n = ::recv(fd, buffer, sizeof(buffer), 0);
|
||||
if (n > 0) {
|
||||
out.append(buffer, buffer + n);
|
||||
continue;
|
||||
}
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
::close(fd);
|
||||
response = std::move(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<nlohmann::json> HyprlandKeyboardBackend::requestJson(const std::string& request) const {
|
||||
std::string response;
|
||||
if (!sendRequest(request, response) || response.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
try {
|
||||
return nlohmann::json::parse(response);
|
||||
} catch (const nlohmann::json::exception& e) {
|
||||
kLog.warn("failed to parse hyprland response for {}: {}", request, e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::seedLayoutFromDevices() {
|
||||
const auto json = requestJson("j/devices");
|
||||
if (!json || !json->is_object()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto keyboardsIt = json->find("keyboards");
|
||||
if (keyboardsIt == json->end() || !keyboardsIt->is_array()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& keyboard : *keyboardsIt) {
|
||||
if (!keyboard.is_object() || !keyboard.value("main", false)) {
|
||||
continue;
|
||||
}
|
||||
const std::string layout = keyboard.value("active_keymap", "");
|
||||
if (!layout.empty() && layout != "error") {
|
||||
m_currentLayoutName = layout;
|
||||
m_mainKeyboardName = keyboard.value("name", "");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::readSocket() {
|
||||
char buffer[4096];
|
||||
while (true) {
|
||||
const ssize_t n = ::recv(m_eventSocketFd, buffer, sizeof(buffer), MSG_DONTWAIT);
|
||||
if (n > 0) {
|
||||
m_readBuffer.insert(m_readBuffer.end(), buffer, buffer + n);
|
||||
continue;
|
||||
}
|
||||
if (n == 0) {
|
||||
cleanup();
|
||||
if (m_changeCallback) {
|
||||
m_changeCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
kLog.warn("failed to read from hyprland keyboard IPC: {}", std::strerror(errno));
|
||||
cleanup();
|
||||
if (m_changeCallback) {
|
||||
m_changeCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parseMessages();
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::parseMessages() {
|
||||
while (true) {
|
||||
auto it = std::find(m_readBuffer.begin(), m_readBuffer.end(), '\n');
|
||||
if (it == m_readBuffer.end()) {
|
||||
return;
|
||||
}
|
||||
std::string line(m_readBuffer.begin(), it);
|
||||
m_readBuffer.erase(m_readBuffer.begin(), it + 1);
|
||||
if (!line.empty()) {
|
||||
handleEvent(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandKeyboardBackend::handleEvent(std::string_view line) {
|
||||
const auto split = line.find(">>");
|
||||
if (split == std::string_view::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view event = line.substr(0, split);
|
||||
if (event != "activelayout") {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view data = line.substr(split + 2);
|
||||
const auto comma = data.find(',');
|
||||
if (comma == std::string_view::npos || comma + 1 >= data.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view keyboardName = data.substr(0, comma);
|
||||
const std::string_view layoutName = data.substr(comma + 1);
|
||||
|
||||
if (!m_mainKeyboardName.empty() && keyboardName != m_mainKeyboardName) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentLayoutName = std::string(layoutName);
|
||||
if (m_changeCallback) {
|
||||
m_changeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,49 @@
|
||||
|
||||
#include "compositors/keyboard_backend.h"
|
||||
|
||||
#include <functional>
|
||||
#include <json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class HyprlandKeyboardBackend {
|
||||
public:
|
||||
using ChangeCallback = std::function<void()>;
|
||||
|
||||
explicit HyprlandKeyboardBackend(std::string_view compositorHint);
|
||||
~HyprlandKeyboardBackend();
|
||||
|
||||
HyprlandKeyboardBackend(const HyprlandKeyboardBackend&) = delete;
|
||||
HyprlandKeyboardBackend& operator=(const HyprlandKeyboardBackend&) = delete;
|
||||
|
||||
[[nodiscard]] bool isAvailable() const noexcept;
|
||||
[[nodiscard]] bool cycleLayout() const;
|
||||
[[nodiscard]] std::optional<KeyboardLayoutState> layoutState() const;
|
||||
[[nodiscard]] std::optional<std::string> currentLayoutName() const;
|
||||
|
||||
bool connectSocket();
|
||||
void setChangeCallback(ChangeCallback callback);
|
||||
[[nodiscard]] int pollFd() const noexcept { return m_eventSocketFd; }
|
||||
void dispatchPoll(short revents);
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
bool ensureSocketPaths();
|
||||
[[nodiscard]] bool sendRequest(const std::string& request, std::string& response) const;
|
||||
[[nodiscard]] std::optional<nlohmann::json> requestJson(const std::string& request) const;
|
||||
void seedLayoutFromDevices();
|
||||
void readSocket();
|
||||
void parseMessages();
|
||||
void handleEvent(std::string_view line);
|
||||
|
||||
bool m_enabled = false;
|
||||
int m_eventSocketFd = -1;
|
||||
std::string m_requestSocketPath;
|
||||
std::string m_eventSocketPath;
|
||||
std::string m_currentLayoutName;
|
||||
std::string m_mainKeyboardName;
|
||||
std::vector<char> m_readBuffer;
|
||||
ChangeCallback m_changeCallback;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <poll.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -11,10 +13,19 @@ struct KeyboardLayoutState {
|
||||
|
||||
class KeyboardLayoutBackend {
|
||||
public:
|
||||
using ChangeCallback = std::function<void()>;
|
||||
|
||||
virtual ~KeyboardLayoutBackend() = default;
|
||||
|
||||
[[nodiscard]] virtual bool isAvailable() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool cycleLayout() const = 0;
|
||||
[[nodiscard]] virtual std::optional<KeyboardLayoutState> layoutState() const = 0;
|
||||
[[nodiscard]] virtual std::optional<std::string> currentLayoutName() const = 0;
|
||||
|
||||
virtual bool connectSocket() { return false; }
|
||||
virtual void setChangeCallback(ChangeCallback /*callback*/) {}
|
||||
[[nodiscard]] virtual int pollFd() const noexcept { return -1; }
|
||||
[[nodiscard]] virtual short pollEvents() const noexcept { return POLLIN | POLLHUP | POLLERR; }
|
||||
virtual void dispatchPoll(short /*revents*/) {}
|
||||
virtual void cleanup() {}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/poll_source.h"
|
||||
#include "compositors/compositor_platform.h"
|
||||
|
||||
class KeyboardLayoutPollSource final : public PollSource {
|
||||
public:
|
||||
explicit KeyboardLayoutPollSource(CompositorPlatform& platform) : m_platform(platform) {}
|
||||
|
||||
void dispatch(const std::vector<pollfd>& fds, std::size_t startIdx) override {
|
||||
m_platform.dispatchKeyboardLayoutPoll(fds, startIdx);
|
||||
}
|
||||
|
||||
protected:
|
||||
void doAddPollFds(std::vector<pollfd>& fds) override { m_platform.addKeyboardLayoutPollFds(fds); }
|
||||
|
||||
private:
|
||||
CompositorPlatform& m_platform;
|
||||
};
|
||||
Reference in New Issue
Block a user