diff --git a/.github/workflows/release-linux.yaml b/.github/workflows/release-linux.yaml
index fc49a49..29bb93e 100644
--- a/.github/workflows/release-linux.yaml
+++ b/.github/workflows/release-linux.yaml
@@ -4,7 +4,9 @@
# while POCO will be linked statically.
name: Build Release Package for Linux
-on: workflow_dispatch
+on:
+ workflow_dispatch:
+ workflow_call:
jobs:
build-deb:
diff --git a/.github/workflows/release-macos.yaml b/.github/workflows/release-macos.yaml
index 3b32b48..46beebb 100644
--- a/.github/workflows/release-macos.yaml
+++ b/.github/workflows/release-macos.yaml
@@ -2,11 +2,26 @@
# Builds a universal binary on macOS.
name: Build Release Package for macOS
-on: workflow_dispatch
+on:
+ workflow_dispatch:
+ workflow_call:
+ secrets:
+ MACOS_CERTIFICATE_APPLICATION:
+ required: true
+ MACOS_CERTIFICATE_INSTALLER:
+ required: true
+ MACOS_CERTIFICATE_PASSWORD:
+ required: true
+ MACOS_NOTARY_API_KEY:
+ required: true
+ MACOS_NOTARY_KEY_ID:
+ required: true
+ MACOS_NOTARY_ISSUER_ID:
+ required: true
jobs:
- build-deb:
- name: ProductBuild Installer, macOS x86_64+arm64
+ build-pkg:
+ name: ProductBuild and DMG Installers, macOS x86_64+arm64
runs-on: macos-latest
steps:
@@ -102,6 +117,27 @@ jobs:
repository: projectM-visualizer/presets-milkdrop-texture-pack
path: presets-milkdrop-texture-pack
+ - name: Import Code Signing Certificates
+ env:
+ MACOS_CERTIFICATE_APPLICATION: ${{ secrets.MACOS_CERTIFICATE_APPLICATION }}
+ MACOS_CERTIFICATE_INSTALLER: ${{ secrets.MACOS_CERTIFICATE_INSTALLER }}
+ MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+ run: |
+ echo "$MACOS_CERTIFICATE_APPLICATION" | base64 --decode > app_cert.p12 && chmod 600 app_cert.p12
+ echo "$MACOS_CERTIFICATE_INSTALLER" | base64 --decode > installer_cert.p12 && chmod 600 installer_cert.p12
+
+ KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
+ security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
+ security default-keychain -s build.keychain
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
+
+ security import app_cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
+ security import installer_cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/productsign
+
+ security set-key-partition-list -S apple-tool:,apple:,codesign:,productbuild: -s -k "$KEYCHAIN_PASSWORD" build.keychain
+
+ rm app_cert.p12 installer_cert.p12
+
- name: Build projectMSDL
run: |
mkdir cmake-build-frontend-sdl2
@@ -112,19 +148,162 @@ jobs:
"-DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install-libprojectm;${GITHUB_WORKSPACE}/install-poco;${GITHUB_WORKSPACE}/install-libsdl2" \
"-DPRESET_DIRS=${{ github.workspace }}/presets-cream-of-the-crop" \
"-DTEXTURE_DIRS=${{ github.workspace }}/presets-milkdrop-texture-pack/textures" \
- '-DDEFAULT_CONFIG_PATH=${application.dir}/../share/projectMSDL/' \
- '-DDEFAULT_PRESETS_PATH=${application.dir}/../share/projectMSDL/presets/' \
- '-DDEFAULT_TEXTURES_PATH=${application.dir}/../share/projectMSDL/textures/' \
-DENABLE_INSTALL_BDEPS=ON
cmake --build cmake-build-frontend-sdl2 --parallel
+ cmake --install cmake-build-frontend-sdl2 --prefix "${{ github.workspace }}/install"
+
+ - name: Sign Application Bundle
+ run: |
+ APP_PATH="${{ github.workspace }}/install/projectM.app"
+ IDENTITY="Developer ID Application: Mischa Spiegelmock (5926VBQM6Y)"
+ ENTITLEMENTS="${{ github.workspace }}/frontend-sdl2/src/resources/projectMSDL.entitlements"
+
+ # Sign frameworks first (SDL2, Poco, etc.)
+ if [ -d "$APP_PATH/Contents/Frameworks" ]; then
+ find "$APP_PATH/Contents/Frameworks" \( -name "*.dylib" -o -name "*.framework" \) -exec \
+ codesign --force --options runtime --sign "$IDENTITY" {} \;
+ fi
+
+ # Sign plugins if present
+ if [ -d "$APP_PATH/Contents/PlugIns" ]; then
+ find "$APP_PATH/Contents/PlugIns" -name "*.dylib" -exec \
+ codesign --force --options runtime --sign "$IDENTITY" {} \;
+ fi
- - name: Package projectMSDL
+ # Sign the main executable with entitlements
+ codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" \
+ "$APP_PATH/Contents/MacOS/projectM"
+
+ # Sign the entire bundle with entitlements
+ codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_PATH"
+
+ # Verify
+ codesign --verify --deep --strict "$APP_PATH"
+
+ - name: Notarize Application Bundle
+ env:
+ API_KEY_BASE64: ${{ secrets.MACOS_NOTARY_API_KEY }}
+ API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
+ API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
run: |
- cd cmake-build-frontend-sdl2
- cpack -G productbuild
+ mkdir -p ~/.private_keys
+ echo "$API_KEY_BASE64" | base64 --decode > ~/.private_keys/AuthKey_${API_KEY_ID}.p8
+ chmod 600 ~/.private_keys/AuthKey_${API_KEY_ID}.p8
+
+ ditto -c -k --keepParent \
+ "${{ github.workspace }}/install/projectM.app" \
+ "projectM-notarize.zip"
+
+ xcrun notarytool submit "projectM-notarize.zip" \
+ --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
+ --key-id "$API_KEY_ID" \
+ --issuer "$API_ISSUER_ID" \
+ --wait
- - name: Upload Artifact
+ xcrun stapler staple "${{ github.workspace }}/install/projectM.app"
+
+ - name: Upload .app Bundle Artifact
uses: actions/upload-artifact@v4
with:
- name: projectMSDL-macOS-Universal
- path: cmake-build-frontend-sdl2/*.pkg
+ name: projectMSDL-macOS-Universal-APP
+ path: install/
+
+ - name: Create projectMSDL PKG Installer
+ run: |
+ # Get version from CMake cache
+ VERSION=$(grep 'CMAKE_PROJECT_VERSION:STATIC=' cmake-build-frontend-sdl2/CMakeCache.txt | grep -Eo '([0-9.]+)')
+
+ # Build component package from signed app
+ pkgbuild \
+ --root "${{ github.workspace }}/install" \
+ --identifier "org.projectm-visualizer.projectmsdl" \
+ --version "$VERSION" \
+ --install-location "/Applications" \
+ --component-plist "frontend-sdl2/src/resources/projectMSDL-component.plist" \
+ "projectMSDL-component.pkg"
+
+ # Build unsigned product archive
+ productbuild \
+ --distribution "frontend-sdl2/src/resources/distribution.xml" \
+ --package-path "." \
+ --resources "frontend-sdl2/src/resources" \
+ "projectM-${VERSION}-macOS-universal-unsigned.pkg"
+
+ # Sign the package with productsign
+ productsign \
+ --sign "Developer ID Installer: Mischa Spiegelmock (5926VBQM6Y)" \
+ "projectM-${VERSION}-macOS-universal-unsigned.pkg" \
+ "projectM-${VERSION}-macOS-universal.pkg"
+
+ rm "projectM-${VERSION}-macOS-universal-unsigned.pkg"
+
+ - name: Notarize PKG Installer
+ env:
+ API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
+ API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
+ run: |
+ PKG_FILE=$(ls projectM-*.pkg | head -1)
+
+ xcrun notarytool submit "$PKG_FILE" \
+ --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
+ --key-id "$API_KEY_ID" \
+ --issuer "$API_ISSUER_ID" \
+ --wait
+
+ xcrun stapler staple "$PKG_FILE"
+
+ - name: Upload PKG Installer Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: projectMSDL-macOS-Universal-PKG
+ path: projectM-*.pkg
+
+ - name: Create projectMSDL DMG Image
+ run: |
+ # Get version from CMake cache
+ VERSION=$(grep 'CMAKE_PROJECT_VERSION:STATIC=' cmake-build-frontend-sdl2/CMakeCache.txt | grep -Eo '([0-9.]+)')
+
+ # Install create-dmg
+ brew install create-dmg
+
+ # Build and sign DMG image
+ create-dmg \
+ --volname "projectM Installer" \
+ --volicon "frontend-sdl2/src/resources/icons/icon.icns" \
+ --background "frontend-sdl2/src/resources/dmg_background.png" \
+ --window-pos 200 120 \
+ --window-size 800 400 \
+ --icon-size 100 \
+ --icon "projectM.app" 200 190 \
+ --hide-extension "projectM.app" \
+ --app-drop-link 600 185 \
+ --codesign "Developer ID Application: Mischa Spiegelmock (5926VBQM6Y)" \
+ "projectM-${VERSION}-macOS-universal.dmg" \
+ "install/"
+
+ - name: Notarize DMG Installer
+ env:
+ API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
+ API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
+ run: |
+ DMG_FILE=$(ls projectM-*.dmg | head -1)
+
+ xcrun notarytool submit "$DMG_FILE" \
+ --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
+ --key-id "$API_KEY_ID" \
+ --issuer "$API_ISSUER_ID" \
+ --wait
+
+ xcrun stapler staple "$DMG_FILE"
+
+ - name: Upload DMG Installer Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: projectMSDL-macOS-Universal-DMG
+ path: projectM-*.dmg
+
+ - name: Cleanup Notarization API Key
+ if: ${{ always() }}
+ env:
+ API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
+ run: rm -f ~/.private_keys/AuthKey_${API_KEY_ID}.p8
diff --git a/.github/workflows/release-windows.yaml b/.github/workflows/release-windows.yaml
index cdb6f49..dddf542 100644
--- a/.github/workflows/release-windows.yaml
+++ b/.github/workflows/release-windows.yaml
@@ -3,7 +3,9 @@
# including projectM.
name: Build Release Package for Windows
-on: workflow_dispatch
+on:
+ workflow_dispatch:
+ workflow_call:
jobs:
build:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000..329a7fc
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,55 @@
+# Unified release workflow
+# Triggers on version tags, builds all platforms, and creates a GitHub release
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ build-linux:
+ name: Build Linux
+ uses: ./.github/workflows/release-linux.yaml
+
+ build-windows:
+ name: Build Windows
+ uses: ./.github/workflows/release-windows.yaml
+
+ build-macos:
+ name: Build macOS
+ uses: ./.github/workflows/release-macos.yaml
+ secrets:
+ MACOS_CERTIFICATE_APPLICATION: ${{ secrets.MACOS_CERTIFICATE_APPLICATION }}
+ MACOS_CERTIFICATE_INSTALLER: ${{ secrets.MACOS_CERTIFICATE_INSTALLER }}
+ MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+ MACOS_NOTARY_API_KEY: ${{ secrets.MACOS_NOTARY_API_KEY }}
+ MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
+ MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
+
+ create-release:
+ name: Create Release
+ needs: [build-linux, build-windows, build-macos]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+
+ - name: Display artifacts
+ run: ls -R artifacts
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
+ files: |
+ artifacts/**/*.pkg
+ artifacts/**/*.deb
+ artifacts/**/*.tar.gz
+ artifacts/**/*.zip
+ artifacts/**/*.msi
diff --git a/packaging-macos.cmake b/packaging-macos.cmake
index f155009..cb34ad9 100644
--- a/packaging-macos.cmake
+++ b/packaging-macos.cmake
@@ -10,8 +10,8 @@ set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/gpl-3
set(CPACK_STRIP_FILES TRUE)
### Productbuild configuration
-set(CPACK_PKGBUILD_IDENTITY_NAME "${CODESIGN_IDENTITY_INSTALLER}")
-set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${CODESIGN_IDENTITY_INSTALLER}")
+set(CPACK_PKGBUILD_IDENTITY_NAME "$ENV{CODESIGN_IDENTITY_INSTALLER}")
+set(CPACK_PRODUCTBUILD_IDENTITY_NAME "$ENV{CODESIGN_IDENTITY_INSTALLER}")
set(CPACK_PRODUCTBUILD_IDENTIFIER "org.projectm-visualizer.projectmsdl")
string(REPLACE ";" "," INSTALL_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ba764dc..4f58bdf 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,6 +31,7 @@ set_target_properties(projectMSDL PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER "org.projectm.frontend.sdl2"
MACOSX_BUNDLE_ICON_FILE "projectMSDL.icns"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${projectMSDL_VERSION}"
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist.in"
)
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
diff --git a/src/resources/Info.plist.in b/src/resources/Info.plist.in
new file mode 100644
index 0000000..84bb6ce
--- /dev/null
+++ b/src/resources/Info.plist.in
@@ -0,0 +1,30 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ @MACOSX_BUNDLE_EXECUTABLE_NAME@
+ CFBundleIconFile
+ @MACOSX_BUNDLE_ICON_FILE@
+ CFBundleIdentifier
+ @MACOSX_BUNDLE_GUI_IDENTIFIER@
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ @MACOSX_BUNDLE_BUNDLE_NAME@
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ @MACOSX_BUNDLE_SHORT_VERSION_STRING@
+ CFBundleVersion
+ @MACOSX_BUNDLE_BUNDLE_VERSION@
+ NSHumanReadableCopyright
+ @MACOSX_BUNDLE_COPYRIGHT@
+ NSHighResolutionCapable
+
+ NSMicrophoneUsageDescription
+ projectM requires microphone access to visualize audio input.
+
+
diff --git a/src/resources/distribution.xml b/src/resources/distribution.xml
new file mode 100644
index 0000000..59826b8
--- /dev/null
+++ b/src/resources/distribution.xml
@@ -0,0 +1,17 @@
+
+
+ projectM
+ org.projectm-visualizer
+
+
+
+
+
+
+
+
+
+
+
+ projectMSDL-component.pkg
+
diff --git a/src/resources/dmg_background.png b/src/resources/dmg_background.png
new file mode 100644
index 0000000..a554f39
Binary files /dev/null and b/src/resources/dmg_background.png differ
diff --git a/src/resources/projectMSDL-component.plist b/src/resources/projectMSDL-component.plist
index 77e470a..b33c936 100644
--- a/src/resources/projectMSDL-component.plist
+++ b/src/resources/projectMSDL-component.plist
@@ -12,7 +12,7 @@
BundleOverwriteAction
upgrade
RootRelativeBundlePath
- Applications/projectM.app
+ projectM.app
-
\ No newline at end of file
+
diff --git a/src/resources/projectMSDL.entitlements b/src/resources/projectMSDL.entitlements
new file mode 100644
index 0000000..d459cb2
--- /dev/null
+++ b/src/resources/projectMSDL.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.device.audio-input
+
+
+