dend/decksurf-sdk
GitHub: dend/decksurf-sdk
这是一个基于 .NET 的非官方 SDK,允许开发者通过 C# 代码直接管理和控制 Elgato Stream Deck 设备,实现按键图像设置、事件监听及跨平台设备交互。
Stars: 32 | Forks: 5

# DeckSurf SDK for .NET
_**适用于 Stream Deck 的非官方软件开发工具包,使用 C# 为 .NET 平台构建。**_
[](https://www.nuget.org/packages/DeckSurf.SDK)
## 安装说明
```
dotnet add package DeckSurf.SDK
```
## 快速开始
```
using DeckSurf.SDK.Core;
using DeckSurf.SDK.Models;
// Enumerate connected Stream Deck devices
var devices = DeviceManager.GetDeviceList();
if (devices.Count == 0)
{
Console.WriteLine("No Stream Deck devices found.");
return;
}
// Use the first device
using var device = devices[0];
// Listen for button presses (filter to Down to avoid double-firing on release)
device.ButtonPressed += (sender, e) =>
{
if (e.EventKind != ButtonEventKind.Down) return;
Console.WriteLine($"Button {e.Id} pressed (type: {e.ButtonKind})");
};
device.StartListening();
// Set a button image (JPEG, PNG, BMP, GIF, or any ImageSharp-supported format)
// Images are automatically resized to match the device's button resolution.
byte[] image = File.ReadAllBytes("icon.png");
device.SetKey(0, image);
// Set brightness (0-100)
device.SetBrightness(80);
```
## 按键布局
按键编号顺序为**从左到右,从上到下**,从 0 开始。使用 `device.ButtonColumns` 和 `device.ButtonRows` 来了解网格布局:
```
Stream Deck Original (5×3):
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
```
| 设备 | 按键数量 | 布局 | 按键分辨率 |
|:---|:---|:---|:---|
| Original / Original 2019 / MK.2 | 15 | 5×3 | 72×72 px |
| XL / XL 2022 | 32 | 8×4 | 96×96 px |
| Mini / Mini 2022 | 6 | 3×2 | 80×80 px |
| Neo | 8 | 4×2 | 96×96 px |
| Plus | 8 | 4×2 | 120×120 px |
传递给 `SetKey()` 的图像会**自动调整大小**以适应设备的按键分辨率。为获得最佳效果,请使用正方形图像。非正方形图像将被拉伸。
## 错误处理
SDK 使用以 `DeckSurfException` 为根的结构化异常模型:
| 异常 | 触发时机 | 关键属性 |
|:---|:---|:---|
| `DeviceCommunicationException` | 操作期间发生 USB I/O 故障 | `IsTransient` — 如果可以安全重试则为 true |
| `DeviceDisconnectedException` | 操作中途设备被拔出 | `DeviceSerial` — 标识是哪个设备 |
| `DeviceNotFoundException` | 设备查找失败(未找到序列号/路径) | — |
| `ImageProcessingException` | 在 `SetKey` 或 `ImageHelper.ResizeImage` 中遇到无法识别的图像格式 | — |
| `ObjectDisposedException` | 在已释放的设备上调用方法 | — |
| `InvalidOperationException` | 在已处于监听状态时调用 `StartListening()` | — |
| `ArgumentOutOfRangeException` | 在 `SetKey` 中按键索引超出范围 | — |
| `IndexOutOfRangeException` | 在 `SetKeyColor` 中按键索引超出范围 | — |
```
using DeckSurf.SDK.Exceptions;
try
{
device.SetKey(0, imageData);
}
catch (DeviceCommunicationException ex) when (ex.IsTransient)
{
// USB I/O failure — safe to retry
}
catch (DeviceDisconnectedException ex)
{
// Device was physically unplugged
Console.WriteLine($"Lost device {ex.DeviceSerial}");
}
```
对于事件驱动的错误处理,请订阅 `DeviceErrorOccurred`:
```
device.DeviceErrorOccurred += (sender, e) =>
{
Console.WriteLine($"Error in {e.OperationName}: {e.Category} (transient: {e.IsTransient})");
};
```
## 设备断开连接
当设备被拔出时,会触发 `DeviceDisconnected` 事件。在此事件之后,设备实例将无法使用——不会再触发任何事件,且所有方法调用都将抛出 `ObjectDisposedException`。请释放该实例并获取一个新实例以重新连接:
```
device.DeviceDisconnected += (sender, e) =>
{
Console.WriteLine("Device disconnected. Attempting to reconnect...");
device.Dispose();
// Re-enumerate to find the device again
if (DeviceManager.TryGetDeviceBySerial(knownSerial, out var newDevice))
{
// Use newDevice
}
};
```
## 多设备支持
使用序列号在重新插拔后进行稳定的设备识别:
```
// Find a specific device
if (DeviceManager.TryGetDeviceBySerial("CL12K1A00042", out var myDevice))
{
using (myDevice)
{
myDevice.StartListening();
myDevice.SetKey(0, imageData);
}
}
// Or throw if not found
var device = DeviceManager.GetDeviceBySerial("CL12K1A00042");
// Monitor for device connection changes
DeviceManager.DeviceListChanged += (sender, e) =>
{
Console.WriteLine("USB device change detected — re-enumerate devices.");
};
```
## 屏幕支持 (Plus & Neo)
带有 LCD 屏幕的设备会公开屏幕操作:
```
if (device.IsScreenSupported)
{
byte[] screenImage = File.ReadAllBytes("banner.jpg");
var resized = ImageHelper.ResizeImage(
screenImage,
device.ScreenWidth,
device.ScreenHeight,
device.ImageRotation,
device.KeyImageFormat);
device.SetScreen(resized, 0, device.ScreenWidth, device.ScreenHeight);
}
```
## 线程安全
SDK **不是线程安全的**。所有事件(`ButtonPressed`、`DeviceDisconnected`、`DeviceErrorOccurred`)都在**后台线程**(线程池)上触发。如果您需要从事件处理程序更新 UI 或调用设备方法,必须同步访问:
```
var deviceLock = new object();
// Safe to call SetKey from a button press handler
device.ButtonPressed += (sender, e) =>
{
if (e.EventKind != ButtonEventKind.Down) return;
lock (deviceLock)
{
device.SetKey(e.Id, highlightImage);
}
};
// WPF/WinForms: marshal to UI thread
device.ButtonPressed += (sender, e) =>
{
Dispatcher.Invoke(() => statusLabel.Text = $"Button {e.Id} pressed");
};
```
## 卸载到后台线程
所有设备 I/O 都是**同步的**——USB HID 没有真正的异步原语。如果您需要避免阻塞 UI 线程(WPF、WinForms),请在调用点使用 `Task.Run`:
```
// WPF button click handler — offload to thread pool
await Task.Run(() => device.SetKey(0, imageData));
await Task.Run(() => device.SetBrightness(80));
```
这是有意为之的:SDK 不会将同步调用包装在虚假的异步方法中。您可以控制何时何地使用线程池线程。
## 设备监控
`DeviceWatcher` 通过序列号监控特定设备,并在连接/断开连接时引发事件:
```
using DeckSurf.SDK.Core;
using var watcher = new DeviceWatcher("CL12K1A00042");
watcher.DeviceConnected += (sender, device) =>
{
Console.WriteLine($"Device reconnected: {device.DisplayName}");
device.StartListening();
};
watcher.DeviceLost += (sender, e) =>
{
Console.WriteLine("Device lost — waiting for reconnection...");
};
watcher.Start();
```
如需获取原始更改通知,请订阅 `DeviceManager.DeviceListChanged`:
```
using DeckSurf.SDK.Core;
DeviceManager.DeviceListChanged += (sender, e) =>
{
foreach (var added in e.Added)
Console.WriteLine($"Connected: {added.Name} ({added.Serial})");
foreach (var removed in e.Removed)
Console.WriteLine($"Disconnected: {removed.Name} ({removed.Serial})");
};
```
## 日志记录
SDK 集成了 `Microsoft.Extensions.Logging`。请在创建设备之前进行配置:
```
using Microsoft.Extensions.Logging;
using DeckSurf.SDK.Core;
DeckSurfConfiguration.LoggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole().SetMinimumLevel(LogLevel.Debug);
});
// All subsequent device operations will be logged
var devices = DeviceManager.GetDeviceList();
```
SDK 会在适当的级别记录设备操作:`Debug` 级别用于 SetKey/SetBrightness 调用,`Information` 级别用于 StartListening/StopListening,`Warning` 级别用于瞬态 USB 错误,`Error` 级别用于设备断开连接。
## 支持的设备
| 设备 | 支持情况 |
|:---|:---|
| Stream Deck XL | 完全支持 |
| Stream Deck XL (2022) | 完全支持 |
| Stream Deck Plus | 完全支持 |
| Stream Deck Original | 完全支持 |
| Stream Deck Original (2019) | 完全支持 |
| Stream Deck MK.2 | 完全支持 |
| Stream Deck Mini | 完全支持 |
| Stream Deck Mini (2022) | 完全支持 |
| Stream Deck Neo | 完全支持 |
## 平台支持
核心功能是**跨平台**的(Windows、macOS、Linux)。SDK 使用 [HidSharp](https://github.com/IntergatedCircuits/HidSharp) 进行 USB HID 通信。
少量实用方法(`ImageHelper.GetFileIcon()`、`ImageHelper.GetImageBuffer(Icon)`)是 **Windows 专用**的,并标记有 `[SupportedOSPlatform("windows")]`。所有核心设备操作均支持跨平台。
### 平台设置
**Windows:** 开箱即用。在运行之前请关闭 Elgato Stream Deck 软件——它独占访问设备。
**macOS:** USB HID 访问可能需要应用授权(`com.apple.security.device.usb`)。如果没有此授权,`GetDeviceList()` 将返回空列表且不会报错。
**Linux:** 配置 udev 规则以允许非 root 用户访问 USB:
```
# /etc/udev/rules.d/99-streamdeck.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", MODE="0666"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0fd9", MODE="0666"
```
然后重新加载:`sudo udevadm control --reload-rules && sudo udevadm trigger`
## 故障排除
**`GetDeviceList()` 返回空列表:**
- 关闭 Elgato Stream Deck 软件(它独占 USB 访问权限)
- 在 Linux 上,确保已配置 udev 规则(见上文)
- 在 macOS 上,检查应用 codesign 配置文件中的 USB 授权
- 断开并重新连接设备
- 验证设备是否出现在您的操作系统设备管理器中
## 文档
请参阅 [`https://docs.deck.surf`](https://docs.deck.surf/) 获取教程和完整的 API 文档。
## 许可证
本项目采用 MIT 许可证。详情请参阅 [LICENSE.md](LICENSE.md)。
标签:DNS解析, Elgato, ImageSharp, NuGet, Stream Deck, 图像处理, 外设驱动, 多人体追踪, 威胁情报, 宏指令, 屏幕控制, 开发者工具, 开源项目, 按键映射, 硬件控制, 网络调试, 自动化, 设备管理