The previous fix used canonical Off (mode 0x05) everywhere we wanted the
trigger to feel released \u2014 pre-race per-frame, idle timeout, shutdown.
Per Sony's docs (Nielk1 Rev 6) mode 0x05 "actively returns the trigger
stop to the neutral position". Re-asserting it 60 times/sec from main
thread, propagated by pydualsense's BG thread to the controller at
~250 Hz, made the trigger motor audibly whine as the firmware repeatedly
snapped the (already-neutral) trigger back to neutral.
Right answer: hybrid. One-shot 0x05 on the in-race \u2192 not-in-race
transition (and on the telemetry-idle timeout) so the firmware actually
retracts the motor; mode 0x00 (TriggerModes.Off, no-op clear) for
steady-state pre-race / idle frames so we're not yelling RESET in the
firmware's ear forever.
Implementation: prev_in_race tracks the last frame's race state. Steady
non-race frames call _apply_normal (mode 0x00); the first frame after a
race-end transition calls _apply_off (mode 0x05). pydualsense's BG
thread holds the 0x05 in memory long enough (one main-thread frame =
~16ms = ~4 BG iterations) to publish it to the controller before main
switches the in-memory state to 0x00.
Restores _apply_normal and DS_MODE_NORMAL that the previous commit
deleted. Updates divergence #4 in the module docstring.
Two issues in the deployed daemon:
1. After FH5 exits, the lightbar stayed lit. reset_triggers() touched
only triggers; pydualsense's BG sendReport thread kept re-publishing
whatever TouchpadColor we last set, so the controller stayed in the
last race color forever.
2. R2 had residual tension in FH5's main menu and on the desktop after
a race. Pre-race / idle states were emitting RacingDSX's NormalTrigger
(mode byte 0x00), which per Sony's docs (Nielk1 Rev6) only clears
state without retracting the trigger motor; mode 0x05 (canonical Off
/ Reset) actively returns the trigger to neutral. RacingDSX-on-Windows
gets away with 0x00 because something else (Steam Input or the OS)
reliably resets the motor on focus loss; on Linux nothing does.
Fixes:
- Drop _apply_normal/DS_MODE_NORMAL. Use _apply_off (mode 0x05) for every
'release the trigger' intent: pre-race per-frame, idle timeout, mid-race
zero-strength fallback, shutdown.
- Add reset_lightbar() that writes RGB(0,0,0).
- Track have_telemetry and fire the idle-timeout branch whenever
telemetry has been silent for IDLE_TIMEOUT_S, regardless of in_race.
Reset both triggers and lightbar in that branch.
Documented as divergence #4 in the module docstring.
BIOS 2423→4101 update on yarn required an fTPM reset, which broke the
sealed age identity at /var/lib/agenix/tpm-identity. Bootstrapped a new
identity against the new SRK and rotated yarn's recipient.
age-plugin-tpm 1.0+ emits age1tag1… (p256tag) recipients by default and
refuses to encrypt to legacy age1tpm1… ones, so rotated mreow's recipient
to the same encoding (same key, new bech32 HRP) and added an
age-plugin-tag→age-plugin-tpm symlink in the rage wrapper so rage's
plugin dispatch finds the binary under the new prefix. Stripped the
trailing host labels from the tpm recipient strings — rage's stricter
bech32 parser now rejects the trailing whitespace; labels live in
adjacent Nix comments instead.
oo7-server 0.6.0 only feeds the systemd / PAM secret to existing
keyrings discovered on disk. On first run no keyring exists yet, the
daemon creates an empty 'Login' collection via LockedKeyring::open,
the credential is silently ignored, and any client Unlock() routes to
a prompt that nothing on a niri desktop can satisfy.
Patches/oo7-server/0001-... is upstream commit cf7b9a9 (PR #443)
regenerated relative to the package's sourceRoot ('server/'). It
switches the auto-created default-keyring path to UnlockedKeyring::open
when a secret is available.
The override threads the patch through pkgs.oo7-server.overrideAttrs
in modules/desktop-oo7-daemon.nix and uses the patched derivation for
both services.dbus.packages and systemd.packages so the user unit and
D-Bus activation file land from the same store path. Cargo.lock is
untouched, so the existing cargoDeps hash stays valid.
Drop the override once nixpkgs ships an oo7-server release that
includes the fix (anything past 0.6.0).
oo7-daemon was running but its 'Login' keyring stayed locked because
nothing supplied a master password, so libsecret clients (flare in
particular) blocked indefinitely on keyring.unlock().
The upstream user unit declares
ImportCredential=oo7.keyring-encryption-password
which picks up matching credentials from systemd's per-service
credential machinery. Wire LoadCredential=oo7.keyring-encryption-password
to the agenix-decrypted secret so the daemon unlocks at session start
without any prompt.
The password itself is a fresh 64-byte urandom value encrypted to all
desktop recipients (admin SSH key + mreow + yarn TPM identities); it's
opaque to the user and never typed manually. Owner is primary so the
user-scope unit's LoadCredential read works without elevating.
Verified the activation script chowns the decrypted file primary:users
mode 0400, the user unit override carries the LoadCredential line, and
the resulting drv builds clean.
Without a secret-service implementation on the bus, libsecret clients
like flare fail at startup with 'The communication with libsecret
failed'. None of the desktop hosts had one wired up.
oo7-daemon is the matching pure-Rust implementation (same project as
the oo7 crate flare uses internally), without the GNOME plumbing that
gnome-keyring would drag in. Register the package's D-Bus service
file and systemd user unit, start the daemon at user login, and alias
the unit as dbus-org.freedesktop.secrets.service so D-Bus
auto-activation also resolves cleanly when the wantedBy start hasn't
fired yet.
Verified the toplevel build and that the resulting system carries the
oo7-daemon user unit, the dbus alias symlink, and the
default.target.wants entry.
- patches/flare/000{1..5}-*.patch: typing indicators, formatted
messages, edited messages, multi-select with delete-for-me, and
in-channel message search. Mirror the matching commits in
~/projects/forks/flare and apply cleanly on top of upstream 0.20.4
(which is what nixpkgs ships).
- home/profiles/gui.nix: include a flare-signal override that appends
the patches via overrideAttrs. None of them touch Cargo.lock so the
cargoDeps hash stays valid; signal-desktop stays alongside it.
agenix activation runs from initrd-nixos-activation-start, which fires
right after /sysroot/persistent is mounted but before impermanence's
stage-2 bind mounts. The TPM identity at /var/lib/agenix/tpm-identity
was therefore unreadable at activation time, and every secret silently
failed to decrypt: 'no readable identities found'. Visible downstream
fallout was pull-update-apply hitting HTTP 401 against the binary cache
because nix-cache-netrc was never written to /run/agenix.
Mark /var/lib/agenix as neededForBoot via a bare fileSystems entry,
mirroring the existing /home/${username} bind. Drop the now-redundant
environment.persistence directory entry to avoid two competing units.
`bun2nix.hook` (used by upstream omp's package.nix) sets
patchPhase = bunPatchPhase
at the end of its setup-hook unless `dontUseBunPatch` is already set.
`bunPatchPhase` only runs `patchShebangs` plus a HOME mktemp; it never
iterates over `$patches`. The standard nixpkgs `patches` attribute
therefore went into the derivation env but was silently ignored at
build time, leaving the deployed omp binary unpatched.
Switch to applying the two patches via `prePatch` (which `bunPatchPhase`
does call). Verified with strings(1) over the rebuilt binary that both
patch hunks land:
/wrong_api_format|...|invalid tool parameters/ (patch 0001)
stubsReasoningContent ... thinkingFormat == "openrouter" (patch 0002)
DeepSeek V4 Pro (and similar reasoning models reached via OpenRouter) reject
multi-turn requests in thinking mode with:
400 The `reasoning_content` in the thinking mode must be passed back
to the API.
omp's existing kimi placeholder injection (`requiresReasoningContentForToolCalls`)
covered this requirement only for `thinkingFormat == "openai"`. OpenRouter
sets `thinkingFormat == "openrouter"`, so the gate never fired even though
the underlying providers behind OpenRouter (DeepSeek, Kimi, etc.) all enforce
the same invariant.
This patch:
1. Extends `requiresReasoningContentForToolCalls` detection: any
reasoning-capable model fronted by OpenRouter now sets the flag.
2. Extends the placeholder gate in `convertMessages` to accept
`thinkingFormat == "openrouter"` alongside `"openai"`.
Cross-provider continuations are the dominant trigger: a conversation
warmed up by Anthropic Claude (whose reasoning is redacted/encrypted on
the wire) followed by a switch to DeepSeek V4 Pro via OpenRouter. omp
cannot synthesize plaintext `reasoning_content` from Anthropic's
encrypted blocks, so the placeholder satisfies DeepSeek's validator
without fabricating a reasoning trace. Real captured reasoning, when
present, short-circuits the placeholder via `hasReasoningField` and
survives intact.
Side benefit: also closes a latent gap where Kimi-via-OpenRouter
(`thinkingFormat == "openrouter"`) had the compat flag set but the
placeholder gate silently rejected it.
Applies cleanly on top of patch 0001.