Compare commits

..
Author SHA1 Message Date
greg 6a3583d744 Add support for Java 17 via mise (#1231) 2026-10-21 18:30:34 +00:00
13 changed files with 57 additions and 322 deletions

No files matched your search

-2
View File
@@ -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)
}
},
-16
View File
@@ -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
View File
@@ -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
View File
@@ -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 -73
View File
@@ -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_);
-54
View File
@@ -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;
}