Skip to content

Commit 6e27390

Browse files
committed
Initial commit: Eject All Disks Stream Deck Plugin
Features: - Single button to eject all external disks on macOS - Visual feedback with animated states (normal, ejecting, success, error) - Customizable button title visibility - Secure implementation using validated diskutil commands - SVG icons with enhanced visibility - Complete property inspector for settings Implementation: - TypeScript with Stream Deck SDK - SVG-based dynamic icons - Security-focused shell commands - Robust error handling - Settings persistence
1 parent f606be5 commit 6e27390

24 files changed

+3640
-0
lines changed

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = tab
8+
max_line_length = 120
9+
10+
[*.{json,jsonc,md}]
11+
indent_size = 4
12+
indent_style = space
13+
14+
[*.{yaml,yml}]
15+
indent_size = 2
16+
indent_style = space

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Node.js
2+
node_modules/
3+
4+
# Stream Deck files
5+
*.sdPlugin/bin
6+
*.sdPlugin/logs

.rules

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Guidance for Building StreamDeck Plugins
2+
3+
## Project Structure
4+
5+
### Required Files
6+
- `manifest.json` - Plugin configuration, paths, and metadata
7+
- `*.sdPlugin` directory - Contains all plugin files
8+
- `/bin` - Compiled JavaScript files
9+
- `/ui` - Property Inspector HTML files
10+
- `/libs` - SDK libraries and assets
11+
- `/imgs` - Plugin images and icons
12+
13+
### Property Inspector
14+
- Place HTML files in `/ui` directory not `/pi`
15+
- Always include required meta tags:
16+
```html
17+
<meta charset="utf-8">
18+
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui,viewport-fit=cover">
19+
<meta name="apple-mobile-web-app-capable" content="yes">
20+
```
21+
- Use Stream Deck's CSS classes for consistent styling:
22+
- `sdpi-wrapper` for the main container
23+
- `sdpi-item` for form elements
24+
- `sdpi-item-label` for labels
25+
- `sdpi-item-value` for input containers
26+
27+
## Settings Implementation
28+
29+
### TypeScript Plugin
30+
- Define settings interface explicitly:
31+
```typescript
32+
type Settings = {
33+
propertyName: boolean | string | number;
34+
};
35+
```
36+
- Use default settings object:
37+
```typescript
38+
const DEFAULT_SETTINGS: Settings = {
39+
propertyName: defaultValue
40+
};
41+
```
42+
- Handle settings in proper lifecycle methods:
43+
- `onWillAppear` - Initialize defaults
44+
- `onDidReceiveSettings` - React to changes
45+
- Always merge with defaults when accessing settings
46+
47+
### Property Inspector
48+
- Required files:
49+
- `sdpi.css` for styling
50+
- `property-inspector.js` for core functionality
51+
- Proper WebSocket event flow:
52+
1. Connect WebSocket
53+
2. Register plugin
54+
3. Request initial settings
55+
4. Handle settings updates
56+
5. Send settings changes
57+
58+
## Button State Management
59+
60+
### Title Options
61+
Only use documented properties:
62+
- `state`: (number) - Button state (0 or 1)
63+
- `target`: (Target) - Display target (Hardware, Software, both)
64+
✅ `setTitle("Text", { state: 0, target: Target.Hardware })`
65+
❌ `setTitle("Text", { color: "#FFF" })`
66+
67+
### Image Options
68+
- Use SVG for dynamic icons
69+
- Add semi-transparent backgrounds for text contrast
70+
- Use stroke outlines for better visibility
71+
- Include error and success states
72+
✅ `setImage("data:image/svg+xml,${svg}", { state: 0 })`
73+
❌ `setImage("path.png", { tint: "blue" })`
74+
75+
## Development Best Practices
76+
77+
### Settings Persistence
78+
- Always handle undefined settings gracefully
79+
- Set defaults in both UI and plugin code
80+
- Use proper typing for type safety
81+
- Test settings persistence across:
82+
- Stream Deck restarts
83+
- Profile changes
84+
- Property Inspector reopens
85+
86+
### Error Handling
87+
- Log errors with streamDeck.logger
88+
- Show visual feedback for errors
89+
- Reset to normal state after errors
90+
- Include error recovery in state management
91+
92+
### Testing
93+
- Validate plugin with `streamdeck validate`
94+
- Test on both hardware and software displays
95+
- Verify settings persistence
96+
- Check all state transitions
97+
- Test error conditions
98+
99+
## Documentation Links
100+
- [Getting Started](https://docs.elgato.com/streamdeck/sdk/introduction/getting-started)
101+
- [Settings Guide](https://docs.elgato.com/streamdeck/sdk/guides/settings)
102+
- [Property Inspector](https://docs.elgato.com/streamdeck/sdk/guides/property-inspector)
103+
- [API Reference](https://docs.elgato.com/streamdeck/sdk/api-reference)

.vscode/launch.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Attach to Plugin",
9+
"type": "node",
10+
"request": "attach",
11+
"processId": "${command:PickProcess}",
12+
"outFiles": ["${workspaceFolder}/bin/**/*.js"],
13+
"resolveSourceMapLocations": ["${workspaceFolder}/**"]
14+
}
15+
]
16+
}

.vscode/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
/* JSON schemas */
3+
"json.schemas": [
4+
{
5+
"fileMatch": ["**/manifest.json"],
6+
"url": "https://schemas.elgato.com/streamdeck/plugins/manifest.json"
7+
},
8+
{
9+
"fileMatch": ["**/layouts/*.json"],
10+
"url": "https://schemas.elgato.com/streamdeck/plugins/layout.json"
11+
}
12+
]
13+
}

README.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Eject All Disks - Stream Deck Plugin
2+
3+
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.
4+
5+
## Features
6+
7+
- Single button to eject all external disks
8+
- Visual feedback for ejection status (normal, ejecting, success, error)
9+
- Customizable button title visibility
10+
- Safe disk ejection using macOS `diskutil`
11+
- Animated states during ejection
12+
- Comprehensive error handling with visual indicators
13+
14+
## Requirements
15+
16+
- macOS 12 or later
17+
- Stream Deck 6.4 or later
18+
- Stream Deck Software
19+
20+
## Installation
21+
22+
1. Download the latest release from the [releases page](https://github.com/brentdeverman/eject-all-disks-streamdeck/releases)
23+
2. Double-click the downloaded `.streamDeckPlugin` file to install it
24+
3. Stream Deck will prompt you to install the plugin
25+
26+
## Usage
27+
28+
1. Drag the "Eject All Disks" action from the "Eject All Disks" category onto your Stream Deck
29+
2. Press the button to eject all external disks
30+
3. The button will display the ejection status visually
31+
4. Configure the button to show or hide the title text via Settings
32+
33+
### Button States
34+
35+
- **Default**: Standard eject icon
36+
- **Ejecting**: Animated eject icon while process runs
37+
- **Success**: Green checkmark with eject icon
38+
- **Error**: Red X with eject icon
39+
40+
### Settings
41+
42+
In the Stream Deck button configuration:
43+
- **Show Title**: Toggle to show/hide the "Eject All Disks" text on the button
44+
45+
## Security
46+
47+
This plugin:
48+
- Only ejects external disks (not internal drives)
49+
- Uses macOS's built-in `diskutil` command with validation
50+
- Validates disk paths before ejection
51+
- Cannot access any other system resources
52+
53+
## Development
54+
55+
### Prerequisites
56+
57+
- Node.js 20 or later
58+
- TypeScript
59+
- Stream Deck SDK and CLI
60+
61+
### Project Structure
62+
63+
```
64+
eject_all_disks_streamdeck/
65+
├── src/ # TypeScript source files
66+
│ ├── actions/ # Action implementations
67+
│ └── plugin.ts # Plugin entry point
68+
├── org.deverman.ejectalldisks.sdPlugin/ # Plugin resources
69+
│ ├── bin/ # Compiled JavaScript
70+
│ ├── ui/ # Property Inspector HTML
71+
│ ├── imgs/ # Icons and images
72+
│ ├── libs/ # Library files
73+
│ └── manifest.json # Plugin configuration
74+
├── dist/ # Packaged plugin (.streamDeckPlugin)
75+
└── README.md # This file
76+
```
77+
78+
### Building the Plugin
79+
80+
1. Clone the repository:
81+
```bash
82+
git clone https://github.com/brentdeverman/eject-all-disks-streamdeck.git
83+
cd eject-all-disks-streamdeck
84+
```
85+
86+
2. Install dependencies:
87+
```bash
88+
npm install
89+
```
90+
91+
3. Build the TypeScript code:
92+
```bash
93+
npm run build
94+
```
95+
96+
4. Create icons and resources:
97+
- SVG icons in the appropriate directories
98+
- Property inspector HTML
99+
- Update manifest.json
100+
101+
5. Pack the plugin:
102+
```bash
103+
# Create dist directory if it doesn't exist
104+
mkdir -p dist
105+
106+
# Pack the plugin
107+
streamdeck pack org.deverman.ejectalldisks.sdPlugin --output dist
108+
```
109+
110+
### Implementation Details
111+
112+
#### SVG Icons
113+
The plugin uses SVG icons for dynamic rendering with different states:
114+
- Normal state: Orange eject icon
115+
- Ejecting state: Animated yellow eject icon
116+
- Success state: Green eject icon with checkmark
117+
- Error state: Red eject icon with X mark
118+
119+
Each icon includes a semi-transparent background for better text contrast.
120+
121+
#### Settings Implementation
122+
Settings are implemented using:
123+
- TypeScript interface for type safety
124+
- Default values to handle initialization
125+
- Property Inspector for UI controls
126+
- WebSocket communication between UI and plugin
127+
128+
#### Shell Command
129+
The plugin uses a secure shell command to safely eject disks:
130+
```bash
131+
IFS=$'\n'
132+
disks=$(diskutil list external | grep -o -E '/dev/disk[0-9]+')
133+
for disk in $disks; do
134+
# Validate disk path format for security
135+
if [[ "$disk" =~ ^/dev/disk[0-9]+$ ]]; then
136+
diskutil unmountDisk "$disk"
137+
else
138+
echo "Invalid disk path: $disk" >&2
139+
fi
140+
done
141+
```
142+
143+
This implementation includes security measures like path validation and proper error handling.
144+
145+
## Troubleshooting
146+
147+
### Common Issues
148+
149+
1. **Button shows error state:**
150+
- Ensure disks aren't currently in use by applications
151+
- Check for file operations in progress
152+
- Try ejecting through Finder first to see specific error messages
153+
154+
2. **Settings not saving:**
155+
- Restart Stream Deck software
156+
- Check permissions
157+
158+
## License
159+
160+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
161+
162+
## Contributing
163+
164+
Contributions are welcome! Please feel free to submit a Pull Request.
165+
166+
## Support
167+
168+
If you encounter any issues:
169+
1. Check the [Issues](https://github.com/brentdeverman/eject-all-disks-streamdeck/issues) page
170+
2. File a new issue with:
171+
- macOS version
172+
- Stream Deck software version
173+
- Steps to reproduce
174+
- Error messages if any
175+
176+
---
177+
178+
Made with ❤️ by Brent Deverman
Binary file not shown.
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 8 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)