diff --git a/app.go b/app.go
index 6ee1378..0be7f96 100644
--- a/app.go
+++ b/app.go
@@ -632,25 +632,36 @@ func (a *App) startupHeadless() {
}
// Initialize Hotkey Listener
- a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.StartRecording, a.StopRecording)
- if runtime.GOOS != "windows" {
- a.hotkeyListener.SetRegistrationErrorCallback(func(err error) {
- logger.Error("Linux hotkey registration failed: %v", err)
- go func() {
- time.Sleep(2 * time.Second)
- if a.overlay != nil {
- a.overlay.Show("Shortcut registration failed. Please add a custom system shortcut calling 'wis-free-v3 --action=toggle' as a fallback.")
- }
- }()
- })
+ if shouldStartBuiltInHotkeyListener() {
+ a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.StartRecording, a.StopRecording)
+ if runtime.GOOS != "windows" {
+ a.hotkeyListener.SetRegistrationErrorCallback(func(err error) {
+ logger.Error("Linux hotkey registration failed: %v", err)
+ go func() {
+ time.Sleep(2 * time.Second)
+ 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("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
func (a *App) Shutdown(ctx context.Context) {
if a.hotkeyListener != nil {
diff --git a/frontend/dist/assets/index.3a064eef.js b/frontend/dist/assets/index.3a064eef.js
new file mode 100644
index 0000000..39f9add
--- /dev/null
+++ b/frontend/dist/assets/index.3a064eef.js
@@ -0,0 +1,3 @@
+(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))n(a);new MutationObserver(a=>{for(const l of a)if(l.type==="childList")for(const r of l.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&n(r)}).observe(document,{childList:!0,subtree:!0});function o(a){const l={};return a.integrity&&(l.integrity=a.integrity),a.referrerpolicy&&(l.referrerPolicy=a.referrerpolicy),a.crossorigin==="use-credentials"?l.credentials="include":a.crossorigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function n(a){if(a.ep)return;a.ep=!0;const l=o(a);fetch(a.href,l)}})();let u=!1;window.toggleApiKey=function(){const e=document.getElementById("apiKey"),t=document.getElementById("eyeBtn");u=!u,e.type=u?"text":"password",t.textContent=u?"\u{1F648}":"\u{1F441}\uFE0F"};window.recordShortcut=function(){const e=document.getElementById("recordBtn"),t=document.getElementById("shortcutInput"),o=i=>i.metaKey||i.key==="Meta"||i.key==="OS"||i.code==="MetaLeft"||i.code==="MetaRight";e.textContent="Recording...",e.disabled=!0,t.value="";let n="";const a=i=>{i.preventDefault(),i.stopPropagation();const d=[];i.ctrlKey&&d.push("ctrl"),i.altKey&&d.push("alt"),i.shiftKey&&d.push("shift"),o(i)&&d.push("win");let s=i.key.toLowerCase();s==="control"&&(s="ctrl"),s==="alt"&&(s="alt"),s==="shift"&&(s="shift"),(s==="meta"||s==="os")&&(s="win"),(i.code==="MetaLeft"||i.code==="MetaRight")&&(s="win"),s===" "&&(s="space");const f=new Set(d);f.add(s);const w=Array.from(f).filter(g=>g!==s);w.push(s),n=w.join("+"),t.value=n,["ctrl","alt","shift","win"].includes(s)||r()},l=i=>{i.preventDefault(),i.stopPropagation(),n&&(n.split("+").every(s=>["ctrl","alt","shift","win"].includes(s))&&n.includes("+")?setTimeout(r,100):!i.ctrlKey&&!i.altKey&&!i.shiftKey&&!o(i)&&r())},r=()=>{if(!n){e.textContent="Record",e.disabled=!1,y();return}window.go.main.App.SaveSettings({shortcut:n}).then(i=>{i&&i.indexOf("Invalid shortcut")!==-1&&alert(i),e.textContent="Record",e.disabled=!1,y()})},y=()=>{window.removeEventListener("keydown",a,!0),window.removeEventListener("keyup",l,!0)};window.addEventListener("keydown",a,!0),window.addEventListener("keyup",l,!0)};function m(e){const t=document.getElementById("historyList");if(!e||e.length===0){t.innerHTML='
No history yet.
';return}t.innerHTML="",[...e].reverse().forEach(o=>{const n=document.createElement("div");n.className="history-item";const a=document.createElement("span");a.className="history-time",a.textContent=new Date(o.timestamp).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});const l=document.createElement("div");l.textContent=o.text,n.appendChild(a),n.appendChild(l),t.appendChild(n)})}async function h(){try{const e=await window.go.main.App.GetSettings();m(e.history||[])}catch(e){console.error("Failed to refresh history:",e)}}async function v(){try{const e=await window.go.main.App.GetSettings();document.getElementById("apiKey").value=e.api_key||"";const t=document.getElementById("shortcutInput");if(t&&(t.value=e.shortcut||"alt+z"),e.linux_press_mode){const n=document.getElementById("linuxPressSection"),a=document.getElementById("linuxPressCommand");n&&(n.style.display="block"),a&&(a.value=e.linux_press_command||"")}document.getElementById("whisperModel").value=e.whisper_model||"whisper-large-v3-turbo",document.getElementById("aiModel").value=e.ai_model||"llama-3.3-70b-versatile",document.getElementById("aiPrompt").value=e.ai_prompt||"",document.getElementById("language").value=e.language||"en",document.getElementById("startupToggle").checked=e.startup||!1;const o=e.app_version&&String(e.app_version).trim()||"dev";document.getElementById("appVersion").textContent=o==="dev"?"dev":"v"+o,m(e.history||[])}catch(e){console.error("Failed to load settings:",e)}}async function E(){try{const e=await window.go.main.App.GetMicrophones(),t=document.getElementById("micDevice");e.forEach(o=>{if(o.index!==-1){const n=document.createElement("option");n.value=o.index,n.textContent=o.name,t.appendChild(n)}})}catch(e){console.error("Failed to load mics:",e)}}window.saveApiKey=async function(){const e=document.getElementById("apiKey").value.trim();await window.go.main.App.SaveSettings({api_key:e}),c()};function c(e="Saved"){const t=document.getElementById("saveStatus");t.textContent=e,t.classList.add("show"),setTimeout(()=>t.classList.remove("show"),2e3)}window.saveWhisperModel=async function(){await window.go.main.App.SaveSettings({whisper_model:document.getElementById("whisperModel").value}),c()};window.saveAiModel=async function(){await window.go.main.App.SaveSettings({ai_model:document.getElementById("aiModel").value}),c()};window.saveLanguage=async function(){await window.go.main.App.SaveSettings({language:document.getElementById("language").value}),c()};window.saveMicDevice=async function(){const e=document.getElementById("micDevice").value;await window.go.main.App.SaveSettings({microphone_device:e==="default"?null:parseInt(e)}),c()};window.savePrompt=async function(){await window.go.main.App.SaveSettings({ai_prompt:document.getElementById("aiPrompt").value.trim()}),c("Prompt saved")};window.copyLinuxPressCommand=async function(){const e=document.getElementById("linuxPressCommand");if(!e)return;const t=e.value||"";try{navigator.clipboard&&navigator.clipboard.writeText?await navigator.clipboard.writeText(t):(e.focus(),e.select(),document.execCommand("copy")),c("Command copied")}catch(o){console.error("Failed to copy Linux command:",o)}};window.toggleStartup=async function(){await window.go.main.App.ToggleStartup(document.getElementById("startupToggle").checked)};window.clearHistory=async function(){confirm("Clear all transcription history?")&&(await window.go.main.App.ClearHistory(),m([]))};async function I(){try{const e=await window.go.main.App.CheckOnline(),t=document.getElementById("connectionStatus");if(e)t.innerHTML="\u25CF Online",t.style.background="var(--green-dim)",t.style.color="var(--green)",t.style.border="1px solid rgba(61, 186, 110, 0.2)";else{t.innerHTML="\u25CF Offline \u2014 install local Whisper for transcription",t.style.background="var(--red-dim)",t.style.color="var(--red)",t.style.border="1px solid rgba(224, 82, 82, 0.2)";const o=document.getElementById("aiModel");for(let n of o.options)n.value!=="None"&&!n.value.startsWith("local-")&&(n.disabled=!0,n.text.includes("(offline)")||(n.text+=" (offline)"))}}catch(e){console.error("Connection check failed:",e)}}async function p(){try{const e=await window.go.main.App.GetWhisperInfo(),t=document.getElementById("whisperModel"),o=t.querySelector('option[value^="local-"]');if(o&&o.remove(),e.installed){document.getElementById("whisperNotInstalled").style.display="none",document.getElementById("whisperInstalled").style.display="block",document.getElementById("installedModel").textContent=e.model+" model";const n=document.createElement("option");n.value="local-"+e.model,n.textContent="\u2B21 Local \u2014 "+e.model+" (offline)",n.style.fontWeight="bold",t.insertBefore(n,t.firstChild)}else document.getElementById("whisperNotInstalled").style.display="block",document.getElementById("whisperInstalled").style.display="none"}catch(e){console.error("Whisper status check failed:",e)}}window.installWhisper=async function(){const e=document.getElementById("whisperModelSelect").value,t=document.getElementById("installBtn");t.disabled=!0,t.textContent="Starting installation...";try{const o=await window.go.main.App.InstallWhisper(e);alert(o+`
+
+A terminal window will open showing the installation progress. This may take several minutes depending on your internet speed.`),setTimeout(p,5e3)}catch(o){alert("Installation failed: "+o)}t.disabled=!1,t.textContent="Install Offline Whisper"};window.uninstallWhisper=async function(){if(!!confirm("Uninstall offline Whisper? This will delete the downloaded model."))try{const e=await window.go.main.App.UninstallWhisper();alert(e),p()}catch(e){alert("Uninstall failed: "+e)}};async function x(){try{if(navigator.userAgent.toLowerCase().includes("linux")){const t=document.getElementById("offlineWhisperSection");t&&(t.style.display="none");const o=document.getElementById("startupLabel");o&&(o.innerText="Run on Startup")}}catch(e){console.error("Failed to get environment:",e)}if(await v(),await E(),await I(),await p(),window.runtime&&typeof window.runtime.EventsOn=="function"&&window.runtime.EventsOn("history:updated",function(){h()}),navigator.userAgent.toLowerCase().includes("linux")){const e=document.getElementById("whisperModel");e&&Array.from(e.options).forEach(t=>{t.value.startsWith("local-")&&t.remove()})}}x();
diff --git a/frontend/dist/assets/index.fbf781c1.js b/frontend/dist/assets/index.fbf781c1.js
deleted file mode 100644
index d37b90e..0000000
--- a/frontend/dist/assets/index.fbf781c1.js
+++ /dev/null
@@ -1,3 +0,0 @@
-(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))n(a);new MutationObserver(a=>{for(const s of a)if(s.type==="childList")for(const r of s.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&n(r)}).observe(document,{childList:!0,subtree:!0});function i(a){const s={};return a.integrity&&(s.integrity=a.integrity),a.referrerpolicy&&(s.referrerPolicy=a.referrerpolicy),a.crossorigin==="use-credentials"?s.credentials="include":a.crossorigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(a){if(a.ep)return;a.ep=!0;const s=i(a);fetch(a.href,s)}})();let u=!1;window.toggleApiKey=function(){const e=document.getElementById("apiKey"),t=document.getElementById("eyeBtn");u=!u,e.type=u?"text":"password",t.textContent=u?"\u{1F648}":"\u{1F441}\uFE0F"};window.recordShortcut=function(){const e=document.getElementById("recordBtn"),t=document.getElementById("shortcutInput"),i=o=>o.metaKey||o.key==="Meta"||o.key==="OS"||o.code==="MetaLeft"||o.code==="MetaRight";e.textContent="Recording...",e.disabled=!0,t.value="";let n="";const a=o=>{o.preventDefault(),o.stopPropagation();const d=[];o.ctrlKey&&d.push("ctrl"),o.altKey&&d.push("alt"),o.shiftKey&&d.push("shift"),i(o)&&d.push("win");let l=o.key.toLowerCase();l==="control"&&(l="ctrl"),l==="alt"&&(l="alt"),l==="shift"&&(l="shift"),(l==="meta"||l==="os")&&(l="win"),(o.code==="MetaLeft"||o.code==="MetaRight")&&(l="win"),l===" "&&(l="space");const y=new Set(d);y.add(l);const w=Array.from(y).filter(g=>g!==l);w.push(l),n=w.join("+"),t.value=n,["ctrl","alt","shift","win"].includes(l)||r()},s=o=>{o.preventDefault(),o.stopPropagation(),n&&(n.split("+").every(l=>["ctrl","alt","shift","win"].includes(l))&&n.includes("+")?setTimeout(r,100):!o.ctrlKey&&!o.altKey&&!o.shiftKey&&!i(o)&&r())},r=()=>{if(!n){e.textContent="Record",e.disabled=!1,f();return}window.go.main.App.SaveSettings({shortcut:n}).then(o=>{o&&o.indexOf("Invalid shortcut")!==-1&&alert(o),e.textContent="Record",e.disabled=!1,f()})},f=()=>{window.removeEventListener("keydown",a,!0),window.removeEventListener("keyup",s,!0)};window.addEventListener("keydown",a,!0),window.addEventListener("keyup",s,!0)};function m(e){const t=document.getElementById("historyList");if(!e||e.length===0){t.innerHTML='No history yet.
';return}t.innerHTML="",[...e].reverse().forEach(i=>{const n=document.createElement("div");n.className="history-item";const a=document.createElement("span");a.className="history-time",a.textContent=new Date(i.timestamp).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});const s=document.createElement("div");s.textContent=i.text,n.appendChild(a),n.appendChild(s),t.appendChild(n)})}async function h(){try{const e=await window.go.main.App.GetSettings();m(e.history||[])}catch(e){console.error("Failed to refresh history:",e)}}async function v(){try{const e=await window.go.main.App.GetSettings();document.getElementById("apiKey").value=e.api_key||"",e.linux_press_mode&&document.getElementById("linuxPressSection")&&(document.getElementById("linuxPressSection").style.display="block",document.getElementById("linuxPressCommand").value=e.linux_press_command||""),document.getElementById("whisperModel").value=e.whisper_model||"whisper-large-v3-turbo",document.getElementById("aiModel").value=e.ai_model||"llama-3.3-70b-versatile",document.getElementById("aiPrompt").value=e.ai_prompt||"",document.getElementById("language").value=e.language||"en",document.getElementById("startupToggle").checked=e.startup||!1;const t=e.app_version&&String(e.app_version).trim()||"dev";document.getElementById("appVersion").textContent=t==="dev"?"dev":"v"+t,m(e.history||[])}catch(e){console.error("Failed to load settings:",e)}}async function E(){try{const e=await window.go.main.App.GetMicrophones(),t=document.getElementById("micDevice");e.forEach(i=>{if(i.index!==-1){const n=document.createElement("option");n.value=i.index,n.textContent=i.name,t.appendChild(n)}})}catch(e){console.error("Failed to load mics:",e)}}window.saveApiKey=async function(){const e=document.getElementById("apiKey").value.trim();await window.go.main.App.SaveSettings({api_key:e}),c()};function c(e="Saved"){const t=document.getElementById("saveStatus");t.textContent=e,t.classList.add("show"),setTimeout(()=>t.classList.remove("show"),2e3)}window.saveWhisperModel=async function(){await window.go.main.App.SaveSettings({whisper_model:document.getElementById("whisperModel").value}),c()};window.saveAiModel=async function(){await window.go.main.App.SaveSettings({ai_model:document.getElementById("aiModel").value}),c()};window.saveLanguage=async function(){await window.go.main.App.SaveSettings({language:document.getElementById("language").value}),c()};window.saveMicDevice=async function(){const e=document.getElementById("micDevice").value;await window.go.main.App.SaveSettings({microphone_device:e==="default"?null:parseInt(e)}),c()};window.savePrompt=async function(){await window.go.main.App.SaveSettings({ai_prompt:document.getElementById("aiPrompt").value.trim()}),c("Prompt saved")};window.toggleStartup=async function(){await window.go.main.App.ToggleStartup(document.getElementById("startupToggle").checked)};window.clearHistory=async function(){confirm("Clear all transcription history?")&&(await window.go.main.App.ClearHistory(),m([]))};async function I(){try{const e=await window.go.main.App.CheckOnline(),t=document.getElementById("connectionStatus");if(e)t.innerHTML="\u25CF Online",t.style.background="var(--green-dim)",t.style.color="var(--green)",t.style.border="1px solid rgba(61, 186, 110, 0.2)";else{t.innerHTML="\u25CF Offline \u2014 install local Whisper for transcription",t.style.background="var(--red-dim)",t.style.color="var(--red)",t.style.border="1px solid rgba(224, 82, 82, 0.2)";const i=document.getElementById("aiModel");for(let n of i.options)n.value!=="None"&&!n.value.startsWith("local-")&&(n.disabled=!0,n.text.includes("(offline)")||(n.text+=" (offline)"))}}catch(e){console.error("Connection check failed:",e)}}async function p(){try{const e=await window.go.main.App.GetWhisperInfo(),t=document.getElementById("whisperModel"),i=t.querySelector('option[value^="local-"]');if(i&&i.remove(),e.installed){document.getElementById("whisperNotInstalled").style.display="none",document.getElementById("whisperInstalled").style.display="block",document.getElementById("installedModel").textContent=e.model+" model";const n=document.createElement("option");n.value="local-"+e.model,n.textContent="\u2B21 Local \u2014 "+e.model+" (offline)",n.style.fontWeight="bold",t.insertBefore(n,t.firstChild)}else document.getElementById("whisperNotInstalled").style.display="block",document.getElementById("whisperInstalled").style.display="none"}catch(e){console.error("Whisper status check failed:",e)}}window.copyLinuxPressCommand=function(){const e=document.getElementById("linuxPressCommand");if(!e)return;const t=e.value||"";navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(t).then(()=>c("Command copied")):((e.focus(),e.select(),document.execCommand("copy")),c("Command copied"))};window.installWhisper=async function(){const e=document.getElementById("whisperModelSelect").value,t=document.getElementById("installBtn");t.disabled=!0,t.textContent="Starting installation...";try{const i=await window.go.main.App.InstallWhisper(e);alert(i+`
-
-A terminal window will open showing the installation progress. This may take several minutes depending on your internet speed.`),setTimeout(p,5e3)}catch(i){alert("Installation failed: "+i)}t.disabled=!1,t.textContent="Install Offline Whisper"};window.uninstallWhisper=async function(){if(!!confirm("Uninstall offline Whisper? This will delete the downloaded model."))try{const e=await window.go.main.App.UninstallWhisper();alert(e),p()}catch(e){alert("Uninstall failed: "+e)}};async function S(){try{if(navigator.userAgent.toLowerCase().includes("linux")){const t=document.getElementById("offlineWhisperSection");t&&(t.style.display="none");const i=document.getElementById("startupLabel");i&&(i.innerText="Run on Startup")}}catch(e){console.error("Failed to get environment:",e)}if(await v(),await E(),await I(),await p(),window.runtime&&typeof window.runtime.EventsOn=="function"&&window.runtime.EventsOn("history:updated",function(){h()}),navigator.userAgent.toLowerCase().includes("linux")){const e=document.getElementById("whisperModel");e&&Array.from(e.options).forEach(t=>{t.value.startsWith("local-")&&t.remove()})}}S();
diff --git a/frontend/dist/index.html b/frontend/dist/index.html
index 312acb7..3abae24 100644
--- a/frontend/dist/index.html
+++ b/frontend/dist/index.html
@@ -6,7 +6,7 @@
Wisp Settings
-
+
@@ -54,8 +54,8 @@
- GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut using the copied command.
- KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut using the copied command.
+ GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut with the copied command.
+ KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut with the copied command.
@@ -189,4 +189,4 @@