diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ec3f945 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +# Export CGO_ENABLED=1 +CGO_ENABLED=1 +# Export C compiler +CC=gcc + +build: + go build -ldflags "-H windowsgui" -o bin/ \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f4de310 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module VolumeFix + +go 1.21 + +require ( + github.com/moutend/go-hook v0.1.0 + github.com/moutend/go-wca v0.3.0 +) + +require ( + github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect + github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect + github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect + github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect + github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect + github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect + github.com/getlantern/systray v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/micmonay/keybd_event v1.1.2 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + golang.org/x/sys v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ea2d001 --- /dev/null +++ b/go.sum @@ -0,0 +1,44 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= +github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= +github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= +github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= +github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk= +github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc= +github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0= +github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o= +github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc= +github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA= +github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA= +github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= +github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE= +github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/jwfergus/winsystray v0.0.0-20200612115431-12d8dc7aa265 h1:ImfStqG+JS2Uxdymr1LqZAVDFq1BZxQ1mybTM18hnGA= +github.com/jwfergus/winsystray v0.0.0-20200612115431-12d8dc7aa265/go.mod h1:rkjpnJveVtl+m/CY0U4hFgCf2FlG9UcEUOLsUAo0pxE= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/micmonay/keybd_event v1.1.2 h1:RpgvPJKOh4Jc+ZYe0OrVzGd2eNMCfuVg3dFTCsuSah4= +github.com/micmonay/keybd_event v1.1.2/go.mod h1:CGMWMDNgsfPljzrAWoybUOSKafQPZpv+rLigt2LzNGI= +github.com/moutend/go-hook v0.1.0 h1:8jGA7zxtcNmiFrHf+KAGpSBbU99fyY9DS1s38MOBJQU= +github.com/moutend/go-hook v0.1.0/go.mod h1:rGHmQESfHpsztJ6jbDoaiCgesGdZttObFlY/ksHIlY4= +github.com/moutend/go-wca v0.3.0 h1:IzhsQ44zBzMdT42xlBjiLSVya9cPYOoKx9E+yXVhFo8= +github.com/moutend/go-wca v0.3.0/go.mod h1:7VrPO512jnjFGJ6rr+zOoCfiYjOHRPNfbttJuxAurcw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0abb53c --- /dev/null +++ b/main.go @@ -0,0 +1,171 @@ +package main + +import ( + "fmt" + "github.com/getlantern/systray" + "github.com/getlantern/systray/example/icon" + "github.com/go-ole/go-ole" + "github.com/moutend/go-hook/pkg/keyboard" + "github.com/moutend/go-hook/pkg/types" + "github.com/moutend/go-wca/pkg/wca" + "math" + "os" + "os/signal" +) + +var ( + // DeviceEnumerator Persistent reference to the device enumerator object + DeviceEnumerator *wca.IMMDeviceEnumerator + // IMMDevice Persistent reference to the audio endpoint device + IMMDevice *wca.IMMDevice + // AudioEndpointVolume Persistent reference to the audio endpoint volume object + AudioEndpointVolume *wca.IAudioEndpointVolume + // Volume tracks volume level + Volume float32 +) + +func main() { + if err := run(); err != nil { + fmt.Println(err) + } +} + +func onReady() { + systray.SetIcon(icon.Data) + systray.SetTitle("Volume Fix") + systray.SetTooltip("Volume Fix") + quit := systray.AddMenuItem("Quit", "Quit the whole app") + go func() { + <-quit.ClickedCh + fmt.Println("Quitting") + systray.Quit() + }() +} + +func onExit() { + // clean up here + os.Exit(0) +} + +func run() error { + // Set up audio endpoint volume + err := setupEndpointVolume() + if err != nil { + return err + } + // Defer teardown of COM objects + defer teardown() + + keyboardChan := make(chan types.KeyboardEvent, 3) + if err := keyboard.Install(nil, keyboardChan); err != nil { + return err + } + defer func() { + err := keyboard.Uninstall() + if err != nil { + fmt.Println(err) + } + }() + + // Systray, run in a separate goroutine to avoid blocking + go func() { + systray.Run(onReady, onExit) + }() + + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + fmt.Println("start capturing keyboard input") + for { + select { + case <-signalChan: + fmt.Println("Received shutdown signal") + return nil + case key := <-keyboardChan: + switch key.VKCode { + case types.VK_VOLUME_UP: + // Stop key from being passed to the system + switch key.Message { + case types.WM_KEYDOWN: + fmt.Println("Volume up key pressed") + Volume = getVolume() + case types.WM_KEYUP: + fmt.Println("Volume up key released") + setVolume(Volume + 0.01) + } + case types.VK_VOLUME_DOWN: + switch key.Message { + case types.WM_KEYDOWN: + fmt.Println("Volume down key pressed") + Volume = getVolume() + case types.WM_KEYUP: + fmt.Println("Volume down key released") + setVolume(Volume - 0.01) + } + } + } + } +} + +func getVolume() float32 { + var volume float32 + err := AudioEndpointVolume.GetMasterVolumeLevelScalar(&volume) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Current volume is ", volume) + return volume +} + +func setVolume(volume float32) { + precision := float64(volume) + // Clamp volume between 0 and 1 + precision = math.Max(0, math.Min(precision, 1)) + // Round to 2 decimal places + precision = math.Round(precision*100) / 100 + volume = float32(precision) + + fmt.Println("Setting volume to ", volume) + err := AudioEndpointVolume.SetMasterVolumeLevelScalar(volume, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func setupEndpointVolume() error { + // Initialize COM library + if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil { + return err + } + // Get Device Enumerator + if err := wca.CoCreateInstance(wca.CLSID_MMDeviceEnumerator, 0, wca.CLSCTX_ALL, wca.IID_IMMDeviceEnumerator, &DeviceEnumerator); err != nil { + return err + } + // Get default audio endpoint from the device enumerator + if err := DeviceEnumerator.GetDefaultAudioEndpoint(wca.ERender, wca.EConsole, &IMMDevice); err != nil { + return err + } + // Activate the audio endpoint volume object + if err := IMMDevice.Activate(wca.IID_IAudioEndpointVolume, wca.CLSCTX_ALL, nil, &AudioEndpointVolume); err != nil { + return err + } + return nil +} + +func teardown() { + // Release the audio endpoint volume object + if AudioEndpointVolume != nil { + AudioEndpointVolume.Release() + } + // Release the audio endpoint device + if IMMDevice != nil { + IMMDevice.Release() + } + // Release the device enumerator + if DeviceEnumerator != nil { + DeviceEnumerator.Release() + } + // Un-initialize COM library + ole.CoUninitialize() +}