Inital commit adding plugin outline to control mac volume
15
src/.vscode/launch.json
vendored
Normal 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
45
src/Makefile
Normal 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
@ -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/
|
BIN
src/com.dlprows.macvolumecontrol.sdPlugin/images/actionIcon.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
BIN
src/com.dlprows.macvolumecontrol.sdPlugin/images/icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/com.dlprows.macvolumecontrol.sdPlugin/images/icon@2x.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 8.3 KiB |
BIN
src/com.dlprows.macvolumecontrol.sdPlugin/images/pluginIcon.png
Normal file
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 36 KiB |
49
src/com.dlprows.macvolumecontrol.sdPlugin/manifest.json
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|