Compare commits

...
Author SHA1 Message Date
molluskandClaude Opus 4.8 abb53af559 docs(deb): document the Debian .deb build environment
cargo-deny / cargo-deny (push) Has been cancelled
windows-build / windows-build (push) Has been cancelled
The .deb recipe already lives in Cargo.toml's [package.metadata.deb], but the
build *environment* (bookworm distrobox, glibc floor, the mandatory separate
CARGO_TARGET_DIR) was only captured in handoff notes. Add a packaging/debian
README so the deb path is as self-documenting as the Arch + AppImage paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 00:03:48 -04:00
molluskandClaude Opus 4.8 c0c1969332 style(music): theme-colored skip glyphs in player bar + drawer
cargo-deny / cargo-deny (push) Has been cancelled
windows-build / windows-build (push) Has been cancelled
The skip-back/forward buttons rendered orange in every theme because the
emoji glyphs ⏮/⏭ (U+23EE/U+23ED) are drawn by the system color-emoji font,
which ignores the button's text color. Replace them with |◀ / ▶| built from
the text-presentation triangles ◀/▶ (U+25C0/U+25B6) — the same family the
play button already uses — so they honor .color() and follow the active
theme like the play button does. Applies to both the now-playing player bar
and the full music drawer panel. Pure visual change; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:33:32 -04:00
d155091eed style(music): center the transport controls on the player bar
The transport buttons were pushed to the far right because the
now-playing label had width(Fill). Regroup the bar into three sections
— left(Fill) identity+label, centered transport, right(Fill) position +
expand — so the controls sit in the middle. Pure widget regrouping; no
message, config, or behavior change.

Implemented by Codex (gpt-5.5), reviewed + gates re-run by Claude.

Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:07:59 -04:00
2 changed files with 136 additions and 31 deletions
+87
View File
@@ -0,0 +1,87 @@
# Debian / Ubuntu `.deb` build
This documents how the `peerspeak_*.deb` is produced, so the deb path is as
self-documenting as the Arch (`packaging/PKGBUILD`) and AppImage paths.
The deb **recipe itself** lives in-repo as the `[package.metadata.deb]` block in
the top-level `Cargo.toml` (cargo-deb's equivalent of a PKGBUILD). This file
documents only the **build environment**, which is otherwise undiscoverable from
a fresh clone.
## TL;DR
```sh
# one-time: create + provision the build box (see "Build environment" below)
distrobox enter peerspeak-bookworm -- bash -lc '
source ~/.cargo/env
cd ~/git/butter/peerspeak
export CARGO_TARGET_DIR=~/.cache/cargo-deb-targets/peerspeak # MANDATORY, see below
cargo deb
'
# output: $CARGO_TARGET_DIR/debian/peerspeak_<version>-1_amd64.deb
```
## Build environment
- **Base: a Debian 12 (bookworm) distrobox named `peerspeak-bookworm`.**
Created with `distrobox create --name peerspeak-bookworm --image debian:12`.
Bookworm ships **glibc 2.36**, which sets the widest practical compatibility
floor (see "glibc floor" below).
- **NEVER build the `.deb` on the Arch host.** Two independent reasons:
1. The Arch host's glibc is far newer, so the resulting `.deb` would demand a
glibc no normal Debian/Ubuntu user has, and ships an empty `Depends`.
2. distrobox shares `$HOME` (and therefore the repo's `target/`) with the host,
so a host build links Arch-compiled C objects into the "Debian" binary.
### One-time provisioning inside the box
```sh
distrobox enter peerspeak-bookworm
sudo apt update
sudo apt install -y build-essential pkg-config clang libclang-dev \
libpipewire-0.3-dev libopus-dev libasound2-dev libxcb1-dev
# clang/libclang -> pipewire-sys bindgen ; libxcb1-dev -> link
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
cargo install cargo-deb
```
### The mandatory separate `CARGO_TARGET_DIR`
Because distrobox shares `$HOME`, the repo's default `target/` is the **same
directory** the Arch host builds into. If you run `cargo deb` without overriding
the target dir, cargo will happily reuse Arch-built `.o`/rlib artifacts and link
them into the Debian binary, producing a `.deb` that crashes or demands the
host's glibc.
Always point the build at a box-local cache:
```sh
export CARGO_TARGET_DIR=~/.cache/cargo-deb-targets/peerspeak
```
(Run `cargo clean` first if you ever suspect a polluted target dir.)
## glibc floor
The `.deb` is built against the build box's glibc, which becomes the install
floor (`libc6 (>= 2.36)` lands in `Depends` via `$auto`):
| Build box | glibc | Runs on |
|----------------------|-------|--------------------------------------|
| `debian:12` (current)| 2.36 | Debian 12+, Ubuntu 24.04+ (glibc ≥ 2.36) |
| `ubuntu:26.04` (old) | 2.43 | Ubuntu 26.04+ only — too narrow, abandoned |
If a friend is on something even older than Debian 12, drop the floor further by
recreating the box from an older base image and rebuilding.
## Runtime `Depends` / `Recommends`
- `Depends = "$auto"` — cargo-deb runs `dpkg-shlibdeps`, which discovers the
linked shared libraries (PipeWire, Opus, ALSA, xcb, glibc, …) automatically.
- `Recommends = "pixelpass, mpv"``pixelpass` provides in-room screen sharing
and `mpv` is the screen-share viewer (these are companion programs invoked as
subprocesses, not linked libraries, so they are Recommends not Depends).
See `pixelpass`'s own `packaging/debian/README.md` for why **its** `Depends`
lists the whole GStreamer stack explicitly.
+49 -31
View File
@@ -5567,7 +5567,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.unwrap_or_else(|| "--:--".to_string());
column![
row![
button(text("").size(13))
button(text("|◀").size(13))
.on_press(AppMessage::MusicPrev)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
@@ -5575,7 +5575,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.on_press(AppMessage::MusicPlayPause)
.style(b_style(color_blue, color_lavender, color_crust, 6.0))
.padding(7),
button(text("").size(13))
button(text("▶|").size(13))
.on_press(AppMessage::MusicNext)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
@@ -5729,35 +5729,53 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.unwrap_or_else(|| "--:--".to_string());
let player_bar = container(
row![
text("").size(18).color(color_blue),
text(now_playing_label(listening_peer_name, current_track_name))
.size(13)
.color(color_text)
.width(iced::Length::Fill),
button(text("").size(13))
.on_press(AppMessage::MusicPrev)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
button(text(music_bar_play_label).size(13))
.on_press(AppMessage::MusicPlayPause)
.style(b_style(color_blue, color_lavender, color_crust, 6.0))
.padding(7),
button(text("").size(13))
.on_press(AppMessage::MusicNext)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
text(format!("{elapsed} / {duration}"))
.size(11)
.color(color_subtext),
button(text(if state.playlist_drawer_open { "" } else { "" }).size(13))
.on_press(AppMessage::TogglePlaylistDrawer)
.style(b_style(
if state.playlist_drawer_open { color_blue } else { color_surface },
color_blue,
if state.playlist_drawer_open { color_crust } else { color_text },
6.0,
))
.padding(7),
container(
row![
text("").size(18).color(color_blue),
text(now_playing_label(listening_peer_name, current_track_name))
.size(13)
.color(color_text),
]
.spacing(8)
.align_y(iced::alignment::Vertical::Center)
)
.width(iced::Length::Fill),
row![
button(text("|◀").size(13))
.on_press(AppMessage::MusicPrev)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
button(text(music_bar_play_label).size(13))
.on_press(AppMessage::MusicPlayPause)
.style(b_style(color_blue, color_lavender, color_crust, 6.0))
.padding(7),
button(text("▶|").size(13))
.on_press(AppMessage::MusicNext)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(7),
]
.spacing(8)
.align_y(iced::alignment::Vertical::Center),
container(
row![
text(format!("{elapsed} / {duration}"))
.size(11)
.color(color_subtext),
button(text(if state.playlist_drawer_open { "" } else { "" }).size(13))
.on_press(AppMessage::TogglePlaylistDrawer)
.style(b_style(
if state.playlist_drawer_open { color_blue } else { color_surface },
color_blue,
if state.playlist_drawer_open { color_crust } else { color_text },
6.0,
))
.padding(7),
]
.spacing(8)
.align_y(iced::alignment::Vertical::Center)
)
.width(iced::Length::Fill)
.align_x(iced::alignment::Horizontal::Right),
]
.spacing(8)
.align_y(iced::alignment::Vertical::Center)