streamdeck_flutter 0.3.1 copy "streamdeck_flutter: ^0.3.1" to clipboard
streamdeck_flutter: ^0.3.1 copied to clipboard

PlatformmacOS

Build Elgato Stream Deck plugins with Flutter. Renders UI offscreen, slices frames into key-sized tiles, and pushes them via the Stream Deck WebSocket protocol.

streamdeck_flutter #

A Flutter framework for building Elgato Stream Deck plugins. Renders Flutter UI offscreen, slices frames into key-sized tiles and touchscreen strips, and pushes them as base64 PNG via the Stream Deck WebSocket protocol. Input events from the Stream Deck are injected back into Flutter's gesture system.

Platform: macOS only.

Requirements #

macOS App Sandbox must be disabled. The plugin connects to the Stream Deck app via a local WebSocket, which is blocked by the default sandbox. Disable it in your entitlements:

<key>com.apple.security.app-sandbox</key>
<false/>

Or add the network client entitlement:

<key>com.apple.security.network.client</key>
<true/>

Quick Start #

1. Add the dependency #

dependencies:
  streamdeck_flutter: ^0.3.0

2. Create your plugin #

import 'package:flutter/material.dart';
import 'package:streamdeck_flutter/streamdeck_flutter.dart';

const _binary = 'my_plugin';
const _uuid = 'com.example.my-plugin';

void main(List<String> args) {
  HeadlessBinding.ensureInitialized();
  runApp(MyPlugin(connection: ConnectionInfo.fromArgs(args)));
}

class MyPlugin extends StatelessWidget {
  const MyPlugin({super.key, required this.connection});

  final ConnectionInfo connection;

  @override
  Widget build(BuildContext context) {
    return StreamDeckPlugin(
      connection: connection,
      actionBuilder: (context) {
        final action = StreamDeck.actionOf(context)!;
        return switch (action.action) {
          '$_uuid.counter' => const CounterAction(),
          _ => const Placeholder(),
        };
      },
      manifest: Manifest(
        uuid: _uuid,
        name: 'My Plugin',
        author: 'Your Name',
        description: 'A Stream Deck plugin built with Flutter.',
        icon: 'assets/imgs/plugin-icon',
        version: '1.0.0.0',
        codePath: '$_binary.app/Contents/MacOS/$_binary',
        actions: [
          ManifestAction(
            id: 'counter',
            name: 'Counter',
            tooltip: 'A simple tap counter.',
            icon: 'assets/imgs/action-icon',
            states: const [ManifestState(image: 'assets/imgs/action-icon')],
          ),
        ],
        os: const [ManifestOS(platform: 'mac', minimumVersion: '10.15')],
        software: const ManifestSoftware(minimumVersion: '6.9'),
      ),
    );
  }
}

3. Create an action widget #

The actionBuilder receives a BuildContext with the action info available via StreamDeck.actionOf(context). Use StreamDeckAction to wrap your UI — it handles routing to Keypad or Dial based on the controller type.

class CounterAction extends StatefulWidget {
  const CounterAction({super.key});

  @override
  State<CounterAction> createState() => _CounterActionState();
}

class _CounterActionState extends State<CounterAction> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return StreamDeckAction(
      onKeyDown: () => setState(() => _count++),
      onDialRotate: (ticks) => setState(() => _count += ticks),
      onDialDown: () => setState(() => _count = 0),
      builder: (context) {
        final isEncoder = StreamDeck.actionOf(context)?.payload.controller == ActionType.encoder;
        return Container(
          color: Colors.black,
          child: Center(
            child: Text(
              isEncoder ? '$_count taps' : '$_count',
              style: TextStyle(color: Colors.white, fontSize: isEncoder ? 20 : 32),
            ),
          ),
        );
      },
    );
  }
}

4. Build and install #

flutter pub run streamdeck_flutter build -i com.example.my-plugin

Builds the app, bundles the plugin, links it, and restarts the Stream Deck app.

Action Types #

Keypad + Encoder (default) #

ManifestAction(
  id: 'my-action',
  name: 'My Action',
  states: const [ManifestState(image: 'assets/imgs/icon')],
  encoder: const ManifestEncoder(
    triggerDescription: ManifestTriggerDescription(
      rotate: 'Adjust value',
      push: 'Confirm',
      touch: 'Toggle',
    ),
  ),
)

Keypad only #

ManifestAction.keypad(
  id: 'key-action',
  name: 'Key Action',
  states: const [ManifestState(image: 'assets/imgs/icon')],
)

Encoder only #

ManifestAction.encoder(
  id: 'dial-action',
  name: 'Dial Action',
  states: const [ManifestState(image: 'assets/imgs/icon')],
)

StreamDeckAction Widget #

Wrap your action UI in StreamDeckAction. It provides a single builder that renders for both keypad and encoder — check StreamDeck.actionOf(context)?.payload.controller to differentiate:

StreamDeckAction(
  onKeyDown: () { /* key pressed */ },
  onKeyUp: () { /* key released */ },
  onDialRotate: (ticks) { /* dial rotated */ },
  onDialDown: () { /* dial pressed */ },
  onDialUp: () { /* dial released */ },
  onTouchTap: (position) { /* touchscreen tapped */ },
  onSettings: (json) { /* settings changed */ },
  builder: (context) => MyWidget(),
)

Settings #

Access action settings:

final action = StreamDeck.actionOf(context)!;
final color = action.payload.setting<String>('color') ?? '#FFFFFF';

Persist settings:

final client = StreamDeck.clientOf(context);
client.setSettings(settings: {'color': '#FF0000'});

CLI Commands #

# Build and install (profile mode)
flutter pub run streamdeck_flutter build -i com.example.my-plugin

# Build in release mode
flutter pub run streamdeck_flutter build -i com.example.my-plugin -m release

# Build and attach for development (debug mode + hot reload)
flutter pub run streamdeck_flutter run -i com.example.my-plugin

# Unlink before building (clean install)
flutter pub run streamdeck_flutter run -i com.example.my-plugin -u

Architecture #

Stream Deck App (WS Server)
    |  WebSocket JSON
Flutter macOS App (WS Client / Plugin)
    |-- StreamDeckPlugin      -> top-level widget, manifest, action routing
    |-- Client                -> connects, registers, sends setImage/setFeedback
    |-- Raster                -> captures UI via CaptureBoundary, change detection
    '-- StreamDeckAction      -> maps SD events to Flutter callbacks

The app runs headless (hidden macOS window). Full frames are captured per action context, and only changed tiles are sent (FNV hash comparison) to minimize WebSocket traffic. Always captures at 2.0x for Retina Stream Deck displays.

The manifest.json is generated automatically by the plugin on startup from the Manifest Dart object. Action UUIDs use short IDs (e.g. 'counter') that are auto-prefixed with the plugin UUID (e.g. com.example.my-plugin.counter).

Encoder Layout (canvas.json) #

The framework renders encoder touchscreen strips as pixel data using a canvas.json layout file bundled as a package asset. The manifest encoder block is generated automatically with the correct path. You do not need to call setFeedbackLayout at runtime — the Stream Deck app reads the layout from the manifest.

Assets (icons, layouts, UI files) are discovered automatically from the built app's AssetManifest.bin and symlinked into the plugin bundle. No manual asset copying is needed.

0
likes
150
points
284
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Build Elgato Stream Deck plugins with Flutter. Renders UI offscreen, slices frames into key-sized tiles, and pushes them via the Stream Deck WebSocket protocol.

Repository (GitHub)
View/report issues

Topics

#streamdeck #elgato #macos #plugin

License

BSD-3-Clause (license)

Dependencies

args, flutter, freezed_annotation, logging, rxdart, standard_message_codec, streamdeck_client

More

Packages that depend on streamdeck_flutter

Packages that implement streamdeck_flutter