diff --git a/spinclock/.gitignore b/spinclock/.gitignore new file mode 100644 index 0000000..08ac015 --- /dev/null +++ b/spinclock/.gitignore @@ -0,0 +1,3 @@ +au.id.hawkins.sd.spinclock.sdPlugin/spinclock +au.id.hawkins.sd.spinclock.sdPlugin/spinclock.exe + diff --git a/spinclock/README.md b/spinclock/README.md new file mode 100644 index 0000000..875e40b --- /dev/null +++ b/spinclock/README.md @@ -0,0 +1,33 @@ +# Example Stream Deck plugin - "spinclock" + +This plugin displays a minimalist clock - the number on the clock is the hour (24h time) +and the rotation indicates the minute. With a little practice it should become easy to +tell the time with some accuracy. + +Tapping on a clock changes its colour to a random colour. + +# Trying it out + +Check this code out somewhere. + +Symlink `the au.id.hawkins.sd.spinclock.sdPlugin` directory into your +plugin directory. See [Elgato's documentation](https://docs.elgato.com/sdk/plugins/getting-started#id-4.-add-the-plugin-to-stream-deck). + +Compile the code, using the `./build.sh` script (sorry Windows users, no `.bat` file, patches welcome). + +Restart the Stream Deck software, your plugin should now be available in the list on the right hand side. When you drag it +onto your profile, the plugin will start. + +Stdout/stderr logs are available, on Mac they are at: + + /Users//Library/Logs/ElgatoStreamDeck + +# Making changes + +After modifying the code, rebuild using the script, and simply kill the running process to make Stream Deck restart it for you: + + killall spinclock + +Note that if your plugin restarts too many times in short succession, +Stream Deck will disable it completely (see the logs above) - the +only way I know of to recover is to restart the Stream Deck software. diff --git a/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/manifest.json b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/manifest.json new file mode 100644 index 0000000..34d9891 --- /dev/null +++ b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/manifest.json @@ -0,0 +1,43 @@ +{ + "UUID": "au.id.hawkins.sd.spinclock", + "SDKVersion": 2, + "Author": "Justin Hawkins", + "CodePath": "spinclock", + "CodePathWin": "spinclock.exe", + "Description": "A spinning clock", + "Name": "SpinClock", + "Icon": "spinclock", + "DisableAutomaticStates": false, + "URL": "https://github.com/tardisx/streamdeck-plugin-examples/spinclock", + "Version": "1.0.0", + "OS": [ + { + "Platform": "mac", + "MinimumVersion": "10.11" + }, + { + "Platform": "windows", + "MinimumVersion": "10" + } + ], + "Software": { + "MinimumVersion": "6.5" + }, + "Category": "Clocks", + "CategoryIcon": "spinclock", + "Actions": [ + { + "Icon": "spinclock", + "Name": "SpinClock!", + "States": [ + { + "Image": "" + } + ], + "Controllers": ["Keypad"], + + "Tooltip": "A clock that tells time by rotation", + "UUID": "au.id.hawkins.sd.spinclock.clock" + } + ] +} diff --git a/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@1x.png b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@1x.png new file mode 100644 index 0000000..cc3dd2a Binary files /dev/null and b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@1x.png differ diff --git a/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@2x.png b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@2x.png new file mode 100644 index 0000000..6fee3a4 Binary files /dev/null and b/spinclock/au.id.hawkins.sd.spinclock.sdPlugin/spinclock@2x.png differ diff --git a/spinclock/build.sh b/spinclock/build.sh new file mode 100755 index 0000000..6743c5e --- /dev/null +++ b/spinclock/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +GOOS=darwin GOOARCH=arm64 go build -o au.id.hawkins.sd.spinclock.sdPlugin/spinclock . +GOOS=windows GOOARCH=amd64 go build -o au.id.hawkins.sd.spinclock.sdPlugin/spinclock.exe . diff --git a/spinclock/go.mod b/spinclock/go.mod new file mode 100644 index 0000000..d1e636f --- /dev/null +++ b/spinclock/go.mod @@ -0,0 +1,7 @@ +module spinclock + +go 1.22.1 + +require github.com/tardisx/streamdeck-plugin v0.0.7 + +require github.com/gorilla/websocket v1.5.3 // indirect diff --git a/spinclock/go.sum b/spinclock/go.sum new file mode 100644 index 0000000..2ba85dc --- /dev/null +++ b/spinclock/go.sum @@ -0,0 +1,6 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/tardisx/streamdeck-plugin v0.0.6 h1:vtaRHiKtcW09jMMIqcg0m+wBMqOq3Cl2PX3ktr7PTAM= +github.com/tardisx/streamdeck-plugin v0.0.6/go.mod h1:ysAJDI3Pi6zIbR3/jX7Pvduy6XGu68caZtSxn5t16V4= +github.com/tardisx/streamdeck-plugin v0.0.7 h1:YpII9PYlIXExe8+DPdffJxCM/2uftCpZGRpNq2rMiZw= +github.com/tardisx/streamdeck-plugin v0.0.7/go.mod h1:ysAJDI3Pi6zIbR3/jX7Pvduy6XGu68caZtSxn5t16V4= diff --git a/spinclock/main.go b/spinclock/main.go new file mode 100644 index 0000000..8bcffcd --- /dev/null +++ b/spinclock/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "fmt" + "log/slog" + "math/rand" + "sync" + "time" + + "github.com/tardisx/streamdeck-plugin" + "github.com/tardisx/streamdeck-plugin/events" + "github.com/tardisx/streamdeck-plugin/tools" +) + +// keep track of instances we've seen, each one has a different +// colour +type clocks struct { + contextColour map[string]string + lock sync.Mutex +} + +// template for making a clock image in SVG +const svgClock = ` + + + + %02d + +` + +func main() { + clocks := clocks{ + contextColour: map[string]string{}, + lock: sync.Mutex{}, + } + slog.Info("Starting up") + c := streamdeck.NewWithLogger(slog.Default()) + + slog.Info("Registering handlers") + c.RegisterHandler(func(e events.ERWillAppear) { + // clock appeared, give it a random colour + slog.Info("appearing " + e.Context) + clocks.lock.Lock() + defer clocks.lock.Unlock() + clocks.contextColour[e.Context] = randRGB() + }) + c.RegisterHandler(func(e events.ERWillDisappear) { + // Stop updating this clock by simply removing it from our struct. + // Note that this is not required, and in this case it means that + // when it gets re-instantiated it will get a new colour. + // But it is good practice to not spend CPU on updating things that + // are not currently being displayed. + slog.Info("disappearing " + e.Context) + clocks.lock.Lock() + defer clocks.lock.Unlock() + delete(clocks.contextColour, e.Context) + }) + c.RegisterHandler(func(e events.ERKeyDown) { + // button pressed, change its colour + slog.Info("keyDown " + e.Context) + clocks.lock.Lock() + defer clocks.lock.Unlock() + clocks.contextColour[e.Context] = randRGB() + drawClock(c, e.Context, clocks.contextColour[e.Context]) + }) + + slog.Info("Connecting web socket") + err := c.Connect() + if err != nil { + panic(err) + } + + // update all clocks, continuously + go func() { + for { + clocks.lock.Lock() + for context, colour := range clocks.contextColour { + drawClock(c, context, colour) + } + clocks.lock.Unlock() + time.Sleep(time.Second) + } + }() + + slog.Info("waiting for the end") + c.WaitForPluginExit() +} + +func drawClock(c streamdeck.Connection, context string, colour string) { + // rotation for this minute of the hour + rot := int(360.0 * (float64(time.Now().Minute()) / 60.0)) + // generate the SVG + svg := fmt.Sprintf(svgClock, colour, rot, time.Now().Hour()) + + // create the event + newImage := events.NewESSetImage( + context, + tools.SVGToPayload(svg), + events.EventTargetBoth, + nil) + + // send it + c.Send(newImage) +} + +// randRGB creates a random colour +func randRGB() string { + return fmt.Sprintf("#%02x%02x%02x", rand.Intn(256), rand.Intn(256), rand.Intn(256)) +}