mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-12-27 03:49:48 +01:00
WIP: input capture
This commit is contained in:
parent
3e884d941c
commit
9440e6a80f
14 changed files with 1077 additions and 14 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'),
|
||||
|
|
596
src/portals/InputCapture.cpp
Normal file
596
src/portals/InputCapture.cpp
Normal 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();
|
||||
}
|
96
src/portals/InputCapture.hpp
Normal file
96
src/portals/InputCapture.hpp
Normal 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
287
src/shared/Eis.cpp
Normal 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
49
src/shared/Eis.hpp
Normal 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
|
Loading…
Reference in a new issue