Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/appimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev \
pkg-config imagemagick file
libpipewire-0.3-dev libwayland-dev pkg-config imagemagick file

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/aur.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ jobs:
fail-fast: false
matrix:
# honkhonk: source build. honkhonk-bin: .deb re-extract.
# honkhonk-git: VCS build from main.
# honkhonk-git: VCS build from main. PR validation rewrites it to the
# candidate head commit below so dependency removals are tested against
# the code that will become main.
pkg: [honkhonk, honkhonk-bin, honkhonk-git]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
Expand All @@ -59,6 +61,17 @@ jobs:
sudo -u builder makepkg --printsrcinfo > /tmp/.SRCINFO.fresh
diff -u .SRCINFO /tmp/.SRCINFO.fresh

- name: Point VCS package at PR head
if: github.event_name == 'pull_request' && matrix.pkg == 'honkhonk-git'
working-directory: packaging/aur/${{ matrix.pkg }}
env:
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
run: |
sed -i \
"s|^source=.*|source=(\"git+https://github.com/${PR_REPO}.git#commit=${PR_HEAD}\")|" \
PKGBUILD

- name: Wait for release artifacts (tag push only)
if: startsWith(github.ref, 'refs/tags/') && matrix.pkg != 'honkhonk-git'
working-directory: packaging/aur/${{ matrix.pkg }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev pkg-config
libpipewire-0.3-dev libwayland-dev pkg-config

- name: Autobuild
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev \
pkg-config lintian imagemagick
libpipewire-0.3-dev libwayland-dev pkg-config lintian imagemagick

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev pkg-config
libpipewire-0.3-dev libwayland-dev pkg-config

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev pkg-config
libpipewire-0.3-dev libwayland-dev pkg-config

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
Expand All @@ -112,7 +112,7 @@ jobs:
run: |
sudo apt-get -o Acquire::Retries=3 update
sudo apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
libgtk-3-dev libglib2.0-dev libxdo-dev libpipewire-0.3-dev pkg-config
libpipewire-0.3-dev libwayland-dev pkg-config

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
Expand Down
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Wayland-native Linux soundboard. Pure Rust — Iced 0.14 GUI (Elm/MVU) + PipeWir
- Iced 0.14 GUI (Elm/MVU); no other frontend framework — UI is `Element<Message>` functions.
- PipeWire via pipewire-rs: one persistent virtual sink, no per-sound node churn.
- Global shortcuts via ashpd (xdg-desktop-portal GlobalShortcuts v2) — Phase 2.
- Tray via tray-icon (StatusNotifierItem; cross-DE). Audio decode via symphonia (pure Rust).
- Tray via ksni (StatusNotifierItem over zbus; cross-DE). Audio decode via symphonia (pure Rust).
- Renderer: wgpu default, tiny-skia software fallback (`HONKHONK_RENDERER=software`).
- **Wayland-native only — no X11. PipeWire only — no direct PulseAudio.**

Expand Down Expand Up @@ -63,8 +63,7 @@ specific APIs. One binary must work on KDE, GNOME, Hyprland, Sway:
`org.freedesktop.portal.FileChooser` · Notifications `org.freedesktop.portal.Notification` ·
Autostart `org.freedesktop.portal.Background`.

Tray uses StatusNotifierItem (SNI) via tray-icon — no XEmbed. The `libayatana-appindicator`
deprecation warning on stderr is upstream and harmless — ignore it.
Tray uses StatusNotifierItem (SNI) via ksni — no XEmbed, no GTK/appindicator tray stack.

## Packaging / XDG

Expand Down
29 changes: 15 additions & 14 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ The complete HTML/JSX mockup lives in the design handoff bundle (exported from c
| Renderer | **wgpu** (default) / **tiny-skia** (fallback) | GPU-accelerated by default, software renderer via env var for edge cases |
| PipeWire | **pipewire-rs 0.8** | Official Rust bindings from PipeWire project. Production-proven |
| Global Shortcuts | **ashpd 0.13** (`global_shortcuts` feature) | Full xdg-desktop-portal GlobalShortcuts API. Async/tokio |
| System Tray | **tray-icon 0.19** + **muda 0.15** | Actively maintained (Tauri team), standalone SNI, cross-DE |
| System Tray | **ksni 0.3** | Pure-Rust StatusNotifierItem over zbus, cross-DE |
| Audio Decode | **symphonia 0.5** | Pure Rust. MP3, WAV, OGG, FLAC, AAC. No C dependencies |
| Audio Playback | **pipewire-rs** streams | Direct PipeWire playback — no rodio/ALSA intermediary |

Expand Down Expand Up @@ -225,8 +225,8 @@ Wayland sessions require GPU drivers (compositor needs them), so wgpu works on a
│ Channels (no IPC serialization)
┌─────────────────────────────┐ ┌────────────────────┐
│ AudioEngine (PipeWire │ │ tray-icon
│ thread) │ │ (main thread,
│ AudioEngine (PipeWire │ │ ksni tray service
│ thread) │ │ (D-Bus thread, │
│ - virtual sink │ │ channel → sub) │
│ - mic passthrough │ └────────────────────┘
│ - playback streams │
Expand All @@ -245,11 +245,12 @@ No IPC serialization. Direct Rust channel communication:

1. **UI → Audio (Commands):** Iced `Command::perform` sends `AudioCommand` via `tokio::sync::mpsc` to PipeWire thread.
2. **Audio → UI (Events):** PipeWire thread sends `AudioEvent` via channel. Iced `Subscription` polls receiver each frame.
3. **Tray → App:** `tray-icon` sends events via channel → Iced `Subscription`.
3. **Tray → App:** `ksni` sends SNI events via channel → Iced `Subscription`.

### Threading Model

- **Main thread:** Iced event loop + tray-icon (both need main thread on Linux)
- **Main thread:** Iced event loop.
- **Tray thread:** Blocking ksni D-Bus StatusNotifierItem service.
- **PipeWire thread:** Dedicated thread running PipeWire's own event loop. Owns `AudioEngine`.
- **Communication:** Bounded `mpsc` channels. Non-blocking sends from main thread.

Expand Down Expand Up @@ -363,7 +364,7 @@ honkhonk/
│ │ └── playback.rs # Play sound to sink + monitor output
│ ├── tray/
│ │ ├── mod.rs
│ │ └── icon.rs # tray-icon setup, menu, quit handler
│ │ └── icon.rs # ksni SNI setup, menu, quit handler
│ ├── shortcuts/ # Phase 2
│ │ ├── mod.rs
│ │ ├── error.rs # PortalError enum
Expand Down Expand Up @@ -400,7 +401,10 @@ honkhonk/
│ ├── 002-pipewire-only-no-pulseaudio.md
│ ├── 003-fixed-slot-hotkey-model.md
│ ├── 004-persistent-sink-no-per-sound-nodes.md
│ └── 005-tray-icon-over-ksni.md
│ ├── 006-fundsp-over-rubberband-soundtouch.md
│ ├── 007-links-only-routing-over-pw-filter.md
│ ├── 008-subprocess-fallback-for-shortcut-config-ui.md
│ └── 009-canvas-sticker-tiles-rejected.md
├── .github/
│ └── workflows/
│ ├── ci.yml # Lint, test, build on PR
Expand All @@ -422,8 +426,7 @@ iced = { version = "0.14", features = ["tokio", "tiny-skia"] }
pipewire = "0.8" # pipewire-rs — official PipeWire Rust bindings
ashpd = { version = "0.13", features = ["global_shortcuts", "tokio"] } # Phase 2
symphonia = { version = "0.5", features = ["mp3", "ogg", "flac", "wav", "pcm", "aac"] }
tray-icon = "0.19" # System tray (StatusNotifierItem)
muda = "0.15" # Menu for tray-icon
ksni = { version = "0.3", features = ["blocking"] } # System tray SNI over zbus
thiserror = "2" # Typed error enums at module boundaries
anyhow = "1" # Error context chains in glue/app layer
serde = { version = "1", features = ["derive"] }
Expand Down Expand Up @@ -530,14 +533,12 @@ This ensures cross-DE compatibility. KDE, GNOME, and Hyprland each implement the

### System Tray

StatusNotifierItem (SNI) is the standard. Implementation via `tray-icon` crate:
1. Initialize on main thread before Iced event loop
2. Menu via `muda` crate: "Show/Hide", separator, "Quit"
StatusNotifierItem (SNI) is the standard. Implementation via `ksni`:
1. Start a blocking SNI D-Bus service from the tray module
2. Provide icon pixmap plus DBusMenu entries: "Show/Hide", separator, "Quit"
3. Events communicated to Iced via channel → Subscription
4. No legacy XEmbed tray support

**Known warning:** `tray-icon` depends on `libappindicator` which loads `libayatana-appindicator` at runtime. This produces a harmless deprecation warning on stderr: `libayatana-appindicator is deprecated. Please use libayatana-appindicator-glib in newly written code.` This is an upstream issue in the `libappindicator` crate — not actionable from our code. Do not suppress it.

### .desktop File

```ini
Expand Down
Loading
Loading