This document covers platform-specific considerations, requirements, and best practices for using nih_plug_vstgui on Windows, macOS, and Linux.
- Operating System: Windows 10 or later (Windows 7/8 may work but are not officially supported)
- Compiler: Visual Studio 2019 or later with C++ development tools
- SDK: Windows 10 SDK
- Architecture: x64 and ARM64 supported
Install Visual Studio with the following components:
- Desktop development with C++
- Windows 10 SDK (latest version)
- CMake tools for Windows
The build system should automatically detect Visual Studio, but you can set these if needed:
# Set Visual Studio path
$env:VSINSTALLDIR = "C:\Program Files\Microsoft Visual Studio\2022\Community"
# Set Windows SDK version
$env:WindowsSDKVersion = "10.0.22621.0"Windows uses HWND for window handles:
use nih_plug::prelude::ParentWindowHandle;
match parent {
ParentWindowHandle::Win32Hwnd(hwnd) => {
// hwnd is a *mut c_void pointing to the HWND
frame.open(hwnd)?;
}
_ => panic!("Expected Win32 window handle"),
}Windows has complex DPI scaling behavior. VSTGUI handles most of this automatically, but you should:
- Declare DPI awareness in your plugin manifest
- Use logical coordinates (VSTGUI will scale to physical pixels)
- Test on displays with different DPI settings (100%, 125%, 150%, 200%)
Issue: Plugin doesn't load in some hosts
- Cause: Missing Visual C++ Redistributable
- Solution: Include redistributable with your installer or link statically
Issue: Window appears but is blank
- Cause: Graphics driver compatibility
- Solution: Update graphics drivers, test on different hardware
Issue: High CPU usage
- Cause: Excessive redrawing or inefficient GDI operations
- Solution: Use
invalid_rect()instead ofinvalid(), minimize redraws
Test on:
- Windows 10 (various versions)
- Windows 11
- Different DPI settings (100%, 125%, 150%, 200%)
- Multiple monitors with different DPI settings
- Different graphics hardware (Intel, NVIDIA, AMD)
Use Visual Studio debugger:
# Build with debug symbols
cargo build
# Launch DAW with debugger
devenv /debugexe "C:\Program Files\DAW\DAW.exe"Set breakpoints in Rust code and step through execution.
When distributing your plugin:
-
Code Signing: Sign your DLL with a valid certificate
signtool sign /f certificate.pfx /p password /t http://timestamp.digicert.com plugin.dll
-
Dependencies: Include or statically link:
- Visual C++ Runtime
- Any additional DLLs
-
Installer: Use WiX, Inno Setup, or NSIS to create an installer
-
Testing: Test on clean Windows installations
- Operating System: macOS 10.13 (High Sierra) or later
- Compiler: Xcode Command Line Tools (Clang)
- Architecture: x86_64 and ARM64 (Apple Silicon) supported
- Frameworks: Cocoa, QuartzCore, Accelerate
Install Xcode Command Line Tools:
xcode-select --installVerify installation:
clang --versionTo build for both Intel and Apple Silicon:
# Build for x86_64
cargo build --target x86_64-apple-darwin --release
# Build for ARM64
cargo build --target aarch64-apple-darwin --release
# Create universal binary
lipo -create \
target/x86_64-apple-darwin/release/libplugin.dylib \
target/aarch64-apple-darwin/release/libplugin.dylib \
-output plugin.dylibmacOS uses NSView for window handles:
use nih_plug::prelude::ParentWindowHandle;
match parent {
ParentWindowHandle::AppKitNsView(ns_view) => {
// ns_view is a *mut c_void pointing to the NSView
frame.open(ns_view)?;
}
_ => panic!("Expected AppKit NSView handle"),
}VSTGUI automatically handles Retina displays, but you should:
- Use logical coordinates (VSTGUI scales to physical pixels)
- Provide @2x bitmap resources for Retina displays
- Test on both Retina and non-Retina displays
macOS supports system-wide dark mode. VSTGUI can adapt to this:
// VSTGUI will automatically use dark mode colors if the system is in dark mode
// You can also manually check and respond to dark mode changesIssue: Plugin crashes on Apple Silicon
- Cause: Not built for ARM64 or Rosetta 2 issues
- Solution: Build universal binary or ARM64-specific binary
Issue: Code signing errors
- Cause: Plugin not signed or signed incorrectly
- Solution: Sign with valid Developer ID certificate
Issue: Gatekeeper blocks plugin
- Cause: Plugin not notarized
- Solution: Notarize your plugin with Apple
Issue: Graphics glitches
- Cause: Metal/OpenGL compatibility issues
- Solution: Update to latest macOS, test on different hardware
Test on:
- macOS 10.13 through latest version
- Intel and Apple Silicon Macs
- Retina and non-Retina displays
- Light and dark mode
- Different DAWs (Logic Pro, Ableton Live, etc.)
Use LLDB debugger:
# Build with debug symbols
cargo build
# Launch DAW with debugger
lldb -- /Applications/DAW.app/Contents/MacOS/DAWOr use Xcode's Instruments for profiling:
instruments -t Leaks /Applications/DAW.app/Contents/MacOS/DAWWhen distributing your plugin:
-
Code Signing: Sign with Developer ID certificate
codesign --force --sign "Developer ID Application: Your Name" \ --options runtime \ --entitlements entitlements.plist \ plugin.vst3 -
Notarization: Submit to Apple for notarization
# Create zip ditto -c -k --keepParent plugin.vst3 plugin.zip # Submit for notarization xcrun notarytool submit plugin.zip \ --apple-id your@email.com \ --team-id TEAMID \ --password app-specific-password # Staple notarization ticket xcrun stapler staple plugin.vst3
-
Installer: Use pkgbuild or create DMG
-
Testing: Test on clean macOS installations
Create entitlements.plist for hardened runtime:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>- Operating System: Ubuntu 20.04+, Fedora 34+, or equivalent
- Compiler: GCC 7+ or Clang 5+
- Display Server: X11 (Wayland via XWayland)
- Architecture: x86_64 and ARM64 supported
Install required development libraries:
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install \
build-essential \
cmake \
libx11-dev \
libxcursor-dev \
libxrandr-dev \
libxinerama-dev \
libxi-dev \
libcairo2-dev \
libfontconfig1-dev \
libfreetype6-dev \
libpango1.0-devFedora:
sudo dnf install \
gcc-c++ \
cmake \
libX11-devel \
libXcursor-devel \
libXrandr-devel \
libXinerama-devel \
libXi-devel \
cairo-devel \
fontconfig-devel \
freetype-devel \
pango-develArch:
sudo pacman -S \
base-devel \
cmake \
libx11 \
libxcursor \
libxrandr \
libxinerama \
libxi \
cairo \
fontconfig \
freetype2 \
pangoLinux uses X11 Window ID:
use nih_plug::prelude::ParentWindowHandle;
match parent {
ParentWindowHandle::X11Window(window_id) => {
// window_id is a u32, convert to pointer for VSTGUI
let window_ptr = window_id as *mut c_void;
frame.open(window_ptr)?;
}
_ => panic!("Expected X11 window handle"),
}VSTGUI needs access to the X11 display:
// VSTGUI will automatically connect to the display specified by $DISPLAY
// Ensure DISPLAY environment variable is setIssue: Cannot open display
- Cause: DISPLAY environment variable not set
- Solution:
export DISPLAY=:0
Issue: Window doesn't appear on Wayland
- Cause: XWayland compatibility issue
- Solution: Force X11 mode:
GDK_BACKEND=x11 your-daw
Issue: Font rendering looks bad
- Cause: Font configuration or Cairo settings
- Solution: Install better fonts, configure fontconfig
Issue: High CPU usage
- Cause: Inefficient X11 operations or excessive redraws
- Solution: Use
invalid_rect(), minimize X11 round-trips
Issue: Crashes with "BadWindow" error
- Cause: Invalid window ID or window destroyed
- Solution: Verify window ID is valid, check window lifecycle
Test on:
- Ubuntu 20.04, 22.04, 24.04
- Fedora 38, 39, 40
- Arch Linux (rolling release)
- Different desktop environments (GNOME, KDE, XFCE)
- X11 and Wayland (via XWayland)
- Different window managers
- HiDPI displays
Use GDB debugger:
# Build with debug symbols
cargo build
# Launch DAW with debugger
gdb --args /usr/bin/dawOr use Valgrind for memory issues:
valgrind --leak-check=full --show-leak-kinds=all /usr/bin/dawWhen distributing your plugin:
-
Binary Compatibility: Build on oldest supported distro (e.g., Ubuntu 20.04)
-
Dependencies: Either:
- Statically link everything (large binary)
- Document required system libraries
- Provide AppImage or Flatpak
-
File Locations: Follow Linux standards
- VST3:
~/.vst3/or/usr/lib/vst3/ - Resources:
/usr/share/your-plugin/
- VST3:
-
Testing: Test on multiple distributions
For CI/CD, use Xvfb for headless testing:
# Start virtual X server
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99
# Run tests
cargo test
# Stop Xvfb
killall XvfbAll platforms use the same coordinate system in VSTGUI:
- Origin at top-left
- X increases to the right
- Y increases downward
- Coordinates are in logical pixels (scaled for DPI)
Each platform handles DPI differently:
Windows:
- System DPI setting (100%, 125%, 150%, 200%)
- Per-monitor DPI awareness
- VSTGUI queries system DPI and scales automatically
macOS:
- Retina displays have 2x pixel density
- VSTGUI uses logical coordinates, scales to physical pixels
- @2x resources for Retina displays
Linux:
- X11 DPI settings (often 96 DPI default)
- Some desktop environments have scaling factors
- VSTGUI respects X11 DPI settings
Use platform-independent path handling:
use std::path::PathBuf;
// Get platform-specific paths
let resources_dir = if cfg!(target_os = "windows") {
PathBuf::from(r"C:\Program Files\YourPlugin\Resources")
} else if cfg!(target_os = "macos") {
PathBuf::from("/Library/Application Support/YourPlugin/Resources")
} else {
PathBuf::from("/usr/share/your-plugin/resources")
};Different platforms use different modifier keys:
Windows/Linux:
- Ctrl for shortcuts (Ctrl+C, Ctrl+V)
- Alt for menu access
macOS:
- Cmd (⌘) for shortcuts (Cmd+C, Cmd+V)
- Ctrl for right-click
Handle this in your code:
let modifier = if cfg!(target_os = "macos") {
Modifiers::CMD
} else {
Modifiers::CTRL
};Windows:
- Right-click for context menus
- Middle-click for special actions
- Scroll wheel for vertical scrolling
- Shift+scroll for horizontal scrolling
macOS:
- Right-click or Ctrl+click for context menus
- Two-finger scroll for vertical/horizontal
- Pinch to zoom (trackpad)
Linux:
- Right-click for context menus
- Middle-click for paste (X11 selection)
- Scroll wheel behavior varies by desktop environment
Each platform has different font rendering:
Windows:
- ClearType (subpixel rendering)
- DirectWrite API
- System fonts: Segoe UI, Consolas
macOS:
- Quartz (subpixel rendering)
- Core Text API
- System fonts: San Francisco, Monaco
Linux:
- FreeType + Cairo
- Fontconfig for font selection
- System fonts vary by distribution
Use platform-appropriate fonts:
let font_name = if cfg!(target_os = "windows") {
"Segoe UI"
} else if cfg!(target_os = "macos") {
"San Francisco"
} else {
"Sans" // Generic sans-serif
};Windows:
- sRGB color space by default
- ICM color management available
macOS:
- Display P3 color space on newer displays
- ColorSync color management
- Automatic color space conversion
Linux:
- sRGB color space typically
- Color management varies by desktop environment
For consistent colors across platforms, use sRGB color space.
Windows:
- GDI/GDI+ can be slow for complex graphics
- Direct2D is faster but requires Windows 7+
- Hardware acceleration available
macOS:
- Quartz is generally fast
- Metal acceleration on newer systems
- Retina displays require more pixels
Linux:
- Cairo performance varies
- X11 network transparency can add latency
- Hardware acceleration depends on drivers
Optimize for the slowest platform, test on all platforms.
Typical build times (release mode, clean build):
Windows:
- ~5-10 minutes (Visual Studio)
- Slower due to Windows Defender scanning
macOS:
- ~3-7 minutes (Clang)
- Faster on Apple Silicon
Linux:
- ~3-7 minutes (GCC/Clang)
- Fastest on modern hardware
Use sccache or cargo-cache to speed up rebuilds.
- Primary Platform: Develop on your primary platform
- Regular Testing: Test on other platforms weekly
- CI/CD: Automate builds and tests for all platforms
- Beta Testing: Get users to test on various configurations
- Release Testing: Thorough testing on all platforms before release
Example GitHub Actions workflow:
name: CI
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install libx11-dev libcairo2-dev
- name: Build
run: cargo build --release
- name: Test
run: cargo test --release- Test Early, Test Often: Test on all platforms throughout development
- Use Platform Abstractions: Let VSTGUI handle platform differences when possible
- Document Platform Requirements: Clearly document what's needed on each platform
- Provide Platform-Specific Installers: Make installation easy for users
- Monitor Platform-Specific Issues: Track issues separately for each platform
- Keep Dependencies Updated: Update VSTGUI and other dependencies regularly
- Use CI/CD: Automate building and testing on all platforms
- Get User Feedback: Beta test on real user systems