Initial commit
@ -1,6 +1,2 @@
|
||||
# Stream Deck Go Plugin
|
||||
# Mac HTTP Proxy Control
|
||||
|
||||
|
||||
find everywhere with {{{NAME}}} and replace it with appropriate values
|
||||
do the manifest
|
||||
write code
|
||||
|
10
src/Makefile
@ -1,7 +1,7 @@
|
||||
INSTALLDIR = ~/Library/Application\ Support/com.elgato.StreamDeck/Plugins/com.dlprows.{{{NAME}}}.sdPlugin
|
||||
INSTALLDIR = ~/Library/Application\ Support/com.elgato.StreamDeck/Plugins/com.dlprows.httpproxy.sdPlugin
|
||||
#BUILDDIR = build
|
||||
#RELEASEDIR = release
|
||||
#SDPLUGINDIR = "./com.dlprows.{{{NAME}}}.sdPlugin"
|
||||
#SDPLUGINDIR = "./com.dlprows.httpproxycontrol.sdPlugin"
|
||||
|
||||
update:
|
||||
killall Stream\ Deck || true
|
||||
@ -9,9 +9,9 @@ update:
|
||||
open -a Elgato\ Stream\ Deck
|
||||
|
||||
build:
|
||||
go build -o com.dlprows.{{{NAME}}}.sdPlugin
|
||||
rm com.dlprows.{{{NAME}}}.streamDeckPlugin
|
||||
./DistributionTool -b -i com.dlprows.{{{NAME}}}.sdPlugin -o .
|
||||
go build -o com.dlprows.httpproxy.sdPlugin
|
||||
rm com.dlprows.httpproxy.streamDeckPlugin || true
|
||||
./DistributionTool -b -i com.dlprows.httpproxy.sdPlugin -o .
|
||||
|
||||
|
||||
#.PHONY: test install build logs
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
53
src/com.dlprows.httpproxy.sdPlugin/manifest.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"Actions": [
|
||||
{
|
||||
"UUID": "com.dlprows.httpproxy.action",
|
||||
"Name": "System Proxy",
|
||||
"Tooltip": "Turn on/off system proxy",
|
||||
"Icon": "Images/icon",
|
||||
"States": [
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": "12"
|
||||
},
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": "12"
|
||||
},
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": "12"
|
||||
},
|
||||
{
|
||||
"Image": "Images/pluginAction",
|
||||
"TitleAlignment": "middle",
|
||||
"FontSize": "12"
|
||||
}
|
||||
],
|
||||
"Controllers": [ "Keypad" ],
|
||||
"SupportedInMultiActions": false
|
||||
}
|
||||
],
|
||||
"Author": "dlprows",
|
||||
"Name": "Http Proxy Control",
|
||||
"Description": "Turn on and off HTTP(S) proxy settings in mac os",
|
||||
"URL": "https://encyclopediaofdaniel.com",
|
||||
"Version": "1.0",
|
||||
"CodePath": "httpproxycontrol",
|
||||
"Category": "Networking [dlprows]",
|
||||
"Icon": "Images/pluginIcon",
|
||||
"CategoryIcon": "Images/categoryIcon",
|
||||
"OS": [
|
||||
{
|
||||
"Platform": "mac",
|
||||
"MinimumVersion": "13.0"
|
||||
}
|
||||
],
|
||||
"SDKVersion": 2,
|
||||
"Software": {
|
||||
"MinimumVersion": "6.0"
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
{
|
||||
"Actions": [
|
||||
{
|
||||
"UUID": "com.dlprows.{{{NAME}}}.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": "{{{NAME}}}",
|
||||
"Description": "Control volume on mac",
|
||||
"URL": "https://encyclopediaofdaniel.com",
|
||||
"Version": "1.0",
|
||||
"CodePath": "{{{NAME}}}",
|
||||
"Category": "Volume Control [dlprows]",
|
||||
"Icon": "Images/pluginIcon",
|
||||
"CategoryIcon": "Images/categoryIcon",
|
||||
"OS": [
|
||||
{
|
||||
"Platform": "mac",
|
||||
"MinimumVersion": "13.0"
|
||||
}
|
||||
],
|
||||
"SDKVersion": 2,
|
||||
"Software": {
|
||||
"MinimumVersion": "6.0"
|
||||
}
|
||||
}
|
10
src/go.mod
@ -1,7 +1,9 @@
|
||||
module macvolumecontrol
|
||||
module httpproxycontrol
|
||||
|
||||
go 1.20
|
||||
|
||||
require code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk v1.0.1
|
||||
|
||||
require github.com/gorilla/websocket v1.5.0 // indirect
|
||||
require (
|
||||
code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk v1.0.1 // indirect
|
||||
github.com/b4b4r07/go-pipe v0.0.0-20191010045404-84b446f57366 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
)
|
||||
|
@ -1,6 +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/b4b4r07/go-pipe v0.0.0-20191010045404-84b446f57366 h1:FVAkDbBxovi2ID0vaxY7vCGvdKASt9r0TGPM1u4YQyg=
|
||||
github.com/b4b4r07/go-pipe v0.0.0-20191010045404-84b446f57366/go.mod h1:1ymsiQNa3qebVEEVtuIdhtAXRfjO4qFCFq1bBUOT2HE=
|
||||
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=
|
||||
|
144
src/main.go
@ -2,21 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"macvolumecontrol/logging"
|
||||
"macvolumecontrol/volume"
|
||||
"httpproxycontrol/logging"
|
||||
"httpproxycontrol/networking"
|
||||
|
||||
"code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk"
|
||||
sdcontext "code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk/context"
|
||||
)
|
||||
|
||||
var _currentSettings *volume.VolumeSettings = &volume.VolumeSettings{}
|
||||
|
||||
func main() {
|
||||
logging.Enable()
|
||||
log.Println("Starting plugin")
|
||||
@ -38,64 +34,26 @@ func main() {
|
||||
|
||||
func setup(client *streamdeck.Client) {
|
||||
log.Println("Registering actions")
|
||||
action := client.Action("com.dlprows.macvolumecontrol.dialaction")
|
||||
action := client.Action("com.dlprows.httpproxy.action")
|
||||
|
||||
contexts := make(map[string]struct{})
|
||||
|
||||
action.RegisterHandler(streamdeck.DialRotate, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
|
||||
log.Println("dial rotate")
|
||||
action.RegisterHandler(streamdeck.KeyDown, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
|
||||
log.Println("Key Down")
|
||||
|
||||
p := streamdeck.DialRotatePayload[any]{}
|
||||
toggleVpn()
|
||||
|
||||
if err := json.Unmarshal(event.Payload, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
client.SetState(ctx, getState())
|
||||
|
||||
//volume.ChangeVolumeWithKeyboard(p.Ticks)
|
||||
newSettings, err := volume.ChangeVolume(p.Ticks)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setFeedbackIfNeeded(ctx, client, newSettings)
|
||||
})
|
||||
|
||||
action.RegisterHandler(streamdeck.DialDown, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
|
||||
log.Println("dial down")
|
||||
|
||||
newSettings, err := volume.ToggleMute()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setFeedbackIfNeeded(ctx, client, newSettings)
|
||||
})
|
||||
|
||||
action.RegisterHandler(streamdeck.TouchTap, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
|
||||
log.Println("touch tap")
|
||||
|
||||
newSettings, err := volume.ToggleMute()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setFeedbackIfNeeded(ctx, client, newSettings)
|
||||
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{}{}
|
||||
|
||||
newSettings, err := volume.GetVolumeSettings()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setFeedback(ctx, client, newSettings)
|
||||
client.SetState(ctx, getState())
|
||||
return nil
|
||||
})
|
||||
|
||||
action.RegisterHandler(streamdeck.WillDisappear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
|
||||
@ -106,12 +64,7 @@ func setup(client *streamdeck.Client) {
|
||||
|
||||
//start background thread to keep the display up to date if changed outside the stream deck
|
||||
go func() {
|
||||
for range time.Tick(time.Second * 1) {
|
||||
newSettings, err := volume.GetVolumeSettings()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for range time.Tick(time.Second * 10) {
|
||||
|
||||
for ctxStr := range contexts {
|
||||
//for each context
|
||||
@ -119,55 +72,60 @@ func setup(client *streamdeck.Client) {
|
||||
ctx := context.Background()
|
||||
ctx = sdcontext.WithContext(ctx, ctxStr)
|
||||
|
||||
setFeedback(ctx, client, newSettings)
|
||||
client.SetState(ctx, getState())
|
||||
}
|
||||
_currentSettings = newSettings
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func setFeedback(ctx context.Context, client *streamdeck.Client, newSettings *volume.VolumeSettings) error {
|
||||
func toggleVpn() error {
|
||||
/*
|
||||
if on, turn off
|
||||
if off, turn on
|
||||
if broken, turn off
|
||||
*/
|
||||
activeDevice, err := networking.GetActiveDevice()
|
||||
|
||||
payload := FeedbackPayload{}
|
||||
|
||||
opacity := 1.0
|
||||
|
||||
if newSettings.OutputMuted {
|
||||
opacity = 0.5
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload.Value = ValueWithOpacity[string]{
|
||||
fmt.Sprintf("%d%%", newSettings.OutputVolume),
|
||||
opacity,
|
||||
enabled, err := networking.IsProxyEnabled(activeDevice)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload.Indicator = ValueWithOpacity[int]{
|
||||
newSettings.OutputVolume,
|
||||
opacity,
|
||||
}
|
||||
networking.SetProxyServer(activeDevice, "192.168.41.101", "8888")
|
||||
networking.SetProxyState(activeDevice, !enabled)
|
||||
|
||||
payload.Icon = ValueWithOpacity[any]{nil, opacity}
|
||||
|
||||
return client.SetFeedback(ctx, payload)
|
||||
}
|
||||
|
||||
func setFeedbackIfNeeded(ctx context.Context, client *streamdeck.Client, newSettings *volume.VolumeSettings) error {
|
||||
|
||||
if _currentSettings.OutputVolume == newSettings.OutputVolume && _currentSettings.OutputMuted == newSettings.OutputMuted {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getState() int {
|
||||
/*
|
||||
0 for no network
|
||||
1 for proxy off
|
||||
2 for proxy on
|
||||
3 for busted
|
||||
*/
|
||||
activeDevice, err := networking.GetActiveDevice()
|
||||
|
||||
if err != nil {
|
||||
return 3
|
||||
}
|
||||
|
||||
_currentSettings = newSettings
|
||||
return setFeedback(ctx, client, newSettings)
|
||||
}
|
||||
if activeDevice == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
type FeedbackPayload struct {
|
||||
Value ValueWithOpacity[string] `json:"value"`
|
||||
Indicator ValueWithOpacity[int] `json:"indicator"`
|
||||
Icon ValueWithOpacity[any] `json:"icon"`
|
||||
}
|
||||
proxyEnabled, err := networking.IsProxyEnabled(activeDevice)
|
||||
|
||||
type ValueWithOpacity[T any] struct {
|
||||
Value T `json:"value,omitempty"`
|
||||
Opacity float64 `json:"opacity"`
|
||||
if err != nil {
|
||||
return 3
|
||||
}
|
||||
if proxyEnabled {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
108
src/networking/networking.go
Normal file
@ -0,0 +1,108 @@
|
||||
package networking
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetActiveDevice() (string, error) {
|
||||
|
||||
output, err := exec.Command("networksetup", "-listnetworkserviceorder").Output()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSuffix(string(output), "\n"), "\n") {
|
||||
|
||||
if !strings.Contains(line, "Device:") {
|
||||
continue
|
||||
}
|
||||
|
||||
//(Hardware Port: Thunderbolt Ethernet Slot 2, Device: en8)
|
||||
split := strings.Split(line, ",")
|
||||
name := strings.Split(split[0], ":")[1]
|
||||
name = strings.TrimPrefix(name, " ")
|
||||
|
||||
device := strings.Split(split[1], ":")[1]
|
||||
device = device[1 : len(device)-1]
|
||||
|
||||
active := isDeviceActive(device)
|
||||
|
||||
if active {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func isDeviceActive(device string) bool {
|
||||
|
||||
output, _ := exec.Command("ifconfig", device).Output()
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSuffix(string(output), "\n"), "\n") {
|
||||
|
||||
if strings.Contains(line, "status: active") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsProxyEnabled(deviceName string) (bool, error) {
|
||||
output, err := exec.Command("networksetup", "-getwebproxy", deviceName).Output()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
webEnabled := strings.Contains(string(output), "Enabled: Yes\n")
|
||||
|
||||
output, err = exec.Command("networksetup", "-getsecurewebproxy", deviceName).Output()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
secureEnabled := strings.Contains(string(output), "Enabled: Yes\n")
|
||||
|
||||
//todo: if they're not equal, its some sort of error case
|
||||
return webEnabled && secureEnabled, nil
|
||||
}
|
||||
|
||||
func SetProxyState(deviceName string, on bool) error {
|
||||
onOff := "on"
|
||||
if !on {
|
||||
onOff = "off"
|
||||
}
|
||||
|
||||
err := exec.Command("networksetup", "-setwebproxystate", deviceName, onOff).Run()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = exec.Command("networksetup", "-setsecurewebproxystate", deviceName, onOff).Run()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetProxyServer(deviceName string, host string, port string) error {
|
||||
err := exec.Command("networksetup", "-setwebproxy", deviceName, host, port).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = exec.Command("networksetup", "-setsecurewebproxy", deviceName, host, port).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|