A library for creating Stream Deck plugins in Swift.

Overview

StreamDeck

A library for creating Stream Deck plugins in Swift.

Usage

Your plugin class should inherit from StreamDeckPlugin, which handles the WebSocket connection and events automatically. You can override event methods in order to act upon received actions.

import StreamDeck

class CounterPlugin: StreamDeckPlugin {

    var counter: Int = 0

    override func keyDown(action: String, context: String, device: String, payload: KeyEvent) {
        counter += 1
        setTitle(in: context, to: "\(counter)")
    }

}

In order to run your plugin it needs to be registered during startup. The PluginManager manages the lifecycle of your plugin. In your main.swift file add your plugin:

import Foundation
import StreamDeck

PluginManager.main(plugin: CounterPlugin.self)

This is all that should be in the file in order for the Stream Deck software to successfully launch the plugin.

Responding to Events

When events are received by your plugin they are parsed and the corresponding method is called. See the Events Received page for more details. In order for your plugin to receive the event you need to override the method.

  • Note: You don't need to call super when overriding, any internal responses to events are handled automatically.

Each method is called with the top-level properties along with an event specific payload. For instance, to the keyDown event provides a payload that includes the actions settings, coordinates, etc.

Sending Events

In addition to receiving events from the application your plugin can send events. Most of the commands require a context object to specify the instance on the Stream Deck.

Accessing Specific Action Instances

Responding to events is easy because the context is provided, however updating instances outside of a received event requires knowing the state of the Stream Deck.

To aid in this the StreamDeckPlugin has an InstanceManager which tracks willAppear and willDissapear events. The manager provides methods for looking up available instances in a few ways.

The most straight forward is by looking up the context token using .instance(for:). Usually you'll be looking up instances of a specific action or at specific coordinates.

To look up all instances of an action call .instances(with:) with the UUID from your manifest.json file. The ID you pass in will be automatically lowercased.

You can also look up the instance of an action by coordinates by calling .instance(at:).

The lookup methods return an ActionInstance, which provides the context, action ID, and coordinates of the instance.

Exporting Your Plugin

Your plugin executable ships with an automatic way to generate the plugin's manifest.json file in a type-safe manor, similar to SwiftPM's Package.swift.

let manifest = PluginManifest(
    name: "Counter",
    description: "Count things. On your Stream Deck!",
    category: "Counting Actions",
    author: "Emory Dunn",
    icon: "counter",
    version: "0.1",
    os: [
        .mac(minimumVersion: "10.15")
    ],
    software: .minimumVersion("4.1"),
    codePath: "counter-plugin",
    actions: [
        PluginAction(
            name: "Increment",
            uuid: "counter.increment",
            icon: "Icons/plus",
            tooltip: "Increment the count."),
        PluginAction(
            name: "Decrement",
            uuid: "counter.decrement",
            icon: "Icons/minus",
            tooltip: "Decrement the count.")
    ])

PluginManager.main(plugin: CounterPlugin.self, manifest: manifest)

Using the export command you can generate the manifest file and copy the actual executable to the Plugins directory:

counter-plugin export --copy-executable --generate-manifest

You can also specify the output directory, manifest name, executable name, or preview the manifest. Check export -h for all of the options.

Adding StreamDeck as a Dependency

To use the StreamDeck library in a SwiftPM project, add the following line to the dependencies in your Package.swift file:

.package(name: "StreamDeck", url: "https://github.com/emorydunn/StreamDeckPlugin.git", .branch("main"))

Finally, include "StreamDeck" as a dependency for your executable target:

let package = Package(
    // name, products, etc.
    platforms: [.macOS(.v10_15)],
    dependencies: [
        .package(name: "StreamDeck", url: "https://github.com/emorydunn/StreamDeckPlugin.git", .branch("main")),
        // other dependencies
    ],
    targets: [
        .target(name: "<command-line-tool>", dependencies: [
            "StreamDeck"
        ]),
        // other targets
    ]
)
Comments
  • Multi Support Not Working?

    Multi Support Not Working?

    Hey there!

    For a few months now, my plugin hasn't been able to use Multi-Action support. I dumbed this down to an error on my side, but after extreme debugging, I couldn't get it to work.

    I went ahead & forked the latest, current branch of the Counter plugin, & can't seem to get any logs or any of the actions to work with-in a Multi-Action.

    Could you take a look & let me know if I'm missing something, or if this is indeed a bug?

    Thanks!

    opened by SENTINELITE 5
  • Websocket crashes when sending data from the Property Inspector.

    Websocket crashes when sending data from the Property Inspector.

    Hey, Emory!

    Whenever I open the property inspector from within the StreamDeck interface, the backend plugin crashes, & automatically restarts. After the second opening of the PI, on the same key context, or even two separate contexts, the plugin will crash for ~2 minutes.

    I've monitored the console.app's logs for my app streamDeckWSTEST, & I can see the web socket keeps restarting & seeing 'Starting macOS plugin'. As a side note: only sometimes do I see Corpse allowed x of 5.

    I'm not really sure if it's something I'm doing or not. It seems to happen with even a minimal project sending to the PI. I've attached some files & crash logs. I'll also attach my current plugin repo, along with the .sdPlugin file, so you can dig a little deeper if need be.

    Again, thank you for this VERY helpful plugin!

    .ips log

    opened by SENTINELITE 4
  • Manifest isn't generating correctly.

    Manifest isn't generating correctly.

    Running packageName generate-manifest --output /Users/userName/manifest.json doesn't generate the correct output. It appears it's generating mostly hard-coded values? Actions also aren't generated, causing the plugin to not function. I've compared the intended output (from the example script & wiki file), with other Stream Deck plugins, & ended up diff-checking & creating my own manifest.json file. I ended up getting the example project working with this fix.

    Manifest_Comparison
    opened by SENTINELITE 3
  • Finish Implementing Received Events

    Finish Implementing Received Events

    Not all received events been handled.

    • [x] didReceiveSettings
    • [x] didReceiveGlobalSettings
    • [x] keyDown
    • [x] keyUp
    • [x] willAppear
    • [x] willDisappear
    • [x] titleParametersDidChange
    • [x] deviceDidConnect
    • [x] deviceDidDisconnect
    • [x] applicationDidLaunch
    • [x] applicationDidTerminate
    • [x] systemDidWakeUp
    • [x] propertyInspectorDidAppear
    • [x] propertyInspectorDidDisappear
    • [x] sendToPlugin
    opened by emorydunn 2
  • Setting an Image in the Bundle Doesn't Pass State

    Setting an Image in the Bundle Doesn't Pass State

    The method, Action.setImage(toImage:withExtension:subdirectory:target:state:) has parameters for target and state but these aren't used when calling the the final setImage(to:target:state:).

    Passing in nil for the image also results in unexpected behavior, with url(forResource:withExtension:subdirectory:) returning the first image found rather than resetting the icon like other methods.

    opened by emorydunn 0
  • Expand the Command Line Options

    Expand the Command Line Options

    Actively developing a plugin has lots of friction, one of the most common is generating the manifest and copying the executable into Application Support. This PR replaces the generate-manifest command with an export command that includes more defaults for where to copy files.

    opened by emorydunn 0
  • Explore Options for Incorporating Actions into the Event Stream

    Explore Options for Incorporating Actions into the Event Stream

    Currently actions are defined in the manifest, which is only used for generating JSON. The plugin itself has no knowledge of what actions it supports, simply calling methods for events with the ID sent from the Stream Deck app. For small plugins this is fine and can be easily handled with a switch, however once a plugin has a number of actions or actions that involve more complex logic for handling events this becomes messy with each method needing to switch over every action.

    If the ActionInstances were registered with the plugin itself then the event handler could also route events to specific actions automatically, only calling the generic event handler if no matching action was found (or if the action doesn't respond to an event).

    opened by emorydunn 0
  • Finish Implementing Sent Events

    Finish Implementing Sent Events

    Check that all events the plugin can send are implemented and work correctly.

    • [x] setSettings
    • [x] getSettings
    • [x] setGlobalSettings
    • [x] getGlobalSettings
    • [x] openUrl
    • [x] logMessage
    • [x] setTitle
    • [x] setImage
    • [x] showAlert
    • [x] showOk
    • [x] setState
    • [x] switchToProfile
    • [x] sendToPropertyInspector
    • [x] sendToPlugin
    opened by emorydunn 0
  • Consider Deprecating Optional Booleans

    Consider Deprecating Optional Booleans

    The plugin manifest makes extensive use of optional booleans, such that not including the value uses the default. This makes sense for hand-edited JSON, but for generated manifest it means almost every action attribute is option, which is a little odd in Swift.

    Instead these properties should be marked as non-optional with a default implementation providing the SDK default value. This will also simplify the user's code, as they won't need to have a bunch of unused properties.

    opened by emorydunn 0
  • Support Rotary Encoders on the StreamDeck+

    Support Rotary Encoders on the StreamDeck+

    ~The StreamDeck+ remains undocumented, but Elgato updated their sample plugins, so some of the manifest can be updated.~

    Documentation is up and this is the full list of changes:

    • [x] Add UserTitleEnabled property to the manifest.
    • [x] Add the device type kESDSDKDeviceType_StreamDeckPlus to detect Stream Deck + devices.
    • [x] Add Encoder to the manifest for Stream Deck + devices.
    • [x] Add TriggerDescription to the manifest for Stream Deck + devices.
    • [x] Add Layouts for Stream Deck + displays.
    • [x] Add setFeedback event for Stream Deck + displays.
    • [x] Add setFeedbackLayout event for Stream Deck + displays.
    • [x] Add touchTap event for Stream Deck + displays.
    • [x] Add dialPress event for Stream Deck + encoders.
    • [x] Add dialRotate event for Stream Deck + encoders.
    • [x] Update willAppear and willDisappear events to include the controller property.
    opened by emorydunn 0
  • Decoding Events Fails on Non-String Settings

    Decoding Events Fails on Non-String Settings

    Settings are hard-coded to decode as [String:String], however if the PI sends other (valid) JSON types back the event will fail to decode. For instance, checkboxes might be sent as {"someKey": true}.

    Settings should support any valid JSON. The simple fix is to deliver a Data blob to the Action to decode itself. A nicer solution would be to allow the Action to declare a Settings type that the plugin could then decode before forwarding to an Action.

    opened by emorydunn 0
  • The `device` Key is Technically Optional

    The `device` Key is Technically Optional

    In doing some tests on a laptop that hasn't had a Stream Deck connected I'm getting a decoding error:

    09:34:10.3280 Failed to decode data for event titleParametersDidChange
    09:34:10.3282 The data couldn’t be read because it is missing.
    09:34:10.3288 keyNotFound(CodingKeys(stringValue: "device", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"device\", intValue: nil) (\"device\").", underlyingError: nil))
    

    Admittedly this is kind of an unusual case.

    opened by emorydunn 0
Releases(0.2.0)
  • 0.2.0(Nov 15, 2022)

  • 0.1.0(Feb 19, 2022)

    This represents the original API model which is being deprecated in favor of the Action API.

    The new API is more object oriented and integrates the manifest directly into the plugin's model. The event handler will automatically route events rather than needing to switch on Action IDs manually. It's recommended to migrate to the new API as soon as it's released.

    There isn't a migration path for the original API, so if your plugin still uses it you should pin the package version here.

    Source code(tar.gz)
    Source code(zip)
Owner
Emory Dunn
Digital tech, software developer, and general maker in the lovely East Bay town of Emeryville, California.
Emory Dunn
iOS_UHF_Sample is a sample App to demonstrate how to use UHFSDK library.

iOS_UHF_Sample is a sample App to demonstrate how to use UHFSDK library.

GIGA-TMS 0 Dec 6, 2021
Unopinionated and flexible library for easily integrating Tumblr data into your iOS or OS X application.

Tumblr SDK for iOS An unopinionated and flexible library for easily integrating Tumblr data into your iOS or OS X application. The library uses ARC re

Tumblr 420 Dec 8, 2022
Client library for accessing Azure Storage on an iOS device

Azure Storage Client Library for iOS Overview This library is designed to help you build iOS applications that use Microsoft Azure Storage. At the mom

Microsoft Azure 81 Oct 15, 2022
RadioTimeKit - The Swift SDK for TuneIn RadioTimeKit is a Swift package to use the TuneIn API.

RadioTimeKit - The Swift SDK for TuneIn RadioTimeKit is a Swift package to use the TuneIn API. The goal for development was to have a Swift SDK to get

Frank Gregor 2 Jun 20, 2022
This is swift project example to connect VNPTSmartCA SDK using Swift Language.

Example source code to integrate with VNPTSmartCA iOS SDK To run the example project, clone repository, and run pod install Requirements Installation

null 1 Feb 14, 2022
Home-assistant-swift-sdk - Used to integrate the Home Assistant APIs with your Swift-based apps.

home-assistant-swift-sdk This open-source library allows you to interact with a Home Assistant instance in your Swift-based (e.g., iOS, macOS, etc.) a

Alexander Golden 0 Dec 31, 2021
Azure Functions in Swift! Purely in Swift!

Azure Functions for Swift ⚡️ Write Azure Functions in Swift. This framework supports the new Azure Functions Custom Handlers (starting from 0.6.0) in

Saleh Albuga 87 Jan 3, 2023
Official Appwrite Swift SDK 🦅🍎

Appwrite Swift SDK This SDK is compatible with Appwrite server version 0.11.x. For older versions, please check previous releases. This is the Swift S

Appwrite 27 Dec 25, 2022
Swift SDK for Blockfrost.io API

Swift5 API client for Blockfrost Swift 5 SDK for Blockfrost.io API. Installation • Usage • API Endpoints Installation Swift package manager dependenci

blockfrost.io 10 Dec 24, 2022
WalletConnect Swift SDK v2

Wallet Connect v.2 - Swift Swift implementation of WalletConnect v.2 protocol for native iOS applications. Requirements iOS 13 XCode 13 Swift 5 Usage

WalletConnect Labs 16 Mar 30, 2022
Swift framework for authenticating with the Spotify API

SpotifyLogin SpotifyLogin is a Swift 5 Framework for authenticating with the Spotify API. Usage of this framework is bound under the Developer Terms o

Spotify 344 Jan 4, 2023
⚡️ A fully-featured and blazing-fast Swift API client to interact with Algolia.

The perfect starting point to integrate Algolia within your Swift project Documentation • Community Forum • Stack Overflow • Report a bug • FAQ • Supp

Algolia 192 Dec 23, 2022
iOS Mobile Automation With Swift

ios-mobile-automation Since I could hardly find any resources on iOS automation

Ahmethan G. 0 Dec 19, 2021
Simple proxy in Swift for converting between HTTP and API Gateway Lambda payloads

SwiftLambdaProxy A simple proxy that can convert HTTP requests to Lambda API Gat

Jānis Kiršteins 1 Jan 3, 2022
JustTrivia - Trivia Application Created in Swift

JustTrivia - Trivia Application Created in Swift Usage Please use a small number

null 0 Dec 30, 2021
Task-Manager - Task Manager App With Swift

Task-Manager It's typical task manager where user can assign the importance, def

Andrey Buchevskiy 1 Jan 10, 2022
📲 The curated list of iOS Developer interview questions and answers, Swift & Objective-C

Awesome iOS interview questions and answers ?? Get started by picking interview's language and start preparing right now Install the app Prepare for t

Dasha Korneichuk 996 Dec 28, 2022
A very simple way to implement Backbone.js style custom event listeners and triggering in Swift for iOS development.

Swift Custom Events A very simple way to implement Backbone.js style custom event listeners and triggering in Swift for iOS development. This provides

Stephen Haney 98 Dec 25, 2022
MpesaSDK - Swift SDK for the M-Pesa API (Mozambique)

M-Pesa SDK Swift package for M-Pesa API (Mozambique) Ready Methods/APIs C2B B2B

Algy Ali 16 Jul 29, 2022