input-capture: impl keymap

This commit is contained in:
Gwilherm Folliot 2024-10-07 15:17:25 +02:00
parent 01d24daacc
commit 99121e00b2
No known key found for this signature in database
GPG key ID: 90236D3623DCD660
9 changed files with 191 additions and 20 deletions

View file

@ -66,7 +66,8 @@ pkg_check_modules(
gbm gbm
hyprlang>=0.2.0 hyprlang>=0.2.0
hyprutils hyprutils
hyprwayland-scanner>=0.4.2) hyprwayland-scanner>=0.4.2
uuid)
# check whether we can find sdbus-c++ through pkg-config # check whether we can find sdbus-c++ through pkg-config
pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++>=2.0.0) pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++>=2.0.0)

View file

@ -7,6 +7,10 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <uuid/uuid.h>
std::string execAndGet(const char* cmd) { std::string execAndGet(const char* cmd) {
Debug::log(LOG, "execAndGet: {}", 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; }); 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<int, std::string> 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;
}

View file

@ -5,4 +5,6 @@
std::string execAndGet(const char* cmd); std::string execAndGet(const char* cmd);
void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message); void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message);
bool inShellPath(const std::string& exec); bool inShellPath(const std::string& exec);
void sendEmptyDbusMethodReply(sdbus::MethodCall& call, u_int32_t responseCode); 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);

View file

@ -14,6 +14,7 @@ executable('xdg-desktop-portal-hyprland',
dependency('sdbus-c++'), dependency('sdbus-c++'),
dependency('threads'), dependency('threads'),
dependency('wayland-client'), dependency('wayland-client'),
dependency('uuid'),
], ],
include_directories: inc, include_directories: inc,
install: true, install: true,

View file

@ -20,6 +20,10 @@ CInputCapturePortal::CInputCapturePortal(SP<CCHyprlandInputCaptureManagerV1> mgr
onMotion(wl_fixed_to_double(x), wl_fixed_to_double(y), wl_fixed_to_double(dx), wl_fixed_to_double(dy)); 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->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); }); 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 = createDBusRequest(requestHandle);
session->request->onDestroy = [session]() { session->request.release(); }; session->request->onDestroy = [session]() { session->request.release(); };
session->eis = std::make_unique<EmulatedInputServer>("eis-" + sessionId); session->eis = std::make_unique<EmulatedInputServer>("eis-" + sessionId, keymap);
sessions.emplace(sessionHandle, session); sessions.emplace(sessionHandle, session);
@ -370,6 +374,13 @@ void CInputCapturePortal::onKey(uint32_t id, bool pressed) {
value->key(id, 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) { void CInputCapturePortal::onButton(uint32_t button, bool pressed) {
for (const auto& [key, session] : sessions) for (const auto& [key, session] : sessions)
session->button(button, pressed); session->button(button, pressed);
@ -525,6 +536,13 @@ void CInputCapturePortal::SSession::motion(double dx, double dy) {
eis->sendMotion(dx, 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) { void CInputCapturePortal::SSession::key(uint32_t key, bool pressed) {
if (status != ACTIVATED) if (status != ACTIVATED)
return; return;

View file

@ -30,6 +30,7 @@ class CInputCapturePortal {
void onConnectToEIS(sdbus::MethodCall& methodCall); void onConnectToEIS(sdbus::MethodCall& methodCall);
void onMotion(double x, double y, double dx, double dy); 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 onKey(uint32_t key, bool pressed);
void onButton(uint32_t button, bool pressed); void onButton(uint32_t button, bool pressed);
void onAxis(bool axis, double value); void onAxis(bool axis, double value);
@ -51,7 +52,7 @@ class CInputCapturePortal {
std::unordered_map<uint32_t, SBarrier> barriers; std::unordered_map<uint32_t, SBarrier> barriers;
uint32_t activationId = 0; uint32_t activationId = 0;
ClientStatus status = CREATED; ClientStatus status = CREATED;
// //
bool activate(double x, double y, uint32_t borderId); bool activate(double x, double y, uint32_t borderId);
@ -61,6 +62,7 @@ class CInputCapturePortal {
void motion(double dx, double dy); void motion(double dx, double dy);
void key(uint32_t key, bool pressed); void key(uint32_t key, bool pressed);
void keymap(Keymap keymap);
void button(uint32_t button, bool pressed); void button(uint32_t button, bool pressed);
void axis(bool axis, double value); void axis(bool axis, double value);
void axisValue120(bool axis, int32_t value120); void axisValue120(bool axis, int32_t value120);
@ -81,12 +83,14 @@ class CInputCapturePortal {
uint sessionCounter = 0; uint sessionCounter = 0;
uint lastZoneSet = 0; uint lastZoneSet = 0;
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture"; Keymap keymap; //We store the active keymap ready to be sent when creating EIS
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
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); bool sessionValid(sdbus::ObjectPath sessionHandle);
void deactivate(sdbus::ObjectPath sessionHandle);
void disable(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);
}; };

View file

@ -1,11 +1,17 @@
#include "Eis.hpp" #include "Eis.hpp"
#include "../core/PortalManager.hpp" #include "../core/PortalManager.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "src/helpers/Log.hpp" #include "src/helpers/Log.hpp"
#include <alloca.h>
#include <libeis.h> #include <libeis.h>
#include <sys/mman.h>
#include <unistd.h>
EmulatedInputServer::EmulatedInputServer(std::string socketName) { EmulatedInputServer::EmulatedInputServer(std::string socketName, Keymap _keymap) {
Debug::log(LOG, "[EIS] Init socket: {}", socketName); Debug::log(LOG, "[EIS] Init socket: {}", socketName);
keymap = _keymap;
const char* xdg = getenv("XDG_RUNTIME_DIR"); const char* xdg = getenv("XDG_RUNTIME_DIR");
if (xdg) if (xdg)
socketPath = std::string(xdg) + "/" + socketName; 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* keyboard = eis_seat_new_device(client.seat);
eis_device_configure_name(keyboard, "captured keyboard"); eis_device_configure_name(keyboard, "captured keyboard");
eis_device_configure_capability(keyboard, EIS_DEVICE_CAP_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_add(keyboard);
eis_device_resume(keyboard); eis_device_resume(keyboard);
client.keyboard = 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) //TODO: remove and re-add devices when monitors change (see: mutter/meta-input-capture-session.c:1107)
void EmulatedInputServer::clearPointer() { void EmulatedInputServer::clearPointer() {
@ -205,6 +251,10 @@ void EmulatedInputServer::stopEmulating() {
eis_device_stop_emulating(client.keyboard); eis_device_stop_emulating(client.keyboard);
} }
void EmulatedInputServer::setKeymap(Keymap _keymap) {
keymap = _keymap;
}
void EmulatedInputServer::sendMotion(double x, double y) { void EmulatedInputServer::sendMotion(double x, double y) {
if (!client.pointer) if (!client.pointer)
return; return;

View file

@ -3,17 +3,24 @@
#include <libeis.h> #include <libeis.h>
#include <string> #include <string>
struct Keymap {
int32_t fd = 0;
uint32_t size = 0;
};
/* /*
* Responsible to creating a socket for input communication * Responsible to creating a socket for input communication
*/ */
class EmulatedInputServer { class EmulatedInputServer {
public: public:
EmulatedInputServer(std::string socketPath); EmulatedInputServer(std::string socketPath, Keymap keymap);
std::string socketPath; std::string socketPath;
void startEmulating(int activationId); void startEmulating(int activationId);
void stopEmulating(); void stopEmulating();
void setKeymap(Keymap _keymap);
void sendMotion(double x, double y); void sendMotion(double x, double y);
void sendKey(uint32_t key, bool pressed); void sendKey(uint32_t key, bool pressed);
void sendButton(uint32_t button, bool pressed); void sendButton(uint32_t button, bool pressed);
@ -38,10 +45,13 @@ class EmulatedInputServer {
eis_device* keyboard = nullptr; eis_device* keyboard = nullptr;
} client; } client;
int onEvent(eis_event* e); Keymap keymap;
void pollEvents();
void ensurePointer(eis_event* event); int onEvent(eis_event* e);
void ensureKeyboard(eis_event* event); void pollEvents();
void clearPointer(); void ensurePointer(eis_event* event);
void clearKeyboard(); void ensureKeyboard(eis_event* event);
Keymap openKeymap();
void clearPointer();
void clearKeyboard();
}; };

@ -1 +1 @@
Subproject commit 479cc226451c264396a4c442710d6b56dce2fa46 Subproject commit d3674e1f4eac730efc01c08e794a988be31ec73e