75c113909d
- Adds fyne.io/systray with a programmatic monkey face icon and Quit menu item - Logs to discord-lobby-mute.log next to the exe so startup errors are visible without a console window - Windows now uses raw Win32 RegisterHotKey in a locked goroutine instead of golang.design/x/hotkey, avoiding a deadlock where systray and the hotkey library both try to own the main thread message loop - Build with: GOOS=windows GOARCH=amd64 go build -ldflags "-H windowsgui" -o discord-lobby-mute.exe . Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
1.7 KiB
Go
89 lines
1.7 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.design/x/hotkey"
|
|
)
|
|
|
|
var modMap = map[string]hotkey.Modifier{
|
|
"ctrl": hotkey.ModCtrl,
|
|
"shift": hotkey.ModShift,
|
|
"alt": hotkey.ModAlt,
|
|
"win": hotkey.ModWin,
|
|
"super": hotkey.ModWin,
|
|
}
|
|
|
|
var (
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
procRegisterHotKey = user32.NewProc("RegisterHotKey")
|
|
procGetMessageW = user32.NewProc("GetMessageW")
|
|
)
|
|
|
|
// winMsg mirrors the Win32 MSG struct.
|
|
type winMsg struct {
|
|
hwnd uintptr
|
|
message uint32
|
|
wParam uintptr
|
|
lParam uintptr
|
|
time uint32
|
|
ptX int32
|
|
ptY int32
|
|
}
|
|
|
|
func startHotkey(keybind string) (<-chan struct{}, error) {
|
|
mods, key, err := parseKeybind(keybind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// hotkey.Modifier and hotkey.Key values on Windows are the native Win32
|
|
// MOD_* and VK_* constants, so they can be passed directly to RegisterHotKey.
|
|
var modFlags uintptr
|
|
for _, m := range mods {
|
|
modFlags |= uintptr(m)
|
|
}
|
|
vk := uintptr(key)
|
|
|
|
ch := make(chan struct{}, 1)
|
|
ready := make(chan error, 1)
|
|
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
// Intentionally no defer UnlockOSThread — this goroutine owns its OS
|
|
// thread for the lifetime of the app to keep the hotkey message queue alive.
|
|
|
|
ret, _, e := procRegisterHotKey.Call(0, 1, modFlags, vk)
|
|
if ret == 0 {
|
|
ready <- fmt.Errorf("RegisterHotKey: %w", e)
|
|
return
|
|
}
|
|
close(ready)
|
|
|
|
const wmHotkey = 0x0312
|
|
var msg winMsg
|
|
for {
|
|
r, _, _ := procGetMessageW.Call(
|
|
uintptr(unsafe.Pointer(&msg)), 0, wmHotkey, wmHotkey,
|
|
)
|
|
if r == 0 || r == ^uintptr(0) { // WM_QUIT or error
|
|
return
|
|
}
|
|
select {
|
|
case ch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
if err := <-ready; err != nil {
|
|
return nil, err
|
|
}
|
|
return ch, nil
|
|
}
|