9.8 KiB
Building RustDesk for macOS (Jerry's MacBook Air, Apple Silicon)
This is a fork of rustdesk/rustdesk v1.4.7, patched to build against current macOS tooling (Xcode 26.5, Flutter 3.44, Dart 3.12).
Last verified build: 2026-06-08, RustDesk 1.4.7 (arm64), dmg size 27MB.
TL;DR (already-done state)
If this Mac still has everything set up, you can re-build with:
cd ~/rustdesk-master2
export VCPKG_ROOT=~/vcpkg PATH="$VCPKG_ROOT:$PATH" \
LIBCLANG_PATH=/Library/Developer/CommandLineTools/usr/lib/libclang.dylib
cargo build --release --features flutter
flutter build macos --release
./res/post-build-mac.sh # re-sign, install to /Applications, create dmg
open /Applications/RustDesk.app
1. One-time environment setup (if starting fresh)
1.1. Xcode (App Store)
- Install full Xcode 26.5 from App Store. CommandLineTools alone is not enough —
Flutter's
xcodebuildandpod installneed the full Xcode. - After install:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer sudo xcodebuild -license accept sudo xcodebuild -runFirstLaunch # installs CoreSimulator.framework etc.
1.2. VCPKG (C++ deps: libvpx, libyuv, opus, aom)
git clone https://github.com/microsoft/vcpkg ~/vcpkg
cd ~/vcpkg && ./bootstrap-vcpkg.sh
vcpkg install --triplet=arm64-osx libvpx libyuv opus aom
Add to ~/.zshrc:
export VCPKG_ROOT="$HOME/vcpkg"
export PATH="$VCPKG_ROOT:$PATH"
1.3. Flutter 3.44+
Download from https://docs.flutter.dev/get-started/install/macos, then:
export FLUTTER_ROOT="$HOME/flutter"
export PATH="$FLUTTER_ROOT/bin:$PATH"
flutter doctor # should show green checkmarks for Xcode and Chrome
1.4. CocoaPods (needed by Flutter macOS plugins)
brew install cocoapods
Note: On this Mac homebrew is x86_64 (prefix /usr/local), not arm64 native. Most
things work but see the libclang note below.
1.5. flutter_rust_bridge_codegen 1.80.1
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked
2. Project-specific patches (all in git, see git log)
2.1. flutter/pubspec.yaml
extended_text: ^15.0.2 # was 14.0.0; old missing SelectionHandler methods
dependency_overrides:
google_fonts: 5.0.0 # 6.x breaks with const Map<FontWeight, ...>
2.2. flutter/lib/generated_bridge.dart (codegen output, 2 fixes)
After running flutter_rust_bridge_codegen (see step 3), patch:
asTypedList(raw.length).setAll(0, raw)→cast<ffi.Int32>().asTypedList(...)andcast<ffi.Uint8>()inapi2wire_int_32_list/api2wire_uint_8_listtypedef DartPort = ffi.Int;→typedef DartPort = ffi.Int64;
2.3. flutter/lib/common.dart (4 call sites)
DialogTheme(→DialogThemeData(const TabBarTheme(→TabBarThemeData(
2.4. flutter/macos/Runner/Configs/Release.xcconfig
EXCLUDED_ARCHS[sdk=macosx*] = x86_64
Without this, release builds fail because Xcode 26.5's ARCHS_STANDARD includes x86_64
but our Rust dylib is arm64-only.
2.5. ~/.pub-cache/.../google_fonts-5.0.0/lib/src/google_fonts_variant.dart
Dart 3.12 rejects const Map<FontWeight, String>{...}. Change const to final on
_fontWeightToFilenameWeightParts. This patch is in the global pub cache and will
be wiped by flutter pub cache clean. To make it permanent, copy the patched file into
./local_google_fonts/ and use dependency_overrides: google_fonts: { path: ... }.
2.6. src/common.rs — hardcode enable-audio default to off (NOT via custom.txt)
The naive approach (custom.txt) is broken for unsigned forks. load_custom_client
reads Contents/Resources/custom.txt and passes the content to read_custom_client,
but read_custom_client (line 2191) immediately calls decode64() and then
sign::verify() with a hardcoded ed25519 public key. Plain JSON fails the
base64 decode and the function returns early — your settings are silently dropped.
Only official signed custom-client builds can use the custom.txt path.
What we do instead: inject enable-audio = N directly into DEFAULT_SETTINGS at
the end of load_custom_client, in src/common.rs:2104-2114:
// Custom fork: hardcode enable-audio = N as the default. User can still flip it on in Settings.
// Reason: RustDesk's official custom.txt path requires ed25519-signed base64 (see
// read_custom_client at line 2191 — decode64 + sign::verify with key
// "5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM="), which we can't produce without the
// official signing key. So we bypass custom.txt and inject directly into DEFAULT_SETTINGS.
// entry().or_insert() preserves the priority chain: OVERWRITE_SETTINGS → CONFIG2 → DEFAULT_SETTINGS.
{
let mut defaults = config::DEFAULT_SETTINGS.write().unwrap();
defaults.entry("enable-audio".to_string()).or_insert("N".to_string());
}
entry().or_insert() (not insert()) is intentional — it lets users override
enable-audio by writing to their own RustDesk2.toml's [options] table
(CONFIG2 wins in the priority chain).
To add more default-off settings, append more defaults.entry(...).or_insert(...)
lines. Any key from the KEYS_SETTINGS whitelist at
libs/hbb_common/src/config.rs:3130 works.
Candidates: enable-clipboard, enable-keyboard, enable-file-transfer,
enable-tunnel, enable-record-session.
Verified end-to-end: debug eprintln confirmed DEFAULT_SETTINGS.get("enable-audio") == Some("N")
at startup. UI's "Settings → Permissions" page now shows the audio checkbox unchecked.
User can still flip it on in Settings.
3. Build steps (from a clean checkout)
cd ~/rustdesk-master2
# 3.1. Pull hbb_common submodule (only matters if you switch to submodule setup;
# we currently have it tracked as a regular directory, see [[git-remote-cardsoon]])
# git submodule update --init --recursive
# 3.2. Environment
export VCPKG_ROOT=~/vcpkg
export PATH="$VCPKG_ROOT:$PATH"
export LIBCLANG_PATH="/Library/Developer/CommandLineTools/usr/lib/libclang.dylib"
# ^^^ must be the Xcode CLT one (arm64-compatible), NOT /usr/local/opt/llvm/
# 3.3. Flutter pub get
cd flutter && flutter pub get && cd ..
# 3.4. Generate FFI bindings (Flutter <-> Rust)
flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart \
--c-output ./flutter/macos/Runner/bridge_generated.h \
--llvm-path /Library/Developer/CommandLineTools/usr/
# Then apply the 2 hand-patches in 2.2 above.
# 3.5. Rust release build
cargo build --release --features flutter
# 3.6. Flutter macOS app build
flutter build macos --release
# 3.7. Re-sign, install, package
./res/post-build-mac.sh
4. Critical gotchas (learned the hard way)
4.1. Re-sign after every build
flutter build macos produces an .app that does not launch on macOS 26.5.
The main executable and the embedded Flutter plugin frameworks end up with
different ad-hoc Team IDs, and macOS's strict dyld refuses the mismatch.
Always run after every flutter build macos:
codesign --force --deep -s - path/to/RustDesk.app
The res/post-build-mac.sh script does this for you. The --deep flag is
essential so that all framework bundles inside the .app get re-signed together.
4.2. Use hdiutil for .dmg, not create-dmg
create-dmg (the official RustDesk build tool) depends on Node's macos-alias
native module, which is x86_64-built and won't load on this arm64 Mac.
npm rebuild also fails because macos-alias uses the outdated nan@2 N-API
shim that's incompatible with Node 18+.
Use hdiutil instead (see res/post-build-mac.sh).
4.3. libclang must be arm64
For flutter_rust_bridge_codegen to work, libclang.dylib must be loadable.
The Homebrew one at /usr/local/opt/llvm/lib/libclang.dylib is x86_64.
The Xcode CLT one at /Library/Developer/CommandLineTools/usr/lib/libclang.dylib
is universal (arm64 + x86_64). Use the latter.
4.4. EXCLUDED_ARCHS for release
Xcode 26.5's default ARCHS_STANDARD includes x86_64, but our Rust dylib
is arm64-only. Set EXCLUDED_ARCHS[sdk=macosx*] = x86_64 in Release.xcconfig,
or build a universal dylib with lipo.
4.5. Debug config doesn't need EXCLUDED_ARCHS
flutter build macos --debug works without the EXCLUDED_ARCHS hack because
debug builds use ONLY_ACTIVE_ARCH=YES which only targets the host arch.
5. What you get
After res/post-build-mac.sh:
| File | Location | Size |
|---|---|---|
Release .app |
flutter/build/macos/Build/Products/Release/RustDesk.app |
57 MB |
Installed .app |
/Applications/RustDesk.app |
57 MB |
.dmg installer |
rustdesk-1.4.7-arm64.dmg |
27 MB |
| Rust lib | target/release/liblibrustdesk.dylib |
22.6 MB |
Bundle ID: com.carriez.rustdesk (NOT the official com.rustdesk.client —
change in flutter/macos/Runner/Configs/AppInfo.xcconfig if needed).
Architecture: arm64 only. For Intel Mac support, build a universal
dylib with lipo and drop the EXCLUDED_ARCHS hack.
6. First-launch permissions
When you launch the app for the first time, macOS will prompt for:
- Screen Recording (System Settings → Privacy & Security → Screen Recording) — needed to capture the screen when you are being controlled
- Microphone — for audio forwarding
- Accessibility — for keyboard/mouse input forwarding
Grant these or the corresponding features won't work.
7. NOT covered here (out of scope)
- iOS / Android builds (different toolchains)
- Apple notarization (would need an Apple Developer ID account)
- Self-hosted rendezvous/relay server (use public
rs-ny.rustdesk.comfor now) - Universal binary (arm64 + x86_64)
- Code signing with a real developer certificate