mac-vnc-server is a macOS-only VNC/RFB server written in Swift. It captures the local Mac screen, accepts keyboard and mouse input from a VNC client, and exposes the session on a configurable TCP port.
The default setup is optimized for local testing with Apple Screen Sharing:
- bind address:
127.0.0.1 - base port:
5900 - password:
macvnc - FPS target:
30 - scale:
1.0 - encoding:
auto
Use SSH tunneling or an explicit LAN bind for remote use.
- macOS 13 or newer
- Xcode / Swift toolchain compatible with
swift-tools-version: 6.3 - Screen Recording permission
- Accessibility / Post Event permission for keyboard and mouse injection
The package links macOS-native frameworks:
ScreenCaptureKitfor screen captureCoreGraphics/ApplicationServicesfor input injection and permissionsAppKitfor clipboard integrationzlibfor compressed framebuffer encodings
swift build -c releaseThe binary is produced at:
.build/release/mac-vnc-server
For an explicit Apple Silicon build:
swift build -c release --arch arm64Development builds always report:
0.0.0-development
The release workflow replaces that value with the Git tag being released, stripping a leading v. For example, release tag v1.2.3 builds a binary that reports 1.2.3.
Show the version:
./.build/release/mac-vnc-server version
./.build/release/mac-vnc-server --helpThe server also prints its version at startup.
Run this once:
./.build/release/mac-vnc-server permissionsThen grant the requested permissions in macOS System Settings:
- Privacy & Security -> Screen Recording
- Privacy & Security -> Accessibility
Restart the server after granting permissions.
Check current status:
./.build/release/mac-vnc-server diagnoseDefault command:
./.build/release/mac-vnc-serverEquivalent explicit command:
./.build/release/mac-vnc-server run --bind 127.0.0.1 --port 5900 --fps 30 --scale 1 --encoding auto --password macvncBy default, the server exposes both the combined desktop and each display individually:
5900 all displays, composed as one virtual framebuffer
5901 display 1
5902 display 2
... one additional port per display
Connect with Apple Screen Sharing:
open 'vnc://127.0.0.1:5900'Password:
macvnc
For unattended local testing, fill the native Screen Sharing password dialog with AppleScript:
open 'vnc://127.0.0.1:5900'
sleep 2
osascript -e 'tell application "System Events" to keystroke "macvnc"' \
-e 'tell application "System Events" to key code 36'Do not store test credentials in Keychain unless you explicitly want that behavior.
Bind all interfaces:
./.build/release/mac-vnc-server --bind 0.0.0.0 --port 5900 --password macvncOr bind a specific LAN IP:
./.build/release/mac-vnc-server --bind 192.168.1.10 --port 5900 --password macvncThe server refuses unauthenticated non-loopback binds by default. To disable auth for clients that support unauthenticated VNC, you must opt in explicitly:
./.build/release/mac-vnc-server --bind 0.0.0.0 --no-password --insecure-allow-no-authClassic VNC password auth is weak and limited by the protocol. For untrusted networks, prefer an SSH tunnel:
ssh -L 5900:127.0.0.1:5900 user@mac-host
open 'vnc://127.0.0.1:5900'mac-vnc-server [run] [options]
mac-vnc-server permissions
mac-vnc-server diagnose
mac-vnc-server version
mac-vnc-server --help
run is optional when the first argument is a flag.
Options:
| Option | Default | Description |
|---|---|---|
--bind <ipv4> |
127.0.0.1 |
IPv4 address to listen on. |
--port <port> / -p <port> |
5900 |
TCP port, or base port when --display is omitted. |
--password <value> |
macvnc |
Classic VNC auth password. |
--no-password |
off | Use unauthenticated VNC. Apple Screen Sharing does not accept this path. |
--insecure-allow-no-auth |
off | Required with --no-password on non-loopback binds. |
--fps <1...120> |
30 |
Target framebuffer update rate. |
--scale <value> |
1.0 |
Virtual framebuffer scale. 1.0 is usually best for Retina/LAN performance. |
--encoding <auto|zrle|zlib|raw> |
auto |
Framebuffer encoding preference. |
--display <all|number> |
automatic | Display mode. Omit it to serve all displays on the base port and each display on consecutive ports. Use all for only the combined desktop, or a 1-based display number for only that display. |
Omitting --display starts multiple listeners. With the default base port, 5900 keeps the previous combined-desktop behavior and 5901, 5902, ... expose each monitor separately:
./.build/release/mac-vnc-server
open 'vnc://127.0.0.1:5900' # all displays
open 'vnc://127.0.0.1:5901' # display 1
open 'vnc://127.0.0.1:5902' # display 2To keep a single listener with the combined desktop:
./.build/release/mac-vnc-server --display allTo serve only one monitor on the selected port:
./.build/release/mac-vnc-server --display 1 --port 5900Use diagnose to list display numbers:
./.build/release/mac-vnc-server diagnoseThe server implements the RFB handshake and core client messages:
- protocol negotiation
SecurityType Noneand classic VNC authSetPixelFormatSetEncodingsFramebufferUpdateRequestKeyEventPointerEventClientCutText
Apple Screen Sharing negotiates RFB 3.3 and requires VNC auth, so the default password is enabled.
Screen capture uses ScreenCaptureKit with one stream per selected display. Captured frames are stored in BGRA format and composed into a virtual framebuffer. The virtual framebuffer supports multiple displays and maps VNC coordinates back to macOS global coordinates for mouse input.
--encoding auto chooses a compatible encoding based on the client:
- Apple Screen Sharing: persistent Zlib encoding (
6) - generic clients with ZRLE: ZRLE (
16) - generic clients with Zlib: Zlib (
6) - fallback: Raw (
0)
Zlib is kept as a persistent stream per VNC connection, which is required for stable compressed updates with Apple Screen Sharing.
Keyboard and mouse events are injected with CGEvent.
The server processes input on the read loop and streams framebuffer updates on a separate writer queue. This prevents keyboard/mouse events from getting stuck behind frame compression or socket writes.
For Apple Screen Sharing, Alt_L / Alt_R keysyms are remapped to macOS Command because the native client sends Command that way. This enables shortcuts such as Cmd+C, Cmd+V, Cmd+W, and Cmd+Q.
Clipboard support uses NSPasteboard and classic VNC cut text messages. This path is basic text clipboard support; full extended clipboard support is not implemented yet.
This repository includes two workflows:
.github/workflows/ci.yml
Runs on pull requests and pushes to main:
swift testswift build -c release
.github/workflows/release.yml
Runs when a GitHub Release is published:
- replaces
0.0.0-developmentinAppVersion.swiftwith the release tag - runs tests
- builds an arm64 macOS release binary
- prepares the binary plus SHA-256 checksum
- uploads
mac-vnc-serverandmac-vnc-server.sha256to the GitHub Release
The default password is printed on server startup:
password=macvnc
For scripted testing, use AppleScript to type it instead of Keychain.
Make sure you are running a build with the split reader/writer architecture. Rebuild:
swift build -c releaseThen restart the server.
Use the default encoding first:
./.build/release/mac-vnc-server --encoding autoIf testing a generic client, try:
./.build/release/mac-vnc-server --encoding zrle
./.build/release/mac-vnc-server --encoding zlib
./.build/release/mac-vnc-server --encoding rawUse another port:
./.build/release/mac-vnc-server --port 5903
open 'vnc://127.0.0.1:5903'Run:
./.build/release/mac-vnc-server permissions
./.build/release/mac-vnc-server diagnoseThen restart the server after granting permissions.