Skip to content

改进 2.x 对 macOS 的支持 - #350

Open
WhatDamon wants to merge 52 commits into
MicYou-Dev:masterfrom
WhatDamon:tauri-macos-optimize
Open

WhatDamon wants to merge 52 commits into
MicYou-Dev:masterfrom
WhatDamon:tauri-macos-optimize

Conversation

@WhatDamon

@WhatDamon WhatDamon commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  1. 窗口装饰调整:在 macOS 下使用系统原生窗口装饰,使用原生的红绿灯替代原来的窗口控制按钮,恢复了 macOS 窗口阴影
  2. 接管 macOS 顶部菜单栏:现在 macOS 的顶部菜单栏不再是 tauri 的默认内容
  3. 修复 macOS 强调色获取:采用 AppKit NSColor.controlAccentColor 获取强调色信息并应用
  4. 恢复 1.x 液态玻璃图标:复用原有资产,顺便调整了构建
  5. 托盘图标模板化:改用按 alpha 着色的 template 图而不是原有图标
  6. Dock 集成:借 NSDockTile 显示在线客户端数
  7. 打包改进:macOS 提供 Universal 包(Arm64 + x86_64),Close 能否提供 macOS 的 x86_64 版本? #339
  8. 统一最低系统要求:降级 ORT,使其最低要求变为 macOS 11,同时更改 Info.plist,顺便让 ORT 能够运行在 Intel Mac
  9. macOS AEC 远场参考:使用 CoreAudio Process Tap 实现(需要 macOS 14.2+)
  10. DMG 背景:引入了之前设计的 DMG 背景指引
  11. 袖珍模式修复:修复袖珍模式下无法按照预期弹出下拉菜单以及退出的问题(同样影响 Windows),Close [Bug]: 2.0.3切到紧凑模式之后,切不回来了。 #346
  12. 资源与构建一致性:macOS 打包不带 resources/alsa(同样影响 Windows)

由于这是一个超大单体 PR,部分修改也顺带解决了一些长期困扰 Windows 的问题,发布 Release 的时候注意不要只提及 macOS 支持改进
另外指出风险:ORT 降级不保证部分依赖它的插件能够在此版本工作

image image

The window event listener fires for every window the app creates, so
replaying the main window's layout geometry also moved the plugin panel
window's controls. Reuse the existing main-window lookup for the layout
command, and log a failed initial layout request instead of dropping it.
The macOS window override restated shadow: false from the base window
config, which maps to tao's with_has_shadow(false) and turns the native
window shadow off. That was invisible while the window was undecorated,
but it is now an obvious defect next to the native title bar, so enable
the shadow for macOS only.
The popup placement fed the physical values of outerPosition()/outerSize()
straight into APIs that take logical pixels (WebviewWindow options,
setPosition), so on a HiDPI display the coordinates were scaled by the backing
factor and the menu landed off screen -- e.g. a 469pt wide bar on a 2x display
put the 220pt popup at x=1430, ~138pt past the right edge.

Convert to logical units once, then clamp into the work area of the monitor the
bar sits on and flip above the bar when there is no room left below.
Point the documentation item at the project site, add an official-website
entry, and drop the copy-version item: the clipboard write ran on the
native-menu -> IPC path without the user activation WebKit requires, so it
silently did nothing.
Reuse the terms the file already uses elsewhere: 开日志所/出日志牍 from
the about section, 语言 from the language setting, 网页 from the web
strings; and restore the ellipsis on the settings item so it reads like
the other locales. 語 was also the only traditional character in the
menu section.
The menu section mixed classical renderings (尽显/噤声/闭窗/陈情) with
modern coinages (关于/设置/视图/窗口/撤销/重做/粘贴/全选/最小化/缩放/
全屏/语言/模式/监听/主页). Rewrite the modern ones in the same register,
keeping the terms the file already uses elsewhere (诸设 ← 复默认之设).
…e menu

The service menu now carries a 连接方式 submenu (wi-fi/usb/web) and a 网卡
submenu, both driving the same state the window uses: the mode is the
persisted connectionMode ref and the interface goes through selectIp, so
switching while streaming still asks for confirmation. 网卡 is greyed in
USB mode, where adb reverse makes the bind address meaningless, and lists
only 所有网卡 until the backend reports interfaces.
…the zh-hk menu

The lzh menu section had been classicalized with newly coined compounds. Where the
rest of the file already carries a classical rendering, reuse it instead: the
connection mode is 连接之法 (app.connectionMode), the network interface is 网接口
(换网接口 / 监听诸网接口), About is 关于, Settings is 设置, the service is 服务,
mute is 静之, monitoring is 监听, copy is 誊 (誊日志), Help is 览助 and the
language is 语言.

The zh-hk menu section was written in formal written Chinese while the rest of
that locale speaks Cantonese. Localize our range: 收埋 MicYou / 收埋其他 /
全部顯示 / 閂咗個視窗 / 縮到最細 / 放大縮細 / 全部揀晒 / 剪走 / 黐上 / 再做 /
開日誌資料夾 / 去贊助 / 使用說明, and reuse the wording the file already has for
the pocket mode (袋裝模式) and the connection mode (連線模式).
The badge mirrors the connected client count while a stream is active
(one device for Wi-Fi/USB, the web client count for Web) and is cleared
otherwise, so a hidden window still shows that MicYou is live.
The macOS 26 icon needs a compiled asset catalog, so the bundled .icns is
no longer enough. Compile the Icon Composer project with actool before
bundling, ship the resulting Assets.car next to the icns, and set
CFBundleIconName through a merged Info.plist.
The bundled runtime was arm64-only and needed macOS 14, which made the
11.0 deployment target impossible for Intel machines. Ship the
universal2 build of ONNX Runtime 1.19.2 (both slices target 11.0) and
stop pulling ort's default features, whose api-27 requirement rejected
any runtime older than 1.27.
Tauri only lipos the main binary, so the CLI and TUI sidecars have to be
built for both architectures and merged here before bundling, named
after the universal triple. The macOS CI jobs now build
universal-apple-darwin, and the release mirror matches the universal DMG
for both architectures.
AEC availability was decided in four separate places: the app crate's
`aec_supported()` returned `!cfg!(target_os = "macos")`, the GUI and the
audio-chain dialog each repeated `!isMacOS`, and the CLI/TUI had to call
back into the app crate to ask.

Move the decision into the audio library so every frontend reads the same
answer:

- `AecFailure` gains generic `UnsupportedOs` and `PermissionDenied` reasons.
- `AecAvailability` + `aec_reference_availability()` report whether this
  system can capture a far-end reference at all, detected at run time and
  cached for the process.
- `AudioDspSettings::normalize()` now downgrades a force-enabled
  `aec_enabled` when the platform cannot supply a reference, so settings
  loaded from disk can no longer run without echo cancellation.
- The new macOS module resolves Core Audio through `dlopen`/`dlsym` rather
  than linking it: a linked `AudioHardwareCreateProcessTap` would leave an
  unresolved symbol before macOS 14.2, and resolving the symbol doubles as
  the capability probe.
macOS had no usable speaker reference: MicYou's output device and the cpal
capture both bound BlackHole, so the "far end" was MicYou's own processed
microphone audio and AEC would have cancelled the user's voice instead of
the echo. The app therefore never wired the macOS capture path at all.

Capture the system playback instead, excluding MicYou's own process, with
AudioHardwareCreateProcessTap (macOS 14.2+). The tap feeds the same shared
ring buffer as the Windows and Linux paths through `push_to_buffer`, so
downmixing, resampling and the failure channel stay unchanged and only the
platform source differs.

Details that the capture depends on:

- The tap runs through a private aggregate device and stays unmuted, so the
  user still hears the original playback while AEC uses a copy of it.
- The Core Audio functions are resolved with `dlopen`/`dlsym`, which keeps
  older systems loadable and doubles as the capability probe.
- A bundle without `NSAudioCaptureUsageDescription` is rejected before the
  tap is created with `PermissionDenied`. macOS refuses the authorization
  request silently and then delivers silence, so the stream itself cannot
  distinguish "capturing nothing" from "not allowed to capture".
- Every resource (IOProc, aggregate device, tap, Objective-C description)
  is released on stop.
The transport layer decided AEC support for itself: `system.rs` only built the
far-end capture on Windows and Linux, `aec_supported()` duplicated the platform
policy inside the app crate, and the CLI and TUI reached back into the app crate
to ask about it.

The audio crate owns the answer now:

- The loopback capture is built unconditionally; the platform source is chosen
  inside the crate, so the transport no longer carries a platform branch for it.
- Restored AEC state reports the capability instead of assuming success, so an
  unsupported system is visible to every frontend before a session starts.
- `update_audio_settings` and the CLI reject an enable with the stable reason
  code (`unsupported_os`) instead of a hardcoded English sentence, and the new
  `get_aec_status` command lets the GUI read the same answer.
The GUI decided AEC support on its own: `SettingsDialog.vue` and
`AudioChainDialog.vue` each computed `!isMacOS`, which is now wrong twice
over - macOS 14.2+ can run AEC, and an unsupported system has a reason
worth showing.

Read the backend's answer instead, from one shared composable:

- `useAecStatus` fetches `get_aec_status` once and follows
  `aec-status-changed`, so every component sees the same state and the
  settings that used to keep a private copy no longer diverge.
- Capability reasons (`unsupported_os`, `permission_denied`) disable the
  toggle and explain themselves; runtime reasons such as `reference_lost`
  keep the toggle but report that the capture stopped.
- The bundle now declares `NSAudioCaptureUsageDescription`. macOS refuses
  the authorization request outright without it and then feeds the tap
  silence, so the capture would look healthy while producing nothing.
`resources/alsa/micyou-pipewire.conf` tells ALSA to route through the PipeWire
sink, which only exists on Linux: `pipewire::is_available()` returns false off
Linux and the only caller short-circuits on it, so `find_alsa_config` never runs
on macOS and the file shipped as dead weight.

Move it out of the shared resource list into the Linux platform config, which
also drops it from the Windows bundle. Note that resource globs are not
recursive, so `resources/*` copies files only and any future resource directory
has to be listed by the platforms that need it.
@micyou-automation micyou-automation Bot added processing 正在处理 size/xxl 1000+ 行 labels Sep 27, 2026
Two packaging improvements:

- `npm run tauri build` appends `--target universal-apple-darwin` on macOS
  unless the caller passes an explicit `--target`, so one download runs on both
  Apple Silicon and Intel. `dev` still builds for the host only and CI passes
  its target explicitly, so both keep their current behaviour.
- The DMG carries the installer artwork: a 660x400 background with the app at
  (180,180) and the Applications folder at (480,180), which is symmetric around
  the arrow the art draws between them.

The artwork is committed as SVG (source) plus the rendered PNG the bundler
needs. Only that one file is copied into the disk image, so a Retina `@2x`
variant is not possible without changing the bundler.
Finder sizes a disk-image background by its point size, which AppKit derives from
the pixels and the DPI tag (points = pixels / (dpi / 72)). A 660x400 image tagged
72dpi filled the window with only half the pixels a Retina display can show, and a
1320x800 image left untagged would have been read as 1320 points wide and overflowed.

The background is now rendered at 1320x800 and tagged 144dpi: the same 660x400
points of layout with twice the pixels behind it. Rendering lives in
build-dmg-background.js (wired into beforeBundleCommand) because the mistake is
invisible - a regenerated 1x image still fills the window, it just looks soft - and
the script asserts the pixel and DPI tags it wrote so that cannot slip through.
The header pill and the pocket bar carried Tailwind's grab/grabbing cursor to
signal that dragging them moves the window. macOS windows are dragged by the
titlebar without such an affordance, so both surfaces now keep the regular
pointer there while Windows and Linux keep the grab cursor.
The macOS accent came from `defaults read -g AppleAccentColor`. That key does not
exist while the accent is left at the default "Multicolor" choice, so on a stock Mac
the lookup produced nothing, the command answered with the unsupported fallback
(#5b7cfa) and the "system" theme mode silently ignored the system. The 0..=6 table
also reported Graphite as unsupported.

NSColor.controlAccentColor answers on every supported version and already resolves
Multicolor and Graphite to the colour macOS actually draws; the preference-key path
stays as a fallback. The frontend no longer trusts a value read once at startup
either: it re-reads the accent when the system theme mode is selected and when the
window regains focus, which is when the user returns from System Settings.
The tray used the coloured application icon, which sits next to the menu bar's own
monochrome items without following the bar's appearance. macOS wants a template image
there: it is drawn from the alpha channel alone and tinted by the system.

build-tray-icon.js renders that glyph from the same app_icon.svg the UI uses, whose
currentColor resolves to black in a standalone render, and crops the view box to the
artwork's measured bounds. The crop is what makes the size work out: the tray layer
gives every tray image a fixed 18pt height while ignoring the pixel count and any DPI
tag, so the raw canvas - holding a glyph that fills only ~66% of it - would have shown
the icon at roughly 12pt. 36px then supplies the 2x detail those 18 points need.
macOS shows an app's usage descriptions inside its permission prompts, and the app
only carried English text: on a Chinese system the System Audio Recording prompt
explained itself in English. The texts now ship as zh-Hans, zh-Hant and zh-HK
InfoPlist.strings, which bundle.macOS.files copies into Contents/Resources, since the
bundler copies directories.

Two prompts were missing their description entirely and so explained nothing: the
desktop app advertises its own mDNS service through mdns-sd, whose own multicast
sockets bypass the Bonjour API (hence no NSBonjourServices list), and it drives
Terminal through osascript.

cat, lzh and zh-ss deliberately keep the English text - they have no macOS language
code, with cat being the meow locale - so they fall back to Info.plist. A test pins
every InfoPlist.strings key against Info.plist, because a mismatch only drops the
localization silently.
yamllint's defaults flag the workflows that were authored upstream: 4-space step
indentation, long single-line commands and CRLF in the release mirrors. Those are
style rules, so they are turned off here rather than reformatting release pipelines.
Everything that guards correctness - duplicated keys, colons, commas, brackets and
syntax errors - stays enabled.
The layout command returned the geometry it had just applied, but the frontend
never read it: the header room is reserved in the stylesheet, which is where the
safe area actually has to stay in step. Returning it meant a serialized payload
whose safe_area field had no consumer at all, so the command now reports only
whether the placement could be scheduled.

Mode::parse fell back to the full layout without a word, which hides a
frontend/backend disagreement about the layout names; it logs one now. The
placement also refuses to run when AppKit reports an unusable button pitch
instead of stacking all three controls, because the idempotency check would then
keep them stacked until the next resize.
The tray and the app menu share one global menu listener, so every click logged a
warning from the other handler. That crossover is expected rather than a mistake,
so both are debug output now.

findInvalidMenuNode also accepted a top-level item, which the menu bar cannot
hold: the backend rejects the whole menu instead of skipping that entry, so the
check reports it as invalid instead.
useAecStatus kept an unlisten handle and exported a disposer for it, but nothing
calls the disposer and the singleton is never torn down, so the handle only
existed to be dropped. A rejected listener registration was swallowed as well and
is logged now.

PocketLayout tracked whether an overlay window had been created even though every
entry point resets the handle first, which made the reuse branch unreachable and
the field write-only. The path that clamps the popup into the monitor bounds
swallowed its failures without a word.
actool was invoked with a hardcoded 10.13 while the bundle declares
bundle.macOS.minimumSystemVersion 11.0 and every binary inside it is built for
11.0, so the committed catalog had been compiled for a system the app cannot run
on at all. The floor is now read from that config key instead of being repeated
in the script, and the committed Assets.car is regenerated with it.

The floor does change the output rather than only its metadata: 10.13 produced a
1867353 byte catalog and 11.0 produces 1861973 bytes, with the same 24 entries
and 9 renditions. actool remains unreproducible run to run, which is why the
script still skips regeneration unless the project is newer or --force is passed.
@WhatDamon
WhatDamon marked this pull request as ready for review September 27, 2026 13:10
@micyou-automation micyou-automation Bot added waitmerge 等待合并 and removed processing 正在处理 labels Sep 27, 2026

@ChouChiu ChouChiu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整体看过一遍 diff,发现 2 个问题,详见行内评论。已确认没有问题的部分:universal 构建的 CI 产物命名与 mirrorchyan 匹配;PocketLayout 浮层复用同一 label 的行为与原先一致;macOS 原生关闭按钮经前端走隐藏/退出流程,exit_app 不会再触发 CloseRequested,不会死循环;app 菜单与托盘的菜单 id 靠前缀区分,互不干扰。以上均为静态审查,未编译运行。

*runtime_available = true;
// A platform without a reference source can never become available, so the
// restored state reports the capability instead of assuming success.
let availability = micyou_audio::aec_reference_availability();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

permission_denied 会在每次会话开始时被清掉。

restore_aec_runtime 用 aec_reference_availability() 重建状态,而它只会返回 ready 或 unsupported_os。所以 macOS tap 因 bundle 缺少 NSAudioCaptureUsageDescription 失败时(比如开发构建):

  1. 前端 useAecStatus 把 permission_denied 当作能力原因,禁用开关并显示提示
  2. 下一次 SessionStarting 时这里发出 {available: true, reason: null},aecSupported 翻回 true,提示消失,开关重新可用
  3. loopback 重试后再次失败,又弹一次 "AEC 已禁用" 通知,之后每个会话都如此

重载应用后 get_aec_status 也不会报告这个原因。缺少 Info.plist key 是 bundle 的固定属性,建议把 bundle_declares_audio_capture() 的检查并入有缓存的 aec_reference_availability(),让它直接返回 permission_denied。

if (mode === 'system') void refreshSystemAccent();
});
void getCurrentWindow()
.onFocusChanged(({ payload: focused }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

每次调用 useTheme() 都会注册一个 themeMode watcher 和一个 onFocusChanged 监听,且监听从不注销。

useTheme() 在 App.vue、SettingsDialog.vue、CustomCssDialog.vue、FloatingWindow.vue 都有调用,所以主窗口每次获得焦点,refreshSystemAccent 至少并发执行 3 次:每次都重置 systemAccentInitialized 并调用 get_system_accent_color(Linux 上每次都会起 gsettings 进程),几个结果竞争写 systemAccentHex。切到 system 主题时同理。

建议像 useAecStatus 那样在模块级只注册一次。

ChouChiu and others added 4 commits September 30, 2026 20:32
The missing NSAudioCaptureUsageDescription check only surfaced as a runtime
failure, which restore_aec_runtime reset at the start of every session, so the
UI flipped back to available and AEC retried and failed again each session.
It is fixed for the life of the process, so aec_reference_availability now
reports permission_denied directly.

Also register the system accent watcher and focus listener once per window
instead of once per useTheme() caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master extracted the Tauri-free micyou-core crate (MicYou-Dev#352) and deleted
src-tauri/src/lib.rs and commands/system.rs, so this branch's changes to
those files are moved to where the code now lives:

- AEC availability: micyou-core settings and audio pipeline use
  aec_reference_availability() instead of the removed aec_supported(); the
  loopback capture is created on every platform
- AppKit accent colour: micyou-core platform/accent.rs, with classes looked
  up via Class::get so the CLI/TUI (no AppKit) fall back to the defaults key
- macOS window layout, Dock badge, app menu commands: src-tauri window.rs
- close interception, app menu forwarding, initial layout: src-tauri app.rs
- Info.plist localization test: src-tauri app.rs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@micyou-automation micyou-automation Bot added reviewing 正在复核 and removed waitmerge 等待合并 labels Oct 2, 2026
ChouChiu and others added 3 commits October 2, 2026 21:23
Master reworked the desktop windows around a self-drawn frameless shell
(MicYou-Dev#353). This branch keeps native traffic lights on macOS on top of it:

- pocket mode: master's in-window menus replace the overlay windows; the
  macOS bar keeps the spacer for the native controls instead of drawing
  its own minimize/close buttons
- settings now open in their own window, so the macOS menu's About and
  Sponsors entries pass the request through the micyou_settings_request
  localStorage key, and its language entries switch the locale directly
- the pocket window still grows to 800x600 while an in-window dialog is
  open, now through master's fixed-size helpers
- system accent: master's focus refresh replaces this branch's listener;
  switching the theme to "system" still re-reads the accent right away
- backend: drop the removed floating window commands, keep the macOS
  window layout, Dock badge and app menu commands next to the KWin effects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master switched the frontend package manager to bun (MicYou-Dev#354). The macOS
helper scripts this branch adds (tray icon, macOS icon, DMG background)
now run through bun in package.json and the Tauri before*Command hooks,
and the macOS packaging notes in AGENTS.md use bun commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve 26 conflicts per maintainer review: keep master's changes; drop universal packaging and ort downgrade; layer macOS Process Tap with BlackHole fallback; port PR frontend intents into src/platform + features.
@OrientCOMPASS

Copy link
Copy Markdown
Contributor

@WhatDamon @ChouChiu 我把 master 合进了本分支并解决了全部冲突,推送了合并提交,请两位 review & test。

这次推送是什么

  • 正统 merge commit:双亲 c446a18(本分支原 51 个提交的 HEAD,全部保留、未改写)+ d821da0(master v2.1.1)。fast-forward 推送,随时可 git revert -m 1 整体撤销。
  • 相对 master 净变更:66 文件,+3834 / −124(22 新增、44 修改、0 删除),即 PR 页面 Files changed 现在显示的内容。
  • 版本号四处(tauri.conf.json / gradle.properties / src-tauri/Cargo.toml / package.json)均保持 2.1.1,未 bump。
  • 26 处冲突全部解决,全仓库无冲突标记残留。解决原则与逐项决策见文末折叠区。

⚠️ 请先看:静态复查发现一处 macOS 回落的接线错误(建议先修再验)

macos_reference_thread(loopback.rs)用 aec_reference_availability().available 决定走 Process Tap 还是 BlackHole,但 detect_reference_availability()(aec.rs)在 macOS < 14.2 时返回 ready()(available = true,语义是"AEC 整体可用,经 BlackHole")。两边语义没对齐,导致:

  1. macOS 11–14.1:调度进 tap 路径 → CoreAudioApi::load() 失败 → 报 UnsupportedOs,BlackHole 回落永远不会执行——相对 master(直接 BlackHole 采集)是回归,也违背本次合并"老系统保住 AEC"的核心承诺;
  2. 14.2+ 且 bundle 未声明 NSAudioCaptureUsageDescription(如开发构建)但装有 BlackHole:同样进 tap 路径 → PermissionDenied,不回落——与 detect 里自己的注释("BlackHole still works, so only report this when there is nothing to fall back to")矛盾;
  3. else(BlackHole)分支唯一可达场景是 permission_denied = 未声明 且没有 BlackHole——恰好在无 BlackHole 时才去试 BlackHole。

建议的最小修复(process_tap_available / bundle_declares_audio_capture 均已是 pub(crate),只改 loopback.rs 这一处条件):

if crate::macos_tap::process_tap_available()
    && crate::macos_tap::bundle_declares_audio_capture()
{
    crate::macos_tap::capture_thread(active, buffer, failure);
} else {
    log::info!("[Loopback] Process tap unavailable; capturing from BlackHole instead");
    cpal_capture_thread(active, buffer, failure);
}

detect/availability 保持不动:它继续负责 UI 层"AEC 整体是否可用"(探测结果经 OnceLock 按进程缓存,不被会话重置),调度层只关心"tap 此刻能不能真的采到"。修复后三个场景自洽:<14.2 → BlackHole(未装则运行时报可重试的 VirtualSourceMissing);14.2+ 未声明 → BlackHole 回落;14.2+ 已声明 → tap。

方案认可的话,我可以把它作为 follow-up commit 推上来,@WhatDamon 也可以直接在自己分支提交,都行。

验证状态

受限环境(2C/1G Linux 沙盒)已验证:cargo check(micyou-audio/core/cli/tui,0 warning)、cargo test -p micyou-audio 33 passed(含 PureVox6/AEC7 golden 位精确测试,#366 承诺未破坏)、vue-tsc --noEmit(顺手修掉 3 个真实类型错误)、vite build(2812 模块)、cargo metadata --locked、7 语言 locale 完整性与 key 对齐、前端 71 命令 / 18 事件与 Rust 注册双向核对、Cargo.lock 相对 master 仅 3 行新增(零 ort)、全仓库无冲突标记。

沙盒验证不了、依赖 CI / 真机的:全部 #[cfg(target_os = "macos")] 路径(macos_tap.rs 693 行、menubar、dock、accent)、cargo check -p micyou-desktop、clippy 全绿(micyou-infer/ops.rs 的 approximate_constant 是 master 既有问题,CI 无 clippy 步骤)、gradle。

CI(e8c83d5 上):build-android ✅;build-tauri-macos(arm64 / x86_64)、build-tauri-windows、build-tauri-linux 进行中——macOS 双架构 job 正好覆盖上面编译不了的部分,编译面问题 CI 能兜住;行为面(尤其上面那个回落问题)需要 @WhatDamon 真机过一遍验收清单(11–14.1 BlackHole 回落排第一)。

@ChouChiu 请对照合并版复核你 9/30 的两条行内意见

  1. system.rs:344(permission_denied 被会话恢复清掉):合并版里 system.rs 未改(= master 版),restore_aec_runtime 落位 audio_pipeline.rs,不再硬编码 available: true,改为读能力探测上报 available / enabled && available / reason。你担心的场景(14.2+ 未声明 + 无 BlackHole)下探测返回 permission_denied,按进程缓存,会话恢复会重现而非清掉它;运行时的临时失败(设备热插拔等)仍按 master 的 AecFailure 每次会话上报、可重试。请看是否解决,以及上面那个接线问题是否影响你的结论。
  2. useTheme.ts:285(watcher / focus 监听重复注册且从不注销):合并版采用 master 的结构,唯一改动是把 themeMode watcher 的注册放进 initializeSystemAccent()——它带 systemAccentInitialized 一次性守卫,多个 useTheme() 调用点(App.vue / SettingsDialog / CustomCssDialog / FloatingWindow)不会再重复注册;focus 监听是 master 原有的 DOM addEventListener,同一守卫内注册一次。监听器随窗口生命周期存活、不随 unmount 注销——若你认为仍需可注销语义,可以再议。

其他说明

  • universal 包与 ort 降级已按维护组意见移除(与 master 的 micyou-infer 冲突):CI 保持 arm64 + x86_64 双产物,能否提供 macOS 的 x86_64 版本? #339 由独立 x86_64 DMG 覆盖;Info.plist、三语 InfoPlist.strings、minimumSystemVersion: "11.0" 与这些无关,已保留。AGENTS.md 的 macOS 打包段已同步改写。
  • master 遗留的 24 MB 死文件 tauri-app/src-tauri/resources/libonnxruntime.so(feat: 重新支持 Intel Mac,纯 Rust VM 替代 ort/ONNX Runtime——PureVox6/AEC7 权重编译期内嵌 #366 后无引用,却仍被 resources/* 打进所有平台安装包)本次刻意未动——它是 master 自己的问题,混进本 PR 会模糊边界,建议另开小 PR 删除。
  • 对任何一处冲突解决有异议,直接回复指出,可单独调整或局部回退;完整执行报告(逐项决策 + 验证明细 + 真机验收清单)需要的话我贴出来。
冲突解决原则与逐项决策(点击展开)

总原则:master 这 8 天的全部修改保留;PR 中与 master 冲突的 universal 打包与 ort 依赖移除;PR 的意图(macOS 原生装饰 / 菜单栏 / Dock / 强调色 / Process Tap AEC / 袖珍模式修复等)重新落位到 master 的新结构上。

# 决策 理由
1 loopback.rs 整体取 master 版,只叠加 PR 的 4 处意图(TARGET_RATE/lock/set_failure/push_to_buffer 放宽为 pub(crate))+ 新增 macos_reference_thread 调度器与 blackhole_device_present() PR 版基于 ringbuf 0.3 / wasapi 0.16,在 master 的 ringbuf 0.5 / wasapi 0.25 下编译不过;且 PR 原本删掉 cpal 采集会让 macOS 11–14.1 彻底失去 AEC
2 aec.rs:master 的失败码枚举(去 ModelMissing,权重已 include_bytes! 内嵌)+ PR 新增的 PermissionDenied / UnsupportedOs + PR 的能力探测层 aec_reference_availability();detect_reference_availability() 的 macOS 分支重写:<14.2 返回 ready() 而非 unsupported(),避免被 enforce_aec_availability() 永久禁用 AEC 两套模型合流;"驱动是否安装"属运行时状态,保持可重试
3 audio_pipeline.rs 以 master 重写版(526 行重构)为骨架,精确落 PR 的 3 处意图:restore_aec_runtime 读能力探测;loopback 改为全平台建立(保留 master 的 Option 外壳只改构造,if let Some(lb) 等代码零改动);aec_runtime_available 初值来自探测 macOS 拿到远端参考正是 PR 要填的缺口,改动面压到最小
4 前端 4 处违规改造:useWindow / useAecStatus / useAppMenu / App.vue 全部改走 src/platform(command / onEvent),文件落位 master 的 features/* 新址,MenuNode / PredefinedName 移入 platform/types.ts master 规定只有 src/platform/ 能 import @tauri-apps/*(基线 0 违规),PR 的写法破坏该约束
5 SettingsWindow.vue(master 已 2834→288 行拆分)无法逐块合并,PR 意图重新落位:AEC 可用性判定委托 useAecStatus() 单例(顺带消除 master 重复的两套 AEC 状态);permission_denied 专属文案进 AudioSection.vue;菜单跳转经新建 useSettingsRequest.ts;语言切换加跨窗口 storage 同步 意图等价迁移,不复活旧巨型文件
6 app.rs:保留 PR 新增的 get_aec_status(前端依赖),丢弃 server_prefs_exists 注册 该函数 master 已删除,保留即编译失败
7 tailwind 3 → 4 类名跟随 master:flex-shrink-0 → shrink-0 等 master 已迁移 tailwind 4

按维护组意见移除(含 git 自动合并进来的部分,做了全仓库扫描):ort = "=2.0.0-rc.13" 及连带的 ndarray、未使用的 micyou-protocol 依赖;prepare-sidecars.js 回到 master 63 行版(丢弃双 target 循环 + lipo + universal2 校验);run-tauri.js 去掉 --target universal-apple-darwin 注入;4 个 workflow 恢复 arm64/x86_64 双产物 matrix 与 *-arm64.dmg / *-x86_64.dmg 命名(已核对 PR 对这 4 个文件的改动全部是 universal 相关,无误删)。

保留(与 ort 无关):Info.plist、三语 InfoPlist.strings、minimumSystemVersion: "11.0"。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reviewing 正在复核 size/xxl 1000+ 行

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 2.0.3切到紧凑模式之后,切不回来了。 能否提供 macOS 的 x86_64 版本?

3 participants