mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-21 14:44:40 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3583d744 |
No files matched your search
@@ -489,8 +489,6 @@ jobs:
|
||||
/tmp/test-xr-tracking
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
|
||||
/tmp/test-xr-touch
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
|
||||
/tmp/test-pad-controller-config
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
|
||||
/tmp/test-xr-vsync
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
|
||||
|
||||
@@ -1545,7 +1545,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
val runtimeDir = File(filesDir, ".wayland-rt").apply { mkdirs() }
|
||||
FileUtils.copyAsset(this, "wayland/keymap.xkb", File(runtimeDir, "keymap.xkb"))
|
||||
com.droiddeck.launcher.input.VrControllers.publish(this)
|
||||
|
||||
val turnip = TurnipDriver(this)
|
||||
com.droiddeck.launcher.gpu.DriverSetupLog.write(this, "surface.driver-setup")
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.io.File
|
||||
|
||||
/** What SteamVR sees for the game pad. The driver reads the file once, when SteamVR starts. */
|
||||
object VrControllers {
|
||||
enum class Emulate(val key: String) { OFF("off"), INDEX("index"), TOUCH("touch"), VIVE("vive") }
|
||||
|
||||
data class Settings(val emulate: Emulate = Emulate.INDEX, val sticksMoveHands: Boolean = false)
|
||||
|
||||
const val FILE = "droiddeck-vr-controllers"
|
||||
private const val PREFS = "vr_controllers"
|
||||
|
||||
private val _settings = MutableStateFlow(Settings())
|
||||
val settings: StateFlow<Settings> = _settings.asStateFlow()
|
||||
private var loaded = false
|
||||
|
||||
fun load(context: Context): Settings {
|
||||
if (!loaded) {
|
||||
val p = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
_settings.value = Settings(
|
||||
emulate = Emulate.entries.firstOrNull { it.key == p.getString("emulate", null) } ?: Emulate.INDEX,
|
||||
sticksMoveHands = p.getBoolean("sticksMoveHands", false),
|
||||
)
|
||||
loaded = true
|
||||
}
|
||||
return _settings.value
|
||||
}
|
||||
|
||||
fun setEmulate(context: Context, emulate: Emulate) = save(context, load(context).copy(emulate = emulate))
|
||||
fun setSticksMoveHands(context: Context, on: Boolean) = save(context, load(context).copy(sticksMoveHands = on))
|
||||
|
||||
private fun save(context: Context, settings: Settings) {
|
||||
_settings.value = settings
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putString("emulate", settings.emulate.key)
|
||||
.putBoolean("sticksMoveHands", settings.sticksMoveHands)
|
||||
.apply()
|
||||
publish(context)
|
||||
}
|
||||
|
||||
fun encode(settings: Settings): String =
|
||||
"emulate=${settings.emulate.key}\nsticks=${if (settings.sticksMoveHands) "hands" else "game"}\n"
|
||||
|
||||
/** Writes into the session's runtime dir, which the guest sees as /run/droiddeck. */
|
||||
fun publish(context: Context) {
|
||||
runCatching {
|
||||
val dir = File(context.filesDir, ".wayland-rt").apply { mkdirs() }
|
||||
val tmp = File(dir, "$FILE.tmp")
|
||||
tmp.writeText(encode(load(context)))
|
||||
tmp.renameTo(File(dir, FILE))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,33 +79,6 @@ internal object GlassesTab {
|
||||
const val VR = "vr"
|
||||
const val HARDWARE = "hardware"
|
||||
const val TRACKING = "tracking"
|
||||
const val CONTROLLERS = "controllers"
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun VrControllerGroups(host: MenuHost) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
androidx.compose.runtime.LaunchedEffect(Unit) { com.droiddeck.launcher.input.VrControllers.load(context) }
|
||||
val c by com.droiddeck.launcher.input.VrControllers.settings.collectAsState()
|
||||
val emulate = com.droiddeck.launcher.input.VrControllers.Emulate.entries
|
||||
SettingsGroup(stringResource(R.string.vr_controllers_group)) {
|
||||
ChoiceRow(host, "vr-emulate", stringResource(R.string.vr_controllers_emulate), stringResource(R.string.vr_controllers_emulate_hint),
|
||||
emulate.map { it to stringResource(when (it) {
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.OFF -> R.string.vr_controllers_off
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.INDEX -> R.string.vr_controllers_index
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.TOUCH -> R.string.vr_controllers_touch
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.VIVE -> R.string.vr_controllers_vive
|
||||
}) }, c.emulate) { com.droiddeck.launcher.input.VrControllers.setEmulate(context, it) }
|
||||
if (c.emulate != com.droiddeck.launcher.input.VrControllers.Emulate.OFF) ChoiceRow(host, "vr-sticks",
|
||||
stringResource(R.string.vr_controllers_sticks), stringResource(R.string.vr_controllers_sticks_hint),
|
||||
listOf(false to stringResource(R.string.vr_controllers_sticks_game), true to stringResource(R.string.vr_controllers_sticks_hands)),
|
||||
c.sticksMoveHands) { com.droiddeck.launcher.input.VrControllers.setSticksMoveHands(context, it) }
|
||||
SettingsRow(stringResource(R.string.vr_controllers_buttons), stringResource(when (c.emulate) {
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.OFF -> R.string.vr_controllers_buttons_off
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.VIVE -> R.string.vr_controllers_buttons_vive
|
||||
else -> R.string.vr_controllers_buttons_stick
|
||||
}), hintLines = 6) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -115,14 +115,13 @@ internal fun SteamHome(s: FrontEndState, a: FrontEndActions, modifier: Modifier)
|
||||
drop = drop.takeIf { headset },
|
||||
overlay = { lifted ->
|
||||
if (headset) DropSheet(
|
||||
drop, glassesTabs(glasses) + (GlassesTab.CONTROLLERS to stringResource(R.string.vr_controllers_tab)), covered = lifted,
|
||||
drop, glassesTabs(glasses), covered = lifted,
|
||||
button = { enabled ->
|
||||
PrimaryButton(stringResource(R.string.games_play_steamvr), enabled = !s.busy && glasses.usable, large = true, icon = Icons.Filled.PlayArrow, fill = true,
|
||||
modifier = Modifier.fillMaxWidth().testTag("play-steamvr")) { if (enabled) { drop.open = false; a.onPlaySteamVr() } }
|
||||
},
|
||||
) { tab ->
|
||||
if (tab == GlassesTab.CONTROLLERS) VrControllerGroups(rememberMenuHost())
|
||||
else GlassesGroups(rememberMenuHost(), glasses, glassesSettings,
|
||||
GlassesGroups(rememberMenuHost(), glasses, glassesSettings,
|
||||
onRecenter = GlassesMenuMirror::recenter, inSession = false, tab = tab)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2515,22 +2515,6 @@
|
||||
<string name="glasses_tracking_flat_hint">Automatic keeps this 2D game fixed in view. Choose Virtual screen in Display to use head tracking.</string>
|
||||
<string name="glasses_tracking_sbs_hint">Head tracking applies to a virtual screen or a running SteamVR game.</string>
|
||||
<string name="glasses_hardware">Device</string>
|
||||
<string name="vr_controllers_tab">Controllers</string>
|
||||
<string name="vr_controllers_group">SteamVR controllers</string>
|
||||
<string name="vr_controllers_emulate">Game pad in VR</string>
|
||||
<string name="vr_controllers_emulate_hint">The two VR hands your controller plays as. Takes effect the next time SteamVR starts. Joy-Cons and tracked hands still take over their side.</string>
|
||||
<string name="vr_controllers_off">Regular game pad</string>
|
||||
<string name="vr_controllers_index">Valve Index controllers</string>
|
||||
<string name="vr_controllers_touch">Meta Quest 3 controllers</string>
|
||||
<string name="vr_controllers_vive">HTC Vive wands</string>
|
||||
<string name="vr_controllers_sticks">Sticks start by</string>
|
||||
<string name="vr_controllers_sticks_hint">Press both sticks in together during VR to switch.</string>
|
||||
<string name="vr_controllers_sticks_game">Moving you in the game</string>
|
||||
<string name="vr_controllers_sticks_hands">Moving the hands</string>
|
||||
<string name="vr_controllers_buttons">Buttons</string>
|
||||
<string name="vr_controllers_buttons_stick">Triggers are triggers, bumpers grip, A/B and X/Y are each hand\'s face buttons, stick clicks click the thumbsticks, Guide opens the SteamVR menu. D-pad up and down move the hands closer or farther.</string>
|
||||
<string name="vr_controllers_buttons_vive">The sticks are trackpads; A/X or a stick click presses them. Triggers are triggers, bumpers grip, Start and Select are menu, Guide opens the SteamVR menu.</string>
|
||||
<string name="vr_controllers_buttons_off">No VR hands. Games that support a game pad, and the SteamVR menu, get your controller as usual.</string>
|
||||
<string name="glasses_brightness">Brightness</string>
|
||||
<string name="glasses_volume">Volume</string>
|
||||
<string name="glasses_sunblock">Sunblock</string>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class VrControllersTest {
|
||||
@Test fun encodesTheKeysTheSteamVrDriverReads() {
|
||||
assertEquals("emulate=index\nsticks=game\n", VrControllers.encode(VrControllers.Settings()))
|
||||
assertEquals("emulate=touch\nsticks=hands\n",
|
||||
VrControllers.encode(VrControllers.Settings(VrControllers.Emulate.TOUCH, sticksMoveHands = true)))
|
||||
assertEquals(listOf("off", "index", "touch", "vive"), VrControllers.Emulate.entries.map { it.key })
|
||||
}
|
||||
}
|
||||
+13
-21
@@ -465,28 +465,20 @@ results, app recovery behavior, and the unverified powered-adapter design.
|
||||
|
||||
### Not done yet
|
||||
|
||||
- **Controllers**: the driver adds two hands driven by the session's game pad. It reads
|
||||
- **Controllers**: the driver adds two Vive-profile hands driven by the session's game pad. It reads
|
||||
the absolute-state snapshot the app already publishes in fake input ring 0
|
||||
(`FAKE_EVDEV_MEMFD_PATHS`), so the app needs no new plumbing. **Play SteamVR → Settings → Controllers**
|
||||
chooses what they are; the app writes the choice to `/run/droiddeck/droiddeck-vr-controllers`
|
||||
(`emulate=index|touch|vive|off`, `sticks=game|hands`) and the driver reads it when SteamVR starts:
|
||||
- **Valve Index** (default) and **Meta Quest 3** (Touch Plus) are thumbstick controllers with the
|
||||
stock input profiles, so games use their own Index or Touch bindings. Triggers are triggers, bumpers
|
||||
grip, A/B and X/Y are each hand's face buttons, stick clicks are thumbstick clicks and Guide is system.
|
||||
Quest's left menu button is Start or Select.
|
||||
- **HTC Vive wands** keep the older layout: the sticks are trackpads, A/X or a stick click presses them,
|
||||
Start and Select are menu.
|
||||
- **Regular game pad** adds no hands, for games that play with a game pad.
|
||||
|
||||
The hands rest low in front of each side and point where the head looks. By default the sticks move you
|
||||
in the game; pressing both sticks in together switches them to moving the hands and back. Each stick then
|
||||
moves its hand sideways (40 cm), up (50 cm) and down (30 cm) and turns it as if from an elbow 45 cm
|
||||
behind, so pointers and aim follow it too; the hand eases back when the stick is let go. The d-pad's up
|
||||
and down move both hands in and out; while a hand's trigger or grip is held they move only that hand
|
||||
(a slingshot or bow pulled back), and it eases back on release. Both hands activate in SteamVR and their
|
||||
laser shows on the dashboard. They switch off while another driver's controller is connected. A Joy-Con
|
||||
hand replaces the pad hand on its side, and bare camera hands replace both while no controller is
|
||||
connected (above).
|
||||
(`FAKE_EVDEV_MEMFD_PATHS`), so the app needs no new plumbing. They rest low in front of each side and
|
||||
point where the head looks. Each stick moves its hand sideways (40 cm), up (50 cm) and down (30 cm)
|
||||
and turns it as if from an elbow 45 cm behind, so pointers and aim follow it too; the hand eases
|
||||
back when the stick is let go. The d-pad's up and down move both hands in and out; while a hand's
|
||||
trigger or grip is held they move only that hand (a slingshot or bow pulled back), and it eases back
|
||||
on release. Clicking the left stick hands the sticks to the game as trackpads (walking, turning,
|
||||
teleport) and back. Mapping, right hand: A/R3 = trackpad click, RT = trigger, RB = grip, Start = menu, guide =
|
||||
system. Left hand: X, LT, LB, Select. Both hands activate in SteamVR and their laser shows on the
|
||||
dashboard; guide, Start and A presses show up in the ring the driver reads. They are drawn as Index
|
||||
controllers and switch off while another driver's controller is connected. A Joy-Con hand replaces
|
||||
the pad hand on its side, and bare camera hands replace both while no controller is connected
|
||||
(above).
|
||||
- **Layers**: only the first layer of each frame is drawn. SteamVR's compositor composites the
|
||||
dashboard into it, so this has not mattered yet.
|
||||
- **Timing**: no reprojection. The presenter blits the frame submitted last once `vrcompositor`'s GPU
|
||||
|
||||
+12
-1
@@ -12,7 +12,18 @@ else
|
||||
export DROIDDECK_BUILD_TREE_STATE=unknown
|
||||
fi
|
||||
sdk_dir=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-"${HOME}/Library/Android/sdk"}}
|
||||
java_dir=${JAVA_HOME:-"/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"}
|
||||
java_dir="${JAVA_HOME:-/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home}"
|
||||
[[ -x "$java_dir/bin/java" ]] || {
|
||||
mise_java="$(mise -C "${repo_root}" which java 2>/dev/null)"
|
||||
if [[ -n "$mise_java" ]]; then
|
||||
mise_home="$(dirname "$mise_java")"
|
||||
[[ "$mise_home" == */bin ]] && mise_home="$(dirname "$mise_home")"
|
||||
[[ -x "$mise_home/bin/java" ]] && java_dir="$mise_home"
|
||||
fi
|
||||
}
|
||||
[[ -x "$java_dir/bin/java" ]] || java_dir="${HOME}/.local/share/mise/installs/java/temurin-17"
|
||||
[[ "$java_dir" == */homebrew/* ]] && echo "using homebrew java path: $java_dir"
|
||||
[[ "$java_dir" == */mise/* ]] && echo "using mise java path: $java_dir"
|
||||
image_name=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v3}
|
||||
build_variant=${DROIDDECK_BUILD_VARIANT:-release}
|
||||
case "$build_variant" in
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "handcam_shm.h"
|
||||
#include "hand_skeleton.h"
|
||||
#include "index_pose.h"
|
||||
#include "pad_controller_config.h"
|
||||
#include "joycon_shm.h"
|
||||
#include "presenter.h"
|
||||
#include "spatial_tracking.h"
|
||||
@@ -1028,57 +1027,45 @@ public:
|
||||
pose_.qWorldFromDriverRotation.w = pose_.qDriverFromHeadRotation.w = pose_.qRotation.w = 1;
|
||||
}
|
||||
|
||||
void SetProfile(PadProfile profile) { profile_ = profile; }
|
||||
|
||||
EVRInitError Activate(uint32_t id) override {
|
||||
id_ = id;
|
||||
PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(id_);
|
||||
XrTrackingFile tracking;
|
||||
DroidDeckXrTracking packet{};
|
||||
xrMode_ = tracking.Read(&packet) && XrTrackingFile::Fresh(packet);
|
||||
const PadProfile profile = xrMode_ ? PadProfile::Touch : profile_;
|
||||
touch_ = profile == PadProfile::Touch;
|
||||
index_ = profile == PadProfile::Index;
|
||||
const char *model = touch_ ? "DroidDeck Quest Touch" : index_ ? "DroidDeck Index Hand" : "DroidDeck Pad Hand";
|
||||
VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, model);
|
||||
VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, touch_ ? "Meta" : index_ ? "Valve" : "DroidDeck");
|
||||
VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, xrMode_ ? "DroidDeck Quest Touch" : "DroidDeck Pad Hand");
|
||||
VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, xrMode_ ? "Meta" : "DroidDeck");
|
||||
VRProperties()->SetStringProperty(c, Prop_TrackingSystemName_String, "droiddeck");
|
||||
VRProperties()->SetStringProperty(c, Prop_RenderModelName_String, touch_ ?
|
||||
VRProperties()->SetStringProperty(c, Prop_RenderModelName_String, xrMode_ ?
|
||||
(right_ ? "oculus_quest_plus_controller_right" : "oculus_quest_plus_controller_left") :
|
||||
(right_ ? "{indexcontroller}valve_controller_knu_1_0_right" : "{indexcontroller}valve_controller_knu_1_0_left"));
|
||||
VRProperties()->SetStringProperty(c, Prop_ControllerType_String, touch_ ? "oculus_touch" : index_ ? "knuckles" : "vive_controller");
|
||||
VRProperties()->SetStringProperty(c, Prop_InputProfilePath_String, touch_ ? "{oculus}/input/touch_profile.json" :
|
||||
index_ ? "{indexcontroller}/input/index_controller_profile.json" : "{htc}/input/vive_controller_profile.json");
|
||||
VRProperties()->SetStringProperty(c, Prop_ControllerType_String, xrMode_ ? "oculus_touch" : "vive_controller");
|
||||
VRProperties()->SetStringProperty(c, Prop_InputProfilePath_String, xrMode_ ?
|
||||
"{oculus}/input/touch_profile.json" : "{htc}/input/vive_controller_profile.json");
|
||||
VRProperties()->SetInt32Property(c, Prop_ControllerRoleHint_Int32,
|
||||
right_ ? TrackedControllerRole_RightHand : TrackedControllerRole_LeftHand);
|
||||
VRProperties()->SetInt32Property(c, Prop_DeviceClass_Int32, TrackedDeviceClass_Controller);
|
||||
auto in = VRDriverInput();
|
||||
const bool stick = touch_ || index_;
|
||||
const char *stickPath = touch_ ? "/input/joystick" : "/input/thumbstick";
|
||||
const auto path = [](const char *base, const char *leaf) { return std::string(base) + leaf; };
|
||||
in->CreateBooleanComponent(c, "/input/system/click", &system_);
|
||||
if (!index_) in->CreateBooleanComponent(c, "/input/application_menu/click", &menu_);
|
||||
in->CreateBooleanComponent(c, stick ? "/input/grip/touch" : "/input/grip/click", &grip_);
|
||||
in->CreateBooleanComponent(c, "/input/application_menu/click", &menu_);
|
||||
in->CreateBooleanComponent(c, "/input/grip/click", &grip_);
|
||||
in->CreateBooleanComponent(c, "/input/trigger/click", &triggerClick_);
|
||||
in->CreateScalarComponent(c, "/input/trigger/value", &trigger_, VRScalarType_Absolute, VRScalarUnits_NormalizedOneSided);
|
||||
const char *axisBase = stick ? stickPath : "/input/trackpad";
|
||||
in->CreateScalarComponent(c, path(axisBase, "/x").c_str(), &padX_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided);
|
||||
in->CreateScalarComponent(c, path(axisBase, "/y").c_str(), &padY_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided);
|
||||
in->CreateBooleanComponent(c, path(axisBase, "/click").c_str(), &padClick_);
|
||||
in->CreateBooleanComponent(c, path(axisBase, "/touch").c_str(), &padTouch_);
|
||||
if (stick) {
|
||||
const bool letters = touch_ && !right_;
|
||||
in->CreateScalarComponent(c, xrMode_ ? "/input/joystick/x" : "/input/trackpad/x", &padX_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided);
|
||||
in->CreateScalarComponent(c, xrMode_ ? "/input/joystick/y" : "/input/trackpad/y", &padY_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided);
|
||||
in->CreateBooleanComponent(c, xrMode_ ? "/input/joystick/click" : "/input/trackpad/click", &padClick_);
|
||||
in->CreateBooleanComponent(c, xrMode_ ? "/input/joystick/touch" : "/input/trackpad/touch", &padTouch_);
|
||||
if (xrMode_) {
|
||||
in->CreateScalarComponent(c, "/input/grip/value", &gripValue_, VRScalarType_Absolute, VRScalarUnits_NormalizedOneSided);
|
||||
if (index_) in->CreateScalarComponent(c, "/input/grip/force", &gripForce_, VRScalarType_Absolute, VRScalarUnits_NormalizedOneSided);
|
||||
in->CreateBooleanComponent(c, "/input/trigger/touch", &triggerTouch_);
|
||||
in->CreateBooleanComponent(c, letters ? "/input/x/click" : "/input/a/click", &primaryClick_);
|
||||
in->CreateBooleanComponent(c, letters ? "/input/y/click" : "/input/b/click", &secondaryClick_);
|
||||
in->CreateBooleanComponent(c, letters ? "/input/x/touch" : "/input/a/touch", &primaryTouch_);
|
||||
in->CreateBooleanComponent(c, letters ? "/input/y/touch" : "/input/b/touch", &secondaryTouch_);
|
||||
if (touch_) in->CreateBooleanComponent(c, "/input/thumbrest/touch", &thumbrestTouch_);
|
||||
in->CreateBooleanComponent(c, right_ ? "/input/a/click" : "/input/x/click", &primaryClick_);
|
||||
in->CreateBooleanComponent(c, right_ ? "/input/b/click" : "/input/y/click", &secondaryClick_);
|
||||
in->CreateBooleanComponent(c, right_ ? "/input/a/touch" : "/input/x/touch", &primaryTouch_);
|
||||
in->CreateBooleanComponent(c, right_ ? "/input/b/touch" : "/input/y/touch", &secondaryTouch_);
|
||||
in->CreateBooleanComponent(c, "/input/thumbrest/touch", &thumbrestTouch_);
|
||||
}
|
||||
in->CreateHapticComponent(c, "/output/haptic", &haptic_);
|
||||
Log("%s hand activated (%s)", right_ ? "right" : "left", xrMode_ ? "Quest Touch" : model);
|
||||
Log("%s hand activated (%s)", right_ ? "right" : "left", xrMode_ ? "Quest Touch" : "game pad");
|
||||
return VRInitError_None;
|
||||
}
|
||||
void Deactivate() override { ClearInputs(); id_ = k_unTrackedDeviceIndexInvalid; }
|
||||
@@ -1098,14 +1085,11 @@ public:
|
||||
if (id_ == k_unTrackedDeviceIndexInvalid) return;
|
||||
auto in = VRDriverInput();
|
||||
EVRInputError error = VRInputError_None;
|
||||
for (VRInputComponentHandle_t h : {system_, menu_, grip_, triggerClick_, padClick_, padTouch_, triggerTouch_,
|
||||
primaryClick_, secondaryClick_, primaryTouch_, secondaryTouch_}) {
|
||||
if (!h) continue;
|
||||
for (VRInputComponentHandle_t h : {system_, menu_, grip_, triggerClick_, padClick_, padTouch_}) {
|
||||
const auto result = in->UpdateBooleanComponent(h, false, 0);
|
||||
if (result != VRInputError_None) error = result;
|
||||
}
|
||||
for (VRInputComponentHandle_t h : {trigger_, padX_, padY_, gripValue_, gripForce_}) {
|
||||
if (!h) continue;
|
||||
for (VRInputComponentHandle_t h : {trigger_, padX_, padY_}) {
|
||||
const auto result = in->UpdateScalarComponent(h, 0.0f, 0);
|
||||
if (result != VRInputError_None) error = result;
|
||||
}
|
||||
@@ -1202,7 +1186,6 @@ public:
|
||||
in->UpdateBooleanComponent(system_, right_ && (b & PAD_GUIDE), 0);
|
||||
in->UpdateBooleanComponent(menu_, (b & (right_ ? PAD_START : PAD_SELECT)) != 0, 0);
|
||||
const auto gripError = in->UpdateBooleanComponent(grip_, opticalInputs_.grip, 0);
|
||||
if (gripValue_) in->UpdateScalarComponent(gripValue_, opticalInputs_.grip ? 1.0f : 0.0f, 0);
|
||||
const auto clickError = in->UpdateBooleanComponent(triggerClick_, opticalInputs_.click, 0);
|
||||
const auto triggerError = in->UpdateScalarComponent(trigger_, opticalInputs_.trigger, 0);
|
||||
const float x = pad ? pad->axes[right_ ? 2 : 0]/32767.0f : 0;
|
||||
@@ -1269,33 +1252,15 @@ public:
|
||||
const uint32_t b = pad->buttons;
|
||||
const float trig = pad->axes[right_ ? 4 : 5] / 255.0f;
|
||||
const bool touch = !sticksMoveHands && stickX * stickX + stickY * stickY > 0.01f;
|
||||
const bool grip = (b & (right_ ? PAD_RB : PAD_LB)) != 0;
|
||||
in->UpdateBooleanComponent(system_, right_ && (b & PAD_GUIDE), 0);
|
||||
in->UpdateBooleanComponent(menu_, (b & (right_ ? PAD_START : PAD_SELECT)) != 0, 0);
|
||||
in->UpdateBooleanComponent(grip_, (b & (right_ ? PAD_RB : PAD_LB)) != 0, 0);
|
||||
in->UpdateScalarComponent(trigger_, trig, 0);
|
||||
in->UpdateBooleanComponent(triggerClick_, trig > 0.6f, 0);
|
||||
in->UpdateScalarComponent(padX_, touch ? stickX : 0.0f, 0);
|
||||
in->UpdateScalarComponent(padY_, touch ? stickY : 0.0f, 0);
|
||||
if (!touch_ && !index_) {
|
||||
in->UpdateBooleanComponent(menu_, (b & (right_ ? PAD_START : PAD_SELECT)) != 0, 0);
|
||||
in->UpdateBooleanComponent(grip_, grip, 0);
|
||||
in->UpdateBooleanComponent(padTouch_, touch || (b & (right_ ? PAD_A : PAD_X)), 0);
|
||||
in->UpdateBooleanComponent(padClick_, (b & (right_ ? (PAD_A | PAD_R3) : PAD_X)) != 0, 0);
|
||||
return;
|
||||
}
|
||||
// Touch and Index: face buttons are their own inputs and the stick is a thumbstick.
|
||||
const bool primary = b & (right_ ? PAD_A : PAD_X), secondary = b & (right_ ? PAD_B : PAD_Y);
|
||||
const bool both = (b & PAD_L3) && (b & PAD_R3);
|
||||
if (menu_) in->UpdateBooleanComponent(menu_, !right_ && (b & (PAD_START | PAD_SELECT)), 0);
|
||||
in->UpdateBooleanComponent(grip_, grip, 0);
|
||||
in->UpdateScalarComponent(gripValue_, grip ? 1.0f : 0.0f, 0);
|
||||
if (gripForce_) in->UpdateScalarComponent(gripForce_, grip ? 1.0f : 0.0f, 0);
|
||||
in->UpdateBooleanComponent(triggerTouch_, trig > 0.05f, 0);
|
||||
in->UpdateBooleanComponent(primaryClick_, primary, 0);
|
||||
in->UpdateBooleanComponent(secondaryClick_, secondary, 0);
|
||||
in->UpdateBooleanComponent(primaryTouch_, primary, 0);
|
||||
in->UpdateBooleanComponent(secondaryTouch_, secondary, 0);
|
||||
in->UpdateBooleanComponent(padTouch_, touch, 0);
|
||||
in->UpdateBooleanComponent(padClick_, !both && (b & (right_ ? PAD_R3 : PAD_L3)), 0);
|
||||
in->UpdateBooleanComponent(padTouch_, touch || (b & (right_ ? PAD_A : PAD_X)), 0);
|
||||
in->UpdateBooleanComponent(padClick_, (b & (right_ ? (PAD_A | PAD_R3) : PAD_X)) != 0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -1332,15 +1297,14 @@ private:
|
||||
static constexpr int16_t kGrabTrigger = 153;
|
||||
static constexpr float kStickDeadzone = 0.12f, kHandRangeX = 0.40f, kHandRangeUp = 0.50f, kHandRangeDown = 0.30f, kHandSmoothing = 0.05f, kHandPivot = 0.45f;
|
||||
bool right_;
|
||||
bool xrMode_ = false, touch_ = false, index_ = false;
|
||||
PadProfile profile_ = PadProfile::Index;
|
||||
bool xrMode_ = false;
|
||||
uint32_t id_ = k_unTrackedDeviceIndexInvalid;
|
||||
DriverPose_t pose_ = {};
|
||||
float pull_ = 0.0f, handX_ = 0.0f, handY_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point lastUpdate_ = std::chrono::steady_clock::now();
|
||||
VRInputComponentHandle_t system_ = 0, menu_ = 0, grip_ = 0, triggerClick_ = 0, trigger_ = 0, padX_ = 0, padY_ = 0,
|
||||
padClick_ = 0, padTouch_ = 0, haptic_ = 0;
|
||||
VRInputComponentHandle_t gripValue_ = 0, gripForce_ = 0, triggerTouch_ = 0, primaryClick_ = 0, secondaryClick_ = 0,
|
||||
VRInputComponentHandle_t gripValue_ = 0, triggerTouch_ = 0, primaryClick_ = 0, secondaryClick_ = 0,
|
||||
primaryTouch_ = 0, secondaryTouch_ = 0, thumbrestTouch_ = 0;
|
||||
};
|
||||
|
||||
@@ -1630,8 +1594,7 @@ private:
|
||||
VRServerDriverHost()->TrackedDevicePoseUpdated(id_, head, sizeof(DriverPose_t));
|
||||
const bool lumaJoyCons = (spatial.flags & DD_SPATIAL_ACTIVE) && (spatial.flags & DD_SPATIAL_JOYCONS);
|
||||
const bool optical = !lumaJoyCons && (spatial.flags & DD_SPATIAL_ACTIVE) && (spatial.flags & DD_SPATIAL_HANDS);
|
||||
const uint32_t chord = PAD_L3 | PAD_R3;
|
||||
if (!optical && havePad && (pad.buttons & chord) == chord && (lastPad_.buttons & chord) != chord) {
|
||||
if (!optical && havePad && (pad.buttons & PAD_L3) && !(lastPad_.buttons & PAD_L3)) {
|
||||
sticksMoveHands_ = !sticksMoveHands_;
|
||||
Log("sticks now move %s", sticksMoveHands_ ? "the hands" : "in the game");
|
||||
}
|
||||
@@ -1750,7 +1713,6 @@ private:
|
||||
PropertyContainerHandle_t container_ = k_ulInvalidPropertyContainer;
|
||||
public:
|
||||
void SetHands(CardboardController *left, CardboardController *right) { hands_[0] = left; hands_[1] = right; }
|
||||
void SetSticksMoveHands(bool on) { sticksMoveHands_ = on; }
|
||||
void SetHandControllers(HandController *left, HandController *right) { handControllers_[0] = left; handControllers_[1] = right; }
|
||||
bool WantsHand(int side) const { return wantHand_[side].load(); }
|
||||
|
||||
@@ -1767,7 +1729,7 @@ private:
|
||||
PadState lastPad_;
|
||||
VRInputComponentHandle_t proximity_ = 0;
|
||||
bool worn_ = true;
|
||||
bool sticksMoveHands_ = false;
|
||||
bool sticksMoveHands_ = true;
|
||||
float reach_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point lastReach_ = std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point lastScan_{};
|
||||
@@ -1805,12 +1767,7 @@ public:
|
||||
Log("DroidDeck Cardboard driver loaded");
|
||||
if (!VRServerDriverHost()->TrackedDeviceAdded("droiddeck-cardboard-hmd", TrackedDeviceClass_HMD, &hmd_))
|
||||
Log("TrackedDeviceAdded failed");
|
||||
const PadControllerConfig pads = read_pad_controller_config("/run/droiddeck/" DROIDDECK_VR_CONTROLLERS_FILE);
|
||||
Log("game pad hands: %s, sticks start in %s", pad_profile_name(pads.profile), pads.sticksMoveHands ? "the hands" : "the game");
|
||||
hmd_.SetSticksMoveHands(pads.sticksMoveHands);
|
||||
if (pads.profile != PadProfile::Off && VRSettings()->GetBool(kSettingsSection, "gamepadControllers")) {
|
||||
left_.SetProfile(pads.profile);
|
||||
right_.SetProfile(pads.profile);
|
||||
if (VRSettings()->GetBool(kSettingsSection, "gamepadControllers")) {
|
||||
VRServerDriverHost()->TrackedDeviceAdded("droiddeck-pad-left", TrackedDeviceClass_Controller, &left_);
|
||||
VRServerDriverHost()->TrackedDeviceAdded("droiddeck-pad-right", TrackedDeviceClass_Controller, &right_);
|
||||
hmd_.SetHands(&left_, &right_);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#define DROIDDECK_VR_CONTROLLERS_FILE "droiddeck-vr-controllers"
|
||||
|
||||
enum class PadProfile { Off, Index, Touch, Vive };
|
||||
|
||||
struct PadControllerConfig {
|
||||
PadProfile profile = PadProfile::Index;
|
||||
bool sticksMoveHands = false;
|
||||
};
|
||||
|
||||
// One key=value per line, written by the app before SteamVR starts.
|
||||
inline PadControllerConfig pad_controller_config(const char *text) {
|
||||
PadControllerConfig config;
|
||||
if (!text) return config;
|
||||
for (const char *line = text; *line;) {
|
||||
const char *end = std::strchr(line, '\n');
|
||||
const size_t length = end ? size_t(end - line) : std::strlen(line);
|
||||
const auto is = [&](const char *expected) {
|
||||
return std::strlen(expected) == length && !std::strncmp(line, expected, length);
|
||||
};
|
||||
if (is("emulate=off")) config.profile = PadProfile::Off;
|
||||
else if (is("emulate=index")) config.profile = PadProfile::Index;
|
||||
else if (is("emulate=touch")) config.profile = PadProfile::Touch;
|
||||
else if (is("emulate=vive")) config.profile = PadProfile::Vive;
|
||||
else if (is("sticks=hands")) config.sticksMoveHands = true;
|
||||
else if (is("sticks=game")) config.sticksMoveHands = false;
|
||||
if (!end) break;
|
||||
line = end + 1;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
inline PadControllerConfig read_pad_controller_config(const char *path) {
|
||||
char text[256] = {};
|
||||
if (FILE *f = std::fopen(path, "r")) {
|
||||
const size_t n = std::fread(text, 1, sizeof(text) - 1, f);
|
||||
text[n] = 0;
|
||||
std::fclose(f);
|
||||
}
|
||||
return pad_controller_config(text);
|
||||
}
|
||||
|
||||
inline const char *pad_profile_name(PadProfile profile) {
|
||||
switch (profile) {
|
||||
case PadProfile::Off: return "off";
|
||||
case PadProfile::Index: return "Index";
|
||||
case PadProfile::Touch: return "Quest Touch";
|
||||
case PadProfile::Vive: return "Vive";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
@@ -80,7 +80,6 @@ int main() {
|
||||
Context context;
|
||||
VRDriverContext() = &context;
|
||||
CardboardController controller(false);
|
||||
controller.SetProfile(PadProfile::Vive);
|
||||
assert(controller.Activate(1) == VRInitError_None);
|
||||
const auto gripHandle=context.input.handles.at("/input/grip/click");
|
||||
const auto clickHandle=context.input.handles.at("/input/trigger/click");
|
||||
@@ -261,37 +260,4 @@ int main() {
|
||||
joyCon.Update(nullptr, false, {1,0,0,0}, now, &spatial);
|
||||
assert(!joyCon.GetPose().deviceIsConnected && context.input.values.at(joyTrigger) == 0);
|
||||
puts("PASS: optical controller recovery and Luma Joy-Con camera, fallback, head loss and origin changes");
|
||||
|
||||
for (const PadProfile profile : {PadProfile::Index, PadProfile::Touch}) {
|
||||
auto &padContext = context;
|
||||
padContext.input.handles.clear();
|
||||
CardboardController pad(true);
|
||||
pad.SetProfile(profile);
|
||||
assert(pad.Activate(2) == VRInitError_None);
|
||||
const auto &h = padContext.input.handles;
|
||||
const std::string stick = profile == PadProfile::Touch ? "/input/joystick" : "/input/thumbstick";
|
||||
assert(!h.count("/input/trackpad/x") && h.count("/input/b/click") && h.count("/input/grip/value"));
|
||||
assert(h.count("/input/application_menu/click") == (profile == PadProfile::Touch));
|
||||
DriverPose_t head{};
|
||||
head.qRotation.w = 1;
|
||||
PadState state{};
|
||||
state.buttons = PAD_A | PAD_RB;
|
||||
state.axes[2] = 32767;
|
||||
state.axes[4] = 255;
|
||||
pad.Update(head, &state, false, 0);
|
||||
const auto value = [&](const std::string &name) { return padContext.input.values.at(h.at(name)); };
|
||||
assert(value("/input/a/click") == 1 && value("/input/b/click") == 0);
|
||||
assert(value(stick + "/x") == 1 && value(stick + "/touch") == 1 && value(stick + "/click") == 0);
|
||||
assert(value("/input/grip/value") == 1 && value("/input/trigger/value") == 1);
|
||||
pad.Update(head, &state, true, 0);
|
||||
assert(value(stick + "/x") == 0);
|
||||
state = {};
|
||||
state.buttons = PAD_R3;
|
||||
pad.Update(head, &state, false, 0);
|
||||
assert(value(stick + "/click") == 1);
|
||||
state.buttons = PAD_L3 | PAD_R3;
|
||||
pad.Update(head, &state, false, 0);
|
||||
assert(value(stick + "/click") == 0);
|
||||
}
|
||||
puts("PASS: game pad hands as Index and Quest Touch thumbstick controllers");
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "../src/pad_controller_config.h"
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
int main() {
|
||||
auto config = pad_controller_config(nullptr);
|
||||
assert(config.profile == PadProfile::Index && !config.sticksMoveHands);
|
||||
config = pad_controller_config("emulate=touch\nsticks=hands\n");
|
||||
assert(config.profile == PadProfile::Touch && config.sticksMoveHands);
|
||||
config = pad_controller_config("emulate=off");
|
||||
assert(config.profile == PadProfile::Off);
|
||||
config = pad_controller_config("emulate=vive\nsticks=game");
|
||||
assert(config.profile == PadProfile::Vive && !config.sticksMoveHands);
|
||||
config = pad_controller_config("emulate=offline\nsticks=handsome\n");
|
||||
assert(config.profile == PadProfile::Index && !config.sticksMoveHands);
|
||||
assert(read_pad_controller_config("/nonexistent/droiddeck").profile == PadProfile::Index);
|
||||
std::puts("pad controller config ok");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user