Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0d072f1
feat(helper): add AppFadersHelper executable target with placeholder …
cheefbird Jan 26, 2026
d983c3c
feat(helper): add LaunchAgent plist for XPC service registration
cheefbird Jan 26, 2026
af1c588
feat(helper): add XPC protocol definitions for host and driver clients
cheefbird Jan 26, 2026
c435bcd
feat(helper): add VolumeStore as central volume storage
cheefbird Jan 26, 2026
7dabd9c
feat(helper): add HelperService implementing XPC protocols with valid…
cheefbird Jan 26, 2026
504ad53
feat(helper): implement XPC listener with connection handling
cheefbird Jan 26, 2026
e6ad29a
refactor(helper): rename Protocols.swift to XPCProtocols.swift
cheefbird Jan 26, 2026
1336c78
feat(driver): add AudioServerPlugIn_MachServices for XPC helper access
cheefbird Jan 26, 2026
149e7cd
feat(driver): add HelperBridge XPC client with real-time safe volume …
cheefbird Jan 26, 2026
77e2db3
feat(driver): connect to helper XPC service on initialization
cheefbird Jan 26, 2026
0e5304e
refactor(driver): remove custom AudioObject IPC code from VirtualDevice
cheefbird Jan 26, 2026
94d0537
refactor(driver): remove dead IPC types from AudioTypes
cheefbird Jan 26, 2026
ab0e70e
refactor(driver): delete VolumeStore, now in helper
cheefbird Jan 26, 2026
7214f20
feat(host): add XPC error cases to DriverError
cheefbird Jan 26, 2026
02084b1
feat(host): add XPC protocol definition for helper communication
cheefbird Jan 26, 2026
b21886b
refactor(host): rewrite DriverBridge to use XPC
cheefbird Jan 26, 2026
31e238d
refactor(host): update AudioOrchestrator for async DriverBridge
cheefbird Jan 26, 2026
6f010cf
docs: adding some newer ones
cheefbird Jan 27, 2026
ebb0c30
feat(scripts): update install-driver.sh to install helper
cheefbird Jan 27, 2026
c139810
feat(scripts): update uninstall-driver.sh to remove helper
cheefbird Jan 27, 2026
994cca0
test(host): update DriverBridgeTests for async XPC interface
cheefbird Jan 27, 2026
8da5cec
docs: add XPC integration test procedure
cheefbird Jan 27, 2026
59a3b84
fix(driver): use LaunchDaemon for helper and defer XPC init
cheefbird Jan 27, 2026
d945769
docs: update integration test with results
cheefbird Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@
"target": "AppFaders",
"configuration": "release",
"preLaunchTask": "swift: Build Release AppFaders"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:AppFaders}",
"name": "Debug AppFadersHelper",
"target": "AppFadersHelper",
"configuration": "debug",
"preLaunchTask": "swift: Build Debug AppFadersHelper"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:AppFaders}",
"name": "Release AppFadersHelper",
"target": "AppFadersHelper",
"configuration": "release",
"preLaunchTask": "swift: Build Release AppFadersHelper"
}
]
}
5 changes: 5 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let package = Package(
],
products: [
.executable(name: "AppFaders", targets: ["AppFaders"]),
.executable(name: "AppFadersHelper", targets: ["AppFadersHelper"]),
.library(name: "AppFadersDriver", type: .dynamic, targets: ["AppFadersDriver"]),
.plugin(name: "BundleAssembler", targets: ["BundleAssembler"])
],
Expand All @@ -22,6 +23,10 @@ let package = Package(
.product(name: "CAAudioHardware", package: "CAAudioHardware")
]
),
.executableTarget(
name: "AppFadersHelper",
dependencies: []
),
.target(
name: "AppFadersDriverBridge",
dependencies: [],
Expand Down
6 changes: 6 additions & 0 deletions Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
<string>com.fbreidenbach.appfaders.virtualdevice</string>
</dict>

<!-- Mach Services the driver can access from coreaudiod sandbox (QA1811) -->
<key>AudioServerPlugIn_MachServices</key>
<array>
<string>com.fbreidenbach.appfaders.helper</string>
</array>

<!-- Copyright -->
<key>NSHumanReadableCopyright</key>
<string>Copyright 2026 AppFaders</string>
Expand Down
17 changes: 17 additions & 0 deletions Resources/com.fbreidenbach.appfaders.helper.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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>Label</key>
<string>com.fbreidenbach.appfaders.helper</string>
<key>MachServices</key>
<dict>
<key>com.fbreidenbach.appfaders.helper</key>
<true/>
</dict>
<key>ProgramArguments</key>
<array>
<string>/Library/Application Support/AppFaders/AppFadersHelper</string>
</array>
</dict>
</plist>
58 changes: 48 additions & 10 deletions Scripts/install-driver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
HAL_PLUGINS_DIR="/Library/Audio/Plug-Ins/HAL"
DRIVER_NAME="AppFadersDriver.driver"
HELPER_NAME="AppFadersHelper"
HELPER_SUPPORT_DIR="/Library/Application Support/AppFaders"
LAUNCHDAEMON_PLIST="com.fbreidenbach.appfaders.helper.plist"
LAUNCHDAEMONS_DIR="/Library/LaunchDaemons"

# colors for output
RED='\033[0;31m'
Expand All @@ -27,37 +31,71 @@ error() {

cd "$PROJECT_DIR"

# step 1: build
# step 1: build driver and helper
info "Building project..."
swift build || error "Build failed"

# step 2: locate built dylib
# step 2: install helper (before driver so XPC service is available)
info "Installing helper service..."

HELPER_BINARY=".build/debug/$HELPER_NAME"
if [[ ! -f $HELPER_BINARY ]]; then
error "Helper binary not found at $HELPER_BINARY"
fi

# create support directory
sudo mkdir -p "$HELPER_SUPPORT_DIR"

# copy helper binary
sudo cp "$HELPER_BINARY" "$HELPER_SUPPORT_DIR/"
sudo chmod 755 "$HELPER_SUPPORT_DIR/$HELPER_NAME"
sudo chown root:wheel "$HELPER_SUPPORT_DIR/$HELPER_NAME"
info "Helper binary installed to $HELPER_SUPPORT_DIR"

# install LaunchDaemon plist
PLIST_SOURCE="$PROJECT_DIR/Resources/$LAUNCHDAEMON_PLIST"
if [[ ! -f $PLIST_SOURCE ]]; then
error "LaunchDaemon plist not found at $PLIST_SOURCE"
fi

# unload existing if present (ignore errors)
sudo launchctl bootout system "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST" 2>/dev/null || true

sudo cp "$PLIST_SOURCE" "$LAUNCHDAEMONS_DIR/"
sudo chown root:wheel "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST"
sudo chmod 644 "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST"

# bootstrap the LaunchDaemon into system domain
sudo launchctl bootstrap system "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST" || warn "Failed to bootstrap LaunchDaemon (may already be loaded)"
info "Helper LaunchDaemon installed and bootstrapped"

# step 3: locate built driver dylib
DYLIB_PATH=".build/debug/libAppFadersDriver.dylib"
if [[ ! -f $DYLIB_PATH ]]; then
error "Built dylib not found at $DYLIB_PATH"
fi
info "Found dylib: $DYLIB_PATH"

# step 3: locate bundle structure created by plugin
# step 4: locate bundle structure created by plugin
BUNDLE_PATH=$(find .build -path "*BundleAssembler/$DRIVER_NAME" -type d 2>/dev/null | head -1)
if [[ -z $BUNDLE_PATH || ! -d $BUNDLE_PATH ]]; then
error "Bundle structure not found. Make sure BundleAssembler plugin ran."
fi
info "Found bundle: $BUNDLE_PATH"

# step 4: copy dylib to bundle Contents/MacOS/
# step 5: copy dylib to bundle Contents/MacOS/
MACOS_DIR="$BUNDLE_PATH/Contents/MacOS"
BINARY_DEST="$MACOS_DIR/AppFadersDriver"
info "Copying dylib to bundle..."
cp "$DYLIB_PATH" "$BINARY_DEST"
chmod 755 "$BINARY_DEST"

# step 5: fix install name (dylib references @rpath/libAppFadersDriver.dylib which won't resolve)
# step 6: fix install name (dylib references @rpath/libAppFadersDriver.dylib which won't resolve)
info "Fixing install name..."
install_name_tool -id "@loader_path/AppFadersDriver" "$BINARY_DEST"
install_name_tool -change "@rpath/libAppFadersDriver.dylib" "@loader_path/AppFadersDriver" "$BINARY_DEST"

# step 6: code sign the binary
# step 7: code sign the binary
info "Code signing binary..."
# remove marker file that interferes with signing
rm -f "$BUNDLE_PATH/.bundle-ready"
Expand All @@ -70,7 +108,7 @@ info "Using identity hash: $SIGNING_HASH"
codesign --force --options runtime --timestamp --sign "$SIGNING_HASH" "$BINARY_DEST" || error "Code signing failed"
info "Binary signed"

# step 7: verify bundle structure
# step 8: verify bundle structure
if [[ ! -f "$BUNDLE_PATH/Contents/Info.plist" ]]; then
error "Info.plist missing from bundle"
fi
Expand All @@ -79,7 +117,7 @@ if [[ ! -f $BINARY_DEST ]]; then
fi
info "Bundle structure verified"

# step 8: install to HAL directory (requires sudo)
# step 9: install to HAL directory (requires sudo)
INSTALL_PATH="$HAL_PLUGINS_DIR/$DRIVER_NAME"
info "Installing to $INSTALL_PATH (requires sudo)..."

Expand All @@ -93,12 +131,12 @@ sudo chown -R root:wheel "$INSTALL_PATH"
sudo chmod -R 755 "$INSTALL_PATH"
info "Driver installed"

# step 9: restart coreaudiod
# step 10: restart coreaudiod
info "Restarting coreaudiod (requires sudo)..."
sudo killall coreaudiod 2>/dev/null || true
sleep 2

# step 10: verify device appears
# step 11: verify device appears
info "Verifying device registration..."
sleep 1

Expand Down
61 changes: 52 additions & 9 deletions Scripts/uninstall-driver.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
#!/bin/bash
# uninstall-driver.sh
# Removes the AppFaders driver from the system
# Removes the AppFaders driver and helper service from the system

set -e

DRIVER_PATH="/Library/Audio/Plug-Ins/HAL/AppFadersDriver.driver"
HELPER_NAME="AppFadersHelper"
HELPER_SUPPORT_DIR="/Library/Application Support/AppFaders"
LAUNCHDAEMON_PLIST="com.fbreidenbach.appfaders.helper.plist"
LAUNCHDAEMONS_DIR="/Library/LaunchDaemons"

if [[ ! -d $DRIVER_PATH ]]; then
echo "Driver not installed at $DRIVER_PATH"
exit 0
fi
# colors for output
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }

echo "Removing $DRIVER_PATH..."
sudo rm -rf "$DRIVER_PATH"
# step 1: remove driver
if [[ -d $DRIVER_PATH ]]; then
info "Removing driver at $DRIVER_PATH..."
sudo rm -rf "$DRIVER_PATH"
else
warn "Driver not installed at $DRIVER_PATH"
fi

echo "Restarting coreaudiod..."
# step 2: restart coreaudiod
info "Restarting coreaudiod..."
sudo killall coreaudiod 2>/dev/null || true
sleep 2

echo "Done. Driver uninstalled."
# step 3: unload LaunchDaemon (ignore errors if not loaded)
info "Unloading helper LaunchDaemon..."
sudo launchctl bootout system "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST" 2>/dev/null || true

# step 4: remove LaunchDaemon plist
if [[ -f "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST" ]]; then
info "Removing LaunchDaemon plist..."
sudo rm -f "$LAUNCHDAEMONS_DIR/$LAUNCHDAEMON_PLIST"
else
warn "LaunchDaemon plist not found"
fi

# step 5: remove helper binary
if [[ -f "$HELPER_SUPPORT_DIR/$HELPER_NAME" ]]; then
info "Removing helper binary..."
sudo rm -f "$HELPER_SUPPORT_DIR/$HELPER_NAME"
else
warn "Helper binary not found"
fi

# step 6: remove support directory if empty
if [[ -d "$HELPER_SUPPORT_DIR" ]]; then
if [[ -z "$(ls -A "$HELPER_SUPPORT_DIR")" ]]; then
info "Removing empty support directory..."
sudo rmdir "$HELPER_SUPPORT_DIR"
else
warn "Support directory not empty, leaving in place"
fi
fi

info "Done. Driver and helper uninstalled."
Loading
Loading