mirror of
https://github.com/jahruz67/wisp-open.git
synced 2026-08-08 18:14:08 +00:00
fix: Add cross-platform handling for tray, updater, and icon generation
- 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
This commit is contained in:
@@ -126,6 +126,9 @@ func (a *App) startup(ctx context.Context) {
|
||||
})
|
||||
|
||||
// Start system tray in a goroutine
|
||||
// PLATFORM NOTE: On Linux, Wails' GTK main loop needs tray.Start() to be
|
||||
// called synchronously (uses systray.Register). On Windows (and macOS),
|
||||
// tray.Start() blocks for the Win32 message pump, so it must run in a goroutine.
|
||||
if runtime.GOOS == "linux" {
|
||||
tray.Start(a)
|
||||
} else {
|
||||
@@ -295,7 +298,15 @@ func (a *App) processRecording(recordingPath string) {
|
||||
// IDIOT-PROOFING: Ignore extremely short recordings (less than ~100ms or ~3KB)
|
||||
// that are likely accidental clicks or hardware glitches.
|
||||
stat, statErr := os.Stat(recordingPath)
|
||||
if statErr == nil && stat.Size() < 4000 {
|
||||
if statErr != nil {
|
||||
logger.Error("Failed to stat recording file: %v", statErr)
|
||||
if a.overlay != nil {
|
||||
a.overlay.Hide()
|
||||
}
|
||||
tray.UpdateStatus("Ready")
|
||||
return
|
||||
}
|
||||
if stat.Size() < 4000 {
|
||||
logger.Info("Discarding tiny recording (%d bytes)", stat.Size())
|
||||
os.Remove(recordingPath)
|
||||
if a.overlay != nil {
|
||||
@@ -406,6 +417,9 @@ func (a *App) GetSettings() map[string]interface{} {
|
||||
conf["history"] = a.config.History
|
||||
conf["startup"] = platform.IsInStartup()
|
||||
conf["app_version"] = AppVersion
|
||||
// PLATFORM NOTE: Linux-only settings — press daemon command and ydotool status.
|
||||
// These are not included in the Windows build. See linux_press_daemon.go
|
||||
// and text_insert_linux.go for the implementations.
|
||||
if runtime.GOOS == "linux" {
|
||||
if exePath, err := os.Executable(); err == nil {
|
||||
conf["linux_press_command"] = exePath + " --press"
|
||||
@@ -421,6 +435,10 @@ func (a *App) SaveSettings(settings map[string]interface{}) string {
|
||||
if val, ok := settings["api_key"].(string); ok {
|
||||
a.config.APIKey = val
|
||||
}
|
||||
// PLATFORM NOTE: Shortcut saving is disabled on Linux because Linux uses
|
||||
// the `--press` daemon approach (GNOME custom shortcuts) instead of the
|
||||
// built-in hotkey listener. On Windows, we allow the user to configure
|
||||
// the shortcut through the settings UI.
|
||||
if runtime.GOOS != "linux" {
|
||||
if val, ok := settings["shortcut"].(string); ok {
|
||||
_, _, modOnly, ok := hotkey.ParseShortcut(val)
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Wisp Settings</title>
|
||||
|
||||
<script type="module" crossorigin src="/assets/index.bbf41643.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index.91f83df4.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.6e77aa4d.css">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ func (l *Listener) Start() {
|
||||
}
|
||||
|
||||
if modOnly {
|
||||
// WINDOWS-ONLY FEATURE: Modifier-only shortcuts (e.g. ctrl+win without a key)
|
||||
// are only supported on Windows via modifier polling. On Linux, this is rejected.
|
||||
if runtime.GOOS != "windows" {
|
||||
logger.Error("Modifier-only shortcuts like %q are only supported on Windows", l.shortcut)
|
||||
l.mu.Unlock()
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Linux media playback management.
|
||||
// Uses playerctl to pause/resume media during recording.
|
||||
// The Windows equivalent is in internal/windows/
|
||||
// ============================================================
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — No-op overlay implementation for Linux.
|
||||
// On Linux, the overlay is intentionally disabled (no desktop
|
||||
// notifications). The Windows equivalent with full Win32 overlay
|
||||
// is in internal/windows/overlay.go
|
||||
// ============================================================
|
||||
|
||||
package linux
|
||||
|
||||
// linuxOverlay intentionally does not show system notifications. Tray status
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Linux process management utilities.
|
||||
// Uses syscall.Kill(pid, 0) to check if a process is running.
|
||||
// The Windows equivalent is in internal/windows/process.go
|
||||
// ============================================================
|
||||
|
||||
package linux
|
||||
|
||||
import "syscall"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Linux startup (autostart) management via
|
||||
// XDG Desktop Entry files (.desktop). Creates/removes autostart
|
||||
// entries and manages the application's .desktop file.
|
||||
// The Windows equivalent is in internal/windows/
|
||||
// ============================================================
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
// ============================================================
|
||||
// CROSS-PLATFORM FILE — This defines the Overlay interface
|
||||
// that is implemented differently on each platform:
|
||||
// - Windows: internal/windows/overlay.go (Win32 overlay)
|
||||
// - Linux: internal/linux/overlay.go (no-op)
|
||||
// This file itself is compiled on ALL platforms.
|
||||
// ============================================================
|
||||
|
||||
package platform
|
||||
|
||||
// Overlay defines the cross-platform interface for screen overlays
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Delegates all platform operations to the
|
||||
// internal/linux package (overlay, media, startup, process).
|
||||
// The Windows equivalent is platform_windows.go
|
||||
// ============================================================
|
||||
|
||||
package platform
|
||||
|
||||
import "wis-free-v3/internal/linux"
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Delegates all platform operations to the
|
||||
// internal/windows package (overlay, media, startup, process).
|
||||
// The Linux equivalent is platform_linux.go
|
||||
// ============================================================
|
||||
|
||||
package platform
|
||||
|
||||
import "wis-free-v3/internal/windows"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -115,7 +116,13 @@ func (m *Manager) Install(model string) error {
|
||||
return fmt.Errorf("failed to create install directory: %w", err)
|
||||
}
|
||||
|
||||
// Create install script
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
// On Linux/macOS, the automatic installer is not available.
|
||||
// Users should install whisper.cpp from their package manager or build from source.
|
||||
return fmt.Errorf("automatic whisper installation is not available on %s; install whisper.cpp from your package manager (e.g. 'sudo apt install whisper-cpp' or 'brew install whisper-cpp') or build from source at https://github.com/ggerganov/whisper.cpp, then place the binary and model in %s", runtime.GOOS, m.installDir)
|
||||
}
|
||||
|
||||
// Create install script (Windows batch file)
|
||||
scriptPath := filepath.Join(m.installDir, "install.bat")
|
||||
script := m.generateInstallScript(model)
|
||||
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
|
||||
@@ -363,11 +370,18 @@ func (m *Manager) getBinaryPath() string {
|
||||
// whisper.cpp extracts to a Release subdirectory
|
||||
releaseDir := filepath.Join(m.installDir, "Release")
|
||||
|
||||
// Try different possible binary names
|
||||
// whisper-cli.exe is the new standard (main.exe is deprecated)
|
||||
// Try different possible binary names (platform-aware)
|
||||
// On Windows: whisper-cli.exe / main.exe
|
||||
// On Linux/macOS: whisper-cli / main
|
||||
possibleNames := []string{
|
||||
"whisper-cli.exe",
|
||||
"main.exe",
|
||||
"whisper-cli",
|
||||
"main",
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
possibleNames = []string{
|
||||
"whisper-cli.exe",
|
||||
"main.exe",
|
||||
}
|
||||
}
|
||||
|
||||
// First check in Release subdirectory
|
||||
@@ -386,8 +400,11 @@ func (m *Manager) getBinaryPath() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Default to Release/main.exe
|
||||
return filepath.Join(releaseDir, "main.exe")
|
||||
// Default path
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(releaseDir, "main.exe")
|
||||
}
|
||||
return filepath.Join(releaseDir, "main")
|
||||
}
|
||||
|
||||
// CheckOnline checks if internet is available
|
||||
|
||||
+28
-25
@@ -279,52 +279,55 @@ func initDynamicIcons() {
|
||||
}
|
||||
|
||||
func createMicPNG(c color.Color) []byte {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
|
||||
const S = 128 // canvas size
|
||||
img := image.NewRGBA(image.Rect(0, 0, S, S))
|
||||
// All pixels are transparent by default (new RGBA starts with 0 alpha).
|
||||
|
||||
cx, cy := S/2, S/2 // center (64, 50)
|
||||
|
||||
// Let's draw the microphone parts
|
||||
for y := 0; y < 64; y++ {
|
||||
for x := 0; x < 64; x++ {
|
||||
for y := 0; y < S; y++ {
|
||||
for x := 0; x < S; x++ {
|
||||
drawPixel := false
|
||||
|
||||
// 1. Capsule Body (Rounded Rectangle)
|
||||
// Capsule center is X=32, Y=25. Width=14 (radius 7), height of straight part = 10 (Y from 20 to 30)
|
||||
if x >= 25 && x <= 39 && y >= 20 && y <= 30 {
|
||||
// Capsule center is X=64, Y=50. Width=28 (radius 14), height of straight part = 20 (Y from 40 to 60)
|
||||
if x >= 50 && x <= 78 && y >= 40 && y <= 60 {
|
||||
drawPixel = true
|
||||
} else if y < 20 {
|
||||
// Top cap: center (32, 20), radius 7
|
||||
dx := float64(x - 32)
|
||||
dy := float64(y - 20)
|
||||
if dx*dx+dy*dy <= 49 { // 7^2
|
||||
} else if y < 40 {
|
||||
// Top cap: center (64, 40), radius 14
|
||||
dx := float64(x - cx)
|
||||
dy := float64(y - 40)
|
||||
if dx*dx+dy*dy <= 196 { // 14^2
|
||||
drawPixel = true
|
||||
}
|
||||
} else if y > 30 && y <= 37 {
|
||||
// Bottom cap: center (32, 30), radius 7
|
||||
dx := float64(x - 32)
|
||||
dy := float64(y - 30)
|
||||
if dx*dx+dy*dy <= 49 {
|
||||
} else if y > 60 && y <= 74 {
|
||||
// Bottom cap: center (64, 60), radius 14
|
||||
dx := float64(x - cx)
|
||||
dy := float64(y - 60)
|
||||
if dx*dx+dy*dy <= 196 {
|
||||
drawPixel = true
|
||||
}
|
||||
}
|
||||
|
||||
// 2. U-stand
|
||||
// Center of U-stand circle is (32, 25).
|
||||
// Outer radius = 15, inner radius = 12 (thickness 3)
|
||||
// Only draw for Y >= 25 and Y <= 40
|
||||
dx := float64(x - 32)
|
||||
dy := float64(y - 25)
|
||||
// Center of U-stand circle is (64, 50).
|
||||
// Outer radius = 30, inner radius = 24 (thickness 6)
|
||||
// Only draw for Y >= 50 and Y <= 80
|
||||
dx := float64(x - cx)
|
||||
dy := float64(y - cy)
|
||||
distSq := dx*dx + dy*dy
|
||||
if y >= 25 && y <= 40 && distSq >= 144 && distSq <= 225 { // 12^2 to 15^2
|
||||
if y >= 50 && y <= 80 && distSq >= 576 && distSq <= 900 { // 24^2 to 30^2
|
||||
drawPixel = true
|
||||
}
|
||||
|
||||
// 3. Stem (Vertical line from Y=40 to 50, X=31 to 33)
|
||||
if x >= 31 && x <= 33 && y >= 40 && y <= 50 {
|
||||
// 3. Stem (Vertical line from Y=80 to 100, X=62 to 66)
|
||||
if x >= 62 && x <= 66 && y >= 80 && y <= 100 {
|
||||
drawPixel = true
|
||||
}
|
||||
|
||||
// 4. Base (Horizontal line at Y=50 to 52, X=20 to 44)
|
||||
if x >= 20 && x <= 44 && y >= 50 && y <= 52 {
|
||||
// 4. Base (Horizontal line at Y=100 to 104, X=40 to 88)
|
||||
if x >= 40 && x <= 88 && y >= 100 && y <= 104 {
|
||||
drawPixel = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Sets the GTK program name for the system tray
|
||||
// on Linux. This is required for proper desktop integration.
|
||||
// ============================================================
|
||||
|
||||
package tray
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Tray startup for Linux. On Linux, Wails'
|
||||
// GTK main loop is already running, so we use systray.Register
|
||||
// instead of systray.Run (which would start a second gtk_main).
|
||||
// The Windows equivalent is tray_start_nonlinux.go
|
||||
// ============================================================
|
||||
|
||||
package tray
|
||||
|
||||
import "github.com/getlantern/systray"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build !linux
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Tray startup for Windows (and macOS).
|
||||
// Uses systray.Run which blocks and runs the Win32 message pump.
|
||||
// The Linux equivalent is tray_start_linux.go which uses
|
||||
// systray.Register to integrate with the existing GTK loop.
|
||||
// ============================================================
|
||||
|
||||
package tray
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Windows media playback management.
|
||||
// Uses Win32 keybd_event to send VK_MEDIA_PLAY_PAUSE and a
|
||||
// PowerShell script to check media playback state via SMTC.
|
||||
// The Linux equivalent is internal/linux/media.go (playerctl)
|
||||
// ============================================================
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Native Windows overlay window using
|
||||
// Win32 API directly (CreateWindowEx, GDI drawing, etc.).
|
||||
// This shows a pill-shaped overlay on screen during recording.
|
||||
// The Linux equivalent is internal/linux/overlay.go (no-op).
|
||||
// ============================================================
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Windows process management utilities.
|
||||
// Uses Win32 API (OpenProcess, GetExitCodeProcess) to check
|
||||
// if a process is running. The Linux equivalent for process
|
||||
// checking is in internal/linux/process.go
|
||||
// ============================================================
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Windows startup (autostart) management
|
||||
// via the Windows Registry (HKCU\Software\Microsoft\Windows\
|
||||
// CurrentVersion\Run). The Linux equivalent for autostart
|
||||
// management is internal/linux/startup.go (XDG .desktop files)
|
||||
// ============================================================
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — Portal-based global hotkey implementation
|
||||
// for Linux using the org.freedesktop.portal.GlobalShortcuts
|
||||
// D-Bus API (Wayland/desktop-agnostic).
|
||||
// The Windows equivalent is in hotkey_windows.go
|
||||
// ============================================================
|
||||
|
||||
package hotkey
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//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 (
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
//go:build !linux
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Stub for the Linux press daemon.
|
||||
// On Windows, the press daemon is not used.
|
||||
// The real implementation is in linux_press_daemon.go
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
func (a *App) startLinuxPressDaemon() {}
|
||||
|
||||
@@ -87,6 +87,8 @@ func main() {
|
||||
OnBeforeClose: app.beforeClose,
|
||||
StartHidden: true,
|
||||
Bind: []interface{}{app},
|
||||
// PLATFORM NOTE: Linux-specific Wails options (sets the program name
|
||||
// for desktop integration). These options are only applied on Linux.
|
||||
Linux: &linux.Options{
|
||||
ProgramName: "wis-free-v3",
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Stub for the Unix domain socket IPC.
|
||||
// On Windows, single-instance enforcement uses a lock file
|
||||
// (see main.go), and helper invocations don't use Unix sockets.
|
||||
// The real Unix implementation is in main_instance_unix.go
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
// secondInstanceWake is unused on Windows (second-instance UX not wired here).
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
//go:build unix && !windows
|
||||
|
||||
// ============================================================
|
||||
// UNIX-ONLY FILE — This file compiles on Linux (and macOS) but
|
||||
// NOT on Windows. It provides Unix domain socket support for
|
||||
// single-instance enforcement and IPC communication with
|
||||
// helper processes (e.g. GNOME custom shortcuts).
|
||||
// The Windows equivalent stub is main_instance_stub.go
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
+10
-2
@@ -1,5 +1,11 @@
|
||||
//go:build linux
|
||||
|
||||
// ============================================================
|
||||
// LINUX-ONLY FILE — This file compiles ONLY on Linux.
|
||||
// Any changes here will NOT affect the Windows build.
|
||||
// For the Windows equivalent, see text_insert_nonlinux.go
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -36,11 +42,11 @@ func (a *App) insertTranscription(text string) {
|
||||
|
||||
func (a *App) releaseLinuxInputFocus() {
|
||||
if a.ctx == nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
wailsruntime.WindowHide(a.ctx)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func typeLinuxTextWithYdotool(text string) error {
|
||||
@@ -114,6 +120,8 @@ func linuxYdotoolStatus() map[string]interface{} {
|
||||
status["message"] = "ydotool is ready for direct typing."
|
||||
return status
|
||||
}
|
||||
|
||||
func getYdotoolSocketPath() (string, error) {
|
||||
if socketPath := strings.TrimSpace(os.Getenv("YDOTOOL_SOCKET")); socketPath != "" {
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
return socketPath, nil
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build !linux
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — This file compiles on Windows (and macOS)
|
||||
// but NOT on Linux. It uses robotgo for clipboard/paste which
|
||||
// is Windows-specific in this app. The Linux equivalent is
|
||||
// text_insert_linux.go which uses ydotool instead.
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -37,6 +44,6 @@ func (a *App) insertTranscription(text string) {
|
||||
|
||||
// pasteText simulates Ctrl+V to paste from clipboard
|
||||
func (a *App) pasteText() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
robotgo.KeyTap("v", "ctrl")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
//go:build !linux
|
||||
|
||||
// ============================================================
|
||||
// WINDOWS-ONLY FILE — Stub for the Linux ydotool status check.
|
||||
// On Windows, ydotool is not used, so this returns "not ready".
|
||||
// The real implementation is in text_insert_linux.go
|
||||
// ============================================================
|
||||
|
||||
package main
|
||||
|
||||
func linuxYdotoolStatus() map[string]interface{} {
|
||||
|
||||
Reference in New Issue
Block a user