build(appimage): harden thin bundle workflow
Keep host multimedia libraries out of the AppImage, document the exact Ubuntu build inputs, and resolve Rust 1.97 release-gate warnings.
This commit is contained in:
@@ -24,7 +24,10 @@ The AppImage runs on any reasonably current glibc-based distro that has:
|
|||||||
|
|
||||||
- **A Vulkan-capable GPU + driver** (peerspeak's iced/wgpu renderer). Mesa/RADV
|
- **A Vulkan-capable GPU + driver** (peerspeak's iced/wgpu renderer). Mesa/RADV
|
||||||
on AMD/Intel or the NVIDIA driver all work.
|
on AMD/Intel or the NVIDIA driver all work.
|
||||||
- **PipeWire** (with the PulseAudio shim, for `pactl`).
|
- **PipeWire** with the PulseAudio shim, `pactl`, and the host `libpulse.so.0`
|
||||||
|
client library. The Pulse client stack is deliberately not bundled because
|
||||||
|
PixelPass launches host GStreamer tools that must keep using the host's
|
||||||
|
matching multimedia libraries.
|
||||||
- For **screen-share only** — pixelpass shells out to these on the host `PATH`;
|
- For **screen-share only** — pixelpass shells out to these on the host `PATH`;
|
||||||
it prints the exact package names for your distro if any are missing:
|
it prints the exact package names for your distro if any are missing:
|
||||||
- **GStreamer + plugins** (`gst-launch-1.0`/`gst-inspect-1.0`, base,
|
- **GStreamer + plugins** (`gst-launch-1.0`/`gst-inspect-1.0`, base,
|
||||||
@@ -58,13 +61,23 @@ distrobox create --yes --image ubuntu:24.04 --name peerspeak-appimage
|
|||||||
distrobox enter peerspeak-appimage -- sudo apt-get update
|
distrobox enter peerspeak-appimage -- sudo apt-get update
|
||||||
distrobox enter peerspeak-appimage -- sudo apt-get install -y \
|
distrobox enter peerspeak-appimage -- sudo apt-get install -y \
|
||||||
build-essential cmake clang libclang-dev pkg-config \
|
build-essential cmake clang libclang-dev pkg-config \
|
||||||
libpipewire-0.3-dev libspa-0.2-dev libasound2-dev libxcb1-dev \
|
libpipewire-0.3-dev libspa-0.2-dev libpulse-dev libasound2-dev libxcb1-dev \
|
||||||
curl ca-certificates file patchelf git
|
curl ca-certificates file patchelf git
|
||||||
|
rustup toolchain install 1.97.1 --profile default
|
||||||
|
|
||||||
# Build (the host's ~/.rustup toolchain is glibc-2.17-baseline, so it runs in the
|
# PeerSpeak's ownership validator needs the modern SPA-JSON parser headers it
|
||||||
# box; isolated CARGO_TARGET_DIRs keep it off the host target/):
|
# is tested against. Distrobox exposes the host's headers under /run/host;
|
||||||
|
# pkg-config still links Ubuntu's older ABI-compatible libraries, preserving
|
||||||
|
# the glibc baseline. This checkout's verified host-header version is 1.6.8:
|
||||||
|
test -f /run/host/usr/include/spa-0.2/spa/utils/json-core.h
|
||||||
|
|
||||||
|
# Build with the repository's pinned Rust version (the host's ~/.rustup
|
||||||
|
# toolchain is glibc-2.17-baseline, so it runs in the box; isolated
|
||||||
|
# CARGO_TARGET_DIRs keep it off the host target/):
|
||||||
distrobox enter peerspeak-appimage -- env \
|
distrobox enter peerspeak-appimage -- env \
|
||||||
PATH="$HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin:$PATH" \
|
PATH="$HOME/.rustup/toolchains/1.97.1-x86_64-unknown-linux-gnu/bin:$PATH" \
|
||||||
|
SYSTEM_DEPS_LIBSPA_INCLUDE="/run/host/usr/include/spa-0.2" \
|
||||||
|
SYSTEM_DEPS_LIBPIPEWIRE_INCLUDE="/run/host/usr/include/pipewire-0.3:/run/host/usr/include/spa-0.2" \
|
||||||
./packaging/appimage/build-appimage.sh
|
./packaging/appimage/build-appimage.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
# X11) is dlopen'd at runtime and is on the AppImage excludelist because it must
|
# X11) is dlopen'd at runtime and is on the AppImage excludelist because it must
|
||||||
# match the host driver, and pixelpass's capture/encode tools (gst-launch-1.0,
|
# match the host driver, and pixelpass's capture/encode tools (gst-launch-1.0,
|
||||||
# pactl, mpv) are expected on the host PATH. So the AppImage carries just the two
|
# pactl, mpv) are expected on the host PATH. So the AppImage carries just the two
|
||||||
# binaries plus their handful of non-excludelisted libs. The custom AppRun
|
# binaries plus their handful of non-excludelisted libs. PulseAudio's client
|
||||||
# prepends usr/bin to PATH so peerspeak's own $PATH lookup finds the bundled
|
# stack is also excluded: pixelpass shells out to host GStreamer, and letting
|
||||||
# pixelpass, while the host's tools stay reachable.
|
# those subprocesses inherit Ubuntu's bundled libsndfile/libmpg123 stack can
|
||||||
|
# override incompatible host multimedia libraries. The custom AppRun prepends
|
||||||
|
# usr/bin to PATH so peerspeak's own $PATH lookup finds the bundled pixelpass,
|
||||||
|
# while the host's tools and matching audio stack stay reachable.
|
||||||
#
|
#
|
||||||
# All runtime assets (notification WAVs, avatar presets, window icon, fonts) are
|
# All runtime assets (notification WAVs, avatar presets, window icon, fonts) are
|
||||||
# include_bytes!-embedded in the peerspeak binary, so nothing else is bundled.
|
# include_bytes!-embedded in the peerspeak binary, so nothing else is bundled.
|
||||||
@@ -80,6 +83,7 @@ echo ">> running linuxdeploy (bundles libs, builds the AppImage)"
|
|||||||
--appdir "$appdir" \
|
--appdir "$appdir" \
|
||||||
-e "$appdir/usr/bin/peerspeak" \
|
-e "$appdir/usr/bin/peerspeak" \
|
||||||
-e "$appdir/usr/bin/pixelpass" \
|
-e "$appdir/usr/bin/pixelpass" \
|
||||||
|
--exclude-library 'lib*.so*' \
|
||||||
-d "$repo/packaging/peerspeak.desktop" \
|
-d "$repo/packaging/peerspeak.desktop" \
|
||||||
-i "$repo/assets/icons/peerspeak-256.png" \
|
-i "$repo/assets/icons/peerspeak-256.png" \
|
||||||
--icon-filename peerspeak \
|
--icon-filename peerspeak \
|
||||||
|
|||||||
+14
-14
@@ -5568,7 +5568,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
.size(11)
|
.size(11)
|
||||||
.color(color_subtext),
|
.color(color_subtext),
|
||||||
slider(0.0..=1.0, state.config.background_dim, AppMessage::SetBackgroundDim)
|
slider(0.0..=1.0, state.config.background_dim, AppMessage::SetBackgroundDim)
|
||||||
.step(0.05),
|
.step(0.05_f32),
|
||||||
text("Set a picture from your computer as the app background. Auto-resized; a dimming overlay keeps text readable. Applies live.")
|
text("Set a picture from your computer as the app background. Auto-resized; a dimming overlay keeps text readable. Applies live.")
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(color_subtext),
|
.color(color_subtext),
|
||||||
@@ -5996,7 +5996,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
).width(iced::Length::Fill),
|
).width(iced::Length::Fill),
|
||||||
text(format!("Input Volume (mic): {:.0}%", state.config.input_volume * 100.0)).size(11).color(color_subtext),
|
text(format!("Input Volume (mic): {:.0}%", state.config.input_volume * 100.0)).size(11).color(color_subtext),
|
||||||
slider(0.0..=2.0, state.config.input_volume, AppMessage::InputVolumeChanged)
|
slider(0.0..=2.0, state.config.input_volume, AppMessage::InputVolumeChanged)
|
||||||
.step(0.05)
|
.step(0.05_f32)
|
||||||
.on_release(AppMessage::PersistConfig),
|
.on_release(AppMessage::PersistConfig),
|
||||||
].spacing(8).width(iced::Length::Fill),
|
].spacing(8).width(iced::Length::Fill),
|
||||||
column![
|
column![
|
||||||
@@ -6008,7 +6008,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
).width(iced::Length::Fill),
|
).width(iced::Length::Fill),
|
||||||
text(format!("Output Volume: {:.0}%", state.config.output_volume * 100.0)).size(11).color(color_subtext),
|
text(format!("Output Volume: {:.0}%", state.config.output_volume * 100.0)).size(11).color(color_subtext),
|
||||||
slider(0.0..=2.0, state.config.output_volume, AppMessage::OutputVolumeChanged)
|
slider(0.0..=2.0, state.config.output_volume, AppMessage::OutputVolumeChanged)
|
||||||
.step(0.05)
|
.step(0.05_f32)
|
||||||
.on_release(AppMessage::PersistConfig),
|
.on_release(AppMessage::PersistConfig),
|
||||||
].spacing(8).width(iced::Length::Fill),
|
].spacing(8).width(iced::Length::Fill),
|
||||||
].spacing(20).align_y(iced::alignment::Vertical::Top).width(iced::Length::Fill),
|
].spacing(20).align_y(iced::alignment::Vertical::Top).width(iced::Length::Fill),
|
||||||
@@ -6618,7 +6618,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
avatar_view(&state.config.avatar, &state.name, &state.self_id, 34.0),
|
avatar_view(&state.config.avatar, &state.name, &state.self_id, 34.0),
|
||||||
text(format!("{} (You)", &state.name)).size(16).color(color_text),
|
text(format!("{} (You)", state.name)).size(16).color(color_text),
|
||||||
horizontal_space(),
|
horizontal_space(),
|
||||||
if state.is_muted {
|
if state.is_muted {
|
||||||
text("[Muted]").size(14).color(color_red)
|
text("[Muted]").size(14).color(color_red)
|
||||||
@@ -6933,7 +6933,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
slider(0.0..=2.0, current_vol, move |v| {
|
slider(0.0..=2.0, current_vol, move |v| {
|
||||||
AppMessage::PeerVolumeChanged(peer_id_clone, v)
|
AppMessage::PeerVolumeChanged(peer_id_clone, v)
|
||||||
})
|
})
|
||||||
.step(0.01)
|
.step(0.01_f32)
|
||||||
.on_release(AppMessage::PersistConfig)
|
.on_release(AppMessage::PersistConfig)
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
@@ -6946,7 +6946,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
slider(-1.0..=1.0, current_pan, move |v| {
|
slider(-1.0..=1.0, current_pan, move |v| {
|
||||||
AppMessage::PeerPanChanged(peer_id_clone, v)
|
AppMessage::PeerPanChanged(peer_id_clone, v)
|
||||||
})
|
})
|
||||||
.step(0.05)
|
.step(0.05_f32)
|
||||||
.on_release(AppMessage::PersistConfig),
|
.on_release(AppMessage::PersistConfig),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
@@ -6961,7 +6961,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
slider(0.0..=METER_MAX, current_gate, move |v| {
|
slider(0.0..=METER_MAX, current_gate, move |v| {
|
||||||
AppMessage::PeerGateChanged(peer_id_clone, v)
|
AppMessage::PeerGateChanged(peer_id_clone, v)
|
||||||
})
|
})
|
||||||
.step(0.001)
|
.step(0.001_f32)
|
||||||
.on_release(AppMessage::PersistConfig),
|
.on_release(AppMessage::PersistConfig),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
@@ -6980,7 +6980,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
slider(EQ_GAIN_DB_MIN..=EQ_GAIN_DB_MAX, value, move |v| {
|
slider(EQ_GAIN_DB_MIN..=EQ_GAIN_DB_MAX, value, move |v| {
|
||||||
AppMessage::PeerEqChanged(peer_id_clone, band, v)
|
AppMessage::PeerEqChanged(peer_id_clone, band, v)
|
||||||
})
|
})
|
||||||
.step(0.5)
|
.step(0.5_f32)
|
||||||
.on_release(AppMessage::PersistConfig),
|
.on_release(AppMessage::PersistConfig),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
@@ -7215,7 +7215,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
clip_progress(music_status.position, music_status.total),
|
clip_progress(music_status.position, music_status.total),
|
||||||
AppMessage::MusicSeek,
|
AppMessage::MusicSeek,
|
||||||
)
|
)
|
||||||
.step(0.001),
|
.step(0.001_f32),
|
||||||
text(format!("{elapsed} / {duration}"))
|
text(format!("{elapsed} / {duration}"))
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(color_subtext),
|
.color(color_subtext),
|
||||||
@@ -7225,7 +7225,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
state.config.music_volume,
|
state.config.music_volume,
|
||||||
AppMessage::MusicSetVolume
|
AppMessage::MusicSetVolume
|
||||||
)
|
)
|
||||||
.step(0.01),
|
.step(0.01_f32),
|
||||||
button(
|
button(
|
||||||
text("Browse")
|
text("Browse")
|
||||||
.size(12)
|
.size(12)
|
||||||
@@ -7319,7 +7319,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
effective_music_volume(state),
|
effective_music_volume(state),
|
||||||
AppMessage::MusicSetSourceVolume
|
AppMessage::MusicSetSourceVolume
|
||||||
)
|
)
|
||||||
.step(0.01),
|
.step(0.01_f32),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.into()
|
.into()
|
||||||
@@ -7815,7 +7815,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
},
|
},
|
||||||
move |fraction| AppMessage::SeekAudio(att.id, fraction),
|
move |fraction| AppMessage::SeekAudio(att.id, fraction),
|
||||||
)
|
)
|
||||||
.step(0.001)
|
.step(0.001_f32)
|
||||||
.width(iced::Length::Fixed(180.0)),
|
.width(iced::Length::Fixed(180.0)),
|
||||||
text(format!("{elapsed} / {duration}"))
|
text(format!("{elapsed} / {duration}"))
|
||||||
.size(11)
|
.size(11)
|
||||||
@@ -7827,7 +7827,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
slider(0.0..=2.0, effective_clip_volume(state, att.id), move |v| {
|
slider(0.0..=2.0, effective_clip_volume(state, att.id), move |v| {
|
||||||
AppMessage::SetClipVolumeFor(att.id, v)
|
AppMessage::SetClipVolumeFor(att.id, v)
|
||||||
},)
|
},)
|
||||||
.step(0.01)
|
.step(0.01_f32)
|
||||||
.width(iced::Length::Fixed(80.0)),
|
.width(iced::Length::Fixed(80.0)),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
@@ -7945,7 +7945,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
state.config.clip_volume,
|
state.config.clip_volume,
|
||||||
AppMessage::SetClipVolume
|
AppMessage::SetClipVolume
|
||||||
)
|
)
|
||||||
.step(0.01)
|
.step(0.01_f32)
|
||||||
.width(iced::Length::Fixed(110.0)),
|
.width(iced::Length::Fixed(110.0)),
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
|
|||||||
@@ -435,13 +435,10 @@ where
|
|||||||
|
|
||||||
let was_hovered = self.hovered_link.is_some();
|
let was_hovered = self.hovered_link.is_some();
|
||||||
self.hovered_link = local_position.and_then(|position| {
|
self.hovered_link = local_position.and_then(|position| {
|
||||||
state.paragraph.hit_span(position).and_then(|span| {
|
state
|
||||||
if spans.get(span)?.link.is_some() {
|
.paragraph
|
||||||
Some(span)
|
.hit_span(position)
|
||||||
} else {
|
.filter(|&span| spans.get(span).is_some_and(|span| span.link.is_some()))
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if was_hovered != self.hovered_link.is_some() {
|
if was_hovered != self.hovered_link.is_some() {
|
||||||
|
|||||||
Reference in New Issue
Block a user