input-capture: impl protocol (#7919)

Co-authored-by: Gerben Meijer <infernix@gmail.com>
Co-authored-by: Emerson Matos <emegson@live.com>
Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Co-authored-by: Ashley Stonham <reddrop@reddrop.net>
Co-authored-by: extra-salad <87635414+extra-salad@users.noreply.github.com>
This commit is contained in:
Gwilherm Folliot
2026-07-15 23:56:05 +02:00
committed by GitHub
co-authored by Gerben Meijer Emerson Matos Mihai Fufezan Ashley Stonham extra-salad
parent d7fc7240f4
commit 0b0d7ede21
33 changed files with 1210 additions and 88 deletions
+1
View File
@@ -31,6 +31,7 @@ runs:
libc++ \
libdisplay-info \
libdrm \
libei \
libepoxy \
libfontenc \
libglvnd \
+5 -3
View File
@@ -252,8 +252,7 @@ set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.91)
set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.49)
set(LIBINPUT_MINIMUM_VERSION 1.29)
pkg_check_modules(
deps
pkg_check_modules(deps
REQUIRED
IMPORTED_TARGET GLOBAL
xkbcommon>=${XKBCOMMON_MINIMUM_VERSION}
@@ -267,6 +266,7 @@ pkg_check_modules(
xcursor
libdrm
libinput>=${LIBINPUT_MINIMUM_VERSION}
libeis-1.0
gbm
gio-2.0
re2
@@ -514,7 +514,7 @@ else()
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
endif()
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.7.0)
if(hyprland_protocols_dep_FOUND)
pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir)
message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}")
@@ -593,6 +593,8 @@ protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" fal
protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false)
protocolnew("staging/ext-background-effect" "ext-background-effect-v1" false)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-input-capture-v1"
true)
protocolwayland()
# tools
+1
View File
@@ -667,6 +667,7 @@ def generate_stub(root: Path) -> str:
("description", "string", True),
("desc", "string", True),
("device", "{inclusive?: boolean, list?: string[]}", True),
("allow_input_capture", "boolean", True),
],
)
)
+2
View File
@@ -25,6 +25,7 @@
lcms2,
libGL,
libdrm,
libei,
libexecinfo,
libinput,
libxcb,
@@ -191,6 +192,7 @@ customStdenv.mkDerivation (finalAttrs: {
libdrm
libgbm
libGL
libei
libinput
libuuid
libxcursor
+6 -1
View File
@@ -1510,6 +1510,7 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
bool drag = false;
bool submapUniversal = false;
bool isPerDevice = false;
bool allowInputCapture = false;
const auto BINDARGS = command.substr(4);
for (auto const& arg : BINDARGS) {
@@ -1536,6 +1537,7 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
break;
case 'u': submapUniversal = true; break;
case 'k': isPerDevice = true; break;
case 'x': allowInputCapture = true; break;
default: return "bind: invalid flag";
}
}
@@ -1645,7 +1647,8 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
drag,
submapUniversal,
deviceInclusive,
devices});
devices,
allowInputCapture});
}
return {};
@@ -1904,6 +1907,8 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
type = PERMISSION_TYPE_PLUGIN;
else if (data[1] == "keyboard" || data[1] == "keeb")
type = PERMISSION_TYPE_KEYBOARD;
else if (data[1] == "input-capture")
type = PERMISSION_TYPE_INPUT_CAPTURE;
if (data[2] == "ask")
mode = PERMISSION_RULE_ALLOW_MODE_ASK;
@@ -789,6 +789,10 @@ static SDispatchResult forceidle(const std::string& args) {
return wrap(Actions::forceIdle(duration.value()));
}
static SDispatchResult releaseInputCapture(const std::string& args) {
return wrap(Actions::releaseInputCapture());
}
CDispatcherTranslator::CDispatcherTranslator() {
m_dispMap["exec"] = ::exec;
m_dispMap["execr"] = ::execr;
@@ -860,4 +864,5 @@ CDispatcherTranslator::CDispatcherTranslator() {
m_dispMap["global"] = ::globalDispatcher;
m_dispMap["setprop"] = ::setprop;
m_dispMap["forceidle"] = ::forceidle;
m_dispMap["releaseinputcapture"] = ::releaseInputCapture;
}
@@ -1,6 +1,7 @@
#include "LuaBindingsInternal.hpp"
#include <hyprutils/string/String.hpp>
#include <lua.h>
#include "Check.hpp"
@@ -12,6 +13,7 @@
#include "../../../state/WorkspaceState.hpp"
#include "../../../devices/IKeyboard.hpp"
#include "../../../desktop/rule/windowRule/WindowRule.hpp"
#include "config/shared/actions/ConfigActions.hpp"
using namespace Config;
using namespace Config::Lua;
@@ -224,6 +226,10 @@ static int dsp_forceIdle(lua_State* L) {
return Internal::checkResult(L, CA::forceIdle((float)lua_tonumber(L, lua_upvalueindex(1))));
}
static int dsp_releaseInputCapture(lua_State* L) {
return Internal::checkResult(L, CA::releaseInputCapture());
}
static int hlExecCmd(lua_State* L) {
const auto proc = Check::string(L, 1);
@@ -339,6 +345,11 @@ static int hlForceIdle(lua_State* L) {
return 1;
}
static int hlReleaseInputCapture(lua_State* L) {
lua_pushcclosure(L, dsp_releaseInputCapture, 1);
return 1;
}
static std::expected<uint32_t, std::string> resolveKeycode(const std::string& key) {
if (isNumber(key) && std::stoi(key) > 9)
return (uint32_t)std::stoi(key);
@@ -1394,6 +1405,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
Internal::setFn(L, "global", hlGlobal);
Internal::setFn(L, "force_renderer_reload", hlForceRendererReload);
Internal::setFn(L, "force_idle", hlForceIdle);
Internal::setFn(L, "release_input_capture", hlReleaseInputCapture);
Internal::setFn(L, "focus", hlFocus);
Internal::setFn(L, "no_op", hlNoop);
}
@@ -246,6 +246,7 @@ static int hlBind(lua_State* L) {
}
lua_pop(L, 1);
}
kb.allowInputCapture = getBool("allow_input_capture");
lua_pop(L, 1);
}
+2
View File
@@ -165,6 +165,8 @@ static int keybindIndex(lua_State* L) {
lua_pushboolean(L, (*keybind)->deviceInclusive);
else if (key == "devices")
pushDeviceList(L, **keybind);
else if (key == "allow_input_capture")
lua_pushboolean(L, (*keybind)->allowInputCapture);
else
lua_pushnil(L);
@@ -22,6 +22,7 @@
#include "../../../config/shared/monitor/MonitorRuleManager.hpp"
#include "../../../protocols/IdleNotify.hpp"
#include "../../../protocols/GlobalShortcuts.hpp"
#include "../../../protocols/InputCapture.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../managers/XWaylandManager.hpp"
#include "../../../layout/algorithm/Algorithm.hpp"
@@ -1761,3 +1762,8 @@ ActionResult Actions::moveIntoOrCreateGroup(Math::eDirection dir, std::optional<
return {};
}
ActionResult Actions::releaseInputCapture() {
PROTO::inputCapture->forceRelease();
return {};
}
@@ -104,6 +104,8 @@ namespace Config::Actions {
ActionResult denyWindowFromGroup(eTogglableAction action);
ActionResult moveIntoOrCreateGroup(Math::eDirection dir, std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult releaseInputCapture();
class CActionState {
public:
CActionState() = default;
+6
View File
@@ -713,6 +713,12 @@ std::vector<SP<IValue>> Values::getConfigValues() {
MS<Bool>("experimental:wp_cm_1_2", "Allow wp-cm-v1 version 2", true),
/*
* input_capture:
*/
MS<Bool>("input-capture:capture_modifiers", "If enabled, modifiers are also captured and sent to the program", false),
MS<Bool>("input-capture:enforce_barriers", "If enabled, throw a wayland error when a invalid barrier is received", true),
/*
* quirks:
*/
+6 -3
View File
@@ -1041,6 +1041,8 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
ret += "a";
if (kb->hasDescription)
ret += "d";
if (kb->allowInputCapture)
ret += "x";
ret += std::format("\n\tmodmask: {}\n\tsubmap: {}\n\tkey: {}\n\tkeycode: {}\n\tcatchall: {}\n\tdescription: {}\n\tdispatcher: {}\n\targ: {}\n\n", kb->modmask,
kb->submap.name, kb->key.empty() ? kb->displayKey : kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg);
@@ -1067,13 +1069,14 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
"keycode": {},
"catch_all": {},
"description": "{}",
"allow_input_capture": {},
"dispatcher": "{}",
"arg": "{}"
}},)#",
kb->locked ? "true" : "false", kb->mouse ? "true" : "false", kb->release ? "true" : "false", kb->repeat ? "true" : "false", kb->longPress ? "true" : "false",
kb->nonConsuming ? "true" : "false", kb->autoConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name),
kb->submapUniversal, escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler),
escapeJSONStrings(kb->arg));
kb->nonConsuming ? "true" : "false", kb->autoConsuming ? "true" : "false", kb->allowInputCapture ? "true" : "false", kb->hasDescription ? "true" : "false",
kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal, escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false",
escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), escapeJSONStrings(kb->arg));
}
trimTrailingComma(ret);
ret += "]";
+2
View File
@@ -221,6 +221,8 @@ I18n::CI18nEngine::CI18nEngine() {
registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "An application <b>{app}</b> is trying to capture your cursor position.\n\nDo you want to allow it to?");
registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "An application <b>{app}</b> is trying to load a plugin: <b>{plugin}</b>.\n\nDo you want to allow it to?");
registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A new keyboard has been detected: <b>{keyboard}</b>.\n\nDo you want to allow it to operate?");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_INPUT_CAPTURE,
"An application <b>{app}</b> is trying to capture your inputs.\n\nDo you want to allow it to do so?");
registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unknown)");
registerEntry("en_US", TXT_KEY_PERMISSION_TITLE, "Permission request");
registerEntry("en_US", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: you can set persistent rules for these in the Hyprland config file.");
+1
View File
@@ -19,6 +19,7 @@ namespace I18n {
TXT_KEY_PERMISSION_REQUEST_CURSOR_POS,
TXT_KEY_PERMISSION_REQUEST_PLUGIN,
TXT_KEY_PERMISSION_REQUEST_KEYBOARD,
TXT_KEY_PERMISSION_REQUEST_INPUT_CAPTURE,
TXT_KEY_PERMISSION_UNKNOWN_NAME,
TXT_KEY_PERMISSION_TITLE,
TXT_KEY_PERMISSION_PERSISTENCE_HINT,
+7 -2
View File
@@ -5,6 +5,7 @@
#include "../devices/IKeyboard.hpp"
#include "../managers/SeatManager.hpp"
#include "../managers/fullscreen/FullscreenController.hpp"
#include "../protocols/InputCapture.hpp"
#include "../protocols/ShortcutsInhibit.hpp"
#include "../protocols/Hotkey.hpp"
#include "../protocols/core/DataDevice.hpp"
@@ -107,7 +108,8 @@ CKeybindManager::CKeybindManager() {
"event",
"global",
"setprop",
"forceidle"}) {
"forceidle",
"releaseinputcapture"}) {
m_dispatchers[name] = [n = std::string(name)](std::string args) -> SDispatchResult { return Config::Legacy::translator()->run(n, args); };
}
@@ -372,7 +374,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP<IKeyboard> pKeyboard) {
// handleInternalKeybinds returns true when the key should be suppressed,
// while this function returns true when the key event should be sent
if (handleInternalKeybinds(internalKeysym))
if (!PROTO::inputCapture->isCaptured() && handleInternalKeybinds(internalKeysym))
return false;
const auto MODS = g_pInputManager->getModsFromAllKBs();
@@ -630,6 +632,9 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
std::vector<SP<SKeybind>> bindsHit;
for (auto& k : m_keybinds) {
if (PROTO::inputCapture->isCaptured() && !k->allowInputCapture)
continue;
const bool SPECIALDISPATCHER = k->handler == "global" || k->handler == "pass" || k->handler == "sendshortcut" || k->handler == "mouse" || k->releasePending;
const bool SPECIALTRIGGERED = std::ranges::find_if(m_pressedSpecialBinds, [&](const auto& other) { return other == k; }) != m_pressedSpecialBinds.end();
const bool IGNORECONDITIONS =
+2
View File
@@ -59,6 +59,7 @@ struct SKeybind {
bool submapUniversal = false;
bool deviceInclusive = false;
std::unordered_set<std::string> devices = {};
bool allowInputCapture = false;
bool enabled = true;
std::string displayKey = "";
@@ -177,6 +178,7 @@ class CKeybindManager {
void updateXKBTranslationState();
bool ensureMouseBindState();
static SDispatchResult releaseInputCapture(std::string);
friend class CCompositor;
friend class CInputManager;
+3
View File
@@ -49,6 +49,7 @@
#include "../protocols/SinglePixel.hpp"
#include "../protocols/SecurityContext.hpp"
#include "../protocols/CTMControl.hpp"
#include "../protocols/InputCapture.hpp"
#include "../protocols/HyprlandSurface.hpp"
#include "../protocols/ImageCaptureSource.hpp"
#include "../protocols/ImageCopyCapture.hpp"
@@ -207,6 +208,7 @@ CProtocolManager::CProtocolManager() {
PROTO::singlePixel = makeUnique<CSinglePixelProtocol>(&wp_single_pixel_buffer_manager_v1_interface, 1, "SinglePixel");
PROTO::securityContext = makeUnique<CSecurityContextProtocol>(&wp_security_context_manager_v1_interface, 1, "SecurityContext");
PROTO::ctm = makeUnique<CHyprlandCTMControlProtocol>(&hyprland_ctm_control_manager_v1_interface, 2, "CTMControl");
PROTO::inputCapture = makeUnique<CInputCaptureProtocol>(&hyprland_input_capture_manager_v1_interface, 1, "InputCapture");
PROTO::hyprlandSurface = makeUnique<CHyprlandSurfaceProtocol>(&hyprland_surface_manager_v1_interface, 2, "HyprlandSurface");
PROTO::contentType = makeUnique<CContentTypeProtocol>(&wp_content_type_manager_v1_interface, 1, "ContentType");
PROTO::xdgTag = makeUnique<CXDGToplevelTagProtocol>(&xdg_toplevel_tag_manager_v1_interface, 1, "XDGTag");
@@ -318,6 +320,7 @@ CProtocolManager::~CProtocolManager() {
PROTO::singlePixel.reset();
PROTO::securityContext.reset();
PROTO::ctm.reset();
PROTO::inputCapture.reset();
PROTO::hyprlandSurface.reset();
PROTO::contentType.reset();
PROTO::colorManagement.reset();
+2
View File
@@ -6,6 +6,7 @@
#include "../protocols/PrimarySelection.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/LayerShell.hpp"
#include "../protocols/InputCapture.hpp"
#include "../Compositor.hpp"
#include "../desktop/state/FocusState.hpp"
#include "../devices/IKeyboard.hpp"
@@ -168,6 +169,7 @@ void CSeatManager::updateActiveKeyboardData() {
if (m_keyboard)
PROTO::seat->updateRepeatInfo(m_keyboard->m_repeatRate, m_keyboard->m_repeatDelay);
PROTO::seat->updateKeymap();
PROTO::inputCapture->updateKeymap();
}
void CSeatManager::setKeyboardFocus(SP<CWLSurfaceResource> surf) {
+335
View File
@@ -0,0 +1,335 @@
#include "Eis.hpp"
#include "Compositor.hpp"
#include "devices/IKeyboard.hpp"
#include "managers/SeatManager.hpp"
#include "state/MonitorState.hpp"
#include "output/Monitor.hpp"
#include <alloca.h>
#include <cstdint>
#include <hyprutils/os/FileDescriptor.hpp>
#include <libeis.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-server-core.h>
CEis::CEis(std::string socketName) {
Log::logger->log(Log::INFO, "[EIS] Init socket: {}", socketName);
const char* xdg = getenv("XDG_RUNTIME_DIR");
if (xdg)
m_socketPath = std::string(xdg) + "/" + socketName;
if (m_socketPath.empty()) {
Log::logger->log(Log::ERR, "[EIS] Socket path is empty");
return;
}
m_eisCtx = eis_new(nullptr);
if (!m_eisCtx) {
Log::logger->log(Log::ERR, "[EIS] Cannot create eis context");
return;
}
if (eis_setup_backend_socket(m_eisCtx, m_socketPath.c_str())) {
Log::logger->log(Log::ERR, "[EIS] Cannot init eis socket on {}", m_socketPath);
return;
}
Log::logger->log(Log::INFO, "[EIS] Listening on {}", m_socketPath);
m_eventSource = wl_event_loop_add_fd(
g_pCompositor->m_wlEventLoop, eis_get_fd(m_eisCtx), WL_EVENT_READABLE, [](int fd, uint32_t mask, void* data) { return ((CEis*)data)->pollEvents(); }, this);
}
CEis::~CEis() {
if (m_eventSource)
wl_event_source_remove(m_eventSource);
if (m_eisCtx) {
Log::logger->log(Log::INFO, "[EIS] Server fd {} destroyed", eis_get_fd(m_eisCtx));
eis_unref(m_eisCtx);
}
}
int CEis::pollEvents() {
if (!m_eisCtx)
return 0;
eis_dispatch(m_eisCtx);
//Pull every available events
while (true) {
eis_event* e = eis_get_event(m_eisCtx);
if (!e) {
eis_event_unref(e);
break;
}
int rc = onEvent(e);
eis_event_unref(e);
if (rc != 0)
break;
}
return 0;
}
int CEis::onEvent(eis_event* e) {
eis_client* eisClient = nullptr;
eis_seat* seat = nullptr;
eis_device* device = nullptr;
switch (eis_event_get_type(e)) {
case EIS_EVENT_CLIENT_CONNECT:
eisClient = eis_event_get_client(e);
Log::logger->log(Log::INFO, "[EIS] {} client connected: {}", eis_client_is_sender(eisClient) ? "Sender" : "Receiver", eis_client_get_name(eisClient));
if (eis_client_is_sender(eisClient)) {
Log::logger->log(Log::WARN, "[EIS] Unexpected sender client {} connected to input capture session", eis_client_get_name(eisClient));
eis_client_disconnect(eisClient);
return 0;
}
if (m_client.m_handle) {
Log::logger->log(Log::WARN, "[EIS] Unexpected additional client {} connected to input capture session", eis_client_get_name(eisClient));
eis_client_disconnect(eisClient);
return 0;
}
m_client.m_handle = eisClient;
eis_client_connect(eisClient);
Log::logger->log(Log::INFO, "[EIS] Creating new default seat");
seat = eis_client_new_seat(eisClient, "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);
m_client.m_seat = seat;
break;
case EIS_EVENT_CLIENT_DISCONNECT:
eisClient = eis_event_get_client(e);
Log::logger->log(Log::INFO, "[EIS] {} disconnected", eis_client_get_name(eisClient));
eis_client_disconnect(eisClient);
eis_seat_unref(m_client.m_seat);
clearPointer();
clearKeyboard();
m_client.m_handle = nullptr;
break;
case EIS_EVENT_SEAT_BIND:
Log::logger->log(Log::INFO, "[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();
else
clearPointer();
if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD))
ensureKeyboard();
else
clearKeyboard();
break;
case EIS_EVENT_DEVICE_CLOSED:
device = eis_event_get_device(e);
if (device == m_client.m_pointer)
clearPointer();
else if (device == m_client.m_keyboard) {
clearKeyboard();
} else
Log::logger->log(Log::WARN, "[EIS] Unknown device to close");
break;
default: return 0;
}
return 0;
}
void CEis::ensurePointer() {
if (m_client.m_pointer)
return;
if (!m_client.m_seat)
return;
Log::logger->log(Log::INFO, "[EIS] Creating pointer");
eis_device* pointer = eis_seat_new_device(m_client.m_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 const& o : State::monitorState()->monitors()) {
eis_region* r = eis_device_new_region(pointer);
eis_region_set_offset(r, o->m_position.x, o->m_position.y);
eis_region_set_size(r, o->m_pixelSize.x, o->m_pixelSize.y);
eis_region_set_physical_scale(r, o->m_scale);
eis_region_add(r);
eis_region_unref(r);
}
eis_device_add(pointer);
eis_device_resume(pointer);
m_client.m_pointer = pointer;
}
void CEis::ensureKeyboard() {
if (m_client.m_keyboard)
return;
if (!m_client.m_seat)
return;
Log::logger->log(Log::INFO, "[EIS] Creating keyboard");
eis_device* keyboard = eis_seat_new_device(m_client.m_seat);
eis_device_configure_name(keyboard, "captured keyboard");
eis_device_configure_capability(keyboard, EIS_DEVICE_CAP_KEYBOARD);
SKeymap _keymap = getKeymap();
if (_keymap.m_fd != -1) {
Log::logger->log(Log::INFO, "[EIS] Using keymap {}", _keymap.m_fd, _keymap.m_size);
eis_keymap* eis_keymap = eis_device_new_keymap(keyboard, EIS_KEYMAP_TYPE_XKB, _keymap.m_fd, _keymap.m_size);
if (eis_keymap) {
eis_keymap_add(eis_keymap);
eis_keymap_unref(eis_keymap);
} else {
Log::logger->log(Log::INFO, "[EIS] Cannot open keymap");
}
}
eis_device_add(keyboard);
eis_device_resume(keyboard);
m_client.m_keyboard = keyboard;
}
CEis::SKeymap CEis::getKeymap() {
SKeymap _keymap = {.m_fd = -1, .m_size = -1};
SP<IKeyboard> keyboard = g_pSeatManager->m_keyboard.lock();
if (!keyboard)
return _keymap;
int32_t size = keyboard->m_xkbKeymapString.length() + 1;
int fd = keyboard->m_xkbKeymapFD.get();
_keymap.m_size = size;
_keymap.m_fd = fd;
return _keymap;
}
void CEis::clearPointer() {
if (!m_client.m_pointer)
return;
Log::logger->log(Log::INFO, "[EIS] Clearing pointer");
eis_device_remove(m_client.m_pointer);
eis_device_unref(m_client.m_pointer);
m_client.m_pointer = nullptr;
}
void CEis::clearKeyboard() {
if (!m_client.m_keyboard)
return;
Log::logger->log(Log::INFO, "[EIS] Clearing keyboard");
eis_device_remove(m_client.m_keyboard);
eis_device_unref(m_client.m_keyboard);
m_client.m_keyboard = nullptr;
}
int CEis::getFileDescriptor() {
if (!m_eisCtx)
return -1;
return eis_backend_fd_add_client(m_eisCtx);
}
void CEis::startEmulating(int sequence) {
Log::logger->log(Log::INFO, "[EIS] Start Emulating");
if (m_client.m_pointer)
eis_device_start_emulating(m_client.m_pointer, sequence);
if (m_client.m_keyboard)
eis_device_start_emulating(m_client.m_keyboard, sequence);
}
void CEis::stopEmulating() {
Log::logger->log(Log::INFO, "[EIS] Stop Emulating");
if (m_client.m_pointer)
eis_device_stop_emulating(m_client.m_pointer);
if (m_client.m_keyboard)
eis_device_stop_emulating(m_client.m_keyboard);
}
void CEis::resetKeyboard() {
if (!m_client.m_keyboard) //We don't re-create the keyboard if it doesn't exist
return;
clearKeyboard();
ensureKeyboard();
}
void CEis::resetPointer() {
if (!m_client.m_pointer) //We don't re-create the pointer if it doesn't exist
return;
clearPointer();
ensurePointer();
}
void CEis::sendMotion(double x, double y) {
if (!m_client.m_pointer)
return;
eis_device_pointer_motion(m_client.m_pointer, x, y);
}
void CEis::sendKey(uint32_t key, bool pressed) {
if (!m_client.m_keyboard)
return;
uint64_t now = eis_now(m_eisCtx);
eis_device_keyboard_key(m_client.m_keyboard, key, pressed);
eis_device_frame(m_client.m_keyboard, now);
}
void CEis::sendModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) {
if (!m_client.m_keyboard)
return;
uint64_t now = eis_now(m_eisCtx);
eis_device_keyboard_send_xkb_modifiers(m_client.m_keyboard, modsDepressed, modsLatched, modsLocked, group);
eis_device_frame(m_client.m_keyboard, now);
}
void CEis::sendButton(uint32_t button, bool pressed) {
if (!m_client.m_pointer)
return;
eis_device_button_button(m_client.m_pointer, button, pressed);
}
void CEis::sendScrollDiscrete(int32_t x, int32_t y) {
if (!m_client.m_pointer)
return;
eis_device_scroll_discrete(m_client.m_pointer, x, y);
}
void CEis::sendScrollDelta(double x, double y) {
if (!m_client.m_pointer)
return;
eis_device_scroll_delta(m_client.m_pointer, x, y);
}
void CEis::sendScrollStop(bool x, bool y) {
if (!m_client.m_pointer)
return;
eis_device_scroll_stop(m_client.m_pointer, x, y);
}
void CEis::sendPointerFrame() {
if (!m_client.m_pointer)
return;
uint64_t now = eis_now(m_eisCtx);
eis_device_frame(m_client.m_pointer, now);
}
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <libeis.h>
#include <string>
#include <wayland-server-core.h>
/*
* Responsible to creating a socket for input communication
*/
class CEis {
public:
CEis(std::string socketPath);
~CEis();
void startEmulating(int activationId);
void stopEmulating();
void resetKeyboard();
void resetPointer();
void sendMotion(double x, double y);
void sendKey(uint32_t key, bool pressed);
void sendModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group);
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();
//
std::string m_socketPath;
private:
struct SKeymap {
int32_t m_fd = 0;
uint32_t m_size = 0;
};
struct SClient {
eis_client* m_handle = nullptr;
eis_seat* m_seat = nullptr;
eis_device* m_pointer = nullptr;
eis_device* m_keyboard = nullptr;
} m_client;
int onEvent(eis_event* e);
int pollEvents();
void ensurePointer();
void ensureKeyboard();
SKeymap getKeymap();
void clearPointer();
void clearKeyboard();
bool m_stop = false;
eis* m_eisCtx = nullptr;
wl_event_source* m_eventSource = nullptr;
};
+34 -4
View File
@@ -27,6 +27,7 @@
#include "../../protocols/core/DataDevice.hpp"
#include "../../protocols/core/Compositor.hpp"
#include "../../protocols/XDGShell.hpp"
#include "../../protocols/InputCapture.hpp"
#include "../../devices/Mouse.hpp"
#include "../../devices/VirtualPointer.hpp"
@@ -153,6 +154,9 @@ void CInputManager::onMouseMoved(IPointer::SMotionEvent e) {
PROTO::relativePointer->sendRelativeMotion(sc<uint64_t>(e.timeMs) * 1000, delta, unaccel);
Pointer::mgr()->move(DELTA);
if (PROTO::inputCapture->isCaptured())
return;
mouseMoveUnified(e.timeMs, false, e.mouse);
m_lastCursorMovement.reset();
@@ -744,6 +748,11 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e, SP<IPointer> mouse)
if (e.mouse)
recheckMouseWarpOnMouseInput();
PROTO::inputCapture->button(e.button, e.state);
if (PROTO::inputCapture->isCaptured())
return;
m_lastCursorMovement.reset();
if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) {
@@ -976,7 +985,14 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP<IPointer> pointer) {
if (e.mouse)
recheckMouseWarpOnMouseInput();
bool passEvent = g_pKeybindManager->onAxisEvent(e, pointer);
PROTO::inputCapture->axis(e.axis, e.delta);
if (e.source == 0)
PROTO::inputCapture->axisValue120(e.axis, e.delta);
else if (e.delta == 0)
PROTO::inputCapture->axisStop(e.axis);
PROTO::inputCapture->frame();
bool passEvent = !PROTO::inputCapture->isCaptured() && g_pKeybindManager->onAxisEvent(e, pointer);
if (!passEvent)
return;
@@ -1070,6 +1086,11 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP<IPointer> pointer) {
}
void CInputManager::onPointerFrame() {
PROTO::inputCapture->frame();
if (PROTO::inputCapture->isCaptured())
return;
if (!m_pointerAxisFramePending)
return;
@@ -1604,10 +1625,12 @@ void CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SP<IKeyboar
if (info.cancelled)
return;
bool passEvent = DISALLOWACTION;
PROTO::inputCapture->key(event.keycode, event.state);
bool passEvent = DISALLOWACTION && !PROTO::inputCapture->isCaptured();
if (!DISALLOWACTION)
passEvent = g_pKeybindManager->onKeyEvent(event, pKeyboard);
passEvent = g_pKeybindManager->onKeyEvent(event, pKeyboard) && !PROTO::inputCapture->isCaptured();
if (passEvent) {
auto state = event.state;
@@ -1654,6 +1677,7 @@ void CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SP<IKeyboar
}
void CInputManager::onKeyboardMod(SP<IKeyboard> pKeyboard) {
static auto PSENDMOD = CConfigValue<Hyprlang::INT>("input-capture:capture_modifiers");
if (!pKeyboard->m_enabled)
return;
@@ -1662,9 +1686,15 @@ void CInputManager::onKeyboardMod(SP<IKeyboard> pKeyboard) {
const auto IME = m_relay.m_inputMethod.lock();
const bool HASIME = IME && IME->hasGrab();
const bool USEIME = HASIME && !DISALLOWACTION;
auto MODS = pKeyboard->m_modifiersState;
if (*PSENDMOD) {
PROTO::inputCapture->modifiers(MODS.depressed, MODS.latched, MODS.locked, MODS.group);
if (PROTO::inputCapture->isCaptured())
return;
}
// use merged mods states when sending to ime or when sending to seat with no ime
// if passing from ime, send mods directly without merging
if (USEIME || !HASIME) {
+1
View File
@@ -94,6 +94,7 @@ class CInputManager {
void onMouseWheel(IPointer::SAxisEvent, SP<IPointer> pointer = nullptr);
void onPointerFrame();
void onKeyboardKey(const IKeyboard::SKeyEvent&, SP<IKeyboard>);
void onMouseFrame();
void onKeyboardMod(SP<IKeyboard>);
void newKeyboard(SP<IKeyboard>);
@@ -54,6 +54,7 @@ static const char* permissionToString(eDynamicPermissionType type) {
case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN";
case PERMISSION_TYPE_KEYBOARD: return "PERMISSION_TYPE_KEYBOARD";
case PERMISSION_TYPE_CURSOR_POS: return "PERMISSION_TYPE_CURSOR_POS";
case PERMISSION_TYPE_INPUT_CAPTURE: return "PERMISSION_TYPE_INPUT_CAPTURE";
}
return "error";
@@ -256,6 +257,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{"app", appName}, {"plugin", binaryPath}}); break;
case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break;
case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break;
case PERMISSION_TYPE_INPUT_CAPTURE: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_INPUT_CAPTURE, {{"app", appName}}); break;
}
std::vector<std::string> options;
@@ -19,6 +19,7 @@ enum eDynamicPermissionType : uint8_t {
PERMISSION_TYPE_PLUGIN,
PERMISSION_TYPE_KEYBOARD,
PERMISSION_TYPE_CURSOR_POS,
PERMISSION_TYPE_INPUT_CAPTURE,
};
enum eDynamicPermissionRuleSource : uint8_t {
+7
View File
@@ -8,6 +8,7 @@
#include "../protocols/IdleNotify.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/core/Seat.hpp"
#include "../protocols/InputCapture.hpp"
#include "debug/log/Logger.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../render/pass/ClearPassElement.hpp"
@@ -831,6 +832,12 @@ void CPointerManager::move(const Vector2D& deltaLogical) {
const auto oldPos = m_pointerPos;
auto newPos = oldPos + Vector2D{std::isnan(deltaLogical.x) ? 0.0 : deltaLogical.x, std::isnan(deltaLogical.y) ? 0.0 : deltaLogical.y};
if (!g_pInputManager->isLocked())
PROTO::inputCapture->motion(newPos, deltaLogical);
if (PROTO::inputCapture->isCaptured())
return;
warpTo(newPos);
}
+496
View File
@@ -0,0 +1,496 @@
#include "InputCapture.hpp"
#include "Compositor.hpp"
#include "config/ConfigValue.hpp"
#include "debug/log/Logger.hpp"
#include "devices/IKeyboard.hpp"
#include "event/EventBus.hpp"
#include "hyprland-input-capture-v1.hpp"
#include "pointer/PointerManager.hpp"
#include "managers/SeatManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp"
#include "managers/permissions/DynamicPermissionManager.hpp"
#include "protocols/WaylandProtocol.hpp"
#include "render/Renderer.hpp"
#include "state/MonitorState.hpp"
#include <algorithm>
#include <cairo.h>
#include <cstdint>
#include <fcntl.h>
#include <glaze/core/context.hpp>
#include <glaze/util/parse.hpp>
#include <hyprlang.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <libeis.h>
#include <optional>
#include <string>
static int eisCounter = 0;
CInputCaptureResource::CInputCaptureResource(SP<CHyprlandInputCaptureV1> resource_, std::string handle) : m_sessionId(handle), m_resource(resource_) {
if UNLIKELY (!good())
return;
Log::logger->log(Log::INFO, "[input-capture]({}) new session", m_sessionId.c_str());
m_resource->setOnDestroy([this](CHyprlandInputCaptureV1* r) { PROTO::inputCapture->destroyResource(this); }); //Remove & free this session
m_resource->setEnable([this](CHyprlandInputCaptureV1* r) { onEnable(); });
m_resource->setAddBarrier(
[this](CHyprlandInputCaptureV1* r, uint32_t zoneSet, uint32_t id, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { onAddBarrier(zoneSet, id, x1, y1, x2, y2); });
m_resource->setDisable([this](CHyprlandInputCaptureV1* r) { disable(); });
m_resource->setRelease([this](CHyprlandInputCaptureV1* r, uint32_t activationId, double x, double y) { onRelease(activationId, x, y); });
m_resource->setClearBarriers([this](CHyprlandInputCaptureV1* r) { onClearBarriers(); });
m_eis = makeUnique<CEis>("eis-" + std::to_string(eisCounter++));
const int EISFD = m_eis->getFileDescriptor();
if (EISFD >= 0)
m_resource->sendEisFd(EISFD);
else
Log::logger->log(Log::ERR, "[input-capture]({}) failed to create EIS client fd", m_sessionId.c_str());
m_keyRepeatTimer = makeShared<CEventLoopTimer>(
std::nullopt,
[this](SP<CEventLoopTimer> self, void* data) {
if (!m_keyRepeat.active || m_status != CLIENT_STATUS_ACTIVATED || !m_eis)
return;
m_eis->sendKey(m_keyRepeat.key, false);
m_eis->sendKey(m_keyRepeat.key, true);
if (m_keyRepeat.rate > 0)
self->updateTimeout(std::chrono::milliseconds(1000 / m_keyRepeat.rate));
},
nullptr);
if (g_pEventLoopManager)
g_pEventLoopManager->addTimer(m_keyRepeatTimer);
m_monitorCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] {
onClearBarriers();
disable();
m_eis->resetPointer();
});
}
CInputCaptureResource::~CInputCaptureResource() {
stopKeyRepeat();
if (m_keyRepeatTimer && g_pEventLoopManager) {
g_pEventLoopManager->removeTimer(m_keyRepeatTimer);
m_keyRepeatTimer.reset();
}
if (m_status == CLIENT_STATUS_ACTIVATED)
PROTO::inputCapture->forceRelease();
Log::logger->log(Log::INFO, "[input-capture]({}) session destroyed", m_sessionId.c_str());
PROTO::inputCapture->clearBarriers(m_sessionId);
};
bool CInputCaptureResource::enabled() {
return m_status == CLIENT_STATUS_ENABLED;
}
bool CInputCaptureResource::good() {
return m_resource->resource();
}
void CInputCaptureResource::onEnable() {
Log::logger->log(Log::INFO, "[input-capture]({}) session enabled", m_sessionId.c_str());
m_status = CLIENT_STATUS_ENABLED;
}
enum eValidResult : uint8_t {
INVALID,
PARTIAL,
VALID
};
static eValidResult isBarrierValidAgainstMonitor(int x1, int y1, int x2, int y2, PHLMONITOR monitor) {
int mx1 = monitor->m_position.x;
int my1 = monitor->m_position.y;
const int width = static_cast<int>(monitor->m_size.x);
const int height = static_cast<int>(monitor->m_size.y);
int mx2 = mx1 + width - 1;
int my2 = my1 + height - 1;
if (x1 == x2) { //If zone is vertical
if (x1 != mx1 && x1 != mx2 + 1) //If the zone don't touch the left or right side
return INVALID;
if (y1 != my1 || y2 != my2) { //If the zone is shorter than the height of the screen
if ((my1 <= y1 && y1 <= my2) || (my1 <= y2 && y2 <= my2)) //Maybe the segments are overlapping
return PARTIAL;
return INVALID;
}
} else {
if (y1 != my1 && y1 != my2 + 1) //If the zone don't touch the bottom or top side
return INVALID;
if (x1 != mx1 || x2 != mx2) { //If the zone is shorter than the height of the screen
if ((mx1 <= x1 && x1 <= mx2) || (mx1 <= x2 && x2 <= mx2)) //Maybe the segments are overlapping
return PARTIAL;
return INVALID;
}
}
return VALID;
}
static bool isBarrierValid(int x1, int y1, int x2, int y2) {
if (x1 != x2 && y1 != y2) //At least one axis should be aligned
return false;
if (x1 == x2 && y1 == y2) //The barrier should have non-null area
return false;
if (x1 > x2)
std::swap(x1, x2);
if (y1 > y2)
std::swap(y1, y2);
int valid = 0;
int partial = 0; //Used to detect if a barrier is placed on the side of two monitors
for (auto& o : State::monitorState()->monitors()) {
switch (isBarrierValidAgainstMonitor(x1, y1, x2, y2, o)) {
case VALID: valid++; break;
case PARTIAL: partial++; break;
case INVALID: break;
}
}
return valid == 1 && partial == 0;
}
void CInputCaptureResource::onAddBarrier(uint32_t zoneSet, uint32_t id, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) {
static auto PENFORCEBARRIERS = CConfigValue<Hyprlang::INT>("input-capture:enforce_barriers");
// Protocol coordinates are transported as uint32_t. Negative global-space
// coordinates are encoded via two's complement and must be reinterpreted.
const int32_t sx1 = static_cast<int32_t>(x1);
const int32_t sy1 = static_cast<int32_t>(y1);
const int32_t sx2 = static_cast<int32_t>(x2);
const int32_t sy2 = static_cast<int32_t>(y2);
bool valid = isBarrierValid(sx1, sy1, sx2, sy2);
if (!valid) {
Log::logger->log(Log::INFO, "[input-capture]({}) Barrier {} is invalid [{}, {}], [{}, {}]", m_sessionId.c_str(), id, sx1, sy1, sx2, sy2);
if (*PENFORCEBARRIERS) {
m_resource->error(HYPRLAND_INPUT_CAPTURE_V1_ERROR_INVALID_BARRIER, "The barrier id " + std::to_string(id) + " is invalid");
return;
}
}
Log::logger->log(Log::INFO, "[input-capture]({}) Barrier {} [{}, {}], [{}, {}] added", m_sessionId.c_str(), id, sx1, sy1, sx2, sy2);
PROTO::inputCapture->addBarrier({.sessionId = m_sessionId, .id = id, .x1 = sx1, .y1 = sy1, .x2 = sx2, .y2 = sy2});
}
void CInputCaptureResource::onDisable() {
disable();
}
void CInputCaptureResource::onRelease(uint32_t activationId_, double x, double y) {
if (activationId_ != m_activationId) { // If id is invalid we still want to release the mouse to avoid any issue
Log::logger->log(Log::WARN, "[input-capture]({}) Invalid activation id {} expected {}", m_sessionId.c_str(), activationId_, m_activationId);
}
deactivate();
if (x != -1 && y != -1)
Pointer::mgr()->warpTo({x, y});
g_pHyprRenderer->ensureCursorRenderingMode();
}
void CInputCaptureResource::onClearBarriers() {
PROTO::inputCapture->clearBarriers(m_sessionId);
}
bool CInputCaptureResource::activate(double x, double y, uint32_t borderId) {
if (m_status != CLIENT_STATUS_ENABLED)
return false;
const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_INPUT_CAPTURE);
if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW)
return false;
m_activationId += 5;
m_status = CLIENT_STATUS_ACTIVATED;
Log::logger->log(Log::INFO, "[input-capture]({}) Input captured, activationId: {}, borderId: {}, x: {}, y: {}", m_sessionId.c_str(), m_activationId, borderId, x, y);
m_eis->startEmulating(m_activationId);
g_pHyprRenderer->ensureCursorRenderingMode();
m_resource->sendActivated(m_activationId, x, y, borderId);
return true;
}
void CInputCaptureResource::deactivate() {
if (m_status != CLIENT_STATUS_ACTIVATED)
return;
Log::logger->log(Log::INFO, "[input-capture]({}) Input released", m_sessionId.c_str());
stopKeyRepeat();
m_status = CLIENT_STATUS_ENABLED;
m_eis->stopEmulating();
PROTO::inputCapture->release();
m_resource->sendDeactivated(m_activationId);
}
void CInputCaptureResource::disable() {
stopKeyRepeat();
if (m_status == CLIENT_STATUS_ACTIVATED)
deactivate();
if (m_status != CLIENT_STATUS_ENABLED)
return;
m_status = CLIENT_STATUS_CREATED;
m_resource->sendDisabled();
}
void CInputCaptureResource::motion(double dx, double dy) {
m_eis->sendMotion(dx, dy);
}
void CInputCaptureResource::key(uint32_t key, bool pressed) {
m_eis->sendKey(key, pressed);
if (pressed)
startKeyRepeat(key);
else if (m_keyRepeat.active && m_keyRepeat.key == key)
stopKeyRepeat();
}
void CInputCaptureResource::modifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) {
m_eis->sendModifiers(modsDepressed, modsLatched, modsLocked, group);
}
void CInputCaptureResource::button(uint32_t button, bool pressed) {
m_eis->sendButton(button, pressed);
}
void CInputCaptureResource::axis(bool axis, double value) {
int32_t x = 0;
int32_t y = 0;
if (axis)
x = value;
else
y = value;
m_eis->sendScrollDelta(x, y);
}
void CInputCaptureResource::axisValue120(bool axis, int32_t value120) {
int32_t x = 0;
int32_t y = 0;
if (axis)
x = value120;
else
y = value120;
m_eis->sendScrollDiscrete(x, y);
}
void CInputCaptureResource::axisStop(bool axis) {
m_eis->sendScrollStop(axis, !axis);
}
void CInputCaptureResource::frame() {
m_eis->sendPointerFrame();
}
static bool isLineIntersect(double p0X, double p0Y, double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y) {
float s1X = p1X - p0X;
float s1Y = p1Y - p0Y;
float s2X = p3X - p2X;
float s2Y = p3Y - p2Y;
float s = (-s1Y * (p0X - p2X) + s1X * (p0Y - p2Y)) / (-s2X * s1Y + s1X * s2Y);
float t = (s2X * (p0Y - p2Y) - s2Y * (p0X - p2X)) / (-s2X * s1Y + s1X * s2Y);
return s >= 0 && s <= 1 && t >= 0 && t <= 1;
}
std::optional<CInputCaptureProtocol::SBarrier> CInputCaptureProtocol::isColliding(double px, double py, double nx, double ny) {
for (const auto& barrier : barriers) {
auto session = getSession(barrier.sessionId);
if (!session.has_value())
continue;
if (!session.value()->enabled())
continue;
if (isLineIntersect(barrier.x1, barrier.y1, barrier.x2, barrier.y2, px, py, nx, ny))
return std::optional(barrier);
}
return std::nullopt;
}
CInputCaptureProtocol::CInputCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
;
}
void CInputCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
const auto& RESOURCE = m_vManagers.emplace_back(makeUnique<CHyprlandInputCaptureManagerV1>(client, ver, id));
RESOURCE->setOnDestroy([this](CHyprlandInputCaptureManagerV1* p) { std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == p->resource(); }); });
RESOURCE->setCreateSession([this](CHyprlandInputCaptureManagerV1* pMgr, uint32_t id, std::string handle) { onCreateSession(pMgr, id, handle); });
}
void CInputCaptureProtocol::onCreateSession(CHyprlandInputCaptureManagerV1* pMgr, uint32_t id, std::string handle) {
const auto RESOURCE = m_Sessions.emplace_back(makeShared<CInputCaptureResource>(makeShared<CHyprlandInputCaptureV1>(pMgr->client(), pMgr->version(), id), handle));
if UNLIKELY (!RESOURCE->good()) {
pMgr->noMemory();
m_Sessions.pop_back();
return;
}
Log::logger->log(Log::INFO, "New InputCapture at id {}", id);
}
void CInputCaptureProtocol::destroyResource(CInputCaptureResource* resource) {
std::erase_if(m_Sessions, [resource](const auto& other) { return other->m_sessionId == resource->m_sessionId; });
}
bool CInputCaptureProtocol::isCaptured() {
return active != nullptr;
}
void CInputCaptureProtocol::updateKeymap() {
for (auto& session : m_Sessions) {
session->updateKeymap();
}
}
void CInputCaptureProtocol::motion(const Vector2D& absolutePosition, const Vector2D& delta) {
auto [x, y] = absolutePosition;
auto [dx, dy] = delta;
auto matched = isColliding(x, y, x - dx, y - dy);
if (matched.has_value()) {
auto session = getSession(matched->sessionId);
if (!session.has_value()) {
Log::logger->log(Log::ERR, "Cannot find session {} for triggering barrier {}", matched.value().id, matched.value().sessionId);
return;
}
if (session.value()->activate(x, y, matched.value().id))
active = session.value();
}
if (active)
active->motion(dx, dy);
}
std::optional<SP<CInputCaptureResource>> CInputCaptureProtocol::getSession(std::string sessionId) {
auto result = std::ranges::find_if(m_Sessions, [sessionId](SP<CInputCaptureResource> elem) { return sessionId == elem->m_sessionId; });
if (result == m_Sessions.end())
return std::nullopt;
return std::optional(*result);
}
void CInputCaptureProtocol::addBarrier(SBarrier barrier) {
barriers.emplace_back(barrier);
}
void CInputCaptureProtocol::clearBarriers(std::string sessionId) {
std::erase_if(barriers, [sessionId](SBarrier b) { return b.sessionId == sessionId; });
Log::logger->log(Log::INFO, "[input-capture]({}) Barriers cleared", sessionId.c_str());
}
void CInputCaptureProtocol::release() {
active = nullptr;
}
void CInputCaptureProtocol::forceRelease() {
Log::logger->log(Log::INFO, "[input-capture] Force release input capture");
if (active) {
auto cpy = active; //Because deactivate will put active to nullptr
cpy->deactivate();
cpy->disable();
}
release();
}
void CInputCaptureProtocol::key(uint32_t keyCode, wl_keyboard_key_state state) {
if (active)
active->key(keyCode, state);
}
void CInputCaptureProtocol::modifiers(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
if (active)
active->modifiers(mods_depressed, mods_latched, mods_locked, group);
}
void CInputCaptureProtocol::button(uint32_t button, wl_pointer_button_state state) {
if (active)
active->button(button, state);
}
void CInputCaptureProtocol::axis(wl_pointer_axis axis, double value) {
if (active)
active->axis(axis, value);
}
void CInputCaptureProtocol::axisValue120(wl_pointer_axis axis, int32_t value120) {
if (active)
active->axisValue120(axis, value120);
}
void CInputCaptureProtocol::axisStop(wl_pointer_axis axis) {
if (active)
active->axisStop(axis);
}
void CInputCaptureProtocol::frame() {
if (active)
active->frame();
}
void CInputCaptureResource::updateKeymap() {
Log::logger->log(Log::INFO, "[input-capture] Got new keymap, reseting keyboard");
stopKeyRepeat();
m_eis->resetKeyboard();
}
bool CInputCaptureResource::keyRepeats(uint32_t key) {
const auto KEYBOARD = g_pSeatManager->m_keyboard.lock();
if (!KEYBOARD || !KEYBOARD->m_xkbKeymap || KEYBOARD->m_repeatRate <= 0)
return false;
return xkb_keymap_key_repeats(KEYBOARD->m_xkbKeymap, key + 8);
}
void CInputCaptureResource::startKeyRepeat(uint32_t key) {
const auto KEYBOARD = g_pSeatManager->m_keyboard.lock();
if (!KEYBOARD || KEYBOARD->m_repeatRate <= 0) {
stopKeyRepeat();
return;
}
if (!keyRepeats(key))
return;
m_keyRepeat.active = true;
m_keyRepeat.key = key;
m_keyRepeat.rate = KEYBOARD->m_repeatRate;
if (m_keyRepeatTimer)
m_keyRepeatTimer->updateTimeout(std::chrono::milliseconds(std::max(0, KEYBOARD->m_repeatDelay)));
}
void CInputCaptureResource::stopKeyRepeat() {
m_keyRepeat.active = false;
if (m_keyRepeatTimer)
m_keyRepeatTimer->updateTimeout(std::nullopt);
}
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include "managers/input/Eis.hpp"
#include "helpers/memory/Memory.hpp"
#include "helpers/signal/Signal.hpp"
#include "hyprland-input-capture-v1.hpp"
#include "../protocols/WaylandProtocol.hpp"
#include <cstdint>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/signal/Listener.hpp>
#include <optional>
#include <wayland-server.h>
class CEventLoopTimer;
class CInputCaptureResource {
public:
CInputCaptureResource(SP<CHyprlandInputCaptureV1> resource_, std::string handle);
~CInputCaptureResource();
void motion(double dx, double dy);
void key(uint32_t key, bool pressed);
void modifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group);
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();
void updateKeymap();
bool activate(double x, double y, uint32_t borderId);
void disable();
void deactivate();
bool enabled();
bool good();
//
std::string m_sessionId;
private:
enum eClientStatus : uint8_t {
CLIENT_STATUS_CREATED, //Is ready to be activated
CLIENT_STATUS_ENABLED, //Is ready for receiving inputs
CLIENT_STATUS_ACTIVATED, //Currently receiving inputs
CLIENT_STATUS_STOPPED //Can no longer be activated
};
void onEnable();
void onAddBarrier(uint32_t zoneSet, uint32_t id, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2);
void onDisable();
void onRelease(uint32_t activationId, double x, double y);
void onClearBarriers();
void startKeyRepeat(uint32_t key);
void stopKeyRepeat();
bool keyRepeats(uint32_t key);
//
SP<CHyprlandInputCaptureV1> m_resource;
UP<CEis> m_eis;
SP<CEventLoopTimer> m_keyRepeatTimer;
uint32_t m_activationId = 0;
eClientStatus m_status = eClientStatus::CLIENT_STATUS_CREATED;
CHyprSignalListener m_monitorCallback;
struct {
bool active = false;
uint32_t key = 0;
int rate = 0;
} m_keyRepeat;
};
class CInputCaptureProtocol : public IWaylandProtocol {
public:
struct SBarrier {
std::string sessionId = "";
uint id = 0;
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
};
CInputCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name);
void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);
void destroyResource(CInputCaptureResource* pointer);
void addBarrier(SBarrier barrier);
void clearBarriers(std::string sessionId);
void updateKeymap();
bool isCaptured();
void motion(const Vector2D& absolutePosition, const Vector2D& delta);
void key(uint32_t keyCode, wl_keyboard_key_state state);
void modifiers(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
void button(uint32_t button, wl_pointer_button_state state);
void axis(wl_pointer_axis axis, double value);
void axisValue120(wl_pointer_axis axis, int32_t value120);
void axisStop(wl_pointer_axis axis);
void frame();
void forceRelease();
void release();
private:
std::vector<UP<CHyprlandInputCaptureManagerV1>> m_vManagers;
std::vector<SP<CInputCaptureResource>> m_Sessions;
SP<CInputCaptureResource> active = nullptr;
std::vector<SBarrier> barriers;
//
void onCreateSession(CHyprlandInputCaptureManagerV1* pMgr, uint32_t id, std::string handle);
std::optional<SBarrier> isColliding(double px, double py, double nx, double ny);
std::optional<SP<CInputCaptureResource>> getSession(std::string sessionId);
};
namespace PROTO {
inline UP<CInputCaptureProtocol> inputCapture;
}
+2 -2
View File
@@ -81,8 +81,8 @@ void CWLOutputResource::updateState() {
m_resource->sendMode(WL_OUTPUT_MODE_CURRENT, m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y, m_monitor->m_refreshRate * 1000.0);
m_resource->sendGeometry(0, 0, m_monitor->m_output->physicalSize.x, m_monitor->m_output->physicalSize.y, m_monitor->m_output->subpixel, m_monitor->m_output->make.c_str(),
m_monitor->m_output->model.c_str(), m_monitor->m_transform);
m_resource->sendGeometry(m_monitor->m_position.x, m_monitor->m_position.y, m_monitor->m_output->physicalSize.x, m_monitor->m_output->physicalSize.y,
m_monitor->m_output->subpixel, m_monitor->m_output->make.c_str(), m_monitor->m_output->model.c_str(), m_monitor->m_transform);
if (m_resource->version() >= 2)
m_resource->sendDone();
+2 -1
View File
@@ -25,6 +25,7 @@
#include "../protocols/core/Compositor.hpp"
#include "../protocols/DRMSyncobj.hpp"
#include "../protocols/LinuxDMABUF.hpp"
#include "../protocols/InputCapture.hpp"
#include "../errorOverlay/Overlay.hpp"
#include "../debug/Overlay.hpp"
#include "../notification/NotificationOverlay.hpp"
@@ -2895,7 +2896,7 @@ void IHyprRenderer::ensureCursorRenderingMode() {
m_cursorHiddenByCondition =
m_cursorHiddenConditions.hiddenOnTimeout || m_cursorHiddenConditions.hiddenOnTouch || m_cursorHiddenConditions.hiddenOnTablet || m_cursorHiddenConditions.hiddenOnKeyboard;
const bool HIDE = m_cursorHiddenByCondition || (*PINVISIBLE != 0);
const bool HIDE = m_cursorHiddenByCondition || (*PINVISIBLE != 0) || PROTO::inputCapture->isCaptured();
if (HIDE == m_cursorHidden)
return;
+2
View File
@@ -132,6 +132,7 @@ TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) {
keybind->submapUniversal = false;
keybind->deviceInclusive = true;
keybind->devices = {"kbd-a", "kbd-b"};
keybind->allowInputCapture = true;
Objects::CLuaKeybind::push(L, keybind);
lua_setglobal(L, "kb");
@@ -151,6 +152,7 @@ TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) {
assert(kb.non_consuming == true)
assert(kb.dont_inhibit == true)
assert(type(kb.devices) == "table")
assert(kb.allow_input_capture == true)
kb:remove()
kb:unbind()