fix: 切换后销毁非活动后端

AI-assisted-by: Codex

AI-model: gpt-5.6-sol

AI-reasoning-effort: medium

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
2026-08-23 20:33:44 +08:00
co-authored by Codex
parent 4e58ca6d1d
commit e3759efa48
15 changed files with 47 additions and 15 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
## 项目范围
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.10`。
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.11`。
不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。
+6
View File
@@ -2,6 +2,12 @@
本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 的格式。
## [0.1.11] - 2026-08-23
### 变更
- 切换后端时销毁非当前后端的网页窗口,保留登录会话但不再保活其 renderer。
## [0.1.10] - 2026-08-23
### 修复
+1 -1
View File
@@ -2,7 +2,7 @@
## 项目范围
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.10`。
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.11`。
不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。
+1 -1
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(meloasr-fcitx5 VERSION 0.1.10 LANGUAGES CXX)
project(meloasr-fcitx5 VERSION 0.1.11 LANGUAGES CXX)
include(GNUInstallDirs)
include(CTest)
+1 -1
View File
@@ -6,7 +6,7 @@ Comment[zh_CN]=将 MeloASR 网页后端连接到当前输入上下文
Type=SharedLibrary
Library=@MELOASR_LIBRARY_PATH@
Category=Module
Version=0.1.10
Version=0.1.11
OnDemand=False
Configurable=False
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "meloasr",
"version": "0.1.10",
"version": "0.1.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meloasr",
"version": "0.1.10",
"version": "0.1.11",
"devDependencies": {
"@types/node": "^24.0.0",
"electron": "42.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "meloasr",
"productName": "MeloASR",
"version": "0.1.10",
"version": "0.1.11",
"private": true,
"author": "MeloASR contributors",
"description": "基于网页语音识别后端和 Fcitx5 的 Linux 实时语音输入工具",
+2 -2
View File
@@ -1,6 +1,6 @@
# Linux 分发打包
MeloASR 0.1.10 的系统包由两部分组成:
MeloASR 0.1.11 的系统包由两部分组成:
- Electron 应用安装到 `/opt/meloasr`,命令入口为 `/usr/bin/meloasr`;
- Fcitx5 常驻 addon 由目标发行版中的 CMake/Fcitx5 开发包编译,并安装到该环境报告的 addon 目录。
@@ -40,7 +40,7 @@ npm run build:linux:dir
packaging/deb/build-deb.sh
```
产物位于 `packaging/out/meloasr_0.1.10_<arch>.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。
产物位于 `packaging/out/meloasr_0.1.11_<arch>.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。
## rpm
+1 -1
View File
@@ -1,5 +1,5 @@
pkgname=meloasr
pkgver=0.1.10
pkgver=0.1.11
pkgrel=1
pkgdesc='Web-backed voice input for Fcitx5'
arch=('x86_64')
+1 -1
View File
@@ -1,6 +1,6 @@
# Arch Linux 构建
发布时把项目源码归档为 `meloasr-0.1.10.tar.gz`,与 `PKGBUILD` 放在同一目录,随后运行:
发布时把项目源码归档为 `meloasr-0.1.11.tar.gz`,与 `PKGBUILD` 放在同一目录,随后运行:
```bash
makepkg --syncdeps --cleanbuild
+1
View File
@@ -16,6 +16,7 @@
<binary>meloasr</binary>
</provides>
<releases>
<release version="0.1.11" date="2026-08-23"/>
<release version="0.1.10" date="2026-08-23"/>
<release version="0.1.9" date="2026-08-23"/>
<release version="0.1.8" date="2026-08-23"/>
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
version="${MELOASR_VERSION:-0.1.10}"
version="${MELOASR_VERSION:-0.1.11}"
arch="${MELOASR_DEB_ARCH:-$(dpkg --print-architecture)}"
app_dir="${1:-${MELOASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}"
work_dir="${project_dir}/packaging/out/deb-root"
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
version="${MELOASR_VERSION:-0.1.10}"
version="${MELOASR_VERSION:-0.1.11}"
app_dir="${1:-${MELOASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}"
stage_dir="${project_dir}/packaging/out/rpm-root"
top_dir="${project_dir}/packaging/out/rpmbuild"
+3
View File
@@ -36,6 +36,9 @@ tar -C %{buildroot} -xzf %{SOURCE0}
/etc/xdg/autostart/meloasr.desktop
%changelog
* Sun Aug 23 2026 MeloASR contributors - 0.1.11-1
- Release only the selected speech backend renderer.
* Sun Aug 23 2026 MeloASR contributors - 0.1.10-1
- Restore the Nix npm configuration hook before building the application.
+24 -2
View File
@@ -12,6 +12,7 @@ interface BackendRecord {
page: BrowserWindow;
windows: Set<BrowserWindow>;
exposed: boolean;
disposed: boolean;
status: BackendRuntimeStatus;
}
@@ -72,6 +73,7 @@ export class BackendManager extends EventEmitter {
page: root,
windows: new Set([root]),
exposed: false,
disposed: false,
status: loadingStatus(definition)
};
this.records.set(id, record);
@@ -89,7 +91,12 @@ export class BackendManager extends EventEmitter {
async select(id: string): Promise<void> {
requireBackend(id);
this.hideRecord(this.records.get(this.settings.get().backendId));
const previousId = this.settings.get().backendId;
if (previousId === id) {
await this.ensure(id);
return;
}
this.disposeRecord(previousId);
this.settings.update({ backendId: id });
await this.ensure(id);
this.emit('active-changed', id);
@@ -200,7 +207,7 @@ export class BackendManager extends EventEmitter {
private registerWindow(window: BrowserWindow, record: BackendRecord): void {
window.on('close', (event) => {
if ((app as typeof app & { isQuitting?: boolean }).isQuitting) return;
if (record.disposed || (app as typeof app & { isQuitting?: boolean }).isQuitting) return;
event.preventDefault();
this.hideRecord(record);
});
@@ -209,6 +216,10 @@ export class BackendManager extends EventEmitter {
overrideBrowserWindowOptions: { show: record.exposed, skipTaskbar: !record.exposed, focusable: true }
}));
window.webContents.on('did-create-window', (child) => {
if (record.disposed) {
child.destroy();
return;
}
record.windows.add(child);
child.on('closed', () => record.windows.delete(child));
this.registerWindow(child, record);
@@ -241,6 +252,17 @@ export class BackendManager extends EventEmitter {
for (const window of record.windows) this.parkWindow(window);
}
private disposeRecord(id: string): void {
const record = this.records.get(id);
if (!record) return;
record.disposed = true;
this.records.delete(id);
for (const window of [...record.windows]) {
if (!window.isDestroyed()) window.destroy();
}
record.windows.clear();
}
private parkWindow(window: BrowserWindow): void {
if (window.isDestroyed()) return;
window.hide();