Custom macOS Client Generator #82
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Custom macOS Client Generator | |
run-name: Custom macOS Client Generator | |
on: | |
workflow_dispatch: | |
inputs: | |
server: | |
description: 'Rendezvous Server' | |
required: true | |
default: '' | |
type: string | |
key: | |
description: 'Public Key' | |
required: true | |
default: '' | |
type: string | |
apiServer: | |
description: 'API Server' | |
required: true | |
default: '' | |
type: string | |
custom: | |
description: "Custom JSON" | |
required: true | |
default: '' | |
type: string | |
uuid: | |
description: "uuid of request" | |
required: true | |
default: '' | |
type: string | |
iconlink: | |
description: "icon link" | |
required: false | |
default: 'false' | |
type: string | |
logolink: | |
description: "logo link" | |
required: false | |
default: 'false' | |
type: string | |
appname: | |
description: "app name" | |
required: true | |
default: 'rustdesk' | |
type: string | |
filename: | |
description: "Filename" | |
required: true | |
default: 'rustdesk' | |
type: string | |
extras: | |
description: "extra inputs in json" | |
required: true | |
default: '{}' | |
type: string | |
env: | |
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html | |
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html | |
MAC_RUST_VERSION: "1.81" | |
CARGO_NDK_VERSION: "3.1.2" | |
SCITER_ARMV7_CMAKE_VERSION: "3.29.7" | |
SCITER_NASM_DEBVERSION: "2.14-1" | |
LLVM_VERSION: "15.0.6" | |
FLUTTER_VERSION: "3.24.5" | |
ANDROID_FLUTTER_VERSION: "3.24.5" # >= 3.16 is very slow on my android phone, but work well on most of others. We may switch to new flutter after changing to texture rendering (I believe it can solve my problem). | |
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" # for arm64 linux because official Dart SDK does not work | |
FLUTTER_ELINUX_VERSION: "3.16.9" | |
TAG_NAME: "${{ inputs.upload-tag }}" | |
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" | |
# vcpkg version: 2024.07.12 | |
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c" | |
VERSION: "${{ fromJson(inputs.extras).version }}" | |
NDK_VERSION: "r27c" | |
#signing keys env variable checks | |
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" | |
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}" | |
UPLOAD_ARTIFACT: 'true' | |
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}" | |
STATUS_URL: "${{ secrets.GENURL }}/updategh" | |
jobs: | |
generate-bridge: | |
uses: ./.github/workflows/bridge.yml | |
with: | |
version: ${{ fromJson(inputs.extras).version }} | |
build-for-macos-flutter: | |
name: Build macOS | |
runs-on: macos-latest | |
needs: [generate-bridge] | |
strategy: | |
fail-fast: false | |
matrix: | |
job: | |
# - { | |
# target: x86_64-apple-darwin, | |
# os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel | |
# extra-build-args: "", | |
# arch: x86_64, | |
# vcpkg-triplet: x64-osx, | |
# } | |
- { | |
target: aarch64-apple-darwin, | |
os: macos-latest, | |
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296 | |
extra-build-args: "--screencapturekit", | |
arch: aarch64, | |
vcpkg-triplet: arm64-osx, | |
} | |
steps: | |
- name: Export GitHub Actions cache environment variables | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); | |
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); | |
- name: Set rdgen value | |
if: ${{ fromJson(inputs.extras).rdgen == 'true' }} | |
run: | | |
echo "STATUS_URL=${{ secrets.GENURL }}/updategh" >> $env:GITHUB_ENV | |
- name: Set rdgen value | |
if: ${{ fromJson(inputs.extras).rdgen == 'false' }} | |
run: | | |
echo "STATUS_URL=${{ inputs.apiServer }}/api/updategh" >> $env:GITHUB_ENV | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "5% complete"}' | |
- name: Checkout source code | |
if: ${{ env.VERSION != 'master' }} | |
uses: actions/checkout@v4 | |
with: | |
repository: rustdesk/rustdesk | |
ref: refs/tags/${{ env.VERSION }} | |
submodules: recursive | |
- name: Checkout source code | |
if: ${{ env.VERSION == 'master' }} | |
uses: actions/checkout@v4 | |
with: | |
repository: rustdesk/rustdesk | |
submodules: recursive | |
- name: Restore bridge files | |
uses: actions/download-artifact@master | |
with: | |
name: bridge-artifact | |
path: ./ | |
- name: Install imagemagick and potrace and nasm and and | |
shell: bash | |
run: | | |
brew install imagemagick potrace nasm cmake gcc wget ninja | |
echo "$(brew --prefix imagemagick)/bin" >> $GITHUB_PATH | |
- name: Update macOS Info.plist and settings | |
continue-on-error: false | |
shell: bash | |
run: | | |
# MACSTUFF Backup the original files | |
cp ./flutter/macos/Runner/Info.plist ./flutter/macos/Runner/Info.plist.bak | |
cp ./flutter/macos/Runner/Configs/AppInfo.xcconfig ./flutter/macos/Runner/Configs/AppInfo.xcconfig.bak | |
# MACSTUFF Update Info.plist | |
sed -i '' -e 's|<key>CFBundleName</key>.*<string>.*</string>|<key>CFBundleName</key>\n\t<string>${{ inputs.appname }}</string>|' ./flutter/macos/Runner/Info.plist | |
sed -i '' -e 's|<key>CFBundleDisplayName</key>.*<string>.*</string>|<key>CFBundleDisplayName</key>\n\t<string>${{ inputs.appname }}</string>|' ./flutter/macos/Runner/Info.plist | |
sed -i '' -e 's|<key>CFBundleIdentifier</key>.*<string>.*</string>|<key>CFBundleIdentifier</key>\n\t<string>com.${{ inputs.appname }}.app</string>|' ./flutter/macos/Runner/Info.plist | |
# sed -i '' '/<key>NSHumanReadableCopyright<\/key>/{n;s/<string>.*<\/string>/<string>Copyright 2025 ${{ inputs.appname }}. All rights reserved.<\/string>/;}' ./flutter/macos/Runner/Info.plist | |
# MACSTUFF Update AppInfo.xcconfig | |
sed -i '' -e 's|PRODUCT_NAME = .*|PRODUCT_NAME = ${{ inputs.appname }}|' ./flutter/macos/Runner/Configs/AppInfo.xcconfig | |
sed -i '' -e 's|PRODUCT_BUNDLE_IDENTIFIER = .*|PRODUCT_BUNDLE_IDENTIFIER = com.${{ inputs.appname }}.app|' ./flutter/macos/Runner/Configs/AppInfo.xcconfig | |
sed -i '' -e 's|Purslane Ltd.|${{ inputs.appname }}|' ./flutter/macos/Runner/Configs/AppInfo.xcconfig | |
# Keep DEVELOPMENT_TEAM if it exists, don't blank it out | |
sed -i -e 's|Purslane Ltd.|${{ inputs.appname }}|' ./Cargo.toml | |
sed -i -e 's|Purslane Ltd|${{ inputs.appname }}|' ./libs/portable/Cargo.toml | |
# Update Xcode project settings | |
sed -i '' -e 's/PRODUCT_NAME = "RustDesk"/PRODUCT_NAME = "${{ inputs.appname }}"/' ./flutter/macos/Runner.xcodeproj/project.pbxproj | |
sed -i '' -e 's/PRODUCT_BUNDLE_IDENTIFIER = ".*"/PRODUCT_BUNDLE_IDENTIFIER = "com.${{ inputs.appname }}.app"/' ./flutter/macos/Runner.xcodeproj/project.pbxproj | |
# Don't modify DEVELOPMENT_TEAM in project.pbxproj | |
# Update CMake settings | |
if [ -f "./flutter/macos/CMakeLists.txt" ]; then | |
sed -i '' -e 's/set(BINARY_NAME ".*")/set(BINARY_NAME "${{ inputs.appname }}")/' ./flutter/macos/CMakeLists.txt | |
fi | |
# Update Podfile - keep the target as 'Runner' | |
# sed -i '' -e 's/target '"'"'Runner'"'"' do/target '"'"'${{ inputs.appname }}'"'"' do/' ./flutter/macos/Podfile | |
sed -i '' -e 's/target '"'"'Runner'"'"' do/target '"'"'Runner'"'"' do/' ./flutter/macos/Podfile | |
cp ./src/lang/en.rs ./src/lang/en.rs.bak | |
cp ./src/lang/nl.rs ./src/lang/nl.rs.bak | |
find ./src/lang -name "*.rs" -exec sed -i '' -e 's|RustDesk|${{ inputs.appname }}|' {} \; | |
sed -i '' -e 's|RustDesk|${{ inputs.appname }}|' ./src/lang/nl.rs | |
sed -i '' -e 's|Homepage: https://rustdesk.com|Homepage: ${{ fromJson(inputs.extras).urlLink }}|' ./build.py | |
sed -i '' -e "s|launchUrl(Uri.parse('https://rustdesk.com'));|launchUrl(Uri.parse('${{ fromJson(inputs.extras).urlLink }}'));|" ./flutter/lib/common.dart | |
sed -i '' -e "s|launchUrlString('https://rustdesk.com');|launchUrlString('${{ fromJson(inputs.extras).urlLink }}');|" ./flutter/lib/desktop/pages/desktop_setting_page.dart | |
sed -i '' -e "s|launchUrlString('https://rustdesk.com/privacy.html')|launchUrlString('${{ fromJson(inputs.extras).urlLink }}/privacy.html')|" ./flutter/lib/desktop/pages/desktop_setting_page.dart | |
sed -i '' -e "s|const url = 'https://rustdesk.com/';|const url = '${{ fromJson(inputs.extras).urlLink }}';|" ./flutter/lib/mobile/pages/settings_page.dart | |
sed -i '' -e "s|launchUrlString('https://rustdesk.com/privacy.html')|launchUrlString('${{ fromJson(inputs.extras).urlLink }}/privacy.html')|" ./flutter/lib/mobile/pages/settings_page.dart | |
sed -i '' -e "s|https://rustdesk.com/privacy.html|${{ fromJson(inputs.extras).urlLink }}/privacy.html|" ./flutter/lib/desktop/pages/install_page.dart | |
- name: change download link to custom | |
if: fromJson(inputs.extras).downloadLink != 'https://rustdesk.com/download' | |
continue-on-error: true | |
shell: bash | |
run: | | |
sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/desktop/pages/desktop_home_page.dart | |
sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/mobile/pages/connection_page.dart | |
sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./src/ui/index.tis | |
# Update slogan | |
#sed -i '' '/<key>NSHumanReadableCopyright<\/key>/{n;s/<string>.*<\/string>/<string>Copyright 2025 ${{ inputs.appname }}. All rights reserved.<\/string>/;}' ./flutter/macos/Runner/Info.plist | |
# Update slogan - About in en.rs | |
sed -i '' -e 's/("Slogan_tip", "Made with heart in this chaotic world!")/("Slogan_tip", "Powered by ${{ inputs.appname }}")/' ./src/lang/en.rs | |
sed -i '' -e 's/("About RustDesk", "")/("About RustDesk", "About ${{ inputs.appname }}")/' ./src/lang/en.rs | |
# Update slogan - About in nl.rs | |
sed -i '' -e 's/("Slogan_tip", "Ontwikkeld met het hart voor deze chaotische wereld!")/("Slogan_tip", "Powered by ${{ inputs.appname }}")/' ./src/lang/nl.rs | |
sed -i '' -e 's/("Your Desktop", "Uw Bureaublad")/("Your Desktop", "Uw ${{ inputs.appname }}")/' ./src/lang/nl.rs | |
sed -i '' -e 's/("About RustDesk", "Over RustDesk")/("About RustDesk", "Over ${{ inputs.appname }}")/' ./src/lang/nl.rs | |
sed -i '' -e 's/("About", "Over")/("About", "Over ${{ inputs.appname }}")/' ./src/lang/nl.rs | |
sed -i -e 's|rs-ny.rustdesk.com|${{ inputs.server }}|' ./libs/hbb_common/src/config.rs | |
sed -i -e 's|OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=|${{ inputs.key }}|' ./libs/hbb_common/src/config.rs | |
sed -i -e 's|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/allowCustom.diff | |
git apply allowCustom.diff | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/removeSetupServerTip.diff | |
git apply removeSetupServerTip.diff | |
# Update pubspec.yaml with proper YAML formatting | |
cp ./flutter/pubspec.yaml ./flutter/pubspec.yaml.bak | |
echo " archive: ^3.6.1" > ./flutter/temp_dependency.txt | |
awk '/intl:/{print;system("cat ./flutter/temp_dependency.txt");next}1' ./flutter/pubspec.yaml > ./flutter/pubspec.yaml.tmp | |
mv ./flutter/pubspec.yaml.tmp ./flutter/pubspec.yaml | |
rm ./flutter/temp_dependency.txt | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "10% complete"}' | |
- name: Install flutter | |
uses: subosito/[email protected] | |
with: | |
channel: "stable" | |
flutter-version: ${{ env.FLUTTER_VERSION }} | |
cache: true | |
- name: Patch flutter | |
continue-on-error: true | |
run: | | |
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter))) | |
cd $(dirname $(dirname $(which flutter))) | |
[[ "3.24.4" == 3.24.5 ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff | |
- name: Workaround for flutter issue | |
shell: bash | |
run: | | |
cd "$(dirname "$(which flutter)")" | |
# https://github.com/flutter/flutter/issues/1.3.43 | |
sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart | |
grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart | |
- name: Install Rust toolchain | |
uses: dtolnay/rust-toolchain@v1 | |
with: | |
toolchain: ${{ env.MAC_RUST_VERSION }} | |
targets: ${{ matrix.job.target }} | |
components: "rustfmt" | |
- uses: Swatinem/rust-cache@v2 | |
with: | |
prefix-key: ${{ matrix.job.os }} | |
- name: Magick stuff for macOS | |
if: ${{ inputs.iconlink != 'false' }} | |
continue-on-error: false | |
shell: bash | |
run: | | |
# Create all necessary directories first | |
mkdir -p ./res | |
mkdir -p ./flutter/assets | |
mkdir -p ./flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset | |
mkdir -p ./macos/Runner/Assets.xcassets/AppIcon.appiconset | |
mkdir -p ./rustdesk/data/flutter_assets/assets | |
# Download icon using curl with additional SSL options | |
curl -k -L --tlsv1.2 --proto =https --ssl-reqd \ | |
-H "User-Agent: Mozilla/5.0" \ | |
"${{ fromJson(inputs.iconlink).url }}/get_png?filename=${{ fromJson(inputs.iconlink).file }}&uuid=${{ fromJson(inputs.iconlink).uuid }}" \ | |
-o ./res/icon.png || wget --no-check-certificate -O ./res/icon.png "${{ fromJson(inputs.iconlink).url }}/get_png?filename=${{ fromJson(inputs.iconlink).file }}&uuid=${{ fromJson(inputs.iconlink).uuid }}" | |
# Backup existing files (if they exist) | |
[ -f "./res/32x32.png" ] && mv ./res/32x32.png ./res/32x32.png.bak | |
[ -f "./res/64x64.png" ] && mv ./res/64x64.png ./res/64x64.png.bak | |
[ -f "./res/128x128.png" ] && mv ./res/128x128.png ./res/128x128.png.bak | |
[ -f "./res/mac-icon.png" ] && mv ./res/mac-icon.png ./res/mac-icon.png.bak | |
[ -f "./flutter/assets/icon.png" ] && mv ./flutter/assets/icon.png ./flutter/assets/icon.png.bak | |
[ -f "./flutter/assets/icon.svg" ] && mv ./flutter/assets/icon.svg ./flutter/assets/icon.svg.bak | |
[ -f "./rustdesk/data/flutter_assets/assets/icon.svg" ] && mv ./rustdesk/data/flutter_assets/assets/icon.svg ./rustdesk/data/flutter_assets/assets/icon.svg.bak | |
# Create standard app icons | |
magick ./res/icon.png -resize 32x32 ./res/32x32.png | |
magick ./res/icon.png -resize 64x64 ./res/64x64.png | |
magick ./res/icon.png -resize 128x128 ./res/128x128.png | |
# Copy icon to Flutter assets | |
cp ./res/icon.png ./flutter/assets/icon.png | |
cp ./res/icon.png ./rustdesk/data/flutter_assets/assets/icon.png | |
# Convert PNG to SVG using potrace | |
magick ./res/icon.png -flatten ./temp_icon.pbm | |
potrace --svg -o ./flutter/assets/icon.svg ./temp_icon.pbm | |
cp ./flutter/assets/icon.svg ./rustdesk/data/flutter_assets/assets/icon.svg | |
rm ./temp_icon.pbm | |
# Create macOS app icons | |
magick ./res/icon.png -resize 16x16 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png" | |
magick ./res/icon.png -resize 32x32 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png" | |
magick ./res/icon.png -resize 64x64 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png" | |
magick ./res/icon.png -resize 128x128 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png" | |
magick ./res/icon.png -resize 256x256 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png" | |
magick ./res/icon.png -resize 512x512 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png" | |
magick ./res/icon.png -resize 1024x1024 "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png" | |
# Create macOS specific icons | |
magick ./res/icon.png -resize 128x128 ./res/mac-icon.png | |
# Create dark mode tray icon (optimized for macOS menu bar) | |
magick ./res/icon.png -resize 22x22 -colorspace gray -alpha set -background none -channel A -evaluate set 100% ./res/mac-tray-dark-x2.png | |
# Create light mode tray icon (optimized for macOS menu bar) | |
magick ./res/icon.png -resize 22x22 -negate -colorspace gray -alpha set -background none -channel A -evaluate set 100% ./res/mac-tray-light-x2.png | |
# Create AppIcon.icns (macOS native icon format) | |
mkdir -p ./iconset.iconset | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png" "./iconset.iconset/icon_16x16.png" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png" "./iconset.iconset/[email protected]" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png" "./iconset.iconset/icon_32x32.png" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png" "./iconset.iconset/[email protected]" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png" "./iconset.iconset/icon_128x128.png" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png" "./iconset.iconset/[email protected]" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png" "./iconset.iconset/icon_256x256.png" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png" "./iconset.iconset/[email protected]" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png" "./iconset.iconset/icon_512x512.png" | |
cp "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png" "./iconset.iconset/[email protected]" | |
iconutil -c icns ./iconset.iconset -o ./flutter/macos/Runner/AppIcon.icns | |
rm -rf ./iconset.iconset | |
# Create Contents.json for macOS app icon | |
echo '{ | |
"images": [ | |
{"size":"16x16","idiom":"mac","filename":"app_icon_16.png","scale":"1x"}, | |
{"size":"16x16","idiom":"mac","filename":"app_icon_32.png","scale":"2x"}, | |
{"size":"32x32","idiom":"mac","filename":"app_icon_32.png","scale":"1x"}, | |
{"size":"32x32","idiom":"mac","filename":"app_icon_64.png","scale":"2x"}, | |
{"size":"128x128","idiom":"mac","filename":"app_icon_128.png","scale":"1x"}, | |
{"size":"128x128","idiom":"mac","filename":"app_icon_256.png","scale":"2x"}, | |
{"size":"256x256","idiom":"mac","filename":"app_icon_256.png","scale":"1x"}, | |
{"size":"256x256","idiom":"mac","filename":"app_icon_512.png","scale":"2x"}, | |
{"size":"512x512","idiom":"mac","filename":"app_icon_512.png","scale":"1x"}, | |
{"size":"512x512","idiom":"mac","filename":"app_icon_1024.png","scale":"2x"} | |
], | |
"info": { | |
"version": 1, | |
"author": "xcode" | |
} | |
}' > "flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json" | |
# Copy icons and Contents.json to both locations | |
cp -r flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/* macos/Runner/Assets.xcassets/AppIcon.appiconset/ | |
# Verify files exist and show their sizes | |
echo "Verifying generated files:" | |
ls -lh ./res/mac-tray-dark-x2.png | |
ls -lh ./res/mac-tray-light-x2.png | |
ls -lh ./res/mac-icon.png | |
echo "Flutter macOS app icons:" | |
ls -lh flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/ | |
echo "Flutter assets:" | |
ls -lh flutter/assets/icon.* | |
echo "RustDesk Flutter assets:" | |
ls -lh rustdesk/data/flutter_assets/assets/ | |
- name: replace flutter icons | |
if: ${{ inputs.iconlink != 'false' }} | |
continue-on-error: false | |
shell: bash | |
run: | | |
cd ./flutter | |
# Create required directories and files | |
mkdir -p web | |
mkdir -p assets | |
echo '{"name":"${{ inputs.appname }}","short_name":"${{ inputs.appname }}","start_url":"/","display":"standalone","background_color":"#ffffff","theme_color":"#ffffff","description":"A remote desktop software."}' > web/manifest.json | |
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>${{ inputs.appname }}</title></head><body></body></html>' > web/index.html | |
# Ensure the AppIcon.appiconset directory exists | |
mkdir -p macos/Runner/Assets.xcassets/AppIcon.appiconset | |
# Copy the processed icons to Flutter locations | |
cp ../res/mac-icon.png ./assets/icon.png | |
cp ../flutter/assets/icon.svg ./assets/icon.svg || true | |
flutter pub upgrade win32 | |
flutter pub get | |
flutter pub run flutter_launcher_icons | |
cd .. | |
- name: ui.rs | |
if: ${{ inputs.iconlink != 'false' }} | |
continue-on-error: true | |
shell: bash | |
run: | | |
cp ./src/ui.rs ./src/ui.rs.bak | |
if [ -f "./res/icon.png" ]; then | |
SEARCH_STR="iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAEiuAABIrgHwmhA7AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAEx9JREFUeJztnXmYHMV5h9" | |
b64=$(base64 < ./res/icon.png) | |
sed -i '' -e "s~$SEARCH_STR.*\"~$b64\"~" ./src/ui.rs | |
fi | |
- name: fix connection delay | |
continue-on-error: false | |
if: ${{ fromJson(inputs.extras).delayFix == 'true' }} | |
shell: bash | |
run: | | |
sed -i '' -e '/if !key.is_empty() && !token.is_empty() {/,/}/d' ./src/client.rs | |
- name: add cycle monitors to toolbar | |
continue-on-error: true | |
if: fromJson(inputs.extras).cycleMonitor == 'true' | |
run: | | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/cycle_monitor.diff | |
git apply cycle_monitor.diff | |
- name: use X for offline display instead of orange circle | |
continue-on-error: true | |
if: fromJson(inputs.extras).xOffline == 'true' | |
run: | | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/xoffline.diff | |
git apply xoffline.diff | |
- name: hide-cm | |
continue-on-error: true | |
if: fromJson(inputs.extras).hidecm == 'true' | |
run: | | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/hidecm.diff | |
git apply hidecm.diff | |
- name: statussort | |
continue-on-error: true | |
if: fromJson(inputs.extras).statussort == 'true' | |
run: | | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/statussort.diff | |
git apply statussort.diff | |
- name: removeNewVersionNotif | |
continue-on-error: true | |
if: fromJson(inputs.extras).removeNewVersionNotif == 'true' | |
run: | | |
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/removeNewVersionNotif.diff | |
git apply removeNewVersionNotif.diff | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "15% complete"}' | |
- uses: Swatinem/rust-cache@v2 | |
with: | |
prefix-key: ${{ matrix.job.os }} | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "20% complete"}' | |
- name: Setup vcpkg with Github Actions binary cache | |
uses: lukka/run-vcpkg@v11 | |
with: | |
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} | |
doNotCache: false | |
- name: Install vcpkg dependencies | |
run: | | |
if ! $VCPKG_ROOT/vcpkg \ | |
install \ | |
--x-install-root="$VCPKG_ROOT/installed"; then | |
find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do | |
echo "$_1:" | |
echo "======" | |
cat "$_1" | |
echo "======" | |
echo "" | |
done | |
exit 1 | |
fi | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "25% complete"}' | |
- name: Create MacOS directory structure | |
run: | | |
mkdir -p ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS | |
- name: Build rustdesk | |
run: | | |
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then | |
MIN_MACOS_VERSION="12.3" | |
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py | |
sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile | |
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml | |
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj | |
fi | |
sed -i -e "s/RustDesk.app/\"${{ inputs.appname }}.app\"/" build.py | |
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }} | |
# - name: Copy service file | |
# run: | | |
# cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/ | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "50% complete, this step takes about 5 minutes, be patient."}' | |
- name: Install rcodesign tool | |
if: env.MACOS_P12_BASE64 != null | |
shell: bash | |
run: | | |
pushd /tmp | |
wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-macos-universal.tar.gz | |
tar -zxvf apple-codesign-0.22.0-macos-universal.tar.gz | |
mv apple-codesign-0.22.0-macos-universal/rcodesign /usr/local/bin | |
popd | |
- name: Install build runtime | |
run: | | |
brew install llvm create-dmg nasm cmake gcc wget ninja | |
# pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner | |
if command -v pkg-config &>/dev/null; then | |
echo "pkg-config is already installed" | |
else | |
brew install pkg-config | |
fi | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "70% complete, this step takes about 5 minutes, be patient."}' | |
- name: Show version information (Rust, cargo, Clang) | |
shell: bash | |
run: | | |
clang --version || true | |
rustup -V | |
rustup toolchain list | |
rustup default | |
cargo -V | |
rustc -V | |
- name: icon svg handling | |
if: ${{ inputs.iconlink != 'false' }} | |
continue-on-error: false | |
shell: bash | |
run: | | |
ASSETS_DIR="build/macos/Build/Products/Release/RustDesk.app/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets" | |
mkdir -p "$ASSETS_DIR" | |
if [ -f "$ASSETS_DIR/icon.svg" ]; then | |
mv "$ASSETS_DIR/icon.svg" "$ASSETS_DIR/icon.svg.bak" | |
fi | |
# First convert PNG to PBM (bitmap) | |
magick convert ./res/icon.png ./temp_icon.pbm | |
# Then use potrace to convert to SVG | |
potrace --svg -o "$ASSETS_DIR/icon.svg" ./temp_icon.pbm | |
rm ./temp_icon.pbm | |
- name: logo handling | |
if: ${{ inputs.logolink != 'false' }} | |
continue-on-error: false | |
shell: bash | |
run: | | |
ASSETS_DIR="build/macos/Build/Products/Release/RustDesk.app/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets" | |
mkdir -p "$ASSETS_DIR" | |
curl -k -L --tlsv1.2 --proto =https --ssl-reqd \ | |
-H "User-Agent: Mozilla/5.0" \ | |
"${{ fromJson(inputs.logolink).url }}/get_png?filename=${{ fromJson(inputs.logolink).file }}&uuid=${{ fromJson(inputs.logolink).uuid }}" \ | |
-o "$ASSETS_DIR/logo.png" || \ | |
wget --no-check-certificate \ | |
-O "$ASSETS_DIR/logo.png" \ | |
"${{ fromJson(inputs.logolink).url }}/get_png?filename=${{ fromJson(inputs.logolink).file }}&uuid=${{ fromJson(inputs.logolink).uuid }}" | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "85% complete"}' | |
- name: Sign macOS app bundle | |
if: env.MACOS_P12_BASE64 != '' | |
run: | | |
cd flutter/build/macos/Build/Products/Release | |
# Debug info | |
echo "Current directory contents:" | |
ls -la | |
# Rename RustDesk.app to the custom app name first | |
if [ -d "RustDesk.app" ]; then | |
# First rename the app if it's still called RustDesk.app | |
mv "RustDesk.app" "${{ inputs.appname }}.app" | |
echo "Renamed RustDesk.app to ${{ inputs.appname }}.app" | |
fi | |
echo "App bundle contents after rename:" | |
ls -la "${{ inputs.appname }}.app" || echo "App not found" | |
ls -la "${{ inputs.appname }}.app/Contents" || echo "Contents not found" | |
# Decode the certificate | |
echo "${{ secrets.MACOS_P12_BASE64 }}" | base64 --decode > certificate.p12 | |
# Sign the app bundle and its contents | |
if [ -d "${{ inputs.appname }}.app/Contents/MacOS" ]; then | |
echo "Signing main executable..." | |
MAIN_EXECUTABLE="${{ inputs.appname }}.app/Contents/MacOS/${{ inputs.appname }}" | |
if [ -f "$MAIN_EXECUTABLE" ]; then | |
rcodesign sign --p12-file certificate.p12 --p12-password "${{ secrets.MACOS_P12_PASSWORD }}" \ | |
--code-signature-flags runtime "$MAIN_EXECUTABLE" | |
else | |
echo "Main executable not found at expected path: $MAIN_EXECUTABLE" | |
# Try to find the actual executable | |
echo "Available executables in MacOS directory:" | |
ls -la "${{ inputs.appname }}.app/Contents/MacOS/" | |
ACTUAL_EXECUTABLE=$(ls "${{ inputs.appname }}.app/Contents/MacOS/" | head -n 1) | |
if [ -n "$ACTUAL_EXECUTABLE" ]; then | |
echo "Found executable: $ACTUAL_EXECUTABLE" | |
rcodesign sign --p12-file certificate.p12 --p12-password "${{ secrets.MACOS_P12_PASSWORD }}" \ | |
--code-signature-flags runtime "${{ inputs.appname }}.app/Contents/MacOS/$ACTUAL_EXECUTABLE" | |
fi | |
fi | |
echo "Signing frameworks..." | |
find "${{ inputs.appname }}.app/Contents/Frameworks" -type f -not -name ".*" -exec \ | |
rcodesign sign --p12-file certificate.p12 --p12-password "${{ secrets.MACOS_P12_PASSWORD }}" \ | |
--code-signature-flags runtime {} \; | |
echo "Signing main bundle..." | |
rcodesign sign --p12-file certificate.p12 --p12-password "${{ secrets.MACOS_P12_PASSWORD }}" \ | |
--code-signature-flags runtime "${{ inputs.appname }}.app" | |
else | |
echo "Error: Invalid app bundle structure" | |
exit 1 | |
fi | |
# Clean up | |
rm certificate.p12 | |
- name: Create DMG | |
run: | | |
cd /Users/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/flutter/build/macos/Build/Products/Release | |
# Print directory contents for debugging | |
echo "Directory contents:" | |
ls -la | |
# Find the actual .app bundle | |
if [ -d "RustDesk.app" ]; then | |
# First rename the app if it's still called RustDesk.app | |
mv "RustDesk.app" "${{ inputs.appname }}.app" | |
fi | |
if [ ! -d "${{ inputs.appname }}.app" ]; then | |
echo "Could not find .app bundle!" | |
exit 1 | |
fi | |
echo "Creating DMG for ${{ inputs.appname }}.app" | |
create-dmg \ | |
--volname "${{ inputs.appname }}" \ | |
--window-pos 200 120 \ | |
--window-size 800 400 \ | |
--icon-size 100 \ | |
--icon "${{ inputs.appname }}.app" 200 190 \ | |
--hide-extension "${{ inputs.appname }}.app" \ | |
--app-drop-link 600 185 \ | |
"${{ inputs.appname }}-${{ matrix.job.arch }}.dmg" \ | |
"${{ inputs.appname }}.app" | |
mv "${{ inputs.appname }}-${{ matrix.job.arch }}.dmg" $GITHUB_WORKSPACE/ | |
- name: Rename rustdesk | |
if: env.UPLOAD_ARTIFACT == 'true' | |
run: | | |
cd $GITHUB_WORKSPACE | |
echo "Directory contents:" | |
ls -la | |
# Find the DMG file dynamically | |
DMG_FILE=$(find . -name "${{ inputs.appname }}-${{ matrix.job.arch }}.dmg") | |
if [ -n "$DMG_FILE" ]; then | |
echo "Found DMG file: $DMG_FILE" | |
mv "$DMG_FILE" "${{ inputs.filename }}.dmg" | |
echo "Renamed to ${{ inputs.filename }}.dmg" | |
else | |
echo "No DMG file found matching the pattern" | |
exit 1 | |
fi | |
- name: send file to rdgen server | |
if: ${{ fromJson(inputs.extras).rdgen == 'true' }} | |
shell: bash | |
run: | | |
curl -i -X POST \ | |
-H "Content-Type: multipart/form-data" \ | |
-H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" \ | |
-F "file=@$GITHUB_WORKSPACE/${{ inputs.filename }}.dmg" \ | |
-F "uuid=${{ inputs.uuid }}" \ | |
"${{ secrets.GENURL }}/save_custom_client" | |
- name: send file to api server | |
if: ${{ fromJson(inputs.extras).rdgen == 'false' }} | |
shell: bash | |
run: | | |
curl -i -X POST \ | |
-H "Content-Type: multipart/form-data" \ | |
-H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" \ | |
-F "file=@$GITHUB_WORKSPACE/${{ inputs.filename }}.dmg" \ | |
"${{ inputs.apiServer }}/api/save_custom_client" | |
- name: Report Status | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "Success"}' | |
- name: failed | |
if: failure() | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "Generation failed, try again"}' | |
- name: failed | |
if: cancelled() | |
uses: fjogeleit/http-request-action@v1 | |
with: | |
url: ${{ env.STATUS_URL }} | |
method: 'POST' | |
customHeaders: '{"Content-Type": "application/json"}' | |
data: '{"uuid": "${{ inputs.uuid }}", "status": "Generation cancelled, try again"}' |