WIP: input capture

This commit is contained in:
Gwilherm Folliot 2024-09-25 18:06:16 +02:00
parent d7f18dda5e
commit 3b12e2998e
No known key found for this signature in database
GPG key ID: 90236D3623DCD660
14 changed files with 1077 additions and 14 deletions

3
.gitmodules vendored
View file

@ -1,6 +1,7 @@
[submodule "hyprland-protocols"]
path = subprojects/hyprland-protocols
url = https://github.com/hyprwm/hyprland-protocols
url = https://github.com/3l0w/hyprland-protocols
branch = feat/input-capture-impl
[submodule "subprojects/sdbus-cpp"]
path = subprojects/sdbus-cpp
url = https://github.com/Kistler-Group/sdbus-cpp

View file

@ -62,6 +62,8 @@ pkg_check_modules(
libpipewire-0.3>=1.1.82
libspa-0.2
libdrm
libeis-1.0
dbus-1
gbm
hyprlang>=0.2.0
hyprutils>=0.2.6
@ -130,6 +132,8 @@ protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-global-shortcuts-v1"
true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-export-v1"
true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-input-capture-v1"
true)
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
# Installation

View file

@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.hyprland
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;org.freedesktop.impl.portal.InputCapture;
UseIn=wlroots;Hyprland;sway;Wayfire;river;

View file

@ -61,6 +61,9 @@ install_data(
'hyprland.portal',
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'),
)
version = run_command('cat', files('VERSION'), check: true).stdout().strip()
add_project_arguments(f'-DXDPH_VERSION="@version@"', language : 'cpp')
inc = include_directories('.', 'protocols')

View file

@ -22,6 +22,7 @@ client_protocols = [
'wlr-foreign-toplevel-management-unstable-v1.xml',
hl_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml',
hl_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml',
hl_protocol_dir / 'protocols/hyprland-input-capture-v1.xml',
wl_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml',
]

View file

@ -1,7 +1,10 @@
#include "PortalManager.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "wayland.hpp"
#include <hyprutils/memory/SharedPtr.hpp>
#include <memory>
#include <pipewire/pipewire.h>
#include <poll.h>
#include <sys/mman.h>
@ -9,6 +12,7 @@
#include <unistd.h>
#include <thread>
#include <vector>
SOutput::SOutput(SP<CCWlOutput> output_) : output(output_) {
output->setName([this](CCWlOutput* o, const char* name_) {
@ -19,13 +23,19 @@ SOutput::SOutput(SP<CCWlOutput> output_) : output(output_) {
Debug::log(LOG, "Found output name {}", name);
});
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { //
refreshRate = refresh;
});
output->setGeometry([this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model,
int32_t transform_) { //
transform = (wl_output_transform)transform_;
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
refreshRate = refresh;
this->width = width;
this->height = height;
});
output->setGeometry(
[this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) {
transform = (wl_output_transform)transform_;
this->x = x;
this->y = y;
});
output->setScale([this](CCWlOutput* r, uint32_t factor) { this->scale = factor; });
output->setDone([](CCWlOutput* r) { g_pPortalManager->m_sPortals.inputCapture->zonesChanged(); });
}
CPortalManager::CPortalManager() {
@ -63,7 +73,9 @@ void CPortalManager::onGlobal(uint32_t name, const char* interface, uint32_t ver
m_sPortals.globalShortcuts = std::make_unique<CGlobalShortcutsPortal>(makeShared<CCHyprlandGlobalShortcutsManagerV1>(
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_global_shortcuts_manager_v1_interface, version)));
}
if (INTERFACE == hyprland_input_capture_manager_v1_interface.name)
m_sPortals.inputCapture = std::make_unique<CInputCapturePortal>(makeShared<CCHyprlandInputCaptureManagerV1>(
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_input_capture_manager_v1_interface, version)));
else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name) {
m_sWaylandConnection.hyprlandToplevelMgr = makeShared<CCHyprlandToplevelExportManagerV1>(
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_toplevel_export_manager_v1_interface, version));
@ -417,6 +429,7 @@ void CPortalManager::startEventLoop() {
m_sPortals.screencopy.reset();
m_sPortals.screenshot.reset();
m_sHelpers.toplevel.reset();
m_sPortals.inputCapture.reset();
m_pConnection.reset();
pw_loop_destroy(m_sPipewire.loop);
@ -438,6 +451,10 @@ SOutput* CPortalManager::getOutputFromName(const std::string& name) {
return nullptr;
}
std::vector<std::unique_ptr<SOutput>> const& CPortalManager::getAllOutputs() {
return m_vOutputs;
}
static char* gbm_find_render_node(drmDevice* device) {
drmDevice* devices[64];
char* render_node = NULL;

View file

@ -1,13 +1,16 @@
#pragma once
#include <cstdint>
#include <memory>
#include <sdbus-c++/sdbus-c++.h>
#include <hyprlang.hpp>
#include "wayland.hpp"
#include "../portals/Screencopy.hpp"
#include "../portals/Screenshot.hpp"
#include "../portals/GlobalShortcuts.hpp"
#include "../portals/InputCapture.hpp"
#include "../helpers/Timer.hpp"
#include "../shared/ToplevelManager.hpp"
#include <gbm.h>
@ -33,6 +36,9 @@ struct SOutput {
uint32_t id = 0;
float refreshRate = 60.0;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
uint32_t width, height;
int32_t x, y;
int32_t scale;
};
struct SDMABUFModifier {
@ -44,13 +50,14 @@ class CPortalManager {
public:
CPortalManager();
void init();
void init();
void onGlobal(uint32_t name, const char* interface, uint32_t version);
void onGlobalRemoved(uint32_t name);
sdbus::IConnection* getConnection();
SOutput* getOutputFromName(const std::string& name);
sdbus::IConnection* getConnection();
SOutput* getOutputFromName(const std::string& name);
std::vector<std::unique_ptr<SOutput>> const& getAllOutputs();
struct {
pw_loop* loop = nullptr;
@ -60,6 +67,7 @@ class CPortalManager {
std::unique_ptr<CScreencopyPortal> screencopy;
std::unique_ptr<CScreenshotPortal> screenshot;
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
std::unique_ptr<CInputCapturePortal> inputCapture;
} m_sPortals;
struct {

View file

@ -42,4 +42,4 @@ int main(int argc, char** argv, char** envp) {
g_pPortalManager->init();
return 0;
}
}

View file

@ -9,6 +9,7 @@ executable('xdg-desktop-portal-hyprland',
dependency('hyprlang'),
dependency('hyprutils'),
dependency('libdrm'),
dependency('libeis-1.0'),
dependency('libpipewire-0.3'),
dependency('sdbus-c++'),
dependency('threads'),

View file

@ -0,0 +1,596 @@
#include "InputCapture.hpp"
#include "../core/PortalManager.hpp"
#include "../helpers/Log.hpp"
#include "hyprland-input-capture-v1.hpp"
#include "shared/Session.hpp"
#include "src/shared/Eis.hpp"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Types.h>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <unordered_map>
#include <wayland-util.h>
CInputCapturePortal::CInputCapturePortal(SP<CCHyprlandInputCaptureManagerV1> mgr) {
Debug::log(LOG, "[input-capture] initializing input capture portal");
m_sState.manager = mgr;
sessionCounter = 0;
lastZoneSet = 0;
mgr->setAbsoluteMotion([this](CCHyprlandInputCaptureManagerV1* r, wl_fixed_t x, wl_fixed_t y, wl_fixed_t dx, wl_fixed_t dy) {
onAbsoluteMotion(wl_fixed_to_double(x), wl_fixed_to_double(y), wl_fixed_to_double(dx), wl_fixed_to_double(dy));
});
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->setAxis([this](CCHyprlandInputCaptureManagerV1* r, hyprlandInputCaptureManagerV1Axis axis, double value) { onAxis(axis, value); });
mgr->setAxisValue120([this](CCHyprlandInputCaptureManagerV1* r, hyprlandInputCaptureManagerV1Axis axis, int32_t value120) { onAxis(axis, value120); });
mgr->setAxisStop([this](CCHyprlandInputCaptureManagerV1* r, hyprlandInputCaptureManagerV1Axis axis) { onAxisStop(axis); });
mgr->setFrame([this](CCHyprlandInputCaptureManagerV1* r) { onFrame(); });
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
m_pObject->registerMethod(INTERFACE_NAME, "CreateSession", "oossa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onCreateSession(c); });
m_pObject->registerMethod(INTERFACE_NAME, "GetZones", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onGetZones(c); });
m_pObject->registerMethod(INTERFACE_NAME, "SetPointerBarriers", "oosa{sv}aa{sv}u", "ua{sv}", [&](sdbus::MethodCall c) { onSetPointerBarriers(c); });
m_pObject->registerMethod(INTERFACE_NAME, "Enable", "osa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onEnable(c); });
m_pObject->registerMethod(INTERFACE_NAME, "Disable", "osa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onDisable(c); });
m_pObject->registerMethod(INTERFACE_NAME, "Release", "osa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onRelease(c); });
m_pObject->registerMethod(INTERFACE_NAME, "ConnectToEIS", "osa{sv}", "h", [&](sdbus::MethodCall c) { onConnectToEIS(c); });
m_pObject->registerProperty(INTERFACE_NAME, "SupportedCapabilities", "u", [](sdbus::PropertyGetReply& reply) { reply << (uint)(1 | 2); });
m_pObject->registerProperty(INTERFACE_NAME, "version", "u", [](sdbus::PropertyGetReply& reply) { reply << (uint)1; });
m_pObject->finishRegistration();
for (auto& o : g_pPortalManager->getAllOutputs()) {
Debug::log(LOG, "{} {}x{}", o->name, o->width, o->height);
}
Debug::log(LOG, "[input-capture] init successful");
}
void complete(sdbus::MethodCall& call) {
auto reply = call.createReply();
reply << (uint32_t)0;
reply << std::unordered_map<std::string, sdbus::Variant>{};
reply.send();
}
void CInputCapturePortal::onCreateSession(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New session:");
sdbus::ObjectPath requestHandle, sessionHandle;
call >> requestHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
std::string parentWindow;
call >> parentWindow;
std::unordered_map<std::string, sdbus::Variant> options;
call >> options;
uint32_t capabilities = options["capabilities"];
Debug::log(LOG, "[input-capture] | {}", requestHandle.c_str());
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
Debug::log(LOG, "[input-capture] | parent_window: {}", parentWindow);
Debug::log(LOG, "[input-capture] | capabilities : {}", capabilities);
std::string sessionId = "input-capture-" + std::to_string(sessionCounter++);
Debug::log(LOG, "[input-capture] | sessionId : {}", sessionId);
const std::shared_ptr<Session> session = std::make_shared<Session>();
session->appid = appID;
session->requestHandle = requestHandle;
session->sessionHandle = sessionHandle;
session->sessionId = sessionId;
session->capabilities = capabilities;
session->activationId = 0;
session->status = CREATED;
session->session = createDBusSession(sessionHandle);
session->session->onDestroy = [session, this]() {
if (session->status == ACTIVATED) {
disable(session->sessionHandle);
}
session->eis->stopServer();
Debug::log(LOG, "[input-capture] Session {} destroyed", session->sessionHandle.c_str());
session->session.release();
};
session->request = createDBusRequest(requestHandle);
session->request->onDestroy = [session]() { session->request.release(); };
session->eis = std::make_unique<EmulatedInputServer>("eis-" + sessionId);
sessions.emplace(sessionHandle, session);
std::unordered_map<std::string, sdbus::Variant> results;
results["capabilities"] = (uint)3;
results["session_id"] = sessionId;
auto reply = call.createReply();
reply << (uint32_t)0;
reply << results;
reply.send();
}
void CInputCapturePortal::onGetZones(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New GetZones request:");
sdbus::ObjectPath requestHandle, sessionHandle;
call >> requestHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
Debug::log(LOG, "[input-capture] | {}", requestHandle.c_str());
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return;
std::vector<sdbus::Struct<uint32_t, uint32_t, int32_t, int32_t>> zones;
for (auto& o : g_pPortalManager->getAllOutputs()) {
zones.push_back(sdbus::Struct(o->width, o->height, o->x, o->y));
}
std::unordered_map<std::string, sdbus::Variant> results;
results["zones"] = zones;
results["zone_set"] = ++lastZoneSet;
auto reply = call.createReply();
reply << (uint32_t)0;
reply << results;
reply.send();
}
void CInputCapturePortal::onSetPointerBarriers(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New SetPointerBarriers request:");
sdbus::ObjectPath requestHandle, sessionHandle;
call >> requestHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
Debug::log(LOG, "[input-capture] | {}", requestHandle.c_str());
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return complete(call);
std::unordered_map<std::string, sdbus::Variant> options;
call >> options;
std::vector<std::unordered_map<std::string, sdbus::Variant>> barriers;
call >> barriers;
uint32_t zoneSet;
call >> zoneSet;
Debug::log(LOG, "[input-capture] | zoneSet: {}", zoneSet);
if (zoneSet != lastZoneSet) {
Debug::log(WARN, "[input-capture] Invalid zone set discarding barriers");
complete(call); //TODO: We should return failed_barries
return;
}
sessions[sessionHandle]->barriers.clear();
for (const auto& b : barriers) {
uint id = b.at("barrier_id");
int x1, y1, x2, y2;
sdbus::Struct<int, int, int, int> p = b.at("position");
x1 = p.get<0>();
y1 = p.get<1>();
x2 = p.get<2>();
y2 = p.get<3>();
Debug::log(LOG, "[input-capture] | barrier: {}, [{}, {}] [{}, {}]", id, x1, y1, x2, y2);
sessions[sessionHandle]->barriers[id] = {id, x1, y1, x2, y2};
}
std::vector<uint> failedBarriers;
std::unordered_map<std::string, sdbus::Variant> results;
results["failed_barriers"] = failedBarriers;
auto reply = call.createReply();
reply << (uint32_t)0;
reply << results;
reply.send();
}
void CInputCapturePortal::onDisable(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New Disable request:");
sdbus::ObjectPath sessionHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return complete(call);
disable(sessionHandle);
complete(call);
}
void CInputCapturePortal::onEnable(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New Enable request:");
sdbus::ObjectPath sessionHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return complete(call);
sessions[sessionHandle]->status = ENABLED;
complete(call);
}
void CInputCapturePortal::onRelease(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New Release request:");
sdbus::ObjectPath sessionHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return complete(call);
std::unordered_map<std::string, sdbus::Variant> options;
call >> options;
uint32_t activationId = options["activation_id"];
if (activationId != sessions[sessionHandle]->activationId) {
Debug::log(WARN, "[input-capture] Invalid activation id {} expected {}", activationId, sessions[sessionHandle]->activationId);
complete(call);
return;
}
deactivate(sessionHandle);
//TODO: maybe warp pointer
complete(call);
}
void CInputCapturePortal::onConnectToEIS(sdbus::MethodCall& call) {
Debug::log(LOG, "[input-capture] New ConnectToEIS request:");
sdbus::ObjectPath sessionHandle;
call >> sessionHandle;
std::string appID;
call >> appID;
std::unordered_map<std::string, sdbus::Variant> options;
call >> options;
Debug::log(LOG, "[input-capture] | {}", sessionHandle.c_str());
Debug::log(LOG, "[input-capture] | appid: {}", appID);
if (!sessionValid(sessionHandle))
return complete(call);
int sockfd = sessions[sessionHandle]->eis->getFileDescriptor();
Debug::log(LOG, "[input-capture] Connected to the socket. File descriptor: {}", sockfd);
auto reply = call.createReply();
reply << (sdbus::UnixFd)sockfd;
reply.send();
}
bool CInputCapturePortal::sessionValid(sdbus::ObjectPath sessionHandle) {
if (!sessions.contains(sessionHandle)) {
Debug::log(WARN, "[input-capture] Unknown session handle: {}", sessionHandle.c_str());
return false;
}
return sessions[sessionHandle]->status != STOPPED;
}
bool get_line_intersection(double p0_x, double p0_y, double p1_x, double p1_y, double p2_x, double p2_y, double p3_x, double p3_y, double* i_x, double* i_y) {
float s1_x, s1_y, s2_x, s2_y;
s1_x = p1_x - p0_x;
s1_y = p1_y - p0_y;
s2_x = p3_x - p2_x;
s2_y = p3_y - p2_y;
float s, t;
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
// Collision detected
if (i_x != NULL)
*i_x = p0_x + (t * s1_x);
if (i_y != NULL)
*i_y = p0_y + (t * s1_y);
return 1;
}
return 0; // No collision
}
bool testCollision(Barrier barrier, double px, double py, double nx, double ny) {
return get_line_intersection(barrier.x1, barrier.y1, barrier.x2, barrier.y2, px, py, nx, ny, nullptr, nullptr);
}
uint32_t CInputCapturePortal::Session::isColliding(double px, double py, double nx, double ny) {
for (const auto& [key, value] : barriers) {
if (testCollision(value, px, py, nx, ny)) {
return key;
}
}
return 0;
}
void CInputCapturePortal::onAbsoluteMotion(double x, double y, double dx, double dy) {
for (const auto& [key, session] : sessions) {
int matched = session->isColliding(x, y, x - dx, y - dy);
if (matched != 0) {
activate(key, x, y, matched);
}
session->motion(dx, dy);
}
}
void CInputCapturePortal::onKey(uint32_t key, bool pressed) {
for (const auto& [_, value] : sessions) {
value->key(key, pressed);
}
}
void CInputCapturePortal::onButton(uint32_t button, bool pressed) {
for (const auto& [_, session] : sessions) {
session->button(button, pressed);
}
}
void CInputCapturePortal::onAxis(bool axis, double value) {
for (const auto& [_, session] : sessions) {
session->axis(axis, value);
}
}
void CInputCapturePortal::onAxisValue120(bool axis, int32_t value120) {
for (const auto& [_, session] : sessions) {
session->axisValue120(axis, value120);
}
}
void CInputCapturePortal::onAxisStop(bool axis) {
for (const auto& [_, session] : sessions) {
session->axisStop(axis);
}
}
void CInputCapturePortal::onFrame() {
for (const auto& [_, session] : sessions) {
session->frame();
}
}
void CInputCapturePortal::activate(sdbus::ObjectPath sessionHandle, double x, double y, uint32_t borderId) {
if (!sessionValid(sessionHandle))
return;
auto session = sessions[sessionHandle];
if (!session->activate(x, y, borderId))
return;
auto signal = m_pObject->createSignal(INTERFACE_NAME, "Activated");
signal << sessionHandle;
g_pPortalManager->m_sPortals.inputCapture->INTERFACE_NAME;
std::unordered_map<std::string, sdbus::Variant> results;
results["activation_id"] = session->activationId;
results["cursor_position"] = sdbus::Struct<double, double>(x, y);
results["barrier_id"] = borderId;
signal << results;
m_pObject->emitSignal(signal);
}
bool CInputCapturePortal::Session::activate(double x, double y, uint32_t borderId) {
if (status != ENABLED) {
return false;
}
activationId += 5;
status = ACTIVATED;
Debug::log(LOG, "[input-capture] Input captured for {} activationId: {}", sessionHandle.c_str(), activationId);
eis->startEmulating(activationId);
//TODO: capture the pointer
return true;
}
void CInputCapturePortal::deactivate(sdbus::ObjectPath sessionHandle) {
if (!sessionValid(sessionHandle))
return;
auto session = sessions[sessionHandle];
if (!session->deactivate())
return;
auto signal = m_pObject->createSignal(INTERFACE_NAME, "Deactivated");
signal << sessionHandle;
std::unordered_map<std::string, sdbus::Variant> options;
options["activation_id"] = session->activationId;
signal << options;
m_pObject->emitSignal(signal);
//TODO: release the pointer
}
bool CInputCapturePortal::Session::deactivate() {
if (status != ACTIVATED) {
return false;
}
Debug::log(LOG, "[input-capture] Input released for {}", sessionHandle.c_str());
eis->stopEmulating();
status = ENABLED;
return true;
}
void CInputCapturePortal::zonesChanged() {
if (sessions.empty())
return;
Debug::log(LOG, "[input-capture] Monitor layout has changed, notifing clients");
for (auto& [key, value] : sessions) {
if (!value->zoneChanged())
continue;
auto signal = m_pObject->createSignal(INTERFACE_NAME, "Deactivated");
signal << key;
std::unordered_map<std::string, sdbus::Variant> options;
signal << options;
m_pObject->emitSignal(signal);
}
}
bool CInputCapturePortal::Session::zoneChanged() {
//TODO: notify EIS
return true;
}
void CInputCapturePortal::disable(sdbus::ObjectPath sessionHandle) {
if (!sessionValid(sessionHandle))
return;
auto session = sessions[sessionHandle];
if (!session->disable())
return;
if (session->status == ACTIVATED)
deactivate(sessionHandle);
auto signal = m_pObject->createSignal(INTERFACE_NAME, "Disable");
signal << sessionHandle;
std::unordered_map<std::string, sdbus::Variant> options;
signal << options;
m_pObject->emitSignal(signal);
}
bool CInputCapturePortal::Session::disable() {
status = STOPPED;
Debug::log(LOG, "[input-capture] Session {} disabled", sessionHandle.c_str());
return true;
}
void CInputCapturePortal::Session::motion(double dx, double dy) {
if (status != ACTIVATED)
return;
eis->sendMotion(dx, dy);
}
void CInputCapturePortal::Session::key(uint32_t key, bool pressed) {
if (status != ACTIVATED)
return;
eis->sendKey(key, pressed);
}
void CInputCapturePortal::Session::button(uint32_t button, bool pressed) {
if (status != ACTIVATED)
return;
eis->sendButton(button, pressed);
}
void CInputCapturePortal::Session::axis(bool axis, double value) {
if (status != ACTIVATED)
return;
double x = 0;
double y = 0;
if (axis) {
x = value;
} else {
y = value;
}
eis->sendScrollDelta(x, y);
}
void CInputCapturePortal::Session::axisValue120(bool axis, int32_t value) {
if (status != ACTIVATED)
return;
int32_t x = 0;
int32_t y = 0;
if (axis) {
x = value;
} else {
y = value;
}
eis->sendScrollDiscrete(x, y);
}
void CInputCapturePortal::Session::axisStop(bool axis) {
eis->sendScrollStop(axis, !axis);
}
void CInputCapturePortal::Session::frame() {
eis->sendPointerFrame();
}

View file

@ -0,0 +1,96 @@
#pragma once
#include "hyprland-input-capture-v1.hpp"
#include "shared/Eis.hpp"
#include <cstdint>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <unordered_map>
#include <wayland-client-protocol.h>
#include "../includes.hpp"
#include "../shared/Session.hpp"
typedef int ClientStatus;
const ClientStatus CREATED = 0; //Is ready to be activated
const ClientStatus ENABLED = 1; //Is ready for receiving inputs
const ClientStatus ACTIVATED = 2; //Currently receiving inputs
const ClientStatus STOPPED = 3; //Can no longer be activated
struct Barrier {
uint id;
int x1, y1, x2, y2;
};
class CInputCapturePortal {
public:
CInputCapturePortal(SP<CCHyprlandInputCaptureManagerV1> mgr);
void onCreateSession(sdbus::MethodCall& methodCall);
void onGetZones(sdbus::MethodCall& methodCall);
void onSetPointerBarriers(sdbus::MethodCall& methodCall);
void onEnable(sdbus::MethodCall& methodCall);
void onDisable(sdbus::MethodCall& methodCall);
void onRelease(sdbus::MethodCall& methodCall);
void onConnectToEIS(sdbus::MethodCall& methodCall);
void onAbsoluteMotion(double x, double y, double dx, double dy);
void onKey(uint32_t key, bool pressed);
void onButton(uint32_t button, bool pressed);
void onAxis(bool axis, double value);
void onAxisValue120(bool axis, int32_t value120);
void onAxisStop(bool axis);
void onFrame();
void zonesChanged();
struct Session {
std::string appid;
sdbus::ObjectPath requestHandle, sessionHandle;
std::string sessionId;
uint32_t capabilities;
std::unique_ptr<SDBusRequest> request;
std::unique_ptr<SDBusSession> session;
std::unique_ptr<EmulatedInputServer> eis;
std::unordered_map<uint32_t, Barrier> barriers;
uint32_t activationId;
ClientStatus status;
//
bool activate(double x, double y, uint32_t borderId);
bool deactivate();
bool disable();
bool zoneChanged();
void motion(double dx, double dy);
void key(uint32_t key, bool pressed);
void button(uint32_t button, bool pressed);
void axis(bool axis, double value);
void axisValue120(bool axis, int32_t value120);
void axisStop(bool axis);
void frame();
uint32_t isColliding(double px, double py, double nx, double ny);
};
private:
struct {
SP<CCHyprlandInputCaptureManagerV1> manager;
} m_sState;
std::unordered_map<std::string, const std::shared_ptr<Session>> sessions;
//
std::unique_ptr<sdbus::IObject> m_pObject;
uint sessionCounter;
uint lastZoneSet;
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.InputCapture";
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
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);
};

287
src/shared/Eis.cpp Normal file
View file

@ -0,0 +1,287 @@
#include "Eis.hpp"
#include "core/PortalManager.hpp"
#include "src/helpers/Log.hpp"
#include <libeis.h>
#include <string>
#include <sys/poll.h>
#include <thread>
EmulatedInputServer::EmulatedInputServer(std::string socketName) {
Debug::log(LOG, "[EIS] init socket: {}", socketName);
const char* xdg = getenv("XDG_RUNTIME_DIR");
if (xdg)
socketPath = std::string(xdg) + "/" + socketName;
if (socketPath.empty()) {
Debug::log(ERR, "[EIS] Socket path is empty");
return;
}
client.handle = NULL;
client.seat = NULL;
client.pointer = NULL;
client.keyboard = NULL;
eis = eis_new(NULL);
if (eis_setup_backend_socket(eis, socketPath.c_str())) {
Debug::log(ERR, "[EIS] Cannot init eis socket on {}", socketPath);
return;
}
Debug::log(LOG, "[EIS] Listening on {}", socketPath);
stop = false;
std::thread thread(&EmulatedInputServer::listen, this);
thread.detach();
}
void EmulatedInputServer::listen() {
struct pollfd fds = {
.fd = eis_get_fd(eis),
.events = POLLIN,
.revents = 0,
};
int nevents;
//Pull foverer events
while (!stop && (nevents = poll(&fds, 1, 1000)) > -1) {
eis_dispatch(eis);
//Pull every availaible events
while (true) {
eis_event* e = eis_get_event(eis);
if (!e) {
eis_event_unref(e);
break;
}
int rc = onEvent(e);
eis_event_unref(e);
if (rc != 0)
break;
}
}
}
int EmulatedInputServer::onEvent(eis_event* e) {
eis_client* client;
eis_seat* seat;
eis_device* device;
switch (eis_event_get_type(e)) {
case EIS_EVENT_CLIENT_CONNECT:
client = eis_event_get_client(e);
Debug::log(LOG, "[EIS] {} client connected: {}", eis_client_is_sender(client) ? "sender" : "receiver", eis_client_get_name(client));
if (eis_client_is_sender(client)) {
Debug::log(WARN, "[EIS] Unexpected sender client {} connected to input capture session", eis_client_get_name(client));
eis_client_disconnect(client);
return 0;
}
if (this->client.handle != nullptr) {
Debug::log(WARN, "[EIS] Unexpected additional client {} connected to input capture session", eis_client_get_name(client));
eis_client_disconnect(client);
return 0;
}
this->client.handle = client;
eis_client_connect(client);
Debug::log(LOG, "[EIS] creating new default seat");
seat = eis_client_new_seat(client, "default");
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL);
eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD);
eis_seat_add(seat);
this->client.seat = seat;
break;
case EIS_EVENT_CLIENT_DISCONNECT:
client = eis_event_get_client(e);
Debug::log(LOG, "[EIS] {} disconnected", eis_client_get_name(client));
eis_client_disconnect(client);
eis_seat_unref(this->client.seat);
clearPointer();
clearKeyboard();
this->client.handle = NULL;
break;
case EIS_EVENT_SEAT_BIND:
Debug::log(LOG, "[EIS] Binding seats...");
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) && eis_event_seat_has_capability(e, EIS_DEVICE_CAP_BUTTON) &&
eis_event_seat_has_capability(e, EIS_DEVICE_CAP_SCROLL))
ensurePointer(e);
else
clearPointer();
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD))
ensureKeyboard(e);
else
clearKeyboard();
break;
case EIS_EVENT_DEVICE_CLOSED:
device = eis_event_get_device(e);
if (device == this->client.pointer) {
clearPointer();
} else if (device == this->client.keyboard) {
Debug::log(LOG, "[EIS] Clearing keyboard");
clearKeyboard();
} else {
Debug::log(WARN, "[EIS] Unknown device to close");
}
break;
case EIS_EVENT_FRAME: Debug::log(LOG, "[EIS] Got event EIS_EVENT_FRAME"); break;
case EIS_EVENT_DEVICE_START_EMULATING: Debug::log(LOG, "[EIS] Got event EIS_EVENT_DEVICE_START_EMULATING"); break;
case EIS_EVENT_DEVICE_STOP_EMULATING: Debug::log(LOG, "[EIS] Got event EIS_EVENT_DEVICE_STOP_EMULATING"); break;
case EIS_EVENT_POINTER_MOTION: Debug::log(LOG, "[EIS] Got event EIS_EVENT_POINTER_MOTION"); break;
case EIS_EVENT_POINTER_MOTION_ABSOLUTE: Debug::log(LOG, "[EIS] Got event EIS_EVENT_POINTER_MOTION_ABSOLUTE"); break;
case EIS_EVENT_BUTTON_BUTTON: Debug::log(LOG, "[EIS] Got event EIS_EVENT_BUTTON_BUTTON"); break;
case EIS_EVENT_SCROLL_DELTA: Debug::log(LOG, "[EIS] Got event EIS_EVENT_SCROLL_DELTA"); break;
case EIS_EVENT_SCROLL_STOP: Debug::log(LOG, "[EIS] Got event EIS_EVENT_SCROLL_STOP"); break;
case EIS_EVENT_SCROLL_CANCEL: Debug::log(LOG, "[EIS] Got event EIS_EVENT_SCROLL_CANCEL"); break;
case EIS_EVENT_SCROLL_DISCRETE: Debug::log(LOG, "[EIS] Got event EIS_EVENT_SCROLL_DISCRETE"); break;
case EIS_EVENT_KEYBOARD_KEY: Debug::log(LOG, "[EIS] Got event EIS_EVENT_KEYBOARD_KEY"); break;
case EIS_EVENT_TOUCH_DOWN: Debug::log(LOG, "[EIS] Got event EIS_EVENT_TOUCH_DOWN"); break;
case EIS_EVENT_TOUCH_UP: Debug::log(LOG, "[EIS] Got event EIS_EVENT_TOUCH_UP"); break;
case EIS_EVENT_TOUCH_MOTION: Debug::log(LOG, "[EIS] Got event EIS_EVENT_TOUCH_MOTION"); break;
}
return 0;
}
void EmulatedInputServer::ensurePointer(eis_event* event) {
if (client.pointer != nullptr)
return;
struct eis_device* pointer = eis_seat_new_device(client.seat);
eis_device_configure_name(pointer, "captured relative pointer");
eis_device_configure_capability(pointer, EIS_DEVICE_CAP_POINTER);
eis_device_configure_capability(pointer, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability(pointer, EIS_DEVICE_CAP_SCROLL);
for (auto& o : g_pPortalManager->getAllOutputs()) {
struct eis_region* r = eis_device_new_region(pointer);
eis_region_set_offset(r, o->x, o->y);
eis_region_set_size(r, o->width, o->height);
eis_region_set_physical_scale(r, o->scale);
eis_region_add(r);
eis_region_unref(r);
}
eis_device_add(pointer);
eis_device_resume(pointer);
client.pointer = pointer;
}
void EmulatedInputServer::ensureKeyboard(eis_event* event) {
if (client.keyboard != nullptr)
return;
struct 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
eis_device_add(keyboard);
eis_device_resume(keyboard);
client.keyboard = keyboard;
}
//TODO: remove and re-add devices when monitors change (see: mutter/meta-input-capture-session.c:1107)
void EmulatedInputServer::clearPointer() {
if (client.pointer == nullptr)
return;
Debug::log(LOG, "[EIS] Clearing pointer");
eis_device_remove(client.pointer);
eis_device_unref(client.pointer);
client.pointer = nullptr;
}
void EmulatedInputServer::clearKeyboard() {
if (client.keyboard == nullptr)
return;
Debug::log(LOG, "[EIS] Clearing keyboard");
eis_device_remove(client.keyboard);
eis_device_unref(client.keyboard);
client.keyboard = nullptr;
}
int EmulatedInputServer::getFileDescriptor() {
return eis_backend_fd_add_client(eis);
}
void EmulatedInputServer::startEmulating(int sequence) {
Debug::log(LOG, "[EIS] Start Emulating");
if (client.pointer != nullptr)
eis_device_start_emulating(client.pointer, sequence);
if (client.keyboard != nullptr)
eis_device_start_emulating(client.keyboard, sequence);
}
void EmulatedInputServer::stopEmulating() {
Debug::log(LOG, "[EIS] Stop Emulating");
if (client.pointer != nullptr)
eis_device_stop_emulating(client.pointer);
if (client.keyboard != nullptr)
eis_device_stop_emulating(client.keyboard);
}
void EmulatedInputServer::sendMotion(double x, double y) {
if (client.pointer == nullptr)
return;
eis_device_pointer_motion(client.pointer, x, y);
}
void EmulatedInputServer::sendKey(uint32_t key, bool pressed) {
if (client.keyboard == nullptr)
return;
uint64_t now = eis_now(eis);
eis_device_keyboard_key(client.keyboard, key, pressed);
eis_device_frame(client.keyboard, now);
}
void EmulatedInputServer::sendButton(uint32_t button, bool pressed) {
if (client.pointer == nullptr)
return;
eis_device_button_button(client.pointer, button, pressed);
}
void EmulatedInputServer::sendScrollDiscrete(int32_t x, int32_t y) {
if (client.pointer == nullptr)
return;
eis_device_scroll_discrete(client.pointer, x, y);
}
void EmulatedInputServer::sendScrollDelta(double x, double y) {
if (client.pointer == nullptr)
return;
eis_device_scroll_delta(client.pointer, x, y);
}
void EmulatedInputServer::sendScrollStop(bool x, bool y) {
if (client.pointer == nullptr)
return;
eis_device_scroll_stop(client.pointer, x, y);
}
void EmulatedInputServer::sendPointerFrame() {
if (client.pointer == nullptr)
return;
uint64_t now = eis_now(eis);
eis_device_frame(client.pointer, now);
}
void EmulatedInputServer::stopServer() {
stop = true;
}

49
src/shared/Eis.hpp Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <libeis.h>
#include <string>
#include <sys/socket.h>
struct EisClient {
struct eis_client* handle;
struct eis_seat* seat;
struct eis_device* pointer;
struct eis_device* keyboard;
};
/*
* Responsible to creating a socket for input communication
*/
class EmulatedInputServer {
public:
EmulatedInputServer(std::string socketPath);
std::string socketPath;
void startEmulating(int activationId);
void stopEmulating();
void sendMotion(double x, double y);
void sendKey(uint32_t key, bool pressed);
void sendButton(uint32_t button, bool pressed);
void sendScrollDelta(double x, double y);
void sendScrollDiscrete(int32_t x, int32_t y);
void sendScrollStop(bool stopX, bool stopY);
void sendPointerFrame();
int getFileDescriptor();
void stopServer();
private:
bool stop;
struct eis* eis;
EisClient client;
int onEvent(eis_event* e);
void listen();
void ensurePointer(eis_event* event);
void ensureKeyboard(eis_event* event);
void clearPointer();
void clearKeyboard();
};

@ -1 +1 @@
Subproject commit 4d29e48433270a2af06b8bc711ca1fe5109746cd
Subproject commit 53a994b2efbcc19862125fc9a8d5a752a24a0f20