diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
new file mode 100644
index 000000000..f128e1e8a
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,364 @@
+name: Build and Release
+
+on:
+ push:
+ branches:
+ - master
+ - ubuntu25.10-wayland
+ tags:
+ - "v*"
+ pull_request:
+ branches:
+ - master
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: Linux
+ os: ubuntu-24.04
+ artifact_name: dsview-linux-x86_64
+ archive_name: linux-x86_64
+ - name: Windows
+ os: windows-2022
+ artifact_name: dsview-windows-x86_64
+ archive_name: windows-x86_64
+ - name: macOS
+ os: macos-latest
+ artifact_name: dsview-macos
+ archive_name: macos
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - name: Compute build metadata
+ id: meta
+ shell: bash
+ run: |
+ python3 <<'PY' >> "${GITHUB_OUTPUT}"
+ import os
+ import pathlib
+ import re
+
+ cmake = pathlib.Path("CMakeLists.txt").read_text(encoding="utf-8")
+
+ def pick(name: str) -> str:
+ match = re.search(rf"set\({name}\s+([0-9]+)\)", cmake)
+ if not match:
+ raise SystemExit(f"Unable to find {name} in CMakeLists.txt")
+ return match.group(1)
+
+ project_version = ".".join(
+ [
+ pick("DS_VERSION_MAJOR"),
+ pick("DS_VERSION_MINOR"),
+ pick("DS_VERSION_MICRO"),
+ ]
+ )
+
+ ref_type = os.environ.get("GITHUB_REF_TYPE", "")
+ ref_name = os.environ.get("GITHUB_REF_NAME", "")
+ short_sha = os.environ.get("GITHUB_SHA", "")[:7]
+
+ if ref_type == "tag":
+ tag_version = ref_name[1:] if ref_name.startswith("v") else ref_name
+ if tag_version != project_version:
+ raise SystemExit(
+ f"Git tag {ref_name} does not match project version {project_version}"
+ )
+ artifact_version = project_version
+ package_version = project_version
+ release_title = f"DSView {project_version}"
+ else:
+ artifact_version = f"{project_version}+git.{short_sha}"
+ package_version = f"{project_version}+git{short_sha}"
+ release_title = f"DSView {artifact_version}"
+
+ print(f"project_version={project_version}")
+ print(f"artifact_version={artifact_version}")
+ print(f"package_version={package_version}")
+ print(f"release_title={release_title}")
+ PY
+
+ - name: Install Linux dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ cmake ninja-build pkg-config \
+ qt6-base-dev qt6-tools-dev qt6-wayland \
+ libglib2.0-dev libfftw3-dev libusb-1.0-0-dev \
+ libboost-dev zlib1g-dev python3-dev
+
+ - name: Set up MSYS2
+ if: runner.os == 'Windows'
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: MINGW64
+ update: true
+ install: >-
+ base-devel
+ git
+ zip
+ mingw-w64-x86_64-toolchain
+ mingw-w64-x86_64-cmake
+ mingw-w64-x86_64-ninja
+ mingw-w64-x86_64-pkgconf
+ mingw-w64-x86_64-python
+ mingw-w64-x86_64-zlib
+ mingw-w64-x86_64-glib2
+ mingw-w64-x86_64-libusb
+ mingw-w64-x86_64-fftw
+ mingw-w64-x86_64-boost
+ mingw-w64-x86_64-qt6-base
+ mingw-w64-x86_64-qt6-tools
+
+ - name: Install macOS dependencies
+ if: runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew update
+ brew install cmake ninja pkg-config qt@6 glib libusb fftw boost python
+
+ - name: Configure Linux
+ if: runner.os == 'Linux'
+ shell: bash
+ run: cmake -S . -B build -G Ninja -DDSVIEW_PREFER_QT6=ON -DDSVIEW_PACKAGE_VERSION_OVERRIDE="${{ steps.meta.outputs.package_version }}"
+
+ - name: Configure Windows
+ if: runner.os == 'Windows'
+ shell: msys2 {0}
+ run: |
+ set -euo pipefail
+ export PATH="/mingw64/bin:${PATH}"
+ export PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:/mingw64/share/pkgconfig"
+ export CMAKE_PREFIX_PATH="/mingw64"
+ export Python3_ROOT_DIR="/mingw64"
+ export Boost_ROOT="/mingw64"
+ cmake_bin="$(command -v cmake)"
+ pkg_config_bin="$(command -v pkg-config || command -v pkgconf)"
+ python_bin="$(command -v python3 || command -v python)"
+ python_include="$("${python_bin}" -c 'import sysconfig; print(sysconfig.get_path("include"))')"
+ python_library="$("${python_bin}" -c 'import pathlib, sysconfig; libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var("LIBPL") or ""; library = sysconfig.get_config_var("LDLIBRARY") or sysconfig.get_config_var("LIBRARY") or ""; print(pathlib.Path(libdir, library) if libdir and library else "")')"
+ echo "cmake=${cmake_bin}"
+ echo "pkg-config=${pkg_config_bin}"
+ echo "python=${python_bin}"
+ echo "python-include=${python_include}"
+ echo "python-library=${python_library}"
+ "${cmake_bin}" -S . -B build -G Ninja \
+ -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
+ -DPKG_CONFIG_EXECUTABLE="${pkg_config_bin}" \
+ -DPython3_EXECUTABLE="${python_bin}" \
+ -DPython3_ROOT_DIR="${Python3_ROOT_DIR}" \
+ -DPython3_INCLUDE_DIR="${python_include}" \
+ -DPython3_LIBRARY="${python_library}" \
+ -DBoost_ROOT="${Boost_ROOT}" \
+ -DBOOST_INCLUDEDIR="/mingw64/include" \
+ -DBOOST_LIBRARYDIR="/mingw64/lib" \
+ -DDSVIEW_PREFER_QT6=ON \
+ -DDSVIEW_PACKAGE_VERSION_OVERRIDE="${{ steps.meta.outputs.package_version }}" \
+ 2>&1 | tee configure-windows.log
+ status=${PIPESTATUS[0]}
+ if [[ ${status} -ne 0 ]]; then
+ test -f build/CMakeFiles/CMakeError.log && cat build/CMakeFiles/CMakeError.log
+ test -f build/CMakeFiles/CMakeOutput.log && cat build/CMakeFiles/CMakeOutput.log
+ exit ${status}
+ fi
+
+ - name: Configure macOS
+ if: runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew_prefix="$(brew --prefix)"
+ qt_prefix="$(brew --prefix qt@6)"
+ glib_prefix="$(brew --prefix glib)"
+ libusb_prefix="$(brew --prefix libusb)"
+ fftw_prefix="$(brew --prefix fftw)"
+ boost_prefix="$(brew --prefix boost)"
+ export PATH="${qt_prefix}/bin:${brew_prefix}/bin:$PATH"
+ pkg_config_bin="$(command -v pkg-config || command -v pkgconf)"
+ python_bin="$(command -v python3 || command -v python)"
+ python_prefix="$(cd "$(dirname "${python_bin}")/.." && pwd)"
+ export PKG_CONFIG_PATH="${glib_prefix}/lib/pkgconfig:${libusb_prefix}/lib/pkgconfig:${fftw_prefix}/lib/pkgconfig:${boost_prefix}/lib/pkgconfig:${python_prefix}/lib/pkgconfig:${brew_prefix}/lib/pkgconfig:${qt_prefix}/lib/pkgconfig"
+ python_include="$("${python_bin}" -c 'import sysconfig; print(sysconfig.get_path("include"))')"
+ python_library="$("${python_bin}" -c 'import pathlib, sysconfig; libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var("LIBPL") or ""; library = sysconfig.get_config_var("LDLIBRARY") or sysconfig.get_config_var("LIBRARY") or ""; print(pathlib.Path(libdir, library) if libdir and library else "")')"
+ cmake -S . -B build -G Ninja \
+ -DDSVIEW_PREFER_QT6=ON \
+ -DDSVIEW_PACKAGE_VERSION_OVERRIDE="${{ steps.meta.outputs.package_version }}" \
+ -DCMAKE_PREFIX_PATH="${qt_prefix};${glib_prefix};${libusb_prefix};${fftw_prefix};${boost_prefix};${python_prefix};${brew_prefix}" \
+ -DPKG_CONFIG_EXECUTABLE="${pkg_config_bin}" \
+ -DPython3_EXECUTABLE="${python_bin}" \
+ -DPython3_ROOT_DIR="${python_prefix}" \
+ -DPython3_INCLUDE_DIR="${python_include}" \
+ -DPython3_LIBRARY="${python_library}" \
+ -DBoost_ROOT="${boost_prefix}" \
+ 2>&1 | tee configure-macos.log
+ status=${PIPESTATUS[0]}
+ if [[ ${status} -ne 0 ]]; then
+ test -f build/CMakeFiles/CMakeError.log && cat build/CMakeFiles/CMakeError.log
+ test -f build/CMakeFiles/CMakeOutput.log && cat build/CMakeFiles/CMakeOutput.log
+ exit ${status}
+ fi
+
+ - name: Build Linux
+ if: runner.os == 'Linux'
+ shell: bash
+ run: cmake --build build --parallel
+
+ - name: Build Windows
+ if: runner.os == 'Windows'
+ shell: msys2 {0}
+ run: |
+ cmake --build build --parallel 2>&1 | tee build-windows.log
+ status=${PIPESTATUS[0]}
+ if [[ ${status} -ne 0 ]]; then
+ exit ${status}
+ fi
+
+ - name: Build macOS
+ if: runner.os == 'macOS'
+ shell: bash
+ run: |
+ cmake --build build --parallel 2>&1 | tee build-macos.log
+ status=${PIPESTATUS[0]}
+ if [[ ${status} -ne 0 ]]; then
+ exit ${status}
+ fi
+
+ - name: Upload failure diagnostics
+ if: failure()
+ uses: actions/upload-artifact@v5
+ with:
+ name: failure-diagnostics-${{ matrix.artifact_name }}-${{ steps.meta.outputs.artifact_version }}
+ path: |
+ configure-*.log
+ build-*.log
+ build/CMakeFiles/CMakeError.log
+ build/CMakeFiles/CMakeOutput.log
+ if-no-files-found: ignore
+ retention-days: 14
+
+ - name: Smoke check Linux
+ if: runner.os == 'Linux'
+ shell: bash
+ run: ./build.dir/DSView -v
+
+ - name: Smoke check Windows
+ if: runner.os == 'Windows'
+ shell: msys2 {0}
+ run: ./build.dir/DSView.exe -v
+
+ - name: Smoke check macOS
+ if: runner.os == 'macOS'
+ shell: bash
+ run: ./build.dir/DSView.app/Contents/MacOS/DSView -v
+
+ - name: Package Linux
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ set -euo pipefail
+ version="${{ steps.meta.outputs.artifact_version }}"
+ stage="${RUNNER_TEMP}/stage"
+ mkdir -p "${stage}" dist
+ cmake --install build --prefix "${stage}/usr"
+ tar -C "${stage}" -czf "dist/DSView-${version}-${{ matrix.archive_name }}.tar.gz" usr
+ cpack --config build/CPackConfig.cmake -G DEB -B dist/cpack
+ deb_file="$(find dist/cpack -maxdepth 1 -type f -name '*.deb' | head -n 1)"
+ mv "${deb_file}" "dist/DSView-${version}-linux-amd64.deb"
+
+ - name: Package Windows
+ if: runner.os == 'Windows'
+ shell: msys2 {0}
+ run: |
+ set -euo pipefail
+ version="${{ steps.meta.outputs.artifact_version }}"
+ dist_dir="$(pwd)/dist"
+ stage="${RUNNER_TEMP}/stage"
+ package_root="${RUNNER_TEMP}/DSView-${version}-${{ matrix.archive_name }}"
+ mkdir -p "${stage}" "${package_root}" dist
+ cmake --install build --prefix "${stage}"
+ cp -R "${stage}/bin/." "${package_root}/"
+ cp -R "${stage}/share/DSView/." "${package_root}/"
+ mkdir -p "${package_root}/decoders"
+ cp -R "${stage}/share/libsigrokdecode4DSL/decoders/." "${package_root}/decoders/"
+
+ deployqt="$(command -v windeployqt6.exe || command -v windeployqt.exe || command -v windeployqt6 || command -v windeployqt)"
+ "${deployqt}" --release --compiler-runtime "${package_root}/DSView.exe"
+
+ for dep in $(ldd "${package_root}/DSView.exe" | awk '/=> \\/mingw64\\// {print $3}'); do
+ cp -n "${dep}" "${package_root}/"
+ done
+
+ (cd "${RUNNER_TEMP}" && zip -r "${dist_dir}/DSView-${version}-${{ matrix.archive_name }}.zip" "$(basename "${package_root}")" >/dev/null)
+
+ - name: Package macOS
+ if: runner.os == 'macOS'
+ shell: bash
+ run: |
+ set -euo pipefail
+ version="${{ steps.meta.outputs.artifact_version }}"
+ stage="${RUNNER_TEMP}/stage"
+ app_path="${stage}/DSView.app"
+ mkdir -p "${stage}" dist
+ export PATH="$(brew --prefix qt@6)/bin:$PATH"
+
+ cmake --install build --prefix "${stage}"
+ mkdir -p "${app_path}/Contents/share"
+ cp -R "${stage}/share/DSView" "${app_path}/Contents/share/DSView"
+ cp -R "${stage}/share/libsigrokdecode4DSL" "${app_path}/Contents/share/libsigrokdecode4DSL"
+
+ macdeployqt "${app_path}" -dmg
+ ditto -c -k --sequesterRsrc --keepParent "${app_path}" "dist/DSView-${version}-${{ matrix.archive_name }}.zip"
+ mv "${stage}/DSView.dmg" "dist/DSView-${version}-${{ matrix.archive_name }}.dmg"
+
+ - name: Upload build artifact
+ uses: actions/upload-artifact@v5
+ with:
+ name: ${{ matrix.artifact_name }}-${{ steps.meta.outputs.artifact_version }}
+ path: dist/*
+ if-no-files-found: error
+ retention-days: 14
+
+ release:
+ name: Publish GitHub release
+ if: github.ref_type == 'tag'
+ runs-on: ubuntu-24.04
+ needs:
+ - build
+
+ steps:
+ - name: Download packaged artifacts
+ uses: actions/download-artifact@v5
+ with:
+ path: release-artifacts
+
+ - name: Create or update release
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ set -euo pipefail
+ tag="${GITHUB_REF_NAME}"
+ title="DSView ${tag#v}"
+ if ! gh release view "${tag}" >/dev/null 2>&1; then
+ gh release create "${tag}" --title "${title}" --generate-notes
+ else
+ gh release edit "${tag}" --title "${title}"
+ fi
+ mapfile -d '' files < <(find release-artifacts -type f -print0)
+ gh release upload "${tag}" "${files[@]}" --clobber
diff --git a/.gitignore b/.gitignore
index 81fa4defb..749da0210 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,9 @@ moc_*.cpp_parameters
DSView-prj
build*
+!.github/
+!.github/workflows/
+!.github/workflows/build-release.yml
share
.vscode
qtpro
diff --git a/CMake/FindFFTW.cmake b/CMake/FindFFTW.cmake
index 82095c7e3..04e0ba8d0 100755
--- a/CMake/FindFFTW.cmake
+++ b/CMake/FindFFTW.cmake
@@ -11,16 +11,24 @@ FIND_PATH(FFTW_INCLUDE_DIR
NAMES
fftw3.h
PATHS
+ /opt/homebrew/include
+ /opt/homebrew/opt/fftw/include
+ /mingw64/include
+ /clang64/include
/usr/local/include
/opt/local/include
/usr/include
)
-SET(FFTW_NAMES ${FFTW_NAMES} fftw3 fftw3f fftw3l fftw3-3)
+SET(FFTW_NAMES ${FFTW_NAMES} fftw3 fftw3f fftw3l fftw3-3 libfftw3 libfftw3-3)
FIND_LIBRARY(FFTW_LIBRARY
NAMES
${FFTW_NAMES}
PATHS
+ /opt/homebrew/lib
+ /opt/homebrew/opt/fftw/lib
+ /mingw64/lib
+ /clang64/lib
/usr/local/lib64
/opt/local/lib64
/usr/lib64
diff --git a/CMake/Findlibusb-1.0.cmake b/CMake/Findlibusb-1.0.cmake
index 663f7d799..782259794 100755
--- a/CMake/Findlibusb-1.0.cmake
+++ b/CMake/Findlibusb-1.0.cmake
@@ -49,20 +49,27 @@ if (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS)
find_path(LIBUSB_1_INCLUDE_DIR
NAMES
- libusb.h
+ libusb-1.0/libusb.h
+ libusb.h
PATHS
+ /opt/homebrew/include
+ /opt/homebrew/opt/libusb/include
+ /mingw64/include
+ /clang64/include
/usr/local/include
/opt/local/include
/usr/include
- PATH_SUFFIXES
- libusb-1.0
)
find_library(LIBUSB_1_LIBRARY
NAMES
- usb-1.0 usb
+ usb-1.0 libusb-1.0 usb
PATHS
+ /opt/homebrew/lib
+ /opt/homebrew/opt/libusb/lib
+ /mingw64/lib
+ /clang64/lib
/usr/local/lib64
/opt/local/lib64
/usr/lib64
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e5eed435..2045c4cfe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@
## along with this program. If not, see .
##
-cmake_minimum_required(VERSION 2.8.6)
+cmake_minimum_required(VERSION 3.16)
project(DSView)
@@ -49,6 +49,18 @@ include(GNUInstallDirs)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake")
#list(APPEND CMAKE_PREFIX_PATH "xxx.cmake find path")
+if(WIN32 AND EXISTS "/mingw64")
+ list(APPEND CMAKE_PREFIX_PATH "/mingw64")
+ list(APPEND CMAKE_INCLUDE_PATH "/mingw64/include")
+ list(APPEND CMAKE_LIBRARY_PATH "/mingw64/lib")
+endif()
+
+if(APPLE AND EXISTS "/opt/homebrew")
+ list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
+ list(APPEND CMAKE_INCLUDE_PATH "/opt/homebrew/include")
+ list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/lib")
+endif()
+
find_package(PkgConfig)
if(NOT PKG_CONFIG_FOUND)
@@ -558,7 +570,15 @@ if(WIN32)
set(CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR}