mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-12-28 12:19:48 +01:00
input-capture: impl keymap
This commit is contained in:
parent
a151525c0a
commit
5ddf1c2022
8 changed files with 189 additions and 19 deletions
|
@ -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>
|
||||||
|
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
using namespace Hyprutils::OS;
|
using namespace Hyprutils::OS;
|
||||||
|
@ -55,3 +59,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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -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,6 +83,8 @@ class CInputCapturePortal {
|
||||||
uint sessionCounter = 0;
|
uint sessionCounter = 0;
|
||||||
uint lastZoneSet = 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 INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture";
|
||||||
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
Keymap keymap;
|
||||||
|
|
||||||
int onEvent(eis_event* e);
|
int onEvent(eis_event* e);
|
||||||
void pollEvents();
|
void pollEvents();
|
||||||
void ensurePointer(eis_event* event);
|
void ensurePointer(eis_event* event);
|
||||||
void ensureKeyboard(eis_event* event);
|
void ensureKeyboard(eis_event* event);
|
||||||
|
Keymap openKeymap();
|
||||||
void clearPointer();
|
void clearPointer();
|
||||||
void clearKeyboard();
|
void clearKeyboard();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 479cc226451c264396a4c442710d6b56dce2fa46
|
Subproject commit d3674e1f4eac730efc01c08e794a988be31ec73e
|
Loading…
Reference in a new issue