Refactor config history management to enforce byte size limit and optimize item prepending; improve hotkey listener synchronization to prevent deadlocks; enhance transcriber error handling to avoid leaking sensitive information; address multiple race conditions and potential crashes in audio processing; implement logging improvements and fix resource leaks; add comprehensive issue tracking documentation.

This commit is contained in:
Your Name
2026-06-09 09:16:55 -07:00
parent 05c8fbc474
commit 52bb65f3a0
14 changed files with 410 additions and 85 deletions
+34 -20
View File
@@ -6,6 +6,7 @@ import (
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
@@ -86,12 +87,13 @@ type Overlay struct {
text string
isShowing bool
running bool
closed int32 // atomic: 1 = Close() has been called
mu sync.RWMutex
stopCh chan struct{}
bgBrush syscall.Handle
hFont syscall.Handle
volume float64 // Current raw volume (0.0 - 1.0)
smoothedVolume float64 // Moving average volume for smoother animations
volume uint64 // atomic: float64 stored via math.Float64bits
smoothedVolume uint64 // atomic: float64 stored via math.Float64bits
}
var globalOverlay *Overlay
@@ -131,17 +133,27 @@ func (o *Overlay) Hide() {
}
}
// SetVolume updates the current audio volume level
// SetVolume updates the current audio volume level using lock-free atomics
// so the audio callback thread never blocks on a mutex.
func (o *Overlay) SetVolume(level float64) {
o.mu.Lock()
o.volume = level
// Stronger smoothing: 10% new value, 90% old value to reduce jitter/flicker
o.smoothedVolume = (o.smoothedVolume * 0.9) + (level * 0.1)
o.mu.Unlock()
atomic.StoreUint64(&o.volume, math.Float64bits(level))
for {
oldBits := atomic.LoadUint64(&o.smoothedVolume)
old := math.Float64frombits(oldBits)
// Stronger smoothing: 10% new value, 90% old value to reduce jitter/flicker
smoothed := (old * 0.9) + (level * 0.1)
if atomic.CompareAndSwapUint64(&o.smoothedVolume, oldBits, math.Float64bits(smoothed)) {
break
}
}
}
// Close stops the overlay
// Close stops the overlay. Safe to call multiple times.
func (o *Overlay) Close() {
if !atomic.CompareAndSwapInt32(&o.closed, 0, 1) {
return // Already closed
}
if o.hwnd != 0 {
procPostMessage.Call(uintptr(o.hwnd), WM_CLOSE, 0, 0)
}
@@ -256,17 +268,18 @@ func (o *Overlay) run() {
procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
procDispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
} else {
o.mu.RLock()
isShowing := o.isShowing
o.mu.RUnlock()
o.mu.RLock()
isShowing := o.isShowing
text := o.text
o.mu.RUnlock()
if isShowing {
// Higher resolution sleep for smooth animation when visible
time.Sleep(2 * time.Millisecond)
} else {
// Greatly reduce wakeups to save CPU when hidden (app is idle 99% of the time)
time.Sleep(50 * time.Millisecond)
}
if isShowing && (strings.HasPrefix(text, "Recording") || strings.HasPrefix(text, "Transcribing")) {
// Higher resolution sleep only when animating
time.Sleep(2 * time.Millisecond)
} else {
// Reduce wakeups when hidden or showing static text (Ready, errors)
time.Sleep(50 * time.Millisecond)
}
}
}
}
@@ -297,8 +310,9 @@ func overlayWndProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uin
text = globalOverlay.text
bgBrush = globalOverlay.bgBrush
hFont = globalOverlay.hFont
volume = globalOverlay.smoothedVolume
globalOverlay.mu.RUnlock()
// Read volume atomically (written by audio thread without lock)
volume = math.Float64frombits(atomic.LoadUint64(&globalOverlay.smoothedVolume))
}
// 1. Clear memory DC with background
+21 -3
View File
@@ -1,16 +1,34 @@
//go:build windows
package windows
import "syscall"
import (
"errors"
"syscall"
)
// IsProcessRunning checks if a process with the given PID exists on Windows.
// Uses GetExitCodeProcess to distinguish "not running" from "running but
// access denied" (protected/system processes), avoiding false negatives
// that would allow duplicate instances to launch.
func IsProcessRunning(pid int) bool {
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
handle, err := syscall.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
// ACCESS_DENIED means the process is running but we can't query it.
// Treat that as "running" to avoid clobbering the lock file.
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
return true
}
return false
}
syscall.CloseHandle(handle)
return true
defer syscall.CloseHandle(handle)
// Still confirm the process hasn't exited by checking its exit code.
// STILL_ACTIVE (259) is the documented value for a live process.
var exitCode uint32
if err := syscall.GetExitCodeProcess(handle, &exitCode); err != nil {
return true // We got a handle, so the process exists.
}
return exitCode == 259
}