Skip to main content
Version: main

Plugin protocols

Tinct supports two plugin communication protocols with automatic detection.

Protocol comparison

FeatureJSON-stdioGo-Plugin (RPC)
LanguagesAny (shell, Python, Ruby, etc.)Go
CommunicationJSON over stdin/stdoutRPC over stdio
Process modelNew process per invocationPersistent process (reused)
Startup costHigh (fork+exec each time)Low (RPC to running process)
IsolationBasic process isolationEnhanced with crash recovery
Health checksNoneAutomatic monitoring
Error handlingstderr textStructured RPC errors
BidirectionalNoYes (plugin can call back)
StateStatelessCan maintain state
DependenciesNonehashicorp/go-plugin
ComplexityMinimalModerate

Automatic detection

Tinct automatically detects which protocol to use:

  1. Runs plugin --plugin-info
  2. Parses the plugin_protocol field
  3. Uses the appropriate executor

No configuration required.

JSON-stdio protocol

Best for simple plugins written in any language.

When to use

  • Writing plugins in shell scripts, Python, Ruby, or any language
  • Plugin is simple and short-lived
  • Maximum portability
  • Minimal dependencies

How it works

1. Plugin info query

$ my-plugin --plugin-info

Returns:

{
"name": "my-plugin",
"type": "output",
"version": "1.0.0",
"protocol_version": "0.0.1",
"description": "My plugin",
"plugin_protocol": "json-stdio"
}

2. Execution flow

tinct                          plugin
│ │
│──── spawn process ──────────▶│
│ │
│──── JSON via stdin ─────────▶│
│ │
│◀─── JSON via stdout ─────────│
│ │
│◀─── process exits ───────────│
│ │

Input plugin data format

Input plugins receive configuration and must return palette data:

Stdin (configuration):

{
"source": "/path/to/image.jpg",
"options": {
"colours": 16,
"algorithm": "kmeans"
}
}

Stdout (palette):

{
"colours": {
"background": {"hex": "#1e1e2e", "rgb": {"r": 30, "g": 30, "b": 46}},
"foreground": {"hex": "#cdd6f4", "rgb": {"r": 205, "g": 214, "b": 244}},
"accent1": {"hex": "#89b4fa", "rgb": {"r": 137, "g": 180, "b": 250}}
},
"theme_type": "dark",
"wallpaper_path": "/path/to/image.jpg"
}

Output plugin data format

Output plugins receive the full palette:

Stdin (palette):

{
"colours": {
"background": {
"role": "background",
"hex": "#1e1e2e",
"rgb": {"r": 30, "g": 30, "b": 46},
"rgba": {"r": 30, "g": 30, "b": 46, "a": 255},
"luminance": 0.027,
"is_light": false,
"hue": 240,
"saturation": 0.21
},
"foreground": {
"role": "foreground",
"hex": "#cdd6f4",
"rgb": {"r": 205, "g": 214, "b": 244},
"rgba": {"r": 205, "g": 214, "b": 244, "a": 255}
}
},
"theme_type": "dark",
"all_colours": [...],
"wallpaper_path": "/path/to/wallpaper.jpg"
}

Stdout (status):

{
"success": true,
"files_written": [
"/home/user/.config/myapp/colours.conf"
],
"message": "Generated configuration"
}

Error handling

Write errors to stderr:

echo "Error: config directory not found" >&2
exit 1

Go-plugin protocol

Best for performance-critical Go plugins.

When to use

  • Writing plugins in Go
  • Plugin does heavy computation
  • Need better error handling and crash recovery
  • Plugin maintains state between invocations
  • Want health monitoring

How it works

1. Plugin info query (same as JSON-stdio)

$ my-plugin --plugin-info

Returns:

{
"name": "my-plugin",
"type": "output",
"version": "1.0.0",
"protocol_version": "0.0.1",
"description": "My Go plugin",
"plugin_protocol": "go-plugin"
}

2. Execution flow

tinct                          plugin (persistent)
│ │
│──── spawn process ──────────▶│
│ │
│──── RPC: Generate() ────────▶│
│◀─── RPC response ────────────│
│ │
│──── RPC: Generate() ────────▶│ (reused)
│◀─── RPC response ────────────│
│ │
│──── RPC: Kill() ────────────▶│
│◀─── process exits ───────────│

Performance comparison

Protocol100 InvocationsAvg per Call
JSON-stdio~5.2s~52ms
Go-Plugin~0.8s~8ms

Go-Plugin is approximately 6x faster for repeated invocations due to process reuse.

RPC interface

Output plugins implement:

type OutputPlugin interface {
// Generate creates configuration files from palette data
Generate(ctx context.Context, palette PaletteData) (map[string][]byte, error)

// PreExecute validates environment (optional)
PreExecute(ctx context.Context) (skip bool, reason string, err error)

// PostExecute runs after file generation (optional)
PostExecute(ctx context.Context, files []string) error

// GetMetadata returns plugin information
GetMetadata() PluginInfo
}

Input plugins implement:

type InputPlugin interface {
// Extract generates a palette from the source
Extract(ctx context.Context, config InputConfig) (*PaletteData, error)

// GetMetadata returns plugin information
GetMetadata() PluginInfo

// WallpaperPath returns path to wallpaper (optional)
WallpaperPath() string
}

Handshake configuration

var Handshake = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "TINCT_PLUGIN",
MagicCookieValue: "tinct",
}

Wallpaper support

Input plugins can provide wallpaper images to output plugins.

JSON-stdio format

Return wallpaper_path in the response:

{
"colours": [...],
"wallpaper_path": "/path/to/wallpaper.png"
}

Go-plugin format

Implement the WallpaperPath() method:

func (p *MyInputPlugin) WallpaperPath() string {
return p.imagePath
}

Output plugins receive the wallpaper path in the palette data and can use it for applications like hyprpaper.

Protocol version

The current protocol version is 0.0.1. Plugins should declare this in their metadata:

{
"protocol_version": "0.0.1"
}

Tinct checks version compatibility and warns if a plugin uses an incompatible version.

See also