diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8afead3..148ed86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -165,6 +165,68 @@ jobs: LanMountainDesktop/bin/Release/ retention-days: 7 + build-android: + runs-on: ubuntu-latest + name: Build_Android + # Android head 尚未合入时允许跳过 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + + - name: Check Android head exists + id: check + run: | + if [ -f "LanMountainDesktop.Mobile.Android/LanMountainDesktop.Mobile.Android.csproj" ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup .NET + if: steps.check.outputs.exists == 'true' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-quality: 'preview' + + - name: Setup Java + if: steps.check.outputs.exists == 'true' + uses: actions/setup-java@v4 + with: + distribution: 'microsoft' + java-version: '17' + + - name: Install Android workload + if: steps.check.outputs.exists == 'true' + run: dotnet workload install android + + - name: Cache NuGet packages + if: steps.check.outputs.exists == 'true' + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/.nuget/packages + key: ${{ runner.os }}-nuget-android-${{ hashFiles('**/Directory.Packages.props') }} + restore-keys: | + ${{ runner.os }}-nuget-android- + ${{ runner.os }}-nuget- + + - name: Build Android head + if: steps.check.outputs.exists == 'true' + run: dotnet build LanMountainDesktop.Mobile.Android/LanMountainDesktop.Mobile.Android.csproj -c Release -v minimal + + - name: Upload artifacts + if: steps.check.outputs.exists == 'true' + uses: actions/upload-artifact@v7 + with: + name: build-android + path: | + LanMountainDesktop.Mobile.Android/bin/Release/**/*.apk + if-no-files-found: warn + retention-days: 7 + pack-plugin-packages: runs-on: ubuntu-latest name: Pack_Plugin_Packages diff --git a/AGENTS.md b/AGENTS.md index 792e090..441e85b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,11 @@ - `LanMountainDesktop/Views/` and `ViewModels/`: UI 页面、窗口与视图模型 - `LanMountainDesktop/Services/`: 设置、遥测、启动、持久化、业务服务 - `LanMountainDesktop.PluginSdk/`: 插件 SDK 公共接口和默认打包行为 +- `LanMountainDesktop.Platform.Abstractions/`: 平台差异层接口(跨平台契约,禁止平台专属 API) +- `LanMountainDesktop.Platform.Windows/`: Windows 平台实现(P/Invoke、桌面层、点击穿透等) +- `LanMountainDesktop.Platform.Android/`: Android 平台实现 +- `LanMountainDesktop.Mobile/`: 共享移动 UI 壳(组件面板) +- `LanMountainDesktop.Mobile.Android/`: Android head(入口) - `LanMountainDesktop.Shared.Contracts/`: 宿主/插件共享契约 - `LanMountainDesktop.Tests/`: 宿主与 SDK 测试 - `.trae/specs/`: feature 级规格、任务拆解和验收清单 @@ -91,6 +96,7 @@ dotnet test LanMountainDesktop.slnx -c Debug - 视觉规范:`docs/VISUAL_SPEC.md` - 圆角规范:`docs/CORNER_RADIUS_SPEC.md` - 生态边界:`docs/ECOSYSTEM_BOUNDARIES.md` +- 跨平台架构:`docs/CROSS_PLATFORM.md` - SDK v5 迁移:`docs/PLUGIN_SDK_V5_MIGRATION.md` 如果多个文档都提到同一件事,以 `docs/ai/DOC_SOURCES.md` 列出的权威来源为准。 diff --git a/docs/CROSS_PLATFORM.md b/docs/CROSS_PLATFORM.md new file mode 100644 index 0000000..7f7bf26 --- /dev/null +++ b/docs/CROSS_PLATFORM.md @@ -0,0 +1,92 @@ +# 跨平台架构说明 + +> 本文档描述阑山桌面的跨平台(桌面 + 移动)架构方案与分层约束。 +> 当前目标平台:Windows(主力)、Linux/macOS(桌面构建)、Android(移动端)。iOS 暂不在范围内。 + +## 1. 总体分层 + +``` +┌─────────────────────────────────────────────────────┐ +│ Heads(平台入口) │ +│ LanMountainDesktop(WinExe 桌面宿主) │ +│ LanMountainDesktop.Mobile.Android(Android head) │ +├─────────────────────────────────────────────────────┤ +│ 共享 UI 层 │ +│ LanMountainDesktop.Mobile(移动壳/组件面板) │ +│ ComponentSystem / Controls / Theme(随桌面宿主) │ +├─────────────────────────────────────────────────────┤ +│ 平台差异层(Platform) │ +│ LanMountainDesktop.Platform.Abstractions(接口) │ +│ LanMountainDesktop.Platform.Windows(Windows 实现) │ +│ (后续按需增加 Platform.Android / Platform.MacOS) │ +├─────────────────────────────────────────────────────┤ +│ 共享核心层(必须无平台依赖) │ +│ Shared.Contracts / Shared.IPC / Settings.Core │ +│ Appearance / DesktopComponents.Runtime / PluginSdk │ +│ Host.Abstractions / PluginIsolation.Contracts │ +└─────────────────────────────────────────────────────┘ +``` + +## 1.1 当前落地状态 + +- ✅ `Platform.Abstractions`:接口 + NoOp 实现 + `PlatformLog` 日志桥 +- ✅ `Platform.Windows`:电源管理、原生对话框、桌面层嵌入、窗口置底/区域穿透、 + DWM 互操作、图标服务(WindowsIconService/UwpManifestIconResolver)、包标识查询 +- ✅ `Platform.MacOS`:MacIconService +- ✅ `Platform.Android`:骨架(后续按需填充) +- ✅ `Mobile` + `Mobile.Android`:单视图组件面板壳,APK 可构建 +- ✅ 主工程 `LanMountainDesktop` 已无任何 DllImport/LibraryImport +- ✅ PluginIsolation.Ipc 新增 `InProcPluginIpcTransport`(进程内直通,含测试) + +主工程保留的平台相关内容(有意为之): +- `WindowsNotificationListener` / `WindowsSmtcMusicControlService` / `LocationService` + 的 WinRT **反射**调用路径(无编译期平台依赖,运行时 `OperatingSystem.IsWindows()` 保护), + P/Invoke 部分已抽到 `Platform.Windows.WindowsPackageIdentity`。 +- `LinuxPowerManagementService`(纯命令行调用,无平台 API)。 +- 静态工厂门面(`*ServiceFactory`)留在 `LanMountainDesktop.Services` 命名空间, + 保持既有调用点不变,内部委托平台实现。 + +## 2. Platform 层规则 + +- 所有 P/Invoke、`DllImport`/`LibraryImport`、Windows 注册表、COM/Office 互操作等平台专属代码, + 一律放入 `LanMountainDesktop.Platform.<平台>` 项目,禁止出现在主工程与共享层。 +- `Platform.Abstractions` 只包含接口、跨平台 DTO 与 Null/NoOp 兜底实现。 +- Head 项目在启动时负责注册对应平台实现(桌面注册 Windows 实现,Android 注册移动实现或 NoOp)。 + +## 3. 插件体系的平台策略 + +| 能力 | 桌面 | Android | +|---|---|---| +| 插件契约(PluginSdk / PluginIsolation.Contracts) | 保留 | 保留(不变) | +| 进程内加载(AssemblyLoadContext) | 保留 | 保留 | +| 进程外隔离(命名管道 + AirAppHost 子进程) | 保留 | 不可用,降级为进程内 | +| IPC 传输 | 命名管道(dotnetCampus.Ipc) | 进程内直通(InProc transport,同一契约) | +| AirApp 运行时(独立进程) | 保留 | 第一版不提供 | + +插件代码不感知传输差异:同一套 `PluginIsolation` 契约在桌面走管道,在移动端走进程内直通。 + +## 4. 移动端形态 + +- 移动端不存在"桌面层自由摆放"的交互,组件以**组件面板**(可滚动卡片流/网格)呈现。 +- 复用同一批组件控件与 Appearance 主题资源,仅替换容器布局。 +- 单窗口生命周期(`ISingleViewApplicationLifetime`)。 + +## 5. 明确舍弃(移动端不提供) + +- 电源管理(PowerManagementService) +- 桌面层嵌入 / 点击穿透(MainWindowDesktopLayerService、WindowPassthroughService) +- Office 互操作(MudTools.OfficeInterop.*) +- Harmony 运行时补丁(Platform/Windows/Patches,仅随 Windows head 编译) +- AirApp 独立进程运行时 + +## 6. 构建与 CI + +- 桌面:`dotnet build LanMountainDesktop.slnx`(Windows/Linux/macOS 三平台 CI)。 +- Android:`dotnet build LanMountainDesktop.Mobile.Android/...csproj`(CI `build-android` job, + 需要 `dotnet workload install android`)。 +- 共享层新增依赖时,必须确认该包支持 `net10.0-android`,否则放入 Platform 层。 + +## 7. 相关文档 + +- 共享库平台依赖审计报告:`docs/cross-platform-shared-audit.md` +- 架构总览:`docs/ARCHITECTURE.md` diff --git a/docs/cross-platform-shared-audit.md b/docs/cross-platform-shared-audit.md new file mode 100644 index 0000000..73a837b --- /dev/null +++ b/docs/cross-platform-shared-audit.md @@ -0,0 +1,55 @@ +# 共享库跨平台依赖审计报告 + +> 审计目标:确认以下共享库可被 Android head(net10.0-android)安全引用。 +> 审计方法:源码级扫描 `DllImport`/`LibraryImport`/`Microsoft.Win32`/`System.Drawing`/`WindowsRuntime`/`System.Management` + csproj 包引用检查。 + +## 结论总览 + +| 项目 | 状态 | 说明 | +|---|---|---| +| LanMountainDesktop.Shared.Contracts | ✅ 干净 | 仅引用 Avalonia,无平台 API | +| LanMountainDesktop.Shared.IPC | ⚠️ 可编译,运行时受限 | 依赖 dotnetCampus.Ipc(命名管道)。Android 上可编译但命名管道不可用,移动端必须走进程内传输 | +| LanMountainDesktop.Settings.Core | ✅ 干净 | 无包引用,纯托管 | +| LanMountainDesktop.Appearance | ✅ 干净 | 无包引用,纯托管 | +| LanMountainDesktop.DesktopComponents.Runtime | ✅ 干净 | 无包引用,纯托管 | +| LanMountainDesktop.PluginSdk | ⚠️ 需验证 | 引用 FluentAvaloniaUI / FluentIcons.Avalonia(ExcludeAssets=runtime,仅编译期)与 dotnetCampus.Ipc;Android 引用时需实测还原 | +| LanMountainDesktop.Host.Abstractions | ✅ 干净 | 无包引用 | + +## 详细发现 + +### 1. 源码扫描 + +7 个项目的 `.cs` 源码中均未发现: +- `DllImport` / `LibraryImport`(P/Invoke) +- `Microsoft.Win32`(注册表) +- `System.Drawing` +- `System.Runtime.WindowsRuntime` +- `System.Management` + +平台专属代码全部集中在主工程 `LanMountainDesktop\`(9 个服务文件,正在迁往 +`LanMountainDesktop.Platform.Windows`)。 + +### 2. dotnetCampus.Ipc(Shared.IPC、PluginSdk) + +- 编译期:跨平台 net 库,Android 目标可编译。 +- 运行期:传输基于命名管道,Android 不可用。 +- 处置:移动端一律使用 PluginIsolation 的进程内传输(InProc transport); + Shared.IPC 在移动端应仅作为契约/类型来源,不建立管道连接。 +- 后续(可选):若希望编译期硬隔离,可把"管道传输"从 Shared.IPC 拆到 + Shared.IPC.Pipes 子项目,移动端不引用。当前不阻塞。 + +### 3. FluentAvaloniaUI / FluentIcons.Avalonia(PluginSdk) + +- `ExcludeAssets="runtime"`,仅编译期类型引用。 +- 两者均为纯托管 Avalonia 库,预期可在 Android 还原;由 Mobile head 构建实测确认。 + +### 4. 面向未来移动 AOT 的裁剪风险(仅报告,不修复) + +- 主工程使用 YamlDotNet、反射式配置读取——不在本次 7 个共享库范围内, + 但如后续把相关逻辑下沉共享层,需加 `DynamicallyAccessedMembers` 注解或源生成序列化。 +- Settings.Core 若存在基于 `Activator.CreateInstance` 的设置实例化路径, + 迁移到移动端 AOT(未来 iOS)前需要审计;Android(JIT/interp 可用)不阻塞。 + +## 修复记录 + +本次审计未发现需要修改共享库源码的问题,未做代码改动。