dend/decksurf-sdk

GitHub: dend/decksurf-sdk

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

Stars: 32 | Forks: 5

![DeckSurf SDK Icon](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/ded1d9bd30022942.webp) # DeckSurf SDK for .NET _**适用于 Stream Deck 的非官方软件开发工具包,使用 C# 为 .NET 平台构建。**_ [![NuGet Version](https://img.shields.io/nuget/v/DeckSurf.SDK)](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, 图像处理, 外设驱动, 多人体追踪, 威胁情报, 宏指令, 屏幕控制, 开发者工具, 开源项目, 按键映射, 硬件控制, 网络调试, 自动化, 设备管理