Skip to content

feat(sound): loop a "waiting to connect" cue during SSH agent approval - #148

Merged
Orinks merged 1 commit into
Orinks:devfrom
Nick6489:feature/connect-waiting-sound
Sep 2, 2026
Merged

feat(sound): loop a "waiting to connect" cue during SSH agent approval#148
Orinks merged 1 commit into
Orinks:devfrom
Nick6489:feature/connect-waiting-sound

Conversation

@Nick6489

@Nick6489 Nick6489 commented Sep 2, 2026

Copy link
Copy Markdown

What

Adds a looping "Waiting to connect" sound cue that plays while an SFTP
connection is stalled waiting for the SSH agent to approve the key.

An external SSH agent (Bitwarden's agent, a smartcard) shows that approval in a
dialog that, for a screen-reader user, tends to open behind the Portkey Drop
window with nothing to announce it. The connection then sits there with no
feedback. This fills that gap.

How it works

  • AgentAuthNotice (portkeydrop-core::protocols) -- a new optional
    Arc<dyn Fn()> hook on SftpClient / create_client. SFTP auth calls it once
    on the connect worker, immediately before the first authenticate_publickey_with
    agent signature request -- the exact point an external agent can prompt.
  • The UI's connect worker wraps it in a events::notify_awaiting_agent call,
    which posts AppEvent::ConnectAwaitingAgent.
  • MainFrame::handle_event logs and announces "Waiting for SSH key approval"
    and calls AppState::start_waiting_sound(), which loops the connect_waiting
    cue.
  • Connected and ConnectFailed call stop_waiting_sound(); so does
    disconnect_quietly() (top of every connect, and disconnect) and on_close().

Looping playback (new in the sound layer)

play_looping_sound_file decodes the file once into a SamplesBuffer, repeats
it, and returns a LoopHandle. Dropping the handle -- or calling .stop() --
sets a flag the playback source checks per sample, so the loop ends within one
audio buffer. SoundPlayer::play_event_looping is the pack-aware wrapper.

Looping cues are not counted by wait_for_playback, so the exit-sound wait
never blocks on one.

Sound event

connect_waiting is registered in sound_events.rs under Connections, so it
shows in Settings' per-event mute list and is validated like any other cue. No
audio is bundled
for it (the built-in pack ships no audio at all in this repo);
a pack opts in with a connections/connect_waiting.ogg entry. Until then the
event still logs and announces -- just silently.

Tests

  • player.rs: StopFlag ends the source when the flag is set and reports its
    real channel layout; LoopHandle stops on stop() and on drop; a missing or
    undecodable file yields no handle; volume 0 yields an inert handle without
    touching the device.
  • events.rs: notify_awaiting_agent posts ConnectAwaitingAgent without
    waiting for a reply.
  • state.rs: start_waiting_sound / stop_waiting_sound are a safe no-op in
    the silent (headless / test) state.
  • sound_events.rs: connect_waiting is a known event.

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings,
and cargo test --workspace all pass. The Linux (Ubuntu) clippy path for the
cfg(unix) agent code could not be run locally (no WSL distro); the change to
that function is a new parameter used only in shared code.

Independence from #147

Branched from dev, not from #147. Both touch soundpacks/player.rs and
CHANGELOG.md, so whichever merges second needs a small conflict resolve in the
use block / changelog list -- they are otherwise unrelated and can merge in
either order.

🤖 Generated with Claude Code

When an SFTP connection reaches the SSH agent for a signature, an
external agent such as Bitwarden puts up an approval dialog that, for a
screen-reader user, often opens behind the main window with nothing to
announce it. The connection then just sits there.

SFTP auth now calls an optional `AgentAuthNotice` on the connect worker
just before the first agent signature request. The UI turns that into a
`ConnectAwaitingAgent` event, logs and announces "Waiting for SSH key
approval", and starts the new `connect_waiting` cue on a loop.
`Connected` and `ConnectFailed` stop it, as does starting or tearing
down any connection.

`connect_waiting` is a first-class sound event: it shows in Settings'
per-event mute list and is validated like the others. No audio ships
for it (the built-in pack ships none), so a pack opts in by adding a
`connections/connect_waiting.ogg` entry.

Looping playback is new: `play_looping_sound_file` decodes once into a
`SamplesBuffer`, repeats it, and hands back a `LoopHandle` whose drop
(or `stop()`) ends the sound within one audio buffer. It is not counted
by `wait_for_playback`, so the exit wait never blocks on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARKC56K8mFfAPPc98JPfCP
Orinks pushed a commit that referenced this pull request Sep 2, 2026
The default sound pack ships no audio in this repo. `default_pack_json()`
is `{"sounds": {}}`, the Windows build stages only the exe, prism.dll,
and version.txt, and `ensure_default_pack` writes just the manifest.
Existing users have cues only because the `~/.portkeydrop` -> platform
config migration carried the Python-era pack across; a fresh nightly or
installer install gets a silent default pack.

This adds the one file the `connect_waiting` event (PR #148) needs, at a
suggested assets path. It is wired to nothing yet: bundling the default
pack and pointing `default_pack_json()` at it is a separate decision,
noted in the PR.

connect_waiting.ogg is my own work, contributed under the project's
licence. 48 kHz stereo Ogg Vorbis, 3s, designed to loop.

Changelog: none

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARKC56K8mFfAPPc98JPfCP
@Orinks
Orinks merged commit 5ae05d3 into Orinks:dev Sep 2, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants