mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 02:24:14 -05:00
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:
co-authored by
Gerben Meijer
Emerson Matos
Mihai Fufezan
Ashley Stonham
extra-salad
parent
d7fc7240f4
commit
0b0d7ede21
@@ -31,6 +31,7 @@ runs:
|
||||
libc++ \
|
||||
libdisplay-info \
|
||||
libdrm \
|
||||
libei \
|
||||
libepoxy \
|
||||
libfontenc \
|
||||
libglvnd \
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
*/
|
||||
|
||||
@@ -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 += "]";
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Submodule subprojects/hyprland-protocols updated: 3a5c2bda1c...bd153e76f7
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user