Skip to content

Commit

Permalink
Merge branch 'comm-actor'
Browse files Browse the repository at this point in the history
  • Loading branch information
emorydunn committed Oct 17, 2024
2 parents 3468db4 + ca73919 commit 0b4725e
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 111 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var package = Package(
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.4.3"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),

// Depend on the latest Swift 5.9 prerelease of SwiftSyntax
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,23 @@ OPTIONS:
-h, --help Show help information.
```
If you're building a universal binary there appears to be an issue with the macro which prevents compiling for multiple architectures at once. A workaround is to compile each separately and then combine with with `lipo`.

```shell
# Build each architecture separately and then combine
echo "Building ARM binary"
swift build -c release --arch arm64
echo "Building Intel binary"
swift build -c release --arch x86_64
echo "Creating universal binary"
lipo -create \
.build/arm64-apple-macosx/counter-plugin \
.build/x86_64-apple-macosx/counter-plugin \
-output counter-plugin
```

## Adding `StreamDeck` as a Dependency

To use the `StreamDeck` library in a SwiftPM project,
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamDeck/Command Line/PluginCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
import ArgumentParser

/// Manages a plugins lifecycle and handling launch from the Stream Deck application.
public struct PluginCommand: ParsableCommand {
public struct PluginCommand: AsyncParsableCommand {

/// The command's configuration.
public static var configuration = CommandConfiguration(
Expand Down
18 changes: 6 additions & 12 deletions Sources/StreamDeck/Command Line/StreamDeckCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import OSLog
fileprivate let log = Logger(subsystem: "StreamDeckPlugin", category: "StreamDeckCommand")

/// The command called by the Stream Deck application to run the plugin.
struct StreamDeckCommand: ParsableCommand {
struct StreamDeckCommand: AsyncParsableCommand {

static var configuration = CommandConfiguration(commandName: "register")

Expand All @@ -33,7 +33,7 @@ struct StreamDeckCommand: ParsableCommand {
public var info: String

/// Initialize an instance of the plugin with the properties provided by the command line.
public func run() throws {
public func run() async throws {
let pluginType = PluginCommand.plugin!
let pluginInfo = try PluginRegistrationInfo(string: info)

Expand All @@ -47,20 +47,14 @@ struct StreamDeckCommand: ParsableCommand {

// Create the plugin to handle communication
PluginCommunication.shared = PluginCommunication(port: port, uuid: uuid, event: event, info: pluginInfo)

// Begin monitoring the socket
Task.detached {
await PluginCommunication.shared.monitorSocket()
}

// Send the registration event
try PluginCommunication.shared.registerPlugin()
try await PluginCommunication.shared.registerPlugin(pluginType)

// Create the user's plugin
PluginCommunication.shared.plugin = pluginType.init()
log.log("Plugin started. Monitoring socket.")

log.log("Plugin started. Entering run loop.")
RunLoop.current.run()
// Begin monitoring the socket
await PluginCommunication.shared.monitorSocket()

}

Expand Down
111 changes: 71 additions & 40 deletions Sources/StreamDeck/StreamDeck Plugin/Action/Action+Sent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,52 @@ public extension Action {
/// - settings: A json object which is persistently saved for the action's instance.
@available(*, deprecated, message: "Use the Settings API.")
func setSettings(to settings: [String: Any]) {
PluginCommunication.shared.sendEvent(.setSettings,
context: context,
payload: settings)
Task {
await PluginCommunication.shared.sendEvent(.setSettings,
context: context,
payload: settings)
}
}

/// Save data persistently for the action's instance.
/// - Parameters:
/// - context: An opaque value identifying the instance's action or Property Inspector.
/// - settings: A json object which is persistently saved for the action's instance.
func setSettings(to settings: Settings) {
PluginCommunication.shared.sendEvent(.setSettings,
context: context,
payload: settings)
Task {
await PluginCommunication.shared.sendEvent(.setSettings,
context: context,
payload: settings)
}
}

/// Request the persistent data for the action's instance.
/// - context: An opaque value identifying the instance's action or Property Inspector.
func getSettings() {
PluginCommunication.shared.sendEvent(.getSettings,
context: context,
payload: nil)
Task {
await PluginCommunication.shared.sendEvent(.getSettings,
context: context,
payload: nil)
}
}

/// Open an URL in the default browser.
/// - Parameter url: The URL to open
func openURL(_ url: URL) {
PluginCommunication.shared.sendEvent(.openURL,
context: nil,
payload: ["url": url.path])
Task {
await PluginCommunication.shared.sendEvent(.openURL,
context: nil,
payload: ["url": url.path])
}
}

/// Write a debug log to the logs file.
/// - Parameter message: A string to write to the logs file.
func logMessage(_ message: String) {
PluginCommunication.shared.sendEvent(.logMessage, context: nil, payload: ["message": message])
log.log("\(message, privacy: .public)")
Task {
await PluginCommunication.shared.sendEvent(.logMessage, context: nil, payload: ["message": message])
}
}

/// Write a debug log to the logs file.
Expand Down Expand Up @@ -84,9 +95,11 @@ public extension Action {
payload["target"] = target?.rawValue
payload["state"] = state

PluginCommunication.shared.sendEvent(.setTitle,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setTitle,
context: context,
payload: payload)
}
}

/// Dynamically change the image displayed by an instance of an action.
Expand All @@ -105,9 +118,11 @@ public extension Action {
payload["target"] = target?.rawValue
payload["state"] = state

PluginCommunication.shared.sendEvent(.setImage,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setImage,
context: context,
payload: payload)
}
}

/// Dynamically change the image displayed by an instance of an action.
Expand Down Expand Up @@ -155,21 +170,27 @@ public extension Action {
payload["target"] = target?.rawValue
payload["state"] = state

PluginCommunication.shared.sendEvent(.setImage,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setImage,
context: context,
payload: payload)
}
}

/// Temporarily show an alert icon on the image displayed by an instance of an action.
/// - Parameter context: An opaque value identifying the instance's action or Property Inspector.
func showAlert() {
PluginCommunication.shared.sendEvent(.showAlert, context: context, payload: nil)
Task {
await PluginCommunication.shared.sendEvent(.showAlert, context: context, payload: nil)
}
}

/// Temporarily show an OK checkmark icon on the image displayed by an instance of an action.
/// - Parameter context: An opaque value identifying the instance's action or Property Inspector.
func showOk() {
PluginCommunication.shared.sendEvent(.showOK, context: context, payload: nil)
Task {
await PluginCommunication.shared.sendEvent(.showOK, context: context, payload: nil)
}
}

/// Change the state of the action's instance supporting multiple states.
Expand All @@ -179,9 +200,11 @@ public extension Action {
func setState(to state: Int) {
let payload: [String: Any] = ["state": state]

PluginCommunication.shared.sendEvent(.setState,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setState,
context: context,
payload: payload)
}
}

/// Send a payload to the Property Inspector.
Expand All @@ -190,18 +213,22 @@ public extension Action {
/// - action: The action unique identifier.
/// - payload: A json object that will be received by the Property Inspector.
func sendToPropertyInspector(payload: [String: Any]) {
PluginCommunication.shared.sendEvent(.sendToPropertyInspector,
action: uuid,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.sendToPropertyInspector,
action: uuid,
context: context,
payload: payload)
}
}


/// The plugin can send a `setFeedback` event to the Stream Deck application to dynamically change properties of items on the Stream Deck + touch display layout.
func setFeedback(_ payload: [String: Any]) {
PluginCommunication.shared.sendEvent(.setFeedback,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setFeedback,
context: context,
payload: payload)
}
}

/// The plugin can send a `setFeedbackLayout` event to the Stream Deck application to dynamically change the current Stream Deck + touch display layout.
Expand All @@ -211,9 +238,11 @@ public extension Action {
func setFeedbackLayout(_ layout: LayoutName) {
let payload: [String: Any] = ["layout": layout.id]

PluginCommunication.shared.sendEvent(.setFeedbackLayout,
context: context,
payload: payload)
Task {
await PluginCommunication.shared.sendEvent(.setFeedbackLayout,
context: context,
payload: payload)
}
}

/// Sets the trigger descriptions associated with an encoder (touch display + dial) action instance.
Expand All @@ -224,8 +253,10 @@ public extension Action {
/// To reset the descriptions to the default values defined within the manifest, an empty payload can be sent as part of the event.
/// - Parameter triggerDescription: The new `TriggerDescription` or `nil` to reset.
func setTriggerDescription(_ triggerDescription: TriggerDescription?) {
PluginCommunication.shared.sendEvent(.setTriggerDescription,
context: context,
payload: triggerDescription)
Task {
await PluginCommunication.shared.sendEvent(.setTriggerDescription,
context: context,
payload: triggerDescription)
}
}
}
17 changes: 10 additions & 7 deletions Sources/StreamDeck/StreamDeck Plugin/PluginCommunication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import OSLog
fileprivate let log = Logger(subsystem: "StreamDeckPlugin", category: "PluginCommunication")

/// An object that manages a plugins’s main event loop.
public final class PluginCommunication {
public final actor PluginCommunication {

/// The shared plugin.
public static var shared: PluginCommunication!
Expand Down Expand Up @@ -132,7 +132,7 @@ public final class PluginCommunication {
// want to kick that off and get back to receiving messages
// as soon as possible.
Task(priority: .userInitiated) {
parseMessage(message)
await parseMessage(message)
}

webSocketErrorCount = 0
Expand Down Expand Up @@ -179,7 +179,7 @@ public final class PluginCommunication {
}
}

func parseMessage(_ message: URLSessionWebSocketTask.Message) {
func parseMessage(_ message: URLSessionWebSocketTask.Message) async {
guard let data = readMessage(message) else {
log.warning("WebSocket sent empty message")
return
Expand All @@ -188,7 +188,7 @@ public final class PluginCommunication {
do {
// Decode the event from the data
let eventKey = try decoder.decode(ReceivableEvent.self, from: data)
try parseEvent(event: eventKey.event, context: eventKey.context, data: data)
try await parseEvent(event: eventKey.event, context: eventKey.context, data: data)
} catch {
// Pass the error onto the plugin
log.error("Failed to decode and parse the event received")
Expand Down Expand Up @@ -216,7 +216,7 @@ public final class PluginCommunication {
/// Complete the registration handshake with the server.
/// - Parameter properties: The properties provided by the Stream Deck application.
/// - Throws: Errors while encoding the data to JSON.
func registerPlugin() throws {
func registerPlugin(_ plugin: any Plugin.Type) throws {
let registrationEvent: [String: Any] = [
"event": event,
"uuid": uuid
Expand All @@ -230,6 +230,9 @@ public final class PluginCommunication {

log.log("Sending registration event")
send(data, eventType: event)

// Create the plugin instance
self.plugin = plugin.init()
// send(URLSessionWebSocketTask.Message.data(data)) { error in
// if let error = error {
// log.error("Failed to send \(self.event) event.\n\(error.localizedDescription)")
Expand Down Expand Up @@ -323,12 +326,12 @@ public final class PluginCommunication {
/// - Parameters:
/// - event: The event key.
/// - data: The JSON data.
func parseEvent(event: ReceivableEvent.EventKey, context: String?, data: Data) throws {
func parseEvent(event: ReceivableEvent.EventKey, context: String?, data: Data) async throws {

if shouldLoadSettings {
log.log("Received first event, requesting global settings")
// Get the initial global settings
PluginCommunication.shared.sendEvent(.getGlobalSettings,
await PluginCommunication.shared.sendEvent(.getGlobalSettings,
context: PluginCommunication.shared.uuid,
payload: nil)
shouldLoadSettings = false
Expand Down
Loading

0 comments on commit 0b4725e

Please sign in to comment.