Rename HotkeyDaemon -> Daemon

update readme
This commit is contained in:
LeonardoTrapani
2025-08-08 00:07:38 +02:00
parent 6faa74f29c
commit 903274a249
4 changed files with 100 additions and 103 deletions
+87 -91
View File
@@ -1,139 +1,135 @@
# Hyprvoice
> **Voicepowered typing for Wayland/Hyprland desktops** — press a global shortcut, speak, and watch words appear in whatever window you're focused on.
> **Voicepowered typing for Wayland/Hyprland — press to toggle, speak, instant paste.**
> Streams audio while you talk and **pastes the final text the moment you toggle off** → aims to be the **fastest feel** on Wayland.
**🚧 Early Development Status:** This project is currently in early development. Not ready for production use yet.
**Status:** Early development (expect rough edges)
---
## Why does this hyprvoice exist?
## TL;DR
Typing is slow, repetitive strain injuries are real. This project aims to give Wayland users a fast, privacyrespecting alternative that works entirely on their own hardware _or_ any cloud ASR they trust.
- **Toggle workflow** (Hyprlandfriendly): press to start, press to stop.
- **Cloud streaming ASR** (MVP) → **single final paste** into the focused window.
- **Daemon with clear states & events**; desktop notifications.
- **Clipboardbased injection** (save/restore) with **`wtype`**
- **Unixy pipeline** (small pieces, bounded channels).
---
## Current Implementation Status
## Requirements
| Component | Status | Description |
| ------------------------- | ------- | ------------------------------------------------------------ |
| **Hotkey daemon** | ✅ Done | Background service with Unix socket IPC for command handling |
| **Desktop notifications** | ✅ Done | Recording state changes via `notify-send` |
| **Service management** | ✅ Done | Install/remove systemd user service with embedded unit file |
| **Live audio capture** | 🔄 TODO | PipeWire input with VAD/noise gate |
| **ASR backends** | 🔄 TODO | Local Whisper or cloud APIs (OpenAI, Deepgram, AssemblyAI) |
| **Text injection** | 🔄 TODO | `wtype`/`ydotool` keystrokes to focused window |
| **Configuration** | 🔄 TODO | TOML config file with runtime reload |
- Wayland + **Hyprland**
- **PipeWire** (audio capture)
- **systemd --user** (service)
- **wl-clipboard** (clipboard save/restore)
- **libnotify**/`notify-send` (optional notifications)
- `wtype` or `ydotool` (optional text injection fallback)
> Other distros may work, but Arch/Hyprland is the primary target for now.
---
## Quick Start (Arch / Hyprland)
## Install (Arch / Hyprland)
```bash
# 1. Install from AUR (source build)
yay -S hyprvoice
#   or prebuilt binary
yay -S hyprvoice-bin
# AUR
yay -S hyprvoice # or: yay -S hyprvoice-bin
# 2. Run the interactive setup helper
hyprvoice-install run --backend whispercpp
# 3. Enable the user service
# Enable user service
systemctl --user enable --now hyprvoice.service
# 4. Add a key binding in Hyprland conf
# Hyprland keybind (toggle)
bind = SUPER, R, exec, hyprvoice toggle
```
---
## Configuration file (`~/.config/hyprvoice/config.toml`)
## Usage
**Note:** Configuration file support is not yet implemented. This shows the planned format:
```toml
[asr]
backend = "whispercpp" # whispercpp | openai
model = "medium.en.bin" # used if backend = whispercpp
api_key = "" # used if backend = openai
[vad]
threshold = -45 # dBFS
hang_ms = 300
[inject]
method = "wtype" # wtype | ydotool | clipboard
[keybind]
# If you don't use Hyprland, set an XDG desktop accelerator here
shortcut = "CTRL+ALT+SPACE"
```
All options are documented in the sample config generated by `--init`.
- Press your **toggle** key to start; press again to stop.
- Audio streams to the cloud ASR while you speak.
- On stop (or VAD endpoint), Hyprvoice **pastes once** into the focused window.
- Injection flow: **save clipboard → copy final text → send Ctrl+V → restore clipboard**.
---
## Architecture
## Status
```
┌──────────────┐ Unix Socket ┌──────────────┐ PCM ┌──────────┐
│ CLI Client │◄────────────────│ Hot‑key │────────▶│ Audio │
│ (toggle/stop)│ │ Daemon │ │ Capture │
└──────────────┘ │ (hotkeydaemon│ │ (pipewire│
│ + bus) │ │ + VAD) │
└──────────────┘ └──────────┘
│ │
│ notify-send │ chunks
▼ ▼
┌──────────────┐ ┌──────────┐
│ Desktop │ │ ASR │
│ Notification │ │ Adapter │
└──────────────┘ │(whisper/ │
│ openai) │
└──────────┘
│ text
┌──────────┐
│ Inject │
│ (wtype/ │
│ ydotool) │
└──────────┘
```
| Component | State | Notes |
| -------------------------- | ----- | ------------------------------------------- |
| **Daemon (control plane)** | ✅ | State, IPC, worker orchestration |
| **Recording control** | ✅ | `hyprvoice toggle` |
| **Desktop notifications** | ✅ | `notify-send` (logs fallback) |
| **Audio capture** | 🔄 | PipeWire + VAD |
| **ASR backends** | 🔄 | Cloud **streaming** now; local Whisper next |
| **Text injection** | 🔄 | Clipboard paste → `wtype``ydotool` |
| **Service management** | 🔄 | `systemd --user` |
**Planned Components:**
- `cmd/hyprvoice`: CLI with Cobra commands (existing: `serve`, `toggle`, `stop`)
- `internal/bus`: Unix socket IPC for daemon communication (✅ implemented)
- `internal/hotkeydaemon`: Background service managing recording state (✅ implemented)
- `internal/notify`: Desktop notifications via `notify-send` (✅ implemented)
- `internal/audiocapture`: PipeWire input with VAD/noise gate (🔄 TODO)
- `internal/asr`: ASR backends (Whisper, OpenAI, etc.) (🔄 TODO)
- `internal/inject`: Text injection via wtype/ydotool (🔄 TODO)
- `internal/config`: TOML configuration with runtime reload (🔄 TODO)
Each box will be a Go package inside `internal/` so you can swap implementations without touching public APIs.
Legend: ✅ done · 🔄 in progress · ⏳ planned
---
## Building from Source
## How it works
- **Model:** pipeline + central state (daemon = control plane).
- **State machine:** `idle → recording → transcribing → injecting → idle`.
- **Rule:** switch to **`transcribing`**\*\* as soon as the first audio frame is sent\*\* to the ASR.
### ASCII diagram
```
+-------------------+ Unix socket IPC +-----------+
CLI cmd → | Control Daemon | <---------------------------- | CLI/Tool |
|-------------------| +-----------+
| State: idle/rec/ |
| transcribing/... | events events
| Event bus (chan) | -----> [Notifications] -----> notify-send/log
| |
| frames finals |
+--+-----------+----+
| |
Audio | | Final Text
Frames v v
+--------+ +--------+ text +-----------+
| Audio |-->| ASR | -------------->| Injection |
| Capture| | Stream | | Worker |
+--------+ +--------+ +-----------+
| ^
+--------------+
backpressure via bounded channels
State (daemon):
idle --toggle--> recording --first frame--> transcribing --final--> injecting --done--> idle
```
### Data flow
1. `toggle`**recording**
2. First frame sent → **transcribing**
3. Cloud ASR returns **final****injecting**
4. Paste once → **idle**
5. Notifications at each transition
---
## Build from source
```bash
git clone https://github.com/leonardotrapani/hyprvoice.git
cd hyprvoice
# Compile
CGO_ENABLED=1 go build -o hyprvoice ./cmd/hyprvoice
# Run tests
go test ./...
```
**Requires** Go 1.22+, a C compiler, and `pkg-config` with PipeWire headers.
---
Contributions welcome! Check the [issues](https://github.com/leonardotrapani/hyprvoice/issues) for good first tasks.
## Contributing
- All PRs and issues welcome.
---
## License
This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.
MIT — see [LICENSE.md](LICENSE.md)
+4 -4
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"github.com/leonardotrapani/hyprvoice/internal/bus"
"github.com/leonardotrapani/hyprvoice/internal/hotkeydaemon"
"github.com/leonardotrapani/hyprvoice/internal/daemon"
"github.com/leonardotrapani/hyprvoice/internal/notify"
"github.com/spf13/cobra"
)
@@ -31,9 +31,9 @@ func init() {
func serveCmd() *cobra.Command {
return &cobra.Command{
Use: "serve",
Short: "Run the hotkey daemon",
Short: "Run the daemon",
RunE: func(cmd *cobra.Command, args []string) error {
return (hotkeydaemon.New(notify.Desktop{})).Run()
return (daemon.New(notify.Desktop{})).Run()
},
}
}
@@ -86,7 +86,7 @@ func versionCmd() *cobra.Command {
func stopCmd() *cobra.Command {
return &cobra.Command{
Use: "stop",
Short: "Stop the hotkey daemon",
Short: "Stop the daemon",
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := bus.SendCommand('q')
if err != nil {
@@ -1,4 +1,4 @@
package hotkeydaemon
package daemon
import (
"bufio"
@@ -121,18 +121,19 @@ func (d *Daemon) handle(c net.Conn) {
case 't': // toggle
d.mu.Lock()
d.recording = !d.recording
recording := d.recording
isRecording := d.recording
d.mu.Unlock()
d.notifier.RecordingChanged(recording)
log.Printf("Recording toggled: %t", recording)
fmt.Fprintf(c, "STATUS recording=%t\n", recording)
d.notifier.RecordingChanged(isRecording)
log.Printf("Recording toggled: %t", isRecording)
fmt.Fprintf(c, "STATUS recording=%t\n", isRecording)
case 's': // status
d.mu.Lock()
recording := d.recording
isRecording := d.recording
d.mu.Unlock()
fmt.Fprintf(c, "STATUS recording=%t\n", recording)
fmt.Fprintf(c, "STATUS recording=%t\n", isRecording)
case 'v': // protocol version
fmt.Fprintf(c, "STATUS proto=%s\n", bus.ProtoVer)
case 'q': // quit daemon
@@ -1,4 +1,4 @@
package hotkeydaemon
package daemon
import (
"testing"