mirror of
https://github.com/jahruz67/wisp-open.git
synced 2026-08-08 18:14:08 +00:00
feat: implement audio recording, global hotkey management, and system tray integration
This commit is contained in:
@@ -40,15 +40,16 @@ func sendKey(key byte) {
|
||||
|
||||
// IsPlaying checks if media is currently playing using PowerShell SMTC query
|
||||
func IsPlaying() bool {
|
||||
// Write script to temp file
|
||||
// Write script to temp file only if it doesn't exist
|
||||
tempDir := os.TempDir()
|
||||
scriptPath := filepath.Join(tempDir, "wis_check_media.ps1")
|
||||
scriptPath := filepath.Join(tempDir, "wis_check_media_v2.ps1")
|
||||
|
||||
// Always write to ensure latest version
|
||||
err := os.WriteFile(scriptPath, checkMediaScript, 0644)
|
||||
if err != nil {
|
||||
logger.Error("Failed to write media check script: %v", err)
|
||||
return false
|
||||
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
||||
err = os.WriteFile(scriptPath, checkMediaScript, 0644)
|
||||
if err != nil {
|
||||
logger.Error("Failed to write media check script: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("powershell.exe",
|
||||
@@ -59,13 +60,13 @@ func IsPlaying() bool {
|
||||
"-File", scriptPath,
|
||||
)
|
||||
|
||||
// Hide window completely
|
||||
// Hide window completely to prevent any flickering console
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
CreationFlags: 0x08000000 | 0x00000200, // CREATE_NO_WINDOW | DETACHED_PROCESS
|
||||
}
|
||||
|
||||
err = cmd.Run()
|
||||
err := cmd.Run()
|
||||
|
||||
// Exit code 0 = not playing, Exit code 1 = playing
|
||||
if err == nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"math"
|
||||
"unsafe"
|
||||
"wis-free-v3/internal/logger"
|
||||
|
||||
"github.com/gen2brain/malgo"
|
||||
@@ -138,38 +139,50 @@ func (r *AudioRecorder) Start(filename string) error {
|
||||
return fmt.Errorf("failed to write WAV header: %w", err)
|
||||
}
|
||||
|
||||
// Configure audio callback
|
||||
callbacks := malgo.DeviceCallbacks{
|
||||
Data: r.onAudioData,
|
||||
}
|
||||
|
||||
// Use custom device if specified
|
||||
// Determine device ID pointer
|
||||
var targetDeviceID unsafe.Pointer
|
||||
if r.deviceID != nil {
|
||||
// Enumerate devices to find the matching pointer
|
||||
devices, err := r.ctx.Context.Devices(malgo.Capture)
|
||||
if err == nil {
|
||||
for _, info := range devices {
|
||||
if fmt.Sprintf("%v", info.ID) == *r.deviceID {
|
||||
r.deviceConfig.Capture.DeviceID = info.ID.Pointer()
|
||||
targetDeviceID = info.ID.Pointer()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.deviceConfig.Capture.DeviceID = nil
|
||||
}
|
||||
|
||||
// Initialize device
|
||||
device, err := malgo.InitDevice(r.ctx.Context, r.deviceConfig, callbacks)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return fmt.Errorf("failed to initialize audio device: %w", err)
|
||||
// Check if we need to re-initialize the device
|
||||
configChanged := false
|
||||
if r.deviceConfig.Capture.DeviceID != targetDeviceID {
|
||||
configChanged = true
|
||||
}
|
||||
|
||||
if r.device != nil && configChanged {
|
||||
logger.Info("Audio device config changed, re-initializing...")
|
||||
r.device.Uninit()
|
||||
r.device = nil
|
||||
}
|
||||
|
||||
if r.device == nil {
|
||||
r.deviceConfig.Capture.DeviceID = targetDeviceID
|
||||
callbacks := malgo.DeviceCallbacks{
|
||||
Data: r.onAudioData,
|
||||
}
|
||||
device, err := malgo.InitDevice(r.ctx.Context, r.deviceConfig, callbacks)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return fmt.Errorf("failed to initialize audio device: %w", err)
|
||||
}
|
||||
r.device = device
|
||||
logger.Info("Audio device initialized")
|
||||
}
|
||||
r.device = device
|
||||
|
||||
// Start capturing
|
||||
if err := device.Start(); err != nil {
|
||||
device.Uninit()
|
||||
if err := r.device.Start(); err != nil {
|
||||
r.device.Uninit()
|
||||
r.device = nil
|
||||
file.Close()
|
||||
return fmt.Errorf("failed to start audio capture: %w", err)
|
||||
}
|
||||
@@ -188,10 +201,14 @@ func (r *AudioRecorder) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop and cleanup device
|
||||
// Stop device capture without uninitializing hardware
|
||||
if r.device != nil {
|
||||
r.device.Uninit()
|
||||
r.device = nil
|
||||
if err := r.device.Stop(); err != nil {
|
||||
logger.Error("Failed to stop audio device: %v", err)
|
||||
// On failure, it might be safer to uninit
|
||||
r.device.Uninit()
|
||||
r.device = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize WAV file
|
||||
|
||||
@@ -148,11 +148,11 @@ func (l *Listener) handleKeyEvent(ev hook.Event, pressedKeys map[uint16]bool, is
|
||||
|
||||
if active && !*isRecording {
|
||||
logger.Info("Shortcut activated: starting recording")
|
||||
l.startCallback()
|
||||
go l.startCallback()
|
||||
*isRecording = true
|
||||
} else if !active && *isRecording {
|
||||
logger.Info("Shortcut released: stopping recording")
|
||||
l.stopCallback()
|
||||
go l.stopCallback()
|
||||
*isRecording = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,13 +68,13 @@ func handleMenuEvents(app App, settings, startupItem, exit *systray.MenuItem) {
|
||||
for {
|
||||
select {
|
||||
case <-settings.ClickedCh:
|
||||
app.ShowSettings()
|
||||
go app.ShowSettings()
|
||||
|
||||
case <-startupItem.ClickedCh:
|
||||
toggleStartup(startupItem)
|
||||
go toggleStartup(startupItem)
|
||||
|
||||
case <-exit.ClickedCh:
|
||||
handleExit(app)
|
||||
go handleExit(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user