Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

This commit is contained in:
maxjivi05 committed 2026-10-13 11:24:01 -04:00
1 parent c8c62960f6
commit a746d56dc8
5 files changed
+130 -21

No files matched your search

@@ -17,7 +17,6 @@ import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.Column
@@ -204,9 +203,6 @@ internal fun BoxWithConstraintsScope.DropSheet(
val narrow = LocalNarrowPane.current
val chipFocus = remember(tabs.size) { List(tabs.size) { FocusRequester() } }
val buttonFocus = remember { FocusRequester() }
val buttonSrc = remember { MutableInteractionSource() }
val buttonHovered by buttonSrc.collectIsHoveredAsState()
var buttonFocused by remember { mutableStateOf(false) }
val rightForButton = remember { booleanArrayOf(false) }
var rest by remember { mutableStateOf<Rect?>(null) }
val openState = rememberUpdatedState(state.open)
@@ -277,8 +273,7 @@ internal fun BoxWithConstraintsScope.DropSheet(
BackHandler(enabled = state.open) { close() }
if (state.ride.isRunning || (state.shown && !state.open)) VeilRing()
val lipWanted = !state.open && (!state.shown || state.lipBack) && covered() < 0.5f &&
(buttonFocused || buttonHovered || inputMode.inputMode == InputMode.Touch)
val lipWanted = !state.open && (!state.shown || state.lipBack) && covered() < 0.5f
LaunchedEffect(lipWanted) {
if (lipWanted) {
if (!state.lipBack) delay(Motion.ms(160).toLong())
@@ -453,9 +448,7 @@ internal fun BoxWithConstraintsScope.DropSheet(
.focusProperties { canFocus = enabled }
.onFocusChanged {
if (it.hasFocus && rightForButton[0]) { rightForButton[0] = false; if (openState.value) close() }
buttonFocused = it.hasFocus
}
.hoverable(buttonSrc)
.onPreviewKeyEvent { e ->
val k = e.nativeKeyEvent
if (k.keyCode != KeyEvent.KEYCODE_DPAD_DOWN && !(state.open && k.keyCode == KeyEvent.KEYCODE_DPAD_LEFT)) false
+6
View File
@@ -80,6 +80,12 @@ Added by DroidDeck:
The `faccessat2` exit stop (termux 5ba8b95, for glibc's ENOSYS fallback on kernels before 5.8) and
the `statx` one (emulation on kernels before 4.11) are dropped when the running kernel is newer.
Host build: `ioctl` 31.6 -> 0.5 us; `stat`/`statx`/`faccessat2` about 44 -> 28 us (one stop).
- `0015-robust-list-from-seccomp.patch` - Android's app policy refuses `set_robust_list` and
`get_robust_list`. proot still answers the first with `ENOSYS` but keeps the head each thread
offered (cleared on `execve`, none in a new task), and answers the second for the calling thread
with it. SteamVR's `vrclient.so` asks for the head through libc's `syscall()`, which
`libblsession.so`'s robust.c never sees: `libopenvr_api.so` opens it `RTLD_DEEPBIND`. Steam
aborted on "futex robust_list not initialized by pthreads" as soon as SteamVR was registered.
Prototype (inert unless `PROOT_FASTPATH` is set; see `docs/development/proot-performance.md`):
@@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: DroidDeck <[email protected]>
Date: Tue, 13 Oct 2026 00:00:00 +0000
Subject: [PATCH] seccomp: answer get_robust_list with the head set_robust_list
was refused for
diff --git a/src/execve/exit.c b/src/execve/exit.c
index f32d3ec..b5475b3 100644
--- a/src/execve/exit.c
+++ b/src/execve/exit.c
@@ -473,6 +473,8 @@ void translate_execve_exit(Tracee *tracee)
talloc_set_name_const(tracee->exe, "$exe");
}
+ tracee->robust_list = 0;
+
/* New processes have no heap. */
if (talloc_reference_count(tracee->heap) >= 1) {
talloc_unlink(tracee, tracee->heap);
diff --git a/src/tracee/seccomp.c b/src/tracee/seccomp.c
index 77d5b27..0af232b 100644
--- a/src/tracee/seccomp.c
+++ b/src/tracee/seccomp.c
@@ -559,6 +559,24 @@ static int handle_seccomp_event_common(Tracee *tracee)
}
case PR_set_robust_list:
+ tracee->robust_list = peek_reg(tracee, CURRENT, SYSARG_1);
+ set_result_after_seccomp(tracee, -ENOSYS);
+ break;
+
+ case PR_get_robust_list:
+ {
+ pid_t pid = (pid_t) peek_reg(tracee, CURRENT, SYSARG_1);
+ if (tracee->robust_list == 0 || (pid != 0 && pid != tracee->pid)) {
+ set_result_after_seccomp(tracee, -ENOSYS);
+ break;
+ }
+ poke_word(tracee, peek_reg(tracee, CURRENT, SYSARG_2), tracee->robust_list);
+ if (errno == 0)
+ poke_word(tracee, peek_reg(tracee, CURRENT, SYSARG_3), 3 * sizeof_word(tracee));
+ set_result_after_seccomp(tracee, errno != 0 ? -EFAULT : 0);
+ break;
+ }
+
default:
/* Set errno to -ENOSYS */
set_result_after_seccomp(tracee, -ENOSYS);
diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h
index 1fca85f..496ca75 100644
--- a/src/tracee/tracee.h
+++ b/src/tracee/tracee.h
@@ -248,6 +248,8 @@ typedef struct tracee {
* has to be ignored as it's same syscall entry */
bool seccomp_already_handled_enter;
+ word_t robust_list;
+
/**********************************************************************
* Shared or private resources, depending on the CLONE_FS/VM flags. *
**********************************************************************/
+19 -7
View File
@@ -464,6 +464,7 @@ public:
EVRInitError Activate(uint32_t unObjectId) override {
id_ = unObjectId;
PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(id_);
container_ = c;
width_ = VRSettings()->GetInt32(kSettingsSection, "windowWidth");
height_ = VRSettings()->GetInt32(kSettingsSection, "windowHeight");
if (width_ <= 0) width_ = 1920;
@@ -480,6 +481,7 @@ public:
}
if (pose_.Read(&hp) && hp.display_hz >= 24.0f) hz = hp.display_hz;
Vsync::SetRate(hz);
sentHz_ = hz;
sentTan_ = tanHalf_;
sentAspect_ = eyeAspect_;
VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, "DroidDeck Cardboard");
@@ -509,7 +511,7 @@ public:
VRProperties()->SetBoolProperty(c, Prop_ContainsProximitySensor_Bool, true);
VRDriverInput()->CreateBooleanComponent(c, "/proximity", &proximity_);
VRDriverInput()->UpdateBooleanComponent(proximity_, true, 0);
Log("HMD activated: %dx%d at %.0f Hz, eye tan(half fov) %.3f, eye aspect %.3f", width_, height_, hz, tanHalf_, eyeAspect_);
Log("HMD activated: %dx%d at %.0f Hz, eye tan(half fov) %.3f, eye aspect %.3f", width_, height_, hz, tanHalf_.load(), eyeAspect_.load());
running_ = true;
thread_ = std::thread([this] { PoseLoop(); });
@@ -565,8 +567,9 @@ public:
bool IsDisplayRealDisplay() override { return false; }
void GetRecommendedRenderTargetSize(uint32_t *w, uint32_t *h) override {
// Rendered a little larger than the panel: the lens pass magnifies the middle.
*w = (uint32_t)(height_ * 1.4f * eyeAspect_);
*h = (uint32_t)(height_ * 1.4f);
const float scale = renderScale_;
*w = (uint32_t)(height_ * scale * eyeAspect_);
*h = (uint32_t)(height_ * scale);
}
void GetEyeOutputViewport(EVREye eye, uint32_t *x, uint32_t *y, uint32_t *w, uint32_t *h) override {
*y = 0; *w = (uint32_t)width_ / 2; *h = (uint32_t)height_;
@@ -592,6 +595,7 @@ private:
void UpdateEye(const droiddeck_headpose &hp) {
sentTan_ = tanHalf_ = hp.tan_half_fov;
sentAspect_ = eyeAspect_ = hp.eye_aspect;
renderScale_ = (hp.flags & DROIDDECK_HEADPOSE_MONO) ? 1.0f : 1.4f;
HmdRect2_t r;
r.vTopLeft.v[0] = -tanHalf_ * eyeAspect_;
r.vTopLeft.v[1] = -tanHalf_;
@@ -601,10 +605,17 @@ private:
uint32_t w = 0, h = 0;
GetRecommendedRenderTargetSize(&w, &h);
VRServerDriverHost()->SetRecommendedRenderTargetSize(id_, w, h);
Log("eye now tan(half fov) %.3f, aspect %.3f (%ux%u eye%s), render %ux%u", tanHalf_, eyeAspect_, hp.eye_w, hp.eye_h,
Log("eye now tan(half fov) %.3f, aspect %.3f (%ux%u eye%s), render %ux%u", tanHalf_.load(), eyeAspect_.load(), hp.eye_w, hp.eye_h,
(hp.flags & DROIDDECK_HEADPOSE_MONO) ? ", one eye shown" : "", w, h);
}
void UpdateRate(float hz) {
sentHz_ = hz;
Vsync::SetRate(hz);
VRProperties()->SetFloatProperty(container_, Prop_DisplayFrequency_Float, hz);
Log("panel now %.0f Hz", hz);
}
void PoseLoop() {
uint32_t lastSeq = 0;
auto lastSent = std::chrono::steady_clock::now();
@@ -622,7 +633,7 @@ private:
if (hp.eye_w > 0 && hp.tan_half_fov > 0.0f && hp.eye_aspect > 0.0f &&
(fabsf(hp.tan_half_fov - sentTan_) > 1e-3f || fabsf(hp.eye_aspect - sentAspect_) > 1e-3f))
UpdateEye(hp);
if (hp.display_hz >= 24.0f) Vsync::SetRate(hp.display_hz);
if (hp.display_hz >= 24.0f && fabsf(hp.display_hz - sentHz_) > 0.5f) UpdateRate(hp.display_hz);
const bool worn = (hp.flags & DROIDDECK_HEADPOSE_ENABLED) != 0;
if (worn != worn_) {
worn_ = worn;
@@ -642,8 +653,9 @@ private:
uint32_t id_ = k_unTrackedDeviceIndexInvalid;
int32_t width_ = 1920, height_ = 1080;
float tanHalf_ = 1.0f, eyeAspect_ = 0.889f;
float sentTan_ = 0.0f, sentAspect_ = 0.0f;
std::atomic<float> tanHalf_{1.0f}, eyeAspect_{0.889f}, renderScale_{1.4f};
float sentTan_ = 0.0f, sentAspect_ = 0.0f, sentHz_ = 0.0f;
PropertyContainerHandle_t container_ = k_ulInvalidPropertyContainer;
public:
void SetHands(CardboardController *left, CardboardController *right) { hands_[0] = left; hands_[1] = right; }
+43 -6
View File
@@ -312,21 +312,25 @@ static void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallback
* One pretend display with one mode and one plane. vrcompositor keeps a direct mode window on it even
* when a driver-direct driver takes the frames, and waits on that window's presents, so the display
* needs a working swapchain: its surface is a headless one (VK_EXT_headless_surface, Mesa's WSI).
* The vblank side of VK_EXT_display_control is a 60 Hz clock. Size and rate:
* DROIDDECK_VR_DISPLAY_MODE=WIDTHxHEIGHT@HZ (default 1920x1080@60). */
* The vblank side of VK_EXT_display_control is a clock at the panel's rate. Size and rate:
* DROIDDECK_VR_DISPLAY_MODE=WIDTHxHEIGHT@HZ (default 1920x1080, at the head pose file's rate, else 60). */
#include <fcntl.h>
#include <stdatomic.h>
#include <time.h>
#include "../../../app/src/main/cpp/waylandcomp/src/headpose_shm.h"
#define FAKE_DISPLAY ((VkDisplayKHR)(uintptr_t)0xdd0001)
#define FAKE_MODE ((VkDisplayModeKHR)(uintptr_t)0xdd0002)
static uint32_t g_mode_w = 1920, g_mode_h = 1080, g_mode_mhz = 60000;
static int g_mode_fixed;
static void mode_init(void) {
static int done;
if (done++) return;
const char *m = getenv("DROIDDECK_VR_DISPLAY_MODE");
unsigned w, h, hz;
if (m && sscanf(m, "%ux%u@%u", &w, &h, &hz) == 3 && w && h && hz) { g_mode_w = w; g_mode_h = h; g_mode_mhz = hz * 1000; }
if (m && sscanf(m, "%ux%u@%u", &w, &h, &hz) == 3 && w && h && hz) { g_mode_w = w; g_mode_h = h; g_mode_mhz = hz * 1000; g_mode_fixed = 1; }
}
static uint64_t now_ns(void) {
@@ -334,7 +338,40 @@ static uint64_t now_ns(void) {
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
}
static uint64_t frame_ns(void) { return 1000000000000ull / g_mode_mhz; }
static _Atomic(const struct droiddeck_headpose *) g_pose;
static _Atomic uint64_t g_pose_tried;
static const struct droiddeck_headpose *pose_map(void) {
const struct droiddeck_headpose *p = atomic_load(&g_pose);
if (p) return p;
uint64_t t = now_ns(), last = atomic_load(&g_pose_tried);
if (last && t - last < 1000000000ull) return NULL;
atomic_store(&g_pose_tried, t);
int fd = open("/run/droiddeck/" DROIDDECK_HEADPOSE_FILE, O_RDONLY | O_CLOEXEC);
if (fd < 0) return NULL;
void *m = mmap(NULL, sizeof(struct droiddeck_headpose), PROT_READ, MAP_SHARED, fd, 0);
close(fd);
if (m == MAP_FAILED) return NULL;
const struct droiddeck_headpose *expected = NULL;
if (!atomic_compare_exchange_strong(&g_pose, &expected, (const struct droiddeck_headpose *)m)) {
munmap(m, sizeof(struct droiddeck_headpose));
return expected;
}
return m;
}
static uint32_t mode_mhz(void) {
mode_init();
if (g_mode_fixed) return g_mode_mhz;
const struct droiddeck_headpose *p = pose_map();
if (!p || __atomic_load_n(&p->magic, __ATOMIC_RELAXED) != DROIDDECK_HEADPOSE_MAGIC) return g_mode_mhz;
uint32_t bits = __atomic_load_n((const uint32_t *)&p->display_hz, __ATOMIC_RELAXED);
float hz;
memcpy(&hz, &bits, sizeof(hz));
return hz >= 24.0f && hz <= 240.0f ? (uint32_t)(hz * 1000.0f + 0.5f) : g_mode_mhz;
}
static uint64_t frame_ns(void) { return 1000000000000ull / mode_mhz(); }
static VkDisplayPropertiesKHR display_props(void) {
mode_init();
@@ -384,7 +421,7 @@ static VkResult VKAPI_CALL GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice
}
static VkDisplayModePropertiesKHR mode_props(void) {
mode_init();
VkDisplayModePropertiesKHR m = {FAKE_MODE, {{g_mode_w, g_mode_h}, g_mode_mhz}};
VkDisplayModePropertiesKHR m = {FAKE_MODE, {{g_mode_w, g_mode_h}, mode_mhz()}};
return m;
}
static VkResult VKAPI_CALL GetDisplayModePropertiesKHR(VkPhysicalDevice pd, VkDisplayKHR d, uint32_t *count, VkDisplayModePropertiesKHR *p) {
@@ -452,7 +489,7 @@ static VkResult VKAPI_CALL GetRandROutputDisplayEXT(VkPhysicalDevice pd, void *d
*d = FAKE_DISPLAY; return VK_SUCCESS;
}
/* VK_EXT_display_control on the device: a 60 Hz clock. A vblank event's fence comes back signalled
/* VK_EXT_display_control on the device: a clock at the panel's rate. A vblank event's fence comes back signalled
* once the next tick has passed (the caller's vblank thread waits right after registering). */
static VkResult VKAPI_CALL DisplayPowerControlEXT(VkDevice d, VkDisplayKHR disp, const VkDisplayPowerInfoEXT *info) { return VK_SUCCESS; }
static VkResult signalled_fence(VkDevice d, const VkAllocationCallbacks *a, VkFence *fence) {