mirror of
https://github.com/jahruz67/wisp-open.git
synced 2026-08-08 18:14:08 +00:00
- Add platform notes for tray startup, shortcut saving, and Linux-only settings - Handle stat error in processRecording (missing file) - Select correct binary name based on OS in updater - Increase tray icon canvas size from 64 to 128 pixels - Update frontend dist asset hash
232 lines
5.7 KiB
Go
232 lines
5.7 KiB
Go
//go:build linux
|
|
|
|
// ============================================================
|
|
// LINUX-ONLY FILE — Local HTTP daemon that receives hotkey
|
|
// press/release pings from the GNOME custom shortcut helper.
|
|
// This is the Linux equivalent of the Windows hotkey listener.
|
|
// Any changes here will NOT affect the Windows build.
|
|
// ============================================================
|
|
|
|
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"wis-free-v3/internal/logger"
|
|
)
|
|
|
|
const linuxPressAddr = "127.0.0.1:9876"
|
|
|
|
// linuxPressState holds the daemon's state, protected by a mutex.
|
|
// All field access must be done while holding the mutex.
|
|
type linuxPressState struct {
|
|
mu sync.Mutex
|
|
releaseTimer *time.Timer
|
|
detectTimer *time.Timer
|
|
recording bool
|
|
holdMode bool
|
|
detectingHold bool
|
|
cycle uint64
|
|
}
|
|
|
|
var pressState linuxPressState
|
|
|
|
const (
|
|
// linuxPressHoldDetectWindow is the window in which a second ping indicates
|
|
// the shortcut is being held (push-to-talk mode).
|
|
linuxPressHoldDetectWindow = 1200 * time.Millisecond
|
|
|
|
// linuxPressReleaseGrace is how long after the last ping to wait before
|
|
// stopping recording in hold mode.
|
|
linuxPressReleaseGrace = 450 * time.Millisecond
|
|
)
|
|
|
|
func init() {
|
|
if hasPressFlag(os.Args[1:]) {
|
|
if err := sendLinuxPressPing(); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func hasPressFlag(args []string) bool {
|
|
for _, arg := range args {
|
|
if arg == "--press" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func sendLinuxPressPing() error {
|
|
client := &http.Client{Timeout: 300 * time.Millisecond}
|
|
resp, err := client.Get("http://" + linuxPressAddr + "/press")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return os.ErrNotExist
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) startLinuxPressDaemon() {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/press", func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
logger.Error("Linux press handler recovered from panic: %v", recovered)
|
|
http.Error(w, "press handler failed", http.StatusInternalServerError)
|
|
}
|
|
}()
|
|
|
|
if r.Method != http.MethodGet && r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
a.handleLinuxPressPing()
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
|
|
go func() {
|
|
server := &http.Server{
|
|
Addr: linuxPressAddr,
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 2 * time.Second,
|
|
}
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logger.Error("Linux press daemon stopped: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (a *App) handleLinuxPressPing() {
|
|
pressState.mu.Lock()
|
|
defer pressState.mu.Unlock()
|
|
|
|
if !pressState.recording {
|
|
// First ping: start recording with hold detection
|
|
pressState.cycle++
|
|
cycle := pressState.cycle
|
|
pressState.recording = true
|
|
pressState.holdMode = false
|
|
pressState.detectingHold = true
|
|
go a.startLinuxPressRecording(cycle)
|
|
|
|
if pressState.detectTimer != nil {
|
|
pressState.detectTimer.Stop()
|
|
}
|
|
pressState.detectTimer = time.AfterFunc(linuxPressHoldDetectWindow, func() {
|
|
pressState.mu.Lock()
|
|
defer pressState.mu.Unlock()
|
|
if pressState.cycle == cycle {
|
|
pressState.detectingHold = false
|
|
}
|
|
})
|
|
return
|
|
}
|
|
|
|
// Already recording. If we're still in the hold-detect window or confirmed hold mode,
|
|
// this is a hold-mode keepalive ping (key auto-repeat).
|
|
if pressState.detectingHold || pressState.holdMode {
|
|
pressState.holdMode = true
|
|
pressState.detectingHold = false
|
|
if pressState.detectTimer != nil {
|
|
pressState.detectTimer.Stop()
|
|
pressState.detectTimer = nil
|
|
}
|
|
a.resetLinuxPressReleaseTimerLocked()
|
|
return
|
|
}
|
|
|
|
// Already recording but not in hold mode: this is a toggle-off ping (second press).
|
|
a.stopLinuxPressRecordingLocked()
|
|
}
|
|
|
|
func (a *App) resetLinuxPressReleaseTimerLocked() {
|
|
if pressState.releaseTimer != nil {
|
|
pressState.releaseTimer.Stop()
|
|
}
|
|
|
|
cycle := pressState.cycle
|
|
pressState.releaseTimer = time.AfterFunc(linuxPressReleaseGrace, func() {
|
|
pressState.mu.Lock()
|
|
defer pressState.mu.Unlock()
|
|
if pressState.cycle == cycle && pressState.recording && pressState.holdMode {
|
|
a.stopLinuxPressRecordingLocked()
|
|
}
|
|
})
|
|
}
|
|
|
|
func (a *App) stopLinuxPressRecordingLocked() {
|
|
// Caller must hold pressState.mu
|
|
wasRecording := pressState.recording
|
|
pressState.cycle++
|
|
if pressState.releaseTimer != nil {
|
|
pressState.releaseTimer.Stop()
|
|
pressState.releaseTimer = nil
|
|
}
|
|
if pressState.detectTimer != nil {
|
|
pressState.detectTimer.Stop()
|
|
pressState.detectTimer = nil
|
|
}
|
|
pressState.recording = false
|
|
pressState.holdMode = false
|
|
pressState.detectingHold = false
|
|
if wasRecording {
|
|
go a.stopLinuxPressRecording()
|
|
}
|
|
}
|
|
|
|
func (a *App) startLinuxPressRecording(cycle uint64) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
logger.Error("Linux press start recovered from panic: %v", recovered)
|
|
a.resetFailedLinuxPressCycle(cycle)
|
|
}
|
|
}()
|
|
|
|
a.StartRecording()
|
|
if atomic.LoadInt32(&a.recording) == 0 {
|
|
a.resetFailedLinuxPressCycle(cycle)
|
|
}
|
|
}
|
|
|
|
func (a *App) stopLinuxPressRecording() {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
logger.Error("Linux press stop recovered from panic: %v", recovered)
|
|
}
|
|
}()
|
|
|
|
a.StopRecording()
|
|
}
|
|
|
|
func (a *App) resetFailedLinuxPressCycle(cycle uint64) {
|
|
pressState.mu.Lock()
|
|
defer pressState.mu.Unlock()
|
|
|
|
if pressState.cycle != cycle {
|
|
return
|
|
}
|
|
pressState.cycle++
|
|
if pressState.releaseTimer != nil {
|
|
pressState.releaseTimer.Stop()
|
|
pressState.releaseTimer = nil
|
|
}
|
|
if pressState.detectTimer != nil {
|
|
pressState.detectTimer.Stop()
|
|
pressState.detectTimer = nil
|
|
}
|
|
pressState.recording = false
|
|
pressState.holdMode = false
|
|
pressState.detectingHold = false
|
|
}
|