Initial FrameAssistant baseline from the vrdashboard template
Template stripped to the overlay platform (examples, demo UI and Frame test package removed), renamed to FrameAssistant, and working rules for the project recorded in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#include "ControllerNavigation.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QQuickWindow>
|
||||
#include <iterator>
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user