- Python 56.4%
- TypeScript 41.1%
- Shell 2.3%
- JavaScript 0.2%
|
All checks were successful
Build plugin bundle / bundle (push) Successful in 20s
|
||
|---|---|---|
| .forgejo/workflows | ||
| bin | ||
| defaults | ||
| dist | ||
| lib | ||
| scripts | ||
| src | ||
| typelib | ||
| .gitignore | ||
| .npmrc | ||
| LICENSE | ||
| main.py | ||
| package.json | ||
| plugin.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| rollup.config.js | ||
| tools_clear_storage.py | ||
| tools_list_prints.py | ||
| tsconfig.json | ||
Deck Fingerprint
Fingerprint enrollment and (eventually) Game Mode unlock for the ROG Xbox Ally X and other SteamOS devices with an EgisTec Match-on-Chip sensor.
Packaged as a Decky Loader plugin so it persists across
SteamOS updates (lives in ~/homebrew, no immutable-rootfs changes) and gets
a Game Mode UI for free.
Why this approach
SteamOS ships no fprintd/libfprint, and its rootfs is immutable/atomic, so
you can't just pacman -S fprintd. Instead we vendor a minimal stack that
was validated to run natively on the host:
bin/—fprintddaemon +fprintd-{enroll,verify,list,delete}clientslib/—libfprint-2.so.2(carries theegismocdriver) andlibgusb.so.2
Everything else in the dependency closure is already present on SteamOS. glibc
is forward-compatible here (host 2.41 runs these fine; verified — no missing
symbols). The egismoc driver binds the sensor (1c7a:0588), reports a
press-type Match-on-Chip device with 21 enroll stages.
Runtime model
The backend runs as root ("flags": ["root"]). It does not register
fprintd on the host system bus. Instead it spawns a private dbus-daemon
with a permissive policy and points fprintd + the client tools at it via
DBUS_SYSTEM_BUS_ADDRESS. Nothing touches /etc or /usr; teardown on unload
is clean. Enrolled templates live on the sensor; only metadata sits in
/var/lib/fprint (persistent /var).
The SDCP requirement
Stock upstream egismoc drives this sensor's probe/enroll motions but the
1c7a:0588 on the ROG Xbox Ally X is an SDCP (Secure Device Connection
Protocol) part: without the secure channel, enroll "completes" but the sensor
stores nothing (device template count stays 0), so verify never matches.
The working stack uses libfprint from the SDCP fork
(TenSeventy7/libfprint-egismoc-sdcp, based on libfprint 1.94.9) with two local
changes, built egismoc-only and vendored as lib/libfprint-2.so.2:
- Add
0588to the egismoc id table (TYPE1 | MAX_ENROLL_STAGES_20, matching its0586/0587siblings). - OpenSSL 3.6 host-key fix in
fpi_sdcp_set_host_keys: the fork exported the generated EC public key viaEVP_PKEY_get_octet_string_param(PUB_KEY), which on OpenSSL ≥ 3.x ignores the uncompressed point-format param on immutable provider keys and returns the wrong length (assertpublic_key_len == 65fails → crash). Fixed by deriving the 65-byte uncompressed key with the low-levelEC_POINT_point2oct(..., POINT_CONVERSION_UNCOMPRESSED, ...), matching whatsdcp_get_pkey()already does. Also requires linkingopenssl_depinto the coredepsinlibfprint/meson.build(the fork only wired it in per-driver).
With that, SDCP Connect → device ConnectResponse verifies ("CN=Secure BIO
Sensor"), enroll commits a real on-chip template (count=1), and verify matches.
MoC gotcha: re-enrolling the same finger deletes the existing template first;
if the re-enroll doesn't complete you lose the enrollment. The UI must guard this.
Orphaned templates (e.g. factory Windows Hello) are cleared via libfprint
clear_storage (see tools_clear_storage.py) since fprintd's per-user delete
can't reach them.
TODO
- Lock watcher polls every 1s when unlocked; could use a MobX reaction instead.
Build
Requires Node + pnpm (frontend) — the vendored binaries are prebuilt.
pnpm install
pnpm build # -> dist/index.js
# then zip {plugin.json, dist/, main.py, bin/, lib/, defaults/} into ~/homebrew/plugins/