Add system tray icon, file logging, and fix Windows hotkey conflict
- 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>
This commit is contained in:
+76
-1
@@ -2,7 +2,14 @@
|
||||
|
||||
package main
|
||||
|
||||
import "golang.design/x/hotkey"
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.design/x/hotkey"
|
||||
)
|
||||
|
||||
var modMap = map[string]hotkey.Modifier{
|
||||
"ctrl": hotkey.ModCtrl,
|
||||
@@ -11,3 +18,71 @@ var modMap = map[string]hotkey.Modifier{
|
||||
"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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user