A Stream Deck plugin that adds a button to safely eject all external disks on macOS with a single button press. This plugin provides visual feedback during the ejection process and allows customization of the button appearance.
- Fast native disk ejection - Uses macOS DiskArbitration framework (typically ~6–10x faster than
diskutil) - Pure Swift implementation - Native Stream Deck plugin with no Node.js or shell script dependencies
- Real-time disk count monitoring - Shows the number of attached external disks on the button
- Automatic updates - Disk count updates every 3 seconds as disks are mounted/unmounted
- Single button to eject all external disks in parallel
- Visual feedback for ejection status (normal, ejecting, success, error)
- Customizable button title visibility
- Comprehensive error handling with detailed logging
- macOS 13 or later
- Stream Deck 6.4 or later
- Full Disk Access permission (see Permissions below)
- Xcode Command Line Tools (for building from source)
- Download the latest release from the releases page
- Double-click the downloaded
.streamDeckPluginfile to install it - Stream Deck will prompt you to install the plugin
See Development section below.
This plugin requires Full Disk Access permission to eject disks. Without this permission, disk ejection operations will fail silently or return permission errors.
- Open System Settings (or System Preferences on older macOS)
- Navigate to Privacy & Security > Full Disk Access
- Click the + button to add an application
- Navigate to the Stream Deck application:
/Applications/Elgato Stream Deck.app
- Enable the toggle next to Stream Deck
- Restart the Stream Deck application
Alternatively, if you're running the plugin binary directly for development:
- Add the plugin binary to Full Disk Access:
~/Library/Application Support/com.elgato.StreamDeck/Plugins/org.deverman.ejectalldisks.sdPlugin/org.deverman.ejectalldisks
The macOS DiskArbitration framework requires elevated permissions to unmount and eject volumes. This is a security feature to prevent malicious apps from ejecting disks without user consent. By granting Full Disk Access to Stream Deck, you're authorizing it to perform disk operations on your behalf.
- Drag the "Eject All Disks" action from the "Eject All Disks" category onto your Stream Deck
- The button will automatically display the number of external disks currently attached
- The count updates automatically every 3 seconds as you mount/unmount disks
- Press the button to eject all external disks
- The button will display the ejection status visually
- Configure the button to show or hide the title text via Settings
| State | Description |
|---|---|
| Idle (disks connected) | Shows "X Disk(s)" count |
| Idle (no disks) | Shows "No Disks" |
| Ejecting | Shows "Ejecting..." while operation runs |
| Success | Shows "Ejected!" after successful eject |
| Error | Shows error details: "In Use", "1 of 3 Failed", "Grant Access", etc. |
In the Stream Deck button configuration:
- Show Title: Toggle to show/hide the disk count text on the button
- macOS 13 or later
- Xcode Command Line Tools (
xcode-select --install) - Swift 6.2.1 or later
eject_all_disks_streamdeck/
├── swift-plugin/ # Swift Stream Deck plugin
│ ├── Sources/EjectAllDisksPlugin/ # Plugin source code
│ │ ├── Actions/ # Stream Deck actions
│ │ │ └── EjectAction.swift # Main eject action
│ │ └── EjectAllDisksPlugin.swift # Plugin entry point
│ ├── Tests/ # Swift Testing tests
│ ├── Package.swift # Swift package manifest
│ └── build.sh # Build script
├── swift/ # SwiftDiskArbitration library
│ └── Packages/SwiftDiskArbitration/
├── org.deverman.ejectalldisks.sdPlugin/ # Plugin bundle
│ ├── org.deverman.ejectalldisks # Compiled binary (after build)
│ ├── ui/ # Property Inspector HTML
│ ├── imgs/ # Icons and images
│ └── manifest.json # Plugin configuration
└── README.md # This file
- Clone the repository:
git clone https://github.com/deverman/eject_all_disks_streamdeck.git
cd eject_all_disks_streamdeck- Build the Swift plugin:
cd swift-plugin
./build.sh --installThis compiles the Swift plugin and copies the binary to the plugin bundle.
cd swift-plugin
swift testOption 1: Using Stream Deck CLI (Recommended)
streamdeck link org.deverman.ejectalldisks.sdPluginNote: Install the Stream Deck CLI with npm install -g @elgato/cli if not already installed.
Option 2: Manual Symlink
# Close Stream Deck first
ln -sf "$(pwd)/org.deverman.ejectalldisks.sdPlugin" \
~/Library/Application\ Support/com.elgato.StreamDeck/Plugins/Then restart the Stream Deck application.
- Make changes to Swift files in
swift-plugin/Sources/ - Build and install:
cd swift-plugin && ./build.sh --install - Restart plugin:
streamdeck restart org.deverman.ejectalldisks - Or restart Stream Deck application completely
Plugin logs via system log:
log stream --predicate 'subsystem == "org.deverman.ejectalldisks"' --level debugStream Deck application logs:
tail -f ~/Library/Logs/com.elgato.StreamDeck/StreamDeck0.logPlugin doesn't appear in Stream Deck:
- Ensure the binary exists:
ls org.deverman.ejectalldisks.sdPlugin/org.deverman.ejectalldisks - Run
./build.sh --installto build and install the plugin - Restart Stream Deck application completely
- Check that
manifest.jsonhas correct paths
Build errors:
- Ensure Xcode Command Line Tools are installed:
xcode-select --install - Check Swift version:
swift --version(requires 6.2.1+) - Clean build:
cd swift-plugin && swift package clean && ./build.sh
Disk count not updating:
- Check logs for errors:
log stream --predicate 'subsystem == "org.deverman.ejectalldisks"' - Verify you have external disks mounted (not internal)
- Make sure the action is visible on your Stream Deck
# Build the plugin first
cd swift-plugin
./build.sh --install
# Package using Stream Deck CLI (recommended)
cd ..
streamdeck pack org.deverman.ejectalldisks.sdPlugin
# Or manually create a .streamDeckPlugin file
# zip -r org.deverman.ejectalldisks.streamDeckPlugin \
# org.deverman.ejectalldisks.sdPlugin \
# -x "*.DS_Store" -x "*/logs/*" -x "*.log"The streamdeck pack command creates a properly formatted .streamDeckPlugin file ready for distribution.
The plugin uses the StreamDeckPlugin Swift library:
- EjectAllDisksPlugin - Main plugin class that handles initialization and disk monitoring
- EjectAction - KeyAction that responds to button presses and manages the eject operation
- SwiftDiskArbitration - Local library providing async/await wrapper around macOS DiskArbitration framework
The plugin uses the macOS DiskArbitration framework directly:
- Enumerates all mounted volumes using
DADiskCreateFromVolumePath - Filters to external, ejectable volumes only
- Unmounts each volume using
DADiskUnmount - Ejects the physical device using
DADiskEject - Runs all operations in parallel using Swift concurrency
This approach is typically ~6–10x faster than calling diskutil eject as a subprocess (machine and disk dependent).
This plugin is designed with security as a priority:
- Uses macOS system APIs (not volume names) to detect protected volumes
- Checks
.volumeIsRootFileSystemKeyto identify the boot drive regardless of its name - Checks
.volumeIsBrowsableKeyto skip system-only volumes (Recovery, Preboot, etc.) - Additional DiskArbitration property checks for edge cases
- Never relies on hardcoded volume names - safe even if you renamed "Macintosh HD"
- Does not log volume names - avoids exposing sensitive information like "ConfidentialProject"
- Only logs BSD device names (e.g., "disk2s1") when debug logging is enabled
- Logs are written using OSLog with appropriate privacy levels
- Uses macOS's native DiskArbitration framework for safe unmount and eject
- Requires Full Disk Access permission (user must explicitly grant)
- Runs entirely in user space - no root/sudo required
- Cannot access files on disks, only mount/unmount operations
-
Button shows error state:
- Check logs for which process is blocking ejection
- Common blockers: Spotlight (
mds), backup apps, file sync apps - Try pressing the button again - temporary locks often release quickly
-
Disk won't eject but Finder can eject it:
- Finder sends a "please close files" notification to apps before ejecting
- The native API doesn't send this notification
- Pause or quit the blocking application, then try again
-
Disk count shows 0 but disks are connected:
- Only external, ejectable volumes are counted
- Network drives and internal volumes are excluded
- Check that disks appear in Finder sidebar
-
Plugin not loading:
- Verify binary exists and is executable
- Check Stream Deck logs for error messages
- Try reinstalling the plugin
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter any issues:
- Check the Issues page
- File a new issue with:
- macOS version
- Stream Deck software version
- Steps to reproduce
- Log output if available
Created by Brent Deverman