ParrotInk follows a hybrid asynchronous/threaded model designed to handle real-time I/O without blocking the user interface or audio processing.
- The Brain (Asyncio): The core logic runs on Python's
asyncioevent loop (Main Thread). This handles high-level state management and WebSocket communication. - The Senses (Threading): Blocking I/O operations (Microphone, Global Hotkeys, Mouse Hooks) run in dedicated background threads to prevent freezing the application.
- The Face (Threading): UI elements (System Tray, Floating Indicator) run in their own isolated threads to ensure they remain responsive and don't conflict with the main loop.
graph TD
User[User Hardware] -->|Audio| SD[AudioStreamer (Thread)]
User -->|Keys| Hooks[keyboard (Thread)]
User -->|Mouse| MHooks[pynput (Thread)]
SD -->|Queue| Pipe[AudioPipeline (Async)]
Hooks -->|ThreadSafe Call| Coord[AppCoordinator (Asyncio)]
Coord -->|WebSocket| Cloud[OpenAI / AssemblyAI]
Cloud -->|Events| Coord
Coord -->|State| Tray[Tray Icon (Thread)]
Coord -->|State| Indicator[IndicatorUI (Thread)]
Coord -->|Sound| Spkr[Speaker (Thread)]
Coord -->|Injection| App[Target Window]
main.py: The entry point and orchestrator.AppCoordinator: The central class. Manages state, handles hotkeys, and syncs data between components.
config.toml: User configuration.
audio/streamer.py: Wrapssounddevice. Spawns a C-level thread for audio capture.pipeline.py: Orchestrates the flow from streamer through adapter to provider.adapter.py: Handles resampling (viasoxr) and format conversion.
interaction.pyInputMonitor: Wraps thekeyboardlibrary. Provides zero-leakage suppression and multi-key debouncing.
mouse.pyMouseMonitor: Wrapspynput.mouse. Listens for clicks to trigger click-away cancellation.
injector.py&injection.pySmartInjector: Calculates text deltas and performs Win32SendInputcalls.
anchor.pyAnchor: UsesGetGUIThreadInfoto precisely track the target window/control context.
config.py: Pydantic configuration system with profile resolution.security.py: Secure API key management via Windows Credential Manager.
ui.py:TrayApp(pystray) system tray interface.indicator_ui.py:IndicatorWindowproxy controller.hud_renderer.py: High-performance Skia-based Acrylic HUD.hud_styles.py: Skia drawing logic for the "Glass" visual style.
base.py: Abstract base class for providers.factory.py: Provider instantiation logic.openai_provider.py: OpenAI Realtime API (WebSocket).assemblyai_provider.py: AssemblyAI Streaming V3 (WebSocket).
-
Trigger (Hotkey Press)
keyboardhook thread detects combo.- Calls
coordinator.start_listening()viaasyncio.run_coroutine_threadsafe.
-
Initialization
- Anchor:
Anchor.capture_current()saves the ID of the window/control you are typing into. - Connection:
ConnectionManagerensures a "warm" or new connection. - Feedback:
_play_feedback_sound("start"). - State: Transition to
LISTENING.
- Anchor:
-
The Audio Pipeline
AudioStreamer(Thread) ->AudioPipeline(Async) ->AudioAdapter(Resample) ->Provider.send_audio().
-
The Text Loop (Response)
- WebSocket receives JSON ->
provider._handle_event. AppCoordinatorreceives text ->InjectionController.smart_inject().SmartInjectorperforms backspaces (if needed) and types new text.
- WebSocket receives JSON ->
-
Termination (Hotkey Release / Manual Stop / Click Away)
- Trigger calls
coordinator.stop_listening(). - Cleanup:
AudioPipelinestops.ConnectionManagerstarts idle timer (to keep connection warm) or rotates session.InputMonitorresets debouncing state._play_feedback_sound("stop").
- Trigger calls
sequenceDiagram
participant User
participant Main as AppCoordinator
participant Monitor as InputMonitor (Thread)
participant Pipe as AudioPipeline
participant Cloud as OpenAI/AssemblyAI
participant App as TargetWindow
User->>Monitor: Press Hotkey
Monitor->>Main: trigger start_listening()
Main->>Cloud: ensure_connected()
Cloud-->>Main: Connected
Main->>Pipe: start()
loop Audio Pipeline
Pipe->>Cloud: Processed & Resampled Audio
end
loop Transcription Events
Cloud->>Main: Partial Transcript
Main->>App: smart_inject()
end
User->>Monitor: Release Hotkey (Hold Mode)
Monitor->>Main: trigger stop_listening()
Main->>Pipe: stop()