diff --git a/CMakeLists.txt b/CMakeLists.txt index 009671407..bc594050e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,14 @@ if(APPLE) enable_language(OBJC OBJCXX) endif() +if(APPLE) + option(BUILD_FOR_MAC_APP_STORE "Build for Mac App Store (MAS): disables Sparkle + any localhost port-listener OAuth redirect server, and uses MAS-focused defaults." OFF) +else() + set(BUILD_FOR_MAC_APP_STORE OFF) +endif() + +set(MACOS_BUNDLE_ID "com.dryark.strawberry" CACHE STRING "macOS bundle identifier (CFBundleIdentifier)") + if(POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif() @@ -32,18 +40,24 @@ if(LINUX) endif() if(APPLE) - # Find Sparkle early so cmake/Dmg.cmake (deploy target) can bundle it into the app. - # Sparkle is optional; if not found, update functionality is disabled. - find_library(SPARKLE Sparkle - PATHS - /Library/Frameworks - /System/Library/Frameworks - /opt/homebrew/Frameworks - /opt/homebrew/opt/sparkle-framework/Frameworks - /usr/local/Frameworks - /usr/local/opt/sparkle-framework/Frameworks - PATH_SUFFIXES Frameworks - ) + if(BUILD_FOR_MAC_APP_STORE) + # MAS builds: Sparkle (and QtSparkle) must be disabled. + set(ENABLE_SPARKLE OFF CACHE BOOL "Sparkle integration" FORCE) + set(ENABLE_QTSPARKLE OFF CACHE BOOL "QtSparkle integration" FORCE) + else() + # Find Sparkle early so cmake/Dmg.cmake (deploy target) can bundle it into the app. + # Sparkle is optional; if not found, update functionality is disabled. + find_library(SPARKLE Sparkle + PATHS + /Library/Frameworks + /System/Library/Frameworks + /opt/homebrew/Frameworks + /opt/homebrew/opt/sparkle-framework/Frameworks + /usr/local/Frameworks + /usr/local/opt/sparkle-framework/Frameworks + PATH_SUFFIXES Frameworks + ) + endif() include(cmake/Dmg.cmake) endif() diff --git a/build_tools/macos/build_app.sh b/build_tools/macos/build_app.sh index 254dcdcbf..ebf9d9663 100755 --- a/build_tools/macos/build_app.sh +++ b/build_tools/macos/build_app.sh @@ -91,7 +91,7 @@ run_with_heartbeat() { usage() { cat <<'EOF' Usage: - ./build_tools/macos/build_app.sh [--debug|--release] [--deploy] [--dmg] [--clean] [--build-dir ] + ./build_tools/macos/build_app.sh [--debug|--release] [--mas] [--deploy] [--dmg] [--clean] [--build-dir ] What it does: - Configures and builds Strawberry with CMake + Ninja @@ -100,6 +100,7 @@ What it does: Options: --release Release build (default) --debug Debug build + --mas Build for Mac App Store (BUILD_FOR_MAC_APP_STORE=ON). Disables Sparkle/QtSparkle and any localhost OAuth redirect listener. --deploy Run: cmake --build --target deploy --dmg Run: cmake --build --target dmg (implies --deploy) --clean Delete the build dir before configuring @@ -109,6 +110,7 @@ EOF } config="Release" +do_mas=0 do_deploy=0 do_dmg=0 do_clean=0 @@ -118,6 +120,7 @@ while [[ $# -gt 0 ]]; do case "$1" in --release) config="Release"; shift ;; --debug) config="Debug"; shift ;; + --mas) do_mas=1; shift ;; --deploy) do_deploy=1; shift ;; --dmg) do_dmg=1; do_deploy=1; shift ;; --clean) do_clean=1; shift ;; @@ -165,6 +168,9 @@ fi echo "==> [$(ts)] Repo: ${repo_root}" echo "==> [$(ts)] Build dir: ${build_dir}" echo "==> [$(ts)] Config: ${config}" +if [[ "$do_mas" -eq 1 ]]; then + echo "==> [$(ts)] MAS: enabled (BUILD_FOR_MAC_APP_STORE=ON)" +fi if [[ "$do_clean" -eq 1 ]]; then echo "==> [$(ts)] Cleaning build dir" @@ -213,6 +219,11 @@ cmake_prefix_path="${qt_prefix};${brew_prefix}" cmake_extra_args=() +# Mac App Store build mode +if [[ "$do_mas" -eq 1 ]]; then + cmake_extra_args+=("-DBUILD_FOR_MAC_APP_STORE=ON") +fi + # Optional: override Sparkle update feed / key for your own published builds. # Example: # export SPARKLE_FEED_URL="https://example.com/appcast.xml" diff --git a/build_tools/macos/export_compliance/EXPORT_COMPLIANCE.txt b/build_tools/macos/export_compliance/EXPORT_COMPLIANCE.txt new file mode 100644 index 000000000..69da47119 --- /dev/null +++ b/build_tools/macos/export_compliance/EXPORT_COMPLIANCE.txt @@ -0,0 +1,26 @@ +Encryption Export Compliance Statement (EAR) + +App Name: Strawberry +Bundle ID: @BUNDLE_ID@ +Version: @VERSION@ +Developer: @DEVELOPER@ +Date: @DATE@ +Contact: @CONTACT@ + +Statement +--------- +This app uses encryption only for standard network communications security (for example, TLS/HTTPS connections to web services). + +The app does not implement proprietary or non-standard encryption algorithms, and it does not ship its own cryptographic library in place of Apple’s operating system encryption. + +The app uses only encryption provided by Apple’s operating system (e.g., Apple-provided TLS stacks accessed through system frameworks used by the app and its dependencies). + +The app is not a VPN client/server, does not provide end-to-end encrypted messaging, and does not provide user-controlled key management or custom cryptographic functionality beyond standard transport security. + +Accordingly, the app’s use of encryption is believed to qualify as exempt under U.S. export regulations for mass-market software using standard OS-provided encryption. + +Reference +--------- +Apple: Complying with Encryption Export Regulations +https://developer.apple.com/documentation/security/complying-with-encryption-export-regulations + diff --git a/build_tools/macos/export_compliance/make_pdf.sh b/build_tools/macos/export_compliance/make_pdf.sh new file mode 100755 index 000000000..ec562d717 --- /dev/null +++ b/build_tools/macos/export_compliance/make_pdf.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./build_tools/macos/export_compliance/make_pdf.sh \ + --bundle-id com.dryark.strawberry \ + --version 1.2.3 \ + --developer "Dry Ark LLC" \ + --contact "support@example.com" + +Outputs (in the same folder as this script): + - EXPORT_COMPLIANCE.filled.txt + - EXPORT_COMPLIANCE.pdf + +Notes: + - Uses macOS built-in /usr/bin/textutil + /usr/sbin/cupsfilter to generate the PDF. +EOF +} + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +template="${script_dir}/EXPORT_COMPLIANCE.txt" +filled="${script_dir}/EXPORT_COMPLIANCE.filled.txt" +pdf="${script_dir}/EXPORT_COMPLIANCE.pdf" + +bundle_id="" +version="" +developer="" +contact="" +date_str="$(date +%Y-%m-%d)" + +while [[ $# -gt 0 ]]; do + case "$1" in + --bundle-id) bundle_id="${2:-}"; shift 2 ;; + --version) version="${2:-}"; shift 2 ;; + --developer) developer="${2:-}"; shift 2 ;; + --contact) contact="${2:-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown arg: $1" >&2; usage; exit 2 ;; + esac +done + +if [[ -z "$bundle_id" || -z "$version" || -z "$developer" || -z "$contact" ]]; then + echo "Error: missing required args." >&2 + usage + exit 2 +fi + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "Error: This script is for macOS only (uses textutil)." >&2 + exit 1 +fi + +if [[ ! -f "$template" ]]; then + echo "Error: missing template file: $template" >&2 + exit 1 +fi + +if [[ ! -x /usr/bin/textutil ]]; then + echo "Error: /usr/bin/textutil not found. This should exist on macOS." >&2 + exit 1 +fi +if [[ ! -x /usr/sbin/cupsfilter ]]; then + echo "Error: /usr/sbin/cupsfilter not found. This should exist on macOS." >&2 + exit 1 +fi + +escape_sed_repl() { + # Escape characters that are special in sed replacement strings: \ & and delimiter | + # bash 3.2 compatible + echo "$1" | sed -e 's/[\\&|]/\\&/g' +} + +bundle_id_esc="$(escape_sed_repl "$bundle_id")" +version_esc="$(escape_sed_repl "$version")" +developer_esc="$(escape_sed_repl "$developer")" +contact_esc="$(escape_sed_repl "$contact")" +date_esc="$(escape_sed_repl "$date_str")" + +sed \ + -e "s|@BUNDLE_ID@|${bundle_id_esc}|g" \ + -e "s|@VERSION@|${version_esc}|g" \ + -e "s|@DEVELOPER@|${developer_esc}|g" \ + -e "s|@CONTACT@|${contact_esc}|g" \ + -e "s|@DATE@|${date_esc}|g" \ + "$template" > "$filled" + +rm -f "$pdf" >/dev/null 2>&1 || true + +# textutil does not support direct PDF output on modern macOS; convert to HTML first, then print-to-PDF via cupsfilter. +tmp_html="${script_dir}/EXPORT_COMPLIANCE.tmp.html" +rm -f "$tmp_html" >/dev/null 2>&1 || true +/usr/bin/textutil -convert html "$filled" -output "$tmp_html" >/dev/null + +# Convert HTML to PDF. cupsfilter writes to stdout by default. +/usr/sbin/cupsfilter -i text/html -m application/pdf "$tmp_html" > "$pdf" +rm -f "$tmp_html" >/dev/null 2>&1 || true + +echo "Wrote:" +echo " $filled" +echo " $pdf" + diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt index 4fce209f5..9de58ca04 100644 --- a/dist/CMakeLists.txt +++ b/dist/CMakeLists.txt @@ -10,21 +10,26 @@ if(APPLE) set(LSMinimumSystemVersion 12.0) endif() - # Sparkle (macOS updates) - # These values are embedded into Info.plist and control where the app checks for updates. - # Downstream builders can override on the CMake command line: - # -DSPARKLE_FEED_URL="https://example.com/appcast.xml" - # -DSPARKLE_PUBLIC_ED25519_KEY="base64==" - # - # Defaults preserve upstream behavior, but are intentionally configurable for third-party builds. - if(NOT DEFINED SPARKLE_FEED_URL) - set(SPARKLE_FEED_URL "https://www.strawberrymusicplayer.org/sparkle-macos-@ARCH@") - endif() - if(NOT DEFINED SPARKLE_PUBLIC_ED25519_KEY) - set(SPARKLE_PUBLIC_ED25519_KEY "/OydhYVfypuO2Mf7G6DUqVZWW9G19eFV74qaDCBTOUk=") - endif() + if(BUILD_FOR_MAC_APP_STORE) + # MAS builds must not embed Sparkle update configuration in Info.plist. + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.mas.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist) + else() + # Sparkle (macOS updates) + # These values are embedded into Info.plist and control where the app checks for updates. + # Downstream builders can override on the CMake command line: + # -DSPARKLE_FEED_URL="https://example.com/appcast.xml" + # -DSPARKLE_PUBLIC_ED25519_KEY="base64==" + # + # Defaults preserve upstream behavior, but are intentionally configurable for third-party builds. + if(NOT DEFINED SPARKLE_FEED_URL) + set(SPARKLE_FEED_URL "https://www.strawberrymusicplayer.org/sparkle-macos-@ARCH@") + endif() + if(NOT DEFINED SPARKLE_PUBLIC_ED25519_KEY) + set(SPARKLE_PUBLIC_ED25519_KEY "/OydhYVfypuO2Mf7G6DUqVZWW9G19eFV74qaDCBTOUk=") + endif() - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist) + endif() endif() if(WIN32) diff --git a/dist/macos/Info.mas.plist.in b/dist/macos/Info.mas.plist.in new file mode 100644 index 000000000..1a94bae23 --- /dev/null +++ b/dist/macos/Info.mas.plist.in @@ -0,0 +1,253 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleDevelopmentRegion + English + CFBundleExecutable + strawberry + CFBundleGetInfoString + Strawberry ${STRAWBERRY_VERSION_DISPLAY} + CFBundleIconFile + strawberry.icns + CFBundleIdentifier + @MACOS_BUNDLE_ID@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${STRAWBERRY_VERSION_DISPLAY} + CFBundleName + Strawberry + CFBundlePackageType + APPL + CFBundleShortVersionString + ${STRAWBERRY_VERSION_DISPLAY} + CFBundleVersion + ${STRAWBERRY_VERSION_PACKAGE} + CSResourcesFileMapped + + LSRequiresCarbon + + LSApplicationCategoryType + public.app-category.music + LSMinimumSystemVersion + @LSMinimumSystemVersion@ + ITSAppUsesNonExemptEncryption + + + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + @MACOS_BUNDLE_ID@ + CFBundleURLSchemes + + tidal + + + + + CFBundleDocumentTypes + + + CFBundleTypeOSTypes + + **** + fold + disk + + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + xspf + + CFBundleTypeIconFile + Generic.icns + CFBundleTypeMIMETypes + + application/xspf+xml + + CFBundleTypeName + XSPF Playlist + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + wav + + CFBundleTypeMIMETypes + + audio/x-wav + + CFBundleTypeName + WAVE Audio File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + pls + + CFBundleTypeIconFile + pls.icns + CFBundleTypeName + Shoutcast playlist + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + m3u + + CFBundleTypeIconFile + m3u.icns + CFBundleTypeMIMETypes + + audio/x-mpegurl + + CFBundleTypeName + Playlist file + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + aac + + CFBundleTypeIconFile + mpeg4.icns + CFBundleTypeName + AAC file + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + ogg + ogx + ogm + + CFBundleTypeIconFile + ogg.icns + CFBundleTypeMIMETypes + + audio/ogg + + CFBundleTypeName + Ogg Vorbis File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + oga + + CFBundleTypeIconFile + ogg.icns + CFBundleTypeMIMETypes + + audio/ogg + + CFBundleTypeName + Ogg Audio File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + wma + + CFBundleTypeIconFile + wma.icns + CFBundleTypeName + WIndows Media Audio + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + mp3 + + CFBundleTypeIconFile + mp3.icns + CFBundleTypeMIMETypes + + audio/mpeg + + CFBundleTypeName + MPEG Audio Layer 3 + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + 3gp + + CFBundleTypeIconFile + generic.icns + CFBundleTypeName + 3GPP File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + m4a + + CFBundleTypeIconFile + mpeg4.icns + CFBundleTypeName + MPEG-4 Audio File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + mpc + + CFBundleTypeIconFile + generic.icns + CFBundleTypeName + Musepack Audio File + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + flac + + CFBundleTypeIconFile + generic.icns + CFBundleTypeMIMETypes + + audio/flac + + CFBundleTypeName + FLAC Audio File + CFBundleTypeRole + Viewer + + + + + diff --git a/dist/macos/Info.plist.in b/dist/macos/Info.plist.in index 607cfe952..ec8453023 100644 --- a/dist/macos/Info.plist.in +++ b/dist/macos/Info.plist.in @@ -13,7 +13,7 @@ CFBundleIconFile strawberry.icns CFBundleIdentifier - org.strawberrymusicplayer.strawberry + @MACOS_BUNDLE_ID@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString @@ -34,6 +34,8 @@ public.app-category.music LSMinimumSystemVersion @LSMinimumSystemVersion@ + ITSAppUsesNonExemptEncryption + SUFeedURL @SPARKLE_FEED_URL@ SUPublicEDKey @@ -44,7 +46,7 @@ CFBundleTypeRole Viewer CFBundleURLName - org.strawberrymusicplayer.strawberry + @MACOS_BUNDLE_ID@ CFBundleURLSchemes tidal diff --git a/dist/macos/entitlements.mas.plist b/dist/macos/entitlements.mas.plist new file mode 100644 index 000000000..bf227347b --- /dev/null +++ b/dist/macos/entitlements.mas.plist @@ -0,0 +1,20 @@ + + + + + + com.apple.security.app-sandbox + + + + com.apple.security.network.client + + + + com.apple.security.files.user-selected.read-write + + + + + + diff --git a/src/config.h.in b/src/config.h.in index 84b474d01..afcc42f7a 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -7,6 +7,8 @@ #cmakedefine USE_INSTALL_PREFIX +#cmakedefine BUILD_FOR_MAC_APP_STORE + #cmakedefine HAVE_BACKTRACE #cmakedefine HAVE_ALSA #cmakedefine HAVE_PULSE diff --git a/src/core/localredirectserver.cpp b/src/core/localredirectserver.cpp index db4664649..44108668a 100644 --- a/src/core/localredirectserver.cpp +++ b/src/core/localredirectserver.cpp @@ -52,6 +52,12 @@ LocalRedirectServer::~LocalRedirectServer() { bool LocalRedirectServer::Listen() { +#ifdef BUILD_FOR_MAC_APP_STORE + success_ = false; + error_ = "Local redirect server is disabled in Mac App Store builds."_L1; + return false; +#endif + if (!listen(QHostAddress::LocalHost, static_cast(port_))) { success_ = false; error_ = errorString(); diff --git a/src/core/oauthenticator.cpp b/src/core/oauthenticator.cpp index e60127b9d..2fc5c5a21 100644 --- a/src/core/oauthenticator.cpp +++ b/src/core/oauthenticator.cpp @@ -236,6 +236,14 @@ void OAuthenticator::Authenticate() { return; } + // Mac App Store builds: do not start any localhost listening redirect server. +#ifdef BUILD_FOR_MAC_APP_STORE + if (use_local_redirect_server_) { + Q_EMIT AuthenticationFinished(false, tr("This authentication flow is disabled in Mac App Store builds.")); + return; + } +#endif + QUrl redirect_url(redirect_url_); if (use_local_redirect_server_) {