feat: app icon + Arch package (PKGBUILD + .desktop)

Add a desktop/taskbar identity and a system package for PeerSpeak.

- assets/icons/peerspeak.svg: master app icon — the in-app mic glyph
  over a P2P mesh of peer nodes, Catppuccin Mocha palette. Rendered to
  the hicolor raster sizes (16..512) committed alongside it.
- Window/taskbar icon: embed a 128x128 straight-RGBA blob and load it
  via iced from_rgba (keeps us off iced's heavy `image` feature). Set
  the Wayland/X11 app_id to "peerspeak" so compositors match the window
  to the .desktop launcher and show the icon natively.
- packaging/peerspeak.desktop: launcher (StartupWMClass=peerspeak).
- packaging/PKGBUILD: peerspeak-git VCS package — cargo --frozen build,
  --lib check, installs the binary, .desktop, and hicolor icons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 20:01:26 -04:00
co-authored by Claude Opus 4.8
parent a757353c69
commit 02065d23bc
13 changed files with 137 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<!-- PeerSpeak app icon: in-app mic glyph + P2P mesh nodes, Catppuccin Mocha. -->
<defs>
<linearGradient id="tile" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#1e1e2e"/>
<stop offset="1" stop-color="#181825"/>
</linearGradient>
</defs>
<!-- Rounded-square tile -->
<rect x="20" y="20" width="216" height="216" rx="48" fill="url(#tile)"
stroke="#313244" stroke-width="3"/>
<!-- P2P mesh: edges (under nodes + mic) -->
<g stroke="#45475a" stroke-width="6" stroke-linecap="round" fill="none">
<line x1="74" y1="74" x2="128" y2="128"/>
<line x1="182" y1="74" x2="128" y2="128"/>
<line x1="74" y1="182" x2="128" y2="128"/>
<line x1="182" y1="182" x2="128" y2="128"/>
<line x1="74" y1="74" x2="182" y2="74"/>
<line x1="74" y1="182" x2="182" y2="182"/>
</g>
<!-- P2P mesh: peer nodes -->
<g fill="#b4befe">
<circle cx="74" cy="74" r="11"/>
<circle cx="182" cy="74" r="11"/>
<circle cx="74" cy="182" r="11"/>
<circle cx="182" cy="182" r="11"/>
</g>
<!-- Microphone (hero) — same geometry as the in-app Mic icon, scaled 6.4x -->
<g fill="none" stroke="#89b4fa" stroke-width="13"
stroke-linecap="round" stroke-linejoin="round">
<rect x="108.8" y="68.8" width="38.4" height="70.4" rx="19.2"/>
<path d="M 169.6 123.2 A 41.6 41.6 0 0 0 86.4 123.2"/>
<line x1="128" y1="164.8" x2="128" y2="187.2"/>
<line x1="105.6" y1="187.2" x2="150.4" y2="187.2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+68
View File
@@ -0,0 +1,68 @@
# Maintainer: mollusk <jitty+lc1iz0dc@protonmail.com>
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<commits>.g<short-sha> — 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
}
+11
View File
@@ -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
+17
View File
@@ -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<iced::window::Icon> {
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<AppMessage> {
let core_sub = Subscription::run(core_subscription).map(AppMessage::UiEventReceived);
let event_sub = iced::event::listen().map(AppMessage::EventOccurred);