rewrite readme
This commit is contained in:
@@ -1,59 +1,129 @@
|
||||
# Hyprvoice
|
||||
# Hyprvoice - Voice-Powered Typing for Wayland
|
||||
|
||||
> Voice‑powered 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.
|
||||
Press a toggle key, speak, and get instant text input. A speech-to-text tool designed for Wayland/Hyprland with toggle-based workflow and seamless text injection.
|
||||
|
||||
**Status:** Early development (expect rough edges)
|
||||
## Features
|
||||
|
||||
---
|
||||
- **Toggle workflow**: Press once to start recording, press again to stop and inject text
|
||||
- **Wayland native**: Built specifically for modern Linux desktops (Hyprland, etc.)
|
||||
- **Real-time feedback**: Desktop notifications for recording states and transcription status
|
||||
- **Multiple transcription backends**: OpenAI Whisper (planned: whisper.cpp for local processing)
|
||||
- **Smart text injection**: Clipboard save/restore with direct typing fallback
|
||||
- **Daemon architecture**: Lightweight control plane with efficient pipeline management
|
||||
|
||||
## TL;DR
|
||||
**Status:** Early development - core functionality working, transcription integration in progress
|
||||
|
||||
- Toggle workflow (Hyprland‑friendly): press to start, press to stop.
|
||||
- Pipeline owns state; daemon is a thin control plane (IPC + lifecycle).
|
||||
- Notifications for key events (recording started/ended, aborted).
|
||||
- Audio capture via PipeWire (`pw-record`) with backpressure.
|
||||
- ASR + clipboard injection are planned; injection is currently stubbed.
|
||||
## Installation
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.24.5+ (for building from source)
|
||||
- Wayland + Hyprland
|
||||
- PipeWire tools: `pw-record` and `pw-cli`
|
||||
- systemd --user (service)
|
||||
- Optional: libnotify/`notify-send` (desktop notifications)
|
||||
- Planned/optional: `wl-clipboard` (clipboard save/restore), `wtype`/`ydotool` (text injection)
|
||||
|
||||
> Other distros may work, but Arch/Hyprland is the primary target for now.
|
||||
|
||||
---
|
||||
|
||||
## Install (Arch / Hyprland)
|
||||
### From AUR (Arch Linux)
|
||||
|
||||
```bash
|
||||
# AUR
|
||||
yay -S hyprvoice # or: yay -S hyprvoice-bin
|
||||
# Using your preferred AUR helper
|
||||
yay -S hyprvoice-bin
|
||||
# or
|
||||
paru -S hyprvoice-bin
|
||||
|
||||
# Enable user service
|
||||
systemctl --user enable --now hyprvoice.service
|
||||
```
|
||||
|
||||
# Hyprland keybind (toggle)
|
||||
### Download Binary
|
||||
|
||||
1. Download from [GitHub Releases](https://github.com/leonardotrapani/hyprvoice/releases)
|
||||
2. Install:
|
||||
|
||||
```bash
|
||||
wget https://github.com/leonardotrapani/hyprvoice/releases/latest/download/hyprvoice-linux-x86_64
|
||||
mkdir -p ~/.local/bin
|
||||
mv hyprvoice-linux-x86_64 ~/.local/bin/hyprvoice
|
||||
chmod +x ~/.local/bin/hyprvoice
|
||||
|
||||
# Add to PATH (add to ~/.bashrc or ~/.zshrc)
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/leonardotrapani/hyprvoice.git
|
||||
cd hyprvoice
|
||||
|
||||
# Build the binary
|
||||
CGO_ENABLED=1 go build -o hyprvoice ./cmd/hyprvoice
|
||||
|
||||
# Install locally
|
||||
sudo cp hyprvoice /usr/local/bin/
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Wayland desktop** (Hyprland, Niri, GNOME, KDE, etc.)
|
||||
- **PipeWire audio system** with tools
|
||||
- **System packages**:
|
||||
|
||||
```bash
|
||||
# Arch Linux
|
||||
sudo pacman -S pipewire pipewire-pulse pw-record wl-clipboard
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt install pipewire-pulse pipewire-bin wl-clipboard
|
||||
|
||||
# Fedora
|
||||
sudo dnf install pipewire-utils wl-clipboard
|
||||
```
|
||||
|
||||
**For text injection (recommended):**
|
||||
|
||||
```bash
|
||||
# Arch Linux
|
||||
sudo pacman -S wtype
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt install wtype
|
||||
|
||||
# Alternative: ydotool (if wtype unavailable)
|
||||
# Follow ydotool setup for user permissions
|
||||
```
|
||||
|
||||
**Optional:**
|
||||
|
||||
- `notify-send` (desktop notifications)
|
||||
- `systemd --user` (daemon service)
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Setup daemon service:**
|
||||
|
||||
```bash
|
||||
# Enable and start the user service (reccomended)
|
||||
systemctl --user enable --now hyprvoice.service
|
||||
|
||||
# Or run manually in background
|
||||
hyprvoice serve &
|
||||
```
|
||||
|
||||
2. **Configure Hyprland keybind:**
|
||||
|
||||
```bash
|
||||
# Add to ~/.config/hypr/hyprland.conf
|
||||
bind = SUPER, R, exec, hyprvoice toggle
|
||||
```
|
||||
|
||||
---
|
||||
3. **Test voice input:**
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
# Check daemon status
|
||||
hyprvoice status
|
||||
|
||||
### Basic Usage
|
||||
# Toggle recording (or use Super+R)
|
||||
hyprvoice toggle
|
||||
# Speak something...
|
||||
hyprvoice toggle # Stop and transcribe
|
||||
```
|
||||
|
||||
- Press your toggle key to start; press again to stop.
|
||||
- Audio is captured via PipeWire; the pipeline enters `transcribing` after the first frame.
|
||||
- On toggle‑off during `transcribing`, an `inject` action is sent. Injection is currently simulated (no clipboard paste yet).
|
||||
## Quick Reference
|
||||
|
||||
### CLI Commands
|
||||
### Common Commands
|
||||
|
||||
```bash
|
||||
# Start the daemon
|
||||
@@ -68,37 +138,147 @@ hyprvoice status
|
||||
# Get protocol version
|
||||
hyprvoice version
|
||||
|
||||
# Stop the daemon
|
||||
# Stop the daemon (if not using systemd service)
|
||||
hyprvoice stop
|
||||
```
|
||||
|
||||
---
|
||||
### Keybinding Pattern
|
||||
|
||||
## Status
|
||||
Most setups use this toggle pattern in window manager config:
|
||||
|
||||
| Component | State | Notes |
|
||||
| ---------------------- | ----- | -------------------------------------------------------- |
|
||||
| Daemon (control plane) | ✅ | IPC server, lifecycle; forwards status from the pipeline |
|
||||
| Recording control | ✅ | `hyprvoice toggle` |
|
||||
| Desktop notifications | ✅ | `notify-send` (logs fallback) |
|
||||
| Audio capture | ✅ | PipeWire (`pw-record`) frames + bounded channels |
|
||||
| Simple transcriber | ✅ | Collect audio and transcribe when complete |
|
||||
| OpenAI adapter | ✅ | HTTP API calls with clean audio buffering |
|
||||
| whisper.cpp adapter | ⏳ | Local inference ready for implementation |
|
||||
| Text injection | ⏳ | Not implemented; will use clipboard + `wtype`/`ydotool` |
|
||||
| Service management | 🔄 | `systemd --user` unit example provided |
|
||||
```bash
|
||||
bind = SUPER, R, exec, hyprvoice toggle
|
||||
```
|
||||
|
||||
Legend: ✅ done · 🔄 in progress · ⏳ planned
|
||||
## Keyboard Shortcuts Setup
|
||||
|
||||
---
|
||||
### Hyprland
|
||||
|
||||
## How it works
|
||||
Add to your `~/.config/hypr/hyprland.conf`:
|
||||
|
||||
- Model: The pipeline owns all runtime state; the daemon is a control plane (IPC + lifecycle) that starts/stops a pipeline instance and forwards status.
|
||||
- State machine (pipeline): `idle → recording → transcribing → injecting → idle`.
|
||||
- Rule: switch to `transcribing` as soon as the first audio frame arrives.
|
||||
```bash
|
||||
# Hyprvoice - Voice to Text (toggle recording)
|
||||
bind = SUPER, R, exec, hyprvoice toggle
|
||||
|
||||
### Diagrams
|
||||
# Optional: Status check
|
||||
bind = SUPER SHIFT, R, exec, hyprvoice status && notify-send "Hyprvoice" "$(hyprvoice status)"
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Toggle Workflow
|
||||
|
||||
1. **Press keybind** → Recording starts (notification appears)
|
||||
2. **Speak your text** → Audio captured in real-time
|
||||
3. **Press keybind again** → Recording stops, transcription begins
|
||||
4. **Text appears** → Injected at cursor position or clipboard
|
||||
|
||||
### CLI Usage
|
||||
|
||||
```bash
|
||||
# Start daemon manually (if not using systemd service)
|
||||
hyprvoice serve
|
||||
|
||||
# In another terminal: toggle recording
|
||||
hyprvoice toggle
|
||||
# ... speak ...
|
||||
hyprvoice toggle
|
||||
|
||||
# Check what's happening
|
||||
hyprvoice status
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration will be read from `~/.config/hyprvoice/config.toml` (planned). Currently, the daemon uses default settings.
|
||||
|
||||
### Transcription Providers
|
||||
|
||||
Hyprvoice will support multiple transcription backends:
|
||||
|
||||
#### OpenAI Whisper API (Planned)
|
||||
|
||||
Fast, accurate cloud-based transcription:
|
||||
|
||||
```toml
|
||||
[transcription]
|
||||
provider = "openai"
|
||||
api_key = "your_openai_api_key"
|
||||
model = "whisper-1"
|
||||
language = "auto" # or "en", "es", etc.
|
||||
```
|
||||
|
||||
#### whisper.cpp Local (Planned)
|
||||
|
||||
Private, offline transcription using local models:
|
||||
|
||||
```toml
|
||||
[transcription]
|
||||
provider = "whisper_cpp"
|
||||
model_path = "~/models/ggml-base.en.bin"
|
||||
threads = 4
|
||||
```
|
||||
|
||||
### Service Configuration
|
||||
|
||||
#### Systemd Service
|
||||
|
||||
The daemon runs as a user service:
|
||||
|
||||
```bash
|
||||
# Create service file
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cat > ~/.config/systemd/user/hyprvoice.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Hyprvoice voice-to-text daemon
|
||||
After=pipewire.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/hyprvoice serve
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
|
||||
# Enable and start
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now hyprvoice.service
|
||||
```
|
||||
|
||||
### File Locations
|
||||
|
||||
- **Socket**: `~/.cache/hyprvoice/control.sock` - IPC communication
|
||||
- **PID file**: `~/.cache/hyprvoice/hyprvoice.pid` - Process tracking
|
||||
- **Config**: `~/.config/hyprvoice/config.toml` - User settings (planned)
|
||||
|
||||
## Development Status
|
||||
|
||||
| Component | Status | Notes |
|
||||
| --------------------- | ------ | --------------------------------- |
|
||||
| Core daemon & IPC | ✅ | Unix socket control plane |
|
||||
| Recording workflow | ✅ | Toggle recording via PipeWire |
|
||||
| Audio capture | ✅ | Efficient PipeWire integration |
|
||||
| Desktop notifications | ✅ | Status feedback via notify-send |
|
||||
| OpenAI transcription | ✅ | HTTP API integration |
|
||||
| Text injection | ⏳ | Clipboard + typing implementation |
|
||||
| Configuration system | ⏳ | TOML-based user settings |
|
||||
| Comprehensive tests | ⏳ | Pipeline and integration testing |
|
||||
| whisper.cpp support | ⏳ | Local model inference |
|
||||
|
||||
**Legend**: ✅ Complete · ⏳ Planned
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Hyprvoice uses a **daemon + pipeline** architecture for efficient resource management:
|
||||
|
||||
- **Control Daemon**: Lightweight IPC server managing lifecycle
|
||||
- **Pipeline**: Stateful audio processing (recording → transcribing → injecting)
|
||||
- **State Machine**: `idle → recording → transcribing → injecting → idle`
|
||||
|
||||
### System Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -136,24 +316,15 @@ stateDiagram-v2
|
||||
injecting --> idle: abort
|
||||
```
|
||||
|
||||
### Transcription Strategy
|
||||
### How It Works
|
||||
|
||||
Hyprvoice uses a **simple collect-and-transcribe** approach for reliable transcription:
|
||||
1. **Toggle recording** → Pipeline starts, audio capture begins
|
||||
2. **Audio streaming** → PipeWire frames buffered for transcription
|
||||
3. **Toggle stop** → Recording ends, transcription starts
|
||||
4. **Text injection** → Result typed or copied to clipboard
|
||||
5. **Return to idle** → Pipeline cleaned up, ready for next session
|
||||
|
||||
- **Collect all audio** during recording session
|
||||
- **Single transcription** when recording stops
|
||||
- **Clean, predictable results** with full context
|
||||
- **Provider-agnostic adapter** pattern for different backends
|
||||
|
||||
**Architecture:**
|
||||
|
||||
```
|
||||
Audio Frames → Audio Buffer → Backend Adapter → Transcription
|
||||
↓
|
||||
[OpenAI API, whisper.cpp, etc.]
|
||||
```
|
||||
|
||||
### Data flow
|
||||
### Data Flow
|
||||
|
||||
1. `toggle` (daemon) → create pipeline → recording
|
||||
2. First frame arrives → transcribing (daemon may notify `Transcribing` later)
|
||||
@@ -162,182 +333,164 @@ Audio Frames → Audio Buffer → Backend Adapter → Transcription
|
||||
5. Complete → idle; pipeline stops; daemon clears reference
|
||||
6. Notifications at key transitions
|
||||
|
||||
---
|
||||
## Troubleshooting
|
||||
|
||||
## Build from source
|
||||
### Common Issues
|
||||
|
||||
#### Daemon Issues
|
||||
|
||||
**Daemon won't start:**
|
||||
|
||||
```bash
|
||||
# Check if already running
|
||||
hyprvoice status
|
||||
|
||||
# Check for stale files
|
||||
ls -la ~/.cache/hyprvoice/
|
||||
|
||||
# Clean up and restart
|
||||
rm -f ~/.cache/hyprvoice/hyprvoice.pid
|
||||
rm -f ~/.cache/hyprvoice/control.sock
|
||||
hyprvoice serve
|
||||
```
|
||||
|
||||
**Command not found:**
|
||||
|
||||
```bash
|
||||
# Check installation
|
||||
which hyprvoice
|
||||
|
||||
# Add to PATH if using ~/.local/bin
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
#### Audio Issues
|
||||
|
||||
**No audio recording:**
|
||||
|
||||
```bash
|
||||
# Check PipeWire is running
|
||||
systemctl --user status pipewire
|
||||
|
||||
# Test microphone
|
||||
pw-record --help
|
||||
pw-record test.wav
|
||||
|
||||
# Check microphone permissions and levels
|
||||
```
|
||||
|
||||
**Audio device issues:**
|
||||
|
||||
```bash
|
||||
# List available audio devices
|
||||
pw-cli list-objects | grep -A5 -B5 Audio
|
||||
|
||||
# Check microphone is not muted in system settings
|
||||
```
|
||||
|
||||
#### Notification Issues
|
||||
|
||||
**No desktop notifications:**
|
||||
|
||||
```bash
|
||||
# Test notify-send directly
|
||||
notify-send "Test" "This is a test notification"
|
||||
|
||||
# Install if missing
|
||||
sudo pacman -S libnotify # Arch
|
||||
sudo apt install libnotify-bin # Ubuntu/Debian
|
||||
```
|
||||
|
||||
#### Text Injection Issues
|
||||
|
||||
**Text not appearing (when implemented):**
|
||||
|
||||
- Ensure cursor is in a text field when toggling off recording
|
||||
- Check that `wtype` or clipboard tools are installed
|
||||
- Verify window manager supports the text injection method used
|
||||
|
||||
### Debug Mode
|
||||
|
||||
```bash
|
||||
# Run daemon with verbose output
|
||||
hyprvoice serve
|
||||
|
||||
# Check logs from systemd service (or just see results from hyprvoice serve)
|
||||
journalctl --user -u hyprvoice.service -f
|
||||
|
||||
# Test individual commands
|
||||
hyprvoice toggle
|
||||
hyprvoice status
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Building from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/leonardotrapani/hyprvoice.git
|
||||
cd hyprvoice
|
||||
|
||||
# Build the binary
|
||||
# Install Go dependencies
|
||||
go mod download
|
||||
|
||||
# Build
|
||||
CGO_ENABLED=1 go build -o hyprvoice ./cmd/hyprvoice
|
||||
|
||||
# Run tests (when available)
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# Install locally
|
||||
sudo cp hyprvoice /usr/local/bin/
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Cobra CLI - Command-line interface framework
|
||||
- Go 1.24.5+ - Programming language runtime
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### File Locations
|
||||
|
||||
- Socket: `~/.cache/hyprvoice/control.sock` - IPC communication
|
||||
- PID file: `~/.cache/hyprvoice/hyprvoice.pid` - Process tracking
|
||||
|
||||
### Systemd Service
|
||||
|
||||
In the future, this will be implemented with the command `hyprvoice install`
|
||||
The daemon runs as a user service. To create a systemd service file:
|
||||
|
||||
```bash
|
||||
# Create service file at ~/.config/systemd/user/hyprvoice.service
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cat > ~/.config/systemd/user/hyprvoice.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Hyprvoice daemon
|
||||
After=pipewire.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/hyprvoice serve
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
|
||||
# Enable and start
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now hyprvoice.service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
hyprvoice/
|
||||
├── cmd/hyprvoice/ # Main CLI application
|
||||
├── cmd/hyprvoice/ # CLI application entry point
|
||||
├── internal/
|
||||
│ ├── bus/ # IPC (Unix socket) + PID management
|
||||
│ ├── daemon/ # Control plane (IPC server, lifecycle; no state)
|
||||
│ ├── notify/ # Desktop notifications
|
||||
│ ├── pipeline/ # Pipeline + state machine (record/transcribe/inject)
|
||||
│ ├── recording/ # Audio capture via PipeWire
|
||||
│ └── transcriber/ # Simple transcriber + adapters (OpenAI, whisper.cpp)
|
||||
│ ├── daemon/ # Control daemon (lifecycle management)
|
||||
│ ├── notify/ # Desktop notification integration
|
||||
│ ├── pipeline/ # Audio processing pipeline + state machine
|
||||
│ ├── recording/ # PipeWire audio capture
|
||||
│ └── transcriber/ # Transcription adapters (OpenAI, whisper.cpp)
|
||||
├── go.mod # Go module definition
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### State Machine
|
||||
|
||||
The pipeline operates with these states:
|
||||
|
||||
- idle → recording → transcribing → injecting → idle
|
||||
|
||||
### IPC Protocol
|
||||
|
||||
Single-character commands over Unix socket:
|
||||
|
||||
- `t` - Toggle recording
|
||||
- `s` - Get status
|
||||
- `v` - Get protocol version
|
||||
- `q` - Quit daemon
|
||||
|
||||
### Running in Development
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Terminal 1: Start daemon with logs
|
||||
# Terminal 1: Run daemon with logs
|
||||
go run ./cmd/hyprvoice serve
|
||||
|
||||
# Terminal 2: Test commands
|
||||
go run ./cmd/hyprvoice toggle
|
||||
go run ./cmd/hyprvoice status
|
||||
go run ./cmd/hyprvoice stop
|
||||
```
|
||||
|
||||
---
|
||||
### IPC Protocol
|
||||
|
||||
## Direction / Roadmap
|
||||
Simple single-character commands over Unix socket:
|
||||
|
||||
- **ASR integration**: OpenAI adapter complete; whisper.cpp adapter ready for implementation.
|
||||
- **Proper injection**: clipboard save/restore + Ctrl+V, with `wtype`/`ydotool` fallbacks.
|
||||
- **Configuration options**: devices, sample rate, transcription providers.
|
||||
- **Enhanced features**: VAD for auto-stop, improved chunking strategies if needed.
|
||||
- **Tests**: comprehensive testing for pipeline state transitions and transcription.
|
||||
- Direction is flexible; we can adjust based on UX feedback and performance needs.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Daemon won't start**
|
||||
|
||||
```bash
|
||||
# Check if already running
|
||||
hyprvoice status
|
||||
|
||||
# Check PID file
|
||||
ls -la ~/.cache/hyprvoice/
|
||||
|
||||
# Remove stale files
|
||||
rm ~/.cache/hyprvoice/hyprvoice.pid
|
||||
rm ~/.cache/hyprvoice/control.sock
|
||||
```
|
||||
|
||||
**No notifications**
|
||||
|
||||
```bash
|
||||
# Test notify-send
|
||||
notify-send "Test notification"
|
||||
|
||||
# Check if libnotify is installed
|
||||
which notify-send
|
||||
```
|
||||
|
||||
**Permission errors**
|
||||
|
||||
```bash
|
||||
# Check socket permissions
|
||||
ls -la ~/.cache/hyprvoice/control.sock
|
||||
|
||||
# Recreate cache directory
|
||||
rm -rf ~/.cache/hyprvoice
|
||||
mkdir -p ~/.cache/hyprvoice
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
```bash
|
||||
# Run with verbose logging
|
||||
hyprvoice serve 2>&1 | tee hyprvoice.log
|
||||
```
|
||||
|
||||
---
|
||||
- `t` - Toggle recording on/off
|
||||
- `s` - Get current status
|
||||
- `v` - Get protocol version
|
||||
- `q` - Quit daemon gracefully
|
||||
|
||||
## Contributing
|
||||
|
||||
- All PRs and issues welcome.
|
||||
- Follow existing code conventions
|
||||
- Add tests for new functionality
|
||||
- Update documentation for user-facing changes
|
||||
Contributions welcome! Please:
|
||||
|
||||
---
|
||||
- Follow existing code conventions and patterns
|
||||
- Add tests for new functionality when available
|
||||
- Update documentation for user-facing changes
|
||||
- Test on Hyprland/Wayland before submitting PRs
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE.md](LICENSE.md)
|
||||
MIT License - see [LICENSE.md](LICENSE.md) for details.
|
||||
|
||||
Reference in New Issue
Block a user