diff --git a/CHANGELOG.md b/CHANGELOG.md index a57709c..2d8e0ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 变更 - 切换后端时销毁非当前后端的网页窗口,保留登录会话但不再保活其 renderer。 +- 移除悬浮球点击录音和常驻显示功能,悬浮球仅用于显示语音会话状态。 ## [0.1.10] - 2026-08-23 diff --git a/packaging/rpm/meloasr-binary.spec b/packaging/rpm/meloasr-binary.spec index eccaea8..5c04cf7 100644 --- a/packaging/rpm/meloasr-binary.spec +++ b/packaging/rpm/meloasr-binary.spec @@ -38,6 +38,7 @@ tar -C %{buildroot} -xzf %{SOURCE0} %changelog * Sun Aug 23 2026 MeloASR contributors - 0.1.11-1 - Release only the selected speech backend renderer. +- Keep the overlay as a non-interactive session status indicator. * Sun Aug 23 2026 MeloASR contributors - 0.1.10-1 - Restore the Nix npm configuration hook before building the application. diff --git a/src/main/index.ts b/src/main/index.ts index 8a29063..eec269d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -88,9 +88,6 @@ function registerIpc(services: Services): void { const { settings, autoStart, bridge, backends, overlay, sessions, settingsWindow } = services; const settingsSender = (senderId: number): boolean => Boolean(settingsWindow.window && !settingsWindow.window.isDestroyed() && settingsWindow.window.webContents.id === senderId); - const overlaySender = (senderId: number): boolean => - Boolean(overlay.window && !overlay.window.isDestroyed() && overlay.window.webContents.id === senderId); - ipcMain.handle('settings:get-state', (event) => { if (!settingsSender(event.sender.id)) throw new Error('拒绝未授权的设置请求'); return settingsWindow.state(); @@ -115,10 +112,6 @@ function registerIpc(services: Services): void { overlay.applyPreset(preset, displayId); return settingsWindow.state(); }); - ipcMain.handle('settings:preview-overlay', (event) => { - if (!settingsSender(event.sender.id)) throw new Error('拒绝未授权的预览请求'); - overlay.preview(); - }); ipcMain.handle('settings:begin-positioning', (event) => { if (!settingsSender(event.sender.id)) throw new Error('拒绝未授权的位置调整请求'); overlay.beginPositioning(); @@ -141,12 +134,6 @@ function registerIpc(services: Services): void { settings.update({ autoStart: enabled }); return settingsWindow.state(); }); - ipcMain.handle('settings:set-overlay-persistent', (event, enabled: unknown) => { - if (!settingsSender(event.sender.id) || typeof enabled !== 'boolean') throw new Error('无效的悬浮球设置'); - settings.update({ overlayPersistent: enabled }); - overlay.reconcilePersistent(); - return settingsWindow.state(); - }); ipcMain.handle('settings:set-diagnostic-logging', (event, enabled: unknown) => { if (!settingsSender(event.sender.id) || typeof enabled !== 'boolean') throw new Error('无效的日志设置'); settings.update({ diagnosticLogging: enabled }); @@ -157,9 +144,6 @@ function registerIpc(services: Services): void { if (!settingsSender(event.sender.id)) throw new Error('拒绝未授权的外部链接请求'); await settingsWindow.openRepository(); }); - ipcMain.on('overlay:toggle-recording', (event) => { - if (overlaySender(event.sender.id)) sessions.requestStartFromOverlay(); - }); ipcMain.on('backend-status', (event, payload: BackendStatusPayload) => backends.handleStatus(event, payload)); ipcMain.on('backend-transcript', (event, payload: { backend?: string; text?: string }) => { if (typeof payload?.backend === 'string' && typeof payload.text === 'string' && backends.senderIsActive(event, payload.backend)) { diff --git a/src/main/overlay-controller.ts b/src/main/overlay-controller.ts index 6d4abe1..c2063d0 100644 --- a/src/main/overlay-controller.ts +++ b/src/main/overlay-controller.ts @@ -35,9 +35,9 @@ export class OverlayController { } }); this.window.setAlwaysOnTop(true, 'floating'); + this.window.setIgnoreMouseEvents(true); await this.window.loadFile(path.join(__dirname, '..', 'renderer', 'overlay.html')); this.position(); - this.reconcilePersistent(); } position(point = this.settings.get().overlayPosition): Point { @@ -68,7 +68,6 @@ export class OverlayController { if (!this.window || this.window.isDestroyed()) return; this.positioning = true; this.clearHideTimer(); - this.window.setIgnoreMouseEvents(true); this.window.webContents.send('overlay-state', { state: 'idle' }); this.window.showInactive(); } @@ -77,8 +76,7 @@ export class OverlayController { if (!this.positioning) return; this.positioning = false; if (!this.window || this.window.isDestroyed()) return; - this.window.setIgnoreMouseEvents(false); - this.reconcilePersistent(); + this.window.hide(); } showState(state: SessionState | 'done' | 'error'): void { @@ -90,26 +88,9 @@ export class OverlayController { hideLater(delay = 900): void { this.clearHideTimer(); - if (this.settings.get().overlayPersistent) return; this.hideTimer = setTimeout(() => this.window?.hide(), delay); } - preview(): void { - this.position(); - this.showState('recording'); - this.hideLater(1800); - } - - reconcilePersistent(): void { - if (!this.window || this.window.isDestroyed()) return; - if (this.positioning) return; - if (this.settings.get().overlayPersistent) { - this.showState('idle'); - } else { - this.window.hide(); - } - } - private clearHideTimer(): void { if (this.hideTimer) clearTimeout(this.hideTimer); this.hideTimer = null; diff --git a/src/main/session-controller.ts b/src/main/session-controller.ts index 0ed7981..2f76647 100644 --- a/src/main/session-controller.ts +++ b/src/main/session-controller.ts @@ -54,14 +54,6 @@ export class SessionController extends EventEmitter { if (this.stopRequestedDuringStart) this.stop(); } - requestStartFromOverlay(): void { - if (this.state === 'recording' || this.state === 'starting') { - this.stop(); - return; - } - if (!this.bridge.ready || !this.bridge.requestInputStart()) this.reject('Fcitx5 插件尚未连接'); - } - stop(): void { if (this.state === 'starting') { this.stopRequestedDuringStart = true; diff --git a/src/main/settings-store.ts b/src/main/settings-store.ts index 46fc10f..0d49f06 100644 --- a/src/main/settings-store.ts +++ b/src/main/settings-store.ts @@ -8,7 +8,6 @@ const DEFAULT_SETTINGS: AppSettings = { backendId: 'qianwen', shortcut: 'Control+Shift+space', autoStart: true, - overlayPersistent: false, diagnosticLogging: false, overlayPosition: null, loginNoticeShown: {} @@ -22,10 +21,12 @@ export class SettingsStore extends EventEmitter { try { const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf8')) as Partial & { backend?: string; + overlayPersistent?: unknown; }; + const { overlayPersistent: _obsoleteOverlayPersistent, ...saved } = parsed; this.value = { ...DEFAULT_SETTINGS, - ...parsed, + ...saved, backendId: parsed.backendId ?? parsed.backend ?? DEFAULT_SETTINGS.backendId, overlayPosition: this.isPoint(parsed.overlayPosition) ? { x: Math.round(parsed.overlayPosition.x), y: Math.round(parsed.overlayPosition.y) diff --git a/src/main/settings-window.ts b/src/main/settings-window.ts index 2d47851..0b772fd 100644 --- a/src/main/settings-window.ts +++ b/src/main/settings-window.ts @@ -81,7 +81,6 @@ export class SettingsWindow { sessionState: this.sessions.state, shortcut: current.shortcut, autoStart: current.autoStart, - overlayPersistent: current.overlayPersistent, diagnosticLogging: current.diagnosticLogging, overlayPosition: position, displays: screen.getAllDisplays().map((display, index) => ({ diff --git a/src/preload/overlay.ts b/src/preload/overlay.ts index b8759da..252b87c 100644 --- a/src/preload/overlay.ts +++ b/src/preload/overlay.ts @@ -1,7 +1,6 @@ import { contextBridge, ipcRenderer } from 'electron'; contextBridge.exposeInMainWorld('meloOverlay', { - toggleRecording: (): void => ipcRenderer.send('overlay:toggle-recording'), onState: (listener: (state: string) => void): void => { ipcRenderer.on('overlay-state', (_event, payload: { state: string }) => listener(payload.state)); } diff --git a/src/preload/settings.ts b/src/preload/settings.ts index 5bcbe38..d468318 100644 --- a/src/preload/settings.ts +++ b/src/preload/settings.ts @@ -9,13 +9,10 @@ contextBridge.exposeInMainWorld('meloSettings', { ipcRenderer.invoke('settings:set-position', position, displayId), setPreset: (preset: string, displayId: string): Promise => ipcRenderer.invoke('settings:set-preset', preset, displayId), - previewOverlay: (): Promise => ipcRenderer.invoke('settings:preview-overlay'), beginPositioning: (): Promise => ipcRenderer.invoke('settings:begin-positioning'), endPositioning: (): Promise => ipcRenderer.invoke('settings:end-positioning'), setShortcut: (shortcut: string): Promise => ipcRenderer.invoke('settings:set-shortcut', shortcut), setAutoStart: (enabled: boolean): Promise => ipcRenderer.invoke('settings:set-auto-start', enabled), - setOverlayPersistent: (enabled: boolean): Promise => - ipcRenderer.invoke('settings:set-overlay-persistent', enabled), setDiagnosticLogging: (enabled: boolean): Promise => ipcRenderer.invoke('settings:set-diagnostic-logging', enabled), openRepository: (): Promise => ipcRenderer.invoke('settings:open-repository'), diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 4de6930..dffb497 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -8,19 +8,16 @@ declare global { openDebug(confirmed?: boolean): Promise; setPosition(position: Point, displayId: string): Promise; setPreset(preset: string, displayId: string): Promise; - previewOverlay(): Promise; beginPositioning(): Promise; endPositioning(): Promise; setShortcut(shortcut: string): Promise; setAutoStart(enabled: boolean): Promise; - setOverlayPersistent(enabled: boolean): Promise; setDiagnosticLogging(enabled: boolean): Promise; openRepository(): Promise; onState(listener: (state: SettingsViewState) => void): void; onLoginNotice(listener: (backendLabel: string) => void): void; }; meloOverlay: { - toggleRecording(): void; onState(listener: (state: string) => void): void; }; } diff --git a/src/renderer/overlay.css b/src/renderer/overlay.css index 703e463..ae3e56c 100644 --- a/src/renderer/overlay.css +++ b/src/renderer/overlay.css @@ -1,6 +1,6 @@ html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; } body { display: grid; place-items: center; } -.voice-overlay { position: relative; display: grid; place-items: center; width: 68px; height: 68px; padding: 0; border: 0; border-radius: 50%; background: radial-gradient(circle at 35% 28%, #71aaff, #2568de 70%); box-shadow: 0 8px 24px rgba(23,79,177,.35); cursor: pointer; } +.voice-overlay { position: relative; display: grid; place-items: center; width: 68px; height: 68px; border-radius: 50%; background: radial-gradient(circle at 35% 28%, #71aaff, #2568de 70%); box-shadow: 0 8px 24px rgba(23,79,177,.35); } .ring { position: absolute; inset: 0; border: 2px solid rgba(69,136,245,.45); border-radius: 50%; opacity: 0; } .voice-overlay[data-state="recording"] .ring { animation: ripple 1.7s ease-out infinite; } .voice-overlay[data-state="recording"] .ring.two { animation-delay: .55s; } diff --git a/src/renderer/overlay.html b/src/renderer/overlay.html index 4479504..0ea3cc1 100644 --- a/src/renderer/overlay.html +++ b/src/renderer/overlay.html @@ -6,10 +6,10 @@ - + diff --git a/src/renderer/overlay.ts b/src/renderer/overlay.ts index f36739f..e1ac841 100644 --- a/src/renderer/overlay.ts +++ b/src/renderer/overlay.ts @@ -1,7 +1,3 @@ const root = document.querySelector('.voice-overlay')!; -root.addEventListener('click', () => window.meloOverlay.toggleRecording()); -root.addEventListener('keydown', (event) => { - if (event.key === 'Enter' || event.key === ' ') window.meloOverlay.toggleRecording(); -}); window.meloOverlay.onState((state) => { root.dataset.state = state; }); diff --git a/src/renderer/settings.html b/src/renderer/settings.html index 850d47c..62b0a2b 100644 --- a/src/renderer/settings.html +++ b/src/renderer/settings.html @@ -18,7 +18,6 @@

悬浮球

-
diff --git a/src/renderer/settings.ts b/src/renderer/settings.ts index 2264b1c..62a2d5c 100644 --- a/src/renderer/settings.ts +++ b/src/renderer/settings.ts @@ -10,7 +10,6 @@ interface SettingsViewState { sessionState: 'idle' | 'starting' | 'recording' | 'stopping'; shortcut: string; autoStart: boolean; - overlayPersistent: boolean; diagnosticLogging: boolean; overlayPosition: { x: number; y: number }; displays: Array<{ id: string; label: string; workArea: { x: number; y: number; width: number; height: number } }>; @@ -26,7 +25,6 @@ const x = document.querySelector('#position-x')!; const y = document.querySelector('#position-y')!; const shortcut = document.querySelector('#shortcut')!; const autoStart = document.querySelector('#auto-start')!; -const overlayPersistent = document.querySelector('#overlay-persistent')!; const diagnosticLogging = document.querySelector('#diagnostic-logging')!; const version = document.querySelector('#version')!; const repository = document.querySelector('#repository')!; @@ -71,7 +69,6 @@ function render(next: SettingsViewState): void { renderPositionPreview(next); shortcut.textContent = capturingShortcut ? '请按下新快捷键…' : displayShortcut(next.shortcut); autoStart.checked = next.autoStart; - overlayPersistent.checked = next.overlayPersistent; diagnosticLogging.checked = next.diagnosticLogging; version.textContent = next.version; repository.textContent = next.repositoryUrl ?? '尚未配置'; @@ -197,9 +194,6 @@ shortcut.addEventListener('click', () => { shortcut.focus(); }); autoStart.addEventListener('change', () => void apply(() => window.meloSettings.setAutoStart(autoStart.checked))); -overlayPersistent.addEventListener('change', () => void apply( - () => window.meloSettings.setOverlayPersistent(overlayPersistent.checked) -)); diagnosticLogging.addEventListener('change', () => void apply( () => window.meloSettings.setDiagnosticLogging(diagnosticLogging.checked) )); diff --git a/src/shared/contracts.ts b/src/shared/contracts.ts index 1321797..2305ef3 100644 --- a/src/shared/contracts.ts +++ b/src/shared/contracts.ts @@ -11,7 +11,6 @@ export interface AppSettings { backendId: string; shortcut: string; autoStart: boolean; - overlayPersistent: boolean; diagnosticLogging: boolean; overlayPosition: Point | null; loginNoticeShown: Record; @@ -43,7 +42,6 @@ export interface SettingsViewState { sessionState: SessionState; shortcut: string; autoStart: boolean; - overlayPersistent: boolean; diagnosticLogging: boolean; overlayPosition: Point; displays: DisplayInfo[];