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
|
||||
}
|
||||
}
|
||||
*/
|