Skip to content

dend/decksurf-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

21 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

DeckSurf SDK Icon

DeckSurf SDK for .NET

Unofficial Software Development Kit for your Stream Deck, built in C# for the .NET platform.

Warning

This SDK is under active development and is currently in its alpha stage. That means that there may be breaking changes between releases until it hits 1.0.0.

Note

For the DeckSurf tooling (CLI and plugins), refer to the DeckSurf repository.

NuGet Version

Installation

dotnet add package DeckSurf.SDK

Quick Start

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);

Tip: Button events fire twice per physical press β€” once for Down (pressed) and once for Up (released). Filter on ButtonEventKind.Down if you only want to respond once per press.

Button Layout

Buttons are numbered left-to-right, top-to-bottom, starting at 0. Use device.ButtonColumns and device.ButtonRows to understand the grid:

Stream Deck Original (5Γ—3):
 0  1  2  3  4
 5  6  7  8  9
10 11 12 13 14
Device Buttons Layout Button Resolution
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

Images passed to SetKey() are automatically resized to the device's button resolution. For best results, use square images. Non-square images will be stretched.

Error Handling

The SDK uses a structured exception model rooted in DeckSurfException:

Exception When Key Property
DeviceCommunicationException USB I/O failure during operation IsTransient β€” true if safe to retry
DeviceDisconnectedException Device unplugged mid-operation DeviceSerial β€” identifies which device
DeviceNotFoundException Device lookup failed (serial/path not found) β€”
ImageProcessingException Unrecognized image format in SetKey or ImageHelper.ResizeImage β€”
ObjectDisposedException Method called on a disposed device β€”
InvalidOperationException StartListening() called when already listening β€”
ArgumentOutOfRangeException Button index out of range in SetKey β€”
IndexOutOfRangeException Key index out of range in 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}");
}

For event-driven error handling, subscribe to DeviceErrorOccurred:

device.DeviceErrorOccurred += (sender, e) =>
{
    Console.WriteLine($"Error in {e.OperationName}: {e.Category} (transient: {e.IsTransient})");
};

Device Disconnection

When a device is unplugged, the DeviceDisconnected event fires. After this event, the device instance is unusable β€” no further events will fire and all method calls will throw ObjectDisposedException. Dispose it and acquire a fresh instance to reconnect:

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
    }
};

Multiple Devices

Use serial numbers for stable device identification across re-plugs:

// 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.");
};

Screen Support (Plus & Neo)

Devices with LCD screens expose screen operations:

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);
}

Thread Safety

The SDK is not thread-safe. All events (ButtonPressed, DeviceDisconnected, DeviceErrorOccurred) fire on background threads (thread pool). If you need to update UI or call device methods from event handlers, you must synchronize access:

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");
};

Offloading to Background Threads

All device I/O is synchronous β€” USB HID has no true async primitives. If you need to avoid blocking the UI thread (WPF, WinForms), use Task.Run at the call site:

// WPF button click handler β€” offload to thread pool
await Task.Run(() => device.SetKey(0, imageData));
await Task.Run(() => device.SetBrightness(80));

This is intentional: the SDK does not wrap sync calls in fake async methods. You control when and where thread pool threads are used.

Device Monitoring

DeviceWatcher monitors a specific device by serial number and raises events on connect/disconnect:

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();

For raw change notifications, subscribe to 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})");
};

Logging

The SDK integrates with Microsoft.Extensions.Logging. Configure before creating devices:

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();

The SDK logs device operations at appropriate levels: Debug for SetKey/SetBrightness calls, Information for StartListening/StopListening, Warning for transient USB errors, and Error for device disconnections.

Supported Devices

Device Support
Stream Deck XL Full
Stream Deck XL (2022) Full
Stream Deck Plus Full
Stream Deck Original Full
Stream Deck Original (2019) Full
Stream Deck MK.2 Full
Stream Deck Mini Full
Stream Deck Mini (2022) Full
Stream Deck Neo Full

Platform Support

Core functionality is cross-platform (Windows, macOS, Linux). The SDK uses HidSharp for USB HID communication.

A small number of utility methods (ImageHelper.GetFileIcon(), ImageHelper.GetImageBuffer(Icon)) are Windows-only and are marked with [SupportedOSPlatform("windows")]. All core device operations work cross-platform.

Platform Setup

Windows: Works out of the box. Close the Elgato Stream Deck software before running β€” it holds exclusive access to the device.

macOS: USB HID access may require app entitlements (com.apple.security.device.usb). Without this, GetDeviceList() will return an empty list with no error.

Linux: Configure udev rules for non-root USB access:

# /etc/udev/rules.d/99-streamdeck.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", MODE="0666"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0fd9", MODE="0666"

Then reload: sudo udevadm control --reload-rules && sudo udevadm trigger

Troubleshooting

GetDeviceList() returns empty:

  • Close the Elgato Stream Deck software (it holds exclusive USB access)
  • On Linux, ensure udev rules are configured (see above)
  • On macOS, check USB entitlements in your app's codesign profile
  • Disconnect and reconnect the device
  • Verify the device appears in your OS device manager

Documentation

Refer to https://docs.deck.surf for tutorials and full API documentation.

License

This project is licensed under the MIT License. See LICENSE.md for details.

About

🌱 SDK to manage your Stream Deck from .NET code

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages