#include "ControllerNavigation.h" #include #include #include #include namespace { // OpenVR has no name function for EVRInputError. QString inputErrorName(vr::EVRInputError err) { static const char* const names[] = {"VRInputError_None", "VRInputError_NameNotFound", "VRInputError_WrongType", "VRInputError_InvalidHandle", "VRInputError_InvalidParam", "VRInputError_NoSteam", "VRInputError_MaxCapacityReached", "VRInputError_IPCError", "VRInputError_NoActiveActionSet", "VRInputError_InvalidDevice", "VRInputError_InvalidSkeleton", "VRInputError_InvalidBoneCount", "VRInputError_InvalidCompressedData", "VRInputError_NoData", "VRInputError_BufferTooSmall", "VRInputError_MismatchedActionManifest", "VRInputError_MissingSkeletonData", "VRInputError_InvalidBoneIndex", "VRInputError_InvalidPriority", "VRInputError_PermissionDenied", "VRInputError_InvalidRenderModel"}; const int index = int(err); if (index >= 0 && index < int(std::size(names))) return QString::fromLatin1(names[index]); return QStringLiteral("VRInputError %1").arg(index); } constexpr qint64 kRepeatDelayMs = 400; constexpr qint64 kRepeatIntervalMs = 120; } // namespace ControllerNavigation::ControllerNavigation(QObject* parent) : QObject(parent), m_actions{{ {"/actions/dashboard/in/focus_next", Qt::Key_Tab, Qt::NoModifier, true}, {"/actions/dashboard/in/focus_previous", Qt::Key_Backtab, Qt::ShiftModifier, true}, {"/actions/dashboard/in/increase", Qt::Key_Right, Qt::NoModifier, true}, {"/actions/dashboard/in/decrease", Qt::Key_Left, Qt::NoModifier, true}, {"/actions/dashboard/in/activate", Qt::Key_Space, Qt::NoModifier, false}, {"/actions/dashboard/in/back", Qt::Key_Escape, Qt::NoModifier, false}, }} { m_pollTimer.setInterval(11); connect(&m_pollTimer, &QTimer::timeout, this, &ControllerNavigation::poll); } bool ControllerNavigation::initialize(const QString& manifestPath, QString* error) { vr::IVRInput* input = vr::VRInput(); if (!input) { *error = QStringLiteral("IVRInput interface unavailable"); return false; } vr::EVRInputError err = input->SetActionManifestPath(manifestPath.toUtf8().constData()); if (err != vr::VRInputError_None) { *error = QStringLiteral("SetActionManifestPath(%1) failed: %2") .arg(manifestPath, inputErrorName(err)); return false; } err = input->GetActionSetHandle("/actions/dashboard", &m_actionSet); for (Action& action : m_actions) { if (err == vr::VRInputError_None) err = input->GetActionHandle(action.path, &action.handle); } if (err != vr::VRInputError_None) { *error = QStringLiteral("Resolving input actions failed: %1") .arg(inputErrorName(err)); return false; } m_clock.start(); m_pollTimer.start(); return true; } void ControllerNavigation::poll() { vr::VRActiveActionSet_t activeSet{}; activeSet.ulActionSet = m_actionSet; if (vr::VRInput()->UpdateActionState(&activeSet, sizeof(activeSet), 1) != vr::VRInputError_None) return; QQuickWindow* target = m_target ? m_target() : nullptr; const qint64 now = m_clock.elapsed(); for (Action& action : m_actions) { vr::InputDigitalActionData_t data{}; if (vr::VRInput()->GetDigitalActionData(action.handle, &data, sizeof(data), vr::k_ulInvalidInputValueHandle) != vr::VRInputError_None || !data.bActive) { action.held = false; continue; } if (data.bState && !action.held) { action.held = true; action.nextRepeatMs = now + kRepeatDelayMs; if (target) sendKey(target, action); emit actionTriggered(QString::fromLatin1(action.path).section('/', -1), target != nullptr); } else if (data.bState && action.repeats && now >= action.nextRepeatMs) { action.nextRepeatMs = now + kRepeatIntervalMs; if (target) sendKey(target, action); } else if (!data.bState) { action.held = false; } } } void ControllerNavigation::sendKey(QQuickWindow* window, const Action& action) { QKeyEvent press(QEvent::KeyPress, action.key, action.modifiers); QCoreApplication::sendEvent(window, &press); QKeyEvent release(QEvent::KeyRelease, action.key, action.modifiers); QCoreApplication::sendEvent(window, &release); }