From ae33ffed5b7be1973a910062a5a677fb6347a01e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:10:04 +0000
Subject: [PATCH 01/14] Initial plan
From b615e8e4548659ae5c47bf70c486c43079d36668 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:32:01 +0000
Subject: [PATCH 02/14] feat: add Linux multi-arch AppImage build workflow and
docs
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/4896c4af-2769-44a6-84bc-0a89aac0a078
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 449 ++++++++++++++++++++++++++++++
README.md | 7 +
docs/LINUX_BUILDS.md | 112 ++++++++
platforms/linux/CMakeLists.txt | 16 +-
4 files changed, 582 insertions(+), 2 deletions(-)
create mode 100644 .github/workflows/linux-build.yml
create mode 100644 docs/LINUX_BUILDS.md
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
new file mode 100644
index 0000000000..92f2947b90
--- /dev/null
+++ b/.github/workflows/linux-build.yml
@@ -0,0 +1,449 @@
+name: Linux Build
+
+# Produces self-contained AppImages for QLC+ (qlcplus-qml / v5) on:
+# • x86_64 — native GitHub runner, Qt installed via jurplel/install-qt-action
+# • aarch64 — GitHub-hosted ARM64 runner (ubuntu-22.04-arm), system Qt 6 from apt
+#
+# armv7 is NOT built automatically: GitHub has no armv7 runners, Qt 6 for armv7
+# is only available as Qt 6.2.x via apt on Ubuntu 22.04, and full QEMU emulation
+# of a Qt 6 build takes 45–90 minutes which is impractical for CI. If you need
+# an armv7 artifact, set the `build_armv7` workflow_dispatch input to true; the
+# job will run but may time out on free-tier runners.
+
+on:
+ workflow_dispatch:
+ inputs:
+ build_armv7:
+ description: 'Also attempt armv7 (slow QEMU cross-build, may time-out)'
+ type: boolean
+ default: false
+
+ push:
+ tags:
+ - 'v*'
+
+ pull_request:
+ paths:
+ - '**.cpp'
+ - '**.h'
+ - '**.qml'
+ - '**/CMakeLists.txt'
+ - 'variables.cmake'
+ - 'coverage.cmake'
+ - '.github/workflows/linux-build.yml'
+ - 'platforms/linux/**'
+
+# Cancel in-progress runs for the same ref on PRs so we don't waste runners.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ # ---------------------------------------------------------------------------
+ # Main matrix build: x86_64 (always) + aarch64 (skipped on PRs for speed)
+ # ---------------------------------------------------------------------------
+ build:
+ name: Build (${{ matrix.arch }})
+ runs-on: ${{ matrix.runner }}
+
+ # On PRs only build x86_64 to keep CI fast.
+ # skip_on_pr=true causes the job to be skipped when triggered by a PR.
+ if: ${{ !matrix.skip_on_pr || github.event_name != 'pull_request' }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ # ── x86_64 ──────────────────────────────────────────────────────────
+ # Native GitHub runner; Qt installed via jurplel/install-qt-action so
+ # we get a modern, self-contained Qt build and all libraries are in a
+ # known location.
+ - arch: x86_64
+ runner: ubuntu-22.04
+ qt_method: action
+ qt_version: '6.9.3'
+ linuxdeploy_url: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+ linuxdeploy_qt_url: https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
+ appimagetool_url: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
+ appimage_arch: x86_64
+ run_tests: true
+ skip_on_pr: false
+
+ # ── aarch64 ─────────────────────────────────────────────────────────
+ # GitHub-hosted ARM64 runner (ubuntu-22.04-arm). jurplel/install-qt-
+ # action does not ship Linux-ARM desktop Qt prebuilts, so we use the
+ # system Qt 6 packages from Ubuntu 22.04's apt repositories instead.
+ # If this runner label is unavailable for your fork, replace it with
+ # "ubuntu-22.04" and add QEMU emulation via docker/setup-qemu-action.
+ - arch: aarch64
+ runner: ubuntu-22.04-arm
+ qt_method: apt
+ linuxdeploy_url: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
+ linuxdeploy_qt_url: https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-aarch64.AppImage
+ appimagetool_url: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage
+ appimage_arch: aarch64
+ run_tests: false
+ skip_on_pr: true
+
+ defaults:
+ run:
+ shell: bash
+
+ env:
+ # Make AppImage tools extract and run without FUSE (safer in CI)
+ APPIMAGE_EXTRACT_AND_RUN: "1"
+
+ steps:
+ # ── Checkout ────────────────────────────────────────────────────────────
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+ fetch-depth: 0
+
+ # ── System dependencies ─────────────────────────────────────────────────
+ - name: Install system dependencies
+ run: |
+ set -euxo pipefail
+ sudo apt-get update -q
+ sudo apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ ninja-build \
+ pkg-config \
+ libusb-1.0-0-dev \
+ libftdi1-dev \
+ libasound2-dev \
+ libudev-dev \
+ libfftw3-dev \
+ libmad0-dev \
+ libsndfile1-dev \
+ liblo-dev \
+ libpulse-dev \
+ libfuse2 \
+ patchelf \
+ shared-mime-info \
+ wget \
+ file
+
+ # ── Qt (x86_64): official prebuilts via jurplel/install-qt-action ───────
+ - name: Install Qt via install-qt-action (x86_64)
+ if: ${{ matrix.qt_method == 'action' }}
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: ${{ matrix.qt_version }}
+ cache: true
+ modules: 'qt3d qtimageformats qtmultimedia qtserialport qtwebsockets'
+
+ # ── Qt (aarch64): system packages from Ubuntu apt ────────────────────────
+ - name: Install Qt via apt (aarch64)
+ if: ${{ matrix.qt_method == 'apt' }}
+ run: |
+ set -euxo pipefail
+ sudo apt-get install -y --no-install-recommends \
+ qt6-base-dev \
+ qt6-base-private-dev \
+ qt6-declarative-dev \
+ qt6-declarative-private-dev \
+ qt6-multimedia-dev \
+ qt6-websockets-dev \
+ qt6-svg-dev \
+ qt6-3d-dev \
+ qt6-serialport-dev \
+ qt6-tools-dev \
+ qt6-l10n-tools \
+ qt6-image-formats-plugins \
+ libqt6opengl6-dev \
+ qml6-module-qtquick \
+ qml6-module-qtquick-controls \
+ qml6-module-qtquick-layouts \
+ qml6-module-qtquick-window \
+ qml6-module-qtmultimedia \
+ libqt6multimedia6-plugins \
+ libgl1-mesa-dev
+
+ # ── Build directory cache ─────────────────────────────────────────────
+ - name: Cache CMake build directory
+ uses: actions/cache@v4
+ with:
+ path: build
+ key: ${{ runner.os }}-${{ matrix.arch }}-cmake-${{ hashFiles('**/CMakeLists.txt', 'variables.cmake') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.arch }}-cmake-
+
+ # ── Environment ────────────────────────────────────────────────────────
+ - name: Set build environment variables
+ run: |
+ set -euxo pipefail
+ # APPVERSION from variables.cmake (qmlui variant, e.g. "5.2.2 GIT")
+ APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake \
+ | tr ' ' '-')
+ GIT_REV=$(git rev-parse --short HEAD)
+ echo "APPVERSION=${APPVERSION}-${GIT_REV}" >> "$GITHUB_ENV"
+ echo "GIT_REV=${GIT_REV}" >> "$GITHUB_ENV"
+ echo "INSTALL_ROOT=$(pwd)/AppDir" >> "$GITHUB_ENV"
+ # Find qmake6 (needed by linuxdeploy-plugin-qt)
+ if command -v qmake6 &>/dev/null; then
+ echo "QMAKE=$(command -v qmake6)" >> "$GITHUB_ENV"
+ elif [ -n "${QTDIR:-}" ] && [ -x "$QTDIR/bin/qmake6" ]; then
+ echo "QMAKE=$QTDIR/bin/qmake6" >> "$GITHUB_ENV"
+ elif [ -n "${QTDIR:-}" ] && [ -x "$QTDIR/bin/qmake" ]; then
+ echo "QMAKE=$QTDIR/bin/qmake" >> "$GITHUB_ENV"
+ else
+ echo "QMAKE=$(command -v qmake)" >> "$GITHUB_ENV"
+ fi
+
+ # ── CMake configure ───────────────────────────────────────────────────
+ - name: Configure CMake
+ run: |
+ set -euxo pipefail
+ CMAKE_EXTRA_ARGS=()
+ # For install-qt-action builds, point CMake at the custom Qt prefix
+ if [ "${{ matrix.qt_method }}" = "action" ] && [ -n "${QTDIR:-}" ]; then
+ CMAKE_EXTRA_ARGS+=("-DCMAKE_PREFIX_PATH=${QTDIR}/lib/cmake")
+ fi
+ cmake -S . -B build -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -Dqmlui=ON \
+ -Dmcp_server=ON \
+ -Dappimage=ON \
+ "-DINSTALL_ROOT=${INSTALL_ROOT}" \
+ "${CMAKE_EXTRA_ARGS[@]}"
+
+ # ── Build ──────────────────────────────────────────────────────────────
+ - name: Build
+ run: |
+ set -euxo pipefail
+ cmake --build build -j$(nproc)
+
+ # ── Tests (x86_64 only) ────────────────────────────────────────────────
+ - name: Run unit tests
+ if: ${{ matrix.run_tests }}
+ run: |
+ set -euxo pipefail
+ build/mcp/test/mcp_vc_query_filter_test
+ build/mcp/test/mcp_vc_validation_test
+
+ # ── Install to AppDir ──────────────────────────────────────────────────
+ # cmake --install copies the binary, data files, qlcplus I/O plugins,
+ # desktop file, icons, and (where available) Qt libraries/plugins.
+ # The OPTIONAL keyword on Qt-specific installs in platforms/linux/
+ # CMakeLists.txt ensures that aarch64 system-Qt builds don't fail when
+ # Qt libraries land in architecture-specific paths not known to cmake.
+ - name: Install to AppDir
+ run: |
+ set -euxo pipefail
+ cmake --install build
+
+ # ── Download and extract AppImage tooling ──────────────────────────────
+ - name: Download linuxdeploy and plugin-qt
+ run: |
+ set -euxo pipefail
+ wget -q "${{ matrix.linuxdeploy_url }}" -O linuxdeploy.AppImage
+ wget -q "${{ matrix.linuxdeploy_qt_url }}" -O linuxdeploy-plugin-qt.AppImage
+ wget -q "${{ matrix.appimagetool_url }}" -O appimagetool.AppImage
+ chmod +x linuxdeploy.AppImage linuxdeploy-plugin-qt.AppImage appimagetool.AppImage
+
+ # ── Bundle Qt libraries, plugins, and QML modules via linuxdeploy ──────
+ # linuxdeploy uses ldd to find all shared-library dependencies and copies
+ # them to AppDir/usr/lib. linuxdeploy-plugin-qt additionally copies Qt
+ # platform plugins, image-format plugins, QML imports, and creates a
+ # qt.conf so Qt finds its plugins at runtime inside the AppImage.
+ - name: Bundle dependencies with linuxdeploy
+ env:
+ QMAKE: ${{ env.QMAKE }}
+ QML_SOURCES_PATHS: ${{ github.workspace }}/qmlui
+ run: |
+ set -euxo pipefail
+ ./linuxdeploy.AppImage \
+ --appdir AppDir \
+ --executable AppDir/usr/bin/qlcplus-qml \
+ --desktop-file platforms/linux/qlcplus5.desktop \
+ --icon-file resources/icons/png/qlcplus.png \
+ --plugin qt \
+ --verbosity 1
+
+ # ── Create custom AppRun ───────────────────────────────────────────────
+ # QLC+ compiles data paths as relative strings (e.g. "../share/qlcplus")
+ # that it resolves against the process CWD at runtime. The -Dappimage=ON
+ # CMake option installs data under AppDir/share/ and plugins under
+ # AppDir/lib/, both one level above the binary dir (AppDir/usr/bin/).
+ # Setting CWD to AppDir/usr before exec makes those "../..." paths resolve
+ # correctly inside the AppImage mount.
+ - name: Write custom AppRun
+ run: |
+ set -euxo pipefail
+ cat > AppDir/AppRun << 'APPRUN_EOF'
+ #!/bin/bash
+ set -e
+ HERE="$(dirname "$(readlink -f "${0}")")"
+ export APPDIR="$HERE"
+
+ # Qt platform plugin and QML import paths set up by linuxdeploy-plugin-qt
+ export QT_PLUGIN_PATH="${HERE}/usr/plugins:${QT_PLUGIN_PATH:-}"
+ export QML2_IMPORT_PATH="${HERE}/usr/qml:${QML2_IMPORT_PATH:-}"
+ export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH:-}"
+
+ # Change CWD to AppDir/usr so that relative data paths compiled into
+ # qlcconfig.h (e.g. "../share/qlcplus/fixtures") resolve correctly.
+ cd "${HERE}/usr"
+
+ exec "${HERE}/usr/bin/qlcplus-qml" "$@"
+ APPRUN_EOF
+ # Remove leading whitespace that heredoc indentation introduced
+ sed -i 's/^[[:space:]]*//' AppDir/AppRun
+ chmod +x AppDir/AppRun
+
+ # ── Pack into AppImage ─────────────────────────────────────────────────
+ - name: Build AppImage
+ env:
+ VERSION: ${{ env.APPVERSION }}
+ run: |
+ set -euxo pipefail
+ OUTPUT="QLC+-${APPVERSION}-${{ matrix.appimage_arch }}.AppImage"
+ ./appimagetool.AppImage -v AppDir "$OUTPUT"
+ echo "APPIMAGE_PATH=${OUTPUT}" >> "$GITHUB_ENV"
+
+ # ── SHA-256 checksum ──────────────────────────────────────────────────
+ - name: Generate SHA256 checksum
+ run: |
+ set -euxo pipefail
+ sha256sum "${APPIMAGE_PATH}" > "${APPIMAGE_PATH}.sha256"
+
+ # ── Upload as workflow artifact ────────────────────────────────────────
+ - name: Upload AppImage artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: QLC+-${{ env.APPVERSION }}-${{ matrix.appimage_arch }}
+ path: |
+ ${{ env.APPIMAGE_PATH }}
+ ${{ env.APPIMAGE_PATH }}.sha256
+ if-no-files-found: error
+
+ # ── Attach to GitHub Release on tag push ──────────────────────────────
+ - name: Attach to GitHub Release
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ ${{ env.APPIMAGE_PATH }}
+ ${{ env.APPIMAGE_PATH }}.sha256
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # ---------------------------------------------------------------------------
+ # armv7 — optional, workflow_dispatch only
+ # ---------------------------------------------------------------------------
+ # armv7 is gated behind a manual input because:
+ # 1. GitHub does not offer native armv7 (32-bit ARM) hosted runners.
+ # 2. QEMU emulation of a full Qt 6 CMake build takes 45–90 minutes,
+ # which exceeds typical free-tier job time limits.
+ # 3. Qt 6 for Linux/armhf is only available as Qt 6.2.x via Ubuntu 22.04
+ # apt (no official Qt prebuilts for Linux armhf desktop).
+ # If you need armv7 artifacts, consider a dedicated self-hosted armhf runner
+ # or a Raspberry Pi build farm instead.
+ build-armv7:
+ name: Build (armv7) [manual only]
+ runs-on: ubuntu-22.04
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.build_armv7 == true }}
+
+ defaults:
+ run:
+ shell: bash
+
+ env:
+ APPIMAGE_EXTRACT_AND_RUN: "1"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+ fetch-depth: 0
+
+ - name: Set up QEMU for armv7
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm
+
+ - name: Build inside armv7 Docker container
+ uses: addnab/docker-run-action@v3
+ with:
+ image: arm32v7/ubuntu:22.04
+ options: >-
+ --platform linux/arm/v7
+ -v ${{ github.workspace }}:/workspace
+ -e DEBIAN_FRONTEND=noninteractive
+ run: |
+ set -euxo pipefail
+ apt-get update -q
+ apt-get install -y --no-install-recommends \
+ build-essential cmake ninja-build pkg-config \
+ qt6-base-dev qt6-base-private-dev \
+ qt6-declarative-dev qt6-declarative-private-dev \
+ qt6-multimedia-dev qt6-websockets-dev qt6-svg-dev \
+ qt6-3d-dev qt6-tools-dev qt6-l10n-tools \
+ libusb-1.0-0-dev libftdi1-dev libasound2-dev libudev-dev \
+ libfftw3-dev libmad0-dev libsndfile1-dev liblo-dev libpulse-dev \
+ patchelf wget file git
+ cd /workspace
+ APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake | tr ' ' '-')
+ GIT_REV=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+ APPVERSION="${APPVERSION}-${GIT_REV}"
+ cmake -S . -B build -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -Dqmlui=ON \
+ -Dmcp_server=ON \
+ -Dappimage=ON \
+ "-DINSTALL_ROOT=/workspace/AppDir-armv7"
+ cmake --build build -j$(nproc)
+ cmake --install build
+ # Package as tarball (no armhf AppImage tools available)
+ tar -czf "QLC+-${APPVERSION}-armv7.tar.gz" -C /workspace AppDir-armv7
+ sha256sum "QLC+-${APPVERSION}-armv7.tar.gz" > "QLC+-${APPVERSION}-armv7.tar.gz.sha256"
+ echo "APPVERSION=${APPVERSION}" >> /workspace/.armv7_env
+
+ - name: Read armv7 env
+ run: |
+ set -euxo pipefail
+ # shellcheck disable=SC1091
+ source .armv7_env
+ echo "APPVERSION=${APPVERSION}" >> "$GITHUB_ENV"
+ echo "TARBALL=QLC+-${APPVERSION}-armv7.tar.gz" >> "$GITHUB_ENV"
+
+ - name: Upload armv7 tarball artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: QLC+-${{ env.APPVERSION }}-armv7
+ path: |
+ ${{ env.TARBALL }}
+ ${{ env.TARBALL }}.sha256
+ if-no-files-found: error
+
+ - name: Attach armv7 tarball to GitHub Release
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ ${{ env.TARBALL }}
+ ${{ env.TARBALL }}.sha256
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # ---------------------------------------------------------------------------
+ # Aggregation job — branch protection can require this single check
+ # ---------------------------------------------------------------------------
+ ci-success:
+ name: CI Success
+ runs-on: ubuntu-22.04
+ needs: [build]
+ if: always()
+ steps:
+ - name: Check required build jobs
+ run: |
+ if [[ "${{ needs.build.result }}" == "failure" ]]; then
+ echo "❌ One or more required build jobs failed."
+ exit 1
+ fi
+ echo "✅ All required build jobs succeeded (result: ${{ needs.build.result }})."
diff --git a/README.md b/README.md
index 4bf77b43a7..486a768e2f 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,13 @@
Q Light Controller+
+## Linux Builds (AppImage)
+
+Pre-built, self-contained AppImages for **x86\_64** and **aarch64** are
+attached to every [tagged release](../../releases). See
+[docs/LINUX_BUILDS.md](docs/LINUX_BUILDS.md) for download, verification, and
+run instructions.
+
> ## ⚠️ EXPERIMENTAL FORK — USE AT YOUR OWN RISK ⚠️
>
> **This fork was largely vibe-coded with AI pair-programming.**
diff --git a/docs/LINUX_BUILDS.md b/docs/LINUX_BUILDS.md
new file mode 100644
index 0000000000..33be4ae864
--- /dev/null
+++ b/docs/LINUX_BUILDS.md
@@ -0,0 +1,112 @@
+# Linux Builds — AppImage Downloads
+
+QLC+ (the QML/v5 variant, `qlcplus-qml`) is built automatically as a
+self-contained **AppImage** for multiple Linux architectures on every tagged
+release.
+
+## Supported Architectures
+
+| Architecture | Description |
+|---|---|
+| `x86_64` | Intel / AMD 64-bit (most desktop and server Linux) |
+| `aarch64` | ARMv8 64-bit (Raspberry Pi 4/5 in 64-bit mode, Ampere, Apple Silicon under Rosetta/Asahi, etc.) |
+| `armv7` | ARMv7 hard-float *(manual dispatch only — see notes below)* |
+
+## Downloading an AppImage
+
+1. Go to the [**Releases** page](../../releases) of this repository.
+2. Expand the **Assets** section of the latest release.
+3. Download the AppImage that matches your architecture, for example:
+ - `QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage`
+ - `QLC+-5.2.2-GIT-abcdef1-aarch64.AppImage`
+4. Optionally download the matching `.sha256` file to verify integrity.
+
+## Verifying the Download
+
+```bash
+sha256sum --check QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage.sha256
+```
+
+## Running the AppImage
+
+```bash
+# Make it executable (only needed once)
+chmod +x QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage
+
+# Run it
+./QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage
+```
+
+### Without FUSE (fallback)
+
+AppImages normally require FUSE to mount themselves. If FUSE is unavailable
+(e.g. inside a container or on a stripped-down server image), you can still run
+the AppImage by extracting it first:
+
+```bash
+# Extract the AppImage into a folder called "squashfs-root"
+./QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage --appimage-extract
+
+# Run the extracted binary
+./squashfs-root/AppRun
+```
+
+Or set the environment variable before running:
+
+```bash
+APPIMAGE_EXTRACT_AND_RUN=1 ./QLC+-5.2.2-GIT-abcdef1-x86_64.AppImage
+```
+
+## Runtime Requirements
+
+| Requirement | Minimum version |
+|---|---|
+| Linux kernel | 3.10+ (5.x+ recommended) |
+| glibc | 2.35 (Ubuntu 22.04 baseline) |
+| FUSE 2 | Required for normal AppImage execution (`libfuse2` on Debian/Ubuntu) — or use `--appimage-extract-and-run` |
+| Display | X11 or Wayland (via XWayland) |
+
+No Qt installation is required — all Qt libraries and QML modules are bundled
+inside the AppImage.
+
+## Installing FUSE 2 (if needed)
+
+```bash
+# Debian / Ubuntu
+sudo apt-get install libfuse2
+
+# Fedora / RHEL
+sudo dnf install fuse-libs
+
+# Arch Linux
+sudo pacman -S fuse2
+```
+
+## Notes on armv7
+
+armv7 (32-bit ARM) builds are gated behind a manual `workflow_dispatch` trigger
+because:
+
+- GitHub does not offer hosted armv7 runners.
+- Qt 6 for Linux armv7 is only available as Qt 6.2.x (Ubuntu 22.04 apt).
+- Full QEMU emulation of the build takes 45–90 minutes, exceeding typical
+ free-tier CI time limits.
+
+If you need armv7 packages, consider building on a native Raspberry Pi or
+self-hosted ARM runner. armv7 artifacts are distributed as a `.tar.gz`
+tarball rather than an AppImage (no `linuxdeploy` armhf AppImage tool is
+publicly available).
+
+## Build System Details
+
+The CI workflow lives at
+[`.github/workflows/linux-build.yml`](../.github/workflows/linux-build.yml).
+
+Key choices:
+
+| Aspect | x86_64 | aarch64 |
+|---|---|---|
+| Runner | `ubuntu-22.04` | `ubuntu-22.04-arm` |
+| Qt installation | `jurplel/install-qt-action` (Qt 6.9.3) | System apt Qt 6 |
+| Library bundling | `linuxdeploy` + `linuxdeploy-plugin-qt` | `linuxdeploy` + `linuxdeploy-plugin-qt` |
+| Output | AppImage | AppImage |
diff --git a/platforms/linux/CMakeLists.txt b/platforms/linux/CMakeLists.txt
index 8d4a1fd6c1..364567bd0a 100644
--- a/platforms/linux/CMakeLists.txt
+++ b/platforms/linux/CMakeLists.txt
@@ -155,22 +155,28 @@ if(appimage)
set(PLUGINS_DESTINATION ${INSTALLROOT}/${LIBSDIR}/qt5/plugins)
endif()
+ # OPTIONAL on all Qt plugin installs so that builds using system Qt on non-x86_64
+ # architectures (where paths may differ) do not fail — linuxdeploy handles
+ # bundling the missing Qt plugins in those cases.
install(
FILES
${QT_PLUGINS_PATH}/platforms/libqlinuxfb.so
${QT_PLUGINS_PATH}/platforms/libqxcb.so
${QT_PLUGINS_PATH}/platforms/libqminimal.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/platforms
)
install(
FILES ${QT_PLUGINS_PATH}/xcbglintegrations/libqxcb-glx-integration.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/xcbglintegrations
)
if (QT_VERSION_MAJOR GREATER 5)
install(
FILES ${QT_PLUGINS_PATH}/multimedia/libffmpegmediaplugin.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/multimedia
)
get_filename_component(LIBAVFORMAT_REAL "${QT_LIBS_PATH}/libavformat.so" REALPATH)
@@ -179,6 +185,7 @@ if(appimage)
${QT_LIBS_PATH}/libavformat.so
${QT_LIBS_PATH}/libavformat.so.61
${LIBAVFORMAT_REAL}
+ OPTIONAL
DESTINATION ${INSTALLROOT}/${LIBSDIR}
)
else()
@@ -186,6 +193,7 @@ if(appimage)
FILES
${QT_PLUGINS_PATH}/audio/libqtaudio_alsa.so
${QT_PLUGINS_PATH}/audio/libqtmedia_pulse.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/audio
)
@@ -193,6 +201,7 @@ if(appimage)
FILES
${QT_PLUGINS_PATH}/mediaservice/libgstaudiodecoder.so
${QT_PLUGINS_PATH}/mediaservice/libgstmediaplayer.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/mediaservice
)
endif()
@@ -200,6 +209,7 @@ if(appimage)
install(
FILES
${QT_PLUGINS_PATH}/imageformats/libqsvg.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/imageformats
)
@@ -207,6 +217,7 @@ if(appimage)
install(
FILES
${QT_PLUGINS_PATH}/printsupport/libcupsprintersupport.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/printsupport
)
@@ -219,11 +230,12 @@ if(appimage)
install(
FILES
${QT_PLUGINS_PATH}/geometryloaders/libdefaultgeometryloader.so
+ OPTIONAL
DESTINATION ${PLUGINS_DESTINATION}/geometryloaders
)
if(QT_VERSION VERSION_GREATER_EQUAL 5.15.0)
- install(FILES ${QT_PLUGINS_PATH}/renderers/libopenglrenderer.so DESTINATION ${PLUGINS_DESTINATION}/renderers)
+ install(FILES ${QT_PLUGINS_PATH}/renderers/libopenglrenderer.so OPTIONAL DESTINATION ${PLUGINS_DESTINATION}/renderers)
endif()
set(qmldeps_files
@@ -233,7 +245,7 @@ if(appimage)
${QT_QML_PATH}/Qt3D
${QT_QML_PATH}/QtMultimedia
)
- install(DIRECTORY ${qmldeps_files} DESTINATION ${INSTALLROOT}/bin)
+ install(DIRECTORY ${qmldeps_files} OPTIONAL DESTINATION ${INSTALLROOT}/bin)
endif()
From 8ce14f76ec795b96b7a6e78dbb5cff6595315809 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:34:51 +0000
Subject: [PATCH 03/14] fix: add explicit permissions blocks and remove
unnecessary shellcheck comment
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/4896c4af-2769-44a6-84bc-0a89aac0a078
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 92f2947b90..ac4f5a8490 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -38,6 +38,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+# Default to read-only token; individual jobs that publish releases override.
+permissions:
+ contents: read
+
jobs:
# ---------------------------------------------------------------------------
# Main matrix build: x86_64 (always) + aarch64 (skipped on PRs for speed)
@@ -45,6 +49,8 @@ jobs:
build:
name: Build (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
+ permissions:
+ contents: write # needed by softprops/action-gh-release to upload AppImages
# On PRs only build x86_64 to keep CI fast.
# skip_on_pr=true causes the job to be skipped when triggered by a PR.
@@ -347,6 +353,8 @@ jobs:
name: Build (armv7) [manual only]
runs-on: ubuntu-22.04
if: ${{ github.event_name == 'workflow_dispatch' && inputs.build_armv7 == true }}
+ permissions:
+ contents: write # needed by softprops/action-gh-release to upload tarballs
defaults:
run:
@@ -407,7 +415,6 @@ jobs:
- name: Read armv7 env
run: |
set -euxo pipefail
- # shellcheck disable=SC1091
source .armv7_env
echo "APPVERSION=${APPVERSION}" >> "$GITHUB_ENV"
echo "TARBALL=QLC+-${APPVERSION}-armv7.tar.gz" >> "$GITHUB_ENV"
@@ -439,6 +446,8 @@ jobs:
runs-on: ubuntu-22.04
needs: [build]
if: always()
+ permissions:
+ contents: read
steps:
- name: Check required build jobs
run: |
From 6211e9d5a32d18324f9c79d9dc9ee2f4cf83e8aa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:39:28 +0000
Subject: [PATCH 04/14] fix: use foreach+EXISTS for QML dir install to support
cmake 3.16+
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/4896c4af-2769-44a6-84bc-0a89aac0a078
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
platforms/linux/CMakeLists.txt | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/platforms/linux/CMakeLists.txt b/platforms/linux/CMakeLists.txt
index 364567bd0a..6a257aff19 100644
--- a/platforms/linux/CMakeLists.txt
+++ b/platforms/linux/CMakeLists.txt
@@ -245,7 +245,13 @@ if(appimage)
${QT_QML_PATH}/Qt3D
${QT_QML_PATH}/QtMultimedia
)
- install(DIRECTORY ${qmldeps_files} OPTIONAL DESTINATION ${INSTALLROOT}/bin)
+ # Use EXISTS check instead of OPTIONAL (OPTIONAL on DIRECTORY requires CMake 3.22,
+ # but cmake_minimum_required is 3.16; CI runners have 3.22+ but protect older builds)
+ foreach(_qml_dir ${qmldeps_files})
+ if(EXISTS "${_qml_dir}")
+ install(DIRECTORY "${_qml_dir}" DESTINATION ${INSTALLROOT}/bin)
+ endif()
+ endforeach()
endif()
From 9106647e38e342c8e65a52aef8f4161e429a6aae Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:44:16 +0000
Subject: [PATCH 05/14] ci: add temporary branch push trigger to verify
workflow runs
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/cd1b2937-1365-4589-a711-370f517fb912
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index ac4f5a8490..26b01699c5 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -21,6 +21,10 @@ on:
push:
tags:
- 'v*'
+ # Temporary: also run on the CI development branch so we can verify this
+ # workflow before it is merged. Remove after first successful run.
+ branches:
+ - 'copilot/add-github-actions-workflow-linux-builds'
pull_request:
paths:
From 6e7960d799379b8009444ede38f3769631832ea1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:50:35 +0000
Subject: [PATCH 06/14] fix: rewrite workflow as separate arch jobs to fix
matrix context error
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/cd1b2937-1365-4589-a711-370f517fb912
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 384 +++++++++++++++++++-----------
1 file changed, 241 insertions(+), 143 deletions(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 26b01699c5..8003b9f924 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -46,72 +46,38 @@ concurrency:
permissions:
contents: read
+# ---------------------------------------------------------------------------
+# Shared setup steps are inlined per job because GitHub Actions does not
+# allow the `matrix` context in job-level `if` conditions, so we cannot use
+# a single matrix entry with a per-row skip flag. Two separate jobs keep
+# the conditional logic simple and explicit.
+# ---------------------------------------------------------------------------
+
jobs:
- # ---------------------------------------------------------------------------
- # Main matrix build: x86_64 (always) + aarch64 (skipped on PRs for speed)
- # ---------------------------------------------------------------------------
- build:
- name: Build (${{ matrix.arch }})
- runs-on: ${{ matrix.runner }}
+ # ── x86_64 ──────────────────────────────────────────────────────────────────
+ # Runs on every trigger (PR, tag, dispatch, branch push).
+ # Qt installed via jurplel/install-qt-action (prebuilt binaries, known paths).
+ # Unit tests executed here.
+ build-x86_64:
+ name: Build (x86_64)
+ runs-on: ubuntu-22.04
permissions:
- contents: write # needed by softprops/action-gh-release to upload AppImages
-
- # On PRs only build x86_64 to keep CI fast.
- # skip_on_pr=true causes the job to be skipped when triggered by a PR.
- if: ${{ !matrix.skip_on_pr || github.event_name != 'pull_request' }}
-
- strategy:
- fail-fast: false
- matrix:
- include:
- # ── x86_64 ──────────────────────────────────────────────────────────
- # Native GitHub runner; Qt installed via jurplel/install-qt-action so
- # we get a modern, self-contained Qt build and all libraries are in a
- # known location.
- - arch: x86_64
- runner: ubuntu-22.04
- qt_method: action
- qt_version: '6.9.3'
- linuxdeploy_url: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
- linuxdeploy_qt_url: https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
- appimagetool_url: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
- appimage_arch: x86_64
- run_tests: true
- skip_on_pr: false
-
- # ── aarch64 ─────────────────────────────────────────────────────────
- # GitHub-hosted ARM64 runner (ubuntu-22.04-arm). jurplel/install-qt-
- # action does not ship Linux-ARM desktop Qt prebuilts, so we use the
- # system Qt 6 packages from Ubuntu 22.04's apt repositories instead.
- # If this runner label is unavailable for your fork, replace it with
- # "ubuntu-22.04" and add QEMU emulation via docker/setup-qemu-action.
- - arch: aarch64
- runner: ubuntu-22.04-arm
- qt_method: apt
- linuxdeploy_url: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
- linuxdeploy_qt_url: https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-aarch64.AppImage
- appimagetool_url: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage
- appimage_arch: aarch64
- run_tests: false
- skip_on_pr: true
+ contents: write # needed by softprops/action-gh-release
defaults:
run:
shell: bash
env:
- # Make AppImage tools extract and run without FUSE (safer in CI)
APPIMAGE_EXTRACT_AND_RUN: "1"
steps:
- # ── Checkout ────────────────────────────────────────────────────────────
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 0
- # ── System dependencies ─────────────────────────────────────────────────
- name: Install system dependencies
run: |
set -euxo pipefail
@@ -136,18 +102,195 @@ jobs:
wget \
file
- # ── Qt (x86_64): official prebuilts via jurplel/install-qt-action ───────
- name: Install Qt via install-qt-action (x86_64)
- if: ${{ matrix.qt_method == 'action' }}
uses: jurplel/install-qt-action@v4
with:
- version: ${{ matrix.qt_version }}
+ version: '6.9.3'
cache: true
modules: 'qt3d qtimageformats qtmultimedia qtserialport qtwebsockets'
- # ── Qt (aarch64): system packages from Ubuntu apt ────────────────────────
+ - name: Cache CMake build directory
+ uses: actions/cache@v4
+ with:
+ path: build
+ key: Linux-x86_64-cmake-${{ hashFiles('**/CMakeLists.txt', 'variables.cmake') }}
+ restore-keys: Linux-x86_64-cmake-
+
+ - name: Set build environment variables
+ run: |
+ set -euxo pipefail
+ APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake \
+ | tr ' ' '-')
+ GIT_REV=$(git rev-parse --short HEAD)
+ {
+ echo "APPVERSION=${APPVERSION}-${GIT_REV}"
+ echo "GIT_REV=${GIT_REV}"
+ echo "INSTALL_ROOT=$(pwd)/AppDir"
+ # qmake from install-qt-action is in QTDIR/bin
+ echo "QMAKE=${QTDIR}/bin/qmake"
+ } >> "$GITHUB_ENV"
+
+ - name: Configure CMake
+ run: |
+ set -euxo pipefail
+ cmake -S . -B build -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -Dqmlui=ON \
+ -Dmcp_server=ON \
+ -Dappimage=ON \
+ "-DINSTALL_ROOT=${INSTALL_ROOT}" \
+ "-DCMAKE_PREFIX_PATH=${QTDIR}/lib/cmake"
+
+ - name: Build
+ run: |
+ set -euxo pipefail
+ cmake --build build -j"$(nproc)"
+
+ - name: Run unit tests
+ run: |
+ set -euxo pipefail
+ build/mcp/test/mcp_vc_query_filter_test
+ build/mcp/test/mcp_vc_validation_test
+
+ - name: Install to AppDir
+ run: |
+ set -euxo pipefail
+ cmake --install build
+
+ - name: Download linuxdeploy and plugin-qt
+ run: |
+ set -euxo pipefail
+ wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
+ -O linuxdeploy.AppImage
+ wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
+ -O linuxdeploy-plugin-qt.AppImage
+ wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage \
+ -O appimagetool.AppImage
+ chmod +x linuxdeploy.AppImage linuxdeploy-plugin-qt.AppImage appimagetool.AppImage
+
+ # linuxdeploy uses ldd to find all shared-library dependencies and copies
+ # them to AppDir/usr/lib. linuxdeploy-plugin-qt additionally copies Qt
+ # platform plugins, image-format plugins, QML imports, and creates a
+ # qt.conf so Qt finds its plugins at runtime inside the AppImage.
+ - name: Bundle dependencies with linuxdeploy
+ env:
+ QMAKE: ${{ env.QMAKE }}
+ QML_SOURCES_PATHS: ${{ github.workspace }}/qmlui
+ run: |
+ set -euxo pipefail
+ ./linuxdeploy.AppImage \
+ --appdir AppDir \
+ --executable AppDir/usr/bin/qlcplus-qml \
+ --desktop-file platforms/linux/qlcplus5.desktop \
+ --icon-file resources/icons/png/qlcplus.png \
+ --plugin qt \
+ --verbosity 1
+
+ # QLC+ compiles data paths as relative strings (e.g. "../share/qlcplus")
+ # resolved against the process CWD. -Dappimage=ON installs data under
+ # AppDir/share/ and the binary under AppDir/usr/bin/. Setting CWD to
+ # AppDir/usr before exec makes those "../…" paths resolve correctly.
+ - name: Write custom AppRun
+ run: |
+ set -euxo pipefail
+ cat > AppDir/AppRun << 'APPRUN_EOF'
+ #!/bin/bash
+ set -e
+ HERE="$(dirname "$(readlink -f "${0}")")"
+ export APPDIR="$HERE"
+ export QT_PLUGIN_PATH="${HERE}/usr/plugins${QT_PLUGIN_PATH:+:${QT_PLUGIN_PATH}}"
+ export QML2_IMPORT_PATH="${HERE}/usr/qml${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}"
+ export LD_LIBRARY_PATH="${HERE}/usr/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
+ cd "${HERE}/usr"
+ exec "${HERE}/usr/bin/qlcplus-qml" "$@"
+ APPRUN_EOF
+ sed -i 's/^[[:space:]]*//' AppDir/AppRun
+ chmod +x AppDir/AppRun
+
+ - name: Build AppImage
+ run: |
+ set -euxo pipefail
+ OUTPUT="QLC+-${APPVERSION}-x86_64.AppImage"
+ ./appimagetool.AppImage -v AppDir "$OUTPUT"
+ echo "APPIMAGE_PATH=${OUTPUT}" >> "$GITHUB_ENV"
+
+ - name: Generate SHA256 checksum
+ run: |
+ set -euxo pipefail
+ sha256sum "${APPIMAGE_PATH}" > "${APPIMAGE_PATH}.sha256"
+
+ - name: Upload AppImage artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: QLC+-${{ env.APPVERSION }}-x86_64
+ path: |
+ ${{ env.APPIMAGE_PATH }}
+ ${{ env.APPIMAGE_PATH }}.sha256
+ if-no-files-found: error
+
+ - name: Attach to GitHub Release
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ ${{ env.APPIMAGE_PATH }}
+ ${{ env.APPIMAGE_PATH }}.sha256
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # ── aarch64 ─────────────────────────────────────────────────────────────────
+ # Skipped on pull_request events to keep PR CI fast (ARM builds are ~2x slower).
+ # Uses system Qt 6 from Ubuntu 22.04 apt because jurplel/install-qt-action does
+ # not ship Linux/ARM desktop Qt prebuilts.
+ # Runner: ubuntu-22.04-arm (GitHub-hosted ARM64). If that label is unavailable,
+ # replace with "ubuntu-22.04" and add docker/setup-qemu-action for emulation.
+ build-aarch64:
+ name: Build (aarch64)
+ runs-on: ubuntu-22.04-arm
+ permissions:
+ contents: write
+ if: ${{ github.event_name != 'pull_request' }}
+
+ defaults:
+ run:
+ shell: bash
+
+ env:
+ APPIMAGE_EXTRACT_AND_RUN: "1"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+ fetch-depth: 0
+
+ - name: Install system dependencies
+ run: |
+ set -euxo pipefail
+ sudo apt-get update -q
+ sudo apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ ninja-build \
+ pkg-config \
+ libusb-1.0-0-dev \
+ libftdi1-dev \
+ libasound2-dev \
+ libudev-dev \
+ libfftw3-dev \
+ libmad0-dev \
+ libsndfile1-dev \
+ liblo-dev \
+ libpulse-dev \
+ libfuse2 \
+ patchelf \
+ shared-mime-info \
+ wget \
+ file
+
- name: Install Qt via apt (aarch64)
- if: ${{ matrix.qt_method == 'apt' }}
run: |
set -euxo pipefail
sudo apt-get install -y --no-install-recommends \
@@ -172,93 +315,58 @@ jobs:
libqt6multimedia6-plugins \
libgl1-mesa-dev
- # ── Build directory cache ─────────────────────────────────────────────
- name: Cache CMake build directory
uses: actions/cache@v4
with:
path: build
- key: ${{ runner.os }}-${{ matrix.arch }}-cmake-${{ hashFiles('**/CMakeLists.txt', 'variables.cmake') }}
- restore-keys: |
- ${{ runner.os }}-${{ matrix.arch }}-cmake-
+ key: Linux-aarch64-cmake-${{ hashFiles('**/CMakeLists.txt', 'variables.cmake') }}
+ restore-keys: Linux-aarch64-cmake-
- # ── Environment ────────────────────────────────────────────────────────
- name: Set build environment variables
run: |
set -euxo pipefail
- # APPVERSION from variables.cmake (qmlui variant, e.g. "5.2.2 GIT")
APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake \
| tr ' ' '-')
GIT_REV=$(git rev-parse --short HEAD)
- echo "APPVERSION=${APPVERSION}-${GIT_REV}" >> "$GITHUB_ENV"
- echo "GIT_REV=${GIT_REV}" >> "$GITHUB_ENV"
- echo "INSTALL_ROOT=$(pwd)/AppDir" >> "$GITHUB_ENV"
- # Find qmake6 (needed by linuxdeploy-plugin-qt)
- if command -v qmake6 &>/dev/null; then
- echo "QMAKE=$(command -v qmake6)" >> "$GITHUB_ENV"
- elif [ -n "${QTDIR:-}" ] && [ -x "$QTDIR/bin/qmake6" ]; then
- echo "QMAKE=$QTDIR/bin/qmake6" >> "$GITHUB_ENV"
- elif [ -n "${QTDIR:-}" ] && [ -x "$QTDIR/bin/qmake" ]; then
- echo "QMAKE=$QTDIR/bin/qmake" >> "$GITHUB_ENV"
- else
- echo "QMAKE=$(command -v qmake)" >> "$GITHUB_ENV"
- fi
+ QMAKE=$(command -v qmake6 || command -v qmake)
+ {
+ echo "APPVERSION=${APPVERSION}-${GIT_REV}"
+ echo "GIT_REV=${GIT_REV}"
+ echo "INSTALL_ROOT=$(pwd)/AppDir"
+ echo "QMAKE=${QMAKE}"
+ } >> "$GITHUB_ENV"
- # ── CMake configure ───────────────────────────────────────────────────
- name: Configure CMake
run: |
set -euxo pipefail
- CMAKE_EXTRA_ARGS=()
- # For install-qt-action builds, point CMake at the custom Qt prefix
- if [ "${{ matrix.qt_method }}" = "action" ] && [ -n "${QTDIR:-}" ]; then
- CMAKE_EXTRA_ARGS+=("-DCMAKE_PREFIX_PATH=${QTDIR}/lib/cmake")
- fi
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-Dqmlui=ON \
-Dmcp_server=ON \
-Dappimage=ON \
- "-DINSTALL_ROOT=${INSTALL_ROOT}" \
- "${CMAKE_EXTRA_ARGS[@]}"
+ "-DINSTALL_ROOT=${INSTALL_ROOT}"
- # ── Build ──────────────────────────────────────────────────────────────
- name: Build
run: |
set -euxo pipefail
- cmake --build build -j$(nproc)
+ cmake --build build -j"$(nproc)"
- # ── Tests (x86_64 only) ────────────────────────────────────────────────
- - name: Run unit tests
- if: ${{ matrix.run_tests }}
- run: |
- set -euxo pipefail
- build/mcp/test/mcp_vc_query_filter_test
- build/mcp/test/mcp_vc_validation_test
-
- # ── Install to AppDir ──────────────────────────────────────────────────
- # cmake --install copies the binary, data files, qlcplus I/O plugins,
- # desktop file, icons, and (where available) Qt libraries/plugins.
- # The OPTIONAL keyword on Qt-specific installs in platforms/linux/
- # CMakeLists.txt ensures that aarch64 system-Qt builds don't fail when
- # Qt libraries land in architecture-specific paths not known to cmake.
- name: Install to AppDir
run: |
set -euxo pipefail
cmake --install build
- # ── Download and extract AppImage tooling ──────────────────────────────
- name: Download linuxdeploy and plugin-qt
run: |
set -euxo pipefail
- wget -q "${{ matrix.linuxdeploy_url }}" -O linuxdeploy.AppImage
- wget -q "${{ matrix.linuxdeploy_qt_url }}" -O linuxdeploy-plugin-qt.AppImage
- wget -q "${{ matrix.appimagetool_url }}" -O appimagetool.AppImage
+ wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage \
+ -O linuxdeploy.AppImage
+ wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-aarch64.AppImage \
+ -O linuxdeploy-plugin-qt.AppImage
+ wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage \
+ -O appimagetool.AppImage
chmod +x linuxdeploy.AppImage linuxdeploy-plugin-qt.AppImage appimagetool.AppImage
- # ── Bundle Qt libraries, plugins, and QML modules via linuxdeploy ──────
- # linuxdeploy uses ldd to find all shared-library dependencies and copies
- # them to AppDir/usr/lib. linuxdeploy-plugin-qt additionally copies Qt
- # platform plugins, image-format plugins, QML imports, and creates a
- # qt.conf so Qt finds its plugins at runtime inside the AppImage.
- name: Bundle dependencies with linuxdeploy
env:
QMAKE: ${{ env.QMAKE }}
@@ -273,13 +381,6 @@ jobs:
--plugin qt \
--verbosity 1
- # ── Create custom AppRun ───────────────────────────────────────────────
- # QLC+ compiles data paths as relative strings (e.g. "../share/qlcplus")
- # that it resolves against the process CWD at runtime. The -Dappimage=ON
- # CMake option installs data under AppDir/share/ and plugins under
- # AppDir/lib/, both one level above the binary dir (AppDir/usr/bin/).
- # Setting CWD to AppDir/usr before exec makes those "../..." paths resolve
- # correctly inside the AppImage mount.
- name: Write custom AppRun
run: |
set -euxo pipefail
@@ -288,49 +389,36 @@ jobs:
set -e
HERE="$(dirname "$(readlink -f "${0}")")"
export APPDIR="$HERE"
-
- # Qt platform plugin and QML import paths set up by linuxdeploy-plugin-qt
- export QT_PLUGIN_PATH="${HERE}/usr/plugins:${QT_PLUGIN_PATH:-}"
- export QML2_IMPORT_PATH="${HERE}/usr/qml:${QML2_IMPORT_PATH:-}"
- export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH:-}"
-
- # Change CWD to AppDir/usr so that relative data paths compiled into
- # qlcconfig.h (e.g. "../share/qlcplus/fixtures") resolve correctly.
+ export QT_PLUGIN_PATH="${HERE}/usr/plugins${QT_PLUGIN_PATH:+:${QT_PLUGIN_PATH}}"
+ export QML2_IMPORT_PATH="${HERE}/usr/qml${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}"
+ export LD_LIBRARY_PATH="${HERE}/usr/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
cd "${HERE}/usr"
-
exec "${HERE}/usr/bin/qlcplus-qml" "$@"
APPRUN_EOF
- # Remove leading whitespace that heredoc indentation introduced
sed -i 's/^[[:space:]]*//' AppDir/AppRun
chmod +x AppDir/AppRun
- # ── Pack into AppImage ─────────────────────────────────────────────────
- name: Build AppImage
- env:
- VERSION: ${{ env.APPVERSION }}
run: |
set -euxo pipefail
- OUTPUT="QLC+-${APPVERSION}-${{ matrix.appimage_arch }}.AppImage"
+ OUTPUT="QLC+-${APPVERSION}-aarch64.AppImage"
./appimagetool.AppImage -v AppDir "$OUTPUT"
echo "APPIMAGE_PATH=${OUTPUT}" >> "$GITHUB_ENV"
- # ── SHA-256 checksum ──────────────────────────────────────────────────
- name: Generate SHA256 checksum
run: |
set -euxo pipefail
sha256sum "${APPIMAGE_PATH}" > "${APPIMAGE_PATH}.sha256"
- # ── Upload as workflow artifact ────────────────────────────────────────
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
- name: QLC+-${{ env.APPVERSION }}-${{ matrix.appimage_arch }}
+ name: QLC+-${{ env.APPVERSION }}-aarch64
path: |
${{ env.APPIMAGE_PATH }}
${{ env.APPIMAGE_PATH }}.sha256
if-no-files-found: error
- # ── Attach to GitHub Release on tag push ──────────────────────────────
- name: Attach to GitHub Release
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: softprops/action-gh-release@v2
@@ -338,7 +426,6 @@ jobs:
files: |
${{ env.APPIMAGE_PATH }}
${{ env.APPIMAGE_PATH }}.sha256
- generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -358,7 +445,7 @@ jobs:
runs-on: ubuntu-22.04
if: ${{ github.event_name == 'workflow_dispatch' && inputs.build_armv7 == true }}
permissions:
- contents: write # needed by softprops/action-gh-release to upload tarballs
+ contents: write
defaults:
run:
@@ -400,7 +487,8 @@ jobs:
libfftw3-dev libmad0-dev libsndfile1-dev liblo-dev libpulse-dev \
patchelf wget file git
cd /workspace
- APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake | tr ' ' '-')
+ APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake \
+ | tr ' ' '-')
GIT_REV=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
APPVERSION="${APPVERSION}-${GIT_REV}"
cmake -S . -B build -G Ninja \
@@ -409,19 +497,21 @@ jobs:
-Dmcp_server=ON \
-Dappimage=ON \
"-DINSTALL_ROOT=/workspace/AppDir-armv7"
- cmake --build build -j$(nproc)
+ cmake --build build -j"$(nproc)"
cmake --install build
- # Package as tarball (no armhf AppImage tools available)
tar -czf "QLC+-${APPVERSION}-armv7.tar.gz" -C /workspace AppDir-armv7
- sha256sum "QLC+-${APPVERSION}-armv7.tar.gz" > "QLC+-${APPVERSION}-armv7.tar.gz.sha256"
+ sha256sum "QLC+-${APPVERSION}-armv7.tar.gz" \
+ > "QLC+-${APPVERSION}-armv7.tar.gz.sha256"
echo "APPVERSION=${APPVERSION}" >> /workspace/.armv7_env
- name: Read armv7 env
run: |
set -euxo pipefail
source .armv7_env
- echo "APPVERSION=${APPVERSION}" >> "$GITHUB_ENV"
- echo "TARBALL=QLC+-${APPVERSION}-armv7.tar.gz" >> "$GITHUB_ENV"
+ {
+ echo "APPVERSION=${APPVERSION}"
+ echo "TARBALL=QLC+-${APPVERSION}-armv7.tar.gz"
+ } >> "$GITHUB_ENV"
- name: Upload armv7 tarball artifact
uses: actions/upload-artifact@v4
@@ -448,15 +538,23 @@ jobs:
ci-success:
name: CI Success
runs-on: ubuntu-22.04
- needs: [build]
+ needs: [build-x86_64, build-aarch64]
if: always()
permissions:
contents: read
steps:
- name: Check required build jobs
run: |
- if [[ "${{ needs.build.result }}" == "failure" ]]; then
- echo "❌ One or more required build jobs failed."
+ x86_result="${{ needs.build-x86_64.result }}"
+ arm_result="${{ needs.build-aarch64.result }}"
+ if [[ "${x86_result}" != "success" ]]; then
+ echo "❌ x86_64 build ${x86_result}."
+ exit 1
+ fi
+ # aarch64 is intentionally skipped on pull_request events; only fail
+ # if it actually ran and failed.
+ if [[ "${arm_result}" == "failure" ]]; then
+ echo "❌ aarch64 build failed."
exit 1
fi
- echo "✅ All required build jobs succeeded (result: ${{ needs.build.result }})."
+ echo "✅ x86_64=${x86_result}, aarch64=${arm_result}."
From 94d96be0b5455af67f26a768398dc0f60d6bf6fa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:55:14 +0000
Subject: [PATCH 07/14] fix: use QT_ROOT_DIR env var; add universe repo and fix
aarch64 Qt packages
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/cd1b2937-1365-4589-a711-370f517fb912
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 8003b9f924..048ec5d472 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -126,8 +126,8 @@ jobs:
echo "APPVERSION=${APPVERSION}-${GIT_REV}"
echo "GIT_REV=${GIT_REV}"
echo "INSTALL_ROOT=$(pwd)/AppDir"
- # qmake from install-qt-action is in QTDIR/bin
- echo "QMAKE=${QTDIR}/bin/qmake"
+ # install-qt-action exports QT_ROOT_DIR (not QTDIR)
+ echo "QMAKE=${QT_ROOT_DIR}/bin/qmake"
} >> "$GITHUB_ENV"
- name: Configure CMake
@@ -139,7 +139,7 @@ jobs:
-Dmcp_server=ON \
-Dappimage=ON \
"-DINSTALL_ROOT=${INSTALL_ROOT}" \
- "-DCMAKE_PREFIX_PATH=${QTDIR}/lib/cmake"
+ "-DCMAKE_PREFIX_PATH=${QT_ROOT_DIR}/lib/cmake"
- name: Build
run: |
@@ -293,6 +293,10 @@ jobs:
- name: Install Qt via apt (aarch64)
run: |
set -euxo pipefail
+ # Ensure the 'universe' repository is enabled (ARM runners may ship
+ # with a minimal sources.list that omits it).
+ sudo apt-add-repository universe -y
+ sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
qt6-base-dev \
qt6-base-private-dev \
@@ -312,7 +316,6 @@ jobs:
qml6-module-qtquick-layouts \
qml6-module-qtquick-window \
qml6-module-qtmultimedia \
- libqt6multimedia6-plugins \
libgl1-mesa-dev
- name: Cache CMake build directory
From e210af8f81789ed16e69d0b3d5405dd3b27a7a79 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 17:09:43 +0000
Subject: [PATCH 08/14] fix: offscreen Qt platform for unit tests; libqt6*6-dev
names for aarch64
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/cd1b2937-1365-4589-a711-370f517fb912
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 048ec5d472..edf863df49 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -149,8 +149,8 @@ jobs:
- name: Run unit tests
run: |
set -euxo pipefail
- build/mcp/test/mcp_vc_query_filter_test
- build/mcp/test/mcp_vc_validation_test
+ QT_QPA_PLATFORM=offscreen build/mcp/test/mcp_vc_query_filter_test
+ QT_QPA_PLATFORM=offscreen build/mcp/test/mcp_vc_validation_test
- name: Install to AppDir
run: |
@@ -303,10 +303,10 @@ jobs:
qt6-declarative-dev \
qt6-declarative-private-dev \
qt6-multimedia-dev \
- qt6-websockets-dev \
- qt6-svg-dev \
+ libqt6websockets6-dev \
+ libqt6svg6-dev \
qt6-3d-dev \
- qt6-serialport-dev \
+ libqt6serialport6-dev \
qt6-tools-dev \
qt6-l10n-tools \
qt6-image-formats-plugins \
From 82f630f7ca4734c450a63b93eabaae308199b45f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 17:27:11 +0000
Subject: [PATCH 09/14] fix: LD_LIBRARY_PATH for linuxdeploy; Qt6LinguistTools
libexec symlink on aarch64
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/cd1b2937-1365-4589-a711-370f517fb912
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index edf863df49..58567dd0dd 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -172,10 +172,13 @@ jobs:
# them to AppDir/usr/lib. linuxdeploy-plugin-qt additionally copies Qt
# platform plugins, image-format plugins, QML imports, and creates a
# qt.conf so Qt finds its plugins at runtime inside the AppImage.
+ # LD_LIBRARY_PATH must include AppDir/usr/lib so linuxdeploy can resolve
+ # libqlcplusengine.so and other QLC+ libraries installed by cmake --install.
- name: Bundle dependencies with linuxdeploy
env:
QMAKE: ${{ env.QMAKE }}
QML_SOURCES_PATHS: ${{ github.workspace }}/qmlui
+ LD_LIBRARY_PATH: ${{ github.workspace }}/AppDir/usr/lib
run: |
set -euxo pipefail
./linuxdeploy.AppImage \
@@ -342,6 +345,15 @@ jobs:
- name: Configure CMake
run: |
set -euxo pipefail
+ # Ubuntu 22.04 arm64 packaging bug: Qt6LinguistToolsTargets.cmake
+ # expects lprodump in /usr/lib/qt6/libexec/ but qt6-l10n-tools installs
+ # it in /usr/lib/qt6/bin/. Create symlinks so CMake can find them.
+ if [ -d /usr/lib/qt6/bin ] && [ ! -d /usr/lib/qt6/libexec ]; then
+ sudo mkdir -p /usr/lib/qt6/libexec
+ for f in /usr/lib/qt6/bin/*; do
+ sudo ln -sf "$f" "/usr/lib/qt6/libexec/$(basename "$f")"
+ done
+ fi
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-Dqmlui=ON \
@@ -374,6 +386,7 @@ jobs:
env:
QMAKE: ${{ env.QMAKE }}
QML_SOURCES_PATHS: ${{ github.workspace }}/qmlui
+ LD_LIBRARY_PATH: ${{ github.workspace }}/AppDir/usr/lib
run: |
set -euxo pipefail
./linuxdeploy.AppImage \
From ba84370dd0e2a92e4fe91e1f7e1ccf74b7abebb9 Mon Sep 17 00:00:00 2001
From: Andre Bossard
Date: Tue, 28 Apr 2026 23:40:21 +0200
Subject: [PATCH 10/14] fix: install qt6-tools-dev-tools for lprodump; check
binary not directory
The aarch64 build fails because lprodump is missing from /usr/lib/qt6/libexec/.
The old workaround checked if the directory existed, but on aarch64 Ubuntu the
directory exists while the binary is absent.
Fix:
- Add qt6-tools-dev-tools package (provides lprodump)
- Check for the binary (! -x lprodump) instead of the directory (! -d libexec)
- Apply same fix to armv7 container build section
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 58567dd0dd..ff76185d9e 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -311,6 +311,7 @@ jobs:
qt6-3d-dev \
libqt6serialport6-dev \
qt6-tools-dev \
+ qt6-tools-dev-tools \
qt6-l10n-tools \
qt6-image-formats-plugins \
libqt6opengl6-dev \
@@ -345,13 +346,13 @@ jobs:
- name: Configure CMake
run: |
set -euxo pipefail
- # Ubuntu 22.04 arm64 packaging bug: Qt6LinguistToolsTargets.cmake
- # expects lprodump in /usr/lib/qt6/libexec/ but qt6-l10n-tools installs
- # it in /usr/lib/qt6/bin/. Create symlinks so CMake can find them.
- if [ -d /usr/lib/qt6/bin ] && [ ! -d /usr/lib/qt6/libexec ]; then
- sudo mkdir -p /usr/lib/qt6/libexec
+ # Ubuntu arm64 packaging: Qt6LinguistToolsTargets.cmake expects
+ # lprodump in /usr/lib/qt6/libexec/ but it may be in /usr/lib/qt6/bin/.
+ # Check for the binary, not the directory.
+ sudo mkdir -p /usr/lib/qt6/libexec
+ if [ ! -x /usr/lib/qt6/libexec/lprodump ] && [ -d /usr/lib/qt6/bin ]; then
for f in /usr/lib/qt6/bin/*; do
- sudo ln -sf "$f" "/usr/lib/qt6/libexec/$(basename "$f")"
+ [ -f "$f" ] && sudo ln -sf "$f" "/usr/lib/qt6/libexec/$(basename "$f")"
done
fi
cmake -S . -B build -G Ninja \
@@ -498,10 +499,17 @@ jobs:
qt6-base-dev qt6-base-private-dev \
qt6-declarative-dev qt6-declarative-private-dev \
qt6-multimedia-dev qt6-websockets-dev qt6-svg-dev \
- qt6-3d-dev qt6-tools-dev qt6-l10n-tools \
+ qt6-3d-dev qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \
libusb-1.0-0-dev libftdi1-dev libasound2-dev libudev-dev \
libfftw3-dev libmad0-dev libsndfile1-dev liblo-dev libpulse-dev \
patchelf wget file git
+ # Fix Qt6 libexec layout if lprodump is missing
+ mkdir -p /usr/lib/qt6/libexec
+ if [ ! -x /usr/lib/qt6/libexec/lprodump ] && [ -d /usr/lib/qt6/bin ]; then
+ for f in /usr/lib/qt6/bin/*; do
+ [ -f "$f" ] && ln -sf "$f" "/usr/lib/qt6/libexec/$(basename "$f")"
+ done
+ fi
cd /workspace
APPVERSION=$(awk -F'"' '/set\(APPVERSION "5\./{print $2; exit}' variables.cmake \
| tr ' ' '-')
From f901129b6125ec2e02053e997a3f422bca8f461d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 22:13:08 +0000
Subject: [PATCH 11/14] fix: initialize InputPatch buffered input value
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/78fccb82-719f-4c54-a2c9-a73f8068b292
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
engine/src/inputpatch.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/engine/src/inputpatch.h b/engine/src/inputpatch.h
index a4dc4885b6..4891d6aecd 100644
--- a/engine/src/inputpatch.h
+++ b/engine/src/inputpatch.h
@@ -166,7 +166,9 @@ private slots:
struct InputValue
{
- InputValue() {}
+ InputValue()
+ : value(0)
+ {}
InputValue(uchar v, QString const& k)
: value(v)
, key(k)
From 4ba79604f6e9ddb1d141e629edc0ace1ab70affc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 22:28:02 +0000
Subject: [PATCH 12/14] fix: guard dark mode style hint for older Qt 6
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/78fccb82-719f-4c54-a2c9-a73f8068b292
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
qmlui/main.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/qmlui/main.cpp b/qmlui/main.cpp
index 6b22fc65b0..a0afc32853 100644
--- a/qmlui/main.cpp
+++ b/qmlui/main.cpp
@@ -58,7 +58,9 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
/* Force dark mode so the macOS title bar and window chrome match the dark UI */
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
QGuiApplication::styleHints()->setColorScheme(Qt::ColorScheme::Dark);
+#endif
// Since Qt6, the default rendering backend is Rhi.
// QLC+ doesn't support it yet so OpenGL have to be forced.
From 0707db8effd75056c8cbac9e65bce011afebd9f7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 22:43:31 +0000
Subject: [PATCH 13/14] fix: use direct Qt tools path on aarch64 CI
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/78fccb82-719f-4c54-a2c9-a73f8068b292
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index ff76185d9e..632203aa4b 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -322,6 +322,11 @@ jobs:
qml6-module-qtmultimedia \
libgl1-mesa-dev
+ - name: Add Qt tools to PATH (aarch64)
+ run: |
+ set -euxo pipefail
+ echo "/usr/lib/qt6/bin" >> "$GITHUB_PATH"
+
- name: Cache CMake build directory
uses: actions/cache@v4
with:
From f881fa4a3421e244a3d34a039f6538f9943f5777 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 22:59:12 +0000
Subject: [PATCH 14/14] ci: remove temporary linux workflow branch trigger
Agent-Logs-Url: https://github.com/abossard/qlcplus/sessions/78fccb82-719f-4c54-a2c9-a73f8068b292
Co-authored-by: abossard <86611+abossard@users.noreply.github.com>
---
.github/workflows/linux-build.yml | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 632203aa4b..54811d830c 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -21,10 +21,6 @@ on:
push:
tags:
- 'v*'
- # Temporary: also run on the CI development branch so we can verify this
- # workflow before it is merged. Remove after first successful run.
- branches:
- - 'copilot/add-github-actions-workflow-linux-builds'
pull_request:
paths: