mirror of
https://github.com/kuraeow/ultratears.git
synced 2026-11-07 10:16:00 -10:00
Steam Deck: gyro aiming from the Deck's own controller beside Steam Input
In game mode Steam Input gives the game a virtual gamepad without motion sensors, so motion aiming did nothing on the Deck, and turning Steam Input off would lose the player's layout. deck_imu.c finds the hidraw device of the Deck's controller (28de:1205) that sends state reports and feeds its gyro and accelerometer to the motion fusion, while buttons and sticks keep coming through Steam Input; it starts only when the opened gamepad has no sensors of its own. Report layout and scales as SDL's Steam Deck driver reads them (no code taken). TOTK_DECK_GYRO=0: off. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f44ce52230
commit
d88377ba3a
3 files changed
+111
No files matched your search
@@ -0,0 +1,107 @@
|
||||
// Steam Deck: the gyro and accelerometer of the Deck's own controller, read from its HID device. In game mode Steam
|
||||
// Input hands the game a virtual gamepad, which has buttons and sticks but no motion sensors, so motion aiming did
|
||||
// nothing on the Deck; turning Steam Input off for the game would lose the player's Steam layout. The controller's HID
|
||||
// device (Valve 28de:1205) stays readable beside Steam (Steam's udev rules give the logged-in user access to it): this
|
||||
// thread reads its state reports and feeds their motion to motion_fusion.c, while buttons and sticks keep coming
|
||||
// through Steam Input. Started only when the gamepad SDL opened has no motion sensors of its own (window_sdl.c).
|
||||
// Report layout and scales as SDL's Steam Deck driver reads them (SDL3 src/joystick/hidapi/SDL_hidapi_steamdeck.c,
|
||||
// zlib; no code taken): a 64-byte report, bytes 0-1 the version 0x0001, byte 2 the type (0x09: the controller's state),
|
||||
// the state from byte 4: packet number (4), buttons (8), the two pads (8), then accelerometer x, y, z and gyro x, y, z
|
||||
// as int16, little-endian; 32768 = 2 G and 2000 deg/s. TOTK_DECK_GYRO=0: off.
|
||||
#include <stdint.h>
|
||||
|
||||
int deck_imu_start(void);
|
||||
|
||||
#ifndef __linux__
|
||||
int deck_imu_start(void) { return 0; }
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "host.h"
|
||||
|
||||
enum { REPORT = 64, STATE = 0x09 };
|
||||
static int g_fd = -1, g_started;
|
||||
|
||||
static int le16(const unsigned char *p) { return (int16_t)(p[0] | p[1] << 8); }
|
||||
static int is_state(const unsigned char *b, ssize_t n) { return n >= 36 && b[0] == 0x01 && b[1] == 0x00 && b[2] == STATE; }
|
||||
|
||||
// The hidraw device of the Deck's controller interface: the one of 28de:1205 that sends state reports.
|
||||
static int open_deck(void) {
|
||||
int denied = 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
char path[96], line[256];
|
||||
snprintf(path, sizeof path, "/sys/class/hidraw/hidraw%d/device/uevent", i);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) continue;
|
||||
int match = 0;
|
||||
while (fgets(line, sizeof line, f)) if (!strncmp(line, "HID_ID=", 7) && strstr(line, "000028DE:00001205")) match = 1;
|
||||
fclose(f);
|
||||
if (!match) continue;
|
||||
snprintf(path, sizeof path, "/dev/hidraw%d", i);
|
||||
int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
if (fd < 0) { if (errno == EACCES) denied = 1; continue; }
|
||||
unsigned char b[REPORT];
|
||||
for (int tries = 0; tries < 10; tries++) { // up to ~0.2 s for a report
|
||||
struct pollfd pf = {fd, POLLIN, 0};
|
||||
if (poll(&pf, 1, 20) <= 0) continue;
|
||||
ssize_t n = read(fd, b, sizeof b);
|
||||
if (is_state(b, n)) {
|
||||
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
|
||||
fprintf(stderr, "[totk] Steam Deck controller motion read from %s (beside Steam Input)\n", path);
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
if (denied) fprintf(stderr, "[totk] the Steam Deck controller's HID device is not readable (permissions): no gyro aiming\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void *deck_main(void *arg) {
|
||||
(void)arg;
|
||||
unsigned char b[REPORT];
|
||||
uint64_t quiet = 0, said_quiet = 0;
|
||||
for (;;) {
|
||||
ssize_t n = read(g_fd, b, sizeof b);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
fprintf(stderr, "[totk] Steam Deck controller motion: read failed (%s); stopped\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
if (!is_state(b, n)) continue;
|
||||
const unsigned char *s = b + 4;
|
||||
int ax = le16(s + 20), ay = le16(s + 22), az = le16(s + 24), gx = le16(s + 26), gy = le16(s + 28), gz = le16(s + 30);
|
||||
if (!(ax | ay | az | gx | gy | gz)) { // Steam has the IMU off: nothing to feed
|
||||
if (++quiet == 1000 && !said_quiet++) fprintf(stderr, "[totk] Steam Deck controller: its motion sensors report nothing\n");
|
||||
continue;
|
||||
}
|
||||
quiet = 0;
|
||||
// SDL's gamepad frame from the report (as its Steam Deck driver), then the Switch frame (as window_sdl.c's
|
||||
// pad_sensor): the gyro reads in the report's own axes, the acceleration negated (gravity, not the push).
|
||||
const double k = 2000.0 * M_PI / 180.0 / 32768.0, a = 2.0 / 32768.0;
|
||||
double acc[3] = {-ax * a, -ay * a, -az * a}, gyro[3] = {gx * k, gy * k, gz * k};
|
||||
runtime_motion_feed(acc, gyro, runtime_now_ns(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 1: the Deck's motion is fed (or already was).
|
||||
int deck_imu_start(void) {
|
||||
if (g_started) return g_fd >= 0;
|
||||
g_started = 1;
|
||||
const char *e = getenv("TOTK_DECK_GYRO");
|
||||
if (e && *e == '0') return 0;
|
||||
if ((g_fd = open_deck()) < 0) return 0;
|
||||
pthread_t t;
|
||||
if (pthread_create(&t, NULL, deck_main, NULL) != 0) { close(g_fd); g_fd = -1; return 0; }
|
||||
pthread_detach(t);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
@@ -142,6 +142,7 @@ int window_init(void) {
|
||||
}
|
||||
|
||||
// Motion: the controller's accelerometer and gyro (SDL_EVENT_GAMEPAD_SENSOR_UPDATE) go to motion_fusion.c.
|
||||
int deck_imu_start(void); // deck_imu.c
|
||||
static void pad_attached(void) { // pad_lock held; pad may be NULL
|
||||
if (!pad) { runtime_motion_live(0); return; }
|
||||
fprintf(stderr, "[totk] game controller: %s\n", SDL_GetGamepadName(pad));
|
||||
@@ -158,6 +159,8 @@ static void pad_attached(void) { // pad_lock held; pad may be NULL
|
||||
if (runtime_motion_axes(axes) < 0) fprintf(stderr, "[totk] TOTK_GYRO_AXES=%s is not like \"-x,y,-z\": ignored\n", axes);
|
||||
runtime_motion_live(1);
|
||||
fprintf(stderr, "[totk] controller motion sensors on (%.0f Hz gyro)\n", SDL_GetGamepadSensorDataRate(pad, SDL_SENSOR_GYRO));
|
||||
} else if (deck_imu_start()) { // a Steam Input gamepad on the Steam Deck: the Deck's own motion (deck_imu.c)
|
||||
runtime_motion_live(1);
|
||||
} else {
|
||||
runtime_motion_live(0);
|
||||
fprintf(stderr, "[totk] controller has no motion sensors: no gyro aiming\n");
|
||||
|
||||
@@ -32,6 +32,7 @@ TARGETS = {'probe_cpu': ['src/probe/probe_cpu.c'],
|
||||
'src/host/audio_reverb.c', 'src/host/opus_codec.c', 'src/host/movie_host.c', 'src/host/hid_feedback.c',
|
||||
'src/host/shared_font.c',
|
||||
'src/host/window_sdl.c', 'src/vk/vk.c', 'src/vk/present.c', 'src/vk/fsr.c', 'src/vk/overlay.c', 'src/host/motion_fusion.c',
|
||||
'src/host/deck_imu.c',
|
||||
'src/host/profiler.c', 'src/host/watch.c', 'src/host/frame_rate.c', 'src/host/render_res.c', 'src/host/ui_capture.c',
|
||||
'src/host/ui_aspect.c', 'src/host/zstd_host.c']}
|
||||
# The graphics front end (MacNativeGames' NVN layer) with its macOS stand-ins: own include paths, own rule
|
||||
|
||||
Reference in new issue
Block a user