How to Install QSpeakers on Ubuntu 26.04, 24.04 and 22.04

Install QSpeakers on Ubuntu 26.04, 24.04, and 22.04, then turn driver measurements into a practical enclosure comparison. Select a known or custom loudspeaker, compare sealed, ported, and bandpass response curves, and save a baseline before changing the design.

PublishedAuthorJoshua JamesRead time9 minGuide typeUbuntu

A loudspeaker enclosure can look sensible on paper and still produce the wrong low-frequency response. Installing QSpeakers on Ubuntu lets you compare sealed, ported, and bandpass designs from a driver’s Thiele/Small parameters before committing material to a cabinet.

Ubuntu’s APT package offers the simplest system-managed installation, Flathub supplies the latest release in a sandbox, and a per-user source build keeps QSpeakers itself outside shared system prefixes. The source method also creates a desktop launcher and manual update helper.

Install QSpeakers on Ubuntu

Choose a QSpeakers Installation Method

MethodSource and VersionUpdate OwnerBest Fit
APTUbuntu Universe: 1.8.5 on Ubuntu 26.04, 1.6.9 on Ubuntu 24.04, and 1.6.2 on Ubuntu 22.04APTMost Ubuntu 26.04 users and anyone who prefers the simplest system-managed installation
FlatpakVerified Flathub app, version 1.8.6 for x86_64 and aarch64Flatpak and FlathubUsers who want 1.8.6 in a sandbox or already manage desktop applications through Flathub
SourceOfficial upstream Git tags, latest stable tag 1.8.6update-qspeakers-source, run manuallyUsers who want a per-user build, a versioned install layout, or direct control of upstream updates

Use APT on Ubuntu 26.04 unless you need version 1.8.6. Flatpak is the easier current-release option on Ubuntu 24.04 and 22.04. Choose the source build when you specifically want a local upstream build and are comfortable keeping compiler dependencies installed.

Install one method for normal use. APT and the source build both provide a qspeakers command and a qspeakers.desktop launcher, so installing them together changes which binary and launcher your account opens. Flatpak remains separate but adds another launcher and update path.

Install QSpeakers with APT

QSpeakers is available from the Universe component on Ubuntu 26.04, 24.04, and 22.04. Refresh APT metadata, then install the qspeakers package.

These commands use sudo for system package changes. If your account does not have administrative access, follow the steps to add a user to sudoers on Ubuntu before continuing.

sudo apt update
sudo apt install qspeakers

Confirm that APT registered the installed package and print its exact Ubuntu build:

dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package} ${Version}\n' qspeakers

An output line beginning with ii confirms that the package is installed. The final field contains the release-specific package version.

Install QSpeakers with Flatpak

Ubuntu does not include Flatpak on a standard installation. Complete the Flatpak setup on Ubuntu first, then add Flathub for system-wide applications if the remote is missing:

sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo

Flathub marks QSpeakers as verified and links the listing to the upstream project. The 1.8.6 package supports x86_64 and aarch64. Its permissions exclude network and broad home-directory access while allowing the display and graphics access the desktop app needs. Install its exact application ID:

sudo flatpak install flathub rocks.noise.qspeakers

Verify the system-wide installation and confirm that Flathub owns the installed application:

flatpak info --system rocks.noise.qspeakers

Build QSpeakers from Source

This method builds the official upstream tag as your desktop user and installs each version under ~/.local/opt/qspeakers. A stable current link selects one active version, and a failed build leaves the existing version unchanged.

The upstream tag list does not publish a detached signature or release checksum for this source workflow. The helper resolves numeric tags from the official Git repository, confirms the exact tag commit after cloning, and checks the version declared by qspeakers.pro. These checks catch incomplete or mismatched downloads, but they are not a cryptographic authenticity guarantee.

Install the Source-Build Dependencies

Ubuntu 22.04 uses the package name libqt6charts6-dev, while Ubuntu 24.04 and 26.04 use qt6-charts-dev. This pasteable Bash block selects the available name and installs the Qt 6 build, translation, OpenGL, and desktop-cache toolchain:

bash <<'QSP_DEPS'
set -euo pipefail

sudo apt update
if apt-cache show qt6-charts-dev >/dev/null 2>&1; then
  charts_package=qt6-charts-dev
else
  charts_package=libqt6charts6-dev
fi

sudo apt install \
  build-essential \
  git \
  libgl-dev \
  libgtk-3-bin \
  qmake6 \
  qt6-base-dev \
  "$charts_package" \
  qt6-l10n-tools \
  qt6-tools-dev-tools \
  desktop-file-utils \
  shared-mime-info
QSP_DEPS

Create the QSpeakers Source Update Helper

Create update-qspeakers-source in ~/.local/bin. The setup refuses to overwrite an unrelated file or symbolic link and validates the complete temporary script before replacement. The helper builds without sudo, stages files before activation, and restores the previous version if launcher integration fails.

bash <<'QSP_SETUP'
set -euo pipefail

helper_path="$HOME/.local/bin/update-qspeakers-source"
mkdir -p "$HOME/.local/bin"

if [ -L "$helper_path" ]; then
  printf 'Error: %s is a symbolic link; refusing to replace it.\n' "$helper_path" >&2
  exit 1
fi
if [ -e "$helper_path" ] && {
  [ ! -f "$helper_path" ] ||
    ! grep -Fxq '# linuxcapable-managed-qspeakers-source-helper' "$helper_path"
}; then
  printf 'Error: %s is not the managed QSpeakers helper.\n' "$helper_path" >&2
  exit 1
fi

temporary_helper="$(mktemp "$HOME/.local/bin/.update-qspeakers-source.XXXXXX")"
cleanup_temporary_helper() {
  rm -f -- "$temporary_helper"
}
trap cleanup_temporary_helper EXIT HUP INT TERM

cat >"$temporary_helper" <<'QSP_HELPER'
#!/usr/bin/env bash
# linuxcapable-managed-qspeakers-source-helper
set -euo pipefail

repo_url='https://github.com/be1/qspeakers.git'
install_root="$HOME/.local/opt/qspeakers"
current_link="$install_root/current"
command_path="$HOME/.local/bin/qspeakers"
desktop_path="$HOME/.local/share/applications/qspeakers.desktop"
icon_path="$HOME/.local/share/icons/hicolor/scalable/apps/qspeakers.svg"
hicolor_root="$HOME/.local/share/icons/hicolor"
hicolor_index="$hicolor_root/index.theme"
hicolor_marker="$hicolor_root/.linuxcapable-created-qspeakers-index-theme"
mime_path="$HOME/.local/share/mime/packages/application-x-qspeakers.xml"
metainfo_path="$HOME/.local/share/metainfo/rocks.noise.qspeakers.metainfo.xml"
root_marker="$install_root/.linuxcapable-managed-source"

fail() {
	printf 'Error: %s\n' "$*" >&2
	exit 1
}

if [ "$(id -u)" -eq 0 ]; then
	fail 'run update-qspeakers-source as your regular desktop user, not with sudo'
fi

if [ "$#" -gt 1 ]; then
	fail 'usage: update-qspeakers-source [numeric-tag]'
fi
requested_tag="${1:-}"
if [ -n "$requested_tag" ] &&
	! [[ "$requested_tag" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]]; then
	fail "invalid numeric release tag: $requested_tag"
fi

for required_command in awk desktop-file-validate git grep \
	gtk-update-icon-cache readlink sed sort update-desktop-database \
	update-mime-database; do
	command -v "$required_command" >/dev/null 2>&1 ||
		fail "$required_command is required; install the source-build dependencies and retry"
done

if [ -L "$install_root" ]; then
	fail "$install_root is a symbolic link; refusing to follow it"
fi
if [ -e "$install_root" ]; then
	[ -d "$install_root" ] ||
		fail "$install_root exists but is not a directory"
	[ ! -L "$root_marker" ] &&
		[ -f "$root_marker" ] &&
		[ "$(cat -- "$root_marker")" = \
			'linuxcapable-managed-qspeakers-source-root' ] ||
		fail "$install_root is not marked as a managed QSpeakers source tree"
fi

check_exact_symlink() {
	local link_path="$1"
	local expected_target="$2"

	if [ -L "$link_path" ]; then
		[ "$(readlink -- "$link_path")" = "$expected_target" ] ||
			fail "$link_path is a symlink outside this QSpeakers source workflow"
	elif [ -e "$link_path" ]; then
		fail "$link_path already exists and is not a managed symlink"
	fi
}

check_exact_symlink "$command_path" "$current_link/bin/qspeakers"
check_exact_symlink \
	"$icon_path" \
	"$current_link/share/icons/hicolor/scalable/apps/qspeakers.svg"
check_exact_symlink \
	"$mime_path" \
	"$current_link/share/mime/packages/application-x-qspeakers.xml"
check_exact_symlink \
	"$metainfo_path" \
	"$current_link/share/metainfo/rocks.noise.qspeakers.metainfo.xml"

if [ -L "$desktop_path" ]; then
	fail "$desktop_path is a symbolic link; refusing to follow it"
fi
if [ -e "$desktop_path" ]; then
	if [ ! -f "$desktop_path" ] ||
		! grep -Fxq 'X-LinuxCapable-Managed=QSpeakersSource' "$desktop_path"; then
		fail "$desktop_path exists but is not managed by this QSpeakers source workflow"
	fi
fi

current_prefix=''
current_version='none'
current_commit=''
if [ -e "$current_link" ] || [ -L "$current_link" ]; then
	[ -L "$current_link" ] ||
		fail "$current_link exists but is not a symbolic link"
	current_prefix="$(readlink -f -- "$current_link" || true)"
	case "$current_prefix" in
	"$install_root"/*) ;;
	*) fail "$current_link points outside $install_root" ;;
	esac
	current_marker="$current_prefix/.linuxcapable-managed-version"
	[ ! -L "$current_marker" ] && [ -f "$current_marker" ] ||
		fail "the active QSpeakers prefix is missing its ownership marker"
	[ "$(awk 'END { print NR }' "$current_marker")" = 2 ] ||
		fail 'the active QSpeakers version marker has unexpected content'
	current_version="$(
		sed -n 's/^version=//p' "$current_marker" |
			awk 'NR == 1 { value = $0 } END { print value }'
	)"
	current_commit="$(
		sed -n 's/^commit=//p' "$current_marker" |
			awk 'NR == 1 { value = $0 } END { print value }'
	)"
	[[ "$current_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] ||
		fail "the active QSpeakers version marker is invalid: $current_version"
	[[ "$current_commit" =~ ^[0-9a-f]{40}$ ]] ||
		fail 'the active QSpeakers commit marker is invalid'
fi

tag_listing="$(git ls-remote --tags "$repo_url")" ||
	fail 'could not read QSpeakers tags from the upstream Git repository'
latest_tag="$(
	printf '%s\n' "$tag_listing" |
		sed -nE 's#^[0-9a-f]{40}[[:space:]]+refs/tags/([0-9]+(\.[0-9]+){1,2})$#\1#p' |
		sort -Vu |
		awk 'NF { latest = $0 } END { print latest }'
)"
[ -n "$latest_tag" ] ||
	fail 'the upstream Git repository did not return a stable numeric release tag'
target_tag="${requested_tag:-$latest_tag}"

tag_ref="refs/tags/$target_tag"
tag_object="$(
	printf '%s\n' "$tag_listing" |
		awk -v ref="$tag_ref" '
			$2 == ref && !found {
				value = $1
				found = 1
			}
			END { print value }
		'
)"
peeled_commit="$(
	printf '%s\n' "$tag_listing" |
		awk -v ref="${tag_ref}^{}" '
			$2 == ref && !found {
				value = $1
				found = 1
			}
			END { print value }
		'
)"
target_commit="${peeled_commit:-$tag_object}"
[[ "$target_commit" =~ ^[0-9a-f]{40}$ ]] ||
	fail "release tag $target_tag was not found upstream"

printf 'Installed version: %s\n' "$current_version"
printf 'Latest stable tag: %s\n' "$latest_tag"
if [ -n "$requested_tag" ]; then
	printf 'Requested tag: %s\n' "$target_tag"
fi

replace_symlink() {
	local link_path="$1"
	local link_target="$2"
	local link_directory
	local temporary_directory
	local temporary_link

	link_directory="$(dirname -- "$link_path")" || return 1
	temporary_directory="$(
		mktemp -d -p "$link_directory" .qspeakers-link.XXXXXX
	)" || return 1
	temporary_link="$temporary_directory/link"
	ln -s -- "$link_target" "$temporary_link" || {
		rm -rf -- "$temporary_directory"
		return 1
	}
	mv -Tf -- "$temporary_link" "$link_path" || {
		rm -rf -- "$temporary_directory"
		return 1
	}
	rmdir -- "$temporary_directory" || return 1
}

prepare_integration() {
	local active_prefix="$1"
	local temporary_desktop
	local upstream_desktop="$active_prefix/share/applications/qspeakers.desktop"

	[ -x "$active_prefix/bin/qspeakers" ] || {
		printf 'Error: the active prefix has no executable QSpeakers binary\n' >&2
		return 1
	}
	[ -f "$upstream_desktop" ] || {
		printf 'Error: the active prefix has no QSpeakers desktop entry\n' >&2
		return 1
	}
	[ -f "$active_prefix/share/icons/hicolor/scalable/apps/qspeakers.svg" ] || {
		printf 'Error: the active prefix has no QSpeakers application icon\n' >&2
		return 1
	}
	[ -f "$active_prefix/share/mime/packages/application-x-qspeakers.xml" ] || {
		printf 'Error: the active prefix has no QSpeakers MIME definition\n' >&2
		return 1
	}
	[ -f "$active_prefix/share/metainfo/rocks.noise.qspeakers.metainfo.xml" ] || {
		printf 'Error: the active prefix has no QSpeakers metainfo file\n' >&2
		return 1
	}

	mkdir -p "$HOME/.cache" || return 1
	temporary_desktop="$(
		mktemp --suffix=.desktop "$HOME/.cache/qspeakers-desktop.XXXXXX"
	)" || return 1
	if ! awk -v executable="$command_path" '
		BEGIN {
			saw_tryexec = 0
			saw_exec = 0
			saw_icon = 0
		}
		/^TryExec=/ {
			print "TryExec=" executable
			saw_tryexec = 1
			next
		}
		/^Exec=/ {
			print "Exec=" executable " %f"
			saw_exec = 1
			next
		}
		/^Icon=/ {
			print "Icon=qspeakers"
			saw_icon = 1
			next
		}
		/^X-LinuxCapable-Managed=/ {
			next
		}
		{
			print
		}
		END {
			if (!saw_tryexec) {
				print "TryExec=" executable
			}
			if (!saw_exec) {
				print "Exec=" executable " %f"
			}
			if (!saw_icon) {
				print "Icon=qspeakers"
			}
			print "X-LinuxCapable-Managed=QSpeakersSource"
		}
	' "$upstream_desktop" >"$temporary_desktop"; then
		rm -f -- "$temporary_desktop"
		return 1
	fi
	if ! desktop-file-validate "$temporary_desktop" >&2; then
		rm -f -- "$temporary_desktop"
		return 1
	fi
	printf '%s\n' "$temporary_desktop"
}

apply_integration() {
	local active_prefix="$1"
	local prepared_desktop="$2"
	local created_hicolor_index=false

	mkdir -p \
		"$(dirname -- "$command_path")" \
		"$(dirname -- "$desktop_path")" \
		"$(dirname -- "$icon_path")" \
		"$(dirname -- "$mime_path")" \
		"$(dirname -- "$metainfo_path")" ||
		return 1
	if [ -L "$hicolor_marker" ]; then
		printf 'Error: %s is a symbolic link; refusing to follow it\n' \
			"$hicolor_marker" >&2
		return 1
	elif [ -e "$hicolor_marker" ] &&
		[ "$(cat -- "$hicolor_marker")" != \
			'linuxcapable-created-qspeakers-hicolor-index' ]; then
		printf 'Error: %s has an unexpected ownership marker\n' \
			"$hicolor_marker" >&2
		return 1
	fi
	if [ -L "$hicolor_index" ]; then
		printf 'Error: %s is a symbolic link; refusing to follow it\n' \
			"$hicolor_index" >&2
		return 1
	elif [ ! -e "$hicolor_index" ]; then
		[ -f /usr/share/icons/hicolor/index.theme ] || return 1
		install -m 0644 /usr/share/icons/hicolor/index.theme \
			"$hicolor_index" ||
			return 1
		created_hicolor_index=true
	elif [ ! -f "$hicolor_index" ]; then
		printf 'Error: %s exists but is not a regular file\n' \
			"$hicolor_index" >&2
		return 1
	fi
	if "$created_hicolor_index"; then
		printf '%s\n' 'linuxcapable-created-qspeakers-hicolor-index' \
			>"$hicolor_marker" ||
			return 1
	fi

	replace_symlink "$command_path" "$current_link/bin/qspeakers" ||
		return 1
	replace_symlink \
		"$icon_path" \
		"$current_link/share/icons/hicolor/scalable/apps/qspeakers.svg" ||
		return 1
	replace_symlink \
		"$mime_path" \
		"$current_link/share/mime/packages/application-x-qspeakers.xml" ||
		return 1
	replace_symlink \
		"$metainfo_path" \
		"$current_link/share/metainfo/rocks.noise.qspeakers.metainfo.xml" ||
		return 1
	install -m 0644 "$prepared_desktop" "$desktop_path" || return 1
	rm -f -- "$prepared_desktop" || return 1

	update-desktop-database "$HOME/.local/share/applications" || return 1
	update-mime-database "$HOME/.local/share/mime" || return 1
	gtk-update-icon-cache -q -f "$hicolor_root" || return 1
}

remove_managed_integration() {
	local link_path
	local expected_target

	while IFS=$'\t' read -r link_path expected_target; do
		if [ -L "$link_path" ]; then
			[ "$(readlink -- "$link_path")" = "$expected_target" ] ||
				return 1
			rm -f -- "$link_path" || return 1
		elif [ -e "$link_path" ]; then
			return 1
		fi
	done <<LINKS
$command_path	$current_link/bin/qspeakers
$icon_path	$current_link/share/icons/hicolor/scalable/apps/qspeakers.svg
$mime_path	$current_link/share/mime/packages/application-x-qspeakers.xml
$metainfo_path	$current_link/share/metainfo/rocks.noise.qspeakers.metainfo.xml
LINKS

	if [ -L "$desktop_path" ]; then
		return 1
	elif [ -e "$desktop_path" ]; then
		grep -Fxq 'X-LinuxCapable-Managed=QSpeakersSource' "$desktop_path" ||
			return 1
		rm -f -- "$desktop_path" || return 1
	fi

	if [ -d "$HOME/.local/share/applications" ]; then
		update-desktop-database "$HOME/.local/share/applications" || return 1
	fi
	if [ -d "$HOME/.local/share/mime" ]; then
		update-mime-database "$HOME/.local/share/mime" || return 1
	fi
	if [ -d "$hicolor_root" ] && [ -f "$hicolor_index" ]; then
		gtk-update-icon-cache -q -f "$hicolor_root" || return 1
	fi
	if [ -e "$hicolor_marker" ]; then
		[ ! -L "$hicolor_marker" ] &&
			[ "$(cat -- "$hicolor_marker")" = \
				'linuxcapable-created-qspeakers-hicolor-index' ] ||
			return 1
		if ! find "$hicolor_root" -mindepth 1 \
			! -path "$hicolor_index" \
			! -path "$hicolor_marker" \
			! -path "$hicolor_root/icon-theme.cache" \
			! -type d -print -quit | grep -q .; then
			rm -f -- \
				"$hicolor_root/icon-theme.cache" \
				"$hicolor_index" \
				"$hicolor_marker" ||
				return 1
			rmdir --ignore-fail-on-non-empty \
				"$hicolor_root/scalable/apps" \
				"$hicolor_root/scalable" \
				"$hicolor_root" 2>/dev/null || true
		else
			rm -f -- "$hicolor_marker" || return 1
		fi
	fi
}

activate_prefix() {
	local prefix="$1"
	local old_prefix="$2"
	local prepared_desktop
	local rollback_desktop

	prepared_desktop="$(prepare_integration "$prefix")" ||
		fail 'could not prepare QSpeakers desktop integration'

	replace_symlink "$current_link" "$prefix" || {
		rm -f -- "$prepared_desktop"
		fail 'could not switch the active QSpeakers version'
	}

	if ! apply_integration "$prefix" "$prepared_desktop"; then
		rm -f -- "$prepared_desktop"
		if [ -n "$old_prefix" ]; then
			replace_symlink "$current_link" "$old_prefix" ||
				fail 'desktop integration failed, and the previous active link could not be restored'
			rollback_desktop="$(prepare_integration "$old_prefix")" ||
				fail 'desktop integration failed, and the previous desktop entry could not be prepared'
			if ! apply_integration "$old_prefix" "$rollback_desktop"; then
				rm -f -- "$rollback_desktop"
				fail 'desktop integration failed, and restoring the previous desktop integration also failed'
			fi
			fail 'desktop integration failed; the previous active version was restored'
		else
			rm -f -- "$current_link"
			remove_managed_integration ||
				fail 'desktop integration failed, and its partial files require manual review'
			fail 'desktop integration failed; partial launcher files were removed'
		fi
	fi
}

if [ "$current_version" = "$target_tag" ]; then
	[ "$current_commit" = "$target_commit" ] ||
		fail "upstream tag $target_tag now points to a different commit; review the change before rebuilding"
	current_desktop="$(prepare_integration "$current_prefix")" ||
		fail 'could not prepare QSpeakers desktop integration'
	if ! apply_integration "$current_prefix" "$current_desktop"; then
		rm -f -- "$current_desktop"
		fail 'could not refresh QSpeakers desktop integration'
	fi
	printf '%s\n' 'QSpeakers is already current.'
	exit 0
fi

mkdir -p "$install_root" "$HOME/.cache"
if [ ! -f "$root_marker" ]; then
	printf '%s\n' 'linuxcapable-managed-qspeakers-source-root' >"$root_marker"
fi
[ ! -L "$install_root" ] && [ -d "$install_root" ] &&
	[ ! -L "$root_marker" ] &&
	[ "$(cat -- "$root_marker")" = \
		'linuxcapable-managed-qspeakers-source-root' ] ||
	fail "$install_root failed its managed-root verification"

target_prefix="$install_root/$target_tag"
target_marker="$target_prefix/.linuxcapable-managed-version"
if [ -L "$target_prefix" ]; then
	fail "$target_prefix is a symbolic link; refusing to follow it"
fi
if [ -e "$target_prefix" ]; then
	[ -d "$target_prefix" ] ||
		fail "$target_prefix exists but is not a directory"
	[ ! -L "$target_marker" ] && [ -f "$target_marker" ] ||
		fail "$target_prefix exists without a managed version marker"
	[ "$(awk 'END { print NR }' "$target_marker")" = 2 ] ||
		fail "$target_prefix has an invalid managed version marker"
	stored_version="$(
		sed -n 's/^version=//p' "$target_marker" |
			awk 'NR == 1 { value = $0 } END { print value }'
	)"
	stored_commit="$(
		sed -n 's/^commit=//p' "$target_marker" |
			awk 'NR == 1 { value = $0 } END { print value }'
	)"
	[ "$stored_version" = "$target_tag" ] &&
		[ "$stored_commit" = "$target_commit" ] ||
		fail "$target_prefix does not match upstream tag $target_tag"
	activate_prefix "$target_prefix" "$current_prefix"
	printf 'QSpeakers %s is now active.\n' "$target_tag"
	exit 0
fi

for required_command in desktop-file-validate ldd make nproc qmake6 \
	update-desktop-database update-mime-database; do
	command -v "$required_command" >/dev/null 2>&1 ||
		fail "$required_command is required; install the source-build dependencies and retry"
done
[ -f /usr/include/GL/gl.h ] ||
	fail 'OpenGL development headers are required; install libgl-dev and retry'

lrelease_path=''
if [ -x /usr/lib/qt6/bin/lrelease ]; then
	lrelease_path='/usr/lib/qt6/bin/lrelease'
fi
if [ -z "$lrelease_path" ]; then
	lrelease_path="$(command -v lrelease6 || true)"
fi
if [ -z "$lrelease_path" ]; then
	lrelease_path="$(command -v lrelease || true)"
fi
[ -n "$lrelease_path" ] ||
	fail 'Qt 6 lrelease is required; install qt6-l10n-tools and retry'
lrelease_dir="$(dirname -- "$lrelease_path")"

work_dir="$(mktemp -d "$HOME/.cache/qspeakers-source-build.XXXXXX")"
cleanup_work_dir() {
	rm -rf -- "$work_dir"
}
trap cleanup_work_dir EXIT HUP INT TERM
source_dir="$work_dir/source"
stage_root="$work_dir/stage"
mkdir -p "$stage_root"

printf 'Cloning QSpeakers tag %s...\n' "$target_tag"
git -c advice.detachedHead=false clone \
	--depth 1 \
	--branch "$target_tag" \
	"$repo_url" \
	"$source_dir"
checked_out_commit="$(git -C "$source_dir" rev-parse HEAD)"
[ "$checked_out_commit" = "$target_commit" ] ||
	fail "the checked-out commit does not match upstream tag $target_tag"
grep -Eq \
	"^VERSION[[:space:]]*=[[:space:]]*${target_tag//./\\.}[[:space:]]*$" \
	"$source_dir/qspeakers.pro" ||
	fail "qspeakers.pro does not declare version $target_tag"

printf 'Building QSpeakers %s...\n' "$target_tag"
(
	cd "$source_dir"
	export PATH="$lrelease_dir:$PATH"
	qmake6 -config release "PREFIX=$target_prefix"
	make -j"$(nproc)"
	make INSTALL_ROOT="$stage_root" install
)

staged_prefix="$stage_root$target_prefix"
staged_binary="$staged_prefix/bin/qspeakers"
[ -x "$staged_binary" ] ||
	fail 'the staged QSpeakers binary is missing'
[ -f "$staged_prefix/share/applications/qspeakers.desktop" ] ||
	fail 'the staged QSpeakers desktop entry is missing'
[ -f "$staged_prefix/share/icons/hicolor/scalable/apps/qspeakers.svg" ] ||
	fail 'the staged QSpeakers icon is missing'
[ -f "$staged_prefix/share/mime/packages/application-x-qspeakers.xml" ] ||
	fail 'the staged QSpeakers MIME definition is missing'
[ -f "$staged_prefix/share/metainfo/rocks.noise.qspeakers.metainfo.xml" ] ||
	fail 'the staged QSpeakers metainfo file is missing'
ldd_output="$(ldd "$staged_binary")" ||
	fail 'ldd could not inspect the staged QSpeakers binary'
if grep -Fq 'not found' <<<"$ldd_output"; then
	fail 'the staged QSpeakers binary has unresolved shared-library dependencies'
fi
desktop-file-validate "$staged_prefix/share/applications/qspeakers.desktop"
printf 'version=%s\ncommit=%s\n' "$target_tag" "$target_commit" \
	>"$staged_prefix/.linuxcapable-managed-version"

mv -- "$staged_prefix" "$target_prefix"
activate_prefix "$target_prefix" "$current_prefix"

printf 'QSpeakers %s is now active.\n' "$target_tag"
printf '%s\n' 'Launch command: qspeakers'
QSP_HELPER

expected_helper_sha256='a0028632a73bbf750d8bbbccc5e984caf6d9e09e9ddf93b421b899adc7a1edcc'
bash -n "$temporary_helper"
read -r actual_helper_sha256 _ < <(sha256sum "$temporary_helper")
if [ "$actual_helper_sha256" != "$expected_helper_sha256" ]; then
  printf 'Error: helper SHA-256 mismatch; copy the complete setup block again.\n' >&2
  exit 1
fi
chmod 0755 "$temporary_helper"
mv -Tf -- "$temporary_helper" "$helper_path"
trap - EXIT HUP INT TERM
printf 'Verified helper SHA-256: %s\n' "$actual_helper_sha256"
printf 'Installed helper: %s\n' "$helper_path"
QSP_SETUP

A successful setup prints the verified SHA-256 value and helper path. Example output:

Verified helper SHA-256: a0028632a73bbf750d8bbbccc5e984caf6d9e09e9ddf93b421b899adc7a1edcc
Installed helper: /home/alex/.local/bin/update-qspeakers-source

If the block reports a checksum mismatch, copy the complete setup block again. If it reports an unmanaged path, inspect that file rather than deleting or overwriting it blindly.

Build and Verify QSpeakers

Add the per-user command directory to the current terminal, prove that the shell finds the helper, and run the first build:

export PATH="$HOME/.local/bin:$PATH"
hash -r
command -v update-qspeakers-source
update-qspeakers-source 1.8.6

The Git clone and compiler produce additional progress lines. Example key status lines for the pinned 1.8.6 installation are:

/home/alex/.local/bin/update-qspeakers-source
Installed version: none
Latest stable tag: 1.8.6
Requested tag: 1.8.6
Cloning QSpeakers tag 1.8.6...
Building QSpeakers 1.8.6...
QSpeakers 1.8.6 is now active.
Launch command: qspeakers

Confirm that the qspeakers command resolves to the managed source build, then inspect its version marker, desktop entry, and linked libraries:

bash <<'QSP_VERIFY'
set -euo pipefail

resolved_qspeakers="$(readlink -f -- "$(command -v qspeakers)")"
printf 'QSpeakers binary: %s\n' "$resolved_qspeakers"
cat "$HOME/.local/opt/qspeakers/current/.linuxcapable-managed-version"
desktop-file-validate "$HOME/.local/share/applications/qspeakers.desktop"
ldd_output="$(ldd "$resolved_qspeakers")"
if grep -F 'not found' <<<"$ldd_output"; then
  echo "QSpeakers has missing shared libraries."
  exit 1
else
  echo "No missing QSpeakers shared libraries found."
fi
QSP_VERIFY

The resolved path should end in /.local/opt/qspeakers/1.8.6/bin/qspeakers, the marker should contain a numeric version and a 40-character commit, desktop validation should remain silent, and the final line should report no missing libraries.

Launch QSpeakers on Ubuntu

QSpeakers is a graphical desktop application, so open it from a logged-in Ubuntu desktop session. APT and the source build use the same friendly terminal command:

qspeakers

The Flatpak package launches through its application ID instead:

flatpak run rocks.noise.qspeakers

For a graphical launch, open Ubuntu’s application search, type QSpeakers, and select the yellow speaker-cone icon. The source method installs the upstream SVG icon and a desktop entry that also supplies the matching running dock icon. You can pin that icon to the Ubuntu Dock, close QSpeakers, and use the retained icon for later launches.

A successful launch opens the main design workspace with driver selection, enclosure tabs, enclosure controls, and a frequency-response plot.

Create a First Enclosure Model

Start with reliable Thiele/Small data from the driver manufacturer or your own measurements. Small errors in values such as Fs, Qts, and Vas can move the calculated response enough to undermine the enclosure comparison.

  1. Select a driver with the vendor and model controls. If the driver is not included, open Speaker > New and enter its measured parameters.
  2. Choose the Sealed enclosure, Ported enclosure, or Bandpass enclosure tab. Each alignment exposes the controls used by that design.
  3. Select Optimize after choosing the driver and enclosure type to calculate a practical starting point, then adjust the enclosure values for the design you are comparing.
  4. Read the response curve and change one value at a time. Treat the plot as a design model rather than a finished cabinet specification.

The three controls that define the first comparison are grouped across the top of the workspace: driver selection, enclosure type, and enclosure volume.

For a ported enclosure, review Vb, Fb, Ql, port count, diameter, and the calculated port length together. Confirm that the driver, port, bracing, wall thickness, and internal clearances physically fit before cutting cabinet panels.

Use Project > Save copy… to keep a reusable .qsp project before changing the alignment or enclosure values. A saved baseline makes side-by-side design comparisons easier to reproduce.

Update QSpeakers on Ubuntu

Update the APT Package

APT updates the Ubuntu package from the same configured repositories. Upgrade only QSpeakers when a newer candidate becomes available:

sudo apt update
sudo apt install --only-upgrade qspeakers

Update the Flatpak Package

Flatpak updates the application and its required runtime through Flathub:

sudo flatpak update rocks.noise.qspeakers

Check and Update the Source Build

Compare the active source-build marker with the latest numeric upstream tag before changing anything:

bash <<'QSP_STATUS'
set -euo pipefail

version_marker="$HOME/.local/opt/qspeakers/current/.linuxcapable-managed-version"
installed_version="$(
  sed -n 's/^version=//p' "$version_marker" 2>/dev/null ||
    true
)"
if ! [[ "$installed_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]]; then
  echo "Could not read the active QSpeakers source version." >&2
  exit 1
fi

tag_listing="$(
  git ls-remote --tags https://github.com/be1/qspeakers.git
)"
latest_version="$(
  printf '%s\n' "$tag_listing" |
    sed -nE 's#^[0-9a-f]{40}[[:space:]]+refs/tags/([0-9]+(\.[0-9]+){1,2})$#\1#p' |
    sort -Vu |
    awk 'NF { latest = $0 } END { print latest }'
)"
if ! [[ "$latest_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]]; then
  echo "Could not resolve the latest numeric upstream tag." >&2
  exit 1
fi

printf 'Installed version: %s\nLatest stable tag: %s\n' \
  "$installed_version" "$latest_version"
QSP_STATUS
Installed version: 1.8.6
Latest stable tag: 1.8.6

Run the helper without sudo when the upstream tag is newer. It builds into a new versioned directory and checks the binary, linked libraries, desktop entry, icon, MIME definition, and metadata before switching current:

update-qspeakers-source

An update from 1.8.5 to 1.8.6 includes these key status lines around the Git and compiler progress:

Installed version: 1.8.5
Latest stable tag: 1.8.6
Cloning QSpeakers tag 1.8.6...
Building QSpeakers 1.8.6...
QSpeakers 1.8.6 is now active.
Launch command: qspeakers

Running the same command when no update exists refreshes the managed desktop integration and returns:

Installed version: 1.8.6
Latest stable tag: 1.8.6
QSpeakers is already current.

A future upstream tag can require different dependencies or qmake6 flags, so review its build notes before applying the update. The helper does not create a timer or background job; run it when you want to check and apply an upstream update. An invalid tag, missing tag, changed tag commit, or failed build stops before the active version changes. If desktop integration fails after a version switch, the helper attempts to restore the previous active version and reports a hard error if restoration cannot complete.

Remove QSpeakers from Ubuntu

Remove the APT Package

Remove the Ubuntu package without deleting project files saved in your home directory:

sudo apt remove qspeakers

Check that the package is no longer installed:

dpkg-query -W -f='${db:Status-Abbrev}\n' qspeakers 2>/dev/null |
  grep -q '^ii ' ||
  echo "QSpeakers is not installed by APT."

Remove the Flatpak Package

Uninstall the system-wide Flatpak application. This removes the application itself but leaves the current account’s sandboxed settings and data available for a later reinstall:

sudo flatpak uninstall rocks.noise.qspeakers

Confirm that the application ID is absent from the system installation:

flatpak list --system --app --columns=application |
  grep -Fx rocks.noise.qspeakers ||
  echo "QSpeakers is not installed by Flatpak."

Delete Optional Flatpak Data

Flatpak can leave per-account settings and app data under ~/.var/app/rocks.noise.qspeakers. Inspect the directory before deciding whether to remove it:

if [ -d "$HOME/.var/app/rocks.noise.qspeakers" ]; then
  du -sh -- "$HOME/.var/app/rocks.noise.qspeakers"
else
  echo "No QSpeakers Flatpak data directory found."
fi

Deleting this directory permanently removes the current account’s QSpeakers Flatpak settings and app data. Back up anything you need first.

rm -rf -- "$HOME/.var/app/rocks.noise.qspeakers"

Remove the Source Build

Close QSpeakers and run the removal block as your regular desktop user. It validates each managed prefix, marker, launcher, icon, and link before removing the source versions and their desktop integration.

This removal is limited to the managed source installation. It does not delete saved .qsp projects, QSpeakers preferences, or shared build dependencies. Keep the dependency packages if other source builds use them.

bash <<'QSP_REMOVE'
#!/usr/bin/env bash
set -euo pipefail

install_root="$HOME/.local/opt/qspeakers"
current_link="$install_root/current"
helper_path="$HOME/.local/bin/update-qspeakers-source"
command_path="$HOME/.local/bin/qspeakers"
desktop_path="$HOME/.local/share/applications/qspeakers.desktop"
icon_path="$HOME/.local/share/icons/hicolor/scalable/apps/qspeakers.svg"
hicolor_root="$HOME/.local/share/icons/hicolor"
hicolor_index="$hicolor_root/index.theme"
hicolor_marker="$hicolor_root/.linuxcapable-created-qspeakers-index-theme"
mime_path="$HOME/.local/share/mime/packages/application-x-qspeakers.xml"
metainfo_path="$HOME/.local/share/metainfo/rocks.noise.qspeakers.metainfo.xml"
root_marker="$install_root/.linuxcapable-managed-source"

fail() {
	printf 'Error: %s\n' "$*" >&2
	exit 1
}

if [ "$(id -u)" -eq 0 ]; then
	fail 'run this removal block as your regular desktop user, not with sudo'
fi
if pgrep -x qspeakers >/dev/null 2>&1; then
	fail 'close QSpeakers before removing the source installation'
fi

if [ -L "$helper_path" ]; then
	fail "$helper_path is a symbolic link; refusing to follow it"
elif [ -e "$helper_path" ]; then
	if [ ! -f "$helper_path" ] ||
		! grep -Fxq '# linuxcapable-managed-qspeakers-source-helper' \
			"$helper_path"; then
		fail "$helper_path is not the managed QSpeakers update helper"
	fi
fi

check_exact_symlink() {
	local link_path="$1"
	local expected_target="$2"

	if [ -L "$link_path" ]; then
		[ "$(readlink -- "$link_path")" = "$expected_target" ] ||
			fail "$link_path points outside the managed QSpeakers source install"
	elif [ -e "$link_path" ]; then
		fail "$link_path is not a managed symbolic link"
	fi
}

check_exact_symlink "$command_path" "$current_link/bin/qspeakers"
check_exact_symlink \
	"$icon_path" \
	"$current_link/share/icons/hicolor/scalable/apps/qspeakers.svg"
check_exact_symlink \
	"$mime_path" \
	"$current_link/share/mime/packages/application-x-qspeakers.xml"
check_exact_symlink \
	"$metainfo_path" \
	"$current_link/share/metainfo/rocks.noise.qspeakers.metainfo.xml"

if [ -L "$desktop_path" ]; then
	fail "$desktop_path is a symbolic link; refusing to follow it"
elif [ -e "$desktop_path" ]; then
	if [ ! -f "$desktop_path" ] ||
		! grep -Fxq 'X-LinuxCapable-Managed=QSpeakersSource' \
			"$desktop_path"; then
		fail "$desktop_path is not a managed QSpeakers desktop entry"
	fi
fi

if [ -L "$hicolor_marker" ]; then
	fail "$hicolor_marker is a symbolic link; refusing to follow it"
elif [ -e "$hicolor_marker" ]; then
	[ -f "$hicolor_marker" ] &&
		[ "$(cat -- "$hicolor_marker")" = \
			'linuxcapable-created-qspeakers-hicolor-index' ] ||
		fail "$hicolor_marker has unexpected content"
fi

if [ -L "$install_root" ]; then
	fail "$install_root is a symbolic link; refusing to follow it"
elif [ -e "$install_root" ]; then
	[ -d "$install_root" ] ||
		fail "$install_root exists but is not a directory"
	[ ! -L "$root_marker" ] &&
		[ -f "$root_marker" ] &&
		[ "$(cat -- "$root_marker")" = \
			'linuxcapable-managed-qspeakers-source-root' ] ||
		fail "$install_root is not marked as a managed source tree"

	while IFS= read -r -d '' entry; do
		case "$entry" in
		"$root_marker") ;;
		"$current_link")
			[ -L "$current_link" ] ||
				fail "$current_link is not a symbolic link"
			current_target="$(readlink -f -- "$current_link" || true)"
			case "$current_target" in
			"$install_root"/[0-9]*.[0-9]*) ;;
			*) fail "$current_link points outside the managed version tree" ;;
			esac
			;;
		*)
			version_name="$(basename -- "$entry")"
			[[ "$version_name" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] ||
				fail "$entry is not a managed numeric QSpeakers version"
			[ ! -L "$entry" ] && [ -d "$entry" ] ||
				fail "$entry is not a managed version directory"
			version_marker="$entry/.linuxcapable-managed-version"
			[ ! -L "$version_marker" ] && [ -f "$version_marker" ] ||
				fail "$entry has no managed version marker"
			if [ "$(awk 'END { print NR }' "$version_marker")" != 2 ] ||
				! grep -Fxq "version=$version_name" "$version_marker" ||
				! grep -Eq '^commit=[0-9a-f]{40}$' "$version_marker"; then
				fail "$entry has an invalid managed version marker"
			fi
			;;
		esac
	done < <(find "$install_root" -mindepth 1 -maxdepth 1 -print0)
fi

rm -f -- \
	"$command_path" \
	"$icon_path" \
	"$mime_path" \
	"$metainfo_path" \
	"$desktop_path" \
	"$helper_path"
if [ -d "$install_root" ]; then
	rm -rf -- "$install_root"
fi

if [ -d "$HOME/.local/share/applications" ]; then
	update-desktop-database "$HOME/.local/share/applications"
fi
if [ -d "$HOME/.local/share/mime" ]; then
	update-mime-database "$HOME/.local/share/mime"
fi
if [ -d "$hicolor_root" ] && [ -f "$hicolor_index" ]; then
	gtk-update-icon-cache -q -f "$hicolor_root"
fi
if [ -e "$hicolor_marker" ]; then
	if ! find "$hicolor_root" -mindepth 1 \
		! -path "$hicolor_index" \
		! -path "$hicolor_marker" \
		! -path "$hicolor_root/icon-theme.cache" \
		! -type d -print -quit | grep -q .; then
		rm -f -- \
			"$hicolor_root/icon-theme.cache" \
			"$hicolor_index" \
			"$hicolor_marker"
		rmdir --ignore-fail-on-non-empty \
			"$hicolor_root/scalable/apps" \
			"$hicolor_root/scalable" \
			"$hicolor_root" 2>/dev/null || true
	else
		rm -f -- "$hicolor_marker"
	fi
fi

printf '%s\n' \
	'Removed the managed QSpeakers source install.' \
	'QSpeakers projects and preferences were not changed.'
QSP_REMOVE
Removed the managed QSpeakers source install.
QSpeakers projects and preferences were not changed.

Troubleshoot QSpeakers on Ubuntu

APT Cannot Find the QSpeakers Package

Check whether APT has a package candidate before changing repository settings:

apt-cache policy qspeakers

If the output contains Candidate: (none), Universe may be disabled on a minimal or customised installation. Enable the component, refresh metadata, and check the candidate again:

sudo apt install software-properties-common
sudo add-apt-repository --yes universe
sudo apt update
apt-cache policy qspeakers

A versioned Candidate line means APT can install the package. The Ubuntu Universe repository guide explains the component and how to verify it on customised systems.

Flathub Is Missing or Disabled

Inspect the system-wide Flatpak remotes, including disabled entries:

flatpak remotes --system --show-disabled --columns=name,options |
  grep -E '^flathub[[:space:]]'

If the Flathub row contains disabled, re-enable the existing system remote:

sudo flatpak remote-modify --enable flathub

If the check returns no Flathub row, add the remote and retry the QSpeakers installation:

sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo

The Source Helper Is Not Found

If a terminal opened before the helper was created cannot find the command, refresh that shell’s per-user path and command cache:

export PATH="$HOME/.local/bin:$PATH"
hash -r
command -v update-qspeakers-source
command -v qspeakers

Both checks should return paths under ~/.local/bin. If the helper remains absent, repeat the helper-creation block; its ownership guard will refresh only the managed helper created during source setup.

The Source Build Reports a Missing Dependency

Inspect the Qt build tools, OpenGL header, translation compiler, and release-specific Charts package before retrying the dependency block:

bash <<'QSP_DEP_CHECK'
set -euo pipefail

if apt-cache show qt6-charts-dev >/dev/null 2>&1; then
  charts_package=qt6-charts-dev
else
  charts_package=libqt6charts6-dev
fi

for package_name in \
  build-essential \
  git \
  libgl-dev \
  libgtk-3-bin \
  qmake6 \
  qt6-base-dev \
  "$charts_package" \
  qt6-l10n-tools \
  qt6-tools-dev-tools \
  desktop-file-utils \
  shared-mime-info; do
  package_state="$(
    dpkg-query -W -f='${db:Status-Abbrev}' "$package_name" 2>/dev/null ||
      true
  )"
  if [[ "$package_state" == ii* ]]; then
    printf 'Installed: %s\n' "$package_name"
  else
    printf 'Missing: %s\n' "$package_name"
  fi
done
QSP_DEP_CHECK

Every dependency should print an Installed line. Ubuntu 22.04 selects libqt6charts6-dev, while Ubuntu 24.04 and 26.04 select qt6-charts-dev. Re-run the dependency installation block if any package prints as Missing, then run update-qspeakers-source again.

The Source Helper Refuses an Existing Path or Tag

The helper stops when its install root, command link, desktop entry, icon, MIME file, metadata link, or update-helper path is not marked as belonging to the managed source installation. It also stops if an installed tag now resolves to a different upstream commit. These failures protect an unrelated installation and the last working build; inspect the reported path or review the upstream tag before changing anything. Do not bypass the guard with a broad recursive deletion.

The Launcher Does Not Appear

First start QSpeakers with the terminal command for the installed method. Source-build users can pass the active tag to the helper so it refreshes the desktop entry, application database, MIME database, and icon cache without selecting a newer upstream release:

bash <<'QSP_REFRESH'
set -euo pipefail

current_tag="$(
  sed -n 's/^version=//p' \
    "$HOME/.local/opt/qspeakers/current/.linuxcapable-managed-version" \
    2>/dev/null ||
    true
)"
if ! [[ "$current_tag" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]]; then
  echo "Could not read the active QSpeakers source tag." >&2
  exit 1
fi
update-qspeakers-source "$current_tag"
QSP_REFRESH

APT users can verify /usr/share/applications/qspeakers.desktop with dpkg -L qspeakers, while Flatpak users can confirm the application with flatpak info --system rocks.noise.qspeakers. If the correct terminal launch works but application search still lacks the launcher, sign out and back in once so the desktop session reloads its application index. Installing another method is not a launcher repair and usually creates a second update path.

Conclusion

QSpeakers is ready to turn reliable driver data into repeatable enclosure comparisons. Save a .qsp baseline, change one variable at a time, and keep updates with the installation method you selected. Before cutting material, confirm driver and port clearance, wall thickness, bracing, and other physical limits the response plot cannot model.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: