use crate::config::{NetworkMode, RecordingMode}; use crate::friends::Friend; use crate::network::PeerState; use crate::presence::{FriendPresence, PresenceMode}; use iroh::{EndpointAddr, EndpointId}; #[derive(Debug, Clone)] pub enum CoreCommand { /// Join a room. `ticket` is "create" (or empty) to mint a fresh room, else a /// share ticket to join. `room_name` is the creator's chosen cosmetic label /// for a NEW room; it's ignored when joining (the label rides in the ticket). Join { name: String, ticket: String, room_name: String, input_device: Option, output_device: Option, echo_cancellation: bool, avatar: crate::avatar::Avatar }, Leave, ToggleMute, /// Change our avatar (W4) and re-announce it to the room over presence. SetAvatar(crate::avatar::Avatar), ToggleDeafen, SetPttMode(bool), SetPttActive(bool), SetPeerVolume(EndpointId, f32), /// Listener-side per-peer EQ. Local only; never leaves this app instance. SetPeerEq(EndpointId, crate::audio::eq::EqSettings), /// Listener-side per-peer pan. Local only; never leaves this app instance. SetPeerPan(EndpointId, f32), /// Locally mute/unmute a peer: when muted, their audio is decoded (so levels /// still show) but not mixed into our output. SetPeerMuted(EndpointId, bool), SetNoiseGateThreshold(f32), /// App-internal capture gain (mic), applied before the gate/encode. 1.0 = unity. SetInputVolume(f32), /// App-internal playback gain on the mixed output. 1.0 = unity. SetOutputVolume(f32), /// Start/stop a standalone capture-only stream that reports the raw mic /// level via [`UiEvent::MicLevel`], for gate calibration outside a call. /// Ignored while a room session is active (the in-call meter covers that). SetMicMonitor { enabled: bool, input_device: Option }, /// Set the relay/discovery posture. Takes effect on the next room join, /// since the endpoint is (re)built then. SetNetworkMode(NetworkMode), /// Start/stop recording the call to a local WAV (your mic + the incoming /// mix). No-op start if already recording / not in a call. SetRecording(bool), /// Set what a recording captures (mixed / per-peer stems / both). Takes /// effect on the next recording start. Sent at startup from config. SetRecordingMode(RecordingMode), /// Broadcast a room text-chat message. No-op when not in a call. SendChat(String), /// Set the pixelpass binary location (config override, empty = use `$PATH`). /// Sent at startup so screen-share can resolve the binary. SetPixelpassPath(Option), /// Start sharing our screen: spawn a pixelpass host and announce its ticket /// on our presence so the room can watch. No-op when not in a call. StartScreenShare, /// Stop sharing our screen: kill the pixelpass host and clear the presence /// ticket. No-op when not sharing. StopScreenShare, /// Watch a peer's screen share: spawn a pixelpass viewer for `ticket` and /// open it in a local player. ViewShare(String), /// Mint a fresh persistent identity (W7), discarding the old one. Takes effect /// on the next room join (the endpoint is rebuilt then). The core replies with /// an updated [`UiEvent::IdentityStatus`]. RegenerateIdentity, /// Add a friend (W7). Core owns the friends store: it mutates + persists it and /// replies with [`UiEvent::FriendsUpdated`]. `addr` seeds `last_addr` if known /// (e.g. added from a room). Idempotent — re-adding an existing id is a no-op. AddFriend { id: EndpointId, name: String, addr: Option }, /// Remove a friend by id (W7). RemoveFriend(EndpointId), /// Locally rename a friend (W7). RenameFriend(EndpointId, String), /// Set our presence posture (W7). Gates the idle listener (answer friends-only / /// invisible) and the outbound ping scheduler (invisible = fully dark). Sent at /// startup from config and whenever the user changes it. SetPresenceMode(PresenceMode), } #[derive(Debug, Clone)] pub enum UiEvent { RoomJoined { ticket: String, self_id: String }, RoomLeft, PeerJoined { id: EndpointId, state: PeerState }, PeerLeft { id: EndpointId }, PeerConnectionFailed { id: EndpointId }, PeerUpdated { id: EndpointId, state: PeerState }, /// Audio link to a peer is being (re)established — show a connecting state. PeerConnecting { id: EndpointId }, /// Audio link to a peer is up and carrying audio. PeerConnected { id: EndpointId }, AudioLevels(Vec<(EndpointId, f32)>), /// Raw (pre-gate, pre-mute) normalized RMS of the local mic, `0.0..=1.0`, /// for the settings level meter. Throttled to ~10/sec. MicLevel(f32), /// Call recording started; carries the absolute WAV path being written. RecordingStarted { path: String }, /// Call recording stopped; carries the finished WAV path. RecordingStopped { path: String }, /// A room text-chat message arrived from a peer (never our own — local /// messages are echoed by the UI on send). `from` is the sender's node id /// string, used to key their avatar (W4). ChatMessage { from: String, name: String, text: String }, /// Our own screen share started; the UI flips the Share button to "Stop". ScreenShareStarted, /// Our own screen share stopped (or failed to start). ScreenShareStopped, /// Our node identity (W7): the current node id string, and whether it is /// PERSISTED to disk. Sent once at startup and again after a regenerate. /// `persisted = false` means the key file couldn't be read/written and we're /// running on an ephemeral fallback — a degraded state the UI must surface, /// since the id (and thus friend recognition) won't survive the next launch. /// `error` carries the reason when degraded, for the UI explainer. IdentityStatus { node_id: String, persisted: bool, error: Option }, /// The friends list (W7), now owned by core. Sent at startup (after load) and /// after every add/remove/rename so the GUI renders from this snapshot instead /// of owning the store. `read_only` is true when `friends.json` failed to load /// (malformed) — the GUI shows a degraded warning and disables edits so we never /// overwrite the damaged file (backlog A16). FriendsUpdated { friends: Vec, read_only: bool }, /// A friend's live presence from a successful ping reply (W7): online, or in a /// joinable gathering (with a one-click ticket). Emitted by the outbound ping /// scheduler; absence of a recent event = treat as offline. FriendPresence { id: EndpointId, presence: FriendPresence }, /// The Discoverable time-box elapsed (W7 P6): the core auto-reverted our presence /// posture to the carried `mode` (always `Normal`) and stopped publishing. The /// GUI must mirror + persist this so its presence picker stops showing /// Discoverable. Distinct from a user-driven change so the GUI knows to update /// without having issued the command itself. PresenceModeReverted { mode: PresenceMode }, Error(String), }