Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
35822e8
Run the service well from an AppImage
claude Nov 10, 2026
4be51b0
Offer to add an AppImage to the application menu
claude Nov 10, 2026
f99203f
Build AppImages for x86_64 and arm64
claude Nov 10, 2026
dd39629
Clear an AppImage's login unit away from an installed Weir
claude Nov 10, 2026
d902682
Print the AppImage tools' checksums at the end of the job
claude Nov 10, 2026
9befc5a
Find the one-color tray icon inside an AppImage
claude Nov 10, 2026
b690baa
Keep Weir running when a restart cannot start the new AppImage
claude Nov 10, 2026
587a5a8
Pin the AppImage tools by checksum
claude Nov 10, 2026
2017a96
Print the AppImage tools' checksums when they do not match
claude Nov 10, 2026
a65c85f
Keep the tray icon on GNOME after Restart Weir
claude Nov 11, 2026
56bf8b7
Show the one-color tray icon from an AppImage on GNOME
claude Nov 11, 2026
a7ec2cc
Show hotkey notifications on GNOME
claude Nov 11, 2026
6e102a2
Advise quitting Weir before putting a new AppImage in place
claude Nov 11, 2026
f31a1bb
Explain hotkeys where the desktop only runs commands
claude Nov 11, 2026
ae00738
Merge branch 'claude/arm64' into claude/appimage
claude Nov 11, 2026
458ddad
Say the ARM AppImage has not been tried on ARM hardware yet
claude Nov 11, 2026
a91baf2
Bring the mixer back after Restart Weir
claude Nov 11, 2026
8aeec7b
Show the tray icon afresh when its look changes
claude Nov 11, 2026
384eb0e
Give the tray menu icons COSMIC can draw
claude Nov 11, 2026
dfd1ef6
Say off Plasma that both popup choices show a notification
claude Nov 11, 2026
17a4ff4
Draw the recording dot into the tray icon
claude Nov 12, 2026
6d6beba
Open the mixer at a new login the service lived through
claude Nov 12, 2026
9a22716
Let Arch Linux ARM users build Weir from the AUR
claude Nov 12, 2026
ad0ab0c
Keep room for the recording outline at the mixer's sides
claude Nov 12, 2026
b3025b9
Read the tray icon's pixels as fixed chunks
claude Nov 12, 2026
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
110 changes: 104 additions & 6 deletions .github/workflows/packages.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Builds Weir's packages: an RPM for Fedora and Nobara and a .deb for
# Ubuntu, Linux Mint, Pop!_OS and Debian, each for x86_64 and for 64-bit
# ARM, and a package for Arch Linux and the systems built on it (x86_64
# only), whose recipe also goes to the AUR.
# Builds Weir's packages: an RPM for Fedora and Nobara, a .deb for
# Ubuntu, Linux Mint, Pop!_OS and Debian, and an AppImage for any other
# distribution, each for x86_64 and for 64-bit ARM, and a package for Arch
# Linux and the systems built on it (x86_64 only), whose recipe also goes
# to the AUR.
#
# * Publishing a release on GitHub builds them and attaches them to the
# release, with a SHA256SUMS file. GitHub also signs a record that this
Expand Down Expand Up @@ -156,6 +157,95 @@ jobs:
if-no-files-found: error
retention-days: 14

appimage:
name: ${{ matrix.name }}
needs: version
strategy:
fail-fast: false
matrix:
include:
- name: AppImage
runner: ubuntu-24.04
arch: x86_64
appimagetool_sha256: ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0
runtime_sha256: 2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d
- name: AppImage, arm64
runner: ubuntu-24.04-arm
arch: aarch64
appimagetool_sha256: f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158
runtime_sha256: 00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444
# Built on the oldest system it is for, like the .deb: it needs the C
# library of Ubuntu 24.04 or newer, and PipeWire's from the system.
runs-on: ${{ matrix.runner }}
env:
# Pinned, by version here and by checksum in the matrix:
# appimagetool packs the AppImage, and the runtime is the small
# program at its front that mounts and starts it, in every copy
# people download. A version changes both checksums.
APPIMAGETOOL_VERSION: "1.9.1"
RUNTIME_VERSION: "20251108"
steps:
- uses: actions/checkout@v4
- name: Install the build tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
clang cmake pkg-config libpipewire-0.3-dev desktop-file-utils appstream \
squashfs-tools zsync
# The ARM image may come without rustup; install it if so.
- name: Install Rust
run: |
if ! command -v rustup >/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.cargo/bin:$PATH"
fi
rustup toolchain install stable --profile minimal
rustup default stable
- name: Fetch appimagetool and the AppImage runtime
run: |
mkdir -p tools
base=https://github.com/AppImage
curl -fsSL -o tools/appimagetool \
"$base/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-${{ matrix.arch }}.AppImage"
curl -fsSL -o tools/runtime \
"$base/type2-runtime/releases/download/$RUNTIME_VERSION/runtime-${{ matrix.arch }}"
sha256sum -c <<EOF || { sha256sum tools/appimagetool tools/runtime; exit 1; }
${{ matrix.appimagetool_sha256 }} tools/appimagetool
${{ matrix.runtime_sha256 }} tools/runtime
EOF
chmod +x tools/appimagetool
# The runner may have no FUSE, so appimagetool unpacks itself to run.
- name: Build the AppImage
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: make appimage APPIMAGETOOL="$PWD/tools/appimagetool" APPIMAGE_RUNTIME=tools/runtime
- name: Check the AppImage
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
cd target/appimage
ls -la
desktop-file-validate AppDir/weir.desktop
appstreamcli validate --no-net AppDir/usr/share/metainfo/io.github.mourty.weir.metainfo.xml
image=$(ls Weir-*-${{ matrix.arch }}.AppImage)
test -s "$image.zsync"
# It starts, and runs the program asked for.
"./$image" weirctl --version
"./$image" weir-daemon --version
# Where updaters look for the next version. The runtime reads
# this without mounting, but only when not told to unpack itself.
env -u APPIMAGE_EXTRACT_AND_RUN "./$image" --appimage-updateinformation | tee update-info
grep -q "^gh-releases-zsync|Mourty|Weir|latest|Weir-\*-${{ matrix.arch }}.AppImage.zsync$" update-info
- uses: actions/upload-artifact@v4
with:
name: appimage-${{ matrix.arch }}
path: |
target/appimage/Weir-*.AppImage
target/appimage/Weir-*.AppImage.zsync
if-no-files-found: error
retention-days: 14

arch:
name: Arch Linux
needs: version
Expand Down Expand Up @@ -269,7 +359,7 @@ jobs:
publish:
name: Attach to the release
if: github.event_name == 'release'
needs: [rpm, deb, arch]
needs: [rpm, deb, appimage, arch]
runs-on: ubuntu-24.04
permissions:
contents: write # attach the files to the release
Expand All @@ -287,12 +377,19 @@ jobs:
pattern: deb-*
merge-multiple: true
path: dist
- uses: actions/download-artifact@v4
with:
pattern: appimage-*
merge-multiple: true
path: dist
- uses: actions/download-artifact@v4
with:
name: arch
path: dist
# The .zsync files are for updaters, which check what they download
# against them; they are attached but not listed.
- name: Write checksums
run: cd dist && sha256sum -- *.rpm *.deb *.pkg.tar.zst > SHA256SUMS && cat SHA256SUMS
run: cd dist && sha256sum -- *.rpm *.deb *.AppImage *.pkg.tar.zst > SHA256SUMS && cat SHA256SUMS
# Signed in the same run that built the packages, so it vouches for
# exactly these files; a file swapped on the release later fails the
# check. Attestations need a public repository (or GitHub Enterprise).
Expand All @@ -302,6 +399,7 @@ jobs:
subject-path: |
dist/*.rpm
dist/*.deb
dist/*.AppImage
dist/*.pkg.tar.zst
- name: Upload to the release
env:
Expand Down
24 changes: 18 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,26 @@ Makefile. `make rpm-install` numbers its package by date and time, so dnf
counts it as newer than a released package of the same version: run
`sudo dnf remove weir` before installing a downloaded one.

`make appimage-dir` lays out an AppImage's contents in
`target/appimage/AppDir`: what `make install` puts down under `/usr`,
less the guides and the login unit, plus `packaging/appimage/AppRun`.
`make appimage` packs them with
[appimagetool](https://github.com/AppImage/appimagetool), which it does
not install (`make appimage APPIMAGETOOL=/path/to/appimagetool`), and
with `zsyncmake` (`sudo apt install zsync`) also writes the `.zsync` file
updaters use. It builds for the computer it runs on.

GitHub builds the packages whenever a pull request changes the
packaging, and on demand from **Actions → Packages → Run workflow**. Each
run keeps them as downloads on its page for two weeks, for trying before a
release. It builds the RPM and the `.deb` twice, for x86_64 and for
64-bit ARM (on GitHub's ARM runners, free for public repositories), and
release. It builds the RPM, the `.deb` and the AppImage twice, for x86_64
and for 64-bit ARM (on GitHub's ARM runners, free for public repositories), and
an Arch Linux package for x86_64 in an Arch container, from
`packaging/aur/PKGBUILD`, which is also the recipe for the
[AUR](https://aur.archlinux.org/packages/weir). The **Checks** workflow
runs clippy and the tests on ARM too.
[AUR](https://aur.archlinux.org/packages/weir). The recipe also lists
`aarch64`, so Arch Linux ARM users can build it from the AUR, but nothing
here builds or tests it that way. The **Checks** workflow runs clippy and
the tests on ARM too.

[COPR](https://copr.fedorainfracloud.org/coprs/mourt/Weir/), Fedora's
build service, also builds the RPM for every current Fedora release and
Expand All @@ -265,8 +276,9 @@ branch fails instead of publishing unreleased code as a release.
tag**, type `v1.1.0` (the version with a `v` in front) and pick
**Create new tag on publish**. Give it a title, write what changed (or
press **Generate release notes**), and press **Publish release**.
4. The **Packages** workflow builds the RPMs, the `.deb` files and the
Arch package and attaches them to the release, with a `SHA256SUMS` file, in
4. The **Packages** workflow builds the RPMs, the `.deb` files, the
AppImages (with their `.zsync` files, for updaters) and the Arch
package and attaches them to the release, with a `SHA256SUMS` file, in
about twenty minutes.
It refuses to start if the tag and the version numbers disagree.
It also signs an attestation for each package (listed on the
Expand Down
63 changes: 62 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ CARGO ?= cargo
CARGO_FLAGS ?= --release
BINARIES := weir-daemon weir weirctl

.PHONY: all build test fuzz install uninstall clean help rpm rpm-install deb icons licenses
.PHONY: all build test fuzz install uninstall clean help rpm rpm-install deb appimage appimage-dir icons licenses

all: build

Expand All @@ -66,6 +66,8 @@ help:
@echo "make rpm build an RPM package into $(RPMTOP)/RPMS"
@echo "make rpm-install build the RPM and install it with dnf"
@echo "make deb build a .deb package into target/deb"
@echo "make appimage build an AppImage into target/appimage (needs appimagetool)"
@echo "make appimage-dir lay out the AppImage's contents, without packing them"
@echo "make icons redraw the window icon (needs rsvg-convert)"
@echo "make licenses gather the libraries' license notices (needs cargo-about)"

Expand Down Expand Up @@ -260,3 +262,62 @@ deb:
dpkg-deb --root-owner-group -Zxz --build $(DEBROOT) $(DEB)
@echo ""
@echo "Package built: $(DEB)"

# ---- AppImage ------------------------------------------------------------
#
# One file holding Weir's programs, for any distribution with PipeWire,
# for the computer it is built on (x86_64 or aarch64). `make appimage-dir`
# lays out its contents in target/appimage/AppDir; `make appimage` packs
# them with appimagetool, which it does not install: put it on the PATH or
# point APPIMAGETOOL at it, and point APPIMAGE_RUNTIME at the AppImage
# runtime to put in front, or appimagetool downloads the newest. Both come
# from github.com/AppImage; the Packages workflow uses pinned versions.
#
# The programs need only the C library and PipeWire's library from the
# system, so nothing else is bundled. PipeWire's own library stays the
# system's: it loads its plugins from the system's folders.

APPIMAGE_ARCH := $(shell uname -m)
APPIMAGE_DIR := target/appimage
APPIMAGE_ROOT := $(APPIMAGE_DIR)/AppDir
APPIMAGE_FILE := $(APPIMAGE_DIR)/Weir-$(VERSION)-$(APPIMAGE_ARCH).AppImage
APPIMAGETOOL ?= appimagetool
APPIMAGE_RUNTIME ?=
# Where updaters such as Gear Lever and AppImageUpdate look for the next
# version: the .zsync file beside it on GitHub's latest release.
APPIMAGE_UPDATE := gh-releases-zsync|Mourty|Weir|latest|Weir-*-$(APPIMAGE_ARCH).AppImage.zsync

appimage-dir:
rm -rf $(APPIMAGE_DIR)
$(MAKE) install DESTDIR=$(CURDIR)/$(APPIMAGE_ROOT) PREFIX=/usr
@# Nothing an AppImage uses: the guides are online, and Weir writes its
@# own login unit, naming the AppImage, when asked to start at login.
rm -rf $(APPIMAGE_ROOT)/usr/share/doc $(APPIMAGE_ROOT)/usr/share/systemd
strip --strip-unneeded $(addprefix $(APPIMAGE_ROOT)/usr/bin/,$(BINARIES))
install -m755 packaging/appimage/AppRun $(APPIMAGE_ROOT)/AppRun
@# Inside an AppImage the menu entry names the program, not a path.
sed -i 's|^Exec=.*|Exec=weir|' $(APPIMAGE_ROOT)/usr/share/applications/weir.desktop
cp $(APPIMAGE_ROOT)/usr/share/applications/weir.desktop $(APPIMAGE_ROOT)/weir.desktop
cp packaging/weir.svg $(APPIMAGE_ROOT)/weir.svg
cp crates/gui/assets/weir.png $(APPIMAGE_ROOT)/.DirIcon
@# The tray names usr/share/icons for the one-color icon. GNOME's tray
@# looks only at the files right in that folder, Plasma in hicolor.
cp packaging/weir-symbolic.svg $(APPIMAGE_ROOT)/usr/share/icons/weir-symbolic.svg
@echo ""
@echo "Laid out in $(APPIMAGE_ROOT)"

appimage: appimage-dir
@command -v $(APPIMAGETOOL) >/dev/null 2>&1 || { \
echo "appimagetool not found. Get it from"; \
echo " https://github.com/AppImage/appimagetool/releases"; \
echo "and run make appimage APPIMAGETOOL=/path/to/appimagetool"; \
exit 1; }
@command -v zsyncmake >/dev/null 2>&1 || \
echo "zsyncmake not found: no .zsync file for updaters (sudo apt install zsync)"
@# From the AppImage's folder: zsyncmake writes the .zsync file where it runs.
tool=$$(realpath "$$(command -v $(APPIMAGETOOL))") && cd $(APPIMAGE_DIR) && \
ARCH=$(APPIMAGE_ARCH) "$$tool" \
$(if $(APPIMAGE_RUNTIME),--runtime-file $(abspath $(APPIMAGE_RUNTIME))) \
-u '$(APPIMAGE_UPDATE)' AppDir $(notdir $(APPIMAGE_FILE))
@echo ""
@echo "AppImage built: $(APPIMAGE_FILE)"
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,20 @@ you saved it to:

On a 64-bit ARM computer, such as a Raspberry Pi 5, an Apple Silicon Mac
running Fedora Asahi Remix, or an ARM laptop, take the `.aarch64.rpm` or
the `_arm64.deb` instead. There is no ARM package for Arch. The ARM
packages pass the same automatic tests as the others, but have not yet
been tried on an ARM computer, so if you try one, please report how it
went. On a Raspberry Pi, Weir needs Raspberry Pi OS from late 2025 (based
the `_arm64.deb` instead. There is no ready-made ARM package for Arch;
on Arch Linux ARM, build Weir with the Arch steps below. The ARM
packages pass the same automatic tests as the others, but neither they
nor an Arch Linux ARM build have been tried on an ARM computer yet, so if
you try one, please report how it went. On a Raspberry Pi, Weir needs Raspberry Pi OS from late 2025 (based
on Debian 13) or newer.

**On any other distribution**, or to try Weir without installing it, take
the AppImage: `Weir-…-x86_64.AppImage`, or `Weir-…-aarch64.AppImage` on
ARM (like the ARM packages, not yet tried on an ARM computer). Allow it
to run (`chmod +x Weir-*.AppImage`) and open it. It needs PipeWire with
WirePlumber and a system from 2024 or newer; the
[user guide](docs/USER_GUIDE.md#running-weir-as-an-appimage) has the rest.

GitHub builds the packages from the code here and signs a record of it.
To check that a download is exactly what GitHub built, use the
[GitHub command-line tool](https://cli.github.com/), signed in with
Expand Down
2 changes: 1 addition & 1 deletion crates/daemon/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ impl Controller {
window_clients: AtomicUsize::new(0),
window_tx: Mutex::new(None),
spectrum_watchers: Mutex::new(BTreeMap::new()),
login: Box::new(login::Systemd),
login: login::here(weir_protocol::appimage::appimage().as_ref()),
hotkey_runner: Mutex::new(None),
shortcut_settings: tokio::sync::Notify::new(),
popups: Mutex::new(None),
Expand Down
37 changes: 34 additions & 3 deletions crates/daemon/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ pub enum Install {
Make(PathBuf),
/// Run from where it was built.
BuildFolder(PathBuf),
/// From an AppImage: the AppImage file.
AppImage(PathBuf),
/// Somewhere else, or nobody knows.
Elsewhere(Option<PathBuf>),
}
Expand All @@ -136,9 +138,14 @@ impl Install {
/// moment, so it is asked once.
pub fn here() -> Install {
static HERE: OnceLock<Install> = OnceLock::new();
HERE.get_or_init(|| match weir_protocol::own_exe() {
Ok(exe) => Install::of(&exe, dirs::home_dir().as_deref(), package),
Err(_) => Install::Elsewhere(None),
HERE.get_or_init(|| {
if let Some(a) = weir_protocol::appimage::appimage() {
return Install::AppImage(a.file);
}
match weir_protocol::own_exe() {
Ok(exe) => Install::of(&exe, dirs::home_dir().as_deref(), package),
Err(_) => Install::Elsewhere(None),
}
})
.clone()
}
Expand Down Expand Up @@ -171,6 +178,7 @@ impl Install {
Install::Unpackaged(exe) => format!("{}, in no package", exe.display()),
Install::Make(exe) => format!("make install, {}", exe.display()),
Install::BuildFolder(exe) => format!("a build folder, {}", exe.display()),
Install::AppImage(file) => format!("AppImage, {}", file.display()),
Install::Elsewhere(Some(exe)) => exe.display().to_string(),
Install::Elsewhere(None) => "unknown".into(),
}
Expand Down Expand Up @@ -200,6 +208,17 @@ impl Install {
release's source download) and run make install again"
}
Install::BuildFolder(_) => "Get the new source and build it again",
Install::AppImage(file) => {
// Quit first: while it runs, an AppImage cannot be written
// over (file managers say it is in use).
return format!(
"Download the new AppImage for your computer from the release page. Choose \
Quit Weir in Weir's tray menu, allow the new file to run and open it, then \
delete this one ({}). Or let Gear Lever or AppImageUpdate update it, then \
choose Restart Weir in Weir's tray menu",
file.display()
);
}
Install::Unpackaged(_) | Install::Elsewhere(_) => "Download it from the release page",
};
// Installing replaces the files, but the old version keeps running
Expand Down Expand Up @@ -362,6 +381,18 @@ mod tests {
at("/home/sam/src/Weir/target/release/weir-daemon", &copr).describe(),
"a build folder, /home/sam/src/Weir/target/release/weir-daemon"
);
let appimage = Install::AppImage("/home/sam/Apps/Weir-1.5.0-x86_64.AppImage".into());
assert_eq!(
appimage.describe(),
"AppImage, /home/sam/Apps/Weir-1.5.0-x86_64.AppImage"
);
let how = appimage.how_to_update();
assert!(how.contains("Choose Quit Weir"), "{how}");
assert!(how.contains("delete this one (/home/sam/Apps/Weir-1.5.0-x86_64.AppImage)"));
assert!(
how.ends_with("then choose Restart Weir in Weir's tray menu"),
"{how}"
);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions crates/daemon/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ pub fn window_env() -> Option<Vec<(String, String)>> {
session_from_manager(&manager_env())
}

/// The variables for a window on a desktop that began after the daemon
/// started: the manager's, which that desktop has just handed over, since
/// the daemon's own may name the desktop it started on, gone since. Where
/// the manager has none, as [`window_env`].
pub fn new_desktop_env() -> Option<Vec<(String, String)>> {
session_from_manager(&manager_env()).or_else(window_env)
}

/// The systemd user manager's environment, or nothing where there is no
/// systemd or it cannot be asked.
fn manager_env() -> String {
Expand Down
Loading
Loading