Files

279 lines
9.8 KiB
Markdown

# Building RustDesk for macOS (Jerry's MacBook Air, Apple Silicon)
This is a fork of [rustdesk/rustdesk](https://github.com/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:
```bash
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 `xcodebuild` and `pod install` need the full Xcode.
- After install:
```bash
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)
```bash
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`:
```sh
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:
```sh
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)
```bash
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
```bash
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`
```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(...)` and `cast<ffi.Uint8>()` in `api2wire_int_32_list` / `api2wire_uint_8_list`
- `typedef 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`
```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](src/common.rs#L2104):
```rust
// 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](libs/hbb_common/src/config.rs#L3130) 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)
```bash
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`:
```bash
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.com` for now)
- Universal binary (arm64 + x86_64)
- Code signing with a real developer certificate