diff --git a/assets/icons/peerspeak-128.png b/assets/icons/peerspeak-128.png
new file mode 100644
index 0000000..ffb3e59
Binary files /dev/null and b/assets/icons/peerspeak-128.png differ
diff --git a/assets/icons/peerspeak-128.rgba b/assets/icons/peerspeak-128.rgba
new file mode 100644
index 0000000..9222d40
Binary files /dev/null and b/assets/icons/peerspeak-128.rgba differ
diff --git a/assets/icons/peerspeak-16.png b/assets/icons/peerspeak-16.png
new file mode 100644
index 0000000..97609c2
Binary files /dev/null and b/assets/icons/peerspeak-16.png differ
diff --git a/assets/icons/peerspeak-24.png b/assets/icons/peerspeak-24.png
new file mode 100644
index 0000000..f39d021
Binary files /dev/null and b/assets/icons/peerspeak-24.png differ
diff --git a/assets/icons/peerspeak-256.png b/assets/icons/peerspeak-256.png
new file mode 100644
index 0000000..50ac6a6
Binary files /dev/null and b/assets/icons/peerspeak-256.png differ
diff --git a/assets/icons/peerspeak-32.png b/assets/icons/peerspeak-32.png
new file mode 100644
index 0000000..b2e0d10
Binary files /dev/null and b/assets/icons/peerspeak-32.png differ
diff --git a/assets/icons/peerspeak-48.png b/assets/icons/peerspeak-48.png
new file mode 100644
index 0000000..b163bf4
Binary files /dev/null and b/assets/icons/peerspeak-48.png differ
diff --git a/assets/icons/peerspeak-512.png b/assets/icons/peerspeak-512.png
new file mode 100644
index 0000000..c60cf96
Binary files /dev/null and b/assets/icons/peerspeak-512.png differ
diff --git a/assets/icons/peerspeak-64.png b/assets/icons/peerspeak-64.png
new file mode 100644
index 0000000..05b7a44
Binary files /dev/null and b/assets/icons/peerspeak-64.png differ
diff --git a/assets/icons/peerspeak.svg b/assets/icons/peerspeak.svg
new file mode 100644
index 0000000..4d2444a
--- /dev/null
+++ b/assets/icons/peerspeak.svg
@@ -0,0 +1,41 @@
+
+
diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD
new file mode 100644
index 0000000..932feea
--- /dev/null
+++ b/packaging/PKGBUILD
@@ -0,0 +1,68 @@
+# Maintainer: mollusk
+pkgname=peerspeak-git
+_pkgname=peerspeak
+pkgver=0.1.0
+pkgrel=1
+pkgdesc="Decentralized peer-to-peer voice chat (Rust/iroh/PipeWire/Opus/iced)"
+arch=('x86_64')
+url="https://gitbutter.xyz/mollusk/peerspeak"
+license=('custom')
+depends=('pipewire' 'opus')
+makedepends=('git' 'cargo' 'pkgconf')
+optdepends=('pixelpass: screen sharing inside a room'
+ 'mpv: screen-share viewer (vlc is used as a fallback)')
+provides=('peerspeak')
+conflicts=('peerspeak')
+options=('!lto' '!debug')
+source=("$_pkgname::git+ssh://git@gitbutter.xyz/mollusk/peerspeak.git")
+sha256sums=('SKIP')
+
+pkgver() {
+ cd "$srcdir/$_pkgname"
+ # 0.1.0.r.g — sorts monotonically across rebuilds.
+ printf '%s.r%s.g%s' \
+ "$(awk -F'\"' '/^version =/{print $2; exit}' Cargo.toml)" \
+ "$(git rev-list --count HEAD)" \
+ "$(git rev-parse --short HEAD)"
+}
+
+prepare() {
+ cd "$srcdir/$_pkgname"
+ # Vendor deps up front so build() can run --frozen (no surprise network).
+ export CARGO_HOME="$srcdir/cargo-home"
+ cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
+}
+
+build() {
+ cd "$srcdir/$_pkgname"
+ export CARGO_HOME="$srcdir/cargo-home"
+ export RUSTUP_TOOLCHAIN=stable
+ export CARGO_TARGET_DIR=target
+ cargo build --frozen --release --bin "$_pkgname"
+}
+
+check() {
+ cd "$srcdir/$_pkgname"
+ export CARGO_HOME="$srcdir/cargo-home"
+ export RUSTUP_TOOLCHAIN=stable
+ # Library unit tests only — the integration suites bind real iroh/QUIC
+ # endpoints and fail in a sandboxed/offline build environment.
+ cargo test --frozen --release --lib
+}
+
+package() {
+ cd "$srcdir/$_pkgname"
+
+ install -Dm755 "target/release/$_pkgname" "$pkgdir/usr/bin/$_pkgname"
+ install -Dm644 "packaging/$_pkgname.desktop" \
+ "$pkgdir/usr/share/applications/$_pkgname.desktop"
+
+ # Hicolor icon theme (scalable SVG + the rendered raster sizes).
+ install -Dm644 "assets/icons/$_pkgname.svg" \
+ "$pkgdir/usr/share/icons/hicolor/scalable/apps/$_pkgname.svg"
+ local s
+ for s in 16 24 32 48 64 128 256 512; do
+ install -Dm644 "assets/icons/$_pkgname-$s.png" \
+ "$pkgdir/usr/share/icons/hicolor/${s}x${s}/apps/$_pkgname.png"
+ done
+}
diff --git a/packaging/peerspeak.desktop b/packaging/peerspeak.desktop
new file mode 100644
index 0000000..2f3694b
--- /dev/null
+++ b/packaging/peerspeak.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Type=Application
+Name=PeerSpeak
+GenericName=P2P Voice Chat
+Comment=Decentralized peer-to-peer voice chat
+Exec=peerspeak
+Icon=peerspeak
+Terminal=false
+Categories=Network;Telephony;Chat;
+Keywords=voice;chat;voip;p2p;peer;
+StartupWMClass=peerspeak
diff --git a/src/app/mod.rs b/src/app/mod.rs
index d55c044..7765b7f 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -336,6 +336,15 @@ pub fn run_gui() -> iced::Result {
// dock doesn't squeeze the controls column). Layout is responsive.
size: init_size,
position: iced::window::Position::Centered,
+ // App/taskbar icon (mainly used on X11/XWayland; native Wayland takes
+ // the icon from the .desktop file matched by app_id instead).
+ icon: window_icon(),
+ // app_id must match the .desktop basename so Wayland compositors
+ // (e.g. KWin) attach our launcher icon to the window.
+ platform_specific: iced::window::settings::PlatformSpecific {
+ application_id: "peerspeak".to_string(),
+ ..Default::default()
+ },
// We save the final size ourselves on CloseRequested, then exit.
exit_on_close_request: false,
..Default::default()
@@ -343,6 +352,14 @@ pub fn run_gui() -> iced::Result {
.run()
}
+/// Build the window icon from an embedded 128×128 straight-RGBA blob rendered
+/// from `assets/icons/peerspeak.svg`. Using `from_rgba` (always available) keeps
+/// us off iced's heavy `image` feature — the blob is raw pixels, no decoder.
+fn window_icon() -> Option {
+ const RGBA: &[u8] = include_bytes!("../../assets/icons/peerspeak-128.rgba");
+ iced::window::icon::from_rgba(RGBA.to_vec(), 128, 128).ok()
+}
+
fn subscription(_state: &AppState) -> Subscription {
let core_sub = Subscription::run(core_subscription).map(AppMessage::UiEventReceived);
let event_sub = iced::event::listen().map(AppMessage::EventOccurred);