feat: implement enhanced Linux hotkey handling and add command copying functionality

This commit is contained in:
jahruz67
2026-05-20 15:13:52 -07:00
parent 181e663de4
commit 910877d35e
6 changed files with 132 additions and 36 deletions
+23 -12
View File
@@ -632,25 +632,36 @@ func (a *App) startupHeadless() {
} }
// Initialize Hotkey Listener // Initialize Hotkey Listener
a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.StartRecording, a.StopRecording) if shouldStartBuiltInHotkeyListener() {
if runtime.GOOS != "windows" { a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.StartRecording, a.StopRecording)
a.hotkeyListener.SetRegistrationErrorCallback(func(err error) { if runtime.GOOS != "windows" {
logger.Error("Linux hotkey registration failed: %v", err) a.hotkeyListener.SetRegistrationErrorCallback(func(err error) {
go func() { logger.Error("Linux hotkey registration failed: %v", err)
time.Sleep(2 * time.Second) go func() {
if a.overlay != nil { time.Sleep(2 * time.Second)
a.overlay.Show("Shortcut registration failed. Please add a custom system shortcut calling 'wis-free-v3 --action=toggle' as a fallback.") if a.overlay != nil {
} a.overlay.Show("Shortcut registration failed. Add a custom system shortcut with the command shown in Settings.")
}() }
}) }()
})
}
a.hotkeyListener.Start()
} else {
logger.Info("Linux portal hotkey disabled; use the --press command from Settings for GNOME shortcuts")
} }
a.hotkeyListener.Start()
logger.Info("Components initialized successfully!") logger.Info("Components initialized successfully!")
logger.Info("Basic app components loaded, continuing startup...") logger.Info("Basic app components loaded, continuing startup...")
} }
func shouldStartBuiltInHotkeyListener() bool {
if runtime.GOOS != "linux" {
return true
}
return os.Getenv("WISFREE_USE_PORTAL_HOTKEY") == "1"
}
// Shutdown cleans up resources // Shutdown cleans up resources
func (a *App) Shutdown(ctx context.Context) { func (a *App) Shutdown(ctx context.Context) {
if a.hotkeyListener != nil { if a.hotkeyListener != nil {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -6,7 +6,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Wisp Settings</title> <title>Wisp Settings</title>
<script type="module" crossorigin src="/assets/index.fbf781c1.js"></script> <script type="module" crossorigin src="/assets/index.3a064eef.js"></script>
<link rel="stylesheet" href="/assets/index.6e77aa4d.css"> <link rel="stylesheet" href="/assets/index.6e77aa4d.css">
</head> </head>
@@ -54,8 +54,8 @@
<button onclick="copyLinuxPressCommand()">Copy</button> <button onclick="copyLinuxPressCommand()">Copy</button>
</div> </div>
</div> </div>
<p class="hint">GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut using the copied command.</p> <p class="hint">GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut with the copied command.</p>
<p class="hint">KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut using the copied command.</p> <p class="hint">KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut with the copied command.</p>
</div> </div>
<!-- Microphone --> <!-- Microphone -->
@@ -189,4 +189,4 @@
</body> </body>
</html> </html>
+30 -2
View File
@@ -333,7 +333,16 @@
const settings = await window.go.main.App.GetSettings(); const settings = await window.go.main.App.GetSettings();
document.getElementById('apiKey').value = settings.api_key || ''; document.getElementById('apiKey').value = settings.api_key || '';
document.getElementById('shortcutInput').value = settings.shortcut || 'alt+z'; const shortcutInput = document.getElementById('shortcutInput');
if (shortcutInput) {
shortcutInput.value = settings.shortcut || 'alt+z';
}
if (settings.linux_press_mode) {
const section = document.getElementById('linuxPressSection');
const cmdInput = document.getElementById('linuxPressCommand');
if (section) section.style.display = 'block';
if (cmdInput) cmdInput.value = settings.linux_press_command || '';
}
document.getElementById('whisperModel').value = settings.whisper_model || 'whisper-large-v3-turbo'; document.getElementById('whisperModel').value = settings.whisper_model || 'whisper-large-v3-turbo';
document.getElementById('aiModel').value = settings.ai_model || 'llama-3.3-70b-versatile'; document.getElementById('aiModel').value = settings.ai_model || 'llama-3.3-70b-versatile';
document.getElementById('aiPrompt').value = settings.ai_prompt || ''; document.getElementById('aiPrompt').value = settings.ai_prompt || '';
@@ -407,6 +416,25 @@
showSaveStatus('Prompt saved'); showSaveStatus('Prompt saved');
}; };
window.copyLinuxPressCommand = async function () {
const cmdInput = document.getElementById('linuxPressCommand');
if (!cmdInput) return;
const command = cmdInput.value || '';
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(command);
} else {
cmdInput.focus();
cmdInput.select();
document.execCommand('copy');
}
showSaveStatus('Command copied');
} catch (err) {
console.error('Failed to copy Linux command:', err);
}
};
window.toggleStartup = async function () { window.toggleStartup = async function () {
await window.go.main.App.ToggleStartup(document.getElementById('startupToggle').checked); await window.go.main.App.ToggleStartup(document.getElementById('startupToggle').checked);
}; };
@@ -560,4 +588,4 @@
</script> </script>
</body> </body>
</html> </html>
+72 -15
View File
@@ -13,9 +13,21 @@ import (
const linuxPressAddr = "127.0.0.1:9876" const linuxPressAddr = "127.0.0.1:9876"
var ( var (
linuxPressMu sync.Mutex linuxPressMu sync.Mutex
linuxPressTimer *time.Timer linuxPressReleaseTimer *time.Timer
linuxPressRecording bool linuxPressDetectTimer *time.Timer
linuxPressRecording bool
linuxPressHoldMode bool
linuxPressDetectingHold bool
)
const (
// GNOME custom shortcuts commonly auto-repeat while the key is held.
// If we see a second ping quickly, treat the shortcut as push-to-talk.
linuxPressHoldDetectWindow = 1200 * time.Millisecond
// Once hold mode is confirmed, lack of fresh pings means the key was released.
linuxPressReleaseGrace = 450 * time.Millisecond
) )
func init() { func init() {
@@ -58,22 +70,36 @@ func (a *App) startLinuxPressDaemon() {
if !linuxPressRecording { if !linuxPressRecording {
linuxPressRecording = true linuxPressRecording = true
linuxPressHoldMode = false
linuxPressDetectingHold = true
go a.StartRecording() go a.StartRecording()
}
if linuxPressTimer != nil { if linuxPressDetectTimer != nil {
linuxPressTimer.Stop() linuxPressDetectTimer.Stop()
}
linuxPressTimer = time.AfterFunc(300*time.Millisecond, func() {
linuxPressMu.Lock()
defer linuxPressMu.Unlock()
if linuxPressRecording {
linuxPressRecording = false
go a.StopRecording()
} }
}) linuxPressDetectTimer = time.AfterFunc(linuxPressHoldDetectWindow, func() {
linuxPressMu.Lock()
defer linuxPressMu.Unlock()
linuxPressDetectingHold = false
})
w.WriteHeader(http.StatusNoContent)
return
}
if linuxPressDetectingHold || linuxPressHoldMode {
linuxPressHoldMode = true
linuxPressDetectingHold = false
if linuxPressDetectTimer != nil {
linuxPressDetectTimer.Stop()
linuxPressDetectTimer = nil
}
resetLinuxPressReleaseTimer(a)
w.WriteHeader(http.StatusNoContent)
return
}
stopLinuxPressRecording(a)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
}) })
@@ -81,3 +107,34 @@ func (a *App) startLinuxPressDaemon() {
_ = http.ListenAndServe(linuxPressAddr, mux) _ = http.ListenAndServe(linuxPressAddr, mux)
}() }()
} }
func resetLinuxPressReleaseTimer(a *App) {
if linuxPressReleaseTimer != nil {
linuxPressReleaseTimer.Stop()
}
linuxPressReleaseTimer = time.AfterFunc(linuxPressReleaseGrace, func() {
linuxPressMu.Lock()
defer linuxPressMu.Unlock()
if linuxPressRecording && linuxPressHoldMode {
stopLinuxPressRecording(a)
}
})
}
func stopLinuxPressRecording(a *App) {
if linuxPressReleaseTimer != nil {
linuxPressReleaseTimer.Stop()
linuxPressReleaseTimer = nil
}
if linuxPressDetectTimer != nil {
linuxPressDetectTimer.Stop()
linuxPressDetectTimer = nil
}
if linuxPressRecording {
linuxPressRecording = false
linuxPressHoldMode = false
linuxPressDetectingHold = false
go a.StopRecording()
}
}