mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-06 00:35:58 +01:00
input-capture: impl keymap
This commit is contained in:
parent
01d24daacc
commit
99121e00b2
9 changed files with 191 additions and 20 deletions
|
@ -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)
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
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<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;
|
||||
}
|
||||
|
|
|
@ -6,3 +6,5 @@ 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);
|
||||
int allocateSHMFile(size_t len);
|
||||
bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr);
|
||||
|
|
|
@ -14,6 +14,7 @@ executable('xdg-desktop-portal-hyprland',
|
|||
dependency('sdbus-c++'),
|
||||
dependency('threads'),
|
||||
dependency('wayland-client'),
|
||||
dependency('uuid'),
|
||||
],
|
||||
include_directories: inc,
|
||||
install: true,
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
||||
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<EmulatedInputServer>("eis-" + sessionId);
|
||||
session->eis = std::make_unique<EmulatedInputServer>("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;
|
||||
|
|
|
@ -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);
|
||||
|
@ -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,6 +83,8 @@ class CInputCapturePortal {
|
|||
uint sessionCounter = 0;
|
||||
uint lastZoneSet = 0;
|
||||
|
||||
Keymap keymap; //We store the active keymap ready to be sent when creating EIS
|
||||
|
||||
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture";
|
||||
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
#include "Eis.hpp"
|
||||
#include "../core/PortalManager.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "src/helpers/Log.hpp"
|
||||
#include <alloca.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);
|
||||
|
||||
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;
|
||||
|
|
|
@ -3,17 +3,24 @@
|
|||
#include <libeis.h>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 479cc226451c264396a4c442710d6b56dce2fa46
|
||||
Subproject commit d3674e1f4eac730efc01c08e794a988be31ec73e
|
Loading…
Reference in a new issue