mirror of
https://github.com/jahruz67/wisp-open.git
synced 2026-08-08 18:14:08 +00:00
refactor: implement cross-platform abstraction for process management, media control, and startup configuration (TESTING)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
//go:build linux
|
||||
package linux
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"wis-free-v3/internal/logger"
|
||||
)
|
||||
|
||||
// IsPlaying checks if media is currently playing using playerctl
|
||||
func IsPlaying() bool {
|
||||
cmd := exec.Command("playerctl", "status")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return string(out) == "Playing\n"
|
||||
}
|
||||
|
||||
// TogglePlayPause sends the play-pause command via playerctl
|
||||
func TogglePlayPause() {
|
||||
cmd := exec.Command("playerctl", "play-pause")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logger.Error("Failed to toggle media on Linux: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// PauseMedia checks if playing, pauses if so, returns whether we paused
|
||||
func PauseMedia() bool {
|
||||
wasPlaying := IsPlaying()
|
||||
if wasPlaying {
|
||||
TogglePlayPause()
|
||||
}
|
||||
return wasPlaying
|
||||
}
|
||||
|
||||
// ResumeMedia resumes only if we paused it
|
||||
func ResumeMedia(wasPaused bool) {
|
||||
if wasPaused {
|
||||
TogglePlayPause()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build linux
|
||||
package linux
|
||||
|
||||
// linuxOverlay provides a simple dummy implementation for Linux.
|
||||
// It relies on tray icon status updates for visual feedback instead of raw drawing.
|
||||
type linuxOverlay struct{}
|
||||
|
||||
func NewOverlay() *linuxOverlay {
|
||||
return &linuxOverlay{}
|
||||
}
|
||||
|
||||
func (o *linuxOverlay) Show(message string) {}
|
||||
func (o *linuxOverlay) Hide() {}
|
||||
func (o *linuxOverlay) SetVolume(level float64) {}
|
||||
func (o *linuxOverlay) Close() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build linux
|
||||
package linux
|
||||
|
||||
import "syscall"
|
||||
|
||||
// IsProcessRunning checks if a process with the given PID exists on Linux.
|
||||
func IsProcessRunning(pid int) bool {
|
||||
// sending signal 0 checks if the process exists and we have permission
|
||||
err := syscall.Kill(pid, 0)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//go:build linux
|
||||
package linux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "wis-free-v3"
|
||||
desktopFileContent = `[Desktop Entry]
|
||||
Type=Application
|
||||
Name=WIS Free V3
|
||||
Exec="%s"
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
X-GNOME-Autostart-enabled=true
|
||||
`
|
||||
)
|
||||
|
||||
func getAutostartPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
configDir := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configDir == "" {
|
||||
configDir = filepath.Join(home, ".config")
|
||||
}
|
||||
autostartDir := filepath.Join(configDir, "autostart")
|
||||
|
||||
if err := os.MkdirAll(autostartDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(autostartDir, appName+".desktop"), nil
|
||||
}
|
||||
|
||||
func getExecutablePath() (string, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Abs(exe)
|
||||
}
|
||||
|
||||
func AddToStartup() error {
|
||||
autostartPath, err := getAutostartPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine autostart path: %w", err)
|
||||
}
|
||||
|
||||
exePath, err := getExecutablePath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
content := fmt.Sprintf(desktopFileContent, exePath)
|
||||
if err := os.WriteFile(autostartPath, []byte(content), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write autostart file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveFromStartup() error {
|
||||
autostartPath, err := getAutostartPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine autostart path: %w", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(autostartPath); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove autostart file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsInStartup() bool {
|
||||
autostartPath, err := getAutostartPath()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = os.Stat(autostartPath)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package platform
|
||||
|
||||
// Overlay defines the cross-platform interface for screen overlays
|
||||
type Overlay interface {
|
||||
Show(message string)
|
||||
Hide()
|
||||
SetVolume(level float64)
|
||||
Close()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//go:build linux
|
||||
package platform
|
||||
|
||||
import "wis-free-v3/internal/linux"
|
||||
|
||||
func NewOverlay() Overlay {
|
||||
return linux.NewOverlay()
|
||||
}
|
||||
|
||||
func PauseMedia() bool {
|
||||
return linux.PauseMedia()
|
||||
}
|
||||
|
||||
func ResumeMedia(wasPaused bool) {
|
||||
linux.ResumeMedia(wasPaused)
|
||||
}
|
||||
|
||||
func AddToStartup() error {
|
||||
return linux.AddToStartup()
|
||||
}
|
||||
|
||||
func RemoveFromStartup() error {
|
||||
return linux.RemoveFromStartup()
|
||||
}
|
||||
|
||||
func IsInStartup() bool {
|
||||
return linux.IsInStartup()
|
||||
}
|
||||
|
||||
func IsProcessRunning(pid int) bool {
|
||||
return linux.IsProcessRunning(pid)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
package platform
|
||||
|
||||
import "wis-free-v3/internal/windows"
|
||||
|
||||
func NewOverlay() Overlay {
|
||||
return windows.NewOverlay()
|
||||
}
|
||||
|
||||
func PauseMedia() bool {
|
||||
return windows.PauseMedia()
|
||||
}
|
||||
|
||||
func ResumeMedia(wasPaused bool) {
|
||||
windows.ResumeMedia(wasPaused)
|
||||
}
|
||||
|
||||
func AddToStartup() error {
|
||||
return windows.AddToStartup()
|
||||
}
|
||||
|
||||
func RemoveFromStartup() error {
|
||||
return windows.RemoveFromStartup()
|
||||
}
|
||||
|
||||
func IsInStartup() bool {
|
||||
return windows.IsInStartup()
|
||||
}
|
||||
|
||||
func IsProcessRunning(pid int) bool {
|
||||
return windows.IsProcessRunning(pid)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package whisper
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func hideWindowContext(cmd *exec.Cmd) {
|
||||
// Not needed on non-Windows platforms
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build windows
|
||||
|
||||
package whisper
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func hideWindowContext(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"wis-free-v3/internal/logger"
|
||||
@@ -285,11 +284,8 @@ func (m *Manager) Transcribe(audioPath string) (string, error) {
|
||||
// Set working directory to the binary's location so it can find DLLs
|
||||
cmd.Dir = filepath.Dir(binaryPath)
|
||||
|
||||
// Hide the console window
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
// Hide the console window on Windows
|
||||
hideWindowContext(cmd)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
outputStr := string(output)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"wis-free-v3/internal/config"
|
||||
"wis-free-v3/internal/logger"
|
||||
"wis-free-v3/internal/system/startup"
|
||||
"wis-free-v3/internal/platform"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
)
|
||||
@@ -50,9 +50,9 @@ func onReady(app App) {
|
||||
|
||||
menuSettings := systray.AddMenuItem("Settings", "Open settings window")
|
||||
menuStartup := systray.AddMenuItemCheckbox(
|
||||
"Start with Windows",
|
||||
"Automatically start when Windows boots",
|
||||
startup.IsInStartup(),
|
||||
"Start with system",
|
||||
"Automatically start when computer boots",
|
||||
platform.IsInStartup(),
|
||||
)
|
||||
|
||||
systray.AddSeparator()
|
||||
@@ -82,18 +82,18 @@ func handleMenuEvents(app App, settings, startupItem, exit *systray.MenuItem) {
|
||||
// toggleStartup handles the startup toggle menu item.
|
||||
func toggleStartup(item *systray.MenuItem) {
|
||||
if item.Checked() {
|
||||
if err := startup.RemoveFromStartup(); err != nil {
|
||||
if err := platform.RemoveFromStartup(); err != nil {
|
||||
logger.Error("Failed to remove from startup: %v", err)
|
||||
} else {
|
||||
item.Uncheck()
|
||||
logger.Info("Removed from Windows startup")
|
||||
logger.Info("Removed from system startup")
|
||||
}
|
||||
} else {
|
||||
if err := startup.AddToStartup(); err != nil {
|
||||
if err := platform.AddToStartup(); err != nil {
|
||||
logger.Error("Failed to add to startup: %v", err)
|
||||
} else {
|
||||
item.Check()
|
||||
logger.Info("Added to Windows startup")
|
||||
logger.Info("Added to system startup")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package media
|
||||
//go:build windows
|
||||
package windows
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
keybd_event = user32.NewProc("keybd_event")
|
||||
user32Media = syscall.NewLazyDLL("user32.dll")
|
||||
keybd_event = user32Media.NewProc("keybd_event")
|
||||
)
|
||||
|
||||
//go:embed check-media.ps1
|
||||
@@ -1,4 +1,5 @@
|
||||
package overlay
|
||||
//go:build windows
|
||||
package windows
|
||||
|
||||
import (
|
||||
"math"
|
||||
@@ -255,8 +256,17 @@ func (o *Overlay) run() {
|
||||
procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
|
||||
procDispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
|
||||
} else {
|
||||
// Higher resolution sleep for better responsiveness while keeping CPU low
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
o.mu.RLock()
|
||||
isShowing := o.isShowing
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
package windows
|
||||
|
||||
import "syscall"
|
||||
|
||||
// IsProcessRunning checks if a process with the given PID exists on Windows.
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
syscall.CloseHandle(handle)
|
||||
return true
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Package startup manages Windows startup registry entries for the application.
|
||||
// It allows the application to automatically start when the user logs in.
|
||||
package startup
|
||||
//go:build windows
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
Reference in New Issue
Block a user