Repository navigation
Conversation
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.
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.
ChouChiu
left a comment
There was a problem hiding this comment.
整体看过一遍 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(); |
There was a problem hiding this comment.
permission_denied 会在每次会话开始时被清掉。
restore_aec_runtime 用 aec_reference_availability() 重建状态,而它只会返回 ready 或 unsupported_os。所以 macOS tap 因 bundle 缺少 NSAudioCaptureUsageDescription 失败时(比如开发构建):
- 前端
useAecStatus把permission_denied当作能力原因,禁用开关并显示提示 - 下一次
SessionStarting时这里发出{available: true, reason: null},aecSupported翻回 true,提示消失,开关重新可用 - 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 }) => { |
There was a problem hiding this comment.
每次调用 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 那样在模块级只注册一次。
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>
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.
|
@WhatDamon @ChouChiu 我把 master 合进了本分支并解决了全部冲突,推送了合并提交,请两位 review & test。 这次推送是什么
|
| # | 决策 | 理由 |
|---|---|---|
| 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"。
Summary
NSColor.controlAccentColor获取强调色信息并应用NSDockTile显示在线客户端数Info.plist,顺便让 ORT 能够运行在 Intel Macresources/alsa(同样影响 Windows)