From 99121e00b23c6807f65475952830fddb31e1d89c Mon Sep 17 00:00:00 2001 From: Gwilherm Folliot Date: Mon, 7 Oct 2024 15:17:25 +0200 Subject: [PATCH] input-capture: impl keymap --- CMakeLists.txt | 3 +- src/helpers/MiscFunctions.cpp | 85 ++++++++++++++++++++++++++++++++++ src/helpers/MiscFunctions.hpp | 4 +- src/meson.build | 1 + src/portals/InputCapture.cpp | 20 +++++++- src/portals/InputCapture.hpp | 18 ++++--- src/shared/Eis.cpp | 54 ++++++++++++++++++++- src/shared/Eis.hpp | 24 +++++++--- subprojects/hyprland-protocols | 2 +- 9 files changed, 191 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ba0b5f..1eaa06b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,8 @@ pkg_check_modules( gbm hyprlang>=0.2.0 hyprutils - hyprwayland-scanner>=0.4.2) + hyprwayland-scanner>=0.4.2 + uuid) # check whether we can find sdbus-c++ through pkg-config pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++>=2.0.0) diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 336e450..92d2f3b 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include std::string execAndGet(const char* cmd) { Debug::log(LOG, "execAndGet: {}", cmd); @@ -58,3 +62,84 @@ bool inShellPath(const std::string& exec) { return std::ranges::any_of(paths, [&exec](std::string& path) { return access((path + "/" + exec).c_str(), X_OK) == 0; }); } + +std::string getRandomUUID() { + std::string uuid; + uuid_t uuid_; + uuid_generate_random(uuid_); + return std::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", (uint16_t)uuid_[0], (uint16_t)uuid_[1], + (uint16_t)uuid_[2], (uint16_t)uuid_[3], (uint16_t)uuid_[4], (uint16_t)uuid_[5], (uint16_t)uuid_[6], (uint16_t)uuid_[7], (uint16_t)uuid_[8], + (uint16_t)uuid_[9], (uint16_t)uuid_[10], (uint16_t)uuid_[11], (uint16_t)uuid_[12], (uint16_t)uuid_[13], (uint16_t)uuid_[14], (uint16_t)uuid_[15]); +} + +std::pair openExclusiveShm() { + // Only absolute paths can be shared across different shm_open() calls + std::string name = "/" + getRandomUUID(); + + for (size_t i = 0; i < 69; ++i) { + int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) + return {fd, name}; + } + + return {-1, ""}; +} + +int allocateSHMFile(size_t len) { + auto [fd, name] = openExclusiveShm(); + if (fd < 0) + return -1; + + shm_unlink(name.c_str()); + + int ret; + do { + ret = ftruncate(fd, len); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} + +bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr) { + auto [fd, name] = openExclusiveShm(); + if (fd < 0) { + return false; + } + + // CLOEXEC is guaranteed to be set by shm_open + int ro_fd = shm_open(name.c_str(), O_RDONLY, 0); + if (ro_fd < 0) { + shm_unlink(name.c_str()); + close(fd); + return false; + } + + shm_unlink(name.c_str()); + + // Make sure the file cannot be re-opened in read-write mode (e.g. via + // "/proc/self/fd/" on Linux) + if (fchmod(fd, 0) != 0) { + close(fd); + close(ro_fd); + return false; + } + + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + close(ro_fd); + return false; + } + + *rw_fd_ptr = fd; + *ro_fd_ptr = ro_fd; + return true; +} diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index f335a83..194d248 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -5,4 +5,6 @@ std::string execAndGet(const char* cmd); void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message); bool inShellPath(const std::string& exec); -void sendEmptyDbusMethodReply(sdbus::MethodCall& call, u_int32_t responseCode); \ No newline at end of file +void sendEmptyDbusMethodReply(sdbus::MethodCall& call, u_int32_t responseCode); +int allocateSHMFile(size_t len); +bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr); diff --git a/src/meson.build b/src/meson.build index 2f8d1e6..ad66f5b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ executable('xdg-desktop-portal-hyprland', dependency('sdbus-c++'), dependency('threads'), dependency('wayland-client'), + dependency('uuid'), ], include_directories: inc, install: true, diff --git a/src/portals/InputCapture.cpp b/src/portals/InputCapture.cpp index ebfb507..6009760 100644 --- a/src/portals/InputCapture.cpp +++ b/src/portals/InputCapture.cpp @@ -20,6 +20,10 @@ CInputCapturePortal::CInputCapturePortal(SP mgr onMotion(wl_fixed_to_double(x), wl_fixed_to_double(y), wl_fixed_to_double(dx), wl_fixed_to_double(dy)); }); + mgr->setKeymap([this](CCHyprlandInputCaptureManagerV1* r, hyprlandInputCaptureManagerV1KeymapFormat format, int32_t fd, uint32_t size) { + onKeymap(format == HYPRLAND_INPUT_CAPTURE_MANAGER_V1_KEYMAP_FORMAT_XKB_V1 ? fd : 0, size); + }); + mgr->setKey([this](CCHyprlandInputCaptureManagerV1* r, uint32_t key, hyprlandInputCaptureManagerV1KeyState state) { onKey(key, state); }); mgr->setButton([this](CCHyprlandInputCaptureManagerV1* r, uint32_t button, hyprlandInputCaptureManagerV1ButtonState state) { onButton(button, state); }); @@ -107,7 +111,7 @@ void CInputCapturePortal::onCreateSession(sdbus::MethodCall& call) { session->request = createDBusRequest(requestHandle); session->request->onDestroy = [session]() { session->request.release(); }; - session->eis = std::make_unique("eis-" + sessionId); + session->eis = std::make_unique("eis-" + sessionId, keymap); sessions.emplace(sessionHandle, session); @@ -370,6 +374,13 @@ void CInputCapturePortal::onKey(uint32_t id, bool pressed) { value->key(id, pressed); } +void CInputCapturePortal::onKeymap(int32_t fd, uint32_t size) { + keymap.fd = fd; + keymap.size = size; + for (const auto& [key, value] : sessions) + value->keymap(keymap); +} + void CInputCapturePortal::onButton(uint32_t button, bool pressed) { for (const auto& [key, session] : sessions) session->button(button, pressed); @@ -525,6 +536,13 @@ void CInputCapturePortal::SSession::motion(double dx, double dy) { eis->sendMotion(dx, dy); } +void CInputCapturePortal::SSession::keymap(Keymap keymap) { + if (status == STOPPED) + return; + + eis->setKeymap(keymap); +} + void CInputCapturePortal::SSession::key(uint32_t key, bool pressed) { if (status != ACTIVATED) return; diff --git a/src/portals/InputCapture.hpp b/src/portals/InputCapture.hpp index 31e5c80..75091f1 100644 --- a/src/portals/InputCapture.hpp +++ b/src/portals/InputCapture.hpp @@ -30,6 +30,7 @@ class CInputCapturePortal { void onConnectToEIS(sdbus::MethodCall& methodCall); void onMotion(double x, double y, double dx, double dy); + void onKeymap(int32_t fd, uint32_t size); void onKey(uint32_t key, bool pressed); void onButton(uint32_t button, bool pressed); void onAxis(bool axis, double value); @@ -51,7 +52,7 @@ class CInputCapturePortal { std::unordered_map barriers; uint32_t activationId = 0; - ClientStatus status = CREATED; + ClientStatus status = CREATED; // bool activate(double x, double y, uint32_t borderId); @@ -61,6 +62,7 @@ class CInputCapturePortal { void motion(double dx, double dy); void key(uint32_t key, bool pressed); + void keymap(Keymap keymap); void button(uint32_t button, bool pressed); void axis(bool axis, double value); void axisValue120(bool axis, int32_t value120); @@ -81,12 +83,14 @@ class CInputCapturePortal { uint sessionCounter = 0; uint lastZoneSet = 0; - const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture"; - const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; + Keymap keymap; //We store the active keymap ready to be sent when creating EIS - bool sessionValid(sdbus::ObjectPath sessionHandle); + const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture"; + const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; - void activate(sdbus::ObjectPath sessionHandle, double x, double y, uint32_t borderId); - void deactivate(sdbus::ObjectPath sessionHandle); - void disable(sdbus::ObjectPath sessionHandle); + bool sessionValid(sdbus::ObjectPath sessionHandle); + + void activate(sdbus::ObjectPath sessionHandle, double x, double y, uint32_t borderId); + void deactivate(sdbus::ObjectPath sessionHandle); + void disable(sdbus::ObjectPath sessionHandle); }; diff --git a/src/shared/Eis.cpp b/src/shared/Eis.cpp index 46fbd93..4c3a5a1 100644 --- a/src/shared/Eis.cpp +++ b/src/shared/Eis.cpp @@ -1,11 +1,17 @@ #include "Eis.hpp" #include "../core/PortalManager.hpp" +#include "../helpers/MiscFunctions.hpp" #include "src/helpers/Log.hpp" +#include #include +#include +#include -EmulatedInputServer::EmulatedInputServer(std::string socketName) { +EmulatedInputServer::EmulatedInputServer(std::string socketName, Keymap _keymap) { Debug::log(LOG, "[EIS] Init socket: {}", socketName); + keymap = _keymap; + const char* xdg = getenv("XDG_RUNTIME_DIR"); if (xdg) socketPath = std::string(xdg) + "/" + socketName; @@ -152,13 +158,53 @@ void EmulatedInputServer::ensureKeyboard(eis_event* event) { eis_device* keyboard = eis_seat_new_device(client.seat); eis_device_configure_name(keyboard, "captured keyboard"); eis_device_configure_capability(keyboard, EIS_DEVICE_CAP_KEYBOARD); - // TODO: layout + + if (keymap.fd != 0) { + Keymap _keymap = openKeymap(); + Debug::log(LOG, "Using keymap {}", _keymap.fd); + eis_keymap* eis_keymap = eis_device_new_keymap(keyboard, EIS_KEYMAP_TYPE_XKB, _keymap.fd, _keymap.size); + eis_keymap_add(eis_keymap); + eis_keymap_unref(eis_keymap); + } + eis_device_add(keyboard); eis_device_resume(keyboard); client.keyboard = keyboard; } +Keymap EmulatedInputServer::openKeymap() { + Keymap _keymap; + + void* src = mmap(nullptr, keymap.size, PROT_READ, MAP_PRIVATE, keymap.fd, 0); + if (src == MAP_FAILED) { + Debug::log(ERR, "Failed to mmap the compositor keymap fd"); + return _keymap; + } + + int keymapFD = allocateSHMFile(keymap.size); + if (keymapFD < 0) { + Debug::log(ERR, "Failed to create a keymap file for keyboard grab"); + return _keymap; + } + + char* dst = (char*)mmap(nullptr, keymap.size, PROT_READ | PROT_WRITE, MAP_SHARED, keymapFD, 0); + if (dst == MAP_FAILED) { + Debug::log(ERR, "Failed to mmap a keymap file for keyboard grab"); + close(keymapFD); + return _keymap; + } + + memcpy(dst, src, keymap.size); + munmap(dst, keymap.size); + munmap(src, keymap.size); + + _keymap.fd = keymapFD; + _keymap.size = keymap.size; + + return _keymap; +} + //TODO: remove and re-add devices when monitors change (see: mutter/meta-input-capture-session.c:1107) void EmulatedInputServer::clearPointer() { @@ -205,6 +251,10 @@ void EmulatedInputServer::stopEmulating() { eis_device_stop_emulating(client.keyboard); } +void EmulatedInputServer::setKeymap(Keymap _keymap) { + keymap = _keymap; +} + void EmulatedInputServer::sendMotion(double x, double y) { if (!client.pointer) return; diff --git a/src/shared/Eis.hpp b/src/shared/Eis.hpp index 2df8946..918dc71 100644 --- a/src/shared/Eis.hpp +++ b/src/shared/Eis.hpp @@ -3,17 +3,24 @@ #include #include +struct Keymap { + int32_t fd = 0; + uint32_t size = 0; +}; + /* * Responsible to creating a socket for input communication */ class EmulatedInputServer { public: - EmulatedInputServer(std::string socketPath); + EmulatedInputServer(std::string socketPath, Keymap keymap); std::string socketPath; void startEmulating(int activationId); void stopEmulating(); + void setKeymap(Keymap _keymap); + void sendMotion(double x, double y); void sendKey(uint32_t key, bool pressed); void sendButton(uint32_t button, bool pressed); @@ -38,10 +45,13 @@ class EmulatedInputServer { eis_device* keyboard = nullptr; } client; - int onEvent(eis_event* e); - void pollEvents(); - void ensurePointer(eis_event* event); - void ensureKeyboard(eis_event* event); - void clearPointer(); - void clearKeyboard(); + Keymap keymap; + + int onEvent(eis_event* e); + void pollEvents(); + void ensurePointer(eis_event* event); + void ensureKeyboard(eis_event* event); + Keymap openKeymap(); + void clearPointer(); + void clearKeyboard(); }; diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols index 479cc22..d3674e1 160000 --- a/subprojects/hyprland-protocols +++ b/subprojects/hyprland-protocols @@ -1 +1 @@ -Subproject commit 479cc226451c264396a4c442710d6b56dce2fa46 +Subproject commit d3674e1f4eac730efc01c08e794a988be31ec73e