Inital commit adding plugin outline to control mac volume

This commit is contained in:
Daniel Prows 2023-08-09 10:12:38 -06:00
parent 8981e31787
commit 74a9482dbc
21 changed files with 365 additions and 0 deletions

15
src/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "${command:pickGoProcess}"
}
]
}

BIN
src/DistributionTool Executable file

Binary file not shown.

45
src/Makefile Normal file
View File

@ -0,0 +1,45 @@
INSTALLDIR = "~/Library/Application\ Support/com.elgato.StreamDeck/Plugins/com.dlprows.macvolumecontrol.sdPlugin"
#BUILDDIR = build
#RELEASEDIR = release
#SDPLUGINDIR = "./com.dlprows.macvolumecontrol.sdPlugin"
update:
killall Stream\ Deck.app || true
go build -o $(INSTALLDIR) .
open -a Elgato\ Stream\ Deck
build:
go build -o com.dlprows.macvolumecontrol.sdPlugin
rm com.dlprows.macvolumecontrol.streamDeckPlugin
./DistributionTool -b -i com.dlprows.macvolumecontrol.sdPlugin -o .
#.PHONY: test install build logs
#build:
#$(GO) build $(GOFLAGS)
#test:
#$(GO) run $(GOFLAGS) main.go -port 12345 -pluginUUID 213 -registerEvent test -info "{\"application\":{\"language\":\"en\",\"platform\":\"windows\",\"version\":\"10\"},\"plugin\":{\"version\":\"1.1\"},\"devicePixelRatio\":2,\"devices\":[{\"id\":\"55F16B35884A859CCE4FFA1FC8D3DE5B\",\"name\":\"Device Name\",\"size\":{\"columns\":5,\"rows\":3},\"type\":0},{\"id\":\"B8F04425B95855CF417199BCB97CD2BB\",\"name\":\"Another Device\",\"size\":{\"columns\":3,\"rows\":2},\"type\":1}]}"
#
#sdplugin: build
#rm -rf $(SDPLUGINDIR)
#mkdir -p $(SDPLUGINDIR)
#cp *.json $(SDPLUGINDIR)
#cp *.exe $(SDPLUGINDIR)
#cp *.html $(SDPLUGINDIR)
#cp -r images $(SDPLUGINDIR)
#
#install: uninstall sdplugin
#mv $(SDPLUGINDIR) $(INSTALLDIR)
#
#uninstall:
#rm -rf $(INSTALLDIR)
#
#logs:
#tail -f "$(TMP)"/$(MAKEFILEDIR).log*
#
#release: sdplugin
#rm -rf $(RELEASEDIR)
#mkdir $(RELEASEDIR)
#DistributionTool -b -i $(SDPLUGINDIR) -o $(RELEASEDIR)

41
src/README.md Normal file
View File

@ -0,0 +1,41 @@
# StreamDeck plugin skeleton
This is a skeleton project for getting started making StreamDeck plugins using the [Golang client library][1].
## Usage
Clone this repo and copy the directory to somewhere appropriate in your `GOPATH`. From there, most things are done
using the supplied Makefile.
Build:
make build
This outputs a build binary in to the project directory but doesn't do much else. To test out the plugin with your
StreamDeck, you need to do a local install.
Install:
make install
This builds an `.sdPlugin` directory under the `build` directory and then copies it over to the StreamDeck plugin
directory. **Note: you'll need to close the StreamDeck software before doing this. If you don't, you'll get a device
or resource busy error.**
View logs:
make logs
If you've enabled logging, logs will get dumped in to a temporary file. This make command tails all log files and needs to
be run after you've fired up the StreamDeck software, as the temporary file won't have been created before then.
Build for release:
make release
To distribute your StreamDeck plugin, you need to use Elgato's [DistributionTool][2]. Download it, put it somewhere on your
path, and then `make release` should work. If you have any problems in your `manifest.json`, this tool will ask you to fix
them before it outputs a release-ready plugin.
[1]: https://code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk
[2]: https://developer.elgato.com/documentation/stream-deck/sdk/exporting-your-plugin/

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,49 @@
{
"Actions": [
{
"UUID": "com.dlprows.macvolumecontrol.dialaction",
"Name": "Volume",
"Tooltip": "Control system volume",
"Icon": "Images/icon",
"States": [
{
"Image": "Images/pluginAction",
"TitleAlignment": "middle",
"FontSize": "12"
}
],
"Controllers": [ "Encoder" ],
"Encoder": {
"background": "backgroundImage",
"Icon": "Images/actionIcon",
"layout": "$B1",
"StackColor": "#AABBCC",
"TriggerDescription": {
"Rotate": "System Volume",
"Push": "Mute",
"Touch": "Mute"
}
},
"SupportedInMultiActions": false
}
],
"Author": "dlprows",
"Name": "MacVolumeControl",
"Description": "Control volume on mac",
"URL": "https://encyclopediaofdaniel.com",
"Version": "1.0",
"CodePath": "macvolumecontrol",
"Category": "Volume Control [dlprows]",
"Icon": "Images/pluginIcon",
"CategoryIcon": "Images/categoryIcon",
"OS": [
{
"Platform": "mac",
"MinimumVersion": "13.0"
}
],
"SDKVersion": 2,
"Software": {
"MinimumVersion": "6.0"
}
}

7
src/go.mod Normal file
View File

@ -0,0 +1,7 @@
module macvolumecontrol
go 1.20
require code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk v1.0.1
require github.com/gorilla/websocket v1.5.0 // indirect

6
src/go.sum Normal file
View File

@ -0,0 +1,6 @@
code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk v1.0.1 h1:2IoDJrWt6Bp5IcXTtqjdygDo3Ruh5VHsS9R5MjI2WWQ=
code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk v1.0.1/go.mod h1:SaMt3PIkPtNwkgrcLpD89GK/7tLaUkcEvOZaHUC66+4=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/samwho/streamdeck v0.0.0-20190725183037-2b866fdcb4a6 h1:ocHB+wIjLCdideequ8l8dUem6DxHf/A4Mmo0TRxBf1U=
github.com/samwho/streamdeck v0.0.0-20190725183037-2b866fdcb4a6/go.mod h1:OjeKL1Q8xRGm1zHGmtu9JeQUFNOXXXIcSZvCNzRP3GM=

43
src/keyboard/keyboard.go Normal file
View File

@ -0,0 +1,43 @@
package keyboard
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework AppKit
#import <AppKit/AppKit.h>
NSEvent* createEvent(int k, bool down) {
int flags = down ? 0xa00 : 0xb00;
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeSystemDefined
location:NSZeroPoint
modifierFlags:flags
timestamp:0
windowNumber:0
context:nil
subtype:8
data1:(k << 16 | flags)
data2:-1
];
return event;
}
void pressKey(int k) {
@autoreleasepool {
NSEvent* down = createEvent(k, true);
NSEvent* up = createEvent(k, false);
CGEventPost(kCGHIDEventTap, down.CGEvent);
usleep(1000);
CGEventPost(kCGHIDEventTap, up.CGEvent);
}
}
*/
import "C"
func PressMediaKey(key int) {
C.pressKey(C.int(key))
}
const (
VOLUMEUP = 0
VOLUMEDOWN = 1
MUTE = 7
)

31
src/logging/logging.go Normal file
View File

@ -0,0 +1,31 @@
package logging
import (
"fmt"
"log"
"os"
"path/filepath"
"code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk"
)
func directory() string {
ex, err := os.Executable()
if err != nil {
log.Fatalf("error getting directory: %v", err)
}
return filepath.Base(filepath.Dir(ex))
}
// Enable turns logging on for the StreamDeck API client as well as the global
// log object. It sends both to a temp file that contains <project_directory>.log.
func Enable() {
d := directory()
f, err := os.CreateTemp("./", fmt.Sprintf("%s.log", d))
if err != nil {
log.Fatalf("error creating temp file: %v", err)
}
streamdeck.Log().SetOutput(f)
log.SetPrefix(fmt.Sprintf("%s ", d))
log.SetOutput(f)
}

128
src/main.go Normal file
View File

@ -0,0 +1,128 @@
package main
import (
"context"
"encoding/json"
"log"
"os"
"time"
"macvolumecontrol/keyboard"
"macvolumecontrol/logging"
"code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk"
)
func main() {
logging.Enable()
log.Println("Starting plugin")
params, err := streamdeck.ParseRegistrationParams(os.Args)
if err != nil {
log.Fatalf("error parsing registration params: %v", err)
}
sd := streamdeck.NewClient(context.Background(), params)
defer sd.Close()
setup(sd)
if err := sd.Run(); err != nil {
log.Fatalf("error running streamdeck client: %v\n", err)
}
}
func setup(client *streamdeck.Client) {
log.Println("Registering actions")
action := client.Action("com.dlprows.macvolumecontrol.dialaction")
debounce := time.Now()
contexts := make(map[string]struct{})
action.RegisterHandler(streamdeck.DialRotate, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
log.Println("dial rotate")
t := time.Now()
elapsed := t.Sub(debounce)
if elapsed.Milliseconds() < 50 {
log.Println("dial rotate skipped")
return nil
}
debounce = t
p := streamdeck.DialRotatePayload[any]{}
if err := json.Unmarshal(event.Payload, &p); err != nil {
return err
}
if p.Ticks > 0 {
keyboard.PressMediaKey(keyboard.VOLUMEUP)
} else {
keyboard.PressMediaKey(keyboard.VOLUMEDOWN)
}
return nil
})
action.RegisterHandler(streamdeck.DialDown, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
log.Println("dial down")
keyboard.PressMediaKey(keyboard.MUTE)
return nil
})
action.RegisterHandler(streamdeck.WillAppear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
log.Println("Will Appear")
contexts[event.Context] = struct{}{}
return nil
})
action.RegisterHandler(streamdeck.WillDisappear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
log.Println("Will Disappear")
delete(contexts, event.Context)
return nil
})
//start background thread to keep the display up to date if changed outside the stream deck
//go func() {
//}()
}
/*
osascript -e 'output volume of (get volume settings)'
osascript -e 'set volume output volume 50'
osascript -e 'output muted of (get volume settings)'
osascript -e 'set volume output muted true'
*/
/*
for ctxStr := range contexts {
//for each context
//build a new context that can be used to perform outbound requests
ctx := context.Background()
ctx = sdcontext.WithContext(ctx, ctxStr)
img, err := streamdeck.Image(graph(readings))
if err != nil {
log.Printf("error creating image: %v\n", err)
continue
}
if err := client.SetImage(ctx, img, streamdeck.HardwareAndSoftware); err != nil {
log.Printf("error setting image: %v\n", err)
continue
}
title := ""
if pi.ShowText {
title = fmt.Sprintf("CPU\n%d%%", int(r[0]))
}
if err := client.SetTitle(ctx, title, streamdeck.HardwareAndSoftware); err != nil {
log.Printf("error setting title: %v\n", err)
continue
}
}
*/