feat(macos+flutter): 新增 Impeller 渲染引擎开关与 Intel Mac 渲染兼容修复
本次提交包含以下核心变更: 1. 修复 RawKeyboard 断言错误,添加 HardwareKeyboard 事件处理器 2. 实现 Intel Mac 自动降级玻璃渲染质量,避免黑屏闪烁 3. 新增 macOS 端 Impeller 渲染引擎开关设置,支持动态切换 4. 修复 macOS 双标题栏问题,隐藏系统原生交通灯按钮 5. 更新多语言国际化支持,新增 Impeller 相关翻译 6. 优化 WebRTC 依赖下载,使用国内镜像避免超时
This commit is contained in:
@@ -3,12 +3,14 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store</string>
|
||||
<string>app-store-connect</string>
|
||||
<key>teamID</key>
|
||||
<string>5V9NVUU6K5</string>
|
||||
<key>uploadSymbols</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>uploadBitcode</key>
|
||||
<false/>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -17,7 +17,7 @@ import flutter_app_group_directory
|
||||
import flutter_image_compress_macos
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_local_notifications
|
||||
import flutter_secure_storage_macos
|
||||
import flutter_secure_storage_darwin
|
||||
import flutter_tts
|
||||
import flutter_webrtc
|
||||
import gal
|
||||
@@ -56,7 +56,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
|
||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||
|
||||
@@ -35,6 +35,9 @@ target 'Runner' do
|
||||
end
|
||||
end
|
||||
|
||||
# WebRTC-SDK 通过国内镜像下载,避免 github.com 连接超时
|
||||
pod 'WebRTC-SDK', :podspec => 'WebRTC-SDK.podspec.json'
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
|
||||
@@ -36,7 +36,7 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- flutter_webrtc (1.4.0):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (= 144.7559.01)
|
||||
- WebRTC-SDK (= 144.7559.09)
|
||||
- FlutterMacOS (1.0.0)
|
||||
- gal (1.0.0):
|
||||
- Flutter
|
||||
@@ -58,7 +58,6 @@ PODS:
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- pro_image_editor (12.0.8):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- quill_native_bridge_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
@@ -91,7 +90,7 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (144.7559.01)
|
||||
- WebRTC-SDK (144.7559.09)
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
@@ -119,7 +118,7 @@ DEPENDENCIES:
|
||||
- nearby_service (from `Flutter/ephemeral/.symlinks/plugins/nearby_service/darwin`)
|
||||
- network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- pro_image_editor (from `Flutter/ephemeral/.symlinks/plugins/pro_image_editor/darwin`)
|
||||
- pro_image_editor (from `Flutter/ephemeral/.symlinks/plugins/pro_image_editor/macos`)
|
||||
- quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`)
|
||||
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
||||
- rive_native (from `Flutter/ephemeral/.symlinks/plugins/rive_native/macos`)
|
||||
@@ -133,6 +132,7 @@ DEPENDENCIES:
|
||||
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
|
||||
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
- WebRTC-SDK (from `WebRTC-SDK.podspec.json`)
|
||||
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -140,7 +140,6 @@ SPEC REPOS:
|
||||
- CwlCatchException
|
||||
- CwlCatchExceptionSupport
|
||||
- OrderedSet
|
||||
- WebRTC-SDK
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
@@ -190,7 +189,7 @@ EXTERNAL SOURCES:
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
pro_image_editor:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/pro_image_editor/darwin
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/pro_image_editor/macos
|
||||
quill_native_bridge_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos
|
||||
record_macos:
|
||||
@@ -217,6 +216,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
WebRTC-SDK:
|
||||
:podspec: WebRTC-SDK.podspec.json
|
||||
window_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
@@ -237,7 +238,7 @@ SPEC CHECKSUMS:
|
||||
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
|
||||
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||
flutter_tts: ae915565cc6948444b513acc8ee021993281e027
|
||||
flutter_webrtc: f1fb1a00c6080461bb2bc6caa5b93d07ddb68466
|
||||
flutter_webrtc: fec3dc9284a1f6d21ec2caa91c90bb4c1824eb04
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||
@@ -247,11 +248,11 @@ SPEC CHECKSUMS:
|
||||
network_info_plus: 21d1cd6a015ccb2fdff06a1fbfa88d54b4e92f61
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
pro_image_editor: e57a76b305fd8c2e06d57f92aec82b9c6a6e8d9f
|
||||
pro_image_editor: e4f2ca0bcf5d755d81620d531b00e0906ccaa0ae
|
||||
quill_native_bridge_macos: 2b005cb56902bb740e0cd9620aa399dfac6b4882
|
||||
record_macos: 5d55909f9650314be6424ffd6b123ac75a08c3c1
|
||||
rive_native: 7c43020540833c8258564b726317daf39d2c3bda
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
screen_retriever_macos: c5508cc3c66ff0d4db650480cf0ab691e220d933
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
speech_to_text: 3b313d98516d3d0406cea424782ec25470c59d19
|
||||
@@ -261,9 +262,9 @@ SPEC CHECKSUMS:
|
||||
video_compress: 752b161da855df2492dd1a8fa899743cc8fe9534
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
WebRTC-SDK: ab9b5319e458c2bfebdc92b3600740da35d5630d
|
||||
WebRTC-SDK: 4a9a5db10ced18b045ffe9e31a74db47eae9f2df
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: da7153fcdbf00f48b745928f1db268024c39f8e4
|
||||
PODFILE CHECKSUM: 8d56010d72ffd0e8cc2b1391c301e2d755b5f9d9
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -753,7 +753,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD)";
|
||||
ARCHS = "x86_64 arm64";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — macOS 应用代理
|
||||
/// 创建时间: 2026-06-02
|
||||
/// 更新时间: 2026-06-19
|
||||
/// 作用: 应用启动入口,注册应用级 MethodChannel(Dock 徽章/菜单栏金句/Spotlight 索引)
|
||||
/// 上次更新: 新增 NSStatusItem 菜单栏金句、NSDockTile 徽章、CoreSpotlight 索引能力
|
||||
/// 更新时间: 2026-06-25
|
||||
/// 作用: 应用启动入口,注册应用级 MethodChannel(Dock 徽章/菜单栏金句/Spotlight 索引/CPU 架构检测/Impeller 开关/应用重启)
|
||||
/// 上次更新: 1. 新增 getImpellerRunningStatus 方法,返回当前引擎实际运行的 Impeller 状态
|
||||
/// (区别于 getImpellerEnabled 返回的 UserDefaults 设置值)。
|
||||
/// 2. 改进 restartApp:添加日志、错误检查、延迟 800ms 终止确保新应用完全启动。
|
||||
/// ============================================================
|
||||
|
||||
import Cocoa
|
||||
@@ -29,22 +31,77 @@ class AppDelegate: FlutterAppDelegate {
|
||||
// MARK: - 应用生命周期
|
||||
// ============================================================
|
||||
|
||||
/// 应用启动完成:注册应用级 MethodChannel
|
||||
/// 应用启动完成(channel 已在 MainFlutterWindow.awakeFromNib 中提前注册)
|
||||
override func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
super.applicationDidFinishLaunching(notification)
|
||||
}
|
||||
|
||||
// 获取 FlutterViewController(窗口已在 NIB 加载时创建)
|
||||
guard let controller = NSApplication.shared.windows.first?.contentViewController as? FlutterViewController else {
|
||||
return
|
||||
}
|
||||
/// 注册应用级 MethodChannel(apps.xy.xianyan/macos.app)
|
||||
///
|
||||
/// 必须在 Flutter 引擎启动 Dart 代码前调用,否则 Dart 侧调用 getCpuArchitecture
|
||||
/// 等方法会触发 MissingPluginException。
|
||||
/// 由 MainFlutterWindow.awakeFromNib 在创建 FlutterViewController 后立即调用。
|
||||
static func registerAppChannel(controller: FlutterViewController) {
|
||||
let appDelegate = NSApp.delegate as? AppDelegate
|
||||
|
||||
methodChannel = FlutterMethodChannel(
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "apps.xy.xianyan/macos.app",
|
||||
binaryMessenger: controller.engine.binaryMessenger
|
||||
)
|
||||
|
||||
methodChannel?.setMethodCallHandler { [weak self] call, result in
|
||||
channel.setMethodCallHandler { call, result in
|
||||
switch call.method {
|
||||
// ---------- CPU 架构检测(用于玻璃质量降级) ----------
|
||||
case "getCpuArchitecture":
|
||||
let arch = getCpuArchitecture()
|
||||
result(arch)
|
||||
|
||||
// ---------- Impeller 渲染引擎开关 ----------
|
||||
// 用户可在设置中手动开启/关闭 Impeller,修改后需重启应用生效
|
||||
// 默认值:Apple Silicon 开启,Intel Mac 关闭(Metal 驱动有渲染资源累积 bug)
|
||||
|
||||
// 获取用户设置的 Impeller 开关状态(从 UserDefaults 读取,含默认值逻辑)
|
||||
// 注意:此值可能已被用户修改但尚未重启生效
|
||||
case "getImpellerEnabled":
|
||||
let enabled = getImpellerEnabledWithDefault()
|
||||
result(enabled)
|
||||
|
||||
// 获取当前引擎实际运行的 Impeller 状态
|
||||
// 此值在应用启动时固定(MainFlutterWindow.currentImpellerEnabled),
|
||||
// 不随用户设置改变,用于在 UI 中显示"当前实际渲染引擎"
|
||||
case "getImpellerRunningStatus":
|
||||
let running = MainFlutterWindow.currentImpellerEnabled
|
||||
result(running)
|
||||
|
||||
case "setImpellerEnabled":
|
||||
let args = call.arguments as? [String: Any]
|
||||
let enabled = args?["enabled"] as? Bool ?? false
|
||||
UserDefaults.standard.set(enabled, forKey: "xianyan_enable_impeller")
|
||||
result(nil)
|
||||
|
||||
// ---------- 应用重启 ----------
|
||||
// 用户修改 Impeller 开关后,可选择立即重启应用使设置生效
|
||||
// 改进:添加日志、错误检查、延迟终止确保新应用完全启动
|
||||
case "restartApp":
|
||||
let url = URL(fileURLWithPath: Bundle.main.bundlePath)
|
||||
let config = NSWorkspace.OpenConfiguration()
|
||||
config.activates = true
|
||||
NSLog("[restartApp] 正在启动新实例: \(url.path)")
|
||||
NSWorkspace.shared.openApplication(at: url, configuration: config) { newApp, error in
|
||||
if let error = error {
|
||||
NSLog("[restartApp] 启动新实例失败: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
NSLog("[restartApp] 新实例已启动,延迟 800ms 后终止当前实例")
|
||||
// 延迟 800ms 确保新应用完全启动后再终止当前应用
|
||||
// 避免新应用尚未完成初始化就被旧应用的终止流程影响
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
||||
NSLog("[restartApp] 终止当前实例")
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
}
|
||||
result(nil)
|
||||
|
||||
// ---------- Dock 徽章(NSDockTile) ----------
|
||||
case "setDockBadge":
|
||||
let args = call.arguments as? [String: Any]
|
||||
@@ -56,14 +113,14 @@ class AppDelegate: FlutterAppDelegate {
|
||||
case "updateStatusBarSentence":
|
||||
let args = call.arguments as? [String: Any]
|
||||
let sentence = args?["sentence"] as? String ?? ""
|
||||
self?.updateStatusItem(sentence: sentence)
|
||||
appDelegate?.updateStatusItem(sentence: sentence)
|
||||
result(nil)
|
||||
|
||||
// ---------- Spotlight 索引(CoreSpotlight) ----------
|
||||
case "indexSpotlightItems":
|
||||
let args = call.arguments as? [String: Any]
|
||||
let items = args?["items"] as? [[String: Any]] ?? []
|
||||
self?.indexSpotlightItems(items: items)
|
||||
appDelegate?.indexSpotlightItems(items: items)
|
||||
result(nil)
|
||||
|
||||
case "clearSpotlightIndex":
|
||||
@@ -97,7 +154,7 @@ class AppDelegate: FlutterAppDelegate {
|
||||
// ============================================================
|
||||
|
||||
/// 创建或更新菜单栏金句,截断到 30 字符显示
|
||||
private func updateStatusItem(sentence: String) {
|
||||
func updateStatusItem(sentence: String) {
|
||||
if statusItem == nil {
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
statusItem?.button?.target = self
|
||||
@@ -115,12 +172,50 @@ class AppDelegate: FlutterAppDelegate {
|
||||
NSPasteboard.general.setString(currentSentence, forType: .string)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MARK: - CPU 架构检测
|
||||
// ============================================================
|
||||
|
||||
/// 获取 CPU 架构标识,用于 Dart 端判断是否为 Intel Mac
|
||||
///
|
||||
/// 通过 `uname` 系统调用获取机器硬件标识:
|
||||
/// - `arm64` → Apple Silicon (M1/M2/M3+)
|
||||
/// - `x86_64` → Intel CPU
|
||||
///
|
||||
/// Dart 端通过 MethodChannel 调用此方法,在应用启动时缓存结果,
|
||||
/// 用于动态调整液态玻璃渲染质量(Intel Mac 降级避免黑屏闪烁)。
|
||||
static func getCpuArchitecture() -> String {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
return machineMirror.children.reduce("") { identifier, element in
|
||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取 Impeller 启用状态(含默认值逻辑)
|
||||
///
|
||||
/// 与 MainFlutterWindow.shouldEnableImpeller() 逻辑一致:
|
||||
/// - 用户已显式设置:返回用户设置的值
|
||||
/// - 未设置(首次启动):Apple Silicon 默认开启,Intel Mac 默认关闭
|
||||
///
|
||||
/// 此方法用于 Dart 端读取开关当前状态,确保 UI 显示与实际引擎行为一致。
|
||||
static func getImpellerEnabledWithDefault() -> Bool {
|
||||
let defaults = UserDefaults.standard
|
||||
if defaults.object(forKey: "xianyan_enable_impeller") != nil {
|
||||
return defaults.bool(forKey: "xianyan_enable_impeller")
|
||||
}
|
||||
// 未设置过:Apple Silicon 默认开启,Intel Mac 默认关闭
|
||||
return getCpuArchitecture() != "x86_64"
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MARK: - Spotlight 索引(CoreSpotlight)
|
||||
// ============================================================
|
||||
|
||||
/// 将条目索引到 Spotlight,支持点击搜索结果跳转
|
||||
private func indexSpotlightItems(items: [[String: Any]]) {
|
||||
func indexSpotlightItems(items: [[String: Any]]) {
|
||||
let searchableItems = items.map { item in
|
||||
let attributeSet = CSSearchableItemAttributeSet(itemContentType: "public.text")
|
||||
attributeSet.title = item["title"] as? String
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
<string>NSApplication</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>闲言需要访问您的相册以选择和保存壁纸图片。</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true/>
|
||||
<!-- FLTEnableImpeller 不在此声明,由原生代码根据用户设置动态控制 -->
|
||||
<!-- URL Scheme — 供 xianyan:// 深度链接跳转 -->
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — macOS 主窗口
|
||||
/// 创建时间: 2026-06-02
|
||||
/// 更新时间: 2026-06-24
|
||||
/// 作用: 主窗口初始化,注册 apps.xy.xianyan/macos MethodChannel
|
||||
/// 上次更新: 实现菜单栏语言切换 — 新增 menuItemIds 静态数组(XIB objectId 列表)、
|
||||
/// assignMenuItemIdentifiers(为每个 menuItem 设置 identifier)、
|
||||
/// setMenuLanguage(根据 languageId 加载对应 lproj/MainMenu.strings
|
||||
/// 并递归更新 NSApp.mainMenu 所有菜单项标题)。
|
||||
/// 修复软件内切换语言后系统菜单栏不跟随切换的问题。
|
||||
/// 同时保留先前修复:在 awakeFromNib 提前调用
|
||||
/// MainFlutterWindowManipulator.start(mainFlutterWindow: self)
|
||||
/// 避免 reset() 走 start(nil) 分支触发竞态崩溃;
|
||||
/// contentViewController 由 FlutterViewController 改为
|
||||
/// MacOSWindowUtilsViewController 包装,使原生侧 setMaterial/
|
||||
/// setNSVisualEffectViewState 等方法的类型转换成功。
|
||||
/// 通过 setExtraFlag 保留 fullSizeContentView + titlebarAppearsTransparent
|
||||
/// 实现标题栏融合(红黄绿灯由 Flutter 侧 DesktopWindowTitleBar 自绘)
|
||||
/// 更新时间: 2026-06-25
|
||||
/// 作用: 主窗口初始化,通过环境变量控制 Impeller 开关,
|
||||
/// 注册 apps.xy.xianyan/macos MethodChannel
|
||||
/// 上次更新: 1. 新增 currentImpellerEnabled 静态变量缓存当前引擎实际运行的 Impeller 状态,
|
||||
/// 区别于用户设置的值(可能已修改但未重启生效)。
|
||||
/// 2. 新增 getCurrentImpellerRunningStatus() 公开方法供 AppDelegate channel 调用。
|
||||
/// 3. 改用 setenv("FLUTTER_ENGINE_SWITCH_0") 环境变量方式传递 Impeller 开关,
|
||||
/// 默认值:Apple Silicon 开启,Intel Mac 关闭。
|
||||
/// 4. 修复"双标题栏"问题:提取 hideSystemTrafficButtons(of:) 公共方法,
|
||||
/// 在 awakeFromNib / setDarkMode / handleFullScreenExit 三个时机统一隐藏
|
||||
/// 系统红黄绿灯按钮,避免与 Flutter 自绘的 DesktopWindowTitleBar 重复显示。
|
||||
/// 5. 统一 Intel Mac 与 Apple Silicon 的窗口栏处理方案:
|
||||
/// 所有 Mac(含 Intel Mac)统一使用 MacOSWindowUtilsViewController 包装、
|
||||
/// 窗口背景透明(isOpaque/backgroundColor)、MainFlutterWindowManipulator.start()、
|
||||
/// 标题栏透明属性(titlebarAppearsTransparent/titleVisibility/fullSizeContentView)。
|
||||
/// 根因:此前 Intel Mac 跳过这些设置导致系统标题栏仍占据空间,
|
||||
/// 与 Flutter 自绘标题栏形成上下两栏。仅 Impeller 开关默认值保留差异化
|
||||
/// (Apple Silicon 默认开启,Intel Mac 默认关闭)。
|
||||
/// ============================================================
|
||||
|
||||
import Cocoa
|
||||
@@ -33,10 +36,79 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
/// Touch Bar 按钮配置 [{label: "加粗", action: "bold"}, ...]
|
||||
private var touchBarItems: [[String: String]] = []
|
||||
|
||||
/// 当前引擎实际运行的 Impeller 状态(应用启动时固定,不随用户设置改变)
|
||||
///
|
||||
/// 此值在 awakeFromNib 中通过 shouldEnableImpeller() 设置,
|
||||
/// 表示当前 Flutter 引擎实际是否在使用 Impeller 渲染。
|
||||
/// 与 UserDefaults 中的设置值不同:用户修改设置后,
|
||||
/// UserDefaults 返回的是修改后的值,但引擎实际运行的仍是启动时的值,
|
||||
/// 需重启应用才能让新设置生效。
|
||||
static var currentImpellerEnabled: Bool = false
|
||||
|
||||
// ============================================================
|
||||
// MARK: - CPU 架构检测
|
||||
// ============================================================
|
||||
|
||||
/// 检测是否为 Intel Mac(x86_64)
|
||||
///
|
||||
/// Intel Mac 的 GPU 驱动在透明窗口 + NSVisualEffectView 环境下
|
||||
/// 会出现渲染撕裂、字体割裂、闪烁等问题。
|
||||
/// 检测架构后,在 Intel Mac 上禁用透明窗口特效,使用标准窗口配置。
|
||||
private static func isIntelMac() -> Bool {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
let machine = machineMirror.children.reduce("") { identifier, element in
|
||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
return machine == "x86_64"
|
||||
}
|
||||
|
||||
/// 读取用户设置,判断是否启用 Impeller 渲染引擎
|
||||
///
|
||||
/// Impeller 是 Flutter 的新一代渲染引擎(基于 Metal),
|
||||
/// 在 Intel Mac 上可能出现渲染资源累积导致色差/字体割裂。
|
||||
///
|
||||
/// 默认值策略(用户未设置时):
|
||||
/// - Apple Silicon (arm64): 默认开启 — Impeller 在 Metal 驱动上性能更好
|
||||
/// - Intel Mac (x86_64): 默认关闭 — Metal 驱动有渲染资源累积 bug
|
||||
///
|
||||
/// 用户可在「通用设置 → 高级」中手动覆盖此设置,修改后需重启应用生效。
|
||||
///
|
||||
/// - Returns: 是否启用 Impeller
|
||||
private static func shouldEnableImpeller() -> Bool {
|
||||
let defaults = UserDefaults.standard
|
||||
// 检查用户是否已显式设置过此值
|
||||
// UserDefaults.bool 对未设置的 key 返回 false,无法区分"显式关闭"和"从未设置"
|
||||
// 因此用 object(forKey:) 判断 key 是否存在
|
||||
if defaults.object(forKey: "xianyan_enable_impeller") != nil {
|
||||
return defaults.bool(forKey: "xianyan_enable_impeller")
|
||||
}
|
||||
// 未设置过:Apple Silicon 默认开启,Intel Mac 默认关闭
|
||||
return !Self.isIntelMac()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
|
||||
// 通过环境变量传递 Impeller 开关(必须在 FlutterViewController 创建前设置)
|
||||
// Flutter 引擎在初始化时读取 FLUTTER_ENGINE_SWITCH_<N> 环境变量,
|
||||
// 将其值作为命令行参数传递给引擎(优先级高于 Info.plist 的 FLTEnableImpeller)
|
||||
// 默认值:Apple Silicon 开启(性能更好),Intel Mac 关闭(Metal 驱动有渲染资源累积 bug)
|
||||
let enableImpeller = Self.shouldEnableImpeller()
|
||||
let impellerArg = enableImpeller ? "--enable-impeller" : "--no-enable-impeller"
|
||||
setenv("FLUTTER_ENGINE_SWITCH_0", impellerArg, 1)
|
||||
|
||||
// 缓存当前引擎实际运行的 Impeller 状态
|
||||
// 此值在应用启动时固定,供 Dart 侧查询当前实际渲染引擎
|
||||
// 与 UserDefaults 中的设置值不同:用户修改设置后 UserDefaults 返回修改后的值,
|
||||
// 但引擎实际运行的仍是此缓存值,需重启应用才能让新设置生效
|
||||
Self.currentImpellerEnabled = enableImpeller
|
||||
|
||||
// 创建 FlutterViewController(其内部引擎会读取上方设置的环境变量)
|
||||
let flutterViewController = FlutterViewController()
|
||||
|
||||
// 关键修复:使用 MacOSWindowUtilsViewController 包装 FlutterViewController
|
||||
// macos_window_utils 插件的原生方法(setMaterial/setNSVisualEffectViewState/
|
||||
// addVisualEffectSubview 等)内部执行
|
||||
@@ -46,6 +118,9 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
// 'MacOSWindowUtilsViewController'" 崩溃。
|
||||
// MacOSWindowUtilsViewController.loadView() 会创建 NSVisualEffectView 作为
|
||||
// 根视图并 addChild(flutterViewController),是侧边栏毛玻璃等特效的载体。
|
||||
//
|
||||
// 所有 Mac(含 Intel Mac)统一使用此包装,提供 NSVisualEffectView 载体,
|
||||
// 实现 macOS 原生侧边栏毛玻璃等窗口特效。
|
||||
let macOSWindowUtilsViewController = MacOSWindowUtilsViewController(
|
||||
flutterViewController: flutterViewController
|
||||
)
|
||||
@@ -57,13 +132,20 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
// [NSWindow setBackgroundColor:] → NSThemeFrame._updateBackdropView → removeFromSuperview,
|
||||
// 与 Impeller raster 线程的 SetupRenderPass 产生竞态,导致空指针崩溃(Release 模式时序更紧)。
|
||||
// 修复:将不变属性前移到引擎启动前设置,Dart 侧不再在渲染期重复调用。
|
||||
self.isOpaque = false
|
||||
self.backgroundColor = NSColor.clear
|
||||
//
|
||||
// 标题栏透明属性(titlebarAppearsTransparent / titleVisibility / fullSizeContentView)
|
||||
// 所有 Mac 统一设置:让 Flutter 自绘的 DesktopWindowTitleBar 完全接管标题栏区域,
|
||||
// 避免系统标题栏残留形成"双标题栏"(系统标题栏 + Flutter 自绘标题栏)。
|
||||
//
|
||||
// 窗口背景透明(isOpaque / backgroundColor)所有 Mac 统一设置:
|
||||
// 配合 NSVisualEffectView 实现毛玻璃效果。
|
||||
self.titlebarAppearsTransparent = true
|
||||
self.titleVisibility = .hidden
|
||||
if !self.styleMask.contains(.fullSizeContentView) {
|
||||
self.styleMask.insert(.fullSizeContentView)
|
||||
}
|
||||
self.isOpaque = false
|
||||
self.backgroundColor = NSColor.clear
|
||||
|
||||
// 关键修复:提前调用 MainFlutterWindowManipulator.start(mainFlutterWindow: self)
|
||||
// 让 macos_window_utils 插件的 mainFlutterWindow 静态属性先被设置。
|
||||
@@ -82,22 +164,38 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
// 不会调用 configureMainFlutterWindow()(该方法会再次包装 contentViewController,
|
||||
// 但因已是 MacOSWindowUtilsViewController,重复包装会导致 FlutterViewController
|
||||
// 被嵌套两层,引发布局异常)。当前方案在上方已正确包装,无需 configure。
|
||||
//
|
||||
// 所有 Mac(含 Intel Mac)统一调用 start(),确保 macos_window_utils 插件
|
||||
// 的 mainFlutterWindow 静态属性被正确设置。
|
||||
MainFlutterWindowManipulator.start(mainFlutterWindow: self)
|
||||
|
||||
// start() 覆盖后重新设置透明属性(引擎启动前,无竞态风险)
|
||||
self.isOpaque = false
|
||||
self.backgroundColor = NSColor.clear
|
||||
// 所有 Mac 统一设置:start() 可能覆盖上述预设属性,需重新设置
|
||||
self.titlebarAppearsTransparent = true
|
||||
self.titleVisibility = .hidden
|
||||
if !self.styleMask.contains(.fullSizeContentView) {
|
||||
self.styleMask.insert(.fullSizeContentView)
|
||||
}
|
||||
self.isOpaque = false
|
||||
self.backgroundColor = NSColor.clear
|
||||
|
||||
// 关键修复:立即隐藏系统标题栏红黄绿灯按钮
|
||||
// Flutter 侧 DesktopWindowTitleBar 已自绘 macOS 风格的红黄绿按钮,
|
||||
// 若不在此处隐藏系统按钮,应用启动初期会同时显示两套按钮形成"双标题栏"。
|
||||
// 此前隐藏逻辑仅写在 setDarkMode 中,而 setDarkMode 有去重机制(仅主题变化时触发一次),
|
||||
// 导致启动时若主题未变化则系统按钮始终可见。
|
||||
// Intel Mac 同样需要隐藏(Flutter 自绘标题栏在所有桌面平台一致显示)
|
||||
hideSystemTrafficButtons(of: self)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
// 在 Flutter 引擎启动前注册自定义 MethodChannel,避免 MissingPluginException
|
||||
registerPlatformChannel(controller: flutterViewController)
|
||||
|
||||
// 注册应用级 MethodChannel(apps.xy.xianyan/macos.app)
|
||||
// 必须在引擎启动 Dart 代码前注册,否则 getCpuArchitecture 等方法会 MissingPluginException
|
||||
AppDelegate.registerAppChannel(controller: flutterViewController)
|
||||
|
||||
// 监听全屏退出:修复全屏退出后窗口背景变黑的问题
|
||||
// 全屏模式下 macOS 强制窗口不透明,退出后需重新设置背景透明
|
||||
NotificationCenter.default.addObserver(
|
||||
@@ -141,11 +239,15 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
@objc private func handleFullScreenExit(_ notification: Notification) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
// 重新设置窗口背景透明(让 NSVisualEffectView 透出)
|
||||
// 重新设置窗口透明属性(所有 Mac 统一处理)
|
||||
// 全屏模式下 macOS 强制窗口不透明并可能重置这些属性,退出后需重新设置
|
||||
self.titlebarAppearsTransparent = true
|
||||
self.isOpaque = false
|
||||
self.backgroundColor = NSColor.clear
|
||||
// 重新设置标题栏透明(由 Flutter 侧自绘标题栏)
|
||||
self.titlebarAppearsTransparent = true
|
||||
// 重新隐藏系统红黄绿灯按钮
|
||||
// 全屏模式下 macOS 可能重置 standardWindowButton 的可见性,
|
||||
// 退出全屏后需重新隐藏,避免与 Flutter 自绘标题栏重复显示
|
||||
self.hideSystemTrafficButtons(of: self)
|
||||
// 触发窗口重绘
|
||||
self.contentView?.needsDisplay = true
|
||||
// 通知 Dart 侧重新应用完整窗口特效(NSVisualEffectView 等)
|
||||
@@ -307,15 +409,35 @@ class MainFlutterWindow: NSWindow, NSTouchBarDelegate {
|
||||
window.appearance = appearance
|
||||
// 标题栏透明,由 Flutter 侧自绘标题栏
|
||||
window.titlebarAppearsTransparent = true
|
||||
// 隐藏系统标题栏按钮(红黄绿),由 Flutter 侧自绘
|
||||
window.standardWindowButton(.closeButton)?.isHidden = true
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
// 标题栏高度为 0,让内容延伸到顶部
|
||||
if let contentView = window.contentView {
|
||||
contentView.superview?.wantsLayer = true
|
||||
}
|
||||
}
|
||||
// 隐藏系统红黄绿灯按钮(提取为公共方法,统一三处调用点)
|
||||
// 原内联代码移至 hideSystemTrafficButtons(),避免重复
|
||||
hideSystemTrafficButtons()
|
||||
}
|
||||
|
||||
/// 隐藏系统标题栏的红黄绿按钮(closeButton/miniaturizeButton/zoomButton)
|
||||
///
|
||||
/// Flutter 侧 DesktopWindowTitleBar 已自绘 macOS 风格的红黄绿按钮,
|
||||
/// 系统原生按钮需隐藏避免重复显示形成"双标题栏"。
|
||||
///
|
||||
/// 调用时机:
|
||||
/// 1. `awakeFromNib` — 应用启动时立即隐藏(避免启动初期显示两套按钮)
|
||||
/// 2. `setDarkMode` — 主题切换时同步隐藏(兼容多窗口场景)
|
||||
/// 3. `handleFullScreenExit` — 全屏退出后重新隐藏(系统可能重置可见性)
|
||||
///
|
||||
/// - Parameter window: 指定窗口;nil 则遍历 `NSApplication.shared.windows`,
|
||||
/// 用于 setDarkMode 等多窗口场景
|
||||
private func hideSystemTrafficButtons(of window: NSWindow? = nil) {
|
||||
let windows = window.map { [$0] } ?? NSApplication.shared.windows
|
||||
for win in windows {
|
||||
win.standardWindowButton(.closeButton)?.isHidden = true
|
||||
win.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
win.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取系统当前外观(light / dark)
|
||||
|
||||
26
macos/WebRTC-SDK.podspec.json
Normal file
26
macos/WebRTC-SDK.podspec.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "WebRTC-SDK",
|
||||
"version": "144.7559.09",
|
||||
"summary": "WebRTC pre-compiled library for Darwin.",
|
||||
"description": "WebRTC pre-compiled library for Darwin.\nThe binary files in this repository are compiled using Google WebRTC source code M version,\nand a series of optimization patches from the webrtc-sdk community have been added.",
|
||||
"homepage": "https://github.com/webrtc-sdk/Specs",
|
||||
"license": {
|
||||
"type": "BSD",
|
||||
"file": "WebRTC.xcframework/LICENSE"
|
||||
},
|
||||
"authors": "webrtc-sdk",
|
||||
"platforms": {
|
||||
"ios": "13.0",
|
||||
"osx": "10.15",
|
||||
"tvos": "17.0",
|
||||
"visionos": "26.0"
|
||||
},
|
||||
"source": {
|
||||
"http": "https://ghfast.top/https://github.com/webrtc-sdk/Specs/releases/download/144.7559.09/WebRTC.xcframework.zip"
|
||||
},
|
||||
"vendored_frameworks": "WebRTC.xcframework",
|
||||
"pod_target_xcconfig": {
|
||||
"EXCLUDED_ARCHS[sdk=appletvsimulator*]": "x86_64",
|
||||
"EXCLUDED_ARCHS[sdk=xrsimulator*]": "x86_64"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user